dsh-dock 0.11.2 → 0.11.5

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.
Files changed (41) hide show
  1. package/LICENSE +18 -18
  2. package/README.md +255 -396
  3. package/client.js +596 -98
  4. package/cordis.patch.yml +2 -2
  5. package/features/animation/host.js +119 -119
  6. package/features/animation/view.jsx +1662 -1662
  7. package/features/balance/host.js +494 -494
  8. package/features/balance/view.js +351 -351
  9. package/features/games/games/breakout.jsx +227 -227
  10. package/features/games/games/gomoku.jsx +244 -244
  11. package/features/games/games/racer.jsx +169 -169
  12. package/features/games/games/shared.jsx +118 -118
  13. package/features/games/games/snake.jsx +144 -144
  14. package/features/games/games/sokoban.jsx +180 -180
  15. package/features/games/games/tank.jsx +128 -128
  16. package/features/games/games/tetris.jsx +308 -308
  17. package/features/games/games/xiangqi.jsx +277 -277
  18. package/features/games/tank-core.js +152 -152
  19. package/features/games/view.jsx +639 -639
  20. package/features/games/xiangqi-core.js +299 -299
  21. package/features/heartbeat/view.js +35 -35
  22. package/features/mobile-relay/gateway.js +472 -472
  23. package/features/mobile-relay/host.js +622 -570
  24. package/features/mobile-relay/view.jsx +380 -300
  25. package/features/modelconfig/host.js +406 -406
  26. package/features/modelconfig/view.js +490 -490
  27. package/features/notify/host.js +374 -374
  28. package/features/notify/view.jsx +751 -751
  29. package/features/theme/view.js +61 -61
  30. package/features/tokenlog/host.js +882 -883
  31. package/features/tokenlog/view.jsx +1369 -932
  32. package/features/visionproxy/host.js +315 -315
  33. package/index.js +195 -195
  34. package/package.json +66 -64
  35. package/screenshots/dock-tokenlog-charts-2.png +0 -0
  36. package/screenshots/dock-tokenlog-charts-3.png +0 -0
  37. package/screenshots/dock-tokenlog-charts.png +0 -0
  38. package/src/client.jsx +677 -677
  39. package/src/host-core.js +207 -207
  40. package/src/shared.js +192 -192
  41. package/src/task-track.js +354 -359
package/client.js CHANGED
@@ -2342,6 +2342,70 @@ function fmtTime(ts) {
2342
2342
  const p = (x) => String(x).padStart(2, "0");
2343
2343
  return d.getFullYear() + "-" + p(d.getMonth() + 1) + "-" + p(d.getDate()) + " " + p(d.getHours()) + ":" + p(d.getMinutes()) + ":" + p(d.getSeconds());
2344
2344
  }
