From cbb00d4729243e4949b3c921fc2f94cb03ca8aaa Mon Sep 17 00:00:00 2001
From: Junjie <fallin.jie@qq.com>
Date: 星期五, 27 三月 2026 18:47:43 +0800
Subject: [PATCH] #

---
 src/main/webapp/views/ai/diagnosis.html |  215 +++++++++++++++++++++++++++++++++++++++++++++++++++--
 1 files changed, 206 insertions(+), 9 deletions(-)

diff --git a/src/main/webapp/views/ai/diagnosis.html b/src/main/webapp/views/ai/diagnosis.html
index 70686ae..59aaacd 100644
--- a/src/main/webapp/views/ai/diagnosis.html
+++ b/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;
     }
@@ -616,7 +628,7 @@
         <footer class="composer-panel">
           <div class="composer-head">
             <div><strong>鍚� AI 鍔╂墜鎻愰棶</strong></div>
-            <div>{{ currentChatId ? '浼氳瘽宸茬粦瀹�' : '涓存椂浼氳瘽' }}</div>
+            <div>{{ currentRunTokenSummary || currentChatTokenSummary || (currentChatId ? '浼氳瘽宸茬粦瀹�' : '涓存椂浼氳瘽') }}</div>
           </div>
           <el-input
             v-model="userInput"
@@ -644,7 +656,7 @@
 
   <script type="text/javascript" src="../../static/vue/js/vue.min.js"></script>
   <script type="text/javascript" src="../../static/vue/element/element.js"></script>
-  <script type="text/javascript" src="../../static/js/common.js?v=20260309_i18n_fix1" charset="utf-8"></script>
+  <script type="text/javascript" src="../../static/js/common.js" charset="utf-8"></script>
   <script src="../../static/js/marked.min.js"></script>
   <script src="../../static/js/purify.min.js"></script>
   <script>
@@ -679,6 +691,8 @@
           lastRenderTs: 0,
           renderIntervalMs: 120,
           stepChars: 6,
+          runTokenUsage: null,
+          sessionTokenBase: null,
           userInput: '',
           autoScrollThreshold: 80,
           chats: [],
@@ -731,13 +745,54 @@
           var current = this.findChat(this.currentChatId);
           if (!current && this.resetting) return '鏂板缓浼氳瘽锛岀瓑寰呴鏉℃秷鎭�';
           if (!current) return '浼氳瘽 ' + this.currentChatId;
