dsh-image-gen 0.6.11 → 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.
Files changed (37) hide show
  1. package/lib/client.js +2676 -359
  2. package/lib/client.js.map +1 -1
  3. package/lib/index.js +7867 -1176
  4. package/lib/types/baoyu-compose.d.ts +61 -0
  5. package/lib/types/client/gallery-store.d.ts +40 -1
  6. package/lib/types/client/gallery-view.d.ts +22 -0
  7. package/lib/types/client/index.d.ts +10 -3
  8. package/lib/types/client/inspiration-image-cache.d.ts +2 -2
  9. package/lib/types/client/inspiration-style.d.ts +1 -1
  10. package/lib/types/client/inspiration-view.d.ts +1 -0
  11. package/lib/types/client/settings-write.d.ts +4 -0
  12. package/lib/types/client/studio-style.d.ts +1 -1
  13. package/lib/types/client/studio-view.d.ts +3 -0
  14. package/lib/types/client/tl/studio-tl-canvas.d.ts +7 -0
  15. package/lib/types/client/tl/tl-brand-theme.d.ts +7 -0
  16. package/lib/types/config.d.ts +26 -7
  17. package/lib/types/import-route.d.ts +16 -0
  18. package/lib/types/index.d.ts +1 -0
  19. package/lib/types/inspiration.d.ts +40 -1
  20. package/lib/types/openai-compatible.d.ts +1 -1
  21. package/lib/types/shared.d.ts +8 -0
  22. package/lib/types/studio-route.d.ts +2 -0
  23. package/lib/types/studio.d.ts +24 -0
  24. package/package.json +20 -20
  25. package/lib/types/client/canvas/canvas-bridge.d.ts +0 -10
  26. package/lib/types/client/canvas/canvas-model.d.ts +0 -129
  27. package/lib/types/client/canvas/canvas-nodes.d.ts +0 -23
  28. package/lib/types/client/canvas/canvas-store.d.ts +0 -29
  29. package/lib/types/client/canvas/canvas-style.d.ts +0 -1
  30. package/lib/types/client/canvas/canvas-view.d.ts +0 -8
  31. package/lib/types/client/canvas/rf-base-css.d.ts +0 -2
  32. package/lib/types/client/studio-chat-panel.d.ts +0 -23
  33. package/lib/types/client/tl/diagram-landing.d.ts +0 -75
  34. package/lib/types/client/tl/diagram-layout.d.ts +0 -76
  35. package/lib/types/client/tl/diagram-measure.d.ts +0 -40
  36. package/lib/types/diagram.d.ts +0 -42
  37. package/lib/types/studio-chat.d.ts +0 -48
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
@@ -116227,6 +116517,7 @@ img.ProseMirror-separator {
116227
116517
  const GRID_GAP = 40;
116228
116518
  const GRID_COLS = 3;
116229
116519
  const mountedCanvases = /* @__PURE__ */ new Set();
116520
+ const mountedEditors = /* @__PURE__ */ new Set();
116230
116521
  /** Reset canvas-owned data only. Gallery records and host attachments are untouched. */
116231
116522
  function clearStudioTlCanvases() {
116232
116523
  if (mountedCanvases.size === 0) return false;
@@ -116236,6 +116527,17 @@ img.ProseMirror-separator {
116236
116527
  return true;
116237
116528
  }
116238
116529
  /**
116530
+ * Cancel in-editor selection on every mounted canvas, keeping shapes and sync
116531
+ * untouched. Selection is interaction state tied to the surface: leaving the
116532
+ * studio page cancels it so a stale selection can neither resurface on return
116533
+ * nor linger in the host mirror.
116534
+ */
116535
+ function deselectStudioTlCanvases() {
116536
+ if (mountedEditors.size === 0) return false;
116537
+ for (const editor of mountedEditors) editor.run(() => editor.selectNone());
116538
+ return true;
116539
+ }
116540
+ /**
116239
116541
  * Dock the stock toolbar vertically along the left edge (Figma-style) instead
116240
116542
  * of tldraw's signature bottom-center pill. It is still the same
116241
116543
  * DefaultToolbar with the same tools and overflow handling — only the
@@ -116410,7 +116712,10 @@ img.ProseMirror-separator {
116410
116712
  const retrySync = (0, react.useRef)(() => {});
116411
116713
  const onMount = (0, react.useCallback)((editor) => {
116412
116714
  let active = true;
116715
+ editor.run(() => editor.selectNone());
116716
+ mountedEditors.add(editor);
116413
116717
  applyBrandTheme(editor);
116718
+ const stopThemeSync = syncTldrawThemeWithHost(editor);
116414
116719
  editor.updateInstanceState({ isGridMode: true });
116415
116720
  console.info(`[dsh-image-gen] tldraw mounted (instance ${editor.id})`);
116416
116721
  const stopLanding = registerTlLandingConsumer((items) => {
@@ -116443,8 +116748,10 @@ img.ProseMirror-separator {
116443
116748
  mountedCanvases.add(clear);
116444
116749
  return () => {
116445
116750
  active = false;
116751
+ mountedEditors.delete(editor);
116446
116752
  mountedCanvases.delete(clear);
116447
116753
  stopLanding();
116754
+ stopThemeSync();
116448
116755
  stopSync();
116449
116756
  retrySync.current = () => {};
116450
116757
  };
@@ -116489,6 +116796,31 @@ img.ProseMirror-separator {
116489
116796
  unconfigured: "未配置",
116490
116797
  recent: "最近生成",
116491
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: "收藏失败,请重试",
116492
116824
  generate: "文生图",
116493
116825
  edit: "图生图",
116494
116826
  reference: "参考图",
@@ -116545,6 +116877,8 @@ img.ProseMirror-separator {
116545
116877
  infiniteCanvas: "无限画布",
116546
116878
  loading: "正在读取图片…",
116547
116879
  generationFailed: "生成失败",
116880
+ gatewayTimeout: "请求被反向代理中断(超时):请重试;反复出现需调大代理超时或换直连网络",
116881
+ requestFailed: "请求失败(HTTP {status})",
116548
116882
  selectHistory: "从左侧选择一张图片,或在右侧开始新的生成。",
116549
116883
  created: "生成时间",
116550
116884
  elapsed: "耗时",
@@ -116552,6 +116886,7 @@ img.ProseMirror-separator {
116552
116886
  output: "输出参数",
116553
116887
  closeReference: "移除参考图",
116554
116888
  uploadInvalid: "请选择有效的图片文件(最大 10MB)",
116889
+ uploadUnreadable: "部分图片读取失败,请重新选择后再试",
116555
116890
  imageLoadFailed: "图片读取失败",
116556
116891
  fullscreen: "大图全屏",
116557
116892
  close: "关闭",
@@ -116598,6 +116933,31 @@ img.ProseMirror-separator {
116598
116933
  unconfigured: "Not configured",
116599
116934
  recent: "Recent generations",
116600
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",
116601
116961
  generate: "Text to image",
116602
116962
  edit: "Image to image",
116603
116963
  reference: "Reference image",
@@ -116654,6 +117014,8 @@ img.ProseMirror-separator {
116654
117014
  infiniteCanvas: "Infinite canvas",
116655
117015
  loading: "Loading image…",
116656
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})",
116657
117019
  selectHistory: "Select an image on the left, or start a new generation on the right.",
116658
117020
  created: "Created",
116659
117021
  elapsed: "Elapsed",
@@ -116661,6 +117023,7 @@ img.ProseMirror-separator {
116661
117023
  output: "Output",
116662
117024
  closeReference: "Remove reference",
116663
117025
  uploadInvalid: "Choose a valid image file up to 10MB.",
117026
+ uploadUnreadable: "Some images could not be read; pick them again.",
116664
117027
  imageLoadFailed: "Could not load image",
116665
117028
  fullscreen: "Fullscreen",
116666
117029
  close: "Close",
@@ -116704,17 +117067,28 @@ img.ProseMirror-separator {
116704
117067
  };
116705
117068
  /** localStorage flag remembering that the one-time infinite-canvas hint was dismissed. */
116706
117069
  const INFINITE_CANVAS_HINT_KEY = "dsh-ig-infinite-canvas-hint-dismissed";
116707
- const StudioView = ({ locale, credentialEvents, workspace, initialPrompt, initialCanvasSurface, showInfiniteCanvasHint, onInitialPromptApplied, onOpenInspiration }) => {
117070
+ const StudioView = ({ locale, credentialEvents, workspace, initialPrompt, initialReference, initialCanvasSurface, showInfiniteCanvasHint, onInitialPromptApplied, onInitialReferenceApplied, onOpenInspiration }) => {
116708
117071
  const [lang, setLang] = (0, react.useState)(() => locale?.getSnapshot?.().active?.startsWith("en") ? "en" : "zh");
116709
117072
  const [config, setConfig] = (0, react.useState)(null);
116710
117073
  const [configLoading, setConfigLoading] = (0, react.useState)(true);
116711
117074
  const [configError, setConfigError] = (0, react.useState)(null);
116712
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);
116713
117087
  const [sidebarCollapsed, setSidebarCollapsed] = (0, react.useState)(false);
116714
117088
  /** Manual-only collapse for the right generate form; the form stays put in
116715
117089
  * narrow seats because it is the primary generation surface. Starts folded
116716
117090
  * when the studio opens straight into the infinite canvas. */
116717
- const [generateCollapsed, setGenerateCollapsed] = (0, react.useState)(initialCanvasSurface === "infinite");
117091
+ const [generateCollapsed, setGenerateCollapsed] = (0, react.useState)(initialCanvasSurface === "infinite" && initialPrompt === void 0);
116718
117092
  /** Workbench root, observed so a narrow column (right sidebar) can fold the recent rail. */
116719
117093
  const workbenchRootRef = (0, react.useRef)(null);
116720
117094
  /** Set once the user toggles the rail by hand; auto-folding then stands down for this mount. */
@@ -116818,6 +117192,15 @@ img.ProseMirror-separator {
116818
117192
  for (const [name, value] of Object.entries(values ?? {})) text = text.replace(`{${name}}`, value);
116819
117193
  return text;
116820
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
+ };
116821
117204
  (0, react.useEffect)(() => locale?.subscribe?.(() => setLang(locale.getSnapshot().active?.startsWith("en") ? "en" : "zh")), [locale]);
116822
117205
  const formStateRef = (0, react.useRef)({
116823
117206
  provider,
@@ -116839,7 +117222,7 @@ img.ProseMirror-separator {
116839
117222
  signal: controller.signal,
116840
117223
  credentials: "same-origin"
116841
117224
  });
116842
- const payload = await response.json();
117225
+ const payload = await readStudioBody(response);
116843
117226
  if (!response.ok || !("providers" in payload)) throw new Error("error" in payload && payload.error ? payload.error : "Studio unavailable");
116844
117227
  setConfig(payload);
116845
117228
  if (silent) {
@@ -116885,6 +117268,20 @@ img.ProseMirror-separator {
116885
117268
  unsubscribe();
116886
117269
  };
116887
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
+ }, []);
116888
117285
  (0, react.useEffect)(() => {
116889
117286
  return () => {
116890
117287
  requestRef.current?.abort();
@@ -117009,7 +117406,65 @@ img.ProseMirror-separator {
117009
117406
  };
117010
117407
  };
117011
117408
  const handleImageClick = () => {};
117012
- 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) => {
117013
117468
  if (!fileList || fileList.length === 0) return;
117014
117469
  const files = Array.from(fileList);
117015
117470
  const validFiles = [];
@@ -117035,13 +117490,31 @@ img.ProseMirror-separator {
117035
117490
  const toAdd = validFiles.slice(0, available);
117036
117491
  if (validFiles.length > available) setError(t("maxReferencesExceeded", { max: String(maxReferences) }));
117037
117492
  else setError(null);
117038
- 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) => ({
117039
117504
  id: `upload-${Date.now()}-${idx}-${Math.random().toString(36).slice(2, 7)}`,
117040
117505
  file,
117041
117506
  previewUrl: URL.createObjectURL(file)
117042
117507
  }));
117043
117508
  setReferences((prev) => [...prev, ...newItems]);
117044
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]);
117045
117518
  const removeReference = (id) => {
117046
117519
  const target = referencesRef.current.find((item) => item.id === id);
117047
117520
  if (target && target.previewUrl.startsWith("blob:")) URL.revokeObjectURL(target.previewUrl);
@@ -117173,7 +117646,7 @@ img.ProseMirror-separator {
117173
117646
  ...referencesPayload === void 0 ? {} : { references: referencesPayload }
117174
117647
  })
117175
117648
  });
