dsh-llm-codebuddy-power 1.0.0 → 1.3.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_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_badgeCheck)) .-h11cq_badgeInfo{padding-right:8px}.-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");
@@ -49,7 +36,6 @@ window.__ModuleLoader__.load({
49
36
  "badgeActionAnchor": "-h11cq_badgeActionAnchor",
50
37
  "badgeCheck": "-h11cq_badgeCheck",
51
38
  "badgeCheckOn": "-h11cq_badgeCheckOn",
52
- "badgeDefaultTag": "-h11cq_badgeDefaultTag",
53
39
  "badgeDragging": "-h11cq_badgeDragging",
54
40
  "badgeDropAfter": "-h11cq_badgeDropAfter",
55
41
  "badgeDropBefore": "-h11cq_badgeDropBefore",
@@ -81,16 +67,16 @@ window.__ModuleLoader__.load({
81
67
  * CodeBuddy 设置分区:多账号登录页。
82
68
  *
83
69
  * 展示全部已登录账号(每个账号一个徽章),在此发起浏览器登录以**追加**账号、
84
- * 退出单个或选中的多个账号、指定或取消默认账号;下方是两种登录状态下都渲染的
70
+ * 退出单个或选中的多个账号、拖拽排序;下方是两种登录状态下都渲染的
85
71
  * 界面偏好行。退出是破坏性操作(会删除磁盘上的凭据),因此单个与批量都先弹确认框。
86
72
  * 弹窗可见性由 shell 拥有,故此处不需要关闭控件。
87
73
  */
88
74
  /** 客户端轮询已发起登录的间隔(毫秒)。 */
89
75
  const POLL_INTERVAL_MS = 1500;
90
- /** 客户端放弃前持续轮询的时长(毫秒)。 */
76
+ /** 客户端等待登录完成的时长(毫秒),与宿主的 `LOGIN_TIMEOUT_MS` 对齐。 */
91
77
  const POLL_DEADLINE_MS = 600 * 1e3;
92
78
  /** 复制成功后图标保持对勾的时长(毫秒)。 */
93
- const COPIED_FEEDBACK_MS = 1e3;
79
+ const COPIED_FEEDBACK_MS$1 = 1e3;
94
80
  /**
95
81
  * 解码 CodeBuddy 的 `departmentFullName`,它是 base64 编码的 UTF-8。
96
82
  * 不是合法 base64 时回退为原始值。
@@ -134,24 +120,21 @@ window.__ModuleLoader__.load({
134
120
  return e.clientX < rect.left + rect.width / 2 ? "before" : "after";
135
121
  }
136
122
  /**
137
- * 一个账号徽章:整块信息区(昵称及其内边距)悬停看明细,默认账号另带一个「默认」标记。
123
+ * 一个账号徽章:整块信息区(昵称及其内边距)悬停看明细。
138
124
  *
139
125
  * 提示挂在整块信息区而不是只挂在昵称文字上:昵称常常很短,能触发提示的只有那几个
140
126
  * 字;把锚点扩到这块区域的整个高度与左侧留白,徽章上除功能按钮外的地方都能唤起它。
141
127
  * 不用"Tooltip 包住整个徽章、按钮上禁用"的写法 —— `Tooltip` 的 `disabled` 会直接
142
128
  * 清空它的悬停/聚焦状态,从昵称移到按钮再移回来时提示不会再出现。
143
129
  *
144
- * 徽章本身不承担任何管理动作:退出与默认账号的选择都在顶部操作区,徽章只回答
145
- * "这是哪个账号、它是不是默认的"。唯一可点的例外是那个「默认」标记 —— 它标出默认
146
- * 账号,自己也就是取消默认的入口。
130
+ * 徽章本身不承担任何管理动作:退出与排序都在顶部操作区,徽章只回答"这是哪个账号"。
147
131
  */
148
132
  function AccountBadge(props) {
149
- const { account, isDefault, multiSelect, checked, t, onClearDefault, onToggleChecked, drag } = props;
133
+ const { account, multiSelect, checked, t, onToggleChecked, drag } = props;
150
134
  const dragging = drag?.dragging ? ` ${CodeBuddySection_module_css_default.badgeDragging}` : "";
151
135
  const marker = drag?.marker === "before" ? ` ${CodeBuddySection_module_css_default.badgeDropBefore}` : drag?.marker === "after" ? ` ${CodeBuddySection_module_css_default.badgeDropAfter}` : "";
152
136
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
153
137
  className: `${CodeBuddySection_module_css_default.badge}${dragging}${marker}`,
154
- "data-default": isDefault ? "" : void 0,
155
138
  draggable: drag !== void 0,
156
139
  onDragStart: drag === void 0 ? void 0 : (e) => {
157
140
  e.dataTransfer.effectAllowed = "move";
@@ -168,43 +151,27 @@ window.__ModuleLoader__.load({
168
151
  e.preventDefault();
169
152
  drag.drop(badgeHalf(e));
170
153
  },
171
- children: [
172
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
173
- label: accountDetail(account, t),
174
- side: "top",
175
- delayMs: 300,
176
- maxWidth: 320,
154
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
155
+ label: accountDetail(account, t),
156
+ side: "top",
157
+ delayMs: 300,
158
+ maxWidth: 320,
159
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
160
+ className: CodeBuddySection_module_css_default.badgeInfo,
177
161
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
178
- className: CodeBuddySection_module_css_default.badgeInfo,
179
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
180
- className: CodeBuddySection_module_css_default.badgeInfoText,
181
- children: account.nickname
182
- })
162
+ className: CodeBuddySection_module_css_default.badgeInfoText,
163
+ children: account.nickname
183
164
  })
184
- }),
185
- isDefault && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
186
- label: t("clearDefault"),
187
- side: "top",
188
- delayMs: 300,
189
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
190
- type: "button",
191
- className: CodeBuddySection_module_css_default.badgeDefaultTag,
192
- "aria-pressed": true,
193
- "aria-label": `${t("clearDefault")}: ${account.nickname}`,
194
- onClick: onClearDefault,
195
- children: t("defaultTag")
196
- })
197
- }),
198
- multiSelect && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
199
- type: "button",
200
- role: "checkbox",
201
- "aria-checked": checked,
202
- "aria-label": `${t("selectAccount")}: ${account.nickname}`,
203
- className: `${CodeBuddySection_module_css_default.badgeCheck} ${checked ? CodeBuddySection_module_css_default.badgeCheckOn : ""}`,
204
- onClick: onToggleChecked,
205
- children: checked && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCheckOutline16, { size: 14 })
206
165
  })
207
- ]
166
+ }), multiSelect && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
167
+ type: "button",
168
+ role: "checkbox",
169
+ "aria-checked": checked,
170
+ "aria-label": `${t("selectAccount")}: ${account.nickname}`,
171
+ className: `${CodeBuddySection_module_css_default.badgeCheck} ${checked ? CodeBuddySection_module_css_default.badgeCheckOn : ""}`,
172
+ onClick: onToggleChecked,
173
+ children: checked && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCheckOutlineRegular, { size: 14 })
174
+ })]
208
175
  });
209
176
  }
210
177
  /**
@@ -213,11 +180,10 @@ window.__ModuleLoader__.load({
213
180
  * @returns 分区的元素树。
214
181
  */
