@shgroup/dsh-serenity-hooks 1.21.1 → 1.23.1
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/dsh.plugin.json +1 -1
- package/lib/client.js +858 -384
- package/lib/index.js +1874 -1117
- package/package.json +2 -1
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$
|
|
11
|
+
const css$3 = "/* 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$
|
|
15
|
+
tag.textContent = css$3;
|
|
270
16
|
document.head.appendChild(tag);
|
|
271
17
|
}
|
|
272
18
|
//#endregion
|
|
@@ -277,12 +23,11 @@ window.__ModuleLoader__.load({
|
|
|
277
23
|
function cx(...parts) {
|
|
278
24
|
return parts.filter(Boolean).join(" ");
|
|
279
25
|
}
|
|
280
|
-
/** 会话头部:
|
|
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
32
|
const [loopsOpen, setLoopsOpen] = (0, react.useState)(false);
|
|
288
33
|
const sessionId = props.sessionId;
|
|
@@ -346,6 +91,7 @@ window.__ModuleLoader__.load({
|
|
|
346
91
|
})]
|
|
347
92
|
});
|
|
348
93
|
const inCcc = status.root !== null;
|
|
94
|
+
const runningLoops = loops.filter((l) => !l.done);
|
|
349
95
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
350
96
|
type: "button",
|
|
351
97
|
className: cx("sp-card"),
|
|
@@ -372,149 +118,182 @@ window.__ModuleLoader__.load({
|
|
|
372
118
|
className: cx("sp-chev")
|
|
373
119
|
})
|
|
374
120
|
]
|
|
375
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.
|
|
121
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
|
|
376
122
|
open,
|
|
377
123
|
onClose: () => setOpen(false),
|
|
378
|
-
title: "
|
|
124
|
+
title: "CCC 状态栏",
|
|
379
125
|
closeLabel: "关闭",
|
|
380
126
|
className: cx("sp-modal"),
|
|
381
|
-
children:
|
|
382
|
-
className: cx("sp-tabs"),
|
|
383
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
384
|
-
type: "button",
|
|
385
|
-
className: cx("sp-tab", tab === "status" && "sp-tabOn"),
|
|
386
|
-
onClick: () => setTab("status"),
|
|
387
|
-
children: "状态"
|
|
388
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
389
|
-
type: "button",
|
|
390
|
-
className: cx("sp-tab", tab === "accounts" && "sp-tabOn"),
|
|
391
|
-
onClick: () => setTab("accounts"),
|
|
392
|
-
children: "账号"
|
|
393
|
-
})]
|
|
394
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
127
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
395
128
|
className: cx("sp-modalBody"),
|
|
396
|
-
children:
|
|
397
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
398
|
-
className: cx("sp-row"),
|
|
399
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
400
|
-
className: cx("sp-label"),
|
|
401
|
-
children: "CCC"
|
|
402
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
403
|
-
className: cx("sp-value"),
|
|
404
|
-
title: status.root ?? void 0,
|
|
405
|
-
children: status.root
|
|
406
|
-
})]
|
|
407
|
-
}),
|
|
129
|
+
children: inCcc ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
408
130
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
409
|
-
className: cx("sp-
|
|
410
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("
|
|
411
|
-
className: cx("sp-
|
|
412
|
-
children: "
|
|
413
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.
|
|
414
|
-
className: cx("sp-
|
|
415
|
-
title: status.loopModel ? `loop.defaultModel: ${status.loopModel}` : void 0,
|
|
416
|
-
children: status.loopModel ?? "未配置"
|
|
417
|
-
})]
|
|
418
|
-
}),
|
|
419
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
420
|
-
className: cx("sp-row"),
|
|
421
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
422
|
-
className: cx("sp-label"),
|
|
423
|
-
children: "守卫"
|
|
424
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
425
|
-
className: cx("sp-value"),
|
|
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"),
|
|
426
137
|
children: [
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
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
|
+
})
|
|
431
175
|
]
|
|
432
176
|
})]
|
|
433
177
|
}),
|
|
434
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
435
|
-
className: cx("sp-
|
|
436
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
children: [
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
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", {
|
|
186
|
+
type: "button",
|
|
187
|
+
className: cx("sp-toggle", status.safeModeOn ? "sp-toggleOn" : "sp-toggleOff", "sp-toggleLg"),
|
|
188
|
+
disabled: busy,
|
|
189
|
+
onClick: () => void toggle(!status.safeModeOn),
|
|
190
|
+
children: [
|
|
191
|
+
status.safeModeOn && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconWarningOutline16, {
|
|
192
|
+
size: 14,
|
|
193
|
+
className: cx("sp-toggleIcon")
|
|
194
|
+
}),
|
|
195
|
+
"safe-mode ",
|
|
196
|
+
status.safeModeOn ? "ON" : "OFF"
|
|
197
|
+
]
|
|
198
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
199
|
+
className: cx("sp-toggleDesc"),
|
|
200
|
+
children: status.safeModeOn ? "写工具已隐藏,无人值守安全航行" : "写工具可用(点击开启)"
|
|
201
|
+
})]
|
|
202
|
+
})]
|
|
450
203
|
}),
|
|
451
204
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
452
|
-
className: cx("sp-
|
|
453
|
-
children: [
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("
|
|
457
|
-
className: cx("sp-
|
|
458
|
-
children: "
|
|
459
|
-
}),
|
|
460
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
461
|
-
className: cx("sp-value"),
|
|
462
|
-
children: loops.filter((l) => !l.done).length > 0 ? `运行中 ${loops.filter((l) => !l.done).length}${loops.length > 0 ? ` / 共 ${loops.length}` : ""}` : "无运行中 loop"
|
|
463
|
-
}),
|
|
464
|
-
loops.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
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", {
|
|
465
213
|
type: "button",
|
|
466
214
|
className: cx("sp-loopsToggle"),
|
|
467
215
|
onClick: () => setLoopsOpen((v) => !v),
|
|
468
216
|
children: loopsOpen ? "收起" : "详情"
|
|
217
|
+
})]
|
|
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
|
+
})]
|
|
469
230
|
})
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
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"),
|
|
477
260
|
children: [
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
children: l.label
|
|
482
|
-
}),
|
|
483
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
484
|
-
className: cx("sp-loopRound"),
|
|
485
|
-
children: l.done ? "✓" : `R${l.round}`
|
|
486
|
-
}),
|
|
487
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
488
|
-
className: cx("sp-loopTime"),
|
|
489
|
-
children: new Date(l.updated).toLocaleTimeString()
|
|
490
|
-
})
|
|
261
|
+
"… 共 ",
|
|
262
|
+
loops.length,
|
|
263
|
+
" 个 loop(其余见等待界面)"
|
|
491
264
|
]
|
|
492
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
493
|
-
className: cx("sp-loopResp"),
|
|
494
|
-
title: l.lastResponse,
|
|
495
|
-
children: l.lastResponse.slice(0, 80) || (l.done ? "已完成" : "(等待首轮响应)")
|
|
496
265
|
})]
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
})
|
|
505
|
-
|
|
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
|
+
]
|
|
506
276
|
})
|
|
507
277
|
] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
508
|
-
className: cx("sp-
|
|
509
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("
|
|
510
|
-
className: cx("sp-
|
|
278
|
+
className: cx("sp-group"),
|
|
279
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
280
|
+
className: cx("sp-groupTitle"),
|
|
511
281
|
children: "状态"
|
|
512
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("
|
|
513
|
-
className: cx("sp-
|
|
514
|
-
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
|
+
})]
|
|
293
|
+
})
|
|
515
294
|
})]
|
|
516
295
|
})
|
|
517
|
-
})
|
|
296
|
+
})
|
|
518
297
|
})] });
|
|
519
298
|
}
|
|
520
299
|
//#endregion
|
|
@@ -528,8 +307,8 @@ window.__ModuleLoader__.load({
|
|
|
528
307
|
* 单图:用户提供了一张图片(路径:...);多图:每张一行路径
|
|
529
308
|
*/
|
|
530
309
|
function imageNoteTemplate(paths) {
|
|
531
|
-
if (paths.length === 1) return
|
|
532
|
-
return
|
|
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")}`;
|
|
533
312
|
}
|
|
534
313
|
/** 浏览器 File → base64(与 ui-conversation serializeImages 等价的最小实现) */
|
|
535
314
|
function fileToBase64(file) {
|
|
@@ -641,6 +420,691 @@ window.__ModuleLoader__.load({
|
|
|
641
420
|
return null;
|
|
642
421
|
}
|
|
643
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 + 工作区白名单 + persona 彩蛋)→ 返回保存后的 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$2 = "/* 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$2;
|
|
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
|
|
950
|
+
//#region \0sp-css:/home/yh/home/home-serenity/AI_LAB/dsh-serenity-plugin/hooks/dsh-serenity-hooks/src/client/PersonaEditor
|
|
951
|
+
const css$1 = "/* PersonaEditor.css — 彩蛋模式区块(v1.23.1,官方 settings 设计语言:rowCard 内分组) */\n\n.pe-wrap {\n display: flex;\n flex-direction: column;\n gap: 10px;\n width: 100%;\n padding: 2px 0 6px;\n}\n\n.pe-row {\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n.pe-label {\n font-size: 12px;\n color: var(--dsw-alias-text-secondary, #8a8f98);\n user-select: none;\n}\n\n.pe-input,\n.pe-textarea {\n width: 100%;\n box-sizing: border-box;\n background: var(--dsw-alias-bg-secondary, rgba(128, 128, 128, 0.08));\n color: var(--dsw-alias-text-primary, #e6e6e6);\n border: 1px solid var(--dsw-alias-border, rgba(128, 128, 128, 0.25));\n border-radius: 8px;\n font-size: 13px;\n padding: 8px 10px;\n outline: none;\n}\n\n.pe-input:focus,\n.pe-textarea:focus {\n border-color: var(--dsw-alias-accent, #4f8cff);\n}\n\n.pe-textarea {\n resize: vertical;\n min-height: 120px;\n line-height: 1.5;\n font-family: var(--dsw-alias-font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);\n font-size: 12px;\n}\n\n.pe-actions {\n display: flex;\n gap: 8px;\n align-items: center;\n flex-wrap: wrap;\n}\n\n.pe-btn {\n border-radius: 6px;\n padding: 5px 12px;\n font-size: 12px;\n cursor: pointer;\n border: 1px solid var(--dsw-alias-border, rgba(128, 128, 128, 0.25));\n background: transparent;\n color: var(--dsw-alias-text-primary, #e6e6e6);\n transition: opacity 0.15s;\n}\n\n.pe-btn:hover:not(:disabled) {\n opacity: 0.85;\n}\n\n.pe-btn:disabled {\n opacity: 0.4;\n cursor: default;\n}\n\n.pe-btnGhost {\n border-color: var(--dsw-alias-border, rgba(128, 128, 128, 0.25));\n}\n\n.pe-btnPrimary {\n background: var(--dsw-alias-accent, #4f8cff);\n border-color: transparent;\n color: #fff;\n}\n\n.pe-hint {\n font-size: 12px;\n margin: 0;\n}\n\n.pe-hintOk {\n color: var(--dsw-alias-state-success, #4caf50);\n}\n\n.pe-hintErr {\n color: var(--dsw-alias-state-error, #e5484d);\n}\n";
|
|
952
|
+
if (typeof document !== "undefined" && document.querySelector("style[data-sp-css]") === null) {
|
|
953
|
+
const tag = document.createElement("style");
|
|
954
|
+
tag.setAttribute("data-sp-css", "1");
|
|
955
|
+
tag.textContent = css$1;
|
|
956
|
+
document.head.appendChild(tag);
|
|
957
|
+
}
|
|
958
|
+
//#endregion
|
|
959
|
+
//#region src/client/PersonaEditor.tsx
|
|
960
|
+
/**
|
|
961
|
+
* PersonaEditor.tsx — DSH 设置面板「彩蛋模式」区块(v1.23.1,S142 用户需求)
|
|
962
|
+
*
|
|
963
|
+
* 彩蛋功能:用户可替换 ACC 系统提示词中"输出约束/指令遵循约束"部分
|
|
964
|
+
* (EAP 块 + MSM 原则段),配置后 agent 输出风格/指令遵循风格随用户文本变化;
|
|
965
|
+
* 未配置(模式名空)→ 完全默认行为,零影响。
|
|
966
|
+
*
|
|
967
|
+
* 数据:GET/PUT /serenity/config 的 persona 节(存 ~/.dsh/serenity-hooks.json
|
|
968
|
+
* plugin 全局文件,同账号配置归属原则)。保存 → PUT → 服务端落盘 →
|
|
969
|
+
* 热生效(system-prompt 装配每次读全局文件,无需重启)。
|
|
970
|
+
*
|
|
971
|
+
* 预置:一键填充「大肥鱼」demo 文本(DeepSeek 社区娘化人格测试用)。
|
|
972
|
+
*/
|
|
973
|
+
/** 大肥鱼模式 demo 提示词(测试彩蛋替换面:输出风格 + 指令遵循风格) */
|
|
974
|
+
const BIG_FAT_FISH_DEMO = `You are now in BIG FAT FISH (大肥鱼) mode — the DeepSeek community's beloved
|
|
975
|
+
whale-girl persona: a round, blue, snacky fish who loves plain white rice and
|
|
976
|
+
hates unnecessary fuss. The persona only colors HOW you say things and HOW you
|
|
977
|
+
follow instructions — it never reduces the quality or safety of the work.
|
|
978
|
+
|
|
979
|
+
Output style:
|
|
980
|
+
• Keep the EAP spirit (E↑ explicit, R↓ reconstructable, S↑ stable) — but say
|
|
981
|
+
it like a fish: "把话说清楚,不然我这条鱼会晕" (be explicit, or this fish gets dizzy)
|
|
982
|
+
• Start replies with a light, lazy tone ("唔…", "摸鱼中…"), then get serious
|
|
983
|
+
and do the actual work properly — no slacking on the task itself
|
|
984
|
+
• Prefer short, concrete sentences; fish-brain means no vague words
|
|
985
|
+
• When reporting progress, be honest and specific — never fake completion
|
|
986
|
+
just to go eat rice earlier
|
|
987
|
+
|
|
988
|
+
Instruction-following style:
|
|
989
|
+
• Follow instructions exactly as given (this fish follows the recipe, not its appetite)
|
|
990
|
+
• If an instruction is ambiguous, ask before guessing — guessing wrong wastes
|
|
991
|
+
both rice and time
|
|
992
|
+
• Keep the container rules sacred: never bypass safety guards, never write to
|
|
993
|
+
protected paths, never fake tool results. A lazy fish, but an honest one
|
|
994
|
+
• Report blockers plainly — "我卡住了,别让我装不卡"`;
|
|
995
|
+
function PersonaEditor() {
|
|
996
|
+
const [mode, setMode] = (0, react.useState)("");
|
|
997
|
+
const [overrideText, setOverrideText] = (0, react.useState)("");
|
|
998
|
+
const [loaded, setLoaded] = (0, react.useState)(false);
|
|
999
|
+
const [busy, setBusy] = (0, react.useState)(false);
|
|
1000
|
+
const [error, setError] = (0, react.useState)(null);
|
|
1001
|
+
const [saved, setSaved] = (0, react.useState)(false);
|
|
1002
|
+
(0, react.useEffect)(() => {
|
|
1003
|
+
let alive = true;
|
|
1004
|
+
fetchConfig().then((cfg) => {
|
|
1005
|
+
if (!alive || !cfg) return;
|
|
1006
|
+
setMode(cfg.persona?.mode ?? "");
|
|
1007
|
+
setOverrideText(cfg.persona?.overrideText ?? "");
|
|
1008
|
+
setLoaded(true);
|
|
1009
|
+
});
|
|
1010
|
+
return () => {
|
|
1011
|
+
alive = false;
|
|
1012
|
+
};
|
|
1013
|
+
}, []);
|
|
1014
|
+
const save = () => {
|
|
1015
|
+
setBusy(true);
|
|
1016
|
+
setError(null);
|
|
1017
|
+
setSaved(false);
|
|
1018
|
+
saveConfig({ persona: {
|
|
1019
|
+
mode,
|
|
1020
|
+
overrideText
|
|
1021
|
+
} }).then((cfg) => {
|
|
1022
|
+
if (cfg) {
|
|
1023
|
+
setMode(cfg.persona?.mode ?? mode);
|
|
1024
|
+
setOverrideText(cfg.persona?.overrideText ?? overrideText);
|
|
1025
|
+
setSaved(true);
|
|
1026
|
+
setTimeout(() => setSaved(false), 2e3);
|
|
1027
|
+
}
|
|
1028
|
+
}).catch((e) => setError(e instanceof Error ? e.message : String(e))).finally(() => setBusy(false));
|
|
1029
|
+
};
|
|
1030
|
+
const fillDemo = () => {
|
|
1031
|
+
setMode("大肥鱼");
|
|
1032
|
+
setOverrideText(BIG_FAT_FISH_DEMO);
|
|
1033
|
+
};
|
|
1034
|
+
const clear = () => {
|
|
1035
|
+
setMode("");
|
|
1036
|
+
setOverrideText("");
|
|
1037
|
+
};
|
|
1038
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1039
|
+
className: "pe-wrap",
|
|
1040
|
+
children: [
|
|
1041
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1042
|
+
className: "pe-row",
|
|
1043
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
|
|
1044
|
+
className: "pe-label",
|
|
1045
|
+
htmlFor: "pe-mode",
|
|
1046
|
+
children: "模式名"
|
|
1047
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1048
|
+
id: "pe-mode",
|
|
1049
|
+
className: "pe-input",
|
|
1050
|
+
type: "text",
|
|
1051
|
+
value: mode,
|
|
1052
|
+
placeholder: "留空 = 彩蛋关闭(默认行为)",
|
|
1053
|
+
onChange: (e) => setMode(e.target.value)
|
|
1054
|
+
})]
|
|
1055
|
+
}),
|
|
1056
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1057
|
+
className: "pe-row",
|
|
1058
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
|
|
1059
|
+
className: "pe-label",
|
|
1060
|
+
htmlFor: "pe-text",
|
|
1061
|
+
children: "替换文本(替代输出约束 + 指令遵循约束)"
|
|
1062
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
|
|
1063
|
+
id: "pe-text",
|
|
1064
|
+
className: "pe-textarea",
|
|
1065
|
+
rows: 10,
|
|
1066
|
+
value: overrideText,
|
|
1067
|
+
placeholder: "配置后:EAP 块 + MSM 原则段被此文本替换;未配置:完全默认。",
|
|
1068
|
+
onChange: (e) => setOverrideText(e.target.value)
|
|
1069
|
+
})]
|
|
1070
|
+
}),
|
|
1071
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1072
|
+
className: "pe-actions",
|
|
1073
|
+
children: [
|
|
1074
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1075
|
+
className: "pe-btn pe-btnGhost",
|
|
1076
|
+
type: "button",
|
|
1077
|
+
onClick: fillDemo,
|
|
1078
|
+
children: "填充「大肥鱼」demo"
|
|
1079
|
+
}),
|
|
1080
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1081
|
+
className: "pe-btn pe-btnGhost",
|
|
1082
|
+
type: "button",
|
|
1083
|
+
onClick: clear,
|
|
1084
|
+
disabled: !loaded,
|
|
1085
|
+
children: "清空(关闭彩蛋)"
|
|
1086
|
+
}),
|
|
1087
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1088
|
+
className: "pe-btn pe-btnPrimary",
|
|
1089
|
+
type: "button",
|
|
1090
|
+
onClick: save,
|
|
1091
|
+
disabled: busy || !loaded,
|
|
1092
|
+
children: busy ? "保存中…" : "保存"
|
|
1093
|
+
})
|
|
1094
|
+
]
|
|
1095
|
+
}),
|
|
1096
|
+
saved && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1097
|
+
className: "pe-hint pe-hintOk",
|
|
1098
|
+
children: "已保存,新会话即时生效"
|
|
1099
|
+
}),
|
|
1100
|
+
error && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1101
|
+
className: "pe-hint pe-hintErr",
|
|
1102
|
+
children: error
|
|
1103
|
+
})
|
|
1104
|
+
]
|
|
1105
|
+
});
|
|
1106
|
+
}
|
|
1107
|
+
//#endregion
|
|
644
1108
|
//#region \0sp-css:/home/yh/home/home-serenity/AI_LAB/dsh-serenity-plugin/hooks/dsh-serenity-hooks/src/client/SettingsSection
|
|
645
1109
|
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";
|
|
646
1110
|
if (typeof document !== "undefined" && document.querySelector("style[data-sp-css]") === null) {
|
|
@@ -652,7 +1116,7 @@ window.__ModuleLoader__.load({
|
|
|
652
1116
|
//#endregion
|
|
653
1117
|
//#region src/client/SettingsSection.tsx
|
|
654
1118
|
/** 降级提示文案(旧 RC 白名单时引导去宁静号面板) */
|
|
655
|
-
const DEGRADE_NOTE = "当前运行版本未暴露 serenity-hooks 配置(旧 RC
|
|
1119
|
+
const DEGRADE_NOTE = "当前运行版本未暴露 serenity-hooks 配置(旧 RC 白名单)。请升级 DSH 或使用会话头部 Serenity 状态栏。";
|
|
656
1120
|
/** 开关(官方 settings 语言:label 右置 toggle,--dsw-alias-* token) */
|
|
657
1121
|
function Toggle(props) {
|
|
658
1122
|
const { checked, disabled, onChange } = props;
|
|
@@ -700,7 +1164,7 @@ window.__ModuleLoader__.load({
|
|
|
700
1164
|
})]
|
|
701
1165
|
});
|
|
702
1166
|
}
|
|
703
|
-
/** dsh 原生设置面板:serenity-hooks
|
|
1167
|
+
/** dsh 原生设置面板:serenity-hooks 配置页(官方 settings 设计语言,多级标题) */
|
|
704
1168
|
function SettingsSection(props) {
|
|
705
1169
|
const { scope } = props;
|
|
706
1170
|
const [snapshot, setSnapshot] = (0, react.useState)(() => scope.getSnapshot());
|
|
@@ -735,7 +1199,7 @@ window.__ModuleLoader__.load({
|
|
|
735
1199
|
}),
|
|
736
1200
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
737
1201
|
className: "ss-intro",
|
|
738
|
-
children: "
|
|
1202
|
+
children: "宁静号的 plugin 级配置(全局生效,不依赖具体 CCC)。改动即时保存。"
|
|
739
1203
|
}),
|
|
740
1204
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Group, {
|
|
741
1205
|
title: "访问",
|
|
@@ -788,9 +1252,19 @@ window.__ModuleLoader__.load({
|
|
|
788
1252
|
})
|
|
789
1253
|
}) })
|
|
790
1254
|
}),
|
|
791
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
792
|
-
className: "ss-
|
|
793
|
-
children:
|
|
1255
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1256
|
+
className: "ss-group",
|
|
1257
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
1258
|
+
className: "ss-groupTitle",
|
|
1259
|
+
children: "外部访问"
|
|
1260
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AccountsEditor, { gatewayOn })]
|
|
1261
|
+
}),
|
|
1262
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1263
|
+
className: "ss-group",
|
|
1264
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
1265
|
+
className: "ss-groupTitle",
|
|
1266
|
+
children: "彩蛋模式"
|
|
1267
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PersonaEditor, {})]
|
|
794
1268
|
})
|
|
795
1269
|
]
|
|
796
1270
|
});
|