@eddyskywalker/dsh-chatgpt-subscription 0.41.1 → 0.43.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.
Files changed (58) hide show
  1. package/CHANGELOG.md +77 -0
  2. package/README.md +1 -0
  3. package/lib/client.js +445 -476
  4. package/lib/client.js.map +1 -1
  5. package/lib/index.js +632 -219
  6. package/lib/types/client/command-code/CommandCodeComposerQuota.d.ts.map +1 -1
  7. package/lib/types/client/common/format.d.ts +16 -0
  8. package/lib/types/client/common/format.d.ts.map +1 -1
  9. package/lib/types/client/index.d.ts.map +1 -1
  10. package/lib/types/client/kimi-code/KimiCodeComposerQuota.d.ts.map +1 -1
  11. package/lib/types/client/kimi-code/KimiCodeSection.d.ts.map +1 -1
  12. package/lib/types/client/kimi-code/KimiQuotaCard.d.ts.map +1 -1
  13. package/lib/types/client/kimi-code/locales.d.ts +41 -14
  14. package/lib/types/client/kimi-code/locales.d.ts.map +1 -1
  15. package/lib/types/client/kimi-code/styles.d.ts.map +1 -1
  16. package/lib/types/client/locales-ru/hub.d.ts.map +1 -1
  17. package/lib/types/client/locales-ru/kimi-code.d.ts.map +1 -1
  18. package/lib/types/client/locales-ru/opendesign.d.ts.map +1 -1
  19. package/lib/types/client/locales.d.ts +1 -27
  20. package/lib/types/client/locales.d.ts.map +1 -1
  21. package/lib/types/client/opendesign/OpenDesignSection.d.ts.map +1 -1
  22. package/lib/types/client/opendesign/locales.d.ts +12 -0
  23. package/lib/types/client/opendesign/locales.d.ts.map +1 -1
  24. package/lib/types/compat.d.ts +1 -1
  25. package/lib/types/host/claude/types.d.ts +7 -1
  26. package/lib/types/host/claude/types.d.ts.map +1 -1
  27. package/lib/types/host/compaction-route/index.d.ts +16 -1
  28. package/lib/types/host/compaction-route/index.d.ts.map +1 -1
  29. package/lib/types/host/glm-coding-plan/types.d.ts +5 -1
  30. package/lib/types/host/glm-coding-plan/types.d.ts.map +1 -1
  31. package/lib/types/host/kimi-code/account-pool.d.ts +26 -1
  32. package/lib/types/host/kimi-code/account-pool.d.ts.map +1 -1
  33. package/lib/types/host/kimi-code/client.d.ts +24 -0
  34. package/lib/types/host/kimi-code/client.d.ts.map +1 -1
  35. package/lib/types/host/kimi-code/model-catalog.d.ts.map +1 -1
  36. package/lib/types/host/kimi-code/oauth.d.ts +16 -0
  37. package/lib/types/host/kimi-code/oauth.d.ts.map +1 -1
  38. package/lib/types/host/kimi-code/routes.d.ts.map +1 -1
  39. package/lib/types/host/opendesign/adapter.d.ts +11 -13
  40. package/lib/types/host/opendesign/adapter.d.ts.map +1 -1
  41. package/lib/types/host/opendesign/catalog.d.ts +14 -0
  42. package/lib/types/host/opendesign/catalog.d.ts.map +1 -1
  43. package/lib/types/host/opendesign/line.d.ts +17 -6
  44. package/lib/types/host/opendesign/line.d.ts.map +1 -1
  45. package/lib/types/host/opendesign/routes.d.ts.map +1 -1
  46. package/lib/types/host/opendesign/token-store.d.ts +91 -4
  47. package/lib/types/host/opendesign/token-store.d.ts.map +1 -1
  48. package/lib/types/host/opendesign/types.d.ts.map +1 -1
  49. package/lib/types/host/qoder/types.d.ts +6 -1
  50. package/lib/types/host/qoder/types.d.ts.map +1 -1
  51. package/lib/types/host/step-code/types.d.ts +5 -1
  52. package/lib/types/host/step-code/types.d.ts.map +1 -1
  53. package/lib/types/index.d.ts.map +1 -1
  54. package/lib/types/shared/opendesign-contracts.d.ts +24 -1
  55. package/lib/types/shared/opendesign-contracts.d.ts.map +1 -1
  56. package/package.json +1 -1
  57. package/lib/types/client/CompactionRouteBundleConfig.d.ts +0 -19
  58. package/lib/types/client/CompactionRouteBundleConfig.d.ts.map +0 -1
package/lib/client.js CHANGED
@@ -146,11 +146,11 @@ window.__ModuleLoader__.load({
146
146
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
147
147
  className: "dsh-codex-composer-quota",
148
148
  "data-level": quota === null ? "normal" : quota.remainingPercent <= 5 ? "danger" : quota.remainingPercent <= 20 ? "warning" : "normal",
149
- "aria-label": quota === null ? t("quickQuotaLoading") : `${t("quickQuotaLabel")}: ${formatPercent$1(quota.remainingPercent)}`,
150
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("quickQuotaLabel") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: quota === null ? loading ? t("quickQuotaLoading") : "—" : formatPercent$1(quota.remainingPercent) })]
149
+ "aria-label": quota === null ? t("quickQuotaLoading") : `${t("quickQuotaLabel")}: ${formatPercent$2(quota.remainingPercent)}`,
150
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("quickQuotaLabel") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: quota === null ? loading ? t("quickQuotaLoading") : "—" : formatPercent$2(quota.remainingPercent) })]
151
151
  });
152
152
  }
