dsh-llm-codebuddy-power 1.0.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js CHANGED
@@ -10,20 +10,7 @@ window.__ModuleLoader__.load({
10
10
  let react_jsx_runtime = require("react/jsx-runtime");
11
11
  let react_dom = require("react-dom");
12
12
  //#region src/prefs.ts
13
- /**
14
- * CodeBuddy 界面偏好的命名空间契约,Host 与 Client 两面共享。
15
- *
16
- * 这些偏好是**插件自己的设置命名空间**里的一段:`Config` 提供部署级 base,
17
- * 用户在 Web 设置页的写入落在 harness 的用户设置文档里,因此换浏览器、重启
18
- * harness 后仍然生效 —— 其中「极简模式画图工具」还会被宿主读取来决定
19
- * `codebuddy-draw` 对模型是否可见,存在浏览器里会让同一次部署因浏览器而异。
20
- *
21
- * 本文件必须保持零依赖:两面都会编译它,而 Client 面若因此引入 schemastery
22
- * 就会把它打进浏览器产物(校验 schema 是宿主面的事,见 `./settings.ts`)。
23
- *
24
- * @module dsh-llm-codebuddy-power/prefs
25
- */
26
- /** 本插件拥有的设置命名空间,与插件名一致。 */
13
+ /** 本插件的设置入口 id,与插件名一致。 */
27
14
  const CODEBUDDY_SETTINGS_NAMESPACE = "llm-codebuddy-power";
28
15
  /** 未覆盖任何一层时使用的偏好默认值。 */
29
16
  const DEFAULT_CODEBUDDY_SETTINGS = {
@@ -33,7 +20,7 @@ window.__ModuleLoader__.load({
33
20
  };
34
21
  //#endregion
35
22
  //#region \0dsh-css:D:\data\code\deepseek-harness\packages\my\dsh-llm-codebuddy-power\src\client\CodeBuddySection.module.css.mjs
36
- const css$4 = ".-h11cq_section{flex-direction:column;gap:12px;max-width:720px;padding:8px 0;display:flex}.-h11cq_title{color:var(--dsw-alias-label-primary);margin:0;font-size:18px;font-weight:600}.-h11cq_desc{color:var(--dsw-alias-label-secondary);margin:0;font-size:14px;line-height:22px}.-h11cq_muted{color:var(--dsw-alias-label-secondary);margin:0;font-size:14px}.-h11cq_error{color:var(--dsw-alias-state-error-primary);margin:0;font-size:14px}.-h11cq_accountHeader{border-bottom:1px solid var(--dsw-alias-border-l2);flex-wrap:wrap;justify-content:flex-start;align-items:center;gap:8px;padding-bottom:8px;display:flex}.-h11cq_splitButton{align-items:center;display:inline-flex}.-h11cq_splitMain{box-shadow:inset -1px 0 0 var(--dsw-alias-border-l3);border-right-width:0;border-top-right-radius:0;border-bottom-right-radius:0}.-h11cq_splitActionAnchor{display:inline-flex}.-h11cq_splitAction{border-top-left-radius:0;border-bottom-left-radius:0;padding:0 10px}.-h11cq_splitMain:focus-visible,.-h11cq_splitAction:focus-visible{outline-offset:-2px}.-h11cq_badges{flex-wrap:wrap;gap:8px;display:flex}.-h11cq_badge{box-sizing:border-box;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);border-radius:16px;align-items:center;gap:6px;max-width:100%;min-height:32px;padding:0 4px 0 0;display:flex}.-h11cq_badge[data-default]{border-color:var(--dsw-alias-brand-primary);background:var(--dsw-alias-interactive-bg-hover-accent)}.-h11cq_badgeInfo{align-self:stretch;align-items:center;min-width:0;padding-left:12px;display:flex}.-h11cq_badgeDragging{opacity:.4}.-h11cq_badgeDropBefore,.-h11cq_badgeDropAfter{position:relative}.-h11cq_badgeDropBefore:before,.-h11cq_badgeDropAfter:after{content:\"\";z-index:1;background:var(--dsw-alias-brand-primary);pointer-events:none;border-radius:1px;width:2px;position:absolute;top:0;bottom:0}.-h11cq_badgeDropBefore:before{left:-5px}.-h11cq_badgeDropAfter:after{right:-5px}.-h11cq_badgeInfoText{color:var(--dsw-alias-label-primary);text-overflow:ellipsis;white-space:nowrap;max-width:180px;font-size:13px;line-height:20px;overflow:hidden}.-h11cq_badge:not(:has(.-h11cq_badgeDefaultTag)):not(:has(.-h11cq_badgeCheck)) .-h11cq_badgeInfo{padding-right:8px}.-h11cq_badgeDefaultTag{background:var(--dsw-alias-interactive-bg-hover-accent);color:var(--dsw-alias-brand-primary);cursor:pointer;border:0;border-radius:999px;flex:none;padding:0 6px;font-size:12px;line-height:20px}.-h11cq_badgeDefaultTag:hover{background:var(--dsw-alias-interactive-bg-hover)}.-h11cq_badgeDefaultTag:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}.-h11cq_badgeActionAnchor{flex:none;display:inline-flex}.-h11cq_badgeAction{width:24px;height:24px;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:0;border-radius:12px;justify-content:center;align-items:center;padding:0;display:inline-flex}.-h11cq_badgeAction:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover-danger);color:var(--dsw-alias-state-error-primary)}.-h11cq_badgeAction:disabled{cursor:default;opacity:.4}.-h11cq_badgeAction:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}.-h11cq_badgeCheck{border:1px solid var(--dsw-alias-border-l3);width:20px;height:20px;color:var(--dsw-alias-label-primary-foreground);cursor:pointer;background:0 0;border-radius:6px;flex:none;justify-content:center;align-items:center;padding:0;display:inline-flex}.-h11cq_badgeCheckOn{border-color:var(--dsw-alias-brand-primary);background:var(--dsw-alias-brand-primary)}.-h11cq_badgeCheck:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}.-h11cq_divider{background:var(--dsw-alias-border-l2);height:1px}.-h11cq_prefRow{border-bottom:1px solid var(--dsw-alias-border-l2);align-items:center;gap:8px;padding:8px 0;display:flex}.-h11cq_prefRowText{flex-direction:column;flex:1;gap:4px;min-width:0;padding-right:48px;display:flex}.-h11cq_prefTitle{color:var(--dsw-alias-label-primary);font-size:14px;font-weight:400;line-height:22px}.-h11cq_prefDesc{color:var(--dsw-alias-label-secondary);font-size:12px;font-weight:400;line-height:18px}.-h11cq_switch{box-sizing:border-box;background:var(--dsw-alias-border-l3);cursor:pointer;border:0;border-radius:10px;flex:none;width:36px;height:20px;padding:2px;position:relative}.-h11cq_switchOn{background:var(--dsw-alias-brand-primary)}.-h11cq_switch:disabled{cursor:default;opacity:.5}.-h11cq_switch:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}.-h11cq_thumb{corner-shape:round;background:var(--dsw-alias-label-primary-foreground);border-radius:50%;width:16px;height:16px;transition:transform .12s;display:block}.-h11cq_switchOn .-h11cq_thumb{transform:translate(16px)}.-h11cq_prefInput{width:120px}";
23
+ const css$4 = ".-h11cq_section{flex-direction:column;gap:12px;padding:8px 0;display:flex}.-h11cq_title{color:var(--dsw-alias-label-primary);margin:0;font-size:18px;font-weight:600}.-h11cq_desc{color:var(--dsw-alias-label-secondary);margin:0;font-size:14px;line-height:22px}.-h11cq_muted{color:var(--dsw-alias-label-secondary);margin:0;font-size:14px}.-h11cq_error{color:var(--dsw-alias-state-error-primary);margin:0;font-size:14px}.-h11cq_accountHeader{border-bottom:1px solid var(--dsw-alias-border-l2);flex-wrap:wrap;justify-content:flex-start;align-items:center;gap:8px;padding-bottom:8px;display:flex}.-h11cq_splitButton{align-items:center;display:inline-flex}.-h11cq_splitMain{box-shadow:inset -1px 0 0 var(--dsw-alias-border-l3);border-right-width:0;border-top-right-radius:0;border-bottom-right-radius:0}.-h11cq_splitActionAnchor{display:inline-flex}.-h11cq_splitAction{border-top-left-radius:0;border-bottom-left-radius:0;padding:0 10px}.-h11cq_splitMain:focus-visible,.-h11cq_splitAction:focus-visible{outline-offset:-2px}.-h11cq_badges{flex-wrap:wrap;gap:8px;display:flex}.-h11cq_badge{box-sizing:border-box;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);border-radius:16px;align-items:center;gap:6px;max-width:100%;min-height:32px;padding:0 4px 0 0;display:flex}.-h11cq_badge[data-default]{border-color:var(--dsw-alias-brand-primary);background:var(--dsw-alias-interactive-bg-hover-accent)}.-h11cq_badgeInfo{align-self:stretch;align-items:center;min-width:0;padding-left:12px;display:flex}.-h11cq_badgeDragging{opacity:.4}.-h11cq_badgeDropBefore,.-h11cq_badgeDropAfter{position:relative}.-h11cq_badgeDropBefore:before,.-h11cq_badgeDropAfter:after{content:\"\";z-index:1;background:var(--dsw-alias-brand-primary);pointer-events:none;border-radius:1px;width:2px;position:absolute;top:0;bottom:0}.-h11cq_badgeDropBefore:before{left:-5px}.-h11cq_badgeDropAfter:after{right:-5px}.-h11cq_badgeInfoText{color:var(--dsw-alias-label-primary);text-overflow:ellipsis;white-space:nowrap;max-width:180px;font-size:13px;line-height:20px;overflow:hidden}.-h11cq_badge:not(:has(.-h11cq_badgeDefaultTag)):not(:has(.-h11cq_badgeCheck)) .-h11cq_badgeInfo{padding-right:8px}.-h11cq_badgeDefaultTag{background:var(--dsw-alias-interactive-bg-hover-accent);color:var(--dsw-alias-brand-primary);cursor:pointer;border:0;border-radius:999px;flex:none;padding:0 6px;font-size:12px;line-height:20px}.-h11cq_badgeDefaultTag:hover{background:var(--dsw-alias-interactive-bg-hover)}.-h11cq_badgeDefaultTag:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}.-h11cq_badgeActionAnchor{flex:none;display:inline-flex}.-h11cq_badgeAction{width:24px;height:24px;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:0;border-radius:12px;justify-content:center;align-items:center;padding:0;display:inline-flex}.-h11cq_badgeAction:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover-danger);color:var(--dsw-alias-state-error-primary)}.-h11cq_badgeAction:disabled{cursor:default;opacity:.4}.-h11cq_badgeAction:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}.-h11cq_badgeCheck{border:1px solid var(--dsw-alias-border-l3);width:20px;height:20px;color:var(--dsw-alias-label-primary-foreground);cursor:pointer;background:0 0;border-radius:6px;flex:none;justify-content:center;align-items:center;padding:0;display:inline-flex}.-h11cq_badgeCheckOn{border-color:var(--dsw-alias-brand-primary);background:var(--dsw-alias-brand-primary)}.-h11cq_badgeCheck:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}.-h11cq_divider{background:var(--dsw-alias-border-l2);height:1px}.-h11cq_prefRow{border-bottom:1px solid var(--dsw-alias-border-l2);align-items:center;gap:8px;padding:8px 0;display:flex}.-h11cq_prefRowText{flex-direction:column;flex:1;gap:4px;min-width:0;padding-right:48px;display:flex}.-h11cq_prefTitle{color:var(--dsw-alias-label-primary);font-size:14px;font-weight:400;line-height:22px}.-h11cq_prefDesc{color:var(--dsw-alias-label-secondary);font-size:12px;font-weight:400;line-height:18px}.-h11cq_switch{box-sizing:border-box;background:var(--dsw-alias-border-l3);cursor:pointer;border:0;border-radius:10px;flex:none;width:36px;height:20px;padding:2px;position:relative}.-h11cq_switchOn{background:var(--dsw-alias-brand-primary)}.-h11cq_switch:disabled{cursor:default;opacity:.5}.-h11cq_switch:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}.-h11cq_thumb{corner-shape:round;background:var(--dsw-alias-label-primary-foreground);border-radius:50%;width:16px;height:16px;transition:transform .12s;display:block}.-h11cq_switchOn .-h11cq_thumb{transform:translate(16px)}.-h11cq_prefInput{width:120px}";
37
24
  const tagId$4 = "dsh-llm-codebuddy-power/CodeBuddySection.module.css";
38
25
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$4) + "]") === null) {
39
26
  const tag = document.createElement("style");
@@ -87,7 +74,7 @@ window.__ModuleLoader__.load({
87
74
  */
88
75
  /** 客户端轮询已发起登录的间隔(毫秒)。 */
89
76
  const POLL_INTERVAL_MS = 1500;
90
- /** 客户端放弃前持续轮询的时长(毫秒)。 */
77
+ /** 客户端等待登录完成的时长(毫秒),与宿主的 `LOGIN_TIMEOUT_MS` 对齐。 */
91
78
  const POLL_DEADLINE_MS = 600 * 1e3;
92
79
  /** 复制成功后图标保持对勾的时长(毫秒)。 */
93
80
  const COPIED_FEEDBACK_MS = 1e3;
@@ -202,7 +189,7 @@ window.__ModuleLoader__.load({
202
189
  "aria-label": `${t("selectAccount")}: ${account.nickname}`,
203
190
  className: `${CodeBuddySection_module_css_default.badgeCheck} ${checked ? CodeBuddySection_module_css_default.badgeCheckOn : ""}`,
204
191
  onClick: onToggleChecked,
205
- children: checked && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCheckOutline16, { size: 14 })
192
+ children: checked && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCheckOutlineRegular, { size: 14 })
206
193
  })
207
194
  ]
208
195
  });
