dsh-dock 0.10.3 → 0.11.1

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 CHANGED
@@ -16,7 +16,7 @@
16
16
 
17
17
  | 功能 | 说明 |
18
18
  |---|---|
19
- | **用量记录** | 记录全部 LLM API 调用:秒级时间筛选、Token/费用统计(峰谷计价 + 官网价目自动同步)、分组汇总、明细检索与 CSV 导出 |
19
+ | **用量记录** | 记录全部 LLM API 调用:秒级时间筛选、Token/费用统计(**可配置各模型单价、持久保存并按自填单价计费**,支持多段分时价,内置峰谷计价 + 官网价目自动同步作兜底)、分组汇总、明细检索与 CSV 导出 |
20
20
  | **模型设置** | 编辑各 Provider 模型目录:输入类型(文本/图片 + 标注)与思考强度档位;图片能力可由你手动声明,新增 Provider 自动刷新加载,写回官方配置热生效 |
21
21
  | **模型余额** | 展示所有模型 Provider 账户余额(5 分钟自动刷新) |
22
22
  | **任务动画** | 19 种任务运行动画(含星际远征、星云潮汐、曲速航道、量子雷达、星座网络、深海脉动、神经电路等),速度随任务活动联动,配置全部持久化 |
@@ -109,6 +109,7 @@ dsh plugin --profile web remove dsh-dock # 卸载
109
109
  4. 「分组汇总」按 无 / 提供商 / 模型 / 状态 / 强度 维度切换
110
110
  5. 明细表 15 列可排序,100 行/页,点击「会话 ID」即筛选该会话;行右侧「详情」看单次调用信息
111
111
  6. 「CSV 导出」下载当前筛选结果
112
+ 7. **费用不准?点「单价设置」按模型填真实单价**:官网刊例价是抓来的,与实际计费(第三方中转、折扣、自建端点)常有出入。点开独立的「单价设置」子弹窗(可最大化、拖动、缩放),可按模型逐条配置单价(人民币元 / 百万 tokens,匹配模型名子串即可),并设置兜底单价、USD→CNY 汇率、是否继续自动同步官网价。每个模型还可配置**多段分时价**(如 DeepSeek 的高峰/优惠时段),命中哪段用哪段、时段外回落基准价,支持跨零点(如 `23~7`)与半点边界(如 `8.5` = 08:30)。保存后写入宿主配置**持久保存**,费用立即按自填单价重算(无需重扫历史);计价优先级:**自定义单价 > 官网同步价 > 内置价 > 兜底价**。明细表里走兜底价的金额会标「兜底」,方便你补配。
112
113
 
113
114
  **模型余额**
114
115
 
@@ -265,6 +266,7 @@ dsh plugin --profile web remove dsh-dock # 卸载
265
266
  4. 「分组汇总」按 无 / 提供商 / 模型 / 状态 / 强度 维度切换
266
267
  5. 明细表 15 列可排序,100 行/页,点击「会话 ID」即筛选该会话;行右侧「详情」看单次调用信息
267
268
  6. 「CSV 导出」下载当前筛选结果
269
+ 7. **费用不准?点「单价设置」按模型填真实单价**:官网刊例价是抓来的,与实际计费(第三方中转、折扣、自建端点)常有出入。点开独立的「单价设置」子弹窗(可最大化、拖动、缩放),可按模型逐条配置单价(人民币元 / 百万 tokens,匹配模型名子串即可),并设置兜底单价、USD→CNY 汇率、是否继续自动同步官网价。每个模型还可配置**多段分时价**(如 DeepSeek 的高峰/优惠时段),命中哪段用哪段、时段外回落基准价,支持跨零点(如 `23~7`)与半点边界(如 `8.5` = 08:30)。保存后写入宿主配置**持久保存**,费用立即按自填单价重算(无需重扫历史);计价优先级:**自定义单价 > 官网同步价 > 内置价 > 兜底价**。明细表里走兜底价的金额会标「兜底」,方便你补配。
268
270
 
269
271
  **模型余额**
270
272
 
@@ -346,6 +348,7 @@ dsh plugin --profile web remove dsh-dock # 卸载
346
348
  - **功能开关刷新后重置**:v0.5.0 起功能开关已持久化(浏览器本地存储),刷新/重启后保持。
347
349
  - **任务动画/任务通知/运行状态不生效**:三者的路由都在宿主进程内(动画/通知的配置持久化也在宿主),旧宿主需重启 `dsh web` 加载(面板页会提示「宿主进程是旧版本」)。另注意通知与运行状态已各自独立成菜单项,需在功能坞里分别开启「任务通知」「运行状态」。
348
350
  - **用量记录空白**:确认 dsh 是较新版本且插件宿主已加载(查看启动日志含 `[dsh-dock] host half loaded`);旧宿主进程需重启 dsh web 加载新路由。
351
+ - **费用不准 / 与实际账单对不上**:金额是按单价估算的,官网抓取来的刊例价不代表你的实际计费。在用量记录页点「单价设置」(独立子弹窗,可最大化/拖动/缩放),按模型填真实单价并保存(持久化到宿主配置),费用会立即按自填单价重算;每个模型还支持多段分时价(跨零点 / 半点边界)。计价优先级为「自定义单价 > 官网同步价 > 内置价 > 兜底价」,明细里走兜底价的金额会标「兜底」。
349
352
  - **余额显示「需登录 / 无密钥」**:对应 Provider 未在 dsh 凭据中配置可查询余额的密钥,属正常状态提示。
350
353
  - **切模型 / 打开历史会话卡住,报 `preset "…" failed to mount`**:这是该 **agent 预设**与 dsh 版本
351
354
  的 schema 漂移,不是本插件的问题。预设目录在 `~/.dsh/.agent-presets/<id>/`,dsh 升级后预设里的
package/client.js CHANGED
@@ -2297,7 +2297,7 @@ function subscribePanel(fn) {
2297
2297
  }
2298
2298
 
2299
2299
  // features/tokenlog/view.jsx
2300
- var import_react2 = require("react");
2300
+ var import_react2 = __toESM(require("react"), 1);
2301
2301
  var import_jsx_runtime = require("react/jsx-runtime");