117176
- const payload = await response.json();
117649
+ const payload = await readStudioBody(response);
117177
117650
  if (!response.ok || !("attachment" in payload)) throw new Error("error" in payload && payload.error ? payload.error : `${target.profile.label}: ${t("generationFailed")}`);
117178
117651
  return {
117179
117652
  payload,
@@ -117236,7 +117709,7 @@ img.ProseMirror-separator {
117236
117709
  ...referencesPayload === void 0 ? {} : { references: referencesPayload }
117237
117710
  })
117238
117711
  });
117239
- const payload = await response.json();
117712
+ const payload = await readStudioBody(response);
117240
117713
  if (!response.ok || !("attachment" in payload)) throw new Error("error" in payload && payload.error ? payload.error : t("generationFailed"));
117241
117714
  const generatedList = Array.isArray(payload.items) && payload.items.length > 0 ? payload.items : [{
117242
117715
  attachment: payload.attachment,
@@ -117308,8 +117781,8 @@ img.ProseMirror-separator {
117308
117781
  selected,
117309
117782
  selectedBatchItems
117310
117783
  ]);
117311
- 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");
117312
- 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;
117313
117786
  const saveGalleryEntry = async (item) => {
117314
117787
  let savedTo = item.savedTo;
117315
117788
  const targetRoot = workspace?.path || config?.workspaceRoot;
@@ -117489,44 +117962,272 @@ img.ProseMirror-separator {
117489
117962
  children: [
117490
117963
  !sidebarCollapsed && (0, react_jsx_runtime.jsxs)("aside", {
117491
117964
  className: "dsh-ig-recent-panel",
117492
- children: [(0, react_jsx_runtime.jsxs)("div", {
117493
- className: "dsh-ig-panel-title",
117494
- children: [(0, react_jsx_runtime.jsxs)("div", {
117495
- style: {
117496
- display: "flex",
117497
- alignItems: "center",
117498
- gap: "8px"
117499
- },
117500
- children: [(0, react_jsx_runtime.jsx)("span", { children: t("recent") }), (0, react_jsx_runtime.jsx)("span", {
117501
- className: "dsh-ig-count-badge",
117502
- 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 })
117503
117987
  })]
117504
- }), (0, react_jsx_runtime.jsx)("button", {
117505
- type: "button",
117506
- className: "dsh-ig-collapse-btn",
117507
- onClick: () => {
117508
- railToggledByUserRef.current = true;
117509
- setSidebarCollapsed(true);
117510
- },
117511
- title: t("collapseSidebar"),
117512
- children: (0, react_jsx_runtime.jsx)(PanelLeftClose, { size: 15 })
117513
- })]
117514
- }), (0, react_jsx_runtime.jsxs)("div", {
117515
- className: "dsh-ig-recent-scroll",
117516
- children: [items.length === 0 ? (0, react_jsx_runtime.jsxs)("div", {
117517
- className: "dsh-ig-recent-empty",
117518
- children: [(0, react_jsx_runtime.jsx)(ImagePlus, { size: 22 }), (0, react_jsx_runtime.jsx)("span", { children: t("empty") })]
117519
- }) : displayItems.map((item) => (0, react_jsx_runtime.jsx)(RecentItem, {
117520
- item,
117521
- active: selected?.id === item.id,
117522
- onClick: () => selectItem(item)
117523
- }, item.id)), items.length > visibleLimit && (0, react_jsx_runtime.jsx)("button", {
117524
- type: "button",
117525
- className: "dsh-ig-load-more",
117526
- onClick: () => setVisibleLimit((l) => l + 30),
117527
- children: t("loadMore", { n: String(items.length - visibleLimit) })
117528
- })]
117529
- })]
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
+ })
117530
118231
  }),
117531
118232
  (0, react_jsx_runtime.jsxs)("main", {
117532
118233
  className: "dsh-ig-canvas-column",
@@ -117923,21 +118624,30 @@ img.ProseMirror-separator {
117923
118624
  " ",
117924
118625
  (0, react_jsx_runtime.jsx)("b", { children: "*" })
117925
118626
  ]
117926
- }), (0, react_jsx_runtime.jsxs)("span", { children: [(0, react_jsx_runtime.jsxs)("button", {
117927
- type: "button",
117928
- className: "dsh-ig-find-inspiration",
117929
- onClick: onOpenInspiration,
117930
- children: [(0, react_jsx_runtime.jsx)(Sparkles, { size: 11 }), t("findInspiration")]
117931
- }), (0, react_jsx_runtime.jsx)("button", {
117932
- type: "button",
117933
- onClick: () => setPrompt(""),
117934
- children: t("clear")
117935
- })] })]
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
+ ] })]
117936
118647
  }),
117937
118648
  (0, react_jsx_runtime.jsx)("textarea", {
117938
118649
  id: "dsh-ig-prompt",
117939
118650
  value: prompt,
117940
- maxLength: 2e3,
117941
118651
  onChange: (event) => setPrompt(event.target.value),
117942
118652
  onKeyDown: (event) => {
117943
118653
  if (event.nativeEvent.isComposing) return;
@@ -117948,7 +118658,7 @@ img.ProseMirror-separator {
117948
118658
  },
117949
118659
  placeholder: t("promptPlaceholder")
117950
118660
  }),
117951
- (0, react_jsx_runtime.jsxs)("small", { children: [prompt.length, "/2000"] })
118661
+ (0, react_jsx_runtime.jsx)("small", { children: prompt.length })
117952
118662
  ]
117953
118663
  }),
117954
118664
  config === null ? configLoading ? (0, react_jsx_runtime.jsx)("div", {
@@ -118541,6 +119251,26 @@ img.ProseMirror-separator {
118541
119251
  })
118542
119252
  });
