@dsh-plus/image-studio 0.1.22 → 0.1.24
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 +680 -602
- package/package.json +2 -2
- package/src/client/card.tsx +142 -480
- package/src/client/client.ts +2 -2
- package/src/client/draft.ts +128 -0
- package/src/client/preset-sections.tsx +321 -0
- package/src/client/styles.ts +3 -4
- package/src/ns.ts +2 -2
package/lib/client.js
CHANGED
|
@@ -4,9 +4,175 @@ window.__ModuleLoader__.load({
|
|
|
4
4
|
var module = { exports: {} };
|
|
5
5
|
var exports = module.exports;
|
|
6
6
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7
|
-
let react = require("react");
|
|
8
7
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
8
|
+
let react = require("react");
|
|
9
9
|
let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
10
|
+
//#region ../shared/src/client/card.tsx
|
|
11
|
+
const IDLE_STATUS = {
|
|
12
|
+
kind: "idle",
|
|
13
|
+
text: ""
|
|
14
|
+
};
|
|
15
|
+
function CardChrome(props) {
|
|
16
|
+
const p = props.prefix;
|
|
17
|
+
const badge = props.statusBadge;
|
|
18
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
19
|
+
className: `${p}-section`,
|
|
20
|
+
"aria-label": props.title,
|
|
21
|
+
children: [
|
|
22
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
23
|
+
className: `${p}-sectionHead`,
|
|
24
|
+
children: [
|
|
25
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
26
|
+
className: `${p}-sectionTitle`,
|
|
27
|
+
children: props.title
|
|
28
|
+
}),
|
|
29
|
+
badge !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
30
|
+
className: `${p}-sectionBadge ${badge.on ? `${p}-statusOn` : `${p}-statusOff`}`,
|
|
31
|
+
children: badge.text
|
|
32
|
+
}) : null,
|
|
33
|
+
props.dirty ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
34
|
+
className: `${p}-pending`,
|
|
35
|
+
children: props.dirtyLabel
|
|
36
|
+
}) : null
|
|
37
|
+
]
|
|
38
|
+
}),
|
|
39
|
+
props.description === "" ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
40
|
+
className: `${p}-sectionDesc`,
|
|
41
|
+
children: props.description
|
|
42
|
+
}),
|
|
43
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
44
|
+
className: `${p}-sectionBody`,
|
|
45
|
+
children: [
|
|
46
|
+
props.readOnlyNotice !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
47
|
+
className: `${p}-readOnly`,
|
|
48
|
+
role: "status",
|
|
49
|
+
children: props.readOnlyNotice
|
|
50
|
+
}) : null,
|
|
51
|
+
props.children,
|
|
52
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
53
|
+
className: `${p}-sectionFooter`,
|
|
54
|
+
children: [props.status !== void 0 && props.status.kind !== "idle" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
55
|
+
className: `${p}-status${props.status.kind === "error" ? ` ${p}-statusError` : ""}`,
|
|
56
|
+
role: "status",
|
|
57
|
+
children: props.status.text
|
|
58
|
+
}) : null, props.actions.map((action) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
59
|
+
type: "button",
|
|
60
|
+
className: `${p}-btn ${action.variant === "primary" ? `${p}-btnPrimary` : `${p}-btnGhost`}`,
|
|
61
|
+
disabled: action.disabled === true,
|
|
62
|
+
onClick: action.onClick,
|
|
63
|
+
children: action.label
|
|
64
|
+
}, action.key))]
|
|
65
|
+
})
|
|
66
|
+
]
|
|
67
|
+
})
|
|
68
|
+
]
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
/** 取值未到达时的占位:与 CardChrome 同形态,避免宿主页里闪一次卡片外壳。 */
|
|
72
|
+
function CardLoading(props) {
|
|
73
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("section", {
|
|
74
|
+
className: `${props.prefix}-section`,
|
|
75
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
76
|
+
className: `${props.prefix}-readOnly`,
|
|
77
|
+
role: "status",
|
|
78
|
+
children: props.text
|
|
79
|
+
})
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
//#endregion
|
|
83
|
+
//#region ../shared/src/client/config-slots.ts
|
|
84
|
+
/** dsh-plus 聚合 bundle 的包名(各插件 row 均经其补丁层插入 profile)。 */
|
|
85
|
+
const DSH_PLUS_BUNDLE = "@dsh-plus/bundle-main";
|
|
86
|
+
/**
|
|
87
|
+
* 把一张配置卡片注册进插件页的两个配置槽位。
|
|
88
|
+
* 两个槽位都由 0.2.1-alpha.1 的插件页声明,任一缺席即报错——不做版本探测。
|
|
89
|
+
*/
|
|
90
|
+
function injectPluginConfigCard(slots, reg) {
|
|
91
|
+
const base = {
|
|
92
|
+
locale: reg.ns,
|
|
93
|
+
inject: reg.inject
|
|
94
|
+
};
|
|
95
|
+
slots.inject("plugins.row.config", () => slots.register({
|
|
96
|
+
name: "plugins.row.config",
|
|
97
|
+
key: `${DSH_PLUS_BUNDLE}#${reg.rowId}`,
|
|
98
|
+
...base
|
|
99
|
+
}, reg.component));
|
|
100
|
+
slots.inject("plugins.bundle.config", () => slots.register({
|
|
101
|
+
name: "plugins.bundle.config",
|
|
102
|
+
key: reg.pkg,
|
|
103
|
+
...base
|
|
104
|
+
}, reg.component));
|
|
105
|
+
}
|
|
106
|
+
//#endregion
|
|
107
|
+
//#region ../shared/src/client/draft.ts
|
|
108
|
+
/**
|
|
109
|
+
* 单命名空间 staged 草稿(简单配置卡片共用):读 scope 快照、播种草稿、
|
|
110
|
+
* 脏判定、保存(revision fencing + 重新装载)与状态行。
|
|
111
|
+
*
|
|
112
|
+
* 语义与官方 staged 表单一致:Host 更新不覆盖在途编辑,只有保存才写回;
|
|
113
|
+
* 保存成功后按 Host 解析值重置草稿。`from`/`patch` 参与 effect 与 useMemo
|
|
114
|
+
* 依赖,必须是模块级稳定函数(不要内联箭头函数)。
|
|
115
|
+
* @module @dsh-plus/shared/client/draft
|
|
116
|
+
*/
|
|
117
|
+
//#endregion
|
|
118
|
+
//#region ../shared/src/client/fetch.ts
|
|
119
|
+
async function parse(res, guard) {
|
|
120
|
+
const body = await res.json();
|
|
121
|
+
if (!res.ok) throw new Error(body.error ?? `HTTP ${res.status}`);
|
|
122
|
+
if (guard !== void 0 && !guard(body)) throw new Error(`响应结构不符合预期(${res.url})`);
|
|
123
|
+
return body;
|
|
124
|
+
}
|
|
125
|
+
/** GET 同源 JSON 端点。 */
|
|
126
|
+
async function getJson(route, guard) {
|
|
127
|
+
return parse(await fetch(route, { credentials: "same-origin" }), guard);
|
|
128
|
+
}
|
|
129
|
+
/** POST 同源 JSON 端点(body 可省略)。 */
|
|
130
|
+
async function postJson(route, body, guard) {
|
|
131
|
+
return parse(await fetch(route, {
|
|
132
|
+
method: "POST",
|
|
133
|
+
credentials: "same-origin",
|
|
134
|
+
headers: { "content-type": "application/json" },
|
|
135
|
+
...body === void 0 ? {} : { body: JSON.stringify(body) }
|
|
136
|
+
}), guard);
|
|
137
|
+
}
|
|
138
|
+
//#endregion
|
|
139
|
+
//#region ../shared/src/client/fields.tsx
|
|
140
|
+
function TextField(props) {
|
|
141
|
+
const p = props.prefix ?? "dshp";
|
|
142
|
+
const invalid = props.invalid === true;
|
|
143
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
144
|
+
className: `${p}-field`,
|
|
145
|
+
children: [
|
|
146
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
147
|
+
className: `${p}-head`,
|
|
148
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
|
|
149
|
+
className: `${p}-label`,
|
|
150
|
+
htmlFor: props.id,
|
|
151
|
+
children: props.label
|
|
152
|
+
}), props.badge !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
153
|
+
className: `${p}-badge ${props.badge.set ? `${p}-badgeSet` : `${p}-badgeUnset`}`,
|
|
154
|
+
children: props.badge.text
|
|
155
|
+
}) : null]
|
|
156
|
+
}),
|
|
157
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
158
|
+
id: props.id,
|
|
159
|
+
className: `${p}-input${invalid ? ` ${p}-inputInvalid` : ""}`,
|
|
160
|
+
type: props.password === true ? "password" : "text",
|
|
161
|
+
inputMode: props.numeric === true ? "numeric" : void 0,
|
|
162
|
+
autoComplete: props.password === true ? "off" : void 0,
|
|
163
|
+
"aria-invalid": invalid || void 0,
|
|
164
|
+
value: props.value,
|
|
165
|
+
disabled: props.disabled === true,
|
|
166
|
+
onChange: (event) => props.onEdit(event.target.value)
|
|
167
|
+
}),
|
|
168
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
169
|
+
className: invalid ? `${p}-invalid` : `${p}-hint`,
|
|
170
|
+
children: invalid ? props.invalidLabel ?? "" : props.hint ?? ""
|
|
171
|
+
})
|
|
172
|
+
]
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
//#endregion
|
|
10
176
|
//#region ../../node_modules/.pnpm/lucide-react@1.38.0_react@18.2.0/node_modules/lucide-react/dist/esm/shared/src/utils/mergeClasses.mjs
|
|
11
177
|
/**
|
|
12
178
|
* @license lucide-react v1.38.0 - ISC
|
|
@@ -272,158 +438,6 @@ window.__ModuleLoader__.load({
|
|
|
272
438
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Layers, { ...lucideProps(props) });
|
|
273
439
|
}
|
|
274
440
|
//#endregion
|
|
275
|
-
//#region ../shared/src/client/card.tsx
|
|
276
|
-
const IDLE_STATUS = {
|
|
277
|
-
kind: "idle",
|
|
278
|
-
text: ""
|
|
279
|
-
};
|
|
280
|
-
function CardChrome(props) {
|
|
281
|
-
const p = props.prefix;
|
|
282
|
-
const badge = props.statusBadge;
|
|
283
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
|
|
284
|
-
className: `${p}-card${props.open ? ` ${p}-cardOpen` : ""}`,
|
|
285
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
286
|
-
type: "button",
|
|
287
|
-
className: `${p}-header`,
|
|
288
|
-
"aria-expanded": props.open,
|
|
289
|
-
"aria-label": props.title,
|
|
290
|
-
onClick: () => props.onToggle(!props.open),
|
|
291
|
-
children: [
|
|
292
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
293
|
-
className: `${p}-headText`,
|
|
294
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
295
|
-
className: `${p}-name`,
|
|
296
|
-
children: props.title
|
|
297
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
298
|
-
className: `${p}-description`,
|
|
299
|
-
children: props.description
|
|
300
|
-
})]
|
|
301
|
-
}),
|
|
302
|
-
badge !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
303
|
-
className: `${p}-statusBadge ${badge.on ? `${p}-statusOn` : `${p}-statusOff`}`,
|
|
304
|
-
children: badge.text
|
|
305
|
-
}) : null,
|
|
306
|
-
props.dirty ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
307
|
-
className: `${p}-pending`,
|
|
308
|
-
children: props.dirtyLabel
|
|
309
|
-
}) : null,
|
|
310
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutlineRegular, { className: `${p}-chevron${props.open ? ` ${p}-chevronOpen` : ""}` })
|
|
311
|
-
]
|
|
312
|
-
}), props.open ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
313
|
-
className: `${p}-body`,
|
|
314
|
-
children: [
|
|
315
|
-
props.readOnlyNotice !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
316
|
-
className: `${p}-readOnly`,
|
|
317
|
-
role: "status",
|
|
318
|
-
children: props.readOnlyNotice
|
|
319
|
-
}) : null,
|
|
320
|
-
props.children,
|
|
321
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
322
|
-
className: `${p}-footer`,
|
|
323
|
-
children: [props.status !== void 0 && props.status.kind !== "idle" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
324
|
-
className: `${p}-status${props.status.kind === "error" ? ` ${p}-statusError` : ""}`,
|
|
325
|
-
role: "status",
|
|
326
|
-
children: props.status.text
|
|
327
|
-
}) : null, props.actions.map((action) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
328
|
-
type: "button",
|
|
329
|
-
className: `${p}-btn ${action.variant === "primary" ? `${p}-btnPrimary` : `${p}-btnGhost`}`,
|
|
330
|
-
disabled: action.disabled === true,
|
|
331
|
-
onClick: action.onClick,
|
|
332
|
-
children: action.label
|
|
333
|
-
}, action.key))]
|
|
334
|
-
})
|
|
335
|
-
]
|
|
336
|
-
}) : null]
|
|
337
|
-
});
|
|
338
|
-
}
|
|
339
|
-
//#endregion
|
|
340
|
-
//#region ../shared/src/client/config-slots.ts
|
|
341
|
-
/** dsh-plus 聚合 bundle 的包名(各插件 row 均经其补丁层插入 profile)。 */
|
|
342
|
-
const DSH_PLUS_BUNDLE = "@dsh-plus/bundle-main";
|
|
343
|
-
/**
|
|
344
|
-
* 把一张配置卡片同时注册进 legacy 与新版三个槽位。
|
|
345
|
-
* 任一槽位在当前 dsh 版本未声明时对应 inject 挂起等待,版本不命中即 no-op。
|
|
346
|
-
*/
|
|
347
|
-
function injectPluginConfigCard(slots, reg) {
|
|
348
|
-
const base = {
|
|
349
|
-
locale: reg.ns,
|
|
350
|
-
inject: reg.inject
|
|
351
|
-
};
|
|
352
|
-
slots.inject("settings.plugin.item", () => slots.register({
|
|
353
|
-
name: "settings.plugin.item",
|
|
354
|
-
key: reg.ns,
|
|
355
|
-
...base
|
|
356
|
-
}, reg.component));
|
|
357
|
-
slots.inject("plugins.row.config", () => slots.register({
|
|
358
|
-
name: "plugins.row.config",
|
|
359
|
-
key: `${DSH_PLUS_BUNDLE}#${reg.rowId}`,
|
|
360
|
-
...base
|
|
361
|
-
}, reg.component));
|
|
362
|
-
slots.inject("plugins.bundle.config", () => slots.register({
|
|
363
|
-
name: "plugins.bundle.config",
|
|
364
|
-
key: reg.pkg,
|
|
365
|
-
...base
|
|
366
|
-
}, reg.component));
|
|
367
|
-
}
|
|
368
|
-
//#endregion
|
|
369
|
-
//#region ../shared/src/client/fetch.ts
|
|
370
|
-
async function parse(res, guard) {
|
|
371
|
-
const body = await res.json();
|
|
372
|
-
if (!res.ok) throw new Error(body.error ?? `HTTP ${res.status}`);
|
|
373
|
-
if (guard !== void 0 && !guard(body)) throw new Error(`响应结构不符合预期(${res.url})`);
|
|
374
|
-
return body;
|
|
375
|
-
}
|
|
376
|
-
/** GET 同源 JSON 端点。 */
|
|
377
|
-
async function getJson(route, guard) {
|
|
378
|
-
return parse(await fetch(route, { credentials: "same-origin" }), guard);
|
|
379
|
-
}
|
|
380
|
-
/** POST 同源 JSON 端点(body 可省略)。 */
|
|
381
|
-
async function postJson(route, body, guard) {
|
|
382
|
-
return parse(await fetch(route, {
|
|
383
|
-
method: "POST",
|
|
384
|
-
credentials: "same-origin",
|
|
385
|
-
headers: { "content-type": "application/json" },
|
|
386
|
-
...body === void 0 ? {} : { body: JSON.stringify(body) }
|
|
387
|
-
}), guard);
|
|
388
|
-
}
|
|
389
|
-
//#endregion
|
|
390
|
-
//#region ../shared/src/client/fields.tsx
|
|
391
|
-
function TextField(props) {
|
|
392
|
-
const p = props.prefix ?? "dshp";
|
|
393
|
-
const invalid = props.invalid === true;
|
|
394
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
395
|
-
className: `${p}-field`,
|
|
396
|
-
children: [
|
|
397
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
398
|
-
className: `${p}-head`,
|
|
399
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
|
|
400
|
-
className: `${p}-label`,
|
|
401
|
-
htmlFor: props.id,
|
|
402
|
-
children: props.label
|
|
403
|
-
}), props.badge !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
404
|
-
className: `${p}-badge ${props.badge.set ? `${p}-badgeSet` : `${p}-badgeUnset`}`,
|
|
405
|
-
children: props.badge.text
|
|
406
|
-
}) : null]
|
|
407
|
-
}),
|
|
408
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
409
|
-
id: props.id,
|
|
410
|
-
className: `${p}-input${invalid ? ` ${p}-inputInvalid` : ""}`,
|
|
411
|
-
type: props.password === true ? "password" : "text",
|
|
412
|
-
inputMode: props.numeric === true ? "numeric" : void 0,
|
|
413
|
-
autoComplete: props.password === true ? "off" : void 0,
|
|
414
|
-
"aria-invalid": invalid || void 0,
|
|
415
|
-
value: props.value,
|
|
416
|
-
disabled: props.disabled === true,
|
|
417
|
-
onChange: (event) => props.onEdit(event.target.value)
|
|
418
|
-
}),
|
|
419
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
420
|
-
className: invalid ? `${p}-invalid` : `${p}-hint`,
|
|
421
|
-
children: invalid ? props.invalidLabel ?? "" : props.hint ?? ""
|
|
422
|
-
})
|
|
423
|
-
]
|
|
424
|
-
});
|
|
425
|
-
}
|
|
426
|
-
//#endregion
|
|
427
441
|
//#region ../shared/src/client/scope.ts
|
|
428
442
|
/**
|
|
429
443
|
* remote.settings 直连实现的命名空间 scope(官方配置页 tab 同款模式)。
|
|
@@ -514,33 +528,33 @@ window.__ModuleLoader__.load({
|
|
|
514
528
|
//#endregion
|
|
515
529
|
//#region ../shared/src/client/styles.ts
|
|
516
530
|
/**
|
|
517
|
-
*
|
|
518
|
-
*
|
|
519
|
-
*
|
|
531
|
+
* 配置分节样式工厂(各插件 client 的公共收编版)。
|
|
532
|
+
*
|
|
533
|
+
* 目标宿主 dsh 0.2.1-alpha.1:插件页把配置视图放进已有页面级内边距的容器里
|
|
534
|
+
* (`div.detailSections[data-plugin-config]` 或 `section.detailSection[data-plugin-config]`),
|
|
535
|
+
* 外层无边框——故这里输出的是**无边框分节**(小节标题 13px/600、字段 12px 0、
|
|
536
|
+
* 横向零内边距、页脚非 sticky),度量对齐官方设置页
|
|
537
|
+
* (`@deepseek-ai/dsh-client-ui-primitives` 的 settings-form)。
|
|
538
|
+
*
|
|
539
|
+
* 移动端与可达性(767px 断点 + pointer:coarse,对齐 ui-mobile-fit 约定):
|
|
520
540
|
* - 窄屏 input/textarea 16px(防 iOS 聚焦缩放)、可点目标 ≥44px;
|
|
521
|
-
* -
|
|
522
|
-
* - 展开态 body 底部 sticky 保存栏(长表单滚动中始终可达)。
|
|
541
|
+
* - 页脚按钮等宽换行、状态文字独占一行。
|
|
523
542
|
* 沿用官方 data-plugin / data-plugin-css 约定(HMR 据此卸载),不覆盖上游选择器。
|
|
524
543
|
* @module @dsh-plus/shared/client/styles
|
|
525
544
|
*/
|
|
526
|
-
/**
|
|
545
|
+
/** 生成全套分节 CSS;prefix 为插件类名前缀(如 'dne'),extra 为插件特有规则。 */
|
|
527
546
|
function cardCss(prefix, extra = "") {
|
|
528
547
|
return `
|
|
529
|
-
.${prefix}-
|
|
530
|
-
.${prefix}-
|
|
531
|
-
.${prefix}-
|
|
532
|
-
.${prefix}-
|
|
533
|
-
.${prefix}-
|
|
534
|
-
.${prefix}-
|
|
535
|
-
.${prefix}-
|
|
536
|
-
.${prefix}-description{color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.5}
|
|
537
|
-
.${prefix}-chevron{color:var(--dsw-alias-label-tertiary);flex:none;transition:transform .16s;display:inline-flex}
|
|
538
|
-
.${prefix}-chevronOpen{transform:rotate(180deg)}
|
|
548
|
+
.${prefix}-section{box-sizing:border-box;flex-direction:column;width:100%;min-width:0;padding:16px 0;display:flex}
|
|
549
|
+
.${prefix}-sectionHead{align-items:center;gap:8px;min-width:0;display:flex}
|
|
550
|
+
.${prefix}-sectionTitle{min-width:0;color:var(--dsw-alias-label-primary);flex:1;margin:0;font-size:13px;font-weight:600;line-height:1.5}
|
|
551
|
+
.${prefix}-sectionBadge{white-space:nowrap;border-radius:999px;flex:none;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}
|
|
552
|
+
.${prefix}-sectionDesc{color:var(--dsw-alias-label-tertiary);margin:4px 0 0;font-size:12px;line-height:1.5}
|
|
553
|
+
.${prefix}-sectionBody{flex-direction:column;min-width:0;display:flex}
|
|
554
|
+
.${prefix}-sectionFooter{align-items:center;gap:8px;padding-top:16px;display:flex;flex-wrap:wrap}
|
|
539
555
|
.${prefix}-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}
|
|
540
|
-
.${prefix}-statusBadge{white-space:nowrap;border-radius:999px;flex:none;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}
|
|
541
556
|
.${prefix}-statusOn{background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary)}
|
|
542
557
|
.${prefix}-statusOff{color:var(--dsw-alias-label-tertiary)}
|
|
543
|
-
.${prefix}-body{border-top:1px solid var(--dsw-alias-border-l2);margin:0 16px;padding-bottom:8px}
|
|
544
558
|
.${prefix}-field{flex-direction:column;gap:6px;padding:12px 0;display:flex}
|
|
545
559
|
.${prefix}-field+.${prefix}-field{border-top:1px solid var(--dsw-alias-border-l2)}
|
|
546
560
|
.${prefix}-head{align-items:center;gap:8px;display:flex}
|
|
@@ -569,7 +583,6 @@ window.__ModuleLoader__.load({
|
|
|
569
583
|
.${prefix}-groupLabel{color:var(--dsw-alias-label-secondary);font-size:12px;font-weight:600;padding:12px 0 2px;margin:0}
|
|
570
584
|
.${prefix}-readOnly{color:var(--dsw-alias-label-tertiary);margin:12px 0 0;font-size:12px;line-height:1.5}
|
|
571
585
|
.${prefix}-warn{color:var(--dsw-alias-label-error);margin:12px 0 0;font-size:12px;line-height:1.5}
|
|
572
|
-
.${prefix}-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;flex-wrap:wrap}
|
|
573
586
|
.${prefix}-status{min-width:0;color:var(--dsw-alias-label-secondary);flex:1;margin:0;font-size:12px;line-height:1.5}
|
|
574
587
|
.${prefix}-statusError{color:var(--dsw-alias-label-error)}
|
|
575
588
|
.${prefix}-btn{appearance:none;font:inherit;cursor:pointer;border:1px solid #0000;border-radius:8px;padding:5px 14px;font-size:13px;line-height:1.5}
|
|
@@ -579,14 +592,10 @@ window.__ModuleLoader__.load({
|
|
|
579
592
|
.${prefix}-btn:disabled{opacity:.4;cursor:default}
|
|
580
593
|
.${prefix}-btn:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}
|
|
581
594
|
@media (max-width:767px){
|
|
582
|
-
.${prefix}-body{margin:0 10px}
|
|
583
|
-
.${prefix}-header{padding:12px 12px}
|
|
584
595
|
.${prefix}-input,.${prefix}-select{font-size:16px;height:40px}
|
|
585
596
|
.${prefix}-checkRow{min-height:44px;padding:8px 0}
|
|
586
|
-
.${prefix}-footer{position:sticky;bottom:0;background:var(--dsw-alias-bg-layer-2);margin:0 -10px;padding:10px 10px 6px;border-top:1px solid var(--dsw-alias-border-l2);border-bottom-left-radius:12px;border-bottom-right-radius:12px}
|
|
587
597
|
.${prefix}-status{flex-basis:100%;flex:none}
|
|
588
598
|
.${prefix}-btn{min-height:44px;flex:1;min-width:96px}
|
|
589
|
-
.${prefix}-statusBadge{display:none}
|
|
590
599
|
}
|
|
591
600
|
@media (pointer:coarse){
|
|
592
601
|
.${prefix}-btn{min-height:44px}
|
|
@@ -617,13 +626,97 @@ ${extra}
|
|
|
617
626
|
* settings 命名空间字面量(纯常量,零依赖,浏览器半可安全引入)。
|
|
618
627
|
* 服务端直接以字面量注册(0.1.2-alpha.2 起 dsh-settings 对命名空间做编译期
|
|
619
628
|
* 语法校验,见 config.ts);浏览器半把它作为配置卡片的注册键
|
|
620
|
-
*
|
|
621
|
-
*
|
|
629
|
+
* (插件页配置卡片挂在 row/bundle config 槽位上,与命名空间解耦,
|
|
630
|
+
* 见 shared/config-slots),
|
|
622
631
|
* 同一字面量两处共用,防止漂移。
|
|
623
632
|
* @module image-studio/ns
|
|
624
633
|
*/
|
|
625
634
|
const SETTINGS_NS = "dsh-plus-image-studio";
|
|
626
635
|
//#endregion
|
|
636
|
+
//#region src/client/api.ts
|
|
637
|
+
/**
|
|
638
|
+
* 数据端点通道(前端预留):同源 fetch 封装,类型与 dto.ts 对齐。
|
|
639
|
+
* 本次仅交付通道层,不交付调用它的界面。
|
|
640
|
+
* @module image-studio/client/api
|
|
641
|
+
*/
|
|
642
|
+
const BASE = "/dsh-plus/image-studio";
|
|
643
|
+
/** 提交生图任务。 */
|
|
644
|
+
function generate(request) {
|
|
645
|
+
return postJson(`${BASE}/generate`, request);
|
|
646
|
+
}
|
|
647
|
+
/** 批量任务轮询。 */
|
|
648
|
+
function fetchTasks() {
|
|
649
|
+
return getJson(`${BASE}/tasks`);
|
|
650
|
+
}
|
|
651
|
+
/** 取消任务。 */
|
|
652
|
+
function cancelTask(taskId) {
|
|
653
|
+
return postJson(`${BASE}/tasks/${taskId}/cancel`);
|
|
654
|
+
}
|
|
655
|
+
/** 画廊列表应答形状守卫:items 需为数组(渲染期直接 map)。 */
|
|
656
|
+
function isGalleryList(value) {
|
|
657
|
+
if (typeof value !== "object" || value === null) return false;
|
|
658
|
+
return Array.isArray(value.items);
|
|
659
|
+
}
|
|
660
|
+
/** 画廊列表。 */
|
|
661
|
+
function fetchGallery() {
|
|
662
|
+
return getJson(`${BASE}/gallery`, isGalleryList);
|
|
663
|
+
}
|
|
664
|
+
/** 删除画廊条目。 */
|
|
665
|
+
function deleteGalleryItem(itemId) {
|
|
666
|
+
return fetch(`${BASE}/gallery/${itemId}`, {
|
|
667
|
+
method: "DELETE",
|
|
668
|
+
credentials: "same-origin"
|
|
669
|
+
}).then((res) => res.json());
|
|
670
|
+
}
|
|
671
|
+
/** 图片 URL(<img src> 直连,非 fetch)。 */
|
|
672
|
+
function imageUrl(imageId) {
|
|
673
|
+
return `${BASE}/images/${imageId}`;
|
|
674
|
+
}
|
|
675
|
+
/** 上传原图 URL(缩略图直连,非 fetch)。 */
|
|
676
|
+
function uploadImageUrl(uploadId) {
|
|
677
|
+
return `${BASE}/uploads/${uploadId}`;
|
|
678
|
+
}
|
|
679
|
+
/** 上传本地图片:raw body 流(query 携带原文件名,仅展示用)。 */
|
|
680
|
+
async function uploadImage(file) {
|
|
681
|
+
const query = new URLSearchParams({ name: file.name });
|
|
682
|
+
const res = await fetch(`${BASE}/uploads?${query.toString()}`, {
|
|
683
|
+
method: "POST",
|
|
684
|
+
credentials: "same-origin",
|
|
685
|
+
headers: { "content-type": "application/octet-stream" },
|
|
686
|
+
body: file
|
|
687
|
+
});
|
|
688
|
+
const body = await res.json();
|
|
689
|
+
if (!res.ok) throw new Error(body.message ?? body.error ?? `HTTP ${res.status}`);
|
|
690
|
+
return body;
|
|
691
|
+
}
|
|
692
|
+
/** 上传列表(最新在后;界面自行倒序)。 */
|
|
693
|
+
async function fetchUploads() {
|
|
694
|
+
return getJson(`${BASE}/uploads`);
|
|
695
|
+
}
|
|
696
|
+
/** 凭据状态(describe,永不回传值)。 */
|
|
697
|
+
function fetchCredentialStatus(refName) {
|
|
698
|
+
return getJson(`${BASE}/credentials/${refName}`);
|
|
699
|
+
}
|
|
700
|
+
/** 设置凭据值。 */
|
|
701
|
+
function setCredential(refName, value) {
|
|
702
|
+
return postJson(`${BASE}/credentials/${refName}`, { value });
|
|
703
|
+
}
|
|
704
|
+
/** 删除凭据。 */
|
|
705
|
+
function unsetCredential(refName) {
|
|
706
|
+
return fetch(`${BASE}/credentials/${refName}`, {
|
|
707
|
+
method: "DELETE",
|
|
708
|
+
credentials: "same-origin"
|
|
709
|
+
}).then((res) => res.json());
|
|
710
|
+
}
|
|
711
|
+
/** 协议 registry + 参数目录。 */
|
|
712
|
+
function fetchProviders() {
|
|
713
|
+
return getJson(`${BASE}/providers`);
|
|
714
|
+
}
|
|
715
|
+
/** 预设只读快照(读写走官方 settings RPC)。 */
|
|
716
|
+
function fetchPresets() {
|
|
717
|
+
return getJson(`${BASE}/presets`);
|
|
718
|
+
}
|
|
719
|
+
//#endregion
|
|
627
720
|
//#region src/credentials.ts
|
|
628
721
|
/**
|
|
629
722
|
* 凭据引用名派生与校验:提供商预设 id → IMAGE_STUDIO_PRESET_<ID>。
|
|
@@ -935,103 +1028,10 @@ ${extra}
|
|
|
935
1028
|
if (spec.value !== void 0) item.value = validateParamValue(key, spec.value);
|
|
936
1029
|
out[key] = item;
|
|
937
1030
|
}
|
|
938
|
-
return out;
|
|
939
|
-
}
|
|
940
|
-
//#endregion
|
|
941
|
-
//#region src/client/
|
|
942
|
-
/**
|
|
943
|
-
* 数据端点通道(前端预留):同源 fetch 封装,类型与 dto.ts 对齐。
|
|
944
|
-
* 本次仅交付通道层,不交付调用它的界面。
|
|
945
|
-
* @module image-studio/client/api
|
|
946
|
-
*/
|
|
947
|
-
const BASE = "/dsh-plus/image-studio";
|
|
948
|
-
/** 提交生图任务。 */
|
|
949
|
-
function generate(request) {
|
|
950
|
-
return postJson(`${BASE}/generate`, request);
|
|
951
|
-
}
|
|
952
|
-
/** 批量任务轮询。 */
|
|
953
|
-
function fetchTasks() {
|
|
954
|
-
return getJson(`${BASE}/tasks`);
|
|
955
|
-
}
|
|
956
|
-
/** 取消任务。 */
|
|
957
|
-
function cancelTask(taskId) {
|
|
958
|
-
return postJson(`${BASE}/tasks/${taskId}/cancel`);
|
|
959
|
-
}
|
|
960
|
-
/** 画廊列表应答形状守卫:items 需为数组(渲染期直接 map)。 */
|
|
961
|
-
function isGalleryList(value) {
|
|
962
|
-
if (typeof value !== "object" || value === null) return false;
|
|
963
|
-
return Array.isArray(value.items);
|
|
964
|
-
}
|
|
965
|
-
/** 画廊列表。 */
|
|
966
|
-
function fetchGallery() {
|
|
967
|
-
return getJson(`${BASE}/gallery`, isGalleryList);
|
|
968
|
-
}
|
|
969
|
-
/** 删除画廊条目。 */
|
|
970
|
-
function deleteGalleryItem(itemId) {
|
|
971
|
-
return fetch(`${BASE}/gallery/${itemId}`, {
|
|
972
|
-
method: "DELETE",
|
|
973
|
-
credentials: "same-origin"
|
|
974
|
-
}).then((res) => res.json());
|
|
975
|
-
}
|
|
976
|
-
/** 图片 URL(<img src> 直连,非 fetch)。 */
|
|
977
|
-
function imageUrl(imageId) {
|
|
978
|
-
return `${BASE}/images/${imageId}`;
|
|
979
|
-
}
|
|
980
|
-
/** 上传原图 URL(缩略图直连,非 fetch)。 */
|
|
981
|
-
function uploadImageUrl(uploadId) {
|
|
982
|
-
return `${BASE}/uploads/${uploadId}`;
|
|
983
|
-
}
|
|
984
|
-
/** 上传本地图片:raw body 流(query 携带原文件名,仅展示用)。 */
|
|
985
|
-
async function uploadImage(file) {
|
|
986
|
-
const query = new URLSearchParams({ name: file.name });
|
|
987
|
-
const res = await fetch(`${BASE}/uploads?${query.toString()}`, {
|
|
988
|
-
method: "POST",
|
|
989
|
-
credentials: "same-origin",
|
|
990
|
-
headers: { "content-type": "application/octet-stream" },
|
|
991
|
-
body: file
|
|
992
|
-
});
|
|
993
|
-
const body = await res.json();
|
|
994
|
-
if (!res.ok) throw new Error(body.message ?? body.error ?? `HTTP ${res.status}`);
|
|
995
|
-
return body;
|
|
996
|
-
}
|
|
997
|
-
/** 上传列表(最新在后;界面自行倒序)。 */
|
|
998
|
-
async function fetchUploads() {
|
|
999
|
-
return getJson(`${BASE}/uploads`);
|
|
1000
|
-
}
|
|
1001
|
-
/** 凭据状态(describe,永不回传值)。 */
|
|
1002
|
-
function fetchCredentialStatus(refName) {
|
|
1003
|
-
return getJson(`${BASE}/credentials/${refName}`);
|
|
1004
|
-
}
|
|
1005
|
-
/** 设置凭据值。 */
|
|
1006
|
-
function setCredential(refName, value) {
|
|
1007
|
-
return postJson(`${BASE}/credentials/${refName}`, { value });
|
|
1008
|
-
}
|
|
1009
|
-
/** 删除凭据。 */
|
|
1010
|
-
function unsetCredential(refName) {
|
|
1011
|
-
return fetch(`${BASE}/credentials/${refName}`, {
|
|
1012
|
-
method: "DELETE",
|
|
1013
|
-
credentials: "same-origin"
|
|
1014
|
-
}).then((res) => res.json());
|
|
1015
|
-
}
|
|
1016
|
-
/** 协议 registry + 参数目录。 */
|
|
1017
|
-
function fetchProviders() {
|
|
1018
|
-
return getJson(`${BASE}/providers`);
|
|
1019
|
-
}
|
|
1020
|
-
/** 预设只读快照(读写走官方 settings RPC)。 */
|
|
1021
|
-
function fetchPresets() {
|
|
1022
|
-
return getJson(`${BASE}/presets`);
|
|
1023
|
-
}
|
|
1024
|
-
//#endregion
|
|
1025
|
-
//#region src/client/card.tsx
|
|
1026
|
-
/**
|
|
1027
|
-
* 配置卡片(injectPluginConfigCard 三槽位,legacy key = settings 命名空间,
|
|
1028
|
-
* 0.1.6-alpha.2 起为插件页 row/bundle config,view 分发 summary/page):
|
|
1029
|
-
* 三组预设(提供商/参数/提示词)+ 高级项(并发/超时/代理/画廊上限)的
|
|
1030
|
-
* staged draft 编辑;提供商预设逐条附 API Key 管理(凭据端点,值不回显)。
|
|
1031
|
-
* JSON 字段(extraHeaders/paramSpecs)以文本镜像编辑,保存时解析 +
|
|
1032
|
-
* validateParamSpecs 本地校验,非法即拦在保存前。
|
|
1033
|
-
* @module image-studio/client/card
|
|
1034
|
-
*/
|
|
1031
|
+
return out;
|
|
1032
|
+
}
|
|
1033
|
+
//#endregion
|
|
1034
|
+
//#region src/client/draft.ts
|
|
1035
1035
|
/** 配置值 → 编辑草稿。 */
|
|
1036
1036
|
function draftOf(value) {
|
|
1037
1037
|
return {
|
|
@@ -1103,6 +1103,41 @@ ${extra}
|
|
|
1103
1103
|
uploadTtlHours: Math.max(1, Math.floor(Number(draft.uploadTtlHours) || 24))
|
|
1104
1104
|
};
|
|
1105
1105
|
}
|
|
1106
|
+
/** 空提供商预设(新增行用;协议取目录首项)。 */
|
|
1107
|
+
function emptyProviderDraft(protocol) {
|
|
1108
|
+
return {
|
|
1109
|
+
id: "",
|
|
1110
|
+
name: "",
|
|
1111
|
+
protocol,
|
|
1112
|
+
baseUrl: "",
|
|
1113
|
+
model: "",
|
|
1114
|
+
extraHeadersText: "{}"
|
|
1115
|
+
};
|
|
1116
|
+
}
|
|
1117
|
+
/** 空参数预设(新增行用;id 由时间戳生成,行序即身份)。 */
|
|
1118
|
+
function emptyParamDraft() {
|
|
1119
|
+
return {
|
|
1120
|
+
id: `param-${Date.now().toString(36)}`,
|
|
1121
|
+
name: "",
|
|
1122
|
+
endpoint: "generation",
|
|
1123
|
+
specsText: "{}"
|
|
1124
|
+
};
|
|
1125
|
+
}
|
|
1126
|
+
/** 空提示词预设(新增行用)。 */
|
|
1127
|
+
function emptyPromptDraft() {
|
|
1128
|
+
return {
|
|
1129
|
+
id: `prompt-${Date.now().toString(36)}`,
|
|
1130
|
+
name: "",
|
|
1131
|
+
text: ""
|
|
1132
|
+
};
|
|
1133
|
+
}
|
|
1134
|
+
//#endregion
|
|
1135
|
+
//#region src/client/preset-sections.tsx
|
|
1136
|
+
/**
|
|
1137
|
+
* 配置卡片的三组预设编辑区:提供商(含逐条 API Key 管理)、提示词、参数。
|
|
1138
|
+
* 从 card.tsx 拆出,避免单组件过长;草稿类型与纯转换在 ./draft.ts。
|
|
1139
|
+
* @module image-studio/client/preset-sections
|
|
1140
|
+
*/
|
|
1106
1141
|
/** 单个提供商预设的 API Key 行(describe 徽标 + 设置/删除,值不回显)。 */
|
|
1107
1142
|
function CredentialRow(props) {
|
|
1108
1143
|
const { t, presetId, disabled } = props;
|
|
@@ -1160,22 +1195,343 @@ ${extra}
|
|
|
1160
1195
|
]
|
|
1161
1196
|
});
|
|
1162
1197
|
}
|
|
1198
|
+
function ProviderPresetSection(props) {
|
|
1199
|
+
const { t, draft, disabled, protocols, onPatch, onPatchProvider } = props;
|
|
1200
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
1201
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", {
|
|
1202
|
+
className: "imsc-groupTitle",
|
|
1203
|
+
children: t("card.providers")
|
|
1204
|
+
}),
|
|
1205
|
+
draft.providers.map((provider, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1206
|
+
className: "imsc-presetBox",
|
|
1207
|
+
children: [
|
|
1208
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1209
|
+
className: "imsc-presetHead",
|
|
1210
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1211
|
+
className: "imsc-presetName",
|
|
1212
|
+
children: provider.name || provider.id || "—"
|
|
1213
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1214
|
+
type: "button",
|
|
1215
|
+
className: "imsc-btn imsc-btnGhost",
|
|
1216
|
+
disabled,
|
|
1217
|
+
onClick: () => onPatch({ providers: draft.providers.filter((_, i) => i !== index) }),
|
|
1218
|
+
children: t("common.delete")
|
|
1219
|
+
})]
|
|
1220
|
+
}),
|
|
1221
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1222
|
+
className: "imsc-grid2",
|
|
1223
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TextField, {
|
|
1224
|
+
prefix: "imsc",
|
|
1225
|
+
id: `imsc-pid-${index}`,
|
|
1226
|
+
label: t("card.field.id"),
|
|
1227
|
+
hint: t("card.idHint"),
|
|
1228
|
+
value: provider.id,
|
|
1229
|
+
invalid: provider.id !== "" && !isValidPresetId(provider.id),
|
|
1230
|
+
invalidLabel: t("card.idInvalid"),
|
|
1231
|
+
disabled,
|
|
1232
|
+
onEdit: (v) => onPatchProvider(index, { id: v })
|
|
1233
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TextField, {
|
|
1234
|
+
prefix: "imsc",
|
|
1235
|
+
id: `imsc-pname-${index}`,
|
|
1236
|
+
label: t("card.field.name"),
|
|
1237
|
+
value: provider.name,
|
|
1238
|
+
disabled,
|
|
1239
|
+
onEdit: (v) => onPatchProvider(index, { name: v })
|
|
1240
|
+
})]
|
|
1241
|
+
}),
|
|
1242
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1243
|
+
className: "imsc-grid2",
|
|
1244
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1245
|
+
className: "imsc-field",
|
|
1246
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1247
|
+
className: "imsc-head",
|
|
1248
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
|
|
1249
|
+
className: "imsc-label",
|
|
1250
|
+
htmlFor: `imsc-pproto-${index}`,
|
|
1251
|
+
children: t("card.field.protocol")
|
|
1252
|
+
})
|
|
1253
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
|
|
1254
|
+
id: `imsc-pproto-${index}`,
|
|
1255
|
+
className: "imsc-select",
|
|
1256
|
+
value: provider.protocol,
|
|
1257
|
+
disabled,
|
|
1258
|
+
onChange: (event) => onPatchProvider(index, { protocol: event.target.value }),
|
|
1259
|
+
children: (protocols.length > 0 ? protocols : [{
|
|
1260
|
+
id: provider.protocol,
|
|
1261
|
+
label: provider.protocol
|
|
1262
|
+
}]).map((protocol) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
1263
|
+
value: protocol.id,
|
|
1264
|
+
children: protocol.label
|
|
1265
|
+
}, protocol.id))
|
|
1266
|
+
})]
|
|
1267
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TextField, {
|
|
1268
|
+
prefix: "imsc",
|
|
1269
|
+
id: `imsc-pmodel-${index}`,
|
|
1270
|
+
label: t("card.field.model"),
|
|
1271
|
+
value: provider.model,
|
|
1272
|
+
disabled,
|
|
1273
|
+
onEdit: (v) => onPatchProvider(index, { model: v })
|
|
1274
|
+
})]
|
|
1275
|
+
}),
|
|
1276
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TextField, {
|
|
1277
|
+
prefix: "imsc",
|
|
1278
|
+
id: `imsc-purl-${index}`,
|
|
1279
|
+
label: t("card.field.baseUrl"),
|
|
1280
|
+
value: provider.baseUrl,
|
|
1281
|
+
disabled,
|
|
1282
|
+
onEdit: (v) => onPatchProvider(index, { baseUrl: v })
|
|
1283
|
+
}),
|
|
1284
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1285
|
+
className: "imsc-field",
|
|
1286
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1287
|
+
className: "imsc-head",
|
|
1288
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
|
|
1289
|
+
className: "imsc-label",
|
|
1290
|
+
htmlFor: `imsc-pheaders-${index}`,
|
|
1291
|
+
children: t("card.field.extraHeaders")
|
|
1292
|
+
})
|
|
1293
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
|
|
1294
|
+
id: `imsc-pheaders-${index}`,
|
|
1295
|
+
className: "imsc-textarea",
|
|
1296
|
+
value: provider.extraHeadersText,
|
|
1297
|
+
disabled,
|
|
1298
|
+
onChange: (event) => onPatchProvider(index, { extraHeadersText: event.target.value })
|
|
1299
|
+
})]
|
|
1300
|
+
}),
|
|
1301
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(CredentialRow, {
|
|
1302
|
+
t,
|
|
1303
|
+
presetId: provider.id,
|
|
1304
|
+
disabled
|
|
1305
|
+
})
|
|
1306
|
+
]
|
|
1307
|
+
}, `provider-${index}`)),
|
|
1308
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
1309
|
+
type: "button",
|
|
1310
|
+
className: "imsc-btn imsc-btnGhost",
|
|
1311
|
+
disabled,
|
|
1312
|
+
onClick: () => onPatch({ providers: [...draft.providers, emptyProviderDraft(protocols[0]?.id ?? "openai-images")] }),
|
|
1313
|
+
children: [
|
|
1314
|
+
t("card.add"),
|
|
1315
|
+
" · ",
|
|
1316
|
+
t("card.providers")
|
|
1317
|
+
]
|
|
1318
|
+
})
|
|
1319
|
+
] });
|
|
1320
|
+
}
|
|
1321
|
+
function PromptPresetSection(props) {
|
|
1322
|
+
const { t, draft, disabled, onPatch, onPatchPrompt } = props;
|
|
1323
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
1324
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", {
|
|
1325
|
+
className: "imsc-groupTitle",
|
|
1326
|
+
children: t("card.prompts")
|
|
1327
|
+
}),
|
|
1328
|
+
draft.prompts.map((prompt, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1329
|
+
className: "imsc-presetBox",
|
|
1330
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1331
|
+
className: "imsc-presetHead",
|
|
1332
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1333
|
+
className: "imsc-input",
|
|
1334
|
+
"aria-label": t("card.field.name"),
|
|
1335
|
+
value: prompt.name,
|
|
1336
|
+
disabled,
|
|
1337
|
+
onChange: (event) => onPatchPrompt(index, { name: event.target.value })
|
|
1338
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1339
|
+
type: "button",
|
|
1340
|
+
className: "imsc-btn imsc-btnGhost",
|
|
1341
|
+
disabled,
|
|
1342
|
+
onClick: () => onPatch({ prompts: draft.prompts.filter((_, i) => i !== index) }),
|
|
1343
|
+
children: t("common.delete")
|
|
1344
|
+
})]
|
|
1345
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
|
|
1346
|
+
className: "imsc-textarea",
|
|
1347
|
+
"aria-label": t("card.field.text"),
|
|
1348
|
+
value: prompt.text,
|
|
1349
|
+
disabled,
|
|
1350
|
+
onChange: (event) => onPatchPrompt(index, { text: event.target.value })
|
|
1351
|
+
})]
|
|
1352
|
+
}, `prompt-${index}`)),
|
|
1353
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
1354
|
+
type: "button",
|
|
1355
|
+
className: "imsc-btn imsc-btnGhost",
|
|
1356
|
+
disabled,
|
|
1357
|
+
onClick: () => onPatch({ prompts: [...draft.prompts, emptyPromptDraft()] }),
|
|
1358
|
+
children: [
|
|
1359
|
+
t("card.add"),
|
|
1360
|
+
" · ",
|
|
1361
|
+
t("card.prompts")
|
|
1362
|
+
]
|
|
1363
|
+
})
|
|
1364
|
+
] });
|
|
1365
|
+
}
|
|
1366
|
+
function ParamPresetSection(props) {
|
|
1367
|
+
const { t, draft, disabled, onPatch, onPatchParam } = props;
|
|
1368
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
1369
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", {
|
|
1370
|
+
className: "imsc-groupTitle",
|
|
1371
|
+
children: t("card.params")
|
|
1372
|
+
}),
|
|
1373
|
+
draft.params.map((param, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1374
|
+
className: "imsc-presetBox",
|
|
1375
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1376
|
+
className: "imsc-presetHead",
|
|
1377
|
+
children: [
|
|
1378
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1379
|
+
className: "imsc-input",
|
|
1380
|
+
"aria-label": t("card.field.name"),
|
|
1381
|
+
value: param.name,
|
|
1382
|
+
disabled,
|
|
1383
|
+
onChange: (event) => onPatchParam(index, { name: event.target.value })
|
|
1384
|
+
}),
|
|
1385
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
1386
|
+
className: "imsc-select",
|
|
1387
|
+
"aria-label": t("card.field.endpoint"),
|
|
1388
|
+
value: param.endpoint,
|
|
1389
|
+
disabled,
|
|
1390
|
+
onChange: (event) => onPatchParam(index, { endpoint: event.target.value }),
|
|
1391
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
1392
|
+
value: "generation",
|
|
1393
|
+
children: t("tab.generation")
|
|
1394
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
1395
|
+
value: "edit",
|
|
1396
|
+
children: t("tab.edit")
|
|
1397
|
+
})]
|
|
1398
|
+
}),
|
|
1399
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1400
|
+
type: "button",
|
|
1401
|
+
className: "imsc-btn imsc-btnGhost",
|
|
1402
|
+
disabled,
|
|
1403
|
+
onClick: () => onPatch({ params: draft.params.filter((_, i) => i !== index) }),
|
|
1404
|
+
children: t("common.delete")
|
|
1405
|
+
})
|
|
1406
|
+
]
|
|
1407
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
|
|
1408
|
+
className: "imsc-textarea",
|
|
1409
|
+
"aria-label": t("card.field.paramSpecs"),
|
|
1410
|
+
placeholder: "{ \"size\": { \"enabled\": true, \"value\": \"1024x1024\" } }",
|
|
1411
|
+
value: param.specsText,
|
|
1412
|
+
disabled,
|
|
1413
|
+
onChange: (event) => onPatchParam(index, { specsText: event.target.value })
|
|
1414
|
+
})]
|
|
1415
|
+
}, `param-${index}`)),
|
|
1416
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
1417
|
+
type: "button",
|
|
1418
|
+
className: "imsc-btn imsc-btnGhost",
|
|
1419
|
+
disabled,
|
|
1420
|
+
onClick: () => onPatch({ params: [...draft.params, emptyParamDraft()] }),
|
|
1421
|
+
children: [
|
|
1422
|
+
t("card.add"),
|
|
1423
|
+
" · ",
|
|
1424
|
+
t("card.params")
|
|
1425
|
+
]
|
|
1426
|
+
})
|
|
1427
|
+
] });
|
|
1428
|
+
}
|
|
1429
|
+
//#endregion
|
|
1430
|
+
//#region src/client/card.tsx
|
|
1431
|
+
/**
|
|
1432
|
+
* 配置卡片(injectPluginConfigCard 两槽位 = 插件页 row/bundle config,
|
|
1433
|
+
* view 分发 summary/page):
|
|
1434
|
+
* 三组预设(提供商/参数/提示词)+ 高级项(并发/超时/代理/画廊上限)的
|
|
1435
|
+
* staged draft 编辑;提供商预设逐条附 API Key 管理(凭据端点,值不回显)。
|
|
1436
|
+
* JSON 字段(extraHeaders/paramSpecs)以文本镜像编辑,保存时解析 +
|
|
1437
|
+
* validateParamSpecs 本地校验,非法即拦在保存前。
|
|
1438
|
+
* 草稿类型与纯转换在 ./draft.ts,三组预设编辑区在 ./preset-sections.tsx。
|
|
1439
|
+
* @module image-studio/client/card
|
|
1440
|
+
*/
|
|
1441
|
+
/** 协议目录(服务端 /providers):驱动提供商预设的协议下拉。 */
|
|
1442
|
+
function useProtocols() {
|
|
1443
|
+
const [protocols, setProtocols] = (0, react.useState)([]);
|
|
1444
|
+
(0, react.useEffect)(() => {
|
|
1445
|
+
fetchProviders().then((res) => setProtocols(res.protocols.map((p) => ({
|
|
1446
|
+
id: p.id,
|
|
1447
|
+
label: p.label
|
|
1448
|
+
})))).catch(() => {});
|
|
1449
|
+
}, []);
|
|
1450
|
+
return protocols;
|
|
1451
|
+
}
|
|
1452
|
+
/** 卡片动作:放弃(回到 Host 值并重置基线)与保存。 */
|
|
1453
|
+
function cardActions(options) {
|
|
1454
|
+
const { t } = options;
|
|
1455
|
+
return [{
|
|
1456
|
+
key: "discard",
|
|
1457
|
+
label: t("card.discard"),
|
|
1458
|
+
disabled: !options.dirty || options.saving,
|
|
1459
|
+
onClick: options.onDiscard
|
|
1460
|
+
}, {
|
|
1461
|
+
key: "save",
|
|
1462
|
+
label: t(options.saving ? "common.saving" : "card.save"),
|
|
1463
|
+
variant: "primary",
|
|
1464
|
+
disabled: !options.dirty || options.saving || options.disabled,
|
|
1465
|
+
onClick: options.onSave
|
|
1466
|
+
}];
|
|
1467
|
+
}
|
|
1468
|
+
/** 高级项:并发 / 超时 / 代理 / 画廊上限 / 上传保留时长。 */
|
|
1469
|
+
function AdvancedFields(props) {
|
|
1470
|
+
const { t, draft, disabled, onPatch } = props;
|
|
1471
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
1472
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", {
|
|
1473
|
+
className: "imsc-groupTitle",
|
|
1474
|
+
children: t("card.advanced")
|
|
1475
|
+
}),
|
|
1476
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1477
|
+
className: "imsc-grid2",
|
|
1478
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TextField, {
|
|
1479
|
+
prefix: "imsc",
|
|
1480
|
+
id: "imsc-conc",
|
|
1481
|
+
label: t("card.maxConcurrent"),
|
|
1482
|
+
numeric: true,
|
|
1483
|
+
value: draft.maxConcurrent,
|
|
1484
|
+
disabled,
|
|
1485
|
+
onEdit: (v) => onPatch({ maxConcurrent: v })
|
|
1486
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TextField, {
|
|
1487
|
+
prefix: "imsc",
|
|
1488
|
+
id: "imsc-timeout",
|
|
1489
|
+
label: t("card.requestTimeoutMs"),
|
|
1490
|
+
numeric: true,
|
|
1491
|
+
value: draft.requestTimeoutMs,
|
|
1492
|
+
disabled,
|
|
1493
|
+
onEdit: (v) => onPatch({ requestTimeoutMs: v })
|
|
1494
|
+
})]
|
|
1495
|
+
}),
|
|
1496
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1497
|
+
className: "imsc-grid2",
|
|
1498
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TextField, {
|
|
1499
|
+
prefix: "imsc",
|
|
1500
|
+
id: "imsc-proxy",
|
|
1501
|
+
label: t("card.proxy"),
|
|
1502
|
+
value: draft.proxy,
|
|
1503
|
+
disabled,
|
|
1504
|
+
onEdit: (v) => onPatch({ proxy: v })
|
|
1505
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TextField, {
|
|
1506
|
+
prefix: "imsc",
|
|
1507
|
+
id: "imsc-gmax",
|
|
1508
|
+
label: t("card.galleryMaxItems"),
|
|
1509
|
+
numeric: true,
|
|
1510
|
+
value: draft.galleryMaxItems,
|
|
1511
|
+
disabled,
|
|
1512
|
+
onEdit: (v) => onPatch({ galleryMaxItems: v })
|
|
1513
|
+
})]
|
|
1514
|
+
}),
|
|
1515
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TextField, {
|
|
1516
|
+
prefix: "imsc",
|
|
1517
|
+
id: "imsc-upload-ttl",
|
|
1518
|
+
label: t("card.uploadTtlHours"),
|
|
1519
|
+
numeric: true,
|
|
1520
|
+
value: draft.uploadTtlHours,
|
|
1521
|
+
disabled,
|
|
1522
|
+
onEdit: (v) => onPatch({ uploadTtlHours: v })
|
|
1523
|
+
})
|
|
1524
|
+
] });
|
|
1525
|
+
}
|
|
1163
1526
|
function StudioConfigCard(props) {
|
|
1164
1527
|
const { t, scope, api } = props;
|
|
1165
1528
|
const snapshot = (0, react.useSyncExternalStore)((listener) => scope.subscribe(listener), () => scope.getSnapshot());
|
|
1166
1529
|
const value = snapshot.value;
|
|
1167
|
-
const [open, setOpen] = (0, react.useState)(props.view === "page");
|
|
1168
1530
|
const [draft, setDraft] = (0, react.useState)(null);
|
|
1169
1531
|
const [baseline, setBaseline] = (0, react.useState)("");
|
|
1170
1532
|
const [saving, setSaving] = (0, react.useState)(false);
|
|
1171
1533
|
const [status, setStatus] = (0, react.useState)(IDLE_STATUS);
|
|
1172
|
-
const
|
|
1173
|
-
(0, react.useEffect)(() => {
|
|
1174
|
-
fetchProviders().then((res) => setProtocols(res.protocols.map((p) => ({
|
|
1175
|
-
id: p.id,
|
|
1176
|
-
label: p.label
|
|
1177
|
-
})))).catch(() => {});
|
|
1178
|
-
}, []);
|
|
1534
|
+
const protocols = useProtocols();
|
|
1179
1535
|
(0, react.useEffect)(() => {
|
|
1180
1536
|
if (value === void 0 || draft !== null) return;
|
|
1181
1537
|
const initial = draftOf(value);
|
|
@@ -1184,12 +1540,9 @@ ${extra}
|
|
|
1184
1540
|
}, [value, draft]);
|
|
1185
1541
|
const dirty = (0, react.useMemo)(() => draft !== null && JSON.stringify(draft) !== baseline, [draft, baseline]);
|
|
1186
1542
|
if (props.view === "summary") return t("card.summary");
|
|
1187
|
-
if (value === void 0 || draft === null) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
className: "imsc-loading",
|
|
1191
|
-
children: t("common.loading")
|
|
1192
|
-
})
|
|
1543
|
+
if (value === void 0 || draft === null) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CardLoading, {
|
|
1544
|
+
prefix: "imsc",
|
|
1545
|
+
text: t("common.loading")
|
|
1193
1546
|
});
|
|
1194
1547
|
const disabled = !snapshot.writable;
|
|
1195
1548
|
const onSave = () => {
|
|
@@ -1224,24 +1577,29 @@ ${extra}
|
|
|
1224
1577
|
});
|
|
1225
1578
|
setStatus(IDLE_STATUS);
|
|
1226
1579
|
};
|
|
1227
|
-
const
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1580
|
+
const sectionProps = {
|
|
1581
|
+
t,
|
|
1582
|
+
draft,
|
|
1583
|
+
disabled,
|
|
1584
|
+
protocols,
|
|
1585
|
+
onPatch: patch,
|
|
1586
|
+
onPatchProvider: (index, next) => patch({ providers: draft.providers.map((p, i) => i === index ? {
|
|
1587
|
+
...p,
|
|
1588
|
+
...next
|
|
1589
|
+
} : p) }),
|
|
1590
|
+
onPatchParam: (index, next) => patch({ params: draft.params.map((p, i) => i === index ? {
|
|
1591
|
+
...p,
|
|
1592
|
+
...next
|
|
1593
|
+
} : p) }),
|
|
1594
|
+
onPatchPrompt: (index, next) => patch({ prompts: draft.prompts.map((p, i) => i === index ? {
|
|
1595
|
+
...p,
|
|
1596
|
+
...next
|
|
1597
|
+
} : p) })
|
|
1598
|
+
};
|
|
1239
1599
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(CardChrome, {
|
|
1240
1600
|
prefix: "imsc",
|
|
1241
1601
|
title: t("card.title"),
|
|
1242
1602
|
description: t("card.description"),
|
|
1243
|
-
open,
|
|
1244
|
-
onToggle: setOpen,
|
|
1245
1603
|
statusBadge: {
|
|
1246
1604
|
text: draft.providers.length > 0 ? t("card.badgeOn").replace("{n}", String(draft.providers.length)) : t("card.badgeOff"),
|
|
1247
1605
|
on: draft.providers.length > 0
|
|
@@ -1250,307 +1608,28 @@ ${extra}
|
|
|
1250
1608
|
dirtyLabel: t("common.unsaved"),
|
|
1251
1609
|
readOnlyNotice: disabled ? t("card.readOnly") : void 0,
|
|
1252
1610
|
status,
|
|
1253
|
-
actions:
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1611
|
+
actions: cardActions({
|
|
1612
|
+
t,
|
|
1613
|
+
dirty,
|
|
1614
|
+
saving,
|
|
1615
|
+
disabled,
|
|
1616
|
+
onDiscard: () => {
|
|
1258
1617
|
const restored = draftOf(value);
|
|
1259
1618
|
setDraft(restored);
|
|
1260
1619
|
setBaseline(JSON.stringify(restored));
|
|
1261
1620
|
setStatus(IDLE_STATUS);
|
|
1262
|
-
}
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
label: t(saving ? "common.saving" : "card.save"),
|
|
1266
|
-
variant: "primary",
|
|
1267
|
-
disabled: !dirty || saving || disabled,
|
|
1268
|
-
onClick: onSave
|
|
1269
|
-
}],
|
|
1621
|
+
},
|
|
1622
|
+
onSave
|
|
1623
|
+
}),
|
|
1270
1624
|
children: [
|
|
1271
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
children: [
|
|
1278
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1279
|
-
className: "imsc-presetHead",
|
|
1280
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1281
|
-
className: "imsc-presetName",
|
|
1282
|
-
children: provider.name || provider.id || "—"
|
|
1283
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1284
|
-
type: "button",
|
|
1285
|
-
className: "imsc-btn imsc-btnGhost",
|
|
1286
|
-
disabled,
|
|
1287
|
-
onClick: () => patch({ providers: draft.providers.filter((_, i) => i !== index) }),
|
|
1288
|
-
children: t("common.delete")
|
|
1289
|
-
})]
|
|
1290
|
-
}),
|
|
1291
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1292
|
-
className: "imsc-grid2",
|
|
1293
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TextField, {
|
|
1294
|
-
prefix: "imsc",
|
|
1295
|
-
id: `imsc-pid-${index}`,
|
|
1296
|
-
label: t("card.field.id"),
|
|
1297
|
-
hint: t("card.idHint"),
|
|
1298
|
-
value: provider.id,
|
|
1299
|
-
invalid: provider.id !== "" && !isValidPresetId(provider.id),
|
|
1300
|
-
invalidLabel: t("card.idInvalid"),
|
|
1301
|
-
disabled,
|
|
1302
|
-
onEdit: (v) => patchProvider(index, { id: v })
|
|
1303
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TextField, {
|
|
1304
|
-
prefix: "imsc",
|
|
1305
|
-
id: `imsc-pname-${index}`,
|
|
1306
|
-
label: t("card.field.name"),
|
|
1307
|
-
value: provider.name,
|
|
1308
|
-
disabled,
|
|
1309
|
-
onEdit: (v) => patchProvider(index, { name: v })
|
|
1310
|
-
})]
|
|
1311
|
-
}),
|
|
1312
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1313
|
-
className: "imsc-grid2",
|
|
1314
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1315
|
-
className: "imsc-field",
|
|
1316
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1317
|
-
className: "imsc-head",
|
|
1318
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
|
|
1319
|
-
className: "imsc-label",
|
|
1320
|
-
htmlFor: `imsc-pproto-${index}`,
|
|
1321
|
-
children: t("card.field.protocol")
|
|
1322
|
-
})
|
|
1323
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
|
|
1324
|
-
id: `imsc-pproto-${index}`,
|
|
1325
|
-
className: "imsc-select",
|
|
1326
|
-
value: provider.protocol,
|
|
1327
|
-
disabled,
|
|
1328
|
-
onChange: (event) => patchProvider(index, { protocol: event.target.value }),
|
|
1329
|
-
children: (protocols.length > 0 ? protocols : [{
|
|
1330
|
-
id: provider.protocol,
|
|
1331
|
-
label: provider.protocol
|
|
1332
|
-
}]).map((protocol) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
1333
|
-
value: protocol.id,
|
|
1334
|
-
children: protocol.label
|
|
1335
|
-
}, protocol.id))
|
|
1336
|
-
})]
|
|
1337
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TextField, {
|
|
1338
|
-
prefix: "imsc",
|
|
1339
|
-
id: `imsc-pmodel-${index}`,
|
|
1340
|
-
label: t("card.field.model"),
|
|
1341
|
-
value: provider.model,
|
|
1342
|
-
disabled,
|
|
1343
|
-
onEdit: (v) => patchProvider(index, { model: v })
|
|
1344
|
-
})]
|
|
1345
|
-
}),
|
|
1346
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TextField, {
|
|
1347
|
-
prefix: "imsc",
|
|
1348
|
-
id: `imsc-purl-${index}`,
|
|
1349
|
-
label: t("card.field.baseUrl"),
|
|
1350
|
-
value: provider.baseUrl,
|
|
1351
|
-
disabled,
|
|
1352
|
-
onEdit: (v) => patchProvider(index, { baseUrl: v })
|
|
1353
|
-
}),
|
|
1354
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1355
|
-
className: "imsc-field",
|
|
1356
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1357
|
-
className: "imsc-head",
|
|
1358
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
|
|
1359
|
-
className: "imsc-label",
|
|
1360
|
-
htmlFor: `imsc-pheaders-${index}`,
|
|
1361
|
-
children: t("card.field.extraHeaders")
|
|
1362
|
-
})
|
|
1363
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
|
|
1364
|
-
id: `imsc-pheaders-${index}`,
|
|
1365
|
-
className: "imsc-textarea",
|
|
1366
|
-
value: provider.extraHeadersText,
|
|
1367
|
-
disabled,
|
|
1368
|
-
onChange: (event) => patchProvider(index, { extraHeadersText: event.target.value })
|
|
1369
|
-
})]
|
|
1370
|
-
}),
|
|
1371
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(CredentialRow, {
|
|
1372
|
-
t,
|
|
1373
|
-
presetId: provider.id,
|
|
1374
|
-
disabled
|
|
1375
|
-
})
|
|
1376
|
-
]
|
|
1377
|
-
}, `provider-${index}`)),
|
|
1378
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
1379
|
-
type: "button",
|
|
1380
|
-
className: "imsc-btn imsc-btnGhost",
|
|
1381
|
-
disabled,
|
|
1382
|
-
onClick: () => patch({ providers: [...draft.providers, {
|
|
1383
|
-
id: "",
|
|
1384
|
-
name: "",
|
|
1385
|
-
protocol: protocols[0]?.id ?? "openai-images",
|
|
1386
|
-
baseUrl: "",
|
|
1387
|
-
model: "",
|
|
1388
|
-
extraHeadersText: "{}"
|
|
1389
|
-
}] }),
|
|
1390
|
-
children: [
|
|
1391
|
-
t("card.add"),
|
|
1392
|
-
" · ",
|
|
1393
|
-
t("card.providers")
|
|
1394
|
-
]
|
|
1395
|
-
}),
|
|
1396
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", {
|
|
1397
|
-
className: "imsc-groupTitle",
|
|
1398
|
-
children: t("card.prompts")
|
|
1399
|
-
}),
|
|
1400
|
-
draft.prompts.map((prompt, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1401
|
-
className: "imsc-presetBox",
|
|
1402
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1403
|
-
className: "imsc-presetHead",
|
|
1404
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1405
|
-
className: "imsc-input",
|
|
1406
|
-
"aria-label": t("card.field.name"),
|
|
1407
|
-
value: prompt.name,
|
|
1408
|
-
disabled,
|
|
1409
|
-
onChange: (event) => patchPrompt(index, { name: event.target.value })
|
|
1410
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1411
|
-
type: "button",
|
|
1412
|
-
className: "imsc-btn imsc-btnGhost",
|
|
1413
|
-
disabled,
|
|
1414
|
-
onClick: () => patch({ prompts: draft.prompts.filter((_, i) => i !== index) }),
|
|
1415
|
-
children: t("common.delete")
|
|
1416
|
-
})]
|
|
1417
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
|
|
1418
|
-
className: "imsc-textarea",
|
|
1419
|
-
"aria-label": t("card.field.text"),
|
|
1420
|
-
value: prompt.text,
|
|
1421
|
-
disabled,
|
|
1422
|
-
onChange: (event) => patchPrompt(index, { text: event.target.value })
|
|
1423
|
-
})]
|
|
1424
|
-
}, `prompt-${index}`)),
|
|
1425
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
1426
|
-
type: "button",
|
|
1427
|
-
className: "imsc-btn imsc-btnGhost",
|
|
1428
|
-
disabled,
|
|
1429
|
-
onClick: () => patch({ prompts: [...draft.prompts, {
|
|
1430
|
-
id: `prompt-${Date.now().toString(36)}`,
|
|
1431
|
-
name: "",
|
|
1432
|
-
text: ""
|
|
1433
|
-
}] }),
|
|
1434
|
-
children: [
|
|
1435
|
-
t("card.add"),
|
|
1436
|
-
" · ",
|
|
1437
|
-
t("card.prompts")
|
|
1438
|
-
]
|
|
1439
|
-
}),
|
|
1440
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", {
|
|
1441
|
-
className: "imsc-groupTitle",
|
|
1442
|
-
children: t("card.params")
|
|
1443
|
-
}),
|
|
1444
|
-
draft.params.map((param, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1445
|
-
className: "imsc-presetBox",
|
|
1446
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1447
|
-
className: "imsc-presetHead",
|
|
1448
|
-
children: [
|
|
1449
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1450
|
-
className: "imsc-input",
|
|
1451
|
-
"aria-label": t("card.field.name"),
|
|
1452
|
-
value: param.name,
|
|
1453
|
-
disabled,
|
|
1454
|
-
onChange: (event) => patchParam(index, { name: event.target.value })
|
|
1455
|
-
}),
|
|
1456
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
1457
|
-
className: "imsc-select",
|
|
1458
|
-
"aria-label": t("card.field.endpoint"),
|
|
1459
|
-
value: param.endpoint,
|
|
1460
|
-
disabled,
|
|
1461
|
-
onChange: (event) => patchParam(index, { endpoint: event.target.value }),
|
|
1462
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
1463
|
-
value: "generation",
|
|
1464
|
-
children: t("tab.generation")
|
|
1465
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
1466
|
-
value: "edit",
|
|
1467
|
-
children: t("tab.edit")
|
|
1468
|
-
})]
|
|
1469
|
-
}),
|
|
1470
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1471
|
-
type: "button",
|
|
1472
|
-
className: "imsc-btn imsc-btnGhost",
|
|
1473
|
-
disabled,
|
|
1474
|
-
onClick: () => patch({ params: draft.params.filter((_, i) => i !== index) }),
|
|
1475
|
-
children: t("common.delete")
|
|
1476
|
-
})
|
|
1477
|
-
]
|
|
1478
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
|
|
1479
|
-
className: "imsc-textarea",
|
|
1480
|
-
"aria-label": t("card.field.paramSpecs"),
|
|
1481
|
-
placeholder: "{ \"size\": { \"enabled\": true, \"value\": \"1024x1024\" } }",
|
|
1482
|
-
value: param.specsText,
|
|
1483
|
-
disabled,
|
|
1484
|
-
onChange: (event) => patchParam(index, { specsText: event.target.value })
|
|
1485
|
-
})]
|
|
1486
|
-
}, `param-${index}`)),
|
|
1487
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
1488
|
-
type: "button",
|
|
1489
|
-
className: "imsc-btn imsc-btnGhost",
|
|
1490
|
-
disabled,
|
|
1491
|
-
onClick: () => patch({ params: [...draft.params, {
|
|
1492
|
-
id: `param-${Date.now().toString(36)}`,
|
|
1493
|
-
name: "",
|
|
1494
|
-
endpoint: "generation",
|
|
1495
|
-
specsText: "{}"
|
|
1496
|
-
}] }),
|
|
1497
|
-
children: [
|
|
1498
|
-
t("card.add"),
|
|
1499
|
-
" · ",
|
|
1500
|
-
t("card.params")
|
|
1501
|
-
]
|
|
1502
|
-
}),
|
|
1503
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", {
|
|
1504
|
-
className: "imsc-groupTitle",
|
|
1505
|
-
children: t("card.advanced")
|
|
1506
|
-
}),
|
|
1507
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1508
|
-
className: "imsc-grid2",
|
|
1509
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TextField, {
|
|
1510
|
-
prefix: "imsc",
|
|
1511
|
-
id: "imsc-conc",
|
|
1512
|
-
label: t("card.maxConcurrent"),
|
|
1513
|
-
numeric: true,
|
|
1514
|
-
value: draft.maxConcurrent,
|
|
1515
|
-
disabled,
|
|
1516
|
-
onEdit: (v) => patch({ maxConcurrent: v })
|
|
1517
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TextField, {
|
|
1518
|
-
prefix: "imsc",
|
|
1519
|
-
id: "imsc-timeout",
|
|
1520
|
-
label: t("card.requestTimeoutMs"),
|
|
1521
|
-
numeric: true,
|
|
1522
|
-
value: draft.requestTimeoutMs,
|
|
1523
|
-
disabled,
|
|
1524
|
-
onEdit: (v) => patch({ requestTimeoutMs: v })
|
|
1525
|
-
})]
|
|
1526
|
-
}),
|
|
1527
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1528
|
-
className: "imsc-grid2",
|
|
1529
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TextField, {
|
|
1530
|
-
prefix: "imsc",
|
|
1531
|
-
id: "imsc-proxy",
|
|
1532
|
-
label: t("card.proxy"),
|
|
1533
|
-
value: draft.proxy,
|
|
1534
|
-
disabled,
|
|
1535
|
-
onEdit: (v) => patch({ proxy: v })
|
|
1536
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TextField, {
|
|
1537
|
-
prefix: "imsc",
|
|
1538
|
-
id: "imsc-gmax",
|
|
1539
|
-
label: t("card.galleryMaxItems"),
|
|
1540
|
-
numeric: true,
|
|
1541
|
-
value: draft.galleryMaxItems,
|
|
1542
|
-
disabled,
|
|
1543
|
-
onEdit: (v) => patch({ galleryMaxItems: v })
|
|
1544
|
-
})]
|
|
1545
|
-
}),
|
|
1546
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TextField, {
|
|
1547
|
-
prefix: "imsc",
|
|
1548
|
-
id: "imsc-upload-ttl",
|
|
1549
|
-
label: t("card.uploadTtlHours"),
|
|
1550
|
-
numeric: true,
|
|
1551
|
-
value: draft.uploadTtlHours,
|
|
1625
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ProviderPresetSection, { ...sectionProps }),
|
|
1626
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(PromptPresetSection, { ...sectionProps }),
|
|
1627
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ParamPresetSection, { ...sectionProps }),
|
|
1628
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(AdvancedFields, {
|
|
1629
|
+
t,
|
|
1630
|
+
draft,
|
|
1552
1631
|
disabled,
|
|
1553
|
-
|
|
1632
|
+
onPatch: patch
|
|
1554
1633
|
})
|
|
1555
1634
|
]
|
|
1556
1635
|
});
|
|
@@ -4001,10 +4080,8 @@ ${extra}
|
|
|
4001
4080
|
.imsc-loading{padding:14px 16px;margin:0;font-size:13px;color:var(--dsw-alias-label-tertiary)}
|
|
4002
4081
|
.imsc-groupTitle { margin: 14px 0 6px; font-size: 13px; font-weight: 600; }
|
|
4003
4082
|
.imsc-presetBox {
|
|
4004
|
-
border: 1px solid var(--dsw-alias-border-l3);
|
|
4005
|
-
|
|
4006
|
-
padding: 10px;
|
|
4007
|
-
margin-bottom: 10px;
|
|
4083
|
+
border-top: 1px solid var(--dsw-alias-border-l3);
|
|
4084
|
+
padding: 10px 0 2px;
|
|
4008
4085
|
display: grid;
|
|
4009
4086
|
gap: 8px;
|
|
4010
4087
|
}
|
|
@@ -4027,6 +4104,7 @@ ${extra}
|
|
|
4027
4104
|
.imsc-credRow { display: flex; align-items: center; gap: 8px; }
|
|
4028
4105
|
.imsc-credRow .imsc-input { flex: 1; min-width: 0; }
|
|
4029
4106
|
|
|
4107
|
+
|
|
4030
4108
|
/* ── 移动端(≤767px,与 ui-mobile-fit 同断点)── */
|
|
4031
4109
|
@media (max-width: 767px) {
|
|
4032
4110
|
.ims-modal {
|