2302
2302
  function rpcCall(method, args) {
2303
2303
  return fetch("/dsh-dock/tokenlog/" + method, {
@@ -2425,6 +2425,38 @@ var css = `
2425
2425
  .dtok-pager.top{margin:0;}
2426
2426
  .dtok-pager.bottom{margin:0;}
2427
2427
  .dtok-err{color:var(--dsw-alias-state-error-primary,#ff7a7a);font-size:12px;}
2428
+ /* ---- \u5355\u4EF7\u8BBE\u7F6E\uFF08\u5B50\u5F39\u7A97\uFF09 ---- */
2429
+ .dtok-pm-backdrop{position:fixed;inset:0;z-index:2100;background:rgba(15,17,21,.5);backdrop-filter:blur(3px);display:flex;align-items:center;justify-content:center;}
2430
+ .dtok-pm{box-sizing:border-box;width:min(980px,calc(100vw - 32px));height:min(760px,calc(100vh - 32px));display:flex;flex-direction:column;border-radius:14px;border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-2,#1c212b);color:var(--dsw-alias-label-primary);box-shadow:0 20px 64px rgb(0 0 0 / .4);overflow:hidden;}
2431
+ .dtok-pm.max{border-radius:10px;}
2432
+ .dtok-pm-head{display:flex;align-items:center;gap:10px;padding:10px 12px;border-bottom:1px solid var(--dsw-alias-border-l1);cursor:move;user-select:none;flex:none;background:var(--dsw-alias-bg-layer-1);}
2433
+ .dtok-pm.max .dtok-pm-head{cursor:default;}
2434
+ .dtok-pm-head b{font-size:14px;}
2435
+ .dtok-pm-sub{font-size:11px;color:var(--dsw-alias-label-secondary);flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
2436
+ .dtok-pm-ctrls{display:flex;gap:6px;flex:none;}
2437
+ .dtok-pm-body{flex:1;min-height:0;overflow-y:auto;padding:12px 14px;}
2438
+ .dtok-pm-resize{position:absolute;right:0;bottom:0;width:16px;height:16px;cursor:nwse-resize;touch-action:none;}
2439
+ .dtok-pm{position:relative;}
2440
+ /* ---- \u5355\u4EF7\u8BBE\u7F6E\uFF08\u8868\u5355\u5185\u5BB9\uFF09 ---- */
2441
+ .dtok-price{display:flex;flex-direction:column;gap:12px;}
2442
+ .dtok-price-grid{display:flex;gap:10px;flex-wrap:wrap;align-items:flex-end;}
2443
+ .dtok-price-field{display:flex;flex-direction:column;gap:3px;}
2444
+ .dtok-price-field label{font-size:11px;color:var(--dsw-alias-label-secondary);}
2445
+ .dtok-price-num{width:92px;}
2446
+ .dtok-price input[type="text"],.dtok-price-num{background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);border:1px solid var(--dsw-alias-border-l2);border-radius:6px;padding:4px 6px;font-size:12px;font-family:inherit;}
2447
+ .dtok-price .dtok-btn.tiny{padding:2px 8px;font-size:11px;}
2448
+ .dtok-price-msg{font-size:12px;}
2449
+ .dtok-price-msg.ok{color:var(--dsw-alias-state-success-primary,#4ade80);}
2450
+ .dtok-price-msg.err{color:var(--dsw-alias-state-error-primary,#ff7a7a);}
2451
+ .dtok-price-hint{font-size:11px;color:var(--dsw-alias-label-secondary);line-height:1.6;}
2452
+ .dtok-prow{border:1px solid var(--dsw-alias-border-l1);border-radius:10px;padding:8px 10px;margin-bottom:8px;background:var(--dsw-alias-bg-layer-1);}
2453
+ .dtok-prow-head{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin-bottom:6px;}
2454
+ .dtok-pseg{display:flex;align-items:center;gap:6px;flex-wrap:wrap;margin:4px 0;}
2455
+ .dtok-pseg-name{font-size:11px;color:var(--dsw-alias-label-secondary);min-width:112px;}
2456
+ .dtok-pseg-tilde{font-size:11px;color:var(--dsw-alias-label-secondary);}
2457
+ .dtok-src{font-size:11px;color:var(--dsw-alias-label-tertiary,#94a3b8);}
2458
+ .dtok-src.custom{color:var(--dsw-alias-state-success-primary,#4ade80);}
2459
+ .dtok-src.fallback{color:var(--dk-warn);}
2428
2460
  `;
2429
2461
  function Detail({ rec, onClose, rate }) {
2430
2462
  const st = statusInfo(rec);
@@ -2438,15 +2470,16 @@ function Detail({ rec, onClose, rate }) {
2438
2470
  ["\u9519\u8BEF\u4FE1\u606F", rec.errorMsg || "\u2014"],
2439
2471
  ["\u9519\u8BEF\u7801", rec.errorCode || "\u2014"],
2440
2472
  ["Request ID", rec.requestId || "\u2014"],
2441
- ["\u8F93\u5165 Token(\u672A\u7F13\u5B58)", fmtNum(rec.inputTokens)],
2473
+ ["\u8F93\u5165 Token(\u7F13\u5B58\u547D\u4E2D)", fmtNum(rec.cacheReadTokens)],
2474
+ ["\u8F93\u5165 Token(\u7F13\u5B58\u672A\u547D\u4E2D)", fmtNum(rec.inputTokens)],
2442
2475
  ["\u8F93\u51FA Token", fmtNum(rec.outputTokens)],
2443
- ["\u7F13\u5B58\u547D\u4E2D Token", fmtNum(rec.cacheReadTokens)],
2444
2476
  ["\u7F13\u5B58\u5199\u5165 Token", fmtNum(rec.cacheWriteTokens)],
2445
2477
  ["\u63A8\u7406 Token", fmtNum(rec.reasoningTokens)],
2446
2478
  ["\u8BA1\u8D39\u8F93\u5165", fmtNum(rec.billedInput)],
2447
2479
  ["\u7F13\u5B58\u547D\u4E2D\u7387", rec.cacheHitPercent + "%"],
2448
2480
  ["\u603B Token", fmtNum(rec.totalTokens)],
2449
2481
  ["\u6D88\u8017\u91D1\u989D(\u4F30\u7B97)", fmtCostCny(rec.cost, rateCny)],
2482
+ ["\u8BA1\u4EF7\u6765\u6E90", rec.pricingSource ? { custom: "\u81EA\u5B9A\u4E49\u5355\u4EF7", official: "\u5B98\u7F51\u540C\u6B65\u4EF7", builtin: "\u5185\u7F6E\u9ED8\u8BA4\u4EF7", fallback: "\u515C\u5E95\u5355\u4EF7" }[rec.pricingSource] || rec.pricingSource : "\u2014"],
2450
2483
  ["\u63A8\u7406\u5F3A\u5EA6", rec.effort || "\u2014"],
2451
2484
  ["\u8017\u65F6", fmtDuration(rec.llmMs)],
2452
2485
  ["Turn / Step", rec.turn + " / " + rec.step]
@@ -2460,6 +2493,334 @@ function Detail({ rec, onClose, rate }) {
2460
2493
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "dtok-btn", style: { marginTop: 12 }, onClick: onClose, children: "\u5173\u95ED" })
2461
2494
  ] }) });
