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 = () => {
<div className="w-full chatItem max-w-[1000px] mx-auto" key={index}>
{record.role === 'system' && <ChatWelcome />}
{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>
......
import { Button } from '@nextui-org/react'
import { Link } from '@nextui-org/react'
import type { Answer } from '@/types/chat'
import AnswerProDetailIcon from '@/assets/svg/answerProDetail.svg?react'
import LinkIcon from '@/assets/svg/link.svg?react'
interface ChatAnswerAttachmentProps {
answer: Answer
}
export const ChatAnswerAttachment: React.FC<ChatAnswerAttachmentProps> = ({ answer }) => {
const handleReferenceLink = (link: string) => {
window.open(link)
}
return (
<div className="attachmentList flex flex-col gap-[20px] mt-[20px]">
{answer.attachmentList && answer.attachmentList.map((attachment, index) => (
......@@ -29,19 +25,19 @@ export const ChatAnswerAttachment: React.FC<ChatAnswerAttachmentProps> = ({ answ
{/* 附件:引用文件 */}
{attachment.type === 'reference' && (
<div>
<p className="text-[14px] text-[#8D9795] mb-[12px] pl-[14px]">
<p className="text-[14px] text-[#8D9795] mb-[12px]">
已为您找到
{attachment.content.docList.length}
篇资料作为参考
篇资料作为参考
</p>
<div className="flex flex-col gap-[12px]">
{ attachment.content.docList.map(doc => (
<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">
<LinkIcon />
<div className="w-[150px] sm:w-full text-nowrap text-ellipsis overflow-hidden">
{doc.docName}
</div>
</Button>
<div className="flex flex-col gap-[9px]">
{ attachment.content.docList.map((doc, docIdx) => (
<Link size="sm" key={doc.docId} isExternal href={doc.docId} showAnchorIcon underline="hover">
{docIdx + 1}
.
{' '}
{doc.docName}
</Link>
))}
</div>
</div>
......
import { Avatar, Spinner } from '@nextui-org/react'
import { motion } from 'framer-motion'
import { useState } from 'react'
import { ChatAnswerShower } from './ChatAnswerShower'
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'
interface ChatAnswerBoxProps {
......@@ -10,9 +12,16 @@ interface ChatAnswerBoxProps {
showIndex: number
isLastAnswer: boolean
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 (
<div>
{record.answerList.map((item, index) => {
......@@ -27,7 +36,7 @@ export const ChatAnswerBox: React.FC<ChatAnswerBoxProps> = ({ record, showIndex
{item.answer && (
<div className="content">
{item.isShow && <ChatAnswerShower answer={item} />}
{!item.isShow && <ChatAnswerParser answer={item} />}
{!item.isShow && <ChatAnswerParser onComplate={() => handleComplate(item)} answer={item} />}
</div>
)}
{!item.answer && (
......@@ -36,6 +45,7 @@ export const ChatAnswerBox: React.FC<ChatAnswerBoxProps> = ({ record, showIndex
</motion.div>
<div className="w-[130px]"></div>
</div>
{isLastAnswer && isShowRecommend && recommendUseAnswer && <ChatAnswerRecommend onSubmitQuestion={onSubmitQuestion} answer={recommendUseAnswer} />}
<div className="h-[32px] w-full"></div>
</div>
)
......
......@@ -5,22 +5,25 @@ import rehypeSanitize from 'rehype-sanitize'
import remarkGfm from 'remark-gfm'
import { ChatAnswerAttachment } from './ChatAnswerAttchment'
import { ChatAnswerOperate } from './ChatAnswerOperate'
import { formatMarkdown } from './markdownFormatter'
import type { Answer } from '@/types/chat'
interface ChatAnswerParserProps {
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 [currentIndex, setCurrentIndex] = useState(0)
const [isTyping, setIsTyping] = useState(false)
useEffect(() => {
setIsTyping(true)
if (currentIndex < answer.answer.length) {
if (currentIndex < formatAnswer.length) {
const timer = setTimeout(() => {
setDisplayedText(answer.answer.slice(0, currentIndex + 1))
setDisplayedText(formatAnswer.slice(0, currentIndex + 1))
setCurrentIndex(prevIndex => prevIndex + 1)
}, 10) // 调整此值以改变打字速度
......@@ -28,6 +31,7 @@ export const ChatAnswerParser: React.FC<ChatAnswerParserProps> = ({ answer }) =>
}
else {
setIsTyping(false)
onComplate()
}
}, [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 {
groupId?: string
question?: string
recordId?: string
conversationId?: string
terminateFlag?: boolean
toolName?: string
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