dsh-token-gauge 0.1.8 → 0.1.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/lib/client.mjs +124 -25
  2. package/package.json +6 -2
  3. package/src/client.ts +117 -33
package/lib/client.mjs CHANGED
@@ -32,7 +32,6 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
32
32
  var client_exports = {};
33
33
  __export(client_exports, {
34
34
  apply: () => apply,
35
- inject: () => inject,
36
35
  name: () => name
37
36
  });
38
37
  module.exports = __toCommonJS(client_exports);
@@ -966,34 +965,134 @@ function GaugePanel() {
966
965
  }
967
966
  });
968
967
  }
969
- var name = PLUGIN_ID;
970
- var inject = ["slots"];
971
- function apply(ctx) {
972
- let disposeRegistration = void 0;
973
- const doRegister = () => {
974
- disposeRegistration = ctx.slots.register(
975
- {
976
- name: "shell.overlay",
977
- id: PLUGIN_ID,
978
- order: 100,
979
- label: "Token \u4EEA\u8868\u76D8"
980
- },
981
- () => import_react.default.createElement(GaugePanel)
982
- );
968
+ function GaugeTab() {
969
+ return import_react.default.createElement("div", {
970
+ ref: gaugeRef,
971
+ className: "tg-panel-root",
972
+ style: {
973
+ width: "100%",
974
+ height: "100%",
975
+ boxSizing: "border-box",
976
+ overflow: "hidden",
977
+ background: "var(--dsw-alias-bg-base)",
978
+ color: "var(--dsw-alias-label-primary)"
979
+ }
980
+ });
981
+ }
982
+ var GaugeIcon = (size) => {
983
+ const s = typeof size === "number" && size > 0 ? size : 16;
984
+ const svgProps = {
985
+ width: s,
986
+ height: s,
987
+ viewBox: "0 0 16 16",
988
+ fill: "none",
989
+ xmlns: "http://www.w3.org/2000/svg",
990
+ "aria-hidden": "true"
983
991
  };
984
- let stop = void 0;
985
- try {
986
- if (typeof ctx.slots.inject === "function") {
987
- stop = ctx.slots.inject("shell.overlay", doRegister);
988
- } else {
989
- doRegister();
992
+ const arc = import_react.default.createElement("path", {
993
+ d: "M2.6 11.6a5.6 5.6 0 1 1 10.8 0",
994
+ stroke: "currentColor",
995
+ strokeWidth: 1.5,
996
+ strokeLinecap: "round"
997
+ });
998
+ const needle = import_react.default.createElement("path", {
999
+ d: "M8 11.6 10.4 7.2",
1000
+ stroke: "currentColor",
1001
+ strokeWidth: 1.5,
1002
+ strokeLinecap: "round"
1003
+ });
1004
+ return import_react.default.createElement("svg", svgProps, arc, needle);
1005
+ };
1006
+ var TAB_ORDER = 90;
1007
+ var name = PLUGIN_ID;
1008
+ function findOurTab(snap) {
1009
+ if (!snap) return null;
1010
+ const trees = [snap.splits, snap.bottomSplits];
1011
+ for (const tree of trees) {
1012
+ if (!tree) continue;
1013
+ const stack = [tree];
1014
+ while (stack.length) {
1015
+ const node = stack.pop();
1016
+ if (!node) continue;
1017
+ if (Array.isArray(node.tabs)) {
1018
+ for (const t2 of node.tabs) if (t2 && t2.type === PLUGIN_ID) return t2;
1019
+ }
1020
+ if (Array.isArray(node.children)) stack.push(...node.children);
1021
+ if (node.splits) stack.push(node.splits);
1022
+ if (node.bottomSplits) stack.push(node.bottomSplits);
990
1023
  }
991
- } catch (e) {
992
- console.error("[dsh-token-gauge] \u6CE8\u518C shell.overlay \u5931\u8D25", e);
993
1024
  }
1025
+ return null;
1026
+ }
1027
+ function apply(ctx) {
1028
+ let unregisterTab = null;
1029
+ let mountedOverlay = false;
1030
+ let slotsService = null;
1031
+ let fallbackTimer = void 0;
1032
+ let openTimer = null;
1033
+ const stopSlots = ctx.inject(["slots"], (slotsCtx) => {
1034
+ slotsService = slotsCtx.slots;
1035
+ });
1036
+ const mountOverlay = (reason) => {
1037
+ if (mountedOverlay || unregisterTab) return;
1038
+ if (!slotsService || typeof slotsService.register !== "function") return;
1039
+ console.warn(`[dsh-token-gauge] ${reason}\uFF0C\u4EEA\u8868\u76D8\u6539\u4E3A\u53F3\u4E0B\u89D2\u6D6E\u5C42\u663E\u793A\uFF08\u4ECD\u53EF\u7528\uFF09\u3002`);
1040
+ try {
1041
+ const d = slotsService.register(
1042
+ { name: "shell.overlay", id: PLUGIN_ID, order: 100, label: "Token \u4EEA\u8868\u76D8" },
1043
+ () => import_react.default.createElement(GaugePanel)
1044
+ );
1045
+ if (typeof d === "function") mountedOverlay = true;
1046
+ } catch (e) {
1047
+ console.error("[dsh-token-gauge] \u6D6E\u5C42\u515C\u5E95\u4E5F\u5931\u8D25", e);
1048
+ }
1049
+ };
1050
+ const stopSidebar = ctx.inject(["betterSidebar"], (sidebarCtx) => {
1051
+ const sidebar = sidebarCtx.betterSidebar;
1052
+ if (!sidebar || typeof sidebar.registerTab !== "function") return;
1053
+ unregisterTab = sidebar.registerTab({
1054
+ id: PLUGIN_ID,
1055
+ title: "Token \u4EEA\u8868\u76D8",
1056
+ single: true,
1057
+ order: TAB_ORDER,
1058
+ icon: GaugeIcon,
1059
+ component: () => import_react.default.createElement(GaugeTab)
1060
+ });
1061
+ if (typeof fallbackTimer !== "undefined") clearTimeout(fallbackTimer);
1062
+ let attempts = 0;
1063
+ openTimer = setInterval(() => {
1064
+ attempts++;
1065
+ const snap = sidebar.getSnapshot?.();
1066
+ const sid = snap?.sessionId;
1067
+ if (sid !== void 0 && typeof sidebar.openTab === "function") {
1068
+ try {
1069
+ sidebar.openTab({ type: PLUGIN_ID });
1070
+ } catch {
1071
+ }
1072
+ }
1073
+ if (sid !== void 0 && findOurTab(snap)) {
1074
+ clearInterval(openTimer);
1075
+ openTimer = null;
1076
+ return;
1077
+ }
1078
+ if (attempts >= 45) {
1079
+ clearInterval(openTimer);
1080
+ openTimer = null;
1081
+ mountOverlay("better-sidebar \u5DF2\u6CE8\u518C\u4F46\u4F1A\u8BDD\u59CB\u7EC8\u672A\u5C31\u7EEA");
1082
+ }
1083
+ }, 800);
1084
+ return () => {
1085
+ if (unregisterTab) unregisterTab();
1086
+ };
1087
+ });
1088
+ fallbackTimer = setTimeout(() => {
1089
+ if (!unregisterTab) mountOverlay("\u672A\u53D6\u5F97 dsh-better-sidebar \u670D\u52A1\uFF08\u8BF7\u786E\u8BA4\u5176\u5DF2\u5B89\u88C5\u5E76\u542F\u7528\uFF09");
1090
+ }, 8e3);
994
1091
  return () => {
995
- if (typeof disposeRegistration === "function") disposeRegistration();
996
- if (typeof stop === "function") stop();
1092
+ if (typeof fallbackTimer !== "undefined") clearTimeout(fallbackTimer);
1093
+ if (openTimer) clearInterval(openTimer);
1094
+ if (typeof stopSidebar === "function") stopSidebar();
1095
+ if (typeof stopSlots === "function") stopSlots();
997
1096
  };
998
1097
  }
999
1098
  ; return module.exports; } });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-token-gauge",
3
- "version": "0.1.8",
3
+ "version": "0.1.10",
4
4
  "description": "DSH 机车风格 Token 用量/费用仪表盘侧边栏插件(稳定版 0.1.1-rc.x 兼容)",
5
5
  "type": "module",
6
6
  "main": "lib/index.mjs",
@@ -60,7 +60,8 @@
60
60
  },