2462
2495
  }
2496
+ function numStr(v) {
2497
+ return v === void 0 || v === null ? "" : String(v);
2498
+ }
2499
+ function segToDraft(s) {
2500
+ return {
2501
+ start: numStr(s && s.start),
2502
+ end: numStr(s && s.end),
2503
+ input: numStr(s && s.input),
2504
+ output: numStr(s && s.output),
2505
+ cacheRead: numStr(s && s.cacheRead),
2506
+ cacheWrite: numStr(s && s.cacheWrite)
2507
+ };
2508
+ }
2509
+ function rowToDraft(r) {
2510
+ return {
2511
+ match: r.match || "",
2512
+ input: numStr(r.input),
2513
+ output: numStr(r.output),
2514
+ cacheRead: numStr(r.cacheRead),
2515
+ cacheWrite: numStr(r.cacheWrite),
2516
+ // 旧数据可能是单段 peak(宿主已归一为 peaks,这里再兼容一次直连旧宿主的情况)
2517
+ peaks: (Array.isArray(r.peaks) && r.peaks.length ? r.peaks : r.peak && typeof r.peak === "object" ? [r.peak] : []).map(segToDraft)
2518
+ };
2519
+ }
2520
+ function newSegDraft() {
2521
+ return segToDraft({ start: 9, end: 14, input: "", output: "", cacheRead: "", cacheWrite: "" });
2522
+ }
2523
+ function PriceModal({ onClose, onSaved }) {
2524
+ const [maxed, setMaxed] = (0, import_react2.useState)(false);
2525
+ const [geom, setGeom] = (0, import_react2.useState)(null);
2526
+ const dlgRef = (0, import_react2.useRef)(null);
2527
+ const dragRef = (0, import_react2.useRef)(null);
2528
+ const startDrag = (0, import_react2.useCallback)((e, kind) => {
2529
+ if (e.button !== void 0 && e.button !== 0) return;
2530
+ if (kind === "move" && e.target && e.target.closest && e.target.closest("button,select,input,label,details,summary")) return;
2531
+ const node = dlgRef.current;
2532
+ if (!node) return;
2533
+ const rect2 = node.getBoundingClientRect();
2534
+ dragRef.current = { kind, startX: e.clientX, startY: e.clientY, left: rect2.left, top: rect2.top, w: rect2.width, h: rect2.height };
2535
+ if (e.preventDefault) e.preventDefault();
2536
+ }, []);
2537
+ (0, import_react2.useEffect)(() => {
2538
+ const onMove = (ev) => {
2539
+ const d = dragRef.current;
2540
+ if (!d) return;
2541
+ const dx = ev.clientX - d.startX, dy = ev.clientY - d.startY;
2542
+ const vw = window.innerWidth, vh = window.innerHeight;
2543
+ if (d.kind === "move") {
2544
+ const x = Math.min(Math.max(0, d.left + dx), Math.max(0, vw - 80));
2545
+ const y = Math.min(Math.max(0, d.top + dy), Math.max(0, vh - 40));
2546
+ setGeom({ x, y, w: d.w, h: d.h });
2547
+ } else {
2548
+ const w = Math.max(560, Math.min(d.w + dx, vw - 16));
2549
+ const h = Math.max(360, Math.min(d.h + dy, vh - 16));
2550
+ setGeom({ x: d.left, y: d.top, w, h });
2551
+ }
2552
+ };
2553
+ const onUp = () => {
2554
+ dragRef.current = null;
2555
+ };
2556
+ window.addEventListener("pointermove", onMove);
2557
+ window.addEventListener("pointerup", onUp);
2558
+ return () => {
2559
+ window.removeEventListener("pointermove", onMove);
2560
+ window.removeEventListener("pointerup", onUp);
2561
+ };
2562
+ }, []);
2563
+ (0, import_react2.useEffect)(() => {
2564
+ const onKey = (e) => {
2565
+ if (e.key === "Escape") onClose();
2566
+ };
2567
+ window.addEventListener("keydown", onKey);
2568
+ return () => window.removeEventListener("keydown", onKey);
2569
+ }, [onClose]);
2570
+ const style = maxed ? { position: "fixed", left: 8, top: 8, right: 8, bottom: 8, width: "auto", height: "auto" } : geom ? { position: "fixed", left: geom.x, top: geom.y, width: geom.w, height: geom.h } : null;
2571
+ const portalTarget = typeof document !== "undefined" && document.body ? document.body : null;
2572
+ const overlay = /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dtok-pm-backdrop", onClick: onClose, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { ref: dlgRef, className: "dtok-pm" + (maxed ? " max" : ""), style, onClick: (e) => e.stopPropagation(), children: [
2573
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dtok-pm-head", onPointerDown: (e) => startDrag(e, "move"), onDoubleClick: () => setMaxed((v) => !v), children: [
2574
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("b", { children: "\u5355\u4EF7\u8BBE\u7F6E" }),
2575
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dtok-pm-sub", children: "\u4EBA\u6C11\u5E01\u5143 / \u767E\u4E07 tokens \xB7 \u81EA\u5B9A\u4E49\u5355\u4EF7\u4F18\u5148\u4E8E\u5B98\u7F51\u4EF7\u4E0E\u5185\u7F6E\u4EF7" }),
2576
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "dtok-pm-ctrls", children: [
2577
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "dtok-btn tiny", title: maxed ? "\u8FD8\u539F" : "\u6700\u5927\u5316", onClick: () => setMaxed((v) => !v), children: maxed ? "\u2750" : "\u25A2" }),
2578
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "dtok-btn tiny", title: "\u5173\u95ED", onClick: onClose, children: "\u2715" })
2579
+ ] })
2580
+ ] }),
2581
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dtok-pm-body", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PricingEditor, { onClose, onSaved, embedded: true }) }),
2582
+ maxed ? null : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dtok-pm-resize", title: "\u62D6\u52A8\u7F29\u653E", onPointerDown: (e) => startDrag(e, "resize") })
2583
+ ] }) });
2584
+ return portalTarget && typeof import_react2.default.createPortal === "function" ? import_react2.default.createPortal(overlay, portalTarget) : overlay;
2585
+ }
2586
+ function PricingEditor({ onClose, onSaved, embedded }) {
2587
+ const [cfg, setCfg] = (0, import_react2.useState)(null);
2588
+ const [rows, setRows] = (0, import_react2.useState)([]);
2589
+ const [rate, setRate] = (0, import_react2.useState)("7.2");
2590
+ const [fetchOn, setFetchOn] = (0, import_react2.useState)(true);
2591
+ const [fb, setFb] = (0, import_react2.useState)({ input: "", output: "", cacheRead: "", cacheWrite: "" });
2592
+ const [newMatch, setNewMatch] = (0, import_react2.useState)("");
2593
+ const [msg, setMsg] = (0, import_react2.useState)(null);
2594
+ const [saving, setSaving] = (0, import_react2.useState)(false);
2595
+ (0, import_react2.useEffect)(() => {
2596
+ let cancel = false;
2597
+ rpcCall("pricing", {}).then((d) => {
2598
+ if (cancel) return;
2599
+ setCfg(d);
2600
+ setRate(numStr(d.usdCnyRate));
2601
+ setFetchOn(!!d.fetchOfficial);
2602
+ setFb({
2603
+ input: numStr(d.fallback && d.fallback.input),
2604
+ output: numStr(d.fallback && d.fallback.output),
2605
+ cacheRead: numStr(d.fallback && d.fallback.cacheRead),
2606
+ cacheWrite: numStr(d.fallback && d.fallback.cacheWrite)
2607
+ });
2608
+ setRows((d.pricing || []).map(rowToDraft));
2609
+ }).catch((e) => {
2610
+ if (!cancel) setMsg({ ok: false, text: String(e && e.message || e) });
2611
+ });
2612
+ return () => {
2613
+ cancel = true;
2614
+ };
2615
+ }, []);
2616
+ const patchRow = (i, patch) => setRows((rs) => rs.map((r, j) => j === i ? Object.assign({}, r, patch) : r));
2617
+ const delRow = (i) => setRows((rs) => rs.filter((_, j) => j !== i));
2618
+ const addSeg = (i) => setRows((rs) => rs.map((r, j) => j === i ? Object.assign({}, r, { peaks: r.peaks.concat([newSegDraft()]) }) : r));
2619
+ const delSeg = (i, k) => setRows((rs) => rs.map((r, j) => j === i ? Object.assign({}, r, { peaks: r.peaks.filter((_, x) => x !== k) }) : r));
2620
+ const patchSeg = (i, k, patch) => setRows((rs) => rs.map((r, j) => j === i ? Object.assign({}, r, { peaks: r.peaks.map((s, x) => x === k ? Object.assign({}, s, patch) : s) }) : r));
2621
+ const addRow = (m) => {
2622
+ const match = String(m === void 0 ? newMatch : m).trim();
2623
+ if (!match) return;
2624
+ if (rows.some((r) => r.match.toLowerCase() === match.toLowerCase())) {
2625
+ setMsg({ ok: false, text: "\u300C" + match + "\u300D\u5DF2\u5B58\u5728" });
2626
+ return;
2627
+ }
2628
+ setRows((rs) => rs.concat([rowToDraft({ match, input: 0, output: 0, cacheRead: 0, cacheWrite: 0 })]));
2629
+ setNewMatch("");
2630
+ setMsg(null);
2631
+ };
2632
+ const save = () => {
2633
+ if (saving) return;
2634
+ const payload = { usdCnyRate: Number(rate), fetchOfficial: fetchOn };
2635
+ payload.pricing = rows.map((r) => {
2636
+ const out = {
2637
+ match: String(r.match || "").trim(),
2638
+ input: Number(r.input || 0),
2639
+ output: Number(r.output || 0),
2640
+ cacheRead: Number(r.cacheRead || 0),
2641
+ cacheWrite: Number(r.cacheWrite || 0)
2642
+ };
2643
+ const peaks = (r.peaks || []).map((s) => ({
2644
+ start: Number(s.start || 0),
2645
+ end: Number(s.end || 0),
2646
+ input: Number(s.input || 0),
2647
+ output: Number(s.output || 0),
2648
+ cacheRead: Number(s.cacheRead || 0),
2649
+ cacheWrite: Number(s.cacheWrite || 0)
2650
+ }));
2651
+ if (peaks.length) out.peaks = peaks;
2652
+ return out;
2653
+ });
2654
+ payload.fallback = {
2655
+ input: Number(fb.input || 0),
2656
+ output: Number(fb.output || 0),
2657
+ cacheRead: Number(fb.cacheRead || 0),
2658
+ cacheWrite: Number(fb.cacheWrite || 0)
2659
+ };
2660
+ setSaving(true);
2661
+ setMsg(null);
2662
+ rpcCall("setpricing", payload).then((d) => {
2663
+ setCfg(d);
2664
+ setRows((d.pricing || []).map(rowToDraft));
2665
+ setMsg({ ok: true, text: "\u5355\u4EF7\u5DF2\u4FDD\u5B58\uFF0C\u8D39\u7528\u5DF2\u6309\u65B0\u4EF7\u5373\u65F6\u91CD\u7B97" });
2666
+ if (onSaved) onSaved();
2667
+ }).catch((e) => setMsg({ ok: false, text: String(e && e.message || e) })).finally(() => setSaving(false));
2668
+ };
2669
+ const configured = rows.map((r) => String(r.match).toLowerCase());
2670
+ const candidates2 = (cfg && cfg.models || []).filter((m) => configured.indexOf(String(m).toLowerCase()) < 0);
2671
+ const srcOf = (m) => cfg && cfg.sourcesByModel && cfg.sourcesByModel[m] || "";
2672
+ const onFallback = (cfg && cfg.models || []).filter((m) => srcOf(m) === "\u515C\u5E95");
2673
+ const num = (value, onChange, extra) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { className: "dtok-price-num", type: "number", min: "0", step: "0.01", value, onChange: (e) => onChange(e.target.value), style: extra });
2674
+ const segNum = (value, onChange, placeholder) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2675
+ "input",
2676
+ {
2677
+ className: "dtok-price-num",
2678
+ type: "number",
2679
+ min: "0",
2680
+ step: "0.01",
2681
+ value,
2682
+ placeholder,
2683
+ onChange: (e) => onChange(e.target.value),
2684
+ style: { width: 80 }
2685
+ }
2686
+ );
2687
+ const segLabel = (s) => {
2688
+ const a = numStr(s.start), b = numStr(s.end);
2689
+ if (a === "" || b === "") return "\u65F6\u6BB5";
2690
+ return a + "~" + b + (Number(a) === Number(b) ? "\uFF08\u5168\u5929\uFF09" : Number(a) > Number(b) ? "\uFF08\u8DE8\u96F6\u70B9\uFF09" : "");
2691
+ };
2692
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: embedded ? "dtok-price emb" : "dtok-price", onClick: (e) => e.stopPropagation(), children: [
2693
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dtok-price-grid", children: [
2694
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dtok-price-field", children: [
2695
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { children: "USD\u2192CNY \u6C47\u7387" }),
2696
+ num(rate, setRate)
2697
+ ] }),
2698
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { style: { display: "flex", alignItems: "center", gap: 6, fontSize: 12, color: "var(--dsw-alias-label-secondary)" }, children: [
2699
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { type: "checkbox", checked: fetchOn, onChange: (e) => setFetchOn(e.target.checked) }),
2700
+ "\u5B98\u7F51\u4EF7\u76EE\u81EA\u52A8\u540C\u6B65\uFF08\u5173\u95ED\u540E\u4EC5\u7528\u81EA\u5B9A\u4E49/\u5185\u7F6E\u4EF7\uFF09"
2701
+ ] })
2702
+ ] }),
2703
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
2704
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dtok-section-title", style: { margin: "0 0 4px" }, children: [
2705
+ "\u6309\u6A21\u578B\u914D\u7F6E\u5355\u4EF7\uFF08",
2706
+ rows.length,
2707
+ " \u6761\uFF09"
2708
+ ] }),
2709
+ onFallback.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dtok-price-hint", children: [
2710
+ "\u4EE5\u4E0B\u7528\u8FC7\u7684\u6A21\u578B\u5F53\u524D\u8D70\u300C\u515C\u5E95\u4EF7\u300D\uFF0C\u5EFA\u8BAE\u4F18\u5148\u8865\u5355\u4EF7\uFF1A",
2711
+ onFallback.join("\u3001")
2712
+ ] }) : null,
2713
+ rows.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dtok-price-hint", children: "\u5C1A\u672A\u914D\u7F6E\u81EA\u5B9A\u4E49\u5355\u4EF7\uFF0C\u5F53\u524D\u4F7F\u7528\u5B98\u7F51\u540C\u6B65\u4EF7 / \u5185\u7F6E\u4EF7 / \u515C\u5E95\u4EF7\u3002" }) : rows.map((r, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dtok-prow", children: [
2714
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dtok-prow-head", children: [
2715
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { className: "dtok-input", style: { maxWidth: 240 }, type: "text", value: r.match, placeholder: "\u6A21\u578B\u5339\u914D\uFF08\u5B50\u4E32\uFF09", onChange: (e) => patchRow(i, { match: e.target.value }) }),
2716
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "dtok-src " + (srcOf(r.match) === "\u81EA\u5B9A\u4E49" ? "custom" : srcOf(r.match) === "\u515C\u5E95" ? "fallback" : ""), children: [
2717
+ "\u5F53\u524D\u6765\u6E90\uFF1A",
2718
+ srcOf(r.match) || "\u2014"
2719
+ ] }),
2720
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "dtok-btn tiny", onClick: () => delRow(i), children: "\u5220\u9664\u6A21\u578B" })
2721
+ ] }),
2722
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dtok-price-grid", children: [
2723
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dtok-price-field", children: [
2724
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { children: "\u57FA\u51C6\u8F93\u5165\uFF08\u7F13\u5B58\u547D\u4E2D\uFF09" }),
2725
+ num(r.cacheRead, (v) => patchRow(i, { cacheRead: v }))
2726
+ ] }),
2727
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dtok-price-field", children: [
2728
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { children: "\u57FA\u51C6\u8F93\u5165\uFF08\u7F13\u5B58\u672A\u547D\u4E2D\uFF09" }),
2729
+ num(r.input, (v) => patchRow(i, { input: v }))
2730
+ ] }),
2731
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dtok-price-field", children: [
2732
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { children: "\u57FA\u51C6\u8F93\u51FA" }),
2733
+ num(r.output, (v) => patchRow(i, { output: v }))
2734
+ ] }),
2735
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dtok-price-field", children: [
2736
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { children: "\u57FA\u51C6\u7F13\u5B58\u5199\u5165" }),
2737
+ num(r.cacheWrite, (v) => patchRow(i, { cacheWrite: v }))
2738
+ ] })
2739
+ ] }),
2740
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dtok-price-hint", style: { marginTop: 2 }, children: "\u7F13\u5B58\u5199\u5165\uFF1D\u672A\u547D\u4E2D\u7F13\u5B58\u3001\u672C\u6B21\u65B0\u5EFA\u7F13\u5B58\u7684\u90A3\u90E8\u5206\u8F93\u5165\uFF08Claude \u7B49\u6309\u6EA2\u4EF7\u5355\u72EC\u6536\u5EFA\u7F13\u5B58\u8D39\uFF09\uFF1BDeepSeek \u4E0D\u6536\u6B64\u9879\uFF0C\u586B 0 \u5373\u53EF\u3002" }),
2741
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dtok-price-hint", style: { marginTop: 2 }, children: "\u5206\u65F6\u6BB5\u4EF7\uFF08\u53EF\u591A\u6BB5\uFF0C\u547D\u4E2D\u54EA\u6BB5\u7528\u54EA\u6BB5\uFF1B\u65F6\u6BB5\u5916\u56DE\u843D\u5230\u57FA\u51C6\u4EF7\u3002start>end \u8868\u793A\u8DE8\u96F6\u70B9\uFF0C\u5982 23~7\uFF1Bstart=end \u8868\u793A\u5168\u5929\uFF09" }),
2742
+ r.peaks.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dtok-price-hint", children: "\uFF08\u672A\u8BBE\u7F6E\u5206\u65F6\u6BB5\uFF0C\u6309\u57FA\u51C6\u4EF7\u8BA1\u8D39\uFF09" }) : null,
2743
+ r.peaks.map((s, k) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dtok-pseg", children: [
2744
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dtok-pseg-name", children: segLabel(s) }),
2745
+ num(s.start, (v) => patchSeg(i, k, { start: v }), { width: 56 }),
2746
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dtok-pseg-tilde", children: "~" }),
2747
+ num(s.end, (v) => patchSeg(i, k, { end: v }), { width: 56 }),
2748
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dtok-pseg-tilde", children: "\u65F6" }),
2749
+ segNum(s.cacheRead, (v) => patchSeg(i, k, { cacheRead: v }), "\u7F13\u5B58\u547D\u4E2D"),
2750
+ segNum(s.input, (v) => patchSeg(i, k, { input: v }), "\u672A\u547D\u4E2D"),
2751
+ segNum(s.output, (v) => patchSeg(i, k, { output: v }), "\u8F93\u51FA"),
2752
+ segNum(s.cacheWrite, (v) => patchSeg(i, k, { cacheWrite: v }), "\u5199\u5165"),
2753
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "dtok-btn tiny", onClick: () => delSeg(i, k), children: "\u5220\u9664\u65F6\u6BB5" })
2754
+ ] }, k)),
2755
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dtok-price-hint", style: { opacity: 0.85 }, children: "\u65F6\u6BB5\u4EF7\u4ECE\u5DE6\u5230\u53F3\uFF1A\u8F93\u5165\uFF08\u547D\u4E2D\uFF09/ \u8F93\u5165\uFF08\u672A\u547D\u4E2D\uFF09/ \u8F93\u51FA / \u7F13\u5B58\u5199\u5165" }),
2756
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "dtok-btn tiny", onClick: () => addSeg(i), children: "+ \u6DFB\u52A0\u65F6\u6BB5" })
2757
+ ] }, i)),
2758
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dtok-price-grid", style: { marginTop: 6 }, children: [
2759
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dtok-price-field", style: { flex: "0 0 220px" }, children: [
2760
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { children: "\u65B0\u589E\u6A21\u578B\u5339\u914D" }),
2761
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { className: "dtok-input", style: { maxWidth: 220 }, type: "text", list: "dtok-model-candidates", placeholder: "\u5982 deepseek-v4-flash \u6216\u67D0\u4E2D\u8F6C\u6A21\u578B\u540D", value: newMatch, onChange: (e) => setNewMatch(e.target.value) }),
2762
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("datalist", { id: "dtok-model-candidates", children: candidates2.map((m) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: m }, m)) })
2763
+ ] }),
2764
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "dtok-btn", onClick: () => addRow(), children: "+ \u6DFB\u52A0" })
2765
+ ] }),
2766
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dtok-price-hint", children: [
2767
+ "\u63D0\u793A\uFF1A\u5339\u914D\u662F\u300C\u6A21\u578B\u540D\u5305\u542B\u8BE5\u5B50\u4E32\u300D\uFF0C\u53EF\u53EA\u5199\u5173\u952E\u7247\u6BB5\uFF08\u5982 ",
2768
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", { children: "v4-flash" }),
2769
+ "\uFF09\uFF1B\u8D8A\u5177\u4F53\u7684\u6761\u76EE\u5EFA\u8BAE\u653E\u8D8A\u524D\uFF08\u5F53\u524D\u6309\u5217\u8868\u987A\u5E8F\u547D\u4E2D\uFF09\u3002"
2770
+ ] })
2771
+ ] }),
2772
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
2773
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dtok-section-title", style: { margin: "0 0 4px" }, children: "\u515C\u5E95\u5355\u4EF7\uFF08\u672A\u5339\u914D\u4EFB\u4F55\u6761\u76EE\u65F6\u4F7F\u7528\uFF09" }),
2774
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dtok-price-grid", children: [
2775
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dtok-price-field", children: [
2776
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { children: "\u8F93\u5165\uFF08\u7F13\u5B58\u547D\u4E2D\uFF09" }),
2777
+ num(fb.cacheRead, (v) => setFb((s) => Object.assign({}, s, { cacheRead: v })))
2778
+ ] }),
2779
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dtok-price-field", children: [
2780
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { children: "\u8F93\u5165\uFF08\u7F13\u5B58\u672A\u547D\u4E2D\uFF09" }),
2781
+ num(fb.input, (v) => setFb((s) => Object.assign({}, s, { input: v })))
2782
+ ] }),
2783
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dtok-price-field", children: [
2784
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { children: "\u8F93\u51FA" }),
2785
+ num(fb.output, (v) => setFb((s) => Object.assign({}, s, { output: v })))
2786
+ ] }),
2787
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dtok-price-field", children: [
2788
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { children: "\u7F13\u5B58\u5199\u5165" }),
2789
+ num(fb.cacheWrite, (v) => setFb((s) => Object.assign({}, s, { cacheWrite: v })))
2790
+ ] })
2791
+ ] })
2792
+ ] }),
2793
+ cfg && cfg.builtin && cfg.builtin.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("details", { children: [
2794
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("summary", { className: "dtok-price-hint", style: { cursor: "pointer" }, children: [
2795
+ "\u67E5\u770B\u5185\u7F6E\u9ED8\u8BA4\u5355\u4EF7\uFF08",
2796
+ cfg.builtin.length,
2797
+ " \u6761\uFF0C\u81EA\u5B9A\u4E49\u914D\u7F6E\u672A\u547D\u4E2D\u65F6\u6309\u6B64\u515C\u5E95\uFF09"
2798
+ ] }),
2799
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dtok-table-wrap", style: { marginTop: 6 }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("table", { className: "dtok-table", children: [
2800
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("tr", { children: [
2801
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { children: "\u5339\u914D" }),
2802
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { children: "\u8F93\u5165(\u547D\u4E2D)" }),
2803
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { children: "\u8F93\u5165(\u672A\u547D\u4E2D)" }),
2804
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { children: "\u8F93\u51FA" }),
2805
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { children: "\u7F13\u5B58\u5199\u5165" }),
2806
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { children: "\u5206\u65F6\u6BB5" })
2807
+ ] }) }),
2808
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("tbody", { children: cfg.builtin.map((b) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("tr", { children: [
2809
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { children: b.match }),
2810
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { children: b.cacheRead }),
2811
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { children: b.input }),
2812
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { children: b.output }),
2813
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { children: b.cacheWrite }),
2814
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { children: b.peaks && b.peaks.length ? b.peaks.map((s) => s.start + "~" + s.end).join("\u3001") + "\u65F6" : "\u2014" })
2815
+ ] }, b.match)) })
2816
+ ] }) })
2817
+ ] }) : null,
2818
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dtok-price-grid", children: [
2819
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "dtok-btn primary", disabled: saving, onClick: save, children: saving ? "\u4FDD\u5B58\u4E2D\u2026" : "\u4FDD\u5B58\u5355\u4EF7" }),
2820
+ msg ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dtok-price-msg " + (msg.ok ? "ok" : "err"), children: msg.text }) : null
2821
+ ] })
2822
+ ] });
2823
+ }
2463
2824
  function TokenLogView(props) {
2464
2825
  const navSession = props && props.params && props.params.sessionId ? props.params.sessionId : null;
2465
2826
  const [savedFilters] = (0, import_react2.useState)(loadSavedFilters);
@@ -2478,6 +2839,7 @@ function TokenLogView(props) {
2478
2839
  const [err, setErr] = (0, import_react2.useState)("");
2479
2840
  const [page, setPage] = (0, import_react2.useState)(0);
2480
2841
  const [detailRec, setDetailRec] = (0, import_react2.useState)(null);
2842
+ const [showPricing, setShowPricing] = (0, import_react2.useState)(() => !!(props && props.params && props.params.openPricing));
2481
2843
  const pageSize = 100;
2482
2844
  (0, import_react2.useEffect)(() => {
2483
2845
  saveFilters({ fromStr, toStr, provider, model, status, effort, sessionId, dim });
@@ -2608,8 +2970,8 @@ function TokenLogView(props) {
2608
2970
  const cards = totals ? [
2609
2971
  { v: fmtNum(totals.calls), l: "\u8C03\u7528\u6B21\u6570" },
2610
2972
  { v: fmtCompact(totals.totalTokens), l: "\u603B Token" },
2611
- { v: fmtCompact(totals.inputTokens), l: "\u8F93\u5165(\u672A\u7F13\u5B58)" },
2612
- { v: fmtCompact(totals.cacheReadTokens), l: "\u7F13\u5B58\u547D\u4E2D" },
2973
+ { v: fmtCompact(totals.cacheReadTokens), l: "\u8F93\u5165(\u547D\u4E2D)" },
2974
+ { v: fmtCompact(totals.inputTokens), l: "\u8F93\u5165(\u672A\u547D\u4E2D)" },
2613
2975
  { v: totals.cacheHitPct + "%", l: "\u7F13\u5B58\u547D\u4E2D\u7387" },
2614
2976
  { v: fmtCompact(totals.outputTokens), l: "\u8F93\u51FA" },
2615
2977
  { v: fmtCostCny(totals.cost, rateCny), l: "\u6D88\u8017\u91D1\u989D(\u4F30\u7B97\xB7\u4EBA\u6C11\u5E01)" },
@@ -2620,8 +2982,8 @@ function TokenLogView(props) {
2620
2982
  const summaryRows = (data && data.summary || []).map((r) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("tr", { children: [
2621
2983
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { children: r.key }),
2622
2984
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { children: fmtNum(r.calls) }),
2623
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { children: fmtCompact(r.inputTokens) }),
2624
2985
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { children: fmtCompact(r.cacheReadTokens) }),
2986
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { children: fmtCompact(r.inputTokens) }),
2625
2987
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { children: r.cacheHitPct + "%" }),
2626
2988
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { children: fmtCompact(r.outputTokens) }),
2627
2989
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { children: fmtCompact(r.totalTokens) }),
@@ -2642,13 +3004,19 @@ function TokenLogView(props) {
2642
3004
  }, children: shortId(r.sessionId) }) }),
2643
3005
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { children: r.provider || "\u2014" }),
2644
3006
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { children: r.model || "\u2014" }),
2645
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { children: fmtNum(r.inputTokens) }),
2646
3007
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { children: fmtNum(r.cacheReadTokens) }),
3008
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { children: fmtNum(r.inputTokens) }),
2647
3009
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { children: r.cacheHitPercent + "%" }),
2648
3010
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { children: fmtNum(r.outputTokens) }),
2649
3011
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { children: fmtNum(r.reasoningTokens) }),
2650
3012
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { children: fmtNum(r.totalTokens) }),
2651
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { children: fmtCostCny(r.cost, rateCny) }),
3013
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("td", { title: r.pricingSource ? "\u8BA1\u4EF7\u6765\u6E90\uFF1A" + ({ custom: "\u81EA\u5B9A\u4E49\u5355\u4EF7", official: "\u5B98\u7F51\u540C\u6B65\u4EF7", builtin: "\u5185\u7F6E\u9ED8\u8BA4\u4EF7", fallback: "\u515C\u5E95\u5355\u4EF7" }[r.pricingSource] || r.pricingSource) : "", children: [
3014
+ fmtCostCny(r.cost, rateCny),
3015
+ r.pricingSource === "fallback" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "dtok-src fallback", children: [
3016
+ " ",
3017
+ "\u515C\u5E95"
3018
+ ] }) : null
3019
+ ] }),
2652
3020
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { children: r.effort || "\u2014" }),
2653
3021
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("td", { children: [
2654
3022
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dtok-code " + st.cls, children: st.label }) }),
@@ -2680,8 +3048,8 @@ function TokenLogView(props) {
2680
3048
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("tr", { children: [
2681
3049
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { children: "\u7EF4\u5EA6" }),
2682
3050
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { children: "\u8C03\u7528" }),
2683
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { children: "\u8F93\u5165" }),
2684
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { children: "\u7F13\u5B58" }),
3051
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { children: "\u8F93\u5165(\u547D\u4E2D)" }),
3052
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { children: "\u8F93\u5165(\u672A\u547D\u4E2D)" }),
2685
3053
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { children: "\u547D\u4E2D\u7387" }),
2686
3054
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { children: "\u8F93\u51FA" }),
2687
3055
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { children: "\u603BToken" }),
@@ -2723,8 +3091,8 @@ function TokenLogView(props) {
2723
3091
  sortTh("\u4F1A\u8BDDID", "sessionId"),
2724
3092
  sortTh("\u63D0\u4F9B\u5546", "provider"),
2725
3093
  sortTh("\u6A21\u578B", "model"),
2726
- sortTh("\u8F93\u5165", "inputTokens"),
2727
- sortTh("\u7F13\u5B58", "cacheReadTokens"),
3094
+ sortTh("\u8F93\u5165(\u547D\u4E2D)", "cacheReadTokens"),
3095
+ sortTh("\u8F93\u5165(\u672A\u547D\u4E2D)", "inputTokens"),
2728
3096
  sortTh("\u547D\u4E2D%", "cacheHitPercent"),
2729
3097
  sortTh("\u8F93\u51FA", "outputTokens"),
2730
3098
  sortTh("\u63A8\u7406", "reasoningTokens"),
@@ -2779,8 +3147,16 @@ function TokenLogView(props) {
2779
3147
  ] }),