2345
+ function fmtCostCnyExact(usd, rate) {
2346
+ const v = (Number(usd) || 0) * (Number(rate) > 0 ? Number(rate) : 7.2);
2347
+ return "\xA5" + v.toFixed(4);
2348
+ }
2349
+ function fmtDurCompact(ms) {
2350
+ if (ms === null || ms === void 0 || isNaN(ms)) return "\u2014";
2351
+ if (ms < 1e3) return Math.round(ms) + "ms";
2352
+ if (ms < 6e4) return (ms / 1e3).toFixed(1) + "s";
2353
+ if (ms < 36e5) return Math.round(ms / 6e4) + "m";
2354
+ return (ms / 36e5).toFixed(1) + "h";
2355
+ }
2356
+ function startOfToday() {
2357
+ const d = /* @__PURE__ */ new Date();
2358
+ d.setHours(0, 0, 0, 0);
2359
+ return d.getTime();
2360
+ }
2361
+ function hourOf(ts) {
2362
+ return new Date(ts).getHours();
2363
+ }
2364
+ var EXPORT_COLUMNS = [
2365
+ ["\u65F6\u95F4", (r) => fmtTime(r.time)],
2366
+ ["\u4F1A\u8BDDID", (r) => r.sessionId || ""],
2367
+ ["\u63D0\u4F9B\u5546", (r) => r.provider || "\u2014"],
2368
+ ["\u6A21\u578B", (r) => r.model || "\u2014"],
2369
+ ["\u8F93\u5165(\u547D\u4E2D)", (r) => fmtNum(r.cacheReadTokens)],
2370
+ ["\u8F93\u5165(\u672A\u547D\u4E2D)", (r) => fmtNum(r.inputTokens)],
2371
+ ["\u547D\u4E2D%", (r) => (Number(r.cacheHitPercent) || 0) + "%"],
2372
+ ["\u8F93\u51FA", (r) => fmtNum(r.outputTokens)],
2373
+ ["\u63A8\u7406", (r) => fmtNum(r.reasoningTokens)],
2374
+ ["\u603B\u989D", (r) => fmtNum(r.totalTokens)],
2375
+ ["\u91D1\u989D\uFF08\u4EBA\u6C11\u5E01\uFF09", (r, rate) => fmtCostCnyExact(r.cost, rate)],
2376
+ ["\u5F3A\u5EA6", (r) => r.effort || "\u2014"],
2377
+ ["\u72B6\u6001", (r) => statusInfo(r).label],
2378
+ ["\u8017\u65F6", (r) => fmtDuration(r.llmMs)]
2379
+ ];
2380
+ function csvCell(v) {
2381
+ const s = String(v === void 0 || v === null ? "" : v);
2382
+ return /[",\n\r]/.test(s) ? '"' + s.replace(/"/g, '""') + '"' : s;
2383
+ }
2384
+ function buildExportCsv(rows, rate) {
2385
+ const list = Array.isArray(rows) ? rows : [];
2386
+ const lines = [EXPORT_COLUMNS.map(([title]) => csvCell(title)).join(",")];
2387
+ for (const r of list) lines.push(EXPORT_COLUMNS.map(([, get]) => csvCell(get(r, rate))).join(","));
2388
+ if (list.length) {
2389
+ const sum = (k) => list.reduce((a, r) => a + (Number(r[k]) || 0), 0);
2390
+ const totalInput = sum("inputTokens") + sum("cacheReadTokens");
2391
+ const hitPct = totalInput > 0 ? Math.round(sum("cacheReadTokens") / totalInput * 100) : 0;
2392
+ const totals = {
2393
+ "\u8C03\u7528\u6B21\u6570": String(list.length),
2394
+ "\u8F93\u5165(\u547D\u4E2D)": fmtNum(sum("cacheReadTokens")),
2395
+ "\u8F93\u5165(\u672A\u547D\u4E2D)": fmtNum(sum("inputTokens")),
2396
+ "\u547D\u4E2D%": hitPct + "%",
2397
+ "\u8F93\u51FA": fmtNum(sum("outputTokens")),
2398
+ "\u63A8\u7406": fmtNum(sum("reasoningTokens")),
2399
+ "\u603B\u989D": fmtNum(sum("totalTokens")),
2400
+ "\u91D1\u989D\uFF08\u4EBA\u6C11\u5E01\uFF09": fmtCostCnyExact(sum("cost"), rate)
2401
+ };
2402
+ lines.push(EXPORT_COLUMNS.map(([title]) => {
2403
+ if (title === "\u63D0\u4F9B\u5546") return csvCell("\u5408\u8BA1(" + list.length + " \u6761\u8C03\u7528)");
2404
+ return csvCell(totals[title] === void 0 ? "" : totals[title]);
2405
+ }).join(","));
2406
+ }
2407
+ return lines.join("\r\n");
2408
+ }
2345
2409
  var STORE_KEY = "dsh-dock/tokenlog/filters/v1";
2346
2410
  var FILTER_FIELDS = ["fromStr", "toStr", "provider", "model", "status", "effort", "sessionId", "dim"];
2347
2411
  function loadSavedFilters() {
@@ -2379,6 +2443,204 @@ function statusInfo(r) {
2379
2443
  if (r.status === "interrupted") return { code: 500, label: "500", cls: "warn", title: "\u4E2D\u65AD" };
2380
2444
  return { code: 0, label: "\u2026", cls: "pend", title: "\u8FDB\u884C\u4E2D" };
2381
2445
  }
2446
+ var CHART_COLORS = ["#60a5fa", "#fbbf24", "#4ade80", "#f472b6", "#a78bfa", "#fb923c", "#2dd4bf", "#e879f9", "#94a3b8", "#f87171"];
2447
+ function chartColor(i) {
2448
+ return CHART_COLORS[i % CHART_COLORS.length];
2449
+ }
2450
+ var CHART_METRICS = [
2451
+ ["totalTokens", "\u603B Token", (v) => fmtCompact(v)],
2452
+ ["calls", "\u8C03\u7528\u6B21\u6570", (v) => fmtNum(v)],
2453
+ ["cost", "\u91D1\u989D", (v, rate) => fmtCostCny(v, rate)],
2454
+ ["outputTokens", "\u8F93\u51FA Token", (v) => fmtCompact(v)]
2455
+ ];
2456
+ var CHART_DIMS = [
2457
+ ["model", "\u6A21\u578B"],
2458
+ ["provider", "\u63D0\u4F9B\u5546"],
2459
+ ["status", "\u72B6\u6001"],
2460
+ ["effort", "\u63A8\u7406\u5F3A\u5EA6"]
2461
+ ];
2462
+ function emptyAgg() {
2463
+ return { calls: 0, totalTokens: 0, outputTokens: 0, inputTokens: 0, cacheReadTokens: 0, reasoningTokens: 0, cost: 0, llmMs: 0, timed: 0 };
2464
+ }
2465
+ function addToAgg(a, r) {
2466
+ a.calls += 1;
2467
+ a.totalTokens += Number(r.totalTokens) || 0;
2468
+ a.outputTokens += Number(r.outputTokens) || 0;
2469
+ a.inputTokens += Number(r.inputTokens) || 0;
2470
+ a.cacheReadTokens += Number(r.cacheReadTokens) || 0;
2471
+ a.reasoningTokens += Number(r.reasoningTokens) || 0;
2472
+ a.cost += Number(r.cost) || 0;
2473
+ if (typeof r.llmMs === "number") {
2474
+ a.llmMs += r.llmMs;
2475
+ a.timed += 1;
2476
+ }
2477
+ }
2478
+ function groupByKey(records, keyFn, metric) {
2479
+ const map = /* @__PURE__ */ new Map();
2480
+ for (const r of records) {
2481
+ const k = keyFn(r);
2482
+ let a = map.get(k);
2483
+ if (!a) {
2484
+ a = emptyAgg();
2485
+ a.key = k;
2486
+ map.set(k, a);
2487
+ }
2488
+ addToAgg(a, r);
2489
+ }
2490
+ return [...map.values()].sort((a, b) => (b[metric] || 0) - (a[metric] || 0));
2491
+ }
2492
+ function DonutChart({ items, metric, metricFmt, rate, centerLabel, emptyText }) {
2493
+ const total = items.reduce((s, it) => s + (Number(it[metric]) || 0), 0);
2494
+ if (!items.length || total <= 0) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dtok-chart-empty", children: emptyText || "\u6682\u65E0\u6570\u636E" });
2495
+ const shown = items.slice(0, 8);
2496
+ const restSum = items.slice(8).reduce((s, it) => s + (Number(it[metric]) || 0), 0);
2497
+ if (restSum > 0) {
2498
+ const restAgg = Object.assign(emptyAgg(), { key: "\u5176\u4ED6(" + (items.length - 8) + "\u9879)" });
2499
+ restAgg[metric] = restSum;
2500
+ shown.push(restAgg);
2501
+ }
2502
+ const R = 15.9155;
2503
+ let acc = 0;
2504
+ const segs = shown.map((it, i) => {
2505
+ const v = Number(it[metric]) || 0;
2506
+ const frac = v / total;
2507
+ const seg = /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2508
+ "circle",
2509
+ {
2510
+ cx: "21",
2511
+ cy: "21",
2512
+ r: R,
2513
+ fill: "none",
2514
+ stroke: chartColor(i),
2515
+ strokeWidth: frac > 0.02 ? 7 : 5,
2516
+ strokeDasharray: frac * 100 + " " + (100 - frac * 100),
2517
+ strokeDashoffset: String(25 - acc * 100),
2518
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("title", { children: it.key + "\uFF1A" + metricFmt(v, rate) + "\uFF08" + (frac * 100).toFixed(1) + "%\uFF09" })
2519
+ },
2520
+ it.key
2521
+ );
2522
+ acc += frac;
2523
+ return seg;
2524
+ });
2525
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dtok-donut-wrap", children: [
2526
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { className: "dtok-donut", viewBox: "0 0 42 42", role: "img", children: [
2527
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "21", cy: "21", r: R, fill: "none", stroke: "var(--dsw-alias-border-l1)", strokeWidth: "7" }),
2528
+ segs,
2529
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("text", { x: "21", y: "19.5", textAnchor: "middle", className: "dtok-donut-v", children: metricFmt(total, rate) }),
2530
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("text", { x: "21", y: "26", textAnchor: "middle", className: "dtok-donut-l", children: centerLabel })
2531
+ ] }),
2532
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dtok-legend", children: shown.map((it, i) => {
2533
+ const v = Number(it[metric]) || 0;
2534
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dtok-legend-row", title: it.key, children: [
2535
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dtok-dot", style: { background: chartColor(i) } }),
2536
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dtok-legend-k", children: it.key }),
2537
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "dtok-legend-v", children: [
2538
+ metricFmt(v, rate),
2539
+ " \xB7 ",
2540
+ (v / total * 100).toFixed(1),
2541
+ "%"
2542
+ ] })
2543
+ ] }, it.key);
2544
+ }) })
2545
+ ] });
2546
+ }
2547
+ function HBarChart({ items, metric, metricFmt, rate, maxRows, emptyText }) {
2548
+ if (!items.length) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dtok-chart-empty", children: emptyText || "\u6682\u65E0\u6570\u636E" });
2549
+ const shown = items.slice(0, maxRows || 12);
2550
+ const max = Math.max(...shown.map((it) => Number(it[metric]) || 0), 1e-9);
2551
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dtok-hbars", children: shown.map((it, i) => {
2552
+ const v = Number(it[metric]) || 0;
2553
+ const pct = Math.max(1.5, v / max * 100);
2554
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dtok-hbar-row", title: it.key + "\uFF1A" + metricFmt(v, rate), children: [
2555
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dtok-hbar-k", children: it.key }),
2556
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dtok-hbar-track", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dtok-hbar-fill", style: { width: pct + "%", background: chartColor(i) } }) }),
2557
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dtok-hbar-v", children: metricFmt(v, rate) })
2558
+ ] }, it.key);
2559
+ }) });
2560
+ }
2561
+ function HourChart({ hours, metric, metricFmt, rate, emptyText }) {
2562
+ const total = hours.reduce((s, h) => s + (Number(h[metric]) || 0), 0);
2563
+ if (total <= 0) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dtok-chart-empty", children: emptyText || "\u6682\u65E0\u6570\u636E" });
2564
+ const max = Math.max(...hours.map((h) => Number(h[metric]) || 0), 1e-9);
2565
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dtok-hours", role: "img", children: hours.map((h) => {
2566
+ const v = Number(h[metric]) || 0;
2567
+ const pct = v > 0 ? Math.max(2, v / max * 100) : 0;
2568
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dtok-hour-col", title: h.hour + " \u65F6\uFF1A" + metricFmt(v, rate), children: pct > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dtok-hour-bar", style: { height: pct + "%" } }) : null }, h.hour);
2569
+ }) });
2570
+ }
2571
+ function ChartsPanel({ records, rate }) {
2572
+ const [metric, setMetric] = (0, import_react2.useState)("totalTokens");
2573
+ const [dim, setDimLocal] = (0, import_react2.useState)("model");
2574
+ const metricDef = CHART_METRICS.find((m) => m[0] === metric) || CHART_METRICS[0];
2575
+ const metricFmt = metricDef[2];
2576
+ const todayRecs = (0, import_react2.useMemo)(() => {
2577
+ const t0 = startOfToday();
2578
+ return records.filter((r) => Number(r.time) >= t0);
2579
+ }, [records]);
2580
+ const todayByModel = (0, import_react2.useMemo)(() => groupByKey(todayRecs, (r) => r.model || "(\u672A\u77E5)", metric), [todayRecs, metric]);
2581
+ const byDim = (0, import_react2.useMemo)(() => groupByKey(records, (r) => r[dim] || "(\u7A7A)", metric), [records, dim, metric]);
2582
+ const modelTime = (0, import_react2.useMemo)(() => groupByKey(records, (r) => r.model || "(\u672A\u77E5)", "llmMs"), [records]);
2583
+ const timedCalls = (0, import_react2.useMemo)(() => records.reduce((s, r) => s + (typeof r.llmMs === "number" ? 1 : 0), 0), [records]);
2584
+ const hours = (0, import_react2.useMemo)(() => {
2585
+ const arr = [];
2586
+ for (let h = 0; h < 24; h++) {
2587
+ const a = emptyAgg();
2588
+ a.hour = h;
2589
+ arr.push(a);
2590
+ }
2591
+ for (const r of records) addToAgg(arr[hourOf(r.time)], r);
2592
+ return arr;
2593
+ }, [records]);
2594
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dtok-charts", children: [
2595
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dtok-chart-toolbar", children: [
2596
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dtok-chart-toolbar-label", children: "\u6307\u6807" }),
2597
+ CHART_METRICS.map(([id, label]) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "dtok-btn tiny" + (metric === id ? " primary" : ""), onClick: () => setMetric(id), children: label }, id)),
2598
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dtok-chart-toolbar-note", children: "\u56FE\u8868\u8DDF\u968F\u4E0A\u65B9\u7B5B\u9009\u6761\u4EF6\uFF08\u65F6\u95F4 / \u4F1A\u8BDD / \u63D0\u4F9B\u5546 / \u6A21\u578B / \u72B6\u6001 / \u5F3A\u5EA6\uFF09\u5B9E\u65F6\u8054\u52A8" })
2599
+ ] }),
2600
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dtok-chart-grid", children: [
2601
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dtok-chart-card", children: [
2602
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dtok-chart-title", children: [
2603
+ "\u4ECA\u65E5\u6A21\u578B\u7528\u91CF\u5360\u6BD4",
2604
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "dtok-chart-sub", children: [
2605
+ metricDef[1],
2606
+ " \xB7 ",
2607
+ todayRecs.length,
2608
+ " \u6B21\u8C03\u7528"
2609
+ ] })
2610
+ ] }),
2611
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DonutChart, { items: todayByModel, metric, metricFmt, rate, centerLabel: "\u4ECA\u65E5" + metricDef[1], emptyText: "\u4ECA\u65E5\u6682\u65E0\u8C03\u7528" })
2612
+ ] }),
2613
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dtok-chart-card", children: [
2614
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dtok-chart-title", children: [
2615
+ "24 \u5C0F\u65F6\u8C03\u7528\u5206\u5E03",
2616
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "dtok-chart-sub", children: [
2617
+ "\u6309\u5C0F\u65F6\u805A\u5408 \xB7 ",
2618
+ metricDef[1]
2619
+ ] })
2620
+ ] }),
2621
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(HourChart, { hours, metric, metricFmt, rate })
2622
+ ] }),
2623
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dtok-chart-card", children: [
2624
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dtok-chart-title", children: [
2625
+ "\u6A21\u578B\u8017\u65F6\u6392\u884C",
2626
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "dtok-chart-sub", children: [
2627
+ "\u7D2F\u8BA1 LLM \u8017\u65F6 \xB7 \u5DF2\u8BA1\u65F6 ",
2628
+ timedCalls,
2629
+ " \u6B21"
2630
+ ] })
2631
+ ] }),
2632
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(HBarChart, { items: modelTime, metric: "llmMs", metricFmt: (v) => fmtDurCompact(v), rate, emptyText: "\u6682\u65E0\u8BA1\u65F6\u6570\u636E" })
2633
+ ] }),
2634
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dtok-chart-card", children: [
2635
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dtok-chart-title", children: [
2636
+ "\u5206\u5E03\u7EDF\u8BA1",
2637
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dtok-chart-sub", children: CHART_DIMS.map(([id, label]) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "dtok-btn tiny" + (dim === id ? " primary" : ""), style: { marginLeft: 4 }, onClick: () => setDimLocal(id), children: label }, id)) })
2638
+ ] }),
2639
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(HBarChart, { items: byDim, metric, metricFmt, rate })
2640
+ ] })
2641
+ ] })
2642
+ ] });
2643
+ }
2382
2644
  var css = `
2383
2645
  .dtok-root{display:flex;flex-direction:column;gap:8px;width:100%;min-width:0;}
2384
2646
  .dtok-status{display:flex;align-items:center;gap:10px;font-size:12px;color:var(--dsw-alias-label-secondary);flex-wrap:wrap;}
@@ -2425,6 +2687,58 @@ var css = `
2425
2687
  .dtok-pager.top{margin:0;}
2426
2688
  .dtok-pager.bottom{margin:0;}
