@feiyang666/dsh-usage-plugin 1.13.1 → 1.14.0
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/CHANGELOG.md +28 -23
- package/README.md +5 -2
- package/README.zh.md +7 -2
- package/RELEASE_NOTES.md +46 -0
- package/lib/client.js +319 -25
- package/lib/index.js +88 -24
- package/package.json +89 -88
package/CHANGELOG.md
CHANGED
|
@@ -8,36 +8,34 @@
|
|
|
8
8
|
|
|
9
9
|
---
|
|
10
10
|
|
|
11
|
-
##
|
|
11
|
+
## v1.14.0 (2026-08-27)
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
- 复用百炼 CLI(`bl`)的「控制台登录」OAuth token(`~/.bailian/config.json` 的 `access_token`),**不需要阿里云 AccessKey / BSS 权限**,权限面远小于 `AliyunBSSReadOnlyAccess`。
|
|
15
|
-
- Host 侧新增 `console-token` 查询模式:读取 `~/.bailian/config.json` → 调百炼门户网关 `zeldaHttp.apikeyMgr./tokenplan/personal/api/v2/usage` → 返回配额已用百分比与重置时间。
|
|
16
|
-
- 客户端渲染:百分比按官方 CLI 的 `round(x*1000)/10` 格式(如 `20.6%`),重置时间显示本地日期 + 相对倒计时;「本周配额」卡片用连续线段展示开始 / 结束日期(开始到今天深蓝、今天到结束浅色),今天节点随日期动态移动,窗口宽度随浏览器缩放自适应。
|
|
17
|
-
- 说明:展示的是**配额已用百分比**,不是剩余 token 数;百炼 Token Plan 实际没有 5 小时配额(相关 UI 已移除)。
|
|
18
|
-
- 新增依赖 `undici`(`package.json`)。
|
|
13
|
+
> 📄 本版本英文发布说明(English release notes):[RELEASE_NOTES.md](./RELEASE_NOTES.md)
|
|
19
14
|
|
|
20
|
-
|
|
15
|
+
### 新增 / New
|
|
21
16
|
|
|
22
|
-
|
|
17
|
+
- **消息底部「本轮 token」弹窗(两层统计)**:多轮完成后,助手消息底部操作行(复制 / 点赞 / 点踩 / 回复 那一排)新增「本轮 token」按钮,点击弹出 `Token 明细`:
|
|
18
|
+
- **对话累计**:整场对话(当前会话)的 总 token / 总消耗 / 耗时 / 缓存命中率;
|
|
19
|
+
- **本轮明细**:本次输出 / 本轮 token / 本轮消耗 / 本轮耗时 / 本轮缓存命中率 / **缓存命中长条图**(命中部分单独绿色渲染 + 百分比)/ **按模型卡片**(每模型:模型名 + 总成本,一行 输入·未命中 / 缓存命中 / 输出(+推理),有费用再一行 峰 / 谷);
|
|
20
|
+
- 时长统一按「X分Y秒」(英文 `Xm Ys`)显示;本轮窗口取该消息实际产出所在 step 的起止,本轮命中率与对话累计各自独立统计。
|
|
21
|
+
- **宿主记录带会话标识**:`llm/stream` 捕获每条记录时新增 `sessionId`(优先 `options.sessionId`,缺失回退当前 agent 会话);新增 `tokenForMessage` API——按「会话 + 时间窗」返回本轮明细与对话累计两层聚合。
|
|
22
|
+
- **内部/工具调用单独分组**:`list` 同时返回 `toolCalls`(`model` 为 fake/unknown/空 或 `dsh*` 内部路由,如 `dsh2shell-*`/`fake`),概览「消耗明细」下方新增可折叠的「工具调用(内部)」分组(默认收起,显示 服务商·模型 / 调用数 / 总消耗),与真实模型调用分开。
|
|
23
|
+
- **语言跟随系统设置**:移除「用量与消耗」头部的中英文切换按钮,语言完全跟随宿主「通用设置 → 语言」,切换即时生效(`locale` 服务 `subscribe` + `locale/change`);不再写 `localStorage`(`dsh-usage-lang`),旧残留值不再覆盖系统语言;「会话 / 设置」标签页名改为按渲染时读取的 thunk,随语言切换实时更新。
|
|
24
|
+
- **移动端适配**:表格在 ≤900px 贴合屏宽(`min-width:0 !important`)、不再出现横向滑条;宽表折叠中间列;弹窗统计卡与按模型卡片随宽度自适应;弹窗滚动条外观隐藏(仍可滚轮滚动)。
|
|
25
|
+
- **英文适配补全**:「高峰 / 空闲时段说明」整块与 19 个遗漏键(当前 · 工作日/周末时段、工作日高峰卡片提示、用量日历悬停提示、价格表两段长说明等)补上英译。
|
|
23
26
|
|
|
24
|
-
|
|
25
|
-
- **响应式语言切换**:语言以 React 状态注入 Usage / Balance 两个面板(`useLang` 订阅机制),切换不依赖任何网络请求;SUBTABS 等集合标签改为渲染时翻译。外部标签页名(slots label)在注册时定格,页面刷新后更新——按钮 tooltip 与两份 README 均已注明。
|
|
26
|
-
- **瞬态提示文案同样响应式**:导出 / 导入 / 目录选择等状态消息与客户端校验错误改为语义描述符(`{ k }` / `{ k, tail }` / `{ seq }` / `errorKey`)存储,渲染时才翻译,切换语言后既有提示立即跟随当前语言。
|
|
27
|
-
- **缺失凭据错误不再泄漏内部键**:`PROVIDERS.credentialHint` 更名为 `credentialHintKey`;DeepSeek 未配置凭据的错误改由技术凭据名生成(如「未找到 DEEPSEEK_API_KEY,请配置后重试」),新增 `missingCredentialError` 助手与单测断言错误中不含 `hint.*`。
|
|
28
|
-
- **测试与调试钩子**:新增 `exports.__i18n`(t / tb / msgText / setLang / 日期格式化)与 `test/client-i18n.test.js` —— 覆盖语言即时切换、localStorage 持久化跨挂载生效、日期 / 星期渲染、瞬态消息双语文案、余额语义键双语全覆盖。
|
|
29
|
-
- **余额查询改用语义化字段**:host(`lib/balance.js`)不再返回中文展示文案,改为 `labelKey / hintKey / meaningKey / sourceNoteKey / balanceLabelKey` 等稳定键,双语文案集中到客户端;成功响应负载不含中文展示文本(新增单测覆盖)。错误消息本期保持中文,拟后续跟进。
|
|
30
|
-
- **本地化日期与星期**:新增 `dayLabel / fmtMonthLabel / dailyStatsTitle / weekdayLabels`,中文保持 `2026年8月22日` 习惯格式,英文输出 `Aug 22, 2026`、`August 2026` 与 Mo–Su 星期表头。
|
|
31
|
-
- **实现范围**:改动 `lib/client.js` 与 `lib/balance.js`;PNG 报告、日历悬停提示等 canvas 文案同步支持英文;与数据相关的比较(如合计行 key `"合计"`)保留原文不受翻译影响。宿主端日志 / 错误文案保持中文,拟后续跟进。
|
|
27
|
+
### 修复 / Fixes
|
|
32
28
|
|
|
33
|
-
|
|
29
|
+
- **弹窗不可见**:对话树祖先的 transform 会破坏 `position: fixed`,改用 `react-dom` 平台种子的 `createPortal` 把弹窗挂到 `document.body`。
|
|
30
|
+
- **整场会话聚合**:按 `messageId` 查找消息完成时间改为读取 `data.finalNode.messageId` / `data.closing.finalNode.messageId`,时间窗正确生效(不再把整场对话圈进来)。
|
|
31
|
+
- **语言切换失效**:不再持久化语言选择,宿主设置切回中文即时恢复、无需刷新。
|
|
32
|
+
- **表格显示**:宽表 `width:max-content`,列按内容撑开、外层容器横向滚动,数字不再被压到换行/重叠。
|
|
34
33
|
|
|
35
|
-
|
|
34
|
+
### 测试 / Tests
|
|
36
35
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
-
|
|
40
|
-
- **新增 `.mailmap`**:把 PR #7 提交作者 `Ruslan R. Musakalimov <gitlab@letsweb.me>` 映射到 GitHub 账号 @ayleen,使仓库 Contributors 图与 git blame 正确归属历史提交。
|
|
36
|
+
- 周末计费规则回归测试(issue #9):宿主 `isPeakAt`(新增 `test/period.test.js`)+ 客户端 `isPeakNow` / `periodNow`(`test/client-i18n.test.js`),覆盖 issue 的 5 个时刻向量、生效时间边界、峰段窗口左右端点。
|
|
37
|
+
- i18n 测试改为覆盖「跟随宿主语言服务实时切换」「旧 localStorage 值不再覆盖宿主语言」。
|
|
38
|
+
- 全部 27/27 通过。
|
|
41
39
|
|
|
42
40
|
---
|
|
43
41
|
|
|
@@ -49,6 +47,11 @@
|
|
|
49
47
|
- **百炼(Qwen)Token Plan 配额查询**(社区贡献,[@wuhuqif176](https://github.com/wuhuqif176),[PR #8](https://github.com/feiyang-dev/dsh-usage-plugin/pull/8)):「剩余余额查询」新增「百炼 Token Plan」子页签——复用百炼 CLI(`bl`)的控制台 OAuth token(`~/.bailian/config.json`),无需阿里云 AccessKey;展示本周配额已用百分比(进度条 + `已用 xx.x%`)与本周开始 / 结束日期(连续线段,今天节点随日期动态移动)。新增依赖 `undici`。
|
|
50
48
|
- **本地改进**:消耗明细表顶部与底部各渲染一份分页条(长列表翻页更顺手);DeepSeek 系 provider 未单独上报 `cacheWriteTokens` 时,「缓存写入」列改用未命中 token 数(`inputTokens`)兜底显示,避免长期为空。
|
|
51
49
|
|
|
50
|
+
### 文档 / Docs
|
|
51
|
+
|
|
52
|
+
- **补全贡献者名单**(`package.json` contributors):新增 [@ayleen](https://github.com/ayleen)(PR #7 英文界面)与 [@wuhuqif176](https://github.com/wuhuqif176)(PR #8 百炼配额)。
|
|
53
|
+
- **新增 `.mailmap`**:把 PR #7 提交作者 `Ruslan R. Musakalimov <gitlab@letsweb.me>` 映射到 GitHub 账号 @ayleen,使仓库 Contributors 图与 git blame 正确归属历史提交。
|
|
54
|
+
|
|
52
55
|
### 致谢 / Acknowledgements
|
|
53
56
|
|
|
54
57
|
- **[@ayleen](https://github.com/ayleen)**:实现英文界面与响应式语言切换(PR #7)。
|
|
@@ -274,4 +277,6 @@
|
|
|
274
277
|
- CSV / JSON / PNG 长图导出(最新在前,最多 2000 条,超出提示),支持自定义导出目录(原生目录选择器)与「打开所在目录」。
|
|
275
278
|
- JSON / CSV 导入合并,按时间去重。
|
|
276
279
|
- Windows / macOS / Linux 跨平台路径与原生操作适配;启动诊断日志 `dsh-usage-boot.log`。
|
|
280
|
+
---
|
|
277
281
|
|
|
282
|
+
📄 **英文发布说明入口(English release notes)**:[RELEASE_NOTES.md](./RELEASE_NOTES.md)
|
package/README.md
CHANGED
|
@@ -40,8 +40,11 @@ dsh-usage-plugin is a **usage & cost tracker** plugin in the DeepSeek Harness ec
|
|
|
40
40
|
- **Export**: CSV / JSON / **PNG long image** (newest-first, up to the latest 2000 records, warns if exceeded; the PNG report includes peak/off-peak cost columns), to any directory (native picker), auto-opens the folder after export.
|
|
41
41
|
- **Import**: merge-imports JSON / CSV files, deduplicated by time.
|
|
42
42
|
- **Persistence**: records are written live to `<session workspace>/dsh-usage/usage-records.json` and restored on restart (cap 100000 records).
|
|
43
|
-
- **UI adaptation**: panel typography scales with the app's display-size setting (em-relative fonts);
|
|
44
|
-
- **English UI (i18n)**: the panel
|
|
43
|
+
- **UI adaptation**: panel typography scales with the app's display-size setting (em-relative fonts); wide tables scroll horizontally on desktop (`max-content` + `overflow-x`) and **fit the screen width on mobile (≤900px, no horizontal scrollbar)**; popup cards adapt to the viewport.
|
|
44
|
+
- **English UI (i18n)**: the panel follows the harness's own language setting (General Settings → Language) — switch it there and the plugin follows instantly, no separate toggle, no `localStorage` override. The bilingual dictionary covers the whole panel, the peak/off-peak billing-period hints, the balance query and the PNG report; host-level Conversation/Settings tab labels are re-read per render, so they follow the language switch live.
|
|
45
|
+
- **Per-message token popup**: when a turn finishes, the assistant message's footer action row shows a **"Turn tokens"** button; clicking opens a `Token Details` popup in two layers — **Conversation total** (the whole session's total tokens / total cost / run time / cache-hit rate) and **This turn** (this output's tokens, turn tokens, turn cost, turn duration, turn cache-hit rate, a cache-hit bar, and per-model cards showing each model's input·miss / cache hit / output (+ reasoning) plus its peak/off-peak cost split). Durations are shown as `Xm Ys`. Each usage record is tagged with the conversation (`sessionId`), so per-turn and per-conversation stats are computed independently.
|
|
46
|
+
- **Internal/tool calls grouped separately**: internal/placeholder calls (e.g. `dsh2shell-*` with model `fake`) are excluded from the model cost breakdown and collected in a collapsible **"Tool calls (internal)"** group below the breakdown, so only real model calls count in the cost tables.
|
|
47
|
+
- **Mobile adaptation**: tables fit the screen width on ≤900px (no horizontal scrollbar); wide tables collapse the middle columns; the popup and its stat cards / per-model cards adapt to the viewport.
|
|
45
48
|
|
|
46
49
|
---
|
|
47
50
|
|
package/README.zh.md
CHANGED
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
|
|
7
7
|
[GitHub](https://github.com/feiyang-dev/dsh-usage-plugin) · [npm](https://www.npmjs.com/package/@feiyang666/dsh-usage-plugin) · MIT License
|
|
8
8
|
|
|
9
|
+
> 📄 **本版本英文发布说明(What’s New in v1.14.0)**:[RELEASE_NOTES.md](./RELEASE_NOTES.md)
|
|
10
|
+
|
|
9
11
|
**由开发者制作的 DeepSeek Harness 插件** —— 记录每一次模型调用的 token 用量与消耗,支持峰谷计费、余额查询、日历热力图与 CSV / JSON / PNG 导出。
|
|
10
12
|
|
|
11
13
|

|
|
@@ -40,8 +42,11 @@ dsh-usage-plugin 是 DeepSeek Harness 生态的**用量与消耗统计插件**
|
|
|
40
42
|
- **导出**:CSV / JSON / **PNG 长图**(按最新在前展示,最多含最近 2000 条,超出会提示;PNG 报告含高峰 / 空闲消耗分列统计),可导出到任意目录(原生目录选择器),导出后自动打开所在目录。
|
|
41
43
|
- **导入**:选择文件(JSON / CSV)合并导入,按时间去重。
|
|
42
44
|
- **持久化**:记录实时落盘到**固定专用数据目录**(如 `%LOCALAPPDATA%\dsh-usage-plugin\dsh-usage\usage-records.json`,详见下文「数据目录」),与当前工作区无关,重启自动恢复(上限 100000 条,尽量多存)。
|
|
43
|
-
- **界面适配**:面板字号跟随应用「显示大小」设置自动缩放(em
|
|
44
|
-
- **英文界面(i18n
|
|
45
|
+
- **界面适配**:面板字号跟随应用「显示大小」设置自动缩放(em 相对字号);桌面端宽表在容器内横向滑动(max-content + overflow-x),**移动端(≤900px)表格贴合屏宽、不再出现横向滑条**,弹窗与卡片随视口自适应。
|
|
46
|
+
- **英文界面(i18n)**:面板语言跟随宿主的「通用设置 → 语言」——在那里切换,插件即时跟随,不再自带切换按钮,也不再用 `localStorage` 覆盖系统语言。双语词典覆盖整个面板(含高峰 / 空闲时段说明、余额查询、PNG 报告);外部「会话 / 设置」标签页名按渲染时读取,切换语言后实时更新。
|
|
47
|
+
- **消息底部 token 弹窗**:一轮输出完成后,助手消息底部操作行新增「本轮 token」按钮,点击弹出 `Token 明细`,分两层统计——**对话累计**(整场会话的 总 token / 总消耗 / 耗时 / 缓存命中率)与**本轮明细**(本次输出 / 本轮 token / 本轮消耗 / 本轮耗时 / 本轮缓存命中率、缓存命中长条图、按模型卡片:每个模型显示 输入·未命中 / 缓存命中 / 输出(+推理)与峰 / 谷成本分列)。时长按「X分Y秒」显示。宿主记录带会话标识(`sessionId`),本轮与整场对话统计各自独立。
|
|
48
|
+
- **内部/工具调用单独分组**:内部/占位调用(如 `dsh2shell-*`、模型 `fake`)不混入模型消耗明细,单独收在「消耗明细」下方可折叠的 **「工具调用(内部)」** 分组里(默认收起),成本表只统计真实模型调用。
|
|
49
|
+
- **移动端适配**:≤900px 时表格贴合屏宽(不再出现横向滑条)、宽表折叠中间列,弹窗统计卡与按模型卡片随视口自适应。
|
|
45
50
|
|
|
46
51
|
---
|
|
47
52
|
|
package/RELEASE_NOTES.md
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Release Notes (English)
|
|
2
|
+
|
|
3
|
+
The authoritative **English** release notes for `@feiyang666/dsh-usage-plugin`.
|
|
4
|
+
|
|
5
|
+
Each new version's notes are added at the **top** of this file; the **[Version index](#version-index)** at the **bottom** is the default entry point for jumping to any release.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## v1.14.0 (2026-08-27)
|
|
10
|
+
|
|
11
|
+
This release focuses on **per-message token insights**, cleaner cost breakdowns, **system-level language sync**, and **mobile adaptation**.
|
|
12
|
+
|
|
13
|
+
### Per-message "Turn tokens" popup
|
|
14
|
+
|
|
15
|
+
After a turn finishes, the assistant message's footer action row gains a **"Turn tokens"** button. Clicking it opens a `Token Details` popup with two layers:
|
|
16
|
+
|
|
17
|
+
- **Conversation total** — the whole session's total tokens, total cost, run time and cache-hit rate;
|
|
18
|
+
- **This turn** — this output's tokens, turn tokens, turn cost, turn duration, turn cache-hit rate, a cache-hit bar, and per-model cards (each model's input·miss / cache hit / output (+ reasoning) plus its peak/off-peak cost split).
|
|
19
|
+
|
|
20
|
+
Durations are shown as `Xm Ys`. Every usage record is tagged with the conversation (`sessionId`), so per-turn and per-conversation stats are computed independently.
|
|
21
|
+
|
|
22
|
+
### Internal/tool calls grouped separately
|
|
23
|
+
|
|
24
|
+
Internal/placeholder calls (e.g. `dsh2shell-*` with model `fake`) no longer clutter the model cost tables; they are collected in a collapsible **"Tool calls (internal)"** group below the cost breakdown, so only real model calls count in the cost tables.
|
|
25
|
+
|
|
26
|
+
### Language follows the harness setting
|
|
27
|
+
|
|
28
|
+
The in-panel language toggle is removed; the plugin now follows the harness's own language setting (General Settings → Language) instantly, with no `localStorage` override — this also fixes the "still English after switching back" bug.
|
|
29
|
+
|
|
30
|
+
### Mobile adaptation
|
|
31
|
+
|
|
32
|
+
Tables fit the screen width on ≤900px (no horizontal scrollbar), wide tables collapse the middle columns, and the popup adapts to the viewport.
|
|
33
|
+
|
|
34
|
+
### Also fixed
|
|
35
|
+
|
|
36
|
+
- The popup previously rendered off-screen inside transformed ancestors — now portaled to `document.body`;
|
|
37
|
+
- Per-message windowing now reads the message's real step boundaries, so "this turn" and "conversation total" stats are truly independent;
|
|
38
|
+
- Wide tables no longer wrap numbers into unreadable lines.
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Version index
|
|
43
|
+
|
|
44
|
+
| Version | Date | English notes |
|
|
45
|
+
| --- | --- | --- |
|
|
46
|
+
| v1.14.0 | 2026-08-27 | [What's New in v1.14.0](#v1140-2026-08-27) |
|
package/lib/client.js
CHANGED
|
@@ -6,12 +6,25 @@ window.__ModuleLoader__.load({
|
|
|
6
6
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7
7
|
var React = require("react");
|
|
8
8
|
var el = React.createElement;
|
|
9
|
+
// react-dom 是平台种子模块:用于把弹窗 Portal 到 document.body,
|
|
10
|
+
// 避免对话树祖先的 transform/contain 破坏 position:fixed(弹窗不可见)。
|
|
11
|
+
var ReactDOM = null;
|
|
12
|
+
try { ReactDOM = require("react-dom"); } catch (e2) {}
|
|
13
|
+
// 隐藏弹窗卡片滚动条外观(仍可滚轮滚动),并在小屏下贴近视口
|
|
14
|
+
try {
|
|
15
|
+
if (typeof document !== "undefined" && document.head && !document.getElementById("dsh-usage-tok-style")) {
|
|
16
|
+
var __tokStyle = document.createElement("style");
|
|
17
|
+
__tokStyle.id = "dsh-usage-tok-style";
|
|
18
|
+
__tokStyle.textContent = "[data-dsh-usage-tok]{scrollbar-width:none;-ms-overflow-style:none}[data-dsh-usage-tok]::-webkit-scrollbar{width:0;height:0;display:none}";
|
|
19
|
+
document.head.appendChild(__tokStyle);
|
|
20
|
+
}
|
|
21
|
+
} catch (e3) {}
|
|
9
22
|
|
|
10
23
|
// ── i18n: zh keys → en dictionary (community addition) ──
|
|
11
|
-
var __T_EN = {"未知模型":"Unknown model","未知服务商":"Unknown provider","完成":"Done","工具调用":"Tool call","超长":"Overlong","错误":"Error","自动(生效前基础价 · 生效后峰谷价)":"Auto (base price before effective date · peak/valley after)","基础价格":"Base price","峰谷价格":"Peak/valley price","自动":"Auto","canvas 2d 不可用":"canvas 2D unavailable","用量与消耗报告":"Usage & Cost Report","生成时间 ":"Generated ","(北京) · 共 ":" (Beijing) · total "," 条调用 · 缓存命中率 ":" calls · cache-hit rate "," · 高峰价消耗 ":" · peak-rate cost "," · 空闲价消耗 ":" · off-peak cost "," · 合计 ":" · total "," · 报告仅含最近 ":" · report limited to the latest "," 条":" records","调用次数":"Calls","输入 · 未命中":"Input · miss","输入 · 缓存命中":"Input · cache hit","输出":"Output","高峰消耗":"Peak cost","空闲消耗":"Off-peak cost","总消耗 (峰谷价)":"Total cost (peak/valley)","缓存命中列表(共 ":"Cache hit list ("," 条)":" records)","时间(北京)":"Time (Beijing)","模型":"Model","输入·未命中":"Input·miss","缓存命中":"Cache hit","缓存写入":"Cache write","推理":"Reasoning","命中率":"Hit rate","时段":"Period","结束":"End","消耗(峰谷)":"Cost (peak/valley)","峰":"Peak","谷":"Valley","消耗表(按模型 · 峰谷价 · 高峰/空闲分列)":"Cost table (by model · peak/valley prices)","调用":"Calls","总消耗":"Total cost","合计":"Total","消耗表(按 API 服务商 × 模型 · 峰谷价)":"Cost table (by API provider × model · peak/valley)","API 服务商 / 模型":"API provider / model"," 个模型)":" models)","总费用合计":"Total cost overall","计价按请求的 API 服务商分别应用已核验价格;DigitalOcean 美元价按 USD/CNY 汇率折算;无法可靠映射的第三方价格按 ¥0。":"Billing applies verified prices per requesting API provider; DigitalOcean USD prices are converted at the USD/CNY rate; third-party prices that cannot be mapped reliably are counted as ¥0."," 个服务商":" providers","今天":"Today","近7天":"Last 7 days","近30天":"Last 30 days","全部":"All","自定义区间:":"Custom range:","至":"to","清除筛选":"Clear filters","当前范围:共 ":"Current range: ","(全部记录)":"(all records)"," · 总消耗 ":" · total cost ","高峰 ":"Peak "," · 空闲 ":" · off-peak ","命中率 ":"hit rate ","高峰时段 9:00–12:00、14:00–18:00":"Peak hours 9:00–12:00 & 14:00–18:00","其余空闲时段":"other hours are off-peak","高峰 + 空闲 · ":"Peak + off-peak · ","消耗表(按模型)":"Cost table (by model)","API 服务商":"API provider","高峰/空闲":"Peak/Off-peak","消耗明细(按 API 服务商 × 模型)":"Cost breakdown (by API provider × model)","DeepSeek 官方请求按官方峰谷价;SiliconFlow 按自身人民币公开价;DigitalOcean 按美元公开价 × USD/CNY 汇率折算人民币;千问暂不计费;AMD GPU Cloud DeepSeek V4 Flash 免费按 ¥0。模型名与 API 服务商均以请求参数为准。":"DeepSeek official requests use official peak/valley prices; SiliconFlow uses its own public CNY prices; DigitalOcean uses public USD prices converted to CNY at the USD/CNY rate; Qwen is not billed yet; AMD GPU Cloud DeepSeek V4 Flash is free at ¥0. Model names and API providers follow the actual request parameters."," 次(高峰 ":" calls (peak "," / 空闲 ":" / off-peak "," · 缓存命中 ":" · cache hit "," · 输出 ":" · output "," · 推理 ":" · reasoning "," · 空闲消耗 ":" · off-peak cost ","‹ 上月":"‹ Prev month","下月 ›":"Next month ›","按消耗":"By cost","按调用数":"By calls","热力:":"Heatmap: ","低 → 高(":"low → high (","当日消耗":"Day cost","当日调用数":"Day calls","· 悬停查看详情,点击某天查看当日调用":"· hover for details, click a day for that day's calls","本月调用":"Calls this month","本月消耗":"Cost this month","本月暂无记录。":"No records this month.","日期":"Date","总消耗(自动)":"Total cost (auto)"," 调用明细":" call details"," 条 · ":" records · "," | 空闲 ":" | off-peak "," | 合计 ":" | total ","该日无记录。":"No records for this day.","消耗":"Cost","清除区间":"Clear range"," 条 · 输入·未命中 ":" records · input·miss "," · 缓存写入 ":" · cache write "," · 高峰消耗 ":" · peak cost ","该时间范围内没有记录。":"No records in this range.","‹ 上一页":"‹ Prev page","第 ":"Page "," 页 · 每页 ":" page · per page "," 条 · 共 ":" records · total ","下一页 ›":"Next page ›","新价格表生效时间未知":"New price schedule effective date unknown","新价格表(峰谷价)将于 ":"The new peak/valley price schedule takes effect ","(北京时间)生效,当前按旧价格表(基础价)计费":" (Beijing time); currently billing on the old base-price schedule","新价格表(峰谷价)已生效(自 ":"New peak/valley schedule in effect (since "," 起)":")","今日 $ / ¥ 汇率":"Today's $/¥ rate","暂不可用":"Temporarily unavailable","汇率日期 ":"Rate date ","等待获取":"Pending fetch","汇率状态":"Rate status","缓存汇率":"Cached rate","最新可用":"Latest available","获取失败":"Fetch failed","刷新汇率":"Refresh rate","美元计价的 DigitalOcean 消耗按该 USD/CNY 汇率换算成人民币;人民币定价不受汇率影响。":"USD-denominated DigitalOcean costs are converted to CNY at this rate; CNY pricing is unaffected by it.","DeepSeek 官方 API 价格表":"DeepSeek Official API Price Table","本表仅用于 DeepSeek 官方 Provider;第三方 Provider 按下方已核验的自身价格计费,无法可靠匹配价格的模型按 0 统计。价格按官方公布固定,不可编辑。":"This table applies only to the official DeepSeek provider; third-party providers bill at their own verified prices below, and models without a reliable price match are counted as 0. Prices are fixed as officially published and are not editable.","峰谷价":"Peak/valley","基础价":"Base","自动模式:按调用时间自动选择计费档位——新价格表生效前的调用按基础价(旧价格表),生效后的调用按峰谷价(高峰时段 9:00–12:00、14:00–18:00 用高峰价,其余空闲时段用空闲价)。当前显示:":"Auto mode: the billing tier is chosen automatically by call time — calls made before the new schedule took effect use base (old) prices, calls after it use peak/valley prices (peak hours 9:00–12:00 & 14:00–18:00 at peak price, other hours at off-peak). Currently shown:","基础价表(旧价格表,新价格生效前)":"Base price table (old schedule, before new prices take effect)","峰谷价表(新价格表)":"Peak/valley table (new schedule)","。价格按官方公布固定,不可编辑。":". Prices are fixed as officially published and are not editable.","缓存命中(输入)":"Cache hit (input)","空闲 · 缓存命中":"Off-peak · cache hit","空闲 · 输入":"Off-peak · input","空闲 · 输出":"Off-peak · output","高峰 · 缓存命中":"Peak · cache hit","高峰 · 输入":"Peak · input","高峰 · 输出":"Peak · output","单位:元 / 百万 tokens。高峰时段(北京时间 9:00–12:00、14:00–18:00)用高峰价,其余空闲时段用空闲价(空闲价 = 高峰价的一半)。概览、用量日历、缓存命中列表中的消耗均已按高峰 / 空闲分列统计。":"Unit: CNY per million tokens. Peak hours (Beijing 9:00–12:00 & 14:00–18:00) use peak prices; other hours use off-peak (= half of peak). Costs in Overview, Usage Calendar and Cache Hit List are split into peak / off-peak accordingly.","第三方平台价格与覆盖状态(2026-08-19 核验)":"Third-party platform prices & coverage (verified 2026-08-19)","只把能与记录中的 provider/model 可靠匹配的价格用于自动计费;美元价格先按 USD 计算,再乘 USD/CNY 汇率统一折算成人民币。":"Only prices that reliably match a record's provider/model are used for automatic billing; USD prices are computed in USD first, then converted to CNY at the USD/CNY rate.","模型 / 状态":"Model / status","输入":"Input","说明":"Notes","已用于自动计费":"Used for auto billing","缓存命中价按 2026-08-03 起生效公告":"Cache-hit price per the notice effective 2026-08-03","按输入价":"Billed at input price","公告未单列缓存命中价":"The notice lists no separate cache-hit price","自动按 USD/CNY 汇率换算人民币并计费":"Auto-converted to CNY at the USD/CNY rate and billed","Alibaba / 千问":"Alibaba / Qwen","暂不计费":"Not billed yet","调用与 token 正常统计;费用暂按 ¥0":"Calls and tokens tracked normally; cost counted as ¥0 for now","免费;费用固定按 ¥0":"Free; cost fixed at ¥0","DigitalOcean 使用美元官方价并按最新可用 USD/CNY 汇率折算人民币;千问暂不计费;AMD GPU Cloud 的 DeepSeek V4 Flash 免费按 ¥0。":"DigitalOcean bills at official USD prices converted via the latest available USD/CNY rate; Qwen is not billed yet; AMD GPU Cloud DeepSeek V4 Flash is free at ¥0.","概览":"Overview","用量日历":"Usage Calendar","缓存命中列表":"Cache Hit List","价格表":"Price Table","已导出:":"Exported: ","导出失败:":"Export failed: ","未知错误":"unknown error","导出中…":"Exporting…","生成图片中…":"Rendering image…","画布不可用":"Canvas unavailable","生成图片失败:":"Image rendering failed: ","打开目录选择…":"Open directory picker…","导出目标:":"Export destination:","选择目录失败:":"Failed to pick directory: ","打开文件夹失败:":"Failed to open folder: ","读取文件…":"Reading file…","导入成功:新增 ":"Import OK: added "," 条,跳过重复 ":", skipped duplicates "," 条,忽略无效 ":", ignored invalid "," 条,现有共 ":", total now ","导入失败:":"Import failed: ","读取文件失败":"Failed to read file","用量与消耗":"Usage & Cost","记录插件激活后的每一次模型调用":"Logs every model call since the plugin was activated","当前 · 高峰时段":"Current · peak hours","当前 · 空闲时段":"Current · off-peak hours","新价格已生效":"New prices in effect","新价格未生效":"New prices not yet in effect","刷新":"Refresh","清空":"Clear","计价说明:DeepSeek 官方与 SiliconFlow 按人民币价格;DigitalOcean 按美元官方价 × USD/CNY 汇率折算人民币;千问暂不计费;AMD GPU Cloud DeepSeek V4 Flash 免费按 ¥0。":"Billing notes: DeepSeek official and SiliconFlow bill in CNY; DigitalOcean bills at official USD prices × USD/CNY rate; Qwen is not billed yet; AMD GPU Cloud DeepSeek V4 Flash is free at ¥0.","导出 CSV":"Export CSV","导出 JSON":"Export JSON","导出图片 (PNG)":"Export image (PNG)","打开目录":"Open folder","导出目标目录(留空 = 默认数据目录)":"Export target directory (empty = default data dir)","选择目录…":"Choose directory…","已恢复默认数据目录":"Restored default data directory","重置":"Reset","选择文件导入":"Choose a file to import","数据持久化:":"Data persistence:","(每次调用实时落盘,插件重启后自动恢复,最多保留 100000 条)":"(every call is written to disk live, restored automatically after restart, up to 100000 records)","持久化未启用:":"Persistence disabled:","未知原因":"unknown reason","DEEPSEEK_API_KEY(推理 Key)":"DEEPSEEK_API_KEY (inference key)","模型设置中 Provider ID 或显示名为 siliconflow 的提供商 API Key":"API key of the provider whose Provider ID or display name is siliconflow in model settings","打开 SiliconFlow API 密钥":"Open SiliconFlow API keys","账户级 dop_v1_ Personal Access Token;Read Only(api:read)或 billing:read":"Account-level dop_v1_ Personal Access Token; Read Only (api:read) or billing:read","创建 DigitalOcean Account API Token":"Create a DigitalOcean account API token","暂无公开余额 API,仅支持控制台查看":"No public balance API yet — console only","打开 AMD Developer Cloud":"Open AMD Developer Cloud","查询失败":"Query failed","无法读取凭据状态":"Cannot read credential status","请输入以 dop_v1_ 开头的 DigitalOcean 账户 PAT。":"Enter a DigitalOcean account PAT starting with dop_v1_.","保存失败":"Save failed","使用凭据 ":"Using credential ","来源 ":"source ","模型提供商 ":"Model provider ","端点 ":"Endpoint "," · 账户余额":" · account balance","USD · 预付款/信用余额":"USD · prepaid/credit balance","USD · 待结算金额":"USD · pending amount","USD · 当前无余额":"USD · no balance"," · 账户可用":" · account available"," · 账户不可用":" · account unavailable"," · 查询成功":" · query succeeded","剩余余额查询":"Remaining balance query","查询 DeepSeek、SiliconFlow 与 DigitalOcean 账户余额":"Query DeepSeek, SiliconFlow and DigitalOcean account balances","打开控制台":"Open console","查询中…":"Querying…","查询账单":"Query billing","查询余额":"Query balance","所需凭据:":"Required credentials:","仅支持控制台查看":"Console only","插件不会尝试未经公开文档确认的端点,也不会把 AMD 推理 Key 当作账单凭据。":"The plugin never probes endpoints unconfirmed by public docs, and never uses AMD inference keys as billing credentials.","请确认凭据类型正确、权限包含余额/账单读取,且网络可访问服务商官方 API。":"Please verify the credential type is correct, its permissions include balance/billing read, and the provider's official API is reachable from this network.","打开服务商凭据页面":"Open provider credentials page","正在查询余额…":"Querying balance…","可用":"Available","不可用":"Unavailable","账单更新时间":"Billing updated","查询时间":"Queried at","北京时间":"Beijing time","请创建账户级 Personal Access Token。可选 Read Only(api:read,包含 billing:read),或自定义 billing:read;不要使用 Gradient AI 推理 Key。":"Please create an account-level Personal Access Token. Choose Read Only (api:read incl. billing:read) or a custom scope with billing:read; do not use a Gradient AI inference key.","正在检查已保存的 Token…":"Checking saved token…","已保存:":"Saved: ","(来源:":" (source: ","无法读取 Token 状态:":"Cannot read token status: ","尚未保存 DIGITALOCEAN_TOKEN。":"DIGITALOCEAN_TOKEN is not saved yet.","输入新的 dop_v1_ Token 可替换(当前值已隐藏)":"Enter a new dop_v1_ token to replace it (current value hidden)","保存中…":"Saving…","保存并查询":"Save & query","当前 Token 来自只读环境变量;请在原来源中修改,页面不会覆盖它。":"The current token comes from a read-only environment variable; edit it at its source — this panel will not overwrite it.","Token 已安全保存;页面和 API 响应不会回传明文。":"Token stored securely; neither the page nor API responses echo it back.","SiliconFlow 查询说明":"SiliconFlow query notes","公开 API 已成功返回 ¥0.00。SiliconFlow 的 /v1/user/info 当前不返回代金券或历史用量;控制台可用总额可能非零。这里忠实展示 API 原始余额字段,不把它等同于控制台完整额度。":"The public API returned ¥0.00 successfully. SiliconFlow's /v1/user/info currently returns no vouchers or historical usage; the console total may still be non-zero. The raw balance field from the API is shown faithfully here and should not be read as the full console credit.","余额卡片展示 SiliconFlow /v1/user/info 公开 API 返回的原始余额字段。":"The balance card shows the raw balance field returned by SiliconFlow's public /v1/user/info API.","返回字段":"Returned fields","凭据读取规则":"Credential lookup rules","插件只检查“设置 → 模型”中 Provider ID 或显示名为 siliconflow 的提供商,并读取其 apiKeyEnv 对应的已保存 API Key。":"The plugin only checks providers in Settings → Models whose Provider ID or display name is siliconflow, and reads the saved API key referenced by their apiKeyEnv.","如果未找到提供商、未填写 API Key 或凭据引用失效,插件会停止查询并说明需要修复的配置;不会回退到其他服务商的 Key。":"If no such provider exists, the key is empty, or the credential reference is broken, the plugin stops querying and explains what needs fixing; it never falls back to another provider's key.","DigitalOcean 查询的是主账户 Billing API;DigitalOcean AI 推理 Key 与 AMD GPU Cloud 推理 Key 均不能直接查询账单。":"DigitalOcean queries the main account Billing API; DigitalOcean AI inference keys and AMD GPU Cloud inference keys cannot query billing directly.","\n调用 ":"\ncalls ",")\n输入·未命中 ":")\ninput·miss ","\n高峰消耗 ":"\npeak cost ","\n无记录":"\nno records","查询 DeepSeek、SiliconFlow、DigitalOcean 与百炼 Token Plan":"Query DeepSeek, SiliconFlow, DigitalOcean & Bailian Token Plan balances","百炼 Token Plan · 1 周配额":"Bailian Token Plan · 1-week quota","百炼 Token Plan 配额用量":"Bailian Token Plan quota usage","百炼 Token Plan":"Bailian Token Plan","本周配额":"This week's quota","开始":"Start","结束":"End","已用 ":"Used ","百炼 Token Plan 查询说明":"Bailian Token Plan query notes","本查询复用百炼 CLI(bl)的“控制台登录”OAuth token,不需要阿里云 AccessKey,权限面很小。":"This query reuses the console-login OAuth token from the Bailian CLI (bl); no Alibaba Cloud AccessKey is needed and the permission scope is minimal.","如尚未登录,请先在终端执行:bl auth login --console,然后回到本页点“查询余额”。":"If you haven't logged in yet, run: bl auth login --console, then come back and click 'Query balance'.","打开百炼 CLI 登录说明":"Open Bailian CLI login docs","百炼控制台 OAuth 登录(~/.bailian/config.json 的 access_token,用 bl auth login --console 生成)":"Bailian console OAuth login (access_token in ~/.bailian/config.json, created by bl auth login --console)","数据来自阿里云百炼控制台内部门户网关(zeldaHttp.apikeyMgr./tokenplan/personal/api/v2/usage),展示的是 1 周配额已用进度与周期。":"Data from Alibaba Cloud Bailian console portal gateway (zeldaHttp.apikeyMgr./tokenplan/personal/api/v2/usage), showing 1-week quota usage progress and period."};
|
|
24
|
+
var __T_EN = {"本期无消耗(所有用量为 0)":"No usage this turn (all zeros)","按模型":"By model","…及另外 ":"…plus "," 个模型(完整列表见「用量与消耗」页签)":" more models (full list in the Usage & Cost tab)","对话累计":"Conversation total","本轮明细":"This turn","本轮 token":"Turn tokens","本轮消耗":"Turn cost","本轮缓存命中率":"Turn hit rate","本轮耗时":"Turn duration","本轮无记录":"No records this turn","工具调用(内部)":"Tool calls (internal)"," 条内部调用":" internal calls","展开":"Expand","收起":"Collapse","详细记录请在「用量与消耗」页签查看":"Full details are in the Usage & Cost tab","获取中…":"Loading…","总 token":"Total tokens","Token 明细":"Token details","本次输出":"This output","总消耗":"Total cost","耗时":"Run time","缓存命中率":"Cache hit rate","关闭":"Close","无 Token 数据":"No token data","当前 · ":"Current · ","工作日高峰时段":"Weekday peak hours","工作日空闲时段":"Weekday off-peak hours","周末高峰时段":"Weekend peak hours","周末空闲时段":"Weekend off-peak hours","周一至周五 9:00–12:00、14:00–18:00":"Mon–Fri 9:00–12:00 & 14:00–18:00","工作日其余时间":"Other weekday hours","周末(周六、周日)全天按空闲价计费":"All weekend (Sat & Sun) billed at the off-peak rate","2026-08-23 前仍按原规则分峰谷":"Before 2026-08-23 the old peak/valley split still applies","高峰 / 空闲时段说明":"Peak / off-peak hours","工作日高峰":"Weekday peak","空闲时段":"Off-peak hours"," 周一至周五 9:00–12:00、14:00–18:00":" Mon–Fri 9:00–12:00 & 14:00–18:00"," 工作日其余时间,以及周末(周六、周日)全天(自 2026-08-23 起;此前仍按原规则分峰谷)":" Weekday off-peak hours, plus all weekend (Sat & Sun) since 2026-08-23 (before that, the old peak/valley split still applies)","工作日高峰 9:00–12:00、14:00–18:00 · 周末全天空闲价":"Weekday peak 9:00–12:00 & 14:00–18:00 · all-weekend off-peak rate","\n调用 ":"\ncalls ",")\n输入·未命中 ":")\ninput·miss ","\n高峰消耗 ":"\npeak cost ","\n无记录":"\nno records","自动模式:按调用时间自动选择计费档位——新价格表生效前的调用按基础价(旧价格表),生效后的调用按峰谷价(工作日高峰时段 9:00–12:00、14:00–18:00 用高峰价,其余时间——含周末全天——用空闲价)。当前显示:":"Auto mode: the billing tier is chosen automatically by call time — calls made before the new price schedule took effect are billed at base (old) prices; calls after it use peak/valley prices (weekday peak hours 9:00–12:00 & 14:00–18:00 at the peak price, all other times — including all weekend — at the off-peak price). Currently shown:","单位:元 / 百万 tokens。工作日高峰时段(北京时间 9:00–12:00、14:00–18:00)用高峰价,其余时间用空闲价(空闲价 = 高峰价的一半);自 2026-08-23 起周末(周六、周日)全天按空闲价计费,新规则生效前仍按原规则。概览、用量日历、缓存命中列表中的消耗均已按高峰 / 空闲分列统计。":"Unit: CNY per million tokens. Weekday peak hours (Beijing time 9:00–12:00 & 14:00–18:00) use the peak price; all other times use the off-peak price (off-peak = half of peak). Since 2026-08-23, weekends (Sat & Sun) are billed at the off-peak rate all day; calls before the new rule still follow the old split. Costs in Overview, Usage Calendar and the Cache Hit List are split into peak / off-peak accordingly.","未知模型":"Unknown model","未知服务商":"Unknown provider","完成":"Done","工具调用":"Tool call","超长":"Overlong","错误":"Error","自动(生效前基础价 · 生效后峰谷价)":"Auto (base price before effective date · peak/valley after)","基础价格":"Base price","峰谷价格":"Peak/valley price","自动":"Auto","canvas 2d 不可用":"canvas 2D unavailable","用量与消耗报告":"Usage & Cost Report","生成时间 ":"Generated ","(北京) · 共 ":" (Beijing) · total "," 条调用 · 缓存命中率 ":" calls · cache-hit rate "," · 高峰价消耗 ":" · peak-rate cost "," · 空闲价消耗 ":" · off-peak cost "," · 合计 ":" · total "," · 报告仅含最近 ":" · report limited to the latest "," 条":" records","调用次数":"Calls","输入 · 未命中":"Input · miss","输入 · 缓存命中":"Input · cache hit","输出":"Output","高峰消耗":"Peak cost","空闲消耗":"Off-peak cost","总消耗 (峰谷价)":"Total cost (peak/valley)","缓存命中列表(共 ":"Cache hit list ("," 条)":" records)","时间(北京)":"Time (Beijing)","模型":"Model","输入·未命中":"Input·miss","缓存命中":"Cache hit","缓存写入":"Cache write","推理":"Reasoning","命中率":"Hit rate","时段":"Period","结束":"End","消耗(峰谷)":"Cost (peak/valley)","峰":"Peak","谷":"Valley","消耗表(按模型 · 峰谷价 · 高峰/空闲分列)":"Cost table (by model · peak/valley prices)","调用":"Calls","总消耗":"Total cost","合计":"Total","消耗表(按 API 服务商 × 模型 · 峰谷价)":"Cost table (by API provider × model · peak/valley)","API 服务商 / 模型":"API provider / model"," 个模型)":" models)","总费用合计":"Total cost overall","计价按请求的 API 服务商分别应用已核验价格;DigitalOcean 美元价按 USD/CNY 汇率折算;无法可靠映射的第三方价格按 ¥0。":"Billing applies verified prices per requesting API provider; DigitalOcean USD prices are converted at the USD/CNY rate; third-party prices that cannot be mapped reliably are counted as ¥0."," 个服务商":" providers","今天":"Today","近7天":"Last 7 days","近30天":"Last 30 days","全部":"All","自定义区间:":"Custom range:","至":"to","清除筛选":"Clear filters","当前范围:共 ":"Current range: ","(全部记录)":"(all records)"," · 总消耗 ":" · total cost ","高峰 ":"Peak "," · 空闲 ":" · off-peak ","命中率 ":"hit rate ","高峰时段 9:00–12:00、14:00–18:00":"Peak hours 9:00–12:00 & 14:00–18:00","其余空闲时段":"other hours are off-peak","高峰 + 空闲 · ":"Peak + off-peak · ","消耗表(按模型)":"Cost table (by model)","API 服务商":"API provider","高峰/空闲":"Peak/Off-peak","消耗明细(按 API 服务商 × 模型)":"Cost breakdown (by API provider × model)","DeepSeek 官方请求按官方峰谷价;SiliconFlow 按自身人民币公开价;DigitalOcean 按美元公开价 × USD/CNY 汇率折算人民币;千问暂不计费;AMD GPU Cloud DeepSeek V4 Flash 免费按 ¥0。模型名与 API 服务商均以请求参数为准。":"DeepSeek official requests use official peak/valley prices; SiliconFlow uses its own public CNY prices; DigitalOcean uses public USD prices converted to CNY at the USD/CNY rate; Qwen is not billed yet; AMD GPU Cloud DeepSeek V4 Flash is free at ¥0. Model names and API providers follow the actual request parameters."," 次(高峰 ":" calls (peak "," / 空闲 ":" / off-peak "," · 缓存命中 ":" · cache hit "," · 输出 ":" · output "," · 推理 ":" · reasoning "," · 空闲消耗 ":" · off-peak cost ","‹ 上月":"‹ Prev month","下月 ›":"Next month ›","按消耗":"By cost","按调用数":"By calls","热力:":"Heatmap: ","低 → 高(":"low → high (","当日消耗":"Day cost","当日调用数":"Day calls","· 悬停查看详情,点击某天查看当日调用":"· hover for details, click a day for that day's calls","本月调用":"Calls this month","本月消耗":"Cost this month","本月暂无记录。":"No records this month.","日期":"Date","总消耗(自动)":"Total cost (auto)"," 调用明细":" call details"," 条 · ":" records · "," | 空闲 ":" | off-peak "," | 合计 ":" | total ","该日无记录。":"No records for this day.","消耗":"Cost","清除区间":"Clear range"," 条 · 输入·未命中 ":" records · input·miss "," · 缓存写入 ":" · cache write "," · 高峰消耗 ":" · peak cost ","该时间范围内没有记录。":"No records in this range.","‹ 上一页":"‹ Prev page","第 ":"Page "," 页 · 每页 ":" page · per page "," 条 · 共 ":" records · total ","下一页 ›":"Next page ›","新价格表生效时间未知":"New price schedule effective date unknown","新价格表(峰谷价)将于 ":"The new peak/valley price schedule takes effect ","(北京时间)生效,当前按旧价格表(基础价)计费":" (Beijing time); currently billing on the old base-price schedule","新价格表(峰谷价)已生效(自 ":"New peak/valley schedule in effect (since "," 起)":")","今日 $ / ¥ 汇率":"Today's $/¥ rate","暂不可用":"Temporarily unavailable","汇率日期 ":"Rate date ","等待获取":"Pending fetch","汇率状态":"Rate status","缓存汇率":"Cached rate","最新可用":"Latest available","获取失败":"Fetch failed","刷新汇率":"Refresh rate","美元计价的 DigitalOcean 消耗按该 USD/CNY 汇率换算成人民币;人民币定价不受汇率影响。":"USD-denominated DigitalOcean costs are converted to CNY at this rate; CNY pricing is unaffected by it.","DeepSeek 官方 API 价格表":"DeepSeek Official API Price Table","本表仅用于 DeepSeek 官方 Provider;第三方 Provider 按下方已核验的自身价格计费,无法可靠匹配价格的模型按 0 统计。价格按官方公布固定,不可编辑。":"This table applies only to the official DeepSeek provider; third-party providers bill at their own verified prices below, and models without a reliable price match are counted as 0. Prices are fixed as officially published and are not editable.","峰谷价":"Peak/valley","基础价":"Base","自动模式:按调用时间自动选择计费档位——新价格表生效前的调用按基础价(旧价格表),生效后的调用按峰谷价(高峰时段 9:00–12:00、14:00–18:00 用高峰价,其余空闲时段用空闲价)。当前显示:":"Auto mode: the billing tier is chosen automatically by call time — calls made before the new schedule took effect use base (old) prices, calls after it use peak/valley prices (peak hours 9:00–12:00 & 14:00–18:00 at peak price, other hours at off-peak). Currently shown:","基础价表(旧价格表,新价格生效前)":"Base price table (old schedule, before new prices take effect)","峰谷价表(新价格表)":"Peak/valley table (new schedule)","。价格按官方公布固定,不可编辑。":". Prices are fixed as officially published and are not editable.","缓存命中(输入)":"Cache hit (input)","空闲 · 缓存命中":"Off-peak · cache hit","空闲 · 输入":"Off-peak · input","空闲 · 输出":"Off-peak · output","高峰 · 缓存命中":"Peak · cache hit","高峰 · 输入":"Peak · input","高峰 · 输出":"Peak · output","单位:元 / 百万 tokens。高峰时段(北京时间 9:00–12:00、14:00–18:00)用高峰价,其余空闲时段用空闲价(空闲价 = 高峰价的一半)。概览、用量日历、缓存命中列表中的消耗均已按高峰 / 空闲分列统计。":"Unit: CNY per million tokens. Peak hours (Beijing 9:00–12:00 & 14:00–18:00) use peak prices; other hours use off-peak (= half of peak). Costs in Overview, Usage Calendar and Cache Hit List are split into peak / off-peak accordingly.","第三方平台价格与覆盖状态(2026-08-19 核验)":"Third-party platform prices & coverage (verified 2026-08-19)","只把能与记录中的 provider/model 可靠匹配的价格用于自动计费;美元价格先按 USD 计算,再乘 USD/CNY 汇率统一折算成人民币。":"Only prices that reliably match a record's provider/model are used for automatic billing; USD prices are computed in USD first, then converted to CNY at the USD/CNY rate.","模型 / 状态":"Model / status","输入":"Input","说明":"Notes","已用于自动计费":"Used for auto billing","缓存命中价按 2026-08-03 起生效公告":"Cache-hit price per the notice effective 2026-08-03","按输入价":"Billed at input price","公告未单列缓存命中价":"The notice lists no separate cache-hit price","自动按 USD/CNY 汇率换算人民币并计费":"Auto-converted to CNY at the USD/CNY rate and billed","Alibaba / 千问":"Alibaba / Qwen","暂不计费":"Not billed yet","调用与 token 正常统计;费用暂按 ¥0":"Calls and tokens tracked normally; cost counted as ¥0 for now","免费;费用固定按 ¥0":"Free; cost fixed at ¥0","DigitalOcean 使用美元官方价并按最新可用 USD/CNY 汇率折算人民币;千问暂不计费;AMD GPU Cloud 的 DeepSeek V4 Flash 免费按 ¥0。":"DigitalOcean bills at official USD prices converted via the latest available USD/CNY rate; Qwen is not billed yet; AMD GPU Cloud DeepSeek V4 Flash is free at ¥0.","概览":"Overview","用量日历":"Usage Calendar","缓存命中列表":"Cache Hit List","价格表":"Price Table","已导出:":"Exported: ","导出失败:":"Export failed: ","未知错误":"unknown error","导出中…":"Exporting…","生成图片中…":"Rendering image…","画布不可用":"Canvas unavailable","生成图片失败:":"Image rendering failed: ","打开目录选择…":"Open directory picker…","导出目标:":"Export destination:","选择目录失败:":"Failed to pick directory: ","打开文件夹失败:":"Failed to open folder: ","读取文件…":"Reading file…","导入成功:新增 ":"Import OK: added "," 条,跳过重复 ":", skipped duplicates "," 条,忽略无效 ":", ignored invalid "," 条,现有共 ":", total now ","导入失败:":"Import failed: ","读取文件失败":"Failed to read file","用量与消耗":"Usage & Cost","记录插件激活后的每一次模型调用":"Logs every model call since the plugin was activated","当前 · 高峰时段":"Current · peak hours","当前 · 空闲时段":"Current · off-peak hours","新价格已生效":"New prices in effect","新价格未生效":"New prices not yet in effect","刷新":"Refresh","清空":"Clear","计价说明:DeepSeek 官方与 SiliconFlow 按人民币价格;DigitalOcean 按美元官方价 × USD/CNY 汇率折算人民币;千问暂不计费;AMD GPU Cloud DeepSeek V4 Flash 免费按 ¥0。":"Billing notes: DeepSeek official and SiliconFlow bill in CNY; DigitalOcean bills at official USD prices × USD/CNY rate; Qwen is not billed yet; AMD GPU Cloud DeepSeek V4 Flash is free at ¥0.","导出 CSV":"Export CSV","导出 JSON":"Export JSON","导出图片 (PNG)":"Export image (PNG)","打开目录":"Open folder","导出目标目录(留空 = 默认数据目录)":"Export target directory (empty = default data dir)","选择目录…":"Choose directory…","已恢复默认数据目录":"Restored default data directory","重置":"Reset","选择文件导入":"Choose a file to import","数据持久化:":"Data persistence:","(每次调用实时落盘,插件重启后自动恢复,最多保留 100000 条)":"(every call is written to disk live, restored automatically after restart, up to 100000 records)","持久化未启用:":"Persistence disabled:","未知原因":"unknown reason","DEEPSEEK_API_KEY(推理 Key)":"DEEPSEEK_API_KEY (inference key)","模型设置中 Provider ID 或显示名为 siliconflow 的提供商 API Key":"API key of the provider whose Provider ID or display name is siliconflow in model settings","打开 SiliconFlow API 密钥":"Open SiliconFlow API keys","账户级 dop_v1_ Personal Access Token;Read Only(api:read)或 billing:read":"Account-level dop_v1_ Personal Access Token; Read Only (api:read) or billing:read","创建 DigitalOcean Account API Token":"Create a DigitalOcean account API token","暂无公开余额 API,仅支持控制台查看":"No public balance API yet — console only","打开 AMD Developer Cloud":"Open AMD Developer Cloud","查询失败":"Query failed","无法读取凭据状态":"Cannot read credential status","请输入以 dop_v1_ 开头的 DigitalOcean 账户 PAT。":"Enter a DigitalOcean account PAT starting with dop_v1_.","保存失败":"Save failed","使用凭据 ":"Using credential ","来源 ":"source ","模型提供商 ":"Model provider ","端点 ":"Endpoint "," · 账户余额":" · account balance","USD · 预付款/信用余额":"USD · prepaid/credit balance","USD · 待结算金额":"USD · pending amount","USD · 当前无余额":"USD · no balance"," · 账户可用":" · account available"," · 账户不可用":" · account unavailable"," · 查询成功":" · query succeeded","剩余余额查询":"Remaining balance query","查询 DeepSeek、SiliconFlow 与 DigitalOcean 账户余额":"Query DeepSeek, SiliconFlow and DigitalOcean account balances","打开控制台":"Open console","查询中…":"Querying…","查询账单":"Query billing","查询余额":"Query balance","所需凭据:":"Required credentials:","仅支持控制台查看":"Console only","插件不会尝试未经公开文档确认的端点,也不会把 AMD 推理 Key 当作账单凭据。":"The plugin never probes endpoints unconfirmed by public docs, and never uses AMD inference keys as billing credentials.","请确认凭据类型正确、权限包含余额/账单读取,且网络可访问服务商官方 API。":"Please verify the credential type is correct, its permissions include balance/billing read, and the provider's official API is reachable from this network.","打开服务商凭据页面":"Open provider credentials page","正在查询余额…":"Querying balance…","可用":"Available","不可用":"Unavailable","账单更新时间":"Billing updated","查询时间":"Queried at","北京时间":"Beijing time","请创建账户级 Personal Access Token。可选 Read Only(api:read,包含 billing:read),或自定义 billing:read;不要使用 Gradient AI 推理 Key。":"Please create an account-level Personal Access Token. Choose Read Only (api:read incl. billing:read) or a custom scope with billing:read; do not use a Gradient AI inference key.","正在检查已保存的 Token…":"Checking saved token…","已保存:":"Saved: ","(来源:":" (source: ","无法读取 Token 状态:":"Cannot read token status: ","尚未保存 DIGITALOCEAN_TOKEN。":"DIGITALOCEAN_TOKEN is not saved yet.","输入新的 dop_v1_ Token 可替换(当前值已隐藏)":"Enter a new dop_v1_ token to replace it (current value hidden)","保存中…":"Saving…","保存并查询":"Save & query","当前 Token 来自只读环境变量;请在原来源中修改,页面不会覆盖它。":"The current token comes from a read-only environment variable; edit it at its source — this panel will not overwrite it.","Token 已安全保存;页面和 API 响应不会回传明文。":"Token stored securely; neither the page nor API responses echo it back.","SiliconFlow 查询说明":"SiliconFlow query notes","公开 API 已成功返回 ¥0.00。SiliconFlow 的 /v1/user/info 当前不返回代金券或历史用量;控制台可用总额可能非零。这里忠实展示 API 原始余额字段,不把它等同于控制台完整额度。":"The public API returned ¥0.00 successfully. SiliconFlow's /v1/user/info currently returns no vouchers or historical usage; the console total may still be non-zero. The raw balance field from the API is shown faithfully here and should not be read as the full console credit.","余额卡片展示 SiliconFlow /v1/user/info 公开 API 返回的原始余额字段。":"The balance card shows the raw balance field returned by SiliconFlow's public /v1/user/info API.","返回字段":"Returned fields","凭据读取规则":"Credential lookup rules","插件只检查“设置 → 模型”中 Provider ID 或显示名为 siliconflow 的提供商,并读取其 apiKeyEnv 对应的已保存 API Key。":"The plugin only checks providers in Settings → Models whose Provider ID or display name is siliconflow, and reads the saved API key referenced by their apiKeyEnv.","如果未找到提供商、未填写 API Key 或凭据引用失效,插件会停止查询并说明需要修复的配置;不会回退到其他服务商的 Key。":"If no such provider exists, the key is empty, or the credential reference is broken, the plugin stops querying and explains what needs fixing; it never falls back to another provider's key.","DigitalOcean 查询的是主账户 Billing API;DigitalOcean AI 推理 Key 与 AMD GPU Cloud 推理 Key 均不能直接查询账单。":"DigitalOcean queries the main account Billing API; DigitalOcean AI inference keys and AMD GPU Cloud inference keys cannot query billing directly.","\n调用 ":"\ncalls ",")\n输入·未命中 ":")\ninput·miss ","\n高峰消耗 ":"\npeak cost ","\n无记录":"\nno records","查询 DeepSeek、SiliconFlow、DigitalOcean 与百炼 Token Plan":"Query DeepSeek, SiliconFlow, DigitalOcean & Bailian Token Plan balances","百炼 Token Plan · 1 周配额":"Bailian Token Plan · 1-week quota","百炼 Token Plan 配额用量":"Bailian Token Plan quota usage","百炼 Token Plan":"Bailian Token Plan","本周配额":"This week's quota","开始":"Start","结束":"End","已用 ":"Used ","百炼 Token Plan 查询说明":"Bailian Token Plan query notes","本查询复用百炼 CLI(bl)的“控制台登录”OAuth token,不需要阿里云 AccessKey,权限面很小。":"This query reuses the console-login OAuth token from the Bailian CLI (bl); no Alibaba Cloud AccessKey is needed and the permission scope is minimal.","如尚未登录,请先在终端执行:bl auth login --console,然后回到本页点“查询余额”。":"If you haven't logged in yet, run: bl auth login --console, then come back and click 'Query balance'.","打开百炼 CLI 登录说明":"Open Bailian CLI login docs","百炼控制台 OAuth 登录(~/.bailian/config.json 的 access_token,用 bl auth login --console 生成)":"Bailian console OAuth login (access_token in ~/.bailian/config.json, created by bl auth login --console)","数据来自阿里云百炼控制台内部门户网关(zeldaHttp.apikeyMgr./tokenplan/personal/api/v2/usage),展示的是 1 周配额已用进度与周期。":"Data from Alibaba Cloud Bailian console portal gateway (zeldaHttp.apikeyMgr./tokenplan/personal/api/v2/usage), showing 1-week quota usage progress and period."};
|
|
12
25
|
|
|
13
26
|
function __detectLang() {
|
|
14
|
-
|
|
27
|
+
// 语言完全跟随宿主设置(通用设置 → 语言):不再读 localStorage,避免旧选择覆盖系统语言
|
|
15
28
|
try { var nav = (typeof navigator !== "undefined" && navigator.language) || "en"; return nav.toLowerCase().indexOf("zh") === 0 ? "zh" : "en"; } catch (e2) { return "en"; }
|
|
16
29
|
}
|
|
17
30
|
var __LANG = __detectLang();
|
|
@@ -101,10 +114,20 @@ window.__ModuleLoader__.load({
|
|
|
101
114
|
var next = lang === "zh" ? "zh" : "en";
|
|
102
115
|
if (next === __LANG) return __LANG;
|
|
103
116
|
__LANG = next;
|
|
104
|
-
|
|
117
|
+
// 仅内存生效:不再写 localStorage,语言始终以宿主设置为准
|
|
105
118
|
for (var i = 0; i < __langListeners.length; i++) { try { __langListeners[i](__LANG); } catch (e2) {} }
|
|
106
119
|
return __LANG;
|
|
107
120
|
};
|
|
121
|
+
// 跟随宿主语言服务(通用设置 → 语言):读取当前语言并订阅变化,切换后即时生效
|
|
122
|
+
var syncWithSystemLocale = function (locale) {
|
|
123
|
+
if (!locale || typeof locale.getLocale !== "function") return;
|
|
124
|
+
var sync = function () {
|
|
125
|
+
var snap = locale.getLocale();
|
|
126
|
+
setLang(snap && snap.active === "zh" ? "zh" : "en");
|
|
127
|
+
};
|
|
128
|
+
sync();
|
|
129
|
+
if (typeof locale.subscribe === "function") locale.subscribe(sync);
|
|
130
|
+
};
|
|
108
131
|
function useLang() {
|
|
109
132
|
var st = React.useState(__LANG);
|
|
110
133
|
React.useEffect(function () {
|
|
@@ -146,11 +169,9 @@ window.__ModuleLoader__.load({
|
|
|
146
169
|
subtab: { border: "1px solid rgba(128,128,128,.35)", background: "transparent", borderRadius: 6, padding: "5px 14px", fontSize: fs(12), cursor: "pointer", color: "inherit" },
|
|
147
170
|
subtabOn: { border: "1px solid rgba(90,140,255,.5)", background: "rgba(90,140,255,.22)", borderRadius: 6, padding: "5px 14px", fontSize: fs(12), cursor: "pointer", color: "inherit", fontWeight: 600 },
|
|
148
171
|
scroll: { overflowX: "auto", overflowY: "hidden", width: "100%", maxWidth: "100%", minWidth: 0 },
|
|
149
|
-
//
|
|
150
|
-
//
|
|
151
|
-
|
|
152
|
-
// 窄屏时表格保持 720px、外层 st.scroll 容器横向滚动,避免 9–11 列被压到逐字竖排。
|
|
153
|
-
tbl: { width: "100%", minWidth: 720, borderCollapse: "collapse", fontSize: fs(12) },
|
|
172
|
+
// 表格按内容撑开(width:max-content),列不被压缩,数值不会换行/重叠;
|
|
173
|
+
// 外层 st.scroll(overflow-x:auto)在窗口不够宽时横向滚动。minWidth:720 兜底极窄屏。
|
|
174
|
+
tbl: { width: "max-content", minWidth: 720, borderCollapse: "collapse", fontSize: fs(12) },
|
|
154
175
|
th: { textAlign: "right", padding: "7px 8px", borderBottom: "1px solid rgba(128,128,128,.2)", whiteSpace: "normal", wordBreak: "break-word", opacity: 0.55, fontWeight: 500, lineHeight: 1.5 },
|
|
155
176
|
thFirst: { textAlign: "left", padding: "7px 8px", borderBottom: "1px solid rgba(128,128,128,.2)", whiteSpace: "normal", wordBreak: "break-word", opacity: 0.55, fontWeight: 500, lineHeight: 1.5 },
|
|
156
177
|
td: { textAlign: "right", padding: "7px 8px", borderBottom: "1px solid rgba(128,128,128,.2)", whiteSpace: "normal", wordBreak: "break-word" },
|
|
@@ -230,7 +251,10 @@ window.__ModuleLoader__.load({
|
|
|
230
251
|
// 极窄屏 (≤ 540px):对带 collapse-mobile 的宽表隐藏第 4 列至倒数第 2 列,
|
|
231
252
|
// 只保留前 3 个主标识列 + 最后合计列(如 消耗表/明细表 保留 模型/服务商/调用/总消耗),
|
|
232
253
|
// 避免每列被压到极限宽度而逐字竖排。价格表等窄表不折叠,保留横向滚动。
|
|
233
|
-
"@media (max-width:540px){.dsh-usage-table.collapse-mobile tr>*:not(:nth-child(-n+3)):not(:last-child){display:none}.dsh-usage-table.collapse-mobile{min-width:320px}}"
|
|
254
|
+
"@media (max-width:540px){.dsh-usage-table.collapse-mobile tr>*:not(:nth-child(-n+3)):not(:last-child){display:none}.dsh-usage-table.collapse-mobile{min-width:320px}}" +
|
|
255
|
+
// 移动端适配(≤900px):表格贴合屏宽、不出现横向滑条;宽表同样折叠中间列。
|
|
256
|
+
// !important 覆盖内联 minWidth 与其它插件的 min-width 规则。
|
|
257
|
+
"@media (max-width:900px){.dsh-usage-table{min-width:0!important;width:100%!important;max-width:100%!important}.dsh-usage-table.collapse-mobile tr>*:not(:nth-child(-n+3)):not(:last-child){display:none!important}.dsh-usage-table.collapse-mobile{min-width:0!important}}";
|
|
234
258
|
document.head.appendChild(styleSheetEl);
|
|
235
259
|
}
|
|
236
260
|
|
|
@@ -384,12 +408,12 @@ var api = function (payload) {
|
|
|
384
408
|
// ── 计费时段说明(区分工作日高峰 / 工作日空闲 / 周末全天空闲)──
|
|
385
409
|
function BillingHint(props) {
|
|
386
410
|
return el("div", { style: st.billHint },
|
|
387
|
-
el("div", { style: st.billHintTitle }, "高峰 / 空闲时段说明"),
|
|
411
|
+
el("div", { style: st.billHintTitle }, t("高峰 / 空闲时段说明")),
|
|
388
412
|
el("div", null,
|
|
389
|
-
el("span", { style: st.badgePeak }, "工作日高峰"), " 周一至周五 9:00–12:00、14:00–18:00"
|
|
413
|
+
el("span", { style: st.badgePeak }, t("工作日高峰")), t(" 周一至周五 9:00–12:00、14:00–18:00")
|
|
390
414
|
),
|
|
391
415
|
el("div", null,
|
|
392
|
-
el("span", { style: st.badgeValley }, "空闲时段"), " 工作日其余时间,以及周末(周六、周日)全天(自 2026-08-23 起;此前仍按原规则分峰谷)"
|
|
416
|
+
el("span", { style: st.badgeValley }, t("空闲时段")), t(" 工作日其余时间,以及周末(周六、周日)全天(自 2026-08-23 起;此前仍按原规则分峰谷)")
|
|
393
417
|
)
|
|
394
418
|
);
|
|
395
419
|
}
|
|
@@ -628,6 +652,9 @@ var api = function (payload) {
|
|
|
628
652
|
// ── Overview view ──
|
|
629
653
|
function OverviewView(props) {
|
|
630
654
|
var allRecords = props.records, regime = props.regime;
|
|
655
|
+
var toolCalls = props.toolCalls || [];
|
|
656
|
+
var toolOpenState = React.useState(false);
|
|
657
|
+
var toolOpen = toolOpenState[0], setToolOpen = toolOpenState[1];
|
|
631
658
|
var presetState = React.useState("all");
|
|
632
659
|
var preset = presetState[0], setPreset = presetState[1];
|
|
633
660
|
var fromState = React.useState("");
|
|
@@ -711,6 +738,18 @@ var api = function (payload) {
|
|
|
711
738
|
models.sort(function (a, b) { return b.cost - a.cost || (a.modelKey < b.modelKey ? -1 : 1); });
|
|
712
739
|
providerGroups.push({ provider: gprov, models: models });
|
|
713
740
|
}
|
|
741
|
+
// 内部工具/占位调用(单独一个可折叠分组)
|
|
742
|
+
var toolAgg = {};
|
|
743
|
+
for (var ti = 0; ti < toolCalls.length; ti++) {
|
|
744
|
+
var tr = toolCalls[ti];
|
|
745
|
+
var tk = (tr.provider || '') + '|' + (tr.model || '');
|
|
746
|
+
var tg = toolAgg[tk];
|
|
747
|
+
if (!tg) { tg = toolAgg[tk] = { provider: tr.provider || '—', model: tr.model || '—', calls: 0, cost: 0 }; }
|
|
748
|
+
tg.calls += 1; tg.cost += tr.autoCost || 0;
|
|
749
|
+
}
|
|
750
|
+
var toolRows = [];
|
|
751
|
+
for (var tki in toolAgg) toolRows.push(toolAgg[tki]);
|
|
752
|
+
toolRows.sort(function (a, b) { return b.calls - a.calls || (a.provider < b.provider ? -1 : 1); });
|
|
714
753
|
// 服务商 × 模型明细行(组头行 + 组内模型行)
|
|
715
754
|
var pmRows = [];
|
|
716
755
|
for (var gi = 0; gi < providerGroups.length; gi++) {
|
|
@@ -841,7 +880,29 @@ var api = function (payload) {
|
|
|
841
880
|
)
|
|
842
881
|
),
|
|
843
882
|
el("div", { style: st.note, marginTop: 8 },
|
|
844
|
-
t("DeepSeek 官方请求按官方峰谷价;SiliconFlow 按自身人民币公开价;DigitalOcean 按美元公开价 × USD/CNY 汇率折算人民币;千问暂不计费;AMD GPU Cloud DeepSeek V4 Flash 免费按 ¥0。模型名与 API 服务商均以请求参数为准。"))
|
|
883
|
+
t("DeepSeek 官方请求按官方峰谷价;SiliconFlow 按自身人民币公开价;DigitalOcean 按美元公开价 × USD/CNY 汇率折算人民币;千问暂不计费;AMD GPU Cloud DeepSeek V4 Flash 免费按 ¥0。模型名与 API 服务商均以请求参数为准。")),
|
|
884
|
+
toolCalls.length ? el("div", { style: st.sec, marginTop: 14 },
|
|
885
|
+
el("div", { style: { display: "flex", alignItems: "center", gap: 8 } },
|
|
886
|
+
el("span", { style: { fontSize: fs(13), fontWeight: 600 } }, t("工具调用(内部)") + " · " + fmtInt(toolCalls.length) + t(" 条内部调用")),
|
|
887
|
+
el("button", { style: st.btn, onClick: function () { setToolOpen(!toolOpen); } }, toolOpen ? t("收起") : t("展开"))
|
|
888
|
+
),
|
|
889
|
+
toolOpen ? el("div", { style: st.scroll },
|
|
890
|
+
el("table", { className: "dsh-usage-table collapse-mobile", style: st.tbl },
|
|
891
|
+
el("thead", null, el("tr", null,
|
|
892
|
+
el("th", { style: st.thFirst }, t("API 服务商 / 模型")),
|
|
893
|
+
el("th", { style: st.th }, t("调用")),
|
|
894
|
+
el("th", { style: st.th }, t("总消耗"))
|
|
895
|
+
)),
|
|
896
|
+
el("tbody", null, toolRows.map(function (tg, idx) {
|
|
897
|
+
return el("tr", { key: String(idx) },
|
|
898
|
+
el("td", { style: st.tdFirst }, tg.model + " · " + tg.provider),
|
|
899
|
+
el("td", { style: st.td }, fmtInt(tg.calls)),
|
|
900
|
+
el("td", { style: st.td }, fmtMoney(tg.cost))
|
|
901
|
+
);
|
|
902
|
+
}))
|
|
903
|
+
)
|
|
904
|
+
) : null
|
|
905
|
+
) : null
|
|
845
906
|
);
|
|
846
907
|
}
|
|
847
908
|
|
|
@@ -1310,7 +1371,7 @@ var api = function (payload) {
|
|
|
1310
1371
|
];
|
|
1311
1372
|
|
|
1312
1373
|
function UsagePanel(props) {
|
|
1313
|
-
var lang = useLang();
|
|
1374
|
+
var lang = useLang(); // 订阅语言变化:宿主切换语言时触发本面板重渲染(值本身仅作触发用)
|
|
1314
1375
|
var state = React.useState({ records: [], count: 0, dataPath: "", persistOk: false, persistError: "", pricing: null, days: [] });
|
|
1315
1376
|
var data = state[0], setData = state[1];
|
|
1316
1377
|
var tabState = React.useState("overview");
|
|
@@ -1429,7 +1490,6 @@ var api = function (payload) {
|
|
|
1429
1490
|
),
|
|
1430
1491
|
el("div", { style: st.actions },
|
|
1431
1492
|
el("button", { style: st.btn, onClick: refresh }, t("刷新")),
|
|
1432
|
-
el("button", { style: st.btn, title: "Language / 语言(外部标签页名在刷新页面后更新)", onClick: function () { setLang(lang === "en" ? "zh" : "en"); } }, lang === "en" ? "中文" : "EN"),
|
|
1433
1493
|
el("button", { style: st.btn, onClick: doClear }, t("清空"))
|
|
1434
1494
|
)
|
|
1435
1495
|
),
|
|
@@ -1449,7 +1509,7 @@ var api = function (payload) {
|
|
|
1449
1509
|
loading
|
|
1450
1510
|
? el(LoadingView, null)
|
|
1451
1511
|
: el("div", null,
|
|
1452
|
-
tab === "overview" ? el(OverviewView, { records: records, regime: "auto" }) : null,
|
|
1512
|
+
tab === "overview" ? el(OverviewView, { records: records, regime: "auto", toolCalls: data.toolCalls || [] }) : null,
|
|
1453
1513
|
tab === "calendar" ? el(CalendarView, { records: records, regime: "auto", days: data.days || [] }) : null,
|
|
1454
1514
|
tab === "cache" ? el(CacheListView, { records: records, regime: "auto" }) : null,
|
|
1455
1515
|
tab === "prices" ? el(PriceView, { pricing: data.pricing, effectiveAt: data.effectiveAt, fx: data.fx || {}, onFxRefresh: function () { api({ action: "fxRefresh" }).then(refresh).catch(function () {}); }, onChanged: refresh }) : null
|
|
@@ -1480,7 +1540,7 @@ var api = function (payload) {
|
|
|
1480
1540
|
|
|
1481
1541
|
// ── Balance panel ──
|
|
1482
1542
|
function BalancePanel() {
|
|
1483
|
-
var lang = useLang();
|
|
1543
|
+
var lang = useLang(); // 订阅语言变化:宿主切换语言时触发本面板重渲染(值本身仅作触发用)
|
|
1484
1544
|
var state = React.useState({ status: "idle", data: null, error: "" });
|
|
1485
1545
|
var s = state[0], setS = state[1];
|
|
1486
1546
|
var providerState = React.useState("deepseek");
|
|
@@ -1750,6 +1810,225 @@ var api = function (payload) {
|
|
|
1750
1810
|
);
|
|
1751
1811
|
}
|
|
1752
1812
|
|
|
1813
|
+
// ── 消息底部 token 入口(conversation.chat.assistant-actions)──
|
|
1814
|
+
// 时长格式化为「Xm Ys」/「X分Y秒」
|
|
1815
|
+
var fmtDur = function (ms) {
|
|
1816
|
+
if (!ms || ms <= 0) return "0s";
|
|
1817
|
+
var secs = Math.round(ms / 1000);
|
|
1818
|
+
var m = Math.floor(secs / 60), r = secs % 60;
|
|
1819
|
+
if (m > 0) return __LANG === "en" ? (m + "m" + r + "s") : (m + "分" + r + "秒");
|
|
1820
|
+
return __LANG === "en" ? (r + "s") : (r + "秒");
|
|
1821
|
+
};
|
|
1822
|
+
|
|
1823
|
+
var __tok = {
|
|
1824
|
+
overlay: { position: "fixed", inset: 0, zIndex: 2147483000, background: "rgba(0,0,0,.4)", display: "flex", alignItems: "center", justifyContent: "center", padding: 16 },
|
|
1825
|
+
card: { background: "#ffffff", color: "#1c2733", borderRadius: 12, maxWidth: "min(680px, 94vw)", width: "100%", maxHeight: "calc(100vh - 40px)", overflowY: "auto", scrollbarWidth: "none", msOverflowStyle: "none", boxShadow: "0 14px 44px rgba(0,0,0,.4)", padding: "14px 16px", fontSize: fs(12.5) },
|
|
1826
|
+
title: { display: "flex", justifyContent: "space-between", alignItems: "center", fontSize: fs(14), fontWeight: 600, marginBottom: 10 },
|
|
1827
|
+
hint: { fontSize: fs(11.5), opacity: 0.6, marginBottom: 10 },
|
|
1828
|
+
row: { display: "flex", alignItems: "center", gap: 10, padding: "6px 0", borderBottom: "1px solid #eef1f5", fontSize: fs(12) },
|
|
1829
|
+
cellModel: { flex: "0 0 32%", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" },
|
|
1830
|
+
cellTime: { flex: "0 0 14%", opacity: 0.7, fontSize: fs(11) },
|
|
1831
|
+
cellT: { flex: "0 0 11%", textAlign: "right", whiteSpace: "nowrap" },
|
|
1832
|
+
cellPeriod: { flex: "0 0 6%", textAlign: "center", fontWeight: 600 },
|
|
1833
|
+
cellCost: { flex: "0 0 16%", textAlign: "right", whiteSpace: "nowrap", fontWeight: 600 },
|
|
1834
|
+
title2: { fontSize: fs(13), fontWeight: 600, margin: "12px 0 6px" },
|
|
1835
|
+
statRow: { display: "flex", flexWrap: "wrap", gap: 10, marginBottom: 12 },
|
|
1836
|
+
stat: { flex: "1 1 0", minWidth: 96, background: "#f5f7fa", borderRadius: 8, padding: "8px 10px" },
|
|
1837
|
+
statLabel: { fontSize: fs(11), opacity: 0.6, marginBottom: 2 },
|
|
1838
|
+
statValue: { fontSize: fs(15), fontWeight: 700 },
|
|
1839
|
+
barWrap: { display: "flex", alignItems: "center", gap: 10, marginBottom: 12 },
|
|
1840
|
+
barTrack: { flex: 1, height: 12, borderRadius: 999, background: "#eef1f5", overflow: "hidden" },
|
|
1841
|
+
barFill: { height: "100%", borderRadius: 999, background: "#22a45d", transition: "width .3s ease" },
|
|
1842
|
+
barLabel: { fontSize: fs(11.5), whiteSpace: "nowrap", opacity: 0.85 },
|
|
1843
|
+
mCard: { background: "#f7f8fa", borderRadius: 8, padding: "8px 10px", marginBottom: 8 },
|
|
1844
|
+
mHead: { display: "flex", justifyContent: "space-between", alignItems: "center", gap: 8, marginBottom: 4 },
|
|
1845
|
+
mName: { fontWeight: 600, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", fontSize: fs(12), flex: "1 1 0", minWidth: 0 },
|
|
1846
|
+
mCost: { color: "#1c2733", fontWeight: 700, whiteSpace: "nowrap" },
|
|
1847
|
+
mLine: { fontSize: fs(11.5), opacity: 0.85, overflowWrap: "anywhere" },
|
|
1848
|
+
close: Object.assign({}, st.btn, { marginLeft: "auto" })
|
|
1849
|
+
};
|
|
1850
|
+
|
|
1851
|
+
function MessageTokenAction(props) {
|
|
1852
|
+
var messageId = props.messageId;
|
|
1853
|
+
var sessionId = props.sessionId;
|
|
1854
|
+
var openState = React.useState(false);
|
|
1855
|
+
var open = openState[0], setOpen = openState[1];
|
|
1856
|
+
var dataState = React.useState({ status: "idle", data: null });
|
|
1857
|
+
var s1 = dataState[0], setData = dataState[1];
|
|
1858
|
+
// 从会话快照读取该消息的完成时间与所在步骤开始时间,用作“本轮”时间窗
|
|
1859
|
+
var msgTime = 0, stepStart = 0;
|
|
1860
|
+
if (props.useSession && typeof props.useSession === "function") {
|
|
1861
|
+
try {
|
|
1862
|
+
var pick = function (f) {
|
|
1863
|
+
return props.useSession(function (snap) {
|
|
1864
|
+
if (!snap || !snap.chat || !snap.chat.nodes) return 0;
|
|
1865
|
+
var arr = typeof snap.chat.nodes.values === "function" ? snap.chat.nodes.values() : null;
|
|
1866
|
+
if (!arr) return 0;
|
|
1867
|
+
for (var ni = 0; ni < arr.length; ni++) {
|
|
1868
|
+
var vh = arr[ni];
|
|
1869
|
+
if (!vh || !vh.data) continue;
|
|
1870
|
+
var fin = vh.data.finalNode || (vh.data.closing && vh.data.closing.finalNode) || null;
|
|
1871
|
+
if (fin && fin.messageId === messageId) return f(fin);
|
|
1872
|
+
}
|
|
1873
|
+
return 0;
|
|
1874
|
+
}) || 0;
|
|
1875
|
+
};
|
|
1876
|
+
msgTime = pick(function (fin) { return fin.time || 0; });
|
|
1877
|
+
stepStart = pick(function (fin) { return (fin.timing && fin.timing.stepStartTime) || 0; });
|
|
1878
|
+
} catch (e) {}
|
|
1879
|
+
}
|
|
1880
|
+
|
|
1881
|
+
React.useEffect(function () {
|
|
1882
|
+
var from = stepStart || (msgTime ? (msgTime - 5 * 60 * 1000) : 0);
|
|
1883
|
+
var to = msgTime || 0;
|
|
1884
|
+
if (!sessionId) { setData({ status: "idle", data: null }); return; }
|
|
1885
|
+
setData({ status: "loading", data: s1.data });
|
|
1886
|
+
api({ action: "tokenForMessage", sessionId: sessionId, from: from, to: to }).then(function (res) {
|
|
1887
|
+
setData({ status: res && res.ok ? "done" : "error", data: res });
|
|
1888
|
+
}).catch(function () { setData({ status: "error", data: null }); });
|
|
1889
|
+
}, [messageId, sessionId, msgTime, stepStart]);
|
|
1890
|
+
|
|
1891
|
+
var convo = s1.status === "done" && s1.data && s1.data.conversation ? s1.data.conversation : null;
|
|
1892
|
+
var agg2 = s1.status === "done" && s1.data && s1.data.aggregate ? s1.data.aggregate : null;
|
|
1893
|
+
var recs = s1.status === "done" && s1.data && s1.data.records ? s1.data.records : [];
|
|
1894
|
+
var convoTokens = convo ? (convo.input + convo.cacheRead + convo.cacheWrite + convo.output + convo.reasoning) : 0;
|
|
1895
|
+
var turnTokens = agg2 ? (agg2.input + agg2.cacheRead + agg2.cacheWrite + agg2.output + agg2.reasoning) : 0;
|
|
1896
|
+
var label = t("获取中…");
|
|
1897
|
+
if (s1.status === "error") label = "—";
|
|
1898
|
+
if (s1.status === "done") {
|
|
1899
|
+
if (agg2) label = t("本轮 token") + " " + fmtInt(turnTokens);
|
|
1900
|
+
else if (convo) label = t("本轮 token") + " " + fmtInt(convoTokens);
|
|
1901
|
+
else label = "—";
|
|
1902
|
+
}
|
|
1903
|
+
|
|
1904
|
+
// 按本轮记录聚合模型
|
|
1905
|
+
var byModel = {};
|
|
1906
|
+
for (var bi = 0; bi < recs.length; bi++) {
|
|
1907
|
+
var br = recs[bi];
|
|
1908
|
+
var mk = br.model || br.modelKey || "unknown";
|
|
1909
|
+
var m = byModel[mk];
|
|
1910
|
+
if (!m) { m = byModel[mk] = { model: mk, calls: 0, input: 0, cacheRead: 0, cacheWrite: 0, output: 0, reasoning: 0, peakCost: 0, offCost: 0, totalCost: 0 }; }
|
|
1911
|
+
m.calls++;
|
|
1912
|
+
m.input += br.inputTokens; m.cacheRead += br.cacheReadTokens; m.cacheWrite += br.cacheWriteTokens;
|
|
1913
|
+
m.output += br.outputTokens; m.reasoning += br.reasoningTokens;
|
|
1914
|
+
if (br.peak) m.peakCost += br.autoCost; else m.offCost += br.autoCost;
|
|
1915
|
+
m.totalCost += br.autoCost;
|
|
1916
|
+
}
|
|
1917
|
+
var modelRows = [];
|
|
1918
|
+
for (var mk2 in byModel) modelRows.push(byModel[mk2]);
|
|
1919
|
+
|
|
1920
|
+
function stat(label2, value2) {
|
|
1921
|
+
return el("div", { style: __tok.stat },
|
|
1922
|
+
el("div", { style: __tok.statLabel }, label2),
|
|
1923
|
+
el("div", { style: __tok.statValue }, value2)
|
|
1924
|
+
);
|
|
1925
|
+
}
|
|
1926
|
+
|
|
1927
|
+
function cacheBar() {
|
|
1928
|
+
if (!agg2) return null;
|
|
1929
|
+
var pct = Math.min(100, Math.max(0, agg2.hitRate));
|
|
1930
|
+
return el("div", { style: __tok.barWrap },
|
|
1931
|
+
el("div", { style: __tok.barTrack },
|
|
1932
|
+
el("div", { style: Object.assign({}, __tok.barFill, { width: pct + "%" }) })
|
|
1933
|
+
),
|
|
1934
|
+
el("span", { style: __tok.barLabel }, t("缓存命中率") + " " + (pct ? pct.toFixed(1) : "0") + "%")
|
|
1935
|
+
);
|
|
1936
|
+
}
|
|
1937
|
+
|
|
1938
|
+
function models() {
|
|
1939
|
+
if (!recs.length) return null;
|
|
1940
|
+
modelRows.sort(function (x, y) { return (y.totalCost - x.totalCost) || (y.calls - x.calls); });
|
|
1941
|
+
var MAX_MODELS = 6;
|
|
1942
|
+
var shown = modelRows.slice(0, MAX_MODELS);
|
|
1943
|
+
var extra = modelRows.length - shown.length;
|
|
1944
|
+
var cards = shown.map(function (m, idx) {
|
|
1945
|
+
return el("div", { key: String(idx), style: __tok.mCard },
|
|
1946
|
+
el("div", { style: __tok.mHead },
|
|
1947
|
+
el("span", { style: __tok.mName, title: m.model }, m.model),
|
|
1948
|
+
el("span", { style: __tok.mCost }, fmtMoney(m.totalCost))
|
|
1949
|
+
),
|
|
1950
|
+
el("div", { style: __tok.mLine },
|
|
1951
|
+
t("输入·未命中") + " " + fmtInt(m.input) + " · " + t("缓存命中") + " " + fmtInt(m.cacheRead) + " · " + t("输出") + " " + fmtInt(m.output) + (m.reasoning ? " · " + t("推理") + " " + fmtInt(m.reasoning) : "")
|
|
1952
|
+
),
|
|
1953
|
+
(m.peakCost > 0 || m.offCost > 0) ? el("div", { style: __tok.mLine },
|
|
1954
|
+
el("span", { style: { color: "#e08700" } }, t("峰") + " " + fmtMoney(m.peakCost)),
|
|
1955
|
+
" · ",
|
|
1956
|
+
el("span", { style: { color: "#3d6bd6" } }, t("谷") + " " + fmtMoney(m.offCost))
|
|
1957
|
+
) : null
|
|
1958
|
+
);
|
|
1959
|
+
});
|
|
1960
|
+
if (extra > 0) cards.push(el("div", { key: "more-models", style: __tok.hint }, t("…及另外 ") + fmtInt(extra) + t(" 个模型(完整列表见「用量与消耗」页签)")));
|
|
1961
|
+
return el("div", null, cards);
|
|
1962
|
+
}
|
|
1963
|
+
function turnStats() {
|
|
1964
|
+
if (!agg2) return null;
|
|
1965
|
+
// 记录只存开始时间,单条调用时 min/max 相同会算成 0;本轮耗时直接用
|
|
1966
|
+
// 该消息所在 step 的起止(完成时间 − step 开始时间),才是真实耗时。
|
|
1967
|
+
var span = (msgTime && stepStart) ? (msgTime - stepStart) : ((agg2.time && agg2.time.end) ? Math.max(0, agg2.time.end - agg2.time.start) : 0);
|
|
1968
|
+
return el("div", { style: __tok.statRow },
|
|
1969
|
+
stat(t("本次输出"), fmtInt(agg2.output)),
|
|
1970
|
+
stat(t("本轮 token"), fmtInt(turnTokens)),
|
|
1971
|
+
stat(t("本轮消耗"), fmtMoney(agg2.totalCost)),
|
|
1972
|
+
stat(t("本轮耗时"), fmtDur(span)),
|
|
1973
|
+
stat(t("本轮缓存命中率"), (agg2.hitRate ? agg2.hitRate.toFixed(1) : "0") + "%")
|
|
1974
|
+
);
|
|
1975
|
+
}
|
|
1976
|
+
|
|
1977
|
+
function summary() {
|
|
1978
|
+
if (!convo) return null;
|
|
1979
|
+
var span = (convo.time && convo.time.end) ? Math.max(0, convo.time.end - convo.time.start) : 0;
|
|
1980
|
+
return el("div", { style: __tok.statRow },
|
|
1981
|
+
stat(t("总 token"), fmtInt(convoTokens)),
|
|
1982
|
+
stat(t("总消耗"), fmtMoney(convo.totalCost)),
|
|
1983
|
+
stat(t("耗时"), fmtDur(span)),
|
|
1984
|
+
stat(t("缓存命中率"), (convo.hitRate ? convo.hitRate.toFixed(1) : "0") + "%")
|
|
1985
|
+
);
|
|
1986
|
+
}
|
|
1987
|
+
|
|
1988
|
+
function body() {
|
|
1989
|
+
if (s1.status === "loading") return el("div", { style: __tok.hint }, t("获取中…"));
|
|
1990
|
+
if (s1.status === "error") return el("div", { style: __tok.hint }, t("查询失败"));
|
|
1991
|
+
if (!convo && !agg2) return el("div", { style: __tok.hint }, t("无 Token 数据"));
|
|
1992
|
+
var zero = convo ? (convoTokens === 0 && convo.totalCost === 0) : (turnTokens === 0 && agg2.totalCost === 0);
|
|
1993
|
+
if (zero) return el("div", { style: __tok.hint }, t("本期无消耗(所有用量为 0)"));
|
|
1994
|
+
return el("div", null,
|
|
1995
|
+
convo ? el("div", null,
|
|
1996
|
+
el("div", { style: __tok.title2 }, t("对话累计")),
|
|
1997
|
+
summary()
|
|
1998
|
+
) : null,
|
|
1999
|
+
el("div", { style: __tok.title2 }, t("本轮明细")),
|
|
2000
|
+
turnStats(),
|
|
2001
|
+
cacheBar(),
|
|
2002
|
+
recs.length ? el("div", null,
|
|
2003
|
+
el("div", { style: __tok.title2 }, t("按模型")),
|
|
2004
|
+
models()
|
|
2005
|
+
) : el("div", { style: __tok.hint }, t("本轮无记录")),
|
|
2006
|
+
el("div", { style: __tok.hint }, t("详细记录请在「用量与消耗」页签查看"))
|
|
2007
|
+
);
|
|
2008
|
+
}
|
|
2009
|
+
|
|
2010
|
+
var overlayEl = open ? el("div", { key: "usage-tok-overlay", style: __tok.overlay, onClick: function () { setOpen(false); } },
|
|
2011
|
+
el("div", { style: __tok.card, "data-dsh-usage-tok": "1", onClick: function (e) { e.stopPropagation(); } },
|
|
2012
|
+
el("div", { style: __tok.title },
|
|
2013
|
+
el("span", null, t("Token 明细")),
|
|
2014
|
+
el("button", { style: __tok.close, onClick: function () { setOpen(false); } }, t("关闭"))
|
|
2015
|
+
),
|
|
2016
|
+
body()
|
|
2017
|
+
)
|
|
2018
|
+
) : null;
|
|
2019
|
+
if (overlayEl && ReactDOM && typeof ReactDOM.createPortal === "function" && typeof document !== "undefined" && document.body) {
|
|
2020
|
+
overlayEl = ReactDOM.createPortal(overlayEl, document.body);
|
|
2021
|
+
}
|
|
2022
|
+
return el("div", { style: { display: "inline-flex", alignItems: "center", gap: 4 } },
|
|
2023
|
+
el("button", { type: "button", key: "usage-tok",
|
|
2024
|
+
style: Object.assign({}, st.btn, { padding: "0 6px", minWidth: 0, fontSize: fs(11) }),
|
|
2025
|
+
title: t("Token 明细"),
|
|
2026
|
+
onClick: function () { setOpen(true); }
|
|
2027
|
+
}, label),
|
|
2028
|
+
overlayEl
|
|
2029
|
+
);
|
|
2030
|
+
}
|
|
2031
|
+
|
|
1753
2032
|
// ── plugin ──
|
|
1754
2033
|
var inject = ["slots"];
|
|
1755
2034
|
|
|
@@ -1757,37 +2036,48 @@ var api = function (payload) {
|
|
|
1757
2036
|
var slots = ctx.get("slots");
|
|
1758
2037
|
if (slots === undefined) return;
|
|
1759
2038
|
var timer = ctx.get("timer");
|
|
2039
|
+
// 语言跟随宿主设置(通用设置 → 语言):读取当前语言并订阅变化,切换后即时生效
|
|
2040
|
+
var locale = ctx.get("locale");
|
|
2041
|
+
if (locale) syncWithSystemLocale(locale);
|
|
2042
|
+
if (ctx.on) ctx.on("locale/change", function (snap) { setLang(snap && snap.active === "zh" ? "zh" : "en"); });
|
|
1760
2043
|
|
|
1761
2044
|
slots.inject("conversation.view", function () {
|
|
1762
2045
|
return slots.register(
|
|
1763
|
-
{ name: "conversation.view", id: "usage-cost-view", order: 20, label: t("用量与消耗") },
|
|
2046
|
+
{ name: "conversation.view", id: "usage-cost-view", order: 20, label: function () { return t("用量与消耗"); } },
|
|
1764
2047
|
function () { return el("div", { style: st.tab }, el(UsagePanel, { timer: timer })); }
|
|
1765
2048
|
);
|
|
1766
2049
|
});
|
|
1767
2050
|
|
|
1768
2051
|
slots.inject("conversation.view", function () {
|
|
1769
2052
|
return slots.register(
|
|
1770
|
-
{ name: "conversation.view", id: "balance-view", order: 30, label: t("剩余余额查询") },
|
|
2053
|
+
{ name: "conversation.view", id: "balance-view", order: 30, label: function () { return t("剩余余额查询"); } },
|
|
1771
2054
|
function () { return el("div", { style: st.tab }, el(BalancePanel, null)); }
|
|
1772
2055
|
);
|
|
1773
2056
|
});
|
|
1774
2057
|
|
|
1775
2058
|
slots.inject("settings.section", function () {
|
|
1776
2059
|
return slots.register(
|
|
1777
|
-
{ name: "settings.section", id: "usage-cost", order: 30, label: t("用量与消耗") },
|
|
2060
|
+
{ name: "settings.section", id: "usage-cost", order: 30, label: function () { return t("用量与消耗"); } },
|
|
1778
2061
|
function () { return el(UsagePanel, { timer: timer }); }
|
|
1779
2062
|
);
|
|
1780
2063
|
});
|
|
1781
2064
|
|
|
1782
2065
|
slots.inject("settings.section", function () {
|
|
1783
2066
|
return slots.register(
|
|
1784
|
-
{ name: "settings.section", id: "balance", order: 31, label: t("剩余余额查询") },
|
|
2067
|
+
{ name: "settings.section", id: "balance", order: 31, label: function () { return t("剩余余额查询"); } },
|
|
1785
2068
|
function () { return el(BalancePanel, null); }
|
|
1786
2069
|
);
|
|
1787
2070
|
});
|
|
2071
|
+
|
|
2072
|
+
slots.inject("conversation.chat.assistant-actions", function () {
|
|
2073
|
+
return slots.register(
|
|
2074
|
+
{ name: "conversation.chat.assistant-actions", id: "usage-token", order: 8 },
|
|
2075
|
+
MessageTokenAction
|
|
2076
|
+
);
|
|
2077
|
+
});
|
|
1788
2078
|
}
|
|
1789
2079
|
|
|
1790
|
-
// test/debug surface: instant-switch guarantees
|
|
2080
|
+
// test/debug surface: instant-switch guarantees, locale formatters, peak/off-peak judgment
|
|
1791
2081
|
exports.__i18n = {
|
|
1792
2082
|
t: t,
|
|
1793
2083
|
tb: tb,
|
|
@@ -1796,9 +2086,11 @@ var api = function (payload) {
|
|
|
1796
2086
|
dayLabel: dayLabel,
|
|
1797
2087
|
fmtMonthLabel: fmtMonthLabel,
|
|
1798
2088
|
dailyStatsTitle: dailyStatsTitle,
|
|
1799
|
-
weekdayLabels: weekdayLabels
|
|
2089
|
+
weekdayLabels: weekdayLabels,
|
|
2090
|
+
isPeakNow: isPeakNow,
|
|
2091
|
+
periodNow: periodNow
|
|
1800
2092
|
};
|
|
1801
|
-
// test/debug surface: instant-switch guarantees
|
|
2093
|
+
// test/debug surface: instant-switch guarantees, locale formatters, peak/off-peak judgment
|
|
1802
2094
|
exports.__i18n = {
|
|
1803
2095
|
t: t,
|
|
1804
2096
|
tb: tb,
|
|
@@ -1807,7 +2099,9 @@ var api = function (payload) {
|
|
|
1807
2099
|
dayLabel: dayLabel,
|
|
1808
2100
|
fmtMonthLabel: fmtMonthLabel,
|
|
1809
2101
|
dailyStatsTitle: dailyStatsTitle,
|
|
1810
|
-
weekdayLabels: weekdayLabels
|
|
2102
|
+
weekdayLabels: weekdayLabels,
|
|
2103
|
+
isPeakNow: isPeakNow,
|
|
2104
|
+
periodNow: periodNow
|
|
1811
2105
|
};
|
|
1812
2106
|
exports.inject = inject;
|
|
1813
2107
|
exports.apply = apply;
|
package/lib/index.js
CHANGED
|
@@ -26,6 +26,22 @@ import {
|
|
|
26
26
|
resolveBalanceEndpoint
|
|
27
27
|
} from './balance.js'
|
|
28
28
|
|
|
29
|
+
// 周末统一按空闲价计费规则生效时间:北京时间 2026-08-23 00:00(周日)。
|
|
30
|
+
// 生效前(含周末)仍按原峰谷分段计费;生效后周末(周六、周日)全天统一按空闲价,工作日仍按峰谷分段。
|
|
31
|
+
export const WEEKEND_FLAT_AT = Date.parse('2026-08-23T00:00:00+08:00')
|
|
32
|
+
|
|
33
|
+
// 高峰时段判定:新规则(2026-08-23 起)周末(周六、周日)全天不再区分峰谷,统一按空闲价;
|
|
34
|
+
// 工作日仍按北京时间 9:00–12:00、14:00–18:00 为高峰;新规则生效前的调用仍按原规则判定。
|
|
35
|
+
// 星期取自平移后的北京时间(ts + 8h),避免 UTC 日历与北京日历在 16:00–24:00 UTC 段错位。
|
|
36
|
+
// 导出为独立函数,供 test/period.test.js 做回归测试(issue #9)。
|
|
37
|
+
export function isPeakAt(ts) {
|
|
38
|
+
const d = new Date(ts + 8 * 3600 * 1000)
|
|
39
|
+
const wd = d.getUTCDay() // 0=周日 1=周一 … 6=周六
|
|
40
|
+
if (ts >= WEEKEND_FLAT_AT && (wd === 0 || wd === 6)) return false
|
|
41
|
+
const t = d.getUTCHours() * 60 + d.getUTCMinutes()
|
|
42
|
+
return (t >= 9 * 60 && t < 12 * 60) || (t >= 14 * 60 && t < 18 * 60)
|
|
43
|
+
}
|
|
44
|
+
|
|
29
45
|
export default {
|
|
30
46
|
inject: ['fs', 'webServer', 'subprocess', 'credentials', 'settings', 'sandboxPolicy', 'agents'],
|
|
31
47
|
apply(ctx) {
|
|
@@ -86,9 +102,9 @@ export default {
|
|
|
86
102
|
// 新价格表(峰谷价)生效时间:北京时间 2026-08-17 00:00。
|
|
87
103
|
// 在此之前的调用按旧价格表(基础价 base)计费;之后按新价格表(峰谷价)计费。
|
|
88
104
|
const EFFECTIVE_AT = Date.parse('2026-08-17T00:00:00+08:00')
|
|
89
|
-
|
|
90
|
-
//
|
|
91
|
-
const
|
|
105
|
+
|
|
106
|
+
// 高峰/空闲判定使用模块级 isPeakAt(含周末规则与生效时间保护;实现见文件顶部导出,供回归测试)
|
|
107
|
+
const isPeak = isPeakAt
|
|
92
108
|
|
|
93
109
|
function modelKey(model) {
|
|
94
110
|
const m = String(model || '').toLowerCase()
|
|
@@ -98,16 +114,6 @@ export default {
|
|
|
98
114
|
return 'unknown'
|
|
99
115
|
}
|
|
100
116
|
|
|
101
|
-
// 高峰时段判定:新规则(2026-08-23 起)周末(周六、周日)全天不再区分峰谷,统一按空闲价;
|
|
102
|
-
// 工作日仍按北京时间 9:00–12:00、14:00–18:00 为高峰;新规则生效前的调用仍按原规则判定。
|
|
103
|
-
function isPeak(ts) {
|
|
104
|
-
const d = new Date(ts + 8 * 3600 * 1000)
|
|
105
|
-
const wd = d.getUTCDay() // 0=周日 1=周一 … 6=周六
|
|
106
|
-
if (ts >= WEEKEND_FLAT_AT && (wd === 0 || wd === 6)) return false
|
|
107
|
-
const t = d.getUTCHours() * 60 + d.getUTCMinutes()
|
|
108
|
-
return (t >= 9 * 60 && t < 12 * 60) || (t >= 14 * 60 && t < 18 * 60)
|
|
109
|
-
}
|
|
110
|
-
|
|
111
117
|
// Third-party providers are priced only when a verified provider/model
|
|
112
118
|
// mapping exists. Unknown mappings deliberately remain zero rather than
|
|
113
119
|
// inheriting DeepSeek prices from a similar model name.
|
|
@@ -161,6 +167,51 @@ export default {
|
|
|
161
167
|
return (hit * p.cacheHit + miss * p.cacheMiss + out * p.output) / 1e6
|
|
162
168
|
}
|
|
163
169
|
|
|
170
|
+
// 过滤内部/占位调用(如 harness 内部 dsh2shell-* + model "fake"):
|
|
171
|
+
// 这些不是用户调用的 API 服务商,不出现在用量明细 / 消耗明细 / 日历统计里。
|
|
172
|
+
function isRealCall(rec) {
|
|
173
|
+
const model = String(rec.model || '').trim().toLowerCase()
|
|
174
|
+
const provider = String(rec.provider || '').trim().toLowerCase()
|
|
175
|
+
if (!model || model === 'fake' || model === 'unknown') return false
|
|
176
|
+
if (!provider || provider === 'unknown') return false
|
|
177
|
+
// 内部 agent/工具路由(dsh2shell-* 等)不计入用户可见明细
|
|
178
|
+
if (provider.indexOf('dsh') === 0) return false
|
|
179
|
+
return true
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// 把一条记录投影为 API 响应形状(供 list / tokenForMessage 共用,含成本与峰谷判定)。
|
|
183
|
+
function projectRecord(r) {
|
|
184
|
+
return {
|
|
185
|
+
time: r.time, model: r.model, provider: r.provider, purpose: r.purpose, sessionId: r.sessionId || '',
|
|
186
|
+
inputTokens: r.inputTokens, outputTokens: r.outputTokens,
|
|
187
|
+
cacheReadTokens: r.cacheReadTokens, cacheWriteTokens: r.cacheWriteTokens,
|
|
188
|
+
reasoningTokens: r.reasoningTokens, finishReason: r.finishReason,
|
|
189
|
+
usdCnyRate: r.usdCnyRate || 0, fxDate: r.fxDate || '',
|
|
190
|
+
modelKey: modelKey(r.model),
|
|
191
|
+
baseCost: costFor(r, 'base'), peakValleyCost: costFor(r, 'peakValley'), autoCost: costFor(r, 'auto'),
|
|
192
|
+
peak: isPeak(r.time)
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// 对已投影的记录做聚合(对话累计 / 本轮明细共用)。
|
|
197
|
+
function computeAgg(items) {
|
|
198
|
+
const agg = { calls: items.length, input: 0, output: 0, cacheRead: 0, cacheWrite: 0, reasoning: 0, peakCost: 0, offCost: 0, baseCost: 0, totalCost: 0, hitRate: 0 }
|
|
199
|
+
let firstTime = 0, lastTime = 0
|
|
200
|
+
for (const it of items) {
|
|
201
|
+
agg.input += it.inputTokens; agg.output += it.outputTokens
|
|
202
|
+
agg.cacheRead += it.cacheReadTokens; agg.cacheWrite += it.cacheWriteTokens
|
|
203
|
+
agg.reasoning += it.reasoningTokens
|
|
204
|
+
if (it.peak) agg.peakCost += it.peakValleyCost; else agg.offCost += it.peakValleyCost
|
|
205
|
+
agg.baseCost += it.baseCost; agg.totalCost += it.autoCost
|
|
206
|
+
if (!firstTime || it.time < firstTime) firstTime = it.time
|
|
207
|
+
if (it.time > lastTime) lastTime = it.time
|
|
208
|
+
}
|
|
209
|
+
const denom = agg.cacheRead + agg.input
|
|
210
|
+
agg.hitRate = denom > 0 ? (agg.cacheRead / denom * 100) : 0
|
|
211
|
+
agg.time = { start: firstTime, end: lastTime }
|
|
212
|
+
return agg
|
|
213
|
+
}
|
|
214
|
+
|
|
164
215
|
const msg = (e) => String((e && e.message) || e)
|
|
165
216
|
const fail = (message) => ({ ok: false, error: message })
|
|
166
217
|
const pad2 = (n) => (n < 10 ? '0' : '') + n
|
|
@@ -203,6 +254,7 @@ export default {
|
|
|
203
254
|
function buildDays() {
|
|
204
255
|
const map = {}
|
|
205
256
|
for (const r of records) {
|
|
257
|
+
if (!isRealCall(r)) continue;
|
|
206
258
|
const key = bjKey(r.time)
|
|
207
259
|
let d = map[key]
|
|
208
260
|
if (!d) {
|
|
@@ -341,6 +393,7 @@ export default {
|
|
|
341
393
|
model: String(raw.model || ''),
|
|
342
394
|
provider: String(raw.provider || ''),
|
|
343
395
|
purpose: String(raw.purpose || ''),
|
|
396
|
+
sessionId: String(raw.sessionId || ''),
|
|
344
397
|
inputTokens: toNum(raw.inputTokens),
|
|
345
398
|
outputTokens: toNum(raw.outputTokens),
|
|
346
399
|
cacheReadTokens: toNum(raw.cacheReadTokens),
|
|
@@ -489,6 +542,7 @@ export default {
|
|
|
489
542
|
const model = (options && options.model) || ''
|
|
490
543
|
const provider = (options && options.provider) || ''
|
|
491
544
|
const purpose = options && options.purpose ? String(options.purpose) : ''
|
|
545
|
+
let sessionId = options && options.sessionId ? String(options.sessionId) : ''
|
|
492
546
|
const startedAt = Date.now()
|
|
493
547
|
let usage = null
|
|
494
548
|
let finishReason = ''
|
|
@@ -506,6 +560,9 @@ export default {
|
|
|
506
560
|
}
|
|
507
561
|
} finally {
|
|
508
562
|
if (usage) {
|
|
563
|
+
if (!sessionId) {
|
|
564
|
+
try { const ag = currentAgent(); if (ag && ag.session && ag.session.id) sessionId = String(ag.session.id) } catch (e) {}
|
|
565
|
+
}
|
|
509
566
|
if (provider === 'digital-ocean' || provider === 'digitalocean') {
|
|
510
567
|
try { await refreshFxRate(false) } catch (e) {}
|
|
511
568
|
}
|
|
@@ -519,6 +576,7 @@ export default {
|
|
|
519
576
|
model,
|
|
520
577
|
provider,
|
|
521
578
|
purpose,
|
|
579
|
+
sessionId,
|
|
522
580
|
inputTokens: usage.inputTokens || 0,
|
|
523
581
|
outputTokens: usage.outputTokens || 0,
|
|
524
582
|
cacheReadTokens: usage.cacheReadTokens || 0,
|
|
@@ -1129,17 +1187,23 @@ export default {
|
|
|
1129
1187
|
switch (action) {
|
|
1130
1188
|
case 'list': {
|
|
1131
1189
|
try { await refreshFxRate(false) } catch (e) {}
|
|
1132
|
-
const items = records.map(
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1190
|
+
const items = records.filter(isRealCall).map(projectRecord)
|
|
1191
|
+
const toolItems = records.filter((r) => !isRealCall(r)).map(projectRecord)
|
|
1192
|
+
return { ok: true, records: items, count: items.length, toolCalls: toolItems, dataPath, persistOk, persistError, pricing: PRICING, effectiveAt: EFFECTIVE_AT, days: buildDays(), fx: FX }
|
|
1193
|
+
}
|
|
1194
|
+
case 'tokenForMessage': {
|
|
1195
|
+
// 消息底部弹窗:分两层统计——整场对话累计(conversation)+ 本轮明细(aggregate/records)。
|
|
1196
|
+
const sid = body && body.sessionId ? String(body.sessionId) : ''
|
|
1197
|
+
const from = Number(body && body.from) || 0
|
|
1198
|
+
const to = Number(body && body.to) || 0
|
|
1199
|
+
let sessionRecs = records.filter(isRealCall)
|
|
1200
|
+
if (sid) sessionRecs = sessionRecs.filter((r) => String(r.sessionId || '') === sid)
|
|
1201
|
+
let turnRecs = sessionRecs
|
|
1202
|
+
if (from > 0) turnRecs = turnRecs.filter((r) => r.time >= from)
|
|
1203
|
+
if (to > 0) turnRecs = turnRecs.filter((r) => r.time <= to)
|
|
1204
|
+
const convoAgg = computeAgg(sessionRecs.map(projectRecord))
|
|
1205
|
+
const turnAgg = computeAgg(turnRecs.map(projectRecord))
|
|
1206
|
+
return { ok: true, records: turnRecs.map(projectRecord), aggregate: turnAgg, conversation: convoAgg, count: turnRecs.length, from, to }
|
|
1143
1207
|
}
|
|
1144
1208
|
case 'clear': {
|
|
1145
1209
|
const n = records.length
|
package/package.json
CHANGED
|
@@ -1,88 +1,89 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@feiyang666/dsh-usage-plugin",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "DeepSeek Harness usage & cost tracker plugin: per-call token/cache-hit stats, peak/off-peak billing, DeepSeek balance query, CSV/JSON/PNG export with custom destination, and persistent local storage. Ships a host half plus a web client half in one npm package; installs into a DSH profile as a dsh.bundle with one command (dsh plugin --profile web add @feiyang666/dsh-usage-plugin).",
|
|
5
|
-
"keywords": [
|
|
6
|
-
"deepseek",
|
|
7
|
-
"harness",
|
|
8
|
-
"dsh",
|
|
9
|
-
"plugin",
|
|
10
|
-
"usage",
|
|
11
|
-
"cost",
|
|
12
|
-
"tokens",
|
|
13
|
-
"cache",
|
|
14
|
-
"balance"
|
|
15
|
-
],
|
|
16
|
-
"license": "MIT",
|
|
17
|
-
"type": "module",
|
|
18
|
-
"main": "lib/index.js",
|
|
19
|
-
"repository": {
|
|
20
|
-
"type": "git",
|
|
21
|
-
"url": "git+https://github.com/feiyang-dev/dsh-usage-plugin.git"
|
|
22
|
-
},
|
|
23
|
-
"contributors": [
|
|
24
|
-
{
|
|
25
|
-
"name": "ayleen",
|
|
26
|
-
"url": "https://github.com/ayleen"
|
|
27
|
-
},
|
|
28
|
-
{
|
|
29
|
-
"name": "wuhuqif176",
|
|
30
|
-
"url": "https://github.com/wuhuqif176"
|
|
31
|
-
},
|
|
32
|
-
{
|
|
33
|
-
"name": "Martin-soaring-dev",
|
|
34
|
-
"url": "https://github.com/Martin-soaring-dev"
|
|
35
|
-
},
|
|
36
|
-
{
|
|
37
|
-
"name": "liu3734",
|
|
38
|
-
"url": "https://github.com/liu3734"
|
|
39
|
-
},
|
|
40
|
-
{
|
|
41
|
-
"name": "mumuer1024",
|
|
42
|
-
"url": "https://github.com/mumuer1024"
|
|
43
|
-
}
|
|
44
|
-
],
|
|
45
|
-
"homepage": "https://github.com/feiyang-dev/dsh-usage-plugin#readme",
|
|
46
|
-
"bugs": {
|
|
47
|
-
"url": "https://github.com/feiyang-dev/dsh-usage-plugin/issues"
|
|
48
|
-
},
|
|
49
|
-
"exports": {
|
|
50
|
-
".": "./lib/index.js",
|
|
51
|
-
"./client": "./lib/client.js",
|
|
52
|
-
"./package.json": "./package.json"
|
|
53
|
-
},
|
|
54
|
-
"engines": {
|
|
55
|
-
"node": ">=18"
|
|
56
|
-
},
|
|
57
|
-
"dsh": {
|
|
58
|
-
"bundle": {
|
|
59
|
-
"patch": "./cordis.patch.yml"
|
|
60
|
-
},
|
|
61
|
-
"client": {
|
|
62
|
-
"platform": "web",
|
|
63
|
-
"inject": [
|
|
64
|
-
"@deepseek-ai/dsh-client-ui-conversation"
|
|
65
|
-
]
|
|
66
|
-
}
|
|
67
|
-
},
|
|
68
|
-
"files": [
|
|
69
|
-
"lib",
|
|
70
|
-
"scripts",
|
|
71
|
-
"cordis.patch.yml",
|
|
72
|
-
"README.md",
|
|
73
|
-
"README.zh.md",
|
|
74
|
-
"CHANGELOG.md"
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
"
|
|
79
|
-
"
|
|
80
|
-
"
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@feiyang666/dsh-usage-plugin",
|
|
3
|
+
"version": "1.14.0",
|
|
4
|
+
"description": "DeepSeek Harness usage & cost tracker plugin: per-call token/cache-hit stats, peak/off-peak billing, DeepSeek balance query, CSV/JSON/PNG export with custom destination, and persistent local storage. Ships a host half plus a web client half in one npm package; installs into a DSH profile as a dsh.bundle with one command (dsh plugin --profile web add @feiyang666/dsh-usage-plugin).",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"deepseek",
|
|
7
|
+
"harness",
|
|
8
|
+
"dsh",
|
|
9
|
+
"plugin",
|
|
10
|
+
"usage",
|
|
11
|
+
"cost",
|
|
12
|
+
"tokens",
|
|
13
|
+
"cache",
|
|
14
|
+
"balance"
|
|
15
|
+
],
|
|
16
|
+
"license": "MIT",
|
|
17
|
+
"type": "module",
|
|
18
|
+
"main": "lib/index.js",
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "git+https://github.com/feiyang-dev/dsh-usage-plugin.git"
|
|
22
|
+
},
|
|
23
|
+
"contributors": [
|
|
24
|
+
{
|
|
25
|
+
"name": "ayleen",
|
|
26
|
+
"url": "https://github.com/ayleen"
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"name": "wuhuqif176",
|
|
30
|
+
"url": "https://github.com/wuhuqif176"
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"name": "Martin-soaring-dev",
|
|
34
|
+
"url": "https://github.com/Martin-soaring-dev"
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"name": "liu3734",
|
|
38
|
+
"url": "https://github.com/liu3734"
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"name": "mumuer1024",
|
|
42
|
+
"url": "https://github.com/mumuer1024"
|
|
43
|
+
}
|
|
44
|
+
],
|
|
45
|
+
"homepage": "https://github.com/feiyang-dev/dsh-usage-plugin#readme",
|
|
46
|
+
"bugs": {
|
|
47
|
+
"url": "https://github.com/feiyang-dev/dsh-usage-plugin/issues"
|
|
48
|
+
},
|
|
49
|
+
"exports": {
|
|
50
|
+
".": "./lib/index.js",
|
|
51
|
+
"./client": "./lib/client.js",
|
|
52
|
+
"./package.json": "./package.json"
|
|
53
|
+
},
|
|
54
|
+
"engines": {
|
|
55
|
+
"node": ">=18"
|
|
56
|
+
},
|
|
57
|
+
"dsh": {
|
|
58
|
+
"bundle": {
|
|
59
|
+
"patch": "./cordis.patch.yml"
|
|
60
|
+
},
|
|
61
|
+
"client": {
|
|
62
|
+
"platform": "web",
|
|
63
|
+
"inject": [
|
|
64
|
+
"@deepseek-ai/dsh-client-ui-conversation"
|
|
65
|
+
]
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
"files": [
|
|
69
|
+
"lib",
|
|
70
|
+
"scripts",
|
|
71
|
+
"cordis.patch.yml",
|
|
72
|
+
"README.md",
|
|
73
|
+
"README.zh.md",
|
|
74
|
+
"CHANGELOG.md",
|
|
75
|
+
"RELEASE_NOTES.md"
|
|
76
|
+
],
|
|
77
|
+
"scripts": {
|
|
78
|
+
"wire": "node scripts/wire.js",
|
|
79
|
+
"check": "node scripts/check-package.js",
|
|
80
|
+
"prepublishOnly": "node scripts/check-package.js",
|
|
81
|
+
"pack": "npm pack"
|
|
82
|
+
},
|
|
83
|
+
"peerDependencies": {
|
|
84
|
+
"@deepseek-ai/cordis": "^4.0.1"
|
|
85
|
+
},
|
|
86
|
+
"dependencies": {
|
|
87
|
+
"undici": "^7.0.0"
|
|
88
|
+
}
|
|
89
|
+
}
|