From 7173a05b0694302aa12560abe720156f48aaf8d7 Mon Sep 17 00:00:00 2001 From: yiqi-017 <22300246009@m.fudan.edu.cn> Date: Sat, 6 Jun 2026 12:00:41 +0800 Subject: [PATCH 1/3] =?UTF-8?q?[FIX]:=E4=BC=B0=E7=AE=97=E4=BB=B7=E6=A0=BC?= =?UTF-8?q?=E6=94=B9=E6=88=90=E7=BC=93=E5=AD=98=E7=8E=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontends/desktop/static/app.js | 45 +++++++++++++++++------------ frontends/desktop/static/index.html | 6 ++-- frontends/desktop/static/styles.css | 12 +------- 3 files changed, 30 insertions(+), 33 deletions(-) diff --git a/frontends/desktop/static/app.js b/frontends/desktop/static/app.js index 737303d4..c8fa0114 100644 --- a/frontends/desktop/static/app.js +++ b/frontends/desktop/static/app.js @@ -329,7 +329,7 @@ const I18N = { 'collab.timeMin': '{n} 分钟前', 'collab.timeHr': '{n} 小时前', 'collab.timeDay': '{n} 天前', - 'page.token.title': 'Token 统计', 'page.token.sub': '每会话与累计的 token 用量及估算成本', + 'page.token.title': 'Token 统计', 'page.token.sub': '每会话与累计的 token 用量及缓存率', 'status.connecting': '连接中…', 'status.ready': '就绪', 'status.running': '运行中', 'status.disconnected': '未连接', 'status.stopped': '已停止', 'status.idle': '空闲', 'conv.emptyList': '暂无会话,点「+ 新对话」开始', 'conv.defaultTitle': '新对话', @@ -385,7 +385,7 @@ const I18N = { 'proc.imbotWechat': 'imbot · 微信', 'proc.imbotDing': 'imbot · 钉钉', 'proc.scheduler': '定时任务调度', 'cm.scheduling': '调度中', 'cm.running': '执行中', 'cm.idleSt': '空闲', 'cm.master': '已派 3 子任务', 'cm.w1': '子任务:抓取数据', 'cm.w2': '子任务:复核结果', 'cm.sub': '等待派单', - 'tok.total': '累计 token', 'tok.cost': '估算成本', 'tok.today': '今日 token', 'tok.tabAll': '聊天', 'tok.tabConductor': 'Conductor', 'tok.condTotal': 'Conductor 累计', 'tok.condCurrent': 'Conductor 本次', 'tok.condTip': 'Conductor 消耗的 token 不计入聊天累计 token 中', 'tok.disclaimer': '不同 API 网站的计费价格可能会有差异,请以实际网站为准。', 'tok.chartToggle': '趋势图', + 'tok.total': '累计 token', 'tok.cost': '缓存率', 'tok.today': '今日 token', 'tok.tabAll': '聊天', 'tok.tabConductor': 'Conductor', 'tok.condTotal': 'Conductor 累计', 'tok.condCurrent': 'Conductor 本次', 'tok.condTip': 'Conductor 消耗的 token 不计入聊天累计 token 中', 'tok.disclaimer': '不同 API 网站的计费价格可能会有差异,请以实际网站为准。', 'tok.chartToggle': '趋势图', 'tok.colSession': '会话', 'tok.colIn': '输入', 'tok.colOut': '输出', 'tok.colCacheW': '缓存写入', 'tok.colCache': '缓存读取', 'tok.colCost': '成本', 'tok.from': '从', 'tok.to': '到', 'tok.reset': '重置', 'tok.noData': '暂无记录', 'tok.deleted': '此会话已删除', 'tok.pricingUnknown': '⚠ 此模型计费规则尚未明确,按默认估算', @@ -481,7 +481,7 @@ const I18N = { 'collab.timeMin': '{n}m ago', 'collab.timeHr': '{n}h ago', 'collab.timeDay': '{n}d ago', - 'page.token.title': 'Token usage', 'page.token.sub': 'Per-session and total token usage & estimated cost', + 'page.token.title': 'Token usage', 'page.token.sub': 'Per-session and total token usage & cache rate', 'status.connecting': 'Connecting…', 'status.ready': 'Ready', 'status.running': 'Running', 'status.disconnected': 'Disconnected', 'status.stopped': 'Stopped', 'status.idle': 'Idle', 'conv.emptyList': 'No chats yet — click “+ New chat”', 'conv.defaultTitle': 'New chat', @@ -537,7 +537,7 @@ const I18N = { 'proc.imbotWechat': 'imbot · WeChat', 'proc.imbotDing': 'imbot · DingTalk', 'proc.scheduler': 'Scheduler', 'cm.scheduling': 'Scheduling', 'cm.running': 'Running', 'cm.idleSt': 'Idle', 'cm.master': 'Dispatched 3 subtasks', 'cm.w1': 'Subtask: fetch data', 'cm.w2': 'Subtask: review results', 'cm.sub': 'Waiting for tasks', - 'tok.total': 'Total tokens', 'tok.cost': 'Est. cost', 'tok.today': 'Today tokens', 'tok.tabAll': 'Chat', 'tok.tabConductor': 'Conductor', 'tok.condTotal': 'Conductor Total', 'tok.condCurrent': 'Conductor Current', 'tok.condTip': 'Conductor tokens are not included in chat totals', 'tok.disclaimer': 'Pricing may vary by API provider. Please refer to the actual website.', 'tok.chartToggle': 'Trend', + 'tok.total': 'Total tokens', 'tok.cost': 'Cache rate', 'tok.today': 'Today tokens', 'tok.tabAll': 'Chat', 'tok.tabConductor': 'Conductor', 'tok.condTotal': 'Conductor Total', 'tok.condCurrent': 'Conductor Current', 'tok.condTip': 'Conductor tokens are not included in chat totals', 'tok.disclaimer': 'Pricing may vary by API provider. Please refer to the actual website.', 'tok.chartToggle': 'Trend', 'tok.colSession': 'Session', 'tok.colIn': 'Input', 'tok.colOut': 'Output', 'tok.colCacheW': 'Cache write', 'tok.colCache': 'Cache read', 'tok.colCost': 'Cost', 'tok.from': 'From', 'tok.to': 'To', 'tok.reset': 'Reset', 'tok.noData': 'No records', 'tok.deleted': 'Session deleted', 'tok.pricingUnknown': '⚠ Pricing not confirmed, using defaults', @@ -3693,12 +3693,13 @@ function tokGetFiltered() { } function tokRenderStats(filtered, all) { - let total=0, cost=0; - filtered.forEach(r=>{total+=(r.input||0)+(r.output||0); cost+=parseFloat(estCost(r.input||0,r.output||0,r.model,r.cacheRead||0,r.cacheCreate||0));}); + let total=0, totalInput=0, totalCacheRead=0, totalCacheCreate=0; + filtered.forEach(r=>{total+=(r.input||0)+(r.output||0)+(r.cacheRead||0)+(r.cacheCreate||0); totalInput+=(r.input||0); totalCacheRead+=(r.cacheRead||0); totalCacheCreate+=(r.cacheCreate||0);}); if(tokTotalN) tokTotalN.textContent=fmtTok(total); - if(tokCostN) tokCostN.textContent='¥ '+cost.toFixed(1); + const cacheBase = totalInput + totalCacheRead + totalCacheCreate; + if(tokCostN) tokCostN.textContent= cacheBase > 0 ? (totalCacheRead / cacheBase * 100).toFixed(1) + '%' : '0%'; const todayStart=new Date(); todayStart.setHours(0,0,0,0); const todayTs=todayStart.getTime()/1000; - let todayT=0; all.filter(r=>r.ts>=todayTs).forEach(r=>{todayT+=(r.input||0)+(r.output||0);}); + let todayT=0; all.filter(r=>r.ts>=todayTs).forEach(r=>{todayT+=(r.input||0)+(r.output||0)+(r.cacheRead||0)+(r.cacheCreate||0);}); if(tokTodayN) tokTodayN.textContent=fmtTok(todayT); } @@ -3721,15 +3722,18 @@ function tokRenderTable(records) { if(_tokPage>=totalPages)_tokPage=totalPages-1; const pageItems=sorted.slice(_tokPage*TOK_PER_PAGE,(_tokPage+1)*TOK_PER_PAGE); for(const s of pageItems){ - let sc=0; s.prompts.forEach(p=>{sc+=parseFloat(estCost(p.input||0,p.output||0,p.model,p.cacheRead||0,p.cacheCreate||0));}); + const sCacheBase = s.input + s.cacheRead + s.cacheCreate; + const sCacheRate = sCacheBase > 0 ? (s.cacheRead / sCacheBase * 100).toFixed(1) + '%' : '0%'; const tr=document.createElement('tr'); tr.className='tok-row-session'; - tr.innerHTML=`