@dsh-plus/image-studio 0.1.21 → 0.1.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js CHANGED
@@ -277,9 +277,72 @@ window.__ModuleLoader__.load({
277
277
  kind: "idle",
278
278
  text: ""
279
279
  };
280
+ /** 状态行 + 动作按钮(两种形态共用,类名随形态切换度量)。 */
281
+ function CardFoot(props) {
282
+ const p = props.prefix;
283
+ const section = props.variant === "section";
284
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
285
+ className: section ? `${p}-sectionFooter` : `${p}-footer`,
286
+ children: [props.status !== void 0 && props.status.kind !== "idle" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
287
+ className: `${p}-status${props.status.kind === "error" ? ` ${p}-statusError` : ""}`,
288
+ role: "status",
289
+ children: props.status.text
290
+ }) : null, props.actions.map((action) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
291
+ type: "button",
292
+ className: `${p}-btn ${action.variant === "primary" ? `${p}-btnPrimary` : `${p}-btnGhost`}`,
293
+ disabled: action.disabled === true,
294
+ onClick: action.onClick,
295
+ children: action.label
296
+ }, action.key))]
297
+ });
298
+ }
299
+ /** 只读提示 + 字段区 + 页脚(两种形态共用)。 */
300
+ function CardBody(props) {
301
+ const p = props.prefix;
302
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
303
+ props.readOnlyNotice !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
304
+ className: `${p}-readOnly`,
305
+ role: "status",
306
+ children: props.readOnlyNotice
307
+ }) : null,
308
+ props.children,
309
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CardFoot, { ...props })
310
+ ] });
311
+ }
280
312
  function CardChrome(props) {
281
313
  const p = props.prefix;
282
314
  const badge = props.statusBadge;
315
+ if (props.variant === "section") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
316
+ className: `${p}-section`,
317
+ "aria-label": props.title,
318
+ children: [
319
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
320
+ className: `${p}-sectionHead`,
321
+ children: [
322
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
323
+ className: `${p}-sectionTitle`,
324
+ children: props.title
325
+ }),
326
+ badge !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
327
+ className: `${p}-sectionBadge ${badge.on ? `${p}-statusOn` : `${p}-statusOff`}`,
328
+ children: badge.text
329
+ }) : null,
330
+ props.dirty ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
331
+ className: `${p}-pending`,
332
+ children: props.dirtyLabel
333
+ }) : null
334
+ ]
335
+ }),
336
+ props.description === "" ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
337
+ className: `${p}-sectionDesc`,
338
+ children: props.description
339
+ }),
340
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
341
+ className: `${p}-sectionBody`,
342
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CardBody, { ...props })
343
+ })
344
+ ]
345
+ });
283
346
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
284
347
  className: `${p}-card${props.open ? ` ${p}-cardOpen` : ""}`,
285
348
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
@@ -309,38 +372,44 @@ window.__ModuleLoader__.load({
309
372
  }) : null,
310
373
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutlineRegular, { className: `${p}-chevron${props.open ? ` ${p}-chevronOpen` : ""}` })
311
374
  ]
312
- }), props.open ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
375
+ }), props.open ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
313
376
  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
- ]
377
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CardBody, { ...props })
336
378
  }) : null]
337
379
  });
338
380
  }
381
+ /** 取值未到达时的占位:与 CardChrome 同形态,避免宿主页里闪一次卡片外壳。 */
382
+ function CardLoading(props) {
383
+ if (props.variant === "section") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("section", {
384
+ className: `${props.prefix}-section`,
385
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
386
+ className: `${props.prefix}-readOnly`,
387
+ role: "status",
388
+ children: props.text
389
+ })
390
+ });
391
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", {
392
+ className: `${props.prefix}-card`,
393
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
394
+ className: `${props.prefix}-readOnly`,
395
+ role: "status",
396
+ children: props.text
397
+ })
398
+ });
399
+ }
339
400
  //#endregion
340
401
  //#region ../shared/src/client/config-slots.ts
341
402
  /** dsh-plus 聚合 bundle 的包名(各插件 row 均经其补丁层插入 profile)。 */
342
403
  const DSH_PLUS_BUNDLE = "@dsh-plus/bundle-main";