61
61
  "peerDependencies": {
62
62
  "@deepseek-ai/cordis": "*",
63
- "@deepseek-ai/dsh-host-webserver": "*"
63
+ "@deepseek-ai/dsh-host-webserver": "*",
64
+ "dsh-better-sidebar": ">=0.15.2"
64
65
  },
65
66
  "peerDependenciesMeta": {
66
67
  "@deepseek-ai/cordis": {
@@ -68,6 +69,9 @@
68
69
  },
69
70
  "@deepseek-ai/dsh-host-webserver": {
70
71
  "optional": true
72
+ },
73
+ "dsh-better-sidebar": {
74
+ "optional": true
71
75
  }
72
76
  },
73
77
  "devDependencies": {
package/src/client.ts CHANGED
@@ -4,7 +4,9 @@
4
4
  // 通过同域 HTTP 与 Host 半边通信:
5
5
  // GET /plugins/dsh-token-gauge/state -> 完整 State(JSON)
6
6
  // POST /plugins/dsh-token-gauge/cmd -> {type,payload} -> 更新后 State
7
- // 通过官方 shell.overlay 插槽独立挂载浮层面板(方案 A),不再依赖 dsh-better-sidebar。
7
+ // 优先把仪表盘注册到 dsh-better-sidebar 的右侧工作台 Tab(停靠侧边栏),注册后轮询
8
+ // openTab 直到会话就绪并自动打开激活(解决“已挂载却未显示”:session 未就绪时 openTab
9
+ // 会静默 no-op);若 better-sidebar 始终不可用,回退到官方 shell.overlay 浮层兜底。
8
10
  // 为避免与运行时 React 产生双实例 hook 冲突,本组件不调用任何 hook,
9
11
  // 仅在 ref 回调里挂载一次原生 DOM 仪表盘,并自行轮询刷新。
10
12
  // ============================================================================
@@ -223,6 +225,22 @@ function GaugePanel(): React.ReactElement {
223
225
  })
