feat: 整加八大类追问聊天窗体

This commit is contained in:
wenjinbo 2025-11-04 09:50:36 +08:00
parent d66b64de07
commit 63954dddf4
9 changed files with 2532 additions and 150 deletions

View File

@ -209,6 +209,11 @@
window.addEventListener('homepage-mounted', handleHomePageMounted) window.addEventListener('homepage-mounted', handleHomePageMounted)
window.addEventListener('homepage-unmounted', handleHomePageUnmounted) window.addEventListener('homepage-unmounted', handleHomePageUnmounted)
// homePage
window.addEventListener('update-drag-mode', ((e: CustomEvent) => {
isDragEnabled.value = e.detail.enabled
}) as EventListener)
// homePage // homePage
if ((window as any).__isHomePage__) { if ((window as any).__isHomePage__) {
isHomePageActive.value = true isHomePageActive.value = true
@ -236,6 +241,9 @@
window.removeEventListener('scroll', handleScroll) window.removeEventListener('scroll', handleScroll)
window.removeEventListener('homepage-mounted', handleHomePageMounted) window.removeEventListener('homepage-mounted', handleHomePageMounted)
window.removeEventListener('homepage-unmounted', handleHomePageUnmounted) window.removeEventListener('homepage-unmounted', handleHomePageUnmounted)
window.removeEventListener('update-drag-mode', ((e: CustomEvent) => {
isDragEnabled.value = e.detail.enabled
}) as EventListener)
// //
if ($unsub) { if ($unsub) {
@ -427,7 +435,7 @@
top: 0; top: 0;
left: 0; left: 0;
right: 0; right: 0;
z-index: 1000; z-index: 1003;
pointer-events: none; // 穿 pointer-events: none; // 穿
// pointer-events // pointer-events
@ -720,7 +728,7 @@
position: fixed; position: fixed;
top: 12px; top: 12px;
left: 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%); 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: 1px solid rgba(0, 212, 255, 0.3);
border-radius: 8px; border-radius: 8px;

File diff suppressed because it is too large Load Diff

View File

@ -699,98 +699,7 @@
</div> </div>
</div> </div>
<!-- AI助手对话 - 已注释 --> <!-- AI助手对话卡片头部 -->
<!-- <div v-else-if="config.id === 'inputCard'" class="module-card__chat-info">
<div ref="chatHistoryRef" class="chat-history">
<div v-if="chatMessages.length === 0 && taskList.length === 0" class="chat-empty">
<p>开始与AI助手对话吧</p>
</div>
<div
v-for="(message, index) in chatMessages"
:key="index"
class="chat-message"
:class="{ 'chat-message--user': message.type === 'user', 'chat-message--ai': message.type === 'ai' }"
>
<div class="chat-message__avatar">
<Users v-if="message.type === 'user'" :size="16" />
<Activity v-else :size="16" />
</div>
<div class="chat-message__content">
<div class="chat-message__text">{{ message.content }}</div>
<div class="chat-message__time">{{ message.timestamp }}</div>
</div>
</div>
<div v-if="taskList && taskList.length > 0 && chatMessages.length > 0" class="task-list-container">
<div class="task-list-header" @click.stop="toggleTaskList">
<Activity :size="16" class="task-list-icon" />
<h4 class="task-list-title">数据加载进度</h4>
<span class="task-list-badge">{{ completedTaskCount }}/{{ taskList.length }}</span>
<button class="task-list-toggle" :title="isTaskListExpanded ? '收起' : '展开'" @click.stop="toggleTaskList">
<ChevronUp v-if="isTaskListExpanded" :size="16" />
<ChevronDown v-else :size="16" />
</button>
</div>
<transition name="task-list-expand">
<div v-show="isTaskListExpanded" class="task-list">
<div
v-for="task in taskList"
:key="task.id"
class="task-item"
:class="`task-item--${task.status}`"
>
<div class="task-icon">
<div v-if="task.status === 'loading'" class="task-spinner">
<Loader2 :size="14" class="animate-spin" />
</div>
<svg v-else-if="task.status === 'completed'" xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round">
<polyline points="20 6 9 17 4 12"/>
</svg>
<svg v-else-if="task.status === 'error'" xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10"/>
<line x1="15" y1="9" x2="9" y2="15"/>
<line x1="9" y1="9" x2="15" y2="15"/>
</svg>
<svg v-else xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10"/>
</svg>
</div>
<span class="task-title">{{ task.title }}</span>
<span class="task-status-text">
{{ getTaskStatusText(task.status) }}
</span>
</div>
</div>
</transition>
</div>
</div>
<div class="chat-input-area">
<div class="chat-input-container">
<el-input
v-model="currentMessage"
placeholder="请输入您的问题..."
type="textarea"
:rows="2"
resize="none"
class="chat-input"
@keydown.enter.prevent="handleSendMessage"
:disabled="isAiThinking"
/>
<button
class="chat-send-btn"
@mousedown.stop
@click.stop="handleSendMessage"
:disabled="!currentMessage.trim() || isAiThinking"
:title="isAiThinking ? 'AI正在思考中...' : '发送消息'"
>
<Activity v-if="isAiThinking" :size="16" class="animate-spin" />
<span v-else>发送</span>
</button>
</div>
</div>
</div> -->
<!-- 输入框卡片 --> <!-- 输入框卡片 -->
<div v-else-if="config.id === 'inputCard'" class="module-card__input-card"> <div v-else-if="config.id === 'inputCard'" class="module-card__input-card">
@ -1017,7 +926,8 @@ import {
MapPin, MapPin,
AlertCircle, AlertCircle,
Phone, Phone,
GraduationCap GraduationCap,
MessageSquare
} from 'lucide-vue-next'; } from 'lucide-vue-next';
import VabQrCode from '@/plugins/VabQrCode'; import VabQrCode from '@/plugins/VabQrCode';
// PDF HomePage // PDF HomePage
@ -1095,6 +1005,11 @@ interface Emits {
(e: 'show-card-selector'): void; (e: 'show-card-selector'): void;
// //
(e: 'remove-selected-card', cardId: string): 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<Props>(), { const props = withDefaults(defineProps<Props>(), {
@ -1366,7 +1281,7 @@ const editingPhoneNumber = ref('');
// //
const isPrecautionQrMode = ref(false); // 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 { interface ChatMessage {
@ -1378,17 +1293,47 @@ interface ChatMessage {
// //
const chatMessages = ref<ChatMessage[]>([]); const chatMessages = ref<ChatMessage[]>([]);
//
const isStreamingResponse = ref(false);
//
const currentStreamMessage = ref('');
//
const chatHistoryRef = ref<HTMLElement>();
//
const isTaskListExpanded = ref(true);
//
const completedTaskCount = computed(() => {
return props.taskList?.filter(task => task.status === 'completed').length || 0;
});
/** /**
* 切换任务列表展开/收起状态 - 已注释 * 切换任务列表展开/收起状态
*/ */
// const toggleTaskList = (event?: Event) => { const toggleTaskList = (event?: Event) => {
// if (event) { if (event) {
// event.stopPropagation(); event.stopPropagation();
// event.preventDefault(); event.preventDefault();
// } }
// isTaskListExpanded.value = !isTaskListExpanded.value; isTaskListExpanded.value = !isTaskListExpanded.value;
// console.log(':', isTaskListExpanded.value ? '' : ''); console.log('任务列表状态切换:', isTaskListExpanded.value ? '展开' : '收起');
// }; };
/**
* 获取任务状态文本
*/
const getTaskStatusText = (status: string): string => {
const statusMap: Record<string, string> = {
pending: '等待中',
loading: '加载中',
completed: '已完成',
error: '失败'
};
return statusMap[status] || status;
};
/** /**
* 根据图标名称获取对应的图标组件 * 根据图标名称获取对应的图标组件
@ -1407,7 +1352,8 @@ const iconComponent = computed(() => {
Loader2, Loader2,
ChevronDown, ChevronDown,
ChevronUp, ChevronUp,
QrCode QrCode,
MessageSquare
}; };
return iconMap[props.config.icon as keyof typeof iconMap] || FileText; return iconMap[props.config.icon as keyof typeof iconMap] || FileText;
}); });
@ -1525,25 +1471,89 @@ const handleDoubleClick = (event: MouseEvent) => {
}; };
/** /**
* 格式化时间戳 - 已注释 * 格式化时间戳
*/ */
// const formatTimestamp = (date: Date): string => { const formatTimestamp = (date: Date): string => {
// return date.toLocaleTimeString('zh-CN', { return date.toLocaleTimeString('zh-CN', {
// hour: '2-digit', hour: '2-digit',
// minute: '2-digit' minute: '2-digit'
// }); });
// }; };
/** /**
* 滚动到对话历史底部 - 已注释 * 格式化消息内容支持HTML和换行
*/ */
// const scrollToBottom = () => { const formatMessageContent = (content: string): string => {
// nextTick(() => { if (!content) {
// if (chatHistoryRef.value) { console.log('formatMessageContent: 内容为空', content);
// chatHistoryRef.value.scrollTop = chatHistoryRef.value.scrollHeight; return '';
// } }
// });
// }; console.log('formatMessageContent: 格式化消息内容:', {
originalContent: content,
contentLength: content.length,
contentType: typeof content
});
// HTML
const formattedContent = content.replace(/\n/g, '<br>');
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回复 - 已注释 * 模拟AI回复 - 已注释
@ -1942,6 +1952,22 @@ const handleInputCardSubmit = async () => {
} }
try { try {
//
const userMessage: ChatMessage = {
type: 'user',
content: inputText,
timestamp: formatTimestamp(new Date())
};
chatMessages.value.push(userMessage);
//
emit('ai-user-message', inputText);
//
nextTick(() => {
scrollToBottom();
});
// sendMessageToAgentInputCard // sendMessageToAgentInputCard
const { sendMessageToAgentInputCard } = await import('@/api/chat'); const { sendMessageToAgentInputCard } = await import('@/api/chat');
@ -1957,15 +1983,200 @@ const handleInputCardSubmit = async () => {
console.log('发送输入卡片消息:', requestData); console.log('发送输入卡片消息:', requestData);
// //
const response = await sendMessageToAgentInputCard(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();
});
}
// IDID
// 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 = ''; inputCardText.value = '';
//
// emit('message-received', { answer: streamedAnswer, conversationId, messageId });
} catch (error) { } catch (error) {
console.error('发送输入卡片消息失败:', error); console.error('发送输入卡片消息失败:', error);
ElMessage.error('消息发送失败,请重试'); ElMessage.error('消息发送失败,请重试');
@ -2294,6 +2505,13 @@ watch(
// { immediate: true } // // { immediate: true } //
// ); // );
// 使
defineExpose({
addStreamMessage,
clearChatMessages,
scrollToBottom
});
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@ -3332,16 +3550,7 @@ watch(
} }
// AI -
// &__chat-info {
// display: flex;
// flex-direction: column;
// height: 100%; //
// min-height: 200px; //
// overflow: hidden;
// position: relative; //
// flex: 1; //
// }
// //
&__input-card { &__input-card {
@ -3691,6 +3900,11 @@ watch(
} }
} }
// - // -
// .chat-history { // .chat-history {
// flex: 1; // flex: 1;
@ -4768,7 +4982,7 @@ watch(
opacity: 0.8; opacity: 0.8;
transform: rotate(2deg) scale(1.02) translate3d(0, 0, 0); transform: rotate(2deg) scale(1.02) translate3d(0, 0, 0);
box-shadow: 0 12px 40px rgba(0, 212, 255, 0.3); box-shadow: 0 12px 40px rgba(0, 212, 255, 0.3);
z-index: 1000; z-index: 1002;
transition: none !important; /* 禁用所有过渡效果以提升性能 */ transition: none !important; /* 禁用所有过渡效果以提升性能 */
pointer-events: none; /* 防止拖拽时触发其他事件 */ pointer-events: none; /* 防止拖拽时触发其他事件 */
@ -6484,4 +6698,27 @@ watch(
color: rgba(0, 212, 255, 1); color: rgba(0, 212, 255, 1);
} }
} }
//
@keyframes messageSlideIn {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes streamingDot {
0%, 80%, 100% {
transform: scale(0.8);
opacity: 0.5;
}
40% {
transform: scale(1);
opacity: 1;
}
}
</style> </style>

View File

@ -66,7 +66,8 @@ export const MODULE_CONFIGS: ModuleConfig[] = [
icon: 'Activity', icon: 'Activity',
color: '#00D4FF', color: '#00D4FF',
description: '用于输入消息或指令的交互界面' description: '用于输入消息或指令的交互界面'
} },
] ]
// 主题色彩配置 // 主题色彩配置

