1 files added
3 files modified
| | |
| | | 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'; |
| | |
| | | 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; |
| | |
| | | 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); |
| | |
| | | } |
| | | 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) { |
| | |
| | | </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> |
| | | ); |
| | |
| | | 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' }); |
| | | } |
| | |
| | | setIsReview(!isReview); |
| | | }; |
| | | |
| | | //提交信息,需要校验规则 |
| | | //提交信息,需要校验规则 |
| | | const handleSubmit = async () => { |
| | | if (formRef.current) { |
| | | formRef.current.validate(undefined, (errors, values) => { |
| | |
| | | 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) { |
| | |
| | | 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: ( |
| | |
| | | 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); |
| | | } |
| | | } |
| | | } |
| | |
| | | } |
| | | } |
| | | |
| | | // 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); |
New file |
| | |
| | | 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; |
| | |
| | | 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' }); |
| | | } |
| | |
| | | if (res.type) { |
| | | // setIsModalResult(res.data); |
| | | let userInfo = $$.getSessionStorage('customerSystemUser'); |
| | | console.log(userInfo, 'userInfo'); |
| | | setIsModalEventInfo({ |
| | | ...res.data, |
| | | mediateUnitName: userInfo?.unit || '', |
| | |
| | | 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: ( |
| | |
| | | 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); |