@shgroup/dsh-serenity-hooks 1.21.0 → 1.23.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
@@ -7,266 +7,12 @@ window.__ModuleLoader__.load({
7
7
  let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
8
8
  let react = require("react");
9
9
  let react_jsx_runtime = require("react/jsx-runtime");
10
- //#region src/client/accounts-api.ts
11
- /** wire 账号 → 本地编辑行 */
12
- function accountDraftFromWire(a) {
13
- return {
14
- id: a.id,
15
- user: a.user,
16
- pass: "",
17
- isNew: false,
18
- hasPassword: a.hasPassword
19
- };
20
- }
21
- /** 生成新账号 id(本地编辑用;提交时若为空由服务端拒绝) */
22
- function newAccountId() {
23
- return `a-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 6)}`;
24
- }
25
- /** 本地编辑行 → wire 账号(提交时;pass 字段携带) */
26
- function accountToWire(d) {
27
- return {
28
- id: d.id,
29
- user: d.user,
30
- pass: d.pass
31
- };
32
- }
33
- /** 校验本地编辑行:user 非空;新账号必须设密码 */
34
- function validateDraft(d) {
35
- if (d.user.trim() === "") return "用户名不能为空";
36
- if (d.isNew && d.pass === "") return "新账号必须设置密码";
37
- return null;
38
- }
39
- const CONFIG_PATH = "/serenity/config";
40
- /** GET 配置(含账号列表;无 hash) */
41
- async function fetchConfig(sessionId) {
42
- const qs = sessionId ? `?sessionId=${encodeURIComponent(sessionId)}` : "";
43
- const res = await fetch(`${CONFIG_PATH}${qs}`, { headers: {
44
- accept: "application/json",
45
- "x-serenity-ui": "1"
46
- } });
47
- if (!res.ok) return null;
48
- return (await res.json()).config ?? null;
49
- }
50
- /** PUT 配置(账号 patch)→ 返回保存后的 wire */
51
- async function saveConfig(sessionId, patch) {
52
- const res = await fetch(CONFIG_PATH, {
53
- method: "PUT",
54
- headers: {
55
- "content-type": "application/json",
56
- "x-serenity-ui": "1"
57
- },
58
- body: JSON.stringify({
59
- sessionId,
60
- config: patch
61
- })
62
- });
63
- if (!res.ok) {
64
- const body = await res.json().catch(() => null);
65
- throw new Error(body?.error ?? `保存失败(${res.status})`);
66
- }
67
- return (await res.json()).config ?? null;
68
- }
69
- //#endregion
70
- //#region \0sp-css:/home/yh/home/home-serenity/AI_LAB/dsh-serenity-plugin/hooks/dsh-serenity-hooks/src/client/AccountsTab
71
- const css$2 = "/* AccountsTab — 高级面板账号 tab(v1.21;--dsw-alias-* 语义 token,明暗自适应) */\n\n.at-root {\n display: flex;\n flex-direction: column;\n gap: 8px;\n padding: 6px 2px;\n font-size: 13px;\n}\n\n.at-note {\n margin: 0;\n font-size: 12px;\n opacity: 0.65;\n}\n\n.at-row {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.at-label {\n font-weight: 600;\n white-space: nowrap;\n}\n\n.at-input {\n flex: 1;\n min-width: 0;\n padding: 5px 8px;\n border: 1px solid var(--dsw-alias-border, #333);\n border-radius: 6px;\n background: var(--dsw-alias-field, #222);\n color: var(--dsw-alias-text, #eee);\n font-size: 13px;\n}\n\n.at-input.at-port {\n flex: none;\n width: 72px;\n}\n\n.at-list {\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n.at-head {\n display: flex;\n gap: 8px;\n font-size: 12px;\n opacity: 0.6;\n padding: 0 2px;\n}\n\n.at-item {\n display: flex;\n gap: 8px;\n align-items: center;\n}\n\n.at-colUser { flex: 1.2; }\n.at-colPass { flex: 1.6; }\n.at-colOp { flex: none; width: 48px; }\n\n.at-del {\n flex: none;\n padding: 4px 10px;\n border: 1px solid var(--dsw-alias-border, #444);\n border-radius: 6px;\n background: transparent;\n color: var(--dsw-alias-danger, #f87171);\n font-size: 12px;\n cursor: pointer;\n}\n\n.at-add {\n align-self: flex-start;\n padding: 5px 12px;\n border: 1px dashed var(--dsw-alias-border, #444);\n border-radius: 6px;\n background: transparent;\n color: var(--dsw-alias-text, #ccc);\n font-size: 12px;\n cursor: pointer;\n}\n\n.at-error {\n margin: 0;\n font-size: 12px;\n color: var(--dsw-alias-danger, #f87171);\n}\n\n.at-actions {\n display: flex;\n justify-content: flex-end;\n padding-top: 4px;\n}\n\n.at-save {\n padding: 6px 22px;\n border: 0;\n border-radius: 6px;\n background: var(--dsw-alias-accent, #3b82f6);\n color: #fff;\n font-size: 13px;\n cursor: pointer;\n}\n\n.at-save:disabled {\n opacity: 0.6;\n cursor: default;\n}\n";
72
- if (typeof document !== "undefined" && document.querySelector("style[data-sp-css]") === null) {
73
- const tag = document.createElement("style");
74
- tag.setAttribute("data-sp-css", "1");
75
- tag.textContent = css$2;
76
- document.head.appendChild(tag);
77
- }
78
- //#endregion
79
- //#region src/client/AccountsTab.tsx
80
- /**
81
- * AccountsTab.tsx — 高级面板「账号」tab(v1.21 复杂配置层)
82
- *
83
- * 承载复杂配置:gateway 监听 host/port + 账号列表 CRUD(含密码)。
84
- * 简单配置(开关/阈值)在 dsh 原生设置面板(SettingsSection)——本 tab 仅提示引导。
85
- * 数据:GET/PUT /serenity/config(accounts-api.ts);无滚动条约束(紧凑布局)。
86
- */
87
- function AccountsTab(props) {
88
- const { sessionId } = props;
89
- const [config, setConfig] = (0, react.useState)(null);
90
- const [drafts, setDrafts] = (0, react.useState)([]);
91
- const [host, setHost] = (0, react.useState)("0.0.0.0");
92
- const [port, setPort] = (0, react.useState)(3081);
93
- const [error, setError] = (0, react.useState)(null);
94
- const [busy, setBusy] = (0, react.useState)(false);
95
- const [loaded, setLoaded] = (0, react.useState)(false);
96
- (0, react.useEffect)(() => {
97
- let alive = true;
98
- fetchConfig(sessionId).then((cfg) => {
99
- if (!alive) return;
100
- if (cfg) {
101
- setConfig(cfg);
102
- setHost(cfg.gateway.host);
103
- setPort(cfg.gateway.port);
104
- setDrafts(cfg.gateway.accounts.map(accountDraftFromWire));
105
- }
106
- setLoaded(true);
107
- });
108
- return () => {
109
- alive = false;
110
- };
111
- }, [sessionId]);
112
- const updateDraft = (id, patch) => {
113
- setDrafts((ds) => ds.map((d) => d.id === id ? {
114
- ...d,
115
- ...patch
116
- } : d));
117
- };
118
- const addRow = () => {
119
- setDrafts((ds) => [...ds, {
120
- id: newAccountId(),
121
- user: "",
122
- pass: "",
123
- isNew: true,
124
- hasPassword: false
125
- }]);
126
- };
127
- const removeRow = (id) => {
128
- setDrafts((ds) => ds.filter((d) => d.id !== id));
129
- };
130
- const save = async () => {
131
- for (const d of drafts) {
132
- const err = validateDraft(d);
133
- if (err) {
134
- setError(err);
135
- return;
136
- }
137
- }
138
- setBusy(true);
139
- setError(null);
140
- try {
141
- const cfg = await saveConfig(sessionId, { gateway: {
142
- host,
143
- port,
144
- accounts: drafts.map(accountToWire)
145
- } });
146
- if (cfg) {
147
- setConfig(cfg);
148
- setDrafts(cfg.gateway.accounts.map(accountDraftFromWire));
149
- }
150
- } catch (err) {
151
- setError(err instanceof Error ? err.message : String(err));
152
- } finally {
153
- setBusy(false);
154
- }
155
- };
156
- if (!loaded) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
157
- className: "at-root",
158
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
159
- className: "at-note",
160
- children: "加载中…"
161
- })
162
- });
163
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
164
- className: "at-root",
165
- children: [
166
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
167
- className: "at-note",
168
- children: "复杂配置(账号密码)。开关/阈值等简单配置请到 dsh 设置面板「Serenity」页。"
169
- }),
170
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
171
- className: "at-row",
172
- children: [
173
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
174
- className: "at-label",
175
- children: "监听地址"
176
- }),
177
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
178
- className: "at-input",
179
- value: host,
180
- onChange: (e) => setHost(e.target.value),
181
- placeholder: "0.0.0.0"
182
- }),
183
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
184
- className: "at-label",
185
- children: "端口"
186
- }),
187
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
188
- className: "at-input at-port",
189
- type: "number",
190
- value: port,
191
- min: 1,
192
- max: 65535,
193
- onChange: (e) => setPort(Number(e.target.value) || 0)
194
- })
195
- ]
196
- }),
197
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
198
- className: "at-list",
199
- children: [
200
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
201
- className: "at-head",
202
- children: [
203
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
204
- className: "at-col at-colUser",
205
- children: "用户名"
206
- }),
207
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
208
- className: "at-col at-colPass",
209
- children: "密码"
210
- }),
211
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "at-col at-colOp" })
212
- ]
213
- }),
214
- drafts.map((d) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
215
- className: "at-item",
216
- children: [
217
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
218
- className: "at-input at-colUser",
219
- value: d.user,
220
- placeholder: "用户名",
221
- onChange: (e) => updateDraft(d.id, { user: e.target.value })
222
- }),
223
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
224
- className: "at-input at-colPass",
225
- type: "password",
226
- value: d.pass,
227
- placeholder: d.isNew ? "必填" : d.hasPassword ? "留空保留原密码" : "未设置",
228
- onChange: (e) => updateDraft(d.id, { pass: e.target.value })
229
- }),
230
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
231
- type: "button",
232
- className: "at-del",
233
- onClick: () => removeRow(d.id),
234
- children: "删除"
235
- })
236
- ]
237
- }, d.id)),
238
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
239
- type: "button",
240
- className: "at-add",
241
- onClick: addRow,
242
- children: "+ 添加账号"
243
- })
244
- ]
245
- }),
246
- error !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
247
- className: "at-error",
248
- children: error
249
- }),
250
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
251
- className: "at-actions",
252
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
253
- type: "button",
254
- className: "at-save",
255
- disabled: busy,
256
- onClick: () => void save(),
257
- children: busy ? "保存中…" : "保存"
258
- })
259
- })
260
- ]
261
- });
262
- }
263
- //#endregion
264
10
  //#region \0sp-css:/home/yh/home/home-serenity/AI_LAB/dsh-serenity-plugin/hooks/dsh-serenity-hooks/src/client/SafeModePanel