2427
2689
  .dtok-err{color:var(--dsw-alias-state-error-primary,#ff7a7a);font-size:12px;}
2690
+ /* ---- \u67E5\u8BE2\u52A0\u8F7D\u52A8\u753B ----
2691
+ \u6570\u636E\u91CF\u5927\u65F6\u300C\u67E5\u8BE2\u300D\u8981\u7B49\u597D\u51E0\u79D2\uFF0C\u65E7\u7248\u53EA\u6709\u4E00\u884C\u300C\u52A0\u8F7D\u4E2D\u2026\u300D\uFF1A\u7528\u6237\u65E2\u4E0D\u786E\u5B9A\u70B9\u4E0A\u6CA1\u70B9\u4E0A\uFF0C
2692
+ \u4E5F\u4E0D\u77E5\u9053\u6570\u636E\u4EC0\u4E48\u65F6\u5019\u6362\u6389\u3002\u8FD9\u91CC\u7528\u4E00\u6BB5\u6709\u8DA3\u7684\u300C\u8BB0\u8D26\u5C0F\u961F\u6E05\u70B9\u300D\u52A8\u753B\u586B\u6EE1\u7B49\u5F85\uFF0C\u5E76\u5728\u7ED3\u679C
2693
+ \u771F\u6B63\u5230\u4F4D\u65F6\u7ED9\u4E00\u6B21\u300C\u2713 \u6570\u636E\u5DF2\u66F4\u65B0\u300D+ KPI \u5361\u56DE\u5F39\uFF0C\u8BA9\u300C\u6570\u636E\u53D8\u4E86\u300D\u8FD9\u4EF6\u4E8B\u88AB\u770B\u89C1\u3002 */
2694
+ .dtok-btn{transition:transform .08s ease,background .15s ease,filter .15s ease,border-color .15s ease;}
2695
+ .dtok-btn.primary:not([disabled]):active{transform:scale(.95);}
2696
+ .dtok-btn.loading{position:relative;overflow:hidden;opacity:1;cursor:progress;border-color:var(--dk-accent);}
2697
+ .dtok-btn.loading::after{content:"";position:absolute;inset:0;border-radius:inherit;pointer-events:none;background:linear-gradient(100deg,transparent 18%,rgb(255 255 255 / .4) 50%,transparent 82%);transform:translateX(-130%);animation:dtok-btn-sheen 1.05s linear infinite;}
2698
+ @keyframes dtok-btn-sheen{to{transform:translateX(130%);}}
2699
+ .dtok-spin{display:inline-block;width:10px;height:10px;margin-right:5px;vertical-align:-1px;border:2px solid rgb(255 255 255 / .35);border-top-color:#fff;border-radius:50%;animation:dtok-rotate .7s linear infinite;}
2700
+ .dtok-spin.inline{width:9px;height:9px;margin:0;vertical-align:0;border-color:rgb(127 127 127 / .35);border-top-color:var(--dk-accent);}
2701
+ @keyframes dtok-rotate{to{transform:rotate(360deg);}}
2702
+ /* \u72B6\u6001\u884C\u91CC\u7684\u300C\u27F3 \u52A0\u8F7D\u4E2D...\u300D\uFF1A\u8F6C\u5708 + \u7701\u7565\u53F7\u4E00\u4E2A\u4E00\u4E2A\u8E66\u51FA\u6765\uFF0C\u6700\u7ECF\u5178\u7684\u90A3\u79CD\u52A0\u8F7D\u52A8\u753B */
2703
+ .dtok-busy{display:inline-flex;align-items:center;gap:5px;}
2704
+ .dtok-dots{display:inline-block;width:12px;text-align:left;}
2705
+ .dtok-dots i{font-style:normal;opacity:0;animation:dtok-dot 1.2s linear infinite;}
2706
+ .dtok-dots i:nth-child(2){animation-delay:.2s;}
2707
+ .dtok-dots i:nth-child(3){animation-delay:.4s;}
2708
+ @keyframes dtok-dot{0%{opacity:0;}25%,80%{opacity:1;}100%{opacity:0;}}
2709
+ .dtok-body-inner{display:flex;flex-direction:column;gap:8px;min-width:0;transition:opacity .25s ease;}
2710
+ .dtok-body-inner.busy{opacity:.45;pointer-events:none;}
2711
+ .dtok-loading{position:sticky;top:0;z-index:6;display:flex;align-items:center;flex-wrap:wrap;gap:10px 12px;padding:9px 14px;border-radius:12px;border:1px solid var(--dk-accent);background:linear-gradient(180deg,rgb(255 255 255 / .07),rgb(0 0 0 / .1)),var(--dsw-alias-bg-layer-2,#1c212b);box-shadow:0 10px 26px rgb(0 0 0 / .3);animation:dtok-load-in .3s ease;}
2712
+ @keyframes dtok-load-in{from{opacity:0;transform:translateY(-6px);}to{opacity:1;transform:none;}}
2713
+ .dtok-load-scene{position:relative;display:flex;align-items:flex-end;gap:1px;height:30px;flex:none;padding:0 6px;}
2714
+ .dtok-load-emoji{display:inline-block;font-size:19px;line-height:1;animation:dtok-hop 1.1s cubic-bezier(.36,.07,.19,.97) infinite;filter:drop-shadow(0 2px 3px rgb(0 0 0 / .3));}
2715
+ .dtok-load-emoji.e2{animation-delay:.15s;}
2716
+ .dtok-load-emoji.e3{animation-delay:.3s;}
2717
+ @keyframes dtok-hop{0%,100%{transform:translateY(0) rotate(-5deg);}35%{transform:translateY(-9px) rotate(2deg) scale(1.1);}65%{transform:translateY(0) rotate(5deg);}}
2718
+ .dtok-load-spark{position:absolute;font-size:11px;color:var(--dk-accent);animation:dtok-twinkle 1.4s ease-in-out infinite;}
2719
+ .dtok-load-spark.s1{left:-2px;top:-4px;}
2720
+ .dtok-load-spark.s2{right:-2px;top:2px;animation-delay:.6s;}
2721
+ @keyframes dtok-twinkle{0%,100%{opacity:0;transform:scale(.4) rotate(0);}45%{opacity:1;transform:scale(1.2) rotate(90deg);}}
2722
+ .dtok-load-msg{flex:1 1 170px;min-width:0;display:flex;flex-direction:column;gap:2px;}
2723
+ .dtok-load-msg b{font-size:13px;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;background:linear-gradient(90deg,var(--dsw-alias-label-primary),var(--dk-accent),var(--dsw-alias-label-primary));background-size:220% 100%;-webkit-background-clip:text;background-clip:text;color:transparent;animation:dtok-shine 2.6s linear infinite,dtok-msg-in .32s ease;}
2724
+ @keyframes dtok-shine{0%{background-position:130% 0;}100%{background-position:-130% 0;}}
2725
+ @keyframes dtok-msg-in{from{opacity:0;transform:translateY(4px);}to{opacity:1;transform:none;}}
2726
+ .dtok-load-sub{font-size:11px;color:var(--dsw-alias-label-secondary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
2727
+ .dtok-load-sub.wait{color:var(--dk-warn);}
2728
+ .dtok-load-track{position:relative;flex:0 1 140px;min-width:88px;height:6px;border-radius:999px;background:rgb(127 127 127 / .22);overflow:hidden;}
2729
+ .dtok-load-fill{position:absolute;top:0;bottom:0;width:38%;border-radius:999px;background:linear-gradient(90deg,transparent,var(--dk-accent),transparent);animation:dtok-sweep 1.15s cubic-bezier(.45,.05,.55,.95) infinite;}
2730
+ @keyframes dtok-sweep{0%{transform:translateX(-115%);}100%{transform:translateX(380%);}}
2731
+ .dtok-cards.dtok-pop .dtok-card{animation:dtok-card-in .5s cubic-bezier(.2,.9,.3,1.3) both;animation-delay:calc(var(--i,0) * 45ms);}
2732
+ @keyframes dtok-card-in{from{opacity:0;transform:translateY(7px) scale(.96);}to{opacity:1;transform:none;}}
2733
+ .dtok-updated{font-size:11px;color:var(--dsw-alias-state-success-primary,#4ade80);animation:dtok-updated 2.4s ease forwards;}
2734
+ @keyframes dtok-updated{0%{opacity:0;transform:translateY(-3px) scale(.85);}12%{opacity:1;transform:none;}70%{opacity:1;}100%{opacity:0;}}
2735
+ @media (prefers-reduced-motion:reduce){
2736
+ .dtok-load-emoji,.dtok-load-spark,.dtok-load-fill,.dtok-load-msg b,.dtok-btn.loading::after,.dtok-spin,.dtok-loading,.dtok-cards.dtok-pop .dtok-card{animation:none !important;}
2737
+ .dtok-dots i{animation:none !important;opacity:1;}
2738
+ .dtok-load-emoji{transform:none;}
2739
+ .dtok-body-inner.busy{opacity:.75;}
2740
+ .dtok-btn.primary:not([disabled]):active{transform:none;}
2741
+ }
2428
2742
  /* ---- \u5355\u4EF7\u8BBE\u7F6E\uFF08\u5B50\u5F39\u7A97\uFF09 ---- */
2429
2743
  .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
2744
  .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;}
@@ -2457,6 +2771,35 @@ var css = `
2457
2771
  .dtok-src{font-size:11px;color:var(--dsw-alias-label-tertiary,#94a3b8);}
2458
2772
  .dtok-src.custom{color:var(--dsw-alias-state-success-primary,#4ade80);}
2459
2773
  .dtok-src.fallback{color:var(--dk-warn);}
2774
+ /* ---- \u56FE\u8868\u7EDF\u8BA1\uFF08\u7EAF CSS/SVG\uFF09 ---- */
2775
+ .dtok-charts{display:flex;flex-direction:column;gap:10px;min-width:0;}
2776
+ .dtok-chart-toolbar{display:flex;align-items:center;gap:6px;flex-wrap:wrap;padding:6px 10px;border:1px solid var(--dsw-alias-border-l1);border-radius:10px;background:var(--dsw-alias-bg-layer-1);}
2777
+ .dtok-chart-toolbar-label{font-size:12px;color:var(--dsw-alias-label-secondary);flex:none;}
2778
+ .dtok-chart-toolbar-note{font-size:11px;color:var(--dsw-alias-label-tertiary,#94a3b8);margin-left:auto;}
2779
+ .dtok-chart-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(320px,1fr));gap:10px;}
2780
+ .dtok-chart-card{border:1px solid var(--dsw-alias-border-l1);border-radius:10px;background:var(--dsw-alias-bg-layer-1);padding:10px 12px;min-width:0;display:flex;flex-direction:column;gap:8px;}
2781
+ .dtok-chart-title{font-size:13px;font-weight:600;color:var(--dsw-alias-label-primary);display:flex;align-items:center;gap:8px;flex-wrap:wrap;}
2782
+ .dtok-chart-sub{font-size:11px;font-weight:400;color:var(--dsw-alias-label-secondary);}
2783
+ .dtok-chart-empty{text-align:center;color:var(--dsw-alias-label-secondary);padding:28px 0;font-size:12px;}
2784
+ .dtok-donut-wrap{display:flex;align-items:center;gap:14px;flex-wrap:wrap;}
2785
+ .dtok-donut{width:150px;height:150px;flex:none;}
2786
+ .dtok-donut-v{font-size:6.5px;font-weight:700;fill:var(--dsw-alias-label-primary);}
2787
+ .dtok-donut-l{font-size:3.6px;fill:var(--dsw-alias-label-secondary);}
2788
+ .dtok-legend{flex:1;min-width:150px;display:flex;flex-direction:column;gap:3px;max-height:170px;overflow:auto;}
2789
+ .dtok-legend-row{display:flex;align-items:center;gap:6px;font-size:11px;min-width:0;}
2790
+ .dtok-dot{width:8px;height:8px;border-radius:50%;flex:none;}
2791
+ .dtok-legend-k{color:var(--dsw-alias-label-primary);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;}
2792
+ .dtok-legend-v{color:var(--dsw-alias-label-secondary);margin-left:auto;flex:none;}
2793
+ .dtok-hbars{display:flex;flex-direction:column;gap:4px;max-height:220px;overflow:auto;}
2794
+ .dtok-hbar-row{display:flex;align-items:center;gap:8px;font-size:11px;min-width:0;}
2795
+ .dtok-hbar-k{flex:0 1 34%;color:var(--dsw-alias-label-primary);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
2796
+ .dtok-hbar-track{flex:1;height:12px;border-radius:4px;background:rgb(127 127 127 / .16);overflow:hidden;min-width:40px;}
2797
+ .dtok-hbar-fill{display:block;height:100%;border-radius:4px;transition:width .3s ease;}
2798
+ .dtok-hbar-v{flex:none;color:var(--dsw-alias-label-secondary);min-width:52px;text-align:right;}
2799
+ .dtok-hours{display:flex;align-items:flex-end;gap:3px;height:130px;padding-top:4px;}
2800
+ .dtok-hour-col{flex:1;display:flex;align-items:flex-end;height:100%;min-width:0;}
2801
+ .dtok-hour-bar{display:block;width:100%;border-radius:3px 3px 0 0;background:linear-gradient(180deg,var(--dk-accent),rgb(127 127 127 / .25));min-height:2px;transition:height .3s ease;}
2802
+ .dtok-hour-col:hover .dtok-hour-bar{filter:brightness(1.3);}
2460
2803
  `;
2461
2804
  function Detail({ rec, onClose, rate }) {
2462
2805
  const st = statusInfo(rec);
@@ -2821,6 +3164,39 @@ function PricingEditor({ onClose, onSaved, embedded }) {
2821
3164
  ] })
2822
3165
  ] });
2823
3166
  }
3167
+ var LOAD_MSGS = [
3168
+ "\u6B63\u5728\u7FFB\u5F00\u8D26\u672C",
3169
+ "\u6B63\u5728\u4E00\u679A\u4E00\u679A\u6570 Token",
3170
+ "\u6B63\u5728\u7ED9\u6BCF\u6761\u8C03\u7528\u8D34\u4EF7\u7B7E",
3171
+ "\u6B63\u5728\u6838\u5BF9\u7F13\u5B58\u547D\u4E2D\u7387",
3172
+ "\u6B63\u5728\u628A\u5355\u4EF7\u4E58\u8FDB\u6BCF\u4E00\u884C",
3173
+ "\u6B63\u5728\u628A\u8868\u683C\u7801\u6574\u9F50"
3174
+ ];
3175
+ function LoadingBanner() {
3176
+ const [tick, setTick] = (0, import_react2.useState)(0);
3177
+ (0, import_react2.useEffect)(() => {
3178
+ const timer = setInterval(() => setTick((t) => t + 1), 1300);
3179
+ return () => clearInterval(timer);
3180
+ }, []);
3181
+ const long = tick >= 4;
3182
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dtok-loading", role: "status", "aria-live": "polite", children: [
3183
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "dtok-load-scene", "aria-hidden": "true", children: [
3184
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dtok-load-spark s1", children: "\u2726" }),
3185
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dtok-load-emoji e1", children: "\u{1F9EE}" }),
3186
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dtok-load-emoji e2", children: "\u{1F4D2}" }),
3187
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dtok-load-emoji e3", children: "\u{1F50D}" }),
3188
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dtok-load-spark s2", children: "\u2726" })
3189
+ ] }),
3190
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "dtok-load-msg", children: [
3191
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("b", { children: [
3192
+ LOAD_MSGS[tick % LOAD_MSGS.length],
3193
+ "\u2026"
3194
+ ] }, tick),
3195
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dtok-load-sub" + (long ? " wait" : ""), children: long ? "\u6570\u636E\u8F83\u591A\uFF0C\u518D\u7B49\u4E00\u5C0F\u4F1A\u513F\uFF0C\u6B63\u5728\u5168\u529B\u6E05\u70B9\uFF5E" : "\u6B63\u5728\u6309\u5F53\u524D\u6761\u4EF6\u91CD\u65B0\u7EDF\u8BA1\uFF0C\u901A\u5E38\u51E0\u79D2\u5185\u5B8C\u6210" })
3196
+ ] }),
3197
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dtok-load-track", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dtok-load-fill" }) })
3198
+ ] });
3199
+ }
2824
3200
  function TokenLogView(props) {
2825
3201
  const navSession = props && props.params && props.params.sessionId ? props.params.sessionId : null;
2826
3202
  const navAt = props && props.params && props.params.navAt ? props.params.navAt : 0;
@@ -2841,6 +3217,20 @@ function TokenLogView(props) {
2841
3217
  const [page, setPage] = (0, import_react2.useState)(0);
2842
3218
  const [detailRec, setDetailRec] = (0, import_react2.useState)(null);
2843
3219
  const [showPricing, setShowPricing] = (0, import_react2.useState)(() => !!(props && props.params && props.params.openPricing));
3220
+ const [tab, setTab] = (0, import_react2.useState)(() => props && props.params && props.params.charts ? "charts" : "detail");
3221
+ const [fetchedAt, setFetchedAt] = (0, import_react2.useState)(0);
3222
+ const [justUpdated, setJustUpdated] = (0, import_react2.useState)(false);
3223
+ const wasLoadingRef = (0, import_react2.useRef)(false);
3224
+ (0, import_react2.useEffect)(() => {
3225
+ if (wasLoadingRef.current && !loading && !err) setFetchedAt(Date.now());
3226
+ wasLoadingRef.current = loading;
3227
+ }, [loading, err]);
3228
+ (0, import_react2.useEffect)(() => {
3229
+ if (!fetchedAt) return;
3230
+ setJustUpdated(true);
3231
+ const timer = setTimeout(() => setJustUpdated(false), 2400);
3232
+ return () => clearTimeout(timer);
3233
+ }, [fetchedAt]);
2844
3234
  const pageSize = 100;
2845
3235
  (0, import_react2.useEffect)(() => {
2846
3236
  saveFilters({ fromStr, toStr, provider, model, status, effort, sessionId, dim });
@@ -2906,6 +3296,7 @@ function TokenLogView(props) {
2906
3296
  setPage(0);
2907
3297
  }).catch((e) => setErr(String(e && e.message || e))).finally(() => setLoading(false));
2908
3298
  }, [navAt]);
3299
+ const rateCny = data && data.rateUsdCny || 7.2;
2909
3300
  const resetFilters = (0, import_react2.useCallback)(() => {
2910
3301
  setFromStr("");
2911
3302
  setToStr("");
@@ -2924,7 +3315,8 @@ function TokenLogView(props) {
2924
3315
  }, []);
2925
3316
  const exportCsv = (0, import_react2.useCallback)(() => {
2926
3317
  rpcCall("export", buildQ(false)).then((d) => {
2927
- const blob = new Blob([d.csv], { type: "text/csv;charset=utf-8" });
3318
+ const csv = d && Array.isArray(d.rows) ? buildExportCsv(d.rows, d && d.rateUsdCny || rateCny) : String(d && d.csv || "");
3319
+ const blob = new Blob(["\uFEFF" + csv], { type: "text/csv;charset=utf-8" });
2928
3320
  const url = URL.createObjectURL(blob);
2929
3321
  const a = document.createElement("a");
2930
3322
  a.href = url;
@@ -2932,7 +3324,7 @@ function TokenLogView(props) {
2932
3324
  a.click();
2933
3325
  URL.revokeObjectURL(url);
2934
3326
  }).catch((e) => setErr(String(e && e.message || e)));
2935
- }, [buildQ]);
3327
+ }, [buildQ, rateCny]);
2936
3328
  (0, import_react2.useEffect)(() => {
2937
3329
  const timer = setInterval(() => {
2938
3330
  rpcCall("query", buildQ(true)).then((d) => {
@@ -2970,7 +3362,6 @@ function TokenLogView(props) {
2970
3362
  const pageCount = Math.max(1, Math.ceil(sorted.length / pageSize));
2971
3363
  const pageSafe = Math.min(page, pageCount - 1);
2972
3364
  const pageRows = sorted.slice(pageSafe * pageSize, (pageSafe + 1) * pageSize);
2973
- const rateCny = data && data.rateUsdCny || 7.2;
2974
3365
  const cards = totals ? [
2975
3366
  { v: fmtNum(totals.calls), l: "\u8C03\u7528\u6B21\u6570" },
2976
3367
  { v: fmtCompact(totals.totalTokens), l: "\u603B Token" },
@@ -3034,86 +3425,108 @@ function TokenLogView(props) {
3034
3425
  "\u9519\u8BEF: ",
3035
3426
  err
3036
3427
  ] }, "err"));
3037
- if (cards.length) bodyNodes.push(
3038
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dtok-cards", children: cards.map((c) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dtok-card", children: [
3039
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "v", children: c.v }),
3040
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "l", children: c.l })
3041
- ] }, c.l)) }, "cards")
3042
- );
3043
- if (summaryRows.length) bodyNodes.push(
3044
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dtok-section-title", children: [
3045
- "\u7EDF\u8BA1\u5206\u7EC4: ",
3046
- dim || "\u65E0",
3047
- " (",
3048
- summaryRows.length,
3049
- " \u7EC4)"
3050
- ] }, "sum"),
3051
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dtok-table-wrap", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("table", { className: "dtok-table", children: [
3052
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("tr", { children: [
3053
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { children: "\u7EF4\u5EA6" }),
3054
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { children: "\u8C03\u7528" }),
3055
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { children: "\u8F93\u5165(\u547D\u4E2D)" }),
3056
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { children: "\u8F93\u5165(\u672A\u547D\u4E2D)" }),
3057
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { children: "\u547D\u4E2D\u7387" }),
3058
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { children: "\u8F93\u51FA" }),
3059
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { children: "\u603BToken" }),
3060
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { children: "\u91D1\u989D\uFF08\u4EBA\u6C11\u5E01\uFF09" }),
3061
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { children: "\u8017\u65F6" })
3062
- ] }) }),
3063
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("tbody", { children: summaryRows })
3064
- ] }) }, "sumtab")
3065
- );
3066
- bodyNodes.push(/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dtok-section-title", children: "\u6309\u7EF4\u5EA6\u7EDF\u8BA1" }, "dimtitle"));
3067
3428
  bodyNodes.push(
3068
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dtok-filter", style: { padding: "4px 0 0", border: "none", background: "transparent" }, children: ["", "provider", "model", "status", "effort"].map((d) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "dtok-btn" + (dim === d ? " primary" : ""), onClick: () => {
3069
- setDim(d);
3070
- runQuery(d);
3071
- }, children: d === "" ? "\u65E0\u5206\u7EC4" : d }, d)) }, "dimrow")
3429
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dtok-filter", style: { padding: "2px 0 0", border: "none", background: "transparent", gap: 4 }, children: [
3430
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "dtok-btn" + (tab === "detail" ? " primary" : ""), onClick: () => setTab("detail"), children: "\u660E\u7EC6" }),
3431
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "dtok-btn" + (tab === "charts" ? " primary" : ""), onClick: () => setTab("charts"), children: "\u56FE\u8868\u7EDF\u8BA1" })
3432
+ ] }, "tabs")
3072
3433
  );
