| | |
| | | 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; |
| | | } |
| | |
| | | renderIntervalMs: 120, |
| | | stepChars: 6, |
| | | runTokenUsage: null, |
| | | sessionTokenBase: null, |
| | | userInput: '', |
| | | autoScrollThreshold: 80, |
| | | chats: [], |
| | |
| | | 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() { |
| | |
| | | } |
| | | 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); |
| | |
| | | }, |
| | | 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 ''; |
| | |
| | | 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 '刚刚创建'; |
| | |
| | | if (!chatId || this.streaming) return; |
| | | this.currentChatId = chatId; |
| | | this.runTokenUsage = null; |
| | | this.sessionTokenBase = null; |
| | | this.resetting = false; |
| | | this.persistAssistantState(); |
| | | this.switchChat(); |
| | |
| | | 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 { |
| | |
| | | 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(); |
| | | }, |
| | |
| | | self.clearAssistantState(); |
| | | self.currentChatId = ''; |
| | | self.clear(); |
| | | self.runTokenUsage = null; |
| | | self.sessionTokenBase = null; |
| | | self.loadChats(false); |
| | | } |
| | | }) |
| | |
| | | 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; |
| | |
| | | }, |
| | | 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.onerror = function() { |
| | | self.stop(); |
| | | }; |
| | | this.resetting = false; |
| | | this.persistAssistantState(); |
| | | }, |
| | | ensureTyping: function() { |
| | | if (this.typingTimer) return; |
| | |
| | | clear: function() { |
| | | this.messages = []; |
| | | this.pendingText = ''; |
| | | if (!this.currentChatId) { |
| | | this.sessionTokenBase = null; |
| | | } |
| | | } |
| | | }, |
| | | mounted: function() { |