215
182
  function CodeBuddySection(props) {
216
- const { t, useStore, usePrefs, refreshAccounts, startLogin, copyLoginLink, cancelLogin, pollLogin, logoutAccounts, reorderAccounts, setDefault, setPref, actions } = props;
183
+ const { t, useStore, usePrefs, refreshAccounts, startLogin, resumeLogin, copyLoginLink, cancelLogin, pollLogin, logoutAccounts, reorderAccounts, setPref, actions } = props;
217
184
  const phase = useStore((s) => s.phase);
218
185
  const error = useStore((s) => s.error);
219
186
  const accounts = useStore((s) => s.accounts);
220
- const defaultUid = useStore((s) => s.defaultUid);
221
187
  const loginState = useStore((s) => s.loginState);
222
188
  const prefs = usePrefs((value) => value);
223
189
  const [dangerText, setDangerText] = (0, react.useState)(() => String(prefs.dangerPct));
@@ -227,39 +193,54 @@ window.__ModuleLoader__.load({
227
193
  const [checked, setChecked] = (0, react.useState)([]);
228
194
  /** 待确认的退出目标;null 表示没有待确认的退出。 */
229
195
  const [pendingLogout, setPendingLogout] = (0, react.useState)(null);
230
- const halted = (0, react.useRef)(false);
231
- /** 卸载后为 true;登录轮询就此停止,而不是触碰状态。 */
232
- const isHalted = () => halted.current;
233
196
  (0, react.useEffect)(() => {
234
197
  refreshAccounts();
235
- }, [refreshAccounts]);
198
+ resumeLogin();
199
+ }, [refreshAccounts, resumeLogin]);
236
200
  (0, react.useEffect)(() => {
237
201
  if (loginState === null) return;
238
- halted.current = false;
239
- const { state, startedAt } = loginState;
240
- let timer;
202
+ const run = {
203
+ stopped: false,
204
+ abort: new AbortController()
205
+ };
206
+ /** 本次跑次是否已收尾(完成、超时、取消或卸载)。 */
207
+ const isStopped = () => run.stopped;
208
+ /**
209
+ * 收尾本次跑次:停掉轮询与截止定时器,并中止在飞的那次请求。
210
+ *
211
+ * 中止是必须的:宿主侧 `pollLogin` 现在立即作答,但**请求已经发出去**这件事
212
+ * 与定时器无关 —— 只清定时器,那一次请求仍占着连接,而浏览器对同一源的并发连接
213
+ * 数有限,积压后会把账号、用量等其余请求一起挤成待处理。
214
+ */
215
+ const stop = () => {
216
+ run.stopped = true;
217
+ if (run.deadline !== void 0) window.clearTimeout(run.deadline);
218
+ if (run.poll !== void 0) window.clearTimeout(run.poll);
219
+ run.abort.abort();
220
+ };
221
+ run.deadline = window.setTimeout(() => {
222
+ if (isStopped()) return;
223
+ stop();
224
+ actions.setLoginState(null);
225
+ actions.fail(t("timeout"));
226
+ }, Math.max(0, loginState.startedAt + POLL_DEADLINE_MS - Date.now()));
241
227
  const tick = async () => {
242
- if (isHalted()) return;
228
+ if (isStopped()) return;
243
229
  let done = false;
244
230
  try {
245
- done = await pollLogin(state);
231
+ done = await pollLogin(loginState.state, run.abort.signal);
246
232
  } catch {
247
233
  done = false;
248
234
  }
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"));
235
+ if (isStopped()) return;
236
+ if (done) {
237
+ stop();
254
238
  return;
255
239
  }
256
- timer = window.setTimeout(tick, POLL_INTERVAL_MS);
240
+ run.poll = window.setTimeout(tick, POLL_INTERVAL_MS);
257
241
  };
258
242
  tick();
259
- return () => {
260
- halted.current = true;
261
- if (timer !== void 0) window.clearTimeout(timer);
262
- };
243
+ return stop;
263
244
  }, [
264
245
  loginState,
265
246
  pollLogin,
@@ -270,16 +251,17 @@ window.__ModuleLoader__.load({
270
251
  startLogin();
271
252
  }, [startLogin]);
272
253
  /**
273
- * 放弃这次登录:先中止宿主的轮询,再清掉本地的握手状态。
254
+ * 放弃这次登录:先清掉本地的握手状态,再通知宿主中止。
274
255
  *
275
- * 顺序不能反 —— 先清状态的话,正在飞的那次 `pollLogin` 仍会按 `done: false` 排下一次
276
- * 轮询;而先中止,状态一清,effect 的 `loginState === null` 分支就不再重建轮询链。
256
+ * 顺序不能反 —— 取消对用户是即刻的,而 `cancelLogin` 是一次宿主调用,等它返回才
257
+ * 收回按钮会让取消显得没反应。先清状态是安全的:那一次状态变更会让上面的 effect
258
+ * 收尾,正在飞的那次 `pollLogin` 返回后就此停手,不会再排下一次轮询。
277
259
  */
278
260
  const onCancelLogin = (0, react.useCallback)(() => {
279
- if (loginState === null) return;
280
- cancelLogin(loginState.state).finally(() => {
281
- actions.setLoginState(null);
282
- });
261
+ const pending = loginState;
262
+ if (pending === null) return;
263
+ actions.setLoginState(null);
264
+ cancelLogin(pending.state);
283
265
  }, [
284
266
  loginState,
285
267
  cancelLogin,
@@ -298,13 +280,9 @@ window.__ModuleLoader__.load({
298
280
  copiedTimer.current = window.setTimeout(() => {
299
281
  copiedTimer.current = void 0;
300
282
  setCopied(false);
301
- }, COPIED_FEEDBACK_MS);
283
+ }, COPIED_FEEDBACK_MS$1);
302
284
  });
303
285
  }, [copied, copyLoginLink]);
304
- const [defaultMenuOpen, setDefaultMenuOpen] = (0, react.useState)(false);
305
- const onClearDefault = (0, react.useCallback)(() => {
306
- setDefault(null);
307
- }, [setDefault]);
308
286
  const onToggleChecked = (0, react.useCallback)((uid) => {
309
287
  setChecked((current) => current.includes(uid) ? current.filter((item) => item !== uid) : [...current, uid]);
310
288
  }, []);
@@ -348,10 +326,7 @@ window.__ModuleLoader__.load({
348
326
  })
349
327
  });
350
328
  const pendingCount = pendingLogout?.length ?? 0;
351
- const defaultItems = accounts.map((account) => ({
352
- id: account.uid,
353
- label: account.nickname
354
- }));
329
+ if (props.view === "summary") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: t("intro") });
355
330
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
356
331
  className: CodeBuddySection_module_css_default.section,
357
332
  children: [
@@ -390,37 +365,12 @@ window.__ModuleLoader__.load({
390
365
  size: "md",
391
366
  className: CodeBuddySection_module_css_default.splitAction,
392
367
  "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 }),
368
+ 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
369
  onClick: loginState === null ? onCopyLoginLink : onCancelLogin
395
370
  })
396
371
  })
397
372
  })]
398
373
  }),
399
- accounts.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Menu, {
400
- open: defaultMenuOpen,
401
- portal: true,
402
- align: "start",
403
- selectedId: defaultUid ?? void 0,
404
- items: defaultItems,
405
- onClose: () => {
406
- setDefaultMenuOpen(false);
407
- },
408
- onSelect: (uid) => {
409
- setDefaultMenuOpen(false);
410
- if (uid === defaultUid) return;
411
- setDefault(uid);
412
- },
413
- anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
414
- variant: "outline",
415
- size: "md",
416
- "aria-haspopup": "menu",
417
- "aria-expanded": defaultMenuOpen,
418
- onClick: () => {
419
- setDefaultMenuOpen((current) => !current);
420
- },
421
- children: t("chooseDefault")
422
- })
423
- }),
424
374
  accounts.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
425
375
  variant: "outline",
426
376
  size: "md",