3073
- bodyNodes.push(/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dtok-section-title", children: "\u8C03\u7528\u660E\u7EC6" }, "dettitle"));
3074
- if (sorted.length === 0) {
3075
- bodyNodes.push(/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dtok-empty", children: loading ? "\u52A0\u8F7D\u4E2D\u2026" : "\u65E0\u5339\u914D\u8BB0\u5F55" }, "empty"));
3076
- } else {
3077
- const pager = (key, cls) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dtok-pager " + cls, children: [
3078
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "dtok-btn", disabled: pageSafe <= 0, onClick: () => setPage(pageSafe - 1), children: "\u4E0A\u4E00\u9875" }),
3079
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
3080
- "\u7B2C ",
3081
- pageSafe + 1,
3082
- " / ",
3083
- pageCount,
3084
- " \u9875 \xB7 \u5171 ",
3085
- sorted.length,
3086
- " \u6761"
3087
- ] }),
3088
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "dtok-btn", disabled: pageSafe >= pageCount - 1, onClick: () => setPage(pageSafe + 1), children: "\u4E0B\u4E00\u9875" })
3089
- ] }, key);
3090
- bodyNodes.push(pager("pager-top", "top"));
3434
+ if (tab === "charts") {
3091
3435
  bodyNodes.push(
3436
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChartsPanel, { records, rate: rateCny }, "charts-" + fetchedAt)
3437
+ );
3438
+ } else {
3439
+ if (cards.length) bodyNodes.push(
3440
+ // key 带 fetchedAt:每次「手动查询 / 首次加载」拿到新数据时重建一次,KPI 卡重播回弹动画
3441
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dtok-cards" + (fetchedAt ? " dtok-pop" : ""), children: cards.map((c, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dtok-card", style: { "--i": String(i) }, children: [
3442
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "v", children: c.v }),
3443
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "l", children: c.l })
3444
+ ] }, c.l)) }, "cards-" + fetchedAt)
3445
+ );
3446
+ if (summaryRows.length) bodyNodes.push(
3447
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dtok-section-title", children: [
3448
+ "\u7EDF\u8BA1\u5206\u7EC4: ",
3449
+ dim || "\u65E0",
3450
+ " (",
3451
+ summaryRows.length,
3452
+ " \u7EC4)"
3453
+ ] }, "sum"),
3092
3454
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dtok-table-wrap", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("table", { className: "dtok-table", children: [
3093
3455
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("tr", { children: [
3094
- sortTh("\u65F6\u95F4", "time"),
3095
- sortTh("\u4F1A\u8BDDID", "sessionId"),
3096
- sortTh("\u63D0\u4F9B\u5546", "provider"),
3097
- sortTh("\u6A21\u578B", "model"),
3098
- sortTh("\u8F93\u5165(\u547D\u4E2D)", "cacheReadTokens"),
3099
- sortTh("\u8F93\u5165(\u672A\u547D\u4E2D)", "inputTokens"),
3100
- sortTh("\u547D\u4E2D%", "cacheHitPercent"),
3101
- sortTh("\u8F93\u51FA", "outputTokens"),
3102
- sortTh("\u63A8\u7406", "reasoningTokens"),
3103
- sortTh("\u603B\u989D", "totalTokens"),
3104
- sortTh("\u91D1\u989D\uFF08\u4EBA\u6C11\u5E01\uFF09", "cost"),
3105
- sortTh("\u5F3A\u5EA6", "effort"),
3106
- sortTh("\u72B6\u6001", "status"),
3107
- sortTh("\u8017\u65F6", "llmMs")
3456
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { children: "\u7EF4\u5EA6" }),
3457
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { children: "\u8C03\u7528" }),
3458
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { children: "\u8F93\u5165(\u547D\u4E2D)" }),
3459
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { children: "\u8F93\u5165(\u672A\u547D\u4E2D)" }),
3460
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { children: "\u547D\u4E2D\u7387" }),
3461
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { children: "\u8F93\u51FA" }),
3462
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { children: "\u603BToken" }),
3463
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { children: "\u91D1\u989D\uFF08\u4EBA\u6C11\u5E01\uFF09" }),
3464
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { children: "\u8017\u65F6" })
3108
3465
  ] }) }),
