| src/main/java/com/zy/ai/controller/WcsDiagnosisController.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/main/java/com/zy/ai/entity/AiChatMessage.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/main/java/com/zy/ai/entity/ChatCompletionRequest.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/main/java/com/zy/ai/service/WcsDiagnosisService.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/main/java/com/zy/ai/service/impl/AiChatStoreServiceImpl.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/main/resources/sql/20260312_create_sys_ai_chat_storage.sql | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/main/webapp/views/ai/diagnosis.html | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 |
src/main/java/com/zy/ai/controller/WcsDiagnosisController.java
@@ -25,13 +25,14 @@ private AiUtils aiUtils; @GetMapping("/runAiStream") public SseEmitter runAiStream() { public SseEmitter runAiStream(@RequestParam(value = "chatId", required = false) String chatId, @RequestParam(value = "reset", required = false, defaultValue = "false") boolean reset) { SseEmitter emitter = new SseEmitter(0L); new Thread(() -> { try { WcsDiagnosisRequest request = aiUtils.makeAiRequest(1000, "对当前系统进行巡检,如果有异常情况就进行详细的分析,如果没有异常情况则当成一次检查\n\n"); wcsDiagnosisService.diagnoseStream(request, emitter); wcsDiagnosisService.diagnoseStream(request, chatId, reset, emitter); } catch (Exception e) { try { emitter.complete(); } catch (Exception ignore) {} } src/main/java/com/zy/ai/entity/AiChatMessage.java
@@ -28,6 +28,9 @@ private String content; @TableField("reasoning_content") private String reasoningContent; @TableField("create_time") private Date createTime; } src/main/java/com/zy/ai/entity/ChatCompletionRequest.java
@@ -21,6 +21,7 @@ public static class Message { private String role; // "user" / "assistant" / "system" private String content; private String reasoningContent; private String name; private String tool_call_id; private List<ToolCall> tool_calls; src/main/java/com/zy/ai/service/WcsDiagnosisService.java
@@ -35,8 +35,14 @@ @Autowired private AiChatStoreService aiChatStoreService; public void diagnoseStream(WcsDiagnosisRequest request, SseEmitter emitter) { public void diagnoseStream(WcsDiagnosisRequest request, String chatId, boolean reset, SseEmitter emitter) { List<ChatCompletionRequest.Message> messages = new ArrayList<>(); if (chatId != null && !chatId.isEmpty() && reset) { aiChatStoreService.deleteChat(chatId); } AiPromptTemplate promptTemplate = aiPromptTemplateService.resolvePublished(AiPromptScene.DIAGNOSE_STREAM.getCode()); ChatCompletionRequest.Message mcpSystem = new ChatCompletionRequest.Message(); @@ -47,7 +53,11 @@ mcpUser.setRole("user"); mcpUser.setContent(aiUtils.buildDiagnosisUserContentMcp(request)); runMcpStreamingDiagnosis(messages, mcpSystem, mcpUser, promptTemplate, 0.3, 2048, emitter, null); ChatCompletionRequest.Message storedUser = new ChatCompletionRequest.Message(); storedUser.setRole("user"); storedUser.setContent(buildDiagnoseDisplayPrompt(request)); runMcpStreamingDiagnosis(messages, mcpSystem, mcpUser, storedUser, promptTemplate, 0.3, 2048, emitter, chatId); } public void askStream(String prompt, @@ -77,7 +87,7 @@ mcpUser.setRole("user"); mcpUser.setContent(prompt == null ? "" : prompt); runMcpStreamingDiagnosis(messages, mcpSystem, mcpUser, promptTemplate, 0.3, 2048, emitter, finalChatId); runMcpStreamingDiagnosis(messages, mcpSystem, mcpUser, mcpUser, promptTemplate, 0.3, 2048, emitter, finalChatId); } public List<Map<String, Object>> listChats() { @@ -101,6 +111,7 @@ private void runMcpStreamingDiagnosis(List<ChatCompletionRequest.Message> baseMessages, ChatCompletionRequest.Message systemPrompt, ChatCompletionRequest.Message userQuestion, ChatCompletionRequest.Message storedUserQuestion, AiPromptTemplate promptTemplate, Double temperature, Integer maxTokens, @@ -115,6 +126,7 @@ throw new IllegalStateException("No MCP tools registered"); } AgentUsageStats usageStats = new AgentUsageStats(); StringBuilder reasoningBuffer = new StringBuilder(); baseMessages.add(systemPrompt); baseMessages.add(userQuestion); @@ -123,11 +135,13 @@ messages.addAll(baseMessages); sse(emitter, "<think>\\n正在初始化诊断与工具环境...\\n"); appendReasoning(reasoningBuffer, "正在初始化诊断与工具环境...\n"); int maxRound = 10; int i = 0; while(true) { sse(emitter, "\\n正在分析(第" + (i + 1) + "轮)...\\n"); appendReasoning(reasoningBuffer, "\n正在分析(第" + (i + 1) + "轮)...\n"); ChatCompletionResponse resp = llmChatService.chatCompletion(messages, temperature, maxTokens, tools); if (resp == null || resp.getChoices() == null || resp.getChoices().isEmpty() || resp.getChoices().get(0).getMessage() == null) { throw new IllegalStateException("LLM returned empty response"); @@ -137,6 +151,7 @@ ChatCompletionRequest.Message assistant = resp.getChoices().get(0).getMessage(); messages.add(assistant); sse(emitter, assistant.getContent()); appendReasoning(reasoningBuffer, assistant == null ? null : assistant.getContent()); List<ChatCompletionRequest.ToolCall> toolCalls = assistant.getTool_calls(); if (toolCalls == null || toolCalls.isEmpty()) { @@ -147,6 +162,7 @@ String toolName = tc != null && tc.getFunction() != null ? tc.getFunction().getName() : null; if (toolName == null || toolName.trim().isEmpty()) continue; sse(emitter, "\\n准备调用工具:" + toolName + "\\n"); appendReasoning(reasoningBuffer, "\n准备调用工具:" + toolName + "\n"); JSONObject args = new JSONObject(); if (tc.getFunction() != null && tc.getFunction().getArguments() != null && !tc.getFunction().getArguments().trim().isEmpty()) { try { @@ -166,6 +182,7 @@ output = err; } sse(emitter, "\\n工具返回,正在继续推理...\\n"); appendReasoning(reasoningBuffer, "\n工具返回,正在继续推理...\n"); ChatCompletionRequest.Message toolMsg = new ChatCompletionRequest.Message(); toolMsg.setRole("tool"); toolMsg.setTool_call_id(tc == null ? null : tc.getId()); @@ -177,6 +194,7 @@ } sse(emitter, "\\n正在根据数据进行分析...\\n</think>\\n\\n"); appendReasoning(reasoningBuffer, "\n正在根据数据进行分析...\n"); ChatCompletionRequest.Message diagnosisMessage = new ChatCompletionRequest.Message(); diagnosisMessage.setRole("system"); @@ -203,9 +221,10 @@ ChatCompletionRequest.Message a = new ChatCompletionRequest.Message(); a.setRole("assistant"); a.setContent(assistantBuffer.toString()); a.setReasoningContent(reasoningBuffer.toString()); aiChatStoreService.saveConversation(chatId, buildTitleFromPrompt(userQuestion.getContent()), userQuestion, buildTitleFromPrompt(storedUserQuestion == null ? null : storedUserQuestion.getContent()), storedUserQuestion == null ? userQuestion : storedUserQuestion, a, promptTemplate, usageStats.getPromptTokens(), @@ -260,6 +279,13 @@ payload.put("totalTokens", usageStats.getTotalTokens()); payload.put("llmCallCount", usageStats.getLlmCallCount()); return payload; } private void appendReasoning(StringBuilder reasoningBuffer, String text) { if (reasoningBuffer == null || text == null || text.isEmpty()) { return; } reasoningBuffer.append(text); } private void sendLargeText(SseEmitter emitter, String text) { @@ -378,6 +404,13 @@ } } private String buildDiagnoseDisplayPrompt(WcsDiagnosisRequest request) { if (request == null || request.getAlarmMessage() == null || request.getAlarmMessage().trim().isEmpty()) { return "对当前系统进行巡检"; } return request.getAlarmMessage().trim(); } private static class AgentUsageStats { private long promptTokens; private long completionTokens; src/main/java/com/zy/ai/service/impl/AiChatStoreServiceImpl.java
@@ -87,6 +87,7 @@ ChatCompletionRequest.Message message = new ChatCompletionRequest.Message(); message.setRole(row.getRole()); message.setContent(row.getContent()); message.setReasoningContent(row.getReasoningContent()); result.add(message); } return result; @@ -166,6 +167,7 @@ row.setSeqNo(seqNo); row.setRole(cut(source.getRole(), 32)); row.setContent(source.getContent()); row.setReasoningContent(source.getReasoningContent()); row.setCreateTime(now); aiChatMessageMapper.insert(row); } src/main/resources/sql/20260312_create_sys_ai_chat_storage.sql
@@ -29,6 +29,7 @@ `seq_no` INT NOT NULL COMMENT '顺序号', `role` VARCHAR(32) NOT NULL COMMENT '角色:user/assistant', `content` LONGTEXT COMMENT '消息内容', `reasoning_content` LONGTEXT COMMENT 'AI深度思考内容', `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', PRIMARY KEY (`id`), KEY `idx_sys_ai_chat_message_chat_seq` (`chat_id`, `seq_no`) src/main/webapp/views/ai/diagnosis.html
@@ -374,6 +374,18 @@ margin-bottom: 0; } .markdown-body > details.think-block + h1, .markdown-body > details.think-block + h2, .markdown-body > details.think-block + h3, .markdown-body > details.think-block + h4, .markdown-body > details.think-block + p, .markdown-body > details.think-block + ul, .markdown-body > details.think-block + ol, .markdown-body > details.think-block + pre, .markdown-body > details.think-block + blockquote { margin-top: 0; } .markdown-body p { margin: 0 0 10px; } @@ -680,6 +692,7 @@ renderIntervalMs: 120, stepChars: 6, runTokenUsage: null, sessionTokenBase: null, userInput: '', autoScrollThreshold: 80, chats: [], @@ -732,14 +745,15 @@ var current = this.findChat(this.currentChatId); if (!current && this.resetting) return '新建会话,等待首条消息'; if (!current) return '会话 ' + this.currentChatId; var tokenText = this.tokenSummaryText(current); var tokenText = this.sessionTokenSummaryText(current, '累计'); return tokenText ? (this.chatLabel(current) + ' · ' + tokenText) : this.chatLabel(current); }, currentChatTokenSummary: function() { var current = this.findChat(this.currentChatId); return current ? this.tokenSummaryText(current) : ''; return this.currentChatId ? this.sessionTokenSummaryText(current, '当前会话') : ''; }, currentRunTokenSummary: function() { if (this.currentChatId) return ''; return this.runTokenUsage ? this.tokenSummaryText(this.runTokenUsage, '本次') : ''; }, inlinePrompts: function() { @@ -811,12 +825,21 @@ } return DOMPurify.sanitize(marked.parse(source)); }, composeAssistantMarkdown: function(content, reasoningContent) { var answer = content || ''; var reasoning = reasoningContent || ''; if (!reasoning) { return answer; } return '<think>\n' + reasoning + '\n</think>\n\n' + answer; }, loadChats: function(preferKeepCurrent) { var self = this; fetch(baseUrl + '/ai/diagnose/chats', { headers: self.authHeaders() }) .then(function(r) { return r.json(); }) .then(function(arr) { self.chats = Array.isArray(arr) ? arr : []; self.syncSessionTokenState(); if (preferKeepCurrent && self.currentChatId) return; if (!self.currentChatId && self.sortedChats.length > 0) { self.openChat(self.sortedChats[0].chatId); @@ -842,13 +865,61 @@ }, chatOptionLabel: function(chat) { if (!chat) return '未命名会话'; var suffix = this.tokenSummaryText(chat); var suffix = this.sessionTokenSummaryText(chat, '累计'); return this.chatLabel(chat) + ' · ' + (chat.size || 0) + ' 条 · ' + this.chatUpdatedAt(chat) + (suffix ? (' · ' + suffix) : ''); }, numericValue: function(value) { if (value === null || value === undefined || value === '') return 0; var num = Number(value); return isNaN(num) ? 0 : num; }, buildTokenSnapshot: function(prompt, completion, total) { return { prompt: this.numericValue(prompt), completion: this.numericValue(completion), total: this.numericValue(total) }; }, sessionTokenSnapshot: function(chat) { return this.buildTokenSnapshot( chat ? chat.sumPromptTokens : 0, chat ? chat.sumCompletionTokens : 0, chat ? chat.sumTotalTokens : 0 ); }, expectedSessionTokenSnapshot: function() { if (!this.currentChatId || !this.runTokenUsage) { return null; } var base = this.sessionTokenBase || this.buildTokenSnapshot(0, 0, 0); return this.buildTokenSnapshot( base.prompt + this.numericValue(this.runTokenUsage.promptTokens), base.completion + this.numericValue(this.runTokenUsage.completionTokens), base.total + this.numericValue(this.runTokenUsage.totalTokens) ); }, mergedSessionTokenSnapshot: function(chat) { var snapshot = this.sessionTokenSnapshot(chat); var expected = this.expectedSessionTokenSnapshot(); if (!expected) { return snapshot; } if (snapshot.total >= expected.total) { return snapshot; } return expected; }, syncSessionTokenState: function() { if (!this.currentChatId || !this.runTokenUsage) { return; } var current = this.findChat(this.currentChatId); var snapshot = this.sessionTokenSnapshot(current); var expected = this.expectedSessionTokenSnapshot(); if (expected && snapshot.total >= expected.total) { this.runTokenUsage = null; this.sessionTokenBase = null; } }, tokenSummaryText: function(source, prefix) { if (!source) return ''; @@ -858,6 +929,12 @@ var completion = this.numericValue(source.completionTokens != null ? source.completionTokens : source.lastCompletionTokens); var label = prefix || '上次'; return label + ' tokens ' + total + '(输' + prompt + ' / 出' + completion + ')'; }, sessionTokenSummaryText: function(chat, prefix) { var snapshot = this.mergedSessionTokenSnapshot(chat); if (!snapshot || !snapshot.total) return ''; var label = prefix || '当前会话'; return label + ' tokens ' + snapshot.total + '(输' + snapshot.prompt + ' / 出' + snapshot.completion + ')'; }, chatUpdatedAt: function(chat) { if (!chat || !chat.updatedAt) return '刚刚创建'; @@ -882,6 +959,7 @@ if (!chatId || this.streaming) return; this.currentChatId = chatId; this.runTokenUsage = null; this.sessionTokenBase = null; this.resetting = false; this.persistAssistantState(); this.switchChat(); @@ -900,10 +978,11 @@ for (var i = 0; i < arr.length; i++) { var m = arr[i] || {}; if (m.role === 'assistant') { var md = self.composeAssistantMarkdown(m.content || '', m.reasoningContent || ''); msgs.push({ role: 'assistant', md: m.content || '', html: self.renderMarkdown(m.content || '', false), md: md, html: self.renderMarkdown(md, false), ts: self.nowStr() }); } else { @@ -930,6 +1009,7 @@ this.currentChatId = Date.now() + '_' + Math.random().toString(36).substr(2, 8); this.resetting = true; this.runTokenUsage = null; this.sessionTokenBase = this.buildTokenSnapshot(0, 0, 0); this.clear(); this.persistAssistantState(); }, @@ -946,6 +1026,8 @@ self.clearAssistantState(); self.currentChatId = ''; self.clear(); self.runTokenUsage = null; self.sessionTokenBase = null; self.loadChats(false); } }) @@ -993,6 +1075,8 @@ if (this.streaming) return; var message = (this.userInput || '').trim(); if (!message) return; var current = this.findChat(this.currentChatId); this.sessionTokenBase = this.sessionTokenSnapshot(current); this.loading = true; this.streaming = true; this.runTokenUsage = null; @@ -1035,15 +1119,30 @@ }, start: function() { if (this.streaming) return; this.clear(); var prompt = '对当前系统进行巡检,如果有异常情况就进行详细的分析,如果没有异常情况则当成一次检查'; var current = this.findChat(this.currentChatId); this.sessionTokenBase = this.sessionTokenSnapshot(current); this.loading = true; this.streaming = true; this.runTokenUsage = null; this.messages.push({ role: 'user', text: prompt, ts: this.nowStr() }); this.appendAssistantPlaceholder(); this.scrollToBottom(true); var self = this; this.source = new EventSource(baseUrl + '/ai/diagnose/runAiStream'); var url = baseUrl + '/ai/diagnose/runAiStream'; var params = []; if (this.currentChatId) { params.push('chatId=' + encodeURIComponent(this.currentChatId)); } if (this.resetting) { params.push('reset=true'); } if (params.length > 0) { url += '?' + params.join('&'); } this.persistAssistantState(); this.source = new EventSource(url); this.source.onopen = function() { self.loading = false; }; @@ -1066,6 +1165,8 @@ this.source.onerror = function() { self.stop(); }; this.resetting = false; this.persistAssistantState(); }, ensureTyping: function() { if (this.typingTimer) return; @@ -1123,6 +1224,9 @@ clear: function() { this.messages = []; this.pendingText = ''; if (!this.currentChatId) { this.sessionTokenBase = null; } } }, mounted: function() {