Spaces:
Sleeping
Sleeping
| import styles from './index.module.less'; | |
| import { useEffect, useState, useRef, Children } from 'react'; | |
| import MindMapItem from './mindMapItem'; | |
| import PackIcon from '@/assets/pack-up.svg'; | |
| import SendIcon from '@/assets/sendIcon.svg'; | |
| import { Tooltip, Input, message } from 'antd'; | |
| import IconFont from '@/components/iconfont'; | |
| import ReactMarkdown from "react-markdown"; | |
| import ShowRightIcon from "@/assets/show-right-icon.png"; | |
| import rehypeRaw from 'rehype-raw'; | |
| import classNames from 'classnames'; | |
| import { fetchEventSource } from '@microsoft/fetch-event-source'; | |
| import { GET_SSE_DATA } from '@/config/cgi'; | |
| import { replaceStr } from '@/utils/tools'; | |
| const RenderTest = () => { | |
| let eventSource: any = null; | |
| let sseTimer: any = useRef(null); | |
| const [isWaiting, setIsWaiting] = useState(false); | |
| const [question, setQuestion] = useState(""); | |
| const [stashQuestion, setStashQuestion] = useState(""); | |
| const [isEnd, setIsEnd] = useState(false); | |
| const [showEndNode, setShowEndNode] = useState(false); | |
| // 一组节点的渲染草稿 | |
| const [draft, setDraft] = useState(''); | |
| // 一轮完整对话结束 | |
| const [chatIsOver, setChatIsOver] = useState(true); | |
| // 一组节点的思考草稿是不是打印结束 | |
| const [draftEnd, setDraftEnd] = useState(false); | |
| const [progress1, setProgress1] = useState(''); | |
| const [progress2, setProgress2] = useState(''); | |
| const [progressEnd, setProgressEnd] = useState(false); | |
| const [conclusion, setConclusion] = useState(''); | |
| const [stashConclusion, setstashConclusion] = useState(''); | |
| const [query, setQuery] = useState([]); | |
| const [searchList, setSearchList] = useState([]); | |
| // 整体的渲染树 | |
| const [renderData, setRenderData] = useState<any[]>([]); | |
| const [currentNode, setCurrentNode] = useState<any>(null); | |
| // 渲染minddata里的第几个item | |
| const [renderIndex, setRenderIndex] = useState<number>(0); | |
| const [response, setResponse] = useState(""); | |
| const [currentStep, setCurrentStep] = useState(0); | |
| // steps展开收起的信息 | |
| const [collapseInfo, setCollapseInfo] = useState([true, true]); | |
| const [mapWidth, setMapWidth] = useState(0); | |
| // 是否展示右侧内容 | |
| const [showRight, setShowRight] = useState(true); | |
| const [currentNodeRendering, setCurrentNodeRendering] = useState(false); | |
| const [selectedIds, setSelectedIds] = useState([]); | |
| const [nodeName, setNodeName] = useState(''); | |
| const hasHighlight = useRef(false); | |
| const conclusionRender = useRef(false); | |
| const nodeDraftRender = useRef(false); | |
| const [obj, setObj] = useState<any>(null); | |
| const [nodeOutputEnd, setNodeEnd] = useState(false); | |
| const [adjList, setAdjList] = useState([]); | |
| const TEXT_INTERVAL = 20; | |
| const SEARCHLIST_INTERVAL = 80; | |
| const toggleRight = () => { | |
| setShowRight(!showRight); | |
| }; | |
| const findAndUpdateStatus = (nodes: any[], targetNode: any) => { | |
| return nodes.map((node) => { | |
| if (node.state === 1 && node.id !== 0) { | |
| return { ...node, state: 3 }; | |
| } | |
| if (node.name === targetNode) { | |
| return { ...node, state: 1 }; | |
| } | |
| if (node.children) { | |
| // 递归地在子节点中查找 | |
| node.children = findAndUpdateStatus(node.children, targetNode); | |
| } | |
| return node; | |
| }); | |
| } | |
| const generateEndStyle = () => { | |
| // 获取所有class为endline的div元素 | |
| const endlineDivs = document.getElementsByClassName('endline'); | |
| const mindMap = document.getElementById("mindMap"); | |
| // 确保至少有两个元素 | |
| if (endlineDivs.length >= 2 && mindMap) { | |
| // 获取第一个和最后一个元素的边界框(bounding rectangle) | |
| const firstRect = endlineDivs[0].getBoundingClientRect(); | |
| const lastRect = endlineDivs[endlineDivs.length - 1].getBoundingClientRect(); | |
| const mindMapRect = mindMap?.getBoundingClientRect(); | |
| // 计算y值的差值 | |
| const yDiff = lastRect.top - firstRect.top; | |
| // const top = firstRect.top - mindMapRect.top; | |
| // 如果需要包含元素的完整高度(不仅仅是顶部位置),可以加上元素的高度 | |
| // const yDiffWithHeight = yDiff + (lastRect.height - firstRect.height); | |
| return { | |
| top: firstRect.top - mindMapRect.top, | |
| height: yDiff + 1 | |
| }; | |
| } else { | |
| return { | |
| top: '50%', | |
| height: 0 | |
| }; | |
| } | |
| }; | |
| const generateWidth = () => { | |
| const articles = document.querySelectorAll('article'); | |
| // 确保至少有两个元素 | |
| if (articles?.length) { | |
| let maxRight = 0; | |
| articles.forEach((item, index) => { | |
| if (item.getBoundingClientRect().right > maxRight) { | |
| maxRight = item.getBoundingClientRect().right; | |
| } | |
| }) | |
| const firstArticle = articles[0].getBoundingClientRect(); | |
| if (maxRight - firstArticle.left + 200 > mapWidth) { | |
| return maxRight - firstArticle.left + 200 | |
| } else { | |
| return mapWidth; | |
| } | |
| } else { | |
| return 100; | |
| } | |
| }; | |
| // 逐字渲染 | |
| const renderDraft = (str: string, type: string, endCallback: () => void) => { | |
| // 已经输出的字符数量 | |
| let outputIndex = 0; | |
| // 输出字符的函数 | |
| const outputText = () => { | |
| // 给出高亮后draft输出的结束标志 | |
| if (type === 'stepDraft-1' && outputIndex + 3 > str?.length) { | |
| nodeDraftRender.current = true; | |
| } | |
| // 如果还有字符未输出 | |
| if (outputIndex < str?.length) { | |
| // 获取接下来要输出的1个字符(或剩余字符,如果不足3个) | |
| let chunk = str.slice(outputIndex, Math.min(outputIndex + 10, str.length)); | |
| // 更新已输出字符的索引 | |
| outputIndex += chunk.length; | |
| if (type === 'thought') { | |
| setDraft(str.slice(0, outputIndex)); | |
| } else if (type === "stepDraft-0") { | |
| setProgress1(str.slice(0, outputIndex)); | |
| } else if (type === "stepDraft-1") { | |
| setProgress2(str.slice(0, outputIndex)); | |
| } else if (type === "conclusion") { | |
| setConclusion(str.slice(0, outputIndex)); | |
| } else if (type === "response") { | |
| setResponse(str.slice(0, outputIndex)); | |
| } | |
| } else { | |
| // 如果没有更多字符需要输出,则清除定时器 | |
| clearInterval(intervalId); | |
| endCallback && endCallback() | |
| } | |
| } | |
| // 设定定时器ID | |
| let intervalId = setInterval(outputText, TEXT_INTERVAL); | |
| } | |
| // 渲染搜索结果renderSearchList | |
| const renderSearchList = () => { | |
| let outputIndex = 0; | |
| const content = JSON.parse(currentNode.actions[currentStep].result[0].content); | |
| const arr: any = Object.keys(content).map(item => { | |
| return { id: item, ...content[item] }; | |
| }); | |
| const len = Object.keys(content).length; | |
| const outputText = () => { | |
| outputIndex++; | |
| if (outputIndex < len + 1) { | |
| setSearchList(arr.slice(0, outputIndex)); | |
| } else { | |
| clearInterval(intervalId); | |
| } | |
| }; | |
| // 设定定时器ID | |
| let intervalId = setInterval(outputText, SEARCHLIST_INTERVAL); | |
| }; | |
| // 高亮searchList | |
| const highLightSearchList = (ids: any) => { | |
| setSelectedIds([]); | |
| const newStep = currentStep + 1; | |
| setCurrentStep(newStep); | |
| const highlightArr: any = [...searchList]; | |
| highlightArr.forEach((item: any) => { | |
| if (ids.includes(Number(item.id))) { | |
| item.highLight = true; | |
| } | |
| }) | |
| highlightArr.sort((item1: any, item2: any) => { | |
| if (item1.highLight === item2.highLight) { | |
| return 0; | |
| } | |
| // 如果item1是highlight,放在前面 | |
| if (item1.highLight) { | |
| return -1; | |
| } | |
| // 如果item2是highlight,放在后面 | |
| return 1; | |
| }) | |
| setSearchList(highlightArr); | |
| renderDraft(currentNode.actions[1].thought, `stepDraft-1`, () => { }); | |
| hasHighlight.current = true; // 标记为高亮已执行 | |
| }; | |
| // 渲染结论 | |
| const renderConclusion = () => { | |
| const res = window.localStorage.getItem('nodeRes') || ''; | |
| const replaced = replaceStr(res); | |
| // setTimeout(() => { setCollapseInfo([false, false]); }, 2000); | |
| setCollapseInfo([false, false]); | |
| setConclusion(replaced); | |
| setstashConclusion(res); | |
| // 给出conclusion结束的条件 | |
| if (stashConclusion.length + 5 > res.length) { | |
| conclusionRender.current = true; | |
| setProgressEnd(true); | |
| } | |
| }; | |
| // 渲染query | |
| const renderQuery = (endCallback: () => void) => { | |
| const queries = currentNode.actions[currentStep]?.args?.query; | |
| setQuery(queries); | |
| endCallback && endCallback(); | |
| }; | |
| const renderSteps = () => { | |
| setCurrentNodeRendering(true); | |
| const queryEndCallback = () => { | |
| if (currentNode.actions[currentStep].result[0].content) { | |
| if (currentNode.actions[currentStep].type === "BingBrowser.search" || currentNode.actions[currentStep].type === "BingBrowser") { | |
| renderSearchList(); | |
| } | |
| } | |
| }; | |
| const thoughtEndCallback = () => { | |
| if (currentNode.actions[currentStep]?.args?.query?.length) { | |
| renderQuery(queryEndCallback); | |
| } else { | |
| queryEndCallback(); | |
| } | |
| }; | |
| if (currentNode.actions[currentStep].thought) { | |
| renderDraft(currentNode.actions[currentStep].thought, `stepDraft-${currentStep}`, thoughtEndCallback); | |
| } | |
| } | |
| // 展开收起 | |
| const toggleCard = (index: number) => { | |
| const arr = [...collapseInfo]; | |
| arr[index] = !arr[index]; | |
| setCollapseInfo(arr); | |
| }; | |
| // 渲染过程中保持渲染文字可见 | |
| const keepScrollTop = (divA: any, divB: any) => { | |
| // 获取 divB 的当前高度 | |
| const bHeight = divB.offsetHeight; | |
| // 检查 divA 是否需要滚动(即 divB 的高度是否大于 divA 的可视高度) | |
| if (bHeight > divA.offsetHeight) { | |
| // 滚动到 divB 的底部在 divA 的可视区域内 | |
| divA.scrollTop = bHeight - divA.offsetHeight; | |
| } | |
| }; | |
| useEffect(() => { | |
| setRenderData([ | |
| { | |
| id: 0, | |
| state: 3, | |
| name: '原始问题', | |
| children: adjList | |
| } | |
| ]) | |
| }, [JSON.stringify(adjList)]); | |
| useEffect(() => { | |
| console.log('render data changed-----', renderData); | |
| }, [renderData]); | |
| useEffect(() => { | |
| if (currentStep === 1) { | |
| setCollapseInfo([false, true]); | |
| } | |
| }, [currentStep]); | |
| useEffect(() => { | |
| if (nodeOutputEnd && !localStorage.getItem('nodeRes')) { | |
| // 如果节点输出结束了,但是response还没有结束,认为节点渲染已结束 | |
| conclusionRender.current = true; | |
| setProgressEnd(true); | |
| return; | |
| } | |
| if (nodeDraftRender.current && localStorage.getItem('nodeRes')) { | |
| renderConclusion(); | |
| } | |
| }, [localStorage.getItem('nodeRes'), nodeDraftRender.current, nodeOutputEnd]); | |
| useEffect(() => { | |
| if (obj?.response?.nodes[obj.current_node]?.detail?.state !== 1) { | |
| setIsWaiting(true); | |
| } | |
| if (obj?.response?.nodes?.[obj.current_node].detail?.state === 0 && currentNode?.current_node === obj.current_node) { | |
| console.log('node render end-----', obj); | |
| setNodeEnd(true); | |
| } | |
| if (obj?.current_node && obj?.response?.state === 3) { | |
| // 当node节点的数据可以开始渲染时,给currentnode赋值 | |
| // update conclusion | |
| if (obj.response.nodes[obj.current_node]?.detail?.actions?.length === 2 && | |
| obj.response.nodes[obj.current_node]?.detail?.state === 1 && | |
| obj.response.nodes[obj.current_node]?.detail.response) { | |
| window.localStorage.setItem('nodeRes', obj.response.nodes[obj.current_node]?.detail.response); | |
| } | |
| if (obj.current_node && | |
| (obj.response.nodes[obj.current_node]?.detail?.state === 1) && | |
| obj.response.nodes[obj.current_node]?.detail?.actions?.length && | |
| currentStep === 0 && | |
| currentNode?.current_node !== obj?.current_node | |
| ) { | |
| // 更新当前渲染节点 | |
| console.log('update current node----'); | |
| setIsWaiting(false); | |
| setCurrentNode({ ...obj.response.nodes[obj.current_node]?.detail, current_node: obj.current_node }); | |
| } | |
| // 设置highlight | |
| if (!selectedIds.length && | |
| obj.response.nodes[obj.current_node]?.detail?.actions?.[1]?.type === 'BingBrowser.select' && | |
| (obj.response.nodes[obj.current_node]?.detail?.state === 1)) { | |
| setSelectedIds(obj.response.nodes[obj.current_node]?.detail?.actions?.[1]?.args?.select_ids || []); | |
| setCurrentNode({ ...obj.response.nodes[obj.current_node]?.detail, current_node: obj.current_node }); | |
| } | |
| } | |
| }, [obj]); | |
| useEffect(() => { | |
| // 输出思考过程 | |
| if (!currentNode || currentNodeRendering) { return; } | |
| renderSteps(); | |
| }, [currentNode, currentNodeRendering, selectedIds]); | |
| useEffect(() => { | |
| if (!hasHighlight.current && selectedIds.length && currentNode?.actions.length === 2) { | |
| // 渲染高亮的search信息 | |
| highLightSearchList(selectedIds); | |
| } | |
| }, [selectedIds, currentNode]); | |
| useEffect(() => { | |
| // 当前节点渲染结束 | |
| if (nodeName && nodeName !== currentNode?.current_node && progressEnd && !isEnd) { | |
| resetNode(nodeName); | |
| setMapWidth(generateWidth()); | |
| } | |
| }, [nodeName, currentNode, progressEnd, isEnd]); | |
| let responseTimer: any = useRef(null); | |
| useEffect(() => { | |
| if (isEnd) { | |
| responseTimer.current = setInterval(() => { | |
| const divA = document.getElementById('chatArea') as HTMLDivElement; | |
| const divB = document.getElementById('messageWindowId') as HTMLDivElement; | |
| keepScrollTop(divA, divB); | |
| if (chatIsOver) { | |
| clearInterval(responseTimer.current); | |
| } | |
| }, 500); | |
| setTimeout(() => { | |
| setShowEndNode(true); | |
| }, 300); | |
| } else if (responseTimer.current) { | |
| // 如果 isEnd 变为 false,清除定时器 | |
| clearInterval(responseTimer.current); | |
| responseTimer.current = null; | |
| } | |
| // 返回清理函数,确保组件卸载时清除定时器 | |
| return () => { | |
| if (responseTimer.current) { | |
| clearInterval(responseTimer.current); | |
| responseTimer.current = null; | |
| } | |
| }; | |
| }, [isEnd, chatIsOver]); | |
| useEffect(() => { | |
| setRenderData([]); | |
| setResponse(''); | |
| setDraft(''); | |
| setIsEnd(false); | |
| setShowRight(true); | |
| window.localStorage.setItem('nodeRes', ''); | |
| window.localStorage.setItem('finishedNodes', ''); | |
| }, [question]); | |
| const resetNode = (targetNode: string) => { | |
| if (targetNode === 'response') return; // 如果开始response了,所有节点都渲染完了,不需要reset | |
| // 渲染下一个节点前,初始化状态 | |
| const newData = findAndUpdateStatus(renderData, targetNode); | |
| console.log('reset node------', targetNode, renderData); | |
| setCurrentStep(0); | |
| setQuery([]); | |
| setSearchList([]); | |
| setConclusion(''); | |
| setCollapseInfo([true, true]); | |
| setProgress1(''); | |
| setProgress2(''); | |
| setProgressEnd(false); | |
| setCurrentNode(null); | |
| setCurrentNodeRendering(false); | |
| setSelectedIds([]); | |
| setNodeEnd(false); | |
| hasHighlight.current = false; | |
| nodeDraftRender.current = false; | |
| conclusionRender.current = false; | |
| window.localStorage.setItem('nodeRes', ''); | |
| }; | |
| const formatData = (data: any) => { | |
| try { | |
| setIsWaiting(false); | |
| const obj = JSON.parse(data); | |
| if (!obj.current_node && obj.response.state === 0) { | |
| console.log('chat is over end-------'); | |
| setChatIsOver(true); | |
| return; | |
| } | |
| if (!obj.current_node && obj.response.state === 9) { | |
| setShowRight(false); | |
| setIsEnd(true); | |
| const replaced = replaceStr(obj.response.response); | |
| setResponse(replaced); | |
| return; | |
| } | |
| if (!obj.current_node && obj.response.state === 1 && !currentNode) { | |
| // 有thought,没有node | |
| setDraftEnd(false); | |
| setDraft(obj.response.response); | |
| } | |
| if (!obj.current_node && (obj.response.state !== 1 || obj.response.state !== 0 || obj.response.state !== 9)) { | |
| // 有thought,没有node, 不用处理渲染 | |
| //console.log('loading-------------', obj); | |
| setDraftEnd(true); | |
| setIsWaiting(true); | |
| } | |
| if (obj.current_node && obj.response.state === 3) { | |
| setNodeName(obj.current_node); | |
| // 有node | |
| setObj(obj); | |
| const newAdjList = obj.response?.adjacency_list; | |
| if (newAdjList?.length > 0) { | |
| setAdjList(newAdjList); | |
| } | |
| } | |
| } catch (err) { | |
| console.log('format error-----', err); | |
| } | |
| }; | |
| const startEventSource = () => { | |
| if (!chatIsOver) { | |
| message.warning('有对话进行中!'); | |
| return; | |
| } | |
| setQuestion(stashQuestion); | |
| setChatIsOver(false); | |
| const postData = { | |
| inputs: [ | |
| { | |
| role: 'user', | |
| content: stashQuestion | |
| } | |
| ] | |
| } | |
| const ctrl = new AbortController(); | |
| eventSource = fetchEventSource(GET_SSE_DATA, { | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/json', | |
| }, | |
| body: JSON.stringify(postData), | |
| onmessage(ev) { | |
| formatData(ev.data); | |
| }, | |
| onerror(err) { | |
| console.log('sse error------', err); | |
| }, | |
| // signal: ctrl.signal, | |
| }); | |
| }; | |
| const abortEventSource = () => { | |
| if (eventSource) { | |
| eventSource.close(); // 或使用其他方法关闭连接,具体取决于库的API | |
| eventSource = null; | |
| console.log('EventSource connection aborted due to timeout.'); | |
| message.error('连接中断,2s后即将刷新页面---'); | |
| setTimeout(() => { | |
| location.reload(); | |
| }, 2000); | |
| } | |
| }; | |
| return <div className={styles.mainPage} style={!showRight ? { maxWidth: '1000px' } : {}}> | |
| <div className={styles.chatContent}> | |
| <div className={styles.top} id="chatArea"> | |
| <div id="messageWindowId"> | |
| { | |
| question && <div className={styles.question}> | |
| <span>{question}</span> | |
| </div> | |
| } | |
| { | |
| (draft || response || renderData?.length > 0) && | |
| <div className={styles.answer}> | |
| { | |
| renderData?.length > 0 ? <div className={styles.inner}> | |
| <div className={styles.mapArea}> | |
| <ul className={styles.mindmap} id="mindMap" style={isEnd ? { width: mapWidth, overflow: "hidden" } : {}}> | |
| {renderData.map((item: any) => ( | |
| <MindMapItem key={item.name} item={item} isEnd={isEnd} /> | |
| ))} | |
| {showEndNode && | |
| <div className={styles.end} style={generateEndStyle()}> | |
| <div className={styles.node}> | |
| <article>最终回复</article> | |
| </div> | |
| </div> | |
| } | |
| </ul> | |
| </div> | |
| </div> : <></> | |
| } | |
| { | |
| !response && <div className={styles.draft}> | |
| {/* {!draftEnd && draft && <div className={styles.loading}> | |
| <div></div> | |
| <div></div> | |
| <div></div> | |
| </div>} */} | |
| <ReactMarkdown rehypePlugins={[rehypeRaw]}>{replaceStr(draft)}</ReactMarkdown> | |
| </div> | |
| } | |
| {response && <div className={styles.response}> | |
| <ReactMarkdown rehypePlugins={[rehypeRaw]}>{response}</ReactMarkdown> | |
| </div>} | |
| </div> | |
| } | |
| </div> | |
| </div> | |
| <div className={styles.sendArea}> | |
| <Input type="text" placeholder='说点什么吧~ Shift+Enter 换行 ; Enter 发送' onChange={(e) => { setStashQuestion(e.target.value) }} | |
| onPressEnter={startEventSource} /> | |
| <button onClick={startEventSource}> | |
| <img src={SendIcon} /> | |
| 发送 | |
| </button> | |
| </div> | |
| <div className={styles.notice}>如果想要更丝滑的体验,请在本地搭建-<a href='https://github.com/InternLM/MindSearch' target='_blank'>MindSearch <IconFont type='icon-GithubFilled' /></a></div> | |
| </div> | |
| {showRight && <div className={styles.progressContent}> | |
| { | |
| currentNode && <> | |
| <div className={styles.toggleIcon} onClick={toggleRight}> | |
| <Tooltip placement="top" title="收起"> | |
| <img src={PackIcon} /> | |
| </Tooltip></div> | |
| <div className={styles.titleNode}>{currentNode?.content || currentNode?.node}</div> | |
| { | |
| currentNode?.actions?.length ? <> | |
| { | |
| currentNode.actions.map((item: any, idx: number) => ( | |
| currentStep >= idx && <div className={classNames( | |
| styles.steps, | |
| item.type === "BingBrowser.search" ? styles.thinking : styles.select | |
| )} key={`step-${idx}`}> | |
| <div className={styles.title}> | |
| <i></i>{item.type === "BingBrowser.search" ? "思考" : item.type === "BingBrowser.select" ? "信息来源" : "信息整合"} | |
| <div className={styles.open} onClick={() => { toggleCard(idx) }}> | |
| <IconFont type={collapseInfo[idx] ? "icon-shouqi" : "icon-xiangxiazhankai"} /> | |
| </div> | |
| </div> | |
| <div className={classNames( | |
| styles.con, | |
| !collapseInfo[idx] ? styles.collapsed : "" | |
| )}> | |
| { | |
| item.type === "BingBrowser.search" && <div className={styles.thought}> | |
| <ReactMarkdown rehypePlugins={[rehypeRaw]}>{progress1}</ReactMarkdown> | |
| </div> | |
| } | |
| { | |
| item.type === "BingBrowser.search" && query.length > 0 && <div className={styles.query}> | |
| <div className={styles.subTitle}><IconFont type="icon-SearchOutlined" />搜索关键词</div> | |
| { | |
| query.map((item, index) => (<div key={`query-item-${item}`} className={classNames(styles.queryItem, styles.fadeIn)}> | |
| {item} | |
| </div>)) | |
| } | |
| </div> | |
| } | |
| { | |
| currentStep === idx && searchList.length > 0 && <div className={styles.searchList}> | |
| {item.type === "BingBrowser.search" && <div className={styles.subTitle}><IconFont type="icon-DocOutlined" />信息来源</div>} | |
| { | |
| item.type === "BingBrowser.select" && <div className={styles.thought}> | |
| <ReactMarkdown rehypePlugins={[rehypeRaw]}>{progress2}</ReactMarkdown> | |
| </div> | |
| } | |
| <div className={styles.scrollCon} style={(searchList.length > 5 && currentStep === 0) ? { height: '300px' } : {}}> | |
| <div className={styles.inner} style={(searchList.length > 5 && currentStep === 0) ? { position: 'absolute', bottom: 0, left: 0 } : {}}> | |
| { | |
| searchList.map((item: any, num: number) => ( | |
| <div className={classNames( | |
| styles.searchItem, | |
| item.highLight ? styles.highLight : "" | |
| )} key={`search-item-${item.url}-${idx}`}> | |
| <p className={styles.summ}>{item.id}. {item?.title}</p> | |
| <p className={styles.url}>{item?.url}</p> | |
| </div> | |
| )) | |
| } | |
| </div> | |
| </div> | |
| </div> | |
| } | |
| </div> | |
| </div> | |
| )) | |
| } | |
| </> : <></> | |
| } | |
| </> | |
| } | |
| { | |
| conclusion && <div className={styles.steps}> | |
| <div className={styles.title}> | |
| <i></i>信息整合 | |
| </div> | |
| <div className={styles.conclusion}> | |
| <ReactMarkdown rehypePlugins={[rehypeRaw]}>{conclusion}</ReactMarkdown> | |
| </div> | |
| </div> | |
| } | |
| {isWaiting && question && <div className={styles.loading99}></div>} | |
| </div>} | |
| { | |
| !showRight && <div className={styles.showRight} onClick={toggleRight}> | |
| <img src={ShowRightIcon} /> | |
| </div> | |
| } | |
| </div> | |
| }; | |
| export default RenderTest; | |