265
- const css$1 = "/* SafeModePanel — 会话头部 Serenity 状态徽章(点击展开详情卡)。\n * 遵循 web-styling.md:颜色只用 --dsw-alias-* 语义 token(明暗自适应);\n * 尺寸与官方 header 标签一致;前缀 sp- 防冲突。 */\n\n.sp-root {\n position: relative;\n display: inline-flex;\n align-items: center;\n gap: 6px;\n height: 24px;\n padding: 0 4px 0 4px;\n border-radius: 6px;\n background: var(--dsw-alias-bg-module-platform);\n font-size: 12px;\n line-height: 24px;\n white-space: nowrap;\n}\n\n.sp-badge {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n height: 22px;\n padding: 0 6px;\n border: none;\n border-radius: 5px;\n background: transparent;\n color: inherit;\n font-size: 12px;\n cursor: pointer;\n transition: background var(--ds-transition-duration-fast, 0.1s) var(--ds-ease-in-out, ease);\n}\n\n.sp-badge:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.sp-badge:focus-visible {\n outline: 2px solid var(--dsw-alias-button-ghost-active-border);\n outline-offset: 1px;\n}\n\n.sp-dot {\n flex: none;\n width: 7px;\n height: 7px;\n border-radius: 50%;\n}\n\n.sp-dotOn {\n background: var(--dsw-alias-state-success-primary);\n}\n\n.sp-dotOff {\n background: var(--dsw-alias-label-dimmed);\n}\n\n.sp-brand {\n color: var(--dsw-alias-label-primary);\n font-weight: 500;\n}\n\n.sp-toggle {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n height: 18px;\n padding: 0 8px;\n border: none;\n border-radius: 5px;\n font-size: 12px;\n font-weight: 600;\n line-height: 18px;\n cursor: pointer;\n transition: background var(--ds-transition-duration-fast, 0.1s) var(--ds-ease-in-out, ease);\n}\n\n.sp-toggle:focus-visible {\n outline: 2px solid var(--dsw-alias-button-ghost-active-border);\n outline-offset: 1px;\n}\n\n.sp-toggle:disabled {\n opacity: 0.55;\n cursor: default;\n}\n\n.sp-toggleOn {\n background: var(--dsw-alias-state-error-primary);\n color: var(--dsw-alias-label-primary-foreground);\n}\n\n.sp-toggleOn:hover:not(:disabled) {\n background: var(--dsw-alias-state-error-secondary);\n}\n\n.sp-toggleOff {\n background: var(--dsw-alias-interactive-bg-hover);\n color: var(--dsw-alias-label-secondary);\n}\n\n.sp-toggleOff:hover:not(:disabled) {\n background: var(--dsw-alias-interactive-bg-hover-solid);\n}\n\n.sp-toggleIcon {\n flex: none;\n}\n\n.sp-toggleLg {\n height: 26px;\n padding: 0 14px;\n font-size: 13px;\n line-height: 26px;\n border-radius: 6px;\n}\n\n/* 详情卡:相对徽章绝对定位(右对齐),卡片 token */\n.sp-pop {\n position: absolute;\n top: calc(100% + 8px);\n right: 0;\n z-index: 20;\n width: 300px;\n box-sizing: border-box;\n padding: 12px 14px;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 12px;\n background: var(--dsw-specific-tip);\n box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n\n/* v1.21 大面板:双 tab(状态/账号),无滚动条约束(内容在视口内完整容纳) */\n.sp-popLg {\n width: 460px;\n padding: 10px 14px 14px;\n}\n\n.sp-tabs {\n display: flex;\n gap: 4px;\n padding-bottom: 8px;\n border-bottom: 1px solid var(--dsw-alias-border-l1);\n}\n\n.sp-tab {\n flex: 1;\n padding: 6px 0;\n border: none;\n border-radius: 6px;\n background: transparent;\n color: var(--dsw-alias-label-secondary);\n font-size: 13px;\n cursor: pointer;\n}\n\n.sp-tab:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.sp-tabOn {\n background: var(--dsw-alias-interactive-bg-active, rgba(128, 128, 128, 0.2));\n color: var(--dsw-alias-label-primary);\n font-weight: 600;\n}\n\n.sp-popRow {\n display: flex;\n align-items: baseline;\n gap: 10px;\n min-width: 0;\n}\n\n.sp-popLabel {\n flex: none;\n width: 34px;\n font-size: 12px;\n color: var(--dsw-alias-label-caption);\n}\n\n.sp-popValue {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n font-size: 12px;\n color: var(--dsw-alias-label-secondary);\n font-family: var(--ds-font-family-code, monospace);\n}\n\n.sp-popActions {\n display: flex;\n justify-content: flex-end;\n padding-top: 4px;\n border-top: 1px solid var(--dsw-alias-border-l1);\n margin-top: 2px;\n}\n\n/* loop 运行状态区块(WebUI 等待界面;并行任务各自一行) */\n.sp-popSection {\n display: flex;\n flex-direction: column;\n gap: 4px;\n padding-top: 6px;\n border-top: 1px solid var(--dsw-alias-border-l1);\n margin-top: 2px;\n}\n\n.sp-loopItem {\n display: flex;\n flex-direction: column;\n gap: 2px;\n padding: 2px 0 2px 44px;\n min-width: 0;\n}\n\n.sp-loopHead {\n display: flex;\n align-items: baseline;\n gap: 8px;\n min-width: 0;\n}\n\n.sp-loopLabel {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-size: 12px;\n font-weight: 600;\n color: var(--dsw-alias-label-primary);\n}\n\n.sp-loopRound {\n flex: none;\n font-size: 11px;\n color: var(--dsw-alias-state-info-primary);\n font-family: var(--ds-font-family-code, monospace);\n}\n\n.sp-loopTime {\n flex: none;\n font-size: 11px;\n color: var(--dsw-alias-label-caption);\n}\n\n.sp-loopResp {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-size: 11px;\n color: var(--dsw-alias-label-secondary);\n font-family: var(--ds-font-family-code, monospace);\n}\n\n@media (prefers-reduced-motion: reduce) {\n .sp-toggle,\n .sp-badge {\n transition: none;\n }\n}\n";
11
+ const css$2 = "/* SafeModePanel — 会话头部 Serenity 状态卡片(v1.22 UX 重构)\n * 卡片 → 点击 → 官方 Modal(body portal 遮罩 + 居中)\n * 模态内:tab 栏 + 分组(groupTitle 标题 + rowCard 行卡)——看齐官方 settings 设计语言\n * 遵循 web-styling.md:颜色只用 --dsw-alias-* 语义 token(明暗自适应);\n * 前缀 sp- 防冲突。 */\n\n/* ── 头部状态卡片(可点击) ── */\n\n.sp-card {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n height: 26px;\n padding: 0 10px;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 8px;\n background: var(--dsw-alias-bg-module-platform);\n color: inherit;\n font-size: 12px;\n cursor: pointer;\n transition: background var(--ds-transition-duration-fast, 0.1s) var(--ds-ease-in-out, ease),\n border-color var(--ds-transition-duration-fast, 0.1s) var(--ds-ease-in-out, ease);\n}\n\n.sp-card:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n border-color: var(--dsw-alias-border-l2);\n}\n\n.sp-card:focus-visible {\n outline: 2px solid var(--dsw-alias-button-ghost-active-border);\n outline-offset: 1px;\n}\n\n.sp-card:disabled {\n opacity: 0.6;\n cursor: default;\n}\n\n.sp-dot {\n flex: none;\n width: 7px;\n height: 7px;\n border-radius: 50%;\n}\n\n.sp-dotOn {\n background: var(--dsw-alias-state-success-primary);\n}\n\n.sp-dotOff {\n background: var(--dsw-alias-label-dimmed);\n}\n\n.sp-brand {\n color: var(--dsw-alias-label-primary);\n font-weight: 500;\n}\n\n.sp-chev {\n color: var(--dsw-alias-label-dimmed);\n}\n\n/* safe-mode 徽标(卡片本体直接可见;ON 红 / OFF 灰) */\n.sp-sm {\n display: inline-flex;\n align-items: center;\n gap: 3px;\n height: 18px;\n padding: 0 7px;\n border-radius: 9px;\n font-size: 11px;\n font-weight: 600;\n line-height: 1;\n}\n\n.sp-smOn {\n background: var(--dsw-alias-state-warning-bg, rgba(245, 158, 11, 0.18));\n color: var(--dsw-alias-state-warning-fg, #f59e0b);\n border: 1px solid var(--dsw-alias-state-warning-border, rgba(245, 158, 11, 0.4));\n}\n\n.sp-smOff {\n background: var(--dsw-alias-interactive-bg-active, rgba(128, 128, 128, 0.16));\n color: var(--dsw-alias-label-dimmed);\n border: 1px solid var(--dsw-alias-border-l1);\n}\n\n.sp-smIcon {\n flex: none;\n}\n\n/* ── 官方 Modal 定制(宽度 + 内容紧凑) ── */\n\n.sp-modal {\n width: 520px;\n max-width: calc(100vw - 48px);\n}\n\n/* ── tab 栏 ── */\n\n.sp-tabs {\n display: flex;\n gap: 6px;\n padding: 0 2px 12px;\n border-bottom: 1px solid var(--dsw-alias-border-l1);\n}\n\n.sp-tab {\n flex: 1;\n padding: 8px 0;\n border: none;\n border-radius: 8px;\n background: transparent;\n color: var(--dsw-alias-label-secondary);\n font-size: 13px;\n font-weight: 500;\n cursor: pointer;\n transition: background var(--ds-transition-duration-fast, 0.1s) var(--ds-ease-in-out, ease);\n}\n\n.sp-tab:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.sp-tabOn {\n background: var(--dsw-alias-interactive-bg-active, rgba(128, 128, 128, 0.2));\n color: var(--dsw-alias-label-primary);\n font-weight: 600;\n}\n\n/* ── 模态内容 ── */\n\n.sp-modalBody {\n display: flex;\n flex-direction: column;\n gap: 16px;\n padding-top: 14px;\n /* 内容超出视口时 body 区滚动(兜底——不再截断);整卡不滚动 */\n overflow-y: auto;\n max-height: calc(100vh - 200px);\n}\n\n/* ── 分组(官方 settings 语言:groupTitle + rows) ── */\n\n.sp-group {\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n\n.sp-groupHead {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n}\n\n.sp-groupTitle {\n margin: 0;\n font-size: 12px;\n line-height: 18px;\n font-weight: 600;\n letter-spacing: 0.02em;\n text-transform: uppercase;\n color: var(--dsw-alias-label-secondary);\n}\n\n.sp-rows {\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n\n/* 行卡(rowCard:border + radius + padding;值与 label 分离、长内容可换行) */\n.sp-row {\n display: flex;\n align-items: baseline;\n gap: 12px;\n min-width: 0;\n padding: 8px 12px;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 10px;\n background: var(--dsw-alias-bg-module);\n}\n\n.sp-label {\n flex: none;\n width: 76px;\n font-size: 12px;\n font-weight: 500;\n color: var(--dsw-alias-label-secondary);\n white-space: nowrap;\n}\n\n.sp-value {\n flex: 1;\n min-width: 0;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-primary);\n font-family: var(--ds-font-family-code, monospace);\n word-break: break-all;\n overflow-wrap: anywhere;\n}\n\n/* 安全模式动作区 */\n.sp-actions {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 8px 12px;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 10px;\n background: var(--dsw-alias-bg-module);\n}\n\n.sp-toggle {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n height: 24px;\n padding: 0 10px;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 6px;\n font-size: 12px;\n cursor: pointer;\n transition: background var(--ds-transition-duration-fast, 0.1s) var(--ds-ease-in-out, ease);\n}\n\n.sp-toggleLg {\n height: 30px;\n padding: 0 16px;\n font-size: 13px;\n}\n\n.sp-toggleOn {\n background: var(--dsw-alias-state-warning-bg, rgba(245, 158, 11, 0.15));\n color: var(--dsw-alias-state-warning-fg, #f59e0b);\n border-color: var(--dsw-alias-state-warning-border, rgba(245, 158, 11, 0.4));\n}\n\n.sp-toggleOff {\n background: var(--dsw-alias-interactive-bg-hover);\n color: var(--dsw-alias-label-secondary);\n}\n\n.sp-toggleIcon {\n flex: none;\n}\n\n.sp-toggleDesc {\n flex: 1;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-tertiary);\n}\n\n/* loop 运行状态区块 */\n\n.sp-loopsToggle {\n flex: none;\n padding: 2px 10px;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 6px;\n background: transparent;\n color: var(--dsw-alias-label-secondary);\n font-size: 11px;\n cursor: pointer;\n}\n\n.sp-loopsToggle:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.sp-loopsList {\n display: flex;\n flex-direction: column;\n gap: 4px;\n margin-top: 2px;\n}\n\n.sp-loopItem {\n display: flex;\n flex-direction: column;\n gap: 2px;\n padding: 8px 12px;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 8px;\n min-width: 0;\n}\n\n.sp-loopHead {\n display: flex;\n align-items: baseline;\n gap: 8px;\n min-width: 0;\n}\n\n.sp-loopLabel {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-size: 12px;\n font-weight: 500;\n color: var(--dsw-alias-label-primary);\n}\n\n.sp-loopRound {\n flex: none;\n font-size: 11px;\n color: var(--dsw-alias-label-dimmed);\n}\n\n.sp-loopTime {\n flex: none;\n font-size: 11px;\n color: var(--dsw-alias-label-dimmed);\n}\n\n.sp-loopResp {\n font-size: 11px;\n line-height: 16px;\n color: var(--dsw-alias-label-secondary);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.sp-loopsMore {\n font-size: 11px;\n color: var(--dsw-alias-label-dimmed);\n padding: 4px 12px;\n}\n\n/* 底部提示(配置入口引导) */\n.sp-hint {\n margin: 0;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-tertiary);\n word-break: break-word;\n overflow-wrap: anywhere;\n padding: 8px 12px;\n border: 1px dashed var(--dsw-alias-border-l2);\n border-radius: 10px;\n}\n";
266
12
  if (typeof document !== "undefined" && document.querySelector("style[data-sp-css]") === null) {
267
13
  const tag = document.createElement("style");
268
14
  tag.setAttribute("data-sp-css", "1");
269
- tag.textContent = css$1;
15
+ tag.textContent = css$2;
270
16
  document.head.appendChild(tag);
271
17
  }