3109
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("tbody", { children: detailRows })
3110
- ] }) }, "detail")
3466
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("tbody", { children: summaryRows })
3467
+ ] }) }, "sumtab")
3111
3468
  );
3112
- bodyNodes.push(pager("pager-bottom", "bottom"));
3469
+ bodyNodes.push(/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dtok-section-title", children: "\u6309\u7EF4\u5EA6\u7EDF\u8BA1" }, "dimtitle"));
3470
+ bodyNodes.push(
3471
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dtok-filter", style: { padding: "4px 0 0", border: "none", background: "transparent" }, children: ["", "provider", "model", "status", "effort"].map((d) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "dtok-btn" + (dim === d ? " primary" : ""), onClick: () => {
3472
+ setDim(d);
3473
+ runQuery(d);
3474
+ }, children: d === "" ? "\u65E0\u5206\u7EC4" : d }, d)) }, "dimrow")
3475
+ );
3476
+ bodyNodes.push(/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dtok-section-title", children: "\u8C03\u7528\u660E\u7EC6" }, "dettitle"));
3477
+ if (sorted.length === 0) {
3478
+ bodyNodes.push(/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dtok-empty", children: loading ? "\u52A0\u8F7D\u4E2D\u2026" : "\u65E0\u5339\u914D\u8BB0\u5F55" }, "empty"));
3479
+ } else {
3480
+ const pager = (key, cls) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dtok-pager " + cls, children: [
3481
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "dtok-btn", disabled: pageSafe <= 0, onClick: () => setPage(pageSafe - 1), children: "\u4E0A\u4E00\u9875" }),
3482
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
3483
+ "\u7B2C ",
3484
+ pageSafe + 1,
3485
+ " / ",
3486
+ pageCount,
3487
+ " \u9875 \xB7 \u5171 ",
3488
+ sorted.length,
3489
+ " \u6761"
3490
+ ] }),
3491
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "dtok-btn", disabled: pageSafe >= pageCount - 1, onClick: () => setPage(pageSafe + 1), children: "\u4E0B\u4E00\u9875" })
3492
+ ] }, key);
3493
+ bodyNodes.push(pager("pager-top", "top"));
3494
+ bodyNodes.push(
3495
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dtok-table-wrap", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("table", { className: "dtok-table", children: [
3496
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("tr", { children: [
3497
+ sortTh("\u65F6\u95F4", "time"),
3498
+ sortTh("\u4F1A\u8BDDID", "sessionId"),
3499
+ sortTh("\u63D0\u4F9B\u5546", "provider"),
3500
+ sortTh("\u6A21\u578B", "model"),
3501
+ sortTh("\u8F93\u5165(\u547D\u4E2D)", "cacheReadTokens"),
3502
+ sortTh("\u8F93\u5165(\u672A\u547D\u4E2D)", "inputTokens"),
3503
+ sortTh("\u547D\u4E2D%", "cacheHitPercent"),
3504
+ sortTh("\u8F93\u51FA", "outputTokens"),
3505
+ sortTh("\u63A8\u7406", "reasoningTokens"),
3506
+ sortTh("\u603B\u989D", "totalTokens"),
3507
+ sortTh("\u91D1\u989D\uFF08\u4EBA\u6C11\u5E01\uFF09", "cost"),
3508
+ sortTh("\u5F3A\u5EA6", "effort"),
3509
+ sortTh("\u72B6\u6001", "status"),
3510
+ sortTh("\u8017\u65F6", "llmMs")
3511
+ ] }) }),
3512
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("tbody", { children: detailRows })
3513
+ ] }) }, "detail")
3514
+ );
3515
+ bodyNodes.push(pager("pager-bottom", "bottom"));
3516
+ }
3113
3517
  }