118543
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
+ };
118544
119274
  function useInView() {
118545
119275
  const [inView, setInView] = (0, react.useState)(false);
118546
119276
  const observerRef = (0, react.useRef)(null);
@@ -118638,8 +119368,17 @@ img.ProseMirror-separator {
118638
119368
  const MAX_BYTES = 188743680;
118639
119369
  const pending = /* @__PURE__ */ new Map();
118640
119370
  let cacheEpoch = 0;
118641
- function fetchInspirationImage(sourceId, caseId) {
118642
- 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);
118643
119382
  const existing = pending.get(key);
118644
119383
  if (existing !== void 0) return existing;
118645
119384
  const requestEpoch = cacheEpoch;
@@ -118662,8 +119401,8 @@ img.ProseMirror-separator {
118662
119401
  pending.set(key, task);
118663
119402
  return task;
118664
119403
  }
118665
- async function evictInspirationImage(sourceId, caseId) {
118666
- const key = `${sourceId}:${caseId}`;
119404
+ async function evictInspirationImage(sourceId, caseId, rev) {
119405
+ const key = cacheKey(sourceId, caseId, rev);
118667
119406
  pending.delete(key);
118668
119407
  const db = await openDatabase$1();
118669
119408
  if (db === void 0) return;
@@ -118850,6 +119589,988 @@ img.ProseMirror-separator {
118850
119589
  return Array.isArray(sources) && sources.length > 0 && sources.every((source) => typeof source === "object" && source !== null && Array.isArray(source.cases));
118851
119590
  }
118852
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
118853
120574
  //#region lib/types/client/inspiration-view.js
118854
120575
  const FAVORITES_STORAGE_KEY = "dsh-ig-inspiration-favorites";
118855
120576
  function loadInspirationFavorites() {
@@ -118941,6 +120662,7 @@ img.ProseMirror-separator {
118941
120662
  allCategories: "全部分类",
118942
120663
  allStyles: "全部风格",
118943
120664
  allScenes: "全部场景",
120665
+ categoryNav: "分类导航",
118944
120666
  search: "搜索案例、Prompt、风格…",
118945
120667
  results: "找到 {count} 个案例",
118946
120668
  noResults: "没有匹配的素材,换个关键词或筛选条件试试。",
@@ -118950,6 +120672,9 @@ img.ProseMirror-separator {
118950
120672
  copied: "已复制",
118951
120673
  use: "使用这个 Prompt",
118952
120674
  source: "查看原来源",
120675
+ useReference: "提示词 + 参考图",
120676
+ referenceLoading: "获取参考图中…",
120677
+ referenceFailed: "参考图获取失败,请重试",
118953
120678
  loading: "正在读取素材库…",
118954
120679
  loadFailed: "素材库读取失败,请稍后重试。",
118955
120680
  copyFailed: "复制失败",
@@ -118964,7 +120689,21 @@ img.ProseMirror-separator {
118964
120689
  favorite: "收藏",
118965
120690
  favorited: "已收藏",
118966
120691
  zoomHint: "点击放大查看",
118967
- 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: "拼接结果预览"
118968
120707
  },
118969
120708
  en: {
118970
120709
  kicker: "Prompt inspiration",
@@ -118984,6 +120723,7 @@ img.ProseMirror-separator {
118984
120723
  allCategories: "All categories",
118985
120724
  allStyles: "All styles",
118986
120725
  allScenes: "All scenes",
120726
+ categoryNav: "Categories",
118987
120727
  search: "Search examples, prompts, styles…",
118988
120728
  results: "{count} examples",
118989
120729
  noResults: "No matching examples. Try another keyword or filter.",
@@ -118993,6 +120733,9 @@ img.ProseMirror-separator {
118993
120733
  copied: "Copied",
118994
120734
  use: "Use this prompt",
118995
120735
  source: "View source",
120736
+ useReference: "Prompt + reference",
120737
+ referenceLoading: "Fetching image…",
120738
+ referenceFailed: "Failed to load the reference image",
118996
120739
  loading: "Loading inspiration…",
118997
120740
  loadFailed: "Could not load the inspiration library.",
118998
120741
  copyFailed: "Failed to copy",
@@ -119007,12 +120750,28 @@ img.ProseMirror-separator {
119007
120750
  favorite: "Favorite",
119008
120751
  favorited: "Favorited",
119009
120752
  zoomHint: "Click to zoom in",
119010
- close: "Close"
119011
- }
119012
- };
119013
- 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 }) => {
119014
120771
  const [language, setLanguage] = (0, react.useState)(() => locale?.getSnapshot?.().active?.startsWith("en") ? "en" : "zh");
119015
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);
119016
120775
  const [catalogError, setCatalogError] = (0, react.useState)(false);
119017
120776
  const [search, setSearch] = (0, react.useState)("");
119018
120777
  const [category, setCategory] = (0, react.useState)("");
@@ -119028,8 +120787,12 @@ img.ProseMirror-separator {
119028
120787
  const [onlyFavorites, setOnlyFavorites] = (0, react.useState)(false);
119029
120788
  const [lightboxCase, setLightboxCase] = (0, react.useState)(null);
119030
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)("");
119031
120793
  const toastTimerRef = (0, react.useRef)(null);
119032
120794
  const sentinelRef = (0, react.useRef)(null);
120795
+ const listRef = (0, react.useRef)(null);
119033
120796
  (0, react.useEffect)(() => {
119034
120797
  if (!locale?.subscribe) return;
119035
120798
  return locale.subscribe(() => setLanguage(locale.getSnapshot?.().active?.startsWith("en") ? "en" : "zh"));
@@ -119099,7 +120862,33 @@ img.ProseMirror-separator {
119099
120862
  mounted = false;
119100
120863
  };
119101
120864
  }, []);
119102
- 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
+ };
119103
120892
  const matchingCases = (0, react.useMemo)(() => {
119104
120893
  if (source === null) return [];
119105
120894
  const query = search.trim().toLowerCase();
@@ -119141,6 +120930,67 @@ img.ProseMirror-separator {
119141
120930
  onlyFavorites
119142
120931
  ]);
119143
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]);
119144
120994
  (0, react.useEffect)(() => {
119145
120995
  const el = sentinelRef.current;
119146
120996
  if (!el) return;
@@ -119265,179 +121115,256 @@ img.ProseMirror-separator {
119265
121115
  className: "dsh-ig-spin",
119266
121116
  size: 23
119267
121117
  }), (0, react_jsx_runtime.jsx)("span", { children: t("loading") })]
119268
- }) : (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsxs)("div", {
119269
- className: "dsh-ig-inspiration-toolbar",
119270
- children: [
119271
- (0, react_jsx_runtime.jsxs)("label", {
119272
- className: "dsh-ig-inspiration-search",
119273
- children: [(0, react_jsx_runtime.jsx)(Search, { size: 15 }), (0, react_jsx_runtime.jsx)("input", {
119274
- value: search,
119275
- onChange: (event) => setSearch(event.target.value),
119276
- placeholder: t("search")
119277
- })]
119278
- }),
119279
- (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", {
119280
121205
  type: "button",
119281
- className: `dsh-ig-inspiration-fav-filter ${onlyFavorites ? "is-active" : ""}`,
119282
- onClick: () => setOnlyFavorites((prev) => !prev),
119283
- title: t("onlyFavorites"),
119284
- children: [(0, react_jsx_runtime.jsx)(Star, {
119285
- size: 14,
119286
- className: onlyFavorites ? "fill-star" : ""
119287
- }), (0, react_jsx_runtime.jsxs)("span", { children: [t("onlyFavorites"), favorites.size > 0 ? ` (${favorites.size})` : ""] })]
119288
- }),
119289
- (0, react_jsx_runtime.jsxs)("select", {
119290
- value: category,
119291
- onChange: (event) => setCategory(event.target.value),
119292
- "aria-label": t("allCategories"),
119293
- children: [(0, react_jsx_runtime.jsx)("option", {
119294
- value: "",
119295
- children: t("allCategories")
119296
- }), source.categories.map((item) => (0, react_jsx_runtime.jsx)("option", {
119297
- value: item,
119298
- children: translateTag(item, language)
119299
- }, item))]
119300
- }),
119301
- (0, react_jsx_runtime.jsxs)("select", {
119302
- value: style,
119303
- onChange: (event) => setStyle(event.target.value),
119304
- "aria-label": t("allStyles"),
119305
- children: [(0, react_jsx_runtime.jsx)("option", {
119306
- value: "",
119307
- children: t("allStyles")
119308
- }), source.styles.map((item) => (0, react_jsx_runtime.jsx)("option", {
119309
- value: item,
119310
- children: translateTag(item, language)
119311
- }, item))]
119312
- }),
119313
- (0, react_jsx_runtime.jsxs)("select", {
119314
- value: scene,
119315
- onChange: (event) => setScene(event.target.value),
119316
- "aria-label": t("allScenes"),
119317
- children: [(0, react_jsx_runtime.jsx)("option", {
119318
- value: "",
119319
- children: t("allScenes")
119320
- }), source.scenes.map((item) => (0, react_jsx_runtime.jsx)("option", {
119321
- value: item,
119322
- children: translateTag(item, language)
119323
- }, item))]
119324
- })
119325
- ]
119326
- }), (0, react_jsx_runtime.jsxs)("div", {
119327
- className: "dsh-ig-inspiration-layout",
119328
- children: [(0, react_jsx_runtime.jsxs)("div", { children: [(0, react_jsx_runtime.jsxs)("div", {
119329
- className: "dsh-ig-inspiration-summary",
119330
- 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) })]
119331
- }), matchingCases.length === 0 ? (0, react_jsx_runtime.jsx)("div", {
119332
- className: "dsh-ig-inspiration-empty",
119333
- children: onlyFavorites ? t("noFavorites") : t("noResults")
119334
- }) : (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsx)("div", {
119335
- className: "dsh-ig-inspiration-grid",
119336
- children: visibleCases.map((item) => (0, react_jsx_runtime.jsx)(InspirationCard, {
119337
- item,
119338
- sourceId: source.id,
119339
- selected: selected?.id === item.id,
119340
- isFavorited: favorites.has(item.id),
119341
- language,
119342
- featuredLabel: t("featured"),
119343
- retryLabel: t("retry"),
119344
- onSelect: () => {
119345
- setSelected(item);
119346
- 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
+ });
119347
121213
  },
119348
- onToggleFavorite: () => toggleFavorite(item.id)
119349
- }, item.id))
119350
- }), visibleCases.length < matchingCases.length ? (0, react_jsx_runtime.jsx)("div", {
119351
- ref: sentinelRef,
119352
- style: {
119353
- height: 32,
119354
- margin: "16px 0"
119355
- }
119356
- }) : (0, react_jsx_runtime.jsx)("div", {
119357
- className: "dsh-ig-inspiration-end-hint",
119358
- children: t("allLoaded", { count: String(matchingCases.length) })
119359
- })] })] }), (0, react_jsx_runtime.jsx)("aside", {
119360
- className: "dsh-ig-inspiration-inspector",
119361
- children: selected === null ? (0, react_jsx_runtime.jsxs)("div", {
119362
- className: "dsh-ig-inspiration-inspector-empty",
119363
- children: [(0, react_jsx_runtime.jsx)(Image$2, { size: 28 }), (0, react_jsx_runtime.jsx)("span", { children: t("selectHint") })]
119364
- }) : (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsxs)("div", {
119365
- className: "dsh-ig-inspiration-inspector-image",
119366
- onClick: () => setLightboxCase({
119367
- sourceId: source.id,
119368
- caseId: selected.id,
119369
- title: selected.title,
119370
- alt: selected.imageAlt
119371
- }),
119372
- title: t("zoomHint"),
119373
- children: [(0, react_jsx_runtime.jsx)(InspirationImage, {
119374
- sourceId: source.id,
119375
- caseId: selected.id,
119376
- alt: selected.imageAlt,
119377
- retryLabel: t("retry")
119378
- }), (0, react_jsx_runtime.jsxs)("span", {
119379
- className: "dsh-ig-inspiration-inspector-zoom-hint",
119380
- children: [(0, react_jsx_runtime.jsx)(Maximize2, { size: 11 }), t("zoomHint")]
119381
- })]
119382
- }), (0, react_jsx_runtime.jsxs)("div", {
119383
- className: "dsh-ig-inspiration-inspector-body",
119384
- children: [
119385
- (0, react_jsx_runtime.jsxs)("div", {
119386
- className: "dsh-ig-inspiration-inspector-head",
119387
- children: [(0, react_jsx_runtime.jsx)("h2", { children: selected.title }), (0, react_jsx_runtime.jsx)("button", {
119388
- type: "button",
119389
- className: `dsh-ig-inspiration-inspector-fav ${favorites.has(selected.id) ? "is-favorited" : ""}`,
119390
- onClick: () => toggleFavorite(selected.id),
119391
- title: favorites.has(selected.id) ? t("favorited") : t("favorite"),
119392
- children: (0, react_jsx_runtime.jsx)(Star, {
119393
- size: 15,
119394
- className: favorites.has(selected.id) ? "fill-star" : ""
119395
- })
119396
- })]
119397
- }),
119398
- selected.sourceLabel && (0, react_jsx_runtime.jsx)("p", {
119399
- className: "dsh-ig-inspiration-origin",
119400
- children: selected.sourceLabel
119401
- }),
119402
- (0, react_jsx_runtime.jsxs)("div", {
119403
- className: "dsh-ig-inspiration-tags",
119404
- 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))]
119405
- }),
119406
- (0, react_jsx_runtime.jsxs)("div", {
119407
- className: "dsh-ig-inspiration-prompt-label",
119408
- children: [(0, react_jsx_runtime.jsx)("span", { children: t("prompt") }), (0, react_jsx_runtime.jsx)("span", { children: selected.prompt.length })]
119409
- }),
119410
- (0, react_jsx_runtime.jsx)("p", {
119411
- className: "dsh-ig-inspiration-prompt",
119412
- 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
119413
121287
  }),
119414
- (0, react_jsx_runtime.jsxs)("div", {
119415
- className: "dsh-ig-inspiration-actions",
119416
- children: [
119417
- (0, react_jsx_runtime.jsxs)("button", {
119418
- type: "button",
119419
- onClick: () => void copyPrompt(),
119420
- children: [copied ? (0, react_jsx_runtime.jsx)(Check, { size: 14 }) : (0, react_jsx_runtime.jsx)(Clipboard, { size: 14 }), copied ? t("copied") : t("copy")]
119421
- }),
119422
- selected.sourceUrl ?? selected.githubUrl ? (0, react_jsx_runtime.jsxs)("a", {
119423
- className: "dsh-ig-inspiration-source-link",
119424
- href: selected.sourceUrl ?? selected.githubUrl,
119425
- target: "_blank",
119426
- rel: "noreferrer",
119427
- children: [(0, react_jsx_runtime.jsx)(ExternalLink, { size: 14 }), t("source")]
119428
- }) : (0, react_jsx_runtime.jsx)("span", {}),
119429
- (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", {
119430
121305
  type: "button",
119431
- className: "dsh-ig-inspiration-use",
119432
- onClick: () => onUsePrompt(selected.prompt),
119433
- children: [(0, react_jsx_runtime.jsx)(Sparkles, { size: 14 }), t("use")]
119434
- })
119435
- ]
119436
- })
119437
- ]
119438
- })] })
119439
- })]
119440
- })] }),
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
+ ] })] }),
119441
121368
  lightboxCase !== null && (0, react_jsx_runtime.jsx)("div", {
119442
121369
  className: "dsh-ig-inspiration-lightbox",
119443
121370
  onClick: () => setLightboxCase(null),
@@ -119457,6 +121384,7 @@ img.ProseMirror-separator {
119457
121384
  children: (0, react_jsx_runtime.jsx)(InspirationImage, {
119458
121385
  sourceId: lightboxCase.sourceId,
119459
121386
  caseId: lightboxCase.caseId,
121387
+ revision: lightboxCase.revision,
119460
121388
  alt: lightboxCase.alt,
119461
121389
  retryLabel: t("retry")
119462
121390
  })
@@ -119509,7 +121437,155 @@ img.ProseMirror-separator {
119509
121437
  ]
119510
121438
  });
119511
121439
  };
