@mars-sea/dsh-commandcode-provider 0.11.0 → 0.11.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/lib/client.js CHANGED
@@ -36,150 +36,6 @@ window.__ModuleLoader__.load({
36
36
  };
37
37
  }
38
38
  //#endregion
39
- //#region src/command-locales.ts
40
- const commandcodeCommand = {
41
- zh: {
42
- title: "📊 Command Code 用量{account}",
43
- accountTitle: "📊 {label}{badges}",
44
- accountSeparator: "────────────────────",
45
- activeBadge: " ✅ 当前使用",
46
- invalidCredentialBadge: " ⛔ 密钥无效",
47
- cooldownBadge: " ⏳ 限额冷却中,重置 {when}",
48
- rateLimitBadge: " ⏳ 已达限额(等待窗口探测)",
49
- unconfigured: " (未配置 API 密钥)",
50
- blockedInvalidKey: "⛔ API 密钥无效或已过期 — 服务端拒绝了全部请求(401),请检查该账户的密钥配置",
51
- blockedServiceUnavailable: "⚠️ Command Code 服务暂时不可用(5xx),稍后重试",
52
- blockedNetwork: "⚠️ 无法连接 Command Code 服务 — 请检查网络或 API 地址",
53
- planLine: " 📦 套餐 {name}{status}{period}",
54
- planPeriodSuffix: " · 账期截止 {date}",
55
- usageHeader: "── 请求 ──────────────────────────────",
56
- requestsLine: " 💬 请求 {n} 次 / 失败 {f} 成功率 {r}%",
57
- costLine: " 💰 花费 {money} ({credits} credits)",
58
- tokensLine: " 🔤 Token {in} 入 / {out} 出",
59
- creditsHeader: "── 信用 ──────────────────────────────",
60
- monthlyLine: " 💳 月额度 {monthly} (已购 {purchased} / 赠送 {free})",
61
- barLine: " └ {bar} {pct}%",
62
- windowsHeader: "── 窗口用量 ──────────────────────────",
63
- fiveHourLine: " ⏱ 5 小时 {used} / {cap}{warn}",
64
- weeklyLine: " 📅 每周 {used} / {cap}{warn}",
65
- windowBarLine: " └ {bar} 重置 {when}",
66
- exceededWarning: " ⚠️ 超限!",
67
- resetSuffix: "重置 {when}",
68
- partialFailures: "⚠️ 部分端点失败: {list}",
69
- noData: "(无数据 — 请检查 API 密钥)",
70
- errorText: "获取 Command Code 用量失败:{message}",
71
- imageGate: "当前会话已包含图片,而模型 {model} 不支持图片输入;请选择支持图片的模型,或先移除会话中的图片。"
72
- },
73
- en: {
74
- title: "📊 Command Code usage{account}",
75
- accountTitle: "📊 {label}{badges}",
76
- accountSeparator: "────────────────────",
77
- activeBadge: " ✅ active",
78
- invalidCredentialBadge: " ⛔ invalid key",
79
- cooldownBadge: " ⏳ cooling down, resets {when}",
80
- rateLimitBadge: " ⏳ rate-limited (waiting for window probe)",
81
- unconfigured: " (no API key configured)",
82
- blockedInvalidKey: "⛔ API key invalid or expired — the server rejected every request (401); check the key configured for this account",
83
- blockedServiceUnavailable: "⚠️ Command Code service temporarily unavailable (5xx); try again later",
84
- blockedNetwork: "⚠️ could not reach the Command Code service — check your network or the API base setting",
85
- planLine: " 📦 Plan {name}{status}{period}",
86
- planPeriodSuffix: " · period ends {date}",
87
- usageHeader: "── Requests ──────────────────────────",
88
- requestsLine: " 💬 Requests {n} / failed {f} success rate {r}%",
89
- costLine: " 💰 Spend {money} ({credits} credits)",
90
- tokensLine: " 🔤 Tokens {in} in / {out} out",
91
- creditsHeader: "── Credits ───────────────────────────",
92
- monthlyLine: " 💳 Monthly {monthly} (purchased {purchased} / free {free})",
93
- barLine: " └ {bar} {pct}%",
94
- windowsHeader: "── Window usage ──────────────────────",
95
- fiveHourLine: " ⏱ 5-hour {used} / {cap}{warn}",
96
- weeklyLine: " 📅 Weekly {used} / {cap}{warn}",
97
- windowBarLine: " └ {bar} resets {when}",
98
- exceededWarning: " ⚠️ exceeded!",
99
- resetSuffix: "resets {when}",
100
- partialFailures: "⚠️ some endpoints failed: {list}",
101
- noData: "(no data — check your API key)",
102
- errorText: "Could not fetch Command Code usage: {message}",
103
- imageGate: "This session already contains images, and model {model} does not accept image input; please select an image-capable model, or remove the images from the session first."
104
- }
105
- };
106
- //#endregion
107
- //#region src/client/sessions.ts
108
- /**
109
- * Friendly-error wrapper for the harness's image-session gate.
110
- *
111
- * The host rejects switching to a text-only model while the session already
112
- * contains images with a `model-unavailable` error
113
- * (`dsh-host-apiproxy`'s `session.selectModel` handler). That rejection is
114
- * intentional and cannot be relaxed from the plugin side — the adapter's
115
- * `inputModalities` is exactly what makes the guard work. What we CAN do is
116
- * make the error message friendlier: wrap the shared
117
- * `connection.api.sessions.selectModel` face so a `model-unavailable`
118
- * rejection shows a clear, actionable hint (with the requested model name)
119
- * instead of the raw English harness message.
120
- *
121
- * The wrapper is deliberately narrow: only the `model-unavailable` code is
122
- * rewritten, only when the message matches the image-session gate, and only
123
- * the message text changes — the error code and details pass through
124
- * untouched so any caller that switches on `error.code` keeps working.
125
- *
126
- * The wire types are spelled structurally here (not imported from
127
- * `@deepseek-ai/dsh-host-apiproxy`) so this client bundle does not drag an
128
- * extra peer dependency into the package; the shapes are stable and the
129
- * client build inlines them anyway.
130
- *
131
- * The wrapper takes a `getLocale` thunk because it is reached from a
132
- * non-React path that has no `t` in scope; the supplied thunk reads the
133
- * active locale at call time (typically `() => ctx.locale.getLocale().active`
134
- * in the client entry), and the message template lives in the shared
135
- * `commandcodeCommand` dictionary used by the Host-side `/commandcode`
136
- * command — the same bilingual surface serves both.
137
- *
138
- * This module is deliberately free of React and other client-platform
139
- * imports so the node test runner can exercise it directly.
140
- */
141
- /** Whether a selectModel rejection is the harness's image-session gate. */
142
- function isImageSessionRejection(result) {
143
- return !result.result.ok && result.result.error.code === "model-unavailable" && result.result.error.message.includes("does not accept image input");
144
- }
145
- /** Wrap the shared sessions API so selectModel failures read friendlier. */
146
- function withFriendlyImageError(sessions, getLocale) {
147
- const selectModel = sessions.selectModel.bind(sessions);
148
- return {
149
- ...sessions,
150
- selectModel: async (payload, signal) => {
151
- const result = await selectModel(payload, signal);
152
- if (!isImageSessionRejection(result)) return result;
153
- const model = result.result.error.details?.model ?? payload.model;
154
- const template = commandcodeCommand[getLocale()].imageGate ?? commandcodeCommand.en.imageGate;
155
- return {
156
- ...result,
157
- result: {
158
- ...result.result,
159
- error: {
160
- ...result.result.error,
161
- message: template.replace("{model}", model)
162
- }
163
- }
164
- };
165
- }
166
- };
167
- }
168
- /**
169
- * Install the friendly-error wrapper when a legacy sessions face is present.
170
- *
171
- * @returns whether the wrapper was installed. The rewrite is only UX polish;
172
- * a 0.1.2 connection has no `api.sessions`, and its absence must never block
173
- * the plugin from mounting its settings, credential, or usage surfaces.
174
- */
175
- function installFriendlyImageError(connection, getLocale) {
176
- const api = connection.api;
177
- const sessions = api?.sessions;
178
- if (api === void 0 || sessions === void 0 || typeof sessions.selectModel !== "function") return false;
179
- api.sessions = withFriendlyImageError(sessions, getLocale);
180
- return true;
181
- }
182
- //#endregion
183
39
  //#region src/client/settings.ts