3114
3518
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dtok-root", onClick: (e) => e.stopPropagation(), children: [
3115
3519
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dtok-status", children: [
3116
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "count", children: loading ? "\u52A0\u8F7D\u4E2D\u2026" : data ? data.counts.matching + " / " + data.counts.total + " \u6761" : "" }),
3520
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "count", children: loading ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "dtok-busy", children: [
3521
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dtok-spin inline", "aria-hidden": "true" }),
3522
+ "\u52A0\u8F7D\u4E2D",
3523
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "dtok-dots", "aria-hidden": "true", children: [
3524
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("i", { children: "." }),
3525
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("i", { children: "." }),
3526
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("i", { children: "." })
3527
+ ] })
3528
+ ] }) : data ? data.counts.matching + " / " + data.counts.total + " \u6761" : "" }),
3529
+ !loading && justUpdated && !err ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dtok-updated", children: "\u2713 \u6570\u636E\u5DF2\u66F4\u65B0" }, fetchedAt) : null,
3117
3530
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: "\u5168\u5C4F\u8BF7\u7528\u9762\u677F\u53F3\u4E0A\u89D2\u300C\u6700\u5927\u5316\u300D\uFF1B\u9762\u677F\u6253\u5F00\u671F\u95F4\u6BCF 5 \u79D2\u81EA\u52A8\u5237\u65B0" })
3118
3531
  ] }),
3119
3532
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dtok-filter", children: [
@@ -3149,7 +3562,10 @@ function TokenLogView(props) {
3149
3562
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "", children: "\u5168\u90E8" }),
3150
3563
  opts(data && data.efforts)
3151
3564
  ] }),
