From bdeacb9f02dfa74bac74296a4a2c989a8e0d45ff Mon Sep 17 00:00:00 2001 From: xusd <330628789@qq.com> Date: Thu, 26 Jun 2025 17:52:12 +0800 Subject: [PATCH] feature:AI演示 --- src/views/register/index.jsx | 74 ++++++++- src/views/register/visit/index.jsx | 10 src/views/register/visit/component/AiChat.jsx | 273 ++++++++++++++++++++++++++++++++++ src/views/register/visit/SelfInspection.jsx | 68 +++++++- 4 files changed, 402 insertions(+), 23 deletions(-) diff --git a/src/views/register/index.jsx b/src/views/register/index.jsx index 5a040c7..3ece25e 100644 --- a/src/views/register/index.jsx +++ b/src/views/register/index.jsx @@ -9,6 +9,7 @@ import React, { useEffect, useState, useMemo, useRef } from 'react'; import { useNavigate } from 'react-router-dom'; import { tab0, tab1, tab2, tab3, tab4, tab5, tab6, allSign, tab1bg } from '@/assets/images'; +import { aiMainTitle, Matter, applyRecord, transfer, transfer_1, Aimge, examine } from '@/assets/images' import TableView from '../../components/TableView'; import NewPage from '../../components/NewPage'; import TableDraftSearch from '../../components/TableDraftSearch'; @@ -32,7 +33,9 @@ import './index.less'; import ResponseDetail from './matterDetail/responseDetail'; import SupervisingViews from './matterDetail/SupervisingViews'; -import { AiQuestion } from './visit/component/levelDetail'; +// import { AiQuestion } from './visit/component/levelDetail'; +import AiChat from './visit/component/AiChat'; +import { IconSend } from '@arco-design/web-react/icon'; // 顶部加上 const FormItem = Form.Item; const TabPane = Tabs.TabPane; const { RangePicker } = DatePicker; @@ -2617,10 +2620,43 @@ const [aiLawData, setAiLawData] = useState([]); const [caseDetailAi, setCaseDetailAi] = useState({}); const [caseClaim, setCaseClaim] = useState(''); + const [aiResult, setAiResult] = useState(null); // AI分析结果 + const [caseDes, setCaseDes] = useState(''); // 案件描述 + + // AI相关函数 + const handleAi = () => { + setCaseDes(''); + setAiQuestionView(true); + }; + useEffect(() => { getCountData(); const visitWorkBenchStore = $$.getSessionStorage('visitWorkBench'); //缓存数据 const toKey = $$.getQueryString('tabActivekey'); //跳转的活跃key + + // 获取aiResult参数 + const aiResultParam = $$.getQueryString('aiResult'); + if (aiResultParam) { + try { + const aiResultData = JSON.parse(decodeURIComponent(aiResultParam)); + setAiResult(aiResultData); + console.log('获取到AI分析结果:', aiResultData); + } catch (error) { + console.error('解析aiResult参数失败:', error); + } + } + + // 从sessionStorage获取caseDes参数 + const caseDesFromStorage = sessionStorage.getItem('ai_chat_caseDes'); + if (caseDesFromStorage) { + // 将案件描述包装成指定的查询格式 + const formattedCaseDes = `我有一个案件:${caseDesFromStorage},请给我一些调解策略和相关法条信息`; + setCaseDes(formattedCaseDes); + console.log('获取到案件描述:', formattedCaseDes); + // 使用后立即删除,避免重复使用 + sessionStorage.removeItem('ai_chat_caseDes'); + } + setMoutedFlag(true); if (visitWorkBenchStore) { setTabActivekey(visitWorkBenchStore.tabActivekey); @@ -2633,6 +2669,17 @@ } window.addEventListener('beforeunload', handleBeforeUnload); }, []); + + // 监听caseDes变化,如果有caseDes则自动打开AiChat弹窗 + useEffect(() => { + if (caseDes && caseDes.trim()) { + console.log('检测到案件描述,1秒后自动打开AiChat弹窗:', caseDes); + // 延迟1秒等页面渲染完毕后再打开弹窗 + setTimeout(() => { + setAiQuestionView(true); + }, 1000); + } + }, [caseDes]); useEffect(() => { if (moutedFlag) { @@ -4179,14 +4226,23 @@ </Button> </div> </Modal> - <AiQuestion - visible={AiQuestionView} - onClose={() => setAiQuestionView(false)} - aiData={aiData} - aiLawData={aiLawData} - caseDetailAi={caseDetailAi} - caseClaim={caseClaim} - /> + {/* 解纷数智人图标 */} + <div className="gradient-box" onClick={() => handleAi()} style={{ top: '50%', zIndex: 1000 }}> + <div><img src={Aimge} alt='' style={{ width: '111px', height: '120px' }} /></div> + <div className="gradient-box-mainTitle">解纷数智人</div> + <div className="gradient-box-mainTitle"><img src={aiMainTitle} alt='' style={{ width: '14px', height: '14px', marginTop: '4px' }} /></div> + </div> + {AiQuestionView && ( + <div style={{position: 'fixed', top: 0, left: 0, width: '100vw', height: '100vh', background: 'rgba(0,0,0,0.15)', zIndex: 2000, display: 'flex', alignItems: 'center', justifyContent: 'center'}}> + <div style={{position: 'relative'}}> + <AiChat user="hugeinfo" onClose={() => { + setAiQuestionView(false) + setCaseDes('') + sessionStorage.removeItem('ai_chat_caseDes'); + }} initialQuery={caseDes} visible={AiQuestionView} /> + </div> + </div> + )} </div> </NewPage> ); diff --git a/src/views/register/visit/SelfInspection.jsx b/src/views/register/visit/SelfInspection.jsx index 5e95d76..190cff3 100644 --- a/src/views/register/visit/SelfInspection.jsx +++ b/src/views/register/visit/SelfInspection.jsx @@ -34,6 +34,22 @@ return $$.ax.request({ urlAi: `case-law/getRiskResult`, data, typeAi: 'post', service: 'mediate' }); } +//获取久拖未决结果 +function getLongRiskResultApi(data) { + // 构造AI请求参数格式 + const aiRequestData = { + inputs: { + caseId: data.caseId, + caseText: data.caseText + }, + response_mode: "blocking", + user: "system", + query: "分析案件是否为久拖未决案件", + conversation_id: data.caseId + }; + return $$.ax.request({ url: `ai/chat/risk`, data: aiRequestData, type: 'post', service: 'sys' }); +} + function embeddingTextToMilvusApi(data) { return $$.ax.request({ urlAi: `case-law/embeddingTextToMilvus`, data, typeAi: 'post', service: 'mediate' }); } @@ -170,7 +186,7 @@ setIsReview(!isReview); }; - //提交信息,需要校验规则 + //提交信息,需要校验规则 const handleSubmit = async () => { if (formRef.current) { formRef.current.validate(undefined, (errors, values) => { @@ -285,15 +301,18 @@ plaintiffsCertiNo, defendantsCertiNo, }); - global.setSpinning(false); + if (res.type) { - if (res.data?.length < 1) { - submitDisputeApi(data, isSelfAccept) - } else { - setRepeatData({ hisData: res.data, isSelfAccept, newData: data, visible: true }) - console.log(repeatData, 'repeatData'); - } + if (res.data?.length < 1) { + submitDisputeApi(data, isSelfAccept) + } else { + setRepeatData({ hisData: res.data, isSelfAccept, newData: data, visible: true }) + global.setSpinning(false); + console.log(repeatData, 'repeatData'); + return; + } } + }; async function submitDisputeApi(data, isSelfAccept) { @@ -302,7 +321,7 @@ if (response.type) { if (isSelfAccept) { getRiskResult({ caseId: id, caseText: (data.caseDes || '') + '/n' + (data.caseClaim || '') }); - embeddingTextToMilvus({ caseId: id, caseDes: data.caseDes || '', caseClaim: data.caseClaim || '' }); + // embeddingTextToMilvus({ caseId: id, caseDes: data.caseDes || '', caseClaim: data.caseClaim || '' }); //自行受理 Message.success({ content: ( @@ -316,12 +335,30 @@ position: 'bottom', }); navigate(`/mediate/visit/handleFeedback?caseTaskId=${response.data}&caseId=${id}`); + // 跳转后关闭转圈效果 + global.setSpinning(false); } else { getRiskResult({ caseId: id, caseText: (data.caseDes || '') + '/n' + (data.caseClaim || '') }); - embeddingTextToMilvus({ caseId: id, caseDes: data.caseDes || '', caseClaim: data.caseClaim || '' }); + // 获取久拖未决分析结果并传入跳转页面 + const longRiskResult = await getLongRiskResult({ caseId: id, caseText: (data.caseDes || '') + '/n' + (data.caseClaim || '') }); + // embeddingTextToMilvus({ caseId: id, caseDes: data.caseDes || '', caseClaim: data.caseClaim || '' }); Message.success('提交成功!'); - navigate(`/mediate/visit/visitWorkBench`, { replace: true }); + + // 只有当longRiskResult === '1'时才带上参数 + if (longRiskResult === '1') { + // 将AI分析结果作为参数传入跳转页面 + const aiResultParam = longRiskResult ? `?aiResult=${longRiskResult}` : ''; + // 当longRiskResult=='1'时,将caseDes存储到sessionStorage + if (data.caseDes) { + sessionStorage.setItem('ai_chat_caseDes', data.caseDes); + } + navigate(`/mediate/visit/visitWorkBench${aiResultParam}`, { replace: true }); + } else { + navigate(`/mediate/visit/visitWorkBench`, { replace: true }); + } setCurrent(2); + // 跳转后关闭转圈效果 + global.setSpinning(false); } } } @@ -333,6 +370,15 @@ } } + // ai分析是否为久拖未决案件 + async function getLongRiskResult(data) { + const res = await getLongRiskResultApi(data); + if (res.type) { + return res.data; // 返回AI分析结果 + } + return null; + } + // 向量化处理 async function embeddingTextToMilvus(data) { const res = await embeddingTextToMilvusApi(data); diff --git a/src/views/register/visit/component/AiChat.jsx b/src/views/register/visit/component/AiChat.jsx new file mode 100644 index 0000000..a25a4e8 --- /dev/null +++ b/src/views/register/visit/component/AiChat.jsx @@ -0,0 +1,273 @@ +import React, { useState, useRef, useEffect } from 'react'; +import { Input, Button, Spin } from '@arco-design/web-react'; +import * as $$ from '@/utils/utility'; +import { IconSend } from '@arco-design/web-react/icon'; +const { TextArea } = Input; + +const SESSION_KEY = 'ai_conversation_id'; +const MSG_KEY = 'ai_chat_messages'; + +const AiChat = ({ user = 'system', onClose, initialQuery = '', visible = false }) => { + // 初始化时从sessionStorage恢复消息 + const [messages, setMessages] = useState(() => { + try { + const msgStr = sessionStorage.getItem(MSG_KEY); + return msgStr ? JSON.parse(msgStr) : []; + } catch { + return []; + } + }); + const [input, setInput] = useState(''); + const [loading, setLoading] = useState(false); + const scrollRef = useRef(null); + const [conversationId, setConversationId] = useState(() => sessionStorage.getItem(SESSION_KEY) || ''); + const [typing, setTyping] = useState(false); + const [hasInitialized, setHasInitialized] = useState(false); + + // 每次messages变化时保存到sessionStorage + useEffect(() => { + sessionStorage.setItem(MSG_KEY, JSON.stringify(messages)); + // 每次消息变化时自动滚动到底部 + if (scrollRef.current) { + setTimeout(() => { + scrollRef.current.scrollTop = scrollRef.current.scrollHeight; + }, 50); + } + }, [messages]); + + // 初始化时如果有initialQuery,自动发送 + useEffect(() => { + console.log('AiChat useEffect - initialQuery:', initialQuery, 'hasInitialized:', hasInitialized); + if (initialQuery && !hasInitialized) { + console.log('准备自动发送initialQuery:', initialQuery); + setHasInitialized(true); + setInput(initialQuery); + // 延迟一下确保组件完全加载 + setTimeout(() => { + console.log('开始自动发送initialQuery:', initialQuery); + handleSend(initialQuery); + }, 100); + } + }, [initialQuery, hasInitialized]); + + // 弹窗显示时自动滚动到底部 + useEffect(() => { + if (visible && scrollRef.current) { + setTimeout(() => { + scrollRef.current.scrollTop = scrollRef.current.scrollHeight; + }, 100); + } + }, [visible]); + + // 关闭时不清除会话id和消息 + // useEffect(() => { + // return () => { + // sessionStorage.removeItem(SESSION_KEY); + // sessionStorage.removeItem(MSG_KEY); + // }; + // }, []); + + // 发送消息 + const handleSend = async (customInput = null) => { + const query = customInput || input.trim(); + if (!query) return; + + setMessages([...messages, { role: 'user', text: query }]); + // 无论是否为自定义输入,都清空输入框 + setInput(''); + setLoading(true); + + // 请求AI接口 + const res = await $$.ax.request({ + url: 'ai/chat/chat', + type: 'post', + service: 'sys', + data: { + inputs: {}, + response_mode: 'blocking', + user, + query, + conversation_id: conversationId || '', + }, + }); + + setLoading(false); + + if (res.type && res.data && res.data.answer) { + // 打字机效果 + let answer = res.data.answer; + let i = 0; + setTyping(true); + setMessages(msgs => [...msgs, { role: 'ai', text: '' }]); + const typeWriter = () => { + setMessages(msgs => { + const newMsgs = [...msgs]; + newMsgs[newMsgs.length - 1] = { role: 'ai', text: answer.slice(0, i + 1) }; + return newMsgs; + }); + i++; + // 每次打字后自动滚动到底部 + setTimeout(() => { + if (scrollRef.current) { + scrollRef.current.scrollTop = scrollRef.current.scrollHeight; + } + }, 0); + if (i < answer.length) { + setTimeout(typeWriter, 15); // 速度可调 + } else { + setTyping(false); + // 结束后再滚动一次 + setTimeout(() => { + if (scrollRef.current) { + scrollRef.current.scrollTop = scrollRef.current.scrollHeight; + } + }, 0); + } + }; + typeWriter(); + + // 首次返回有conversationId则保存 + if (res.data.conversationId && !conversationId) { + setConversationId(res.data.conversationId); + sessionStorage.setItem(SESSION_KEY, res.data.conversationId); + } + } + }; + + // 回车发送 + const handleKeyDown = (e) => { + if (e.key === 'Enter' && !e.shiftKey) { + e.preventDefault(); + handleSend(); + } + }; + + // 关闭对话框时清除会话id + const handleClose = () => { + // sessionStorage.removeItem(SESSION_KEY); + // setConversationId(''); + if (onClose) onClose(); + }; + + return ( + <div style={{ width: '60vw', height: '80vh', margin: '40px', display: 'flex', flexDirection: 'column', background: '#fff', borderRadius: 8, boxShadow: '0 2px 16px #ddd', overflow: 'hidden' }}> + {/* 顶部标题栏 */} + <div style={{ height: 48, background: '#fff', display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '0 24px', borderTopLeftRadius: 8, borderTopRightRadius: 8, borderBottom: '1px solid #f0f0f0' }}> + <span style={{ color: '#222', fontWeight: 500, fontSize: 16, letterSpacing: 1 }}>解纷数智人</span> + <span + onClick={handleClose} + style={{ + width: 28, + height: 28, + borderRadius: '50%', + background: '#f0f1f2', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + cursor: 'pointer', + color: '#6b7280', + fontSize: 18, + transition: 'background 0.2s', + }} + onMouseOver={e => (e.currentTarget.style.background = '#e5e7eb')} + onMouseOut={e => (e.currentTarget.style.background = '#f0f1f2')} + > + × + </span> + </div> + {/* 对话内容区 */} + <div + ref={scrollRef} + style={{ + flex: 1, + overflowY: 'auto', + padding: 32, + borderBottom: '1px solid #f0f0f0', + }} + > + {messages.map((msg, idx) => ( + <div + key={idx} + style={{ + marginBottom: 18, + display: 'flex', + justifyContent: 'flex-start', + }} + > + <div + style={{ + background: msg.role === 'user' ? '#e6f7ff' : '#f7f8fa', + color: '#1a6fb8', + borderRadius: 8, + padding: '10px 20px', + maxWidth: '95%', + minWidth: 60, + wordBreak: 'break-all', + textAlign: 'left', + boxShadow: msg.role === 'ai' ? '0 2px 8px #f0f1f2' : 'none', + }} + > + {msg.text} + </div> + </div> + ))} + {loading && ( + <div style={{ textAlign: 'left', marginBottom: 18 }}> + <Spin /> + </div> + )} + </div> + {/* 优化后的输入区 */} + <div style={{ + background: '#fff', + borderRadius: 16, + boxShadow: '0 2px 8px #f0f1f2', + padding: 20, + margin: 24, + display: 'flex', + alignItems: 'flex-end', + minHeight: 80 + }}> + <TextArea + value={input} + onChange={setInput} + onKeyDown={handleKeyDown} + placeholder="您想咨询什么..." + autoSize={{ minRows: 2, maxRows: 4 }} + style={{ + border: 'none', + outline: 'none', + resize: 'none', + background: 'transparent', + fontSize: 16, + flex: 1, + boxShadow: 'none', + padding: '12px 0 12px 8px', + borderRadius: 12, + minHeight: 48 + }} + /> + <Button + type="primary" + icon={<IconSend />} + onClick={handleSend} + disabled={loading || !input.trim()} + style={{ + marginLeft: 16, + borderRadius: 8, + height: 40, + minWidth: 64, + fontSize: 16, + display: 'flex', + alignItems: 'center', + justifyContent: 'center' + }} + > + 发送 + </Button> + </div> + </div> + ); +}; + +export default AiChat; diff --git a/src/views/register/visit/index.jsx b/src/views/register/visit/index.jsx index ab6ff9b..8b36df4 100644 --- a/src/views/register/visit/index.jsx +++ b/src/views/register/visit/index.jsx @@ -52,6 +52,11 @@ return $$.ax.request({ urlAi: `case-law/getRepeatResult`, data, typeAi: 'post', service: 'mediate' }); } +//获取久拖未决结果 +function getLongRiskResultApi(data) { + return $$.ax.request({ url: `ai/chat/risk`, data, type: 'post', service: 'sys' }); +} + function getCaseAndEventInfoApi(id) { return $$.ax.request({ url: `thGridCitizenEvent/getCaseAndEventInfo?caseId=${id}`, type: 'get', service: 'mediate' }); } @@ -319,7 +324,6 @@ if (res.type) { // setIsModalResult(res.data); let userInfo = $$.getSessionStorage('customerSystemUser'); - console.log(userInfo, 'userInfo'); setIsModalEventInfo({ ...res.data, mediateUnitName: userInfo?.unit || '', @@ -599,7 +603,7 @@ setIsShowModal(false); if (isModalSelfAccept) { getRiskResult({ caseId: id, caseText: (isShowModalData.caseDes || '') + '/n' + (isShowModalData.caseClaim || '') }); - embeddingTextToMilvus({ caseId: id, caseDes: isShowModalData.caseDes || '', caseClaim: isShowModalData.caseClaim || '' }); + // embeddingTextToMilvus({ caseId: id, caseDes: isShowModalData.caseDes || '', caseClaim: isShowModalData.caseClaim || '' }); //自行受理 Message.success({ content: ( @@ -615,7 +619,7 @@ navigate(`/mediate/visit/handleFeedback?caseTaskId=${isModalResult.data}&caseId=${id}`); } else { getRiskResult({ caseId: id, caseText: (isShowModalData.caseDes || '') + '/n' + (isShowModalData.caseClaim || '') }); - embeddingTextToMilvus({ caseId: id, caseDes: isShowModalData.caseDes || '', caseClaim: isShowModalData.caseClaim || '' }); + // embeddingTextToMilvus({ caseId: id, caseDes: isShowModalData.caseDes || '', caseClaim: isShowModalData.caseClaim || '' }); Message.success('提交成功!'); navigate(`/mediate/visit/visitWorkBench`, { replace: true }); setCurrent(2); -- Gitblit v1.8.0