dsh-dock 0.1.0 → 0.4.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/client.js CHANGED
@@ -1,115 +1,2154 @@
1
- // dsh-dock · Client 半部(浏览器 bundle,手写 lazy-CJS 工厂格式,无需构建器)
2
- // 面板挂载在 settings.section:设置 → 功能中枢
3
- // 每个功能是一个模块:FEATURES 注册表 + featureViews 视图组件 + 独立开关(内存态)。
4
- // planned: true 的功能为规划占位(见 README 路线图),接入后移除 planned 并提供视图即可。
1
+ /* dsh-dock client bundle — generated by scripts/build-client.mjs(源码:src/ + features/,勿手改本文件) */
5
2
  window.__ModuleLoader__.load({
6
3
  id: "dsh-dock",
7
4
  factory: (require) => {
8
5
  var module = { exports: {} };
9
6
  var exports = module.exports;
10
7
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
11
- let react = require("react");
8
+ var __create = Object.create;
9
+ var __defProp = Object.defineProperty;
10
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
11
+ var __getOwnPropNames = Object.getOwnPropertyNames;
12
+ var __getProtoOf = Object.getPrototypeOf;
13
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
14
+ var __export = (target, all) => {
15
+ for (var name in all)
16
+ __defProp(target, name, { get: all[name], enumerable: true });
17
+ };
18
+ var __copyProps = (to, from, except, desc) => {
19
+ if (from && typeof from === "object" || typeof from === "function") {
20
+ for (let key of __getOwnPropNames(from))
21
+ if (!__hasOwnProp.call(to, key) && key !== except)
22
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
23
+ }
24
+ return to;
25
+ };
26
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
27
+ // If the importer is in node compatibility mode or this is not an ESM
28
+ // file that has been converted to a CommonJS file using a Babel-
29
+ // compatible transform (i.e. "__esModule" has not been set), then set
30
+ // "default" to the CommonJS "module.exports" for node compatibility.
31
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
32
+ mod
33
+ ));
34
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
12
35
 
13
- const name = "dsh-dock";
14
- const inject = ["timer"];
36
+ // src/client.jsx
37
+ var client_exports = {};
38
+ __export(client_exports, {
39
+ apply: () => apply,
40
+ dockBridge: () => dockBridge,
41
+ inject: () => inject
42
+ });
43
+ module.exports = __toCommonJS(client_exports);
44
+ var import_react7 = __toESM(require("react"), 1);
15
45
 
16
- // ---- 功能注册表:新功能在这里加一条,featureViews 里加同名视图 ----
17
- const FEATURES = [
18
- { id: "heartbeat", name: "心跳监视", description: "示例功能:面板侧运行时长心跳(纯 Client)", defaultEnabled: true },
19
- { id: "theme", name: "主题信息", description: "示例功能:读取当前主题快照(纯 Client)", defaultEnabled: true },
20
- { id: "balance", name: "模型余额", description: "接入路线图 0.2.0:展示所有模型 Provider 账户余额", planned: true },
21
- { id: "tokenlog", name: "Token 用量记录", description: "接入路线图 0.3.0:记录全部 LLM API 调用并统计", planned: true },
22
- { id: "animation", name: "任务动画", description: "接入路线图 0.4.0:任务进度动画与通知", planned: true }
23
- ];
46
+ // src/shared.js
47
+ var import_react = __toESM(require("react"), 1);
48
+ var FeatureBoundary = class extends import_react.default.Component {
49
+ constructor(props) {
50
+ super(props);
51
+ this.state = { error: null };
52
+ }
53
+ static getDerivedStateFromError(error) {
54
+ return { error };
55
+ }
56
+ render() {
57
+ if (this.state.error) {
58
+ const msg = this.state.error && this.state.error.message ? this.state.error.message : String(this.state.error);
59
+ return import_react.default.createElement(
60
+ "div",
61
+ { className: "dockm-note dockm-err" },
62
+ "\u529F\u80FD\u89C6\u56FE\u6E32\u67D3\u51FA\u9519\uFF1A" + msg + "\uFF08\u8BE5\u529F\u80FD\u6765\u81EA\u5916\u90E8\u5305\uFF0C\u4E0D\u5F71\u54CD\u9762\u677F\u5176\u4ED6\u529F\u80FD\uFF09"
63
+ );
64
+ }
65
+ return this.props.children;
66
+ }
67
+ };
68
+ var featureState = /* @__PURE__ */ new Map();
69
+ var stateListeners = /* @__PURE__ */ new Set();
70
+ function notifyState() {
71
+ for (const fn of stateListeners) fn();
72
+ }
73
+ function initFeatureState(defs) {
74
+ for (const f of defs) {
75
+ if (!featureState.has(f.id)) featureState.set(f.id, { enabled: !f.planned && f.defaultEnabled !== false, error: null });
76
+ }
77
+ }
78
+ function stateOf(id) {
79
+ let st = featureState.get(id);
80
+ if (!st) {
81
+ st = { enabled: true, error: null };
82
+ featureState.set(id, st);
83
+ }
84
+ return st;
85
+ }
86
+ function toggleFeature(id) {
87
+ const st = stateOf(id);
88
+ st.enabled = !st.enabled;
89
+ notifyState();
90
+ }
91
+ function subscribeFeatureState(fn) {
92
+ stateListeners.add(fn);
93
+ return () => {
94
+ stateListeners.delete(fn);
95
+ };
96
+ }
97
+ var CHIP_STORE_KEY = "dsh-dock/chips/v1";
98
+ var chipVisible = { map: {} };
99
+ try {
100
+ if (typeof localStorage !== "undefined") {
101
+ const raw = localStorage.getItem(CHIP_STORE_KEY);
102
+ const obj = raw ? JSON.parse(raw) : null;
103
+ if (obj && typeof obj === "object") chipVisible.map = obj;
104
+ }
105
+ } catch {
106
+ }
107
+ function persistChipVisible() {
108
+ try {
109
+ if (typeof localStorage !== "undefined") localStorage.setItem(CHIP_STORE_KEY, JSON.stringify(chipVisible.map));
110
+ } catch {
111
+ }
112
+ }
113
+ function chipShown(id) {
114
+ return chipVisible.map[id] !== false;
115
+ }
116
+ function setChipShown(id, value) {
117
+ chipVisible.map[id] = value !== false;
118
+ persistChipVisible();
119
+ notifyState();
120
+ }
121
+ var panelNav = { open: false, active: "home", params: null };
122
+ var navListeners = /* @__PURE__ */ new Set();
123
+ function notifyNav() {
124
+ for (const fn of navListeners) fn();
125
+ }
126
+ function openPanel(active, params) {
127
+ panelNav.open = true;
128
+ panelNav.active = typeof active === "string" && active ? active : "home";
129
+ panelNav.params = params && typeof params === "object" ? params : null;
130
+ notifyNav();
131
+ }
132
+ function setPanelOpen(value) {
133
+ panelNav.open = !!value;
134
+ if (!value) panelNav.params = null;
135
+ notifyNav();
136
+ }
137
+ function navigatePanel(active) {
138
+ panelNav.active = typeof active === "string" && active ? active : "home";
139
+ notifyNav();
140
+ }
141
+ function subscribePanel(fn) {
142
+ navListeners.add(fn);
143
+ return () => {
144
+ navListeners.delete(fn);
145
+ };
146
+ }
24
147
 
25
- const CSS = [
26
- ".dock-root{display:flex;flex-direction:column;gap:12px;padding:4px 0;color:var(--dsw-alias-label-primary);font-size:13px;}",
27
- ".dock-intro{color:var(--dsw-alias-label-secondary);line-height:1.6;}",
28
- ".dock-card{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-1);border-radius:10px;padding:12px 14px;display:flex;flex-direction:column;gap:8px;}",
29
- ".dock-card-head{display:flex;align-items:center;gap:8px;}",
30
- ".dock-dot{width:8px;height:8px;border-radius:50%;background:var(--dsw-alias-label-secondary);flex:none;}",
31
- ".dock-dot.on{background:var(--dsw-alias-state-success-primary);}",
32
- ".dock-dot.err{background:var(--dsw-alias-state-error-primary);}",
33
- ".dock-name{font-weight:600;flex:none;}",
34
- ".dock-desc{color:var(--dsw-alias-label-secondary);flex:1;}",
35
- ".dock-switch{flex:none;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-secondary);border-radius:999px;padding:3px 10px;cursor:pointer;font-size:12px;}",
36
- ".dock-switch.on{color:var(--dsw-alias-state-success-primary);border-color:var(--dsw-alias-state-success-primary);}",
37
- ".dock-badge{flex:none;border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);border-radius:999px;padding:3px 10px;font-size:12px;}",
38
- ".dock-body{border-top:1px solid var(--dsw-alias-border-l1);padding-top:8px;color:var(--dsw-alias-label-secondary);display:flex;flex-direction:column;gap:4px;}"
39
- ].join("\n");
148
+ // features/tokenlog/view.jsx
149
+ var import_react2 = require("react");
150
+ var import_jsx_runtime = require("react/jsx-runtime");
151
+ function rpcCall(method, args) {
152
+ return fetch("/dsh-dock/tokenlog/" + method, {
153
+ method: "POST",
154
+ headers: { "content-type": "application/json" },
155
+ body: JSON.stringify(args === void 0 ? {} : args)
156
+ }).then(async (res) => {
157
+ const data = await res.json().catch(() => ({}));
158
+ if (res.ok && data && data.ok === true) return data.data;
159
+ if (res.status === 405 || res.status === 404) {
160
+ throw new Error("\u5BBF\u4E3B\u8FDB\u7A0B\u662F\u65E7\u7248\u672C\uFF08\u6CA1\u6709\u7528\u91CF\u8BB0\u5F55\u8DEF\u7531\uFF09\uFF0C\u91CD\u542F dsh web \u540E\u91CD\u8BD5");
161
+ }
162
+ if (data && data.ok === false) throw new Error(data.error && data.error.message || "HTTP " + res.status);
163
+ throw new Error("HTTP " + res.status + (data && data.error && data.error.message ? ": " + data.error.message : ""));
164
+ });
165
+ }
166
+ function fmtNum(n) {
167
+ return (Number(n) || 0).toLocaleString("en-US");
168
+ }
169
+ function fmtCompact(n) {
170
+ n = Number(n) || 0;
171
+ if (n >= 1e9) return (n / 1e9).toFixed(2) + "B";
172
+ if (n >= 1e6) return (n / 1e6).toFixed(2) + "M";
173
+ if (n >= 1e3) return (n / 1e3).toFixed(1) + "K";
174
+ return String(Math.round(n));
175
+ }
176
+ function fmtCost(n) {
177
+ const v = Number(n) || 0;
178
+ if (v >= 10) return "$" + v.toFixed(2);
179
+ if (v >= 0.01) return "$" + v.toFixed(4);
180
+ return "$" + v.toFixed(6);
181
+ }
182
+ function fmtCostCny(usd, rate) {
183
+ const v = (Number(usd) || 0) * (Number(rate) > 0 ? Number(rate) : 7.2);
184
+ if (v >= 1e4) return "\xA5" + (v / 1e4).toFixed(2) + "\u4E07";
185
+ if (v >= 100) return "\xA5" + v.toFixed(0);
186
+ return "\xA5" + v.toFixed(2);
187
+ }
188
+ function fmtDuration(ms) {
189
+ if (ms === null || ms === void 0 || isNaN(ms)) return "\u2014";
190
+ if (ms < 1e3) return Math.round(ms) + "ms";
191
+ if (ms < 6e4) return (ms / 1e3).toFixed(1) + "s";
192
+ return Math.floor(ms / 6e4) + "m" + Math.round(ms % 6e4 / 1e3) + "s";
193
+ }
194
+ function fmtTime(ts) {
195
+ if (!ts) return "";
196
+ const d = new Date(ts);
197
+ const p = (x) => String(x).padStart(2, "0");
198
+ return d.getFullYear() + "-" + p(d.getMonth() + 1) + "-" + p(d.getDate()) + " " + p(d.getHours()) + ":" + p(d.getMinutes()) + ":" + p(d.getSeconds());
199
+ }
200
+ var STORE_KEY = "dsh-dock/tokenlog/filters/v1";
201
+ var FILTER_FIELDS = ["fromStr", "toStr", "provider", "model", "status", "effort", "sessionId", "dim"];
202
+ function loadSavedFilters() {
203
+ try {
204
+ if (typeof localStorage === "undefined") return null;
205
+ const raw = localStorage.getItem(STORE_KEY);
206
+ if (!raw) return null;
207
+ const obj = JSON.parse(raw);
208
+ if (!obj || typeof obj !== "object") return null;
209
+ const out = {};
210
+ for (const k of FILTER_FIELDS) out[k] = typeof obj[k] === "string" ? obj[k] : "";
211
+ return out;
212
+ } catch (e) {
213
+ return null;
214
+ }
215
+ }
216
+ function saveFilters(f) {
217
+ try {
218
+ if (typeof localStorage === "undefined") return;
219
+ localStorage.setItem(STORE_KEY, JSON.stringify(f));
220
+ } catch (e) {
221
+ }
222
+ }
223
+ function shortId(sid) {
224
+ if (!sid) return "";
225
+ if (sid.length <= 16) return sid;
226
+ return sid.slice(0, 8) + "\u2026" + sid.slice(-6);
227
+ }
228
+ function statusInfo(r) {
229
+ if (r.status === "completed") return { code: 200, label: "200", cls: "ok", title: "\u6210\u529F" };
230
+ if (r.status === "max-tokens") return { code: 200, label: "200", cls: "warn", title: "\u5B8C\u6210(\u8FBE\u5230\u8F93\u51FA\u4E0A\u9650)" };
231
+ if (r.status === "error") return { code: r.statusCode || 500, label: String(r.statusCode || 500), cls: "err", title: r.errorMsg || r.errorCode || "\u8C03\u7528\u5931\u8D25" };
232
+ if (r.status === "aborted") return { code: 499, label: "499", cls: "warn", title: "\u5DF2\u53D6\u6D88" };
233
+ if (r.status === "blocked") return { code: 403, label: "403", cls: "warn", title: "\u5DF2\u963B\u6B62" };
234
+ if (r.status === "interrupted") return { code: 500, label: "500", cls: "warn", title: "\u4E2D\u65AD" };
235
+ return { code: 0, label: "\u2026", cls: "pend", title: "\u8FDB\u884C\u4E2D" };
236
+ }
237
+ var css = `
238
+ .dtok-root{display:flex;flex-direction:column;gap:8px;width:100%;min-width:0;}
239
+ .dtok-status{display:flex;align-items:center;gap:10px;font-size:12px;color:var(--dsw-alias-label-secondary);flex-wrap:wrap;}
240
+ .dtok-status .count{color:var(--dsw-alias-label-primary);}
241
+ .dtok-filter{display:flex;align-items:center;gap:6px;flex-wrap:wrap;padding:8px 10px;border:1px solid var(--dsw-alias-border-l1);border-radius:10px;background:var(--dsw-alias-bg-layer-1);}
242
+ .dtok-filter label{font-size:12px;color:var(--dsw-alias-label-secondary);white-space:nowrap;flex:none;}
243
+ .dtok-input,.dtok-select{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 8px;font-size:12px;font-family:inherit;max-width:170px;}
244
+ .dtok-input[type="datetime-local"]{width:158px;}
245
+ .dtok-btn{cursor:pointer;border-radius:6px;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);padding:4px 12px;font-size:12px;font-family:inherit;flex:none;}
246
+ .dtok-btn:hover{background:var(--dsw-alias-interactive-bg-hover);}
247
+ .dtok-btn[disabled]{opacity:.5;cursor:default;}
248
+ .dtok-btn.primary{background:var(--dsw-alias-accent,#2f6fed);border-color:var(--dsw-alias-accent,#2f6fed);color:#fff;}
249
+ .dtok-btn.primary:hover{filter:brightness(1.1);}
250
+ .dtok-body{display:flex;flex-direction:column;gap:8px;min-width:0;}
251
+ .dtok-cards{display:flex;gap:8px;flex-wrap:wrap;}
252
+ .dtok-card{background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l1);border-radius:10px;padding:8px 14px;min-width:112px;}
253
+ .dtok-card .v{font-size:18px;font-weight:700;color:var(--dsw-alias-label-primary);}
254
+ .dtok-card .l{font-size:11px;color:var(--dsw-alias-label-secondary);margin-top:2px;}
255
+ .dtok-section-title{font-size:13px;font-weight:600;margin:6px 0 0;color:var(--dsw-alias-label-primary);}
256
+ .dtok-table-wrap{overflow:auto;border:1px solid var(--dsw-alias-border-l1);border-radius:10px;max-width:100%;}
257
+ .dtok-table{border-collapse:collapse;width:100%;font-size:12px;white-space:nowrap;color:var(--dsw-alias-label-primary);}
258
+ .dtok-table th{background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-secondary);text-align:left;padding:7px 10px;position:sticky;top:0;z-index:1;border-bottom:1px solid var(--dsw-alias-border-l1);font-weight:600;user-select:none;cursor:pointer;}
259
+ .dtok-table th:hover{color:var(--dsw-alias-label-primary);}
260
+ .dtok-table td{padding:6px 10px;border-bottom:1px solid rgba(122,132,152,0.12);}
261
+ .dtok-table tr:hover td{background:rgba(80,110,180,0.10);}
262
+ .dtok-empty{text-align:center;color:var(--dsw-alias-label-secondary);padding:32px 0;font-size:13px;}
263
+ .dtok-sid{color:var(--dsw-alias-accent,#7ab8ff);cursor:pointer;text-decoration:underline dotted;}
264
+ .dtok-sid:hover{filter:brightness(1.25);}
265
+ .dtok-code{font-weight:700;font-family:ui-monospace,monospace;}
266
+ .dtok-code.ok{color:var(--dsw-alias-state-success-primary,#4ade80);}
267
+ .dtok-code.warn{color:var(--dsw-alias-state-warning-primary,#fbbf24);}
268
+ .dtok-code.err{color:var(--dsw-alias-state-error-primary,#f87171);}
269
+ .dtok-code.pend{color:var(--dsw-alias-label-tertiary,#94a3b8);}
270
+ .dtok-detail-link{color:var(--dsw-alias-accent,#7ab8ff);cursor:pointer;font-size:11px;}
271
+ .dtok-detail-link:hover{text-decoration:underline;}
272
+ .dtok-detail{position:fixed;inset:0;z-index:10000;background:rgba(0,0,0,0.5);display:flex;align-items:center;justify-content:center;}
273
+ .dtok-detail-card{background:var(--dsw-alias-bg-layer-2,#1c212b);border:1px solid var(--dsw-alias-border-l2,#3a4150);border-radius:12px;padding:20px 24px;max-width:640px;width:92%;max-height:80vh;overflow:auto;color:var(--dsw-alias-label-primary,#e8eaf0);}
274
+ .dtok-detail-card h3{margin:0 0 12px;font-size:15px;}
275
+ .dtok-detail-row{display:flex;gap:8px;padding:4px 0;font-size:12px;border-bottom:1px solid rgba(122,132,152,0.1);}
276
+ .dtok-detail-row .k{color:var(--dsw-alias-label-secondary,#9aa3b5);min-width:110px;flex-shrink:0;}
277
+ .dtok-detail-row .v{word-break:break-all;}
278
+ .dtok-sort-mark{opacity:0.6;margin-left:3px;}
279
+ .dtok-pager{display:flex;gap:8px;align-items:center;font-size:12px;color:var(--dsw-alias-label-secondary);}
280
+ .dtok-pager.top{margin:0;}
281
+ .dtok-pager.bottom{margin:0;}
282
+ .dtok-err{color:var(--dsw-alias-state-error-primary,#ff7a7a);font-size:12px;}
283
+ `;
284
+ function Detail({ rec, onClose, rate }) {
285
+ const st = statusInfo(rec);
286
+ const rateCny = Number(rate) > 0 ? Number(rate) : 7.2;
287
+ const rows = [
288
+ ["\u4F1A\u8BDD ID", rec.sessionId],
289
+ ["\u65F6\u95F4", fmtTime(rec.time)],
290
+ ["\u63D0\u4F9B\u5546", rec.provider],
291
+ ["\u6A21\u578B", rec.model],
292
+ ["\u72B6\u6001\u7801", st.label + " (" + st.title + ")"],
293
+ ["\u9519\u8BEF\u4FE1\u606F", rec.errorMsg || "\u2014"],
294
+ ["\u9519\u8BEF\u7801", rec.errorCode || "\u2014"],
295
+ ["Request ID", rec.requestId || "\u2014"],
296
+ ["\u8F93\u5165 Token(\u672A\u7F13\u5B58)", fmtNum(rec.inputTokens)],
297
+ ["\u8F93\u51FA Token", fmtNum(rec.outputTokens)],
298
+ ["\u7F13\u5B58\u547D\u4E2D Token", fmtNum(rec.cacheReadTokens)],
299
+ ["\u7F13\u5B58\u5199\u5165 Token", fmtNum(rec.cacheWriteTokens)],
300
+ ["\u63A8\u7406 Token", fmtNum(rec.reasoningTokens)],
301
+ ["\u8BA1\u8D39\u8F93\u5165", fmtNum(rec.billedInput)],
302
+ ["\u7F13\u5B58\u547D\u4E2D\u7387", rec.cacheHitPercent + "%"],
303
+ ["\u603B Token", fmtNum(rec.totalTokens)],
304
+ ["\u6D88\u8017\u91D1\u989D(\u4F30\u7B97)", fmtCost(rec.cost) + " \u2248 " + fmtCostCny(rec.cost, rateCny)],
305
+ ["\u63A8\u7406\u5F3A\u5EA6", rec.effort || "\u2014"],
306
+ ["\u8017\u65F6", fmtDuration(rec.llmMs)],
307
+ ["Turn / Step", rec.turn + " / " + rec.step]
308
+ ];
309
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dtok-detail", onClick: onClose, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dtok-detail-card", onClick: (e) => e.stopPropagation(), children: [
310
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", { children: "\u8C03\u7528\u8BE6\u60C5" }),
311
+ rows.map(([k, v]) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dtok-detail-row", children: [
312
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "k", children: k }),
313
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "v", children: v })
314
+ ] }, k)),
315
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "dtok-btn", style: { marginTop: 12 }, onClick: onClose, children: "\u5173\u95ED" })
316
+ ] }) });
317
+ }
318
+ function TokenLogView(props) {
319
+ const navSession = props && props.params && props.params.sessionId ? props.params.sessionId : null;
320
+ const [savedFilters] = (0, import_react2.useState)(loadSavedFilters);
321
+ const [fromStr, setFromStr] = (0, import_react2.useState)(() => savedFilters && savedFilters.fromStr || "");
322
+ const [toStr, setToStr] = (0, import_react2.useState)(() => savedFilters && savedFilters.toStr || "");
323
+ const [provider, setProvider] = (0, import_react2.useState)(() => savedFilters && savedFilters.provider || "");
324
+ const [model, setModel] = (0, import_react2.useState)(() => savedFilters && savedFilters.model || "");
325
+ const [status, setStatus] = (0, import_react2.useState)(() => savedFilters && savedFilters.status || "");
326
+ const [effort, setEffort] = (0, import_react2.useState)(() => savedFilters && savedFilters.effort || "");
327
+ const [sessionId, setSessionId] = (0, import_react2.useState)(() => savedFilters && savedFilters.sessionId || "");
328
+ const [dim, setDim] = (0, import_react2.useState)(() => savedFilters && savedFilters.dim || "");
329
+ const [sortKey, setSortKey] = (0, import_react2.useState)("time");
330
+ const [sortDir, setSortDir] = (0, import_react2.useState)("desc");
331
+ const [data, setData] = (0, import_react2.useState)(null);
332
+ const [loading, setLoading] = (0, import_react2.useState)(false);
333
+ const [err, setErr] = (0, import_react2.useState)("");
334
+ const [page, setPage] = (0, import_react2.useState)(0);
335
+ const [detailRec, setDetailRec] = (0, import_react2.useState)(null);
336
+ const pageSize = 100;
337
+ (0, import_react2.useEffect)(() => {
338
+ saveFilters({ fromStr, toStr, provider, model, status, effort, sessionId, dim });
339
+ }, [fromStr, toStr, provider, model, status, effort, sessionId, dim]);
340
+ (0, import_react2.useEffect)(() => {
341
+ let cancel = false;
342
+ setLoading(true);
343
+ setErr("");
344
+ const q = {};
345
+ if (savedFilters) {
346
+ if (savedFilters.fromStr) q.from = new Date(savedFilters.fromStr).getTime();
347
+ if (savedFilters.toStr) q.to = new Date(savedFilters.toStr).getTime();
348
+ if (savedFilters.provider) q.provider = savedFilters.provider;
349
+ if (savedFilters.model) q.model = savedFilters.model;
350
+ if (savedFilters.status) q.status = savedFilters.status;
351
+ if (savedFilters.effort) q.effort = savedFilters.effort;
352
+ if (savedFilters.sessionId) q.sessionId = savedFilters.sessionId;
353
+ if (savedFilters.dim) q.dim = savedFilters.dim;
354
+ }
355
+ rpcCall("scan", {}).then(() => cancel ? null : rpcCall("query", q)).then((d) => {
356
+ if (cancel) return;
357
+ setData(d);
358
+ }).catch((e) => {
359
+ if (!cancel) setErr(String(e && e.message || e));
360
+ }).finally(() => {
361
+ if (!cancel) setLoading(false);
362
+ });
363
+ return () => {
364
+ cancel = true;
365
+ };
366
+ }, [savedFilters]);
367
+ const buildQ = (0, import_react2.useCallback)((withDim, dimOverride) => {
368
+ const q = {};
369
+ if (fromStr) q.from = new Date(fromStr).getTime();
370
+ if (toStr) q.to = new Date(toStr).getTime();
371
+ if (provider) q.provider = provider;
372
+ if (model) q.model = model;
373
+ if (status) q.status = status;
374
+ if (effort) q.effort = effort;
375
+ if (sessionId) q.sessionId = sessionId;
376
+ const dimNow = dimOverride !== void 0 ? dimOverride : dim;
377
+ if (withDim && dimNow) q.dim = dimNow;
378
+ return q;
379
+ }, [fromStr, toStr, provider, model, status, effort, sessionId, dim]);
380
+ const runQuery = (0, import_react2.useCallback)((dimOverride) => {
381
+ setLoading(true);
382
+ setErr("");
383
+ rpcCall("query", buildQ(true, dimOverride)).then((d) => {
384
+ setData(d);
385
+ setPage(0);
386
+ }).catch((e) => setErr(String(e && e.message || e))).finally(() => setLoading(false));
387
+ }, [buildQ]);
388
+ (0, import_react2.useEffect)(() => {
389
+ if (!navSession) return;
390
+ setSessionId(navSession);
391
+ setLoading(true);
392
+ setErr("");
393
+ rpcCall("query", Object.assign({}, buildQ(true), { sessionId: navSession })).then((d) => {
394
+ setData(d);
395
+ setPage(0);
396
+ }).catch((e) => setErr(String(e && e.message || e))).finally(() => setLoading(false));
397
+ }, [navSession]);
398
+ const resetFilters = (0, import_react2.useCallback)(() => {
399
+ setFromStr("");
400
+ setToStr("");
401
+ setProvider("");
402
+ setModel("");
403
+ setStatus("");
404
+ setEffort("");
405
+ setSessionId("");
406
+ setDim("");
407
+ setLoading(true);
408
+ setErr("");
409
+ rpcCall("query", {}).then((d) => {
410
+ setData(d);
411
+ setPage(0);
412
+ }).catch((e) => setErr(String(e && e.message || e))).finally(() => setLoading(false));
413
+ }, []);
414
+ const exportCsv = (0, import_react2.useCallback)(() => {
415
+ rpcCall("export", buildQ(false)).then((d) => {
416
+ const blob = new Blob([d.csv], { type: "text/csv;charset=utf-8" });
417
+ const url = URL.createObjectURL(blob);
418
+ const a = document.createElement("a");
419
+ a.href = url;
420
+ a.download = "dsh-dock-tokenlog-" + Date.now() + ".csv";
421
+ a.click();
422
+ URL.revokeObjectURL(url);
423
+ }).catch((e) => setErr(String(e && e.message || e)));
424
+ }, [buildQ]);
425
+ (0, import_react2.useEffect)(() => {
426
+ const timer = setInterval(() => {
427
+ rpcCall("query", buildQ(true)).then((d) => {
428
+ setData(d);
429
+ setErr("");
430
+ }).catch(() => {
431
+ });
432
+ }, 5e3);
433
+ return () => clearInterval(timer);
434
+ }, [buildQ]);
435
+ const toggleSort = (0, import_react2.useCallback)((key) => {
436
+ if (sortKey === key) setSortDir(sortDir === "asc" ? "desc" : "asc");
437
+ else {
438
+ setSortKey(key);
439
+ setSortDir("asc");
440
+ }
441
+ }, [sortKey, sortDir]);
442
+ const records = data && data.records || [];
443
+ const totals = data && data.totals;
444
+ const sessionIds = data && data.sessionIds || [];
445
+ const sorted = (0, import_react2.useMemo)(() => {
446
+ const arr = records.slice();
447
+ const key = sortKey;
448
+ const dir = sortDir === "asc" ? 1 : -1;
449
+ arr.sort((a, b) => {
450
+ const av = a[key];
451
+ const bv = b[key];
452
+ if (av === null || av === void 0 || av === "") return 1;
453
+ if (bv === null || bv === void 0 || bv === "") return -1;
454
+ if (typeof av === "string") return av.localeCompare(String(bv)) * dir;
455
+ return (Number(av) - Number(bv)) * dir;
456
+ });
457
+ return arr;
458
+ }, [records, sortKey, sortDir]);
459
+ const pageCount = Math.max(1, Math.ceil(sorted.length / pageSize));
460
+ const pageSafe = Math.min(page, pageCount - 1);
461
+ const pageRows = sorted.slice(pageSafe * pageSize, (pageSafe + 1) * pageSize);
462
+ const rateCny = data && data.rateUsdCny || 7.2;
463
+ const cards = totals ? [
464
+ { v: fmtNum(totals.calls), l: "\u8C03\u7528\u6B21\u6570" },
465
+ { v: fmtCompact(totals.totalTokens), l: "\u603B Token" },
466
+ { v: fmtCompact(totals.inputTokens), l: "\u8F93\u5165(\u672A\u7F13\u5B58)" },
467
+ { v: fmtCompact(totals.cacheReadTokens), l: "\u7F13\u5B58\u547D\u4E2D" },
468
+ { v: totals.cacheHitPct + "%", l: "\u7F13\u5B58\u547D\u4E2D\u7387" },
469
+ { v: fmtCompact(totals.outputTokens), l: "\u8F93\u51FA" },
470
+ { v: fmtCost(totals.cost), l: "\u6D88\u8017\u91D1\u989D(\u4F30\u7B97)" },
471
+ { v: fmtCostCny(totals.cost, rateCny), l: "\u6D88\u8017\u91D1\u989D(\xA5)" },
472
+ { v: fmtDuration(totals.llmMs), l: "\u7D2F\u8BA1\u8017\u65F6" + (totals.timed ? " (" + totals.timed + "\u6B65)" : "") }
473
+ ] : [];
474
+ const opts = (arr) => (arr || []).map((x) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: x, children: x || "(\u7A7A)" }, x || "(none)"));
475
+ const modelChoices = provider && data && data.modelsByProvider ? data.modelsByProvider[provider] || [] : data && data.models || [];
476
+ const summaryRows = (data && data.summary || []).map((r) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("tr", { children: [
477
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { children: r.key }),
478
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { children: fmtNum(r.calls) }),
479
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { children: fmtCompact(r.inputTokens) }),
480
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { children: fmtCompact(r.cacheReadTokens) }),
481
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { children: r.cacheHitPct + "%" }),
482
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { children: fmtCompact(r.outputTokens) }),
483
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { children: fmtCompact(r.totalTokens) }),
484
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { children: fmtCost(r.cost) }),
485
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { children: fmtCostCny(r.cost, rateCny) }),
486
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { children: fmtDuration(r.llmMs) })
487
+ ] }, r.key));
488
+ const sortTh = (label, key) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("th", { onClick: () => toggleSort(key), title: "\u70B9\u51FB\u6392\u5E8F", children: [
489
+ label,
490
+ sortKey === key ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dtok-sort-mark", children: sortDir === "asc" ? "\u25B2" : "\u25BC" }) : null
491
+ ] });
492
+ const detailRows = pageRows.map((r) => {
493
+ const st = statusInfo(r);
494
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("tr", { children: [
495
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { children: fmtTime(r.time) }),
496
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dtok-sid", title: "\u70B9\u51FB\u6309\u6B64\u4F1A\u8BDD\u7B5B\u9009", onClick: () => {
497
+ setSessionId(r.sessionId);
498
+ runQuery();
499
+ }, children: shortId(r.sessionId) }) }),
500
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { children: r.provider || "\u2014" }),
501
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { children: r.model || "\u2014" }),
502
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { children: fmtNum(r.inputTokens) }),
503
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { children: fmtNum(r.cacheReadTokens) }),
504
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { children: r.cacheHitPercent + "%" }),
505
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { children: fmtNum(r.outputTokens) }),
506
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { children: fmtNum(r.reasoningTokens) }),
507
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { children: fmtNum(r.totalTokens) }),
508
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { children: fmtCost(r.cost) }),
509
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { children: fmtCostCny(r.cost, rateCny) }),
510
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { children: r.effort || "\u2014" }),
511
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("td", { children: [
512
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dtok-code " + st.cls, children: st.label }) }),
513
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("a", { className: "dtok-detail-link", onClick: () => setDetailRec(r), children: "\u67E5\u770B\u8BE6\u60C5" }) })
514
+ ] }),
515
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { children: fmtDuration(r.llmMs) })
516
+ ] }, r.id);
517
+ });
518
+ const bodyNodes = [];
519
+ if (err) bodyNodes.push(/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dtok-err", children: [
520
+ "\u9519\u8BEF: ",
521
+ err
522
+ ] }, "err"));
523
+ if (cards.length) bodyNodes.push(
524
+ /* @__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: [
525
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "v", children: c.v }),
526
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "l", children: c.l })
527
+ ] }, c.l)) }, "cards")
528
+ );
529
+ if (summaryRows.length) bodyNodes.push(
530
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dtok-section-title", children: [
531
+ "\u7EDF\u8BA1\u5206\u7EC4: ",
532
+ dim || "\u65E0",
533
+ " (",
534
+ summaryRows.length,
535
+ " \u7EC4)"
536
+ ] }, "sum"),
537
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dtok-table-wrap", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("table", { className: "dtok-table", children: [
538
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("tr", { children: [
539
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { children: "\u7EF4\u5EA6" }),
540
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { children: "\u8C03\u7528" }),
541
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { children: "\u8F93\u5165" }),
542
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { children: "\u7F13\u5B58" }),
543
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { children: "\u547D\u4E2D\u7387" }),
544
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { children: "\u8F93\u51FA" }),
545
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { children: "\u603BToken" }),
546
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { children: "\u91D1\u989D" }),
547
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { children: "\u91D1\u989D(\xA5)" }),
548
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { children: "\u8017\u65F6" })
549
+ ] }) }),
550
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("tbody", { children: summaryRows })
551
+ ] }) }, "sumtab")
552
+ );
553
+ bodyNodes.push(/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dtok-section-title", children: "\u6309\u7EF4\u5EA6\u7EDF\u8BA1" }, "dimtitle"));
554
+ bodyNodes.push(
555
+ /* @__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: () => {
556
+ setDim(d);
557
+ runQuery(d);
558
+ }, children: d === "" ? "\u65E0\u5206\u7EC4" : d }, d)) }, "dimrow")
559
+ );
560
+ bodyNodes.push(/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dtok-section-title", children: "\u8C03\u7528\u660E\u7EC6" }, "dettitle"));
561
+ if (sorted.length === 0) {
562
+ bodyNodes.push(/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dtok-empty", children: loading ? "\u52A0\u8F7D\u4E2D\u2026" : "\u65E0\u5339\u914D\u8BB0\u5F55" }, "empty"));
563
+ } else {
564
+ const pager = (key, cls) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dtok-pager " + cls, children: [
565
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "dtok-btn", disabled: pageSafe <= 0, onClick: () => setPage(pageSafe - 1), children: "\u4E0A\u4E00\u9875" }),
566
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
567
+ "\u7B2C ",
568
+ pageSafe + 1,
569
+ " / ",
570
+ pageCount,
571
+ " \u9875 \xB7 \u5171 ",
572
+ sorted.length,
573
+ " \u6761"
574
+ ] }),
575
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "dtok-btn", disabled: pageSafe >= pageCount - 1, onClick: () => setPage(pageSafe + 1), children: "\u4E0B\u4E00\u9875" })
576
+ ] }, key);
577
+ bodyNodes.push(pager("pager-top", "top"));
578
+ bodyNodes.push(
579
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dtok-table-wrap", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("table", { className: "dtok-table", children: [
580
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("tr", { children: [
581
+ sortTh("\u65F6\u95F4", "time"),
582
+ sortTh("\u4F1A\u8BDDID", "sessionId"),
583
+ sortTh("\u63D0\u4F9B\u5546", "provider"),
584
+ sortTh("\u6A21\u578B", "model"),
585
+ sortTh("\u8F93\u5165", "inputTokens"),
586
+ sortTh("\u7F13\u5B58", "cacheReadTokens"),
587
+ sortTh("\u547D\u4E2D%", "cacheHitPercent"),
588
+ sortTh("\u8F93\u51FA", "outputTokens"),
589
+ sortTh("\u63A8\u7406", "reasoningTokens"),
590
+ sortTh("\u603B\u989D", "totalTokens"),
591
+ sortTh("\u91D1\u989D", "cost"),
592
+ sortTh("\u91D1\u989D(\xA5)", "cost"),
593
+ sortTh("\u5F3A\u5EA6", "effort"),
594
+ sortTh("\u72B6\u6001", "status"),
595
+ sortTh("\u8017\u65F6", "llmMs")
596
+ ] }) }),
597
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("tbody", { children: detailRows })
598
+ ] }) }, "detail")
599
+ );
600
+ bodyNodes.push(pager("pager-bottom", "bottom"));
601
+ }
602
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dtok-root", onClick: (e) => e.stopPropagation(), children: [
603
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dtok-status", children: [
604
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "count", children: loading ? "\u52A0\u8F7D\u4E2D\u2026" : data ? data.counts.matching + " / " + data.counts.total + " \u6761" : "" }),
605
+ /* @__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" })
606
+ ] }),
607
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dtok-filter", children: [
608
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { children: "\u8D77" }),
609
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { className: "dtok-input", type: "datetime-local", step: "1", value: fromStr, onChange: (e) => setFromStr(e.target.value) }),
610
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { children: "\u6B62" }),
611
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { className: "dtok-input", type: "datetime-local", step: "1", value: toStr, onChange: (e) => setToStr(e.target.value) }),
612
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { children: "\u4F1A\u8BDD" }),
613
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("select", { className: "dtok-select", value: sessionId, onChange: (e) => setSessionId(e.target.value), children: [
614
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "", children: "\u5168\u90E8" }),
615
+ opts(sessionIds)
616
+ ] }),
617
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { children: "\u63D0\u4F9B\u5546" }),
618
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("select", { className: "dtok-select", value: provider, onChange: (e) => {
619
+ setProvider(e.target.value);
620
+ setModel("");
621
+ }, children: [
622
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "", children: "\u5168\u90E8" }),
623
+ opts(data && data.providers)
624
+ ] }),
625
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { children: "\u6A21\u578B" }),
626
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("select", { className: "dtok-select", value: model, onChange: (e) => setModel(e.target.value), children: [
627
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "", children: "\u5168\u90E8" }),
628
+ opts(modelChoices)
629
+ ] }),
630
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { children: "\u72B6\u6001" }),
631
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("select", { className: "dtok-select", value: status, onChange: (e) => setStatus(e.target.value), children: [
632
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "", children: "\u5168\u90E8" }),
633
+ opts(data && data.statuses)
634
+ ] }),
635
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { children: "\u63A8\u7406\u5F3A\u5EA6" }),
636
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("select", { className: "dtok-select", value: effort, onChange: (e) => setEffort(e.target.value), children: [
637
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "", children: "\u5168\u90E8" }),
638
+ opts(data && data.efforts)
639
+ ] }),
640
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "dtok-btn primary", onClick: runQuery, children: "\u67E5\u8BE2" }),
641
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "dtok-btn", onClick: resetFilters, children: "\u91CD\u7F6E" }),
642
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "dtok-btn", onClick: exportCsv, children: "\u5BFC\u51FA CSV" })
643
+ ] }),
644
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dtok-body", children: bodyNodes }),
645
+ detailRec ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Detail, { rec: detailRec, onClose: () => setDetailRec(null), rate: rateCny }) : null
646
+ ] });
647
+ }
648
+ function todayStart() {
649
+ const d = /* @__PURE__ */ new Date();
650
+ d.setHours(0, 0, 0, 0);
651
+ return d.getTime();
652
+ }
653
+ function TokenLogHomeStat() {
654
+ const [snap, setSnap] = (0, import_react2.useState)({ totals: null, rate: 7.2, err: "" });
655
+ (0, import_react2.useEffect)(() => {
656
+ let cancel = false;
657
+ const load = () => rpcCall("query", { from: todayStart() }).then((d) => {
658
+ if (!cancel) setSnap({ totals: d && d.totals, rate: d && d.rateUsdCny || 7.2, err: "" });
659
+ }).catch((e) => {
660
+ if (!cancel) setSnap((s) => ({ totals: s.totals, rate: s.rate, err: String(e && e.message || e) }));
661
+ });
662
+ load();
663
+ const timer = setInterval(load, 6e4);
664
+ return () => {
665
+ cancel = true;
666
+ clearInterval(timer);
667
+ };
668
+ }, []);
669
+ if (!snap.totals) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: snap.err ? "\u7528\u91CF\u67E5\u8BE2\u5931\u8D25\uFF08\u70B9\u51FB\u8FDB\u5165\u67E5\u770B\uFF09" : "\u6B63\u5728\u62C9\u53D6\u4ECA\u65E5\u7528\u91CF\u2026" });
670
+ const t = snap.totals;
671
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
672
+ "\u4ECA\u65E5 ",
673
+ fmtNum(t.calls),
674
+ " \u6B21\u8C03\u7528 \xB7 ",
675
+ fmtCompact(t.totalTokens),
676
+ " Token \xB7 ",
677
+ fmtCost(t.cost),
678
+ "\uFF08",
679
+ fmtCostCny(t.cost, snap.rate),
680
+ "\uFF09"
681
+ ] });
682
+ }
683
+ function TokenLogChip(props) {
684
+ const sid = props && props.sessionId || props && props.session && props.session.sessionId || null;
685
+ const [snap, setSnap] = (0, import_react2.useState)({ totals: null, err: "" });
686
+ (0, import_react2.useEffect)(() => {
687
+ if (!sid) return;
688
+ let cancel = false;
689
+ const load = () => rpcCall("query", { sessionId: sid }).then((d) => {
690
+ if (!cancel) setSnap({ totals: d && d.totals, err: "" });
691
+ }).catch((e) => {
692
+ if (!cancel) setSnap((s) => ({ totals: s.totals, err: String(e && e.message || e) }));
693
+ });
694
+ load();
695
+ const timer = setInterval(load, 1e4);
696
+ return () => {
697
+ cancel = true;
698
+ clearInterval(timer);
699
+ };
700
+ }, [sid]);
701
+ if (!sid) return null;
702
+ const t = snap.totals;
703
+ const label = snap.err && !t ? snap.err.includes("\u91CD\u542F") ? "\u7528\u91CF\xB7\u9700\u91CD\u542F\u5BBF\u4E3B" : "\u7528\u91CF\xB7\u5931\u8D25" : t ? "\u26C1 " + fmtCompact(t.totalTokens) + (t.cost > 0 ? " \xB7 " + fmtCost(t.cost) : "") : "\u26C1 \u2026";
704
+ const title = t ? "\u672C\u4F1A\u8BDD " + fmtNum(t.calls) + " \u6B21\u8C03\u7528 \xB7 " + fmtNum(t.totalTokens) + " Token \xB7 " + fmtCost(t.cost) + "\uFF08\u4F30\u7B97\uFF09\n\u70B9\u51FB\u5728\u529F\u80FD\u575E\u67E5\u770B\u7528\u91CF\u8BB0\u5F55" : (snap.err ? snap.err + "\n" : "") + "\u70B9\u51FB\u5728\u529F\u80FD\u575E\u67E5\u770B\u7528\u91CF\u8BB0\u5F55";
705
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
706
+ "button",
707
+ {
708
+ type: "button",
709
+ className: "dockchip" + (snap.err && !t ? " err" : ""),
710
+ title,
711
+ "aria-label": "\u4F1A\u8BDD\u7528\u91CF",
712
+ onClick: () => openPanel("tokenlog", { sessionId: sid }),
713
+ children: [
714
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dockchip-dot", style: { background: "#fbbf24" } }),
715
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: label })
716
+ ]
717
+ }
718
+ );
719
+ }
720
+ var feature = {
721
+ id: "tokenlog",
722
+ name: "\u7528\u91CF\u8BB0\u5F55",
723
+ order: 110,
724
+ accent: "#fbbf24",
725
+ 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",
726
+ css,
727
+ View: TokenLogView,
728
+ HomeStat: TokenLogHomeStat,
729
+ Chip: TokenLogChip
730
+ };
40
731
 