343
404
  /**
405
+ * 视图 → 外壳形态:插件页 page 视图渲染在官方页面容器内(宿主已画标题、图标、
406
+ * 面包屑与页面级内边距),再套卡片会形成卡片套卡片并压缩移动端可用宽度,
407
+ * 故用无边框分节;summary 与 legacy 槽位保持卡片形态。
408
+ */
409
+ function cardVariantFor(view) {
410
+ return view === "page" ? "section" : "card";
411
+ }
412
+ /**
344
413
  * 把一张配置卡片同时注册进 legacy 与新版三个槽位。
345
414
  * 任一槽位在当前 dsh 版本未声明时对应 inject 挂起等待,版本不命中即 no-op。
346
415
  */
@@ -366,6 +435,17 @@ window.__ModuleLoader__.load({
366
435
  }, reg.component));
367
436
  }
368
437
  //#endregion
438
+ //#region ../shared/src/client/draft.ts
439
+ /**
440
+ * 单命名空间 staged 草稿(简单配置卡片共用):读 scope 快照、播种草稿、
441
+ * 脏判定、保存(revision fencing + 重新装载)与状态行。
442
+ *
443
+ * 语义与官方 staged 表单一致:Host 更新不覆盖在途编辑,只有保存才写回;
444
+ * 保存成功后按 Host 解析值重置草稿。`from`/`patch` 参与 effect 与 useMemo
445
+ * 依赖,必须是模块级稳定函数(不要内联箭头函数)。
446
+ * @module @dsh-plus/shared/client/draft
447
+ */
448
+ //#endregion
369
449
  //#region ../shared/src/client/fetch.ts