153
- function formatPercent$1(value) {
153
+ function formatPercent$2(value) {
154
154
  return `${new Intl.NumberFormat(void 0, { maximumFractionDigits: 0 }).format(value)}%`;
155
155
  }
156
156
  //#endregion
@@ -1147,6 +1147,26 @@ window.__ModuleLoader__.load({
1147
1147
  if (value >= 1e3) return Math.round(value / 1e3) + "k";
1148
1148
  return String(value);
1149
1149
  }
1150
+ /**
1151
+ * A share as a person reads it: at most `digits` decimals, no trailing zeros.
1152
+ *
1153
+ * The formatting has to happen on the RESULT rather than on its inputs. Most
1154
+ * percentages on these cards are a remainder — `100 - used` — and that
1155
+ * subtraction is exactly where binary floating point shows up: `100 - 8.04` is
1156
+ * `91.96000000000001`, `100 - 0.0007 * 100` is `99.93` but `100 - 0.0014 * 100`
1157
+ * is `99.86` only because it happens to land on a friendly neighbour. String
1158
+ * interpolation of the difference therefore prints a long tail for roughly a
1159
+ * third of all two-decimal inputs, which reads as "sometimes" — and no amount of
1160
+ * rounding the used share beforehand fixes a subtraction done afterwards.
1161
+ *
1162
+ * Anything non-finite renders as `0`, because every caller has already decided
1163
+ * the window is measured; this is the last line of defence, not a claim.
1164
+ */
1165
+ function formatPercent$1(value, digits = 2) {
1166
+ if (!Number.isFinite(value)) return "0";
1167
+ const factor = 10 ** Math.min(6, Math.max(0, Math.trunc(digits)));
1168
+ return String(Math.round(value * factor) / factor);
1169
+ }
1150
1170
  /** A stamp in the reader's own locale, or an em dash when there is none. */
1151
1171
  function formatDate$1(ms) {
1152
1172
  if (ms === void 0 || ms === null || !Number.isFinite(ms) || ms <= 0) return "—";
@@ -4631,6 +4651,195 @@ window.__ModuleLoader__.load({
4631
4651
  });
4632
4652
  }
4633
4653
  //#endregion
4654
+ //#region src/client/CompactionRouteCard.tsx
4655
+ /**
4656
+ * Choose the model an automatic compaction summarizes on.
4657
+ *
4658
+ * The route lives in this plugin's preferences and is applied by the host's
4659
+ * `llm/stream` listener, so a saved choice takes effect on the next compaction
4660
+ * without a restart. Both selects are needed together: a model id alone belongs
4661
+ * to no route.
4662
+ *
4663
+ * The picker loads its directory on demand rather than on mount: opening the
4664
+ * settings page must not cost a request to every line (that page is opened for
4665
+ * unrelated reasons all the time), and the card already knows the saved route
4666
+ * from the status it was rendered with.
4667
+ */
4668
+ function CompactionRouteCard({ api, preferences, disabled = false, labels, onSaved }) {
4669
+ const [picking, setPicking] = (0, react.useState)(false);
4670
+ const [candidates, setCandidates] = (0, react.useState)(null);
4671
+ const [loadError, setLoadError] = (0, react.useState)(null);
4672
+ const [provider, setProvider] = (0, react.useState)("");
4673
+ const [model, setModel] = (0, react.useState)("");
4674
+ const [busy, setBusy] = (0, react.useState)(false);
4675
+ const [notice, setNotice] = (0, react.useState)(null);
4676
+ const [error, setError] = (0, react.useState)(null);
4677
+ const load = (0, react.useCallback)(async () => {
4678
+ try {
4679
+ const value = await api.compactionCandidates();
4680
+ setCandidates(value);
4681
+ setLoadError(null);
4682
+ } catch (cause) {
4683
+ setLoadError(cause instanceof Error ? cause.message : String(cause));
4684
+ }
4685
+ }, [api]);
4686
+ (0, react.useEffect)(() => {
4687
+ if (picking && candidates === null) load();
4688
+ }, [
4689
+ picking,
4690
+ candidates,
4691
+ load
4692
+ ]);
4693
+ (0, react.useEffect)(() => {
4694
+ const saved = preferences?.compactionRoute ?? null;
4695
+ if (saved === null) return;
4696
+ setProvider((current) => current === "" ? saved.provider : current);
4697
+ setModel((current) => current === "" ? saved.model : current);
4698
+ }, [preferences?.compactionRoute]);
4699
+ const providers = candidates?.providers ?? [];
4700
+ const models = providers.find((entry) => entry.id === provider)?.models ?? [];
4701
+ const selected = models.find((entry) => entry.id === model);
4702
+ const savedProvider = preferences?.compactionRoute?.provider ?? "";
4703
+ const savedModel = preferences?.compactionRoute?.model ?? "";
4704
+ const canSave = provider !== "" && model !== "" && (provider !== savedProvider || model !== savedModel) && !busy && !disabled;
4705
+ const providerOptions = savedProvider === "" || providers.some((entry) => entry.id === savedProvider) ? providers : [{
4706
+ id: savedProvider,
4707
+ name: savedProvider
4708
+ }, ...providers];
4709
+ const modelOptions = model !== "" && !models.some((entry) => entry.id === model) ? [{
4710
+ id: model,
4711
+ name: model,
4712
+ modalities: []
4713
+ }, ...models] : models;
4714
+ const currentLabel = savedProvider === "" ? labels.follow : `${savedProvider} / ${savedModel}`;
4715
+ const save = async (target) => {
4716
+ setBusy(true);
4717
+ setError(null);
4718
+ setNotice(null);
4719
+ try {
4720
+ onSaved(await api.updatePreferences({ compactionRoute: target }));
4721
+ setNotice(labels.saved);
4722
+ setPicking(false);
4723
+ } catch (cause) {
4724
+ setError(cause instanceof Error ? cause.message : String(cause));
4725
+ } finally {
4726
+ setBusy(false);
4727
+ }
4728
+ };
4729
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
4730
+ className: "dsha-group",
4731
+ children: [
4732
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4733
+ className: "dsha-grouphead",
4734
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { children: labels.title })
4735
+ }),
4736
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4737
+ className: "dsha-muted",
4738
+ children: labels.hint
4739
+ }),
4740
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4741
+ className: "dsha-pref-row",
4742
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: currentLabel }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4743
+ className: "dsha-btn",
4744
+ type: "button",
4745
+ disabled: busy || disabled,
4746
+ onClick: () => setPicking((current) => !current),
4747
+ children: labels.change
4748
+ })]
4749
+ }),
4750
+ picking ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
4751
+ loadError !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4752
+ className: "dsh-codex-error",
4753
+ role: "alert",
4754
+ children: loadError
4755
+ }) : null,
4756
+ candidates !== null && providers.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4757
+ className: "dsh-codex-empty",
4758
+ children: labels.empty
4759
+ }) : null,
4760
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4761
+ className: "dsha-pref-row",
4762
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: labels.provider }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
4763
+ className: "dsha-select",
4764
+ "aria-label": labels.provider,
4765
+ value: provider,
4766
+ disabled: busy || disabled,
4767
+ onChange: (event) => {
4768
+ setProvider(event.currentTarget.value);
4769
+ setModel("");
4770
+ },
4771
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
4772
+ value: "",
4773
+ children: "—"
4774
+ }), providerOptions.map((entry) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
4775
+ value: entry.id,
4776
+ children: entry.name
4777
+ }, entry.id))]
4778
+ })]
4779
+ }),
4780
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4781
+ className: "dsha-pref-row",
4782
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: labels.model }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
4783
+ className: "dsha-select",
4784
+ "aria-label": labels.model,
4785
+ value: model,
4786
+ disabled: busy || disabled || provider === "",
4787
+ onChange: (event) => setModel(event.currentTarget.value),
4788
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
4789
+ value: "",
4790
+ children: "—"
4791
+ }), modelOptions.map((entry) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
4792
+ value: entry.id,
4793
+ children: entry.name
4794
+ }, entry.id))]
4795
+ })]
4796
+ }),
4797
+ selected !== void 0 && selected.modalities.length > 0 && !selected.modalities.includes("video") ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4798
+ className: "dsh-codex-warning",
4799
+ children: labels.noVideo
4800
+ }) : null,
4801
+ selected !== void 0 && selected.modalities.length > 0 && !selected.modalities.includes("image") ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4802
+ className: "dsh-codex-warning",
4803
+ children: labels.noImage
4804
+ }) : null,
4805
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4806
+ className: "dsha-actions",
4807
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4808
+ className: "dsha-btn dsha-btn-primary",
4809
+ type: "button",
4810
+ disabled: !canSave,
4811
+ onClick: () => void save({
4812
+ provider,
4813
+ model
4814
+ }),
4815
+ children: labels.save
4816
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4817
+ className: "dsha-btn",
4818
+ type: "button",
4819
+ disabled: busy || disabled || savedProvider === "",
4820
+ onClick: () => void save(null),
4821
+ children: labels.clear
4822
+ })]
4823
+ })
4824
+ ] }) : null,
4825
+ notice !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4826
+ className: "dsh-codex-success",
4827
+ role: "status",
4828
+ children: notice
4829
+ }) : null,
4830
+ error !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
4831
+ className: "dsh-codex-error",
4832
+ role: "alert",
4833
+ children: [
4834
+ labels.failed,
4835
+ ": ",
4836
+ error
4837
+ ]
4838
+ }) : null
4839
+ ]
4840
+ });
4841
+ }
4842
+ //#endregion
4634
4843
  //#region src/client/kimi-code/locales.ts
4635
4844
  const NS_KIMI_CODE = "dsh-kimi-code";
