dsh-image-gen 0.7.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js CHANGED
@@ -75,6 +75,8 @@ window.__ModuleLoader__.load({
75
75
  //#region lib/types/shared.js
76
76
  /** Browser route used by the generated-image card. */
77
77
  const IMAGE_ROUTE = "/plugins/dsh-image-gen/image";
78
+ /** Browser route that uploads user-picked images as DSH attachments. */
79
+ const IMPORT_ROUTE = "/plugins/dsh-image-gen/import";
78
80
  /** Browser route used for deleting generated images and workspace files. */
79
81
  const DELETE_ROUTE = "/plugins/dsh-image-gen/delete";
80
82
  /** Same-origin route used by the browser image workbench. */
@@ -300,9 +302,11 @@ window.__ModuleLoader__.load({
300
302
  //#endregion
301
303
  //#region lib/types/client/gallery-store.js
302
304
  const DB_NAME$2 = "dsh_image_gen_db";
303
- const DB_VERSION = 2;
305
+ const DB_VERSION = 4;
304
306
  const STORE_NAME = "gallery_history";
305
307
  const TOMBSTONE_STORE = "gallery_tombstones";
308
+ const FAV_PROMPT_STORE = "favorite_prompts";
309
+ const FAV_FOLDER_STORE = "favorite_folders";
306
310
  let dbPromise = null;
307
311
  let tombstonesCache = null;
308
312
  function getDB() {
@@ -317,14 +321,25 @@ window.__ModuleLoader__.load({
317
321
  const db = event.target.result;
318
322
  if (!db.objectStoreNames.contains(STORE_NAME)) db.createObjectStore(STORE_NAME, { keyPath: "id" }).createIndex("createdAt", "createdAt", { unique: false });
319
323
  if (!db.objectStoreNames.contains(TOMBSTONE_STORE)) db.createObjectStore(TOMBSTONE_STORE, { keyPath: "id" });
324
+ if (!db.objectStoreNames.contains(FAV_PROMPT_STORE)) db.createObjectStore(FAV_PROMPT_STORE, { keyPath: "id" });
325
+ if (!db.objectStoreNames.contains(FAV_FOLDER_STORE)) db.createObjectStore(FAV_FOLDER_STORE, { keyPath: "id" });
320
326
  };
321
327
  request.onsuccess = () => {
322
- resolve(request.result);
328
+ const db = request.result;
329
+ db.onversionchange = () => {
330
+ db.close();
331
+ dbPromise = null;
332
+ };
333
+ resolve(db);
323
334
  };
324
335
  request.onerror = () => {
325
336
  dbPromise = null;
326
337
  reject(request.error);
327
338
  };
339
+ request.onblocked = () => {
340
+ dbPromise = null;
341
+ reject(/* @__PURE__ */ new Error("IndexedDB upgrade blocked. Close other open Studio tabs and retry."));
342
+ };
328
343
  });
329
344
  return dbPromise;
330
345
  }
@@ -551,6 +566,7 @@ window.__ModuleLoader__.load({
551
566
  */
552
567
  function isItemInWorkspace(item, workspace) {
553
568
  if (!workspace || !workspace.workspaceId && !workspace.path && (!workspace.sessionIds || workspace.sessionIds.length === 0)) return true;
569
+ if (!item.workspaceId && !item.sessionId && !item.workspacePath && !item.savedTo) return true;
554
570
  if (item.workspaceId && workspace.workspaceId && item.workspaceId === workspace.workspaceId) return true;
555
571
  if (item.sessionId && Array.isArray(workspace.sessionIds) && workspace.sessionIds.includes(item.sessionId)) return true;
556
572
  if (item.workspacePath && workspace.path) {
@@ -563,6 +579,234 @@ window.__ModuleLoader__.load({
563
579
  }
564
580
  return false;
565
581
  }
582
+ const favoriteListeners = /* @__PURE__ */ new Set();
583
+ function notifyFavorites() {
584
+ for (const listener of favoriteListeners) try {
585
+ listener();
586
+ } catch (err) {
587
+ console.error("[dsh-image-gen] Favorites listener error:", err);
588
+ }
589
+ }
590
+ /** Subscribe to favorites mutations (insert/delete). */
591
+ function subscribeFavorites(listener) {
592
+ favoriteListeners.add(listener);
593
+ return () => {
594
+ favoriteListeners.delete(listener);
595
+ };
596
+ }
597
+ /** Stable base36 id for a favorite prompt; identical trimmed text dedupes. */
598
+ function promptIdOf(text) {
599
+ let hash = 5381;
600
+ for (let i = 0; i < text.length; i++) hash = (hash << 5) + hash + text.charCodeAt(i) | 0;
601
+ return `p${(hash >>> 0).toString(36)}-${text.length.toString(36)}`;
602
+ }
603
+ /**
604
+ * Collision-free prompt id. Distinct texts can share the djb2 hash and the
605
+ * length, which would silently overwrite an unrelated saved prompt; suffix
606
+ * until the id is free or already owned by the same text.
607
+ */
608
+ async function uniquePromptId(db, text) {
609
+ const rows = await readAll(db, FAV_PROMPT_STORE);
610
+ const byId = new Map(rows.map((row) => [row.id, row]));
611
+ const base = promptIdOf(text);
612
+ let candidate = base;
613
+ let suffix = 1;
614
+ while (byId.has(candidate) && byId.get(candidate)?.text !== text) {
615
+ suffix++;
616
+ candidate = `${base}-${suffix}`;
617
+ }
618
+ return candidate;
619
+ }
620
+ function readAll(db, storeName) {
621
+ return new Promise((resolve, reject) => {
622
+ const req = db.transaction(storeName, "readonly").objectStore(storeName).openCursor();
623
+ const rows = [];
624
+ req.onsuccess = (event) => {
625
+ const cursor = event.target.result;
626
+ if (cursor) {
627
+ rows.push(cursor.value);
628
+ cursor.continue();
629
+ } else resolve(rows);
630
+ };
631
+ req.onerror = () => reject(req.error);
632
+ });
633
+ }
634
+ /**
635
+ * Keep one prompt in the favorites rail; identical trimmed text overwrites
636
+ * (one record per distinct prompt).
637
+ * @returns false when the write failed.
638
+ */
639
+ async function saveFavoritePrompt(text) {
640
+ const trimmed = text.trim();
641
+ if (trimmed.length === 0) return false;
642
+ try {
643
+ const db = await getDB();
644
+ const id = await uniquePromptId(db, trimmed);
645
+ await new Promise((resolve, reject) => {
646
+ const req = db.transaction(FAV_PROMPT_STORE, "readwrite").objectStore(FAV_PROMPT_STORE).put({
647
+ id,
648
+ text: trimmed,
649
+ addedAt: Date.now()
650
+ });
651
+ req.onsuccess = () => resolve();
652
+ req.onerror = () => reject(req.error);
653
+ });
654
+ notifyFavorites();
655
+ return true;
656
+ } catch (err) {
657
+ console.warn("[dsh-image-gen] Failed to save favorite prompt:", err);
658
+ return false;
659
+ }
660
+ }
661
+ /** Prompt favorites, newest first. */
662
+ async function getFavoritePrompts() {
663
+ try {
664
+ return (await readAll(await getDB(), FAV_PROMPT_STORE)).sort((a, b) => b.addedAt - a.addedAt);
665
+ } catch (err) {
666
+ console.warn("[dsh-image-gen] Failed to read favorite prompts:", err);
667
+ return [];
668
+ }
669
+ }
670
+ /** Drop one prompt favorite. */
671
+ async function deleteFavoritePrompt(id) {
672
+ try {
673
+ const db = await getDB();
674
+ await new Promise((resolve, reject) => {
675
+ const req = db.transaction(FAV_PROMPT_STORE, "readwrite").objectStore(FAV_PROMPT_STORE).delete(id);
676
+ req.onsuccess = () => resolve();
677
+ req.onerror = () => reject(req.error);
678
+ });
679
+ notifyFavorites();
680
+ } catch (err) {
681
+ console.warn("[dsh-image-gen] Failed to delete favorite prompt:", err);
682
+ }
683
+ }
684
+ function folderIdOf(kind, name) {
685
+ return `f-${kind}-${name.trim().toLowerCase().replace(/\s+/g, "-")}-${name.length.toString(36)}`;
686
+ }
687
+ /** Create a favorites folder; returns null when the name is empty or already used for the kind. */
688
+ async function addFavoriteFolder(kind, name) {
689
+ const trimmed = name.trim();
690
+ if (trimmed === "") return null;
691
+ try {
692
+ const db = await getDB();
693
+ if ((await readAll(db, FAV_FOLDER_STORE)).some((folder) => folder.kind === kind && folder.name === trimmed)) return null;
694
+ const folder = {
695
+ id: folderIdOf(kind, trimmed),
696
+ kind,
697
+ name: trimmed,
698
+ addedAt: Date.now()
699
+ };
700
+ await new Promise((resolve, reject) => {
701
+ const req = db.transaction(FAV_FOLDER_STORE, "readwrite").objectStore(FAV_FOLDER_STORE).put(folder);
702
+ req.onsuccess = () => resolve();
703
+ req.onerror = () => reject(req.error);
704
+ });
705
+ notifyFavorites();
706
+ return folder;
707
+ } catch (err) {
708
+ console.warn("[dsh-image-gen] Failed to add favorite folder:", err);
709
+ return null;
710
+ }
711
+ }
712
+ /** List all favorite folders. */
713
+ async function getFavoriteFolders() {
714
+ try {
715
+ return (await readAll(await getDB(), FAV_FOLDER_STORE)).sort((a, b) => a.addedAt - b.addedAt);
716
+ } catch (err) {
717
+ console.warn("[dsh-image-gen] Failed to read favorite folders:", err);
718
+ return [];
719
+ }
720
+ }
721
+ /** Delete a folder; member favorites survive and become unfiled. */
722
+ async function deleteFavoriteFolder(id) {
723
+ try {
724
+ const db = await getDB();
725
+ await new Promise((resolve, reject) => {
726
+ const tx = db.transaction(FAV_PROMPT_STORE, "readwrite");
727
+ const req = tx.objectStore(FAV_PROMPT_STORE).openCursor();
728
+ req.onsuccess = (event) => {
729
+ const cursor = event.target.result;
730
+ if (cursor) {
731
+ const row = cursor.value;
732
+ if (row.folderId === id) {
733
+ delete row.folderId;
734
+ cursor.update(row);
735
+ }
736
+ cursor.continue();
737
+ }
738
+ };
739
+ tx.oncomplete = () => resolve();
740
+ tx.onerror = () => reject(tx.error);
741
+ });
742
+ await new Promise((resolve, reject) => {
743
+ const req = db.transaction(FAV_FOLDER_STORE, "readwrite").objectStore(FAV_FOLDER_STORE).delete(id);
744
+ req.onsuccess = () => resolve();
745
+ req.onerror = () => reject(req.error);
746
+ });
747
+ notifyFavorites();
748
+ } catch (err) {
749
+ console.warn("[dsh-image-gen] Failed to delete favorite folder:", err);
750
+ }
751
+ }
752
+ async function moveFavorite(storeName, id, folderId) {
753
+ try {
754
+ const db = await getDB();
755
+ const ok = await new Promise((resolve, reject) => {
756
+ const tx = db.transaction(storeName, "readwrite");
757
+ const req = tx.objectStore(storeName).get(id);
758
+ req.onsuccess = () => {
759
+ const row = req.result;
760
+ if (row === void 0) {
761
+ resolve(false);
762
+ return;
763
+ }
764
+ if (folderId === void 0) delete row.folderId;
765
+ else row.folderId = folderId;
766
+ tx.objectStore(storeName).put(row);
767
+ resolve(true);
768
+ };
769
+ tx.onerror = () => reject(tx.error);
770
+ });
771
+ if (ok) notifyFavorites();
772
+ return ok;
773
+ } catch (err) {
774
+ console.warn("[dsh-image-gen] Failed to move favorite:", err);
775
+ return false;
776
+ }
777
+ }
778
+ /** File a favorite prompt into a folder (undefined unfiles it). */
779
+ function moveFavoritePrompt(id, folderId) {
780
+ return moveFavorite(FAV_PROMPT_STORE, id, folderId);
781
+ }
782
+ /** Rewrite a favorite prompt's text in place, keeping its id stable. */
783
+ async function updateFavoritePrompt(id, text) {
784
+ const trimmed = text.trim();
785
+ if (trimmed === "") return false;
786
+ try {
787
+ const db = await getDB();
788
+ const ok = await new Promise((resolve, reject) => {
789
+ const tx = db.transaction(FAV_PROMPT_STORE, "readwrite");
790
+ const req = tx.objectStore(FAV_PROMPT_STORE).get(id);
791
+ req.onsuccess = () => {
792
+ const row = req.result;
793
+ if (row === void 0) {
794
+ resolve(false);
795
+ return;
796
+ }
797
+ row.text = trimmed;
798
+ tx.objectStore(FAV_PROMPT_STORE).put(row);
799
+ resolve(true);
800
+ };
801
+ tx.onerror = () => reject(tx.error);
802
+ });
803
+ if (ok) notifyFavorites();
804
+ return ok;
805
+ } catch (err) {
806
+ console.warn("[dsh-image-gen] Failed to update favorite prompt:", err);
807
+ return false;
808
+ }
809
+ }
566
810
  //#endregion
567
811
  //#region node_modules/.pnpm/lucide-react@1.38.0_react@18.3.1/node_modules/lucide-react/dist/esm/shared/src/utils/mergeClasses.mjs
568
812
  /**
@@ -1209,6 +1453,34 @@ window.__ModuleLoader__.load({
1209
1453
  * This source code is licensed under the ISC license.
1210
1454
  * See the LICENSE file in the root directory of this source tree.
1211
1455
  */
1456
+ const Shuffle = createLucideIcon("shuffle", [
1457
+ ["path", {
1458
+ d: "m18 14 4 4-4 4",
1459
+ key: "10pe0f"
1460
+ }],
1461
+ ["path", {
1462
+ d: "m18 2 4 4-4 4",
1463
+ key: "pucp1d"
1464
+ }],
1465
+ ["path", {
1466
+ d: "M2 18h1.973a4 4 0 0 0 3.3-1.7l5.454-8.6a4 4 0 0 1 3.3-1.7H22",
1467
+ key: "1ailkh"
1468
+ }],
1469
+ ["path", {
1470
+ d: "M2 6h1.972a4 4 0 0 1 3.6 2.2",
1471
+ key: "km57vx"
1472
+ }],
1473
+ ["path", {
1474
+ d: "M22 18h-6.041a4 4 0 0 1-3.3-1.8l-.359-.45",
1475
+ key: "os18l9"
1476
+ }]
1477
+ ]);
1478
+ /**
1479
+ * @license lucide-react v1.38.0 - ISC
1480
+ *
1481
+ * This source code is licensed under the ISC license.
1482
+ * See the LICENSE file in the root directory of this source tree.
1483
+ */
1212
1484
  const SlidersHorizontal = createLucideIcon("sliders-horizontal", [
1213
1485
  ["path", {
1214
1486
  d: "M10 5H3",
@@ -115760,6 +116032,24 @@ img.ProseMirror-separator {
115760
116032
  }
115761
116033
  });
115762
116034
  }
116035
+ /**
116036
+ * Match tldraw's light/dark canvas to the DSH host theme. The host publishes
116037
+ * dark mode as the data-ds-dark-theme attribute on <body>; tldraw keeps its
116038
+ * own persisted user preference, so apply the host value now and observe
116039
+ * later flips. Returns the observer disposer.
116040
+ */
116041
+ function syncTldrawThemeWithHost(editor) {
116042
+ const apply = () => {
116043
+ editor.user.updateUserPreferences({ colorScheme: document.body.hasAttribute("data-ds-dark-theme") ? "dark" : "light" });
116044
+ };
116045
+ apply();
116046
+ const observer = new MutationObserver(apply);
116047
+ observer.observe(document.body, {
116048
+ attributes: true,
116049
+ attributeFilter: ["data-ds-dark-theme"]
116050
+ });
116051
+ return () => observer.disconnect();
116052
+ }
115763
116053
  //#endregion
115764
116054
  //#region lib/types/client/tl/canvas-selection.js
115765
116055
  /** Bound raster dimensions before exporting; large multi-image PNGs get a
@@ -116425,6 +116715,7 @@ img.ProseMirror-separator {
116425
116715
  editor.run(() => editor.selectNone());
116426
116716
  mountedEditors.add(editor);
116427
116717
  applyBrandTheme(editor);
116718
+ const stopThemeSync = syncTldrawThemeWithHost(editor);
116428
116719
  editor.updateInstanceState({ isGridMode: true });
116429
116720
  console.info(`[dsh-image-gen] tldraw mounted (instance ${editor.id})`);
116430
116721
  const stopLanding = registerTlLandingConsumer((items) => {
@@ -116460,6 +116751,7 @@ img.ProseMirror-separator {
116460
116751
  mountedEditors.delete(editor);
116461
116752
  mountedCanvases.delete(clear);
116462
116753
  stopLanding();
116754
+ stopThemeSync();
116463
116755
  stopSync();
116464
116756
  retrySync.current = () => {};
116465
116757
  };
@@ -116504,6 +116796,31 @@ img.ProseMirror-separator {
116504
116796
  unconfigured: "未配置",
116505
116797
  recent: "最近生成",
116506
116798
  empty: "暂无生成历史",
116799
+ railTabRecent: "最新生成",
116800
+ railTabFavorites: "收藏",
116801
+ favImages: "收藏图片",
116802
+ favPrompts: "收藏提示词",
116803
+ favNewFolder: "新建文件夹",
116804
+ favFolderPlaceholder: "文件夹名称",
116805
+ favCreate: "创建",
116806
+ favCancel: "取消",
116807
+ favMoveTo: "移动到文件夹",
116808
+ favNoFolder: "未分组",
116809
+ favMove: "移动",
116810
+ favDeleteFolder: "删除文件夹",
116811
+ favEditPrompt: "编辑提示词",
116812
+ favUse: "使用",
116813
+ favSave: "保存",
116814
+ favEditPromptPlaceholder: "输入提示词内容…",
116815
+ favFolder: "所属文件夹",
116816
+ favEmptyImages: "在图片操作区点「收藏」后,图片会显示在这里",
116817
+ favEmptyPrompts: "点提示词输入框旁的「☆ 收藏」后,提示词会显示在这里",
116818
+ favoritePrompt: "收藏提示词",
116819
+ favActionShort: "收藏",
116820
+ favPromptSaved: "已收藏提示词",
116821
+ favPromptApplied: "已填入提示词",
116822
+ favRefApplied: "已加入参考图",
116823
+ favSaveFailed: "收藏失败,请重试",
116507
116824
  generate: "文生图",
116508
116825
  edit: "图生图",
116509
116826
  reference: "参考图",
@@ -116560,6 +116877,8 @@ img.ProseMirror-separator {
116560
116877
  infiniteCanvas: "无限画布",
116561
116878
  loading: "正在读取图片…",
116562
116879
  generationFailed: "生成失败",
116880
+ gatewayTimeout: "请求被反向代理中断(超时):请重试;反复出现需调大代理超时或换直连网络",
116881
+ requestFailed: "请求失败(HTTP {status})",
116563
116882
  selectHistory: "从左侧选择一张图片,或在右侧开始新的生成。",
116564
116883
  created: "生成时间",
116565
116884
  elapsed: "耗时",
@@ -116567,6 +116886,7 @@ img.ProseMirror-separator {
116567
116886
  output: "输出参数",
116568
116887
  closeReference: "移除参考图",
116569
116888
  uploadInvalid: "请选择有效的图片文件(最大 10MB)",
116889
+ uploadUnreadable: "部分图片读取失败,请重新选择后再试",
116570
116890
  imageLoadFailed: "图片读取失败",
116571
116891
  fullscreen: "大图全屏",
116572
116892
  close: "关闭",
@@ -116613,6 +116933,31 @@ img.ProseMirror-separator {
116613
116933
  unconfigured: "Not configured",
116614
116934
  recent: "Recent generations",
116615
116935
  empty: "No generated images yet",
116936
+ railTabRecent: "Latest",
116937
+ railTabFavorites: "Favorites",
116938
+ favImages: "Favorite images",
116939
+ favPrompts: "Favorite prompts",
116940
+ favNewFolder: "New folder",
116941
+ favFolderPlaceholder: "Folder name",
116942
+ favCreate: "Create",
116943
+ favCancel: "Cancel",
116944
+ favMoveTo: "Move to folder",
116945
+ favNoFolder: "Unfiled",
116946
+ favMove: "Move",
116947
+ favDeleteFolder: "Delete folder",
116948
+ favEditPrompt: "Edit prompt",
116949
+ favUse: "Use",
116950
+ favSave: "Save",
116951
+ favEditPromptPlaceholder: "Prompt text…",
116952
+ favFolder: "Folder",
116953
+ favEmptyImages: "Click Favorite on an image; favorited images appear here",
116954
+ favEmptyPrompts: "Click ☆ Save beside the prompt box",
116955
+ favoritePrompt: "Save prompt",
116956
+ favActionShort: "Save",
116957
+ favPromptSaved: "Prompt saved",
116958
+ favPromptApplied: "Prompt applied",
116959
+ favRefApplied: "Reference added",
116960
+ favSaveFailed: "Could not save; please retry",
116616
116961
  generate: "Text to image",
116617
116962
  edit: "Image to image",
116618
116963
  reference: "Reference image",
@@ -116669,6 +117014,8 @@ img.ProseMirror-separator {
116669
117014
  infiniteCanvas: "Infinite canvas",
116670
117015
  loading: "Loading image…",
116671
117016
  generationFailed: "Generation failed",
117017
+ gatewayTimeout: "The reverse proxy cut the request (timeout): retry; if it repeats, raise the proxy timeout or use a direct network.",
117018
+ requestFailed: "Request failed (HTTP {status})",
116672
117019
  selectHistory: "Select an image on the left, or start a new generation on the right.",
116673
117020
  created: "Created",
116674
117021
  elapsed: "Elapsed",
@@ -116676,6 +117023,7 @@ img.ProseMirror-separator {
116676
117023
  output: "Output",
116677
117024
  closeReference: "Remove reference",
116678
117025
  uploadInvalid: "Choose a valid image file up to 10MB.",
117026
+ uploadUnreadable: "Some images could not be read; pick them again.",
116679
117027
  imageLoadFailed: "Could not load image",
116680
117028
  fullscreen: "Fullscreen",
116681
117029
  close: "Close",
@@ -116719,17 +117067,28 @@ img.ProseMirror-separator {
116719
117067
  };
116720
117068
  /** localStorage flag remembering that the one-time infinite-canvas hint was dismissed. */
116721
117069
  const INFINITE_CANVAS_HINT_KEY = "dsh-ig-infinite-canvas-hint-dismissed";
116722
- const StudioView = ({ locale, credentialEvents, workspace, initialPrompt, initialCanvasSurface, showInfiniteCanvasHint, onInitialPromptApplied, onOpenInspiration }) => {
117070
+ const StudioView = ({ locale, credentialEvents, workspace, initialPrompt, initialReference, initialCanvasSurface, showInfiniteCanvasHint, onInitialPromptApplied, onInitialReferenceApplied, onOpenInspiration }) => {
116723
117071
  const [lang, setLang] = (0, react.useState)(() => locale?.getSnapshot?.().active?.startsWith("en") ? "en" : "zh");
116724
117072
  const [config, setConfig] = (0, react.useState)(null);
116725
117073
  const [configLoading, setConfigLoading] = (0, react.useState)(true);
116726
117074
  const [configError, setConfigError] = (0, react.useState)(null);
116727
117075
  const [items, setItems] = (0, react.useState)([]);
117076
+ /** Which rail tab is showing: generation history or the favorites rail. */
117077
+ const [railTab, setRailTab] = (0, react.useState)("recent");
117078
+ const [favPrompts, setFavPrompts] = (0, react.useState)([]);
117079
+ const [favFolders, setFavFolders] = (0, react.useState)([]);
117080
+ const [activePromptFolder, setActivePromptFolder] = (0, react.useState)("");
117081
+ const [folderDraft, setFolderDraft] = (0, react.useState)(null);
117082
+ const [folderDraftName, setFolderDraftName] = (0, react.useState)("");
117083
+ const [editingPrompt, setEditingPrompt] = (0, react.useState)(null);
117084
+ const [editPromptText, setEditPromptText] = (0, react.useState)("");
117085
+ const [editPromptFolder, setEditPromptFolder] = (0, react.useState)("");
117086
+ const [movingFav, setMovingFav] = (0, react.useState)(null);
116728
117087
  const [sidebarCollapsed, setSidebarCollapsed] = (0, react.useState)(false);
116729
117088
  /** Manual-only collapse for the right generate form; the form stays put in
116730
117089
  * narrow seats because it is the primary generation surface. Starts folded
116731
117090
  * when the studio opens straight into the infinite canvas. */
116732
- const [generateCollapsed, setGenerateCollapsed] = (0, react.useState)(initialCanvasSurface === "infinite");
117091
+ const [generateCollapsed, setGenerateCollapsed] = (0, react.useState)(initialCanvasSurface === "infinite" && initialPrompt === void 0);
116733
117092
  /** Workbench root, observed so a narrow column (right sidebar) can fold the recent rail. */
116734
117093
  const workbenchRootRef = (0, react.useRef)(null);
116735
117094
  /** Set once the user toggles the rail by hand; auto-folding then stands down for this mount. */
@@ -116833,6 +117192,15 @@ img.ProseMirror-separator {
116833
117192
  for (const [name, value] of Object.entries(values ?? {})) text = text.replace(`{${name}}`, value);
116834
117193
  return text;
116835
117194
  };
117195
+ /** Parse a studio response body; a gateway HTML error page (502/504 from a
117196
+ * reverse proxy) must surface as a readable message, not a JSON SyntaxError. */
117197
+ const readStudioBody = async (response) => {
117198
+ try {
117199
+ return await response.json();
117200
+ } catch {
117201
+ return { error: response.status === 502 || response.status === 504 ? t("gatewayTimeout") : t("requestFailed", { status: String(response.status) }) };
117202
+ }
117203
+ };
116836
117204
  (0, react.useEffect)(() => locale?.subscribe?.(() => setLang(locale.getSnapshot().active?.startsWith("en") ? "en" : "zh")), [locale]);
116837
117205
  const formStateRef = (0, react.useRef)({
116838
117206
  provider,
@@ -116854,7 +117222,7 @@ img.ProseMirror-separator {
116854
117222
  signal: controller.signal,
116855
117223
  credentials: "same-origin"
116856
117224
  });
116857
- const payload = await response.json();
117225
+ const payload = await readStudioBody(response);
116858
117226
  if (!response.ok || !("providers" in payload)) throw new Error("error" in payload && payload.error ? payload.error : "Studio unavailable");
116859
117227
  setConfig(payload);
116860
117228
  if (silent) {
@@ -116900,6 +117268,20 @@ img.ProseMirror-separator {
116900
117268
  unsubscribe();
116901
117269
  };
116902
117270
  }, []);
117271
+ (0, react.useEffect)(() => {
117272
+ let mounted = true;
117273
+ const load = () => void Promise.all([getFavoritePrompts(), getFavoriteFolders()]).then(([prompts, folders]) => {
117274
+ if (!mounted) return;
117275
+ setFavPrompts(prompts);
117276
+ setFavFolders(folders);
117277
+ });
117278
+ load();
117279
+ const unsubscribe = subscribeFavorites(load);
117280
+ return () => {
117281
+ mounted = false;
117282
+ unsubscribe();
117283
+ };
117284
+ }, []);
116903
117285
  (0, react.useEffect)(() => {
116904
117286
  return () => {
116905
117287
  requestRef.current?.abort();
@@ -117024,7 +117406,65 @@ img.ProseMirror-separator {
117024
117406
  };
117025
117407
  };
117026
117408
  const handleImageClick = () => {};
117027
- const addReferenceFiles = (fileList) => {
117409
+ /** Keep the current prompt text in the favorites rail. */
117410
+ const handleFavoritePrompt = async () => {
117411
+ if (prompt.trim().length === 0) {
117412
+ setError(t("needPrompt"));
117413
+ return;
117414
+ }
117415
+ if (await saveFavoritePrompt(prompt)) {
117416
+ railToggledByUserRef.current = true;
117417
+ setSidebarCollapsed(false);
117418
+ setRailTab("favorites");
117419
+ flash(t("favPromptSaved"));
117420
+ } else setError(t("favSaveFailed"));
117421
+ };
117422
+ /** Turn one favorited gallery image back into a live reference (edit mode). */
117423
+ const applyFavoriteImage = async (item) => {
117424
+ const max = maxReferences;
117425
+ if (referencesRef.current.length >= max) {
117426
+ setError(t("maxReferencesExceeded", { max: String(max) }));
117427
+ return;
117428
+ }
117429
+ try {
117430
+ const blob = await fetchAttachmentBlob(item.attachment);
117431
+ const newItem = {
117432
+ id: `fav-${item.id}-${Date.now()}`,
117433
+ attachment: item.attachment,
117434
+ previewUrl: URL.createObjectURL(blob)
117435
+ };
117436
+ const next = [...referencesRef.current, newItem];
117437
+ setReferences(next);
117438
+ referencesRef.current = next;
117439
+ setMode("edit");
117440
+ setError(null);
117441
+ flash(t("favRefApplied"));
117442
+ } catch {
117443
+ setError(t("imageLoadFailed"));
117444
+ }
117445
+ };
117446
+ const createFavFolder = async () => {
117447
+ if (await addFavoriteFolder("prompt", folderDraftName) === null) {
117448
+ flash(t("favSaveFailed"));
117449
+ return;
117450
+ }
117451
+ setFolderDraft(null);
117452
+ setFolderDraftName("");
117453
+ };
117454
+ const openPromptEditor = (fav) => {
117455
+ setEditingPrompt(fav);
117456
+ setEditPromptText(fav.text);
117457
+ setEditPromptFolder(fav.folderId ?? "");
117458
+ };
117459
+ const doMoveFavorite = (folderId) => {
117460
+ if (movingFav === null) return;
117461
+ moveFavoritePrompt(movingFav.id, folderId);
117462
+ };
117463
+ const applyFavoritePrompt = (fav) => {
117464
+ setPrompt(fav.text);
117465
+ flash(t("favPromptApplied"));
117466
+ };
117467
+ const addReferenceFiles = async (fileList) => {
117028
117468
  if (!fileList || fileList.length === 0) return;
117029
117469
  const files = Array.from(fileList);
117030
117470
  const validFiles = [];
@@ -117050,13 +117490,31 @@ img.ProseMirror-separator {
117050
117490
  const toAdd = validFiles.slice(0, available);
117051
117491
  if (validFiles.length > available) setError(t("maxReferencesExceeded", { max: String(maxReferences) }));
117052
117492
  else setError(null);
117053
- const newItems = toAdd.map((file, idx) => ({
117493
+ const snapshotted = [];
117494
+ for (const file of toAdd) try {
117495
+ const bytes = await file.arrayBuffer();
117496
+ snapshotted.push(new File([bytes], file.name, {
117497
+ type: file.type,
117498
+ lastModified: file.lastModified
117499
+ }));
117500
+ } catch {
117501
+ setError(t("uploadUnreadable"));
117502
+ }
117503
+ const newItems = snapshotted.map((file, idx) => ({
117054
117504
  id: `upload-${Date.now()}-${idx}-${Math.random().toString(36).slice(2, 7)}`,
117055
117505
  file,
117056
117506
  previewUrl: URL.createObjectURL(file)
117057
117507
  }));
117058
117508
  setReferences((prev) => [...prev, ...newItems]);
117059
117509
  };
117510
+ const appliedReferenceRef = (0, react.useRef)(null);
117511
+ (0, react.useEffect)(() => {
117512
+ if (initialReference === void 0 || appliedReferenceRef.current === initialReference) return;
117513
+ appliedReferenceRef.current = initialReference;
117514
+ addReferenceFiles([initialReference]);
117515
+ setMode("edit");
117516
+ onInitialReferenceApplied?.();
117517
+ }, [initialReference, onInitialReferenceApplied]);
117060
117518
  const removeReference = (id) => {
117061
117519
  const target = referencesRef.current.find((item) => item.id === id);
117062
117520
  if (target && target.previewUrl.startsWith("blob:")) URL.revokeObjectURL(target.previewUrl);
@@ -117188,7 +117646,7 @@ img.ProseMirror-separator {
117188
117646
  ...referencesPayload === void 0 ? {} : { references: referencesPayload }
117189
117647
  })
117190
117648
  });
117191
- const payload = await response.json();
117649
+ const payload = await readStudioBody(response);
117192
117650
  if (!response.ok || !("attachment" in payload)) throw new Error("error" in payload && payload.error ? payload.error : `${target.profile.label}: ${t("generationFailed")}`);
117193
117651
  return {
117194
117652
  payload,
@@ -117251,7 +117709,7 @@ img.ProseMirror-separator {
117251
117709
  ...referencesPayload === void 0 ? {} : { references: referencesPayload }
117252
117710
  })
117253
117711
  });
117254
- const payload = await response.json();
117712
+ const payload = await readStudioBody(response);
117255
117713
  if (!response.ok || !("attachment" in payload)) throw new Error("error" in payload && payload.error ? payload.error : t("generationFailed"));
117256
117714
  const generatedList = Array.isArray(payload.items) && payload.items.length > 0 ? payload.items : [{
117257
117715
  attachment: payload.attachment,
@@ -117323,8 +117781,8 @@ img.ProseMirror-separator {
117323
117781
  selected,
117324
117782
  selectedBatchItems
117325
117783
  ]);
117326
- const saveButtonLabel = currentBatch !== null ? pendingGalleryItems.length > 0 ? t("saveSelected", { count: String(pendingGalleryItems.length) }) : selectedBatchItems.length === 0 ? t("saveSelected", { count: "0" }) : t("inGallery") : isSelectedInGallery ? t("inGallery") : t("saveToGallery");
117327
- const saveSelectionComplete = currentBatch === null ? isSelectedInGallery : selectedBatchItems.length > 0 && pendingGalleryItems.length === 0;
117784
+ const saveButtonLabel = currentBatch !== null ? pendingGalleryItems.length > 0 ? t("saveSelected", { count: String(pendingGalleryItems.length) }) : selectedBatchItems.length === 0 ? t("saveSelected", { count: "0" }) : t("inGallery") : pendingGalleryItems.length > 0 ? t("saveToGallery") : selected !== null ? t("inGallery") : t("saveToGallery");
117785
+ const saveSelectionComplete = currentBatch === null ? pendingGalleryItems.length === 0 : selectedBatchItems.length > 0 && pendingGalleryItems.length === 0;
117328
117786
  const saveGalleryEntry = async (item) => {
117329
117787
  let savedTo = item.savedTo;
117330
117788
  const targetRoot = workspace?.path || config?.workspaceRoot;
@@ -117504,44 +117962,272 @@ img.ProseMirror-separator {
117504
117962
  children: [
117505
117963
  !sidebarCollapsed && (0, react_jsx_runtime.jsxs)("aside", {
117506
117964
  className: "dsh-ig-recent-panel",
117507
- children: [(0, react_jsx_runtime.jsxs)("div", {
117508
- className: "dsh-ig-panel-title",
117509
- children: [(0, react_jsx_runtime.jsxs)("div", {
117510
- style: {
117511
- display: "flex",
117512
- alignItems: "center",
117513
- gap: "8px"
117514
- },
117515
- children: [(0, react_jsx_runtime.jsx)("span", { children: t("recent") }), (0, react_jsx_runtime.jsx)("span", {
117516
- className: "dsh-ig-count-badge",
117517
- children: items.length
117965
+ children: [
117966
+ (0, react_jsx_runtime.jsxs)("div", {
117967
+ className: "dsh-ig-panel-title",
117968
+ children: [(0, react_jsx_runtime.jsxs)("div", {
117969
+ style: {
117970
+ display: "flex",
117971
+ alignItems: "center",
117972
+ gap: "8px"
117973
+ },
117974
+ children: [(0, react_jsx_runtime.jsx)("span", { children: railTab === "recent" ? t("recent") : t("railTabFavorites") }), (0, react_jsx_runtime.jsx)("span", {
117975
+ className: "dsh-ig-count-badge",
117976
+ children: railTab === "recent" ? items.length : items.filter((item) => item.isFavorite).length + favPrompts.length
117977
+ })]
117978
+ }), (0, react_jsx_runtime.jsx)("button", {
117979
+ type: "button",
117980
+ className: "dsh-ig-collapse-btn",
117981
+ onClick: () => {
117982
+ railToggledByUserRef.current = true;
117983
+ setSidebarCollapsed(true);
117984
+ },
117985
+ title: t("collapseSidebar"),
117986
+ children: (0, react_jsx_runtime.jsx)(PanelLeftClose, { size: 15 })
117518
117987
  })]
117519
- }), (0, react_jsx_runtime.jsx)("button", {
117520
- type: "button",
117521
- className: "dsh-ig-collapse-btn",
117522
- onClick: () => {
117523
- railToggledByUserRef.current = true;
117524
- setSidebarCollapsed(true);
117525
- },
117526
- title: t("collapseSidebar"),
117527
- children: (0, react_jsx_runtime.jsx)(PanelLeftClose, { size: 15 })
117528
- })]
117529
- }), (0, react_jsx_runtime.jsxs)("div", {
117530
- className: "dsh-ig-recent-scroll",
117531
- children: [items.length === 0 ? (0, react_jsx_runtime.jsxs)("div", {
117532
- className: "dsh-ig-recent-empty",
117533
- children: [(0, react_jsx_runtime.jsx)(ImagePlus, { size: 22 }), (0, react_jsx_runtime.jsx)("span", { children: t("empty") })]
117534
- }) : displayItems.map((item) => (0, react_jsx_runtime.jsx)(RecentItem, {
117535
- item,
117536
- active: selected?.id === item.id,
117537
- onClick: () => selectItem(item)
117538
- }, item.id)), items.length > visibleLimit && (0, react_jsx_runtime.jsx)("button", {
117539
- type: "button",
117540
- className: "dsh-ig-load-more",
117541
- onClick: () => setVisibleLimit((l) => l + 30),
117542
- children: t("loadMore", { n: String(items.length - visibleLimit) })
117543
- })]
117544
- })]
117988
+ }),
117989
+ (0, react_jsx_runtime.jsxs)("div", {
117990
+ className: "dsh-ig-rail-tabs",
117991
+ role: "tablist",
117992
+ children: [(0, react_jsx_runtime.jsx)("button", {
117993
+ type: "button",
117994
+ role: "tab",
117995
+ "aria-selected": railTab === "recent",
117996
+ className: `dsh-ig-rail-tab ${railTab === "recent" ? "is-active" : ""}`,
117997
+ onClick: () => setRailTab("recent"),
117998
+ children: t("railTabRecent")
117999
+ }), (0, react_jsx_runtime.jsx)("button", {
118000
+ type: "button",
118001
+ role: "tab",
118002
+ "aria-selected": railTab === "favorites",
118003
+ className: `dsh-ig-rail-tab ${railTab === "favorites" ? "is-active" : ""}`,
118004
+ onClick: () => setRailTab("favorites"),
118005
+ children: t("railTabFavorites")
118006
+ })]
118007
+ }),
118008
+ railTab === "recent" ? (0, react_jsx_runtime.jsxs)("div", {
118009
+ className: "dsh-ig-recent-scroll",
118010
+ children: [items.length === 0 ? (0, react_jsx_runtime.jsxs)("div", {
118011
+ className: "dsh-ig-recent-empty",
118012
+ children: [(0, react_jsx_runtime.jsx)(ImagePlus, { size: 22 }), (0, react_jsx_runtime.jsx)("span", { children: t("empty") })]
118013
+ }) : displayItems.map((item) => (0, react_jsx_runtime.jsx)(RecentItem, {
118014
+ item,
118015
+ active: selected?.id === item.id,
118016
+ onClick: () => selectItem(item)
118017
+ }, item.id)), items.length > visibleLimit && (0, react_jsx_runtime.jsx)("button", {
118018
+ type: "button",
118019
+ className: "dsh-ig-load-more",
118020
+ onClick: () => setVisibleLimit((l) => l + 30),
118021
+ children: t("loadMore", { n: String(items.length - visibleLimit) })
118022
+ })]
118023
+ }) : (0, react_jsx_runtime.jsxs)("div", {
118024
+ className: "dsh-ig-fav-scroll",
118025
+ children: [
118026
+ (0, react_jsx_runtime.jsx)("div", {
118027
+ className: "dsh-ig-fav-section-label",
118028
+ children: (0, react_jsx_runtime.jsx)("span", { children: t("favImages") })
118029
+ }),
118030
+ (() => {
118031
+ const list = items.filter((item) => item.isFavorite);
118032
+ if (list.length === 0) return (0, react_jsx_runtime.jsxs)("div", {
118033
+ className: "dsh-ig-fav-empty",
118034
+ children: [(0, react_jsx_runtime.jsx)(ImagePlus, { size: 18 }), (0, react_jsx_runtime.jsx)("span", { children: t("favEmptyImages") })]
118035
+ });
118036
+ return (0, react_jsx_runtime.jsx)("div", {
118037
+ className: "dsh-ig-fav-grid",
118038
+ children: list.map((item) => (0, react_jsx_runtime.jsx)(RecentItem, {
118039
+ item,
118040
+ active: false,
118041
+ onClick: () => void applyFavoriteImage(item)
118042
+ }, item.id))
118043
+ });
118044
+ })(),
118045
+ (0, react_jsx_runtime.jsxs)("div", {
118046
+ className: "dsh-ig-fav-section-label",
118047
+ children: [(0, react_jsx_runtime.jsx)("span", { children: t("favPrompts") }), (0, react_jsx_runtime.jsx)("button", {
118048
+ type: "button",
118049
+ className: "dsh-ig-fav-folder-add",
118050
+ onClick: () => {
118051
+ setFolderDraft({ kind: "prompt" });
118052
+ setFolderDraftName("");
118053
+ },
118054
+ title: t("favNewFolder"),
118055
+ children: "+"
118056
+ })]
118057
+ }),
118058
+ (0, react_jsx_runtime.jsx)(FavoriteFolderBar, {
118059
+ folders: favFolders.filter((folder) => folder.kind === "prompt"),
118060
+ active: activePromptFolder,
118061
+ onSelect: (id) => setActivePromptFolder(id === activePromptFolder ? "" : id),
118062
+ onDelete: (id) => void deleteFavoriteFolder(id),
118063
+ deleteLabel: t("favDeleteFolder")
118064
+ }),
118065
+ folderDraft?.kind === "prompt" && (0, react_jsx_runtime.jsxs)("div", {
118066
+ className: "dsh-ig-fav-folder-draft",
118067
+ children: [
118068
+ (0, react_jsx_runtime.jsx)("input", {
118069
+ value: folderDraftName,
118070
+ placeholder: t("favFolderPlaceholder"),
118071
+ onChange: (e) => setFolderDraftName(e.target.value),
118072
+ onKeyDown: (e) => {
118073
+ if (e.key === "Enter") createFavFolder();
118074
+ }
118075
+ }),
118076
+ (0, react_jsx_runtime.jsx)("button", {
118077
+ type: "button",
118078
+ onClick: () => void createFavFolder(),
118079
+ children: t("favCreate")
118080
+ }),
118081
+ (0, react_jsx_runtime.jsx)("button", {
118082
+ type: "button",
118083
+ onClick: () => setFolderDraft(null),
118084
+ children: t("favCancel")
118085
+ })
118086
+ ]
118087
+ }),
118088
+ (() => {
118089
+ const list = favPrompts.filter((fav) => activePromptFolder === "" || fav.folderId === activePromptFolder);
118090
+ if (list.length === 0) return (0, react_jsx_runtime.jsxs)("div", {
118091
+ className: "dsh-ig-fav-empty",
118092
+ children: [(0, react_jsx_runtime.jsx)(PencilLine, { size: 18 }), (0, react_jsx_runtime.jsx)("span", { children: t("favEmptyPrompts") })]
118093
+ });
118094
+ return (0, react_jsx_runtime.jsx)("div", {
118095
+ className: "dsh-ig-fav-prompts",
118096
+ children: list.map((fav) => (0, react_jsx_runtime.jsxs)("div", {
118097
+ className: "dsh-ig-fav-prompt",
118098
+ children: [(0, react_jsx_runtime.jsx)("button", {
118099
+ type: "button",
118100
+ className: "dsh-ig-fav-prompt-text",
118101
+ title: fav.text,
118102
+ onClick: () => openPromptEditor(fav),
118103
+ children: fav.text
118104
+ }), (0, react_jsx_runtime.jsx)("button", {
118105
+ type: "button",
118106
+ className: "dsh-ig-fav-del",
118107
+ title: t("remove"),
118108
+ onClick: () => void deleteFavoritePrompt(fav.id),
118109
+ children: (0, react_jsx_runtime.jsx)(X, { size: 11 })
118110
+ })]
118111
+ }, fav.id))
118112
+ });
118113
+ })()
118114
+ ]
118115
+ })
118116
+ ]
118117
+ }),
118118
+ (editingPrompt !== null || movingFav !== null) && (0, react_jsx_runtime.jsx)("div", {
118119
+ className: "dsh-ig-fav-dialog-wrap",
118120
+ onClick: () => {
118121
+ setEditingPrompt(null);
118122
+ setMovingFav(null);
118123
+ },
118124
+ children: (0, react_jsx_runtime.jsxs)("div", {
118125
+ className: "dsh-ig-fav-dialog",
118126
+ onClick: (e) => e.stopPropagation(),
118127
+ children: [editingPrompt !== null && (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
118128
+ (0, react_jsx_runtime.jsx)("div", {
118129
+ className: "dsh-ig-fav-dialog-title",
118130
+ children: t("favEditPrompt")
118131
+ }),
118132
+ (0, react_jsx_runtime.jsx)("textarea", {
118133
+ className: "dsh-ig-fav-dialog-textarea",
118134
+ value: editPromptText,
118135
+ placeholder: t("favEditPromptPlaceholder"),
118136
+ onChange: (e) => setEditPromptText(e.target.value),
118137
+ rows: 5
118138
+ }),
118139
+ (0, react_jsx_runtime.jsxs)("label", {
118140
+ className: "dsh-ig-fav-dialog-folder-label",
118141
+ children: [t("favFolder"), (0, react_jsx_runtime.jsxs)("select", {
118142
+ value: editPromptFolder,
118143
+ onChange: (e) => setEditPromptFolder(e.target.value),
118144
+ children: [(0, react_jsx_runtime.jsx)("option", {
118145
+ value: "",
118146
+ children: t("favNoFolder")
118147
+ }), favFolders.filter((folder) => folder.kind === "prompt").map((folder) => (0, react_jsx_runtime.jsx)("option", {
118148
+ value: folder.id,
118149
+ children: folder.name
118150
+ }, folder.id))]
118151
+ })]
118152
+ }),
118153
+ (0, react_jsx_runtime.jsxs)("div", {
118154
+ className: "dsh-ig-fav-dialog-actions",
118155
+ children: [
118156
+ (0, react_jsx_runtime.jsx)("button", {
118157
+ type: "button",
118158
+ className: "is-primary",
118159
+ onClick: () => {
118160
+ updateFavoritePrompt(editingPrompt.id, editPromptText).then((ok) => {
118161
+ if (ok) moveFavoritePrompt(editingPrompt.id, editPromptFolder === "" ? void 0 : editPromptFolder);
118162
+ });
118163
+ setEditingPrompt(null);
118164
+ },
118165
+ children: t("favSave")
118166
+ }),
118167
+ (0, react_jsx_runtime.jsx)("button", {
118168
+ type: "button",
118169
+ onClick: () => {
118170
+ applyFavoritePrompt({
118171
+ ...editingPrompt,
118172
+ text: editPromptText
118173
+ });
118174
+ setEditingPrompt(null);
118175
+ },
118176
+ children: t("favUse")
118177
+ }),
118178
+ (0, react_jsx_runtime.jsx)("button", {
118179
+ type: "button",
118180
+ onClick: () => {
118181
+ setMovingFav({
118182
+ kind: "prompt",
118183
+ id: editingPrompt.id,
118184
+ folderId: editingPrompt.folderId
118185
+ });
118186
+ setEditingPrompt(null);
118187
+ },
118188
+ children: t("favMove")
118189
+ }),
118190
+ (0, react_jsx_runtime.jsx)("button", {
118191
+ type: "button",
118192
+ onClick: () => setEditingPrompt(null),
118193
+ children: t("favCancel")
118194
+ })
118195
+ ]
118196
+ })
118197
+ ] }), editingPrompt === null && movingFav !== null && (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
118198
+ (0, react_jsx_runtime.jsx)("div", {
118199
+ className: "dsh-ig-fav-dialog-title",
118200
+ children: t("favMoveTo")
118201
+ }),
118202
+ (0, react_jsx_runtime.jsxs)("div", {
118203
+ className: "dsh-ig-fav-dialog-folders",
118204
+ children: [(0, react_jsx_runtime.jsx)("button", {
118205
+ type: "button",
118206
+ onClick: () => {
118207
+ doMoveFavorite(void 0);
118208
+ setMovingFav(null);
118209
+ },
118210
+ children: t("favNoFolder")
118211
+ }), favFolders.filter((folder) => folder.kind === movingFav.kind).map((folder) => (0, react_jsx_runtime.jsx)("button", {
118212
+ type: "button",
118213
+ className: movingFav.folderId === folder.id ? "is-current" : "",
118214
+ onClick: () => {
118215
+ doMoveFavorite(folder.id);
118216
+ setMovingFav(null);
118217
+ },
118218
+ children: folder.name
118219
+ }, folder.id))]
118220
+ }),
118221
+ (0, react_jsx_runtime.jsx)("div", {
118222
+ className: "dsh-ig-fav-dialog-actions",
118223
+ children: (0, react_jsx_runtime.jsx)("button", {
118224
+ type: "button",
118225
+ onClick: () => setMovingFav(null),
118226
+ children: t("favCancel")
118227
+ })
118228
+ })
118229
+ ] })]
118230
+ })
117545
118231
  }),
117546
118232
  (0, react_jsx_runtime.jsxs)("main", {
117547
118233
  className: "dsh-ig-canvas-column",
@@ -117938,21 +118624,30 @@ img.ProseMirror-separator {
117938
118624
  " ",
117939
118625
  (0, react_jsx_runtime.jsx)("b", { children: "*" })
117940
118626
  ]
117941
- }), (0, react_jsx_runtime.jsxs)("span", { children: [(0, react_jsx_runtime.jsxs)("button", {
117942
- type: "button",
117943
- className: "dsh-ig-find-inspiration",
117944
- onClick: onOpenInspiration,
117945
- children: [(0, react_jsx_runtime.jsx)(Sparkles, { size: 11 }), t("findInspiration")]
117946
- }), (0, react_jsx_runtime.jsx)("button", {
117947
- type: "button",
117948
- onClick: () => setPrompt(""),
117949
- children: t("clear")
117950
- })] })]
118627
+ }), (0, react_jsx_runtime.jsxs)("span", { children: [
118628
+ (0, react_jsx_runtime.jsxs)("button", {
118629
+ type: "button",
118630
+ className: "dsh-ig-find-inspiration",
118631
+ onClick: onOpenInspiration,
118632
+ children: [(0, react_jsx_runtime.jsx)(Sparkles, { size: 11 }), t("findInspiration")]
118633
+ }),
118634
+ (0, react_jsx_runtime.jsxs)("button", {
118635
+ type: "button",
118636
+ className: "dsh-ig-fav-save-btn",
118637
+ onClick: () => void handleFavoritePrompt(),
118638
+ title: t("favoritePrompt"),
118639
+ children: [(0, react_jsx_runtime.jsx)(Star, { size: 12 }), t("favActionShort")]
118640
+ }),
118641
+ (0, react_jsx_runtime.jsx)("button", {
118642
+ type: "button",
118643
+ onClick: () => setPrompt(""),
118644
+ children: t("clear")
118645
+ })
118646
+ ] })]
117951
118647
  }),
117952
118648
  (0, react_jsx_runtime.jsx)("textarea", {
117953
118649
  id: "dsh-ig-prompt",
117954
118650
  value: prompt,
117955
- maxLength: 2e3,
117956
118651
  onChange: (event) => setPrompt(event.target.value),
117957
118652
  onKeyDown: (event) => {
117958
118653
  if (event.nativeEvent.isComposing) return;
@@ -117963,7 +118658,7 @@ img.ProseMirror-separator {
117963
118658
  },
117964
118659
  placeholder: t("promptPlaceholder")
117965
118660
  }),
117966
- (0, react_jsx_runtime.jsxs)("small", { children: [prompt.length, "/2000"] })
118661
+ (0, react_jsx_runtime.jsx)("small", { children: prompt.length })
117967
118662
  ]
117968
118663
  }),
117969
118664
  config === null ? configLoading ? (0, react_jsx_runtime.jsx)("div", {
@@ -118556,6 +119251,26 @@ img.ProseMirror-separator {
118556
119251
  })
118557
119252
  });
118558
119253
  };
119254
+ const FavoriteFolderBar = ({ folders, active, onSelect, onDelete, deleteLabel }) => {
119255
+ if (folders.length === 0) return null;
119256
+ return (0, react_jsx_runtime.jsx)("div", {
119257
+ className: "dsh-ig-fav-folders",
119258
+ children: folders.map((folder) => (0, react_jsx_runtime.jsxs)("span", {
119259
+ className: `dsh-ig-fav-folder-chip ${active === folder.id ? "is-active" : ""}`,
119260
+ children: [(0, react_jsx_runtime.jsx)("button", {
119261
+ type: "button",
119262
+ onClick: () => onSelect(folder.id),
119263
+ children: folder.name
119264
+ }), (0, react_jsx_runtime.jsx)("button", {
119265
+ type: "button",
119266
+ className: "dsh-ig-fav-folder-del",
119267
+ title: deleteLabel,
119268
+ onClick: () => onDelete(folder.id),
119269
+ children: (0, react_jsx_runtime.jsx)(X, { size: 9 })
119270
+ })]
119271
+ }, folder.id))
119272
+ });
119273
+ };
118559
119274
  function useInView() {
118560
119275
  const [inView, setInView] = (0, react.useState)(false);
118561
119276
  const observerRef = (0, react.useRef)(null);
@@ -118653,8 +119368,17 @@ img.ProseMirror-separator {
118653
119368
  const MAX_BYTES = 188743680;
118654
119369
  const pending = /* @__PURE__ */ new Map();
118655
119370
  let cacheEpoch = 0;
118656
- function fetchInspirationImage(sourceId, caseId) {
118657
- const key = `${sourceId}:${caseId}`;
119371
+ /**
119372
+ * Resolve the IndexedDB key for one image. `rev` (the source's imageRevision,
119373
+ * falling back to its version) namespaces the cache by image content: when a
119374
+ * source re-packages its images at stable case IDs, a bumped rev orphans the
119375
+ * old blobs instead of serving them forever.
119376
+ */
119377
+ function cacheKey(sourceId, caseId, rev) {
119378
+ return rev === void 0 ? `${sourceId}:${caseId}` : `${sourceId}:${caseId}@${rev}`;
119379
+ }
119380
+ function fetchInspirationImage(sourceId, caseId, rev) {
119381
+ const key = cacheKey(sourceId, caseId, rev);
118658
119382
  const existing = pending.get(key);
118659
119383
  if (existing !== void 0) return existing;
118660
119384
  const requestEpoch = cacheEpoch;
@@ -118677,8 +119401,8 @@ img.ProseMirror-separator {
118677
119401
  pending.set(key, task);
118678
119402
  return task;
118679
119403
  }
118680
- async function evictInspirationImage(sourceId, caseId) {
118681
- const key = `${sourceId}:${caseId}`;
119404
+ async function evictInspirationImage(sourceId, caseId, rev) {
119405
+ const key = cacheKey(sourceId, caseId, rev);
118682
119406
  pending.delete(key);
118683
119407
  const db = await openDatabase$1();
118684
119408
  if (db === void 0) return;
@@ -118865,6 +119589,988 @@ img.ProseMirror-separator {
118865
119589
  return Array.isArray(sources) && sources.length > 0 && sources.every((source) => typeof source === "object" && source !== null && Array.isArray(source.cases));
118866
119590
  }
118867
119591
  //#endregion
119592
+ //#region lib/types/inspiration/data/baoyu-compose.json
119593
+ var types = [
119594
+ {
119595
+ "id": "hero",
119596
+ "description": "Large visual impact, title overlay",
119597
+ "composition": "Large focal visual (60-70% area), title overlay on visual, dramatic composition"
119598
+ },
119599
+ {
119600
+ "id": "conceptual",
119601
+ "description": "Concept visualization, abstract core ideas",
119602
+ "composition": "Abstract shapes representing core concepts, information hierarchy, clean zones"
119603
+ },
119604
+ {
119605
+ "id": "typography",
119606
+ "description": "Text-focused layout, prominent title",
119607
+ "composition": "Title as primary element (40%+ area), minimal supporting visuals, strong hierarchy"
119608
+ },
119609
+ {
119610
+ "id": "metaphor",
119611
+ "description": "Visual metaphor, concrete expressing abstract",
119612
+ "composition": "Concrete object/scene representing abstract idea, symbolic elements, emotional resonance"
119613
+ },
119614
+ {
119615
+ "id": "scene",
119616
+ "description": "Atmospheric scene, narrative feel",
119617
+ "composition": "Atmospheric environment, narrative elements, mood-setting lighting and colors"
119618
+ },
119619
+ {
119620
+ "id": "minimal",
119621
+ "description": "Minimalist composition, generous whitespace",
119622
+ "composition": "Single focal element, generous whitespace (60%+), essential shapes only"
119623
+ }
119624
+ ];
119625
+ var palettes = [
119626
+ {
119627
+ "id": "warm",
119628
+ "tagline": "Friendly, approachable, human-centered",
119629
+ "colors": [
119630
+ {
119631
+ "role": "Primary 1",
119632
+ "name": "Warm Orange",
119633
+ "hex": "#ed8936"
119634
+ },
119635
+ {
119636
+ "role": "Primary 2",
119637
+ "name": "Golden Yellow",
119638
+ "hex": "#f6ad55"
119639
+ },
119640
+ {
119641
+ "role": "Primary 3",
119642
+ "name": "Terracotta",
119643
+ "hex": "#c05621"
119644
+ },
119645
+ {
119646
+ "role": "Background",
119647
+ "name": "Cream",
119648
+ "hex": "#fffaf0"
119649
+ },
119650
+ {
119651
+ "role": "Background Alt",
119652
+ "name": "Soft Peach",
119653
+ "hex": "#fed7aa"
119654
+ },
119655
+ {
119656
+ "role": "Accent 1",
119657
+ "name": "Deep Brown",
119658
+ "hex": "#744210"
119659
+ },
119660
+ {
119661
+ "role": "Accent 2",
119662
+ "name": "Soft Red",
119663
+ "hex": "#e53e3e"
119664
+ }
119665
+ ],
119666
+ "decorativeHints": [
119667
+ "Sun rays, warm lighting effects",
119668
+ "Rounded shapes, organic curves",
119669
+ "Hearts, smiling faces, friendly icons",
119670
+ "Warm gradient overlays"
119671
+ ],
119672
+ "bestFor": ""
119673
+ },
119674
+ {
119675
+ "id": "cool",
119676
+ "tagline": "Technical, professional, precise",
119677
+ "colors": [
119678
+ {
119679
+ "role": "Primary 1",
119680
+ "name": "Engineering Blue",
119681
+ "hex": "#2563eb"
119682
+ },
119683
+ {
119684
+ "role": "Primary 2",
119685
+ "name": "Navy Blue",
119686
+ "hex": "#1e3a5f"
119687
+ },
119688
+ {
119689
+ "role": "Primary 3",
119690
+ "name": "Cyan",
119691
+ "hex": "#06b6d4"
119692
+ },
119693
+ {
119694
+ "role": "Background",
119695
+ "name": "Light Gray",
119696
+ "hex": "#f8f9fa"
119697
+ },
119698
+ {
119699
+ "role": "Background Alt",
119700
+ "name": "Blueprint Off-White",
119701
+ "hex": "#faf8f5"
119702
+ },
119703
+ {
119704
+ "role": "Accent 1",
119705
+ "name": "Amber",
119706
+ "hex": "#f59e0b"
119707
+ },
119708
+ {
119709
+ "role": "Accent 2",
119710
+ "name": "Light Blue",
119711
+ "hex": "#bfdbfe"
119712
+ }
119713
+ ],
119714
+ "decorativeHints": [
119715
+ "Grid lines and alignment guides",
119716
+ "Dimension indicators and measurements",
119717
+ "Technical schematics and diagrams",
119718
+ "Geometric precision elements"
119719
+ ],
119720
+ "bestFor": ""
119721
+ },
119722
+ {
119723
+ "id": "dark",
119724
+ "tagline": "Cinematic, premium, atmospheric",
119725
+ "colors": [
119726
+ {
119727
+ "role": "Primary 1",
119728
+ "name": "Electric Purple",
119729
+ "hex": "#8b5cf6"
119730
+ },
119731
+ {
119732
+ "role": "Primary 2",
119733
+ "name": "Cyan Blue",
119734
+ "hex": "#06b6d4"
119735
+ },
119736
+ {
119737
+ "role": "Primary 3",
119738
+ "name": "Magenta Pink",
119739
+ "hex": "#ec4899"
119740
+ },
119741
+ {
119742
+ "role": "Background",
119743
+ "name": "Deep Purple-Black",
119744
+ "hex": "#0a0a0a"
119745
+ },
119746
+ {
119747
+ "role": "Background Alt",
119748
+ "name": "Rich Navy",
119749
+ "hex": "#1a1a2e"
119750
+ },
119751
+ {
119752
+ "role": "Accent 1",
119753
+ "name": "Amber",
119754
+ "hex": "#f59e0b"
119755
+ },
119756
+ {
119757
+ "role": "Accent 2",
119758
+ "name": "Pure White",
119759
+ "hex": "#ffffff"
119760
+ }
119761
+ ],
119762
+ "decorativeHints": [
119763
+ "Glowing accent elements and neon highlights",
119764
+ "Atmospheric fog or particle effects",
119765
+ "Silhouettes with backlit edges",
119766
+ "Subtle gradient backgrounds"
119767
+ ],
119768
+ "bestFor": ""
119769
+ },
119770
+ {
119771
+ "id": "earth",
119772
+ "tagline": "Natural, organic, grounded",
119773
+ "colors": [
119774
+ {
119775
+ "role": "Primary 1",
119776
+ "name": "Forest Green",
119777
+ "hex": "#276749"
119778
+ },
119779
+ {
119780
+ "role": "Primary 2",
119781
+ "name": "Sage",
119782
+ "hex": "#9ae6b4"
119783
+ },
119784
+ {
119785
+ "role": "Primary 3",
119786
+ "name": "Earth Brown",
119787
+ "hex": "#744210"
119788
+ },
119789
+ {
119790
+ "role": "Background",
119791
+ "name": "Sand Beige",
119792
+ "hex": "#f5e6d3"
119793
+ },
119794
+ {
119795
+ "role": "Background Alt",
119796
+ "name": "Sky Blue",
119797
+ "hex": "#e0f2fe"
119798
+ },
119799
+ {
119800
+ "role": "Accent 1",
119801
+ "name": "Sunset Orange",
119802
+ "hex": "#ed8936"
119803
+ },
119804
+ {
119805
+ "role": "Accent 2",
119806
+ "name": "Water Blue",
119807
+ "hex": "#63b3ed"
119808
+ }
119809
+ ],
119810
+ "decorativeHints": [
119811
+ "Leaves, trees, mountains, natural forms",
119812
+ "Sun, clouds, organic flowing lines",
119813
+ "Botanical illustrations",
119814
+ "Earthy textures and natural patterns"
119815
+ ],
119816
+ "bestFor": ""
119817
+ },
119818
+ {
119819
+ "id": "elegant",
119820
+ "tagline": "Sophisticated, refined, understated luxury",
119821
+ "colors": [
119822
+ {
119823
+ "role": "Primary 1",
119824
+ "name": "Soft Coral",
119825
+ "hex": "#e8a598"
119826
+ },
119827
+ {
119828
+ "role": "Primary 2",
119829
+ "name": "Muted Teal",
119830
+ "hex": "#5b8a8a"
119831
+ },
119832
+ {
119833
+ "role": "Primary 3",
119834
+ "name": "Dusty Rose",
119835
+ "hex": "#d4a5a5"
119836
+ },
119837
+ {
119838
+ "role": "Background",
119839
+ "name": "Warm Cream",
119840
+ "hex": "#f5f0e6"
119841
+ },
119842
+ {
119843
+ "role": "Background Alt",
119844
+ "name": "Soft Beige",
119845
+ "hex": "#f0ebe0"
119846
+ },
119847
+ {
119848
+ "role": "Accent 1",
119849
+ "name": "Gold",
119850
+ "hex": "#c9a962"
119851
+ },
119852
+ {
119853
+ "role": "Accent 2",
119854
+ "name": "Copper",
119855
+ "hex": "#b87333"
119856
+ }
119857
+ ],
119858
+ "decorativeHints": [
119859
+ "Delicate ornamental details",
119860
+ "Subtle gradients and soft transitions",
119861
+ "Refined geometric patterns",
119862
+ "Balanced, symmetrical compositions"
119863
+ ],
119864
+ "bestFor": ""
119865
+ },
119866
+ {
119867
+ "id": "macaron",
119868
+ "tagline": "Soft macaron pastel color blocks on warm cream",
119869
+ "colors": [
119870
+ {
119871
+ "role": "Primary 1",
119872
+ "name": "Sky Blue",
119873
+ "hex": "#a8d8ea"
119874
+ },
119875
+ {
119876
+ "role": "Primary 2",
119877
+ "name": "Mint Green",
119878
+ "hex": "#b5e5cf"
119879
+ },
119880
+ {
119881
+ "role": "Primary 3",
119882
+ "name": "Lavender",
119883
+ "hex": "#d5c6e0"
119884
+ },
119885
+ {
119886
+ "role": "Background",
119887
+ "name": "Warm Cream",
119888
+ "hex": "#f5f0e8"
119889
+ },
119890
+ {
119891
+ "role": "Background Alt",
119892
+ "name": "Peach",
119893
+ "hex": "#ffd5c2"
119894
+ },
119895
+ {
119896
+ "role": "Accent 1",
119897
+ "name": "Coral Red",
119898
+ "hex": "#e8655a"
119899
+ },
119900
+ {
119901
+ "role": "Accent 2",
119902
+ "name": "Deep Charcoal",
119903
+ "hex": "#2d2d2d"
119904
+ }
119905
+ ],
119906
+ "decorativeHints": [
119907
+ "Rounded pastel color blocks",
119908
+ "Subtle warm paper grain texture",
119909
+ "Soft shadows, no hard edges",
119910
+ "Gentle gradient transitions between zones"
119911
+ ],
119912
+ "bestFor": ""
119913
+ },
119914
+ {
119915
+ "id": "mono",
119916
+ "tagline": "Clean, focused, essential",
119917
+ "colors": [
119918
+ {
119919
+ "role": "Primary 1",
119920
+ "name": "Pure Black",
119921
+ "hex": "#000000"
119922
+ },
119923
+ {
119924
+ "role": "Primary 2",
119925
+ "name": "Near Black",
119926
+ "hex": "#1f1f1f"
119927
+ },
119928
+ {
119929
+ "role": "Primary 3",
119930
+ "name": "Dark Gray",
119931
+ "hex": "#374151"
119932
+ },
119933
+ {
119934
+ "role": "Background",
119935
+ "name": "White",
119936
+ "hex": "#ffffff"
119937
+ },
119938
+ {
119939
+ "role": "Background Alt",
119940
+ "name": "Off-White",
119941
+ "hex": "#fafafa"
119942
+ },
119943
+ {
119944
+ "role": "Accent 2",
119945
+ "name": "Medium Gray",
119946
+ "hex": "#9ca3af"
119947
+ }
119948
+ ],
119949
+ "decorativeHints": [
119950
+ "Maximum negative space",
119951
+ "Thin lines and minimal strokes",
119952
+ "Single focal point emphasis",
119953
+ "Stark contrast between elements"
119954
+ ],
119955
+ "bestFor": ""
119956
+ },
119957
+ {
119958
+ "id": "pastel",
119959
+ "tagline": "Gentle, whimsical, soft",
119960
+ "colors": [
119961
+ {
119962
+ "role": "Primary 1",
119963
+ "name": "Soft Pink",
119964
+ "hex": "#ffb6c1"
119965
+ },
119966
+ {
119967
+ "role": "Primary 2",
119968
+ "name": "Mint",
119969
+ "hex": "#98d8c8"
119970
+ },
119971
+ {
119972
+ "role": "Primary 3",
119973
+ "name": "Lavender",
119974
+ "hex": "#c8a2c8"
119975
+ },
119976
+ {
119977
+ "role": "Background",
119978
+ "name": "White",
119979
+ "hex": "#ffffff"
119980
+ },
119981
+ {
119982
+ "role": "Background Alt",
119983
+ "name": "Light Cream",
119984
+ "hex": "#fff8e7"
119985
+ },
119986
+ {
119987
+ "role": "Accent 1",
119988
+ "name": "Butter Yellow",
119989
+ "hex": "#fffacd"
119990
+ },
119991
+ {
119992
+ "role": "Accent 2",
119993
+ "name": "Sky Blue",
119994
+ "hex": "#bee3f8"
119995
+ }
119996
+ ],
119997
+ "decorativeHints": [
119998
+ "Cute rounded proportions",
119999
+ "Stars, sparkles, flowers, decorative flourishes",
120000
+ "Soft shadows and gentle highlights",
120001
+ "Storybook-style elements"
120002
+ ],
120003
+ "bestFor": ""
120004
+ },
120005
+ {
120006
+ "id": "retro",
120007
+ "tagline": "Nostalgic, vintage, classic",
120008
+ "colors": [
120009
+ {
120010
+ "role": "Primary 1",
120011
+ "name": "Coral Red",
120012
+ "hex": "#e07a5f"
120013
+ },
120014
+ {
120015
+ "role": "Primary 2",
120016
+ "name": "Mint Green",
120017
+ "hex": "#81b29a"
120018
+ },
120019
+ {
120020
+ "role": "Primary 3",
120021
+ "name": "Mustard Yellow",
120022
+ "hex": "#f2cc8f"
120023
+ },
120024
+ {
120025
+ "role": "Primary 4",
120026
+ "name": "Dark Maroon",
120027
+ "hex": "#5d3a3a"
120028
+ },
120029
+ {
120030
+ "role": "Background",
120031
+ "name": "Cream Off-White",
120032
+ "hex": "#f5f0e6"
120033
+ },
120034
+ {
120035
+ "role": "Background Alt",
120036
+ "name": "Aged Paper",
120037
+ "hex": "#f5e6d3"
120038
+ },
120039
+ {
120040
+ "role": "Accent 1",
120041
+ "name": "Burnt Orange",
120042
+ "hex": "#d4764a"
120043
+ },
120044
+ {
120045
+ "role": "Accent 2",
120046
+ "name": "Rock Blue",
120047
+ "hex": "#577590"
120048
+ },
120049
+ {
120050
+ "role": "Accent 3",
120051
+ "name": "Vintage Gold",
120052
+ "hex": "#c9a227"
120053
+ },
120054
+ {
120055
+ "role": "Accent 4",
120056
+ "name": "Faded Teal",
120057
+ "hex": "#2f7373"
120058
+ }
120059
+ ],
120060
+ "decorativeHints": [
120061
+ "Halftone dots and vintage badges",
120062
+ "Aged textures with subtle paper grain",
120063
+ "Sunburst/radiating lines for energy",
120064
+ "Pill-shaped clouds, small dots and stars",
120065
+ "Classic icons and retro motifs"
120066
+ ],
120067
+ "bestFor": ""
120068
+ },
120069
+ {
120070
+ "id": "duotone",
120071
+ "tagline": "Dramatic, cinematic, two-color high contrast",
120072
+ "colors": [
120073
+ {
120074
+ "role": "Primary 1",
120075
+ "name": "Burnt Orange",
120076
+ "hex": "#e8751a"
120077
+ },
120078
+ {
120079
+ "role": "Primary 2",
120080
+ "name": "Deep Teal",
120081
+ "hex": "#0a6e6e"
120082
+ },
120083
+ {
120084
+ "role": "Background",
120085
+ "name": "Off-Black",
120086
+ "hex": "#121212"
120087
+ },
120088
+ {
120089
+ "role": "Background Alt",
120090
+ "name": "Dark Charcoal",
120091
+ "hex": "#1e1e1e"
120092
+ },
120093
+ {
120094
+ "role": "Accent 1",
120095
+ "name": "Warm Cream",
120096
+ "hex": "#f5e6d0"
120097
+ },
120098
+ {
120099
+ "role": "Accent 2",
120100
+ "name": "Amber Highlight",
120101
+ "hex": "#f4a623"
120102
+ }
120103
+ ],
120104
+ "decorativeHints": [
120105
+ "Stark two-color separation across entire composition",
120106
+ "Halftone transitions between the two colors",
120107
+ "Silhouettes in one color against the other",
120108
+ "Minimal use of third color (only for small highlights)",
120109
+ "High contrast figure-ground relationships"
120110
+ ],
120111
+ "bestFor": "",
120112
+ "pairs": [
120113
+ {
120114
+ "name": "Orange + Teal",
120115
+ "a": "#e8751a",
120116
+ "b": "#0a6e6e",
120117
+ "feel": "Cinematic, action"
120118
+ },
120119
+ {
120120
+ "name": "Red + Cream",
120121
+ "a": "#c0392b",
120122
+ "b": "#f5e6d0",
120123
+ "feel": "Bold, classic"
120124
+ },
120125
+ {
120126
+ "name": "Blue + Gold",
120127
+ "a": "#1a3a5c",
120128
+ "b": "#d4a843",
120129
+ "feel": "Prestigious, premium"
120130
+ },
120131
+ {
120132
+ "name": "Purple + Green",
120133
+ "a": "#6b3fa0",
120134
+ "b": "#2ecc71",
120135
+ "feel": "Futuristic, contrast"
120136
+ },
120137
+ {
120138
+ "name": "Magenta + Cyan",
120139
+ "a": "#c2185b",
120140
+ "b": "#00bcd4",
120141
+ "feel": "Vibrant, pop"
120142
+ },
120143
+ {
120144
+ "name": "Crimson + Navy",
120145
+ "a": "#dc143c",
120146
+ "b": "#0d1b2a",
120147
+ "feel": "Dramatic, noir"
120148
+ }
120149
+ ]
120150
+ },
120151
+ {
120152
+ "id": "vivid",
120153
+ "tagline": "Energetic, bold, attention-grabbing",
120154
+ "colors": [
120155
+ {
120156
+ "role": "Primary 1",
120157
+ "name": "Bright Red",
120158
+ "hex": "#ef4444"
120159
+ },
120160
+ {
120161
+ "role": "Primary 2",
120162
+ "name": "Neon Green",
120163
+ "hex": "#22c55e"
120164
+ },
120165
+ {
120166
+ "role": "Primary 3",
120167
+ "name": "Electric Blue",
120168
+ "hex": "#3b82f6"
120169
+ },
120170
+ {
120171
+ "role": "Background",
120172
+ "name": "Light Blue",
120173
+ "hex": "#eff6ff"
120174
+ },
120175
+ {
120176
+ "role": "Background Alt",
120177
+ "name": "Soft Lavender",
120178
+ "hex": "#f5f3ff"
120179
+ },
120180
+ {
120181
+ "role": "Accent 1",
120182
+ "name": "Bright Orange",
120183
+ "hex": "#fb923c"
120184
+ },
120185
+ {
120186
+ "role": "Accent 2",
120187
+ "name": "Vivid Yellow",
120188
+ "hex": "#facc15"
120189
+ }
120190
+ ],
120191
+ "decorativeHints": [
120192
+ "Dynamic diagonal lines and angles",
120193
+ "Bold geometric shapes and color blocks",
120194
+ "Dramatic lighting effects",
120195
+ "High-energy visual compositions"
120196
+ ],
120197
+ "bestFor": ""
120198
+ }
120199
+ ];
120200
+ var renderings = [
120201
+ {
120202
+ "id": "flat-vector",
120203
+ "tagline": "Clean, modern, geometric illustration",
120204
+ "lines": [
120205
+ "Clean outlines with uniform stroke weight",
120206
+ "Closed shapes (coloring-book style)",
120207
+ "Rounded line endings, avoid sharp corners",
120208
+ "Consistent stroke width throughout"
120209
+ ],
120210
+ "texture": [
120211
+ "None: smooth, flat color fills only",
120212
+ "No gradients, shadows, or noise",
120213
+ "Solid color blocks"
120214
+ ],
120215
+ "depth": [
120216
+ "Flat: no shadows, no perspective",
120217
+ "2D layering with overlap for depth illusion",
120218
+ "Optional 2.5D isometric layering (front/back occlusion, no atmospheric perspective)",
120219
+ "No 3D effects or bevels"
120220
+ ],
120221
+ "elements": [
120222
+ "Geometric icons and simple shapes",
120223
+ "Bold outlined objects with clean fills",
120224
+ "Geometric simplification: complex objects → basic shapes (trees → lollipop/triangle, buildings → rectangles)",
120225
+ "\"Toy model\" aesthetic: cute, rounded proportions",
120226
+ "Decorative: dots, lines, sunbursts, pill-shaped clouds, small stars",
120227
+ "Isolated elements on clean backgrounds"
120228
+ ]
120229
+ },
120230
+ {
120231
+ "id": "pixel",
120232
+ "tagline": "Retro 8-bit, nostalgic, chunky",
120233
+ "lines": [
120234
+ "Pixel grid alignment, no anti-aliasing",
120235
+ "Staircase edges on diagonals",
120236
+ "Single-pixel or double-pixel outlines",
120237
+ "Blocky, angular forms"
120238
+ ],
120239
+ "texture": [
120240
+ "Dithering patterns for gradients",
120241
+ "No smooth transitions",
120242
+ "Cross-hatching with pixel precision",
120243
+ "Limited 16-32 color palette per scene"
120244
+ ],
120245
+ "depth": [
120246
+ "None: flat pixel planes only",
120247
+ "Parallax layering (foreground/background)",
120248
+ "No perspective or 3D effects"
120249
+ ],
120250
+ "elements": [
120251
+ "8-bit sprites and chunky shapes",
120252
+ "Simple iconography: stars, hearts, arrows",
120253
+ "Text bubbles with pixel borders",
120254
+ "Progress bars with chunky segments",
120255
+ "Retro gaming UI elements"
120256
+ ]
120257
+ },
120258
+ {
120259
+ "id": "digital",
120260
+ "tagline": "Polished, precise, modern",
120261
+ "lines": [
120262
+ "Clean, precise, computer-perfect edges",
120263
+ "Consistent stroke weights",
120264
+ "Sharp corners where appropriate",
120265
+ "Anti-aliased smooth rendering"
120266
+ ],
120267
+ "texture": [
120268
+ "Smooth surfaces with no visible texture",
120269
+ "Subtle gradients permitted (soft, controlled)",
120270
+ "Frosted glass and blur effects",
120271
+ "Clean shadows with consistent direction"
120272
+ ],
120273
+ "depth": [
120274
+ "Subtle gradients and soft drop shadows",
120275
+ "Layered card-based layouts",
120276
+ "Light 3D effects (subtle, not realistic)",
120277
+ "Material Design-inspired elevation"
120278
+ ],
120279
+ "elements": [
120280
+ "Polished icons and UI components",
120281
+ "Data visualizations: charts, graphs, metrics",
120282
+ "Card layouts and structured grids",
120283
+ "Tag chips, progress bars, status indicators",
120284
+ "Clean geometric shapes"
120285
+ ]
120286
+ },
120287
+ {
120288
+ "id": "hand-drawn",
120289
+ "tagline": "Sketchy, organic, personal",
120290
+ "lines": [
120291
+ "Sketchy, organic, slightly imperfect strokes",
120292
+ "Variable line weight (thicker at pressure points)",
120293
+ "Wavy connectors and arrows",
120294
+ "Natural hand tremor visible"
120295
+ ],
120296
+ "texture": [
120297
+ "Paper grain and subtle surface texture",
120298
+ "Pencil/pen/marker texture on strokes",
120299
+ "Casual fills with visible brush direction"
120300
+ ],
120301
+ "depth": [
120302
+ "Minimal: light hand-drawn shadows or hatching",
120303
+ "No realistic depth or perspective",
120304
+ "Simple layering with overlap"
120305
+ ],
120306
+ "elements": [
120307
+ "Doodles, organic shapes, hand-lettered labels",
120308
+ "Conceptual icons with sketchy quality",
120309
+ "Connection lines with hand-drawn wavy feel",
120310
+ "Stars, arrows, underlines, circles, checkmarks",
120311
+ "Stick figures and simple characters"
120312
+ ]
120313
+ },
120314
+ {
120315
+ "id": "painterly",
120316
+ "tagline": "Soft, artistic, expressive",
120317
+ "lines": [
120318
+ "Soft brush strokes with variable opacity",
120319
+ "No hard outlines; edges defined by color transitions",
120320
+ "Organic flowing strokes with natural blending"
120321
+ ],
120322
+ "texture": [
120323
+ "Visible paint or watercolor wash textures",
120324
+ "Color bleeds and wet-on-wet effects",
120325
+ "Paper texture showing through transparent areas",
120326
+ "Brush stroke patterns visible"
120327
+ ],
120328
+ "depth": [
120329
+ "Soft edges with natural color blending",
120330
+ "Atmospheric depth through color fading",
120331
+ "Layered washes creating depth illusion"
120332
+ ],
120333
+ "elements": [
120334
+ "Watercolor washes as backgrounds",
120335
+ "Natural elements: leaves, flowers, organic forms",
120336
+ "Soft gradients and color transitions",
120337
+ "Splatter and drip effects as accents",
120338
+ "Botanical and environmental motifs"
120339
+ ]
120340
+ },
120341
+ {
120342
+ "id": "chalk",
120343
+ "tagline": "Educational, authentic, classroom",
120344
+ "lines": [
120345
+ "Imperfect chalk strokes with variable pressure",
120346
+ "Visible chalk texture and grain",
120347
+ "Slightly wobbly, hand-drawn quality",
120348
+ "Thick strokes for emphasis, thin for details"
120349
+ ],
120350
+ "texture": [
120351
+ "Chalk dust effects around text and elements",
120352
+ "Board surface (dark, slightly worn)",
120353
+ "Eraser smudges and residue",
120354
+ "Grainy chalk quality on all elements"
120355
+ ],
120356
+ "depth": [
120357
+ "None: flat chalk drawings on board surface",
120358
+ "Layering through erasure and redrawing",
120359
+ "No shadows or perspective"
120360
+ ],
120361
+ "elements": [
120362
+ "Chalk doodles: stars, arrows, underlines",
120363
+ "Mathematical formulas and diagrams",
120364
+ "Stick figures and simple icons",
120365
+ "Connection lines with chalk feel",
120366
+ "Checkmarks, circles, boxes for lists",
120367
+ "Wooden frame border optional"
120368
+ ]
120369
+ },
120370
+ {
120371
+ "id": "screen-print",
120372
+ "tagline": "Bold, limited-color poster art with print texture",
120373
+ "lines": [
120374
+ "Clean, sharp edges between color blocks",
120375
+ "No outlines — shapes defined by color boundaries",
120376
+ "Stencil-cut quality, bold silhouettes",
120377
+ "Geometric shapes with precise registration"
120378
+ ],
120379
+ "texture": [
120380
+ "Halftone dot patterns within color fills",
120381
+ "Slight color layer misregistration (offset between print layers)",
120382
+ "Paper grain texture beneath colors",
120383
+ "Risograph / screen print imperfections (ink spread, dot gain)"
120384
+ ],
120385
+ "depth": [
120386
+ "Flat color planes layered front to back",
120387
+ "Depth through silhouette overlap and color layering",
120388
+ "No gradients — tonal variation via halftone density",
120389
+ "Negative space as active compositional element"
120390
+ ],
120391
+ "elements": [
120392
+ "Bold silhouettes and symbolic shapes",
120393
+ "Geometric framing (circles, arches, triangles)",
120394
+ "Figure-ground inversion (negative space forms secondary image)",
120395
+ "Limited icon vocabulary: key props, symbolic objects",
120396
+ "Typography integrated as design element, not overlay",
120397
+ "Vintage poster border treatments"
120398
+ ]
120399
+ }
120400
+ ];
120401
+ var textLevels = [
120402
+ {
120403
+ "id": "none",
120404
+ "area": "85% visual area"
120405
+ },
120406
+ {
120407
+ "id": "title-only",
120408
+ "area": "85% visual area"
120409
+ },
120410
+ {
120411
+ "id": "title-subtitle",
120412
+ "area": "75% visual area"
120413
+ },
120414
+ {
120415
+ "id": "text-rich",
120416
+ "area": "60% visual area"
120417
+ }
120418
+ ];
120419
+ var moods = [
120420
+ {
120421
+ "id": "subtle",
120422
+ "application": "Use low contrast, muted colors, light visual weight, calm aesthetic"
120423
+ },
120424
+ {
120425
+ "id": "balanced",
120426
+ "application": "Use medium contrast, normal saturation, balanced visual weight"
120427
+ },
120428
+ {
120429
+ "id": "bold",
120430
+ "application": "Use high contrast, vivid saturated colors, heavy visual weight, dynamic energy"
120431
+ }
120432
+ ];
120433
+ //#endregion
120434
+ //#region lib/types/baoyu-compose.js
120435
+ /**
120436
+ * Dimension-composition prompt assembly for the inspiration composer tab.
120437
+ * Data is generated by scripts/build-baoyu-compose.mjs from JimLiu/baoyu-skills
120438
+ * (baoyu-cover-image skill, MIT license).
120439
+ */
120440
+ /** Pseudo inspiration source id that switches the view into composer mode. */
120441
+ const BAOYU_COMPOSER_SOURCE_ID = "baoyu-compose";
120442
+ const BAOYU_TYPES = types;
120443
+ const BAOYU_PALETTES = palettes;
120444
+ const BAOYU_RENDERINGS = renderings;
120445
+ const BAOYU_TEXT_LEVELS = textLevels;
120446
+ const BAOYU_MOODS = moods;
120447
+ /** Chinese labels for every dimension option id, shown when the UI language is zh. */
120448
+ const BAOYU_LABELS_ZH = {
120449
+ hero: "主视觉大图",
120450
+ conceptual: "概念抽象",
120451
+ typography: "文字主导",
120452
+ metaphor: "隐喻象征",
120453
+ scene: "氛围场景",
120454
+ minimal: "极简留白",
120455
+ warm: "暖色",
120456
+ cool: "冷色",
120457
+ dark: "暗色",
120458
+ earth: "大地色",
120459
+ elegant: "优雅",
120460
+ macaron: "马卡龙",
120461
+ mono: "单色",
120462
+ pastel: "粉彩",
120463
+ retro: "复古",
120464
+ duotone: "双色调",
120465
+ vivid: "高饱和",
120466
+ "flat-vector": "扁平矢量",
120467
+ pixel: "像素风",
120468
+ digital: "数字插画",
120469
+ "hand-drawn": "手绘",
120470
+ painterly: "绘画感",
120471
+ chalk: "粉笔",
120472
+ "screen-print": "丝网印刷",
120473
+ none: "无文字",
120474
+ "title-only": "仅标题",
120475
+ "title-subtitle": "标题+副标题",
120476
+ "text-rich": "丰富文字",
120477
+ subtle: "含蓄",
120478
+ balanced: "均衡",
120479
+ bold: "强烈"
120480
+ };
120481
+ const BAOYU_DEFAULT_SELECTION = {
120482
+ type: "metaphor",
120483
+ palette: "warm",
120484
+ rendering: "flat-vector",
120485
+ text: "title-only",
120486
+ mood: "balanced",
120487
+ title: ""
120488
+ };
120489
+ function palette(id) {
120490
+ return BAOYU_PALETTES.find((item) => item.id === id);
120491
+ }
120492
+ function rendering(id) {
120493
+ return BAOYU_RENDERINGS.find((item) => item.id === id);
120494
+ }
120495
+ function typeOf(id) {
120496
+ return BAOYU_TYPES.find((item) => item.id === id);
120497
+ }
120498
+ function moodOf(id) {
120499
+ return BAOYU_MOODS.find((item) => item.id === id);
120500
+ }
120501
+ function textLevel(id) {
120502
+ return BAOYU_TEXT_LEVELS.find((item) => item.id === id);
120503
+ }
120504
+ /** Assemble the five-dimension selection into one generate_image-ready prompt. */
120505
+ function composeBaoyuPrompt(selection) {
120506
+ const chosenType = typeOf(selection.type);
120507
+ const chosenPalette = palette(selection.palette);
120508
+ const chosenRendering = rendering(selection.rendering);
120509
+ const chosenText = textLevel(selection.text);
120510
+ const chosenMood = moodOf(selection.mood);
120511
+ if (chosenType === void 0 || chosenPalette === void 0 || chosenRendering === void 0 || chosenText === void 0 || chosenMood === void 0) throw new Error("invalid-baoyu-selection");
120512
+ const title = selection.title.trim();
120513
+ if (selection.text !== "none" && title === "") throw new Error("baoyu-title-required");
120514
+ const primary = chosenPalette.colors.filter((color) => color.role.startsWith("Primary"));
120515
+ const background = chosenPalette.colors.filter((color) => color.role.startsWith("Background"));
120516
+ const accent = chosenPalette.colors.filter((color) => color.role.startsWith("Accent"));
120517
+ const swatch = (colors) => colors.map((color) => `${color.name} ${color.hex}`).join(", ");
120518
+ const textElements = selection.text === "none" ? "No text elements; the full area stays visual." : [
120519
+ `Title: "${title}"`,
120520
+ ...selection.text === "title-subtitle" || selection.text === "text-rich" ? ["Subtitle: derive one short supporting line from the title"] : [],
120521
+ ...selection.text === "text-rich" ? ["Tags: derive 2-3 keyword badges from the title"] : []
120522
+ ].join("\n");
120523
+ const pairNote = chosenPalette.pairs === void 0 ? "" : `\n- Duotone pair: pick ONE dominant two-color pair from ${chosenPalette.pairs.map((pair) => `${pair.name} (${pair.a} + ${pair.b})`).join("; ")}`;
120524
+ return [
120525
+ "Cover image.",
120526
+ "",
120527
+ "# Visual Design",
120528
+ `- Type: ${chosenType.id} — ${chosenType.composition}`,
120529
+ `- Palette: ${chosenPalette.id} (${chosenPalette.tagline})`,
120530
+ `- Rendering: ${chosenRendering.id} — ${chosenRendering.tagline}`,
120531
+ `- Text level: ${chosenText.id} (${chosenText.area})`,
120532
+ `- Mood: ${chosenMood.id} — ${chosenMood.application}`,
120533
+ "",
120534
+ "# Text Elements",
120535
+ textElements,
120536
+ "",
120537
+ "# Composition",
120538
+ `- Type composition: ${chosenType.composition}`,
120539
+ "- Layout: generous whitespace (40-60% breathing room); one dominant focal point with 1-2 supporting elements plus decorative accents; main visual centered or slightly left, reserving the right side for the title when included.",
120540
+ `- Decorative: ${chosenPalette.decorativeHints.join("; ")}`,
120541
+ "",
120542
+ "# Color Scheme",
120543
+ `- Primary: ${swatch(primary)}`,
120544
+ `- Background: ${swatch(background)}`,
120545
+ `- Accent: ${swatch(accent)}${pairNote}`,
120546
+ `- Mood adjustment: ${chosenMood.application}`,
120547
+ "- Color constraint: Color values (#hex) and color names are rendering guidance only — do NOT display color names, hex codes, or palette labels as visible text in the image.",
120548
+ "",
120549
+ `# Rendering Notes (${chosenRendering.id})`,
120550
+ `- Lines: ${chosenRendering.lines.join("; ")}`,
120551
+ `- Texture: ${chosenRendering.texture.join("; ")}`,
120552
+ `- Depth: ${chosenRendering.depth.join("; ")}`,
120553
+ `- Elements: ${chosenRendering.elements.join("; ")}`,
120554
+ "",
120555
+ "# Core Principles",
120556
+ "- Ample whitespace, highlight the core message, avoid cluttered layouts.",
120557
+ "- Simplified silhouettes for any characters — no realistic human faces or bodies.",
120558
+ "- Icon-based vocabulary: represent concepts with simple, recognizable icons."
120559
+ ].join("\n");
120560
+ }
120561
+ /** Randomize every visual dimension, keeping the current title text. */
120562
+ function randomBaoyuSelection(current) {
120563
+ const pick = (items) => items[Math.floor(Math.random() * items.length)];
120564
+ return {
120565
+ type: pick(BAOYU_TYPES).id,
120566
+ palette: pick(BAOYU_PALETTES).id,
120567
+ rendering: pick(BAOYU_RENDERINGS).id,
120568
+ text: pick(BAOYU_TEXT_LEVELS).id,
120569
+ mood: pick(BAOYU_MOODS).id,
120570
+ title: current.title
120571
+ };
120572
+ }
120573
+ //#endregion
118868
120574
  //#region lib/types/client/inspiration-view.js
118869
120575
  const FAVORITES_STORAGE_KEY = "dsh-ig-inspiration-favorites";
118870
120576
  function loadInspirationFavorites() {
@@ -118956,6 +120662,7 @@ img.ProseMirror-separator {
118956
120662
  allCategories: "全部分类",
118957
120663
  allStyles: "全部风格",
118958
120664
  allScenes: "全部场景",
120665
+ categoryNav: "分类导航",
118959
120666
  search: "搜索案例、Prompt、风格…",
118960
120667
  results: "找到 {count} 个案例",
118961
120668
  noResults: "没有匹配的素材,换个关键词或筛选条件试试。",
@@ -118965,6 +120672,9 @@ img.ProseMirror-separator {
118965
120672
  copied: "已复制",
118966
120673
  use: "使用这个 Prompt",
118967
120674
  source: "查看原来源",
120675
+ useReference: "提示词 + 参考图",
120676
+ referenceLoading: "获取参考图中…",
120677
+ referenceFailed: "参考图获取失败,请重试",
118968
120678
  loading: "正在读取素材库…",
118969
120679
  loadFailed: "素材库读取失败,请稍后重试。",
118970
120680
  copyFailed: "复制失败",
@@ -118979,7 +120689,21 @@ img.ProseMirror-separator {
118979
120689
  favorite: "收藏",
118980
120690
  favorited: "已收藏",
118981
120691
  zoomHint: "点击放大查看",
118982
- close: "关闭"
120692
+ close: "关闭",
120693
+ composeTab: "宝玉维度组合",
120694
+ composeHint: "五个维度自由拼接封面 Prompt,数据源自宝玉封图 skill(JimLiu/baoyu-skills,MIT)。",
120695
+ composeType: "类型",
120696
+ composePalette: "色板",
120697
+ composeRendering: "渲染",
120698
+ composeText: "文字",
120699
+ composeMood: "情绪",
120700
+ composeTitleLabel: "标题文字",
120701
+ composeTitlePlaceholder: "输入封面标题(文字层级含标题时必填)",
120702
+ composeTitleRequired: "该文字层级需要先填写标题。",
120703
+ composeRandomize: "随机组合",
120704
+ composeUse: "用此 Prompt 去生成",
120705
+ composeCombos: "共 {count} 种组合",
120706
+ composePreview: "拼接结果预览"
118983
120707
  },
118984
120708
  en: {
118985
120709
  kicker: "Prompt inspiration",
@@ -118999,6 +120723,7 @@ img.ProseMirror-separator {
118999
120723
  allCategories: "All categories",
119000
120724
  allStyles: "All styles",
119001
120725
  allScenes: "All scenes",
120726
+ categoryNav: "Categories",
119002
120727
  search: "Search examples, prompts, styles…",
119003
120728
  results: "{count} examples",
119004
120729
  noResults: "No matching examples. Try another keyword or filter.",
@@ -119008,6 +120733,9 @@ img.ProseMirror-separator {
119008
120733
  copied: "Copied",
119009
120734
  use: "Use this prompt",
119010
120735
  source: "View source",
120736
+ useReference: "Prompt + reference",
120737
+ referenceLoading: "Fetching image…",
120738
+ referenceFailed: "Failed to load the reference image",
119011
120739
  loading: "Loading inspiration…",
119012
120740
  loadFailed: "Could not load the inspiration library.",
119013
120741
  copyFailed: "Failed to copy",
@@ -119022,12 +120750,28 @@ img.ProseMirror-separator {
119022
120750
  favorite: "Favorite",
119023
120751
  favorited: "Favorited",
119024
120752
  zoomHint: "Click to zoom in",
119025
- close: "Close"
119026
- }
119027
- };
119028
- const InspirationView = ({ locale, onUsePrompt }) => {
120753
+ close: "Close",
120754
+ composeTab: "Baoyu Composer",
120755
+ composeHint: "Stitch a cover prompt from five dimensions, based on the baoyu cover-image skill (JimLiu/baoyu-skills, MIT).",
120756
+ composeType: "Type",
120757
+ composePalette: "Palette",
120758
+ composeRendering: "Rendering",
120759
+ composeText: "Text",
120760
+ composeMood: "Mood",
120761
+ composeTitleLabel: "Title text",
120762
+ composeTitlePlaceholder: "Cover title (required when the text level includes a title)",
120763
+ composeTitleRequired: "This text level needs a title first.",
120764
+ composeRandomize: "Randomize",
120765
+ composeUse: "Generate with this prompt",
120766
+ composeCombos: "{count} combinations",
120767
+ composePreview: "Composed prompt preview"
120768
+ }
120769
+ };
120770
+ const InspirationView = ({ locale, onUsePrompt, onUseReference }) => {
119029
120771
  const [language, setLanguage] = (0, react.useState)(() => locale?.getSnapshot?.().active?.startsWith("en") ? "en" : "zh");
119030
120772
  const [catalog, setCatalog] = (0, react.useState)(null);
120773
+ const [activeSourceId, setActiveSourceId] = (0, react.useState)("");
120774
+ const [baoyuSelection, setBaoyuSelection] = (0, react.useState)(BAOYU_DEFAULT_SELECTION);
119031
120775
  const [catalogError, setCatalogError] = (0, react.useState)(false);
119032
120776
  const [search, setSearch] = (0, react.useState)("");
119033
120777
  const [category, setCategory] = (0, react.useState)("");
@@ -119043,8 +120787,12 @@ img.ProseMirror-separator {
119043
120787
  const [onlyFavorites, setOnlyFavorites] = (0, react.useState)(false);
119044
120788
  const [lightboxCase, setLightboxCase] = (0, react.useState)(null);
119045
120789
  const [toast, setToast] = (0, react.useState)(null);
120790
+ const [referenceBusy, setReferenceBusy] = (0, react.useState)(false);
120791
+ const [pendingJump, setPendingJump] = (0, react.useState)("");
120792
+ const [activeCategory, setActiveCategory] = (0, react.useState)("");
119046
120793
  const toastTimerRef = (0, react.useRef)(null);
119047
120794
  const sentinelRef = (0, react.useRef)(null);
120795
+ const listRef = (0, react.useRef)(null);
119048
120796
  (0, react.useEffect)(() => {
119049
120797
  if (!locale?.subscribe) return;
119050
120798
  return locale.subscribe(() => setLanguage(locale.getSnapshot?.().active?.startsWith("en") ? "en" : "zh"));
@@ -119114,7 +120862,33 @@ img.ProseMirror-separator {
119114
120862
  mounted = false;
119115
120863
  };
119116
120864
  }, []);
119117
- const source = catalog?.sources[0] ?? null;
120865
+ const source = catalog?.sources.find((candidate) => candidate.id === activeSourceId) ?? catalog?.sources[0] ?? null;
120866
+ const composerActive = activeSourceId === BAOYU_COMPOSER_SOURCE_ID;
120867
+ /** Switch the active library; each source owns its own category/style/scene vocabularies. */
120868
+ const switchSource = (id) => {
120869
+ if (id === activeSourceId) return;
120870
+ setActiveSourceId(id);
120871
+ setCategory("");
120872
+ setStyle("");
120873
+ setScene("");
120874
+ setOnlyFavorites(false);
120875
+ setSelected(null);
120876
+ setPendingJump("");
120877
+ setActiveCategory("");
120878
+ };
120879
+ const useSelectedReference = async () => {
120880
+ if (selected === null || source === null || onUseReference === void 0) return;
120881
+ setReferenceBusy(true);
120882
+ try {
120883
+ const blob = await fetchInspirationImage(source.id, selected.id, source.imageRevision ?? source.version);
120884
+ const ext = blob.type === "image/jpeg" ? "jpg" : blob.type.split("/")[1]?.replace(/[^a-z0-9]/gi, "") || "png";
120885
+ onUseReference(new File([blob], `inspiration-${selected.id}.${ext}`, { type: blob.type.startsWith("image/") ? blob.type : "image/webp" }), selected.prompt);
120886
+ } catch {
120887
+ showToast(t("referenceFailed"), true);
120888
+ } finally {
120889
+ setReferenceBusy(false);
120890
+ }
120891
+ };
119118
120892
  const matchingCases = (0, react.useMemo)(() => {
119119
120893
  if (source === null) return [];
119120
120894
  const query = search.trim().toLowerCase();
@@ -119156,6 +120930,67 @@ img.ProseMirror-separator {
119156
120930
  onlyFavorites
119157
120931
  ]);
119158
120932
  const visibleCases = matchingCases.slice(0, visibleLimit);
120933
+ const categoryCounts = (0, react.useMemo)(() => {
120934
+ const counts = /* @__PURE__ */ new Map();
120935
+ for (const item of matchingCases) counts.set(item.category, (counts.get(item.category) ?? 0) + 1);
120936
+ return counts;
120937
+ }, [matchingCases]);
120938
+ const visibleGroups = (0, react.useMemo)(() => {
120939
+ if (source === null) return [];
120940
+ const byCategory = /* @__PURE__ */ new Map();
120941
+ for (const item of visibleCases) {
120942
+ const bucket = byCategory.get(item.category);
120943
+ if (bucket !== void 0) bucket.push(item);
120944
+ else byCategory.set(item.category, [item]);
120945
+ }
120946
+ return source.categories.flatMap((name) => {
120947
+ const items = byCategory.get(name);
120948
+ return items !== void 0 ? [{
120949
+ name,
120950
+ items
120951
+ }] : [];
120952
+ });
120953
+ }, [source, visibleCases]);
120954
+ const visibleGroupSignature = visibleGroups.map((group) => group.name).join("\0");
120955
+ const jumpToCategory = (name) => {
120956
+ if (category !== "") setCategory("");
120957
+ setPendingJump(name);
120958
+ };
120959
+ (0, react.useEffect)(() => {
120960
+ if (pendingJump === "") return;
120961
+ const index = matchingCases.findIndex((item) => item.category === pendingJump);
120962
+ if (index < 0) return;
120963
+ if (index >= visibleLimit) {
120964
+ setVisibleLimit(index + 60);
120965
+ return;
120966
+ }
120967
+ const raf = requestAnimationFrame(() => {
120968
+ document.getElementById(`dsh-ig-cat-${encodeURIComponent(pendingJump)}`)?.scrollIntoView({
120969
+ behavior: "smooth",
120970
+ block: "start"
120971
+ });
120972
+ setPendingJump("");
120973
+ });
120974
+ return () => cancelAnimationFrame(raf);
120975
+ }, [
120976
+ pendingJump,
120977
+ matchingCases,
120978
+ visibleLimit
120979
+ ]);
120980
+ (0, react.useEffect)(() => {
120981
+ const root = listRef.current;
120982
+ if (root === null) return;
120983
+ const headers = Array.from(root.querySelectorAll(".dsh-ig-inspiration-group-head"));
120984
+ if (headers.length === 0) return;
120985
+ const categoryByTarget = /* @__PURE__ */ new Map();
120986
+ for (const header of headers) categoryByTarget.set(header, header.dataset.category ?? "");
120987
+ const observer = new IntersectionObserver((entries) => {
120988
+ const visible = entries.find((entry) => entry.isIntersecting);
120989
+ if (visible !== void 0) setActiveCategory(categoryByTarget.get(visible.target) ?? "");
120990
+ }, { rootMargin: "0px 0px -75% 0px" });
120991
+ for (const header of headers) observer.observe(header);
120992
+ return () => observer.disconnect();
120993
+ }, [visibleGroupSignature]);
119159
120994
  (0, react.useEffect)(() => {
119160
120995
  const el = sentinelRef.current;
119161
120996
  if (!el) return;
@@ -119280,179 +121115,256 @@ img.ProseMirror-separator {
119280
121115
  className: "dsh-ig-spin",
119281
121116
  size: 23
119282
121117
  }), (0, react_jsx_runtime.jsx)("span", { children: t("loading") })]
119283
- }) : (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsxs)("div", {
119284
- className: "dsh-ig-inspiration-toolbar",
119285
- children: [
119286
- (0, react_jsx_runtime.jsxs)("label", {
119287
- className: "dsh-ig-inspiration-search",
119288
- children: [(0, react_jsx_runtime.jsx)(Search, { size: 15 }), (0, react_jsx_runtime.jsx)("input", {
119289
- value: search,
119290
- onChange: (event) => setSearch(event.target.value),
119291
- placeholder: t("search")
119292
- })]
119293
- }),
119294
- (0, react_jsx_runtime.jsxs)("button", {
121118
+ }) : (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [catalog !== null ? (0, react_jsx_runtime.jsxs)("div", {
121119
+ className: "dsh-ig-inspiration-sources",
121120
+ role: "tablist",
121121
+ children: [catalog.sources.map((item) => (0, react_jsx_runtime.jsx)("button", {
121122
+ type: "button",
121123
+ role: "tab",
121124
+ "aria-selected": !composerActive && item.id === source?.id,
121125
+ className: `dsh-ig-inspiration-source-chip ${!composerActive && item.id === source?.id ? "is-active" : ""}`,
121126
+ onClick: () => switchSource(item.id),
121127
+ children: item.label
121128
+ }, item.id)), (0, react_jsx_runtime.jsxs)("button", {
121129
+ type: "button",
121130
+ role: "tab",
121131
+ "aria-selected": composerActive,
121132
+ className: `dsh-ig-inspiration-source-chip ${composerActive ? "is-active" : ""}`,
121133
+ onClick: () => switchSource(BAOYU_COMPOSER_SOURCE_ID),
121134
+ children: [(0, react_jsx_runtime.jsx)(Sparkles, { size: 11 }), t("composeTab")]
121135
+ })]
121136
+ }) : null, composerActive ? (0, react_jsx_runtime.jsx)(BaoyuComposerPanel, {
121137
+ language,
121138
+ selection: baoyuSelection,
121139
+ onChange: setBaoyuSelection,
121140
+ onUsePrompt
121141
+ }) : (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
121142
+ (0, react_jsx_runtime.jsxs)("div", {
121143
+ className: "dsh-ig-inspiration-toolbar",
121144
+ children: [
121145
+ (0, react_jsx_runtime.jsxs)("label", {
121146
+ className: "dsh-ig-inspiration-search",
121147
+ children: [(0, react_jsx_runtime.jsx)(Search, { size: 15 }), (0, react_jsx_runtime.jsx)("input", {
121148
+ value: search,
121149
+ onChange: (event) => setSearch(event.target.value),
121150
+ placeholder: t("search")
121151
+ })]
121152
+ }),
121153
+ (0, react_jsx_runtime.jsxs)("button", {
121154
+ type: "button",
121155
+ className: `dsh-ig-inspiration-fav-filter ${onlyFavorites ? "is-active" : ""}`,
121156
+ onClick: () => setOnlyFavorites((prev) => !prev),
121157
+ title: t("onlyFavorites"),
121158
+ children: [(0, react_jsx_runtime.jsx)(Star, {
121159
+ size: 14,
121160
+ className: onlyFavorites ? "fill-star" : ""
121161
+ }), (0, react_jsx_runtime.jsxs)("span", { children: [t("onlyFavorites"), favorites.size > 0 ? ` (${favorites.size})` : ""] })]
121162
+ }),
121163
+ (0, react_jsx_runtime.jsxs)("select", {
121164
+ value: category,
121165
+ onChange: (event) => setCategory(event.target.value),
121166
+ "aria-label": t("allCategories"),
121167
+ children: [(0, react_jsx_runtime.jsx)("option", {
121168
+ value: "",
121169
+ children: t("allCategories")
121170
+ }), source.categories.map((item) => (0, react_jsx_runtime.jsx)("option", {
121171
+ value: item,
121172
+ children: translateTag(item, language)
121173
+ }, item))]
121174
+ }),
121175
+ (0, react_jsx_runtime.jsxs)("select", {
121176
+ value: style,
121177
+ onChange: (event) => setStyle(event.target.value),
121178
+ "aria-label": t("allStyles"),
121179
+ children: [(0, react_jsx_runtime.jsx)("option", {
121180
+ value: "",
121181
+ children: t("allStyles")
121182
+ }), source.styles.map((item) => (0, react_jsx_runtime.jsx)("option", {
121183
+ value: item,
121184
+ children: translateTag(item, language)
121185
+ }, item))]
121186
+ }),
121187
+ (0, react_jsx_runtime.jsxs)("select", {
121188
+ value: scene,
121189
+ onChange: (event) => setScene(event.target.value),
121190
+ "aria-label": t("allScenes"),
121191
+ children: [(0, react_jsx_runtime.jsx)("option", {
121192
+ value: "",
121193
+ children: t("allScenes")
121194
+ }), source.scenes.map((item) => (0, react_jsx_runtime.jsx)("option", {
121195
+ value: item,
121196
+ children: translateTag(item, language)
121197
+ }, item))]
121198
+ })
121199
+ ]
121200
+ }),
121201
+ (0, react_jsx_runtime.jsxs)("nav", {
121202
+ className: "dsh-ig-inspiration-catnav",
121203
+ "aria-label": t("categoryNav"),
121204
+ children: [(0, react_jsx_runtime.jsxs)("button", {
119295
121205
  type: "button",
119296
- className: `dsh-ig-inspiration-fav-filter ${onlyFavorites ? "is-active" : ""}`,
119297
- onClick: () => setOnlyFavorites((prev) => !prev),
119298
- title: t("onlyFavorites"),
119299
- children: [(0, react_jsx_runtime.jsx)(Star, {
119300
- size: 14,
119301
- className: onlyFavorites ? "fill-star" : ""
119302
- }), (0, react_jsx_runtime.jsxs)("span", { children: [t("onlyFavorites"), favorites.size > 0 ? ` (${favorites.size})` : ""] })]
119303
- }),
119304
- (0, react_jsx_runtime.jsxs)("select", {
119305
- value: category,
119306
- onChange: (event) => setCategory(event.target.value),
119307
- "aria-label": t("allCategories"),
119308
- children: [(0, react_jsx_runtime.jsx)("option", {
119309
- value: "",
119310
- children: t("allCategories")
119311
- }), source.categories.map((item) => (0, react_jsx_runtime.jsx)("option", {
119312
- value: item,
119313
- children: translateTag(item, language)
119314
- }, item))]
119315
- }),
119316
- (0, react_jsx_runtime.jsxs)("select", {
119317
- value: style,
119318
- onChange: (event) => setStyle(event.target.value),
119319
- "aria-label": t("allStyles"),
119320
- children: [(0, react_jsx_runtime.jsx)("option", {
119321
- value: "",
119322
- children: t("allStyles")
119323
- }), source.styles.map((item) => (0, react_jsx_runtime.jsx)("option", {
119324
- value: item,
119325
- children: translateTag(item, language)
119326
- }, item))]
119327
- }),
119328
- (0, react_jsx_runtime.jsxs)("select", {
119329
- value: scene,
119330
- onChange: (event) => setScene(event.target.value),
119331
- "aria-label": t("allScenes"),
119332
- children: [(0, react_jsx_runtime.jsx)("option", {
119333
- value: "",
119334
- children: t("allScenes")
119335
- }), source.scenes.map((item) => (0, react_jsx_runtime.jsx)("option", {
119336
- value: item,
119337
- children: translateTag(item, language)
119338
- }, item))]
119339
- })
119340
- ]
119341
- }), (0, react_jsx_runtime.jsxs)("div", {
119342
- className: "dsh-ig-inspiration-layout",
119343
- children: [(0, react_jsx_runtime.jsxs)("div", { children: [(0, react_jsx_runtime.jsxs)("div", {
119344
- className: "dsh-ig-inspiration-summary",
119345
- children: [(0, react_jsx_runtime.jsx)("strong", { children: t("results", { count: String(matchingCases.length) }) }), (0, react_jsx_runtime.jsx)("span", { children: source.version.slice(0, 8) })]
119346
- }), matchingCases.length === 0 ? (0, react_jsx_runtime.jsx)("div", {
119347
- className: "dsh-ig-inspiration-empty",
119348
- children: onlyFavorites ? t("noFavorites") : t("noResults")
119349
- }) : (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsx)("div", {
119350
- className: "dsh-ig-inspiration-grid",
119351
- children: visibleCases.map((item) => (0, react_jsx_runtime.jsx)(InspirationCard, {
119352
- item,
119353
- sourceId: source.id,
119354
- selected: selected?.id === item.id,
119355
- isFavorited: favorites.has(item.id),
119356
- language,
119357
- featuredLabel: t("featured"),
119358
- retryLabel: t("retry"),
119359
- onSelect: () => {
119360
- setSelected(item);
119361
- setCopied(false);
121206
+ className: `dsh-ig-inspiration-catnav-chip ${category === "" && activeCategory === "" ? "is-active" : ""}`,
121207
+ onClick: () => {
121208
+ if (category !== "") setCategory("");
121209
+ listRef.current?.scrollIntoView({
121210
+ behavior: "smooth",
121211
+ block: "start"
121212
+ });
119362
121213
  },
119363
- onToggleFavorite: () => toggleFavorite(item.id)
119364
- }, item.id))
119365
- }), visibleCases.length < matchingCases.length ? (0, react_jsx_runtime.jsx)("div", {
119366
- ref: sentinelRef,
119367
- style: {
119368
- height: 32,
119369
- margin: "16px 0"
119370
- }
119371
- }) : (0, react_jsx_runtime.jsx)("div", {
119372
- className: "dsh-ig-inspiration-end-hint",
119373
- children: t("allLoaded", { count: String(matchingCases.length) })
119374
- })] })] }), (0, react_jsx_runtime.jsx)("aside", {
119375
- className: "dsh-ig-inspiration-inspector",
119376
- children: selected === null ? (0, react_jsx_runtime.jsxs)("div", {
119377
- className: "dsh-ig-inspiration-inspector-empty",
119378
- children: [(0, react_jsx_runtime.jsx)(Image$2, { size: 28 }), (0, react_jsx_runtime.jsx)("span", { children: t("selectHint") })]
119379
- }) : (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsxs)("div", {
119380
- className: "dsh-ig-inspiration-inspector-image",
119381
- onClick: () => setLightboxCase({
119382
- sourceId: source.id,
119383
- caseId: selected.id,
119384
- title: selected.title,
119385
- alt: selected.imageAlt
119386
- }),
119387
- title: t("zoomHint"),
119388
- children: [(0, react_jsx_runtime.jsx)(InspirationImage, {
119389
- sourceId: source.id,
119390
- caseId: selected.id,
119391
- alt: selected.imageAlt,
119392
- retryLabel: t("retry")
119393
- }), (0, react_jsx_runtime.jsxs)("span", {
119394
- className: "dsh-ig-inspiration-inspector-zoom-hint",
119395
- children: [(0, react_jsx_runtime.jsx)(Maximize2, { size: 11 }), t("zoomHint")]
119396
- })]
119397
- }), (0, react_jsx_runtime.jsxs)("div", {
119398
- className: "dsh-ig-inspiration-inspector-body",
119399
- children: [
119400
- (0, react_jsx_runtime.jsxs)("div", {
119401
- className: "dsh-ig-inspiration-inspector-head",
119402
- children: [(0, react_jsx_runtime.jsx)("h2", { children: selected.title }), (0, react_jsx_runtime.jsx)("button", {
119403
- type: "button",
119404
- className: `dsh-ig-inspiration-inspector-fav ${favorites.has(selected.id) ? "is-favorited" : ""}`,
119405
- onClick: () => toggleFavorite(selected.id),
119406
- title: favorites.has(selected.id) ? t("favorited") : t("favorite"),
119407
- children: (0, react_jsx_runtime.jsx)(Star, {
119408
- size: 15,
119409
- className: favorites.has(selected.id) ? "fill-star" : ""
119410
- })
119411
- })]
119412
- }),
119413
- selected.sourceLabel && (0, react_jsx_runtime.jsx)("p", {
119414
- className: "dsh-ig-inspiration-origin",
119415
- children: selected.sourceLabel
119416
- }),
119417
- (0, react_jsx_runtime.jsxs)("div", {
119418
- className: "dsh-ig-inspiration-tags",
119419
- children: [(0, react_jsx_runtime.jsx)("span", { children: translateTag(selected.category, language) }), selected.styles.slice(0, 3).map((item) => (0, react_jsx_runtime.jsx)("span", { children: translateTag(item, language) }, item))]
119420
- }),
119421
- (0, react_jsx_runtime.jsxs)("div", {
119422
- className: "dsh-ig-inspiration-prompt-label",
119423
- children: [(0, react_jsx_runtime.jsx)("span", { children: t("prompt") }), (0, react_jsx_runtime.jsx)("span", { children: selected.prompt.length })]
119424
- }),
119425
- (0, react_jsx_runtime.jsx)("p", {
119426
- className: "dsh-ig-inspiration-prompt",
119427
- children: selected.prompt
121214
+ children: [(0, react_jsx_runtime.jsx)("span", { children: t("allCategories") }), (0, react_jsx_runtime.jsx)("em", { children: matchingCases.length })]
121215
+ }), source.categories.map((name) => {
121216
+ const count = categoryCounts.get(name) ?? 0;
121217
+ const isActive = category === name || category === "" && activeCategory === name;
121218
+ return (0, react_jsx_runtime.jsxs)("button", {
121219
+ type: "button",
121220
+ disabled: count === 0,
121221
+ "aria-current": isActive || void 0,
121222
+ className: `dsh-ig-inspiration-catnav-chip ${isActive ? "is-active" : ""}`,
121223
+ onClick: () => jumpToCategory(name),
121224
+ children: [(0, react_jsx_runtime.jsx)("span", { children: translateTag(name, language) }), (0, react_jsx_runtime.jsx)("em", { children: count })]
121225
+ }, name);
121226
+ })]
121227
+ }),
121228
+ (0, react_jsx_runtime.jsxs)("div", {
121229
+ className: "dsh-ig-inspiration-layout",
121230
+ children: [(0, react_jsx_runtime.jsxs)("div", { children: [(0, react_jsx_runtime.jsxs)("div", {
121231
+ className: "dsh-ig-inspiration-summary",
121232
+ children: [(0, react_jsx_runtime.jsx)("strong", { children: t("results", { count: String(matchingCases.length) }) }), (0, react_jsx_runtime.jsx)("span", { children: source.version.slice(0, 8) })]
121233
+ }), matchingCases.length === 0 ? (0, react_jsx_runtime.jsx)("div", {
121234
+ className: "dsh-ig-inspiration-empty",
121235
+ children: onlyFavorites ? t("noFavorites") : t("noResults")
121236
+ }) : (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsx)("div", {
121237
+ ref: listRef,
121238
+ className: "dsh-ig-inspiration-list",
121239
+ children: visibleGroups.map((group) => (0, react_jsx_runtime.jsxs)("section", {
121240
+ className: "dsh-ig-inspiration-group",
121241
+ children: [(0, react_jsx_runtime.jsxs)("h3", {
121242
+ id: `dsh-ig-cat-${encodeURIComponent(group.name)}`,
121243
+ "data-category": group.name,
121244
+ className: "dsh-ig-inspiration-group-head",
121245
+ children: [(0, react_jsx_runtime.jsx)("span", { children: translateTag(group.name, language) }), (0, react_jsx_runtime.jsx)("em", { children: categoryCounts.get(group.name) ?? group.items.length })]
121246
+ }), (0, react_jsx_runtime.jsx)("div", {
121247
+ className: "dsh-ig-inspiration-grid",
121248
+ children: group.items.map((item) => (0, react_jsx_runtime.jsx)(InspirationCard, {
121249
+ item,
121250
+ sourceId: source.id,
121251
+ sourceRevision: source.imageRevision ?? source.version,
121252
+ selected: selected?.id === item.id,
121253
+ isFavorited: favorites.has(item.id),
121254
+ language,
121255
+ featuredLabel: t("featured"),
121256
+ retryLabel: t("retry"),
121257
+ onSelect: () => {
121258
+ setSelected(item);
121259
+ setCopied(false);
121260
+ },
121261
+ onToggleFavorite: () => toggleFavorite(item.id)
121262
+ }, item.id))
121263
+ })]
121264
+ }, group.name))
121265
+ }), visibleCases.length < matchingCases.length ? (0, react_jsx_runtime.jsx)("div", {
121266
+ ref: sentinelRef,
121267
+ style: {
121268
+ height: 32,
121269
+ margin: "16px 0"
121270
+ }
121271
+ }) : (0, react_jsx_runtime.jsx)("div", {
121272
+ className: "dsh-ig-inspiration-end-hint",
121273
+ children: t("allLoaded", { count: String(matchingCases.length) })
121274
+ })] })] }), (0, react_jsx_runtime.jsx)("aside", {
121275
+ className: "dsh-ig-inspiration-inspector",
121276
+ children: selected === null ? (0, react_jsx_runtime.jsxs)("div", {
121277
+ className: "dsh-ig-inspiration-inspector-empty",
121278
+ children: [(0, react_jsx_runtime.jsx)(Image$2, { size: 28 }), (0, react_jsx_runtime.jsx)("span", { children: t("selectHint") })]
121279
+ }) : (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsxs)("div", {
121280
+ className: "dsh-ig-inspiration-inspector-image",
121281
+ onClick: () => setLightboxCase({
121282
+ sourceId: source.id,
121283
+ caseId: selected.id,
121284
+ revision: source.imageRevision ?? source.version,
121285
+ title: selected.title,
121286
+ alt: selected.imageAlt
119428
121287
  }),
119429
- (0, react_jsx_runtime.jsxs)("div", {
119430
- className: "dsh-ig-inspiration-actions",
119431
- children: [
119432
- (0, react_jsx_runtime.jsxs)("button", {
119433
- type: "button",
119434
- onClick: () => void copyPrompt(),
119435
- children: [copied ? (0, react_jsx_runtime.jsx)(Check, { size: 14 }) : (0, react_jsx_runtime.jsx)(Clipboard, { size: 14 }), copied ? t("copied") : t("copy")]
119436
- }),
119437
- selected.sourceUrl ?? selected.githubUrl ? (0, react_jsx_runtime.jsxs)("a", {
119438
- className: "dsh-ig-inspiration-source-link",
119439
- href: selected.sourceUrl ?? selected.githubUrl,
119440
- target: "_blank",
119441
- rel: "noreferrer",
119442
- children: [(0, react_jsx_runtime.jsx)(ExternalLink, { size: 14 }), t("source")]
119443
- }) : (0, react_jsx_runtime.jsx)("span", {}),
119444
- (0, react_jsx_runtime.jsxs)("button", {
121288
+ title: t("zoomHint"),
121289
+ children: [(0, react_jsx_runtime.jsx)(InspirationImage, {
121290
+ sourceId: source.id,
121291
+ caseId: selected.id,
121292
+ revision: source.imageRevision ?? source.version,
121293
+ alt: selected.imageAlt,
121294
+ retryLabel: t("retry")
121295
+ }), (0, react_jsx_runtime.jsxs)("span", {
121296
+ className: "dsh-ig-inspiration-inspector-zoom-hint",
121297
+ children: [(0, react_jsx_runtime.jsx)(Maximize2, { size: 11 }), t("zoomHint")]
121298
+ })]
121299
+ }), (0, react_jsx_runtime.jsxs)("div", {
121300
+ className: "dsh-ig-inspiration-inspector-body",
121301
+ children: [
121302
+ (0, react_jsx_runtime.jsxs)("div", {
121303
+ className: "dsh-ig-inspiration-inspector-head",
121304
+ children: [(0, react_jsx_runtime.jsx)("h2", { children: selected.title }), (0, react_jsx_runtime.jsx)("button", {
119445
121305
  type: "button",
119446
- className: "dsh-ig-inspiration-use",
119447
- onClick: () => onUsePrompt(selected.prompt),
119448
- children: [(0, react_jsx_runtime.jsx)(Sparkles, { size: 14 }), t("use")]
119449
- })
119450
- ]
119451
- })
119452
- ]
119453
- })] })
119454
- })]
119455
- })] }),
121306
+ className: `dsh-ig-inspiration-inspector-fav ${favorites.has(selected.id) ? "is-favorited" : ""}`,
121307
+ onClick: () => toggleFavorite(selected.id),
121308
+ title: favorites.has(selected.id) ? t("favorited") : t("favorite"),
121309
+ children: (0, react_jsx_runtime.jsx)(Star, {
121310
+ size: 15,
121311
+ className: favorites.has(selected.id) ? "fill-star" : ""
121312
+ })
121313
+ })]
121314
+ }),
121315
+ selected.sourceLabel && (0, react_jsx_runtime.jsx)("p", {
121316
+ className: "dsh-ig-inspiration-origin",
121317
+ children: selected.sourceLabel
121318
+ }),
121319
+ (0, react_jsx_runtime.jsxs)("div", {
121320
+ className: "dsh-ig-inspiration-tags",
121321
+ children: [(0, react_jsx_runtime.jsx)("span", { children: translateTag(selected.category, language) }), selected.styles.slice(0, 3).map((item) => (0, react_jsx_runtime.jsx)("span", { children: translateTag(item, language) }, item))]
121322
+ }),
121323
+ (0, react_jsx_runtime.jsxs)("div", {
121324
+ className: "dsh-ig-inspiration-prompt-label",
121325
+ children: [(0, react_jsx_runtime.jsx)("span", { children: t("prompt") }), (0, react_jsx_runtime.jsx)("span", { children: selected.prompt.length })]
121326
+ }),
121327
+ (0, react_jsx_runtime.jsx)("p", {
121328
+ className: "dsh-ig-inspiration-prompt",
121329
+ children: selected.prompt
121330
+ }),
121331
+ (0, react_jsx_runtime.jsxs)("div", {
121332
+ className: "dsh-ig-inspiration-actions",
121333
+ children: [
121334
+ (0, react_jsx_runtime.jsxs)("button", {
121335
+ type: "button",
121336
+ onClick: () => void copyPrompt(),
121337
+ children: [copied ? (0, react_jsx_runtime.jsx)(Check, { size: 14 }) : (0, react_jsx_runtime.jsx)(Clipboard, { size: 14 }), copied ? t("copied") : t("copy")]
121338
+ }),
121339
+ selected.sourceUrl ?? selected.githubUrl ? (0, react_jsx_runtime.jsxs)("a", {
121340
+ className: "dsh-ig-inspiration-source-link",
121341
+ href: selected.sourceUrl ?? selected.githubUrl,
121342
+ target: "_blank",
121343
+ rel: "noreferrer",
121344
+ children: [(0, react_jsx_runtime.jsx)(ExternalLink, { size: 14 }), t("source")]
121345
+ }) : (0, react_jsx_runtime.jsx)("span", {}),
121346
+ (0, react_jsx_runtime.jsxs)("button", {
121347
+ type: "button",
121348
+ className: "dsh-ig-inspiration-use",
121349
+ onClick: () => onUsePrompt(selected.prompt),
121350
+ children: [(0, react_jsx_runtime.jsx)(Sparkles, { size: 14 }), t("use")]
121351
+ }),
121352
+ onUseReference !== void 0 ? (0, react_jsx_runtime.jsxs)("button", {
121353
+ type: "button",
121354
+ className: "dsh-ig-inspiration-use is-secondary",
121355
+ disabled: referenceBusy,
121356
+ onClick: () => {
121357
+ useSelectedReference();
121358
+ },
121359
+ children: [(0, react_jsx_runtime.jsx)(ImagePlus, { size: 14 }), referenceBusy ? t("referenceLoading") : t("useReference")]
121360
+ }) : null
121361
+ ]
121362
+ })
121363
+ ]
121364
+ })] })
121365
+ })]
121366
+ })
121367
+ ] })] }),
119456
121368
  lightboxCase !== null && (0, react_jsx_runtime.jsx)("div", {
119457
121369
  className: "dsh-ig-inspiration-lightbox",
119458
121370
  onClick: () => setLightboxCase(null),
@@ -119472,6 +121384,7 @@ img.ProseMirror-separator {
119472
121384
  children: (0, react_jsx_runtime.jsx)(InspirationImage, {
119473
121385
  sourceId: lightboxCase.sourceId,
119474
121386
  caseId: lightboxCase.caseId,
121387
+ revision: lightboxCase.revision,
119475
121388
  alt: lightboxCase.alt,
119476
121389
  retryLabel: t("retry")
119477
121390
  })
@@ -119524,7 +121437,155 @@ img.ProseMirror-separator {
119524
121437
  ]
119525
121438
  });
119526
121439
  };
