dsh-connect-modelscope-token-plan 0.4.0 → 0.6.1
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 +235 -178
- package/README.md +20 -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 +770 -165
- package/cordis.patch.yml +45 -9
- package/lib/host-config.js +288 -21
- package/lib/index.js +916 -232
- package/lib/llm-adapter.js +45 -19
- package/package.json +115 -115
- package/screenshots.json +2 -1
- package/assets/icon-preview.png +0 -0
package/client.js
CHANGED
|
@@ -46,24 +46,65 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
46
46
|
return slash >= 0 ? id.slice(0, slash) : id;
|
|
47
47
|
}
|
|
48
48
|
/**
|
|
49
|
-
*
|
|
49
|
+
* 清单行要显示的名字:组头已经把 owner 说过一次,名字若还带着 `owner/` 前缀就切掉
|
|
50
|
+
* ——行宽还给名字本身。
|
|
50
51
|
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
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% 更有用。
|
|
89
|
+
*
|
|
90
|
+
* `tokens` 由调用方保证是正的有限数(`providerOf` 已挡),所以这里不再兜一次底。
|
|
55
91
|
* @param {number} tokens - 窗口大小(tokens)。
|
|
56
92
|
* @returns {string} 短标注。
|
|
57
93
|
*/
|
|
58
94
|
function formatWindow(tokens) {
|
|
59
|
-
if (tokens >=
|
|
60
|
-
|
|
61
|
-
|
|
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`;
|
|
62
99
|
}
|
|
63
|
-
if (tokens >= 1024) return `${Math.round(tokens / 1024)}K`;
|
|
64
100
|
return String(tokens);
|
|
65
101
|
}
|
|
66
|
-
|
|
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;
|
|
67
108
|
var init_const = __esmMin((() => {
|
|
68
109
|
init_wire();
|
|
69
110
|
NS = "dsh-connect-modelscope-token-plan";
|
|
@@ -79,6 +120,7 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
79
120
|
PROVIDER_RESET_PATH = `/api/${NS}/provider/reset`;
|
|
80
121
|
DRAW_PATH = `/api/${NS}/draw`;
|
|
81
122
|
DRAW_RESET_PATH = `/api/${NS}/draw/reset`;
|
|
123
|
+
TREND_WINDOW_PATH = `/api/${NS}/trend/window`;
|
|
82
124
|
MODELSCOPE_TOKEN_URL = "https://modelscope.cn/my/myaccesstoken";
|
|
83
125
|
MODELSCOPE_USAGE_URL = "https://modelscope.cn/magicube/usage?tab=consume";
|
|
84
126
|
MODELSCOPE_MODEL_URL_BASE = "https://www.modelscope.cn/models";
|
|
@@ -115,7 +157,7 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
115
157
|
"tab.models": "模型",
|
|
116
158
|
"tab.access": "接入",
|
|
117
159
|
"section.balance": "魔粒余额(官方数据)",
|
|
118
|
-
"section.local": "
|
|
160
|
+
"section.local": "本地调用(本客户端 · 仅经本插件)",
|
|
119
161
|
"section.trend": "近 {days} 天本地调用趋势",
|
|
120
162
|
"section.events": "限流与错误事件",
|
|
121
163
|
"section.token": "访问令牌",
|
|
@@ -129,14 +171,16 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
129
171
|
"balance.usagePage": "官方用量明细(网页)→",
|
|
130
172
|
"balance.unavailable": "魔粒余额暂不可读:{error}",
|
|
131
173
|
"quota.headline": "今日 {calls} 次 · {models} 个模型",
|
|
132
|
-
"quota.
|
|
174
|
+
"quota.rowCalls": "{calls} 次",
|
|
175
|
+
"quota.rowTokens": "{tokens} token",
|
|
176
|
+
"quota.note": "本地口径:只统计本客户端经本插件的调用(桌面端与 Web 端各记各的);直连魔搭的其它客户端不计入。消耗总量以官方魔粒余额为准。",
|
|
133
177
|
"quota.unavailable": "本地计数本次读不到(原因见上方提示)",
|
|
134
178
|
"probe.validity": "验令牌(零额度)",
|
|
135
179
|
"probe.validOk": "令牌有效(HTTP {status})",
|
|
136
180
|
"probe.fail": "失败:{error}",
|
|
137
181
|
"models.viewOnSite": "在魔搭查看 →",
|
|
138
182
|
"models.viewOnSiteTitle": "打开模型详情页(魔粒单价等只在网页展示)",
|
|
139
|
-
"models.featuredTitle": "
|
|
183
|
+
"models.featuredTitle": "推荐厂商(深度求索 / 智谱 / 通义千问)——它们的模型置顶显示",
|
|
140
184
|
"provider.featured": "推荐",
|
|
141
185
|
"section.provider": "接入为 DSH 模型",
|
|
142
186
|
"provider.enable": "把魔搭模型接入 DSH 模型选择器",
|
|
@@ -154,38 +198,67 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
154
198
|
"provider.reset": "回到默认",
|
|
155
199
|
"provider.enabledCount": "已启用 {count} / {total}",
|
|
156
200
|
"provider.rosterHint": "勾选要进入 DSH 模型选择器的模型;不勾 = 全部提供",
|
|
201
|
+
"provider.search": "搜索模型名或 ID",
|
|
202
|
+
"provider.noMatch": "没有匹配的模型。",
|
|
203
|
+
"provider.groupCount": "{count} 个",
|
|
204
|
+
"provider.groupSelected": "已选 {count}",
|
|
157
205
|
"provider.quotaExhausted": "额度耗尽",
|
|
158
206
|
"provider.vision": "视觉",
|
|
159
207
|
"provider.notConfigured": "还没有访问令牌,先到「接入」tab 配置",
|
|
160
208
|
"provider.source.panel": "面板",
|
|
161
209
|
"provider.source.config": "配置",
|
|
162
210
|
"provider.contextWindow": "上下文窗口",
|
|
163
|
-
"provider.
|
|
164
|
-
"provider.
|
|
211
|
+
"provider.contextWindowOverride": "上下文窗口覆盖",
|
|
212
|
+
"provider.contextDefault": "不覆盖(默认)",
|
|
213
|
+
"provider.contextHint": "目录未声明窗口时套用此值(会盖过内置策展表);保存后立即重新注册",
|
|
165
214
|
"provider.contextCuratedTitle": "窗口来自内置策展表(models.dev 交叉真值),平台未亲口声明;带 * 标记",
|
|
215
|
+
"provider.maxOutput": "最大输出",
|
|
216
|
+
"provider.maxOutputOverrideTitle": "最大输出:来自 maxTokensOverrides 里你写的值(盖过目录声明)",
|
|
217
|
+
"provider.maxOutputHint": "魔搭目录不返回输出上限,未覆盖的模型按 harness 默认(32768)截断;要更大就在 maxTokensOverrides 里逐模型写实测值(面板徽章用「最大输出」显示生效值)",
|
|
166
218
|
"section.draw": "出图工具",
|
|
167
|
-
"draw.enable": "
|
|
219
|
+
"draw.enable": "开启后,agent 可在对话里用 modelscope_draw_image 生成或编辑图片",
|
|
168
220
|
"draw.on": "已开启",
|
|
169
221
|
"draw.off": "未开启",
|
|
170
222
|
"draw.busy": "切换中…",
|
|
171
223
|
"draw.mounted": "已注册:{tool}",
|
|
172
|
-
"draw.notMounted": "
|
|
173
|
-
"draw.restartHint": "
|
|
174
|
-
"draw.
|
|
224
|
+
"draw.notMounted": "未注册——打开上方开关即可开启。",
|
|
225
|
+
"draw.restartHint": "开关立即保存;工具要到下次启动 DSH 才挂载(挂载后无法撤销)。",
|
|
226
|
+
"draw.currentNone": "当前目录里没有可出图的模型——出图会失败(先确认访问令牌已配置、目录已拉到)。",
|
|
227
|
+
"draw.currentSrc.declaredTextToImage": "平台声明为文生图",
|
|
228
|
+
"draw.currentSrc.knownTextToImage": "已知可纯文生图",
|
|
229
|
+
"draw.currentSrc.catalogFirst": "目录里的第一个出图模型",
|
|
230
|
+
"draw.autoHint": "由插件按能力挑;当前会选 {model}",
|
|
231
|
+
"draw.autoHintReason": "由插件按能力挑;当前会选 {model}({reason})",
|
|
232
|
+
"draw.models": "出图模型",
|
|
175
233
|
"draw.badge": "出图",
|
|
176
|
-
"draw.
|
|
234
|
+
"draw.badge.textToImage": "文生图",
|
|
235
|
+
"draw.badge.imageToImage": "图生图",
|
|
236
|
+
"draw.imageOnlyTitle": "该模型只能图生图/改图:必须通过 imageUrl 传一张输入图,不能纯文生图。",
|
|
237
|
+
"draw.auto": "自动选择",
|
|
238
|
+
"draw.noCandidates": "当前目录里没有可出图的模型,这个工具暂时用不了。",
|
|
177
239
|
"draw.needsKey": "还没有访问令牌,先到「接入」tab 配置。",
|
|
178
240
|
"draw.reset": "回到默认",
|
|
179
241
|
"draw.error": "出图设置保存失败:{error}",
|
|
180
242
|
"draw.source.panel": "面板",
|
|
181
243
|
"draw.source.config": "配置",
|
|
182
|
-
"draw.absent.noTools": "
|
|
244
|
+
"draw.absent.noTools": "开关已开,但本机没有 tools 服务,工具没挂上(Host 侧限制,不是配置问题)。",
|
|
183
245
|
"draw.absent.peerLoad": "开关已开,但 tools 模块加载失败,出图工具缺席(Host 问题,非配置)。",
|
|
184
246
|
"draw.absent.registry": "开关已开,但工具注册被 Host 拒绝,出图工具缺席(Host 问题,非配置)。",
|
|
185
247
|
"draw.absent.unknown": "开关已开,但出图工具未挂载,原因未知。",
|
|
248
|
+
"draw.preferredGone": "已保存的默认模型 {model} 已不在当前目录(下架或改名)——出图会用它失败,请在下面重选。",
|
|
186
249
|
"trend.none": "还没有本地调用记录。",
|
|
250
|
+
"trend.unreadable": "本地计数读不到,趋势图无法显示(不是 0 次)。",
|
|
187
251
|
"trend.legend": "柱长相对区间内最大值,仅本地口径。",
|
|
252
|
+
"trend.metric": "趋势指标",
|
|
253
|
+
"trend.metricCalls": "调用次数",
|
|
254
|
+
"trend.metricTokens": "token 数",
|
|
255
|
+
"trend.legendTokens": "按 token 数相对区间内最大值画柱,仅本地口径;失败调用没有用量记录,与「该日无数据」一样显示为 0。",
|
|
256
|
+
"trend.window": "显示天数",
|
|
257
|
+
"trend.windowDefault": "默认(跟随配置)",
|
|
258
|
+
"trend.windowDays": "{days} 天",
|
|
259
|
+
"trend.windowHint": "只决定画多少天;能回看多久另有一个保留窗口(留得比画得多才画得出更远)。",
|
|
188
260
|
"events.none": "暂无事件。",
|
|
261
|
+
"events.unreadable": "本地计数读不到,事件流无法显示(不是没有事件)。",
|
|
189
262
|
"events.quota": "额度",
|
|
190
263
|
"events.rate_limit": "限频",
|
|
191
264
|
"events.error": "错误",
|
|
@@ -236,7 +309,7 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
236
309
|
"tab.models": "Models",
|
|
237
310
|
"tab.access": "Access",
|
|
238
311
|
"section.balance": "Magicube balance (official)",
|
|
239
|
-
"section.local": "Local calls (only through this plugin)",
|
|
312
|
+
"section.local": "Local calls (this client · only through this plugin)",
|
|
240
313
|
"section.trend": "Local call trend, last {days} days",
|
|
241
314
|
"section.events": "Rate-limit and error events",
|
|
242
315
|
"section.token": "Access token",
|
|
@@ -250,14 +323,16 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
250
323
|
"balance.usagePage": "Official usage details (web) →",
|
|
251
324
|
"balance.unavailable": "Magicube balance temporarily unreadable: {error}",
|
|
252
325
|
"quota.headline": "Today {calls} calls · {models} models",
|
|
253
|
-
"quota.
|
|
326
|
+
"quota.rowCalls": "{calls} calls",
|
|
327
|
+
"quota.rowTokens": "{tokens} tokens",
|
|
328
|
+
"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.",
|
|
254
329
|
"quota.unavailable": "Local counters could not be read this time (reason in the notices above)",
|
|
255
330
|
"probe.validity": "Verify token (free)",
|
|
256
331
|
"probe.validOk": "Token valid (HTTP {status})",
|
|
257
332
|
"probe.fail": "Failed: {error}",
|
|
258
333
|
"models.viewOnSite": "View on ModelScope →",
|
|
259
334
|
"models.viewOnSiteTitle": "Open the model page (per-model Magicube price is web-only)",
|
|
260
|
-
"models.featuredTitle": "Recommended
|
|
335
|
+
"models.featuredTitle": "Recommended vendors (DeepSeek / Zhipu / Qwen) — their models are pinned to the top",
|
|
261
336
|
"provider.featured": "Recommended",
|
|
262
337
|
"section.provider": "Register as a DSH model",
|
|
263
338
|
"provider.enable": "Register ModelScope models in the DSH model picker",
|
|
@@ -275,38 +350,67 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
275
350
|
"provider.reset": "Reset",
|
|
276
351
|
"provider.enabledCount": "{count} / {total} enabled",
|
|
277
352
|
"provider.rosterHint": "Tick the models to offer; unticked = offer all",
|
|
353
|
+
"provider.search": "Search model name or ID",
|
|
354
|
+
"provider.noMatch": "No matching models.",
|
|
355
|
+
"provider.groupCount": "{count}",
|
|
356
|
+
"provider.groupSelected": "{count} selected",
|
|
278
357
|
"provider.quotaExhausted": "Quota exhausted",
|
|
279
358
|
"provider.vision": "vision",
|
|
280
359
|
"provider.notConfigured": "No token yet — configure it in the Access tab",
|
|
281
360
|
"provider.source.panel": "panel",
|
|
282
361
|
"provider.source.config": "config",
|
|
283
362
|
"provider.contextWindow": "Context window",
|
|
284
|
-
"provider.
|
|
285
|
-
"provider.
|
|
363
|
+
"provider.contextWindowOverride": "Context window override",
|
|
364
|
+
"provider.contextDefault": "No override (default)",
|
|
365
|
+
"provider.contextHint": "Applied when the catalog declares no window (it overrides the built-in curated table); re-registers immediately",
|
|
286
366
|
"provider.contextCuratedTitle": "Window from the built-in curated table (models.dev cross-check), not declared by the platform; marked with *",
|
|
367
|
+
"provider.maxOutput": "Max output",
|
|
368
|
+
"provider.maxOutputOverrideTitle": "Max output: the value you wrote in maxTokensOverrides (it wins over the catalogue)",
|
|
369
|
+
"provider.maxOutputHint": "The ModelScope catalogue returns no output ceiling, so models without an override are cut off at the harness default (32768); write per-model measured values in maxTokensOverrides for more (the badge shows the effective value)",
|
|
287
370
|
"section.draw": "Draw tool",
|
|
288
|
-
"draw.enable": "
|
|
371
|
+
"draw.enable": "Once on, the agent can generate or edit images with modelscope_draw_image",
|
|
289
372
|
"draw.on": "On",
|
|
290
373
|
"draw.off": "Off",
|
|
291
374
|
"draw.busy": "Switching…",
|
|
292
375
|
"draw.mounted": "Registered: {tool}",
|
|
293
|
-
"draw.notMounted": "Not registered —
|
|
294
|
-
"draw.restartHint": "The switch saves at once
|
|
295
|
-
"draw.
|
|
376
|
+
"draw.notMounted": "Not registered — turn the switch above on.",
|
|
377
|
+
"draw.restartHint": "The switch saves at once; the tool mounts the next time DSH starts (a mount cannot be undone).",
|
|
378
|
+
"draw.currentNone": "No image model in the catalogue right now — drawing will fail (check the access token and that the catalogue has loaded).",
|
|
379
|
+
"draw.currentSrc.declaredTextToImage": "platform-declared text-to-image",
|
|
380
|
+
"draw.currentSrc.knownTextToImage": "known to accept text-to-image",
|
|
381
|
+
"draw.currentSrc.catalogFirst": "first image model in the catalogue",
|
|
382
|
+
"draw.autoHint": "Chosen by the plugin for capability; it would pick {model}",
|
|
383
|
+
"draw.autoHintReason": "Chosen by the plugin for capability; it would pick {model} ({reason})",
|
|
384
|
+
"draw.models": "Image model",
|
|
296
385
|
"draw.badge": "image",
|
|
297
|
-
"draw.
|
|
386
|
+
"draw.badge.textToImage": "text-to-image",
|
|
387
|
+
"draw.badge.imageToImage": "image-to-image",
|
|
388
|
+
"draw.imageOnlyTitle": "Image-to-image only: an input image must be passed via imageUrl; pure text-to-image is not supported.",
|
|
389
|
+
"draw.auto": "Auto",
|
|
390
|
+
"draw.noCandidates": "No image model in the catalogue right now, so this tool cannot work yet.",
|
|
298
391
|
"draw.needsKey": "No token yet — configure it in the Access tab.",
|
|
299
392
|
"draw.reset": "Reset",
|
|
300
393
|
"draw.error": "Saving the draw settings failed: {error}",
|
|
301
394
|
"draw.source.panel": "panel",
|
|
302
395
|
"draw.source.config": "config",
|
|
303
|
-
"draw.absent.noTools": "The switch is on, but this Host
|
|
396
|
+
"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).",
|
|
304
397
|
"draw.absent.peerLoad": "The switch is on, but the tools module failed to load; the draw tool is absent (a Host fault, not config).",
|
|
305
398
|
"draw.absent.registry": "The switch is on, but the Host refused the tool registration; the draw tool is absent (a Host fault, not config).",
|
|
306
399
|
"draw.absent.unknown": "The switch is on, but the draw tool is not mounted; the reason is unknown.",
|
|
400
|
+
"draw.preferredGone": "The saved default model {model} is no longer in the catalogue (delisted or renamed) — drawing with it will fail. Pick another one below.",
|
|
307
401
|
"trend.none": "No local call records yet.",
|
|
402
|
+
"trend.unreadable": "Local counts could not be read; the trend chart is unavailable (this is not zero calls).",
|
|
308
403
|
"trend.legend": "Bar lengths are relative to the maximum in the range (local scope only).",
|
|
404
|
+
"trend.metric": "Trend metric",
|
|
405
|
+
"trend.metricCalls": "Calls",
|
|
406
|
+
"trend.metricTokens": "Tokens",
|
|
407
|
+
"trend.legendTokens": "Bar lengths are relative to the largest token count in the range (local scope only). Failed calls record no usage, and days with no data also read as 0.",
|
|
408
|
+
"trend.window": "Days shown",
|
|
409
|
+
"trend.windowDefault": "Default (follow config)",
|
|
410
|
+
"trend.windowDays": "{days} days",
|
|
411
|
+
"trend.windowHint": "Only sets how many days are drawn; how far back history is kept is a separate retention window.",
|
|
309
412
|
"events.none": "No events.",
|
|
413
|
+
"events.unreadable": "Local counters unreadable; event stream cannot be shown (not \"no events\").",
|
|
310
414
|
"events.quota": "Quota",
|
|
311
415
|
"events.rate_limit": "Rate limit",
|
|
312
416
|
"events.error": "Error",
|
|
@@ -371,6 +475,36 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
371
475
|
if (number >= 1e4) return Math.round(number).toLocaleString();
|
|
372
476
|
return String(Math.round(number * 100) / 100);
|
|
373
477
|
}
|
|
478
|
+
/**
|
|
479
|
+
* 大数字的紧凑文本:`< 1000` 原样,`< 1e6` 用 `k`,`≥ 1e6` 用 `m`(各留 1~2 位小数,
|
|
480
|
+
* 去尾零)。**只给 token 这类会自然到百万级的读数用**——调用次数一天几十上百,千分位
|
|
481
|
+
* 逗号反而更好读,那条路径继续走 `count()`。
|
|
482
|
+
*
|
|
483
|
+
* 单位用小写 k/m:视觉上与 SI 前缀一致,也避开「兆」的歧义(`M` 在中文语境里常被读成
|
|
484
|
+
* 万亿)。负值与 NaN 分别返回 `-1.2k` 与 `NaN`——不做掩饰,异常值该在面板上现形。
|
|
485
|
+
*
|
|
486
|
+
* **与 `const.ts` 的 `formatWindow()` 刻意不同,不是漏统一**:那个印的是**上下文窗口
|
|
487
|
+
* 规格**(`128K` / `256K` / `1M`,按上游与市场的叫法、K=1024 口径),回答「大概能放
|
|
488
|
+
* 多少上下文」;这里印的是**实测用量读数**(`1.2k` / `3.4m`,十进制、小写),回答
|
|
489
|
+
* 「实际用了多少」。同一数字在两处印法不同,是因为它们量纲不同(规格 vs 读数)。
|
|
490
|
+
*/
|
|
491
|
+
function compactCount(value) {
|
|
492
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return String(value);
|
|
493
|
+
const trim = (number) => String(Number(number.toFixed(number >= 100 ? 0 : number >= 10 ? 1 : 2)));
|
|
494
|
+
const sign = value < 0 ? "-" : "";
|
|
495
|
+
const abs = Math.abs(value);
|
|
496
|
+
if (abs < 1e3) return `${sign}${trim(abs)}`;
|
|
497
|
+
const tiers = [
|
|
498
|
+
[1e3, "k"],
|
|
499
|
+
[1e6, "m"],
|
|
500
|
+
[1e9, "b"]
|
|
501
|
+
];
|
|
502
|
+
for (const [i, [divisor, unit]] of tiers.entries()) {
|
|
503
|
+
const shown = trim(abs / divisor);
|
|
504
|
+
if (i === tiers.length - 1 || Number(shown) < 1e3) return `${sign}${shown}${unit}`;
|
|
505
|
+
}
|
|
506
|
+
return `${sign}${trim(abs)}b`;
|
|
507
|
+
}
|
|
374
508
|
/** 填 `{key}` 模板:vars 的每个键对应模板里的同名占位符。 */
|
|
375
509
|
function format(template, vars) {
|
|
376
510
|
let text = template;
|
|
@@ -636,6 +770,68 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
636
770
|
lineHeight: "16px",
|
|
637
771
|
color: "var(--dsw-alias-label-secondary)"
|
|
638
772
|
},
|
|
773
|
+
trendChartBars: {
|
|
774
|
+
display: "flex",
|
|
775
|
+
alignItems: "flex-end",
|
|
776
|
+
gap: 2,
|
|
777
|
+
height: 96,
|
|
778
|
+
padding: "0 1px"
|
|
779
|
+
},
|
|
780
|
+
trendChartBar: {
|
|
781
|
+
flex: 1,
|
|
782
|
+
minWidth: 2,
|
|
783
|
+
height: "100%",
|
|
784
|
+
display: "flex",
|
|
785
|
+
alignItems: "flex-end",
|
|
786
|
+
position: "relative"
|
|
787
|
+
},
|
|
788
|
+
trendChartFill: {
|
|
789
|
+
width: "100%",
|
|
790
|
+
minHeight: 2,
|
|
791
|
+
borderRadius: 2,
|
|
792
|
+
background: BRAND
|
|
793
|
+
},
|
|
794
|
+
trendChartValue: {
|
|
795
|
+
position: "absolute",
|
|
796
|
+
left: 0,
|
|
797
|
+
right: 0,
|
|
798
|
+
fontSize: 9,
|
|
799
|
+
lineHeight: 1,
|
|
800
|
+
textAlign: "center",
|
|
801
|
+
color: "var(--dsw-alias-label-secondary)",
|
|
802
|
+
fontVariantNumeric: "tabular-nums",
|
|
803
|
+
pointerEvents: "none"
|
|
804
|
+
},
|
|
805
|
+
trendChartAxis: {
|
|
806
|
+
display: "flex",
|
|
807
|
+
justifyContent: "space-between",
|
|
808
|
+
gap: 12,
|
|
809
|
+
marginTop: 6,
|
|
810
|
+
fontSize: 11,
|
|
811
|
+
color: "var(--dsw-alias-label-secondary)"
|
|
812
|
+
},
|
|
813
|
+
trendMetricBar: {
|
|
814
|
+
display: "flex",
|
|
815
|
+
gap: 2,
|
|
816
|
+
marginBottom: 6,
|
|
817
|
+
alignItems: "center"
|
|
818
|
+
},
|
|
819
|
+
trendMetricBtn: {
|
|
820
|
+
appearance: "none",
|
|
821
|
+
background: "none",
|
|
822
|
+
border: "none",
|
|
823
|
+
borderBottom: "1px solid transparent",
|
|
824
|
+
padding: "2px 8px",
|
|
825
|
+
fontSize: 11,
|
|
826
|
+
color: "var(--dsw-alias-label-secondary)",
|
|
827
|
+
cursor: "pointer",
|
|
828
|
+
outlineOffset: -2
|
|
829
|
+
},
|
|
830
|
+
trendMetricBtnActive: {
|
|
831
|
+
color: "var(--dsw-alias-label-primary)",
|
|
832
|
+
fontWeight: 600,
|
|
833
|
+
borderBottom: `1px solid ${BRAND}`
|
|
834
|
+
},
|
|
639
835
|
muted: { color: "var(--dsw-alias-label-secondary)" },
|
|
640
836
|
error: { color: "var(--dsw-alias-state-error-primary)" },
|
|
641
837
|
empty: {
|
|
@@ -673,6 +869,22 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
673
869
|
fontSize: 12,
|
|
674
870
|
margin: "10px 0 0"
|
|
675
871
|
},
|
|
872
|
+
dotSep: {
|
|
873
|
+
color: "var(--dsw-alias-label-secondary)",
|
|
874
|
+
opacity: .55,
|
|
875
|
+
fontSize: 11
|
|
876
|
+
},
|
|
877
|
+
advancedBlock: {
|
|
878
|
+
marginTop: 14,
|
|
879
|
+
paddingTop: 12,
|
|
880
|
+
borderTop: "1px solid var(--dsw-alias-border-l1)"
|
|
881
|
+
},
|
|
882
|
+
advancedHint: {
|
|
883
|
+
marginTop: 6,
|
|
884
|
+
fontSize: 11,
|
|
885
|
+
lineHeight: "16px",
|
|
886
|
+
color: "var(--dsw-alias-label-secondary)"
|
|
887
|
+
},
|
|
676
888
|
rosterTools: {
|
|
677
889
|
display: "flex",
|
|
678
890
|
gap: 8,
|
|
@@ -710,6 +922,27 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
710
922
|
gap: 8
|
|
711
923
|
},
|
|
712
924
|
modelRowOff: { opacity: .55 },
|
|
925
|
+
modelGroup: {
|
|
926
|
+
display: "flex",
|
|
927
|
+
alignItems: "center",
|
|
928
|
+
gap: 6,
|
|
929
|
+
width: "100%",
|
|
930
|
+
padding: "10px 4px 2px",
|
|
931
|
+
background: "none",
|
|
932
|
+
border: "none",
|
|
933
|
+
textAlign: "left",
|
|
934
|
+
cursor: "pointer",
|
|
935
|
+
fontSize: 12,
|
|
936
|
+
fontWeight: 600,
|
|
937
|
+
color: "var(--dsw-alias-label-secondary)"
|
|
938
|
+
},
|
|
939
|
+
modelGroupOwner: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" },
|
|
940
|
+
modelGroupCount: {
|
|
941
|
+
fontWeight: 400,
|
|
942
|
+
opacity: .75,
|
|
943
|
+
fontVariantNumeric: "tabular-nums"
|
|
944
|
+
},
|
|
945
|
+
modelRowGrouped: { paddingLeft: 22 },
|
|
713
946
|
modelCheck: {
|
|
714
947
|
flex: "none",
|
|
715
948
|
width: 15,
|
|
@@ -860,13 +1093,15 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
860
1093
|
};
|
|
861
1094
|
case "quota": return {
|
|
862
1095
|
daily: { usedLocal: null },
|
|
863
|
-
perModel:
|
|
1096
|
+
perModel: null,
|
|
864
1097
|
countingNote: "local-counting"
|
|
865
1098
|
};
|
|
866
|
-
case "events": return
|
|
1099
|
+
case "events": return null;
|
|
867
1100
|
case "trend": return {
|
|
868
1101
|
days: 0,
|
|
869
|
-
|
|
1102
|
+
windowDays: null,
|
|
1103
|
+
maxDays: 0,
|
|
1104
|
+
buckets: null
|
|
870
1105
|
};
|
|
871
1106
|
case "models": return {
|
|
872
1107
|
available: false,
|
|
@@ -940,6 +1175,8 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
940
1175
|
const id = String(row.id ?? "");
|
|
941
1176
|
const w = row.contextWindow;
|
|
942
1177
|
const contextSource = row.contextSource === "override" || row.contextSource === "curated" ? row.contextSource : "catalog";
|
|
1178
|
+
const maxOutputSource = row.maxOutputSource === "override" ? "override" : "catalog";
|
|
1179
|
+
const out = row.maxOutputLength;
|
|
943
1180
|
return {
|
|
944
1181
|
id,
|
|
945
1182
|
name: typeof row.name === "string" && row.name !== "" ? row.name : id,
|
|
@@ -947,7 +1184,9 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
947
1184
|
available: row.available !== false,
|
|
948
1185
|
quotaExhausted: row.quotaExhausted === true,
|
|
949
1186
|
contextSource,
|
|
950
|
-
|
|
1187
|
+
maxOutputSource,
|
|
1188
|
+
...typeof w === "number" && Number.isFinite(w) && w > 0 ? { contextWindow: w } : {},
|
|
1189
|
+
...typeof out === "number" && Number.isFinite(out) && out > 0 ? { maxOutputLength: out } : {}
|
|
951
1190
|
};
|
|
952
1191
|
}).filter((entry) => entry.id !== "") : [];
|
|
953
1192
|
return {
|
|
@@ -987,15 +1226,28 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
987
1226
|
const candidates = Array.isArray(d.candidateIds) ? d.candidateIds.map((id) => String(id ?? "")).filter((id) => id !== "") : [];
|
|
988
1227
|
const reason = d.absentReason;
|
|
989
1228
|
const absentReason = reason === "no-tools-service" || reason === "peer-load-failed" || reason === "registry-refused" ? reason : null;
|
|
1229
|
+
const rawCandidates = Array.isArray(d.candidates) ? d.candidates.map((entry) => {
|
|
1230
|
+
const row = entry ?? {};
|
|
1231
|
+
return {
|
|
1232
|
+
id: String(row.id ?? ""),
|
|
1233
|
+
capability: row.capability === "text-to-image" ? "text-to-image" : row.capability === "image-to-image" ? "image-to-image" : "other",
|
|
1234
|
+
textToImage: row.textToImage === true
|
|
1235
|
+
};
|
|
1236
|
+
}).filter((row) => row.id !== "") : null;
|
|
1237
|
+
const effectiveSource = d.effectiveSource === "preferred" || d.effectiveSource === "declared-text-to-image" || d.effectiveSource === "known-text-to-image" || d.effectiveSource === "catalog-first" ? d.effectiveSource : null;
|
|
990
1238
|
return {
|
|
991
1239
|
enabled: d.enabled === true,
|
|
992
1240
|
source: d.source === "panel" ? "panel" : "config",
|
|
993
1241
|
candidateIds: candidates,
|
|
994
1242
|
candidateCount: typeof d.candidateCount === "number" && Number.isFinite(d.candidateCount) ? d.candidateCount : candidates.length,
|
|
1243
|
+
...rawCandidates !== null ? { candidates: rawCandidates } : {},
|
|
995
1244
|
preferredModel: typeof d.preferredModel === "string" && d.preferredModel !== "" ? d.preferredModel : null,
|
|
996
1245
|
preferredSource: d.preferredSource === "panel" ? "panel" : "config",
|
|
1246
|
+
..."effectiveModel" in d ? { effectiveModel: typeof d.effectiveModel === "string" && d.effectiveModel !== "" ? d.effectiveModel : null } : {},
|
|
1247
|
+
..."effectiveSource" in d ? { effectiveSource } : {},
|
|
997
1248
|
absentReason,
|
|
998
1249
|
mountPending: d.mountPending === true,
|
|
1250
|
+
preferredInCatalog: d.preferredInCatalog === true ? true : d.preferredInCatalog === false ? false : null,
|
|
999
1251
|
toolName: typeof d.toolName === "string" && d.toolName !== "" ? d.toolName : "modelscope_draw_image"
|
|
1000
1252
|
};
|
|
1001
1253
|
}
|
|
@@ -1048,10 +1300,14 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
1048
1300
|
source: "config",
|
|
1049
1301
|
candidateIds: [],
|
|
1050
1302
|
candidateCount: 0,
|
|
1303
|
+
candidates: [],
|
|
1051
1304
|
preferredModel: null,
|
|
1052
1305
|
preferredSource: "config",
|
|
1306
|
+
effectiveModel: null,
|
|
1307
|
+
effectiveSource: null,
|
|
1053
1308
|
absentReason: null,
|
|
1054
1309
|
mountPending: false,
|
|
1310
|
+
preferredInCatalog: null,
|
|
1055
1311
|
toolName: "modelscope_draw_image"
|
|
1056
1312
|
});
|
|
1057
1313
|
GUIDANCE_BY_CODE = Object.freeze({
|
|
@@ -1277,17 +1533,19 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
1277
1533
|
function LocalDailyCard({ snapshot, tt }) {
|
|
1278
1534
|
const quota = snapshot.quota;
|
|
1279
1535
|
const daily = quota?.daily ?? { usedLocal: null };
|
|
1280
|
-
const perModel =
|
|
1536
|
+
const perModel = quota?.perModel;
|
|
1537
|
+
const perModelRows = Array.isArray(perModel) ? perModel : perModel === null ? null : [];
|
|
1281
1538
|
const unknownUsed = !(typeof daily.usedLocal === "number" && Number.isFinite(daily.usedLocal));
|
|
1282
1539
|
const usedText = unknownUsed ? "—" : count(daily.usedLocal);
|
|
1540
|
+
const perModelUnknown = perModelRows === null;
|
|
1283
1541
|
return h("div", { style: S.card }, h("div", { style: S.poolName }, format(tt("quota.headline"), {
|
|
1284
1542
|
calls: usedText,
|
|
1285
|
-
models: count(
|
|
1286
|
-
})), unknownUsed ? h("div", { style: {
|
|
1543
|
+
models: perModelUnknown ? "—" : count(perModelRows.length)
|
|
1544
|
+
})), unknownUsed || perModelUnknown ? h("div", { style: {
|
|
1287
1545
|
...S.formNote,
|
|
1288
1546
|
color: "var(--dsw-alias-state-warn-primary)"
|
|
1289
|
-
} }, tt("quota.unavailable")) : null, h(ModelUsageTable, {
|
|
1290
|
-
rows:
|
|
1547
|
+
} }, tt("quota.unavailable")) : null, perModelUnknown ? null : h(ModelUsageTable, {
|
|
1548
|
+
rows: perModelRows,
|
|
1291
1549
|
tt
|
|
1292
1550
|
}), h("div", { style: S.trendLegend }, tt("quota.note")));
|
|
1293
1551
|
}
|
|
@@ -1303,7 +1561,7 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
1303
1561
|
}, h("div", { style: S.trendRowHead }, h("span", {
|
|
1304
1562
|
style: S.trendModel,
|
|
1305
1563
|
title: row.modelId
|
|
1306
|
-
}, row.modelId), h("span", { style: S.trendCredits }, count(row.calls) + (row.tokens !== null ? ` · ${
|
|
1564
|
+
}, row.modelId), h("span", { style: S.trendCredits }, format(tt("quota.rowCalls"), { calls: count(row.calls) }) + (row.tokens !== null ? ` · ${format(tt("quota.rowTokens"), { tokens: compactCount(row.tokens) })}` : ""))), pct >= 1 ? h("div", {
|
|
1307
1565
|
style: S.trendBar,
|
|
1308
1566
|
role: "progressbar",
|
|
1309
1567
|
"aria-valuenow": Math.round(pct),
|
|
@@ -1315,31 +1573,170 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
1315
1573
|
} })) : null);
|
|
1316
1574
|
}), h("div", { style: S.trendLegend }, tt("trend.legend")));
|
|
1317
1575
|
}
|
|
1318
|
-
/**
|
|
1576
|
+
/**
|
|
1577
|
+
* 轴上的日期短标注:砍掉年份与补零(`2026-09-18` → `9-18`)。
|
|
1578
|
+
*
|
|
1579
|
+
* 轴的跨度最多 trendDays(默认 14,多在同年内),年份在刻度间重复出现,占掉的
|
|
1580
|
+
* 宽度全是从图的呼吸里匀来的;补零的 `09` 同样多占一格而没多给信息。不是
|
|
1581
|
+
* `YYYY-MM-DD` 形状时原样返回——宁可显示一个陌生字符串,也不按位置硬切。
|
|
1582
|
+
* @param {string} key - 天桶 dateKey(本地日期 YYYY-MM-DD)。
|
|
1583
|
+
* @returns {string} 短标注。
|
|
1584
|
+
*/
|
|
1585
|
+
function shortDateKey(key) {
|
|
1586
|
+
const value = typeof key === "string" ? key : "";
|
|
1587
|
+
const match = /^\d{4}-(\d{2})-(\d{2})$/.exec(value);
|
|
1588
|
+
return match === null ? value : `${Number(match[1])}-${Number(match[2])}`;
|
|
1589
|
+
}
|
|
1590
|
+
/**
|
|
1591
|
+
* 轴刻度落在哪些桶上(返回下标)。
|
|
1592
|
+
*
|
|
1593
|
+
* 桶数 ≤ {@link MAX_TICKS} 时逐日都标;否则按 `(n-1)/(MAX_TICKS-1)` 等距取点、
|
|
1594
|
+
* 首尾必含——与姊妹插件 Agnes 的用量图同一取法(它 30 天标 7 个)。等距取点是
|
|
1595
|
+
* 对**下标**取整而不是对像素分均,所以刻度与其柱子所属的区间大体对齐,只是轴
|
|
1596
|
+
* 用 `space-between` 铺开;两端的日期恒为真实首末日。
|
|
1597
|
+
* @param {number} count - 桶数。
|
|
1598
|
+
* @returns {number[]} 升序下标。
|
|
1599
|
+
*/
|
|
1600
|
+
function tickIndices(count) {
|
|
1601
|
+
if (!Number.isFinite(count) || count <= 0) return [];
|
|
1602
|
+
if (count <= MAX_TICKS) return Array.from({ length: count }, (_, index) => index);
|
|
1603
|
+
const step = (count - 1) / 6;
|
|
1604
|
+
const out = [];
|
|
1605
|
+
for (let tick = 0; tick < MAX_TICKS; tick += 1) {
|
|
1606
|
+
const index = Math.round(tick * step);
|
|
1607
|
+
if (index > (out[out.length - 1] ?? -1)) out.push(index);
|
|
1608
|
+
}
|
|
1609
|
+
return out;
|
|
1610
|
+
}
|
|
1611
|
+
/**
|
|
1612
|
+
* 近 N 天本地调用趋势(天桶;缺桶 = 该日 0 次,真实零)——**竖向柱状图**。
|
|
1613
|
+
*
|
|
1614
|
+
* token 与调用次数共用同一根柱子但**各自归一化**(`max` 按当前指标现算):两者量级
|
|
1615
|
+
* 差三四个数量级,若共用一把尺,次数柱会被 token 柱压成贴地的一条线——那图就撒谎了。
|
|
1616
|
+
* 一次只画一个指标(不放双柱),窄容器里双柱既挤又难读,且对比还得再解释一遍。
|
|
1617
|
+
*
|
|
1618
|
+
* 切换态活在组件内的 useState:它是纯显示偏好,不落盘——与「显示天数」不同,那个
|
|
1619
|
+
* 会改变 Host 的取数窗口(真要持久化就该走 usage-prefs,别混进这里)。
|
|
1620
|
+
*/
|
|
1319
1621
|
function TrendBars({ buckets, tt }) {
|
|
1622
|
+
const [metric, setMetric] = useState("calls");
|
|
1623
|
+
if (buckets === null) return h("div", { style: S.trendLegend }, tt("trend.unreadable"));
|
|
1320
1624
|
if (!Array.isArray(buckets) || buckets.length === 0) return h("div", { style: S.trendLegend }, tt("trend.none"));
|
|
1321
1625
|
const rows = buckets.map((bucket) => ({
|
|
1322
1626
|
dateKey: bucket.dateKey,
|
|
1323
|
-
calls: Number.isFinite(bucket.calls) ? Math.max(0, bucket.calls) : 0
|
|
1627
|
+
calls: Number.isFinite(bucket.calls) ? Math.max(0, bucket.calls) : 0,
|
|
1628
|
+
tokens: Number.isFinite(bucket.tokens) ? Math.max(0, bucket.tokens) : 0
|
|
1324
1629
|
}));
|
|
1325
|
-
const
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1630
|
+
const tokenMode = metric === "tokens";
|
|
1631
|
+
const values = rows.map((row) => tokenMode ? row.tokens : row.calls);
|
|
1632
|
+
const max = Math.max(0, ...values);
|
|
1633
|
+
/** 读数文本:次数走 count(千分位),token 走 compactCount(`1.2k` / `3.4m`)。 */
|
|
1634
|
+
const readout = (value) => tokenMode ? compactCount(value) : count(value);
|
|
1635
|
+
return h("div", null, h("div", {
|
|
1636
|
+
style: S.trendMetricBar,
|
|
1637
|
+
role: "group",
|
|
1638
|
+
"aria-label": tt("trend.metric")
|
|
1639
|
+
}, ...[["calls", "trend.metricCalls"], ["tokens", "trend.metricTokens"]].map(([key, labelKey]) => h("button", {
|
|
1640
|
+
key,
|
|
1641
|
+
type: "button",
|
|
1642
|
+
"aria-pressed": metric === key,
|
|
1643
|
+
style: metric === key ? {
|
|
1644
|
+
...S.trendMetricBtn,
|
|
1645
|
+
...S.trendMetricBtnActive
|
|
1646
|
+
} : S.trendMetricBtn,
|
|
1647
|
+
onClick: () => setMetric(key)
|
|
1648
|
+
}, tt(labelKey)))), h("div", { style: S.trendChartBars }, rows.map((row, index) => {
|
|
1649
|
+
const value = values[index] ?? 0;
|
|
1650
|
+
const pct = max > 0 ? value / max * 100 : 0;
|
|
1651
|
+
return h("div", {
|
|
1652
|
+
key: row.dateKey,
|
|
1653
|
+
style: S.trendChartBar,
|
|
1654
|
+
title: `${row.dateKey}: ${readout(value)}`
|
|
1655
|
+
}, value > 0 ? h("span", {
|
|
1656
|
+
style: {
|
|
1657
|
+
...S.trendChartValue,
|
|
1658
|
+
bottom: `calc(${pct}% + 3px)`
|
|
1659
|
+
},
|
|
1660
|
+
"aria-hidden": "true"
|
|
1661
|
+
}, readout(value)) : null, h("div", {
|
|
1662
|
+
style: {
|
|
1663
|
+
...S.trendChartFill,
|
|
1664
|
+
height: `${pct}%`
|
|
1665
|
+
},
|
|
1666
|
+
role: "progressbar",
|
|
1667
|
+
"aria-label": `${row.dateKey} ${readout(value)}`,
|
|
1668
|
+
"aria-valuenow": Math.round(pct),
|
|
1669
|
+
"aria-valuemin": 0,
|
|
1670
|
+
"aria-valuemax": 100
|
|
1671
|
+
}));
|
|
1672
|
+
})), h("div", { style: S.trendChartAxis }, tickIndices(rows.length).map((index) => {
|
|
1673
|
+
const key = rows[index]?.dateKey ?? "";
|
|
1674
|
+
return h("span", {
|
|
1675
|
+
key,
|
|
1676
|
+
title: key,
|
|
1677
|
+
style: { whiteSpace: "nowrap" }
|
|
1678
|
+
}, shortDateKey(key));
|
|
1679
|
+
})), h("div", { style: S.trendLegend }, tt(tokenMode ? "trend.legendTokens" : "trend.legend")));
|
|
1680
|
+
}
|
|
1681
|
+
/**
|
|
1682
|
+
* 趋势显示窗口选择器:默认(跟随 `trendDays` 配置)/ 7 / 14 / 30 / 90 天。
|
|
1683
|
+
*
|
|
1684
|
+
* 它改的是**画多少天**,不是**留多少天**(保留窗口是 host-config 的
|
|
1685
|
+
* `retentionDays`)——所以档位只列到 `trend.maxDays` 为止:画得比留得多会把已剪的
|
|
1686
|
+
* 日子画成 0,那个组合在 Host 侧已被夹掉,这里也不给它入口。
|
|
1687
|
+
*
|
|
1688
|
+
* 保存成功后由调用方 `load()` 回显 Host 落盘的值(不做乐观更新,与 provider /
|
|
1689
|
+
* draw 的写路径同一纪律)。当前值不在档位里(例如手动改过 usage-prefs.json)时
|
|
1690
|
+
* value 与任何 option 都不匹配 → 浏览器显示第一项,但下一发 onChange 仍发正确的
|
|
1691
|
+
* 值——与上下文窗口选择器同一处可接受的边缘表现,不为它造「自定义」档位。
|
|
1692
|
+
*/
|
|
1693
|
+
function TrendWindowPicker({ trend, busy, error, onChange, tt }) {
|
|
1694
|
+
if (trend === void 0) return null;
|
|
1695
|
+
const maxDays = typeof trend.maxDays === "number" && Number.isFinite(trend.maxDays) && trend.maxDays > 0 ? Math.floor(trend.maxDays) : 0;
|
|
1696
|
+
const tiers = TREND_WINDOW_TIERS.filter((tier) => maxDays === 0 || tier <= maxDays);
|
|
1697
|
+
const current = trend.windowDays;
|
|
1698
|
+
return h("div", { style: {
|
|
1699
|
+
display: "flex",
|
|
1700
|
+
gap: 8,
|
|
1701
|
+
alignItems: "center",
|
|
1702
|
+
flexWrap: "wrap",
|
|
1703
|
+
marginBottom: 10
|
|
1704
|
+
} }, h("label", {
|
|
1330
1705
|
style: {
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
marginLeft: 10
|
|
1706
|
+
fontSize: 12,
|
|
1707
|
+
color: "var(--dsw-alias-label-secondary)"
|
|
1334
1708
|
},
|
|
1335
|
-
|
|
1336
|
-
},
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1709
|
+
htmlFor: "trend-window"
|
|
1710
|
+
}, tt("trend.window")), h("select", {
|
|
1711
|
+
id: "trend-window",
|
|
1712
|
+
style: {
|
|
1713
|
+
...S.rosterBulk,
|
|
1714
|
+
height: 28,
|
|
1715
|
+
width: "auto"
|
|
1716
|
+
},
|
|
1717
|
+
disabled: busy,
|
|
1718
|
+
value: current === null || current === void 0 ? "" : String(current),
|
|
1719
|
+
onChange: (event) => {
|
|
1720
|
+
const value = String(event.target?.value ?? "");
|
|
1721
|
+
onChange(value === "" ? null : Number(value));
|
|
1722
|
+
}
|
|
1723
|
+
}, h("option", {
|
|
1724
|
+
value: "",
|
|
1725
|
+
key: "default"
|
|
1726
|
+
}, tt("trend.windowDefault")), tiers.map((tier) => h("option", {
|
|
1727
|
+
value: String(tier),
|
|
1728
|
+
key: String(tier)
|
|
1729
|
+
}, format(tt("trend.windowDays"), { days: tier })))), h("span", { style: {
|
|
1730
|
+
...S.muted,
|
|
1731
|
+
fontSize: 11
|
|
1732
|
+
} }, tt("trend.windowHint")), error !== null ? h("span", {
|
|
1733
|
+
style: S.formError,
|
|
1734
|
+
role: "alert"
|
|
1735
|
+
}, error) : null);
|
|
1340
1736
|
}
|
|
1341
|
-
/** 事件流(429/错误),Newest-first
|
|
1737
|
+
/** 事件流(429/错误),Newest-first。`events` 为 null = 读不到,渲染「—」(读不到 ≠ 0)。 */
|
|
1342
1738
|
function EventsList({ events, tt }) {
|
|
1739
|
+
if (events === null) return h("div", { style: S.trendLegend }, tt("events.unreadable"));
|
|
1343
1740
|
if (!Array.isArray(events) || events.length === 0) return h("div", { style: S.trendLegend }, tt("events.none"));
|
|
1344
1741
|
return h("div", null, events.map((event, index) => h("div", {
|
|
1345
1742
|
key: `${event.at}-${index}`,
|
|
@@ -1402,6 +1799,33 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
1402
1799
|
}, tt("token.link")));
|
|
1403
1800
|
}
|
|
1404
1801
|
/**
|
|
1802
|
+
* 按 owner 把清单分组,**推荐的三家置顶**(组内保持目录首见序)。
|
|
1803
|
+
*
|
|
1804
|
+
* 分组的理由只有一个:`owner/model` 的 owner 前缀在每一行里重复,而面板一屏能放
|
|
1805
|
+
* 十几行——同一个 owner 说十几遍既占宽度又不提供任何信息。置顶沿用「推荐」徽章的
|
|
1806
|
+
* 同一份 `FEATURED_OWNERS`,两处不可能对「哪三家值得置顶」说两套话。
|
|
1807
|
+
* @param {RosterRow[]} rows - 归一后的清单行。
|
|
1808
|
+
* @returns {{owner: string, rows: RosterRow[]}[]} 分组(置顶 owner 在前)。
|
|
1809
|
+
*/
|
|
1810
|
+
function groupByOwner(rows) {
|
|
1811
|
+
const order = [];
|
|
1812
|
+
const byOwner = /* @__PURE__ */ new Map();
|
|
1813
|
+
for (const row of rows) {
|
|
1814
|
+
const owner = catalogOwner(row.id);
|
|
1815
|
+
const bucket = byOwner.get(owner);
|
|
1816
|
+
if (bucket === void 0) {
|
|
1817
|
+
byOwner.set(owner, [row]);
|
|
1818
|
+
order.push(owner);
|
|
1819
|
+
} else bucket.push(row);
|
|
1820
|
+
}
|
|
1821
|
+
const featured = FEATURED_OWNERS.filter((owner) => byOwner.has(owner));
|
|
1822
|
+
const rest = order.filter((owner) => !FEATURED_OWNERS.includes(owner));
|
|
1823
|
+
return [...featured, ...rest].map((owner) => ({
|
|
1824
|
+
owner,
|
|
1825
|
+
rows: byOwner.get(owner) ?? []
|
|
1826
|
+
}));
|
|
1827
|
+
}
|
|
1828
|
+
/**
|
|
1405
1829
|
* 接入为 DSH 模型的区块(开关 + 注册状态 + 允许清单)。
|
|
1406
1830
|
*
|
|
1407
1831
|
* 这是 M4 把抽象「试调」升级成真接入的落点:开关翻转即 POST provider 路由,
|
|
@@ -1432,6 +1856,8 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
1432
1856
|
const hostIds = status.enabledIds;
|
|
1433
1857
|
const hostKey = useMemo(() => JSON.stringify(hostIds), [hostIds]);
|
|
1434
1858
|
const [draft, setDraft] = useState(() => hostIds.slice());
|
|
1859
|
+
const [search, setSearch] = useState("");
|
|
1860
|
+
const [collapsed, setCollapsed] = useState([]);
|
|
1435
1861
|
const hideAllMode = parseAllowedList(draft).mode === "none";
|
|
1436
1862
|
useEffect(() => {
|
|
1437
1863
|
setDraft(normalizeRosterSave(hostIds));
|
|
@@ -1444,30 +1870,20 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
1444
1870
|
return kept.includes(id) ? kept.filter((x) => x !== id) : [...kept, id];
|
|
1445
1871
|
});
|
|
1446
1872
|
const ticked = hideAllMode ? 0 : roster.filter((row) => draft.includes(row.id)).length;
|
|
1447
|
-
const
|
|
1448
|
-
let statusNode;
|
|
1449
|
-
if (!degraded && typeof status.error === "string" && status.error !== "") statusNode = h("div", {
|
|
1873
|
+
const alertNode = !(status.error === "unavailable") && typeof status.error === "string" && status.error !== "" ? h("div", {
|
|
1450
1874
|
style: S.formError,
|
|
1451
1875
|
role: "alert"
|
|
1452
|
-
}, format(tt("provider.error"), { error: status.error }));
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
}
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
} }, tt("provider.llmMissing"));
|
|
1464
|
-
else statusNode = h("div", {
|
|
1465
|
-
style: {
|
|
1466
|
-
...S.muted,
|
|
1467
|
-
fontSize: 12
|
|
1468
|
-
},
|
|
1469
|
-
role: "status"
|
|
1470
|
-
}, tt("provider.notRegistered"));
|
|
1876
|
+
}, format(tt("provider.error"), { error: status.error })) : null;
|
|
1877
|
+
const statusPhrase = alertNode !== null ? null : status.registered === true ? {
|
|
1878
|
+
key: "provider.registered",
|
|
1879
|
+
warn: false
|
|
1880
|
+
} : status.llmAvailable !== true ? {
|
|
1881
|
+
key: "provider.llmMissing",
|
|
1882
|
+
warn: true
|
|
1883
|
+
} : {
|
|
1884
|
+
key: "provider.notRegistered",
|
|
1885
|
+
warn: false
|
|
1886
|
+
};
|
|
1471
1887
|
const tokenHint = enabled && tokenPresent === false ? h("div", { style: {
|
|
1472
1888
|
...S.formNote,
|
|
1473
1889
|
color: "var(--dsw-alias-state-warn-primary)"
|
|
@@ -1491,19 +1907,18 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
1491
1907
|
label: "1M"
|
|
1492
1908
|
}
|
|
1493
1909
|
];
|
|
1494
|
-
const contextNode = onSaveContextWindow === void 0 ? null : h("div", { style: {
|
|
1910
|
+
const contextNode = onSaveContextWindow === void 0 ? null : h("div", { style: S.advancedBlock }, h("div", { style: {
|
|
1495
1911
|
display: "flex",
|
|
1496
1912
|
gap: 8,
|
|
1497
1913
|
alignItems: "center",
|
|
1498
|
-
flexWrap: "wrap"
|
|
1499
|
-
margin: "0 0 8px"
|
|
1914
|
+
flexWrap: "wrap"
|
|
1500
1915
|
} }, h("label", {
|
|
1501
1916
|
style: {
|
|
1502
1917
|
fontSize: 12,
|
|
1503
1918
|
color: "var(--dsw-alias-label-secondary)"
|
|
1504
1919
|
},
|
|
1505
1920
|
htmlFor: "provider-context-window"
|
|
1506
|
-
}, tt("provider.
|
|
1921
|
+
}, tt("provider.contextWindowOverride")), h("select", {
|
|
1507
1922
|
id: "provider-context-window",
|
|
1508
1923
|
style: {
|
|
1509
1924
|
...S.rosterBulk,
|
|
@@ -1521,13 +1936,7 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
1521
1936
|
}, tt("provider.contextDefault")), CONTEXT_TIERS.map((tier) => h("option", {
|
|
1522
1937
|
value: String(tier.value),
|
|
1523
1938
|
key: String(tier.value)
|
|
1524
|
-
}, tier.label))), h("
|
|
1525
|
-
style: {
|
|
1526
|
-
...S.muted,
|
|
1527
|
-
fontSize: 11
|
|
1528
|
-
},
|
|
1529
|
-
title: tt("provider.contextHint")
|
|
1530
|
-
}, tt("provider.contextHint")));
|
|
1939
|
+
}, tier.label)))), h("div", { style: S.advancedHint }, tt("provider.contextHint")));
|
|
1531
1940
|
const switchRow = h("div", { style: {
|
|
1532
1941
|
display: "flex",
|
|
1533
1942
|
gap: 10,
|
|
@@ -1541,57 +1950,35 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
1541
1950
|
label: tt(enabled ? "provider.on" : "provider.off"),
|
|
1542
1951
|
busyLabel: tt("provider.busy"),
|
|
1543
1952
|
title: tt("provider.enable")
|
|
1544
|
-
}), h("span", {
|
|
1953
|
+
}), statusPhrase === null ? null : h("span", {
|
|
1954
|
+
style: S.dotSep,
|
|
1955
|
+
"aria-hidden": "true"
|
|
1956
|
+
}, "·"), statusPhrase === null ? null : h("span", {
|
|
1957
|
+
role: "status",
|
|
1958
|
+
style: {
|
|
1959
|
+
fontSize: 12,
|
|
1960
|
+
color: statusPhrase.warn ? "var(--dsw-alias-state-warn-primary)" : "var(--dsw-alias-label-secondary)"
|
|
1961
|
+
}
|
|
1962
|
+
}, tt(statusPhrase.key)), h("span", {
|
|
1963
|
+
style: S.dotSep,
|
|
1964
|
+
"aria-hidden": "true"
|
|
1965
|
+
}, "·"), h("span", { style: {
|
|
1545
1966
|
fontSize: 11,
|
|
1546
1967
|
color: "var(--dsw-alias-label-secondary)"
|
|
1547
1968
|
} }, tt(sourceKey)));
|
|
1548
|
-
const
|
|
1549
|
-
...S.muted,
|
|
1550
|
-
fontSize: 12,
|
|
1551
|
-
marginTop: 6
|
|
1552
|
-
} }, tt("provider.rosterHint")) : h("div", { style: S.modelPanel }, h("div", { style: S.rosterTools }, h("span", {
|
|
1553
|
-
style: S.rosterCount,
|
|
1554
|
-
title: format(tt("provider.enabledCount"), {
|
|
1555
|
-
count: ticked,
|
|
1556
|
-
total: roster.length
|
|
1557
|
-
})
|
|
1558
|
-
}, format(tt("provider.enabledCount"), {
|
|
1559
|
-
count: ticked,
|
|
1560
|
-
total: roster.length
|
|
1561
|
-
})), h("button", {
|
|
1562
|
-
type: "button",
|
|
1563
|
-
style: S.rosterBulk,
|
|
1564
|
-
disabled: busy,
|
|
1565
|
-
onClick: () => {
|
|
1566
|
-
setDraft(rosterIds.slice());
|
|
1567
|
-
onSaveList(rosterIds.slice());
|
|
1568
|
-
}
|
|
1569
|
-
}, tt("provider.allowAll")), h("button", {
|
|
1570
|
-
type: "button",
|
|
1571
|
-
style: S.rosterBulk,
|
|
1572
|
-
disabled: busy,
|
|
1573
|
-
onClick: () => {
|
|
1574
|
-
const none = hideAllRoster();
|
|
1575
|
-
setDraft(none);
|
|
1576
|
-
onSaveList(none);
|
|
1577
|
-
}
|
|
1578
|
-
}, tt("provider.hideAll")), h("button", {
|
|
1579
|
-
type: "button",
|
|
1580
|
-
style: S.primary,
|
|
1581
|
-
disabled: busy,
|
|
1582
|
-
onClick: () => onSaveList(draft.slice())
|
|
1583
|
-
}, tt("provider.saveList"))), h("ul", {
|
|
1584
|
-
style: S.modelList,
|
|
1585
|
-
role: "list"
|
|
1586
|
-
}, roster.map((row) => {
|
|
1969
|
+
const renderRosterRow = (row) => {
|
|
1587
1970
|
const id = row.id;
|
|
1588
1971
|
const unusable = row.available === false || row.quotaExhausted === true;
|
|
1589
1972
|
return h("li", {
|
|
1590
1973
|
key: id,
|
|
1591
1974
|
style: unusable ? {
|
|
1592
1975
|
...S.modelRow,
|
|
1976
|
+
...S.modelRowGrouped,
|
|
1593
1977
|
...S.modelRowOff
|
|
1594
|
-
} :
|
|
1978
|
+
} : {
|
|
1979
|
+
...S.modelRow,
|
|
1980
|
+
...S.modelRowGrouped
|
|
1981
|
+
}
|
|
1595
1982
|
}, h("div", { style: S.modelRowHead }, h("label", { style: {
|
|
1596
1983
|
display: "flex",
|
|
1597
1984
|
alignItems: "center",
|
|
@@ -1609,39 +1996,131 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
1609
1996
|
}), h("span", {
|
|
1610
1997
|
style: S.modelName,
|
|
1611
1998
|
title: id
|
|
1612
|
-
}, row.name)), row.vision === true ? h("span", { style: S.modelBadge }, tt("provider.vision")) : null, typeof row.contextWindow === "number" ? h("span", {
|
|
1999
|
+
}, trimOwnerPrefix(id, row.name))), row.vision === true ? h("span", { style: S.modelBadge }, tt("provider.vision")) : null, typeof row.contextWindow === "number" ? h("span", {
|
|
1613
2000
|
style: row.contextSource === "curated" ? {
|
|
1614
2001
|
...S.modelBadge,
|
|
1615
2002
|
color: "var(--dsw-alias-label-secondary)"
|
|
1616
2003
|
} : S.modelBadge,
|
|
1617
2004
|
title: row.contextSource === "curated" ? tt("provider.contextCuratedTitle") : tt("provider.contextWindow")
|
|
1618
|
-
}, row.contextSource === "curated" ? formatWindow(row.contextWindow) + " *" : formatWindow(row.contextWindow)) : null,
|
|
1619
|
-
style: S.
|
|
1620
|
-
title: tt("
|
|
1621
|
-
},
|
|
2005
|
+
}, row.contextSource === "curated" ? formatWindow(row.contextWindow) + " *" : formatWindow(row.contextWindow)) : null, typeof row.maxOutputLength === "number" ? h("span", {
|
|
2006
|
+
style: S.modelBadge,
|
|
2007
|
+
title: row.maxOutputSource === "override" ? tt("provider.maxOutputOverrideTitle") : tt("provider.maxOutput")
|
|
2008
|
+
}, formatWindow(row.maxOutputLength)) : null, row.quotaExhausted === true ? h("span", { style: {
|
|
1622
2009
|
...S.modelBadge,
|
|
1623
2010
|
color: "var(--dsw-alias-state-error-primary)"
|
|
1624
2011
|
} }, tt("provider.quotaExhausted")) : null, h("a", {
|
|
1625
2012
|
style: {
|
|
1626
2013
|
...S.formNote,
|
|
1627
2014
|
margin: 0,
|
|
1628
|
-
marginLeft: "auto"
|
|
2015
|
+
marginLeft: "auto",
|
|
2016
|
+
whiteSpace: "nowrap"
|
|
1629
2017
|
},
|
|
1630
2018
|
href: modelscopeModelUrl(row.id),
|
|
1631
2019
|
target: "_blank",
|
|
1632
2020
|
rel: "noreferrer",
|
|
1633
2021
|
title: tt("models.viewOnSiteTitle")
|
|
1634
2022
|
}, tt("models.viewOnSite"))));
|
|
1635
|
-
}
|
|
2023
|
+
};
|
|
2024
|
+
const query = search.trim().toLowerCase();
|
|
2025
|
+
const visibleRows = query === "" ? roster : roster.filter((row) => `${row.id} ${String(row.name ?? "")}`.toLowerCase().includes(query));
|
|
2026
|
+
const ownerGroups = groupByOwner(visibleRows);
|
|
2027
|
+
const rosterBlock = roster.length === 0 ? h("div", { style: {
|
|
2028
|
+
...S.muted,
|
|
2029
|
+
fontSize: 12,
|
|
2030
|
+
marginTop: 6
|
|
2031
|
+
} }, tt("provider.rosterHint")) : h("div", { style: S.modelPanel }, h("div", { style: S.rosterTools }, h("input", {
|
|
2032
|
+
type: "search",
|
|
2033
|
+
name: "provider-model-search",
|
|
2034
|
+
autoComplete: "off",
|
|
2035
|
+
style: {
|
|
2036
|
+
...S.input,
|
|
2037
|
+
flex: "1 1 160px",
|
|
2038
|
+
minWidth: 0
|
|
2039
|
+
},
|
|
2040
|
+
placeholder: tt("provider.search"),
|
|
2041
|
+
"aria-label": tt("provider.search"),
|
|
2042
|
+
value: search,
|
|
2043
|
+
disabled: busy,
|
|
2044
|
+
onChange: (event) => setSearch(String(event.target?.value ?? ""))
|
|
2045
|
+
}), h("span", {
|
|
2046
|
+
style: S.rosterCount,
|
|
2047
|
+
title: format(tt("provider.enabledCount"), {
|
|
2048
|
+
count: ticked,
|
|
2049
|
+
total: roster.length
|
|
2050
|
+
})
|
|
2051
|
+
}, format(tt("provider.enabledCount"), {
|
|
2052
|
+
count: ticked,
|
|
2053
|
+
total: roster.length
|
|
2054
|
+
})), h("button", {
|
|
2055
|
+
type: "button",
|
|
2056
|
+
style: S.rosterBulk,
|
|
2057
|
+
disabled: busy,
|
|
2058
|
+
onClick: () => {
|
|
2059
|
+
setDraft(rosterIds.slice());
|
|
2060
|
+
onSaveList(rosterIds.slice());
|
|
2061
|
+
}
|
|
2062
|
+
}, tt("provider.allowAll")), h("button", {
|
|
2063
|
+
type: "button",
|
|
2064
|
+
style: S.rosterBulk,
|
|
2065
|
+
disabled: busy,
|
|
2066
|
+
onClick: () => {
|
|
2067
|
+
const none = hideAllRoster();
|
|
2068
|
+
setDraft(none);
|
|
2069
|
+
onSaveList(none);
|
|
2070
|
+
}
|
|
2071
|
+
}, tt("provider.hideAll")), h("button", {
|
|
2072
|
+
type: "button",
|
|
2073
|
+
style: S.primary,
|
|
2074
|
+
disabled: busy,
|
|
2075
|
+
onClick: () => onSaveList(draft.slice())
|
|
2076
|
+
}, tt("provider.saveList"))), h("div", { style: S.advancedHint }, tt("provider.maxOutputHint")), visibleRows.length === 0 ? h("div", { style: {
|
|
2077
|
+
...S.muted,
|
|
2078
|
+
fontSize: 12,
|
|
2079
|
+
padding: "10px 4px"
|
|
2080
|
+
} }, tt("provider.noMatch")) : h("ul", {
|
|
2081
|
+
style: S.modelList,
|
|
2082
|
+
role: "list"
|
|
2083
|
+
}, ownerGroups.map((group) => {
|
|
2084
|
+
const isCollapsed = query === "" && collapsed.includes(group.owner);
|
|
2085
|
+
const groupTicked = hideAllMode ? 0 : group.rows.filter((row) => draft.includes(row.id)).length;
|
|
2086
|
+
return h("li", { key: group.owner }, h("button", {
|
|
2087
|
+
type: "button",
|
|
2088
|
+
style: S.modelGroup,
|
|
2089
|
+
"aria-expanded": !isCollapsed,
|
|
2090
|
+
"aria-label": `${tt(isCollapsed ? "section.expand" : "section.collapse")}: ${group.owner}`,
|
|
2091
|
+
onClick: () => setCollapsed((current) => current.includes(group.owner) ? current.filter((owner) => owner !== group.owner) : [...current, group.owner])
|
|
2092
|
+
}, h("svg", {
|
|
2093
|
+
viewBox: "0 0 16 16",
|
|
2094
|
+
width: 12,
|
|
2095
|
+
height: 12,
|
|
2096
|
+
fill: "none",
|
|
2097
|
+
stroke: "currentColor",
|
|
2098
|
+
strokeWidth: "1.5",
|
|
2099
|
+
strokeLinecap: "round",
|
|
2100
|
+
strokeLinejoin: "round",
|
|
2101
|
+
"aria-hidden": "true",
|
|
2102
|
+
style: isCollapsed ? {
|
|
2103
|
+
...S.chevron,
|
|
2104
|
+
transform: "rotate(-90deg)"
|
|
2105
|
+
} : S.chevron
|
|
2106
|
+
}, h("path", { d: "M3 6l5 5 5-5" })), h("span", { style: S.modelGroupOwner }, group.owner), FEATURED_OWNERS.includes(group.owner) ? h("span", {
|
|
2107
|
+
style: S.modelBadgeFeatured,
|
|
2108
|
+
title: tt("models.featuredTitle")
|
|
2109
|
+
}, 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", {
|
|
2110
|
+
style: S.modelList,
|
|
2111
|
+
role: "list"
|
|
2112
|
+
}, group.rows.map(renderRosterRow)));
|
|
2113
|
+
})));
|
|
2114
|
+
const resetRow = h("div", { style: S.rosterFoot }, h("button", {
|
|
1636
2115
|
type: "button",
|
|
1637
2116
|
style: S.button,
|
|
1638
2117
|
disabled: busy,
|
|
1639
2118
|
onClick: onReset
|
|
1640
|
-
}, tt("provider.reset")))
|
|
1641
|
-
return h("div", null, switchRow,
|
|
2119
|
+
}, tt("provider.reset")));
|
|
2120
|
+
return h("div", null, switchRow, alertNode, tokenHint, h("div", { style: {
|
|
1642
2121
|
...S.sectionTitle,
|
|
1643
2122
|
margin: "18px 0 8px"
|
|
1644
|
-
} }, tt("provider.models")), rosterBlock, error !== null ? h("div", {
|
|
2123
|
+
} }, tt("provider.models")), rosterBlock, contextNode, resetRow, error !== null ? h("div", {
|
|
1645
2124
|
style: S.formError,
|
|
1646
2125
|
role: "alert"
|
|
1647
2126
|
}, error) : null);
|
|
@@ -1660,11 +2139,15 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
1660
2139
|
* @param {object} props - 渲染参数。
|
|
1661
2140
|
* @returns {unknown} 卡片节点。
|
|
1662
2141
|
*/
|
|
1663
|
-
function DrawCard({ draw, busy, error, onToggle, onReset, tokenPresent, tt }) {
|
|
2142
|
+
function DrawCard({ draw, busy, error, onToggle, onReset, onSelectModel, onAutoModel, tokenPresent, tt }) {
|
|
1664
2143
|
const status = drawOf(draw);
|
|
1665
2144
|
const enabled = status.enabled === true;
|
|
1666
2145
|
const candidates = status.candidateIds;
|
|
1667
2146
|
const sourceKey = status.source === "panel" ? "draw.source.panel" : "draw.source.config";
|
|
2147
|
+
const effectiveModel = status.effectiveModel;
|
|
2148
|
+
const hasEffectiveKey = effectiveModel !== void 0;
|
|
2149
|
+
const candidateDetails = status.candidates;
|
|
2150
|
+
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;
|
|
1668
2151
|
const switchRow = h("div", { style: {
|
|
1669
2152
|
display: "flex",
|
|
1670
2153
|
gap: 10,
|
|
@@ -1707,7 +2190,7 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
1707
2190
|
},
|
|
1708
2191
|
role: "status"
|
|
1709
2192
|
}, tt("draw.notMounted"));
|
|
1710
|
-
const restartHint = enabled ? h("div", { style: {
|
|
2193
|
+
const restartHint = enabled && status.mountPending === true ? h("div", { style: {
|
|
1711
2194
|
...S.formNote,
|
|
1712
2195
|
color: "var(--dsw-alias-state-warn-primary)"
|
|
1713
2196
|
} }, tt("draw.restartHint")) : null;
|
|
@@ -1715,32 +2198,120 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
1715
2198
|
...S.formNote,
|
|
1716
2199
|
color: "var(--dsw-alias-state-warn-primary)"
|
|
1717
2200
|
} }, tt("draw.needsKey")) : null;
|
|
1718
|
-
const
|
|
2201
|
+
const preferredGoneHint = status.preferredModel !== null && status.preferredInCatalog === false ? h("div", {
|
|
2202
|
+
style: {
|
|
2203
|
+
...S.formNote,
|
|
2204
|
+
color: "var(--dsw-alias-state-warn-primary)"
|
|
2205
|
+
},
|
|
2206
|
+
role: "status"
|
|
2207
|
+
}, format(tt("draw.preferredGone"), { model: status.preferredModel })) : null;
|
|
2208
|
+
const autoDescription = !hasEffectiveKey || status.preferredModel !== null ? null : effectiveModel === null || effectiveModel === "" ? h("div", { style: {
|
|
1719
2209
|
...S.muted,
|
|
1720
|
-
fontSize:
|
|
1721
|
-
|
|
1722
|
-
|
|
2210
|
+
fontSize: 11,
|
|
2211
|
+
lineHeight: "16px",
|
|
2212
|
+
paddingLeft: 25
|
|
2213
|
+
} }, tt("draw.currentNone")) : h("div", { style: {
|
|
2214
|
+
...S.muted,
|
|
2215
|
+
fontSize: 11,
|
|
2216
|
+
lineHeight: "16px",
|
|
2217
|
+
paddingLeft: 25
|
|
2218
|
+
} }, autoReasonKey === null ? format(tt("draw.autoHint"), { model: effectiveModel }) : format(tt("draw.autoHintReason"), {
|
|
2219
|
+
model: effectiveModel,
|
|
2220
|
+
reason: tt(autoReasonKey)
|
|
2221
|
+
}));
|
|
2222
|
+
const RADIO_NAME = "modelscope-draw-model";
|
|
2223
|
+
/**
|
|
2224
|
+
* 一个候选的能力徽章(**正向各给一枚**,不是「能力减项 + 警告」)。
|
|
2225
|
+
*
|
|
2226
|
+
* 之前写成「平台能力档一枚 + 不能纯文生图时再加警告」是不对称的:能吃纯文生图
|
|
2227
|
+
* 是**加项优势**,却在 UI 上完全不可见,于是两个 Qwen-Image-Edit 看起来一样,
|
|
2228
|
+
* 其中一个还莫名带着警告。现在两个事实各画一枚:
|
|
2229
|
+
* - `图生图`——平台声明的 image-to-image(改图/图生图);
|
|
2230
|
+
* - `文生图`——能吃纯文生图(平台声明的 text-to-image,或实测已知的那个 id)。
|
|
2231
|
+
* 只给「图生图」的模型,徽章上挂 title 说明它必须给输入图(把警告从第二枚
|
|
2232
|
+
* 徽章降级成悬停说明,列表不再被警告噪声占满)。
|
|
2233
|
+
* 旧 Host 没有候选明细时退化为通用「出图」徽章——不猜能力。
|
|
2234
|
+
*/
|
|
2235
|
+
const capabilityBadges = (row) => {
|
|
2236
|
+
if (row.capability === null) return [h("span", {
|
|
2237
|
+
key: "badge",
|
|
2238
|
+
style: S.modelBadge
|
|
2239
|
+
}, tt("draw.badge"))];
|
|
2240
|
+
const badges = [];
|
|
2241
|
+
if (row.capability === "image-to-image") badges.push(h("span", {
|
|
2242
|
+
key: "badge-i2i",
|
|
2243
|
+
style: S.modelBadge,
|
|
2244
|
+
title: row.textToImage === true ? void 0 : tt("draw.imageOnlyTitle")
|
|
2245
|
+
}, tt("draw.badge.imageToImage")));
|
|
2246
|
+
if (row.textToImage === true) badges.push(h("span", {
|
|
2247
|
+
key: "badge-t2i",
|
|
2248
|
+
style: S.modelBadge
|
|
2249
|
+
}, tt("draw.badge.textToImage")));
|
|
2250
|
+
if (badges.length === 0) badges.push(h("span", {
|
|
2251
|
+
key: "badge",
|
|
2252
|
+
style: S.modelBadge
|
|
2253
|
+
}, tt("draw.badge")));
|
|
2254
|
+
return badges;
|
|
2255
|
+
};
|
|
2256
|
+
const candidateRows = Array.isArray(candidateDetails) ? candidateDetails.map((row) => ({
|
|
2257
|
+
id: row.id,
|
|
2258
|
+
capability: row.capability,
|
|
2259
|
+
textToImage: row.textToImage
|
|
2260
|
+
})) : candidates.map((id) => ({
|
|
2261
|
+
id,
|
|
2262
|
+
capability: null,
|
|
2263
|
+
textToImage: null
|
|
2264
|
+
}));
|
|
2265
|
+
/**
|
|
2266
|
+
* 一行广播的骨架:单选框 + 名字(+ 其余徽章由调用方给),可选第二行说明。
|
|
2267
|
+
*
|
|
2268
|
+
* `description` 是「这一项是什么」的地方(只有「自动选择」需要),缩进对齐到
|
|
2269
|
+
* 文字起点(复选框 15px + gap 10px = 25px),读起来是该项的注脚而不是新一行。
|
|
2270
|
+
*/
|
|
2271
|
+
const radioRow = (key, checked, label, labelText, disabled, onChange, extra, description) => h("li", {
|
|
2272
|
+
key,
|
|
2273
|
+
style: S.modelRow
|
|
2274
|
+
}, h("div", { style: S.modelRowHead }, h("label", { style: {
|
|
1723
2275
|
display: "flex",
|
|
1724
|
-
|
|
1725
|
-
gap:
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
}, h("
|
|
2276
|
+
alignItems: "center",
|
|
2277
|
+
gap: 10,
|
|
2278
|
+
flex: "1 1 auto",
|
|
2279
|
+
minWidth: 0,
|
|
2280
|
+
cursor: disabled ? "default" : "pointer"
|
|
2281
|
+
} }, h("input", {
|
|
2282
|
+
type: "radio",
|
|
2283
|
+
name: RADIO_NAME,
|
|
2284
|
+
checked,
|
|
2285
|
+
disabled,
|
|
2286
|
+
onChange,
|
|
2287
|
+
style: S.modelCheck,
|
|
2288
|
+
"aria-label": label
|
|
2289
|
+
}), h("span", {
|
|
1730
2290
|
style: S.modelName,
|
|
1731
|
-
title:
|
|
1732
|
-
},
|
|
2291
|
+
title: label
|
|
2292
|
+
}, labelText)), ...extra), ...description === void 0 ? [] : [description]);
|
|
2293
|
+
const candidateBlock = candidateRows.length === 0 ? h("div", { style: {
|
|
2294
|
+
...S.muted,
|
|
2295
|
+
fontSize: 12,
|
|
2296
|
+
marginTop: 6
|
|
2297
|
+
} }, tt("draw.noCandidates")) : h("div", { style: S.modelPanel }, h("ul", {
|
|
2298
|
+
style: S.modelList,
|
|
2299
|
+
role: "radiogroup",
|
|
2300
|
+
"aria-label": tt("draw.models")
|
|
2301
|
+
}, 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", {
|
|
2302
|
+
key: "link",
|
|
1733
2303
|
style: {
|
|
1734
2304
|
...S.formNote,
|
|
1735
2305
|
margin: 0,
|
|
1736
|
-
marginLeft: "auto"
|
|
2306
|
+
marginLeft: "auto",
|
|
2307
|
+
whiteSpace: "nowrap"
|
|
1737
2308
|
},
|
|
1738
|
-
href: modelscopeModelUrl(id),
|
|
2309
|
+
href: modelscopeModelUrl(row.id),
|
|
1739
2310
|
target: "_blank",
|
|
1740
2311
|
rel: "noreferrer",
|
|
1741
2312
|
title: tt("models.viewOnSiteTitle")
|
|
1742
|
-
}, tt("models.viewOnSite")))));
|
|
1743
|
-
return h("div", null, switchRow, statusNode,
|
|
2313
|
+
}, tt("models.viewOnSite"))]))));
|
|
2314
|
+
return h("div", null, switchRow, statusNode, h("div", { style: {
|
|
1744
2315
|
...S.sectionTitle,
|
|
1745
2316
|
margin: "18px 0 8px"
|
|
1746
2317
|
} }, tt("draw.models")), candidateBlock, h("div", { style: S.rosterFoot }, h("button", {
|
|
@@ -1751,9 +2322,9 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
1751
2322
|
}, tt("draw.reset"))), error !== null ? h("div", {
|
|
1752
2323
|
style: S.formError,
|
|
1753
2324
|
role: "alert"
|
|
1754
|
-
}, error) : null);
|
|
2325
|
+
}, error) : null, restartHint, tokenHint, preferredGoneHint);
|
|
1755
2326
|
}
|
|
1756
|
-
var EVENT_KIND_KEY, DRAW_ABSENT_KEY;
|
|
2327
|
+
var MAX_TICKS, TREND_WINDOW_TIERS, EVENT_KIND_KEY, DRAW_ABSENT_KEY;
|
|
1757
2328
|
var init_cards = __esmMin((() => {
|
|
1758
2329
|
init_format();
|
|
1759
2330
|
init_runtime();
|
|
@@ -1762,6 +2333,13 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
1762
2333
|
init_snapshot();
|
|
1763
2334
|
init_toggle_switch();
|
|
1764
2335
|
init_allow_list();
|
|
2336
|
+
MAX_TICKS = 7;
|
|
2337
|
+
TREND_WINDOW_TIERS = [
|
|
2338
|
+
7,
|
|
2339
|
+
14,
|
|
2340
|
+
30,
|
|
2341
|
+
90
|
|
2342
|
+
];
|
|
1765
2343
|
EVENT_KIND_KEY = {
|
|
1766
2344
|
quota: "events.quota",
|
|
1767
2345
|
rate_limit: "events.rate_limit",
|
|
@@ -2100,6 +2678,25 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
2100
2678
|
]);
|
|
2101
2679
|
const toggleDraw = useCallback((enabled) => void runDrawWrite(DRAW_PATH, { enabled }), [runDrawWrite]);
|
|
2102
2680
|
const resetDraw = useCallback(() => void runDrawWrite(DRAW_RESET_PATH, {}), [runDrawWrite]);
|
|
2681
|
+
const saveDrawModel = useCallback((modelId) => void runDrawWrite(DRAW_PATH, { modelId }), [runDrawWrite]);
|
|
2682
|
+
const [trendBusy, setTrendBusy] = useState(false);
|
|
2683
|
+
const [trendError, setTrendError] = useState(null);
|
|
2684
|
+
const saveTrendWindow = useCallback(async (windowDays) => {
|
|
2685
|
+
setTrendBusy(true);
|
|
2686
|
+
setTrendError(null);
|
|
2687
|
+
try {
|
|
2688
|
+
await postWrite(TREND_WINDOW_PATH, { windowDays });
|
|
2689
|
+
load();
|
|
2690
|
+
} catch (reason) {
|
|
2691
|
+
setTrendError(writeErrorText(reason, tt));
|
|
2692
|
+
} finally {
|
|
2693
|
+
setTrendBusy(false);
|
|
2694
|
+
}
|
|
2695
|
+
}, [
|
|
2696
|
+
load,
|
|
2697
|
+
postWrite,
|
|
2698
|
+
tt
|
|
2699
|
+
]);
|
|
2103
2700
|
const [tokenBusy, setTokenBusy] = useState(false);
|
|
2104
2701
|
const [tokenError, setTokenError] = useState(null);
|
|
2105
2702
|
const saveToken = useCallback(async (value) => {
|
|
@@ -2181,8 +2778,14 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
2181
2778
|
open: openSections.trend,
|
|
2182
2779
|
onToggle: () => toggleSection("trend"),
|
|
2183
2780
|
tt
|
|
2184
|
-
}, h(
|
|
2185
|
-
|
|
2781
|
+
}, h(TrendWindowPicker, {
|
|
2782
|
+
trend: snap.trend,
|
|
2783
|
+
busy: trendBusy,
|
|
2784
|
+
error: trendError,
|
|
2785
|
+
onChange: (windowDays) => void saveTrendWindow(windowDays),
|
|
2786
|
+
tt
|
|
2787
|
+
}), h(TrendBars, {
|
|
2788
|
+
buckets: snap.trend?.buckets ?? null,
|
|
2186
2789
|
tt
|
|
2187
2790
|
})), h(SectionCard, {
|
|
2188
2791
|
title: tt("section.events"),
|
|
@@ -2190,7 +2793,7 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
2190
2793
|
onToggle: () => toggleSection("events"),
|
|
2191
2794
|
tt
|
|
2192
2795
|
}, h(EventsList, {
|
|
2193
|
-
events: snap.events ??
|
|
2796
|
+
events: snap.events ?? null,
|
|
2194
2797
|
tt
|
|
2195
2798
|
})));
|
|
2196
2799
|
};
|
|
@@ -2220,6 +2823,8 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
2220
2823
|
error: drawError,
|
|
2221
2824
|
onToggle: toggleDraw,
|
|
2222
2825
|
onReset: resetDraw,
|
|
2826
|
+
onSelectModel: (id) => saveDrawModel(id),
|
|
2827
|
+
onAutoModel: () => saveDrawModel(null),
|
|
2223
2828
|
tokenPresent: data?.token?.present === true,
|
|
2224
2829
|
tt
|
|
2225
2830
|
})));
|