dsh-image-gen 0.9.1 → 0.9.2

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
@@ -337,7 +337,7 @@ window.__ModuleLoader__.load({
337
337
  }
338
338
  if (!isRecord(value) || Object.keys(value).length === 0) throw new Error("ComfyUI workflow must be a non-empty API-format JSON object");
339
339
  if (!Object.values(value).some((node) => {
340
- const inputs = record$1(node)?.inputs;
340
+ const inputs = record$2(node)?.inputs;
341
341
  return inputs !== void 0 && containsPromptPlaceholder(inputs);
342
342
  })) throw new Error(`ComfyUI workflow must contain ${COMFYUI_PROMPT_PLACEHOLDER} (or ${LEGACY_PROMPT_PLACEHOLDER}) in a text input`);
343
343
  return value;
@@ -346,7 +346,7 @@ window.__ModuleLoader__.load({
346
346
  function countImagePlaceholders(workflow) {
347
347
  let count = 0;
348
348
  for (const node of Object.values(workflow)) {
349
- const inputs = record$1(record$1(node)?.inputs);
349
+ const inputs = record$2(record$2(node)?.inputs);
350
350
  if (inputs !== void 0 && isImagePlaceholder(inputs[IMAGE_INPUT_KEY])) count += 1;
351
351
  }
352
352
  return count;
@@ -362,7 +362,7 @@ window.__ModuleLoader__.load({
362
362
  function isRecord(value) {
363
363
  return typeof value === "object" && value !== null && !Array.isArray(value);
364
364
  }
365
- function record$1(value) {
365
+ function record$2(value) {
366
366
  return isRecord(value) ? value : void 0;
367
367
  }
368
368
  //#endregion
@@ -116729,8 +116729,27 @@ img.ProseMirror-separator {
116729
116729
  //#region lib/types/client/tl/tl-canvas-bridge.js
116730
116730
  const pending$1 = /* @__PURE__ */ new Map();
116731
116731
  const consumers = /* @__PURE__ */ new Set();
116732
+ const availabilityListeners = /* @__PURE__ */ new Set();
116732
116733
  let delivering = false;
116733
116734
  let generation = 0;
116735
+ /** History actions must not silently queue into a canvas that is not open. */
116736
+ function hasTlLandingConsumer() {
116737
+ return consumers.size > 0;
116738
+ }
116739
+ function subscribeTlLandingAvailability(listener) {
116740
+ availabilityListeners.add(listener);
116741
+ return () => {
116742
+ availabilityListeners.delete(listener);
116743
+ };
116744
+ }
116745
+ function sendTlLandingToOpenCanvas(item) {
116746
+ if (!hasTlLandingConsumer()) return false;
116747
+ pushTlLandings([{
116748
+ ...item,
116749
+ onlyWhileCanvasOpen: true
116750
+ }]);
116751
+ return true;
116752
+ }
116734
116753
  /** Invalidates async landings that started before the user cleared the canvas. */
116735
116754
  function getTlLandingGeneration() {
116736
116755
  return generation;
@@ -116750,12 +116769,18 @@ img.ProseMirror-separator {
116750
116769
  }
116751
116770
  /** Register a ready editor. False means it unmounted before committing. */
116752
116771
  function registerTlLandingConsumer(consumer) {
116772
+ const wasAvailable = hasTlLandingConsumer();
116753
116773
  consumers.add(consumer);
116774
+ if (!wasAvailable) for (const listener of availabilityListeners) listener();
116754
116775
  deliver();
116776
+ let registered = true;
116755
116777
  return () => {
116778
+ if (!registered) return;
116779
+ registered = false;
116756
116780
  consumers.delete(consumer);
116757
116781
  if (consumers.size === 0) {
116758
- for (const [id, item] of pending$1) if (item.fromConversation) pending$1.delete(id);
116782
+ for (const [id, item] of pending$1) if (item.fromConversation || item.onlyWhileCanvasOpen) pending$1.delete(id);
116783
+ for (const listener of availabilityListeners) listener();
116759
116784
  }
116760
116785
  };
116761
116786
  }
@@ -116782,6 +116807,84 @@ img.ProseMirror-separator {
116782
116807
  }
116783
116808
  }
116784
116809
  //#endregion
116810
+ //#region lib/types/client/tl/gallery-image-transfer.js
116811
+ const GALLERY_IMAGE_MIME = "application/x-dsh-image-gen-gallery-image";
116812
+ const EXTENSIONS = /* @__PURE__ */ new Map([
116813
+ ["image/png", "png"],
116814
+ ["image/jpeg", "jpg"],
116815
+ ["image/webp", "webp"],
116816
+ ["image/gif", "gif"]
116817
+ ]);
116818
+ function galleryLanding(item) {
116819
+ return {
116820
+ galleryId: item.id,
116821
+ attachment: item.attachment,
116822
+ prompt: item.prompt.slice(0, 120),
116823
+ provider: item.provider,
116824
+ model: item.model
116825
+ };
116826
+ }
116827
+ /** A real file also works when the host drops custom MIME data between containers. */
116828
+ function startGalleryImageDrag(event, item, blob) {
116829
+ const transfer = event.dataTransfer;
116830
+ const mediaType = blob?.type || item.attachment.mediaType;
116831
+ const extension = EXTENSIONS.get(mediaType);
116832
+ if (!transfer || !blob?.size || !extension) {
116833
+ event.preventDefault();
116834
+ return false;
116835
+ }
116836
+ try {
116837
+ const name = item.id.replace(/[^A-Za-z0-9_-]/g, "_").slice(0, 100) || "image";
116838
+ const file = new File([blob], `${name}.${extension}`, { type: mediaType });
116839
+ transfer.items.clear();
116840
+ if (transfer.items.add(file) === null) throw new Error("Drag file unavailable");
116841
+ transfer.effectAllowed = "copy";
116842
+ try {
116843
+ transfer.setData(GALLERY_IMAGE_MIME, JSON.stringify(galleryLanding(item)));
116844
+ } catch {}
116845
+ return true;
116846
+ } catch {
116847
+ event.preventDefault();
116848
+ return false;
116849
+ }
116850
+ }
116851
+ function readGalleryImageDrop(transfer) {
116852
+ try {
116853
+ const raw = transfer.getData(GALLERY_IMAGE_MIME);
116854
+ if (!raw || raw.length > 64e3) return void 0;
116855
+ const value = JSON.parse(raw);
116856
+ if (!record$1(value) || !text(value.galleryId) || !record$1(value.attachment)) return void 0;
116857
+ const a = value.attachment;
116858
+ if (!text(a.attachmentId) || typeof a.mediaType !== "string" || !EXTENSIONS.has(a.mediaType) || !dimension(a.bytes) || !dimension(a.width) || !dimension(a.height) || a.name !== void 0 && (typeof a.name !== "string" || a.name.length > 1024)) return void 0;
116859
+ if (typeof value.prompt !== "string" || value.prompt.length > 120 || typeof value.provider !== "string" || !text(value.provider) || typeof value.model !== "string" || value.model.length > 1024) return void 0;
116860
+ return {
116861
+ galleryId: value.galleryId,
116862
+ attachment: {
116863
+ attachmentId: a.attachmentId,
116864
+ mediaType: a.mediaType,
116865
+ bytes: a.bytes,
116866
+ width: a.width,
116867
+ height: a.height,
116868
+ ...typeof a.name === "string" ? { name: a.name } : {}
116869
+ },
116870
+ prompt: value.prompt,
116871
+ provider: value.provider,
116872
+ model: value.model
116873
+ };
116874
+ } catch {
116875
+ return;
116876
+ }
116877
+ }
116878
+ function record$1(value) {
116879
+ return typeof value === "object" && value !== null && !Array.isArray(value);
116880
+ }
116881
+ function text(value) {
116882
+ return typeof value === "string" && value.length > 0 && value.length <= 1024;
116883
+ }
116884
+ function dimension(value) {
116885
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
116886
+ }
116887
+ //#endregion
116785
116888
  //#region lib/types/client/tl/studio-tl-canvas.js
116786
116889
  /**
116787
116890
  * Infinite canvas surface for the Studio workbench, backed by tldraw.
@@ -116850,7 +116953,7 @@ img.ProseMirror-separator {
116850
116953
  const collapsed = prompt.replace(/\s+/g, " ").trim();
116851
116954
  return collapsed.length === 0 ? void 0 : collapsed.slice(0, 120);
116852
116955
  }
116853
- async function landTlItems(editor, items, isActive) {
116956
+ async function landTlItems(editor, items, isActive, onUnreadable) {
116854
116957
  const landedIds = landedIdsOf(editor);
116855
116958
  const pending = items.filter((item) => !landedIds.has(item.galleryId));
116856
116959
  if (pending.length === 0) return true;
@@ -116879,6 +116982,7 @@ img.ProseMirror-separator {
116879
116982
  });
116880
116983
  } catch (error) {
116881
116984
  console.warn("[dsh-image-gen] canvas landing skipped (attachment unreadable):", item.galleryId, error);
116985
+ if (isActive()) onUnreadable?.();
116882
116986
  continue;
116883
116987
  }
116884
116988
  const size = displaySizeOf(item.attachment);
@@ -116985,9 +117089,12 @@ img.ProseMirror-separator {
116985
117089
  phase: "idle",
116986
117090
  count: 0
116987
117091
  });
117092
+ const [landingError, setLandingError] = (0, react.useState)(false);
116988
117093
  const retrySync = (0, react.useRef)(() => {});
117094
+ const editorRef = (0, react.useRef)(null);
116989
117095
  const onMount = (0, react.useCallback)((editor) => {
116990
117096
  let active = true;
117097
+ editorRef.current = editor;
116991
117098
  editor.run(() => editor.selectNone());
116992
117099
  mountedEditors.add(editor);
116993
117100
  applyBrandTheme(editor);
@@ -116996,11 +117103,13 @@ img.ProseMirror-separator {
116996
117103
  console.info(`[dsh-image-gen] tldraw mounted (instance ${editor.id})`);
116997
117104
  const stopLanding = registerTlLandingConsumer((items) => {
116998
117105
  const generation = getTlLandingGeneration();
116999
- return landTlItems(editor, items, () => active && generation === getTlLandingGeneration());
117106
+ setLandingError(false);
117107
+ return landTlItems(editor, items, () => active && generation === getTlLandingGeneration(), () => setLandingError(true));
117000
117108
  });
117001
117109
  let stopSync = startCanvasSync(editor, setSyncStatus);
117002
117110
  retrySync.current = () => stopSync.retry();
117003
117111
  const clear = () => {
117112
+ setLandingError(false);
117004
117113
  stopSync();
117005
117114
  editor.complete();
117006
117115
  editor.run(() => {
@@ -117024,6 +117133,7 @@ img.ProseMirror-separator {
117024
117133
  mountedCanvases.add(clear);
117025
117134
  return () => {
117026
117135
  active = false;
117136
+ if (editorRef.current === editor) editorRef.current = null;
117027
117137
  mountedEditors.delete(editor);
117028
117138
  mountedCanvases.delete(clear);
117029
117139
  stopLanding();
@@ -117035,17 +117145,30 @@ img.ProseMirror-separator {
117035
117145
  const statusText = lang === "en" ? syncStatus.phase === "ready" ? `Selection ready · ${syncStatus.count} shapes` : syncStatus.phase === "preparing" ? `Preparing selection · ${syncStatus.count} shapes…` : "Selection sync failed" : syncStatus.phase === "ready" ? `选区已就绪 · ${syncStatus.count} 个元素` : syncStatus.phase === "preparing" ? `正在准备选区 · ${syncStatus.count} 个元素…` : "选区同步失败";
117036
117146
  return (0, react_jsx_runtime.jsxs)("div", {
117037
117147
  className: "dsh-ig-tl-canvas",
117148
+ onDropCapture: (event) => {
117149
+ const item = readGalleryImageDrop(event.dataTransfer);
117150
+ const editor = editorRef.current;
117151
+ if (!item || !editor) return;
117152
+ event.preventDefault();
117153
+ event.stopPropagation();
117154
+ setLandingError(false);
117155
+ const generation = getTlLandingGeneration();
117156
+ landTlItems(editor, [item], () => editorRef.current === editor && generation === getTlLandingGeneration(), () => setLandingError(true)).catch((error) => {
117157
+ if (editorRef.current === editor) setLandingError(true);
117158
+ console.warn("[dsh-image-gen] history image drop failed:", error);
117159
+ });
117160
+ },
117038
117161
  children: [(0, react_jsx_runtime.jsx)(Tldraw, {
117039
117162
  persistenceKey: "dsh-image-gen-workbench-v1",
117040
117163
  sessionId,
117041
117164
  components: TL_COMPONENTS,
117042
117165
  onMount
117043
- }), syncStatus.phase !== "idle" && (0, react_jsx_runtime.jsxs)("div", {
117166
+ }), (landingError || syncStatus.phase !== "idle") && (0, react_jsx_runtime.jsxs)("div", {
117044
117167
  className: "dsh-ig-canvas-sync-status",
117045
- "data-phase": syncStatus.phase,
117168
+ "data-phase": landingError ? "error" : syncStatus.phase,
117046
117169
  role: "status",
117047
117170
  "aria-live": "polite",
117048
- children: [(0, react_jsx_runtime.jsxs)("span", { children: [statusText, syncStatus.phase === "error" ? `:${canvasSyncError(syncStatus.error, lang)}` : ""] }), syncStatus.phase === "error" && (0, react_jsx_runtime.jsx)("button", {
117171
+ children: [(0, react_jsx_runtime.jsx)("span", { children: landingError ? lang === "en" ? "Could not read the original image. Reload it and try again." : "无法读取原图,请重新加载图片后重试。" : (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [statusText, syncStatus.phase === "error" ? `:${canvasSyncError(syncStatus.error, lang)}` : ""] }) }), !landingError && syncStatus.phase === "error" && (0, react_jsx_runtime.jsx)("button", {
117049
117172
  type: "button",
117050
117173
  onClick: () => retrySync.current(),
117051
117174
  children: lang === "en" ? "Retry" : "重试"
@@ -117062,6 +117185,26 @@ img.ProseMirror-separator {
117062
117185
  return en ? "Check the connection or re-import an unavailable image, then retry." : "请检查连接;若原图已失效,请重新导入后重试。";
117063
117186
  }
117064
117187
  //#endregion
117188
+ //#region lib/types/client/tl/gallery-canvas-button.js
117189
+ /** An explicit alternative to native dragging, including a visible unavailable state. */
117190
+ const GalleryCanvasButton = ({ item, lang, disabled, className, onToast }) => {
117191
+ const available = (0, react.useSyncExternalStore)(subscribeTlLandingAvailability, hasTlLandingConsumer, () => false);
117192
+ const open = lang === "en" ? "Open the infinite canvas first" : "请先打开无限画布";
117193
+ const title = !available ? open : disabled ? lang === "en" ? "Wait for the image to load" : "请等待图片加载完成" : lang === "en" ? "Send to canvas" : "发送到画布";
117194
+ return (0, react_jsx_runtime.jsx)("button", {
117195
+ type: "button",
117196
+ className,
117197
+ title,
117198
+ "aria-label": title,
117199
+ disabled: !available || disabled,
117200
+ onClick: (event) => {
117201
+ event.stopPropagation();
117202
+ onToast(sendTlLandingToOpenCanvas(galleryLanding(item)) ? lang === "en" ? "Sent to canvas" : "已发送到画布" : open);
117203
+ },
117204
+ children: (0, react_jsx_runtime.jsx)(Frame, { size: 14 })
117205
+ });
117206
+ };
117207
+ //#endregion
117065
117208
  //#region lib/types/client/studio-view.js
117066
117209
  /** Below this workbench width the recent-generations rail folds automatically. */
117067
117210
  const WORKBENCH_NARROW_WIDTH = 560;
@@ -118351,6 +118494,8 @@ img.ProseMirror-separator {
118351
118494
  }) : displayItems.map((item) => (0, react_jsx_runtime.jsx)(RecentItem, {
118352
118495
  item,
118353
118496
  active: selected?.id === item.id,
118497
+ lang,
118498
+ onToast: flash,
118354
118499
  onClick: () => selectItem(item)
118355
118500
  }, item.id)), items.length > visibleLimit && (0, react_jsx_runtime.jsx)("button", {
118356
118501
  type: "button",
@@ -118376,6 +118521,8 @@ img.ProseMirror-separator {
118376
118521
  children: list.map((item) => (0, react_jsx_runtime.jsx)(RecentItem, {
118377
118522
  item,
118378
118523
  active: false,
118524
+ lang,
118525
+ onToast: flash,
118379
118526
  onClick: () => void applyFavoriteImage(item)
118380
118527
  }, item.id))
118381
118528
  });
@@ -119602,23 +119749,37 @@ img.ProseMirror-separator {
119602
119749
  label: opt.value === "__default__" ? lang === "en" ? "Provider default (omit)" : "渠道默认(不发送)" : LOCALIZED_QUALITIES[opt.value]?.[lang] ?? opt.label
119603
119750
  }));
119604
119751
  }
119605
- const RecentItem = ({ item, active, onClick }) => {
119752
+ const RecentItem = ({ item, active, lang, onToast, onClick }) => {
119606
119753
  const [setRef, inView] = useInView();
119607
119754
  const image = useAttachmentImage(item.attachment, inView);
119608
- return (0, react_jsx_runtime.jsx)("button", {
119609
- ref: setRef,
119610
- type: "button",
119755
+ return (0, react_jsx_runtime.jsxs)("div", {
119611
119756
  className: `dsh-ig-recent-item ${active ? "is-active" : ""}`,
119612
- onClick,
119613
- title: item.prompt,
119614
- children: (0, react_jsx_runtime.jsx)("div", {
119615
- className: "dsh-ig-recent-thumb",
119616
- children: image.url !== null ? (0, react_jsx_runtime.jsx)("img", {
119617
- src: image.url,
119618
- alt: "",
119619
- loading: "lazy"
119620
- }) : (0, react_jsx_runtime.jsx)(ImagePlus, { size: 18 })
119621
- })
119757
+ children: [(0, react_jsx_runtime.jsx)("button", {
119758
+ ref: setRef,
119759
+ type: "button",
119760
+ className: "dsh-ig-recent-select",
119761
+ onClick,
119762
+ title: item.prompt,
119763
+ draggable: Boolean(image.blob?.size),
119764
+ onDragStart: (event) => {
119765
+ startGalleryImageDrag(event, item, image.blob);
119766
+ },
119767
+ children: (0, react_jsx_runtime.jsx)("div", {
119768
+ className: "dsh-ig-recent-thumb",
119769
+ children: image.url !== null ? (0, react_jsx_runtime.jsx)("img", {
119770
+ src: image.url,
119771
+ alt: "",
119772
+ loading: "lazy",
119773
+ draggable: false
119774
+ }) : (0, react_jsx_runtime.jsx)(ImagePlus, { size: 18 })
119775
+ })
119776
+ }), (0, react_jsx_runtime.jsx)(GalleryCanvasButton, {
119777
+ item,
119778
+ lang,
119779
+ disabled: !image.blob?.size,
119780
+ className: "dsh-ig-recent-send",
119781
+ onToast
119782
+ })]
119622
119783
  });
119623
119784
  };
119624
119785
  const FavoriteFolderBar = ({ folders, active, onSelect, onDelete, deleteLabel }) => {
@@ -123457,6 +123618,7 @@ img.ProseMirror-separator {
123457
123618
  }) : (0, react_jsx_runtime.jsx)("div", {
123458
123619
  className: "dsh-ig-gallery-grid",
123459
123620
  children: filteredItems.map((item, idx) => (0, react_jsx_runtime.jsx)(GalleryCard, {
123621
+ lang,
123460
123622
  item,
123461
123623
  index: idx,
123462
123624
  isManageMode,
@@ -123841,7 +124003,7 @@ img.ProseMirror-separator {
123841
124003
  ]
123842
124004
  });
123843
124005
  };
123844
- const GalleryCard = ({ item, index: _index, isManageMode, isSelected, t, onClick, onToggleSelect, onRequestDelete, onPreview: _onPreview, onBlobLoaded, onToast }) => {
124006
+ const GalleryCard = ({ lang, item, index: _index, isManageMode, isSelected, t, onClick, onToggleSelect, onRequestDelete, onPreview: _onPreview, onBlobLoaded, onToast }) => {
123845
124007
  const [url, setUrl] = (0, react.useState)();
123846
124008
  const [blob, setBlob] = (0, react.useState)();
123847
124009
  const [loading, setLoading] = (0, react.useState)(true);
@@ -123931,11 +124093,23 @@ img.ProseMirror-separator {
123931
124093
  className: "dsh-ig-gallery-card-img",
123932
124094
  src: url,
123933
124095
  alt: item.prompt,
123934
- loading: "lazy"
124096
+ loading: "lazy",
124097
+ draggable: !isManageMode && Boolean(blob?.size),
124098
+ onDragStart: (event) => {
124099
+ if (isManageMode) event.preventDefault();
124100
+ else startGalleryImageDrag(event, item, blob);
124101
+ }
123935
124102
  }),
123936
124103
  (0, react_jsx_runtime.jsxs)("div", {
123937
124104
  className: "dsh-ig-card-toolbar",
123938
124105
  children: [
124106
+ !isManageMode && (0, react_jsx_runtime.jsx)(GalleryCanvasButton, {
124107
+ item,
124108
+ lang,
124109
+ disabled: !blob?.size,
124110
+ className: "dsh-ig-tool-btn",
124111
+ onToast
124112
+ }),
123939
124113
  (0, react_jsx_runtime.jsx)("button", {
123940
124114
  type: "button",
123941
124115
  className: "dsh-ig-tool-btn",
@@ -124229,12 +124403,48 @@ img.ProseMirror-separator {
124229
124403
  }
124230
124404
  //#endregion
124231
124405
  //#region lib/types/client/tl/conversation-landings.js
124406
+ /** Keep one landing subscription while the optional UI service loads or unloads. */
124407
+ function createCanvasSessionSelection() {
124408
+ let current;
124409
+ const listeners = /* @__PURE__ */ new Set();
124410
+ const notify = () => {
124411
+ for (const listener of listeners) listener();
124412
+ };
124413
+ return {
124414
+ getSnapshot: () => current?.getSnapshot(),
124415
+ subscribe: (listener) => {
124416
+ listeners.add(listener);
124417
+ return () => {
124418
+ listeners.delete(listener);
124419
+ };
124420
+ },
124421
+ bind(source) {
124422
+ current = source;
124423
+ const stop = source.subscribe(notify);
124424
+ notify();
124425
+ return () => {
124426
+ stop();
124427
+ if (current !== source) return;
124428
+ current = void 0;
124429
+ notify();
124430
+ };
124431
+ }
124432
+ };
124433
+ }
124434
+ function currentSessionId(sessions, selection) {
124435
+ const selected = selection?.getSnapshot();
124436
+ if (selected !== void 0) return selected.key || void 0;
124437
+ const snapshot = sessions.list.getSnapshot();
124438
+ if (snapshot.current) return snapshot.current;
124439
+ const mainViews = Object.values(snapshot.byId ?? {}).filter((row) => (row.retainedBy?.mainView ?? 0) > 0);
124440
+ return mainViews.length === 1 ? mainViews[0].id : void 0;
124441
+ }
124232
124442
  /** Subscribe to live additions, never to card rendering or history replay. */
124233
- function startConversationLandings(sessions) {
124443
+ function startConversationLandings(sessions, selection) {
124234
124444
  let source;
124235
124445
  let stopEvents;
124236
124446
  const followCurrent = () => {
124237
- const id = sessions.list.getSnapshot().current;
124447
+ const id = currentSessionId(sessions, selection);
124238
124448
  const next = id === void 0 ? void 0 : sessions.binding(id)?.eventSource;
124239
124449
  if (next === source) return;
124240
124450
  stopEvents?.();
@@ -124263,15 +124473,20 @@ img.ProseMirror-separator {
124263
124473
  });
124264
124474
  };
124265
124475
  const stopList = sessions.list.subscribe(followCurrent);
124476
+ const stopSelection = selection?.subscribe(followCurrent);
124266
124477
  followCurrent();
124267
124478
  return () => {
124268
124479
  stopList();
124480
+ stopSelection?.();
124269
124481
  stopEvents?.();
124270
124482
  };
124271
124483
  }
124272
124484
  //#endregion
124273
124485
  //#region lib/types/client/studio-style.js
124274
124486
  const STUDIO_STYLE = `
124487
+ .dsh-ig-recent-select{display:block;width:100%;height:100%;padding:0;border:0;border-radius:inherit;background:transparent;cursor:pointer}
124488
+ .dsh-ig-recent-send{position:absolute;right:3px;bottom:3px;display:flex;align-items:center;justify-content:center;width:25px;height:25px;padding:0;border:1px solid var(--ig-line);border-radius:5px;background:var(--ig-bg-panel);cursor:pointer}
124489
+ .dsh-ig-recent-send:disabled{opacity:.45;cursor:not-allowed}
124275
124490
  .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}
124276
124491
  .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%)}
124277
124492
  body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a}
@@ -125221,7 +125436,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
125221
125436
  `;
125222
125437
  //#endregion
125223
125438
  //#region lib/types/client/index.js
125224
- console.info(`[dsh-image-gen] canvas bundle 2026-10-15T09:51:10.196Z`);
125439
+ console.info(`[dsh-image-gen] canvas bundle 2026-10-19T16:02:35.438Z`);
125225
125440
  /** Right-sidebar tab-type registration id (DSH 0.1.5 `sidebarRightTabs`). */
125226
125441
  const SIDEBAR_STUDIO_TAB_ID = "dsh-image-gen/studio";
125227
125442
  /** The tab kind: a page type opened by kind, recognizing no resource address. */
@@ -125296,6 +125511,11 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
125296
125511
  reset: "重置",
125297
125512
  resetTitle: "重置为默认官方地址",
125298
125513
  endpointHintGoogle: "Google 官方地址或反代端点(全路径)。",
125514
+ googleProtocol: "请求协议",
125515
+ googleInteractions: "Interactions(默认)",
125516
+ googleNative: "原生 generateContent",
125517
+ saveGoogleProtocolFirst: "请求协议尚未保存,请先点击保存,再拉取模型或测试连接。",
125518
+ endpointHintGoogleNative: "填写 API 基础地址(如 https://example.com/v1beta);也兼容 Interactions 或 generateContent 完整地址。",
125299
125519
  endpointHintOpenAI: "官方 api.openai.com 的 /v1 地址;中转站请使用下方「OpenAI 兼容」行。",
125300
125520
  endpointHintOpenAICompat: "中转站/自建服务的 OpenAI 兼容 /v1 地址(必填),例如 https://your-relay.example.com/v1。",
125301
125521
  editFormat: "图生图请求形态",
@@ -125461,6 +125681,11 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
125461
125681
  reset: "Reset",
125462
125682
  resetTitle: "Reset to official default URL",
125463
125683
  endpointHintGoogle: "Official Google endpoint or reverse proxy (full path).",
125684
+ googleProtocol: "Request protocol",
125685
+ googleInteractions: "Interactions (default)",
125686
+ googleNative: "Native generateContent",
125687
+ saveGoogleProtocolFirst: "Save the request protocol before fetching models or testing the connection.",
125688
+ endpointHintGoogleNative: "API base URL (e.g. https://example.com/v1beta). Full Interactions or generateContent URLs also work.",
125464
125689
  endpointHintOpenAI: "Official api.openai.com /v1 base URL; for relays use the \"OpenAI-compatible\" row below.",
125465
125690
  endpointHintOpenAICompat: "OpenAI-compatible /v1 base URL of your relay or self-hosted service (required), e.g. https://your-relay.example.com/v1.",
125466
125691
  editFormat: "Edit request format",
@@ -125881,7 +126106,13 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
125881
126106
  ctx.inject(["sessions"], (owner) => {
125882
126107
  const sessions = owner.get("sessions");
125883
126108
  if (typeof sessions?.binding !== "function" || typeof sessions.list?.subscribe !== "function") return;
125884
- owner.effect(() => startConversationLandings(sessions), "dsh-image-gen: live canvas images");
126109
+ const selection = createCanvasSessionSelection();
126110
+ owner.effect(() => startConversationLandings(sessions, selection), "dsh-image-gen: live canvas images");
126111
+ owner.inject(["uiSession"], (uiOwner) => {
126112
+ const current = uiOwner.get("uiSession")?.adapter?.current;
126113
+ if (typeof current?.getSnapshot !== "function" || typeof current.subscribe !== "function") return;
126114
+ uiOwner.effect(() => selection.bind(current), "dsh-image-gen: current canvas session");
126115
+ });
125885
126116
  });
125886
126117
  ctx.effect(() => {
125887
126118
  const style = document.createElement("style");
@@ -126098,6 +126329,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126098
126329
  expanded: false,
126099
126330
  model: "",
126100
126331
  baseURL: "",
126332
+ googleNative: false,
126101
126333
  keyInput: "",
126102
126334
  keyStatus: "checking",
126103
126335
  keyInfo: void 0,
@@ -126187,6 +126419,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126187
126419
  ...emptyProviderRow(),
126188
126420
  model: modelOf(provider, value),
126189
126421
  baseURL: baseURLOf(provider, value),
126422
+ ...provider === "google" ? { googleNative: value?.googleNative === true } : {},
126190
126423
  ...provider === "openai" ? { sizeTableText: editExtraTextOf(value?.openaiSizes) } : {},
126191
126424
  ...provider === "comfyui" ? {
126192
126425
  workflows: resolveComfyUIWorkflows(value ?? {}),
@@ -126573,6 +126806,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126573
126806
  if (provider === "openai") for (const tiers of Object.values(sizeTable)) for (const size of Object.values(tiers)) validateOfficialSize(row.model, size);
126574
126807
  await saveSetting(modelFieldOf(provider), row.model);
126575
126808
  await saveSetting(baseURLFieldOf(provider), row.baseURL);
126809
+ if (provider === "google") await saveSetting("googleNative", row.googleNative);
126576
126810
  if (provider === "openai") await saveSetting("openaiSizes", sizeTable);
126577
126811
  if (provider === "openai-compat") {
126578
126812
  await saveSetting("openaiCompatEditFormat", row.editFormat);
@@ -126624,6 +126858,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126624
126858
  const address = row.baseURL.trim();
126625
126859
  if (address.length === 0) return t("endpointRequired");
126626
126860
  if (address !== baseURLOf(provider, props.scope.getSnapshot().value).trim()) return t("saveEndpointFirst");
126861
+ if (provider === "google" && row.googleNative !== (props.scope.getSnapshot().value?.googleNative === true)) return t("saveGoogleProtocolFirst");
126627
126862
  };
126628
126863
  /** Localize the host's missing-address error while preserving upstream details. */
126629
126864
  const providerErrorMessage = (message) => message === "Base URL is not configured" ? t("savedEndpointMissing") : message ?? t("testFailed");
@@ -126995,10 +127230,31 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126995
127230
  }),
126996
127231
  (0, react_jsx_runtime.jsx)("span", {
126997
127232
  className: "dsh-ig-hint",
126998
- children: t(CLOUD_HINT_KEYS[provider])
127233
+ children: t(provider === "google" && row.googleNative ? "endpointHintGoogleNative" : CLOUD_HINT_KEYS[provider])
126999
127234
  })
127000
127235
  ]
127001
127236
  }),
127237
+ provider === "google" ? (0, react_jsx_runtime.jsxs)("label", {
127238
+ className: "dsh-ig-field",
127239
+ children: [(0, react_jsx_runtime.jsx)("span", {
127240
+ className: "dsh-ig-label",
127241
+ children: t("googleProtocol")
127242
+ }), (0, react_jsx_runtime.jsxs)("select", {
127243
+ className: "dsh-ig-input",
127244
+ value: row.googleNative ? "native" : "interactions",
127245
+ onChange: (event) => {
127246
+ updateRow(provider, { googleNative: event.target.value === "native" });
127247
+ },
127248
+ disabled: !settingsWritable,
127249
+ children: [(0, react_jsx_runtime.jsx)("option", {
127250
+ value: "interactions",
127251
+ children: t("googleInteractions")
127252
+ }), (0, react_jsx_runtime.jsx)("option", {
127253
+ value: "native",
127254
+ children: t("googleNative")
127255
+ })]
127256
+ })]
127257
+ }) : null,
127002
127258
  (0, react_jsx_runtime.jsxs)("label", {
127003
127259
  className: "dsh-ig-field",
127004
127260
  children: [