From 63954dddf498097144b5164901491ad842affcd5 Mon Sep 17 00:00:00 2001 From: wenjinbo <599483010@qq.com> Date: Tue, 4 Nov 2025 09:50:36 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=95=B4=E5=8A=A0=E5=85=AB=E5=A4=A7?= =?UTF-8?q?=E7=B1=BB=E8=BF=BD=E9=97=AE=E8=81=8A=E5=A4=A9=E7=AA=97=E4=BD=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../library/components/VabTopBar/index.vue | 12 +- .../src/components/AIResponseDrawer.vue | 1077 +++++++++++++++++ .../src/components/dashboard/ModuleCard.vue | 503 ++++++-- chat-client/src/constants/modules.ts | 3 +- chat-client/src/services/dashboardService.ts | 20 +- chat-client/src/types/dashboard.ts | 3 +- .../src/views/chatweb/homePage/index.vue | 974 ++++++++++++++- .../src/views/guest/homePage/index.vue | 83 ++ .../brichat/service/impl/ChatServiceImpl.java | 7 +- 9 files changed, 2532 insertions(+), 150 deletions(-) create mode 100644 chat-client/src/components/AIResponseDrawer.vue diff --git a/chat-client/library/components/VabTopBar/index.vue b/chat-client/library/components/VabTopBar/index.vue index c36b06a..6519ed4 100644 --- a/chat-client/library/components/VabTopBar/index.vue +++ b/chat-client/library/components/VabTopBar/index.vue @@ -209,6 +209,11 @@ window.addEventListener('homepage-mounted', handleHomePageMounted) window.addEventListener('homepage-unmounted', handleHomePageUnmounted) + // 监听来自homePage的拖拽状态更新 + window.addEventListener('update-drag-mode', ((e: CustomEvent) => { + isDragEnabled.value = e.detail.enabled + }) as EventListener) + // 初始化时检查是否已经在 homePage if ((window as any).__isHomePage__) { isHomePageActive.value = true @@ -236,6 +241,9 @@ window.removeEventListener('scroll', handleScroll) window.removeEventListener('homepage-mounted', handleHomePageMounted) window.removeEventListener('homepage-unmounted', handleHomePageUnmounted) + window.removeEventListener('update-drag-mode', ((e: CustomEvent) => { + isDragEnabled.value = e.detail.enabled + }) as EventListener) // 取消订阅 if ($unsub) { @@ -427,7 +435,7 @@ top: 0; left: 0; right: 0; - z-index: 1000; + z-index: 1003; pointer-events: none; // 允许点击穿透到下方内容 // 子元素恢复 pointer-events @@ -720,7 +728,7 @@ position: fixed; top: 12px; left: 12px; - z-index: 1001; + z-index: 1004; background: linear-gradient(135deg, rgba(0, 212, 255, 0.15) 0%, rgba(24, 144, 255, 0.15) 100%); border: 1px solid rgba(0, 212, 255, 0.3); border-radius: 8px; diff --git a/chat-client/src/components/AIResponseDrawer.vue b/chat-client/src/components/AIResponseDrawer.vue new file mode 100644 index 0000000..235ad5e --- /dev/null +++ b/chat-client/src/components/AIResponseDrawer.vue @@ -0,0 +1,1077 @@ + + + + + + + diff --git a/chat-client/src/components/dashboard/ModuleCard.vue b/chat-client/src/components/dashboard/ModuleCard.vue index 7425686..3d204c1 100644 --- a/chat-client/src/components/dashboard/ModuleCard.vue +++ b/chat-client/src/components/dashboard/ModuleCard.vue @@ -699,98 +699,7 @@ - - +
@@ -1017,7 +926,8 @@ import { MapPin, AlertCircle, Phone, - GraduationCap + GraduationCap, + MessageSquare } from 'lucide-vue-next'; import VabQrCode from '@/plugins/VabQrCode'; // 规章制度的 PDF 预览弹窗迁移到 HomePage 页面,组件不再直接引用 @@ -1095,6 +1005,11 @@ interface Emits { (e: 'show-card-selector'): void; // 新增:让父组件移除选中的卡片 (e: 'remove-selected-card', cardId: string): void; + // 新增:AI响应相关事件 + (e: 'ai-user-message', message: string): void; + (e: 'ai-stream-start'): void; + (e: 'ai-stream-update', content: string): void; + (e: 'ai-stream-finish', finalContent?: string): void; } const props = withDefaults(defineProps(), { @@ -1366,7 +1281,7 @@ const editingPhoneNumber = ref(''); // 运用信息卡片状态 const isPrecautionQrMode = ref(false); // 是否显示二维码模式 -const precautionEditText = ref('配属长沙南所CRH2C-2344 ,担当 D1814次交路,CRH2C-2344 列 00 车主控,途中 2344列 00 车报司机室感烟探头火灾报警(401),司机反馈有焦糊味,司机操作降弓停车,拔取主控。将重联端两头车连接切换器打至分割位、空气管开闭器打至开位,切除 2344 列全列空气制动,断开2344 列00 车总配电柜 CN1 至 CN7 连接器'); // 编辑文本内容 +const precautionEditText = ref('配属广州南所CRH2C-2344 ,担当 D1814次交路,CRH2C-2344 列 00 车主控,途中 2344列 00 车报司机室感烟探头火灾报警(401),司机反馈有焦糊味,司机操作降弓停车,拔取主控。将重联端两头车连接切换器打至分割位、空气管开闭器打至开位,切除 2344 列全列空气制动,断开2344 列00 车总配电柜 CN1 至 CN7 连接器'); // 编辑文本内容 // 对话消息接口 interface ChatMessage { @@ -1378,17 +1293,47 @@ interface ChatMessage { // 对话消息列表 const chatMessages = ref([]); +// 流式响应状态 +const isStreamingResponse = ref(false); + +// 当前流式响应消息 +const currentStreamMessage = ref(''); + +// 聊天历史引用 +const chatHistoryRef = ref(); + +// 任务列表展开状态 +const isTaskListExpanded = ref(true); + +// 计算完成的任务数量 +const completedTaskCount = computed(() => { + return props.taskList?.filter(task => task.status === 'completed').length || 0; +}); + /** - * 切换任务列表展开/收起状态 - 已注释 + * 切换任务列表展开/收起状态 */ -// const toggleTaskList = (event?: Event) => { -// if (event) { -// event.stopPropagation(); -// event.preventDefault(); -// } -// isTaskListExpanded.value = !isTaskListExpanded.value; -// console.log('任务列表状态切换:', isTaskListExpanded.value ? '展开' : '收起'); -// }; +const toggleTaskList = (event?: Event) => { + if (event) { + event.stopPropagation(); + event.preventDefault(); + } + isTaskListExpanded.value = !isTaskListExpanded.value; + console.log('任务列表状态切换:', isTaskListExpanded.value ? '展开' : '收起'); +}; + +/** + * 获取任务状态文本 + */ +const getTaskStatusText = (status: string): string => { + const statusMap: Record = { + pending: '等待中', + loading: '加载中', + completed: '已完成', + error: '失败' + }; + return statusMap[status] || status; +}; /** * 根据图标名称获取对应的图标组件 @@ -1407,7 +1352,8 @@ const iconComponent = computed(() => { Loader2, ChevronDown, ChevronUp, - QrCode + QrCode, + MessageSquare }; return iconMap[props.config.icon as keyof typeof iconMap] || FileText; }); @@ -1525,25 +1471,89 @@ const handleDoubleClick = (event: MouseEvent) => { }; /** - * 格式化时间戳 - 已注释 + * 格式化时间戳 */ -// const formatTimestamp = (date: Date): string => { -// return date.toLocaleTimeString('zh-CN', { -// hour: '2-digit', -// minute: '2-digit' -// }); -// }; +const formatTimestamp = (date: Date): string => { + return date.toLocaleTimeString('zh-CN', { + hour: '2-digit', + minute: '2-digit' + }); +}; /** - * 滚动到对话历史底部 - 已注释 + * 格式化消息内容,支持HTML和换行 */ -// const scrollToBottom = () => { -// nextTick(() => { -// if (chatHistoryRef.value) { -// chatHistoryRef.value.scrollTop = chatHistoryRef.value.scrollHeight; -// } -// }); -// }; +const formatMessageContent = (content: string): string => { + if (!content) { + console.log('formatMessageContent: 内容为空', content); + return ''; + } + + console.log('formatMessageContent: 格式化消息内容:', { + originalContent: content, + contentLength: content.length, + contentType: typeof content + }); + + // 将换行符转换为HTML换行 + const formattedContent = content.replace(/\n/g, '
'); + console.log('formatMessageContent: 格式化后的内容:', { + formattedContent, + formattedLength: formattedContent.length + }); + + return formattedContent; +}; + +/** + * 添加流式响应消息 + */ +const addStreamMessage = (content: string, isComplete: boolean = false) => { + if (isComplete) { + // 流式响应完成,添加完整消息 + const aiMessage: ChatMessage = { + type: 'ai', + content: content, + timestamp: formatTimestamp(new Date()) + }; + chatMessages.value.push(aiMessage); + isStreamingResponse.value = false; + currentStreamMessage.value = ''; + + // 滚动到底部 + nextTick(() => { + scrollToBottom(); + }); + } else { + // 流式响应进行中 + isStreamingResponse.value = true; + currentStreamMessage.value = content; + } +}; + +/** + * 清空聊天消息 + */ +const clearChatMessages = () => { + chatMessages.value = []; + isStreamingResponse.value = false; + currentStreamMessage.value = ''; +}; + + + + + +/** + * 滚动到对话历史底部 + */ +const scrollToBottom = () => { + nextTick(() => { + if (chatHistoryRef.value) { + chatHistoryRef.value.scrollTop = chatHistoryRef.value.scrollHeight; + } + }); +}; /** * 模拟AI回复 - 已注释 @@ -1942,6 +1952,22 @@ const handleInputCardSubmit = async () => { } try { + // 先添加用户消息到聊天记录 + const userMessage: ChatMessage = { + type: 'user', + content: inputText, + timestamp: formatTimestamp(new Date()) + }; + chatMessages.value.push(userMessage); + + // 发送用户消息事件到父组件(用于抽屉显示) + emit('ai-user-message', inputText); + + // 滚动到底部 + nextTick(() => { + scrollToBottom(); + }); + // 调用 sendMessageToAgentInputCard 方法 const { sendMessageToAgentInputCard } = await import('@/api/chat'); @@ -1957,15 +1983,200 @@ const handleInputCardSubmit = async () => { console.log('发送输入卡片消息:', requestData); - // 发送请求 + // 发送流式请求 const response = await sendMessageToAgentInputCard(requestData); - console.log('输入卡片响应:', response); - ElMessage.success('消息发送成功'); + console.log('API响应对象:', { + response, + responseType: typeof response, + hasBody: !!response?.body, + headers: response?.headers, + status: response?.status, + ok: response?.ok + }); + + if (!response || !response.body) { + console.error('响应无效:', response); + throw new Error('No response body'); + } + + // 检查响应类型 + const contentType = response.headers?.get('content-type'); + console.log('响应内容类型:', contentType); + + // 开始流式响应显示 + isStreamingResponse.value = true; + currentStreamMessage.value = ''; + + // 发送流式响应开始事件到父组件 + emit('ai-stream-start'); + + console.log('流式响应状态设置:', { + isStreamingResponse: isStreamingResponse.value, + currentStreamMessage: currentStreamMessage.value, + chatMessagesLength: chatMessages.value.length + }); + + // 处理流式响应 + const reader = response.body.getReader(); + const decoder = new TextDecoder(); + let buffer = ''; + let streamedAnswer = ''; + let conversationId = ''; + let messageId = ''; + let taskId = ''; + + console.log('开始处理流式响应...', { + readerType: typeof reader, + decoderType: typeof decoder + }); + + while (true) { + const { done, value } = await reader.read(); + // console.log('读取流式数据块:', { done, valueLength: value?.length, valueType: typeof value }); + + if (done) { + // console.log('流式数据读取完成'); + break; + } + + const decodedChunk = decoder.decode(value, { stream: true }); + // console.log('解码后的数据块:', decodedChunk); + + buffer += decodedChunk; + // console.log('当前缓冲区内容:', buffer); + + const chunks = buffer.split(/\n\n/); + buffer = chunks.pop() || ''; + // console.log('分割后的数据块数量:', chunks.length, '剩余缓冲区:', buffer); + + for (const chunk of chunks) { + const eventData = chunk.trim(); + // console.log('处理事件数据:', eventData); + + if (!eventData) { + //console.log('跳过空事件数据'); + continue; + } + + const jsonData = eventData.replace(/^data:\s*/, ''); + // console.log('提取的JSON数据:', jsonData); + + try { + const data = JSON.parse(jsonData); + // console.log('解析成功的流式数据:', data); + + if (data.answer) { + streamedAnswer += data.answer; + // console.log('data.answerr', data.answer); + // console.log('累积答案更新:', { + // newAnswer: data.answer, + // totalAnswer: streamedAnswer, + // answerLength: streamedAnswer.length + // }); + + // 实时更新流式响应显示 + currentStreamMessage.value = streamedAnswer; + + // 发送流式响应更新事件到父组件 + emit('ai-stream-update', streamedAnswer); + + // console.log('更新流式消息显示:', { + // currentStreamMessage: currentStreamMessage.value, + // isStreamingResponse: isStreamingResponse.value, + // streamedAnswerLength: streamedAnswer.length, + // chatMessagesLength: chatMessages.value.length + // }); + + // 强制触发Vue响应式更新 + nextTick(() => { + console.log('nextTick执行,准备滚动到底部'); + scrollToBottom(); + }); + } + + // 更新会话ID和消息ID + // if (data.conversationId) { + // conversationId = data.conversationId; + // console.log('更新conversationId:', conversationId); + // } + // if (data.messageId) { + // messageId = data.messageId; + // console.log('更新messageId:', messageId); + // } + // if (data.taskId) { + // taskId = data.taskId; + // console.log('更新taskId:', taskId); + // } + + } catch (e) { + console.error('解析流式数据错误:', e, '原始数据:', jsonData, '事件数据:', eventData); + } + } + } + + // 处理剩余数据 + console.log('处理剩余缓冲区数据:', buffer); + if (buffer) { + const eventData = buffer.trim(); + // console.log('剩余事件数据:', eventData); + if (eventData) { + try { + const jsonData = eventData.replace(/^data:\s*/, ''); + // console.log('剩余JSON数据:', jsonData); + const data = JSON.parse(jsonData); + // console.log('剩余解析成功的数据:', data); + + if (data.answer) { + streamedAnswer += data.answer; + // console.log('剩余答案累积:', streamedAnswer); + // 实时更新流式响应显示 + currentStreamMessage.value = streamedAnswer; + + // 发送流式响应更新事件到父组件 + emit('ai-stream-update', streamedAnswer); + } + if (data.conversationId) { + conversationId = data.conversationId; + } + if (data.messageId) { + messageId = data.messageId; + } + if (data.taskId) { + taskId = data.taskId; + } + } catch (e) { + console.error('最终解析错误:', e, '剩余数据:', eventData); + } + } + } + + console.log('流式响应完成:', { + answer: streamedAnswer, + conversationId, + messageId, + taskId + }); + + // 流式响应完成,使用 addStreamMessage 函数完成显示 + if (streamedAnswer) { + addStreamMessage(streamedAnswer, true); + } else { + // 如果没有内容,也要停止流式响应状态 + isStreamingResponse.value = false; + } + + // 发送流式响应完成事件到父组件 + emit('ai-stream-finish', streamedAnswer); + + ElMessage.success('消息发送成功,响应已接收'); // 清空输入框 inputCardText.value = ''; + // 可以在这里触发事件或回调来处理接收到的答案 + // 例如:emit('message-received', { answer: streamedAnswer, conversationId, messageId }); + } catch (error) { console.error('发送输入卡片消息失败:', error); ElMessage.error('消息发送失败,请重试'); @@ -2294,6 +2505,13 @@ watch( // { immediate: true } // 立即执行一次 // ); +// 暴露方法给父组件使用 +defineExpose({ + addStreamMessage, + clearChatMessages, + scrollToBottom +}); + \ No newline at end of file diff --git a/chat-client/src/constants/modules.ts b/chat-client/src/constants/modules.ts index ce9dc7f..338b6e2 100644 --- a/chat-client/src/constants/modules.ts +++ b/chat-client/src/constants/modules.ts @@ -66,7 +66,8 @@ export const MODULE_CONFIGS: ModuleConfig[] = [ icon: 'Activity', color: '#00D4FF', description: '用于输入消息或指令的交互界面' - } + }, + ] // 主题色彩配置 diff --git a/chat-client/src/services/dashboardService.ts b/chat-client/src/services/dashboardService.ts index 515d8fd..81ddb1b 100644 --- a/chat-client/src/services/dashboardService.ts +++ b/chat-client/src/services/dashboardService.ts @@ -322,7 +322,7 @@ export const generateCaseData = (): TypicalCase[] => { { id: '1', fileName: '附件:CRH2C型动车组总配热损故障典型案例.pdf', - fileUrl: 'http://10.183.199.18/ocr-src-doc-preview/附件:CRH2C型动车组总配热损故障典型案例.pdf', + fileUrl: 'http://192.168.8.253/ocr-src-doc-preview/附件:CRH2C型动车组总配热损故障典型案例.pdf', caseSummary: 'CRH2C型动车组在运行中因总配电柜CN3连接器热损及母板熏黑变色,导致152线回路短路接地,引发00车司机室感烟探头火灾报警(代码401),司机反馈有焦糊味并操作降弓停车。', caseAnalysis: '', emergencyPoints: '', @@ -331,7 +331,7 @@ export const generateCaseData = (): TypicalCase[] => { { id: '2', fileName: '附件:CRH2C型动车组总配热损故障典型案例.pdf', - fileUrl: 'http://10.183.199.18/ocr-src-doc-preview/附件:CRH2C型动车组总配热损故障典型案例.pdf', + fileUrl: 'http://192.168.8.253/ocr-src-doc-preview/附件:CRH2C型动车组总配热损故障典型案例.pdf', caseSummary: '总配电柜热损故障中,152线接地短路导致00车总配母板与148线回路导通,造成全列【显示灯电源】断路器跳断,虽受电弓实际升起但MON屏显示异常,同时触发司机室感烟探头报警。', caseAnalysis: '', emergencyPoints: '', @@ -340,7 +340,7 @@ export const generateCaseData = (): TypicalCase[] => { { id: '3', fileName: '附件:CRH2C型动车组总配热损故障典型案例.pdf', - fileUrl: 'http://10.183.199.18/ocr-src-doc-preview/附件:CRH2C型动车组总配热损故障典型案例.pdf', + fileUrl: 'http://192.168.8.253/ocr-src-doc-preview/附件:CRH2C型动车组总配热损故障典型案例.pdf', caseSummary: '故障分析表明,总配电柜内EBFBR继电器线圈对地短路引发大电流热损,热损产生的烟雾触发感烟探头报警(代码401),并伴随VCB状态显示异常和受电弓显示故障。', caseAnalysis: '', emergencyPoints: '', @@ -399,14 +399,14 @@ export const generateRuleData = (): RuleRegulation[] => { title: 'CRH380A动车组运行中触发火灾报警及设备故障案例', content: '2025年5月25日南昌局CRH380A动车组运行中,00车司机室总配电盘触发集成柜烟感探头火灾报警,伴随ATP、CIR黑屏及断路器跳断故障。', fileName: 'TB 9085.pdf', - fileUrl: 'http://10.183.199.18/ocr-src-doc-preview/TB 9085.pdf' + fileUrl: 'http://192.168.8.253/ocr-src-doc-preview/TB 9085.pdf' }, { id: '2', title: '故障原因分析:过电压吸收器击穿短路引发连锁反应', content: '故障原因为总配电柜内CIR主机供电接触器的过电压吸收器击穿短路,导致过流引发电路板热损、绝缘失效,间接触发烟感探头报警。', fileName: 'TB 9085.pdf', - fileUrl: 'http://10.183.199.18/ocr-src-doc-preview/TB 9085.pdf' + fileUrl: 'http://192.168.8.253/ocr-src-doc-preview/TB 9085.pdf' } ] } @@ -440,7 +440,7 @@ export const generateRouteData = (): RouteInfo[] => { currentRoute: '广州南所-成都东', followingTrainNumber: '0D1821\n(00主', followingRoute: '成都东-天府动车所', - workScheduleUrl: 'http://10.183.199.18/ocr-src-doc-preview/D1820.pdf', + workScheduleUrl: 'http://192.168.8.253/ocr-src-doc-preview/D1820.pdf', safetyReminders: [ { id: '1', @@ -599,7 +599,7 @@ export const generateFaultInfoData = (): FaultInfo => { }, { number: 3, - content: '3)若检查为旅客吸烟、吸入灰尘、水雾等导致假火情,在烟火报警控制器上进行复位。' + content: '若检查为旅客吸烟、吸入灰尘、水雾等导致假火情,在烟火报警控制器上进行复位。' } ] } @@ -615,11 +615,11 @@ export const generatePersonnelData = (): PersonnelInfo[] => { name: '向亮', phone: '013922466474', position: '技师', - department: '长沙动车乘务车间', + department: '广州南动车乘务车间', trainTypeQualifications: 'CRH2A、CRH380A、CR400AF、CR400BF', trainingRecords: [ { - title: '长沙西所通知[2025]45号--关于开展全员学习CRH3C-3028动车组网络、牵引及制动系统自主化改造内容的通知', + title: '广州南所通知[2025]45号--关于开展全员学习CRH3C-3028动车组网络、牵引及制动系统自主化改造内容的通知', date: '2025-01-15', status: 'completed', statusText: '已完成' @@ -649,7 +649,7 @@ export const generatePersonnelData = (): PersonnelInfo[] => { name: '施文龙', phone: '013128646100', position: '技师', - department: '长沙动车乘务车间', + department: '广州南动车乘务车间', trainTypeQualifications: 'CRH2A、CRH380A、CR400AF、CR400BF', trainingRecords: [ { diff --git a/chat-client/src/types/dashboard.ts b/chat-client/src/types/dashboard.ts index 76ff77e..778024e 100644 --- a/chat-client/src/types/dashboard.ts +++ b/chat-client/src/types/dashboard.ts @@ -245,7 +245,8 @@ export type ModuleType = | 'typicalCase' | 'ruleRegulation' | 'precautions' - | 'inputCard'; + | 'inputCard' + | 'chatResponse'; // 模块配置接口 export interface ModuleConfig { diff --git a/chat-client/src/views/chatweb/homePage/index.vue b/chat-client/src/views/chatweb/homePage/index.vue index ae79e10..6f31674 100644 --- a/chat-client/src/views/chatweb/homePage/index.vue +++ b/chat-client/src/views/chatweb/homePage/index.vue @@ -159,9 +159,16 @@ @open-case-pdf="handleOpenCasePdf" @show-card-selector="showCardSelector" @remove-selected-card="removeSelectedCard" + @ai-user-message="handleAIUserMessage" + @ai-stream-start="handleAIStreamStart" + @ai-stream-update="handleAIStreamUpdate" + @ai-stream-finish="handleAIStreamFinish" />
+ + + @@ -243,6 +250,16 @@ >