119512
- 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", {
119513
121589
  type: "button",
119514
121590
  className: `dsh-ig-inspiration-card ${selected ? "is-selected" : ""}`,
119515
121591
  onClick: onSelect,
@@ -119536,6 +121612,7 @@ img.ProseMirror-separator {
119536
121612
  (0, react_jsx_runtime.jsx)(InspirationImage, {
119537
121613
  sourceId,
119538
121614
  caseId: item.id,
121615
+ revision: sourceRevision,
119539
121616
  alt: item.imageAlt,
119540
121617
  retryLabel
119541
121618
  })
@@ -119545,7 +121622,7 @@ img.ProseMirror-separator {
119545
121622
  children: [(0, react_jsx_runtime.jsx)("strong", { children: item.title }), (0, react_jsx_runtime.jsx)("span", { children: translateTag(item.category, language) })]
119546
121623
  })]
119547
121624
  });
119548
- const InspirationImage = ({ sourceId, caseId, alt, retryLabel = "Retry" }) => {
121625
+ const InspirationImage = ({ sourceId, caseId, revision, alt, retryLabel = "Retry" }) => {
119549
121626
  const [node, setNode] = (0, react.useState)(null);
119550
121627
  const [visible, setVisible] = (0, react.useState)(false);
119551
121628
  const [url, setUrl] = (0, react.useState)(null);
@@ -119566,7 +121643,7 @@ img.ProseMirror-separator {
119566
121643
  urlRef.current = null;
119567
121644
  }
119568
121645
  setUrl(null);
119569
- fetchInspirationImage(sourceId, caseId).then((blob) => {
121646
+ fetchInspirationImage(sourceId, caseId, revision).then((blob) => {
119570
121647
  if (!active) return;
119571
121648
  const next = URL.createObjectURL(blob);
119572
121649
  if (urlRef.current !== null) URL.revokeObjectURL(urlRef.current);
@@ -119581,7 +121658,8 @@ img.ProseMirror-separator {
119581
121658
  }, [
119582
121659
  visible,
119583
121660
  sourceId,
119584
- caseId
121661
+ caseId,
121662
+ revision
119585
121663
  ]);
119586
121664
  (0, react.useEffect)(() => {
119587
121665
  return () => {
@@ -119618,7 +121696,7 @@ img.ProseMirror-separator {
119618
121696
  urlRef.current = null;
119619
121697
  }
119620
121698
  setUrl(null);
119621
- evictInspirationImage(sourceId, caseId).then(() => fetchInspirationImage(sourceId, caseId)).then((blob) => {
121699
+ evictInspirationImage(sourceId, caseId, revision).then(() => fetchInspirationImage(sourceId, caseId, revision)).then((blob) => {
119622
121700
  const next = URL.createObjectURL(blob);
119623
121701
  if (urlRef.current !== null) URL.revokeObjectURL(urlRef.current);
119624
121702
  urlRef.current = next;
@@ -119736,6 +121814,12 @@ img.ProseMirror-separator {
119736
121814
  filterXAI: "xAI Grok",
119737
121815
  filterZhipu: "智谱 GLM",
119738
121816
  filterComfyUI: "本地 ComfyUI",
121817
+ filterImport: "导入",
121818
+ importImages: "导入图片",
121819
+ importAdded: "已导入 {count} 张图片",
121820
+ importPartial: "导入 {count} 张,{fail} 张失败",
121821
+ importFailed: "导入失败",
121822
+ importPickHint: "选择要导入的图片(PNG/JPG/WebP/GIF)",
119739
121823
  filterAllModels: "全部模型",
119740
121824
  filterAllRatios: "全部比例",
119741
121825
  searchPlaceholder: "搜索 Prompt、标签…",
@@ -119832,6 +121916,12 @@ img.ProseMirror-separator {
119832
121916
  filterXAI: "xAI Grok",
119833
121917
  filterZhipu: "Zhipu GLM",
119834
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)",
119835
121925
  filterAllModels: "All Models",
119836
121926
  filterAllRatios: "All Ratios",
119837
121927
  searchPlaceholder: "Search prompt, tags…",
@@ -119914,6 +122004,19 @@ img.ProseMirror-separator {
119914
122004
  }
119915
122005
  };
119916
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
+ }
119917
122020
  function formatRelativeTime(timestamp, t) {
119918
122021
  const diff = Date.now() - timestamp;
119919
122022
  if (diff < 6e4) return t("justNow");
@@ -119969,9 +122072,10 @@ img.ProseMirror-separator {
119969
122072
  return "1:1";
119970
122073
  }
119971
122074
  const GalleryViewTab = (props) => {
119972
- const { locale, sessionId, useSessions, useWorkspaces, credentialEvents, inSidebar, defaultTab, initialCanvasSurface } = props;
122075
+ const { locale, sessionId, useSessions, useWorkspaces, credentialEvents, inSidebar, defaultTab, initialCanvasSurface, useTabInfo } = props;
119973
122076
  const [activeTab, setActiveTab] = (0, react.useState)(defaultTab ?? "gallery");
119974
122077
  const [studioDraft, setStudioDraft] = (0, react.useState)(void 0);
122078
+ const [studioReference, setStudioReference] = (0, react.useState)(void 0);
119975
122079
  const [items, setItems] = (0, react.useState)([]);
119976
122080
  const [search, setSearch] = (0, react.useState)("");
119977
122081
  const [selectedProvider, setSelectedProvider] = (0, react.useState)("all");
@@ -119992,6 +122096,10 @@ img.ProseMirror-separator {
119992
122096
  localStorage.setItem("dsh-ig-only-current-workspace", String(onlyCurrentWorkspace));
119993
122097
  } catch {}
119994
122098
  }, [onlyCurrentWorkspace]);
122099
+ const tabVisible = useTabInfo?.().tab?.visible;
122100
+ (0, react.useEffect)(() => {
122101
+ if (tabVisible === false) deselectStudioTlCanvases();
122102
+ }, [tabVisible]);
119995
122103
  (0, react.useEffect)(() => {
119996
122104
  let mounted = true;
119997
122105
  fetch(STUDIO_ROUTE).then((res) => res.ok ? res.json() : null).then((data) => {
@@ -120082,11 +122190,80 @@ img.ProseMirror-separator {
120082
122190
  setToast(null);
120083
122191
  }, 2e3);
120084
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
+ };
120085
122256
  const useInspirationPrompt = (0, react.useCallback)((prompt) => {
120086
122257
  setStudioDraft(prompt);
120087
122258
  setActiveTab("studio");
120088
122259
  }, []);
122260
+ const useInspirationReference = (0, react.useCallback)((file, prompt) => {
122261
+ setStudioDraft(prompt);
122262
+ setStudioReference(file);
122263
+ setActiveTab("studio");
122264
+ }, []);
120089
122265
  const clearStudioDraft = (0, react.useCallback)(() => setStudioDraft(void 0), []);
122266
+ const clearStudioReference = (0, react.useCallback)(() => setStudioReference(void 0), []);
120090
122267
  (0, react.useEffect)(() => {
120091
122268
  if (inSidebar) return void 0;
120092
122269
  const seat = document.querySelector("[data-composer-seat]");
@@ -120636,6 +122813,10 @@ img.ProseMirror-separator {
120636
122813
  (0, react_jsx_runtime.jsx)("option", {
120637
122814
  value: "comfyui",
120638
122815
  children: t("filterComfyUI")
122816
+ }),
122817
+ (0, react_jsx_runtime.jsx)("option", {
122818
+ value: "import",
122819
+ children: t("filterImport")
120639
122820
  })
120640
122821
  ]
120641
122822
  }),
@@ -120718,27 +122899,48 @@ img.ProseMirror-separator {
120718
122899
  ]
120719
122900
  }), (0, react_jsx_runtime.jsxs)("div", {
120720
122901
  className: "dsh-ig-studio-toolbar-right",
120721
- children: [(0, react_jsx_runtime.jsxs)("select", {
120722
- className: "dsh-ig-studio-select dsh-ig-studio-select-sort",
120723
- value: sortBy,
120724
- onChange: (e) => setSortBy(e.target.value),
120725
- children: [(0, react_jsx_runtime.jsx)("option", {
120726
- value: "newest",
120727
- children: t("sortNewest")
120728
- }), (0, react_jsx_runtime.jsx)("option", {
120729
- value: "oldest",
120730
- children: t("sortOldest")
120731
- })]
120732
- }), (0, react_jsx_runtime.jsxs)("button", {
120733
- type: "button",
120734
- className: `dsh-ig-studio-btn ${isManageMode ? "is-active" : ""}`,
120735
- title: isManageMode ? t("exitManage") : t("manage"),
120736
- onClick: () => {
120737
- if (isManageMode) handleExitManage();
120738
- else setIsManageMode(true);
120739
- },
120740
- children: [(0, react_jsx_runtime.jsx)(SquareCheckBig, { size: 13 }), (0, react_jsx_runtime.jsx)("span", { children: isManageMode ? t("exitManage") : t("manage") })]
120741
- })]
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
+ ]
120742
122944
  })]
120743
122945
  }),
120744
122946
  (0, react_jsx_runtime.jsx)("div", {
@@ -120808,15 +123010,18 @@ img.ProseMirror-separator {
120808
123010
  }, item.id))
120809
123011
  }) : activeTab === "inspiration" ? (0, react_jsx_runtime.jsx)(InspirationView, {
120810
123012
  locale,
120811
- onUsePrompt: useInspirationPrompt
123013
+ onUsePrompt: useInspirationPrompt,
123014
+ onUseReference: useInspirationReference
120812
123015
  }) : (0, react_jsx_runtime.jsx)(StudioView, {
120813
123016
  locale,
120814
123017
  credentialEvents,
120815
123018
  workspace: activeWorkspace,
120816
123019
  initialPrompt: studioDraft,
123020
+ initialReference: studioReference,
120817
123021
  initialCanvasSurface,
120818
123022
  showInfiniteCanvasHint: !inSidebar,
120819
123023
  onInitialPromptApplied: clearStudioDraft,
123024
+ onInitialReferenceApplied: clearStudioReference,
120820
123025
  onOpenInspiration: () => setActiveTab("inspiration")
120821
123026
  })
120822
123027
  }),
@@ -120976,7 +123181,7 @@ img.ProseMirror-separator {
120976
123181
  children: [
120977
123182
  (0, react_jsx_runtime.jsx)("span", {
120978
123183
  className: "dsh-ig-tag",
120979
- children: previewItem.provider
123184
+ children: previewItem.provider === "import" ? t("filterImport") : previewItem.provider
120980
123185
  }),
120981
123186
  previewItem.model && (0, react_jsx_runtime.jsx)("span", {
120982
123187
  className: "dsh-ig-tag dsh-ig-tag-model",
@@ -121034,7 +123239,7 @@ img.ProseMirror-separator {
121034
123239
  children: [
121035
123240
  (0, react_jsx_runtime.jsx)("span", {
121036
123241
  className: "dsh-ig-tag",
121037
- children: previewItem.provider
123242
+ children: previewItem.provider === "import" ? t("filterImport") : previewItem.provider
121038
123243
  }),
121039
123244
  previewItem.model ? (0, react_jsx_runtime.jsx)("span", {
121040
123245
  className: "dsh-ig-tag dsh-ig-tag-model",
@@ -121627,105 +123832,139 @@ img.ProseMirror-separator {
121627
123832
  //#region lib/types/client/studio-style.js
121628
123833
  const STUDIO_STYLE = `
121629
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}
121630
- .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}
121631
123837
  .dsh-ig-workbench *{box-sizing:border-box}.dsh-ig-workbench button,.dsh-ig-workbench select,.dsh-ig-workbench textarea{font:inherit;color:inherit}
121632
- .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}
121633
123839
  .dsh-ig-workbench-grid.is-sidebar-collapsed{grid-template-columns:minmax(0,1fr) minmax(286px,340px)}
121634
123840
  .dsh-ig-workbench-grid.is-generate-collapsed{grid-template-columns:minmax(190px,238px) minmax(0,1fr)}
121635
123841
  .dsh-ig-workbench-grid.is-sidebar-collapsed.is-generate-collapsed{grid-template-columns:minmax(0,1fr)}
121636
- .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)}
121637
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}
121638
- .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}
121639
- .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}
121640
- .dsh-ig-collapse-btn:hover{border-color:var(--ig-line);background:var(--ig-soft);color:var(--foreground,#182033)}
121641
- .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}
121642
- .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}
121643
- .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}
121644
- .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)}
121645
- .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))}
121646
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)}
121647
123886
  .dsh-ig-canvas-toolbar-left,.dsh-ig-canvas-toolbar-right{display:flex;align-items:center;gap:6px;min-width:0}
121648
123887
  .dsh-ig-canvas-toolbar-left{justify-self:start}
121649
123888
  .dsh-ig-canvas-toolbar-right{justify-self:end}
121650
123889
  .dsh-ig-canvas-toolbar-center{display:flex;align-items:center;justify-self:center;gap:6px;min-width:0}
121651
123890
  .dsh-ig-toolbar-divider{width:1px;height:16px;background:var(--ig-line);flex-shrink:0;margin:0 2px}
121652
- .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}
121653
- .dsh-ig-canvas-toolbar button:hover{border-color:#9bb4f5;background:rgba(47,100,245,.05);color:var(--ig-blue)}
121654
- .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}
121655
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)}
121656
- .dsh-ig-zoom-display{font-size:11px;min-width:38px;text-align:center;color:#64748b;user-select:none;font-variant-numeric:tabular-nums}
121657
- .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)}
121658
123897
  .dsh-ig-status-badge-wrap{position:relative;display:inline-flex;align-items:center}
121659
- .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}
121660
- .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)}
121661
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}
121662
123901
  .dsh-ig-status-dot.is-ready{background:#10b981;box-shadow:0 0 0 2px rgba(16,185,129,.2)}
121663
123902
  .dsh-ig-status-dot.is-muted{background:#ef4444;box-shadow:0 0 0 2px rgba(239,68,68,.2)}
121664
- .dsh-ig-status-text{font-size:11.5px;font-weight:500;color:var(--foreground,#242f44);line-height:1}
121665
- .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}
121666
123905
  .dsh-ig-status-badge-wrap:hover .dsh-ig-status-arrow{transform:rotate(180deg);color:var(--ig-blue)}
121667
- .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)}
121668
123907
  .dsh-ig-status-badge-wrap:hover .dsh-ig-status-popover{opacity:1;pointer-events:auto;transform:translateY(0)}
121669
- .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}
121670
123909
  .dsh-ig-status-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:5px}
121671
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}
121672
123911
  .dsh-ig-status-list li.is-configured{background:rgba(16,185,129,.05);border:1px solid rgba(16,185,129,.12)}
121673
123912
  .dsh-ig-status-list li.is-missing{background:rgba(239,68,68,.04);border:1px solid rgba(239,68,68,.12);opacity:.75}
121674
123913
  .dsh-ig-status-item-name{display:flex;flex-direction:column;min-width:0}
121675
- .dsh-ig-status-item-name strong{font-size:11.5px;font-weight:600;color:var(--foreground,#182033);line-height:16px}
121676
- .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}
121677
123916
  .dsh-ig-status-item-tag{font-size:10px;font-weight:600;padding:2px 6px;border-radius:4px;flex-shrink:0}
121678
123917
  .is-configured .dsh-ig-status-item-tag{color:#059669;background:rgba(16,185,129,.14)}
121679
123918
  .is-missing .dsh-ig-status-item-tag{color:#dc2626;background:rgba(239,68,68,.14)}
121680
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}
121681
123920
  .dsh-ig-canvas.is-dragging,.dsh-ig-canvas.is-dragging *{cursor:grabbing!important}
121682
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}
121683
- .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}
121684
- .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}
121685
- .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}
121686
- .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)}
121687
- .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)}
121688
- .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)}
121689
- .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}
121690
- .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)}
121691
123930
  .dsh-ig-reference-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(64px,1fr));gap:8px}
121692
- .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)}
121693
123932
  .dsh-ig-reference-card img{width:100%;height:100%;object-fit:cover}
121694
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}
121695
123934
  .dsh-ig-reference-card button:hover{background:#ef4444}
121696
- .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}
121697
123936
  .dsh-ig-reference-add:hover{border-color:var(--ig-blue);background:rgba(47,100,245,.05);color:var(--ig-blue)}
121698
- .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}
121699
- .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)}
121700
- .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)}
121701
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}
121702
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)}
121703
- .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}
121704
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}
121705
- .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}
121706
123945
  .dsh-ig-workbench-modal-header{display:flex;align-items:center;gap:10px}
121707
123946
  .dsh-ig-workbench-modal-header strong{font-size:14px;font-weight:650;color:#b91c1c}
121708
- .dsh-ig-workbench-modal-desc{font-size:12px;line-height:18px;color:#555f77;margin:0}
121709
- .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}
121710
123949
  .dsh-ig-workbench-modal-actions{display:flex;justify-content:flex-end;gap:8px;margin-top:4px}
121711
123950
  .dsh-ig-workbench-modal-actions button{height:32px;padding:0 14px;border-radius:7px;font-size:11.5px;font-weight:600;cursor:pointer}
121712
- .dsh-ig-workbench-modal-cancel{border:1px solid var(--ig-line);background:var(--background,#fff);color:#6b7280}
121713
- .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)}
121714
123953
  .dsh-ig-workbench-modal-danger{border:0;background:#dc2626;color:#fff}
121715
123954
  .dsh-ig-workbench-modal-danger:hover{background:#b91c1c}
121716
123955
  @keyframes dshIgPulse{50%{transform:scale(1.06);box-shadow:0 0 0 16px rgba(47,100,245,.025)}}
121717
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}
121718
123957
  @keyframes dshIgFade{from{opacity:0;transform:translate(-50%,6px)}to{opacity:1;transform:translate(-50%,0)}}
121719
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}}
121720
- @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}}
121721
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}}
121722
123961
  @media(prefers-reduced-motion:reduce){.dsh-ig-workbench *{scroll-behavior:auto!important;animation-duration:.01ms!important;transition-duration:.01ms!important}}
121723
- .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}
121724
- .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}
121725
- .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)}
121726
123965
  .dsh-ig-workspace-checkbox{cursor:pointer;accent-color:#2f64f5;width:13.5px;height:13.5px;margin:0}
121727
123966
  .dsh-ig-workspace-filter-text{font-weight:500;white-space:nowrap}
121728
- .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}
121729
123968
  .dsh-ig-canvas-cluster{display:grid;gap:16px;align-items:center;justify-content:center;user-select:none;pointer-events:auto;flex-shrink:0}
121730
123969
  .dsh-ig-canvas-cluster[data-count="2"]{grid-template-columns:repeat(2,1fr);max-width:880px}
121731
123970
  .dsh-ig-canvas-cluster[data-count="3"],.dsh-ig-canvas-cluster[data-count="4"]{grid-template-columns:repeat(2,1fr);max-width:760px}
@@ -121738,19 +123977,19 @@ img.ProseMirror-separator {
121738
123977
  .dsh-ig-canvas-badge.is-model{max-width:calc(100% - 20px);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:600}
121739
123978
  .dsh-ig-canvas-item.is-checked .dsh-ig-canvas-badge{background:var(--ig-blue)}
121740
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)}
121741
- .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)}
121742
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)}
121743
123982
  .dsh-ig-lightbox-nav:hover{background:rgba(47,100,245,.85);transform:translateY(-50%) scale(1.08)}
121744
123983
  .dsh-ig-lightbox-nav.is-prev{left:18px}
121745
123984
  .dsh-ig-lightbox-nav.is-next{right:18px}
121746
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}
121747
123986
  .dsh-ig-save-gallery-btn:hover{background:rgba(47,100,245,.12)!important}
121748
- .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}
121749
123988
 
121750
123989
  /* tldraw infinite canvas surface (P0 spike) */
121751
123990
  .dsh-ig-tl-canvas{position:relative;flex:1 1 0;height:0;min-height:0;overflow:hidden;isolation:isolate}
121752
- .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}
121753
- .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)}
121754
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}
121755
123994
 
121756
123995
  /* ---- Right-sidebar seat variant (DSH 0.1.5 'sidebar.right.pane.tab') ----
@@ -121943,6 +124182,10 @@ img.ProseMirror-separator {
121943
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}
121944
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)}
121945
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)}
121946
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)}
121947
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}
121948
124191
  .dsh-ig-inspiration-empty.is-catalog-error{display:flex;flex-direction:column;gap:12px;color:var(--inspiration-text)}
@@ -121966,7 +124209,37 @@ img.ProseMirror-separator {
121966
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}
121967
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)}
121968
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}
121969
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}
121970
124243
  `;
121971
124244
  //#endregion
121972
124245
  //#region lib/types/client/conversation-image-revisions.js
@@ -122038,6 +124311,12 @@ img.ProseMirror-separator {
122038
124311
  return typeof candidate.attachmentId === "string" && (candidate.mediaType === "image/png" || candidate.mediaType === "image/jpeg" || candidate.mediaType === "image/webp" || candidate.mediaType === "image/gif") && typeof candidate.bytes === "number" && typeof candidate.width === "number" && typeof candidate.height === "number";
122039
124312
  }
122040
124313
  //#endregion
124314
+ //#region lib/types/client/settings-write.js
124315
+ /** DSH 0.1.7 resolves refused writes to false; older settings scopes resolve to void. */
124316
+ async function writeSetting(scope, field, value, rejectedMessage) {
124317
+ if (await scope.set(field, value) === false) throw new Error(rejectedMessage);
124318
+ }
124319
+ //#endregion
122041
124320
  //#region lib/types/client/provider-pill.js
122042
124321
  /**
122043
124322
  * Composer tool-row pill: switch the default image provider without leaving
@@ -122058,7 +124337,8 @@ img.ProseMirror-separator {
122058
124337
  subSignedOut: "未登录",
122059
124338
  subStatusUnknown: "状态未知",
122060
124339
  readOnly: "图像设置为只读,无法在此切换",
122061
- switchFailed: "切换失败"
124340
+ switchFailed: "切换失败",
124341
+ writeRejected: "设置未保存,请重试"
122062
124342
  },
122063
124343
  en: {
122064
124344
  pillLabel: "Image",
@@ -122072,7 +124352,8 @@ img.ProseMirror-separator {
122072
124352
  subSignedOut: "Signed out",
122073
124353
  subStatusUnknown: "Unknown",
122074
124354
  readOnly: "Image settings are read-only; switch them in the config source",
122075
- switchFailed: "Switch failed"
124355
+ switchFailed: "Switch failed",
124356
+ writeRejected: "Setting was not saved. Please try again"
122076
124357
  }
122077
124358
  };
122078
124359
  /** Provider row names, per locale. Brand words stay untranslated; only the
@@ -122280,7 +124561,7 @@ img.ProseMirror-separator {
122280
124561
  }
122281
124562
  setError("");
122282
124563
  setPending(provider);
122283
- props.scope.set("provider", provider).then(() => {
124564
+ writeSetting(props.scope, "provider", provider, t("writeRejected")).then(() => {
122284
124565
  setOpen(false);
122285
124566
  }).catch((cause) => {
122286
124567
  setError(cause instanceof Error ? cause.message : String(cause));
@@ -122473,7 +124754,7 @@ img.ProseMirror-separator {
122473
124754
  `;
122474
124755
  //#endregion
122475
124756
  //#region lib/types/client/index.js
122476
- console.info(`[dsh-image-gen] canvas bundle 2026-09-23T16:16:55.812Z`);
124757
+ console.info(`[dsh-image-gen] canvas bundle 2026-09-28T16:41:08.496Z`);
122477
124758
  /** Right-sidebar tab-type registration id (DSH 0.1.5 `sidebarRightTabs`). */
122478
124759
  const SIDEBAR_STUDIO_TAB_ID = "dsh-image-gen/studio";
122479
124760
  /** The tab kind: a page type opened by kind, recognizing no resource address. */
@@ -122545,7 +124826,8 @@ img.ProseMirror-separator {
122545
124826
  editFormat: "图生图请求形态",
122546
124827
  editFormatMultipart: "标准 multipart(OpenAI 官方)",
122547
124828
  editFormatJsonImageUrlArray: "JSON images 数组(商汤等中转)",
122548
- editFormatHint: "文生图不受影响。仅当中转站的图生图接口使用自有 JSON 契约(如商汤 SenseNova)时才需要切换。",
124829
+ editFormatFormReferenceImages: "multipart + reference_images JSON 数组(base64)",
124830
+ editFormatHint: "文生图不受影响。仅当中转站的图生图接口使用自有契约(如商汤 SenseNova 的 JSON images 数组,或要求 reference_images base64 数组的中转)时才需要切换。",
122549
124831
  editExtra: "附加 JSON 字段",
122550
124832
  editExtraPlaceholder: "{\"watermark\": false, \"prompt_extend\": true}",
122551
124833
  editExtraHint: "仅 JSON 形态生效;会合并到请求体末尾,可覆盖默认字段。留空表示不附加。",
@@ -122598,6 +124880,7 @@ img.ProseMirror-separator {
122598
124880
  saving: "保存中…",
122599
124881
  save: "保存",
122600
124882
  saved: "已保存",
124883
+ settingsWriteRejected: "有一项设置未能保存,请检查后重试。",
122601
124884
  savedToPath: "已保存到",
122602
124885
  generating: "正在生成图片…",
122603
124886
  loading: "正在加载图片…",
@@ -122688,7 +124971,8 @@ img.ProseMirror-separator {
122688
124971
  editFormat: "Edit request format",
122689
124972
  editFormatMultipart: "Standard multipart (official OpenAI)",
122690
124973
  editFormatJsonImageUrlArray: "JSON images array (SenseNova etc.)",
122691
- 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.",
122692
124976
  editExtra: "Extra JSON fields",
122693
124977
  editExtraPlaceholder: "{\"watermark\": false, \"prompt_extend\": true}",
122694
124978
  editExtraHint: "Only used with the JSON format; merged into the request body last and may override defaults. Leave empty for none.",
@@ -122741,6 +125025,7 @@ img.ProseMirror-separator {
122741
125025
  saving: "Saving…",
122742
125026
  save: "Save",
122743
125027
  saved: "Saved",
125028
+ settingsWriteRejected: "A setting could not be saved. Please check and try again.",
122744
125029
  savedToPath: "Saved to",
122745
125030
  generating: "Generating image…",
122746
125031
  loading: "Loading image…",
@@ -123057,19 +125342,40 @@ img.ProseMirror-separator {
123057
125342
  :has(.dsh-ig-gallery-page:not(.dsh-ig-page-in-sidebar)) [class*="widthHandle"],
123058
125343
  .root:has(.dsh-ig-gallery-page:not(.dsh-ig-page-in-sidebar)) [class*="widthHandle"]{display:none!important}
123059
125344
  `;
123060
- /** Required browser services. */
125345
+ /**
125346
+ * Required browser services. The settings form reader is deliberately absent:
125347
+ * DSH 0.1.7 serves it as `configForms` (keyed by the Loader entry id) while
125348
+ * 0.1.5/0.1.6 expose `settingsScope` (keyed by the legacy namespace), so it is
125349
+ * probed at runtime in `apply`. A static inject of either name would park this
125350
+ * fiber forever on hosts from the other generation — the issue #55 failure.
125351
+ */
123061
125352
  const inject = [
123062
125353
  "slots",
123063
125354
  "connection",
123064
125355
  "remote",
123065
- "settingsScope",
123066
125356
  "locale"
123067
125357
  ];
125358
+ /** Host with neither settings generation: reads empty, writes refuse, never throws. */
125359
+ function degradedScope() {
125360
+ return {
125361
+ getSnapshot: () => ({
125362
+ value: void 0,
125363
+ writable: false
125364
+ }),
125365
+ subscribe: () => () => {},
125366
+ set: async () => false
125367
+ };
125368
+ }
123068
125369
  /** Mount the settings card, generated-image card, and native conversation gallery view. */
123069
125370
  function apply(ctx) {
123070
- const scope = ctx.settingsScope.bind({ namespace: IMAGE_GENERATION_NAMESPACE });
123071
- const chatScope = ctx.settingsScope.bind({ namespace: "ui-chat" });
123072
- const isCompactTranscript = () => chatScope.getSnapshot().value?.transcriptView !== "normal";
125371
+ const configForms = ctx.get("configForms");
125372
+ const settingsScopeApi = configForms === void 0 ? ctx.get("settingsScope") : void 0;
125373
+ const scope = configForms?.get("image-gen") ?? settingsScopeApi?.bind({ namespace: "image-generation" }) ?? degradedScope();
125374
+ const chatScope = configForms?.get("ui-chat") ?? settingsScopeApi?.bind({ namespace: "ui-chat" }) ?? degradedScope();
125375
+ const isCompactTranscript = () => {
125376
+ const mode = chatScope.getSnapshot().value?.transcriptView;
125377
+ return configForms === void 0 ? mode !== "normal" : mode !== "standard" && mode !== "detailed" && mode !== "verbose";
125378
+ };
123073
125379
  const locale = ctx.get("locale");
123074
125380
  const promotion = { enabled: false };
123075
125381
  ctx.inject(["sessions"], (owner) => {
@@ -123359,6 +125665,10 @@ img.ProseMirror-separator {
123359
125665
  return "";
123360
125666
  }
123361
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
+ }
123362
125672
  /** Build one row per provider from persisted settings, including ComfyUI extras. */
123363
125673
  function rowsFromSettings(value) {
123364
125674
  const rows = {};
@@ -123372,7 +125682,7 @@ img.ProseMirror-separator {
123372
125682
  timeoutSeconds: Math.max(1, Math.round((value?.comfyuiTimeoutMs ?? 3e5) / 1e3))
123373
125683
  } : {},
123374
125684
  ...provider === "openai-compat" ? {
123375
- editFormat: value?.openaiCompatEditFormat === "jsonImageUrlArray" ? "jsonImageUrlArray" : "multipart",
125685
+ editFormat: parseEditFormat(value?.openaiCompatEditFormat),
123376
125686
  editExtraText: editExtraTextOf(value?.openaiCompatEditExtra)
123377
125687
  } : {},
123378
125688
  ...provider === "seedream" ? {
@@ -123562,6 +125872,7 @@ img.ProseMirror-separator {
123562
125872
  if (params) for (const [k, v] of Object.entries(params)) text = text.replace(`{${k}}`, v);
123563
125873
  return text;
123564
125874
  };
125875
+ const saveSetting = (field, value) => writeSetting(props.scope, field, value, t("settingsWriteRejected"));
123565
125876
  const providerLabels = {
123566
125877
  google: t("providerGoogle"),
123567
125878
  openai: t("providerOpenAI"),
@@ -123673,7 +125984,7 @@ img.ProseMirror-separator {
123673
125984
  setDefaultProvider(provider);
123674
125985
  setProviderMessage("");
123675
125986
  setProviderMessageIsError(false);
123676
- props.scope.set("provider", provider).catch((cause) => {
125987
+ saveSetting("provider", provider).catch((cause) => {
123677
125988
  setDefaultProvider(snapshot.value?.provider ?? "google");
123678
125989
  setProviderMessage(cause instanceof Error ? cause.message : String(cause));
123679
125990
  setProviderMessageIsError(true);
@@ -123699,12 +126010,12 @@ img.ProseMirror-separator {
123699
126010
  }
123700
126011
  if (new Set(entries.map((entry) => entry.name)).size !== entries.length) throw new Error(t("workflowDuplicateName"));
123701
126012
  const activeEntry = entries.find((entry) => entry.name === row.activeWorkflow) ?? entries[0];
123702
- await props.scope.set("comfyuiBaseURL", row.baseURL);
123703
- await props.scope.set("comfyuiWorkflows", entries);
123704
- await props.scope.set("comfyuiActiveWorkflow", activeEntry === void 0 ? "" : activeEntry.name);
123705
- await props.scope.set("comfyuiWorkflowJson", activeEntry === void 0 ? "" : activeEntry.json);
123706
- await props.scope.set("comfyuiWorkflowName", activeEntry === void 0 ? "" : activeEntry.name);
123707
- await props.scope.set("comfyuiTimeoutMs", Math.max(1, Math.round(row.timeoutSeconds)) * 1e3);
126013
+ await saveSetting("comfyuiBaseURL", row.baseURL);
126014
+ await saveSetting("comfyuiWorkflows", entries);
126015
+ await saveSetting("comfyuiActiveWorkflow", activeEntry === void 0 ? "" : activeEntry.name);
126016
+ await saveSetting("comfyuiWorkflowJson", activeEntry === void 0 ? "" : activeEntry.json);
126017
+ await saveSetting("comfyuiWorkflowName", activeEntry === void 0 ? "" : activeEntry.name);
126018
+ await saveSetting("comfyuiTimeoutMs", Math.max(1, Math.round(row.timeoutSeconds)) * 1e3);
123708
126019
  } else if (isSubscriptionProvider(provider)) await Promise.resolve();
123709
126020
  else {
123710
126021
  let editExtra = {};
@@ -123721,16 +126032,16 @@ img.ProseMirror-separator {
123721
126032
  editExtra = parsed;
123722
126033
  }
123723
126034
  }
123724
- await props.scope.set(modelFieldOf(provider), row.model);
123725
- await props.scope.set(baseURLFieldOf(provider), row.baseURL);
126035
+ await saveSetting(modelFieldOf(provider), row.model);
126036
+ await saveSetting(baseURLFieldOf(provider), row.baseURL);
123726
126037
  if (provider === "openai-compat") {
123727
- await props.scope.set("openaiCompatEditFormat", row.editFormat);
123728
- await props.scope.set("openaiCompatEditExtra", editExtra);
126038
+ await saveSetting("openaiCompatEditFormat", row.editFormat);
126039
+ await saveSetting("openaiCompatEditExtra", editExtra);
123729
126040
  }
123730
126041
  if (provider === "seedream") {
123731
- await props.scope.set("seedreamOutputFormat", row.outputFormat);
123732
- await props.scope.set("seedreamWatermark", row.watermark);
123733
- await props.scope.set("seedreamBackground", row.background);
126042
+ await saveSetting("seedreamOutputFormat", row.outputFormat);
126043
+ await saveSetting("seedreamWatermark", row.watermark);
126044
+ await saveSetting("seedreamBackground", row.background);
123734
126045
  }
123735
126046
  if (row.keyInput.trim().length > 0) {
123736
126047
  const keyRef = cloudCredentialRef(provider);
@@ -124021,7 +126332,7 @@ img.ProseMirror-separator {
124021
126332
  setSaveToWorkspace(next);
124022
126333
  setWorkspaceMessage("");
124023
126334
  setWorkspaceMessageIsError(false);
124024
- props.scope.set("saveToWorkspace", next).catch((cause) => {
126335
+ saveSetting("saveToWorkspace", next).catch((cause) => {
124025
126336
  setSaveToWorkspace(snapshot.value?.saveToWorkspace ?? true);
124026
126337
  setWorkspaceMessage(cause instanceof Error ? cause.message : String(cause));
124027
126338
  setWorkspaceMessageIsError(true);
@@ -124033,7 +126344,7 @@ img.ProseMirror-separator {
124033
126344
  if (next === (snapshot.value?.workspaceFolder ?? "dsh-image-gen")) return;
124034
126345
  setWorkspaceMessage("");
124035
126346
  setWorkspaceMessageIsError(false);
124036
- props.scope.set("workspaceFolder", next).then(() => {
126347
+ saveSetting("workspaceFolder", next).then(() => {
124037
126348
  setWorkspaceMessage(t("saved"));
124038
126349
  }).catch((cause) => {
124039
126350
  setWorkspaceFolder(snapshot.value?.workspaceFolder ?? "dsh-image-gen");
@@ -124046,7 +126357,7 @@ img.ProseMirror-separator {
124046
126357
  setShowPill(next);
124047
126358
  setUiMessage("");
124048
126359
  setUiMessageIsError(false);
124049
- props.scope.set("showProviderPill", next).catch((cause) => {
126360
+ saveSetting("showProviderPill", next).catch((cause) => {
124050
126361
  setShowPill(snapshot.value?.showProviderPill === true);
124051
126362
  setUiMessage(cause instanceof Error ? cause.message : String(cause));
124052
126363
  setUiMessageIsError(true);
@@ -124188,16 +126499,23 @@ img.ProseMirror-separator {
124188
126499
  className: "dsh-ig-input",
124189
126500
  value: row.editFormat,
124190
126501
  onChange: (event) => {
124191
- updateRow(provider, { editFormat: event.target.value === "jsonImageUrlArray" ? "jsonImageUrlArray" : "multipart" });
126502
+ updateRow(provider, { editFormat: parseEditFormat(event.target.value) });
124192
126503
  },
124193
126504
  disabled: !snapshot.writable,
124194
- children: [(0, react_jsx_runtime.jsx)("option", {
124195
- value: "multipart",
124196
- children: t("editFormatMultipart")
124197
- }), (0, react_jsx_runtime.jsx)("option", {
124198
- value: "jsonImageUrlArray",
124199
- children: t("editFormatJsonImageUrlArray")
124200
- })]
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
+ ]
124201
126519
  }),
124202
126520
  (0, react_jsx_runtime.jsx)("span", {
124203
126521
  className: "dsh-ig-hint",
@@ -125353,7 +127671,6 @@ img.ProseMirror-separator {
125353
127671
  (0, react_jsx_runtime.jsxs)("label", { children: [(0, react_jsx_runtime.jsx)("span", { children: t("prompt") }), (0, react_jsx_runtime.jsx)("textarea", {
125354
127672
  ref: regenerateTextareaRef,
125355
127673
  value: regeneratePrompt,
125356
- maxLength: 2e3,
125357
127674
  disabled: isRegenerating,
125358
127675
  onChange: (event) => setRegeneratePrompt(event.target.value),
125359
127676
  onKeyDown: (event) => {