@@ -213,7 +200,7 @@ window.__ModuleLoader__.load({
213
200
  * @returns 分区的元素树。
214
201
  */
215
202
  function CodeBuddySection(props) {
216
- const { t, useStore, usePrefs, refreshAccounts, startLogin, copyLoginLink, cancelLogin, pollLogin, logoutAccounts, reorderAccounts, setDefault, setPref, actions } = props;
203
+ const { t, useStore, usePrefs, refreshAccounts, startLogin, resumeLogin, copyLoginLink, cancelLogin, pollLogin, logoutAccounts, reorderAccounts, setDefault, setPref, actions } = props;
217
204
  const phase = useStore((s) => s.phase);
218
205
  const error = useStore((s) => s.error);
219
206
  const accounts = useStore((s) => s.accounts);
@@ -227,39 +214,54 @@ window.__ModuleLoader__.load({
227
214
  const [checked, setChecked] = (0, react.useState)([]);
228
215
  /** 待确认的退出目标;null 表示没有待确认的退出。 */
229
216
  const [pendingLogout, setPendingLogout] = (0, react.useState)(null);
230
- const halted = (0, react.useRef)(false);
231
- /** 卸载后为 true;登录轮询就此停止,而不是触碰状态。 */
232
- const isHalted = () => halted.current;
233
217
  (0, react.useEffect)(() => {
234
218
  refreshAccounts();
235
- }, [refreshAccounts]);
219
+ resumeLogin();
220
+ }, [refreshAccounts, resumeLogin]);
236
221
  (0, react.useEffect)(() => {
237
222
  if (loginState === null) return;
238
- halted.current = false;
239
- const { state, startedAt } = loginState;
240
- let timer;
223
+ const run = {
224
+ stopped: false,
225
+ abort: new AbortController()
226
+ };
227
+ /** 本次跑次是否已收尾(完成、超时、取消或卸载)。 */
228
+ const isStopped = () => run.stopped;
229
+ /**
230
+ * 收尾本次跑次:停掉轮询与截止定时器,并中止在飞的那次请求。
231
+ *
232
+ * 中止是必须的:宿主侧 `pollLogin` 现在立即作答,但**请求已经发出去**这件事
233
+ * 与定时器无关 —— 只清定时器,那一次请求仍占着连接,而浏览器对同一源的并发连接
234
+ * 数有限,积压后会把账号、用量等其余请求一起挤成待处理。
235
+ */
236
+ const stop = () => {
237
+ run.stopped = true;
238
+ if (run.deadline !== void 0) window.clearTimeout(run.deadline);
239
+ if (run.poll !== void 0) window.clearTimeout(run.poll);
240
+ run.abort.abort();
241
+ };
242
+ run.deadline = window.setTimeout(() => {
243
+ if (isStopped()) return;
244
+ stop();
245
+ actions.setLoginState(null);
246
+ actions.fail(t("timeout"));
247
+ }, Math.max(0, loginState.startedAt + POLL_DEADLINE_MS - Date.now()));
241
248
  const tick = async () => {
242
- if (isHalted()) return;
249
+ if (isStopped()) return;
243
250
  let done = false;
244
251
  try {
245
- done = await pollLogin(state);
252
+ done = await pollLogin(loginState.state, run.abort.signal);
246
253
  } catch {
247
254
  done = false;
248
255
  }
249
- if (isHalted()) return;
250
- if (done) return;
251
- if (Date.now() - startedAt >= POLL_DEADLINE_MS) {
252
- actions.setLoginState(null);
253
- actions.fail(t("timeout"));
256
+ if (isStopped()) return;
257
+ if (done) {
258
+ stop();
254
259
  return;
255
260
  }
256
- timer = window.setTimeout(tick, POLL_INTERVAL_MS);
261
+ run.poll = window.setTimeout(tick, POLL_INTERVAL_MS);
257
262
  };
258
263
  tick();
259
- return () => {
260
- halted.current = true;
261
- if (timer !== void 0) window.clearTimeout(timer);
262
- };
264
+ return stop;
263
265
  }, [
264
266
  loginState,
265
267
  pollLogin,
@@ -270,16 +272,17 @@ window.__ModuleLoader__.load({
270
272
  startLogin();
271
273
  }, [startLogin]);
272
274
  /**
273
- * 放弃这次登录:先中止宿主的轮询,再清掉本地的握手状态。
275
+ * 放弃这次登录:先清掉本地的握手状态,再通知宿主中止。
274
276
  *
275
- * 顺序不能反 —— 先清状态的话,正在飞的那次 `pollLogin` 仍会按 `done: false` 排下一次
276
- * 轮询;而先中止,状态一清,effect 的 `loginState === null` 分支就不再重建轮询链。
277
+ * 顺序不能反 —— 取消对用户是即刻的,而 `cancelLogin` 是一次宿主调用,等它返回才
278
+ * 收回按钮会让取消显得没反应。先清状态是安全的:那一次状态变更会让上面的 effect
279
+ * 收尾,正在飞的那次 `pollLogin` 返回后就此停手,不会再排下一次轮询。
277
280
  */
278
281
  const onCancelLogin = (0, react.useCallback)(() => {
279
- if (loginState === null) return;
280
- cancelLogin(loginState.state).finally(() => {
281
- actions.setLoginState(null);
282
- });
282
+ const pending = loginState;
283
+ if (pending === null) return;
284
+ actions.setLoginState(null);
285
+ cancelLogin(pending.state);
283
286
  }, [
284
287
  loginState,
285
288
  cancelLogin,
@@ -352,6 +355,7 @@ window.__ModuleLoader__.load({
352
355
  id: account.uid,
353
356
  label: account.nickname
354
357
  }));
358
+ if (props.view === "summary") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: t("intro") });
355
359
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
356
360
  className: CodeBuddySection_module_css_default.section,
357
361
  children: [
@@ -390,7 +394,7 @@ window.__ModuleLoader__.load({
390
394
  size: "md",
391
395
  className: CodeBuddySection_module_css_default.splitAction,
392
396
  "aria-label": loginState === null ? copied ? t("copied") : t("copyLoginLink") : t("cancelLogin"),
393
- icon: loginState === null ? copied ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCheckOutline16, { size: 14 }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCopyOutline16, { size: 14 }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCloseOutline16, { size: 14 }),
397
+ icon: loginState === null ? copied ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCheckOutlineRegular, { size: 14 }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCopyOutlineRegular, { size: 14 }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCloseOutlineRegular, { size: 14 }),
394
398
  onClick: loginState === null ? onCopyLoginLink : onCancelLogin
395
399
  })
396
400
  })
@@ -445,7 +449,7 @@ window.__ModuleLoader__.load({
445
449
  onClick: () => {
446
450
  setPendingLogout([...checked]);
447
451
  },
448
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconTrashOutline16, { size: 14 })
452
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconTrashOutlineRegular, { size: 14 })
449
453
  })
