dsh-connect-modelscope-token-plan 0.2.0 → 0.3.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 +101 -0
- package/README.md +33 -2
- package/client.js +778 -85
- package/cordis.patch.yml +27 -0
- package/lib/{host-config-C4bWNPWU.js → host-config.js} +206 -47
- package/lib/index.js +2003 -297
- 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
|
@@ -14,14 +14,32 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
14
14
|
//#endregion
|
|
15
15
|
//#region src/client/const.ts
|
|
16
16
|
function modelscopeModelUrl(id) {
|
|
17
|
-
return `${MODELSCOPE_MODEL_URL_BASE}/${
|
|
17
|
+
return `${MODELSCOPE_MODEL_URL_BASE}/${id.split("/").map(encodeURIComponent).join("/")}`;
|
|
18
18
|
}
|
|
19
19
|
/** 从 `owner/model` 取 owner(无斜杠时整段作 owner)。 */
|
|
20
20
|
function catalogOwner(id) {
|
|
21
21
|
const slash = id.indexOf("/");
|
|
22
22
|
return slash >= 0 ? id.slice(0, slash) : id;
|
|
23
23
|
}
|
|
24
|
-
|
|
24
|
+
/**
|
|
25
|
+
* 徽章用的窗口短标注(tokens → "128K" / "1M")。
|
|
26
|
+
*
|
|
27
|
+
* K=1024、M=1024²,与档位值(131072…1048576)同一把尺。**不美化**:目录声明的
|
|
28
|
+
* 128000 显示成 "125K" 而不是 "128K"——标签必须能从数值反推出来,四舍五入到
|
|
29
|
+
* 好看的整数是撒谎。≥1M 用 M(`1.25M` 保留至多一位小数,去掉尾随 .0),
|
|
30
|
+
* ≥1024 用 K,更小的原样显示。
|
|
31
|
+
* @param {number} tokens - 窗口大小(tokens)。
|
|
32
|
+
* @returns {string} 短标注。
|
|
33
|
+
*/
|
|
34
|
+
function formatWindow(tokens) {
|
|
35
|
+
if (tokens >= 1048576) {
|
|
36
|
+
const m = tokens / 1048576;
|
|
37
|
+
return `${m >= 10 || Number.isInteger(m) ? Math.round(m) : Math.round(m * 10) / 10}M`;
|
|
38
|
+
}
|
|
39
|
+
if (tokens >= 1024) return `${Math.round(tokens / 1024)}K`;
|
|
40
|
+
return String(tokens);
|
|
41
|
+
}
|
|
42
|
+
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, HIDE_ALL_MODELS, MODELSCOPE_TOKEN_URL, MODELSCOPE_USAGE_URL, MODELSCOPE_MODEL_URL_BASE, FEATURED_OWNERS;
|
|
25
43
|
var init_const = __esmMin((() => {
|
|
26
44
|
NS = "dsh-connect-modelscope-token-plan";
|
|
27
45
|
PANEL_ID = NS;
|
|
@@ -32,7 +50,10 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
32
50
|
PROBE_PATH = `/api/${NS}/probe`;
|
|
33
51
|
PROVIDER_PATH = `/api/${NS}/provider`;
|
|
34
52
|
PROVIDER_ROSTER_PATH = `/api/${NS}/provider/roster`;
|
|
53
|
+
PROVIDER_CONTEXT_PATH = `/api/${NS}/provider/context`;
|
|
35
54
|
PROVIDER_RESET_PATH = `/api/${NS}/provider/reset`;
|
|
55
|
+
DRAW_PATH = `/api/${NS}/draw`;
|
|
56
|
+
DRAW_RESET_PATH = `/api/${NS}/draw/reset`;
|
|
36
57
|
HIDE_ALL_MODELS = "__hide_all__";
|
|
37
58
|
MODELSCOPE_TOKEN_URL = "https://modelscope.cn/my/myaccesstoken";
|
|
38
59
|
MODELSCOPE_USAGE_URL = "https://modelscope.cn/magicube/usage?tab=consume";
|
|
@@ -62,6 +83,10 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
62
83
|
"panel.authError": "令牌被拒绝(401/403)。请到「接入」tab 更换访问令牌。",
|
|
63
84
|
"panel.noToken": "还没有配置魔搭访问令牌。到「接入」tab 粘贴一枚 ms-… 令牌即可。",
|
|
64
85
|
"panel.shapeDrift": "上游返回的结构可能有变:{detail}",
|
|
86
|
+
"panel.payloadError": "Host 返回了无法读取的响应(可能页面被登录墙或代理截获)。",
|
|
87
|
+
"shape.missingKeys": "Host 快照缺少必需的顶层键:{keys}",
|
|
88
|
+
"common.httpError": "无法读取 Host 响应(非预期响应,可能页面被登录墙截获)。",
|
|
89
|
+
"common.timeout": "请求超时,请重试。",
|
|
65
90
|
"tab.quota": "额度",
|
|
66
91
|
"tab.models": "模型",
|
|
67
92
|
"tab.access": "接入",
|
|
@@ -81,8 +106,7 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
81
106
|
"balance.unavailable": "魔粒余额暂不可读:{error}",
|
|
82
107
|
"quota.headline": "今日 {calls} 次 · {models} 个模型",
|
|
83
108
|
"quota.note": "本地口径:只统计经本插件的调用,直连魔搭的其它客户端不计入;消耗总量以官方魔粒余额为准。",
|
|
84
|
-
"
|
|
85
|
-
"models.loading": "读取目录中…",
|
|
109
|
+
"quota.unavailable": "本地计数本次读不到(原因见上方提示)",
|
|
86
110
|
"probe.validity": "验令牌(零额度)",
|
|
87
111
|
"probe.validOk": "令牌有效(HTTP {status})",
|
|
88
112
|
"probe.fail": "失败:{error}",
|
|
@@ -94,6 +118,7 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
94
118
|
"provider.enable": "把魔搭模型接入 DSH 模型选择器",
|
|
95
119
|
"provider.on": "已接入",
|
|
96
120
|
"provider.off": "未接入",
|
|
121
|
+
"provider.busy": "切换中…",
|
|
97
122
|
"provider.registered": "已注册(模型可在选择器里选用)",
|
|
98
123
|
"provider.notRegistered": "未注册",
|
|
99
124
|
"provider.llmMissing": "本机 Host 未提供 LLM 注册服务",
|
|
@@ -110,18 +135,45 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
110
135
|
"provider.notConfigured": "还没有访问令牌,先到「接入」tab 配置",
|
|
111
136
|
"provider.source.panel": "面板",
|
|
112
137
|
"provider.source.config": "配置",
|
|
138
|
+
"provider.contextWindow": "上下文窗口",
|
|
139
|
+
"provider.contextDefault": "默认(跟随目录)",
|
|
140
|
+
"provider.contextHint": "目录没声明窗口的模型按此值生效;保存后立即重新注册",
|
|
141
|
+
"provider.contextCuratedTitle": "窗口来自内置策展表(models.dev 交叉真值),平台未亲口声明;带 * 标记",
|
|
142
|
+
"section.draw": "出图工具",
|
|
143
|
+
"draw.enable": "给 agent 注册 modelscope_draw_image 工具",
|
|
144
|
+
"draw.on": "已开启",
|
|
145
|
+
"draw.off": "未开启",
|
|
146
|
+
"draw.busy": "切换中…",
|
|
147
|
+
"draw.mounted": "已注册:{tool}",
|
|
148
|
+
"draw.notMounted": "未注册——勾选上方开关即可开启。",
|
|
149
|
+
"draw.restartHint": "开关立即保存,但工具的挂载发生在**下次 Host 启动**(工具注册无法撤销)。",
|
|
150
|
+
"draw.models": "出图将使用以下模型",
|
|
151
|
+
"draw.badge": "出图",
|
|
152
|
+
"draw.noCandidates": "当前目录里没有出图模型(平台声明 image-to-image / text-to-image 的条目为空);出图不可用。",
|
|
153
|
+
"draw.needsKey": "还没有访问令牌,先到「接入」tab 配置。",
|
|
154
|
+
"draw.reset": "回到默认",
|
|
155
|
+
"draw.error": "出图设置保存失败:{error}",
|
|
156
|
+
"draw.source.panel": "面板",
|
|
157
|
+
"draw.source.config": "配置",
|
|
158
|
+
"draw.absent.noTools": "开关已开,但本机 Host 未提供 agent tools 服务,出图工具缺席(Host 侧限制,非配置问题)。",
|
|
159
|
+
"draw.absent.peerLoad": "开关已开,但 tools 模块加载失败,出图工具缺席(Host 问题,非配置)。",
|
|
160
|
+
"draw.absent.registry": "开关已开,但工具注册被 Host 拒绝,出图工具缺席(Host 问题,非配置)。",
|
|
161
|
+
"draw.absent.unknown": "开关已开,但出图工具未挂载,原因未知。",
|
|
113
162
|
"trend.none": "还没有本地调用记录。",
|
|
114
163
|
"trend.legend": "柱长相对区间内最大值,仅本地口径。",
|
|
115
164
|
"events.none": "暂无事件。",
|
|
116
165
|
"events.quota": "额度",
|
|
117
166
|
"events.rate_limit": "限频",
|
|
118
167
|
"events.error": "错误",
|
|
168
|
+
"events.unknown": "未知",
|
|
119
169
|
"token.status": "状态:{present}(来源 {source},校验 {valid})",
|
|
120
170
|
"token.save": "保存",
|
|
121
171
|
"token.forget": "忘掉已保存",
|
|
122
172
|
"token.placeholder": "粘贴 ms-… 访问令牌",
|
|
173
|
+
"token.ariaLabel": "魔搭访问令牌",
|
|
123
174
|
"token.hint": "在魔搭「访问令牌」页生成。保存即生效(写入 DSH 凭据服务,不依赖重启)。环境变量 MODELSCOPE_API_KEY 只在 DSH 启动时读一次,且优先级高于此处保存的值——设了它就别再用面板存。",
|
|
124
175
|
"token.ephemeral": "此 Host 没有凭据服务:面板保存的令牌重启即丢,请改用凭据服务或环境变量。",
|
|
176
|
+
"token.readError": "读不到凭据存储,无法确认令牌状态(不代表没配令牌):{error}",
|
|
125
177
|
"token.link": "打开魔搭访问令牌页 →",
|
|
126
178
|
"source.credentials": "凭据服务",
|
|
127
179
|
"source.env": "环境变量",
|
|
@@ -130,7 +182,13 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
130
182
|
"present.yes": "已配置",
|
|
131
183
|
"present.no": "未配置",
|
|
132
184
|
"validity.yes": "有效",
|
|
133
|
-
"validity.no": "未校验"
|
|
185
|
+
"validity.no": "未校验",
|
|
186
|
+
"validity.bad": "无效",
|
|
187
|
+
"composer.prefix": "魔搭",
|
|
188
|
+
"composer.pillTitle": "魔搭额度:官方魔粒余额 + 今日本地调用次数",
|
|
189
|
+
"composer.balanceUnit": "魔粒",
|
|
190
|
+
"composer.localCalls": "今日本地 {calls} 次",
|
|
191
|
+
"composer.stale": "上次数据"
|
|
134
192
|
};
|
|
135
193
|
en = {
|
|
136
194
|
"panel.title": "ModelScope Access · Quota",
|
|
@@ -146,6 +204,10 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
146
204
|
"panel.authError": "Token rejected (401/403). Replace the access token in the Access tab.",
|
|
147
205
|
"panel.noToken": "No ModelScope access token configured yet. Paste an ms-… token in the Access tab.",
|
|
148
206
|
"panel.shapeDrift": "Upstream shape may have changed: {detail}",
|
|
207
|
+
"panel.payloadError": "Host returned an unreadable response (a login wall or proxy may have intercepted it).",
|
|
208
|
+
"shape.missingKeys": "Host snapshot omitted required top-level key(s): {keys}",
|
|
209
|
+
"common.httpError": "Unexpected Host response (a login wall or proxy may have intercepted it).",
|
|
210
|
+
"common.timeout": "Request timed out; please retry.",
|
|
149
211
|
"tab.quota": "Quota",
|
|
150
212
|
"tab.models": "Models",
|
|
151
213
|
"tab.access": "Access",
|
|
@@ -165,8 +227,7 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
165
227
|
"balance.unavailable": "Magicube balance temporarily unreadable: {error}",
|
|
166
228
|
"quota.headline": "Today {calls} calls · {models} models",
|
|
167
229
|
"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…",
|
|
230
|
+
"quota.unavailable": "Local counters could not be read this time (reason in the notices above)",
|
|
170
231
|
"probe.validity": "Verify token (free)",
|
|
171
232
|
"probe.validOk": "Token valid (HTTP {status})",
|
|
172
233
|
"probe.fail": "Failed: {error}",
|
|
@@ -178,6 +239,7 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
178
239
|
"provider.enable": "Register ModelScope models in the DSH model picker",
|
|
179
240
|
"provider.on": "On",
|
|
180
241
|
"provider.off": "Off",
|
|
242
|
+
"provider.busy": "Switching…",
|
|
181
243
|
"provider.registered": "Registered (models selectable in the picker)",
|
|
182
244
|
"provider.notRegistered": "Not registered",
|
|
183
245
|
"provider.llmMissing": "This Host exposes no LLM service",
|
|
@@ -194,18 +256,45 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
194
256
|
"provider.notConfigured": "No token yet — configure it in the Access tab",
|
|
195
257
|
"provider.source.panel": "panel",
|
|
196
258
|
"provider.source.config": "config",
|
|
259
|
+
"provider.contextWindow": "Context window",
|
|
260
|
+
"provider.contextDefault": "Default (catalog)",
|
|
261
|
+
"provider.contextHint": "Applies to models whose catalog entry declares no window; re-registers immediately",
|
|
262
|
+
"provider.contextCuratedTitle": "Window from the built-in curated table (models.dev cross-check), not declared by the platform; marked with *",
|
|
263
|
+
"section.draw": "Draw tool",
|
|
264
|
+
"draw.enable": "Register the modelscope_draw_image tool for the agent",
|
|
265
|
+
"draw.on": "On",
|
|
266
|
+
"draw.off": "Off",
|
|
267
|
+
"draw.busy": "Switching…",
|
|
268
|
+
"draw.mounted": "Registered: {tool}",
|
|
269
|
+
"draw.notMounted": "Not registered — tick the switch above.",
|
|
270
|
+
"draw.restartHint": "The switch saves at once, but the tool mounts at the NEXT Host start (a tool registration cannot be undone).",
|
|
271
|
+
"draw.models": "Draw calls will use the models below",
|
|
272
|
+
"draw.badge": "image",
|
|
273
|
+
"draw.noCandidates": "This catalogue declares no image model (no entry with image-to-image / text-to-image); drawing is unavailable.",
|
|
274
|
+
"draw.needsKey": "No token yet — configure it in the Access tab.",
|
|
275
|
+
"draw.reset": "Reset",
|
|
276
|
+
"draw.error": "Saving the draw settings failed: {error}",
|
|
277
|
+
"draw.source.panel": "panel",
|
|
278
|
+
"draw.source.config": "config",
|
|
279
|
+
"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).",
|
|
280
|
+
"draw.absent.peerLoad": "The switch is on, but the tools module failed to load; the draw tool is absent (a Host fault, not config).",
|
|
281
|
+
"draw.absent.registry": "The switch is on, but the Host refused the tool registration; the draw tool is absent (a Host fault, not config).",
|
|
282
|
+
"draw.absent.unknown": "The switch is on, but the draw tool is not mounted; the reason is unknown.",
|
|
197
283
|
"trend.none": "No local call records yet.",
|
|
198
284
|
"trend.legend": "Bar lengths are relative to the maximum in the range (local scope only).",
|
|
199
285
|
"events.none": "No events.",
|
|
200
286
|
"events.quota": "Quota",
|
|
201
287
|
"events.rate_limit": "Rate limit",
|
|
202
288
|
"events.error": "Error",
|
|
289
|
+
"events.unknown": "Unknown",
|
|
203
290
|
"token.status": "Status: {present} (source {source}, check {valid})",
|
|
204
291
|
"token.save": "Save",
|
|
205
292
|
"token.forget": "Forget saved",
|
|
206
293
|
"token.placeholder": "Paste an ms-… access token",
|
|
294
|
+
"token.ariaLabel": "ModelScope access token",
|
|
207
295
|
"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
296
|
"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.",
|
|
297
|
+
"token.readError": "Could not read the credential store, so the token state is unknown (this does NOT mean no token is configured): {error}",
|
|
209
298
|
"token.link": "Open the ModelScope access-token page →",
|
|
210
299
|
"source.credentials": "credentials",
|
|
211
300
|
"source.env": "environment",
|
|
@@ -214,7 +303,13 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
214
303
|
"present.yes": "configured",
|
|
215
304
|
"present.no": "not configured",
|
|
216
305
|
"validity.yes": "verified",
|
|
217
|
-
"validity.no": "unverified"
|
|
306
|
+
"validity.no": "unverified",
|
|
307
|
+
"validity.bad": "invalid",
|
|
308
|
+
"composer.prefix": "ModelScope",
|
|
309
|
+
"composer.pillTitle": "ModelScope quota: official Magicube balance + today's local call count",
|
|
310
|
+
"composer.balanceUnit": "Magicubes",
|
|
311
|
+
"composer.localCalls": "local today {calls}",
|
|
312
|
+
"composer.stale": "stale data"
|
|
218
313
|
};
|
|
219
314
|
}));
|
|
220
315
|
|
|
@@ -248,7 +343,7 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
248
343
|
if (number >= 1e4) return Math.round(number).toLocaleString();
|
|
249
344
|
return String(Math.round(number * 100) / 100);
|
|
250
345
|
}
|
|
251
|
-
/** 填 `{
|
|
346
|
+
/** 填 `{key}` 模板:vars 的每个键对应模板里的同名占位符。 */
|
|
252
347
|
function format(template, vars) {
|
|
253
348
|
let text = template;
|
|
254
349
|
for (const [key, value] of Object.entries(vars || {})) text = text.split(`{${key}}`).join(String(value));
|
|
@@ -350,7 +445,7 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
350
445
|
fontSize: 13,
|
|
351
446
|
color: "var(--dsw-alias-label-secondary)",
|
|
352
447
|
cursor: "pointer",
|
|
353
|
-
|
|
448
|
+
outlineOffset: -2
|
|
354
449
|
},
|
|
355
450
|
tabActive: {
|
|
356
451
|
color: "var(--dsw-alias-label-primary)",
|
|
@@ -632,7 +727,43 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
632
727
|
borderRadius: 12,
|
|
633
728
|
background: "var(--dsw-alias-bg-layer-1)",
|
|
634
729
|
padding: "12px 14px"
|
|
635
|
-
}
|
|
730
|
+
},
|
|
731
|
+
pillTrigger: {
|
|
732
|
+
appearance: "none",
|
|
733
|
+
WebkitAppearance: "none",
|
|
734
|
+
height: 28,
|
|
735
|
+
borderStyle: "none",
|
|
736
|
+
borderWidth: 0,
|
|
737
|
+
background: "none",
|
|
738
|
+
color: "inherit",
|
|
739
|
+
display: "inline-flex",
|
|
740
|
+
alignItems: "center",
|
|
741
|
+
gap: 6,
|
|
742
|
+
padding: "0 10px",
|
|
743
|
+
borderRadius: 999,
|
|
744
|
+
fontSize: 12,
|
|
745
|
+
fontWeight: 600,
|
|
746
|
+
lineHeight: "18px",
|
|
747
|
+
cursor: "default",
|
|
748
|
+
flexShrink: 0,
|
|
749
|
+
whiteSpace: "nowrap"
|
|
750
|
+
},
|
|
751
|
+
pillDot: {
|
|
752
|
+
width: 8,
|
|
753
|
+
height: 8,
|
|
754
|
+
borderRadius: 999,
|
|
755
|
+
flex: "none",
|
|
756
|
+
background: "var(--dsw-alias-label-secondary)"
|
|
757
|
+
},
|
|
758
|
+
pillPlan: { opacity: .72 },
|
|
759
|
+
pillStale: {
|
|
760
|
+
opacity: .62,
|
|
761
|
+
fontStyle: "italic"
|
|
762
|
+
},
|
|
763
|
+
pillTierOk: "#22c55e",
|
|
764
|
+
pillTierExhausted: "#ef4444",
|
|
765
|
+
pillTierUnknown: "#9ca3af",
|
|
766
|
+
pillTierExhaustedText: { color: "var(--dsw-alias-state-error-primary)" }
|
|
636
767
|
};
|
|
637
768
|
}));
|
|
638
769
|
|
|
@@ -676,7 +807,8 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
676
807
|
source: "none",
|
|
677
808
|
valid: null,
|
|
678
809
|
checkedAt: null,
|
|
679
|
-
ephemeral: true
|
|
810
|
+
ephemeral: true,
|
|
811
|
+
readError: null
|
|
680
812
|
};
|
|
681
813
|
case "balance": return {
|
|
682
814
|
available: null,
|
|
@@ -686,7 +818,7 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
686
818
|
error: null
|
|
687
819
|
};
|
|
688
820
|
case "quota": return {
|
|
689
|
-
daily: { usedLocal:
|
|
821
|
+
daily: { usedLocal: null },
|
|
690
822
|
perModel: [],
|
|
691
823
|
countingNote: "local-counting"
|
|
692
824
|
};
|
|
@@ -729,17 +861,23 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
729
861
|
*/
|
|
730
862
|
function interpretSnapshot(body) {
|
|
731
863
|
const payload = body;
|
|
732
|
-
if (payload && payload.ok === false)
|
|
864
|
+
if (payload && payload.ok === false) {
|
|
865
|
+
const code = typeof payload.code === "string" && payload.code !== "" ? payload.code : CLIENT_CODE.PAYLOAD_ERROR;
|
|
866
|
+
return {
|
|
867
|
+
data: null,
|
|
868
|
+
error: {
|
|
869
|
+
message: typeof payload.error === "string" && payload.error !== "" ? payload.error : null,
|
|
870
|
+
code
|
|
871
|
+
}
|
|
872
|
+
};
|
|
873
|
+
}
|
|
874
|
+
if (!payload || payload.ok !== true) return {
|
|
733
875
|
data: null,
|
|
734
876
|
error: {
|
|
735
|
-
message:
|
|
736
|
-
code:
|
|
877
|
+
message: null,
|
|
878
|
+
code: CLIENT_CODE.PAYLOAD_ERROR
|
|
737
879
|
}
|
|
738
880
|
};
|
|
739
|
-
if (!payload || payload.ok !== true) return {
|
|
740
|
-
data: null,
|
|
741
|
-
error: "unexpected payload"
|
|
742
|
-
};
|
|
743
881
|
const raw = payload;
|
|
744
882
|
const missingKeys = SNAPSHOT_REQUIRED_KEYS.filter((key) => raw[key] === void 0);
|
|
745
883
|
return {
|
|
@@ -752,19 +890,23 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
752
890
|
* 把 wire/路由返回的 provider 块归一成 `ProviderStatus`;不合法一律降级。
|
|
753
891
|
* 形状由 wire.ts 钉死,本函数只做防御性归一(旧 Host 可能缺字段)。
|
|
754
892
|
*/
|
|
755
|
-
function providerOf(raw) {
|
|
893
|
+
function providerOf$1(raw) {
|
|
756
894
|
if (raw === null || typeof raw !== "object") return DEGRADED_PROVIDER;
|
|
757
895
|
const p = raw;
|
|
758
896
|
const ids = Array.isArray(p.enabledIds) ? p.enabledIds.map((id) => String(id ?? "")).filter((id) => id !== "") : [];
|
|
759
897
|
const roster = Array.isArray(p.roster) ? p.roster.map((entry) => {
|
|
760
898
|
const row = entry ?? {};
|
|
761
899
|
const id = String(row.id ?? "");
|
|
900
|
+
const w = row.contextWindow;
|
|
901
|
+
const contextSource = row.contextSource === "override" || row.contextSource === "curated" ? row.contextSource : "catalog";
|
|
762
902
|
return {
|
|
763
903
|
id,
|
|
764
904
|
name: typeof row.name === "string" && row.name !== "" ? row.name : id,
|
|
765
905
|
vision: row.vision === true,
|
|
766
906
|
available: row.available !== false,
|
|
767
|
-
quotaExhausted: row.quotaExhausted === true
|
|
907
|
+
quotaExhausted: row.quotaExhausted === true,
|
|
908
|
+
contextSource,
|
|
909
|
+
...typeof w === "number" && Number.isFinite(w) && w > 0 ? { contextWindow: w } : {}
|
|
768
910
|
};
|
|
769
911
|
}).filter((entry) => entry.id !== "") : [];
|
|
770
912
|
return {
|
|
@@ -773,10 +915,11 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
773
915
|
llmAvailable: p.llmAvailable === true,
|
|
774
916
|
registered: p.registered === true,
|
|
775
917
|
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,
|
|
918
|
+
modelCount: typeof p.modelCount === "number" && Number.isFinite(p.modelCount) ? p.modelCount : roster.length,
|
|
919
|
+
enabledCount: typeof p.enabledCount === "number" && Number.isFinite(p.enabledCount) ? p.enabledCount : 0,
|
|
778
920
|
allowed: p.allowed === "none" ? "none" : p.allowed === "list" ? "list" : "all",
|
|
779
921
|
enabledIds: ids,
|
|
922
|
+
contextWindow: typeof p.contextWindow === "number" && Number.isFinite(p.contextWindow) && p.contextWindow > 0 ? p.contextWindow : null,
|
|
780
923
|
roster
|
|
781
924
|
};
|
|
782
925
|
}
|
|
@@ -789,6 +932,33 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
789
932
|
return `HTTP ${status}`;
|
|
790
933
|
}
|
|
791
934
|
/**
|
|
935
|
+
* 把 wire/路由返回的出图块归一成 `DrawStatus`;不合法一律降级。
|
|
936
|
+
*
|
|
937
|
+
* 与 `providerOf` 同一套防御性归一(旧 Host 可能缺字段)。`absentReason` 只认三个
|
|
938
|
+
* 已知码——上游若新增第四个,归一到 `null` 而不是把它透传成渲染不出的值;面板的
|
|
939
|
+
* 「缺席」措辞有兜底键,但**状态本身**宁可显示「未知」也不假装认识它。
|
|
940
|
+
* @param {unknown} raw - 原始块。
|
|
941
|
+
* @returns {DrawStatus} 归一后的状态。
|
|
942
|
+
*/
|
|
943
|
+
function drawOf(raw) {
|
|
944
|
+
if (raw === null || typeof raw !== "object") return DEGRADED_DRAW;
|
|
945
|
+
const d = raw;
|
|
946
|
+
const candidates = Array.isArray(d.candidateIds) ? d.candidateIds.map((id) => String(id ?? "")).filter((id) => id !== "") : [];
|
|
947
|
+
const reason = d.absentReason;
|
|
948
|
+
const absentReason = reason === "no-tools-service" || reason === "peer-load-failed" || reason === "registry-refused" ? reason : null;
|
|
949
|
+
return {
|
|
950
|
+
enabled: d.enabled === true,
|
|
951
|
+
source: d.source === "panel" ? "panel" : "config",
|
|
952
|
+
candidateIds: candidates,
|
|
953
|
+
candidateCount: typeof d.candidateCount === "number" && Number.isFinite(d.candidateCount) ? d.candidateCount : candidates.length,
|
|
954
|
+
preferredModel: typeof d.preferredModel === "string" && d.preferredModel !== "" ? d.preferredModel : null,
|
|
955
|
+
preferredSource: d.preferredSource === "panel" ? "panel" : "config",
|
|
956
|
+
absentReason,
|
|
957
|
+
mountPending: d.mountPending === true,
|
|
958
|
+
toolName: typeof d.toolName === "string" && d.toolName !== "" ? d.toolName : "modelscope_draw_image"
|
|
959
|
+
};
|
|
960
|
+
}
|
|
961
|
+
/**
|
|
792
962
|
* 面板决策:这张快照意味着什么。纯函数,Node 套件驱动同一个函数。
|
|
793
963
|
*
|
|
794
964
|
* `missingKeys` 是 `interpretSnapshot` 查出的缺失顶层键(非空 = 双端契约漂移)。
|
|
@@ -805,7 +975,7 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
805
975
|
const guidanceKey = failure === null ? null : GUIDANCE_BY_CODE[failure.code] ?? null;
|
|
806
976
|
const guidance = guidanceKey === null ? null : guidanceKey === "panel.configError" ? format(tt(guidanceKey), { error: failure?.message }) : tt(guidanceKey);
|
|
807
977
|
const shapeWarnings = Array.isArray(data?.shapeWarnings) ? [...data.shapeWarnings] : [];
|
|
808
|
-
if (missingKeys.length > 0) shapeWarnings.push(
|
|
978
|
+
if (missingKeys.length > 0) shapeWarnings.push(format(tt("shape.missingKeys"), { keys: missingKeys.join(", ") }));
|
|
809
979
|
return {
|
|
810
980
|
failure,
|
|
811
981
|
needsSetup,
|
|
@@ -814,10 +984,11 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
814
984
|
shapeWarnings
|
|
815
985
|
};
|
|
816
986
|
}
|
|
817
|
-
var DEGRADED_PROVIDER, GUIDANCE_BY_CODE, FORM_EXCLUDED_CODES;
|
|
987
|
+
var CLIENT_CODE, DEGRADED_PROVIDER, DEGRADED_DRAW, GUIDANCE_BY_CODE, FORM_EXCLUDED_CODES;
|
|
818
988
|
var init_snapshot = __esmMin((() => {
|
|
819
989
|
init_format();
|
|
820
990
|
init_wire();
|
|
991
|
+
CLIENT_CODE = Object.freeze({ PAYLOAD_ERROR: "payload_error" });
|
|
821
992
|
DEGRADED_PROVIDER = Object.freeze({
|
|
822
993
|
enabled: false,
|
|
823
994
|
source: "config",
|
|
@@ -828,8 +999,20 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
828
999
|
enabledCount: 0,
|
|
829
1000
|
allowed: "all",
|
|
830
1001
|
enabledIds: [],
|
|
1002
|
+
contextWindow: null,
|
|
831
1003
|
roster: []
|
|
832
1004
|
});
|
|
1005
|
+
DEGRADED_DRAW = Object.freeze({
|
|
1006
|
+
enabled: false,
|
|
1007
|
+
source: "config",
|
|
1008
|
+
candidateIds: [],
|
|
1009
|
+
candidateCount: 0,
|
|
1010
|
+
preferredModel: null,
|
|
1011
|
+
preferredSource: "config",
|
|
1012
|
+
absentReason: null,
|
|
1013
|
+
mountPending: false,
|
|
1014
|
+
toolName: "modelscope_draw_image"
|
|
1015
|
+
});
|
|
833
1016
|
GUIDANCE_BY_CODE = Object.freeze({
|
|
834
1017
|
auth_error: "panel.authError",
|
|
835
1018
|
config_error: "panel.configError",
|
|
@@ -838,7 +1021,8 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
838
1021
|
upstream_error: "panel.upstream",
|
|
839
1022
|
rate_limited: "panel.upstream",
|
|
840
1023
|
quota_exceeded: "panel.upstream",
|
|
841
|
-
internal_error: "panel.internalError"
|
|
1024
|
+
internal_error: "panel.internalError",
|
|
1025
|
+
payload_error: "panel.payloadError"
|
|
842
1026
|
});
|
|
843
1027
|
FORM_EXCLUDED_CODES = Object.freeze(/* @__PURE__ */ new Set([
|
|
844
1028
|
"config_error",
|
|
@@ -847,7 +1031,8 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
847
1031
|
"upstream_error",
|
|
848
1032
|
"rate_limited",
|
|
849
1033
|
"quota_exceeded",
|
|
850
|
-
"internal_error"
|
|
1034
|
+
"internal_error",
|
|
1035
|
+
"payload_error"
|
|
851
1036
|
]));
|
|
852
1037
|
}));
|
|
853
1038
|
|
|
@@ -899,8 +1084,8 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
899
1084
|
inset: 0,
|
|
900
1085
|
borderRadius: 999,
|
|
901
1086
|
pointerEvents: "none",
|
|
902
|
-
border: `1px solid ${on ?
|
|
903
|
-
background: on ?
|
|
1087
|
+
border: `1px solid ${on ? BRAND : "var(--dsw-alias-border-l2, #36373b)"}`,
|
|
1088
|
+
background: on ? BRAND : "var(--dsw-alias-bg-layer-2, #2a2b31)",
|
|
904
1089
|
transition: "background .15s, border-color .15s"
|
|
905
1090
|
}
|
|
906
1091
|
}, h("span", { style: {
|
|
@@ -921,6 +1106,7 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
921
1106
|
var TRACK_W, TRACK_H, THUMB, TRAVEL;
|
|
922
1107
|
var init_toggle_switch = __esmMin((() => {
|
|
923
1108
|
init_runtime();
|
|
1109
|
+
init_styles();
|
|
924
1110
|
TRACK_W = 30;
|
|
925
1111
|
TRACK_H = 17;
|
|
926
1112
|
THUMB = 12;
|
|
@@ -982,12 +1168,17 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
982
1168
|
*/
|
|
983
1169
|
function LocalDailyCard({ snapshot, tt }) {
|
|
984
1170
|
const quota = snapshot.quota;
|
|
985
|
-
const daily = quota?.daily ?? { usedLocal:
|
|
1171
|
+
const daily = quota?.daily ?? { usedLocal: null };
|
|
986
1172
|
const perModel = Array.isArray(quota?.perModel) ? quota.perModel : [];
|
|
1173
|
+
const unknownUsed = !(typeof daily.usedLocal === "number" && Number.isFinite(daily.usedLocal));
|
|
1174
|
+
const usedText = unknownUsed ? "—" : count(daily.usedLocal);
|
|
987
1175
|
return h("div", { style: S.card }, h("div", { style: S.poolName }, format(tt("quota.headline"), {
|
|
988
|
-
calls:
|
|
1176
|
+
calls: usedText,
|
|
989
1177
|
models: count(perModel.length)
|
|
990
|
-
})), h(
|
|
1178
|
+
})), unknownUsed ? h("div", { style: {
|
|
1179
|
+
...S.formNote,
|
|
1180
|
+
color: "var(--dsw-alias-state-warn-primary)"
|
|
1181
|
+
} }, tt("quota.unavailable")) : null, h(ModelUsageTable, {
|
|
991
1182
|
rows: perModel,
|
|
992
1183
|
tt
|
|
993
1184
|
}), h("div", { style: S.trendLegend }, tt("quota.note")));
|
|
@@ -1019,21 +1210,25 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
1019
1210
|
/** 近 N 天本地调用趋势(天桶;缺桶 = 该日 0 次,真实零)。 */
|
|
1020
1211
|
function TrendBars({ buckets, tt }) {
|
|
1021
1212
|
if (!Array.isArray(buckets) || buckets.length === 0) return h("div", { style: S.trendLegend }, tt("trend.none"));
|
|
1022
|
-
const
|
|
1023
|
-
|
|
1024
|
-
|
|
1213
|
+
const rows = buckets.map((bucket) => ({
|
|
1214
|
+
dateKey: bucket.dateKey,
|
|
1215
|
+
calls: Number.isFinite(bucket.calls) ? Math.max(0, bucket.calls) : 0
|
|
1216
|
+
}));
|
|
1217
|
+
const max = Math.max(0, ...rows.map((row) => row.calls));
|
|
1218
|
+
return h("div", null, rows.map((row) => h("div", {
|
|
1219
|
+
key: row.dateKey,
|
|
1025
1220
|
style: S.trendRowHead
|
|
1026
|
-
}, h("span", { style: S.trendModel },
|
|
1221
|
+
}, h("span", { style: S.trendModel }, row.dateKey), h("div", {
|
|
1027
1222
|
style: {
|
|
1028
1223
|
...S.trendBar,
|
|
1029
1224
|
flex: "1",
|
|
1030
1225
|
marginLeft: 10
|
|
1031
1226
|
},
|
|
1032
1227
|
"aria-hidden": "true"
|
|
1033
|
-
}, max > 0 &&
|
|
1228
|
+
}, max > 0 && row.calls > 0 ? h("div", { style: {
|
|
1034
1229
|
...S.barFill,
|
|
1035
|
-
width: `${
|
|
1036
|
-
} }) : null), h("span", { style: S.quotaUsed }, String(
|
|
1230
|
+
width: `${row.calls / max * 100}%`
|
|
1231
|
+
} }) : null), h("span", { style: S.quotaUsed }, String(row.calls)))), h("div", { style: S.trendLegend }, tt("trend.legend")));
|
|
1037
1232
|
}
|
|
1038
1233
|
/** 事件流(429/错误),Newest-first。 */
|
|
1039
1234
|
function EventsList({ events, tt }) {
|
|
@@ -1041,7 +1236,7 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
1041
1236
|
return h("div", null, events.map((event, index) => h("div", {
|
|
1042
1237
|
key: `${event.at}-${index}`,
|
|
1043
1238
|
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", {
|
|
1239
|
+
}, h("span", { style: S.quotaUsed }, isoTime(event.at)), h("span", { style: S.modelTag }, tt(EVENT_KIND_KEY[event.kind] ?? "events.unknown")), h("span", {
|
|
1045
1240
|
style: {
|
|
1046
1241
|
...S.trendModel,
|
|
1047
1242
|
flex: "1"
|
|
@@ -1054,8 +1249,11 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
1054
1249
|
const [value, setValue] = useState("");
|
|
1055
1250
|
const source = token?.source ?? "none";
|
|
1056
1251
|
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:
|
|
1252
|
+
const validKey = token?.valid === true ? "validity.yes" : token?.valid === false ? "validity.bad" : "validity.no";
|
|
1253
|
+
return h("div", null, token?.readError ? h("div", { style: {
|
|
1254
|
+
...S.formNote,
|
|
1255
|
+
color: "var(--dsw-alias-state-warn-primary)"
|
|
1256
|
+
} }, format(tt("token.readError"), { error: token.readError })) : h("div", { style: S.quotaUsed }, format(tt("token.status"), {
|
|
1059
1257
|
present: token?.present ? tt("present.yes") : tt("present.no"),
|
|
1060
1258
|
source: tt(sourceKey),
|
|
1061
1259
|
valid: tt(validKey)
|
|
@@ -1063,6 +1261,7 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
1063
1261
|
style: S.input,
|
|
1064
1262
|
type: "password",
|
|
1065
1263
|
placeholder: tt("token.placeholder"),
|
|
1264
|
+
"aria-label": tt("token.ariaLabel"),
|
|
1066
1265
|
value,
|
|
1067
1266
|
onChange: (event) => setValue(String(event.target.value ?? ""))
|
|
1068
1267
|
}), h("button", {
|
|
@@ -1109,25 +1308,30 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
1109
1308
|
*
|
|
1110
1309
|
* 允许清单的语义(路由 §9 钉死):空清单 = 不过滤 = 全部提供,所以「全部」
|
|
1111
1310
|
* 发 `[]`;「全部隐藏」发哨兵 `HIDE_ALL_MODELS`(「什么都不提供」)。哨兵
|
|
1112
|
-
*
|
|
1113
|
-
*
|
|
1311
|
+
* 是**独占态**:draft 含哨兵时整个清单就是「全部隐藏」——勾一个真实模型会
|
|
1312
|
+
* 退出哨兵态(剔除哨兵),Host 落盘值带哨兵时只保留哨兵。**不能**让哨兵与
|
|
1313
|
+
* 真实 id 混排:Host 的 resolveAllowedList 判「含哨兵即 none(全隐藏)」,而
|
|
1314
|
+
* 面板若把混排清单里的真实 id 画成勾中、计数为 N,UI 就与落盘撒谎了。
|
|
1315
|
+
* 曾踩过:点「全部隐藏」后复选框仍可勾,勾出的混排清单保存后「已启用 1/N」
|
|
1316
|
+
* 而实际提供 0 个。
|
|
1114
1317
|
*
|
|
1115
1318
|
* 额度耗尽的模型保留在清单里(灰显、勾选框禁用),这是故意的:roster 是目录
|
|
1116
1319
|
* 事实,picker 那边由 Host 自己丢掉它们,面板不替它删。
|
|
1117
1320
|
*/
|
|
1118
|
-
function ProviderCard({ provider, busy, error, onToggle, onSaveList, onReset, tokenPresent, tt }) {
|
|
1119
|
-
const status = providerOf(provider);
|
|
1321
|
+
function ProviderCard({ provider, busy, error, onToggle, onSaveList, onSaveContextWindow, onReset, tokenPresent, tt }) {
|
|
1322
|
+
const status = providerOf$1(provider);
|
|
1120
1323
|
const enabled = status.enabled === true;
|
|
1121
1324
|
const hostIds = status.enabledIds;
|
|
1122
1325
|
const hostKey = useMemo(() => JSON.stringify(hostIds), [hostIds]);
|
|
1123
1326
|
const [draft, setDraft] = useState(() => hostIds.slice());
|
|
1327
|
+
const hideAllMode = draft.includes(HIDE_ALL_MODELS);
|
|
1124
1328
|
useEffect(() => {
|
|
1125
|
-
setDraft(hostIds);
|
|
1329
|
+
setDraft(hostIds.includes("__hide_all__") ? [HIDE_ALL_MODELS] : hostIds);
|
|
1126
1330
|
}, [hostKey]);
|
|
1127
1331
|
const roster = status.roster;
|
|
1128
1332
|
const rosterIds = roster.map((row) => row.id);
|
|
1129
|
-
const toggleOne = (id) => setDraft((current) => current.includes(id) ? current.filter((x) => x !== id) : [...current, id]);
|
|
1130
|
-
const ticked = roster.filter((row) => draft.includes(row.id)).length;
|
|
1333
|
+
const toggleOne = (id) => setDraft((current) => current.includes(id) ? current.filter((x) => x !== id) : [...current.filter((x) => x !== HIDE_ALL_MODELS), id]);
|
|
1334
|
+
const ticked = hideAllMode ? 0 : roster.filter((row) => draft.includes(row.id)).length;
|
|
1131
1335
|
const degraded = status.error === "unavailable";
|
|
1132
1336
|
let statusNode;
|
|
1133
1337
|
if (!degraded && typeof status.error === "string" && status.error !== "") statusNode = h("div", {
|
|
@@ -1157,6 +1361,61 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
1157
1361
|
color: "var(--dsw-alias-state-warn-primary)"
|
|
1158
1362
|
} }, tt("provider.notConfigured")) : null;
|
|
1159
1363
|
const sourceKey = status.source === "panel" ? "provider.source.panel" : "provider.source.config";
|
|
1364
|
+
const CONTEXT_TIERS = [
|
|
1365
|
+
{
|
|
1366
|
+
value: 131072,
|
|
1367
|
+
label: "128K"
|
|
1368
|
+
},
|
|
1369
|
+
{
|
|
1370
|
+
value: 262144,
|
|
1371
|
+
label: "256K"
|
|
1372
|
+
},
|
|
1373
|
+
{
|
|
1374
|
+
value: 524288,
|
|
1375
|
+
label: "512K"
|
|
1376
|
+
},
|
|
1377
|
+
{
|
|
1378
|
+
value: 1048576,
|
|
1379
|
+
label: "1M"
|
|
1380
|
+
}
|
|
1381
|
+
];
|
|
1382
|
+
const contextNode = onSaveContextWindow === void 0 ? null : h("div", { style: {
|
|
1383
|
+
display: "flex",
|
|
1384
|
+
gap: 8,
|
|
1385
|
+
alignItems: "center",
|
|
1386
|
+
flexWrap: "wrap",
|
|
1387
|
+
margin: "0 0 8px"
|
|
1388
|
+
} }, h("label", {
|
|
1389
|
+
style: {
|
|
1390
|
+
fontSize: 12,
|
|
1391
|
+
color: "var(--dsw-alias-label-secondary)"
|
|
1392
|
+
},
|
|
1393
|
+
htmlFor: "provider-context-window"
|
|
1394
|
+
}, tt("provider.contextWindow")), h("select", {
|
|
1395
|
+
id: "provider-context-window",
|
|
1396
|
+
style: {
|
|
1397
|
+
...S.rosterBulk,
|
|
1398
|
+
width: "auto"
|
|
1399
|
+
},
|
|
1400
|
+
disabled: busy,
|
|
1401
|
+
value: status.contextWindow === null ? "" : String(status.contextWindow),
|
|
1402
|
+
onChange: (event) => {
|
|
1403
|
+
const value = String(event.target?.value ?? "");
|
|
1404
|
+
onSaveContextWindow(value === "" ? null : Number(value));
|
|
1405
|
+
}
|
|
1406
|
+
}, h("option", {
|
|
1407
|
+
value: "",
|
|
1408
|
+
key: "default"
|
|
1409
|
+
}, tt("provider.contextDefault")), CONTEXT_TIERS.map((tier) => h("option", {
|
|
1410
|
+
value: String(tier.value),
|
|
1411
|
+
key: String(tier.value)
|
|
1412
|
+
}, tier.label))), h("span", {
|
|
1413
|
+
style: {
|
|
1414
|
+
...S.muted,
|
|
1415
|
+
fontSize: 11
|
|
1416
|
+
},
|
|
1417
|
+
title: tt("provider.contextHint")
|
|
1418
|
+
}, tt("provider.contextHint")));
|
|
1160
1419
|
const switchRow = h("div", { style: {
|
|
1161
1420
|
display: "flex",
|
|
1162
1421
|
gap: 10,
|
|
@@ -1168,6 +1427,7 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
1168
1427
|
onChange: () => onToggle(!enabled),
|
|
1169
1428
|
busy,
|
|
1170
1429
|
label: tt(enabled ? "provider.on" : "provider.off"),
|
|
1430
|
+
busyLabel: tt("provider.busy"),
|
|
1171
1431
|
title: tt("provider.enable")
|
|
1172
1432
|
}), h("span", { style: {
|
|
1173
1433
|
fontSize: 11,
|
|
@@ -1228,7 +1488,7 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
1228
1488
|
cursor: busy || unusable ? "default" : "pointer"
|
|
1229
1489
|
} }, h("input", {
|
|
1230
1490
|
type: "checkbox",
|
|
1231
|
-
checked: draft.includes(id),
|
|
1491
|
+
checked: !hideAllMode && draft.includes(id),
|
|
1232
1492
|
disabled: busy || unusable,
|
|
1233
1493
|
onChange: () => toggleOne(id),
|
|
1234
1494
|
style: S.modelCheck,
|
|
@@ -1236,7 +1496,13 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
1236
1496
|
}), h("span", {
|
|
1237
1497
|
style: S.modelName,
|
|
1238
1498
|
title: id
|
|
1239
|
-
}, row.name)), row.vision === true ? h("span", { style: S.modelBadge }, tt("provider.vision")) : null,
|
|
1499
|
+
}, row.name)), row.vision === true ? h("span", { style: S.modelBadge }, tt("provider.vision")) : null, typeof row.contextWindow === "number" ? h("span", {
|
|
1500
|
+
style: row.contextSource === "curated" ? {
|
|
1501
|
+
...S.modelBadge,
|
|
1502
|
+
color: "var(--dsw-alias-label-secondary)"
|
|
1503
|
+
} : S.modelBadge,
|
|
1504
|
+
title: row.contextSource === "curated" ? tt("provider.contextCuratedTitle") : tt("provider.contextWindow")
|
|
1505
|
+
}, row.contextSource === "curated" ? formatWindow(row.contextWindow) + " *" : formatWindow(row.contextWindow)) : null, FEATURED_OWNERS.includes(catalogOwner(row.id)) ? h("span", {
|
|
1240
1506
|
style: S.modelBadgeFeatured,
|
|
1241
1507
|
title: tt("models.featuredTitle")
|
|
1242
1508
|
}, tt("provider.featured")) : null, row.quotaExhausted === true ? h("span", { style: {
|
|
@@ -1259,7 +1525,7 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
1259
1525
|
disabled: busy,
|
|
1260
1526
|
onClick: onReset
|
|
1261
1527
|
}, tt("provider.reset"))));
|
|
1262
|
-
return h("div", null, switchRow, statusNode, tokenHint, h("div", { style: {
|
|
1528
|
+
return h("div", null, switchRow, statusNode, tokenHint, contextNode, h("div", { style: {
|
|
1263
1529
|
...S.sectionTitle,
|
|
1264
1530
|
margin: "18px 0 8px"
|
|
1265
1531
|
} }, tt("provider.models")), rosterBlock, error !== null ? h("div", {
|
|
@@ -1267,7 +1533,114 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
1267
1533
|
role: "alert"
|
|
1268
1534
|
}, error) : null);
|
|
1269
1535
|
}
|
|
1270
|
-
|
|
1536
|
+
/**
|
|
1537
|
+
* 出图工具卡:开关、候选模型、挂载状态。
|
|
1538
|
+
*
|
|
1539
|
+
* **与 ProviderCard 的关键差异,也是这张卡存在的主要理由**:provider 开关翻转会
|
|
1540
|
+
* 立即重注册,而出图工具的开关翻转**要等下次 Host 启动**——peer 的 tools registry
|
|
1541
|
+
* 没有 unregister。所以这张卡必须把这件事说出来(`draw.restartHint`),否则用户
|
|
1542
|
+
* 翻开关后看不到任何变化,会以为坏了。
|
|
1543
|
+
*
|
|
1544
|
+
* 缺席原因是**分开渲染**的:`no-tools-service` 是「这个 Host 没提供该服务」(正常),
|
|
1545
|
+
* 另两种是 Host 缺陷。三种都给出各自的一句话,因为「开关开着但工具不在」如果没有
|
|
1546
|
+
* 解释,用户没有任何办法自己诊断。
|
|
1547
|
+
* @param {object} props - 渲染参数。
|
|
1548
|
+
* @returns {unknown} 卡片节点。
|
|
1549
|
+
*/
|
|
1550
|
+
function DrawCard({ draw, busy, error, onToggle, onReset, tokenPresent, tt }) {
|
|
1551
|
+
const status = drawOf(draw);
|
|
1552
|
+
const enabled = status.enabled === true;
|
|
1553
|
+
const candidates = status.candidateIds;
|
|
1554
|
+
const sourceKey = status.source === "panel" ? "draw.source.panel" : "draw.source.config";
|
|
1555
|
+
const switchRow = h("div", { style: {
|
|
1556
|
+
display: "flex",
|
|
1557
|
+
gap: 10,
|
|
1558
|
+
alignItems: "center",
|
|
1559
|
+
flexWrap: "wrap",
|
|
1560
|
+
margin: "0 0 8px"
|
|
1561
|
+
} }, h(ToggleSwitch, {
|
|
1562
|
+
checked: enabled,
|
|
1563
|
+
onChange: () => onToggle(!enabled),
|
|
1564
|
+
busy,
|
|
1565
|
+
label: tt(enabled ? "draw.on" : "draw.off"),
|
|
1566
|
+
busyLabel: tt("draw.busy"),
|
|
1567
|
+
title: tt("draw.enable")
|
|
1568
|
+
}), h("span", { style: {
|
|
1569
|
+
fontSize: 11,
|
|
1570
|
+
color: "var(--dsw-alias-label-secondary)"
|
|
1571
|
+
} }, tt(sourceKey)));
|
|
1572
|
+
let statusNode;
|
|
1573
|
+
if (status.absentReason !== null) {
|
|
1574
|
+
const key = DRAW_ABSENT_KEY[status.absentReason] ?? "draw.absent.unknown";
|
|
1575
|
+
const severe = status.absentReason !== "no-tools-service";
|
|
1576
|
+
statusNode = h("div", {
|
|
1577
|
+
style: severe ? {
|
|
1578
|
+
...S.formNote,
|
|
1579
|
+
color: "var(--dsw-alias-state-warn-primary)"
|
|
1580
|
+
} : S.formNote,
|
|
1581
|
+
role: "status"
|
|
1582
|
+
}, tt(key));
|
|
1583
|
+
} else if (enabled) statusNode = h("div", {
|
|
1584
|
+
style: {
|
|
1585
|
+
fontSize: 12,
|
|
1586
|
+
color: "var(--dsw-alias-label-secondary)"
|
|
1587
|
+
},
|
|
1588
|
+
role: "status"
|
|
1589
|
+
}, format(tt("draw.mounted"), { tool: status.toolName }));
|
|
1590
|
+
else statusNode = h("div", {
|
|
1591
|
+
style: {
|
|
1592
|
+
...S.muted,
|
|
1593
|
+
fontSize: 12
|
|
1594
|
+
},
|
|
1595
|
+
role: "status"
|
|
1596
|
+
}, tt("draw.notMounted"));
|
|
1597
|
+
const restartHint = enabled ? h("div", { style: {
|
|
1598
|
+
...S.formNote,
|
|
1599
|
+
color: "var(--dsw-alias-state-warn-primary)"
|
|
1600
|
+
} }, tt("draw.restartHint")) : null;
|
|
1601
|
+
const tokenHint = enabled && tokenPresent === false ? h("div", { style: {
|
|
1602
|
+
...S.formNote,
|
|
1603
|
+
color: "var(--dsw-alias-state-warn-primary)"
|
|
1604
|
+
} }, tt("draw.needsKey")) : null;
|
|
1605
|
+
const candidateBlock = candidates.length === 0 ? h("div", { style: {
|
|
1606
|
+
...S.muted,
|
|
1607
|
+
fontSize: 12,
|
|
1608
|
+
marginTop: 6
|
|
1609
|
+
} }, tt("draw.noCandidates")) : h("div", { style: {
|
|
1610
|
+
display: "flex",
|
|
1611
|
+
flexDirection: "column",
|
|
1612
|
+
gap: 4
|
|
1613
|
+
} }, candidates.map((id) => h("div", {
|
|
1614
|
+
key: id,
|
|
1615
|
+
style: S.modelRowHead
|
|
1616
|
+
}, h("code", {
|
|
1617
|
+
style: S.modelName,
|
|
1618
|
+
title: id
|
|
1619
|
+
}, id), h("span", { style: S.modelBadge }, tt("draw.badge")), h("a", {
|
|
1620
|
+
style: {
|
|
1621
|
+
...S.formNote,
|
|
1622
|
+
margin: 0,
|
|
1623
|
+
marginLeft: "auto"
|
|
1624
|
+
},
|
|
1625
|
+
href: modelscopeModelUrl(id),
|
|
1626
|
+
target: "_blank",
|
|
1627
|
+
rel: "noreferrer",
|
|
1628
|
+
title: tt("models.viewOnSiteTitle")
|
|
1629
|
+
}, tt("models.viewOnSite")))));
|
|
1630
|
+
return h("div", null, switchRow, statusNode, restartHint, tokenHint, h("div", { style: {
|
|
1631
|
+
...S.sectionTitle,
|
|
1632
|
+
margin: "18px 0 8px"
|
|
1633
|
+
} }, tt("draw.models")), candidateBlock, h("div", { style: S.rosterFoot }, h("button", {
|
|
1634
|
+
type: "button",
|
|
1635
|
+
style: S.button,
|
|
1636
|
+
disabled: busy,
|
|
1637
|
+
onClick: onReset
|
|
1638
|
+
}, tt("draw.reset"))), error !== null ? h("div", {
|
|
1639
|
+
style: S.formError,
|
|
1640
|
+
role: "alert"
|
|
1641
|
+
}, error) : null);
|
|
1642
|
+
}
|
|
1643
|
+
var EVENT_KIND_KEY, DRAW_ABSENT_KEY;
|
|
1271
1644
|
var init_cards = __esmMin((() => {
|
|
1272
1645
|
init_format();
|
|
1273
1646
|
init_runtime();
|
|
@@ -1280,13 +1653,38 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
1280
1653
|
rate_limit: "events.rate_limit",
|
|
1281
1654
|
error: "events.error"
|
|
1282
1655
|
};
|
|
1656
|
+
DRAW_ABSENT_KEY = {
|
|
1657
|
+
"no-tools-service": "draw.absent.noTools",
|
|
1658
|
+
"peer-load-failed": "draw.absent.peerLoad",
|
|
1659
|
+
"registry-refused": "draw.absent.registry"
|
|
1660
|
+
};
|
|
1283
1661
|
}));
|
|
1284
1662
|
|
|
1285
1663
|
//#endregion
|
|
1286
1664
|
//#region src/client/http.ts
|
|
1287
|
-
/**
|
|
1665
|
+
/** 一次带超时的 fetch;内部 controller,调用方无需传 signal。 */
|
|
1666
|
+
async function fetchWithTimeout(url, init) {
|
|
1667
|
+
const controller = typeof AbortController === "function" ? new AbortController() : null;
|
|
1668
|
+
const timer = controller === null ? null : setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
|
|
1669
|
+
try {
|
|
1670
|
+
return await fetch(url, {
|
|
1671
|
+
...init,
|
|
1672
|
+
...controller === null ? {} : { signal: controller.signal }
|
|
1673
|
+
});
|
|
1674
|
+
} finally {
|
|
1675
|
+
if (timer !== null) clearTimeout(timer);
|
|
1676
|
+
}
|
|
1677
|
+
}
|
|
1678
|
+
/**
|
|
1679
|
+
* POST 并解析 body;非 JSON 响应返回 null。
|
|
1680
|
+
*
|
|
1681
|
+
* 这里**故意不**检查 `response.ok`(与 {@link getJson} 不同):host 的写操作错误
|
|
1682
|
+
* 响应体是 `{ok:false, code, error}`,调用方靠 `body.error` 给用户看具体原因
|
|
1683
|
+
* (令牌格式错、额度耗尽、上游超时)。丢掉它就只能显示一句通用的「操作失败」,
|
|
1684
|
+
* 用户无从下手。getJson 可以直接返回 null——读操作没有错误详情可展示。
|
|
1685
|
+
*/
|
|
1288
1686
|
async function postJson(path, payload) {
|
|
1289
|
-
return await (await
|
|
1687
|
+
return await (await fetchWithTimeout(path, {
|
|
1290
1688
|
method: "POST",
|
|
1291
1689
|
headers: {
|
|
1292
1690
|
"content-type": "application/json",
|
|
@@ -1298,14 +1696,35 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
1298
1696
|
}
|
|
1299
1697
|
/** GET 同源 JSON;非 2xx 或非 JSON 返回 null(调用方决定怎么降级)。 */
|
|
1300
1698
|
async function getJson(path) {
|
|
1301
|
-
const response = await
|
|
1699
|
+
const response = await fetchWithTimeout(path, {
|
|
1302
1700
|
headers: { accept: "application/json" },
|
|
1303
1701
|
cache: "no-store"
|
|
1304
1702
|
});
|
|
1305
1703
|
if (!response.ok) return null;
|
|
1306
1704
|
return await response.json().catch(() => null);
|
|
1307
1705
|
}
|
|
1308
|
-
|
|
1706
|
+
/**
|
|
1707
|
+
* 写路径的成败判定收口:失败返回 Error,成功返回 null。
|
|
1708
|
+
*
|
|
1709
|
+
* `postJson` 只把「HTTP/解析失败」折成 `null`,域失败是 `{ok:false, error}`(Host 对
|
|
1710
|
+
* 写操作恒回 HTTP 200,成败看 body)。三个写回调曾各判一次,而**「忘掉令牌」漏判**——
|
|
1711
|
+
* 它直接 `await postJson(...)` 就往下走,于是 Host 侧刻意传播的失败(凭据文件只读时
|
|
1712
|
+
* `ms-auth.forget` 抛错,见其文件头「谎报成功比失败更糟」)在界面上表现为「什么都没
|
|
1713
|
+
* 发生」:没有错误行、busy 清掉、刷新后的快照里令牌还在。判据收在这里一份,三个调用
|
|
1714
|
+
* 方(开关/清单、保存令牌、忘掉令牌)不可能再各自漂移。
|
|
1715
|
+
* @param {ApiBody|null} body - postJson 的返回值。
|
|
1716
|
+
* @param {string} fallback - body 里没有可读原因时的一句话。
|
|
1717
|
+
* @returns {Error|null} 失败原因;`null` = 成功。
|
|
1718
|
+
*/
|
|
1719
|
+
function writeFailure(body, fallback) {
|
|
1720
|
+
if (body !== null && body.ok === true) return null;
|
|
1721
|
+
const reason = typeof body?.error === "string" && body.error.trim() !== "" ? body.error : fallback;
|
|
1722
|
+
return new Error(reason);
|
|
1723
|
+
}
|
|
1724
|
+
var FETCH_TIMEOUT_MS;
|
|
1725
|
+
var init_http = __esmMin((() => {
|
|
1726
|
+
FETCH_TIMEOUT_MS = 2e4;
|
|
1727
|
+
}));
|
|
1309
1728
|
|
|
1310
1729
|
//#endregion
|
|
1311
1730
|
//#region src/client/use-polling-interval.ts
|
|
@@ -1382,12 +1801,19 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
1382
1801
|
const [cadenceMs, setCadenceMs] = useState(defaultCadenceMs);
|
|
1383
1802
|
/** Host 少给的顶层键(契约漂移);透给面板当 shapeWarning,不静默。 */
|
|
1384
1803
|
const [missingKeys, setMissingKeys] = useState([]);
|
|
1804
|
+
/**
|
|
1805
|
+
* 每一次轮询尝试都加一,成功与失败都算。`updatedAt` 只在成功时变,而
|
|
1806
|
+
* 面板的 provider 兜底 GET 需要的是「下一轮还会再来」这个信号——快照本身
|
|
1807
|
+
* 失败的那一轮,恰好是兜底路径也应该再试的一轮。
|
|
1808
|
+
*/
|
|
1809
|
+
const [pollRevision, setPollRevision] = useState(0);
|
|
1385
1810
|
const generation = useRef(0);
|
|
1386
1811
|
const inFlight = useRef(null);
|
|
1387
1812
|
const cadenceRef = useRef(cadenceMs);
|
|
1388
1813
|
cadenceRef.current = cadenceMs;
|
|
1389
1814
|
const load = useCallback(async () => {
|
|
1390
1815
|
generation.current += 1;
|
|
1816
|
+
setPollRevision((revision) => revision + 1);
|
|
1391
1817
|
const mine = generation.current;
|
|
1392
1818
|
const isCurrent = () => generation.current === mine;
|
|
1393
1819
|
inFlight.current?.abort?.();
|
|
@@ -1438,7 +1864,8 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
1438
1864
|
loadedOnce,
|
|
1439
1865
|
updatedAt,
|
|
1440
1866
|
load,
|
|
1441
|
-
missingKeys
|
|
1867
|
+
missingKeys,
|
|
1868
|
+
pollRevision
|
|
1442
1869
|
};
|
|
1443
1870
|
}
|
|
1444
1871
|
var init_use_snapshot_polling = __esmMin((() => {
|
|
@@ -1452,7 +1879,7 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
1452
1879
|
//#endregion
|
|
1453
1880
|
//#region src/client/panel-page.ts
|
|
1454
1881
|
function PanelPage({ tt, localeSubscribe }) {
|
|
1455
|
-
const { data, error,
|
|
1882
|
+
const { data, error, updatedAt, load, missingKeys, pollRevision } = useSnapshotPolling();
|
|
1456
1883
|
const [, setLocaleRevision] = useState(0);
|
|
1457
1884
|
const [openSections, setOpenSections] = useState({
|
|
1458
1885
|
balance: true,
|
|
@@ -1460,6 +1887,7 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
1460
1887
|
trend: true,
|
|
1461
1888
|
events: false,
|
|
1462
1889
|
provider: true,
|
|
1890
|
+
draw: true,
|
|
1463
1891
|
token: true
|
|
1464
1892
|
});
|
|
1465
1893
|
const [activeTab, setActiveTab] = useState("quota");
|
|
@@ -1473,67 +1901,126 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
1473
1901
|
[key]: !current[key]
|
|
1474
1902
|
}));
|
|
1475
1903
|
}, []);
|
|
1476
|
-
const { failure,
|
|
1477
|
-
const showSetup = needsSetup && loadedOnce;
|
|
1904
|
+
const { failure, guidance, shapeWarnings } = viewOf(data, error, tt, missingKeys);
|
|
1478
1905
|
const rawProvider = data?.provider;
|
|
1479
1906
|
const hasProviderBlock = rawProvider !== void 0 && rawProvider !== null;
|
|
1480
1907
|
const [fetchedProvider, setFetchedProvider] = useState(null);
|
|
1481
|
-
const provider = hasProviderBlock ? providerOf(rawProvider) : fetchedProvider ?? DEGRADED_PROVIDER;
|
|
1908
|
+
const provider = hasProviderBlock ? providerOf$1(rawProvider) : fetchedProvider ?? DEGRADED_PROVIDER;
|
|
1482
1909
|
useEffect(() => {
|
|
1483
1910
|
if (hasProviderBlock) return void 0;
|
|
1484
1911
|
let cancelled = false;
|
|
1485
1912
|
getJson(PROVIDER_PATH).then((body) => {
|
|
1486
|
-
if (!cancelled && body !== null && body.ok === true) setFetchedProvider(providerOf(body));
|
|
1913
|
+
if (!cancelled && body !== null && body.ok === true) setFetchedProvider(providerOf$1(body));
|
|
1914
|
+
}).catch(() => {});
|
|
1915
|
+
return () => {
|
|
1916
|
+
cancelled = true;
|
|
1917
|
+
};
|
|
1918
|
+
}, [hasProviderBlock, pollRevision]);
|
|
1919
|
+
const rawDraw = data?.draw;
|
|
1920
|
+
const hasDrawBlock = rawDraw !== void 0 && rawDraw !== null;
|
|
1921
|
+
const [fetchedDraw, setFetchedDraw] = useState(null);
|
|
1922
|
+
const draw = hasDrawBlock ? drawOf(rawDraw) : fetchedDraw ?? DEGRADED_DRAW;
|
|
1923
|
+
useEffect(() => {
|
|
1924
|
+
if (hasDrawBlock) return void 0;
|
|
1925
|
+
let cancelled = false;
|
|
1926
|
+
getJson(DRAW_PATH).then((body) => {
|
|
1927
|
+
if (!cancelled && body !== null && body.ok === true) setFetchedDraw(drawOf(body));
|
|
1487
1928
|
}).catch(() => {});
|
|
1488
1929
|
return () => {
|
|
1489
1930
|
cancelled = true;
|
|
1490
1931
|
};
|
|
1491
|
-
}, [
|
|
1932
|
+
}, [hasDrawBlock, pollRevision]);
|
|
1492
1933
|
const [providerBusy, setProviderBusy] = useState(false);
|
|
1493
1934
|
const [providerError, setProviderError] = useState(null);
|
|
1935
|
+
const writeErrorText = (reason, t) => reason instanceof Error && reason.name === "AbortError" ? t("common.timeout") : errorText(reason);
|
|
1936
|
+
/**
|
|
1937
|
+
* POST 一个写路径,并把 body 里的域失败抛出去(Host 对写操作恒回 HTTP 200,成败
|
|
1938
|
+
* 看 body)。判据只有 `http.ts#writeFailure` 一份——三个写回调曾各判一次,而
|
|
1939
|
+
* 「忘掉令牌」那条漏了,于是凭据文件只读时界面表现为「什么都没发生」。
|
|
1940
|
+
*/
|
|
1941
|
+
const postWrite = useCallback(async (path, payload) => {
|
|
1942
|
+
const failure = writeFailure(await postJson(path, payload), tt("common.httpError"));
|
|
1943
|
+
if (failure !== null) throw failure;
|
|
1944
|
+
}, [tt]);
|
|
1494
1945
|
const runProviderWrite = useCallback(async (path, payload) => {
|
|
1495
1946
|
setProviderBusy(true);
|
|
1496
1947
|
setProviderError(null);
|
|
1497
1948
|
try {
|
|
1498
|
-
|
|
1499
|
-
if (body === null || body.ok !== true) throw new Error(typeof body?.error === "string" ? body.error : "HTTP error");
|
|
1949
|
+
await postWrite(path, payload);
|
|
1500
1950
|
load();
|
|
1501
1951
|
} catch (reason) {
|
|
1502
|
-
setProviderError(format(tt("provider.error"), { error:
|
|
1952
|
+
setProviderError(format(tt("provider.error"), { error: writeErrorText(reason, tt) }));
|
|
1503
1953
|
} finally {
|
|
1504
1954
|
setProviderBusy(false);
|
|
1505
1955
|
}
|
|
1506
|
-
}, [
|
|
1956
|
+
}, [
|
|
1957
|
+
load,
|
|
1958
|
+
postWrite,
|
|
1959
|
+
tt
|
|
1960
|
+
]);
|
|
1507
1961
|
const toggleProvider = useCallback((enabled) => void runProviderWrite(PROVIDER_PATH, { enabled }), [runProviderWrite]);
|
|
1508
|
-
const saveRoster = useCallback((enabledIds) =>
|
|
1962
|
+
const saveRoster = useCallback((enabledIds) => {
|
|
1963
|
+
const normalized = enabledIds.includes("__hide_all__") ? [HIDE_ALL_MODELS] : enabledIds.filter((id) => id !== HIDE_ALL_MODELS);
|
|
1964
|
+
runProviderWrite(PROVIDER_ROSTER_PATH, { enabledIds: normalized });
|
|
1965
|
+
}, [runProviderWrite]);
|
|
1509
1966
|
const resetProvider = useCallback(() => void runProviderWrite(PROVIDER_RESET_PATH, {}), [runProviderWrite]);
|
|
1967
|
+
const saveContextWindow = useCallback((contextWindow) => {
|
|
1968
|
+
runProviderWrite(PROVIDER_CONTEXT_PATH, { contextWindow });
|
|
1969
|
+
}, [runProviderWrite]);
|
|
1970
|
+
const [drawBusy, setDrawBusy] = useState(false);
|
|
1971
|
+
const [drawError, setDrawError] = useState(null);
|
|
1972
|
+
const runDrawWrite = useCallback(async (path, payload) => {
|
|
1973
|
+
setDrawBusy(true);
|
|
1974
|
+
setDrawError(null);
|
|
1975
|
+
try {
|
|
1976
|
+
await postWrite(path, payload);
|
|
1977
|
+
load();
|
|
1978
|
+
} catch (reason) {
|
|
1979
|
+
setDrawError(format(tt("draw.error"), { error: writeErrorText(reason, tt) }));
|
|
1980
|
+
} finally {
|
|
1981
|
+
setDrawBusy(false);
|
|
1982
|
+
}
|
|
1983
|
+
}, [
|
|
1984
|
+
load,
|
|
1985
|
+
postWrite,
|
|
1986
|
+
tt
|
|
1987
|
+
]);
|
|
1988
|
+
const toggleDraw = useCallback((enabled) => void runDrawWrite(DRAW_PATH, { enabled }), [runDrawWrite]);
|
|
1989
|
+
const resetDraw = useCallback(() => void runDrawWrite(DRAW_RESET_PATH, {}), [runDrawWrite]);
|
|
1510
1990
|
const [tokenBusy, setTokenBusy] = useState(false);
|
|
1511
1991
|
const [tokenError, setTokenError] = useState(null);
|
|
1512
1992
|
const saveToken = useCallback(async (value) => {
|
|
1513
1993
|
setTokenBusy(true);
|
|
1514
1994
|
setTokenError(null);
|
|
1515
1995
|
try {
|
|
1516
|
-
|
|
1517
|
-
if (body === null || body.ok !== true) throw new Error(typeof body?.error === "string" ? body.error : "HTTP error");
|
|
1996
|
+
await postWrite(TOKEN_PATH, { token: value });
|
|
1518
1997
|
load();
|
|
1519
1998
|
} catch (reason) {
|
|
1520
|
-
setTokenError(
|
|
1999
|
+
setTokenError(writeErrorText(reason, tt));
|
|
1521
2000
|
} finally {
|
|
1522
2001
|
setTokenBusy(false);
|
|
1523
2002
|
}
|
|
1524
|
-
}, [
|
|
2003
|
+
}, [
|
|
2004
|
+
load,
|
|
2005
|
+
postWrite,
|
|
2006
|
+
tt
|
|
2007
|
+
]);
|
|
1525
2008
|
const forgetToken = useCallback(async () => {
|
|
1526
2009
|
setTokenBusy(true);
|
|
1527
2010
|
setTokenError(null);
|
|
1528
2011
|
try {
|
|
1529
|
-
await
|
|
2012
|
+
await postWrite(TOKEN_FORGET_PATH, {});
|
|
1530
2013
|
load();
|
|
1531
2014
|
} catch (reason) {
|
|
1532
|
-
setTokenError(
|
|
2015
|
+
setTokenError(writeErrorText(reason, tt));
|
|
1533
2016
|
} finally {
|
|
1534
2017
|
setTokenBusy(false);
|
|
1535
2018
|
}
|
|
1536
|
-
}, [
|
|
2019
|
+
}, [
|
|
2020
|
+
load,
|
|
2021
|
+
postWrite,
|
|
2022
|
+
tt
|
|
2023
|
+
]);
|
|
1537
2024
|
const tokenState = data?.token ?? null;
|
|
1538
2025
|
const sampleModel = data?.models?.sample?.[0] ?? null;
|
|
1539
2026
|
const [verifyBusy, setVerifyBusy] = useState(false);
|
|
@@ -1545,20 +2032,17 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
1545
2032
|
setVerifyError(null);
|
|
1546
2033
|
try {
|
|
1547
2034
|
const body = await postJson(PROBE_PATH, { modelId });
|
|
2035
|
+
if (body !== null) load();
|
|
1548
2036
|
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 : "
|
|
2037
|
+
else setVerifyError(typeof body?.error === "string" ? body.error : tt("common.httpError"));
|
|
1550
2038
|
} catch (reason) {
|
|
1551
|
-
setVerifyError(
|
|
2039
|
+
setVerifyError(writeErrorText(reason, tt));
|
|
1552
2040
|
} finally {
|
|
1553
2041
|
setVerifyBusy(false);
|
|
1554
2042
|
}
|
|
1555
|
-
}, [tt]);
|
|
2043
|
+
}, [load, tt]);
|
|
1556
2044
|
const quotaBody = () => {
|
|
1557
2045
|
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
2046
|
const snap = data;
|
|
1563
2047
|
return h("div", null, snap.token?.present === false ? h("div", {
|
|
1564
2048
|
style: S.formNote,
|
|
@@ -1608,9 +2092,23 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
1608
2092
|
error: providerError,
|
|
1609
2093
|
onToggle: toggleProvider,
|
|
1610
2094
|
onSaveList: saveRoster,
|
|
2095
|
+
onSaveContextWindow: saveContextWindow,
|
|
1611
2096
|
onReset: resetProvider,
|
|
1612
2097
|
tokenPresent: data?.token?.present === true,
|
|
1613
2098
|
tt
|
|
2099
|
+
})), h(SectionCard, {
|
|
2100
|
+
title: tt("section.draw"),
|
|
2101
|
+
open: openSections.draw,
|
|
2102
|
+
onToggle: () => toggleSection("draw"),
|
|
2103
|
+
tt
|
|
2104
|
+
}, h(DrawCard, {
|
|
2105
|
+
draw,
|
|
2106
|
+
busy: drawBusy,
|
|
2107
|
+
error: drawError,
|
|
2108
|
+
onToggle: toggleDraw,
|
|
2109
|
+
onReset: resetDraw,
|
|
2110
|
+
tokenPresent: data?.token?.present === true,
|
|
2111
|
+
tt
|
|
1614
2112
|
})));
|
|
1615
2113
|
const accessBody = () => h("div", null, h(SectionCard, {
|
|
1616
2114
|
title: tt("section.token"),
|
|
@@ -1690,6 +2188,175 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
1690
2188
|
init_styles();
|
|
1691
2189
|
}));
|
|
1692
2190
|
|
|
2191
|
+
//#endregion
|
|
2192
|
+
//#region src/client/composer-usage-pill.ts
|
|
2193
|
+
/**
|
|
2194
|
+
* 一个候选自带的 provider,空串算「没有」。
|
|
2195
|
+
*
|
|
2196
|
+
* `typeof provider === "string"` 是不够的判据:`""` 也是字符串。照类型判定会让
|
|
2197
|
+
* `{ provider: "" }` 被当成一个「可用的 next」,于是它遮住仍然可用的 `lastUsed`
|
|
2198
|
+
* ——切模型那一帧胶囊就消失。所以这里判的是**非空字符串**。
|
|
2199
|
+
*/
|
|
2200
|
+
function providerOf(candidate) {
|
|
2201
|
+
const provider = candidate?.provider;
|
|
2202
|
+
return typeof provider === "string" && provider !== "" ? provider : void 0;
|
|
2203
|
+
}
|
|
2204
|
+
/**
|
|
2205
|
+
* 会话当前指向的 provider。
|
|
2206
|
+
*
|
|
2207
|
+
* `next` 优先于 `lastUsed`(切换已落地但还没发出去时,选择器显示的就是 `next`),
|
|
2208
|
+
* 但**只在 `next` 真的带了一个非空 provider 时**才优先:picker 刚切完、新选择还没
|
|
2209
|
+
* 落到 `next` 上的那几帧 `next` 是 `{}` 或 `{ provider: "" }`,照「有 next 就用
|
|
2210
|
+
* next」读会得到 `undefined`,于是归属模型在「切换」这个动作本身上把胶囊藏起来。
|
|
2211
|
+
* 两侧都没有可用 provider 时返回 `undefined`——那才是真的判不出来。
|
|
2212
|
+
*/
|
|
2213
|
+
function selectedProviderOf(projection) {
|
|
2214
|
+
const source = projection ?? {};
|
|
2215
|
+
return providerOf(source.next) ?? providerOf(source.lastUsed);
|
|
2216
|
+
}
|
|
2217
|
+
/**
|
|
2218
|
+
* 余额读数;`null` 表示**不知道**(没读到 / 上游没给 / 字段不是有限数)。
|
|
2219
|
+
*
|
|
2220
|
+
* 特意不接受 `error` 参数做二次判断:错误文本只影响 tooltip 的措辞,不影响
|
|
2221
|
+
* 「数字可不可信」。把两者揉在一个判定里,会让「上次读到的 143 仍然有效」这种
|
|
2222
|
+
* 情形(`error` 非空但 `available` 有值)被误打成未知。
|
|
2223
|
+
*/
|
|
2224
|
+
function availableOf(balance) {
|
|
2225
|
+
const available = balance?.available;
|
|
2226
|
+
return typeof available === "number" && Number.isFinite(available) ? available : null;
|
|
2227
|
+
}
|
|
2228
|
+
/** 今日本地调用次数;`null` 同上,表示读不到(不是 0)。 */
|
|
2229
|
+
function usedTodayOf(snapshot) {
|
|
2230
|
+
const used = snapshot?.quota?.daily?.usedLocal;
|
|
2231
|
+
return typeof used === "number" && Number.isFinite(used) ? used : null;
|
|
2232
|
+
}
|
|
2233
|
+
/** 由余额读数定档;`null`(未读到)与 `0`(读到了、就是零)在此分道。 */
|
|
2234
|
+
function tierOf(available) {
|
|
2235
|
+
if (available === null) return "unknown";
|
|
2236
|
+
return available > 0 ? "ok" : "exhausted";
|
|
2237
|
+
}
|
|
2238
|
+
/** 「余额」段的正文:读不到画「—」+ 单位,读到了画数字 + 单位。 */
|
|
2239
|
+
function balanceText(available, tt) {
|
|
2240
|
+
return `${available === null ? "—" : count(available)} ${tt("composer.balanceUnit")}`;
|
|
2241
|
+
}
|
|
2242
|
+
/** 「今日本地」段的正文:读不到画「—」,读到了画次数。 */
|
|
2243
|
+
function callsText(usedToday, tt) {
|
|
2244
|
+
return format(tt("composer.localCalls"), { calls: usedToday === null ? "—" : count(usedToday) });
|
|
2245
|
+
}
|
|
2246
|
+
/**
|
|
2247
|
+
* 胶囊正文:余额段 + 本地次数段,各带各的口径。两段都必须出现——只画有值的那
|
|
2248
|
+
* 一段会让「哪一段是空的」变成排版事故(读者分不清是没读到还是这一栏根本不在),
|
|
2249
|
+
* 而「—」明确说了是前者。
|
|
2250
|
+
*/
|
|
2251
|
+
function readingText(available, usedToday, tt) {
|
|
2252
|
+
return `${balanceText(available, tt)} · ${callsText(usedToday, tt)}`;
|
|
2253
|
+
}
|
|
2254
|
+
/**
|
|
2255
|
+
* 数字是否已经算「上次数据」。
|
|
2256
|
+
*
|
|
2257
|
+
* 两个触发条件各自独立:读失败(`error` 非空,上一轮的数值还留在屏幕上)本身就是
|
|
2258
|
+
* 陈旧;而长时间没有**成功**的读(`updatedAt` 不动)说明定时轮询没在按时回来
|
|
2259
|
+
* ——失败退避或页面被挂起都会这样。取 3 个声明周期而不是 1 个:一次偶发失败就会
|
|
2260
|
+
* 拉长间隔,卡在 1 个周期上会让每一次抖动都把读数打成陈旧的。
|
|
2261
|
+
*
|
|
2262
|
+
* `updatedAt === 0` 表示**从没读到过**,那不是「陈旧」而是「还没有」——调用方据此
|
|
2263
|
+
* 决定不标;本函数只在有读数的前提下判断,故返回 false。
|
|
2264
|
+
*/
|
|
2265
|
+
function staleOf(input) {
|
|
2266
|
+
if (input.error !== null && input.error !== void 0) return input.updatedAt > 0;
|
|
2267
|
+
return input.updatedAt > 0 && input.now - input.updatedAt > input.cadenceMs * STALE_CADENCES;
|
|
2268
|
+
}
|
|
2269
|
+
/**
|
|
2270
|
+
* tooltip 的一行:只把**读者下一步会问的**事实拼进来——数字是哪来的(前缀)、
|
|
2271
|
+
* 余额为什么没有(错误原文)、什么时候读的(fetchedAt)、本地计数为什么没有
|
|
2272
|
+
* (quota 那一路的提示)、以及数字是不是旧的。逐段 `filter` 掉空值,不留下
|
|
2273
|
+
* 「 · · 」这样的空槽。
|
|
2274
|
+
*/
|
|
2275
|
+
function titleOf(props) {
|
|
2276
|
+
const { tt, balance, usedToday, stale } = props;
|
|
2277
|
+
const parts = [tt("composer.pillTitle")];
|
|
2278
|
+
const failure = balance?.error;
|
|
2279
|
+
parts.push(typeof failure === "string" && failure !== "" ? format(tt("balance.unavailable"), { error: failure }) : null);
|
|
2280
|
+
const fetchedAt = balance?.fetchedAt;
|
|
2281
|
+
parts.push(typeof fetchedAt === "string" && fetchedAt !== "" ? format(tt("balance.fetched"), { time: isoTime(fetchedAt) }) : null);
|
|
2282
|
+
parts.push(usedToday === null ? tt("quota.unavailable") : null);
|
|
2283
|
+
parts.push(stale ? tt("composer.stale") : null);
|
|
2284
|
+
return parts.filter((part) => part !== null).join(" · ");
|
|
2285
|
+
}
|
|
2286
|
+
/**
|
|
2287
|
+
* 内层:只在会话选中本插件的模型时挂载。轮询 hook 在这里,被挂/摘即起停——
|
|
2288
|
+
* 非归属 provider 的会话不发任何请求。
|
|
2289
|
+
*/
|
|
2290
|
+
function ModelscopePill(props) {
|
|
2291
|
+
const { tt } = props;
|
|
2292
|
+
const { data, error, loadedOnce, updatedAt } = useSnapshotPolling();
|
|
2293
|
+
const balance = data?.balance ?? null;
|
|
2294
|
+
const available = availableOf(balance);
|
|
2295
|
+
const usedToday = usedTodayOf(data);
|
|
2296
|
+
const cadenceMs = statedCadenceMs(data?.pollSeconds, DEFAULT_CADENCE_MS);
|
|
2297
|
+
const stale = staleOf({
|
|
2298
|
+
error,
|
|
2299
|
+
updatedAt,
|
|
2300
|
+
now: Date.now(),
|
|
2301
|
+
cadenceMs
|
|
2302
|
+
});
|
|
2303
|
+
const pending = !loadedOnce && data === null;
|
|
2304
|
+
const tier = tierOf(available);
|
|
2305
|
+
const title = titleOf({
|
|
2306
|
+
tt,
|
|
2307
|
+
balance,
|
|
2308
|
+
usedToday,
|
|
2309
|
+
stale
|
|
2310
|
+
});
|
|
2311
|
+
return h("button", {
|
|
2312
|
+
type: "button",
|
|
2313
|
+
className: "dsh-modelscope-composer-pill",
|
|
2314
|
+
title,
|
|
2315
|
+
"aria-label": title,
|
|
2316
|
+
style: S.pillTrigger
|
|
2317
|
+
}, h("span", {
|
|
2318
|
+
className: "dsh-modelscope-composer-pill-dot",
|
|
2319
|
+
"aria-hidden": "true",
|
|
2320
|
+
style: {
|
|
2321
|
+
...S.pillDot,
|
|
2322
|
+
background: TIER_INK[tier]
|
|
2323
|
+
}
|
|
2324
|
+
}), h("span", {
|
|
2325
|
+
className: "dsh-modelscope-composer-pill-plan",
|
|
2326
|
+
style: S.pillPlan
|
|
2327
|
+
}, `${tt("composer.prefix")} · `), h("span", {
|
|
2328
|
+
className: "dsh-modelscope-composer-pill-text",
|
|
2329
|
+
style: tier === "exhausted" ? S.pillTierExhaustedText : void 0
|
|
2330
|
+
}, pending ? "…" : readingText(available, usedToday, tt)), stale ? h("span", {
|
|
2331
|
+
className: "dsh-modelscope-composer-pill-stale",
|
|
2332
|
+
style: S.pillStale
|
|
2333
|
+
}, ` · ${tt("composer.stale")}`) : null);
|
|
2334
|
+
}
|
|
2335
|
+
/**
|
|
2336
|
+
* 外层门控:固定一处调 `useProjection`,再决定内层挂谁。非归属 provider 返回
|
|
2337
|
+
* `null`(不渲染任何东西,也不跑任何请求)。
|
|
2338
|
+
*/
|
|
2339
|
+
function ComposerUsagePill(props) {
|
|
2340
|
+
const selection = props.useProjection?.("modelSelection");
|
|
2341
|
+
if (selectedProviderOf(selection) !== "modelscope-token-plan") return null;
|
|
2342
|
+
return h(ModelscopePill, { tt: props.tt });
|
|
2343
|
+
}
|
|
2344
|
+
var MODELSCOPE_PROVIDER, DEFAULT_CADENCE_MS, STALE_CADENCES, TIER_INK;
|
|
2345
|
+
var init_composer_usage_pill = __esmMin((() => {
|
|
2346
|
+
init_runtime();
|
|
2347
|
+
init_styles();
|
|
2348
|
+
init_format();
|
|
2349
|
+
init_use_snapshot_polling();
|
|
2350
|
+
MODELSCOPE_PROVIDER = "modelscope-token-plan";
|
|
2351
|
+
DEFAULT_CADENCE_MS = 3e4;
|
|
2352
|
+
STALE_CADENCES = 3;
|
|
2353
|
+
TIER_INK = {
|
|
2354
|
+
ok: S.pillTierOk,
|
|
2355
|
+
exhausted: S.pillTierExhausted,
|
|
2356
|
+
unknown: S.pillTierUnknown
|
|
2357
|
+
};
|
|
2358
|
+
}));
|
|
2359
|
+
|
|
1693
2360
|
//#endregion
|
|
1694
2361
|
//#region src/client/apply.ts
|
|
1695
2362
|
/**
|
|
@@ -1732,6 +2399,17 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
1732
2399
|
} catch (error) {
|
|
1733
2400
|
console.warn(`[${NS}] config card registration failed:`, error);
|
|
1734
2401
|
}
|
|
2402
|
+
try {
|
|
2403
|
+
disposers.push(ctx.slots.inject("conversation.input.right", () => ctx.slots.register({
|
|
2404
|
+
name: "conversation.input.right",
|
|
2405
|
+
id: `${NS}-composer-pill`,
|
|
2406
|
+
locale: NS,
|
|
2407
|
+
order: 4,
|
|
2408
|
+
inject: () => ({ tt })
|
|
2409
|
+
}, ComposerUsagePill)));
|
|
2410
|
+
} catch (error) {
|
|
2411
|
+
console.warn(`[${NS}] composer pill registration failed:`, error);
|
|
2412
|
+
}
|
|
1735
2413
|
ctx.effect(() => () => {
|
|
1736
2414
|
for (const dispose of disposers.splice(0)) try {
|
|
1737
2415
|
dispose();
|
|
@@ -1743,6 +2421,7 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
1743
2421
|
init_const();
|
|
1744
2422
|
init_i18n();
|
|
1745
2423
|
init_panel_page();
|
|
2424
|
+
init_composer_usage_pill();
|
|
1746
2425
|
inject = ["slots", "locale"];
|
|
1747
2426
|
}));
|
|
1748
2427
|
|
|
@@ -1758,6 +2437,7 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
1758
2437
|
init_styles();
|
|
1759
2438
|
init_cards();
|
|
1760
2439
|
init_panel_page();
|
|
2440
|
+
init_composer_usage_pill();
|
|
1761
2441
|
init_use_polling_interval();
|
|
1762
2442
|
init_use_snapshot_polling();
|
|
1763
2443
|
init_http();
|
|
@@ -1767,6 +2447,7 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
1767
2447
|
const panel = Object.freeze({
|
|
1768
2448
|
interpretSnapshot,
|
|
1769
2449
|
viewOf,
|
|
2450
|
+
providerOf: providerOf$1,
|
|
1770
2451
|
errorOfStatus,
|
|
1771
2452
|
dictionaries: Object.freeze({
|
|
1772
2453
|
zh,
|
|
@@ -1787,12 +2468,24 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
1787
2468
|
usePollingInterval,
|
|
1788
2469
|
useSnapshotPolling,
|
|
1789
2470
|
getJson,
|
|
1790
|
-
postJson
|
|
2471
|
+
postJson,
|
|
2472
|
+
writeFailure,
|
|
2473
|
+
MODELSCOPE_PROVIDER,
|
|
2474
|
+
selectedProviderOf,
|
|
2475
|
+
availableOf,
|
|
2476
|
+
usedTodayOf,
|
|
2477
|
+
tierOf,
|
|
2478
|
+
balanceText,
|
|
2479
|
+
callsText,
|
|
2480
|
+
readingText,
|
|
2481
|
+
staleOf
|
|
1791
2482
|
}),
|
|
1792
2483
|
components: Object.freeze({
|
|
1793
2484
|
BalanceCard,
|
|
2485
|
+
ComposerUsagePill,
|
|
1794
2486
|
EventsList,
|
|
1795
2487
|
LocalDailyCard,
|
|
2488
|
+
ModelscopePill,
|
|
1796
2489
|
ModelUsageTable,
|
|
1797
2490
|
PanelPage,
|
|
1798
2491
|
ProviderCard,
|