-          return this.chatLabel(current);
+          var tokenText = this.sessionTokenSummaryText(current, '绱');
+          return tokenText ? (this.chatLabel(current) + ' 路 ' + tokenText) : this.chatLabel(current);
+        },
+        currentChatTokenSummary: function() {
+          var current = this.findChat(this.currentChatId);
+          return this.currentChatId ? this.sessionTokenSummaryText(current, '褰撳墠浼氳瘽') : '';
+        },
+        currentRunTokenSummary: function() {
+          if (this.currentChatId) return '';
+          return this.runTokenUsage ? this.tokenSummaryText(this.runTokenUsage, '鏈') : '';
         },
         inlinePrompts: function() {
           return this.promptPresets.slice(1);
         }
       },
       methods: {
+        stateHost: function() {
+          try {
+            if (window.top && window.top !== window) {
+              return window.top;
+            }
+          } catch (e) {}
+          return window;
+        },
+        getAssistantState: function() {
+          var host = this.stateHost();
+          if (!host.__WCS_AI_ASSISTANT_STATE__) {
+            host.__WCS_AI_ASSISTANT_STATE__ = {};
+          }
+          return host.__WCS_AI_ASSISTANT_STATE__;
+        },
+        restoreAssistantState: function() {
+          var state = this.getAssistantState();
+          return {
+            chatId: state.currentChatId || '',
+            resetting: state.resetting === true
+          };
+        },
+        persistAssistantState: function() {
+          var state = this.getAssistantState();
+          state.currentChatId = this.currentChatId || '';
+          state.resetting = this.resetting === true;
+        },
+        clearAssistantState: function() {
+          var state = this.getAssistantState();
+          state.currentChatId = '';
+          state.resetting = false;
+        },
         authHeaders: function() {
           var token = localStorage.getItem('token');
           return token ? { token: token } : {};
@@ -770,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);
@@ -801,7 +865,76 @@
         },
         chatOptionLabel: function(chat) {
           if (!chat) return '鏈懡鍚嶄細璇�';
-          return this.chatLabel(chat) + ' 路 ' + (chat.size || 0) + ' 鏉� 路 ' + this.chatUpdatedAt(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 '';
+          var total = this.numericValue(source.totalTokens != null ? source.totalTokens : source.lastTotalTokens);
+          if (!total) return '';
+          var prompt = this.numericValue(source.promptTokens != null ? source.promptTokens : source.lastPromptTokens);
+          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 '鍒氬垰鍒涘缓';
@@ -825,6 +958,10 @@
         openChat: function(chatId) {
           if (!chatId || this.streaming) return;
           this.currentChatId = chatId;
+          this.runTokenUsage = null;
+          this.sessionTokenBase = null;
+          this.resetting = false;
+          this.persistAssistantState();
           this.switchChat();
         },
         switchChat: function() {
@@ -841,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 {
@@ -858,6 +996,7 @@
               self.messages = msgs;
               self.pendingText = '';
               self.resetting = false;
+              self.persistAssistantState();
               self.$nextTick(function() { self.scrollToBottom(true); });
             })
             .catch(function() {
@@ -869,7 +1008,10 @@
           if (this.streaming) return;
           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();
         },
         deleteChat: function() {
           var self = this;
@@ -881,8 +1023,11 @@
             .then(function(r) { return r.json(); })
             .then(function(ok) {
               if (ok === true) {
+                self.clearAssistantState();
                 self.currentChatId = '';
                 self.clear();
+                self.runTokenUsage = null;
+                self.sessionTokenBase = null;
                 self.loadChats(false);
               }
             })
@@ -930,8 +1075,11 @@
           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;
           this.messages.push({ role: 'user', text: message, ts: this.nowStr() });
           this.appendAssistantPlaceholder();
           this.scrollToBottom(true);
@@ -939,12 +1087,19 @@
           var url = baseUrl + '/ai/diagnose/askStream?prompt=' + encodeURIComponent(message);
           if (this.currentChatId) url += '&chatId=' + encodeURIComponent(this.currentChatId);
           if (this.resetting) url += '&reset=true';
+          this.persistAssistantState();
 
           this.source = new EventSource(url);
           var self = this;
           this.source.onopen = function() {
             self.loading = false;
           };
+          this.source.addEventListener('token_usage', function(e) {
+            if (!e || !e.data) return;
+            try {
+              self.runTokenUsage = JSON.parse(e.data);
+            } catch (ignore) {}
+          });
           this.source.onmessage = function(e) {
             if (!e || !e.data) return;
             var chunk = (e.data || '').replace(/\\n/g, '\n');
@@ -960,20 +1115,43 @@
           };
           this.userInput = '';
           this.resetting = false;
+          this.persistAssistantState();
         },
         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;
           };
+          this.source.addEventListener('token_usage', function(e) {
+            if (!e || !e.data) return;
+            try {
+              self.runTokenUsage = JSON.parse(e.data);
+            } catch (ignore) {}
+          });
           this.source.onmessage = function(e) {
             if (!e || !e.data) return;
             var chunk = (e.data || '').replace(/\\n/g, '\n');
@@ -987,6 +1165,8 @@
           this.source.onerror = function() {
             self.stop();
           };
+          this.resetting = false;
+          this.persistAssistantState();
         },
         ensureTyping: function() {
           if (this.typingTimer) return;
@@ -1044,12 +1224,29 @@
         clear: function() {
           this.messages = [];
           this.pendingText = '';
+          if (!this.currentChatId) {
+            this.sessionTokenBase = null;
+          }
         }
       },
       mounted: function() {
-        this.loadChats(false);
+        var restored = this.restoreAssistantState();
+        if (restored.chatId) {
+          this.currentChatId = restored.chatId;
+          this.resetting = restored.resetting;
+          if (this.resetting) {
+            this.clear();
+          } else {
+            this.switchChat();
+          }
+          this.loadChats(true);
+          return;
+        }
+        this.newChat();
+        this.loadChats(true);
       },
       beforeDestroy: function() {
+        this.persistAssistantState();
         this.stop(true);
       }
     });

--
Gitblit v1.9.1