4636
4845
  const zh$10 = {
@@ -4731,11 +4940,7 @@ window.__ModuleLoader__.load({
4731
4940
  cacheOutput: "输出 tokens",
4732
4941
  cacheRequests: "请求数",
4733
4942
  cacheWrite: "缓存写入",
4734
- cacheWrite5m: "5 分钟档",
4735
- cacheWrite1h: "1 小时档",
4736
- cacheWriteHint: "写入是独立计费项:1h 写入单价约为 5m 的两倍,命中后会按原 TTL 续期。分档显示 0 表示服务端没有回报该档的写入数(响应头缺失就是静默),不等于该档确实没写入。",
4737
4943
  unknownMetric: "暂无",
4738
- cacheHint: "同一会话内:系统提示与工具列表一旦变化会使缓存整体失效,因此保持工具集合稳定、把新增内容追加在末尾,命中率最高。",
4739
4944
  cacheExpired: "本会话已空闲超过缓存有效期,下一轮将重新处理约 {tokens} 个 token。",
4740
4945
  cacheDrift: "最近前缀状态",
4741
4946
  cacheDriftFirst: "首个请求(缓存为空)",
@@ -4786,7 +4991,20 @@ window.__ModuleLoader__.load({
4786
4991
  loading: "加载中...",
4787
4992
  loginFailed: "登录失败",
4788
4993
  requests: "次请求",
4789
- tokens: "tokens"
4994
+ tokens: "tokens",
4995
+ compactionRoute: "压缩模型",
4996
+ compactionRouteHint: "自动压缩时由哪个模型来写摘要(仅对本线路的会话生效)。留空则继续用会话自己的模型,也就继续消耗这条线路的额度。",
4997
+ compactionRouteProvider: "线路",
4998
+ compactionRouteModel: "模型",
4999
+ compactionRouteSave: "保存",
5000
+ compactionRouteClear: "恢复会话模型",
5001
+ compactionRouteChange: "设置",
5002
+ compactionRouteFollow: "跟随会话模型(默认)",
5003
+ compactionRouteSaved: "已保存,下次压缩生效。",
5004
+ compactionRouteFailed: "保存失败",
5005
+ compactionRouteNoVideo: "该模型不接受视频:含视频的会话会回落到会话模型完成压缩。",
5006
+ compactionRouteNoImage: "该模型不接受图片:摘要里的截图会被替换为占位文本。",
5007
+ compactionRouteEmpty: "没有可用的线路——先登录任意一条线路再回来设置。"
4790
5008
  };
4791
5009
  const en$10 = {
4792
5010
  ...accountPoolEn,
@@ -4887,11 +5105,7 @@ window.__ModuleLoader__.load({
4887
5105
  cacheOutput: "Output tokens",
4888
5106
  cacheRequests: "Requests",
4889
5107
  cacheWrite: "Cache writes",
4890
- cacheWrite5m: "5-minute tier",
4891
- cacheWrite1h: "1-hour tier",
4892
- cacheWriteHint: "A write is billed separately: a 1h write costs about twice a 5m one, and a hit renews the entry under its original TTL. A tier showing 0 means the service did not report a write for it (a missing response header is silence), not that nothing was written there.",
4893
5108
  unknownMetric: "n/a",
4894
- cacheHint: "Within one session: any change to the system prompt or the tool list invalidates the whole prefix cache, so keep the tool set stable and append new material at the tail for the best hit rate.",
4895
5109
  cacheDrift: "Last prefix state",
4896
5110
  cacheDriftFirst: "First request (cache cold)",
4897
5111
  cacheDriftStable: "Stable (prefix unchanged)",
@@ -4941,7 +5155,20 @@ window.__ModuleLoader__.load({
4941
5155
  loading: "Loading...",
4942
5156
  loginFailed: "Sign-in failed",
4943
5157
  requests: "requests",
4944
- tokens: "tokens"
5158
+ tokens: "tokens",
5159
+ compactionRoute: "Compaction model",
5160
+ compactionRouteHint: "Which model writes the summary when the context is compacted — it applies to sessions on this line only. Leave it unset to keep the conversation's own model, and this line's quota.",
5161
+ compactionRouteProvider: "Line",
5162
+ compactionRouteModel: "Model",
5163
+ compactionRouteSave: "Save",
5164
+ compactionRouteClear: "Use the conversation model",
5165
+ compactionRouteChange: "Change",
5166
+ compactionRouteFollow: "Follows the conversation model (default)",
5167
+ compactionRouteSaved: "Saved; it applies to the next compaction.",
5168
+ compactionRouteFailed: "Save failed",
5169
+ compactionRouteNoVideo: "This model takes no video: a session that carries video falls back to the conversation model.",
5170
+ compactionRouteNoImage: "This model takes no images: screenshots become placeholder text in the summary.",
5171
+ compactionRouteEmpty: "No route is available — sign in to one first."
4945
5172
  };
4946
5173
  const dictionaries$10 = {
4947
5174
  zh: zh$10,
@@ -5213,10 +5440,12 @@ window.__ModuleLoader__.load({
5213
5440
  const SHORT_PER_WEEK = 5;
5214
5441
  /** 5 小时窗口在月额度里的份数。 */
5215
5442
  const SHORT_PER_MONTH = 25;
5216
- /** 百分比,最多两位小数;非有限数不画。 */
5443
+ /** 提示条箭头离气泡两端的最小距离,免得圆角把三角切掉。 */
5444
+ const ARROW_INSET = 12;
5445
+ /** 百分比,最多两位小数;非有限数不画。舍入规则放在共享的 `formatPercent`。 */
5217
5446
  function percentText$2(value) {
5218
5447
  if (!Number.isFinite(value)) return "0%";
5219
- return `${Math.round(value * 100) / 100}%`;
5448
+ return `${formatPercent$1(value)}%`;
5220
5449
  }
5221
5450
  /** `YYYY-MM-DD HH:mm`,本地时区;令牌到期用它,年份不能省。 */
5222
5451
  function dateText(ms) {
@@ -5306,6 +5535,43 @@ window.__ModuleLoader__.load({
5306
5535
  const timer = window.setInterval(() => setClock(Date.now()), 3e4);
5307
5536
  return () => window.clearInterval(timer);
5308
5537
  }, [shortReset]);
5538
+ const tipPercent = Math.max(0, Math.min(100, totalPercent));
5539
+ const barRef = (0, react.useRef)(null);
5540
+ const tipRef = (0, react.useRef)(null);
5541
+ const [tipPlacement, setTipPlacement] = (0, react.useState)({
5542
+ shift: 0,
5543
+ arrowPx: null
5544
+ });
5545
+ (0, react.useEffect)(() => {
5546
+ const wrap = barRef.current;
5547
+ const tip = tipRef.current;
5548
+ if (wrap === null || tip === null) return void 0;
5549
+ const place = () => {
5550
+ const width = wrap.clientWidth;
5551
+ const bubble = tip.offsetWidth;
5552
+ if (width <= 0 || bubble <= 0) {
5553
+ setTipPlacement({
5554
+ shift: 0,
5555
+ arrowPx: null
5556
+ });
5557
+ return;
5558
+ }
5559
+ const half = bubble / 2;
5560
+ const anchor = width * tipPercent / 100;
5561
+ const clamped = Math.min(Math.max(anchor, half), Math.max(half, width - half));
5562
+ const arrow = Math.min(Math.max(anchor - (clamped - half), ARROW_INSET), bubble - ARROW_INSET);
5563
+ setTipPlacement({
5564
+ shift: Math.round((clamped - anchor) * 10) / 10,
5565
+ arrowPx: Math.round(arrow * 10) / 10
5566
+ });
5567
+ };
5568
+ place();
5569
+ if (typeof ResizeObserver === "undefined") return void 0;
5570
+ const observer = new ResizeObserver(place);
5571
+ observer.observe(wrap);
5572
+ observer.observe(tip);
5573
+ return () => observer.disconnect();
5574
+ }, [tipPercent]);
5309
5575
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5310
5576
  className: "dsha-kq",
5311
5577
  children: [
@@ -5357,6 +5623,8 @@ window.__ModuleLoader__.load({
5357
5623
  }),
5358
5624
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5359
5625
  className: "dsha-kq-barwrap",
5626
+ ref: barRef,
5627
+ style: { "--kq-tip-x": `${tipPercent}%` },
5360
5628
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5361
5629
  className: "dsha-kq-meter dsha-kq-meter--total",
5362
5630
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
@@ -5371,6 +5639,11 @@ window.__ModuleLoader__.load({
5371
5639
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5372
5640
  className: "dsha-kq-tip",
5373
5641
  role: "tooltip",
5642
+ ref: tipRef,
5643
+ style: {
5644
+ "--kq-tip-shift": `${tipPlacement.shift}px`,
5645
+ ...tipPlacement.arrowPx === null ? {} : { "--kq-tip-arrow": `${tipPlacement.arrowPx}px` }
5646
+ },
5374
5647
  children: t.kqTip.replace("{pct}", percentText$2(totalPercent))
5375
5648
  })]
5376
5649
  }),
@@ -5416,19 +5689,6 @@ window.__ModuleLoader__.load({
5416
5689
  //#endregion
5417
5690
  //#region src/client/kimi-code/KimiCodeSection.tsx
5418
5691
  const api$12 = createLineApi("/kimi-code/api", "Kimi Code");
5419
- /** Money the service reports in minor units, rendered in its own currency. */
5420
- function formatMoney(cents, currency) {
5421
- if (cents === null) return "—";
5422
- const code = currency ?? "USD";
5423
- try {
5424
- return new Intl.NumberFormat(void 0, {
5425
- style: "currency",
5426
- currency: code
5427
- }).format(cents / 100);
5428
- } catch {
5429
- return `${(cents / 100).toFixed(2)} ${code}`;
5430
- }
5431
- }
5432
5692
  /**
5433
5693
  * One token count as the card prints it.
5434
5694
  *
@@ -5461,6 +5721,8 @@ window.__ModuleLoader__.load({
5461
5721
  const [savingModel, setSavingModel] = (0, react.useState)(null);
5462
5722
  const [copied, setCopied] = (0, react.useState)(false);
5463
5723
  const [connectionNotice, setConnectionNotice] = (0, react.useState)(null);
5724
+ const [subscriptionApi] = (0, react.useState)(() => new SubscriptionApi());
5725
+ const [compactionPreferences, setCompactionPreferences] = (0, react.useState)(null);
5464
5726
  /** Follow-up poll owed while the host refreshes the quota behind an answer. */
5465
5727
  const quotaFollowUp = (0, react.useRef)(null);
5466
5728
  const t = useLineDict(NS_KIMI_CODE, zh$10);
@@ -5484,6 +5746,16 @@ window.__ModuleLoader__.load({
5484
5746
  setLoading(false);
5485
5747
  }
5486
5748
  }, []);
5749
+ (0, react.useEffect)(() => {
5750
+ let live = true;
5751
+ subscriptionApi.status().then((answer) => {
5752
+ const preferences = answer?.preferences;
5753
+ if (live && preferences !== void 0 && preferences !== null) setCompactionPreferences(preferences);
5754
+ }).catch(() => void 0);
5755
+ return () => {
5756
+ live = false;
5757
+ };
5758
+ }, [subscriptionApi]);
5487
5759
  (0, react.useEffect)(() => {
5488
5760
  loadStatus();
5489
5761
  const refreshWhenVisible = () => {
@@ -5782,7 +6054,7 @@ window.__ModuleLoader__.load({
5782
6054
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5783
6055
  className: "dsha-page",
5784
6056
  children: [
5785
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AccountPoolSection, {
6057
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(AccountPoolSection, {
5786
6058
  accounts: status?.accounts ?? [],
5787
6059
  activeAccountId: status?.activeAccountId,
5788
6060
  rotationStrategy: status?.rotationStrategy ?? "sequential",
@@ -5806,59 +6078,74 @@ window.__ModuleLoader__.load({
5806
6078
  now,
5807
6079
  labels: t
5808
6080
  }),
5809
- children: loginPending && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5810
- className: "dsha-device-box",
5811
- children: [
5812
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: t.deviceCodeSection }),
5813
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
5814
- className: "dsha-muted",
5815
- children: t.deviceCodeHint
5816
- }),
5817
- flow?.userCode && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5818
- className: "dsha-code-row",
5819
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5820
- className: "dsha-code",
5821
- children: flow.userCode
5822
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
5823
- className: "dsha-btn",
5824
- onClick: () => void handleCopyCode(),
5825
- children: copied ? t.copied : t.copyCode
5826
- })]
5827
- }),
5828
- flow?.verificationUriComplete && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
5829
- className: "dsha-link",
5830
- href: flow.verificationUriComplete,
5831
- target: "_blank",
5832
- rel: "noreferrer noopener",
5833
- children: flow.verificationUriComplete
5834
- }),
5835
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5836
- className: "dsha-row",
5837
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5838
- className: "dsha-label",
5839
- children: t.codeExpires
5840
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5841
- className: "dsha-value",
5842
- children: formatDate$1(flow?.expiresAt)
5843
- })]
5844
- }),
5845
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5846
- className: "dsha-actions",
5847
- children: [flow?.verificationUriComplete && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
5848
- className: "dsha-btn",
6081
+ children: [
6082
+ loginPending && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6083
+ className: "dsha-device-box",
6084
+ children: [
6085
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: t.deviceCodeSection }),
6086
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
6087
+ className: "dsha-muted",
6088
+ children: t.deviceCodeHint
6089
+ }),
6090
+ flow?.userCode && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6091
+ className: "dsha-code-row",
6092
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6093
+ className: "dsha-code",
6094
+ children: flow.userCode
6095
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
6096
+ className: "dsha-btn",
6097
+ onClick: () => void handleCopyCode(),
6098
+ children: copied ? t.copied : t.copyCode
6099
+ })]
6100
+ }),
6101
+ flow?.verificationUriComplete && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
6102
+ className: "dsha-link",
5849
6103
  href: flow.verificationUriComplete,
