@mackwan84/dsh-tool-ui-mockup 0.1.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 ADDED
@@ -0,0 +1,1998 @@
1
+ window.__ModuleLoader__.load({
2
+ id: "@mackwan84/dsh-tool-ui-mockup",
3
+ factory: (require) => {
4
+ var module = { exports: {} };
5
+ var exports = module.exports;
6
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
+ let react = require("react");
8
+ let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
9
+ let react_jsx_runtime = require("react/jsx-runtime");
10
+ //#region src/client/shared.ts
11
+ /** 工具卡片与设置面板共享的客户端最小面:图片路由 URL 与 RPC 调用折叠。 */
12
+ /** 图片文件名 → webServer 路由 URL(host 半区 /ui-mockup/images 服务资产库图片)。 */
13
+ function imageUrl(name, cwd) {
14
+ const base = `/ui-mockup/images/${encodeURIComponent(name)}`;
15
+ return cwd !== void 0 && cwd !== "" ? `${base}?cwd=${encodeURIComponent(cwd)}` : base;
16
+ }
17
+ /**
18
+ * 把 RPC 结果折叠成「值或抛错」:错误信息面向用户可读,
19
+ * 错误码保留在消息前缀便于排障。
20
+ * 注意:宿主信封校验要求 payload 必填(clientRequestSchema 的 payload 为
21
+ * z.unknown() 非可选),JSON.stringify 会丢弃值为 undefined 的字段,因此
22
+ * 无参端点(overview / test-connection)也必须发送一个 {}。
23
+ */
24
+ async function callPanel(connection, endpoint, payload = {}) {
25
+ const result = await connection.rpc.call("/ui-mockup", endpoint, payload ?? {});
26
+ if (!result.ok) throw new Error(`[${result.error.code}] ${result.error.message}`);
27
+ return result.value;
28
+ }
29
+ /** 锚点在过滤后列表中的索引 → 所在页码(1-based);无锚点(-1)返回 null。 */
30
+ function anchorPageOf(anchorIndex, pageSize) {
31
+ return anchorIndex < 0 ? null : Math.floor(anchorIndex / pageSize) + 1;
32
+ }
33
+ //#endregion
34
+ //#region src/client/toolview.tsx
35
+ /**
36
+ * ui_mockup 工具卡片:生成图内嵌展示 + 确认/选用/修改意见反馈按钮。
37
+ * 纯展示组件,接收 tool.call.toolview 的 owner payload 与框架注入的
38
+ * `t`(i18n)和 `inputActions`(反馈按钮通过 setDraft + submit 发送消息)。
39
+ * 视觉与 DSH 原生一致:Button 原语 + --dsw-alias-* 主题令牌。
40
+ */
41
+ /** 反馈按钮发送给 agent 的消息正文(中文固定,模型可见文本不随 UI 语言切换)。 */
42
+ function buildFeedbackMessage(t, name, index, opinion) {
43
+ if (opinion !== void 0 && opinion.trim() !== "") return t("card.feedbackMessage", {
44
+ opinion: opinion.trim(),
45
+ name
46
+ });
47
+ return t("card.selectMessage", {
48
+ n: index + 1,
49
+ name
50
+ });
51
+ }
52
+ function UiMockupToolview({ block, inputActions, cwd, t, anchor }) {
53
+ const [showFeedback, setShowFeedback] = (0, react.useState)(false);
54
+ const [opinion, setOpinion] = (0, react.useState)("");
55
+ const [selected, setSelected] = (0, react.useState)("");
56
+ const [anchorPicked, setAnchorPicked] = (0, react.useState)("");
57
+ const [anchoredNames, setAnchoredNames] = (0, react.useState)(/* @__PURE__ */ new Set());
58
+ const [anchorError, setAnchorError] = (0, react.useState)("");
59
+ const setAnchor = async (name) => {
60
+ if (anchor === void 0) return;
61
+ setAnchorError("");
62
+ try {
63
+ await anchor.set(name, cwd);
64
+ setAnchoredNames((prev) => new Set(prev).add(name));
65
+ } catch (err) {
66
+ setAnchorError(err instanceof Error ? err.message : String(err));
67
+ }
68
+ };
69
+ if (!("kind" in block)) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
70
+ style: {
71
+ padding: "8px 0",
72
+ fontSize: 13,
73
+ color: "var(--dsw-alias-label-tertiary)"
74
+ },
75
+ children: t("card.generating")
76
+ });
77
+ const images = block.content.filter((item) => item.type === "image");
78
+ const message = block.content.filter((item) => item.type === "text").map((item) => item.text).join("");
79
+ if (images.length === 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
80
+ style: {
81
+ padding: "8px 0",
82
+ whiteSpace: "pre-wrap",
83
+ fontSize: 13,
84
+ lineHeight: "20px",
85
+ color: "var(--dsw-alias-label-primary)"
86
+ },
87
+ children: message
88
+ });
89
+ const send = (text) => {
90
+ inputActions.setDraft(text);
91
+ inputActions.submit();
92
+ };
93
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
94
+ style: {
95
+ display: "flex",
96
+ flexDirection: "column",
97
+ gap: 8,
98
+ padding: "8px 0"
99
+ },
100
+ children: [
101
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
102
+ style: {
103
+ display: "flex",
104
+ flexWrap: "wrap",
105
+ gap: 8
106
+ },
107
+ children: images.map((image, index) => {
108
+ const name = image.attachment.name ?? `mockup-${index + 1}.png`;
109
+ const anchored = anchoredNames.has(name);
110
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("figure", {
111
+ style: { margin: 0 },
112
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
113
+ src: imageUrl(name, cwd),
114
+ alt: name,
115
+ loading: "lazy",
116
+ style: {
117
+ maxWidth: 240,
118
+ maxHeight: 240,
119
+ borderRadius: 8,
120
+ border: `1px solid ${anchored ? "var(--dsw-alias-brand-primary)" : "var(--dsw-alias-border-l2)"}`,
121
+ objectFit: "contain",
122
+ background: "var(--dsw-alias-bg-layer-3)"
123
+ }
124
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("figcaption", {
125
+ style: {
126
+ fontSize: 12,
127
+ lineHeight: "17px",
128
+ color: "var(--dsw-alias-label-tertiary)",
129
+ marginTop: 2
130
+ },
131
+ children: [name, anchored ? ` · ${t("card.anchored")}` : ""]
132
+ })]
133
+ }, `${name}:${index}`);
134
+ })
135
+ }),
136
+ anchorError !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
137
+ style: {
138
+ fontSize: 12,
139
+ color: "var(--dsw-alias-label-error)"
140
+ },
141
+ children: anchorError
142
+ }),
143
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
144
+ style: {
145
+ display: "flex",
146
+ flexWrap: "wrap",
147
+ gap: 8,
148
+ alignItems: "center"
149
+ },
150
+ children: [
151
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
152
+ variant: "primary",
153
+ size: "sm",
154
+ onClick: () => send(t("card.confirmMessage", { name: images[0].attachment.name ?? "mockup-1.png" })),
155
+ children: t("card.confirm")
156
+ }),
157
+ images.length > 1 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
158
+ value: selected,
159
+ onChange: (event) => {
160
+ const raw = event.target.value;
161
+ if (raw === "") return;
162
+ const index = Number(raw);
163
+ setSelected("");
164
+ const name = images[index].attachment.name ?? `mockup-${index + 1}.png`;
165
+ send(buildFeedbackMessage(t, name, index));
166
+ },
167
+ style: {
168
+ height: 28,
169
+ padding: "0 8px",
170
+ border: "1px solid var(--dsw-alias-border-l2)",
171
+ borderRadius: 999,
172
+ background: "var(--dsw-alias-bg-layer-3)",
173
+ font: "inherit",
174
+ fontSize: 13,
175
+ color: "var(--dsw-alias-label-primary)"
176
+ },
177
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
178
+ value: "",
179
+ disabled: true,
180
+ children: t("card.selectPlaceholder")
181
+ }), images.map((image, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
182
+ value: index,
183
+ children: t("card.select", { n: index + 1 })
184
+ }, index))]
185
+ }),
186
+ anchor !== void 0 && (images.length === 1 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
187
+ variant: "ghost",
188
+ size: "sm",
189
+ title: t("card.setAnchor"),
190
+ onClick: () => void setAnchor(images[0].attachment.name ?? "mockup-1.png"),
191
+ children: t("card.setAnchorButton")
192
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
193
+ value: anchorPicked,
194
+ onChange: (event) => {
195
+ const raw = event.target.value;
196
+ if (raw === "") return;
197
+ const index = Number(raw);
198
+ setAnchorPicked("");
199
+ const name = images[index].attachment.name ?? `mockup-${index + 1}.png`;
200
+ setAnchor(name);
201
+ },
202
+ title: t("card.setAnchor"),
203
+ style: {
204
+ height: 28,
205
+ padding: "0 8px",
206
+ border: "1px solid var(--dsw-alias-border-l2)",
207
+ borderRadius: 999,
208
+ background: "var(--dsw-alias-bg-layer-3)",
209
+ font: "inherit",
210
+ fontSize: 13,
211
+ color: "var(--dsw-alias-label-primary)"
212
+ },
213
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
214
+ value: "",
215
+ disabled: true,
216
+ children: t("card.setAnchorSelect")
217
+ }), images.map((image, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
218
+ value: index,
219
+ children: t("card.setAnchorOption", { n: index + 1 })
220
+ }, index))]
221
+ })),
222
+ (() => {
223
+ const firstName = images[0].attachment.name ?? "mockup-1.png";
224
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
225
+ href: imageUrl(firstName, cwd),
226
+ target: "_blank",
227
+ rel: "noreferrer",
228
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
229
+ variant: "ghost",
230
+ size: "sm",
231
+ children: t("card.openOriginal")
232
+ })
233
+ });
234
+ })(),
235
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
236
+ variant: "ghost",
237
+ size: "sm",
238
+ onClick: () => setShowFeedback((value) => !value),
239
+ children: t("card.feedback")
240
+ })
241
+ ]
242
+ }),
243
+ showFeedback && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
244
+ style: {
245
+ display: "flex",
246
+ flexDirection: "column",
247
+ gap: 6
248
+ },
249
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
250
+ value: opinion,
251
+ onChange: (event) => setOpinion(event.target.value),
252
+ placeholder: t("card.feedbackPlaceholder"),
253
+ rows: 2,
254
+ style: {
255
+ resize: "vertical",
256
+ padding: "6px 12px",
257
+ border: "1px solid var(--dsw-alias-border-l2)",
258
+ borderRadius: 8,
259
+ background: "var(--dsw-alias-bg-layer-3)",
260
+ font: "inherit",
261
+ fontSize: 13,
262
+ lineHeight: "20px",
263
+ color: "var(--dsw-alias-label-primary)"
264
+ }
265
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
266
+ style: {
267
+ display: "flex",
268
+ gap: 8
269
+ },
270
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
271
+ variant: "primary",
272
+ size: "sm",
273
+ onClick: () => {
274
+ const name = images[0].attachment.name ?? "mockup-1.png";
275
+ send(buildFeedbackMessage(t, name, 0, opinion));
276
+ setOpinion("");
277
+ setShowFeedback(false);
278
+ },
279
+ children: t("card.feedbackSubmit")
280
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
281
+ variant: "ghost",
282
+ size: "sm",
283
+ onClick: () => setShowFeedback(false),
284
+ children: t("card.feedbackCancel")
285
+ })]
286
+ })]
287
+ })
288
+ ]
289
+ });
290
+ }
291
+ //#endregion
292
+ //#region src/client/settings-panel.css?inline
293
+ var settings_panel_default = ".ui-mockup-section, .ui-mockup-tabpanel, .ui-mockup-history-toolbar, .ui-mockup-history-row, .ui-mockup-history-summary {\n min-width: 0;\n}\n\n.ui-mockup-history-summary {\n overflow-wrap: anywhere;\n flex: 1 1 0;\n}\n\n.ui-mockup-section {\n box-sizing: border-box;\n container-type: inline-size;\n}\n\n.ui-mockup-card, .ui-mockup-provider-card, .ui-mockup-overview, .ui-mockup-overview-status, .ui-mockup-quick-step {\n box-sizing: border-box;\n min-width: 0;\n max-width: 100%;\n}\n\n.ui-mockup-tabs {\n scrollbar-width: thin;\n gap: 22px;\n max-width: 100%;\n overflow: auto hidden;\n}\n\n.ui-mockup-tab {\n white-space: nowrap;\n flex: none;\n}\n\n.ui-mockup-field-label {\n min-width: 96px;\n}\n\n.ui-mockup-model-select, .ui-mockup-field-row > * {\n max-width: 100%;\n}\n\n.ui-mockup-history-search {\n flex: 220px;\n min-width: 0;\n}\n\n.ui-mockup-history-pagination, .ui-mockup-history-pages, .ui-mockup-history-anchor-nav {\n min-width: 0;\n max-width: 100%;\n}\n\n.ui-mockup-history-page-number {\n min-width: 28px;\n}\n\n@container (width <= 640px) {\n .ui-mockup-tabs {\n gap: 16px;\n }\n\n .ui-mockup-field-label {\n flex: 1 0 100%;\n min-width: 0;\n }\n\n .ui-mockup-card, .ui-mockup-provider-card, .ui-mockup-preferences-footer, .ui-mockup-overview, .ui-mockup-overview-status, .ui-mockup-quick-step {\n overflow-wrap: anywhere;\n overflow: hidden;\n }\n\n .ui-mockup-overview-status, .ui-mockup-quick-step {\n flex-wrap: wrap;\n }\n\n .ui-mockup-field-row > *, .ui-mockup-preferences-footer > *, .ui-mockup-preferences-actions > *, .ui-mockup-preferences-actions button {\n box-sizing: border-box;\n min-width: 0;\n max-width: 100%;\n }\n\n .ui-mockup-preferences-footer button, .ui-mockup-preferences-actions button {\n white-space: nowrap;\n text-overflow: ellipsis;\n overflow: hidden;\n }\n\n .ui-mockup-preferences-footer, .ui-mockup-history-toolbar, .ui-mockup-history-row, .ui-mockup-history-anchor-nav {\n flex-wrap: wrap;\n }\n\n .ui-mockup-history-toolbar, .ui-mockup-history-row, .ui-mockup-history-actions, .ui-mockup-history-pagination, .ui-mockup-history-pages, .ui-mockup-history-anchor-nav {\n overflow: hidden;\n }\n\n .ui-mockup-history-pages {\n flex-wrap: wrap;\n }\n\n .ui-mockup-history-toolbar > *, .ui-mockup-history-actions > *, .ui-mockup-history-actions button, .ui-mockup-history-anchor-nav > * {\n box-sizing: border-box;\n min-width: 0;\n max-width: 100%;\n }\n\n .ui-mockup-history-toolbar button, .ui-mockup-history-actions button, .ui-mockup-history-pages button, .ui-mockup-history-anchor-nav button {\n white-space: nowrap;\n text-overflow: ellipsis;\n min-width: 0;\n max-width: 100%;\n overflow: hidden;\n }\n\n .ui-mockup-history-row > img {\n max-width: 100%;\n height: auto;\n }\n\n .ui-mockup-preferences-actions, .ui-mockup-history-actions {\n width: 100%;\n }\n\n .ui-mockup-preferences-actions {\n justify-content: flex-end;\n }\n\n .ui-mockup-history-summary {\n flex-basis: calc(100% - 70px);\n }\n}\n";
294
+ //#endregion
295
+ //#region src/client/settings-panel.tsx
296
+ /**
297
+ * 「UI 草图」设置区块:单个 settings.section 挂四张子页(概览 / 提供方与模型 /
298
+ * 生成偏好 / 生成历史),子页内切换与已确认线框一致。
299
+ * 视觉完全使用 DSH 主题令牌(--dsw-*)与原生控件,浅/深色自适应;
300
+ * 数据面:偏好经同名 settings 命名空间镜像读写,历史/锚点/测试连接走私有 RPC 频道。
301
+ */
302
+ /** 面板本地默认值:须与宿主 DEFAULT_PREFS 保持一致(改动时两侧同步)。 */
303
+ const PANEL_DEFAULTS = {
304
+ defaultFidelity: "wireframe",
305
+ defaultPlatform: "web",
306
+ defaultCount: 2,
307
+ outputDir: "design/images",
308
+ pollTimeoutMinutes: 10,
309
+ wireframeModel: "",
310
+ highFidelityModel: "",
311
+ defaultSize: ""
312
+ };
313
+ const SUBPAGES = [
314
+ "overview",
315
+ "provider",
316
+ "preferences",
317
+ "history"
318
+ ];
319
+ /** 子页标题 key 表(i18n词条 panel.tab.*)。 */
320
+ const TAB_KEYS = {
321
+ overview: "panel.tab.overview",
322
+ provider: "panel.tab.provider",
323
+ preferences: "panel.tab.preferences",
324
+ history: "panel.tab.history"
325
+ };
326
+ function UiMockupSection({ t, prefs, connection }) {
327
+ const [page, setPage] = (0, react.useState)("overview");
328
+ const tabsId = (0, react.useId)();
329
+ const tabRefs = (0, react.useRef)([]);
330
+ const selectTabFromKeyboard = (event, index) => {
331
+ const nextIndex = event.key === "Home" ? 0 : event.key === "End" ? SUBPAGES.length - 1 : event.key === "ArrowRight" ? (index + 1) % SUBPAGES.length : event.key === "ArrowLeft" ? (index - 1 + SUBPAGES.length) % SUBPAGES.length : null;
332
+ if (nextIndex === null) return;
333
+ const nextPage = SUBPAGES[nextIndex];
334
+ if (nextPage === void 0) return;
335
+ event.preventDefault();
336
+ setPage(nextPage);
337
+ tabRefs.current[nextIndex]?.focus();
338
+ };
339
+ const activeTabId = `${tabsId}-tab-${page}`;
340
+ const activePanelId = `${tabsId}-panel-${page}`;
341
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
342
+ className: "ui-mockup-section",
343
+ style: sectionStyle,
344
+ children: [
345
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("style", {
346
+ "data-ui-mockup-styles": true,
347
+ children: settings_panel_default
348
+ }),
349
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
350
+ role: "tablist",
351
+ "aria-label": t("panel.nav"),
352
+ className: "ui-mockup-tabs",
353
+ style: tabsStyle,
354
+ children: SUBPAGES.map((key, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
355
+ id: `${tabsId}-tab-${key}`,
356
+ ref: (element) => {
357
+ tabRefs.current[index] = element;
358
+ },
359
+ type: "button",
360
+ role: "tab",
361
+ className: "ui-mockup-tab",
362
+ "aria-selected": page === key,
363
+ "aria-controls": `${tabsId}-panel-${key}`,
364
+ tabIndex: page === key ? 0 : -1,
365
+ "data-active": page === key,
366
+ onClick: () => setPage(key),
367
+ onKeyDown: (event) => selectTabFromKeyboard(event, index),
368
+ style: page === key ? {
369
+ ...tabStyle,
370
+ ...tabActiveStyle
371
+ } : tabStyle,
372
+ children: t(TAB_KEYS[key])
373
+ }, key))
374
+ }),
375
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
376
+ id: activePanelId,
377
+ role: "tabpanel",
378
+ "aria-labelledby": activeTabId,
379
+ className: "ui-mockup-tabpanel",
380
+ tabIndex: 0,
381
+ children: [
382
+ page === "overview" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(OverviewPage, {
383
+ t,
384
+ connection
385
+ }),
386
+ page === "provider" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ProviderPage, {
387
+ t,
388
+ prefs,
389
+ connection
390
+ }),
391
+ page === "preferences" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PreferencesPage, {
392
+ t,
393
+ prefs
394
+ }),
395
+ page === "history" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(HistoryPage, {
396
+ t,
397
+ connection
398
+ })
399
+ ]
400
+ })
401
+ ]
402
+ });
403
+ }
404
+ /**
405
+ * DSH 真实主题令牌(--dsw-alias-*,与 ui-theme / ui-settings-plugins 的
406
+ * module.css 同名同用途)与原生设置区实测尺寸。布局仍用内联样式,但取值
407
+ * 逐项对齐 fields.module.css / PluginsSettingsSection.module.css,不再自造。
408
+ */
409
+ const tokens = {
410
+ labelPrimary: "var(--dsw-alias-label-primary)",
411
+ labelSecondary: "var(--dsw-alias-label-secondary)",
412
+ labelTertiary: "var(--dsw-alias-label-tertiary)",
413
+ labelError: "var(--dsw-alias-label-error)",
414
+ border: "var(--dsw-alias-border-l2)",
415
+ bgLayer: "var(--dsw-alias-bg-layer-3)",
416
+ brand: "var(--dsw-alias-brand-primary)"
417
+ };
418
+ /** 面板根容器:与原生设置 section 同宽(max-width 760px)。 */
419
+ const sectionStyle = {
420
+ display: "flex",
421
+ flexDirection: "column",
422
+ gap: 12,
423
+ maxWidth: 760,
424
+ width: "100%",
425
+ color: tokens.labelPrimary
426
+ };
427
+ const tabsStyle = {
428
+ display: "flex",
429
+ alignItems: "flex-end",
430
+ borderBottom: `1px solid ${tokens.border}`,
431
+ marginTop: 2
432
+ };
433
+ const tabStyle = {
434
+ position: "relative",
435
+ border: 0,
436
+ padding: "7px 1px 9px",
437
+ background: "transparent",
438
+ color: tokens.labelTertiary,
439
+ font: "inherit",
440
+ fontSize: 13,
441
+ lineHeight: "20px",
442
+ cursor: "pointer"
443
+ };
444
+ const tabActiveStyle = { color: tokens.labelPrimary };
445
+ /** select 与原生 .input 同款(34px 高 / 8px 圆角 / layer-3 背景 / focus 品牌色边框)。 */
446
+ const selectStyle = {
447
+ height: 34,
448
+ padding: "0 12px",
449
+ border: `1px solid ${tokens.border}`,
450
+ borderRadius: 8,
451
+ background: tokens.bgLayer,
452
+ font: "inherit",
453
+ fontSize: 13,
454
+ lineHeight: "20px",
455
+ color: tokens.labelPrimary
456
+ };
457
+ /** 状态点:直接复用原语 StateDot(done 绿 / warning 琥珀),观感与会话列表一致。 */
458
+ function StatusDot({ ok, busy }) {
459
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.StateDot, {
460
+ state: busy ? "ongoing" : ok ? "done" : "warning",
461
+ className: "ui-mockup-dot"
462
+ });
463
+ }
464
+ /**
465
+ * 卡片容器:恢复已确认线框的圆角卡片布局,视觉用真实令牌
466
+ * (border-l2 边框 / 8px 圆角 / 标题 15px·600 对齐原生 heading 层级)。
467
+ */
468
+ function Card({ title, children }) {
469
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
470
+ className: "ui-mockup-card",
471
+ style: {
472
+ display: "flex",
473
+ flexDirection: "column",
474
+ gap: 8,
475
+ border: `1px solid ${tokens.border}`,
476
+ borderRadius: 8,
477
+ padding: "10px 12px"
478
+ },
479
+ children: [title !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
480
+ style: {
481
+ fontSize: 15,
482
+ fontWeight: 600,
483
+ lineHeight: "22px"
484
+ },
485
+ children: title
486
+ }), children]
487
+ });
488
+ }
489
+ /**
490
+ * 字段行:横排(label 左 96px,控件右)。为对齐原生 `.field + .field { border-top }`
491
+ * 的表单节奏,相邻字段行之间画 1px 分隔线;卡片内首行由调用方传 `first` 免除。
492
+ * 内容区统一 min-height 34(控件基准高)并垂直居中,使 radio / 输入框 / 纯文字行
493
+ * 各行等高、底边齐平,分隔线间距一致。
494
+ * 无障碍:用 role=group + aria-labelledby 而非 <label> 包裹——单个 label 只能
495
+ * 隐式关联一个可标记元素,多 radio 场景下其余选项会失去标签关联。
496
+ */
497
+ function FieldRow({ label, children, first = false }) {
498
+ const labelId = (0, react.useId)();
499
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
500
+ role: "group",
501
+ "aria-labelledby": labelId,
502
+ className: "ui-mockup-field-row",
503
+ style: {
504
+ display: "flex",
505
+ alignItems: "center",
506
+ gap: 10,
507
+ flexWrap: "wrap",
508
+ minHeight: 34,
509
+ paddingTop: 10,
510
+ ...first ? {} : { borderTop: `1px solid ${tokens.border}` }
511
+ },
512
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
513
+ id: labelId,
514
+ className: "ui-mockup-field-label",
515
+ style: {
516
+ fontSize: 13,
517
+ lineHeight: "20px",
518
+ color: tokens.labelSecondary
519
+ },
520
+ children: label
521
+ }), children]
522
+ });
523
+ }
524
+ /** 提示行:对齐原生 hint(12px tertiary)/ invalid(12px error),不带边框。 */
525
+ function Notice({ children, danger }) {
526
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
527
+ style: {
528
+ margin: 0,
529
+ fontSize: 12,
530
+ lineHeight: "20px",
531
+ color: danger ? tokens.labelError : tokens.labelTertiary
532
+ },
533
+ children
534
+ });
535
+ }
536
+ /** 快速使用三步的图标与词条键(显式常量,规避模板字面量推断不进字典联合)。 */
537
+ const QUICK_STEPS = [
538
+ {
539
+ Icon: _deepseek_ai_dsh_client_ui_primitives.IconNewChatOutline16,
540
+ title: "panel.overview.step1Title",
541
+ body: "panel.overview.step1Body"
542
+ },
543
+ {
544
+ Icon: _deepseek_ai_dsh_client_ui_primitives.IconEditOutline16,
545
+ title: "panel.overview.step2Title",
546
+ body: "panel.overview.step2Body"
547
+ },
548
+ {
549
+ Icon: _deepseek_ai_dsh_client_ui_primitives.IconCheckOutline16,
550
+ title: "panel.overview.step3Title",
551
+ body: "panel.overview.step3Body"
552
+ }
553
+ ];
554
+ function OverviewPage({ t, connection }) {
555
+ const [data, setData] = (0, react.useState)();
556
+ const [error, setError] = (0, react.useState)("");
557
+ (0, react.useEffect)(() => {
558
+ let alive = true;
559
+ callPanel(connection, "overview").then((value) => {
560
+ if (alive) setData(value);
561
+ }).catch((err) => {
562
+ if (alive) setError(err instanceof Error ? err.message : String(err));
563
+ });
564
+ return () => {
565
+ alive = false;
566
+ };
567
+ }, [connection]);
568
+ if (error !== "") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Notice, {
569
+ danger: true,
570
+ children: t("panel.loadFailed", { error })
571
+ });
572
+ if (data === void 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
573
+ style: { color: tokens.labelTertiary },
574
+ children: t("panel.loading")
575
+ });
576
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
577
+ className: "ui-mockup-overview",
578
+ style: {
579
+ display: "flex",
580
+ flexDirection: "column",
581
+ gap: 12
582
+ },
583
+ children: [
584
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
585
+ style: {
586
+ margin: 0,
587
+ fontSize: 13,
588
+ lineHeight: "20px",
589
+ color: tokens.labelTertiary
590
+ },
591
+ children: t("panel.overview.intro")
592
+ }),
593
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Card, {
594
+ title: t("panel.overview.quickTitle"),
595
+ children: QUICK_STEPS.map(({ Icon, title, body }) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
596
+ className: "ui-mockup-quick-step",
597
+ style: {
598
+ display: "flex",
599
+ gap: 8
600
+ },
601
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
602
+ "aria-hidden": true,
603
+ style: {
604
+ flex: "none",
605
+ color: tokens.labelSecondary,
606
+ marginTop: 2
607
+ },
608
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, { size: 16 })
609
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
610
+ style: {
611
+ margin: 0,
612
+ lineHeight: "20px"
613
+ },
614
+ children: [
615
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", {
616
+ style: { fontSize: 13 },
617
+ children: t(title)
618
+ }),
619
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("br", {}),
620
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
621
+ style: {
622
+ fontSize: 13,
623
+ color: tokens.labelSecondary
624
+ },
625
+ children: t(body)
626
+ })
627
+ ]
628
+ })]
629
+ }, title))
630
+ }),
631
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
632
+ className: "ui-mockup-overview-status",
633
+ style: {
634
+ display: "flex",
635
+ alignItems: "center",
636
+ gap: 8,
637
+ fontSize: 13,
638
+ lineHeight: "20px",
639
+ color: tokens.labelSecondary
640
+ },
641
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatusDot, { ok: data.credential.configured }), t("panel.overview.statusLine", {
642
+ provider: data.provider === "volcengine" || data.provider === "dashscope" ? t(PROVIDER_NAME_KEYS[data.provider]) : t("panel.provider.unknown"),
643
+ credential: data.credential.configured ? t("panel.credential.ready") : t("panel.credential.missing", { credential: data.provider === "volcengine" ? PROVIDER_CREDENTIALS.volcengine : PROVIDER_CREDENTIALS.dashscope })
644
+ })]
645
+ })
646
+ ]
647
+ });
648
+ }
649
+ /** 来源层 id → 词条键(显式映射,规避模板字面量推断不进字典联合)。 */
650
+ const SOURCE_LABELS = {
651
+ env: "panel.credential.source.env",
652
+ file: "panel.credential.source.file",
653
+ "project-env": "panel.credential.source.project-env",
654
+ "user-env": "panel.credential.source.user-env",
655
+ ambient: "panel.credential.source.ambient"
656
+ };
657
+ function sourceLabelText(t, source) {
658
+ if (source === void 0) return void 0;
659
+ const key = SOURCE_LABELS[source];
660
+ return key === void 0 ? source : t(key);
661
+ }
662
+ const WIREFRAME_MODEL_HINTS = {
663
+ dashscope: [
664
+ "",
665
+ "qwen-image-3.0",
666
+ "qwen-image-2.0",
667
+ "wan2.7-image"
668
+ ],
669
+ volcengine: [
670
+ "",
671
+ "doubao-seedream-4-5-251128",
672
+ "doubao-seedream-4-0-250828"
673
+ ],
674
+ unknown: ["", "qwen-image-3.0"]
675
+ };
676
+ const HIGH_FIDELITY_MODEL_HINTS = {
677
+ dashscope: [
678
+ "",
679
+ "qwen-image-3.0-pro",
680
+ "qwen-image-2.0-pro",
681
+ "wan2.7-image-pro"
682
+ ],
683
+ volcengine: [
684
+ "",
685
+ "doubao-seedream-5-0-pro-260628",
686
+ "doubao-seedream-5-0-260128"
687
+ ],
688
+ unknown: ["", "qwen-image-3.0-pro"]
689
+ };
690
+ /** 各提供方的凭据引用名(与 Provider Config 默认值一致)。 */
691
+ const PROVIDER_CREDENTIALS = {
692
+ dashscope: "DASHSCOPE_API_KEY",
693
+ volcengine: "ARK_API_KEY",
694
+ unknown: "DASHSCOPE_API_KEY"
695
+ };
696
+ /** 生效提供方 id → 本地化名称键;unknown 无名可显,调用方特判。 */
697
+ const PROVIDER_NAME_KEYS = {
698
+ dashscope: "panel.provider.dashscopeName",
699
+ volcengine: "panel.provider.volcengineName"
700
+ };
701
+ function ProviderPage({ t, prefs, connection }) {
702
+ const snap = prefs.getSnapshot();
703
+ usePrefSync(prefs);
704
+ const [testResult, setTestResult] = (0, react.useState)(null);
705
+ const [testing, setTesting] = (0, react.useState)(false);
706
+ const [writeError, setWriteError] = (0, react.useState)("");
707
+ const [providerId, setProviderId] = (0, react.useState)("unknown");
708
+ const [statusUnknown, setStatusUnknown] = (0, react.useState)(true);
709
+ const [switching, setSwitching] = (0, react.useState)(false);
710
+ const [providerNotice, setProviderNotice] = (0, react.useState)(null);
711
+ const [credential, setCredential] = (0, react.useState)();
712
+ const [keyDraft, setKeyDraft] = (0, react.useState)("");
713
+ const [keyBusy, setKeyBusy] = (0, react.useState)(false);
714
+ const [keyNotice, setKeyNotice] = (0, react.useState)(null);
715
+ const refreshProviderStatus = (0, react.useCallback)(async () => {
716
+ try {
717
+ const value = await callPanel(connection, "provider/status");
718
+ setProviderId(value.active);
719
+ setStatusUnknown(false);
720
+ return value.active;
721
+ } catch {
722
+ setProviderId("unknown");
723
+ setStatusUnknown(true);
724
+ return "unknown";
725
+ }
726
+ }, [connection]);
727
+ (0, react.useEffect)(() => {
728
+ refreshProviderStatus();
729
+ }, [refreshProviderStatus]);
730
+ const credentialName = PROVIDER_CREDENTIALS[providerId];
731
+ /** 一键切换生效提供方:宿主改写 home 用户层 patch(DSH 热重载),完成后刷新状态。 */
732
+ const switchProvider = async (target) => {
733
+ if (switching) return;
734
+ setSwitching(true);
735
+ setProviderNotice(null);
736
+ try {
737
+ const result = await callPanel(connection, "provider/switch", { provider: target });
738
+ if (result.active === target && result.pending !== true) {
739
+ const current = prefs.getSnapshot().value;
740
+ if (current !== void 0 && (current.wireframeModel !== "" || current.highFidelityModel !== "")) try {
741
+ await prefs.set("wireframeModel", "");
742
+ await prefs.set("highFidelityModel", "");
743
+ setProviderNotice(t("panel.provider.modelsReset"));
744
+ } catch {}
745
+ }
746
+ } catch (err) {
747
+ setTestResult(err instanceof Error ? err.message : String(err));
748
+ } finally {
749
+ await refreshProviderStatus();
750
+ await refreshCredential();
751
+ setSwitching(false);
752
+ }
753
+ };
754
+ const refreshCredential = (0, react.useCallback)(async () => {
755
+ try {
756
+ const value = await callPanel(connection, "overview");
757
+ setCredential(value.credential);
758
+ return value.credential;
759
+ } catch {
760
+ setCredential(void 0);
761
+ return;
762
+ }
763
+ }, [connection]);
764
+ (0, react.useEffect)(() => {
765
+ refreshCredential();
766
+ }, [refreshCredential]);
767
+ /** 写入(覆盖)或清除存储中的密钥;成功后清空草稿并刷新状态。 */
768
+ const applyKey = async (endpoint) => {
769
+ setKeyBusy(true);
770
+ setKeyNotice(null);
771
+ try {
772
+ const result = await callPanel(connection, endpoint, endpoint === "credential/set" ? { value: keyDraft } : {});
773
+ setCredential(result.credential);
774
+ setKeyDraft("");
775
+ setKeyNotice({
776
+ kind: "ok",
777
+ text: endpoint === "credential/set" ? t("panel.credential.savedNotice") : t("panel.credential.clearedNotice", { credential: credentialName })
778
+ });
779
+ } catch (err) {
780
+ setKeyNotice({
781
+ kind: "error",
782
+ text: err instanceof Error ? err.message : String(err)
783
+ });
784
+ } finally {
785
+ setKeyBusy(false);
786
+ }
787
+ };
788
+ const runTest = async () => {
789
+ setTesting(true);
790
+ setTestResult(null);
791
+ try {
792
+ const result = await callPanel(connection, "test-connection");
793
+ setTestResult(result.ok ? t("panel.test.ok") : result.reason === "missing-key" ? t("panel.test.missingKey", { credential: credentialName }) : result.reason === "invalid-key" ? t("panel.test.invalidKey", { credential: credentialName }) : result.reason === "unknown" ? t("panel.test.unknown", { detail: result.detail ?? "" }) : t("panel.test.gatewayFail", { detail: result.detail ?? "" }));
794
+ } catch (err) {
795
+ setTestResult(err instanceof Error ? err.message : String(err));
796
+ } finally {
797
+ setTesting(false);
798
+ }
799
+ };
800
+ const sourceLabel = sourceLabelText(t, credential?.source);
801
+ /** 纯状态文本(不含来源),供提供方选中卡第一行使用;来源另起一行淡化展示。 */
802
+ const credentialStatus = credential === void 0 ? t("panel.credential.checking") : credential.configured ? t("panel.credential.ready") : t("panel.credential.missing", { credential: credentialName });
803
+ const credentialLine = credential === void 0 ? t("panel.credential.checking") : credential.configured ? sourceLabel === void 0 ? t("panel.credential.ready") : t("panel.credential.readyWithSource", { source: sourceLabel }) : t("panel.credential.missing", { credential: credentialName });
804
+ const statusReadable = !statusUnknown;
805
+ const dashscopeActive = providerId === "dashscope" || providerId === "unknown" && statusUnknown;
806
+ const volcengineActive = providerId === "volcengine";
807
+ /** 选中卡:中性蓝灰边框 + 浅灰填充(对齐 DSH 原生「外观」选项卡);未选中:border-l2 实线。 */
808
+ const providerCardStyle = (active) => ({
809
+ flex: "1 1 200px",
810
+ boxSizing: "border-box",
811
+ border: active ? "1px solid var(--dsw-static-neutral-bluish-400)" : `1px solid ${tokens.border}`,
812
+ borderRadius: 16,
813
+ background: active ? "var(--dsw-alias-bg-module-platform)" : "transparent",
814
+ padding: "10px 14px",
815
+ cursor: "default"
816
+ });
817
+ const providerMetaStyle = {
818
+ marginTop: 4,
819
+ display: "flex",
820
+ flexDirection: "column",
821
+ gap: 2
822
+ };
823
+ const providerStatusStyle = {
824
+ display: "flex",
825
+ alignItems: "center",
826
+ gap: 6,
827
+ fontSize: 12,
828
+ lineHeight: "17px",
829
+ color: tokens.labelSecondary
830
+ };
831
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
832
+ style: {
833
+ display: "flex",
834
+ flexDirection: "column",
835
+ gap: 12
836
+ },
837
+ children: [
838
+ snap.mode === "memory" || !snap.writable ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Notice, { children: t("panel.readonlyBanner") }) : null,
839
+ writeError !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Notice, {
840
+ danger: true,
841
+ children: writeError
842
+ }),
843
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Card, {
844
+ title: t("panel.provider.title"),
845
+ children: [
846
+ statusUnknown && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Notice, { children: t("panel.provider.unknownHint") }),
847
+ providerNotice !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Notice, { children: providerNotice }),
848
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
849
+ style: {
850
+ display: "flex",
851
+ gap: 8,
852
+ flexWrap: "wrap"
853
+ },
854
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
855
+ className: "ui-mockup-provider-card",
856
+ style: {
857
+ ...providerCardStyle(dashscopeActive),
858
+ cursor: dashscopeActive || switching ? "default" : "pointer"
859
+ },
860
+ onClick: () => {
861
+ if (!dashscopeActive) switchProvider("dashscope");
862
+ },
863
+ children: [
864
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
865
+ type: "radio",
866
+ name: "ui-mockup-provider",
867
+ checked: dashscopeActive,
868
+ disabled: switching,
869
+ readOnly: true,
870
+ "aria-label": t("panel.provider.dashscopeName")
871
+ }),
872
+ " ",
873
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", {
874
+ style: { fontSize: 13 },
875
+ children: t("panel.provider.dashscopeName")
876
+ }),
877
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
878
+ style: providerMetaStyle,
879
+ children: dashscopeActive ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
880
+ style: providerStatusStyle,
881
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatusDot, {
882
+ ok: credential?.configured === true,
883
+ busy: switching
884
+ }), statusUnknown && providerId === "unknown" ? t("panel.provider.fallbackActive") : credentialStatus]
885
+ }), credential?.configured && sourceLabel !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
886
+ style: {
887
+ fontSize: 12,
888
+ lineHeight: "17px",
889
+ color: tokens.labelTertiary,
890
+ paddingLeft: 14
891
+ },
892
+ children: sourceLabel
893
+ })] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
894
+ style: providerStatusStyle,
895
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatusDot, {
896
+ ok: false,
897
+ busy: switching
898
+ }), t("panel.provider.inactive")]
899
+ })
900
+ })
901
+ ]
902
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
903
+ className: "ui-mockup-provider-card",
904
+ style: {
905
+ ...providerCardStyle(volcengineActive),
906
+ cursor: volcengineActive || switching ? "default" : "pointer"
907
+ },
908
+ onClick: () => {
909
+ if (!volcengineActive) switchProvider("volcengine");
910
+ },
911
+ children: [
912
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
913
+ type: "radio",
914
+ name: "ui-mockup-provider",
915
+ checked: volcengineActive,
916
+ disabled: switching,
917
+ readOnly: true,
918
+ "aria-label": t("panel.provider.volcengineName")
919
+ }),
920
+ " ",
921
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", {
922
+ style: { fontSize: 13 },
923
+ children: t("panel.provider.volcengineName")
924
+ }),
925
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
926
+ style: providerMetaStyle,
927
+ children: volcengineActive ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
928
+ style: providerStatusStyle,
929
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatusDot, {
930
+ ok: credential?.configured === true,
931
+ busy: switching
932
+ }), credentialStatus]
933
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
934
+ style: {
935
+ fontSize: 12,
936
+ lineHeight: "17px",
937
+ color: tokens.labelTertiary
938
+ },
939
+ children: statusReadable ? t("panel.provider.volcengineDisabled") : t("panel.provider.inactive")
940
+ })
941
+ })
942
+ ]
943
+ })]
944
+ })
945
+ ]
946
+ }),
947
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Card, {
948
+ title: t("panel.credential.title", { credential: credentialName }),
949
+ children: [
950
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
951
+ style: {
952
+ display: "flex",
953
+ alignItems: "center",
954
+ gap: 10,
955
+ flexWrap: "wrap"
956
+ },
957
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
958
+ style: {
959
+ display: "flex",
960
+ alignItems: "center",
961
+ gap: 8,
962
+ flex: 1,
963
+ fontSize: 13,
964
+ lineHeight: "20px",
965
+ color: tokens.labelSecondary
966
+ },
967
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatusDot, {
968
+ ok: credential?.configured === true,
969
+ busy: testing
970
+ }), credentialLine]
971
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
972
+ variant: "outline",
973
+ size: "sm",
974
+ onClick: () => void runTest(),
975
+ disabled: testing,
976
+ children: testing ? t("panel.testing") : t("panel.testConnection")
977
+ })]
978
+ }),
979
+ testResult !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Notice, { children: testResult }),
980
+ credential?.writable === true ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
981
+ style: {
982
+ display: "flex",
983
+ gap: 8,
984
+ flexWrap: "wrap",
985
+ alignItems: "center"
986
+ },
987
+ children: [
988
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Input, {
989
+ type: "password",
990
+ "aria-label": t("panel.credential.keyInputLabel", { credential: credentialName }),
991
+ style: { flex: "1 1 220px" },
992
+ value: keyDraft,
993
+ autoComplete: "off",
994
+ placeholder: t("panel.credential.writePlaceholder", { credential: credentialName }),
995
+ onChange: (event) => setKeyDraft(event.target.value),
996
+ onKeyDown: (event) => {
997
+ if (event.key === "Enter" && keyDraft.trim() !== "") applyKey("credential/set");
998
+ }
999
+ }),
1000
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1001
+ variant: "primary",
1002
+ size: "sm",
1003
+ disabled: keyBusy || keyDraft.trim() === "",
1004
+ onClick: () => void applyKey("credential/set"),
1005
+ children: t("panel.credential.save")
1006
+ }),
1007
+ credential.configured && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1008
+ variant: "ghost",
1009
+ size: "sm",
1010
+ disabled: keyBusy,
1011
+ onClick: () => void applyKey("credential/unset"),
1012
+ children: t("panel.credential.clear")
1013
+ })
1014
+ ]
1015
+ }) : credential !== void 0 && credential.configured ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Notice, { children: t("panel.credential.notWritable", { source: sourceLabel ?? "" }) }) : null,
1016
+ keyNotice !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Notice, {
1017
+ danger: keyNotice.kind === "error",
1018
+ children: keyNotice.text
1019
+ }),
1020
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1021
+ style: {
1022
+ fontSize: 12,
1023
+ lineHeight: "17px",
1024
+ color: tokens.labelTertiary,
1025
+ display: "flex",
1026
+ flexDirection: "column",
1027
+ gap: 2,
1028
+ marginTop: 2
1029
+ },
1030
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1031
+ style: {
1032
+ fontWeight: 500,
1033
+ color: tokens.labelSecondary
1034
+ },
1035
+ children: t("panel.credential.howTitle")
1036
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("ol", {
1037
+ style: {
1038
+ margin: 0,
1039
+ paddingLeft: 18
1040
+ },
1041
+ children: [
1042
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: t("panel.credential.way1", { credential: credentialName }) }),
1043
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: t("panel.credential.way2") }),
1044
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: t("panel.credential.way3", { credential: credentialName }) }),
1045
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: t("panel.credential.way4", { credential: credentialName }) })
1046
+ ]
1047
+ })]
1048
+ })
1049
+ ]
1050
+ }),
1051
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Card, {
1052
+ title: t("panel.models.title"),
1053
+ children: [
1054
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FieldRow, {
1055
+ first: true,
1056
+ label: t("panel.models.wireframe"),
1057
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
1058
+ "aria-label": t("panel.models.wireframe"),
1059
+ className: "ui-mockup-model-select",
1060
+ value: snap.value?.wireframeModel ?? "",
1061
+ onChange: (event) => void writePref(prefs, "wireframeModel", event.target.value.trim(), setWriteError),
1062
+ style: {
1063
+ ...selectStyle,
1064
+ width: "min(260px, 100%)"
1065
+ },
1066
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1067
+ value: "",
1068
+ children: t("panel.models.followDefault")
1069
+ }), WIREFRAME_MODEL_HINTS[providerId].filter(Boolean).map((m) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1070
+ value: m,
1071
+ children: m
1072
+ }, m))]
1073
+ })
1074
+ }),
1075
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FieldRow, {
1076
+ label: t("panel.models.highFidelity"),
1077
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
1078
+ "aria-label": t("panel.models.highFidelity"),
1079
+ className: "ui-mockup-model-select",
1080
+ value: snap.value?.highFidelityModel ?? "",
1081
+ onChange: (event) => void writePref(prefs, "highFidelityModel", event.target.value.trim(), setWriteError),
1082
+ style: {
1083
+ ...selectStyle,
1084
+ width: "min(260px, 100%)"
1085
+ },
1086
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1087
+ value: "",
1088
+ children: t("panel.models.followDefault")
1089
+ }), HIGH_FIDELITY_MODEL_HINTS[providerId].filter(Boolean).map((m) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1090
+ value: m,
1091
+ children: m
1092
+ }, m))]
1093
+ })
1094
+ }),
1095
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Notice, { children: `${t("panel.models.wireframe")}: ${WIREFRAME_MODEL_HINTS[providerId].filter(Boolean).join(", ")} · ${t("panel.models.highFidelity")}: ${HIGH_FIDELITY_MODEL_HINTS[providerId].filter(Boolean).join(", ")}` })
1096
+ ]
1097
+ })
1098
+ ]
1099
+ });
1100
+ }
1101
+ const EDITABLE_PREF_FIELDS = [
1102
+ "defaultFidelity",
1103
+ "defaultPlatform",
1104
+ "defaultCount",
1105
+ "pollTimeoutMinutes",
1106
+ "defaultSize"
1107
+ ];
1108
+ /** 只比较本页可编辑字段;Provider 模型偏好不应影响本页保存按钮。 */
1109
+ function editablePrefsEqual(left, right) {
1110
+ return left.defaultFidelity === right.defaultFidelity && left.defaultPlatform === right.defaultPlatform && left.defaultCount === right.defaultCount && left.pollTimeoutMinutes === right.pollTimeoutMinutes && left.defaultSize === right.defaultSize;
1111
+ }
1112
+ /** unset 成功以用户层字段消失为准;resolved value 可以来自自定义 composition base。 */
1113
+ function editableUserOverridesCleared(user) {
1114
+ if (user === void 0) return true;
1115
+ if (typeof user !== "object" || user === null || Array.isArray(user)) return false;
1116
+ return EDITABLE_PREF_FIELDS.every((field) => !Object.prototype.hasOwnProperty.call(user, field));
1117
+ }
1118
+ function PreferencesPage({ t, prefs }) {
1119
+ const snap = prefs.getSnapshot();
1120
+ usePrefSync(prefs);
1121
+ const initial = (0, react.useMemo)(() => snap.value ?? PANEL_DEFAULTS, [snap.value]);
1122
+ const [draft, setDraft] = (0, react.useState)(initial);
1123
+ const [baseline, setBaseline] = (0, react.useState)(initial);
1124
+ const [savedAt, setSavedAt] = (0, react.useState)(0);
1125
+ const [error, setError] = (0, react.useState)("");
1126
+ const [mutating, setMutating] = (0, react.useState)(false);
1127
+ const mutationLock = (0, react.useRef)(false);
1128
+ const dirty = !editablePrefsEqual(draft, baseline);
1129
+ (0, react.useEffect)(() => {
1130
+ if (!dirty && !mutating && snap.value !== void 0) {
1131
+ setDraft(snap.value);
1132
+ setBaseline(snap.value);
1133
+ }
1134
+ }, [
1135
+ dirty,
1136
+ mutating,
1137
+ snap.value
1138
+ ]);
1139
+ const patch = (part) => {
1140
+ if (mutationLock.current) return;
1141
+ setDraft((prev) => ({
1142
+ ...prev,
1143
+ ...part
1144
+ }));
1145
+ };
1146
+ const beginMutation = () => {
1147
+ if (mutationLock.current) return false;
1148
+ mutationLock.current = true;
1149
+ setMutating(true);
1150
+ setSavedAt(0);
1151
+ setError("");
1152
+ return true;
1153
+ };
1154
+ const endMutation = () => {
1155
+ mutationLock.current = false;
1156
+ setMutating(false);
1157
+ };
1158
+ const confirmApplied = (expected) => {
1159
+ const actual = prefs.getSnapshot().value;
1160
+ if (actual === void 0 || !editablePrefsEqual(actual, expected)) throw new Error(t("panel.prefs.writeNotApplied"));
1161
+ return actual;
1162
+ };
1163
+ /** 单字段写入后立即确认落盘;宿主 set 失败时静默恢复(不抛错),逐字段确认
1164
+ 能在第一时间中止后续写入,避免静默部分落盘。 */
1165
+ const setField = async (field, value) => {
1166
+ await prefs.set(field, value);
1167
+ const actual = prefs.getSnapshot().value;
1168
+ if (actual === void 0 || actual[field] !== value) throw new Error(t("panel.prefs.writeNotAppliedField", { field }));
1169
+ };
1170
+ const confirmResetApplied = () => {
1171
+ const current = prefs.getSnapshot();
1172
+ if (current.value === void 0 || !editableUserOverridesCleared(current.user)) throw new Error(t("panel.prefs.writeNotApplied"));
1173
+ return current.value;
1174
+ };
1175
+ const save = async () => {
1176
+ if (!beginMutation()) return;
1177
+ try {
1178
+ const saved = {
1179
+ ...draft,
1180
+ defaultCount: Math.min(4, Math.max(1, Math.round(draft.defaultCount))),
1181
+ pollTimeoutMinutes: Math.max(1, Math.round(draft.pollTimeoutMinutes))
1182
+ };
1183
+ await setField("defaultFidelity", saved.defaultFidelity);
1184
+ await setField("defaultPlatform", saved.defaultPlatform);
1185
+ await setField("defaultCount", saved.defaultCount);
1186
+ await setField("pollTimeoutMinutes", saved.pollTimeoutMinutes);
1187
+ await setField("defaultSize", saved.defaultSize);
1188
+ const applied = confirmApplied(saved);
1189
+ setDraft(applied);
1190
+ setBaseline(applied);
1191
+ setSavedAt(Date.now());
1192
+ } catch (err) {
1193
+ setError(err instanceof Error ? err.message : String(err));
1194
+ } finally {
1195
+ endMutation();
1196
+ }
1197
+ };
1198
+ const resetDefaults = async () => {
1199
+ if (!beginMutation()) return;
1200
+ try {
1201
+ for (const field of EDITABLE_PREF_FIELDS) await prefs.unset(field);
1202
+ const applied = confirmResetApplied();
1203
+ setDraft(applied);
1204
+ setBaseline(applied);
1205
+ setSavedAt(Date.now());
1206
+ } catch (err) {
1207
+ setError(err instanceof Error ? err.message : String(err));
1208
+ } finally {
1209
+ endMutation();
1210
+ }
1211
+ };
1212
+ const readonlyNote = snap.mode === "memory" || !snap.writable;
1213
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1214
+ style: {
1215
+ display: "flex",
1216
+ flexDirection: "column",
1217
+ gap: 12
1218
+ },
1219
+ children: [
1220
+ readonlyNote && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Notice, { children: t("panel.readonlyBanner") }),
1221
+ error !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Notice, {
1222
+ danger: true,
1223
+ children: error
1224
+ }),
1225
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Card, { children: [
1226
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(FieldRow, {
1227
+ first: true,
1228
+ label: t("panel.prefs.fidelity"),
1229
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Radio, {
1230
+ label: t("panel.prefs.fidelityWireframe"),
1231
+ checked: draft.defaultFidelity === "wireframe",
1232
+ onChange: () => patch({ defaultFidelity: "wireframe" }),
1233
+ name: "pref-fidelity",
1234
+ disabled: readonlyNote || mutating
1235
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Radio, {
1236
+ label: t("panel.prefs.fidelityHigh"),
1237
+ checked: draft.defaultFidelity === "high-fidelity",
1238
+ onChange: () => patch({ defaultFidelity: "high-fidelity" }),
1239
+ name: "pref-fidelity",
1240
+ disabled: readonlyNote || mutating
1241
+ })]
1242
+ }),
1243
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(FieldRow, {
1244
+ label: t("panel.prefs.platform"),
1245
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Radio, {
1246
+ label: "Web",
1247
+ checked: draft.defaultPlatform === "web",
1248
+ onChange: () => patch({ defaultPlatform: "web" }),
1249
+ name: "pref-platform",
1250
+ disabled: readonlyNote || mutating
1251
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Radio, {
1252
+ label: "Mobile",
1253
+ checked: draft.defaultPlatform === "mobile",
1254
+ onChange: () => patch({ defaultPlatform: "mobile" }),
1255
+ name: "pref-platform",
1256
+ disabled: readonlyNote || mutating
1257
+ })]
1258
+ }),
1259
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FieldRow, {
1260
+ label: t("panel.prefs.count"),
1261
+ children: [
1262
+ 1,
1263
+ 2,
1264
+ 3,
1265
+ 4
1266
+ ].map((n) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Radio, {
1267
+ label: String(n),
1268
+ checked: draft.defaultCount === n,
1269
+ onChange: () => patch({ defaultCount: n }),
1270
+ name: "pref-count",
1271
+ disabled: readonlyNote || mutating
1272
+ }, n))
1273
+ }),
1274
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(FieldRow, {
1275
+ label: t("panel.prefs.pollTimeout"),
1276
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Input, {
1277
+ type: "number",
1278
+ "aria-label": t("panel.prefs.pollTimeout"),
1279
+ min: 1,
1280
+ max: 60,
1281
+ style: { width: 100 },
1282
+ value: draft.pollTimeoutMinutes,
1283
+ onChange: (event) => patch({ pollTimeoutMinutes: Number(event.target.value) }),
1284
+ disabled: readonlyNote || mutating
1285
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1286
+ style: {
1287
+ fontSize: 12,
1288
+ color: tokens.labelTertiary
1289
+ },
1290
+ children: t("panel.prefs.minutes")
1291
+ })]
1292
+ }),
1293
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FieldRow, {
1294
+ label: t("panel.prefs.backoff"),
1295
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1296
+ style: {
1297
+ fontSize: 13,
1298
+ lineHeight: "20px",
1299
+ color: tokens.labelTertiary
1300
+ },
1301
+ children: t("panel.prefs.backoffFixed")
1302
+ })
1303
+ }),
1304
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FieldRow, {
1305
+ label: t("panel.prefs.size"),
1306
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
1307
+ "aria-label": t("panel.prefs.size"),
1308
+ value: draft.defaultSize,
1309
+ onChange: (event) => patch({ defaultSize: event.target.value }),
1310
+ disabled: readonlyNote || mutating,
1311
+ style: selectStyle,
1312
+ children: [
1313
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1314
+ value: "",
1315
+ children: t("panel.models.followDefault")
1316
+ }),
1317
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1318
+ value: "1024*1024",
1319
+ children: "1024*1024"
1320
+ }),
1321
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1322
+ value: "1280*720",
1323
+ children: "1280*720"
1324
+ }),
1325
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1326
+ value: "720*1280",
1327
+ children: "720*1280"
1328
+ })
1329
+ ]
1330
+ })
1331
+ })
1332
+ ] }),
1333
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1334
+ className: "ui-mockup-preferences-footer",
1335
+ style: {
1336
+ display: "flex",
1337
+ justifyContent: "space-between",
1338
+ alignItems: "center"
1339
+ },
1340
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1341
+ variant: "ghost",
1342
+ size: "sm",
1343
+ onClick: () => void resetDefaults(),
1344
+ disabled: readonlyNote || mutating,
1345
+ children: t("panel.prefs.reset")
1346
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1347
+ className: "ui-mockup-preferences-actions",
1348
+ style: {
1349
+ display: "flex",
1350
+ gap: 10,
1351
+ alignItems: "center"
1352
+ },
1353
+ children: [savedAt > 0 && !dirty && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1354
+ role: "status",
1355
+ "aria-live": "polite",
1356
+ style: {
1357
+ fontSize: 12,
1358
+ color: tokens.labelTertiary
1359
+ },
1360
+ children: t("panel.prefs.saved")
1361
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1362
+ variant: "primary",
1363
+ size: "sm",
1364
+ onClick: () => void save(),
1365
+ disabled: readonlyNote || mutating || !dirty,
1366
+ children: t("panel.prefs.save")
1367
+ })]
1368
+ })]
1369
+ })
1370
+ ]
1371
+ });
1372
+ }
1373
+ function HistoryPage({ t, connection }) {
1374
+ const cwd = void 0;
1375
+ const [rows, setRows] = (0, react.useState)();
1376
+ const [anchorFile, setAnchorFile] = (0, react.useState)(null);
1377
+ const [anchorIndex, setAnchorIndex] = (0, react.useState)(-1);
1378
+ const [total, setTotal] = (0, react.useState)(0);
1379
+ const [page, setPage] = (0, react.useState)(1);
1380
+ const [queryDraft, setQueryDraft] = (0, react.useState)("");
1381
+ const [appliedQuery, setAppliedQuery] = (0, react.useState)("");
1382
+ const [error, setError] = (0, react.useState)("");
1383
+ const [confirmingClear, setConfirmingClear] = (0, react.useState)(false);
1384
+ const requestSeq = (0, react.useRef)(0);
1385
+ const reload = (0, react.useCallback)(async (needle, targetPage) => {
1386
+ const seq = ++requestSeq.current;
1387
+ setError("");
1388
+ try {
1389
+ const data = await callPanel(connection, "history/list", {
1390
+ cwd,
1391
+ query: needle,
1392
+ page: targetPage,
1393
+ pageSize: 5
1394
+ });
1395
+ if (seq !== requestSeq.current) return;
1396
+ setRows(data.entries);
1397
+ setAnchorFile(data.anchorFile);
1398
+ setAnchorIndex(data.anchorIndex);
1399
+ setTotal(data.total);
1400
+ setPage(data.page);
1401
+ } catch (err) {
1402
+ if (seq !== requestSeq.current) return;
1403
+ setError(err instanceof Error ? err.message : String(err));
1404
+ }
1405
+ }, [connection, cwd]);
1406
+ (0, react.useEffect)(() => {
1407
+ reload("", 1);
1408
+ }, [reload]);
1409
+ const submitSearch = () => {
1410
+ setAppliedQuery(queryDraft);
1411
+ reload(queryDraft, 1);
1412
+ };
1413
+ const totalPages = Math.max(1, Math.ceil(total / 5));
1414
+ const anchorPage = anchorPageOf(anchorIndex, 5);
1415
+ const act = async (endpoint, payload, stayPage = page) => {
1416
+ try {
1417
+ await callPanel(connection, endpoint, payload);
1418
+ await reload(appliedQuery, stayPage);
1419
+ } catch (err) {
1420
+ setError(err instanceof Error ? err.message : String(err));
1421
+ }
1422
+ };
1423
+ if (error !== "") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Notice, {
1424
+ danger: true,
1425
+ children: t("panel.loadFailed", { error })
1426
+ });
1427
+ if (rows === void 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1428
+ style: { color: tokens.labelTertiary },
1429
+ children: t("panel.loading")
1430
+ });
1431
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1432
+ style: {
1433
+ display: "flex",
1434
+ flexDirection: "column",
1435
+ gap: 10
1436
+ },
1437
+ children: [
1438
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1439
+ role: "search",
1440
+ "aria-label": t("panel.history.searchLabel"),
1441
+ className: "ui-mockup-history-toolbar",
1442
+ style: {
1443
+ display: "flex",
1444
+ gap: 8
1445
+ },
1446
+ children: [
1447
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Input, {
1448
+ type: "search",
1449
+ "aria-label": t("panel.history.searchLabel"),
1450
+ className: "ui-mockup-history-search",
1451
+ placeholder: t("panel.history.searchPlaceholder"),
1452
+ value: queryDraft,
1453
+ onChange: (event) => setQueryDraft(event.target.value),
1454
+ onKeyDown: (event) => {
1455
+ if (event.key !== "Enter") return;
1456
+ event.preventDefault();
1457
+ submitSearch();
1458
+ }
1459
+ }),
1460
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1461
+ variant: "outline",
1462
+ size: "sm",
1463
+ onClick: submitSearch,
1464
+ children: t("panel.history.search")
1465
+ }),
1466
+ confirmingClear ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1467
+ variant: "ghost",
1468
+ size: "sm",
1469
+ onClick: () => void act("history/clear", { cwd }, 1).finally(() => setConfirmingClear(false)),
1470
+ style: {
1471
+ color: tokens.labelError,
1472
+ borderColor: tokens.labelError
1473
+ },
1474
+ children: t("panel.history.confirmClear")
1475
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1476
+ variant: "ghost",
1477
+ size: "sm",
1478
+ onClick: () => setConfirmingClear(false),
1479
+ children: t("panel.history.cancelClear")
1480
+ })] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1481
+ variant: "ghost",
1482
+ size: "sm",
1483
+ onClick: () => setConfirmingClear(true),
1484
+ children: t("panel.history.clear")
1485
+ })
1486
+ ]
1487
+ }),
1488
+ queryDraft !== appliedQuery && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1489
+ role: "status",
1490
+ "aria-live": "polite",
1491
+ style: {
1492
+ fontSize: 12,
1493
+ lineHeight: "17px",
1494
+ color: tokens.labelTertiary
1495
+ },
1496
+ children: t("panel.history.pendingSearch")
1497
+ }),
1498
+ rows.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1499
+ style: {
1500
+ color: tokens.labelTertiary,
1501
+ fontSize: 13,
1502
+ lineHeight: "20px",
1503
+ padding: "18px 0",
1504
+ textAlign: "center"
1505
+ },
1506
+ children: t("panel.history.empty")
1507
+ }),
1508
+ rows.map((row, index) => {
1509
+ const first = row.files[0];
1510
+ const name = first === void 0 ? "" : first.split("/").pop() ?? "";
1511
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1512
+ className: "ui-mockup-history-row",
1513
+ style: {
1514
+ display: "flex",
1515
+ gap: 10,
1516
+ alignItems: "center",
1517
+ border: `${row.anchored ? 2 : 1}px solid ${tokens.border}`,
1518
+ borderRadius: 8,
1519
+ padding: 8,
1520
+ position: "relative"
1521
+ },
1522
+ children: [
1523
+ row.anchored && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1524
+ style: {
1525
+ position: "absolute",
1526
+ top: -9,
1527
+ right: 10,
1528
+ background: "var(--dsw-alias-bg-module-platform)",
1529
+ borderRadius: 999,
1530
+ fontSize: 11,
1531
+ lineHeight: "17px",
1532
+ padding: "1px 8px",
1533
+ fontWeight: 500,
1534
+ color: tokens.labelSecondary
1535
+ },
1536
+ children: ["🚩 ", t("panel.history.anchorTag")]
1537
+ }),
1538
+ name !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
1539
+ src: imageUrl(name, cwd),
1540
+ alt: name,
1541
+ loading: "lazy",
1542
+ width: 56,
1543
+ height: 56,
1544
+ style: {
1545
+ borderRadius: 8,
1546
+ border: `1px solid ${tokens.border}`,
1547
+ objectFit: "cover",
1548
+ background: tokens.bgLayer,
1549
+ flexShrink: 0
1550
+ }
1551
+ }),
1552
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1553
+ className: "ui-mockup-history-summary",
1554
+ style: {
1555
+ minWidth: 0,
1556
+ display: "flex",
1557
+ flexDirection: "column",
1558
+ gap: 2
1559
+ },
1560
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1561
+ style: {
1562
+ whiteSpace: "nowrap",
1563
+ overflow: "hidden",
1564
+ textOverflow: "ellipsis",
1565
+ fontSize: 13,
1566
+ lineHeight: "20px",
1567
+ fontWeight: row.anchored ? 600 : 400
1568
+ },
1569
+ children: row.description
1570
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1571
+ style: {
1572
+ fontSize: 12,
1573
+ lineHeight: "17px",
1574
+ color: tokens.labelTertiary
1575
+ },
1576
+ children: [
1577
+ formatTime(row.time),
1578
+ " · ",
1579
+ row.model ?? "—",
1580
+ " · ",
1581
+ row.size ?? "—",
1582
+ " ·",
1583
+ " ",
1584
+ t("panel.history.fileCount", { n: row.files.length })
1585
+ ]
1586
+ })]
1587
+ }),
1588
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1589
+ className: "ui-mockup-history-actions",
1590
+ style: {
1591
+ display: "flex",
1592
+ gap: 6,
1593
+ flexWrap: "wrap"
1594
+ },
1595
+ children: [row.anchored && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1596
+ variant: "ghost",
1597
+ size: "sm",
1598
+ onClick: () => void act("anchor/unset", { cwd }),
1599
+ children: t("panel.history.unsetAnchor")
1600
+ }), name !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
1601
+ href: imageUrl(name, cwd),
1602
+ target: "_blank",
1603
+ rel: "noreferrer",
1604
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1605
+ variant: "outline",
1606
+ size: "sm",
1607
+ children: t("card.openOriginal")
1608
+ })
1609
+ })]
1610
+ })
1611
+ ]
1612
+ }, `${row.time}:${row.files[0] ?? index}`);
1613
+ }),
1614
+ anchorPage !== null && anchorPage !== page && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1615
+ className: "ui-mockup-history-anchor-nav",
1616
+ style: {
1617
+ display: "flex",
1618
+ alignItems: "center",
1619
+ gap: 6,
1620
+ fontSize: 12,
1621
+ lineHeight: "17px",
1622
+ color: tokens.labelSecondary
1623
+ },
1624
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: ["🚩 ", t("panel.history.anchorOnPage", { n: anchorPage })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1625
+ variant: "ghost",
1626
+ size: "sm",
1627
+ onClick: () => void reload(appliedQuery, anchorPage),
1628
+ children: t("panel.history.goToAnchor")
1629
+ })]
1630
+ }),
1631
+ totalPages > 1 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1632
+ className: "ui-mockup-history-pagination",
1633
+ style: {
1634
+ display: "flex",
1635
+ alignItems: "center",
1636
+ justifyContent: "space-between",
1637
+ gap: 8,
1638
+ flexWrap: "wrap"
1639
+ },
1640
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1641
+ style: {
1642
+ fontSize: 12,
1643
+ color: tokens.labelTertiary
1644
+ },
1645
+ children: t("panel.history.totalCount", { n: total })
1646
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1647
+ className: "ui-mockup-history-pages",
1648
+ style: {
1649
+ display: "flex",
1650
+ alignItems: "center",
1651
+ gap: 4
1652
+ },
1653
+ children: [
1654
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1655
+ variant: "ghost",
1656
+ size: "sm",
1657
+ disabled: page <= 1,
1658
+ onClick: () => void reload(appliedQuery, page - 1),
1659
+ children: t("panel.history.prev")
1660
+ }),
1661
+ Array.from({ length: totalPages }, (_, i) => i + 1).map((p) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1662
+ variant: p === page ? "primary" : "ghost",
1663
+ size: "sm",
1664
+ className: "ui-mockup-history-page-number",
1665
+ onClick: () => void reload(appliedQuery, p),
1666
+ children: p
1667
+ }, p)),
1668
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1669
+ variant: "ghost",
1670
+ size: "sm",
1671
+ disabled: page >= totalPages,
1672
+ onClick: () => void reload(appliedQuery, page + 1),
1673
+ children: t("panel.history.next")
1674
+ })
1675
+ ]
1676
+ })]
1677
+ }),
1678
+ anchorFile !== null && rows.some((r) => r.anchored) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Notice, { children: t("panel.history.anchorHint") })
1679
+ ]
1680
+ });
1681
+ }
1682
+ function formatTime(iso) {
1683
+ const date = new Date(iso);
1684
+ if (Number.isNaN(date.getTime())) return iso;
1685
+ const pad = (n) => String(n).padStart(2, "0");
1686
+ return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}`;
1687
+ }
1688
+ function Radio(props) {
1689
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
1690
+ style: {
1691
+ display: "inline-flex",
1692
+ alignItems: "center",
1693
+ gap: 4
1694
+ },
1695
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1696
+ type: "radio",
1697
+ name: props.name,
1698
+ checked: props.checked,
1699
+ disabled: props.disabled,
1700
+ onChange: props.onChange
1701
+ }), props.label]
1702
+ });
1703
+ }
1704
+ /** 订阅偏好快照使面板随宿主文档变更刷新(写回后镜像更新 → re-render)。 */
1705
+ function usePrefSync(prefs) {
1706
+ const [, bump] = (0, react.useState)(0);
1707
+ (0, react.useEffect)(() => prefs.subscribe(() => bump((n) => n + 1)), [prefs]);
1708
+ }
1709
+ /** 单字段写入并显示错误。 */
1710
+ async function writePref(prefs, field, value, onError) {
1711
+ try {
1712
+ await prefs.set(field, value);
1713
+ onError("");
1714
+ } catch (err) {
1715
+ onError(err instanceof Error ? err.message : String(err));
1716
+ }
1717
+ }
1718
+ //#endregion
1719
+ //#region src/client/locales.ts
1720
+ /** ui_mockup 客户端 UI 文案(双语,跟随 DSH 语言切换):工具卡片 + 设置面板。 */
1721
+ const NS = "ui-mockup";
1722
+ /** 简体中文 UI 文案。 */
1723
+ const zh = {
1724
+ "card.generating": "生成中…",
1725
+ "card.confirm": "确认采用这版",
1726
+ "card.select": "选用第 {n} 版",
1727
+ "card.selectPlaceholder": "选用某一版…",
1728
+ "card.openOriginal": "打开原图",
1729
+ "card.feedback": "提交修改意见",
1730
+ "card.feedbackPlaceholder": "描述要修改的地方…",
1731
+ "card.feedbackSubmit": "提交并重新生成",
1732
+ "card.feedbackCancel": "取消",
1733
+ "card.confirmMessage": "确认采用这版设计(文件:{name})",
1734
+ "card.selectMessage": "选用第 {n} 版(文件:{name})",
1735
+ "card.feedbackMessage": "{opinion}(基于文件:{name},请按此意见重新生成)",
1736
+ "card.setAnchor": "设为风格锚点:此后未显式传参考图的生成将自动引用这张图保持风格一致",
1737
+ "card.setAnchorButton": "设为锚点",
1738
+ "card.setAnchorSelect": "设为锚点某一版…",
1739
+ "card.setAnchorOption": "第 {n} 版",
1740
+ "card.anchored": "风格锚点",
1741
+ "panel.nav": "UI 草图",
1742
+ "panel.tab.overview": "概览",
1743
+ "panel.tab.provider": "提供方与模型",
1744
+ "panel.tab.preferences": "生成偏好",
1745
+ "panel.tab.history": "生成历史",
1746
+ "panel.loading": "加载中…",
1747
+ "panel.loadFailed": "加载失败: {error}",
1748
+ "panel.readonlyBanner": "当前连接的设置存储为进程内模式或只读,偏好修改不会持久保存。",
1749
+ "panel.testConnection": "测试连接",
1750
+ "panel.testing": "测试中…",
1751
+ "panel.overview.intro": "在研讨阶段生成 UI 线框图 / 高保真设计稿:先看图确认方向,再写实现代码,避免做完才发现界面不符合预期。",
1752
+ "panel.overview.quickTitle": "快速使用",
1753
+ "panel.overview.step1Title": "何时触发",
1754
+ "panel.overview.step1Body": "需求研讨中助手会主动提议出草图,也可以直接说「出个草图」。",
1755
+ "panel.overview.step2Title": "如何反馈",
1756
+ "panel.overview.step2Body": "生成图下方直接点「确认采用 / 选用第 N 版 / 提交修改意见」,或直接打字说。",
1757
+ "panel.overview.step3Title": "如何锁定",
1758
+ "panel.overview.step3Body": "确认后自动提炼进 design/spec.md,无需手动操作。",
1759
+ "panel.overview.statusLine": "当前提供方:{provider} · {credential}",
1760
+ "panel.provider.title": "提供方",
1761
+ "panel.provider.dashscopeName": "阿里云百炼 DashScope",
1762
+ "panel.provider.volcengineName": "火山方舟 Volcengine",
1763
+ "panel.provider.unknown": "未挂载图像提供方",
1764
+ "panel.provider.fallbackActive": "默认生效(DashScope)",
1765
+ "panel.provider.unknownHint": "面板暂时读不到提供方状态(宿主可能仍是旧版本,请重启 DSH 后再试);已按安装默认显示为 DashScope 生效。",
1766
+ "panel.provider.volcengineDisabled": "已安装未启用。点击本卡片即可切换——插件会改写 DSH 用户层 patch,组合热重载后立即生效。",
1767
+ "panel.provider.inactive": "未启用",
1768
+ "panel.provider.modelsReset": "已随切换把模型分层默认重置为「跟随提供方默认」——旧提供方的模型 ID 对新提供方无效。",
1769
+ "panel.credential.title": "凭据({credential})",
1770
+ "panel.credential.ready": "凭据已配置",
1771
+ "panel.credential.missing": "未配置 {credential}",
1772
+ "panel.credential.checking": "检测中…",
1773
+ "panel.credential.howTitle": "配置方式(按读取优先级排序, 任选其一即可):",
1774
+ "panel.credential.way1": "进程环境变量: 启动 DSH 前执行 export {credential}=sk-xxx(CI/容器同理)",
1775
+ "panel.credential.way2": "DSH 密钥存储: ~/.dsh/.credentials.yaml(可在本页下方直接写入,或在 DSH 设置 · 模型页写入)",
1776
+ "panel.credential.way3": "项目 .env: 在启动目录(通常是项目根)的 .env 文件中写 {credential}=sk-xxx",
1777
+ "panel.credential.way4": "DSH 主目录 .env: ~/.dsh/.env 中写 {credential}=sk-xxx",
1778
+ "panel.credential.readyWithSource": "凭据已配置 · 来源: {source}",
1779
+ "panel.credential.keyInputLabel": "{credential} 密钥",
1780
+ "panel.credential.source.env": "进程环境变量",
1781
+ "panel.credential.source.file": "DSH 密钥存储(~/.dsh/.credentials.yaml)",
1782
+ "panel.credential.source.project-env": "项目 .env",
1783
+ "panel.credential.source.user-env": "~/.dsh/.env",
1784
+ "panel.credential.source.ambient": "启动环境",
1785
+ "panel.credential.writePlaceholder": "输入新的 {credential}(写入即覆盖,不会回显)",
1786
+ "panel.credential.save": "保存(覆盖)",
1787
+ "panel.credential.clear": "清除已存密钥",
1788
+ "panel.credential.savedNotice": "已写入 DSH 密钥存储 ✓",
1789
+ "panel.credential.clearedNotice": "已清除 DSH 密钥存储中的 {credential} ✓",
1790
+ "panel.credential.notWritable": "当前密钥由更高优先级的来源({source})提供,面板写入不会生效;如需在面板管理,请先移除该来源中的同名变量。",
1791
+ "panel.test.ok": "凭据有效:鉴权通过,网关可达。",
1792
+ "panel.test.missingKey": "未找到 {credential}——请按上方「配置方式」任选其一配置后重试。",
1793
+ "panel.test.invalidKey": "网关拒绝了这把密钥(鉴权未通过)——请确认 {credential} 是否正确、是否已过期。",
1794
+ "panel.test.gatewayFail": "网关不可达: {detail}",
1795
+ "panel.test.unknown": "探测结果无法判定({detail})——网关行为有变化,请直接生成一张草图验证。",
1796
+ "panel.models.title": "模型分层默认",
1797
+ "panel.models.wireframe": "线框图",
1798
+ "panel.models.highFidelity": "高保真",
1799
+ "panel.models.followDefault": "跟随提供方默认",
1800
+ "panel.prefs.fidelity": "保真度偏好",
1801
+ "panel.prefs.fidelityWireframe": "线框图",
1802
+ "panel.prefs.fidelityHigh": "高保真",
1803
+ "panel.prefs.platform": "目标平台",
1804
+ "panel.prefs.count": "一次生成数量",
1805
+ "panel.prefs.pollTimeout": "轮询超时",
1806
+ "panel.prefs.minutes": "分钟",
1807
+ "panel.prefs.backoff": "限流退避策略",
1808
+ "panel.prefs.backoffFixed": "25 秒 × 2 次(自动)",
1809
+ "panel.prefs.size": "默认尺寸",
1810
+ "panel.prefs.reset": "恢复默认",
1811
+ "panel.prefs.save": "保存",
1812
+ "panel.prefs.saved": "已保存 ✓",
1813
+ "panel.prefs.writeNotApplied": "设置存储未确认本次更改,请重试。",
1814
+ "panel.prefs.writeNotAppliedField": "「{field}」未写入设置存储,保存已中止;之前的字段可能已落盘,请核对后重试。",
1815
+ "panel.history.search": "搜索",
1816
+ "panel.history.searchLabel": "搜索生成历史",
1817
+ "panel.history.searchPlaceholder": "按描述搜索…",
1818
+ "panel.history.pendingSearch": "搜索条件已更改,点击“搜索”更新结果。",
1819
+ "panel.history.clear": "清空历史",
1820
+ "panel.history.confirmClear": "确认清空?",
1821
+ "panel.history.cancelClear": "取消",
1822
+ "panel.history.empty": "暂无生成历史; 出一张草图试试。",
1823
+ "panel.history.setAnchor": "设为锚点",
1824
+ "panel.history.unsetAnchor": "解除锚点",
1825
+ "panel.history.anchorTag": "风格锚点",
1826
+ "panel.history.anchorHint": "设为锚点后, 调用 ui_mockup 未显式传 reference 时会自动引用该图保持多页风格一致。",
1827
+ "panel.history.prev": "上一页",
1828
+ "panel.history.next": "下一页",
1829
+ "panel.history.totalCount": "共 {n} 条",
1830
+ "panel.history.anchorOnPage": "风格锚点在第 {n} 页",
1831
+ "panel.history.goToAnchor": "前往",
1832
+ "panel.history.fileCount": "{n} 张图"
1833
+ };
1834
+ /** English UI copy. */
1835
+ const en = {
1836
+ "card.generating": "Generating…",
1837
+ "card.confirm": "Confirm this version",
1838
+ "card.select": "Use version {n}",
1839
+ "card.selectPlaceholder": "Pick a version…",
1840
+ "card.openOriginal": "Open original",
1841
+ "card.feedback": "Submit feedback",
1842
+ "card.feedbackPlaceholder": "Describe what to change…",
1843
+ "card.feedbackSubmit": "Submit & regenerate",
1844
+ "card.feedbackCancel": "Cancel",
1845
+ "card.confirmMessage": "Confirm this design (file: {name})",
1846
+ "card.selectMessage": "Use version {n} (file: {name})",
1847
+ "card.feedbackMessage": "{opinion} (based on file: {name}, regenerate accordingly)",
1848
+ "card.setAnchor": "Set as style anchor: subsequent generations without an explicit reference will reuse this image for consistent style",
1849
+ "card.setAnchorButton": "Set as anchor",
1850
+ "card.setAnchorSelect": "Set a version as anchor…",
1851
+ "card.setAnchorOption": "Version {n}",
1852
+ "card.anchored": "Style anchor",
1853
+ "panel.nav": "UI Mockups",
1854
+ "panel.tab.overview": "Overview",
1855
+ "panel.tab.provider": "Provider & models",
1856
+ "panel.tab.preferences": "Preferences",
1857
+ "panel.tab.history": "History",
1858
+ "panel.loading": "Loading…",
1859
+ "panel.loadFailed": "Failed to load: {error}",
1860
+ "panel.readonlyBanner": "This connection uses in-process or read-only settings storage; preference changes will not persist.",
1861
+ "panel.testConnection": "Test connection",
1862
+ "panel.testing": "Testing…",
1863
+ "panel.overview.intro": "Generate wireframes / high-fidelity mockups during discussion: confirm direction from the picture before writing code.",
1864
+ "panel.overview.quickTitle": "Quick start",
1865
+ "panel.overview.step1Title": "When to trigger",
1866
+ "panel.overview.step1Body": "The agent offers a sketch during requirement talks; you can also just say \"sketch this\".",
1867
+ "panel.overview.step2Title": "How to give feedback",
1868
+ "panel.overview.step2Body": "Click \"confirm / use version N / submit feedback\" under the image, or type it directly.",
1869
+ "panel.overview.step3Title": "How to lock it in",
1870
+ "panel.overview.step3Body": "On confirmation the design is distilled into design/spec.md automatically.",
1871
+ "panel.overview.statusLine": "Provider: {provider} · {credential}",
1872
+ "panel.provider.title": "Provider",
1873
+ "panel.provider.dashscopeName": "Alibaba DashScope",
1874
+ "panel.provider.volcengineName": "Volcengine Ark",
1875
+ "panel.provider.unknown": "No image provider mounted",
1876
+ "panel.provider.fallbackActive": "Active by default (DashScope)",
1877
+ "panel.provider.unknownHint": "The panel cannot read the provider status right now (the host may still be an older build; restart DSH and retry). Shown as DashScope active per the install default.",
1878
+ "panel.provider.volcengineDisabled": "Installed but disabled. Click this card to switch — the plugin rewrites the home user patch layer and DSH hot-reloads the composition.",
1879
+ "panel.provider.inactive": "Disabled",
1880
+ "panel.provider.modelsReset": "Model defaults were reset to 'follow provider default' on switch — the previous provider's model IDs are not valid here.",
1881
+ "panel.credential.title": "Credentials ({credential})",
1882
+ "panel.credential.ready": "API key configured",
1883
+ "panel.credential.missing": "{credential} not configured",
1884
+ "panel.credential.checking": "Checking…",
1885
+ "panel.credential.howTitle": "How to configure (checked in this order; any one is enough):",
1886
+ "panel.credential.way1": "Process environment: export {credential}=sk-xxx before launching DSH (same for CI/containers)",
1887
+ "panel.credential.way2": "DSH credential store: ~/.dsh/.credentials.yaml (written from DSH Settings · Models)",
1888
+ "panel.credential.way3": "Project .env: add {credential}=sk-xxx to the .env in the launch directory (usually the project root)",
1889
+ "panel.credential.way4": "DSH home .env: add {credential}=sk-xxx to ~/.dsh/.env",
1890
+ "panel.credential.readyWithSource": "API key configured · source: {source}",
1891
+ "panel.credential.keyInputLabel": "{credential} key",
1892
+ "panel.credential.source.env": "process environment",
1893
+ "panel.credential.source.file": "DSH credential store (~/.dsh/.credentials.yaml)",
1894
+ "panel.credential.source.project-env": "project .env",
1895
+ "panel.credential.source.user-env": "~/.dsh/.env",
1896
+ "panel.credential.source.ambient": "launch environment",
1897
+ "panel.credential.writePlaceholder": "Enter a new {credential} (overwrites, never echoed back)",
1898
+ "panel.credential.save": "Save (overwrite)",
1899
+ "panel.credential.clear": "Clear stored key",
1900
+ "panel.credential.savedNotice": "Written to the DSH credential store ✓",
1901
+ "panel.credential.clearedNotice": "{credential} removed from the DSH credential store ✓",
1902
+ "panel.credential.notWritable": "The key is currently supplied by a higher-priority source ({source}); panel writes would not take effect. Remove that variable first to manage it here.",
1903
+ "panel.test.ok": "Credential valid: authentication passed, gateway reachable.",
1904
+ "panel.test.missingKey": "{credential} not found — configure it via any option above, then retry.",
1905
+ "panel.test.invalidKey": "The gateway rejected this key (authentication failed) — check that {credential} is correct and not expired.",
1906
+ "panel.test.gatewayFail": "Gateway unreachable: {detail}",
1907
+ "panel.test.unknown": "Probe result inconclusive ({detail}) — gateway behavior may have changed; verify by generating a sketch.",
1908
+ "panel.models.title": "Model defaults by fidelity",
1909
+ "panel.models.wireframe": "Wireframe",
1910
+ "panel.models.highFidelity": "High fidelity",
1911
+ "panel.models.followDefault": "Follow provider default",
1912
+ "panel.prefs.fidelity": "Fidelity preference",
1913
+ "panel.prefs.fidelityWireframe": "Wireframe",
1914
+ "panel.prefs.fidelityHigh": "High fidelity",
1915
+ "panel.prefs.platform": "Target platform",
1916
+ "panel.prefs.count": "Images per request",
1917
+ "panel.prefs.pollTimeout": "Polling timeout",
1918
+ "panel.prefs.minutes": "minutes",
1919
+ "panel.prefs.backoff": "Rate-limit backoff",
1920
+ "panel.prefs.backoffFixed": "25s × 2 (automatic)",
1921
+ "panel.prefs.size": "Default size",
1922
+ "panel.prefs.reset": "Reset to defaults",
1923
+ "panel.prefs.save": "Save",
1924
+ "panel.prefs.saved": "Saved ✓",
1925
+ "panel.prefs.writeNotApplied": "The settings store did not confirm this change. Please retry.",
1926
+ "panel.prefs.writeNotAppliedField": "\"{field}\" was not confirmed by the settings store; saving stopped. Earlier fields may already be applied — please verify and retry.",
1927
+ "panel.history.search": "Search",
1928
+ "panel.history.searchLabel": "Search generation history",
1929
+ "panel.history.searchPlaceholder": "Search by description…",
1930
+ "panel.history.pendingSearch": "Search criteria changed. Select Search to update the results.",
1931
+ "panel.history.clear": "Clear history",
1932
+ "panel.history.confirmClear": "Confirm clear?",
1933
+ "panel.history.cancelClear": "Cancel",
1934
+ "panel.history.empty": "No generations yet; ask for a sketch to get started.",
1935
+ "panel.history.setAnchor": "Set as anchor",
1936
+ "panel.history.unsetAnchor": "Unset anchor",
1937
+ "panel.history.anchorTag": "Style anchor",
1938
+ "panel.history.anchorHint": "Once set, ui_mockup calls without an explicit reference automatically reuse this image for consistent style.",
1939
+ "panel.history.prev": "Previous",
1940
+ "panel.history.next": "Next",
1941
+ "panel.history.totalCount": "{n} entries",
1942
+ "panel.history.anchorOnPage": "Style anchor on page {n}",
1943
+ "panel.history.goToAnchor": "Go to anchor",
1944
+ "panel.history.fileCount": "{n} images"
1945
+ };
1946
+ //#endregion
1947
+ //#region src/client/index.ts
1948
+ /**
1949
+ * 客户端半区硬依赖:卡片与设置槽注册、语言切换、connection 通道与设置域服务。
1950
+ */
1951
+ const inject = [
1952
+ "slots",
1953
+ "locale",
1954
+ "connection",
1955
+ "settingsScope"
1956
+ ];
1957
+ function apply(ctx) {
1958
+ ctx.effect(() => ctx.locale.register(NS, {
1959
+ zh,
1960
+ en
1961
+ }), "ui-mockup: dictionaries");
1962
+ const prefs = ctx.get("settingsScope").bind({ namespace: "ui-mockup" });
1963
+ const connection = ctx.get("connection");
1964
+ /**
1965
+ * 卡片锚点入口:设锚动作发生在「刚看完这版图」的瞬间,注入窄面
1966
+ * set/unset 闭包捕获 connection,直连 RPC 不绕 agent 消息流。
1967
+ */
1968
+ const anchorFace = { set: (file, cwd) => callPanel(connection, "anchor/set", {
1969
+ file,
1970
+ cwd
1971
+ }) };
1972
+ ctx.slots.inject("tool.call.toolview", () => ctx.slots.register({
1973
+ name: "tool.call.toolview",
1974
+ key: "ui_mockup",
1975
+ locale: NS,
1976
+ inject: () => ({ anchor: anchorFace })
1977
+ }, UiMockupToolview));
1978
+ const panelInjected = () => ({
1979
+ prefs,
1980
+ connection
1981
+ });
1982
+ ctx.slots.inject("settings.section", () => ctx.slots.register({
1983
+ name: "settings.section",
1984
+ id: "ui-mockup",
1985
+ order: 100,
1986
+ label: () => ctx.locale.bind(NS)("panel.nav"),
1987
+ locale: NS,
1988
+ inject: panelInjected
1989
+ }, UiMockupSection));
1990
+ }
1991
+ //#endregion
1992
+ exports.apply = apply;
1993
+ exports.inject = inject;
1994
+ return module.exports;
1995
+ }
1996
+ });
1997
+
1998
+ //# sourceMappingURL=client.js.map