370
450
  async function parse(res, guard) {
371
451
  const body = await res.json();
@@ -520,6 +600,9 @@ window.__ModuleLoader__.load({
520
600
  * - 窄屏 input/textarea 16px(防 iOS 聚焦缩放)、可点目标 ≥44px;
521
601
  * - footer 按钮等宽换行、状态文字独占一行;
522
602
  * - 展开态 body 底部 sticky 保存栏(长表单滚动中始终可达)。
603
+ * - 分节形态(-section,插件页 page 视图内嵌宿主页面):无边框/背景、横向零
604
+ * 内边距,页脚不 sticky——官方插件页已提供页面级内边距,再叠一层卡片会
605
+ * 缩窄移动端可用宽度(卡片套卡片)。
523
606
  * 沿用官方 data-plugin / data-plugin-css 约定(HMR 据此卸载),不覆盖上游选择器。
524
607
  * @module @dsh-plus/shared/client/styles
525
608
  */
@@ -566,6 +649,15 @@ window.__ModuleLoader__.load({
566
649
  .${prefix}-checkRow input:checked::after{left:16px;background:var(--dsw-alias-bg-base)}
567
650
  .${prefix}-checkRow input:disabled{opacity:.4;cursor:default}
568
651
  .${prefix}-checkRow input:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}
652
+ /* 宿主内嵌分节形态(插件页 page 视图):官方设置页同构——无边框/背景,
653
+ 横向零内边距(宽度交给宿主页面容器),小节标题 13px/600,页脚非 sticky。 */
654
+ .${prefix}-section{box-sizing:border-box;flex-direction:column;width:100%;min-width:0;padding:16px 0;display:flex}
655
+ .${prefix}-sectionHead{align-items:center;gap:8px;min-width:0;display:flex}
656
+ .${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}
657
+ .${prefix}-sectionBadge{white-space:nowrap;border-radius:999px;flex:none;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}
658
+ .${prefix}-sectionDesc{color:var(--dsw-alias-label-tertiary);margin:4px 0 0;font-size:12px;line-height:1.5}
659
+ .${prefix}-sectionBody{flex-direction:column;min-width:0;display:flex}
660
+ .${prefix}-sectionFooter{align-items:center;gap:8px;padding-top:16px;display:flex;flex-wrap:wrap}
569
661
  .${prefix}-groupLabel{color:var(--dsw-alias-label-secondary);font-size:12px;font-weight:600;padding:12px 0 2px;margin:0}
570
662
  .${prefix}-readOnly{color:var(--dsw-alias-label-tertiary);margin:12px 0 0;font-size:12px;line-height:1.5}
571
663
  .${prefix}-warn{color:var(--dsw-alias-label-error);margin:12px 0 0;font-size:12px;line-height:1.5}
@@ -587,6 +679,8 @@ window.__ModuleLoader__.load({
587
679
  .${prefix}-status{flex-basis:100%;flex:none}
588
680
  .${prefix}-btn{min-height:44px;flex:1;min-width:96px}
589
681
  .${prefix}-statusBadge{display:none}
682
+ .${prefix}-sectionBadge{display:inline-block}
683
+ .${prefix}-sectionFooter{position:static}
590
684
  }
591
685
  @media (pointer:coarse){
592
686
  .${prefix}-btn{min-height:44px}
@@ -624,6 +718,90 @@ ${extra}
624
718
  */
625
719
  const SETTINGS_NS = "dsh-plus-image-studio";
626
720
  //#endregion
721
+ //#region src/client/api.ts
722
+ /**
723
+ * 数据端点通道(前端预留):同源 fetch 封装,类型与 dto.ts 对齐。
724
+ * 本次仅交付通道层,不交付调用它的界面。
725
+ * @module image-studio/client/api
726
+ */
727
+ const BASE = "/dsh-plus/image-studio";
728
+ /** 提交生图任务。 */
729
+ function generate(request) {
730
+ return postJson(`${BASE}/generate`, request);
731
+ }
732
+ /** 批量任务轮询。 */
733
+ function fetchTasks() {
734
+ return getJson(`${BASE}/tasks`);
735
+ }
736
+ /** 取消任务。 */
737
+ function cancelTask(taskId) {
738
+ return postJson(`${BASE}/tasks/${taskId}/cancel`);
739
+ }
740
+ /** 画廊列表应答形状守卫:items 需为数组(渲染期直接 map)。 */
741
+ function isGalleryList(value) {
742
+ if (typeof value !== "object" || value === null) return false;
743
+ return Array.isArray(value.items);
744
+ }
745
+ /** 画廊列表。 */
746
+ function fetchGallery() {
747
+ return getJson(`${BASE}/gallery`, isGalleryList);
748
+ }
749
+ /** 删除画廊条目。 */
750
+ function deleteGalleryItem(itemId) {
751
+ return fetch(`${BASE}/gallery/${itemId}`, {
752
+ method: "DELETE",
753
+ credentials: "same-origin"
754
+ }).then((res) => res.json());
755
+ }
756
+ /** 图片 URL(<img src> 直连,非 fetch)。 */
757
+ function imageUrl(imageId) {
758
+ return `${BASE}/images/${imageId}`;
759
+ }
760
+ /** 上传原图 URL(缩略图直连,非 fetch)。 */
761
+ function uploadImageUrl(uploadId) {
762
+ return `${BASE}/uploads/${uploadId}`;
763
+ }
764
+ /** 上传本地图片:raw body 流(query 携带原文件名,仅展示用)。 */
765
+ async function uploadImage(file) {
766
+ const query = new URLSearchParams({ name: file.name });
767
+ const res = await fetch(`${BASE}/uploads?${query.toString()}`, {
768
+ method: "POST",
769
+ credentials: "same-origin",
770
+ headers: { "content-type": "application/octet-stream" },
771
+ body: file
772
+ });
773
+ const body = await res.json();
774
+ if (!res.ok) throw new Error(body.message ?? body.error ?? `HTTP ${res.status}`);
775
+ return body;
776
+ }
777
+ /** 上传列表(最新在后;界面自行倒序)。 */
778
+ async function fetchUploads() {
779
+ return getJson(`${BASE}/uploads`);
780
+ }
781
+ /** 凭据状态(describe,永不回传值)。 */
782
+ function fetchCredentialStatus(refName) {
783
+ return getJson(`${BASE}/credentials/${refName}`);
784
+ }
785
+ /** 设置凭据值。 */
786
+ function setCredential(refName, value) {
787
+ return postJson(`${BASE}/credentials/${refName}`, { value });
788
+ }
789
+ /** 删除凭据。 */
790
+ function unsetCredential(refName) {
791
+ return fetch(`${BASE}/credentials/${refName}`, {
792
+ method: "DELETE",
793
+ credentials: "same-origin"
794
+ }).then((res) => res.json());
795
+ }
796
+ /** 协议 registry + 参数目录。 */
797
+ function fetchProviders() {
798
+ return getJson(`${BASE}/providers`);
799
+ }
800
+ /** 预设只读快照(读写走官方 settings RPC)。 */
801
+ function fetchPresets() {
802
+ return getJson(`${BASE}/presets`);
803
+ }
804
+ //#endregion
627
805
  //#region src/credentials.ts
628
806
  /**
629
807
  * 凭据引用名派生与校验:提供商预设 id → IMAGE_STUDIO_PRESET_<ID>。
@@ -938,100 +1116,7 @@ ${extra}
938
1116
  return out;
939
1117
  }
940
1118
  //#endregion
941
- //#region src/client/api.ts
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
- */
1119
+ //#region src/client/draft.ts
1035
1120
  /** 配置值 → 编辑草稿。 */
1036
1121
  function draftOf(value) {
1037
1122
  return {
@@ -1103,6 +1188,41 @@ ${extra}
1103
1188
  uploadTtlHours: Math.max(1, Math.floor(Number(draft.uploadTtlHours) || 24))
1104
1189
  };
1105
1190
  }
1191
+ /** 空提供商预设(新增行用;协议取目录首项)。 */
1192
+ function emptyProviderDraft(protocol) {
1193
+ return {
1194
+ id: "",
1195
+ name: "",
1196
+ protocol,
1197
+ baseUrl: "",
1198
+ model: "",
1199
+ extraHeadersText: "{}"
1200
+ };
1201
+ }
1202
+ /** 空参数预设(新增行用;id 由时间戳生成,行序即身份)。 */
1203
+ function emptyParamDraft() {
1204
+ return {
1205
+ id: `param-${Date.now().toString(36)}`,
1206
+ name: "",
1207
+ endpoint: "generation",
1208
+ specsText: "{}"
1209
+ };
1210
+ }
1211
+ /** 空提示词预设(新增行用)。 */
1212
+ function emptyPromptDraft() {
1213
+ return {
1214
+ id: `prompt-${Date.now().toString(36)}`,
1215
+ name: "",
1216
+ text: ""
1217
+ };
1218
+ }
1219
+ //#endregion
1220
+ //#region src/client/preset-sections.tsx
1221
+ /**
1222
+ * 配置卡片的三组预设编辑区:提供商(含逐条 API Key 管理)、提示词、参数。
1223
+ * 从 card.tsx 拆出,避免单组件过长;草稿类型与纯转换在 ./draft.ts。
1224
+ * @module image-studio/client/preset-sections
1225
+ */
1106
1226
  /** 单个提供商预设的 API Key 行(describe 徽标 + 设置/删除,值不回显)。 */
1107
1227
  function CredentialRow(props) {
1108
1228
  const { t, presetId, disabled } = props;
@@ -1160,6 +1280,334 @@ ${extra}
1160
1280
  ]
1161
1281
  });
1162
1282
  }
1283
+ function ProviderPresetSection(props) {
1284
+ const { t, draft, disabled, protocols, onPatch, onPatchProvider } = props;
1285
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
1286
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", {
1287
+ className: "imsc-groupTitle",
1288
+ children: t("card.providers")
1289
+ }),
1290
+ draft.providers.map((provider, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1291
+ className: "imsc-presetBox",
1292
+ children: [
1293
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1294
+ className: "imsc-presetHead",
1295
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1296
+ className: "imsc-presetName",
1297
+ children: provider.name || provider.id || "—"
1298
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1299
+ type: "button",
1300
+ className: "imsc-btn imsc-btnGhost",
1301
+ disabled,
1302
+ onClick: () => onPatch({ providers: draft.providers.filter((_, i) => i !== index) }),
1303
+ children: t("common.delete")
1304
+ })]
1305
+ }),
1306
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1307
+ className: "imsc-grid2",
1308
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TextField, {
1309
+ prefix: "imsc",
1310
+ id: `imsc-pid-${index}`,
1311
+ label: t("card.field.id"),
1312
+ hint: t("card.idHint"),
1313
+ value: provider.id,
1314
+ invalid: provider.id !== "" && !isValidPresetId(provider.id),
1315
+ invalidLabel: t("card.idInvalid"),
1316
+ disabled,
1317
+ onEdit: (v) => onPatchProvider(index, { id: v })
1318
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TextField, {
1319
+ prefix: "imsc",
1320
+ id: `imsc-pname-${index}`,
1321
+ label: t("card.field.name"),
1322
+ value: provider.name,
1323
+ disabled,
1324
+ onEdit: (v) => onPatchProvider(index, { name: v })
1325
+ })]
1326
+ }),
1327
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1328
+ className: "imsc-grid2",
1329
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1330
+ className: "imsc-field",
1331
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1332
+ className: "imsc-head",
1333
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
1334
+ className: "imsc-label",
1335
+ htmlFor: `imsc-pproto-${index}`,
1336
+ children: t("card.field.protocol")
1337
+ })
1338
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
1339
+ id: `imsc-pproto-${index}`,
1340
+ className: "imsc-select",
1341
+ value: provider.protocol,
1342
+ disabled,
1343
+ onChange: (event) => onPatchProvider(index, { protocol: event.target.value }),
1344
+ children: (protocols.length > 0 ? protocols : [{
1345
+ id: provider.protocol,
1346
+ label: provider.protocol
1347
+ }]).map((protocol) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1348
+ value: protocol.id,
1349
+ children: protocol.label
1350
+ }, protocol.id))
1351
+ })]
1352
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TextField, {
1353
+ prefix: "imsc",
1354
+ id: `imsc-pmodel-${index}`,
1355
+ label: t("card.field.model"),
1356
+ value: provider.model,
1357
+ disabled,
1358
+ onEdit: (v) => onPatchProvider(index, { model: v })
1359
+ })]
1360
+ }),
1361
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TextField, {
1362
+ prefix: "imsc",
1363
+ id: `imsc-purl-${index}`,
1364
+ label: t("card.field.baseUrl"),
1365
+ value: provider.baseUrl,
1366
+ disabled,
1367
+ onEdit: (v) => onPatchProvider(index, { baseUrl: v })
1368
+ }),
1369
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1370
+ className: "imsc-field",
1371
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1372
+ className: "imsc-head",
1373
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
1374
+ className: "imsc-label",
1375
+ htmlFor: `imsc-pheaders-${index}`,
1376
+ children: t("card.field.extraHeaders")
1377
+ })
1378
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
1379
+ id: `imsc-pheaders-${index}`,
1380
+ className: "imsc-textarea",
1381
+ value: provider.extraHeadersText,
1382
+ disabled,
1383
+ onChange: (event) => onPatchProvider(index, { extraHeadersText: event.target.value })
1384
+ })]
1385
+ }),
1386
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CredentialRow, {
1387
+ t,
1388
+ presetId: provider.id,
1389
+ disabled
1390
+ })
1391
+ ]
1392
+ }, `provider-${index}`)),
1393
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1394
+ type: "button",
1395
+ className: "imsc-btn imsc-btnGhost",
1396
+ disabled,
1397
+ onClick: () => onPatch({ providers: [...draft.providers, emptyProviderDraft(protocols[0]?.id ?? "openai-images")] }),
1398
+ children: [
1399
+ t("card.add"),
1400
+ " · ",
1401
+ t("card.providers")
1402
+ ]
1403
+ })
1404
+ ] });
1405
+ }
1406
+ function PromptPresetSection(props) {
1407
+ const { t, draft, disabled, onPatch, onPatchPrompt } = props;
1408
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
1409
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", {
1410
+ className: "imsc-groupTitle",
1411
+ children: t("card.prompts")
1412
+ }),
1413
+ draft.prompts.map((prompt, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1414
+ className: "imsc-presetBox",
1415
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1416
+ className: "imsc-presetHead",
1417
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1418
+ className: "imsc-input",
1419
+ "aria-label": t("card.field.name"),
1420
+ value: prompt.name,
1421
+ disabled,
1422
+ onChange: (event) => onPatchPrompt(index, { name: event.target.value })
1423
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1424
+ type: "button",
1425
+ className: "imsc-btn imsc-btnGhost",
1426
+ disabled,
1427
+ onClick: () => onPatch({ prompts: draft.prompts.filter((_, i) => i !== index) }),
1428
+ children: t("common.delete")
1429
+ })]
1430
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
1431
+ className: "imsc-textarea",
1432
+ "aria-label": t("card.field.text"),
1433
+ value: prompt.text,
1434
+ disabled,
1435
+ onChange: (event) => onPatchPrompt(index, { text: event.target.value })
1436
+ })]
1437
+ }, `prompt-${index}`)),
1438
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1439
+ type: "button",
1440
+ className: "imsc-btn imsc-btnGhost",
1441
+ disabled,
1442
+ onClick: () => onPatch({ prompts: [...draft.prompts, emptyPromptDraft()] }),
1443
+ children: [
1444
+ t("card.add"),
1445
+ " · ",
1446
+ t("card.prompts")
1447
+ ]
1448
+ })
1449
+ ] });
1450
+ }
1451
+ function ParamPresetSection(props) {
1452
+ const { t, draft, disabled, onPatch, onPatchParam } = props;
1453
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
1454
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", {
1455
+ className: "imsc-groupTitle",
1456
+ children: t("card.params")
1457
+ }),
1458
+ draft.params.map((param, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1459
+ className: "imsc-presetBox",
1460
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1461
+ className: "imsc-presetHead",
1462
+ children: [
1463
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1464
+ className: "imsc-input",
1465
+ "aria-label": t("card.field.name"),
1466
+ value: param.name,
1467
+ disabled,
1468
+ onChange: (event) => onPatchParam(index, { name: event.target.value })
1469
+ }),
1470
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
1471
+ className: "imsc-select",
1472
+ "aria-label": t("card.field.endpoint"),
1473
+ value: param.endpoint,
1474
+ disabled,
1475
+ onChange: (event) => onPatchParam(index, { endpoint: event.target.value }),
1476
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1477
+ value: "generation",
1478
+ children: t("tab.generation")
1479
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1480
+ value: "edit",
1481
+ children: t("tab.edit")
1482
+ })]
1483
+ }),
1484
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1485
+ type: "button",
1486
+ className: "imsc-btn imsc-btnGhost",
1487
+ disabled,
1488
+ onClick: () => onPatch({ params: draft.params.filter((_, i) => i !== index) }),
1489
+ children: t("common.delete")
1490
+ })
1491
+ ]
1492
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
1493
+ className: "imsc-textarea",
1494
+ "aria-label": t("card.field.paramSpecs"),
1495
+ placeholder: "{ \"size\": { \"enabled\": true, \"value\": \"1024x1024\" } }",
1496
+ value: param.specsText,
1497
+ disabled,
1498
+ onChange: (event) => onPatchParam(index, { specsText: event.target.value })
1499
+ })]
1500
+ }, `param-${index}`)),
1501
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1502
+ type: "button",
1503
+ className: "imsc-btn imsc-btnGhost",
1504
+ disabled,
1505
+ onClick: () => onPatch({ params: [...draft.params, emptyParamDraft()] }),
1506
+ children: [
1507
+ t("card.add"),
1508
+ " · ",
1509
+ t("card.params")
1510
+ ]
1511
+ })
1512
+ ] });
1513
+ }
1514
+ //#endregion
1515
+ //#region src/client/card.tsx
1516
+ /**
1517
+ * 配置卡片(injectPluginConfigCard 三槽位,legacy key = settings 命名空间,
1518
+ * 0.1.6-alpha.2 起为插件页 row/bundle config,view 分发 summary/page):
1519
+ * 三组预设(提供商/参数/提示词)+ 高级项(并发/超时/代理/画廊上限)的
1520
+ * staged draft 编辑;提供商预设逐条附 API Key 管理(凭据端点,值不回显)。
1521
+ * JSON 字段(extraHeaders/paramSpecs)以文本镜像编辑,保存时解析 +
1522
+ * validateParamSpecs 本地校验,非法即拦在保存前。
1523
+ * 草稿类型与纯转换在 ./draft.ts,三组预设编辑区在 ./preset-sections.tsx。
1524
+ * @module image-studio/client/card
1525
+ */
1526
+ /** 协议目录(服务端 /providers):驱动提供商预设的协议下拉。 */
1527
+ function useProtocols() {
1528
+ const [protocols, setProtocols] = (0, react.useState)([]);
1529
+ (0, react.useEffect)(() => {
1530
+ fetchProviders().then((res) => setProtocols(res.protocols.map((p) => ({
1531
+ id: p.id,
1532
+ label: p.label
1533
+ })))).catch(() => {});
1534
+ }, []);
1535
+ return protocols;
1536
+ }
1537
+ /** 卡片动作:放弃(回到 Host 值并重置基线)与保存。 */
1538
+ function cardActions(options) {
1539
+ const { t } = options;
1540
+ return [{
1541
+ key: "discard",
1542
+ label: t("card.discard"),
1543
+ disabled: !options.dirty || options.saving,
1544
+ onClick: options.onDiscard
1545
+ }, {
1546
+ key: "save",
1547
+ label: t(options.saving ? "common.saving" : "card.save"),
1548
+ variant: "primary",
1549
+ disabled: !options.dirty || options.saving || options.disabled,
1550
+ onClick: options.onSave
1551
+ }];
1552
+ }
1553
+ /** 高级项:并发 / 超时 / 代理 / 画廊上限 / 上传保留时长。 */
1554
+ function AdvancedFields(props) {
1555
+ const { t, draft, disabled, onPatch } = props;
1556
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
1557
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", {
1558
+ className: "imsc-groupTitle",
1559
+ children: t("card.advanced")
1560
+ }),
1561
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1562
+ className: "imsc-grid2",
1563
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TextField, {
1564
+ prefix: "imsc",
1565
+ id: "imsc-conc",
1566
+ label: t("card.maxConcurrent"),
1567
+ numeric: true,
1568
+ value: draft.maxConcurrent,
1569
+ disabled,
1570
+ onEdit: (v) => onPatch({ maxConcurrent: v })
1571
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TextField, {
1572
+ prefix: "imsc",
1573
+ id: "imsc-timeout",
1574
+ label: t("card.requestTimeoutMs"),
1575
+ numeric: true,
1576
+ value: draft.requestTimeoutMs,
1577
+ disabled,
1578
+ onEdit: (v) => onPatch({ requestTimeoutMs: v })
1579
+ })]
1580
+ }),
1581
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1582
+ className: "imsc-grid2",
1583
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TextField, {
1584
+ prefix: "imsc",
1585
+ id: "imsc-proxy",
1586
+ label: t("card.proxy"),
1587
+ value: draft.proxy,
1588
+ disabled,
1589
+ onEdit: (v) => onPatch({ proxy: v })
1590
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TextField, {
1591
+ prefix: "imsc",
1592
+ id: "imsc-gmax",
1593
+ label: t("card.galleryMaxItems"),
1594
+ numeric: true,
1595
+ value: draft.galleryMaxItems,
1596
+ disabled,
1597
+ onEdit: (v) => onPatch({ galleryMaxItems: v })
1598
+ })]
1599
+ }),
1600
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TextField, {
1601
+ prefix: "imsc",
1602
+ id: "imsc-upload-ttl",
1603
+ label: t("card.uploadTtlHours"),
1604
+ numeric: true,
1605
+ value: draft.uploadTtlHours,
1606
+ disabled,
1607
+ onEdit: (v) => onPatch({ uploadTtlHours: v })
1608
+ })
1609
+ ] });
1610
+ }
1163
1611
  function StudioConfigCard(props) {
1164
1612
  const { t, scope, api } = props;
1165
1613
  const snapshot = (0, react.useSyncExternalStore)((listener) => scope.subscribe(listener), () => scope.getSnapshot());
@@ -1169,13 +1617,7 @@ ${extra}
1169
1617
  const [baseline, setBaseline] = (0, react.useState)("");
1170
1618
  const [saving, setSaving] = (0, react.useState)(false);
1171
1619
  const [status, setStatus] = (0, react.useState)(IDLE_STATUS);
1172
- const [protocols, setProtocols] = (0, react.useState)([]);
1173
- (0, react.useEffect)(() => {
1174
- fetchProviders().then((res) => setProtocols(res.protocols.map((p) => ({
1175
- id: p.id,
1176
- label: p.label
1177
- })))).catch(() => {});
1178
- }, []);
1620
+ const protocols = useProtocols();
1179
1621
  (0, react.useEffect)(() => {
1180
1622
  if (value === void 0 || draft !== null) return;
1181
1623
  const initial = draftOf(value);
@@ -1184,12 +1626,11 @@ ${extra}
1184
1626
  }, [value, draft]);
1185
1627
  const dirty = (0, react.useMemo)(() => draft !== null && JSON.stringify(draft) !== baseline, [draft, baseline]);
1186
1628
  if (props.view === "summary") return t("card.summary");
1187
- if (value === void 0 || draft === null) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", {
1188
- className: "imsc-card",
1189
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1190
- className: "imsc-loading",
1191
- children: t("common.loading")
1192
- })
1629
+ const variant = cardVariantFor(props.view);
1630
+ if (value === void 0 || draft === null) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CardLoading, {
1631
+ prefix: "imsc",
1632
+ variant,
1633
+ text: t("common.loading")
1193
1634
  });