5850
6104
  target: "_blank",
5851
6105
  rel: "noreferrer noopener",
5852
- children: t.openSignInPage
5853
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
5854
- className: "dsha-btn",
5855
- disabled: busy !== null,
5856
- onClick: () => void handleCancelLogin(),
5857
- children: t.cancelSignIn
5858
- })]
6106
+ children: flow.verificationUriComplete
6107
+ }),
6108
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6109
+ className: "dsha-row",
6110
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6111
+ className: "dsha-label",
6112
+ children: t.codeExpires
6113
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6114
+ className: "dsha-value",
6115
+ children: formatDate$1(flow?.expiresAt)
6116
+ })]
6117
+ }),
6118
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6119
+ className: "dsha-actions",
6120
+ children: [flow?.verificationUriComplete && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
6121
+ className: "dsha-btn",
6122
+ href: flow.verificationUriComplete,
6123
+ target: "_blank",
6124
+ rel: "noreferrer noopener",
6125
+ children: t.openSignInPage
6126
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
6127
+ className: "dsha-btn",
6128
+ disabled: busy !== null,
6129
+ onClick: () => void handleCancelLogin(),
6130
+ children: t.cancelSignIn
6131
+ })]
6132
+ })
6133
+ ]
6134
+ }),
6135
+ status?.quotaError != null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
6136
+ className: "dsha-notice",
6137
+ children: status.quotaError
6138
+ }),
6139
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6140
+ className: "dsha-actions",
6141
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
6142
+ className: "dsha-btn",
6143
+ disabled: busy !== null || !status?.authenticated,
6144
+ onClick: () => void handleRefreshQuota(),
6145
+ children: busy === "quota" ? t.refreshingQuota : t.refreshQuota
5859
6146
  })
5860
- ]
5861
- })
6147
+ })
6148
+ ]
5862
6149
  }),
5863
6150
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
5864
6151
  className: "dsha-group",
@@ -5980,33 +6267,35 @@ window.__ModuleLoader__.load({
5980
6267
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5981
6268
  className: "dsha-label",
5982
6269
  children: t.cacheWrite
5983
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
6270
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5984
6271
  className: "dsha-value",
5985
- children: [
5986
- tokenCount(status.cache.cacheWriteTokens, t),
5987
- " · ",
5988
- t.cacheWrite5m,
5989
- " ",
5990
- tokenCount(status.cache.cacheWrite5mTokens, t),
5991
- " · ",
5992
- t.cacheWrite1h,
5993
- " ",
5994
- tokenCount(status.cache.cacheWrite1hTokens, t)
5995
- ]
6272
+ children: tokenCount(status.cache.cacheWriteTokens, t)
5996
6273
  })]
5997
- }),
5998
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
5999
- className: "dsha-muted",
6000
- children: t.cacheWriteHint
6001
- }),
6002
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
6003
- className: "dsha-muted",
6004
- children: t.cacheHint
6005
6274
  })
6006
6275
  ]
6007
6276
  })
6008
6277
  ]
6009
6278
  }),
6279
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CompactionRouteCard, {
6280
+ api: subscriptionApi,
6281
+ preferences: compactionPreferences,
6282
+ labels: {
6283
+ title: t.compactionRoute,
6284
+ hint: t.compactionRouteHint,
6285
+ provider: t.compactionRouteProvider,
6286
+ model: t.compactionRouteModel,
6287
+ save: t.compactionRouteSave,
6288
+ clear: t.compactionRouteClear,
6289
+ change: t.compactionRouteChange,
6290
+ follow: t.compactionRouteFollow,
6291
+ saved: t.compactionRouteSaved,
6292
+ failed: t.compactionRouteFailed,
6293
+ noVideo: t.compactionRouteNoVideo,
6294
+ noImage: t.compactionRouteNoImage,
6295
+ empty: t.compactionRouteEmpty
6296
+ },
6297
+ onSaved: setCompactionPreferences
6298
+ }),
6010
6299
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
6011
6300
  className: "dsha-group",
6012
6301
  children: [
@@ -6144,69 +6433,6 @@ window.__ModuleLoader__.load({
6144
6433
  })
6145
6434
  ]
6146
6435
  }),
6147
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
6148
- className: "dsha-group",
6149
- children: [
6150
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6151
- className: "dsha-grouphead",
6152
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { children: t.quotaSection }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
6153
- className: "dsha-btn",
6154
- disabled: busy !== null || !status?.authenticated,
6155
- onClick: () => void handleRefreshQuota(),
6156
- children: busy === "quota" ? t.refreshingQuota : t.refreshQuota
6157
- })]
6158
- }),
6159
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
6160
- className: "dsha-muted",
6161
- children: t.quotaDesc
6162
- }),
6163
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
6164
- className: "dsha-muted",
6165
- children: t.quotaFactsScope
6166
- }),
6167
- status?.quotaError && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
6168
- className: "dsha-notice",
6169
- children: status.quotaError
6170
- }),
6171
- !status?.authenticated ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6172
- className: "dsha-empty",
6173
- children: t.signedOut
6174
- }) : quota === null || quota === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6175
- className: "dsha-empty",
6176
- children: busy === "quota" ? t.refreshingQuota : t.quotaEmpty
6177
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6178
- className: "dsha-quota-card",
6179
- children: [
6180
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6181
- className: "dsha-quota-title",
6182
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: t.quotaPlan }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: quota.planName || t.planUnknown })]
6183
- }),
6184
- quota.extraUsage !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6185
- className: "dsha-meter-wrap",
6186
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6187
- className: "dsha-meter-label",
6188
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t.quotaWallet }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: formatMoney(quota.extraUsage.balanceCents, quota.extraUsage.currency) })]
6189
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6190
- className: "dsha-meter-meta",
6191
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: formatMoney(quota.extraUsage.totalCents, quota.extraUsage.currency) }), quota.extraUsage.monthlyChargeLimitEnabled && quota.extraUsage.monthlyChargeLimitCents !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [
6192
- t.quotaWalletLimit,
6193
- ": ",
6194
- formatMoney(quota.extraUsage.monthlyChargeLimitCents, quota.extraUsage.currency)
6195
- ] })]
6196
- })]
6197
- }),
6198
- quota.windows.length === 0 && quota.extraUsage === null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6199
- className: "dsha-empty",
6200
- children: t.quotaEmpty
6201
- }),
6202
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6203
- className: "dsha-timestamp",
6204
- children: t.updatedAt.replace("{time}", new Date(quota.fetchedAt).toLocaleString())
6205
- })
6206
- ]
6207
- })
6208
- ]
6209
- }),
6210
6436
  error && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6211
6437
  className: "dsha-error",
