Commit 898f1d23 by HoMeTown

feat: 推荐问题

parent e60b07fe
<?xml version="1.0" encoding="UTF-8"?>
<svg width="14px" height="14px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>蒙版</title>
<defs>
<rect id="path-1" x="0" y="0" width="24" height="24"></rect>
</defs>
<g id="晓得---PC端页面-草稿" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="晓得-PC端---当前对话提问" transform="translate(-1446.000000, -972.000000)">
<g id="输入框" transform="translate(498.000000, 948.000000)">
<g id="发送-点击" transform="translate(940.000000, 16.000000)">
<g id="编组-9" transform="translate(8.000000, 8.000000)">
<mask id="mask-2" fill="white">
<use xlink:href="#path-1"></use>
</mask>
<g id="蒙版"></g>
<path d="M6.11031748,17.8599937 C6.89136606,18.6410423 6.89136606,19.9073723 6.11031748,20.6884209 L5.16750844,21.6312299 C4.38645986,22.4122785 3.1201299,22.4122785 2.33908131,21.6312299 C1.55803273,20.8501813 1.55803273,19.5838514 2.33908131,18.8028028 L3.28189036,17.8599937 C4.06293894,17.0789452 5.3292689,17.0789452 6.11031748,17.8599937 Z M18.5659347,2.66666667 C20.0386941,2.66666667 21.2326014,3.860574 21.2326014,5.33333333 L21.2326014,16.6666667 C21.2326014,17.7712362 20.3371709,18.6666667 19.2326014,18.6666667 C18.1280319,18.6666667 17.2326014,17.7712362 17.2326014,16.6666667 L17.2322949,9.56566667 L11.0531265,15.7456118 C10.2720779,16.5266604 9.00574798,16.5266604 8.2246994,15.7456118 C7.44365081,14.9645632 7.44365081,13.6982333 8.2246994,12.9171847 L14.4742949,6.66666667 L7.23260141,6.66666667 C6.12803191,6.66666667 5.23260141,5.77123617 5.23260141,4.66666667 C5.23260141,3.56209717 6.12803191,2.66666667 7.23260141,2.66666667 L18.5659347,2.66666667 Z" id="形状结合" fill="#666" mask="url(#mask-2)"></path>
</g>
</g>
</g>
</g>
</g>
</svg>
...@@ -146,7 +146,7 @@ export const Chat: React.FC = () => { ...@@ -146,7 +146,7 @@ export const Chat: React.FC = () => {
<div className="w-full chatItem max-w-[1000px] mx-auto" key={index}> <div className="w-full chatItem max-w-[1000px] mx-auto" key={index}>
{record.role === 'system' && <ChatWelcome />} {record.role === 'system' && <ChatWelcome />}
{record.role === 'user' && <ChatItemUser record={record} />} {record.role === 'user' && <ChatItemUser record={record} />}
{record.role === 'ai' && <ChatAnswerBox isLastAnswer={index === allItems.length - 1} showIndex={0} record={record} index={index} />} {record.role === 'ai' && <ChatAnswerBox onSubmitQuestion={handleSubmitQuestion} isLastAnswer={index === allItems.length - 1} showIndex={0} record={record} index={index} />}
</div> </div>
))} ))}
</div> </div>
......
import { Button } from '@nextui-org/react' import { Link } from '@nextui-org/react'
import type { Answer } from '@/types/chat' import type { Answer } from '@/types/chat'
import AnswerProDetailIcon from '@/assets/svg/answerProDetail.svg?react' import AnswerProDetailIcon from '@/assets/svg/answerProDetail.svg?react'
import LinkIcon from '@/assets/svg/link.svg?react'
interface ChatAnswerAttachmentProps { interface ChatAnswerAttachmentProps {
answer: Answer answer: Answer
} }
export const ChatAnswerAttachment: React.FC<ChatAnswerAttachmentProps> = ({ answer }) => { export const ChatAnswerAttachment: React.FC<ChatAnswerAttachmentProps> = ({ answer }) => {
const handleReferenceLink = (link: string) => {
window.open(link)
}
return ( return (
<div className="attachmentList flex flex-col gap-[20px] mt-[20px]"> <div className="attachmentList flex flex-col gap-[20px] mt-[20px]">
{answer.attachmentList && answer.attachmentList.map((attachment, index) => ( {answer.attachmentList && answer.attachmentList.map((attachment, index) => (
...@@ -29,19 +25,19 @@ export const ChatAnswerAttachment: React.FC<ChatAnswerAttachmentProps> = ({ answ ...@@ -29,19 +25,19 @@ export const ChatAnswerAttachment: React.FC<ChatAnswerAttachmentProps> = ({ answ
{/* 附件:引用文件 */} {/* 附件:引用文件 */}
{attachment.type === 'reference' && ( {attachment.type === 'reference' && (
<div> <div>
<p className="text-[14px] text-[#8D9795] mb-[12px] pl-[14px]"> <p className="text-[14px] text-[#8D9795] mb-[12px]">
已为您找到 已为您找到
{attachment.content.docList.length} {attachment.content.docList.length}
篇资料作为参考 篇资料作为参考
</p> </p>
<div className="flex flex-col gap-[12px]"> <div className="flex flex-col gap-[9px]">
{ attachment.content.docList.map(doc => ( { attachment.content.docList.map((doc, docIdx) => (
<Button onClick={() => handleReferenceLink(doc.docId)} key={doc.docId} color="primary" variant="light" className="text-left bg-[#F6F6F8] w-fit text-[#333] rounded-[8px] data-[hover=true]:bg-[#E5F6FF] data-[hover=true]:text-primary"> <Link size="sm" key={doc.docId} isExternal href={doc.docId} showAnchorIcon underline="hover">
<LinkIcon /> {docIdx + 1}
<div className="w-[150px] sm:w-full text-nowrap text-ellipsis overflow-hidden"> .
{' '}
{doc.docName} {doc.docName}
</div> </Link>
</Button>
))} ))}
</div> </div>
</div> </div>
......
import { Avatar, Spinner } from '@nextui-org/react' import { Avatar, Spinner } from '@nextui-org/react'
import { motion } from 'framer-motion' import { motion } from 'framer-motion'
import { useState } from 'react'
import { ChatAnswerShower } from './ChatAnswerShower' import { ChatAnswerShower } from './ChatAnswerShower'
import { ChatAnswerParser } from './ChatAnswerParser' import { ChatAnswerParser } from './ChatAnswerParser'
import type { ChatRecord } from '@/types/chat' import { ChatAnswerRecommend } from './ChatAnswerRecommend'
import type { Answer, ChatRecord } from '@/types/chat'
import AvatarBot from '@/assets/avatarBot.png' import AvatarBot from '@/assets/avatarBot.png'
interface ChatAnswerBoxProps { interface ChatAnswerBoxProps {
...@@ -10,9 +12,16 @@ interface ChatAnswerBoxProps { ...@@ -10,9 +12,16 @@ interface ChatAnswerBoxProps {
showIndex: number showIndex: number
isLastAnswer: boolean isLastAnswer: boolean
index: number index: number
onSubmitQuestion: (question: string) => void
} }
export const ChatAnswerBox: React.FC<ChatAnswerBoxProps> = ({ record, showIndex }) => { export const ChatAnswerBox: React.FC<ChatAnswerBoxProps> = ({ record, showIndex, isLastAnswer, onSubmitQuestion }) => {
const [isShowRecommend, setIsShowRecommend] = useState(false)
const [recommendUseAnswer, setRecommendUseAnswer] = useState<Answer>()
const handleComplate = (answer: Answer) => {
setIsShowRecommend(true)
setRecommendUseAnswer(answer)
}
return ( return (
<div> <div>
{record.answerList.map((item, index) => { {record.answerList.map((item, index) => {
...@@ -27,7 +36,7 @@ export const ChatAnswerBox: React.FC<ChatAnswerBoxProps> = ({ record, showIndex ...@@ -27,7 +36,7 @@ export const ChatAnswerBox: React.FC<ChatAnswerBoxProps> = ({ record, showIndex
{item.answer && ( {item.answer && (
<div className="content"> <div className="content">
{item.isShow && <ChatAnswerShower answer={item} />} {item.isShow && <ChatAnswerShower answer={item} />}
{!item.isShow && <ChatAnswerParser answer={item} />} {!item.isShow && <ChatAnswerParser onComplate={() => handleComplate(item)} answer={item} />}
</div> </div>
)} )}
{!item.answer && ( {!item.answer && (
...@@ -36,6 +45,7 @@ export const ChatAnswerBox: React.FC<ChatAnswerBoxProps> = ({ record, showIndex ...@@ -36,6 +45,7 @@ export const ChatAnswerBox: React.FC<ChatAnswerBoxProps> = ({ record, showIndex
</motion.div> </motion.div>
<div className="w-[130px]"></div> <div className="w-[130px]"></div>
</div> </div>
{isLastAnswer && isShowRecommend && recommendUseAnswer && <ChatAnswerRecommend onSubmitQuestion={onSubmitQuestion} answer={recommendUseAnswer} />}
<div className="h-[32px] w-full"></div> <div className="h-[32px] w-full"></div>
</div> </div>
) )
......
...@@ -5,22 +5,25 @@ import rehypeSanitize from 'rehype-sanitize' ...@@ -5,22 +5,25 @@ import rehypeSanitize from 'rehype-sanitize'
import remarkGfm from 'remark-gfm' import remarkGfm from 'remark-gfm'
import { ChatAnswerAttachment } from './ChatAnswerAttchment' import { ChatAnswerAttachment } from './ChatAnswerAttchment'
import { ChatAnswerOperate } from './ChatAnswerOperate' import { ChatAnswerOperate } from './ChatAnswerOperate'
import { formatMarkdown } from './markdownFormatter'
import type { Answer } from '@/types/chat' import type { Answer } from '@/types/chat'
interface ChatAnswerParserProps { interface ChatAnswerParserProps {
answer: Answer answer: Answer
onComplate: () => void
} }
export const ChatAnswerParser: React.FC<ChatAnswerParserProps> = ({ answer }) => { export const ChatAnswerParser: React.FC<ChatAnswerParserProps> = ({ onComplate, answer }) => {
const formatAnswer = formatMarkdown(answer.answer || '')
const [displayedText, setDisplayedText] = useState('') const [displayedText, setDisplayedText] = useState('')
const [currentIndex, setCurrentIndex] = useState(0) const [currentIndex, setCurrentIndex] = useState(0)
const [isTyping, setIsTyping] = useState(false) const [isTyping, setIsTyping] = useState(false)
useEffect(() => { useEffect(() => {
setIsTyping(true) setIsTyping(true)
if (currentIndex < answer.answer.length) { if (currentIndex < formatAnswer.length) {
const timer = setTimeout(() => { const timer = setTimeout(() => {
setDisplayedText(answer.answer.slice(0, currentIndex + 1)) setDisplayedText(formatAnswer.slice(0, currentIndex + 1))
setCurrentIndex(prevIndex => prevIndex + 1) setCurrentIndex(prevIndex => prevIndex + 1)
}, 10) // 调整此值以改变打字速度 }, 10) // 调整此值以改变打字速度
...@@ -28,6 +31,7 @@ export const ChatAnswerParser: React.FC<ChatAnswerParserProps> = ({ answer }) => ...@@ -28,6 +31,7 @@ export const ChatAnswerParser: React.FC<ChatAnswerParserProps> = ({ answer }) =>
} }
else { else {
setIsTyping(false) setIsTyping(false)
onComplate()
} }
}, [answer, currentIndex]) }, [answer, currentIndex])
......
import { useEffect, useState } from 'react'
import { Button, Skeleton } from '@nextui-org/react'
import type { Answer } from '@/types/chat'
import { fetchQueryRecommendQuestion } from '@/api/chat'
import SendIcon from '@/assets/svg/sendBlack.svg?react'
interface ChatAnswerRecommendProps {
answer: Answer
onSubmitQuestion: (question: string) => void
}
export const ChatAnswerRecommend: React.FC<ChatAnswerRecommendProps> = ({ answer, onSubmitQuestion }) => {
const [questionList, setQuestionList] = useState<string[]>([])
const getAnswerRecommend = async () => {
const res = await fetchQueryRecommendQuestion(answer.conversationId || '', answer.recordId || '')
if (res.ok) {
setQuestionList(res.data.questionList)
}
}
useEffect(() => {
getAnswerRecommend()
})
return (
<div className="pl-[62px] mt-[12px] flex flex-col">
{questionList.length !== 0 && (
<div className="flex flex-col gap-[8px]">
{
questionList.map((item, index) => (
<Button onClick={() => onSubmitQuestion(item)} key={index} color="primary" variant="light" className="text-left bg-[#fff] w-fit text-[#333] rounded-[8px] data-[hover=true]:bg-[#F6F6F8] data-[hover=true]:text-[#333]">
<div className="w-[150px] sm:w-full text-nowrap text-ellipsis overflow-hidden">
{item}
</div>
<SendIcon />
</Button>
))
}
</div>
)}
{
questionList.length === 0 && (
<div className="flex flex-col gap-[8px]">
<Skeleton className="w-[300px] rounded-lg">
<div className="h-[40px] w-full rounded-lg bg-[#fff]"></div>
</Skeleton>
<Skeleton className="w-[300px] rounded-lg">
<div className="h-[40px] w-full rounded-lg bg-[#fff]"></div>
</Skeleton>
</div>
)
}
</div>
)
}
...@@ -22,6 +22,7 @@ export interface Answer { ...@@ -22,6 +22,7 @@ export interface Answer {
groupId?: string groupId?: string
question?: string question?: string
recordId?: string recordId?: string
conversationId?: string
terminateFlag?: boolean terminateFlag?: boolean
toolName?: string toolName?: string
attachmentList: Attachment[] attachmentList: Attachment[]
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment