dsh-dock 0.10.2 → 0.11.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/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 种任务运行动画(含星际远征、星云潮汐、曲速航道、量子雷达、星座网络、深海脉动、神经电路等),速度随任务活动联动,配置全部持久化 |
@@ -33,8 +33,6 @@
33
33
  - **模型余额**:当前选中 Provider 的账户余额(跟随模型切换实时更新),点击打开功能坞并定位到该 Provider 的余额详情;
34
34
  - **用量记录**:当前会话的总 Token 与估算花费(10 秒静默刷新),点击打开功能坞并跳转到该会话的用量记录。
35
35
 
36
- ![会话区余额/用量小控件](screenshots/dock-chips.png)
37
-
38
36
  勾选「启用」即显示,点芯片跳转;每个功能可在面板页脚或设置页单独设置「会话页显示/隐藏」。
39
37
 
40
38
  ## 界面
@@ -111,6 +109,7 @@ dsh plugin --profile web remove dsh-dock # 卸载
111
109
  4. 「分组汇总」按 无 / 提供商 / 模型 / 状态 / 强度 维度切换
112
110
  5. 明细表 15 列可排序,100 行/页,点击「会话 ID」即筛选该会话;行右侧「详情」看单次调用信息
113
111
  6. 「CSV 导出」下载当前筛选结果
112
+ 7. **费用不准?点「单价设置」按模型填真实单价**:官网刊例价是抓来的,与实际计费(第三方中转、折扣、自建端点)常有出入。点开独立的「单价设置」子弹窗(可最大化、拖动、缩放),可按模型逐条配置单价(人民币元 / 百万 tokens,匹配模型名子串即可),并设置兜底单价、USD→CNY 汇率、是否继续自动同步官网价。每个模型还可配置**多段分时价**(如 DeepSeek 的高峰/优惠时段),命中哪段用哪段、时段外回落基准价,支持跨零点(如 `23~7`)与半点边界(如 `8.5` = 08:30)。保存后写入宿主配置**持久保存**,费用立即按自填单价重算(无需重扫历史);计价优先级:**自定义单价 > 官网同步价 > 内置价 > 兜底价**。明细表里走兜底价的金额会标「兜底」,方便你补配。
114
113
 
115
114
  **模型余额**
116
115
 
@@ -191,8 +190,6 @@ dsh plugin --profile web remove dsh-dock # 卸载
191
190
  - **模型余额**:当前选中 Provider 的账户余额(跟随模型切换实时更新),点击打开功能坞并定位到该 Provider 的余额详情;
192
191
  - **用量记录**:当前会话的总 Token 与估算花费(10 秒静默刷新),点击打开功能坞并跳转到该会话的用量记录。
193
192
 
194
- ![会话区余额/用量小控件](screenshots/dock-chips.png)
195
-
196
193
  勾选「启用」即显示,点芯片跳转;每个功能可在面板页脚或设置页单独设置「会话页显示/隐藏」。
197
194
 
198
195
  ## 界面
@@ -269,6 +266,7 @@ dsh plugin --profile web remove dsh-dock # 卸载
269
266
  4. 「分组汇总」按 无 / 提供商 / 模型 / 状态 / 强度 维度切换
270
267
  5. 明细表 15 列可排序,100 行/页,点击「会话 ID」即筛选该会话;行右侧「详情」看单次调用信息
271
268
  6. 「CSV 导出」下载当前筛选结果
269
+ 7. **费用不准?点「单价设置」按模型填真实单价**:官网刊例价是抓来的,与实际计费(第三方中转、折扣、自建端点)常有出入。点开独立的「单价设置」子弹窗(可最大化、拖动、缩放),可按模型逐条配置单价(人民币元 / 百万 tokens,匹配模型名子串即可),并设置兜底单价、USD→CNY 汇率、是否继续自动同步官网价。每个模型还可配置**多段分时价**(如 DeepSeek 的高峰/优惠时段),命中哪段用哪段、时段外回落基准价,支持跨零点(如 `23~7`)与半点边界(如 `8.5` = 08:30)。保存后写入宿主配置**持久保存**,费用立即按自填单价重算(无需重扫历史);计价优先级:**自定义单价 > 官网同步价 > 内置价 > 兜底价**。明细表里走兜底价的金额会标「兜底」,方便你补配。
272
270
 
273
271
  **模型余额**
274
272
 
@@ -350,6 +348,7 @@ dsh plugin --profile web remove dsh-dock # 卸载
350
348
  - **功能开关刷新后重置**:v0.5.0 起功能开关已持久化(浏览器本地存储),刷新/重启后保持。
351
349
  - **任务动画/任务通知/运行状态不生效**:三者的路由都在宿主进程内(动画/通知的配置持久化也在宿主),旧宿主需重启 `dsh web` 加载(面板页会提示「宿主进程是旧版本」)。另注意通知与运行状态已各自独立成菜单项,需在功能坞里分别开启「任务通知」「运行状态」。
352
350
  - **用量记录空白**:确认 dsh 是较新版本且插件宿主已加载(查看启动日志含 `[dsh-dock] host half loaded`);旧宿主进程需重启 dsh web 加载新路由。
351
+ - **费用不准 / 与实际账单对不上**:金额是按单价估算的,官网抓取来的刊例价不代表你的实际计费。在用量记录页点「单价设置」(独立子弹窗,可最大化/拖动/缩放),按模型填真实单价并保存(持久化到宿主配置),费用会立即按自填单价重算;每个模型还支持多段分时价(跨零点 / 半点边界)。计价优先级为「自定义单价 > 官网同步价 > 内置价 > 兜底价」,明细里走兜底价的金额会标「兜底」。
353
352
  - **余额显示「需登录 / 无密钥」**:对应 Provider 未在 dsh 凭据中配置可查询余额的密钥,属正常状态提示。
354
353
  - **切模型 / 打开历史会话卡住,报 `preset "…" failed to mount`**:这是该 **agent 预设**与 dsh 版本