@@ -445,7 +395,7 @@ window.__ModuleLoader__.load({
445
395
  onClick: () => {
446
396
  setPendingLogout([...checked]);
447
397
  },
448
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconTrashOutline16, { size: 14 })
398
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconTrashOutlineRegular, { size: 14 })
449
399
  })
450
400
  })
451
401
  })
@@ -458,11 +408,9 @@ window.__ModuleLoader__.load({
458
408
  className: CodeBuddySection_module_css_default.badges,
459
409
  children: accounts.map((account) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AccountBadge, {
460
410
  account,
461
- isDefault: account.uid === defaultUid,
462
411
  multiSelect,
463
412
  checked: checked.includes(account.uid),
464
413
  t,
465
- onClearDefault,
466
414
  onToggleChecked: () => {
467
415
  onToggleChecked(account.uid);
468
416
  },
@@ -524,6 +472,9 @@ window.__ModuleLoader__.load({
524
472
  * 三行界面偏好,单独抽出以便与账号区解耦 —— 它们与登录状态无关。两个布尔行是
525
473
  * 开关按钮(role="switch")而非下拉框:开关一次点击即可翻转,与文件浏览器的
526
474
  * 显示隐藏开关一致。
475
+ *
476
+ * 两个用量项相邻:余量告警百分比只对圆环的填充色起作用,把它隔在画图工具之后会让
477
+ * "圆环显示不显示"与"圆环几时变红"分成两处去找。
527
478
  */
528
479
  function UsagePrefRows(props) {
529
480
  const { t, prefs, dangerText, setDangerText, setPref } = props;
@@ -551,29 +502,6 @@ window.__ModuleLoader__.load({
551
502
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: CodeBuddySection_module_css_default.thumb })
552
503
  })]
553
504
  }),
554
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
555
- className: CodeBuddySection_module_css_default.prefRow,
556
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
557
- className: CodeBuddySection_module_css_default.prefRowText,
558
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
559
- className: CodeBuddySection_module_css_default.prefTitle,
560
- children: t("drawToolMinimal")
561
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
562
- className: CodeBuddySection_module_css_default.prefDesc,
563
- children: t("drawToolMinimalDesc")
564
- })]
565
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
566
- type: "button",
567
- role: "switch",
568
- "aria-checked": prefs.drawToolInMinimal,
569
- "aria-label": t("drawToolMinimal"),
570
- className: `${CodeBuddySection_module_css_default.switch} ${prefs.drawToolInMinimal ? CodeBuddySection_module_css_default.switchOn : ""}`,
571
- onClick: () => {
572
- setPref("drawToolInMinimal", !prefs.drawToolInMinimal);
573
- },
574
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: CodeBuddySection_module_css_default.thumb })
575
- })]
576
- }),
577
505
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
578
506
  className: CodeBuddySection_module_css_default.prefRow,
579
507
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -604,12 +532,35 @@ window.__ModuleLoader__.load({
604
532
  setPref("dangerPct", next);
605
533
  }
606
534
  })]
535
+ }),
536
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
537
+ className: CodeBuddySection_module_css_default.prefRow,
538
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
539
+ className: CodeBuddySection_module_css_default.prefRowText,
540
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
541
+ className: CodeBuddySection_module_css_default.prefTitle,
542
+ children: t("drawToolMinimal")
543
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
544
+ className: CodeBuddySection_module_css_default.prefDesc,
545
+ children: t("drawToolMinimalDesc")
546
+ })]
547
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
548
+ type: "button",
549
+ role: "switch",
550
+ "aria-checked": prefs.drawToolInMinimal,
551
+ "aria-label": t("drawToolMinimal"),
552
+ className: `${CodeBuddySection_module_css_default.switch} ${prefs.drawToolInMinimal ? CodeBuddySection_module_css_default.switchOn : ""}`,
553
+ onClick: () => {
554
+ setPref("drawToolInMinimal", !prefs.drawToolInMinimal);
555
+ },
556
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: CodeBuddySection_module_css_default.thumb })
557
+ })]
607
558
  })
608
559
  ] });
609
560
  }
610
561
  //#endregion
611
562
  //#region \0dsh-css:D:\data\code\deepseek-harness\packages\my\dsh-llm-codebuddy-power\src\client\AccountSwitcher.module.css.mjs
612
- const css$3 = "._7gEqNa_root{flex:none;align-items:center;gap:4px;display:inline-flex}._7gEqNa_ringAnchor{align-items:center;display:inline-flex}._7gEqNa_trigger{box-sizing:border-box;min-width:calc(28px + var(--dsh-content-font-delta,0px));height:calc(28px + var(--dsh-content-font-delta,0px));max-width:96px;color:var(--dsw-alias-label-secondary);white-space:nowrap;text-overflow:ellipsis;cursor:pointer;background:0 0;border:0;border-radius:6px;justify-content:center;align-items:center;padding:0 6px;font-size:12px;line-height:1;display:inline-flex;overflow:hidden}._7gEqNa_trigger:hover{background:var(--dsw-alias-interactive-bg-hover)}._7gEqNa_trigger:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}._7gEqNa_menuDefault{background:var(--dsw-alias-interactive-bg-hover-accent);color:var(--dsw-alias-label-secondary);border-radius:8px;margin-left:6px;padding:0 6px;font-size:11px;line-height:16px}";
563
+ const css$3 = "._7gEqNa_root{flex:none;align-items:center;gap:4px;display:inline-flex}._7gEqNa_ringAnchor{align-items:center;display:inline-flex}._7gEqNa_trigger{box-sizing:border-box;min-width:calc(28px + var(--dsh-content-font-delta,0px));height:calc(28px + var(--dsh-content-font-delta,0px));max-width:96px;color:var(--dsw-alias-label-secondary);white-space:nowrap;text-overflow:ellipsis;cursor:pointer;background:0 0;border:0;border-radius:6px;justify-content:center;align-items:center;padding:0 6px;font-size:12px;line-height:1;display:inline-flex;overflow:hidden}._7gEqNa_trigger:hover{background:var(--dsw-alias-interactive-bg-hover)}._7gEqNa_trigger:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}";
613
564
  const tagId$3 = "dsh-llm-codebuddy-power/AccountSwitcher.module.css";