272
18
  //#endregion
@@ -277,14 +23,13 @@ window.__ModuleLoader__.load({
277
23
  function cx(...parts) {
278
24
  return parts.filter(Boolean).join(" ");
279
25
  }
280
- /** 会话头部:可点击徽章 + safe-mode 开关;点击徽章展开双 tab 大面板 */
26
+ /** 会话头部:CCC 状态栏卡片(点击 → 官方 Modal 居中,纯状态展示) */
281
27
  function SafeModePanel(props) {
282
28
  const [status, setStatus] = (0, react.useState)(null);
283
29
  const [busy, setBusy] = (0, react.useState)(false);
284
30
  const [open, setOpen] = (0, react.useState)(false);
285
- const [tab, setTab] = (0, react.useState)("status");
286
31
  const [loops, setLoops] = (0, react.useState)([]);
287
- const rootRef = (0, react.useRef)(null);
32
+ const [loopsOpen, setLoopsOpen] = (0, react.useState)(false);
288
33
  const sessionId = props.sessionId;
289
34
  const refresh = (0, react.useCallback)(async () => {
290
35
  const qs = sessionId ? `?sessionId=${encodeURIComponent(sessionId)}` : "";
@@ -317,14 +62,6 @@ window.__ModuleLoader__.load({
317
62
  clearInterval(timer);
318
63
  };
319
64
  }, [open, sessionId]);
320
- (0, react.useEffect)(() => {
321
- if (!open) return;
322
- const onDown = (e) => {
323
- if (rootRef.current !== null && !rootRef.current.contains(e.target)) setOpen(false);
324
- };
325
- document.addEventListener("pointerdown", onDown);
326
- return () => document.removeEventListener("pointerdown", onDown);
327
- }, [open]);
328
65
  const toggle = async (on) => {
329
66
  setBusy(true);
330
67
  try {
@@ -344,104 +81,108 @@ window.__ModuleLoader__.load({
344
81
  setBusy(false);
345
82
  }
346
83
  };
347
- if (!status) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
348
- className: cx("sp-root"),
349
- children: "⏳ Serenity…"
84
+ if (!status) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
85
+ type: "button",
86
+ className: cx("sp-card"),
87
+ disabled: true,
88
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: cx("sp-dot", "sp-dotOff") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
89
+ className: cx("sp-brand"),
90
+ children: "Serenity…"
91
+ })]
350
92
  });
351
93
  const inCcc = status.root !== null;
352
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
353
- ref: rootRef,
354
- className: cx("sp-root"),
355
- title: status.root ?? void 0,
94
+ const runningLoops = loops.filter((l) => !l.done);
95
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
96
+ type: "button",
97
+ className: cx("sp-card"),
98
+ "aria-haspopup": "dialog",
99
+ "aria-expanded": open,
100
+ onClick: () => setOpen(true),
101
+ title: inCcc ? `Serenity v${status.accVersion} — ${status.root}` : "Serenity(未激活)",
356
102
  children: [
357
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
358
- type: "button",
359
- className: cx("sp-badge"),
360
- "aria-expanded": open,
361
- onClick: () => setOpen((v) => !v),
362
- title: open ? "收起详情" : "查看 Serenity 状态",
363
- children: [
364
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: cx("sp-dot", inCcc ? "sp-dotOn" : "sp-dotOff") }),
365
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
366
- className: cx("sp-brand"),
367
- children: ["Serenity v", status.accVersion]
368
- }),
369
- open ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronUpOutline14, { size: 12 }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutline14, { size: 12 })
370
- ]
103
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: cx("sp-dot", inCcc ? "sp-dotOn" : "sp-dotOff") }),
104
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
105
+ className: cx("sp-brand"),
106
+ children: ["Serenity v", status.accVersion]
371
107
  }),
372
- inCcc && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
373
- type: "button",
374
- className: cx("sp-toggle", status.safeModeOn ? "sp-toggleOn" : "sp-toggleOff"),
375
- disabled: busy,
376
- onClick: () => void toggle(!status.safeModeOn),
377
- title: status.safeModeOn ? "关闭 safe-mode(解除 bash 限制)" : "开启 safe-mode(隐藏写工具)",
378
- children: [
379
- status.safeModeOn && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconWarningOutline16, {
380
- size: 12,
381
- className: cx("sp-toggleIcon")
382
- }),
383
- "safe-mode ",
384
- status.safeModeOn ? "ON" : "OFF"
385
- ]
108
+ inCcc && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
109
+ className: cx("sp-sm", status.safeModeOn ? "sp-smOn" : "sp-smOff"),
110
+ title: status.safeModeOn ? "safe-mode 已开启(隐藏写工具)" : "safe-mode 关闭",
111
+ children: [status.safeModeOn && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconWarningOutline16, {
112
+ size: 11,
113
+ className: cx("sp-smIcon")
114
+ }), status.safeModeOn ? "safe" : "safe-off"]
386
115
  }),
