| | |
| | | <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" |
| | |
| | | lastRenderTs: 0, |
| | | renderIntervalMs: 120, |
| | | stepChars: 6, |
| | | runTokenUsage: null, |
| | | userInput: '', |
| | | autoScrollThreshold: 80, |
| | | chats: [], |
| | |
| | | var current = this.findChat(this.currentChatId); |
| | | if (!current && this.resetting) return '新建会话,等待首条消息'; |
| | | if (!current) return '会话 ' + this.currentChatId; |
| | | return this.chatLabel(current); |
| | | var tokenText = this.tokenSummaryText(current); |
| | | return tokenText ? (this.chatLabel(current) + ' · ' + tokenText) : this.chatLabel(current); |
| | | }, |
| | | currentChatTokenSummary: function() { |
| | | var current = this.findChat(this.currentChatId); |
| | | return current ? this.tokenSummaryText(current) : ''; |
| | | }, |
| | | currentRunTokenSummary: function() { |
| | | return this.runTokenUsage ? this.tokenSummaryText(this.runTokenUsage, '本次') : ''; |
| | | }, |
| | | inlinePrompts: function() { |
| | | return this.promptPresets.slice(1); |
| | |
| | | }, |
| | | chatOptionLabel: function(chat) { |
| | | if (!chat) return '未命名会话'; |
| | | return this.chatLabel(chat) + ' · ' + (chat.size || 0) + ' 条 · ' + this.chatUpdatedAt(chat); |
| | | var suffix = this.tokenSummaryText(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; |
| | | }, |
| | | 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 + ')'; |
| | | }, |
| | | chatUpdatedAt: function(chat) { |
| | | if (!chat || !chat.updatedAt) return '刚刚创建'; |
| | |
| | | openChat: function(chatId) { |
| | | if (!chatId || this.streaming) return; |
| | | this.currentChatId = chatId; |
| | | this.runTokenUsage = null; |
| | | this.switchChat(); |
| | | }, |
| | | switchChat: function() { |
| | |
| | | if (this.streaming) return; |
| | | this.currentChatId = Date.now() + '_' + Math.random().toString(36).substr(2, 8); |
| | | this.resetting = true; |
| | | this.runTokenUsage = null; |
| | | this.clear(); |
| | | }, |
| | | deleteChat: function() { |
| | |
| | | if (!message) return; |
| | | this.loading = true; |
| | | this.streaming = true; |
| | | this.runTokenUsage = null; |
| | | this.messages.push({ role: 'user', text: message, ts: this.nowStr() }); |
| | | this.appendAssistantPlaceholder(); |
| | | this.scrollToBottom(true); |
| | |
| | | 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'); |
| | |
| | | this.clear(); |
| | | this.loading = true; |
| | | this.streaming = true; |
| | | this.runTokenUsage = null; |
| | | this.appendAssistantPlaceholder(); |
| | | this.scrollToBottom(true); |
| | | |
| | |
| | | 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'); |