@jxgame2020/dsh-token-quota 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +133 -0
- package/lib/client.js +83 -77
- package/lib/types/client/TokenQuotaPanel.js +15 -12
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# @jxgame2020/dsh-token-quota
|
|
2
|
+
|
|
3
|
+
DeepSeek Harness 的**每日按模型 Token 限额**插件:后端按模型累计当日 token 用量并持久化、
|
|
4
|
+
达到上限时拦截请求;前端在 Web UI 右侧提供浮窗,实时显示每个被监控模型的
|
|
5
|
+
「实际/上限」,可对每个模型单独设置限额,满额时一键或自动切换模型。
|
|
6
|
+
|
|
7
|
+
对 DSH 核心仓库**零改动**:不修改任何核心文件、不挂载官方 bundle、不依赖转发事件
|
|
8
|
+
白名单;数据通过插件自有的 HTTP 路由(`GET /token-quota`)轮询读取,配置通过
|
|
9
|
+
settings 的 `token-quota` 命名空间读写。
|
|
10
|
+
|
|
11
|
+
## 功能
|
|
12
|
+
|
|
13
|
+
- **按模型独立计数**:输入 + 输出 + 缓存 token 全部计入当日用量;重启不丢
|
|
14
|
+
(持久化到 `$DSH_HOME/token-quota.json`)。
|
|
15
|
+
- **硬拦截**:达到上限后请求直接停止并提示切换,不会继续消耗额度。
|
|
16
|
+
- **满额策略**(四选一,面板设置里切换,即时生效):
|
|
17
|
+
- 停止请求并提示
|
|
18
|
+
- 自动切换到其它已监控且未满的限额模型
|
|
19
|
+
- 自动切换到其它任意可用模型(含非限额)
|
|
20
|
+
- 自动切换(优先非限额,其次未监控)
|
|
21
|
+
- **每日自动重置**:默认本机时区午夜;可在设置里改为任意时区(UTC−12 ~ +14)
|
|
22
|
+
与时刻(0:00–23:55),到点清零当日计数并归档用量日志。
|
|
23
|
+
- **用量日志**:每模型每天一条(日期 / 模型 / 用量),与监控勾选无关。
|
|
24
|
+
- **浮窗面板**:实时显示各模型「今日已用 / 上限」,行内一键「选择」切换模型、
|
|
25
|
+
`⚙` 折叠设置单模型限额。
|
|
26
|
+
|
|
27
|
+
## 安装
|
|
28
|
+
|
|
29
|
+
### 方式一:npm 安装(推荐)
|
|
30
|
+
|
|
31
|
+
在你的 web profile(`~/.dsh/profiles/web`)下:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
cd ~/.dsh/profiles/web
|
|
35
|
+
npm install @jxgame2020/dsh-token-quota
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
> 用 pnpm 管理 profile 的话:`pnpm add @jxgame2020/dsh-token-quota`。
|
|
39
|
+
|
|
40
|
+
在 `cordis.patch.yml` 中挂载插件:
|
|
41
|
+
|
|
42
|
+
```yaml
|
|
43
|
+
- insert:
|
|
44
|
+
- id: token-quota
|
|
45
|
+
name: '@jxgame2020/dsh-token-quota'
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
重启 `dsh web`,浏览器右侧出现「每日 Token 限额」浮窗即安装成功。
|
|
49
|
+
|
|
50
|
+
### 方式二:从 GitHub 源码安装
|
|
51
|
+
|
|
52
|
+
适合想改源码、离线分发或审阅代码的情况。源码仓库不提交构建产物(`lib/`),
|
|
53
|
+
clone 后需在本机构建(工具链来自 DSH 仓库的 workspace)。
|
|
54
|
+
|
|
55
|
+
1. 克隆到与 `deepseek-harness` 同级的开发目录:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
git clone https://github.com/jxgame/dsh-token-quota.git deepseek-harness-package/dsh-token-quota
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
2. 在 `deepseek-harness/pnpm-workspace.yaml` 的 `packages:` 下注册该包(让它的
|
|
62
|
+
peer 依赖从仓库 workspace 解析):
|
|
63
|
+
|
|
64
|
+
```yaml
|
|
65
|
+
packages:
|
|
66
|
+
- ../deepseek-harness-package/dsh-token-quota/packages/token-quota
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
然后在仓库根执行 `cd deepseek-harness && pnpm install`。
|
|
70
|
+
|
|
71
|
+
3. 构建:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
cd deepseek-harness-package/dsh-token-quota/packages/token-quota
|
|
75
|
+
pnpm exec tsc -p tsconfig.json # 类型检查 + 产出 lib/types
|
|
76
|
+
pnpm exec tsdown # 产出 lib/index.js + lib/client.js
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
4. 挂载到 profile:`~/.dsh/profiles/web/package.json` 的 dependencies 使用本地路径:
|
|
80
|
+
|
|
81
|
+
```json
|
|
82
|
+
"@jxgame2020/dsh-token-quota": "link:/<绝对路径>/deepseek-harness-package/dsh-token-quota/packages/token-quota"
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
然后 `cd ~/.dsh/profiles/web && pnpm install`;`cordis.patch.yml` 挂载与
|
|
86
|
+
方式一相同;重启 `dsh web`。
|
|
87
|
+
|
|
88
|
+
## 使用
|
|
89
|
+
|
|
90
|
+
- 头部「设置」弹窗(改动即时自动保存,右上角 × 关闭)
|
|
91
|
+
- **监控模型**:勾选要监控的模型(默认全部)。未勾选的模型不显示、
|
|
92
|
+
不计入当日用量、不受限额拦截(切换时仍可选)。
|
|
93
|
+
- **满额后处理**:四选一(见上文「功能」)。
|
|
94
|
+
- **每日重置时间**:时区(UTC−12 ~ UTC+14)与时刻(0:00–23:55,5 分钟步进),
|
|
95
|
+
到点自动清零;不设置则沿用「本机时区午夜」。
|
|
96
|
+
- 头部「日志」按钮(设置左侧):用量日志弹窗,表格列出日期 / 模型 / 用量,
|
|
97
|
+
每模型每天一条。
|
|
98
|
+
- 模型行:
|
|
99
|
+
- 名称后的「选择」按钮:一键切换当前会话到该模型。
|
|
100
|
+
- 行尾 `⚙`:展开该行限额输入框(数字,`0`=不限),保存后收起。
|
|
101
|
+
- 进度条:绿 <80%,黄 80–100%,红 = 已满额。
|
|
102
|
+
|
|
103
|
+
## 数据与重置
|
|
104
|
+
|
|
105
|
+
- 计数文件:`$DSH_HOME/token-quota.json`(默认 `~/.dsh/token-quota.json`)。
|
|
106
|
+
- 重置周期默认「本机时区午夜」;可改为任意时区与时刻。跨过重置时刻时当日计数
|
|
107
|
+
清零并归档进用量日志。
|
|
108
|
+
- 限额与面板配置保存在 `~/.dsh/settings.yaml` 的 `token-quota` 命名空间
|
|
109
|
+
(`limits` / `monitored` / `onFull` / `reset`)。
|
|
110
|
+
- 面板数据读取:`GET /token-quota`(当前快照)、`GET /token-quota/log`(历史用量)。
|
|
111
|
+
|
|
112
|
+
## 目录结构
|
|
113
|
+
|
|
114
|
+
```
|
|
115
|
+
dsh-token-quota/
|
|
116
|
+
└── packages/token-quota/
|
|
117
|
+
├── src/
|
|
118
|
+
│ ├── index.ts # Host 服务:计量/持久化/拦截/HTTP 快照路由
|
|
119
|
+
│ ├── types.ts # 共享类型与 wire 常量
|
|
120
|
+
│ ├── invariant.ts # 运行时守卫
|
|
121
|
+
│ └── client/ # 浏览器面板(shell.overlay 浮窗)
|
|
122
|
+
│ ├── index.ts # apply:轮询 + 满额策略 + 面板注册
|
|
123
|
+
│ ├── TokenQuotaPanel.tsx
|
|
124
|
+
│ ├── store.ts
|
|
125
|
+
│ ├── locales.ts
|
|
126
|
+
│ └── TokenQuotaPanel.module.css
|
|
127
|
+
├── tsconfig.json # host + client 一起编译
|
|
128
|
+
└── tsdown.config.ts # host 库 + 浏览器 bundle(closure-factory)
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## License
|
|
132
|
+
|
|
133
|
+
MIT
|
package/lib/client.js
CHANGED
|
@@ -119,7 +119,7 @@ window.__ModuleLoader__.load({
|
|
|
119
119
|
}
|
|
120
120
|
//#endregion
|
|
121
121
|
//#region \0dsh-css:/Users/jxgame/Desktop/works/deepseek-harness-package/dsh-token-quota/packages/token-quota/src/client/TokenQuotaPanel.module.css.mjs
|
|
122
|
-
const css = ".NPLLIq_panel{box-sizing:border-box;background:var(--dsw-alias-bg-overlay);border:1px solid var(--dsw-alias-border-l1);pointer-events:auto;z-index:40;border-radius:12px;flex-direction:column;width:320px;max-height:min(72vh,560px);display:flex;position:fixed;top:56px;right:12px;overflow:hidden;box-shadow:0 8px 28px #0000002e}.NPLLIq_header{border-bottom:1px solid var(--dsw-alias-border-l1);cursor:grab;user-select:none;touch-action:none;justify-content:space-between;align-items:flex-start;gap:8px;padding:10px 12px;display:flex}.NPLLIq_header:active{cursor:grabbing}.NPLLIq_headerText{flex-direction:column;gap:2px;min-width:0;display:flex}.NPLLIq_title{color:var(--dsw-alias-label-primary);font-size:13px;font-weight:600}.NPLLIq_subtitle{color:var(--dsw-alias-label-secondary);font-size:11px}.NPLLIq_collapse{color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;border-radius:6px;flex:none;padding:2px 6px;font-size:11px}.NPLLIq_collapse:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-1)}.NPLLIq_body{padding:4px 0;overflow-y:auto}.NPLLIq_row{border-bottom:1px solid var(--dsw-alias-border-l1);flex-direction:column;gap:6px;padding:8px 12px;display:flex}.NPLLIq_rowHeader{justify-content:space-between;align-items:center;gap:8px;display:flex}.NPLLIq_rowName{color:var(--dsw-alias-label-primary);text-overflow:ellipsis;white-space:nowrap;align-items:center;gap:6px;font-size:12px;font-weight:500;display:inline-flex;overflow:hidden}.NPLLIq_currentBadge{color:var(--dsw-alias-state-success-primary);border:1px solid var(--dsw-alias-state-success-primary);border-radius:8px;flex:none;padding:1px 6px;font-size:10px}.NPLLIq_rowMeta{color:var(--dsw-alias-label-secondary);font-variant-numeric:tabular-nums;flex:none;font-size:11px}.NPLLIq_bar{background:var(--dsw-alias-bg-layer-1);border-radius:3px;height:6px;overflow:hidden}.NPLLIq_fillIdle{background:var(--dsw-alias-brand-primary);height:100%;transition:width .2s}.NPLLIq_fillWarn{background:var(--dsw-alias-state-warn-primary);height:100%;transition:width .2s}.NPLLIq_fillOver{background:var(--dsw-alias-state-error-primary);height:100%;transition:width .2s}.NPLLIq_controls{align-items:center;gap:6px;display:flex}.NPLLIq_input{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-1);min-width:0;color:var(--dsw-alias-label-primary);border-radius:6px;flex:1;padding:4px 8px;font-size:11px}.NPLLIq_input::placeholder{color:var(--dsw-alias-label-secondary)}.NPLLIq_save,.NPLLIq_switch{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);cursor:pointer;border-radius:6px;flex:none;padding:4px 10px;font-size:11px}.NPLLIq_save:hover,.NPLLIq_switch:hover:not(:disabled){background:var(--dsw-alias-bg-layer-2)}.NPLLIq_switch:disabled{opacity:.5;cursor:default}.NPLLIq_notice,.NPLLIq_noticeError{color:var(--dsw-alias-label-secondary);padding:10px 12px;font-size:12px}.NPLLIq_noticeError{color:var(--dsw-alias-state-error-primary)}.NPLLIq_tab{background:var(--dsw-alias-brand-primary);border:1px solid var(--dsw-alias-brand-primary);color:var(--dsw-alias-bg-overlay);pointer-events:auto;cursor:pointer;z-index:90;border-radius:10px;align-items:center;gap:6px;padding:9px 14px;font-size:12px;font-weight:600;display:inline-flex;position:fixed;top:56px;right:12px;box-shadow:0 6px 20px #00000040}.NPLLIq_tab:hover{filter:brightness(1.08)}.NPLLIq_tabIcon{font-size:12px;line-height:1}.NPLLIq_tabLabel{white-space:nowrap}.NPLLIq_headerActions{flex:none;align-items:center;gap:4px;display:flex}.NPLLIq_settingsBtn{color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;border-radius:6px;flex:none;padding:2px 6px;font-size:11px}.NPLLIq_settingsBtn:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-1)}.NPLLIq_selectBtn{border:1px solid var(--dsw-alias-border-l1);color:var(--dsw-alias-brand-primary);cursor:pointer;background:0 0;border-radius:6px;flex:none;padding:1px 6px;font-size:10px}.NPLLIq_selectBtn:hover{background:var(--dsw-alias-bg-layer-1)}.NPLLIq_gearBtn{color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;flex:none;margin-left:6px;padding:0 2px;font-size:12px;line-height:1}.NPLLIq_gearBtn:hover{color:var(--dsw-alias-label-primary)}.NPLLIq_fullNotice{color:var(--dsw-alias-state-error-primary);background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-state-error-primary);border-radius:8px;margin:6px 12px;padding:8px 10px;font-size:12px}.NPLLIq_dialog{z-index:100;box-sizing:border-box;background:var(--dsw-alias-bg-overlay);border:1px solid var(--dsw-alias-border-l1);border-radius:12px;flex-direction:column;gap:12px;width:280px;max-height:80vh;padding:14px;display:flex;position:fixed;top:50%;left:50%;overflow-y:auto;transform:translate(-50%,-50%);box-shadow:0 8px 28px #00000040}.NPLLIq_dialogTitle{color:var(--dsw-alias-label-primary);font-size:13px;font-weight:600}.NPLLIq_dialogSection{flex-direction:column;gap:6px;display:flex}.NPLLIq_dialogLabel{color:var(--dsw-alias-label-primary);font-size:12px;font-weight:500}.NPLLIq_monitorHint{color:var(--dsw-alias-label-secondary);font-size:11px}.NPLLIq_monitorList{flex-direction:column;gap:2px;max-height:180px;display:flex;overflow-y:auto}.NPLLIq_monitorRow{color:var(--dsw-alias-label-primary);cursor:pointer;border-radius:6px;align-items:center;gap:6px;padding:2px 4px;font-size:12px;display:flex}.NPLLIq_monitorRow:hover{background:var(--dsw-alias-bg-layer-1)}.NPLLIq_monitorName{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.NPLLIq_radioRow{color:var(--dsw-alias-label-primary);cursor:pointer;border-radius:6px;align-items:center;gap:6px;padding:3px 4px;font-size:12px;display:flex}.NPLLIq_radioRow:hover{background:var(--dsw-alias-bg-layer-1)}.NPLLIq_dialogActions{justify-content:flex-end;gap:6px;display:flex}.NPLLIq_dialogBtn,.NPLLIq_dialogBtnPrimary{border:1px solid var(--dsw-alias-border-l1);cursor:pointer;border-radius:6px;padding:4px 12px;font-size:11px}.NPLLIq_dialogBtn{background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary)}.NPLLIq_dialogBtnPrimary{background:var(--dsw-alias-brand-primary);border-color:var(--dsw-alias-brand-primary);color:var(--dsw-alias-bg-overlay)}.NPLLIq_dialogHeader{z-index:2;background:var(--dsw-alias-bg-overlay);cursor:grab;user-select:none;touch-action:none;justify-content:space-between;align-items:center;gap:8px;margin:-14px -14px 0;padding:14px 14px 8px;display:flex;position:sticky;top:-14px}.NPLLIq_dialogHeader:active{cursor:grabbing}.NPLLIq_dialogClose{width:28px;height:28px;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;border-radius:8px;flex:none;justify-content:center;align-items:center;font-size:18px;line-height:1;display:flex}.NPLLIq_dialogClose:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-1)}.NPLLIq_resetRow{flex-wrap:wrap;align-items:center;gap:6px;display:flex}.NPLLIq_resetSelect{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);border-radius:6px;padding:3px 6px;font-size:12px}.NPLLIq_logTable{border-collapse:collapse;width:100%;font-size:11px}.NPLLIq_logTable th{text-align:left;color:var(--dsw-alias-label-secondary);border-bottom:1px solid var(--dsw-alias-border-l1);padding:4px 6px;font-weight:500}.NPLLIq_logTable td{color:var(--dsw-alias-label-primary);border-bottom:1px solid var(--dsw-alias-border-l1);font-variant-numeric:tabular-nums;padding:4px 6px}.NPLLIq_logDayCol{white-space:nowrap;color:var(--dsw-alias-label-secondary)}.NPLLIq_logModelCol{text-overflow:ellipsis;white-space:nowrap;max-width:160px;overflow:hidden}.NPLLIq_logUsedCol{text-align:right}";
|
|
122
|
+
const css = ".NPLLIq_panel{box-sizing:border-box;background:var(--dsw-alias-bg-overlay);border:1px solid var(--dsw-alias-border-l1);pointer-events:auto;z-index:40;border-radius:12px;flex-direction:column;width:320px;max-height:min(72vh,560px);display:flex;position:fixed;top:56px;right:12px;overflow:hidden;box-shadow:0 8px 28px #0000002e}.NPLLIq_header{border-bottom:1px solid var(--dsw-alias-border-l1);cursor:grab;user-select:none;touch-action:none;justify-content:space-between;align-items:flex-start;gap:8px;padding:10px 12px;display:flex}.NPLLIq_header:active{cursor:grabbing}.NPLLIq_headerText{flex-direction:column;gap:2px;min-width:0;display:flex}.NPLLIq_title{color:var(--dsw-alias-label-primary);font-size:13px;font-weight:600}.NPLLIq_subtitle{color:var(--dsw-alias-label-secondary);font-size:11px}.NPLLIq_collapse{color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;border-radius:6px;flex:none;padding:2px 6px;font-size:11px}.NPLLIq_collapse:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-1)}.NPLLIq_body{padding:4px 0;overflow-y:auto}.NPLLIq_row{border-bottom:1px solid var(--dsw-alias-border-l1);flex-direction:column;gap:6px;padding:8px 12px;display:flex}.NPLLIq_rowHeader{justify-content:space-between;align-items:center;gap:8px;display:flex}.NPLLIq_rowName{color:var(--dsw-alias-label-primary);text-overflow:ellipsis;white-space:nowrap;align-items:center;gap:6px;font-size:12px;font-weight:500;display:inline-flex;overflow:hidden}.NPLLIq_currentBadge{color:var(--dsw-alias-state-success-primary);border:1px solid var(--dsw-alias-state-success-primary);border-radius:8px;flex:none;padding:1px 6px;font-size:10px}.NPLLIq_rowMeta{color:var(--dsw-alias-label-secondary);font-variant-numeric:tabular-nums;flex:none;font-size:11px}.NPLLIq_bar{background:var(--dsw-alias-bg-layer-1);border-radius:3px;height:6px;overflow:hidden}.NPLLIq_fillIdle{background:var(--dsw-alias-brand-primary);height:100%;transition:width .2s}.NPLLIq_fillWarn{background:var(--dsw-alias-state-warn-primary);height:100%;transition:width .2s}.NPLLIq_fillOver{background:var(--dsw-alias-state-error-primary);height:100%;transition:width .2s}.NPLLIq_controls{align-items:center;gap:6px;display:flex}.NPLLIq_input{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-1);min-width:0;color:var(--dsw-alias-label-primary);border-radius:6px;flex:1;padding:4px 8px;font-size:11px}.NPLLIq_input::placeholder{color:var(--dsw-alias-label-secondary)}.NPLLIq_save,.NPLLIq_switch{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);cursor:pointer;border-radius:6px;flex:none;padding:4px 10px;font-size:11px}.NPLLIq_save:hover,.NPLLIq_switch:hover:not(:disabled){background:var(--dsw-alias-bg-layer-2)}.NPLLIq_switch:disabled{opacity:.5;cursor:default}.NPLLIq_notice,.NPLLIq_noticeError{color:var(--dsw-alias-label-secondary);padding:10px 12px;font-size:12px}.NPLLIq_noticeError{color:var(--dsw-alias-state-error-primary)}.NPLLIq_tab{background:var(--dsw-alias-brand-primary);border:1px solid var(--dsw-alias-brand-primary);color:var(--dsw-alias-bg-overlay);pointer-events:auto;cursor:pointer;z-index:90;border-radius:10px;align-items:center;gap:6px;padding:9px 14px;font-size:12px;font-weight:600;display:inline-flex;position:fixed;top:56px;right:12px;box-shadow:0 6px 20px #00000040}.NPLLIq_tab:hover{filter:brightness(1.08)}.NPLLIq_tabIcon{font-size:12px;line-height:1}.NPLLIq_tabLabel{white-space:nowrap}.NPLLIq_headerActions{flex:none;align-items:center;gap:4px;display:flex}.NPLLIq_settingsBtn{color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;border-radius:6px;flex:none;padding:2px 6px;font-size:11px}.NPLLIq_settingsBtn:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-1)}.NPLLIq_selectBtn{border:1px solid var(--dsw-alias-border-l1);color:var(--dsw-alias-brand-primary);cursor:pointer;background:0 0;border-radius:6px;flex:none;padding:1px 6px;font-size:10px}.NPLLIq_selectBtn:hover{background:var(--dsw-alias-bg-layer-1)}.NPLLIq_gearBtn{color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;flex:none;margin-left:6px;padding:0 2px;font-size:12px;line-height:1}.NPLLIq_gearBtn:hover{color:var(--dsw-alias-label-primary)}.NPLLIq_fullNotice{color:var(--dsw-alias-state-error-primary);background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-state-error-primary);border-radius:8px;margin:6px 12px;padding:8px 10px;font-size:12px}.NPLLIq_dialog{z-index:100;box-sizing:border-box;background:var(--dsw-alias-bg-overlay);border:1px solid var(--dsw-alias-border-l1);border-radius:12px;flex-direction:column;gap:12px;width:280px;max-height:80vh;padding:14px;display:flex;position:fixed;top:50%;left:50%;overflow-y:auto;transform:translate(-50%,-50%);box-shadow:0 8px 28px #00000040}.NPLLIq_dialogTitle{color:var(--dsw-alias-label-primary);font-size:13px;font-weight:600}.NPLLIq_dialogSection{flex-direction:column;gap:6px;display:flex}.NPLLIq_dialogLabel{color:var(--dsw-alias-label-primary);font-size:12px;font-weight:500}.NPLLIq_monitorHint{color:var(--dsw-alias-label-secondary);font-size:11px}.NPLLIq_monitorList{flex-direction:column;gap:2px;max-height:180px;display:flex;overflow-y:auto}.NPLLIq_monitorRow{color:var(--dsw-alias-label-primary);cursor:pointer;border-radius:6px;align-items:center;gap:6px;padding:2px 4px;font-size:12px;display:flex}.NPLLIq_monitorRow:hover{background:var(--dsw-alias-bg-layer-1)}.NPLLIq_monitorName{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.NPLLIq_radioRow{color:var(--dsw-alias-label-primary);cursor:pointer;border-radius:6px;align-items:center;gap:6px;padding:3px 4px;font-size:12px;display:flex}.NPLLIq_radioRow:hover{background:var(--dsw-alias-bg-layer-1)}.NPLLIq_dialogActions{justify-content:flex-end;gap:6px;display:flex}.NPLLIq_dialogBtn,.NPLLIq_dialogBtnPrimary{border:1px solid var(--dsw-alias-border-l1);cursor:pointer;border-radius:6px;padding:4px 12px;font-size:11px}.NPLLIq_dialogBtn{background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary)}.NPLLIq_dialogBtnPrimary{background:var(--dsw-alias-brand-primary);border-color:var(--dsw-alias-brand-primary);color:var(--dsw-alias-bg-overlay)}.NPLLIq_dialogHeader{z-index:2;background:var(--dsw-alias-bg-overlay);cursor:grab;user-select:none;touch-action:none;justify-content:space-between;align-items:center;gap:8px;margin:-14px -14px 0;padding:14px 14px 8px;display:flex;position:sticky;top:-14px}.NPLLIq_dialogHeader:active{cursor:grabbing}.NPLLIq_dialogClose{width:28px;height:28px;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;border-radius:8px;flex:none;justify-content:center;align-items:center;font-size:18px;line-height:1;display:flex}.NPLLIq_dialogClose:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-1)}.NPLLIq_resetRow{flex-wrap:wrap;align-items:center;gap:6px;display:flex}.NPLLIq_resetSelect{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);border-radius:6px;padding:3px 6px;font-size:12px}.NPLLIq_logTable{border-collapse:collapse;width:100%;font-size:11px}.NPLLIq_logTable th{text-align:left;color:var(--dsw-alias-label-secondary);border-bottom:1px solid var(--dsw-alias-border-l1);padding:4px 6px;font-weight:500}.NPLLIq_logTable td{color:var(--dsw-alias-label-primary);border-bottom:1px solid var(--dsw-alias-border-l1);font-variant-numeric:tabular-nums;padding:4px 6px}.NPLLIq_logDayCol{white-space:nowrap;color:var(--dsw-alias-label-secondary)}.NPLLIq_logModelCol{text-overflow:ellipsis;white-space:nowrap;max-width:160px;overflow:hidden}.NPLLIq_logUsedCol{text-align:right}.NPLLIq_logDialog{gap:0;width:380px;max-height:min(72vh,480px);padding:0}.NPLLIq_logHeader{background:var(--dsw-alias-bg-overlay);cursor:grab;user-select:none;touch-action:none;flex:none;justify-content:space-between;align-items:center;gap:8px;padding:14px 14px 8px;display:flex}.NPLLIq_logHeader:active{cursor:grabbing}.NPLLIq_logScroll{flex:1;min-height:0;padding:0 14px 14px;overflow:auto}";
|
|
123
123
|
const tagId = "@jxgame2020/dsh-token-quota/TokenQuotaPanel.module.css";
|
|
124
124
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
|
|
125
125
|
const tag = document.createElement("style");
|
|
@@ -129,56 +129,59 @@ window.__ModuleLoader__.load({
|
|
|
129
129
|
document.head.appendChild(tag);
|
|
130
130
|
}
|
|
131
131
|
var TokenQuotaPanel_module_css_default = {
|
|
132
|
-
"
|
|
132
|
+
"logUsedCol": "NPLLIq_logUsedCol",
|
|
133
|
+
"fillIdle": "NPLLIq_fillIdle",
|
|
134
|
+
"monitorHint": "NPLLIq_monitorHint",
|
|
135
|
+
"logScroll": "NPLLIq_logScroll",
|
|
136
|
+
"title": "NPLLIq_title",
|
|
137
|
+
"body": "NPLLIq_body",
|
|
138
|
+
"row": "NPLLIq_row",
|
|
133
139
|
"gearBtn": "NPLLIq_gearBtn",
|
|
134
|
-
"controls": "NPLLIq_controls",
|
|
135
|
-
"input": "NPLLIq_input",
|
|
136
|
-
"switch": "NPLLIq_switch",
|
|
137
140
|
"monitorName": "NPLLIq_monitorName",
|
|
138
|
-
"
|
|
139
|
-
"
|
|
140
|
-
"
|
|
141
|
-
"
|
|
142
|
-
"
|
|
143
|
-
"
|
|
144
|
-
"
|
|
145
|
-
"
|
|
146
|
-
"
|
|
147
|
-
"
|
|
148
|
-
"
|
|
149
|
-
"logModelCol": "NPLLIq_logModelCol",
|
|
150
|
-
"collapse": "NPLLIq_collapse",
|
|
151
|
-
"monitorRow": "NPLLIq_monitorRow",
|
|
141
|
+
"radioRow": "NPLLIq_radioRow",
|
|
142
|
+
"header": "NPLLIq_header",
|
|
143
|
+
"fillOver": "NPLLIq_fillOver",
|
|
144
|
+
"tabLabel": "NPLLIq_tabLabel",
|
|
145
|
+
"selectBtn": "NPLLIq_selectBtn",
|
|
146
|
+
"input": "NPLLIq_input",
|
|
147
|
+
"notice": "NPLLIq_notice",
|
|
148
|
+
"dialogSection": "NPLLIq_dialogSection",
|
|
149
|
+
"dialogLabel": "NPLLIq_dialogLabel",
|
|
150
|
+
"dialogActions": "NPLLIq_dialogActions",
|
|
151
|
+
"headerActions": "NPLLIq_headerActions",
|
|
152
152
|
"rowName": "NPLLIq_rowName",
|
|
153
|
-
"fillWarn": "NPLLIq_fillWarn",
|
|
154
153
|
"rowMeta": "NPLLIq_rowMeta",
|
|
155
|
-
"
|
|
156
|
-
"subtitle": "NPLLIq_subtitle",
|
|
157
|
-
"resetRow": "NPLLIq_resetRow",
|
|
154
|
+
"collapse": "NPLLIq_collapse",
|
|
158
155
|
"save": "NPLLIq_save",
|
|
159
|
-
"selectBtn": "NPLLIq_selectBtn",
|
|
160
156
|
"resetSelect": "NPLLIq_resetSelect",
|
|
161
|
-
"
|
|
162
|
-
"
|
|
163
|
-
"headerActions": "NPLLIq_headerActions",
|
|
164
|
-
"panel": "NPLLIq_panel",
|
|
165
|
-
"monitorHint": "NPLLIq_monitorHint",
|
|
166
|
-
"fullNotice": "NPLLIq_fullNotice",
|
|
167
|
-
"dialogSection": "NPLLIq_dialogSection",
|
|
168
|
-
"tabIcon": "NPLLIq_tabIcon",
|
|
169
|
-
"settingsBtn": "NPLLIq_settingsBtn",
|
|
170
|
-
"dialogLabel": "NPLLIq_dialogLabel",
|
|
171
|
-
"title": "NPLLIq_title",
|
|
172
|
-
"notice": "NPLLIq_notice",
|
|
157
|
+
"logDialog": "NPLLIq_logDialog",
|
|
158
|
+
"dialogClose": "NPLLIq_dialogClose",
|
|
173
159
|
"dialog": "NPLLIq_dialog",
|
|
174
|
-
"
|
|
175
|
-
"
|
|
160
|
+
"dialogBtnPrimary": "NPLLIq_dialogBtnPrimary",
|
|
161
|
+
"rowHeader": "NPLLIq_rowHeader",
|
|
162
|
+
"controls": "NPLLIq_controls",
|
|
163
|
+
"currentBadge": "NPLLIq_currentBadge",
|
|
164
|
+
"tabIcon": "NPLLIq_tabIcon",
|
|
165
|
+
"dialogHeader": "NPLLIq_dialogHeader",
|
|
176
166
|
"tab": "NPLLIq_tab",
|
|
177
167
|
"dialogTitle": "NPLLIq_dialogTitle",
|
|
178
|
-
"
|
|
179
|
-
"
|
|
168
|
+
"logDayCol": "NPLLIq_logDayCol",
|
|
169
|
+
"settingsBtn": "NPLLIq_settingsBtn",
|
|
170
|
+
"fillWarn": "NPLLIq_fillWarn",
|
|
171
|
+
"fullNotice": "NPLLIq_fullNotice",
|
|
172
|
+
"bar": "NPLLIq_bar",
|
|
173
|
+
"dialogBtn": "NPLLIq_dialogBtn",
|
|
174
|
+
"switch": "NPLLIq_switch",
|
|
175
|
+
"panel": "NPLLIq_panel",
|
|
176
|
+
"headerText": "NPLLIq_headerText",
|
|
177
|
+
"monitorRow": "NPLLIq_monitorRow",
|
|
178
|
+
"logHeader": "NPLLIq_logHeader",
|
|
179
|
+
"resetRow": "NPLLIq_resetRow",
|
|
180
|
+
"monitorList": "NPLLIq_monitorList",
|
|
180
181
|
"logTable": "NPLLIq_logTable",
|
|
181
|
-
"
|
|
182
|
+
"subtitle": "NPLLIq_subtitle",
|
|
183
|
+
"noticeError": "NPLLIq_noticeError",
|
|
184
|
+
"logModelCol": "NPLLIq_logModelCol"
|
|
182
185
|
};
|
|
183
186
|
//#endregion
|
|
184
187
|
//#region lib/types/client/TokenQuotaPanel.js
|
|
@@ -311,6 +314,16 @@ window.__ModuleLoader__.load({
|
|
|
311
314
|
name: model.name
|
|
312
315
|
}))).sort((a, b) => a.key.localeCompare(b.key)), [groups]);
|
|
313
316
|
const isMonitoredKey = (key) => monitored === null || monitored.includes(key);
|
|
317
|
+
(0, react.useEffect)(() => {
|
|
318
|
+
if (!logOpen) return;
|
|
319
|
+
let cancelled = false;
|
|
320
|
+
fetch("/token-quota/log", { headers: { accept: "application/json" } }).then((response) => response.json(), () => null).then((data) => {
|
|
321
|
+
if (!cancelled) actions.setLog(data);
|
|
322
|
+
});
|
|
323
|
+
return () => {
|
|
324
|
+
cancelled = true;
|
|
325
|
+
};
|
|
326
|
+
}, [logOpen, actions]);
|
|
314
327
|
if (collapsed) return (0, react_jsx_runtime.jsxs)("div", {
|
|
315
328
|
className: TokenQuotaPanel_module_css_default.tab,
|
|
316
329
|
role: "button",
|
|
@@ -338,16 +351,6 @@ window.__ModuleLoader__.load({
|
|
|
338
351
|
const openSettings = () => {
|
|
339
352
|
actions.setDialogOpen(true);
|
|
340
353
|
};
|
|
341
|
-
(0, react.useEffect)(() => {
|
|
342
|
-
if (!logOpen) return;
|
|
343
|
-
let cancelled = false;
|
|
344
|
-
fetch("/token-quota/log", { headers: { accept: "application/json" } }).then((response) => response.json(), () => null).then((data) => {
|
|
345
|
-
if (!cancelled) actions.setLog(data);
|
|
346
|
-
});
|
|
347
|
-
return () => {
|
|
348
|
-
cancelled = true;
|
|
349
|
-
};
|
|
350
|
-
}, [logOpen, actions]);
|
|
351
354
|
const commitLimit = (row) => {
|
|
352
355
|
const raw = drafts[row.key]?.trim();
|
|
353
356
|
setDrafts((prev) => {
|
|
@@ -663,7 +666,7 @@ window.__ModuleLoader__.load({
|
|
|
663
666
|
}),
|
|
664
667
|
logOpen && (0, react_jsx_runtime.jsxs)("div", {
|
|
665
668
|
ref: logRef,
|
|
666
|
-
className: TokenQuotaPanel_module_css_default.dialog
|
|
669
|
+
className: `${TokenQuotaPanel_module_css_default.dialog} ${TokenQuotaPanel_module_css_default.logDialog}`,
|
|
667
670
|
style: logPos !== null ? {
|
|
668
671
|
left: logPos.x,
|
|
669
672
|
top: logPos.y,
|
|
@@ -671,7 +674,7 @@ window.__ModuleLoader__.load({
|
|
|
671
674
|
} : void 0,
|
|
672
675
|
children: [
|
|
673
676
|
(0, react_jsx_runtime.jsxs)("div", {
|
|
674
|
-
className: TokenQuotaPanel_module_css_default.
|
|
677
|
+
className: TokenQuotaPanel_module_css_default.logHeader,
|
|
675
678
|
onPointerDown: (event) => {
|
|
676
679
|
beginDrag(event, logRef.current, setLogPos);
|
|
677
680
|
},
|
|
@@ -692,30 +695,33 @@ window.__ModuleLoader__.load({
|
|
|
692
695
|
className: TokenQuotaPanel_module_css_default.notice,
|
|
693
696
|
children: t("logEmpty")
|
|
694
697
|
}),
|
|
695
|
-
(0, react_jsx_runtime.
|
|
696
|
-
className: TokenQuotaPanel_module_css_default.
|
|
697
|
-
children:
|
|
698
|
-
|
|
699
|
-
(0, react_jsx_runtime.jsx)("
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
698
|
+
(0, react_jsx_runtime.jsx)("div", {
|
|
699
|
+
className: TokenQuotaPanel_module_css_default.logScroll,
|
|
700
|
+
children: (0, react_jsx_runtime.jsxs)("table", {
|
|
701
|
+
className: TokenQuotaPanel_module_css_default.logTable,
|
|
702
|
+
children: [(0, react_jsx_runtime.jsx)("thead", { children: (0, react_jsx_runtime.jsxs)("tr", { children: [
|
|
703
|
+
(0, react_jsx_runtime.jsx)("th", { children: t("logDay") }),
|
|
704
|
+
(0, react_jsx_runtime.jsx)("th", { children: t("logModel") }),
|
|
705
|
+
(0, react_jsx_runtime.jsx)("th", {
|
|
706
|
+
className: TokenQuotaPanel_module_css_default.logUsedCol,
|
|
707
|
+
children: t("logUsed")
|
|
708
|
+
})
|
|
709
|
+
] }) }), (0, react_jsx_runtime.jsx)("tbody", { children: log?.entries.map((entry) => (0, react_jsx_runtime.jsxs)("tr", { children: [
|
|
710
|
+
(0, react_jsx_runtime.jsx)("td", {
|
|
711
|
+
className: TokenQuotaPanel_module_css_default.logDayCol,
|
|
712
|
+
children: entry.day
|
|
713
|
+
}),
|
|
714
|
+
(0, react_jsx_runtime.jsx)("td", {
|
|
715
|
+
className: TokenQuotaPanel_module_css_default.logModelCol,
|
|
716
|
+
title: entry.key,
|
|
717
|
+
children: entry.key
|
|
718
|
+
}),
|
|
719
|
+
(0, react_jsx_runtime.jsx)("td", {
|
|
720
|
+
className: TokenQuotaPanel_module_css_default.logUsedCol,
|
|
721
|
+
children: formatTokens(entry.used)
|
|
722
|
+
})
|
|
723
|
+
] }, `${entry.day}/${entry.key}`)) })]
|
|
724
|
+
})
|
|
719
725
|
})
|
|
720
726
|
]
|
|
721
727
|
})
|
|
@@ -133,6 +133,20 @@ export function TokenQuotaPanel({ t, load, setLimit, selectModel, setMonitored,
|
|
|
133
133
|
name: model.name,
|
|
134
134
|
}))).sort((a, b) => a.key.localeCompare(b.key)), [groups]);
|
|
135
135
|
const isMonitoredKey = (key) => monitored === null || monitored.includes(key);
|
|
136
|
+
// Fetch the usage log whenever the log dialog opens. Kept ABOVE the
|
|
137
|
+
// collapsed early-return: every hook must run on every render, otherwise
|
|
138
|
+
// React unmounts the component the moment the panel collapses (the old
|
|
139
|
+
// placement made the collapsed tab crash instead of showing).
|
|
140
|
+
useEffect(() => {
|
|
141
|
+
if (!logOpen)
|
|
142
|
+
return;
|
|
143
|
+
let cancelled = false;
|
|
144
|
+
void fetch('/token-quota/log', { headers: { accept: 'application/json' } }).then((response) => response.json(), () => null).then((data) => {
|
|
145
|
+
if (!cancelled)
|
|
146
|
+
actions.setLog(data);
|
|
147
|
+
});
|
|
148
|
+
return () => { cancelled = true; };
|
|
149
|
+
}, [logOpen, actions]);
|
|
136
150
|
if (collapsed) {
|
|
137
151
|
return (_jsxs("div", { className: css.tab, role: "button", tabIndex: 0, title: t('expandHint'), "aria-label": t('expandHint'), onClick: () => { setCollapsed(false); }, onKeyDown: (event) => {
|
|
138
152
|
if (event.key === 'Enter' || event.key === ' ') {
|
|
@@ -144,17 +158,6 @@ export function TokenQuotaPanel({ t, load, setLimit, selectModel, setMonitored,
|
|
|
144
158
|
const openSettings = () => {
|
|
145
159
|
actions.setDialogOpen(true);
|
|
146
160
|
};
|
|
147
|
-
// Fetch the usage log whenever the log dialog opens.
|
|
148
|
-
useEffect(() => {
|
|
149
|
-
if (!logOpen)
|
|
150
|
-
return;
|
|
151
|
-
let cancelled = false;
|
|
152
|
-
void fetch('/token-quota/log', { headers: { accept: 'application/json' } }).then((response) => response.json(), () => null).then((data) => {
|
|
153
|
-
if (!cancelled)
|
|
154
|
-
actions.setLog(data);
|
|
155
|
-
});
|
|
156
|
-
return () => { cancelled = true; };
|
|
157
|
-
}, [logOpen, actions]);
|
|
158
161
|
const commitLimit = (row) => {
|
|
159
162
|
const raw = drafts[row.key]?.trim();
|
|
160
163
|
setDrafts((prev) => {
|
|
@@ -210,6 +213,6 @@ export function TokenQuotaPanel({ t, load, setLimit, selectModel, setMonitored,
|
|
|
210
213
|
hour: reset?.hour ?? 0,
|
|
211
214
|
minute: Number(event.target.value),
|
|
212
215
|
});
|
|
213
|
-
}, children: Array.from({ length: 12 }, (_, i) => i * 5).map(minute => (_jsxs("option", { value: minute, children: [String(minute).padStart(2, '0'), " \u5206"] }, minute))) })] })] })] })), logOpen && (_jsxs("div", { ref: logRef, className: css.dialog
|
|
216
|
+
}, children: Array.from({ length: 12 }, (_, i) => i * 5).map(minute => (_jsxs("option", { value: minute, children: [String(minute).padStart(2, '0'), " \u5206"] }, minute))) })] })] })] })), logOpen && (_jsxs("div", { ref: logRef, className: `${css.dialog} ${css.logDialog}`, style: logPos !== null ? { left: logPos.x, top: logPos.y, transform: 'none' } : undefined, children: [_jsxs("div", { className: css.logHeader, onPointerDown: (event) => { beginDrag(event, logRef.current, setLogPos); }, children: [_jsx("div", { className: css.dialogTitle, children: t('logsTitle') }), _jsx("button", { type: "button", className: css.dialogClose, title: t('close'), onClick: () => { actions.setLogOpen(false); }, children: "\u00D7" })] }), log !== null && log.entries.length === 0 && (_jsx("div", { className: css.notice, children: t('logEmpty') })), _jsx("div", { className: css.logScroll, children: _jsxs("table", { className: css.logTable, children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { children: t('logDay') }), _jsx("th", { children: t('logModel') }), _jsx("th", { className: css.logUsedCol, children: t('logUsed') })] }) }), _jsx("tbody", { children: log?.entries.map(entry => (_jsxs("tr", { children: [_jsx("td", { className: css.logDayCol, children: entry.day }), _jsx("td", { className: css.logModelCol, title: entry.key, children: entry.key }), _jsx("td", { className: css.logUsedCol, children: formatTokens(entry.used) })] }, `${entry.day}/${entry.key}`))) })] }) })] }))] }));
|
|
214
217
|
}
|
|
215
218
|
//# sourceMappingURL=TokenQuotaPanel.js.map
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jxgame2020/dsh-token-quota",
|
|
3
3
|
"description": "Daily per-model token quota accounting, enforcement, and a floating quota panel for the DeepSeek Harness — a fully self-contained plugin (no core-Harness wiring, no generated RPC contract)",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.4",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|