2780
3148
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "dtok-btn primary", onClick: runQuery, children: "\u67E5\u8BE2" }),
2781
3149
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "dtok-btn", onClick: resetFilters, children: "\u91CD\u7F6E" }),
2782
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "dtok-btn", onClick: exportCsv, children: "\u5BFC\u51FA CSV" })
3150
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "dtok-btn", onClick: exportCsv, children: "\u5BFC\u51FA CSV" }),
3151
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "dtok-btn" + (showPricing ? " primary" : ""), onClick: () => setShowPricing(true), title: "\u914D\u7F6E\u5404\u6A21\u578B\u5355\u4EF7\uFF0C\u8D39\u7528\u6309\u81EA\u586B\u5355\u4EF7\u8BA1\u7B97", children: "\u5355\u4EF7\u8BBE\u7F6E" })
2783
3152
  ] }),
3153
+ showPricing ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PriceModal, { onClose: () => setShowPricing(false), onSaved: runQuery }) : null,
3154
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dtok-status", children: data && data.pricingInfo ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
3155
+ "\u8BA1\u4EF7\uFF1A",
3156
+ data.pricingInfo.hasCustom ? "\u81EA\u5B9A\u4E49\u5355\u4EF7 " + data.pricingInfo.customCount + " \u6761" : "\u672A\u914D\u7F6E\u81EA\u5B9A\u4E49\u5355\u4EF7",
3157
+ " \xB7 \u5B98\u7F51\u81EA\u52A8\u540C\u6B65\uFF1A" + (data.pricingInfo.fetchOfficial ? "\u5F00" : "\u5173"),
3158
+ data.pricingInfo.sources && data.pricingInfo.sources.length ? " \xB7 \u672C\u9875\u6D89\u53CA\u6765\u6E90\uFF1A" + data.pricingInfo.sources.join("/") : ""
3159
+ ] }) : null }),
2784
3160
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dtok-body", children: bodyNodes }),
2785
3161
  detailRec ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Detail, { rec: detailRec, onClose: () => setDetailRec(null), rate: rateCny }) : null
