Commit e71e99cf by HoMeTown

feat: 最大对话轮数处理

parent b065bf04
...@@ -92,6 +92,7 @@ export default defineConfig({ ...@@ -92,6 +92,7 @@ export default defineConfig({
pathRewrite: { [`^${apiUrl}`]: '' }, pathRewrite: { [`^${apiUrl}`]: '' },
}, },
}, },
compress: false,
}, },
security: { security: {
// 与 Web 安全有关的选项 // 与 Web 安全有关的选项
......
...@@ -63,8 +63,12 @@ export function fetchStreamResponse(url: string, body: Record<string, any>, onMe ...@@ -63,8 +63,12 @@ export function fetchStreamResponse(url: string, body: Record<string, any>, onMe
method: 'POST', method: 'POST',
body: JSON.stringify(body), body: JSON.stringify(body),
}) })
.then(response => response.body?.getReader()) .then((response) => {
.then(reader => processMessage(reader)) return response.body?.getReader()
})
.then((reader) => {
return processMessage(reader)
})
.catch(error => onMessage({ .catch(error => onMessage({
type: 'ERROR', type: 'ERROR',
content: error, content: error,
......
...@@ -3,6 +3,7 @@ import { useParams } from 'react-router-dom' ...@@ -3,6 +3,7 @@ import { useParams } from 'react-router-dom'
import { Button } from '@nextui-org/react' import { Button } from '@nextui-org/react'
import { motion } from 'framer-motion' import { motion } from 'framer-motion'
import { useScroll } from 'ahooks' import { useScroll } from 'ahooks'
import { toast } from 'react-hot-toast'
import styles from './Chat.module.less' import styles from './Chat.module.less'
import { ChatSlogan } from './components/ChatSlogan' import { ChatSlogan } from './components/ChatSlogan'
import { ChatMaskBar } from './components/ChatMaskBar' import { ChatMaskBar } from './components/ChatMaskBar'
...@@ -15,12 +16,13 @@ import { ChatEditor } from '@/components/ChatEditor' ...@@ -15,12 +16,13 @@ import { ChatEditor } from '@/components/ChatEditor'
import type { ChatRecord } from '@/types/chat' import type { ChatRecord } from '@/types/chat'
import { fetchUserQaRecordPage } from '@/api/conversation' import { fetchUserQaRecordPage } from '@/api/conversation'
import { fetchCheckTokenApi, fetchStreamResponse } from '@/api/chat' import { fetchCheckTokenApi, fetchStreamResponse } from '@/api/chat'
import { clearShouldSendQuestion, fetchConversations } from '@/store/conversationSlice' import { clearShouldSendQuestion, createConversation, fetchConversations } from '@/store/conversationSlice'
import type { RootState } from '@/store' import type { RootState } from '@/store'
import { useAppDispatch, useAppSelector } from '@/store/hook' import { useAppDispatch, useAppSelector } from '@/store/hook'
import ScrollBtoIcon from '@/assets/svg/scrollBto.svg?react' import ScrollBtoIcon from '@/assets/svg/scrollBto.svg?react'
import { setIsAsking } from '@/store/chatSlice' import { setIsAsking } from '@/store/chatSlice'
import SdreamLoading from '@/components/SdreamLoading' import SdreamLoading from '@/components/SdreamLoading'
import AddNewChat from '@/assets/svg/addNewChat.svg?react'
export const Chat: React.FC = () => { export const Chat: React.FC = () => {
let ignore = false let ignore = false
...@@ -68,7 +70,7 @@ export const Chat: React.FC = () => { ...@@ -68,7 +70,7 @@ export const Chat: React.FC = () => {
productCode, productCode,
}, },
(msg) => { (msg) => {
if (msg.type === 'DATA') { if (msg?.type === 'DATA' && msg?.content?.code === '00000000') {
setAllItems((prevItems) => { setAllItems((prevItems) => {
const newItems = [...prevItems] // 创建数组的浅拷贝 const newItems = [...prevItems] // 创建数组的浅拷贝
const lastIndex = newItems.length - 1 const lastIndex = newItems.length - 1
...@@ -89,6 +91,38 @@ export const Chat: React.FC = () => { ...@@ -89,6 +91,38 @@ export const Chat: React.FC = () => {
return newItems return newItems
}) })
} }
if (msg?.type === 'DATA' && msg?.content?.code === '01010005') {
// showToast('已超过会话支持的轮数上线!', 'error')
toast(t => (
<div className="flex items-center">
<p className="text-[14px]">⚠️ 超过最大轮数上线!请新建对话 👉🏻</p>
<Button
color="primary"
size="sm"
variant="light"
isIconOnly
onClick={() => {
dispatch(createConversation({
conversationData: {},
shouldNavigate: true,
shouldSendQuestion: '',
}))
toast.dismiss(t.id)
}}
>
<AddNewChat />
</Button>
</div>
), {
position: 'bottom-center',
duration: 0,
style: {
// transform: 'translateY(-400px)',
marginBottom: '120px',
},
})
return
}
if (msg.type === 'END') { if (msg.type === 'END') {
if (isNew) { if (isNew) {
setTimeout(() => { setTimeout(() => {
......
...@@ -38,8 +38,8 @@ export const ChatAnswerAttachment: React.FC<ChatAnswerAttachmentProps> = ({ from ...@@ -38,8 +38,8 @@ export const ChatAnswerAttachment: React.FC<ChatAnswerAttachmentProps> = ({ from
} }
} }
return ( return (
<div className="attachmentList flex flex-col gap-[20px]"> <div className="cardList flex flex-col gap-[20px]">
{answer.attachmentList && answer.attachmentList.map((attachment, index) => { {answer.cardList && answer.cardList.map((attachment, index) => {
if (attachment?.type) { if (attachment?.type) {
return ( return (
<div key={`${attachment.type}_${index}`}> <div key={`${attachment.type}_${index}`}>
......
...@@ -56,7 +56,7 @@ export const ChatAnswerBox: React.FC<ChatAnswerBoxProps> = ({ record, showIndex, ...@@ -56,7 +56,7 @@ export const ChatAnswerBox: React.FC<ChatAnswerBoxProps> = ({ record, showIndex,
<div <div
className="ml-[20px] bg-white rounded-[20px] box-border px-[16px] py-[12px] sm:px-[24px] sm:py-[20px]" className="ml-[20px] bg-white rounded-[20px] box-border px-[16px] py-[12px] sm:px-[24px] sm:py-[20px]"
> >
{(item.answer?.length || item.attachmentList?.length) {(item.answer?.length || item.cardList?.length)
? ( ? (
<div className="content"> <div className="content">
{item.isShow && <ChatAnswerShower onSubmitQuestion={onSubmitQuestion} isLastAnswer={isLastAnswer} answer={item} />} {item.isShow && <ChatAnswerShower onSubmitQuestion={onSubmitQuestion} isLastAnswer={isLastAnswer} answer={item} />}
......
import React, { useEffect, useState } from 'react' import React, { useEffect, useState } from 'react'
import { Chip } from '@nextui-org/react'
import { ChatAnswerAttachment } from './ChatAnswerAttchment' import { ChatAnswerAttachment } from './ChatAnswerAttchment'
import { ChatAnswerOperate } from './ChatAnswerOperate' import { ChatAnswerOperate } from './ChatAnswerOperate'
import { formatMarkdown } from './markdownFormatter' import { formatMarkdown } from './markdownFormatter'
...@@ -16,6 +17,24 @@ interface ChatAnswerParserProps { ...@@ -16,6 +17,24 @@ interface ChatAnswerParserProps {
onSubmitQuestion: (question: string, productCode?: string) => void onSubmitQuestion: (question: string, productCode?: string) => void
} }
function CheckIcon({ size, height, width, ...props }) {
return (
<svg
fill="none"
height={size || height || 24}
viewBox="0 0 24 24"
width={size || width || 24}
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="M12 2C6.49 2 2 6.49 2 12C2 17.51 6.49 22 12 22C17.51 22 22 17.51 22 12C22 6.49 17.51 2 12 2ZM16.78 9.7L11.11 15.37C10.97 15.51 10.78 15.59 10.58 15.59C10.38 15.59 10.19 15.51 10.05 15.37L7.22 12.54C6.93 12.25 6.93 11.77 7.22 11.48C7.51 11.19 7.99 11.19 8.28 11.48L10.58 13.78L15.72 8.64C16.01 8.35 16.49 8.35 16.78 8.64C17.07 8.93 17.07 9.4 16.78 9.7Z"
fill="currentColor"
/>
</svg>
)
}
export const ChatAnswerParser: React.FC<ChatAnswerParserProps> = ({ isLastAnswer, onTyping, onComplate, answer, isStopTyping, onSubmitQuestion }) => { export const ChatAnswerParser: React.FC<ChatAnswerParserProps> = ({ isLastAnswer, onTyping, onComplate, answer, isStopTyping, onSubmitQuestion }) => {
const formatAnswer = formatMarkdown(answer.answer || '') const formatAnswer = formatMarkdown(answer.answer || '')
const [displayedText, setDisplayedText] = useState('') const [displayedText, setDisplayedText] = useState('')
...@@ -124,21 +143,36 @@ export const ChatAnswerParser: React.FC<ChatAnswerParserProps> = ({ isLastAnswer ...@@ -124,21 +143,36 @@ export const ChatAnswerParser: React.FC<ChatAnswerParserProps> = ({ isLastAnswer
}, [isStopTyping]) }, [isStopTyping])
useEffect(() => { useEffect(() => {
setHideOperate((answer.attachmentList || []).some(attachment => attachment?.type === 'box' || attachment?.type?.includes('card-'))) setHideOperate((answer.cardList || []).some(attachment => attachment?.type === 'box' || attachment?.type?.includes('card-')))
}, [answer.attachmentList]) }, [answer.cardList])
return ( return (
<div className="answerParser"> <div className="answerParser">
<div className="mb-[8px]">
{/* <Chip color="primary" className="mb-[12px]">{answer.step?.message}</Chip> */}
{ answer.step?.step === 'answering' && (
<Chip color="warning" variant="flat">
{answer.step?.message}
</Chip>
)}
{answer.step?.step === 'finished' && (
<Chip color="primary" variant="flat" startContent={<CheckIcon size={18} />}>
{answer.step?.message}
</Chip>
)}
</div>
{!!displayedText.length && ( {!!displayedText.length && (
<div className={answer.attachmentList?.length ? 'mb-[20px]' : ''}> <div className={answer.cardList?.length ? 'mb-[20px]' : ''}>
<MarkdownDetail> <MarkdownDetail>
{displayedText} {displayedText}
</MarkdownDetail> </MarkdownDetail>
</div> </div>
)} )}
{!isTyping {!isTyping
&& answer.attachmentList && answer.cardList
&& answer.attachmentList?.length !== 0 && answer.cardList?.length !== 0
&& <ChatAnswerAttachment fromParser isLastAnswer={isLastAnswer} onSubmitQuestion={onSubmitQuestion} answer={answer} />} && <ChatAnswerAttachment fromParser isLastAnswer={isLastAnswer} onSubmitQuestion={onSubmitQuestion} answer={answer} />}
{!isTyping && !hideOperate && <ChatAnswerOperate answer={answer} />} {!isTyping && !hideOperate && <ChatAnswerOperate answer={answer} />}
......
...@@ -11,11 +11,14 @@ interface ChatAnswerRecommendProps { ...@@ -11,11 +11,14 @@ interface ChatAnswerRecommendProps {
export const ChatAnswerRecommend: React.FC<ChatAnswerRecommendProps> = ({ answer, onSubmitQuestion }) => { export const ChatAnswerRecommend: React.FC<ChatAnswerRecommendProps> = ({ answer, onSubmitQuestion }) => {
let isGet = false let isGet = false
const [questionList, setQuestionList] = useState<string[]>([]) const [questionList, setQuestionList] = useState<string[]>([])
const [loading, setLoading] = useState<boolean>(false)
const getAnswerRecommend = async () => { const getAnswerRecommend = async () => {
setLoading(true)
const res = await fetchQueryRecommendQuestion(answer.conversationId || '', answer.recordId || '') const res = await fetchQueryRecommendQuestion(answer.conversationId || '', answer.recordId || '')
if (res.ok) { if (res.ok) {
setQuestionList(res.data.questionList) setQuestionList(res.data.questionList)
} }
setLoading(false)
} }
useEffect(() => { useEffect(() => {
...@@ -26,7 +29,7 @@ export const ChatAnswerRecommend: React.FC<ChatAnswerRecommendProps> = ({ answer ...@@ -26,7 +29,7 @@ export const ChatAnswerRecommend: React.FC<ChatAnswerRecommendProps> = ({ answer
}, []) }, [])
return ( return (
<div className="pl-[62px] mt-[12px] flex flex-col"> <div className="pl-[62px] mt-[12px] flex flex-col">
{questionList.length !== 0 && ( {!loading && questionList.length !== 0 && (
<div className="flex flex-col gap-[8px]"> <div className="flex flex-col gap-[8px]">
{ {
questionList.map((item, index) => ( questionList.map((item, index) => (
...@@ -41,7 +44,7 @@ export const ChatAnswerRecommend: React.FC<ChatAnswerRecommendProps> = ({ answer ...@@ -41,7 +44,7 @@ export const ChatAnswerRecommend: React.FC<ChatAnswerRecommendProps> = ({ answer
</div> </div>
)} )}
{ {
questionList.length === 0 && ( loading && questionList.length === 0 && (
<div className="flex flex-col gap-[8px]"> <div className="flex flex-col gap-[8px]">
<Skeleton className="w-full sm:w-[300px] rounded-lg"> <Skeleton className="w-full sm:w-[300px] rounded-lg">
<div className="h-[40px] w-full rounded-lg bg-[#fff]"></div> <div className="h-[40px] w-full rounded-lg bg-[#fff]"></div>
......
...@@ -11,17 +11,17 @@ interface ChatAnswerShowerProps { ...@@ -11,17 +11,17 @@ interface ChatAnswerShowerProps {
} }
export const ChatAnswerShower: React.FC<ChatAnswerShowerProps> = ({ answer, isLastAnswer, onSubmitQuestion }) => { export const ChatAnswerShower: React.FC<ChatAnswerShowerProps> = ({ answer, isLastAnswer, onSubmitQuestion }) => {
const hideOperate = (answer.attachmentList || []).some(attachment => attachment.type === 'box' || attachment?.type?.includes('card-')) const hideOperate = (answer.cardList || []).some(attachment => attachment.type === 'box' || attachment?.type?.includes('card-'))
return ( return (
<div className="answerShower"> <div className="answerShower">
{answer.answer && ( {answer.answer && (
<div className={answer.attachmentList?.length ? 'sm:mb-[20px]' : ''}> <div className={answer.cardList?.length ? 'sm:mb-[20px]' : ''}>
<MarkdownDetail> <MarkdownDetail>
{formatMarkdown(answer.answer || '')} {formatMarkdown(answer.answer || '')}
</MarkdownDetail> </MarkdownDetail>
</div> </div>
)} )}
{answer.attachmentList && answer.attachmentList?.length !== 0 && <ChatAnswerAttachment onSubmitQuestion={onSubmitQuestion} isLastAnswer={isLastAnswer} answer={answer} />} {answer.cardList && answer.cardList?.length !== 0 && <ChatAnswerAttachment onSubmitQuestion={onSubmitQuestion} isLastAnswer={isLastAnswer} answer={answer} />}
{/* {} */} {/* {} */}
{!hideOperate && <ChatAnswerOperate answer={answer} />} {!hideOperate && <ChatAnswerOperate answer={answer} />}
</div> </div>
......
...@@ -131,7 +131,7 @@ export const Collect: React.FC = () => { ...@@ -131,7 +131,7 @@ export const Collect: React.FC = () => {
<MarkdownDetail> <MarkdownDetail>
{formatMarkdown(item.answer || '')} {formatMarkdown(item.answer || '')}
</MarkdownDetail> </MarkdownDetail>
{item.attachmentList && item.attachmentList?.length !== 0 && <ChatAnswerAttachment answer={item} />} {item.cardList && item.cardList?.length !== 0 && <ChatAnswerAttachment answer={item} />}
<div className="mt-[12px] flex gap-[4px] justify-between items-center"> <div className="mt-[12px] flex gap-[4px] justify-between items-center">
<div className="text-12px text-[#B2B8C1]"> <div className="text-12px text-[#B2B8C1]">
{item.collectionTime} {item.collectionTime}
......
...@@ -22,6 +22,11 @@ export interface Attachment { ...@@ -22,6 +22,11 @@ export interface Attachment {
content: AttachmentContent content: AttachmentContent
} }
interface AnswerStep {
step: string
message: string
}
export interface Answer { export interface Answer {
endAnswerFlag?: boolean endAnswerFlag?: boolean
isStopTyping?: boolean isStopTyping?: boolean
...@@ -35,7 +40,8 @@ export interface Answer { ...@@ -35,7 +40,8 @@ export interface Answer {
conversationId?: string conversationId?: string
terminateFlag?: boolean terminateFlag?: boolean
toolName?: string toolName?: string
attachmentList: Attachment[] cardList: Attachment[]
step?: AnswerStep
} }
export interface OriginalRecord { export interface OriginalRecord {
......
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