#
Junjie
1 天以前 cbb00d4729243e4949b3c921fc2f94cb03ca8aaa
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);
      }
    });