dsh-image-gen 0.7.0 → 0.8.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js CHANGED
@@ -75,6 +75,8 @@ window.__ModuleLoader__.load({
75
75
  //#region lib/types/shared.js
76
76
  /** Browser route used by the generated-image card. */
77
77
  const IMAGE_ROUTE = "/plugins/dsh-image-gen/image";
78
+ /** Browser route that uploads user-picked images as DSH attachments. */
79
+ const IMPORT_ROUTE = "/plugins/dsh-image-gen/import";
78
80
  /** Browser route used for deleting generated images and workspace files. */
79
81
  const DELETE_ROUTE = "/plugins/dsh-image-gen/delete";
80
82
  /** Same-origin route used by the browser image workbench. */
@@ -215,7 +217,7 @@ window.__ModuleLoader__.load({
215
217
  const DEFAULT_SUBSCRIPTION_MODELS = {
216
218
  "chatgpt-sub": "gpt-image-2.5-flare",
217
219
  "grok-sub": "grok-imagine-image-2.0",
218
- "google-sub": "gemini-3-pro-image"
220
+ "google-sub": "gemini-3.1-flash-image"
219
221
  };
220
222
  const DEFAULT_MODELS = {
221
223
  google: DEFAULT_GOOGLE_MODEL,
@@ -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",
@@ -1706,6 +1978,49 @@ window.__ModuleLoader__.load({
1706
1978
  const active = configured.find((profile) => profile.provider === activeProvider);
1707
1979
  return (active === void 0 ? configured : [active, ...configured.filter((profile) => profile.provider !== activeProvider)]).slice(0, 2).map((profile) => profile.provider);
1708
1980
  }
1981
+ const RATIO_SORT_ORDER = [
1982
+ "auto",
1983
+ "1:1",
1984
+ "3:2",
1985
+ "2:3",
1986
+ "4:3",
1987
+ "3:4",
1988
+ "4:5",
1989
+ "5:4",
1990
+ "16:9",
1991
+ "9:16",
1992
+ "21:9"
1993
+ ];
1994
+ const QUALITY_SORT_ORDER = [
1995
+ "auto",
1996
+ "standard",
1997
+ "1k",
1998
+ "1K",
1999
+ "2k",
2000
+ "2K",
2001
+ "3K",
2002
+ "4K",
2003
+ "low",
2004
+ "medium",
2005
+ "high",
2006
+ "xhigh",
2007
+ "max",
2008
+ "hd"
2009
+ ];
2010
+ /** Union of every configured profile's options, for the shared comparison pickers. */
2011
+ function comparisonOptionUnion(profiles, key) {
2012
+ const sortOrder = key === "ratioOptions" ? RATIO_SORT_ORDER : QUALITY_SORT_ORDER;
2013
+ const seen = /* @__PURE__ */ new Map();
2014
+ for (const profile of profiles) {
2015
+ if (!profile.configured) continue;
2016
+ for (const option of profile[key]) if (!seen.has(option.value)) seen.set(option.value, option);
2017
+ }
2018
+ const rank = (value) => {
2019
+ const index = sortOrder.indexOf(value);
2020
+ return index === -1 ? sortOrder.length : index;
2021
+ };
2022
+ return [...seen.values()].sort((a, b) => rank(a.value) - rank(b.value));
2023
+ }
1709
2024
  //#endregion
1710
2025
  //#region node_modules/.pnpm/@tldraw+utils@5.3.0/node_modules/@tldraw/utils/dist-esm/lib/version.mjs
1711
2026
  const TLDRAW_LIBRARY_VERSION_KEY = "__TLDRAW_LIBRARY_VERSIONS__";
@@ -115760,6 +116075,24 @@ img.ProseMirror-separator {
115760
116075
  }
115761
116076
  });
115762
116077
  }
116078
+ /**
116079
+ * Match tldraw's light/dark canvas to the DSH host theme. The host publishes
116080
+ * dark mode as the data-ds-dark-theme attribute on <body>; tldraw keeps its
116081
+ * own persisted user preference, so apply the host value now and observe
116082
+ * later flips. Returns the observer disposer.
116083
+ */
116084
+ function syncTldrawThemeWithHost(editor) {
116085
+ const apply = () => {
116086
+ editor.user.updateUserPreferences({ colorScheme: document.body.hasAttribute("data-ds-dark-theme") ? "dark" : "light" });
116087
+ };
116088
+ apply();
116089
+ const observer = new MutationObserver(apply);
116090
+ observer.observe(document.body, {
116091
+ attributes: true,
116092
+ attributeFilter: ["data-ds-dark-theme"]
116093
+ });
116094
+ return () => observer.disconnect();
116095
+ }
115763
116096
  //#endregion
115764
116097
  //#region lib/types/client/tl/canvas-selection.js
115765
116098
  /** Bound raster dimensions before exporting; large multi-image PNGs get a
@@ -116425,6 +116758,7 @@ img.ProseMirror-separator {
116425
116758
  editor.run(() => editor.selectNone());
116426
116759
  mountedEditors.add(editor);
116427
116760
  applyBrandTheme(editor);
116761
+ const stopThemeSync = syncTldrawThemeWithHost(editor);
116428
116762
  editor.updateInstanceState({ isGridMode: true });
116429
116763
  console.info(`[dsh-image-gen] tldraw mounted (instance ${editor.id})`);
116430
116764
  const stopLanding = registerTlLandingConsumer((items) => {
@@ -116460,6 +116794,7 @@ img.ProseMirror-separator {
116460
116794
  mountedEditors.delete(editor);
116461
116795
  mountedCanvases.delete(clear);
116462
116796
  stopLanding();
116797
+ stopThemeSync();
116463
116798
  stopSync();
116464
116799
  retrySync.current = () => {};
116465
116800
  };
@@ -116504,6 +116839,31 @@ img.ProseMirror-separator {
116504
116839
  unconfigured: "未配置",
116505
116840
  recent: "最近生成",
116506
116841
  empty: "暂无生成历史",
116842
+ railTabRecent: "最新生成",
116843
+ railTabFavorites: "收藏",
116844
+ favImages: "收藏图片",
116845
+ favPrompts: "收藏提示词",
116846
+ favNewFolder: "新建文件夹",
116847
+ favFolderPlaceholder: "文件夹名称",
116848
+ favCreate: "创建",
116849
+ favCancel: "取消",
116850
+ favMoveTo: "移动到文件夹",
116851
+ favNoFolder: "未分组",
116852
+ favMove: "移动",
116853
+ favDeleteFolder: "删除文件夹",
116854
+ favEditPrompt: "编辑提示词",
116855
+ favUse: "使用",
116856
+ favSave: "保存",
116857
+ favEditPromptPlaceholder: "输入提示词内容…",
116858
+ favFolder: "所属文件夹",
116859
+ favEmptyImages: "在图片操作区点「收藏」后,图片会显示在这里",
116860
+ favEmptyPrompts: "点提示词输入框旁的「☆ 收藏」后,提示词会显示在这里",
116861
+ favoritePrompt: "收藏提示词",
116862
+ favActionShort: "收藏",
116863
+ favPromptSaved: "已收藏提示词",
116864
+ favPromptApplied: "已填入提示词",
116865
+ favRefApplied: "已加入参考图",
116866
+ favSaveFailed: "收藏失败,请重试",
116507
116867
  generate: "文生图",
116508
116868
  edit: "图生图",
116509
116869
  reference: "参考图",
@@ -116517,6 +116877,8 @@ img.ProseMirror-separator {
116517
116877
  model: "Model",
116518
116878
  ratio: "比例",
116519
116879
  quality: "清晰度",
116880
+ subscriptionParameterHint: "所选比例会自动补充到生成提示词中;实际尺寸和清晰度仍由 ChatGPT 决定,请以生成结果为准。",
116881
+ subscriptionRatioMismatch: "ChatGPT 订阅未按所选比例出图,请查看实际尺寸。",
116520
116882
  start: "开始生成",
116521
116883
  generating: "正在生成…",
116522
116884
  cancelGenerate: "取消生成",
@@ -116560,6 +116922,8 @@ img.ProseMirror-separator {
116560
116922
  infiniteCanvas: "无限画布",
116561
116923
  loading: "正在读取图片…",
116562
116924
  generationFailed: "生成失败",
116925
+ gatewayTimeout: "请求被反向代理中断(超时):请重试;反复出现需调大代理超时或换直连网络",
116926
+ requestFailed: "请求失败(HTTP {status})",
116563
116927
  selectHistory: "从左侧选择一张图片,或在右侧开始新的生成。",
116564
116928
  created: "生成时间",
116565
116929
  elapsed: "耗时",
@@ -116567,6 +116931,7 @@ img.ProseMirror-separator {
116567
116931
  output: "输出参数",
116568
116932
  closeReference: "移除参考图",
116569
116933
  uploadInvalid: "请选择有效的图片文件(最大 10MB)",
116934
+ uploadUnreadable: "部分图片读取失败,请重新选择后再试",
116570
116935
  imageLoadFailed: "图片读取失败",
116571
116936
  fullscreen: "大图全屏",
116572
116937
  close: "关闭",
@@ -116613,6 +116978,31 @@ img.ProseMirror-separator {
116613
116978
  unconfigured: "Not configured",
116614
116979
  recent: "Recent generations",
116615
116980
  empty: "No generated images yet",
116981
+ railTabRecent: "Latest",
116982
+ railTabFavorites: "Favorites",
116983
+ favImages: "Favorite images",
116984
+ favPrompts: "Favorite prompts",
116985
+ favNewFolder: "New folder",
116986
+ favFolderPlaceholder: "Folder name",
116987
+ favCreate: "Create",
116988
+ favCancel: "Cancel",
116989
+ favMoveTo: "Move to folder",
116990
+ favNoFolder: "Unfiled",
116991
+ favMove: "Move",
116992
+ favDeleteFolder: "Delete folder",
116993
+ favEditPrompt: "Edit prompt",
116994
+ favUse: "Use",
116995
+ favSave: "Save",
116996
+ favEditPromptPlaceholder: "Prompt text…",
116997
+ favFolder: "Folder",
116998
+ favEmptyImages: "Click Favorite on an image; favorited images appear here",
116999
+ favEmptyPrompts: "Click ☆ Save beside the prompt box",
117000
+ favoritePrompt: "Save prompt",
117001
+ favActionShort: "Save",
117002
+ favPromptSaved: "Prompt saved",
117003
+ favPromptApplied: "Prompt applied",
117004
+ favRefApplied: "Reference added",
117005
+ favSaveFailed: "Could not save; please retry",
116616
117006
  generate: "Text to image",
116617
117007
  edit: "Image to image",
116618
117008
  reference: "Reference image",
@@ -116626,6 +117016,8 @@ img.ProseMirror-separator {
116626
117016
  model: "Model",
116627
117017
  ratio: "Aspect ratio",
116628
117018
  quality: "Quality",
117019
+ subscriptionParameterHint: "The selected aspect ratio is automatically added to the generation prompt. ChatGPT still determines the final dimensions and quality; check the generated result.",
117020
+ subscriptionRatioMismatch: "ChatGPT subscription returned a different aspect ratio. Check the actual dimensions.",
116629
117021
  start: "Generate",
116630
117022
  generating: "Generating…",
116631
117023
  cancelGenerate: "Cancel",
@@ -116669,6 +117061,8 @@ img.ProseMirror-separator {
116669
117061
  infiniteCanvas: "Infinite canvas",
116670
117062
  loading: "Loading image…",
116671
117063
  generationFailed: "Generation failed",
117064
+ gatewayTimeout: "The reverse proxy cut the request (timeout): retry; if it repeats, raise the proxy timeout or use a direct network.",
117065
+ requestFailed: "Request failed (HTTP {status})",
116672
117066
  selectHistory: "Select an image on the left, or start a new generation on the right.",
116673
117067
  created: "Created",
116674
117068
  elapsed: "Elapsed",
@@ -116676,6 +117070,7 @@ img.ProseMirror-separator {
116676
117070
  output: "Output",
116677
117071
  closeReference: "Remove reference",
116678
117072
  uploadInvalid: "Choose a valid image file up to 10MB.",
117073
+ uploadUnreadable: "Some images could not be read; pick them again.",
116679
117074
  imageLoadFailed: "Could not load image",
116680
117075
  fullscreen: "Fullscreen",
116681
117076
  close: "Close",
@@ -116719,17 +117114,28 @@ img.ProseMirror-separator {
116719
117114
  };
116720
117115
  /** localStorage flag remembering that the one-time infinite-canvas hint was dismissed. */
116721
117116
  const INFINITE_CANVAS_HINT_KEY = "dsh-ig-infinite-canvas-hint-dismissed";
116722
- const StudioView = ({ locale, credentialEvents, workspace, initialPrompt, initialCanvasSurface, showInfiniteCanvasHint, onInitialPromptApplied, onOpenInspiration }) => {
117117
+ const StudioView = ({ locale, credentialEvents, workspace, initialPrompt, initialReference, initialCanvasSurface, showInfiniteCanvasHint, onInitialPromptApplied, onInitialReferenceApplied, onOpenInspiration }) => {
116723
117118
  const [lang, setLang] = (0, react.useState)(() => locale?.getSnapshot?.().active?.startsWith("en") ? "en" : "zh");
116724
117119
  const [config, setConfig] = (0, react.useState)(null);
116725
117120
  const [configLoading, setConfigLoading] = (0, react.useState)(true);
116726
117121
  const [configError, setConfigError] = (0, react.useState)(null);
116727
117122
  const [items, setItems] = (0, react.useState)([]);
117123
+ /** Which rail tab is showing: generation history or the favorites rail. */
117124
+ const [railTab, setRailTab] = (0, react.useState)("recent");
117125
+ const [favPrompts, setFavPrompts] = (0, react.useState)([]);
117126
+ const [favFolders, setFavFolders] = (0, react.useState)([]);
117127
+ const [activePromptFolder, setActivePromptFolder] = (0, react.useState)("");
117128
+ const [folderDraft, setFolderDraft] = (0, react.useState)(null);
117129
+ const [folderDraftName, setFolderDraftName] = (0, react.useState)("");
117130
+ const [editingPrompt, setEditingPrompt] = (0, react.useState)(null);
117131
+ const [editPromptText, setEditPromptText] = (0, react.useState)("");
117132
+ const [editPromptFolder, setEditPromptFolder] = (0, react.useState)("");
117133
+ const [movingFav, setMovingFav] = (0, react.useState)(null);
116728
117134
  const [sidebarCollapsed, setSidebarCollapsed] = (0, react.useState)(false);
116729
117135
  /** Manual-only collapse for the right generate form; the form stays put in
116730
117136
  * narrow seats because it is the primary generation surface. Starts folded
116731
117137
  * when the studio opens straight into the infinite canvas. */
116732
- const [generateCollapsed, setGenerateCollapsed] = (0, react.useState)(initialCanvasSurface === "infinite");
117138
+ const [generateCollapsed, setGenerateCollapsed] = (0, react.useState)(initialCanvasSurface === "infinite" && initialPrompt === void 0);
116733
117139
  /** Workbench root, observed so a narrow column (right sidebar) can fold the recent rail. */
116734
117140
  const workbenchRootRef = (0, react.useRef)(null);
116735
117141
  /** Set once the user toggles the rail by hand; auto-folding then stands down for this mount. */
@@ -116826,13 +117232,20 @@ img.ProseMirror-separator {
116826
117232
  const maxReferences = comparisonEnabled ? comparisonProviders.includes("dashscope") ? 3 : 5 : provider === "dashscope" ? 3 : 5;
116827
117233
  const referencesRef = (0, react.useRef)(references);
116828
117234
  referencesRef.current = references;
116829
- const comparisonSelectionsAllSubscription = comparisonEnabled && comparisonProviders.length > 0 && comparisonProviders.every((provider) => isSubscriptionProvider(provider));
116830
- const singleProviderIsSubscription = !comparisonEnabled && isSubscriptionProvider(provider);
116831
117235
  const t = (key, values) => {
116832
117236
  let text = COPY$1[lang][key];
116833
117237
  for (const [name, value] of Object.entries(values ?? {})) text = text.replace(`{${name}}`, value);
116834
117238
  return text;
116835
117239
  };
117240
+ /** Parse a studio response body; a gateway HTML error page (502/504 from a
117241
+ * reverse proxy) must surface as a readable message, not a JSON SyntaxError. */
117242
+ const readStudioBody = async (response) => {
117243
+ try {
117244
+ return await response.json();
117245
+ } catch {
117246
+ return { error: response.status === 502 || response.status === 504 ? t("gatewayTimeout") : t("requestFailed", { status: String(response.status) }) };
117247
+ }
117248
+ };
116836
117249
  (0, react.useEffect)(() => locale?.subscribe?.(() => setLang(locale.getSnapshot().active?.startsWith("en") ? "en" : "zh")), [locale]);
116837
117250
  const formStateRef = (0, react.useRef)({
116838
117251
  provider,
@@ -116854,7 +117267,7 @@ img.ProseMirror-separator {
116854
117267
  signal: controller.signal,
116855
117268
  credentials: "same-origin"
116856
117269
  });
116857
- const payload = await response.json();
117270
+ const payload = await readStudioBody(response);
116858
117271
  if (!response.ok || !("providers" in payload)) throw new Error("error" in payload && payload.error ? payload.error : "Studio unavailable");
116859
117272
  setConfig(payload);
116860
117273
  if (silent) {
@@ -116900,6 +117313,20 @@ img.ProseMirror-separator {
116900
117313
  unsubscribe();
116901
117314
  };
116902
117315
  }, []);
117316
+ (0, react.useEffect)(() => {
117317
+ let mounted = true;
117318
+ const load = () => void Promise.all([getFavoritePrompts(), getFavoriteFolders()]).then(([prompts, folders]) => {
117319
+ if (!mounted) return;
117320
+ setFavPrompts(prompts);
117321
+ setFavFolders(folders);
117322
+ });
117323
+ load();
117324
+ const unsubscribe = subscribeFavorites(load);
117325
+ return () => {
117326
+ mounted = false;
117327
+ unsubscribe();
117328
+ };
117329
+ }, []);
116903
117330
  (0, react.useEffect)(() => {
116904
117331
  return () => {
116905
117332
  requestRef.current?.abort();
@@ -116967,6 +117394,8 @@ img.ProseMirror-separator {
116967
117394
  ratio,
116968
117395
  quality
116969
117396
  ]);
117397
+ const comparisonRatioOpts = (0, react.useMemo)(() => localizeRatioOptions(comparisonOptionUnion(comparisonProfiles, "ratioOptions"), lang), [comparisonProfiles, lang]);
117398
+ const comparisonQualityOpts = (0, react.useMemo)(() => localizeQualityOptions(comparisonOptionUnion(comparisonProfiles, "qualityOptions"), lang), [comparisonProfiles, lang]);
116970
117399
  const applyProvider = (profile) => {
116971
117400
  setProvider(profile.provider);
116972
117401
  setModel(profile.model);
@@ -117024,7 +117453,65 @@ img.ProseMirror-separator {
117024
117453
  };
117025
117454
  };
117026
117455
  const handleImageClick = () => {};
117027
- const addReferenceFiles = (fileList) => {
117456
+ /** Keep the current prompt text in the favorites rail. */
117457
+ const handleFavoritePrompt = async () => {
117458
+ if (prompt.trim().length === 0) {
117459
+ setError(t("needPrompt"));
117460
+ return;
117461
+ }
117462
+ if (await saveFavoritePrompt(prompt)) {
117463
+ railToggledByUserRef.current = true;
117464
+ setSidebarCollapsed(false);
117465
+ setRailTab("favorites");
117466
+ flash(t("favPromptSaved"));
117467
+ } else setError(t("favSaveFailed"));
117468
+ };
117469
+ /** Turn one favorited gallery image back into a live reference (edit mode). */
117470
+ const applyFavoriteImage = async (item) => {
117471
+ const max = maxReferences;
117472
+ if (referencesRef.current.length >= max) {
117473
+ setError(t("maxReferencesExceeded", { max: String(max) }));
117474
+ return;
117475
+ }
117476
+ try {
117477
+ const blob = await fetchAttachmentBlob(item.attachment);
117478
+ const newItem = {
117479
+ id: `fav-${item.id}-${Date.now()}`,
117480
+ attachment: item.attachment,
117481
+ previewUrl: URL.createObjectURL(blob)
117482
+ };
117483
+ const next = [...referencesRef.current, newItem];
117484
+ setReferences(next);
117485
+ referencesRef.current = next;
117486
+ setMode("edit");
117487
+ setError(null);
117488
+ flash(t("favRefApplied"));
117489
+ } catch {
117490
+ setError(t("imageLoadFailed"));
117491
+ }
117492
+ };
117493
+ const createFavFolder = async () => {
117494
+ if (await addFavoriteFolder("prompt", folderDraftName) === null) {
117495
+ flash(t("favSaveFailed"));
117496
+ return;
117497
+ }
117498
+ setFolderDraft(null);
117499
+ setFolderDraftName("");
117500
+ };
117501
+ const openPromptEditor = (fav) => {
117502
+ setEditingPrompt(fav);
117503
+ setEditPromptText(fav.text);
117504
+ setEditPromptFolder(fav.folderId ?? "");
117505
+ };
117506
+ const doMoveFavorite = (folderId) => {
117507
+ if (movingFav === null) return;
117508
+ moveFavoritePrompt(movingFav.id, folderId);
117509
+ };
117510
+ const applyFavoritePrompt = (fav) => {
117511
+ setPrompt(fav.text);
117512
+ flash(t("favPromptApplied"));
117513
+ };
117514
+ const addReferenceFiles = async (fileList) => {
117028
117515
  if (!fileList || fileList.length === 0) return;
117029
117516
  const files = Array.from(fileList);
117030
117517
  const validFiles = [];
@@ -117050,13 +117537,31 @@ img.ProseMirror-separator {
117050
117537
  const toAdd = validFiles.slice(0, available);
117051
117538
  if (validFiles.length > available) setError(t("maxReferencesExceeded", { max: String(maxReferences) }));
117052
117539
  else setError(null);
117053
- const newItems = toAdd.map((file, idx) => ({
117540
+ const snapshotted = [];
117541
+ for (const file of toAdd) try {
117542
+ const bytes = await file.arrayBuffer();
117543
+ snapshotted.push(new File([bytes], file.name, {
117544
+ type: file.type,
117545
+ lastModified: file.lastModified
117546
+ }));
117547
+ } catch {
117548
+ setError(t("uploadUnreadable"));
117549
+ }
117550
+ const newItems = snapshotted.map((file, idx) => ({
117054
117551
  id: `upload-${Date.now()}-${idx}-${Math.random().toString(36).slice(2, 7)}`,
117055
117552
  file,
117056
117553
  previewUrl: URL.createObjectURL(file)
117057
117554
  }));
117058
117555
  setReferences((prev) => [...prev, ...newItems]);
117059
117556
  };
117557
+ const appliedReferenceRef = (0, react.useRef)(null);
117558
+ (0, react.useEffect)(() => {
117559
+ if (initialReference === void 0 || appliedReferenceRef.current === initialReference) return;
117560
+ appliedReferenceRef.current = initialReference;
117561
+ addReferenceFiles([initialReference]);
117562
+ setMode("edit");
117563
+ onInitialReferenceApplied?.();
117564
+ }, [initialReference, onInitialReferenceApplied]);
117060
117565
  const removeReference = (id) => {
117061
117566
  const target = referencesRef.current.find((item) => item.id === id);
117062
117567
  if (target && target.previewUrl.startsWith("blob:")) URL.revokeObjectURL(target.previewUrl);
@@ -117188,7 +117693,7 @@ img.ProseMirror-separator {
117188
117693
  ...referencesPayload === void 0 ? {} : { references: referencesPayload }
117189
117694
  })
117190
117695
  });
117191
- const payload = await response.json();
117696
+ const payload = await readStudioBody(response);
117192
117697
  if (!response.ok || !("attachment" in payload)) throw new Error("error" in payload && payload.error ? payload.error : `${target.profile.label}: ${t("generationFailed")}`);
117193
117698
  return {
117194
117699
  payload,
@@ -117232,6 +117737,7 @@ img.ProseMirror-separator {
117232
117737
  success: String(successes.length),
117233
117738
  failed: String(failed)
117234
117739
  }));
117740
+ else if (galleryEntries.some(hasSubscriptionRatioMismatch)) flash(t("subscriptionRatioMismatch"));
117235
117741
  return;
117236
117742
  }
117237
117743
  const response = await fetch(STUDIO_ROUTE, {
@@ -117251,7 +117757,7 @@ img.ProseMirror-separator {
117251
117757
  ...referencesPayload === void 0 ? {} : { references: referencesPayload }
117252
117758
  })
117253
117759
  });
117254
- const payload = await response.json();
117760
+ const payload = await readStudioBody(response);
117255
117761
  if (!response.ok || !("attachment" in payload)) throw new Error("error" in payload && payload.error ? payload.error : t("generationFailed"));
117256
117762
  const generatedList = Array.isArray(payload.items) && payload.items.length > 0 ? payload.items : [{
117257
117763
  attachment: payload.attachment,
@@ -117295,6 +117801,7 @@ img.ProseMirror-separator {
117295
117801
  success: String(generatedList.length),
117296
117802
  failed: String(payload.failedCount)
117297
117803
  }));
117804
+ else if (galleryEntries.some(hasSubscriptionRatioMismatch)) flash(t("subscriptionRatioMismatch"));
117298
117805
  } catch (submitError) {
117299
117806
  if (controller.signal.aborted) return;
117300
117807
  setError(messageOf(submitError));
@@ -117323,8 +117830,8 @@ img.ProseMirror-separator {
117323
117830
  selected,
117324
117831
  selectedBatchItems
117325
117832
  ]);
117326
- const saveButtonLabel = currentBatch !== null ? pendingGalleryItems.length > 0 ? t("saveSelected", { count: String(pendingGalleryItems.length) }) : selectedBatchItems.length === 0 ? t("saveSelected", { count: "0" }) : t("inGallery") : isSelectedInGallery ? t("inGallery") : t("saveToGallery");
117327
- const saveSelectionComplete = currentBatch === null ? isSelectedInGallery : selectedBatchItems.length > 0 && pendingGalleryItems.length === 0;
117833
+ 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");
117834
+ const saveSelectionComplete = currentBatch === null ? pendingGalleryItems.length === 0 : selectedBatchItems.length > 0 && pendingGalleryItems.length === 0;
117328
117835
  const saveGalleryEntry = async (item) => {
117329
117836
  let savedTo = item.savedTo;
117330
117837
  const targetRoot = workspace?.path || config?.workspaceRoot;
@@ -117504,44 +118011,272 @@ img.ProseMirror-separator {
117504
118011
  children: [
117505
118012
  !sidebarCollapsed && (0, react_jsx_runtime.jsxs)("aside", {
117506
118013
  className: "dsh-ig-recent-panel",
117507
- children: [(0, react_jsx_runtime.jsxs)("div", {
117508
- className: "dsh-ig-panel-title",
117509
- children: [(0, react_jsx_runtime.jsxs)("div", {
117510
- style: {
117511
- display: "flex",
117512
- alignItems: "center",
117513
- gap: "8px"
117514
- },
117515
- children: [(0, react_jsx_runtime.jsx)("span", { children: t("recent") }), (0, react_jsx_runtime.jsx)("span", {
117516
- className: "dsh-ig-count-badge",
117517
- children: items.length
118014
+ children: [
118015
+ (0, react_jsx_runtime.jsxs)("div", {
118016
+ className: "dsh-ig-panel-title",
118017
+ children: [(0, react_jsx_runtime.jsxs)("div", {
118018
+ style: {
118019
+ display: "flex",
118020
+ alignItems: "center",
118021
+ gap: "8px"
118022
+ },
118023
+ children: [(0, react_jsx_runtime.jsx)("span", { children: railTab === "recent" ? t("recent") : t("railTabFavorites") }), (0, react_jsx_runtime.jsx)("span", {
118024
+ className: "dsh-ig-count-badge",
118025
+ children: railTab === "recent" ? items.length : items.filter((item) => item.isFavorite).length + favPrompts.length
118026
+ })]
118027
+ }), (0, react_jsx_runtime.jsx)("button", {
118028
+ type: "button",
118029
+ className: "dsh-ig-collapse-btn",
118030
+ onClick: () => {
118031
+ railToggledByUserRef.current = true;
118032
+ setSidebarCollapsed(true);
118033
+ },
118034
+ title: t("collapseSidebar"),
118035
+ children: (0, react_jsx_runtime.jsx)(PanelLeftClose, { size: 15 })
117518
118036
  })]
117519
- }), (0, react_jsx_runtime.jsx)("button", {
117520
- type: "button",
117521
- className: "dsh-ig-collapse-btn",
117522
- onClick: () => {
117523
- railToggledByUserRef.current = true;
117524
- setSidebarCollapsed(true);
117525
- },
117526
- title: t("collapseSidebar"),
117527
- children: (0, react_jsx_runtime.jsx)(PanelLeftClose, { size: 15 })
117528
- })]
117529
- }), (0, react_jsx_runtime.jsxs)("div", {
117530
- className: "dsh-ig-recent-scroll",
117531
- children: [items.length === 0 ? (0, react_jsx_runtime.jsxs)("div", {
117532
- className: "dsh-ig-recent-empty",
117533
- children: [(0, react_jsx_runtime.jsx)(ImagePlus, { size: 22 }), (0, react_jsx_runtime.jsx)("span", { children: t("empty") })]
117534
- }) : displayItems.map((item) => (0, react_jsx_runtime.jsx)(RecentItem, {
117535
- item,
117536
- active: selected?.id === item.id,
117537
- onClick: () => selectItem(item)
117538
- }, item.id)), items.length > visibleLimit && (0, react_jsx_runtime.jsx)("button", {
117539
- type: "button",
117540
- className: "dsh-ig-load-more",
117541
- onClick: () => setVisibleLimit((l) => l + 30),
117542
- children: t("loadMore", { n: String(items.length - visibleLimit) })
117543
- })]
117544
- })]
118037
+ }),
118038
+ (0, react_jsx_runtime.jsxs)("div", {
118039
+ className: "dsh-ig-rail-tabs",
118040
+ role: "tablist",
118041
+ children: [(0, react_jsx_runtime.jsx)("button", {
118042
+ type: "button",
118043
+ role: "tab",
118044
+ "aria-selected": railTab === "recent",
118045
+ className: `dsh-ig-rail-tab ${railTab === "recent" ? "is-active" : ""}`,
118046
+ onClick: () => setRailTab("recent"),
118047
+ children: t("railTabRecent")
118048
+ }), (0, react_jsx_runtime.jsx)("button", {
118049
+ type: "button",
118050
+ role: "tab",
118051
+ "aria-selected": railTab === "favorites",
118052
+ className: `dsh-ig-rail-tab ${railTab === "favorites" ? "is-active" : ""}`,
118053
+ onClick: () => setRailTab("favorites"),
118054
+ children: t("railTabFavorites")
118055
+ })]
118056
+ }),
118057
+ railTab === "recent" ? (0, react_jsx_runtime.jsxs)("div", {
118058
+ className: "dsh-ig-recent-scroll",
118059
+ children: [items.length === 0 ? (0, react_jsx_runtime.jsxs)("div", {
118060
+ className: "dsh-ig-recent-empty",
118061
+ children: [(0, react_jsx_runtime.jsx)(ImagePlus, { size: 22 }), (0, react_jsx_runtime.jsx)("span", { children: t("empty") })]
118062
+ }) : displayItems.map((item) => (0, react_jsx_runtime.jsx)(RecentItem, {
118063
+ item,
118064
+ active: selected?.id === item.id,
118065
+ onClick: () => selectItem(item)
118066
+ }, item.id)), items.length > visibleLimit && (0, react_jsx_runtime.jsx)("button", {
118067
+ type: "button",
118068
+ className: "dsh-ig-load-more",
118069
+ onClick: () => setVisibleLimit((l) => l + 30),
118070
+ children: t("loadMore", { n: String(items.length - visibleLimit) })
118071
+ })]
118072
+ }) : (0, react_jsx_runtime.jsxs)("div", {
118073
+ className: "dsh-ig-fav-scroll",
118074
+ children: [
118075
+ (0, react_jsx_runtime.jsx)("div", {
118076
+ className: "dsh-ig-fav-section-label",
118077
+ children: (0, react_jsx_runtime.jsx)("span", { children: t("favImages") })
118078
+ }),
118079
+ (() => {
118080
+ const list = items.filter((item) => item.isFavorite);
118081
+ if (list.length === 0) return (0, react_jsx_runtime.jsxs)("div", {
118082
+ className: "dsh-ig-fav-empty",
118083
+ children: [(0, react_jsx_runtime.jsx)(ImagePlus, { size: 18 }), (0, react_jsx_runtime.jsx)("span", { children: t("favEmptyImages") })]
118084
+ });
118085
+ return (0, react_jsx_runtime.jsx)("div", {
118086
+ className: "dsh-ig-fav-grid",
118087
+ children: list.map((item) => (0, react_jsx_runtime.jsx)(RecentItem, {
118088
+ item,
118089
+ active: false,
118090
+ onClick: () => void applyFavoriteImage(item)
118091
+ }, item.id))
118092
+ });
118093
+ })(),
118094
+ (0, react_jsx_runtime.jsxs)("div", {
118095
+ className: "dsh-ig-fav-section-label",
118096
+ children: [(0, react_jsx_runtime.jsx)("span", { children: t("favPrompts") }), (0, react_jsx_runtime.jsx)("button", {
118097
+ type: "button",
118098
+ className: "dsh-ig-fav-folder-add",
118099
+ onClick: () => {
118100
+ setFolderDraft({ kind: "prompt" });
118101
+ setFolderDraftName("");
118102
+ },
118103
+ title: t("favNewFolder"),
118104
+ children: "+"
118105
+ })]
118106
+ }),
118107
+ (0, react_jsx_runtime.jsx)(FavoriteFolderBar, {
118108
+ folders: favFolders.filter((folder) => folder.kind === "prompt"),
118109
+ active: activePromptFolder,
118110
+ onSelect: (id) => setActivePromptFolder(id === activePromptFolder ? "" : id),
118111
+ onDelete: (id) => void deleteFavoriteFolder(id),
118112
+ deleteLabel: t("favDeleteFolder")
118113
+ }),
118114
+ folderDraft?.kind === "prompt" && (0, react_jsx_runtime.jsxs)("div", {
118115
+ className: "dsh-ig-fav-folder-draft",
118116
+ children: [
118117
+ (0, react_jsx_runtime.jsx)("input", {
118118
+ value: folderDraftName,
118119
+ placeholder: t("favFolderPlaceholder"),
118120
+ onChange: (e) => setFolderDraftName(e.target.value),
118121
+ onKeyDown: (e) => {
118122
+ if (e.key === "Enter") createFavFolder();
118123
+ }
118124
+ }),
118125
+ (0, react_jsx_runtime.jsx)("button", {
118126
+ type: "button",
118127
+ onClick: () => void createFavFolder(),
118128
+ children: t("favCreate")
118129
+ }),
118130
+ (0, react_jsx_runtime.jsx)("button", {
118131
+ type: "button",
118132
+ onClick: () => setFolderDraft(null),
118133
+ children: t("favCancel")
118134
+ })
118135
+ ]
118136
+ }),
118137
+ (() => {
118138
+ const list = favPrompts.filter((fav) => activePromptFolder === "" || fav.folderId === activePromptFolder);
118139
+ if (list.length === 0) return (0, react_jsx_runtime.jsxs)("div", {
118140
+ className: "dsh-ig-fav-empty",
118141
+ children: [(0, react_jsx_runtime.jsx)(PencilLine, { size: 18 }), (0, react_jsx_runtime.jsx)("span", { children: t("favEmptyPrompts") })]
118142
+ });
118143
+ return (0, react_jsx_runtime.jsx)("div", {
118144
+ className: "dsh-ig-fav-prompts",
118145
+ children: list.map((fav) => (0, react_jsx_runtime.jsxs)("div", {
118146
+ className: "dsh-ig-fav-prompt",
118147
+ children: [(0, react_jsx_runtime.jsx)("button", {
118148
+ type: "button",
118149
+ className: "dsh-ig-fav-prompt-text",
118150
+ title: fav.text,
118151
+ onClick: () => openPromptEditor(fav),
118152
+ children: fav.text
118153
+ }), (0, react_jsx_runtime.jsx)("button", {
118154
+ type: "button",
118155
+ className: "dsh-ig-fav-del",
118156
+ title: t("remove"),
118157
+ onClick: () => void deleteFavoritePrompt(fav.id),
118158
+ children: (0, react_jsx_runtime.jsx)(X, { size: 11 })
118159
+ })]
118160
+ }, fav.id))
118161
+ });
118162
+ })()
118163
+ ]
118164
+ })
118165
+ ]
118166
+ }),
118167
+ (editingPrompt !== null || movingFav !== null) && (0, react_jsx_runtime.jsx)("div", {
118168
+ className: "dsh-ig-fav-dialog-wrap",
118169
+ onClick: () => {
118170
+ setEditingPrompt(null);
118171
+ setMovingFav(null);
118172
+ },
118173
+ children: (0, react_jsx_runtime.jsxs)("div", {
118174
+ className: "dsh-ig-fav-dialog",
118175
+ onClick: (e) => e.stopPropagation(),
118176
+ children: [editingPrompt !== null && (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
118177
+ (0, react_jsx_runtime.jsx)("div", {
118178
+ className: "dsh-ig-fav-dialog-title",
118179
+ children: t("favEditPrompt")
118180
+ }),
118181
+ (0, react_jsx_runtime.jsx)("textarea", {
118182
+ className: "dsh-ig-fav-dialog-textarea",
118183
+ value: editPromptText,
118184
+ placeholder: t("favEditPromptPlaceholder"),
118185
+ onChange: (e) => setEditPromptText(e.target.value),
118186
+ rows: 5
118187
+ }),
118188
+ (0, react_jsx_runtime.jsxs)("label", {
118189
+ className: "dsh-ig-fav-dialog-folder-label",
118190
+ children: [t("favFolder"), (0, react_jsx_runtime.jsxs)("select", {
118191
+ value: editPromptFolder,
118192
+ onChange: (e) => setEditPromptFolder(e.target.value),
118193
+ children: [(0, react_jsx_runtime.jsx)("option", {
118194
+ value: "",
118195
+ children: t("favNoFolder")
118196
+ }), favFolders.filter((folder) => folder.kind === "prompt").map((folder) => (0, react_jsx_runtime.jsx)("option", {
118197
+ value: folder.id,
118198
+ children: folder.name
118199
+ }, folder.id))]
118200
+ })]
118201
+ }),
118202
+ (0, react_jsx_runtime.jsxs)("div", {
118203
+ className: "dsh-ig-fav-dialog-actions",
118204
+ children: [
118205
+ (0, react_jsx_runtime.jsx)("button", {
118206
+ type: "button",
118207
+ className: "is-primary",
118208
+ onClick: () => {
118209
+ updateFavoritePrompt(editingPrompt.id, editPromptText).then((ok) => {
118210
+ if (ok) moveFavoritePrompt(editingPrompt.id, editPromptFolder === "" ? void 0 : editPromptFolder);
118211
+ });
118212
+ setEditingPrompt(null);
118213
+ },
118214
+ children: t("favSave")
118215
+ }),
118216
+ (0, react_jsx_runtime.jsx)("button", {
118217
+ type: "button",
118218
+ onClick: () => {
118219
+ applyFavoritePrompt({
118220
+ ...editingPrompt,
118221
+ text: editPromptText
118222
+ });
118223
+ setEditingPrompt(null);
118224
+ },
118225
+ children: t("favUse")
118226
+ }),
118227
+ (0, react_jsx_runtime.jsx)("button", {
118228
+ type: "button",
118229
+ onClick: () => {
118230
+ setMovingFav({
118231
+ kind: "prompt",
118232
+ id: editingPrompt.id,
118233
+ folderId: editingPrompt.folderId
118234
+ });
118235
+ setEditingPrompt(null);
118236
+ },
118237
+ children: t("favMove")
118238
+ }),
118239
+ (0, react_jsx_runtime.jsx)("button", {
118240
+ type: "button",
118241
+ onClick: () => setEditingPrompt(null),
118242
+ children: t("favCancel")
118243
+ })
118244
+ ]
118245
+ })
118246
+ ] }), editingPrompt === null && movingFav !== null && (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
118247
+ (0, react_jsx_runtime.jsx)("div", {
118248
+ className: "dsh-ig-fav-dialog-title",
118249
+ children: t("favMoveTo")
118250
+ }),
118251
+ (0, react_jsx_runtime.jsxs)("div", {
118252
+ className: "dsh-ig-fav-dialog-folders",
118253
+ children: [(0, react_jsx_runtime.jsx)("button", {
118254
+ type: "button",
118255
+ onClick: () => {
118256
+ doMoveFavorite(void 0);
118257
+ setMovingFav(null);
118258
+ },
118259
+ children: t("favNoFolder")
118260
+ }), favFolders.filter((folder) => folder.kind === movingFav.kind).map((folder) => (0, react_jsx_runtime.jsx)("button", {
118261
+ type: "button",
118262
+ className: movingFav.folderId === folder.id ? "is-current" : "",
118263
+ onClick: () => {
118264
+ doMoveFavorite(folder.id);
118265
+ setMovingFav(null);
118266
+ },
118267
+ children: folder.name
118268
+ }, folder.id))]
118269
+ }),
118270
+ (0, react_jsx_runtime.jsx)("div", {
118271
+ className: "dsh-ig-fav-dialog-actions",
118272
+ children: (0, react_jsx_runtime.jsx)("button", {
118273
+ type: "button",
118274
+ onClick: () => setMovingFav(null),
118275
+ children: t("favCancel")
118276
+ })
118277
+ })
118278
+ ] })]
118279
+ })
117545
118280
  }),
117546
118281
  (0, react_jsx_runtime.jsxs)("main", {
117547
118282
  className: "dsh-ig-canvas-column",
@@ -117819,7 +118554,8 @@ img.ProseMirror-separator {
117819
118554
  (0, react_jsx_runtime.jsx)("span", { children: formatRelativeTime$1(selected.createdAt, lang) }),
117820
118555
  (0, react_jsx_runtime.jsx)("span", { children: selected.provider }),
117821
118556
  (0, react_jsx_runtime.jsx)("span", { children: selected.model }),
117822
- (0, react_jsx_runtime.jsx)("span", { children: selected.attachment.width && selected.attachment.height ? `${selected.attachment.width} × ${selected.attachment.height}` : "—" })
118557
+ (0, react_jsx_runtime.jsx)("span", { children: selected.provider === "chatgpt-sub" ? selected.output : selected.attachment.width && selected.attachment.height ? `${selected.attachment.width} × ${selected.attachment.height}` : "—" }),
118558
+ hasSubscriptionRatioMismatch(selected) && (0, react_jsx_runtime.jsx)("span", { children: t("subscriptionRatioMismatch") })
117823
118559
  ]
117824
118560
  })
117825
118561
  ] })
@@ -117938,21 +118674,30 @@ img.ProseMirror-separator {
117938
118674
  " ",
117939
118675
  (0, react_jsx_runtime.jsx)("b", { children: "*" })
117940
118676
  ]
117941
- }), (0, react_jsx_runtime.jsxs)("span", { children: [(0, react_jsx_runtime.jsxs)("button", {
117942
- type: "button",
117943
- className: "dsh-ig-find-inspiration",
117944
- onClick: onOpenInspiration,
117945
- children: [(0, react_jsx_runtime.jsx)(Sparkles, { size: 11 }), t("findInspiration")]
117946
- }), (0, react_jsx_runtime.jsx)("button", {
117947
- type: "button",
117948
- onClick: () => setPrompt(""),
117949
- children: t("clear")
117950
- })] })]
118677
+ }), (0, react_jsx_runtime.jsxs)("span", { children: [
118678
+ (0, react_jsx_runtime.jsxs)("button", {
118679
+ type: "button",
118680
+ className: "dsh-ig-find-inspiration",
118681
+ onClick: onOpenInspiration,
118682
+ children: [(0, react_jsx_runtime.jsx)(Sparkles, { size: 11 }), t("findInspiration")]
118683
+ }),
118684
+ (0, react_jsx_runtime.jsxs)("button", {
118685
+ type: "button",
118686
+ className: "dsh-ig-fav-save-btn",
118687
+ onClick: () => void handleFavoritePrompt(),
118688
+ title: t("favoritePrompt"),
118689
+ children: [(0, react_jsx_runtime.jsx)(Star, { size: 12 }), t("favActionShort")]
118690
+ }),
118691
+ (0, react_jsx_runtime.jsx)("button", {
118692
+ type: "button",
118693
+ onClick: () => setPrompt(""),
118694
+ children: t("clear")
118695
+ })
118696
+ ] })]
117951
118697
  }),