3152
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "dtok-btn primary", onClick: runQuery, children: "\u67E5\u8BE2" }),
3565
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { className: "dtok-btn primary" + (loading ? " loading" : ""), disabled: loading, title: "\u6309\u5F53\u524D\u6761\u4EF6\u67E5\u8BE2", onClick: () => runQuery(), children: [
3566
+ loading ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dtok-spin", "aria-hidden": "true" }) : null,
3567
+ loading ? "\u67E5\u8BE2\u4E2D\u2026" : "\u67E5\u8BE2"
3568
+ ] }),
3153
3569
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "dtok-btn", onClick: resetFilters, children: "\u91CD\u7F6E" }),
3154
3570
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "dtok-btn", onClick: exportCsv, children: "\u5BFC\u51FA CSV" }),
3155
3571
  /* @__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" })
@@ -3161,7 +3577,10 @@ function TokenLogView(props) {
3161
3577
  " \xB7 \u5B98\u7F51\u81EA\u52A8\u540C\u6B65\uFF1A" + (data.pricingInfo.fetchOfficial ? "\u5F00" : "\u5173"),
3162
3578
  data.pricingInfo.sources && data.pricingInfo.sources.length ? " \xB7 \u672C\u9875\u6D89\u53CA\u6765\u6E90\uFF1A" + data.pricingInfo.sources.join("/") : ""
3163
3579
  ] }) : null }),
3164
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dtok-body", children: bodyNodes }),
3580
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dtok-body", children: [
3581
+ loading ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LoadingBanner, {}) : null,
3582
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dtok-body-inner" + (loading ? " busy" : ""), children: bodyNodes })
3583
+ ] }),
3165
3584
  detailRec ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Detail, { rec: detailRec, onClose: () => setDetailRec(null), rate: rateCny }) : null
3166
3585
  ] });
3167
3586
  }
@@ -10752,10 +11171,10 @@ function MobileRelayView() {
10752
11171
  function MobileRelayOverlay() {
10753
11172
  (0, import_react21.useEffect)(() => {
10754
11173
  if (typeof window === "undefined" || typeof document === "undefined") return void 0;
10755
- if (window.__dshDockMobileDrawer) return void 0;
11174
+ const hostPresent = !!(window.__dshDockMobileDrawer && document.querySelector(".dsh-mobile-drawer-btn"));
10756
11175
  const mq = window.matchMedia ? window.matchMedia("(max-width:700px)") : null;
10757
11176
  if (!mq) return void 0;
10758
- window.__dshDockMobileDrawer = "client";
11177
+ if (!hostPresent) window.__dshDockMobileDrawer = "client";
10759
11178
  const narrow = () => mq.matches;
10760
11179
  const frameEl = () => document.querySelector('[class$="frame"]');
10761
11180
  const sidebarCollapsed = () => {
@@ -10777,17 +11196,72 @@ function MobileRelayOverlay() {
10777
11196
  const c = d.querySelector('button[aria-label="\u5173\u95ED\u8BE6\u60C5"]');
10778
11197
  if (c) c.click();
10779
11198
  };
10780
- const fab = document.createElement("button");
10781
- fab.type = "button";
10782
- fab.className = "dsh-mobile-drawer-btn";
10783
- fab.setAttribute("aria-label", "\u6253\u5F00\u4F1A\u8BDD\u5217\u8868");
10784
- fab.innerHTML = '<svg viewBox="0 0 24 24"><path d="M4 7h16M4 12h16M4 17h10"/></svg>';
10785
- fab.addEventListener("click", expand);
10786
- const scrim = document.createElement("div");
10787
- scrim.className = "dsh-mobile-scrim";
10788
- document.body.appendChild(fab);
10789
- document.body.appendChild(scrim);
11199
+ let fab = null;
11200
+ let scrim = null;
11201
+ if (!hostPresent) {
11202
+ fab = document.createElement("button");
11203
+ fab.type = "button";
11204
+ fab.className = "dsh-mobile-drawer-btn";
11205
+ fab.setAttribute("aria-label", "\u6253\u5F00\u4F1A\u8BDD\u5217\u8868");
11206
+ fab.innerHTML = '<svg viewBox="0 0 24 24"><path d="M4 7h16M4 12h16M4 17h10"/></svg>';
11207
+ fab.addEventListener("click", expand);
11208
+ scrim = document.createElement("div");
11209
+ scrim.className = "dsh-mobile-scrim";
11210
+ document.body.appendChild(fab);
11211
+ document.body.appendChild(scrim);
11212
+ }
11213
+ const tabbar = document.createElement("nav");
11214
+ tabbar.className = "dsh-mobile-tabbar";
11215
+ tabbar.setAttribute("aria-label", "\u5E95\u90E8\u5BFC\u822A");
11216
+ const tabDefs = [
11217
+ {
11218
+ id: "sessions",
11219
+ label: "\u4F1A\u8BDD",
11220
+ icon: '<svg viewBox="0 0 24 24"><path d="M21 12a8 8 0 0 1-8 8H5l-2 2V12a8 8 0 0 1 8-8h2a8 8 0 0 1 8 8z"/></svg>',
11221
+ act: () => expand()
11222
+ },
11223
+ {
11224
+ id: "dock",
11225
+ label: "\u529F\u80FD\u575E",
11226
+ icon: '<svg viewBox="0 0 24 24"><rect x="4" y="4" width="7" height="7" rx="1.5"/><rect x="13" y="4" width="7" height="7" rx="1.5"/><rect x="4" y="13" width="7" height="7" rx="1.5"/><rect x="13" y="13" width="7" height="7" rx="1.5"/></svg>',
11227
+ act: () => {
11228
+ if (panelNav.open) setPanelOpen(false);
11229
+ else openPanel("home");
11230
+ }
11231
+ },
11232
+ {
11233
+ id: "settings",
11234
+ label: "\u8BBE\u7F6E",
11235
+ icon: '<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="3.2"/><path d="M19.4 15a1.7 1.7 0 0 0 .34 1.87l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06A1.7 1.7 0 0 0 15 19.4a1.7 1.7 0 0 0-1 1.55V21a2 2 0 1 1-4 0v-.09A1.7 1.7 0 0 0 9 19.4a1.7 1.7 0 0 0-1.87.34l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06A1.7 1.7 0 0 0 4.6 15a1.7 1.7 0 0 0-1.55-1H3a2 2 0 1 1 0-4h.09A1.7 1.7 0 0 0 4.6 9a1.7 1.7 0 0 0-.34-1.87l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06A1.7 1.7 0 0 0 9 4.6a1.7 1.7 0 0 0 1-1.55V3a2 2 0 1 1 4 0v.09a1.7 1.7 0 0 0 1 1.51 1.7 1.7 0 0 0 1.87-.34l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06A1.7 1.7 0 0 0 19.4 9c.23.6.86 1 1.51 1H21a2 2 0 1 1 0 4h-.09a1.7 1.7 0 0 0-1.51 1z"/></svg>',
11236
+ act: () => {
11237
+ const b = [...document.querySelectorAll("button")].find((x) => (x.getAttribute("aria-label") || "") === "\u8BBE\u7F6E" || (x.textContent || "").trim() === "\u8BBE\u7F6E");
11238
+ if (b) b.click();
11239
+ }
11240
+ }
11241
+ ];
11242
+ const tabButtons = tabDefs.map((def) => {
11243
+ const b = document.createElement("button");
11244
+ b.type = "button";
11245
+ b.className = "dsh-mobile-tab";
11246
+ b.dataset.tab = def.id;
11247
+ b.innerHTML = def.icon + "<span>" + def.label + "</span>";
11248
+ b.addEventListener("click", () => def.act());
11249
+ tabbar.appendChild(b);
11250
+ return b;
11251
+ });
11252
+ document.body.appendChild(tabbar);
11253
+ const syncTabs = () => {
11254
+ const expanded = !sidebarCollapsed();
11255
+ const settingsOpen = dialogOpen();
11256
+ for (const b of tabButtons) {
11257
+ const id = b.dataset.tab;
11258
+ const on = id === "sessions" && expanded || id === "dock" && panelNav.open || id === "settings" && settingsOpen;
11259
+ b.classList.toggle("on", on);
11260
+ }
11261
+ };
11262
+ const unsubPanel = subscribePanel(syncTabs);
10790
11263
  const place = () => {
11264
+ if (!fab) return;
10791
11265
  const vh = window.innerHeight;
10792
11266
  let top = null;
10793
11267
  const g = document.querySelector(".dgfab");
@@ -10805,10 +11279,14 @@ function MobileRelayOverlay() {
10805
11279
  const sync = () => {
10806
11280
  const on = narrow() && !!frameEl() && !dialogOpen();
10807
11281
  const collapsed = sidebarCollapsed();
10808
- const showFab = on && collapsed;
10809
- fab.style.display = showFab ? "" : "none";
10810
- if (showFab) place();
10811
- scrim.style.display = on && !collapsed ? "" : "none";
11282
+ if (fab) {
11283
+ const showFab = on && collapsed && !panelNav.open;
11284
+ fab.style.display = showFab ? "" : "none";
11285
+ if (showFab) place();
11286
+ }
11287
+ if (scrim) scrim.style.display = on && !collapsed ? "" : "none";
11288
+ tabbar.style.display = narrow() && !!frameEl() && !panelNav.open ? "" : "none";
11289
+ syncTabs();
10812
11290
  };
10813
11291
  sync();
10814
11292
  const observer = typeof MutationObserver !== "undefined" ? new MutationObserver(sync) : null;
@@ -10822,7 +11300,7 @@ function MobileRelayOverlay() {
10822
11300
  if (!narrow()) return;
10823
11301
  const t = e.target;
10824
11302
  if (!t || !t.closest) return;
10825
- if (t.closest(".dsh-mobile-drawer-btn")) return;
11303
+ if (t.closest(".dsh-mobile-drawer-btn") || t.closest(".dsh-mobile-tabbar")) return;
10826
11304
  const expanded = !!document.querySelector('button[aria-label="\u6536\u8D77\u4FA7\u8FB9\u680F"],button[aria-label="Collapse sidebar"]');
10827
11305
  const col = document.querySelector('[class*="sidebarCol"]');
10828
11306
  const inSidebar = col && col.contains(t);
@@ -10840,16 +11318,18 @@ function MobileRelayOverlay() {
10840
11318
  if (t.closest('[role="dialog"],[class*="dockm"],[class*="dgfab"],[class*="dgwin"],[class*="dgame"],[class*="detailsCol"]')) return;
10841
11319
  collapse();
10842
11320
  };
10843
- document.addEventListener("click", onClick, true);
11321
+ if (!hostPresent) document.addEventListener("click", onClick, true);
10844
11322
  return () => {
10845
- document.removeEventListener("click", onClick, true);
11323
+ if (!hostPresent) document.removeEventListener("click", onClick, true);
10846
11324
  if (observer) observer.disconnect();
10847
11325
  clearInterval(timer);
11326
+ unsubPanel();
10848
11327
  if (mq.removeEventListener) mq.removeEventListener("change", onMq);
10849
11328
  else if (mq.removeListener) mq.removeListener(onMq);
10850
11329
  window.removeEventListener("resize", place);
10851
- fab.remove();
10852
- scrim.remove();
11330
+ if (fab) fab.remove();
11331
+ if (scrim) scrim.remove();
11332
+ tabbar.remove();
10853
11333
  if (window.__dshDockMobileDrawer === "client") delete window.__dshDockMobileDrawer;
10854
11334
  };
10855
11335
  }, []);
@@ -10876,14 +11356,32 @@ var feature10 = {
10876
11356
  .dmr{--dmr-accent:var(--dk-accent,#2f6fed);--dmr-accent-soft:color-mix(in srgb,var(--dmr-accent) 13%,transparent);display:flex;flex-direction:column;gap:16px;max-width:720px;color:var(--dsw-alias-label-primary);font-size:13px;line-height:1.5}.dmr h3,.dmr h4,.dmr p{margin:0}.dmr h3{font-size:17px;line-height:1.25;letter-spacing:-.01em}.dmr h4{font-size:13px}.dmr-hero,.dmr-status-head{display:flex;align-items:flex-start;gap:12px}.dmr-hero>div,.dmr-status-head>div{min-width:0;display:flex;flex-direction:column;gap:4px}.dmr-hero p,.dmr-status-head p,.dmr-field small,.dmr-share small,.dmr-overview-card small,.dmr-task small,.dmr-note p{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:1.55}.dmr-hero-icon{width:42px;height:42px;display:inline-flex;align-items:center;justify-content:center;flex:none;border-radius:14px;background:var(--dmr-accent-soft);color:var(--dmr-accent);border:1px solid color-mix(in srgb,var(--dmr-accent) 35%,var(--dsw-alias-border-l1))}.dmr-steps{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px;margin:0;padding:0;list-style:none}.dmr-steps li{display:flex;gap:9px;padding:11px;border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-1);border-radius:12px}.dmr-steps li>span{width:21px;height:21px;display:inline-flex;align-items:center;justify-content:center;flex:none;border-radius:50%;font-size:11px;font-weight:700;color:var(--dmr-accent);background:var(--dmr-accent-soft)}.dmr-steps div{display:flex;flex-direction:column;gap:3px;min-width:0}.dmr-steps strong{font-size:12px}.dmr-steps small{font-size:11px;color:var(--dsw-alias-label-secondary);line-height:1.45}.dmr-field{display:flex;flex-direction:column;gap:6px;font-weight:600}.dmr-field input,.dmr-field select,.dmr-note textarea{box-sizing:border-box;width:100%;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);border-radius:10px;padding:10px 12px;min-height:44px;font:inherit;outline:none;transition:border-color .18s ease,box-shadow .18s ease}.dmr-field input:focus,.dmr-field select:focus,.dmr-note textarea:focus{border-color:var(--dmr-accent);box-shadow:0 0 0 3px var(--dmr-accent-soft)}.dmr-field small{font-weight:400}.dmr-network-grid{display:grid;grid-template-columns:minmax(0,1.4fr) minmax(150px,.6fr);gap:12px}.dmr-security{padding:12px 13px;border:1px solid color-mix(in srgb,var(--dk-warn) 48%,var(--dsw-alias-border-l1));border-radius:12px;background:color-mix(in srgb,var(--dk-warn) 9%,transparent)}.dmr-security strong{display:block;margin-bottom:4px;color:var(--dk-warn);font-size:12px}.dmr-security p{font-size:12px;color:var(--dsw-alias-label-secondary);line-height:1.55}.dmr-primary,.dmr-secondary,.dmr-text-button,.dmr-icon-button{font:inherit;touch-action:manipulation;cursor:pointer;transition:transform .15s ease,background .18s ease,border-color .18s ease,opacity .18s ease}.dmr-primary,.dmr-secondary{min-height:44px;display:inline-flex;align-items:center;justify-content:center;gap:8px;border-radius:10px;padding:9px 13px;font-weight:600}.dmr-primary{align-self:flex-start;border:1px solid var(--dmr-accent);background:var(--dmr-accent);color:#fff}.dmr-primary:hover{filter:brightness(1.05)}.dmr-secondary{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary)}.dmr-secondary:hover{border-color:var(--dmr-accent);background:var(--dmr-accent-soft)}.dmr-primary:active,.dmr-secondary:active,.dmr-icon-button:active{transform:scale(.98)}.dmr-primary:disabled,.dmr-secondary:disabled,.dmr-text-button:disabled,.dmr-icon-button:disabled{cursor:not-allowed;opacity:.5}.dmr-message{display:flex;align-items:flex-start;gap:7px;padding:10px 12px;border-radius:10px;font-size:12px}.dmr-message.success{color:var(--dsw-alias-state-success-primary);background:color-mix(in srgb,var(--dsw-alias-state-success-primary) 10%,transparent)}.dmr-message.error{color:var(--dsw-alias-state-error-primary);background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 10%,transparent)}.dmr-status-head{justify-content:space-between}.dmr-eyebrow{display:inline-flex;align-items:center;gap:6px;font-size:11px;font-weight:600;color:var(--dmr-accent)}.dmr-eyebrow i{width:7px;height:7px;border-radius:50%;background:currentColor;box-shadow:0 0 0 4px var(--dmr-accent-soft)}.dmr-icon-button{display:inline-flex;align-items:center;justify-content:center;flex:none;width:44px;height:44px;border:1px solid var(--dsw-alias-border-l2);border-radius:10px;background:transparent;color:var(--dsw-alias-label-secondary)}.dmr-icon-button:hover{color:var(--dsw-alias-state-error-primary);border-color:currentColor}.dmr-share{display:flex;flex-direction:column;gap:9px;padding:13px;border:1px solid color-mix(in srgb,var(--dmr-accent) 32%,var(--dsw-alias-border-l1));border-radius:13px;background:var(--dmr-accent-soft)}.dmr-share-layout{display:grid;grid-template-columns:196px minmax(0,1fr);gap:16px;align-items:center}.dmr-qr-card{box-sizing:border-box;display:flex;flex-direction:column;align-items:center;gap:5px;padding:10px;border-radius:12px;background:#fff;color:#111827;text-align:center}.dmr-qr-card img{display:block;width:176px;height:176px;max-width:100%;object-fit:contain}.dmr-qr-card strong{font-size:12px}.dmr-qr-card small{color:#4b5563;font-size:10px}.dmr-qr-loading{display:grid;place-items:center;width:176px;height:176px;color:#64748b;font-size:12px}.dmr-share-detail{min-width:0;display:flex;flex-direction:column;gap:10px}.dmr-share-detail>div:first-child{display:flex;flex-direction:column;gap:4px}.dmr-link{padding:10px 11px;border-radius:8px;background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l1);font:12px/1.4 ui-monospace,SFMono-Regular,Menlo,monospace;word-break:break-all;color:var(--dsw-alias-label-secondary)}.dmr-share-actions{display:flex;gap:8px;flex-wrap:wrap}.dmr-share-actions .dmr-primary{align-self:auto}.dmr-overview{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px}.dmr-overview-card{display:flex;flex-direction:column;gap:3px;padding:12px;border:1px solid var(--dsw-alias-border-l1);border-radius:12px;background:var(--dsw-alias-bg-layer-1)}.dmr-overview-card>span{font-size:11px;color:var(--dsw-alias-label-tertiary)}.dmr-overview-card strong{font-size:16px;letter-spacing:-.01em}.dmr-section{display:flex;flex-direction:column;gap:9px}.dmr-section-head{display:flex;align-items:center;justify-content:space-between;gap:10px}.dmr-section-head small{font-size:11px;color:var(--dsw-alias-label-tertiary)}.dmr-text-button{border:0;background:transparent;color:var(--dmr-accent);padding:8px;min-height:36px;font-weight:600}.dmr-task-list{display:flex;flex-direction:column;gap:8px}.dmr-task{display:flex;align-items:flex-start;gap:9px;padding:10px 11px;border:1px solid var(--dsw-alias-border-l1);border-radius:10px;background:var(--dsw-alias-bg-layer-1)}.dmr-task>div{display:flex;min-width:0;flex:1;flex-direction:column;gap:2px}.dmr-task strong{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dmr-phase{flex:none;border-radius:999px;padding:3px 8px;font-size:11px;background:var(--dmr-accent-soft);color:var(--dmr-accent)}.dmr-phase.write{color:var(--dsw-alias-state-success-primary)}.dmr-phase.code{color:var(--dk-warn)}.dmr-phase.search{color:#0d9488}.dmr-empty{padding:18px 12px;text-align:center;border:1px dashed var(--dsw-alias-border-l2);border-radius:10px;color:var(--dsw-alias-label-secondary);font-size:12px}.dmr-note{display:flex;flex-direction:column;gap:9px;padding-top:2px}.dmr-note blockquote{margin:0;padding:10px 12px;border-left:3px solid var(--dmr-accent);border-radius:0 9px 9px 0;background:var(--dmr-accent-soft);white-space:pre-wrap;font-size:12px}.dmr-note textarea{min-height:88px;resize:vertical;line-height:1.5}.dmr-note .dmr-secondary{align-self:flex-start}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}@media (max-width:680px){.dmr{gap:14px;font-size:14px}.dmr h3{font-size:18px}.dmr-steps{grid-template-columns:1fr;gap:8px}.dmr-steps li{padding:10px}.dmr-steps small,.dmr-hero p,.dmr-status-head p,.dmr-field small,.dmr-share small,.dmr-overview-card small,.dmr-task small,.dmr-note p{font-size:12px}.dmr-network-grid{grid-template-columns:1fr;gap:10px}.dmr-field input,.dmr-field select{font-size:16px}.dmr-primary,.dmr-secondary{width:100%;font-size:14px}.dmr-share-layout{grid-template-columns:1fr;gap:12px}.dmr-qr-card{width:min(240px,100%);margin:0 auto}.dmr-qr-card img,.dmr-qr-loading{width:210px;height:210px}.dmr-share-actions{flex-direction:column}.dmr-share-actions .dmr-primary{width:100%}.dmr-overview{gap:8px}.dmr-overview-card{padding:11px}.dmr-status-head{gap:8px}.dmr-link{font-size:11px}.dmr-note textarea{font-size:16px;min-height:104px}.dmr-note .dmr-secondary{align-self:stretch}.dmr-compact .dmr-hero-icon{width:40px;height:40px;border-radius:13px}}@media (prefers-reduced-motion:reduce){.dmr-primary,.dmr-secondary,.dmr-text-button,.dmr-icon-button,.dmr-field input,.dmr-field select,.dmr-note textarea{transition:none}}.dmr-lan{flex:none;margin-top:6px;padding-top:16px;border-top:1px solid var(--dsw-alias-border-l1)}.dmr-lan-head{display:flex;align-items:flex-start;justify-content:space-between;gap:12px}.dmr-lan-title{min-width:0;display:flex;flex-direction:column;gap:4px}.dmr-lan-title p{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:1.55}.dmr-lan-badge{flex:none;display:inline-flex;align-items:center;gap:6px;border-radius:999px;padding:5px 10px;font-size:11px;font-weight:700;border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-tertiary)}.dmr-lan-badge.on{border-color:color-mix(in srgb,var(--dmr-accent) 45%,transparent);background:var(--dmr-accent-soft);color:var(--dmr-accent)}.dmr-lan-active,.dmr-lan-idle{display:flex;flex-direction:column;gap:12px}.dmr-lan-active .dmr-share-detail small strong{color:var(--dsw-alias-state-error-primary)}.dmr-danger{border-color:color-mix(in srgb,var(--dsw-alias-state-error-primary) 45%,var(--dsw-alias-border-l2));color:var(--dsw-alias-state-error-primary)}.dmr-danger:hover{border-color:var(--dsw-alias-state-error-primary);background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 10%,transparent)}
10877
11357
  /* \u62BD\u5C49\u628A\u624B\uFF1A\u8D34\u5DE6\u8FB9\u7F18\uFF08\u540C\u8DA3\u5473\u6E38\u620F\u6D6E\u6807\u7684\u8FB9\u7F18\u5438\u9644\u8BED\u8A00\uFF09\uFF0Ctop \u7531 place() \u52A8\u6001\u8BBE\u5B9A\uFF1B
10878
11358
  * z \u5E8F 70 < \u906E\u7F69 75 < \u4FA7\u680F 80 < overlayLayer 90\uFF0C\u9762\u677F/\u5F39\u7A97\u6253\u5F00\u65F6\u81EA\u7136\u88AB\u76D6\u3002 */