355
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);
@@ -2447,6 +2479,7 @@ function Detail({ rec, onClose, rate }) {
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,333 @@ 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" }),
2725
+ num(r.input, (v) => patchRow(i, { input: 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\u51FA" }),
2729
+ num(r.output, (v) => patchRow(i, { output: 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\u7F13\u5B58\u547D\u4E2D" }),
2733
+ num(r.cacheRead, (v) => patchRow(i, { cacheRead: 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: "\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" }),
2741
+ 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,
2742
+ r.peaks.map((s, k) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dtok-pseg", children: [
2743
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dtok-pseg-name", children: segLabel(s) }),
2744
+ num(s.start, (v) => patchSeg(i, k, { start: v }), { width: 56 }),
2745
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dtok-pseg-tilde", children: "~" }),
2746
+ num(s.end, (v) => patchSeg(i, k, { end: v }), { width: 56 }),
2747
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dtok-pseg-tilde", children: "\u65F6" }),
2748
+ segNum(s.input, (v) => patchSeg(i, k, { input: v }), "\u8F93\u5165"),
2749
+ segNum(s.output, (v) => patchSeg(i, k, { output: v }), "\u8F93\u51FA"),
2750
+ segNum(s.cacheRead, (v) => patchSeg(i, k, { cacheRead: v }), "\u7F13\u5B58\u547D\u4E2D"),
2751
+ segNum(s.cacheWrite, (v) => patchSeg(i, k, { cacheWrite: v }), "\u7F13\u5B58\u5199\u5165"),
2752
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "dtok-btn tiny", onClick: () => delSeg(i, k), children: "\u5220\u9664\u65F6\u6BB5" })
2753
+ ] }, k)),
2754
+ /* @__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 / \u8F93\u51FA / \u7F13\u5B58\u547D\u4E2D / \u7F13\u5B58\u5199\u5165" }),
2755
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "dtok-btn tiny", onClick: () => addSeg(i), children: "+ \u6DFB\u52A0\u65F6\u6BB5" })
2756
+ ] }, i)),
2757
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dtok-price-grid", style: { marginTop: 6 }, children: [
2758
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dtok-price-field", style: { flex: "0 0 220px" }, children: [
2759
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { children: "\u65B0\u589E\u6A21\u578B\u5339\u914D" }),
2760
+ /* @__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) }),
2761
+ /* @__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)) })
2762
+ ] }),
2763
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "dtok-btn", onClick: () => addRow(), children: "+ \u6DFB\u52A0" })
2764
+ ] }),
2765
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dtok-price-hint", children: [
2766
+ "\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 ",
2767
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", { children: "v4-flash" }),
2768
+ "\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"
2769
+ ] })
2770
+ ] }),
2771
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
2772
+ /* @__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" }),
2773
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dtok-price-grid", children: [
2774
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dtok-price-field", children: [
2775
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { children: "\u8F93\u5165" }),
2776
+ num(fb.input, (v) => setFb((s) => Object.assign({}, s, { input: v })))
2777
+ ] }),
2778
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dtok-price-field", children: [
2779
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { children: "\u8F93\u51FA" }),
2780
+ num(fb.output, (v) => setFb((s) => Object.assign({}, s, { output: v })))
2781
+ ] }),
2782
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dtok-price-field", children: [
2783
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { children: "\u7F13\u5B58\u547D\u4E2D" }),
2784
+ num(fb.cacheRead, (v) => setFb((s) => Object.assign({}, s, { cacheRead: v })))
2785
+ ] }),
2786
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dtok-price-field", children: [
2787
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { children: "\u7F13\u5B58\u5199\u5165" }),
2788
+ num(fb.cacheWrite, (v) => setFb((s) => Object.assign({}, s, { cacheWrite: v })))
2789
+ ] })
2790
+ ] })
2791
+ ] }),
2792
+ cfg && cfg.builtin && cfg.builtin.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("details", { children: [
2793
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("summary", { className: "dtok-price-hint", style: { cursor: "pointer" }, children: [
2794
+ "\u67E5\u770B\u5185\u7F6E\u9ED8\u8BA4\u5355\u4EF7\uFF08",
2795
+ cfg.builtin.length,
2796
+ " \u6761\uFF0C\u81EA\u5B9A\u4E49\u914D\u7F6E\u672A\u547D\u4E2D\u65F6\u6309\u6B64\u515C\u5E95\uFF09"
2797
+ ] }),
2798
+ /* @__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: [
2799
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("tr", { children: [
2800
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { children: "\u5339\u914D" }),
2801
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { children: "\u8F93\u5165" }),
2802
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { children: "\u8F93\u51FA" }),
2803
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { children: "\u7F13\u5B58\u547D\u4E2D" }),
2804
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { children: "\u7F13\u5B58\u5199\u5165" }),
2805
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { children: "\u5206\u65F6\u6BB5" })
2806
+ ] }) }),
2807
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("tbody", { children: cfg.builtin.map((b) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("tr", { children: [
2808
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { children: b.match }),
2809
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { children: b.input }),
2810
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { children: b.output }),
2811
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { children: b.cacheRead }),
2812
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { children: b.cacheWrite }),
2813
+ /* @__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" })
2814
+ ] }, b.match)) })
2815
+ ] }) })
2816
+ ] }) : null,
2817
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dtok-price-grid", children: [
2818
+ /* @__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" }),
2819
+ msg ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dtok-price-msg " + (msg.ok ? "ok" : "err"), children: msg.text }) : null
2820
+ ] })
2821
+ ] });
2822
+ }
2463
2823
  function TokenLogView(props) {
2464
2824
  const navSession = props && props.params && props.params.sessionId ? props.params.sessionId : null;
2465
2825
  const [savedFilters] = (0, import_react2.useState)(loadSavedFilters);
@@ -2478,6 +2838,7 @@ function TokenLogView(props) {
2478
2838
  const [err, setErr] = (0, import_react2.useState)("");
2479
2839
  const [page, setPage] = (0, import_react2.useState)(0);
2480
2840
  const [detailRec, setDetailRec] = (0, import_react2.useState)(null);
2841
+ const [showPricing, setShowPricing] = (0, import_react2.useState)(() => !!(props && props.params && props.params.openPricing));
2481
2842
  const pageSize = 100;
2482
2843
  (0, import_react2.useEffect)(() => {
2483
2844
  saveFilters({ fromStr, toStr, provider, model, status, effort, sessionId, dim });
@@ -2648,7 +3009,13 @@ function TokenLogView(props) {
2648
3009
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { children: fmtNum(r.outputTokens) }),
2649
3010
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { children: fmtNum(r.reasoningTokens) }),
2650
3011
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { children: fmtNum(r.totalTokens) }),
2651
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { children: fmtCostCny(r.cost, rateCny) }),
3012
+ /* @__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: [
3013
+ fmtCostCny(r.cost, rateCny),
3014
+ r.pricingSource === "fallback" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "dtok-src fallback", children: [
3015
+ " ",
3016
+ "\u515C\u5E95"
3017
+ ] }) : null
3018
+ ] }),
2652
3019
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { children: r.effort || "\u2014" }),
2653
3020
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("td", { children: [
2654
3021
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dtok-code " + st.cls, children: st.label }) }),
@@ -2779,8 +3146,16 @@ function TokenLogView(props) {
2779
3146
  ] }),
2780
3147
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "dtok-btn primary", onClick: runQuery, children: "\u67E5\u8BE2" }),
2781
3148
  /* @__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" })
3149
+ /* @__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" + (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
3151
  ] }),
3152
+ showPricing ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PriceModal, { onClose: () => setShowPricing(false), onSaved: runQuery }) : null,
3153
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dtok-status", children: data && data.pricingInfo ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
3154
+ "\u8BA1\u4EF7\uFF1A",
3155
+ data.pricingInfo.hasCustom ? "\u81EA\u5B9A\u4E49\u5355\u4EF7 " + data.pricingInfo.customCount + " \u6761" : "\u672A\u914D\u7F6E\u81EA\u5B9A\u4E49\u5355\u4EF7",
3156
+ " \xB7 \u5B98\u7F51\u81EA\u52A8\u540C\u6B65\uFF1A" + (data.pricingInfo.fetchOfficial ? "\u5F00" : "\u5173"),
3157
+ data.pricingInfo.sources && data.pricingInfo.sources.length ? " \xB7 \u672C\u9875\u6D89\u53CA\u6765\u6E90\uFF1A" + data.pricingInfo.sources.join("/") : ""
3158
+ ] }) : null }),
2784
3159
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dtok-body", children: bodyNodes }),
2785
3160
  detailRec ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Detail, { rec: detailRec, onClose: () => setDetailRec(null), rate: rateCny }) : null
2786
3161
  ] });
@@ -2859,7 +3234,7 @@ var feature = {
2859
3234
  name: "\u7528\u91CF\u8BB0\u5F55",
2860
3235
  order: 110,
2861
3236
  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",
3237
+ 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
3238
  css,
2864
3239
  View: TokenLogView,
2865
3240
  HomeStat: TokenLogHomeStat,
@@ -10503,7 +10878,7 @@ var feature10 = {
10503
10878
  };
10504
10879
 
10505
10880
  // src/client.jsx
10506
- var DOCK_VERSION = "0.10.2";
10881
+ var DOCK_VERSION = "0.11.0";
10507
10882
  var BUILTIN_FEATURES = [feature, feature2, feature3, feature4, feature5, feature6, feature7, feature8, feature9, feature10];
10508
10883
  var PLANNED_FEATURES = [];
10509
10884
  var PLANNED_NOTES = {};
@@ -11035,7 +11410,7 @@ function HomeView(props) {
11035
11410
  })
11036
11411
  );
11037
11412
  }
11038
- function DockPanel() {
11413
+ function DockPanel(props) {
11039
11414
  const ctx = ctxRef.current;
11040
11415
  const [, force] = import_react22.default.useReducer((n) => n + 1, 0);
11041
11416
  useExternalVersion();
@@ -11061,7 +11436,7 @@ function DockPanel() {
11061
11436
  import_react22.default.createElement(
11062
11437
  FeatureBoundary,
11063
11438
  { key: f.id, label: f.name },
11064
- import_react22.default.createElement(View, { ctx, feature: f })
11439
+ import_react22.default.createElement(View, { ctx, feature: f, params: props && props.params })
11065
11440
  )
11066
11441
  ) : null;
11067
11442
  return import_react22.default.createElement(
@@ -11164,7 +11539,7 @@ function apply(ctx) {
11164
11539
  ));
11165
11540
  slots.inject("settings.section", () => slots.register(
11166
11541
  { name: "settings.section", id: "dsh-dock", order: 90, label: "\u529F\u80FD\u575E" },
11167
- () => import_react22.default.createElement(DockPanel, null)
11542
+ (props) => import_react22.default.createElement(DockPanel, props)
11168
11543
  ));
11169
11544
  slots.inject("conversation.input.left", () => slots.register(
11170
11545
  { name: "conversation.input.left", id: "dsh-dock-chips", order: 10, label: "\u529F\u80FD\u575E" },