dsh-token-gauge 0.1.8 → 0.1.9

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/lib/client.mjs CHANGED
@@ -966,34 +966,94 @@ function GaugePanel() {
966
966
  }
967
967
  });
968
968
  }
969
+ function GaugeTab() {
970
+ return import_react.default.createElement("div", {
971
+ ref: gaugeRef,
972
+ className: "tg-panel-root",
973
+ style: {
974
+ width: "100%",
975
+ height: "100%",
976
+ boxSizing: "border-box",
977
+ overflow: "hidden",
978
+ background: "var(--dsw-alias-bg-base)",
979
+ color: "var(--dsw-alias-label-primary)"
980
+ }
981
+ });
982
+ }
983
+ var GaugeIcon = (size) => {
984
+ const s = typeof size === "number" && size > 0 ? size : 16;
985
+ const svgProps = {
986
+ width: s,
987
+ height: s,
988
+ viewBox: "0 0 16 16",
989
+ fill: "none",
990
+ xmlns: "http://www.w3.org/2000/svg",
991
+ "aria-hidden": "true"
992
+ };
993
+ const arc = import_react.default.createElement("path", {
994
+ d: "M2.6 11.6a5.6 5.6 0 1 1 10.8 0",
995
+ stroke: "currentColor",
996
+ strokeWidth: 1.5,
997
+ strokeLinecap: "round"
998
+ });
999
+ const needle = import_react.default.createElement("path", {
1000
+ d: "M8 11.6 10.4 7.2",
1001
+ stroke: "currentColor",
1002
+ strokeWidth: 1.5,
1003
+ strokeLinecap: "round"
1004
+ });
1005
+ return import_react.default.createElement("svg", svgProps, arc, needle);
1006
+ };
1007
+ var TAB_ORDER = 90;
969
1008
  var name = PLUGIN_ID;
970
1009
  var inject = ["slots"];
971
1010
  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