450
454
  })
451
455
  })
@@ -676,6 +680,52 @@ window.__ModuleLoader__.load({
676
680
  });
677
681
  }
678
682
  /**
683
+ * 把剩余赠送额度压成圆环中心那点宽度放得下的写法(`999`、`1.23k`、`12.3k`、`123k`、`1.23m`)。
684
+ *
685
+ * 单位内保留三位有效数字,而不是只换算能整除的值:额度常是 12000、15000 这类数,
686
+ * 只换算整除的会让同一处时而 `12000` 时而 `1.2k`。
687
+ *
688
+ * 先按量级定档、再只取整一次:每档都取整会把误差累积起来,`1_234_567` 会在 `k` 档
689
+ * 先被写成 `1235`、再进位成 `1.24m`,而它本应是 `1.23m`。定档只看量级,取整只做一次。
690
+ *
691
+ * 取整仍可能把本档推到一千(`999_600` → `1000`),此时再进一档,否则会写出 `1000k`
692
+ * 与相邻的 `1m` 并存成两种记法。
693
+ * @param value - 剩余赠送额度,非负。
694
+ * @returns 最多三位有效数字、带 `k`/`m`/`g` 单位的紧凑写法。
695
+ */
696
+ function formatCompact(value) {
697
+ const units = [
698
+ "",
699
+ "k",
700
+ "m",
701
+ "g"
702
+ ];
703
+ /** 按三位有效数字取整:大于等于 100 取整,大于等于 10 保留一位,其余保留两位。 */
704
+ const round3 = (scaled) => {
705
+ const digits = scaled >= 100 ? 0 : scaled >= 10 ? 1 : 2;
706
+ const rounded = Math.round(scaled * 10 ** digits) / 10 ** digits;
707
+ return {
708
+ text: rounded.toLocaleString(void 0, {
709
+ maximumFractionDigits: digits,
710
+ useGrouping: false
711
+ }),
712
+ carries: rounded >= 1e3
713
+ };
714
+ };
715
+ let scaled = value;
716
+ let unit = 0;
717
+ while (scaled >= 1e3 && unit < units.length - 1) {
718
+ scaled /= 1e3;
719
+ unit += 1;
720
+ }
721
+ let result = round3(scaled);
722
+ if (result.carries && unit < units.length - 1) {
723
+ unit += 1;
724
+ result = round3(scaled / 1e3);
725
+ }
726
+ return `${result.text}${units[unit]}`;
727
+ }
728
+ /**
679
729
  * 构建圆环提示:合并后的已用/总量与百分比,其后是主池数值与它的重置时刻、赠送池,
680
730
  * 最后是最近到期的赠送窗口明细。
681
731
  *
@@ -755,6 +805,8 @@ window.__ModuleLoader__.load({
755
805
  const limit = (usage?.main?.limit ?? 0) + (usage?.gift?.limit ?? 0);
756
806
  const used = (usage?.main?.used ?? 0) + (usage?.gift?.used ?? 0);
757
807
  const pct = limit > 0 ? Math.min(Math.max(used / limit * 100, 0), 100) : void 0;
808
+ const giftLeft = Math.max((usage?.gift?.limit ?? 0) - (usage?.gift?.used ?? 0), 0);
809
+ const ringText = giftLeft > 0 ? formatCompact(giftLeft) : pct?.toFixed(2);
758
810
  const tip = usage !== null && pct !== void 0 ? ringTooltip(usage, {
759
811
  used,
760
812
  limit
@@ -813,7 +865,7 @@ window.__ModuleLoader__.load({
813
865
  fill: "var(--dsw-alias-label-primary)",
814
866
  fontSize: RING_FONT_SIZE,
815
867
  fontWeight: 600,
816
- children: pct.toFixed(2)
868
+ children: ringText
817
869
  })
818
870
  ]
819
871
  })
@@ -916,7 +968,7 @@ window.__ModuleLoader__.load({
916
968
  }
917
969
  //#endregion
918
970
  //#region \0dsh-css:D:\data\code\deepseek-harness\packages\my\dsh-llm-codebuddy-power\src\client\ModelPanel.module.css.mjs
919
- const css$2 = ".pAq4ya_root{flex:none;display:inline-flex;position:relative}.pAq4ya_trigger{min-width:calc(28px + var(--dsh-content-font-delta,0px));height:calc(28px + var(--dsh-content-font-delta,0px));color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:0;border-radius:6px;justify-content:center;align-items:center;gap:4px;padding:6px;display:inline-flex}.pAq4ya_rate{font-variant-numeric:tabular-nums;color:var(--dsw-alias-label-secondary);font-size:12px}.pAq4ya_trigger:hover{background:var(--dsw-alias-interactive-bg-hover)}.pAq4ya_trigger[data-inactive] svg{opacity:.36}.pAq4ya_trigger[data-inactive]:hover svg{opacity:.6}.pAq4ya_panel{z-index:1100;box-sizing:border-box;visibility:hidden;background:var(--dsw-specific-menu);--dsw-elevation-stroke-color:var(--dsw-alias-border-l1);width:min(360px,100vw - 24px);max-height:min(560px,70vh);box-shadow:var(--dsw-elevation-prominent);color:var(--dsw-alias-label-primary);border:0;border-radius:20px;flex-direction:column;gap:8px;padding:12px;font-size:13px;line-height:20px;display:flex;position:fixed}.pAq4ya_panel[data-positioned]{visibility:visible}.pAq4ya_header{border-bottom:.5px solid var(--dsw-alias-border-l2);align-items:baseline;gap:8px;padding-bottom:6px;display:flex}.pAq4ya_headerTitle{color:var(--dsw-alias-label-primary);font-weight:600}.pAq4ya_error{color:var(--dsw-alias-state-error-primary);font-size:12px}.pAq4ya_list{flex-direction:column;gap:2px;min-height:0;display:flex;overflow-y:auto}.pAq4ya_row{border-radius:8px;flex-direction:column;gap:2px;padding:6px 8px;display:flex}.pAq4ya_row:hover,.pAq4ya_rowSelected{background:var(--dsw-alias-interactive-bg-hover)}.pAq4ya_rowMain{text-align:left;width:100%;color:var(--dsw-alias-label-primary);cursor:pointer;font:inherit;background:0 0;border:0;flex-wrap:wrap;align-items:flex-start;gap:6px;padding:0;display:flex}.pAq4ya_rowName{white-space:nowrap;flex:none;font-weight:500}.pAq4ya_tag{border:.5px solid;border-radius:4px;flex:none;padding:0 5px;font-size:11px;line-height:16px}.pAq4ya_tagTier{color:var(--dsw-alias-label-secondary);border-color:var(--dsw-alias-border-l3)}.pAq4ya_rowTail{flex:none;align-items:baseline;gap:4px;margin-left:auto;display:inline-flex}.pAq4ya_credits{color:var(--dsw-alias-label-tertiary);font-variant-numeric:tabular-nums;flex:none;font-size:12px}.pAq4ya_creditsStruck{color:var(--dsw-alias-label-tertiary);font-variant-numeric:tabular-nums;flex:none;font-size:12px;text-decoration:line-through}.pAq4ya_discounted{color:var(--dsw-alias-brand-primary);font-variant-numeric:tabular-nums;flex:none;font-size:12px}.pAq4ya_check{color:var(--dsw-alias-label-primary);flex:0 0 16px;place-items:center;display:grid}.pAq4ya_empty{text-align:center;color:var(--dsw-alias-label-tertiary);padding:16px 0}.pAq4ya_tip{z-index:1099;box-sizing:border-box;overscroll-behavior:contain;background:var(--dsw-specific-menu);--dsw-elevation-stroke-color:var(--dsw-alias-border-l1);max-width:calc(100vw - 16px);max-height:min(360px,70vh);box-shadow:var(--dsw-elevation-prominent);color:var(--dsw-alias-label-primary);border:0;border-radius:14px;flex-direction:column;gap:6px;padding:10px 12px;font-size:12px;line-height:18px;display:flex;position:fixed;overflow-y:auto}.pAq4ya_tipName{color:var(--dsw-alias-label-primary);font-size:13px;font-weight:600}.pAq4ya_tipDesc{color:var(--dsw-alias-label-secondary)}.pAq4ya_tipFeatures,.pAq4ya_tipSpend,.pAq4ya_tipPromo{align-items:baseline;gap:8px;display:flex}.pAq4ya_tipLabel{color:var(--dsw-alias-label-tertiary);flex:none}.pAq4ya_tipVal{text-align:right;margin-left:auto}.pAq4ya_tipCreditsStruck{color:var(--dsw-alias-label-tertiary);text-decoration:line-through}.pAq4ya_tipDiscounted{color:var(--dsw-alias-brand-primary);margin-left:4px}.pAq4ya_tipTier{color:var(--dsw-alias-label-secondary)}.pAq4ya_tipDivider{background:var(--dsw-alias-border-l2);height:.5px}.pAq4ya_tipCaps{color:var(--dsw-alias-label-secondary);align-items:center;gap:8px;display:flex}.pAq4ya_tipInput{border:.5px solid var(--dsw-alias-border-l3);background:var(--dsw-alias-bg-layer-1);min-width:0;height:24px;color:var(--dsw-alias-label-primary);font:inherit;border-radius:5px;flex:1;padding:0 6px;font-size:12px}.pAq4ya_tipInput:focus-visible{outline:1.5px solid var(--dsw-alias-brand-primary);outline-offset:1px}.pAq4ya_tipEffort{border:.5px solid var(--dsw-alias-border-l3);background:var(--dsw-alias-bg-layer-1);max-width:100%;height:24px;color:var(--dsw-alias-label-primary);cursor:pointer;font:inherit;border-radius:5px;align-items:center;gap:4px;padding:0 6px 0 8px;font-size:12px;display:inline-flex}.pAq4ya_tipEffort:hover{background:var(--dsw-alias-interactive-bg-hover)}.pAq4ya_tipEffortMenu{min-width:0;margin-left:auto}.pAq4ya_tipEffortValue{white-space:nowrap;text-overflow:ellipsis;overflow:hidden}.pAq4ya_tipEffortChevron{color:var(--dsw-alias-label-tertiary);flex:none}.pAq4ya_tipValue{min-width:0;color:var(--dsw-alias-label-primary);font:inherit;flex:1;font-size:12px;line-height:20px}.pAq4ya_tipActions{justify-content:flex-end;gap:8px;display:flex}.pAq4ya_tipSave{background:var(--dsw-alias-button-primary-fill);height:28px;color:var(--dsw-alias-label-primary-foreground);cursor:pointer;font:inherit;border:0;border-radius:14px;padding:0 12px;font-size:12px;line-height:18px}.pAq4ya_tipSave:disabled{opacity:.4;cursor:not-allowed}.pAq4ya_tipSave:hover:not(:disabled){background:var(--dsw-alias-button-primary-hover)}.pAq4ya_tipAction{border:.5px solid var(--dsw-alias-border-l3);height:28px;color:var(--dsw-alias-label-primary);cursor:pointer;font:inherit;background:0 0;border-radius:14px;padding:0 12px;font-size:12px;line-height:18px}.pAq4ya_tipAction:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.pAq4ya_tipLink{color:var(--dsw-alias-link);cursor:pointer;font:inherit;background:0 0;border:0;align-self:flex-start;padding:0;font-size:12px;line-height:18px}.pAq4ya_tipLink:hover{text-decoration:underline}.pAq4ya_tipLink:focus-visible{outline:1.5px solid var(--dsw-alias-brand-primary);outline-offset:2px;border-radius:3px}.pAq4ya_tipLinkChevron{margin-left:2px}@media (hover:none){.pAq4ya_row{padding:10px 8px}.pAq4ya_tipAction,.pAq4ya_tipSave{border-radius:18px;height:36px;padding:0 16px}.pAq4ya_tipLink{align-items:center;min-height:28px;display:inline-flex}.pAq4ya_tipInput{height:32px;font-size:14px}.pAq4ya_tipCaps{gap:10px}}";
971
+ const css$2 = ".pAq4ya_root{flex:none;display:inline-flex;position:relative}.pAq4ya_trigger{min-width:calc(28px + var(--dsh-content-font-delta,0px));height:calc(28px + var(--dsh-content-font-delta,0px));color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:0;border-radius:6px;justify-content:center;align-items:center;gap:4px;padding:6px;display:inline-flex}.pAq4ya_rate{font-variant-numeric:tabular-nums;color:var(--dsw-alias-label-secondary);font-size:12px}.pAq4ya_trigger:hover{background:var(--dsw-alias-interactive-bg-hover)}.pAq4ya_trigger[data-inactive] svg{opacity:.36}.pAq4ya_trigger[data-inactive]:hover svg{opacity:.6}.pAq4ya_panel{z-index:1100;box-sizing:border-box;visibility:hidden;--dsw-elevation-stroke-color:var(--dsw-alias-border-l1);width:min(360px,100vw - 24px);max-height:min(560px,70vh);box-shadow:var(--dsw-elevation-prominent);color:var(--dsw-alias-label-primary);border:0;flex-direction:column;gap:8px;padding:12px;font-size:13px;line-height:20px;display:flex;position:fixed;overflow:hidden}.pAq4ya_panel[data-positioned]{visibility:visible}.pAq4ya_header{border-bottom:.5px solid var(--dsw-alias-border-l2);align-items:baseline;gap:8px;padding-bottom:6px;display:flex}.pAq4ya_headerTitle{color:var(--dsw-alias-label-primary);font-weight:600}.pAq4ya_error{color:var(--dsw-alias-state-error-primary);font-size:12px}.pAq4ya_list{flex-direction:column;gap:2px;min-height:0;display:flex;overflow-y:auto}.pAq4ya_row{border-radius:8px;flex-direction:column;gap:2px;padding:6px 8px;display:flex}.pAq4ya_row:hover,.pAq4ya_rowSelected{background:var(--dsw-alias-interactive-bg-hover)}.pAq4ya_rowMain{text-align:left;width:100%;color:var(--dsw-alias-label-primary);cursor:pointer;font:inherit;background:0 0;border:0;flex-wrap:wrap;align-items:flex-start;gap:6px;padding:0;display:flex}.pAq4ya_rowName{white-space:nowrap;flex:none;font-weight:500}.pAq4ya_tag{border:.5px solid;border-radius:4px;flex:none;padding:0 5px;font-size:11px;line-height:16px}.pAq4ya_tagTier{color:var(--dsw-alias-label-secondary);border-color:var(--dsw-alias-border-l3)}.pAq4ya_rowTail{flex:none;align-items:baseline;gap:4px;margin-left:auto;display:inline-flex}.pAq4ya_credits{color:var(--dsw-alias-label-tertiary);font-variant-numeric:tabular-nums;flex:none;font-size:12px}.pAq4ya_creditsStruck{color:var(--dsw-alias-label-tertiary);font-variant-numeric:tabular-nums;flex:none;font-size:12px;text-decoration:line-through}.pAq4ya_discounted{color:var(--dsw-alias-brand-primary);font-variant-numeric:tabular-nums;flex:none;font-size:12px}.pAq4ya_check{color:var(--dsw-alias-label-primary);flex:0 0 16px;place-items:center;display:grid}.pAq4ya_empty{text-align:center;color:var(--dsw-alias-label-tertiary);padding:16px 0}.pAq4ya_tip{z-index:1099;box-sizing:border-box;--dsw-elevation-stroke-color:var(--dsw-alias-border-l1);max-width:calc(100vw - 16px);max-height:min(360px,70vh);box-shadow:var(--dsw-elevation-prominent);color:var(--dsw-alias-label-primary);border:0;flex-direction:column;font-size:12px;line-height:18px;display:flex;position:fixed}.pAq4ya_tipScroll{overscroll-behavior:contain;flex-direction:column;gap:6px;min-height:0;padding:10px 12px;display:flex;overflow-y:auto}.pAq4ya_tipName{color:var(--dsw-alias-label-primary);font-size:13px;font-weight:600}.pAq4ya_tipDesc{color:var(--dsw-alias-label-secondary)}.pAq4ya_tipFeatures,.pAq4ya_tipSpend,.pAq4ya_tipPromo{align-items:baseline;gap:8px;display:flex}.pAq4ya_tipLabel{color:var(--dsw-alias-label-tertiary);flex:none}.pAq4ya_tipVal{text-align:right;margin-left:auto}.pAq4ya_tipCreditsStruck{color:var(--dsw-alias-label-tertiary);text-decoration:line-through}.pAq4ya_tipDiscounted{color:var(--dsw-alias-brand-primary);margin-left:4px}.pAq4ya_tipTier{color:var(--dsw-alias-label-secondary)}.pAq4ya_tipDivider{background:var(--dsw-alias-border-l2);height:.5px}.pAq4ya_tipCaps{color:var(--dsw-alias-label-secondary);align-items:center;gap:8px;display:flex}.pAq4ya_tipInput{border:.5px solid var(--dsw-alias-border-l3);background:var(--dsw-alias-bg-layer-1);min-width:0;height:24px;color:var(--dsw-alias-label-primary);font:inherit;border-radius:5px;flex:1;padding:0 6px;font-size:12px}.pAq4ya_tipInput:focus-visible{outline:1.5px solid var(--dsw-alias-brand-primary);outline-offset:1px}.pAq4ya_tipEffort{border:.5px solid var(--dsw-alias-border-l3);background:var(--dsw-alias-bg-layer-1);max-width:100%;height:24px;color:var(--dsw-alias-label-primary);cursor:pointer;font:inherit;border-radius:5px;align-items:center;gap:4px;padding:0 6px 0 8px;font-size:12px;display:inline-flex}.pAq4ya_tipEffort:hover{background:var(--dsw-alias-interactive-bg-hover)}.pAq4ya_tipEffortMenu{min-width:0;margin-left:auto}.pAq4ya_tipEffortValue{white-space:nowrap;text-overflow:ellipsis;overflow:hidden}.pAq4ya_tipEffortChevron{color:var(--dsw-alias-label-tertiary);flex:none}.pAq4ya_tipValue{min-width:0;color:var(--dsw-alias-label-primary);font:inherit;flex:1;font-size:12px;line-height:20px}.pAq4ya_tipActions{justify-content:flex-end;gap:8px;display:flex}.pAq4ya_tipSave{background:var(--dsw-alias-button-primary-fill);height:28px;color:var(--dsw-alias-label-primary-foreground);cursor:pointer;font:inherit;border:0;border-radius:14px;padding:0 12px;font-size:12px;line-height:18px}.pAq4ya_tipSave:disabled{opacity:.4;cursor:not-allowed}.pAq4ya_tipSave:hover:not(:disabled){background:var(--dsw-alias-button-primary-hover)}.pAq4ya_tipAction{border:.5px solid var(--dsw-alias-border-l3);height:28px;color:var(--dsw-alias-label-primary);cursor:pointer;font:inherit;background:0 0;border-radius:14px;padding:0 12px;font-size:12px;line-height:18px}.pAq4ya_tipAction:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.pAq4ya_tipLink{color:var(--dsw-alias-link);cursor:pointer;font:inherit;background:0 0;border:0;align-self:flex-start;padding:0;font-size:12px;line-height:18px}.pAq4ya_tipLink:hover{text-decoration:underline}.pAq4ya_tipLink:focus-visible{outline:1.5px solid var(--dsw-alias-brand-primary);outline-offset:2px;border-radius:3px}.pAq4ya_tipLinkChevron{margin-left:2px}@media (hover:none){.pAq4ya_row{padding:10px 8px}.pAq4ya_tipAction,.pAq4ya_tipSave{border-radius:18px;height:36px;padding:0 16px}.pAq4ya_tipLink{align-items:center;min-height:28px;display:inline-flex}.pAq4ya_tipInput{height:32px;font-size:14px}.pAq4ya_tipCaps{gap:10px}}";
920
972
  const tagId$2 = "dsh-llm-codebuddy-power/ModelPanel.module.css";
921
973
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$2) + "]") === null) {
922
974
  const tag = document.createElement("style");
@@ -965,6 +1017,7 @@ window.__ModuleLoader__.load({
965
1017
  "tipName": "pAq4ya_tipName",
966
1018
  "tipPromo": "pAq4ya_tipPromo",
967
1019
  "tipSave": "pAq4ya_tipSave",
1020
+ "tipScroll": "pAq4ya_tipScroll",
968
1021
  "tipSpend": "pAq4ya_tipSpend",
969
1022
  "tipTier": "pAq4ya_tipTier",
970
1023
  "tipVal": "pAq4ya_tipVal",
@@ -1735,7 +1788,7 @@ window.__ModuleLoader__.load({
1735
1788
  })]
1736
1789
  })
1737
1790
  }),
1738
- state.open && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1791
+ state.open && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.MenuSurface, {
1739
1792
  ref: panelRef,
1740
1793
  className: ModelPanel_module_css_default.panel,
1741
1794
  style: panelStyle,
@@ -1872,7 +1925,7 @@ window.__ModuleLoader__.load({
1872
1925
  const hasMidSection = row.credits !== void 0 && row.credits.length > 0 || promotionText !== void 0 || promotionAction !== void 0;
1873
1926
  const tipDiscounted = row.promotion?.discountedCredits;
1874
1927
  const tipStrikeOriginal = tipDiscounted !== void 0 && row.promotion?.displayMode !== "replace";
1875
- return (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1928
+ return (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.MenuSurface, {
1876
1929
  ref: tipRef,
1877
1930
  className: ModelPanel_module_css_default.tip,
1878
1931
  role: "tooltip",
@@ -1881,206 +1934,209 @@ window.__ModuleLoader__.load({
1881
1934
  top: tip.top,
1882
1935
  width: tip.width
1883
1936
  },
1884
- children: [
1885
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1886
- className: ModelPanel_module_css_default.tipName,
1887
- children: row.name
1888
- }),
1889
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1890
- className: ModelPanel_module_css_default.tipDesc,
1891
- children: description ?? t("modelPanelDescriptionMissing")
1892
- }),
1893
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1894
- className: ModelPanel_module_css_default.tipFeatures,
1895
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1896
- className: ModelPanel_module_css_default.tipLabel,
1897
- children: t("modelPanelFeatures")
1898
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1899
- className: ModelPanel_module_css_default.tipVal,
1900
- children: featureLabels(row, t)
1901
- })]
1902
- }),
1903
- tierText !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1904
- className: ModelPanel_module_css_default.tipTier,
1905
- children: tierText
1906
- }),
1907
- tierAction !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1908
- type: "button",
1909
- className: ModelPanel_module_css_default.tipLink,
1910
- onClick: (event) => {
1911
- runAction(row, tierAction, shouldRestoreFocus(event));
1912
- },
1913
- children: [t("modelPanelUpgrade"), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1914
- className: ModelPanel_module_css_default.tipLinkChevron,
1915
- "aria-hidden": "true",
1916
- children: "›"
1917
- })]
1918
- }),
1919
- hasMidSection && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: ModelPanel_module_css_default.tipDivider }),
1920
- row.credits !== void 0 && row.credits.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1921
- className: ModelPanel_module_css_default.tipSpend,
1922
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1923
- className: ModelPanel_module_css_default.tipLabel,
1924
- children: t("modelPanelSpendRate")
1925
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1926
- className: ModelPanel_module_css_default.tipVal,
1927
- children: [
1928
- tipDiscounted === void 0 && row.credits,
1929
- tipStrikeOriginal && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1930
- className: ModelPanel_module_css_default.tipCreditsStruck,
1931
- children: row.credits
1932
- }),
1933
- tipDiscounted !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1934
- className: ModelPanel_module_css_default.tipDiscounted,
1935
- style: row.promotion?.color === void 0 ? void 0 : { color: row.promotion.color },
1936
- children: tipDiscounted
1937
- })
1938
- ]
1939
- })]
1940
- }),
1941
- promotionText !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1942
- className: ModelPanel_module_css_default.tipPromo,
1943
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: promotionText })
1944
- }),
1945
- (promotionAction !== void 0 || tip.source === "touch") && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1946
- type: "button",
1947
- className: ModelPanel_module_css_default.tipAction,
1948
- onClick: (event) => {
1949
- runAction(row, promotionAction, shouldRestoreFocus(event));
1950
- },
1951
- children: t("modelPanelUse")
1952
- }),
1953
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: ModelPanel_module_css_default.tipDivider }),
1954
- efforts !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1955
- className: ModelPanel_module_css_default.tipCaps,
1956
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1957
- className: ModelPanel_module_css_default.tipLabel,
1958
- children: t("modelPanelEffort")
1959
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Menu, {
1960
- open: effortOpen,
1961
- portal: true,
1962
- dense: true,
1963
- side: "bottom",
1964
- align: "end",
1965
- selectedId: selectedEffort === void 0 ? EFFORT_DEFAULT_ID : selectedEffort,
1966
- items: effortItems,
1967
- onSelect: (id) => {
1968
- chooseEffort(row, id === EFFORT_DEFAULT_ID ? void 0 : id);
1937
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1938
+ className: ModelPanel_module_css_default.tipScroll,
1939
+ children: [
1940
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1941
+ className: ModelPanel_module_css_default.tipName,
1942
+ children: row.name
1943
+ }),
1944
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1945
+ className: ModelPanel_module_css_default.tipDesc,
1946
+ children: description ?? t("modelPanelDescriptionMissing")
1947
+ }),
1948
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1949
+ className: ModelPanel_module_css_default.tipFeatures,
1950
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1951
+ className: ModelPanel_module_css_default.tipLabel,
1952
+ children: t("modelPanelFeatures")
1953
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1954
+ className: ModelPanel_module_css_default.tipVal,
1955
+ children: featureLabels(row, t)
1956
+ })]
1957
+ }),
1958
+ tierText !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1959
+ className: ModelPanel_module_css_default.tipTier,
1960
+ children: tierText
1961
+ }),
1962
+ tierAction !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1963
+ type: "button",
1964
+ className: ModelPanel_module_css_default.tipLink,
1965
+ onClick: (event) => {
1966
+ runAction(row, tierAction, shouldRestoreFocus(event));
1969
1967
  },
1970
- onClose: () => {
1971
- setEffortOpen(false);
1968
+ children: [t("modelPanelUpgrade"), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1969
+ className: ModelPanel_module_css_default.tipLinkChevron,
1970
+ "aria-hidden": "true",
1971
+ children: "›"
1972
+ })]
1973
+ }),
1974
+ hasMidSection && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: ModelPanel_module_css_default.tipDivider }),
1975
+ row.credits !== void 0 && row.credits.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1976
+ className: ModelPanel_module_css_default.tipSpend,
1977
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1978
+ className: ModelPanel_module_css_default.tipLabel,
1979
+ children: t("modelPanelSpendRate")
1980
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1981
+ className: ModelPanel_module_css_default.tipVal,
1982
+ children: [
1983
+ tipDiscounted === void 0 && row.credits,
1984
+ tipStrikeOriginal && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1985
+ className: ModelPanel_module_css_default.tipCreditsStruck,
1986
+ children: row.credits
1987
+ }),
1988
+ tipDiscounted !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1989
+ className: ModelPanel_module_css_default.tipDiscounted,
1990
+ style: row.promotion?.color === void 0 ? void 0 : { color: row.promotion.color },
1991
+ children: tipDiscounted
1992
+ })
1993
+ ]
1994
+ })]
1995
+ }),
1996
+ promotionText !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1997
+ className: ModelPanel_module_css_default.tipPromo,
1998
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: promotionText })
1999
+ }),
2000
+ (promotionAction !== void 0 || tip.source === "touch") && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2001
+ type: "button",
2002
+ className: ModelPanel_module_css_default.tipAction,
2003
+ onClick: (event) => {
2004
+ runAction(row, promotionAction, shouldRestoreFocus(event));
1972
2005
  },
1973
- className: ModelPanel_module_css_default.tipEffortMenu,
1974
- anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1975
- type: "button",
1976
- className: ModelPanel_module_css_default.tipEffort,
1977
- "aria-haspopup": "menu",
1978
- "aria-expanded": effortOpen,
1979
- onClick: () => {
1980
- setEffortOpen((open) => !open);
2006
+ children: t("modelPanelUse")
2007
+ }),
2008
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: ModelPanel_module_css_default.tipDivider }),
2009
+ efforts !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2010
+ className: ModelPanel_module_css_default.tipCaps,
2011
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2012
+ className: ModelPanel_module_css_default.tipLabel,
2013
+ children: t("modelPanelEffort")
2014
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Menu, {
2015
+ open: effortOpen,
2016
+ portal: true,
2017
+ dense: true,
2018
+ side: "bottom",
2019
+ align: "end",
2020
+ selectedId: selectedEffort === void 0 ? EFFORT_DEFAULT_ID : selectedEffort,
2021
+ items: effortItems,
2022
+ onSelect: (id) => {
2023
+ chooseEffort(row, id === EFFORT_DEFAULT_ID ? void 0 : id);
1981
2024
  },
1982
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1983
- className: ModelPanel_module_css_default.tipEffortValue,
1984
- children: effortLabel
1985
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1986
- className: ModelPanel_module_css_default.tipEffortChevron,
1987
- "aria-hidden": true,
1988
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutline14, {})
1989
- })]
1990
- })
1991
- })]
1992
- }),
1993
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1994
- className: ModelPanel_module_css_default.tipCaps,
1995
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1996
- className: ModelPanel_module_css_default.tipLabel,
1997
- children: t("modelPanelContextWindow")
1998
- }), tipEdit.editing ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1999
- type: "text",
2000
- inputMode: "text",
2001
- className: `${ModelPanel_module_css_default.tipInput} ${ModelPanel_module_css_default.tipVal}`,
2002
- autoFocus: true,
2003
- value: tipEdit.contextDraft,
2004
- onChange: (event) => {
2005
- const next = event.currentTarget.value;
2006
- if (!DRAFT_PATTERN.test(next)) return;
2007
- setTipEdit({
2008
- ...tipEdit,
2009
- contextDraft: clampOnInput(next, row.contextWindowCap)
2010
- });
2011
- },
2012
- onKeyDown: (event) => {
2013
- if (event.key !== "Enter") return;
2014
- event.preventDefault();
2015
- saveTipEdit(row, tipEdit);
2016
- }
2017
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2018
- className: `${ModelPanel_module_css_default.tipValue} ${ModelPanel_module_css_default.tipVal}`,
2019
- children: formatTokens(row.contextWindow)
2020
- })]
2021
- }),
2022
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2023
- className: ModelPanel_module_css_default.tipCaps,
2024
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2025
- className: ModelPanel_module_css_default.tipLabel,
2026
- children: t("modelPanelMaxOutput")
2027
- }), tipEdit.editing ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2028
- type: "text",
2029
- inputMode: "text",
2030
- className: `${ModelPanel_module_css_default.tipInput} ${ModelPanel_module_css_default.tipVal}`,
2031
- value: tipEdit.outputDraft,
2032
- onChange: (event) => {
2033
- const next = event.currentTarget.value;
2034
- if (!DRAFT_PATTERN.test(next)) return;
2035
- setTipEdit({
2036
- ...tipEdit,
2037
- outputDraft: clampOnInput(next, row.maxOutputTokensCap)
2038
- });
2039
- },
2040
- onKeyDown: (event) => {
2041
- if (event.key !== "Enter") return;
2042
- event.preventDefault();
2043
- saveTipEdit(row, tipEdit);
2044
- }
2045
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2046
- className: `${ModelPanel_module_css_default.tipValue} ${ModelPanel_module_css_default.tipVal}`,
2047
- children: formatTokens(row.maxOutputTokens)
2048
- })]
2049
- }),
2050
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2051
- className: ModelPanel_module_css_default.tipActions,
2052
- children: [
2053
- tipEdit.editing && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2054
- type: "button",
2055
- className: ModelPanel_module_css_default.tipAction,
2056
- disabled: tipEdit.saving,
2057
- onClick: () => {
2058
- restoreDefault(row);
2025
+ onClose: () => {
2026
+ setEffortOpen(false);
2059
2027
  },
2060
- children: t("modelPanelRestoreDefault")
2061
- }),
2062
- tipEdit.editing && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2063
- type: "button",
2064
- className: ModelPanel_module_css_default.tipAction,
2065
- disabled: tipEdit.saving,
2066
- onClick: () => {
2067
- setTipEdit(draftsFor(row, false));
2028
+ className: ModelPanel_module_css_default.tipEffortMenu,
2029
+ anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
2030
+ type: "button",
2031
+ className: ModelPanel_module_css_default.tipEffort,
2032
+ "aria-haspopup": "menu",
2033
+ "aria-expanded": effortOpen,
2034
+ onClick: () => {
2035
+ setEffortOpen((open) => !open);
2036
+ },
2037
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2038
+ className: ModelPanel_module_css_default.tipEffortValue,
2039
+ children: effortLabel
2040
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2041
+ className: ModelPanel_module_css_default.tipEffortChevron,
2042
+ "aria-hidden": true,
2043
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutlineRegular, {})
2044
+ })]
2045
+ })
2046
+ })]
2047
+ }),
2048
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2049
+ className: ModelPanel_module_css_default.tipCaps,
2050
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2051
+ className: ModelPanel_module_css_default.tipLabel,
2052
+ children: t("modelPanelContextWindow")
2053
+ }), tipEdit.editing ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2054
+ type: "text",
2055
+ inputMode: "text",
2056
+ className: `${ModelPanel_module_css_default.tipInput} ${ModelPanel_module_css_default.tipVal}`,
2057
+ autoFocus: true,
2058
+ value: tipEdit.contextDraft,
2059
+ onChange: (event) => {
2060
+ const next = event.currentTarget.value;
2061
+ if (!DRAFT_PATTERN.test(next)) return;
2062
+ setTipEdit({
2063
+ ...tipEdit,
2064
+ contextDraft: clampOnInput(next, row.contextWindowCap)
2065
+ });
2068
2066
  },
2069
- children: t("cancel")
2070
- }),
2071
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2072
- type: "button",
2073
- className: ModelPanel_module_css_default.tipSave,
2074
- disabled: tipEdit.saving,
2075
- onClick: () => {
2076
- if (tipEdit.editing) saveTipEdit(row, tipEdit);
2077
- else setTipEdit(draftsFor(row, true));
2067
+ onKeyDown: (event) => {
2068
+ if (event.key !== "Enter") return;
2069
+ event.preventDefault();
2070
+ saveTipEdit(row, tipEdit);
2071
+ }
2072
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2073
+ className: `${ModelPanel_module_css_default.tipValue} ${ModelPanel_module_css_default.tipVal}`,
2074
+ children: formatTokens(row.contextWindow)
2075
+ })]
2076
+ }),
2077
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2078
+ className: ModelPanel_module_css_default.tipCaps,
2079
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2080
+ className: ModelPanel_module_css_default.tipLabel,
2081
+ children: t("modelPanelMaxOutput")
2082
+ }), tipEdit.editing ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2083
+ type: "text",
2084
+ inputMode: "text",
2085
+ className: `${ModelPanel_module_css_default.tipInput} ${ModelPanel_module_css_default.tipVal}`,
2086
+ value: tipEdit.outputDraft,
2087
+ onChange: (event) => {
2088
+ const next = event.currentTarget.value;
2089
+ if (!DRAFT_PATTERN.test(next)) return;
2090
+ setTipEdit({
2091
+ ...tipEdit,
2092
+ outputDraft: clampOnInput(next, row.maxOutputTokensCap)
2093
+ });
2078
2094
  },
2079
- children: tipEdit.editing ? tipEdit.saving ? t("modelPanelSaving") : t("modelPanelSave") : t("modelPanelEdit")
2080
- })
2081
- ]
2082
- })
2083
- ]
2095
+ onKeyDown: (event) => {
2096
+ if (event.key !== "Enter") return;
2097
+ event.preventDefault();
2098
+ saveTipEdit(row, tipEdit);
2099
+ }
2100
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2101
+ className: `${ModelPanel_module_css_default.tipValue} ${ModelPanel_module_css_default.tipVal}`,
2102
+ children: formatTokens(row.maxOutputTokens)
2103
+ })]
2104
+ }),
2105
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2106
+ className: ModelPanel_module_css_default.tipActions,
2107
+ children: [
2108
+ tipEdit.editing && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2109
+ type: "button",
2110
+ className: ModelPanel_module_css_default.tipAction,
2111
+ disabled: tipEdit.saving,
2112
+ onClick: () => {
2113
+ restoreDefault(row);
2114
+ },
2115
+ children: t("modelPanelRestoreDefault")
2116
+ }),
2117
+ tipEdit.editing && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2118
+ type: "button",
2119
+ className: ModelPanel_module_css_default.tipAction,
2120
+ disabled: tipEdit.saving,
2121
+ onClick: () => {
2122
+ setTipEdit(draftsFor(row, false));
2123
+ },
2124
+ children: t("cancel")
2125
+ }),
2126
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2127
+ type: "button",
2128
+ className: ModelPanel_module_css_default.tipSave,
2129
+ disabled: tipEdit.saving,
2130
+ onClick: () => {
2131
+ if (tipEdit.editing) saveTipEdit(row, tipEdit);
2132
+ else setTipEdit(draftsFor(row, true));
2133
+ },
2134
+ children: tipEdit.editing ? tipEdit.saving ? t("modelPanelSaving") : t("modelPanelSave") : t("modelPanelEdit")
2135
+ })
2136
+ ]
2137
+ })
2138
+ ]
2139
+ })
2084
2140
  }), document.body);
2085
2141
  })()
2086
2142
  ]
@@ -2529,22 +2585,21 @@ window.__ModuleLoader__.load({
2529
2585
  //#endregion
2530
2586
  //#region src/client/store.ts
2531
2587
  /**
2532
- * CodeBuddy 页面 store:设置分区共享的账号快照。
2588
+ * CodeBuddy 页面 store:配置页共享的账号快照。
2533
2589
  *
2534
- * 客户端插件在设置页条目下挂载本句柄,因此在设置分区里完成的登录、退出或改
2590
+ * 客户端插件在配置页条目下挂载本句柄,因此在该页完成的登录、退出或改
2535
2591
  * 默认账号无需跨组件接线即可到达同一页面的各个徽章。
2536
2592
  *
2537
- * **界面偏好不在这里**:它们是 harness 用户设置文档里的一段(见 `../prefs.ts`),
2538
- * 由 `./index.ts` 经 `ctx.settingsScope.bind()` 读取,并以一个派生的活快照经
2539
- * 注入面的 `hooks` 隔舱同时下发给设置页与对话输入区的控件组 —— 后者处在另一个
2540
- * 作用域,共享不了本句柄。偏好此前存在 `localStorage` 并由输入区按 5 秒轮询
2541
- * 读取,导致开关与圆环之间有最多一个轮询周期的延迟。
2593
+ * **界面偏好不在这里**:它们是插件自己的 volatile 配置字段(见 `../prefs.ts`),
2594
+ * 由 `./index.ts` 经 `ctx.configForms.get()` 读取,并以一个派生的活快照经
2595
+ * 注入面的 `hooks` 隔舱同时下发给配置页与对话输入区的控件组 —— 后者处在另一个
2596
+ * 作用域,共享不了本句柄。
2542
2597
  *
2543
2598
  * @module dsh-llm-codebuddy-power/client/store
2544
2599
  */