41
- function apply(ctx) {
42
- const slots = ctx.get("slots");
43
- if (slots === undefined) return;
732
+ // features/modelconfig/view.js
733
+ var import_react3 = __toESM(require("react"), 1);
734
+ var modelsStore = {
735
+ snap: { data: null, loading: false, error: null },
736
+ listeners: /* @__PURE__ */ new Set(),
737
+ subscribe(fn) {
738
+ this.listeners.add(fn);
739
+ return () => {
740
+ this.listeners.delete(fn);
741
+ };
742
+ },
743
+ set(patch) {
744
+ this.snap = Object.assign({}, this.snap, patch);
745
+ for (const fn of this.listeners) fn();
746
+ },
747
+ load() {
748
+ if (this.snap.loading) return;
749
+ this.set({ loading: true });
750
+ fetch("/dsh-dock/models", { signal: AbortSignal.timeout(2e4) }).then((res) => res.ok ? res.json() : Promise.reject(new Error("\u6A21\u578B\u76EE\u5F55\u63A5\u53E3 HTTP " + res.status))).then((data) => this.set({ data, loading: false, error: null })).catch((e) => this.set({ loading: false, error: e && e.message || String(e) }));
751
+ }
752
+ };
753
+ function useModels() {
754
+ const [snap, setSnap] = import_react3.default.useState(modelsStore.snap);
755
+ import_react3.default.useEffect(() => {
756
+ const off = modelsStore.subscribe(() => setSnap(modelsStore.snap));
757
+ if (!modelsStore.snap.data && !modelsStore.snap.loading) modelsStore.load();
758
+ return off;
759
+ }, []);
760
+ return snap;
761
+ }
762
+ var DKM_MODALITIES = [["text", "\u6587\u672C"], ["image", "\u56FE\u7247"]];
763
+ var DKM_TAGS = [["video", "\u89C6\u9891"], ["audio", "\u97F3\u9891"], ["document", "\u6587\u6863"]];
764
+ var DKM_PI_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh", "max"];
765
+ var DKM_DS_LEVELS = ["off", "low", "high", "max"];
766
+ var DKM_EFFORT_NAMES = { off: "\u5173\u95ED", minimal: "\u6781\u4F4E", low: "\u4F4E", medium: "\u4E2D", high: "\u9AD8", xhigh: "\u8D85\u9AD8", max: "\u6700\u9AD8" };
767
+ function draftFromProvider(p) {
768
+ return {
769
+ id: p.id,
770
+ kind: p.kind,
771
+ thinking: p.thinking === "disabled" ? "disabled" : "enabled",
772
+ defaultEffort: DKM_DS_LEVELS.indexOf(p.defaultEffort) >= 0 ? p.defaultEffort : "high",
773
+ models: (p.models || []).map((m) => {
774
+ const levels = {};
775
+ if (m.efforts === "custom" && m.effortMap && typeof m.effortMap === "object") {
776
+ for (const lv of DKM_PI_LEVELS) levels[lv] = m.effortMap[lv] !== void 0;
777
+ }
778
+ return {
779
+ id: m.id || "",
780
+ name: m.name || "",
781
+ contextWindow: m.contextWindow != null ? String(m.contextWindow) : "",
782
+ maxTokens: m.maxTokens != null ? String(m.maxTokens) : "",
783
+ input: m.input && m.input.length ? m.input.slice() : ["text"],
784
+ tags: (m.tags || []).slice(),
785
+ effortsMode: m.efforts === "custom" ? "custom" : m.efforts === "off" ? "off" : "inherit",
786
+ effortLevels: levels,
787
+ raw: m.raw && typeof m.raw === "object" ? m.raw : null
788
+ };
789
+ })
790
+ };
791
+ }
792
+ function ModelsView() {
793
+ const snap = useModels();
794
+ const data = snap.data;
795
+ const providers = data && Array.isArray(data.providers) ? data.providers : [];
796
+ const [selId, setSelId] = import_react3.default.useState(null);
797
+ const [draft, setDraft] = import_react3.default.useState(null);
798
+ const [builtKey, setBuiltKey] = import_react3.default.useState(null);
799
+ const [dirty, setDirty] = import_react3.default.useState(false);
800
+ const [saving, setSaving] = import_react3.default.useState(false);
801
+ const [msg, setMsg] = import_react3.default.useState(null);
802
+ const [vpDraft, setVpDraft] = import_react3.default.useState(null);
803
+ const [vpBuilt, setVpBuilt] = import_react3.default.useState(null);
804
+ const [vpDirty, setVpDirty] = import_react3.default.useState(false);
805
+ const [vpSaving, setVpSaving] = import_react3.default.useState(false);
806
+ const [vpMsg, setVpMsg] = import_react3.default.useState(null);
807
+ const cur = providers.length > 0 ? providers.find((p) => p.id === selId) || providers[0] : null;
808
+ const wantKey = cur ? cur.id + ":" + String(data && data.generatedAt || 0) : null;
809
+ if (cur && builtKey !== wantKey) {
810
+ setBuiltKey(wantKey);
811
+ setDraft(draftFromProvider(cur));
812
+ setDirty(false);
813
+ }
814
+ const vpWantKey = data ? String(data.generatedAt) : null;
815
+ if (data && vpBuilt !== vpWantKey) {
816
+ setVpBuilt(vpWantKey);
817
+ const vp = data.visionProxy || {};
818
+ setVpDraft({ enabled: !!vp.enabled, provider: String(vp.provider || ""), model: String(vp.model || "") });
819
+ setVpDirty(false);
820
+ }
821
+ const saveVp = () => {
822
+ if (!vpDraft || vpSaving) return;
823
+ setVpSaving(true);
824
+ fetch("/dsh-dock/models", {
825
+ method: "POST",
826
+ headers: { "content-type": "application/json" },
827
+ body: JSON.stringify({ visionProxy: vpDraft, revisions: data && data.revisions || {} }),
828
+ signal: AbortSignal.timeout(2e4)
829
+ }).then((res) => res.json().then((b2) => ({ status: res.status, body: b2 })).catch(() => ({ status: res.status, body: {} }))).then((r2) => {
830
+ if (r2.status === 200 && r2.body && r2.body.ok) {
831
+ setVpMsg({ ok: true, text: "\u56FE\u7247\u4EE3\u7406\u914D\u7F6E\u5DF2\u4FDD\u5B58\uFF0C\u5373\u65F6\u751F\u6548" });
832
+ modelsStore.load();
833
+ } else {
834
+ setVpMsg({ ok: false, text: r2.body && r2.body.error || "HTTP " + r2.status });
835
+ }
836
+ }).catch((e) => setVpMsg({ ok: false, text: e && e.message || String(e) })).then(() => setVpSaving(false));
837
+ };
838
+ const patchDraft = (patch) => {
839
+ setDraft(Object.assign({}, draft, patch));
840
+ setDirty(true);
841
+ setMsg(null);
842
+ };
843
+ const patchModel = (i, patch) => {
844
+ const models = draft.models.slice();
845
+ models[i] = Object.assign({}, models[i], patch);
846
+ patchDraft({ models });
847
+ };
848
+ const toggleIn = (list, v) => list.indexOf(v) >= 0 ? list.filter((x) => x !== v) : list.concat([v]);
849
+ const ALL_INPUT = DKM_MODALITIES.map((x) => x[0]);
850
+ const ALL_TAGS = DKM_TAGS.map((x) => x[0]);
851
+ const allInputs = (on) => ({ input: on ? ALL_INPUT.slice() : [], tags: on ? ALL_TAGS.slice() : [] });
852
+ const allLevels = (on) => {
853
+ const lv = {};
854
+ for (const k of DKM_PI_LEVELS) lv[k] = !!on;
855
+ return { effortsMode: "custom", effortLevels: lv };
856
+ };
857
+ const bulkModels = (fn) => patchDraft({ models: draft.models.map((m) => Object.assign({}, m, fn(m))) });
858
+ const selectProvider = (id) => {
859
+ setSelId(id);
860
+ setMsg(null);
861
+ };
862
+ const save = () => {
863
+ if (!draft || !cur || !dirty || saving) return;
864
+ const models = draft.models.map((m) => ({
865
+ id: String(m.id || "").trim(),
866
+ name: String(m.name || "").trim(),
867
+ contextWindow: m.contextWindow === "" ? void 0 : Number(m.contextWindow),
868
+ maxTokens: m.maxTokens === "" ? void 0 : Number(m.maxTokens),
869
+ input: m.input,
870
+ tags: m.tags,
871
+ effortsMode: m.effortsMode,
872
+ effortLevels: m.effortLevels,
873
+ raw: m.raw
874
+ }));
875
+ const payload = { provider: cur.id, revisions: data && data.revisions || {}, models };
876
+ if (cur.kind === "deepseek") {
877
+ payload.thinking = draft.thinking;
878
+ payload.defaultEffort = draft.defaultEffort;
879
+ }
880
+ setSaving(true);
881
+ fetch("/dsh-dock/models", {
882
+ method: "POST",
883
+ headers: { "content-type": "application/json" },
884
+ body: JSON.stringify(payload),
885
+ signal: AbortSignal.timeout(2e4)
886
+ }).then((res) => res.json().then((body2) => ({ status: res.status, body: body2 })).catch(() => ({ status: res.status, body: {} }))).then((r2) => {
887
+ if (r2.status === 200 && r2.body && r2.body.ok) {
888
+ setMsg({ ok: true, text: "\u5DF2\u5199\u56DE\u5B98\u65B9\u914D\u7F6E\uFF1B\u91CD\u65B0\u6253\u5F00\u4F1A\u8BDD\u6A21\u578B\u9009\u62E9\u5668\u5373\u53EF\u9009\u62E9\u65B0\u6A21\u578B/\u5F3A\u5EA6\u6863" });
889
+ modelsStore.load();
890
+ } else {
891
+ setMsg({ ok: false, text: r2.body && r2.body.error || "HTTP " + r2.status });
892
+ }
893
+ }).catch((e) => setMsg({ ok: false, text: e && e.message || String(e) })).then(() => setSaving(false));
894
+ };
895
+ const modelRow = (m, i) => import_react3.default.createElement(
896
+ "div",
897
+ { key: i, className: "dkm-model" },
898
+ import_react3.default.createElement(
899
+ "div",
900
+ { className: "dkm-model-head" },
901
+ import_react3.default.createElement("span", { className: "dkm-model-idx" }, "#" + (i + 1)),
902
+ import_react3.default.createElement("button", {
903
+ type: "button",
904
+ className: "dkm-del",
905
+ onClick: () => patchDraft({ models: draft.models.filter((_, j) => j !== i) })
906
+ }, "\u5220\u9664")
907
+ ),
908
+ import_react3.default.createElement(
909
+ "div",
910
+ { className: "dkm-grid" },
911
+ import_react3.default.createElement("input", { className: "dkm-input", placeholder: "\u6A21\u578B id\uFF08\u5FC5\u586B\uFF0C\u5982 deepseek-v4-pro\uFF09", value: m.id, onChange: (e) => patchModel(i, { id: e.target.value }) }),
912
+ import_react3.default.createElement("input", { className: "dkm-input", placeholder: "\u663E\u793A\u540D\u79F0\uFF08\u53EF\u9009\uFF09", value: m.name, onChange: (e) => patchModel(i, { name: e.target.value }) }),
913
+ import_react3.default.createElement("input", { className: "dkm-input", placeholder: "\u4E0A\u4E0B\u6587\u7A97\u53E3", inputMode: "numeric", value: m.contextWindow, onChange: (e) => patchModel(i, { contextWindow: e.target.value }) }),
914
+ import_react3.default.createElement("input", { className: "dkm-input", placeholder: "\u6700\u5927\u8F93\u51FA", inputMode: "numeric", value: m.maxTokens, onChange: (e) => patchModel(i, { maxTokens: e.target.value }) })
915
+ ),
916
+ import_react3.default.createElement(
917
+ "div",
918
+ { className: "dkm-checks" },
919
+ import_react3.default.createElement("span", { className: "dkm-label" }, "\u8F93\u5165\u7C7B\u578B\uFF1A"),
920
+ DKM_MODALITIES.map(([v, label]) => import_react3.default.createElement(
921
+ "label",
922
+ {
923
+ key: v,
924
+ className: "dkm-check",
925
+ title: v === "image" ? "\u52FE\u9009=\u7AEF\u70B9\u539F\u751F\u652F\u6301\u56FE\u7247\uFF08\u539F\u56FE\u76F4\u53D1\uFF0C\u56FE\u7247\u7406\u89E3\u4EE3\u7406\u8DF3\u8FC7\uFF09\uFF1B\u7EAF\u6587\u672C\u6A21\u578B\u52FF\u52FE\u2014\u2014\u6536\u56FE\u4F1A\u81EA\u52A8\u4EA4\u7ED9\u56FE\u7247\u7406\u89E3\u4EE3\u7406\u8BC6\u522B" : null
926
+ },
927
+ import_react3.default.createElement("input", {
928
+ type: "checkbox",
929
+ checked: m.input.indexOf(v) >= 0,
930
+ onChange: () => {
931
+ const next = toggleIn(m.input, v);
932
+ patchModel(i, { input: next.length > 0 ? next : ["text"] });
933
+ }
934
+ }),
935
+ label
936
+ )),
937
+ import_react3.default.createElement("span", { className: "dkm-label dkm-tag", title: "\u6807\u6CE8\u4EC5\u5728\u672C\u9762\u677F\u5C55\u793A\u4E0E\u6301\u4E45\u5316\uFF0C\u4E0D\u53C2\u4E0E\u8BF7\u6C42\u8DEF\u7531\uFF08\u5B98\u65B9 schema \u53EA\u63A5\u53D7\u6587\u672C/\u56FE\u7247\uFF09" }, "\u6807\u6CE8\uFF1A"),
938
+ DKM_TAGS.map(([v, label]) => import_react3.default.createElement(
939
+ "label",
940
+ { key: v, className: "dkm-check dkm-tag" },
941
+ import_react3.default.createElement("input", { type: "checkbox", checked: m.tags.indexOf(v) >= 0, onChange: () => patchModel(i, { tags: toggleIn(m.tags, v) }) }),
942
+ label
943
+ )),
944
+ import_react3.default.createElement("button", { type: "button", className: "dkm-mini", title: "\u4E00\u952E\u52FE\u9009\u5168\u90E8\u8F93\u5165\u7C7B\u578B\u4E0E\u6807\u6CE8", onClick: () => patchModel(i, allInputs(true)) }, "\u5168\u9009"),
945
+ import_react3.default.createElement("button", { type: "button", className: "dkm-mini", title: "\u4E00\u952E\u53D6\u6D88\u5168\u90E8\u52FE\u9009\uFF08\u8F93\u5165\u7559\u7A7A = \u7EE7\u627F\u76EE\u5F55\u9ED8\u8BA4\uFF09", onClick: () => patchModel(i, allInputs(false)) }, "\u53D6\u6D88\u5168\u9009")
946
+ ),
947
+ import_react3.default.createElement(
948
+ "div",
949
+ { className: "dkm-efforts" },
950
+ import_react3.default.createElement("span", { className: "dkm-label" }, "\u601D\u8003\u5F3A\u5EA6\uFF1A"),
951
+ cur.kind === "deepseek" ? import_react3.default.createElement("span", { className: "dkm-sub" }, "\u968F Provider \u8BBE\u7F6E\uFF08Off/Low/High/Max\uFF0C\u5168\u6A21\u578B\u5171\u4EAB\uFF09") : import_react3.default.createElement(
952
+ "span",
953
+ null,
954
+ import_react3.default.createElement(
955
+ "select",
956
+ {
957
+ className: "dkm-select",
958
+ value: m.effortsMode,
959
+ onChange: (e) => patchModel(i, {
960
+ effortsMode: e.target.value,
961
+ effortLevels: e.target.value === "custom" && m.effortsMode !== "custom" ? { off: true, low: true, high: true } : m.effortLevels
962
+ })
963
+ },
964
+ import_react3.default.createElement("option", { value: "inherit" }, "\u8DDF\u968F\u76EE\u5F55\u9ED8\u8BA4"),
965
+ import_react3.default.createElement("option", { value: "off" }, "\u4E0D\u652F\u6301\u601D\u8003"),
966
+ import_react3.default.createElement("option", { value: "custom" }, "\u81EA\u5B9A\u4E49\u6863\u4F4D")
967
+ ),
968
+ m.effortsMode === "custom" ? DKM_PI_LEVELS.map((lv) => import_react3.default.createElement(
969
+ "label",
970
+ { key: lv, className: "dkm-check" },
971
+ import_react3.default.createElement("input", {
972
+ type: "checkbox",
973
+ checked: !!m.effortLevels[lv],
974
+ onChange: () => {
975
+ const nl = Object.assign({}, m.effortLevels);
976
+ nl[lv] = !nl[lv];
977
+ patchModel(i, { effortLevels: nl });
978
+ }
979
+ }),
980
+ DKM_EFFORT_NAMES[lv] || lv
981
+ )) : null,
982
+ import_react3.default.createElement("button", { type: "button", className: "dkm-mini", title: "\u4E00\u952E\u52FE\u9009\u5168\u90E8\u5F3A\u5EA6\u6863\uFF08\u5207\u4E3A\u81EA\u5B9A\u4E49\uFF09", onClick: () => patchModel(i, allLevels(true)) }, "\u5168\u9009"),
983
+ import_react3.default.createElement("button", { type: "button", className: "dkm-mini", title: "\u4E00\u952E\u53D6\u6D88\u5168\u90E8\u5F3A\u5EA6\u6863", onClick: () => patchModel(i, allLevels(false)) }, "\u53D6\u6D88\u5168\u9009")
984
+ )
985
+ )
986
+ );
987
+ const body = [];
988
+ if (snap.error) {
989
+ body.push(import_react3.default.createElement(
990
+ "div",
991
+ { key: "err", className: "dkm-error" },
992
+ "\u6A21\u578B\u76EE\u5F55\u62C9\u53D6\u5931\u8D25\uFF1A" + snap.error + " ",
993
+ import_react3.default.createElement("button", { type: "button", className: "dkb-refresh", onClick: () => modelsStore.load() }, "\u91CD\u8BD5")
994
+ ));
995
+ } else if (!data) {
996
+ body.push(import_react3.default.createElement("div", { key: "loading", className: "dkm-note" }, snap.loading ? "\u6B63\u5728\u62C9\u53D6\u6A21\u578B\u76EE\u5F55\u2026" : "\u7B49\u5F85\u62C9\u53D6\u6A21\u578B\u76EE\u5F55"));
997
+ }
998
+ if (data && data.visionProxy === void 0) {
999
+ body.push(import_react3.default.createElement(
1000
+ "div",
1001
+ { key: "visionproxy-old", className: "dkm-note" },
1002
+ "\u56FE\u7247\u7406\u89E3\u4EE3\u7406\u9700\u8981\u65B0\u7248\u5BBF\u4E3B\u8FDB\u7A0B\uFF1A\u5F53\u524D dsh web \u8F83\u65E7\uFF0C\u91CD\u542F\u540E\u6B64\u9762\u677F\u53EF\u7528\u3002"
1003
+ ));
1004
+ }
1005
+ if (data && data.visionProxy !== void 0 && vpDraft) {
1006
+ const effInput = (m) => (m.runtimeInput && m.runtimeInput.length > 0 ? m.runtimeInput : m.input) || [];
1007
+ const vpDirect = [];
1008
+ const vpProxied = [];
1009
+ for (const p of providers) {
1010
+ for (const m of p.models || []) {
1011
+ const item = { key: p.id + "/" + m.id, label: (p.displayName || p.id) + " / " + (m.name || m.id), model: m.name || m.id };
1012
+ (effInput(m).indexOf("image") >= 0 ? vpDirect : vpProxied).push(item);
1013
+ }
1014
+ }
1015
+ const vpCandidates = vpDirect.slice();
1016
+ const vpKey = vpDraft.provider + "/" + vpDraft.model;
1017
+ const vpKnown = vpCandidates.some((c) => c.key === vpKey);
1018
+ body.push(import_react3.default.createElement(
1019
+ "div",
1020
+ { key: "visionproxy", className: "dkm-prov" },
1021
+ import_react3.default.createElement(
1022
+ "div",
1023
+ { className: "dkm-prov-head" },
1024
+ import_react3.default.createElement("span", { className: "dkm-name" }, "\u56FE\u7247\u7406\u89E3\u4EE3\u7406"),
1025
+ import_react3.default.createElement("span", { className: "dkm-badge" }, vpDraft.enabled ? "\u5DF2\u542F\u7528" : "\u5DF2\u505C\u7528"),
1026
+ import_react3.default.createElement("span", { className: "dkm-sub" }, "\u7EAF\u6587\u672C\u6A21\u578B\u6536\u5230\u56FE\u7247\u65F6\u81EA\u52A8\u8C03\u7528\u6240\u9009\u89C6\u89C9\u6A21\u578B\u8BC6\u522B\uFF0C\u8BC6\u522B\u6587\u672C\u66FF\u6362\u56FE\u7247\uFF1B\u591A\u6A21\u6001\u6A21\u578B\u539F\u6837\u81EA\u8BC6\u522B")
1027
+ ),
1028
+ import_react3.default.createElement(
1029
+ "div",
1030
+ { className: "dkm-checks" },
1031
+ import_react3.default.createElement(
1032
+ "label",
1033
+ { className: "dkm-check" },
1034
+ import_react3.default.createElement("input", {
1035
+ type: "checkbox",
1036
+ checked: vpDraft.enabled,
1037
+ onChange: () => {
1038
+ setVpDraft(Object.assign({}, vpDraft, { enabled: !vpDraft.enabled }));
1039
+ setVpDirty(true);
1040
+ setVpMsg(null);
1041
+ }
1042
+ }),
1043
+ "\u542F\u7528"
1044
+ ),
1045
+ import_react3.default.createElement("span", { className: "dkm-label" }, "\u89C6\u89C9\u6A21\u578B\uFF1A"),
1046
+ import_react3.default.createElement(
1047
+ "select",
1048
+ {
1049
+ className: "dkm-select",
1050
+ value: vpKey,
1051
+ onChange: (e) => {
1052
+ const idx = e.target.value.indexOf("/");
1053
+ setVpDraft(Object.assign({}, vpDraft, { provider: e.target.value.slice(0, idx), model: e.target.value.slice(idx + 1) }));
1054
+ setVpDirty(true);
1055
+ setVpMsg(null);
1056
+ }
1057
+ },
1058
+ import_react3.default.createElement("option", { value: "/" }, "\uFF08\u672A\u9009\u62E9\uFF09"),
1059
+ !vpKnown && vpDraft.provider ? import_react3.default.createElement("option", { value: vpKey }, vpDraft.provider + " / " + vpDraft.model + "\uFF08\u5F53\u524D\uFF09") : null,
1060
+ vpCandidates.map((c) => import_react3.default.createElement("option", { key: c.key, value: c.key }, c.label))
1061
+ ),
1062
+ vpCandidates.length === 0 ? import_react3.default.createElement("span", { className: "dkm-sub" }, "\u76EE\u5F55\u91CC\u6682\u65E0\u591A\u6A21\u6001\u6A21\u578B\u2014\u2014\u5148\u5728\u4E0B\u65B9\u7ED9\u771F\xB7\u591A\u6A21\u6001\u6A21\u578B\u52FE\u9009\u300C\u56FE\u7247\u300D\u8F93\u5165\u7C7B\u578B") : null
1063
+ ),
1064
+ import_react3.default.createElement(
1065
+ "div",
1066
+ { className: "dkm-sub" },
1067
+ "\u5224\u5B9A\u4E0E\u5B98\u65B9\u8FD0\u884C\u65F6\u540C\u6E90\uFF1A\u300C\u56FE\u7247\u300D\u52FE\u9009\uFF08\u6216\u76EE\u5F55\u9ED8\u8BA4\uFF09= \u7AEF\u70B9\u539F\u751F\u652F\u6301\u3001\u539F\u56FE\u76F4\u53D1\uFF1B\u5176\u4F59\u6A21\u578B\u6536\u56FE\u81EA\u52A8\u8D70\u89C6\u89C9\u6A21\u578B\u3002\u6CE8\u610F\uFF1A\u82E5\u7AEF\u70B9\u5B9E\u9645\u4E0D\u8BA4\u56FE\u5374\u52FE\u4E86\u300C\u56FE\u7247\u300D\uFF0C\u6A21\u578B\u4F1A\u770B\u4E0D\u89C1\u56FE\u7247\u3001\u8F6C\u800C\u7528\u5DE5\u5177\u778E\u6298\u817E\u2014\u2014\u4E0D\u786E\u5B9A\u5C31\u4E0D\u8981\u52FE\uFF0C\u4EA4\u7ED9\u4EE3\u7406\u3002"
1068
+ ),
1069
+ vpDirect.length > 0 ? import_react3.default.createElement(
1070
+ "div",
1071
+ { className: "dkm-checks" },
1072
+ import_react3.default.createElement("span", { className: "dkm-label" }, "\u539F\u56FE\u76F4\u53D1\uFF08\u591A\u6A21\u6001\uFF0C" + vpDirect.length + "\uFF09\uFF1A"),
1073
+ vpDirect.map((c) => import_react3.default.createElement("span", { key: c.key, className: "dkm-chip", title: c.key }, c.model))
1074
+ ) : null,
1075
+ vpProxied.length > 0 ? import_react3.default.createElement(
1076
+ "div",
1077
+ { className: "dkm-checks" },
1078
+ import_react3.default.createElement("span", { className: "dkm-label" }, "\u8D70\u89C6\u89C9\u4EE3\u7406\uFF08\u7EAF\u6587\u672C\uFF0C" + vpProxied.length + "\uFF09\uFF1A"),
1079
+ import_react3.default.createElement("span", { className: "dkm-sub" }, vpProxied.map((c) => c.model).join("\u3001"))
1080
+ ) : null,
1081
+ import_react3.default.createElement(
1082
+ "div",
1083
+ { className: "dkm-savebar" },
1084
+ import_react3.default.createElement("button", { type: "button", className: "dkm-save", disabled: !vpDirty || vpSaving, onClick: saveVp }, vpSaving ? "\u4FDD\u5B58\u4E2D\u2026" : "\u4FDD\u5B58\u56FE\u7247\u4EE3\u7406\u914D\u7F6E"),
1085
+ vpMsg ? import_react3.default.createElement("span", { className: "dkm-msg " + (vpMsg.ok ? "ok" : "err") }, vpMsg.text) : import_react3.default.createElement("span", { className: "dkm-msg" }, vpDirty ? "\u6709\u672A\u4FDD\u5B58\u7684\u4FEE\u6539" : "")
1086
+ )
1087
+ ));
1088
+ }
1089
+ if (data && providers.length === 0) {
1090
+ body.push(import_react3.default.createElement("div", { key: "empty", className: "dkm-note" }, "\u6CA1\u6709\u53EF\u7F16\u8F91\u7684\u6A21\u578B Provider\uFF08Host \u4FA7\u5C1A\u672A\u914D\u7F6E\uFF09"));
1091
+ }
1092
+ if (providers.length > 0) {
1093
+ body.push(import_react3.default.createElement(
1094
+ "div",
1095
+ { key: "chips", className: "dkm-chips" },
1096
+ providers.map((p) => import_react3.default.createElement("button", {
1097
+ type: "button",
1098
+ key: p.id,
1099
+ className: "dkm-chip" + (cur && p.id === cur.id ? " on" : ""),
1100
+ onClick: () => selectProvider(p.id)
1101
+ }, p.displayName + "\uFF08" + (p.models ? p.models.length : 0) + "\uFF09"))
1102
+ ));
1103
+ }
1104
+ if (cur && draft) {
1105
+ body.push(import_react3.default.createElement(
1106
+ "div",
1107
+ { key: "prov", className: "dkm-prov" },
1108
+ import_react3.default.createElement(
1109
+ "div",
1110
+ { className: "dkm-prov-head" },
1111
+ import_react3.default.createElement("span", { className: "dkm-name" }, cur.displayName),
1112
+ import_react3.default.createElement("span", { className: "dkm-badge" }, cur.kind === "deepseek" ? "\u5B98\u65B9 DeepSeek" : "\u81EA\u5B9A\u4E49\u8DEF\u7531"),
1113
+ import_react3.default.createElement("span", { className: "dkm-sub" }, [cur.api, cur.baseURL].filter(Boolean).join(" \xB7 "))
1114
+ ),
1115
+ cur.kind === "deepseek" ? import_react3.default.createElement(
1116
+ "div",
1117
+ { className: "dkm-controls" },
1118
+ import_react3.default.createElement("span", { className: "dkm-label" }, "\u601D\u8003\u6A21\u5F0F\uFF1A"),
1119
+ import_react3.default.createElement(
1120
+ "select",
1121
+ { className: "dkm-select", value: draft.thinking, onChange: (e) => patchDraft({ thinking: e.target.value }) },
1122
+ import_react3.default.createElement("option", { value: "enabled" }, "\u5F00\u542F"),
1123
+ import_react3.default.createElement("option", { value: "disabled" }, "\u5173\u95ED")
1124
+ ),
1125
+ import_react3.default.createElement("span", { className: "dkm-label" }, "\u9ED8\u8BA4\u5F3A\u5EA6\uFF1A"),
1126
+ import_react3.default.createElement(
1127
+ "select",
1128
+ { className: "dkm-select", value: draft.defaultEffort, onChange: (e) => patchDraft({ defaultEffort: e.target.value }) },
1129
+ DKM_DS_LEVELS.map((lv) => import_react3.default.createElement("option", { key: lv, value: lv }, DKM_EFFORT_NAMES[lv] || lv))
1130
+ )
1131
+ ) : null,
1132
+ import_react3.default.createElement(
1133
+ "div",
1134
+ { className: "dkm-toolbar" },
1135
+ import_react3.default.createElement("span", { className: "dkm-toolbar-label" }, "\u6279\u91CF\uFF08\u5168\u90E8\u6A21\u578B\uFF09\uFF1A"),
1136
+ import_react3.default.createElement("button", { type: "button", className: "dkm-mini", title: "\u5168\u90E8\u6A21\u578B\uFF1A\u52FE\u9009\u5168\u90E8\u8F93\u5165\u7C7B\u578B\u4E0E\u6807\u6CE8", onClick: () => bulkModels(() => allInputs(true)) }, "\u8F93\u5165\u5168\u9009"),
1137
+ import_react3.default.createElement("button", { type: "button", className: "dkm-mini", title: "\u5168\u90E8\u6A21\u578B\uFF1A\u53D6\u6D88\u5168\u90E8\u8F93\u5165\u52FE\u9009\uFF08\u7559\u7A7A = \u7EE7\u627F\u76EE\u5F55\u9ED8\u8BA4\uFF09", onClick: () => bulkModels(() => allInputs(false)) }, "\u8F93\u5165\u53D6\u6D88\u5168\u9009"),
1138
+ cur.kind !== "deepseek" ? import_react3.default.createElement("button", { type: "button", className: "dkm-mini", title: "\u5168\u90E8\u6A21\u578B\uFF1A\u52FE\u9009\u5168\u90E8\u5F3A\u5EA6\u6863\uFF08\u5207\u4E3A\u81EA\u5B9A\u4E49\uFF09", onClick: () => bulkModels(() => allLevels(true)) }, "\u5F3A\u5EA6\u5168\u9009") : null,
1139
+ cur.kind !== "deepseek" ? import_react3.default.createElement("button", { type: "button", className: "dkm-mini", title: "\u5168\u90E8\u6A21\u578B\uFF1A\u53D6\u6D88\u5168\u90E8\u5F3A\u5EA6\u6863", onClick: () => bulkModels(() => allLevels(false)) }, "\u5F3A\u5EA6\u53D6\u6D88\u5168\u9009") : null
1140
+ ),
1141
+ import_react3.default.createElement(
1142
+ "div",
1143
+ { style: { display: "flex", flexDirection: "column", gap: "8px" } },
1144
+ draft.models.map(modelRow),
1145
+ import_react3.default.createElement("button", {
1146
+ type: "button",
1147
+ className: "dkm-add",
1148
+ onClick: () => patchDraft({
1149
+ models: draft.models.concat([{ id: "", name: "", contextWindow: "", maxTokens: "", input: ["text"], tags: [], effortsMode: "inherit", effortLevels: {} }])
1150
+ })
1151
+ }, "+ \u6DFB\u52A0\u6A21\u578B")
1152
+ ),
1153
+ import_react3.default.createElement(
1154
+ "div",
1155
+ { className: "dkm-savebar" },
1156
+ import_react3.default.createElement("span", { className: "dkm-msg" }, dirty ? "\u6709\u672A\u4FDD\u5B58\u7684\u4FEE\u6539" : "\u65E0\u672A\u4FDD\u5B58\u4FEE\u6539"),
1157
+ import_react3.default.createElement("button", { type: "button", className: "dkm-save", disabled: !dirty || saving, onClick: save }, saving ? "\u4FDD\u5B58\u4E2D\u2026" : "\u4FDD\u5B58\u5199\u56DE\u5B98\u65B9\u914D\u7F6E"),
1158
+ import_react3.default.createElement("button", { type: "button", className: "dkb-refresh", disabled: saving, onClick: () => {
1159
+ setBuiltKey(null);
1160
+ modelsStore.load();
1161
+ } }, "\u91CD\u65B0\u62C9\u53D6"),
1162
+ msg ? import_react3.default.createElement("span", { className: "dkm-msg " + (msg.ok ? "ok" : "err") }, msg.text) : null
1163
+ )
1164
+ ));
1165
+ }
1166
+ return import_react3.default.createElement("div", { className: "dkm-list" }, body);
1167
+ }
1168
+ function ModelsStat() {
1169
+ const snap = useModels();
1170
+ const data = snap.data;
1171
+ if (snap.error) return import_react3.default.createElement("span", { className: "dockm-err" }, "\u6A21\u578B\u76EE\u5F55\u62C9\u53D6\u5931\u8D25\uFF08\u70B9\u51FB\u8FDB\u5165\u67E5\u770B\uFF09");
1172
+ if (!data) return import_react3.default.createElement("span", null, snap.loading ? "\u6B63\u5728\u62C9\u53D6\u6A21\u578B\u76EE\u5F55\u2026" : "\u7B49\u5F85\u62C9\u53D6\u6A21\u578B\u76EE\u5F55");
1173
+ const providers = Array.isArray(data.providers) ? data.providers : [];
1174
+ const total = providers.reduce((n, p) => n + (p.models ? p.models.length : 0), 0);
1175
+ return import_react3.default.createElement("span", null, providers.length + " \u4E2A Provider \xB7 " + total + " \u4E2A\u6A21\u578B");
1176
+ }
1177
+ var feature2 = {
1178
+ id: "modelconfig",
1179
+ name: "\u6A21\u578B\u8BBE\u7F6E",
1180
+ order: 100,
1181
+ accent: "#22d3ee",
1182
+ description: "\u7F16\u8F91\u5404 Provider \u6A21\u578B\u76EE\u5F55\uFF1A\u8F93\u5165\u7C7B\u578B\uFF08\u6587\u672C/\u56FE\u7247 + \u6807\u6CE8\uFF09\u4E0E\u601D\u8003\u5F3A\u5EA6\u6863\u4F4D\uFF1B\u5199\u56DE\u5B98\u65B9\u914D\u7F6E\u70ED\u751F\u6548\uFF0C\u4F1A\u8BDD\u6A21\u578B\u9009\u62E9\u5668\u5373\u65F6\u53EF\u9009",
1183
+ css: [
1184
+ ".dkm-note{color:var(--dsw-alias-label-secondary);font-size:12px;}",
1185
+ ".dkm-error{color:var(--dsw-alias-state-error-primary);font-size:12px;}",
1186
+ ".dkm-chips{display:flex;flex-wrap:wrap;gap:6px;}",
1187
+ ".dkm-chip{cursor:pointer;border:1px solid var(--dsw-alias-border-l2);background:transparent;color:var(--dsw-alias-label-secondary);border-radius:999px;padding:3px 12px;font-family:inherit;font-size:12px;}",
1188
+ ".dkm-chip.on{color:var(--dsw-alias-accent,#4d9fff);border-color:currentColor;}",
1189
+ ".dkm-prov{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-1);border-radius:10px;padding:10px 12px;display:flex;flex-direction:column;gap:8px;}",
1190
+ ".dkm-prov-head{display:flex;align-items:center;gap:8px;flex-wrap:wrap;}",
1191
+ ".dkm-name{font-weight:600;}",
1192
+ ".dkm-sub{color:var(--dsw-alias-label-tertiary);font-size:11px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:100%;}",
1193
+ ".dkm-badge{flex:none;font-size:11px;border-radius:999px;padding:0 8px;color:var(--dsw-alias-label-tertiary);border:1px solid var(--dsw-alias-border-l2);}",
1194
+ ".dkm-controls{display:flex;align-items:center;gap:10px;flex-wrap:wrap;border-top:1px solid var(--dsw-alias-border-l1);padding-top:8px;}",
1195
+ ".dkm-label{color:var(--dsw-alias-label-secondary);font-size:12px;flex:none;}",
1196
+ ".dkm-select{background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;padding:3px 8px;font-family:inherit;font-size:12px;}",
1197
+ ".dkm-model{border:1px solid var(--dsw-alias-border-l1);border-radius:10px;padding:8px 10px;display:flex;flex-direction:column;gap:6px;}",
1198
+ ".dkm-model-head{display:flex;align-items:center;gap:6px;}",
1199
+ ".dkm-model-idx{color:var(--dsw-alias-label-tertiary);font-size:11px;flex:none;}",
1200
+ ".dkm-grid{display:grid;grid-template-columns:1.3fr 1.3fr .8fr .8fr;gap:6px;align-items:center;}",
1201
+ ".dkm-input{box-sizing:border-box;width:100%;background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;padding:4px 8px;font-family:inherit;font-size:12px;}",
1202
+ ".dkm-checks{display:flex;align-items:center;gap:12px;flex-wrap:wrap;}",
1203
+ ".dkm-check{display:inline-flex;align-items:center;gap:5px;cursor:pointer;color:var(--dsw-alias-label-secondary);font-size:12px;}",
1204
+ ".dkm-check input{accent-color:var(--dsw-alias-accent,#4d9fff);margin:0;}",
1205
+ ".dkm-tag{color:var(--dsw-alias-label-tertiary);}",
1206
+ ".dkm-efforts{display:flex;align-items:center;gap:10px;flex-wrap:wrap;}",
1207
+ ".dkm-del{margin-left:auto;cursor:pointer;border:1px solid var(--dsw-alias-border-l2);background:transparent;color:var(--dsw-alias-label-tertiary);border-radius:8px;padding:2px 10px;font-family:inherit;font-size:12px;flex:none;}",
1208
+ ".dkm-del:hover{color:var(--dsw-alias-state-error-primary);border-color:currentColor;}",
1209
+ ".dkm-add{cursor:pointer;border:1px dashed var(--dsw-alias-border-l2);background:transparent;color:var(--dsw-alias-label-secondary);border-radius:10px;padding:6px 14px;font-family:inherit;font-size:12px;align-self:flex-start;}",
1210
+ ".dkm-add:hover{color:var(--dsw-alias-label-primary);}",
1211
+ ".dkm-savebar{display:flex;align-items:center;gap:10px;flex-wrap:wrap;border-top:1px solid var(--dsw-alias-border-l1);padding-top:10px;}",
1212
+ ".dkm-save{cursor:pointer;border:1px solid var(--dsw-alias-accent,#4d9fff);color:var(--dsw-alias-accent,#4d9fff);background:transparent;border-radius:8px;padding:4px 14px;font-family:inherit;font-size:12px;flex:none;}",
1213
+ ".dkm-save:hover{background:var(--dsw-alias-interactive-bg-hover);}",
1214
+ ".dkm-save[disabled]{opacity:.5;cursor:default;}",
1215
+ ".dkm-msg{font-size:12px;color:var(--dsw-alias-label-secondary);}",
1216
+ ".dkm-msg.ok{color:var(--dsw-alias-state-success-primary);}",
1217
+ ".dkm-msg.err{color:var(--dsw-alias-state-error-primary);}",
1218
+ ".dkm-list{display:flex;flex-direction:column;gap:8px;}",
1219
+ ".dkm-ro{color:var(--dsw-alias-label-secondary);font-size:12px;}",
1220
+ ".dkm-mini{cursor:pointer;border:1px solid var(--dsw-alias-border-l2);background:transparent;color:var(--dsw-alias-label-tertiary);border-radius:999px;padding:1px 8px;font-family:inherit;font-size:11px;flex:none;}",
1221
+ ".dkm-mini:hover{color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-secondary);}",
1222
+ ".dkm-toolbar{display:flex;align-items:center;gap:6px;flex-wrap:wrap;border-top:1px dashed var(--dsw-alias-border-l1);padding-top:8px;}",
1223
+ ".dkm-toolbar-label{color:var(--dsw-alias-label-tertiary);font-size:11px;flex:none;}"
1224
+ ].join("\n"),
1225
+ View: ModelsView,
1226
+ HomeStat: ModelsStat
1227
+ };
44
1228
 
45
- // 开关状态:浏览器内存态,随页面生命周期
46
- const state = new Map();
47
- for (const f of FEATURES) state.set(f.id, { enabled: !!f.defaultEnabled, error: null });
1229
+ // features/heartbeat/view.js
1230
+ var import_react4 = __toESM(require("react"), 1);
1231
+ var loadedAt = Date.now();
1232
+ function uptimeText() {
1233
+ const sec = Math.max(0, Math.floor((Date.now() - loadedAt) / 1e3));
1234
+ const m = Math.floor(sec / 60);
1235
+ return "\u9762\u677F\u5DF2\u8FD0\u884C " + (m > 0 ? m + " \u5206 " : "") + sec % 60 + " \u79D2";
1236
+ }
1237
+ function HeartbeatView(props) {
1238
+ const ctx = props && props.ctx;
1239
+ const [txt, setTxt] = import_react4.default.useState(uptimeText());
1240
+ import_react4.default.useEffect(() => ctx && typeof ctx.interval === "function" ? ctx.interval(() => setTxt(uptimeText()), 1e3) : void 0, []);
1241
+ return import_react4.default.createElement("div", null, txt);
1242
+ }
1243
+ function HeartbeatStat(props) {
1244
+ const ctx = props && props.ctx;
1245
+ const [txt, setTxt] = import_react4.default.useState(uptimeText());
1246
+ import_react4.default.useEffect(() => ctx && typeof ctx.interval === "function" ? ctx.interval(() => setTxt(uptimeText()), 1e3) : void 0, []);
1247
+ return import_react4.default.createElement("span", null, txt);
1248
+ }
1249
+ var feature3 = {
1250
+ id: "heartbeat",
1251
+ name: "\u5FC3\u8DF3\u76D1\u89C6",
1252
+ order: 140,
1253
+ accent: "#34d399",
1254
+ description: "\u793A\u4F8B\u529F\u80FD\uFF1A\u9762\u677F\u4FA7\u8FD0\u884C\u65F6\u957F\u5FC3\u8DF3\uFF08\u7EAF Client\uFF09",
1255
+ View: HeartbeatView,
1256
+ HomeStat: HeartbeatStat
1257
+ };
48
1258
 
49
- // ---- 每个功能的客户端视图:key 与注册表 id 一致 ----
50
- const featureViews = {
51
- heartbeat: function HeartbeatView() {
52
- const [sec, setSec] = react.useState(0);
53
- react.useEffect(() => ctx.interval(() => setSec((s) => s + 1), 1000), []);
54
- const m = Math.floor(sec / 60);
55
- return react.createElement("div", null,
56
- "面板已运行 " + (m > 0 ? m + " 分 " : "") + (sec % 60) + " 秒");
57
- },
58
- theme: function ThemeView() {
59
- const theme = ctx.get("theme");
60
- if (theme === undefined) return react.createElement("div", null, "theme 服务不可用");
61
- let snap = null;
62
- try { snap = theme.getTheme(); }
63
- catch (err) { return react.createElement("div", null, "读取失败:" + String((err && err.message) || err)); }
64
- const label = snap && typeof snap.id === "string" ? snap.id
65
- : snap && typeof snap.name === "string" ? snap.name : "未知";
66
- return react.createElement("div", null, "当前主题:" + label);
67
- }
68
- };
1259
+ // features/theme/view.js
1260
+ var import_react5 = __toESM(require("react"), 1);
1261
+ function readThemeLabel(ctx) {
1262
+ const theme = ctx && ctx.get ? ctx.get("theme") : void 0;
1263
+ if (theme === void 0) return { err: "theme \u670D\u52A1\u4E0D\u53EF\u7528" };
1264
+ let snap = null;
1265
+ try {
1266
+ snap = theme.getTheme();
1267
+ } catch (err) {
1268
+ return { err: "\u8BFB\u53D6\u5931\u8D25\uFF1A" + String(err && err.message || err) };
1269
+ }
1270
+ const label = snap && typeof snap.id === "string" ? snap.id : snap && typeof snap.name === "string" ? snap.name : "\u672A\u77E5";
1271
+ return { label };
1272
+ }
1273
+ function ThemeView(props) {
1274
+ const r = readThemeLabel(props && props.ctx);
1275
+ if (r.err) return import_react5.default.createElement("div", null, r.err);
1276
+ return import_react5.default.createElement("div", null, "\u5F53\u524D\u4E3B\u9898\uFF1A" + r.label);
1277
+ }
1278
+ function ThemeStat(props) {
1279
+ const r = readThemeLabel(props && props.ctx);
1280
+ if (r.err) return import_react5.default.createElement("span", null, r.err);
1281
+ return import_react5.default.createElement("span", null, "\u5F53\u524D\u4E3B\u9898\uFF1A" + r.label);
1282
+ }
1283
+ var feature4 = {
1284
+ id: "theme",
1285
+ name: "\u4E3B\u9898\u4FE1\u606F",
1286
+ order: 150,
1287
+ accent: "#a78bfa",
1288
+ description: "\u793A\u4F8B\u529F\u80FD\uFF1A\u8BFB\u53D6\u5F53\u524D\u4E3B\u9898\u5FEB\u7167\uFF08\u7EAF Client\uFF09",
1289
+ View: ThemeView,
1290
+ HomeStat: ThemeStat
1291
+ };
69
1292
 
70
- function DockPanel() {
71
- const [, force] = react.useReducer((n) => n + 1, 0);
72
- const toggle = (id) => {
73
- const st = state.get(id);
74
- if (!st) return;
75
- st.enabled = !st.enabled;
76
- force();
77
- };
78
- return react.createElement("div", { className: "dock-root" },
79
- react.createElement("style", null, CSS),
80
- react.createElement("div", { className: "dock-intro" },
81
- "功能中枢(dsh-dock)· 所有小功能集中在这一个面板里管理。每个功能是独立模块:开关只影响自己,单个功能出错不影响其他功能。新功能按注册表模式追加(FEATURES + featureViews 各加一条)。"),
82
- FEATURES.map((f) => {
83
- const st = state.get(f.id);
84
- const View = featureViews[f.id];
85
- return react.createElement("div", { className: "dock-card", key: f.id },
86
- react.createElement("div", { className: "dock-card-head" },
87
- react.createElement("span", { className: "dock-dot" + (st.error ? " err" : st.enabled ? " on" : "") }),
88
- react.createElement("span", { className: "dock-name" }, f.name),
89
- react.createElement("span", { className: "dock-desc" }, f.description),
90
- f.planned
91
- ? react.createElement("span", { className: "dock-badge" }, "规划中")
92
- : react.createElement("button", {
93
- className: "dock-switch" + (st.enabled ? " on" : ""),
94
- onClick: () => toggle(f.id)
95
- }, st.enabled ? "已启用" : "已停用")),
96
- f.planned
97
- ? react.createElement("div", { className: "dock-body" }, "待接入:见 README 路线图")
98
- : st.error ? react.createElement("div", { className: "dock-body dock-err" }, "功能出错:" + st.error) : null,
99
- !f.planned && st.enabled && View
100
- ? react.createElement("div", { className: "dock-body" }, react.createElement(View, null))
101
- : null);
102
- }));
103
- }
1293
+ // features/balance/view.js
1294
+ var import_react6 = __toESM(require("react"), 1);
1295
+ var ACCENT_PALETTE = ["#4d9fff", "#34d399", "#fbbf24", "#a78bfa", "#f472b6", "#38bdf8", "#fb923c", "#4ade80", "#e879f9", "#22d3ee"];
1296
+ var CURATED_ACCENTS = {
1297
+ "deepseek-official": "#4d9fff",
1298
+ deepseek: "#4d9fff",
1299
+ "qwen-token-plan-cn": "#fbbf24",
1300
+ fangzhou: "#a78bfa",
1301
+ openai: "#10a37f",
1302
+ anthropic: "#d97757",
1303
+ "google-gemini": "#4285f4"
1304
+ };
1305
+ var C_TOTAL = "#34d399";
1306
+ var C_GRANTED = "#22d3ee";
1307
+ var C_TOPUP = "#fbbf24";
1308
+ var C_ERR = "#f87171";
1309
+ function accentOf(id) {
1310
+ if (CURATED_ACCENTS[id]) return CURATED_ACCENTS[id];
1311
+ let h = 0;
1312
+ const s = String(id);
1313
+ for (let i = 0; i < s.length; i++) h = h * 31 + s.charCodeAt(i) >>> 0;
1314
+ return ACCENT_PALETTE[h % ACCENT_PALETTE.length];
1315
+ }
1316
+ var fmt = (v) => v == null || v === "" ? "\u2014" : String(v);
1317
+ var balanceStore = {
1318
+ snap: { data: null, loading: false, error: null },
1319
+ listeners: /* @__PURE__ */ new Set(),
1320
+ subscribe(fn) {
1321
+ this.listeners.add(fn);
1322
+ return () => {
1323
+ this.listeners.delete(fn);
1324
+ };
1325
+ },
1326
+ set(patch) {
1327
+ this.snap = Object.assign({}, this.snap, patch);
1328
+ for (const fn of this.listeners) fn();
1329
+ },
1330
+ load() {
1331
+ if (this.snap.loading) return;
1332
+ this.set({ loading: true });
1333
+ fetch("/dsh-dock/balance", { signal: AbortSignal.timeout(2e4) }).then((res) => res.ok ? res.json() : Promise.reject(new Error("\u4F59\u989D\u63A5\u53E3 HTTP " + res.status))).then((data) => this.set({ data, loading: false, error: null })).catch((e) => this.set({ loading: false, error: e && e.message || String(e) }));
1334
+ }
1335
+ };
1336
+ function useBalance(ctx) {
1337
+ const [snap, setSnap] = import_react6.default.useState(balanceStore.snap);
1338
+ import_react6.default.useEffect(() => {
1339
+ const off = balanceStore.subscribe(() => setSnap(balanceStore.snap));
1340
+ if (!balanceStore.snap.data && !balanceStore.snap.loading) balanceStore.load();
1341
+ const disposer = ctx && typeof ctx.interval === "function" ? ctx.interval(() => balanceStore.load(), 5 * 60 * 1e3) : null;
1342
+ return () => {
1343
+ off();
1344
+ if (disposer) disposer();
1345
+ };
1346
+ }, []);
1347
+ return snap;
1348
+ }
1349
+ function BalanceView(props) {
1350
+ const snap = useBalance(props && props.ctx);
1351
+ const load = () => balanceStore.load();
1352
+ import_react6.default.useEffect(() => {
1353
+ const pid = props && props.params && props.params.provider;
1354
+ if (!pid || typeof document === "undefined") return;
1355
+ const t = setTimeout(() => {
1356
+ try {
1357
+ const el = document.querySelector('[data-dock-provider="' + pid + '"]');
1358
+ if (el) {
1359
+ el.scrollIntoView({ block: "center", behavior: "smooth" });
1360
+ el.classList.remove("dkb-flash");
1361
+ void el.offsetWidth;
1362
+ el.classList.add("dkb-flash");
1363
+ }
1364
+ } catch {
1365
+ }
1366
+ }, 80);
1367
+ return () => clearTimeout(t);
1368
+ }, [props && props.params]);
1369
+ const data = snap.data;
1370
+ const providers = data && Array.isArray(data.providers) ? data.providers : [];
1371
+ const okCount = providers.filter((p) => p.balance && p.balance.status === "ok").length;
1372
+ const def = data && data.default ? data.default.provider : null;
1373
+ const body = [];
1374
+ if (snap.error) {
1375
+ body.push(import_react6.default.createElement("div", { key: "err", className: "dkb-note dkb-error" }, "\u4F59\u989D\u67E5\u8BE2\u5931\u8D25\uFF1A" + snap.error));
1376
+ } else if (!data) {
1377
+ body.push(import_react6.default.createElement("div", { key: "loading", className: "dkb-note" }, snap.loading ? "\u6B63\u5728\u62C9\u53D6\u4F59\u989D\u2026" : "\u8FD8\u6CA1\u6709\u6570\u636E\uFF0C\u70B9\u51FB\u4E0B\u65B9\u5237\u65B0"));
1378
+ } else if (providers.length === 0) {
1379
+ body.push(import_react6.default.createElement("div", { key: "empty", className: "dkb-note" }, "\u6CA1\u6709\u627E\u5230\u5DF2\u914D\u7F6E\u7684\u6A21\u578B Provider"));
1380
+ } else {
1381
+ body.push(import_react6.default.createElement(
1382
+ "div",
1383
+ { key: "rows", className: "dkb-rows" },
1384
+ providers.map((p) => {
1385
+ const accent = accentOf(p.id);
1386
+ const b = p.balance;
1387
+ let badge = ["\u672A\u77E5", ""];
1388
+ if (b) {
1389
+ if (b.status === "ok") badge = ["\u53EF\u7528", "ok"];
1390
+ else if (b.status === "unsupported") badge = ["\u4E0D\u652F\u6301", ""];
1391
+ else if (b.status === "no-credential") badge = ["\u672A\u914D\u7F6E\u5BC6\u94A5", "warn"];
1392
+ else if (b.status === "login-required") badge = ["\u9700\u767B\u5F55", "warn"];
1393
+ else badge = ["\u67E5\u8BE2\u5931\u8D25", "err"];
1394
+ }
1395
+ const sub = ["ID: " + p.id, p.api ? "api: " + p.api : null, p.baseURL ? p.baseURL : null].filter(Boolean).join(" \xB7 ");
1396
+ const cells = [];
1397
+ let balBody = null;
1398
+ if (b && b.status === "ok" && b.kind === "quota") {
1399
+ cells.push(import_react6.default.createElement(
1400
+ "div",
1401
+ { key: "q", className: "dkb-bal" },
1402
+ import_react6.default.createElement("span", { className: "dkb-cur", style: { color: accent } }, "\u603B\u989D\u5EA6"),
1403
+ import_react6.default.createElement("span", { className: "dkb-total", style: { color: C_TOTAL } }, "\u5269\u4F59 " + fmt(b.remaining) + (b.unit ? " " + b.unit : "")),
1404
+ import_react6.default.createElement("span", { className: "dkb-part" }, "\u5DF2\u7528 " + fmt(b.used) + " / \u603B " + fmt(b.limit)),
1405
+ b.resetTime ? import_react6.default.createElement("span", { className: "dkb-part" }, "\u91CD\u7F6E " + String(b.resetTime).slice(0, 16)) : null
1406
+ ));
1407
+ (Array.isArray(b.dims) ? b.dims : []).forEach((d, i) => {
1408
+ cells.push(import_react6.default.createElement(
1409
+ "div",
1410
+ { key: "d" + i, className: "dkb-bal" },
1411
+ import_react6.default.createElement("span", { className: "dkb-cur", style: { color: accent } }, d.window === "weekly" ? "\u5468\u989D\u5EA6" : "\u5C0F\u65F6\u989D\u5EA6"),
1412
+ import_react6.default.createElement("span", { className: "dkb-total", style: { color: C_TOTAL } }, "\u5269\u4F59 " + fmt(d.remaining)),
1413
+ import_react6.default.createElement("span", { className: "dkb-part" }, "\u5DF2\u7528 " + fmt(d.used) + " / " + fmt(d.limit)),
1414
+ d.resetTime ? import_react6.default.createElement("span", { className: "dkb-part" }, "\u91CD\u7F6E " + String(d.resetTime).slice(0, 16)) : null
1415
+ ));
1416
+ });
1417
+ } else if (b && b.status === "ok") {
1418
+ const infos = Array.isArray(b.infos) ? b.infos : [];
1419
+ cells.push(infos.map((i, idx) => import_react6.default.createElement(
1420
+ "div",
1421
+ { key: idx, className: "dkb-bal" },
1422
+ import_react6.default.createElement("span", { className: "dkb-cur", style: { color: accent } }, i.currency),
1423
+ import_react6.default.createElement("span", { className: "dkb-total", style: { color: C_TOTAL } }, "\u603B\u989D " + fmt(i.totalBalance)),
1424
+ i.grantedBalance != null ? import_react6.default.createElement("span", { className: "dkb-part", style: { color: C_GRANTED } }, "\u8D60\u9001 " + fmt(i.grantedBalance)) : null,
1425
+ i.toppedUpBalance != null ? import_react6.default.createElement("span", { className: "dkb-part", style: { color: C_TOPUP } }, "\u5145\u503C " + fmt(i.toppedUpBalance)) : null
1426
+ )));
1427
+ } else if (b && b.status === "login-required" && b.consoleUrl) {
1428
+ balBody = import_react6.default.createElement(
1429
+ "div",
1430
+ { className: "dkb-bal" },
1431
+ import_react6.default.createElement("a", { href: b.consoleUrl, target: "_blank", rel: "noreferrer", className: "dkb-link", style: { color: accent } }, "\u53BB\u63A7\u5236\u53F0\u67E5\u770B\u4F59\u989D \u2192")
1432
+ );
1433
+ } else {
1434
+ balBody = import_react6.default.createElement("div", { className: "dkb-note" }, b && b.message || "\u672A\u77E5\u72B6\u6001");
1435
+ }
1436
+ if (cells.length > 0) balBody = import_react6.default.createElement("div", { className: "dkb-rows" }, cells);
1437
+ return import_react6.default.createElement(
1438
+ "div",
1439
+ { key: p.id, className: "dkb-row", "data-dock-provider": p.id },
1440
+ import_react6.default.createElement(
1441
+ "div",
1442
+ { className: "dkb-row-head" },
1443
+ import_react6.default.createElement("span", { className: "dkb-dot", style: { background: accent } }),
1444
+ import_react6.default.createElement("span", { className: "dkb-name", style: { color: accent } }, p.displayName),
1445
+ def === p.id ? import_react6.default.createElement("span", { className: "dkb-default" }, "\u9ED8\u8BA4") : null,
1446
+ import_react6.default.createElement("span", { className: "dkb-badge" + (badge[1] ? " " + badge[1] : ""), style: badge[1] === "err" ? { color: C_ERR, borderColor: C_ERR } : null }, badge[0])
1447
+ ),
1448
+ sub ? import_react6.default.createElement("div", { className: "dkb-sub" }, sub) : null,
1449
+ p.apiKeyEnv ? import_react6.default.createElement("div", { className: "dkb-sub" }, "\u5BC6\u94A5: " + p.apiKeyEnv + (p.credentialConfigured ? " \u2713" : " \u2717")) : null,
1450
+ p.models && p.models.length > 0 ? import_react6.default.createElement(
1451
+ "div",
1452
+ { className: "dkb-chips" },
1453
+ p.models.slice(0, 8).map((m) => import_react6.default.createElement("span", { key: m, className: "dkb-chip", style: { border: "1px solid " + accent, color: accent, background: accent + "1a" } }, m)),
1454
+ p.models.length > 8 ? import_react6.default.createElement("span", { key: "more", className: "dkb-chip" }, "+" + (p.models.length - 8)) : null
1455
+ ) : null,
1456
+ balBody
1457
+ );
1458
+ })
1459
+ ));
1460
+ }
1461
+ const now = new Date(data && data.generatedAt ? data.generatedAt : Date.now());
1462
+ return import_react6.default.createElement(
1463
+ "div",
1464
+ { className: "dkb-rows" },
1465
+ import_react6.default.createElement(
1466
+ "div",
1467
+ { className: "dkb-note" },
1468
+ data && !snap.error && providers.length > 0 ? okCount + "/" + providers.length + " \u4E2A Provider \u53EF\u67E5\u4F59\u989D" : "\u6A21\u578B\u4F59\u989D \xB7 Host \u534A\u90E8\u5B9E\u65F6\u62C9\u53D6"
1469
+ ),
1470
+ body,
1471
+ import_react6.default.createElement(
1472
+ "div",
1473
+ { className: "dkb-foot" },
1474
+ import_react6.default.createElement("span", null, snap.loading ? "\u5237\u65B0\u4E2D\u2026" : "\u66F4\u65B0\u4E8E " + now.toLocaleTimeString()),
1475
+ import_react6.default.createElement("button", { type: "button", className: "dkb-refresh", onClick: load }, "\u5237\u65B0")
1476
+ )
1477
+ );
1478
+ }
1479
+ function BalanceStat(props) {
1480
+ const snap = useBalance(props && props.ctx);
1481
+ const data = snap.data;
1482
+ const providers = data && Array.isArray(data.providers) ? data.providers : [];
1483
+ if (snap.error) return import_react6.default.createElement("span", { className: "dockm-err" }, "\u4F59\u989D\u67E5\u8BE2\u5931\u8D25\uFF08\u70B9\u51FB\u8FDB\u5165\u67E5\u770B\u8BE6\u60C5\uFF09");
1484
+ if (!data) return import_react6.default.createElement("span", null, snap.loading ? "\u6B63\u5728\u62C9\u53D6\u4F59\u989D\u2026" : "\u7B49\u5F85\u62C9\u53D6\u4F59\u989D");
1485
+ const ok = providers.filter((p) => p.balance && p.balance.status === "ok").length;
1486
+ return import_react6.default.createElement("span", null, providers.length + " \u4E2A Provider \xB7 " + ok + " \u4E2A\u53EF\u67E5\u4F59\u989D");
1487
+ }
1488
+ function useCurrentProvider(ctx, sessionId) {
1489
+ const [cur, setCur] = import_react6.default.useState(null);
1490
+ import_react6.default.useEffect(() => {
1491
+ let alive = true, off = null;
1492
+ try {
1493
+ const models = ctx && ctx.get ? ctx.get("modelDirectories") : void 0;
1494
+ if (models && typeof models.directoryFor === "function" && sessionId) {
1495
+ const dir = models.directoryFor(sessionId);
1496
+ const face = dir && typeof dir.subscribe === "function" ? dir : dir && dir.store;
1497
+ const read = () => {
1498
+ if (!alive) return;
1499
+ try {
1500
+ const snap = face && typeof face.getSnapshot === "function" ? face.getSnapshot() : null;
1501
+ setCur(snap && snap.current ? snap.current : null);
1502
+ } catch {
1503
+ }
1504
+ };
1505
+ if (face && typeof face.subscribe === "function") {
1506
+ read();
1507
+ off = face.subscribe(read);
1508
+ }
1509
+ }
1510
+ } catch {
1511
+ }
1512
+ return () => {
1513
+ alive = false;
1514
+ if (typeof off === "function") off();
1515
+ };
1516
+ }, [sessionId]);
1517
+ return cur;
1518
+ }
1519
+ function chipBalanceText(p) {
1520
+ const b = p && p.balance;
1521
+ if (!b) return "\u4F59\u989D \u2026";
1522
+ if (b.status !== "ok") {
1523
+ if (b.status === "login-required") return "\u4F59\u989D\xB7\u9700\u767B\u5F55";
1524
+ if (b.status === "no-credential") return "\u4F59\u989D\xB7\u65E0\u5BC6\u94A5";
1525
+ if (b.status === "unsupported") return "\u4F59\u989D\xB7\u4E0D\u652F\u6301";
1526
+ return "\u4F59\u989D\xB7\u67E5\u8BE2\u5931\u8D25";
1527
+ }
1528
+ if (b.kind === "quota") {
1529
+ const v2 = Number(b.remaining);
1530
+ const s2 = Number.isFinite(v2) ? v2 >= 1e4 ? (v2 / 1e4).toFixed(1) + "\u4E07" : String(Math.round(v2)) : fmt(b.remaining);
1531
+ return "\u4F59\u989D \u5269 " + s2 + (b.unit ? " " + b.unit : "");
1532
+ }
1533
+ const info = Array.isArray(b.infos) && b.infos[0] ? b.infos[0] : null;
1534
+ if (!info) return "\u4F59\u989D \u2026";
1535
+ const cur = info.currency === "USD" ? "$" : "\xA5";
1536
+ const v = Number(info.totalBalance);
1537
+ const s = Number.isFinite(v) ? v >= 1e4 ? (v / 1e4).toFixed(2) + "\u4E07" : String(Math.round(v * 100) / 100) : fmt(info.totalBalance);
1538
+ return "\u4F59\u989D " + cur + s;
1539
+ }
1540
+ function BalanceChip(props) {
1541
+ const snap = useBalance(props && props.ctx);
1542
+ const data = snap.data;
1543
+ const providers = data && Array.isArray(data.providers) ? data.providers : [];
1544
+ const sel = useCurrentProvider(props && props.ctx, props && props.sessionId);
1545
+ const selProvider = sel && (sel.provider || sel.selected && sel.selected.provider) || null;
1546
+ const def = selProvider || (data && data.default ? data.default.provider : null);
1547
+ const cur = def ? providers.find((p) => p.id === def) : null;
1548
+ const accent = cur ? accentOf(cur.id) : "#94a3b8";
1549
+ const text = snap.error ? "\u4F59\u989D\xB7\u5931\u8D25" : !data ? snap.loading ? "\u4F59\u989D \u2026" : "\u4F59\u989D \u2014" : cur ? chipBalanceText(cur) : "\u4F59\u989D \u2014";
1550
+ const selModel = sel && (sel.model || sel.selected && sel.selected.model) || null;
1551
+ const title = cur ? (cur.displayName || cur.id) + (selModel ? " \xB7 \u5F53\u524D\u6A21\u578B " + selModel : "") + " \xB7 \u70B9\u51FB\u5728\u529F\u80FD\u575E\u67E5\u770B\u4F59\u989D\u8BE6\u60C5" : "\u70B9\u51FB\u6253\u5F00\u529F\u80FD\u575E \xB7 \u6A21\u578B\u4F59\u989D";
1552
+ return import_react6.default.createElement(
1553
+ "button",
1554
+ {
1555
+ type: "button",
1556
+ className: "dockchip" + (snap.error || cur && cur.balance && cur.balance.status === "error" ? " err" : ""),
1557
+ title,
1558
+ "aria-label": "\u6A21\u578B\u4F59\u989D",
1559
+ onClick: () => openPanel("balance", def ? { provider: def } : null)
1560
+ },
1561
+ import_react6.default.createElement("span", { className: "dockchip-dot", style: { background: accent } }),
1562
+ import_react6.default.createElement("span", null, text)
1563
+ );
1564
+ }
1565
+ var feature5 = {
1566
+ id: "balance",
1567
+ name: "\u6A21\u578B\u4F59\u989D",
1568
+ order: 120,
1569
+ accent: "#4d9fff",
1570
+ description: "\u5C55\u793A\u6240\u6709\u6A21\u578B Provider \u8D26\u6237\u4F59\u989D\uFF08Host \u62C9\u53D6\uFF0C5 \u5206\u949F\u81EA\u52A8\u5237\u65B0\uFF09",
1571
+ css: [
1572
+ ".dkb-note{color:var(--dsw-alias-label-secondary);font-size:12px;}",
1573
+ ".dkb-error{color:var(--dsw-alias-state-error-primary);}",
1574
+ ".dkb-rows{display:flex;flex-direction:column;gap:8px;}",
1575
+ ".dkb-row{border:1px solid var(--dsw-alias-border-l1);border-radius:8px;padding:8px 10px;display:flex;flex-direction:column;gap:5px;}",
1576
+ ".dkb-row-head{display:flex;align-items:center;gap:8px;flex-wrap:wrap;}",
1577
+ ".dkb-dot{width:8px;height:8px;border-radius:50%;flex:none;}",
1578
+ ".dkb-name{font-weight:600;font-size:13px;color:var(--dsw-alias-label-primary);}",
1579
+ ".dkb-default{color:var(--dsw-alias-accent,#4d9fff);font-size:11px;border:1px solid currentColor;border-radius:999px;padding:0 6px;}",
1580
+ ".dkb-badge{font-size:11px;border-radius:999px;padding:1px 8px;color:var(--dsw-alias-label-tertiary);border:1px solid var(--dsw-alias-border-l2);}",
1581
+ ".dkb-badge.ok{color:var(--dsw-alias-state-success-primary);border-color:currentColor;}",
1582
+ ".dkb-badge.warn{color:var(--dsw-alias-state-warning-primary);border-color:currentColor;}",
1583
+ ".dkb-badge.err{color:var(--dsw-alias-state-error-primary);border-color:currentColor;}",
1584
+ ".dkb-sub{color:var(--dsw-alias-label-tertiary);font-size:11px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}",
1585
+ ".dkb-chips{display:flex;flex-wrap:wrap;gap:4px;}",
1586
+ ".dkb-chip{font-size:10px;border-radius:6px;padding:1px 6px;}",
1587
+ ".dkb-bal{display:flex;gap:10px;flex-wrap:wrap;font-size:12px;color:var(--dsw-alias-label-primary);align-items:baseline;}",
1588
+ ".dkb-cur{font-weight:600;min-width:48px;}",
1589
+ ".dkb-total{font-weight:600;}",
1590
+ ".dkb-part{font-size:11px;color:var(--dsw-alias-label-tertiary);}",
1591
+ ".dkb-link{font-size:12px;text-decoration:none;}",
1592
+ ".dkb-link:hover{text-decoration:underline;}",
1593
+ ".dkb-foot{display:flex;align-items:center;justify-content:space-between;gap:8px;color:var(--dsw-alias-label-tertiary);font-size:12px;}",
1594
+ ".dkb-refresh{cursor:pointer;color:var(--dsw-alias-label-primary);background:transparent;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;padding:2px 10px;font-family:inherit;font-size:12px;}",
1595
+ ".dkb-refresh:hover{background:var(--dsw-alias-interactive-bg-hover);}",
1596
+ // chips 点击定位后的行高亮闪烁
1597
+ ".dkb-row.dkb-flash{animation:dkb-flash 1.8s var(--ds-ease-in-out);}",
1598
+ "@keyframes dkb-flash{0%,55%{border-color:var(--dsw-alias-accent,#4d9fff);box-shadow:0 0 0 2px color-mix(in srgb,var(--dsw-alias-accent,#4d9fff) 25%,transparent);}100%{border-color:var(--dsw-alias-border-l1);box-shadow:none;}}"
1599
+ ].join("\n"),
1600
+ View: BalanceView,
1601
+ HomeStat: BalanceStat,
1602
+ Chip: BalanceChip
1603
+ };
1604
+
1605
+ // src/client.jsx
1606
+ var DOCK_VERSION = "0.4.0";
1607
+ var BUILTIN_FEATURES = [feature, feature2, feature3, feature4, feature5];
1608
+ var PLANNED_FEATURES = [
1609
+ { id: "animation", name: "\u4EFB\u52A1\u52A8\u753B", order: 130, accent: "#f472b6", planned: true, description: "\u63A5\u5165\u8DEF\u7EBF\u56FE 0.5.0\uFF1A\u4EFB\u52A1\u8FDB\u5EA6\u52A8\u753B\u4E0E\u901A\u77E5" }
1610
+ ];
1611
+ var PLANNED_NOTES = {
1612
+ animation: "\u5F85\u63A5\u5165\uFF08\u8DEF\u7EBF\u56FE 0.5.0\uFF09\uFF1A\u4EFB\u52A1\u8FDB\u5EA6\u52A8\u753B\u4E0E\u5B8C\u6210\u901A\u77E5\u3002"
1613
+ };
1614
+ var externalDefs = [];
1615
+ var externalListeners = /* @__PURE__ */ new Set();
1616
+ function notifyExternal() {
1617
+ for (const fn of externalListeners) fn();
1618
+ }
1619
+ var dockBridge = {
1620
+ version: DOCK_VERSION,
1621
+ register(def) {
1622
+ if (!def || typeof def !== "object" || typeof def.id !== "string" || !def.id || typeof def.View !== "function") {
1623
+ console.error("[dsh-dock] dockBridge.register \u53C2\u6570\u4E0D\u7B26\uFF08\u9700 { id, name, View }\uFF09:", def);
1624
+ return () => {
1625
+ };
1626
+ }
1627
+ const entry = {
1628
+ id: def.id,
1629
+ name: String(def.name || def.id),
1630
+ order: typeof def.order === "number" ? def.order : 500,
1631
+ accent: typeof def.accent === "string" ? def.accent : "#94a3b8",
1632
+ description: String(def.description || ""),
1633
+ css: typeof def.css === "string" ? def.css : "",
1634
+ View: def.View,
1635
+ HomeStat: typeof def.HomeStat === "function" ? def.HomeStat : null,
1636
+ external: true,
1637
+ package: typeof def.package === "string" ? def.package : ""
1638
+ };
1639
+ const i = externalDefs.findIndex((x) => x.id === entry.id);
1640
+ if (i >= 0) externalDefs[i] = entry;
1641
+ else externalDefs.push(entry);
1642
+ if (entry.css) ensureCss();
1643
+ notifyExternal();
1644
+ return () => {
1645
+ const j = externalDefs.indexOf(entry);
1646
+ if (j >= 0) {
1647
+ externalDefs.splice(j, 1);
1648
+ notifyExternal();
1649
+ }
1650
+ };
1651
+ },
1652
+ features() {
1653
+ return externalDefs.map((x) => Object.assign({}, x));
1654
+ }
1655
+ };
1656
+ function allModules() {
1657
+ return BUILTIN_FEATURES.concat(PLANNED_FEATURES, externalDefs).slice().sort((a, b) => (a.order || 500) - (b.order || 500));
1658
+ }
1659
+ var SHELL_CSS = [
1660
+ ".dock-root{display:flex;flex-direction:column;gap:12px;padding:4px 0;color:var(--dsw-alias-label-primary);font-size:13px;}",
1661
+ ".dock-intro{color:var(--dsw-alias-label-secondary);line-height:1.6;}",
1662
+ ".dock-card{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-1);border-radius:10px;padding:12px 14px;display:flex;flex-direction:column;gap:8px;}",
1663
+ ".dock-card-head{display:flex;align-items:center;gap:8px;}",
1664
+ ".dock-dot{width:8px;height:8px;border-radius:50%;background:var(--dsw-alias-label-secondary);flex:none;}",
1665
+ ".dock-dot.on{background:var(--dsw-alias-state-success-primary);}",
1666
+ ".dock-dot.err{background:var(--dsw-alias-state-error-primary);}",
1667
+ ".dock-name{font-weight:600;flex:none;}",
1668
+ ".dock-desc{color:var(--dsw-alias-label-secondary);flex:1;}",
1669
+ ".dock-switch{flex:none;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-secondary);border-radius:999px;padding:3px 10px;cursor:pointer;font-size:12px;}",
1670
+ ".dock-switch.on{color:var(--dsw-alias-state-success-primary);border-color:var(--dsw-alias-state-success-primary);}",
1671
+ ".dock-badge{flex:none;border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);border-radius:999px;padding:3px 10px;font-size:12px;}",
1672
+ ".dock-body{border-top:1px solid var(--dsw-alias-border-l1);padding-top:8px;color:var(--dsw-alias-label-secondary);display:flex;flex-direction:column;gap:4px;}",
1673
+ // 侧栏入口按钮(docke2- 前缀)+ 功能坞弹出面板(dockm- 前缀,仿 dsh 设置的居中模态:遮罩 + 对话框,左导航 + 右内容)
1674
+ ".docke2-btn{box-sizing:border-box;cursor:pointer;display:inline-flex;align-items:center;gap:6px;border:none;background:transparent;color:var(--dsw-alias-label-secondary);border-radius:10px;height:32px;font-family:inherit;font-size:13px;line-height:32px;transition:background .15s var(--ds-ease-in-out),color .15s var(--ds-ease-in-out);}",
1675
+ ".docke2-btn:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary);}",
1676
+ ".docke2-btn.docke2-on{color:var(--dsw-alias-accent,#4d9fff);background:var(--dsw-alias-accent-soft,color-mix(in srgb,var(--dsw-alias-accent,#4d9fff) 12%,transparent));}",
1677
+ ".docke2-label{white-space:nowrap;overflow:hidden;}",
1678
+ ".dockm-backdrop{position:fixed;inset:0;z-index:80;display:flex;align-items:center;justify-content:center;background:color-mix(in srgb,var(--dsw-alias-bg-layer-1) 55%,transparent);backdrop-filter:blur(4px);pointer-events:auto;animation:dockm-fade .15s var(--ds-ease-in-out);}",
1679
+ "@keyframes dockm-fade{from{opacity:0}to{opacity:1}}",
1680
+ ".dockm-dialog{box-sizing:border-box;position:relative;width:min(1080px,calc(100vw - 32px));height:min(700px,calc(100vh - 32px));display:flex;flex-direction:column;border-radius:16px;border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);box-shadow:0 20px 64px rgb(0 0 0 / .32);overflow:hidden;animation:dockm-pop .18s var(--ds-ease-in-out);}",
1681
+ "@keyframes dockm-pop{from{opacity:0;transform:translateY(10px) scale(.985)}to{opacity:1;transform:none}}",
1682
+ ".dockm-head{flex:none;display:flex;align-items:center;gap:8px;padding:12px 16px;border-bottom:1px solid var(--dsw-alias-border-l1);cursor:move;user-select:none;touch-action:none;}",
1683
+ ".dockm-title{font-weight:600;font-size:14px;}",
1684
+ ".dockm-sub{color:var(--dsw-alias-label-tertiary);font-size:12px;}",
1685
+ ".dockm-ctrls{margin-left:auto;flex:none;display:flex;align-items:center;gap:2px;}",
1686
+ ".dockm-close{cursor:pointer;border:none;background:transparent;color:var(--dsw-alias-label-tertiary);border-radius:8px;width:28px;height:28px;display:inline-flex;align-items:center;justify-content:center;font-size:14px;}",
1687
+ ".dockm-close:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary);}",
1688
+ ".dockm-win{cursor:pointer;border:none;background:transparent;color:var(--dsw-alias-label-tertiary);border-radius:8px;width:28px;height:28px;display:inline-flex;align-items:center;justify-content:center;font-size:13px;}",
1689
+ ".dockm-win:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary);}",
1690
+ ".dockm-dialog.dockm-max{border-radius:12px;}",
1691
+ ".dockm-dialog.dockm-min{height:auto;min-height:0;}",
1692
+ ".dockm-dialog.dockm-min .dockm-body{display:none;}",
1693
+ ".dockm-resize{position:absolute;right:0;bottom:0;width:16px;height:16px;cursor:nwse-resize;z-index:3;touch-action:none;}",
1694
+ ".dockm-body{flex:1;min-height:0;display:flex;}",
1695
+ ".dockm-nav{flex:none;width:176px;display:flex;flex-direction:column;gap:2px;padding:10px 8px;overflow-y:auto;border-right:1px solid var(--dsw-alias-border-l1);}",
1696
+ ".dockm-nav-item{display:flex;align-items:center;gap:8px;padding:8px 10px;border:none;background:transparent;border-radius:10px;cursor:pointer;color:var(--dsw-alias-label-secondary);font-family:inherit;font-size:13px;text-align:left;transition:background .15s var(--ds-ease-in-out),color .15s var(--ds-ease-in-out);}",
1697
+ ".dockm-nav-item:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary);}",
1698
+ ".dockm-nav-item.on{background:var(--dsw-alias-accent-soft,color-mix(in srgb,var(--dsw-alias-accent,#4d9fff) 12%,transparent));color:var(--dsw-alias-label-primary);font-weight:600;}",
1699
+ ".dockm-nav-item .dockm-badge{margin-left:auto;}",
1700
+ ".dockm-dot{width:8px;height:8px;border-radius:50%;flex:none;}",
1701
+ ".dockm-badge{flex:none;font-size:11px;border-radius:999px;padding:0 8px;color:var(--dsw-alias-label-tertiary);border:1px solid var(--dsw-alias-border-l2);}",
1702
+ ".dockm-content{flex:1;min-width:0;display:flex;flex-direction:column;gap:12px;padding:16px 18px;overflow-y:auto;}",
1703
+ ".dockm-content-head{display:flex;flex-direction:column;gap:4px;}",
1704
+ ".dockm-name{display:flex;align-items:center;gap:8px;font-weight:600;font-size:15px;}",
1705
+ ".dockm-desc{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:1.6;}",
1706
+ ".dockm-view{display:flex;flex-direction:column;gap:8px;border-top:1px solid var(--dsw-alias-border-l1);padding-top:10px;}",
1707
+ ".dockm-note{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:1.6;border-top:1px solid var(--dsw-alias-border-l1);padding-top:10px;}",
1708
+ ".dockm-err{color:var(--dsw-alias-state-error-primary);}",
1709
+ ".dockm-foot{margin-top:auto;display:flex;align-items:center;gap:8px;color:var(--dsw-alias-label-tertiary);font-size:11px;border-top:1px solid var(--dsw-alias-border-l1);padding-top:10px;flex-wrap:wrap;}",
1710
+ ".dockm-switch{cursor:pointer;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-secondary);border-radius:999px;padding:4px 12px;font-family:inherit;font-size:12px;margin-left:auto;}",
1711
+ ".dockm-switch:hover{color:var(--dsw-alias-label-primary);}",
1712
+ ".dockm-switch.on{color:var(--dsw-alias-state-success-primary);border-color:currentColor;}",
1713
+ // 首页总揽(dockh- 前缀):导航首项图标 + 模块卡片网格
1714
+ ".dockm-navhome{display:inline-flex;align-items:center;color:var(--dsw-alias-accent,#4d9fff);flex:none;}",
1715
+ ".dockh-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:10px;}",
1716
+ ".dockh-card{display:flex;flex-direction:column;gap:6px;border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-1);border-radius:10px;padding:10px 12px;cursor:pointer;transition:border-color .15s var(--ds-ease-in-out);}",
1717
+ ".dockh-card:hover,.dockh-card:focus-visible{border-color:var(--dsw-alias-accent,#4d9fff);outline:none;}",
1718
+ ".dockh-head{display:flex;align-items:center;gap:8px;}",
1719
+ ".dockh-name{font-weight:600;flex:none;}",
1720
+ ".dockh-badge{margin-left:auto;flex:none;font-size:11px;border-radius:999px;padding:0 8px;color:var(--dsw-alias-label-tertiary);border:1px solid var(--dsw-alias-border-l2);}",
1721
+ ".dockh-badge.on{color:var(--dsw-alias-state-success-primary);border-color:currentColor;}",
1722
+ ".dockh-badge.off{border-style:dashed;}",
1723
+ ".dockh-desc{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:1.5;}",
1724
+ ".dockh-stat{color:var(--dsw-alias-label-tertiary);font-size:12px;border-top:1px solid var(--dsw-alias-border-l1);padding-top:6px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}",
1725
+ ".dockh-foot{display:flex;align-items:center;gap:8px;}",
1726
+ ".dockh-go{color:var(--dsw-alias-label-tertiary);font-size:11px;}",
1727
+ // 会话输入区工具行 chips(dockchip- 前缀):余额/用量随身小控件,挂在模型选择器左侧
1728
+ ".dockchip-row{display:inline-flex;align-items:center;gap:4px;min-width:0;}",
1729
+ ".dockchip{display:inline-flex;align-items:center;gap:5px;cursor:pointer;border:none;background:transparent;color:var(--dsw-alias-label-tertiary);border-radius:8px;padding:2px 8px;font-family:inherit;font-size:11px;line-height:18px;white-space:nowrap;transition:background .15s var(--ds-ease-in-out),color .15s var(--ds-ease-in-out);}",
1730
+ ".dockchip:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary);}",
1731
+ ".dockchip .dockchip-dot{width:6px;height:6px;border-radius:50%;flex:none;}",
1732
+ ".dockchip.err{color:var(--dsw-alias-state-error-primary);}"
1733
+ ].join("\n");
1734
+ var dockCssTag = null;
1735
+ function fullDockCss() {
1736
+ const parts = [SHELL_CSS];
1737
+ for (const f of BUILTIN_FEATURES) if (f.css) parts.push(f.css);
1738
+ for (const f of externalDefs) if (f.css) parts.push(f.css);
1739
+ return parts.join("\n");
1740
+ }
1741
+ function ensureCss() {
1742
+ if (typeof document === "undefined") return;
1743
+ try {
1744
+ if (!dockCssTag || !dockCssTag.isConnected) {
1745
+ if (document.querySelector('style[data-plugin-css="dsh-dock"]')) {
1746
+ dockCssTag = document.querySelector('style[data-plugin-css="dsh-dock"]');
1747
+ } else {
1748
+ dockCssTag = document.createElement("style");
1749
+ dockCssTag.dataset.pluginCss = "dsh-dock";
1750
+ document.head.appendChild(dockCssTag);
1751
+ }
1752
+ }
1753
+ dockCssTag.textContent = fullDockCss();
1754
+ } catch (e) {
1755
+ console.error("[dsh-dock] ensureCss failed:", e && e.message ? e.message : String(e));
1756
+ }
1757
+ }
1758
+ function useExternalVersion() {
1759
+ const [, bump] = import_react7.default.useReducer((n) => n + 1, 0);
1760
+ import_react7.default.useEffect(() => {
1761
+ externalListeners.add(bump);
1762
+ return () => {
1763
+ externalListeners.delete(bump);
1764
+ };
1765
+ }, []);
1766
+ }
1767
+ var lastGeom = { x: null, y: null, w: null, h: null };
1768
+ function DockIcon() {
1769
+ return import_react7.default.createElement(
1770
+ "svg",
1771
+ { width: 16, height: 16, viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: 1.3, "aria-hidden": true },
1772
+ import_react7.default.createElement("rect", { x: 2.5, y: 2.5, width: 4.4, height: 4.4, rx: 1.2 }),
1773
+ import_react7.default.createElement("rect", { x: 9, y: 2.5, width: 4.4, height: 4.4, rx: 1.2 }),
1774
+ import_react7.default.createElement("rect", { x: 2.5, y: 9, width: 4.4, height: 4.4, rx: 1.2 }),
1775
+ import_react7.default.createElement("rect", { x: 9, y: 9, width: 4.4, height: 4.4, rx: 1.2 })
1776
+ );
1777
+ }
1778
+ function DockEntry(props) {
1779
+ const wide = !!props.wide;
1780
+ const [open, setOpen] = import_react7.default.useState(panelNav.open);
1781
+ import_react7.default.useEffect(() => subscribePanel(() => setOpen(panelNav.open)), []);
1782
+ return import_react7.default.createElement("button", {
1783
+ type: "button",
1784
+ className: "docke2-btn" + (open ? " docke2-on" : ""),
1785
+ style: wide ? { marginLeft: "auto", transform: "translateY(46px)", zIndex: 1, height: 42, lineHeight: "42px", padding: "0 12px" } : { transform: "translateY(44px)", zIndex: 1, width: 36, height: 36, justifyContent: "center", padding: 0 },
1786
+ title: "\u529F\u80FD\u575E",
1787
+ "aria-label": "\u529F\u80FD\u575E",
1788
+ "aria-expanded": open,
1789
+ onClick: () => setPanelOpen(!open)
1790
+ }, import_react7.default.createElement(DockIcon, null), wide ? import_react7.default.createElement("span", { className: "docke2-label" }, "\u529F\u80FD\u575E") : null);
1791
+ }
1792
+ function DockModal() {
1793
+ const [nav, setNav] = import_react7.default.useState({ open: panelNav.open, active: panelNav.active, params: panelNav.params });
1794
+ import_react7.default.useEffect(() => subscribePanel(() => setNav({ open: panelNav.open, active: panelNav.active, params: panelNav.params })), []);
1795
+ const open = nav.open || typeof document === "undefined";
1796
+ const active = nav.active;
1797
+ const setActive = navigatePanel;
1798
+ const navParams = nav.params;
1799
+ const [, force] = import_react7.default.useReducer((n) => n + 1, 0);
1800
+ useExternalVersion();
1801
+ const [win, setWin] = import_react7.default.useState(() => ({ mode: "normal", x: null, y: null, w: null, h: null }));
1802
+ const dlgRef = import_react7.default.useRef(null);
1803
+ import_react7.default.useEffect(() => {
1804
+ if (lastGeom.w) setWin({ mode: "normal", x: lastGeom.x, y: lastGeom.y, w: lastGeom.w, h: lastGeom.h });
1805
+ }, []);
1806
+ function beginDrag(e, type) {
1807
+ if (win.mode !== "normal" || e.button !== 0) return;
1808
+ if (type === "move" && e.target && e.target.closest && e.target.closest("button,select,input")) return;
1809
+ const node = dlgRef.current;
1810
+ if (!node || typeof window === "undefined") return;
1811
+ const rect = node.getBoundingClientRect();
1812
+ const startX = e.clientX, startY = e.clientY;
1813
+ const origin = { left: rect.left, top: rect.top, w: rect.width, h: rect.height };
1814
+ const clampX = (v) => Math.min(Math.max(8, v), window.innerWidth - origin.w - 8);
1815
+ const clampY = (v) => Math.min(Math.max(8, v), window.innerHeight - origin.h - 8);
1816
+ const onMove = (ev) => {
1817
+ const dx = ev.clientX - startX, dy = ev.clientY - startY;
1818
+ if (type === "move") setWin((s) => Object.assign({}, s, { x: clampX(origin.left + dx), y: clampY(origin.top + dy) }));
1819
+ else setWin((s) => Object.assign({}, s, {
1820
+ w: Math.max(640, Math.min(origin.w + dx, window.innerWidth - 16)),
1821
+ h: Math.max(420, Math.min(origin.h + dy, window.innerHeight - 16))
1822
+ }));
1823
+ };
1824
+ const onUp = () => {
1825
+ window.removeEventListener("pointermove", onMove);
1826
+ window.removeEventListener("pointerup", onUp);
1827
+ setWin((s) => {
1828
+ lastGeom = { x: s.x, y: s.y, w: s.w, h: s.h };
1829
+ return s;
1830
+ });
1831
+ };
1832
+ window.addEventListener("pointermove", onMove);
1833
+ window.addEventListener("pointerup", onUp);
1834
+ }
1835
+ if (!open) return null;
1836
+ const MODULES = allModules();
1837
+ const isHome = active === "home";
1838
+ const mod = isHome ? null : MODULES.find((m) => m.id === active) || MODULES[0];
1839
+ const st = mod ? stateOf(mod.id) : null;
1840
+ const View = mod ? mod.View : null;
1841
+ const viewNode = mod && View ? import_react7.default.createElement(
1842
+ "div",
1843
+ { className: "dockm-view" },
1844
+ import_react7.default.createElement(
1845
+ mod.external ? FeatureBoundary : import_react7.default.Fragment,
1846
+ null,
1847
+ import_react7.default.createElement(View, { ctx: ctxRef.current, feature: mod, params: navParams })
1848
+ )
1849
+ ) : null;
1850
+ const enabledCount = MODULES.filter((m) => {
1851
+ const s = stateOf(m.id);
1852
+ return !!(s && s.enabled);
1853
+ }).length;
1854
+ const dialogStyle = win.mode === "max" ? { position: "fixed", left: 10, top: 10, right: 10, bottom: 10, width: "auto", height: "auto" } : win.x != null || win.y != null || win.w != null || win.h != null ? {
1855
+ position: "fixed",
1856
+ left: win.x != null ? win.x : void 0,
1857
+ top: win.y != null ? win.y : void 0,
1858
+ width: win.w != null ? win.w : void 0,
1859
+ height: win.mode === "min" ? "auto" : win.h != null ? win.h : void 0
1860
+ } : null;
1861
+ return import_react7.default.createElement(
1862
+ "div",
1863
+ { className: "dockm-backdrop", onClick: () => setPanelOpen(false) },
1864
+ import_react7.default.createElement(
1865
+ "div",
1866
+ {
1867
+ className: "dockm-dialog" + (win.mode === "max" ? " dockm-max" : "") + (win.mode === "min" ? " dockm-min" : ""),
1868
+ style: dialogStyle,
1869
+ ref: dlgRef,
1870
+ onClick: (e) => e.stopPropagation()
1871
+ },
1872
+ import_react7.default.createElement(
1873
+ "div",
1874
+ {
1875
+ className: "dockm-head",
1876
+ onPointerDown: (e) => beginDrag(e, "move"),
1877
+ onDoubleClick: () => setWin((s) => Object.assign({}, s, { mode: s.mode === "max" ? "normal" : "max" }))
1878
+ },
1879
+ import_react7.default.createElement(DockIcon, null),
1880
+ import_react7.default.createElement("span", { className: "dockm-title" }, "\u529F\u80FD\u575E"),
1881
+ import_react7.default.createElement("span", { className: "dockm-sub" }, "dsh-dock \xB7 \u4E5F\u53EF\u5728 \u8BBE\u7F6E \u2192 \u529F\u80FD\u575E \u6253\u5F00\u7BA1\u7406\u9875"),
1882
+ import_react7.default.createElement(
1883
+ "span",
1884
+ { className: "dockm-ctrls" },
1885
+ import_react7.default.createElement("button", {
1886
+ type: "button",
1887
+ className: "dockm-win",
1888
+ title: win.mode === "min" ? "\u8FD8\u539F" : "\u6700\u5C0F\u5316",
1889
+ onClick: () => setWin((s) => Object.assign({}, s, { mode: s.mode === "min" ? "normal" : "min" }))
1890
+ }, "\u2581"),
1891
+ import_react7.default.createElement("button", {
1892
+ type: "button",
1893
+ className: "dockm-win",
1894
+ title: win.mode === "max" ? "\u8FD8\u539F" : "\u6700\u5927\u5316",
1895
+ onClick: () => setWin((s) => Object.assign({}, s, { mode: s.mode === "max" ? "normal" : "max" }))
1896
+ }, win.mode === "max" ? "\u2750" : "\u25A2"),
1897
+ import_react7.default.createElement("button", { type: "button", className: "dockm-close", "aria-label": "\u5173\u95ED", title: "\u5173\u95ED", onClick: () => setPanelOpen(false) }, "\u2715")
1898
+ )
1899
+ ),
1900
+ import_react7.default.createElement(
1901
+ "div",
1902
+ { className: "dockm-body" },
1903
+ import_react7.default.createElement(
1904
+ "nav",
1905
+ { className: "dockm-nav", "aria-label": "\u529F\u80FD\u6A21\u5757" },
1906
+ import_react7.default.createElement(
1907
+ "button",
1908
+ {
1909
+ type: "button",
1910
+ key: "home",
1911
+ className: "dockm-nav-item" + (isHome ? " on" : ""),
1912
+ onClick: () => setActive("home")
1913
+ },
1914
+ import_react7.default.createElement("span", { className: "dockm-navhome" }, import_react7.default.createElement(DockIcon, null)),
1915
+ import_react7.default.createElement("span", null, "\u9996\u9875")
1916
+ ),
1917
+ MODULES.map((m) => import_react7.default.createElement(
1918
+ "button",
1919
+ {
1920
+ type: "button",
1921
+ key: m.id,
1922
+ className: "dockm-nav-item" + (m.id === active ? " on" : ""),
1923
+ onClick: () => setActive(m.id)
1924
+ },
1925
+ import_react7.default.createElement("span", { className: "dockm-dot", style: { background: m.accent } }),
1926
+ import_react7.default.createElement("span", null, m.name),
1927
+ m.planned ? import_react7.default.createElement("span", { className: "dockm-badge" }, "\u89C4\u5212\u4E2D") : null,
1928
+ m.external ? import_react7.default.createElement("span", { className: "dockm-badge" }, "\u5916\u90E8") : null
1929
+ ))
1930
+ ),
1931
+ import_react7.default.createElement(
1932
+ "div",
1933
+ { className: "dockm-content" },
1934
+ import_react7.default.createElement(
1935
+ "div",
1936
+ { className: "dockm-content-head" },
1937
+ import_react7.default.createElement(
1938
+ "div",
1939
+ { className: "dockm-name" },
1940
+ isHome ? import_react7.default.createElement("span", { className: "dockm-navhome" }, import_react7.default.createElement(DockIcon, null)) : import_react7.default.createElement("span", { className: "dockm-dot", style: { background: mod.accent } }),
1941
+ isHome ? "\u9996\u9875" : mod.name,
1942
+ !isHome && mod.planned ? import_react7.default.createElement("span", { className: "dockm-badge" }, "\u89C4\u5212\u4E2D") : null,
1943
+ !isHome && mod.external ? import_react7.default.createElement("span", { className: "dockm-badge" }, "\u5916\u90E8\u5305" + (mod.package ? " \xB7 " + mod.package : "")) : null
1944
+ ),
1945
+ import_react7.default.createElement(
1946
+ "div",
1947
+ { className: "dockm-desc" },
1948
+ isHome ? "\u6240\u6709\u5B50\u529F\u80FD\u603B\u63FD\uFF1A\u8FD0\u884C\u72B6\u6001\u3001\u6982\u8981\u4E0E\u5FEB\u6377\u5F00\u5173\uFF0C\u70B9\u51FB\u5361\u7247\u8FDB\u5165\u5BF9\u5E94\u529F\u80FD\u9875\u3002" : mod.description
1949
+ )
1950
+ ),
1951
+ isHome ? import_react7.default.createElement("div", { className: "dockm-view" }, import_react7.default.createElement(HomeView, { ctx: ctxRef.current, onOpen: setActive, onToggle: force })) : mod.planned ? import_react7.default.createElement("div", { className: "dockm-note" }, PLANNED_NOTES[mod.id] || "\u5F85\u63A5\u5165\uFF1A\u89C1 README \u8DEF\u7EBF\u56FE") : st && st.enabled && viewNode ? viewNode : st && st.error ? import_react7.default.createElement("div", { className: "dockm-note dockm-err" }, "\u529F\u80FD\u51FA\u9519\uFF1A" + st.error) : import_react7.default.createElement("div", { className: "dockm-note" }, "\u8BE5\u529F\u80FD\u5F53\u524D\u4E3A\u505C\u7528\u72B6\u6001\uFF08\u8BB0\u5FC6\u6001\u968F\u9875\u9762\u751F\u547D\u5468\u671F\uFF0C0.5.0 \u8D77\u6301\u4E45\u5316\uFF09"),
1952
+ import_react7.default.createElement(
1953
+ "div",
1954
+ { className: "dockm-foot" },
1955
+ import_react7.default.createElement("span", null, isHome ? "\u529F\u80FD\u575E v" + DOCK_VERSION + " \xB7 \u5171 " + MODULES.length + " \u4E2A\u529F\u80FD\u6A21\u5757\uFF0C" + enabledCount + " \u4E2A\u5DF2\u542F\u7528" : "\u529F\u80FD\u575E v" + DOCK_VERSION + " \xB7 \u65B0\u529F\u80FD\u6309\u8DEF\u7EBF\u56FE\u8FFD\u52A0"),
1956
+ !isHome && mod && !mod.planned && st ? import_react7.default.createElement("button", {
1957
+ type: "button",
1958
+ className: "dockm-switch" + (st.enabled ? " on" : ""),
1959
+ onClick: () => {
1960
+ toggleFeature(mod.id);
1961
+ force();
1962
+ }
1963
+ }, st.enabled ? "\u5DF2\u542F\u7528\uFF08\u70B9\u51FB\u505C\u7528\uFF09" : "\u5DF2\u505C\u7528\uFF08\u70B9\u51FB\u542F\u7528\uFF09") : null,
1964
+ !isHome && mod && typeof mod.Chip === "function" ? import_react7.default.createElement("button", {
1965
+ type: "button",
1966
+ className: "dockm-switch" + (chipShown(mod.id) ? " on" : ""),
1967
+ title: "\u63A7\u5236\u4F1A\u8BDD\u8F93\u5165\u533A\uFF08\u6A21\u578B\u9009\u62E9\u5668\u5DE6\u4FA7\uFF09\u662F\u5426\u663E\u793A\u672C\u529F\u80FD\u7684\u968F\u8EAB\u5C0F\u63A7\u4EF6",
1968
+ onClick: () => {
1969
+ setChipShown(mod.id, !chipShown(mod.id));
1970
+ force();
1971
+ }
1972
+ }, chipShown(mod.id) ? "\u4F1A\u8BDD\u9875\u663E\u793A\u4E2D" : "\u4F1A\u8BDD\u9875\u5DF2\u9690\u85CF") : null
1973
+ )
1974
+ )
1975
+ ),
1976
+ win.mode === "normal" ? import_react7.default.createElement("div", { className: "dockm-resize", onPointerDown: (e) => beginDrag(e, "size") }) : null
1977
+ )
1978
+ );
1979
+ }
1980
+ function HomeView(props) {
1981
+ const ctx = props && props.ctx;
1982
+ const [, force] = import_react7.default.useReducer((n) => n + 1, 0);
1983
+ useExternalVersion();
1984
+ const open = (id) => {
1985
+ if (props && typeof props.onOpen === "function") props.onOpen(id);
1986
+ };
1987
+ return import_react7.default.createElement(
1988
+ "div",
1989
+ { className: "dockh-grid" },
1990
+ allModules().map((m) => {
1991
+ const st = stateOf(m.id);
1992
+ const enabled = !!(st && st.enabled);
1993
+ const Stat = m.HomeStat;
1994
+ const statNode = m.planned ? import_react7.default.createElement("span", null, PLANNED_NOTES[m.id] || "\u5F85\u63A5\u5165\uFF1A\u89C1 README \u8DEF\u7EBF\u56FE") : enabled && Stat ? import_react7.default.createElement(
1995
+ m.external ? FeatureBoundary : import_react7.default.Fragment,
1996
+ null,
1997
+ import_react7.default.createElement(Stat, { ctx })
1998
+ ) : import_react7.default.createElement("span", null, "\u5DF2\u505C\u7528\uFF0C\u542F\u7528\u540E\u5728\u6B64\u5C55\u793A\u8FD0\u884C\u6982\u8981");
1999
+ return import_react7.default.createElement(
2000
+ "div",
2001
+ {
2002
+ key: m.id,
2003
+ className: "dockh-card",
2004
+ role: "button",
2005
+ tabIndex: 0,
2006
+ onClick: () => open(m.id),
2007
+ onKeyDown: (e) => {
2008
+ if (e.key === "Enter" || e.key === " ") {
2009
+ e.preventDefault();
2010
+ open(m.id);
2011
+ }
2012
+ }
2013
+ },
2014
+ import_react7.default.createElement(
2015
+ "div",
2016
+ { className: "dockh-head" },
2017
+ import_react7.default.createElement("span", { className: "dockm-dot", style: { background: m.accent } }),
2018
+ import_react7.default.createElement("span", { className: "dockh-name" }, m.name),
2019
+ m.external ? import_react7.default.createElement("span", { className: "dockh-badge", title: m.package || void 0 }, "\u5916\u90E8") : null,
2020
+ import_react7.default.createElement(
2021
+ "span",
2022
+ { className: "dockh-badge" + (m.planned ? "" : enabled ? " on" : " off") },
2023
+ m.planned ? "\u89C4\u5212\u4E2D" : enabled ? "\u5DF2\u542F\u7528" : "\u5DF2\u505C\u7528"
2024
+ )
2025
+ ),
2026
+ import_react7.default.createElement("div", { className: "dockh-desc" }, m.description),
2027
+ import_react7.default.createElement("div", { className: "dockh-stat" }, statNode),
2028
+ import_react7.default.createElement(
2029
+ "div",
2030
+ { className: "dockh-foot" },
2031
+ import_react7.default.createElement("span", { className: "dockh-go" }, "\u67E5\u770B\u8BE6\u60C5 \u2192"),
2032
+ m.planned ? null : import_react7.default.createElement("button", {
2033
+ type: "button",
2034
+ className: "dockm-switch" + (enabled ? " on" : ""),
2035
+ onClick: (e) => {
2036
+ e.stopPropagation();
2037
+ toggleFeature(m.id);
2038
+ if (props && typeof props.onToggle === "function") props.onToggle();
2039
+ force();
2040
+ }
2041
+ }, enabled ? "\u505C\u7528" : "\u542F\u7528")
2042
+ )
2043
+ );
2044
+ })
2045
+ );
2046
+ }
2047
+ function DockPanel() {
2048
+ const ctx = ctxRef.current;
2049
+ const [, force] = import_react7.default.useReducer((n) => n + 1, 0);
2050
+ useExternalVersion();
2051
+ const toggle = (id) => {
2052
+ toggleFeature(id);
2053
+ force();
2054
+ };
2055
+ return import_react7.default.createElement(
2056
+ "div",
2057
+ { className: "dock-root" },
2058
+ import_react7.default.createElement(
2059
+ "div",
2060
+ { className: "dock-intro" },
2061
+ "\u529F\u80FD\u575E\uFF08dsh-dock\uFF09\xB7 \u6240\u6709\u5C0F\u529F\u80FD\u96C6\u4E2D\u5728\u8FD9\u4E00\u4E2A\u9762\u677F\u91CC\u7BA1\u7406\u3002v0.4.0 \u8D77\u6BCF\u4E2A\u529F\u80FD\u662F\u72EC\u7ACB\u6A21\u5757\uFF08features/<id>/\uFF09\uFF0C",
2062
+ "\u53EF\u5355\u72EC\u63D0\u53D6\u6253\u5305\u53D1\u5E03\uFF08scripts/extract-feature.mjs\uFF09\uFF1B\u72EC\u7ACB\u53D1\u5E03\u7684\u529F\u80FD\u5305\u88C5\u56DE\u540E\u7ECF dockBridge \u6CE8\u518C\u8FDB\u672C\u9762\u677F\u3002"
2063
+ ),
2064
+ allModules().map((f) => {
2065
+ const st = stateOf(f.id);
2066
+ const View = f.View;
2067
+ const viewNode = !f.planned && st.enabled && View ? import_react7.default.createElement(
2068
+ "div",
2069
+ { className: "dock-body" },
2070
+ import_react7.default.createElement(
2071
+ f.external ? FeatureBoundary : import_react7.default.Fragment,
2072
+ null,
2073
+ import_react7.default.createElement(View, { ctx, feature: f })
2074
+ )
2075
+ ) : null;
2076
+ return import_react7.default.createElement(
2077
+ "div",
2078
+ { className: "dock-card", key: f.id },
2079
+ import_react7.default.createElement(
2080
+ "div",
2081
+ { className: "dock-card-head" },
2082
+ import_react7.default.createElement("span", { className: "dock-dot" + (st.error ? " err" : st.enabled ? " on" : "") }),
2083
+ import_react7.default.createElement("span", { className: "dock-name" }, f.name),
2084
+ import_react7.default.createElement("span", { className: "dock-desc" }, f.description + (f.external ? "\uFF08\u6765\u81EA\u5916\u90E8\u5305" + (f.package ? " " + f.package : "") + "\uFF09" : "")),
2085
+ f.planned ? import_react7.default.createElement("span", { className: "dock-badge" }, "\u89C4\u5212\u4E2D") : import_react7.default.createElement("button", {
2086
+ className: "dock-switch" + (st.enabled ? " on" : ""),
2087
+ onClick: () => toggle(f.id)
2088
+ }, st.enabled ? "\u5DF2\u542F\u7528" : "\u5DF2\u505C\u7528"),
2089
+ !f.planned && typeof f.Chip === "function" ? import_react7.default.createElement("button", {
2090
+ className: "dock-switch" + (chipShown(f.id) ? " on" : ""),
2091
+ title: "\u63A7\u5236\u4F1A\u8BDD\u8F93\u5165\u533A\uFF08\u6A21\u578B\u9009\u62E9\u5668\u5DE6\u4FA7\uFF09\u662F\u5426\u663E\u793A\u672C\u529F\u80FD\u7684\u968F\u8EAB\u5C0F\u63A7\u4EF6",
2092
+ onClick: () => {
2093
+ setChipShown(f.id, !chipShown(f.id));
2094
+ force();
2095
+ }
2096
+ }, chipShown(f.id) ? "\u4F1A\u8BDD\u9875\u663E\u793A" : "\u4F1A\u8BDD\u9875\u9690\u85CF") : null
2097
+ ),
2098
+ f.planned ? import_react7.default.createElement("div", { className: "dock-body" }, PLANNED_NOTES[f.id] || "\u5F85\u63A5\u5165\uFF1A\u89C1 README \u8DEF\u7EBF\u56FE") : st.error ? import_react7.default.createElement("div", { className: "dock-body dockm-err" }, "\u529F\u80FD\u51FA\u9519\uFF1A" + st.error) : null,
2099
+ viewNode
2100
+ );
2101
+ })
2102
+ );
2103
+ }
2104
+ function DockChips(props) {
2105
+ const [, force] = import_react7.default.useReducer((n) => n + 1, 0);
2106
+ import_react7.default.useEffect(() => subscribeFeatureState(() => force()), []);
2107
+ const items = [];
2108
+ for (const f of allModules()) {
2109
+ if (f.planned || !stateOf(f.id).enabled || !chipShown(f.id) || typeof f.Chip !== "function") continue;
2110
+ items.push(import_react7.default.createElement(f.Chip, {
2111
+ key: f.id,
2112
+ ctx: props.ctx,
2113
+ feature: f,
2114
+ session: props.session,
2115
+ sessionId: props.sessionId,
2116
+ input: props.input
2117
+ }));
2118
+ }
2119
+ if (items.length === 0) return null;
2120
+ return import_react7.default.createElement("div", { className: "dockchip-row" }, items);
2121
+ }
2122
+ var ctxRef = { current: null };
2123
+ function apply(ctx) {
2124
+ ctxRef.current = ctx;
2125
+ ensureCss();
2126
+ initFeatureState(BUILTIN_FEATURES.concat(PLANNED_FEATURES));
2127
+ const slots = ctx.get("slots");
2128
+ if (slots === void 0) return;
2129
+ slots.inject("sidebar.footer.action", () => slots.register(
2130
+ { name: "sidebar.footer.action", id: "dsh-dock", order: 1, label: "\u529F\u80FD\u575E" },
2131
+ (props) => import_react7.default.createElement(DockEntry, props)
2132
+ ));
2133
+ slots.inject("shell.overlay", () => slots.register(
2134
+ { name: "shell.overlay", id: "dsh-dock-panel", order: 21, label: "\u529F\u80FD\u575E\u9762\u677F" },
2135
+ () => import_react7.default.createElement(DockModal, null)
2136
+ ));
2137
+ slots.inject("settings.section", () => slots.register(
2138
+ { name: "settings.section", id: "dsh-dock", order: 90, label: "\u529F\u80FD\u575E" },
2139
+ () => import_react7.default.createElement(DockPanel, null)
2140
+ ));
2141
+ slots.inject("conversation.input.left", () => slots.register(
2142
+ { name: "conversation.input.left", id: "dsh-dock-chips", order: 10, label: "\u529F\u80FD\u575E" },
2143
+ (zone) => import_react7.default.createElement(DockChips, Object.assign({}, zone, { ctx: ctxRef.current }))
2144
+ ));
2145
+ }
2146
+ var inject = ["timer"];
104
2147
 
105
- slots.inject("settings.section", () => slots.register(
106
- { name: "settings.section", id: "dsh-dock", order: 90, label: "功能中枢" },
107
- () => react.createElement(DockPanel, null)));
108
- }
109
2148
 
110
- exports.name = name;
111
- exports.inject = inject;
112
2149
  exports.apply = apply;
2150
+ exports.inject = inject;
2151
+ exports.dockBridge = dockBridge;
113
2152
  return module.exports;
114
2153
  }
115
- });
2154
+ });