dsh-connect-modelscope-token-plan 0.3.0 → 0.5.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 +116 -74
- package/README.md +23 -12
- package/assets/tab-account-and-usage.png +0 -0
- package/assets/tab-model-draw.png +0 -0
- package/assets/tab-model-list.png +0 -0
- package/client.js +778 -182
- package/cordis.patch.yml +13 -4
- package/lib/host-config.js +271 -47
- package/lib/index.js +1575 -567
- package/lib/llm-adapter.js +38 -18
- package/package.json +2 -2
- package/screenshots.json +2 -1
- package/assets/icon-preview.png +0 -0
package/client.js
CHANGED
|
@@ -11,6 +11,30 @@ 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) {
|
|
@@ -22,25 +46,67 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
22
46
|
return slash >= 0 ? id.slice(0, slash) : id;
|
|
23
47
|
}
|
|
24
48
|
/**
|
|
25
|
-
*
|
|
49
|
+
* 清单行要显示的名字:组头已经把 owner 说过一次,名字若还带着 `owner/` 前缀就切掉
|
|
50
|
+
* ——行宽还给名字本身。
|
|
51
|
+
*
|
|
52
|
+
* 为什么需要它:魔搭目录**只回 id**,没有单独的 name 字段,所以 Host 侧
|
|
53
|
+
* `str(entry.name, id)` 的兜底让每一行的 name 就等于 id(见 llm-models.ts)。分组
|
|
54
|
+
* 之后那些前缀与组头逐字重复,一个 34 个模型的清单里同一个 owner 要说十几遍。
|
|
55
|
+
*
|
|
56
|
+
* 只切**与 owner 逐字相同**的前缀:name 若是目录给的人话名字(`DeepSeek V4 Pro`)
|
|
57
|
+
* 或来自别的 owner,原样返回——不按位置硬切,免得把真正的名字切坏。行的 `title`
|
|
58
|
+
* 与复选框的 `aria-label` 仍然用完整 id,所以切掉的信息没有丢。
|
|
59
|
+
* @param {string} id - 完整模型 id(`owner/model`)。
|
|
60
|
+
* @param {string} name - 目录给的名字(通常等于 id)。
|
|
61
|
+
* @returns {string} 行内显示的名字。
|
|
62
|
+
*/
|
|
63
|
+
function trimOwnerPrefix(id, name) {
|
|
64
|
+
const prefix = `${catalogOwner(id)}/`;
|
|
65
|
+
return name.startsWith(prefix) ? name.slice(prefix.length) : name;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* 徽章用的窗口短标注(tokens → "128K" / "1M")——按**这个数被写出来时用的那个
|
|
69
|
+
* 进制**渲染,不把它换算到另一把尺上。
|
|
70
|
+
*
|
|
71
|
+
* 为什么不「一律除以 1024」:上游自己混用两种写法。档位表与模型的出厂规格是 2 的
|
|
72
|
+
* 幂(131072、262144、1048576),而目录声明与 models.dev / 策展表报的是十进制
|
|
73
|
+
* (128000、200000、256000、1000000——见 `context-catalog.ts` 里逐条抄下来的那些
|
|
74
|
+
* 数)。一律除 1024 会把后一类印成 125K / 195K / 250K / 977K:与上游自己写的名字
|
|
75
|
+
* 全都对不上。LongCat-Flash-Lite 上游就叫 256K,面板印 250K 是另一种撒谎;
|
|
76
|
+
* GLM-5.2 的 1M 印成 977K,读起来像「比档位里的 1M 小一截」——而档位里那个 1M 是
|
|
77
|
+
* 1048576,跟 1000000 差 4.6%,本来就是同一个市场叫法。
|
|
78
|
+
*
|
|
79
|
+
* 判据(与姊妹插件 Agnes 的 `tokenSize` 同一条规则,她那份头注记着同一个坑):
|
|
80
|
+
* 1. ≥ 1M 走 M——十进制的 1,000,000 与二进制的 1,048,576 都读 "1M";
|
|
81
|
+
* 2. 能整除 1000 的按十进制(128000 → "128K"、200000 → "200K");
|
|
82
|
+
* 3. 能整除 1024 的按二进制(131072 → "128K"、262144 → "256K");
|
|
83
|
+
* 4. 两者都不整除的零散声明按十进制取整(123456 → "123K");取整进位到四位数
|
|
84
|
+
* 就交给 M 档(999,999 读 "1M",不读 "1000K")。
|
|
85
|
+
*
|
|
86
|
+
* 代价说在明处:第 2、3 条会**撞车**——128000(两个进制都整除,走十进制)与
|
|
87
|
+
* 131072 都印 "128K"。这是有意的:窗口徽章回答的是「大概能放多少上下文」,而
|
|
88
|
+
* 128K / 256K / 1M 是上游与市场共用的叫法,跟它一致比区分那 2.4% 更有用。
|
|
26
89
|
*
|
|
27
|
-
*
|
|
28
|
-
* 128000 显示成 "125K" 而不是 "128K"——标签必须能从数值反推出来,四舍五入到
|
|
29
|
-
* 好看的整数是撒谎。≥1M 用 M(`1.25M` 保留至多一位小数,去掉尾随 .0),
|
|
30
|
-
* ≥1024 用 K,更小的原样显示。
|
|
90
|
+
* `tokens` 由调用方保证是正的有限数(`providerOf` 已挡),所以这里不再兜一次底。
|
|
31
91
|
* @param {number} tokens - 窗口大小(tokens)。
|
|
32
92
|
* @returns {string} 短标注。
|
|
33
93
|
*/
|
|
34
94
|
function formatWindow(tokens) {
|
|
35
|
-
if (tokens >=
|
|
36
|
-
|
|
37
|
-
|
|
95
|
+
if (tokens >= 1e6) return millionLabel(tokens);
|
|
96
|
+
if (tokens >= 1024) {
|
|
97
|
+
const k = tokens % 1e3 === 0 ? tokens / 1e3 : tokens % 1024 === 0 ? tokens / 1024 : Math.round(tokens / 1e3);
|
|
98
|
+
return k >= 1e3 ? millionLabel(tokens) : `${k}K`;
|
|
38
99
|
}
|
|
39
|
-
if (tokens >= 1024) return `${Math.round(tokens / 1024)}K`;
|
|
40
100
|
return String(tokens);
|
|
41
101
|
}
|
|
42
|
-
|
|
102
|
+
/** M 档读数:≥10 只留整数,否则至多一位小数(`1048576` → "1M"、`2500000` → "2.5M")。 */
|
|
103
|
+
function millionLabel(tokens) {
|
|
104
|
+
const m = tokens / 1e6;
|
|
105
|
+
return `${m >= 10 || Number.isInteger(m) ? Math.round(m) : Math.round(m * 10) / 10}M`;
|
|
106
|
+
}
|
|
107
|
+
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, TREND_WINDOW_PATH, MODELSCOPE_TOKEN_URL, MODELSCOPE_USAGE_URL, MODELSCOPE_MODEL_URL_BASE, FEATURED_OWNERS;
|
|
43
108
|
var init_const = __esmMin((() => {
|
|
109
|
+
init_wire();
|
|
44
110
|
NS = "dsh-connect-modelscope-token-plan";
|
|
45
111
|
PANEL_ID = NS;
|
|
46
112
|
SNAPSHOT_PATH = `/api/${NS}/snapshot`;
|
|
@@ -54,7 +120,7 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
54
120
|
PROVIDER_RESET_PATH = `/api/${NS}/provider/reset`;
|
|
55
121
|
DRAW_PATH = `/api/${NS}/draw`;
|
|
56
122
|
DRAW_RESET_PATH = `/api/${NS}/draw/reset`;
|
|
57
|
-
|
|
123
|
+
TREND_WINDOW_PATH = `/api/${NS}/trend/window`;
|
|
58
124
|
MODELSCOPE_TOKEN_URL = "https://modelscope.cn/my/myaccesstoken";
|
|
59
125
|
MODELSCOPE_USAGE_URL = "https://modelscope.cn/magicube/usage?tab=consume";
|
|
60
126
|
MODELSCOPE_MODEL_URL_BASE = "https://www.modelscope.cn/models";
|
|
@@ -91,7 +157,7 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
91
157
|
"tab.models": "模型",
|
|
92
158
|
"tab.access": "接入",
|
|
93
159
|
"section.balance": "魔粒余额(官方数据)",
|
|
94
|
-
"section.local": "
|
|
160
|
+
"section.local": "本地调用(本客户端 · 仅经本插件)",
|
|
95
161
|
"section.trend": "近 {days} 天本地调用趋势",
|
|
96
162
|
"section.events": "限流与错误事件",
|
|
97
163
|
"section.token": "访问令牌",
|
|
@@ -105,14 +171,16 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
105
171
|
"balance.usagePage": "官方用量明细(网页)→",
|
|
106
172
|
"balance.unavailable": "魔粒余额暂不可读:{error}",
|
|
107
173
|
"quota.headline": "今日 {calls} 次 · {models} 个模型",
|
|
108
|
-
"quota.
|
|
174
|
+
"quota.rowCalls": "{calls} 次",
|
|
175
|
+
"quota.rowTokens": "{tokens} token",
|
|
176
|
+
"quota.note": "本地口径:只统计本客户端经本插件的调用(桌面端与 Web 端各记各的);直连魔搭的其它客户端不计入。消耗总量以官方魔粒余额为准。",
|
|
109
177
|
"quota.unavailable": "本地计数本次读不到(原因见上方提示)",
|
|
110
178
|
"probe.validity": "验令牌(零额度)",
|
|
111
179
|
"probe.validOk": "令牌有效(HTTP {status})",
|
|
112
180
|
"probe.fail": "失败:{error}",
|
|
113
181
|
"models.viewOnSite": "在魔搭查看 →",
|
|
114
182
|
"models.viewOnSiteTitle": "打开模型详情页(魔粒单价等只在网页展示)",
|
|
115
|
-
"models.featuredTitle": "
|
|
183
|
+
"models.featuredTitle": "推荐厂商(深度求索 / 智谱 / 通义千问)——它们的模型置顶显示",
|
|
116
184
|
"provider.featured": "推荐",
|
|
117
185
|
"section.provider": "接入为 DSH 模型",
|
|
118
186
|
"provider.enable": "把魔搭模型接入 DSH 模型选择器",
|
|
@@ -130,37 +198,56 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
130
198
|
"provider.reset": "回到默认",
|
|
131
199
|
"provider.enabledCount": "已启用 {count} / {total}",
|
|
132
200
|
"provider.rosterHint": "勾选要进入 DSH 模型选择器的模型;不勾 = 全部提供",
|
|
201
|
+
"provider.search": "搜索模型名或 ID",
|
|
202
|
+
"provider.noMatch": "没有匹配的模型。",
|
|
203
|
+
"provider.groupCount": "{count} 个",
|
|
204
|
+
"provider.groupSelected": "已选 {count}",
|
|
133
205
|
"provider.quotaExhausted": "额度耗尽",
|
|
134
206
|
"provider.vision": "视觉",
|
|
135
207
|
"provider.notConfigured": "还没有访问令牌,先到「接入」tab 配置",
|
|
136
208
|
"provider.source.panel": "面板",
|
|
137
209
|
"provider.source.config": "配置",
|
|
138
210
|
"provider.contextWindow": "上下文窗口",
|
|
139
|
-
"provider.
|
|
140
|
-
"provider.
|
|
211
|
+
"provider.contextWindowOverride": "上下文窗口覆盖",
|
|
212
|
+
"provider.contextDefault": "不覆盖(默认)",
|
|
213
|
+
"provider.contextHint": "目录未声明窗口时套用此值(会盖过内置策展表);保存后立即重新注册",
|
|
141
214
|
"provider.contextCuratedTitle": "窗口来自内置策展表(models.dev 交叉真值),平台未亲口声明;带 * 标记",
|
|
142
215
|
"section.draw": "出图工具",
|
|
143
|
-
"draw.enable": "
|
|
216
|
+
"draw.enable": "开启后,agent 可在对话里用 modelscope_draw_image 生成或编辑图片",
|
|
144
217
|
"draw.on": "已开启",
|
|
145
218
|
"draw.off": "未开启",
|
|
146
219
|
"draw.busy": "切换中…",
|
|
147
220
|
"draw.mounted": "已注册:{tool}",
|
|
148
|
-
"draw.notMounted": "
|
|
149
|
-
"draw.restartHint": "
|
|
150
|
-
"draw.
|
|
221
|
+
"draw.notMounted": "未注册——打开上方开关即可开启。",
|
|
222
|
+
"draw.restartHint": "开关立即保存;工具要到下次启动 DSH 才挂载(挂载后无法撤销)。",
|
|
223
|
+
"draw.currentNone": "当前目录里没有可出图的模型——出图会失败(先确认访问令牌已配置、目录已拉到)。",
|
|
224
|
+
"draw.currentSrc.declaredTextToImage": "平台声明为文生图",
|
|
225
|
+
"draw.currentSrc.knownTextToImage": "已知可纯文生图",
|
|
226
|
+
"draw.currentSrc.catalogFirst": "目录里的第一个出图模型",
|
|
227
|
+
"draw.autoHint": "由插件按能力挑;当前会选 {model}",
|
|
228
|
+
"draw.autoHintReason": "由插件按能力挑;当前会选 {model}({reason})",
|
|
229
|
+
"draw.models": "出图模型",
|
|
151
230
|
"draw.badge": "出图",
|
|
152
|
-
"draw.
|
|
231
|
+
"draw.badge.textToImage": "文生图",
|
|
232
|
+
"draw.badge.imageToImage": "图生图",
|
|
233
|
+
"draw.imageOnlyTitle": "该模型只能图生图/改图:必须通过 imageUrl 传一张输入图,不能纯文生图。",
|
|
234
|
+
"draw.auto": "自动选择",
|
|
235
|
+
"draw.noCandidates": "当前目录里没有可出图的模型,这个工具暂时用不了。",
|
|
153
236
|
"draw.needsKey": "还没有访问令牌,先到「接入」tab 配置。",
|
|
154
237
|
"draw.reset": "回到默认",
|
|
155
238
|
"draw.error": "出图设置保存失败:{error}",
|
|
156
239
|
"draw.source.panel": "面板",
|
|
157
240
|
"draw.source.config": "配置",
|
|
158
|
-
"draw.absent.noTools": "
|
|
241
|
+
"draw.absent.noTools": "开关已开,但本机没有 tools 服务,工具没挂上(Host 侧限制,不是配置问题)。",
|
|
159
242
|
"draw.absent.peerLoad": "开关已开,但 tools 模块加载失败,出图工具缺席(Host 问题,非配置)。",
|
|
160
243
|
"draw.absent.registry": "开关已开,但工具注册被 Host 拒绝,出图工具缺席(Host 问题,非配置)。",
|
|
161
244
|
"draw.absent.unknown": "开关已开,但出图工具未挂载,原因未知。",
|
|
162
245
|
"trend.none": "还没有本地调用记录。",
|
|
163
246
|
"trend.legend": "柱长相对区间内最大值,仅本地口径。",
|
|
247
|
+
"trend.window": "显示天数",
|
|
248
|
+
"trend.windowDefault": "默认(跟随配置)",
|
|
249
|
+
"trend.windowDays": "{days} 天",
|
|
250
|
+
"trend.windowHint": "只决定画多少天;能回看多久另有一个保留窗口(留得比画得多才画得出更远)。",
|
|
164
251
|
"events.none": "暂无事件。",
|
|
165
252
|
"events.quota": "额度",
|
|
166
253
|
"events.rate_limit": "限频",
|
|
@@ -212,7 +299,7 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
212
299
|
"tab.models": "Models",
|
|
213
300
|
"tab.access": "Access",
|
|
214
301
|
"section.balance": "Magicube balance (official)",
|
|
215
|
-
"section.local": "Local calls (only through this plugin)",
|
|
302
|
+
"section.local": "Local calls (this client · only through this plugin)",
|
|
216
303
|
"section.trend": "Local call trend, last {days} days",
|
|
217
304
|
"section.events": "Rate-limit and error events",
|
|
218
305
|
"section.token": "Access token",
|
|
@@ -226,14 +313,16 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
226
313
|
"balance.usagePage": "Official usage details (web) →",
|
|
227
314
|
"balance.unavailable": "Magicube balance temporarily unreadable: {error}",
|
|
228
315
|
"quota.headline": "Today {calls} calls · {models} models",
|
|
229
|
-
"quota.
|
|
316
|
+
"quota.rowCalls": "{calls} calls",
|
|
317
|
+
"quota.rowTokens": "{tokens} tokens",
|
|
318
|
+
"quota.note": "Local scope: only calls this client made through this plugin are counted (desktop and web keep separate ledgers); clients calling ModelScope directly are not. Total consumption is governed by the official Magicube balance.",
|
|
230
319
|
"quota.unavailable": "Local counters could not be read this time (reason in the notices above)",
|
|
231
320
|
"probe.validity": "Verify token (free)",
|
|
232
321
|
"probe.validOk": "Token valid (HTTP {status})",
|
|
233
322
|
"probe.fail": "Failed: {error}",
|
|
234
323
|
"models.viewOnSite": "View on ModelScope →",
|
|
235
324
|
"models.viewOnSiteTitle": "Open the model page (per-model Magicube price is web-only)",
|
|
236
|
-
"models.featuredTitle": "Recommended
|
|
325
|
+
"models.featuredTitle": "Recommended vendors (DeepSeek / Zhipu / Qwen) — their models are pinned to the top",
|
|
237
326
|
"provider.featured": "Recommended",
|
|
238
327
|
"section.provider": "Register as a DSH model",
|
|
239
328
|
"provider.enable": "Register ModelScope models in the DSH model picker",
|
|
@@ -251,37 +340,56 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
251
340
|
"provider.reset": "Reset",
|
|
252
341
|
"provider.enabledCount": "{count} / {total} enabled",
|
|
253
342
|
"provider.rosterHint": "Tick the models to offer; unticked = offer all",
|
|
343
|
+
"provider.search": "Search model name or ID",
|
|
344
|
+
"provider.noMatch": "No matching models.",
|
|
345
|
+
"provider.groupCount": "{count}",
|
|
346
|
+
"provider.groupSelected": "{count} selected",
|
|
254
347
|
"provider.quotaExhausted": "Quota exhausted",
|
|
255
348
|
"provider.vision": "vision",
|
|
256
349
|
"provider.notConfigured": "No token yet — configure it in the Access tab",
|
|
257
350
|
"provider.source.panel": "panel",
|
|
258
351
|
"provider.source.config": "config",
|
|
259
352
|
"provider.contextWindow": "Context window",
|
|
260
|
-
"provider.
|
|
261
|
-
"provider.
|
|
353
|
+
"provider.contextWindowOverride": "Context window override",
|
|
354
|
+
"provider.contextDefault": "No override (default)",
|
|
355
|
+
"provider.contextHint": "Applied when the catalog declares no window (it overrides the built-in curated table); re-registers immediately",
|
|
262
356
|
"provider.contextCuratedTitle": "Window from the built-in curated table (models.dev cross-check), not declared by the platform; marked with *",
|
|
263
357
|
"section.draw": "Draw tool",
|
|
264
|
-
"draw.enable": "
|
|
358
|
+
"draw.enable": "Once on, the agent can generate or edit images with modelscope_draw_image",
|
|
265
359
|
"draw.on": "On",
|
|
266
360
|
"draw.off": "Off",
|
|
267
361
|
"draw.busy": "Switching…",
|
|
268
362
|
"draw.mounted": "Registered: {tool}",
|
|
269
|
-
"draw.notMounted": "Not registered —
|
|
270
|
-
"draw.restartHint": "The switch saves at once
|
|
271
|
-
"draw.
|
|
363
|
+
"draw.notMounted": "Not registered — turn the switch above on.",
|
|
364
|
+
"draw.restartHint": "The switch saves at once; the tool mounts the next time DSH starts (a mount cannot be undone).",
|
|
365
|
+
"draw.currentNone": "No image model in the catalogue right now — drawing will fail (check the access token and that the catalogue has loaded).",
|
|
366
|
+
"draw.currentSrc.declaredTextToImage": "platform-declared text-to-image",
|
|
367
|
+
"draw.currentSrc.knownTextToImage": "known to accept text-to-image",
|
|
368
|
+
"draw.currentSrc.catalogFirst": "first image model in the catalogue",
|
|
369
|
+
"draw.autoHint": "Chosen by the plugin for capability; it would pick {model}",
|
|
370
|
+
"draw.autoHintReason": "Chosen by the plugin for capability; it would pick {model} ({reason})",
|
|
371
|
+
"draw.models": "Image model",
|
|
272
372
|
"draw.badge": "image",
|
|
273
|
-
"draw.
|
|
373
|
+
"draw.badge.textToImage": "text-to-image",
|
|
374
|
+
"draw.badge.imageToImage": "image-to-image",
|
|
375
|
+
"draw.imageOnlyTitle": "Image-to-image only: an input image must be passed via imageUrl; pure text-to-image is not supported.",
|
|
376
|
+
"draw.auto": "Auto",
|
|
377
|
+
"draw.noCandidates": "No image model in the catalogue right now, so this tool cannot work yet.",
|
|
274
378
|
"draw.needsKey": "No token yet — configure it in the Access tab.",
|
|
275
379
|
"draw.reset": "Reset",
|
|
276
380
|
"draw.error": "Saving the draw settings failed: {error}",
|
|
277
381
|
"draw.source.panel": "panel",
|
|
278
382
|
"draw.source.config": "config",
|
|
279
|
-
"draw.absent.noTools": "The switch is on, but this Host
|
|
383
|
+
"draw.absent.noTools": "The switch is on, but this Host has no tools service, so the tool is not mounted (a Host limitation, not a config problem).",
|
|
280
384
|
"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
385
|
"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
386
|
"draw.absent.unknown": "The switch is on, but the draw tool is not mounted; the reason is unknown.",
|
|
283
387
|
"trend.none": "No local call records yet.",
|
|
284
388
|
"trend.legend": "Bar lengths are relative to the maximum in the range (local scope only).",
|
|
389
|
+
"trend.window": "Days shown",
|
|
390
|
+
"trend.windowDefault": "Default (follow config)",
|
|
391
|
+
"trend.windowDays": "{days} days",
|
|
392
|
+
"trend.windowHint": "Only sets how many days are drawn; how far back history is kept is a separate retention window.",
|
|
285
393
|
"events.none": "No events.",
|
|
286
394
|
"events.quota": "Quota",
|
|
287
395
|
"events.rate_limit": "Rate limit",
|
|
@@ -315,7 +423,11 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
315
423
|
|
|
316
424
|
//#endregion
|
|
317
425
|
//#region src/client/format.ts
|
|
318
|
-
/**
|
|
426
|
+
/**
|
|
427
|
+
* Time and number formatters(与 sensenova 的 format.ts 同源、按需裁剪——去注释后
|
|
428
|
+
* 代码行重叠 73%,与 agnes 那份已分家(45%)。2026-10-16 实测,复核
|
|
429
|
+
* `node tools/dev/cross-repo-drift.mjs`)。
|
|
430
|
+
*/
|
|
319
431
|
/**
|
|
320
432
|
* Host 声明的 cadence(秒)转毫秒;无可用数字时回退。Host 在源头已 clamp,
|
|
321
433
|
* 面板不做第二次 opinion;亚秒值向上取整到 1s(setInterval 的 0 = 尽快)。
|
|
@@ -608,6 +720,46 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
608
720
|
lineHeight: "16px",
|
|
609
721
|
color: "var(--dsw-alias-label-secondary)"
|
|
610
722
|
},
|
|
723
|
+
trendChartBars: {
|
|
724
|
+
display: "flex",
|
|
725
|
+
alignItems: "flex-end",
|
|
726
|
+
gap: 2,
|
|
727
|
+
height: 96,
|
|
728
|
+
padding: "0 1px"
|
|
729
|
+
},
|
|
730
|
+
trendChartBar: {
|
|
731
|
+
flex: 1,
|
|
732
|
+
minWidth: 2,
|
|
733
|
+
height: "100%",
|
|
734
|
+
display: "flex",
|
|
735
|
+
alignItems: "flex-end",
|
|
736
|
+
position: "relative"
|
|
737
|
+
},
|
|
738
|
+
trendChartFill: {
|
|
739
|
+
width: "100%",
|
|
740
|
+
minHeight: 2,
|
|
741
|
+
borderRadius: 2,
|
|
742
|
+
background: BRAND
|
|
743
|
+
},
|
|
744
|
+
trendChartValue: {
|
|
745
|
+
position: "absolute",
|
|
746
|
+
left: 0,
|
|
747
|
+
right: 0,
|
|
748
|
+
fontSize: 9,
|
|
749
|
+
lineHeight: 1,
|
|
750
|
+
textAlign: "center",
|
|
751
|
+
color: "var(--dsw-alias-label-secondary)",
|
|
752
|
+
fontVariantNumeric: "tabular-nums",
|
|
753
|
+
pointerEvents: "none"
|
|
754
|
+
},
|
|
755
|
+
trendChartAxis: {
|
|
756
|
+
display: "flex",
|
|
757
|
+
justifyContent: "space-between",
|
|
758
|
+
gap: 12,
|
|
759
|
+
marginTop: 6,
|
|
760
|
+
fontSize: 11,
|
|
761
|
+
color: "var(--dsw-alias-label-secondary)"
|
|
762
|
+
},
|
|
611
763
|
muted: { color: "var(--dsw-alias-label-secondary)" },
|
|
612
764
|
error: { color: "var(--dsw-alias-state-error-primary)" },
|
|
613
765
|
empty: {
|
|
@@ -645,6 +797,22 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
645
797
|
fontSize: 12,
|
|
646
798
|
margin: "10px 0 0"
|
|
647
799
|
},
|
|
800
|
+
dotSep: {
|
|
801
|
+
color: "var(--dsw-alias-label-secondary)",
|
|
802
|
+
opacity: .55,
|
|
803
|
+
fontSize: 11
|
|
804
|
+
},
|
|
805
|
+
advancedBlock: {
|
|
806
|
+
marginTop: 14,
|
|
807
|
+
paddingTop: 12,
|
|
808
|
+
borderTop: "1px solid var(--dsw-alias-border-l1)"
|
|
809
|
+
},
|
|
810
|
+
advancedHint: {
|
|
811
|
+
marginTop: 6,
|
|
812
|
+
fontSize: 11,
|
|
813
|
+
lineHeight: "16px",
|
|
814
|
+
color: "var(--dsw-alias-label-secondary)"
|
|
815
|
+
},
|
|
648
816
|
rosterTools: {
|
|
649
817
|
display: "flex",
|
|
650
818
|
gap: 8,
|
|
@@ -682,6 +850,27 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
682
850
|
gap: 8
|
|
683
851
|
},
|
|
684
852
|
modelRowOff: { opacity: .55 },
|
|
853
|
+
modelGroup: {
|
|
854
|
+
display: "flex",
|
|
855
|
+
alignItems: "center",
|
|
856
|
+
gap: 6,
|
|
857
|
+
width: "100%",
|
|
858
|
+
padding: "10px 4px 2px",
|
|
859
|
+
background: "none",
|
|
860
|
+
border: "none",
|
|
861
|
+
textAlign: "left",
|
|
862
|
+
cursor: "pointer",
|
|
863
|
+
fontSize: 12,
|
|
864
|
+
fontWeight: 600,
|
|
865
|
+
color: "var(--dsw-alias-label-secondary)"
|
|
866
|
+
},
|
|
867
|
+
modelGroupOwner: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" },
|
|
868
|
+
modelGroupCount: {
|
|
869
|
+
fontWeight: 400,
|
|
870
|
+
opacity: .75,
|
|
871
|
+
fontVariantNumeric: "tabular-nums"
|
|
872
|
+
},
|
|
873
|
+
modelRowGrouped: { paddingLeft: 22 },
|
|
685
874
|
modelCheck: {
|
|
686
875
|
flex: "none",
|
|
687
876
|
width: 15,
|
|
@@ -768,33 +957,46 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
768
957
|
}));
|
|
769
958
|
|
|
770
959
|
//#endregion
|
|
771
|
-
//#region src/shared/
|
|
772
|
-
var
|
|
773
|
-
var
|
|
774
|
-
|
|
775
|
-
"
|
|
776
|
-
"
|
|
777
|
-
"
|
|
778
|
-
"
|
|
779
|
-
"
|
|
780
|
-
"
|
|
781
|
-
"
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
960
|
+
//#region src/shared/codes.ts
|
|
961
|
+
var CODE;
|
|
962
|
+
var init_codes = __esmMin((() => {
|
|
963
|
+
CODE = Object.freeze({
|
|
964
|
+
CONFIG_ERROR: "config_error",
|
|
965
|
+
NETWORK_ERROR: "network_error",
|
|
966
|
+
TIMEOUT_ERROR: "timeout_error",
|
|
967
|
+
AUTH_ERROR: "auth_error",
|
|
968
|
+
RATE_LIMITED: "rate_limited",
|
|
969
|
+
QUOTA_EXCEEDED: "quota_exceeded",
|
|
970
|
+
UPSTREAM_ERROR: "upstream_error",
|
|
971
|
+
/**
|
|
972
|
+
* 插件自身抛错(聚合器、状态层、路由之外的意外)。
|
|
973
|
+
*
|
|
974
|
+
* 曾经只作为 `soft()` / `failureCode()` 的兜底字符串出现在快照里,没进这张
|
|
975
|
+
* 表——于是 Client 的 `GUIDANCE_BY_CODE` 也没法映射它(那份映射被测试钉住
|
|
976
|
+
* 「每个键必须在 CODE 里」),最需要人看的内部错误反而没有引导文案。
|
|
977
|
+
* 正式收进码表,两端从此同一份真源。
|
|
978
|
+
*/
|
|
979
|
+
INTERNAL_ERROR: "internal_error",
|
|
980
|
+
/**
|
|
981
|
+
* Client 自产码:Host 响应无法解读成快照(非 JSON / 缺 ok 字段)时,
|
|
982
|
+
* 面板用它走引导文案,而不是把裸英文串("unexpected payload")上屏。
|
|
983
|
+
*
|
|
984
|
+
* 本表是 `GUIDANCE_BY_CODE` 的键集真源——新增 client 码必须同时进
|
|
985
|
+
* 这张表,两侧才同步。
|
|
986
|
+
*/
|
|
987
|
+
PAYLOAD_ERROR: "payload_error"
|
|
988
|
+
});
|
|
791
989
|
}));
|
|
792
990
|
|
|
793
991
|
//#endregion
|
|
794
992
|
//#region src/client/snapshot.ts
|
|
795
993
|
/**
|
|
796
|
-
* 缺失块的空形状,**逐字对齐 Host 的降级实现**(`snapshot-aggregate.ts`
|
|
797
|
-
* `
|
|
994
|
+
* 缺失块的空形状,**逐字对齐 Host 的降级实现**(`snapshot-aggregate.ts` 各
|
|
995
|
+
* `soft()` 失败分支、TokenStatus 的失败分支)。
|
|
996
|
+
*
|
|
997
|
+
* `provider` 块现在是**省略**而不是由 Host 塞一个降级块(2026-10-17):Host 那份
|
|
998
|
+
* 降级形状「看起来就是一个答案」,面板据此认为块在、不再走 `GET /provider` 兜底,
|
|
999
|
+
* 于是只能拿编出来的值渲染。省略之后,兜底真的跑起来,降级形状只剩这里一份。
|
|
798
1000
|
*
|
|
799
1001
|
* 这里的每一条都是「Host 那一路失败时真的产出的形状」,不是随手捏的:面板渲染
|
|
800
1002
|
* 的每一个字段都必须有一个与 Host 同构的空值,否则补出来的空块自己就会炸——
|
|
@@ -862,7 +1064,7 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
862
1064
|
function interpretSnapshot(body) {
|
|
863
1065
|
const payload = body;
|
|
864
1066
|
if (payload && payload.ok === false) {
|
|
865
|
-
const code = typeof payload.code === "string" && payload.code !== "" ? payload.code :
|
|
1067
|
+
const code = typeof payload.code === "string" && payload.code !== "" ? payload.code : CODE.PAYLOAD_ERROR;
|
|
866
1068
|
return {
|
|
867
1069
|
data: null,
|
|
868
1070
|
error: {
|
|
@@ -875,7 +1077,7 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
875
1077
|
data: null,
|
|
876
1078
|
error: {
|
|
877
1079
|
message: null,
|
|
878
|
-
code:
|
|
1080
|
+
code: CODE.PAYLOAD_ERROR
|
|
879
1081
|
}
|
|
880
1082
|
};
|
|
881
1083
|
const raw = payload;
|
|
@@ -946,13 +1148,25 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
946
1148
|
const candidates = Array.isArray(d.candidateIds) ? d.candidateIds.map((id) => String(id ?? "")).filter((id) => id !== "") : [];
|
|
947
1149
|
const reason = d.absentReason;
|
|
948
1150
|
const absentReason = reason === "no-tools-service" || reason === "peer-load-failed" || reason === "registry-refused" ? reason : null;
|
|
1151
|
+
const rawCandidates = Array.isArray(d.candidates) ? d.candidates.map((entry) => {
|
|
1152
|
+
const row = entry ?? {};
|
|
1153
|
+
return {
|
|
1154
|
+
id: String(row.id ?? ""),
|
|
1155
|
+
capability: row.capability === "text-to-image" ? "text-to-image" : row.capability === "image-to-image" ? "image-to-image" : "other",
|
|
1156
|
+
textToImage: row.textToImage === true
|
|
1157
|
+
};
|
|
1158
|
+
}).filter((row) => row.id !== "") : null;
|
|
1159
|
+
const effectiveSource = d.effectiveSource === "preferred" || d.effectiveSource === "declared-text-to-image" || d.effectiveSource === "known-text-to-image" || d.effectiveSource === "catalog-first" ? d.effectiveSource : null;
|
|
949
1160
|
return {
|
|
950
1161
|
enabled: d.enabled === true,
|
|
951
1162
|
source: d.source === "panel" ? "panel" : "config",
|
|
952
1163
|
candidateIds: candidates,
|
|
953
1164
|
candidateCount: typeof d.candidateCount === "number" && Number.isFinite(d.candidateCount) ? d.candidateCount : candidates.length,
|
|
1165
|
+
...rawCandidates !== null ? { candidates: rawCandidates } : {},
|
|
954
1166
|
preferredModel: typeof d.preferredModel === "string" && d.preferredModel !== "" ? d.preferredModel : null,
|
|
955
1167
|
preferredSource: d.preferredSource === "panel" ? "panel" : "config",
|
|
1168
|
+
..."effectiveModel" in d ? { effectiveModel: typeof d.effectiveModel === "string" && d.effectiveModel !== "" ? d.effectiveModel : null } : {},
|
|
1169
|
+
..."effectiveSource" in d ? { effectiveSource } : {},
|
|
956
1170
|
absentReason,
|
|
957
1171
|
mountPending: d.mountPending === true,
|
|
958
1172
|
toolName: typeof d.toolName === "string" && d.toolName !== "" ? d.toolName : "modelscope_draw_image"
|
|
@@ -984,11 +1198,11 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
984
1198
|
shapeWarnings
|
|
985
1199
|
};
|
|
986
1200
|
}
|
|
987
|
-
var
|
|
1201
|
+
var DEGRADED_PROVIDER, DEGRADED_DRAW, GUIDANCE_BY_CODE, FORM_EXCLUDED_CODES;
|
|
988
1202
|
var init_snapshot = __esmMin((() => {
|
|
989
1203
|
init_format();
|
|
990
1204
|
init_wire();
|
|
991
|
-
|
|
1205
|
+
init_codes();
|
|
992
1206
|
DEGRADED_PROVIDER = Object.freeze({
|
|
993
1207
|
enabled: false,
|
|
994
1208
|
source: "config",
|
|
@@ -1007,8 +1221,11 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
1007
1221
|
source: "config",
|
|
1008
1222
|
candidateIds: [],
|
|
1009
1223
|
candidateCount: 0,
|
|
1224
|
+
candidates: [],
|
|
1010
1225
|
preferredModel: null,
|
|
1011
1226
|
preferredSource: "config",
|
|
1227
|
+
effectiveModel: null,
|
|
1228
|
+
effectiveSource: null,
|
|
1012
1229
|
absentReason: null,
|
|
1013
1230
|
mountPending: false,
|
|
1014
1231
|
toolName: "modelscope_draw_image"
|
|
@@ -1113,6 +1330,73 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
1113
1330
|
TRAVEL = 13;
|
|
1114
1331
|
}));
|
|
1115
1332
|
|
|
1333
|
+
//#endregion
|
|
1334
|
+
//#region src/shared/allow-list.ts
|
|
1335
|
+
/**
|
|
1336
|
+
* 把线编码的 `enabledIds` 解析成联合——**全插件只有这一处解释**。
|
|
1337
|
+
*
|
|
1338
|
+
* 判据(与旧 `resolveAllowedList` 的承诺一致,且现在 `filterByEnabled` 等
|
|
1339
|
+
* 消费者也真的按它做):
|
|
1340
|
+
* 1. 非数组或空数组 → `all`(新装没做过选择,仍提供全部);
|
|
1341
|
+
* 2. 含哨兵(哪怕还混着别的 id)→ `none`(哨兵是独占态,优先于任何真实 id);
|
|
1342
|
+
* 3. 其余非空清单 → `list`(严格白名单;条目滤掉非字符串与空串、
|
|
1343
|
+
* 去重保序——落盘前 `provider-store` 已归一遍,这里是解释不是归一)。
|
|
1344
|
+
*
|
|
1345
|
+
* 注意 `[""]` 这类「滤完什么都不剩」的脏清单归 `list` 且 `ids: []`:过滤
|
|
1346
|
+
* 结果为空,与旧行为一致(它匹配不到任何真实 id)。
|
|
1347
|
+
* @param {unknown} enabledIds - 线编码的允许清单。
|
|
1348
|
+
* @returns {AllowedList} 联合。
|
|
1349
|
+
*/
|
|
1350
|
+
function parseAllowedList(enabledIds) {
|
|
1351
|
+
if (!Array.isArray(enabledIds) || enabledIds.length === 0) return { mode: "all" };
|
|
1352
|
+
if (enabledIds.includes("__hide_all__")) return { mode: "none" };
|
|
1353
|
+
const ids = [];
|
|
1354
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1355
|
+
for (const entry of enabledIds) {
|
|
1356
|
+
const id = typeof entry === "string" ? entry : "";
|
|
1357
|
+
if (id === "" || seen.has(id)) continue;
|
|
1358
|
+
seen.add(id);
|
|
1359
|
+
ids.push(id);
|
|
1360
|
+
}
|
|
1361
|
+
return {
|
|
1362
|
+
mode: "list",
|
|
1363
|
+
ids
|
|
1364
|
+
};
|
|
1365
|
+
}
|
|
1366
|
+
/**
|
|
1367
|
+
* 面板草稿 → 落盘清单的归一:哨兵独占态。
|
|
1368
|
+
*
|
|
1369
|
+
* 草稿含哨兵(「全部隐藏」)时落盘值只保留哨兵——不能让它与真实 id 混排:
|
|
1370
|
+
* Host 把混排判 `none`,而面板会把混排里的真实 id 画成勾中,「已启用 N」
|
|
1371
|
+
* 与实际提供 0 个撒谎(踩过,见 cards.ts 的注释)。其余情况剥掉哨兵、
|
|
1372
|
+
* 滤掉空串后原样落盘。
|
|
1373
|
+
* @param {readonly unknown[]} draft - 面板勾选的草稿。
|
|
1374
|
+
* @returns {string[]} 落盘的 `enabledIds`。
|
|
1375
|
+
*/
|
|
1376
|
+
function normalizeRosterSave(draft) {
|
|
1377
|
+
return rosterSaveOf(parseAllowedList(draft));
|
|
1378
|
+
}
|
|
1379
|
+
/**
|
|
1380
|
+
* 联合 → 落盘清单({@link parseAllowedList} 的逆运算):`none` 序列化成
|
|
1381
|
+
* 哨兵清单,`all` 序列化成空清单(「不过滤」的拼写),`list` 原样带出 id。
|
|
1382
|
+
* @param {AllowedList} list - 联合。
|
|
1383
|
+
* @returns {string[]} 线编码的 `enabledIds`。
|
|
1384
|
+
*/
|
|
1385
|
+
function rosterSaveOf(list) {
|
|
1386
|
+
switch (list.mode) {
|
|
1387
|
+
case "all": return [];
|
|
1388
|
+
case "none": return [HIDE_ALL_MODELS];
|
|
1389
|
+
case "list": return [...list.ids];
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
/** 「全部隐藏」的落盘清单({@link rosterSaveOf} 的 `none` 特例;每次新数组)。 */
|
|
1393
|
+
function hideAllRoster() {
|
|
1394
|
+
return rosterSaveOf({ mode: "none" });
|
|
1395
|
+
}
|
|
1396
|
+
var init_allow_list = __esmMin((() => {
|
|
1397
|
+
init_wire();
|
|
1398
|
+
}));
|
|
1399
|
+
|
|
1116
1400
|
//#endregion
|
|
1117
1401
|
//#region src/client/cards.ts
|
|
1118
1402
|
/** 可折叠 section 卡:全宽头部按钮 + 旋转 chevron;open/onToggle 由 props 进。 */
|
|
@@ -1195,7 +1479,7 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
1195
1479
|
}, h("div", { style: S.trendRowHead }, h("span", {
|
|
1196
1480
|
style: S.trendModel,
|
|
1197
1481
|
title: row.modelId
|
|
1198
|
-
}, row.modelId), h("span", { style: S.trendCredits }, count(row.calls) + (row.tokens !== null ? ` · ${count(row.tokens)}
|
|
1482
|
+
}, row.modelId), h("span", { style: S.trendCredits }, format(tt("quota.rowCalls"), { calls: count(row.calls) }) + (row.tokens !== null ? ` · ${format(tt("quota.rowTokens"), { tokens: count(row.tokens) })}` : ""))), pct >= 1 ? h("div", {
|
|
1199
1483
|
style: S.trendBar,
|
|
1200
1484
|
role: "progressbar",
|
|
1201
1485
|
"aria-valuenow": Math.round(pct),
|
|
@@ -1207,7 +1491,42 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
1207
1491
|
} })) : null);
|
|
1208
1492
|
}), h("div", { style: S.trendLegend }, tt("trend.legend")));
|
|
1209
1493
|
}
|
|
1210
|
-
/**
|
|
1494
|
+
/**
|
|
1495
|
+
* 轴上的日期短标注:砍掉年份与补零(`2026-09-18` → `9-18`)。
|
|
1496
|
+
*
|
|
1497
|
+
* 轴的跨度最多 trendDays(默认 14,多在同年内),年份在刻度间重复出现,占掉的
|
|
1498
|
+
* 宽度全是从图的呼吸里匀来的;补零的 `09` 同样多占一格而没多给信息。不是
|
|
1499
|
+
* `YYYY-MM-DD` 形状时原样返回——宁可显示一个陌生字符串,也不按位置硬切。
|
|
1500
|
+
* @param {string} key - 天桶 dateKey(本地日期 YYYY-MM-DD)。
|
|
1501
|
+
* @returns {string} 短标注。
|
|
1502
|
+
*/
|
|
1503
|
+
function shortDateKey(key) {
|
|
1504
|
+
const value = typeof key === "string" ? key : "";
|
|
1505
|
+
const match = /^\d{4}-(\d{2})-(\d{2})$/.exec(value);
|
|
1506
|
+
return match === null ? value : `${Number(match[1])}-${Number(match[2])}`;
|
|
1507
|
+
}
|
|
1508
|
+
/**
|
|
1509
|
+
* 轴刻度落在哪些桶上(返回下标)。
|
|
1510
|
+
*
|
|
1511
|
+
* 桶数 ≤ {@link MAX_TICKS} 时逐日都标;否则按 `(n-1)/(MAX_TICKS-1)` 等距取点、
|
|
1512
|
+
* 首尾必含——与姊妹插件 Agnes 的用量图同一取法(它 30 天标 7 个)。等距取点是
|
|
1513
|
+
* 对**下标**取整而不是对像素分均,所以刻度与其柱子所属的区间大体对齐,只是轴
|
|
1514
|
+
* 用 `space-between` 铺开;两端的日期恒为真实首末日。
|
|
1515
|
+
* @param {number} count - 桶数。
|
|
1516
|
+
* @returns {number[]} 升序下标。
|
|
1517
|
+
*/
|
|
1518
|
+
function tickIndices(count) {
|
|
1519
|
+
if (!Number.isFinite(count) || count <= 0) return [];
|
|
1520
|
+
if (count <= MAX_TICKS) return Array.from({ length: count }, (_, index) => index);
|
|
1521
|
+
const step = (count - 1) / 6;
|
|
1522
|
+
const out = [];
|
|
1523
|
+
for (let tick = 0; tick < MAX_TICKS; tick += 1) {
|
|
1524
|
+
const index = Math.round(tick * step);
|
|
1525
|
+
if (index > (out[out.length - 1] ?? -1)) out.push(index);
|
|
1526
|
+
}
|
|
1527
|
+
return out;
|
|
1528
|
+
}
|
|
1529
|
+
/** 近 N 天本地调用趋势(天桶;缺桶 = 该日 0 次,真实零)——**竖向柱状图**。 */
|
|
1211
1530
|
function TrendBars({ buckets, tt }) {
|
|
1212
1531
|
if (!Array.isArray(buckets) || buckets.length === 0) return h("div", { style: S.trendLegend }, tt("trend.none"));
|
|
1213
1532
|
const rows = buckets.map((bucket) => ({
|
|
@@ -1215,20 +1534,93 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
1215
1534
|
calls: Number.isFinite(bucket.calls) ? Math.max(0, bucket.calls) : 0
|
|
1216
1535
|
}));
|
|
1217
1536
|
const max = Math.max(0, ...rows.map((row) => row.calls));
|
|
1218
|
-
return h("div", null, rows.map((row) =>
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1537
|
+
return h("div", null, h("div", { style: S.trendChartBars }, rows.map((row) => {
|
|
1538
|
+
const pct = max > 0 ? row.calls / max * 100 : 0;
|
|
1539
|
+
return h("div", {
|
|
1540
|
+
key: row.dateKey,
|
|
1541
|
+
style: S.trendChartBar,
|
|
1542
|
+
title: `${row.dateKey}: ${count(row.calls)}`
|
|
1543
|
+
}, row.calls > 0 ? h("span", {
|
|
1544
|
+
style: {
|
|
1545
|
+
...S.trendChartValue,
|
|
1546
|
+
bottom: `calc(${pct}% + 3px)`
|
|
1547
|
+
},
|
|
1548
|
+
"aria-hidden": "true"
|
|
1549
|
+
}, count(row.calls)) : null, h("div", {
|
|
1550
|
+
style: {
|
|
1551
|
+
...S.trendChartFill,
|
|
1552
|
+
height: `${pct}%`
|
|
1553
|
+
},
|
|
1554
|
+
role: "progressbar",
|
|
1555
|
+
"aria-label": `${row.dateKey} ${count(row.calls)}`,
|
|
1556
|
+
"aria-valuenow": Math.round(pct),
|
|
1557
|
+
"aria-valuemin": 0,
|
|
1558
|
+
"aria-valuemax": 100
|
|
1559
|
+
}));
|
|
1560
|
+
})), h("div", { style: S.trendChartAxis }, tickIndices(rows.length).map((index) => {
|
|
1561
|
+
const key = rows[index]?.dateKey ?? "";
|
|
1562
|
+
return h("span", {
|
|
1563
|
+
key,
|
|
1564
|
+
title: key,
|
|
1565
|
+
style: { whiteSpace: "nowrap" }
|
|
1566
|
+
}, shortDateKey(key));
|
|
1567
|
+
})), h("div", { style: S.trendLegend }, tt("trend.legend")));
|
|
1568
|
+
}
|
|
1569
|
+
/**
|
|
1570
|
+
* 趋势显示窗口选择器:默认(跟随 `trendDays` 配置)/ 7 / 14 / 30 / 90 天。
|
|
1571
|
+
*
|
|
1572
|
+
* 它改的是**画多少天**,不是**留多少天**(保留窗口是 host-config 的
|
|
1573
|
+
* `retentionDays`)——所以档位只列到 `trend.maxDays` 为止:画得比留得多会把已剪的
|
|
1574
|
+
* 日子画成 0,那个组合在 Host 侧已被夹掉,这里也不给它入口。
|
|
1575
|
+
*
|
|
1576
|
+
* 保存成功后由调用方 `load()` 回显 Host 落盘的值(不做乐观更新,与 provider /
|
|
1577
|
+
* draw 的写路径同一纪律)。当前值不在档位里(例如手动改过 usage-prefs.json)时
|
|
1578
|
+
* value 与任何 option 都不匹配 → 浏览器显示第一项,但下一发 onChange 仍发正确的
|
|
1579
|
+
* 值——与上下文窗口选择器同一处可接受的边缘表现,不为它造「自定义」档位。
|
|
1580
|
+
*/
|
|
1581
|
+
function TrendWindowPicker({ trend, busy, error, onChange, tt }) {
|
|
1582
|
+
if (trend === void 0) return null;
|
|
1583
|
+
const maxDays = typeof trend.maxDays === "number" && Number.isFinite(trend.maxDays) && trend.maxDays > 0 ? Math.floor(trend.maxDays) : 0;
|
|
1584
|
+
const tiers = TREND_WINDOW_TIERS.filter((tier) => maxDays === 0 || tier <= maxDays);
|
|
1585
|
+
const current = trend.windowDays;
|
|
1586
|
+
return h("div", { style: {
|
|
1587
|
+
display: "flex",
|
|
1588
|
+
gap: 8,
|
|
1589
|
+
alignItems: "center",
|
|
1590
|
+
flexWrap: "wrap",
|
|
1591
|
+
marginBottom: 10
|
|
1592
|
+
} }, h("label", {
|
|
1222
1593
|
style: {
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
marginLeft: 10
|
|
1594
|
+
fontSize: 12,
|
|
1595
|
+
color: "var(--dsw-alias-label-secondary)"
|
|
1226
1596
|
},
|
|
1227
|
-
|
|
1228
|
-
},
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1597
|
+
htmlFor: "trend-window"
|
|
1598
|
+
}, tt("trend.window")), h("select", {
|
|
1599
|
+
id: "trend-window",
|
|
1600
|
+
style: {
|
|
1601
|
+
...S.rosterBulk,
|
|
1602
|
+
height: 28,
|
|
1603
|
+
width: "auto"
|
|
1604
|
+
},
|
|
1605
|
+
disabled: busy,
|
|
1606
|
+
value: current === null || current === void 0 ? "" : String(current),
|
|
1607
|
+
onChange: (event) => {
|
|
1608
|
+
const value = String(event.target?.value ?? "");
|
|
1609
|
+
onChange(value === "" ? null : Number(value));
|
|
1610
|
+
}
|
|
1611
|
+
}, h("option", {
|
|
1612
|
+
value: "",
|
|
1613
|
+
key: "default"
|
|
1614
|
+
}, tt("trend.windowDefault")), tiers.map((tier) => h("option", {
|
|
1615
|
+
value: String(tier),
|
|
1616
|
+
key: String(tier)
|
|
1617
|
+
}, format(tt("trend.windowDays"), { days: tier })))), h("span", { style: {
|
|
1618
|
+
...S.muted,
|
|
1619
|
+
fontSize: 11
|
|
1620
|
+
} }, tt("trend.windowHint")), error !== null ? h("span", {
|
|
1621
|
+
style: S.formError,
|
|
1622
|
+
role: "alert"
|
|
1623
|
+
}, error) : null);
|
|
1232
1624
|
}
|
|
1233
1625
|
/** 事件流(429/错误),Newest-first。 */
|
|
1234
1626
|
function EventsList({ events, tt }) {
|
|
@@ -1294,6 +1686,33 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
1294
1686
|
}, tt("token.link")));
|
|
1295
1687
|
}
|
|
1296
1688
|
/**
|
|
1689
|
+
* 按 owner 把清单分组,**推荐的三家置顶**(组内保持目录首见序)。
|
|
1690
|
+
*
|
|
1691
|
+
* 分组的理由只有一个:`owner/model` 的 owner 前缀在每一行里重复,而面板一屏能放
|
|
1692
|
+
* 十几行——同一个 owner 说十几遍既占宽度又不提供任何信息。置顶沿用「推荐」徽章的
|
|
1693
|
+
* 同一份 `FEATURED_OWNERS`,两处不可能对「哪三家值得置顶」说两套话。
|
|
1694
|
+
* @param {RosterRow[]} rows - 归一后的清单行。
|
|
1695
|
+
* @returns {{owner: string, rows: RosterRow[]}[]} 分组(置顶 owner 在前)。
|
|
1696
|
+
*/
|
|
1697
|
+
function groupByOwner(rows) {
|
|
1698
|
+
const order = [];
|
|
1699
|
+
const byOwner = /* @__PURE__ */ new Map();
|
|
1700
|
+
for (const row of rows) {
|
|
1701
|
+
const owner = catalogOwner(row.id);
|
|
1702
|
+
const bucket = byOwner.get(owner);
|
|
1703
|
+
if (bucket === void 0) {
|
|
1704
|
+
byOwner.set(owner, [row]);
|
|
1705
|
+
order.push(owner);
|
|
1706
|
+
} else bucket.push(row);
|
|
1707
|
+
}
|
|
1708
|
+
const featured = FEATURED_OWNERS.filter((owner) => byOwner.has(owner));
|
|
1709
|
+
const rest = order.filter((owner) => !FEATURED_OWNERS.includes(owner));
|
|
1710
|
+
return [...featured, ...rest].map((owner) => ({
|
|
1711
|
+
owner,
|
|
1712
|
+
rows: byOwner.get(owner) ?? []
|
|
1713
|
+
}));
|
|
1714
|
+
}
|
|
1715
|
+
/**
|
|
1297
1716
|
* 接入为 DSH 模型的区块(开关 + 注册状态 + 允许清单)。
|
|
1298
1717
|
*
|
|
1299
1718
|
* 这是 M4 把抽象「试调」升级成真接入的落点:开关翻转即 POST provider 路由,
|
|
@@ -1324,38 +1743,34 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
1324
1743
|
const hostIds = status.enabledIds;
|
|
1325
1744
|
const hostKey = useMemo(() => JSON.stringify(hostIds), [hostIds]);
|
|
1326
1745
|
const [draft, setDraft] = useState(() => hostIds.slice());
|
|
1327
|
-
const
|
|
1746
|
+
const [search, setSearch] = useState("");
|
|
1747
|
+
const [collapsed, setCollapsed] = useState([]);
|
|
1748
|
+
const hideAllMode = parseAllowedList(draft).mode === "none";
|
|
1328
1749
|
useEffect(() => {
|
|
1329
|
-
setDraft(
|
|
1750
|
+
setDraft(normalizeRosterSave(hostIds));
|
|
1330
1751
|
}, [hostKey]);
|
|
1331
1752
|
const roster = status.roster;
|
|
1332
1753
|
const rosterIds = roster.map((row) => row.id);
|
|
1333
|
-
const toggleOne = (id) => setDraft((current) =>
|
|
1754
|
+
const toggleOne = (id) => setDraft((current) => {
|
|
1755
|
+
const ids = parseAllowedList(current);
|
|
1756
|
+
const kept = ids.mode === "list" ? [...ids.ids] : [];
|
|
1757
|
+
return kept.includes(id) ? kept.filter((x) => x !== id) : [...kept, id];
|
|
1758
|
+
});
|
|
1334
1759
|
const ticked = hideAllMode ? 0 : roster.filter((row) => draft.includes(row.id)).length;
|
|
1335
|
-
const
|
|
1336
|
-
let statusNode;
|
|
1337
|
-
if (!degraded && typeof status.error === "string" && status.error !== "") statusNode = h("div", {
|
|
1760
|
+
const alertNode = !(status.error === "unavailable") && typeof status.error === "string" && status.error !== "" ? h("div", {
|
|
1338
1761
|
style: S.formError,
|
|
1339
1762
|
role: "alert"
|
|
1340
|
-
}, format(tt("provider.error"), { error: status.error }));
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
}
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
} }, tt("provider.llmMissing"));
|
|
1352
|
-
else statusNode = h("div", {
|
|
1353
|
-
style: {
|
|
1354
|
-
...S.muted,
|
|
1355
|
-
fontSize: 12
|
|
1356
|
-
},
|
|
1357
|
-
role: "status"
|
|
1358
|
-
}, tt("provider.notRegistered"));
|
|
1763
|
+
}, format(tt("provider.error"), { error: status.error })) : null;
|
|
1764
|
+
const statusPhrase = alertNode !== null ? null : status.registered === true ? {
|
|
1765
|
+
key: "provider.registered",
|
|
1766
|
+
warn: false
|
|
1767
|
+
} : status.llmAvailable !== true ? {
|
|
1768
|
+
key: "provider.llmMissing",
|
|
1769
|
+
warn: true
|
|
1770
|
+
} : {
|
|
1771
|
+
key: "provider.notRegistered",
|
|
1772
|
+
warn: false
|
|
1773
|
+
};
|
|
1359
1774
|
const tokenHint = enabled && tokenPresent === false ? h("div", { style: {
|
|
1360
1775
|
...S.formNote,
|
|
1361
1776
|
color: "var(--dsw-alias-state-warn-primary)"
|
|
@@ -1379,19 +1794,18 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
1379
1794
|
label: "1M"
|
|
1380
1795
|
}
|
|
1381
1796
|
];
|
|
1382
|
-
const contextNode = onSaveContextWindow === void 0 ? null : h("div", { style: {
|
|
1797
|
+
const contextNode = onSaveContextWindow === void 0 ? null : h("div", { style: S.advancedBlock }, h("div", { style: {
|
|
1383
1798
|
display: "flex",
|
|
1384
1799
|
gap: 8,
|
|
1385
1800
|
alignItems: "center",
|
|
1386
|
-
flexWrap: "wrap"
|
|
1387
|
-
margin: "0 0 8px"
|
|
1801
|
+
flexWrap: "wrap"
|
|
1388
1802
|
} }, h("label", {
|
|
1389
1803
|
style: {
|
|
1390
1804
|
fontSize: 12,
|
|
1391
1805
|
color: "var(--dsw-alias-label-secondary)"
|
|
1392
1806
|
},
|
|
1393
1807
|
htmlFor: "provider-context-window"
|
|
1394
|
-
}, tt("provider.
|
|
1808
|
+
}, tt("provider.contextWindowOverride")), h("select", {
|
|
1395
1809
|
id: "provider-context-window",
|
|
1396
1810
|
style: {
|
|
1397
1811
|
...S.rosterBulk,
|
|
@@ -1409,13 +1823,7 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
1409
1823
|
}, tt("provider.contextDefault")), CONTEXT_TIERS.map((tier) => h("option", {
|
|
1410
1824
|
value: String(tier.value),
|
|
1411
1825
|
key: String(tier.value)
|
|
1412
|
-
}, tier.label))), h("
|
|
1413
|
-
style: {
|
|
1414
|
-
...S.muted,
|
|
1415
|
-
fontSize: 11
|
|
1416
|
-
},
|
|
1417
|
-
title: tt("provider.contextHint")
|
|
1418
|
-
}, tt("provider.contextHint")));
|
|
1826
|
+
}, tier.label)))), h("div", { style: S.advancedHint }, tt("provider.contextHint")));
|
|
1419
1827
|
const switchRow = h("div", { style: {
|
|
1420
1828
|
display: "flex",
|
|
1421
1829
|
gap: 10,
|
|
@@ -1429,56 +1837,35 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
1429
1837
|
label: tt(enabled ? "provider.on" : "provider.off"),
|
|
1430
1838
|
busyLabel: tt("provider.busy"),
|
|
1431
1839
|
title: tt("provider.enable")
|
|
1432
|
-
}), h("span", {
|
|
1840
|
+
}), statusPhrase === null ? null : h("span", {
|
|
1841
|
+
style: S.dotSep,
|
|
1842
|
+
"aria-hidden": "true"
|
|
1843
|
+
}, "·"), statusPhrase === null ? null : h("span", {
|
|
1844
|
+
role: "status",
|
|
1845
|
+
style: {
|
|
1846
|
+
fontSize: 12,
|
|
1847
|
+
color: statusPhrase.warn ? "var(--dsw-alias-state-warn-primary)" : "var(--dsw-alias-label-secondary)"
|
|
1848
|
+
}
|
|
1849
|
+
}, tt(statusPhrase.key)), h("span", {
|
|
1850
|
+
style: S.dotSep,
|
|
1851
|
+
"aria-hidden": "true"
|
|
1852
|
+
}, "·"), h("span", { style: {
|
|
1433
1853
|
fontSize: 11,
|
|
1434
1854
|
color: "var(--dsw-alias-label-secondary)"
|
|
1435
1855
|
} }, tt(sourceKey)));
|
|
1436
|
-
const
|
|
1437
|
-
...S.muted,
|
|
1438
|
-
fontSize: 12,
|
|
1439
|
-
marginTop: 6
|
|
1440
|
-
} }, tt("provider.rosterHint")) : h("div", { style: S.modelPanel }, h("div", { style: S.rosterTools }, h("span", {
|
|
1441
|
-
style: S.rosterCount,
|
|
1442
|
-
title: format(tt("provider.enabledCount"), {
|
|
1443
|
-
count: ticked,
|
|
1444
|
-
total: roster.length
|
|
1445
|
-
})
|
|
1446
|
-
}, format(tt("provider.enabledCount"), {
|
|
1447
|
-
count: ticked,
|
|
1448
|
-
total: roster.length
|
|
1449
|
-
})), h("button", {
|
|
1450
|
-
type: "button",
|
|
1451
|
-
style: S.rosterBulk,
|
|
1452
|
-
disabled: busy,
|
|
1453
|
-
onClick: () => {
|
|
1454
|
-
setDraft(rosterIds.slice());
|
|
1455
|
-
onSaveList(rosterIds.slice());
|
|
1456
|
-
}
|
|
1457
|
-
}, tt("provider.allowAll")), h("button", {
|
|
1458
|
-
type: "button",
|
|
1459
|
-
style: S.rosterBulk,
|
|
1460
|
-
disabled: busy,
|
|
1461
|
-
onClick: () => {
|
|
1462
|
-
setDraft([HIDE_ALL_MODELS]);
|
|
1463
|
-
onSaveList([HIDE_ALL_MODELS]);
|
|
1464
|
-
}
|
|
1465
|
-
}, tt("provider.hideAll")), h("button", {
|
|
1466
|
-
type: "button",
|
|
1467
|
-
style: S.primary,
|
|
1468
|
-
disabled: busy,
|
|
1469
|
-
onClick: () => onSaveList(draft.slice())
|
|
1470
|
-
}, tt("provider.saveList"))), h("ul", {
|
|
1471
|
-
style: S.modelList,
|
|
1472
|
-
role: "list"
|
|
1473
|
-
}, roster.map((row) => {
|
|
1856
|
+
const renderRosterRow = (row) => {
|
|
1474
1857
|
const id = row.id;
|
|
1475
1858
|
const unusable = row.available === false || row.quotaExhausted === true;
|
|
1476
1859
|
return h("li", {
|
|
1477
1860
|
key: id,
|
|
1478
1861
|
style: unusable ? {
|
|
1479
1862
|
...S.modelRow,
|
|
1863
|
+
...S.modelRowGrouped,
|
|
1480
1864
|
...S.modelRowOff
|
|
1481
|
-
} :
|
|
1865
|
+
} : {
|
|
1866
|
+
...S.modelRow,
|
|
1867
|
+
...S.modelRowGrouped
|
|
1868
|
+
}
|
|
1482
1869
|
}, h("div", { style: S.modelRowHead }, h("label", { style: {
|
|
1483
1870
|
display: "flex",
|
|
1484
1871
|
alignItems: "center",
|
|
@@ -1496,39 +1883,128 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
1496
1883
|
}), h("span", {
|
|
1497
1884
|
style: S.modelName,
|
|
1498
1885
|
title: id
|
|
1499
|
-
}, row.name)), row.vision === true ? h("span", { style: S.modelBadge }, tt("provider.vision")) : null, typeof row.contextWindow === "number" ? h("span", {
|
|
1886
|
+
}, trimOwnerPrefix(id, row.name))), row.vision === true ? h("span", { style: S.modelBadge }, tt("provider.vision")) : null, typeof row.contextWindow === "number" ? h("span", {
|
|
1500
1887
|
style: row.contextSource === "curated" ? {
|
|
1501
1888
|
...S.modelBadge,
|
|
1502
1889
|
color: "var(--dsw-alias-label-secondary)"
|
|
1503
1890
|
} : S.modelBadge,
|
|
1504
1891
|
title: row.contextSource === "curated" ? tt("provider.contextCuratedTitle") : tt("provider.contextWindow")
|
|
1505
|
-
}, row.contextSource === "curated" ? formatWindow(row.contextWindow) + " *" : formatWindow(row.contextWindow)) : null,
|
|
1506
|
-
style: S.modelBadgeFeatured,
|
|
1507
|
-
title: tt("models.featuredTitle")
|
|
1508
|
-
}, tt("provider.featured")) : null, row.quotaExhausted === true ? h("span", { style: {
|
|
1892
|
+
}, row.contextSource === "curated" ? formatWindow(row.contextWindow) + " *" : formatWindow(row.contextWindow)) : null, row.quotaExhausted === true ? h("span", { style: {
|
|
1509
1893
|
...S.modelBadge,
|
|
1510
1894
|
color: "var(--dsw-alias-state-error-primary)"
|
|
1511
1895
|
} }, tt("provider.quotaExhausted")) : null, h("a", {
|
|
1512
1896
|
style: {
|
|
1513
1897
|
...S.formNote,
|
|
1514
1898
|
margin: 0,
|
|
1515
|
-
marginLeft: "auto"
|
|
1899
|
+
marginLeft: "auto",
|
|
1900
|
+
whiteSpace: "nowrap"
|
|
1516
1901
|
},
|
|
1517
1902
|
href: modelscopeModelUrl(row.id),
|
|
1518
1903
|
target: "_blank",
|
|
1519
1904
|
rel: "noreferrer",
|
|
1520
1905
|
title: tt("models.viewOnSiteTitle")
|
|
1521
1906
|
}, tt("models.viewOnSite"))));
|
|
1522
|
-
}
|
|
1907
|
+
};
|
|
1908
|
+
const query = search.trim().toLowerCase();
|
|
1909
|
+
const visibleRows = query === "" ? roster : roster.filter((row) => `${row.id} ${String(row.name ?? "")}`.toLowerCase().includes(query));
|
|
1910
|
+
const ownerGroups = groupByOwner(visibleRows);
|
|
1911
|
+
const rosterBlock = roster.length === 0 ? h("div", { style: {
|
|
1912
|
+
...S.muted,
|
|
1913
|
+
fontSize: 12,
|
|
1914
|
+
marginTop: 6
|
|
1915
|
+
} }, tt("provider.rosterHint")) : h("div", { style: S.modelPanel }, h("div", { style: S.rosterTools }, h("input", {
|
|
1916
|
+
type: "search",
|
|
1917
|
+
name: "provider-model-search",
|
|
1918
|
+
autoComplete: "off",
|
|
1919
|
+
style: {
|
|
1920
|
+
...S.input,
|
|
1921
|
+
flex: "1 1 160px",
|
|
1922
|
+
minWidth: 0
|
|
1923
|
+
},
|
|
1924
|
+
placeholder: tt("provider.search"),
|
|
1925
|
+
"aria-label": tt("provider.search"),
|
|
1926
|
+
value: search,
|
|
1927
|
+
disabled: busy,
|
|
1928
|
+
onChange: (event) => setSearch(String(event.target?.value ?? ""))
|
|
1929
|
+
}), h("span", {
|
|
1930
|
+
style: S.rosterCount,
|
|
1931
|
+
title: format(tt("provider.enabledCount"), {
|
|
1932
|
+
count: ticked,
|
|
1933
|
+
total: roster.length
|
|
1934
|
+
})
|
|
1935
|
+
}, format(tt("provider.enabledCount"), {
|
|
1936
|
+
count: ticked,
|
|
1937
|
+
total: roster.length
|
|
1938
|
+
})), h("button", {
|
|
1939
|
+
type: "button",
|
|
1940
|
+
style: S.rosterBulk,
|
|
1941
|
+
disabled: busy,
|
|
1942
|
+
onClick: () => {
|
|
1943
|
+
setDraft(rosterIds.slice());
|
|
1944
|
+
onSaveList(rosterIds.slice());
|
|
1945
|
+
}
|
|
1946
|
+
}, tt("provider.allowAll")), h("button", {
|
|
1947
|
+
type: "button",
|
|
1948
|
+
style: S.rosterBulk,
|
|
1949
|
+
disabled: busy,
|
|
1950
|
+
onClick: () => {
|
|
1951
|
+
const none = hideAllRoster();
|
|
1952
|
+
setDraft(none);
|
|
1953
|
+
onSaveList(none);
|
|
1954
|
+
}
|
|
1955
|
+
}, tt("provider.hideAll")), h("button", {
|
|
1956
|
+
type: "button",
|
|
1957
|
+
style: S.primary,
|
|
1958
|
+
disabled: busy,
|
|
1959
|
+
onClick: () => onSaveList(draft.slice())
|
|
1960
|
+
}, tt("provider.saveList"))), visibleRows.length === 0 ? h("div", { style: {
|
|
1961
|
+
...S.muted,
|
|
1962
|
+
fontSize: 12,
|
|
1963
|
+
padding: "10px 4px"
|
|
1964
|
+
} }, tt("provider.noMatch")) : h("ul", {
|
|
1965
|
+
style: S.modelList,
|
|
1966
|
+
role: "list"
|
|
1967
|
+
}, ownerGroups.map((group) => {
|
|
1968
|
+
const isCollapsed = query === "" && collapsed.includes(group.owner);
|
|
1969
|
+
const groupTicked = hideAllMode ? 0 : group.rows.filter((row) => draft.includes(row.id)).length;
|
|
1970
|
+
return h("li", { key: group.owner }, h("button", {
|
|
1971
|
+
type: "button",
|
|
1972
|
+
style: S.modelGroup,
|
|
1973
|
+
"aria-expanded": !isCollapsed,
|
|
1974
|
+
"aria-label": `${tt(isCollapsed ? "section.expand" : "section.collapse")}: ${group.owner}`,
|
|
1975
|
+
onClick: () => setCollapsed((current) => current.includes(group.owner) ? current.filter((owner) => owner !== group.owner) : [...current, group.owner])
|
|
1976
|
+
}, h("svg", {
|
|
1977
|
+
viewBox: "0 0 16 16",
|
|
1978
|
+
width: 12,
|
|
1979
|
+
height: 12,
|
|
1980
|
+
fill: "none",
|
|
1981
|
+
stroke: "currentColor",
|
|
1982
|
+
strokeWidth: "1.5",
|
|
1983
|
+
strokeLinecap: "round",
|
|
1984
|
+
strokeLinejoin: "round",
|
|
1985
|
+
"aria-hidden": "true",
|
|
1986
|
+
style: isCollapsed ? {
|
|
1987
|
+
...S.chevron,
|
|
1988
|
+
transform: "rotate(-90deg)"
|
|
1989
|
+
} : S.chevron
|
|
1990
|
+
}, h("path", { d: "M3 6l5 5 5-5" })), h("span", { style: S.modelGroupOwner }, group.owner), FEATURED_OWNERS.includes(group.owner) ? h("span", {
|
|
1991
|
+
style: S.modelBadgeFeatured,
|
|
1992
|
+
title: tt("models.featuredTitle")
|
|
1993
|
+
}, tt("provider.featured")) : null, h("span", { style: S.modelGroupCount }, format(tt("provider.groupCount"), { count: group.rows.length })), groupTicked > 0 ? h("span", { style: S.modelGroupCount }, format(tt("provider.groupSelected"), { count: groupTicked })) : null), isCollapsed ? null : h("ul", {
|
|
1994
|
+
style: S.modelList,
|
|
1995
|
+
role: "list"
|
|
1996
|
+
}, group.rows.map(renderRosterRow)));
|
|
1997
|
+
})));
|
|
1998
|
+
const resetRow = h("div", { style: S.rosterFoot }, h("button", {
|
|
1523
1999
|
type: "button",
|
|
1524
2000
|
style: S.button,
|
|
1525
2001
|
disabled: busy,
|
|
1526
2002
|
onClick: onReset
|
|
1527
|
-
}, tt("provider.reset")))
|
|
1528
|
-
return h("div", null, switchRow,
|
|
2003
|
+
}, tt("provider.reset")));
|
|
2004
|
+
return h("div", null, switchRow, alertNode, tokenHint, h("div", { style: {
|
|
1529
2005
|
...S.sectionTitle,
|
|
1530
2006
|
margin: "18px 0 8px"
|
|
1531
|
-
} }, tt("provider.models")), rosterBlock, error !== null ? h("div", {
|
|
2007
|
+
} }, tt("provider.models")), rosterBlock, contextNode, resetRow, error !== null ? h("div", {
|
|
1532
2008
|
style: S.formError,
|
|
1533
2009
|
role: "alert"
|
|
1534
2010
|
}, error) : null);
|
|
@@ -1547,11 +2023,15 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
1547
2023
|
* @param {object} props - 渲染参数。
|
|
1548
2024
|
* @returns {unknown} 卡片节点。
|
|
1549
2025
|
*/
|
|
1550
|
-
function DrawCard({ draw, busy, error, onToggle, onReset, tokenPresent, tt }) {
|
|
2026
|
+
function DrawCard({ draw, busy, error, onToggle, onReset, onSelectModel, onAutoModel, tokenPresent, tt }) {
|
|
1551
2027
|
const status = drawOf(draw);
|
|
1552
2028
|
const enabled = status.enabled === true;
|
|
1553
2029
|
const candidates = status.candidateIds;
|
|
1554
2030
|
const sourceKey = status.source === "panel" ? "draw.source.panel" : "draw.source.config";
|
|
2031
|
+
const effectiveModel = status.effectiveModel;
|
|
2032
|
+
const hasEffectiveKey = effectiveModel !== void 0;
|
|
2033
|
+
const candidateDetails = status.candidates;
|
|
2034
|
+
const autoReasonKey = status.effectiveSource === "declared-text-to-image" ? "draw.currentSrc.declaredTextToImage" : status.effectiveSource === "known-text-to-image" ? "draw.currentSrc.knownTextToImage" : status.effectiveSource === "catalog-first" ? "draw.currentSrc.catalogFirst" : null;
|
|
1555
2035
|
const switchRow = h("div", { style: {
|
|
1556
2036
|
display: "flex",
|
|
1557
2037
|
gap: 10,
|
|
@@ -1602,32 +2082,113 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
1602
2082
|
...S.formNote,
|
|
1603
2083
|
color: "var(--dsw-alias-state-warn-primary)"
|
|
1604
2084
|
} }, tt("draw.needsKey")) : null;
|
|
1605
|
-
const
|
|
2085
|
+
const autoDescription = !hasEffectiveKey || status.preferredModel !== null ? null : effectiveModel === null || effectiveModel === "" ? h("div", { style: {
|
|
1606
2086
|
...S.muted,
|
|
1607
|
-
fontSize:
|
|
1608
|
-
|
|
1609
|
-
|
|
2087
|
+
fontSize: 11,
|
|
2088
|
+
lineHeight: "16px",
|
|
2089
|
+
paddingLeft: 25
|
|
2090
|
+
} }, tt("draw.currentNone")) : h("div", { style: {
|
|
2091
|
+
...S.muted,
|
|
2092
|
+
fontSize: 11,
|
|
2093
|
+
lineHeight: "16px",
|
|
2094
|
+
paddingLeft: 25
|
|
2095
|
+
} }, autoReasonKey === null ? format(tt("draw.autoHint"), { model: effectiveModel }) : format(tt("draw.autoHintReason"), {
|
|
2096
|
+
model: effectiveModel,
|
|
2097
|
+
reason: tt(autoReasonKey)
|
|
2098
|
+
}));
|
|
2099
|
+
const RADIO_NAME = "modelscope-draw-model";
|
|
2100
|
+
/**
|
|
2101
|
+
* 一个候选的能力徽章(**正向各给一枚**,不是「能力减项 + 警告」)。
|
|
2102
|
+
*
|
|
2103
|
+
* 之前写成「平台能力档一枚 + 不能纯文生图时再加警告」是不对称的:能吃纯文生图
|
|
2104
|
+
* 是**加项优势**,却在 UI 上完全不可见,于是两个 Qwen-Image-Edit 看起来一样,
|
|
2105
|
+
* 其中一个还莫名带着警告。现在两个事实各画一枚:
|
|
2106
|
+
* - `图生图`——平台声明的 image-to-image(改图/图生图);
|
|
2107
|
+
* - `文生图`——能吃纯文生图(平台声明的 text-to-image,或实测已知的那个 id)。
|
|
2108
|
+
* 只给「图生图」的模型,徽章上挂 title 说明它必须给输入图(把警告从第二枚
|
|
2109
|
+
* 徽章降级成悬停说明,列表不再被警告噪声占满)。
|
|
2110
|
+
* 旧 Host 没有候选明细时退化为通用「出图」徽章——不猜能力。
|
|
2111
|
+
*/
|
|
2112
|
+
const capabilityBadges = (row) => {
|
|
2113
|
+
if (row.capability === null) return [h("span", {
|
|
2114
|
+
key: "badge",
|
|
2115
|
+
style: S.modelBadge
|
|
2116
|
+
}, tt("draw.badge"))];
|
|
2117
|
+
const badges = [];
|
|
2118
|
+
if (row.capability === "image-to-image") badges.push(h("span", {
|
|
2119
|
+
key: "badge-i2i",
|
|
2120
|
+
style: S.modelBadge,
|
|
2121
|
+
title: row.textToImage === true ? void 0 : tt("draw.imageOnlyTitle")
|
|
2122
|
+
}, tt("draw.badge.imageToImage")));
|
|
2123
|
+
if (row.textToImage === true) badges.push(h("span", {
|
|
2124
|
+
key: "badge-t2i",
|
|
2125
|
+
style: S.modelBadge
|
|
2126
|
+
}, tt("draw.badge.textToImage")));
|
|
2127
|
+
if (badges.length === 0) badges.push(h("span", {
|
|
2128
|
+
key: "badge",
|
|
2129
|
+
style: S.modelBadge
|
|
2130
|
+
}, tt("draw.badge")));
|
|
2131
|
+
return badges;
|
|
2132
|
+
};
|
|
2133
|
+
const candidateRows = Array.isArray(candidateDetails) ? candidateDetails.map((row) => ({
|
|
2134
|
+
id: row.id,
|
|
2135
|
+
capability: row.capability,
|
|
2136
|
+
textToImage: row.textToImage
|
|
2137
|
+
})) : candidates.map((id) => ({
|
|
2138
|
+
id,
|
|
2139
|
+
capability: null,
|
|
2140
|
+
textToImage: null
|
|
2141
|
+
}));
|
|
2142
|
+
/**
|
|
2143
|
+
* 一行广播的骨架:单选框 + 名字(+ 其余徽章由调用方给),可选第二行说明。
|
|
2144
|
+
*
|
|
2145
|
+
* `description` 是「这一项是什么」的地方(只有「自动选择」需要),缩进对齐到
|
|
2146
|
+
* 文字起点(复选框 15px + gap 10px = 25px),读起来是该项的注脚而不是新一行。
|
|
2147
|
+
*/
|
|
2148
|
+
const radioRow = (key, checked, label, labelText, disabled, onChange, extra, description) => h("li", {
|
|
2149
|
+
key,
|
|
2150
|
+
style: S.modelRow
|
|
2151
|
+
}, h("div", { style: S.modelRowHead }, h("label", { style: {
|
|
1610
2152
|
display: "flex",
|
|
1611
|
-
|
|
1612
|
-
gap:
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
}, h("
|
|
2153
|
+
alignItems: "center",
|
|
2154
|
+
gap: 10,
|
|
2155
|
+
flex: "1 1 auto",
|
|
2156
|
+
minWidth: 0,
|
|
2157
|
+
cursor: disabled ? "default" : "pointer"
|
|
2158
|
+
} }, h("input", {
|
|
2159
|
+
type: "radio",
|
|
2160
|
+
name: RADIO_NAME,
|
|
2161
|
+
checked,
|
|
2162
|
+
disabled,
|
|
2163
|
+
onChange,
|
|
2164
|
+
style: S.modelCheck,
|
|
2165
|
+
"aria-label": label
|
|
2166
|
+
}), h("span", {
|
|
1617
2167
|
style: S.modelName,
|
|
1618
|
-
title:
|
|
1619
|
-
},
|
|
2168
|
+
title: label
|
|
2169
|
+
}, labelText)), ...extra), ...description === void 0 ? [] : [description]);
|
|
2170
|
+
const candidateBlock = candidateRows.length === 0 ? h("div", { style: {
|
|
2171
|
+
...S.muted,
|
|
2172
|
+
fontSize: 12,
|
|
2173
|
+
marginTop: 6
|
|
2174
|
+
} }, tt("draw.noCandidates")) : h("div", { style: S.modelPanel }, h("ul", {
|
|
2175
|
+
style: S.modelList,
|
|
2176
|
+
role: "radiogroup",
|
|
2177
|
+
"aria-label": tt("draw.models")
|
|
2178
|
+
}, radioRow("__auto__", status.preferredModel === null, tt("draw.auto"), tt("draw.auto"), busy, () => onAutoModel(), [], autoDescription === null ? void 0 : autoDescription), ...candidateRows.map((row) => radioRow(row.id, status.preferredModel === row.id, row.id, row.id, busy, () => onSelectModel(row.id), [...capabilityBadges(row), h("a", {
|
|
2179
|
+
key: "link",
|
|
1620
2180
|
style: {
|
|
1621
2181
|
...S.formNote,
|
|
1622
2182
|
margin: 0,
|
|
1623
|
-
marginLeft: "auto"
|
|
2183
|
+
marginLeft: "auto",
|
|
2184
|
+
whiteSpace: "nowrap"
|
|
1624
2185
|
},
|
|
1625
|
-
href: modelscopeModelUrl(id),
|
|
2186
|
+
href: modelscopeModelUrl(row.id),
|
|
1626
2187
|
target: "_blank",
|
|
1627
2188
|
rel: "noreferrer",
|
|
1628
2189
|
title: tt("models.viewOnSiteTitle")
|
|
1629
|
-
}, tt("models.viewOnSite")))));
|
|
1630
|
-
return h("div", null, switchRow, statusNode,
|
|
2190
|
+
}, tt("models.viewOnSite"))]))));
|
|
2191
|
+
return h("div", null, switchRow, statusNode, h("div", { style: {
|
|
1631
2192
|
...S.sectionTitle,
|
|
1632
2193
|
margin: "18px 0 8px"
|
|
1633
2194
|
} }, tt("draw.models")), candidateBlock, h("div", { style: S.rosterFoot }, h("button", {
|
|
@@ -1638,9 +2199,9 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
1638
2199
|
}, tt("draw.reset"))), error !== null ? h("div", {
|
|
1639
2200
|
style: S.formError,
|
|
1640
2201
|
role: "alert"
|
|
1641
|
-
}, error) : null);
|
|
2202
|
+
}, error) : null, restartHint, tokenHint);
|
|
1642
2203
|
}
|
|
1643
|
-
var EVENT_KIND_KEY, DRAW_ABSENT_KEY;
|
|
2204
|
+
var MAX_TICKS, TREND_WINDOW_TIERS, EVENT_KIND_KEY, DRAW_ABSENT_KEY;
|
|
1644
2205
|
var init_cards = __esmMin((() => {
|
|
1645
2206
|
init_format();
|
|
1646
2207
|
init_runtime();
|
|
@@ -1648,6 +2209,14 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
1648
2209
|
init_const();
|
|
1649
2210
|
init_snapshot();
|
|
1650
2211
|
init_toggle_switch();
|
|
2212
|
+
init_allow_list();
|
|
2213
|
+
MAX_TICKS = 7;
|
|
2214
|
+
TREND_WINDOW_TIERS = [
|
|
2215
|
+
7,
|
|
2216
|
+
14,
|
|
2217
|
+
30,
|
|
2218
|
+
90
|
|
2219
|
+
];
|
|
1651
2220
|
EVENT_KIND_KEY = {
|
|
1652
2221
|
quota: "events.quota",
|
|
1653
2222
|
rate_limit: "events.rate_limit",
|
|
@@ -1960,8 +2529,7 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
1960
2529
|
]);
|
|
1961
2530
|
const toggleProvider = useCallback((enabled) => void runProviderWrite(PROVIDER_PATH, { enabled }), [runProviderWrite]);
|
|
1962
2531
|
const saveRoster = useCallback((enabledIds) => {
|
|
1963
|
-
|
|
1964
|
-
runProviderWrite(PROVIDER_ROSTER_PATH, { enabledIds: normalized });
|
|
2532
|
+
runProviderWrite(PROVIDER_ROSTER_PATH, { enabledIds: normalizeRosterSave(enabledIds) });
|
|
1965
2533
|
}, [runProviderWrite]);
|
|
1966
2534
|
const resetProvider = useCallback(() => void runProviderWrite(PROVIDER_RESET_PATH, {}), [runProviderWrite]);
|
|
1967
2535
|
const saveContextWindow = useCallback((contextWindow) => {
|
|
@@ -1987,6 +2555,25 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
1987
2555
|
]);
|
|
1988
2556
|
const toggleDraw = useCallback((enabled) => void runDrawWrite(DRAW_PATH, { enabled }), [runDrawWrite]);
|
|
1989
2557
|
const resetDraw = useCallback(() => void runDrawWrite(DRAW_RESET_PATH, {}), [runDrawWrite]);
|
|
2558
|
+
const saveDrawModel = useCallback((modelId) => void runDrawWrite(DRAW_PATH, { modelId }), [runDrawWrite]);
|
|
2559
|
+
const [trendBusy, setTrendBusy] = useState(false);
|
|
2560
|
+
const [trendError, setTrendError] = useState(null);
|
|
2561
|
+
const saveTrendWindow = useCallback(async (windowDays) => {
|
|
2562
|
+
setTrendBusy(true);
|
|
2563
|
+
setTrendError(null);
|
|
2564
|
+
try {
|
|
2565
|
+
await postWrite(TREND_WINDOW_PATH, { windowDays });
|
|
2566
|
+
load();
|
|
2567
|
+
} catch (reason) {
|
|
2568
|
+
setTrendError(writeErrorText(reason, tt));
|
|
2569
|
+
} finally {
|
|
2570
|
+
setTrendBusy(false);
|
|
2571
|
+
}
|
|
2572
|
+
}, [
|
|
2573
|
+
load,
|
|
2574
|
+
postWrite,
|
|
2575
|
+
tt
|
|
2576
|
+
]);
|
|
1990
2577
|
const [tokenBusy, setTokenBusy] = useState(false);
|
|
1991
2578
|
const [tokenError, setTokenError] = useState(null);
|
|
1992
2579
|
const saveToken = useCallback(async (value) => {
|
|
@@ -2068,7 +2655,13 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
2068
2655
|
open: openSections.trend,
|
|
2069
2656
|
onToggle: () => toggleSection("trend"),
|
|
2070
2657
|
tt
|
|
2071
|
-
}, h(
|
|
2658
|
+
}, h(TrendWindowPicker, {
|
|
2659
|
+
trend: snap.trend,
|
|
2660
|
+
busy: trendBusy,
|
|
2661
|
+
error: trendError,
|
|
2662
|
+
onChange: (windowDays) => void saveTrendWindow(windowDays),
|
|
2663
|
+
tt
|
|
2664
|
+
}), h(TrendBars, {
|
|
2072
2665
|
buckets: snap.trend?.buckets ?? [],
|
|
2073
2666
|
tt
|
|
2074
2667
|
})), h(SectionCard, {
|
|
@@ -2107,6 +2700,8 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
2107
2700
|
error: drawError,
|
|
2108
2701
|
onToggle: toggleDraw,
|
|
2109
2702
|
onReset: resetDraw,
|
|
2703
|
+
onSelectModel: (id) => saveDrawModel(id),
|
|
2704
|
+
onAutoModel: () => saveDrawModel(null),
|
|
2110
2705
|
tokenPresent: data?.token?.present === true,
|
|
2111
2706
|
tt
|
|
2112
2707
|
})));
|
|
@@ -2186,6 +2781,7 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
2186
2781
|
init_use_snapshot_polling();
|
|
2187
2782
|
init_runtime();
|
|
2188
2783
|
init_styles();
|
|
2784
|
+
init_allow_list();
|
|
2189
2785
|
}));
|
|
2190
2786
|
|
|
2191
2787
|
//#endregion
|