224
226
  }
225
227
 
228
+ // 停靠在 better-sidebar 工作台 Tab 内的面板:填满容器(非固定定位),同样零 hook、ref 挂原生 DOM。
229
+ function GaugeTab(): React.ReactElement {
230
+ return React.createElement('div', {
231
+ ref: gaugeRef,
232
+ className: 'tg-panel-root',
233
+ style: {
234
+ width: '100%',
235
+ height: '100%',
236
+ boxSizing: 'border-box',
237
+ overflow: 'hidden',
238
+ background: 'var(--dsw-alias-bg-base)',
239
+ color: 'var(--dsw-alias-label-primary)',
240
+ } as any,
241
+ })
242
+ }
243
+
226
244
  // 侧边栏图标:better-sidebar 以 `icon(size)` 形式调用(见其内置 tab:
227
245
  // `icon: (size) => jsx(Icon, { size })`),故这里接收数字尺寸。用 currentColor
228
246
  // 着色以自适应明暗主题,纯 SVG 零依赖。
@@ -258,45 +276,111 @@ const TAB_ORDER = 90
258
276
 
259
277
  export const name = PLUGIN_ID
260
278
 
261
- // 方案 A:通过官方 shell.overlay 插槽(list 型、additive、全框架级浮层)独立挂载仪表盘,
262
- // 不再依赖第三方插件 dsh-better-sidebar。slots 是 layout 提供的框架级服务(root 早期即
263
- // 就绪),故插件级 `inject: ['slots']` 不会触发此前 betterSidebar 的 pending / without-inject
264
- // 问题——这正是脱离 better-sidebar 的核心收益。
265
- export const inject = ['slots']
279
+ // 不声明顶层 inject(避免 pending 导致插件加载失败)。better-sidebar(兄弟 bundle 服务)
280
+ // 与 slots(框架级浮层服务)都用 apply 内的 ctx.inject 软注入回调按需获取。
281
+
282
+ // 在 better-sidebar 快照里查找我们的 Tab 是否已打开(按 type 判定,single 单例)。
283
+ function findOurTab(snap: any): any {
284
+ if (!snap) return null
285
+ const trees = [snap.splits, snap.bottomSplits]
286
+ for (const tree of trees) {
287
+ if (!tree) continue
288
+ const stack: any[] = [tree]
289
+ while (stack.length) {
290
+ const node = stack.pop()
291
+ if (!node) continue
292
+ if (Array.isArray(node.tabs)) {
293
+ for (const t of node.tabs) if (t && t.type === PLUGIN_ID) return t
294
+ }
295
+ if (Array.isArray(node.children)) stack.push(...node.children)
296
+ if (node.splits) stack.push(node.splits)
297
+ if (node.bottomSplits) stack.push(node.bottomSplits)
298
+ }
299
+ }
300
+ return null
301
+ }
266
302
 