387
- open && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
388
- className: cx("sp-pop", "sp-popLg"),
389
- role: "dialog",
390
- "aria-label": "Serenity 高级设定",
391
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
392
- className: cx("sp-tabs"),
393
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
394
- type: "button",
395
- className: cx("sp-tab", tab === "status" && "sp-tabOn"),
396
- onClick: () => setTab("status"),
397
- children: "状态"
398
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
399
- type: "button",
400
- className: cx("sp-tab", tab === "accounts" && "sp-tabOn"),
401
- onClick: () => setTab("accounts"),
402
- children: "账号"
116
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutline14, {
117
+ size: 12,
118
+ className: cx("sp-chev")
119
+ })
120
+ ]
121
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
122
+ open,
123
+ onClose: () => setOpen(false),
124
+ title: "CCC 状态栏",
125
+ closeLabel: "关闭",
126
+ className: cx("sp-modal"),
127
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
128
+ className: cx("sp-modalBody"),
129
+ children: inCcc ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
130
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
131
+ className: cx("sp-group"),
132
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
133
+ className: cx("sp-groupTitle"),
134
+ children: "运行环境"
135
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
136
+ className: cx("sp-rows"),
137
+ children: [
138
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
139
+ className: cx("sp-row"),
140
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
141
+ className: cx("sp-label"),
142
+ children: "CCC"
143
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
144
+ className: cx("sp-value"),
145
+ title: status.root ?? void 0,
146
+ children: status.root
147
+ })]
148
+ }),
149
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
150
+ className: cx("sp-row"),
151
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
152
+ className: cx("sp-label"),
153
+ children: "Loop 模型"
154
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
155
+ className: cx("sp-value"),
156
+ title: status.loopModel ? `loop.defaultModel: ${status.loopModel}` : void 0,
157
+ children: status.loopModel ?? "未配置"
158
+ })]
159
+ }),
160
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
161
+ className: cx("sp-row"),
162
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
163
+ className: cx("sp-label"),
164
+ children: "守卫"
165
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
166
+ className: cx("sp-value"),
167
+ children: [
168
+ "blacklist ",
169
+ status.blacklist.length,
170
+ " 条",
171
+ status.threshold !== null ? ` · keeper 阈值 ${status.threshold}` : ""
172
+ ]
173
+ })]
174
+ })
175
+ ]
403
176
  })]
404
- }), tab === "accounts" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AccountsTab, { sessionId: String(sessionId ?? "") }) : inCcc ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
405
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
406
- className: cx("sp-popRow"),
407
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
408
- className: cx("sp-popLabel"),
409
- children: "CCC"
410
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
411
- className: cx("sp-popValue"),
412
- title: status.root ?? void 0,
413
- children: status.root
414
- })]
415
- }),
416
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
417
- className: cx("sp-popRow"),
418
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
419
- className: cx("sp-popLabel"),
420
- children: "loop"
421
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
422
- className: cx("sp-popValue"),
423
- title: status.loopModel ? `loop.defaultModel: ${status.loopModel}` : void 0,
424
- children: status.loopModel ?? "未配置"
425
- })]
426
- }),
427
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
428
- className: cx("sp-popRow"),
429
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
430
- className: cx("sp-popLabel"),
431
- children: "守卫"
432
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
433
- className: cx("sp-popValue"),
434
- children: [
435
- "blacklist ",
436
- status.blacklist.length,
437
- " 条",
438
- status.threshold !== null ? ` · keeper 阈值 ${status.threshold}` : ""
439
- ]
440
- })]
441
- }),
442
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
443
- className: cx("sp-popActions"),
444
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
177
+ }),
178
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
179
+ className: cx("sp-group"),
180
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
181
+ className: cx("sp-groupTitle"),
182
+ children: "安全模式"
183
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
184
+ className: cx("sp-actions"),
185
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
445
186
  type: "button",
446
187
  className: cx("sp-toggle", status.safeModeOn ? "sp-toggleOn" : "sp-toggleOff", "sp-toggleLg"),
447
188
  disabled: busy,
@@ -454,58 +195,106 @@ window.__ModuleLoader__.load({
454
195
  "safe-mode ",
455
196
  status.safeModeOn ? "ON" : "OFF"
456
197
  ]
457
- })
458
- }),
459
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
460
- className: cx("sp-popSection"),
461
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
462
- className: cx("sp-popRow"),
463
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
464
- className: cx("sp-popLabel"),
465
- children: "loops"
466
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
467
- className: cx("sp-popValue"),
468
- children: loops.filter((l) => !l.done).length > 0 ? `运行中 ${loops.filter((l) => !l.done).length}${loops.length > 0 ? ` / 共 ${loops.length}` : ""}` : "无运行中 loop"
198
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
199
+ className: cx("sp-toggleDesc"),
200
+ children: status.safeModeOn ? "写工具已隐藏,无人值守安全航行" : "写工具可用(点击开启)"
201
+ })]
202
+ })]
203
+ }),
204
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
205
+ className: cx("sp-group"),
206
+ children: [
207
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
208
+ className: cx("sp-groupHead"),
209
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
210
+ className: cx("sp-groupTitle"),
211
+ children: "Loop 运行"
212
+ }), loops.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
213
+ type: "button",
214
+ className: cx("sp-loopsToggle"),
215
+ onClick: () => setLoopsOpen((v) => !v),
216
+ children: loopsOpen ? "收起" : "详情"
469
217
  })]
470
- }), loops.slice(0, 4).map((l) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
471
- className: cx("sp-loopItem"),
472
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
473
- className: cx("sp-loopHead"),
218
+ }),
219
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
220
+ className: cx("sp-rows"),
221
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
222
+ className: cx("sp-row"),
223
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
224
+ className: cx("sp-label"),
225
+ children: "状态"
226
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
227
+ className: cx("sp-value"),
228
+ children: runningLoops.length > 0 ? `运行中 ${runningLoops.length}${loops.length > 0 ? ` / 共 ${loops.length}` : ""}` : "无运行中 loop"
229
+ })]
230
+ })
231
+ }),
232
+ loopsOpen && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
233
+ className: cx("sp-loopsList"),
234
+ children: [loops.slice(0, 3).map((l) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
235
+ className: cx("sp-loopItem"),
236
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
237
+ className: cx("sp-loopHead"),
238
+ children: [
239
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
240
+ className: cx("sp-loopLabel"),
241
+ title: l.label,
242
+ children: l.label
243
+ }),
244
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
245
+ className: cx("sp-loopRound"),
246
+ children: l.done ? "✓ 完成" : `R${l.round}`
247
+ }),
248
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
249
+ className: cx("sp-loopTime"),
250
+ children: new Date(l.updated).toLocaleTimeString()
251
+ })
252
+ ]
253
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
254
+ className: cx("sp-loopResp"),
255
+ title: l.lastResponse,
256
+ children: l.lastResponse.slice(0, 80) || (l.done ? "已完成" : "(等待首轮响应)")
257
+ })]
258
+ }, l.label)), loops.length > 3 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
259
+ className: cx("sp-loopsMore"),
474
260
  children: [
475
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
476
- className: cx("sp-loopLabel"),
477
- title: l.label,
478
- children: l.label
479
- }),
480
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
481
- className: cx("sp-loopRound"),
482
- children: l.done ? "✓" : `R${l.round}`
483
- }),
484
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
485
- className: cx("sp-loopTime"),
486
- children: new Date(l.updated).toLocaleTimeString()
487
- })
261
+ "… 共 ",
262
+ loops.length,
263
+ " 个 loop(其余见等待界面)"
488
264
  ]
489
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
490
- className: cx("sp-loopResp"),
491
- title: l.lastResponse,
492
- children: l.lastResponse.slice(0, 60) || (l.done ? "已完成" : "(等待首轮响应)")
493
265
  })]
494
- }, l.label))]
266
+ })
267
+ ]
268
+ }),
269
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
270
+ className: cx("sp-hint"),
271
+ children: [
272
+ "💡 开关/阈值/账号/工作区白名单请在 ",
273
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: "设置 → Serenity" }),
274
+ " 中配置。"
275
+ ]
276
+ })
277
+ ] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
278
+ className: cx("sp-group"),
279
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
280
+ className: cx("sp-groupTitle"),
281
+ children: "状态"
282
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
283
+ className: cx("sp-rows"),
284
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
285
+ className: cx("sp-row"),
286
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
287
+ className: cx("sp-label"),
288
+ children: "ACC"
289
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
290
+ className: cx("sp-value"),
291
+ children: "未激活(工作区不在 CCC 内)"
292
+ })]
495
293
  })
496
- ] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
497
- className: cx("sp-popRow"),
498
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
499
- className: cx("sp-popLabel"),
500
- children: "状态"
501
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
502
- className: cx("sp-popValue"),
503
- children: "ACC 未激活(工作区不在 CCC 内)"
504
- })]
505
294
  })]
506
295
  })
507
- ]
508
- });
296
+ })
297
+ })] });
509
298
  }
510
299
  //#endregion
511
300
  //#region src/client/image-fallback-api.ts