117952
118698
  (0, react_jsx_runtime.jsx)("textarea", {
117953
118699
  id: "dsh-ig-prompt",
117954
118700
  value: prompt,
117955
- maxLength: 2e3,
117956
118701
  onChange: (event) => setPrompt(event.target.value),
117957
118702
  onKeyDown: (event) => {
117958
118703
  if (event.nativeEvent.isComposing) return;
@@ -117963,7 +118708,7 @@ img.ProseMirror-separator {
117963
118708
  },
117964
118709
  placeholder: t("promptPlaceholder")
117965
118710
  }),
117966
- (0, react_jsx_runtime.jsxs)("small", { children: [prompt.length, "/2000"] })
118711
+ (0, react_jsx_runtime.jsx)("small", { children: prompt.length })
117967
118712
  ]
117968
118713
  }),
117969
118714
  config === null ? configLoading ? (0, react_jsx_runtime.jsx)("div", {
@@ -117994,82 +118739,70 @@ img.ProseMirror-separator {
117994
118739
  onClick: () => toggleComparison(true),
117995
118740
  children: [(0, react_jsx_runtime.jsx)(Copy, { size: 13 }), t("compareModels")]
117996
118741
  })]
117997
- }), comparisonEnabled ? (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsxs)("div", {
117998
- className: "dsh-ig-compare-box",
117999
- children: [
118000
- (0, react_jsx_runtime.jsxs)("div", {
118001
- className: "dsh-ig-compare-heading",
118002
- children: [(0, react_jsx_runtime.jsxs)("div", { children: [(0, react_jsx_runtime.jsx)("strong", { children: t("compareSelect") }), (0, react_jsx_runtime.jsx)("small", { children: t("compareHint") })] }), (0, react_jsx_runtime.jsx)("span", { children: t("compareSelected", { count: String(comparisonTargets.length) }) })]
118003
- }),
118004
- (0, react_jsx_runtime.jsx)("div", {
118005
- className: "dsh-ig-model-checks",
118006
- children: comparisonProfiles.map((item) => {
118007
- const target = comparisonTargets.find((candidate) => candidate.profile.provider === item.provider);
118008
- return (0, react_jsx_runtime.jsxs)("button", {
118009
- type: "button",
118010
- className: target ? "is-selected" : "",
118011
- "aria-pressed": Boolean(target),
118012
- onClick: () => toggleComparisonProvider(item.provider),
118013
- children: [
118014
- (0, react_jsx_runtime.jsx)("span", {
118015
- className: "dsh-ig-model-checkmark",
118016
- children: target ? (0, react_jsx_runtime.jsx)(Check, { size: 12 }) : null
118017
- }),
118018
- (0, react_jsx_runtime.jsxs)("span", {
118019
- className: "dsh-ig-model-copy",
118020
- children: [(0, react_jsx_runtime.jsx)("strong", { children: item.label }), (0, react_jsx_runtime.jsx)("small", {
118021
- title: item.model,
118022
- children: item.model
118023
- })]
118024
- }),
118025
- target && (0, react_jsx_runtime.jsxs)("span", {
118026
- className: "dsh-ig-model-settings",
118027
- children: [
118028
- target.ratio,
118029
- " · ",
118030
- target.quality,
118031
- target.adjusted ? (0, react_jsx_runtime.jsx)("em", { children: t("compareAdjusted") }) : null
118032
- ]
118033
- })
118034
- ]
118035
- }, item.provider);
118036
- })
118037
- }),
118038
- (0, react_jsx_runtime.jsx)("p", { children: t("compareParameterHint") })
118039
- ]
118040
- }), comparisonSelectionsAllSubscription ? (0, react_jsx_runtime.jsxs)("div", {
118041
- className: "dsh-ig-field-grid",
118042
- children: [(0, react_jsx_runtime.jsx)(FieldSelect, {
118043
- label: t("ratio"),
118044
- value: "auto",
118045
- onChange: () => {},
118046
- options: [{
118047
- value: "auto",
118048
- label: lang === "en" ? "Channel default" : "通道默认"
118049
- }]
118050
- }), (0, react_jsx_runtime.jsx)(FieldSelect, {
118051
- label: t("quality"),
118052
- value: "auto",
118053
- onChange: () => {},
118054
- options: [{
118055
- value: "auto",
118056
- label: lang === "en" ? "Channel default" : "通道默认"
118057
- }]
118058
- })]
118059
- }) : (0, react_jsx_runtime.jsxs)("div", {
118060
- className: "dsh-ig-field-grid",
118061
- children: [(0, react_jsx_runtime.jsx)(FieldSelect, {
118062
- label: t("ratio"),
118063
- value: ratio,
118064
- onChange: setRatio,
118065
- options: comparisonRatioOptions(lang)
118066
- }), (0, react_jsx_runtime.jsx)(FieldSelect, {
118067
- label: t("quality"),
118068
- value: quality,
118069
- onChange: setQuality,
118070
- options: comparisonQualityOptions(lang)
118071
- })]
118072
- })] }) : (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
118742
+ }), comparisonEnabled ? (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
118743
+ (0, react_jsx_runtime.jsxs)("div", {
118744
+ className: "dsh-ig-compare-box",
118745
+ children: [
118746
+ (0, react_jsx_runtime.jsxs)("div", {
118747
+ className: "dsh-ig-compare-heading",
118748
+ children: [(0, react_jsx_runtime.jsxs)("div", { children: [(0, react_jsx_runtime.jsx)("strong", { children: t("compareSelect") }), (0, react_jsx_runtime.jsx)("small", { children: t("compareHint") })] }), (0, react_jsx_runtime.jsx)("span", { children: t("compareSelected", { count: String(comparisonTargets.length) }) })]
118749
+ }),
118750
+ (0, react_jsx_runtime.jsx)("div", {
118751
+ className: "dsh-ig-model-checks",
118752
+ children: comparisonProfiles.map((item) => {
118753
+ const target = comparisonTargets.find((candidate) => candidate.profile.provider === item.provider);
118754
+ return (0, react_jsx_runtime.jsxs)("button", {
118755
+ type: "button",
118756
+ className: target ? "is-selected" : "",
118757
+ "aria-pressed": Boolean(target),
118758
+ onClick: () => toggleComparisonProvider(item.provider),
118759
+ children: [
118760
+ (0, react_jsx_runtime.jsx)("span", {
118761
+ className: "dsh-ig-model-checkmark",
118762
+ children: target ? (0, react_jsx_runtime.jsx)(Check, { size: 12 }) : null
118763
+ }),
118764
+ (0, react_jsx_runtime.jsxs)("span", {
118765
+ className: "dsh-ig-model-copy",
118766
+ children: [(0, react_jsx_runtime.jsx)("strong", { children: item.label }), (0, react_jsx_runtime.jsx)("small", {
118767
+ title: item.model,
118768
+ children: item.model
118769
+ })]
118770
+ }),
118771
+ target && (0, react_jsx_runtime.jsxs)("span", {
118772
+ className: "dsh-ig-model-settings",
118773
+ children: [
118774
+ target.ratio,
118775
+ " · ",
118776
+ target.quality,
118777
+ target.adjusted ? (0, react_jsx_runtime.jsx)("em", { children: t("compareAdjusted") }) : null
118778
+ ]
118779
+ })
118780
+ ]
118781
+ }, item.provider);
118782
+ })
118783
+ }),
118784
+ (0, react_jsx_runtime.jsx)("p", { children: t("compareParameterHint") })
118785
+ ]
118786
+ }),
118787
+ (0, react_jsx_runtime.jsxs)("div", {
118788
+ className: "dsh-ig-field-grid",
118789
+ children: [(0, react_jsx_runtime.jsx)(FieldSelect, {
118790
+ label: t("ratio"),
118791
+ value: ratio,
118792
+ onChange: setRatio,
118793
+ options: comparisonRatioOpts
118794
+ }), (0, react_jsx_runtime.jsx)(FieldSelect, {
118795
+ label: t("quality"),
118796
+ value: quality,
118797
+ onChange: setQuality,
118798
+ options: comparisonQualityOpts
118799
+ })]
118800
+ }),
118801
+ comparisonTargets.some((target) => target.profile.provider === "chatgpt-sub") && (0, react_jsx_runtime.jsx)("div", {
118802
+ className: "dsh-ig-inline-note",
118803
+ children: t("subscriptionParameterHint")
118804
+ })
118805
+ ] }) : (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
118073
118806
  (0, react_jsx_runtime.jsxs)("div", {
118074
118807
  className: "dsh-ig-field-grid",
118075
118808
  children: [(0, react_jsx_runtime.jsx)(FieldSelect, {
@@ -118090,26 +118823,7 @@ img.ProseMirror-separator {
118090
118823
  }]
118091
118824
  })]
118092
118825
  }),