- );
1011
+ let unregisterTab = null;
1012
+ let overlayDispose = void 0;
1013
+ let registeredViaBetter = false;
1014
+ let fallbackTimer = void 0;
1015
+ const tryMountOverlay = (reason) => {
1016
+ if (registeredViaBetter || overlayDispose !== void 0) return;
1017
+ 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`);
1018
+ try {
1019
+ overlayDispose = ctx.slots.register(
1020
+ { name: "shell.overlay", id: PLUGIN_ID, order: 100, label: "Token \u4EEA\u8868\u76D8" },
1021
+ () => import_react.default.createElement(GaugePanel)
1022
+ );
1023
+ } catch (e) {
1024
+ console.error("[dsh-token-gauge] \u6D6E\u5C42\u515C\u5E95\u4E5F\u5931\u8D25", e);
1025
+ }
983
1026
  };
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();
1027
+ const disposeSidebar = ctx.inject(["betterSidebar"], (sidebarCtx) => {
1028
+ const sidebar = sidebarCtx.betterSidebar;
1029
+ if (!sidebar || typeof sidebar.registerTab !== "function") return;
1030
+ unregisterTab = sidebar.registerTab({
1031
+ id: PLUGIN_ID,
1032
+ title: "Token \u4EEA\u8868\u76D8",
1033
+ single: true,
1034
+ order: TAB_ORDER,
1035
+ icon: GaugeIcon,
1036
+ component: () => import_react.default.createElement(GaugeTab)
1037
+ });
1038
+ registeredViaBetter = true;
1039
+ if (typeof fallbackTimer !== "undefined") clearTimeout(fallbackTimer);
1040
+ try {
1041
+ if (typeof sidebar.openTab === "function") sidebar.openTab({ type: PLUGIN_ID, single: true });
1042
+ } catch (e) {
990
1043
  }
991
- } catch (e) {
992
- console.error("[dsh-token-gauge] \u6CE8\u518C shell.overlay \u5931\u8D25", e);
993
- }
1044
+ return () => {
1045
+ if (unregisterTab) unregisterTab();
1046
+ };
1047
+ });
1048
+ fallbackTimer = setTimeout(() => {
1049
+ if (!registeredViaBetter) {
1050
+ tryMountOverlay("\u672A\u53D6\u5F97 dsh-better-sidebar \u670D\u52A1\uFF08\u8BF7\u786E\u8BA4\u5176\u5DF2\u5B89\u88C5\u5E76\u542F\u7528\uFF09");
1051
+ }
1052
+ }, 3e4);
994
1053
  return () => {
995
- if (typeof disposeRegistration === "function") disposeRegistration();
996
- if (typeof stop === "function") stop();
1054
+ if (typeof fallbackTimer !== "undefined") clearTimeout(fallbackTimer);
1055
+ if (typeof disposeSidebar === "function") disposeSidebar();
1056
+ if (typeof overlayDispose === "function") overlayDispose();
997
1057
  };
998
1058
  }
999
1059
  ; 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.9",
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,8 @@
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(停靠侧边栏);若 better-sidebar
8
+ // 未就绪,回退到官方 shell.overlay 浮层(双模式,保证仪表盘永远可见)。
8
9
  // 为避免与运行时 React 产生双实例 hook 冲突,本组件不调用任何 hook,
9
10
  // 仅在 ref 回调里挂载一次原生 DOM 仪表盘,并自行轮询刷新。
10
11
  // ============================================================================
@@ -223,6 +224,22 @@ function GaugePanel(): React.ReactElement {
223
224
  })
224
225
  }
225
226
 
227
+ // 停靠在 better-sidebar 工作台 Tab 内的面板:填满容器(非固定定位),同样零 hook、ref 挂原生 DOM。
228
+ function GaugeTab(): React.ReactElement {
229
+ return React.createElement('div', {
230
+ ref: gaugeRef,
231
+ className: 'tg-panel-root',
232
+ style: {
233
+ width: '100%',
234
+ height: '100%',
235
+ boxSizing: 'border-box',
236
+ overflow: 'hidden',
237
+ background: 'var(--dsw-alias-bg-base)',
238
+ color: 'var(--dsw-alias-label-primary)',
239
+ } as any,
240
+ })
241
+ }
242
+
226
243
  // 侧边栏图标:better-sidebar 以 `icon(size)` 形式调用(见其内置 tab:
227
244
  // `icon: (size) => jsx(Icon, { size })`),故这里接收数字尺寸。用 currentColor
228
245
  // 着色以自适应明暗主题,纯 SVG 零依赖。
@@ -258,45 +275,67 @@ const TAB_ORDER = 90
258
275
 
259
276
  export const name = PLUGIN_ID
260
277
 
261
- // 方案 A:通过官方 shell.overlay 插槽(list 型、additive、全框架级浮层)独立挂载仪表盘,
262
- // 不再依赖第三方插件 dsh-better-sidebar。slots 是 layout 提供的框架级服务(root 早期即
263
- // 就绪),故插件级 `inject: ['slots']` 不会触发此前 betterSidebar 的 pending / without-inject
264
- // 问题——这正是脱离 better-sidebar 的核心收益。
278
+ // slots 是 layout 提供的框架级服务(root 早期即就绪),用于浮层兜底,可安全声明顶层 inject。
279
+ // 注意:betterSidebar 是兄弟 bundle 提供的服务,不用顶层 inject(会触发 pending),改用
280
+ // apply 内的 ctx.inject 软注入回调。
265
281
  export const inject = ['slots']
266
282
 
267
283
  export function apply(ctx: any): (() => void) | void {
268
- let disposeRegistration: unknown = void 0
284
+ let unregisterTab: (() => void) | null = null
285
+ let overlayDispose: unknown = void 0
286
+ let registeredViaBetter = false
287
+ let fallbackTimer: any = void 0
269
288
 
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
- )
289
+ // 浮层兜底:把仪表盘挂到官方 shell.overlay(全程右下角 33vh),保证即使 better-sidebar
290
+ // 缺失/未启用,仪表盘也始终可见。
291
+ const tryMountOverlay = (reason: string): void => {
292
+ if (registeredViaBetter || overlayDispose !== void 0) return
293
+ console.warn(`[dsh-token-gauge] ${reason},仪表盘改为右下角浮层显示(仍可用)。`)
294
+ try {
295
+ overlayDispose = ctx.slots.register(
296
+ { name: 'shell.overlay', id: PLUGIN_ID, order: 100, label: 'Token 仪表盘' },
297
+ () => React.createElement(GaugePanel),
298
+ )
299
+ } catch (e) {
300
+ console.error('[dsh-token-gauge] 浮层兜底也失败', e)
301
+ }
283
302
  }
284
303
 
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()
304
+ // 主路径:注册到 better-sidebar 右侧工作台 Tab(停靠侧边栏,带图标、靠前排序)
305
+ const disposeSidebar = ctx.inject(['betterSidebar'], (sidebarCtx: any) => {
306
+ const sidebar = sidebarCtx.betterSidebar
307
+ if (!sidebar || typeof sidebar.registerTab !== 'function') return
308
+ unregisterTab = sidebar.registerTab({
309
+ id: PLUGIN_ID,
310
+ title: 'Token 仪表盘',
311
+ single: true,
312
+ order: TAB_ORDER,
313
+ icon: GaugeIcon,
314
+ component: () => React.createElement(GaugeTab),
315
+ })
316
+ registeredViaBetter = true
317
+ if (typeof fallbackTimer !== 'undefined') clearTimeout(fallbackTimer)
318
+ // 会话就绪时尝试自动打开 Tab(未就绪会静默 no-op,用户也可从「+ 新建 Tab」菜单手动打开)
319
+ try {
320
+ if (typeof sidebar.openTab === 'function') sidebar.openTab({ type: PLUGIN_ID, single: true })
321
+ } catch (e) {
322
+ /* ignore */
293
323
  }
294
- } catch (e) {
295
- console.error('[dsh-token-gauge] 注册 shell.overlay 失败', e)
296
- }
324
+ return () => {
325
+ if (unregisterTab) unregisterTab()
326
+ }
327
+ })
328
+
329
+ // 兜底:30s 内 better-sidebar 仍未就绪 → 浮层
330
+ fallbackTimer = setTimeout(() => {
331
+ if (!registeredViaBetter) {
332
+ tryMountOverlay('未取得 dsh-better-sidebar 服务(请确认其已安装并启用)')
333
+ }
334
+ }, 30000)
297
335
 
298
336
  return () => {
299
- if (typeof disposeRegistration === 'function') (disposeRegistration as () => void)()
300
- if (typeof stop === 'function') (stop as () => void)()
337
+ if (typeof fallbackTimer !== 'undefined') clearTimeout(fallbackTimer)
338
+ if (typeof disposeSidebar === 'function') (disposeSidebar as () => void)()
339
+ if (typeof overlayDispose === 'function') (overlayDispose as () => void)()
301
340
  }
302
341
  }