这是一个测试弹窗,用于验证Element Plus是否正常工作

+ + + + @@ -253,6 +270,7 @@ import { useAclStore } from '@/store/modules/acl'; import { MODULE_CONFIGS } from '@/constants/modules'; import ModuleCard from '@/components/dashboard/ModuleCard.vue'; import DetailModal from '@/components/dashboard/DetailModal.vue'; +import AIResponseDrawer from '@/components/AIResponseDrawer.vue'; import type { ModuleConfig, ModuleType, SearchResult, StatusStats, Position, Size, RuleRegulation, TypicalCase } from '@/types/dashboard'; import { webSocketService } from '@/utils/websocket'; import { baseURL } from '@/config'; @@ -275,7 +293,8 @@ import { MessageSquare, Activity, Phone, - GraduationCap + GraduationCap, + Minimize } from 'lucide-vue-next'; // 使用store @@ -304,6 +323,20 @@ const questionTextarea = ref(); // 输入框引用 const atSuggestionsStyle = ref({}); // @推荐框的样式定位 const selectedCards = ref([]); // 选中的卡片列表,用于显示tag标签 +// AI响应抽屉相关 +const showAIDrawer = ref(false); // 控制AI响应抽屉显示 +const aiDrawerRef = ref>(); // AI抽屉组件引用 +const pendingUserMessage = ref(''); // 缓存待添加的用户消息 + +// AI消息相关(保留接口定义以兼容现有代码) +interface AIMessage { + type: 'user' | 'ai'; + content: string; + timestamp: string; +} + +// 监听抽屉状态变化(已移至AIResponseDrawer组件内部处理) + // 监听弹窗状态变化 watch(showCardSelectorDialog, (newVal, oldVal) => { // 弹窗状态变化处理 @@ -1432,6 +1465,12 @@ const toggleDragMode = () => { isDragEnabled.value = !isDragEnabled.value; }; +/** + * 根据抽屉状态自动控制拖拽功能 + * 现在抽屉展开时也允许拖拽,只有在缩略状态时才可能影响拖拽 + */ +// updateDragBasedOnDrawer方法已移除,拖拽控制现在完全由用户手动控制 + /** * 保存当前布局到文件 */ @@ -1550,6 +1589,76 @@ const showNotification = (message: string, type: 'success' | 'error' | 'info' = }, 3000); }; +/** + * 处理AI用户消息事件 + */ +const handleAIUserMessage = (message: string) => { + // 缓存用户消息,等待流式响应开始时再显示抽屉和添加消息 + pendingUserMessage.value = message; +}; + +/** + * 处理AI流式响应开始事件 + */ +const handleAIStreamStart = () => { + // 只在流式响应开始时才显示抽屉 + showAIDrawer.value = true; + + nextTick(() => { + // 如果有缓存的用户消息,先添加到抽屉 + if (pendingUserMessage.value) { + aiDrawerRef.value?.addUserMessage(pendingUserMessage.value); + pendingUserMessage.value = ''; // 清空缓存 + } + + // 拖拽状态现在完全由用户手动控制 + + // 开始流式响应 + aiDrawerRef.value?.startStreaming(); + }); +}; + +/** + * 处理AI流式响应更新事件 + */ +const handleAIStreamUpdate = (content: string) => { + // 更新流式内容 + aiDrawerRef.value?.updateStreamContent(content); +}; + +/** + * 处理AI流式响应完成事件 + */ +const handleAIStreamFinish = (finalContent?: string) => { + // 完成流式响应 + aiDrawerRef.value?.finishStreaming(finalContent); +}; + +/** + * 处理AI抽屉关闭事件 + */ +const handleAIDrawerClose = () => { + showAIDrawer.value = false; + console.log('AI抽屉关闭'); +}; + +/** + * 处理AI抽屉缩略事件 + */ +const handleAIDrawerMinimize = () => { + console.log('AI抽屉已缩略'); +}; + +/** + * 处理AI抽屉展开事件 + */ +const handleAIDrawerMaximize = () => { + console.log('AI抽屉已展开'); +}; + +// AI相关方法已移至AIResponseDrawer组件中 + + @@ -1568,9 +1677,12 @@ onMounted(async () => { // 监听来自 VabTopBar 的事件 window.addEventListener('toggle-drag-mode', ((e: CustomEvent) => { + // 允许用户随时手动切换拖拽模式,不受抽屉状态限制 isDragEnabled.value = e.detail.enabled; }) as EventListener); + // 监听抽屉状态变化,自动控制拖拽功能(已移至AIResponseDrawer组件内部处理) + window.addEventListener('save-layout', (() => { saveCurrentLayout(); }) as EventListener); @@ -1592,6 +1704,8 @@ onUnmounted(() => { window.removeEventListener('save-layout', (() => {}) as EventListener); window.removeEventListener('restore-layout', (() => {}) as EventListener); + // 打字机定时器已移至AIResponseDrawer组件中 + // 断开 WebSocket 连接并清理监听器 webSocketService.off('message', handleWebSocketMessage); webSocketService.off('connect'); @@ -2638,4 +2752,862 @@ watch(rulePdfDialogVisible, (newValue) => { text-align: center; line-height: 1.2; } + +/* AI响应抽屉样式 - 参考ModuleCard背景色 */ +.ai-response-drawer { + z-index: 1005 !important; + + /* 全局强制覆盖 - 最高优先级 */ + &.el-drawer, + & .el-drawer, + & .el-drawer *, + & .el-drawer__wrapper, + & .el-drawer__container, + & .el-drawer__body { + background: linear-gradient(135deg, rgba(15, 23, 42, 0.8) 0%, rgba(30, 41, 59, 0.6) 100%) !important; + background-color: transparent !important; + } + + /* 强制重写所有可能的Element Plus CSS变量 */ + :deep(.el-drawer), + :deep(.el-drawer *), + :deep(.el-drawer__wrapper), + :deep(.el-drawer__container) { + --el-drawer-bg-color: transparent !important; + --el-dialog-bg-color: transparent !important; + --el-bg-color: transparent !important; + --el-color-white: transparent !important; + --el-drawer-padding-primary: 0px !important; + } + + :deep(.el-drawer) { + background: linear-gradient(135deg, rgba(15, 23, 42, 0.8) 0%, rgba(30, 41, 59, 0.6) 100%) !important; + background-color: transparent !important; + backdrop-filter: none !important; + border-left: 2px solid rgba(0, 212, 255, 0.3); + box-shadow: + -8px 0 32px rgba(0, 212, 255, 0.15), + inset 1px 0 0 rgba(0, 212, 255, 0.1); + z-index: 1005 !important; + transition: all 0.3s ease; + pointer-events: auto; + + &::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: + radial-gradient(circle at 20% 20%, rgba(0, 212, 255, 0.08) 0%, transparent 50%), + radial-gradient(circle at 80% 80%, rgba(0, 162, 255, 0.06) 0%, transparent 50%), + linear-gradient(45deg, transparent 30%, rgba(0, 212, 255, 0.02) 50%, transparent 70%); + pointer-events: none; + } + } + + :deep(.el-overlay) { + z-index: 3999 !important; + background: transparent !important; + backdrop-filter: none !important; + } + + :deep(.el-drawer__header) { + padding: 0; + margin-bottom: 0; + border-bottom: 1px solid rgba(0, 212, 255, 0.3); + box-shadow: 0 2px 8px rgba(0, 212, 255, 0.1); + } + + :deep(.el-drawer__body) { + padding: 0; + height: calc(100% - 60px); + overflow: hidden; + position: relative; + background: transparent !important; + background-color: transparent !important; + } + + /* 强制覆盖所有可能的白色背景元素 */ + :deep(.el-drawer__wrapper) { + background: transparent !important; + background-color: transparent !important; + } + + :deep(.el-drawer__container) { + background: transparent !important; + background-color: transparent !important; + } + + /* 如果还有其他内部元素 */ + :deep(.el-drawer *) { + background-color: transparent !important; + } + + /* 重新设置主要内容区域的背景 */ + :deep(.el-drawer__body), + :deep(.drawer-content), + :deep(.messages-container) { + background: transparent !important; + background-color: transparent !important; + } +} + +.drawer-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 18px 24px; + background: linear-gradient(135deg, + rgba(0, 212, 255, 0.1) 0%, + rgba(0, 162, 255, 0.08) 50%, + rgba(0, 212, 255, 0.05) 100% + ); + border-bottom: 1px solid rgba(0, 212, 255, 0.3); + position: relative; + z-index: 1005; + + &::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: linear-gradient(90deg, + transparent 0%, + rgba(0, 212, 255, 0.05) 50%, + transparent 100% + ); + pointer-events: none; + } + + .header-title { + display: flex; + align-items: center; + gap: 10px; + color: #ffffff; + font-size: 17px; + font-weight: 600; + text-shadow: 0 0 8px rgba(0, 212, 255, 0.3); + position: relative; + z-index: 1; + + svg { + color: #00D4FF; + filter: drop-shadow(0 0 4px rgba(0, 212, 255, 0.5)); + } + } + + .header-actions { + position: relative; + z-index: 1; + display: flex; + align-items: center; + gap: 8px; + + :deep(.el-button) { + color: rgba(255, 255, 255, 0.8); + background: rgba(0, 212, 255, 0.1); + border: 1px solid rgba(0, 212, 255, 0.3); + border-radius: 6px; + transition: all 0.3s ease; + + &:hover { + color: #ffffff; + background: rgba(0, 212, 255, 0.2); + border-color: rgba(0, 212, 255, 0.5); + box-shadow: 0 0 8px rgba(0, 212, 255, 0.3); + } + + &:disabled { + color: rgba(255, 255, 255, 0.3); + background: rgba(0, 212, 255, 0.05); + border-color: rgba(0, 212, 255, 0.1); + } + } + + .drawer-minimize-btn-header { + width: 32px; + height: 32px; + background: rgba(0, 212, 255, 0.1); + border: 1px solid rgba(0, 212, 255, 0.3); + border-radius: 6px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + transition: all 0.3s ease; + color: rgba(255, 255, 255, 0.8); + + &:hover { + color: #ffffff; + background: rgba(0, 212, 255, 0.2); + border-color: rgba(0, 212, 255, 0.5); + box-shadow: 0 0 8px rgba(0, 212, 255, 0.3); + transform: scale(1.05); + } + + svg { + transition: all 0.3s ease; + } + + &:hover svg { + transform: rotate(180deg); + } + } + } +} + +.drawer-content { + height: 100%; + display: flex; + flex-direction: column; + position: relative; + z-index: 1; +} + +.messages-container { + flex: 1; + padding: 24px; + overflow-y: auto; + scroll-behavior: smooth; + position: relative; + + &::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: + radial-gradient(circle at 10% 10%, rgba(0, 212, 255, 0.03) 0%, transparent 50%), + radial-gradient(circle at 90% 90%, rgba(0, 162, 255, 0.02) 0%, transparent 50%); + pointer-events: none; + z-index: -1; + } + + &::-webkit-scrollbar { + width: 8px; + } + + &::-webkit-scrollbar-track { + background: linear-gradient(180deg, + rgba(0, 212, 255, 0.08) 0%, + rgba(0, 162, 255, 0.05) 100% + ); + border-radius: 4px; + border: 1px solid rgba(0, 212, 255, 0.1); + } + + &::-webkit-scrollbar-thumb { + background: linear-gradient(180deg, + rgba(0, 212, 255, 0.4) 0%, + rgba(0, 162, 255, 0.3) 100% + ); + border-radius: 4px; + border: 1px solid rgba(0, 212, 255, 0.2); + box-shadow: inset 0 0 4px rgba(0, 212, 255, 0.2); + + &:hover { + background: linear-gradient(180deg, + rgba(0, 212, 255, 0.6) 0%, + rgba(0, 162, 255, 0.5) 100% + ); + box-shadow: + inset 0 0 4px rgba(0, 212, 255, 0.3), + 0 0 8px rgba(0, 212, 255, 0.2); + } + } +} + +.message-item { + margin-bottom: 20px; + + &.user { + .user-message { + display: flex; + align-items: flex-start; + gap: 12px; + + .message-avatar { + width: 32px; + height: 32px; + border-radius: 50%; + background: rgba(0, 212, 255, 0.2); + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + + svg { + color: #00D4FF; + } + } + + .message-content { + flex: 1; + background: linear-gradient(135deg, + rgba(0, 212, 255, 0.12) 0%, + rgba(0, 162, 255, 0.08) 100% + ); + border: 1px solid rgba(0, 212, 255, 0.3); + border-radius: 16px 16px 16px 6px; + padding: 14px 18px; + box-shadow: + 0 2px 8px rgba(0, 212, 255, 0.1), + inset 0 1px 0 rgba(0, 212, 255, 0.1); + backdrop-filter: blur(5px); + + .message-text { + color: #ffffff; + line-height: 1.5; + word-wrap: break-word; + } + + .message-time { + font-size: 12px; + color: rgba(255, 255, 255, 0.5); + margin-top: 8px; + } + } + } + } + + &.ai { + .ai-message { + display: flex; + align-items: flex-start; + gap: 12px; + + .message-avatar { + width: 32px; + height: 32px; + border-radius: 50%; + background: rgba(82, 196, 26, 0.2); + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + + svg { + color: #00FFC8; + filter: drop-shadow(0 0 4px rgba(0, 255, 200, 0.5)); + } + + .typing-indicator { + display: flex; + gap: 2px; + + span { + width: 4px; + height: 4px; + border-radius: 50%; + background: #00FFC8; + animation: typing 1.4s infinite ease-in-out; + + &:nth-child(1) { animation-delay: 0s; } + &:nth-child(2) { animation-delay: 0.2s; } + &:nth-child(3) { animation-delay: 0.4s; } + } + } + } + + .message-content { + flex: 1; + background: linear-gradient(135deg, + rgba(0, 255, 200, 0.12) 0%, + rgba(0, 212, 255, 0.08) 100% + ); + border: 1px solid rgba(0, 255, 200, 0.3); + border-radius: 16px 16px 6px 16px; + padding: 14px 18px; + position: relative; + box-shadow: + 0 2px 8px rgba(0, 255, 200, 0.1), + inset 0 1px 0 rgba(0, 255, 200, 0.1); + backdrop-filter: blur(5px); + + .message-text { + color: #ffffff; + line-height: 1.5; + word-wrap: break-word; + + :deep(strong) { + font-weight: 600; + color: #00D4FF; + } + + :deep(em) { + font-style: italic; + color: #FAAD14; + } + + :deep(code) { + background: rgba(0, 0, 0, 0.3); + padding: 2px 6px; + border-radius: 4px; + font-family: 'Courier New', monospace; + font-size: 0.9em; + color: #00D4FF; + } + + :deep(pre) { + background: rgba(0, 0, 0, 0.3); + padding: 12px; + border-radius: 6px; + margin: 8px 0; + overflow-x: auto; + + code { + background: none; + padding: 0; + color: #ffffff; + } + } + } + + &.streaming-text { + min-height: 20px; + } + + .streaming-cursor { + display: inline-block; + color: #00FFC8; + animation: blink 1s infinite; + margin-left: 2px; + text-shadow: 0 0 4px rgba(0, 255, 200, 0.5); + } + + .message-time { + font-size: 12px; + color: rgba(255, 255, 255, 0.5); + margin-top: 8px; + } + } + } + } + + &.streaming { + .ai-message .message-content { + border-color: rgba(0, 255, 200, 0.5); + box-shadow: + 0 0 12px rgba(0, 255, 200, 0.3), + 0 2px 8px rgba(0, 255, 200, 0.1), + inset 0 1px 0 rgba(0, 255, 200, 0.2); + animation: streamingGlow 2s ease-in-out infinite alternate; + } + } +} + +.empty-state { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + height: 100%; + text-align: center; + color: rgba(255, 255, 255, 0.6); + + .empty-icon { + margin-bottom: 20px; + opacity: 0.5; + + svg { + color: rgba(0, 212, 255, 0.5); + } + } + + .empty-text { + h3 { + font-size: 18px; + font-weight: 500; + margin: 0 0 8px 0; + color: rgba(255, 255, 255, 0.8); + } + + p { + font-size: 14px; + margin: 0; + line-height: 1.5; + } + } +} + +@keyframes typing { + 0%, 60%, 100% { + transform: translateY(0); + opacity: 0.5; + } + 30% { + transform: translateY(-10px); + opacity: 1; + } +} + +@keyframes blink { + 0%, 50% { + opacity: 1; + } + 51%, 100% { + opacity: 0; + } +} + +@keyframes streamingGlow { + 0% { + box-shadow: + 0 0 12px rgba(0, 255, 200, 0.3), + 0 2px 8px rgba(0, 255, 200, 0.1), + inset 0 1px 0 rgba(0, 255, 200, 0.2); + } + 100% { + box-shadow: + 0 0 20px rgba(0, 255, 200, 0.5), + 0 4px 16px rgba(0, 255, 200, 0.2), + inset 0 1px 0 rgba(0, 255, 200, 0.3); + } +} + + +/* AI悬浮图标样式 - 右下角固定位置 */ +.ai-floating-icon { + position: fixed; + bottom: 24px; + right: 24px; + z-index: 1006; + width: 64px; + height: 64px; + cursor: pointer; + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + animation: slideInFromRight 0.5s cubic-bezier(0.4, 0, 0.2, 1); + + .floating-icon-inner { + width: 100%; + height: 100%; + background: linear-gradient(135deg, rgba(15, 23, 42, 0.9) 0%, rgba(30, 41, 59, 0.8) 100%); + border: 2px solid rgba(0, 212, 255, 0.4); + border-radius: 16px; + display: flex; + align-items: center; + justify-content: center; + backdrop-filter: blur(15px); + box-shadow: + 0 8px 32px rgba(0, 212, 255, 0.2), + 0 0 0 1px rgba(0, 212, 255, 0.1), + inset 0 1px 0 rgba(255, 255, 255, 0.1); + transition: all 0.3s ease; + position: relative; + overflow: hidden; + + &::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: + radial-gradient(circle at 30% 30%, rgba(0, 212, 255, 0.1) 0%, transparent 50%), + radial-gradient(circle at 70% 70%, rgba(0, 162, 255, 0.08) 0%, transparent 50%); + pointer-events: none; + } + + svg { + color: #00D4FF; + filter: drop-shadow(0 0 8px rgba(0, 212, 255, 0.6)); + transition: all 0.3s ease; + position: relative; + z-index: 1; + } + } + + .activity-indicator { + position: absolute; + top: -2px; + right: -2px; + + .pulse-dot { + width: 12px; + height: 12px; + background: #00FFC8; + border-radius: 50%; + animation: pulse 2s infinite; + box-shadow: + 0 0 8px rgba(0, 255, 200, 0.8), + 0 0 16px rgba(0, 255, 200, 0.4); + border: 2px solid rgba(15, 23, 42, 0.8); + } + } + + .message-count { + position: absolute; + top: -8px; + right: -8px; + background: linear-gradient(135deg, #ff4d4f 0%, #ff7875 100%); + color: white; + font-size: 12px; + font-weight: 600; + min-width: 20px; + height: 20px; + border-radius: 10px; + display: flex; + align-items: center; + justify-content: center; + padding: 0 6px; + box-shadow: + 0 2px 8px rgba(255, 77, 79, 0.4), + 0 0 0 2px rgba(15, 23, 42, 0.8); + border: 1px solid rgba(255, 255, 255, 0.2); + } + + &:hover { + transform: translateY(-4px) scale(1.05); + + .floating-icon-inner { + background: linear-gradient(135deg, rgba(15, 23, 42, 0.95) 0%, rgba(30, 41, 59, 0.9) 100%); + border-color: rgba(0, 212, 255, 0.6); + box-shadow: + 0 12px 48px rgba(0, 212, 255, 0.3), + 0 0 0 1px rgba(0, 212, 255, 0.2), + inset 0 1px 0 rgba(255, 255, 255, 0.2); + + svg { + transform: scale(1.1); + filter: drop-shadow(0 0 12px rgba(0, 212, 255, 0.8)); + } + } + } + + &:active { + transform: translateY(-2px) scale(1.02); + } + + &.has-activity { + .floating-icon-inner { + border-color: rgba(0, 255, 200, 0.5); + box-shadow: + 0 8px 32px rgba(0, 255, 200, 0.2), + 0 0 0 1px rgba(0, 255, 200, 0.1), + inset 0 1px 0 rgba(255, 255, 255, 0.1); + animation: activityGlow 2s ease-in-out infinite alternate; + } + } +} + +@keyframes slideInFromRight { + 0% { + opacity: 0; + transform: translateX(100px) scale(0.8); + } + 60% { + opacity: 1; + transform: translateX(-5px) scale(1.05); + } + 100% { + opacity: 1; + transform: translateX(0) scale(1); + } +} + +@keyframes activityGlow { + 0% { + box-shadow: + 0 8px 32px rgba(0, 255, 200, 0.2), + 0 0 0 1px rgba(0, 255, 200, 0.1), + inset 0 1px 0 rgba(255, 255, 255, 0.1); + } + 100% { + box-shadow: + 0 12px 48px rgba(0, 255, 200, 0.4), + 0 0 0 1px rgba(0, 255, 200, 0.3), + inset 0 1px 0 rgba(255, 255, 255, 0.2); + } +} + +/* 悬浮图标的淡入淡出动画 */ +.float-fade-enter-active { + transition: all 0.5s cubic-bezier(0.4, 0, 0.2, 1); +} + +.float-fade-leave-active { + transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); +} + +.float-fade-enter-from { + opacity: 0; + transform: translateX(100px) scale(0.8); +} + +.float-fade-leave-to { + opacity: 0; + transform: translateX(100px) scale(0.8); +} + +@keyframes pulse { + 0% { + transform: scale(1); + opacity: 1; + } + 50% { + transform: scale(1.2); + opacity: 0.7; + } + 100% { + transform: scale(1); + opacity: 1; + } +} + +// AI抽屉和悬浮图标响应式设计 +@media (max-width: 768px) { + .ai-response-drawer { + :deep(.el-drawer) { + width: 90% !important; + } + } + + .messages-container { + padding: 16px; + } + + .message-item { + .user-message, + .ai-message { + gap: 8px; + + .message-avatar { + width: 28px; + height: 28px; + + svg { + width: 14px; + height: 14px; + } + } + + .message-content { + padding: 10px 12px; + font-size: 14px; + } + } + } + + // 移动端悬浮图标调整 + .ai-floating-icon { + bottom: 16px; + right: 16px; + width: 56px; + height: 56px; + + .floating-icon-inner { + border-radius: 14px; + + svg { + width: 20px; + height: 20px; + } + } + + .activity-indicator .pulse-dot { + width: 10px; + height: 10px; + } + + .message-count { + top: -6px; + right: -6px; + min-width: 18px; + height: 18px; + font-size: 11px; + } + } +} + +@media (max-width: 480px) { + .ai-floating-icon { + bottom: 12px; + right: 12px; + width: 48px; + height: 48px; + + .floating-icon-inner { + border-radius: 12px; + + svg { + width: 18px; + height: 18px; + } + } + + .activity-indicator .pulse-dot { + width: 8px; + height: 8px; + } + + .message-count { + top: -4px; + right: -4px; + min-width: 16px; + height: 16px; + font-size: 10px; + } + } +} + + + diff --git a/chat-client/src/views/guest/homePage/index.vue b/chat-client/src/views/guest/homePage/index.vue index 2d601f5..88254a2 100644 --- a/chat-client/src/views/guest/homePage/index.vue +++ b/chat-client/src/views/guest/homePage/index.vue @@ -163,6 +163,10 @@ @open-case-pdf="handleOpenCasePdf" @show-card-selector="showCardSelector" @remove-selected-card="removeSelectedCard" + @ai-user-message="handleAIUserMessage" + @ai-stream-start="handleAIStreamStart" + @ai-stream-update="handleAIStreamUpdate" + @ai-stream-finish="handleAIStreamFinish" /> @@ -237,6 +241,15 @@ + + + @@ -246,6 +259,7 @@ import { useRoute } from 'vue-router'; import { MODULE_CONFIGS } from '@/constants/modules'; import ModuleCard from '@/components/dashboard/ModuleCard.vue'; import DetailModal from '@/components/dashboard/DetailModal.vue'; +import AIResponseDrawer from '@/components/AIResponseDrawer.vue'; import type { ModuleConfig, ModuleType, SearchResult, StatusStats, Position, Size, RuleRegulation, TypicalCase } from '@/types/dashboard'; import { webSocketService } from '@/utils/websocket'; import { baseURL } from '@/config'; @@ -305,6 +319,10 @@ const questionTextarea = ref(); // 输入框引用 const atSuggestionsStyle = ref({}); // @推荐框的样式定位 const selectedCards = ref([]); // 选中的卡片列表,用于显示tag标签 +// AI响应抽屉相关 +const showAIDrawer = ref(false); // 控制AI响应抽屉显示 +const aiDrawerRef = ref>(); // AI抽屉组件引用 + // 监听弹窗状态变化 watch(showCardSelectorDialog, (newVal, oldVal) => { // 弹窗状态变化处理 @@ -1545,6 +1563,71 @@ const showNotification = (message: string, type: 'success' | 'error' | 'info' = }, 3000); }; +/** + * 处理AI用户消息事件 + */ +const handleAIUserMessage = (message: string) => { + console.log('收到用户消息:', message); + // 显示AI抽屉 + showAIDrawer.value = true; + // 添加用户消息到抽屉 + nextTick(() => { + aiDrawerRef.value?.addUserMessage(message); + }); +}; + +/** + * 处理AI流式响应开始事件 + */ +const handleAIStreamStart = () => { + console.log('AI流式响应开始'); + // 确保抽屉是打开的 + showAIDrawer.value = true; + // 开始流式响应 + nextTick(() => { + aiDrawerRef.value?.startStreaming(); + }); +}; + +/** + * 处理AI流式响应更新事件 + */ +const handleAIStreamUpdate = (content: string) => { + console.log('AI流式响应更新:', content.length, '字符'); + // 更新流式内容 + aiDrawerRef.value?.updateStreamContent(content); +}; + +/** + * 处理AI流式响应完成事件 + */ +const handleAIStreamFinish = (finalContent?: string) => { + console.log('AI流式响应完成,最终内容长度:', finalContent?.length || 0); + // 完成流式响应 + aiDrawerRef.value?.finishStreaming(finalContent); +}; + +/** + * 处理AI抽屉关闭事件 + */ +const handleAIDrawerClose = () => { + showAIDrawer.value = false; +}; + +/** + * 处理AI抽屉缩略事件 + */ +const handleAIDrawerMinimize = () => { + console.log('AI抽屉已缩略'); +}; + +/** + * 处理AI抽屉展开事件 + */ +const handleAIDrawerMaximize = () => { + console.log('AI抽屉已展开'); +}; + diff --git a/chat-server/src/main/java/com/bjtds/brichat/service/impl/ChatServiceImpl.java b/chat-server/src/main/java/com/bjtds/brichat/service/impl/ChatServiceImpl.java index 7b9d44d..ce3c264 100644 --- a/chat-server/src/main/java/com/bjtds/brichat/service/impl/ChatServiceImpl.java +++ b/chat-server/src/main/java/com/bjtds/brichat/service/impl/ChatServiceImpl.java @@ -4,6 +4,7 @@ import com.bjtds.brichat.entity.chat.AgentInputCardReq; import com.bjtds.brichat.entity.chat.ChatMessage; import com.bjtds.brichat.entity.chat.ChatTypeVo; import com.bjtds.brichat.entity.dataset.TUserDataset; +import com.bjtds.brichat.entity.sys.ApiKey; import com.bjtds.brichat.mapper.opengauss.TUserDatasetMapper; import com.bjtds.brichat.service.KnowledgeBaseService; import com.bjtds.brichat.service.TApiKeyService; @@ -131,14 +132,16 @@ public class ChatServiceImpl implements ChatService { datasetIds.add(datasetId); } } - UUID appId = UUID.fromString("f940e32f-e302-49b8-b030-591539c26779"); + + ApiKey workFlowInfo = apiKeyService.queryByName("agentInput"); + UUID appId = UUID.fromString(workFlowInfo.getRemark()); final boolean b = knowledgeBaseService.addDatasetsToWorkflow(appId, datasetIds); //启动对话流 ChatMessageSendRequest sendRequest = new ChatMessageSendRequest(); sendRequest.setUserId(agentInputCardReq.getUserId()); sendRequest.setContent(agentInputCardReq.getContent()); - sendRequest.setApiKey("app-jCKfKpPW8f9C7ZXLElAq1DSS"); + sendRequest.setApiKey(workFlowInfo.getValue()); Map inputMap = new HashMap<>(); inputMap.put("sys_message_id", sysMessage); inputMap.put("context", agentInputCardReq.getContext());