1194
1635
  const disabled = !snapshot.writable;
1195
1636
  const onSave = () => {
@@ -1224,24 +1665,32 @@ ${extra}
1224
1665
  });
1225
1666
  setStatus(IDLE_STATUS);
1226
1667
  };
1227
- const patchProvider = (index, next) => patch({ providers: draft.providers.map((p, i) => i === index ? {
1228
- ...p,
1229
- ...next
1230
- } : p) });
1231
- const patchParam = (index, next) => patch({ params: draft.params.map((p, i) => i === index ? {
1232
- ...p,
1233
- ...next
1234
- } : p) });
1235
- const patchPrompt = (index, next) => patch({ prompts: draft.prompts.map((p, i) => i === index ? {
1236
- ...p,
1237
- ...next
1238
- } : p) });
1668
+ const sectionProps = {
1669
+ t,
1670
+ draft,
1671
+ disabled,
1672
+ protocols,
1673
+ onPatch: patch,
1674
+ onPatchProvider: (index, next) => patch({ providers: draft.providers.map((p, i) => i === index ? {
1675
+ ...p,
1676
+ ...next
1677
+ } : p) }),
1678
+ onPatchParam: (index, next) => patch({ params: draft.params.map((p, i) => i === index ? {
1679
+ ...p,
1680
+ ...next
1681
+ } : p) }),
1682
+ onPatchPrompt: (index, next) => patch({ prompts: draft.prompts.map((p, i) => i === index ? {
1683
+ ...p,
1684
+ ...next
1685
+ } : p) })
1686
+ };
1239
1687
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(CardChrome, {
1240
1688
  prefix: "imsc",
1241
1689
  title: t("card.title"),
1242
1690
  description: t("card.description"),
1243
1691
  open,
1244
1692
  onToggle: setOpen,
1693
+ variant,
1245
1694
  statusBadge: {
1246
1695
  text: draft.providers.length > 0 ? t("card.badgeOn").replace("{n}", String(draft.providers.length)) : t("card.badgeOff"),
1247
1696
  on: draft.providers.length > 0
@@ -1250,307 +1699,28 @@ ${extra}
1250
1699
  dirtyLabel: t("common.unsaved"),
1251
1700
  readOnlyNotice: disabled ? t("card.readOnly") : void 0,
1252
1701
  status,
1253
- actions: [{
1254
- key: "discard",
1255
- label: t("card.discard"),
1256
- disabled: !dirty || saving,
1257
- onClick: () => {
1702
+ actions: cardActions({
1703
+ t,
1704
+ dirty,
1705
+ saving,
1706
+ disabled,
1707
+ onDiscard: () => {
1258
1708
  const restored = draftOf(value);
1259
1709
  setDraft(restored);
1260
1710
  setBaseline(JSON.stringify(restored));
1261
1711
  setStatus(IDLE_STATUS);
1262
- }
1263
- }, {
1264
- key: "save",
1265
- label: t(saving ? "common.saving" : "card.save"),
1266
- variant: "primary",
1267
- disabled: !dirty || saving || disabled,
1268
- onClick: onSave
1269
- }],
1712
+ },
1713
+ onSave
1714
+ }),
1270
1715
  children: [
1271
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", {
1272
- className: "imsc-groupTitle",
1273
- children: t("card.providers")
1274
- }),
1275
- draft.providers.map((provider, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1276
- className: "imsc-presetBox",
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,
1716
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ProviderPresetSection, { ...sectionProps }),
1717
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PromptPresetSection, { ...sectionProps }),
1718
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ParamPresetSection, { ...sectionProps }),
1719
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AdvancedFields, {
1720
+ t,
1721
+ draft,
1552
1722
  disabled,
1553
- onEdit: (v) => patch({ uploadTtlHours: v })
1723
+ onPatch: patch
1554
1724
  })