614
565
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$3) + "]") === null) {
615
566
  const tag = document.createElement("style");
@@ -619,7 +570,6 @@ window.__ModuleLoader__.load({
619
570
  document.head.appendChild(tag);
620
571
  }
621
572
  var AccountSwitcher_module_css_default = {
622
- "menuDefault": "_7gEqNa_menuDefault",
623
573
  "ringAnchor": "_7gEqNa_ringAnchor",
624
574
  "root": "_7gEqNa_root",
625
575
  "trigger": "_7gEqNa_trigger"
@@ -676,6 +626,52 @@ window.__ModuleLoader__.load({
676
626
  });
677
627
  }
678
628
  /**
629
+ * 把剩余赠送额度压成圆环中心那点宽度放得下的写法(`999`、`1.23k`、`12.3k`、`123k`、`1.23m`)。
630
+ *
631
+ * 单位内保留三位有效数字,而不是只换算能整除的值:额度常是 12000、15000 这类数,
632
+ * 只换算整除的会让同一处时而 `12000` 时而 `1.2k`。
633
+ *
634
+ * 先按量级定档、再只取整一次:每档都取整会把误差累积起来,`1_234_567` 会在 `k` 档
635
+ * 先被写成 `1235`、再进位成 `1.24m`,而它本应是 `1.23m`。定档只看量级,取整只做一次。
636
+ *
637
+ * 取整仍可能把本档推到一千(`999_600` → `1000`),此时再进一档,否则会写出 `1000k`
638
+ * 与相邻的 `1m` 并存成两种记法。
639
+ * @param value - 剩余赠送额度,非负。
640
+ * @returns 最多三位有效数字、带 `k`/`m`/`g` 单位的紧凑写法。
641
+ */
642
+ function formatCompact(value) {
643
+ const units = [
644
+ "",
645
+ "k",
646
+ "m",
647
+ "g"
648
+ ];
649
+ /** 按三位有效数字取整:大于等于 100 取整,大于等于 10 保留一位,其余保留两位。 */
650
+ const round3 = (scaled) => {
651
+ const digits = scaled >= 100 ? 0 : scaled >= 10 ? 1 : 2;
652
+ const rounded = Math.round(scaled * 10 ** digits) / 10 ** digits;
653
+ return {
654
+ text: rounded.toLocaleString(void 0, {
655
+ maximumFractionDigits: digits,
656
+ useGrouping: false
657
+ }),
658
+ carries: rounded >= 1e3
659
+ };
660
+ };
661
+ let scaled = value;
662
+ let unit = 0;
663
+ while (scaled >= 1e3 && unit < units.length - 1) {
664
+ scaled /= 1e3;
665
+ unit += 1;
666
+ }
667
+ let result = round3(scaled);
668
+ if (result.carries && unit < units.length - 1) {
669
+ unit += 1;
670
+ result = round3(scaled / 1e3);
671
+ }
672
+ return `${result.text}${units[unit]}`;
673
+ }
674
+ /**
679
675
  * 构建圆环提示:合并后的已用/总量与百分比,其后是主池数值与它的重置时刻、赠送池,
680
676
  * 最后是最近到期的赠送窗口明细。
681
677
  *
@@ -702,7 +698,6 @@ window.__ModuleLoader__.load({
702
698
  function AccountSwitcher(props) {
703
699
  const { t, usePrefs, fetchAccounts, fetchSessionAccount, activate, fetchUsage } = props;
704
700
  const [accounts, setAccounts] = (0, react.useState)([]);
705
- const [defaultUid, setDefaultUid] = (0, react.useState)(null);
706
701
  const prefs = usePrefs((value) => value);
707
702
  /** 该会话此刻生效的账号 uid;未读到时为 null。 */
708
703
  const [activeUid, setActiveUid] = (0, react.useState)(null);
@@ -725,7 +720,6 @@ window.__ModuleLoader__.load({
725
720
  fetchAccounts().then((value) => {
726
721
  if (stopped || value === null) return;
727
722
  setAccounts(value.accounts);
728
- setDefaultUid(value.defaultUid);
729
723
  });
730
724
  fetchSessionAccount().then((uid) => {
731
725
  if (!stopped) setActiveUid(uid);
@@ -749,22 +743,21 @@ window.__ModuleLoader__.load({
749
743
  prefs.showUsage
750
744
  ]);
751
745
  if (accounts.length === 0) return null;
752
- const active = accounts.find((account) => account.uid === activeUid) ?? accounts.find((account) => account.uid === defaultUid) ?? accounts[0];
746
+ const active = accounts.find((account) => account.uid === activeUid) ?? accounts[0];
753
747
  if (active === void 0) return null;
754
748
  const label = `${t("switchAccount")}: ${active.nickname}`;
755
749
  const limit = (usage?.main?.limit ?? 0) + (usage?.gift?.limit ?? 0);
756
750
  const used = (usage?.main?.used ?? 0) + (usage?.gift?.used ?? 0);
757
751
  const pct = limit > 0 ? Math.min(Math.max(used / limit * 100, 0), 100) : void 0;
752
+ const giftLeft = Math.max((usage?.gift?.limit ?? 0) - (usage?.gift?.used ?? 0), 0);
753
+ const ringText = giftLeft > 0 ? formatCompact(giftLeft) : pct?.toFixed(2);
758
754
  const tip = usage !== null && pct !== void 0 ? ringTooltip(usage, {
759
755
  used,
760
756
  limit
761
757
  }, pct, t) : null;
762
758
  const items = accounts.map((account) => ({
763
759
  id: account.uid,
764
- label: account.uid === defaultUid ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [account.nickname, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
765
- className: AccountSwitcher_module_css_default.menuDefault,
766
- children: t("defaultTag")
767
- })] }) : account.nickname
760
+ label: account.nickname
768
761
  }));
769
762
  const size = RING_SIZE;
770
763
  const stroke = RING_STROKE;
@@ -813,7 +806,7 @@ window.__ModuleLoader__.load({
813
806
  fill: "var(--dsw-alias-label-primary)",
814
807
  fontSize: RING_FONT_SIZE,
815
808
  fontWeight: 600,
816
- children: pct.toFixed(2)
809
+ children: ringText
817
810
  })
818
811
  ]
819
812
  })
@@ -837,7 +830,6 @@ window.__ModuleLoader__.load({
837
830
  fetchAccounts().then((value) => {
838
831
  if (value === null) return;
839
832
  setAccounts(value.accounts);
840
- setDefaultUid(value.defaultUid);
841
833
  });
842
834
  return;
843
835
  }
@@ -916,7 +908,7 @@ window.__ModuleLoader__.load({
916
908
  }
917
909
  //#endregion
918
910
  //#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}}";
911
+ 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
912
  const tagId$2 = "dsh-llm-codebuddy-power/ModelPanel.module.css";
921
913
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$2) + "]") === null) {
922
914
  const tag = document.createElement("style");
@@ -965,6 +957,7 @@ window.__ModuleLoader__.load({
965
957
  "tipName": "pAq4ya_tipName",
966
958
  "tipPromo": "pAq4ya_tipPromo",
967
959
  "tipSave": "pAq4ya_tipSave",
960
+ "tipScroll": "pAq4ya_tipScroll",
968
961
  "tipSpend": "pAq4ya_tipSpend",
969
962
  "tipTier": "pAq4ya_tipTier",
970
963
  "tipVal": "pAq4ya_tipVal",
@@ -1735,7 +1728,7 @@ window.__ModuleLoader__.load({
1735
1728
  })]
1736
1729
  })
1737
1730
  }),
1738
- state.open && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1731
+ state.open && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.MenuSurface, {
1739
1732
  ref: panelRef,
1740
1733
  className: ModelPanel_module_css_default.panel,
1741
1734
  style: panelStyle,
@@ -1872,7 +1865,7 @@ window.__ModuleLoader__.load({
1872
1865
  const hasMidSection = row.credits !== void 0 && row.credits.length > 0 || promotionText !== void 0 || promotionAction !== void 0;
1873
1866
  const tipDiscounted = row.promotion?.discountedCredits;
1874
1867
  const tipStrikeOriginal = tipDiscounted !== void 0 && row.promotion?.displayMode !== "replace";
1875
- return (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1868
+ return (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.MenuSurface, {
1876
1869
  ref: tipRef,
1877
1870
  className: ModelPanel_module_css_default.tip,
1878
1871
  role: "tooltip",
@@ -1881,206 +1874,209 @@ window.__ModuleLoader__.load({
1881
1874
  top: tip.top,
1882
1875
  width: tip.width
1883
1876
  },
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);
1877
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1878
+ className: ModelPanel_module_css_default.tipScroll,
1879
+ children: [
1880
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1881
+ className: ModelPanel_module_css_default.tipName,
1882
+ children: row.name
1883
+ }),
1884
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1885
+ className: ModelPanel_module_css_default.tipDesc,
1886
+ children: description ?? t("modelPanelDescriptionMissing")
1887
+ }),
1888
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1889
+ className: ModelPanel_module_css_default.tipFeatures,
1890
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1891
+ className: ModelPanel_module_css_default.tipLabel,
1892
+ children: t("modelPanelFeatures")
1893
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1894
+ className: ModelPanel_module_css_default.tipVal,
1895
+ children: featureLabels(row, t)
1896
+ })]
1897
+ }),
1898
+ tierText !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1899
+ className: ModelPanel_module_css_default.tipTier,
1900
+ children: tierText
1901
+ }),
1902
+ tierAction !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1903
+ type: "button",
1904
+ className: ModelPanel_module_css_default.tipLink,
1905
+ onClick: (event) => {
1906
+ runAction(row, tierAction, shouldRestoreFocus(event));
1969
1907
  },