2786
3162
  ] });
@@ -2859,7 +3235,7 @@ var feature = {
2859
3235
  name: "\u7528\u91CF\u8BB0\u5F55",
2860
3236
  order: 110,
2861
3237
  accent: "#fbbf24",
2862
- description: "\u8BB0\u5F55\u5168\u90E8 LLM API \u8C03\u7528\uFF1A\u79D2\u7EA7\u65F6\u95F4\u7B5B\u9009\u3001Token/\u8D39\u7528\u7EDF\u8BA1\uFF08\u5CF0\u8C37\u8BA1\u4EF7+\u5B98\u7F51\u4EF7\u76EE\u81EA\u52A8\u540C\u6B65\uFF09\u3001\u5206\u7EC4\u6C47\u603B\u3001\u660E\u7EC6\u68C0\u7D22\u4E0E CSV \u5BFC\u51FA",
3238
+ description: "\u8BB0\u5F55\u5168\u90E8 LLM API \u8C03\u7528\uFF1A\u79D2\u7EA7\u65F6\u95F4\u7B5B\u9009\u3001Token/\u8D39\u7528\u7EDF\u8BA1\uFF08\u53EF\u914D\u7F6E\u5404\u6A21\u578B\u5355\u4EF7\uFF0C\u6301\u4E45\u4FDD\u5B58\u5E76\u6309\u81EA\u586B\u5355\u4EF7\u8BA1\u8D39\uFF1B\u5185\u7F6E\u5CF0\u8C37\u8BA1\u4EF7+\u5B98\u7F51\u4EF7\u76EE\u81EA\u52A8\u540C\u6B65\u4F5C\u515C\u5E95\uFF09\u3001\u5206\u7EC4\u6C47\u603B\u3001\u660E\u7EC6\u68C0\u7D22\u4E0E CSV \u5BFC\u51FA",
2863
3239
  css,
2864
3240
  View: TokenLogView,
2865
3241
  HomeStat: TokenLogHomeStat,
@@ -10503,7 +10879,7 @@ var feature10 = {
10503
10879
  };
10504
10880
 
10505
10881
  // src/client.jsx
10506
- var DOCK_VERSION = "0.10.3";
10882
+ var DOCK_VERSION = "0.11.1";
10507
10883
  var BUILTIN_FEATURES = [feature, feature2, feature3, feature4, feature5, feature6, feature7, feature8, feature9, feature10];
10508
10884
  var PLANNED_FEATURES = [];
10509
10885
  var PLANNED_NOTES = {};
@@ -11035,7 +11411,7 @@ function HomeView(props) {
11035
11411
  })