2545
2600
  /**
2546
2601
  * 声明 CodeBuddy 页面状态与写入接口。
2547
- * @returns store 句柄(在 apply 中挂载于设置页条目下)。
2602
+ * @returns store 句柄(在 apply 中挂载于配置页条目下)。
2548
2603
  */
2549
2604
  function createCodeBuddyStore() {
2550
2605
  return (0, _deepseek_ai_dsh_client_store.defineStore)({
@@ -2570,8 +2625,16 @@ window.__ModuleLoader__.load({
2570
2625
  draft.phase = "error";
2571
2626
  draft.error = message;
2572
2627
  },
2628
+ clearError: (draft) => {
2629
+ draft.error = null;
2630
+ if (draft.phase === "error") draft.phase = "idle";
2631
+ },
2573
2632
  setLoginState: (draft, state) => {
2574
2633
  draft.loginState = state;
2634
+ },
2635
+ adoptLogin: (draft, state) => {
2636
+ if (draft.loginState !== null) return;
2637
+ draft.loginState = state;
2575
2638
  }
2576
2639
  }
2577
2640
  });
@@ -2583,14 +2646,27 @@ window.__ModuleLoader__.load({
2583
2646
  /**
2584
2647
  * 在共享通道上调用一个鉴权 endpoint。该通道调用按设计不做类型约束;
2585
2648
  * endpoint 所记录的 payload 结构在此唯一的 wire 边界处恢复。
2649
+ * @param rpc - 当前连接的 RPC 调用器。
2650
+ * @param endpoint - 通道下的 endpoint 名。
2651
+ * @param payload - endpoint 自己的请求载荷。
2652
+ * @param signal - 可选取消;中止后该请求的连接随即释放,而不是等宿主应答。
2653
+ * @returns endpoint 自己声明的结果。
2586
2654
  */
2587
- function authCall(rpc, endpoint, payload = {}) {
2588
- return rpc.call(AUTH_CHANNEL, endpoint, payload).then((result) => result);
2655
+ function authCall(rpc, endpoint, payload = {}, signal) {
2656
+ return rpc.call(AUTH_CHANNEL, endpoint, payload, signal).then((result) => result);
2589
2657
  }
2590
2658
  //#endregion
2591
2659
  //#region src/client/index.ts
2592
2660
  /** 本插件拥有的字典命名空间。 */
2593
2661
  const NS = "settings.codebuddy";
2662
+ /**
2663
+ * 本插件在 profile 里的包名,也是 Plugins 页派发配置的键。
2664
+ *
2665
+ * 该页以 `pkg.name` 派发 `plugins.bundle.config`(见 `PluginManagerPage` 的
2666
+ * `renderSlot(..., { entryKey: pkg.name })`),所以这里的值必须与 package.json 的
2667
+ * `name` 逐字一致;写错不会报错,只会让配置区静默地不出现。
2668
+ */
2669
+ const BUNDLE_NAME = "dsh-llm-codebuddy-power";
2594
2670
  /** 必需服务:插槽注册表、locale 服务、wire、共享模型目录服务(该面板读取
2595
2671
  * 与官方选择器相同的 store)、uiConversation(附件图片 URL),以及设置作用域
2596
2672
  * (界面偏好所在的命名空间)。
@@ -2607,7 +2683,7 @@ window.__ModuleLoader__.load({
2607
2683
  "remote",
2608
2684
  "remote.session",
2609
2685
  "uiConversation",
2610
- "settingsScope"
2686
+ "configForms"
2611
2687
  ];
2612
2688
  /**
2613
2689
  * 在插槽声明后注册 CodeBuddy 设置分区与对话内账号控件。
@@ -2621,10 +2697,10 @@ window.__ModuleLoader__.load({
2621
2697
  const rpc = ctx.get("connection").rpc;
2622
2698
  const store = createCodeBuddyStore();
2623
2699
  const t = ctx.locale.bind(NS);
2624
- const prefsScope = ctx.settingsScope.bind({ namespace: CODEBUDDY_SETTINGS_NAMESPACE });
2700
+ const prefsForm = ctx.configForms.get(CODEBUDDY_SETTINGS_NAMESPACE);
2625
2701
  const prefsStore = (0, _deepseek_ai_dsh_client_store.createSnapshotStore)(DEFAULT_CODEBUDDY_SETTINGS);
2626
2702
  const publishPrefs = () => {
2627
- const section = prefsScope.getSnapshot().value;
2703
+ const section = prefsForm.getSnapshot().value;
2628
2704
  if (section === void 0) return;
2629
2705
  const current = prefsStore.getSnapshot();
2630
2706
  if (current.showUsage === section.showUsage && current.dangerPct === section.dangerPct && current.drawToolInMinimal === section.drawToolInMinimal) return;
@@ -2635,15 +2711,15 @@ window.__ModuleLoader__.load({
2635
2711
  });
2636
2712
  };
2637
2713
  ctx.effect(() => {
2638
- const off = prefsScope.subscribe(publishPrefs);
2714
+ const off = prefsForm.subscribe(publishPrefs);
2639
2715
  publishPrefs();
2640
2716
  return off;
2641
2717
  }, "dsh-llm-codebuddy-power: settings prefs");
2642
2718
  /** 写入一个偏好字段;用户层覆盖部署级 base。 */
2643
2719
  const setPref = (field, value) => {
2644
- prefsScope.set(field, value);
2720
+ prefsForm.set(field, value);
2645
2721
  };
2646
- /** 读取账号集合并发布到设置页的 store。 */
2722
+ /** 读取账号集合并发布到配置页的 store。 */
2647
2723
  const refreshAccounts = async (actions) => {
2648
2724
  actions.start();
2649
2725
  try {
@@ -2671,6 +2747,7 @@ window.__ModuleLoader__.load({
2671
2747
  * @returns 复制模式下链接是否已进剪贴板;打开模式恒为 true。
2672
2748
  */
2673
2749
  const beginLogin = async (openBrowser) => {
2750
+ actions.clearError();
2674
2751
  const result = await authCall(rpc, "startLogin");
2675
2752
  if (!result.ok) {
2676
2753
  actions.fail(`${result.error.code}: ${result.error.message}`);
@@ -2683,12 +2760,32 @@ window.__ModuleLoader__.load({
2683
2760
  }
2684
2761
  actions.setLoginState({
2685
2762
  state: result.value.state,
2686
- startedAt: Date.now()
2763
+ startedAt: result.value.startedAt ?? Date.now()
2687
2764
  });
2688
2765
  return true;
2689
2766
  };
2690
- const pollLogin = async (state) => {
2691
- const result = await authCall(rpc, "pollLogin", { state });
2767
+ /**
2768
+ * 接回宿主仍在轮询的握手。浏览器刷新会丢掉本地的「登录中」状态,而宿主的
2769
+ * 握手还在等,不接回就会显示成可再次登录,用户点了便平白多出第二次握手。
2770
+ */
2771
+ const resumeLogin = async () => {
2772
+ const result = await authCall(rpc, "pendingLogin");
2773
+ if (!result.ok || result.value === void 0) return;
2774
+ actions.adoptLogin(result.value);
2775
+ };
2776
+ /**
2777
+ * 轮询一次握手的结果。
2778
+ *
2779
+ * 宿主侧立即作答(不等待后台握手),因此这里每次都是一次短请求;`signal` 用于在
2780
+ * 组件收尾时真正中止那次在飞的请求并释放连接 —— 只清定时器的话,已经发出的
2781
+ * 请求仍会挂在网络上,而浏览器的同源连接数有限,积多了会把后续所有到该源的
2782
+ * 请求(账号、用量、会话账号)一起挤成待处理。
2783
+ * @param state - 握手 id。
2784
+ * @param signal - 本次轮询的取消信号。
2785
+ * @returns 该握手是否已完成且账号已持久化。
2786
+ */
2787
+ const pollLogin = async (state, signal) => {
2788
+ const result = await authCall(rpc, "pollLogin", { state }, signal);
2692
2789
  if (!result.ok || !result.value.done) return false;
2693
2790
  actions.setLoginState(null);
2694
2791
  await refreshAccounts(actions);
@@ -2715,9 +2812,12 @@ window.__ModuleLoader__.load({
2715
2812
  startLogin: async () => {
2716
2813
  await beginLogin(true);
2717
2814
  },
2815
+ resumeLogin,
2718
2816
  copyLoginLink: () => beginLogin(false),
2719
- cancelLogin: async (state) => {
2720
- return (await authCall(rpc, "cancelLogin", { state })).ok;
2817
+ cancelLogin: async (state, signal) => {
2818
+ try {
2819
+ await authCall(rpc, "cancelLogin", { state }, signal);
2820
+ } catch {}
2721
2821
  },
2722
2822
  pollLogin,
2723
2823
  logoutAccounts,
@@ -2726,11 +2826,9 @@ window.__ModuleLoader__.load({
2726
2826
  setPref
2727
2827
  };
2728
2828
  };
2729
- ctx.slots.inject("settings.section", () => ctx.slots.register({
2730
- name: "settings.section",
2731
- id: "codebuddy",
2732
- order: 25,
2733
- label: () => t("nav"),
2829
+ ctx.slots.inject("plugins.bundle.config", () => ctx.slots.register({
2830
+ name: "plugins.bundle.config",
2831
+ key: BUNDLE_NAME,
2734
2832
  store,
2735
2833
  locale: NS,
2736
2834
  inject: injected