1555
1725
  ]
1556
1726
  });
@@ -4026,6 +4196,14 @@ ${extra}
4026
4196
  }
4027
4197
  .imsc-credRow { display: flex; align-items: center; gap: 8px; }
4028
4198
  .imsc-credRow .imsc-input { flex: 1; min-width: 0; }
4199
+ /* 分节形态(插件页 page 视图,无边框分节):预设块改为分隔线分组,去掉第二层内边距 */
4200
+ .imsc-section .imsc-presetBox {
4201
+ border: 0;
4202
+ border-top: 1px solid var(--dsw-alias-border-l2);
4203
+ border-radius: 0;
4204
+ padding: 10px 0 2px;
4205
+ margin-bottom: 0;
4206
+ }
4029
4207
 
4030
4208
  /* ── 移动端(≤767px,与 ui-mobile-fit 同断点)── */
4031
4209
  @media (max-width: 767px) {
@@ -4059,6 +4237,7 @@ ${extra}
4059
4237
  .ims-btn { min-height: 40px; }
4060
4238
  .ims-btnSmall { min-height: 32px; }
4061
4239
  .imsc-grid2 { grid-template-columns: 1fr; }
4240
+ .imsc-section .imsc-presetBox { padding: 10px 0 2px; }
4062
4241
  }
4063
4242
  `;
4064
4243
  /** 注入面板 + 入口 + 卡片附加样式(重复调用幂等:已存在则跳过)。 */