dsh-cost-meter 1.8.1 → 1.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -7
- package/README.zh-CN.md +6 -6
- package/docs/billing-statistics.md +2 -2
- package/lib/client.statistics.js +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -8,9 +8,9 @@
|
|
|
8
8
|
|
|
9
9
|
Per-conversation cost · daily totals · OpenCode Go subscription quota display · budget with usage percentage · official account balance · custom provider balance · balance progress bar · history · peak/off-peak pricing hours display (peak hours UTC 01:00–04:00, 06:00–10:00; weekends and Chinese public holidays are off-peak all day, with separate labels) · pre-switch popup & system-notification alerts for peak/off-peak changes (position / lead time / alert type configurable) · one-click price sync from the official docs · Codex-style token usage heat grid · multi-vendor model pricing (built-in 90+ model price catalog with auto-matching) · mainstream Coding Plan quota queries & display (Anthropic / Z.ai / MiniMax / Kimi / OpenRouter / SiliconFlow / CommandCode / SCNet / Volcano Ark / Qwen / Xiaomi MiMo) plan/API dual-track billing (subscription quota vs pay-as-you-go money separated, per-1% & full-window token/equivalent-cost estimates with daily/weekly/monthly curves) · · quota strip above the input box (budget / Go / coding-plan usage in one row, toggleable)
|
|
10
10
|
|
|
11
|
-
[](https://github.com/Han-1413141/dsh-cost-meter)
|
|
12
12
|
|
|
13
|
-
**v1.8.
|
|
13
|
+
**v1.8.2** fixes the “Remote package already registered” error when opening cost statistics or conversation details (#210). Open **Conversation cost details** below the input box, or **Settings → Cost → Cost statistics**. Both DSH 0.1.7-rc.2 and 0.2.0-rc.2 client registration lifecycles are covered by regression tests. See the [statistics guide](docs/billing-statistics.md#english) and [release notes](docs/release-notes/v1.8.2.md).
|
|
14
14
|
|
|
15
15
|
Desktop users: follow the [Desktop installation instructions](docs/install-troubleshooting.md#desktop-安装与更新) for the application's own CLI and `desktop` Profile.
|
|
16
16
|
|
|
@@ -30,7 +30,7 @@ Desktop users: follow the [Desktop installation instructions](docs/install-troub
|
|
|
30
30
|
|
|
31
31
|
| Feature | Location | Description |
|
|
32
32
|
|---|---|---|
|
|
33
|
-
| Cost statistics | Below the conversation input / title bar / Settings → Cost → Cost statistics | Day, week, month, all retained and custom periods; API/Plan split, trends, model/conversation rankings and per-call costs. [Guide](docs/billing-statistics.md#english); statistics requires
|
|
33
|
+
| Cost statistics | Below the conversation input / title bar / Settings → Cost → Cost statistics | Day, week, month, all retained and custom periods; API/Plan split, trends, model/conversation rankings and per-call costs. [Guide](docs/billing-statistics.md#english); statistics requires DSH’s asynchronous module loader |
|
|
34
34
|
| Per-model cost card | Sidebar / composer dock (optional) | Disabled by default; inline Top-N, Other totals, shares and optional tokens, Today / Last 90 days, remembered expansion and a Top-1 chip. See the [guide](docs/model-cost-card.md#english) |
|
|
35
35
|
| Per-conversation cost | Below the composer / session title bar | Live accumulated cost + input/cache/output tokens; the composer footer shows cache hit rate before Input (cache reads / all input, including cache writes); position configurable |
|
|
36
36
|
| Official balance | Sidebar top / Settings page (configurable) | Total / granted / topped-up balance, auto-refresh + manual refresh; optional three-segment progress bar (blue/orange/gray), whose today segment only counts official-channel spend (coding plans / custom providers excluded) |
|
|
@@ -320,22 +320,22 @@ On Node.js 20, use `npm install -g pnpm@10` instead. See [pnpm installation and
|
|
|
320
320
|
dsh plugin --profile web add dsh-cost-meter
|
|
321
321
|
```
|
|
322
322
|
|
|
323
|
-
**PowerShell one-click script** (copy the whole line, paste, press Enter; pnpm is provisioned automatically, git is auto-detected — no clone needed; the install chain is **pinned to the release tag `v1.8.
|
|
323
|
+
**PowerShell one-click script** (copy the whole line, paste, press Enter; pnpm is provisioned automatically, git is auto-detected — no clone needed; the install chain is **pinned to the release tag `v1.8.2`** — review the script before running):
|
|
324
324
|
|
|
325
325
|
```powershell
|
|
326
|
-
irm https://raw.githubusercontent.com/Han-1413141/dsh-cost-meter/v1.8.
|
|
326
|
+
irm https://raw.githubusercontent.com/Han-1413141/dsh-cost-meter/v1.8.2/install.ps1 | iex
|
|
327
327
|
```
|
|
328
328
|
|
|
329
329
|
**Or a plain command line** (the machine must already have pnpm and git; also pinned to the tag):
|
|
330
330
|
|
|
331
331
|
```sh
|
|
332
|
-
dsh plugin --profile web add github:Han-1413141/dsh-cost-meter#v1.8.
|
|
332
|
+
dsh plugin --profile web add github:Han-1413141/dsh-cost-meter#v1.8.2
|
|
333
333
|
```
|
|
334
334
|
|
|
335
335
|
Without git, use the GitHub tag archive:
|
|
336
336
|
|
|
337
337
|
```sh
|
|
338
|
-
dsh plugin --profile web add https://github.com/Han-1413141/dsh-cost-meter/archive/refs/tags/v1.8.
|
|
338
|
+
dsh plugin --profile web add https://github.com/Han-1413141/dsh-cost-meter/archive/refs/tags/v1.8.2.tar.gz
|
|
339
339
|
```
|
|
340
340
|
|
|
341
341
|
After installing, **restart** `dsh web` (plugin rows, the Typert manifest and the client bundle are all scanned at startup):
|
package/README.zh-CN.md
CHANGED
|
@@ -8,9 +8,9 @@
|
|
|
8
8
|
|
|
9
9
|
本会话费用 · 当日费用 · OpenCode Go 订阅额度显示 · 预算与已用百分比 · 官方账户余额 · 自定义 Provider 余额查询(可配任意 HTTP 端点) · 余额三段进度条 · 历史记录 · 峰谷计价时段显示(UTC 01:00–04:00、06:00–10:00 为峰时段;周末与中国法定假日全天按谷价,分别标注) · 峰/谷切换前弹窗与系统通知提醒(位置/提前量/提醒类型可配) · 官方价格一键同步 · 类 Codex Token 用量热图 · 多厂商多模型价格计费(内置 90+ 模型价格目录与自动匹配) · 主流 Coding Plan 额度查询与显示(Anthropic / Z.ai / MiniMax / Kimi / OpenRouter / SiliconFlow / CommandCode / SCNet / 火山方舟 / 千问 / 小米 MiMo 十一家,含 Volcano Ark AK/SK 签名与 MiMo 控制台 Cookie 查询) · Plan/API 双轨计费(订阅额度与按量金额分离统计,每 1% 额度与满窗的 token/等值金额估算及日/周/月曲线) · 输入框上方额度横条(预算/Go/Coding Plan 用量一条横排显示,可开关)
|
|
10
10
|
|
|
11
|
-
[](https://github.com/Han-1413141/dsh-cost-meter)
|
|
12
12
|
|
|
13
|
-
**v1.8.
|
|
13
|
+
**v1.8.2**:修复打开计费统计或单会话明细时出现的「Remote package already registered」错误(#210)。入口为输入框下方的 **本会话费用明细**,或 **设置 → 费用 → 计费统计**。已补充 DSH 0.1.7-rc.2 与 0.2.0-rc.2 真实客户端注册流程的回归测试。详见[统计说明](docs/billing-statistics.md#简体中文)及[更新说明](docs/release-notes/v1.8.2.md)。
|
|
14
14
|
|
|
15
15
|
桌面端用户请按 [Desktop 安装说明](docs/install-troubleshooting.md#desktop-安装与更新),使用应用自带的 CLI 和 `desktop` Profile。
|
|
16
16
|
|
|
@@ -320,22 +320,22 @@ Node.js 20 请改用 `npm install -g pnpm@10`。版本要求见 [pnpm 官方安
|
|
|
320
320
|
dsh plugin --profile web add dsh-cost-meter
|
|
321
321
|
```
|
|
322
322
|
|
|
323
|
-
**PowerShell 一键脚本**(复制整行粘贴回车;自动补齐 pnpm、自动探测 git,无需克隆仓库;安装链**固定到发布 tag `v1.8.
|
|
323
|
+
**PowerShell 一键脚本**(复制整行粘贴回车;自动补齐 pnpm、自动探测 git,无需克隆仓库;安装链**固定到发布 tag `v1.8.2`**,建议先下载审阅再运行):
|
|
324
324
|
|
|
325
325
|
```powershell
|
|
326
|
-
irm https://raw.githubusercontent.com/Han-1413141/dsh-cost-meter/v1.8.
|
|
326
|
+
irm https://raw.githubusercontent.com/Han-1413141/dsh-cost-meter/v1.8.2/install.ps1 | iex
|
|
327
327
|
```
|
|
328
328
|
|
|
329
329
|
**或直接命令行**(机器上需已有 pnpm 与 git;同样固定到 tag):
|
|
330
330
|
|
|
331
331
|
```sh
|
|
332
|
-
dsh plugin --profile web add github:Han-1413141/dsh-cost-meter#v1.8.
|
|
332
|
+
dsh plugin --profile web add github:Han-1413141/dsh-cost-meter#v1.8.2
|
|
333
333
|
```
|
|
334
334
|
|
|
335
335
|
没有 git 时可用 GitHub tag 打包直链:
|
|
336
336
|
|
|
337
337
|
```sh
|
|
338
|
-
dsh plugin --profile web add https://github.com/Han-1413141/dsh-cost-meter/archive/refs/tags/v1.8.
|
|
338
|
+
dsh plugin --profile web add https://github.com/Han-1413141/dsh-cost-meter/archive/refs/tags/v1.8.2.tar.gz
|
|
339
339
|
```
|
|
340
340
|
|
|
341
341
|
安装后**重启** `dsh web`(插件行、Typert 清单与客户端 bundle 均在启动时扫描):
|
|
@@ -21,7 +21,7 @@ Call details are reconstructed on demand from one conversation's available usage
|
|
|
21
21
|
|
|
22
22
|
Cache hit rate is `cacheRead / (input + cacheRead + cacheWrite)`. Token composition is a token-count share, not a cost share. Reported reasoning tokens can overlap output and are shown separately; the configured reasoning rate determines any additional charge.
|
|
23
23
|
|
|
24
|
-
The statistics screen uses DSH's package-local asynchronous module loader
|
|
24
|
+
The statistics screen uses DSH's package-local asynchronous module loader. Since **1.8.2**, the main and statistics Remote contributions have distinct registration identities; their combined lifecycle is tested against the real **0.1.7-rc.2** and **0.2.0-rc.2** client registry/gateway. Older hosts without that loader show an upgrade message; existing metering and settings remain available. Both shipped client files stay below the 256 KiB per-file limit.
|
|
25
25
|
|
|
26
26
|
## 简体中文
|
|
27
27
|
|
|
@@ -44,7 +44,7 @@ API 费用是按上报用量和配置单价计算的估算,不是厂商账单
|
|
|
44
44
|
|
|
45
45
|
缓存命中率为 `缓存读取 / (未缓存输入 + 缓存读取 + 缓存写入)`。Token 构成展示数量占比,不是费用占比。推理 Token 可能包含在输出中,因此单独列示,不再次加入总 Token;是否另收推理费取决于配置的单价。
|
|
46
46
|
|
|
47
|
-
统计页使用 DSH
|
|
47
|
+
统计页使用 DSH 的异步模块加载器。从 **1.8.2** 起,主客户端与统计模块使用各自的 Remote 注册标识;两部分组合加载的流程已通过真实 **0.1.7-rc.2** 和 **0.2.0-rc.2** 客户端 registry/gateway 验证。没有该能力的旧宿主会显示升级提示,原有计费和设置继续可用。两个客户端文件分别遵守 256 KiB 大小限制。
|
|
48
48
|
|
|
49
49
|
## Design references
|
|
50
50
|
|
package/lib/client.statistics.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
window.__ModuleLoader__.load({id:"dsh-cost-meter",chunk:"client.statistics.js",factory:
|
|
1
|
+
window.__ModuleLoader__.load({id:"dsh-cost-meter",chunk:"client.statistics.js",factory:ht=>{const ft=ht("react"),{createElement:t,useState:k,useEffect:vt,Fragment:Q}=ft,yt=["input","output","cacheRead","cacheWrite","reasoning","calls","cost","apiCost"],S=(s,l)=>l==="total"?s.cost:l==="plan"?Math.max(0,s.cost-s.apiCost):s.apiCost,et=s=>s.input+s.cacheRead+s.cacheWrite+s.output,z=(s,l)=>l>0?(100*s/l).toFixed(1)+"%":"—",xt=(s,l)=>new Date(Date.parse(s)+l*864e5).toISOString().slice(0,10),H=s=>l=>{if(typeof l!==s||s==="number"&&!Number.isFinite(l))throw new Error("Invalid statistics "+s);return l},b=H("number"),r=H("string"),A=H("boolean"),K=s=>s===null?null:b(s),P=s=>l=>{if(!Array.isArray(l))throw new Error("Invalid statistics array");return l.map(s)},w=s=>l=>{if(!l||typeof l!="object"||Array.isArray(l))throw new Error("Invalid statistics object");return Object.fromEntries(Object.entries(s).map(([o,d])=>[o,d(l[o])]))},q=Object.fromEntries(yt.map(s=>[s,b])),st=w(q),nt=w({provider:r,model:r,bucket:r,tokens:b,rate:b,cost:b,priced:A,plan:A}),kt=w({kind:r,turn:K,step:K,provider:r,model:r,atMs:b,cost:b,apiCost:b,plan:A,priced:A,longContext:A,rows:P(nt)}),V=w({from:r,to:r,retainedFrom:r,retainedTo:r,totals:st,days:P(w({...q,date:r})),models:P(w({...q,key:r,provider:r,model:r,priced:A})),sessions:P(w({...q,id:r,title:r})),providers:P(r),modelOptions:P(r),sessionCount:b,offset:b,unassignedCost:b,unmodeledCost:b}),G=w({found:A,cost:b,apiCost:b,rows:P(nt),calls:P(kt),totalCalls:b,offset:b,recorded:st,kinds:P(w({...q,kind:r,unpriced:A})),turns:P(w({...q,turn:K,unpriced:A})),totalTurns:b,turnOffset:b}),lt=s=>{const l=w({from:r,to:r,provider:r,model:r,sessionId:r,basis:r,offset:b})(s);if(l.turnOffset=b(s.turnOffset??0),!["api","plan","total"].includes(l.basis)||![l.offset,l.turnOffset].every(o=>Number.isSafeInteger(o)&&o>=0&&o<=1e7))throw new Error("Invalid statistics query");return l},ct=(s,l)=>{const o={parse:l};return{mode:"strict",typeSymbol:"dsh-cost-meter#"+s,schema:o,create:()=>o}},ot={package:"dsh-cost-meter/statistics",descriptors:[["getBillingStatistics","BillingStatistics",V],["getSessionBilling","SessionBilling",G]].map(([s,l,o])=>({id:"dsh-cost-meter#costMeter/"+s,service:"costMeter",namespace:"costMeter",method:s,invocation:{kind:"direct"},parameters:[{name:"query",wire:"query",source:"json",codec:ct("StatisticsQuery",lt)}],result:ct(l,o)}))},Nt=`
|
|
2
2
|
.cm-stat{--cm-accent:#367dce;--cm-plan:#a16aca;--cm-muted:var(--dsw-alias-label-tertiary,#747f8e);color:var(--dsw-alias-label-primary,#1e293b);font-size:13px;line-height:1.5;container-type:inline-size}
|
|
3
3
|
.cm-stat *{box-sizing:border-box}.cm-stat h2,.cm-stat h3,.cm-stat p{margin:0}.cm-stat h2{font-size:24px;letter-spacing:-.6px}.cm-stat h3{font-size:14px;font-weight:650}
|
|
4
4
|
.cm-stat-head{display:flex;align-items:center;justify-content:space-between;gap:16px;margin:10px 0 20px}.cm-stat-sub{font-size:12px;color:var(--cm-muted)}
|
|
@@ -16,4 +16,4 @@ window.__ModuleLoader__.load({id:"dsh-cost-meter",chunk:"client.statistics.js",f
|
|
|
16
16
|
.cm-stat-table{width:100%;border-collapse:collapse;font-size:12px}.cm-stat-table th,.cm-stat-table td{padding:11px 9px;border-bottom:1px solid var(--dsw-alias-border-l1,#e0e5ed);text-align:right;font-variant-numeric:tabular-nums}.cm-stat-table th{font-weight:500;color:var(--cm-muted)}.cm-stat-table th:first-child,.cm-stat-table td:first-child{text-align:left}.cm-stat-table button{color:var(--cm-accent);border:0;background:transparent;text-align:left;padding:0;max-width:300px;overflow-wrap:anywhere}.cm-stat-scroll{overflow:auto}
|
|
17
17
|
.cm-stat-page{display:flex;gap:12px;align-items:center;justify-content:flex-end;margin-top:14px}.cm-stat-note{padding:10px 12px;background:var(--dsw-alias-bg-layer-2,#f5f7fa);border-radius:8px;margin:12px 0!important;font-size:12px;color:var(--cm-muted)}.cm-stat-error{color:var(--dsw-alias-state-error-primary,#c75040);white-space:pre-wrap}.cm-stat-empty{padding:30px;text-align:center;color:var(--cm-muted)}.cm-stat-call{padding:11px 0;border-bottom:1px solid var(--dsw-alias-border-l1,#dce3ec)}.cm-stat-call summary{cursor:pointer;overflow-wrap:anywhere}.cm-stat-call p{overflow-wrap:anywhere;margin-top:6px}
|
|
18
18
|
@container(max-width:700px){.cm-stat-metrics{grid-template-columns:repeat(2,minmax(0,1fr))}.cm-stat-grid{grid-template-columns:1fr}.cm-stat-panel{padding:12px}.cm-stat-head{align-items:start}.cm-stat-controls label{flex:1}.cm-stat input,.cm-stat select{max-width:100%;width:100%}.cm-stat-value{font-size:23px}}
|
|
19
|
-
`;function ot(s,l,o,d){const[m,c]=k({value:null,error:"",loading:!0}),a=JSON.stringify(o);return ft(()=>{let r=!0;return c({value:null,error:"",loading:!0}),s[l](JSON.parse(a)).then(y=>{r&&c({value:y,error:"",loading:!1})},y=>{r&&c({value:null,error:String(y.message??y),loading:!1})}),()=>{r=!1}},[s,l,a,d]),m}const B=(s,l,o={})=>t("button",{type:"button",className:"cm-stat-btn",onClick:l,...o},s);function V({offset:s,count:l,size:o,onChange:d,text:m}){return t("div",{className:"cm-stat-page"},t("span",{className:"cm-stat-sub"},l?`${s+1}–${Math.min(s+o,l)} / ${l}`:"0"),B(m("上一页","Previous"),()=>d(Math.max(0,s-o)),{disabled:!s}),B(m("下一页","Next"),()=>d(s+o),{disabled:s+o>=l}))}function rt({rows:s,money:l,text:o,onSelect:d,label:m}){const c=Math.max(0,...s.map(r=>r.value)),a=s.reduce((r,y)=>r+y.value,0);return t("div",null,t("div",{className:"cm-stat-panel-head"},t("h3",null,m),t("span",{className:"cm-stat-sub"},o("最高 ","Peak ")+l(c)+" · "+o("合计 ","Total ")+l(a))),t("div",{className:"cm-stat-chart",role:"group","aria-label":m},s.map((r,y)=>t("button",{key:y,type:"button",className:"cm-stat-bar",title:r.label+" · "+l(r.value),"aria-label":r.label+" · "+l(r.value),onClick:()=>d(r)},t("span",{style:{height:c?Math.max(1,r.value/c*100)+"%":"0%",opacity:r.value?void 0:.2}})))),t("div",{className:"cm-stat-axis"},t("span",null,s[0]?.label),t("span",null,s.at(-1)?.label)))}function it(s,l){const o=Math.max(1,Math.ceil(s.length/60)),d=[];for(let m=0;m<s.length;m+=o){const c=s.slice(m,m+o),a=c[0].date,r=c.at(-1).date;d.push({from:a,to:r,label:a===r?a:a+" – "+r,value:c.reduce((y,O)=>y+S(O,l),0)})}return d}function dt({api:s,query:l,revision:o,money:d,formatTokens:m,text:c}){const[a,r]=k(0),[y,O]=k(-1),[W,C]=k(0),E=ot(s,"getSessionBilling",{...l,offset:a,turnOffset:W},o),f=E.value,M=l.basis,R={input:c("输入","Input"),output:c("输出","Output"),cacheRead:c("缓存读取","Cache read"),cacheWrite:c("缓存写入","Cache write"),reasoning:c("推理","Reasoning")},j={model:c("模型调用","Model call"),compaction:c("上下文压缩","Compaction"),search:c("原生搜索","Native search")},x=n=>n==null?c("未标明轮次","Turn not recorded"):c("轮次 ","Turn ")+n;if(E.loading)return t("p",{className:"cm-stat-empty",role:"status"},c("正在读取本对话的用量明细…","Loading this conversation’s usage records…"));if(E.error)return t("p",{className:"cm-stat-error",role:"alert"},E.error);if(!f.found)return t("p",{className:"cm-stat-note"},c("没有可用的调用日志。上方账本统计仍然有效;明细不会按零费用处理。","Call logs are unavailable. The ledger totals above remain valid; missing details do not mean zero cost."));const G=n=>M==="total"||(M==="plan"?n.plan:!n.plan),F=Object.keys(R).map(n=>{const u=f.rows.filter(v=>v.bucket===n&&G(v));return{bucket:n,tokens:u.reduce((v,g)=>v+g.tokens,0),cost:u.reduce((v,g)=>v+g.cost,0),unpriced:u.some(v=>!v.priced)}}),T=S(f,M),L=S(f.recorded,M),X=Math.abs(T-L)>Math.max(1e-8,Math.abs(L)*1e-8)||f.totalCalls!==f.recorded.calls,U=(n,u)=>t("p",{key:u,className:"cm-stat-sub"},R[n.bucket]+": "+n.tokens.toLocaleString()+" × "+(n.priced?d(n.rate):"?")+" / 1,000,000 = "+(n.priced?d(n.cost):"?")),_=(n,u,v)=>t("div",{className:"cm-stat-scroll",style:{marginTop:20}},t("h3",null,n),t("table",{className:"cm-stat-table"},t("thead",null,t("tr",null,...[n,c("调用次数","Calls"),c("输入 / 缓存 / 输出 Tokens","Input / cache / output tokens"),"API",c("Plan 等值","Plan equivalent")].map(g=>t("th",{key:g},g)))),t("tbody",null,u.map((g,D)=>t("tr",{key:D},t("td",null,v(g)),t("td",null,g.calls),t("td",null,[g.input,g.cacheRead+g.cacheWrite,g.output].map(m).join(" / ")),t("td",null,d(g.apiCost)+(g.unpriced?" + ?":"")),t("td",null,d(Math.max(0,g.cost-g.apiCost))+(g.unpriced?" + ?":"")))))));return t("section",{className:"cm-stat-panel"},t("div",{className:"cm-stat-panel-head"},t("h3",null,c("单对话费用明细","Conversation cost details")),t("span",{className:"cm-stat-sub"},c("只统计本对话自身的调用","Own calls only; child conversations are separate"))),t("p",{className:"cm-stat-note"},c("明细按日志中的调用时间、用量和当前配置的历史价格规则计算。上方汇总采用已入账金额;调整价格后两者可能不同。","Details use logged usage and call times with the currently configured historical price rules. The summary above uses recorded ledger amounts; changing prices can produce a difference.")),X?t("p",{className:"cm-stat-note"},c("账本与可用明细不同:账本 ","Ledger and available details differ: ledger ")+d(L)+" / "+f.recorded.calls+c(" 次;明细 "," calls; details ")+d(T)+" / "+f.totalCalls+c(" 次。"," calls.")):null,t("div",{className:"cm-stat-scroll"},t("table",{className:"cm-stat-table"},t("thead",null,t("tr",null,...[c("费用构成","Cost component"),"Tokens",c("费用","Cost"),c("费用占比","Cost share")].map(n=>t("th",{key:n},n)))),t("tbody",null,F.map(n=>t("tr",{key:n.bucket},t("td",null,R[n.bucket]),t("td",null,n.tokens.toLocaleString()),t("td",null,d(n.cost)+(n.unpriced?" + ?":"")),t("td",null,z(n.cost,T))))))),t("p",{className:"cm-stat-sub",style:{marginTop:10}},c("推理 Token 可能包含在输出中;单价为 0 时不另收推理费。未定价用量显示 ?。","Reasoning tokens may overlap output; a zero reasoning rate adds no separate charge. Unpriced usage is marked ?.")),_(c("按调用类型统计","Cost by call type"),f.kinds,n=>j[n.kind]??n.kind),_(c("按轮次统计","Cost by turn"),f.turns,n=>x(n.turn)),t("p",{className:"cm-stat-sub"},c("轮次沿用日志编号;未记录轮次的调用单列。每轮包含其全部调用,跨调用分页也不会拆分。","Turn numbers come from the log; calls without one are listed separately. Each turn includes all its calls, across call pages.")),t(V,{offset:W,count:f.totalTurns,size:25,onChange:C,text:c}),t("div",{style:{marginTop:24}},t(rt,{rows:f.calls.map((n,u)=>({label:"#"+(a+u+1),index:u,value:S(n,M)})),money:d,text:c,label:c("逐次调用费用 · 当前页","Cost per call · current page"),onSelect:n=>{O(n.index),document.getElementById("cm-stat-call-"+n.index)?.scrollIntoView({block:"nearest",behavior:"smooth"})}})),f.calls.map((n,u)=>t("details",{key:u,id:"cm-stat-call-"+u,className:"cm-stat-call",open:y===u,onToggle:v=>{!v.currentTarget.open&&y===u&&O(-1)}},t("summary",null,"#"+(a+u+1)+" · "+x(n.turn)+(n.step==null?"":c(" / 步骤 "," / step ")+n.step)+" · "+(j[n.kind]??n.kind)+" · "+n.provider+" / "+n.model+" · "+(n.plan?"Plan ":"API ")+(n.priced?d(n.cost):"?")+(n.longContext?c(" · 长上下文价"," · long-context rate"):"")),t("p",{className:"cm-stat-sub"},new Date(n.atMs).toLocaleString()),n.rows.map(U))),t(V,{offset:a,count:f.totalCalls,size:50,onChange:n=>{r(n),O(-1)},text:c}))}function mt({state:s,api:l,sessionId:o="",formatMoneyUsd:d,formatTokens:m}){const c=s.config.locale==="en"||s.config.locale==="auto"&&!/^zh\b/i.test(navigator.language),a=(e,N)=>c?N:e,[r,y]=k(o?"all":"week"),[O,W]=k(null),[C,E]=k({id:o,title:o}),[f,M]=k(""),[R,j]=k(""),[x,G]=k(o?"total":"api"),[F,T]=k(0),[L,X]=k(0),[U,_]=k(!1),n=s.meta.dayKey||new Date().toISOString().slice(0,10),u=r==="all"?"":r==="custom"?O?.from||n:yt(n,r==="week"?-6:r==="month"?-29:0),v=r==="custom"&&O?.to||n,g={from:u,to:v,provider:f,model:R,basis:x,sessionId:C.id,offset:F},D=ot(l,"getBillingStatistics",g,L+":"+s.meta.now),p=D.value,I=e=>d(e,{...s.config,decimals:Math.max(e!==0&&Math.abs(e)<.01?8:4,s.config.decimals??2)}),pt=e=>{y(e),T(0)},ut=e=>{E(e),T(0),M(""),j("")},wt=x==="total"?a("API + Plan 等值","API + Plan equivalent"):x==="plan"?a("Plan 等值","Plan equivalent"):a("API 费用","API cost"),Y=(e,N,Z,Pt)=>t("label",null,e,t("select",{value:N,onChange:J=>{Z(J.target.value),T(0)}},Pt.map(([J,Tt])=>t("option",{key:J,value:J},Tt)))),St={input:a("未缓存输入","Uncached input"),cacheRead:a("缓存读取","Cache read"),cacheWrite:a("缓存写入","Cache write"),output:a("输出","Output")},gt=(e,N)=>t("section",{className:"cm-stat-panel"},t("div",{className:"cm-stat-panel-head"},t("h3",null,e)),N),h=p?.totals,$=(e,N,Z)=>t("div",{className:"cm-stat-metric",key:e},t("div",{className:"cm-stat-sub"},e),t("div",{className:"cm-stat-value"},N),t("div",{className:"cm-stat-sub"},Z));return t("div",{className:"cm-stat"},t("style",null,Nt),t("header",{className:"cm-stat-head"},t("div",null,t("h2",null,a("计费统计","Cost statistics")),t("p",{className:"cm-stat-sub"},C.id?a("单对话 · ","Conversation · ")+(C.title===C.id&&p?.sessions[0]?.title||C.title):a("全部对话 · 从总额查看每一笔调用","All conversations · from totals to individual calls"))),B(a("刷新","Refresh"),()=>X(e=>e+1))),C.id?B(a("← 全部对话","← All conversations"),()=>ut({id:"",title:""})):null,t("div",{className:"cm-stat-controls"},t("div",{className:"cm-stat-periods"},...[["today",a("今天","Today")],["week",a("近 7 天","Last 7 days")],["month",a("近 30 天","Last 30 days")],["all",a("全部记录","All retained")],["custom",a("自定义","Custom")]].map(([e,N])=>B(N,()=>pt(e),{key:e,"aria-pressed":r===e})))),r==="custom"?t("div",{className:"cm-stat-controls"},...["from","to"].map(e=>t("label",{key:e},e==="from"?a("开始日期","From"):a("结束日期","To"),t("input",{type:"date",value:e==="from"?u:v,onChange:N=>{W({from:u,to:v,[e]:N.target.value}),T(0)}})))):null,t("div",{className:"cm-stat-controls"},Y(a("计费口径","Cost basis"),x,G,[["api",a("API 费用","API cost")],["plan",a("Plan 等值","Plan equivalent")],["total",a("API + Plan 等值","API + Plan equivalent")]]),Y(a("提供商","Provider"),f,e=>{M(e),j("")},[["",a("全部提供商","All providers")],...[...new Set([f,...p?.providers??[]])].filter(Boolean).map(e=>[e,e])]),Y(a("模型","Model"),R,j,[["",a("全部模型","All models")],...[...new Set([R,...p?.modelOptions??[]])].filter(Boolean).map(e=>[e,e])])),t("p",{className:"cm-stat-sub"},a("按宿主时区归日:","Days use the host timezone: ")+(s.meta.timezone||"UTC")+" · "+a("API 金额是按已记录用量和配置单价计算的估算;Plan 为订阅用量的 API 等值,不是订阅账单。外部导入用量在原概览中单列。","API amounts estimate recorded usage at configured rates; Plan is API-equivalent usage, not the subscription invoice. External usage remains separate in Overview.")),D.loading?t("p",{className:"cm-stat-empty",role:"status"},a("加载统计…","Loading statistics…")):D.error?t("p",{className:"cm-stat-error",role:"alert"},D.error):p?t(Q,null,t("p",{className:"cm-stat-sub",style:{marginTop:8}},p.from+" – "+p.to+(p.retainedFrom?" · "+a("账本保留范围 ","Retained ledger ")+p.retainedFrom+" – "+p.retainedTo:"")),t("div",{className:"cm-stat-metrics"},$(a("API 费用","API cost"),I(h.apiCost),a("已入账估算","Recorded estimate")),$(a("Plan 等值费用","Plan equivalent"),I(Math.max(0,h.cost-h.apiCost)),a("不代表实际扣款","Not an actual debit")),$(a("调用次数","Calls"),h.calls.toLocaleString(),p.sessionCount+a(" 个对话 · 平均 "," conversations · average ")+I(h.calls?S(h,x)/h.calls:0)),$(a("缓存命中率","Cache hit rate"),z(h.cacheRead,h.input+h.cacheRead+h.cacheWrite),m(tt(h))+" Tokens")),C.id?t(dt,{key:JSON.stringify([C.id,u,v,f,R,x]),api:l,query:{...g,from:p.from,to:p.to,offset:0},revision:L,money:I,formatTokens:m,text:a}):null,p.models.some(e=>!e.priced)?t("p",{className:"cm-stat-note"},a("部分模型当前未配置价格,金额可能不完整;未定价不等于免费。","Some models have no configured price. Amounts may be incomplete; unpriced usage is not free.")):null,!h.calls&&!h.cost?t("p",{className:"cm-stat-empty"},a("所选范围没有已记录的用量。","No recorded usage in this range.")):null,t("section",{className:"cm-stat-panel"},t(rt,{rows:it(p.days,x),money:I,text:a,label:a("费用趋势 · ","Cost over time · ")+wt,onSelect:e=>{W({from:e.from,to:e.to}),pt("custom")}}),t("p",{className:"cm-stat-sub",style:{marginTop:12}},a("点击柱形查看该日期范围。长区间按相邻日期合并,保留全部金额。","Select a bar to inspect its date range. Long ranges group adjacent days without dropping costs."))),t("div",{className:"cm-stat-grid"},gt(a("模型费用排行","Cost by model"),t(Q,null,t("ol",{className:"cm-stat-rank"},(U?p.models:p.models.slice(0,8)).map(e=>t("li",{key:e.key},t("button",{type:"button",onClick:()=>{M(e.provider),j(e.model),T(0)},title:e.key},t("div",{className:"cm-stat-rankline"},t("span",null,e.model),t("span",{className:"cm-stat-number"},I(S(e,x))+(e.priced?"":" + ?"))),t("div",{className:"cm-stat-sub"},e.provider+" · "+e.calls+a(" 次 · "," calls · ")+z(S(e,x),S(h,x))),t("div",{className:"cm-stat-track"},t("span",{style:{width:z(S(e,x),S(h,x))}}))))),p.models.length>8?B(U?a("收起","Show less"):a("显示全部模型","Show all models"),()=>_(e=>!e)):null,p.unmodeledCost>1e-8?t("p",{className:"cm-stat-note"},a("历史未分配到模型的费用:","Historical cost without a model: ")+I(p.unmodeledCost)):null)),gt(a("Token 构成","Token composition"),t(Q,null,t("ol",{className:"cm-stat-rank"},Object.entries(St).map(([e,N])=>t("li",{key:e},t("div",{className:"cm-stat-rankline"},t("span",null,N),t("span",{className:"cm-stat-number"},m(h[e]))),t("div",{className:"cm-stat-track"},t("span",{style:{width:z(h[e],tt(h)),background:e.startsWith("cache")?"var(--cm-plan)":void 0}}))))),t("p",{className:"cm-stat-note"},a("这里展示 Token 数量占比,不是费用占比。缓存写入计入输入分母,不算缓存命中。","These are token shares, not cost shares. Cache writes count as input, not cache hits.")),t("p",{className:"cm-stat-sub"},a("单独上报的推理 Token:","Reported reasoning tokens: ")+m(h.reasoning))))),C.id?null:t("section",{className:"cm-stat-panel"},t("div",{className:"cm-stat-panel-head"},t("h3",null,a("对话费用排行","Cost by conversation")),t("span",{className:"cm-stat-sub"},a("点击对话查看明细 · 子代理分别计入,不重复相加","Select a conversation for details · each agent counted once"))),t("div",{className:"cm-stat-scroll"},t("table",{className:"cm-stat-table"},t("thead",null,t("tr",null,...[a("对话","Conversation"),a("调用","Calls"),"API",a("Plan 等值","Plan equivalent"),a("缓存命中","Cache hits")].map(e=>t("th",{key:e},e)))),t("tbody",null,p.sessions.map(e=>t("tr",{key:e.id},t("td",null,t("button",{type:"button",title:e.id,onClick:()=>ut({id:e.id,title:e.title})},e.title)),t("td",null,e.calls.toLocaleString()),t("td",null,I(e.apiCost)),t("td",null,I(Math.max(0,e.cost-e.apiCost))),t("td",null,z(e.cacheRead,e.input+e.cacheRead+e.cacheWrite))))))),p.unassignedCost>1e-8?t("p",{className:"cm-stat-note"},a("未关联对话的费用:","Cost not linked to a conversation: ")+I(p.unassignedCost)):null,t(V,{offset:F,count:p.sessionCount,size:25,onChange:T,text:a})))):null)}async function Ct(s){const l=await s.get("remote").$mount(ct);s.effect(()=>()=>l(),"cost-meter: statistics contribution");const o=s.get("remote.costMeter"),d=Object.fromEntries(["getBillingStatistics","getSessionBilling"].map(m=>[m,async c=>{const a=await o[m](c);if(!a?.ok)throw new Error(a?.error?.message||"Statistics request failed");return a.value}]));return m=>t(mt,{...m,api:d})}return{mount:Ct,Statistics:mt,SessionDetail:dt,CONTRIBUTION:ct,parseStatistics:st,parseDetail:nt,dailyChartRows:it}}});
|
|
19
|
+
`;function it(s,l,o,d){const[m,c]=k({value:null,error:"",loading:!0}),a=JSON.stringify(o);return vt(()=>{let i=!0;return c({value:null,error:"",loading:!0}),s[l](JSON.parse(a)).then(y=>{i&&c({value:y,error:"",loading:!1})},y=>{i&&c({value:null,error:String(y.message??y),loading:!1})}),()=>{i=!1}},[s,l,a,d]),m}const B=(s,l,o={})=>t("button",{type:"button",className:"cm-stat-btn",onClick:l,...o},s);function X({offset:s,count:l,size:o,onChange:d,text:m}){return t("div",{className:"cm-stat-page"},t("span",{className:"cm-stat-sub"},l?`${s+1}–${Math.min(s+o,l)} / ${l}`:"0"),B(m("上一页","Previous"),()=>d(Math.max(0,s-o)),{disabled:!s}),B(m("下一页","Next"),()=>d(s+o),{disabled:s+o>=l}))}function rt({rows:s,money:l,text:o,onSelect:d,label:m}){const c=Math.max(0,...s.map(i=>i.value)),a=s.reduce((i,y)=>i+y.value,0);return t("div",null,t("div",{className:"cm-stat-panel-head"},t("h3",null,m),t("span",{className:"cm-stat-sub"},o("最高 ","Peak ")+l(c)+" · "+o("合计 ","Total ")+l(a))),t("div",{className:"cm-stat-chart",role:"group","aria-label":m},s.map((i,y)=>t("button",{key:y,type:"button",className:"cm-stat-bar",title:i.label+" · "+l(i.value),"aria-label":i.label+" · "+l(i.value),onClick:()=>d(i)},t("span",{style:{height:c?Math.max(1,i.value/c*100)+"%":"0%",opacity:i.value?void 0:.2}})))),t("div",{className:"cm-stat-axis"},t("span",null,s[0]?.label),t("span",null,s.at(-1)?.label)))}function dt(s,l){const o=Math.max(1,Math.ceil(s.length/60)),d=[];for(let m=0;m<s.length;m+=o){const c=s.slice(m,m+o),a=c[0].date,i=c.at(-1).date;d.push({from:a,to:i,label:a===i?a:a+" – "+i,value:c.reduce((y,O)=>y+S(O,l),0)})}return d}function mt({api:s,query:l,revision:o,money:d,formatTokens:m,text:c}){const[a,i]=k(0),[y,O]=k(-1),[W,C]=k(0),E=it(s,"getSessionBilling",{...l,offset:a,turnOffset:W},o),f=E.value,M=l.basis,R={input:c("输入","Input"),output:c("输出","Output"),cacheRead:c("缓存读取","Cache read"),cacheWrite:c("缓存写入","Cache write"),reasoning:c("推理","Reasoning")},j={model:c("模型调用","Model call"),compaction:c("上下文压缩","Compaction"),search:c("原生搜索","Native search")},x=n=>n==null?c("未标明轮次","Turn not recorded"):c("轮次 ","Turn ")+n;if(E.loading)return t("p",{className:"cm-stat-empty",role:"status"},c("正在读取本对话的用量明细…","Loading this conversation’s usage records…"));if(E.error)return t("p",{className:"cm-stat-error",role:"alert"},E.error);if(!f.found)return t("p",{className:"cm-stat-note"},c("没有可用的调用日志。上方账本统计仍然有效;明细不会按零费用处理。","Call logs are unavailable. The ledger totals above remain valid; missing details do not mean zero cost."));const Y=n=>M==="total"||(M==="plan"?n.plan:!n.plan),F=Object.keys(R).map(n=>{const u=f.rows.filter(v=>v.bucket===n&&Y(v));return{bucket:n,tokens:u.reduce((v,g)=>v+g.tokens,0),cost:u.reduce((v,g)=>v+g.cost,0),unpriced:u.some(v=>!v.priced)}}),T=S(f,M),L=S(f.recorded,M),Z=Math.abs(T-L)>Math.max(1e-8,Math.abs(L)*1e-8)||f.totalCalls!==f.recorded.calls,U=(n,u)=>t("p",{key:u,className:"cm-stat-sub"},R[n.bucket]+": "+n.tokens.toLocaleString()+" × "+(n.priced?d(n.rate):"?")+" / 1,000,000 = "+(n.priced?d(n.cost):"?")),_=(n,u,v)=>t("div",{className:"cm-stat-scroll",style:{marginTop:20}},t("h3",null,n),t("table",{className:"cm-stat-table"},t("thead",null,t("tr",null,...[n,c("调用次数","Calls"),c("输入 / 缓存 / 输出 Tokens","Input / cache / output tokens"),"API",c("Plan 等值","Plan equivalent")].map(g=>t("th",{key:g},g)))),t("tbody",null,u.map((g,D)=>t("tr",{key:D},t("td",null,v(g)),t("td",null,g.calls),t("td",null,[g.input,g.cacheRead+g.cacheWrite,g.output].map(m).join(" / ")),t("td",null,d(g.apiCost)+(g.unpriced?" + ?":"")),t("td",null,d(Math.max(0,g.cost-g.apiCost))+(g.unpriced?" + ?":"")))))));return t("section",{className:"cm-stat-panel"},t("div",{className:"cm-stat-panel-head"},t("h3",null,c("单对话费用明细","Conversation cost details")),t("span",{className:"cm-stat-sub"},c("只统计本对话自身的调用","Own calls only; child conversations are separate"))),t("p",{className:"cm-stat-note"},c("明细按日志中的调用时间、用量和当前配置的历史价格规则计算。上方汇总采用已入账金额;调整价格后两者可能不同。","Details use logged usage and call times with the currently configured historical price rules. The summary above uses recorded ledger amounts; changing prices can produce a difference.")),Z?t("p",{className:"cm-stat-note"},c("账本与可用明细不同:账本 ","Ledger and available details differ: ledger ")+d(L)+" / "+f.recorded.calls+c(" 次;明细 "," calls; details ")+d(T)+" / "+f.totalCalls+c(" 次。"," calls.")):null,t("div",{className:"cm-stat-scroll"},t("table",{className:"cm-stat-table"},t("thead",null,t("tr",null,...[c("费用构成","Cost component"),"Tokens",c("费用","Cost"),c("费用占比","Cost share")].map(n=>t("th",{key:n},n)))),t("tbody",null,F.map(n=>t("tr",{key:n.bucket},t("td",null,R[n.bucket]),t("td",null,n.tokens.toLocaleString()),t("td",null,d(n.cost)+(n.unpriced?" + ?":"")),t("td",null,z(n.cost,T))))))),t("p",{className:"cm-stat-sub",style:{marginTop:10}},c("推理 Token 可能包含在输出中;单价为 0 时不另收推理费。未定价用量显示 ?。","Reasoning tokens may overlap output; a zero reasoning rate adds no separate charge. Unpriced usage is marked ?.")),_(c("按调用类型统计","Cost by call type"),f.kinds,n=>j[n.kind]??n.kind),_(c("按轮次统计","Cost by turn"),f.turns,n=>x(n.turn)),t("p",{className:"cm-stat-sub"},c("轮次沿用日志编号;未记录轮次的调用单列。每轮包含其全部调用,跨调用分页也不会拆分。","Turn numbers come from the log; calls without one are listed separately. Each turn includes all its calls, across call pages.")),t(X,{offset:W,count:f.totalTurns,size:25,onChange:C,text:c}),t("div",{style:{marginTop:24}},t(rt,{rows:f.calls.map((n,u)=>({label:"#"+(a+u+1),index:u,value:S(n,M)})),money:d,text:c,label:c("逐次调用费用 · 当前页","Cost per call · current page"),onSelect:n=>{O(n.index),document.getElementById("cm-stat-call-"+n.index)?.scrollIntoView({block:"nearest",behavior:"smooth"})}})),f.calls.map((n,u)=>t("details",{key:u,id:"cm-stat-call-"+u,className:"cm-stat-call",open:y===u,onToggle:v=>{!v.currentTarget.open&&y===u&&O(-1)}},t("summary",null,"#"+(a+u+1)+" · "+x(n.turn)+(n.step==null?"":c(" / 步骤 "," / step ")+n.step)+" · "+(j[n.kind]??n.kind)+" · "+n.provider+" / "+n.model+" · "+(n.plan?"Plan ":"API ")+(n.priced?d(n.cost):"?")+(n.longContext?c(" · 长上下文价"," · long-context rate"):"")),t("p",{className:"cm-stat-sub"},new Date(n.atMs).toLocaleString()),n.rows.map(U))),t(X,{offset:a,count:f.totalCalls,size:50,onChange:n=>{i(n),O(-1)},text:c}))}function pt({state:s,api:l,sessionId:o="",formatMoneyUsd:d,formatTokens:m}){const c=s.config.locale==="en"||s.config.locale==="auto"&&!/^zh\b/i.test(navigator.language),a=(e,N)=>c?N:e,[i,y]=k(o?"all":"week"),[O,W]=k(null),[C,E]=k({id:o,title:o}),[f,M]=k(""),[R,j]=k(""),[x,Y]=k(o?"total":"api"),[F,T]=k(0),[L,Z]=k(0),[U,_]=k(!1),n=s.meta.dayKey||new Date().toISOString().slice(0,10),u=i==="all"?"":i==="custom"?O?.from||n:xt(n,i==="week"?-6:i==="month"?-29:0),v=i==="custom"&&O?.to||n,g={from:u,to:v,provider:f,model:R,basis:x,sessionId:C.id,offset:F},D=it(l,"getBillingStatistics",g,L+":"+s.meta.now),p=D.value,I=e=>d(e,{...s.config,decimals:Math.max(e!==0&&Math.abs(e)<.01?8:4,s.config.decimals??2)}),ut=e=>{y(e),T(0)},gt=e=>{E(e),T(0),M(""),j("")},wt=x==="total"?a("API + Plan 等值","API + Plan equivalent"):x==="plan"?a("Plan 等值","Plan equivalent"):a("API 费用","API cost"),tt=(e,N,at,Pt)=>t("label",null,e,t("select",{value:N,onChange:J=>{at(J.target.value),T(0)}},Pt.map(([J,Tt])=>t("option",{key:J,value:J},Tt)))),St={input:a("未缓存输入","Uncached input"),cacheRead:a("缓存读取","Cache read"),cacheWrite:a("缓存写入","Cache write"),output:a("输出","Output")},bt=(e,N)=>t("section",{className:"cm-stat-panel"},t("div",{className:"cm-stat-panel-head"},t("h3",null,e)),N),h=p?.totals,$=(e,N,at)=>t("div",{className:"cm-stat-metric",key:e},t("div",{className:"cm-stat-sub"},e),t("div",{className:"cm-stat-value"},N),t("div",{className:"cm-stat-sub"},at));return t("div",{className:"cm-stat"},t("style",null,Nt),t("header",{className:"cm-stat-head"},t("div",null,t("h2",null,a("计费统计","Cost statistics")),t("p",{className:"cm-stat-sub"},C.id?a("单对话 · ","Conversation · ")+(C.title===C.id&&p?.sessions[0]?.title||C.title):a("全部对话 · 从总额查看每一笔调用","All conversations · from totals to individual calls"))),B(a("刷新","Refresh"),()=>Z(e=>e+1))),C.id?B(a("← 全部对话","← All conversations"),()=>gt({id:"",title:""})):null,t("div",{className:"cm-stat-controls"},t("div",{className:"cm-stat-periods"},...[["today",a("今天","Today")],["week",a("近 7 天","Last 7 days")],["month",a("近 30 天","Last 30 days")],["all",a("全部记录","All retained")],["custom",a("自定义","Custom")]].map(([e,N])=>B(N,()=>ut(e),{key:e,"aria-pressed":i===e})))),i==="custom"?t("div",{className:"cm-stat-controls"},...["from","to"].map(e=>t("label",{key:e},e==="from"?a("开始日期","From"):a("结束日期","To"),t("input",{type:"date",value:e==="from"?u:v,onChange:N=>{W({from:u,to:v,[e]:N.target.value}),T(0)}})))):null,t("div",{className:"cm-stat-controls"},tt(a("计费口径","Cost basis"),x,Y,[["api",a("API 费用","API cost")],["plan",a("Plan 等值","Plan equivalent")],["total",a("API + Plan 等值","API + Plan equivalent")]]),tt(a("提供商","Provider"),f,e=>{M(e),j("")},[["",a("全部提供商","All providers")],...[...new Set([f,...p?.providers??[]])].filter(Boolean).map(e=>[e,e])]),tt(a("模型","Model"),R,j,[["",a("全部模型","All models")],...[...new Set([R,...p?.modelOptions??[]])].filter(Boolean).map(e=>[e,e])])),t("p",{className:"cm-stat-sub"},a("按宿主时区归日:","Days use the host timezone: ")+(s.meta.timezone||"UTC")+" · "+a("API 金额是按已记录用量和配置单价计算的估算;Plan 为订阅用量的 API 等值,不是订阅账单。外部导入用量在原概览中单列。","API amounts estimate recorded usage at configured rates; Plan is API-equivalent usage, not the subscription invoice. External usage remains separate in Overview.")),D.loading?t("p",{className:"cm-stat-empty",role:"status"},a("加载统计…","Loading statistics…")):D.error?t("p",{className:"cm-stat-error",role:"alert"},D.error):p?t(Q,null,t("p",{className:"cm-stat-sub",style:{marginTop:8}},p.from+" – "+p.to+(p.retainedFrom?" · "+a("账本保留范围 ","Retained ledger ")+p.retainedFrom+" – "+p.retainedTo:"")),t("div",{className:"cm-stat-metrics"},$(a("API 费用","API cost"),I(h.apiCost),a("已入账估算","Recorded estimate")),$(a("Plan 等值费用","Plan equivalent"),I(Math.max(0,h.cost-h.apiCost)),a("不代表实际扣款","Not an actual debit")),$(a("调用次数","Calls"),h.calls.toLocaleString(),p.sessionCount+a(" 个对话 · 平均 "," conversations · average ")+I(h.calls?S(h,x)/h.calls:0)),$(a("缓存命中率","Cache hit rate"),z(h.cacheRead,h.input+h.cacheRead+h.cacheWrite),m(et(h))+" Tokens")),C.id?t(mt,{key:JSON.stringify([C.id,u,v,f,R,x]),api:l,query:{...g,from:p.from,to:p.to,offset:0},revision:L,money:I,formatTokens:m,text:a}):null,p.models.some(e=>!e.priced)?t("p",{className:"cm-stat-note"},a("部分模型当前未配置价格,金额可能不完整;未定价不等于免费。","Some models have no configured price. Amounts may be incomplete; unpriced usage is not free.")):null,!h.calls&&!h.cost?t("p",{className:"cm-stat-empty"},a("所选范围没有已记录的用量。","No recorded usage in this range.")):null,t("section",{className:"cm-stat-panel"},t(rt,{rows:dt(p.days,x),money:I,text:a,label:a("费用趋势 · ","Cost over time · ")+wt,onSelect:e=>{W({from:e.from,to:e.to}),ut("custom")}}),t("p",{className:"cm-stat-sub",style:{marginTop:12}},a("点击柱形查看该日期范围。长区间按相邻日期合并,保留全部金额。","Select a bar to inspect its date range. Long ranges group adjacent days without dropping costs."))),t("div",{className:"cm-stat-grid"},bt(a("模型费用排行","Cost by model"),t(Q,null,t("ol",{className:"cm-stat-rank"},(U?p.models:p.models.slice(0,8)).map(e=>t("li",{key:e.key},t("button",{type:"button",onClick:()=>{M(e.provider),j(e.model),T(0)},title:e.key},t("div",{className:"cm-stat-rankline"},t("span",null,e.model),t("span",{className:"cm-stat-number"},I(S(e,x))+(e.priced?"":" + ?"))),t("div",{className:"cm-stat-sub"},e.provider+" · "+e.calls+a(" 次 · "," calls · ")+z(S(e,x),S(h,x))),t("div",{className:"cm-stat-track"},t("span",{style:{width:z(S(e,x),S(h,x))}}))))),p.models.length>8?B(U?a("收起","Show less"):a("显示全部模型","Show all models"),()=>_(e=>!e)):null,p.unmodeledCost>1e-8?t("p",{className:"cm-stat-note"},a("历史未分配到模型的费用:","Historical cost without a model: ")+I(p.unmodeledCost)):null)),bt(a("Token 构成","Token composition"),t(Q,null,t("ol",{className:"cm-stat-rank"},Object.entries(St).map(([e,N])=>t("li",{key:e},t("div",{className:"cm-stat-rankline"},t("span",null,N),t("span",{className:"cm-stat-number"},m(h[e]))),t("div",{className:"cm-stat-track"},t("span",{style:{width:z(h[e],et(h)),background:e.startsWith("cache")?"var(--cm-plan)":void 0}}))))),t("p",{className:"cm-stat-note"},a("这里展示 Token 数量占比,不是费用占比。缓存写入计入输入分母,不算缓存命中。","These are token shares, not cost shares. Cache writes count as input, not cache hits.")),t("p",{className:"cm-stat-sub"},a("单独上报的推理 Token:","Reported reasoning tokens: ")+m(h.reasoning))))),C.id?null:t("section",{className:"cm-stat-panel"},t("div",{className:"cm-stat-panel-head"},t("h3",null,a("对话费用排行","Cost by conversation")),t("span",{className:"cm-stat-sub"},a("点击对话查看明细 · 子代理分别计入,不重复相加","Select a conversation for details · each agent counted once"))),t("div",{className:"cm-stat-scroll"},t("table",{className:"cm-stat-table"},t("thead",null,t("tr",null,...[a("对话","Conversation"),a("调用","Calls"),"API",a("Plan 等值","Plan equivalent"),a("缓存命中","Cache hits")].map(e=>t("th",{key:e},e)))),t("tbody",null,p.sessions.map(e=>t("tr",{key:e.id},t("td",null,t("button",{type:"button",title:e.id,onClick:()=>gt({id:e.id,title:e.title})},e.title)),t("td",null,e.calls.toLocaleString()),t("td",null,I(e.apiCost)),t("td",null,I(Math.max(0,e.cost-e.apiCost))),t("td",null,z(e.cacheRead,e.input+e.cacheRead+e.cacheWrite))))))),p.unassignedCost>1e-8?t("p",{className:"cm-stat-note"},a("未关联对话的费用:","Cost not linked to a conversation: ")+I(p.unassignedCost)):null,t(X,{offset:F,count:p.sessionCount,size:25,onChange:T,text:a})))):null)}async function Ct(s){const l=await s.get("remote").$mount(ot);s.effect(()=>()=>l(),"cost-meter: statistics contribution");const o=s.get("remote.costMeter"),d=Object.fromEntries(["getBillingStatistics","getSessionBilling"].map(m=>[m,async c=>{const a=await o[m](lt(c));if(!a?.ok)throw new Error(a?.error?.message||"Statistics request failed");return(m==="getBillingStatistics"?V:G)(a.value)}]));return m=>t(pt,{...m,api:d})}return{mount:Ct,Statistics:pt,SessionDetail:mt,CONTRIBUTION:ot,parseStatistics:V,parseDetail:G,dailyChartRows:dt}}});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-cost-meter",
|
|
3
|
-
"version": "1.8.
|
|
3
|
+
"version": "1.8.2",
|
|
4
4
|
"description": "DeepSeek Harness 会话费用统计插件:本会话成本、当日费用、历史记录与官方价格同步,支持多厂商多模型价格计费(内置 90+ 模型价格目录与自动匹配)、主流 Coding Plan 订阅额度查询与显示(11 家,含小米 MiMo Token Plan 控制台 Cookie 查询、SCNet Token Plan 本地 Credits 计量与火山方舟 Volcano Ark Coding Plan AK/SK 签名查询)、自定义 Provider 余额查询(可配任意 HTTP 端点)与余额进度条、峰谷计价时段显示与切换前弹窗/系统通知提醒(位置/提前量/类型可配),界面中英双语。Session cost tracking plugin for DeepSeek Harness: per-conversation cost, daily totals, history and official price sync, with multi-vendor model pricing (built-in 90+ model catalog with auto-matching), Coding Plan quota queries & display for 11 vendors (incl. Xiaomi MiMo Token Plan via console cookie, local credits metering for the SCNet Token Plan and Volcano Ark Coding Plan via AK/SK signature), plan/API dual-track billing with per-1% & full-window Token Plan usage estimates, custom provider balance lookup (configurable HTTP endpoint) and balance progress bar, plus peak/off-peak pricing display with pre-switch popup & system-notification alerts (position / lead time / type configurable), in a bilingual (Chinese/English) UI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|