@@ -518,8 +307,8 @@ window.__ModuleLoader__.load({
518
307
  * 单图:用户提供了一张图片(路径:...);多图:每张一行路径
519
308
  */
520
309
  function imageNoteTemplate(paths) {
521
- if (paths.length === 1) return `用户提供了一张图片(路径:${paths[0]})`;
522
- return `用户提供了 ${paths.length} 张图片:\n${paths.map((p) => `- ${p}`).join("\n")}`;
310
+ if (paths.length === 1) return `The user provided an image (path: ${paths[0]})`;
311
+ return `The user provided ${paths.length} images:\n${paths.map((p) => `- ${p}`).join("\n")}`;
523
312
  }
524
313
  /** 浏览器 File → base64(与 ui-conversation serializeImages 等价的最小实现) */
525
314
  function fileToBase64(file) {
@@ -631,8 +420,535 @@ window.__ModuleLoader__.load({
631
420
  return null;
632
421
  }
633
422
  //#endregion
423
+ //#region src/client/accounts-api.ts
424
+ /** wire 账号 → 本地编辑行 */
425
+ function accountDraftFromWire(a) {
426
+ return {
427
+ id: a.id,
428
+ user: a.user,
429
+ pass: "",
430
+ isNew: false,
431
+ hasPassword: a.hasPassword,
432
+ hasTotp: a.hasTotp,
433
+ totpState: "none"
434
+ };
435
+ }
436
+ /** 生成新账号 id(本地编辑用;提交时若为空由服务端拒绝) */
437
+ function newAccountId() {
438
+ return `a-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 6)}`;
439
+ }
440
+ const B32_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
441
+ /** 字节 → base32(无填充) */
442
+ function bytesToBase32(bytes) {
443
+ let bits = 0;
444
+ let value = 0;
445
+ let out = "";
446
+ for (const byte of bytes) {
447
+ value = value << 8 | byte;
448
+ bits += 8;
449
+ while (bits >= 5) {
450
+ out += B32_ALPHABET[value >>> bits - 5 & 31];
451
+ bits -= 5;
452
+ }
453
+ }
454
+ if (bits > 0) out += B32_ALPHABET[value << 5 - bits & 31];
455
+ return out;
456
+ }
457
+ /** 生成 20 字节随机 TOTP secret(Web Crypto getRandomValues) */
458
+ function newTotpSecret() {
459
+ const bytes = /* @__PURE__ */ new Uint8Array(20);
460
+ if (typeof crypto !== "undefined" && typeof crypto.getRandomValues === "function") crypto.getRandomValues(bytes);
461
+ else for (let i = 0; i < bytes.length; i++) bytes[i] = Math.floor(Math.random() * 256);
462
+ return bytesToBase32(bytes);
463
+ }
464
+ /** 构造 otpauth URI(Authenticator 扫码/手动录入) */
465
+ function otpauthUriClient(secret, label, issuer = "Serenity Home") {
466
+ const params = new URLSearchParams({
467
+ secret,
468
+ issuer,
469
+ algorithm: "SHA1",
470
+ digits: "6",
471
+ period: "30"
472
+ });
473
+ return `otpauth://totp/${encodeURIComponent(label)}?${params.toString()}`;
474
+ }
475
+ /** 本地编辑行 → wire 账号(提交时;pass 字段携带) */
476
+ function accountToWire(d) {
477
+ return {
478
+ id: d.id,
479
+ user: d.user,
480
+ pass: d.pass,
481
+ ...d.totpState === "pending" && d.totpSecret !== void 0 && d.totpSecret !== "" ? { totpSecret: d.totpSecret } : d.totpState === "clear" ? { totpReset: true } : {}
482
+ };
483
+ }
484
+ /** 校验本地编辑行:user 非空;新账号必须设密码;pending TOTP 需确认码 */
485
+ function validateDraft(d) {
486
+ if (d.user.trim() === "") return "用户名不能为空";
487
+ if (d.isNew && d.pass === "") return "新账号必须设置密码";
488
+ if (d.totpState === "pending" && !/^\d{6}$/.test(d.totpConfirm ?? "")) return `绑定 ${d.user} 的验证器需输入 6 位确认码`;
489
+ return null;
490
+ }
491
+ const CONFIG_PATH = "/serenity/config";
492
+ /**
493
+ * 获取已有工作区列表(需求 1:白名单从已有工作区选择而非手输)。
494
+ * 走 DSH 原生 RPC `POST /api/workspace.list`(JSON RPC 形态)。
495
+ * 信封必须是完整 ClientRequest:`{ type: 'client-request', rpcId, method, payload }`
496
+ * (api/rpc.ts wire 契约——缺 type/method → clientRequestSchema 校验失败 → bad-request)。
497
+ * 返回 { path, title }[];失败返回空数组(面板显示"暂无可选工作区")。
498
+ */
499
+ async function fetchWorkspaces() {
500
+ try {
501
+ const res = await fetch("/api/workspace.list", {
502
+ method: "POST",
503
+ headers: { "content-type": "application/json" },
504
+ body: JSON.stringify({
505
+ type: "client-request",
506
+ rpcId: `ws-${Date.now().toString(36)}`,
507
+ method: "workspace.list",
508
+ payload: {}
509
+ })
510
+ });
511
+ if (!res.ok) return [];
512
+ const items = (await res.json())?.result?.value?.items;
513
+ if (!Array.isArray(items)) return [];
514
+ return items.filter((i) => typeof i.path === "string" && i.path !== "").map((i) => ({
515
+ path: i.path,
516
+ title: typeof i.title === "string" && i.title !== "" ? i.title : i.path
517
+ }));
518
+ } catch {
519
+ return [];
520
+ }
521
+ }
522
+ /** GET 配置(含账号列表;无 hash) */
523
+ async function fetchConfig() {
524
+ const res = await fetch(CONFIG_PATH, { headers: {
525
+ accept: "application/json",
526
+ "x-serenity-ui": "1"
527
+ } });
528
+ if (!res.ok) return null;
529
+ return (await res.json()).config ?? null;
530
+ }
531
+ /** PUT 配置(账号 patch + 工作区白名单)→ 返回保存后的 wire */
532
+ async function saveConfig(patch) {
533
+ const res = await fetch(CONFIG_PATH, {
534
+ method: "PUT",
535
+ headers: {
536
+ "content-type": "application/json",
537
+ "x-serenity-ui": "1"
538
+ },
539
+ body: JSON.stringify({ config: patch })
540
+ });
541
+ if (!res.ok) {
542
+ const body = await res.json().catch(() => null);
543
+ throw new Error(body?.error ?? `保存失败(${res.status})`);
544
+ }
545
+ return (await res.json()).config ?? null;
546
+ }
547
+ //#endregion
548
+ //#region \0sp-css:/home/yh/home/home-serenity/AI_LAB/dsh-serenity-plugin/hooks/dsh-serenity-hooks/src/client/AccountsEditor
549
+ const css$1 = "/* AccountsEditor — DSH 设置面板「外部访问」区块(v1.22)\n * 与 SettingsSection ss-* 同设计语言:--dsw-alias-* 语义 token,明暗自适应。 */\n\n.ae-root {\n display: flex;\n flex-direction: column;\n gap: 16px;\n padding: 12px 14px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 12px;\n font-size: 13px;\n}\n\n.ae-warn {\n margin: 0;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-state-warning-fg, #f59e0b);\n word-break: break-word;\n}\n\n.ae-block {\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n\n.ae-title {\n margin: 0;\n font-size: 12px;\n line-height: 18px;\n font-weight: 600;\n letter-spacing: 0.02em;\n text-transform: uppercase;\n color: var(--dsw-alias-label-secondary);\n}\n\n.ae-desc {\n margin: 0;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-tertiary);\n word-break: break-word;\n overflow-wrap: anywhere;\n}\n\n.ae-note {\n margin: 0;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-tertiary);\n}\n\n/* ── 监听设置 / 白名单输入行 ── */\n\n.ae-row {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.ae-label {\n flex: none;\n font-size: 12px;\n font-weight: 500;\n color: var(--dsw-alias-label-secondary);\n white-space: nowrap;\n}\n\n.ae-input {\n flex: 1;\n min-width: 0;\n padding: 6px 10px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 8px;\n background: var(--dsw-alias-bg-module);\n color: var(--dsw-alias-label-primary);\n font-size: 13px;\n outline: none;\n}\n\n.ae-input:focus {\n border-color: var(--dsw-alias-button-primary-bg, var(--dsw-alias-accent, #3b82f6));\n}\n\n.ae-input::placeholder {\n color: var(--dsw-alias-label-dimmed);\n}\n\n.ae-input.ae-port {\n flex: none;\n width: 76px;\n}\n\n/* ── 账号列表 ── */\n\n.ae-list {\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n.ae-head {\n display: flex;\n gap: 8px;\n font-size: 11px;\n font-weight: 500;\n color: var(--dsw-alias-label-dimmed);\n padding: 0 2px;\n text-transform: uppercase;\n letter-spacing: 0.02em;\n}\n\n.ae-item {\n display: flex;\n gap: 8px;\n align-items: center;\n padding: 6px 2px;\n}\n\n.ae-colUser { flex: 1.2; }\n.ae-colPass { flex: 1.6; }\n.ae-colTotp { flex: 2.2; min-width: 0; }\n.ae-colOp { flex: none; width: 48px; }\n\n/* ── v1.22.4 TOTP 第二因素 ── */\n\n.ae-totpBtn {\n flex: none;\n padding: 4px 10px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 6px;\n background: transparent;\n color: var(--dsw-alias-label-secondary);\n font-size: 12px;\n cursor: pointer;\n white-space: nowrap;\n}\n\n.ae-totpBtn:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.ae-totpPending {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n align-items: center;\n min-width: 0;\n}\n\n.ae-totpSecret {\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n font-size: 11px;\n background: var(--dsw-alias-surface-raised, rgba(0, 0, 0, 0.25));\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 4px;\n padding: 2px 6px;\n color: var(--dsw-alias-label-secondary);\n word-break: break-all;\n}\n\n.ae-totpUri {\n font-size: 11px;\n color: var(--dsw-alias-accent, #3b82f6);\n text-decoration: none;\n}\n\n.ae-totpUri:hover {\n text-decoration: underline;\n}\n\n.ae-totpConfirm {\n max-width: 120px;\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n letter-spacing: 0.2em;\n}\n\n.ae-totpBadge {\n font-size: 12px;\n color: var(--dsw-alias-state-warning-fg, #f59e0b);\n}\n\n.ae-totpClear {\n display: flex;\n gap: 6px;\n align-items: center;\n}\n\n.ae-check {\n display: flex;\n gap: 8px;\n align-items: center;\n font-size: 12px;\n color: var(--dsw-alias-label-secondary);\n margin-top: 10px;\n cursor: pointer;\n}\n\n.ae-check input {\n accent-color: var(--dsw-alias-accent, #3b82f6);\n}\n\n.ae-del {\n flex: none;\n padding: 4px 10px;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 6px;\n background: transparent;\n color: var(--dsw-alias-state-danger-fg, #f87171);\n font-size: 12px;\n cursor: pointer;\n}\n\n.ae-del:hover {\n background: var(--dsw-alias-state-danger-bg, rgba(248, 113, 113, 0.12));\n}\n\n.ae-add {\n align-self: flex-start;\n padding: 5px 12px;\n border: 1px dashed var(--dsw-alias-border-l2);\n border-radius: 8px;\n background: transparent;\n color: var(--dsw-alias-label-secondary);\n font-size: 12px;\n cursor: pointer;\n}\n\n.ae-add:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.ae-wsSelect {\n margin-top: 8px;\n max-width: 100%;\n cursor: pointer;\n}\n\n/* ── 工作区白名单 chips ── */\n\n.ae-wsList {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n}\n\n.ae-wsChip {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 3px 8px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 8px;\n background: var(--dsw-alias-interactive-bg-active, rgba(128, 128, 128, 0.14));\n}\n\n.ae-wsPath {\n font-size: 12px;\n color: var(--dsw-alias-label-primary);\n font-family: var(--ds-font-family-code, monospace);\n word-break: break-all;\n}\n\n.ae-wsDel {\n flex: none;\n width: 18px;\n height: 18px;\n border: none;\n border-radius: 50%;\n background: transparent;\n color: var(--dsw-alias-label-dimmed);\n font-size: 13px;\n line-height: 1;\n cursor: pointer;\n}\n\n.ae-wsDel:hover {\n background: var(--dsw-alias-state-danger-bg, rgba(248, 113, 113, 0.18));\n color: var(--dsw-alias-state-danger-fg, #f87171);\n}\n\n/* ── 错误 / 操作区 ── */\n\n.ae-error {\n margin: 0;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-state-danger-fg, #f87171);\n word-break: break-word;\n}\n\n.ae-actions {\n display: flex;\n justify-content: flex-end;\n padding-top: 4px;\n border-top: 1px solid var(--dsw-alias-border-l1);\n}\n\n.ae-save {\n padding: 7px 24px;\n border: 0;\n border-radius: 8px;\n background: var(--dsw-alias-button-primary-bg, var(--dsw-alias-accent, #3b82f6));\n color: var(--dsw-alias-button-primary-fg, #fff);\n font-size: 13px;\n font-weight: 500;\n cursor: pointer;\n}\n\n.ae-save:hover:not(:disabled) {\n filter: brightness(1.08);\n}\n\n.ae-save:disabled {\n opacity: 0.6;\n cursor: default;\n}\n";
550
+ if (typeof document !== "undefined" && document.querySelector("style[data-sp-css]") === null) {
551
+ const tag = document.createElement("style");
552
+ tag.setAttribute("data-sp-css", "1");
553
+ tag.textContent = css$1;
554
+ document.head.appendChild(tag);
555
+ }
556
+ //#endregion
557
+ //#region src/client/AccountsEditor.tsx
558
+ /**
559
+ * AccountsEditor.tsx — DSH 设置面板「外部访问」区块(v1.22 重构 + v1.22.4 完善)
560
+ *
561
+ * 归属原则(v1.22 用户拍板):账号密码/工作区白名单是 **plugin 级配置**,
562
+ * 统一在 DSH 设置面板(plugin 层)设定——本组件即该设定面。
563
+ * 数据:GET/PUT /serenity/config(accounts-api.ts;服务端 scrypt hash,
564
+ * 存 ~/.dsh/serenity-hooks.json plugin 全局文件)。
565
+ *
566
+ * 内容:
567
+ * - 监听设置:gateway host/port + Secure Cookie(HTTPS 反代时)
568
+ * - 外部能力:允许新建工作区 / 启用 Authenticator 第二因素
569
+ * - 登录账号:账号列表 CRUD(含密码 + TOTP 绑定/解绑)
570
+ * - 工作区白名单:**从已有工作区选择**(v1.22.4 需求 1;非手输)
571
+ */
572
+ function AccountsEditor(props) {
573
+ const { gatewayOn } = props;
574
+ const [config, setConfig] = (0, react.useState)(null);
575
+ const [drafts, setDrafts] = (0, react.useState)([]);
576
+ const [host, setHost] = (0, react.useState)("0.0.0.0");
577
+ const [port, setPort] = (0, react.useState)(3081);
578
+ const [workspaces, setWorkspaces] = (0, react.useState)([]);
579
+ const [cookieSecure, setCookieSecure] = (0, react.useState)(false);
580
+ const [allowWorkspaceCreate, setAllowWorkspaceCreate] = (0, react.useState)(true);
581
+ const [totpEnabled, setTotpEnabled] = (0, react.useState)(false);
582
+ const [knownWorkspaces, setKnownWorkspaces] = (0, react.useState)([]);
583
+ const [error, setError] = (0, react.useState)(null);
584
+ const [busy, setBusy] = (0, react.useState)(false);
585
+ const [loaded, setLoaded] = (0, react.useState)(false);
586
+ const [saved, setSaved] = (0, react.useState)(false);
587
+ (0, react.useEffect)(() => {
588
+ let alive = true;
589
+ Promise.all([fetchConfig(), fetchWorkspaces()]).then(([cfg, wss]) => {
590
+ if (!alive) return;
591
+ setKnownWorkspaces(wss);
592
+ if (cfg) {
593
+ setConfig(cfg);
594
+ setHost(cfg.gateway.host);
595
+ setPort(cfg.gateway.port);
596
+ setDrafts(cfg.gateway.accounts.map(accountDraftFromWire));
597
+ setWorkspaces(cfg.gateway.workspaces ?? []);
598
+ setCookieSecure(cfg.gateway.cookieSecure === true);
599
+ setAllowWorkspaceCreate(cfg.gateway.allowWorkspaceCreate !== false);
600
+ setTotpEnabled(cfg.gateway.totpEnabled === true);
601
+ }
602
+ setLoaded(true);
603
+ });
604
+ return () => {
605
+ alive = false;
606
+ };
607
+ }, []);
608
+ const updateDraft = (id, patch) => {
609
+ setDrafts((ds) => ds.map((d) => d.id === id ? {
610
+ ...d,
611
+ ...patch
612
+ } : d));
613
+ };
614
+ const addRow = () => {
615
+ setDrafts((ds) => [...ds, {
616
+ id: newAccountId(),
617
+ user: "",
618
+ pass: "",
619
+ isNew: true,
620
+ hasPassword: false,
621
+ hasTotp: false,
622
+ totpState: "none"
623
+ }]);
624
+ };
625
+ const removeRow = (id) => {
626
+ setDrafts((ds) => ds.filter((d) => d.id !== id));
627
+ };
628
+ const startTotpBind = (d) => {
629
+ const secret = newTotpSecret();
630
+ updateDraft(d.id, {
631
+ totpState: "pending",
632
+ totpSecret: secret,
633
+ totpConfirm: ""
634
+ });
635
+ };
636
+ const cancelTotpBind = (d) => {
637
+ updateDraft(d.id, {
638
+ totpState: d.hasTotp ? "none" : "none",
639
+ totpSecret: void 0,
640
+ totpConfirm: ""
641
+ });
642
+ };
643
+ const markTotpClear = (d) => {
644
+ updateDraft(d.id, {
645
+ totpState: "clear",
646
+ totpSecret: void 0,
647
+ totpConfirm: ""
648
+ });
649
+ };
650
+ const toggleWorkspace = (path) => {
651
+ setWorkspaces((ws) => ws.includes(path) ? ws.filter((w) => w !== path) : [...ws, path]);
652
+ };
653
+ const removeWorkspace = (v) => {
654
+ setWorkspaces((ws) => ws.filter((w) => w !== v));
655
+ };
656
+ const save = async () => {
657
+ for (const d of drafts) {
658
+ const err = validateDraft(d);
659
+ if (err) {
660
+ setError(err);
661
+ return;
662
+ }
663
+ }
664
+ setBusy(true);
665
+ setError(null);
666
+ setSaved(false);
667
+ try {
668
+ const cfg = await saveConfig({ gateway: {
669
+ host,
670
+ port,
671
+ accounts: drafts.map(accountToWire),
672
+ workspaces,
673
+ cookieSecure,
674
+ allowWorkspaceCreate,
675
+ totpEnabled
676
+ } });
677
+ if (cfg) {
678
+ setConfig(cfg);
679
+ setDrafts(cfg.gateway.accounts.map(accountDraftFromWire));
680
+ setWorkspaces(cfg.gateway.workspaces ?? []);
681
+ setSaved(true);
682
+ setTimeout(() => setSaved(false), 2e3);
683
+ }
684
+ } catch (err) {
685
+ setError(err instanceof Error ? err.message : String(err));
686
+ } finally {
687
+ setBusy(false);
688
+ }
689
+ };
690
+ if (!loaded) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
691
+ className: "ae-note",
692
+ children: "加载中…"
693
+ });
694
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
695
+ className: "ae-root",
696
+ children: [
697
+ !gatewayOn && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
698
+ className: "ae-warn",
699
+ children: "⚠️ 双端口网关当前关闭——先开启上方「双端口网关」开关,外部访问才生效。"
700
+ }),
701
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
702
+ className: "ae-block",
703
+ children: [
704
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", {
705
+ className: "ae-title",
706
+ children: "监听设置"
707
+ }),
708
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
709
+ className: "ae-row",
710
+ children: [
711
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
712
+ className: "ae-label",
713
+ children: "监听地址"
714
+ }),
715
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
716
+ className: "ae-input",
717
+ value: host,
718
+ onChange: (e) => setHost(e.target.value),
719
+ placeholder: "0.0.0.0"
720
+ }),
721
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
722
+ className: "ae-label",
723
+ children: "端口"
724
+ }),
725
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
726
+ className: "ae-input ae-port",
727
+ type: "number",
728
+ value: port,
729
+ min: 1,
730
+ max: 65535,
731
+ onChange: (e) => setPort(Number(e.target.value) || 0)
732
+ })
733
+ ]
734
+ }),
735
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
736
+ className: "ae-check",
737
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
738
+ type: "checkbox",
739
+ checked: cookieSecure,
740
+ onChange: (e) => setCookieSecure(e.target.checked)
741
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "Secure Cookie(仅 HTTPS 反代时开启;明文 HTTP 下关闭,否则登录态不生效)" })]
742
+ })
743
+ ]
744
+ }),
745
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
746
+ className: "ae-block",
747
+ children: [
748
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", {
749
+ className: "ae-title",
750
+ children: "外部能力"
751
+ }),
752
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
753
+ className: "ae-check",
754
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
755
+ type: "checkbox",
756
+ checked: allowWorkspaceCreate,
757
+ onChange: (e) => setAllowWorkspaceCreate(e.target.checked)
758
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "允许外部新建工作区(关闭后经外部网关的 workspace.create 一律拒绝;仅使用已有工作区)" })]
759
+ }),
760
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
761
+ className: "ae-check",
762
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
763
+ type: "checkbox",
764
+ checked: totpEnabled,
765
+ onChange: (e) => setTotpEnabled(e.target.checked)
766
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "启用 Authenticator 第二因素(关闭时 TOTP 完全禁用;开启后可对账号绑定验证器,登录需输入 6 位码)" })]
767
+ })
768
+ ]
769
+ }),
770
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
771
+ className: "ae-block",
772
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", {
773
+ className: "ae-title",
774
+ children: "登录账号"
775
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
776
+ className: "ae-list",
777
+ children: [
778
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
779
+ className: "ae-head",
780
+ children: [
781
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
782
+ className: "ae-col ae-colUser",
783
+ children: "用户名"
784
+ }),
785
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
786
+ className: "ae-col ae-colPass",
787
+ children: "密码"
788
+ }),
789
+ totpEnabled && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
790
+ className: "ae-col ae-colTotp",
791
+ children: "第二因素"
792
+ }),
793
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "ae-col ae-colOp" })
794
+ ]
795
+ }),
796
+ drafts.map((d) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
797
+ className: "ae-item",
798
+ children: [
799
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
800
+ className: "ae-input ae-colUser",
801
+ value: d.user,
802
+ placeholder: "用户名",
803
+ onChange: (e) => updateDraft(d.id, { user: e.target.value })
804
+ }),
805
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
806
+ className: "ae-input ae-colPass",
807
+ type: "password",
808
+ value: d.pass,
809
+ placeholder: d.isNew ? "必填" : d.hasPassword ? "留空保留原密码" : "未设置",
810
+ onChange: (e) => updateDraft(d.id, { pass: e.target.value })
811
+ }),
812
+ totpEnabled && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
813
+ className: "ae-col ae-colTotp",
814
+ children: d.totpState === "pending" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
815
+ className: "ae-totpPending",
816
+ children: [
817
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
818
+ className: "ae-totpSecret",
819
+ title: "录入到 Authenticator(Google Authenticator / 1Password / Aegis)",
820
+ children: d.totpSecret
821
+ }),
822
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
823
+ className: "ae-totpUri",
824
+ href: d.totpSecret ? otpauthUriClient(d.totpSecret, `${d.user.trim() || "user"}@serenity`) : "#",
825
+ target: "_blank",
826
+ rel: "noreferrer",
827
+ children: "otpauth://"
828
+ }),
829
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
830
+ className: "ae-input ae-totpConfirm",
831
+ type: "text",
832
+ inputMode: "numeric",
833
+ pattern: "[0-9]{6}",
834
+ maxLength: 6,
835
+ value: d.totpConfirm ?? "",
836
+ placeholder: "输入验证码确认",
837
+ onChange: (e) => updateDraft(d.id, { totpConfirm: e.target.value })
838
+ }),
839
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
840
+ type: "button",
841
+ className: "ae-del",
842
+ onClick: () => cancelTotpBind(d),
843
+ children: "取消"
844
+ })
845
+ ]
846
+ }) : d.totpState === "clear" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
847
+ className: "ae-totpClear",
848
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
849
+ className: "ae-totpBadge",
850
+ children: "将解绑"
851
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
852
+ type: "button",
853
+ className: "ae-del",
854
+ onClick: () => updateDraft(d.id, { totpState: "none" }),
855
+ children: "撤销"
856
+ })]
857
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
858
+ type: "button",
859
+ className: "ae-totpBtn",
860
+ onClick: () => d.hasTotp ? markTotpClear(d) : startTotpBind(d),
861
+ title: d.hasTotp ? "已绑定验证器——点击解绑" : "绑定 Authenticator 第二因素",
862
+ children: d.hasTotp ? "✓ 已绑定" : "绑定验证器"
863
+ })
864
+ }),
865
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
866
+ type: "button",
867
+ className: "ae-del",
868
+ onClick: () => removeRow(d.id),
869
+ children: "删除"
870
+ })
871
+ ]
872
+ }, d.id)),
873
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
874
+ type: "button",
875
+ className: "ae-add",
876
+ onClick: addRow,
877
+ children: "+ 添加账号"
878
+ })
879
+ ]
880
+ })]
881
+ }),
882
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
883
+ className: "ae-block",
884
+ children: [
885
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", {
886
+ className: "ae-title",
887
+ children: "工作区白名单"
888
+ }),
889
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
890
+ className: "ae-desc",
891
+ children: "允许外部访问的已有工作区;未选择 = 全部允许。登录后仅白名单内的工作区可见/可用。"
892
+ }),
893
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
894
+ className: "ae-wsList",
895
+ children: workspaces.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
896
+ className: "ae-note",
897
+ children: "(全部允许)"
898
+ }) : workspaces.map((w) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
899
+ className: "ae-wsChip",
900
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
901
+ className: "ae-wsPath",
902
+ children: w
903
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
904
+ type: "button",
905
+ className: "ae-wsDel",
906
+ onClick: () => removeWorkspace(w),
907
+ "aria-label": `移除 ${w}`,
908
+ children: "×"
909
+ })]
910
+ }, w))
911
+ }),
912
+ knownWorkspaces.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
913
+ className: "ae-note",
914
+ children: "暂无可选工作区(workspace.list 未返回条目)"
915
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
916
+ className: "ae-input ae-wsSelect",
917
+ value: "",
918
+ onChange: (e) => {
919
+ if (e.target.value !== "") toggleWorkspace(e.target.value);
920
+ },
921
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
922
+ value: "",
923
+ disabled: true,
924
+ children: "选择已有工作区…"
925
+ }), knownWorkspaces.map((w) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
926
+ value: w.path,
927
+ children: w.title
928
+ }, w.path))]
929
+ })
930
+ ]
931
+ }),
932
+ error !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
933
+ className: "ae-error",
934
+ children: error
935
+ }),
936
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
937
+ className: "ae-actions",
938
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
939
+ type: "button",
940
+ className: "ae-save",
941
+ disabled: busy,
942
+ onClick: () => void save(),
943
+ children: busy ? "保存中…" : saved ? "✓ 已保存" : "保存"
944
+ })
945
+ })
946
+ ]
947
+ });
948
+ }
949
+ //#endregion
634
950
  //#region \0sp-css:/home/yh/home/home-serenity/AI_LAB/dsh-serenity-plugin/hooks/dsh-serenity-hooks/src/client/SettingsSection
