dsh-connect-modelscope-token-plan 0.2.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/CHANGELOG.md +113 -0
- package/README.md +38 -2
- package/client.js +918 -111
- package/cordis.patch.yml +31 -0
- package/lib/{host-config-C4bWNPWU.js → host-config.js} +340 -89
- package/lib/index.js +2681 -410
- package/lib/{llm-adapter-DnaNNORe.js → llm-adapter.js} +21 -12
- package/locale/en.json +2 -1
- package/package.json +13 -5
package/client.js
CHANGED
|
@@ -11,18 +11,61 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
11
11
|
};
|
|
12
12
|
var __commonJSMin = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).exports, mod), cb = null), mod.exports);
|
|
13
13
|
|
|
14
|
+
//#endregion
|
|
15
|
+
//#region src/shared/wire.ts
|
|
16
|
+
var HIDE_ALL_MODELS, SNAPSHOT_REQUIRED_KEYS;
|
|
17
|
+
var init_wire = __esmMin((() => {
|
|
18
|
+
HIDE_ALL_MODELS = "__hide_all__";
|
|
19
|
+
SNAPSHOT_REQUIRED_KEYS = Object.freeze([
|
|
20
|
+
"ok",
|
|
21
|
+
"name",
|
|
22
|
+
"version",
|
|
23
|
+
"now",
|
|
24
|
+
"pollSeconds",
|
|
25
|
+
"cacheSeconds",
|
|
26
|
+
"token",
|
|
27
|
+
"balance",
|
|
28
|
+
"quota",
|
|
29
|
+
"events",
|
|
30
|
+
"trend",
|
|
31
|
+
"models",
|
|
32
|
+
"provider",
|
|
33
|
+
"shapeWarnings",
|
|
34
|
+
"quotaError"
|
|
35
|
+
]);
|
|
36
|
+
}));
|
|
37
|
+
|
|
14
38
|
//#endregion
|
|
15
39
|
//#region src/client/const.ts
|
|
16
40
|
function modelscopeModelUrl(id) {
|
|
17
|
-
return `${MODELSCOPE_MODEL_URL_BASE}/${
|
|
41
|
+
return `${MODELSCOPE_MODEL_URL_BASE}/${id.split("/").map(encodeURIComponent).join("/")}`;
|
|
18
42
|
}
|
|
19
43
|
/** 从 `owner/model` 取 owner(无斜杠时整段作 owner)。 */
|
|
20
44
|
function catalogOwner(id) {
|
|
21
45
|
const slash = id.indexOf("/");
|
|
22
46
|
return slash >= 0 ? id.slice(0, slash) : id;
|
|
23
47
|
}
|
|
24
|
-
|
|
48
|
+
/**
|
|
49
|
+
* 徽章用的窗口短标注(tokens → "128K" / "1M")。
|
|
50
|
+
*
|
|
51
|
+
* K=1024、M=1024²,与档位值(131072…1048576)同一把尺。**不美化**:目录声明的
|
|
52
|
+
* 128000 显示成 "125K" 而不是 "128K"——标签必须能从数值反推出来,四舍五入到
|
|
53
|
+
* 好看的整数是撒谎。≥1M 用 M(`1.25M` 保留至多一位小数,去掉尾随 .0),
|
|
54
|
+
* ≥1024 用 K,更小的原样显示。
|
|
55
|
+
* @param {number} tokens - 窗口大小(tokens)。
|
|
56
|
+
* @returns {string} 短标注。
|
|
57
|
+
*/
|
|
58
|
+
function formatWindow(tokens) {
|
|
59
|
+
if (tokens >= 1048576) {
|
|
60
|
+
const m = tokens / 1048576;
|
|
61
|
+
return `${m >= 10 || Number.isInteger(m) ? Math.round(m) : Math.round(m * 10) / 10}M`;
|
|
62
|
+
}
|
|
63
|
+
if (tokens >= 1024) return `${Math.round(tokens / 1024)}K`;
|
|
64
|
+
return String(tokens);
|
|
65
|
+
}
|
|
66
|
+
var NS, PANEL_ID, SNAPSHOT_PATH, MODELS_PATH, TOKEN_PATH, TOKEN_FORGET_PATH, PROBE_PATH, PROVIDER_PATH, PROVIDER_ROSTER_PATH, PROVIDER_CONTEXT_PATH, PROVIDER_RESET_PATH, DRAW_PATH, DRAW_RESET_PATH, MODELSCOPE_TOKEN_URL, MODELSCOPE_USAGE_URL, MODELSCOPE_MODEL_URL_BASE, FEATURED_OWNERS;
|
|
25
67
|
var init_const = __esmMin((() => {
|
|
68
|
+
init_wire();
|
|
26
69
|
NS = "dsh-connect-modelscope-token-plan";
|
|
27
70
|
PANEL_ID = NS;
|
|
28
71
|
SNAPSHOT_PATH = `/api/${NS}/snapshot`;
|
|
@@ -32,8 +75,10 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
32
75
|
PROBE_PATH = `/api/${NS}/probe`;
|
|
33
76
|
PROVIDER_PATH = `/api/${NS}/provider`;
|
|
34
77
|
PROVIDER_ROSTER_PATH = `/api/${NS}/provider/roster`;
|
|
78
|
+
PROVIDER_CONTEXT_PATH = `/api/${NS}/provider/context`;
|
|
35
79
|
PROVIDER_RESET_PATH = `/api/${NS}/provider/reset`;
|
|
36
|
-
|
|
80
|
+
DRAW_PATH = `/api/${NS}/draw`;
|
|
81
|
+
DRAW_RESET_PATH = `/api/${NS}/draw/reset`;
|
|
37
82
|
MODELSCOPE_TOKEN_URL = "https://modelscope.cn/my/myaccesstoken";
|
|
38
83
|
MODELSCOPE_USAGE_URL = "https://modelscope.cn/magicube/usage?tab=consume";
|
|
39
84
|
MODELSCOPE_MODEL_URL_BASE = "https://www.modelscope.cn/models";
|
|
@@ -62,6 +107,10 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
62
107
|
"panel.authError": "令牌被拒绝(401/403)。请到「接入」tab 更换访问令牌。",
|
|
63
108
|
"panel.noToken": "还没有配置魔搭访问令牌。到「接入」tab 粘贴一枚 ms-… 令牌即可。",
|
|
64
109
|
"panel.shapeDrift": "上游返回的结构可能有变:{detail}",
|
|
110
|
+
"panel.payloadError": "Host 返回了无法读取的响应(可能页面被登录墙或代理截获)。",
|
|
111
|
+
"shape.missingKeys": "Host 快照缺少必需的顶层键:{keys}",
|
|
112
|
+
"common.httpError": "无法读取 Host 响应(非预期响应,可能页面被登录墙截获)。",
|
|
113
|
+
"common.timeout": "请求超时,请重试。",
|
|
65
114
|
"tab.quota": "额度",
|
|
66
115
|
"tab.models": "模型",
|
|
67
116
|
"tab.access": "接入",
|
|
@@ -81,8 +130,7 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
81
130
|
"balance.unavailable": "魔粒余额暂不可读:{error}",
|
|
82
131
|
"quota.headline": "今日 {calls} 次 · {models} 个模型",
|
|
83
132
|
"quota.note": "本地口径:只统计经本插件的调用,直连魔搭的其它客户端不计入;消耗总量以官方魔粒余额为准。",
|
|
84
|
-
"
|
|
85
|
-
"models.loading": "读取目录中…",
|
|
133
|
+
"quota.unavailable": "本地计数本次读不到(原因见上方提示)",
|
|
86
134
|
"probe.validity": "验令牌(零额度)",
|
|
87
135
|
"probe.validOk": "令牌有效(HTTP {status})",
|
|
88
136
|
"probe.fail": "失败:{error}",
|
|
@@ -94,6 +142,7 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
94
142
|
"provider.enable": "把魔搭模型接入 DSH 模型选择器",
|
|
95
143
|
"provider.on": "已接入",
|
|
96
144
|
"provider.off": "未接入",
|
|
145
|
+
"provider.busy": "切换中…",
|
|
97
146
|
"provider.registered": "已注册(模型可在选择器里选用)",
|
|
98
147
|
"provider.notRegistered": "未注册",
|
|
99
148
|
"provider.llmMissing": "本机 Host 未提供 LLM 注册服务",
|
|
@@ -110,18 +159,45 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
110
159
|
"provider.notConfigured": "还没有访问令牌,先到「接入」tab 配置",
|
|
111
160
|
"provider.source.panel": "面板",
|
|
112
161
|
"provider.source.config": "配置",
|
|
162
|
+
"provider.contextWindow": "上下文窗口",
|
|
163
|
+
"provider.contextDefault": "默认(跟随目录)",
|
|
164
|
+
"provider.contextHint": "目录没声明窗口的模型按此值生效;保存后立即重新注册",
|
|
165
|
+
"provider.contextCuratedTitle": "窗口来自内置策展表(models.dev 交叉真值),平台未亲口声明;带 * 标记",
|
|
166
|
+
"section.draw": "出图工具",
|
|
167
|
+
"draw.enable": "给 agent 注册 modelscope_draw_image 工具",
|
|
168
|
+
"draw.on": "已开启",
|
|
169
|
+
"draw.off": "未开启",
|
|
170
|
+
"draw.busy": "切换中…",
|
|
171
|
+
"draw.mounted": "已注册:{tool}",
|
|
172
|
+
"draw.notMounted": "未注册——勾选上方开关即可开启。",
|
|
173
|
+
"draw.restartHint": "开关立即保存,但工具的挂载发生在**下次 Host 启动**(工具注册无法撤销)。",
|
|
174
|
+
"draw.models": "出图将使用以下模型",
|
|
175
|
+
"draw.badge": "出图",
|
|
176
|
+
"draw.noCandidates": "当前目录里没有出图模型(平台声明 image-to-image / text-to-image 的条目为空);出图不可用。",
|
|
177
|
+
"draw.needsKey": "还没有访问令牌,先到「接入」tab 配置。",
|
|
178
|
+
"draw.reset": "回到默认",
|
|
179
|
+
"draw.error": "出图设置保存失败:{error}",
|
|
180
|
+
"draw.source.panel": "面板",
|
|
181
|
+
"draw.source.config": "配置",
|
|
182
|
+
"draw.absent.noTools": "开关已开,但本机 Host 未提供 agent tools 服务,出图工具缺席(Host 侧限制,非配置问题)。",
|
|
183
|
+
"draw.absent.peerLoad": "开关已开,但 tools 模块加载失败,出图工具缺席(Host 问题,非配置)。",
|
|
184
|
+
"draw.absent.registry": "开关已开,但工具注册被 Host 拒绝,出图工具缺席(Host 问题,非配置)。",
|
|
185
|
+
"draw.absent.unknown": "开关已开,但出图工具未挂载,原因未知。",
|
|
113
186
|
"trend.none": "还没有本地调用记录。",
|
|
114
187
|
"trend.legend": "柱长相对区间内最大值,仅本地口径。",
|
|
115
188
|
"events.none": "暂无事件。",
|
|
116
189
|
"events.quota": "额度",
|
|
117
190
|
"events.rate_limit": "限频",
|
|
118
191
|
"events.error": "错误",
|
|
192
|
+
"events.unknown": "未知",
|
|
119
193
|
"token.status": "状态:{present}(来源 {source},校验 {valid})",
|
|
120
194
|
"token.save": "保存",
|
|
121
195
|
"token.forget": "忘掉已保存",
|
|
122
196
|
"token.placeholder": "粘贴 ms-… 访问令牌",
|
|
197
|
+
"token.ariaLabel": "魔搭访问令牌",
|
|
123
198
|
"token.hint": "在魔搭「访问令牌」页生成。保存即生效(写入 DSH 凭据服务,不依赖重启)。环境变量 MODELSCOPE_API_KEY 只在 DSH 启动时读一次,且优先级高于此处保存的值——设了它就别再用面板存。",
|
|
124
199
|
"token.ephemeral": "此 Host 没有凭据服务:面板保存的令牌重启即丢,请改用凭据服务或环境变量。",
|
|
200
|
+
"token.readError": "读不到凭据存储,无法确认令牌状态(不代表没配令牌):{error}",
|
|
125
201
|
"token.link": "打开魔搭访问令牌页 →",
|
|
126
202
|
"source.credentials": "凭据服务",
|
|
127
203
|
"source.env": "环境变量",
|
|
@@ -130,7 +206,13 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
130
206
|
"present.yes": "已配置",
|
|
131
207
|
"present.no": "未配置",
|
|
132
208
|
"validity.yes": "有效",
|
|
133
|
-
"validity.no": "未校验"
|
|
209
|
+
"validity.no": "未校验",
|
|
210
|
+
"validity.bad": "无效",
|
|
211
|
+
"composer.prefix": "魔搭",
|
|
212
|
+
"composer.pillTitle": "魔搭额度:官方魔粒余额 + 今日本地调用次数",
|
|
213
|
+
"composer.balanceUnit": "魔粒",
|
|
214
|
+
"composer.localCalls": "今日本地 {calls} 次",
|
|
215
|
+
"composer.stale": "上次数据"
|
|
134
216
|
};
|
|
135
217
|
en = {
|
|
136
218
|
"panel.title": "ModelScope Access · Quota",
|
|
@@ -146,6 +228,10 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
146
228
|
"panel.authError": "Token rejected (401/403). Replace the access token in the Access tab.",
|
|
147
229
|
"panel.noToken": "No ModelScope access token configured yet. Paste an ms-… token in the Access tab.",
|
|
148
230
|
"panel.shapeDrift": "Upstream shape may have changed: {detail}",
|
|
231
|
+
"panel.payloadError": "Host returned an unreadable response (a login wall or proxy may have intercepted it).",
|
|
232
|
+
"shape.missingKeys": "Host snapshot omitted required top-level key(s): {keys}",
|
|
233
|
+
"common.httpError": "Unexpected Host response (a login wall or proxy may have intercepted it).",
|
|
234
|
+
"common.timeout": "Request timed out; please retry.",
|
|
149
235
|
"tab.quota": "Quota",
|
|
150
236
|
"tab.models": "Models",
|
|
151
237
|
"tab.access": "Access",
|
|
@@ -165,8 +251,7 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
165
251
|
"balance.unavailable": "Magicube balance temporarily unreadable: {error}",
|
|
166
252
|
"quota.headline": "Today {calls} calls · {models} models",
|
|
167
253
|
"quota.note": "Local scope: only calls through this plugin are counted; clients calling ModelScope directly are not. Total consumption is governed by the official Magicube balance.",
|
|
168
|
-
"
|
|
169
|
-
"models.loading": "Loading catalog…",
|
|
254
|
+
"quota.unavailable": "Local counters could not be read this time (reason in the notices above)",
|
|
170
255
|
"probe.validity": "Verify token (free)",
|
|
171
256
|
"probe.validOk": "Token valid (HTTP {status})",
|
|
172
257
|
"probe.fail": "Failed: {error}",
|
|
@@ -178,6 +263,7 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
178
263
|
"provider.enable": "Register ModelScope models in the DSH model picker",
|
|
179
264
|
"provider.on": "On",
|
|
180
265
|
"provider.off": "Off",
|
|
266
|
+
"provider.busy": "Switching…",
|
|
181
267
|
"provider.registered": "Registered (models selectable in the picker)",
|
|
182
268
|
"provider.notRegistered": "Not registered",
|
|
183
269
|
"provider.llmMissing": "This Host exposes no LLM service",
|
|
@@ -194,18 +280,45 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
194
280
|
"provider.notConfigured": "No token yet — configure it in the Access tab",
|
|
195
281
|
"provider.source.panel": "panel",
|
|
196
282
|
"provider.source.config": "config",
|
|
283
|
+
"provider.contextWindow": "Context window",
|
|
284
|
+
"provider.contextDefault": "Default (catalog)",
|
|
285
|
+
"provider.contextHint": "Applies to models whose catalog entry declares no window; re-registers immediately",
|
|
286
|
+
"provider.contextCuratedTitle": "Window from the built-in curated table (models.dev cross-check), not declared by the platform; marked with *",
|
|
287
|
+
"section.draw": "Draw tool",
|
|
288
|
+
"draw.enable": "Register the modelscope_draw_image tool for the agent",
|
|
289
|
+
"draw.on": "On",
|
|
290
|
+
"draw.off": "Off",
|
|
291
|
+
"draw.busy": "Switching…",
|
|
292
|
+
"draw.mounted": "Registered: {tool}",
|
|
293
|
+
"draw.notMounted": "Not registered — tick the switch above.",
|
|
294
|
+
"draw.restartHint": "The switch saves at once, but the tool mounts at the NEXT Host start (a tool registration cannot be undone).",
|
|
295
|
+
"draw.models": "Draw calls will use the models below",
|
|
296
|
+
"draw.badge": "image",
|
|
297
|
+
"draw.noCandidates": "This catalogue declares no image model (no entry with image-to-image / text-to-image); drawing is unavailable.",
|
|
298
|
+
"draw.needsKey": "No token yet — configure it in the Access tab.",
|
|
299
|
+
"draw.reset": "Reset",
|
|
300
|
+
"draw.error": "Saving the draw settings failed: {error}",
|
|
301
|
+
"draw.source.panel": "panel",
|
|
302
|
+
"draw.source.config": "config",
|
|
303
|
+
"draw.absent.noTools": "The switch is on, but this Host exposes no agent tools service; the draw tool is absent (a Host limitation, not config).",
|
|
304
|
+
"draw.absent.peerLoad": "The switch is on, but the tools module failed to load; the draw tool is absent (a Host fault, not config).",
|
|
305
|
+
"draw.absent.registry": "The switch is on, but the Host refused the tool registration; the draw tool is absent (a Host fault, not config).",
|
|
306
|
+
"draw.absent.unknown": "The switch is on, but the draw tool is not mounted; the reason is unknown.",
|
|
197
307
|
"trend.none": "No local call records yet.",
|
|
198
308
|
"trend.legend": "Bar lengths are relative to the maximum in the range (local scope only).",
|
|
199
309
|
"events.none": "No events.",
|
|
200
310
|
"events.quota": "Quota",
|
|
201
311
|
"events.rate_limit": "Rate limit",
|
|
202
312
|
"events.error": "Error",
|
|
313
|
+
"events.unknown": "Unknown",
|
|
203
314
|
"token.status": "Status: {present} (source {source}, check {valid})",
|
|
204
315
|
"token.save": "Save",
|
|
205
316
|
"token.forget": "Forget saved",
|
|
206
317
|
"token.placeholder": "Paste an ms-… access token",
|
|
318
|
+
"token.ariaLabel": "ModelScope access token",
|
|
207
319
|
"token.hint": "Generate one on the ModelScope access-token page. Saving takes effect immediately (written to the DSH credentials service, no restart needed). The MODELSCOPE_API_KEY environment variable is read once at DSH launch and ranks ABOVE a value saved here — set it and stop using the panel.",
|
|
208
320
|
"token.ephemeral": "This Host has no credentials service: a token saved in the panel is lost on restart. Use the credentials service or an environment variable.",
|
|
321
|
+
"token.readError": "Could not read the credential store, so the token state is unknown (this does NOT mean no token is configured): {error}",
|
|
209
322
|
"token.link": "Open the ModelScope access-token page →",
|
|
210
323
|
"source.credentials": "credentials",
|
|
211
324
|
"source.env": "environment",
|
|
@@ -214,13 +327,23 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
214
327
|
"present.yes": "configured",
|
|
215
328
|
"present.no": "not configured",
|
|
216
329
|
"validity.yes": "verified",
|
|
217
|
-
"validity.no": "unverified"
|
|
330
|
+
"validity.no": "unverified",
|
|
331
|
+
"validity.bad": "invalid",
|
|
332
|
+
"composer.prefix": "ModelScope",
|
|
333
|
+
"composer.pillTitle": "ModelScope quota: official Magicube balance + today's local call count",
|
|
334
|
+
"composer.balanceUnit": "Magicubes",
|
|
335
|
+
"composer.localCalls": "local today {calls}",
|
|
336
|
+
"composer.stale": "stale data"
|
|
218
337
|
};
|
|
219
338
|
}));
|
|
220
339
|
|
|
221
340
|
//#endregion
|
|
222
341
|
//#region src/client/format.ts
|
|
223
|
-
/**
|
|
342
|
+
/**
|
|
343
|
+
* Time and number formatters(与 sensenova 的 format.ts 同源、按需裁剪——去注释后
|
|
344
|
+
* 代码行重叠 73%,与 agnes 那份已分家(45%)。2026-10-16 实测,复核
|
|
345
|
+
* `node tools/dev/cross-repo-drift.mjs`)。
|
|
346
|
+
*/
|
|
224
347
|
/**
|
|
225
348
|
* Host 声明的 cadence(秒)转毫秒;无可用数字时回退。Host 在源头已 clamp,
|
|
226
349
|
* 面板不做第二次 opinion;亚秒值向上取整到 1s(setInterval 的 0 = 尽快)。
|
|
@@ -248,7 +371,7 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
248
371
|
if (number >= 1e4) return Math.round(number).toLocaleString();
|
|
249
372
|
return String(Math.round(number * 100) / 100);
|
|
250
373
|
}
|
|
251
|
-
/** 填 `{
|
|
374
|
+
/** 填 `{key}` 模板:vars 的每个键对应模板里的同名占位符。 */
|
|
252
375
|
function format(template, vars) {
|
|
253
376
|
let text = template;
|
|
254
377
|
for (const [key, value] of Object.entries(vars || {})) text = text.split(`{${key}}`).join(String(value));
|
|
@@ -350,7 +473,7 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
350
473
|
fontSize: 13,
|
|
351
474
|
color: "var(--dsw-alias-label-secondary)",
|
|
352
475
|
cursor: "pointer",
|
|
353
|
-
|
|
476
|
+
outlineOffset: -2
|
|
354
477
|
},
|
|
355
478
|
tabActive: {
|
|
356
479
|
color: "var(--dsw-alias-label-primary)",
|
|
@@ -632,38 +755,87 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
632
755
|
borderRadius: 12,
|
|
633
756
|
background: "var(--dsw-alias-bg-layer-1)",
|
|
634
757
|
padding: "12px 14px"
|
|
635
|
-
}
|
|
758
|
+
},
|
|
759
|
+
pillTrigger: {
|
|
760
|
+
appearance: "none",
|
|
761
|
+
WebkitAppearance: "none",
|
|
762
|
+
height: 28,
|
|
763
|
+
borderStyle: "none",
|
|
764
|
+
borderWidth: 0,
|
|
765
|
+
background: "none",
|
|
766
|
+
color: "inherit",
|
|
767
|
+
display: "inline-flex",
|
|
768
|
+
alignItems: "center",
|
|
769
|
+
gap: 6,
|
|
770
|
+
padding: "0 10px",
|
|
771
|
+
borderRadius: 999,
|
|
772
|
+
fontSize: 12,
|
|
773
|
+
fontWeight: 600,
|
|
774
|
+
lineHeight: "18px",
|
|
775
|
+
cursor: "default",
|
|
776
|
+
flexShrink: 0,
|
|
777
|
+
whiteSpace: "nowrap"
|
|
778
|
+
},
|
|
779
|
+
pillDot: {
|
|
780
|
+
width: 8,
|
|
781
|
+
height: 8,
|
|
782
|
+
borderRadius: 999,
|
|
783
|
+
flex: "none",
|
|
784
|
+
background: "var(--dsw-alias-label-secondary)"
|
|
785
|
+
},
|
|
786
|
+
pillPlan: { opacity: .72 },
|
|
787
|
+
pillStale: {
|
|
788
|
+
opacity: .62,
|
|
789
|
+
fontStyle: "italic"
|
|
790
|
+
},
|
|
791
|
+
pillTierOk: "#22c55e",
|
|
792
|
+
pillTierExhausted: "#ef4444",
|
|
793
|
+
pillTierUnknown: "#9ca3af",
|
|
794
|
+
pillTierExhaustedText: { color: "var(--dsw-alias-state-error-primary)" }
|
|
636
795
|
};
|
|
637
796
|
}));
|
|
638
797
|
|
|
639
798
|
//#endregion
|
|
640
|
-
//#region src/shared/
|
|
641
|
-
var
|
|
642
|
-
var
|
|
643
|
-
|
|
644
|
-
"
|
|
645
|
-
"
|
|
646
|
-
"
|
|
647
|
-
"
|
|
648
|
-
"
|
|
649
|
-
"
|
|
650
|
-
"
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
799
|
+
//#region src/shared/codes.ts
|
|
800
|
+
var CODE;
|
|
801
|
+
var init_codes = __esmMin((() => {
|
|
802
|
+
CODE = Object.freeze({
|
|
803
|
+
CONFIG_ERROR: "config_error",
|
|
804
|
+
NETWORK_ERROR: "network_error",
|
|
805
|
+
TIMEOUT_ERROR: "timeout_error",
|
|
806
|
+
AUTH_ERROR: "auth_error",
|
|
807
|
+
RATE_LIMITED: "rate_limited",
|
|
808
|
+
QUOTA_EXCEEDED: "quota_exceeded",
|
|
809
|
+
UPSTREAM_ERROR: "upstream_error",
|
|
810
|
+
/**
|
|
811
|
+
* 插件自身抛错(聚合器、状态层、路由之外的意外)。
|
|
812
|
+
*
|
|
813
|
+
* 曾经只作为 `soft()` / `failureCode()` 的兜底字符串出现在快照里,没进这张
|
|
814
|
+
* 表——于是 Client 的 `GUIDANCE_BY_CODE` 也没法映射它(那份映射被测试钉住
|
|
815
|
+
* 「每个键必须在 CODE 里」),最需要人看的内部错误反而没有引导文案。
|
|
816
|
+
* 正式收进码表,两端从此同一份真源。
|
|
817
|
+
*/
|
|
818
|
+
INTERNAL_ERROR: "internal_error",
|
|
819
|
+
/**
|
|
820
|
+
* Client 自产码:Host 响应无法解读成快照(非 JSON / 缺 ok 字段)时,
|
|
821
|
+
* 面板用它走引导文案,而不是把裸英文串("unexpected payload")上屏。
|
|
822
|
+
*
|
|
823
|
+
* 本表是 `GUIDANCE_BY_CODE` 的键集真源——新增 client 码必须同时进
|
|
824
|
+
* 这张表,两侧才同步。
|
|
825
|
+
*/
|
|
826
|
+
PAYLOAD_ERROR: "payload_error"
|
|
827
|
+
});
|
|
660
828
|
}));
|
|
661
829
|
|
|
662
830
|
//#endregion
|
|
663
831
|
//#region src/client/snapshot.ts
|
|
664
832
|
/**
|
|
665
|
-
* 缺失块的空形状,**逐字对齐 Host 的降级实现**(`snapshot-aggregate.ts`
|
|
666
|
-
* `
|
|
833
|
+
* 缺失块的空形状,**逐字对齐 Host 的降级实现**(`snapshot-aggregate.ts` 各
|
|
834
|
+
* `soft()` 失败分支、TokenStatus 的失败分支)。
|
|
835
|
+
*
|
|
836
|
+
* `provider` 块现在是**省略**而不是由 Host 塞一个降级块(2026-10-17):Host 那份
|
|
837
|
+
* 降级形状「看起来就是一个答案」,面板据此认为块在、不再走 `GET /provider` 兜底,
|
|
838
|
+
* 于是只能拿编出来的值渲染。省略之后,兜底真的跑起来,降级形状只剩这里一份。
|
|
667
839
|
*
|
|
668
840
|
* 这里的每一条都是「Host 那一路失败时真的产出的形状」,不是随手捏的:面板渲染
|
|
669
841
|
* 的每一个字段都必须有一个与 Host 同构的空值,否则补出来的空块自己就会炸——
|
|
@@ -676,7 +848,8 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
676
848
|
source: "none",
|
|
677
849
|
valid: null,
|
|
678
850
|
checkedAt: null,
|
|
679
|
-
ephemeral: true
|
|
851
|
+
ephemeral: true,
|
|
852
|
+
readError: null
|
|
680
853
|
};
|
|
681
854
|
case "balance": return {
|
|
682
855
|
available: null,
|
|
@@ -686,7 +859,7 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
686
859
|
error: null
|
|
687
860
|
};
|
|
688
861
|
case "quota": return {
|
|
689
|
-
daily: { usedLocal:
|
|
862
|
+
daily: { usedLocal: null },
|
|
690
863
|
perModel: [],
|
|
691
864
|
countingNote: "local-counting"
|
|
692
865
|
};
|
|
@@ -729,17 +902,23 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
729
902
|
*/
|
|
730
903
|
function interpretSnapshot(body) {
|
|
731
904
|
const payload = body;
|
|
732
|
-
if (payload && payload.ok === false)
|
|
905
|
+
if (payload && payload.ok === false) {
|
|
906
|
+
const code = typeof payload.code === "string" && payload.code !== "" ? payload.code : CODE.PAYLOAD_ERROR;
|
|
907
|
+
return {
|
|
908
|
+
data: null,
|
|
909
|
+
error: {
|
|
910
|
+
message: typeof payload.error === "string" && payload.error !== "" ? payload.error : null,
|
|
911
|
+
code
|
|
912
|
+
}
|
|
913
|
+
};
|
|
914
|
+
}
|
|
915
|
+
if (!payload || payload.ok !== true) return {
|
|
733
916
|
data: null,
|
|
734
917
|
error: {
|
|
735
|
-
message:
|
|
736
|
-
code:
|
|
918
|
+
message: null,
|
|
919
|
+
code: CODE.PAYLOAD_ERROR
|
|
737
920
|
}
|
|
738
921
|
};
|
|
739
|
-
if (!payload || payload.ok !== true) return {
|
|
740
|
-
data: null,
|
|
741
|
-
error: "unexpected payload"
|
|
742
|
-
};
|
|
743
922
|
const raw = payload;
|
|
744
923
|
const missingKeys = SNAPSHOT_REQUIRED_KEYS.filter((key) => raw[key] === void 0);
|
|
745
924
|
return {
|
|
@@ -752,19 +931,23 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
752
931
|
* 把 wire/路由返回的 provider 块归一成 `ProviderStatus`;不合法一律降级。
|
|
753
932
|
* 形状由 wire.ts 钉死,本函数只做防御性归一(旧 Host 可能缺字段)。
|
|
754
933
|
*/
|
|
755
|
-
function providerOf(raw) {
|
|
934
|
+
function providerOf$1(raw) {
|
|
756
935
|
if (raw === null || typeof raw !== "object") return DEGRADED_PROVIDER;
|
|
757
936
|
const p = raw;
|
|
758
937
|
const ids = Array.isArray(p.enabledIds) ? p.enabledIds.map((id) => String(id ?? "")).filter((id) => id !== "") : [];
|
|
759
938
|
const roster = Array.isArray(p.roster) ? p.roster.map((entry) => {
|
|
760
939
|
const row = entry ?? {};
|
|
761
940
|
const id = String(row.id ?? "");
|
|
941
|
+
const w = row.contextWindow;
|
|
942
|
+
const contextSource = row.contextSource === "override" || row.contextSource === "curated" ? row.contextSource : "catalog";
|
|
762
943
|
return {
|
|
763
944
|
id,
|
|
764
945
|
name: typeof row.name === "string" && row.name !== "" ? row.name : id,
|
|
765
946
|
vision: row.vision === true,
|
|
766
947
|
available: row.available !== false,
|
|
767
|
-
quotaExhausted: row.quotaExhausted === true
|
|
948
|
+
quotaExhausted: row.quotaExhausted === true,
|
|
949
|
+
contextSource,
|
|
950
|
+
...typeof w === "number" && Number.isFinite(w) && w > 0 ? { contextWindow: w } : {}
|
|
768
951
|
};
|
|
769
952
|
}).filter((entry) => entry.id !== "") : [];
|
|
770
953
|
return {
|
|
@@ -773,10 +956,11 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
773
956
|
llmAvailable: p.llmAvailable === true,
|
|
774
957
|
registered: p.registered === true,
|
|
775
958
|
error: typeof p.error === "string" ? p.error : null,
|
|
776
|
-
modelCount: typeof p.modelCount === "number" ? p.modelCount : roster.length,
|
|
777
|
-
enabledCount: typeof p.enabledCount === "number" ? p.enabledCount : 0,
|
|
959
|
+
modelCount: typeof p.modelCount === "number" && Number.isFinite(p.modelCount) ? p.modelCount : roster.length,
|
|
960
|
+
enabledCount: typeof p.enabledCount === "number" && Number.isFinite(p.enabledCount) ? p.enabledCount : 0,
|
|
778
961
|
allowed: p.allowed === "none" ? "none" : p.allowed === "list" ? "list" : "all",
|
|
779
962
|
enabledIds: ids,
|
|
963
|
+
contextWindow: typeof p.contextWindow === "number" && Number.isFinite(p.contextWindow) && p.contextWindow > 0 ? p.contextWindow : null,
|
|
780
964
|
roster
|
|
781
965
|
};
|
|
782
966
|
}
|
|
@@ -789,6 +973,33 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
789
973
|
return `HTTP ${status}`;
|
|
790
974
|
}
|
|
791
975
|
/**
|
|
976
|
+
* 把 wire/路由返回的出图块归一成 `DrawStatus`;不合法一律降级。
|
|
977
|
+
*
|
|
978
|
+
* 与 `providerOf` 同一套防御性归一(旧 Host 可能缺字段)。`absentReason` 只认三个
|
|
979
|
+
* 已知码——上游若新增第四个,归一到 `null` 而不是把它透传成渲染不出的值;面板的
|
|
980
|
+
* 「缺席」措辞有兜底键,但**状态本身**宁可显示「未知」也不假装认识它。
|
|
981
|
+
* @param {unknown} raw - 原始块。
|
|
982
|
+
* @returns {DrawStatus} 归一后的状态。
|
|
983
|
+
*/
|
|
984
|
+
function drawOf(raw) {
|
|
985
|
+
if (raw === null || typeof raw !== "object") return DEGRADED_DRAW;
|
|
986
|
+
const d = raw;
|
|
987
|
+
const candidates = Array.isArray(d.candidateIds) ? d.candidateIds.map((id) => String(id ?? "")).filter((id) => id !== "") : [];
|
|
988
|
+
const reason = d.absentReason;
|
|
989
|
+
const absentReason = reason === "no-tools-service" || reason === "peer-load-failed" || reason === "registry-refused" ? reason : null;
|
|
990
|
+
return {
|
|
991
|
+
enabled: d.enabled === true,
|
|
992
|
+
source: d.source === "panel" ? "panel" : "config",
|
|
993
|
+
candidateIds: candidates,
|
|
994
|
+
candidateCount: typeof d.candidateCount === "number" && Number.isFinite(d.candidateCount) ? d.candidateCount : candidates.length,
|
|
995
|
+
preferredModel: typeof d.preferredModel === "string" && d.preferredModel !== "" ? d.preferredModel : null,
|
|
996
|
+
preferredSource: d.preferredSource === "panel" ? "panel" : "config",
|
|
997
|
+
absentReason,
|
|
998
|
+
mountPending: d.mountPending === true,
|
|
999
|
+
toolName: typeof d.toolName === "string" && d.toolName !== "" ? d.toolName : "modelscope_draw_image"
|
|
1000
|
+
};
|
|
1001
|
+
}
|
|
1002
|
+
/**
|
|
792
1003
|
* 面板决策:这张快照意味着什么。纯函数,Node 套件驱动同一个函数。
|
|
793
1004
|
*
|
|
794
1005
|
* `missingKeys` 是 `interpretSnapshot` 查出的缺失顶层键(非空 = 双端契约漂移)。
|
|
@@ -805,7 +1016,7 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
805
1016
|
const guidanceKey = failure === null ? null : GUIDANCE_BY_CODE[failure.code] ?? null;
|
|
806
1017
|
const guidance = guidanceKey === null ? null : guidanceKey === "panel.configError" ? format(tt(guidanceKey), { error: failure?.message }) : tt(guidanceKey);
|
|
807
1018
|
const shapeWarnings = Array.isArray(data?.shapeWarnings) ? [...data.shapeWarnings] : [];
|
|
808
|
-
if (missingKeys.length > 0) shapeWarnings.push(
|
|
1019
|
+
if (missingKeys.length > 0) shapeWarnings.push(format(tt("shape.missingKeys"), { keys: missingKeys.join(", ") }));
|
|
809
1020
|
return {
|
|
810
1021
|
failure,
|
|
811
1022
|
needsSetup,
|
|
@@ -814,10 +1025,11 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
814
1025
|
shapeWarnings
|
|
815
1026
|
};
|
|
816
1027
|
}
|
|
817
|
-
var DEGRADED_PROVIDER, GUIDANCE_BY_CODE, FORM_EXCLUDED_CODES;
|
|
1028
|
+
var DEGRADED_PROVIDER, DEGRADED_DRAW, GUIDANCE_BY_CODE, FORM_EXCLUDED_CODES;
|
|
818
1029
|
var init_snapshot = __esmMin((() => {
|
|
819
1030
|
init_format();
|
|
820
1031
|
init_wire();
|
|
1032
|
+
init_codes();
|
|
821
1033
|
DEGRADED_PROVIDER = Object.freeze({
|
|
822
1034
|
enabled: false,
|
|
823
1035
|
source: "config",
|
|
@@ -828,8 +1040,20 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
828
1040
|
enabledCount: 0,
|
|
829
1041
|
allowed: "all",
|
|
830
1042
|
enabledIds: [],
|
|
1043
|
+
contextWindow: null,
|
|
831
1044
|
roster: []
|
|
832
1045
|
});
|
|
1046
|
+
DEGRADED_DRAW = Object.freeze({
|
|
1047
|
+
enabled: false,
|
|
1048
|
+
source: "config",
|
|
1049
|
+
candidateIds: [],
|
|
1050
|
+
candidateCount: 0,
|
|
1051
|
+
preferredModel: null,
|
|
1052
|
+
preferredSource: "config",
|
|
1053
|
+
absentReason: null,
|
|
1054
|
+
mountPending: false,
|
|
1055
|
+
toolName: "modelscope_draw_image"
|
|
1056
|
+
});
|
|
833
1057
|
GUIDANCE_BY_CODE = Object.freeze({
|
|
834
1058
|
auth_error: "panel.authError",
|
|
835
1059
|
config_error: "panel.configError",
|
|
@@ -838,7 +1062,8 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
838
1062
|
upstream_error: "panel.upstream",
|
|
839
1063
|
rate_limited: "panel.upstream",
|
|
840
1064
|
quota_exceeded: "panel.upstream",
|
|
841
|
-
internal_error: "panel.internalError"
|
|
1065
|
+
internal_error: "panel.internalError",
|
|
1066
|
+
payload_error: "panel.payloadError"
|
|
842
1067
|
});
|
|
843
1068
|
FORM_EXCLUDED_CODES = Object.freeze(/* @__PURE__ */ new Set([
|
|
844
1069
|
"config_error",
|
|
@@ -847,7 +1072,8 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
847
1072
|
"upstream_error",
|
|
848
1073
|
"rate_limited",
|
|
849
1074
|
"quota_exceeded",
|
|
850
|
-
"internal_error"
|
|
1075
|
+
"internal_error",
|
|
1076
|
+
"payload_error"
|
|
851
1077
|
]));
|
|
852
1078
|
}));
|
|
853
1079
|
|
|
@@ -899,8 +1125,8 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
899
1125
|
inset: 0,
|
|
900
1126
|
borderRadius: 999,
|
|
901
1127
|
pointerEvents: "none",
|
|
902
|
-
border: `1px solid ${on ?
|
|
903
|
-
background: on ?
|
|
1128
|
+
border: `1px solid ${on ? BRAND : "var(--dsw-alias-border-l2, #36373b)"}`,
|
|
1129
|
+
background: on ? BRAND : "var(--dsw-alias-bg-layer-2, #2a2b31)",
|
|
904
1130
|
transition: "background .15s, border-color .15s"
|
|
905
1131
|
}
|
|
906
1132
|
}, h("span", { style: {
|
|
@@ -921,12 +1147,80 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
921
1147
|
var TRACK_W, TRACK_H, THUMB, TRAVEL;
|
|
922
1148
|
var init_toggle_switch = __esmMin((() => {
|
|
923
1149
|
init_runtime();
|
|
1150
|
+
init_styles();
|
|
924
1151
|
TRACK_W = 30;
|
|
925
1152
|
TRACK_H = 17;
|
|
926
1153
|
THUMB = 12;
|
|
927
1154
|
TRAVEL = 13;
|
|
928
1155
|
}));
|
|
929
1156
|
|
|
1157
|
+
//#endregion
|
|
1158
|
+
//#region src/shared/allow-list.ts
|
|
1159
|
+
/**
|
|
1160
|
+
* 把线编码的 `enabledIds` 解析成联合——**全插件只有这一处解释**。
|
|
1161
|
+
*
|
|
1162
|
+
* 判据(与旧 `resolveAllowedList` 的承诺一致,且现在 `filterByEnabled` 等
|
|
1163
|
+
* 消费者也真的按它做):
|
|
1164
|
+
* 1. 非数组或空数组 → `all`(新装没做过选择,仍提供全部);
|
|
1165
|
+
* 2. 含哨兵(哪怕还混着别的 id)→ `none`(哨兵是独占态,优先于任何真实 id);
|
|
1166
|
+
* 3. 其余非空清单 → `list`(严格白名单;条目滤掉非字符串与空串、
|
|
1167
|
+
* 去重保序——落盘前 `provider-store` 已归一遍,这里是解释不是归一)。
|
|
1168
|
+
*
|
|
1169
|
+
* 注意 `[""]` 这类「滤完什么都不剩」的脏清单归 `list` 且 `ids: []`:过滤
|
|
1170
|
+
* 结果为空,与旧行为一致(它匹配不到任何真实 id)。
|
|
1171
|
+
* @param {unknown} enabledIds - 线编码的允许清单。
|
|
1172
|
+
* @returns {AllowedList} 联合。
|
|
1173
|
+
*/
|
|
1174
|
+
function parseAllowedList(enabledIds) {
|
|
1175
|
+
if (!Array.isArray(enabledIds) || enabledIds.length === 0) return { mode: "all" };
|
|
1176
|
+
if (enabledIds.includes("__hide_all__")) return { mode: "none" };
|
|
1177
|
+
const ids = [];
|
|
1178
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1179
|
+
for (const entry of enabledIds) {
|
|
1180
|
+
const id = typeof entry === "string" ? entry : "";
|
|
1181
|
+
if (id === "" || seen.has(id)) continue;
|
|
1182
|
+
seen.add(id);
|
|
1183
|
+
ids.push(id);
|
|
1184
|
+
}
|
|
1185
|
+
return {
|
|
1186
|
+
mode: "list",
|
|
1187
|
+
ids
|
|
1188
|
+
};
|
|
1189
|
+
}
|
|
1190
|
+
/**
|
|
1191
|
+
* 面板草稿 → 落盘清单的归一:哨兵独占态。
|
|
1192
|
+
*
|
|
1193
|
+
* 草稿含哨兵(「全部隐藏」)时落盘值只保留哨兵——不能让它与真实 id 混排:
|
|
1194
|
+
* Host 把混排判 `none`,而面板会把混排里的真实 id 画成勾中,「已启用 N」
|
|
1195
|
+
* 与实际提供 0 个撒谎(踩过,见 cards.ts 的注释)。其余情况剥掉哨兵、
|
|
1196
|
+
* 滤掉空串后原样落盘。
|
|
1197
|
+
* @param {readonly unknown[]} draft - 面板勾选的草稿。
|
|
1198
|
+
* @returns {string[]} 落盘的 `enabledIds`。
|
|
1199
|
+
*/
|
|
1200
|
+
function normalizeRosterSave(draft) {
|
|
1201
|
+
return rosterSaveOf(parseAllowedList(draft));
|
|
1202
|
+
}
|
|
1203
|
+
/**
|
|
1204
|
+
* 联合 → 落盘清单({@link parseAllowedList} 的逆运算):`none` 序列化成
|
|
1205
|
+
* 哨兵清单,`all` 序列化成空清单(「不过滤」的拼写),`list` 原样带出 id。
|
|
1206
|
+
* @param {AllowedList} list - 联合。
|
|
1207
|
+
* @returns {string[]} 线编码的 `enabledIds`。
|
|
1208
|
+
*/
|
|
1209
|
+
function rosterSaveOf(list) {
|
|
1210
|
+
switch (list.mode) {
|
|
1211
|
+
case "all": return [];
|
|
1212
|
+
case "none": return [HIDE_ALL_MODELS];
|
|
1213
|
+
case "list": return [...list.ids];
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
/** 「全部隐藏」的落盘清单({@link rosterSaveOf} 的 `none` 特例;每次新数组)。 */
|
|
1217
|
+
function hideAllRoster() {
|
|
1218
|
+
return rosterSaveOf({ mode: "none" });
|
|
1219
|
+
}
|
|
1220
|
+
var init_allow_list = __esmMin((() => {
|
|
1221
|
+
init_wire();
|
|
1222
|
+
}));
|
|
1223
|
+
|
|
930
1224
|
//#endregion
|
|
931
1225
|
//#region src/client/cards.ts
|
|
932
1226
|
/** 可折叠 section 卡:全宽头部按钮 + 旋转 chevron;open/onToggle 由 props 进。 */
|
|
@@ -982,12 +1276,17 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
982
1276
|
*/
|
|
983
1277
|
function LocalDailyCard({ snapshot, tt }) {
|
|
984
1278
|
const quota = snapshot.quota;
|
|
985
|
-
const daily = quota?.daily ?? { usedLocal:
|
|
1279
|
+
const daily = quota?.daily ?? { usedLocal: null };
|
|
986
1280
|
const perModel = Array.isArray(quota?.perModel) ? quota.perModel : [];
|
|
1281
|
+
const unknownUsed = !(typeof daily.usedLocal === "number" && Number.isFinite(daily.usedLocal));
|
|
1282
|
+
const usedText = unknownUsed ? "—" : count(daily.usedLocal);
|
|
987
1283
|
return h("div", { style: S.card }, h("div", { style: S.poolName }, format(tt("quota.headline"), {
|
|
988
|
-
calls:
|
|
1284
|
+
calls: usedText,
|
|
989
1285
|
models: count(perModel.length)
|
|
990
|
-
})), h(
|
|
1286
|
+
})), unknownUsed ? h("div", { style: {
|
|
1287
|
+
...S.formNote,
|
|
1288
|
+
color: "var(--dsw-alias-state-warn-primary)"
|
|
1289
|
+
} }, tt("quota.unavailable")) : null, h(ModelUsageTable, {
|
|
991
1290
|
rows: perModel,
|
|
992
1291
|
tt
|
|
993
1292
|
}), h("div", { style: S.trendLegend }, tt("quota.note")));
|
|
@@ -1019,21 +1318,25 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
1019
1318
|
/** 近 N 天本地调用趋势(天桶;缺桶 = 该日 0 次,真实零)。 */
|
|
1020
1319
|
function TrendBars({ buckets, tt }) {
|
|
1021
1320
|
if (!Array.isArray(buckets) || buckets.length === 0) return h("div", { style: S.trendLegend }, tt("trend.none"));
|
|
1022
|
-
const
|
|
1023
|
-
|
|
1024
|
-
|
|
1321
|
+
const rows = buckets.map((bucket) => ({
|
|
1322
|
+
dateKey: bucket.dateKey,
|
|
1323
|
+
calls: Number.isFinite(bucket.calls) ? Math.max(0, bucket.calls) : 0
|
|
1324
|
+
}));
|
|
1325
|
+
const max = Math.max(0, ...rows.map((row) => row.calls));
|
|
1326
|
+
return h("div", null, rows.map((row) => h("div", {
|
|
1327
|
+
key: row.dateKey,
|
|
1025
1328
|
style: S.trendRowHead
|
|
1026
|
-
}, h("span", { style: S.trendModel },
|
|
1329
|
+
}, h("span", { style: S.trendModel }, row.dateKey), h("div", {
|
|
1027
1330
|
style: {
|
|
1028
1331
|
...S.trendBar,
|
|
1029
1332
|
flex: "1",
|
|
1030
1333
|
marginLeft: 10
|
|
1031
1334
|
},
|
|
1032
1335
|
"aria-hidden": "true"
|
|
1033
|
-
}, max > 0 &&
|
|
1336
|
+
}, max > 0 && row.calls > 0 ? h("div", { style: {
|
|
1034
1337
|
...S.barFill,
|
|
1035
|
-
width: `${
|
|
1036
|
-
} }) : null), h("span", { style: S.quotaUsed }, String(
|
|
1338
|
+
width: `${row.calls / max * 100}%`
|
|
1339
|
+
} }) : null), h("span", { style: S.quotaUsed }, String(row.calls)))), h("div", { style: S.trendLegend }, tt("trend.legend")));
|
|
1037
1340
|
}
|
|
1038
1341
|
/** 事件流(429/错误),Newest-first。 */
|
|
1039
1342
|
function EventsList({ events, tt }) {
|
|
@@ -1041,7 +1344,7 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
1041
1344
|
return h("div", null, events.map((event, index) => h("div", {
|
|
1042
1345
|
key: `${event.at}-${index}`,
|
|
1043
1346
|
style: S.trendRowHead
|
|
1044
|
-
}, h("span", { style: S.quotaUsed }, isoTime(event.at)), h("span", { style: S.modelTag }, tt(EVENT_KIND_KEY[event.kind])), h("span", {
|
|
1347
|
+
}, h("span", { style: S.quotaUsed }, isoTime(event.at)), h("span", { style: S.modelTag }, tt(EVENT_KIND_KEY[event.kind] ?? "events.unknown")), h("span", {
|
|
1045
1348
|
style: {
|
|
1046
1349
|
...S.trendModel,
|
|
1047
1350
|
flex: "1"
|
|
@@ -1054,8 +1357,11 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
1054
1357
|
const [value, setValue] = useState("");
|
|
1055
1358
|
const source = token?.source ?? "none";
|
|
1056
1359
|
const sourceKey = source === "credentials" ? "source.credentials" : source === "env" ? "source.env" : source === "memory" ? "source.memory" : "source.none";
|
|
1057
|
-
const validKey = token?.valid === true ? "validity.yes" : "validity.no";
|
|
1058
|
-
return h("div", null, h("div", { style:
|
|
1360
|
+
const validKey = token?.valid === true ? "validity.yes" : token?.valid === false ? "validity.bad" : "validity.no";
|
|
1361
|
+
return h("div", null, token?.readError ? h("div", { style: {
|
|
1362
|
+
...S.formNote,
|
|
1363
|
+
color: "var(--dsw-alias-state-warn-primary)"
|
|
1364
|
+
} }, format(tt("token.readError"), { error: token.readError })) : h("div", { style: S.quotaUsed }, format(tt("token.status"), {
|
|
1059
1365
|
present: token?.present ? tt("present.yes") : tt("present.no"),
|
|
1060
1366
|
source: tt(sourceKey),
|
|
1061
1367
|
valid: tt(validKey)
|
|
@@ -1063,6 +1369,7 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
1063
1369
|
style: S.input,
|
|
1064
1370
|
type: "password",
|
|
1065
1371
|
placeholder: tt("token.placeholder"),
|
|
1372
|
+
"aria-label": tt("token.ariaLabel"),
|
|
1066
1373
|
value,
|
|
1067
1374
|
onChange: (event) => setValue(String(event.target.value ?? ""))
|
|
1068
1375
|
}), h("button", {
|
|
@@ -1109,25 +1416,34 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
1109
1416
|
*
|
|
1110
1417
|
* 允许清单的语义(路由 §9 钉死):空清单 = 不过滤 = 全部提供,所以「全部」
|
|
1111
1418
|
* 发 `[]`;「全部隐藏」发哨兵 `HIDE_ALL_MODELS`(「什么都不提供」)。哨兵
|
|
1112
|
-
*
|
|
1113
|
-
*
|
|
1419
|
+
* 是**独占态**:draft 含哨兵时整个清单就是「全部隐藏」——勾一个真实模型会
|
|
1420
|
+
* 退出哨兵态(剔除哨兵),Host 落盘值带哨兵时只保留哨兵。**不能**让哨兵与
|
|
1421
|
+
* 真实 id 混排:Host 的 resolveAllowedList 判「含哨兵即 none(全隐藏)」,而
|
|
1422
|
+
* 面板若把混排清单里的真实 id 画成勾中、计数为 N,UI 就与落盘撒谎了。
|
|
1423
|
+
* 曾踩过:点「全部隐藏」后复选框仍可勾,勾出的混排清单保存后「已启用 1/N」
|
|
1424
|
+
* 而实际提供 0 个。
|
|
1114
1425
|
*
|
|
1115
1426
|
* 额度耗尽的模型保留在清单里(灰显、勾选框禁用),这是故意的:roster 是目录
|
|
1116
1427
|
* 事实,picker 那边由 Host 自己丢掉它们,面板不替它删。
|
|
1117
1428
|
*/
|
|
1118
|
-
function ProviderCard({ provider, busy, error, onToggle, onSaveList, onReset, tokenPresent, tt }) {
|
|
1119
|
-
const status = providerOf(provider);
|
|
1429
|
+
function ProviderCard({ provider, busy, error, onToggle, onSaveList, onSaveContextWindow, onReset, tokenPresent, tt }) {
|
|
1430
|
+
const status = providerOf$1(provider);
|
|
1120
1431
|
const enabled = status.enabled === true;
|
|
1121
1432
|
const hostIds = status.enabledIds;
|
|
1122
1433
|
const hostKey = useMemo(() => JSON.stringify(hostIds), [hostIds]);
|
|
1123
1434
|
const [draft, setDraft] = useState(() => hostIds.slice());
|
|
1435
|
+
const hideAllMode = parseAllowedList(draft).mode === "none";
|
|
1124
1436
|
useEffect(() => {
|
|
1125
|
-
setDraft(hostIds);
|
|
1437
|
+
setDraft(normalizeRosterSave(hostIds));
|
|
1126
1438
|
}, [hostKey]);
|
|
1127
1439
|
const roster = status.roster;
|
|
1128
1440
|
const rosterIds = roster.map((row) => row.id);
|
|
1129
|
-
const toggleOne = (id) => setDraft((current) =>
|
|
1130
|
-
|
|
1441
|
+
const toggleOne = (id) => setDraft((current) => {
|
|
1442
|
+
const ids = parseAllowedList(current);
|
|
1443
|
+
const kept = ids.mode === "list" ? [...ids.ids] : [];
|
|
1444
|
+
return kept.includes(id) ? kept.filter((x) => x !== id) : [...kept, id];
|
|
1445
|
+
});
|
|
1446
|
+
const ticked = hideAllMode ? 0 : roster.filter((row) => draft.includes(row.id)).length;
|
|
1131
1447
|
const degraded = status.error === "unavailable";
|
|
1132
1448
|
let statusNode;
|
|
1133
1449
|
if (!degraded && typeof status.error === "string" && status.error !== "") statusNode = h("div", {
|
|
@@ -1157,6 +1473,61 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
1157
1473
|
color: "var(--dsw-alias-state-warn-primary)"
|
|
1158
1474
|
} }, tt("provider.notConfigured")) : null;
|
|
1159
1475
|
const sourceKey = status.source === "panel" ? "provider.source.panel" : "provider.source.config";
|
|
1476
|
+
const CONTEXT_TIERS = [
|
|
1477
|
+
{
|
|
1478
|
+
value: 131072,
|
|
1479
|
+
label: "128K"
|
|
1480
|
+
},
|
|
1481
|
+
{
|
|
1482
|
+
value: 262144,
|
|
1483
|
+
label: "256K"
|
|
1484
|
+
},
|
|
1485
|
+
{
|
|
1486
|
+
value: 524288,
|
|
1487
|
+
label: "512K"
|
|
1488
|
+
},
|
|
1489
|
+
{
|
|
1490
|
+
value: 1048576,
|
|
1491
|
+
label: "1M"
|
|
1492
|
+
}
|
|
1493
|
+
];
|
|
1494
|
+
const contextNode = onSaveContextWindow === void 0 ? null : h("div", { style: {
|
|
1495
|
+
display: "flex",
|
|
1496
|
+
gap: 8,
|
|
1497
|
+
alignItems: "center",
|
|
1498
|
+
flexWrap: "wrap",
|
|
1499
|
+
margin: "0 0 8px"
|
|
1500
|
+
} }, h("label", {
|
|
1501
|
+
style: {
|
|
1502
|
+
fontSize: 12,
|
|
1503
|
+
color: "var(--dsw-alias-label-secondary)"
|
|
1504
|
+
},
|
|
1505
|
+
htmlFor: "provider-context-window"
|
|
1506
|
+
}, tt("provider.contextWindow")), h("select", {
|
|
1507
|
+
id: "provider-context-window",
|
|
1508
|
+
style: {
|
|
1509
|
+
...S.rosterBulk,
|
|
1510
|
+
width: "auto"
|
|
1511
|
+
},
|
|
1512
|
+
disabled: busy,
|
|
1513
|
+
value: status.contextWindow === null ? "" : String(status.contextWindow),
|
|
1514
|
+
onChange: (event) => {
|
|
1515
|
+
const value = String(event.target?.value ?? "");
|
|
1516
|
+
onSaveContextWindow(value === "" ? null : Number(value));
|
|
1517
|
+
}
|
|
1518
|
+
}, h("option", {
|
|
1519
|
+
value: "",
|
|
1520
|
+
key: "default"
|
|
1521
|
+
}, tt("provider.contextDefault")), CONTEXT_TIERS.map((tier) => h("option", {
|
|
1522
|
+
value: String(tier.value),
|
|
1523
|
+
key: String(tier.value)
|
|
1524
|
+
}, tier.label))), h("span", {
|
|
1525
|
+
style: {
|
|
1526
|
+
...S.muted,
|
|
1527
|
+
fontSize: 11
|
|
1528
|
+
},
|
|
1529
|
+
title: tt("provider.contextHint")
|
|
1530
|
+
}, tt("provider.contextHint")));
|
|
1160
1531
|
const switchRow = h("div", { style: {
|
|
1161
1532
|
display: "flex",
|
|
1162
1533
|
gap: 10,
|
|
@@ -1168,6 +1539,7 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
1168
1539
|
onChange: () => onToggle(!enabled),
|
|
1169
1540
|
busy,
|
|
1170
1541
|
label: tt(enabled ? "provider.on" : "provider.off"),
|
|
1542
|
+
busyLabel: tt("provider.busy"),
|
|
1171
1543
|
title: tt("provider.enable")
|
|
1172
1544
|
}), h("span", { style: {
|
|
1173
1545
|
fontSize: 11,
|
|
@@ -1199,8 +1571,9 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
1199
1571
|
style: S.rosterBulk,
|
|
1200
1572
|
disabled: busy,
|
|
1201
1573
|
onClick: () => {
|
|
1202
|
-
|
|
1203
|
-
|
|
1574
|
+
const none = hideAllRoster();
|
|
1575
|
+
setDraft(none);
|
|
1576
|
+
onSaveList(none);
|
|
1204
1577
|
}
|
|
1205
1578
|
}, tt("provider.hideAll")), h("button", {
|
|
1206
1579
|
type: "button",
|
|
@@ -1228,7 +1601,7 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
1228
1601
|
cursor: busy || unusable ? "default" : "pointer"
|
|
1229
1602
|
} }, h("input", {
|
|
1230
1603
|
type: "checkbox",
|
|
1231
|
-
checked: draft.includes(id),
|
|
1604
|
+
checked: !hideAllMode && draft.includes(id),
|
|
1232
1605
|
disabled: busy || unusable,
|
|
1233
1606
|
onChange: () => toggleOne(id),
|
|
1234
1607
|
style: S.modelCheck,
|
|
@@ -1236,7 +1609,13 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
1236
1609
|
}), h("span", {
|
|
1237
1610
|
style: S.modelName,
|
|
1238
1611
|
title: id
|
|
1239
|
-
}, row.name)), row.vision === true ? h("span", { style: S.modelBadge }, tt("provider.vision")) : null,
|
|
1612
|
+
}, row.name)), row.vision === true ? h("span", { style: S.modelBadge }, tt("provider.vision")) : null, typeof row.contextWindow === "number" ? h("span", {
|
|
1613
|
+
style: row.contextSource === "curated" ? {
|
|
1614
|
+
...S.modelBadge,
|
|
1615
|
+
color: "var(--dsw-alias-label-secondary)"
|
|
1616
|
+
} : S.modelBadge,
|
|
1617
|
+
title: row.contextSource === "curated" ? tt("provider.contextCuratedTitle") : tt("provider.contextWindow")
|
|
1618
|
+
}, row.contextSource === "curated" ? formatWindow(row.contextWindow) + " *" : formatWindow(row.contextWindow)) : null, FEATURED_OWNERS.includes(catalogOwner(row.id)) ? h("span", {
|
|
1240
1619
|
style: S.modelBadgeFeatured,
|
|
1241
1620
|
title: tt("models.featuredTitle")
|
|
1242
1621
|
}, tt("provider.featured")) : null, row.quotaExhausted === true ? h("span", { style: {
|
|
@@ -1259,7 +1638,7 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
1259
1638
|
disabled: busy,
|
|
1260
1639
|
onClick: onReset
|
|
1261
1640
|
}, tt("provider.reset"))));
|
|
1262
|
-
return h("div", null, switchRow, statusNode, tokenHint, h("div", { style: {
|
|
1641
|
+
return h("div", null, switchRow, statusNode, tokenHint, contextNode, h("div", { style: {
|
|
1263
1642
|
...S.sectionTitle,
|
|
1264
1643
|
margin: "18px 0 8px"
|
|
1265
1644
|
} }, tt("provider.models")), rosterBlock, error !== null ? h("div", {
|
|
@@ -1267,7 +1646,114 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
1267
1646
|
role: "alert"
|
|
1268
1647
|
}, error) : null);
|
|
1269
1648
|
}
|
|
1270
|
-
|
|
1649
|
+
/**
|
|
1650
|
+
* 出图工具卡:开关、候选模型、挂载状态。
|
|
1651
|
+
*
|
|
1652
|
+
* **与 ProviderCard 的关键差异,也是这张卡存在的主要理由**:provider 开关翻转会
|
|
1653
|
+
* 立即重注册,而出图工具的开关翻转**要等下次 Host 启动**——peer 的 tools registry
|
|
1654
|
+
* 没有 unregister。所以这张卡必须把这件事说出来(`draw.restartHint`),否则用户
|
|
1655
|
+
* 翻开关后看不到任何变化,会以为坏了。
|
|
1656
|
+
*
|
|
1657
|
+
* 缺席原因是**分开渲染**的:`no-tools-service` 是「这个 Host 没提供该服务」(正常),
|
|
1658
|
+
* 另两种是 Host 缺陷。三种都给出各自的一句话,因为「开关开着但工具不在」如果没有
|
|
1659
|
+
* 解释,用户没有任何办法自己诊断。
|
|
1660
|
+
* @param {object} props - 渲染参数。
|
|
1661
|
+
* @returns {unknown} 卡片节点。
|
|
1662
|
+
*/
|
|
1663
|
+
function DrawCard({ draw, busy, error, onToggle, onReset, tokenPresent, tt }) {
|
|
1664
|
+
const status = drawOf(draw);
|
|
1665
|
+
const enabled = status.enabled === true;
|
|
1666
|
+
const candidates = status.candidateIds;
|
|
1667
|
+
const sourceKey = status.source === "panel" ? "draw.source.panel" : "draw.source.config";
|
|
1668
|
+
const switchRow = h("div", { style: {
|
|
1669
|
+
display: "flex",
|
|
1670
|
+
gap: 10,
|
|
1671
|
+
alignItems: "center",
|
|
1672
|
+
flexWrap: "wrap",
|
|
1673
|
+
margin: "0 0 8px"
|
|
1674
|
+
} }, h(ToggleSwitch, {
|
|
1675
|
+
checked: enabled,
|
|
1676
|
+
onChange: () => onToggle(!enabled),
|
|
1677
|
+
busy,
|
|
1678
|
+
label: tt(enabled ? "draw.on" : "draw.off"),
|
|
1679
|
+
busyLabel: tt("draw.busy"),
|
|
1680
|
+
title: tt("draw.enable")
|
|
1681
|
+
}), h("span", { style: {
|
|
1682
|
+
fontSize: 11,
|
|
1683
|
+
color: "var(--dsw-alias-label-secondary)"
|
|
1684
|
+
} }, tt(sourceKey)));
|
|
1685
|
+
let statusNode;
|
|
1686
|
+
if (status.absentReason !== null) {
|
|
1687
|
+
const key = DRAW_ABSENT_KEY[status.absentReason] ?? "draw.absent.unknown";
|
|
1688
|
+
const severe = status.absentReason !== "no-tools-service";
|
|
1689
|
+
statusNode = h("div", {
|
|
1690
|
+
style: severe ? {
|
|
1691
|
+
...S.formNote,
|
|
1692
|
+
color: "var(--dsw-alias-state-warn-primary)"
|
|
1693
|
+
} : S.formNote,
|
|
1694
|
+
role: "status"
|
|
1695
|
+
}, tt(key));
|
|
1696
|
+
} else if (enabled) statusNode = h("div", {
|
|
1697
|
+
style: {
|
|
1698
|
+
fontSize: 12,
|
|
1699
|
+
color: "var(--dsw-alias-label-secondary)"
|
|
1700
|
+
},
|
|
1701
|
+
role: "status"
|
|
1702
|
+
}, format(tt("draw.mounted"), { tool: status.toolName }));
|
|
1703
|
+
else statusNode = h("div", {
|
|
1704
|
+
style: {
|
|
1705
|
+
...S.muted,
|
|
1706
|
+
fontSize: 12
|
|
1707
|
+
},
|
|
1708
|
+
role: "status"
|
|
1709
|
+
}, tt("draw.notMounted"));
|
|
1710
|
+
const restartHint = enabled ? h("div", { style: {
|
|
1711
|
+
...S.formNote,
|
|
1712
|
+
color: "var(--dsw-alias-state-warn-primary)"
|
|
1713
|
+
} }, tt("draw.restartHint")) : null;
|
|
1714
|
+
const tokenHint = enabled && tokenPresent === false ? h("div", { style: {
|
|
1715
|
+
...S.formNote,
|
|
1716
|
+
color: "var(--dsw-alias-state-warn-primary)"
|
|
1717
|
+
} }, tt("draw.needsKey")) : null;
|
|
1718
|
+
const candidateBlock = candidates.length === 0 ? h("div", { style: {
|
|
1719
|
+
...S.muted,
|
|
1720
|
+
fontSize: 12,
|
|
1721
|
+
marginTop: 6
|
|
1722
|
+
} }, tt("draw.noCandidates")) : h("div", { style: {
|
|
1723
|
+
display: "flex",
|
|
1724
|
+
flexDirection: "column",
|
|
1725
|
+
gap: 4
|
|
1726
|
+
} }, candidates.map((id) => h("div", {
|
|
1727
|
+
key: id,
|
|
1728
|
+
style: S.modelRowHead
|
|
1729
|
+
}, h("code", {
|
|
1730
|
+
style: S.modelName,
|
|
1731
|
+
title: id
|
|
1732
|
+
}, id), h("span", { style: S.modelBadge }, tt("draw.badge")), h("a", {
|
|
1733
|
+
style: {
|
|
1734
|
+
...S.formNote,
|
|
1735
|
+
margin: 0,
|
|
1736
|
+
marginLeft: "auto"
|
|
1737
|
+
},
|
|
1738
|
+
href: modelscopeModelUrl(id),
|
|
1739
|
+
target: "_blank",
|
|
1740
|
+
rel: "noreferrer",
|
|
1741
|
+
title: tt("models.viewOnSiteTitle")
|
|
1742
|
+
}, tt("models.viewOnSite")))));
|
|
1743
|
+
return h("div", null, switchRow, statusNode, restartHint, tokenHint, h("div", { style: {
|
|
1744
|
+
...S.sectionTitle,
|
|
1745
|
+
margin: "18px 0 8px"
|
|
1746
|
+
} }, tt("draw.models")), candidateBlock, h("div", { style: S.rosterFoot }, h("button", {
|
|
1747
|
+
type: "button",
|
|
1748
|
+
style: S.button,
|
|
1749
|
+
disabled: busy,
|
|
1750
|
+
onClick: onReset
|
|
1751
|
+
}, tt("draw.reset"))), error !== null ? h("div", {
|
|
1752
|
+
style: S.formError,
|
|
1753
|
+
role: "alert"
|
|
1754
|
+
}, error) : null);
|
|
1755
|
+
}
|
|
1756
|
+
var EVENT_KIND_KEY, DRAW_ABSENT_KEY;
|
|
1271
1757
|
var init_cards = __esmMin((() => {
|
|
1272
1758
|
init_format();
|
|
1273
1759
|
init_runtime();
|
|
@@ -1275,18 +1761,44 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
1275
1761
|
init_const();
|
|
1276
1762
|
init_snapshot();
|
|
1277
1763
|
init_toggle_switch();
|
|
1764
|
+
init_allow_list();
|
|
1278
1765
|
EVENT_KIND_KEY = {
|
|
1279
1766
|
quota: "events.quota",
|
|
1280
1767
|
rate_limit: "events.rate_limit",
|
|
1281
1768
|
error: "events.error"
|
|
1282
1769
|
};
|
|
1770
|
+
DRAW_ABSENT_KEY = {
|
|
1771
|
+
"no-tools-service": "draw.absent.noTools",
|
|
1772
|
+
"peer-load-failed": "draw.absent.peerLoad",
|
|
1773
|
+
"registry-refused": "draw.absent.registry"
|
|
1774
|
+
};
|
|
1283
1775
|
}));
|
|
1284
1776
|
|
|
1285
1777
|
//#endregion
|
|
1286
1778
|
//#region src/client/http.ts
|
|
1287
|
-
/**
|
|
1779
|
+
/** 一次带超时的 fetch;内部 controller,调用方无需传 signal。 */
|
|
1780
|
+
async function fetchWithTimeout(url, init) {
|
|
1781
|
+
const controller = typeof AbortController === "function" ? new AbortController() : null;
|
|
1782
|
+
const timer = controller === null ? null : setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
|
|
1783
|
+
try {
|
|
1784
|
+
return await fetch(url, {
|
|
1785
|
+
...init,
|
|
1786
|
+
...controller === null ? {} : { signal: controller.signal }
|
|
1787
|
+
});
|
|
1788
|
+
} finally {
|
|
1789
|
+
if (timer !== null) clearTimeout(timer);
|
|
1790
|
+
}
|
|
1791
|
+
}
|
|
1792
|
+
/**
|
|
1793
|
+
* POST 并解析 body;非 JSON 响应返回 null。
|
|
1794
|
+
*
|
|
1795
|
+
* 这里**故意不**检查 `response.ok`(与 {@link getJson} 不同):host 的写操作错误
|
|
1796
|
+
* 响应体是 `{ok:false, code, error}`,调用方靠 `body.error` 给用户看具体原因
|
|
1797
|
+
* (令牌格式错、额度耗尽、上游超时)。丢掉它就只能显示一句通用的「操作失败」,
|
|
1798
|
+
* 用户无从下手。getJson 可以直接返回 null——读操作没有错误详情可展示。
|
|
1799
|
+
*/
|
|
1288
1800
|
async function postJson(path, payload) {
|
|
1289
|
-
return await (await
|
|
1801
|
+
return await (await fetchWithTimeout(path, {
|
|
1290
1802
|
method: "POST",
|
|
1291
1803
|
headers: {
|
|
1292
1804
|
"content-type": "application/json",
|
|
@@ -1298,14 +1810,35 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
1298
1810
|
}
|
|
1299
1811
|
/** GET 同源 JSON;非 2xx 或非 JSON 返回 null(调用方决定怎么降级)。 */
|
|
1300
1812
|
async function getJson(path) {
|
|
1301
|
-
const response = await
|
|
1813
|
+
const response = await fetchWithTimeout(path, {
|
|
1302
1814
|
headers: { accept: "application/json" },
|
|
1303
1815
|
cache: "no-store"
|
|
1304
1816
|
});
|
|
1305
1817
|
if (!response.ok) return null;
|
|
1306
1818
|
return await response.json().catch(() => null);
|
|
1307
1819
|
}
|
|
1308
|
-
|
|
1820
|
+
/**
|
|
1821
|
+
* 写路径的成败判定收口:失败返回 Error,成功返回 null。
|
|
1822
|
+
*
|
|
1823
|
+
* `postJson` 只把「HTTP/解析失败」折成 `null`,域失败是 `{ok:false, error}`(Host 对
|
|
1824
|
+
* 写操作恒回 HTTP 200,成败看 body)。三个写回调曾各判一次,而**「忘掉令牌」漏判**——
|
|
1825
|
+
* 它直接 `await postJson(...)` 就往下走,于是 Host 侧刻意传播的失败(凭据文件只读时
|
|
1826
|
+
* `ms-auth.forget` 抛错,见其文件头「谎报成功比失败更糟」)在界面上表现为「什么都没
|
|
1827
|
+
* 发生」:没有错误行、busy 清掉、刷新后的快照里令牌还在。判据收在这里一份,三个调用
|
|
1828
|
+
* 方(开关/清单、保存令牌、忘掉令牌)不可能再各自漂移。
|
|
1829
|
+
* @param {ApiBody|null} body - postJson 的返回值。
|
|
1830
|
+
* @param {string} fallback - body 里没有可读原因时的一句话。
|
|
1831
|
+
* @returns {Error|null} 失败原因;`null` = 成功。
|
|
1832
|
+
*/
|
|
1833
|
+
function writeFailure(body, fallback) {
|
|
1834
|
+
if (body !== null && body.ok === true) return null;
|
|
1835
|
+
const reason = typeof body?.error === "string" && body.error.trim() !== "" ? body.error : fallback;
|
|
1836
|
+
return new Error(reason);
|
|
1837
|
+
}
|
|
1838
|
+
var FETCH_TIMEOUT_MS;
|
|
1839
|
+
var init_http = __esmMin((() => {
|
|
1840
|
+
FETCH_TIMEOUT_MS = 2e4;
|
|
1841
|
+
}));
|
|
1309
1842
|
|
|
1310
1843
|
//#endregion
|
|
1311
1844
|
//#region src/client/use-polling-interval.ts
|
|
@@ -1382,12 +1915,19 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
1382
1915
|
const [cadenceMs, setCadenceMs] = useState(defaultCadenceMs);
|
|
1383
1916
|
/** Host 少给的顶层键(契约漂移);透给面板当 shapeWarning,不静默。 */
|
|
1384
1917
|
const [missingKeys, setMissingKeys] = useState([]);
|
|
1918
|
+
/**
|
|
1919
|
+
* 每一次轮询尝试都加一,成功与失败都算。`updatedAt` 只在成功时变,而
|
|
1920
|
+
* 面板的 provider 兜底 GET 需要的是「下一轮还会再来」这个信号——快照本身
|
|
1921
|
+
* 失败的那一轮,恰好是兜底路径也应该再试的一轮。
|
|
1922
|
+
*/
|
|
1923
|
+
const [pollRevision, setPollRevision] = useState(0);
|
|
1385
1924
|
const generation = useRef(0);
|
|
1386
1925
|
const inFlight = useRef(null);
|
|
1387
1926
|
const cadenceRef = useRef(cadenceMs);
|
|
1388
1927
|
cadenceRef.current = cadenceMs;
|
|
1389
1928
|
const load = useCallback(async () => {
|
|
1390
1929
|
generation.current += 1;
|
|
1930
|
+
setPollRevision((revision) => revision + 1);
|
|
1391
1931
|
const mine = generation.current;
|
|
1392
1932
|
const isCurrent = () => generation.current === mine;
|
|
1393
1933
|
inFlight.current?.abort?.();
|
|
@@ -1438,7 +1978,8 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
1438
1978
|
loadedOnce,
|
|
1439
1979
|
updatedAt,
|
|
1440
1980
|
load,
|
|
1441
|
-
missingKeys
|
|
1981
|
+
missingKeys,
|
|
1982
|
+
pollRevision
|
|
1442
1983
|
};
|
|
1443
1984
|
}
|
|
1444
1985
|
var init_use_snapshot_polling = __esmMin((() => {
|
|
@@ -1452,7 +1993,7 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
1452
1993
|
//#endregion
|
|
1453
1994
|
//#region src/client/panel-page.ts
|
|
1454
1995
|
function PanelPage({ tt, localeSubscribe }) {
|
|
1455
|
-
const { data, error,
|
|
1996
|
+
const { data, error, updatedAt, load, missingKeys, pollRevision } = useSnapshotPolling();
|
|
1456
1997
|
const [, setLocaleRevision] = useState(0);
|
|
1457
1998
|
const [openSections, setOpenSections] = useState({
|
|
1458
1999
|
balance: true,
|
|
@@ -1460,6 +2001,7 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
1460
2001
|
trend: true,
|
|
1461
2002
|
events: false,
|
|
1462
2003
|
provider: true,
|
|
2004
|
+
draw: true,
|
|
1463
2005
|
token: true
|
|
1464
2006
|
});
|
|
1465
2007
|
const [activeTab, setActiveTab] = useState("quota");
|
|
@@ -1473,67 +2015,125 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
1473
2015
|
[key]: !current[key]
|
|
1474
2016
|
}));
|
|
1475
2017
|
}, []);
|
|
1476
|
-
const { failure,
|
|
1477
|
-
const showSetup = needsSetup && loadedOnce;
|
|
2018
|
+
const { failure, guidance, shapeWarnings } = viewOf(data, error, tt, missingKeys);
|
|
1478
2019
|
const rawProvider = data?.provider;
|
|
1479
2020
|
const hasProviderBlock = rawProvider !== void 0 && rawProvider !== null;
|
|
1480
2021
|
const [fetchedProvider, setFetchedProvider] = useState(null);
|
|
1481
|
-
const provider = hasProviderBlock ? providerOf(rawProvider) : fetchedProvider ?? DEGRADED_PROVIDER;
|
|
2022
|
+
const provider = hasProviderBlock ? providerOf$1(rawProvider) : fetchedProvider ?? DEGRADED_PROVIDER;
|
|
1482
2023
|
useEffect(() => {
|
|
1483
2024
|
if (hasProviderBlock) return void 0;
|
|
1484
2025
|
let cancelled = false;
|
|
1485
2026
|
getJson(PROVIDER_PATH).then((body) => {
|
|
1486
|
-
if (!cancelled && body !== null && body.ok === true) setFetchedProvider(providerOf(body));
|
|
2027
|
+
if (!cancelled && body !== null && body.ok === true) setFetchedProvider(providerOf$1(body));
|
|
2028
|
+
}).catch(() => {});
|
|
2029
|
+
return () => {
|
|
2030
|
+
cancelled = true;
|
|
2031
|
+
};
|
|
2032
|
+
}, [hasProviderBlock, pollRevision]);
|
|
2033
|
+
const rawDraw = data?.draw;
|
|
2034
|
+
const hasDrawBlock = rawDraw !== void 0 && rawDraw !== null;
|
|
2035
|
+
const [fetchedDraw, setFetchedDraw] = useState(null);
|
|
2036
|
+
const draw = hasDrawBlock ? drawOf(rawDraw) : fetchedDraw ?? DEGRADED_DRAW;
|
|
2037
|
+
useEffect(() => {
|
|
2038
|
+
if (hasDrawBlock) return void 0;
|
|
2039
|
+
let cancelled = false;
|
|
2040
|
+
getJson(DRAW_PATH).then((body) => {
|
|
2041
|
+
if (!cancelled && body !== null && body.ok === true) setFetchedDraw(drawOf(body));
|
|
1487
2042
|
}).catch(() => {});
|
|
1488
2043
|
return () => {
|
|
1489
2044
|
cancelled = true;
|
|
1490
2045
|
};
|
|
1491
|
-
}, [
|
|
2046
|
+
}, [hasDrawBlock, pollRevision]);
|
|
1492
2047
|
const [providerBusy, setProviderBusy] = useState(false);
|
|
1493
2048
|
const [providerError, setProviderError] = useState(null);
|
|
2049
|
+
const writeErrorText = (reason, t) => reason instanceof Error && reason.name === "AbortError" ? t("common.timeout") : errorText(reason);
|
|
2050
|
+
/**
|
|
2051
|
+
* POST 一个写路径,并把 body 里的域失败抛出去(Host 对写操作恒回 HTTP 200,成败
|
|
2052
|
+
* 看 body)。判据只有 `http.ts#writeFailure` 一份——三个写回调曾各判一次,而
|
|
2053
|
+
* 「忘掉令牌」那条漏了,于是凭据文件只读时界面表现为「什么都没发生」。
|
|
2054
|
+
*/
|
|
2055
|
+
const postWrite = useCallback(async (path, payload) => {
|
|
2056
|
+
const failure = writeFailure(await postJson(path, payload), tt("common.httpError"));
|
|
2057
|
+
if (failure !== null) throw failure;
|
|
2058
|
+
}, [tt]);
|
|
1494
2059
|
const runProviderWrite = useCallback(async (path, payload) => {
|
|
1495
2060
|
setProviderBusy(true);
|
|
1496
2061
|
setProviderError(null);
|
|
1497
2062
|
try {
|
|
1498
|
-
|
|
1499
|
-
if (body === null || body.ok !== true) throw new Error(typeof body?.error === "string" ? body.error : "HTTP error");
|
|
2063
|
+
await postWrite(path, payload);
|
|
1500
2064
|
load();
|
|
1501
2065
|
} catch (reason) {
|
|
1502
|
-
setProviderError(format(tt("provider.error"), { error:
|
|
2066
|
+
setProviderError(format(tt("provider.error"), { error: writeErrorText(reason, tt) }));
|
|
1503
2067
|
} finally {
|
|
1504
2068
|
setProviderBusy(false);
|
|
1505
2069
|
}
|
|
1506
|
-
}, [
|
|
2070
|
+
}, [
|
|
2071
|
+
load,
|
|
2072
|
+
postWrite,
|
|
2073
|
+
tt
|
|
2074
|
+
]);
|
|
1507
2075
|
const toggleProvider = useCallback((enabled) => void runProviderWrite(PROVIDER_PATH, { enabled }), [runProviderWrite]);
|
|
1508
|
-
const saveRoster = useCallback((enabledIds) =>
|
|
2076
|
+
const saveRoster = useCallback((enabledIds) => {
|
|
2077
|
+
runProviderWrite(PROVIDER_ROSTER_PATH, { enabledIds: normalizeRosterSave(enabledIds) });
|
|
2078
|
+
}, [runProviderWrite]);
|
|
1509
2079
|
const resetProvider = useCallback(() => void runProviderWrite(PROVIDER_RESET_PATH, {}), [runProviderWrite]);
|
|
2080
|
+
const saveContextWindow = useCallback((contextWindow) => {
|
|
2081
|
+
runProviderWrite(PROVIDER_CONTEXT_PATH, { contextWindow });
|
|
2082
|
+
}, [runProviderWrite]);
|
|
2083
|
+
const [drawBusy, setDrawBusy] = useState(false);
|
|
2084
|
+
const [drawError, setDrawError] = useState(null);
|
|
2085
|
+
const runDrawWrite = useCallback(async (path, payload) => {
|
|
2086
|
+
setDrawBusy(true);
|
|
2087
|
+
setDrawError(null);
|
|
2088
|
+
try {
|
|
2089
|
+
await postWrite(path, payload);
|
|
2090
|
+
load();
|
|
2091
|
+
} catch (reason) {
|
|
2092
|
+
setDrawError(format(tt("draw.error"), { error: writeErrorText(reason, tt) }));
|
|
2093
|
+
} finally {
|
|
2094
|
+
setDrawBusy(false);
|
|
2095
|
+
}
|
|
2096
|
+
}, [
|
|
2097
|
+
load,
|
|
2098
|
+
postWrite,
|
|
2099
|
+
tt
|
|
2100
|
+
]);
|
|
2101
|
+
const toggleDraw = useCallback((enabled) => void runDrawWrite(DRAW_PATH, { enabled }), [runDrawWrite]);
|
|
2102
|
+
const resetDraw = useCallback(() => void runDrawWrite(DRAW_RESET_PATH, {}), [runDrawWrite]);
|
|
1510
2103
|
const [tokenBusy, setTokenBusy] = useState(false);
|
|
1511
2104
|
const [tokenError, setTokenError] = useState(null);
|
|
1512
2105
|
const saveToken = useCallback(async (value) => {
|
|
1513
2106
|
setTokenBusy(true);
|
|
1514
2107
|
setTokenError(null);
|
|
1515
2108
|
try {
|
|
1516
|
-
|
|
1517
|
-
if (body === null || body.ok !== true) throw new Error(typeof body?.error === "string" ? body.error : "HTTP error");
|
|
2109
|
+
await postWrite(TOKEN_PATH, { token: value });
|
|
1518
2110
|
load();
|
|
1519
2111
|
} catch (reason) {
|
|
1520
|
-
setTokenError(
|
|
2112
|
+
setTokenError(writeErrorText(reason, tt));
|
|
1521
2113
|
} finally {
|
|
1522
2114
|
setTokenBusy(false);
|
|
1523
2115
|
}
|
|
1524
|
-
}, [
|
|
2116
|
+
}, [
|
|
2117
|
+
load,
|
|
2118
|
+
postWrite,
|
|
2119
|
+
tt
|
|
2120
|
+
]);
|
|
1525
2121
|
const forgetToken = useCallback(async () => {
|
|
1526
2122
|
setTokenBusy(true);
|
|
1527
2123
|
setTokenError(null);
|
|
1528
2124
|
try {
|
|
1529
|
-
await
|
|
2125
|
+
await postWrite(TOKEN_FORGET_PATH, {});
|
|
1530
2126
|
load();
|
|
1531
2127
|
} catch (reason) {
|
|
1532
|
-
setTokenError(
|
|
2128
|
+
setTokenError(writeErrorText(reason, tt));
|
|
1533
2129
|
} finally {
|
|
1534
2130
|
setTokenBusy(false);
|
|
1535
2131
|
}
|
|
1536
|
-
}, [
|
|
2132
|
+
}, [
|
|
2133
|
+
load,
|
|
2134
|
+
postWrite,
|
|
2135
|
+
tt
|
|
2136
|
+
]);
|
|
1537
2137
|
const tokenState = data?.token ?? null;
|
|
1538
2138
|
const sampleModel = data?.models?.sample?.[0] ?? null;
|
|
1539
2139
|
const [verifyBusy, setVerifyBusy] = useState(false);
|
|
@@ -1545,20 +2145,17 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
1545
2145
|
setVerifyError(null);
|
|
1546
2146
|
try {
|
|
1547
2147
|
const body = await postJson(PROBE_PATH, { modelId });
|
|
2148
|
+
if (body !== null) load();
|
|
1548
2149
|
if (body !== null && body.ok === true) setVerifyNote(format(tt("probe.validOk"), { status: Number(body.status ?? 0) }));
|
|
1549
|
-
else setVerifyError(typeof body?.error === "string" ? body.error : "
|
|
2150
|
+
else setVerifyError(typeof body?.error === "string" ? body.error : tt("common.httpError"));
|
|
1550
2151
|
} catch (reason) {
|
|
1551
|
-
setVerifyError(
|
|
2152
|
+
setVerifyError(writeErrorText(reason, tt));
|
|
1552
2153
|
} finally {
|
|
1553
2154
|
setVerifyBusy(false);
|
|
1554
2155
|
}
|
|
1555
|
-
}, [tt]);
|
|
2156
|
+
}, [load, tt]);
|
|
1556
2157
|
const quotaBody = () => {
|
|
1557
2158
|
if (data === null) return h("div", { style: S.empty }, failure === null ? tt("panel.loading") : h("div", { role: "alert" }, guidance ?? format(tt("panel.error"), { error: failure.message })));
|
|
1558
|
-
if (showSetup && failure !== null) return h("div", {
|
|
1559
|
-
style: S.empty,
|
|
1560
|
-
role: "alert"
|
|
1561
|
-
}, guidance ?? format(tt("panel.error"), { error: failure.message }));
|
|
1562
2159
|
const snap = data;
|
|
1563
2160
|
return h("div", null, snap.token?.present === false ? h("div", {
|
|
1564
2161
|
style: S.formNote,
|
|
@@ -1608,9 +2205,23 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
1608
2205
|
error: providerError,
|
|
1609
2206
|
onToggle: toggleProvider,
|
|
1610
2207
|
onSaveList: saveRoster,
|
|
2208
|
+
onSaveContextWindow: saveContextWindow,
|
|
1611
2209
|
onReset: resetProvider,
|
|
1612
2210
|
tokenPresent: data?.token?.present === true,
|
|
1613
2211
|
tt
|
|
2212
|
+
})), h(SectionCard, {
|
|
2213
|
+
title: tt("section.draw"),
|
|
2214
|
+
open: openSections.draw,
|
|
2215
|
+
onToggle: () => toggleSection("draw"),
|
|
2216
|
+
tt
|
|
2217
|
+
}, h(DrawCard, {
|
|
2218
|
+
draw,
|
|
2219
|
+
busy: drawBusy,
|
|
2220
|
+
error: drawError,
|
|
2221
|
+
onToggle: toggleDraw,
|
|
2222
|
+
onReset: resetDraw,
|
|
2223
|
+
tokenPresent: data?.token?.present === true,
|
|
2224
|
+
tt
|
|
1614
2225
|
})));
|
|
1615
2226
|
const accessBody = () => h("div", null, h(SectionCard, {
|
|
1616
2227
|
title: tt("section.token"),
|
|
@@ -1688,6 +2299,176 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
1688
2299
|
init_use_snapshot_polling();
|
|
1689
2300
|
init_runtime();
|
|
1690
2301
|
init_styles();
|
|
2302
|
+
init_allow_list();
|
|
2303
|
+
}));
|
|
2304
|
+
|
|
2305
|
+
//#endregion
|
|
2306
|
+
//#region src/client/composer-usage-pill.ts
|
|
2307
|
+
/**
|
|
2308
|
+
* 一个候选自带的 provider,空串算「没有」。
|
|
2309
|
+
*
|
|
2310
|
+
* `typeof provider === "string"` 是不够的判据:`""` 也是字符串。照类型判定会让
|
|
2311
|
+
* `{ provider: "" }` 被当成一个「可用的 next」,于是它遮住仍然可用的 `lastUsed`
|
|
2312
|
+
* ——切模型那一帧胶囊就消失。所以这里判的是**非空字符串**。
|
|
2313
|
+
*/
|
|
2314
|
+
function providerOf(candidate) {
|
|
2315
|
+
const provider = candidate?.provider;
|
|
2316
|
+
return typeof provider === "string" && provider !== "" ? provider : void 0;
|
|
2317
|
+
}
|
|
2318
|
+
/**
|
|
2319
|
+
* 会话当前指向的 provider。
|
|
2320
|
+
*
|
|
2321
|
+
* `next` 优先于 `lastUsed`(切换已落地但还没发出去时,选择器显示的就是 `next`),
|
|
2322
|
+
* 但**只在 `next` 真的带了一个非空 provider 时**才优先:picker 刚切完、新选择还没
|
|
2323
|
+
* 落到 `next` 上的那几帧 `next` 是 `{}` 或 `{ provider: "" }`,照「有 next 就用
|
|
2324
|
+
* next」读会得到 `undefined`,于是归属模型在「切换」这个动作本身上把胶囊藏起来。
|
|
2325
|
+
* 两侧都没有可用 provider 时返回 `undefined`——那才是真的判不出来。
|
|
2326
|
+
*/
|
|
2327
|
+
function selectedProviderOf(projection) {
|
|
2328
|
+
const source = projection ?? {};
|
|
2329
|
+
return providerOf(source.next) ?? providerOf(source.lastUsed);
|
|
2330
|
+
}
|
|
2331
|
+
/**
|
|
2332
|
+
* 余额读数;`null` 表示**不知道**(没读到 / 上游没给 / 字段不是有限数)。
|
|
2333
|
+
*
|
|
2334
|
+
* 特意不接受 `error` 参数做二次判断:错误文本只影响 tooltip 的措辞,不影响
|
|
2335
|
+
* 「数字可不可信」。把两者揉在一个判定里,会让「上次读到的 143 仍然有效」这种
|
|
2336
|
+
* 情形(`error` 非空但 `available` 有值)被误打成未知。
|
|
2337
|
+
*/
|
|
2338
|
+
function availableOf(balance) {
|
|
2339
|
+
const available = balance?.available;
|
|
2340
|
+
return typeof available === "number" && Number.isFinite(available) ? available : null;
|
|
2341
|
+
}
|
|
2342
|
+
/** 今日本地调用次数;`null` 同上,表示读不到(不是 0)。 */
|
|
2343
|
+
function usedTodayOf(snapshot) {
|
|
2344
|
+
const used = snapshot?.quota?.daily?.usedLocal;
|
|
2345
|
+
return typeof used === "number" && Number.isFinite(used) ? used : null;
|
|
2346
|
+
}
|
|
2347
|
+
/** 由余额读数定档;`null`(未读到)与 `0`(读到了、就是零)在此分道。 */
|
|
2348
|
+
function tierOf(available) {
|
|
2349
|
+
if (available === null) return "unknown";
|
|
2350
|
+
return available > 0 ? "ok" : "exhausted";
|
|
2351
|
+
}
|
|
2352
|
+
/** 「余额」段的正文:读不到画「—」+ 单位,读到了画数字 + 单位。 */
|
|
2353
|
+
function balanceText(available, tt) {
|
|
2354
|
+
return `${available === null ? "—" : count(available)} ${tt("composer.balanceUnit")}`;
|
|
2355
|
+
}
|
|
2356
|
+
/** 「今日本地」段的正文:读不到画「—」,读到了画次数。 */
|
|
2357
|
+
function callsText(usedToday, tt) {
|
|
2358
|
+
return format(tt("composer.localCalls"), { calls: usedToday === null ? "—" : count(usedToday) });
|
|
2359
|
+
}
|
|
2360
|
+
/**
|
|
2361
|
+
* 胶囊正文:余额段 + 本地次数段,各带各的口径。两段都必须出现——只画有值的那
|
|
2362
|
+
* 一段会让「哪一段是空的」变成排版事故(读者分不清是没读到还是这一栏根本不在),
|
|
2363
|
+
* 而「—」明确说了是前者。
|
|
2364
|
+
*/
|
|
2365
|
+
function readingText(available, usedToday, tt) {
|
|
2366
|
+
return `${balanceText(available, tt)} · ${callsText(usedToday, tt)}`;
|
|
2367
|
+
}
|
|
2368
|
+
/**
|
|
2369
|
+
* 数字是否已经算「上次数据」。
|
|
2370
|
+
*
|
|
2371
|
+
* 两个触发条件各自独立:读失败(`error` 非空,上一轮的数值还留在屏幕上)本身就是
|
|
2372
|
+
* 陈旧;而长时间没有**成功**的读(`updatedAt` 不动)说明定时轮询没在按时回来
|
|
2373
|
+
* ——失败退避或页面被挂起都会这样。取 3 个声明周期而不是 1 个:一次偶发失败就会
|
|
2374
|
+
* 拉长间隔,卡在 1 个周期上会让每一次抖动都把读数打成陈旧的。
|
|
2375
|
+
*
|
|
2376
|
+
* `updatedAt === 0` 表示**从没读到过**,那不是「陈旧」而是「还没有」——调用方据此
|
|
2377
|
+
* 决定不标;本函数只在有读数的前提下判断,故返回 false。
|
|
2378
|
+
*/
|
|
2379
|
+
function staleOf(input) {
|
|
2380
|
+
if (input.error !== null && input.error !== void 0) return input.updatedAt > 0;
|
|
2381
|
+
return input.updatedAt > 0 && input.now - input.updatedAt > input.cadenceMs * STALE_CADENCES;
|
|
2382
|
+
}
|
|
2383
|
+
/**
|
|
2384
|
+
* tooltip 的一行:只把**读者下一步会问的**事实拼进来——数字是哪来的(前缀)、
|
|
2385
|
+
* 余额为什么没有(错误原文)、什么时候读的(fetchedAt)、本地计数为什么没有
|
|
2386
|
+
* (quota 那一路的提示)、以及数字是不是旧的。逐段 `filter` 掉空值,不留下
|
|
2387
|
+
* 「 · · 」这样的空槽。
|
|
2388
|
+
*/
|
|
2389
|
+
function titleOf(props) {
|
|
2390
|
+
const { tt, balance, usedToday, stale } = props;
|
|
2391
|
+
const parts = [tt("composer.pillTitle")];
|
|
2392
|
+
const failure = balance?.error;
|
|
2393
|
+
parts.push(typeof failure === "string" && failure !== "" ? format(tt("balance.unavailable"), { error: failure }) : null);
|
|
2394
|
+
const fetchedAt = balance?.fetchedAt;
|
|
2395
|
+
parts.push(typeof fetchedAt === "string" && fetchedAt !== "" ? format(tt("balance.fetched"), { time: isoTime(fetchedAt) }) : null);
|
|
2396
|
+
parts.push(usedToday === null ? tt("quota.unavailable") : null);
|
|
2397
|
+
parts.push(stale ? tt("composer.stale") : null);
|
|
2398
|
+
return parts.filter((part) => part !== null).join(" · ");
|
|
2399
|
+
}
|
|
2400
|
+
/**
|
|
2401
|
+
* 内层:只在会话选中本插件的模型时挂载。轮询 hook 在这里,被挂/摘即起停——
|
|
2402
|
+
* 非归属 provider 的会话不发任何请求。
|
|
2403
|
+
*/
|
|
2404
|
+
function ModelscopePill(props) {
|
|
2405
|
+
const { tt } = props;
|
|
2406
|
+
const { data, error, loadedOnce, updatedAt } = useSnapshotPolling();
|
|
2407
|
+
const balance = data?.balance ?? null;
|
|
2408
|
+
const available = availableOf(balance);
|
|
2409
|
+
const usedToday = usedTodayOf(data);
|
|
2410
|
+
const cadenceMs = statedCadenceMs(data?.pollSeconds, DEFAULT_CADENCE_MS);
|
|
2411
|
+
const stale = staleOf({
|
|
2412
|
+
error,
|
|
2413
|
+
updatedAt,
|
|
2414
|
+
now: Date.now(),
|
|
2415
|
+
cadenceMs
|
|
2416
|
+
});
|
|
2417
|
+
const pending = !loadedOnce && data === null;
|
|
2418
|
+
const tier = tierOf(available);
|
|
2419
|
+
const title = titleOf({
|
|
2420
|
+
tt,
|
|
2421
|
+
balance,
|
|
2422
|
+
usedToday,
|
|
2423
|
+
stale
|
|
2424
|
+
});
|
|
2425
|
+
return h("button", {
|
|
2426
|
+
type: "button",
|
|
2427
|
+
className: "dsh-modelscope-composer-pill",
|
|
2428
|
+
title,
|
|
2429
|
+
"aria-label": title,
|
|
2430
|
+
style: S.pillTrigger
|
|
2431
|
+
}, h("span", {
|
|
2432
|
+
className: "dsh-modelscope-composer-pill-dot",
|
|
2433
|
+
"aria-hidden": "true",
|
|
2434
|
+
style: {
|
|
2435
|
+
...S.pillDot,
|
|
2436
|
+
background: TIER_INK[tier]
|
|
2437
|
+
}
|
|
2438
|
+
}), h("span", {
|
|
2439
|
+
className: "dsh-modelscope-composer-pill-plan",
|
|
2440
|
+
style: S.pillPlan
|
|
2441
|
+
}, `${tt("composer.prefix")} · `), h("span", {
|
|
2442
|
+
className: "dsh-modelscope-composer-pill-text",
|
|
2443
|
+
style: tier === "exhausted" ? S.pillTierExhaustedText : void 0
|
|
2444
|
+
}, pending ? "…" : readingText(available, usedToday, tt)), stale ? h("span", {
|
|
2445
|
+
className: "dsh-modelscope-composer-pill-stale",
|
|
2446
|
+
style: S.pillStale
|
|
2447
|
+
}, ` · ${tt("composer.stale")}`) : null);
|
|
2448
|
+
}
|
|
2449
|
+
/**
|
|
2450
|
+
* 外层门控:固定一处调 `useProjection`,再决定内层挂谁。非归属 provider 返回
|
|
2451
|
+
* `null`(不渲染任何东西,也不跑任何请求)。
|
|
2452
|
+
*/
|
|
2453
|
+
function ComposerUsagePill(props) {
|
|
2454
|
+
const selection = props.useProjection?.("modelSelection");
|
|
2455
|
+
if (selectedProviderOf(selection) !== "modelscope-token-plan") return null;
|
|
2456
|
+
return h(ModelscopePill, { tt: props.tt });
|
|
2457
|
+
}
|
|
2458
|
+
var MODELSCOPE_PROVIDER, DEFAULT_CADENCE_MS, STALE_CADENCES, TIER_INK;
|
|
2459
|
+
var init_composer_usage_pill = __esmMin((() => {
|
|
2460
|
+
init_runtime();
|
|
2461
|
+
init_styles();
|
|
2462
|
+
init_format();
|
|
2463
|
+
init_use_snapshot_polling();
|
|
2464
|
+
MODELSCOPE_PROVIDER = "modelscope-token-plan";
|
|
2465
|
+
DEFAULT_CADENCE_MS = 3e4;
|
|
2466
|
+
STALE_CADENCES = 3;
|
|
2467
|
+
TIER_INK = {
|
|
2468
|
+
ok: S.pillTierOk,
|
|
2469
|
+
exhausted: S.pillTierExhausted,
|
|
2470
|
+
unknown: S.pillTierUnknown
|
|
2471
|
+
};
|
|
1691
2472
|
}));
|
|
1692
2473
|
|
|
1693
2474
|
//#endregion
|
|
@@ -1732,6 +2513,17 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
1732
2513
|
} catch (error) {
|
|
1733
2514
|
console.warn(`[${NS}] config card registration failed:`, error);
|
|
1734
2515
|
}
|
|
2516
|
+
try {
|
|
2517
|
+
disposers.push(ctx.slots.inject("conversation.input.right", () => ctx.slots.register({
|
|
2518
|
+
name: "conversation.input.right",
|
|
2519
|
+
id: `${NS}-composer-pill`,
|
|
2520
|
+
locale: NS,
|
|
2521
|
+
order: 4,
|
|
2522
|
+
inject: () => ({ tt })
|
|
2523
|
+
}, ComposerUsagePill)));
|
|
2524
|
+
} catch (error) {
|
|
2525
|
+
console.warn(`[${NS}] composer pill registration failed:`, error);
|
|
2526
|
+
}
|
|
1735
2527
|
ctx.effect(() => () => {
|
|
1736
2528
|
for (const dispose of disposers.splice(0)) try {
|
|
1737
2529
|
dispose();
|
|
@@ -1743,6 +2535,7 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
1743
2535
|
init_const();
|
|
1744
2536
|
init_i18n();
|
|
1745
2537
|
init_panel_page();
|
|
2538
|
+
init_composer_usage_pill();
|
|
1746
2539
|
inject = ["slots", "locale"];
|
|
1747
2540
|
}));
|
|
1748
2541
|
|
|
@@ -1758,6 +2551,7 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
1758
2551
|
init_styles();
|
|
1759
2552
|
init_cards();
|
|
1760
2553
|
init_panel_page();
|
|
2554
|
+
init_composer_usage_pill();
|
|
1761
2555
|
init_use_polling_interval();
|
|
1762
2556
|
init_use_snapshot_polling();
|
|
1763
2557
|
init_http();
|
|
@@ -1767,6 +2561,7 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
1767
2561
|
const panel = Object.freeze({
|
|
1768
2562
|
interpretSnapshot,
|
|
1769
2563
|
viewOf,
|
|
2564
|
+
providerOf: providerOf$1,
|
|
1770
2565
|
errorOfStatus,
|
|
1771
2566
|
dictionaries: Object.freeze({
|
|
1772
2567
|
zh,
|
|
@@ -1787,12 +2582,24 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
1787
2582
|
usePollingInterval,
|
|
1788
2583
|
useSnapshotPolling,
|
|
1789
2584
|
getJson,
|
|
1790
|
-
postJson
|
|
2585
|
+
postJson,
|
|
2586
|
+
writeFailure,
|
|
2587
|
+
MODELSCOPE_PROVIDER,
|
|
2588
|
+
selectedProviderOf,
|
|
2589
|
+
availableOf,
|
|
2590
|
+
usedTodayOf,
|
|
2591
|
+
tierOf,
|
|
2592
|
+
balanceText,
|
|
2593
|
+
callsText,
|
|
2594
|
+
readingText,
|
|
2595
|
+
staleOf
|
|
1791
2596
|
}),
|
|
1792
2597
|
components: Object.freeze({
|
|
1793
2598
|
BalanceCard,
|
|
2599
|
+
ComposerUsagePill,
|
|
1794
2600
|
EventsList,
|
|
1795
2601
|
LocalDailyCard,
|
|
2602
|
+
ModelscopePill,
|
|
1796
2603
|
ModelUsageTable,
|
|
1797
2604
|
PanelPage,
|
|
1798
2605
|
ProviderCard,
|