118093
- singleProviderIsSubscription ? (0, react_jsx_runtime.jsxs)("div", {
118094
- className: "dsh-ig-field-grid",
118095
- children: [(0, react_jsx_runtime.jsx)(FieldSelect, {
118096
- label: t("ratio"),
118097
- value: "auto",
118098
- onChange: () => {},
118099
- options: [{
118100
- value: "auto",
118101
- label: lang === "en" ? "Channel default" : "通道默认"
118102
- }]
118103
- }), (0, react_jsx_runtime.jsx)(FieldSelect, {
118104
- label: t("quality"),
118105
- value: "auto",
118106
- onChange: () => {},
118107
- options: [{
118108
- value: "auto",
118109
- label: lang === "en" ? "Channel default" : "通道默认"
118110
- }]
118111
- })]
118112
- }) : (0, react_jsx_runtime.jsxs)("div", {
118826
+ (0, react_jsx_runtime.jsxs)("div", {
118113
118827
  className: "dsh-ig-field-grid",
118114
118828
  children: [(0, react_jsx_runtime.jsx)(FieldSelect, {
118115
118829
  label: t("ratio"),
@@ -118123,6 +118837,10 @@ img.ProseMirror-separator {
118123
118837
  options: localizeQualityOptions(activeProfile?.qualityOptions ?? [], lang)
118124
118838
  })]
118125
118839
  }),
118840
+ provider === "chatgpt-sub" && (0, react_jsx_runtime.jsx)("div", {
118841
+ className: "dsh-ig-inline-note",
118842
+ children: t("subscriptionParameterHint")
118843
+ }),
118126
118844
  (0, react_jsx_runtime.jsxs)("div", {
118127
118845
  className: "dsh-ig-field",
118128
118846
  children: [(0, react_jsx_runtime.jsx)("label", { children: t("count") }), (0, react_jsx_runtime.jsx)("div", {
@@ -118438,6 +119156,15 @@ img.ProseMirror-separator {
118438
119156
  ]
118439
119157
  });
118440
119158
  };
119159
+ function hasSubscriptionRatioMismatch(item) {
119160
+ if (item.provider !== "chatgpt-sub" || item.aspectRatio === "auto") return false;
119161
+ const width = item.attachment.width;
119162
+ const height = item.attachment.height;
119163
+ if (width === void 0 || height === void 0 || width <= 0 || height <= 0) return false;
119164
+ const parts = item.aspectRatio?.split(":").map(Number);
119165
+ if (parts === void 0 || parts.length !== 2 || !(parts[0] > 0) || !(parts[1] > 0)) return false;
119166
+ return Math.abs(width / height / (parts[0] / parts[1]) - 1) > .12;
119167
+ }
118441
119168
  const FieldSelect = ({ label, value, options, onChange }) => (0, react_jsx_runtime.jsxs)("label", {
118442
119169
  className: "dsh-ig-field-select",
118443
119170
  children: [(0, react_jsx_runtime.jsx)("span", { children: label }), (0, react_jsx_runtime.jsx)("select", {
@@ -118474,6 +119201,14 @@ img.ProseMirror-separator {
118474
119201
  zh: "3:4 竖向",
118475
119202
  en: "3:4 Portrait"
118476
119203
  },
119204
+ "4:5": {
119205
+ zh: "4:5 肖像",
119206
+ en: "4:5 Portrait"
119207
+ },
119208
+ "5:4": {
119209
+ zh: "5:4 横向",
119210
+ en: "5:4 Landscape"
119211
+ },
118477
119212
  "16:9": {
118478
119213
  zh: "16:9 宽屏",
118479
119214
  en: "16:9 Widescreen"
@@ -118481,6 +119216,10 @@ img.ProseMirror-separator {
118481
119216
  "9:16": {
118482
119217
  zh: "9:16 竖屏",
118483
119218
  en: "9:16 Portrait"
119219
+ },
119220
+ "21:9": {
119221
+ zh: "21:9 超宽",
119222
+ en: "21:9 Ultrawide"
118484
119223
  }
118485
119224
  };
118486
119225
  const LOCALIZED_QUALITIES = {
@@ -118489,8 +119228,32 @@ img.ProseMirror-separator {
118489
119228
  en: "Standard (Recommended)"
118490
119229
  },
118491
119230
  auto: {
118492
- zh: "模型自动",
118493
- en: "Model Auto"
119231
+ zh: "自动",
119232
+ en: "Auto"
119233
+ },
119234
+ low: {
119235
+ zh: "低(快速)",
119236
+ en: "Low (Fast)"
119237
+ },
119238
+ medium: {
119239
+ zh: "中",
119240
+ en: "Medium"
119241
+ },
119242
+ high: {
119243
+ zh: "高",
119244
+ en: "High"
119245
+ },
119246
+ xhigh: {
119247
+ zh: "超高",
119248
+ en: "Extra High"
119249
+ },
119250
+ max: {
119251
+ zh: "最高",
119252
+ en: "Max"
119253
+ },
119254
+ hd: {
119255
+ zh: "高清",
119256
+ en: "HD"
118494
119257
  }
118495
119258
  };
118496
119259
  function localizeRatioOptions(options, lang) {
@@ -118505,38 +119268,6 @@ img.ProseMirror-separator {
118505
119268
  label: LOCALIZED_QUALITIES[opt.value]?.[lang] ?? opt.label
118506
119269
  }));
118507
119270
  }
118508
- function comparisonRatioOptions(lang) {
118509
- return [
118510
- "1:1",
118511
- "3:2",
118512
- "2:3",
118513
- "16:9",
118514
- "9:16"
118515
- ].map((value) => ({
118516
- value,
118517
- label: LOCALIZED_RATIOS[value]?.[lang] ?? value
118518
- }));
118519
- }
118520
- function comparisonQualityOptions(lang) {
118521
- return [
118522
- {
118523
- value: "standard",
118524
- label: lang === "zh" ? "标准" : "Standard"
118525
- },
118526
- {
118527
- value: "1K",
118528
- label: "1K"
118529
- },
118530
- {
118531
- value: "2K",
118532
- label: "2K"
118533
- },
118534
- {
118535
- value: "4K",
118536
- label: "4K"
118537
- }
118538
- ];
118539
- }
118540
119271
  const RecentItem = ({ item, active, onClick }) => {
118541
119272
  const [setRef, inView] = useInView();
118542
119273
  const image = useAttachmentImage(item.attachment, inView);
@@ -118556,6 +119287,26 @@ img.ProseMirror-separator {
118556
119287
  })
118557
119288
  });
118558
119289
  };
119290
+ const FavoriteFolderBar = ({ folders, active, onSelect, onDelete, deleteLabel }) => {
119291
+ if (folders.length === 0) return null;
119292
+ return (0, react_jsx_runtime.jsx)("div", {
119293
+ className: "dsh-ig-fav-folders",
119294
+ children: folders.map((folder) => (0, react_jsx_runtime.jsxs)("span", {
119295
+ className: `dsh-ig-fav-folder-chip ${active === folder.id ? "is-active" : ""}`,
119296
+ children: [(0, react_jsx_runtime.jsx)("button", {
119297
+ type: "button",
119298
+ onClick: () => onSelect(folder.id),
119299
+ children: folder.name
119300
+ }), (0, react_jsx_runtime.jsx)("button", {
119301
+ type: "button",
119302
+ className: "dsh-ig-fav-folder-del",
119303
+ title: deleteLabel,
119304
+ onClick: () => onDelete(folder.id),
119305
+ children: (0, react_jsx_runtime.jsx)(X, { size: 9 })
119306
+ })]
119307
+ }, folder.id))
119308
+ });
119309
+ };
118559
119310
  function useInView() {
118560
119311
  const [inView, setInView] = (0, react.useState)(false);
118561
119312
  const observerRef = (0, react.useRef)(null);
@@ -118653,8 +119404,17 @@ img.ProseMirror-separator {
118653
119404
  const MAX_BYTES = 188743680;
118654
119405
  const pending = /* @__PURE__ */ new Map();
118655
119406
  let cacheEpoch = 0;
118656
- function fetchInspirationImage(sourceId, caseId) {
118657
- const key = `${sourceId}:${caseId}`;
119407
+ /**
119408
+ * Resolve the IndexedDB key for one image. `rev` (the source's imageRevision,
119409
+ * falling back to its version) namespaces the cache by image content: when a
119410
+ * source re-packages its images at stable case IDs, a bumped rev orphans the
119411
+ * old blobs instead of serving them forever.
119412
+ */
119413
+ function cacheKey(sourceId, caseId, rev) {
119414
+ return rev === void 0 ? `${sourceId}:${caseId}` : `${sourceId}:${caseId}@${rev}`;
119415
+ }
119416
+ function fetchInspirationImage(sourceId, caseId, rev) {
119417
+ const key = cacheKey(sourceId, caseId, rev);
118658
119418
  const existing = pending.get(key);
118659
119419
  if (existing !== void 0) return existing;
118660
119420
  const requestEpoch = cacheEpoch;
@@ -118677,8 +119437,8 @@ img.ProseMirror-separator {
118677
119437
  pending.set(key, task);
118678
119438
  return task;
118679
119439
  }
118680
- async function evictInspirationImage(sourceId, caseId) {
118681
- const key = `${sourceId}:${caseId}`;
119440
+ async function evictInspirationImage(sourceId, caseId, rev) {
119441
+ const key = cacheKey(sourceId, caseId, rev);
118682
119442
  pending.delete(key);
118683
119443
  const db = await openDatabase$1();
118684
119444
  if (db === void 0) return;
@@ -118865,6 +119625,988 @@ img.ProseMirror-separator {
118865
119625
  return Array.isArray(sources) && sources.length > 0 && sources.every((source) => typeof source === "object" && source !== null && Array.isArray(source.cases));
118866
119626
  }
118867
119627
  //#endregion
119628
+ //#region lib/types/inspiration/data/baoyu-compose.json
119629
+ var types = [
119630
+ {
119631
+ "id": "hero",
119632
+ "description": "Large visual impact, title overlay",
119633
+ "composition": "Large focal visual (60-70% area), title overlay on visual, dramatic composition"
119634
+ },
119635
+ {
119636
+ "id": "conceptual",
119637
+ "description": "Concept visualization, abstract core ideas",
119638
+ "composition": "Abstract shapes representing core concepts, information hierarchy, clean zones"
119639
+ },
119640
+ {
119641
+ "id": "typography",
119642
+ "description": "Text-focused layout, prominent title",
119643
+ "composition": "Title as primary element (40%+ area), minimal supporting visuals, strong hierarchy"
119644
+ },
119645
+ {
119646
+ "id": "metaphor",
119647
+ "description": "Visual metaphor, concrete expressing abstract",
119648
+ "composition": "Concrete object/scene representing abstract idea, symbolic elements, emotional resonance"
119649
+ },
119650
+ {
119651
+ "id": "scene",
119652
+ "description": "Atmospheric scene, narrative feel",
119653
+ "composition": "Atmospheric environment, narrative elements, mood-setting lighting and colors"
119654
+ },
119655
+ {
119656
+ "id": "minimal",
119657
+ "description": "Minimalist composition, generous whitespace",
119658
+ "composition": "Single focal element, generous whitespace (60%+), essential shapes only"
119659
+ }
119660
+ ];
119661
+ var palettes = [
119662
+ {
119663
+ "id": "warm",
119664
+ "tagline": "Friendly, approachable, human-centered",
119665
+ "colors": [
119666
+ {
119667
+ "role": "Primary 1",
119668
+ "name": "Warm Orange",
119669
+ "hex": "#ed8936"
119670
+ },
119671
+ {
119672
+ "role": "Primary 2",
119673
+ "name": "Golden Yellow",
119674
+ "hex": "#f6ad55"
119675
+ },
119676
+ {
119677
+ "role": "Primary 3",
119678
+ "name": "Terracotta",
119679
+ "hex": "#c05621"
119680
+ },
119681
+ {
119682
+ "role": "Background",
119683
+ "name": "Cream",
119684
+ "hex": "#fffaf0"
119685
+ },
119686
+ {
119687
+ "role": "Background Alt",
119688
+ "name": "Soft Peach",
119689
+ "hex": "#fed7aa"
119690
+ },
119691
+ {
119692
+ "role": "Accent 1",
119693
+ "name": "Deep Brown",
119694
+ "hex": "#744210"
119695
+ },
119696
+ {
119697
+ "role": "Accent 2",
119698
+ "name": "Soft Red",
119699
+ "hex": "#e53e3e"
119700
+ }
119701
+ ],
119702
+ "decorativeHints": [
119703
+ "Sun rays, warm lighting effects",
119704
+ "Rounded shapes, organic curves",
119705
+ "Hearts, smiling faces, friendly icons",
119706
+ "Warm gradient overlays"
119707
+ ],
119708
+ "bestFor": ""
119709
+ },
119710
+ {
119711
+ "id": "cool",
119712
+ "tagline": "Technical, professional, precise",
119713
+ "colors": [
119714
+ {
119715
+ "role": "Primary 1",
119716
+ "name": "Engineering Blue",
119717
+ "hex": "#2563eb"
119718
+ },
119719
+ {
119720
+ "role": "Primary 2",
119721
+ "name": "Navy Blue",
119722
+ "hex": "#1e3a5f"
119723
+ },
119724
+ {
119725
+ "role": "Primary 3",
119726
+ "name": "Cyan",
119727
+ "hex": "#06b6d4"
119728
+ },
119729
+ {
119730
+ "role": "Background",
119731
+ "name": "Light Gray",
119732
+ "hex": "#f8f9fa"
119733
+ },
119734
+ {
119735
+ "role": "Background Alt",
119736
+ "name": "Blueprint Off-White",
119737
+ "hex": "#faf8f5"
119738
+ },
119739
+ {
119740
+ "role": "Accent 1",
119741
+ "name": "Amber",
119742
+ "hex": "#f59e0b"
119743
+ },
119744
+ {
119745
+ "role": "Accent 2",
119746
+ "name": "Light Blue",
119747
+ "hex": "#bfdbfe"
119748
+ }
119749
+ ],
119750
+ "decorativeHints": [
119751
+ "Grid lines and alignment guides",
119752
+ "Dimension indicators and measurements",
119753
+ "Technical schematics and diagrams",
119754
+ "Geometric precision elements"
119755
+ ],
119756
+ "bestFor": ""
119757
+ },
119758
+ {
119759
+ "id": "dark",
119760
+ "tagline": "Cinematic, premium, atmospheric",
119761
+ "colors": [
119762
+ {
119763
+ "role": "Primary 1",
119764
+ "name": "Electric Purple",
119765
+ "hex": "#8b5cf6"
119766
+ },
119767
+ {
119768
+ "role": "Primary 2",
119769
+ "name": "Cyan Blue",
119770
+ "hex": "#06b6d4"
119771
+ },
119772
+ {
119773
+ "role": "Primary 3",
119774
+ "name": "Magenta Pink",
119775
+ "hex": "#ec4899"
119776
+ },
119777
+ {
119778
+ "role": "Background",
119779
+ "name": "Deep Purple-Black",
119780
+ "hex": "#0a0a0a"
119781
+ },
119782
+ {
119783
+ "role": "Background Alt",
119784
+ "name": "Rich Navy",
119785
+ "hex": "#1a1a2e"
119786
+ },
119787
+ {
119788
+ "role": "Accent 1",
119789
+ "name": "Amber",
119790
+ "hex": "#f59e0b"
119791
+ },
119792
+ {
119793
+ "role": "Accent 2",
119794
+ "name": "Pure White",
119795
+ "hex": "#ffffff"
119796
+ }
119797
+ ],
119798
+ "decorativeHints": [
119799
+ "Glowing accent elements and neon highlights",
119800
+ "Atmospheric fog or particle effects",
119801
+ "Silhouettes with backlit edges",
119802
+ "Subtle gradient backgrounds"
119803
+ ],
119804
+ "bestFor": ""
119805
+ },
119806
+ {
119807
+ "id": "earth",
119808
+ "tagline": "Natural, organic, grounded",
119809
+ "colors": [
119810
+ {
119811
+ "role": "Primary 1",
119812
+ "name": "Forest Green",
119813
+ "hex": "#276749"
119814
+ },
119815
+ {
119816
+ "role": "Primary 2",
119817
+ "name": "Sage",
119818
+ "hex": "#9ae6b4"
119819
+ },
119820
+ {
119821
+ "role": "Primary 3",
119822
+ "name": "Earth Brown",
119823
+ "hex": "#744210"
119824
+ },
119825
+ {
119826
+ "role": "Background",
119827
+ "name": "Sand Beige",
119828
+ "hex": "#f5e6d3"
119829
+ },
119830
+ {
119831
+ "role": "Background Alt",
119832
+ "name": "Sky Blue",
119833
+ "hex": "#e0f2fe"
119834
+ },
119835
+ {
119836
+ "role": "Accent 1",
119837
+ "name": "Sunset Orange",
119838
+ "hex": "#ed8936"
119839
+ },
119840
+ {
119841
+ "role": "Accent 2",
119842
+ "name": "Water Blue",
119843
+ "hex": "#63b3ed"
119844
+ }
119845
+ ],
119846
+ "decorativeHints": [
119847
+ "Leaves, trees, mountains, natural forms",
119848
+ "Sun, clouds, organic flowing lines",
119849
+ "Botanical illustrations",
119850
+ "Earthy textures and natural patterns"
119851
+ ],
119852
+ "bestFor": ""
119853
+ },
119854
+ {
119855
+ "id": "elegant",
119856
+ "tagline": "Sophisticated, refined, understated luxury",
119857
+ "colors": [
119858
+ {
119859
+ "role": "Primary 1",
119860
+ "name": "Soft Coral",
119861
+ "hex": "#e8a598"
119862
+ },
119863
+ {
119864
+ "role": "Primary 2",
119865
+ "name": "Muted Teal",
119866
+ "hex": "#5b8a8a"
119867
+ },
119868
+ {
119869
+ "role": "Primary 3",
119870
+ "name": "Dusty Rose",
119871
+ "hex": "#d4a5a5"
119872
+ },
119873
+ {
119874
+ "role": "Background",
119875
+ "name": "Warm Cream",
119876
+ "hex": "#f5f0e6"
119877
+ },
119878
+ {
119879
+ "role": "Background Alt",
119880
+ "name": "Soft Beige",
119881
+ "hex": "#f0ebe0"
119882
+ },
119883
+ {
119884
+ "role": "Accent 1",
119885
+ "name": "Gold",
119886
+ "hex": "#c9a962"
119887
+ },
119888
+ {
119889
+ "role": "Accent 2",
119890
+ "name": "Copper",
119891
+ "hex": "#b87333"
119892
+ }
119893
+ ],
119894
+ "decorativeHints": [
119895
+ "Delicate ornamental details",
119896
+ "Subtle gradients and soft transitions",
119897
+ "Refined geometric patterns",
119898
+ "Balanced, symmetrical compositions"
119899
+ ],
119900
+ "bestFor": ""
119901
+ },
119902
+ {
119903
+ "id": "macaron",
119904
+ "tagline": "Soft macaron pastel color blocks on warm cream",
119905
+ "colors": [
119906
+ {
119907
+ "role": "Primary 1",
119908
+ "name": "Sky Blue",
119909
+ "hex": "#a8d8ea"
119910
+ },
119911
+ {
119912
+ "role": "Primary 2",
119913
+ "name": "Mint Green",
119914
+ "hex": "#b5e5cf"
119915
+ },
119916
+ {
119917
+ "role": "Primary 3",
119918
+ "name": "Lavender",
119919
+ "hex": "#d5c6e0"
119920
+ },
119921
+ {
119922
+ "role": "Background",
119923
+ "name": "Warm Cream",
119924
+ "hex": "#f5f0e8"
119925
+ },
119926
+ {
119927
+ "role": "Background Alt",
119928
+ "name": "Peach",
119929
+ "hex": "#ffd5c2"
119930
+ },
119931
+ {
119932
+ "role": "Accent 1",
119933
+ "name": "Coral Red",
119934
+ "hex": "#e8655a"
119935
+ },
119936
+ {
119937
+ "role": "Accent 2",
119938
+ "name": "Deep Charcoal",
119939
+ "hex": "#2d2d2d"
119940
+ }
119941
+ ],
119942
+ "decorativeHints": [
119943
+ "Rounded pastel color blocks",
119944
+ "Subtle warm paper grain texture",
119945
+ "Soft shadows, no hard edges",
119946
+ "Gentle gradient transitions between zones"
119947
+ ],
119948
+ "bestFor": ""
119949
+ },
119950
+ {
119951
+ "id": "mono",
119952
+ "tagline": "Clean, focused, essential",
119953
+ "colors": [
119954
+ {
119955
+ "role": "Primary 1",
119956
+ "name": "Pure Black",
119957
+ "hex": "#000000"
119958
+ },
119959
+ {
119960
+ "role": "Primary 2",
119961
+ "name": "Near Black",
119962
+ "hex": "#1f1f1f"
119963
+ },
119964
+ {
119965
+ "role": "Primary 3",
119966
+ "name": "Dark Gray",
119967
+ "hex": "#374151"
119968
+ },
119969
+ {
119970
+ "role": "Background",
119971
+ "name": "White",
119972
+ "hex": "#ffffff"
119973
+ },
119974
+ {
119975
+ "role": "Background Alt",
119976
+ "name": "Off-White",
119977
+ "hex": "#fafafa"
119978
+ },
119979
+ {
119980
+ "role": "Accent 2",
119981
+ "name": "Medium Gray",
119982
+ "hex": "#9ca3af"
119983
+ }
119984
+ ],
119985
+ "decorativeHints": [
119986
+ "Maximum negative space",
119987
+ "Thin lines and minimal strokes",
119988
+ "Single focal point emphasis",
119989
+ "Stark contrast between elements"
119990
+ ],
119991
+ "bestFor": ""
119992
+ },
119993
+ {
119994
+ "id": "pastel",
119995
+ "tagline": "Gentle, whimsical, soft",
119996
+ "colors": [
119997
+ {
119998
+ "role": "Primary 1",
119999
+ "name": "Soft Pink",
120000
+ "hex": "#ffb6c1"
120001
+ },
120002
+ {
120003
+ "role": "Primary 2",
120004
+ "name": "Mint",
120005
+ "hex": "#98d8c8"
120006
+ },
120007
+ {
120008
+ "role": "Primary 3",
120009
+ "name": "Lavender",
120010
+ "hex": "#c8a2c8"
120011
+ },
120012
+ {
120013
+ "role": "Background",
120014
+ "name": "White",
120015
+ "hex": "#ffffff"
120016
+ },
120017
+ {
120018
+ "role": "Background Alt",
120019
+ "name": "Light Cream",
120020
+ "hex": "#fff8e7"
120021
+ },
120022
+ {
120023
+ "role": "Accent 1",
120024
+ "name": "Butter Yellow",
120025
+ "hex": "#fffacd"
120026
+ },
120027
+ {
120028
+ "role": "Accent 2",
120029
+ "name": "Sky Blue",
120030
+ "hex": "#bee3f8"
120031
+ }
120032
+ ],
120033
+ "decorativeHints": [
120034
+ "Cute rounded proportions",
120035
+ "Stars, sparkles, flowers, decorative flourishes",
120036
+ "Soft shadows and gentle highlights",
120037
+ "Storybook-style elements"
120038
+ ],
120039
+ "bestFor": ""
120040
+ },
120041
+ {
120042
+ "id": "retro",
120043
+ "tagline": "Nostalgic, vintage, classic",
120044
+ "colors": [
120045
+ {
120046
+ "role": "Primary 1",
120047
+ "name": "Coral Red",
120048
+ "hex": "#e07a5f"
120049
+ },
120050
+ {
120051
+ "role": "Primary 2",
120052
+ "name": "Mint Green",
120053
+ "hex": "#81b29a"
120054
+ },
120055
+ {
120056
+ "role": "Primary 3",
120057
+ "name": "Mustard Yellow",
120058
+ "hex": "#f2cc8f"
120059
+ },
120060
+ {
120061
+ "role": "Primary 4",
120062
+ "name": "Dark Maroon",
120063
+ "hex": "#5d3a3a"
120064
+ },
120065
+ {
120066
+ "role": "Background",
120067
+ "name": "Cream Off-White",
120068
+ "hex": "#f5f0e6"
120069
+ },
120070
+ {
120071
+ "role": "Background Alt",
120072
+ "name": "Aged Paper",
120073
+ "hex": "#f5e6d3"
120074
+ },
120075
+ {
120076
+ "role": "Accent 1",
120077
+ "name": "Burnt Orange",
120078
+ "hex": "#d4764a"
120079
+ },
120080
+ {
120081
+ "role": "Accent 2",
120082
+ "name": "Rock Blue",
120083
+ "hex": "#577590"
120084
+ },
120085
+ {
120086
+ "role": "Accent 3",
120087
+ "name": "Vintage Gold",
120088
+ "hex": "#c9a227"
120089
+ },
120090
+ {
120091
+ "role": "Accent 4",
120092
+ "name": "Faded Teal",
120093
+ "hex": "#2f7373"
120094
+ }
120095
+ ],
120096
+ "decorativeHints": [
120097
+ "Halftone dots and vintage badges",
120098
+ "Aged textures with subtle paper grain",
120099
+ "Sunburst/radiating lines for energy",
120100
+ "Pill-shaped clouds, small dots and stars",
120101
+ "Classic icons and retro motifs"
120102
+ ],
120103
+ "bestFor": ""
120104
+ },
120105
+ {
120106
+ "id": "duotone",
120107
+ "tagline": "Dramatic, cinematic, two-color high contrast",
120108
+ "colors": [
120109
+ {
120110
+ "role": "Primary 1",
120111
+ "name": "Burnt Orange",
120112
+ "hex": "#e8751a"
120113
+ },
120114
+ {
120115
+ "role": "Primary 2",
120116
+ "name": "Deep Teal",
120117
+ "hex": "#0a6e6e"
120118
+ },
120119
+ {
120120
+ "role": "Background",
120121
+ "name": "Off-Black",
120122
+ "hex": "#121212"
120123
+ },
120124
+ {
120125
+ "role": "Background Alt",
120126
+ "name": "Dark Charcoal",
120127
+ "hex": "#1e1e1e"
120128
+ },
120129
+ {
120130
+ "role": "Accent 1",
120131
+ "name": "Warm Cream",
120132
+ "hex": "#f5e6d0"
120133
+ },
120134
+ {
120135
+ "role": "Accent 2",
120136
+ "name": "Amber Highlight",
120137
+ "hex": "#f4a623"
120138
+ }
120139
+ ],
120140
+ "decorativeHints": [
120141
+ "Stark two-color separation across entire composition",
120142
+ "Halftone transitions between the two colors",
120143
+ "Silhouettes in one color against the other",
120144
+ "Minimal use of third color (only for small highlights)",
120145
+ "High contrast figure-ground relationships"
120146
+ ],
120147
+ "bestFor": "",
120148
+ "pairs": [
120149
+ {
120150
+ "name": "Orange + Teal",
120151
+ "a": "#e8751a",
120152
+ "b": "#0a6e6e",
120153
+ "feel": "Cinematic, action"
120154
+ },
120155
+ {
120156
+ "name": "Red + Cream",
120157
+ "a": "#c0392b",
120158
+ "b": "#f5e6d0",
120159
+ "feel": "Bold, classic"
120160
+ },
120161
+ {
120162
+ "name": "Blue + Gold",
120163
+ "a": "#1a3a5c",
120164
+ "b": "#d4a843",
120165
+ "feel": "Prestigious, premium"
120166
+ },
120167
+ {
120168
+ "name": "Purple + Green",
120169
+ "a": "#6b3fa0",
120170
+ "b": "#2ecc71",
120171
+ "feel": "Futuristic, contrast"
120172
+ },
120173
+ {
120174
+ "name": "Magenta + Cyan",
120175
+ "a": "#c2185b",
120176
+ "b": "#00bcd4",
120177
+ "feel": "Vibrant, pop"
120178
+ },
120179
+ {
120180
+ "name": "Crimson + Navy",
120181
+ "a": "#dc143c",
120182
+ "b": "#0d1b2a",
120183
+ "feel": "Dramatic, noir"
120184
+ }
120185
+ ]
120186
+ },
120187
+ {
120188
+ "id": "vivid",
120189
+ "tagline": "Energetic, bold, attention-grabbing",
120190
+ "colors": [
120191
+ {
120192
+ "role": "Primary 1",
120193
+ "name": "Bright Red",
120194
+ "hex": "#ef4444"
120195
+ },
120196
+ {
120197
+ "role": "Primary 2",
120198
+ "name": "Neon Green",
120199
+ "hex": "#22c55e"
120200
+ },
120201
+ {
120202
+ "role": "Primary 3",
120203
+ "name": "Electric Blue",
120204
+ "hex": "#3b82f6"
120205
+ },
120206
+ {
120207
+ "role": "Background",
120208
+ "name": "Light Blue",
120209
+ "hex": "#eff6ff"
120210
+ },
120211
+ {
120212
+ "role": "Background Alt",
120213
+ "name": "Soft Lavender",
120214
+ "hex": "#f5f3ff"
120215
+ },
120216
+ {
120217
+ "role": "Accent 1",
120218
+ "name": "Bright Orange",
120219
+ "hex": "#fb923c"
120220
+ },
120221
+ {
120222
+ "role": "Accent 2",
120223
+ "name": "Vivid Yellow",
120224
+ "hex": "#facc15"
120225
+ }
120226
+ ],
120227
+ "decorativeHints": [
120228
+ "Dynamic diagonal lines and angles",
120229
+ "Bold geometric shapes and color blocks",
120230
+ "Dramatic lighting effects",
120231
+ "High-energy visual compositions"
120232
+ ],
120233
+ "bestFor": ""
120234
+ }
120235
+ ];
120236
+ var renderings = [
120237
+ {
120238
+ "id": "flat-vector",
120239
+ "tagline": "Clean, modern, geometric illustration",
120240
+ "lines": [
120241
+ "Clean outlines with uniform stroke weight",
120242
+ "Closed shapes (coloring-book style)",
120243
+ "Rounded line endings, avoid sharp corners",
120244
+ "Consistent stroke width throughout"
120245
+ ],
120246
+ "texture": [
120247
+ "None: smooth, flat color fills only",
120248
+ "No gradients, shadows, or noise",
120249
+ "Solid color blocks"
120250
+ ],
120251
+ "depth": [
120252
+ "Flat: no shadows, no perspective",
120253
+ "2D layering with overlap for depth illusion",
120254
+ "Optional 2.5D isometric layering (front/back occlusion, no atmospheric perspective)",
120255
+ "No 3D effects or bevels"
120256
+ ],
120257
+ "elements": [
120258
+ "Geometric icons and simple shapes",
120259
+ "Bold outlined objects with clean fills",
120260
+ "Geometric simplification: complex objects → basic shapes (trees → lollipop/triangle, buildings → rectangles)",
120261
+ "\"Toy model\" aesthetic: cute, rounded proportions",
120262
+ "Decorative: dots, lines, sunbursts, pill-shaped clouds, small stars",
120263
+ "Isolated elements on clean backgrounds"
120264
+ ]
120265
+ },
120266
+ {
120267
+ "id": "pixel",
120268
+ "tagline": "Retro 8-bit, nostalgic, chunky",
120269
+ "lines": [
120270
+ "Pixel grid alignment, no anti-aliasing",
120271
+ "Staircase edges on diagonals",
120272
+ "Single-pixel or double-pixel outlines",
120273
+ "Blocky, angular forms"
120274
+ ],
120275
+ "texture": [
120276
+ "Dithering patterns for gradients",
120277
+ "No smooth transitions",
120278
+ "Cross-hatching with pixel precision",
120279
+ "Limited 16-32 color palette per scene"
120280
+ ],
120281
+ "depth": [
120282
+ "None: flat pixel planes only",
120283
+ "Parallax layering (foreground/background)",
120284
+ "No perspective or 3D effects"
120285
+ ],
120286
+ "elements": [
120287
+ "8-bit sprites and chunky shapes",
120288
+ "Simple iconography: stars, hearts, arrows",
120289
+ "Text bubbles with pixel borders",
120290
+ "Progress bars with chunky segments",
120291
+ "Retro gaming UI elements"
120292
+ ]
120293
+ },
120294
+ {
120295
+ "id": "digital",
120296
+ "tagline": "Polished, precise, modern",
120297
+ "lines": [
120298
+ "Clean, precise, computer-perfect edges",
120299
+ "Consistent stroke weights",
120300
+ "Sharp corners where appropriate",
120301
+ "Anti-aliased smooth rendering"
120302
+ ],
120303
+ "texture": [
120304
+ "Smooth surfaces with no visible texture",
120305
+ "Subtle gradients permitted (soft, controlled)",
120306
+ "Frosted glass and blur effects",
120307
+ "Clean shadows with consistent direction"
120308
+ ],
120309
+ "depth": [
120310
+ "Subtle gradients and soft drop shadows",
120311
+ "Layered card-based layouts",
120312
+ "Light 3D effects (subtle, not realistic)",
120313
+ "Material Design-inspired elevation"
120314
+ ],
120315
+ "elements": [
120316
+ "Polished icons and UI components",
120317
+ "Data visualizations: charts, graphs, metrics",
120318
+ "Card layouts and structured grids",
120319
+ "Tag chips, progress bars, status indicators",
120320
+ "Clean geometric shapes"
120321
+ ]
120322
+ },
120323
+ {
120324
+ "id": "hand-drawn",
120325
+ "tagline": "Sketchy, organic, personal",
120326
+ "lines": [
120327
+ "Sketchy, organic, slightly imperfect strokes",
120328
+ "Variable line weight (thicker at pressure points)",
120329
+ "Wavy connectors and arrows",
120330
+ "Natural hand tremor visible"
120331
+ ],
120332
+ "texture": [
120333
+ "Paper grain and subtle surface texture",
120334
+ "Pencil/pen/marker texture on strokes",
120335
+ "Casual fills with visible brush direction"
120336
+ ],
120337
+ "depth": [
120338
+ "Minimal: light hand-drawn shadows or hatching",
120339
+ "No realistic depth or perspective",
120340
+ "Simple layering with overlap"
120341
+ ],
120342
+ "elements": [
120343
+ "Doodles, organic shapes, hand-lettered labels",
120344
+ "Conceptual icons with sketchy quality",
120345
+ "Connection lines with hand-drawn wavy feel",
120346
+ "Stars, arrows, underlines, circles, checkmarks",
120347
+ "Stick figures and simple characters"
120348
+ ]
120349
+ },
120350
+ {
120351
+ "id": "painterly",
120352
+ "tagline": "Soft, artistic, expressive",
120353
+ "lines": [
120354
+ "Soft brush strokes with variable opacity",
120355
+ "No hard outlines; edges defined by color transitions",
120356
+ "Organic flowing strokes with natural blending"
120357
+ ],
120358
+ "texture": [
120359
+ "Visible paint or watercolor wash textures",
120360
+ "Color bleeds and wet-on-wet effects",
120361
+ "Paper texture showing through transparent areas",
120362
+ "Brush stroke patterns visible"
120363
+ ],
120364
+ "depth": [
120365
+ "Soft edges with natural color blending",
120366
+ "Atmospheric depth through color fading",
120367
+ "Layered washes creating depth illusion"
120368
+ ],
120369
+ "elements": [
120370
+ "Watercolor washes as backgrounds",
120371
+ "Natural elements: leaves, flowers, organic forms",
120372
+ "Soft gradients and color transitions",
120373
+ "Splatter and drip effects as accents",
120374
+ "Botanical and environmental motifs"
120375
+ ]
120376
+ },
120377
+ {
120378
+ "id": "chalk",
120379
+ "tagline": "Educational, authentic, classroom",
120380
+ "lines": [
120381
+ "Imperfect chalk strokes with variable pressure",
120382
+ "Visible chalk texture and grain",
120383
+ "Slightly wobbly, hand-drawn quality",
120384
+ "Thick strokes for emphasis, thin for details"
120385
+ ],
120386
+ "texture": [
120387
+ "Chalk dust effects around text and elements",
120388
+ "Board surface (dark, slightly worn)",
120389
+ "Eraser smudges and residue",
120390
+ "Grainy chalk quality on all elements"
120391
+ ],
120392
+ "depth": [
120393
+ "None: flat chalk drawings on board surface",
120394
+ "Layering through erasure and redrawing",
120395
+ "No shadows or perspective"
120396
+ ],
120397
+ "elements": [
120398
+ "Chalk doodles: stars, arrows, underlines",
120399
+ "Mathematical formulas and diagrams",
120400
+ "Stick figures and simple icons",
120401
+ "Connection lines with chalk feel",
120402
+ "Checkmarks, circles, boxes for lists",
120403
+ "Wooden frame border optional"
120404
+ ]
120405
+ },
120406
+ {
120407
+ "id": "screen-print",
120408
+ "tagline": "Bold, limited-color poster art with print texture",
120409
+ "lines": [
120410
+ "Clean, sharp edges between color blocks",
120411
+ "No outlines — shapes defined by color boundaries",
120412
+ "Stencil-cut quality, bold silhouettes",
120413
+ "Geometric shapes with precise registration"
120414
+ ],
120415
+ "texture": [
120416
+ "Halftone dot patterns within color fills",
120417
+ "Slight color layer misregistration (offset between print layers)",
120418
+ "Paper grain texture beneath colors",
120419
+ "Risograph / screen print imperfections (ink spread, dot gain)"
120420
+ ],
120421
+ "depth": [
120422
+ "Flat color planes layered front to back",
120423
+ "Depth through silhouette overlap and color layering",
120424
+ "No gradients — tonal variation via halftone density",
120425
+ "Negative space as active compositional element"
120426
+ ],
120427
+ "elements": [
120428
+ "Bold silhouettes and symbolic shapes",
120429
+ "Geometric framing (circles, arches, triangles)",
120430
+ "Figure-ground inversion (negative space forms secondary image)",
120431
+ "Limited icon vocabulary: key props, symbolic objects",
120432
+ "Typography integrated as design element, not overlay",
120433
+ "Vintage poster border treatments"
120434
+ ]
120435
+ }
120436
+ ];
120437
+ var textLevels = [
120438
+ {
120439
+ "id": "none",
120440
+ "area": "85% visual area"
120441
+ },
120442
+ {
120443
+ "id": "title-only",
120444
+ "area": "85% visual area"
120445
+ },
120446
+ {
120447
+ "id": "title-subtitle",
120448
+ "area": "75% visual area"
120449
+ },
120450
+ {
120451
+ "id": "text-rich",
120452
+ "area": "60% visual area"
120453
+ }
120454
+ ];
120455
+ var moods = [
120456
+ {
120457
+ "id": "subtle",
120458
+ "application": "Use low contrast, muted colors, light visual weight, calm aesthetic"
120459
+ },
120460
+ {
120461
+ "id": "balanced",
120462
+ "application": "Use medium contrast, normal saturation, balanced visual weight"
120463
+ },
120464
+ {
120465
+ "id": "bold",
120466
+ "application": "Use high contrast, vivid saturated colors, heavy visual weight, dynamic energy"
120467
+ }
120468
+ ];
120469
+ //#endregion
120470
+ //#region lib/types/baoyu-compose.js
120471
+ /**
120472
+ * Dimension-composition prompt assembly for the inspiration composer tab.
120473
+ * Data is generated by scripts/build-baoyu-compose.mjs from JimLiu/baoyu-skills
120474
+ * (baoyu-cover-image skill, MIT license).
120475
+ */
120476
+ /** Pseudo inspiration source id that switches the view into composer mode. */
120477
+ const BAOYU_COMPOSER_SOURCE_ID = "baoyu-compose";
120478
+ const BAOYU_TYPES = types;
120479
+ const BAOYU_PALETTES = palettes;
120480
+ const BAOYU_RENDERINGS = renderings;
120481
+ const BAOYU_TEXT_LEVELS = textLevels;
120482
+ const BAOYU_MOODS = moods;
120483
+ /** Chinese labels for every dimension option id, shown when the UI language is zh. */
120484
+ const BAOYU_LABELS_ZH = {
120485
+ hero: "主视觉大图",
120486
+ conceptual: "概念抽象",
120487
+ typography: "文字主导",
120488
+ metaphor: "隐喻象征",
120489
+ scene: "氛围场景",
120490
+ minimal: "极简留白",
120491
+ warm: "暖色",
120492
+ cool: "冷色",
120493
+ dark: "暗色",
120494
+ earth: "大地色",
120495
+ elegant: "优雅",
120496
+ macaron: "马卡龙",
120497
+ mono: "单色",
120498
+ pastel: "粉彩",
120499
+ retro: "复古",
120500
+ duotone: "双色调",
120501
+ vivid: "高饱和",
120502
+ "flat-vector": "扁平矢量",
120503
+ pixel: "像素风",
120504
+ digital: "数字插画",
120505
+ "hand-drawn": "手绘",
120506
+ painterly: "绘画感",
120507
+ chalk: "粉笔",
120508
+ "screen-print": "丝网印刷",
120509
+ none: "无文字",
120510
+ "title-only": "仅标题",
120511
+ "title-subtitle": "标题+副标题",
120512
+ "text-rich": "丰富文字",
120513
+ subtle: "含蓄",
120514
+ balanced: "均衡",
120515
+ bold: "强烈"
120516
+ };
120517
+ const BAOYU_DEFAULT_SELECTION = {
120518
+ type: "metaphor",
120519
+ palette: "warm",
120520
+ rendering: "flat-vector",
120521
+ text: "title-only",
120522
+ mood: "balanced",
120523
+ title: ""
120524
+ };
120525
+ function palette(id) {
120526
+ return BAOYU_PALETTES.find((item) => item.id === id);
120527
+ }
120528
+ function rendering(id) {
120529
+ return BAOYU_RENDERINGS.find((item) => item.id === id);
120530
+ }
120531
+ function typeOf(id) {
120532
+ return BAOYU_TYPES.find((item) => item.id === id);
120533
+ }
120534
+ function moodOf(id) {
120535
+ return BAOYU_MOODS.find((item) => item.id === id);
120536
+ }
120537
+ function textLevel(id) {
120538
+ return BAOYU_TEXT_LEVELS.find((item) => item.id === id);
120539
+ }
120540
+ /** Assemble the five-dimension selection into one generate_image-ready prompt. */
120541
+ function composeBaoyuPrompt(selection) {
120542
+ const chosenType = typeOf(selection.type);
120543
+ const chosenPalette = palette(selection.palette);
120544
+ const chosenRendering = rendering(selection.rendering);
120545
+ const chosenText = textLevel(selection.text);
120546
+ const chosenMood = moodOf(selection.mood);
120547
+ if (chosenType === void 0 || chosenPalette === void 0 || chosenRendering === void 0 || chosenText === void 0 || chosenMood === void 0) throw new Error("invalid-baoyu-selection");
120548
+ const title = selection.title.trim();
120549
+ if (selection.text !== "none" && title === "") throw new Error("baoyu-title-required");
120550
+ const primary = chosenPalette.colors.filter((color) => color.role.startsWith("Primary"));
120551
+ const background = chosenPalette.colors.filter((color) => color.role.startsWith("Background"));
120552
+ const accent = chosenPalette.colors.filter((color) => color.role.startsWith("Accent"));
120553
+ const swatch = (colors) => colors.map((color) => `${color.name} ${color.hex}`).join(", ");
120554
+ const textElements = selection.text === "none" ? "No text elements; the full area stays visual." : [
120555
+ `Title: "${title}"`,
120556
+ ...selection.text === "title-subtitle" || selection.text === "text-rich" ? ["Subtitle: derive one short supporting line from the title"] : [],
120557
+ ...selection.text === "text-rich" ? ["Tags: derive 2-3 keyword badges from the title"] : []
120558
+ ].join("\n");
120559
+ 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("; ")}`;
120560
+ return [
120561
+ "Cover image.",
120562
+ "",
120563
+ "# Visual Design",
120564
+ `- Type: ${chosenType.id} — ${chosenType.composition}`,
120565
+ `- Palette: ${chosenPalette.id} (${chosenPalette.tagline})`,
120566
+ `- Rendering: ${chosenRendering.id} — ${chosenRendering.tagline}`,
120567
+ `- Text level: ${chosenText.id} (${chosenText.area})`,
120568
+ `- Mood: ${chosenMood.id} — ${chosenMood.application}`,
120569
+ "",
120570
+ "# Text Elements",
120571
+ textElements,
120572
+ "",
120573
+ "# Composition",
120574
+ `- Type composition: ${chosenType.composition}`,
120575
+ "- 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.",
120576
+ `- Decorative: ${chosenPalette.decorativeHints.join("; ")}`,
120577
+ "",
120578
+ "# Color Scheme",
120579
+ `- Primary: ${swatch(primary)}`,
120580
+ `- Background: ${swatch(background)}`,
120581
+ `- Accent: ${swatch(accent)}${pairNote}`,
120582
+ `- Mood adjustment: ${chosenMood.application}`,
120583
+ "- 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.",
120584
+ "",
120585
+ `# Rendering Notes (${chosenRendering.id})`,
120586
+ `- Lines: ${chosenRendering.lines.join("; ")}`,
120587
+ `- Texture: ${chosenRendering.texture.join("; ")}`,
120588
+ `- Depth: ${chosenRendering.depth.join("; ")}`,
120589
+ `- Elements: ${chosenRendering.elements.join("; ")}`,
120590
+ "",
120591
+ "# Core Principles",
120592
+ "- Ample whitespace, highlight the core message, avoid cluttered layouts.",
120593
+ "- Simplified silhouettes for any characters — no realistic human faces or bodies.",
120594
+ "- Icon-based vocabulary: represent concepts with simple, recognizable icons."
120595
+ ].join("\n");
120596
+ }
120597
+ /** Randomize every visual dimension, keeping the current title text. */
120598
+ function randomBaoyuSelection(current) {
120599
+ const pick = (items) => items[Math.floor(Math.random() * items.length)];
120600
+ return {
120601
+ type: pick(BAOYU_TYPES).id,
120602
+ palette: pick(BAOYU_PALETTES).id,
120603
+ rendering: pick(BAOYU_RENDERINGS).id,
120604
+ text: pick(BAOYU_TEXT_LEVELS).id,
120605
+ mood: pick(BAOYU_MOODS).id,
120606
+ title: current.title
120607
+ };
120608
+ }
120609
+ //#endregion
118868
120610
  //#region lib/types/client/inspiration-view.js
118869
120611
  const FAVORITES_STORAGE_KEY = "dsh-ig-inspiration-favorites";
118870
120612
  function loadInspirationFavorites() {
@@ -118956,6 +120698,7 @@ img.ProseMirror-separator {
118956
120698
  allCategories: "全部分类",
118957
120699
  allStyles: "全部风格",
118958
120700
  allScenes: "全部场景",
120701
+ categoryNav: "分类导航",
118959
120702
  search: "搜索案例、Prompt、风格…",
118960
120703
  results: "找到 {count} 个案例",
118961
120704
  noResults: "没有匹配的素材,换个关键词或筛选条件试试。",
@@ -118965,6 +120708,9 @@ img.ProseMirror-separator {
118965
120708
  copied: "已复制",
118966
120709
  use: "使用这个 Prompt",
118967
120710
  source: "查看原来源",
120711
+ useReference: "提示词 + 参考图",
120712
+ referenceLoading: "获取参考图中…",
120713
+ referenceFailed: "参考图获取失败,请重试",
118968
120714
  loading: "正在读取素材库…",
118969
120715
  loadFailed: "素材库读取失败,请稍后重试。",
118970
120716
  copyFailed: "复制失败",
@@ -118979,7 +120725,21 @@ img.ProseMirror-separator {
118979
120725
  favorite: "收藏",
118980
120726
  favorited: "已收藏",
118981
120727
  zoomHint: "点击放大查看",
118982
- close: "关闭"
120728
+ close: "关闭",
120729
+ composeTab: "宝玉维度组合",
120730
+ composeHint: "五个维度自由拼接封面 Prompt,数据源自宝玉封图 skill(JimLiu/baoyu-skills,MIT)。",
120731
+ composeType: "类型",
120732
+ composePalette: "色板",
120733
+ composeRendering: "渲染",
120734
+ composeText: "文字",
120735
+ composeMood: "情绪",
120736
+ composeTitleLabel: "标题文字",
120737
+ composeTitlePlaceholder: "输入封面标题(文字层级含标题时必填)",
120738
+ composeTitleRequired: "该文字层级需要先填写标题。",
120739
+ composeRandomize: "随机组合",
120740
+ composeUse: "用此 Prompt 去生成",
120741
+ composeCombos: "共 {count} 种组合",
120742
+ composePreview: "拼接结果预览"
118983
120743
  },
118984
120744
  en: {
118985
120745
  kicker: "Prompt inspiration",
@@ -118999,6 +120759,7 @@ img.ProseMirror-separator {
118999
120759
  allCategories: "All categories",
119000
120760
  allStyles: "All styles",
119001
120761
  allScenes: "All scenes",
120762
+ categoryNav: "Categories",
119002
120763
  search: "Search examples, prompts, styles…",
119003
120764
  results: "{count} examples",
119004
120765
  noResults: "No matching examples. Try another keyword or filter.",
@@ -119008,6 +120769,9 @@ img.ProseMirror-separator {
119008
120769
  copied: "Copied",
119009
120770
  use: "Use this prompt",
119010
120771
  source: "View source",
120772
+ useReference: "Prompt + reference",
120773
+ referenceLoading: "Fetching image…",
120774
+ referenceFailed: "Failed to load the reference image",
119011
120775
  loading: "Loading inspiration…",
119012
120776
  loadFailed: "Could not load the inspiration library.",
119013
120777
  copyFailed: "Failed to copy",
@@ -119022,12 +120786,28 @@ img.ProseMirror-separator {
119022
120786
  favorite: "Favorite",
119023
120787
  favorited: "Favorited",
119024
120788
  zoomHint: "Click to zoom in",
119025
- close: "Close"
119026
- }
119027
- };
119028
- const InspirationView = ({ locale, onUsePrompt }) => {
120789
+ close: "Close",
120790
+ composeTab: "Baoyu Composer",
120791
+ composeHint: "Stitch a cover prompt from five dimensions, based on the baoyu cover-image skill (JimLiu/baoyu-skills, MIT).",
120792
+ composeType: "Type",
120793
+ composePalette: "Palette",
120794
+ composeRendering: "Rendering",
120795
+ composeText: "Text",
120796
+ composeMood: "Mood",
120797
+ composeTitleLabel: "Title text",
120798
+ composeTitlePlaceholder: "Cover title (required when the text level includes a title)",
120799
+ composeTitleRequired: "This text level needs a title first.",
120800
+ composeRandomize: "Randomize",
120801
+ composeUse: "Generate with this prompt",
120802
+ composeCombos: "{count} combinations",
120803
+ composePreview: "Composed prompt preview"
120804
+ }
120805
+ };
120806
+ const InspirationView = ({ locale, onUsePrompt, onUseReference }) => {
119029
120807
  const [language, setLanguage] = (0, react.useState)(() => locale?.getSnapshot?.().active?.startsWith("en") ? "en" : "zh");
119030
120808
  const [catalog, setCatalog] = (0, react.useState)(null);
120809
+ const [activeSourceId, setActiveSourceId] = (0, react.useState)("");
120810
+ const [baoyuSelection, setBaoyuSelection] = (0, react.useState)(BAOYU_DEFAULT_SELECTION);
119031
120811
  const [catalogError, setCatalogError] = (0, react.useState)(false);
119032
120812
  const [search, setSearch] = (0, react.useState)("");
119033
120813
  const [category, setCategory] = (0, react.useState)("");
@@ -119043,8 +120823,12 @@ img.ProseMirror-separator {
119043
120823
  const [onlyFavorites, setOnlyFavorites] = (0, react.useState)(false);
119044
120824
  const [lightboxCase, setLightboxCase] = (0, react.useState)(null);
119045
120825
  const [toast, setToast] = (0, react.useState)(null);
120826
+ const [referenceBusy, setReferenceBusy] = (0, react.useState)(false);
120827
+ const [pendingJump, setPendingJump] = (0, react.useState)("");
120828
+ const [activeCategory, setActiveCategory] = (0, react.useState)("");
119046
120829
  const toastTimerRef = (0, react.useRef)(null);
119047
120830
  const sentinelRef = (0, react.useRef)(null);
120831
+ const listRef = (0, react.useRef)(null);
119048
120832
  (0, react.useEffect)(() => {
119049
120833
  if (!locale?.subscribe) return;
119050
120834
  return locale.subscribe(() => setLanguage(locale.getSnapshot?.().active?.startsWith("en") ? "en" : "zh"));
@@ -119114,7 +120898,33 @@ img.ProseMirror-separator {
119114
120898
  mounted = false;
119115
120899
  };
119116
120900
  }, []);
119117
- const source = catalog?.sources[0] ?? null;
120901
+ const source = catalog?.sources.find((candidate) => candidate.id === activeSourceId) ?? catalog?.sources[0] ?? null;
120902
+ const composerActive = activeSourceId === BAOYU_COMPOSER_SOURCE_ID;
120903
+ /** Switch the active library; each source owns its own category/style/scene vocabularies. */
120904
+ const switchSource = (id) => {
120905
+ if (id === activeSourceId) return;
120906
+ setActiveSourceId(id);
120907
+ setCategory("");
120908
+ setStyle("");
120909
+ setScene("");
120910
+ setOnlyFavorites(false);
120911
+ setSelected(null);
120912
+ setPendingJump("");
120913
+ setActiveCategory("");
120914
+ };
120915
+ const useSelectedReference = async () => {
120916
+ if (selected === null || source === null || onUseReference === void 0) return;
120917
+ setReferenceBusy(true);
120918
+ try {
120919
+ const blob = await fetchInspirationImage(source.id, selected.id, source.imageRevision ?? source.version);
120920
+ const ext = blob.type === "image/jpeg" ? "jpg" : blob.type.split("/")[1]?.replace(/[^a-z0-9]/gi, "") || "png";
120921
+ onUseReference(new File([blob], `inspiration-${selected.id}.${ext}`, { type: blob.type.startsWith("image/") ? blob.type : "image/webp" }), selected.prompt);
120922
+ } catch {
120923
+ showToast(t("referenceFailed"), true);
120924
+ } finally {
120925
+ setReferenceBusy(false);
120926
+ }
120927
+ };
119118
120928
  const matchingCases = (0, react.useMemo)(() => {
119119
120929
  if (source === null) return [];
119120
120930
  const query = search.trim().toLowerCase();
@@ -119156,6 +120966,67 @@ img.ProseMirror-separator {
119156
120966
  onlyFavorites
119157
120967
  ]);
119158
120968
  const visibleCases = matchingCases.slice(0, visibleLimit);
120969
+ const categoryCounts = (0, react.useMemo)(() => {
120970
+ const counts = /* @__PURE__ */ new Map();
120971
+ for (const item of matchingCases) counts.set(item.category, (counts.get(item.category) ?? 0) + 1);
120972
+ return counts;
120973
+ }, [matchingCases]);
120974
+ const visibleGroups = (0, react.useMemo)(() => {
120975
+ if (source === null) return [];
120976
+ const byCategory = /* @__PURE__ */ new Map();
120977
+ for (const item of visibleCases) {
120978
+ const bucket = byCategory.get(item.category);
120979
+ if (bucket !== void 0) bucket.push(item);
120980
+ else byCategory.set(item.category, [item]);
120981
+ }
120982
+ return source.categories.flatMap((name) => {
120983
+ const items = byCategory.get(name);
120984
+ return items !== void 0 ? [{
120985
+ name,
120986
+ items
120987
+ }] : [];
120988
+ });
120989
+ }, [source, visibleCases]);
120990
+ const visibleGroupSignature = visibleGroups.map((group) => group.name).join("\0");
120991
+ const jumpToCategory = (name) => {
120992
+ if (category !== "") setCategory("");
120993
+ setPendingJump(name);
120994
+ };
120995
+ (0, react.useEffect)(() => {
120996
+ if (pendingJump === "") return;
120997
+ const index = matchingCases.findIndex((item) => item.category === pendingJump);
120998
+ if (index < 0) return;
120999
+ if (index >= visibleLimit) {
121000
+ setVisibleLimit(index + 60);
121001
+ return;
121002
+ }
121003
+ const raf = requestAnimationFrame(() => {
121004
+ document.getElementById(`dsh-ig-cat-${encodeURIComponent(pendingJump)}`)?.scrollIntoView({
121005
+ behavior: "smooth",
121006
+ block: "start"
121007
+ });
121008
+ setPendingJump("");
121009
+ });
121010
+ return () => cancelAnimationFrame(raf);
121011
+ }, [
121012
+ pendingJump,
121013
+ matchingCases,
121014
+ visibleLimit
121015
+ ]);
121016
+ (0, react.useEffect)(() => {
121017
+ const root = listRef.current;
121018
+ if (root === null) return;
121019
+ const headers = Array.from(root.querySelectorAll(".dsh-ig-inspiration-group-head"));
121020
+ if (headers.length === 0) return;
121021
+ const categoryByTarget = /* @__PURE__ */ new Map();
121022
+ for (const header of headers) categoryByTarget.set(header, header.dataset.category ?? "");
121023
+ const observer = new IntersectionObserver((entries) => {
121024
+ const visible = entries.find((entry) => entry.isIntersecting);
121025
+ if (visible !== void 0) setActiveCategory(categoryByTarget.get(visible.target) ?? "");
121026
+ }, { rootMargin: "0px 0px -75% 0px" });
121027
+ for (const header of headers) observer.observe(header);
121028
+ return () => observer.disconnect();
121029
+ }, [visibleGroupSignature]);
119159
121030
  (0, react.useEffect)(() => {
119160
121031
  const el = sentinelRef.current;
119161
121032
  if (!el) return;
@@ -119280,179 +121151,256 @@ img.ProseMirror-separator {
119280
121151
  className: "dsh-ig-spin",
119281
121152
  size: 23
119282
121153
  }), (0, react_jsx_runtime.jsx)("span", { children: t("loading") })]
119283
- }) : (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsxs)("div", {
119284
- className: "dsh-ig-inspiration-toolbar",
119285
- children: [
119286
- (0, react_jsx_runtime.jsxs)("label", {
119287
- className: "dsh-ig-inspiration-search",
119288
- children: [(0, react_jsx_runtime.jsx)(Search, { size: 15 }), (0, react_jsx_runtime.jsx)("input", {
119289
- value: search,
119290
- onChange: (event) => setSearch(event.target.value),
119291
- placeholder: t("search")
119292
- })]
119293
- }),
119294
- (0, react_jsx_runtime.jsxs)("button", {
121154
+ }) : (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [catalog !== null ? (0, react_jsx_runtime.jsxs)("div", {
121155
+ className: "dsh-ig-inspiration-sources",
121156
+ role: "tablist",
121157
+ children: [catalog.sources.map((item) => (0, react_jsx_runtime.jsx)("button", {
121158
+ type: "button",
121159
+ role: "tab",
121160
+ "aria-selected": !composerActive && item.id === source?.id,
121161
+ className: `dsh-ig-inspiration-source-chip ${!composerActive && item.id === source?.id ? "is-active" : ""}`,
121162
+ onClick: () => switchSource(item.id),
121163
+ children: item.label
121164
+ }, item.id)), (0, react_jsx_runtime.jsxs)("button", {
121165
+ type: "button",
121166
+ role: "tab",
121167
+ "aria-selected": composerActive,
121168
+ className: `dsh-ig-inspiration-source-chip ${composerActive ? "is-active" : ""}`,
121169
+ onClick: () => switchSource(BAOYU_COMPOSER_SOURCE_ID),
121170
+ children: [(0, react_jsx_runtime.jsx)(Sparkles, { size: 11 }), t("composeTab")]
121171
+ })]
121172
+ }) : null, composerActive ? (0, react_jsx_runtime.jsx)(BaoyuComposerPanel, {
121173
+ language,
121174
+ selection: baoyuSelection,
121175
+ onChange: setBaoyuSelection,
121176
+ onUsePrompt
121177
+ }) : (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
121178
+ (0, react_jsx_runtime.jsxs)("div", {
121179
+ className: "dsh-ig-inspiration-toolbar",
121180
+ children: [
121181
+ (0, react_jsx_runtime.jsxs)("label", {
121182
+ className: "dsh-ig-inspiration-search",
121183
+ children: [(0, react_jsx_runtime.jsx)(Search, { size: 15 }), (0, react_jsx_runtime.jsx)("input", {
121184
+ value: search,
121185
+ onChange: (event) => setSearch(event.target.value),
121186
+ placeholder: t("search")
121187
+ })]
121188
+ }),
121189
+ (0, react_jsx_runtime.jsxs)("button", {
121190
+ type: "button",
121191
+ className: `dsh-ig-inspiration-fav-filter ${onlyFavorites ? "is-active" : ""}`,
121192
+ onClick: () => setOnlyFavorites((prev) => !prev),
121193
+ title: t("onlyFavorites"),
121194
+ children: [(0, react_jsx_runtime.jsx)(Star, {
121195
+ size: 14,
121196
+ className: onlyFavorites ? "fill-star" : ""
121197
+ }), (0, react_jsx_runtime.jsxs)("span", { children: [t("onlyFavorites"), favorites.size > 0 ? ` (${favorites.size})` : ""] })]
121198
+ }),
121199
+ (0, react_jsx_runtime.jsxs)("select", {
121200
+ value: category,
121201
+ onChange: (event) => setCategory(event.target.value),
121202
+ "aria-label": t("allCategories"),
121203
+ children: [(0, react_jsx_runtime.jsx)("option", {
121204
+ value: "",
121205
+ children: t("allCategories")
121206
+ }), source.categories.map((item) => (0, react_jsx_runtime.jsx)("option", {
121207
+ value: item,
121208
+ children: translateTag(item, language)
121209
+ }, item))]
121210
+ }),
121211
+ (0, react_jsx_runtime.jsxs)("select", {
121212
+ value: style,
121213
+ onChange: (event) => setStyle(event.target.value),
121214
+ "aria-label": t("allStyles"),
121215
+ children: [(0, react_jsx_runtime.jsx)("option", {
121216
+ value: "",
121217
+ children: t("allStyles")
121218
+ }), source.styles.map((item) => (0, react_jsx_runtime.jsx)("option", {
121219
+ value: item,
121220
+ children: translateTag(item, language)
121221
+ }, item))]
121222
+ }),
121223
+ (0, react_jsx_runtime.jsxs)("select", {
121224
+ value: scene,
121225
+ onChange: (event) => setScene(event.target.value),
121226
+ "aria-label": t("allScenes"),
121227
+ children: [(0, react_jsx_runtime.jsx)("option", {
121228
+ value: "",
121229
+ children: t("allScenes")
121230
+ }), source.scenes.map((item) => (0, react_jsx_runtime.jsx)("option", {
121231
+ value: item,
121232
+ children: translateTag(item, language)
121233
+ }, item))]
121234
+ })
121235
+ ]
121236
+ }),
121237
+ (0, react_jsx_runtime.jsxs)("nav", {
121238
+ className: "dsh-ig-inspiration-catnav",
121239
+ "aria-label": t("categoryNav"),
121240
+ children: [(0, react_jsx_runtime.jsxs)("button", {
119295
121241
  type: "button",
119296
- className: `dsh-ig-inspiration-fav-filter ${onlyFavorites ? "is-active" : ""}`,
119297
- onClick: () => setOnlyFavorites((prev) => !prev),
119298
- title: t("onlyFavorites"),
119299
- children: [(0, react_jsx_runtime.jsx)(Star, {
119300
- size: 14,
119301
- className: onlyFavorites ? "fill-star" : ""
119302
- }), (0, react_jsx_runtime.jsxs)("span", { children: [t("onlyFavorites"), favorites.size > 0 ? ` (${favorites.size})` : ""] })]
119303
- }),
119304
- (0, react_jsx_runtime.jsxs)("select", {
119305
- value: category,
119306
- onChange: (event) => setCategory(event.target.value),
119307
- "aria-label": t("allCategories"),
119308
- children: [(0, react_jsx_runtime.jsx)("option", {
119309
- value: "",
119310
- children: t("allCategories")
119311
- }), source.categories.map((item) => (0, react_jsx_runtime.jsx)("option", {
119312
- value: item,
119313
- children: translateTag(item, language)
119314
- }, item))]
119315
- }),
119316
- (0, react_jsx_runtime.jsxs)("select", {
119317
- value: style,
119318
- onChange: (event) => setStyle(event.target.value),
119319
- "aria-label": t("allStyles"),
119320
- children: [(0, react_jsx_runtime.jsx)("option", {
119321
- value: "",
119322
- children: t("allStyles")
119323
- }), source.styles.map((item) => (0, react_jsx_runtime.jsx)("option", {
119324
- value: item,
119325
- children: translateTag(item, language)
119326
- }, item))]
119327
- }),
119328
- (0, react_jsx_runtime.jsxs)("select", {
119329
- value: scene,
119330
- onChange: (event) => setScene(event.target.value),
119331
- "aria-label": t("allScenes"),
119332
- children: [(0, react_jsx_runtime.jsx)("option", {
119333
- value: "",
119334
- children: t("allScenes")
119335
- }), source.scenes.map((item) => (0, react_jsx_runtime.jsx)("option", {
119336
- value: item,
119337
- children: translateTag(item, language)
119338
- }, item))]
119339
- })
119340
- ]
119341
- }), (0, react_jsx_runtime.jsxs)("div", {
119342
- className: "dsh-ig-inspiration-layout",
119343
- children: [(0, react_jsx_runtime.jsxs)("div", { children: [(0, react_jsx_runtime.jsxs)("div", {
119344
- className: "dsh-ig-inspiration-summary",
119345
- children: [(0, react_jsx_runtime.jsx)("strong", { children: t("results", { count: String(matchingCases.length) }) }), (0, react_jsx_runtime.jsx)("span", { children: source.version.slice(0, 8) })]
119346
- }), matchingCases.length === 0 ? (0, react_jsx_runtime.jsx)("div", {
119347
- className: "dsh-ig-inspiration-empty",
119348
- children: onlyFavorites ? t("noFavorites") : t("noResults")
119349
- }) : (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsx)("div", {
119350
- className: "dsh-ig-inspiration-grid",
119351
- children: visibleCases.map((item) => (0, react_jsx_runtime.jsx)(InspirationCard, {
119352
- item,
119353
- sourceId: source.id,
119354
- selected: selected?.id === item.id,
119355
- isFavorited: favorites.has(item.id),
119356
- language,
119357
- featuredLabel: t("featured"),
119358
- retryLabel: t("retry"),
119359
- onSelect: () => {
119360
- setSelected(item);
119361
- setCopied(false);
121242
+ className: `dsh-ig-inspiration-catnav-chip ${category === "" && activeCategory === "" ? "is-active" : ""}`,
121243
+ onClick: () => {
121244
+ if (category !== "") setCategory("");
121245
+ listRef.current?.scrollIntoView({
121246
+ behavior: "smooth",
121247
+ block: "start"
121248
+ });
119362
121249
  },
119363
- onToggleFavorite: () => toggleFavorite(item.id)
119364
- }, item.id))
119365
- }), visibleCases.length < matchingCases.length ? (0, react_jsx_runtime.jsx)("div", {
119366
- ref: sentinelRef,
119367
- style: {
119368
- height: 32,
119369
- margin: "16px 0"
119370
- }
119371
- }) : (0, react_jsx_runtime.jsx)("div", {
119372
- className: "dsh-ig-inspiration-end-hint",
119373
- children: t("allLoaded", { count: String(matchingCases.length) })
119374
- })] })] }), (0, react_jsx_runtime.jsx)("aside", {
119375
- className: "dsh-ig-inspiration-inspector",
119376
- children: selected === null ? (0, react_jsx_runtime.jsxs)("div", {
119377
- className: "dsh-ig-inspiration-inspector-empty",
119378
- children: [(0, react_jsx_runtime.jsx)(Image$2, { size: 28 }), (0, react_jsx_runtime.jsx)("span", { children: t("selectHint") })]
119379
- }) : (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsxs)("div", {
119380
- className: "dsh-ig-inspiration-inspector-image",
119381
- onClick: () => setLightboxCase({
119382
- sourceId: source.id,
119383
- caseId: selected.id,
119384
- title: selected.title,
119385
- alt: selected.imageAlt
119386
- }),
119387
- title: t("zoomHint"),
119388
- children: [(0, react_jsx_runtime.jsx)(InspirationImage, {
119389
- sourceId: source.id,
119390
- caseId: selected.id,
119391
- alt: selected.imageAlt,
119392
- retryLabel: t("retry")
119393
- }), (0, react_jsx_runtime.jsxs)("span", {
119394
- className: "dsh-ig-inspiration-inspector-zoom-hint",
119395
- children: [(0, react_jsx_runtime.jsx)(Maximize2, { size: 11 }), t("zoomHint")]
119396
- })]
119397
- }), (0, react_jsx_runtime.jsxs)("div", {
119398
- className: "dsh-ig-inspiration-inspector-body",
119399
- children: [
119400
- (0, react_jsx_runtime.jsxs)("div", {
119401
- className: "dsh-ig-inspiration-inspector-head",
119402
- children: [(0, react_jsx_runtime.jsx)("h2", { children: selected.title }), (0, react_jsx_runtime.jsx)("button", {
119403
- type: "button",
119404
- className: `dsh-ig-inspiration-inspector-fav ${favorites.has(selected.id) ? "is-favorited" : ""}`,
119405
- onClick: () => toggleFavorite(selected.id),
119406
- title: favorites.has(selected.id) ? t("favorited") : t("favorite"),
119407
- children: (0, react_jsx_runtime.jsx)(Star, {
119408
- size: 15,
119409
- className: favorites.has(selected.id) ? "fill-star" : ""
119410
- })
119411
- })]
119412
- }),
119413
- selected.sourceLabel && (0, react_jsx_runtime.jsx)("p", {
119414
- className: "dsh-ig-inspiration-origin",
119415
- children: selected.sourceLabel
119416
- }),
119417
- (0, react_jsx_runtime.jsxs)("div", {
119418
- className: "dsh-ig-inspiration-tags",
119419
- children: [(0, react_jsx_runtime.jsx)("span", { children: translateTag(selected.category, language) }), selected.styles.slice(0, 3).map((item) => (0, react_jsx_runtime.jsx)("span", { children: translateTag(item, language) }, item))]
119420
- }),
119421
- (0, react_jsx_runtime.jsxs)("div", {
119422
- className: "dsh-ig-inspiration-prompt-label",
119423
- children: [(0, react_jsx_runtime.jsx)("span", { children: t("prompt") }), (0, react_jsx_runtime.jsx)("span", { children: selected.prompt.length })]
119424
- }),
119425
- (0, react_jsx_runtime.jsx)("p", {
119426
- className: "dsh-ig-inspiration-prompt",
119427
- children: selected.prompt
121250
+ children: [(0, react_jsx_runtime.jsx)("span", { children: t("allCategories") }), (0, react_jsx_runtime.jsx)("em", { children: matchingCases.length })]
121251
+ }), source.categories.map((name) => {
121252
+ const count = categoryCounts.get(name) ?? 0;
121253
+ const isActive = category === name || category === "" && activeCategory === name;
121254
+ return (0, react_jsx_runtime.jsxs)("button", {
121255
+ type: "button",
121256
+ disabled: count === 0,
121257
+ "aria-current": isActive || void 0,
121258
+ className: `dsh-ig-inspiration-catnav-chip ${isActive ? "is-active" : ""}`,
121259
+ onClick: () => jumpToCategory(name),
121260
+ children: [(0, react_jsx_runtime.jsx)("span", { children: translateTag(name, language) }), (0, react_jsx_runtime.jsx)("em", { children: count })]
121261
+ }, name);
121262
+ })]
121263
+ }),
121264
+ (0, react_jsx_runtime.jsxs)("div", {
121265
+ className: "dsh-ig-inspiration-layout",
121266
+ children: [(0, react_jsx_runtime.jsxs)("div", { children: [(0, react_jsx_runtime.jsxs)("div", {
121267
+ className: "dsh-ig-inspiration-summary",
121268
+ 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) })]
121269
+ }), matchingCases.length === 0 ? (0, react_jsx_runtime.jsx)("div", {
121270
+ className: "dsh-ig-inspiration-empty",
121271
+ children: onlyFavorites ? t("noFavorites") : t("noResults")
121272
+ }) : (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsx)("div", {
121273
+ ref: listRef,
121274
+ className: "dsh-ig-inspiration-list",
121275
+ children: visibleGroups.map((group) => (0, react_jsx_runtime.jsxs)("section", {
121276
+ className: "dsh-ig-inspiration-group",
121277
+ children: [(0, react_jsx_runtime.jsxs)("h3", {
121278
+ id: `dsh-ig-cat-${encodeURIComponent(group.name)}`,
121279
+ "data-category": group.name,
121280
+ className: "dsh-ig-inspiration-group-head",
121281
+ 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 })]
121282
+ }), (0, react_jsx_runtime.jsx)("div", {
121283
+ className: "dsh-ig-inspiration-grid",
121284
+ children: group.items.map((item) => (0, react_jsx_runtime.jsx)(InspirationCard, {
121285
+ item,
121286
+ sourceId: source.id,
121287
+ sourceRevision: source.imageRevision ?? source.version,
121288
+ selected: selected?.id === item.id,
121289
+ isFavorited: favorites.has(item.id),
121290
+ language,
121291
+ featuredLabel: t("featured"),
121292
+ retryLabel: t("retry"),
121293
+ onSelect: () => {
121294
+ setSelected(item);
121295
+ setCopied(false);
121296
+ },
121297
+ onToggleFavorite: () => toggleFavorite(item.id)
121298
+ }, item.id))
121299
+ })]
121300
+ }, group.name))
121301
+ }), visibleCases.length < matchingCases.length ? (0, react_jsx_runtime.jsx)("div", {
121302
+ ref: sentinelRef,
121303
+ style: {
121304
+ height: 32,
121305
+ margin: "16px 0"
121306
+ }
121307
+ }) : (0, react_jsx_runtime.jsx)("div", {
121308
+ className: "dsh-ig-inspiration-end-hint",
121309
+ children: t("allLoaded", { count: String(matchingCases.length) })
121310
+ })] })] }), (0, react_jsx_runtime.jsx)("aside", {
121311
+ className: "dsh-ig-inspiration-inspector",
121312
+ children: selected === null ? (0, react_jsx_runtime.jsxs)("div", {
121313
+ className: "dsh-ig-inspiration-inspector-empty",
121314
+ children: [(0, react_jsx_runtime.jsx)(Image$2, { size: 28 }), (0, react_jsx_runtime.jsx)("span", { children: t("selectHint") })]
121315
+ }) : (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsxs)("div", {
121316
+ className: "dsh-ig-inspiration-inspector-image",
121317
+ onClick: () => setLightboxCase({
121318
+ sourceId: source.id,
121319
+ caseId: selected.id,
121320
+ revision: source.imageRevision ?? source.version,
121321
+ title: selected.title,
121322
+ alt: selected.imageAlt
119428
121323
  }),
119429
- (0, react_jsx_runtime.jsxs)("div", {
119430
- className: "dsh-ig-inspiration-actions",
119431
- children: [
119432
- (0, react_jsx_runtime.jsxs)("button", {
119433
- type: "button",
119434
- onClick: () => void copyPrompt(),
119435
- children: [copied ? (0, react_jsx_runtime.jsx)(Check, { size: 14 }) : (0, react_jsx_runtime.jsx)(Clipboard, { size: 14 }), copied ? t("copied") : t("copy")]
119436
- }),
119437
- selected.sourceUrl ?? selected.githubUrl ? (0, react_jsx_runtime.jsxs)("a", {
119438
- className: "dsh-ig-inspiration-source-link",
119439
- href: selected.sourceUrl ?? selected.githubUrl,
119440
- target: "_blank",
119441
- rel: "noreferrer",
119442
- children: [(0, react_jsx_runtime.jsx)(ExternalLink, { size: 14 }), t("source")]
119443
- }) : (0, react_jsx_runtime.jsx)("span", {}),
119444
- (0, react_jsx_runtime.jsxs)("button", {
121324
+ title: t("zoomHint"),
121325
+ children: [(0, react_jsx_runtime.jsx)(InspirationImage, {
121326
+ sourceId: source.id,
121327
+ caseId: selected.id,
121328
+ revision: source.imageRevision ?? source.version,
121329
+ alt: selected.imageAlt,
121330
+ retryLabel: t("retry")
121331
+ }), (0, react_jsx_runtime.jsxs)("span", {
121332
+ className: "dsh-ig-inspiration-inspector-zoom-hint",
121333
+ children: [(0, react_jsx_runtime.jsx)(Maximize2, { size: 11 }), t("zoomHint")]
121334
+ })]
121335
+ }), (0, react_jsx_runtime.jsxs)("div", {
121336
+ className: "dsh-ig-inspiration-inspector-body",
121337
+ children: [
121338
+ (0, react_jsx_runtime.jsxs)("div", {
121339
+ className: "dsh-ig-inspiration-inspector-head",
121340
+ children: [(0, react_jsx_runtime.jsx)("h2", { children: selected.title }), (0, react_jsx_runtime.jsx)("button", {
119445
121341
  type: "button",
119446
- className: "dsh-ig-inspiration-use",
119447
- onClick: () => onUsePrompt(selected.prompt),
119448
- children: [(0, react_jsx_runtime.jsx)(Sparkles, { size: 14 }), t("use")]
119449
- })
119450
- ]
119451
- })
119452
- ]
119453
- })] })
119454
- })]
119455
- })] }),
121342
+ className: `dsh-ig-inspiration-inspector-fav ${favorites.has(selected.id) ? "is-favorited" : ""}`,
121343
+ onClick: () => toggleFavorite(selected.id),
121344
+ title: favorites.has(selected.id) ? t("favorited") : t("favorite"),
121345
+ children: (0, react_jsx_runtime.jsx)(Star, {
121346
+ size: 15,
121347
+ className: favorites.has(selected.id) ? "fill-star" : ""
121348
+ })
121349
+ })]
121350
+ }),
121351
+ selected.sourceLabel && (0, react_jsx_runtime.jsx)("p", {
121352
+ className: "dsh-ig-inspiration-origin",
121353
+ children: selected.sourceLabel
121354
+ }),
121355
+ (0, react_jsx_runtime.jsxs)("div", {
121356
+ className: "dsh-ig-inspiration-tags",
121357
+ 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))]
121358
+ }),
121359
+ (0, react_jsx_runtime.jsxs)("div", {
121360
+ className: "dsh-ig-inspiration-prompt-label",
121361
+ children: [(0, react_jsx_runtime.jsx)("span", { children: t("prompt") }), (0, react_jsx_runtime.jsx)("span", { children: selected.prompt.length })]
121362
+ }),
121363
+ (0, react_jsx_runtime.jsx)("p", {
121364
+ className: "dsh-ig-inspiration-prompt",
121365
+ children: selected.prompt
121366
+ }),
121367
+ (0, react_jsx_runtime.jsxs)("div", {
121368
+ className: "dsh-ig-inspiration-actions",
121369
+ children: [
121370
+ (0, react_jsx_runtime.jsxs)("button", {
121371
+ type: "button",
121372
+ onClick: () => void copyPrompt(),
121373
+ children: [copied ? (0, react_jsx_runtime.jsx)(Check, { size: 14 }) : (0, react_jsx_runtime.jsx)(Clipboard, { size: 14 }), copied ? t("copied") : t("copy")]
121374
+ }),
121375
+ selected.sourceUrl ?? selected.githubUrl ? (0, react_jsx_runtime.jsxs)("a", {
121376
+ className: "dsh-ig-inspiration-source-link",
121377
+ href: selected.sourceUrl ?? selected.githubUrl,
121378
+ target: "_blank",
121379
+ rel: "noreferrer",
121380
+ children: [(0, react_jsx_runtime.jsx)(ExternalLink, { size: 14 }), t("source")]
121381
+ }) : (0, react_jsx_runtime.jsx)("span", {}),
121382
+ (0, react_jsx_runtime.jsxs)("button", {
121383
+ type: "button",
121384
+ className: "dsh-ig-inspiration-use",
121385
+ onClick: () => onUsePrompt(selected.prompt),
121386
+ children: [(0, react_jsx_runtime.jsx)(Sparkles, { size: 14 }), t("use")]
121387
+ }),
121388
+ onUseReference !== void 0 ? (0, react_jsx_runtime.jsxs)("button", {
121389
+ type: "button",
121390
+ className: "dsh-ig-inspiration-use is-secondary",
121391
+ disabled: referenceBusy,
121392
+ onClick: () => {
121393
+ useSelectedReference();
121394
+ },
121395
+ children: [(0, react_jsx_runtime.jsx)(ImagePlus, { size: 14 }), referenceBusy ? t("referenceLoading") : t("useReference")]
121396
+ }) : null
121397
+ ]
121398
+ })
121399
+ ]
121400
+ })] })
121401
+ })]
121402
+ })
121403
+ ] })] }),
119456
121404
  lightboxCase !== null && (0, react_jsx_runtime.jsx)("div", {
119457
121405
  className: "dsh-ig-inspiration-lightbox",
119458
121406
  onClick: () => setLightboxCase(null),
@@ -119472,6 +121420,7 @@ img.ProseMirror-separator {
119472
121420
  children: (0, react_jsx_runtime.jsx)(InspirationImage, {
119473
121421
  sourceId: lightboxCase.sourceId,
119474
121422
  caseId: lightboxCase.caseId,
121423
+ revision: lightboxCase.revision,
119475
121424
  alt: lightboxCase.alt,
119476
121425
  retryLabel: t("retry")
119477
121426
  })
@@ -119524,7 +121473,155 @@ img.ProseMirror-separator {
119524
121473
  ]
119525
121474
  });
119526
121475
  };