635
- const css = "/* SettingsSection — dsh 原生设置面板 serenity-hooks 简单配置页\n 沿用 --dsw-alias-* 语义 token(明暗自适应)+ 紧凑布局 */\n\n.ss-root {\n display: flex;\n flex-direction: column;\n gap: 10px;\n padding: 8px 0;\n}\n\n.ss-note {\n margin: 0 0 4px;\n font-size: 12px;\n opacity: 0.7;\n}\n\n.ss-row {\n display: flex;\n align-items: center;\n gap: 10px;\n font-size: 13px;\n}\n\n.ss-label {\n min-width: 88px;\n font-weight: 600;\n}\n\n.ss-desc {\n flex: 1;\n font-size: 12px;\n opacity: 0.65;\n}\n\n.ss-row input[type='checkbox'] {\n flex: none;\n}\n\n.ss-row input[type='range'] {\n flex: none;\n width: 140px;\n}\n\n.ss-value {\n min-width: 36px;\n font-variant-numeric: tabular-nums;\n font-size: 12px;\n opacity: 0.8;\n}\n\n.ss-degrade {\n padding: 12px 8px;\n font-size: 13px;\n}\n\n.ss-degrade p {\n margin: 0;\n}\n";
951
+ const css = "/* SettingsSection — dsh 原生设置面板 serenity-hooks 简单配置页\n v1.21.1 看齐 dsh 自身:官方 settings 设计语言——\n section > title + intro + group(groupTitle) + rowCard 行卡;\n 全部 --dsw-alias-* token(与 ui-settings-models 同词汇表)。 */\n\n/* 页面容器(官方 .section:flex column gap 12) */\n.ss-section {\n display: flex;\n flex-direction: column;\n gap: 14px;\n max-width: 720px;\n color: var(--dsw-alias-label-primary);\n}\n\n/* 页面标题(官方 .title:16px/24 500) */\n.ss-title {\n margin: 0;\n font-size: 16px;\n line-height: 24px;\n font-weight: 500;\n color: var(--dsw-alias-label-primary);\n}\n\n/* 页面说明(官方 .intro:14px/22 tertiary) */\n.ss-intro {\n margin: 0;\n font-size: 14px;\n line-height: 22px;\n color: var(--dsw-alias-label-tertiary);\n word-break: break-word;\n overflow-wrap: anywhere;\n}\n\n/* 分组(多级标题:分组标题 + 行卡列表) */\n.ss-group {\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n\n/* 分组标题(二级:12px/18 500 secondary——官方 fieldLabel 风格) */\n.ss-groupTitle {\n margin: 0;\n font-size: 12px;\n line-height: 18px;\n font-weight: 600;\n letter-spacing: 0.02em;\n text-transform: uppercase;\n color: var(--dsw-alias-label-secondary);\n}\n\n/* 行卡列表(官方 .rows:无 marker,gap 8) */\n.ss-rows {\n list-style: none;\n margin: 0;\n padding: 0;\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n\n/* 行卡(官方 .rowCard:border-l2 r12 padding 12/14) */\n.ss-rowCard {\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 12px;\n padding: 12px 14px;\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 16px;\n}\n\n/* 行卡文本(标题 + 说明 左列) */\n.ss-rowText {\n display: flex;\n flex-direction: column;\n gap: 3px;\n min-width: 0;\n flex: 1;\n}\n\n/* 行卡标题(官方 .rowName:14px/22 500) */\n.ss-rowName {\n font-size: 14px;\n line-height: 22px;\n font-weight: 500;\n color: var(--dsw-alias-label-primary);\n}\n\n/* 行卡说明(12px/18 tertiary) */\n.ss-rowDesc {\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-tertiary);\n word-break: break-word;\n overflow-wrap: anywhere;\n}\n\n/* 行卡控件(右置) */\n.ss-rowControl {\n flex: none;\n display: flex;\n align-items: center;\n}\n\n/* 开关(官方 token 的自绘 toggle:track 22px,thumb 16px) */\n.ss-switch {\n position: relative;\n flex: none;\n width: 40px;\n height: 22px;\n cursor: pointer;\n}\n\n.ss-switch input {\n position: absolute;\n opacity: 0;\n width: 100%;\n height: 100%;\n margin: 0;\n cursor: pointer;\n}\n\n.ss-switchTrack {\n position: absolute;\n inset: 0;\n border-radius: 11px;\n background: var(--dsw-alias-interactive-bg-active, rgba(128, 128, 128, 0.3));\n transition: background var(--ds-transition-duration-fast, 0.15s) var(--ds-ease-in-out, ease);\n}\n\n.ss-switchTrack::after {\n content: '';\n position: absolute;\n top: 3px;\n left: 3px;\n width: 16px;\n height: 16px;\n border-radius: 50%;\n background: var(--dsw-alias-label-primary, #eee);\n transition: transform var(--ds-transition-duration-fast, 0.15s) var(--ds-ease-in-out, ease);\n}\n\n.ss-switch input:checked + .ss-switchTrack {\n background: var(--dsw-alias-state-success-primary, #22c55e);\n}\n\n.ss-switch input:checked + .ss-switchTrack::after {\n transform: translateX(18px);\n}\n\n.ss-switch input:disabled + .ss-switchTrack {\n opacity: 0.4;\n cursor: default;\n}\n\n/* 阈值行(滑块 + 值) */\n.ss-threshold {\n display: flex;\n align-items: center;\n gap: 12px;\n}\n\n.ss-threshold input[type='range'] {\n width: 160px;\n}\n\n.ss-value {\n min-width: 40px;\n font-variant-numeric: tabular-nums;\n font-size: 13px;\n font-weight: 600;\n color: var(--dsw-alias-label-primary);\n text-align: right;\n}\n\n/* 底部帮助行 */\n.ss-hint {\n margin: 0;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-tertiary);\n word-break: break-word;\n overflow-wrap: anywhere;\n}\n\n/* 降级提示 */\n.ss-degrade {\n margin: 0;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-state-warn-label);\n word-break: break-word;\n overflow-wrap: anywhere;\n}\n";
636
952
  if (typeof document !== "undefined" && document.querySelector("style[data-sp-css]") === null) {
637
953
  const tag = document.createElement("style");
638
954
  tag.setAttribute("data-sp-css", "1");
@@ -642,8 +958,55 @@ window.__ModuleLoader__.load({
642
958
  //#endregion
643
959
  //#region src/client/SettingsSection.tsx
644
960
  /** 降级提示文案(旧 RC 白名单时引导去宁静号面板) */
645
- const DEGRADE_NOTE = "当前运行版本未暴露 serenity-hooks 配置(旧 RC 白名单)。请在会话头部 Serenity 徽章的「高级设定」面板中管理,或升级 DSH。";
646
- /** dsh 原生设置面板:serenity-hooks 简单配置页 */
961
+ const DEGRADE_NOTE = "当前运行版本未暴露 serenity-hooks 配置(旧 RC 白名单)。请升级 DSH 或使用会话头部 Serenity 状态栏。";
962
+ /** 开关(官方 settings 语言:label 右置 toggle,--dsw-alias-* token) */
963
+ function Toggle(props) {
964
+ const { checked, disabled, onChange } = props;
965
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
966
+ className: "ss-switch",
967
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
968
+ type: "checkbox",
969
+ checked,
970
+ disabled,
971
+ onChange: (e) => onChange(e.target.checked)
972
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "ss-switchTrack" })]
973
+ });
974
+ }
975
+ /** 一个配置行卡(官方 rowCard:标题+说明 左列 / 控件右置) */
976
+ function RowCard(props) {
977
+ const { title, desc, control } = props;
978
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
979
+ className: "ss-rowCard",
980
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
981
+ className: "ss-rowText",
982
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
983
+ className: "ss-rowName",
984
+ children: title
985
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
986
+ className: "ss-rowDesc",
987
+ children: desc
988
+ })]
989
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
990
+ className: "ss-rowControl",
991
+ children: control
992
+ })]
993
+ });
994
+ }
995
+ /** 分组(多级标题:分组标题 + 一组行卡) */
996
+ function Group(props) {
997
+ const { title, children } = props;
998
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
999
+ className: "ss-group",
1000
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
1001
+ className: "ss-groupTitle",
1002
+ children: title
1003
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
1004
+ className: "ss-rows",
1005
+ children
1006
+ })]
1007
+ });
1008
+ }
1009
+ /** dsh 原生设置面板:serenity-hooks 配置页(官方 settings 设计语言,多级标题) */
647
1010
  function SettingsSection(props) {
648
1011
  const { scope } = props;
649
1012
  const [snapshot, setSnapshot] = (0, react.useState)(() => scope.getSnapshot());
@@ -659,99 +1022,84 @@ window.__ModuleLoader__.load({
659
1022
  scope.set("rebuildThreshold", Math.min(1, Math.max(.01, v)));
660
1023
  };
661
1024
  if (!ready) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
662
- className: "ss-degrade",
663
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: DEGRADE_NOTE })
1025
+ className: "ss-section",
1026
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1027
+ className: "ss-degrade",
1028
+ children: DEGRADE_NOTE
1029
+ })
664
1030
  });
