import styles from './index.module.less'; import { useEffect, useState, useRef, useMemo, useContext } from 'react'; import { Input, message, Tooltip } from 'antd'; import ShowRightIcon from './assets/think-progress-icon.svg'; import { MindsearchContext } from './provider/context'; import ChatRight from './components/chat-right'; import { useNavigate, useParams } from 'react-router-dom'; import { fetchEventSource } from '@microsoft/fetch-event-source'; import SessionItem from './components/session-item'; import classNames from 'classnames'; import Notice from './components/notice'; interface INodeInfo { isEnd?: boolean; // 该节点是否结束 current_node?: string; thinkingData?: string; // step1 思考 queries?: []; readingData?: string; // step2 思考 searchList?: []; conclusion?: string; // 节点的结论 selectedIds?: number[]; subQuestion?: string; // 节点的标题 conclusionRef: any[]; outputing?: boolean; }; interface IFormattedData { question?: string; nodes?: any; adjacency_list?: object; response?: string; responseRefList?: any[]; chatIsOver?: boolean; }; interface INodeItem { id: string; name: string; state: number; }; class FatalError extends Error { }; class RetriableError extends Error { }; const MindSearchCon = () => { const navigate = useNavigate(); const params = useParams<{ id: string; robotId: string }>(); const [qaList, setQaList] = useState([]); const [formatted, setFormatted] = useState({}); const [question, setQuestion] = useState(''); const [stashedQuestion, setStashedQuestion] = useState(''); const [newChatTip, setNewChatTip] = useState(false); const [singleObj, setSingleObj] = useState(null); const [isEnd, setIsEnd] = useState(false); const [inputFocused, setFocused] = useState(false); // 一轮完整对话结束 const [chatIsOver, setChatIsOver] = useState(true); const [currentNodeInfo, setCurrentNode] = useState(null); const [currentNodeName, setCurrentNodeName] = useState(''); const [activeNode, setActiveNode] = useState(''); // 是否展示右侧内容 const [showRight, setShowRight] = useState(false); const [adjList, setAdjList] = useState({}); const [historyNode, setHistoryNode] = useState(null); const [hasNewChat, setHasNewChat] = useState(false); // 新开会话 const openNewChat = () => { location.reload(); }; const toggleRight = () => { setShowRight(!showRight); }; // 渲染过程中保持渲染文字可见 const keepScrollTop = () => { const divA = document.getElementById('chatArea') as HTMLDivElement; const divB = document.getElementById('messageWindowId') as HTMLDivElement; // 获取 divB 的当前高度 const bHeight = divB.offsetHeight; // 检查 divA 是否需要滚动(即 divB 的高度是否大于 divA 的可视高度) if (bHeight > divA.offsetHeight) { // 滚动到 divB 的底部在 divA 的可视区域内 divA.scrollTop = bHeight - divA.offsetHeight + 30; } }; const initPageState = () => { setSingleObj(null); setCurrentNodeName(''); setCurrentNode(null); setFormatted({}); setAdjList({}); setShowRight(false); setIsEnd(false); }; const responseTimer: any = useRef(null); useEffect(() => { // console.log('[ms]---', formatted, chatIsOver, responseTimer.current); if (chatIsOver && formatted?.response) { // 一轮对话结束 setQaList((pre) => { return pre.concat(formatted); }); initPageState(); setCurrentNodeName('customer-0'); } if (!chatIsOver && !responseTimer.current) { responseTimer.current = setInterval(() => { keepScrollTop(); }, 50); } if (responseTimer.current && chatIsOver) { // 如果 isEnd 变为 false,清除定时器 clearInterval(responseTimer.current); responseTimer.current = null; } }, [formatted?.response, chatIsOver, responseTimer.current, newChatTip]); useEffect(() => { if (formatted?.question) { setHistoryNode(null); setChatIsOver(false); } }, [formatted?.question]); // 存储节点信息 const stashNodeInfo = (fullInfo: any, nodeName: string) => { // console.log('stash node info------', fullInfo, fullInfo?.response?.stream_state); const content = JSON.parse(fullInfo?.response?.content || '{}') || {}; const searchListStashed: any = Object.keys(content).map((item) => { return { id: item, ...content[item] }; }); const stashedList = JSON.parse(localStorage?.stashedNodes || '{}'); const nodeInfo = stashedList[nodeName] || {}; if (fullInfo?.content) { nodeInfo.subQuestion = fullInfo.content; } if (fullInfo?.response?.formatted?.thought) { // step1 思考 if (!nodeInfo?.readingData && !nodeInfo?.queries?.length) { nodeInfo.thinkingData = fullInfo?.response?.formatted?.thought; } // step2 思考 if (nodeInfo?.thinkingData && nodeInfo?.queries?.length && nodeInfo?.searchList?.length && !nodeInfo?.selectedIds?.length && !nodeInfo?.conclusion) { nodeInfo.readingData = fullInfo?.response?.formatted?.thought; } // conclusion if (nodeInfo?.startConclusion && fullInfo?.response?.stream_state === 1) { nodeInfo.conclusion = fullInfo?.response?.formatted?.thought; } } if (fullInfo?.response?.formatted?.action?.parameters?.query?.length && !nodeInfo.queries?.length) { nodeInfo.queries = fullInfo?.response?.formatted.action.parameters.query; } if (searchListStashed?.length && !nodeInfo.conclusionRef) { nodeInfo.searchList = searchListStashed; nodeInfo.conclusionRef = content; } if (Array.isArray(fullInfo?.response?.formatted?.action?.parameters?.select_ids) && !nodeInfo?.selectedIds?.length) { nodeInfo.selectedIds = fullInfo?.response?.formatted.action.parameters.select_ids; nodeInfo.startConclusion = true; } if (fullInfo?.response?.stream_state) { nodeInfo.outputing = true; } else { nodeInfo.outputing = false; } const nodesList: any = {}; nodesList[nodeName] = { current_node: nodeName, ...nodeInfo, }; window.localStorage.stashedNodes = JSON.stringify({ ...stashedList, ...nodesList }); }; const formatData = (obj: any) => { // 嫦娥6号上有哪些国际科学载荷?它们的作用分别是什么? try { // 更新邻接表 if (obj?.response?.formatted?.adjacency_list) { setAdjList(obj.response?.formatted?.adjacency_list); } if (!obj?.current_node && obj?.response?.formatted?.thought && obj?.response?.stream_state === 1) { // 有thought,没有node, planner思考过程 setFormatted((pre: IFormattedData) => { return { ...pre, response: obj.response.formatted.thought, }; }); } if (obj?.response?.formatted?.ref2url && !formatted?.responseRefList) { setFormatted((pre: IFormattedData) => { return { ...pre, responseRefList: obj?.response?.formatted?.ref2url, }; }); } if (obj?.current_node || obj?.response?.formatted?.node) { // 有node, 临时存储node信息 stashNodeInfo(obj?.response?.formatted?.node?.[obj.current_node], obj.current_node); } } catch (err) { console.log(err); } }; const handleError = (errCode: number, msg: string) => { message.warning(msg || '请求出错了,请稍后再试'); if (errCode === -20032 || errCode === -20033 || errCode === -20039) { // 敏感词校验失败, 新开会话 openNewChat(); return; } console.log('handle error------', msg); setChatIsOver(true); initPageState(); }; const startEventSource = () => { console.log('start event--------'); if (qaList?.length > 4) { setNewChatTip(true); message.warning('对话数已达上限,请在新对话中聊天'); keepScrollTop(); return; } setFormatted({ ...formatted, question }); setQuestion(''); setChatIsOver(false); const ctrl = new AbortController(); const url = '/solve'; // const queryData = { // cancel: true, // prompt: question, // }; const postData = { inputs: question } fetchEventSource(url, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(postData), openWhenHidden: true, signal: ctrl.signal, onmessage(ev) { try { const res = (ev?.data && JSON.parse(ev.data)) || null; if (res?.response?.stream_state === 0) { setChatIsOver(true); setFormatted((pre: IFormattedData) => { return { ...pre, chatIsOver: true, }; }); } else { formatData(res); setSingleObj(res); } } catch (err) { console.log('error on sse---', err); handleError(0, '请求出错了,请稍后再试!'); } }, onerror(err) { console.log('error on sse---', err); handleError(0, ''); ctrl.abort(); if (err instanceof FatalError) { throw err; } }, onclose() { // params?.id && handleUpdateHistoryItem(params?.id); } }); }; // 点击节点 const handleNodeClick = (node: string, idx: number) => { if (isEnd && !chatIsOver) return; // 当节点输出完成,最终response进行中,不允许点击按钮,点击无效 const isFromHistory = qaList?.[idx]?.nodes?.[node]; setShowRight(true); setActiveNode(node); if (isFromHistory) { const info = qaList?.[idx]?.nodes?.[node]; if (!info) { message.error('没有读取到节点信息'); } setHistoryNode(info); } else { setCurrentNodeName(node); } }; // 解析历史记录或者搜索返回的数据 const formatHistoryNode = (originNodeInfo: any) => { // console.log('format history node--------', originNodeInfo); const searchContent = JSON.parse(originNodeInfo?.memory?.[1]?.content || '{}') || {}; const searchListStashed: any = Object.keys(searchContent).map((item) => { return { id: item, ...searchContent[item] }; }); const nodeInfo: INodeInfo = { current_node: originNodeInfo?.current_node || String(Date.now()), thinkingData: originNodeInfo?.memory?.[0]?.formatted?.thought || '', // step1 思考 queries: originNodeInfo?.memory?.[0]?.formatted?.action?.parameters?.query || [], readingData: originNodeInfo?.memory?.[2]?.formatted?.thought || '', // step2 思考 searchList: searchListStashed, conclusionRef: searchContent, conclusion: originNodeInfo?.memory?.[4]?.formatted?.thought || '', // 节点的结论 selectedIds: originNodeInfo?.memory?.[2]?.formatted?.action?.parameters?.select_ids || [], subQuestion: originNodeInfo?.content, // 节点的标题 isEnd: true, outputing: false }; return nodeInfo; }; const createSseChat = () => { if (submitDisabled) { return; } setQuestion(stashedQuestion); setStashedQuestion(''); setCurrentNodeName('customer-0'); }; const checkNodesOutputFinish = () => { const adjListStr = JSON.stringify(adjList); // 服务端没有能准确描述所有节点输出完成的状态,前端从邻接表信息中寻找response信息,不保证完全准确,因为也可能不返回 if (adjListStr.includes('"name":"response"')) { setIsEnd(true); } }; useEffect(() => { if (!adjList) return; if (isEnd) { // 所有节点输出完成时收起右侧 setShowRight(false); } else { checkNodesOutputFinish(); } setFormatted((pre: IFormattedData) => { return { ...pre, adjacency_list: adjList, }; }); }, [adjList, isEnd]); useEffect(() => { const findStashNode = localStorage?.stashedNodes && JSON.parse(localStorage?.stashedNodes || '{}'); if (!findStashNode || !currentNodeName) return; currentNodeName === 'customer-0' ? setCurrentNode(null) : setCurrentNode(findStashNode?.[currentNodeName]); currentNodeName !== 'customer-0' && setShowRight(true); }, [currentNodeName, localStorage?.stashedNodes]); useEffect(() => { if (!singleObj) return; if ((!currentNodeName || currentNodeName === 'customer-0') && singleObj?.current_node) { setCurrentNodeName(singleObj?.current_node); } }, [singleObj, currentNodeName]); useEffect(() => { if (question) { startEventSource(); } }, [question]); useEffect(() => { if (!showRight) { setActiveNode(''); } }, [showRight]); useEffect(() => { localStorage.stashedNodes = ''; localStorage.reformatStashedNodes = ''; return () => { // 返回清理函数,确保组件卸载时清除定时器 if (responseTimer.current) { clearInterval(responseTimer.current); responseTimer.current = null; } }; }, []); const submitDisabled = useMemo(() => { return newChatTip || !stashedQuestion || !chatIsOver; }, [newChatTip, stashedQuestion, chatIsOver]); return (
{qaList.length > 0 && qaList.map((item: IFormattedData, idx) => { return (
{ item.question && }
); }) } { formatted?.question && }
{newChatTip && (
对话数已达上限,请在 新对话 中聊天
)}
{ setStashedQuestion(e.target.value); }} onPressEnter={createSseChat} onFocus={() => { setFocused(true) }} onBlur={() => { setFocused(false) }} />
{showRight ? ( ) : (
)}
); }; export default MindSearchCon;