267
303
  export function apply(ctx: any): (() => void) | void {
268
- let disposeRegistration: unknown = void 0
304
+ let unregisterTab: (() => void) | null = null
305
+ let mountedOverlay = false
306
+ let slotsService: any = null
307
+ let fallbackTimer: any = void 0
308
+ let openTimer: any = null
269
309
 
270
- const doRegister = (): void => {
271
- // shell.overlay 是 click-through 层,entry 需自行 opt-in pointer events(已在
272
- // GaugePanel 外层设 pointerEvents:'auto')。第二参是返回 React.Element 的工厂函数
273
- //(照抄 dsh-cordis-client-runner 官方示例)。
274
- disposeRegistration = ctx.slots.register(
275
- {
276
- name: 'shell.overlay',
277
- id: PLUGIN_ID,
278
- order: 100,
279
- label: 'Token 仪表盘',
280
- },
281
- () => React.createElement(GaugePanel),
282
- )
283
- }
310
+ // 取得框架级 slots(仅用于浮层兜底)。若平台不提供 slots,浮层兜底不可用,但侧边栏主路径不受影响。
311
+ const stopSlots = ctx.inject(['slots'], (slotsCtx: any) => {
312
+ slotsService = slotsCtx.slots
313
+ })
284
314
 
285
- let stop: unknown = void 0
286
- try {
287
- // slots.inject(seat, cb):等 shell.overlay 这个 seat 就绪再注册(root 已声明,
288
- // 通常同步触发)。某些环境下若无 inject 方法,则直接 register 兜底。
289
- if (typeof ctx.slots.inject === 'function') {
290
- stop = ctx.slots.inject('shell.overlay', doRegister)
291
- } else {
292
- doRegister()
315
+ // 浮层兜底:把仪表盘挂到官方 shell.overlay(右下角 33vh),仅当 better-sidebar 不可用或会话始终未就绪时。
316
+ const mountOverlay = (reason: string): void => {
317
+ if (mountedOverlay || unregisterTab) return // 已注册到侧边栏则不再叠加浮层
318
+ if (!slotsService || typeof slotsService.register !== 'function') return
319
+ console.warn(`[dsh-token-gauge] ${reason},仪表盘改为右下角浮层显示(仍可用)。`)
320
+ try {
321
+ const d = slotsService.register(
322
+ { name: 'shell.overlay', id: PLUGIN_ID, order: 100, label: 'Token 仪表盘' },
323
+ () => React.createElement(GaugePanel),
324
+ )
325
+ if (typeof d === 'function') mountedOverlay = true
326
+ } catch (e) {
327
+ console.error('[dsh-token-gauge] 浮层兜底也失败', e)
293
328
  }
294
- } catch (e) {
295
- console.error('[dsh-token-gauge] 注册 shell.overlay 失败', e)
296
329
  }
297
330
 
331
+ // 主路径:注册到 better-sidebar 右侧工作台 Tab(停靠侧边栏),并轮询 openTab 直到会话就绪,
332
+ // 让仪表盘自动打开并成为当前可见页(“已挂载但未显示”的根因是 session 未就绪时 openTab 静默 no-op)。
333
+ const stopSidebar = ctx.inject(['betterSidebar'], (sidebarCtx: any) => {
334
+ const sidebar = sidebarCtx.betterSidebar
335
+ if (!sidebar || typeof sidebar.registerTab !== 'function') return
336
+ unregisterTab = sidebar.registerTab({
337
+ id: PLUGIN_ID,
338
+ title: 'Token 仪表盘',
339
+ single: true,
340
+ order: TAB_ORDER,
341
+ icon: GaugeIcon,
342
+ component: () => React.createElement(GaugeTab),
343
+ })
344
+ if (typeof fallbackTimer !== 'undefined') clearTimeout(fallbackTimer)
345
+ let attempts = 0
346
+ openTimer = setInterval(() => {
347
+ attempts++
348
+ const snap = sidebar.getSnapshot?.()
349
+ const sid = snap?.sessionId
350
+ // session 已就绪才尝试打开(未就绪时 openTab 会静默 no-op)
351
+ if (sid !== undefined && typeof sidebar.openTab === 'function') {
352
+ try {
353
+ sidebar.openTab({ type: PLUGIN_ID })
354
+ } catch {
355
+ /* ignore */
356
+ }
357
+ }
358
+ if (sid !== undefined && findOurTab(snap)) {
359
+ clearInterval(openTimer)
360
+ openTimer = null
361
+ return
362
+ }
363
+ if (attempts >= 45) {
364
+ // 约 36s 仍未打开(极端:会话始终未建立)→ 浮层兜底,保证可见
365
+ clearInterval(openTimer)
366
+ openTimer = null
367
+ mountOverlay('better-sidebar 已注册但会话始终未就绪')
368
+ }
369
+ }, 800)
370
+ return () => {
371
+ if (unregisterTab) unregisterTab()
372
+ }
373
+ })
374
+
375
+ // 兜底:8s 内 better-sidebar 仍未就绪 → 浮层
376
+ fallbackTimer = setTimeout(() => {
377
+ if (!unregisterTab) mountOverlay('未取得 dsh-better-sidebar 服务(请确认其已安装并启用)')
378
+ }, 8000)
379
+
298
380
  return () => {
299
- if (typeof disposeRegistration === 'function') (disposeRegistration as () => void)()
300
- if (typeof stop === 'function') (stop as () => void)()
381
+ if (typeof fallbackTimer !== 'undefined') clearTimeout(fallbackTimer)
382
+ if (openTimer) clearInterval(openTimer)
383
+ if (typeof stopSidebar === 'function') stopSidebar()
384
+ if (typeof stopSlots === 'function') stopSlots()
301
385
  }
302
386
  }