1970
- onClose: () => {
1971
- setEffortOpen(false);
1908
+ children: [t("modelPanelUpgrade"), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1909
+ className: ModelPanel_module_css_default.tipLinkChevron,
1910
+ "aria-hidden": "true",
1911
+ children: "›"
1912
+ })]
1913
+ }),
1914
+ hasMidSection && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: ModelPanel_module_css_default.tipDivider }),
1915
+ row.credits !== void 0 && row.credits.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1916
+ className: ModelPanel_module_css_default.tipSpend,
1917
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1918
+ className: ModelPanel_module_css_default.tipLabel,
1919
+ children: t("modelPanelSpendRate")
1920
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1921
+ className: ModelPanel_module_css_default.tipVal,
1922
+ children: [
1923
+ tipDiscounted === void 0 && row.credits,
1924
+ tipStrikeOriginal && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1925
+ className: ModelPanel_module_css_default.tipCreditsStruck,
1926
+ children: row.credits
1927
+ }),
1928
+ tipDiscounted !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1929
+ className: ModelPanel_module_css_default.tipDiscounted,
1930
+ style: row.promotion?.color === void 0 ? void 0 : { color: row.promotion.color },
1931
+ children: tipDiscounted
1932
+ })
1933
+ ]
1934
+ })]
1935
+ }),
1936
+ promotionText !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1937
+ className: ModelPanel_module_css_default.tipPromo,
1938
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: promotionText })
1939
+ }),
1940
+ (promotionAction !== void 0 || tip.source === "touch") && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1941
+ type: "button",
1942
+ className: ModelPanel_module_css_default.tipAction,
1943
+ onClick: (event) => {
1944
+ runAction(row, promotionAction, shouldRestoreFocus(event));
1972
1945
  },
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);
1946
+ children: t("modelPanelUse")
1947
+ }),
1948
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: ModelPanel_module_css_default.tipDivider }),
1949
+ efforts !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1950
+ className: ModelPanel_module_css_default.tipCaps,
1951
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1952
+ className: ModelPanel_module_css_default.tipLabel,
1953
+ children: t("modelPanelEffort")
1954
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Menu, {
1955
+ open: effortOpen,
1956
+ portal: true,
1957
+ dense: true,
1958
+ side: "bottom",
1959
+ align: "end",
1960
+ selectedId: selectedEffort === void 0 ? EFFORT_DEFAULT_ID : selectedEffort,
1961
+ items: effortItems,
1962
+ onSelect: (id) => {
1963
+ chooseEffort(row, id === EFFORT_DEFAULT_ID ? void 0 : id);
1981
1964
  },
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);
1965
+ onClose: () => {
1966
+ setEffortOpen(false);
2059
1967
  },
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));
1968
+ className: ModelPanel_module_css_default.tipEffortMenu,
1969
+ anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1970
+ type: "button",
1971
+ className: ModelPanel_module_css_default.tipEffort,
1972
+ "aria-haspopup": "menu",
1973
+ "aria-expanded": effortOpen,
1974
+ onClick: () => {
1975
+ setEffortOpen((open) => !open);
1976
+ },
1977
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1978
+ className: ModelPanel_module_css_default.tipEffortValue,
1979
+ children: effortLabel
1980
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1981
+ className: ModelPanel_module_css_default.tipEffortChevron,
1982
+ "aria-hidden": true,
1983
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutlineRegular, {})
1984
+ })]
1985
+ })
1986
+ })]
1987
+ }),
1988
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1989
+ className: ModelPanel_module_css_default.tipCaps,
1990
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1991
+ className: ModelPanel_module_css_default.tipLabel,
1992
+ children: t("modelPanelContextWindow")
1993
+ }), tipEdit.editing ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1994
+ type: "text",
1995
+ inputMode: "text",
1996
+ className: `${ModelPanel_module_css_default.tipInput} ${ModelPanel_module_css_default.tipVal}`,
1997
+ autoFocus: true,
1998
+ value: tipEdit.contextDraft,
1999
+ onChange: (event) => {
2000
+ const next = event.currentTarget.value;
2001
+ if (!DRAFT_PATTERN.test(next)) return;
2002
+ setTipEdit({
2003
+ ...tipEdit,
2004
+ contextDraft: clampOnInput(next, row.contextWindowCap)
2005
+ });
2068
2006
  },
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));
2007
+ onKeyDown: (event) => {
2008
+ if (event.key !== "Enter") return;
2009
+ event.preventDefault();
2010
+ saveTipEdit(row, tipEdit);
2011
+ }
2012
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2013
+ className: `${ModelPanel_module_css_default.tipValue} ${ModelPanel_module_css_default.tipVal}`,
2014
+ children: formatTokens(row.contextWindow)
2015
+ })]
2016
+ }),
2017
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2018
+ className: ModelPanel_module_css_default.tipCaps,
2019
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2020
+ className: ModelPanel_module_css_default.tipLabel,
2021
+ children: t("modelPanelMaxOutput")
2022
+ }), tipEdit.editing ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2023
+ type: "text",
2024
+ inputMode: "text",
2025
+ className: `${ModelPanel_module_css_default.tipInput} ${ModelPanel_module_css_default.tipVal}`,
2026
+ value: tipEdit.outputDraft,
2027
+ onChange: (event) => {
2028
+ const next = event.currentTarget.value;
2029
+ if (!DRAFT_PATTERN.test(next)) return;
2030
+ setTipEdit({
2031
+ ...tipEdit,
2032
+ outputDraft: clampOnInput(next, row.maxOutputTokensCap)
2033
+ });
2078
2034
  },
2079
- children: tipEdit.editing ? tipEdit.saving ? t("modelPanelSaving") : t("modelPanelSave") : t("modelPanelEdit")
2080
- })
2081
- ]
2082
- })
2083
- ]
2035
+ onKeyDown: (event) => {
2036
+ if (event.key !== "Enter") return;
2037
+ event.preventDefault();
2038
+ saveTipEdit(row, tipEdit);
2039
+ }
2040
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2041
+ className: `${ModelPanel_module_css_default.tipValue} ${ModelPanel_module_css_default.tipVal}`,
2042
+ children: formatTokens(row.maxOutputTokens)
2043
+ })]
2044
+ }),
2045
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2046
+ className: ModelPanel_module_css_default.tipActions,
2047
+ children: [
2048
+ tipEdit.editing && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2049
+ type: "button",
2050
+ className: ModelPanel_module_css_default.tipAction,
2051
+ disabled: tipEdit.saving,
2052
+ onClick: () => {
2053
+ restoreDefault(row);
2054
+ },
2055
+ children: t("modelPanelRestoreDefault")
2056
+ }),
2057
+ tipEdit.editing && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2058
+ type: "button",
2059
+ className: ModelPanel_module_css_default.tipAction,
2060
+ disabled: tipEdit.saving,
2061
+ onClick: () => {
2062
+ setTipEdit(draftsFor(row, false));
2063
+ },
2064
+ children: t("cancel")
2065
+ }),
2066
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2067
+ type: "button",
2068
+ className: ModelPanel_module_css_default.tipSave,
2069
+ disabled: tipEdit.saving,
2070
+ onClick: () => {
2071
+ if (tipEdit.editing) saveTipEdit(row, tipEdit);
2072
+ else setTipEdit(draftsFor(row, true));
2073
+ },
2074
+ children: tipEdit.editing ? tipEdit.saving ? t("modelPanelSaving") : t("modelPanelSave") : t("modelPanelEdit")
2075
+ })
2076
+ ]
2077
+ })
2078
+ ]
2079
+ })
2084
2080
  }), document.body);