665
1031
  const gatewayOn = value?.gatewayEnabled ?? false;
666
1032
  const rebuildOn = value?.rebuildEnabled ?? true;
667
1033
  const threshold = value?.rebuildThreshold ?? .9;
668
1034
  const namingOn = value?.namingEnabled ?? true;
669
1035
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
670
- className: "ss-root",
1036
+ className: "ss-section",
671
1037
  children: [
1038
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
1039
+ className: "ss-title",
1040
+ children: "Serenity"
1041
+ }),
672
1042
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
673
- className: "ss-note",
674
- children: "简单配置(开关/阈值)。账号列表等复杂配置请到会话头部 Serenity 徽章「高级设定」面板。"
1043
+ className: "ss-intro",
1044
+ children: "宁静号的 plugin 级配置(全局生效,不依赖具体 CCC)。改动即时保存。"
675
1045
  }),
676
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
677
- className: "ss-row",
678
- children: [
679
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
680
- className: "ss-label",
681
- children: "F1 双端口网关"
682
- }),
683
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
684
- className: "ss-desc",
685
- children: "额外监听端口 + 网页登录(外部访问)"
686
- }),
687
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
688
- type: "checkbox",
1046
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Group, {
1047
+ title: "访问",
1048
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RowCard, {
1049
+ title: "双端口网关",
1050
+ desc: "额外监听一个端口,登录后可从外部访问 Web UI",
1051
+ control: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Toggle, {
689
1052
  checked: gatewayOn,
690
- onChange: (e) => toggle("gatewayEnabled", e.target.checked)
1053
+ onChange: (on) => toggle("gatewayEnabled", on)
691
1054
  })
692
- ]
1055
+ }) })
693
1056
  }),
