feat: header 增加当前会话总 token 消耗统计
- 新增 totalTokens 元素(帮助按钮右侧,🪙 图标 + 数字)
- chat-area.ts 新增 updateTotalTokens() 函数
- 四个触发点:新消息完成、新建会话、加载历史、应用初始化
- 数字自动格式化(toLocaleString)
This commit is contained in:
@@ -384,6 +384,18 @@ export function enableAutoScroll(): void {
|
|||||||
if (scrollBtnEl) scrollBtnEl.style.display = 'none';
|
if (scrollBtnEl) scrollBtnEl.style.display = 'none';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 统计当前会话总 token 消耗并更新 header 显示 */
|
||||||
|
export function updateTotalTokens(): void {
|
||||||
|
const session = state.get<ChatSession | null>(KEYS.CURRENT_SESSION);
|
||||||
|
const el = document.querySelector('#totalTokensValue');
|
||||||
|
if (!el) return;
|
||||||
|
if (!session) { el.textContent = '0'; return; }
|
||||||
|
const total = session.messages
|
||||||
|
.filter(m => m.role === 'assistant' && m.eval_count)
|
||||||
|
.reduce((sum, m) => sum + (m.eval_count || 0), 0);
|
||||||
|
el.textContent = total > 0 ? total.toLocaleString() : '0';
|
||||||
|
}
|
||||||
|
|
||||||
// ── 导出功能 ──
|
// ── 导出功能 ──
|
||||||
|
|
||||||
async function nativeSaveFile(defaultName: string, content: string): Promise<void> {
|
async function nativeSaveFile(defaultName: string, content: string): Promise<void> {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
import { state, KEYS } from '../state/state.js';
|
import { state, KEYS } from '../state/state.js';
|
||||||
import { debounce, escapeHtml, formatTime } from '../utils/utils.js';
|
import { debounce, escapeHtml, formatTime } from '../utils/utils.js';
|
||||||
import { exportAsMarkdown, exportAsHtml, exportAsTxt, renderMessages, clearMessages } from './chat-area.js';
|
import { exportAsMarkdown, exportAsHtml, exportAsTxt, renderMessages, clearMessages, updateTotalTokens } from './chat-area.js';
|
||||||
import { showConfirm } from './prompt-modal.js';
|
import { showConfirm } from './prompt-modal.js';
|
||||||
import { ChatDB } from '../db/chat-db.js';
|
import { ChatDB } from '../db/chat-db.js';
|
||||||
import { logSession, logWarn } from '../services/log-service.js';
|
import { logSession, logWarn } from '../services/log-service.js';
|
||||||
@@ -181,6 +181,7 @@ async function loadHistorySession(sessionId: string): Promise<void> {
|
|||||||
|
|
||||||
clearMessages();
|
clearMessages();
|
||||||
renderMessages();
|
renderMessages();
|
||||||
|
updateTotalTokens();
|
||||||
closeHistoryModal();
|
closeHistoryModal();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { detectLanguage, truncate, escapeHtml, formatSize } from '../utils/utils
|
|||||||
import { getSelectedModel, isThinkEnabled, isVisionAvailable, isToolCallingSupported } from './model-bar.js';
|
import { getSelectedModel, isThinkEnabled, isVisionAvailable, isToolCallingSupported } from './model-bar.js';
|
||||||
import {
|
import {
|
||||||
renderMessages, appendAssistantPlaceholder, appendSystemMessage,
|
renderMessages, appendAssistantPlaceholder, appendSystemMessage,
|
||||||
updateLastAssistantMessage, clearMessages, safeMarkdown, enableAutoScroll
|
updateLastAssistantMessage, clearMessages, safeMarkdown, enableAutoScroll, updateTotalTokens
|
||||||
} from './chat-area.js';
|
} from './chat-area.js';
|
||||||
import { showToast } from './toast.js';
|
import { showToast } from './toast.js';
|
||||||
import { isRagEnabled, performRagRetrieval } from './kb-modal.js';
|
import { isRagEnabled, performRagRetrieval } from './kb-modal.js';
|
||||||
@@ -521,6 +521,7 @@ export async function sendMessage(): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await saveCurrentSession();
|
await saveCurrentSession();
|
||||||
|
updateTotalTokens();
|
||||||
|
|
||||||
// ── 自动提取记忆(非阻塞)──
|
// ── 自动提取记忆(非阻塞)──
|
||||||
if (isMemoryEnabled() && freshSession.messages.length >= 6) {
|
if (isMemoryEnabled() && freshSession.messages.length >= 6) {
|
||||||
@@ -738,6 +739,7 @@ async function sendMessageWithAgentLoop(text: string, currentSession: ChatSessio
|
|||||||
updateLastAssistantMessage(assistantContent, thinkContent || null, loopStats || null);
|
updateLastAssistantMessage(assistantContent, thinkContent || null, loopStats || null);
|
||||||
renderMessages();
|
renderMessages();
|
||||||
await saveCurrentSession();
|
await saveCurrentSession();
|
||||||
|
updateTotalTokens();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
@@ -34,6 +34,7 @@
|
|||||||
<line x1="12" y1="17" x2="12.01" y2="17"/>
|
<line x1="12" y1="17" x2="12.01" y2="17"/>
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
|
<span class="total-tokens" id="totalTokens" title="当前会话总 token 消耗">🪙 <span id="totalTokensValue">0</span></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="header-right">
|
<div class="header-right">
|
||||||
<div class="conn-status pending" id="connStatus" title="连接状态">
|
<div class="conn-status pending" id="connStatus" title="连接状态">
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import { initToast, showToast } from './components/toast.js';
|
|||||||
import { initLightbox, closeLightbox } from './components/lightbox.js';
|
import { initLightbox, closeLightbox } from './components/lightbox.js';
|
||||||
import { initHeader, checkConnection } from './components/header.js';
|
import { initHeader, checkConnection } from './components/header.js';
|
||||||
import { initModelBar, loadModels, setSelectedModel } from './components/model-bar.js';
|
import { initModelBar, loadModels, setSelectedModel } from './components/model-bar.js';
|
||||||
import { initChatArea, renderMessages, clearMessages, enableAutoScroll } from './components/chat-area.js';
|
import { initChatArea, renderMessages, clearMessages, enableAutoScroll, updateTotalTokens } from './components/chat-area.js';
|
||||||
import { initInputArea } from './components/input-area.js';
|
import { initInputArea } from './components/input-area.js';
|
||||||
import { initSettingsModal, closeSettingsModal } from './components/settings-modal.js';
|
import { initSettingsModal, closeSettingsModal } from './components/settings-modal.js';
|
||||||
import { initHistoryModal, closeHistoryModal } from './components/history-modal.js';
|
import { initHistoryModal, closeHistoryModal } from './components/history-modal.js';
|
||||||
@@ -148,6 +148,7 @@ async function startNewSession(): Promise<void> {
|
|||||||
clearMessages();
|
clearMessages();
|
||||||
enableAutoScroll();
|
enableAutoScroll();
|
||||||
renderMessages();
|
renderMessages();
|
||||||
|
updateTotalTokens();
|
||||||
(document.querySelector('#chatInput') as HTMLElement)?.focus();
|
(document.querySelector('#chatInput') as HTMLElement)?.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -242,6 +243,7 @@ async function init(): Promise<void> {
|
|||||||
state.set('memoryEnabled', memoryEnabled);
|
state.set('memoryEnabled', memoryEnabled);
|
||||||
|
|
||||||
(document.querySelector('#chatInput') as HTMLElement)?.focus();
|
(document.querySelector('#chatInput') as HTMLElement)?.focus();
|
||||||
|
updateTotalTokens();
|
||||||
|
|
||||||
logSuccess('应用初始化完成');
|
logSuccess('应用初始化完成');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
@@ -369,6 +369,20 @@ html, body {
|
|||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.total-tokens {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
margin-left: 8px;
|
||||||
|
padding: 2px 8px;
|
||||||
|
font-size: 11px;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
background: var(--bg-subtle, rgba(255,255,255,0.04));
|
||||||
|
border-radius: 10px;
|
||||||
|
white-space: nowrap;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
/* ═══ 模型选择栏 ═══ */
|
/* ═══ 模型选择栏 ═══ */
|
||||||
.model-bar {
|
.model-bar {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
Reference in New Issue
Block a user