2085
2081
  })()
2086
2082
  ]
@@ -2300,12 +2296,17 @@ window.__ModuleLoader__.load({
2300
2296
  * 则渲染,无记录(非 CodeBuddy 轮)渲染为空。与官方 `TurnUsagePanel` 同构:
2301
2297
  * 图标 + 文字标签,点击无详情面板(仅展示一条合计)。
2302
2298
  *
2303
- * 悬停提示附上本轮用过的账号:账号在扣分那一刻随记录写入(见 host 的
2304
- * `recordCredit`),不是悬停时现查 —— 一轮里用户可以切换账号,现查会把整轮
2305
- * 说成最后那个账号。
2299
+ * 悬停提示附上本轮用过的账号与本轮请求 id:两者都在扣分那一刻随记录写入(见 host 的
2300
+ * `recordCredit`),不是悬停时现查 —— 一轮里用户可以切换账号,而请求 id 每轮重铸,
2301
+ * 现查会把整轮说成最后那个账号、或报出当前轮的 id。有 id 时点击 pill 复制它的完整值,
2302
+ * 因为显示在提示里的那个值就是用户要拿去对账的东西。
2306
2303
  *
2307
2304
  * @module dsh-llm-codebuddy-power/client/TurnCreditPill
2308
2305
  */
2306
+ /** 复制成功后 pill 文字保持「已复制」的时长(毫秒),与配置页的复制反馈一致。 */
2307
+ const COPIED_FEEDBACK_MS = 1e3;
2308
+ /** 提示里显示的请求 id 前几位;完整值由点击复制,不必全写在提示里。 */
2309
+ const TURN_REQUEST_ID_DISPLAY_CHARS = 6;
2309
2310
  const pillStyle = {
2310
2311
  display: "inline-flex",
2311
2312
  alignItems: "center",
@@ -2320,13 +2321,29 @@ window.__ModuleLoader__.load({
2320
2321
  whiteSpace: "nowrap"
2321
2322
  };
2322
2323
  /**
2324
+ * 可点击时在 pill 样式上补上按钮语义:指针与键盘反馈,并让字族跟随环境。
2325
+ *
2326
+ * 只覆盖 `fontFamily` 而不是 `font` 简写:后者会把 `pillStyle` 的 `fontSize` 一并重置为
2327
+ * 继承值,同一个 pill 在可点击与不可点击两种形态下就会是两个字号。
2328
+ */
2329
+ const copyableStyle = {
2330
+ ...pillStyle,
2331
+ fontFamily: "inherit",
2332
+ cursor: "pointer"
2333
+ };
2334
+ /**
2323
2335
  * 每轮积分 pill。messageId 反查轮号后查该轮记录;无记录渲染为空(未用
2324
2336
  * CodeBuddy 模型即一轮 0 分也渲染,有记录即证明用了 CodeBuddy)。只显示
2325
- * 积分,模型由 dsh 用量自带;悬停给出本轮用过的账号。
2337
+ * 积分,模型由 dsh 用量自带;悬停给出本轮用过的账号与本轮请求 id。
2326
2338
  */
2327
2339
  function TurnCreditPill(props) {
2328
2340
  const { messageId, useChat, sessionId, creditOf, t } = props;
2329
2341
  const [record, setRecord] = (0, react.useState)(void 0);
2342
+ const [copied, setCopied] = (0, react.useState)(false);
2343
+ const copiedTimer = (0, react.useRef)(void 0);
2344
+ (0, react.useEffect)(() => () => {
2345
+ if (copiedTimer.current !== void 0) window.clearTimeout(copiedTimer.current);
2346
+ }, []);
2330
2347
  const nodes = useChat((snapshot) => snapshot.legacy.nodes);
2331
2348
  const turn = (0, react.useMemo)(() => {
2332
2349
  for (const node of nodes) {
@@ -2355,21 +2372,50 @@ window.__ModuleLoader__.load({
2355
2372
  turn
2356
2373
  ]);
2357
2374
  if (record === void 0) return null;
2375
+ const requestId = record.requestId;
2358
2376
  const creditText = (Math.round(record.credit * 100) / 100).toLocaleString(void 0, {
2359
2377
  maximumFractionDigits: 2,
2360
2378
  useGrouping: false
2361
2379
  });
2362
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
2363
- label: [t("turnCreditTip"), ...record.accounts.map((account) => t("turnCreditAccount", {
2364
- nickname: account.nickname,
2365
- uid: account.uidPrefix
2366
- }))].join("\n"),
2380
+ const tipLines = [t("turnCreditTip"), ...record.accounts.map((account) => t("turnCreditAccount", {
2381
+ nickname: account.nickname,
2382
+ uid: account.uidPrefix
2383
+ }))];
2384
+ if (requestId !== void 0) {
2385
+ tipLines.push(t("turnCreditRequestId", { requestId: requestId.slice(0, TURN_REQUEST_ID_DISPLAY_CHARS) }));
2386
+ tipLines.push(copied ? t("turnCreditCopiedTip") : t("turnCreditCopyTip"));
2387
+ }
2388
+ const tip = tipLines.join("\n");
2389
+ const onCopy = () => {
2390
+ if (requestId === void 0 || copied) return;
2391
+ (0, _deepseek_ai_dsh_client_ui_primitives.writeClipboard)(requestId).then((ok) => {
2392
+ if (!ok) return;
2393
+ setCopied(true);
2394
+ copiedTimer.current = window.setTimeout(() => {
2395
+ copiedTimer.current = void 0;
2396
+ setCopied(false);
2397
+ }, COPIED_FEEDBACK_MS);
2398
+ });
2399
+ };
2400
+ if (requestId === void 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
2401
+ label: tip,
2367
2402
  side: "bottom",
2368
2403
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2369
2404
  style: pillStyle,
2370
2405
  children: t("turnCredit", { credit: creditText })
2371
2406
  })
2372
2407
  });
2408
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
2409
+ label: tip,
2410
+ side: "bottom",
2411
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2412
+ type: "button",
2413
+ style: copyableStyle,
2414
+ "aria-label": t("turnCreditCopy"),
2415
+ onClick: onCopy,
2416
+ children: copied ? t("turnCreditCopied") : t("turnCredit", { credit: creditText })
2417
+ })
2418
+ });
2373
2419
  }
2374
2420
  //#endregion
2375
2421
  //#region src/client/locales.ts