184
40
  /**
185
41
  * Browser controller for the "Command Code" settings page.
@@ -303,6 +159,7 @@ window.__ModuleLoader__.load({
303
159
  }),
304
160
  booleanField$1("filterModelsByPlan"),
305
161
  booleanField$1("webSearch"),
162
+ booleanField$1("showSidebarQuota"),
306
163
  textField("activeAccount")
307
164
  ];
308
165
  /** Whether two model-id lists are equal as sets (order-insensitive). */
@@ -455,6 +312,8 @@ window.__ModuleLoader__.load({
455
312
  streamIdleTimeoutMs: this.field("streamIdleTimeoutMs"),
456
313
  filterModelsByPlan: this.field("filterModelsByPlan"),
457
314
  webSearch: this.field("webSearch"),
315
+ showSidebarQuota: this.field("showSidebarQuota"),
316
+ sidebarQuota: this.sectionValue("showSidebarQuota") === true,
458
317
  activeAccount: this.field("activeAccount"),
459
318
  accounts,
460
319
  accountsRemoving: [...this.removedRefs],
@@ -2291,7 +2150,7 @@ window.__ModuleLoader__.load({
2291
2150
  }
2292
2151
  //#endregion
2293
2152
  //#region package.json
2294
- var version = "0.11.0";
2153
+ var version = "0.11.2";
2295
2154
  var repository = {
2296
2155
  "type": "git",
2297
2156
  "url": "git+https://github.com/Mars-Sea/dsh-commandcode-provider.git"
@@ -2491,7 +2350,8 @@ window.__ModuleLoader__.load({
2491
2350
  "requestTimeoutMs",
2492
2351
  "streamIdleTimeoutMs",
2493
2352
  "filterModelsByPlan",
2494
- "webSearch"
2353
+ "webSearch",
2354
+ "showSidebarQuota"
2495
2355
  ];
2496
2356
  /** One labelled field row in the page body. */
2497
2357
  function Field({ id, label, hint, state, disabled, numeric, placeholder, onEdit, onReset, t }) {
@@ -2543,11 +2403,12 @@ window.__ModuleLoader__.load({
2543
2403
  return t("invalidNumber");
2544
2404
  }
2545
2405
  /**
2546
- * The collapsed "Advanced" card: API base, working dir, both timeouts, and
2547
- * the plan filter live here so the page leads with the facts a user actually
2548
- * touches. Starts collapsed on every visit; expands on demand. While
2549
- * collapsed, a badge names the customized count so a nonzero override stays
2550
- * visible (a number field's error blocks save and must be reachable).
2406
+ * The collapsed "Advanced" card: API base, working dir, both timeouts, the
2407
+ * plan filter, the web-search backend and the sidebar quota card live here so
2408
+ * the page leads with the facts a user actually touches. Starts collapsed on
2409
+ * every visit; expands on demand. While collapsed, a badge names the
2410
+ * customized count so a nonzero override stays visible (a number field's error
2411
+ * blocks save and must be reachable).
2551
2412
  */
2552
2413
  function AdvancedSection({ state, disabled, t, onEdit, onReset }) {
2553
2414
  const [expanded, setExpanded] = (0, react.useState)(false);
@@ -2654,6 +2515,17 @@ window.__ModuleLoader__.load({
2654
2515
  onEdit: (text) => onEdit("webSearch", text),
2655
2516
  onReset: () => onReset("webSearch"),
2656
2517
  t
2518
+ }),
2519
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToggleField, {
2520
+ id: "cc-show-sidebar-quota",
2521
+ label: t("showSidebarQuota"),
2522
+ hint: t("showSidebarQuotaHint"),
2523
+ state: state.showSidebarQuota,
2524
+ disabled,
2525
+ defaultChecked: false,
2526
+ onEdit: (text) => onEdit("showSidebarQuota", text),
2527
+ onReset: () => onReset("showSidebarQuota"),
2528
+ t
2657
2529
  })
2658
2530
  ]
2659
2531
  }) : null,
@@ -3965,12 +3837,68 @@ window.__ModuleLoader__.load({
3965
3837
  }
3966
3838
  //#endregion
3967
3839
  //#region src/client/panel-copy.ts
3968
- /** The panel's literal string table. */
3969
- const PANEL_COPY = {
3840
+ /** The locale namespace both panel slots bind their `t` seat to. */
3841
+ const PANEL_LOCALE_NS = "panel.commandcode";
3842
+ /** The panel's Simplified Chinese string table (the key-set source of truth). */
3843
+ const PANEL_COPY_ZH = {
3844
+ nav: "Command Code",
3845
+ subtitle: "套餐、额度与配额窗口",
3846
+ refresh: "刷新",
3847
+ refreshing: "刷新中…",
3848
+ close: "关闭",
3849
+ closeHint: "返回会话",
3850
+ loading: "正在获取账户用量…",
3851
+ noKey: "尚未配置 API 密钥",
3852
+ noKeyHint: "在 设置 → Command Code 中粘贴密钥或登录后,点击刷新。",
3853
+ plan: "套餐",
3854
+ credits: "额度",
3855
+ limits: "配额窗口",
3856
+ usage: "用量",
3857
+ monthly: "月额度",
3858
+ monthlyLimit: "月额度上限",
3859
+ monthlyUsed: "本月已用",
3860
+ remaining: "剩余",
3861
+ purchased: "已购",
3862
+ free: "赠送",
3863
+ fiveHour: "5 小时窗口",
3864
+ weekly: "每周窗口",
3865
+ fiveHourShort: "5 小时",
3866
+ weeklyShort: "每周",
3867
+ windowUnlimited: "不限",
3868
+ exceeded: "已超限",
3869
+ exhausted: "已用尽",
3870
+ resets: "重置于",
3871
+ requests: "请求",
3872
+ failed: "失败",
3873
+ successRate: "成功率",
3874
+ spend: "花费",
3875
+ tokens: "Token",
3876
+ tokensIn: "入",
3877
+ tokensOut: "出",
3878
+ periodEnds: "账期截止",
3879
+ updated: "更新于",
3880
+ partial: "部分端点数据不可用",
3881
+ active: "当前使用",
3882
+ coolingDown: "限额冷却中",
3883
+ invalidKey: "密钥无效",
3884
+ unconfigured: "未配置",
3885
+ unavailable: "无数据",
3886
+ errorInvalidKey: "API 密钥无效或已过期",
3887
+ errorInvalidKeyHint: "服务端拒绝了全部请求(401)。请检查该账户的密钥,或到 commandcode.ai 控制台重新生成。",
3888
+ errorServiceUnavailable: "Command Code 服务暂时不可用",
3889
+ errorServiceUnavailableHint: "服务端返回错误(5xx),请稍后点击刷新重试。",
3890
+ errorNetwork: "无法连接 Command Code 服务",
3891
+ errorNetworkHint: "所有请求都没有到达服务端。请检查网络连接或 API 地址设置。",
3892
+ errorGeneric: "用量获取失败"
3893
+ };
3894
+ /** The panel's English string table (must mirror {@link PANEL_COPY_ZH}). */
3895
+ const PANEL_COPY_EN = {
3970
3896
  nav: "Command Code",
3971
3897
  subtitle: "Plans, credits and quota windows",
3972
3898
  refresh: "Refresh",
3973
3899
  refreshing: "Refreshing…",
3900
+ close: "Close",
3901
+ closeHint: "Back to the conversation",
3974
3902
  loading: "Loading account usage…",
3975
3903
  noKey: "No API key configured",
3976
3904
  noKeyHint: "Paste a key — or sign in — under Settings → Command Code, then refresh.",
@@ -4015,9 +3943,15 @@ window.__ModuleLoader__.load({
4015
3943
  errorNetworkHint: "No request reached the server. Check your network connection or the API base setting.",
4016
3944
  errorGeneric: "Could not fetch account usage"
4017
3945
  };
4018
- /** Look up one panel string. The fallback key keeps a bad call visible, never blank. */
4019
- function panelText(key) {
4020
- return PANEL_COPY[key] ?? key;
3946
+ /** Every panel key, in the English table's declaration order. */
3947
+ const PANEL_KEYS = Object.keys(PANEL_COPY_EN);
3948
+ /**
3949
+ * The English translator. Used as the fallback when no locale seat is
3950
+ * available (a defensive path: every supported engine supplies one) and as the
3951
+ * default in tests that only assert the projection's figures.
3952
+ */
3953
+ function panelTextEN(key) {
3954
+ return PANEL_COPY_EN[key] ?? key;
4021
3955
  }
4022
3956
  //#endregion
4023
3957
  //#region src/client/panel.ts
@@ -4115,7 +4049,7 @@ window.__ModuleLoader__.load({
4115
4049
  };
4116
4050
  }
4117
4051
  /** Build one account's view. */
4118
- function accountView(entry) {
4052
+ function accountView(entry, t) {
4119
4053
  const { report } = entry;
4120
4054
  const account = report.account;
4121
4055
  const plan = report.plan;
@@ -4126,7 +4060,7 @@ window.__ModuleLoader__.load({
4126
4060
  stats.push({
4127
4061
  label: "requests",
4128
4062
  value: String(usage.completedCount),
4129
- sub: `${usage.failedCount} ${panelText("failed")}`
4063
+ sub: `${usage.failedCount} ${t("failed")}`
4130
4064
  });
4131
4065
  stats.push({
4132
4066
  label: "successRate",
@@ -4141,7 +4075,7 @@ window.__ModuleLoader__.load({
4141
4075
  stats.push({
4142
4076
  label: "tokens",
4143
4077
  value: formatTokensCompact(usage.totalTokensIn + usage.totalTokensOut),
4144
- sub: `${formatTokensCompact(usage.totalTokensIn)} ${panelText("tokensIn")} / ${formatTokensCompact(usage.totalTokensOut)} ${panelText("tokensOut")}`
4078
+ sub: `${formatTokensCompact(usage.totalTokensIn)} ${t("tokensIn")} / ${formatTokensCompact(usage.totalTokensOut)} ${t("tokensOut")}`
4145
4079
  });
4146
4080
  }
4147
4081
  const windows = [];
@@ -4192,15 +4126,14 @@ window.__ModuleLoader__.load({
4192
4126
  };
4193
4127
  }
4194
4128
  /**
4195
- * Every panel string, resolved once per projection. One object with all keys
4196
- * (rather than per-field lookups in the components) keeps the copy table and
4197
- * the render sites in lockstep: a key cannot be read from `text` unless
4198
- * {@link PANEL_COPY} declares it.
4129
+ * Every panel string, resolved once per projection through the translator.
4130
+ * One object with all keys (rather than per-field lookups in the components)
4131
+ * keeps the copy table and the render sites in lockstep: a key cannot be read
4132
+ * from `text` unless {@link PANEL_KEYS} declares it.
4199
4133
  */
4200
- function panelStrings() {
4201
- const keys = Object.keys(PANEL_COPY);
4134
+ function panelStrings(t) {
4202
4135
  const out = {};
4203
- for (const key of keys) out[key] = panelText(key);
4136
+ for (const key of PANEL_KEYS) out[key] = t(key);
4204
4137
  return out;
4205
4138
  }
4206
4139
  /**
@@ -4212,6 +4145,7 @@ window.__ModuleLoader__.load({
4212
4145
  */
4213
4146
  function buildPanelView(input) {
4214
4147
  const { usage } = input;
4148
+ const t = input.t ?? panelTextEN;
4215
4149
  const hidden = new Set(input.removingIds ?? []);
4216
4150
  const seen = /* @__PURE__ */ new Set();
4217
4151
  const entries = (usage.report?.accounts ?? []).filter((entry) => {
@@ -4220,7 +4154,7 @@ window.__ModuleLoader__.load({
4220
4154
  seen.add(entry.id);
4221
4155
  return true;
4222
4156
  });
4223
- const accounts = entries.map(accountView);
4157
+ const accounts = entries.map((entry) => accountView(entry, t));
4224
4158
  const selectedEntry = entries.find((entry) => entry.active) ?? entries[0];
4225
4159
  const selectedView = accounts.find((view) => view.id === selectedEntry?.id);
4226
4160
  const footerBars = [];
@@ -4239,18 +4173,18 @@ window.__ModuleLoader__.load({
4239
4173
  const totalCost = selectedEntry?.report.usage?.totalCost;
4240
4174
  const cost = totalCost === void 0 ? "" : money(totalCost);
4241
4175
  let status = "";
4242
- if (entries.length > 0 && !entries.some((entry) => entry.configured)) status = panelText("unconfigured");
4243
- else if (selectedView?.mark !== void 0 && selectedView.mark !== "active") status = panelText(selectedView.mark);
4244
- else if (selectedEntry === void 0) status = panelText("unavailable");
4245
- const planName = selectedView !== void 0 && selectedView.planName !== "" ? selectedView.planName : panelText("nav");
4246
- const titleParts = planName === panelText("nav") ? [planName] : [panelText("nav"), planName];
4176
+ if (entries.length > 0 && !entries.some((entry) => entry.configured)) status = t("unconfigured");
4177
+ else if (selectedView?.mark !== void 0 && selectedView.mark !== "active") status = t(selectedView.mark);
4178
+ else if (selectedEntry === void 0) status = t("unavailable");
4179
+ const planName = selectedView !== void 0 && selectedView.planName !== "" ? selectedView.planName : t("nav");
4180
+ const titleParts = planName === t("nav") ? [planName] : [t("nav"), planName];
4247
4181
  for (const bar of footerBars) {
4248
4182
  const figures = bar.detail === "" ? "" : ` ${bar.detail}`;
4249
- titleParts.push(`${panelText(bar.label)}${figures} (${bar.percent})`);
4183
+ titleParts.push(`${t(bar.label)}${figures} (${bar.percent})`);
4250
4184
  }
4251
- if (cost !== "") titleParts.push(`${panelText("spend")} ${cost}`);
4185
+ if (cost !== "") titleParts.push(`${t("spend")} ${cost}`);
4252
4186
  return {
4253
- text: panelStrings(),
4187
+ text: panelStrings(t),
4254
4188
  planName,
4255
4189
  status,
4256
4190
  footerBars,
@@ -4336,11 +4270,11 @@ window.__ModuleLoader__.load({
4336
4270
  * the sidebar footer card and the dashboard it opens in the center column.
4337
4271
  *
4338
4272
  * Both render one {@link PanelView} projected by `./panel.ts` — no fact is
4339
- * derived here. Strings arrive as keys into `view.text`, and every one of them
4340
- * is English, because the view is built from `./panel-copy.ts` rather than the
4341
- * harness `ctx.locale` namespace (which follows the user's language and would
4342
- * render this panel in Chinese on a Chinese harness — the thing this surface
4343
- * exists to avoid).
4273
+ * derived here. Strings arrive already localized in `view.text`: the slots
4274
+ * declare the `panel.commandcode` locale namespace, so the renderer hands this
4275
+ * component a `t` seat that is passed into `buildPanelView` — the panel follows
4276
+ * the harness's active language (a language switch mints a new `t`, so the
4277
+ * surfaces re-render without an extra channel).
4344
4278
  *
4345
4279
  * The footer card is the panel's home: the sidebar shell renders it in the foot
4346
4280
  * area directly above the Settings seat, so each quota window's own spend and
@@ -4362,7 +4296,8 @@ window.__ModuleLoader__.load({
4362
4296
  return buildPanelView({
4363
4297
  usage,
4364
4298
  apiKeyConfigured: settings.anyAccountConfigured,
4365
- removingIds: settings.accountsRemoving
4299
+ removingIds: settings.accountsRemoving,
4300
+ t: props.t ?? panelTextEN
4366
4301
  });
4367
4302
  }
4368
4303
  /**
@@ -4688,6 +4623,18 @@ window.__ModuleLoader__.load({
4688
4623
  disabled: view.loading,
4689
4624
  onClick: () => props.refresh(),
4690
4625
  children: view.loading ? text("refreshing") : text("refresh")
4626
+ }),
4627
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
4628
+ variant: "ghost",
4629
+ size: "sm",
4630
+ className: "ccp-close",
4631
+ "aria-label": text("close"),
4632
+ title: text("closeHint"),
4633
+ onClick: () => props.close(),
4634
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4635
+ "aria-hidden": "true",
4636
+ children: "×"
4637
+ })
4691
4638
  })
4692
4639
  ]
4693
4640
  }),
@@ -4773,11 +4720,23 @@ window.__ModuleLoader__.load({
4773
4720
  * matching the shell's own rail geometry. `wide` comes from the shell as an
4774
4721
  * owner prop — unlike the old `sidebar.panellist` row, this slot really does
4775
4722
  * supply it.
4723
+ *
4724
+ * Rendering is gated on the settings page's `showSidebarQuota` toggle, which
4725
+ * defaults OFF: a fresh install shows no quota surface in the sidebar and runs
4726
+ * no background poll. The gate reads the STORED document (the settings
4727
+ * controller's `sidebarQuota` fact), so an unsaved draft cannot show the card,
4728
+ * while a landed save flips it live. The entry is still REGISTERED — this is a
4729
+ * render decision, not a slot registration, so nothing churns the slot ledger.
4776
4730
  */
4777
4731
  function CommandCodeFooterEntry(props) {
4732
+ const visible = props.useCommandCodeSettings((snapshot) => snapshot.sidebarQuota);
4778
4733
  const view = usePanelView(props);
4779
4734
  const startAutoRefresh = props.startAutoRefresh;
4780
- (0, react.useEffect)(() => startAutoRefresh(), [startAutoRefresh]);
4735
+ (0, react.useEffect)(() => {
4736
+ if (!visible) return void 0;
4737
+ return startAutoRefresh();
4738
+ }, [visible, startAutoRefresh]);
4739
+ if (!visible) return null;
4781
4740
  const text = (key) => view.text[key] ?? key;
4782
4741
  const headline = view.footerBars[0];
4783
4742
  const title = view.footTitle;
@@ -5710,6 +5669,11 @@ window.__ModuleLoader__.load({
5710
5669
  .ccp-subtitle{margin:0;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:1.5}
5711
5670
  .ccp-spacer{flex:1}
5712
5671
  .ccp-meta{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:1.5;font-variant-numeric:tabular-nums}
5672
+ /* The dashboard's exit: an icon-sized glyph button. The primitive's own
5673
+ geometry is kept (size="sm"); only the glyph is enlarged and optically
5674
+ centred, so it reads as a dismiss control rather than a text button. */
5675
+ .ccp-close{min-width:28px;justify-content:center;padding-left:0;padding-right:0}
5676
+ .ccp-close span{font-size:16px;line-height:1}
5713
5677
  .ccp-hint{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:1.5}
5714
5678
 
5715
5679
  /* Notices: the no-key guidance, a blocked report, and a stale-data error. */
@@ -5787,7 +5751,7 @@ window.__ModuleLoader__.load({
5787
5751
  streamIdleTimeoutMs: "流空闲超时(毫秒)",
5788
5752
  streamIdleTimeoutMsHint: "生成流停滞多久视为断连;默认 300000(长思考模型可静默数分钟,默认值刻意放宽)。",
5789
5753
  advancedSettings: "高级设置",
5790
- advancedSettingsHint: "API 地址、工作目录、超时与模型过滤等不常修改的选项。",
5754
+ advancedSettingsHint: "API 地址、工作目录、超时、模型过滤、联网搜索与侧边栏额度卡片等不常修改的选项。",
5791
5755
  advancedOverriddenOne: "已自定义 1 项",
5792
5756
  advancedOverriddenMany: "已自定义 {count} 项",
5793
5757
  advancedInvalid: "高级设置中有未填好的数字,请展开修正后再保存。",
@@ -5795,6 +5759,8 @@ window.__ModuleLoader__.load({
5795
5759
  filterModelsByPlanHint: "开启后,模型选择器只列出当前套餐可用的模型;账户持有按需余额时会显示全部。",
5796
5760
  webSearch: "用 Command Code 承载联网搜索",
5797
5761
  webSearchHint: "开启后,dsh 的 web_search 工具由 Command Code 承担(复用同一个 API key 与地址),并优先于其他搜索后端;关闭则把选择权交还给之前的后端(如 modsearch),而不是强制回退到 DeepSeek 搜索。",
5762
+ showSidebarQuota: "在侧边栏显示额度卡片",
5763
+ showSidebarQuotaHint: "开启后,侧边栏底部(Settings 上方)显示 Command Code 套餐与配额卡片,点击可在中间栏打开完整仪表盘。默认关闭:左侧不显示任何额度信息,也不会为其后台刷新用量。保存后立即生效。",
5798
5764
  accountsTitle: "多账户轮换",
5799
5765
  accountsHint: "当前账户达到用量限额(429)或密钥失效(401)时,请求自动切换到下一个账户;全部耗尽时会提示最早的重置时间。",
5800
5766
  accountAdd: "添加账户",
@@ -5919,7 +5885,7 @@ window.__ModuleLoader__.load({
5919
5885
  streamIdleTimeoutMs: "Stream idle timeout (ms)",
5920
5886
  streamIdleTimeoutMsHint: "How long a stalled stream is treated as dead; default 300000 (deliberately generous — long-thinking models can stay silent for minutes).",
5921
5887
  advancedSettings: "Advanced",
5922
- advancedSettingsHint: "Rarely touched options: API base URL, working directory, timeouts, and model filtering.",
5888
+ advancedSettingsHint: "Rarely touched options: API base URL, working directory, timeouts, model filtering, web search, and the sidebar quota card.",
5923
5889
  advancedOverriddenOne: "1 customized",
5924
5890
  advancedOverriddenMany: "{count} customized",
5925
5891
  advancedInvalid: "A number in Advanced settings is not ready to save; expand to fix it.",
@@ -5927,6 +5893,8 @@ window.__ModuleLoader__.load({
5927
5893
  filterModelsByPlanHint: "When on, the model picker lists only models your subscription includes; any on-demand credit balance shows the full catalog.",
5928
5894
  webSearch: "Serve dsh web search with Command Code",
5929
5895
  webSearchHint: "When on, the model-facing web_search tool is backed by Command Code (same API key and base URL as chat), winning over other search backends. Off hands the selection back to the previous backend (e.g. modsearch) instead of forcing the shipped DeepSeek search.",
5896
+ showSidebarQuota: "Show the quota card in the sidebar",
5897
+ showSidebarQuotaHint: "When on, a Command Code plan & quota card sits at the bottom of the sidebar (above Settings) and opens the full dashboard in the centre column. Off by default: nothing is shown on the left, and no background usage poll runs for it. Applies on save.",
5930
5898
  accountsTitle: "Account rotation",
5931
5899
  accountsHint: "When the active account hits its usage limit (429) or its key fails (401), requests switch to the next account; when every account is exhausted the error names the earliest window reset.",
5932
5900
  accountAdd: "Add account",
@@ -6199,6 +6167,15 @@ select.cc-input{appearance:none;-webkit-appearance:none;-moz-appearance:none;box
6199
6167
  */
6200
6168
  const PANEL_ID = "commandcode-panel";
6201
6169
  /**
6170
+ * ui-conversation's reserved `main` key, used only as the exit fallback for a
6171
+ * layout whose `selectPanel` predates the `null` "show the Conversation"
6172
+ * selection (see `close` in {@link applyClientSurfaces}). Declared locally for
6173
+ * the same reason as {@link PANEL_ID}: ui-conversation is not a dependency of
6174
+ * this bundle, and the key is a published contract of the layout (its README
6175
+ * names `conversation` as reserved for the Conversation).
6176
+ */
6177
+ const CONVERSATION_PANEL_ID = "conversation";
6178
+ /**
6202
6179
  * The composer figure's entry id in `conversation.composer.dock`. Its own id,
6203
6180
  * not the shipped `stats` cell's: reusing `stats` would REPLACE the tokens /
6204
6181
  * cache-hit / throughput readout rather than inject into it, and that readout
@@ -6213,12 +6190,15 @@ select.cc-input{appearance:none;-webkit-appearance:none;-moz-appearance:none;box
6213
6190
  */
6214
6191
  function apply(ctx) {
6215
6192
  injectPageCss();
6216
- const connection = ctx.get("connection");
6217
- if (connection !== void 0) installFriendlyImageError(connection, () => ctx.locale.getLocale().active === "zh" ? "zh" : "en");
6218
6193
  ctx.effect(() => ctx.locale.register("settings.commandcode", {
6219
6194
  zh,
6220
6195
  en
6221
6196
  }), "dsh-commandcode-provider: page copy");
6197
+ ctx.effect(() => ctx.locale.register(PANEL_LOCALE_NS, {
6198
+ zh: PANEL_COPY_ZH,
6199
+ en: PANEL_COPY_EN
6200
+ }), "dsh-commandcode-provider: panel copy");
6201
+ const connection = ctx.get("connection");
6222
6202
  const legacyApi = adaptLegacyCredentials(connection?.api?.credentials);
6223
6203
  if (legacyApi !== void 0) {
6224
6204
  applyClientSurfaces(ctx, legacyApi, connection?.hostDescription);
@@ -6431,6 +6411,19 @@ select.cc-input{appearance:none;-webkit-appearance:none;-moz-appearance:none;box
6431
6411
  open: () => {
6432
6412
  const layout = ctx.get("layout");
6433
6413
  if (typeof layout?.selectPanel === "function") layout.selectPanel(PANEL_ID);
6414
+ },
6415
+ close: () => {
6416
+ const layout = ctx.get("layout");
6417
+ if (typeof layout?.selectPanel !== "function") return;
6418
+ try {
6419
+ layout.selectPanel(null);
6420
+ } catch {
6421
+ try {
6422
+ layout.selectPanel(CONVERSATION_PANEL_ID);
6423
+ } catch (error) {
6424
+ console.error("[dsh-commandcode-provider] could not close the plans & quota panel:", error);
6425
+ }
6426
+ }
6434
6427
  }
6435
6428
  });
6436
6429
  ctx.effect(() => injectPanelCss(), "dsh-commandcode-provider: panel styles");
@@ -6438,6 +6431,7 @@ select.cc-input{appearance:none;-webkit-appearance:none;-moz-appearance:none;box
6438
6431
  ctx.slots.inject("main", () => ctx.slots.register({
6439
6432
  name: "main",
6440
6433
  key: PANEL_ID,
6434
+ locale: PANEL_LOCALE_NS,
6441
6435
  inject: panelFace
6442
6436
  }, CommandCodePanel));
6443
6437
  } catch (error) {
@@ -6450,6 +6444,7 @@ select.cc-input{appearance:none;-webkit-appearance:none;-moz-appearance:none;box
6450
6444
  name: "sidebar.footer.action",
6451
6445
  id: PANEL_ID,
6452
6446
  order: 1,
6447
+ locale: PANEL_LOCALE_NS,
6453
6448
  inject: panelFace
6454
6449
  }, CommandCodeFooterEntry));
6455
6450
  } catch (error) {
@@ -6477,8 +6472,6 @@ select.cc-input{appearance:none;-webkit-appearance:none;-moz-appearance:none;box
6477
6472
  //#endregion
6478
6473
  exports.apply = apply;
6479
6474
  exports.inject = inject;
6480
- exports.isImageSessionRejection = isImageSessionRejection;
6481
- exports.withFriendlyImageError = withFriendlyImageError;
6482
6475
  return module.exports;
6483
6476
  }
6484
6477
  });