11036
11412
  );
11037
11413
  }
11038
- function DockPanel() {
11414
+ function DockPanel(props) {
11039
11415
  const ctx = ctxRef.current;
11040
11416
  const [, force] = import_react22.default.useReducer((n) => n + 1, 0);
11041
11417
  useExternalVersion();
@@ -11061,7 +11437,7 @@ function DockPanel() {
11061
11437
  import_react22.default.createElement(
11062
11438
  FeatureBoundary,
11063
11439
  { key: f.id, label: f.name },
11064
- import_react22.default.createElement(View, { ctx, feature: f })
11440
+ import_react22.default.createElement(View, { ctx, feature: f, params: props && props.params })
11065
11441
  )
11066
11442
  ) : null;
11067
11443
  return import_react22.default.createElement(
@@ -11164,7 +11540,7 @@ function apply(ctx) {
11164
11540
  ));
11165
11541
  slots.inject("settings.section", () => slots.register(
11166
11542
  { name: "settings.section", id: "dsh-dock", order: 90, label: "\u529F\u80FD\u575E" },
11167
- () => import_react22.default.createElement(DockPanel, null)
11543
+ (props) => import_react22.default.createElement(DockPanel, props)
11168
11544
  ));
11169
11545
  slots.inject("conversation.input.left", () => slots.register(
11170
11546
  { name: "conversation.input.left", id: "dsh-dock-chips", order: 10, label: "\u529F\u80FD\u575E" },