119527
- const InspirationCard = ({ item, sourceId, selected, isFavorited, language, featuredLabel, retryLabel, onSelect, onToggleFavorite }) => (0, react_jsx_runtime.jsxs)("button", {
121476
+ const BaoyuComposerPanel = ({ language, selection, onChange, onUsePrompt }) => {
121477
+ const [copied, setCopied] = (0, react.useState)(false);
121478
+ const [copyFailed, setCopyFailed] = (0, react.useState)(false);
121479
+ const tr = (key, params) => {
121480
+ let text = COPY[language][key] ?? COPY.zh[key];
121481
+ if (params) for (const [k, v] of Object.entries(params)) text = text.replace(`{${k}}`, v);
121482
+ return text;
121483
+ };
121484
+ const label = (id) => language === "zh" ? BAOYU_LABELS_ZH[id] ?? id : id;
121485
+ const needsTitle = selection.text !== "none";
121486
+ const titleReady = !needsTitle || selection.title.trim() !== "";
121487
+ let prompt = "";
121488
+ if (titleReady) try {
121489
+ prompt = composeBaoyuPrompt(selection);
121490
+ } catch {
121491
+ prompt = "";
121492
+ }
121493
+ const combos = BAOYU_TYPES.length * BAOYU_PALETTES.length * BAOYU_RENDERINGS.length * BAOYU_TEXT_LEVELS.length * BAOYU_MOODS.length;
121494
+ const dimension = (name, key, options) => (0, react_jsx_runtime.jsxs)("div", {
121495
+ className: "dsh-ig-baoyu-dim",
121496
+ children: [(0, react_jsx_runtime.jsx)("div", {
121497
+ className: "dsh-ig-baoyu-dim-head",
121498
+ children: name
121499
+ }), (0, react_jsx_runtime.jsx)("div", {
121500
+ className: "dsh-ig-baoyu-chips",
121501
+ children: options.map((option) => {
121502
+ const palette = key === "palette" ? BAOYU_PALETTES.find((item) => item.id === option.id) : void 0;
121503
+ return (0, react_jsx_runtime.jsxs)("button", {
121504
+ type: "button",
121505
+ className: `dsh-ig-baoyu-chip ${selection[key] === option.id ? "is-active" : ""}`,
121506
+ onClick: () => {
121507
+ onChange({
121508
+ ...selection,
121509
+ [key]: option.id
121510
+ });
121511
+ setCopied(false);
121512
+ },
121513
+ title: language === "zh" ? palette?.tagline ?? option.id : option.id,
121514
+ children: [(0, react_jsx_runtime.jsx)("span", { children: label(option.id) }), palette !== void 0 ? (0, react_jsx_runtime.jsx)("span", {
121515
+ className: "dsh-ig-baoyu-swatch",
121516
+ children: [
121517
+ palette.colors[0],
121518
+ palette.colors.find((color) => color.role === "Background"),
121519
+ palette.colors.find((color) => color.role === "Accent 1")
121520
+ ].map((color, index) => color === void 0 ? null : (0, react_jsx_runtime.jsx)("i", { style: { background: color.hex } }, index))
121521
+ }) : null]
121522
+ }, option.id);
121523
+ })
121524
+ })]
121525
+ }, name);
121526
+ const copyPrompt = async () => {
121527
+ if (prompt === "") return;
121528
+ try {
121529
+ await navigator.clipboard.writeText(prompt);
121530
+ setCopied(true);
121531
+ setCopyFailed(false);
121532
+ } catch {
121533
+ setCopyFailed(true);
121534
+ }
121535
+ };
121536
+ return (0, react_jsx_runtime.jsxs)("div", {
121537
+ className: "dsh-ig-baoyu-panel",
121538
+ children: [
121539
+ (0, react_jsx_runtime.jsxs)("div", {
121540
+ className: "dsh-ig-baoyu-intro",
121541
+ children: [(0, react_jsx_runtime.jsx)("strong", {
121542
+ className: "dsh-ig-baoyu-combos",
121543
+ children: tr("composeCombos", { count: String(combos) })
121544
+ }), (0, react_jsx_runtime.jsx)("span", {
121545
+ className: "dsh-ig-baoyu-hint",
121546
+ children: tr("composeHint")
121547
+ })]
121548
+ }),
121549
+ dimension(tr("composeType"), "type", BAOYU_TYPES),
121550
+ dimension(tr("composePalette"), "palette", BAOYU_PALETTES),
121551
+ dimension(tr("composeRendering"), "rendering", BAOYU_RENDERINGS),
121552
+ dimension(tr("composeText"), "text", BAOYU_TEXT_LEVELS),
121553
+ dimension(tr("composeMood"), "mood", BAOYU_MOODS),
121554
+ (0, react_jsx_runtime.jsxs)("div", {
121555
+ className: "dsh-ig-baoyu-dim",
121556
+ children: [
121557
+ (0, react_jsx_runtime.jsx)("div", {
121558
+ className: "dsh-ig-baoyu-dim-head",
121559
+ children: tr("composeTitleLabel")
121560
+ }),
121561
+ (0, react_jsx_runtime.jsx)("input", {
121562
+ className: "dsh-ig-baoyu-title-input",
121563
+ value: selection.title,
121564
+ placeholder: tr("composeTitlePlaceholder"),
121565
+ disabled: selection.text === "none",
121566
+ onChange: (event) => {
121567
+ onChange({
121568
+ ...selection,
121569
+ title: event.target.value
121570
+ });
121571
+ setCopied(false);
121572
+ }
121573
+ }),
121574
+ needsTitle && !titleReady ? (0, react_jsx_runtime.jsx)("div", {
121575
+ className: "dsh-ig-baoyu-required",
121576
+ children: tr("composeTitleRequired")
121577
+ }) : null
121578
+ ]
121579
+ }),
121580
+ (0, react_jsx_runtime.jsxs)("div", {
121581
+ className: "dsh-ig-baoyu-actions",
121582
+ children: [
121583
+ (0, react_jsx_runtime.jsxs)("button", {
121584
+ type: "button",
121585
+ className: "dsh-ig-baoyu-btn",
121586
+ onClick: () => onChange(randomBaoyuSelection(selection)),
121587
+ children: [(0, react_jsx_runtime.jsx)(Shuffle, { size: 13 }), (0, react_jsx_runtime.jsx)("span", { children: tr("composeRandomize") })]
121588
+ }),
121589
+ (0, react_jsx_runtime.jsxs)("button", {
121590
+ type: "button",
121591
+ className: "dsh-ig-baoyu-btn",
121592
+ disabled: prompt === "",
121593
+ onClick: () => {
121594
+ copyPrompt();
121595
+ },
121596
+ 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") })]
121597
+ }),
121598
+ (0, react_jsx_runtime.jsxs)("button", {
121599
+ type: "button",
121600
+ className: "dsh-ig-baoyu-btn is-primary",
121601
+ disabled: prompt === "",
121602
+ onClick: () => onUsePrompt(prompt),
121603
+ children: [(0, react_jsx_runtime.jsx)(Sparkles, { size: 13 }), (0, react_jsx_runtime.jsx)("span", { children: tr("composeUse") })]
121604
+ }),
121605
+ copyFailed ? (0, react_jsx_runtime.jsx)("span", {
121606
+ className: "dsh-ig-baoyu-required",
121607
+ children: tr("copyFailed")
121608
+ }) : null
121609
+ ]
121610
+ }),
121611
+ prompt !== "" ? (0, react_jsx_runtime.jsxs)("div", {
121612
+ className: "dsh-ig-baoyu-preview-block",
121613
+ children: [(0, react_jsx_runtime.jsx)("div", {
121614
+ className: "dsh-ig-baoyu-preview-head",
121615
+ children: tr("composePreview")
121616
+ }), (0, react_jsx_runtime.jsx)("pre", {
121617
+ className: "dsh-ig-baoyu-preview",
121618
+ children: prompt
121619
+ })]
121620
+ }) : null
121621
+ ]
121622
+ });
121623
+ };
121624
+ const InspirationCard = ({ item, sourceId, sourceRevision, selected, isFavorited, language, featuredLabel, retryLabel, onSelect, onToggleFavorite }) => (0, react_jsx_runtime.jsxs)("button", {
119528
121625
  type: "button",
119529
121626
  className: `dsh-ig-inspiration-card ${selected ? "is-selected" : ""}`,
119530
121627
  onClick: onSelect,
@@ -119551,6 +121648,7 @@ img.ProseMirror-separator {
119551
121648
  (0, react_jsx_runtime.jsx)(InspirationImage, {
119552
121649
  sourceId,
119553
121650
  caseId: item.id,
121651
+ revision: sourceRevision,
119554
121652
  alt: item.imageAlt,
119555
121653
  retryLabel
119556
121654
  })
@@ -119560,7 +121658,7 @@ img.ProseMirror-separator {
119560
121658
  children: [(0, react_jsx_runtime.jsx)("strong", { children: item.title }), (0, react_jsx_runtime.jsx)("span", { children: translateTag(item.category, language) })]
119561
121659
  })]
119562
121660
  });
119563
- const InspirationImage = ({ sourceId, caseId, alt, retryLabel = "Retry" }) => {
121661
+ const InspirationImage = ({ sourceId, caseId, revision, alt, retryLabel = "Retry" }) => {
119564
121662
  const [node, setNode] = (0, react.useState)(null);
119565
121663
  const [visible, setVisible] = (0, react.useState)(false);
119566
121664
  const [url, setUrl] = (0, react.useState)(null);
@@ -119581,7 +121679,7 @@ img.ProseMirror-separator {
119581
121679
  urlRef.current = null;
119582
121680
  }
119583
121681
  setUrl(null);
119584
- fetchInspirationImage(sourceId, caseId).then((blob) => {
121682
+ fetchInspirationImage(sourceId, caseId, revision).then((blob) => {
119585
121683
  if (!active) return;
119586
121684
  const next = URL.createObjectURL(blob);
119587
121685
  if (urlRef.current !== null) URL.revokeObjectURL(urlRef.current);
@@ -119596,7 +121694,8 @@ img.ProseMirror-separator {
119596
121694
  }, [
119597
121695
  visible,
119598
121696
  sourceId,
119599
- caseId
121697
+ caseId,
121698
+ revision
119600
121699
  ]);
119601
121700
  (0, react.useEffect)(() => {
119602
121701
  return () => {
@@ -119633,7 +121732,7 @@ img.ProseMirror-separator {
119633
121732
  urlRef.current = null;
119634
121733
  }
119635
121734
  setUrl(null);
119636
- evictInspirationImage(sourceId, caseId).then(() => fetchInspirationImage(sourceId, caseId)).then((blob) => {
121735
+ evictInspirationImage(sourceId, caseId, revision).then(() => fetchInspirationImage(sourceId, caseId, revision)).then((blob) => {
119637
121736
  const next = URL.createObjectURL(blob);
119638
121737
  if (urlRef.current !== null) URL.revokeObjectURL(urlRef.current);
119639
121738
  urlRef.current = next;
@@ -119751,6 +121850,12 @@ img.ProseMirror-separator {
119751
121850
  filterXAI: "xAI Grok",
119752
121851
  filterZhipu: "智谱 GLM",
119753
121852
  filterComfyUI: "本地 ComfyUI",
121853
+ filterImport: "导入",
121854
+ importImages: "导入图片",
121855
+ importAdded: "已导入 {count} 张图片",
121856
+ importPartial: "导入 {count} 张,{fail} 张失败",
121857
+ importFailed: "导入失败",
121858
+ importPickHint: "选择要导入的图片(PNG/JPG/WebP/GIF)",
119754
121859
  filterAllModels: "全部模型",
119755
121860
  filterAllRatios: "全部比例",
119756
121861
  searchPlaceholder: "搜索 Prompt、标签…",
@@ -119847,6 +121952,12 @@ img.ProseMirror-separator {
119847
121952
  filterXAI: "xAI Grok",
119848
121953
  filterZhipu: "Zhipu GLM",
119849
121954
  filterComfyUI: "Local ComfyUI",
121955
+ filterImport: "Imported",
121956
+ importImages: "Import images",
121957
+ importAdded: "Imported {count} images",
121958
+ importPartial: "Imported {count}, {fail} failed",
121959
+ importFailed: "Import failed",
121960
+ importPickHint: "Choose images to import (PNG/JPG/WebP/GIF)",
119850
121961
  filterAllModels: "All Models",
119851
121962
  filterAllRatios: "All Ratios",
119852
121963
  searchPlaceholder: "Search prompt, tags…",
@@ -119929,6 +122040,19 @@ img.ProseMirror-separator {
119929
122040
  }
119930
122041
  };
119931
122042
  /** Format human-readable relative time */
122043
+ /** Read a browser-picked file as base64 payload for the import route. */
122044
+ function readFileBase64(file) {
122045
+ return new Promise((resolve, reject) => {
122046
+ const reader = new FileReader();
122047
+ reader.onload = () => {
122048
+ const result = reader.result;
122049
+ if (typeof result === "string") resolve(result.slice(result.indexOf(",") + 1));
122050
+ else reject(/* @__PURE__ */ new Error("read-failed"));
122051
+ };
122052
+ reader.onerror = () => reject(/* @__PURE__ */ new Error("read-failed"));
122053
+ reader.readAsDataURL(file);
122054
+ });
122055
+ }
119932
122056
  function formatRelativeTime(timestamp, t) {
119933
122057
  const diff = Date.now() - timestamp;
119934
122058
  if (diff < 6e4) return t("justNow");
@@ -119949,6 +122073,19 @@ img.ProseMirror-separator {
119949
122073
  }
119950
122074
  /** Extract standard aspect ratio for precise filtering */
119951
122075
  function getItemRatio(item) {
122076
+ if (item.provider === "chatgpt-sub" && item.attachment.width && item.attachment.height) {
122077
+ const actual = item.attachment.width / item.attachment.height;
122078
+ for (const [label, value] of [
122079
+ ["1:1", 1],
122080
+ ["16:9", 16 / 9],
122081
+ ["9:16", 9 / 16],
122082
+ ["4:3", 4 / 3],
122083
+ ["3:4", 3 / 4],
122084
+ ["3:2", 3 / 2],
122085
+ ["2:3", 2 / 3]
122086
+ ]) if (Math.abs(actual / value - 1) < .08) return label;
122087
+ return "custom";
122088
+ }
119952
122089
  if (item.aspectRatio && item.aspectRatio !== "custom") return item.aspectRatio;
119953
122090
  if (item.output) {
119954
122091
  const ratioMatch = item.output.match(/\b(1:1|16:9|9:16|4:3|3:4|3:2|2:3)\b/);
@@ -119987,6 +122124,7 @@ img.ProseMirror-separator {
119987
122124
  const { locale, sessionId, useSessions, useWorkspaces, credentialEvents, inSidebar, defaultTab, initialCanvasSurface, useTabInfo } = props;
119988
122125
  const [activeTab, setActiveTab] = (0, react.useState)(defaultTab ?? "gallery");
119989
122126
  const [studioDraft, setStudioDraft] = (0, react.useState)(void 0);
122127
+ const [studioReference, setStudioReference] = (0, react.useState)(void 0);
119990
122128
  const [items, setItems] = (0, react.useState)([]);
119991
122129
  const [search, setSearch] = (0, react.useState)("");
119992
122130
  const [selectedProvider, setSelectedProvider] = (0, react.useState)("all");
@@ -120101,11 +122239,80 @@ img.ProseMirror-separator {
120101
122239
  setToast(null);
120102
122240
  }, 2e3);
120103
122241
  };
122242
+ const [importing, setImporting] = (0, react.useState)(false);
122243
+ const importInputRef = (0, react.useRef)(null);
122244
+ const importImages = async (files) => {
122245
+ if (files === null || files.length === 0 || importing) return;
122246
+ const all = Array.from(files);
122247
+ const picked = all.filter((file) => file.type.startsWith("image/")).slice(0, 8);
122248
+ if (picked.length === 0) {
122249
+ showToast(t("importFailed"));
122250
+ return;
122251
+ }
122252
+ setImporting(true);
122253
+ try {
122254
+ const reads = await Promise.allSettled(picked.map((file) => readFileBase64(file)));
122255
+ const images = reads.flatMap((read, index) => {
122256
+ if (read.status !== "fulfilled") return [];
122257
+ const file = picked[index];
122258
+ return [{
122259
+ data: read.value,
122260
+ mediaType: file.type,
122261
+ ...file.name.length > 0 ? { name: file.name } : {}
122262
+ }];
122263
+ });
122264
+ const readFailures = reads.length - images.length;
122265
+ if (images.length === 0) {
122266
+ showToast(t("importFailed"));
122267
+ return;
122268
+ }
122269
+ const response = await fetch(IMPORT_ROUTE, {
122270
+ method: "POST",
122271
+ credentials: "same-origin",
122272
+ headers: { "content-type": "application/json" },
122273
+ body: JSON.stringify({ images })
122274
+ });
122275
+ if (!response.ok) throw new Error(`HTTP ${String(response.status)}`);
122276
+ const payload = await response.json();
122277
+ const imported = payload.images ?? [];
122278
+ let added = 0;
122279
+ let writeFailures = 0;
122280
+ for (const entry of imported) if (await saveGalleryItem({
122281
+ id: `import-${entry.attachment.attachmentId}-${crypto.randomUUID()}`,
122282
+ attachment: entry.attachment,
122283
+ prompt: "",
122284
+ provider: "import",
122285
+ model: "",
122286
+ createdAt: Date.now(),
122287
+ ...activeWorkspace?.workspaceId ? { workspaceId: activeWorkspace.workspaceId } : {},
122288
+ ...activeWorkspace?.path ? { workspacePath: activeWorkspace.path } : {},
122289
+ ...currentSessionId ? { sessionId: currentSessionId } : {}
122290
+ })) added += 1;
122291
+ else writeFailures += 1;
122292
+ const failCount = (payload.failures ?? []).length + (all.length - picked.length) + readFailures + writeFailures;
122293
+ if (added === 0) showToast(t("importFailed"));
122294
+ else if (failCount > 0) showToast(t("importPartial", {
122295
+ count: String(added),
122296
+ fail: String(failCount)
122297
+ }));
122298
+ else showToast(t("importAdded", { count: String(added) }));
122299
+ } catch {
122300
+ showToast(t("importFailed"));
122301
+ } finally {
122302
+ setImporting(false);
122303
+ }
122304
+ };
120104
122305
  const useInspirationPrompt = (0, react.useCallback)((prompt) => {
120105
122306
  setStudioDraft(prompt);
120106
122307
  setActiveTab("studio");
120107
122308
  }, []);
122309
+ const useInspirationReference = (0, react.useCallback)((file, prompt) => {
122310
+ setStudioDraft(prompt);
122311
+ setStudioReference(file);
122312
+ setActiveTab("studio");
122313
+ }, []);
120108
122314
  const clearStudioDraft = (0, react.useCallback)(() => setStudioDraft(void 0), []);
122315
+ const clearStudioReference = (0, react.useCallback)(() => setStudioReference(void 0), []);
120109
122316
  (0, react.useEffect)(() => {
120110
122317
  if (inSidebar) return void 0;
120111
122318
  const seat = document.querySelector("[data-composer-seat]");
@@ -120655,6 +122862,10 @@ img.ProseMirror-separator {
120655
122862
  (0, react_jsx_runtime.jsx)("option", {
120656
122863
  value: "comfyui",
120657
122864
  children: t("filterComfyUI")
122865
+ }),
122866
+ (0, react_jsx_runtime.jsx)("option", {
122867
+ value: "import",
122868
+ children: t("filterImport")
120658
122869
  })
120659
122870
  ]
120660
122871
  }),
@@ -120737,27 +122948,48 @@ img.ProseMirror-separator {
120737
122948
  ]
120738
122949
  }), (0, react_jsx_runtime.jsxs)("div", {
120739
122950
  className: "dsh-ig-studio-toolbar-right",
120740
- children: [(0, react_jsx_runtime.jsxs)("select", {
120741
- className: "dsh-ig-studio-select dsh-ig-studio-select-sort",
120742
- value: sortBy,
120743
- onChange: (e) => setSortBy(e.target.value),
120744
- children: [(0, react_jsx_runtime.jsx)("option", {
120745
- value: "newest",
120746
- children: t("sortNewest")
120747
- }), (0, react_jsx_runtime.jsx)("option", {
120748
- value: "oldest",
120749
- children: t("sortOldest")
120750
- })]
120751
- }), (0, react_jsx_runtime.jsxs)("button", {
120752
- type: "button",
120753
- className: `dsh-ig-studio-btn ${isManageMode ? "is-active" : ""}`,
120754
- title: isManageMode ? t("exitManage") : t("manage"),
120755
- onClick: () => {
120756
- if (isManageMode) handleExitManage();
120757
- else setIsManageMode(true);
120758
- },
120759
- children: [(0, react_jsx_runtime.jsx)(SquareCheckBig, { size: 13 }), (0, react_jsx_runtime.jsx)("span", { children: isManageMode ? t("exitManage") : t("manage") })]
120760
- })]
122951
+ children: [
122952
+ activeTab === "gallery" && (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsx)("input", {
122953
+ ref: importInputRef,
122954
+ type: "file",
122955
+ accept: "image/png,image/jpeg,image/webp,image/gif",
122956
+ multiple: true,
122957
+ style: { display: "none" },
122958
+ onChange: (event) => {
122959
+ importImages(event.target.files);
122960
+ event.target.value = "";
122961
+ }
122962
+ }), (0, react_jsx_runtime.jsxs)("button", {
122963
+ type: "button",
122964
+ className: "dsh-ig-studio-btn",
122965
+ disabled: importing,
122966
+ title: t("importPickHint"),
122967
+ onClick: () => importInputRef.current?.click(),
122968
+ children: [(0, react_jsx_runtime.jsx)(Upload, { size: 13 }), (0, react_jsx_runtime.jsx)("span", { children: t("importImages") })]
122969
+ })] }),
122970
+ (0, react_jsx_runtime.jsxs)("select", {
122971
+ className: "dsh-ig-studio-select dsh-ig-studio-select-sort",
122972
+ value: sortBy,
122973
+ onChange: (e) => setSortBy(e.target.value),
122974
+ children: [(0, react_jsx_runtime.jsx)("option", {
122975
+ value: "newest",
122976
+ children: t("sortNewest")
122977
+ }), (0, react_jsx_runtime.jsx)("option", {
122978
+ value: "oldest",
122979
+ children: t("sortOldest")
122980
+ })]
122981
+ }),
122982
+ (0, react_jsx_runtime.jsxs)("button", {
122983
+ type: "button",
122984
+ className: `dsh-ig-studio-btn ${isManageMode ? "is-active" : ""}`,
122985
+ title: isManageMode ? t("exitManage") : t("manage"),
122986
+ onClick: () => {
122987
+ if (isManageMode) handleExitManage();
122988
+ else setIsManageMode(true);
122989
+ },
122990
+ children: [(0, react_jsx_runtime.jsx)(SquareCheckBig, { size: 13 }), (0, react_jsx_runtime.jsx)("span", { children: isManageMode ? t("exitManage") : t("manage") })]
122991
+ })
122992
+ ]
120761
122993
  })]
120762
122994
  }),
120763
122995
  (0, react_jsx_runtime.jsx)("div", {
@@ -120827,15 +123059,18 @@ img.ProseMirror-separator {
120827
123059
  }, item.id))
120828
123060
  }) : activeTab === "inspiration" ? (0, react_jsx_runtime.jsx)(InspirationView, {
120829
123061
  locale,
120830
- onUsePrompt: useInspirationPrompt
123062
+ onUsePrompt: useInspirationPrompt,
123063
+ onUseReference: useInspirationReference
120831
123064
  }) : (0, react_jsx_runtime.jsx)(StudioView, {
120832
123065
  locale,
120833
123066
  credentialEvents,
120834
123067
  workspace: activeWorkspace,
120835
123068
  initialPrompt: studioDraft,
123069
+ initialReference: studioReference,
120836
123070
  initialCanvasSurface,
120837
123071
  showInfiniteCanvasHint: !inSidebar,
120838
123072
  onInitialPromptApplied: clearStudioDraft,
123073
+ onInitialReferenceApplied: clearStudioReference,
120839
123074
  onOpenInspiration: () => setActiveTab("inspiration")
120840
123075
  })
120841
123076
  }),
@@ -120995,7 +123230,7 @@ img.ProseMirror-separator {
120995
123230
  children: [
120996
123231
  (0, react_jsx_runtime.jsx)("span", {
120997
123232
  className: "dsh-ig-tag",
120998
- children: previewItem.provider
123233
+ children: previewItem.provider === "import" ? t("filterImport") : previewItem.provider
120999
123234
  }),
121000
123235
  previewItem.model && (0, react_jsx_runtime.jsx)("span", {
121001
123236
  className: "dsh-ig-tag dsh-ig-tag-model",
@@ -121053,7 +123288,7 @@ img.ProseMirror-separator {
121053
123288
  children: [
121054
123289
  (0, react_jsx_runtime.jsx)("span", {
121055
123290
  className: "dsh-ig-tag",
121056
- children: previewItem.provider
123291
+ children: previewItem.provider === "import" ? t("filterImport") : previewItem.provider
121057
123292
  }),
121058
123293
  previewItem.model ? (0, react_jsx_runtime.jsx)("span", {
121059
123294
  className: "dsh-ig-tag dsh-ig-tag-model",
@@ -121386,11 +123621,10 @@ img.ProseMirror-separator {
121386
123621
  //#region lib/types/client/image-result-node.js
121387
123622
  const IMAGE_RESULT_NODE_KIND = "dsh-image-result";
121388
123623
  /**
121389
- * One image result row per Turn, anchored at its own tool/result position while
121390
- * the Turn runs, then optionally re-anchored beside the final answer once the
121391
- * Turn closes (only needed under the folding Compact transcript view).
123624
+ * Give each image Tool result its own Chat row at the event that produced it.
123625
+ * PTC (run_code) sub-calls already have their own stable sub-call identity.
121392
123626
  */
121393
- function createImageResultDefinition(options = {}) {
123627
+ function createImageResultDefinition() {
121394
123628
  return {
121395
123629
  kind: IMAGE_RESULT_NODE_KIND,
121396
123630
  target: "chat",
@@ -121398,77 +123632,52 @@ img.ProseMirror-separator {
121398
123632
  if (event.type === "tool/ptc-dispatch") {
121399
123633
  const subCallId = event.data.subCallId;
121400
123634
  if (typeof subCallId !== "string" || subCallId === "") return null;
121401
- return imageResultFromPtcDispatch(event) === void 0 ? null : {
123635
+ return imageResultsFromPtcDispatch(event).length === 0 ? null : {
121402
123636
  id: `ptc:${subCallId}`,
121403
123637
  role: "start"
121404
123638
  };
121405
123639
  }
121406
- const turn = eventTurn(event);
121407
- if (turn === void 0) return null;
121408
- if (event.type === "turn/start") return {
121409
- id: String(turn),
123640
+ if (event.type === "tool/result" && eventTurn(event) !== void 0 && imageResultsFromMeta(event.data.meta).length > 0) return {
123641
+ id: `result:${event.seq}`,
121410
123642
  role: "start"
121411
123643
  };
121412
- if (event.type === "turn/end") return {
121413
- id: String(turn),
121414
- role: "update"
121415
- };
121416
- if (event.type === "assistant/message") return {
121417
- id: String(turn),
121418
- role: "update"
121419
- };
121420
- if (event.type === "tool/result" && imageResultFromMeta(event.data.meta) !== void 0) return {
121421
- id: String(turn),
121422
- role: "update"
121423
- };
121424
123644
  return null;
121425
123645
  },
121426
123646
  start: (_context, match) => {
121427
123647
  if (match.event.type === "tool/ptc-dispatch") {
121428
- const result = imageResultFromPtcDispatch(match.event);
121429
- if (result === void 0) throw new Error("dsh-image-result start requires a plugin image dispatch");
121430
- return { results: [{
123648
+ const results = imageResultsFromPtcDispatch(match.event);
123649
+ if (results.length === 0) throw new Error("dsh-image-result start requires a plugin image dispatch");
123650
+ return { results: results.map((result) => ({
121431
123651
  ...result,
121432
123652
  seq: match.event.seq
121433
- }] };
123653
+ })) };
121434
123654
  }
121435
123655
  const turn = eventTurn(match.event);
121436
- if (match.event.type !== "turn/start" || turn === void 0) throw new Error("dsh-image-result start requires turn/start");
123656
+ const results = match.event.type === "tool/result" ? imageResultsFromMeta(match.event.data.meta) : [];
123657
+ if (turn === void 0 || results.length === 0) throw new Error("dsh-image-result start requires an image Tool result");
121437
123658
  return {
121438
123659
  turn,
121439
- results: []
123660
+ results: results.map((result) => ({
123661
+ ...result,
123662
+ seq: match.event.seq
123663
+ }))
121440
123664
  };
121441
123665
  },
121442
123666
  update: (context, match) => {
121443
- if (match.event.type === "turn/end") return {
121444
- ...context.state,
121445
- endSeq: match.event.seq
121446
- };
121447
- if (match.event.type === "assistant/message") return {
121448
- ...context.state,
121449
- answerSeq: match.event.seq
121450
- };
121451
- if (match.event.type === "tool/ptc-dispatch") {
121452
- const result = imageResultFromPtcDispatch(match.event);
121453
- return result === void 0 ? context.state : appendResult(context.state, result, match.event.seq);
121454
- }
121455
- if (match.event.type !== "tool/result") return context.state;
121456
- const result = imageResultFromMeta(match.event.data.meta);
121457
- if (result === void 0) return context.state;
121458
- return appendResult(context.state, result, match.event.seq);
123667
+ if (match.event.type !== "tool/ptc-dispatch") return context.state;
123668
+ return imageResultsFromPtcDispatch(match.event).reduce((state, result) => appendResult(state, result, match.event.seq), context.state);
121459
123669
  },
121460
123670
  buildViewNode: (context) => {
121461
123671
  const state = context.state;
121462
- const last = state?.results.at(-1);
121463
- if (state === void 0 || last === void 0) return null;
121464
- const location = context.matches.at(-1)?.location ?? context.start?.location ?? { kind: "unresolved" };
123672
+ const first = state?.results[0];
123673
+ if (state === void 0 || first === void 0) return null;
121465
123674
  return {
121466
123675
  key: context.key,
121467
123676
  kind: IMAGE_RESULT_NODE_KIND,
121468
123677
  id: context.id,
121469
123678
  target: "chat",
121470
- anchorSeq: state.endSeq !== void 0 && compactTranscript(options) ? state.answerSeq ?? state.endSeq ?? last.seq : last.seq,
121471
- location,
123679
+ anchorSeq: first.seq,
123680
+ location: { kind: "session" },
121472
123681
  visibility: "visible",
121473
123682
  data: {
121474
123683
  ...state.turn !== void 0 ? { turn: state.turn } : {},
@@ -121489,13 +123698,6 @@ img.ProseMirror-separator {
121489
123698
  }]
121490
123699
  };
121491
123700
  }
121492
- function compactTranscript(options) {
121493
- try {
121494
- return options.isCompactTranscript?.() ?? true;
121495
- } catch {
121496
- return true;
121497
- }
121498
- }
121499
123701
  /** Parse the plugin-owned durable presentation metadata from a Tool result event. */
121500
123702
  function imageResultFromMeta(value) {
121501
123703
  const meta = record(value);
@@ -121512,12 +123714,26 @@ img.ProseMirror-separator {
121512
123714
  ...typeof meta.seed === "number" ? { seed: meta.seed } : {}
121513
123715
  };
121514
123716
  }
123717
+ /** Parse all successful images from either a single or batch Tool result. */
123718
+ function imageResultsFromMeta(value) {
123719
+ const meta = record(value);
123720
+ if (meta?.kind === "dsh-image-gen-batch" && Array.isArray(meta.images)) return meta.images.flatMap((image) => {
123721
+ const result = imageResultFromMeta(image);
123722
+ return result === void 0 ? [] : [result];
123723
+ });
123724
+ const result = imageResultFromMeta(value);
123725
+ return result === void 0 ? [] : [result];
123726
+ }
121515
123727
  function eventTurn(event) {
121516
123728
  const turn = event.data.turn;
121517
123729
  return typeof turn === "number" && Number.isSafeInteger(turn) && turn >= 0 ? turn : void 0;
121518
123730
  }
121519
123731
  /** Plugin tools whose PTC (run_code) dispatch results are image results. */
121520
- const PTC_IMAGE_TOOL_NAMES = /* @__PURE__ */ new Set(["generate_image", "edit_image"]);
123732
+ const PTC_IMAGE_TOOL_NAMES = /* @__PURE__ */ new Set([
123733
+ "generate_image",
123734
+ "generate_images",
123735
+ "edit_image"
123736
+ ]);
121521
123737
  /**
121522
123738
  * Fixed summary text rendered by the tools' imageOutput(). PTC dispatch events
121523
123739
  * carry no presentationMeta, so provider/model/output are recovered from this
@@ -121525,42 +123741,41 @@ img.ProseMirror-separator {
121525
123741
  */
121526
123742
  const PTC_SUMMARY_PATTERN = /^(?:Generated|Edited) one image with ([^/\s]+)\/(\S+) \((.+)\)\. Attachment ID: /;
121527
123743
  const PTC_SAVED_TO_PATTERN = / It was also saved to the workspace as (.+)\. (?:(?:Saving it to the workspace failed)|(?:It has no local file path)|(?:Respond to the user))/;
121528
- /**
121529
- * Parse a plugin image result from a tool/ptc-dispatch event (#38). PTC
121530
- * run_code sub-calls carry neither a turn nor presentationMeta, so the result
121531
- * is rebuilt from the dispatch payload itself: the image content block for the
121532
- * attachment, arguments for the prompt (and edit source ids), and the tool's
121533
- * fixed-format summary text for provider/model/output. Returns undefined for
121534
- * anything that is not a successful image result from our own image tools.
121535
- */
121536
- function imageResultFromPtcDispatch(event) {
121537
- if (event.type !== "tool/ptc-dispatch") return void 0;
123744
+ /** Pair each rendered image with its own summary, including batches with failed items. */
123745
+ function imageResultsFromPtcDispatch(event) {
123746
+ if (event.type !== "tool/ptc-dispatch") return [];
121538
123747
  const data = event.data;
121539
- if (data.isError === true) return void 0;
121540
- if (typeof data.name !== "string" || !PTC_IMAGE_TOOL_NAMES.has(data.name)) return void 0;
123748
+ if (data.isError === true) return [];
123749
+ if (typeof data.name !== "string" || !PTC_IMAGE_TOOL_NAMES.has(data.name)) return [];
121541
123750
  const content = Array.isArray(data.content) ? data.content : [];
121542
- let attachment;
123751
+ const results = [];
121543
123752
  let summaryText = "";
123753
+ const args = record(data.arguments);
123754
+ const sourceIds = sourceAttachmentIds(args);
121544
123755
  for (const block of content) {
121545
123756
  const candidate = record(block);
121546
123757
  if (candidate === void 0) continue;
121547
- if (candidate.type === "image" && attachment === void 0) attachment = imageAttachment(candidate.attachment);
121548
- else if (candidate.type === "text" && summaryText === "" && typeof candidate.text === "string") summaryText = candidate.text;
123758
+ if (candidate.type === "text" && typeof candidate.text === "string") {
123759
+ summaryText = candidate.text;
123760
+ continue;
123761
+ }
123762
+ if (candidate.type !== "image") continue;
123763
+ const attachment = imageAttachment(candidate.attachment);
123764
+ if (attachment === void 0) continue;
123765
+ const summary = PTC_SUMMARY_PATTERN.exec(summaryText);
123766
+ const savedTo = PTC_SAVED_TO_PATTERN.exec(summaryText)?.[1];
123767
+ const promptAt = data.name === "generate_images" ? summaryText.indexOf("\nImage prompt: ") : -1;
123768
+ results.push({
123769
+ attachment,
123770
+ prompt: promptAt < 0 ? stringValue(args?.prompt, "Generated Image") : summaryText.slice(promptAt + 15),
123771
+ provider: summary?.[1] ?? "",
123772
+ model: summary?.[2] ?? "",
123773
+ output: summary?.[3] ?? "",
123774
+ ...savedTo !== void 0 ? { savedTo } : {},
123775
+ ...sourceIds !== void 0 ? { sourceAttachmentIds: sourceIds } : {}
123776
+ });
121549
123777
  }
121550
- if (attachment === void 0) return void 0;
121551
- const summary = PTC_SUMMARY_PATTERN.exec(summaryText);
121552
- const savedTo = PTC_SAVED_TO_PATTERN.exec(summaryText)?.[1];
121553
- const args = record(data.arguments);
121554
- const sourceIds = sourceAttachmentIds(args);
121555
- return {
121556
- attachment,
121557
- prompt: stringValue(args?.prompt, "Generated Image"),
121558
- provider: summary?.[1] ?? "",
121559
- model: summary?.[2] ?? "",
121560
- output: summary?.[3] ?? "",
121561
- ...savedTo !== void 0 ? { savedTo } : {},
121562
- ...sourceIds !== void 0 ? { sourceAttachmentIds: sourceIds } : {}
121563
- };
123778
+ return results;
121564
123779
  }
121565
123780
  /** Collect edit_image source attachment ids (single or list form) from dispatch arguments. */
121566
123781
  function sourceAttachmentIds(args) {
@@ -121622,16 +123837,16 @@ img.ProseMirror-separator {
121622
123837
  const isNew = event.seq > lastSeq;
121623
123838
  lastSeq = Math.max(lastSeq, event.seq);
121624
123839
  if (change.kind !== "append" || !isNew) continue;
121625
- const result = event.type === "tool/result" ? imageResultFromMeta(event.data.meta) : imageResultFromPtcDispatch(event);
121626
- if (result === void 0) continue;
121627
- pushTlLandingsLive([{
123840
+ const results = event.type === "tool/result" ? imageResultsFromMeta(event.data.meta) : imageResultsFromPtcDispatch(event);
123841
+ if (results.length === 0) continue;
123842
+ pushTlLandingsLive(results.map((result) => ({
121628
123843
  galleryId: String(result.attachment.attachmentId),
121629
123844
  attachment: result.attachment,
121630
123845
  fromConversation: true,
121631
123846
  prompt: result.prompt,
121632
123847
  provider: result.provider,
121633
123848
  model: result.model
121634
- }]);
123849
+ })));
121635
123850
  }
121636
123851
  });
121637
123852
  };
@@ -121646,105 +123861,139 @@ img.ProseMirror-separator {
121646
123861
  //#region lib/types/client/studio-style.js
121647
123862
  const STUDIO_STYLE = `
121648
123863
  .dsh-ig-gallery-page-body.is-workbench{flex:1 1 0;height:100%;min-height:0;padding:0;overflow:hidden;display:flex;flex-direction:column}
121649
- .dsh-ig-workbench{--ig-blue:#2f64f5;--ig-line:color-mix(in srgb,currentColor 11%,transparent);--ig-soft:color-mix(in srgb,currentColor 4%,transparent);position:relative;display:flex;flex-direction:column;min-height:0;height:100%;overflow:hidden;padding:10px 12px 12px;color:var(--foreground,#182033);box-sizing:border-box;background:radial-gradient(circle at 60% -20%,rgba(63,104,246,.07),transparent 30%)}
123864
+ .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%)}
123865
+ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a}
121650
123866
  .dsh-ig-workbench *{box-sizing:border-box}.dsh-ig-workbench button,.dsh-ig-workbench select,.dsh-ig-workbench textarea{font:inherit;color:inherit}
121651
- .dsh-ig-workbench-grid{display:grid;grid-template-columns:minmax(190px,238px) minmax(0,1fr) minmax(286px,340px);grid-template-rows:minmax(0,1fr);min-height:0;height:0;flex:1 1 0;border:1px solid var(--ig-line);border-radius:12px;overflow:hidden;background:var(--background,#fff);box-shadow:0 12px 32px rgba(30,42,76,.05);transition:grid-template-columns .2s ease}
123867
+ .dsh-ig-workbench-grid{display:grid;grid-template-columns:minmax(190px,238px) minmax(0,1fr) minmax(286px,340px);grid-template-rows:minmax(0,1fr);min-height:0;height:0;flex:1 1 0;border:1px solid var(--ig-line);border-radius:12px;overflow:hidden;background:var(--ig-bg);box-shadow:0 12px 32px rgba(30,42,76,.05);transition:grid-template-columns .2s ease}
121652
123868
  .dsh-ig-workbench-grid.is-sidebar-collapsed{grid-template-columns:minmax(0,1fr) minmax(286px,340px)}
121653
123869
  .dsh-ig-workbench-grid.is-generate-collapsed{grid-template-columns:minmax(190px,238px) minmax(0,1fr)}
121654
123870
  .dsh-ig-workbench-grid.is-sidebar-collapsed.is-generate-collapsed{grid-template-columns:minmax(0,1fr)}
121655
- .dsh-ig-recent-panel,.dsh-ig-generate-panel{min-width:0;min-height:0;overflow:hidden;background:color-mix(in srgb,var(--background,#fff) 96%,#f5f7fb)}.dsh-ig-recent-panel{display:flex;flex-direction:column;border-right:1px solid var(--ig-line)}
123871
+ .dsh-ig-recent-panel,.dsh-ig-generate-panel{min-width:0;min-height:0;overflow:hidden;background:var(--ig-bg-panel)}.dsh-ig-recent-panel{display:flex;flex-direction:column;border-right:1px solid var(--ig-line)}
121656
123872
  .dsh-ig-panel-title{height:48px;flex:0 0 48px;padding:0 12px 0 14px;display:flex;align-items:center;justify-content:space-between;border-bottom:1px solid var(--ig-line);font-size:13px;font-weight:650}
121657
- .dsh-ig-count-badge{font-size:11px;min-width:22px;text-align:center;padding:2px 6px;border-radius:999px;background:var(--ig-soft);color:#7b8498;font-weight:500}
121658
- .dsh-ig-collapse-btn{width:28px;height:28px;display:inline-flex;align-items:center;justify-content:center;border:1px solid transparent;border-radius:6px;background:transparent;color:#717b90;cursor:pointer;transition:all .15s}
121659
- .dsh-ig-collapse-btn:hover{border-color:var(--ig-line);background:var(--ig-soft);color:var(--foreground,#182033)}
121660
- .dsh-ig-recent-scroll{flex:1 1 0;height:0;padding:8px;overflow-x:hidden;overflow-y:auto;min-height:0;display:grid;grid-template-columns:repeat(auto-fill,minmax(78px,1fr));gap:6px;align-content:start}.dsh-ig-recent-empty{min-height:180px;display:flex;flex-direction:column;justify-content:center;align-items:center;gap:10px;color:#929bad;text-align:center;font-size:12px;grid-column:1/-1}
121661
- .dsh-ig-recent-item{position:relative;width:100%;aspect-ratio:1;align-self:start;padding:0;border:1px solid transparent;border-radius:8px;background:transparent;cursor:pointer;transition:border-color .16s,box-shadow .16s}.dsh-ig-recent-item:hover{border-color:#b9ccfa;box-shadow:0 1px 4px rgba(30,42,76,.14)}.dsh-ig-recent-item.is-active{border-color:var(--ig-blue);box-shadow:0 0 0 2px rgba(47,100,245,.18)}.dsh-ig-recent-thumb{width:100%;height:100%;border-radius:7px;display:flex;align-items:center;justify-content:center;overflow:hidden;background:#eef1f7;color:#929bad}.dsh-ig-recent-thumb img{width:100%;height:100%;object-fit:cover}
121662
- .dsh-ig-load-more{grid-column:1/-1;width:100%;height:32px;margin:4px 0 2px;display:flex;align-items:center;justify-content:center;border:1px dashed var(--ig-line);border-radius:7px;background:transparent;font-size:11px;color:#6b758b;cursor:pointer;transition:background .15s,color .15s}.dsh-ig-load-more:hover{background:var(--ig-soft);color:var(--ig-blue);border-color:#a2b9f6}
121663
- .dsh-ig-mini-btn{height:22px;padding:0 7px;display:inline-flex;align-items:center;gap:3px;border:1px solid var(--ig-line);border-radius:5px;background:var(--background,#fff);font-size:10.5px;font-weight:500;color:var(--foreground,#242f44);cursor:pointer;transition:all .15s}.dsh-ig-mini-btn:hover{border-color:#9bb4f5;color:var(--ig-blue);background:rgba(47,100,245,.06)}
121664
- .dsh-ig-canvas-column{min-width:0;min-height:0;height:100%;overflow:hidden;display:flex;flex-direction:column;background:color-mix(in srgb,var(--background,#fff) 97%,#e8edf7)}
123873
+ .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}
123874
+ .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}
123875
+ .dsh-ig-collapse-btn:hover{border-color:var(--ig-line);background:var(--ig-soft);color:var(--ig-fg)}
123876
+ .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}
123877
+ .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)}
123878
+ .dsh-ig-fav-folders{display:flex;flex-wrap:wrap;gap:4px;margin:4px 0 6px}
123879
+ .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}
123880
+ .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)}
123881
+ .dsh-ig-fav-folder-chip>button:first-child{border:0;background:transparent;color:inherit;font-size:inherit;padding:0;cursor:pointer}
123882
+ .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)}
123883
+ .dsh-ig-fav-folder-draft{display:flex;gap:4px;margin:2px 0 6px}
123884
+ .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}
123885
+ .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}
123886
+ .dsh-ig-fav-folder-draft button:first-of-type{border-color:var(--ig-blue-line);color:var(--ig-blue)}
123887
+ .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)}
123888
+ .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}
123889
+ .dsh-ig-fav-dialog-title{font-size:12.5px;font-weight:600;color:var(--ig-fg)}
123890
+ .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}
123891
+ .dsh-ig-fav-dialog-folder-label{display:flex;align-items:center;gap:8px;font-size:11.5px;color:var(--ig-fg-2)}
123892
+ .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}
123893
+ .dsh-ig-fav-dialog-actions{display:flex;gap:6px;justify-content:flex-end;flex-wrap:wrap}
123894
+ .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}
123895
+ .dsh-ig-fav-dialog-actions button.is-primary{border-color:var(--ig-blue);background:var(--ig-blue);color:#fff}
123896
+ .dsh-ig-fav-dialog-folders{display:flex;flex-direction:column;gap:4px}
123897
+ .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}
123898
+ .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)}
123899
+ .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}
123900
+ .dsh-ig-fav-prompt{position:relative;display:flex;align-items:flex-start;gap:2px}
123901
+ .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)}
123902
+ .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}
123903
+ .dsh-ig-rail-tabs{flex:none;display:flex;gap:4px;padding:4px 8px 6px;border-bottom:1px solid var(--ig-line)}
123904
+ .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}
123905
+ .dsh-ig-fav-scroll{flex:1 1 0;height:0;overflow-x:hidden;overflow-y:auto;min-height:0;padding:8px}
123906
+ .dsh-ig-fav-section-label{padding:4px 2px 6px;font-size:11px;font-weight:600;color:var(--ig-fg-3)}
123907
+ .dsh-ig-fav-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(64px,1fr));gap:6px}
123908
+ .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}
123909
+ .dsh-ig-fav-prompts{display:flex;flex-direction:column;gap:4px}
123910
+ .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}
123911
+ .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)}
123912
+ .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)}
123913
+ .dsh-ig-canvas-column{min-width:0;min-height:0;height:100%;overflow:hidden;display:flex;flex-direction:column;background:var(--dsw-alias-bg-layer-2,var(--ig-bg))}
121665
123914
  .dsh-ig-canvas-toolbar{height:46px;flex:0 0 46px;display:grid;grid-template-columns:1fr auto 1fr;align-items:center;gap:8px;padding:0 14px;border-bottom:1px solid var(--ig-line)}
121666
123915
  .dsh-ig-canvas-toolbar-left,.dsh-ig-canvas-toolbar-right{display:flex;align-items:center;gap:6px;min-width:0}
121667
123916
  .dsh-ig-canvas-toolbar-left{justify-self:start}
121668
123917
  .dsh-ig-canvas-toolbar-right{justify-self:end}
121669
123918
  .dsh-ig-canvas-toolbar-center{display:flex;align-items:center;justify-self:center;gap:6px;min-width:0}
121670
123919
  .dsh-ig-toolbar-divider{width:1px;height:16px;background:var(--ig-line);flex-shrink:0;margin:0 2px}
121671
- .dsh-ig-canvas-toolbar button{height:30px;min-width:30px;padding:0 9px;display:inline-flex;align-items:center;justify-content:center;gap:5px;border:1px solid var(--ig-line);border-radius:7px;background:var(--background,#fff);color:var(--foreground,#242f44);font-size:11.5px;font-weight:500;cursor:pointer;transition:border-color .15s,background .15s,color .15s}
121672
- .dsh-ig-canvas-toolbar button:hover{border-color:#9bb4f5;background:rgba(47,100,245,.05);color:var(--ig-blue)}
121673
- .dsh-ig-canvas-toolbar button.is-active{border-color:#8ba8f8;background:rgba(47,100,245,.08);color:var(--ig-blue);font-weight:550}
123920
+ .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}
123921
+ .dsh-ig-canvas-toolbar button:hover{border-color:var(--ig-blue-line);background:rgba(47,100,245,.05);color:var(--ig-blue)}
123922
+ .dsh-ig-canvas-toolbar button.is-active{border-color:var(--ig-blue-line);background:rgba(47,100,245,.08);color:var(--ig-blue);font-weight:550}
121674
123923
  .dsh-ig-canvas-toolbar-center button.is-active{border-color:var(--ig-blue);background:rgba(47,100,245,.09);color:var(--ig-blue);font-weight:600;box-shadow:inset 0 0 0 1px rgba(47,100,245,.28)}
121675
- .dsh-ig-zoom-display{font-size:11px;min-width:38px;text-align:center;color:#64748b;user-select:none;font-variant-numeric:tabular-nums}
121676
- .dsh-ig-canvas-hint{flex:0 0 auto;display:flex;align-items:flex-start;gap:8px;padding:7px 12px;border-bottom:1px solid rgba(47,100,245,.14);background:rgba(47,100,245,.055);color:#3d4a66;font-size:11.5px;line-height:17px}.dsh-ig-canvas-hint>svg:first-child{flex-shrink:0;color:var(--ig-blue);margin-top:1px}.dsh-ig-canvas-hint>span{flex:1 1 0;min-width:0}.dsh-ig-canvas-hint>button{flex-shrink:0;width:22px;height:22px;display:inline-flex;align-items:center;justify-content:center;border:0;border-radius:5px;background:transparent;color:#8a94a8;cursor:pointer;transition:background .15s,color .15s}.dsh-ig-canvas-hint>button:hover{background:rgba(47,100,245,.1);color:var(--ig-blue)}
123924
+ .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}
123925
+ .dsh-ig-canvas-hint{flex:0 0 auto;display:flex;align-items:flex-start;gap:8px;padding:7px 12px;border-bottom:1px solid rgba(47,100,245,.14);background:rgba(47,100,245,.055);color:var(--ig-fg-2);font-size:11.5px;line-height:17px}.dsh-ig-canvas-hint>svg:first-child{flex-shrink:0;color:var(--ig-blue);margin-top:1px}.dsh-ig-canvas-hint>span{flex:1 1 0;min-width:0}.dsh-ig-canvas-hint>button{flex-shrink:0;width:22px;height:22px;display:inline-flex;align-items:center;justify-content:center;border:0;border-radius:5px;background:transparent;color:var(--ig-fg-cap);cursor:pointer;transition:background .15s,color .15s}.dsh-ig-canvas-hint>button:hover{background:rgba(47,100,245,.1);color:var(--ig-blue)}
121677
123926
  .dsh-ig-status-badge-wrap{position:relative;display:inline-flex;align-items:center}
121678
- .dsh-ig-status-badge{height:30px;display:inline-flex;align-items:center;gap:7px;padding:0 10px;border:1px solid var(--ig-line);border-radius:7px;background:var(--background,#fff);cursor:pointer;user-select:none;transition:border-color .15s,background .15s,box-shadow .15s}
121679
- .dsh-ig-status-badge:hover{border-color:#9bb4f5;background:rgba(47,100,245,.04)}
123927
+ .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}
123928
+ .dsh-ig-status-badge:hover{border-color:var(--ig-blue-line);background:rgba(47,100,245,.04)}
121680
123929
  .dsh-ig-status-dot{width:7px;height:7px;min-width:7px;max-width:7px;border-radius:50%;flex-shrink:0;box-sizing:border-box}
121681
123930
  .dsh-ig-status-dot.is-ready{background:#10b981;box-shadow:0 0 0 2px rgba(16,185,129,.2)}
121682
123931
  .dsh-ig-status-dot.is-muted{background:#ef4444;box-shadow:0 0 0 2px rgba(239,68,68,.2)}
121683
- .dsh-ig-status-text{font-size:11.5px;font-weight:500;color:var(--foreground,#242f44);line-height:1}
121684
- .dsh-ig-status-arrow{color:#94a3b8;transition:transform .15s,color .15s}
123932
+ .dsh-ig-status-text{font-size:11.5px;font-weight:500;color:var(--ig-fg);line-height:1}
123933
+ .dsh-ig-status-arrow{color:var(--ig-fg-cap);transition:transform .15s,color .15s}
121685
123934
  .dsh-ig-status-badge-wrap:hover .dsh-ig-status-arrow{transform:rotate(180deg);color:var(--ig-blue)}
121686
- .dsh-ig-status-popover{position:absolute;left:0;top:calc(100% + 6px);width:260px;background:var(--background,#fff);border:1px solid var(--ig-line);border-radius:9px;box-shadow:0 12px 36px rgba(20,28,45,.14);padding:10px 12px;z-index:40;opacity:0;pointer-events:none;transform:translateY(4px);transition:opacity .15s cubic-bezier(.16,1,.3,1),transform .15s cubic-bezier(.16,1,.3,1)}
123935
+ .dsh-ig-status-popover{position:absolute;left:0;top:calc(100% + 6px);width:260px;background:var(--ig-bg);border:1px solid var(--ig-line);border-radius:9px;box-shadow:0 12px 36px rgba(20,28,45,.14);padding:10px 12px;z-index:40;opacity:0;pointer-events:none;transform:translateY(4px);transition:opacity .15s cubic-bezier(.16,1,.3,1),transform .15s cubic-bezier(.16,1,.3,1)}
121687
123936
  .dsh-ig-status-badge-wrap:hover .dsh-ig-status-popover{opacity:1;pointer-events:auto;transform:translateY(0)}
121688
- .dsh-ig-status-popover-title{display:flex;align-items:center;justify-content:space-between;font-size:10px;font-weight:650;color:#8590a6;margin-bottom:8px;letter-spacing:.02em;text-transform:uppercase}
123937
+ .dsh-ig-status-popover-title{display:flex;align-items:center;justify-content:space-between;font-size:10px;font-weight:650;color:var(--ig-fg-cap);margin-bottom:8px;letter-spacing:.02em;text-transform:uppercase}
121689
123938
  .dsh-ig-status-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:5px}
121690
123939
  .dsh-ig-status-list li{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:6px 8px;border-radius:6px;transition:background .12s}
121691
123940
  .dsh-ig-status-list li.is-configured{background:rgba(16,185,129,.05);border:1px solid rgba(16,185,129,.12)}
121692
123941
  .dsh-ig-status-list li.is-missing{background:rgba(239,68,68,.04);border:1px solid rgba(239,68,68,.12);opacity:.75}
121693
123942
  .dsh-ig-status-item-name{display:flex;flex-direction:column;min-width:0}
121694
- .dsh-ig-status-item-name strong{font-size:11.5px;font-weight:600;color:var(--foreground,#182033);line-height:16px}
121695
- .dsh-ig-status-item-name small{font-size:9.5px;color:#7c869a;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:145px;line-height:14px}
123943
+ .dsh-ig-status-item-name strong{font-size:11.5px;font-weight:600;color:var(--ig-fg);line-height:16px}
123944
+ .dsh-ig-status-item-name small{font-size:9.5px;color:var(--ig-fg-cap);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:145px;line-height:14px}
121696
123945
  .dsh-ig-status-item-tag{font-size:10px;font-weight:600;padding:2px 6px;border-radius:4px;flex-shrink:0}
121697
123946
  .is-configured .dsh-ig-status-item-tag{color:#059669;background:rgba(16,185,129,.14)}
121698
123947
  .is-missing .dsh-ig-status-item-tag{color:#dc2626;background:rgba(239,68,68,.14)}
121699
123948
  .dsh-ig-canvas{position:relative;flex:1 1 0;height:0;min-height:0;display:flex;align-items:center;justify-content:center;overflow:hidden;padding:24px;touch-action:none;user-select:none;cursor:grab;background-image:linear-gradient(45deg,rgba(114,128,156,.035) 25%,transparent 25%),linear-gradient(-45deg,rgba(114,128,156,.035) 25%,transparent 25%),linear-gradient(45deg,transparent 75%,rgba(114,128,156,.035) 75%),linear-gradient(-45deg,transparent 75%,rgba(114,128,156,.035) 75%);background-size:24px 24px;background-position:0 0,0 12px,12px -12px,-12px 0}
121700
123949
  .dsh-ig-canvas.is-dragging,.dsh-ig-canvas.is-dragging *{cursor:grabbing!important}
121701
123950
  .dsh-ig-canvas img{display:block;max-width:100%;max-height:100%;width:auto;height:auto;border-radius:10px;box-shadow:0 14px 45px rgba(25,35,63,.18);user-select:none;-webkit-user-drag:none;pointer-events:auto}
121702
- .dsh-ig-canvas-empty,.dsh-ig-generating-state{display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;gap:12px;color:#929bad;font-size:12px;padding:26px;cursor:default}
121703
- .dsh-ig-generating-state strong{font-size:15px;color:var(--foreground,#182033)}.dsh-ig-generating-state span{font-size:11px}.dsh-ig-generation-orbit{width:66px;height:66px;border-radius:50%;display:grid;place-items:center;color:var(--ig-blue);background:rgba(47,100,245,.09);box-shadow:0 0 0 10px rgba(47,100,245,.035);animation:dshIgPulse 1.8s ease-in-out infinite}
121704
- .dsh-ig-result-strip{flex:0 0 auto;min-height:52px;padding:8px 14px;display:flex;align-items:center;justify-content:space-between;gap:12px;border-top:1px solid var(--ig-line)}.dsh-ig-result-strip>span{font-size:12px;font-weight:650}.dsh-ig-result-strip>div,.dsh-ig-result-actions>div{display:flex;gap:7px}.dsh-ig-result-strip button,.dsh-ig-result-actions button{height:32px;display:inline-flex;align-items:center;gap:6px;padding:0 10px;border:1px solid var(--ig-line);border-radius:7px;background:var(--background,#fff);font-size:11px;cursor:pointer}.dsh-ig-result-strip button:hover,.dsh-ig-result-actions button:hover{border-color:#9fb2ed;color:var(--ig-blue)}.dsh-ig-result-actions{flex:0 0 auto;min-height:54px;padding:8px 14px;display:flex;align-items:center;justify-content:space-between;gap:12px;border-top:1px solid var(--ig-line)}.dsh-ig-result-actions p{margin:0;min-width:0;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;font-size:11.5px;line-height:18px;color:#596277}.dsh-ig-result-meta{flex:0 0 auto;min-height:32px;padding:6px 14px;display:flex;gap:13px;align-items:center;flex-wrap:wrap;border-top:1px solid var(--ig-line);font-size:10px;color:#8790a3}.dsh-ig-result-meta span+span:before{content:'·';margin-right:13px}
121705
- .dsh-ig-generate-panel{height:100%;display:flex;flex-direction:column;border-left:1px solid var(--ig-line);overflow:hidden}.dsh-ig-generator-form{flex:1 1 0;height:0;padding:15px;display:flex;flex-direction:column;gap:15px;overflow-x:hidden;overflow-y:auto;min-height:0}.dsh-ig-mode-switch{display:grid;grid-template-columns:1fr 1fr;gap:8px}.dsh-ig-mode-switch button{height:39px;border:1px solid var(--ig-line);border-radius:8px;background:var(--background,#fff);display:flex;align-items:center;justify-content:center;gap:7px;font-size:12px;cursor:pointer}.dsh-ig-mode-switch button.is-active{border-color:#789bff;color:var(--ig-blue);background:rgba(47,100,245,.065);box-shadow:inset 0 0 0 1px rgba(47,100,245,.1)}
121706
- .dsh-ig-field{position:relative;display:flex;flex-direction:column;gap:7px}.dsh-ig-field>label,.dsh-ig-field-label label{font-size:11.5px;font-weight:630}.dsh-ig-field>label span{font-weight:400;color:#929bad}.dsh-ig-field-label{display:flex;align-items:center;justify-content:space-between}.dsh-ig-field-label b{color:#e14d4d}.dsh-ig-field-label button{border:0;background:transparent;color:#798397;font-size:10.5px;cursor:pointer}.dsh-ig-field textarea{width:100%;height:112px;padding:11px 12px 22px;resize:vertical;border:1px solid var(--ig-line);border-radius:9px;background:var(--background,#fff);font-size:12px;line-height:19px;outline:none}.dsh-ig-field textarea:focus{border-color:var(--ig-blue);box-shadow:0 0 0 3px rgba(47,100,245,.09)}.dsh-ig-field>small{position:absolute;right:9px;bottom:7px;color:#9aa2b2;font-size:9.5px}.dsh-ig-field-grid{display:grid;grid-template-columns:1fr 1fr;gap:9px}.dsh-ig-field-select{display:flex;flex-direction:column;gap:6px;min-width:0}.dsh-ig-field-select>span{font-size:11px;font-weight:620}.dsh-ig-field-select select{width:100%;height:36px;padding:0 26px 0 9px;border:1px solid var(--ig-line);border-radius:8px;background:var(--background,#fff);font-size:11px;outline:none;text-overflow:ellipsis}.dsh-ig-field-select select:focus{border-color:var(--ig-blue)}.dsh-ig-count-row{display:grid;grid-template-columns:repeat(4,1fr);gap:6px}.dsh-ig-count-pill{height:34px;border:1px solid var(--ig-line);border-radius:7px;background:var(--background,#fff);font-size:12px;font-weight:600;color:#596277;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all .15s}.dsh-ig-count-pill:hover{border-color:#9fb2ed;color:var(--ig-blue)}.dsh-ig-count-pill.is-active{border-color:var(--ig-blue);background:rgba(47,100,245,.08);color:var(--ig-blue);box-shadow:inset 0 0 0 1px var(--ig-blue)}
121707
- .dsh-ig-generation-kind{display:grid;grid-template-columns:1fr 1fr;gap:4px;padding:3px;border:1px solid var(--ig-line);border-radius:9px;background:var(--ig-soft)}.dsh-ig-generation-kind button{height:31px;border:0;border-radius:6px;background:transparent;color:#778196;font-size:11.5px;display:flex;align-items:center;justify-content:center;gap:6px;cursor:pointer;transition:background .15s,color .15s,box-shadow .15s}.dsh-ig-generation-kind button.is-active{background:var(--background,#fff);color:var(--ig-blue);font-weight:650;box-shadow:0 2px 8px rgba(30,42,76,.09)}
121708
- .dsh-ig-compare-box{padding:10px;border:1px solid rgba(47,100,245,.2);border-radius:10px;background:linear-gradient(145deg,rgba(47,100,245,.055),rgba(99,102,241,.018));display:flex;flex-direction:column;gap:9px}.dsh-ig-compare-heading{display:flex;align-items:flex-start;justify-content:space-between;gap:8px}.dsh-ig-compare-heading>div{min-width:0;display:flex;flex-direction:column;gap:2px}.dsh-ig-compare-heading strong{font-size:11.5px}.dsh-ig-compare-heading small{font-size:9.5px;color:#838da1}.dsh-ig-compare-heading>span{flex:none;padding:2px 6px;border-radius:999px;background:rgba(47,100,245,.11);color:var(--ig-blue);font-size:9.5px;font-weight:650}.dsh-ig-model-checks{display:flex;flex-direction:column;gap:6px}.dsh-ig-model-checks>button{min-height:43px;width:100%;padding:6px 7px;display:grid;grid-template-columns:18px minmax(0,1fr) auto;align-items:center;gap:7px;border:1px solid var(--ig-line);border-radius:8px;background:var(--background,#fff);text-align:left;cursor:pointer;transition:border-color .15s,background .15s,transform .15s}.dsh-ig-model-checks>button:hover{border-color:#9cb2ef;transform:translateY(-1px)}.dsh-ig-model-checks>button.is-selected{border-color:#86a4fa;background:rgba(47,100,245,.055);box-shadow:inset 3px 0 0 var(--ig-blue)}.dsh-ig-model-checkmark{width:17px;height:17px;border:1px solid #bdc6d8;border-radius:5px;display:grid;place-items:center;color:#fff;background:var(--background,#fff)}.dsh-ig-model-checks>button.is-selected .dsh-ig-model-checkmark{border-color:var(--ig-blue);background:var(--ig-blue)}.dsh-ig-model-copy{display:flex;flex-direction:column;min-width:0}.dsh-ig-model-copy strong{font-size:11px;line-height:15px}.dsh-ig-model-copy small{max-width:130px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:#80899b;font-size:9.5px;line-height:14px}.dsh-ig-model-settings{display:flex;flex-direction:column;align-items:flex-end;gap:2px;color:#596277;font-size:9.5px;white-space:nowrap}.dsh-ig-model-settings em{font-style:normal;color:#a16a0a;font-size:8.5px}.dsh-ig-compare-box>p{margin:0;color:#8a7180;font-size:9.5px;line-height:14px}
121709
- .dsh-ig-upload{height:92px;border:1px dashed #bcc6d9;border-radius:9px;background:rgba(255,255,255,.45);display:flex;flex-direction:column;align-items:center;justify-content:center;gap:5px;color:#7d879b;cursor:pointer}.dsh-ig-upload strong{font-size:11px;font-weight:570}.dsh-ig-upload small{font-size:9.5px}.dsh-ig-upload:hover,.dsh-ig-upload.is-dragging{border-color:var(--ig-blue);background:rgba(47,100,245,.05);color:var(--ig-blue)}
123951
+ .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}
123952
+ .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}
123953
+ .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}
123954
+ .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)}
123955
+ .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)}
123956
+ .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)}
123957
+ .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}
123958
+ .dsh-ig-upload{height:92px;border:1px dashed var(--ig-line);border-radius:9px;background:color-mix(in srgb,var(--ig-bg) 55%,transparent);display:flex;flex-direction:column;align-items:center;justify-content:center;gap:5px;color:var(--ig-fg-3);cursor:pointer}.dsh-ig-upload strong{font-size:11px;font-weight:570}.dsh-ig-upload small{font-size:9.5px}.dsh-ig-upload:hover,.dsh-ig-upload.is-dragging{border-color:var(--ig-blue);background:rgba(47,100,245,.05);color:var(--ig-blue)}
121710
123959
  .dsh-ig-reference-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(64px,1fr));gap:8px}
121711
- .dsh-ig-reference-card{position:relative;aspect-ratio:1;border-radius:8px;overflow:hidden;background:#eef1f7;border:1px solid var(--ig-line)}
123960
+ .dsh-ig-reference-card{position:relative;aspect-ratio:1;border-radius:8px;overflow:hidden;background:var(--ig-bg-thumb);border:1px solid var(--ig-line)}
121712
123961
  .dsh-ig-reference-card img{width:100%;height:100%;object-fit:cover}
121713
123962
  .dsh-ig-reference-card button{position:absolute;right:4px;top:4px;width:18px;height:18px;border:0;border-radius:50%;display:grid;place-items:center;background:rgba(20,28,45,.75);color:#fff;cursor:pointer;padding:0;transition:background .15s}
121714
123963
  .dsh-ig-reference-card button:hover{background:#ef4444}
121715
- .dsh-ig-reference-add{aspect-ratio:1;border:1px dashed #bcc6d9;border-radius:8px;background:rgba(255,255,255,.45);display:flex;flex-direction:column;align-items:center;justify-content:center;gap:3px;color:#7d879b;cursor:pointer;font-size:10px;transition:border-color .15s,color .15s,background .15s}
123964
+ .dsh-ig-reference-add{aspect-ratio:1;border:1px dashed var(--ig-line);border-radius:8px;background:color-mix(in srgb,var(--ig-bg) 55%,transparent);display:flex;flex-direction:column;align-items:center;justify-content:center;gap:3px;color:var(--ig-fg-3);cursor:pointer;font-size:10px;transition:border-color .15s,color .15s,background .15s}
121716
123965
  .dsh-ig-reference-add:hover{border-color:var(--ig-blue);background:rgba(47,100,245,.05);color:var(--ig-blue)}
121717
- .dsh-ig-reference-preview{position:relative;height:108px;border-radius:9px;overflow:hidden;background:#eef1f7}.dsh-ig-reference-preview img{width:100%;height:100%;object-fit:contain}.dsh-ig-reference-preview button{position:absolute;right:7px;top:7px;width:25px;height:25px;border:0;border-radius:50%;display:grid;place-items:center;background:rgba(20,28,45,.72);color:#fff;cursor:pointer}
121718
- .dsh-ig-inline-note,.dsh-ig-form-error{padding:9px 10px;border-radius:7px;font-size:10.5px;line-height:16px}.dsh-ig-inline-note{background:#fff8e7;color:#8b671c;border:1px solid #f1dfae}.dsh-ig-form-error{background:#fff1f1;color:#b63737;border:1px solid #f2caca}.dsh-ig-form-loading{height:70px;display:grid;place-items:center;color:var(--ig-blue)}
121719
- .dsh-ig-config-error{padding:14px;display:flex;flex-direction:column;align-items:center;gap:10px;background:#fff1f1;border:1px solid #f2caca;border-radius:8px;color:#b63737;font-size:11.5px;text-align:center}.dsh-ig-config-error p{margin:0;line-height:17px}.dsh-ig-config-error button{height:30px;display:inline-flex;align-items:center;gap:6px;padding:0 12px;border:1px solid #e0a8a8;border-radius:6px;background:#fff;color:#b63737;font-size:11.5px;font-weight:600;cursor:pointer}.dsh-ig-config-error button:hover{background:#ffebeb}
123966
+ .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}
123967
+ .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)}
123968
+ .dsh-ig-config-error{padding:14px;display:flex;flex-direction:column;align-items:center;gap:10px;background:rgba(239,68,68,.07);border:1px solid rgba(239,68,68,.3);border-radius:8px;color:var(--ig-danger);font-size:11.5px;text-align:center}.dsh-ig-config-error p{margin:0;line-height:17px}.dsh-ig-config-error button{height:30px;display:inline-flex;align-items:center;gap:6px;padding:0 12px;border:1px solid rgba(239,68,68,.35);border-radius:6px;background:var(--ig-bg);color:var(--ig-danger);font-size:11.5px;font-weight:600;cursor:pointer}.dsh-ig-config-error button:hover{background:rgba(239,68,68,.12)}
121720
123969
  .dsh-ig-generate-button{height:42px;flex:0 0 auto;border:0;border-radius:8px;background:linear-gradient(135deg,#386cf6,#2758e7);color:#fff!important;display:flex;align-items:center;justify-content:center;gap:8px;font-size:12px;font-weight:650;cursor:pointer;box-shadow:0 8px 18px rgba(47,100,245,.22);transition:transform .16s,box-shadow .16s}.dsh-ig-generate-button:hover:not(:disabled){transform:translateY(-1px);box-shadow:0 10px 24px rgba(47,100,245,.28)}.dsh-ig-generate-button:disabled{opacity:.55;cursor:not-allowed;box-shadow:none}
121721
123970
  .dsh-ig-generate-button.is-cancel{background:linear-gradient(135deg,#ef4444,#dc2626);box-shadow:0 8px 18px rgba(239,68,68,.25)}.dsh-ig-generate-button.is-cancel:hover{background:linear-gradient(135deg,#dc2626,#b91c1c);box-shadow:0 10px 24px rgba(239,68,68,.35)}
121722
- .dsh-ig-details{padding:16px;margin:0;overflow:auto}.dsh-ig-details>div{padding:12px 0;border-bottom:1px solid var(--ig-line)}.dsh-ig-details dt{font-size:10.5px;color:#8992a4;margin-bottom:5px}.dsh-ig-details dd{margin:0;font-size:11.5px;line-height:18px;word-break:break-word}.dsh-ig-details-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:12px;height:220px;padding:20px;color:#929bad;font-size:11px;text-align:center}
123971
+ .dsh-ig-details{padding:16px;margin:0;overflow:auto}.dsh-ig-details>div{padding:12px 0;border-bottom:1px solid var(--ig-line)}.dsh-ig-details dt{font-size:10.5px;color:var(--ig-fg-cap);margin-bottom:5px}.dsh-ig-details dd{margin:0;font-size:11.5px;line-height:18px;word-break:break-word}.dsh-ig-details-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:12px;height:220px;padding:20px;color:var(--ig-fg-cap);font-size:11px;text-align:center}
121723
123972
  .dsh-ig-workbench-modal-backdrop{position:fixed;inset:0;background:rgba(15,23,42,.45);backdrop-filter:blur(3px);z-index:999;display:grid;place-items:center;padding:16px}
121724
- .dsh-ig-workbench-modal-box{width:100%;max-width:380px;background:var(--background,#fff);border-radius:12px;padding:20px;box-shadow:0 20px 48px rgba(0,0,0,.22);display:flex;flex-direction:column;gap:14px}
123973
+ .dsh-ig-workbench-modal-box{width:100%;max-width:380px;background:var(--ig-bg);border-radius:12px;padding:20px;box-shadow:0 20px 48px rgba(0,0,0,.22);display:flex;flex-direction:column;gap:14px}
121725
123974
  .dsh-ig-workbench-modal-header{display:flex;align-items:center;gap:10px}
121726
123975
  .dsh-ig-workbench-modal-header strong{font-size:14px;font-weight:650;color:#b91c1c}
121727
- .dsh-ig-workbench-modal-desc{font-size:12px;line-height:18px;color:#555f77;margin:0}
121728
- .dsh-ig-workbench-modal-option{display:flex;align-items:center;gap:8px;font-size:11.5px;color:#4b5563;cursor:pointer}
123976
+ .dsh-ig-workbench-modal-desc{font-size:12px;line-height:18px;color:var(--ig-fg-2);margin:0}
123977
+ .dsh-ig-workbench-modal-option{display:flex;align-items:center;gap:8px;font-size:11.5px;color:var(--ig-fg-2);cursor:pointer}
121729
123978
  .dsh-ig-workbench-modal-actions{display:flex;justify-content:flex-end;gap:8px;margin-top:4px}
121730
123979
  .dsh-ig-workbench-modal-actions button{height:32px;padding:0 14px;border-radius:7px;font-size:11.5px;font-weight:600;cursor:pointer}
121731
- .dsh-ig-workbench-modal-cancel{border:1px solid var(--ig-line);background:var(--background,#fff);color:#6b7280}
121732
- .dsh-ig-workbench-modal-cancel:hover{background:#f3f4f6}
123980
+ .dsh-ig-workbench-modal-cancel{border:1px solid var(--ig-line);background:var(--ig-bg);color:var(--ig-fg-2)}
123981
+ .dsh-ig-workbench-modal-cancel:hover{background:var(--ig-soft)}
121733
123982
  .dsh-ig-workbench-modal-danger{border:0;background:#dc2626;color:#fff}
121734
123983
  .dsh-ig-workbench-modal-danger:hover{background:#b91c1c}
121735
123984
  @keyframes dshIgPulse{50%{transform:scale(1.06);box-shadow:0 0 0 16px rgba(47,100,245,.025)}}
121736
123985
  .dsh-ig-workbench-toast{position:fixed;bottom:28px;left:50%;transform:translateX(-50%);background:rgba(15,23,42,0.88);backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);color:#fff;padding:8px 18px;border-radius:10px;font-size:12.5px;font-weight:500;box-shadow:0 12px 32px rgba(0,0,0,0.3);z-index:99999;animation:dshIgFade .15s ease-out;pointer-events:none}
121737
123986
  @keyframes dshIgFade{from{opacity:0;transform:translate(-50%,6px)}to{opacity:1;transform:translate(-50%,0)}}
121738
123987
  @media(max-width:1120px){.dsh-ig-workbench-grid{grid-template-columns:190px minmax(360px,1fr) 300px}.dsh-ig-result-actions button,.dsh-ig-result-strip button{padding:0 7px;font-size:10px;gap:4px}.dsh-ig-result-actions>div,.dsh-ig-result-strip>div{gap:5px}}
121739
- @media(max-width:900px){.dsh-ig-gallery-page-body.is-workbench{height:auto;overflow:auto}.dsh-ig-workbench{height:auto;min-height:100%;overflow:visible;padding:12px}.dsh-ig-workbench-heading{height:auto;padding-bottom:12px;align-items:center}.dsh-ig-provider-quick>span{display:none}.dsh-ig-workbench-grid{height:auto;display:flex;flex:none;flex-direction:column;overflow:visible}.dsh-ig-recent-panel{height:auto;overflow:hidden;border-right:0;border-bottom:1px solid var(--ig-line)}.dsh-ig-recent-scroll{height:auto;display:flex;overflow-x:auto;overflow-y:hidden;padding:8px}.dsh-ig-recent-item{min-width:220px}.dsh-ig-canvas-column{height:auto;min-height:560px}.dsh-ig-generate-panel{height:auto;border-left:0;border-top:1px solid var(--ig-line);min-height:560px}.dsh-ig-generator-form{height:auto;overflow:visible}.dsh-ig-canvas{height:auto;min-height:360px}}
123988
+ @media(max-width:900px){.dsh-ig-gallery-page-body.is-workbench{height:auto;overflow:auto}.dsh-ig-workbench{height:auto;min-height:100%;overflow:visible;padding:12px}.dsh-ig-workbench-heading{height:auto;padding-bottom:12px;align-items:center}.dsh-ig-provider-quick>span{display:none}.dsh-ig-workbench-grid{height:auto;display:flex;flex:none;flex-direction:column;overflow:visible}.dsh-ig-recent-panel{height:auto;overflow:hidden;border-right:0;border-bottom:1px solid var(--ig-line)}.dsh-ig-recent-scroll{height:auto;display:flex;overflow-x:auto;overflow-y:hidden;padding:8px}.dsh-ig-fav-scroll{height:auto;flex:none;overflow:visible}.dsh-ig-recent-item{min-width:220px}.dsh-ig-canvas-column{height:auto;min-height:560px}.dsh-ig-generate-panel{height:auto;border-left:0;border-top:1px solid var(--ig-line);min-height:560px}.dsh-ig-generator-form{height:auto;overflow:visible}.dsh-ig-canvas{height:auto;min-height:360px}}
121740
123989
  @media(max-width:560px){.dsh-ig-workbench-heading>div{display:block}.dsh-ig-workbench-heading h1{font-size:15px}.dsh-ig-provider-quick select{min-width:128px;max-width:150px}.dsh-ig-canvas{padding:12px;min-height:300px}.dsh-ig-result-strip,.dsh-ig-result-actions{align-items:flex-start;flex-direction:column}.dsh-ig-result-strip>div,.dsh-ig-result-actions>div{width:100%}.dsh-ig-result-strip button,.dsh-ig-result-actions button{flex:1;justify-content:center}.dsh-ig-result-actions button span{display:inline}.dsh-ig-field-grid{grid-template-columns:1fr}.dsh-ig-result-meta span+span:before{display:none}}
121741
123990
  @media(prefers-reduced-motion:reduce){.dsh-ig-workbench *{scroll-behavior:auto!important;animation-duration:.01ms!important;transition-duration:.01ms!important}}
121742
- .dsh-ig-field-label>span{display:flex;align-items:center;gap:5px}.dsh-ig-field-label .dsh-ig-find-inspiration{display:inline-flex;align-items:center;gap:3px;color:var(--ig-blue);font-weight:600}.dsh-ig-field-label .dsh-ig-find-inspiration:hover{color:#1644c1}
121743
- .dsh-ig-workspace-filter-label{height:30px;display:inline-flex;align-items:center;gap:6px;padding:0 10px;border:1px solid var(--ig-line,rgba(0,0,0,.1));border-radius:7px;background:var(--background,#fff);font-size:11.5px;color:var(--foreground,#242f44);cursor:pointer;user-select:none;transition:border-color .15s,background .15s}
121744
- .dsh-ig-workspace-filter-label:hover{border-color:#9bb4f5;background:rgba(47,100,245,.04)}
123991
+ .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)}
123992
+ .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}
123993
+ .dsh-ig-workspace-filter-label:hover{border-color:var(--ig-blue-line);background:rgba(47,100,245,.04)}
121745
123994
  .dsh-ig-workspace-checkbox{cursor:pointer;accent-color:#2f64f5;width:13.5px;height:13.5px;margin:0}
121746
123995
  .dsh-ig-workspace-filter-text{font-weight:500;white-space:nowrap}
121747
- .dsh-ig-workspace-filter-badge{font-size:10px;font-weight:600;padding:1px 6px;border-radius:999px;background:color-mix(in srgb,currentColor 7%,transparent);color:#64748b;font-variant-numeric:tabular-nums}
123996
+ .dsh-ig-workspace-filter-badge{font-size:10px;font-weight:600;padding:1px 6px;border-radius:999px;background:color-mix(in srgb,currentColor 7%,transparent);color:var(--ig-fg-2);font-variant-numeric:tabular-nums}
121748
123997
  .dsh-ig-canvas-cluster{display:grid;gap:16px;align-items:center;justify-content:center;user-select:none;pointer-events:auto;flex-shrink:0}
121749
123998
  .dsh-ig-canvas-cluster[data-count="2"]{grid-template-columns:repeat(2,1fr);max-width:880px}
121750
123999
  .dsh-ig-canvas-cluster[data-count="3"],.dsh-ig-canvas-cluster[data-count="4"]{grid-template-columns:repeat(2,1fr);max-width:760px}
@@ -121757,19 +124006,19 @@ img.ProseMirror-separator {
121757
124006
  .dsh-ig-canvas-badge.is-model{max-width:calc(100% - 20px);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:600}
121758
124007
  .dsh-ig-canvas-item.is-checked .dsh-ig-canvas-badge{background:var(--ig-blue)}
121759
124008
  .dsh-ig-canvas-check{position:absolute;top:10px;right:10px;width:22px;height:22px;border:1.5px solid rgba(255,255,255,.85);border-radius:50%;display:grid;place-items:center;background:rgba(15,23,42,.48);color:#fff;box-shadow:0 2px 8px rgba(15,23,42,.18);backdrop-filter:blur(4px);z-index:2;pointer-events:none}.dsh-ig-canvas-item.is-checked .dsh-ig-canvas-check{border-color:#fff;background:var(--ig-blue)}
121760
- .dsh-ig-canvas-item-loading{width:260px;height:260px;display:flex;align-items:center;justify-content:center;background:rgba(255,255,255,.45);border:1px solid var(--ig-line);border-radius:10px;color:#929bad}
124009
+ .dsh-ig-canvas-item-loading{width:260px;height:260px;display:flex;align-items:center;justify-content:center;background:color-mix(in srgb,var(--ig-bg) 55%,transparent);border:1px solid var(--ig-line);border-radius:10px;color:var(--ig-fg-cap)}
121761
124010
  .dsh-ig-lightbox-nav{position:absolute;top:50%;transform:translateY(-50%);width:42px;height:42px;border-radius:50%;border:0;background:rgba(15,23,42,.65);color:#fff;display:grid;place-items:center;cursor:pointer;transition:background .15s,transform .15s;z-index:10;backdrop-filter:blur(4px)}
121762
124011
  .dsh-ig-lightbox-nav:hover{background:rgba(47,100,245,.85);transform:translateY(-50%) scale(1.08)}
121763
124012
  .dsh-ig-lightbox-nav.is-prev{left:18px}
121764
124013
  .dsh-ig-lightbox-nav.is-next{right:18px}
121765
124014
  .dsh-ig-save-gallery-btn{color:var(--ig-blue)!important;border-color:rgba(47,100,245,.35)!important;background:rgba(47,100,245,.06)!important}
121766
124015
  .dsh-ig-save-gallery-btn:hover{background:rgba(47,100,245,.12)!important}
121767
- .dsh-ig-save-gallery-btn.is-saved{color:#64748b!important;border-color:var(--ig-line)!important;background:transparent!important}
124016
+ .dsh-ig-save-gallery-btn.is-saved{color:var(--ig-fg-2)!important;border-color:var(--ig-line)!important;background:transparent!important}
121768
124017
 
121769
124018
  /* tldraw infinite canvas surface (P0 spike) */
121770
124019
  .dsh-ig-tl-canvas{position:relative;flex:1 1 0;height:0;min-height:0;overflow:hidden;isolation:isolate}
121771
- .dsh-ig-canvas-sync-status{position:absolute;z-index:500;bottom:48px;left:60px;right:16px;display:flex;align-items:center;justify-content:center;gap:8px;max-width:560px;margin:auto;padding:7px 12px;border:1px solid var(--dsh-ig-border);border-radius:10px;background:var(--dsh-ig-surface,#fff);color:var(--dsh-ig-text,#475569);box-shadow:0 2px 8px #0001;font-size:12px;pointer-events:none}
121772
- .dsh-ig-canvas-sync-status[data-phase='error']{color:#b42318;border-color:#fecdca}
124020
+ .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}
124021
+ .dsh-ig-canvas-sync-status[data-phase='error']{color:var(--ig-danger);border-color:rgba(239,68,68,.4)}
121773
124022
  .dsh-ig-canvas-sync-status button{pointer-events:auto;flex-shrink:0;border:1px solid currentColor;border-radius:6px;padding:3px 9px;background:transparent;color:inherit;cursor:pointer}
121774
124023
 
121775
124024
  /* ---- Right-sidebar seat variant (DSH 0.1.5 'sidebar.right.pane.tab') ----
@@ -121962,6 +124211,10 @@ img.ProseMirror-separator {
121962
124211
  .dsh-ig-inspiration-head{display:flex;align-items:flex-start;justify-content:space-between;gap:18px;margin-bottom:18px}.dsh-ig-inspiration-head-actions{display:flex;align-items:center;gap:8px}.dsh-ig-inspiration-kicker{margin:0 0 4px;color:var(--inspiration-blue);font-size:11px;letter-spacing:.08em;font-weight:700;text-transform:uppercase}.dsh-ig-inspiration-head h1{margin:0;font-size:18px;letter-spacing:-.025em;line-height:1.25}.dsh-ig-inspiration-subtitle{margin:5px 0 0;color:var(--inspiration-muted);font-size:12px;line-height:1.5}.dsh-ig-inspiration-refresh,.dsh-ig-inspiration-clear-cache{height:32px;padding:0 11px;display:inline-flex;align-items:center;gap:6px;border:1px solid var(--inspiration-line);border-radius:7px;background:var(--dsw-alias-bg-layer-1,#fff);color:var(--inspiration-muted);font:inherit;font-size:12px;white-space:nowrap;cursor:pointer;transition:all .15s ease}.dsh-ig-inspiration-refresh:hover:not(:disabled){border-color:rgba(47,100,245,.42);color:var(--inspiration-blue);background:rgba(47,100,245,.04)}.dsh-ig-inspiration-clear-cache:hover:not(:disabled){border-color:rgba(239,68,68,.4);color:#ef4444;background:rgba(239,68,68,.04)}.dsh-ig-inspiration-refresh:disabled,.dsh-ig-inspiration-clear-cache:disabled{opacity:.55;cursor:wait}
121963
124212
  .dsh-ig-inspiration-toolbar{display:grid;grid-template-columns:minmax(180px,1fr) auto repeat(3,minmax(110px,140px));gap:8px;padding:10px;border:1px solid var(--inspiration-line);border-radius:10px;background:color-mix(in srgb,var(--dsw-alias-bg-layer-1,#fff) 90%,#edf2ff);margin-bottom:16px}.dsh-ig-inspiration-search{min-width:0;position:relative}.dsh-ig-inspiration-search svg{position:absolute;left:10px;top:50%;transform:translateY(-50%);color:#8995aa}.dsh-ig-inspiration-toolbar input,.dsh-ig-inspiration-toolbar select{width:100%;height:34px;box-sizing:border-box;border:1px solid transparent;border-radius:6px;background:var(--dsw-alias-bg-layer-1,#fff);color:inherit;padding:0 9px;font:inherit;font-size:12px;outline:none}.dsh-ig-inspiration-toolbar input{padding-left:31px}.dsh-ig-inspiration-toolbar input:focus,.dsh-ig-inspiration-toolbar select:focus{border-color:var(--inspiration-blue);box-shadow:0 0 0 3px rgba(47,100,245,.1)}
121964
124213
  .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}
124214
+ .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)}
124215
+ .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}
124216
+ .dsh-ig-inspiration-group{margin:0}
124217
+ .dsh-ig-inspiration-group-head{position:sticky;top:48px;z-index:5;display:flex;align-items:center;gap:8px;margin:6px 0 10px;padding:7px 2px;font-size:12px;font-weight:700;letter-spacing:.02em;color:var(--inspiration-ink);background:color-mix(in srgb,var(--dsw-alias-bg-layer-1,#fff) 86%,transparent);backdrop-filter:blur(6px);scroll-margin-top:48px}.dsh-ig-inspiration-group-head em{font-style:normal;font-weight:600;font-size:10.5px;color:var(--inspiration-muted)}
121965
124218
  .dsh-ig-inspiration-layout{display:grid;grid-template-columns:minmax(0,1fr) minmax(260px,310px);gap:18px;align-items:start}.dsh-ig-inspiration-summary{height:24px;display:flex;align-items:center;justify-content:space-between;color:var(--inspiration-muted);font-size:11.5px}.dsh-ig-inspiration-summary strong{color:inherit;font-weight:650}.dsh-ig-inspiration-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(185px,1fr));gap:12px}.dsh-ig-inspiration-card{position:relative;min-width:0;padding:0;border:1px solid var(--inspiration-line);border-radius:10px;overflow:hidden;background:var(--dsw-alias-bg-layer-1,#fff);text-align:left;cursor:pointer;transition:transform .16s ease,border-color .16s ease,box-shadow .16s ease;content-visibility:auto;contain-intrinsic-size:245px}.dsh-ig-inspiration-card:hover,.dsh-ig-inspiration-card.is-selected{border-color:rgba(47,100,245,.58);box-shadow:0 8px 22px rgba(37,62,123,.11)}.dsh-ig-inspiration-card:hover{transform:translateY(-2px)}.dsh-ig-inspiration-card.is-selected:after{content:'';position:absolute;inset:0;border:2px solid var(--inspiration-blue);border-radius:9px;pointer-events:none}.dsh-ig-inspiration-visual{aspect-ratio:1/1;position:relative;background:linear-gradient(135deg,#f0f4fc,#e3eafa);overflow:hidden}.dsh-ig-inspiration-visual img{width:100%;height:100%;object-fit:cover;display:block}.dsh-ig-inspiration-image-placeholder{height:100%;display:grid;place-items:center;color:#8a99b3}.dsh-ig-inspiration-image-placeholder.is-error{font-size:11px;padding:10px;text-align:center}.dsh-ig-inspiration-card-copy{padding:9px 10px 10px}.dsh-ig-inspiration-card-copy strong{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:12px;line-height:17px}.dsh-ig-inspiration-card-copy span{display:block;margin-top:3px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--inspiration-muted);font-size:10.5px}.dsh-ig-inspiration-featured{position:absolute;top:8px;left:8px;display:inline-flex;align-items:center;gap:3px;padding:3px 6px;border-radius:999px;background:rgba(20,31,52,.72);backdrop-filter:blur(5px);color:#fff;font-size:10px;font-weight:600;z-index:1}.dsh-ig-inspiration-card-star{position:absolute;top:8px;right:8px;width:26px;height:26px;border-radius:50%;background:rgba(20,31,52,.6);backdrop-filter:blur(4px);border:none;color:#fff;display:grid;place-items:center;cursor:pointer;z-index:2;opacity:0;transition:opacity .15s,transform .15s}.dsh-ig-inspiration-card:hover .dsh-ig-inspiration-card-star,.dsh-ig-inspiration-card-star.is-favorited{opacity:1}.dsh-ig-inspiration-card-star.is-favorited{color:#f59e0b;background:rgba(20,31,52,.85)}.dsh-ig-inspiration-card-star:hover{transform:scale(1.15)}
121966
124219
  .dsh-ig-inspiration-empty{min-height:310px;display:grid;place-items:center;padding:30px;text-align:center;color:var(--inspiration-muted);border:1px dashed var(--inspiration-line);border-radius:10px;font-size:12px}
121967
124220
  .dsh-ig-inspiration-empty.is-catalog-error{display:flex;flex-direction:column;gap:12px;color:var(--inspiration-text)}
@@ -121985,7 +124238,37 @@ img.ProseMirror-separator {
121985
124238
  .dsh-ig-inspiration-end-hint{display:flex;align-items:center;justify-content:center;padding:24px 0 16px;color:var(--inspiration-muted);font-size:12px}
121986
124239
  .dsh-ig-inspiration-inspector{position:sticky;top:0;overflow:hidden;border:1px solid var(--inspiration-line);border-radius:11px;background:var(--dsw-alias-bg-layer-1,#fff);box-shadow:0 9px 28px rgba(27,45,88,.06)}.dsh-ig-inspiration-inspector-empty{min-height:310px;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:9px;padding:22px;color:var(--inspiration-muted);font-size:12px;text-align:center}.dsh-ig-inspiration-inspector-image{aspect-ratio:16/10;background:#f1f4fa;position:relative;cursor:zoom-in}.dsh-ig-inspiration-inspector-image img{width:100%;height:100%;object-fit:cover}.dsh-ig-inspiration-inspector-zoom-hint{position:absolute;bottom:8px;right:8px;display:inline-flex;align-items:center;gap:4px;padding:4px 8px;border-radius:6px;background:rgba(15,23,42,.75);backdrop-filter:blur(5px);color:#fff;font-size:10.5px;font-weight:500;opacity:0;transition:opacity .15s ease;pointer-events:none}.dsh-ig-inspiration-inspector-image:hover .dsh-ig-inspiration-inspector-zoom-hint{opacity:1}.dsh-ig-inspiration-inspector-body{padding:14px}.dsh-ig-inspiration-inspector-head{display:flex;align-items:flex-start;justify-content:space-between;gap:8px}.dsh-ig-inspiration-inspector-body h2{margin:0;font-size:14px;line-height:1.4;flex:1}.dsh-ig-inspiration-inspector-fav{width:30px;height:30px;flex-shrink:0;border:1px solid var(--inspiration-line);border-radius:7px;background:var(--dsw-alias-bg-layer-1,#fff);color:var(--inspiration-muted);display:grid;place-items:center;cursor:pointer;transition:all .15s ease}.dsh-ig-inspiration-inspector-fav:hover{border-color:rgba(245,158,11,.4);color:#f59e0b}.dsh-ig-inspiration-inspector-fav.is-favorited{color:#f59e0b;border-color:rgba(245,158,11,.5);background:rgba(245,158,11,.08)}.dsh-ig-inspiration-origin{margin:5px 0 12px;color:var(--inspiration-muted);font-size:11px}.dsh-ig-inspiration-tags{display:flex;flex-wrap:wrap;gap:5px;margin:0 0 12px}.dsh-ig-inspiration-tags span{max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;padding:3px 6px;border-radius:4px;background:#eef3ff;color:#3e5fb8;font-size:10px}.dsh-ig-inspiration-prompt-label{display:flex;align-items:center;justify-content:space-between;margin:0 0 6px;color:var(--inspiration-muted);font-size:10.5px;font-weight:650}.dsh-ig-inspiration-prompt{max-height:155px;margin:0;padding:9px;overflow:auto;white-space:pre-wrap;border:1px solid var(--inspiration-line);border-radius:7px;background:color-mix(in srgb,var(--dsw-alias-bg-layer-2,#f8fafc) 90%,#e9efff);font-size:11px;line-height:1.55;color:#526078}.dsh-ig-inspiration-actions{display:grid;grid-template-columns:1fr 1fr;gap:7px;margin-top:12px}.dsh-ig-inspiration-actions button,.dsh-ig-inspiration-source-link{height:34px;display:inline-flex;align-items:center;justify-content:center;gap:6px;border:1px solid var(--inspiration-line);border-radius:7px;background:var(--dsw-alias-bg-layer-1,#fff);color:#536075;font:inherit;font-size:11.5px;font-weight:600;cursor:pointer;text-decoration:none}.dsh-ig-inspiration-actions button:hover,.dsh-ig-inspiration-source-link:hover{border-color:rgba(47,100,245,.46);color:var(--inspiration-blue)}.dsh-ig-inspiration-actions .dsh-ig-inspiration-use{grid-column:1/-1;border-color:var(--inspiration-blue);background:linear-gradient(135deg,#386cf6,#2758e7);color:#fff;box-shadow:0 7px 15px rgba(47,100,245,.2)}.dsh-ig-inspiration-actions .dsh-ig-inspiration-use:hover{color:#fff;transform:translateY(-1px)}
121987
124240
  .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)}
124241
+ .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)}
124242
+ .dsh-ig-inspiration-actions .dsh-ig-inspiration-use.is-secondary{grid-column:1/-1;border-color:rgba(47,100,245,.5);background:var(--dsw-alias-bg-layer-1,#fff);color:var(--inspiration-blue);box-shadow:none}.dsh-ig-inspiration-actions .dsh-ig-inspiration-use.is-secondary:hover{color:#2758e7;transform:translateY(-1px)}.dsh-ig-inspiration-actions .dsh-ig-inspiration-use.is-secondary:disabled{opacity:.6;cursor:wait;transform:none}
121988
124243
  @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}}
124244
+
124245
+ .dsh-ig-inspiration-source-chip svg{flex-shrink:0}
124246
+ .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}
124247
+ .dsh-ig-baoyu-intro{display:flex;align-items:baseline;gap:10px;flex-wrap:wrap}
124248
+ .dsh-ig-baoyu-combos{font-size:12px;color:var(--inspiration-blue)}
124249
+ .dsh-ig-baoyu-hint{font-size:11px;color:var(--inspiration-muted)}
124250
+ .dsh-ig-baoyu-dim{display:flex;flex-direction:column;gap:7px}
124251
+ .dsh-ig-baoyu-dim-head{font-size:11px;font-weight:650;color:var(--inspiration-muted);letter-spacing:.02em}
124252
+ .dsh-ig-baoyu-chips{display:flex;flex-wrap:wrap;gap:6px}
124253
+ .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}
124254
+ .dsh-ig-baoyu-chip:hover{border-color:rgba(47,100,245,.46);color:var(--inspiration-blue)}
124255
+ .dsh-ig-baoyu-chip.is-active{border-color:var(--inspiration-blue);background:rgba(47,100,245,.09);color:var(--inspiration-blue)}
124256
+ .dsh-ig-baoyu-swatch{display:inline-flex;gap:2px}
124257
+ .dsh-ig-baoyu-swatch i{width:9px;height:9px;border-radius:50%;border:1px solid rgba(15,23,42,.15);display:block}
124258
+ .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}
124259
+ .dsh-ig-baoyu-title-input:focus{outline:none;border-color:rgba(47,100,245,.55)}
124260
+ .dsh-ig-baoyu-title-input:disabled{opacity:.45}
124261
+ .dsh-ig-baoyu-required{color:#dc2626;font-size:11px}
124262
+ .dsh-ig-baoyu-actions{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
124263
+ .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}
124264
+ .dsh-ig-baoyu-btn:hover{border-color:rgba(47,100,245,.46);color:var(--inspiration-blue)}
124265
+ .dsh-ig-baoyu-btn:disabled{opacity:.5;cursor:not-allowed}
124266
+ .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)}
124267
+ .dsh-ig-baoyu-btn.is-primary:hover{color:#fff;transform:translateY(-1px)}
124268
+ .dsh-ig-baoyu-btn.is-primary:disabled{opacity:.5;cursor:not-allowed;transform:none}
124269
+ .dsh-ig-baoyu-preview-block{display:flex;flex-direction:column;gap:6px}
124270
+ .dsh-ig-baoyu-preview-head{font-size:11px;font-weight:650;color:var(--inspiration-muted)}
124271
+ .dsh-ig-baoyu-preview{max-height:260px;margin:0;padding:12px;overflow:auto;white-space:pre-wrap;border:1px solid var(--inspiration-line);border-radius:7px;background:color-mix(in srgb,var(--dsw-alias-bg-layer-2,#f8fafc) 90%,#e9efff);font-size:11px;line-height:1.6;color:#526078}
121989
124272
  `;
121990
124273
  //#endregion
121991
124274
  //#region lib/types/client/conversation-image-revisions.js
@@ -122500,7 +124783,7 @@ img.ProseMirror-separator {
122500
124783
  `;
122501
124784
  //#endregion
122502
124785
  //#region lib/types/client/index.js
122503
- console.info(`[dsh-image-gen] canvas bundle 2026-09-26T07:20:53.102Z`);
124786
+ console.info(`[dsh-image-gen] canvas bundle 2026-09-30T06:41:54.565Z`);
122504
124787
  /** Right-sidebar tab-type registration id (DSH 0.1.5 `sidebarRightTabs`). */
122505
124788
  const SIDEBAR_STUDIO_TAB_ID = "dsh-image-gen/studio";
122506
124789
  /** The tab kind: a page type opened by kind, recognizing no resource address. */
@@ -122528,6 +124811,7 @@ img.ProseMirror-separator {
122528
124811
  subBadgeUnknown: "状态未知",
122529
124812
  subSectionTitle: "订阅生图",
122530
124813
  subHint: "通过已登录的订阅账号生图,无需 API Key。登录在你的授权下进行,不会修改任何 API Key 或默认 Provider。",
124814
+ subProxyHint: "需要代理时,请在登录前开启代理软件的 TUN(虚拟网卡)模式。普通系统代理可能无法覆盖 DSH 后端请求,导致网页授权后登录失败。",
122531
124815
  subAccountLabel: "账号",
122532
124816
  subAccountEmail: "已登录:{email}",
122533
124817
  subAccountNone: "未登录",
@@ -122562,6 +124846,9 @@ img.ProseMirror-separator {
122562
124846
  clearKeyFailed: "清除 Key 失败",
122563
124847
  saveKeyFailed: "保存 Key 失败",
122564
124848
  saveKeyFirst: "请先保存 API Key,再拉取模型或测试连接。",
124849
+ saveEndpointFirst: "接口地址尚未保存,请先点击保存,再拉取模型或测试连接。",
124850
+ endpointRequired: "请填写接口地址并保存,再拉取模型或测试连接。",
124851
+ savedEndpointMissing: "未读取到已保存的接口地址,请保存后重试。",
122565
124852
  clearKeyUnsupported: "当前版本 DSH 不支持在此清除 Key,请到凭据管理中删除。",
122566
124853
  endpoint: "接口地址",
122567
124854
  reset: "重置",
@@ -122572,7 +124859,8 @@ img.ProseMirror-separator {
122572
124859
  editFormat: "图生图请求形态",
122573
124860
  editFormatMultipart: "标准 multipart(OpenAI 官方)",
122574
124861
  editFormatJsonImageUrlArray: "JSON images 数组(商汤等中转)",
122575
- editFormatHint: "文生图不受影响。仅当中转站的图生图接口使用自有 JSON 契约(如商汤 SenseNova)时才需要切换。",
124862
+ editFormatFormReferenceImages: "multipart + reference_images JSON 数组(base64)",
124863
+ editFormatHint: "文生图不受影响。仅当中转站的图生图接口使用自有契约(如商汤 SenseNova 的 JSON images 数组,或要求 reference_images base64 数组的中转)时才需要切换。",
122576
124864
  editExtra: "附加 JSON 字段",
122577
124865
  editExtraPlaceholder: "{\"watermark\": false, \"prompt_extend\": true}",
122578
124866
  editExtraHint: "仅 JSON 形态生效;会合并到请求体末尾,可覆盖默认字段。留空表示不附加。",
@@ -122672,6 +124960,7 @@ img.ProseMirror-separator {
122672
124960
  subBadgeUnknown: "Unknown",
122673
124961
  subSectionTitle: "Subscription generation",
122674
124962
  subHint: "Generates through a logged-in subscription account; no API key needed. Signing in never changes any API key or the default provider.",
124963
+ subProxyHint: "If you need a proxy, enable TUN (virtual network adapter) mode before signing in. A system proxy alone may not cover DSH backend requests, causing sign-in to fail after browser authorization.",
122675
124964
  subAccountLabel: "Account",
122676
124965
  subAccountEmail: "Signed in: {email}",
122677
124966
  subAccountNone: "Not signed in",
@@ -122706,6 +124995,9 @@ img.ProseMirror-separator {
122706
124995
  clearKeyFailed: "Failed to clear key",
122707
124996
  saveKeyFailed: "Failed to save the key",
122708
124997
  saveKeyFirst: "Save the API key first, then fetch models or test the connection.",
124998
+ saveEndpointFirst: "The endpoint address has not been saved. Click Save before fetching models or testing the connection.",
124999
+ endpointRequired: "Enter and save an endpoint address before fetching models or testing the connection.",
125000
+ savedEndpointMissing: "No saved endpoint address was found. Save it and try again.",
122709
125001
  clearKeyUnsupported: "This DSH build cannot clear keys here; remove it from credential management instead.",
122710
125002
  endpoint: "Endpoint / Base URL",
122711
125003
  reset: "Reset",
@@ -122716,7 +125008,8 @@ img.ProseMirror-separator {
122716
125008
  editFormat: "Edit request format",
122717
125009
  editFormatMultipart: "Standard multipart (official OpenAI)",
122718
125010
  editFormatJsonImageUrlArray: "JSON images array (SenseNova etc.)",
122719
- editFormatHint: "Text-to-image is unaffected. Only switch when the relay runs image edits on its own JSON contract (e.g. SenseNova); size is pinned to \"auto\" in this format (the only value SenseNova's edits endpoint accepts) and can be overridden via extra fields.",
125011
+ editFormatFormReferenceImages: "Multipart with a reference_images JSON array (base64)",
125012
+ editFormatHint: "Text-to-image is unaffected. Only switch when the relay runs image edits on its own contract (e.g. SenseNova's JSON images array, or a relay that takes reference_images as a base64 JSON array); size is pinned to \"auto\" in the JSON images format (the only value SenseNova's edits endpoint accepts) and can be overridden via extra fields.",
122720
125013
  editExtra: "Extra JSON fields",
122721
125014
  editExtraPlaceholder: "{\"watermark\": false, \"prompt_extend\": true}",
122722
125015
  editExtraHint: "Only used with the JSON format; merged into the request body last and may override defaults. Leave empty for none.",
@@ -123115,11 +125408,6 @@ img.ProseMirror-separator {
123115
125408
  const configForms = ctx.get("configForms");
123116
125409
  const settingsScopeApi = configForms === void 0 ? ctx.get("settingsScope") : void 0;
123117
125410
  const scope = configForms?.get("image-gen") ?? settingsScopeApi?.bind({ namespace: "image-generation" }) ?? degradedScope();
123118
- const chatScope = configForms?.get("ui-chat") ?? settingsScopeApi?.bind({ namespace: "ui-chat" }) ?? degradedScope();
123119
- const isCompactTranscript = () => {
123120
- const mode = chatScope.getSnapshot().value?.transcriptView;
123121
- return configForms === void 0 ? mode !== "normal" : mode !== "standard" && mode !== "detailed" && mode !== "verbose";
123122
- };
123123
125411
  const locale = ctx.get("locale");
123124
125412
  const promotion = { enabled: false };
123125
125413
  ctx.inject(["sessions"], (owner) => {
@@ -123147,7 +125435,7 @@ img.ProseMirror-separator {
123147
125435
  if (uiConversation === void 0) throw new Error("dsh-image-gen: uiConversation has an incompatible interface");
123148
125436
  promotion.enabled = true;
123149
125437
  const ownerRegister = owner.slots.register.bind(owner.slots);
123150
- owner.effect(() => uiConversation.events.register(createImageResultDefinition({ isCompactTranscript })), "dsh-image-gen: promoted image result node");
125438
+ owner.effect(() => uiConversation.events.register(createImageResultDefinition()), "dsh-image-gen: promoted image result node");
123151
125439
  owner.slots.inject("conversation.chat.node", () => ownerRegister({
123152
125440
  name: "conversation.chat.node",
123153
125441
  key: IMAGE_RESULT_NODE_KIND,
@@ -123246,6 +125534,14 @@ img.ProseMirror-separator {
123246
125534
  promoted: promotion.enabled
123247
125535
  })
123248
125536
  }, GeneratedImageCard));
125537
+ ctx.slots.inject("tool.call.toolview", () => register({
125538
+ name: "tool.call.toolview",
125539
+ key: "generate_images",
125540
+ inject: () => ({
125541
+ locale,
125542
+ promoted: promotion.enabled
125543
+ })
125544
+ }, GeneratedImagesCard));
123249
125545
  ctx.slots.inject("tool.call.toolview", () => register({
123250
125546
  name: "tool.call.toolview",
123251
125547
  key: "edit_image",
@@ -123409,6 +125705,10 @@ img.ProseMirror-separator {
123409
125705
  return "";
123410
125706
  }
123411
125707
  }
125708
+ /** Persisted or selected edit-format value → row state; unknown values fall back to standard multipart. */
125709
+ function parseEditFormat(value) {
125710
+ return value === "jsonImageUrlArray" || value === "formReferenceImages" ? value : "multipart";
125711
+ }
123412
125712
  /** Build one row per provider from persisted settings, including ComfyUI extras. */
123413
125713
  function rowsFromSettings(value) {
123414
125714
  const rows = {};
@@ -123422,7 +125722,7 @@ img.ProseMirror-separator {
123422
125722
  timeoutSeconds: Math.max(1, Math.round((value?.comfyuiTimeoutMs ?? 3e5) / 1e3))
123423
125723
  } : {},
123424
125724
  ...provider === "openai-compat" ? {
123425
- editFormat: value?.openaiCompatEditFormat === "jsonImageUrlArray" ? "jsonImageUrlArray" : "multipart",
125725
+ editFormat: parseEditFormat(value?.openaiCompatEditFormat),
123426
125726
  editExtraText: editExtraTextOf(value?.openaiCompatEditExtra)
123427
125727
  } : {},
123428
125728
  ...provider === "seedream" ? {
@@ -123811,11 +126111,23 @@ img.ProseMirror-separator {
123811
126111
  updateRow(provider, { saving: false });
123812
126112
  }
123813
126113
  };
126114
+ /** These actions use host settings, so drafts must be saved before making a request. */
126115
+ const providerActionBlockMessage = (provider) => {
126116
+ const row = rows[provider];
126117
+ if (row.keyInput.trim().length > 0) return t("saveKeyFirst");
126118
+ if (isSubscriptionProvider(provider)) return void 0;
126119
+ const address = row.baseURL.trim();
126120
+ if (address.length === 0) return t("endpointRequired");
126121
+ if (address !== baseURLOf(provider, props.scope.getSnapshot().value).trim()) return t("saveEndpointFirst");
126122
+ };
126123
+ /** Localize the host's missing-address error while preserving upstream details. */
126124
+ const providerErrorMessage = (message) => message === "Base URL is not configured" ? t("savedEndpointMissing") : message ?? t("testFailed");
123814
126125
  /** Probe through the host route so the browser side never touches credential values. */
123815
126126
  const testConnection = async (provider) => {
123816
- if (rows[provider].keyInput.trim().length > 0) {
126127
+ const blocked = providerActionBlockMessage(provider);
126128
+ if (blocked !== void 0) {
123817
126129
  updateRow(provider, {
123818
- message: t("saveKeyFirst"),
126130
+ message: blocked,
123819
126131
  messageIsError: false,
123820
126132
  testResult: void 0
123821
126133
  });
@@ -123847,9 +126159,10 @@ img.ProseMirror-separator {
123847
126159
  };
123848
126160
  /** Pull the provider's image-capable model ids through the host route (Google and the OpenAI family). */
123849
126161
  const fetchProviderModels = async (provider) => {
123850
- if (rows[provider].keyInput.trim().length > 0) {
126162
+ const blocked = providerActionBlockMessage(provider);
126163
+ if (blocked !== void 0) {
123851
126164
  updateRow(provider, {
123852
- modelFetchMessage: t("saveKeyFirst"),
126165
+ modelFetchMessage: blocked,
123853
126166
  modelFetchIsError: false
123854
126167
  });
123855
126168
  return;
@@ -123886,7 +126199,7 @@ img.ProseMirror-separator {
123886
126199
  modelFetchIsError: true
123887
126200
  });
123888
126201
  else updateRow(provider, {
123889
- modelFetchMessage: payload.message ?? t("testFailed"),
126202
+ modelFetchMessage: providerErrorMessage(payload.message),
123890
126203
  modelFetchIsError: true
123891
126204
  });
123892
126205
  } catch (cause) {
@@ -123937,7 +126250,7 @@ img.ProseMirror-separator {
123937
126250
  if (result.ok) return t("testOk");
123938
126251
  if (result.reason === "missing-key") return t("badgeMissing");
123939
126252
  if (result.reason === "unauthorized") return t("testUnauthorized");
123940
- return `${t("testFailed")}${result.message !== void 0 && result.message.length > 0 ? `: ${result.message}` : ""}`;
126253
+ return `${t("testFailed")}${result.message !== void 0 && result.message.length > 0 ? `: ${providerErrorMessage(result.message)}` : ""}`;
123941
126254
  };
123942
126255
  const badgeOf = (provider) => {
123943
126256
  if (provider === "comfyui") return {
@@ -124239,16 +126552,23 @@ img.ProseMirror-separator {
124239
126552
  className: "dsh-ig-input",
124240
126553
  value: row.editFormat,
124241
126554
  onChange: (event) => {
124242
- updateRow(provider, { editFormat: event.target.value === "jsonImageUrlArray" ? "jsonImageUrlArray" : "multipart" });
126555
+ updateRow(provider, { editFormat: parseEditFormat(event.target.value) });
124243
126556
  },
124244
126557
  disabled: !snapshot.writable,
124245
- children: [(0, react_jsx_runtime.jsx)("option", {
124246
- value: "multipart",
124247
- children: t("editFormatMultipart")
124248
- }), (0, react_jsx_runtime.jsx)("option", {
124249
- value: "jsonImageUrlArray",
124250
- children: t("editFormatJsonImageUrlArray")
124251
- })]
126558
+ children: [
126559
+ (0, react_jsx_runtime.jsx)("option", {
126560
+ value: "multipart",
126561
+ children: t("editFormatMultipart")
126562
+ }),
126563
+ (0, react_jsx_runtime.jsx)("option", {
126564
+ value: "jsonImageUrlArray",
126565
+ children: t("editFormatJsonImageUrlArray")
126566
+ }),
126567
+ (0, react_jsx_runtime.jsx)("option", {
126568
+ value: "formReferenceImages",
126569
+ children: t("editFormatFormReferenceImages")
126570
+ })
126571
+ ]
124252
126572
  }),
124253
126573
  (0, react_jsx_runtime.jsx)("span", {
124254
126574
  className: "dsh-ig-hint",
@@ -124597,13 +126917,21 @@ img.ProseMirror-separator {
124597
126917
  children: [
124598
126918
  (0, react_jsx_runtime.jsxs)("div", {
124599
126919
  className: "dsh-ig-field",
124600
- children: [(0, react_jsx_runtime.jsx)("span", {
124601
- className: "dsh-ig-label",
124602
- children: t("subSectionTitle")
124603
- }), (0, react_jsx_runtime.jsx)("p", {
124604
- className: "dsh-ig-hint",
124605
- children: t("subHint")
124606
- })]
126920
+ children: [
126921
+ (0, react_jsx_runtime.jsx)("span", {
126922
+ className: "dsh-ig-label",
126923
+ children: t("subSectionTitle")
126924
+ }),
126925
+ (0, react_jsx_runtime.jsx)("p", {
126926
+ className: "dsh-ig-hint",
126927
+ children: t("subHint")
126928
+ }),
126929
+ (0, react_jsx_runtime.jsx)("p", {
126930
+ className: "dsh-ig-hint",
126931
+ role: "note",
126932
+ children: t("subProxyHint")
126933
+ })
126934
+ ]
124607
126935
  }),
124608
126936
  (0, react_jsx_runtime.jsxs)("div", {
124609
126937
  className: "dsh-ig-field",
@@ -124929,15 +127257,41 @@ img.ProseMirror-separator {
124929
127257
  sessionId: props.sessionId
124930
127258
  });
124931
127259
  }
127260
+ /** Suppress the folded duplicate on modern hosts; render every image on legacy hosts. */
127261
+ function GeneratedImagesCard(props) {
127262
+ const block = props.block;
127263
+ let results = imageResultsFromMeta(block.meta ?? block.resultView?.meta);
127264
+ if (results.length === 0 && "kind" in block) results = ((block.resultView?.card === "generic" ? block.resultView.content : block.content) ?? []).flatMap((item) => item.type === "image" && item.attachment !== void 0 ? [{
127265
+ attachment: item.attachment,
127266
+ prompt: "Generated Image",
127267
+ provider: "",
127268
+ model: "",
127269
+ output: ""
127270
+ }] : []);
127271
+ if (props.promoted && results.length > 0) return (0, react_jsx_runtime.jsx)(PromotedResultNotice, { locale: props.locale });
127272
+ if (results.length === 0) {
127273
+ if (!("kind" in block)) return (0, react_jsx_runtime.jsx)(ImageResultCard, { locale: props.locale });
127274
+ const content = block.content ?? block.resultView?.content ?? [];
127275
+ return (0, react_jsx_runtime.jsx)("div", { children: content.filter((item) => item.type === "text").map((item, index) => (0, react_jsx_runtime.jsx)("p", { children: item.text }, index)) });
127276
+ }
127277
+ return (0, react_jsx_runtime.jsx)("div", {
127278
+ className: "dsh-ig-promoted-results",
127279
+ children: results.map((result, index) => (0, react_jsx_runtime.jsx)(ImageResultCard, {
127280
+ result,
127281
+ locale: props.locale,
127282
+ sessionId: props.sessionId
127283
+ }, `${result.attachment.attachmentId}:${index}`))
127284
+ });
127285
+ }
124932
127286
  /** Render modern image artifacts as final conversation output instead of Tool process content. */
124933
127287
  function PromotedImageResultNode(props) {
124934
127288
  return (0, react_jsx_runtime.jsx)("div", {
124935
127289
  className: "dsh-ig-promoted-results",
124936
- children: props.node.data.results.map((result) => (0, react_jsx_runtime.jsx)(ImageResultCard, {
127290
+ children: props.node.data.results.map((result, index) => (0, react_jsx_runtime.jsx)(ImageResultCard, {
124937
127291
  result,
124938
127292
  locale: props.locale,
124939
127293
  sessionId: props.sessionId
124940
- }, result.attachment.attachmentId))
127294
+ }, `${result.attachment.attachmentId}:${index}`))
124941
127295
  });
124942
127296
  }
124943
127297
  function PromotedResultNotice({ locale }) {
@@ -125404,7 +127758,6 @@ img.ProseMirror-separator {
125404
127758
  (0, react_jsx_runtime.jsxs)("label", { children: [(0, react_jsx_runtime.jsx)("span", { children: t("prompt") }), (0, react_jsx_runtime.jsx)("textarea", {
125405
127759
  ref: regenerateTextareaRef,
125406
127760
  value: regeneratePrompt,
125407
- maxLength: 2e3,
125408
127761
  disabled: isRegenerating,
125409
127762
  onChange: (event) => setRegeneratePrompt(event.target.value),
125410
127763
  onKeyDown: (event) => {
@@ -125476,6 +127829,7 @@ img.ProseMirror-separator {
125476
127829
  }
125477
127830
  //#endregion
125478
127831
  exports.GeneratedImageCard = GeneratedImageCard;
127832
+ exports.GeneratedImagesCard = GeneratedImagesCard;
125479
127833
  exports.ImageGenerationSettingsCard = ImageGenerationSettingsCard;
125480
127834
  exports.PromotedImageResultNode = PromotedImageResultNode;
125481
127835
  exports.apply = apply;