View File

@ -322,7 +322,7 @@ export const generateCaseData = (): TypicalCase[] => {
{ {
id: '1', id: '1',
fileName: '附件CRH2C型动车组总配热损故障典型案例.pdf', 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司机反馈有焦糊味并操作降弓停车。', caseSummary: 'CRH2C型动车组在运行中因总配电柜CN3连接器热损及母板熏黑变色导致152线回路短路接地引发00车司机室感烟探头火灾报警代码401司机反馈有焦糊味并操作降弓停车。',
caseAnalysis: '', caseAnalysis: '',
emergencyPoints: '', emergencyPoints: '',
@ -331,7 +331,7 @@ export const generateCaseData = (): TypicalCase[] => {
{ {
id: '2', id: '2',
fileName: '附件CRH2C型动车组总配热损故障典型案例.pdf', 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屏显示异常同时触发司机室感烟探头报警。', caseSummary: '总配电柜热损故障中152线接地短路导致00车总配母板与148线回路导通造成全列【显示灯电源】断路器跳断虽受电弓实际升起但MON屏显示异常同时触发司机室感烟探头报警。',
caseAnalysis: '', caseAnalysis: '',
emergencyPoints: '', emergencyPoints: '',
@ -340,7 +340,7 @@ export const generateCaseData = (): TypicalCase[] => {
{ {
id: '3', id: '3',
fileName: '附件CRH2C型动车组总配热损故障典型案例.pdf', 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状态显示异常和受电弓显示故障。', caseSummary: '故障分析表明总配电柜内EBFBR继电器线圈对地短路引发大电流热损热损产生的烟雾触发感烟探头报警代码401并伴随VCB状态显示异常和受电弓显示故障。',
caseAnalysis: '', caseAnalysis: '',
emergencyPoints: '', emergencyPoints: '',
@ -399,14 +399,14 @@ export const generateRuleData = (): RuleRegulation[] => {
title: 'CRH380A动车组运行中触发火灾报警及设备故障案例', title: 'CRH380A动车组运行中触发火灾报警及设备故障案例',
content: '2025年5月25日南昌局CRH380A动车组运行中00车司机室总配电盘触发集成柜烟感探头火灾报警伴随ATP、CIR黑屏及断路器跳断故障。', content: '2025年5月25日南昌局CRH380A动车组运行中00车司机室总配电盘触发集成柜烟感探头火灾报警伴随ATP、CIR黑屏及断路器跳断故障。',
fileName: 'TB 9085.pdf', 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', id: '2',
title: '故障原因分析:过电压吸收器击穿短路引发连锁反应', title: '故障原因分析:过电压吸收器击穿短路引发连锁反应',
content: '故障原因为总配电柜内CIR主机供电接触器的过电压吸收器击穿短路导致过流引发电路板热损、绝缘失效间接触发烟感探头报警。', content: '故障原因为总配电柜内CIR主机供电接触器的过电压吸收器击穿短路导致过流引发电路板热损、绝缘失效间接触发烟感探头报警。',
fileName: 'TB 9085.pdf', 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: '广州南所-成都东', currentRoute: '广州南所-成都东',
followingTrainNumber: '0D1821\n00主', followingTrainNumber: '0D1821\n00主',
followingRoute: '成都东-天府动车所', 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: [ safetyReminders: [
{ {
id: '1', id: '1',
@ -599,7 +599,7 @@ export const generateFaultInfoData = (): FaultInfo => {
}, },
{ {
number: 3, number: 3,
content: '3若检查为旅客吸烟、吸入灰尘、水雾等导致假火情,在烟火报警控制器上进行复位。' content: '若检查为旅客吸烟、吸入灰尘、水雾等导致假火情,在烟火报警控制器上进行复位。'
} }
] ]
} }
@ -615,11 +615,11 @@ export const generatePersonnelData = (): PersonnelInfo[] => {
name: '向亮', name: '向亮',
phone: '013922466474', phone: '013922466474',
position: '技师', position: '技师',
department: '长沙动车乘务车间', department: '广州南动车乘务车间',
trainTypeQualifications: 'CRH2A、CRH380A、CR400AF、CR400BF', trainTypeQualifications: 'CRH2A、CRH380A、CR400AF、CR400BF',
trainingRecords: [ trainingRecords: [
{ {
title: '长沙西所通知[2025]45号--关于开展全员学习CRH3C-3028动车组网络、牵引及制动系统自主化改造内容的通知', title: '广州南所通知[2025]45号--关于开展全员学习CRH3C-3028动车组网络、牵引及制动系统自主化改造内容的通知',
date: '2025-01-15', date: '2025-01-15',
status: 'completed', status: 'completed',
statusText: '已完成' statusText: '已完成'
@ -649,7 +649,7 @@ export const generatePersonnelData = (): PersonnelInfo[] => {
name: '施文龙', name: '施文龙',
phone: '013128646100', phone: '013128646100',
position: '技师', position: '技师',
department: '长沙动车乘务车间', department: '广州南动车乘务车间',
trainTypeQualifications: 'CRH2A、CRH380A、CR400AF、CR400BF', trainTypeQualifications: 'CRH2A、CRH380A、CR400AF、CR400BF',
trainingRecords: [ trainingRecords: [
{ {

View File

@ -245,7 +245,8 @@ export type ModuleType =
| 'typicalCase' | 'typicalCase'
| 'ruleRegulation' | 'ruleRegulation'
| 'precautions' | 'precautions'
| 'inputCard'; | 'inputCard'
| 'chatResponse';
// 模块配置接口 // 模块配置接口
export interface ModuleConfig { export interface ModuleConfig {

File diff suppressed because it is too large Load Diff

View File

@ -163,6 +163,10 @@
@open-case-pdf="handleOpenCasePdf" @open-case-pdf="handleOpenCasePdf"
@show-card-selector="showCardSelector" @show-card-selector="showCardSelector"
@remove-selected-card="removeSelectedCard" @remove-selected-card="removeSelectedCard"
@ai-user-message="handleAIUserMessage"
@ai-stream-start="handleAIStreamStart"
@ai-stream-update="handleAIStreamUpdate"
@ai-stream-finish="handleAIStreamFinish"
/> />
</div> </div>
</section> </section>
@ -237,6 +241,15 @@
</div> </div>
</div> </div>
</el-dialog> </el-dialog>
<!-- AI响应抽屉 -->
<AIResponseDrawer
ref="aiDrawerRef"
v-model="showAIDrawer"
@close="handleAIDrawerClose"
@minimize="handleAIDrawerMinimize"
@maximize="handleAIDrawerMaximize"
/>
</div> </div>
</template> </template>
@ -246,6 +259,7 @@ import { useRoute } from 'vue-router';
import { MODULE_CONFIGS } from '@/constants/modules'; import { MODULE_CONFIGS } from '@/constants/modules';
import ModuleCard from '@/components/dashboard/ModuleCard.vue'; import ModuleCard from '@/components/dashboard/ModuleCard.vue';
import DetailModal from '@/components/dashboard/DetailModal.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 type { ModuleConfig, ModuleType, SearchResult, StatusStats, Position, Size, RuleRegulation, TypicalCase } from '@/types/dashboard';
import { webSocketService } from '@/utils/websocket'; import { webSocketService } from '@/utils/websocket';
import { baseURL } from '@/config'; import { baseURL } from '@/config';
@ -305,6 +319,10 @@ const questionTextarea = ref<HTMLTextAreaElement>(); // 输入框引用
const atSuggestionsStyle = ref({}); // @ const atSuggestionsStyle = ref({}); // @
const selectedCards = ref([]); // tag const selectedCards = ref([]); // tag
// AI
const showAIDrawer = ref(false); // AI
const aiDrawerRef = ref<InstanceType<typeof AIResponseDrawer>>(); // AI
// //
watch(showCardSelectorDialog, (newVal, oldVal) => { watch(showCardSelectorDialog, (newVal, oldVal) => {
// //
@ -1545,6 +1563,71 @@ const showNotification = (message: string, type: 'success' | 'error' | 'info' =
}, 3000); }, 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抽屉已展开');
};

View File

@ -4,6 +4,7 @@ import com.bjtds.brichat.entity.chat.AgentInputCardReq;
import com.bjtds.brichat.entity.chat.ChatMessage; import com.bjtds.brichat.entity.chat.ChatMessage;
import com.bjtds.brichat.entity.chat.ChatTypeVo; import com.bjtds.brichat.entity.chat.ChatTypeVo;
import com.bjtds.brichat.entity.dataset.TUserDataset; 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.mapper.opengauss.TUserDatasetMapper;
import com.bjtds.brichat.service.KnowledgeBaseService; import com.bjtds.brichat.service.KnowledgeBaseService;
import com.bjtds.brichat.service.TApiKeyService; import com.bjtds.brichat.service.TApiKeyService;
@ -131,14 +132,16 @@ public class ChatServiceImpl implements ChatService {
datasetIds.add(datasetId); 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); final boolean b = knowledgeBaseService.addDatasetsToWorkflow(appId, datasetIds);
//启动对话流 //启动对话流
ChatMessageSendRequest sendRequest = new ChatMessageSendRequest(); ChatMessageSendRequest sendRequest = new ChatMessageSendRequest();
sendRequest.setUserId(agentInputCardReq.getUserId()); sendRequest.setUserId(agentInputCardReq.getUserId());
sendRequest.setContent(agentInputCardReq.getContent()); sendRequest.setContent(agentInputCardReq.getContent());
sendRequest.setApiKey("app-jCKfKpPW8f9C7ZXLElAq1DSS"); sendRequest.setApiKey(workFlowInfo.getValue());
Map<String, Object> inputMap = new HashMap<>(); Map<String, Object> inputMap = new HashMap<>();
inputMap.put("sys_message_id", sysMessage); inputMap.put("sys_message_id", sysMessage);
inputMap.put("context", agentInputCardReq.getContext()); inputMap.put("context", agentInputCardReq.getContext());