@@ -2398,9 +2444,6 @@ window.__ModuleLoader__.load({
2398
2444
  logoutSelectedDesc: "Signing out removes the stored credentials; the {count} selected accounts must sign in again to be used.",
2399
2445
  logoutConfirm: "Sign out",
2400
2446
  cancel: "Cancel",
2401
- chooseDefault: "Choose default",
2402
- clearDefault: "Remove default",
2403
- defaultTag: "Default",
2404
2447
  nickname: "Nickname",
2405
2448
  uid: "UID",
2406
2449
  loginExpires: "Sign-in expires",
@@ -2430,6 +2473,11 @@ window.__ModuleLoader__.load({
2430
2473
  turnCredit: "{credit} credits",
2431
2474
  turnCreditTip: "CodeBuddy credits spent this turn",
2432
2475
  turnCreditAccount: "Account: {nickname} ({uid}…)",
2476
+ turnCreditRequestId: "Request ID: {requestId}…",
2477
+ turnCreditCopy: "Copy this turn’s request ID",
2478
+ turnCreditCopyTip: "Click to copy this turn’s request ID",
2479
+ turnCreditCopied: "Copied",
2480
+ turnCreditCopiedTip: "Request ID copied",
2433
2481
  modelPanelTrigger: "CodeBuddy models",
2434
2482
  modelPanelTitle: "CodeBuddy models",
2435
2483
  modelPanelEmpty: "No CodeBuddy models available.",
@@ -2474,9 +2522,6 @@ window.__ModuleLoader__.load({
2474
2522
  logoutSelectedDesc: "退出会删除已存凭据,选中的 {count} 个账号需要重新登录才能继续使用。",
2475
2523
  logoutConfirm: "退出",
2476
2524
  cancel: "取消",
2477
- chooseDefault: "选择默认",
2478
- clearDefault: "取消默认",
2479
- defaultTag: "默认",
2480
2525
  nickname: "昵称",
2481
2526
  uid: "UID",
2482
2527
  loginExpires: "登录到期",
@@ -2506,6 +2551,11 @@ window.__ModuleLoader__.load({
2506
2551
  turnCredit: "{credit} 分",
2507
2552
  turnCreditTip: "本轮消耗的 CodeBuddy 积分",
2508
2553
  turnCreditAccount: "账号:{nickname}({uid}…)",
2554
+ turnCreditRequestId: "请求 ID:{requestId}…",
2555
+ turnCreditCopy: "复制本轮请求 ID",
2556
+ turnCreditCopyTip: "点击复制本轮请求 ID",
2557
+ turnCreditCopied: "已复制",
2558
+ turnCreditCopiedTip: "请求 ID 已复制",
2509
2559
  modelPanelTrigger: "CodeBuddy 模型",
2510
2560
  modelPanelTitle: "CodeBuddy 模型",
2511
2561
  modelPanelEmpty: "暂无可用 CodeBuddy 模型。",
@@ -2529,22 +2579,21 @@ window.__ModuleLoader__.load({
2529
2579
  //#endregion
2530
2580
  //#region src/client/store.ts
2531
2581
  /**
2532
- * CodeBuddy 页面 store:设置分区共享的账号快照。
2582
+ * CodeBuddy 页面 store:配置页共享的账号快照。
2533
2583
  *
2534
- * 客户端插件在设置页条目下挂载本句柄,因此在设置分区里完成的登录、退出或改
2535
- * 默认账号无需跨组件接线即可到达同一页面的各个徽章。
2584
+ * 客户端插件在配置页条目下挂载本句柄,因此在该页完成的登录、退出或排序
2585
+ * 无需跨组件接线即可到达同一页面的各个徽章。
2536
2586
  *
2537
- * **界面偏好不在这里**:它们是 harness 用户设置文档里的一段(见 `../prefs.ts`),
2538
- * 由 `./index.ts` 经 `ctx.settingsScope.bind()` 读取,并以一个派生的活快照经
2539
- * 注入面的 `hooks` 隔舱同时下发给设置页与对话输入区的控件组 —— 后者处在另一个
2540
- * 作用域,共享不了本句柄。偏好此前存在 `localStorage` 并由输入区按 5 秒轮询
2541
- * 读取,导致开关与圆环之间有最多一个轮询周期的延迟。
2587
+ * **界面偏好不在这里**:它们是插件自己的 volatile 配置字段(见 `../prefs.ts`),
2588
+ * 由 `./index.ts` 经 `ctx.configForms.get()` 读取,并以一个派生的活快照经
2589
+ * 注入面的 `hooks` 隔舱同时下发给配置页与对话输入区的控件组 —— 后者处在另一个
2590
+ * 作用域,共享不了本句柄。
2542
2591
  *
2543
2592
  * @module dsh-llm-codebuddy-power/client/store
2544
2593
  */
2545
2594
  /**
2546
2595
  * 声明 CodeBuddy 页面状态与写入接口。
2547
- * @returns store 句柄(在 apply 中挂载于设置页条目下)。
2596
+ * @returns store 句柄(在 apply 中挂载于配置页条目下)。
2548
2597
  */
2549
2598
  function createCodeBuddyStore() {
2550
2599
  return (0, _deepseek_ai_dsh_client_store.defineStore)({
@@ -2552,7 +2601,6 @@ window.__ModuleLoader__.load({
2552
2601
  phase: "loading",
2553
2602
  error: null,
2554
2603
  accounts: [],
2555
- defaultUid: null,
2556
2604
  loginState: null
2557
2605
  }),
2558
2606
  actions: {
@@ -2564,14 +2612,21 @@ window.__ModuleLoader__.load({
2564
2612
  draft.phase = "idle";
2565
2613
  draft.error = null;
2566
2614
  draft.accounts = result.accounts;
2567
- draft.defaultUid = result.defaultUid ?? null;
2568
2615
  },
2569
2616
  fail: (draft, message) => {
2570
2617
  draft.phase = "error";
2571
2618
  draft.error = message;
2572
2619
  },
2620
+ clearError: (draft) => {
2621
+ draft.error = null;
2622
+ if (draft.phase === "error") draft.phase = "idle";
2623
+ },
2573
2624
  setLoginState: (draft, state) => {
2574
2625
  draft.loginState = state;
2626
+ },
2627
+ adoptLogin: (draft, state) => {
2628
+ if (draft.loginState !== null) return;
2629
+ draft.loginState = state;
2575
2630
  }
2576
2631
  }
2577
2632
  });
@@ -2583,14 +2638,27 @@ window.__ModuleLoader__.load({
2583
2638
  /**
2584
2639
  * 在共享通道上调用一个鉴权 endpoint。该通道调用按设计不做类型约束;
2585
2640
  * endpoint 所记录的 payload 结构在此唯一的 wire 边界处恢复。
2641
+ * @param rpc - 当前连接的 RPC 调用器。
2642
+ * @param endpoint - 通道下的 endpoint 名。
2643
+ * @param payload - endpoint 自己的请求载荷。
2644
+ * @param signal - 可选取消;中止后该请求的连接随即释放,而不是等宿主应答。
2645
+ * @returns endpoint 自己声明的结果。
2586
2646
  */
2587
- function authCall(rpc, endpoint, payload = {}) {
2588
- return rpc.call(AUTH_CHANNEL, endpoint, payload).then((result) => result);
2647
+ function authCall(rpc, endpoint, payload = {}, signal) {
2648
+ return rpc.call(AUTH_CHANNEL, endpoint, payload, signal).then((result) => result);
2589
2649
  }
2590
2650
  //#endregion
2591
2651
  //#region src/client/index.ts
2592
2652
  /** 本插件拥有的字典命名空间。 */
2593
2653
  const NS = "settings.codebuddy";
2654
+ /**
2655
+ * 本插件在 profile 里的包名,也是 Plugins 页派发配置的键。
2656
+ *
2657
+ * 该页以 `pkg.name` 派发 `plugins.bundle.config`(见 `PluginManagerPage` 的
2658
+ * `renderSlot(..., { entryKey: pkg.name })`),所以这里的值必须与 package.json 的
2659
+ * `name` 逐字一致;写错不会报错,只会让配置区静默地不出现。
2660
+ */
2661
+ const BUNDLE_NAME = "dsh-llm-codebuddy-power";
2594
2662
  /** 必需服务:插槽注册表、locale 服务、wire、共享模型目录服务(该面板读取
2595
2663
  * 与官方选择器相同的 store)、uiConversation(附件图片 URL),以及设置作用域
2596
2664
  * (界面偏好所在的命名空间)。
@@ -2607,7 +2675,7 @@ window.__ModuleLoader__.load({
2607
2675
  "remote",
2608
2676
  "remote.session",
2609
2677
  "uiConversation",
2610
- "settingsScope"
2678
+ "configForms"
2611
2679
  ];
2612
2680
  /**
2613
2681
  * 在插槽声明后注册 CodeBuddy 设置分区与对话内账号控件。
@@ -2621,10 +2689,10 @@ window.__ModuleLoader__.load({
2621
2689
  const rpc = ctx.get("connection").rpc;
2622
2690
  const store = createCodeBuddyStore();
2623
2691
  const t = ctx.locale.bind(NS);
2624
- const prefsScope = ctx.settingsScope.bind({ namespace: CODEBUDDY_SETTINGS_NAMESPACE });
2692
+ const prefsForm = ctx.configForms.get(CODEBUDDY_SETTINGS_NAMESPACE);
2625
2693
  const prefsStore = (0, _deepseek_ai_dsh_client_store.createSnapshotStore)(DEFAULT_CODEBUDDY_SETTINGS);
2626
2694
  const publishPrefs = () => {
2627
- const section = prefsScope.getSnapshot().value;
2695
+ const section = prefsForm.getSnapshot().value;
2628
2696
  if (section === void 0) return;
2629
2697
  const current = prefsStore.getSnapshot();
2630
2698
  if (current.showUsage === section.showUsage && current.dangerPct === section.dangerPct && current.drawToolInMinimal === section.drawToolInMinimal) return;
@@ -2635,15 +2703,15 @@ window.__ModuleLoader__.load({
2635
2703
  });
2636
2704
  };
2637
2705
  ctx.effect(() => {
2638
- const off = prefsScope.subscribe(publishPrefs);
2706
+ const off = prefsForm.subscribe(publishPrefs);
2639
2707
  publishPrefs();
2640
2708
  return off;
2641
2709
  }, "dsh-llm-codebuddy-power: settings prefs");
2642
2710
  /** 写入一个偏好字段;用户层覆盖部署级 base。 */
2643
2711
  const setPref = (field, value) => {
2644
- prefsScope.set(field, value);
2712
+ prefsForm.set(field, value);
2645
2713
  };
2646
- /** 读取账号集合并发布到设置页的 store。 */
2714
+ /** 读取账号集合并发布到配置页的 store。 */
2647
2715
  const refreshAccounts = async (actions) => {
2648
2716
  actions.start();
2649
2717
  try {
@@ -2671,6 +2739,7 @@ window.__ModuleLoader__.load({
2671
2739
  * @returns 复制模式下链接是否已进剪贴板;打开模式恒为 true。
2672
2740
  */
2673
2741
  const beginLogin = async (openBrowser) => {
2742
+ actions.clearError();
2674
2743
  const result = await authCall(rpc, "startLogin");
2675
2744
  if (!result.ok) {
2676
2745
  actions.fail(`${result.error.code}: ${result.error.message}`);
@@ -2683,12 +2752,32 @@ window.__ModuleLoader__.load({
2683
2752
  }
2684
2753
  actions.setLoginState({
2685
2754
  state: result.value.state,
2686
- startedAt: Date.now()
2755
+ startedAt: result.value.startedAt ?? Date.now()
2687
2756
  });
2688
2757
  return true;
2689
2758
  };
2690
- const pollLogin = async (state) => {
2691
- const result = await authCall(rpc, "pollLogin", { state });
2759
+ /**
2760
+ * 接回宿主仍在轮询的握手。浏览器刷新会丢掉本地的「登录中」状态,而宿主的
2761
+ * 握手还在等,不接回就会显示成可再次登录,用户点了便平白多出第二次握手。
2762
+ */
2763
+ const resumeLogin = async () => {
2764
+ const result = await authCall(rpc, "pendingLogin");
2765
+ if (!result.ok || result.value === void 0) return;
2766
+ actions.adoptLogin(result.value);
2767
+ };
2768
+ /**
2769
+ * 轮询一次握手的结果。
2770
+ *
2771
+ * 宿主侧立即作答(不等待后台握手),因此这里每次都是一次短请求;`signal` 用于在
2772
+ * 组件收尾时真正中止那次在飞的请求并释放连接 —— 只清定时器的话,已经发出的
2773
+ * 请求仍会挂在网络上,而浏览器的同源连接数有限,积多了会把后续所有到该源的
2774
+ * 请求(账号、用量、会话账号)一起挤成待处理。
2775
+ * @param state - 握手 id。
2776
+ * @param signal - 本次轮询的取消信号。
2777
+ * @returns 该握手是否已完成且账号已持久化。
2778
+ */
2779
+ const pollLogin = async (state, signal) => {
2780
+ const result = await authCall(rpc, "pollLogin", { state }, signal);
2692
2781
  if (!result.ok || !result.value.done) return false;
2693
2782
  actions.setLoginState(null);
2694
2783
  await refreshAccounts(actions);
@@ -2704,33 +2793,28 @@ window.__ModuleLoader__.load({
2704
2793
  if (result.ok) await refreshAccounts(actions);
2705
2794
  else actions.fail(`${result.error.code}: ${result.error.message}`);
2706
2795
  };
2707
- const setDefault = async (uid) => {
2708
- const result = await authCall(rpc, "setDefault", { uid: uid ?? "" });
2709
- if (result.ok) await refreshAccounts(actions);
2710
- else actions.fail(`${result.error.code}: ${result.error.message}`);
2711
- };
2712
2796
  return {
2713
2797
  hooks: { prefs: prefsStore },
2714
2798
  refreshAccounts: () => refreshAccounts(actions),
2715
2799
  startLogin: async () => {
2716
2800
  await beginLogin(true);
2717
2801
  },
2802
+ resumeLogin,
2718
2803
  copyLoginLink: () => beginLogin(false),
2719
- cancelLogin: async (state) => {
2720
- return (await authCall(rpc, "cancelLogin", { state })).ok;
2804
+ cancelLogin: async (state, signal) => {
2805
+ try {
2806
+ await authCall(rpc, "cancelLogin", { state }, signal);
2807
+ } catch {}
2721
2808
  },
2722
2809
  pollLogin,
2723
2810
  logoutAccounts,
2724
2811
  reorderAccounts,
2725
- setDefault,
2726
2812
  setPref
2727
2813
  };
2728
2814
  };
2729
- ctx.slots.inject("settings.section", () => ctx.slots.register({
2730
- name: "settings.section",
2731
- id: "codebuddy",
2732
- order: 25,
2733
- label: () => t("nav"),
2815
+ ctx.slots.inject("plugins.bundle.config", () => ctx.slots.register({
2816
+ name: "plugins.bundle.config",
2817
+ key: BUNDLE_NAME,
2734
2818
  store,
2735
2819
  locale: NS,
2736
2820
  inject: injected
@@ -2747,10 +2831,7 @@ window.__ModuleLoader__.load({
2747
2831
  fetchAccounts: async () => {
2748
2832
  const result = await authCall(rpc, "accounts");
2749
2833
  if (!result.ok) return null;
2750
- return {
2751
- accounts: result.value.accounts,
2752
- defaultUid: result.value.defaultUid ?? null
2753
- };
2834
+ return { accounts: result.value.accounts };
2754
2835
  },
2755
2836
  fetchSessionAccount: async () => {
2756
2837
  const result = await authCall(rpc, "sessionAccount", { sessionId });