10879
- @media (max-width:700px){.dsh-mobile-drawer-btn{position:fixed;left:0;top:38vh;z-index:70;box-sizing:border-box;width:30px;height:48px;padding:0;display:flex;align-items:center;justify-content:center;border:1px solid var(--dsw-alias-border-l1,rgba(127,139,161,.35));border-left:none;border-radius:0 12px 12px 0;background:color-mix(in srgb,var(--dsw-alias-bg-layer-2,#1c2230) 88%,transparent);backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);color:var(--dsw-alias-label-primary,#e6eaf2);box-shadow:4px 0 18px rgba(0,0,0,.28);cursor:pointer;touch-action:manipulation;-webkit-tap-highlight-color:transparent}.dsh-mobile-drawer-btn:active{transform:scale(.94)}.dsh-mobile-drawer-btn svg{width:18px;height:18px;fill:none;stroke:currentColor;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round}.dsh-mobile-scrim{position:fixed;inset:0;z-index:75;background:rgba(8,10,14,.45);backdrop-filter:blur(2px);-webkit-backdrop-filter:blur(2px);touch-action:none}}`,
11359
+ @media (max-width:700px){.dsh-mobile-drawer-btn{position:fixed;left:0;top:38vh;z-index:70;box-sizing:border-box;width:30px;height:48px;padding:0;display:flex;align-items:center;justify-content:center;border:1px solid var(--dsw-alias-border-l1,rgba(127,139,161,.35));border-left:none;border-radius:0 12px 12px 0;background:color-mix(in srgb,var(--dsw-alias-bg-layer-2,#1c2230) 88%,transparent);backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);color:var(--dsw-alias-label-primary,#e6eaf2);box-shadow:4px 0 18px rgba(0,0,0,.28);cursor:pointer;touch-action:manipulation;-webkit-tap-highlight-color:transparent}.dsh-mobile-drawer-btn:active{transform:scale(.94)}.dsh-mobile-drawer-btn svg{width:18px;height:18px;fill:none;stroke:currentColor;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round}.dsh-mobile-scrim{position:fixed;inset:0;z-index:75;background:rgba(8,10,14,.45);backdrop-filter:blur(2px);-webkit-backdrop-filter:blur(2px);touch-action:none}
11360
+ /* ---- \u4EFF ZCode \u624B\u673A\u7AEF\uFF1A\u5E95\u90E8 Tab \u680F + \u8F93\u5165\u533A\u5438\u9644\u5E95\u90E8 ---- */
11361
+ .dsh-mobile-tabbar{position:fixed;left:0;right:0;bottom:0;z-index:65;display:flex;align-items:stretch;justify-content:space-around;padding:6px 8px calc(8px + env(safe-area-inset-bottom,0px));background:color-mix(in srgb,var(--dsw-alias-bg-layer-2,#1c2230) 92%,transparent);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);border-top:1px solid var(--dsw-alias-border-l1,rgba(127,139,161,.25));touch-action:manipulation}
11362
+ .dsh-mobile-tab{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:3px;min-height:48px;padding:4px 0;border:0;background:transparent;color:var(--dsw-alias-label-tertiary,#8b95ab);font:600 11px/1.2 inherit;cursor:pointer;-webkit-tap-highlight-color:transparent;border-radius:10px}
11363
+ .dsh-mobile-tab svg{width:21px;height:21px;fill:none;stroke:currentColor;stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round}
11364
+ .dsh-mobile-tab.on{color:var(--dsw-alias-label-primary,#e6eaf2)}
11365
+ .dsh-mobile-tab.on svg{color:var(--dk-accent,#4d9fff)}
11366
+ .dsh-mobile-tab:active{transform:scale(.94)}
11367
+ /* \u8F93\u5165\u533A\u5438\u9644\u5230 Tab \u680F\u4E0A\u65B9\uFF1A\u5BBF\u4E3B\u628A\u6B22\u8FCE\u5185\u5BB9 justify-content:center \u5782\u76F4\u5C45\u4E2D\uFF0C
11368
+ * \u624B\u673A\u89C6\u53E3\u4E0B\u8F93\u5165\u5361\u7247\u60AC\u5728\u4E2D\u90E8\u3001\u4E0B\u65B9\u7559\u51FA\u5927\u7247\u7A7A\u767D\u3002\u6539\u4E3A flex-end \u8D34\u5E95\uFF0C\u5E76\u7ED9
11369
+ * \u6EDA\u52A8\u4F53\u7559\u51FA Tab \u680F\u9AD8\u5EA6\u7684\u5185\u8FB9\u8DDD\uFF0C\u907F\u514D\u5185\u5BB9\u88AB Tab \u680F\u906E\u6321\u3002 */
11370
+ [class*="scrollBody"]{justify-content:flex-end !important;padding-bottom:calc(72px + env(safe-area-inset-bottom,0px)) !important}
11371
+ [class*="scrollBody"]>[class*="composerSeat"]{padding-bottom:6px}
11372
+ /* \u6536\u8D77\u7684\u4FA7\u680F\u8F68\u9053\uFF08collapsed rail\uFF09\u5728\u7A84\u5C4F\u53EA\u5269 1px \u5360\u4F4D\uFF0C\u4F46\u8F68\u9053\u91CC\u7684\u6309\u94AE
11373
+ * \u4ECD\u4F1A\u6EA2\u51FA\u8D34\u5728\u5DE6\u8FB9\u7F18\uFF08\u529F\u80FD\u575E/\u8FDB\u5316/\u8BBE\u7F6E\u4E09\u679A\u9732\u51FA\u534A\u8FB9\u7684\u5706\u94AE\uFF09\u3002\u9690\u85CF\u6574\u5217\uFF0C
11374
+ * \u8FD9\u4E9B\u5165\u53E3\u5DF2\u7531\u5E95\u90E8 Tab \u680F\u63A5\u7BA1\uFF1B\u5C55\u5F00\u62BD\u5C49\uFF08z 80\uFF09\u4E0D\u53D7\u5F71\u54CD\u3002 */
11375
+ [class*="sidebarCol"]:has([class*="collapsed"]){visibility:hidden}
11376
+ /* \u62BD\u5C49\u628A\u624B\u4ECE\u5C4F\u5E55\u4E2D\u90E8\u632A\u5230 Tab \u680F\u4E0A\u65B9\u4E00\u70B9\uFF0C\u514D\u5F97\u5B64\u96F6\u96F6\u60AC\u5728\u534A\u7A7A */
11377
+ .dsh-mobile-drawer-btn{top:auto !important;bottom:calc(96px + env(safe-area-inset-bottom,0px))}}`,
10880
11378
  View: MobileRelayView,
10881
11379
  HomeStat: MobileRelayHomeStat,
10882
11380
  Overlay: MobileRelayOverlay
10883
11381
  };
10884
11382
 
10885
11383
  // src/client.jsx
10886
- var DOCK_VERSION = "0.11.2";
11384
+ var DOCK_VERSION = "0.11.3";
10887
11385
  var BUILTIN_FEATURES = [feature, feature2, feature3, feature4, feature5, feature6, feature7, feature8, feature9, feature10];
10888
11386
  var PLANNED_FEATURES = [];
10889
11387
  var PLANNED_NOTES = {};