dsh-audiogen 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,2457 @@
1
+ window.__ModuleLoader__.load({
2
+ id: "dsh-audiogen",
3
+ factory: (require) => {
4
+ var module = { exports: {} };
5
+ var exports = module.exports;
6
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
+ let react_dom_client = require("react-dom/client");
8
+ let react = require("react");
9
+ let _deepseek_ai_dsh_client_runtime_client = require("@deepseek-ai/dsh-client-runtime/client");
10
+ let react_jsx_runtime = require("react/jsx-runtime");
11
+ //#region src/protocol.ts
12
+ /** Same-origin route family (loopback-only, mirroring dsh-imagegen). */
13
+ const SETTINGS_API = {
14
+ describe: "/api/dsh-audiogen/settings/describe",
15
+ mutate: "/api/dsh-audiogen/settings/mutate"
16
+ };
17
+ /** The audio-generation proxy route. */
18
+ const GENERATE_API = "/api/dsh-audiogen/generate";
19
+ /** Host-mediated built-in provider catalog (channels the user can instantiate). */
20
+ const PRESETS_API = "/api/dsh-audiogen/presets";
21
+ /** Host-persisted generation history routes. */
22
+ const HISTORY_API = {
23
+ list: "/api/dsh-audiogen/history/list",
24
+ append: "/api/dsh-audiogen/history/append",
25
+ remove: "/api/dsh-audiogen/history/remove",
26
+ clear: "/api/dsh-audiogen/history/clear",
27
+ audio: "/api/dsh-audiogen/history/audio"
28
+ };
29
+ //#endregion
30
+ //#region src/client/api.ts
31
+ /**
32
+ * Browser-side API client for the audio generation and history routes.
33
+ */
34
+ var AudiogenApi = class {
35
+ async generate(request) {
36
+ return await (await fetch(GENERATE_API, {
37
+ method: "POST",
38
+ headers: { "content-type": "application/json" },
39
+ body: JSON.stringify(request)
40
+ })).json();
41
+ }
42
+ async history() {
43
+ const body = await (await fetch(HISTORY_API.list, { method: "POST" })).json();
44
+ return body.ok === true ? body.history ?? [] : [];
45
+ }
46
+ async clearHistory() {
47
+ await fetch(HISTORY_API.clear, { method: "POST" });
48
+ }
49
+ };
50
+ //#endregion
51
+ //#region src/client/controller.ts
52
+ var AudioGenController = class {
53
+ panelOpen = false;
54
+ listeners = /* @__PURE__ */ new Set();
55
+ getSnapshot() {
56
+ return { panelOpen: this.panelOpen };
57
+ }
58
+ subscribe(fn) {
59
+ this.listeners.add(fn);
60
+ return () => {
61
+ this.listeners.delete(fn);
62
+ };
63
+ }
64
+ open() {
65
+ if (this.panelOpen) return;
66
+ this.panelOpen = true;
67
+ this.notify();
68
+ }
69
+ close() {
70
+ if (!this.panelOpen) return;
71
+ this.panelOpen = false;
72
+ this.notify();
73
+ }
74
+ toggle() {
75
+ if (this.panelOpen) this.close();
76
+ else this.open();
77
+ }
78
+ notify() {
79
+ for (const fn of [...this.listeners]) fn();
80
+ }
81
+ };
82
+ //#endregion
83
+ //#region src/client/locales.ts
84
+ /**
85
+ * dsh-audiogen surface copy: zh is the key source, en mirrors every key.
86
+ */
87
+ const zh = {
88
+ "entry.label": "AI 音频",
89
+ "entry.tooltip": "AI 音频面板(TTS / 音乐 / 音效)",
90
+ "panel.title": "AI 音频",
91
+ "mode.tts": "文本转语音",
92
+ "mode.music": "音乐生成",
93
+ "mode.sfx": "音效生成",
94
+ "prompt.placeholder": "输入要朗读的文本,或描述想生成的音乐 / 音效…",
95
+ "prompt.required": "请输入文本或提示词",
96
+ "model.label": "模型 / 音色",
97
+ "voice.label": "音色",
98
+ "speed.label": "语速",
99
+ "duration.label": "时长(秒)",
100
+ "format.label": "输出格式",
101
+ "generate": "开始生成",
102
+ "generating": "生成中…",
103
+ "download": "下载",
104
+ "result.empty": "生成结果将显示在这里",
105
+ "result.done": "生成完成,共 {count} 段音频",
106
+ "history.title": "历史记录",
107
+ "history.empty": "暂无历史记录",
108
+ "config.missing": "尚未配置音频 API:请前往「设置 → 插件 → AI 音频」添加渠道。",
109
+ "config.disabled": "插件已停用,请在设置中重新启用。",
110
+ "settings.title": "AI 音频(dsh-audiogen)",
111
+ "settings.description": "配置多厂商音频生成 API 地址与密钥",
112
+ "settings.collapse": "收起",
113
+ "settings.expand": "展开",
114
+ "settings.enabled": "启用插件",
115
+ "settings.announceToAgent": "向 Agent 播报本插件",
116
+ "settings.allowAgentAudio": "允许 Agent 调用音频生成",
117
+ "settings.save": "保存",
118
+ "settings.saving": "保存中…",
119
+ "settings.discard": "放弃修改",
120
+ "settings.unsaved": "有未保存的修改",
121
+ "settings.readOnly": "当前设置为只读。",
122
+ "channels.title": "音频渠道",
123
+ "channels.hint": "每个渠道是一个独立的音频厂商/API 端点,可配置多个。",
124
+ "channels.empty": "还没有渠道。",
125
+ "channels.addProvider": "+ 添加预置厂商",
126
+ "channels.addCustom": "+ 添加自定义渠道",
127
+ "channels.edit": "编辑",
128
+ "channels.delete": "删除",
129
+ "channels.confirm": "确认删除",
130
+ "channels.cancel": "取消",
131
+ "channels.keySet": "已填密钥",
132
+ "channels.keyMissing": "未填密钥",
133
+ "channels.modelCount": "{n} 个模型/音色",
134
+ "channels.noModels": "未配置模型",
135
+ "channels.statusReady": "可用",
136
+ "channels.statusIncomplete": "未完成",
137
+ "channels.untitled": "未命名渠道",
138
+ "channel.name": "名称",
139
+ "channel.apiUrl": "API 地址",
140
+ "channel.apiKey": "API 密钥",
141
+ "channel.apiKeyHint": "留空则保持当前密钥;输入新值可更换。",
142
+ "channel.models": "模型 / 音色(每行一个:别名=上游ID)",
143
+ "channel.modelsHint": "例如 tts-1=tts-1 或 Rachel=21m00Tcm4TlvDq8ikWAM",
144
+ "channel.default": "设为默认",
145
+ "channel.cancel": "取消",
146
+ "channel.save": "保存渠道",
147
+ "presets.title": "预置厂商",
148
+ "presets.custom": "自定义渠道"
149
+ };
150
+ const en = {
151
+ "entry.label": "AI Audio",
152
+ "entry.tooltip": "AI audio panel (TTS / music / SFX)",
153
+ "panel.title": "AI Audio",
154
+ "mode.tts": "Text to speech",
155
+ "mode.music": "Music",
156
+ "mode.sfx": "Sound effects",
157
+ "prompt.placeholder": "Text to speak, or a description of the music / sound effect…",
158
+ "prompt.required": "Prompt or text is required",
159
+ "model.label": "Model / voice",
160
+ "voice.label": "Voice",
161
+ "speed.label": "Speed",
162
+ "duration.label": "Duration (s)",
163
+ "format.label": "Format",
164
+ "generate": "Generate",
165
+ "generating": "Generating…",
166
+ "download": "Download",
167
+ "result.empty": "Generated audio will appear here.",
168
+ "result.done": "Done, {count} audio file(s).",
169
+ "history.title": "History",
170
+ "history.empty": "No audio history yet.",
171
+ "config.missing": "No audio API configured. Open Settings > Plugins > AI Audio and add a channel.",
172
+ "config.disabled": "The plugin is disabled. Enable it in Settings.",
173
+ "settings.title": "AI Audio (dsh-audiogen)",
174
+ "settings.description": "Configure multi-vendor audio generation endpoints and keys",
175
+ "settings.collapse": "Collapse",
176
+ "settings.expand": "Expand",
177
+ "settings.enabled": "Enable plugin",
178
+ "settings.announceToAgent": "Announce this plugin to agents",
179
+ "settings.allowAgentAudio": "Allow agents to generate audio",
180
+ "settings.save": "Save",
181
+ "settings.saving": "Saving…",
182
+ "settings.discard": "Discard",
183
+ "settings.unsaved": "Unsaved changes",
184
+ "settings.readOnly": "Settings are read-only.",
185
+ "channels.title": "Audio channels",
186
+ "channels.hint": "Each channel is an independent audio vendor/API endpoint.",
187
+ "channels.empty": "No channels yet.",
188
+ "channels.addProvider": "+ Add preset vendor",
189
+ "channels.addCustom": "+ Add custom channel",
190
+ "channels.edit": "Edit",
191
+ "channels.delete": "Delete",
192
+ "channels.confirm": "Confirm delete",
193
+ "channels.cancel": "Cancel",
194
+ "channels.keySet": "Key set",
195
+ "channels.keyMissing": "No key",
196
+ "channels.modelCount": "{n} model(s)/voice(s)",
197
+ "channels.noModels": "No models configured",
198
+ "channels.statusReady": "Ready",
199
+ "channels.statusIncomplete": "Incomplete",
200
+ "channels.untitled": "Untitled channel",
201
+ "channel.name": "Name",
202
+ "channel.apiUrl": "API URL",
203
+ "channel.apiKey": "API key",
204
+ "channel.apiKeyHint": "Leave blank to keep the current key.",
205
+ "channel.models": "Models / voices (one per line: alias=upstreamId)",
206
+ "channel.modelsHint": "e.g. tts-1=tts-1 or Rachel=21m00Tcm4TlvDq8ikWAM",
207
+ "channel.default": "Set default",
208
+ "channel.cancel": "Cancel",
209
+ "channel.save": "Save channel",
210
+ "presets.title": "Preset vendors",
211
+ "presets.custom": "Custom channel"
212
+ };
213
+ //#endregion
214
+ //#region src/client/helpers.ts
215
+ /**
216
+ * Shared panel helpers: active-dictionary pick and a small error extractor.
217
+ */
218
+ function dictionary() {
219
+ return (typeof document !== "undefined" ? document.documentElement.lang : "zh").toLowerCase().startsWith("en") ? { ...en } : { ...zh };
220
+ }
221
+ function tt(key, values) {
222
+ const text = dictionary()[key] ?? key;
223
+ if (values === void 0) return text;
224
+ let rendered = text;
225
+ for (const [name, value] of Object.entries(values)) rendered = rendered.replaceAll(`{${name}}`, String(value));
226
+ return rendered;
227
+ }
228
+ //#endregion
229
+ //#region src/client/settings-scope.ts
230
+ /**
231
+ * Browser-side settings scope for the dsh-audiogen namespace, served by the
232
+ * plugin's own loopback bridge routes (/api/dsh-audiogen/settings). The
233
+ * official rc.6 settings scope answers "unavailable" for every third-party
234
+ * namespace (the host-apiproxy allowlist is hard-coded), so this package
235
+ * re-serves its namespace through the host settings seam over a same-origin,
236
+ * loopback-only HTTP pair — the same pattern the dsh-web-ui family bridge
237
+ * uses, self-contained per plugin.
238
+ */
239
+ /** Settings wire face over the bridge routes (fetch-backed). */
240
+ function createBridgeApi(fetchFn) {
241
+ const post = async (path, body) => {
242
+ try {
243
+ const response = await fetchFn(path, {
244
+ method: "POST",
245
+ headers: { "content-type": "application/json" },
246
+ body: JSON.stringify(body)
247
+ });
248
+ if (!response.ok) return { result: {
249
+ ok: false,
250
+ code: "internal",
251
+ message: `bridge HTTP ${response.status}`
252
+ } };
253
+ return { result: await response.json() };
254
+ } catch {
255
+ return { result: {
256
+ ok: false,
257
+ code: "internal",
258
+ message: "settings bridge unreachable"
259
+ } };
260
+ }
261
+ };
262
+ return { settings: {
263
+ describe: async (payload) => post(SETTINGS_API.describe, payload),
264
+ mutate: async (payload) => post(SETTINGS_API.mutate, payload)
265
+ } };
266
+ }
267
+ /**
268
+ * A SettingsScope over the bridge face: serialized queue, revision-fenced
269
+ * writes, recovery read after a refusal. Mirrors the official controller's
270
+ * ordering but trusts the Host-seam value without re-running the wire-schema
271
+ * validation — the seam already validated it.
272
+ */
273
+ var BridgeScopeController = class {
274
+ api;
275
+ spec;
276
+ store;
277
+ /** Whether the namespace currently holds a stored secret (e.g. apiKey). */
278
+ keySet;
279
+ /** Individual secret presence bits, keyed by the settings field name. */
280
+ secretSets;
281
+ tail = Promise.resolve();
282
+ disposed = false;
283
+ constructor(api, spec) {
284
+ this.api = api;
285
+ this.spec = spec;
286
+ this.store = (0, _deepseek_ai_dsh_client_runtime_client.createSnapshotStore)({
287
+ status: "loading",
288
+ value: void 0,
289
+ base: void 0,
290
+ user: void 0,
291
+ revision: void 0,
292
+ writable: false,
293
+ mode: "host"
294
+ });
295
+ this.keySet = (0, _deepseek_ai_dsh_client_runtime_client.createSnapshotStore)(false);
296
+ this.secretSets = (0, _deepseek_ai_dsh_client_runtime_client.createSnapshotStore)({});
297
+ }
298
+ getSnapshot() {
299
+ return this.store.getSnapshot();
300
+ }
301
+ /** Whether a stored secret exists (from the redacted view's secrets list). */
302
+ getKeySetSnapshot() {
303
+ return this.keySet.getSnapshot();
304
+ }
305
+ /** Observe the secret-set flag. */
306
+ subscribeKeySet(listener) {
307
+ return this.keySet.subscribe(listener);
308
+ }
309
+ /** Whether a specific secret field currently has a stored value. */
310
+ getSecretSetSnapshot(field) {
311
+ return this.secretSets.getSnapshot()[field] === true;
312
+ }
313
+ /** Observe changes to individual secret-field presence bits. */
314
+ subscribeSecretSets(listener) {
315
+ return this.secretSets.subscribe(listener);
316
+ }
317
+ subscribe(listener) {
318
+ return this.store.subscribe(listener);
319
+ }
320
+ /** Queue a bridge refresh. */
321
+ load() {
322
+ return this.enqueue(() => this.read());
323
+ }
324
+ set(field, value) {
325
+ return this.enqueue(() => this.writeOps([{
326
+ op: "set",
327
+ path: [field],
328
+ value
329
+ }]));
330
+ }
331
+ unset(field) {
332
+ return this.enqueue(() => this.writeOps([{
333
+ op: "unset",
334
+ path: [field]
335
+ }]));
336
+ }
337
+ /** Apply several path ops in one revision-fenced mutate call (atomic save).
338
+ * Path ops may address plain-object fields (e.g. `channelSecrets.<id>`),
339
+ * but never navigate *inside* arrays — write array fields wholesale. */
340
+ mutateOps(ops) {
341
+ return this.enqueue(() => this.writeOps(ops));
342
+ }
343
+ async dispose() {
344
+ this.disposed = true;
345
+ await this.tail;
346
+ }
347
+ enqueue(operation) {
348
+ if (this.disposed) return Promise.resolve();
349
+ const task = this.tail.then(async () => {
350
+ if (this.disposed) return;
351
+ await operation();
352
+ });
353
+ this.tail = task.catch(() => {});
354
+ return task;
355
+ }
356
+ async read() {
357
+ let response;
358
+ try {
359
+ response = await this.api.describe({});
360
+ } catch {
361
+ if (!this.disposed) this.store.update((draft) => {
362
+ draft.status = "unavailable";
363
+ });
364
+ return;
365
+ }
366
+ if (!response.result.ok || this.disposed) {
367
+ if (!this.disposed) this.store.update((draft) => {
368
+ draft.status = "unavailable";
369
+ });
370
+ return;
371
+ }
372
+ const { namespaces, writable } = response.result.value;
373
+ const view = namespaces?.find((candidate) => candidate.ns === this.spec.namespace);
374
+ if (view === void 0) {
375
+ this.store.update((draft) => {
376
+ draft.status = "unavailable";
377
+ draft.writable = writable === true;
378
+ });
379
+ this.keySet.set(false);
380
+ this.secretSets.set({});
381
+ return;
382
+ }
383
+ this.accept(view, writable);
384
+ }
385
+ async writeOps(ops) {
386
+ const revision = this.getSnapshot().revision;
387
+ let response;
388
+ try {
389
+ response = await this.api.mutate({
390
+ ns: this.spec.namespace,
391
+ ops,
392
+ ...revision === void 0 ? {} : { expectedRevision: revision }
393
+ });
394
+ } catch {
395
+ await this.read();
396
+ return;
397
+ }
398
+ if (!response.result.ok || this.disposed) {
399
+ await this.read();
400
+ return;
401
+ }
402
+ this.accept(response.result.value, void 0);
403
+ }
404
+ accept(view, writable) {
405
+ this.store.update((draft) => {
406
+ draft.revision = view.revision;
407
+ draft.base = view.base;
408
+ draft.user = view.user;
409
+ if (writable !== void 0) draft.writable = writable;
410
+ draft.status = "ready";
411
+ draft.value = view.value;
412
+ });
413
+ const secretSets = Object.fromEntries((view.secrets ?? []).map((secret) => [secret.path.join("."), secret.set]));
414
+ this.keySet.set(Object.values(secretSets).some(Boolean));
415
+ this.secretSets.set(secretSets);
416
+ }
417
+ };
418
+ /**
419
+ * Bind the dsh-audiogen settings scope over the bridge routes and start its
420
+ * initial read (the caller mounts nothing until the scope settles).
421
+ * @param fetchFn - the fetch implementation (the global fetch on loopback).
422
+ * @returns the scope; unavailable when the bridge is unreachable.
423
+ */
424
+ function bindAudiogenScope(fetchFn = fetch) {
425
+ const controller = new BridgeScopeController(createBridgeApi(fetchFn).settings, { namespace: "dsh-audiogen" });
426
+ controller.load();
427
+ return controller;
428
+ }
429
+ /**
430
+ * Flatten the configured channels into the model options the panel lists
431
+ * (aliases; the default channel's models first) plus the default channel id.
432
+ * Falls back to the legacy flat allow-list while no channels exist (upgrade
433
+ * path). Pure projection — no host calls.
434
+ */
435
+ function audioModelOptions(config) {
436
+ const channels = config?.channels ?? [];
437
+ if (channels.length === 0) return { models: [] };
438
+ const defaultId = config?.defaultChannelId !== void 0 && channels.some((channel) => channel.id === config.defaultChannelId) ? config.defaultChannelId : channels[0].id;
439
+ const ordered = [defaultId, ...channels.filter((channel) => channel.id !== defaultId).map((channel) => channel.id)];
440
+ const models = [];
441
+ for (const id of ordered) {
442
+ const channel = channels.find((candidate) => candidate.id === id);
443
+ for (const model of channel.models) if (model.alias !== "" && !models.includes(model.alias)) models.push(model.alias);
444
+ }
445
+ return models.length > 0 ? {
446
+ models,
447
+ defaultChannelId: defaultId
448
+ } : {
449
+ models: [],
450
+ defaultChannelId: defaultId
451
+ };
452
+ }
453
+ //#endregion
454
+ //#region \0dsh-css:/Users/shimingming/Projects_code/dsh-audiogen/src/client/audio-panel.module.css.mjs
455
+ const css$3 = ".Oo1fpq_panel{background:var(--dsw-alias-bg-base,#f7f7f8);height:100%;color:var(--dsw-alias-label-primary,#1f2328);font-family:var(--dsw-font-family,system-ui, sans-serif);flex-direction:column;gap:12px;padding:14px 16px;display:flex;overflow:hidden}.Oo1fpq_header{justify-content:space-between;align-items:center;display:flex}.Oo1fpq_title{margin:0;font-size:16px;font-weight:700}.Oo1fpq_layout{flex:1;gap:14px;min-height:0;display:flex}.Oo1fpq_form{flex-direction:column;flex:none;gap:12px;width:300px;min-width:260px;max-width:340px;display:flex;overflow-y:auto}.Oo1fpq_result{border:1px solid var(--dsw-alias-border-l1,#e5e7eb);background:var(--dsw-alias-bg-layer-1,#fff);border-radius:12px;flex-direction:column;flex:1;min-width:0;padding:14px;display:flex;overflow-y:auto}.Oo1fpq_label{color:var(--dsw-alias-label-secondary,#6b7280);flex-direction:column;gap:5px;font-size:12px;font-weight:600;display:flex}.Oo1fpq_textarea,.Oo1fpq_input,.Oo1fpq_select{border:1px solid var(--dsw-alias-border-l2,#d1d5db);background:var(--dsw-alias-bg-layer-3,#fff);width:100%;min-height:36px;color:var(--dsw-alias-label-primary,#1f2328);font:inherit;border-radius:8px;outline:none;padding:7px 10px;font-size:13px}.Oo1fpq_textarea{resize:vertical;min-height:110px}.Oo1fpq_modeRow{gap:6px;display:flex}.Oo1fpq_modeButton{border:1px solid var(--dsw-alias-border-l2,#d1d5db);font:inherit;cursor:pointer;background:0 0;border-radius:8px;flex:1;padding:7px 8px;font-size:12px}.Oo1fpq_modeButton[data-active=true]{border-color:var(--dsw-alias-brand-primary,#2563eb);color:var(--dsw-alias-brand-primary,#2563eb);font-weight:600}.Oo1fpq_generate{background:var(--dsw-alias-label-primary,#1f2328);color:var(--dsw-alias-bg-layer-3,#fff);font:inherit;cursor:pointer;border:0;border-radius:10px;padding:9px 14px;font-size:13px}.Oo1fpq_generate:disabled{opacity:.5;cursor:default}.Oo1fpq_empty,.Oo1fpq_error,.Oo1fpq_hint{color:var(--dsw-alias-label-secondary,#6b7280);font-size:13px}.Oo1fpq_error{color:#b91c1c}.Oo1fpq_audioList{gap:10px;margin-top:8px;display:grid}.Oo1fpq_audioCard{border:1px solid var(--dsw-alias-border-l2,#e5e7eb);border-radius:10px;flex-direction:column;gap:6px;padding:10px;display:flex}.Oo1fpq_audio{width:100%;height:36px}.Oo1fpq_download{color:#2563eb;font-size:12px;text-decoration:none}.Oo1fpq_history{border-left:1px solid var(--dsw-alias-border-l1,#e5e7eb);flex:none;width:260px;padding-left:12px;overflow-y:auto}.Oo1fpq_historyTitle{font-size:13px;font-weight:700}.Oo1fpq_historyEmpty{color:var(--dsw-alias-label-tertiary,#9ca3af);font-size:12px}.Oo1fpq_historyItem{border-bottom:1px solid var(--dsw-alias-border-l1,#e5e7eb);padding:8px 0}.Oo1fpq_historyPrompt{text-overflow:ellipsis;white-space:nowrap;font-size:12px;overflow:hidden}.Oo1fpq_historyMeta{color:var(--dsw-alias-label-tertiary,#9ca3af);font-size:11px}.Oo1fpq_historyAudio{width:100%;height:30px;margin-top:4px}";
456
+ const tagId$3 = "dsh-audiogen/audio-panel.module.css";
457
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$3) + "]") === null) {
458
+ const tag = document.createElement("style");
459
+ tag.dataset.plugin = "dsh-audiogen";
460
+ tag.dataset.pluginCss = tagId$3;
461
+ tag.textContent = css$3;
462
+ document.head.appendChild(tag);
463
+ }
464
+ var audio_panel_module_css_default = {
465
+ "download": "Oo1fpq_download",
466
+ "input": "Oo1fpq_input",
467
+ "history": "Oo1fpq_history",
468
+ "result": "Oo1fpq_result",
469
+ "historyPrompt": "Oo1fpq_historyPrompt",
470
+ "audioList": "Oo1fpq_audioList",
471
+ "form": "Oo1fpq_form",
472
+ "textarea": "Oo1fpq_textarea",
473
+ "historyMeta": "Oo1fpq_historyMeta",
474
+ "title": "Oo1fpq_title",
475
+ "hint": "Oo1fpq_hint",
476
+ "historyAudio": "Oo1fpq_historyAudio",
477
+ "historyTitle": "Oo1fpq_historyTitle",
478
+ "header": "Oo1fpq_header",
479
+ "audio": "Oo1fpq_audio",
480
+ "label": "Oo1fpq_label",
481
+ "empty": "Oo1fpq_empty",
482
+ "generate": "Oo1fpq_generate",
483
+ "audioCard": "Oo1fpq_audioCard",
484
+ "layout": "Oo1fpq_layout",
485
+ "select": "Oo1fpq_select",
486
+ "historyEmpty": "Oo1fpq_historyEmpty",
487
+ "historyItem": "Oo1fpq_historyItem",
488
+ "error": "Oo1fpq_error",
489
+ "modeButton": "Oo1fpq_modeButton",
490
+ "panel": "Oo1fpq_panel",
491
+ "modeRow": "Oo1fpq_modeRow"
492
+ };
493
+ //#endregion
494
+ //#region src/client/AudioGenPanel.tsx
495
+ /**
496
+ * The AI 音频 panel: a compact audio-generation studio.
497
+ */
498
+ function useConfig(scope) {
499
+ const [value, setValue] = (0, react.useState)(scope.getSnapshot().value);
500
+ (0, react.useEffect)(() => scope.subscribe(() => {
501
+ setValue(scope.getSnapshot().value);
502
+ }), [scope]);
503
+ return value;
504
+ }
505
+ function useHistory() {
506
+ const [entries, setEntries] = (0, react.useState)([]);
507
+ const reload = () => {
508
+ fetch(HISTORY_API.list, { method: "POST" }).then(async (response) => {
509
+ const body = await response.json();
510
+ if (body.ok === true) setEntries(body.history ?? []);
511
+ }).catch(() => {});
512
+ };
513
+ (0, react.useEffect)(() => {
514
+ reload();
515
+ }, []);
516
+ const clear = () => {
517
+ fetch(HISTORY_API.clear, { method: "POST" }).then(() => reload()).catch(() => {});
518
+ };
519
+ return {
520
+ entries,
521
+ reload,
522
+ clear
523
+ };
524
+ }
525
+ function dataUrlOf(audio) {
526
+ return `data:${audio.mime};base64,${audio.b64}`;
527
+ }
528
+ function AudioGenPanel(props) {
529
+ const { api, scope } = props;
530
+ const config = useConfig(scope);
531
+ const enabled = config?.enabled ?? true;
532
+ const modelOptions = audioModelOptions(config);
533
+ const channels = config?.channels ?? [];
534
+ const connected = enabled && channels.some((channel) => {
535
+ const keyHeld = scope.getSecretSetSnapshot(`channelSecrets.${channel.id}`);
536
+ return channel.apiUrl.trim() !== "" && keyHeld && channel.models.length > 0;
537
+ });
538
+ const [mode, setMode] = (0, react.useState)("tts");
539
+ const [prompt, setPrompt] = (0, react.useState)("");
540
+ const [model, setModel] = (0, react.useState)("");
541
+ const [voice, setVoice] = (0, react.useState)("");
542
+ const [speed, setSpeed] = (0, react.useState)("");
543
+ const [duration, setDuration] = (0, react.useState)("");
544
+ const [format, setFormat] = (0, react.useState)("mp3");
545
+ const [loading, setLoading] = (0, react.useState)(false);
546
+ const [error, setError] = (0, react.useState)(null);
547
+ const [outputs, setOutputs] = (0, react.useState)([]);
548
+ const { entries, reload, clear } = useHistory();
549
+ (0, react.useEffect)(() => {
550
+ if (modelOptions.models.length > 0 && !modelOptions.models.includes(model)) setModel(modelOptions.models[0]);
551
+ }, [modelOptions.models, model]);
552
+ const submit = async () => {
553
+ if (prompt.trim() === "") {
554
+ setError(tt("prompt.required"));
555
+ return;
556
+ }
557
+ setLoading(true);
558
+ setError(null);
559
+ try {
560
+ const response = await api.generate({
561
+ mode,
562
+ model: (model || modelOptions.models[0]) ?? "",
563
+ prompt: prompt.trim(),
564
+ ...voice.trim() !== "" ? { voice: voice.trim() } : {},
565
+ ...speed.trim() !== "" ? { speed: Number(speed) } : {},
566
+ ...duration.trim() !== "" ? { duration: Number(duration) } : {},
567
+ ...format.trim() !== "" ? { format: format.trim() } : {}
568
+ });
569
+ if (!response.ok) {
570
+ setError(response.message ?? "生成失败");
571
+ return;
572
+ }
573
+ setOutputs(response.outputs ?? []);
574
+ reload();
575
+ } catch (err) {
576
+ setError(err instanceof Error ? err.message : String(err));
577
+ } finally {
578
+ setLoading(false);
579
+ }
580
+ };
581
+ const modeLabel = (0, react.useMemo)(() => {
582
+ if (mode === "tts") return tt("mode.tts");
583
+ if (mode === "music") return tt("mode.music");
584
+ return tt("mode.sfx");
585
+ }, [mode]);
586
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
587
+ className: audio_panel_module_css_default.panel,
588
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
589
+ className: audio_panel_module_css_default.header,
590
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
591
+ className: audio_panel_module_css_default.title,
592
+ children: tt("panel.title")
593
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
594
+ className: audio_panel_module_css_default.hint,
595
+ children: modeLabel
596
+ })]
597
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
598
+ className: audio_panel_module_css_default.layout,
599
+ children: [
600
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
601
+ className: audio_panel_module_css_default.form,
602
+ children: [
603
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
604
+ className: audio_panel_module_css_default.modeRow,
605
+ children: [
606
+ "tts",
607
+ "music",
608
+ "sfx"
609
+ ].map((item) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
610
+ type: "button",
611
+ className: audio_panel_module_css_default.modeButton,
612
+ "data-active": mode === item ? "true" : "false",
613
+ onClick: () => setMode(item),
614
+ children: item === "tts" ? tt("mode.tts") : item === "music" ? tt("mode.music") : tt("mode.sfx")
615
+ }, item))
616
+ }),
617
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
618
+ className: audio_panel_module_css_default.label,
619
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: mode === "tts" ? "文本" : "提示词" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
620
+ className: audio_panel_module_css_default.textarea,
621
+ value: prompt,
622
+ onChange: (event) => setPrompt(event.target.value),
623
+ placeholder: tt("prompt.placeholder")
624
+ })]
625
+ }),
626
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
627
+ className: audio_panel_module_css_default.label,
628
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: tt("model.label") }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
629
+ className: audio_panel_module_css_default.select,
630
+ value: model,
631
+ onChange: (event) => setModel(event.target.value),
632
+ children: [modelOptions.models.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
633
+ value: "",
634
+ children: "(请在设置中添加)"
635
+ }) : null, modelOptions.models.map((item) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
636
+ value: item,
637
+ children: item
638
+ }, item))]
639
+ })]
640
+ }),
641
+ mode === "tts" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
642
+ className: audio_panel_module_css_default.label,
643
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: tt("voice.label") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
644
+ className: audio_panel_module_css_default.input,
645
+ value: voice,
646
+ onChange: (event) => setVoice(event.target.value),
647
+ placeholder: "alloy / 自定义音色"
648
+ })]
649
+ }) : null,
650
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
651
+ className: audio_panel_module_css_default.label,
652
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: tt("speed.label") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
653
+ className: audio_panel_module_css_default.input,
654
+ type: "number",
655
+ step: "0.1",
656
+ min: "0.5",
657
+ max: "2",
658
+ value: speed,
659
+ onChange: (event) => setSpeed(event.target.value),
660
+ placeholder: "1.0"
661
+ })]
662
+ }),
663
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
664
+ className: audio_panel_module_css_default.label,
665
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: tt("duration.label") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
666
+ className: audio_panel_module_css_default.input,
667
+ type: "number",
668
+ step: "1",
669
+ min: "1",
670
+ max: "120",
671
+ value: duration,
672
+ onChange: (event) => setDuration(event.target.value),
673
+ placeholder: "30"
674
+ })]
675
+ }),
676
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
677
+ className: audio_panel_module_css_default.label,
678
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: tt("format.label") }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
679
+ className: audio_panel_module_css_default.select,
680
+ value: format,
681
+ onChange: (event) => setFormat(event.target.value),
682
+ children: [
683
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
684
+ value: "mp3",
685
+ children: "mp3"
686
+ }),
687
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
688
+ value: "wav",
689
+ children: "wav"
690
+ }),
691
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
692
+ value: "flac",
693
+ children: "flac"
694
+ }),
695
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
696
+ value: "ogg",
697
+ children: "ogg"
698
+ }),
699
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
700
+ value: "pcm",
701
+ children: "pcm"
702
+ })
703
+ ]
704
+ })]
705
+ }),
706
+ !connected && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
707
+ className: audio_panel_module_css_default.hint,
708
+ children: tt("config.missing")
709
+ }),
710
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
711
+ type: "button",
712
+ className: audio_panel_module_css_default.generate,
713
+ disabled: loading || !connected,
714
+ onClick: () => void submit(),
715
+ children: loading ? tt("generating") : tt("generate")
716
+ })
717
+ ]
718
+ }),
719
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
720
+ className: audio_panel_module_css_default.result,
721
+ children: [error !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
722
+ className: audio_panel_module_css_default.error,
723
+ children: error
724
+ }) : null, outputs.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
725
+ className: audio_panel_module_css_default.empty,
726
+ children: tt("result.empty")
727
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
728
+ className: audio_panel_module_css_default.hint,
729
+ children: tt("result.done", { count: outputs.length })
730
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
731
+ className: audio_panel_module_css_default.audioList,
732
+ children: outputs.map((audio, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
733
+ className: audio_panel_module_css_default.audioCard,
734
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("audio", {
735
+ className: audio_panel_module_css_default.audio,
736
+ controls: true,
737
+ preload: "metadata",
738
+ src: dataUrlOf(audio)
739
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
740
+ className: audio_panel_module_css_default.download,
741
+ href: dataUrlOf(audio),
742
+ download: `generated-${index + 1}.${audio.mime.split("/")[1]?.replace("mpeg", "mp3") ?? "mp3"}`,
743
+ children: "下载"
744
+ })]
745
+ }, audio.id))
746
+ })] })]
747
+ }),
748
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("aside", {
749
+ className: audio_panel_module_css_default.history,
750
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
751
+ className: audio_panel_module_css_default.historyHeader,
752
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", {
753
+ className: audio_panel_module_css_default.historyTitle,
754
+ children: tt("history.title")
755
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
756
+ type: "button",
757
+ onClick: clear,
758
+ style: {
759
+ border: 0,
760
+ background: "none",
761
+ cursor: "pointer",
762
+ color: "inherit",
763
+ fontSize: 12
764
+ },
765
+ children: "清空"
766
+ })]
767
+ }), entries.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
768
+ className: audio_panel_module_css_default.historyEmpty,
769
+ children: tt("history.empty")
770
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { children: entries.map((entry) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
771
+ className: audio_panel_module_css_default.historyItem,
772
+ children: [
773
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
774
+ className: audio_panel_module_css_default.historyPrompt,
775
+ children: entry.prompt
776
+ }),
777
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
778
+ className: audio_panel_module_css_default.historyMeta,
779
+ children: [
780
+ entry.mode,
781
+ " · ",
782
+ entry.model,
783
+ entry.channel ? ` · ${entry.channel}` : ""
784
+ ]
785
+ }),
786
+ entry.audio.map((audio, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("audio", {
787
+ className: audio_panel_module_css_default.historyAudio,
788
+ controls: true,
789
+ preload: "none",
790
+ src: audio.url
791
+ }, index))
792
+ ]
793
+ }, entry.id)) })]
794
+ })
795
+ ]
796
+ })]
797
+ });
798
+ }
799
+ //#endregion
800
+ //#region \0dsh-css:/Users/shimingming/Projects_code/dsh-audiogen/src/client/panel.module.css.mjs
801
+ const css$2 = "[data-pane=conversation],[class*=centerCol]{position:relative}[data-dsh-audiogen-view]{z-index:60;background:var(--dsw-alias-bg-base);display:none;position:absolute;inset:0}html[data-dsh-audiogen-active]:not([data-dsh-taskboard-active]):not([data-dsh-ssh-active]) [data-dsh-audiogen-view]{display:block}html[data-dsh-audiogen-active]:not([data-dsh-taskboard-active]):not([data-dsh-ssh-active]) [data-pane=conversation]>:not([data-dsh-audiogen-view]),html[data-dsh-audiogen-active]:not([data-dsh-taskboard-active]):not([data-dsh-ssh-active]) [class*=centerCol]>:not([data-dsh-audiogen-view]){display:none!important}.rwa6qG_entry{width:100%;height:32px;color:var(--dsw-alias-label-secondary);cursor:pointer;white-space:nowrap;background:0 0;border:none;border-radius:8px;align-items:center;gap:8px;padding:0 12px;font-size:13px;display:flex}.rwa6qG_entry:hover{background:var(--dsw-specific-sidebar-nav-item-hover);color:var(--dsw-alias-label-primary)}.rwa6qG_entry[data-active]{background:var(--dsw-specific-sidebar-nav-item-active);color:var(--dsw-alias-label-primary);font-weight:600}.rwa6qG_entryIcon{flex:none;justify-content:center;align-items:center;display:inline-flex}.rwa6qG_entryLabel{text-overflow:ellipsis;overflow:hidden}[data-dsh-frame][data-sidebar-collapsed] .rwa6qG_entry{justify-content:center;width:100%;padding:0}[data-dsh-frame][data-sidebar-collapsed] .rwa6qG_entryLabel{display:none}.rwa6qG_view{overflow:hidden}.rwa6qG_panel,.rwa6qG_panel *,.rwa6qG_panel :before,.rwa6qG_panel :after{box-sizing:border-box}.rwa6qG_panel{background:var(--dsw-alias-bg-base);min-width:0;height:100%;min-height:0;color:var(--dsw-alias-label-primary);font-family:var(--dsw-font-family);flex-direction:column;gap:10px;padding:14px 16px 16px;display:flex;position:relative;overflow:hidden}.rwa6qG_panelHeader{flex:none;justify-content:space-between;align-items:center;gap:12px;display:flex}.rwa6qG_panelHeading{align-items:baseline;gap:10px;min-width:0;display:flex}.rwa6qG_panelTitle{color:var(--dsw-alias-label-primary);white-space:nowrap;margin:0;font-size:16px;font-weight:700}.rwa6qG_githubLink{width:22px;height:22px;color:var(--dsw-alias-label-secondary);border-radius:6px;flex:none;justify-content:center;align-items:center;text-decoration:none;transition:color .12s,background .12s;display:inline-flex}.rwa6qG_githubLink:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-2)}.rwa6qG_connectionStatus{border:1px solid var(--dsw-alias-label-error);height:28px;color:var(--dsw-alias-label-error);font:inherit;white-space:nowrap;background:0 0;border-radius:8px;flex:none;align-items:center;gap:6px;padding:0 10px;font-size:12px;line-height:1;display:inline-flex}.rwa6qG_connectionStatus[data-connected=true]{border-color:var(--dsw-alias-state-success-primary);color:var(--dsw-alias-state-success-primary)}.rwa6qG_connectionDot{background:currentColor;border-radius:50%;width:6px;height:6px}.rwa6qG_updateBanner{border:1px solid var(--dsw-alias-state-warn-primary);color:var(--dsw-alias-state-warn-primary);overflow-wrap:anywhere;border-radius:10px;flex:none;justify-content:space-between;align-items:center;gap:12px;padding:7px 10px 7px 12px;font-size:12px;line-height:1.5;display:flex}.rwa6qG_updateBanner[data-kind=ok]{color:var(--dsw-alias-state-success-primary);border-color:var(--dsw-alias-state-success-primary)}.rwa6qG_updateText{min-width:0}.rwa6qG_updateActions{flex:none;align-items:center;gap:10px;display:inline-flex}.rwa6qG_updateRelease{color:inherit;text-underline-offset:2px;white-space:nowrap;text-decoration:underline}@media (width<=700px){.rwa6qG_panelHeader{align-items:flex-start}.rwa6qG_panelHeading{flex-direction:column;align-items:flex-start;gap:2px}.rwa6qG_updateBanner{flex-direction:column;align-items:flex-start}.rwa6qG_updateActions{justify-content:space-between;width:100%}}.rwa6qG_studio{flex:1;gap:14px;min-width:0;min-height:0;display:flex}.rwa6qG_config{flex-direction:column;flex:none;gap:12px;width:300px;min-width:260px;max-width:340px;height:100%;min-height:0;display:flex;overflow:hidden}.rwa6qG_configScroll{scrollbar-width:thin;scrollbar-color:var(--dsw-alias-border-l2) transparent;flex-direction:column;flex:1;gap:12px;min-height:0;padding-right:2px;display:flex;overflow-y:auto}.rwa6qG_configScroll::-webkit-scrollbar{width:8px}.rwa6qG_configScroll::-webkit-scrollbar-thumb{background:var(--dsw-alias-border-l2);border-radius:999px}.rwa6qG_canvas{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-1);border-radius:12px;flex-direction:column;flex:1;min-width:0;min-height:0;display:flex;position:relative;overflow:hidden}.rwa6qG_taskTray{z-index:6;border:1px solid var(--dsw-alias-border-l2);background:color-mix(in srgb, var(--dsw-alias-bg-layer-1) 92%, transparent);backdrop-filter:blur(10px);border-radius:9px;flex-direction:column;width:min(360px,100% - 24px);max-height:calc(100% - 24px);display:flex;position:absolute;top:12px;right:12px;overflow:hidden;box-shadow:0 8px 24px #0000001f}.rwa6qG_taskTray[data-open=false]{width:auto;max-width:calc(100% - 24px)}.rwa6qG_taskTrayHeader{min-height:34px;color:var(--dsw-alias-label-primary);border-bottom:1px solid var(--dsw-alias-border-l1);align-items:stretch;font-size:12px;font-weight:600;display:flex}.rwa6qG_taskTray[data-open=false] .rwa6qG_taskTrayHeader{border-bottom:0}.rwa6qG_taskTrayToggle{min-width:0;color:inherit;cursor:pointer;font:inherit;font-size:inherit;font-weight:inherit;text-align:left;background:0 0;border:0;flex:1;align-items:center;gap:8px;padding:8px 10px;display:flex}.rwa6qG_taskTrayToggle:hover{background:var(--dsw-alias-bg-layer-2)}.rwa6qG_taskTrayCount{min-width:18px;color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-layer-2);text-align:center;border-radius:999px;padding:1px 5px;font-size:11px;font-weight:500}.rwa6qG_taskTrayChevron{color:var(--dsw-alias-label-tertiary);margin-left:auto;font-size:12px;font-weight:400}.rwa6qG_taskTrayClose{border:0;border-left:1px solid var(--dsw-alias-border-l1);width:32px;color:var(--dsw-alias-label-tertiary);cursor:pointer;font:inherit;background:0 0;font-size:17px}.rwa6qG_taskTrayClose:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-2)}.rwa6qG_taskTray[data-open=false] .rwa6qG_taskTrayToggle{min-height:34px}.rwa6qG_taskRows{min-height:0;overflow-y:auto}.rwa6qG_taskTray[data-open=false] .rwa6qG_taskRows{display:none}.rwa6qG_taskRow{border-top:1px solid var(--dsw-alias-border-l1);grid-template-columns:auto minmax(0,1fr) auto;align-items:center;gap:7px;padding:7px 10px;display:grid}.rwa6qG_taskRow:first-of-type{border-top:0}.rwa6qG_taskStatus{color:var(--dsw-alias-label-tertiary);white-space:nowrap;font-size:11px}.rwa6qG_taskRow[data-status=running] .rwa6qG_taskStatus{color:var(--dsw-alias-brand-primary)}.rwa6qG_taskRow[data-status=failed] .rwa6qG_taskStatus{color:var(--dsw-alias-label-error)}.rwa6qG_taskPrompt{color:var(--dsw-alias-label-secondary);text-overflow:ellipsis;white-space:nowrap;font-size:11px;overflow:hidden}.rwa6qG_taskRow button{color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-layer-2);cursor:pointer;font:inherit;border:0;border-radius:5px;padding:2px 7px;font-size:11px}.rwa6qG_taskRow button:hover{color:var(--dsw-alias-brand-primary)}.rwa6qG_configGuide{z-index:1100;background:#00000059;place-items:center;padding:20px;display:grid;position:fixed;inset:0}.rwa6qG_configGuideBody{border:1px solid var(--dsw-alias-border-l2);width:min(360px,100%);color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-1);border-radius:10px;flex-direction:column;gap:12px;padding:18px;display:flex;box-shadow:0 14px 40px #0003}.rwa6qG_configGuideBody span{color:var(--dsw-alias-label-secondary);font-size:13px;line-height:1.55}.rwa6qG_configGuideBody button{min-height:30px;color:var(--dsw-alias-bg-layer-1);background:var(--dsw-alias-brand-primary);cursor:pointer;font:inherit;border:0;border-radius:7px;align-self:flex-end;padding:0 12px;font-size:12px}.rwa6qG_history{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-1);border-radius:12px;flex-direction:column;flex:none;width:240px;min-width:200px;max-width:280px;min-height:0;display:flex;overflow:hidden}.rwa6qG_historyHeader{border-bottom:1px solid var(--dsw-alias-border-l1);flex:none;justify-content:space-between;align-items:center;gap:8px;padding:10px 12px;display:flex}.rwa6qG_historyFilters{border-bottom:1px solid var(--dsw-alias-border-l1);grid-template-columns:1fr 1fr;gap:6px;padding:8px 10px;display:grid}.rwa6qG_historySearch,.rwa6qG_historyFilters select,.rwa6qG_gallerySearch{box-sizing:border-box;border:1px solid var(--dsw-alias-border-l2);min-width:0;min-height:29px;color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-2);font:inherit;border-radius:7px;padding:0 8px;font-size:11px}.rwa6qG_historySearch{grid-column:1/-1}.rwa6qG_gallerySearch{width:156px}.rwa6qG_galleryTagInput{box-sizing:border-box;border:1px solid var(--dsw-alias-border-l2);width:130px;min-height:29px;color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-2);font:inherit;border-radius:7px;padding:0 8px;font-size:11px}.rwa6qG_galleryBulkButton{border:1px solid var(--dsw-alias-border-l2);min-height:29px;color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-layer-1);cursor:pointer;font:inherit;border-radius:7px;padding:0 8px;font-size:11px}.rwa6qG_galleryBulkButton:hover:not(:disabled){color:var(--dsw-alias-brand-primary);border-color:var(--dsw-alias-brand-primary)}.rwa6qG_galleryBulkButton:disabled{opacity:.45;cursor:default}.rwa6qG_historyTitle{color:var(--dsw-alias-label-primary);font-size:13px;font-weight:600}.rwa6qG_historyClear{font:inherit;color:var(--dsw-alias-label-tertiary);border:1px solid var(--dsw-alias-border-l2);cursor:pointer;background:0 0;border-radius:999px;padding:2px 8px;font-size:11.5px}.rwa6qG_historyClear:hover{color:var(--dsw-alias-label-error);border-color:var(--dsw-alias-label-error)}.rwa6qG_historyList{scrollbar-width:thin;scrollbar-color:var(--dsw-alias-border-l2) transparent;flex-direction:column;flex:1;gap:8px;min-height:0;padding:10px;display:flex;overflow-y:auto}.rwa6qG_historyList::-webkit-scrollbar{width:8px}.rwa6qG_historyList::-webkit-scrollbar-thumb{background:var(--dsw-alias-border-l2);border-radius:999px}.rwa6qG_historyEmpty{text-align:center;color:var(--dsw-alias-label-tertiary);flex:1;justify-content:center;align-items:center;padding:20px;font-size:12px;line-height:1.6;display:flex}.rwa6qG_historyItem{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-2);border-radius:10px;flex-direction:column;flex:none;gap:6px;padding:8px;display:flex}.rwa6qG_historyItem:hover{border-color:var(--dsw-alias-border-l2)}.rwa6qG_historyItem[data-active]{border-color:var(--dsw-alias-brand-primary)}.rwa6qG_historyMain{font:inherit;color:inherit;text-align:left;cursor:pointer;background:0 0;border:none;align-items:flex-start;gap:8px;min-width:0;padding:0;display:flex}.rwa6qG_historyThumb{object-fit:cover;background:var(--dsw-alias-bg-base);border-radius:8px;flex:none;width:52px;height:52px}.rwa6qG_historyThumbPlaceholder{background:var(--dsw-alias-bg-layer-3);border-radius:8px;flex:none;width:52px;height:52px}.rwa6qG_historyInfo{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}.rwa6qG_historyPrompt{color:var(--dsw-alias-label-primary);-webkit-line-clamp:2;-webkit-box-orient:vertical;font-size:12px;line-height:1.4;display:-webkit-box;overflow:hidden}.rwa6qG_historyMeta{color:var(--dsw-alias-label-tertiary);white-space:nowrap;text-overflow:ellipsis;font-size:11px;overflow:hidden}.rwa6qG_historyActions{justify-content:flex-end;gap:6px;display:flex}.rwa6qG_historyAction{font:inherit;color:var(--dsw-alias-label-secondary);border:1px solid var(--dsw-alias-border-l2);cursor:pointer;background:0 0;border-radius:999px;padding:2px 8px;font-size:11.5px}.rwa6qG_historyAction:hover{color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-dimmed)}.rwa6qG_historyAction[data-danger]:hover{color:var(--dsw-alias-label-error);border-color:var(--dsw-alias-label-error)}.rwa6qG_card{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);border-radius:12px;flex-direction:column;flex:none;gap:10px;padding:12px;display:flex}.rwa6qG_modeRow{align-items:center;gap:8px;display:flex}.rwa6qG_modePill{flex:1;justify-content:center;height:28px;font-size:13px}.rwa6qG_uploadBox{min-height:128px;color:var(--dsw-alias-label-secondary);border:1.5px dashed var(--dsw-alias-border-l2);cursor:pointer;font:inherit;text-align:center;background:0 0;border-radius:12px;flex-direction:column;justify-content:center;align-items:center;gap:6px;padding:16px;font-size:12.5px;display:flex}.rwa6qG_uploadBox:hover{color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-dimmed);background:var(--dsw-alias-interactive-bg-hover)}.rwa6qG_uploadBox:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}.rwa6qG_uploadIcon{color:var(--dsw-alias-label-tertiary);display:inline-flex}.rwa6qG_uploadHint{color:var(--dsw-alias-label-tertiary);font-size:11px}.rwa6qG_reference{flex-direction:column;gap:8px;display:flex}.rwa6qG_referenceImage{object-fit:contain;background:var(--dsw-alias-bg-base);border:1px solid var(--dsw-alias-border-l1);border-radius:10px;width:100%;max-height:176px}.rwa6qG_referenceActions{gap:8px;display:flex}.rwa6qG_hiddenFile{display:none}.rwa6qG_prompt{width:100%;min-height:120px;color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-3);border:1px solid var(--dsw-alias-border-l2);resize:vertical;box-sizing:border-box;border-radius:10px;outline:none;padding:10px 12px;font-family:inherit;font-size:13px;line-height:1.6}.rwa6qG_prompt:focus-visible{border-color:var(--dsw-alias-brand-primary)}.rwa6qG_prompt::placeholder{color:var(--dsw-alias-label-tertiary)}.rwa6qG_promptFooter{justify-content:space-between;align-items:center;gap:8px;margin-top:-6px;display:flex}.rwa6qG_templatesButton{border:1px solid var(--dsw-alias-brand-primary);background:linear-gradient(135deg, color-mix(in srgb, var(--dsw-alias-brand-primary) 14%, transparent), color-mix(in srgb, var(--dsw-alias-brand-primary) 5%, transparent));height:26px;color:var(--dsw-alias-brand-primary);cursor:pointer;box-shadow:0 1px 0 color-mix(in srgb, var(--dsw-alias-brand-primary) 22%, transparent);border-radius:999px;align-items:center;gap:6px;padding:0 12px;font-family:inherit;font-size:12px;font-weight:600;transition:transform .12s,box-shadow .12s,background .12s;display:inline-flex}.rwa6qG_templatesButton svg{flex:none}.rwa6qG_templatesButton:hover{background:linear-gradient(135deg, color-mix(in srgb, var(--dsw-alias-brand-primary) 24%, transparent), color-mix(in srgb, var(--dsw-alias-brand-primary) 8%, transparent));color:var(--dsw-alias-brand-primary);box-shadow:0 2px 6px color-mix(in srgb, var(--dsw-alias-brand-primary) 30%, transparent);transform:translateY(-1px)}.rwa6qG_templatesButton:active{transform:translateY(0)}.rwa6qG_enhanceButton{border:1px solid var(--dsw-alias-border-l2);height:26px;color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-layer-2);font:inherit;cursor:pointer;border-radius:999px;margin-left:auto;padding:0 11px;font-size:12px}.rwa6qG_enhanceButton:hover:not(:disabled){color:var(--dsw-alias-brand-primary);border-color:var(--dsw-alias-brand-primary)}.rwa6qG_enhanceButton:disabled{opacity:.5;cursor:default}.rwa6qG_promptCount{color:var(--dsw-alias-label-tertiary);font-variant-numeric:tabular-nums;font-size:11px}.rwa6qG_paramGroup{flex-direction:column;gap:8px;display:flex}.rwa6qG_paramLabel{color:var(--dsw-alias-label-secondary);font-size:12px;font-weight:600}.rwa6qG_optionRow{flex-wrap:wrap;gap:6px;display:flex}.rwa6qG_optionGrid{grid-template-columns:repeat(3,1fr);gap:6px;display:grid}.rwa6qG_optionPill{justify-content:center}.rwa6qG_paramHint{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:1.45}.rwa6qG_footer{border-top:1px solid var(--dsw-alias-border-l1);flex-direction:column;flex:none;align-items:stretch;gap:8px;padding:10px 2px 0 0;display:flex}.rwa6qG_modelWrap{flex-direction:column;gap:5px;min-width:0;display:flex}.rwa6qG_modelLabel{color:var(--dsw-alias-label-secondary);font-size:12px;font-weight:600}.rwa6qG_modelSelect{width:100%;height:36px;color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);cursor:pointer;text-align:left;border-radius:18px;outline:none;justify-content:space-between;align-items:center;gap:8px;padding:0 12px;font-family:inherit;font-size:13px;display:flex}.rwa6qG_modelSelect:focus-visible{border-color:var(--dsw-alias-brand-primary)}.rwa6qG_modelSelect:disabled{opacity:.55;cursor:default}.rwa6qG_modelMenu{min-width:0;display:block;position:relative}.rwa6qG_modelMenuList{z-index:40;background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);border-radius:12px;flex-direction:column;padding:4px;display:flex;position:absolute;bottom:calc(100% + 6px);left:0;right:0;overflow:hidden;box-shadow:0 -8px 24px #0000002e}.rwa6qG_modelMenuItem{width:100%;font:inherit;color:var(--dsw-alias-label-primary);cursor:pointer;text-align:left;white-space:nowrap;text-overflow:ellipsis;background:0 0;border:none;border-radius:8px;padding:7px 10px;font-size:13px;display:block;overflow:hidden}.rwa6qG_modelMenuItem:hover{background:var(--dsw-alias-bg-hover)}.rwa6qG_modelMenuItem[data-selected]{color:var(--dsw-alias-brand-primary);background:var(--dsw-alias-bg-layer-1);font-weight:600}.rwa6qG_generateButton{width:100%}.rwa6qG_generateInner{align-items:center;gap:7px;display:inline-flex}.rwa6qG_canvasState{text-align:center;color:var(--dsw-alias-label-tertiary);flex-direction:column;flex:1;justify-content:center;align-items:center;gap:8px;padding:24px;display:flex}.rwa6qG_canvasStateTitle{color:var(--dsw-alias-label-secondary);font-size:14px;font-weight:600}.rwa6qG_canvasStateHint{max-width:380px;font-size:12px;line-height:1.6}.rwa6qG_canvasEmptyIcon{color:var(--dsw-alias-label-tertiary);margin-bottom:4px;display:inline-flex}.rwa6qG_canvasError{color:var(--dsw-alias-label-error);background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-label-error);overflow-wrap:anywhere;border-radius:10px;flex:none;margin:14px;padding:10px 14px;font-size:12.5px;line-height:1.6}.rwa6qG_canvasBody{scrollbar-width:thin;scrollbar-color:var(--dsw-alias-border-l2) transparent;flex-direction:column;flex:1;gap:10px;min-height:0;padding:14px;display:flex;overflow-y:auto}.rwa6qG_canvasBody::-webkit-scrollbar{width:8px}.rwa6qG_canvasBody::-webkit-scrollbar-thumb{background:var(--dsw-alias-border-l2);border-radius:999px}.rwa6qG_canvasMeta{color:var(--dsw-alias-label-tertiary);flex:none;align-items:center;gap:8px;font-size:12px;display:flex}.rwa6qG_canvasHistoryTag{color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);white-space:nowrap;border-radius:999px;padding:1px 8px;font-size:11px}.rwa6qG_grid{flex:1;grid-template-rows:repeat(2,minmax(0,1fr));grid-template-columns:repeat(2,minmax(0,1fr));gap:14px;min-height:0;display:grid}.rwa6qG_grid[data-count=\"1\"] .rwa6qG_imageCard{grid-area:1/1/3/3}.rwa6qG_imageCard{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-2);cursor:zoom-in;border-radius:12px;flex-direction:column;min-height:0;margin:0;display:flex;position:relative;overflow:hidden}.rwa6qG_imageCard:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}.rwa6qG_image{object-fit:cover;background:var(--dsw-alias-bg-base);flex:1;width:100%;min-height:0;display:block}.rwa6qG_imageCaption{color:var(--dsw-alias-label-tertiary);white-space:nowrap;text-overflow:ellipsis;border-top:1px solid var(--dsw-alias-border-l1);padding:7px 10px;font-size:11px;line-height:1.5;overflow:hidden}.rwa6qG_download{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-mask-1);border:1px solid var(--dsw-alias-border-l2);opacity:0;backdrop-filter:blur(4px);border-radius:999px;padding:2px 10px;font-size:12px;font-weight:500;line-height:20px;text-decoration:none;transition:opacity .12s;position:absolute;top:8px;right:8px}.rwa6qG_imageCard:hover .rwa6qG_download{opacity:1}.rwa6qG_download:hover{background:var(--dsw-alias-bg-base)}.rwa6qG_galleryAdd{font:inherit;color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-mask-1);border:1px solid var(--dsw-alias-border-l2);cursor:pointer;opacity:0;backdrop-filter:blur(4px);border-radius:999px;align-items:center;gap:5px;padding:2px 10px;font-size:12px;font-weight:500;line-height:20px;transition:opacity .12s;display:inline-flex;position:absolute;top:8px;left:8px}.rwa6qG_imageCard:hover .rwa6qG_galleryAdd{opacity:1}.rwa6qG_galleryAdd:hover{background:var(--dsw-alias-bg-base)}.rwa6qG_galleryAdd:disabled{opacity:.4;cursor:default}.rwa6qG_zoomHint{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-mask-1);border:1px solid var(--dsw-alias-border-l2);opacity:0;backdrop-filter:blur(4px);pointer-events:none;border-radius:999px;align-items:center;gap:5px;padding:2px 10px;font-size:12px;font-weight:500;line-height:20px;transition:opacity .12s;display:inline-flex;position:absolute;bottom:8px;left:8px}.rwa6qG_imageCard:hover .rwa6qG_zoomHint{opacity:1}.rwa6qG_spinner,.rwa6qG_bigSpinner{border:2px solid;border-top-color:#0000;border-radius:50%;flex:none;animation:.8s linear infinite rwa6qG_dshImageGenSpin;display:inline-block}.rwa6qG_spinner{width:11px;height:11px}.rwa6qG_bigSpinner{width:30px;height:30px;color:var(--dsw-alias-state-business-primary);border-width:3px;margin-bottom:6px}.rwa6qG_lightbox{z-index:1000;backdrop-filter:blur(6px);background:#000000b8;justify-content:center;align-items:center;padding:24px;display:flex;position:fixed;inset:0}.rwa6qG_lightboxClose{color:#fff;cursor:pointer;background:#ffffff24;border:1px solid #ffffff47;border-radius:50%;justify-content:center;align-items:center;width:38px;height:38px;display:inline-flex;position:absolute;top:16px;right:16px}.rwa6qG_lightboxClose:hover{background:#ffffff42}.rwa6qG_lightboxNav{color:#fff;cursor:pointer;background:#ffffff24;border:1px solid #ffffff47;border-radius:50%;justify-content:center;align-items:center;width:42px;height:42px;display:inline-flex;position:absolute;top:50%;transform:translateY(-50%)}.rwa6qG_lightboxNav:hover{background:#ffffff42}.rwa6qG_lightboxNav[data-dir=prev]{left:max(20px,50% - 640px)}.rwa6qG_lightboxNav[data-dir=next]{right:max(20px,50% - 640px)}.rwa6qG_lightboxFigure{flex-direction:column;gap:10px;width:min(1100px,100vw - 160px);max-width:min(1100px,100vw - 160px);height:min(820px,100vh - 48px);min-height:0;margin:0;display:flex}.rwa6qG_lightboxStage{background:#ffffff0a;border-radius:10px;flex:1;min-height:0;position:relative;overflow:auto}.rwa6qG_lightboxScaleFrame{justify-content:center;align-items:center;min-width:100%;min-height:100%;display:flex}.rwa6qG_lightboxImage{object-fit:contain;border-radius:10px;max-width:100%;max-height:100%;display:block;box-shadow:0 24px 80px #00000080}.rwa6qG_lightboxTools{justify-content:center;align-items:center;gap:6px;display:flex}.rwa6qG_lightboxTool,.rwa6qG_lightboxZoomLevel,.rwa6qG_lightboxCopy{color:#fff;cursor:pointer;background:#ffffff24;border:1px solid #ffffff47;justify-content:center;align-items:center;display:inline-flex}.rwa6qG_lightboxTool,.rwa6qG_lightboxZoomLevel{height:32px}.rwa6qG_lightboxTool{border-radius:50%;width:32px}.rwa6qG_lightboxZoomLevel{min-width:58px;font:inherit;font-variant-numeric:tabular-nums;border-radius:999px;padding:0 9px;font-size:12px}.rwa6qG_lightboxTool:hover,.rwa6qG_lightboxZoomLevel:hover,.rwa6qG_lightboxCopy:hover{background:#ffffff42}.rwa6qG_lightboxCaptionRow{align-items:flex-start;gap:8px;min-width:0;display:flex}.rwa6qG_lightboxCaption{color:#ffffffe6;-webkit-line-clamp:3;-webkit-box-orient:vertical;flex:1;min-width:0;font-size:12px;line-height:1.6;display:-webkit-box;overflow:hidden}.rwa6qG_lightboxCopy{min-height:28px;font:inherit;white-space:nowrap;border-radius:999px;flex:none;gap:5px;padding:4px 9px;font-size:12px}.rwa6qG_lightboxMeta{justify-content:space-between;align-items:center;gap:12px;display:flex}.rwa6qG_lightboxIndex{color:#fffc;font-variant-numeric:tabular-nums;font-size:12px}.rwa6qG_lightboxActions{align-items:center;gap:8px;display:inline-flex}.rwa6qG_lightboxDownload,.rwa6qG_lightboxEdit{font:inherit;color:#fff;cursor:pointer;background:#ffffff24;border:1px solid #ffffff47;border-radius:999px;padding:4px 14px;font-size:12.5px;font-weight:500;text-decoration:none}.rwa6qG_lightboxDownload:hover,.rwa6qG_lightboxEdit:hover{background:#ffffff42}.rwa6qG_lightboxEdit{color:#fff;background:#ffffff24;border:1px solid #ffffff47;border-radius:999px}@media (width<=720px){.rwa6qG_lightbox{padding:16px}.rwa6qG_lightboxFigure{width:calc(100vw - 32px);max-width:none}.rwa6qG_lightboxNav[data-dir=prev]{left:20px}.rwa6qG_lightboxNav[data-dir=next]{right:20px}.rwa6qG_lightboxCaptionRow,.rwa6qG_lightboxMeta{flex-direction:column;align-items:stretch}.rwa6qG_lightboxCopy,.rwa6qG_lightboxActions{align-self:flex-end}}@keyframes rwa6qG_dshImageGenSpin{to{transform:rotate(360deg)}}.rwa6qG_galleryToast{z-index:30;color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-mask-1);border:1px solid var(--dsw-alias-border-l2);backdrop-filter:blur(6px);pointer-events:none;border-radius:999px;align-items:center;gap:7px;padding:6px 16px;font-size:13px;font-weight:500;animation:.16s ease-out rwa6qG_dshImageGenToastIn;display:inline-flex;position:absolute;bottom:24px;left:50%;transform:translate(-50%);box-shadow:0 8px 24px #00000038}@keyframes rwa6qG_dshImageGenToastIn{0%{opacity:0;transform:translate(-50%,6px)}to{opacity:1;transform:translate(-50%)}}@media (prefers-reduced-motion:reduce){.rwa6qG_download,.rwa6qG_spinner,.rwa6qG_bigSpinner{transition:none;animation-duration:1.5s}}.rwa6qG_config[data-gallery=true] .rwa6qG_configScroll>:not(:first-child),.rwa6qG_config[data-gallery=true] .rwa6qG_footer,.rwa6qG_canvas[data-gallery=true]>.rwa6qG_canvasState,.rwa6qG_canvas[data-gallery=true]>.rwa6qG_canvasError,.rwa6qG_canvas[data-gallery=true]>.rwa6qG_canvasBody,.rwa6qG_studio:has(.rwa6qG_config[data-gallery=true])>.rwa6qG_history{display:none}.rwa6qG_config[data-gallery=true] .rwa6qG_configScroll{flex:none;order:1;display:flex;overflow:visible}.rwa6qG_config[data-gallery=true] .rwa6qG_galleryFilters{flex:1;order:2;min-height:0}.rwa6qG_galleryFilters{padding:18px 14px;overflow:hidden auto}.rwa6qG_galleryFilterHeading{color:var(--dsw-alias-label-tertiary);margin:0 4px 10px;font-size:12px;font-weight:600}.rwa6qG_galleryFilter{width:100%;min-height:34px;color:var(--dsw-alias-label-secondary);cursor:pointer;text-align:left;background:0 0;border:0;border-radius:9px;justify-content:space-between;align-items:center;padding:0 10px;display:flex}.rwa6qG_galleryFilter:hover,.rwa6qG_galleryFilter[data-active]{color:var(--dsw-alias-brand-primary);background:color-mix(in srgb, var(--dsw-alias-brand-primary) 10%, transparent)}.rwa6qG_galleryFilterCount{min-width:20px;color:var(--dsw-alias-label-tertiary);background:var(--dsw-alias-bg-layer-2);text-align:center;border-radius:999px;padding:1px 6px;font-size:11px}.rwa6qG_galleryFilterDivider{background:var(--dsw-alias-border-l1);height:1px;margin:18px 4px}.rwa6qG_galleryRatioList{flex-wrap:wrap;gap:6px;display:flex}.rwa6qG_galleryRatio{color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-layer-2);cursor:pointer;border:0;border-radius:999px;padding:6px 10px;font-size:12px}.rwa6qG_galleryRatio[data-active]{color:var(--dsw-alias-brand-primary);background:color-mix(in srgb, var(--dsw-alias-brand-primary) 13%, transparent)}.rwa6qG_galleryTagFilterList{flex-wrap:wrap;gap:6px;display:flex}.rwa6qG_galleryTagFilter{border:1px solid var(--dsw-alias-border-l1);min-width:0;max-width:100%;min-height:27px;color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-layer-2);cursor:pointer;font:inherit;border-radius:6px;align-items:center;gap:5px;padding:0 8px;font-size:11px;display:inline-flex}.rwa6qG_galleryTagFilter span:first-child{text-overflow:ellipsis;white-space:nowrap;max-width:112px;overflow:hidden}.rwa6qG_galleryTagFilter span:last-child{color:var(--dsw-alias-label-tertiary);font-size:10px}.rwa6qG_galleryTagFilter:hover,.rwa6qG_galleryTagFilter[data-active]{color:var(--dsw-alias-brand-primary);background:color-mix(in srgb, var(--dsw-alias-brand-primary) 10%, transparent)}.rwa6qG_galleryFilterNote{color:var(--dsw-alias-label-quaternary);margin:20px 4px 0;font-size:11px;line-height:1.5}.rwa6qG_galleryWorkspace{box-sizing:border-box;flex-direction:column;width:100%;min-width:0;height:100%;min-height:0;padding:22px 24px 26px;display:flex;position:absolute;inset:0;overflow:hidden}.rwa6qG_galleryToolbar{flex:none;justify-content:space-between;align-items:center;gap:16px;min-width:0;margin-bottom:18px;display:flex}.rwa6qG_galleryHeading{color:var(--dsw-alias-label-primary);margin:0;font-size:20px;font-weight:700;display:inline}.rwa6qG_galleryCount{color:var(--dsw-alias-label-tertiary);margin-left:8px;font-size:13px}.rwa6qG_galleryToolbarActions{flex-wrap:wrap;align-items:center;gap:10px;min-width:0;display:flex}.rwa6qG_gallerySelectMode,.rwa6qG_galleryBulkButton,.rwa6qG_gallerySelectionClear{border:1px solid var(--dsw-alias-border-l1);min-height:30px;color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-layer-1);cursor:pointer;font:inherit;border-radius:7px;padding:0 10px;font-size:12px}.rwa6qG_gallerySelectMode:hover,.rwa6qG_gallerySelectMode[data-active],.rwa6qG_galleryBulkButton:hover:not(:disabled){color:var(--dsw-alias-brand-primary);border-color:color-mix(in srgb, var(--dsw-alias-brand-primary) 38%, var(--dsw-alias-border-l1));background:color-mix(in srgb, var(--dsw-alias-brand-primary) 11%, transparent)}.rwa6qG_galleryBulkButton:disabled{cursor:not-allowed;opacity:.45}.rwa6qG_gallerySelectionBar{border:1px solid color-mix(in srgb, var(--dsw-alias-brand-primary) 35%, var(--dsw-alias-border-l1));background:color-mix(in srgb, var(--dsw-alias-brand-primary) 7%, var(--dsw-alias-bg-layer-1));border-radius:9px;flex:none;align-items:center;gap:10px;min-width:0;margin:-4px 0 16px;padding:10px 12px;display:flex}.rwa6qG_gallerySelectionBar strong{color:var(--dsw-alias-brand-primary);flex:none;font-size:12px}.rwa6qG_gallerySelectionClear{margin-left:auto}.rwa6qG_gallerySelectionClear:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-2)}.rwa6qG_galleryTagInput,.rwa6qG_gallerySearch{border:1px solid var(--dsw-alias-border-l1);min-width:0;height:30px;color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-1);font:inherit;border-radius:7px;outline:none;padding:0 10px;font-size:12px}.rwa6qG_galleryTagInput{flex:190px}.rwa6qG_galleryTagInput:focus,.rwa6qG_gallerySearch:focus{border-color:var(--dsw-alias-brand-primary)}.rwa6qG_galleryViewToggle{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-1);border-radius:9px;padding:3px;display:flex}.rwa6qG_galleryViewToggle button,.rwa6qG_gallerySort,.rwa6qG_galleryClear{min-height:30px;color:var(--dsw-alias-label-secondary);cursor:pointer;font:inherit;background:0 0;border:0;border-radius:7px;padding:0 10px;font-size:12px}.rwa6qG_galleryViewToggle button[data-active],.rwa6qG_galleryViewToggle button:hover,.rwa6qG_gallerySort:hover,.rwa6qG_galleryClear:hover{color:var(--dsw-alias-brand-primary);background:color-mix(in srgb, var(--dsw-alias-brand-primary) 11%, transparent)}.rwa6qG_gallerySort{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-1)}.rwa6qG_galleryClear{border:1px solid var(--dsw-alias-border-l1)}.rwa6qG_compareControl{flex-direction:column;gap:6px;margin:0 0 10px;display:flex}.rwa6qG_compareToggle,.rwa6qG_compareModelChoices label{color:var(--dsw-alias-label-secondary);cursor:pointer;align-items:center;gap:6px;font-size:12px;display:flex}.rwa6qG_compareToggle input,.rwa6qG_compareModelChoices input{accent-color:var(--dsw-alias-brand-primary)}.rwa6qG_compareModelChoices{flex-wrap:wrap;gap:6px;display:flex}.rwa6qG_compareModelChoices label{border:1px solid var(--dsw-alias-border-l1);border-radius:5px;padding:4px 6px;font-size:10px}.rwa6qG_comparisonBoard{z-index:4;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);border-radius:8px;flex-direction:column;display:flex;position:absolute;inset:14px;overflow:hidden;box-shadow:0 8px 24px #0000001f}.rwa6qG_comparisonBoard>header{border-bottom:1px solid var(--dsw-alias-border-l1);justify-content:space-between;align-items:center;padding:10px 12px;display:flex}.rwa6qG_comparisonBoard>header div{align-items:baseline;gap:7px;display:flex}.rwa6qG_comparisonBoard>header strong{color:var(--dsw-alias-label-primary);font-size:13px}.rwa6qG_comparisonBoard>header span{color:var(--dsw-alias-label-tertiary);font-size:11px}.rwa6qG_comparisonBoard>header button{border:1px solid var(--dsw-alias-border-l1);min-height:28px;color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-layer-2);cursor:pointer;font:inherit;border-radius:5px;padding:0 9px;font-size:11px}.rwa6qG_comparisonGrid{flex:1;grid-template-rows:minmax(0,1fr);grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:12px;min-height:0;padding:12px;display:grid;overflow:auto}.rwa6qG_comparisonGrid article{flex-direction:column;gap:7px;min-width:0;height:100%;min-height:0;display:flex}.rwa6qG_comparisonGrid article>strong{color:var(--dsw-alias-label-primary);font-size:12px}.rwa6qG_comparisonImageButton{cursor:zoom-in;background:var(--dsw-alias-bg-base);border:0;flex:1;width:100%;min-height:0;padding:0;display:flex;overflow:hidden}.rwa6qG_comparisonImageButton:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-2px}.rwa6qG_comparisonGrid article>span{min-height:0;color:var(--dsw-alias-label-tertiary);background:var(--dsw-alias-bg-layer-2);flex:1;place-items:center;font-size:12px;display:grid}.rwa6qG_comparisonGrid img{object-fit:contain;background:var(--dsw-alias-bg-base);flex:1;width:100%;height:100%;min-height:0;max-height:none;display:block}.rwa6qG_comparisonFullscreen{z-index:1200;background:#000000eb;padding:54px 24px 24px;position:fixed;inset:0;overflow:auto}.rwa6qG_comparisonFullscreenGrid{grid-template-columns:repeat(auto-fit,minmax(300px,1fr));align-items:start;gap:18px;min-height:100%;display:grid}.rwa6qG_comparisonFullscreen figure{min-width:0;margin:0}.rwa6qG_comparisonFullscreen figcaption{color:#fff;margin-bottom:8px;font-size:13px;font-weight:600}.rwa6qG_comparisonFullscreen img{background:#111;width:100%;margin-bottom:10px;display:block}.rwa6qG_historyItem[data-comparison]{border-color:color-mix(in srgb, var(--dsw-alias-brand-primary) 55%, var(--dsw-alias-border-l1))}.rwa6qG_galleryMasonry{box-sizing:border-box;overscroll-behavior:contain;scrollbar-width:thin;scrollbar-color:var(--dsw-alias-border-l2) transparent;flex:auto;grid-template-columns:repeat(3,minmax(0,1fr));grid-auto-rows:max-content;align-content:start;gap:16px;width:100%;min-width:0;max-width:100%;height:0;min-height:0;padding:2px 8px 16px 2px;display:grid;overflow:hidden scroll}.rwa6qG_galleryMasonry::-webkit-scrollbar{width:8px}.rwa6qG_galleryMasonry::-webkit-scrollbar-thumb{background:var(--dsw-alias-border-l2);border-radius:999px}.rwa6qG_galleryCard{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-1);border-radius:14px;width:100%;margin:0;display:block;position:relative;overflow:hidden;box-shadow:0 5px 18px #18203612}.rwa6qG_galleryCard[data-selected]{border-color:var(--dsw-alias-brand-primary);box-shadow:0 0 0 2px color-mix(in srgb, var(--dsw-alias-brand-primary) 26%, transparent), 0 5px 18px #18203612}.rwa6qG_gallerySelect{z-index:2;cursor:pointer;background:#00000094;border:1px solid #ffffffbf;border-radius:7px;place-items:center;width:26px;height:26px;display:grid;position:absolute;top:9px;right:9px}.rwa6qG_gallerySelect input{width:16px;height:16px;accent-color:var(--dsw-alias-brand-primary);cursor:pointer;margin:0}.rwa6qG_galleryImageButton{background:var(--dsw-alias-bg-base);cursor:zoom-in;border:0;width:100%;padding:0;display:block;position:relative}.rwa6qG_galleryImageButton[data-selecting]{cursor:pointer}.rwa6qG_galleryImage{aspect-ratio:4/3;object-fit:cover;width:100%;display:block}.rwa6qG_galleryMasonry[data-view=masonry] .rwa6qG_galleryCard:nth-child(3n+1) .rwa6qG_galleryImage{aspect-ratio:4/5}.rwa6qG_galleryMasonry[data-view=masonry] .rwa6qG_galleryCard:nth-child(3n+2) .rwa6qG_galleryImage{aspect-ratio:4/3}.rwa6qG_galleryMasonry[data-view=masonry] .rwa6qG_galleryCard:nth-child(3n) .rwa6qG_galleryImage{aspect-ratio:3/4}.rwa6qG_galleryBadge{color:#fff;backdrop-filter:blur(4px);background:#121724c2;border-radius:999px;padding:4px 9px;font-size:11px;position:absolute;top:10px;left:10px}.rwa6qG_galleryCardFooter{align-items:center;gap:9px;min-width:0;padding:10px 12px;display:flex}.rwa6qG_galleryAvatar{color:#fff;background:var(--dsw-alias-brand-primary);border-radius:50%;flex:none;place-items:center;width:27px;height:27px;font-size:12px;font-weight:700;display:grid}.rwa6qG_galleryCardInfo{flex-direction:column;flex:1;gap:2px;min-width:0;display:flex}.rwa6qG_galleryCardInfo strong,.rwa6qG_galleryCardInfo small{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.rwa6qG_galleryCardInfo strong{color:var(--dsw-alias-label-primary);font-size:12px}.rwa6qG_galleryCardInfo small{color:var(--dsw-alias-label-tertiary);font-size:10px}.rwa6qG_galleryTags{flex-wrap:wrap;gap:4px;margin-top:4px;display:flex}.rwa6qG_galleryTags button{max-width:96px;min-height:19px;color:var(--dsw-alias-brand-primary);background:color-mix(in srgb, var(--dsw-alias-brand-primary) 10%, transparent);cursor:pointer;font:inherit;text-overflow:ellipsis;white-space:nowrap;border:0;border-radius:4px;padding:1px 6px;font-size:10px;line-height:1.35;overflow:hidden}.rwa6qG_galleryTags button:hover{background:color-mix(in srgb, var(--dsw-alias-brand-primary) 17%, transparent)}.rwa6qG_galleryTags .rwa6qG_galleryTagEdit{color:var(--dsw-alias-label-tertiary);background:0 0;flex:none}.rwa6qG_galleryTagEditor{align-items:center;gap:6px;padding:0 12px 10px 48px;display:flex}.rwa6qG_galleryTagEditor input{border:1px solid var(--dsw-alias-border-l2);min-width:0;height:27px;color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-2);font:inherit;border-radius:6px;outline:none;flex:1;padding:0 8px;font-size:11px}.rwa6qG_galleryTagEditor input:focus{border-color:var(--dsw-alias-brand-primary)}.rwa6qG_galleryTagEditor button{border:1px solid var(--dsw-alias-border-l2);height:27px;color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-layer-1);cursor:pointer;font:inherit;border-radius:6px;padding:0 8px;font-size:11px}.rwa6qG_galleryTagEditor button[type=submit]{color:var(--dsw-alias-brand-primary)}.rwa6qG_galleryRemove{width:24px;height:24px;color:var(--dsw-alias-label-tertiary);cursor:pointer;background:0 0;border:0;border-radius:50%;flex:none;padding:0;font-size:18px}.rwa6qG_galleryRemove:hover{color:var(--dsw-alias-state-error);background:var(--dsw-alias-bg-layer-2)}@media (width<=1100px){.rwa6qG_galleryMasonry{grid-template-columns:repeat(2,minmax(0,1fr))}}@media (width<=760px){.rwa6qG_studio{flex-direction:column;overflow:auto}.rwa6qG_config{width:auto;max-width:none;height:auto;min-height:0}.rwa6qG_config[data-gallery=true]{flex:none}.rwa6qG_canvas{min-height:560px}.rwa6qG_galleryToolbar{flex-direction:column;align-items:flex-start}.rwa6qG_galleryToolbarActions{flex-wrap:wrap;width:100%}.rwa6qG_galleryMasonry{grid-template-columns:1fr}}";
802
+ const tagId$2 = "dsh-audiogen/panel.module.css";
803
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$2) + "]") === null) {
804
+ const tag = document.createElement("style");
805
+ tag.dataset.plugin = "dsh-audiogen";
806
+ tag.dataset.pluginCss = tagId$2;
807
+ tag.textContent = css$2;
808
+ document.head.appendChild(tag);
809
+ }
810
+ var panel_module_css_default = {
811
+ "galleryBadge": "rwa6qG_galleryBadge",
812
+ "taskTrayChevron": "rwa6qG_taskTrayChevron",
813
+ "referenceImage": "rwa6qG_referenceImage",
814
+ "galleryClear": "rwa6qG_galleryClear",
815
+ "canvasEmptyIcon": "rwa6qG_canvasEmptyIcon",
816
+ "lightboxScaleFrame": "rwa6qG_lightboxScaleFrame",
817
+ "lightboxNav": "rwa6qG_lightboxNav",
818
+ "reference": "rwa6qG_reference",
819
+ "modelLabel": "rwa6qG_modelLabel",
820
+ "optionRow": "rwa6qG_optionRow",
821
+ "templatesButton": "rwa6qG_templatesButton",
822
+ "view": "rwa6qG_view",
823
+ "gallerySelectMode": "rwa6qG_gallerySelectMode",
824
+ "configScroll": "rwa6qG_configScroll",
825
+ "modePill": "rwa6qG_modePill",
826
+ "galleryTagFilter": "rwa6qG_galleryTagFilter",
827
+ "galleryRemove": "rwa6qG_galleryRemove",
828
+ "gallerySearch": "rwa6qG_gallerySearch",
829
+ "connectionStatus": "rwa6qG_connectionStatus",
830
+ "bigSpinner": "rwa6qG_bigSpinner",
831
+ "referenceActions": "rwa6qG_referenceActions",
832
+ "lightboxImage": "rwa6qG_lightboxImage",
833
+ "historyActions": "rwa6qG_historyActions",
834
+ "galleryViewToggle": "rwa6qG_galleryViewToggle",
835
+ "connectionDot": "rwa6qG_connectionDot",
836
+ "githubLink": "rwa6qG_githubLink",
837
+ "updateActions": "rwa6qG_updateActions",
838
+ "panel": "rwa6qG_panel",
839
+ "grid": "rwa6qG_grid",
840
+ "galleryMasonry": "rwa6qG_galleryMasonry",
841
+ "updateBanner": "rwa6qG_updateBanner",
842
+ "entry": "rwa6qG_entry",
843
+ "taskTray": "rwa6qG_taskTray",
844
+ "galleryBulkButton": "rwa6qG_galleryBulkButton",
845
+ "canvasHistoryTag": "rwa6qG_canvasHistoryTag",
846
+ "promptCount": "rwa6qG_promptCount",
847
+ "lightboxFigure": "rwa6qG_lightboxFigure",
848
+ "taskStatus": "rwa6qG_taskStatus",
849
+ "lightboxCaption": "rwa6qG_lightboxCaption",
850
+ "canvasMeta": "rwa6qG_canvasMeta",
851
+ "compareModelChoices": "rwa6qG_compareModelChoices",
852
+ "comparisonFullscreenGrid": "rwa6qG_comparisonFullscreenGrid",
853
+ "historyPrompt": "rwa6qG_historyPrompt",
854
+ "panelTitle": "rwa6qG_panelTitle",
855
+ "historyMain": "rwa6qG_historyMain",
856
+ "studio": "rwa6qG_studio",
857
+ "panelHeading": "rwa6qG_panelHeading",
858
+ "prompt": "rwa6qG_prompt",
859
+ "dshImageGenSpin": "rwa6qG_dshImageGenSpin",
860
+ "taskTrayClose": "rwa6qG_taskTrayClose",
861
+ "lightboxActions": "rwa6qG_lightboxActions",
862
+ "gallerySelectionClear": "rwa6qG_gallerySelectionClear",
863
+ "gallerySelect": "rwa6qG_gallerySelect",
864
+ "galleryCardInfo": "rwa6qG_galleryCardInfo",
865
+ "galleryCount": "rwa6qG_galleryCount",
866
+ "galleryTagInput": "rwa6qG_galleryTagInput",
867
+ "config": "rwa6qG_config",
868
+ "galleryFilterCount": "rwa6qG_galleryFilterCount",
869
+ "galleryRatioList": "rwa6qG_galleryRatioList",
870
+ "configGuide": "rwa6qG_configGuide",
871
+ "historyFilters": "rwa6qG_historyFilters",
872
+ "galleryTags": "rwa6qG_galleryTags",
873
+ "modelMenuItem": "rwa6qG_modelMenuItem",
874
+ "uploadHint": "rwa6qG_uploadHint",
875
+ "gallerySort": "rwa6qG_gallerySort",
876
+ "galleryImage": "rwa6qG_galleryImage",
877
+ "taskRows": "rwa6qG_taskRows",
878
+ "taskTrayHeader": "rwa6qG_taskTrayHeader",
879
+ "paramHint": "rwa6qG_paramHint",
880
+ "imageCard": "rwa6qG_imageCard",
881
+ "download": "rwa6qG_download",
882
+ "historyHeader": "rwa6qG_historyHeader",
883
+ "taskPrompt": "rwa6qG_taskPrompt",
884
+ "canvasError": "rwa6qG_canvasError",
885
+ "modeRow": "rwa6qG_modeRow",
886
+ "paramGroup": "rwa6qG_paramGroup",
887
+ "galleryFilter": "rwa6qG_galleryFilter",
888
+ "galleryTagFilterList": "rwa6qG_galleryTagFilterList",
889
+ "lightboxEdit": "rwa6qG_lightboxEdit",
890
+ "history": "rwa6qG_history",
891
+ "historyClear": "rwa6qG_historyClear",
892
+ "generateInner": "rwa6qG_generateInner",
893
+ "lightboxMeta": "rwa6qG_lightboxMeta",
894
+ "galleryFilterHeading": "rwa6qG_galleryFilterHeading",
895
+ "entryLabel": "rwa6qG_entryLabel",
896
+ "compareToggle": "rwa6qG_compareToggle",
897
+ "uploadIcon": "rwa6qG_uploadIcon",
898
+ "galleryAvatar": "rwa6qG_galleryAvatar",
899
+ "galleryImageButton": "rwa6qG_galleryImageButton",
900
+ "modelMenuList": "rwa6qG_modelMenuList",
901
+ "historyThumb": "rwa6qG_historyThumb",
902
+ "lightboxZoomLevel": "rwa6qG_lightboxZoomLevel",
903
+ "canvas": "rwa6qG_canvas",
904
+ "taskRow": "rwa6qG_taskRow",
905
+ "canvasState": "rwa6qG_canvasState",
906
+ "canvasStateTitle": "rwa6qG_canvasStateTitle",
907
+ "canvasStateHint": "rwa6qG_canvasStateHint",
908
+ "galleryToast": "rwa6qG_galleryToast",
909
+ "galleryRatio": "rwa6qG_galleryRatio",
910
+ "historyInfo": "rwa6qG_historyInfo",
911
+ "card": "rwa6qG_card",
912
+ "enhanceButton": "rwa6qG_enhanceButton",
913
+ "updateText": "rwa6qG_updateText",
914
+ "historyThumbPlaceholder": "rwa6qG_historyThumbPlaceholder",
915
+ "galleryHeading": "rwa6qG_galleryHeading",
916
+ "galleryTagEditor": "rwa6qG_galleryTagEditor",
917
+ "modelSelect": "rwa6qG_modelSelect",
918
+ "promptFooter": "rwa6qG_promptFooter",
919
+ "zoomHint": "rwa6qG_zoomHint",
920
+ "lightboxClose": "rwa6qG_lightboxClose",
921
+ "historyMeta": "rwa6qG_historyMeta",
922
+ "galleryWorkspace": "rwa6qG_galleryWorkspace",
923
+ "footer": "rwa6qG_footer",
924
+ "updateRelease": "rwa6qG_updateRelease",
925
+ "optionPill": "rwa6qG_optionPill",
926
+ "historyList": "rwa6qG_historyList",
927
+ "generateButton": "rwa6qG_generateButton",
928
+ "galleryFilterDivider": "rwa6qG_galleryFilterDivider",
929
+ "galleryFilters": "rwa6qG_galleryFilters",
930
+ "historyEmpty": "rwa6qG_historyEmpty",
931
+ "canvasBody": "rwa6qG_canvasBody",
932
+ "galleryToolbar": "rwa6qG_galleryToolbar",
933
+ "historyTitle": "rwa6qG_historyTitle",
934
+ "image": "rwa6qG_image",
935
+ "lightboxTool": "rwa6qG_lightboxTool",
936
+ "gallerySelectionBar": "rwa6qG_gallerySelectionBar",
937
+ "galleryCard": "rwa6qG_galleryCard",
938
+ "entryIcon": "rwa6qG_entryIcon",
939
+ "lightbox": "rwa6qG_lightbox",
940
+ "optionGrid": "rwa6qG_optionGrid",
941
+ "lightboxTools": "rwa6qG_lightboxTools",
942
+ "lightboxIndex": "rwa6qG_lightboxIndex",
943
+ "panelHeader": "rwa6qG_panelHeader",
944
+ "spinner": "rwa6qG_spinner",
945
+ "lightboxStage": "rwa6qG_lightboxStage",
946
+ "galleryFilterNote": "rwa6qG_galleryFilterNote",
947
+ "dshImageGenToastIn": "rwa6qG_dshImageGenToastIn",
948
+ "comparisonGrid": "rwa6qG_comparisonGrid",
949
+ "historyItem": "rwa6qG_historyItem",
950
+ "lightboxCaptionRow": "rwa6qG_lightboxCaptionRow",
951
+ "modelMenu": "rwa6qG_modelMenu",
952
+ "paramLabel": "rwa6qG_paramLabel",
953
+ "taskTrayToggle": "rwa6qG_taskTrayToggle",
954
+ "uploadBox": "rwa6qG_uploadBox",
955
+ "configGuideBody": "rwa6qG_configGuideBody",
956
+ "hiddenFile": "rwa6qG_hiddenFile",
957
+ "lightboxCopy": "rwa6qG_lightboxCopy",
958
+ "galleryAdd": "rwa6qG_galleryAdd",
959
+ "imageCaption": "rwa6qG_imageCaption",
960
+ "historySearch": "rwa6qG_historySearch",
961
+ "lightboxDownload": "rwa6qG_lightboxDownload",
962
+ "galleryToolbarActions": "rwa6qG_galleryToolbarActions",
963
+ "taskTrayCount": "rwa6qG_taskTrayCount",
964
+ "comparisonFullscreen": "rwa6qG_comparisonFullscreen",
965
+ "galleryTagEdit": "rwa6qG_galleryTagEdit",
966
+ "comparisonImageButton": "rwa6qG_comparisonImageButton",
967
+ "compareControl": "rwa6qG_compareControl",
968
+ "historyAction": "rwa6qG_historyAction",
969
+ "comparisonBoard": "rwa6qG_comparisonBoard",
970
+ "modelWrap": "rwa6qG_modelWrap",
971
+ "galleryCardFooter": "rwa6qG_galleryCardFooter"
972
+ };
973
+ //#endregion
974
+ //#region src/client/mount.tsx
975
+ /**
976
+ * Panel view mounting for the AI 音频 panel.
977
+ *
978
+ * Like dsh-imagegen, the panel takes over the center column at the DOM level:
979
+ * a container is appended inside the conversation grid item and a data
980
+ * attribute on <html> hides/shows it.
981
+ */
982
+ const CONVERSATION_COLUMN_SELECTOR = "[data-pane=\"conversation\"], [class*=\"centerCol\"]";
983
+ const ACTIVE_ATTR = "data-dsh-audiogen-active";
984
+ const OTHER_ACTIVE_ATTRS = [
985
+ "data-dsh-taskboard-active",
986
+ "data-dsh-ssh-active",
987
+ "data-dsh-imagegen-active"
988
+ ];
989
+ const ACTIVATE_EVENT = "dsh-panel-activate";
990
+ const PANEL_NAME = "audiogen";
991
+ function conversationColumn() {
992
+ return document.querySelector(CONVERSATION_COLUMN_SELECTOR) ?? void 0;
993
+ }
994
+ function mountPanel(controller, api, scope) {
995
+ let root;
996
+ let container;
997
+ const ensure = () => {
998
+ if (container !== void 0) {
999
+ if (container.isConnected) return;
1000
+ root?.unmount();
1001
+ root = void 0;
1002
+ container.remove();
1003
+ container = void 0;
1004
+ }
1005
+ const column = conversationColumn();
1006
+ if (column === void 0) return;
1007
+ container = document.createElement("div");
1008
+ container.dataset.dshAudiogenView = "";
1009
+ container.className = panel_module_css_default.view;
1010
+ column.appendChild(container);
1011
+ root = (0, react_dom_client.createRoot)(container);
1012
+ root.render(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(AudioGenPanel, {
1013
+ api,
1014
+ scope
1015
+ }));
1016
+ };
1017
+ const waitObserver = new MutationObserver(() => {
1018
+ ensure();
1019
+ });
1020
+ waitObserver.observe(document.body, {
1021
+ childList: true,
1022
+ subtree: true
1023
+ });
1024
+ const applyActive = () => {
1025
+ if (controller.getSnapshot().panelOpen) {
1026
+ for (const attr of OTHER_ACTIVE_ATTRS) document.documentElement.removeAttribute(attr);
1027
+ document.documentElement.setAttribute(ACTIVE_ATTR, "");
1028
+ document.dispatchEvent(new CustomEvent(ACTIVATE_EVENT, { detail: PANEL_NAME }));
1029
+ } else document.documentElement.removeAttribute(ACTIVE_ATTR);
1030
+ };
1031
+ const onOtherActivate = (event) => {
1032
+ const detail = event.detail;
1033
+ if ((detail === "ssh" || detail === "taskboard" || detail === "imagegen") && controller.getSnapshot().panelOpen) controller.close();
1034
+ };
1035
+ const SIDEBAR_ROW_SELECTOR = "[class*=\"sessionRow\"], [class*=\"projectRow\"], [class*=\"searchResultRow\"], [class*=\"searchResultWorkspace\"], [class*=\"newSession\"]";
1036
+ const onClickSidebarRow = (event) => {
1037
+ if (!controller.getSnapshot().panelOpen) return;
1038
+ const target = event.target;
1039
+ if (target === null) return;
1040
+ if (target.closest(SIDEBAR_ROW_SELECTOR) !== null) controller.close();
1041
+ };
1042
+ document.addEventListener("click", onClickSidebarRow, true);
1043
+ document.addEventListener(ACTIVATE_EVENT, onOtherActivate);
1044
+ const unsubscribe = controller.subscribe(applyActive);
1045
+ applyActive();
1046
+ ensure();
1047
+ return () => {
1048
+ document.removeEventListener("click", onClickSidebarRow, true);
1049
+ document.removeEventListener(ACTIVATE_EVENT, onOtherActivate);
1050
+ waitObserver.disconnect();
1051
+ unsubscribe();
1052
+ document.documentElement.removeAttribute(ACTIVE_ATTR);
1053
+ root?.unmount();
1054
+ root = void 0;
1055
+ container?.remove();
1056
+ container = void 0;
1057
+ };
1058
+ }
1059
+ //#endregion
1060
+ //#region src/client/sidebar-entry.ts
1061
+ const FAMILY_ENTRY_SELECTOR = "[data-dsh-taskboard-entry], [data-dsh-ssh-entry], [data-dsh-imagegen-entry], [data-dsh-audiogen-entry]";
1062
+ function sidebarRoot() {
1063
+ const column = document.querySelector("[data-pane=\"sidebar\"], [class*=\"sidebarCol\"]");
1064
+ if (column === null) return void 0;
1065
+ return column.querySelector("[class*=\"logoRow\"]")?.parentElement ?? column.firstElementChild;
1066
+ }
1067
+ function newSessionButton(root) {
1068
+ const nested = root.querySelector("button[class*=\"newSession\"]");
1069
+ if (nested !== null) return nested;
1070
+ for (const child of root.children) if (child.tagName === "BUTTON") return child;
1071
+ }
1072
+ function createEntry(controller, label, tooltip) {
1073
+ const entry = document.createElement("button");
1074
+ entry.type = "button";
1075
+ entry.dataset.dshAudiogenEntry = "";
1076
+ entry.className = panel_module_css_default.entry;
1077
+ entry.setAttribute("aria-label", label);
1078
+ entry.setAttribute("title", tooltip);
1079
+ entry.innerHTML = "<span class=\"" + panel_module_css_default.entryIcon + "\"><svg viewBox=\"0 0 16 16\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.3\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M3 3.5h10v9H3z\"/><path d=\"M1.5 5.5v5\"/><path d=\"M14.5 5.5v5\"/><path d=\"M6 6.5l4 1.5-4 1.5z\"/></svg></span><span class=\"" + panel_module_css_default.entryLabel + "\">" + label + "</span>";
1080
+ entry.addEventListener("click", () => {
1081
+ controller.toggle();
1082
+ });
1083
+ return entry;
1084
+ }
1085
+ function placeEntry(root, entry) {
1086
+ const button = newSessionButton(root);
1087
+ if (button === void 0) return false;
1088
+ if (entry.parentElement !== root) {
1089
+ const row = button.closest("[class*=\"logoRow\"]");
1090
+ const base = row !== null && row.parentElement === root ? row : button;
1091
+ const family = Array.from(root.children).filter((el) => el instanceof HTMLElement && el.matches(FAMILY_ENTRY_SELECTOR));
1092
+ const anchor = family.length > 0 ? family[family.length - 1].nextElementSibling : base.nextElementSibling;
1093
+ root.insertBefore(entry, anchor);
1094
+ }
1095
+ return true;
1096
+ }
1097
+ function mountSidebarEntry(controller, label, tooltip) {
1098
+ const entry = createEntry(controller, label, tooltip);
1099
+ let root;
1100
+ let placed = false;
1101
+ const tryPlace = () => {
1102
+ if (root !== void 0 && !root.isConnected) {
1103
+ rootObserver.disconnect();
1104
+ root = void 0;
1105
+ placed = false;
1106
+ }
1107
+ if (placed) {
1108
+ if (document.body.contains(entry)) return;
1109
+ rootObserver.disconnect();
1110
+ root = void 0;
1111
+ placed = false;
1112
+ }
1113
+ root ??= sidebarRoot();
1114
+ if (root === void 0) return;
1115
+ placed = placeEntry(root, entry);
1116
+ if (placed) rootObserver.observe(root, {
1117
+ childList: true,
1118
+ subtree: true
1119
+ });
1120
+ };
1121
+ const waitObserver = new MutationObserver(() => {
1122
+ tryPlace();
1123
+ });
1124
+ waitObserver.observe(document.body, {
1125
+ childList: true,
1126
+ subtree: true
1127
+ });
1128
+ const rootObserver = new MutationObserver(() => {
1129
+ if (root === void 0 || !root.isConnected) {
1130
+ placed = false;
1131
+ tryPlace();
1132
+ return;
1133
+ }
1134
+ if (!root.contains(entry)) placed = placeEntry(root, entry);
1135
+ });
1136
+ const syncActive = () => {
1137
+ if (controller.getSnapshot().panelOpen) entry.dataset.active = "true";
1138
+ else delete entry.dataset.active;
1139
+ };
1140
+ const unsubscribe = controller.subscribe(syncActive);
1141
+ syncActive();
1142
+ tryPlace();
1143
+ return () => {
1144
+ waitObserver.disconnect();
1145
+ rootObserver.disconnect();
1146
+ unsubscribe();
1147
+ entry.remove();
1148
+ };
1149
+ }
1150
+ //#endregion
1151
+ //#region src/client/settings-form.ts
1152
+ /**
1153
+ * Staged form model behind the plugin settings card. A card stages what the
1154
+ * user types and writes it only when they save — the settings write is a
1155
+ * durable, revision-fenced document mutation, so staging keeps what is on
1156
+ * screen exactly what a save would store. Self-contained slice of the same
1157
+ * pattern the dsh-web-ui family cards use (this package must not depend on a
1158
+ * sibling UI package).
1159
+ */
1160
+ /** A free-text field. An empty draft clears the field. */
1161
+ function textField(field) {
1162
+ return {
1163
+ field,
1164
+ format: (value) => typeof value === "string" ? value : "",
1165
+ parse: (text) => {
1166
+ const trimmed = text.trim();
1167
+ return trimmed === "" ? { kind: "clear" } : {
1168
+ kind: "set",
1169
+ value: trimmed
1170
+ };
1171
+ }
1172
+ };
1173
+ }
1174
+ /** A boolean field, edited through true/false draft text. */
1175
+ function booleanField(field) {
1176
+ return {
1177
+ field,
1178
+ format: (value) => typeof value === "boolean" ? String(value) : "",
1179
+ parse: (text) => {
1180
+ if (text === "true") return {
1181
+ kind: "set",
1182
+ value: true
1183
+ };
1184
+ if (text === "false") return {
1185
+ kind: "set",
1186
+ value: false
1187
+ };
1188
+ }
1189
+ };
1190
+ }
1191
+ /**
1192
+ * Stages one card's edits over one settings scope and writes them on save.
1193
+ *
1194
+ * The Host is the only authority on whether a value was accepted — its
1195
+ * validators own the constraints no schema can express — so the outcome is
1196
+ * read back from the section rather than predicted here. A save that did not
1197
+ * land keeps its drafts, so the user can correct them instead of retyping.
1198
+ */
1199
+ var CardForm = class {
1200
+ scope;
1201
+ options;
1202
+ specs;
1203
+ staged = /* @__PURE__ */ new Map();
1204
+ listeners = /* @__PURE__ */ new Set();
1205
+ saving = false;
1206
+ failed = false;
1207
+ /**
1208
+ * @param scope - the bound settings scope for this card's namespace.
1209
+ * @param specs - the fields this card edits.
1210
+ * @param options.secretSettled - for secret fields, whether the namespace
1211
+ * currently holds a stored secret (the redacted view never round-trips the
1212
+ * value, so a write's outcome is read from the secrets sidecar instead).
1213
+ */
1214
+ constructor(scope, specs, options = {}) {
1215
+ this.scope = scope;
1216
+ this.options = options;
1217
+ this.specs = new Map(specs.map((spec) => [spec.field, spec]));
1218
+ scope.subscribe(() => {
1219
+ this.publish();
1220
+ });
1221
+ }
1222
+ /** Publish a projection of this form, rebuilt whenever the scope or a draft changes. */
1223
+ bind(project) {
1224
+ const store = (0, _deepseek_ai_dsh_client_runtime_client.createSnapshotStore)(project());
1225
+ this.listeners.add(() => {
1226
+ store.set(project());
1227
+ });
1228
+ return store;
1229
+ }
1230
+ /** Read the card-level state: what the Host serves, and what a save would do. */
1231
+ shell() {
1232
+ const snapshot = this.scope.getSnapshot();
1233
+ const plan = this.plan();
1234
+ return {
1235
+ available: snapshot.status !== "loading",
1236
+ exposed: snapshot.status === "ready",
1237
+ writable: snapshot.writable,
1238
+ dirty: plan.length > 0,
1239
+ invalid: plan.some((item) => item.run === void 0),
1240
+ saving: this.saving,
1241
+ failed: this.failed
1242
+ };
1243
+ }
1244
+ /** Read one field's state from the effective section and its staged draft. */
1245
+ field(field) {
1246
+ const spec = this.specOf(field);
1247
+ const staged = this.staged.get(field);
1248
+ if (staged === void 0) return {
1249
+ text: spec.format(this.sectionValue(field)),
1250
+ overridden: this.stored(field),
1251
+ invalid: false
1252
+ };
1253
+ const write = staged.clear ? { kind: "clear" } : spec.parse(staged.text);
1254
+ return {
1255
+ text: staged.text,
1256
+ overridden: write?.kind === "set",
1257
+ invalid: write === void 0 && !(spec.secret === true && staged.text.trim() === "")
1258
+ };
1259
+ }
1260
+ /** The actions the card's slot registration injects. */
1261
+ actions() {
1262
+ return {
1263
+ edit: (field, text) => {
1264
+ this.stage(field, {
1265
+ text,
1266
+ clear: false
1267
+ });
1268
+ },
1269
+ resetField: (field) => {
1270
+ this.stage(field, {
1271
+ text: this.specOf(field).format(this.baseValue(field)),
1272
+ clear: true
1273
+ });
1274
+ },
1275
+ save: () => {
1276
+ this.save();
1277
+ },
1278
+ discard: () => {
1279
+ if (this.staged.size === 0 && !this.failed) return;
1280
+ this.staged.clear();
1281
+ this.failed = false;
1282
+ this.publish();
1283
+ }
1284
+ };
1285
+ }
1286
+ /**
1287
+ * Write every staged edit, then re-seed from what the Host accepted.
1288
+ * @returns settlement after every write and the read-back.
1289
+ */
1290
+ async save() {
1291
+ const plan = this.plan();
1292
+ const writes = plan.flatMap((item) => item.run === void 0 ? [] : [item.run]);
1293
+ if (plan.length === 0 || this.saving || writes.length !== plan.length) return;
1294
+ this.saving = true;
1295
+ this.failed = false;
1296
+ this.publish();
1297
+ let landed = true;
1298
+ for (const write of writes) landed = await write() && landed;
1299
+ if (landed) this.staged.clear();
1300
+ this.saving = false;
1301
+ this.failed = !landed;
1302
+ this.publish();
1303
+ }
1304
+ /**
1305
+ * Every staged edit a save would write. An entry whose draft is not a value
1306
+ * its field accepts carries no write: the form is still dirty, and the save
1307
+ * refuses rather than dropping the edit. A staged edit that matches the
1308
+ * effective section is not a write at all.
1309
+ */
1310
+ plan() {
1311
+ const plan = [];
1312
+ for (const [field, staged] of this.staged) {
1313
+ const spec = this.specOf(field);
1314
+ if (staged.clear) {
1315
+ if (spec.secret === true ? this.options.secretSettled?.(field) ?? false : this.stored(field)) plan.push({
1316
+ field,
1317
+ run: () => this.clear(field)
1318
+ });
1319
+ continue;
1320
+ }
1321
+ if (staged.text === spec.format(this.sectionValue(field))) continue;
1322
+ const write = spec.parse(staged.text);
1323
+ if (write === void 0) plan.push({
1324
+ field,
1325
+ run: void 0
1326
+ });
1327
+ else if (write.kind === "clear") plan.push({
1328
+ field,
1329
+ run: () => this.clear(field)
1330
+ });
1331
+ else plan.push({
1332
+ field,
1333
+ run: () => this.store(field, write.value)
1334
+ });
1335
+ }
1336
+ return plan;
1337
+ }
1338
+ async clear(field) {
1339
+ await this.scope.unset(field);
1340
+ if (this.specOf(field).secret === true) return !(this.options.secretSettled?.(field) ?? false);
1341
+ return !this.stored(field);
1342
+ }
1343
+ async store(field, value) {
1344
+ await this.scope.set(field, value);
1345
+ if (this.specOf(field).secret === true) return this.options.secretSettled?.(field) ?? true;
1346
+ return this.userLayer()?.[field] === value;
1347
+ }
1348
+ stage(field, edit) {
1349
+ this.staged.set(field, edit);
1350
+ this.failed = false;
1351
+ this.publish();
1352
+ }
1353
+ specOf(field) {
1354
+ const spec = this.specs.get(field);
1355
+ if (spec === void 0) throw new Error(`settings card has no field ${field}`);
1356
+ return spec;
1357
+ }
1358
+ snapshotOf() {
1359
+ return this.scope.getSnapshot();
1360
+ }
1361
+ sectionValue(field) {
1362
+ return this.snapshotOf().value?.[field];
1363
+ }
1364
+ baseValue(field) {
1365
+ return this.snapshotOf().base?.[field];
1366
+ }
1367
+ userLayer() {
1368
+ return this.snapshotOf().user;
1369
+ }
1370
+ stored(field) {
1371
+ const user = this.userLayer();
1372
+ return user !== void 0 && Object.hasOwn(user, field);
1373
+ }
1374
+ publish() {
1375
+ for (const listener of [...this.listeners]) listener();
1376
+ }
1377
+ };
1378
+ //#endregion
1379
+ //#region src/client/channels-form.ts
1380
+ /**
1381
+ * Staged form model for the channel list of the settings card. Mirrors the
1382
+ * CardForm staging pattern (dirty → one save) but for a structured value, so
1383
+ * the card can edit N channels, per-channel keys, and the default-channel
1384
+ * flag, then persist everything in one revision-fenced mutate call.
1385
+ *
1386
+ * Storage rules (dictated by dsh-settings semantics):
1387
+ * - the whole `channels` array is written wholesale via `path: ['channels']`;
1388
+ * - every channel's API key lives at `channelSecrets.<channelId>` (a secret
1389
+ * dict), written per-key so untouched keys are never clobbered by a save
1390
+ * the reader could not see (keys are redacted out of the wire view);
1391
+ * - path ops never navigate *inside* the channels array.
1392
+ */
1393
+ /** Deep equality over JSON-compatible data (the change predicate). */
1394
+ function deepEqualJson(a, b) {
1395
+ if (a === b) return true;
1396
+ if (typeof a !== "object" || typeof b !== "object" || a === null || b === null) return false;
1397
+ if (Array.isArray(a) || Array.isArray(b)) {
1398
+ if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) return false;
1399
+ return a.every((entry, index) => deepEqualJson(entry, b[index]));
1400
+ }
1401
+ const left = a;
1402
+ const right = b;
1403
+ const keys = Object.keys(left);
1404
+ if (keys.length !== Object.keys(right).length) return false;
1405
+ return keys.every((key) => key in right && deepEqualJson(left[key], right[key]));
1406
+ }
1407
+ /** Trim and normalize a draft channel (models never carry empty aliases). */
1408
+ function stripChannel(channel) {
1409
+ const models = channel.models.map((model) => ({
1410
+ alias: model.alias.trim(),
1411
+ id: model.id.trim() === "" ? model.alias.trim() : model.id.trim()
1412
+ })).filter((model) => model.alias !== "");
1413
+ return {
1414
+ id: channel.id,
1415
+ preset: channel.preset,
1416
+ name: channel.name.trim(),
1417
+ apiUrl: channel.apiUrl.trim(),
1418
+ models: [...new Map(models.map((model) => [model.alias, model])).values()]
1419
+ };
1420
+ }
1421
+ var ChannelsForm = class {
1422
+ scope;
1423
+ stagedChannels = null;
1424
+ stagedKeys = /* @__PURE__ */ new Map();
1425
+ stagedDefault = null;
1426
+ listeners = /* @__PURE__ */ new Set();
1427
+ saving = false;
1428
+ failed = false;
1429
+ constructor(scope) {
1430
+ this.scope = scope;
1431
+ scope.subscribe(() => {
1432
+ this.publish();
1433
+ });
1434
+ scope.subscribeSecretSets(() => {
1435
+ this.publish();
1436
+ });
1437
+ }
1438
+ /** Publish a projection of this form, rebuilt on every scope or draft change. */
1439
+ bind(project) {
1440
+ const store = (0, _deepseek_ai_dsh_client_runtime_client.createSnapshotStore)(project());
1441
+ this.listeners.add(() => {
1442
+ store.set(project());
1443
+ });
1444
+ return store;
1445
+ }
1446
+ /** Subscribe to staged and persisted channel changes. */
1447
+ subscribe(listener) {
1448
+ this.listeners.add(listener);
1449
+ return () => {
1450
+ this.listeners.delete(listener);
1451
+ };
1452
+ }
1453
+ /** The staged channel list, or the scope value when nothing is staged. */
1454
+ channelsValue() {
1455
+ const view = this.scope.getSnapshot().value;
1456
+ return this.stagedChannels ?? (Array.isArray(view?.channels) ? view.channels.map(toDraft) : []);
1457
+ }
1458
+ /** Whether a channel currently holds a stored or staged secret. */
1459
+ keyHeld(id) {
1460
+ const edit = this.stagedKeys.get(id);
1461
+ if (edit !== void 0) return edit.kind === "set" && edit.value !== "";
1462
+ return this.scope.getSecretSetSnapshot(`channelSecrets.${id}`);
1463
+ }
1464
+ defaultValue() {
1465
+ if (this.stagedDefault !== null) return this.stagedDefault;
1466
+ const view = this.scope.getSnapshot().value;
1467
+ const channels = this.channelsValue();
1468
+ if (view?.defaultChannelId !== void 0 && channels.some((channel) => channel.id === view.defaultChannelId)) return view.defaultChannelId;
1469
+ return channels[0]?.id ?? "";
1470
+ }
1471
+ dirtyValue() {
1472
+ const channels = this.channelsValue();
1473
+ const stagedChanged = this.stagedChannels !== null && !deepEqualJson(this.stagedChannels, scopeChannelsOf(this.scope));
1474
+ const scopeDefault = this.scope.getSnapshot().value?.defaultChannelId ?? channels[0]?.id ?? "";
1475
+ const defaultChanged = this.stagedDefault !== null && this.stagedDefault !== scopeDefault;
1476
+ return stagedChanged || defaultChanged || this.stagedKeys.size > 0;
1477
+ }
1478
+ /** The card-facing snapshot. */
1479
+ snapshot() {
1480
+ const channels = this.channelsValue();
1481
+ const keySet = {};
1482
+ for (const channel of channels) keySet[channel.id] = this.keyHeld(channel.id);
1483
+ return {
1484
+ channels,
1485
+ keySet,
1486
+ defaultChannelId: this.defaultValue(),
1487
+ dirty: this.dirtyValue(),
1488
+ writable: this.scope.getSnapshot().writable !== false,
1489
+ saving: this.saving,
1490
+ failed: this.failed
1491
+ };
1492
+ }
1493
+ /** The actions the card's slot registration injects. */
1494
+ actions() {
1495
+ return {
1496
+ setChannels: (channels) => {
1497
+ this.stageChannels(channels);
1498
+ },
1499
+ setChannelKey: (id, value) => {
1500
+ this.stageKey(id, value);
1501
+ },
1502
+ setDefaultChannel: (id) => {
1503
+ this.stagedDefault = id;
1504
+ this.failed = false;
1505
+ this.publish();
1506
+ },
1507
+ commit: () => this.commit(),
1508
+ discard: () => {
1509
+ if (this.stagedChannels === null && this.stagedKeys.size === 0 && this.stagedDefault === null && !this.failed) return;
1510
+ this.stagedChannels = null;
1511
+ this.stagedKeys.clear();
1512
+ this.stagedDefault = null;
1513
+ this.failed = false;
1514
+ this.publish();
1515
+ }
1516
+ };
1517
+ }
1518
+ stageChannels(channels) {
1519
+ const cleaned = channels.map(stripChannel);
1520
+ this.stagedChannels = cleaned;
1521
+ this.failed = false;
1522
+ this.publish();
1523
+ }
1524
+ stageKey(id, value) {
1525
+ if (value === void 0 || value.trim() === "") {
1526
+ if (this.keyHeld(id)) this.stagedKeys.set(id, { kind: "clear" });
1527
+ } else this.stagedKeys.set(id, {
1528
+ kind: "set",
1529
+ value: value.trim()
1530
+ });
1531
+ this.failed = false;
1532
+ this.publish();
1533
+ }
1534
+ /** Build the single batch of path ops a save performs. */
1535
+ planOps() {
1536
+ const ops = [];
1537
+ if (this.stagedChannels !== null) {
1538
+ ops.push({
1539
+ op: "set",
1540
+ path: ["channels"],
1541
+ value: this.stagedChannels
1542
+ });
1543
+ ops.push({
1544
+ op: "unset",
1545
+ path: ["apiUrl"]
1546
+ });
1547
+ ops.push({
1548
+ op: "unset",
1549
+ path: ["apiKey"]
1550
+ });
1551
+ ops.push({
1552
+ op: "unset",
1553
+ path: ["imageModels"]
1554
+ });
1555
+ }
1556
+ for (const [id, edit] of this.stagedKeys) if (edit.kind === "set") ops.push({
1557
+ op: "set",
1558
+ path: ["channelSecrets", id],
1559
+ value: edit.value
1560
+ });
1561
+ else ops.push({
1562
+ op: "unset",
1563
+ path: ["channelSecrets", id]
1564
+ });
1565
+ if (this.stagedDefault !== null) ops.push({
1566
+ op: "set",
1567
+ path: ["defaultChannelId"],
1568
+ value: this.stagedDefault
1569
+ });
1570
+ return ops;
1571
+ }
1572
+ /**
1573
+ * Write every staged edit, then re-seed from what the Host accepted.
1574
+ * @returns settlement after the write settles.
1575
+ */
1576
+ async commit() {
1577
+ if (this.saving) return;
1578
+ const ops = this.planOps();
1579
+ if (ops.length === 0) return;
1580
+ this.saving = true;
1581
+ this.failed = false;
1582
+ this.publish();
1583
+ try {
1584
+ await this.scope.mutateOps(ops);
1585
+ this.stagedChannels = null;
1586
+ this.stagedKeys.clear();
1587
+ this.stagedDefault = null;
1588
+ this.failed = false;
1589
+ } catch {
1590
+ this.failed = true;
1591
+ } finally {
1592
+ this.saving = false;
1593
+ this.publish();
1594
+ }
1595
+ }
1596
+ publish() {
1597
+ for (const listener of [...this.listeners]) listener();
1598
+ }
1599
+ };
1600
+ /** Project a stored channel into a draft (secrets never travel in channels). */
1601
+ function toDraft(channel) {
1602
+ return {
1603
+ id: channel.id,
1604
+ preset: channel.preset,
1605
+ name: channel.name,
1606
+ apiUrl: channel.apiUrl,
1607
+ models: channel.models.map((model) => ({ ...model }))
1608
+ };
1609
+ }
1610
+ /** The scope's current channels value (a plain array), for change detection. */
1611
+ function scopeChannelsOf(scope) {
1612
+ const view = scope.getSnapshot().value;
1613
+ return Array.isArray(view?.channels) ? view.channels : [];
1614
+ }
1615
+ //#endregion
1616
+ //#region \0dsh-css:/Users/shimingming/Projects_code/dsh-audiogen/src/client/settings-card.module.css.mjs
1617
+ const css$1 = ".zmjoSq_card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;list-style:none;transition:border-color .16s,background .16s}.zmjoSq_card:hover{border-color:var(--dsw-alias-label-dimmed)}.zmjoSq_card:has(.zmjoSq_body){background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-label-dimmed)}.zmjoSq_header{appearance:none;width:100%;font:inherit;color:inherit;text-align:left;cursor:pointer;background:0 0;border:0;border-radius:12px;align-items:center;gap:12px;padding:14px 16px;display:flex}.zmjoSq_header:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-2px}.zmjoSq_headText{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}.zmjoSq_name{color:var(--dsw-alias-label-primary);font-size:15px;font-weight:600;line-height:1.4}.zmjoSq_description{color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.5}.zmjoSq_chevron,.zmjoSq_chevronOpen{color:var(--dsw-alias-label-tertiary);flex:none;transition:transform .16s}.zmjoSq_chevronOpen{transform:rotate(180deg)}.zmjoSq_pending{white-space:nowrap;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);border-radius:999px;flex:none;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}.zmjoSq_body{border-top:1px solid var(--dsw-alias-border-l2);margin:0 16px;padding-bottom:8px}.zmjoSq_versionRow{border-bottom:1px solid var(--dsw-alias-border-l2);justify-content:space-between;align-items:center;gap:12px;padding:12px 0;display:flex}.zmjoSq_versionLabel{color:var(--dsw-alias-label-primary);font-size:13px;font-weight:500;line-height:1.5}.zmjoSq_versionValue{background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);font-family:var(--dsw-font-family-mono,monospace);border-radius:999px;padding:1px 8px;font-size:12px;line-height:1.5}.zmjoSq_field{flex-direction:column;gap:6px;padding:12px 0;display:flex}.zmjoSq_field+.zmjoSq_field{border-top:1px solid var(--dsw-alias-border-l2)}.zmjoSq_head{align-items:center;gap:8px;display:flex}.zmjoSq_label{min-width:0;color:var(--dsw-alias-label-primary);flex:1;font-size:13px;font-weight:500;line-height:1.5}.zmjoSq_badges{align-items:center;gap:8px;display:inline-flex}.zmjoSq_badge{white-space:nowrap;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);border-radius:999px;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}.zmjoSq_reset{font:inherit;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;padding:0;font-size:12px;line-height:1.5}.zmjoSq_reset:hover:not(:disabled){color:var(--dsw-alias-label-primary)}.zmjoSq_reset:disabled{cursor:default;opacity:.5}.zmjoSq_input,.zmjoSq_select{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);height:34px;font:inherit;color:var(--dsw-alias-label-primary);border-radius:8px;outline:none;padding:0 12px;font-size:13px;line-height:1.5}.zmjoSq_input:focus-visible,.zmjoSq_select:focus-visible{border-color:var(--dsw-alias-brand-primary)}.zmjoSq_input:disabled,.zmjoSq_select:disabled{color:var(--dsw-alias-label-tertiary);cursor:default}.zmjoSq_inputInvalid{border:1px solid var(--dsw-alias-label-error);background:var(--dsw-alias-bg-layer-3);height:34px;font:inherit;color:var(--dsw-alias-label-primary);border-radius:8px;outline:none;padding:0 12px;font-size:13px;line-height:1.5}.zmjoSq_textarea,.zmjoSq_textareaInvalid{resize:vertical;background:var(--dsw-alias-bg-layer-3);min-height:70px;font:inherit;color:var(--dsw-alias-label-primary);border-radius:8px;outline:none;padding:8px 12px;font-size:13px;line-height:1.5}.zmjoSq_textarea{border:1px solid var(--dsw-alias-border-l2)}.zmjoSq_textareaInvalid{border:1px solid var(--dsw-alias-label-error)}.zmjoSq_textarea:focus-visible{border-color:var(--dsw-alias-brand-primary)}.zmjoSq_textarea:disabled{color:var(--dsw-alias-label-tertiary);cursor:default}.zmjoSq_hint,.zmjoSq_invalid{margin:0;font-size:12px;line-height:1.5}.zmjoSq_hint{color:var(--dsw-alias-label-tertiary)}.zmjoSq_invalid{color:var(--dsw-alias-label-error)}.zmjoSq_readOnly,.zmjoSq_notExposed{color:var(--dsw-alias-label-tertiary);margin:12px 0 0;font-size:12px;line-height:1.5}.zmjoSq_sectionDivider{background:var(--dsw-alias-border-l2);height:1px;margin:18px 0 14px}.zmjoSq_sectionTitle{color:var(--dsw-alias-label-primary);margin:0;font-size:14px;line-height:1.4}.zmjoSq_sectionHint{color:var(--dsw-alias-label-tertiary);margin:-4px 0 2px;font-size:12px;line-height:1.5}.zmjoSq_modelSection{padding:2px 0 14px}.zmjoSq_sectionHeader{justify-content:space-between;align-items:flex-start;gap:12px;display:flex}.zmjoSq_sectionHeader .zmjoSq_sectionHint{max-width:430px}.zmjoSq_modelSummary{flex-wrap:wrap;align-items:center;gap:6px;margin-top:12px;display:flex}.zmjoSq_modelChip{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);max-width:100%;color:var(--dsw-alias-label-primary);font-family:var(--dsw-font-family-mono,monospace);border-radius:6px;align-items:center;gap:5px;padding:3px 5px 3px 8px;font-size:12px;line-height:1.5;display:inline-flex}.zmjoSq_modelChip>span{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.zmjoSq_modelChip button{appearance:none;width:18px;height:18px;color:var(--dsw-alias-label-tertiary);font:inherit;cursor:pointer;background:0 0;border:0;border-radius:4px;padding:0;line-height:18px}.zmjoSq_modelChip button:hover:not(:disabled){color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-module-platform)}.zmjoSq_modelChip button:disabled{cursor:default;opacity:.45}.zmjoSq_addModel{appearance:none;border:1px dashed var(--dsw-alias-border-l2);min-height:28px;color:var(--dsw-alias-label-secondary);font:inherit;cursor:pointer;background:0 0;border-radius:6px;padding:0 9px;font-size:12px}.zmjoSq_addModel:hover:not(:disabled){color:var(--dsw-alias-brand-primary);border-color:var(--dsw-alias-brand-primary)}.zmjoSq_addModel:disabled{opacity:.45;cursor:default}.zmjoSq_manualModelRow{gap:8px;margin-top:10px;display:flex}.zmjoSq_manualModelRow .zmjoSq_input{flex:1;min-width:0}.zmjoSq_disclosure{appearance:none;border:0;border-top:1px solid var(--dsw-alias-border-l2);width:100%;color:var(--dsw-alias-label-primary);font:inherit;text-align:left;cursor:pointer;background:0 0;align-items:center;gap:8px;padding:13px 0;font-size:13px;font-weight:500;display:flex}.zmjoSq_disclosure>span:nth-child(2){color:var(--dsw-alias-label-tertiary);margin-left:auto;font-size:12px;font-weight:400}.zmjoSq_disclosure>span:last-child{color:var(--dsw-alias-label-tertiary)}.zmjoSq_disclosure:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}.zmjoSq_optionalContent{padding:0 0 8px}.zmjoSq_inlineDisclosure{appearance:none;color:var(--dsw-alias-label-secondary);font:inherit;cursor:pointer;background:0 0;border:0;align-items:center;gap:6px;margin-top:12px;padding:0;font-size:12px;display:inline-flex}.zmjoSq_inlineDisclosure:hover{color:var(--dsw-alias-label-primary)}.zmjoSq_modelFetchRow{align-items:center;gap:8px;display:flex}.zmjoSq_modelFetch,.zmjoSq_modelChoices{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);min-height:32px;color:var(--dsw-alias-label-secondary);font:inherit;border-radius:7px;font-size:12px}.zmjoSq_modelFetch{cursor:pointer;padding:0 10px}.zmjoSq_modelFetch:hover:not(:disabled){color:var(--dsw-alias-brand-primary);border-color:var(--dsw-alias-brand-primary)}.zmjoSq_modelFetch:disabled{opacity:.5;cursor:default}.zmjoSq_modelChoices{flex:1;min-width:0;padding:0 8px}.zmjoSq_modelCandidateList{flex-wrap:wrap;gap:6px 10px;margin-top:10px;display:flex}.zmjoSq_modelCandidateLabel{width:100%;color:var(--dsw-alias-label-tertiary);font-size:12px}.zmjoSq_modelCandidate{appearance:none;min-width:0;color:var(--dsw-alias-label-secondary);cursor:pointer;background:var(--dsw-alias-bg-layer-3);font-size:12px;line-height:1.5;font:inherit;border:1px solid #0000;border-radius:5px;align-items:center;gap:5px;padding:3px 6px;display:inline-flex}.zmjoSq_modelCandidate input{margin:0}.zmjoSq_modelCandidate[data-selected]{border-color:var(--dsw-alias-border-l2);color:var(--dsw-alias-label-primary)}.zmjoSq_footer{border-top:1px solid var(--dsw-alias-border-l2);justify-content:flex-end;align-items:center;gap:8px;padding:12px 0 4px;display:flex}.zmjoSq_failed{min-width:0;color:var(--dsw-alias-label-error);flex:1;margin:0;font-size:12px;line-height:1.5}.zmjoSq_discard,.zmjoSq_save{appearance:none;font:inherit;cursor:pointer;border:1px solid #0000;border-radius:8px;padding:5px 14px;font-size:13px;line-height:1.5}.zmjoSq_discard{border-color:var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);background:0 0}.zmjoSq_discard:hover:not(:disabled){color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-dimmed)}.zmjoSq_save{background:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-3)}.zmjoSq_discard:disabled,.zmjoSq_save:disabled{opacity:.4;cursor:default}.zmjoSq_discard:focus-visible,.zmjoSq_save:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}@media (prefers-reduced-motion:reduce){.zmjoSq_card,.zmjoSq_chevron,.zmjoSq_chevronOpen{transition:none}}.zmjoSq_channelSection{padding:6px 0 2px}.zmjoSq_channelEmpty{color:var(--dsw-alias-label-tertiary);margin:10px 0 0;font-size:12px;line-height:1.5}.zmjoSq_channelList{flex-direction:column;gap:6px;margin:10px 0 0;padding:0;list-style:none;display:flex}.zmjoSq_channelRow{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:10px;align-items:center;gap:10px;padding:8px 10px;display:flex}.zmjoSq_channelRow[data-action]{flex-wrap:wrap}.zmjoSq_channelDotReady,.zmjoSq_channelDotWarn{border-radius:50%;flex:none;width:9px;height:9px}.zmjoSq_channelDotReady{background:var(--dsw-color-success,#2fbf71)}.zmjoSq_channelDotWarn{background:var(--dsw-color-danger,#e5484d)}.zmjoSq_channelMain{appearance:none;min-width:0;font:inherit;text-align:left;cursor:pointer;background:0 0;border:0;flex-direction:column;flex:1;gap:3px;padding:0;display:flex}.zmjoSq_channelName{color:var(--dsw-alias-label-primary);text-overflow:ellipsis;white-space:nowrap;font-size:13px;font-weight:600;line-height:1.4;overflow:hidden}.zmjoSq_channelMeta{flex-wrap:wrap;align-items:center;gap:4px 8px;display:flex}.zmjoSq_channelHost{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:1.5;font-family:var(--dsw-font-family-mono,monospace);text-overflow:ellipsis;white-space:nowrap;max-width:180px;overflow:hidden}.zmjoSq_channelBadge{white-space:nowrap;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);border-radius:999px;padding:0 7px;font-size:11px;font-weight:500;line-height:17px}.zmjoSq_channelBadge[data-warn]{color:var(--dsw-alias-label-error)}.zmjoSq_channelBadge[data-default]{background:var(--dsw-alias-bg-module-poped,var(--dsw-alias-bg-module-platform));color:var(--dsw-alias-brand-primary)}.zmjoSq_channelAction{appearance:none;font:inherit;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:0;border-radius:6px;flex:none;padding:4px 8px;font-size:12px;line-height:1.5}.zmjoSq_channelAction:hover:not(:disabled){color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-module-platform)}.zmjoSq_channelAction[data-danger]{color:var(--dsw-alias-label-error)}.zmjoSq_channelAction:disabled{opacity:.45;cursor:default}.zmjoSq_channelDanger{appearance:none;border:1px solid var(--dsw-alias-label-error);color:var(--dsw-alias-label-error);font:inherit;cursor:pointer;background:0 0;border-radius:6px;flex:none;padding:3px 10px;font-size:12px}.zmjoSq_channelDanger:hover:not(:disabled){background:color-mix(in srgb, var(--dsw-alias-label-error) 12%, transparent)}.zmjoSq_channelDanger:disabled{opacity:.45;cursor:default}.zmjoSq_deleteConfirmText{min-width:0;color:var(--dsw-alias-label-error);flex:1;font-size:12px;line-height:1.5}.zmjoSq_channelAddRow{gap:8px;margin-top:10px;display:flex}.zmjoSq_channelAdd{appearance:none;border:1px dashed var(--dsw-alias-border-l2);min-height:30px;color:var(--dsw-alias-label-secondary);font:inherit;cursor:pointer;background:0 0;border-radius:8px;padding:0 12px;font-size:12px}.zmjoSq_channelAdd:hover:not(:disabled){color:var(--dsw-alias-brand-primary);border-color:var(--dsw-alias-brand-primary)}.zmjoSq_channelAdd:disabled{opacity:.45;cursor:default}.zmjoSq_spacer{flex:1}.zmjoSq_editorBackdrop{z-index:120;background:color-mix(in srgb, var(--dsw-alias-bg-layer-1,#000) 45%, transparent);justify-content:center;align-items:flex-start;padding:9vh 16px 16px;display:flex;position:fixed;inset:0}.zmjoSq_editorPanel{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);border-radius:14px;width:min(560px,100%);max-height:82vh;padding:16px;overflow-y:auto;box-shadow:0 18px 50px #00000059}.zmjoSq_editorHeader{border-bottom:1px solid var(--dsw-alias-border-l2);justify-content:space-between;align-items:flex-start;gap:12px;padding-bottom:10px;display:flex}.zmjoSq_editorClose{appearance:none;width:26px;height:26px;color:var(--dsw-alias-label-tertiary);font:inherit;cursor:pointer;background:0 0;border:0;border-radius:6px;flex:none;font-size:16px;line-height:26px}.zmjoSq_editorClose:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-module-platform)}.zmjoSq_editorField{flex-direction:column;gap:6px;padding:12px 0 0;display:flex}.zmjoSq_editorDivider{background:var(--dsw-alias-border-l2);height:1px;margin:14px 0 4px}.zmjoSq_editorSectionHeader{justify-content:space-between;align-items:center;gap:12px;padding:10px 0 4px;display:flex}.zmjoSq_editorSectionHeader .zmjoSq_label{flex:1}.zmjoSq_editorTools{flex-direction:column;gap:8px;margin-top:10px;display:flex}.zmjoSq_editorFooter{border-top:1px solid var(--dsw-alias-border-l2);align-items:center;gap:8px;padding-top:12px;display:flex}.zmjoSq_detectOk{color:var(--dsw-color-success,var(--dsw-alias-label-secondary));margin:0;font-size:12px;line-height:1.5}.zmjoSq_modelRows{flex-direction:column;gap:8px;margin:8px 0 0;padding:0;list-style:none;display:flex}.zmjoSq_modelRow{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:10px;justify-content:space-between;align-items:center;gap:10px;padding:8px 10px;display:flex}.zmjoSq_modelRowInputs{flex:1;align-items:center;gap:6px;min-width:0;display:flex}.zmjoSq_modelRowInputs .zmjoSq_input{flex:1;min-width:0}.zmjoSq_modelArrow{color:var(--dsw-alias-label-tertiary);flex:none;font-size:12px}.zmjoSq_modelRowBadges{flex:none;align-items:center;gap:6px;display:flex}.zmjoSq_modelBadge{white-space:nowrap;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-tertiary);border-radius:999px;padding:0 7px;font-size:11px;line-height:17px}.zmjoSq_modelBadge[data-verified]{color:var(--dsw-color-success,var(--dsw-alias-label-secondary))}.zmjoSq_modelBadge[data-warn]{color:var(--dsw-alias-label-error)}.zmjoSq_modelRowRemove{appearance:none;width:20px;height:20px;color:var(--dsw-alias-label-tertiary);font:inherit;cursor:pointer;background:0 0;border:0;border-radius:4px;flex:none;padding:0;line-height:20px}.zmjoSq_modelRowRemove:hover:not(:disabled){color:var(--dsw-alias-label-error);background:var(--dsw-alias-bg-module-platform)}.zmjoSq_modelRowRemove:disabled{opacity:.45;cursor:default}.zmjoSq_modelCandidate>.zmjoSq_modelBadge{flex:none}.zmjoSq_channelControls{position:relative}.zmjoSq_presetInline{z-index:10;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);border-radius:10px;width:min(420px,100vw - 48px);max-height:min(440px,60vh);margin:0;padding:12px;position:absolute;bottom:calc(100% + 8px);left:0;overflow-y:auto;box-shadow:0 12px 30px #0000004d}.zmjoSq_presetInlineHeader{justify-content:space-between;align-items:flex-start;gap:12px;display:flex}.zmjoSq_presetList{flex-direction:column;gap:8px;margin-top:12px;display:flex}.zmjoSq_presetRow{appearance:none;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);width:100%;color:inherit;font:inherit;text-align:left;cursor:pointer;border-radius:10px;flex-direction:column;align-items:flex-start;gap:3px;padding:10px 12px;display:flex}.zmjoSq_presetRow:hover:not(:disabled){border-color:var(--dsw-alias-brand-primary)}.zmjoSq_presetRow[data-custom]{border-style:dashed}.zmjoSq_presetRow:disabled{opacity:.5;cursor:default}.zmjoSq_presetName{color:var(--dsw-alias-label-primary);font-size:13px;font-weight:600;line-height:1.4}.zmjoSq_presetMeta{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:1.5;font-family:var(--dsw-font-family-mono,monospace)}.zmjoSq_presetHint{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:1.5}";
1618
+ const tagId$1 = "dsh-audiogen/settings-card.module.css";
1619
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$1) + "]") === null) {
1620
+ const tag = document.createElement("style");
1621
+ tag.dataset.plugin = "dsh-audiogen";
1622
+ tag.dataset.pluginCss = tagId$1;
1623
+ tag.textContent = css$1;
1624
+ document.head.appendChild(tag);
1625
+ }
1626
+ var settings_card_module_css_default = {
1627
+ "channelDotWarn": "zmjoSq_channelDotWarn",
1628
+ "badge": "zmjoSq_badge",
1629
+ "invalid": "zmjoSq_invalid",
1630
+ "footer": "zmjoSq_footer",
1631
+ "channelAdd": "zmjoSq_channelAdd",
1632
+ "editorClose": "zmjoSq_editorClose",
1633
+ "modelRowRemove": "zmjoSq_modelRowRemove",
1634
+ "inlineDisclosure": "zmjoSq_inlineDisclosure",
1635
+ "channelName": "zmjoSq_channelName",
1636
+ "field": "zmjoSq_field",
1637
+ "reset": "zmjoSq_reset",
1638
+ "chevronOpen": "zmjoSq_chevronOpen",
1639
+ "textarea": "zmjoSq_textarea",
1640
+ "notExposed": "zmjoSq_notExposed",
1641
+ "sectionHeader": "zmjoSq_sectionHeader",
1642
+ "optionalContent": "zmjoSq_optionalContent",
1643
+ "channelAddRow": "zmjoSq_channelAddRow",
1644
+ "channelEmpty": "zmjoSq_channelEmpty",
1645
+ "channelDanger": "zmjoSq_channelDanger",
1646
+ "editorField": "zmjoSq_editorField",
1647
+ "chevron": "zmjoSq_chevron",
1648
+ "sectionHint": "zmjoSq_sectionHint",
1649
+ "modelCandidateList": "zmjoSq_modelCandidateList",
1650
+ "save": "zmjoSq_save",
1651
+ "header": "zmjoSq_header",
1652
+ "addModel": "zmjoSq_addModel",
1653
+ "name": "zmjoSq_name",
1654
+ "discard": "zmjoSq_discard",
1655
+ "editorHeader": "zmjoSq_editorHeader",
1656
+ "channelMain": "zmjoSq_channelMain",
1657
+ "detectOk": "zmjoSq_detectOk",
1658
+ "presetMeta": "zmjoSq_presetMeta",
1659
+ "channelBadge": "zmjoSq_channelBadge",
1660
+ "channelMeta": "zmjoSq_channelMeta",
1661
+ "versionValue": "zmjoSq_versionValue",
1662
+ "label": "zmjoSq_label",
1663
+ "presetRow": "zmjoSq_presetRow",
1664
+ "modelSummary": "zmjoSq_modelSummary",
1665
+ "input": "zmjoSq_input",
1666
+ "readOnly": "zmjoSq_readOnly",
1667
+ "channelList": "zmjoSq_channelList",
1668
+ "channelSection": "zmjoSq_channelSection",
1669
+ "modelFetchRow": "zmjoSq_modelFetchRow",
1670
+ "body": "zmjoSq_body",
1671
+ "card": "zmjoSq_card",
1672
+ "head": "zmjoSq_head",
1673
+ "modelRows": "zmjoSq_modelRows",
1674
+ "presetInlineHeader": "zmjoSq_presetInlineHeader",
1675
+ "modelArrow": "zmjoSq_modelArrow",
1676
+ "presetList": "zmjoSq_presetList",
1677
+ "versionRow": "zmjoSq_versionRow",
1678
+ "failed": "zmjoSq_failed",
1679
+ "description": "zmjoSq_description",
1680
+ "modelRow": "zmjoSq_modelRow",
1681
+ "inputInvalid": "zmjoSq_inputInvalid",
1682
+ "modelRowInputs": "zmjoSq_modelRowInputs",
1683
+ "channelAction": "zmjoSq_channelAction",
1684
+ "channelDotReady": "zmjoSq_channelDotReady",
1685
+ "editorSectionHeader": "zmjoSq_editorSectionHeader",
1686
+ "modelCandidateLabel": "zmjoSq_modelCandidateLabel",
1687
+ "modelChip": "zmjoSq_modelChip",
1688
+ "editorFooter": "zmjoSq_editorFooter",
1689
+ "presetHint": "zmjoSq_presetHint",
1690
+ "sectionTitle": "zmjoSq_sectionTitle",
1691
+ "versionLabel": "zmjoSq_versionLabel",
1692
+ "hint": "zmjoSq_hint",
1693
+ "badges": "zmjoSq_badges",
1694
+ "textareaInvalid": "zmjoSq_textareaInvalid",
1695
+ "sectionDivider": "zmjoSq_sectionDivider",
1696
+ "modelCandidate": "zmjoSq_modelCandidate",
1697
+ "modelRowBadges": "zmjoSq_modelRowBadges",
1698
+ "channelHost": "zmjoSq_channelHost",
1699
+ "pending": "zmjoSq_pending",
1700
+ "disclosure": "zmjoSq_disclosure",
1701
+ "modelChoices": "zmjoSq_modelChoices",
1702
+ "presetName": "zmjoSq_presetName",
1703
+ "headText": "zmjoSq_headText",
1704
+ "spacer": "zmjoSq_spacer",
1705
+ "manualModelRow": "zmjoSq_manualModelRow",
1706
+ "editorTools": "zmjoSq_editorTools",
1707
+ "editorBackdrop": "zmjoSq_editorBackdrop",
1708
+ "channelRow": "zmjoSq_channelRow",
1709
+ "modelBadge": "zmjoSq_modelBadge",
1710
+ "channelControls": "zmjoSq_channelControls",
1711
+ "presetInline": "zmjoSq_presetInline",
1712
+ "modelFetch": "zmjoSq_modelFetch",
1713
+ "select": "zmjoSq_select",
1714
+ "deleteConfirmText": "zmjoSq_deleteConfirmText",
1715
+ "editorPanel": "zmjoSq_editorPanel",
1716
+ "editorDivider": "zmjoSq_editorDivider",
1717
+ "modelSection": "zmjoSq_modelSection"
1718
+ };
1719
+ //#endregion
1720
+ //#region src/client/SettingsCard.tsx
1721
+ /**
1722
+ * The dsh-audiogen settings card.
1723
+ *
1724
+ * Registers into the official `settings.plugin.item` slot. It manages a list
1725
+ * of audio channels (each with API URL, per-channel secret, and model/voice
1726
+ * catalog), plus master switches.
1727
+ */
1728
+ var AudioGenSettingsCardController = class {
1729
+ scope;
1730
+ form;
1731
+ channelsForm;
1732
+ constructor(scope) {
1733
+ this.scope = scope;
1734
+ this.form = new CardForm(scope, [
1735
+ booleanField("enabled"),
1736
+ booleanField("announceToAgent"),
1737
+ booleanField("allowAgentAudioGeneration"),
1738
+ textField("defaultModel")
1739
+ ]);
1740
+ this.channelsForm = new ChannelsForm(scope);
1741
+ }
1742
+ projection() {
1743
+ const shell = this.form.shell();
1744
+ return {
1745
+ ...shell,
1746
+ dirty: shell.dirty || this.channelsForm.snapshot().dirty,
1747
+ channels: this.channelsForm.snapshot(),
1748
+ enabled: this.form.field("enabled"),
1749
+ announceToAgent: this.form.field("announceToAgent"),
1750
+ allowAgentAudioGeneration: this.form.field("allowAgentAudioGeneration"),
1751
+ defaultModel: this.form.field("defaultModel")
1752
+ };
1753
+ }
1754
+ inject() {
1755
+ const cardStore = this.form.bind(() => this.projection());
1756
+ this.channelsForm.subscribe(() => {
1757
+ cardStore.set(this.projection());
1758
+ });
1759
+ return {
1760
+ hooks: { audioGenSettingsCard: cardStore },
1761
+ channels: this.channelsForm.actions(),
1762
+ ...this.form.actions()
1763
+ };
1764
+ }
1765
+ };
1766
+ function newChannelDraft(preset, existing) {
1767
+ const id = `ch-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 6)}`;
1768
+ if (preset === void 0) return {
1769
+ id,
1770
+ preset: "",
1771
+ name: "",
1772
+ apiUrl: "",
1773
+ models: []
1774
+ };
1775
+ return {
1776
+ id,
1777
+ preset: preset.id,
1778
+ name: preset.name,
1779
+ apiUrl: preset.apiUrl,
1780
+ models: preset.models.map((model) => ({ ...model }))
1781
+ };
1782
+ }
1783
+ function modelsToText(models) {
1784
+ return models.map((model) => `${model.alias}=${model.id}`).join("\n");
1785
+ }
1786
+ function textToModels(text) {
1787
+ return text.split(/\n|,/).map((line) => line.trim()).filter(Boolean).map((line) => {
1788
+ const eq = line.indexOf("=");
1789
+ if (eq < 0) return {
1790
+ alias: line,
1791
+ id: line
1792
+ };
1793
+ return {
1794
+ alias: line.slice(0, eq).trim(),
1795
+ id: line.slice(eq + 1).trim()
1796
+ };
1797
+ }).filter((model) => model.alias !== "");
1798
+ }
1799
+ function AudioGenSettingsCard(props) {
1800
+ const { t } = props;
1801
+ const state = props.useAudioGenSettingsCard((snapshot) => snapshot);
1802
+ const [open, setOpen] = (0, react.useState)(false);
1803
+ const [editingId, setEditingId] = (0, react.useState)(null);
1804
+ const [presetPickerOpen, setPresetPickerOpen] = (0, react.useState)(false);
1805
+ const [presets, setPresets] = (0, react.useState)([]);
1806
+ const [presetError, setPresetError] = (0, react.useState)(null);
1807
+ const [confirmDeleteId, setConfirmDeleteId] = (0, react.useState)(null);
1808
+ const [editName, setEditName] = (0, react.useState)("");
1809
+ const [editUrl, setEditUrl] = (0, react.useState)("");
1810
+ const [editKey, setEditKey] = (0, react.useState)("");
1811
+ const [editModels, setEditModels] = (0, react.useState)("");
1812
+ const [editDefault, setEditDefault] = (0, react.useState)(false);
1813
+ const channels = state.channels.channels;
1814
+ const editing = editingId === null ? void 0 : channels.find((channel) => channel.id === editingId);
1815
+ (0, react.useEffect)(() => {
1816
+ if (editing === void 0) return;
1817
+ setEditName(editing.name);
1818
+ setEditUrl(editing.apiUrl);
1819
+ setEditKey("");
1820
+ setEditModels(modelsToText(editing.models));
1821
+ setEditDefault(editing.id === state.channels.defaultChannelId);
1822
+ }, [editingId, editing?.id]);
1823
+ if (!state.available) return null;
1824
+ const blocked = !state.dirty || state.invalid || state.saving || state.channels.saving;
1825
+ const saveEdit = () => {
1826
+ if (editingId === null) return;
1827
+ const existing = channels.find((channel) => channel.id === editingId);
1828
+ const models = textToModels(editModels);
1829
+ const updated = {
1830
+ id: editingId,
1831
+ preset: existing?.preset ?? "",
1832
+ name: editName.trim(),
1833
+ apiUrl: editUrl.trim(),
1834
+ models
1835
+ };
1836
+ const next = existing === void 0 ? [...channels, updated] : channels.map((channel) => channel.id === editingId ? updated : channel);
1837
+ props.channels.setChannels(next);
1838
+ if (editKey.trim() !== "") props.channels.setChannelKey(editingId, editKey.trim());
1839
+ if (editDefault) props.channels.setDefaultChannel(editingId);
1840
+ setEditingId(null);
1841
+ };
1842
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
1843
+ className: settings_card_module_css_default.card,
1844
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1845
+ type: "button",
1846
+ className: settings_card_module_css_default.header,
1847
+ "aria-expanded": open,
1848
+ "aria-label": `${t(open ? "settings.collapse" : "settings.expand")}: ${t("settings.title")}`,
1849
+ onClick: () => {
1850
+ setOpen(!open);
1851
+ },
1852
+ children: [
1853
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1854
+ className: settings_card_module_css_default.headText,
1855
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1856
+ className: settings_card_module_css_default.name,
1857
+ children: t("settings.title")
1858
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1859
+ className: settings_card_module_css_default.description,
1860
+ children: t("settings.description")
1861
+ })]
1862
+ }),
1863
+ state.dirty ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1864
+ className: settings_card_module_css_default.pending,
1865
+ children: t("settings.unsaved")
1866
+ }) : null,
1867
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1868
+ className: open ? settings_card_module_css_default.chevronOpen : settings_card_module_css_default.chevron,
1869
+ children: "▾"
1870
+ })
1871
+ ]
1872
+ }), !open ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1873
+ className: settings_card_module_css_default.body,
1874
+ children: [
1875
+ !state.writable ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1876
+ className: settings_card_module_css_default.readOnly,
1877
+ role: "status",
1878
+ children: t("settings.readOnly")
1879
+ }) : null,
1880
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
1881
+ className: settings_card_module_css_default.channelSection,
1882
+ "aria-label": t("channels.title"),
1883
+ children: [
1884
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1885
+ className: settings_card_module_css_default.sectionHeader,
1886
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
1887
+ className: settings_card_module_css_default.sectionTitle,
1888
+ children: t("channels.title")
1889
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1890
+ className: settings_card_module_css_default.sectionHint,
1891
+ children: t("channels.hint")
1892
+ })] })
1893
+ }),
1894
+ channels.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1895
+ className: settings_card_module_css_default.channelEmpty,
1896
+ children: t("channels.empty")
1897
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
1898
+ className: settings_card_module_css_default.channelList,
1899
+ children: channels.map((channel) => {
1900
+ const keyHeld = state.channels.keySet[channel.id] === true;
1901
+ const ready = keyHeld && channel.models.length > 0;
1902
+ const isDefault = channel.id === state.channels.defaultChannelId;
1903
+ if (confirmDeleteId === channel.id) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
1904
+ className: settings_card_module_css_default.channelRow,
1905
+ "data-action": true,
1906
+ children: [
1907
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1908
+ className: settings_card_module_css_default.deleteConfirmText,
1909
+ children: [
1910
+ t("channels.confirm"),
1911
+ ": ",
1912
+ channel.name || t("channels.untitled")
1913
+ ]
1914
+ }),
1915
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1916
+ type: "button",
1917
+ className: settings_card_module_css_default.channelDanger,
1918
+ disabled: !state.writable,
1919
+ onClick: () => {
1920
+ props.channels.setChannels(channels.filter((candidate) => candidate.id !== channel.id));
1921
+ if (isDefault && channels.length > 1) {
1922
+ const next = channels.find((candidate) => candidate.id !== channel.id);
1923
+ if (next !== void 0) props.channels.setDefaultChannel(next.id);
1924
+ }
1925
+ setConfirmDeleteId(null);
1926
+ if (editingId === channel.id) setEditingId(null);
1927
+ },
1928
+ children: t("channels.confirm")
1929
+ }),
1930
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1931
+ type: "button",
1932
+ className: settings_card_module_css_default.channelAction,
1933
+ onClick: () => setConfirmDeleteId(null),
1934
+ children: t("channels.cancel")
1935
+ })
1936
+ ]
1937
+ }, channel.id);
1938
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
1939
+ className: settings_card_module_css_default.channelRow,
1940
+ children: [
1941
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1942
+ className: ready ? settings_card_module_css_default.channelDotReady : settings_card_module_css_default.channelDotWarn,
1943
+ "aria-hidden": "true",
1944
+ title: t(ready ? "channels.statusReady" : "channels.statusIncomplete")
1945
+ }),
1946
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1947
+ type: "button",
1948
+ className: settings_card_module_css_default.channelMain,
1949
+ disabled: !state.writable,
1950
+ onClick: () => setEditingId(channel.id),
1951
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1952
+ className: settings_card_module_css_default.channelName,
1953
+ children: isDefault ? `★ ${channel.name || t("channels.untitled")}` : channel.name || t("channels.untitled")
1954
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1955
+ className: settings_card_module_css_default.channelMeta,
1956
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1957
+ className: settings_card_module_css_default.channelHost,
1958
+ children: channel.apiUrl || "(no url)"
1959
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1960
+ className: settings_card_module_css_default.channelBadge,
1961
+ "data-warn": !keyHeld || channel.models.length === 0 ? "" : void 0,
1962
+ children: [
1963
+ keyHeld ? t("channels.keySet") : t("channels.keyMissing"),
1964
+ " · ",
1965
+ channel.models.length > 0 ? t("channels.modelCount", { n: channel.models.length }) : t("channels.noModels")
1966
+ ]
1967
+ })]
1968
+ })]
1969
+ }),
1970
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1971
+ type: "button",
1972
+ className: settings_card_module_css_default.channelAction,
1973
+ onClick: () => setEditingId(channel.id),
1974
+ children: t("channels.edit")
1975
+ }),
1976
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1977
+ type: "button",
1978
+ className: settings_card_module_css_default.channelAction,
1979
+ "data-danger": true,
1980
+ onClick: () => setConfirmDeleteId(channel.id),
1981
+ children: t("channels.delete")
1982
+ })
1983
+ ]
1984
+ }, channel.id);
1985
+ })
1986
+ }),
1987
+ presetPickerOpen ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1988
+ className: settings_card_module_css_default.channelControls,
1989
+ children: [
1990
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1991
+ className: settings_card_module_css_default.sectionHint,
1992
+ children: t("presets.title")
1993
+ }),
1994
+ presetError !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1995
+ className: settings_card_module_css_default.failed,
1996
+ children: presetError
1997
+ }) : null,
1998
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1999
+ className: settings_card_module_css_default.channelAddRow,
2000
+ children: [
2001
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2002
+ type: "button",
2003
+ className: settings_card_module_css_default.channelAdd,
2004
+ onClick: () => {
2005
+ setPresets([]);
2006
+ setPresetError(null);
2007
+ fetch(PRESETS_API, { method: "POST" }).then(async (response) => {
2008
+ const body = await response.json();
2009
+ if (!response.ok || body.ok !== true || body.presets === void 0) throw new Error(body.message ?? `HTTP ${response.status}`);
2010
+ setPresets(body.presets);
2011
+ }).catch((error) => setPresetError(error instanceof Error ? error.message : String(error)));
2012
+ },
2013
+ children: t("channels.addProvider")
2014
+ }),
2015
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2016
+ type: "button",
2017
+ className: settings_card_module_css_default.channelAdd,
2018
+ onClick: () => {
2019
+ const draft = newChannelDraft(void 0, channels);
2020
+ props.channels.setChannels([...channels, draft]);
2021
+ setPresetPickerOpen(false);
2022
+ setEditingId(draft.id);
2023
+ },
2024
+ children: t("channels.addCustom")
2025
+ }),
2026
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2027
+ type: "button",
2028
+ className: settings_card_module_css_default.channelAction,
2029
+ onClick: () => setPresetPickerOpen(false),
2030
+ children: "×"
2031
+ })
2032
+ ]
2033
+ }),
2034
+ presets.map((preset) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
2035
+ type: "button",
2036
+ className: settings_card_module_css_default.channelAdd,
2037
+ onClick: () => {
2038
+ const draft = newChannelDraft(preset, channels);
2039
+ props.channels.setChannels([...channels, draft]);
2040
+ setPresetPickerOpen(false);
2041
+ setEditingId(draft.id);
2042
+ },
2043
+ children: [
2044
+ preset.name,
2045
+ " — ",
2046
+ preset.hint
2047
+ ]
2048
+ }, preset.id))
2049
+ ]
2050
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2051
+ className: settings_card_module_css_default.channelAddRow,
2052
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2053
+ type: "button",
2054
+ className: settings_card_module_css_default.channelAdd,
2055
+ disabled: !state.writable,
2056
+ onClick: () => {
2057
+ setPresetError(null);
2058
+ setPresetPickerOpen(true);
2059
+ },
2060
+ children: t("channels.addProvider")
2061
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2062
+ type: "button",
2063
+ className: settings_card_module_css_default.channelAdd,
2064
+ disabled: !state.writable,
2065
+ onClick: () => {
2066
+ const draft = newChannelDraft(void 0, channels);
2067
+ props.channels.setChannels([...channels, draft]);
2068
+ setEditingId(draft.id);
2069
+ },
2070
+ children: t("channels.addCustom")
2071
+ })]
2072
+ })
2073
+ ]
2074
+ }),
2075
+ editing !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2076
+ className: settings_card_module_css_default.body,
2077
+ children: [
2078
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2079
+ className: settings_card_module_css_default.field,
2080
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
2081
+ className: settings_card_module_css_default.label,
2082
+ htmlFor: `audiogen-name-${editing.id}`,
2083
+ children: t("channel.name")
2084
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2085
+ id: `audiogen-name-${editing.id}`,
2086
+ className: settings_card_module_css_default.input,
2087
+ value: editName,
2088
+ onChange: (event) => setEditName(event.target.value)
2089
+ })]
2090
+ }),
2091
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2092
+ className: settings_card_module_css_default.field,
2093
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
2094
+ className: settings_card_module_css_default.label,
2095
+ htmlFor: `audiogen-url-${editing.id}`,
2096
+ children: t("channel.apiUrl")
2097
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2098
+ id: `audiogen-url-${editing.id}`,
2099
+ className: settings_card_module_css_default.input,
2100
+ value: editUrl,
2101
+ onChange: (event) => setEditUrl(event.target.value),
2102
+ placeholder: "https://…"
2103
+ })]
2104
+ }),
2105
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2106
+ className: settings_card_module_css_default.field,
2107
+ children: [
2108
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
2109
+ className: settings_card_module_css_default.label,
2110
+ htmlFor: `audiogen-key-${editing.id}`,
2111
+ children: t("channel.apiKey")
2112
+ }),
2113
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2114
+ id: `audiogen-key-${editing.id}`,
2115
+ className: settings_card_module_css_default.input,
2116
+ type: "password",
2117
+ value: editKey,
2118
+ onChange: (event) => setEditKey(event.target.value),
2119
+ placeholder: state.channels.keySet[editing.id] ? "••••••" : ""
2120
+ }),
2121
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2122
+ className: settings_card_module_css_default.sectionHint,
2123
+ children: t("channel.apiKeyHint")
2124
+ })
2125
+ ]
2126
+ }),
2127
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2128
+ className: settings_card_module_css_default.field,
2129
+ children: [
2130
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
2131
+ className: settings_card_module_css_default.label,
2132
+ htmlFor: `audiogen-models-${editing.id}`,
2133
+ children: t("channel.models")
2134
+ }),
2135
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
2136
+ id: `audiogen-models-${editing.id}`,
2137
+ className: settings_card_module_css_default.textarea,
2138
+ value: editModels,
2139
+ onChange: (event) => setEditModels(event.target.value)
2140
+ }),
2141
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2142
+ className: settings_card_module_css_default.sectionHint,
2143
+ children: t("channel.modelsHint")
2144
+ })
2145
+ ]
2146
+ }),
2147
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
2148
+ className: settings_card_module_css_default.label,
2149
+ children: [
2150
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2151
+ type: "checkbox",
2152
+ checked: editDefault,
2153
+ onChange: (event) => setEditDefault(event.target.checked)
2154
+ }),
2155
+ " ",
2156
+ t("channel.default")
2157
+ ]
2158
+ }),
2159
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2160
+ className: settings_card_module_css_default.footer,
2161
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2162
+ type: "button",
2163
+ className: settings_card_module_css_default.discard,
2164
+ onClick: () => setEditingId(null),
2165
+ children: t("channel.cancel")
2166
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2167
+ type: "button",
2168
+ className: settings_card_module_css_default.save,
2169
+ onClick: saveEdit,
2170
+ children: t("channel.save")
2171
+ })]
2172
+ })
2173
+ ]
2174
+ }) : null,
2175
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2176
+ className: settings_card_module_css_default.field,
2177
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
2178
+ className: settings_card_module_css_default.label,
2179
+ children: [
2180
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2181
+ type: "checkbox",
2182
+ checked: state.enabled.text === "true" || state.enabled.text === "",
2183
+ disabled: !state.writable,
2184
+ onChange: (event) => props.edit("enabled", String(event.target.checked))
2185
+ }),
2186
+ " ",
2187
+ t("settings.enabled")
2188
+ ]
2189
+ })
2190
+ }),
2191
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2192
+ className: settings_card_module_css_default.field,
2193
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
2194
+ className: settings_card_module_css_default.label,
2195
+ children: [
2196
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2197
+ type: "checkbox",
2198
+ checked: state.announceToAgent.text === "true" || state.announceToAgent.text === "",
2199
+ disabled: !state.writable,
2200
+ onChange: (event) => props.edit("announceToAgent", String(event.target.checked))
2201
+ }),
2202
+ " ",
2203
+ t("settings.announceToAgent")
2204
+ ]
2205
+ })
2206
+ }),
2207
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2208
+ className: settings_card_module_css_default.field,
2209
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
2210
+ className: settings_card_module_css_default.label,
2211
+ children: [
2212
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2213
+ type: "checkbox",
2214
+ checked: state.allowAgentAudioGeneration.text === "true" || state.allowAgentAudioGeneration.text === "",
2215
+ disabled: !state.writable,
2216
+ onChange: (event) => props.edit("allowAgentAudioGeneration", String(event.target.checked))
2217
+ }),
2218
+ " ",
2219
+ t("settings.allowAgentAudio")
2220
+ ]
2221
+ })
2222
+ }),
2223
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2224
+ className: settings_card_module_css_default.footer,
2225
+ children: [
2226
+ state.failed ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2227
+ className: settings_card_module_css_default.failed,
2228
+ children: "保存失败"
2229
+ }) : null,
2230
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2231
+ type: "button",
2232
+ className: settings_card_module_css_default.discard,
2233
+ disabled: !state.dirty || state.saving,
2234
+ onClick: () => props.discard(),
2235
+ children: t("settings.discard")
2236
+ }),
2237
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2238
+ type: "button",
2239
+ className: settings_card_module_css_default.save,
2240
+ disabled: blocked,
2241
+ onClick: () => {
2242
+ props.save();
2243
+ props.channels.commit();
2244
+ },
2245
+ children: state.saving ? t("settings.saving") : t("settings.save")
2246
+ })
2247
+ ]
2248
+ })
2249
+ ]
2250
+ })]
2251
+ });
2252
+ }
2253
+ //#endregion
2254
+ //#region \0dsh-css:/Users/shimingming/Projects_code/dsh-audiogen/src/client/audio-toolview.module.css.mjs
2255
+ const css = "._7K1QKa_root{border:1px solid var(--dsw-color-border,#e5e7eb);background:var(--dsw-color-surface,#fff);border-radius:10px;min-width:0;padding:10px 12px;display:block}._7K1QKa_header{align-items:center;gap:8px;font-weight:600;display:flex}._7K1QKa_icon{color:var(--dsw-alias-label-secondary,#6b7280)}._7K1QKa_status{color:var(--dsw-alias-label-caption,#9ca3af);margin-left:auto;font-size:12px;font-weight:400}._7K1QKa_message{color:var(--dsw-alias-label-secondary,#6b7280);white-space:pre-wrap;margin:6px 0 0;font-size:13px}._7K1QKa_error{color:#b91c1c;margin:6px 0 0}._7K1QKa_audios{gap:8px;margin-top:8px;display:grid}._7K1QKa_audioRow{align-items:center;gap:8px;display:flex}._7K1QKa_audio{flex:1;width:100%;min-width:0;height:36px}._7K1QKa_download{color:#2563eb;white-space:nowrap;font-size:12px;text-decoration:none}._7K1QKa_empty{color:var(--dsw-alias-label-caption,#9ca3af);font-size:13px}";
2256
+ const tagId = "dsh-audiogen/audio-toolview.module.css";
2257
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
2258
+ const tag = document.createElement("style");
2259
+ tag.dataset.plugin = "dsh-audiogen";
2260
+ tag.dataset.pluginCss = tagId;
2261
+ tag.textContent = css;
2262
+ document.head.appendChild(tag);
2263
+ }
2264
+ var audio_toolview_module_css_default = {
2265
+ "error": "_7K1QKa_error",
2266
+ "audio": "_7K1QKa_audio",
2267
+ "download": "_7K1QKa_download",
2268
+ "audioRow": "_7K1QKa_audioRow",
2269
+ "empty": "_7K1QKa_empty",
2270
+ "status": "_7K1QKa_status",
2271
+ "audios": "_7K1QKa_audios",
2272
+ "root": "_7K1QKa_root",
2273
+ "icon": "_7K1QKa_icon",
2274
+ "header": "_7K1QKa_header",
2275
+ "message": "_7K1QKa_message"
2276
+ };
2277
+ //#endregion
2278
+ //#region src/client/audio-toolview.tsx
2279
+ function isSettled(block) {
2280
+ return typeof block === "object" && block !== null && "content" in block;
2281
+ }
2282
+ function textOf(block) {
2283
+ if (!isSettled(block)) return "";
2284
+ return (block.content ?? []).filter((content) => content.type === "text" && typeof content.text === "string").map((content) => content.text).join("\n");
2285
+ }
2286
+ function parseResult(block) {
2287
+ const text = textOf(block);
2288
+ if (text === "") return {
2289
+ status: "running",
2290
+ message: "正在生成音频…",
2291
+ audio: []
2292
+ };
2293
+ try {
2294
+ const parsed = JSON.parse(text);
2295
+ return {
2296
+ status: parsed.status ?? "completed",
2297
+ message: parsed.message ?? "",
2298
+ audio: Array.isArray(parsed.audio) ? parsed.audio.filter((item) => {
2299
+ return typeof item === "object" && item !== null && typeof item.url === "string";
2300
+ }) : [],
2301
+ ...typeof parsed.error === "string" ? { error: parsed.error } : {}
2302
+ };
2303
+ } catch {
2304
+ return {
2305
+ status: "completed",
2306
+ message: text,
2307
+ audio: []
2308
+ };
2309
+ }
2310
+ }
2311
+ function statusLabel(status) {
2312
+ if (status === "running" || status === "queued") return "生成中";
2313
+ if (status === "failed") return "生成失败";
2314
+ if (status === "cancelled") return "已取消";
2315
+ return "音频结果";
2316
+ }
2317
+ function mimeExt(mime) {
2318
+ if (mime.includes("wav")) return "wav";
2319
+ if (mime.includes("flac")) return "flac";
2320
+ if (mime.includes("ogg")) return "ogg";
2321
+ if (mime.includes("mp4") || mime.includes("m4a")) return "m4a";
2322
+ return "mp3";
2323
+ }
2324
+ function registerAudioToolviews(ctx) {
2325
+ const AudioToolView = (props) => {
2326
+ const result = (0, react.useMemo)(() => parseResult(props.block), [props.block]);
2327
+ const [active, setActive] = (0, react.useState)(null);
2328
+ (0, react.useEffect)(() => {
2329
+ setActive(null);
2330
+ }, [props.callId]);
2331
+ const title = props.toolName === "generate_audio" ? "生成音频" : props.toolName;
2332
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
2333
+ className: audio_toolview_module_css_default.root,
2334
+ "data-state": result.status,
2335
+ "data-tool": props.toolName,
2336
+ children: [
2337
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
2338
+ className: audio_toolview_module_css_default.header,
2339
+ children: [
2340
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2341
+ className: audio_toolview_module_css_default.icon,
2342
+ "aria-hidden": "true",
2343
+ children: "♫"
2344
+ }),
2345
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: title }),
2346
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2347
+ className: audio_toolview_module_css_default.status,
2348
+ children: statusLabel(result.status)
2349
+ })
2350
+ ]
2351
+ }),
2352
+ result.message !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2353
+ className: audio_toolview_module_css_default.message,
2354
+ children: result.message
2355
+ }),
2356
+ result.error !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2357
+ className: audio_toolview_module_css_default.error,
2358
+ children: result.error
2359
+ }),
2360
+ result.audio.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2361
+ className: audio_toolview_module_css_default.audios,
2362
+ children: result.audio.map((audio, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2363
+ className: audio_toolview_module_css_default.audioRow,
2364
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("audio", {
2365
+ className: audio_toolview_module_css_default.audio,
2366
+ controls: true,
2367
+ preload: "metadata",
2368
+ src: audio.url,
2369
+ onPlay: () => setActive(audio.url)
2370
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
2371
+ className: audio_toolview_module_css_default.download,
2372
+ href: audio.url,
2373
+ download: `generated-${index + 1}.${mimeExt(audio.mime)}`,
2374
+ children: "下载"
2375
+ })]
2376
+ }, audio.id ?? audio.url))
2377
+ }),
2378
+ result.audio.length === 0 && result.status !== "running" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2379
+ className: audio_toolview_module_css_default.empty,
2380
+ children: "未返回可播放的音频。"
2381
+ })
2382
+ ]
2383
+ });
2384
+ };
2385
+ ctx.slots.inject("tool.call.toolview", function* () {
2386
+ yield ctx.slots.register({
2387
+ name: "tool.call.toolview",
2388
+ key: "generate_audio"
2389
+ }, AudioToolView);
2390
+ });
2391
+ }
2392
+ //#endregion
2393
+ //#region src/client/index.ts
2394
+ const NS = "dsh-audiogen";
2395
+ const inject = [
2396
+ "slots",
2397
+ "locale",
2398
+ "connection",
2399
+ "sessions"
2400
+ ];
2401
+ function apply(ctx) {
2402
+ ctx.effect(() => ctx.locale.register(NS, {
2403
+ zh,
2404
+ en
2405
+ }), "dsh-audiogen: dictionaries");
2406
+ registerAudioToolviews(ctx);
2407
+ const scope = bindAudiogenScope(ctx.get("connection")?.isLoopback === true ? (input, init) => fetch(input, init) : () => {
2408
+ throw new Error("settings bridge is loopback-only");
2409
+ });
2410
+ ctx.effect(() => {
2411
+ const disposers = [ctx.on("connection/reset", () => {
2412
+ scope.load();
2413
+ })];
2414
+ return () => {
2415
+ for (const dispose of disposers) dispose();
2416
+ };
2417
+ }, "dsh-audiogen: settings scope invalidation");
2418
+ const settingsCard = new AudioGenSettingsCardController(scope);
2419
+ ctx.slots.inject("settings.plugin.item", () => ctx.slots.register({
2420
+ name: "settings.plugin.item",
2421
+ key: "dsh-audiogen",
2422
+ locale: NS,
2423
+ inject: () => settingsCard.inject()
2424
+ }, AudioGenSettingsCard));
2425
+ let uiDisposer;
2426
+ const mountUi = () => {
2427
+ if (uiDisposer !== void 0) return;
2428
+ const controller = new AudioGenController();
2429
+ const api = new AudiogenApi();
2430
+ const disposers = [];
2431
+ try {
2432
+ disposers.push(mountSidebarEntry(controller, tt("entry.label"), tt("entry.tooltip")));
2433
+ disposers.push(mountPanel(controller, api, scope));
2434
+ } catch (error) {
2435
+ console.warn("[dsh-audiogen] mount failed:", error);
2436
+ }
2437
+ uiDisposer = () => {
2438
+ for (const dispose of disposers.splice(0)) dispose();
2439
+ uiDisposer = void 0;
2440
+ };
2441
+ };
2442
+ const syncEnabled = () => {
2443
+ const snapshot = scope.getSnapshot();
2444
+ if (snapshot.status === "ready" ? snapshot.value?.enabled ?? true : snapshot.status === "unavailable") mountUi();
2445
+ else uiDisposer?.();
2446
+ };
2447
+ scope.subscribe(syncEnabled);
2448
+ syncEnabled();
2449
+ }
2450
+ //#endregion
2451
+ exports.apply = apply;
2452
+ exports.inject = inject;
2453
+ return module.exports;
2454
+ }
2455
+ });
2456
+
2457
+ //# sourceMappingURL=client.js.map