694
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
695
- className: "ss-row",
696
- children: [
697
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
698
- className: "ss-label",
699
- children: "F2 超限重建"
700
- }),
701
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
702
- className: "ss-desc",
703
- children: "上下文接近上限时提示 session_rebuild 清空重建"
704
- }),
705
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
706
- type: "checkbox",
1057
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Group, {
1058
+ title: "上下文",
1059
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RowCard, {
1060
+ title: "超限重建",
1061
+ desc: "上下文接近上限时提示调用 session_rebuild 清空重建",
1062
+ control: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Toggle, {
707
1063
  checked: rebuildOn,
708
- onChange: (e) => toggle("rebuildEnabled", e.target.checked)
1064
+ onChange: (on) => toggle("rebuildEnabled", on)
709
1065
  })
710
- ]
711
- }),
712
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
713
- className: "ss-row",
714
- children: [
715
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
716
- className: "ss-label",
717
- children: "重建阈值"
718
- }),
719
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
720
- className: "ss-desc",
721
- children: "contextPressure 触发比例(0.01~1)"
722
- }),
723
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
724
- type: "range",
725
- min: .1,
726
- max: 1,
727
- step: .05,
728
- value: threshold,
729
- disabled: !rebuildOn,
730
- onChange: (e) => setThreshold(Number(e.target.value))
731
- }),
732
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
733
- className: "ss-value",
734
- children: threshold.toFixed(2)
1066
+ }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RowCard, {
1067
+ title: "重建阈值",
1068
+ desc: "上下文占用达到该比例时提示(0.10 ~ 1.00)",
1069
+ control: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1070
+ className: "ss-threshold",
1071
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1072
+ type: "range",
1073
+ min: .1,
1074
+ max: 1,
1075
+ step: .05,
1076
+ value: threshold,
1077
+ disabled: !rebuildOn,
1078
+ onChange: (e) => setThreshold(Number(e.target.value))
1079
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1080
+ className: "ss-value",
1081
+ children: threshold.toFixed(2)
1082
+ })]
735
1083
  })
736
- ]
1084
+ }) })]
737
1085
  }),
738
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
739
- className: "ss-row",
740
- children: [
741
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
742
- className: "ss-label",
743
- children: "F3 会话命名"
744
- }),
745
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
746
- className: "ss-desc",
747
- children: "dsh 会话创建时自动命名 S###-日期"
748
- }),
749
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
750
- type: "checkbox",
1086
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Group, {
1087
+ title: "会话",
1088
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RowCard, {
1089
+ title: "会话命名",
1090
+ desc: "使用宁静号会话时,把当前会话命名为 SESSION 目录名",
1091
+ control: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Toggle, {
751
1092
  checked: namingOn,
752
- onChange: (e) => toggle("namingEnabled", e.target.checked)
1093
+ onChange: (on) => toggle("namingEnabled", on)
753
1094
  })
754
- ]
1095
+ }) })
1096
+ }),
1097
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1098
+ className: "ss-group",
1099
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
1100
+ className: "ss-groupTitle",
1101
+ children: "外部访问"
1102
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AccountsEditor, { gatewayOn })]
755
1103
  })
756
1104
  ]
757
1105
  });