6212
6438
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { children: error })
@@ -17154,6 +17380,8 @@ window.__ModuleLoader__.load({
17154
17380
  catalogSyncedNote: "实时目录已同步 {count} 个模型。",
17155
17381
  noModels: "暂无可用模型。",
17156
17382
  pricingNote: "价格为官方公开价目(美元 / 每百万 token,输入 / 输出)。",
17383
+ unservedBadge: "套餐不支持",
17384
+ unservedHint: "当前套餐不提供的模型默认隐藏,勾选后会强制显示。",
17157
17385
  contextSection: "上下文窗口",
17158
17386
  contextHint: "官方未公布各模型的上下文窗口:默认按 128k 处理,可在这里按模型修正。留空或保存无效值会提示错误;恢复默认会清掉覆盖值。",
17159
17387
  tokens: "tokens",
@@ -17260,6 +17488,8 @@ window.__ModuleLoader__.load({
17260
17488
  catalogSyncedNote: "Live catalog synced: {count} model(s).",
17261
17489
  noModels: "No models available.",
17262
17490
  pricingNote: "Prices are the official public list (USD per 1M tokens, input / output).",
17491
+ unservedBadge: "Not in plan",
17492
+ unservedHint: "Models the current plan does not serve stay hidden by default; checking one here forces it to show.",
17263
17493
  contextSection: "Context windows",
17264
17494
  contextHint: "The vendor publishes no per-model context windows, so models default to 128k; correct any model here. An invalid value is reported rather than saved; restoring clears the override.",
17265
17495
  tokens: "tokens",
@@ -17362,6 +17592,19 @@ window.__ModuleLoader__.load({
17362
17592
  return Math.min(100, Math.max(0, value));
17363
17593
  }
17364
17594
  /**
17595
+ * Whether the plan the gateway bills for withholds this model.
17596
+ *
17597
+ * The host adds `served: false` to a catalog entry the current plan does not
17598
+ * provide. Such a row stays listed — unchecking it is what hides it, and
17599
+ * checking it is how a user forces it visible — so the card labels it rather
17600
+ * than dropping it. The field is optional: `true` and an absent field both mean
17601
+ * served, so the badge and the note never appear for them and every row renders
17602
+ * exactly as it did before this marker existed.
17603
+ */
17604
+ function unserved(model) {
17605
+ return model.served === false;
17606
+ }
17607
+ /**
17365
17608
  * A window's length in the words the service sold it in.
17366
17609
  *
17367
17610
  * A policy states seconds, and the tier's windows are round days and hours:
@@ -18104,6 +18347,11 @@ window.__ModuleLoader__.load({
18104
18347
  style: { fontSize: 12 },
18105
18348
  children: t.modelsHint
18106
18349
  }),
18350
+ models.some(unserved) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
18351
+ className: "dsha-muted",
18352
+ style: { fontSize: 12 },
18353
+ children: t.unservedHint
18354
+ }),
18107
18355
  status !== null && models.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
18108
18356
  className: "dsha-empty",
18109
18357
  children: t.noModels
@@ -18115,12 +18363,15 @@ window.__ModuleLoader__.load({
18115
18363
  children: t.catalogNeverSynced
18116
18364
  }),
18117
18365
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelChecklist, {
18118
- items: models.map((model) => ({
18119
- id: model.id,
18120
- name: model.name,
18121
- hint: model.hint ?? `${formatCapacity$1(model.contextWindow)} · ${model.input.join("+")}${model.reasoning ? " · reasoning" : ""}`,
18122
- enabled: model.enabled
18123
- })),
18366
+ items: models.map((model) => {
18367
+ const hint = model.hint ?? `${formatCapacity$1(model.contextWindow)} · ${model.input.join("+")}${model.reasoning ? " · reasoning" : ""}`;
18368
+ return {
18369
+ id: model.id,
18370
+ name: model.name,
18371
+ hint: unserved(model) ? `${hint} · ${t.unservedBadge}` : hint,
18372
+ enabled: model.enabled
18373
+ };
18374
+ }),
18124
18375
  busy: busyAny,
18125
18376
  onToggle: (id, enabled) => toggleModel(id, enabled),
18126
18377
  onToggleAll: (enabled) => setAllModels(enabled),
@@ -18900,259 +19151,6 @@ window.__ModuleLoader__.load({
18900
19151
  });
18901
19152
  }
18902
19153
  //#endregion
18903
- //#region src/client/CompactionRouteCard.tsx
18904
- /**
18905
- * Choose the model an automatic compaction summarizes on.
18906
- *
18907
- * The route lives in this plugin's preferences and is applied by the host's
18908
- * `llm/stream` listener, so a saved choice takes effect on the next compaction
18909
- * without a restart. Both selects are needed together: a model id alone belongs
18910
- * to no route.
18911
- *
18912
- * The picker loads its directory on demand rather than on mount: opening the
18913
- * settings page must not cost a request to every line (that page is opened for
18914
- * unrelated reasons all the time), and the card already knows the saved route
18915
- * from the status it was rendered with.
18916
- */
18917
- function CompactionRouteCard({ api, preferences, disabled = false, labels, onSaved }) {
18918
- const [picking, setPicking] = (0, react.useState)(false);
18919
- const [candidates, setCandidates] = (0, react.useState)(null);
18920
- const [loadError, setLoadError] = (0, react.useState)(null);
18921
- const [provider, setProvider] = (0, react.useState)("");
18922
- const [model, setModel] = (0, react.useState)("");
18923
- const [busy, setBusy] = (0, react.useState)(false);
18924
- const [notice, setNotice] = (0, react.useState)(null);
18925
- const [error, setError] = (0, react.useState)(null);
18926
- const load = (0, react.useCallback)(async () => {
18927
- try {
18928
- const value = await api.compactionCandidates();
18929
- setCandidates(value);
18930
- setLoadError(null);
18931
- } catch (cause) {
18932
- setLoadError(cause instanceof Error ? cause.message : String(cause));
18933
- }
18934
- }, [api]);
18935
- (0, react.useEffect)(() => {
18936
- if (picking && candidates === null) load();
18937
- }, [
18938
- picking,
18939
- candidates,
18940
- load
18941
- ]);
18942
- (0, react.useEffect)(() => {
18943
- const saved = preferences?.compactionRoute ?? null;
18944
- if (saved === null) return;
18945
- setProvider((current) => current === "" ? saved.provider : current);
18946
- setModel((current) => current === "" ? saved.model : current);
18947
- }, [preferences?.compactionRoute]);
18948
- const providers = candidates?.providers ?? [];
18949
- const models = providers.find((entry) => entry.id === provider)?.models ?? [];
18950
- const selected = models.find((entry) => entry.id === model);
18951
- const savedProvider = preferences?.compactionRoute?.provider ?? "";
18952
- const savedModel = preferences?.compactionRoute?.model ?? "";
18953
- const canSave = provider !== "" && model !== "" && (provider !== savedProvider || model !== savedModel) && !busy && !disabled;
18954
- const providerOptions = savedProvider === "" || providers.some((entry) => entry.id === savedProvider) ? providers : [{
18955
- id: savedProvider,
18956
- name: savedProvider
18957
- }, ...providers];
18958
- const modelOptions = model !== "" && !models.some((entry) => entry.id === model) ? [{
18959
- id: model,
18960
- name: model,
18961
- modalities: []
18962
- }, ...models] : models;
18963
- const currentLabel = savedProvider === "" ? labels.follow : `${savedProvider} / ${savedModel}`;
18964
- const save = async (target) => {
18965
- setBusy(true);
18966
- setError(null);
18967
- setNotice(null);
18968
- try {
18969
- onSaved(await api.updatePreferences({ compactionRoute: target }));
18970
- setNotice(labels.saved);
18971
- setPicking(false);
18972
- } catch (cause) {
18973
- setError(cause instanceof Error ? cause.message : String(cause));
18974
- } finally {
18975
- setBusy(false);
18976
- }
18977
- };
18978
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
18979
- className: "dsha-group",
18980
- children: [
18981
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
18982
- className: "dsha-grouphead",
18983
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { children: labels.title })
18984
- }),
18985
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
18986
- className: "dsha-muted",
18987
- children: labels.hint
18988
- }),
18989
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
18990
- className: "dsha-pref-row",
18991
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: currentLabel }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
18992
- className: "dsha-btn",
18993
- type: "button",
18994
- disabled: busy || disabled,
18995
- onClick: () => setPicking((current) => !current),
18996
- children: labels.change
18997
- })]
18998
- }),
18999
- picking ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
19000
- loadError !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
19001
- className: "dsh-codex-error",
19002
- role: "alert",
19003
- children: loadError
19004
- }) : null,
19005
- candidates !== null && providers.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
19006
- className: "dsh-codex-empty",
19007
- children: labels.empty
19008
- }) : null,
19009
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
19010
- className: "dsha-pref-row",
19011
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: labels.provider }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
19012
- className: "dsha-select",
19013
- "aria-label": labels.provider,
19014
- value: provider,
19015
- disabled: busy || disabled,
19016
- onChange: (event) => {
19017
- setProvider(event.currentTarget.value);
19018
- setModel("");
19019
- },
19020
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
19021
- value: "",
19022
- children: "—"
19023
- }), providerOptions.map((entry) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
19024
- value: entry.id,
19025
- children: entry.name
19026
- }, entry.id))]
19027
- })]
19028
- }),
19029
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
19030
- className: "dsha-pref-row",
19031
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: labels.model }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
19032
- className: "dsha-select",
19033
- "aria-label": labels.model,
19034
- value: model,
19035
- disabled: busy || disabled || provider === "",
19036
- onChange: (event) => setModel(event.currentTarget.value),
19037
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
19038
- value: "",
19039
- children: "—"
19040
- }), modelOptions.map((entry) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
19041
- value: entry.id,
19042
- children: entry.name
19043
- }, entry.id))]
19044
- })]
19045
- }),
19046
- selected !== void 0 && selected.modalities.length > 0 && !selected.modalities.includes("video") ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
19047
- className: "dsh-codex-warning",
19048
- children: labels.noVideo
19049
- }) : null,
19050
- selected !== void 0 && selected.modalities.length > 0 && !selected.modalities.includes("image") ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
19051
- className: "dsh-codex-warning",
19052
- children: labels.noImage
19053
- }) : null,
19054
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
19055
- className: "dsha-actions",
19056
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
19057
- className: "dsha-btn dsha-btn-primary",
19058
- type: "button",
19059
- disabled: !canSave,
19060
- onClick: () => void save({
19061
- provider,
19062
- model
19063
- }),
19064
- children: labels.save
19065
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
19066
- className: "dsha-btn",
19067
- type: "button",
19068
- disabled: busy || disabled || savedProvider === "",
19069
- onClick: () => void save(null),
19070
- children: labels.clear
19071
- })]
19072
- })
19073
- ] }) : null,
19074
- notice !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
19075
- className: "dsh-codex-success",
19076
- role: "status",
19077
- children: notice
19078
- }) : null,
19079
- error !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
19080
- className: "dsh-codex-error",
19081
- role: "alert",
19082
- children: [
19083
- labels.failed,
19084
- ": ",
19085
- error
19086
- ]
19087
- }) : null
19088
- ]
19089
- });
19090
- }
19091
- //#endregion
19092
- //#region src/client/CompactionRouteBundleConfig.tsx
19093
- /**
19094
- * Compaction-model settings on this plugin's own page in the DSH plugin
19095
- * manager.
19096
- *
19097
- * The setting is cross-line, so it does not belong inside any one provider's
19098
- * tab: the plugin's page is where an operator configures *this plugin*, and the
19099
- * bundle-config slot places it between the package description and the rows the
19100
- * bundle declares.
19101
- *
19102
- * The slot is keyed by the bundle's package name, so the page dispatches it for
19103
- * this package only — no subject filter is needed. It also renders its
19104
- * occupants in the `summary` view (a one-liner beside the card); bundle
19105
- * configuration has nothing to say there and renders null.
19106
- *
19107
- * @module dsh-chatgpt-subscription/compaction-route-bundle-config
19108
- */
19109
- /** The package whose page this entry configures. */
19110
- const COMPACTION_ROUTE_PACKAGE = "@eddyskywalker/dsh-chatgpt-subscription";
19111
- /**
19112
- * Render the compaction-model card on this package's bundle page only.
19113
- *
19114
- * Split in two so the hooks below never mount for the summary view: that view
19115
- * is a one-liner drawn beside the card, and fetching this plugin's preferences
19116
- * for it would be a request nobody asked for.
19117
- */
19118
- function CompactionRouteBundleConfig(props) {
19119
- if (props.view !== "page") return null;
19120
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(OwnedSection, { t: props.t });
19121
- }
19122
- function OwnedSection({ t }) {
19123
- const [preferences, setPreferences] = (0, react.useState)(null);
19124
- const [api] = (0, react.useState)(() => new SubscriptionApi());
19125
- (0, react.useEffect)(() => {
19126
- let live = true;
19127
- api.status().then((status) => {
19128
- if (live) setPreferences(status.preferences);
19129
- }).catch(() => void 0);
19130
- return () => {
19131
- live = false;
19132
- };
19133
- }, [api]);
19134
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CompactionRouteCard, {
19135
- api,
19136
- preferences,
19137
- labels: {
19138
- title: t("compactionRoute"),
19139
- hint: t("compactionRouteHint"),
19140
- provider: t("compactionRouteProvider"),
19141
- model: t("compactionRouteModel"),
19142
- save: t("compactionRouteSave"),
19143
- clear: t("compactionRouteClear"),
19144
- change: t("compactionRouteChange"),
19145
- follow: t("compactionRouteFollow"),
19146
- saved: t("compactionRouteSaved"),
19147
- failed: t("compactionRouteFailed"),
19148
- noVideo: t("compactionRouteNoVideo"),
19149
- noImage: t("compactionRouteNoImage"),
19150
- empty: t("compactionRouteEmpty")
19151
- },
19152
- onSaved: setPreferences
19153
- });
19154
- }
19155
- //#endregion
19156
19154
  //#region src/client/locales.ts