119527
- const InspirationCard = ({ item, sourceId, selected, isFavorited, language, featuredLabel, retryLabel, onSelect, onToggleFavorite }) => (0, react_jsx_runtime.jsxs)("button", {
121440
+ const BaoyuComposerPanel = ({ language, selection, onChange, onUsePrompt }) => {
121441
+ const [copied, setCopied] = (0, react.useState)(false);
121442
+ const [copyFailed, setCopyFailed] = (0, react.useState)(false);
121443
+ const tr = (key, params) => {
121444
+ let text = COPY[language][key] ?? COPY.zh[key];
121445
+ if (params) for (const [k, v] of Object.entries(params)) text = text.replace(`{${k}}`, v);
121446
+ return text;
121447
+ };
121448
+ const label = (id) => language === "zh" ? BAOYU_LABELS_ZH[id] ?? id : id;
121449
+ const needsTitle = selection.text !== "none";
121450
+ const titleReady = !needsTitle || selection.title.trim() !== "";
121451
+ let prompt = "";
121452
+ if (titleReady) try {
121453
+ prompt = composeBaoyuPrompt(selection);
121454
+ } catch {
121455
+ prompt = "";
121456
+ }
121457
+ const combos = BAOYU_TYPES.length * BAOYU_PALETTES.length * BAOYU_RENDERINGS.length * BAOYU_TEXT_LEVELS.length * BAOYU_MOODS.length;
121458
+ const dimension = (name, key, options) => (0, react_jsx_runtime.jsxs)("div", {
121459
+ className: "dsh-ig-baoyu-dim",
121460
+ children: [(0, react_jsx_runtime.jsx)("div", {
121461
+ className: "dsh-ig-baoyu-dim-head",
121462
+ children: name
121463
+ }), (0, react_jsx_runtime.jsx)("div", {
121464
+ className: "dsh-ig-baoyu-chips",
121465
+ children: options.map((option) => {
121466
+ const palette = key === "palette" ? BAOYU_PALETTES.find((item) => item.id === option.id) : void 0;
121467
+ return (0, react_jsx_runtime.jsxs)("button", {
121468
+ type: "button",
121469
+ className: `dsh-ig-baoyu-chip ${selection[key] === option.id ? "is-active" : ""}`,
121470
+ onClick: () => {
121471
+ onChange({
121472
+ ...selection,
121473
+ [key]: option.id
121474
+ });
121475
+ setCopied(false);
121476
+ },
121477
+ title: language === "zh" ? palette?.tagline ?? option.id : option.id,
121478
+ children: [(0, react_jsx_runtime.jsx)("span", { children: label(option.id) }), palette !== void 0 ? (0, react_jsx_runtime.jsx)("span", {
121479
+ className: "dsh-ig-baoyu-swatch",
121480
+ children: [
121481
+ palette.colors[0],
121482
+ palette.colors.find((color) => color.role === "Background"),
121483
+ palette.colors.find((color) => color.role === "Accent 1")
121484
+ ].map((color, index) => color === void 0 ? null : (0, react_jsx_runtime.jsx)("i", { style: { background: color.hex } }, index))
121485
+ }) : null]
121486
+ }, option.id);
121487
+ })
121488
+ })]
121489
+ }, name);
121490
+ const copyPrompt = async () => {
121491
+ if (prompt === "") return;
121492
+ try {
121493
+ await navigator.clipboard.writeText(prompt);
121494
+ setCopied(true);
121495
+ setCopyFailed(false);
121496
+ } catch {
121497
+ setCopyFailed(true);
121498
+ }
121499
+ };
121500
+ return (0, react_jsx_runtime.jsxs)("div", {
121501
+ className: "dsh-ig-baoyu-panel",
121502
+ children: [
121503
+ (0, react_jsx_runtime.jsxs)("div", {
121504
+ className: "dsh-ig-baoyu-intro",
121505
+ children: [(0, react_jsx_runtime.jsx)("strong", {
121506
+ className: "dsh-ig-baoyu-combos",
121507
+ children: tr("composeCombos", { count: String(combos) })
121508
+ }), (0, react_jsx_runtime.jsx)("span", {
121509
+ className: "dsh-ig-baoyu-hint",
121510
+ children: tr("composeHint")
121511
+ })]
121512
+ }),
121513
+ dimension(tr("composeType"), "type", BAOYU_TYPES),
121514
+ dimension(tr("composePalette"), "palette", BAOYU_PALETTES),
121515
+ dimension(tr("composeRendering"), "rendering", BAOYU_RENDERINGS),
121516
+ dimension(tr("composeText"), "text", BAOYU_TEXT_LEVELS),
121517
+ dimension(tr("composeMood"), "mood", BAOYU_MOODS),
121518
+ (0, react_jsx_runtime.jsxs)("div", {
121519
+ className: "dsh-ig-baoyu-dim",
121520
+ children: [
121521
+ (0, react_jsx_runtime.jsx)("div", {
121522
+ className: "dsh-ig-baoyu-dim-head",
121523
+ children: tr("composeTitleLabel")
121524
+ }),
121525
+ (0, react_jsx_runtime.jsx)("input", {
121526
+ className: "dsh-ig-baoyu-title-input",
121527
+ value: selection.title,
121528
+ placeholder: tr("composeTitlePlaceholder"),
121529
+ disabled: selection.text === "none",
121530
+ onChange: (event) => {
121531
+ onChange({
121532
+ ...selection,
121533
+ title: event.target.value
121534
+ });
121535
+ setCopied(false);
121536
+ }
121537
+ }),
121538
+ needsTitle && !titleReady ? (0, react_jsx_runtime.jsx)("div", {
121539
+ className: "dsh-ig-baoyu-required",
121540
+ children: tr("composeTitleRequired")
121541
+ }) : null
121542
+ ]
121543
+ }),
121544
+ (0, react_jsx_runtime.jsxs)("div", {
121545
+ className: "dsh-ig-baoyu-actions",
121546
+ children: [
121547
+ (0, react_jsx_runtime.jsxs)("button", {
121548
+ type: "button",
121549
+ className: "dsh-ig-baoyu-btn",
121550
+ onClick: () => onChange(randomBaoyuSelection(selection)),
121551
+ children: [(0, react_jsx_runtime.jsx)(Shuffle, { size: 13 }), (0, react_jsx_runtime.jsx)("span", { children: tr("composeRandomize") })]
121552
+ }),
121553
+ (0, react_jsx_runtime.jsxs)("button", {
121554
+ type: "button",
121555
+ className: "dsh-ig-baoyu-btn",
121556
+ disabled: prompt === "",
121557
+ onClick: () => {
121558
+ copyPrompt();
121559
+ },
121560
+ children: [copied ? (0, react_jsx_runtime.jsx)(Check, { size: 13 }) : (0, react_jsx_runtime.jsx)(Clipboard, { size: 13 }), (0, react_jsx_runtime.jsx)("span", { children: copied ? tr("copied") : tr("copy") })]
121561
+ }),
121562
+ (0, react_jsx_runtime.jsxs)("button", {
121563
+ type: "button",
121564
+ className: "dsh-ig-baoyu-btn is-primary",
121565
+ disabled: prompt === "",
121566
+ onClick: () => onUsePrompt(prompt),
121567
+ children: [(0, react_jsx_runtime.jsx)(Sparkles, { size: 13 }), (0, react_jsx_runtime.jsx)("span", { children: tr("composeUse") })]
121568
+ }),
121569
+ copyFailed ? (0, react_jsx_runtime.jsx)("span", {
121570
+ className: "dsh-ig-baoyu-required",
121571
+ children: tr("copyFailed")
121572
+ }) : null
121573
+ ]
121574
+ }),
121575
+ prompt !== "" ? (0, react_jsx_runtime.jsxs)("div", {
121576
+ className: "dsh-ig-baoyu-preview-block",
121577
+ children: [(0, react_jsx_runtime.jsx)("div", {
121578
+ className: "dsh-ig-baoyu-preview-head",
121579
+ children: tr("composePreview")
121580
+ }), (0, react_jsx_runtime.jsx)("pre", {
121581
+ className: "dsh-ig-baoyu-preview",
121582
+ children: prompt
121583
+ })]
121584
+ }) : null
121585
+ ]
121586
+ });
121587
+ };
121588
+ const InspirationCard = ({ item, sourceId, sourceRevision, selected, isFavorited, language, featuredLabel, retryLabel, onSelect, onToggleFavorite }) => (0, react_jsx_runtime.jsxs)("button", {
119528
121589
  type: "button",
119529
121590
  className: `dsh-ig-inspiration-card ${selected ? "is-selected" : ""}`,
119530
121591
  onClick: onSelect,
@@ -119551,6 +121612,7 @@ img.ProseMirror-separator {
119551
121612
  (0, react_jsx_runtime.jsx)(InspirationImage, {
119552
121613
  sourceId,
119553
121614
  caseId: item.id,
121615
+ revision: sourceRevision,
119554
121616
  alt: item.imageAlt,
119555
121617
  retryLabel
119556
121618
  })
@@ -119560,7 +121622,7 @@ img.ProseMirror-separator {
119560
121622
  children: [(0, react_jsx_runtime.jsx)("strong", { children: item.title }), (0, react_jsx_runtime.jsx)("span", { children: translateTag(item.category, language) })]
119561
121623
  })]
119562
121624
  });
119563
- const InspirationImage = ({ sourceId, caseId, alt, retryLabel = "Retry" }) => {
121625
+ const InspirationImage = ({ sourceId, caseId, revision, alt, retryLabel = "Retry" }) => {
119564
121626
  const [node, setNode] = (0, react.useState)(null);
119565
121627
  const [visible, setVisible] = (0, react.useState)(false);
119566
121628
  const [url, setUrl] = (0, react.useState)(null);
@@ -119581,7 +121643,7 @@ img.ProseMirror-separator {
119581
121643
  urlRef.current = null;
119582
121644
  }
119583
121645
  setUrl(null);
119584
- fetchInspirationImage(sourceId, caseId).then((blob) => {
121646
+ fetchInspirationImage(sourceId, caseId, revision).then((blob) => {
119585
121647
  if (!active) return;
119586
121648
  const next = URL.createObjectURL(blob);
119587
121649
  if (urlRef.current !== null) URL.revokeObjectURL(urlRef.current);
@@ -119596,7 +121658,8 @@ img.ProseMirror-separator {
119596
121658
  }, [
119597
121659
  visible,
119598
121660
  sourceId,
119599
- caseId
121661
+ caseId,
121662
+ revision
119600
121663
  ]);
119601
121664
  (0, react.useEffect)(() => {
119602
121665
  return () => {
@@ -119633,7 +121696,7 @@ img.ProseMirror-separator {
119633
121696
  urlRef.current = null;
119634
121697
  }
119635
121698
  setUrl(null);
119636
- evictInspirationImage(sourceId, caseId).then(() => fetchInspirationImage(sourceId, caseId)).then((blob) => {
121699
+ evictInspirationImage(sourceId, caseId, revision).then(() => fetchInspirationImage(sourceId, caseId, revision)).then((blob) => {
119637
121700
  const next = URL.createObjectURL(blob);
119638
121701
  if (urlRef.current !== null) URL.revokeObjectURL(urlRef.current);
119639
121702
  urlRef.current = next;
@@ -119751,6 +121814,12 @@ img.ProseMirror-separator {
119751
121814
  filterXAI: "xAI Grok",
119752
121815
  filterZhipu: "智谱 GLM",
119753
121816
  filterComfyUI: "本地 ComfyUI",
121817
+ filterImport: "导入",
121818
+ importImages: "导入图片",
121819
+ importAdded: "已导入 {count} 张图片",
121820
+ importPartial: "导入 {count} 张,{fail} 张失败",
121821
+ importFailed: "导入失败",
121822
+ importPickHint: "选择要导入的图片(PNG/JPG/WebP/GIF)",
119754
121823
  filterAllModels: "全部模型",
119755
121824
  filterAllRatios: "全部比例",
119756
121825
  searchPlaceholder: "搜索 Prompt、标签…",
@@ -119847,6 +121916,12 @@ img.ProseMirror-separator {
119847
121916
  filterXAI: "xAI Grok",
119848
121917
  filterZhipu: "Zhipu GLM",
119849
121918
  filterComfyUI: "Local ComfyUI",
121919
+ filterImport: "Imported",
121920
+ importImages: "Import images",
121921
+ importAdded: "Imported {count} images",
121922
+ importPartial: "Imported {count}, {fail} failed",
121923
+ importFailed: "Import failed",
121924
+ importPickHint: "Choose images to import (PNG/JPG/WebP/GIF)",
119850
121925
  filterAllModels: "All Models",
119851
121926
  filterAllRatios: "All Ratios",
119852
121927
  searchPlaceholder: "Search prompt, tags…",
@@ -119929,6 +122004,19 @@ img.ProseMirror-separator {
119929
122004
  }
119930
122005
  };
119931
122006
  /** Format human-readable relative time */
122007
+ /** Read a browser-picked file as base64 payload for the import route. */
122008
+ function readFileBase64(file) {
122009
+ return new Promise((resolve, reject) => {
122010
+ const reader = new FileReader();
122011
+ reader.onload = () => {
122012
+ const result = reader.result;
122013
+ if (typeof result === "string") resolve(result.slice(result.indexOf(",") + 1));
122014
+ else reject(/* @__PURE__ */ new Error("read-failed"));
122015
+ };
122016
+ reader.onerror = () => reject(/* @__PURE__ */ new Error("read-failed"));
122017
+ reader.readAsDataURL(file);
122018
+ });
122019
+ }
119932
122020
  function formatRelativeTime(timestamp, t) {
119933
122021
  const diff = Date.now() - timestamp;
119934
122022
  if (diff < 6e4) return t("justNow");
@@ -119987,6 +122075,7 @@ img.ProseMirror-separator {
119987
122075
  const { locale, sessionId, useSessions, useWorkspaces, credentialEvents, inSidebar, defaultTab, initialCanvasSurface, useTabInfo } = props;
119988
122076
  const [activeTab, setActiveTab] = (0, react.useState)(defaultTab ?? "gallery");
119989
122077
  const [studioDraft, setStudioDraft] = (0, react.useState)(void 0);
122078
+ const [studioReference, setStudioReference] = (0, react.useState)(void 0);
119990
122079
  const [items, setItems] = (0, react.useState)([]);
119991
122080
  const [search, setSearch] = (0, react.useState)("");
119992
122081
  const [selectedProvider, setSelectedProvider] = (0, react.useState)("all");
@@ -120101,11 +122190,80 @@ img.ProseMirror-separator {
120101
122190
  setToast(null);
120102
122191
  }, 2e3);
120103
122192
  };
122193
+ const [importing, setImporting] = (0, react.useState)(false);
122194
+ const importInputRef = (0, react.useRef)(null);
122195
+ const importImages = async (files) => {
122196
+ if (files === null || files.length === 0 || importing) return;
122197
+ const all = Array.from(files);
122198
+ const picked = all.filter((file) => file.type.startsWith("image/")).slice(0, 8);
122199
+ if (picked.length === 0) {
122200
+ showToast(t("importFailed"));
122201
+ return;
122202
+ }
122203
+ setImporting(true);
122204
+ try {
122205
+ const reads = await Promise.allSettled(picked.map((file) => readFileBase64(file)));
122206
+ const images = reads.flatMap((read, index) => {
122207
+ if (read.status !== "fulfilled") return [];
122208
+ const file = picked[index];
122209
+ return [{
122210
+ data: read.value,
122211
+ mediaType: file.type,
122212
+ ...file.name.length > 0 ? { name: file.name } : {}
122213
+ }];
122214
+ });
122215
+ const readFailures = reads.length - images.length;
122216
+ if (images.length === 0) {
122217
+ showToast(t("importFailed"));
122218
+ return;
122219
+ }
122220
+ const response = await fetch(IMPORT_ROUTE, {
122221
+ method: "POST",
122222
+ credentials: "same-origin",
122223
+ headers: { "content-type": "application/json" },
122224
+ body: JSON.stringify({ images })
122225
+ });
122226
+ if (!response.ok) throw new Error(`HTTP ${String(response.status)}`);
122227
+ const payload = await response.json();
122228
+ const imported = payload.images ?? [];
122229
+ let added = 0;
122230
+ let writeFailures = 0;
122231
+ for (const entry of imported) if (await saveGalleryItem({
122232
+ id: `import-${entry.attachment.attachmentId}-${crypto.randomUUID()}`,
122233
+ attachment: entry.attachment,
122234
+ prompt: "",
122235
+ provider: "import",
122236
+ model: "",
122237
+ createdAt: Date.now(),
122238
+ ...activeWorkspace?.workspaceId ? { workspaceId: activeWorkspace.workspaceId } : {},
122239
+ ...activeWorkspace?.path ? { workspacePath: activeWorkspace.path } : {},
122240
+ ...currentSessionId ? { sessionId: currentSessionId } : {}
122241
+ })) added += 1;
122242
+ else writeFailures += 1;
122243
+ const failCount = (payload.failures ?? []).length + (all.length - picked.length) + readFailures + writeFailures;
122244
+ if (added === 0) showToast(t("importFailed"));
122245
+ else if (failCount > 0) showToast(t("importPartial", {
122246
+ count: String(added),
122247
+ fail: String(failCount)
122248
+ }));
122249
+ else showToast(t("importAdded", { count: String(added) }));
122250
+ } catch {
122251
+ showToast(t("importFailed"));
122252
+ } finally {
122253
+ setImporting(false);
122254
+ }
122255
+ };
120104
122256
  const useInspirationPrompt = (0, react.useCallback)((prompt) => {
120105
122257
  setStudioDraft(prompt);
120106
122258
  setActiveTab("studio");
120107
122259
  }, []);
122260
+ const useInspirationReference = (0, react.useCallback)((file, prompt) => {
122261
+ setStudioDraft(prompt);
122262
+ setStudioReference(file);
122263
+ setActiveTab("studio");
122264
+ }, []);
120108
122265
  const clearStudioDraft = (0, react.useCallback)(() => setStudioDraft(void 0), []);
122266
+ const clearStudioReference = (0, react.useCallback)(() => setStudioReference(void 0), []);
120109
122267
  (0, react.useEffect)(() => {
120110
122268
  if (inSidebar) return void 0;
120111
122269
  const seat = document.querySelector("[data-composer-seat]");
@@ -120655,6 +122813,10 @@ img.ProseMirror-separator {
120655
122813
  (0, react_jsx_runtime.jsx)("option", {
120656
122814
  value: "comfyui",
120657
122815
  children: t("filterComfyUI")
122816
+ }),
122817
+ (0, react_jsx_runtime.jsx)("option", {
122818
+ value: "import",
122819
+ children: t("filterImport")
120658
122820
  })
120659
122821
  ]
120660
122822
  }),
@@ -120737,27 +122899,48 @@ img.ProseMirror-separator {
120737
122899
  ]
120738
122900
  }), (0, react_jsx_runtime.jsxs)("div", {
120739
122901
  className: "dsh-ig-studio-toolbar-right",
120740
- children: [(0, react_jsx_runtime.jsxs)("select", {
120741
- className: "dsh-ig-studio-select dsh-ig-studio-select-sort",
120742
- value: sortBy,
120743
- onChange: (e) => setSortBy(e.target.value),
120744
- children: [(0, react_jsx_runtime.jsx)("option", {
120745
- value: "newest",
120746
- children: t("sortNewest")
120747
- }), (0, react_jsx_runtime.jsx)("option", {
120748
- value: "oldest",
120749
- children: t("sortOldest")
120750
- })]
120751
- }), (0, react_jsx_runtime.jsxs)("button", {
120752
- type: "button",
120753
- className: `dsh-ig-studio-btn ${isManageMode ? "is-active" : ""}`,
120754
- title: isManageMode ? t("exitManage") : t("manage"),
120755
- onClick: () => {
120756
- if (isManageMode) handleExitManage();
120757
- else setIsManageMode(true);
120758
- },
120759
- children: [(0, react_jsx_runtime.jsx)(SquareCheckBig, { size: 13 }), (0, react_jsx_runtime.jsx)("span", { children: isManageMode ? t("exitManage") : t("manage") })]
120760
- })]
122902
+ children: [
122903
+ activeTab === "gallery" && (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsx)("input", {
122904
+ ref: importInputRef,
122905
+ type: "file",
122906
+ accept: "image/png,image/jpeg,image/webp,image/gif",
122907
+ multiple: true,
122908
+ style: { display: "none" },
122909
+ onChange: (event) => {
122910
+ importImages(event.target.files);
122911
+ event.target.value = "";
122912
+ }
122913
+ }), (0, react_jsx_runtime.jsxs)("button", {
122914
+ type: "button",
122915
+ className: "dsh-ig-studio-btn",
122916
+ disabled: importing,
122917
+ title: t("importPickHint"),
122918
+ onClick: () => importInputRef.current?.click(),
122919
+ children: [(0, react_jsx_runtime.jsx)(Upload, { size: 13 }), (0, react_jsx_runtime.jsx)("span", { children: t("importImages") })]
122920
+ })] }),
122921
+ (0, react_jsx_runtime.jsxs)("select", {
122922
+ className: "dsh-ig-studio-select dsh-ig-studio-select-sort",
122923
+ value: sortBy,
122924
+ onChange: (e) => setSortBy(e.target.value),
122925
+ children: [(0, react_jsx_runtime.jsx)("option", {
122926
+ value: "newest",
122927
+ children: t("sortNewest")
122928
+ }), (0, react_jsx_runtime.jsx)("option", {
122929
+ value: "oldest",
122930
+ children: t("sortOldest")
122931
+ })]
122932
+ }),
122933
+ (0, react_jsx_runtime.jsxs)("button", {
122934
+ type: "button",
122935
+ className: `dsh-ig-studio-btn ${isManageMode ? "is-active" : ""}`,
122936
+ title: isManageMode ? t("exitManage") : t("manage"),
122937
+ onClick: () => {
122938
+ if (isManageMode) handleExitManage();
122939
+ else setIsManageMode(true);
122940
+ },
122941
+ children: [(0, react_jsx_runtime.jsx)(SquareCheckBig, { size: 13 }), (0, react_jsx_runtime.jsx)("span", { children: isManageMode ? t("exitManage") : t("manage") })]
122942
+ })
122943
+ ]
120761
122944
  })]
120762
122945
  }),
120763
122946
  (0, react_jsx_runtime.jsx)("div", {
@@ -120827,15 +123010,18 @@ img.ProseMirror-separator {
120827
123010
  }, item.id))
120828
123011
  }) : activeTab === "inspiration" ? (0, react_jsx_runtime.jsx)(InspirationView, {
120829
123012
  locale,
120830
- onUsePrompt: useInspirationPrompt
123013
+ onUsePrompt: useInspirationPrompt,
123014
+ onUseReference: useInspirationReference
120831
123015
  }) : (0, react_jsx_runtime.jsx)(StudioView, {
120832
123016
  locale,
120833
123017
  credentialEvents,
120834
123018
  workspace: activeWorkspace,
120835
123019
  initialPrompt: studioDraft,
123020
+ initialReference: studioReference,
120836
123021
  initialCanvasSurface,
120837
123022
  showInfiniteCanvasHint: !inSidebar,
120838
123023
  onInitialPromptApplied: clearStudioDraft,
123024
+ onInitialReferenceApplied: clearStudioReference,
120839
123025
  onOpenInspiration: () => setActiveTab("inspiration")
120840
123026
  })
120841
123027
  }),
@@ -120995,7 +123181,7 @@ img.ProseMirror-separator {
120995
123181
  children: [
120996
123182
  (0, react_jsx_runtime.jsx)("span", {
120997
123183
  className: "dsh-ig-tag",
120998
- children: previewItem.provider
123184
+ children: previewItem.provider === "import" ? t("filterImport") : previewItem.provider
120999
123185
  }),
121000
123186
  previewItem.model && (0, react_jsx_runtime.jsx)("span", {
121001
123187
  className: "dsh-ig-tag dsh-ig-tag-model",
@@ -121053,7 +123239,7 @@ img.ProseMirror-separator {
121053
123239
  children: [
121054
123240
  (0, react_jsx_runtime.jsx)("span", {
121055
123241
  className: "dsh-ig-tag",
121056
- children: previewItem.provider
123242
+ children: previewItem.provider === "import" ? t("filterImport") : previewItem.provider
121057
123243
  }),
121058
123244
  previewItem.model ? (0, react_jsx_runtime.jsx)("span", {
121059
123245
  className: "dsh-ig-tag dsh-ig-tag-model",
@@ -121646,105 +123832,139 @@ img.ProseMirror-separator {
121646
123832
  //#region lib/types/client/studio-style.js
121647
123833
  const STUDIO_STYLE = `
121648
123834
  .dsh-ig-gallery-page-body.is-workbench{flex:1 1 0;height:100%;min-height:0;padding:0;overflow:hidden;display:flex;flex-direction:column}
121649
- .dsh-ig-workbench{--ig-blue:#2f64f5;--ig-line:color-mix(in srgb,currentColor 11%,transparent);--ig-soft:color-mix(in srgb,currentColor 4%,transparent);position:relative;display:flex;flex-direction:column;min-height:0;height:100%;overflow:hidden;padding:10px 12px 12px;color:var(--foreground,#182033);box-sizing:border-box;background:radial-gradient(circle at 60% -20%,rgba(63,104,246,.07),transparent 30%)}
123835
+ .dsh-ig-workbench{--ig-blue:var(--dsw-alias-brand-primary-new-colorprimary-new-color,#2f64f5);--ig-blue-line:color-mix(in srgb,var(--ig-blue) 45%,transparent);--ig-line:var(--dsw-alias-border-l2,color-mix(in srgb,currentColor 11%,transparent));--ig-soft:color-mix(in srgb,currentColor 4%,transparent);--ig-fg:var(--dsw-alias-label-primary,var(--foreground,#182033));--ig-fg-2:var(--dsw-alias-label-secondary,#5a6478);--ig-fg-3:var(--dsw-alias-label-tertiary,#717b90);--ig-fg-cap:var(--dsw-alias-label-caption,#929bad);--ig-bg:var(--dsw-alias-bg-layer-1,var(--background,#fff));--ig-bg-panel:var(--dsw-alias-bg-layer-2,color-mix(in srgb,var(--background,#fff) 96%,#f5f7fb));--ig-bg-thumb:var(--dsw-alias-bg-skeleton,#eef1f7);--ig-danger:#b63737;--ig-warn:#8b671c;position:relative;display:flex;flex-direction:column;min-height:0;height:100%;overflow:hidden;padding:10px 12px 12px;color:var(--ig-fg);box-sizing:border-box;background:radial-gradient(circle at 60% -20%,rgba(63,104,246,.07),transparent 30%)}
123836
+ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a}
121650
123837
  .dsh-ig-workbench *{box-sizing:border-box}.dsh-ig-workbench button,.dsh-ig-workbench select,.dsh-ig-workbench textarea{font:inherit;color:inherit}
121651
- .dsh-ig-workbench-grid{display:grid;grid-template-columns:minmax(190px,238px) minmax(0,1fr) minmax(286px,340px);grid-template-rows:minmax(0,1fr);min-height:0;height:0;flex:1 1 0;border:1px solid var(--ig-line);border-radius:12px;overflow:hidden;background:var(--background,#fff);box-shadow:0 12px 32px rgba(30,42,76,.05);transition:grid-template-columns .2s ease}
123838
+ .dsh-ig-workbench-grid{display:grid;grid-template-columns:minmax(190px,238px) minmax(0,1fr) minmax(286px,340px);grid-template-rows:minmax(0,1fr);min-height:0;height:0;flex:1 1 0;border:1px solid var(--ig-line);border-radius:12px;overflow:hidden;background:var(--ig-bg);box-shadow:0 12px 32px rgba(30,42,76,.05);transition:grid-template-columns .2s ease}
121652
123839
  .dsh-ig-workbench-grid.is-sidebar-collapsed{grid-template-columns:minmax(0,1fr) minmax(286px,340px)}
121653
123840
  .dsh-ig-workbench-grid.is-generate-collapsed{grid-template-columns:minmax(190px,238px) minmax(0,1fr)}
121654
123841
  .dsh-ig-workbench-grid.is-sidebar-collapsed.is-generate-collapsed{grid-template-columns:minmax(0,1fr)}
121655
- .dsh-ig-recent-panel,.dsh-ig-generate-panel{min-width:0;min-height:0;overflow:hidden;background:color-mix(in srgb,var(--background,#fff) 96%,#f5f7fb)}.dsh-ig-recent-panel{display:flex;flex-direction:column;border-right:1px solid var(--ig-line)}
123842
+ .dsh-ig-recent-panel,.dsh-ig-generate-panel{min-width:0;min-height:0;overflow:hidden;background:var(--ig-bg-panel)}.dsh-ig-recent-panel{display:flex;flex-direction:column;border-right:1px solid var(--ig-line)}
121656
123843
  .dsh-ig-panel-title{height:48px;flex:0 0 48px;padding:0 12px 0 14px;display:flex;align-items:center;justify-content:space-between;border-bottom:1px solid var(--ig-line);font-size:13px;font-weight:650}
121657
- .dsh-ig-count-badge{font-size:11px;min-width:22px;text-align:center;padding:2px 6px;border-radius:999px;background:var(--ig-soft);color:#7b8498;font-weight:500}
121658
- .dsh-ig-collapse-btn{width:28px;height:28px;display:inline-flex;align-items:center;justify-content:center;border:1px solid transparent;border-radius:6px;background:transparent;color:#717b90;cursor:pointer;transition:all .15s}
121659
- .dsh-ig-collapse-btn:hover{border-color:var(--ig-line);background:var(--ig-soft);color:var(--foreground,#182033)}
121660
- .dsh-ig-recent-scroll{flex:1 1 0;height:0;padding:8px;overflow-x:hidden;overflow-y:auto;min-height:0;display:grid;grid-template-columns:repeat(auto-fill,minmax(78px,1fr));gap:6px;align-content:start}.dsh-ig-recent-empty{min-height:180px;display:flex;flex-direction:column;justify-content:center;align-items:center;gap:10px;color:#929bad;text-align:center;font-size:12px;grid-column:1/-1}
121661
- .dsh-ig-recent-item{position:relative;width:100%;aspect-ratio:1;align-self:start;padding:0;border:1px solid transparent;border-radius:8px;background:transparent;cursor:pointer;transition:border-color .16s,box-shadow .16s}.dsh-ig-recent-item:hover{border-color:#b9ccfa;box-shadow:0 1px 4px rgba(30,42,76,.14)}.dsh-ig-recent-item.is-active{border-color:var(--ig-blue);box-shadow:0 0 0 2px rgba(47,100,245,.18)}.dsh-ig-recent-thumb{width:100%;height:100%;border-radius:7px;display:flex;align-items:center;justify-content:center;overflow:hidden;background:#eef1f7;color:#929bad}.dsh-ig-recent-thumb img{width:100%;height:100%;object-fit:cover}
121662
- .dsh-ig-load-more{grid-column:1/-1;width:100%;height:32px;margin:4px 0 2px;display:flex;align-items:center;justify-content:center;border:1px dashed var(--ig-line);border-radius:7px;background:transparent;font-size:11px;color:#6b758b;cursor:pointer;transition:background .15s,color .15s}.dsh-ig-load-more:hover{background:var(--ig-soft);color:var(--ig-blue);border-color:#a2b9f6}
121663
- .dsh-ig-mini-btn{height:22px;padding:0 7px;display:inline-flex;align-items:center;gap:3px;border:1px solid var(--ig-line);border-radius:5px;background:var(--background,#fff);font-size:10.5px;font-weight:500;color:var(--foreground,#242f44);cursor:pointer;transition:all .15s}.dsh-ig-mini-btn:hover{border-color:#9bb4f5;color:var(--ig-blue);background:rgba(47,100,245,.06)}
121664
- .dsh-ig-canvas-column{min-width:0;min-height:0;height:100%;overflow:hidden;display:flex;flex-direction:column;background:color-mix(in srgb,var(--background,#fff) 97%,#e8edf7)}
123844
+ .dsh-ig-count-badge{font-size:11px;min-width:22px;text-align:center;padding:2px 6px;border-radius:999px;background:var(--ig-soft);color:var(--ig-fg-3);font-weight:500}
123845
+ .dsh-ig-collapse-btn{width:28px;height:28px;display:inline-flex;align-items:center;justify-content:center;border:1px solid transparent;border-radius:6px;background:transparent;color:var(--ig-fg-3);cursor:pointer;transition:all .15s}
123846
+ .dsh-ig-collapse-btn:hover{border-color:var(--ig-line);background:var(--ig-soft);color:var(--ig-fg)}
123847
+ .dsh-ig-recent-scroll{flex:1 1 0;height:0;padding:8px;overflow-x:hidden;overflow-y:auto;min-height:0;display:grid;grid-template-columns:repeat(auto-fill,minmax(78px,1fr));gap:6px;align-content:start}.dsh-ig-recent-empty{min-height:180px;display:flex;flex-direction:column;justify-content:center;align-items:center;gap:10px;color:var(--ig-fg-cap);text-align:center;font-size:12px;grid-column:1/-1}
123848
+ .dsh-ig-fav-folder-add{margin-left:auto;padding:0 5px;border:0;background:transparent;color:var(--ig-fg-3);font-size:12px;line-height:16px;cursor:pointer;border-radius:4px}.dsh-ig-fav-folder-add:hover{color:var(--ig-blue);background:var(--ig-bg-thumb)}
123849
+ .dsh-ig-fav-folders{display:flex;flex-wrap:wrap;gap:4px;margin:4px 0 6px}
123850
+ .dsh-ig-fav-folder-chip{display:inline-flex;align-items:center;gap:2px;padding:2px 6px;border:1px solid var(--ig-line);border-radius:999px;background:transparent;color:var(--ig-fg-3);font-size:10.5px}
123851
+ .dsh-ig-fav-folder-chip.is-active{border-color:var(--ig-blue-line);color:var(--ig-blue);background:color-mix(in srgb,var(--ig-blue) 10%,transparent)}
123852
+ .dsh-ig-fav-folder-chip>button:first-child{border:0;background:transparent;color:inherit;font-size:inherit;padding:0;cursor:pointer}
123853
+ .dsh-ig-fav-folder-del{border:0;background:transparent;color:var(--ig-fg-3);cursor:pointer;display:inline-flex;padding:0}.dsh-ig-fav-folder-del:hover{color:var(--ig-danger)}
123854
+ .dsh-ig-fav-folder-draft{display:flex;gap:4px;margin:2px 0 6px}
123855
+ .dsh-ig-fav-folder-draft input{flex:1;min-width:0;padding:3px 6px;border:1px solid var(--ig-line);border-radius:5px;background:var(--ig-bg);color:var(--ig-fg);font-size:11px}
123856
+ .dsh-ig-fav-folder-draft button{padding:3px 8px;border:1px solid var(--ig-line);border-radius:5px;background:transparent;color:var(--ig-fg-2);font-size:11px;cursor:pointer}
123857
+ .dsh-ig-fav-folder-draft button:first-of-type{border-color:var(--ig-blue-line);color:var(--ig-blue)}
123858
+ .dsh-ig-fav-dialog-wrap{position:fixed;inset:0;z-index:60;display:flex;align-items:center;justify-content:center;background:rgba(15,20,34,.45)}
123859
+ .dsh-ig-fav-dialog{width:min(420px,92vw);max-height:70vh;overflow:auto;padding:14px;border-radius:10px;background:var(--ig-bg-panel);border:1px solid var(--ig-line);box-shadow:0 12px 32px rgba(10,14,26,.25);display:flex;flex-direction:column;gap:10px}
123860
+ .dsh-ig-fav-dialog-title{font-size:12.5px;font-weight:600;color:var(--ig-fg)}
123861
+ .dsh-ig-fav-dialog-textarea{width:100%;resize:vertical;padding:8px;border:1px solid var(--ig-line);border-radius:6px;background:var(--ig-bg);color:var(--ig-fg);font-size:12px;line-height:1.5}
123862
+ .dsh-ig-fav-dialog-folder-label{display:flex;align-items:center;gap:8px;font-size:11.5px;color:var(--ig-fg-2)}
123863
+ .dsh-ig-fav-dialog-folder-label select{flex:1;padding:4px 6px;border:1px solid var(--ig-line);border-radius:5px;background:var(--ig-bg);color:var(--ig-fg);font-size:11.5px}
123864
+ .dsh-ig-fav-dialog-actions{display:flex;gap:6px;justify-content:flex-end;flex-wrap:wrap}
123865
+ .dsh-ig-fav-dialog-actions button{padding:5px 12px;border:1px solid var(--ig-line);border-radius:6px;background:transparent;color:var(--ig-fg-2);font-size:12px;cursor:pointer}
123866
+ .dsh-ig-fav-dialog-actions button.is-primary{border-color:var(--ig-blue);background:var(--ig-blue);color:#fff}
123867
+ .dsh-ig-fav-dialog-folders{display:flex;flex-direction:column;gap:4px}
123868
+ .dsh-ig-fav-dialog-folders button{text-align:left;padding:6px 10px;border:1px solid var(--ig-line);border-radius:6px;background:transparent;color:var(--ig-fg-2);font-size:12px;cursor:pointer}
123869
+ .dsh-ig-fav-dialog-folders button:hover,.dsh-ig-fav-dialog-folders button.is-current{border-color:var(--ig-blue-line);color:var(--ig-blue);background:color-mix(in srgb,var(--ig-blue) 8%,transparent)}
123870
+ .dsh-ig-fav-del{position:absolute;top:-5px;right:-5px;width:18px;height:18px;display:inline-flex;align-items:center;justify-content:center;padding:0;border:1px solid var(--ig-line);border-radius:50%;background:var(--ig-bg);color:var(--ig-fg-3);cursor:pointer;z-index:2;opacity:0;pointer-events:none;transition:opacity .12s,color .15s,border-color .15s}.dsh-ig-fav-del:hover{color:#d5495c;border-color:#e6a3ad}.dsh-ig-fav-prompt:hover .dsh-ig-fav-del,.dsh-ig-fav-del:focus-visible{opacity:1;pointer-events:auto}.dsh-ig-fav-del:focus-visible{outline:2px solid var(--ig-blue);outline-offset:1px}
123871
+ .dsh-ig-fav-prompt{position:relative;display:flex;align-items:flex-start;gap:2px}
123872
+ .dsh-ig-fav-prompt-text{flex:1;min-width:0;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;text-align:left;padding:5px 8px;border:1px solid var(--ig-line);border-radius:6px;background:transparent;font-size:11px;line-height:1.45;color:var(--ig-fg);cursor:pointer;transition:all .15s}.dsh-ig-fav-prompt-text:hover{border-color:var(--ig-blue-line);background:var(--ig-soft)}
123873
+ .dsh-ig-fav-save-btn{display:inline-flex;align-items:center;justify-content:center;padding:2px 4px;border:0;background:transparent;color:var(--ig-fg-cap);cursor:pointer;transition:color .15s}.dsh-ig-fav-save-btn:hover{color:#e8a33d}
123874
+ .dsh-ig-rail-tabs{flex:none;display:flex;gap:4px;padding:4px 8px 6px;border-bottom:1px solid var(--ig-line)}
123875
+ .dsh-ig-rail-tab{flex:1;height:26px;display:inline-flex;align-items:center;justify-content:center;border:1px solid transparent;border-radius:6px;background:transparent;font-size:12px;color:var(--ig-fg-3);cursor:pointer;transition:all .15s}.dsh-ig-rail-tab:hover{background:var(--ig-soft);color:var(--ig-fg)}.dsh-ig-rail-tab.is-active{border-color:var(--ig-blue-line);background:var(--ig-soft);color:var(--ig-blue);font-weight:600}
123876
+ .dsh-ig-fav-scroll{flex:1 1 0;height:0;overflow-x:hidden;overflow-y:auto;min-height:0;padding:8px}
123877
+ .dsh-ig-fav-section-label{padding:4px 2px 6px;font-size:11px;font-weight:600;color:var(--ig-fg-3)}
123878
+ .dsh-ig-fav-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(64px,1fr));gap:6px}
123879
+ .dsh-ig-fav-empty{min-height:64px;display:flex;flex-direction:column;justify-content:center;align-items:center;gap:8px;color:var(--ig-fg-cap);text-align:center;font-size:11px;border:1px dashed var(--ig-line);border-radius:8px;margin-bottom:8px;padding:8px}
123880
+ .dsh-ig-fav-prompts{display:flex;flex-direction:column;gap:4px}
123881
+ .dsh-ig-recent-item{position:relative;width:100%;aspect-ratio:1;align-self:start;padding:0;border:1px solid transparent;border-radius:8px;background:transparent;cursor:pointer;transition:border-color .16s,box-shadow .16s}.dsh-ig-recent-item:hover{border-color:var(--ig-blue-line);box-shadow:0 1px 4px rgba(30,42,76,.14)}.dsh-ig-recent-item.is-active{border-color:var(--ig-blue);box-shadow:0 0 0 2px rgba(47,100,245,.18)}.dsh-ig-recent-thumb{width:100%;height:100%;border-radius:7px;display:flex;align-items:center;justify-content:center;overflow:hidden;background:var(--ig-bg-thumb);color:var(--ig-fg-cap)}.dsh-ig-recent-thumb img{width:100%;height:100%;object-fit:cover}
123882
+ .dsh-ig-load-more{grid-column:1/-1;width:100%;height:32px;margin:4px 0 2px;display:flex;align-items:center;justify-content:center;border:1px dashed var(--ig-line);border-radius:7px;background:transparent;font-size:11px;color:var(--ig-fg-2);cursor:pointer;transition:background .15s,color .15s}.dsh-ig-load-more:hover{background:var(--ig-soft);color:var(--ig-blue);border-color:var(--ig-blue-line)}
123883
+ .dsh-ig-mini-btn{height:22px;padding:0 7px;display:inline-flex;align-items:center;gap:3px;border:1px solid var(--ig-line);border-radius:5px;background:var(--ig-bg);font-size:10.5px;font-weight:500;color:var(--ig-fg);cursor:pointer;transition:all .15s}.dsh-ig-mini-btn:hover{border-color:var(--ig-blue-line);color:var(--ig-blue);background:rgba(47,100,245,.06)}
123884
+ .dsh-ig-canvas-column{min-width:0;min-height:0;height:100%;overflow:hidden;display:flex;flex-direction:column;background:var(--dsw-alias-bg-layer-2,var(--ig-bg))}
121665
123885
  .dsh-ig-canvas-toolbar{height:46px;flex:0 0 46px;display:grid;grid-template-columns:1fr auto 1fr;align-items:center;gap:8px;padding:0 14px;border-bottom:1px solid var(--ig-line)}
121666
123886
  .dsh-ig-canvas-toolbar-left,.dsh-ig-canvas-toolbar-right{display:flex;align-items:center;gap:6px;min-width:0}
121667
123887
  .dsh-ig-canvas-toolbar-left{justify-self:start}
121668
123888
  .dsh-ig-canvas-toolbar-right{justify-self:end}
121669
123889
  .dsh-ig-canvas-toolbar-center{display:flex;align-items:center;justify-self:center;gap:6px;min-width:0}
121670
123890
  .dsh-ig-toolbar-divider{width:1px;height:16px;background:var(--ig-line);flex-shrink:0;margin:0 2px}
121671
- .dsh-ig-canvas-toolbar button{height:30px;min-width:30px;padding:0 9px;display:inline-flex;align-items:center;justify-content:center;gap:5px;border:1px solid var(--ig-line);border-radius:7px;background:var(--background,#fff);color:var(--foreground,#242f44);font-size:11.5px;font-weight:500;cursor:pointer;transition:border-color .15s,background .15s,color .15s}
121672
- .dsh-ig-canvas-toolbar button:hover{border-color:#9bb4f5;background:rgba(47,100,245,.05);color:var(--ig-blue)}
121673
- .dsh-ig-canvas-toolbar button.is-active{border-color:#8ba8f8;background:rgba(47,100,245,.08);color:var(--ig-blue);font-weight:550}
123891
+ .dsh-ig-canvas-toolbar button{height:30px;min-width:30px;padding:0 9px;display:inline-flex;align-items:center;justify-content:center;gap:5px;border:1px solid var(--ig-line);border-radius:7px;background:var(--ig-bg);color:var(--ig-fg);font-size:11.5px;font-weight:500;cursor:pointer;transition:border-color .15s,background .15s,color .15s}
123892
+ .dsh-ig-canvas-toolbar button:hover{border-color:var(--ig-blue-line);background:rgba(47,100,245,.05);color:var(--ig-blue)}
123893
+ .dsh-ig-canvas-toolbar button.is-active{border-color:var(--ig-blue-line);background:rgba(47,100,245,.08);color:var(--ig-blue);font-weight:550}
121674
123894
  .dsh-ig-canvas-toolbar-center button.is-active{border-color:var(--ig-blue);background:rgba(47,100,245,.09);color:var(--ig-blue);font-weight:600;box-shadow:inset 0 0 0 1px rgba(47,100,245,.28)}
121675
- .dsh-ig-zoom-display{font-size:11px;min-width:38px;text-align:center;color:#64748b;user-select:none;font-variant-numeric:tabular-nums}
121676
- .dsh-ig-canvas-hint{flex:0 0 auto;display:flex;align-items:flex-start;gap:8px;padding:7px 12px;border-bottom:1px solid rgba(47,100,245,.14);background:rgba(47,100,245,.055);color:#3d4a66;font-size:11.5px;line-height:17px}.dsh-ig-canvas-hint>svg:first-child{flex-shrink:0;color:var(--ig-blue);margin-top:1px}.dsh-ig-canvas-hint>span{flex:1 1 0;min-width:0}.dsh-ig-canvas-hint>button{flex-shrink:0;width:22px;height:22px;display:inline-flex;align-items:center;justify-content:center;border:0;border-radius:5px;background:transparent;color:#8a94a8;cursor:pointer;transition:background .15s,color .15s}.dsh-ig-canvas-hint>button:hover{background:rgba(47,100,245,.1);color:var(--ig-blue)}
123895
+ .dsh-ig-zoom-display{font-size:11px;min-width:38px;text-align:center;color:var(--ig-fg-2);user-select:none;font-variant-numeric:tabular-nums}
123896
+ .dsh-ig-canvas-hint{flex:0 0 auto;display:flex;align-items:flex-start;gap:8px;padding:7px 12px;border-bottom:1px solid rgba(47,100,245,.14);background:rgba(47,100,245,.055);color:var(--ig-fg-2);font-size:11.5px;line-height:17px}.dsh-ig-canvas-hint>svg:first-child{flex-shrink:0;color:var(--ig-blue);margin-top:1px}.dsh-ig-canvas-hint>span{flex:1 1 0;min-width:0}.dsh-ig-canvas-hint>button{flex-shrink:0;width:22px;height:22px;display:inline-flex;align-items:center;justify-content:center;border:0;border-radius:5px;background:transparent;color:var(--ig-fg-cap);cursor:pointer;transition:background .15s,color .15s}.dsh-ig-canvas-hint>button:hover{background:rgba(47,100,245,.1);color:var(--ig-blue)}
121677
123897
  .dsh-ig-status-badge-wrap{position:relative;display:inline-flex;align-items:center}
121678
- .dsh-ig-status-badge{height:30px;display:inline-flex;align-items:center;gap:7px;padding:0 10px;border:1px solid var(--ig-line);border-radius:7px;background:var(--background,#fff);cursor:pointer;user-select:none;transition:border-color .15s,background .15s,box-shadow .15s}
121679
- .dsh-ig-status-badge:hover{border-color:#9bb4f5;background:rgba(47,100,245,.04)}
123898
+ .dsh-ig-status-badge{height:30px;display:inline-flex;align-items:center;gap:7px;padding:0 10px;border:1px solid var(--ig-line);border-radius:7px;background:var(--ig-bg);cursor:pointer;user-select:none;transition:border-color .15s,background .15s,box-shadow .15s}
123899
+ .dsh-ig-status-badge:hover{border-color:var(--ig-blue-line);background:rgba(47,100,245,.04)}
121680
123900
  .dsh-ig-status-dot{width:7px;height:7px;min-width:7px;max-width:7px;border-radius:50%;flex-shrink:0;box-sizing:border-box}
121681
123901
  .dsh-ig-status-dot.is-ready{background:#10b981;box-shadow:0 0 0 2px rgba(16,185,129,.2)}
121682
123902
  .dsh-ig-status-dot.is-muted{background:#ef4444;box-shadow:0 0 0 2px rgba(239,68,68,.2)}
121683
- .dsh-ig-status-text{font-size:11.5px;font-weight:500;color:var(--foreground,#242f44);line-height:1}
121684
- .dsh-ig-status-arrow{color:#94a3b8;transition:transform .15s,color .15s}
123903
+ .dsh-ig-status-text{font-size:11.5px;font-weight:500;color:var(--ig-fg);line-height:1}
123904
+ .dsh-ig-status-arrow{color:var(--ig-fg-cap);transition:transform .15s,color .15s}
121685
123905
  .dsh-ig-status-badge-wrap:hover .dsh-ig-status-arrow{transform:rotate(180deg);color:var(--ig-blue)}
121686
- .dsh-ig-status-popover{position:absolute;left:0;top:calc(100% + 6px);width:260px;background:var(--background,#fff);border:1px solid var(--ig-line);border-radius:9px;box-shadow:0 12px 36px rgba(20,28,45,.14);padding:10px 12px;z-index:40;opacity:0;pointer-events:none;transform:translateY(4px);transition:opacity .15s cubic-bezier(.16,1,.3,1),transform .15s cubic-bezier(.16,1,.3,1)}
123906
+ .dsh-ig-status-popover{position:absolute;left:0;top:calc(100% + 6px);width:260px;background:var(--ig-bg);border:1px solid var(--ig-line);border-radius:9px;box-shadow:0 12px 36px rgba(20,28,45,.14);padding:10px 12px;z-index:40;opacity:0;pointer-events:none;transform:translateY(4px);transition:opacity .15s cubic-bezier(.16,1,.3,1),transform .15s cubic-bezier(.16,1,.3,1)}
121687
123907
  .dsh-ig-status-badge-wrap:hover .dsh-ig-status-popover{opacity:1;pointer-events:auto;transform:translateY(0)}
121688
- .dsh-ig-status-popover-title{display:flex;align-items:center;justify-content:space-between;font-size:10px;font-weight:650;color:#8590a6;margin-bottom:8px;letter-spacing:.02em;text-transform:uppercase}
123908
+ .dsh-ig-status-popover-title{display:flex;align-items:center;justify-content:space-between;font-size:10px;font-weight:650;color:var(--ig-fg-cap);margin-bottom:8px;letter-spacing:.02em;text-transform:uppercase}
121689
123909
  .dsh-ig-status-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:5px}
121690
123910
  .dsh-ig-status-list li{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:6px 8px;border-radius:6px;transition:background .12s}
121691
123911
  .dsh-ig-status-list li.is-configured{background:rgba(16,185,129,.05);border:1px solid rgba(16,185,129,.12)}
121692
123912
  .dsh-ig-status-list li.is-missing{background:rgba(239,68,68,.04);border:1px solid rgba(239,68,68,.12);opacity:.75}
121693
123913
  .dsh-ig-status-item-name{display:flex;flex-direction:column;min-width:0}
121694
- .dsh-ig-status-item-name strong{font-size:11.5px;font-weight:600;color:var(--foreground,#182033);line-height:16px}
121695
- .dsh-ig-status-item-name small{font-size:9.5px;color:#7c869a;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:145px;line-height:14px}
123914
+ .dsh-ig-status-item-name strong{font-size:11.5px;font-weight:600;color:var(--ig-fg);line-height:16px}
123915
+ .dsh-ig-status-item-name small{font-size:9.5px;color:var(--ig-fg-cap);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:145px;line-height:14px}
121696
123916
  .dsh-ig-status-item-tag{font-size:10px;font-weight:600;padding:2px 6px;border-radius:4px;flex-shrink:0}
121697
123917
  .is-configured .dsh-ig-status-item-tag{color:#059669;background:rgba(16,185,129,.14)}
121698
123918
  .is-missing .dsh-ig-status-item-tag{color:#dc2626;background:rgba(239,68,68,.14)}
121699
123919
  .dsh-ig-canvas{position:relative;flex:1 1 0;height:0;min-height:0;display:flex;align-items:center;justify-content:center;overflow:hidden;padding:24px;touch-action:none;user-select:none;cursor:grab;background-image:linear-gradient(45deg,rgba(114,128,156,.035) 25%,transparent 25%),linear-gradient(-45deg,rgba(114,128,156,.035) 25%,transparent 25%),linear-gradient(45deg,transparent 75%,rgba(114,128,156,.035) 75%),linear-gradient(-45deg,transparent 75%,rgba(114,128,156,.035) 75%);background-size:24px 24px;background-position:0 0,0 12px,12px -12px,-12px 0}
121700
123920
  .dsh-ig-canvas.is-dragging,.dsh-ig-canvas.is-dragging *{cursor:grabbing!important}
121701
123921
  .dsh-ig-canvas img{display:block;max-width:100%;max-height:100%;width:auto;height:auto;border-radius:10px;box-shadow:0 14px 45px rgba(25,35,63,.18);user-select:none;-webkit-user-drag:none;pointer-events:auto}
121702
- .dsh-ig-canvas-empty,.dsh-ig-generating-state{display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;gap:12px;color:#929bad;font-size:12px;padding:26px;cursor:default}
121703
- .dsh-ig-generating-state strong{font-size:15px;color:var(--foreground,#182033)}.dsh-ig-generating-state span{font-size:11px}.dsh-ig-generation-orbit{width:66px;height:66px;border-radius:50%;display:grid;place-items:center;color:var(--ig-blue);background:rgba(47,100,245,.09);box-shadow:0 0 0 10px rgba(47,100,245,.035);animation:dshIgPulse 1.8s ease-in-out infinite}
121704
- .dsh-ig-result-strip{flex:0 0 auto;min-height:52px;padding:8px 14px;display:flex;align-items:center;justify-content:space-between;gap:12px;border-top:1px solid var(--ig-line)}.dsh-ig-result-strip>span{font-size:12px;font-weight:650}.dsh-ig-result-strip>div,.dsh-ig-result-actions>div{display:flex;gap:7px}.dsh-ig-result-strip button,.dsh-ig-result-actions button{height:32px;display:inline-flex;align-items:center;gap:6px;padding:0 10px;border:1px solid var(--ig-line);border-radius:7px;background:var(--background,#fff);font-size:11px;cursor:pointer}.dsh-ig-result-strip button:hover,.dsh-ig-result-actions button:hover{border-color:#9fb2ed;color:var(--ig-blue)}.dsh-ig-result-actions{flex:0 0 auto;min-height:54px;padding:8px 14px;display:flex;align-items:center;justify-content:space-between;gap:12px;border-top:1px solid var(--ig-line)}.dsh-ig-result-actions p{margin:0;min-width:0;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;font-size:11.5px;line-height:18px;color:#596277}.dsh-ig-result-meta{flex:0 0 auto;min-height:32px;padding:6px 14px;display:flex;gap:13px;align-items:center;flex-wrap:wrap;border-top:1px solid var(--ig-line);font-size:10px;color:#8790a3}.dsh-ig-result-meta span+span:before{content:'·';margin-right:13px}
121705
- .dsh-ig-generate-panel{height:100%;display:flex;flex-direction:column;border-left:1px solid var(--ig-line);overflow:hidden}.dsh-ig-generator-form{flex:1 1 0;height:0;padding:15px;display:flex;flex-direction:column;gap:15px;overflow-x:hidden;overflow-y:auto;min-height:0}.dsh-ig-mode-switch{display:grid;grid-template-columns:1fr 1fr;gap:8px}.dsh-ig-mode-switch button{height:39px;border:1px solid var(--ig-line);border-radius:8px;background:var(--background,#fff);display:flex;align-items:center;justify-content:center;gap:7px;font-size:12px;cursor:pointer}.dsh-ig-mode-switch button.is-active{border-color:#789bff;color:var(--ig-blue);background:rgba(47,100,245,.065);box-shadow:inset 0 0 0 1px rgba(47,100,245,.1)}
121706
- .dsh-ig-field{position:relative;display:flex;flex-direction:column;gap:7px}.dsh-ig-field>label,.dsh-ig-field-label label{font-size:11.5px;font-weight:630}.dsh-ig-field>label span{font-weight:400;color:#929bad}.dsh-ig-field-label{display:flex;align-items:center;justify-content:space-between}.dsh-ig-field-label b{color:#e14d4d}.dsh-ig-field-label button{border:0;background:transparent;color:#798397;font-size:10.5px;cursor:pointer}.dsh-ig-field textarea{width:100%;height:112px;padding:11px 12px 22px;resize:vertical;border:1px solid var(--ig-line);border-radius:9px;background:var(--background,#fff);font-size:12px;line-height:19px;outline:none}.dsh-ig-field textarea:focus{border-color:var(--ig-blue);box-shadow:0 0 0 3px rgba(47,100,245,.09)}.dsh-ig-field>small{position:absolute;right:9px;bottom:7px;color:#9aa2b2;font-size:9.5px}.dsh-ig-field-grid{display:grid;grid-template-columns:1fr 1fr;gap:9px}.dsh-ig-field-select{display:flex;flex-direction:column;gap:6px;min-width:0}.dsh-ig-field-select>span{font-size:11px;font-weight:620}.dsh-ig-field-select select{width:100%;height:36px;padding:0 26px 0 9px;border:1px solid var(--ig-line);border-radius:8px;background:var(--background,#fff);font-size:11px;outline:none;text-overflow:ellipsis}.dsh-ig-field-select select:focus{border-color:var(--ig-blue)}.dsh-ig-count-row{display:grid;grid-template-columns:repeat(4,1fr);gap:6px}.dsh-ig-count-pill{height:34px;border:1px solid var(--ig-line);border-radius:7px;background:var(--background,#fff);font-size:12px;font-weight:600;color:#596277;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all .15s}.dsh-ig-count-pill:hover{border-color:#9fb2ed;color:var(--ig-blue)}.dsh-ig-count-pill.is-active{border-color:var(--ig-blue);background:rgba(47,100,245,.08);color:var(--ig-blue);box-shadow:inset 0 0 0 1px var(--ig-blue)}
121707
- .dsh-ig-generation-kind{display:grid;grid-template-columns:1fr 1fr;gap:4px;padding:3px;border:1px solid var(--ig-line);border-radius:9px;background:var(--ig-soft)}.dsh-ig-generation-kind button{height:31px;border:0;border-radius:6px;background:transparent;color:#778196;font-size:11.5px;display:flex;align-items:center;justify-content:center;gap:6px;cursor:pointer;transition:background .15s,color .15s,box-shadow .15s}.dsh-ig-generation-kind button.is-active{background:var(--background,#fff);color:var(--ig-blue);font-weight:650;box-shadow:0 2px 8px rgba(30,42,76,.09)}
121708
- .dsh-ig-compare-box{padding:10px;border:1px solid rgba(47,100,245,.2);border-radius:10px;background:linear-gradient(145deg,rgba(47,100,245,.055),rgba(99,102,241,.018));display:flex;flex-direction:column;gap:9px}.dsh-ig-compare-heading{display:flex;align-items:flex-start;justify-content:space-between;gap:8px}.dsh-ig-compare-heading>div{min-width:0;display:flex;flex-direction:column;gap:2px}.dsh-ig-compare-heading strong{font-size:11.5px}.dsh-ig-compare-heading small{font-size:9.5px;color:#838da1}.dsh-ig-compare-heading>span{flex:none;padding:2px 6px;border-radius:999px;background:rgba(47,100,245,.11);color:var(--ig-blue);font-size:9.5px;font-weight:650}.dsh-ig-model-checks{display:flex;flex-direction:column;gap:6px}.dsh-ig-model-checks>button{min-height:43px;width:100%;padding:6px 7px;display:grid;grid-template-columns:18px minmax(0,1fr) auto;align-items:center;gap:7px;border:1px solid var(--ig-line);border-radius:8px;background:var(--background,#fff);text-align:left;cursor:pointer;transition:border-color .15s,background .15s,transform .15s}.dsh-ig-model-checks>button:hover{border-color:#9cb2ef;transform:translateY(-1px)}.dsh-ig-model-checks>button.is-selected{border-color:#86a4fa;background:rgba(47,100,245,.055);box-shadow:inset 3px 0 0 var(--ig-blue)}.dsh-ig-model-checkmark{width:17px;height:17px;border:1px solid #bdc6d8;border-radius:5px;display:grid;place-items:center;color:#fff;background:var(--background,#fff)}.dsh-ig-model-checks>button.is-selected .dsh-ig-model-checkmark{border-color:var(--ig-blue);background:var(--ig-blue)}.dsh-ig-model-copy{display:flex;flex-direction:column;min-width:0}.dsh-ig-model-copy strong{font-size:11px;line-height:15px}.dsh-ig-model-copy small{max-width:130px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:#80899b;font-size:9.5px;line-height:14px}.dsh-ig-model-settings{display:flex;flex-direction:column;align-items:flex-end;gap:2px;color:#596277;font-size:9.5px;white-space:nowrap}.dsh-ig-model-settings em{font-style:normal;color:#a16a0a;font-size:8.5px}.dsh-ig-compare-box>p{margin:0;color:#8a7180;font-size:9.5px;line-height:14px}
121709
- .dsh-ig-upload{height:92px;border:1px dashed #bcc6d9;border-radius:9px;background:rgba(255,255,255,.45);display:flex;flex-direction:column;align-items:center;justify-content:center;gap:5px;color:#7d879b;cursor:pointer}.dsh-ig-upload strong{font-size:11px;font-weight:570}.dsh-ig-upload small{font-size:9.5px}.dsh-ig-upload:hover,.dsh-ig-upload.is-dragging{border-color:var(--ig-blue);background:rgba(47,100,245,.05);color:var(--ig-blue)}
123922
+ .dsh-ig-canvas-empty,.dsh-ig-generating-state{display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;gap:12px;color:var(--ig-fg-cap);font-size:12px;padding:26px;cursor:default}
123923
+ .dsh-ig-generating-state strong{font-size:15px;color:var(--ig-fg)}.dsh-ig-generating-state span{font-size:11px}.dsh-ig-generation-orbit{width:66px;height:66px;border-radius:50%;display:grid;place-items:center;color:var(--ig-blue);background:rgba(47,100,245,.09);box-shadow:0 0 0 10px rgba(47,100,245,.035);animation:dshIgPulse 1.8s ease-in-out infinite}
123924
+ .dsh-ig-result-strip{flex:0 0 auto;min-height:52px;padding:8px 14px;display:flex;align-items:center;justify-content:space-between;gap:12px;border-top:1px solid var(--ig-line)}.dsh-ig-result-strip>span{font-size:12px;font-weight:650}.dsh-ig-result-strip>div,.dsh-ig-result-actions>div{display:flex;gap:7px}.dsh-ig-result-strip button,.dsh-ig-result-actions button{height:32px;display:inline-flex;align-items:center;gap:6px;padding:0 10px;border:1px solid var(--ig-line);border-radius:7px;background:var(--ig-bg);font-size:11px;cursor:pointer}.dsh-ig-result-strip button:hover,.dsh-ig-result-actions button:hover{border-color:var(--ig-blue-line);color:var(--ig-blue)}.dsh-ig-result-actions{flex:0 0 auto;min-height:54px;padding:8px 14px;display:flex;align-items:center;justify-content:space-between;gap:12px;border-top:1px solid var(--ig-line)}.dsh-ig-result-actions p{margin:0;min-width:0;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;font-size:11.5px;line-height:18px;color:var(--ig-fg-2)}.dsh-ig-result-meta{flex:0 0 auto;min-height:32px;padding:6px 14px;display:flex;gap:13px;align-items:center;flex-wrap:wrap;border-top:1px solid var(--ig-line);font-size:10px;color:var(--ig-fg-cap)}.dsh-ig-result-meta span+span:before{content:'·';margin-right:13px}
123925
+ .dsh-ig-generate-panel{height:100%;display:flex;flex-direction:column;border-left:1px solid var(--ig-line);overflow:hidden}.dsh-ig-generator-form{flex:1 1 0;height:0;padding:15px;display:flex;flex-direction:column;gap:15px;overflow-x:hidden;overflow-y:auto;min-height:0}.dsh-ig-mode-switch{display:grid;grid-template-columns:1fr 1fr;gap:8px}.dsh-ig-mode-switch button{height:39px;border:1px solid var(--ig-line);border-radius:8px;background:var(--ig-bg);display:flex;align-items:center;justify-content:center;gap:7px;font-size:12px;cursor:pointer}.dsh-ig-mode-switch button.is-active{border-color:var(--ig-blue-line);color:var(--ig-blue);background:rgba(47,100,245,.065);box-shadow:inset 0 0 0 1px rgba(47,100,245,.1)}
123926
+ .dsh-ig-field{position:relative;display:flex;flex-direction:column;gap:7px}.dsh-ig-field>label,.dsh-ig-field-label label{font-size:11.5px;font-weight:630}.dsh-ig-field>label span{font-weight:400;color:var(--ig-fg-cap)}.dsh-ig-field-label{display:flex;align-items:center;justify-content:space-between}.dsh-ig-field-label b{color:#e14d4d}.dsh-ig-field-label button{border:0;background:transparent;color:var(--ig-fg-3);font-size:10.5px;cursor:pointer}.dsh-ig-field textarea{width:100%;height:112px;padding:11px 12px 22px;resize:vertical;border:1px solid var(--ig-line);border-radius:9px;background:var(--ig-bg);font-size:12px;line-height:19px;outline:none}.dsh-ig-field textarea:focus{border-color:var(--ig-blue);box-shadow:0 0 0 3px rgba(47,100,245,.09)}.dsh-ig-field>small{position:absolute;right:9px;bottom:7px;color:var(--ig-fg-cap);font-size:9.5px}.dsh-ig-field-grid{display:grid;grid-template-columns:1fr 1fr;gap:9px}.dsh-ig-field-select{display:flex;flex-direction:column;gap:6px;min-width:0}.dsh-ig-field-select>span{font-size:11px;font-weight:620}.dsh-ig-field-select select{width:100%;height:36px;padding:0 26px 0 9px;border:1px solid var(--ig-line);border-radius:8px;background:var(--ig-bg);font-size:11px;outline:none;text-overflow:ellipsis}.dsh-ig-field-select select:focus{border-color:var(--ig-blue)}.dsh-ig-count-row{display:grid;grid-template-columns:repeat(4,1fr);gap:6px}.dsh-ig-count-pill{height:34px;border:1px solid var(--ig-line);border-radius:7px;background:var(--ig-bg);font-size:12px;font-weight:600;color:var(--ig-fg-2);cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all .15s}.dsh-ig-count-pill:hover{border-color:var(--ig-blue-line);color:var(--ig-blue)}.dsh-ig-count-pill.is-active{border-color:var(--ig-blue);background:rgba(47,100,245,.08);color:var(--ig-blue);box-shadow:inset 0 0 0 1px var(--ig-blue)}
123927
+ .dsh-ig-generation-kind{display:grid;grid-template-columns:1fr 1fr;gap:4px;padding:3px;border:1px solid var(--ig-line);border-radius:9px;background:var(--ig-soft)}.dsh-ig-generation-kind button{height:31px;border:0;border-radius:6px;background:transparent;color:var(--ig-fg-3);font-size:11.5px;display:flex;align-items:center;justify-content:center;gap:6px;cursor:pointer;transition:background .15s,color .15s,box-shadow .15s}.dsh-ig-generation-kind button.is-active{background:var(--ig-bg);color:var(--ig-blue);font-weight:650;box-shadow:0 2px 8px rgba(30,42,76,.09)}
123928
+ .dsh-ig-compare-box{padding:10px;border:1px solid rgba(47,100,245,.2);border-radius:10px;background:linear-gradient(145deg,rgba(47,100,245,.055),rgba(99,102,241,.018));display:flex;flex-direction:column;gap:9px}.dsh-ig-compare-heading{display:flex;align-items:flex-start;justify-content:space-between;gap:8px}.dsh-ig-compare-heading>div{min-width:0;display:flex;flex-direction:column;gap:2px}.dsh-ig-compare-heading strong{font-size:11.5px}.dsh-ig-compare-heading small{font-size:9.5px;color:var(--ig-fg-cap)}.dsh-ig-compare-heading>span{flex:none;padding:2px 6px;border-radius:999px;background:rgba(47,100,245,.11);color:var(--ig-blue);font-size:9.5px;font-weight:650}.dsh-ig-model-checks{display:flex;flex-direction:column;gap:6px}.dsh-ig-model-checks>button{min-height:43px;width:100%;padding:6px 7px;display:grid;grid-template-columns:18px minmax(0,1fr) auto;align-items:center;gap:7px;border:1px solid var(--ig-line);border-radius:8px;background:var(--ig-bg);text-align:left;cursor:pointer;transition:border-color .15s,background .15s,transform .15s}.dsh-ig-model-checks>button:hover{border-color:var(--ig-blue-line);transform:translateY(-1px)}.dsh-ig-model-checks>button.is-selected{border-color:var(--ig-blue-line);background:rgba(47,100,245,.055);box-shadow:inset 3px 0 0 var(--ig-blue)}.dsh-ig-model-checkmark{width:17px;height:17px;border:1px solid var(--ig-line);border-radius:5px;display:grid;place-items:center;color:#fff;background:var(--ig-bg)}.dsh-ig-model-checks>button.is-selected .dsh-ig-model-checkmark{border-color:var(--ig-blue);background:var(--ig-blue)}.dsh-ig-model-copy{display:flex;flex-direction:column;min-width:0}.dsh-ig-model-copy strong{font-size:11px;line-height:15px}.dsh-ig-model-copy small{max-width:130px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--ig-fg-cap);font-size:9.5px;line-height:14px}.dsh-ig-model-settings{display:flex;flex-direction:column;align-items:flex-end;gap:2px;color:var(--ig-fg-2);font-size:9.5px;white-space:nowrap}.dsh-ig-model-settings em{font-style:normal;color:var(--ig-warn);font-size:8.5px}.dsh-ig-compare-box>p{margin:0;color:var(--ig-fg-3);font-size:9.5px;line-height:14px}
123929
+ .dsh-ig-upload{height:92px;border:1px dashed var(--ig-line);border-radius:9px;background:color-mix(in srgb,var(--ig-bg) 55%,transparent);display:flex;flex-direction:column;align-items:center;justify-content:center;gap:5px;color:var(--ig-fg-3);cursor:pointer}.dsh-ig-upload strong{font-size:11px;font-weight:570}.dsh-ig-upload small{font-size:9.5px}.dsh-ig-upload:hover,.dsh-ig-upload.is-dragging{border-color:var(--ig-blue);background:rgba(47,100,245,.05);color:var(--ig-blue)}
121710
123930
  .dsh-ig-reference-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(64px,1fr));gap:8px}
121711
- .dsh-ig-reference-card{position:relative;aspect-ratio:1;border-radius:8px;overflow:hidden;background:#eef1f7;border:1px solid var(--ig-line)}
123931
+ .dsh-ig-reference-card{position:relative;aspect-ratio:1;border-radius:8px;overflow:hidden;background:var(--ig-bg-thumb);border:1px solid var(--ig-line)}
121712
123932
  .dsh-ig-reference-card img{width:100%;height:100%;object-fit:cover}
121713
123933
  .dsh-ig-reference-card button{position:absolute;right:4px;top:4px;width:18px;height:18px;border:0;border-radius:50%;display:grid;place-items:center;background:rgba(20,28,45,.75);color:#fff;cursor:pointer;padding:0;transition:background .15s}
121714
123934
  .dsh-ig-reference-card button:hover{background:#ef4444}
121715
- .dsh-ig-reference-add{aspect-ratio:1;border:1px dashed #bcc6d9;border-radius:8px;background:rgba(255,255,255,.45);display:flex;flex-direction:column;align-items:center;justify-content:center;gap:3px;color:#7d879b;cursor:pointer;font-size:10px;transition:border-color .15s,color .15s,background .15s}
123935
+ .dsh-ig-reference-add{aspect-ratio:1;border:1px dashed var(--ig-line);border-radius:8px;background:color-mix(in srgb,var(--ig-bg) 55%,transparent);display:flex;flex-direction:column;align-items:center;justify-content:center;gap:3px;color:var(--ig-fg-3);cursor:pointer;font-size:10px;transition:border-color .15s,color .15s,background .15s}
121716
123936
  .dsh-ig-reference-add:hover{border-color:var(--ig-blue);background:rgba(47,100,245,.05);color:var(--ig-blue)}
121717
- .dsh-ig-reference-preview{position:relative;height:108px;border-radius:9px;overflow:hidden;background:#eef1f7}.dsh-ig-reference-preview img{width:100%;height:100%;object-fit:contain}.dsh-ig-reference-preview button{position:absolute;right:7px;top:7px;width:25px;height:25px;border:0;border-radius:50%;display:grid;place-items:center;background:rgba(20,28,45,.72);color:#fff;cursor:pointer}
121718
- .dsh-ig-inline-note,.dsh-ig-form-error{padding:9px 10px;border-radius:7px;font-size:10.5px;line-height:16px}.dsh-ig-inline-note{background:#fff8e7;color:#8b671c;border:1px solid #f1dfae}.dsh-ig-form-error{background:#fff1f1;color:#b63737;border:1px solid #f2caca}.dsh-ig-form-loading{height:70px;display:grid;place-items:center;color:var(--ig-blue)}
121719
- .dsh-ig-config-error{padding:14px;display:flex;flex-direction:column;align-items:center;gap:10px;background:#fff1f1;border:1px solid #f2caca;border-radius:8px;color:#b63737;font-size:11.5px;text-align:center}.dsh-ig-config-error p{margin:0;line-height:17px}.dsh-ig-config-error button{height:30px;display:inline-flex;align-items:center;gap:6px;padding:0 12px;border:1px solid #e0a8a8;border-radius:6px;background:#fff;color:#b63737;font-size:11.5px;font-weight:600;cursor:pointer}.dsh-ig-config-error button:hover{background:#ffebeb}
123937
+ .dsh-ig-reference-preview{position:relative;height:108px;border-radius:9px;overflow:hidden;background:var(--ig-bg-thumb)}.dsh-ig-reference-preview img{width:100%;height:100%;object-fit:contain}.dsh-ig-reference-preview button{position:absolute;right:7px;top:7px;width:25px;height:25px;border:0;border-radius:50%;display:grid;place-items:center;background:rgba(20,28,45,.72);color:#fff;cursor:pointer}
123938
+ .dsh-ig-inline-note,.dsh-ig-form-error{padding:9px 10px;border-radius:7px;font-size:10.5px;line-height:16px}.dsh-ig-inline-note{background:rgba(245,158,11,.1);color:var(--ig-warn);border:1px solid rgba(245,158,11,.35)}.dsh-ig-form-error{background:rgba(239,68,68,.08);color:var(--ig-danger);border:1px solid rgba(239,68,68,.3)}.dsh-ig-form-loading{height:70px;display:grid;place-items:center;color:var(--ig-blue)}
123939
+ .dsh-ig-config-error{padding:14px;display:flex;flex-direction:column;align-items:center;gap:10px;background:rgba(239,68,68,.07);border:1px solid rgba(239,68,68,.3);border-radius:8px;color:var(--ig-danger);font-size:11.5px;text-align:center}.dsh-ig-config-error p{margin:0;line-height:17px}.dsh-ig-config-error button{height:30px;display:inline-flex;align-items:center;gap:6px;padding:0 12px;border:1px solid rgba(239,68,68,.35);border-radius:6px;background:var(--ig-bg);color:var(--ig-danger);font-size:11.5px;font-weight:600;cursor:pointer}.dsh-ig-config-error button:hover{background:rgba(239,68,68,.12)}
121720
123940
  .dsh-ig-generate-button{height:42px;flex:0 0 auto;border:0;border-radius:8px;background:linear-gradient(135deg,#386cf6,#2758e7);color:#fff!important;display:flex;align-items:center;justify-content:center;gap:8px;font-size:12px;font-weight:650;cursor:pointer;box-shadow:0 8px 18px rgba(47,100,245,.22);transition:transform .16s,box-shadow .16s}.dsh-ig-generate-button:hover:not(:disabled){transform:translateY(-1px);box-shadow:0 10px 24px rgba(47,100,245,.28)}.dsh-ig-generate-button:disabled{opacity:.55;cursor:not-allowed;box-shadow:none}
121721
123941
  .dsh-ig-generate-button.is-cancel{background:linear-gradient(135deg,#ef4444,#dc2626);box-shadow:0 8px 18px rgba(239,68,68,.25)}.dsh-ig-generate-button.is-cancel:hover{background:linear-gradient(135deg,#dc2626,#b91c1c);box-shadow:0 10px 24px rgba(239,68,68,.35)}
121722
- .dsh-ig-details{padding:16px;margin:0;overflow:auto}.dsh-ig-details>div{padding:12px 0;border-bottom:1px solid var(--ig-line)}.dsh-ig-details dt{font-size:10.5px;color:#8992a4;margin-bottom:5px}.dsh-ig-details dd{margin:0;font-size:11.5px;line-height:18px;word-break:break-word}.dsh-ig-details-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:12px;height:220px;padding:20px;color:#929bad;font-size:11px;text-align:center}
123942
+ .dsh-ig-details{padding:16px;margin:0;overflow:auto}.dsh-ig-details>div{padding:12px 0;border-bottom:1px solid var(--ig-line)}.dsh-ig-details dt{font-size:10.5px;color:var(--ig-fg-cap);margin-bottom:5px}.dsh-ig-details dd{margin:0;font-size:11.5px;line-height:18px;word-break:break-word}.dsh-ig-details-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:12px;height:220px;padding:20px;color:var(--ig-fg-cap);font-size:11px;text-align:center}
121723
123943
  .dsh-ig-workbench-modal-backdrop{position:fixed;inset:0;background:rgba(15,23,42,.45);backdrop-filter:blur(3px);z-index:999;display:grid;place-items:center;padding:16px}
121724
- .dsh-ig-workbench-modal-box{width:100%;max-width:380px;background:var(--background,#fff);border-radius:12px;padding:20px;box-shadow:0 20px 48px rgba(0,0,0,.22);display:flex;flex-direction:column;gap:14px}
123944
+ .dsh-ig-workbench-modal-box{width:100%;max-width:380px;background:var(--ig-bg);border-radius:12px;padding:20px;box-shadow:0 20px 48px rgba(0,0,0,.22);display:flex;flex-direction:column;gap:14px}
121725
123945
  .dsh-ig-workbench-modal-header{display:flex;align-items:center;gap:10px}
121726
123946
  .dsh-ig-workbench-modal-header strong{font-size:14px;font-weight:650;color:#b91c1c}
121727
- .dsh-ig-workbench-modal-desc{font-size:12px;line-height:18px;color:#555f77;margin:0}
121728
- .dsh-ig-workbench-modal-option{display:flex;align-items:center;gap:8px;font-size:11.5px;color:#4b5563;cursor:pointer}
123947
+ .dsh-ig-workbench-modal-desc{font-size:12px;line-height:18px;color:var(--ig-fg-2);margin:0}
123948
+ .dsh-ig-workbench-modal-option{display:flex;align-items:center;gap:8px;font-size:11.5px;color:var(--ig-fg-2);cursor:pointer}
121729
123949
  .dsh-ig-workbench-modal-actions{display:flex;justify-content:flex-end;gap:8px;margin-top:4px}
121730
123950
  .dsh-ig-workbench-modal-actions button{height:32px;padding:0 14px;border-radius:7px;font-size:11.5px;font-weight:600;cursor:pointer}
121731
- .dsh-ig-workbench-modal-cancel{border:1px solid var(--ig-line);background:var(--background,#fff);color:#6b7280}
121732
- .dsh-ig-workbench-modal-cancel:hover{background:#f3f4f6}
123951
+ .dsh-ig-workbench-modal-cancel{border:1px solid var(--ig-line);background:var(--ig-bg);color:var(--ig-fg-2)}
123952
+ .dsh-ig-workbench-modal-cancel:hover{background:var(--ig-soft)}
121733
123953
  .dsh-ig-workbench-modal-danger{border:0;background:#dc2626;color:#fff}
121734
123954
  .dsh-ig-workbench-modal-danger:hover{background:#b91c1c}
121735
123955
  @keyframes dshIgPulse{50%{transform:scale(1.06);box-shadow:0 0 0 16px rgba(47,100,245,.025)}}
121736
123956
  .dsh-ig-workbench-toast{position:fixed;bottom:28px;left:50%;transform:translateX(-50%);background:rgba(15,23,42,0.88);backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);color:#fff;padding:8px 18px;border-radius:10px;font-size:12.5px;font-weight:500;box-shadow:0 12px 32px rgba(0,0,0,0.3);z-index:99999;animation:dshIgFade .15s ease-out;pointer-events:none}
121737
123957
  @keyframes dshIgFade{from{opacity:0;transform:translate(-50%,6px)}to{opacity:1;transform:translate(-50%,0)}}
121738
123958
  @media(max-width:1120px){.dsh-ig-workbench-grid{grid-template-columns:190px minmax(360px,1fr) 300px}.dsh-ig-result-actions button,.dsh-ig-result-strip button{padding:0 7px;font-size:10px;gap:4px}.dsh-ig-result-actions>div,.dsh-ig-result-strip>div{gap:5px}}
121739
- @media(max-width:900px){.dsh-ig-gallery-page-body.is-workbench{height:auto;overflow:auto}.dsh-ig-workbench{height:auto;min-height:100%;overflow:visible;padding:12px}.dsh-ig-workbench-heading{height:auto;padding-bottom:12px;align-items:center}.dsh-ig-provider-quick>span{display:none}.dsh-ig-workbench-grid{height:auto;display:flex;flex:none;flex-direction:column;overflow:visible}.dsh-ig-recent-panel{height:auto;overflow:hidden;border-right:0;border-bottom:1px solid var(--ig-line)}.dsh-ig-recent-scroll{height:auto;display:flex;overflow-x:auto;overflow-y:hidden;padding:8px}.dsh-ig-recent-item{min-width:220px}.dsh-ig-canvas-column{height:auto;min-height:560px}.dsh-ig-generate-panel{height:auto;border-left:0;border-top:1px solid var(--ig-line);min-height:560px}.dsh-ig-generator-form{height:auto;overflow:visible}.dsh-ig-canvas{height:auto;min-height:360px}}
123959
+ @media(max-width:900px){.dsh-ig-gallery-page-body.is-workbench{height:auto;overflow:auto}.dsh-ig-workbench{height:auto;min-height:100%;overflow:visible;padding:12px}.dsh-ig-workbench-heading{height:auto;padding-bottom:12px;align-items:center}.dsh-ig-provider-quick>span{display:none}.dsh-ig-workbench-grid{height:auto;display:flex;flex:none;flex-direction:column;overflow:visible}.dsh-ig-recent-panel{height:auto;overflow:hidden;border-right:0;border-bottom:1px solid var(--ig-line)}.dsh-ig-recent-scroll{height:auto;display:flex;overflow-x:auto;overflow-y:hidden;padding:8px}.dsh-ig-fav-scroll{height:auto;flex:none;overflow:visible}.dsh-ig-recent-item{min-width:220px}.dsh-ig-canvas-column{height:auto;min-height:560px}.dsh-ig-generate-panel{height:auto;border-left:0;border-top:1px solid var(--ig-line);min-height:560px}.dsh-ig-generator-form{height:auto;overflow:visible}.dsh-ig-canvas{height:auto;min-height:360px}}
121740
123960
  @media(max-width:560px){.dsh-ig-workbench-heading>div{display:block}.dsh-ig-workbench-heading h1{font-size:15px}.dsh-ig-provider-quick select{min-width:128px;max-width:150px}.dsh-ig-canvas{padding:12px;min-height:300px}.dsh-ig-result-strip,.dsh-ig-result-actions{align-items:flex-start;flex-direction:column}.dsh-ig-result-strip>div,.dsh-ig-result-actions>div{width:100%}.dsh-ig-result-strip button,.dsh-ig-result-actions button{flex:1;justify-content:center}.dsh-ig-result-actions button span{display:inline}.dsh-ig-field-grid{grid-template-columns:1fr}.dsh-ig-result-meta span+span:before{display:none}}
121741
123961
  @media(prefers-reduced-motion:reduce){.dsh-ig-workbench *{scroll-behavior:auto!important;animation-duration:.01ms!important;transition-duration:.01ms!important}}
121742
- .dsh-ig-field-label>span{display:flex;align-items:center;gap:5px}.dsh-ig-field-label .dsh-ig-find-inspiration{display:inline-flex;align-items:center;gap:3px;color:var(--ig-blue);font-weight:600}.dsh-ig-field-label .dsh-ig-find-inspiration:hover{color:#1644c1}
121743
- .dsh-ig-workspace-filter-label{height:30px;display:inline-flex;align-items:center;gap:6px;padding:0 10px;border:1px solid var(--ig-line,rgba(0,0,0,.1));border-radius:7px;background:var(--background,#fff);font-size:11.5px;color:var(--foreground,#242f44);cursor:pointer;user-select:none;transition:border-color .15s,background .15s}
121744
- .dsh-ig-workspace-filter-label:hover{border-color:#9bb4f5;background:rgba(47,100,245,.04)}
123962
+ .dsh-ig-field-label>span{display:flex;align-items:center;gap:5px}.dsh-ig-field-label .dsh-ig-find-inspiration{display:inline-flex;align-items:center;gap:3px;color:var(--ig-blue);font-weight:600}.dsh-ig-field-label .dsh-ig-find-inspiration:hover{color:color-mix(in srgb,var(--ig-blue) 78%,#fff)}
123963
+ .dsh-ig-workspace-filter-label{height:30px;display:inline-flex;align-items:center;gap:6px;padding:0 10px;border:1px solid var(--ig-line,rgba(0,0,0,.1));border-radius:7px;background:var(--ig-bg);font-size:11.5px;color:var(--ig-fg);cursor:pointer;user-select:none;transition:border-color .15s,background .15s}
123964
+ .dsh-ig-workspace-filter-label:hover{border-color:var(--ig-blue-line);background:rgba(47,100,245,.04)}
121745
123965
  .dsh-ig-workspace-checkbox{cursor:pointer;accent-color:#2f64f5;width:13.5px;height:13.5px;margin:0}
121746
123966
  .dsh-ig-workspace-filter-text{font-weight:500;white-space:nowrap}
121747
- .dsh-ig-workspace-filter-badge{font-size:10px;font-weight:600;padding:1px 6px;border-radius:999px;background:color-mix(in srgb,currentColor 7%,transparent);color:#64748b;font-variant-numeric:tabular-nums}
123967
+ .dsh-ig-workspace-filter-badge{font-size:10px;font-weight:600;padding:1px 6px;border-radius:999px;background:color-mix(in srgb,currentColor 7%,transparent);color:var(--ig-fg-2);font-variant-numeric:tabular-nums}
121748
123968
  .dsh-ig-canvas-cluster{display:grid;gap:16px;align-items:center;justify-content:center;user-select:none;pointer-events:auto;flex-shrink:0}
121749
123969
  .dsh-ig-canvas-cluster[data-count="2"]{grid-template-columns:repeat(2,1fr);max-width:880px}
121750
123970
  .dsh-ig-canvas-cluster[data-count="3"],.dsh-ig-canvas-cluster[data-count="4"]{grid-template-columns:repeat(2,1fr);max-width:760px}
@@ -121757,19 +123977,19 @@ img.ProseMirror-separator {
121757
123977
  .dsh-ig-canvas-badge.is-model{max-width:calc(100% - 20px);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:600}
121758
123978
  .dsh-ig-canvas-item.is-checked .dsh-ig-canvas-badge{background:var(--ig-blue)}
121759
123979
  .dsh-ig-canvas-check{position:absolute;top:10px;right:10px;width:22px;height:22px;border:1.5px solid rgba(255,255,255,.85);border-radius:50%;display:grid;place-items:center;background:rgba(15,23,42,.48);color:#fff;box-shadow:0 2px 8px rgba(15,23,42,.18);backdrop-filter:blur(4px);z-index:2;pointer-events:none}.dsh-ig-canvas-item.is-checked .dsh-ig-canvas-check{border-color:#fff;background:var(--ig-blue)}
121760
- .dsh-ig-canvas-item-loading{width:260px;height:260px;display:flex;align-items:center;justify-content:center;background:rgba(255,255,255,.45);border:1px solid var(--ig-line);border-radius:10px;color:#929bad}
123980
+ .dsh-ig-canvas-item-loading{width:260px;height:260px;display:flex;align-items:center;justify-content:center;background:color-mix(in srgb,var(--ig-bg) 55%,transparent);border:1px solid var(--ig-line);border-radius:10px;color:var(--ig-fg-cap)}
121761
123981
  .dsh-ig-lightbox-nav{position:absolute;top:50%;transform:translateY(-50%);width:42px;height:42px;border-radius:50%;border:0;background:rgba(15,23,42,.65);color:#fff;display:grid;place-items:center;cursor:pointer;transition:background .15s,transform .15s;z-index:10;backdrop-filter:blur(4px)}
121762
123982
  .dsh-ig-lightbox-nav:hover{background:rgba(47,100,245,.85);transform:translateY(-50%) scale(1.08)}
121763
123983
  .dsh-ig-lightbox-nav.is-prev{left:18px}
121764
123984
  .dsh-ig-lightbox-nav.is-next{right:18px}
121765
123985
  .dsh-ig-save-gallery-btn{color:var(--ig-blue)!important;border-color:rgba(47,100,245,.35)!important;background:rgba(47,100,245,.06)!important}
121766
123986
  .dsh-ig-save-gallery-btn:hover{background:rgba(47,100,245,.12)!important}
121767
- .dsh-ig-save-gallery-btn.is-saved{color:#64748b!important;border-color:var(--ig-line)!important;background:transparent!important}
123987
+ .dsh-ig-save-gallery-btn.is-saved{color:var(--ig-fg-2)!important;border-color:var(--ig-line)!important;background:transparent!important}
121768
123988
 
121769
123989
  /* tldraw infinite canvas surface (P0 spike) */
121770
123990
  .dsh-ig-tl-canvas{position:relative;flex:1 1 0;height:0;min-height:0;overflow:hidden;isolation:isolate}
121771
- .dsh-ig-canvas-sync-status{position:absolute;z-index:500;bottom:48px;left:60px;right:16px;display:flex;align-items:center;justify-content:center;gap:8px;max-width:560px;margin:auto;padding:7px 12px;border:1px solid var(--dsh-ig-border);border-radius:10px;background:var(--dsh-ig-surface,#fff);color:var(--dsh-ig-text,#475569);box-shadow:0 2px 8px #0001;font-size:12px;pointer-events:none}
121772
- .dsh-ig-canvas-sync-status[data-phase='error']{color:#b42318;border-color:#fecdca}
123991
+ .dsh-ig-canvas-sync-status{position:absolute;z-index:500;bottom:48px;left:60px;right:16px;display:flex;align-items:center;justify-content:center;gap:8px;max-width:560px;margin:auto;padding:7px 12px;border:1px solid var(--ig-line);border-radius:10px;background:var(--ig-bg);color:var(--ig-fg-2);box-shadow:0 2px 8px #0001;font-size:12px;pointer-events:none}
123992
+ .dsh-ig-canvas-sync-status[data-phase='error']{color:var(--ig-danger);border-color:rgba(239,68,68,.4)}
121773
123993
  .dsh-ig-canvas-sync-status button{pointer-events:auto;flex-shrink:0;border:1px solid currentColor;border-radius:6px;padding:3px 9px;background:transparent;color:inherit;cursor:pointer}
121774
123994
 
121775
123995
  /* ---- Right-sidebar seat variant (DSH 0.1.5 'sidebar.right.pane.tab') ----
@@ -121962,6 +124182,10 @@ img.ProseMirror-separator {
121962
124182
  .dsh-ig-inspiration-head{display:flex;align-items:flex-start;justify-content:space-between;gap:18px;margin-bottom:18px}.dsh-ig-inspiration-head-actions{display:flex;align-items:center;gap:8px}.dsh-ig-inspiration-kicker{margin:0 0 4px;color:var(--inspiration-blue);font-size:11px;letter-spacing:.08em;font-weight:700;text-transform:uppercase}.dsh-ig-inspiration-head h1{margin:0;font-size:18px;letter-spacing:-.025em;line-height:1.25}.dsh-ig-inspiration-subtitle{margin:5px 0 0;color:var(--inspiration-muted);font-size:12px;line-height:1.5}.dsh-ig-inspiration-refresh,.dsh-ig-inspiration-clear-cache{height:32px;padding:0 11px;display:inline-flex;align-items:center;gap:6px;border:1px solid var(--inspiration-line);border-radius:7px;background:var(--dsw-alias-bg-layer-1,#fff);color:var(--inspiration-muted);font:inherit;font-size:12px;white-space:nowrap;cursor:pointer;transition:all .15s ease}.dsh-ig-inspiration-refresh:hover:not(:disabled){border-color:rgba(47,100,245,.42);color:var(--inspiration-blue);background:rgba(47,100,245,.04)}.dsh-ig-inspiration-clear-cache:hover:not(:disabled){border-color:rgba(239,68,68,.4);color:#ef4444;background:rgba(239,68,68,.04)}.dsh-ig-inspiration-refresh:disabled,.dsh-ig-inspiration-clear-cache:disabled{opacity:.55;cursor:wait}
121963
124183
  .dsh-ig-inspiration-toolbar{display:grid;grid-template-columns:minmax(180px,1fr) auto repeat(3,minmax(110px,140px));gap:8px;padding:10px;border:1px solid var(--inspiration-line);border-radius:10px;background:color-mix(in srgb,var(--dsw-alias-bg-layer-1,#fff) 90%,#edf2ff);margin-bottom:16px}.dsh-ig-inspiration-search{min-width:0;position:relative}.dsh-ig-inspiration-search svg{position:absolute;left:10px;top:50%;transform:translateY(-50%);color:#8995aa}.dsh-ig-inspiration-toolbar input,.dsh-ig-inspiration-toolbar select{width:100%;height:34px;box-sizing:border-box;border:1px solid transparent;border-radius:6px;background:var(--dsw-alias-bg-layer-1,#fff);color:inherit;padding:0 9px;font:inherit;font-size:12px;outline:none}.dsh-ig-inspiration-toolbar input{padding-left:31px}.dsh-ig-inspiration-toolbar input:focus,.dsh-ig-inspiration-toolbar select:focus{border-color:var(--inspiration-blue);box-shadow:0 0 0 3px rgba(47,100,245,.1)}
121964
124184
  .dsh-ig-inspiration-fav-filter{height:34px;padding:0 12px;display:inline-flex;align-items:center;gap:6px;border:1px solid var(--inspiration-line);border-radius:6px;background:var(--dsw-alias-bg-layer-1,#fff);color:var(--inspiration-muted);font:inherit;font-size:12px;cursor:pointer;white-space:nowrap;transition:all .15s ease}.dsh-ig-inspiration-fav-filter:hover{border-color:rgba(47,100,245,.4);color:var(--inspiration-blue)}.dsh-ig-inspiration-fav-filter.is-active{background:#eef3ff;border-color:var(--inspiration-blue);color:var(--inspiration-blue);font-weight:600}.fill-star{fill:currentColor}
124185
+ .dsh-ig-inspiration-catnav{position:sticky;top:0;z-index:6;display:flex;flex-wrap:wrap;gap:6px;margin:0 0 14px;padding:8px;border:1px solid var(--inspiration-line);border-radius:10px;background:color-mix(in srgb,var(--dsw-alias-bg-layer-1,#fff) 92%,#edf2ff);backdrop-filter:blur(8px)}
124186
+ .dsh-ig-inspiration-catnav-chip{height:26px;padding:0 11px;display:inline-flex;align-items:center;gap:6px;border:1px solid var(--inspiration-line);border-radius:999px;background:var(--dsw-alias-bg-layer-1,#fff);color:var(--inspiration-muted);font:inherit;font-size:11px;font-weight:600;cursor:pointer;white-space:nowrap;transition:all .15s ease}.dsh-ig-inspiration-catnav-chip em{font-style:normal;font-size:10px;opacity:.75}.dsh-ig-inspiration-catnav-chip:hover:not(:disabled){border-color:rgba(47,100,245,.46);color:var(--inspiration-blue)}.dsh-ig-inspiration-catnav-chip.is-active{border-color:var(--inspiration-blue);background:#eef3ff;color:var(--inspiration-blue)}.dsh-ig-inspiration-catnav-chip:disabled{opacity:.4;cursor:default}
124187
+ .dsh-ig-inspiration-group{margin:0}
124188
+ .dsh-ig-inspiration-group-head{position:sticky;top:48px;z-index:5;display:flex;align-items:center;gap:8px;margin:6px 0 10px;padding:7px 2px;font-size:12px;font-weight:700;letter-spacing:.02em;color:var(--inspiration-ink);background:color-mix(in srgb,var(--dsw-alias-bg-layer-1,#fff) 86%,transparent);backdrop-filter:blur(6px);scroll-margin-top:48px}.dsh-ig-inspiration-group-head em{font-style:normal;font-weight:600;font-size:10.5px;color:var(--inspiration-muted)}
121965
124189
  .dsh-ig-inspiration-layout{display:grid;grid-template-columns:minmax(0,1fr) minmax(260px,310px);gap:18px;align-items:start}.dsh-ig-inspiration-summary{height:24px;display:flex;align-items:center;justify-content:space-between;color:var(--inspiration-muted);font-size:11.5px}.dsh-ig-inspiration-summary strong{color:inherit;font-weight:650}.dsh-ig-inspiration-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(185px,1fr));gap:12px}.dsh-ig-inspiration-card{position:relative;min-width:0;padding:0;border:1px solid var(--inspiration-line);border-radius:10px;overflow:hidden;background:var(--dsw-alias-bg-layer-1,#fff);text-align:left;cursor:pointer;transition:transform .16s ease,border-color .16s ease,box-shadow .16s ease;content-visibility:auto;contain-intrinsic-size:245px}.dsh-ig-inspiration-card:hover,.dsh-ig-inspiration-card.is-selected{border-color:rgba(47,100,245,.58);box-shadow:0 8px 22px rgba(37,62,123,.11)}.dsh-ig-inspiration-card:hover{transform:translateY(-2px)}.dsh-ig-inspiration-card.is-selected:after{content:'';position:absolute;inset:0;border:2px solid var(--inspiration-blue);border-radius:9px;pointer-events:none}.dsh-ig-inspiration-visual{aspect-ratio:1/1;position:relative;background:linear-gradient(135deg,#f0f4fc,#e3eafa);overflow:hidden}.dsh-ig-inspiration-visual img{width:100%;height:100%;object-fit:cover;display:block}.dsh-ig-inspiration-image-placeholder{height:100%;display:grid;place-items:center;color:#8a99b3}.dsh-ig-inspiration-image-placeholder.is-error{font-size:11px;padding:10px;text-align:center}.dsh-ig-inspiration-card-copy{padding:9px 10px 10px}.dsh-ig-inspiration-card-copy strong{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:12px;line-height:17px}.dsh-ig-inspiration-card-copy span{display:block;margin-top:3px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--inspiration-muted);font-size:10.5px}.dsh-ig-inspiration-featured{position:absolute;top:8px;left:8px;display:inline-flex;align-items:center;gap:3px;padding:3px 6px;border-radius:999px;background:rgba(20,31,52,.72);backdrop-filter:blur(5px);color:#fff;font-size:10px;font-weight:600;z-index:1}.dsh-ig-inspiration-card-star{position:absolute;top:8px;right:8px;width:26px;height:26px;border-radius:50%;background:rgba(20,31,52,.6);backdrop-filter:blur(4px);border:none;color:#fff;display:grid;place-items:center;cursor:pointer;z-index:2;opacity:0;transition:opacity .15s,transform .15s}.dsh-ig-inspiration-card:hover .dsh-ig-inspiration-card-star,.dsh-ig-inspiration-card-star.is-favorited{opacity:1}.dsh-ig-inspiration-card-star.is-favorited{color:#f59e0b;background:rgba(20,31,52,.85)}.dsh-ig-inspiration-card-star:hover{transform:scale(1.15)}
121966
124190
  .dsh-ig-inspiration-empty{min-height:310px;display:grid;place-items:center;padding:30px;text-align:center;color:var(--inspiration-muted);border:1px dashed var(--inspiration-line);border-radius:10px;font-size:12px}
121967
124191
  .dsh-ig-inspiration-empty.is-catalog-error{display:flex;flex-direction:column;gap:12px;color:var(--inspiration-text)}
@@ -121985,7 +124209,37 @@ img.ProseMirror-separator {
121985
124209
  .dsh-ig-inspiration-end-hint{display:flex;align-items:center;justify-content:center;padding:24px 0 16px;color:var(--inspiration-muted);font-size:12px}
121986
124210
  .dsh-ig-inspiration-inspector{position:sticky;top:0;overflow:hidden;border:1px solid var(--inspiration-line);border-radius:11px;background:var(--dsw-alias-bg-layer-1,#fff);box-shadow:0 9px 28px rgba(27,45,88,.06)}.dsh-ig-inspiration-inspector-empty{min-height:310px;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:9px;padding:22px;color:var(--inspiration-muted);font-size:12px;text-align:center}.dsh-ig-inspiration-inspector-image{aspect-ratio:16/10;background:#f1f4fa;position:relative;cursor:zoom-in}.dsh-ig-inspiration-inspector-image img{width:100%;height:100%;object-fit:cover}.dsh-ig-inspiration-inspector-zoom-hint{position:absolute;bottom:8px;right:8px;display:inline-flex;align-items:center;gap:4px;padding:4px 8px;border-radius:6px;background:rgba(15,23,42,.75);backdrop-filter:blur(5px);color:#fff;font-size:10.5px;font-weight:500;opacity:0;transition:opacity .15s ease;pointer-events:none}.dsh-ig-inspiration-inspector-image:hover .dsh-ig-inspiration-inspector-zoom-hint{opacity:1}.dsh-ig-inspiration-inspector-body{padding:14px}.dsh-ig-inspiration-inspector-head{display:flex;align-items:flex-start;justify-content:space-between;gap:8px}.dsh-ig-inspiration-inspector-body h2{margin:0;font-size:14px;line-height:1.4;flex:1}.dsh-ig-inspiration-inspector-fav{width:30px;height:30px;flex-shrink:0;border:1px solid var(--inspiration-line);border-radius:7px;background:var(--dsw-alias-bg-layer-1,#fff);color:var(--inspiration-muted);display:grid;place-items:center;cursor:pointer;transition:all .15s ease}.dsh-ig-inspiration-inspector-fav:hover{border-color:rgba(245,158,11,.4);color:#f59e0b}.dsh-ig-inspiration-inspector-fav.is-favorited{color:#f59e0b;border-color:rgba(245,158,11,.5);background:rgba(245,158,11,.08)}.dsh-ig-inspiration-origin{margin:5px 0 12px;color:var(--inspiration-muted);font-size:11px}.dsh-ig-inspiration-tags{display:flex;flex-wrap:wrap;gap:5px;margin:0 0 12px}.dsh-ig-inspiration-tags span{max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;padding:3px 6px;border-radius:4px;background:#eef3ff;color:#3e5fb8;font-size:10px}.dsh-ig-inspiration-prompt-label{display:flex;align-items:center;justify-content:space-between;margin:0 0 6px;color:var(--inspiration-muted);font-size:10.5px;font-weight:650}.dsh-ig-inspiration-prompt{max-height:155px;margin:0;padding:9px;overflow:auto;white-space:pre-wrap;border:1px solid var(--inspiration-line);border-radius:7px;background:color-mix(in srgb,var(--dsw-alias-bg-layer-2,#f8fafc) 90%,#e9efff);font-size:11px;line-height:1.55;color:#526078}.dsh-ig-inspiration-actions{display:grid;grid-template-columns:1fr 1fr;gap:7px;margin-top:12px}.dsh-ig-inspiration-actions button,.dsh-ig-inspiration-source-link{height:34px;display:inline-flex;align-items:center;justify-content:center;gap:6px;border:1px solid var(--inspiration-line);border-radius:7px;background:var(--dsw-alias-bg-layer-1,#fff);color:#536075;font:inherit;font-size:11.5px;font-weight:600;cursor:pointer;text-decoration:none}.dsh-ig-inspiration-actions button:hover,.dsh-ig-inspiration-source-link:hover{border-color:rgba(47,100,245,.46);color:var(--inspiration-blue)}.dsh-ig-inspiration-actions .dsh-ig-inspiration-use{grid-column:1/-1;border-color:var(--inspiration-blue);background:linear-gradient(135deg,#386cf6,#2758e7);color:#fff;box-shadow:0 7px 15px rgba(47,100,245,.2)}.dsh-ig-inspiration-actions .dsh-ig-inspiration-use:hover{color:#fff;transform:translateY(-1px)}
121987
124211
  .dsh-ig-inspiration-lightbox{position:fixed;inset:0;z-index:10000;background:rgba(8,12,22,.88);backdrop-filter:blur(8px);display:flex;align-items:center;justify-content:center;padding:24px;box-sizing:border-box}.dsh-ig-inspiration-lightbox-content{position:relative;max-width:90vw;max-height:90vh;display:flex;flex-direction:column;align-items:center}.dsh-ig-inspiration-lightbox-img-wrap{max-width:85vw;max-height:82vh;border-radius:10px;overflow:hidden;box-shadow:0 25px 60px rgba(0,0,0,.7);background:#05070d}.dsh-ig-inspiration-lightbox-img-wrap img{width:auto;height:auto;max-width:85vw;max-height:82vh;object-fit:contain;display:block}.dsh-ig-inspiration-lightbox-close{position:absolute;top:-16px;right:-16px;width:34px;height:34px;border-radius:50%;background:#1e293b;color:#fff;border:1px solid rgba(255,255,255,.2);cursor:pointer;display:grid;place-items:center;transition:background .15s,transform .15s;z-index:10}.dsh-ig-inspiration-lightbox-close:hover{background:#334155;transform:scale(1.1)}.dsh-ig-inspiration-lightbox-caption{margin-top:12px;color:rgba(255,255,255,.9);font-size:13px;font-weight:500;text-shadow:0 2px 4px rgba(0,0,0,.6)}
124212
+ .dsh-ig-inspiration-sources{display:flex;flex-wrap:wrap;gap:7px;margin-bottom:10px}.dsh-ig-inspiration-source-chip{height:30px;padding:0 13px;display:inline-flex;align-items:center;border:1px solid var(--inspiration-line);border-radius:999px;background:var(--dsw-alias-bg-layer-1,#fff);color:var(--inspiration-muted);font:inherit;font-size:11.5px;font-weight:600;cursor:pointer;transition:all .15s ease}.dsh-ig-inspiration-source-chip:hover{border-color:rgba(47,100,245,.46);color:var(--inspiration-blue)}.dsh-ig-inspiration-source-chip.is-active{border-color:var(--inspiration-blue);background:linear-gradient(135deg,#386cf6,#2758e7);color:#fff;box-shadow:0 5px 12px rgba(47,100,245,.22)}
124213
+ .dsh-ig-inspiration-actions .dsh-ig-inspiration-use.is-secondary{grid-column:1/-1;border-color:rgba(47,100,245,.5);background:var(--dsw-alias-bg-layer-1,#fff);color:var(--inspiration-blue);box-shadow:none}.dsh-ig-inspiration-actions .dsh-ig-inspiration-use.is-secondary:hover{color:#2758e7;transform:translateY(-1px)}.dsh-ig-inspiration-actions .dsh-ig-inspiration-use.is-secondary:disabled{opacity:.6;cursor:wait;transform:none}
121988
124214
  @media(max-width:980px){.dsh-ig-inspiration-layout{grid-template-columns:1fr}.dsh-ig-inspiration-inspector{position:relative;top:auto}.dsh-ig-inspiration-inspector-empty{min-height:150px}.dsh-ig-inspiration-inspector-image{max-height:290px}.dsh-ig-inspiration-inspector-image img{object-fit:contain;background:#f1f4fa}}@media(max-width:700px){.dsh-ig-inspiration{padding:14px}.dsh-ig-inspiration-head{align-items:center}.dsh-ig-inspiration-toolbar{grid-template-columns:1fr 1fr}.dsh-ig-inspiration-search{grid-column:1/-1}.dsh-ig-inspiration-fav-filter{grid-column:1/-1}.dsh-ig-inspiration-grid{grid-template-columns:repeat(auto-fill,minmax(145px,1fr));gap:9px}}@media(max-width:420px){.dsh-ig-inspiration-toolbar{grid-template-columns:1fr}.dsh-ig-inspiration-search{grid-column:auto}}
124215
+
124216
+ .dsh-ig-inspiration-source-chip svg{flex-shrink:0}
124217
+ .dsh-ig-baoyu-panel{border:1px solid var(--inspiration-line);border-radius:11px;background:var(--dsw-alias-bg-layer-1,#fff);padding:18px;display:flex;flex-direction:column;gap:14px}
124218
+ .dsh-ig-baoyu-intro{display:flex;align-items:baseline;gap:10px;flex-wrap:wrap}
124219
+ .dsh-ig-baoyu-combos{font-size:12px;color:var(--inspiration-blue)}
124220
+ .dsh-ig-baoyu-hint{font-size:11px;color:var(--inspiration-muted)}
124221
+ .dsh-ig-baoyu-dim{display:flex;flex-direction:column;gap:7px}
124222
+ .dsh-ig-baoyu-dim-head{font-size:11px;font-weight:650;color:var(--inspiration-muted);letter-spacing:.02em}
124223
+ .dsh-ig-baoyu-chips{display:flex;flex-wrap:wrap;gap:6px}
124224
+ .dsh-ig-baoyu-chip{height:28px;padding:0 10px;display:inline-flex;align-items:center;gap:6px;border:1px solid var(--inspiration-line);border-radius:999px;background:var(--dsw-alias-bg-layer-1,#fff);color:#536075;font:inherit;font-size:11px;font-weight:600;cursor:pointer;transition:all .15s ease}
124225
+ .dsh-ig-baoyu-chip:hover{border-color:rgba(47,100,245,.46);color:var(--inspiration-blue)}
124226
+ .dsh-ig-baoyu-chip.is-active{border-color:var(--inspiration-blue);background:rgba(47,100,245,.09);color:var(--inspiration-blue)}
124227
+ .dsh-ig-baoyu-swatch{display:inline-flex;gap:2px}
124228
+ .dsh-ig-baoyu-swatch i{width:9px;height:9px;border-radius:50%;border:1px solid rgba(15,23,42,.15);display:block}
124229
+ .dsh-ig-baoyu-title-input{height:34px;flex:1;padding:0 11px;border:1px solid var(--inspiration-line);border-radius:7px;background:var(--dsw-alias-bg-layer-1,#fff);color:inherit;font:inherit;font-size:12px}
124230
+ .dsh-ig-baoyu-title-input:focus{outline:none;border-color:rgba(47,100,245,.55)}
124231
+ .dsh-ig-baoyu-title-input:disabled{opacity:.45}
124232
+ .dsh-ig-baoyu-required{color:#dc2626;font-size:11px}
124233
+ .dsh-ig-baoyu-actions{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
124234
+ .dsh-ig-baoyu-btn{height:34px;padding:0 14px;display:inline-flex;align-items:center;gap:6px;border:1px solid var(--inspiration-line);border-radius:7px;background:var(--dsw-alias-bg-layer-1,#fff);color:#536075;font:inherit;font-size:11.5px;font-weight:600;cursor:pointer;transition:all .15s ease}
124235
+ .dsh-ig-baoyu-btn:hover{border-color:rgba(47,100,245,.46);color:var(--inspiration-blue)}
124236
+ .dsh-ig-baoyu-btn:disabled{opacity:.5;cursor:not-allowed}
124237
+ .dsh-ig-baoyu-btn.is-primary{border-color:var(--inspiration-blue);background:linear-gradient(135deg,#386cf6,#2758e7);color:#fff;box-shadow:0 7px 15px rgba(47,100,245,.2)}
124238
+ .dsh-ig-baoyu-btn.is-primary:hover{color:#fff;transform:translateY(-1px)}
124239
+ .dsh-ig-baoyu-btn.is-primary:disabled{opacity:.5;cursor:not-allowed;transform:none}
124240
+ .dsh-ig-baoyu-preview-block{display:flex;flex-direction:column;gap:6px}
124241
+ .dsh-ig-baoyu-preview-head{font-size:11px;font-weight:650;color:var(--inspiration-muted)}
124242
+ .dsh-ig-baoyu-preview{max-height:260px;margin:0;padding:12px;overflow:auto;white-space:pre-wrap;border:1px solid var(--inspiration-line);border-radius:7px;background:color-mix(in srgb,var(--dsw-alias-bg-layer-2,#f8fafc) 90%,#e9efff);font-size:11px;line-height:1.6;color:#526078}
121989
124243
  `;
121990
124244
  //#endregion
121991
124245
  //#region lib/types/client/conversation-image-revisions.js
@@ -122500,7 +124754,7 @@ img.ProseMirror-separator {
122500
124754
  `;
122501
124755
  //#endregion
122502
124756
  //#region lib/types/client/index.js
122503
- console.info(`[dsh-image-gen] canvas bundle 2026-09-26T07:20:53.102Z`);
124757
+ console.info(`[dsh-image-gen] canvas bundle 2026-09-28T16:41:08.496Z`);
122504
124758
  /** Right-sidebar tab-type registration id (DSH 0.1.5 `sidebarRightTabs`). */
122505
124759
  const SIDEBAR_STUDIO_TAB_ID = "dsh-image-gen/studio";
122506
124760
  /** The tab kind: a page type opened by kind, recognizing no resource address. */
@@ -122572,7 +124826,8 @@ img.ProseMirror-separator {
122572
124826
  editFormat: "图生图请求形态",
122573
124827
  editFormatMultipart: "标准 multipart(OpenAI 官方)",
122574
124828
  editFormatJsonImageUrlArray: "JSON images 数组(商汤等中转)",
122575
- editFormatHint: "文生图不受影响。仅当中转站的图生图接口使用自有 JSON 契约(如商汤 SenseNova)时才需要切换。",
124829
+ editFormatFormReferenceImages: "multipart + reference_images JSON 数组(base64)",
124830
+ editFormatHint: "文生图不受影响。仅当中转站的图生图接口使用自有契约(如商汤 SenseNova 的 JSON images 数组,或要求 reference_images base64 数组的中转)时才需要切换。",
122576
124831
  editExtra: "附加 JSON 字段",
122577
124832
  editExtraPlaceholder: "{\"watermark\": false, \"prompt_extend\": true}",
122578
124833
  editExtraHint: "仅 JSON 形态生效;会合并到请求体末尾,可覆盖默认字段。留空表示不附加。",
@@ -122716,7 +124971,8 @@ img.ProseMirror-separator {
122716
124971
  editFormat: "Edit request format",
122717
124972
  editFormatMultipart: "Standard multipart (official OpenAI)",
122718
124973
  editFormatJsonImageUrlArray: "JSON images array (SenseNova etc.)",
122719
- editFormatHint: "Text-to-image is unaffected. Only switch when the relay runs image edits on its own JSON contract (e.g. SenseNova); size is pinned to \"auto\" in this format (the only value SenseNova's edits endpoint accepts) and can be overridden via extra fields.",
124974
+ editFormatFormReferenceImages: "Multipart with a reference_images JSON array (base64)",
124975
+ editFormatHint: "Text-to-image is unaffected. Only switch when the relay runs image edits on its own contract (e.g. SenseNova's JSON images array, or a relay that takes reference_images as a base64 JSON array); size is pinned to \"auto\" in the JSON images format (the only value SenseNova's edits endpoint accepts) and can be overridden via extra fields.",
122720
124976
  editExtra: "Extra JSON fields",
122721
124977
  editExtraPlaceholder: "{\"watermark\": false, \"prompt_extend\": true}",
122722
124978
  editExtraHint: "Only used with the JSON format; merged into the request body last and may override defaults. Leave empty for none.",
@@ -123409,6 +125665,10 @@ img.ProseMirror-separator {
123409
125665
  return "";
123410
125666
  }
123411
125667
  }
125668
+ /** Persisted or selected edit-format value → row state; unknown values fall back to standard multipart. */
125669
+ function parseEditFormat(value) {
125670
+ return value === "jsonImageUrlArray" || value === "formReferenceImages" ? value : "multipart";
125671
+ }
123412
125672
  /** Build one row per provider from persisted settings, including ComfyUI extras. */
123413
125673
  function rowsFromSettings(value) {
123414
125674
  const rows = {};
@@ -123422,7 +125682,7 @@ img.ProseMirror-separator {
123422
125682
  timeoutSeconds: Math.max(1, Math.round((value?.comfyuiTimeoutMs ?? 3e5) / 1e3))
123423
125683
  } : {},
123424
125684
  ...provider === "openai-compat" ? {
123425
- editFormat: value?.openaiCompatEditFormat === "jsonImageUrlArray" ? "jsonImageUrlArray" : "multipart",
125685
+ editFormat: parseEditFormat(value?.openaiCompatEditFormat),
123426
125686
  editExtraText: editExtraTextOf(value?.openaiCompatEditExtra)
123427
125687
  } : {},
123428
125688
  ...provider === "seedream" ? {
@@ -124239,16 +126499,23 @@ img.ProseMirror-separator {
124239
126499
  className: "dsh-ig-input",
124240
126500
  value: row.editFormat,
124241
126501
  onChange: (event) => {
124242
- updateRow(provider, { editFormat: event.target.value === "jsonImageUrlArray" ? "jsonImageUrlArray" : "multipart" });
126502
+ updateRow(provider, { editFormat: parseEditFormat(event.target.value) });
124243
126503
  },
124244
126504
  disabled: !snapshot.writable,
124245
- children: [(0, react_jsx_runtime.jsx)("option", {
124246
- value: "multipart",
124247
- children: t("editFormatMultipart")
124248
- }), (0, react_jsx_runtime.jsx)("option", {
124249
- value: "jsonImageUrlArray",
124250
- children: t("editFormatJsonImageUrlArray")
124251
- })]
126505
+ children: [
126506
+ (0, react_jsx_runtime.jsx)("option", {
126507
+ value: "multipart",
126508
+ children: t("editFormatMultipart")
126509
+ }),
126510
+ (0, react_jsx_runtime.jsx)("option", {
126511
+ value: "jsonImageUrlArray",
126512
+ children: t("editFormatJsonImageUrlArray")
126513
+ }),
126514
+ (0, react_jsx_runtime.jsx)("option", {
126515
+ value: "formReferenceImages",
126516
+ children: t("editFormatFormReferenceImages")
126517
+ })
126518
+ ]
124252
126519
  }),
124253
126520
  (0, react_jsx_runtime.jsx)("span", {
124254
126521
  className: "dsh-ig-hint",
@@ -125404,7 +127671,6 @@ img.ProseMirror-separator {
125404
127671
  (0, react_jsx_runtime.jsxs)("label", { children: [(0, react_jsx_runtime.jsx)("span", { children: t("prompt") }), (0, react_jsx_runtime.jsx)("textarea", {
125405
127672
  ref: regenerateTextareaRef,
125406
127673
  value: regeneratePrompt,
125407
- maxLength: 2e3,
125408
127674
  disabled: isRegenerating,
125409
127675
  onChange: (event) => setRegeneratePrompt(event.target.value),
125410
127676
  onKeyDown: (event) => {