dsh-connect-modelscope-token-plan 0.5.0 → 0.6.2

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/client.js CHANGED
@@ -136,7 +136,7 @@ var dsh_connect_modelscope_token_plan_client = (function() {
136
136
  var zh, en;
137
137
  var init_i18n = __esmMin((() => {
138
138
  zh = {
139
- "panel.title": "魔搭接入 · 额度面板",
139
+ "panel.title": "魔搭模型接入",
140
140
  "panel.refresh": "刷新",
141
141
  "panel.updated": "更新于 {time}",
142
142
  "panel.loading": "加载中…",
@@ -212,6 +212,9 @@ var dsh_connect_modelscope_token_plan_client = (function() {
212
212
  "provider.contextDefault": "不覆盖(默认)",
213
213
  "provider.contextHint": "目录未声明窗口时套用此值(会盖过内置策展表);保存后立即重新注册",
214
214
  "provider.contextCuratedTitle": "窗口来自内置策展表(models.dev 交叉真值),平台未亲口声明;带 * 标记",
215
+ "provider.maxOutput": "最大输出",
216
+ "provider.maxOutputOverrideTitle": "最大输出:来自 maxTokensOverrides 里你写的值(盖过目录声明)",
217
+ "provider.maxOutputHint": "魔搭目录不返回输出上限,未覆盖的模型按 harness 默认(32768)截断;要更大就在 maxTokensOverrides 里逐模型写实测值(面板徽章用「最大输出」显示生效值)",
215
218
  "section.draw": "出图工具",
216
219
  "draw.enable": "开启后,agent 可在对话里用 modelscope_draw_image 生成或编辑图片",
217
220
  "draw.on": "已开启",
@@ -242,13 +245,20 @@ var dsh_connect_modelscope_token_plan_client = (function() {
242
245
  "draw.absent.peerLoad": "开关已开,但 tools 模块加载失败,出图工具缺席(Host 问题,非配置)。",
243
246
  "draw.absent.registry": "开关已开,但工具注册被 Host 拒绝,出图工具缺席(Host 问题,非配置)。",
244
247
  "draw.absent.unknown": "开关已开,但出图工具未挂载,原因未知。",
248
+ "draw.preferredGone": "已保存的默认模型 {model} 已不在当前目录(下架或改名)——出图会用它失败,请在下面重选。",
245
249
  "trend.none": "还没有本地调用记录。",
250
+ "trend.unreadable": "本地计数读不到,趋势图无法显示(不是 0 次)。",
246
251
  "trend.legend": "柱长相对区间内最大值,仅本地口径。",
252
+ "trend.metric": "趋势指标",
253
+ "trend.metricCalls": "调用次数",
254
+ "trend.metricTokens": "token 数",
255
+ "trend.legendTokens": "按 token 数相对区间内最大值画柱,仅本地口径;失败调用没有用量记录,与「该日无数据」一样显示为 0。",
247
256
  "trend.window": "显示天数",
248
257
  "trend.windowDefault": "默认(跟随配置)",
249
258
  "trend.windowDays": "{days} 天",
250
259
  "trend.windowHint": "只决定画多少天;能回看多久另有一个保留窗口(留得比画得多才画得出更远)。",
251
260
  "events.none": "暂无事件。",
261
+ "events.unreadable": "本地计数读不到,事件流无法显示(不是没有事件)。",
252
262
  "events.quota": "额度",
253
263
  "events.rate_limit": "限频",
254
264
  "events.error": "错误",
@@ -278,7 +288,7 @@ var dsh_connect_modelscope_token_plan_client = (function() {
278
288
  "composer.stale": "上次数据"
279
289
  };
280
290
  en = {
281
- "panel.title": "ModelScope Access · Quota",
291
+ "panel.title": "ModelScope Model Access",
282
292
  "panel.refresh": "Refresh",
283
293
  "panel.updated": "Updated {time}",
284
294
  "panel.loading": "Loading…",
@@ -354,6 +364,9 @@ var dsh_connect_modelscope_token_plan_client = (function() {
354
364
  "provider.contextDefault": "No override (default)",
355
365
  "provider.contextHint": "Applied when the catalog declares no window (it overrides the built-in curated table); re-registers immediately",
356
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)",
357
370
  "section.draw": "Draw tool",
358
371
  "draw.enable": "Once on, the agent can generate or edit images with modelscope_draw_image",
359
372
  "draw.on": "On",
@@ -384,13 +397,20 @@ var dsh_connect_modelscope_token_plan_client = (function() {
384
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).",
385
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).",
386
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.",
387
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).",
388
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.",
389
408
  "trend.window": "Days shown",
390
409
  "trend.windowDefault": "Default (follow config)",
391
410
  "trend.windowDays": "{days} days",
392
411
  "trend.windowHint": "Only sets how many days are drawn; how far back history is kept is a separate retention window.",
393
412
  "events.none": "No events.",
413
+ "events.unreadable": "Local counters unreadable; event stream cannot be shown (not \"no events\").",
394
414
  "events.quota": "Quota",
395
415
  "events.rate_limit": "Rate limit",
396
416
  "events.error": "Error",
@@ -455,6 +475,36 @@ var dsh_connect_modelscope_token_plan_client = (function() {
455
475
  if (number >= 1e4) return Math.round(number).toLocaleString();
456
476
  return String(Math.round(number * 100) / 100);
457
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
+ }
458
508
  /** 填 `{key}` 模板:vars 的每个键对应模板里的同名占位符。 */
459
509
  function format(template, vars) {
460
510
  let text = template;
@@ -760,6 +810,28 @@ var dsh_connect_modelscope_token_plan_client = (function() {
760
810
  fontSize: 11,
761
811
  color: "var(--dsw-alias-label-secondary)"
762
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
+ },
763
835
  muted: { color: "var(--dsw-alias-label-secondary)" },
764
836
  error: { color: "var(--dsw-alias-state-error-primary)" },
765
837
  empty: {
@@ -1021,13 +1093,15 @@ var dsh_connect_modelscope_token_plan_client = (function() {
1021
1093
  };
1022
1094
  case "quota": return {
1023
1095
  daily: { usedLocal: null },
1024
- perModel: [],
1096
+ perModel: null,
1025
1097
  countingNote: "local-counting"
1026
1098
  };
1027
- case "events": return [];
1099
+ case "events": return null;
1028
1100
  case "trend": return {
1029
1101
  days: 0,
1030
- buckets: []
1102
+ windowDays: null,
1103
+ maxDays: 0,
1104
+ buckets: null
1031
1105
  };
1032
1106
  case "models": return {
1033
1107
  available: false,
@@ -1101,6 +1175,8 @@ var dsh_connect_modelscope_token_plan_client = (function() {
1101
1175
  const id = String(row.id ?? "");
1102
1176
  const w = row.contextWindow;
1103
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;
1104
1180
  return {
1105
1181
  id,
1106
1182
  name: typeof row.name === "string" && row.name !== "" ? row.name : id,
@@ -1108,7 +1184,9 @@ var dsh_connect_modelscope_token_plan_client = (function() {
1108
1184
  available: row.available !== false,
1109
1185
  quotaExhausted: row.quotaExhausted === true,
1110
1186
  contextSource,
1111
- ...typeof w === "number" && Number.isFinite(w) && w > 0 ? { contextWindow: w } : {}
1187
+ maxOutputSource,
1188
+ ...typeof w === "number" && Number.isFinite(w) && w > 0 ? { contextWindow: w } : {},
1189
+ ...typeof out === "number" && Number.isFinite(out) && out > 0 ? { maxOutputLength: out } : {}
1112
1190
  };
1113
1191
  }).filter((entry) => entry.id !== "") : [];
1114
1192
  return {
@@ -1169,6 +1247,7 @@ var dsh_connect_modelscope_token_plan_client = (function() {
1169
1247
  ..."effectiveSource" in d ? { effectiveSource } : {},
1170
1248
  absentReason,
1171
1249
  mountPending: d.mountPending === true,
1250
+ preferredInCatalog: d.preferredInCatalog === true ? true : d.preferredInCatalog === false ? false : null,
1172
1251
  toolName: typeof d.toolName === "string" && d.toolName !== "" ? d.toolName : "modelscope_draw_image"
1173
1252
  };
1174
1253
  }
@@ -1228,6 +1307,7 @@ var dsh_connect_modelscope_token_plan_client = (function() {
1228
1307
  effectiveSource: null,
1229
1308
  absentReason: null,
1230
1309
  mountPending: false,
1310
+ preferredInCatalog: null,
1231
1311
  toolName: "modelscope_draw_image"
1232
1312
  });
1233
1313
  GUIDANCE_BY_CODE = Object.freeze({
@@ -1453,17 +1533,19 @@ var dsh_connect_modelscope_token_plan_client = (function() {
1453
1533
  function LocalDailyCard({ snapshot, tt }) {
1454
1534
  const quota = snapshot.quota;
1455
1535
  const daily = quota?.daily ?? { usedLocal: null };
1456
- const perModel = Array.isArray(quota?.perModel) ? quota.perModel : [];
1536
+ const perModel = quota?.perModel;
1537
+ const perModelRows = Array.isArray(perModel) ? perModel : perModel === null ? null : [];
1457
1538
  const unknownUsed = !(typeof daily.usedLocal === "number" && Number.isFinite(daily.usedLocal));
1458
1539
  const usedText = unknownUsed ? "—" : count(daily.usedLocal);
1540
+ const perModelUnknown = perModelRows === null;
1459
1541
  return h("div", { style: S.card }, h("div", { style: S.poolName }, format(tt("quota.headline"), {
1460
1542
  calls: usedText,
1461
- models: count(perModel.length)
1462
- })), unknownUsed ? h("div", { style: {
1543
+ models: perModelUnknown ? "—" : count(perModelRows.length)
1544
+ })), unknownUsed || perModelUnknown ? h("div", { style: {
1463
1545
  ...S.formNote,
1464
1546
  color: "var(--dsw-alias-state-warn-primary)"
1465
- } }, tt("quota.unavailable")) : null, h(ModelUsageTable, {
1466
- rows: perModel,
1547
+ } }, tt("quota.unavailable")) : null, perModelUnknown ? null : h(ModelUsageTable, {
1548
+ rows: perModelRows,
1467
1549
  tt
1468
1550
  }), h("div", { style: S.trendLegend }, tt("quota.note")));
1469
1551
  }
@@ -1479,7 +1561,7 @@ var dsh_connect_modelscope_token_plan_client = (function() {
1479
1561
  }, h("div", { style: S.trendRowHead }, h("span", {
1480
1562
  style: S.trendModel,
1481
1563
  title: row.modelId
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", {
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", {
1483
1565
  style: S.trendBar,
1484
1566
  role: "progressbar",
1485
1567
  "aria-valuenow": Math.round(pct),
@@ -1526,33 +1608,63 @@ var dsh_connect_modelscope_token_plan_client = (function() {
1526
1608
  }
1527
1609
  return out;
1528
1610
  }
1529
- /** 近 N 天本地调用趋势(天桶;缺桶 = 该日 0 次,真实零)——**竖向柱状图**。 */
1611
+ /**
1612
+ * 近 N 天本地调用趋势(天桶;缺桶 = 该日 0 次,真实零)——**竖向柱状图**。
1613
+ *
1614
+ * token 与调用次数共用同一根柱子但**各自归一化**(`max` 按当前指标现算):两者量级
1615
+ * 差三四个数量级,若共用一把尺,次数柱会被 token 柱压成贴地的一条线——那图就撒谎了。
1616
+ * 一次只画一个指标(不放双柱),窄容器里双柱既挤又难读,且对比还得再解释一遍。
1617
+ *
1618
+ * 切换态活在组件内的 useState:它是纯显示偏好,不落盘——与「显示天数」不同,那个
1619
+ * 会改变 Host 的取数窗口(真要持久化就该走 usage-prefs,别混进这里)。
1620
+ */
1530
1621
  function TrendBars({ buckets, tt }) {
1622
+ const [metric, setMetric] = useState("calls");
1623
+ if (buckets === null) return h("div", { style: S.trendLegend }, tt("trend.unreadable"));
1531
1624
  if (!Array.isArray(buckets) || buckets.length === 0) return h("div", { style: S.trendLegend }, tt("trend.none"));
1532
1625
  const rows = buckets.map((bucket) => ({
1533
1626
  dateKey: bucket.dateKey,
1534
- 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
1535
1629
  }));
1536
- const max = Math.max(0, ...rows.map((row) => row.calls));
1537
- return h("div", null, h("div", { style: S.trendChartBars }, rows.map((row) => {
1538
- const pct = max > 0 ? row.calls / max * 100 : 0;
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;
1539
1651
  return h("div", {
1540
1652
  key: row.dateKey,
1541
1653
  style: S.trendChartBar,
1542
- title: `${row.dateKey}: ${count(row.calls)}`
1543
- }, row.calls > 0 ? h("span", {
1654
+ title: `${row.dateKey}: ${readout(value)}`
1655
+ }, value > 0 ? h("span", {
1544
1656
  style: {
1545
1657
  ...S.trendChartValue,
1546
1658
  bottom: `calc(${pct}% + 3px)`
1547
1659
  },
1548
1660
  "aria-hidden": "true"
1549
- }, count(row.calls)) : null, h("div", {
1661
+ }, readout(value)) : null, h("div", {
1550
1662
  style: {
1551
1663
  ...S.trendChartFill,
1552
1664
  height: `${pct}%`
1553
1665
  },
1554
1666
  role: "progressbar",
1555
- "aria-label": `${row.dateKey} ${count(row.calls)}`,
1667
+ "aria-label": `${row.dateKey} ${readout(value)}`,
1556
1668
  "aria-valuenow": Math.round(pct),
1557
1669
  "aria-valuemin": 0,
1558
1670
  "aria-valuemax": 100
@@ -1564,7 +1676,7 @@ var dsh_connect_modelscope_token_plan_client = (function() {
1564
1676
  title: key,
1565
1677
  style: { whiteSpace: "nowrap" }
1566
1678
  }, shortDateKey(key));
1567
- })), h("div", { style: S.trendLegend }, tt("trend.legend")));
1679
+ })), h("div", { style: S.trendLegend }, tt(tokenMode ? "trend.legendTokens" : "trend.legend")));
1568
1680
  }
1569
1681
  /**
1570
1682
  * 趋势显示窗口选择器:默认(跟随 `trendDays` 配置)/ 7 / 14 / 30 / 90 天。
@@ -1622,8 +1734,9 @@ var dsh_connect_modelscope_token_plan_client = (function() {
1622
1734
  role: "alert"
1623
1735
  }, error) : null);
1624
1736
  }
1625
- /** 事件流(429/错误),Newest-first。 */
1737
+ /** 事件流(429/错误),Newest-first。`events` 为 null = 读不到,渲染「—」(读不到 ≠ 0)。 */
1626
1738
  function EventsList({ events, tt }) {
1739
+ if (events === null) return h("div", { style: S.trendLegend }, tt("events.unreadable"));
1627
1740
  if (!Array.isArray(events) || events.length === 0) return h("div", { style: S.trendLegend }, tt("events.none"));
1628
1741
  return h("div", null, events.map((event, index) => h("div", {
1629
1742
  key: `${event.at}-${index}`,
@@ -1889,7 +2002,10 @@ var dsh_connect_modelscope_token_plan_client = (function() {
1889
2002
  color: "var(--dsw-alias-label-secondary)"
1890
2003
  } : S.modelBadge,
1891
2004
  title: row.contextSource === "curated" ? tt("provider.contextCuratedTitle") : tt("provider.contextWindow")
1892
- }, row.contextSource === "curated" ? formatWindow(row.contextWindow) + " *" : formatWindow(row.contextWindow)) : null, row.quotaExhausted === true ? h("span", { style: {
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: {
1893
2009
  ...S.modelBadge,
1894
2010
  color: "var(--dsw-alias-state-error-primary)"
1895
2011
  } }, tt("provider.quotaExhausted")) : null, h("a", {
@@ -1957,7 +2073,7 @@ var dsh_connect_modelscope_token_plan_client = (function() {
1957
2073
  style: S.primary,
1958
2074
  disabled: busy,
1959
2075
  onClick: () => onSaveList(draft.slice())
1960
- }, tt("provider.saveList"))), visibleRows.length === 0 ? h("div", { style: {
2076
+ }, tt("provider.saveList"))), h("div", { style: S.advancedHint }, tt("provider.maxOutputHint")), visibleRows.length === 0 ? h("div", { style: {
1961
2077
  ...S.muted,
1962
2078
  fontSize: 12,
1963
2079
  padding: "10px 4px"
@@ -2074,7 +2190,7 @@ var dsh_connect_modelscope_token_plan_client = (function() {
2074
2190
  },
2075
2191
  role: "status"
2076
2192
  }, tt("draw.notMounted"));
2077
- const restartHint = enabled ? h("div", { style: {
2193
+ const restartHint = enabled && status.mountPending === true ? h("div", { style: {
2078
2194
  ...S.formNote,
2079
2195
  color: "var(--dsw-alias-state-warn-primary)"
2080
2196
  } }, tt("draw.restartHint")) : null;
@@ -2082,6 +2198,13 @@ var dsh_connect_modelscope_token_plan_client = (function() {
2082
2198
  ...S.formNote,
2083
2199
  color: "var(--dsw-alias-state-warn-primary)"
2084
2200
  } }, tt("draw.needsKey")) : null;
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;
2085
2208
  const autoDescription = !hasEffectiveKey || status.preferredModel !== null ? null : effectiveModel === null || effectiveModel === "" ? h("div", { style: {
2086
2209
  ...S.muted,
2087
2210
  fontSize: 11,
@@ -2199,7 +2322,7 @@ var dsh_connect_modelscope_token_plan_client = (function() {
2199
2322
  }, tt("draw.reset"))), error !== null ? h("div", {
2200
2323
  style: S.formError,
2201
2324
  role: "alert"
2202
- }, error) : null, restartHint, tokenHint);
2325
+ }, error) : null, restartHint, tokenHint, preferredGoneHint);
2203
2326
  }
2204
2327
  var MAX_TICKS, TREND_WINDOW_TIERS, EVENT_KIND_KEY, DRAW_ABSENT_KEY;
2205
2328
  var init_cards = __esmMin((() => {
@@ -2662,7 +2785,7 @@ var dsh_connect_modelscope_token_plan_client = (function() {
2662
2785
  onChange: (windowDays) => void saveTrendWindow(windowDays),
2663
2786
  tt
2664
2787
  }), h(TrendBars, {
2665
- buckets: snap.trend?.buckets ?? [],
2788
+ buckets: snap.trend?.buckets ?? null,
2666
2789
  tt
2667
2790
  })), h(SectionCard, {
2668
2791
  title: tt("section.events"),
@@ -2670,7 +2793,7 @@ var dsh_connect_modelscope_token_plan_client = (function() {
2670
2793
  onToggle: () => toggleSection("events"),
2671
2794
  tt
2672
2795
  }, h(EventsList, {
2673
- events: snap.events ?? [],
2796
+ events: snap.events ?? null,
2674
2797
  tt
2675
2798
  })));
2676
2799
  };
package/cordis.patch.yml CHANGED
@@ -32,6 +32,17 @@
32
32
  retentionDays: 90
33
33
  # 429/错误事件环形缓冲的上限(面板只展示最近的)。上界 1000,硬夹。
34
34
  maxEvents: 50
35
+ # 单模型「额度耗尽」推断窗口(分钟)。官方只有账户级魔粒余额、没有
36
+ # per-model 额度端点,所以「哪个模型刚才在限频」从本插件的 429 事件流
37
+ # 推断(纯函数见 src/host/usage-store.ts 的 exhaustedModelIds):某模型
38
+ # 最近在这个窗口内出现过 kind=quota 的事件(重分类后的口径——被误判成
39
+ # QUOTA 的 rpm 限频已在此之前纠正为 rate_limit,不算证据),选择器就不再
40
+ # 提供它、面板 roster 灰显;事件移出窗口即恢复。**恢复不可观测是已知
41
+ # 代价**:成功的调用不发事件,额度若在窗口内恢复(按天重置恰好如此),
42
+ # 模型最多灰显满一个窗口——方向保守(不提供注定 429 的模型),且灰显
43
+ # 带着原因。下界 5 分钟(再短规则形同虚设),上界 1440(一天,额度按天
44
+ # 重置,更长的窗口没有对应的恢复语义)。硬夹。
45
+ exhaustionWindowMinutes: 60
35
46
  # Host 侧对 /v1/models 的缓存秒数;面板脚注直接引用此值。上界 600。
36
47
  cacheSeconds: 60
37
48
  # 面板轮询间隔,由 Host 下发、面板跟随。上界 3600(1 小时)。
@@ -47,11 +58,12 @@
47
58
  # tab 的「接入为 DSH 模型」开关可随时翻转,面板保存值优先于这里的默认。
48
59
  # 注册后魔搭模型进入 DSH 模型选择器。
49
60
  #
50
- # 计数范围:注册开关打开后,DSH 的魔搭对话经本插件的适配器出去,每一次
51
- # 调用与每一条 429/错误事件都由 src/host/usage-observer.ts 在流的出口
52
- # 记进同一个 usage-store——所以面板「额度」tab 的本地计数覆盖**全部 DSH
53
- # 魔搭调用**,不只反映面板自身的探针。直连魔搭的其它客户端仍不计入
54
- # (本插件看不见它们),消耗总量以官方魔粒余额为准。
61
+ # 计数范围:注册开关打开后,本客户端(本 profile)的魔搭对话经本插件的
62
+ # 适配器出去,每一次调用与每一条 429/错误事件都由 src/host/usage-observer.ts
63
+ # 在流的出口记进同一个 usage-store。口径是**本客户端**:桌面端与 Web 端是
64
+ # 两个 profile,各记各的账、互不可见(`npm run doctor` 并排列出各 profile),
65
+ # 别把它当跨端总量。直连魔搭的其它客户端同样不计入(本插件看不见它们),
66
+ # 消耗总量以官方魔粒余额为准。
55
67
  #
56
68
  # 偏差方向是**按流计次**:重试不在流内部发生,而是流被消费完后由
57
69
  # agent/request-error 瀑布决定重发,因而每次重试各记一次(按「魔搭按
@@ -59,6 +71,25 @@
59
71
  # 与 README「诚实声明」。
60
72
  registerProvider: false
61
73
  # ------------------------------------------------------------------
74
+ # 逐模型的单次输出上限覆盖(模型 id → tokens)。空对象 = 目录说了算。
75
+ #
76
+ # **为什么必须有它**:本插件对输出上限的策略是「目录声明了才声明」,而魔搭
77
+ # `/v1/models` **从不返回**这个字段(实测每条只有 id / object / owned_by /
78
+ # created,见 docs/REFERENCES.md)。两条合起来 = **每一个**模型的 descriptor
79
+ # 都不带 maxTokens,harness 于是用它的 `defaultMaxTokens ?? 32768` 兜底:
80
+ # 32768 既不是魔搭声明的数,也不是实测过的数,它只是 harness 的缺省值。
81
+ # 目录给不出真值时,只有操作者知道某个模型实测能吐多少——没有这个字段,
82
+ # 那份知识无处可写(写进目录缓存会被下一轮轮询覆盖)。
83
+ #
84
+ # 优先级 `覆盖 > 目录声明 > 不声明`。键是**精确模型 id**,不做子串匹配——
85
+ # 上限是请求参数,宽松的键会顺手改掉一批没被点名的模型(键写 flash 会命中
86
+ # 七八条);值必须是正整数(0 = 未声明;非法值丢弃而不是夹取)。
87
+ # 面板模型清单行会用「最大输出」徽章显示生效值,来源是覆盖还是目录一目了然。
88
+ #
89
+ # 例(把下面这行整行复制出去、去掉 `#` 即可):
90
+ # maxTokensOverrides: { "deepseek-ai/DeepSeek-V4.1-Flash": 65536 }
91
+ maxTokensOverrides: {}
92
+ # ------------------------------------------------------------------
62
93
  # 给 agent 注册出图工具(`modelscope_draw_image`)。**与上面的
63
94
  # registerProvider 是两条独立开关**:那个管「魔搭作为 chat provider」,
64
95
  # 这个管「agent 能不能出图」。默认同样关(opt-in)。