19157
19155
  const NS = "dsh-chatgpt-subscription";
19158
19156
  const dictionaries$1 = {
@@ -19190,19 +19188,6 @@ window.__ModuleLoader__.load({
19190
19188
  proxyModeAuto: "系统代理(自动检测)",
19191
19189
  proxyModeCustom: "自定义代理",
19192
19190
  proxyModeDirect: "直连(禁用代理)",
19193
- compactionRoute: "压缩模型",
19194
- compactionRouteHint: "自动压缩时由哪个模型来写摘要。留空则继续用会话自己的模型,也就继续消耗那条线路的额度。",
19195
- compactionRouteProvider: "线路",
19196
- compactionRouteModel: "模型",
19197
- compactionRouteSave: "保存",
19198
- compactionRouteClear: "恢复会话模型",
19199
- compactionRouteChange: "设置",
19200
- compactionRouteFollow: "跟随会话模型(默认)",
19201
- compactionRouteSaved: "已保存,下次压缩生效。",
19202
- compactionRouteFailed: "保存失败",
19203
- compactionRouteNoVideo: "该模型不接受视频:含视频的会话会回落到会话模型完成压缩。",
19204
- compactionRouteNoImage: "该模型不接受图片:摘要里的截图会被替换为占位文本。",
19205
- compactionRouteEmpty: "没有可用的线路——先登录任意一条线路再回来设置。",
19206
19191
  customProxyUrl: "自定义代理地址",
19207
19192
  customProxyUrlHint: "支持 HTTP/HTTPS/SOCKS5 代理地址,例如 http://127.0.0.1:7890",
19208
19193
  customProxyUrlPlaceholder: "例如: http://127.0.0.1:7890",
@@ -19400,19 +19385,6 @@ window.__ModuleLoader__.load({
19400
19385
  proxyModeAuto: "System Proxy (Auto-detect)",
19401
19386
  proxyModeCustom: "Custom Proxy",
19402
19387
  proxyModeDirect: "Direct (No Proxy)",
19403
- compactionRoute: "Compaction model",
19404
- compactionRouteHint: "Which model writes the summary when the context is compacted. Leave it unset to keep the conversation's own model — and that line's quota.",
19405
- compactionRouteProvider: "Line",
19406
- compactionRouteModel: "Model",
19407
- compactionRouteSave: "Save",
19408
- compactionRouteClear: "Use the conversation model",
19409
- compactionRouteChange: "Change",
19410
- compactionRouteFollow: "Follows the conversation model (default)",
19411
- compactionRouteSaved: "Saved; it applies to the next compaction.",
19412
- compactionRouteFailed: "Save failed",
19413
- compactionRouteNoVideo: "This model takes no video: a session that carries video falls back to the conversation model.",
19414
- compactionRouteNoImage: "This model takes no images: screenshots become placeholder text in the summary.",
19415
- compactionRouteEmpty: "No route is available — sign in to one first.",
19416
19388
  customProxyUrl: "Custom Proxy URL",
19417
19389
  customProxyUrlHint: "Supports HTTP/HTTPS/SOCKS5 proxy URLs, e.g. http://127.0.0.1:7890",
19418
19390
  customProxyUrlPlaceholder: "e.g. http://127.0.0.1:7890",
@@ -20290,19 +20262,6 @@ window.__ModuleLoader__.load({
20290
20262
  proxyModeAuto: "Системный прокси (автоопределение)",
20291
20263
  proxyModeCustom: "Свой прокси",
20292
20264
  proxyModeDirect: "Напрямую (без прокси)",
20293
- compactionRoute: "Модель сжатия",
20294
- compactionRouteHint: "Какая модель пишет сводку при сжатии контекста. Если не выбрана, используется модель диалога — и её лимит.",
20295
- compactionRouteProvider: "Линия",
20296
- compactionRouteModel: "Модель",
20297
- compactionRouteSave: "Сохранить",
20298
- compactionRouteClear: "Модель диалога",
20299
- compactionRouteChange: "Изменить",
20300
- compactionRouteFollow: "Как в диалоге (по умолчанию)",
20301
- compactionRouteSaved: "Сохранено; применится при следующем сжатии.",
20302
- compactionRouteFailed: "Не удалось сохранить",
20303
- compactionRouteNoVideo: "Модель не принимает видео: диалог с видео вернётся к модели диалога.",
20304
- compactionRouteNoImage: "Модель не принимает изображения: скриншоты в сводке станут текстом-заглушкой.",
20305
- compactionRouteEmpty: "Нет доступных линий — сначала войдите в любую.",
20306
20265
  customProxyUrl: "Адрес своего прокси",
20307
20266
  customProxyUrlHint: "Поддерживаются адреса HTTP/HTTPS/SOCKS5, например http://127.0.0.1:7890",
20308
20267
  customProxyUrlPlaceholder: "например http://127.0.0.1:7890",
@@ -20566,11 +20525,7 @@ window.__ModuleLoader__.load({
20566
20525
  cacheOutput: "Выходные токены",
20567
20526
  cacheRequests: "Запросы",
20568
20527
  cacheWrite: "Запись в кеш",
20569
- cacheWrite5m: "Уровень 5 минут",
20570
- cacheWrite1h: "Уровень 1 час",
20571
- cacheWriteHint: "Запись — отдельная статья биллинга: запись на 1 ч стоит примерно вдвое дороже записи на 5 мин, а попадание продлевает её по исходному TTL. 0 на уровне означает, что сервис не сообщил запись для этого уровня (отсутствующий заголовок — молчание), а не то, что там ничего не записали.",
20572
20528
  unknownMetric: "нет данных",
20573
- cacheHint: "В пределах одной сессии: любое изменение системного промпта или списка инструментов сбрасывает весь префиксный кеш, поэтому держите набор инструментов стабильным, а новое добавляйте в конец — так доля попаданий выше.",
20574
20529
  cacheExpired: "Сессия простаивала дольше времени жизни кеша, поэтому следующий ход заново обработает около {tokens} токенов.",
20575
20530
  cacheDrift: "Состояние последнего префикса",
20576
20531
  cacheDriftFirst: "Первый запрос (кеш пуст)",
@@ -20621,7 +20576,20 @@ window.__ModuleLoader__.load({
20621
20576
  loading: "Загрузка…",
20622
20577
  loginFailed: "Вход не выполнен",
20623
20578
  requests: "запросов",
20624
- tokens: "токенов"
20579
+ tokens: "токенов",
20580
+ compactionRoute: "Модель сжатия",
20581
+ compactionRouteHint: "Какая модель пишет сводку при сжатии контекста — только для диалогов этой линии. Если не выбрана, используется модель диалога — и её лимит.",
20582
+ compactionRouteProvider: "Линия",
20583
+ compactionRouteModel: "Модель",
20584
+ compactionRouteSave: "Сохранить",
20585
+ compactionRouteClear: "Модель диалога",
20586
+ compactionRouteChange: "Изменить",
20587
+ compactionRouteFollow: "Как в диалоге (по умолчанию)",
20588
+ compactionRouteSaved: "Сохранено; применится при следующем сжатии.",
20589
+ compactionRouteFailed: "Не удалось сохранить",
20590
+ compactionRouteNoVideo: "Модель не принимает видео: диалог с видео вернётся к модели диалога.",
20591
+ compactionRouteNoImage: "Модель не принимает изображения: скриншоты в сводке станут текстом-заглушкой.",
20592
+ compactionRouteEmpty: "Нет доступных линий — сначала войдите в любую."
20625
20593
  };
20626
20594
  //#endregion
20627
20595
  //#region src/client/locales-ru/minimax-code.ts
@@ -20934,6 +20902,8 @@ window.__ModuleLoader__.load({
20934
20902
  catalogSyncedNote: "Живой каталог синхронизирован: моделей {count}.",
20935
20903
  noModels: "Доступных моделей нет.",
20936
20904
  pricingNote: "Цены — официальный публичный прайс (доллары США за 1 млн токенов, вход / выход).",
20905
+ unservedBadge: "Нет в тарифе",
20906
+ unservedHint: "Модели, которых нет в текущем тарифе, по умолчанию скрыты; отметьте их здесь, чтобы показать принудительно.",
20937
20907
  contextSection: "Контекстные окна",
20938
20908
  contextHint: "Производитель не публикует контекстные окна по моделям, поэтому по умолчанию берётся 128k; здесь любую модель можно поправить. Неверное значение не сохраняется, а показывается ошибкой; восстановление сбрасывает переопределение.",
20939
20909
  tokens: "токены",
@@ -22039,9 +22009,10 @@ window.__ModuleLoader__.load({
22039
22009
  const window = [...quota.windows ?? []].sort((a, b) => (a.windowDurationMins ?? Infinity) - (b.windowDurationMins ?? Infinity))[0];
22040
22010
  if (window !== void 0) {
22041
22011
  const remaining = Math.max(0, 100 - window.usedPercent);
22012
+ const remainingText = formatPercent$1(remaining);
22042
22013
  return {
22043
- text: `${remaining}%`,
22044
- tooltip: `[Command Code] ${window.label}: ${remaining}% left`,
22014
+ text: `${remainingText}%`,
22015
+ tooltip: `[Command Code] ${window.label}: ${remainingText}% left`,
22045
22016
  level: quotaLevel(remaining)
22046
22017
  };
22047
22018
  }
@@ -22100,9 +22071,10 @@ window.__ModuleLoader__.load({
22100
22071
  const window = [...quota.windows ?? []].filter((candidate) => candidate.windowDurationMins !== null).sort((a, b) => (a.windowDurationMins ?? Infinity) - (b.windowDurationMins ?? Infinity))[0] ?? quota.windows[0];
22101
22072
  if (window !== void 0) {
22102
22073
  const remaining = Math.max(0, 100 - window.usedPercent);
22074
+ const remainingText = formatPercent$1(remaining);
22103
22075
  return {
22104
- text: `${remaining}%`,
22105
- tooltip: `[Kimi Code] ${window.label}: ${remaining}% left`,
22076
+ text: `${remainingText}%`,
22077
+ tooltip: `[Kimi Code] ${window.label}: ${remainingText}% left`,
22106
22078
  level: quotaLevel(remaining)
22107
22079
  };
22108
22080
  }
@@ -22244,9 +22216,11 @@ window.__ModuleLoader__.load({
22244
22216
  .dsha-kq-seg{border-radius:999px;display:block;height:100%;transition:width .9s cubic-bezier(.22,.61,.36,1)}
22245
22217
  .dsha-kq-seg--recent{background:var(--kq-code)}
22246
22218
  .dsha-kq-seg--rest{background:var(--kq-rest)}
22247
- .dsha-kq-tip{background:var(--kq-tip-bg);border-radius:10px;bottom:calc(100% + 10px);color:var(--dsw-alias-bg-base,#fff);font-size:13px;line-height:1;opacity:0;padding:9px 14px;pointer-events:none;position:absolute;right:0;transform:translateY(4px);transition:opacity .16s ease-out,transform .16s ease-out;white-space:nowrap}
22248
- .dsha-kq-tip::after{border:6px solid transparent;border-top-color:var(--kq-tip-bg);content:"";position:absolute;right:34px;top:100%}
22249
- .dsha-kq-barwrap:hover .dsha-kq-tip{opacity:1;transform:translateY(0)}
22219
+ .dsha-kq-tip{background:var(--kq-tip-bg);border-radius:10px;bottom:calc(100% + 10px);color:var(--dsw-alias-bg-base,#fff);font-size:13px;line-height:1;left:var(--kq-tip-x,50%);opacity:0;padding:9px 14px;pointer-events:none;position:absolute;transform:translate(calc(-50% + var(--kq-tip-shift,0px)),4px);transition:opacity .16s ease-out,transform .16s ease-out;white-space:nowrap}
22220
+ /* 箭头不跟气泡的位移走:气泡被 --kq-tip-shift 收回卡片内时,箭头按
22221
+ --kq-tip-arrow 单独挪回「已用段末端」在气泡里的位置,所以它指的一直是那个数字。 */
22222
+ .dsha-kq-tip::after{border:6px solid transparent;border-top-color:var(--kq-tip-bg);content:"";left:var(--kq-tip-arrow,50%);margin-left:-6px;position:absolute;top:100%}
22223
+ .dsha-kq-barwrap:hover .dsha-kq-tip{opacity:1;transform:translate(calc(-50% + var(--kq-tip-shift,0px)),0)}
22250
22224
  .dsha-kq-legend{align-items:center;color:var(--kq-fg-2);column-gap:16px;display:flex;flex-wrap:wrap;font-size:13px;margin-top:11px;row-gap:6px}
22251
22225
  .dsha-kq-legend-item{align-items:center;display:inline-flex;gap:6px;white-space:nowrap}
22252
22226
  .dsha-kq-legend-note{color:var(--kq-fg-3);white-space:nowrap}
@@ -54204,11 +54178,6 @@ window.__ModuleLoader__.load({
54204
54178
  label: () => t("hubTitle"),
54205
54179
  locale: NS
54206
54180
  }, ProviderHubSectionWrapper));
54207
- ctx.slots.inject("plugins.bundle.config", () => ctx.slots.register({
54208
- name: "plugins.bundle.config",
54209
- key: COMPACTION_ROUTE_PACKAGE,
54210
- locale: NS
54211
- }, CompactionRouteBundleConfig));
54212
54181
  ctx.slots.inject("conversation.input.right", () => ctx.slots.register({
54213
54182
  name: "conversation.input.right",
54214
54183
  id: "codex-subscription-quota",