dsh-image-gen 0.11.0 → 0.11.1

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/README.en.md CHANGED
@@ -316,6 +316,7 @@ Bring private image generation on your local GPU directly into Agent conversatio
316
316
 
317
317
  - Import and manage multiple named workflows with prompt presets and common placeholders.
318
318
  - Let the Agent select a workflow by name for text-to-image, image-to-image, or batch generation in chat.
319
+ - Placeholders: `{{prompt}}`, `{{seed}}`; with `{{width}}`/`{{height}}` the size or aspect ratio the Agent asks for takes effect (1024×1024 by default); for editing put `{{image}}` in a LoadImage input, one per reference image, filled in node order.
319
320
 
320
321
  > ComfyUI is not yet integrated into Studio or multi-model comparison.
321
322
 
@@ -342,7 +343,7 @@ Bring private image generation on your local GPU directly into Agent conversatio
342
343
  | **Aliyun DashScope / Qwen Image** | ✅ | ✅ | ✅ Multiple | ✅ | ✅ | — |
343
344
  | **xAI Grok Imagine** | ✅ | ✅ | ⚠️ Limited | ✅ | ✅ | — |
344
345
  | **Zhipu GLM-Image** | ✅ | ✅ | — | ✅ | ✅ | — |
345
- | **Local ComfyUI** | ✅ | ✅ | ✅ Single | — | — | — |
346
+ | **Local ComfyUI** | ✅ | ✅ | ✅ Multiple | — | — | — |
346
347
  | **ChatGPT subscription (key-free)** | ✅ | ✅ | ✅ Multiple | ✅ | ✅ | — |
347
348
  | **Grok subscription (key-free)** | ✅ | ✅ | ✅ Multiple | ✅ | ✅ | — |
348
349
  | **Google subscription (key-free)** | ✅ | ✅ | ✅ Multiple | ✅ | ✅ | — |
@@ -412,7 +413,7 @@ The configuration field is `googleNative: true`. It defaults to `false`, keeping
412
413
  <details>
413
414
  <summary><strong>Why is ComfyUI not available in Studio?</strong></summary>
414
415
 
415
- Studio and multi-model comparison currently support cloud Providers only; ComfyUI is not yet integrated. ComfyUI supports text-to-image, single-image editing, batch generation, and multiple named workflows through Agent chat.
416
+ Studio and multi-model comparison currently support cloud Providers only; ComfyUI is not yet integrated. ComfyUI supports text-to-image, image editing (with several reference images), batch generation, and multiple named workflows through Agent chat.
416
417
 
417
418
  </details>
418
419
 
package/README.md CHANGED
@@ -316,6 +316,7 @@ pnpm dsh plugin --profile web add ./dsh-image-gen
316
316
 
317
317
  - 导入并管理多个命名工作流,支持预设 Prompt 和常用占位符。
318
318
  - Agent 可按名称选择工作流,在对话中完成文生图、图生图和批量生图。
319
+ - 占位符:`{{prompt}}`、`{{seed}}`;宽高写 `{{width}}`/`{{height}}` 后,Agent 传入的尺寸或比例才会生效(默认 1024×1024);图生图在 LoadImage 写 `{{image}}`,多张参考图就写多个,按节点编号顺序对应。
319
320
 
320
321
  > ComfyUI 暂未接入 Studio 和多模型对比。
321
322
 
@@ -342,7 +343,7 @@ pnpm dsh plugin --profile web add ./dsh-image-gen
342
343
  | **Aliyun DashScope / Qwen Image** | ✅ | ✅ | ✅ 多图 | ✅ | ✅ | — |
343
344
  | **xAI Grok Imagine** | ✅ | ✅ | ⚠️ 有限 | ✅ | ✅ | — |
344
345
  | **智谱 GLM-Image** | ✅ | ✅ | — | ✅ | ✅ | — |
345
- | **Local ComfyUI** | ✅ | ✅ | ✅ 单图 | — | — | — |
346
+ | **Local ComfyUI** | ✅ | ✅ | ✅ 多图 | — | — | — |
346
347
  | **ChatGPT 订阅(免 Key)** | ✅ | ✅ | ✅ 多图 | ✅ | ✅ | — |
347
348
  | **Grok 订阅(免 Key)** | ✅ | ✅ | ✅ 多图 | ✅ | ✅ | — |
348
349
  | **Google 订阅(免 Key)** | ✅ | ✅ | ✅ 多图 | ✅ | ✅ | — |
@@ -412,7 +413,7 @@ dsh --profile web --dump-config
412
413
  <details>
413
414
  <summary><strong>为什么 ComfyUI 没有出现在 Studio 中?</strong></summary>
414
415
 
415
- 当前 Studio 与多模型对比仅支持云端 Provider,暂未接入 ComfyUI。ComfyUI 已支持在 Agent 对话中进行文生图、单图编辑、批量生图以及多个命名工作流。
416
+ 当前 Studio 与多模型对比仅支持云端 Provider,暂未接入 ComfyUI。ComfyUI 已支持在 Agent 对话中进行文生图、图生图(支持多张参考图)、批量生图以及多个命名工作流。
416
417
 
417
418
  </details>
418
419
 
package/lib/client.js CHANGED
@@ -551,6 +551,19 @@ window.__ModuleLoader__.load({
551
551
  "grok-sub": DEFAULT_SUBSCRIPTION_MODELS["grok-sub"],
552
552
  "google-sub": DEFAULT_SUBSCRIPTION_MODELS["google-sub"]
553
553
  };
554
+ /**
555
+ * Official consoles where each API-key provider issues keys, linked next to
556
+ * the key field (#109). Relays (openai-compat), ComfyUI and the subscription
557
+ * channels have no single place to get one.
558
+ */
559
+ const API_KEY_CONSOLE_URLS = {
560
+ google: "https://aistudio.google.com/apikey",
561
+ openai: "https://platform.openai.com/api-keys",
562
+ seedream: "https://console.volcengine.com/ark/region:ark+cn-beijing/apiKey",
563
+ dashscope: "https://bailian.console.aliyun.com/",
564
+ xai: "https://console.x.ai/team/default/api-keys",
565
+ zhipu: "https://bigmodel.cn/usercenter/proj-mgmt/apikeys"
566
+ };
554
567
  const DEFAULT_BASE_URLS = {
555
568
  google: DEFAULT_GOOGLE_ENDPOINT,
556
569
  openai: DEFAULT_OPENAI_BASE_URL,
@@ -600,16 +613,11 @@ window.__ModuleLoader__.load({
600
613
  //#region lib/types/comfyui-workflow.js
601
614
  /** Pure ComfyUI API-workflow validation and placeholder injection. */
602
615
  const COMFYUI_PROMPT_PLACEHOLDER = "{{prompt}}";
603
- const COMFYUI_IMAGE_PLACEHOLDER = "{{image}}";
604
616
  /** Legacy single-percent placeholders from early releases, still accepted. */
605
617
  const LEGACY_PROMPT_PLACEHOLDER = "%prompt%";
606
- const LEGACY_IMAGE_PLACEHOLDER = "%image%";
607
- /** LoadImage-style input key that receives the uploaded source image name. */
608
- const IMAGE_INPUT_KEY = "image";
609
618
  /** Validate imported JSON without exposing workflow graph details to callers. */
610
619
  function validateComfyUIWorkflowJson(workflowJson) {
611
- const imageInputs = countImagePlaceholders(parseWorkflow(workflowJson));
612
- if (imageInputs > 1) throw new Error(`ComfyUI workflow must contain at most one ${COMFYUI_IMAGE_PLACEHOLDER} image input; found ${String(imageInputs)}`);
620
+ parseWorkflow(workflowJson);
613
621
  }
614
622
  function parseWorkflow(workflowJson) {
615
623
  if (workflowJson.trim().length === 0) throw new Error("Import a ComfyUI API workflow JSON file in Settings before generating");
@@ -627,18 +635,6 @@ window.__ModuleLoader__.load({
627
635
  })) throw new Error(`ComfyUI workflow must contain ${COMFYUI_PROMPT_PLACEHOLDER} (or ${LEGACY_PROMPT_PLACEHOLDER}) in a text input`);
628
636
  return value;
629
637
  }
630
- /** Count `inputs.image` fields that are exactly an image placeholder. */
631
- function countImagePlaceholders(workflow) {
632
- let count = 0;
633
- for (const node of Object.values(workflow)) {
634
- const inputs = record$2(record$2(node)?.inputs);
635
- if (inputs !== void 0 && isImagePlaceholder(inputs[IMAGE_INPUT_KEY])) count += 1;
636
- }
637
- return count;
638
- }
639
- function isImagePlaceholder(value) {
640
- return value === "{{image}}" || value === LEGACY_IMAGE_PLACEHOLDER;
641
- }
642
638
  function containsPromptPlaceholder(value) {
643
639
  if (typeof value === "string") return value.includes("{{prompt}}") || value.includes(LEGACY_PROMPT_PLACEHOLDER);
644
640
  if (Array.isArray(value)) return value.some(containsPromptPlaceholder);
@@ -127748,6 +127744,7 @@ img.ProseMirror-separator {
127748
127744
  apiKeyLabel: "{provider} API Key",
127749
127745
  apiKeyPlaceholder: "留空即可保留已配置的 Key",
127750
127746
  apiKeyHint: "安全保存为 {key};页面不会读回明文。",
127747
+ getApiKey: "去官方获取 Key ↗",
127751
127748
  keyReadOnly: "Key 由 {source} 提供且只读;请在该来源中修改。",
127752
127749
  credentialsUnavailable: "凭据服务不可用,无法保存 API Key;建议升级 DSH 到稳定版本。",
127753
127750
  badgeUnavailable: "凭据服务不可用",
@@ -127832,7 +127829,7 @@ img.ProseMirror-separator {
127832
127829
  workflowImport: "导入 JSON 文件",
127833
127830
  workflowMissing: "尚未导入工作流",
127834
127831
  workflowImported: "已导入 {name}",
127835
- workflowHint: "从 ComfyUI 导出 API Format JSON,在提示词输入写入 {{prompt}},种子可用 {{seed}};图生图工作流在 LoadImage 的 image 输入写入 {{image}}(仅一次)。可导入多个工作流,Agent 也能在调用时按名称指定。",
127832
+ workflowHint: "从 ComfyUI 导出 API Format JSON,在提示词输入写入 {{prompt}},种子可用 {{seed}};宽高可用 {{width}}、{{height}}(Agent 传入尺寸或比例时生效,默认 1024×1024);图生图工作流在 LoadImage 的 image 输入写入 {{image}},多张参考图就写多个,按节点编号顺序对应。可导入多个工作流,Agent 也能在调用时按名称指定。",
127836
127833
  workflowTooLarge: "工作流文件不能超过 5 MB。",
127837
127834
  workflowActiveTitle: "设为当前使用的工作流",
127838
127835
  workflowRemove: "删除",
@@ -127933,6 +127930,7 @@ img.ProseMirror-separator {
127933
127930
  apiKeyLabel: "{provider} API Key",
127934
127931
  apiKeyPlaceholder: "Leave empty to keep configured key",
127935
127932
  apiKeyHint: "Securely saved as {key}; never read back in plaintext.",
127933
+ getApiKey: "Get a key ↗",
127936
127934
  keyReadOnly: "Key is supplied read-only by {source}; update it there.",
127937
127935
  credentialsUnavailable: "Credentials service unavailable: API keys cannot be saved; please upgrade DSH to a stable release.",
127938
127936
  badgeUnavailable: "Credentials unavailable",
@@ -128017,7 +128015,7 @@ img.ProseMirror-separator {
128017
128015
  workflowImport: "Import JSON file",
128018
128016
  workflowMissing: "No workflow imported",
128019
128017
  workflowImported: "Imported {name}",
128020
- workflowHint: "Export an API Format JSON from ComfyUI and place {{prompt}} in its prompt input; {{seed}} is available for a random seed. For image editing put {{image}} (exactly once) in the LoadImage image input. Import as many workflows as you need; the Agent can also pick one by name.",
128018
+ workflowHint: "Export an API Format JSON from ComfyUI and place {{prompt}} in its prompt input; {{seed}} is available for a random seed. {{width}} and {{height}} take the size or aspect ratio the Agent asks for (1024×1024 by default). For image editing put {{image}} in a LoadImage image input; use one per reference image, filled in node order. Import as many workflows as you need; the Agent can also pick one by name.",
128021
128019
  workflowTooLarge: "Workflow files must be no larger than 5 MB.",
128022
128020
  workflowActiveTitle: "Make this the active workflow",
128023
128021
  workflowRemove: "Remove",
@@ -129896,6 +129894,7 @@ img.ProseMirror-separator {
129896
129894
  .dsh-ig-btn-reset{appearance:none;border:0.5px solid var(--dsw-alias-border-l4,#d7dbe0);border-radius:var(--dsw-radius-md,6px);padding:5px 12px;background:var(--dsw-alias-bg-layer-3,#f9fafb);color:var(--dsw-alias-label-secondary,inherit);font:inherit;font-size:13px;cursor:pointer;white-space:nowrap;transition:background .15s,border-color .15s}
129897
129895
  .dsh-ig-btn-reset:hover{background:var(--dsw-alias-bg-layer-2,#edf0f3);border-color:var(--dsw-alias-label-dimmed,#9ca3af)}
129898
129896
  .dsh-ig-hint,.dsh-ig-status{margin:0;color:var(--dsw-alias-label-tertiary,#7b818b);font-size:12px;line-height:1.4}
129897
+ .dsh-ig-link{color:var(--dsw-alias-label-link,#2563eb);text-decoration:none;white-space:nowrap}.dsh-ig-link:hover{text-decoration:underline}
129899
129898
  .dsh-ig-hint-error{color:var(--dsw-alias-label-error,#d33)}
129900
129899
  .dsh-ig-status-error{color:var(--dsw-alias-label-error,#d33);font-weight:500}
129901
129900
  .dsh-ig-actions{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-top:16px;padding-top:12px;border-top:1px solid var(--dsw-alias-border-l2,#eee)}
@@ -130904,18 +130903,46 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
130904
130903
  "google-sub": "Google Sub"
130905
130904
  }
130906
130905
  };
130906
+ /**
130907
+ * Where `position: fixed` offsets start next to the pill, in viewport
130908
+ * coordinates. The host composer wraps the tool row in a transformed box
130909
+ * (`will-change: transform`), which makes fixed descendants resolve against
130910
+ * that box instead of the viewport (#109): viewport coordinates then land the
130911
+ * menu a sidebar-width off whenever the left sidebar is open. Two zero-size
130912
+ * probes read the real origin, whichever CSS rule creates it.
130913
+ */
130914
+ function fixedOriginNear(host) {
130915
+ const probe = (css) => {
130916
+ const element = document.createElement("div");
130917
+ element.style.cssText = `position:fixed;width:0;height:0;visibility:hidden;pointer-events:none;${css}`;
130918
+ host.appendChild(element);
130919
+ const rect = element.getBoundingClientRect();
130920
+ element.remove();
130921
+ return rect;
130922
+ };
130923
+ const topLeft = probe("left:0;top:0");
130924
+ return {
130925
+ left: topLeft.left,
130926
+ top: topLeft.top,
130927
+ bottom: probe("left:0;bottom:0").bottom
130928
+ };
130929
+ }
130907
130930
  /** Compute the fixed-position anchor from the pill button: open upward when
130908
- * the chat pane leaves room above the composer, drop down otherwise. */
130931
+ * the chat pane leaves room above the composer, drop down otherwise. The
130932
+ * numbers are relative to the box fixed elements resolve against. */
130909
130933
  function anchorOf(button) {
130910
130934
  const rect = button?.getBoundingClientRect();
130911
- if (rect === void 0) return void 0;
130935
+ const host = button?.parentElement;
130936
+ if (rect === void 0 || host === null || host === void 0) return void 0;
130937
+ const origin = fixedOriginNear(host);
130938
+ const left = rect.left - origin.left;
130912
130939
  if (rect.top >= 332) return {
130913
- left: rect.left,
130914
- bottom: window.innerHeight - rect.top + 6
130940
+ left,
130941
+ bottom: origin.bottom - rect.top + 6
130915
130942
  };
130916
130943
  return {
130917
- left: rect.left,
130918
- top: rect.bottom + 6
130944
+ left,
130945
+ top: rect.bottom + 6 - origin.top
130919
130946
  };
130920
130947
  }
130921
130948
  /** Whether the user opted into the composer pill; hidden (not merely inert) when off. */
@@ -131384,7 +131411,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
131384
131411
  `;
131385
131412
  //#endregion
131386
131413
  //#region lib/types/client/index.js
131387
- console.info(`[dsh-image-gen] canvas bundle 2026-11-08T14:18:24.304Z`);
131414
+ console.info(`[dsh-image-gen] canvas bundle 2026-11-08T15:35:09.403Z`);
131388
131415
  /** Right-sidebar tab-type registration id (DSH 0.1.5 `sidebarRightTabs`). */
131389
131416
  const SIDEBAR_STUDIO_TAB_ID = "dsh-image-gen/studio";
131390
131417
  /** The tab kind: a page type opened by kind, recognizing no resource address. */
@@ -132701,9 +132728,15 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
132701
132728
  placeholder: row.keyStatus === "configured" ? t("apiKeyPlaceholder") : "",
132702
132729
  disabled: !settingsWritable || keyReadOnly || keyUnavailable
132703
132730
  }),
132704
- (0, react_jsx_runtime.jsx)("span", {
132731
+ (0, react_jsx_runtime.jsxs)("span", {
132705
132732
  className: "dsh-ig-hint",
132706
- children: keyUnavailable ? t("credentialsUnavailable") : keyReadOnly ? t("keyReadOnly", { source: row.keyInfo?.source ?? "" }) : t("apiKeyHint", { key: keyRef })
132733
+ children: [keyUnavailable ? t("credentialsUnavailable") : keyReadOnly ? t("keyReadOnly", { source: row.keyInfo?.source ?? "" }) : t("apiKeyHint", { key: keyRef }), channel === void 0 && API_KEY_CONSOLE_URLS[provider] !== void 0 ? (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [" ", (0, react_jsx_runtime.jsx)("a", {
132734
+ className: "dsh-ig-link",
132735
+ href: API_KEY_CONSOLE_URLS[provider],
132736
+ target: "_blank",
132737
+ rel: "noopener noreferrer",
132738
+ children: t("getApiKey")
132739
+ })] }) : null]
132707
132740
  })
132708
132741
  ]
132709
132742
  }),
package/lib/index.js CHANGED
@@ -7255,6 +7255,88 @@ function readSelectionScreenshot(entry, input) {
7255
7255
  const COMFYUI_PROMPT_PLACEHOLDER = "{{prompt}}";
7256
7256
  const COMFYUI_SEED_PLACEHOLDER = "{{seed}}";
7257
7257
  const COMFYUI_IMAGE_PLACEHOLDER = "{{image}}";
7258
+ const COMFYUI_WIDTH_PLACEHOLDER = "{{width}}";
7259
+ const COMFYUI_HEIGHT_PLACEHOLDER = "{{height}}";
7260
+ /** Frame size used for {{width}} / {{height}} when a call names no size. */
7261
+ const COMFYUI_DEFAULT_SIZE = {
7262
+ width: 1024,
7263
+ height: 1024
7264
+ };
7265
+ /**
7266
+ * Pixel sizes for an aspect ratio, about one megapixel in multiples of 64:
7267
+ * the buckets SDXL / Flux style models are trained on.
7268
+ */
7269
+ const COMFYUI_RATIO_SIZES = {
7270
+ "1:1": {
7271
+ width: 1024,
7272
+ height: 1024
7273
+ },
7274
+ "4:3": {
7275
+ width: 1152,
7276
+ height: 896
7277
+ },
7278
+ "3:4": {
7279
+ width: 896,
7280
+ height: 1152
7281
+ },
7282
+ "3:2": {
7283
+ width: 1216,
7284
+ height: 832
7285
+ },
7286
+ "2:3": {
7287
+ width: 832,
7288
+ height: 1216
7289
+ },
7290
+ "5:4": {
7291
+ width: 1088,
7292
+ height: 896
7293
+ },
7294
+ "4:5": {
7295
+ width: 896,
7296
+ height: 1088
7297
+ },
7298
+ "16:9": {
7299
+ width: 1344,
7300
+ height: 768
7301
+ },
7302
+ "9:16": {
7303
+ width: 768,
7304
+ height: 1344
7305
+ },
7306
+ "21:9": {
7307
+ width: 1536,
7308
+ height: 640
7309
+ }
7310
+ };
7311
+ /**
7312
+ * The frame size a call asks for: an exact `WIDTHxHEIGHT`, or an aspect ratio
7313
+ * mapped to its bucket. Undefined when the call names neither.
7314
+ */
7315
+ function comfyUIRequestedSize(size, aspectRatio) {
7316
+ if (size !== void 0) {
7317
+ const match = /^(\d+)\s*[x*×]\s*(\d+)$/i.exec(size.trim());
7318
+ const width = Number(match?.[1]);
7319
+ const height = Number(match?.[2]);
7320
+ if (match === null || !(width >= 64 && width <= 8192 && height >= 64 && height <= 8192)) throw new Error(`ComfyUI size "${size}" must be WIDTHxHEIGHT with each edge between 64 and 8192`);
7321
+ return {
7322
+ width,
7323
+ height
7324
+ };
7325
+ }
7326
+ if (aspectRatio !== void 0) {
7327
+ const bucket = COMFYUI_RATIO_SIZES[aspectRatio.trim()];
7328
+ if (bucket === void 0) throw new Error(`ComfyUI aspect_ratio "${aspectRatio}" is not supported; use one of ${Object.keys(COMFYUI_RATIO_SIZES).join(", ")} or an exact size`);
7329
+ return { ...bucket };
7330
+ }
7331
+ }
7332
+ /** Inspect the placeholders of an imported workflow without preparing it. */
7333
+ function comfyUIWorkflowSlots(workflowJson) {
7334
+ const workflow = parseWorkflow(workflowJson);
7335
+ return {
7336
+ images: countImagePlaceholders(workflow),
7337
+ size: containsSizePlaceholder(workflow)
7338
+ };
7339
+ }
7258
7340
  /** Legacy single-percent placeholders from early releases, still accepted. */
7259
7341
  const LEGACY_PROMPT_PLACEHOLDER = "%prompt%";
7260
7342
  const LEGACY_SEED_PLACEHOLDER = "%seed%";
@@ -7262,15 +7344,20 @@ const LEGACY_IMAGE_PLACEHOLDER = "%image%";
7262
7344
  /** LoadImage-style input key that receives the uploaded source image name. */
7263
7345
  const IMAGE_INPUT_KEY = "image";
7264
7346
  /**
7265
- * Parse, clone, and inject one prompt plus an optional randomized seed.
7347
+ * Parse, clone, and inject one prompt plus an optional randomized seed, frame
7348
+ * size and source images.
7266
7349
  *
7267
- * Prompt and seed placeholders are replaced inside any string so existing
7268
- * workflows that embed them in longer text keep working. The image
7269
- * placeholder is stricter: it only matches a dedicated `inputs.image` field
7270
- * whose value is exactly the placeholder, and at most one may exist, because
7271
- * the field must carry a single uploaded file name.
7350
+ * Prompt, seed, width and height placeholders are replaced inside any string
7351
+ * so existing workflows that embed them in longer text keep working; a value
7352
+ * that is exactly the seed, width or height placeholder becomes a number. The
7353
+ * image placeholder is stricter: it only matches a dedicated `inputs.image`
7354
+ * field whose value is exactly the placeholder. Several may exist (#117); they
7355
+ * take the uploaded file names in node order, one each, and their number must
7356
+ * match the number of source images.
7272
7357
  */
7273
- function prepareComfyUIWorkflow(workflowJson, prompt, seed = randomSeed(), image) {
7358
+ function prepareComfyUIWorkflow(workflowJson, prompt, seed = randomSeed(), images, size) {
7359
+ const sources = images === void 0 ? [] : typeof images === "string" ? [images] : [...images];
7360
+ const frame = size ?? COMFYUI_DEFAULT_SIZE;
7274
7361
  const workflow = parseWorkflow(workflowJson);
7275
7362
  let promptReplacements = 0;
7276
7363
  const inject = (value) => {
@@ -7281,8 +7368,10 @@ function prepareComfyUIWorkflow(workflowJson, prompt, seed = randomSeed(), image
7281
7368
  replaced = replaced.replaceAll(COMFYUI_PROMPT_PLACEHOLDER, prompt).replaceAll(LEGACY_PROMPT_PLACEHOLDER, prompt);
7282
7369
  }
7283
7370
  if (replaced === "{{seed}}" || replaced === LEGACY_SEED_PLACEHOLDER) return seed;
7371
+ if (replaced === "{{width}}") return frame.width;
7372
+ if (replaced === "{{height}}") return frame.height;
7284
7373
  if (replaced.includes("{{seed}}") || replaced.includes(LEGACY_SEED_PLACEHOLDER)) replaced = replaced.replaceAll(COMFYUI_SEED_PLACEHOLDER, String(seed)).replaceAll(LEGACY_SEED_PLACEHOLDER, String(seed));
7285
- return replaced;
7374
+ return replaced.replaceAll(COMFYUI_WIDTH_PLACEHOLDER, String(frame.width)).replaceAll(COMFYUI_HEIGHT_PLACEHOLDER, String(frame.height));
7286
7375
  }
7287
7376
  if (Array.isArray(value)) return value.map(inject);
7288
7377
  if (!isRecord(value)) return value;
@@ -7295,8 +7384,9 @@ function prepareComfyUIWorkflow(workflowJson, prompt, seed = randomSeed(), image
7295
7384
  if (inputs === void 0) return [nodeId, value];
7296
7385
  const nextInputs = inject(inputs);
7297
7386
  if (isImagePlaceholder(nextInputs[IMAGE_INPUT_KEY])) {
7387
+ const source = sources[imageInputs];
7298
7388
  imageInputs += 1;
7299
- if (image !== void 0) nextInputs[IMAGE_INPUT_KEY] = image;
7389
+ if (source !== void 0) nextInputs[IMAGE_INPUT_KEY] = source;
7300
7390
  }
7301
7391
  return [nodeId, {
7302
7392
  ...node,
@@ -7304,14 +7394,18 @@ function prepareComfyUIWorkflow(workflowJson, prompt, seed = randomSeed(), image
7304
7394
  }];
7305
7395
  }));
7306
7396
  if (promptReplacements === 0) throw new Error(`ComfyUI workflow must contain ${COMFYUI_PROMPT_PLACEHOLDER} in a text input`);
7307
- if (image === void 0) {
7308
- if (imageInputs > 0) throw new Error(`ComfyUI workflow contains an ${COMFYUI_IMAGE_PLACEHOLDER} image input, which requires edit_image with a source image`);
7309
- return prepared;
7310
- }
7311
- if (imageInputs === 0) throw new Error(`ComfyUI workflow must contain exactly one ${COMFYUI_IMAGE_PLACEHOLDER} image input to edit images`);
7312
- if (imageInputs > 1) throw new Error(`ComfyUI workflow must contain exactly one ${COMFYUI_IMAGE_PLACEHOLDER} image input; found ${String(imageInputs)}`);
7397
+ assertImageCount(imageInputs, sources.length);
7313
7398
  return prepared;
7314
7399
  }
7400
+ /** The source images a call brings must fill the workflow's {{image}} inputs one to one. */
7401
+ function assertImageCount(imageInputs, sourceImages) {
7402
+ if (sourceImages === 0) {
7403
+ if (imageInputs > 0) throw new Error(`ComfyUI workflow contains ${String(imageInputs)} ${COMFYUI_IMAGE_PLACEHOLDER} image input(s), which require edit_image with that many source images`);
7404
+ return;
7405
+ }
7406
+ if (imageInputs === 0) throw new Error(`ComfyUI workflow must contain a ${COMFYUI_IMAGE_PLACEHOLDER} image input to edit images`);
7407
+ if (imageInputs !== sourceImages) throw new Error(`ComfyUI workflow has ${String(imageInputs)} ${COMFYUI_IMAGE_PLACEHOLDER} image input(s) but this call has ${String(sourceImages)} source image(s); pass exactly ${String(imageInputs)} (they fill the inputs in node order)`);
7408
+ }
7315
7409
  /** Random seed in the 32-bit range ComfyUI samplers accept. */
7316
7410
  function randomSeed() {
7317
7411
  return Math.floor(Math.random() * 4294967296);
@@ -7332,6 +7426,20 @@ function parseWorkflow(workflowJson) {
7332
7426
  })) throw new Error(`ComfyUI workflow must contain ${COMFYUI_PROMPT_PLACEHOLDER} (or ${LEGACY_PROMPT_PLACEHOLDER}) in a text input`);
7333
7427
  return value;
7334
7428
  }
7429
+ /** Count `inputs.image` fields that are exactly an image placeholder. */
7430
+ function countImagePlaceholders(workflow) {
7431
+ let count = 0;
7432
+ for (const node of Object.values(workflow)) {
7433
+ const inputs = record$7(record$7(node)?.inputs);
7434
+ if (inputs !== void 0 && isImagePlaceholder(inputs[IMAGE_INPUT_KEY])) count += 1;
7435
+ }
7436
+ return count;
7437
+ }
7438
+ function containsSizePlaceholder(value) {
7439
+ if (typeof value === "string") return value.includes("{{width}}") || value.includes("{{height}}");
7440
+ if (Array.isArray(value)) return value.some(containsSizePlaceholder);
7441
+ return isRecord(value) && Object.values(value).some(containsSizePlaceholder);
7442
+ }
7335
7443
  function isImagePlaceholder(value) {
7336
7444
  return value === "{{image}}" || value === LEGACY_IMAGE_PLACEHOLDER;
7337
7445
  }
@@ -7381,7 +7489,7 @@ const MAX_HISTORY_BYTES = 16777216;
7381
7489
  /** Run one ComfyUI text-to-image workflow and return its first final image. */
7382
7490
  async function generateComfyUIImage(input) {
7383
7491
  const seed = randomSeed();
7384
- const workflow = prepareComfyUIWorkflow(input.workflowJson, input.prompt, seed);
7492
+ const workflow = prepareComfyUIWorkflow(input.workflowJson, input.prompt, seed, void 0, input.size);
7385
7493
  return {
7386
7494
  ...await runJob(input, async (baseURL, signal) => {
7387
7495
  return await downloadOutput(baseURL, await waitForOutput(baseURL, await submitWorkflow(baseURL, workflow, signal), signal), input.maxBytes, signal);
@@ -7389,13 +7497,17 @@ async function generateComfyUIImage(input) {
7389
7497
  seed
7390
7498
  };
7391
7499
  }
7392
- /** Upload one source image, run the workflow, and return its first final image. */
7500
+ /**
7501
+ * Upload the source images, run the workflow, and return its first final
7502
+ * image. The images fill the workflow's {{image}} inputs in node order.
7503
+ */
7393
7504
  async function editComfyUIImage(input) {
7394
7505
  const seed = randomSeed();
7395
7506
  return {
7396
7507
  ...await runJob(input, async (baseURL, signal) => {
7397
- const imageName = await uploadSourceImage(baseURL, input.sourceImage, signal);
7398
- return await downloadOutput(baseURL, await waitForOutput(baseURL, await submitWorkflow(baseURL, prepareComfyUIWorkflow(input.workflowJson, input.prompt, seed, imageName), signal), signal), input.maxBytes, signal);
7508
+ const imageNames = [];
7509
+ for (const image of input.sourceImages) imageNames.push(await uploadSourceImage(baseURL, image, signal));
7510
+ return await downloadOutput(baseURL, await waitForOutput(baseURL, await submitWorkflow(baseURL, prepareComfyUIWorkflow(input.workflowJson, input.prompt, seed, imageNames, input.size), signal), signal), input.maxBytes, signal);
7399
7511
  }),
7400
7512
  seed
7401
7513
  };
@@ -11293,16 +11405,7 @@ const LOAD_ENDPOINTS = [
11293
11405
  ];
11294
11406
  /** The image model served through this channel: Nano Banana 2. */
11295
11407
  const ANTIGRAVITY_IMAGE_MODEL = DEFAULT_SUBSCRIPTION_MODELS["google-sub"];
11296
- /**
11297
- * Antigravity client version carried in the browser User-Agent. Google's
11298
- * backend routes by this: a non-Antigravity or outdated UA lands in the
11299
- * enterprise-license check and fails with the misleading 403 #3501
11300
- * "You do not have a valid license of this product". The official
11301
- * auto-updater publishes the current version; changelog scrape and the
11302
- * hardcoded fallback keep the header sane when the updater is unreachable
11303
- * (same strategy as the reference implementation's version.js).
11304
- */
11305
- const ANTIGRAVITY_VERSION_FALLBACK = "2.0.6";
11408
+ const ANTIGRAVITY_VERSION_FALLBACK = "3.0.0";
11306
11409
  const VERSION_URL = "https://antigravity-auto-updater-974169037036.us-central1.run.app";
11307
11410
  const CHANGELOG_URL = "https://antigravity.google/changelog";
11308
11411
  const VERSION_FETCH_TIMEOUT_MS = 5e3;
@@ -11311,13 +11414,24 @@ const VERSION_REGEX = /\d+\.\d+\.\d+/;
11311
11414
  let antigravityVersion;
11312
11415
  /** Current client version: the fetched one, or the fallback until fetched. */
11313
11416
  function getAntigravityVersion() {
11314
- return antigravityVersion ?? ANTIGRAVITY_VERSION_FALLBACK;
11417
+ return antigravityVersion !== void 0 && compareVersions(antigravityVersion, "3.0.0") > 0 ? antigravityVersion : ANTIGRAVITY_VERSION_FALLBACK;
11418
+ }
11419
+ /** Numeric x.y.z comparison: negative, zero or positive like a sort comparator. */
11420
+ function compareVersions(left, right) {
11421
+ const a = left.split(".").map(Number);
11422
+ const b = right.split(".").map(Number);
11423
+ for (let index = 0; index < 3; index += 1) {
11424
+ const diff = (a[index] ?? 0) - (b[index] ?? 0);
11425
+ if (diff !== 0) return diff;
11426
+ }
11427
+ return 0;
11315
11428
  }
11316
11429
  /** Fetch and cache the current version once; failures keep the fallback. */
11317
11430
  async function initAntigravityVersion(signal) {
11318
11431
  signal?.throwIfAborted();
11319
11432
  if (antigravityVersion !== void 0) return;
11320
- const parsed = await fetchVersionText(VERSION_URL, void 0, signal) ?? await fetchVersionText(CHANGELOG_URL, CHANGELOG_SCAN_CHARS, signal);
11433
+ const usable = (version) => version !== void 0 && compareVersions(version, "3.0.0") >= 0 ? version : void 0;
11434
+ const parsed = usable(await fetchVersionText(VERSION_URL, void 0, signal)) ?? usable(await fetchVersionText(CHANGELOG_URL, CHANGELOG_SCAN_CHARS, signal));
11321
11435
  signal?.throwIfAborted();
11322
11436
  if (parsed !== void 0) antigravityVersion = parsed;
11323
11437
  }
@@ -11678,7 +11792,8 @@ async function antigravityGenerateImage(options) {
11678
11792
  const parsed = parseSseImage(text);
11679
11793
  if (parsed !== void 0) return parsed;
11680
11794
  const reason = parseSseError(text);
11681
- failures.push(`${new URL(base).hostname}: ${diagnosticText(reason ?? "response contained no image", options.blob)}`);
11795
+ const said = reason === void 0 ? parseSseText(text) : void 0;
11796
+ failures.push(`${new URL(base).hostname}: ${diagnosticText(reason ?? (said === void 0 ? "response contained no image" : `returned text instead of an image: ${said.slice(0, 200)}`), options.blob)}`);
11682
11797
  continue;
11683
11798
  } catch (error) {
11684
11799
  if (options.signal?.aborted === true) throw error;
@@ -11707,6 +11822,21 @@ function parseSseImage(text) {
11707
11822
  } catch {}
11708
11823
  }
11709
11824
  }
11825
+ /** The text parts of an SSE body, for a response that carried no image. */
11826
+ function parseSseText(text) {
11827
+ const pieces = [];
11828
+ for (const line of text.split("\n")) {
11829
+ if (!line.startsWith("data: ")) continue;
11830
+ const raw = line.slice(6).trim();
11831
+ if (raw.length === 0 || raw === "[DONE]") continue;
11832
+ try {
11833
+ const data = JSON.parse(raw);
11834
+ for (const candidate of data.response?.candidates ?? []) for (const part of candidate.content?.parts ?? []) if (typeof part.text === "string") pieces.push(part.text);
11835
+ } catch {}
11836
+ }
11837
+ const joined = pieces.join("").replace(/\s+/g, " ").trim();
11838
+ return joined.length > 0 ? joined : void 0;
11839
+ }
11710
11840
  /** Pull the first error block an SSE body reports, for better messages. */
11711
11841
  function parseSseError(text) {
11712
11842
  for (const line of text.split("\n")) {
@@ -31253,10 +31383,21 @@ const MAX_LISTED_WORKFLOWS = 20;
31253
31383
  function comfyUIWorkflowLine(config) {
31254
31384
  const workflows = resolveComfyUIWorkflows(config);
31255
31385
  if (workflows.length === 0) return [];
31256
- const listed = workflows.slice(0, MAX_LISTED_WORKFLOWS).map((workflow) => JSON.stringify(workflow.name)).join(", ");
31386
+ const listed = workflows.slice(0, MAX_LISTED_WORKFLOWS).map((workflow) => `${JSON.stringify(workflow.name)}${slotNote(workflow.json)}`).join(", ");
31257
31387
  const more = workflows.length > MAX_LISTED_WORKFLOWS ? ` (+${String(workflows.length - MAX_LISTED_WORKFLOWS)} more)` : "";
31258
31388
  const active = activeComfyUIWorkflow(config);
31259
- return [`ComfyUI workflows available for the workflow argument (provider=comfyui): ${listed}${more}; active: ${JSON.stringify(active?.name ?? "")}. Pass workflow only when the user asks for a specific one; the local ComfyUI server may not be running.`];
31389
+ return [`ComfyUI workflows available for the workflow argument (provider=comfyui): ${listed}${more}; active: ${JSON.stringify(active?.name ?? "")}. Pass workflow only when the user asks for a specific one; the local ComfyUI server may not be running. A workflow marked [N images] runs through edit_image with exactly N source images; only one marked [size] honours size/aspect_ratio.`];
31390
+ }
31391
+ /** What a workflow takes from a call, so the model picks generate vs edit and the image count. */
31392
+ function slotNote(json) {
31393
+ let slots;
31394
+ try {
31395
+ slots = comfyUIWorkflowSlots(json);
31396
+ } catch {
31397
+ return "";
31398
+ }
31399
+ const parts = [...slots.images > 0 ? [`${String(slots.images)} image${slots.images === 1 ? "" : "s"}`] : [], ...slots.size ? ["size"] : []];
31400
+ return parts.length === 0 ? "" : ` [${parts.join(", ")}]`;
31260
31401
  }
31261
31402
  function imageToolContext(config, configuredProviders) {
31262
31403
  const provider = config.provider ?? "google";
@@ -31288,6 +31429,17 @@ const inject = [
31288
31429
  "attachments",
31289
31430
  "credentials"
31290
31431
  ];
31432
+ /**
31433
+ * The frame size a ComfyUI call gets, and a notice when the call asked for a
31434
+ * size the workflow cannot take (#97): only {{width}} / {{height}}
31435
+ * placeholders let the plugin set it, otherwise the workflow's own latent
31436
+ * size wins.
31437
+ */
31438
+ function comfyUIFrame(workflowName, slots, args) {
31439
+ const asked = args.size !== void 0 || args.aspect_ratio !== void 0;
31440
+ if (!slots.size) return asked ? { notice: `Note for the user: ComfyUI workflow "${workflowName}" sets its own frame size (it has no {{width}}/{{height}} placeholders), so the requested ${args.size ?? args.aspect_ratio ?? ""} was not applied.` } : {};
31441
+ return { size: comfyUIRequestedSize(args.size, args.aspect_ratio) ?? { ...COMFYUI_DEFAULT_SIZE } };
31442
+ }
31291
31443
  /** Optional strings from tool callers may contain empty placeholders (#86). */
31292
31444
  function cleanImageToolArgs(args) {
31293
31445
  const clean = { ...args };
@@ -31665,14 +31817,22 @@ function apply(ctx, config = {}) {
31665
31817
  assertGifProvider(args, active.provider);
31666
31818
  if (active.provider === "comfyui") {
31667
31819
  const workflow = selectComfyUIWorkflow(active, args.workflow);
31668
- return saveGenerated(ctx, await generateComfyUIImage({
31820
+ const slots = comfyUIWorkflowSlots(workflow.json);
31821
+ assertImageCount(slots.images, 0);
31822
+ const frame = comfyUIFrame(workflow.name, slots, args);
31823
+ const value = await saveGenerated(ctx, await generateComfyUIImage({
31669
31824
  baseURL: active.baseURL,
31670
31825
  workflowJson: workflow.json,
31671
31826
  prompt: mergeComfyUIPrompt(workflow.presetPrompt, args.prompt),
31672
31827
  timeoutMs: active.timeoutMs,
31673
31828
  maxBytes: ctx.attachments.imageLimits.maxImageBytes,
31674
- signal: exec.signal
31829
+ signal: exec.signal,
31830
+ ...frame.size === void 0 ? {} : { size: frame.size }
31675
31831
  }), active.provider, workflow.name, "API workflow", current(), exec, knownWorkspaceRoots);
31832
+ return frame.notice === void 0 ? value : {
31833
+ ...value,
31834
+ notice: frame.notice
31835
+ };
31676
31836
  }
31677
31837
  if (active.provider === "chatgpt-sub" || active.provider === "grok-sub" || active.provider === "google-sub") {
31678
31838
  const subscriptionParams = subscriptionToolParameters(active.provider, {
@@ -31822,7 +31982,7 @@ function apply(ctx, config = {}) {
31822
31982
  "9:16",
31823
31983
  "21:9"
31824
31984
  ],
31825
- description: "Optional output aspect ratio for Google Gemini, xAI Grok, and subscription channels."
31985
+ description: "Optional output aspect ratio for Google Gemini, xAI Grok, subscription channels, and ComfyUI workflows with {{width}}/{{height}} placeholders (about one megapixel)."
31826
31986
  },
31827
31987
  image_size: {
31828
31988
  type: "string",
@@ -31835,7 +31995,7 @@ function apply(ctx, config = {}) {
31835
31995
  },
31836
31996
  size: {
31837
31997
  type: "string",
31838
- description: "Optional dimensions or size tier for OpenAI, Seedream, or DashScope; WIDTHxHEIGHT maps to aspect_ratio for xAI Grok. Do not combine with aspect_ratio; omit instead of using an empty string."
31998
+ description: "Optional dimensions or size tier for OpenAI, Seedream, or DashScope; WIDTHxHEIGHT maps to aspect_ratio for xAI Grok and sets the frame of ComfyUI workflows with {{width}}/{{height}} placeholders. Do not combine with aspect_ratio; omit instead of using an empty string."
31839
31999
  },
31840
32000
  quality: {
31841
32001
  type: "string",
@@ -31902,7 +32062,7 @@ function apply(ctx, config = {}) {
31902
32062
  "9:16",
31903
32063
  "21:9"
31904
32064
  ],
31905
- description: "Optional output aspect ratio for Google Gemini, xAI Grok, and subscription channels."
32065
+ description: "Optional output aspect ratio for Google Gemini, xAI Grok, subscription channels, and ComfyUI workflows with {{width}}/{{height}} placeholders (about one megapixel)."
31906
32066
  },
31907
32067
  image_size: {
31908
32068
  type: "string",
@@ -31915,7 +32075,7 @@ function apply(ctx, config = {}) {
31915
32075
  },
31916
32076
  size: {
31917
32077
  type: "string",
31918
- description: "Optional dimensions or size tier for OpenAI, Seedream, or DashScope; WIDTHxHEIGHT maps to aspect_ratio for xAI Grok."
32078
+ description: "Optional dimensions or size tier for OpenAI, Seedream, or DashScope; WIDTHxHEIGHT maps to aspect_ratio for xAI Grok and sets the frame of ComfyUI workflows with {{width}}/{{height}} placeholders."
31919
32079
  },
31920
32080
  quality: {
31921
32081
  type: "string",
@@ -31998,6 +32158,7 @@ function apply(ctx, config = {}) {
31998
32158
  seed: { type: "integer" },
31999
32159
  animation: animationSchema,
32000
32160
  original: originalSchema,
32161
+ notice: { type: "string" },
32001
32162
  prompt: {
32002
32163
  type: "string",
32003
32164
  required: true
@@ -32159,7 +32320,7 @@ function apply(ctx, config = {}) {
32159
32320
  "9:16",
32160
32321
  "21:9"
32161
32322
  ],
32162
- description: "Optional output aspect ratio for Google Gemini, xAI Grok, and subscription channels."
32323
+ description: "Optional output aspect ratio for Google Gemini, xAI Grok, subscription channels, and ComfyUI workflows with {{width}}/{{height}} placeholders (about one megapixel)."
32163
32324
  },
32164
32325
  image_size: {
32165
32326
  type: "string",
@@ -32172,7 +32333,7 @@ function apply(ctx, config = {}) {
32172
32333
  },
32173
32334
  size: {
32174
32335
  type: "string",
32175
- description: "Optional output size for OpenAI, Seedream, or DashScope; WIDTHxHEIGHT maps to aspect_ratio for xAI Grok."
32336
+ description: "Optional output size for OpenAI, Seedream, or DashScope; WIDTHxHEIGHT maps to aspect_ratio for xAI Grok and sets the frame of ComfyUI workflows with {{width}}/{{height}} placeholders."
32176
32337
  },
32177
32338
  quality: {
32178
32339
  type: "string",
@@ -32180,7 +32341,7 @@ function apply(ctx, config = {}) {
32180
32341
  },
32181
32342
  workflow: {
32182
32343
  type: "string",
32183
- description: "Optional name of the ComfyUI workflow to run; omit to use the active workflow from settings. Only meaningful when the ComfyUI provider is selected."
32344
+ description: "Optional name of the ComfyUI workflow to run; omit to use the active workflow from settings. Only meaningful when the ComfyUI provider is selected. The source images fill the workflow's {{image}} inputs in node order, so pass as many images as it has inputs."
32184
32345
  },
32185
32346
  ...editGifToolParameters
32186
32347
  },
@@ -32209,22 +32370,28 @@ function apply(ctx, config = {}) {
32209
32370
  signal: exec.signal
32210
32371
  });
32211
32372
  if (active.provider === "comfyui") {
32212
- if (sourceImages.length > 1) throw new Error(`ComfyUI edit_image supports exactly one source image per call; this call resolved ${String(sourceImages.length)} images. Call edit_image again with source_attachment_id set to the single attachment ID of the image to edit.`);
32213
- const sourceImage = sourceImages[0];
32214
- if (sourceImage === void 0) throw new Error("edit_image requires a reference image");
32373
+ if (sourceImages.length === 0) throw new Error("edit_image requires a reference image");
32215
32374
  const workflow = selectComfyUIWorkflow(active, args.workflow);
32216
- return saveGenerated(ctx, await editComfyUIImage({
32375
+ const slots = comfyUIWorkflowSlots(workflow.json);
32376
+ assertImageCount(slots.images, sourceImages.length);
32377
+ const frame = comfyUIFrame(workflow.name, slots, args);
32378
+ const value = await saveGenerated(ctx, await editComfyUIImage({
32217
32379
  baseURL: active.baseURL,
32218
32380
  workflowJson: workflow.json,
32219
32381
  prompt: mergeComfyUIPrompt(workflow.presetPrompt, args.prompt),
32220
- sourceImage: {
32221
- data: sourceImage.data,
32222
- mediaType: sourceImage.mediaType
32223
- },
32382
+ sourceImages: sourceImages.map((image) => ({
32383
+ data: image.data,
32384
+ mediaType: image.mediaType
32385
+ })),
32224
32386
  timeoutMs: active.timeoutMs,
32225
32387
  maxBytes: ctx.attachments.imageLimits.maxImageBytes,
32226
- signal: exec.signal
32388
+ signal: exec.signal,
32389
+ ...frame.size === void 0 ? {} : { size: frame.size }
32227
32390
  }), active.provider, workflow.name, "API workflow", current(), exec, knownWorkspaceRoots);
32391
+ return frame.notice === void 0 ? value : {
32392
+ ...value,
32393
+ notice: frame.notice
32394
+ };
32228
32395
  }
32229
32396
  if (active.provider === "chatgpt-sub" || active.provider === "grok-sub" || active.provider === "google-sub") {
32230
32397
  if (sourceImages.length === 0) throw new Error("edit_image requires a reference image");
@@ -32487,7 +32654,8 @@ function imageOutput(verb) {
32487
32654
  saveError: { type: "string" },
32488
32655
  seed: { type: "integer" },
32489
32656
  animation: animationSchema,
32490
- original: originalSchema
32657
+ original: originalSchema,
32658
+ notice: { type: "string" }
32491
32659
  }
32492
32660
  },
32493
32661
  render: (args, value) => {
@@ -32495,7 +32663,7 @@ function imageOutput(verb) {
32495
32663
  const action = verb === "Generated" ? "It is already attached to the conversation." : "The edited image is attached to the conversation.";
32496
32664
  return [{
32497
32665
  type: "text",
32498
- text: `${verb} one image with ${value.provider}/${value.model} (${value.output}). Attachment ID: ${String(value.attachment.attachmentId)}. ${action}${saved} Respond to the user without reading or searching for the image.${animationNotes(args, value)}${cardNote(value)}${originalNote(value)}`
32666
+ text: `${verb} one image with ${value.provider}/${value.model} (${value.output}). Attachment ID: ${String(value.attachment.attachmentId)}. ${action}${saved} Respond to the user without reading or searching for the image.${animationNotes(args, value)}${cardNote(value)}${originalNote(value)}${value.notice === void 0 ? "" : ` ${value.notice}`}`
32499
32667
  }, {
32500
32668
  type: "image",
32501
32669
  attachment: value.attachment
@@ -48,6 +48,7 @@ export declare const DICT: {
48
48
  readonly apiKeyLabel: "{provider} API Key";
49
49
  readonly apiKeyPlaceholder: "留空即可保留已配置的 Key";
50
50
  readonly apiKeyHint: "安全保存为 {key};页面不会读回明文。";
51
+ readonly getApiKey: "去官方获取 Key ↗";
51
52
  readonly keyReadOnly: "Key 由 {source} 提供且只读;请在该来源中修改。";
52
53
  readonly credentialsUnavailable: "凭据服务不可用,无法保存 API Key;建议升级 DSH 到稳定版本。";
53
54
  readonly badgeUnavailable: "凭据服务不可用";
@@ -132,7 +133,7 @@ export declare const DICT: {
132
133
  readonly workflowImport: "导入 JSON 文件";
133
134
  readonly workflowMissing: "尚未导入工作流";
134
135
  readonly workflowImported: "已导入 {name}";
135
- readonly workflowHint: "从 ComfyUI 导出 API Format JSON,在提示词输入写入 {{prompt}},种子可用 {{seed}};图生图工作流在 LoadImage 的 image 输入写入 {{image}}(仅一次)。可导入多个工作流,Agent 也能在调用时按名称指定。";
136
+ readonly workflowHint: "从 ComfyUI 导出 API Format JSON,在提示词输入写入 {{prompt}},种子可用 {{seed}};宽高可用 {{width}}、{{height}}(Agent 传入尺寸或比例时生效,默认 1024×1024);图生图工作流在 LoadImage 的 image 输入写入 {{image}},多张参考图就写多个,按节点编号顺序对应。可导入多个工作流,Agent 也能在调用时按名称指定。";
136
137
  readonly workflowTooLarge: "工作流文件不能超过 5 MB。";
137
138
  readonly workflowActiveTitle: "设为当前使用的工作流";
138
139
  readonly workflowRemove: "删除";
@@ -233,6 +234,7 @@ export declare const DICT: {
233
234
  readonly apiKeyLabel: "{provider} API Key";
234
235
  readonly apiKeyPlaceholder: "Leave empty to keep configured key";
235
236
  readonly apiKeyHint: "Securely saved as {key}; never read back in plaintext.";
237
+ readonly getApiKey: "Get a key ↗";
236
238
  readonly keyReadOnly: "Key is supplied read-only by {source}; update it there.";
237
239
  readonly credentialsUnavailable: "Credentials service unavailable: API keys cannot be saved; please upgrade DSH to a stable release.";
238
240
  readonly badgeUnavailable: "Credentials unavailable";
@@ -317,7 +319,7 @@ export declare const DICT: {
317
319
  readonly workflowImport: "Import JSON file";
318
320
  readonly workflowMissing: "No workflow imported";
319
321
  readonly workflowImported: "Imported {name}";
320
- readonly workflowHint: "Export an API Format JSON from ComfyUI and place {{prompt}} in its prompt input; {{seed}} is available for a random seed. For image editing put {{image}} (exactly once) in the LoadImage image input. Import as many workflows as you need; the Agent can also pick one by name.";
322
+ readonly workflowHint: "Export an API Format JSON from ComfyUI and place {{prompt}} in its prompt input; {{seed}} is available for a random seed. {{width}} and {{height}} take the size or aspect ratio the Agent asks for (1024×1024 by default). For image editing put {{image}} in a LoadImage image input; use one per reference image, filled in node order. Import as many workflows as you need; the Agent can also pick one by name.";
321
323
  readonly workflowTooLarge: "Workflow files must be no larger than 5 MB.";
322
324
  readonly workflowActiveTitle: "Make this the active workflow";
323
325
  readonly workflowRemove: "Remove";
@@ -1 +1 @@
1
- export declare const PLUGIN_STYLE = "\n.dsh-ig-card{list-style:none;border:0;background:none;padding:0}\n.dsh-ig-head{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:0 0 6px}\n.dsh-ig-head-text{flex:1;min-width:0;display:flex;flex-direction:column;gap:4px}\n.dsh-ig-title{display:block;font-size:15px;font-weight:600;line-height:1.4;color:var(--dsw-alias-label-primary,inherit)}\n.dsh-ig-desc{display:block;font-size:13px;line-height:1.5;color:var(--dsw-alias-label-tertiary,#7b818b)}\n.dsh-ig-body{padding:0}\n.dsh-ig-field{display:flex;flex-direction:column;gap:6px;padding:12px 0}\n.dsh-ig-field + .dsh-ig-field{border-top:0.5px solid var(--dsw-alias-border-l2,#e8ebf0)}\n.dsh-ig-label{font-size:13px;font-weight:500;color:var(--dsw-alias-label-primary,inherit)}\n.dsh-ig-input{box-sizing:border-box;width:100%;height:34px;padding:0 12px;font-size:13px;border:0.5px solid var(--dsw-alias-border-l4,#d7dbe0);border-radius:var(--dsw-radius-md,6px);background:var(--dsw-alias-bg-layer-3,transparent);color:inherit;outline:none;transition:border-color .15s}\n.dsh-ig-input:focus{border-color:var(--dsw-alias-brand-primary,#4c78ff)}\n.dsh-ig-textarea{resize:vertical;min-height:56px;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;line-height:1.5}\n.dsh-ig-input-group{display:flex;gap:8px;align-items:center}\n.dsh-ig-file-row{display:flex;align-items:center;gap:10px;min-width:0}\n.dsh-ig-file-input{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;clip-path:inset(50%)}\n.dsh-ig-file-button{appearance:none;flex:none;border:1px solid var(--dsw-alias-border-l2,#d7dbe0);border-radius:8px;padding:7px 12px;background:var(--dsw-alias-bg-layer-3,#f9fafb);color:var(--dsw-alias-label-secondary,inherit);font-size:13px;cursor:pointer;transition:background .15s,border-color .15s}\n.dsh-ig-file-button:hover{background:var(--dsw-alias-bg-layer-2,#edf0f3);border-color:var(--dsw-alias-label-dimmed,#9ca3af)}\n.dsh-ig-file-button:focus-within{outline:2px solid var(--dsw-alias-brand-primary,#4c78ff);outline-offset:2px}\n.dsh-ig-file-name{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--dsw-alias-label-secondary,inherit);font-size:12px}\n.dsh-ig-workflow-list{list-style:none;margin:0;padding:0;display:grid;gap:8px}\n.dsh-ig-workflow-row{display:flex;flex-direction:column;gap:6px;padding:8px;border:1px solid var(--dsw-alias-border-l2,#eee);border-radius:8px}\n.dsh-ig-workflow-main{display:flex;align-items:center;gap:8px}\n.dsh-ig-workflow-active{display:inline-flex;align-items:center;cursor:pointer;flex:none}\n.dsh-ig-workflow-active input[type=radio]{width:15px;height:15px;accent-color:var(--dsw-alias-brand-primary,#4c78ff);margin:0;cursor:pointer}\n.dsh-ig-workflow-name{flex:1;min-width:0}\n.dsh-ig-btn-reset{appearance:none;border:0.5px solid var(--dsw-alias-border-l4,#d7dbe0);border-radius:var(--dsw-radius-md,6px);padding:5px 12px;background:var(--dsw-alias-bg-layer-3,#f9fafb);color:var(--dsw-alias-label-secondary,inherit);font:inherit;font-size:13px;cursor:pointer;white-space:nowrap;transition:background .15s,border-color .15s}\n.dsh-ig-btn-reset:hover{background:var(--dsw-alias-bg-layer-2,#edf0f3);border-color:var(--dsw-alias-label-dimmed,#9ca3af)}\n.dsh-ig-hint,.dsh-ig-status{margin:0;color:var(--dsw-alias-label-tertiary,#7b818b);font-size:12px;line-height:1.4}\n.dsh-ig-hint-error{color:var(--dsw-alias-label-error,#d33)}\n.dsh-ig-status-error{color:var(--dsw-alias-label-error,#d33);font-weight:500}\n.dsh-ig-actions{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-top:16px;padding-top:12px;border-top:1px solid var(--dsw-alias-border-l2,#eee)}\n.dsh-ig-check-row{display:flex;align-items:center;gap:8px;cursor:pointer}\n.dsh-ig-check-row input[type=checkbox]{width:15px;height:15px;accent-color:var(--dsw-alias-brand-primary,#4c78ff);margin:0}\n.dsh-ig-model-picker{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:8px 12px;max-height:180px;overflow-y:auto;padding:2px 0}\n.dsh-ig-model-picker .dsh-ig-check-row{min-width:0}\n.dsh-ig-model-option{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:12px}\n.dsh-ig-model-default{flex:none;color:var(--dsw-alias-label-tertiary,#7b818b);font-size:11px}\n.dsh-ig-savedto{font-size:12px;line-height:1.5;color:var(--dsw-alias-label-tertiary,#7b818b);word-break:break-all}\n.dsh-ig-save{appearance:none;border:0;border-radius:var(--dsw-radius-md,6px);padding:5px 14px;background:var(--dsw-alias-label-primary,#111827);color:var(--dsw-alias-bg-layer-3,#fff);font:inherit;font-size:13px;font-weight:400;cursor:pointer;transition:opacity .15s}\n.dsh-ig-save:disabled{opacity:.4;cursor:default}\n\n/* Provider list: one expandable row per provider, each saving independently. */\n.dsh-ig-providers{display:flex;flex-direction:column;margin-top:12px;overflow:hidden;border:1px solid var(--dsw-alias-border-l2,#e8ebf0);border-radius:10px;background:var(--dsw-alias-bg-layer-3,transparent)}\n.dsh-ig-provider-row{border:0;background:none;padding:0 14px}\n.dsh-ig-provider-row + .dsh-ig-provider-row{border-top:1px solid var(--dsw-alias-border-l2,#e8ebf0)}\n.dsh-ig-provider-head-row{display:flex;align-items:center;gap:8px;min-height:48px}\n.dsh-ig-provider-head{flex:1;min-width:0;width:100%;min-height:48px;appearance:none;border:0;background:none;font:inherit;color:inherit;text-align:left;cursor:pointer;display:flex;align-items:center;gap:10px;padding:0}\n.dsh-ig-channel-add.dsh-ig-btn-secondary{display:inline-flex;align-items:center;justify-content:center;flex:none;box-sizing:border-box;width:26px;height:26px;padding:0;border:0;border-radius:7px;background:var(--dsw-alias-bg-layer-2,#f3f4f6);color:var(--dsw-alias-label-tertiary,#7b818b);line-height:1;transition:background .15s,color .15s}\n.dsh-ig-channel-add.dsh-ig-btn-secondary:hover:not(:disabled){border:0;background:var(--dsw-alias-bg-layer-1,#e5e7eb);color:var(--dsw-alias-label-primary,inherit)}\n.dsh-ig-channel-add svg{width:14px;height:14px}\n.dsh-ig-provider-head:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary,#4c78ff);outline-offset:2px;border-radius:var(--dsw-radius-sm,4px)}\n.dsh-ig-provider-head:hover .dsh-ig-provider-name{color:var(--dsw-alias-label-primary,inherit)}\n.dsh-ig-provider-head:hover .dsh-ig-provider-chevron{color:var(--dsw-alias-label-primary,inherit)}\n.dsh-ig-provider-name{flex:1;min-width:0;font-size:13px;font-weight:550;line-height:20px;color:var(--dsw-alias-label-primary,inherit);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.dsh-ig-provider-chevron{display:inline-flex;align-items:center;justify-content:center;flex:none;width:20px;height:20px;color:var(--dsw-alias-label-tertiary,#7b818b);transition:transform .16s,color .15s}\n.dsh-ig-provider-chevron-open{transform:rotate(180deg)}\n.dsh-ig-provider-body{border-top:0;padding:0 0 14px}\n.dsh-ig-badge{flex:none;display:inline-flex;align-items:center;gap:5px;box-sizing:border-box;min-height:22px;padding:1px 8px;border:1px solid transparent;border-radius:6px;font-size:11px;font-weight:500;line-height:18px;color:var(--dsw-alias-label-secondary,inherit);white-space:nowrap;max-width:60%;overflow:hidden;text-overflow:ellipsis}\n.dsh-ig-badge-dot{width:5px;height:5px;border-radius:50%;background:currentColor;flex:none}\n.dsh-ig-badge-ok{border-color:rgba(34,197,94,.2);color:#15803d;background:rgba(34,197,94,.08)}\n.dsh-ig-badge-missing{border-color:rgba(239,68,68,.18);color:#b91c1c;background:rgba(239,68,68,.06)}\n.dsh-ig-badge-neutral{border-color:var(--dsw-alias-border-l2,#e8ebf0);color:var(--dsw-alias-label-tertiary,#7b818b);background:var(--dsw-alias-bg-layer-2,transparent)}\n.dsh-ig-badge-checking .dsh-ig-badge-dot{animation:dsh-ig-pulse 1s ease-in-out infinite}\n@keyframes dsh-ig-pulse{50%{opacity:.25}}\n\n/* Default provider radio pills. */\n.dsh-ig-radios{display:flex;flex-wrap:wrap;gap:8px}\n.dsh-ig-radio{display:inline-flex;align-items:center;gap:6px;border:1px solid var(--dsw-alias-border-l2,#d7dbe0);border-radius:999px;padding:5px 12px;cursor:pointer;font-size:12.5px;color:var(--dsw-alias-label-secondary,inherit);transition:border-color .15s,background .15s}\n.dsh-ig-radio:hover{border-color:var(--dsw-alias-label-dimmed,#9ca3af)}\n.dsh-ig-radio-checked{border-color:var(--dsw-alias-brand-primary,#4c78ff);background:rgba(76,120,255,.08);color:var(--dsw-alias-label-primary,inherit)}\n.dsh-ig-radio input[type=radio]{width:14px;height:14px;accent-color:var(--dsw-alias-brand-primary,#4c78ff);margin:0;cursor:pointer}\n\n/* Row-level actions: test connection, clear key, save. */\n.dsh-ig-row-actions{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-top:14px;flex-wrap:wrap}\n.dsh-ig-row-buttons{display:flex;align-items:center;gap:8px;flex-wrap:wrap}\n.dsh-ig-btn-secondary{appearance:none;border:0.5px solid var(--dsw-alias-border-l4,#d7dbe0);border-radius:var(--dsw-radius-md,6px);padding:5px 14px;background:var(--dsw-alias-bg-layer-3,#f9fafb);color:var(--dsw-alias-label-secondary,inherit);font:inherit;font-size:13px;cursor:pointer;white-space:nowrap;transition:background .15s,border-color .15s,opacity .15s}\n.dsh-ig-btn-secondary:hover:not(:disabled){background:var(--dsw-alias-bg-layer-2,#edf0f3);border-color:var(--dsw-alias-label-dimmed,#9ca3af)}\n.dsh-ig-btn-secondary:disabled{opacity:.45;cursor:default}\n.dsh-ig-btn-danger{color:#b91c1c;border-color:rgba(239,68,68,.4)}\n.dsh-ig-btn-danger:hover:not(:disabled){background:rgba(239,68,68,.08);border-color:rgba(239,68,68,.6)}\n\n/* Workspace section within the settings card. */\n.dsh-ig-section{display:flex;flex-direction:column;gap:6px;margin-top:20px;padding-top:14px;border-top:0.5px solid var(--dsw-alias-border-l2,#e8ebf0)}\n.dsh-ig-section-title{font-size:13px;font-weight:500;line-height:1.5;color:var(--dsw-alias-label-primary,inherit)}\n.dsh-ig-status-readonly{color:var(--dsw-alias-label-tertiary,#7b818b);font-style:italic}\n\n.dsh-ig-result{display:grid;gap:10px;max-width:520px}\n.dsh-ig-promoted-results{display:grid;gap:16px}\n.dsh-ig-result-title{font-size:14px;font-weight:600}\n.dsh-ig-container{position:relative;display:inline-block;width:fit-content;max-width:100%;justify-self:start;border-radius:12px;overflow:hidden;line-height:0;isolation:isolate}\n.dsh-ig-container:hover .dsh-ig-toolbar,.dsh-ig-container:focus-within .dsh-ig-toolbar{opacity:1;pointer-events:auto}\n.dsh-ig-toolbar{position:absolute;top:8px;left:8px;display:flex;align-items:center;gap:5px;padding:3px 5px;border-radius:8px;background:rgba(0,0,0,0.65);backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);opacity:0;pointer-events:none;transition:opacity .18s ease;z-index:2;line-height:1}\n.dsh-ig-tool-btn{appearance:none;border:0;background:transparent;color:#fff;padding:5px;border-radius:6px;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;transition:background .15s,color .15s}\n.dsh-ig-tool-btn:hover{background:rgba(255,255,255,0.25)}\n.dsh-ig-tool-btn-danger:hover{background:rgba(239,68,68,0.75)!important;color:#fff!important}\n.dsh-ig-toast{position:absolute;top:100%;left:0;margin-top:5px;padding:3px 8px;border-radius:6px;background:rgba(0,0,0,0.85);color:#fff;font-size:11px;white-space:nowrap;pointer-events:none;z-index:4}\n.dsh-ig-image{display:block;max-width:100%;max-height:520px;border-radius:12px;background:#f2f3f5;cursor:pointer}\n.dsh-ig-version-nav{position:absolute;right:8px;bottom:8px;display:flex;align-items:center;gap:2px;padding:3px;border-radius:999px;background:rgba(15,23,42,.72);color:#fff;backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);font-size:11px;line-height:1;z-index:2}\n.dsh-ig-version-nav button{appearance:none;border:0;background:transparent;color:inherit;width:25px;height:25px;border-radius:50%;display:inline-flex;align-items:center;justify-content:center;cursor:pointer;font-size:17px;line-height:1}\n.dsh-ig-version-nav button:hover:not(:disabled){background:rgba(255,255,255,.2)}\n.dsh-ig-version-nav button:disabled{opacity:.3;cursor:default}\n.dsh-ig-version-count{min-width:34px;text-align:center;font-variant-numeric:tabular-nums}\n.dsh-ig-regenerate-overlay{position:absolute;inset:0;background:rgba(15,23,42,0.52);backdrop-filter:blur(6px);-webkit-backdrop-filter:blur(6px);display:flex;flex-direction:column;align-items:center;justify-content:center;gap:10px;color:#fff;z-index:3;animation:dsh-ig-fade .18s ease-out;line-height:1.4;border-radius:12px}\n.dsh-ig-regenerate-spinner{width:28px;height:28px;border:3px solid rgba(255,255,255,0.25);border-top-color:#fff;border-radius:50%;animation:dsh-ig-spin .8s linear infinite}\n.dsh-ig-regenerate-overlay-text{font-size:12.5px;font-weight:550;color:#fff;letter-spacing:0.2px;text-shadow:0 1px 2px rgba(0,0,0,0.4)}\n.dsh-ig-regenerate-overlay-cancel{appearance:none;border:1px solid rgba(255,255,255,0.4);border-radius:6px;background:rgba(255,255,255,0.15);color:#fff;padding:3px 12px;font-size:11.5px;cursor:pointer;transition:background .15s}\n.dsh-ig-regenerate-overlay-cancel:hover{background:rgba(255,255,255,0.3)}\n@keyframes dsh-ig-spin{to{transform:rotate(360deg)}}\n.dsh-ig-regenerate-backdrop{position:fixed;inset:0;z-index:100000;display:flex;align-items:center;justify-content:center;padding:18px;background:rgba(11,17,29,.6);backdrop-filter:blur(5px);-webkit-backdrop-filter:blur(5px);animation:dsh-ig-fade .15s ease-out}\n.dsh-ig-regenerate-dialog{width:min(520px,100%);border:1px solid var(--dsw-alias-border-l2,#dfe3ea);border-radius:14px;background:var(--dsw-alias-bg-layer-1,#fff);color:var(--dsw-alias-label-primary,#172033);box-shadow:0 24px 72px rgba(11,17,29,.28);padding:20px;box-sizing:border-box;line-height:1.4}\n.dsh-ig-regenerate-dialog h3{margin:0;font-size:16px;font-weight:650}\n.dsh-ig-regenerate-dialog p{margin:7px 0 16px;color:var(--dsw-alias-label-tertiary,#737d8f);font-size:12.5px;line-height:1.55}\n.dsh-ig-regenerate-dialog label{display:grid;gap:7px;font-size:12.5px;font-weight:600}\n.dsh-ig-regenerate-dialog textarea{box-sizing:border-box;width:100%;min-height:132px;resize:vertical;border:1px solid var(--dsw-alias-border-l2,#d7dce5);border-radius:9px;padding:11px 12px;background:var(--dsw-alias-bg-layer-2,#fff);color:inherit;font:inherit;font-size:13px;line-height:1.55;outline:none}\n.dsh-ig-regenerate-dialog textarea:focus{border-color:var(--dsw-alias-brand-primary,#4c78ff);box-shadow:0 0 0 3px rgba(76,120,255,.12)}\n.dsh-ig-regenerate-error{margin-top:10px!important;color:var(--dsw-alias-label-error,#d33)!important}\n.dsh-ig-regenerate-actions{display:flex;justify-content:flex-end;gap:9px;margin-top:16px}\n.dsh-ig-regenerate-actions button{height:35px;padding:0 14px;border-radius:7px;font:inherit;font-size:13px;font-weight:550;cursor:pointer}\n.dsh-ig-regenerate-cancel{border:1px solid var(--dsw-alias-border-l2,#d7dce5);background:transparent;color:inherit}\n.dsh-ig-regenerate-confirm{border:1px solid var(--dsw-alias-brand-primary,#3569ed);background:var(--dsw-alias-brand-primary,#3569ed);color:var(--dsw-alias-label-primary-inverted,#fff)}\n.dsh-ig-regenerate-actions button:disabled{opacity:.5;cursor:default}\n@media(hover:none){.dsh-ig-container .dsh-ig-toolbar{opacity:1;pointer-events:auto}}\n@keyframes dsh-ig-fade{from{opacity:0}to{opacity:1}}\n.dsh-ig-error{color:var(--dsw-alias-label-error,#d33);font-size:13px}\n.dsh-ig-loading{color:var(--dsw-alias-label-tertiary,#7b818b);font-size:13px}\n\n/* Native Workspace Gallery & Studio View (Renders seamlessly inside DSH Session View) */\n.dsh-ig-gallery-page{width:100%;height:100%;background:var(--dsw-alias-bg-layer-1,#ffffff);display:flex;flex-direction:column;overflow:hidden;flex:1}\n\n/* 1. Top Navigation Tab Bar */\n.dsh-ig-studio-tabs-bar{display:flex;align-items:center;gap:6px;padding:6px 24px;border-bottom:1px solid var(--dsw-alias-border-l2,#e5e7eb);background:var(--dsw-alias-bg-layer-1,#ffffff);flex-shrink:0}\n.dsh-ig-studio-tab-btn{appearance:none;-webkit-appearance:none;border:0;background:transparent;display:inline-flex;align-items:center;gap:7px;padding:7px 14px;font-size:13px;font-weight:500;color:var(--dsw-alias-label-secondary,#64748b);cursor:pointer;border-radius:6px;transition:color .15s ease,background-color .15s ease}\n.dsh-ig-studio-tab-btn:hover{color:var(--dsw-alias-label-primary,#0f172a);background:var(--dsw-alias-bg-layer-2,#f1f5f9)}\n.dsh-ig-studio-tab-btn.is-active{color:var(--dsw-alias-brand-primary,#2563eb);font-weight:600;background:rgba(37,99,235,0.08)}\n\n/* 2. Secondary Filter & Search Toolbar */\n.dsh-ig-studio-toolbar{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:10px 24px;background:var(--dsw-alias-bg-layer-1,#ffffff);border-bottom:1px solid var(--dsw-alias-border-l2,#f1f5f9);flex-shrink:0;flex-wrap:nowrap;min-width:0}\n.dsh-ig-studio-toolbar-left{display:flex;align-items:center;gap:10px;flex-wrap:nowrap;flex:1;min-width:0}\n.dsh-ig-studio-toolbar-right{display:flex;align-items:center;gap:10px;flex-shrink:0}\n\n/* Modern Custom Select (Removes OS default arrows & ugly borders) */\n.dsh-ig-studio-select{appearance:none;-webkit-appearance:none;-moz-appearance:none;height:32px;line-height:30px;padding:0 28px 0 12px;font-size:12.5px;color:var(--dsw-alias-label-primary,#334155);background-color:var(--dsw-alias-bg-layer-2,#ffffff);background-image:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 24 24' fill='none' stroke='%2364748b' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E\");background-repeat:no-repeat;background-position:right 9px center;border:1px solid var(--dsw-alias-border-l2,#e2e8f0);border-radius:6px;outline:none;cursor:pointer;box-sizing:border-box;transition:border-color .15s ease,box-shadow .15s ease,background-color .15s ease}\n.dsh-ig-studio-select:hover{border-color:var(--dsw-alias-border-l1,#cbd5e1);background-color:var(--dsw-alias-bg-layer-1,#f8fafc)}\n.dsh-ig-studio-select:focus{border-color:var(--dsw-alias-brand-primary,#3b82f6);box-shadow:0 0 0 2px rgba(59,130,246,0.15)}\n.dsh-ig-studio-select-sort{font-weight:500}\n\n/* Unified Search Input */\n.dsh-ig-studio-search-wrap{position:relative;display:flex;align-items:center;min-width:140px;max-width:320px;flex:1}\n.dsh-ig-studio-search-icon{position:absolute;left:10px;color:var(--dsw-alias-label-tertiary,#94a3b8);pointer-events:none}\n.dsh-ig-studio-search-input{width:100%;height:32px;line-height:30px;padding:0 12px 0 32px;font-size:12.5px;border:1px solid var(--dsw-alias-border-l2,#e2e8f0);border-radius:6px;background-color:var(--dsw-alias-bg-layer-2,#ffffff);color:inherit;outline:none;box-sizing:border-box;transition:border-color .15s ease,box-shadow .15s ease}\n.dsh-ig-studio-search-input:hover{border-color:var(--dsw-alias-border-l1,#cbd5e1)}\n.dsh-ig-studio-search-input:focus{border-color:var(--dsw-alias-brand-primary,#3b82f6);box-shadow:0 0 0 2px rgba(59,130,246,0.15)}\n.dsh-ig-studio-search-input::placeholder{color:var(--dsw-alias-label-tertiary,#94a3b8)}\n\n/* 3. Grid & Responsive Cards */\n.dsh-ig-gallery-page-body{flex:1;overflow-y:auto;padding:20px 24px}\n.dsh-ig-gallery-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:18px}\n.dsh-ig-gallery-sentinel{grid-column:1/-1;height:1px;width:100%}\n.dsh-ig-gallery-card{background:var(--dsw-alias-bg-layer-2,#ffffff);border:1px solid var(--dsw-alias-border-l2,#e2e8f0);border-radius:10px;overflow:hidden;display:flex;flex-direction:column;cursor:pointer;transition:transform .18s ease,box-shadow .18s ease,border-color .18s ease;content-visibility:auto;contain-intrinsic-size:auto 280px}\n.dsh-ig-gallery-card:hover{transform:translateY(-2px);box-shadow:0 10px 20px -5px rgba(0,0,0,0.06),0 4px 6px -2px rgba(0,0,0,0.03);border-color:var(--dsw-alias-border-l1,#cbd5e1)}\n.dsh-ig-gallery-card-media{position:relative;width:100%;aspect-ratio:1/1;background:#f1f5f9;overflow:hidden;display:flex;align-items:center;justify-content:center}\n.dsh-ig-gallery-card-img{width:100%;height:100%;object-fit:cover;transition:transform .2s ease}\n.dsh-ig-gallery-card:hover .dsh-ig-gallery-card-img{transform:scale(1.03)}\n.dsh-ig-gallery-card-loading{font-size:12px;color:#94a3b8}\n.dsh-ig-gallery-card-error{font-size:12px;color:#ef4444;padding:8px;text-align:center}\n\n/* Floating Action Toolbar on Card Hover */\n.dsh-ig-gallery-card:hover .dsh-ig-card-toolbar{opacity:1;pointer-events:auto}\n.dsh-ig-card-toolbar{position:absolute;top:6px;left:6px;display:flex;align-items:center;gap:3px;padding:3px 5px;border-radius:6px;background:rgba(15,23,42,0.72);backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);opacity:0;pointer-events:none;transition:opacity .18s ease;z-index:10;line-height:1}\n\n/* Card Bottom Metadata */\n.dsh-ig-gallery-card-meta{padding:10px 12px;display:flex;flex-direction:column;gap:5px;background:var(--dsw-alias-bg-layer-2,#ffffff);flex:1}\n.dsh-ig-card-badge-row{display:flex;align-items:center}\n.dsh-ig-card-badge{display:inline-block;padding:2px 6px;border-radius:4px;background:var(--dsw-alias-bg-layer-3,#f1f5f9);color:var(--dsw-alias-label-secondary,#475569);font-size:11px;font-weight:500;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.dsh-ig-gallery-card-prompt-line{font-size:12.5px;font-weight:500;color:var(--dsw-alias-label-primary,#1e293b);line-height:1.4;min-height:1.4em;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n/* An imported image has no prompt: its line keeps one row of height and the footer sits at the bottom, so mixed rows align (#113). */\n.dsh-ig-card-footer-row{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-top:auto;padding-top:2px}\n.dsh-ig-card-meta-text{font-size:11px;color:var(--dsw-alias-label-tertiary,#94a3b8);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\n.dsh-ig-card-fav-btn{appearance:none;border:0;background:transparent;color:var(--dsw-alias-label-tertiary,#94a3b8);padding:2px;border-radius:4px;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;transition:color .15s,transform .15s;flex-shrink:0}\n.dsh-ig-card-fav-btn:hover{color:#ef4444;transform:scale(1.15)}\n.dsh-ig-card-fav-btn.is-favorited{color:#ef4444}\n\n/* Placeholders for Upcoming Routes */\n.dsh-ig-placeholder-view{display:flex;align-items:center;justify-content:center;min-height:360px;height:100%;padding:24px}\n.dsh-ig-placeholder-card{max-width:500px;width:100%;text-align:center;padding:36px 28px;background:var(--dsw-alias-bg-layer-2,#ffffff);border:1px dashed var(--dsw-alias-border-l2,#e2e8f0);border-radius:14px;display:flex;flex-direction:column;align-items:center;gap:12px}\n.dsh-ig-placeholder-icon{font-size:40px;line-height:1}\n.dsh-ig-placeholder-header{display:flex;align-items:center;gap:8px;justify-content:center}\n.dsh-ig-placeholder-title{font-size:16px;font-weight:600;color:var(--dsw-alias-label-primary,inherit);margin:0}\n.dsh-ig-placeholder-badge{font-size:11px;font-weight:500;background:rgba(37,99,235,0.1);color:#2563eb;padding:2px 8px;border-radius:12px}\n.dsh-ig-placeholder-desc{font-size:13px;line-height:1.6;color:var(--dsw-alias-label-secondary,#64748b);margin:0}\n.dsh-ig-placeholder-tip{margin-top:6px;padding:8px 12px;font-size:12px;background:var(--dsw-alias-bg-layer-3,#f8fafc);border-radius:8px;color:var(--dsw-alias-label-tertiary,#64748b);text-align:left}\n\n/* Empty State */\n.dsh-ig-gallery-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;min-height:360px;text-align:center;color:var(--dsw-alias-label-tertiary,#94a3b8)}\n.dsh-ig-gallery-empty-icon{display:block;width:44px;height:44px;margin-bottom:10px;color:var(--dsw-alias-label-tertiary,#94a3b8);opacity:.7}\n.dsh-ig-gallery-empty-title{font-size:15px;font-weight:600;color:var(--dsw-alias-label-primary,inherit);margin-bottom:4px}\n.dsh-ig-gallery-empty-desc{font-size:13px;max-width:360px;line-height:1.5}\n\n/* Pure Centered Lightbox */\n.dsh-ig-lightbox-backdrop{position:fixed;inset:0;z-index:99999;background:rgba(0,0,0,0.88);backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);display:flex;flex-direction:column;align-items:center;justify-content:center;padding:20px;box-sizing:border-box;cursor:zoom-out;animation:dsh-ig-fade .15s ease-out}\n.dsh-ig-lightbox-topbar{position:absolute;top:20px;left:24px;right:24px;display:flex;align-items:center;justify-content:space-between;z-index:10;pointer-events:none}\n.dsh-ig-lightbox-meta{display:flex;align-items:center;gap:8px;pointer-events:auto}\n.dsh-ig-tag{display:inline-block;padding:2px 6px;border-radius:4px;background:var(--dsw-alias-bg-layer-3,#edf0f3);color:var(--dsw-alias-label-secondary,inherit);font-weight:500;text-transform:uppercase;font-size:10px}\n.dsh-ig-tag-model{background:rgba(76,120,255,0.1);color:#4c78ff}\n.dsh-ig-lightbox-close-btn{appearance:none;border:0;background:rgba(255,255,255,0.15);color:#fff;border-radius:50%;width:34px;height:34px;font-size:16px;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;transition:background .15s;pointer-events:auto}\n.dsh-ig-lightbox-close-btn:hover{background:rgba(255,255,255,0.3)}\n.dsh-ig-lightbox-img-wrap{max-width:86vw;max-height:78vh;display:flex;align-items:center;justify-content:center;cursor:default}\n.dsh-ig-lightbox-img{max-width:100%;max-height:78vh;object-fit:contain;border-radius:8px;box-shadow:0 24px 60px rgba(0,0,0,0.7);user-select:none}\n.dsh-ig-lightbox-bottombar{position:absolute;bottom:24px;left:50%;transform:translateX(-50%);max-width:min(90vw,640px);background:rgba(20,22,26,0.85);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);border:1px solid rgba(255,255,255,0.15);border-radius:14px;padding:10px 16px;display:flex;flex-direction:column;gap:8px;color:#fff;box-shadow:0 16px 40px rgba(0,0,0,0.5);cursor:default}\n.dsh-ig-lightbox-prompt-text{font-size:13px;line-height:1.4;color:rgba(255,255,255,0.92);display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;word-break:break-word}\n.dsh-ig-lightbox-counter{display:inline-flex;align-items:center;padding:2px 8px;border-radius:4px;background:rgba(255,255,255,0.15);color:#fff;font-size:11.5px;font-weight:500;font-variant-numeric:tabular-nums}\n.dsh-ig-lightbox-nav-btn{position:fixed;top:50%;transform:translateY(-50%);width:44px;height:44px;border-radius:50%;border:1px solid rgba(255,255,255,0.18);background:rgba(255,255,255,0.12);color:#fff;display:flex;align-items:center;justify-content:center;cursor:pointer;z-index:100;backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);transition:background-color .15s ease,transform .15s ease,opacity .15s ease;outline:none}\n.dsh-ig-lightbox-nav-btn:hover:not(:disabled){background:rgba(255,255,255,0.28);transform:translateY(-50%) scale(1.08)}\n.dsh-ig-lightbox-nav-btn:disabled{opacity:0.2;cursor:not-allowed;pointer-events:none}\n.dsh-ig-lightbox-nav-prev{left:24px}\n.dsh-ig-lightbox-nav-next{right:24px}\n.dsh-ig-lightbox-loading{display:flex;align-items:center;justify-content:center;min-width:180px;min-height:180px}\n.dsh-ig-lightbox-spinner{width:36px;height:36px;border:3px solid rgba(255,255,255,0.2);border-top-color:#fff;border-radius:50%;animation:dsh-ig-spin .8s linear infinite}\n@keyframes dsh-ig-spin{to{transform:rotate(360deg)}}\n.dsh-ig-lightbox-btn{appearance:none;border:1px solid rgba(255,255,255,0.18);background:rgba(255,255,255,0.08);color:#fff;border-radius:6px;padding:5px 10px;font-size:12px;cursor:pointer;display:inline-flex;align-items:center;gap:5px;transition:background .15s,border-color .15s,color .15s}\n.dsh-ig-lightbox-btn:hover{background:rgba(255,255,255,0.22)}\n.dsh-ig-lightbox-btn-danger{border-color:rgba(239,68,68,0.4);color:#fca5a5}\n.dsh-ig-lightbox-btn-danger:hover{background:rgba(239,68,68,0.35)!important;color:#fff!important;border-color:rgba(239,68,68,0.7)!important}\n.dsh-ig-gallery-page-toast{position:fixed;bottom:24px;left:50%;transform:translateX(-50%);background:rgba(0,0,0,0.85);color:#fff;padding:6px 14px;border-radius:8px;font-size:13px;z-index:99999;animation:dsh-ig-fade .15s}\n\n/* Card selection and checkbox */\n.dsh-ig-gallery-card.is-selected{box-shadow:0 0 0 2px var(--dsw-alias-brand-primary,#2563eb);border-color:transparent}\n.dsh-ig-card-checkbox{position:absolute;top:8px;left:8px;width:22px;height:22px;border-radius:50%;border:1.5px solid rgba(255,255,255,0.7);background:rgba(0,0,0,0.35);backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px);color:#fff;display:inline-flex;align-items:center;justify-content:center;cursor:pointer;z-index:5;opacity:0;transition:opacity .15s ease,background-color .15s ease,border-color .15s ease;padding:0;outline:none}\n.dsh-ig-gallery-card:hover .dsh-ig-card-checkbox,.dsh-ig-gallery-card.is-manage-mode .dsh-ig-card-checkbox,.dsh-ig-card-checkbox.is-checked{opacity:1}\n.dsh-ig-card-checkbox.is-checked{background:var(--dsw-alias-brand-primary,#2563eb);border-color:var(--dsw-alias-brand-primary,#2563eb)}\n\n/* Studio button in toolbar */\n.dsh-ig-studio-btn{display:inline-flex;align-items:center;gap:6px;height:32px;padding:0 12px;box-sizing:border-box;border-radius:6px;border:1px solid var(--dsw-alias-border-subtle,rgba(0,0,0,0.12));background:var(--dsw-alias-bg-layer-2,#fff);color:var(--dsw-alias-label-primary,inherit);font-size:12.5px;font-weight:500;cursor:pointer;transition:border-color .15s,background .15s,color .15s}\n.dsh-ig-studio-btn:hover{background:var(--dsw-alias-bg-layer-3,#f3f4f6);border-color:var(--dsw-alias-border-default,rgba(0,0,0,0.2))}\n.dsh-ig-studio-btn.is-active{background:var(--dsw-alias-brand-primary,#2563eb);border-color:var(--dsw-alias-brand-primary,#2563eb);color:#fff}\n.dsh-ig-studio-btn-danger{color:#ef4444;border-color:rgba(239,68,68,0.35);background:rgba(239,68,68,0.06)}\n.dsh-ig-studio-btn-danger:hover{background:rgba(239,68,68,0.14);border-color:rgba(239,68,68,0.6);color:#dc2626}\n.dsh-ig-studio-btn-danger.is-active{background:#dc2626;border-color:#dc2626;color:#fff}\n\n/* Floating Batch Action Bar */\n.dsh-ig-batch-bar{position:fixed;bottom:24px;left:50%;transform:translateX(-50%);display:flex;align-items:center;gap:16px;background:rgba(20,24,32,0.92);backdrop-filter:blur(16px);-webkit-backdrop-filter:blur(16px);border:1px solid rgba(255,255,255,0.18);border-radius:40px;padding:8px 16px;box-shadow:0 16px 40px rgba(0,0,0,0.5);z-index:99990;animation:dsh-ig-slide-up .2s cubic-bezier(0.16,1,0.3,1);color:#fff}\n@keyframes dsh-ig-slide-up{from{transform:translate(-50%,20px);opacity:0}to{transform:translate(-50%,0);opacity:1}}\n.dsh-ig-batch-bar-left{display:flex;align-items:center;gap:10px}\n.dsh-ig-batch-bar-right{display:flex;align-items:center;gap:8px;border-left:1px solid rgba(255,255,255,0.15);padding-left:12px}\n.dsh-ig-batch-counter{font-size:13px;font-weight:600;color:rgba(255,255,255,0.95);margin-right:4px}\n.dsh-ig-batch-btn{appearance:none;border:1px solid rgba(255,255,255,0.15);background:rgba(255,255,255,0.08);color:#fff;border-radius:20px;padding:5px 12px;font-size:12px;cursor:pointer;display:inline-flex;align-items:center;gap:5px;transition:background .15s,border-color .15s,color .15s}\n.dsh-ig-batch-btn:hover:not(:disabled){background:rgba(255,255,255,0.2)}\n.dsh-ig-batch-btn:disabled{opacity:0.4;cursor:not-allowed}\n.dsh-ig-batch-btn.is-favorited{background:rgba(239,68,68,0.15);border-color:rgba(239,68,68,0.4);color:#fca5a5}\n.dsh-ig-batch-btn.is-favorited:hover:not(:disabled){background:rgba(239,68,68,0.25)!important;border-color:rgba(239,68,68,0.6)!important;color:#fff!important}\n.dsh-ig-batch-btn-download:hover:not(:disabled){background:rgba(59,130,246,0.2)!important;border-color:rgba(59,130,246,0.5)!important;color:#93c5fd!important}\n.dsh-ig-batch-divider{width:1px;height:16px;background:rgba(255,255,255,0.18);margin:0 2px;display:inline-block}\n.dsh-ig-batch-btn-danger{background:rgba(239,68,68,0.2);border-color:rgba(239,68,68,0.5);color:#fca5a5}\n.dsh-ig-batch-btn-danger:hover:not(:disabled){background:rgba(239,68,68,0.4)!important;border-color:rgba(239,68,68,0.8)!important;color:#fff!important}\n.dsh-ig-batch-btn-exit{border-color:transparent;background:transparent;color:rgba(255,255,255,0.7)}\n.dsh-ig-batch-btn-exit:hover{background:rgba(255,255,255,0.1);color:#fff}\n\n/* Batch Delete Confirmation Modal */\n.dsh-ig-modal-backdrop{position:fixed;inset:0;background:rgba(0,0,0,0.65);backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px);z-index:100005;display:flex;align-items:center;justify-content:center;padding:16px;animation:dsh-ig-fade .15s ease-out}\n.dsh-ig-modal-box{width:100%;max-width:440px;background:var(--dsw-alias-bg-layer-1,#1c1e24);border:1px solid var(--dsw-alias-border-subtle,rgba(255,255,255,0.12));border-radius:12px;padding:22px;box-sizing:border-box;box-shadow:0 20px 50px rgba(0,0,0,0.45);color:var(--dsw-alias-label-primary,#fff);animation:dsh-ig-scale-up .15s ease-out}\n@keyframes dsh-ig-scale-up{from{transform:scale(0.95);opacity:0}to{transform:scale(1);opacity:1}}\n.dsh-ig-modal-header{display:flex;align-items:flex-start;gap:12px;margin-bottom:12px}\n.dsh-ig-modal-icon-danger{width:36px;height:36px;border-radius:50%;background:rgba(239,68,68,0.12);color:#ef4444;display:flex;align-items:center;justify-content:center;flex-shrink:0}\n.dsh-ig-modal-title{font-size:16px;font-weight:600;line-height:1.4}\n.dsh-ig-modal-body{margin-bottom:20px;padding-left:48px}\n.dsh-ig-modal-desc{font-size:13.5px;color:var(--dsw-alias-label-secondary,rgba(255,255,255,0.7));margin:0 0 14px 0;line-height:1.5}\n.dsh-ig-modal-checkbox-label{display:flex;align-items:center;gap:8px;font-size:12.5px;color:var(--dsw-alias-label-primary,inherit);cursor:pointer;user-select:none}\n.dsh-ig-modal-checkbox-label input{margin:0;cursor:pointer;width:15px;height:15px}\n.dsh-ig-modal-footer{display:flex;align-items:center;justify-content:flex-end;gap:10px}\n.dsh-ig-modal-btn{height:34px;padding:0 14px;border-radius:6px;font-size:13px;font-weight:500;cursor:pointer;transition:background .15s,border-color .15s,color .15s;outline:none}\n.dsh-ig-modal-btn-cancel{background:transparent;border:1px solid var(--dsw-alias-border-subtle,rgba(255,255,255,0.2));color:var(--dsw-alias-label-primary,inherit)}\n.dsh-ig-modal-btn-cancel:hover{background:var(--dsw-alias-bg-layer-3,rgba(255,255,255,0.08))}\n.dsh-ig-modal-btn-danger{background:#dc2626;border:1px solid #dc2626;color:#fff}\n.dsh-ig-modal-btn-danger:hover{background:#b91c1c;border-color:#b91c1c}\n.dsh-ig-modal-btn-primary{background:linear-gradient(135deg,#3b82f6,#2563eb);border:1px solid #2563eb;color:#fff;display:inline-flex;align-items:center;gap:6px}\n.dsh-ig-modal-btn-primary:hover:not(:disabled){background:linear-gradient(135deg,#2563eb,#1d4ed8)}\n.dsh-ig-modal-btn-primary:disabled{opacity:0.5;cursor:not-allowed}\n.dsh-ig-regenerate-modal-box{max-width:520px}\n.dsh-ig-regenerate-modal-icon{width:36px;height:36px;border-radius:50%;background:rgba(59,130,246,0.15);color:#3b82f6;display:flex;align-items:center;justify-content:center;flex-shrink:0}\n.dsh-ig-regenerate-modal-title-wrap{display:flex;flex-direction:column;gap:4px}\n.dsh-ig-regenerate-modal-meta{display:flex;align-items:center;gap:6px;flex-wrap:wrap}\n.dsh-ig-regenerate-modal-textarea{width:100%;box-sizing:border-box;background:var(--dsw-alias-bg-layer-2,rgba(0,0,0,0.25));border:1px solid var(--dsw-alias-border-subtle,rgba(255,255,255,0.15));border-radius:8px;padding:10px 12px;font-size:13px;line-height:1.5;color:var(--dsw-alias-label-primary,#fff);resize:vertical;font-family:inherit;outline:none;transition:border-color .15s,box-shadow .15s}\n.dsh-ig-regenerate-modal-textarea:focus{border-color:#3b82f6;box-shadow:0 0 0 2px rgba(59,130,246,0.2)}\n.dsh-ig-lightbox-btn-regenerate:hover{color:#60a5fa!important;border-color:rgba(96,165,250,0.5)!important;background:rgba(96,165,250,0.12)!important}\n.dsh-ig-lightbox-generating-indicator{display:inline-flex;align-items:center;gap:8px;padding:4px 12px;background:rgba(37,99,235,0.2);border:1px solid rgba(59,130,246,0.5);border-radius:20px;font-size:12px;color:#93c5fd;animation:dsh-ig-fade .15s ease-out}\n.dsh-ig-lightbox-spinner-sm{width:12px;height:12px;border:2px solid rgba(147,197,253,0.3);border-top-color:#93c5fd;border-radius:50%;animation:dsh-ig-spin .8s linear infinite;flex-shrink:0}\n.dsh-ig-lightbox-abort-btn{appearance:none;background:transparent;border:0;color:#fca5a5;font-size:11.5px;cursor:pointer;padding:0 4px;margin-left:4px;text-decoration:underline;text-underline-offset:2px}\n.dsh-ig-lightbox-abort-btn:hover{color:#ef4444}\n\n/* Hide floating chat composer and width handles when the conversation-view\n gallery page is active. The right-sidebar variant (dsh-ig-page-in-sidebar)\n is excluded: the native conversation runs beside it and needs its composer\n and the column handles untouched. */\n[data-conversation-scroll]:has(.dsh-ig-gallery-page:not(.dsh-ig-page-in-sidebar)) [data-composer-seat]{display:none!important}\n:has(> [data-conversation-scroll]:has(.dsh-ig-gallery-page:not(.dsh-ig-page-in-sidebar))) > [class*=\"widthHandle\"],\n:has(.dsh-ig-gallery-page:not(.dsh-ig-page-in-sidebar)) [class*=\"widthHandle\"],\n.root:has(.dsh-ig-gallery-page:not(.dsh-ig-page-in-sidebar)) [class*=\"widthHandle\"]{display:none!important}\n";
1
+ export declare const PLUGIN_STYLE = "\n.dsh-ig-card{list-style:none;border:0;background:none;padding:0}\n.dsh-ig-head{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:0 0 6px}\n.dsh-ig-head-text{flex:1;min-width:0;display:flex;flex-direction:column;gap:4px}\n.dsh-ig-title{display:block;font-size:15px;font-weight:600;line-height:1.4;color:var(--dsw-alias-label-primary,inherit)}\n.dsh-ig-desc{display:block;font-size:13px;line-height:1.5;color:var(--dsw-alias-label-tertiary,#7b818b)}\n.dsh-ig-body{padding:0}\n.dsh-ig-field{display:flex;flex-direction:column;gap:6px;padding:12px 0}\n.dsh-ig-field + .dsh-ig-field{border-top:0.5px solid var(--dsw-alias-border-l2,#e8ebf0)}\n.dsh-ig-label{font-size:13px;font-weight:500;color:var(--dsw-alias-label-primary,inherit)}\n.dsh-ig-input{box-sizing:border-box;width:100%;height:34px;padding:0 12px;font-size:13px;border:0.5px solid var(--dsw-alias-border-l4,#d7dbe0);border-radius:var(--dsw-radius-md,6px);background:var(--dsw-alias-bg-layer-3,transparent);color:inherit;outline:none;transition:border-color .15s}\n.dsh-ig-input:focus{border-color:var(--dsw-alias-brand-primary,#4c78ff)}\n.dsh-ig-textarea{resize:vertical;min-height:56px;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;line-height:1.5}\n.dsh-ig-input-group{display:flex;gap:8px;align-items:center}\n.dsh-ig-file-row{display:flex;align-items:center;gap:10px;min-width:0}\n.dsh-ig-file-input{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;clip-path:inset(50%)}\n.dsh-ig-file-button{appearance:none;flex:none;border:1px solid var(--dsw-alias-border-l2,#d7dbe0);border-radius:8px;padding:7px 12px;background:var(--dsw-alias-bg-layer-3,#f9fafb);color:var(--dsw-alias-label-secondary,inherit);font-size:13px;cursor:pointer;transition:background .15s,border-color .15s}\n.dsh-ig-file-button:hover{background:var(--dsw-alias-bg-layer-2,#edf0f3);border-color:var(--dsw-alias-label-dimmed,#9ca3af)}\n.dsh-ig-file-button:focus-within{outline:2px solid var(--dsw-alias-brand-primary,#4c78ff);outline-offset:2px}\n.dsh-ig-file-name{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--dsw-alias-label-secondary,inherit);font-size:12px}\n.dsh-ig-workflow-list{list-style:none;margin:0;padding:0;display:grid;gap:8px}\n.dsh-ig-workflow-row{display:flex;flex-direction:column;gap:6px;padding:8px;border:1px solid var(--dsw-alias-border-l2,#eee);border-radius:8px}\n.dsh-ig-workflow-main{display:flex;align-items:center;gap:8px}\n.dsh-ig-workflow-active{display:inline-flex;align-items:center;cursor:pointer;flex:none}\n.dsh-ig-workflow-active input[type=radio]{width:15px;height:15px;accent-color:var(--dsw-alias-brand-primary,#4c78ff);margin:0;cursor:pointer}\n.dsh-ig-workflow-name{flex:1;min-width:0}\n.dsh-ig-btn-reset{appearance:none;border:0.5px solid var(--dsw-alias-border-l4,#d7dbe0);border-radius:var(--dsw-radius-md,6px);padding:5px 12px;background:var(--dsw-alias-bg-layer-3,#f9fafb);color:var(--dsw-alias-label-secondary,inherit);font:inherit;font-size:13px;cursor:pointer;white-space:nowrap;transition:background .15s,border-color .15s}\n.dsh-ig-btn-reset:hover{background:var(--dsw-alias-bg-layer-2,#edf0f3);border-color:var(--dsw-alias-label-dimmed,#9ca3af)}\n.dsh-ig-hint,.dsh-ig-status{margin:0;color:var(--dsw-alias-label-tertiary,#7b818b);font-size:12px;line-height:1.4}\n.dsh-ig-link{color:var(--dsw-alias-label-link,#2563eb);text-decoration:none;white-space:nowrap}.dsh-ig-link:hover{text-decoration:underline}\n.dsh-ig-hint-error{color:var(--dsw-alias-label-error,#d33)}\n.dsh-ig-status-error{color:var(--dsw-alias-label-error,#d33);font-weight:500}\n.dsh-ig-actions{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-top:16px;padding-top:12px;border-top:1px solid var(--dsw-alias-border-l2,#eee)}\n.dsh-ig-check-row{display:flex;align-items:center;gap:8px;cursor:pointer}\n.dsh-ig-check-row input[type=checkbox]{width:15px;height:15px;accent-color:var(--dsw-alias-brand-primary,#4c78ff);margin:0}\n.dsh-ig-model-picker{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:8px 12px;max-height:180px;overflow-y:auto;padding:2px 0}\n.dsh-ig-model-picker .dsh-ig-check-row{min-width:0}\n.dsh-ig-model-option{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:12px}\n.dsh-ig-model-default{flex:none;color:var(--dsw-alias-label-tertiary,#7b818b);font-size:11px}\n.dsh-ig-savedto{font-size:12px;line-height:1.5;color:var(--dsw-alias-label-tertiary,#7b818b);word-break:break-all}\n.dsh-ig-save{appearance:none;border:0;border-radius:var(--dsw-radius-md,6px);padding:5px 14px;background:var(--dsw-alias-label-primary,#111827);color:var(--dsw-alias-bg-layer-3,#fff);font:inherit;font-size:13px;font-weight:400;cursor:pointer;transition:opacity .15s}\n.dsh-ig-save:disabled{opacity:.4;cursor:default}\n\n/* Provider list: one expandable row per provider, each saving independently. */\n.dsh-ig-providers{display:flex;flex-direction:column;margin-top:12px;overflow:hidden;border:1px solid var(--dsw-alias-border-l2,#e8ebf0);border-radius:10px;background:var(--dsw-alias-bg-layer-3,transparent)}\n.dsh-ig-provider-row{border:0;background:none;padding:0 14px}\n.dsh-ig-provider-row + .dsh-ig-provider-row{border-top:1px solid var(--dsw-alias-border-l2,#e8ebf0)}\n.dsh-ig-provider-head-row{display:flex;align-items:center;gap:8px;min-height:48px}\n.dsh-ig-provider-head{flex:1;min-width:0;width:100%;min-height:48px;appearance:none;border:0;background:none;font:inherit;color:inherit;text-align:left;cursor:pointer;display:flex;align-items:center;gap:10px;padding:0}\n.dsh-ig-channel-add.dsh-ig-btn-secondary{display:inline-flex;align-items:center;justify-content:center;flex:none;box-sizing:border-box;width:26px;height:26px;padding:0;border:0;border-radius:7px;background:var(--dsw-alias-bg-layer-2,#f3f4f6);color:var(--dsw-alias-label-tertiary,#7b818b);line-height:1;transition:background .15s,color .15s}\n.dsh-ig-channel-add.dsh-ig-btn-secondary:hover:not(:disabled){border:0;background:var(--dsw-alias-bg-layer-1,#e5e7eb);color:var(--dsw-alias-label-primary,inherit)}\n.dsh-ig-channel-add svg{width:14px;height:14px}\n.dsh-ig-provider-head:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary,#4c78ff);outline-offset:2px;border-radius:var(--dsw-radius-sm,4px)}\n.dsh-ig-provider-head:hover .dsh-ig-provider-name{color:var(--dsw-alias-label-primary,inherit)}\n.dsh-ig-provider-head:hover .dsh-ig-provider-chevron{color:var(--dsw-alias-label-primary,inherit)}\n.dsh-ig-provider-name{flex:1;min-width:0;font-size:13px;font-weight:550;line-height:20px;color:var(--dsw-alias-label-primary,inherit);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.dsh-ig-provider-chevron{display:inline-flex;align-items:center;justify-content:center;flex:none;width:20px;height:20px;color:var(--dsw-alias-label-tertiary,#7b818b);transition:transform .16s,color .15s}\n.dsh-ig-provider-chevron-open{transform:rotate(180deg)}\n.dsh-ig-provider-body{border-top:0;padding:0 0 14px}\n.dsh-ig-badge{flex:none;display:inline-flex;align-items:center;gap:5px;box-sizing:border-box;min-height:22px;padding:1px 8px;border:1px solid transparent;border-radius:6px;font-size:11px;font-weight:500;line-height:18px;color:var(--dsw-alias-label-secondary,inherit);white-space:nowrap;max-width:60%;overflow:hidden;text-overflow:ellipsis}\n.dsh-ig-badge-dot{width:5px;height:5px;border-radius:50%;background:currentColor;flex:none}\n.dsh-ig-badge-ok{border-color:rgba(34,197,94,.2);color:#15803d;background:rgba(34,197,94,.08)}\n.dsh-ig-badge-missing{border-color:rgba(239,68,68,.18);color:#b91c1c;background:rgba(239,68,68,.06)}\n.dsh-ig-badge-neutral{border-color:var(--dsw-alias-border-l2,#e8ebf0);color:var(--dsw-alias-label-tertiary,#7b818b);background:var(--dsw-alias-bg-layer-2,transparent)}\n.dsh-ig-badge-checking .dsh-ig-badge-dot{animation:dsh-ig-pulse 1s ease-in-out infinite}\n@keyframes dsh-ig-pulse{50%{opacity:.25}}\n\n/* Default provider radio pills. */\n.dsh-ig-radios{display:flex;flex-wrap:wrap;gap:8px}\n.dsh-ig-radio{display:inline-flex;align-items:center;gap:6px;border:1px solid var(--dsw-alias-border-l2,#d7dbe0);border-radius:999px;padding:5px 12px;cursor:pointer;font-size:12.5px;color:var(--dsw-alias-label-secondary,inherit);transition:border-color .15s,background .15s}\n.dsh-ig-radio:hover{border-color:var(--dsw-alias-label-dimmed,#9ca3af)}\n.dsh-ig-radio-checked{border-color:var(--dsw-alias-brand-primary,#4c78ff);background:rgba(76,120,255,.08);color:var(--dsw-alias-label-primary,inherit)}\n.dsh-ig-radio input[type=radio]{width:14px;height:14px;accent-color:var(--dsw-alias-brand-primary,#4c78ff);margin:0;cursor:pointer}\n\n/* Row-level actions: test connection, clear key, save. */\n.dsh-ig-row-actions{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-top:14px;flex-wrap:wrap}\n.dsh-ig-row-buttons{display:flex;align-items:center;gap:8px;flex-wrap:wrap}\n.dsh-ig-btn-secondary{appearance:none;border:0.5px solid var(--dsw-alias-border-l4,#d7dbe0);border-radius:var(--dsw-radius-md,6px);padding:5px 14px;background:var(--dsw-alias-bg-layer-3,#f9fafb);color:var(--dsw-alias-label-secondary,inherit);font:inherit;font-size:13px;cursor:pointer;white-space:nowrap;transition:background .15s,border-color .15s,opacity .15s}\n.dsh-ig-btn-secondary:hover:not(:disabled){background:var(--dsw-alias-bg-layer-2,#edf0f3);border-color:var(--dsw-alias-label-dimmed,#9ca3af)}\n.dsh-ig-btn-secondary:disabled{opacity:.45;cursor:default}\n.dsh-ig-btn-danger{color:#b91c1c;border-color:rgba(239,68,68,.4)}\n.dsh-ig-btn-danger:hover:not(:disabled){background:rgba(239,68,68,.08);border-color:rgba(239,68,68,.6)}\n\n/* Workspace section within the settings card. */\n.dsh-ig-section{display:flex;flex-direction:column;gap:6px;margin-top:20px;padding-top:14px;border-top:0.5px solid var(--dsw-alias-border-l2,#e8ebf0)}\n.dsh-ig-section-title{font-size:13px;font-weight:500;line-height:1.5;color:var(--dsw-alias-label-primary,inherit)}\n.dsh-ig-status-readonly{color:var(--dsw-alias-label-tertiary,#7b818b);font-style:italic}\n\n.dsh-ig-result{display:grid;gap:10px;max-width:520px}\n.dsh-ig-promoted-results{display:grid;gap:16px}\n.dsh-ig-result-title{font-size:14px;font-weight:600}\n.dsh-ig-container{position:relative;display:inline-block;width:fit-content;max-width:100%;justify-self:start;border-radius:12px;overflow:hidden;line-height:0;isolation:isolate}\n.dsh-ig-container:hover .dsh-ig-toolbar,.dsh-ig-container:focus-within .dsh-ig-toolbar{opacity:1;pointer-events:auto}\n.dsh-ig-toolbar{position:absolute;top:8px;left:8px;display:flex;align-items:center;gap:5px;padding:3px 5px;border-radius:8px;background:rgba(0,0,0,0.65);backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);opacity:0;pointer-events:none;transition:opacity .18s ease;z-index:2;line-height:1}\n.dsh-ig-tool-btn{appearance:none;border:0;background:transparent;color:#fff;padding:5px;border-radius:6px;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;transition:background .15s,color .15s}\n.dsh-ig-tool-btn:hover{background:rgba(255,255,255,0.25)}\n.dsh-ig-tool-btn-danger:hover{background:rgba(239,68,68,0.75)!important;color:#fff!important}\n.dsh-ig-toast{position:absolute;top:100%;left:0;margin-top:5px;padding:3px 8px;border-radius:6px;background:rgba(0,0,0,0.85);color:#fff;font-size:11px;white-space:nowrap;pointer-events:none;z-index:4}\n.dsh-ig-image{display:block;max-width:100%;max-height:520px;border-radius:12px;background:#f2f3f5;cursor:pointer}\n.dsh-ig-version-nav{position:absolute;right:8px;bottom:8px;display:flex;align-items:center;gap:2px;padding:3px;border-radius:999px;background:rgba(15,23,42,.72);color:#fff;backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);font-size:11px;line-height:1;z-index:2}\n.dsh-ig-version-nav button{appearance:none;border:0;background:transparent;color:inherit;width:25px;height:25px;border-radius:50%;display:inline-flex;align-items:center;justify-content:center;cursor:pointer;font-size:17px;line-height:1}\n.dsh-ig-version-nav button:hover:not(:disabled){background:rgba(255,255,255,.2)}\n.dsh-ig-version-nav button:disabled{opacity:.3;cursor:default}\n.dsh-ig-version-count{min-width:34px;text-align:center;font-variant-numeric:tabular-nums}\n.dsh-ig-regenerate-overlay{position:absolute;inset:0;background:rgba(15,23,42,0.52);backdrop-filter:blur(6px);-webkit-backdrop-filter:blur(6px);display:flex;flex-direction:column;align-items:center;justify-content:center;gap:10px;color:#fff;z-index:3;animation:dsh-ig-fade .18s ease-out;line-height:1.4;border-radius:12px}\n.dsh-ig-regenerate-spinner{width:28px;height:28px;border:3px solid rgba(255,255,255,0.25);border-top-color:#fff;border-radius:50%;animation:dsh-ig-spin .8s linear infinite}\n.dsh-ig-regenerate-overlay-text{font-size:12.5px;font-weight:550;color:#fff;letter-spacing:0.2px;text-shadow:0 1px 2px rgba(0,0,0,0.4)}\n.dsh-ig-regenerate-overlay-cancel{appearance:none;border:1px solid rgba(255,255,255,0.4);border-radius:6px;background:rgba(255,255,255,0.15);color:#fff;padding:3px 12px;font-size:11.5px;cursor:pointer;transition:background .15s}\n.dsh-ig-regenerate-overlay-cancel:hover{background:rgba(255,255,255,0.3)}\n@keyframes dsh-ig-spin{to{transform:rotate(360deg)}}\n.dsh-ig-regenerate-backdrop{position:fixed;inset:0;z-index:100000;display:flex;align-items:center;justify-content:center;padding:18px;background:rgba(11,17,29,.6);backdrop-filter:blur(5px);-webkit-backdrop-filter:blur(5px);animation:dsh-ig-fade .15s ease-out}\n.dsh-ig-regenerate-dialog{width:min(520px,100%);border:1px solid var(--dsw-alias-border-l2,#dfe3ea);border-radius:14px;background:var(--dsw-alias-bg-layer-1,#fff);color:var(--dsw-alias-label-primary,#172033);box-shadow:0 24px 72px rgba(11,17,29,.28);padding:20px;box-sizing:border-box;line-height:1.4}\n.dsh-ig-regenerate-dialog h3{margin:0;font-size:16px;font-weight:650}\n.dsh-ig-regenerate-dialog p{margin:7px 0 16px;color:var(--dsw-alias-label-tertiary,#737d8f);font-size:12.5px;line-height:1.55}\n.dsh-ig-regenerate-dialog label{display:grid;gap:7px;font-size:12.5px;font-weight:600}\n.dsh-ig-regenerate-dialog textarea{box-sizing:border-box;width:100%;min-height:132px;resize:vertical;border:1px solid var(--dsw-alias-border-l2,#d7dce5);border-radius:9px;padding:11px 12px;background:var(--dsw-alias-bg-layer-2,#fff);color:inherit;font:inherit;font-size:13px;line-height:1.55;outline:none}\n.dsh-ig-regenerate-dialog textarea:focus{border-color:var(--dsw-alias-brand-primary,#4c78ff);box-shadow:0 0 0 3px rgba(76,120,255,.12)}\n.dsh-ig-regenerate-error{margin-top:10px!important;color:var(--dsw-alias-label-error,#d33)!important}\n.dsh-ig-regenerate-actions{display:flex;justify-content:flex-end;gap:9px;margin-top:16px}\n.dsh-ig-regenerate-actions button{height:35px;padding:0 14px;border-radius:7px;font:inherit;font-size:13px;font-weight:550;cursor:pointer}\n.dsh-ig-regenerate-cancel{border:1px solid var(--dsw-alias-border-l2,#d7dce5);background:transparent;color:inherit}\n.dsh-ig-regenerate-confirm{border:1px solid var(--dsw-alias-brand-primary,#3569ed);background:var(--dsw-alias-brand-primary,#3569ed);color:var(--dsw-alias-label-primary-inverted,#fff)}\n.dsh-ig-regenerate-actions button:disabled{opacity:.5;cursor:default}\n@media(hover:none){.dsh-ig-container .dsh-ig-toolbar{opacity:1;pointer-events:auto}}\n@keyframes dsh-ig-fade{from{opacity:0}to{opacity:1}}\n.dsh-ig-error{color:var(--dsw-alias-label-error,#d33);font-size:13px}\n.dsh-ig-loading{color:var(--dsw-alias-label-tertiary,#7b818b);font-size:13px}\n\n/* Native Workspace Gallery & Studio View (Renders seamlessly inside DSH Session View) */\n.dsh-ig-gallery-page{width:100%;height:100%;background:var(--dsw-alias-bg-layer-1,#ffffff);display:flex;flex-direction:column;overflow:hidden;flex:1}\n\n/* 1. Top Navigation Tab Bar */\n.dsh-ig-studio-tabs-bar{display:flex;align-items:center;gap:6px;padding:6px 24px;border-bottom:1px solid var(--dsw-alias-border-l2,#e5e7eb);background:var(--dsw-alias-bg-layer-1,#ffffff);flex-shrink:0}\n.dsh-ig-studio-tab-btn{appearance:none;-webkit-appearance:none;border:0;background:transparent;display:inline-flex;align-items:center;gap:7px;padding:7px 14px;font-size:13px;font-weight:500;color:var(--dsw-alias-label-secondary,#64748b);cursor:pointer;border-radius:6px;transition:color .15s ease,background-color .15s ease}\n.dsh-ig-studio-tab-btn:hover{color:var(--dsw-alias-label-primary,#0f172a);background:var(--dsw-alias-bg-layer-2,#f1f5f9)}\n.dsh-ig-studio-tab-btn.is-active{color:var(--dsw-alias-brand-primary,#2563eb);font-weight:600;background:rgba(37,99,235,0.08)}\n\n/* 2. Secondary Filter & Search Toolbar */\n.dsh-ig-studio-toolbar{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:10px 24px;background:var(--dsw-alias-bg-layer-1,#ffffff);border-bottom:1px solid var(--dsw-alias-border-l2,#f1f5f9);flex-shrink:0;flex-wrap:nowrap;min-width:0}\n.dsh-ig-studio-toolbar-left{display:flex;align-items:center;gap:10px;flex-wrap:nowrap;flex:1;min-width:0}\n.dsh-ig-studio-toolbar-right{display:flex;align-items:center;gap:10px;flex-shrink:0}\n\n/* Modern Custom Select (Removes OS default arrows & ugly borders) */\n.dsh-ig-studio-select{appearance:none;-webkit-appearance:none;-moz-appearance:none;height:32px;line-height:30px;padding:0 28px 0 12px;font-size:12.5px;color:var(--dsw-alias-label-primary,#334155);background-color:var(--dsw-alias-bg-layer-2,#ffffff);background-image:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 24 24' fill='none' stroke='%2364748b' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E\");background-repeat:no-repeat;background-position:right 9px center;border:1px solid var(--dsw-alias-border-l2,#e2e8f0);border-radius:6px;outline:none;cursor:pointer;box-sizing:border-box;transition:border-color .15s ease,box-shadow .15s ease,background-color .15s ease}\n.dsh-ig-studio-select:hover{border-color:var(--dsw-alias-border-l1,#cbd5e1);background-color:var(--dsw-alias-bg-layer-1,#f8fafc)}\n.dsh-ig-studio-select:focus{border-color:var(--dsw-alias-brand-primary,#3b82f6);box-shadow:0 0 0 2px rgba(59,130,246,0.15)}\n.dsh-ig-studio-select-sort{font-weight:500}\n\n/* Unified Search Input */\n.dsh-ig-studio-search-wrap{position:relative;display:flex;align-items:center;min-width:140px;max-width:320px;flex:1}\n.dsh-ig-studio-search-icon{position:absolute;left:10px;color:var(--dsw-alias-label-tertiary,#94a3b8);pointer-events:none}\n.dsh-ig-studio-search-input{width:100%;height:32px;line-height:30px;padding:0 12px 0 32px;font-size:12.5px;border:1px solid var(--dsw-alias-border-l2,#e2e8f0);border-radius:6px;background-color:var(--dsw-alias-bg-layer-2,#ffffff);color:inherit;outline:none;box-sizing:border-box;transition:border-color .15s ease,box-shadow .15s ease}\n.dsh-ig-studio-search-input:hover{border-color:var(--dsw-alias-border-l1,#cbd5e1)}\n.dsh-ig-studio-search-input:focus{border-color:var(--dsw-alias-brand-primary,#3b82f6);box-shadow:0 0 0 2px rgba(59,130,246,0.15)}\n.dsh-ig-studio-search-input::placeholder{color:var(--dsw-alias-label-tertiary,#94a3b8)}\n\n/* 3. Grid & Responsive Cards */\n.dsh-ig-gallery-page-body{flex:1;overflow-y:auto;padding:20px 24px}\n.dsh-ig-gallery-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:18px}\n.dsh-ig-gallery-sentinel{grid-column:1/-1;height:1px;width:100%}\n.dsh-ig-gallery-card{background:var(--dsw-alias-bg-layer-2,#ffffff);border:1px solid var(--dsw-alias-border-l2,#e2e8f0);border-radius:10px;overflow:hidden;display:flex;flex-direction:column;cursor:pointer;transition:transform .18s ease,box-shadow .18s ease,border-color .18s ease;content-visibility:auto;contain-intrinsic-size:auto 280px}\n.dsh-ig-gallery-card:hover{transform:translateY(-2px);box-shadow:0 10px 20px -5px rgba(0,0,0,0.06),0 4px 6px -2px rgba(0,0,0,0.03);border-color:var(--dsw-alias-border-l1,#cbd5e1)}\n.dsh-ig-gallery-card-media{position:relative;width:100%;aspect-ratio:1/1;background:#f1f5f9;overflow:hidden;display:flex;align-items:center;justify-content:center}\n.dsh-ig-gallery-card-img{width:100%;height:100%;object-fit:cover;transition:transform .2s ease}\n.dsh-ig-gallery-card:hover .dsh-ig-gallery-card-img{transform:scale(1.03)}\n.dsh-ig-gallery-card-loading{font-size:12px;color:#94a3b8}\n.dsh-ig-gallery-card-error{font-size:12px;color:#ef4444;padding:8px;text-align:center}\n\n/* Floating Action Toolbar on Card Hover */\n.dsh-ig-gallery-card:hover .dsh-ig-card-toolbar{opacity:1;pointer-events:auto}\n.dsh-ig-card-toolbar{position:absolute;top:6px;left:6px;display:flex;align-items:center;gap:3px;padding:3px 5px;border-radius:6px;background:rgba(15,23,42,0.72);backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);opacity:0;pointer-events:none;transition:opacity .18s ease;z-index:10;line-height:1}\n\n/* Card Bottom Metadata */\n.dsh-ig-gallery-card-meta{padding:10px 12px;display:flex;flex-direction:column;gap:5px;background:var(--dsw-alias-bg-layer-2,#ffffff);flex:1}\n.dsh-ig-card-badge-row{display:flex;align-items:center}\n.dsh-ig-card-badge{display:inline-block;padding:2px 6px;border-radius:4px;background:var(--dsw-alias-bg-layer-3,#f1f5f9);color:var(--dsw-alias-label-secondary,#475569);font-size:11px;font-weight:500;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.dsh-ig-gallery-card-prompt-line{font-size:12.5px;font-weight:500;color:var(--dsw-alias-label-primary,#1e293b);line-height:1.4;min-height:1.4em;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n/* An imported image has no prompt: its line keeps one row of height and the footer sits at the bottom, so mixed rows align (#113). */\n.dsh-ig-card-footer-row{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-top:auto;padding-top:2px}\n.dsh-ig-card-meta-text{font-size:11px;color:var(--dsw-alias-label-tertiary,#94a3b8);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\n.dsh-ig-card-fav-btn{appearance:none;border:0;background:transparent;color:var(--dsw-alias-label-tertiary,#94a3b8);padding:2px;border-radius:4px;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;transition:color .15s,transform .15s;flex-shrink:0}\n.dsh-ig-card-fav-btn:hover{color:#ef4444;transform:scale(1.15)}\n.dsh-ig-card-fav-btn.is-favorited{color:#ef4444}\n\n/* Placeholders for Upcoming Routes */\n.dsh-ig-placeholder-view{display:flex;align-items:center;justify-content:center;min-height:360px;height:100%;padding:24px}\n.dsh-ig-placeholder-card{max-width:500px;width:100%;text-align:center;padding:36px 28px;background:var(--dsw-alias-bg-layer-2,#ffffff);border:1px dashed var(--dsw-alias-border-l2,#e2e8f0);border-radius:14px;display:flex;flex-direction:column;align-items:center;gap:12px}\n.dsh-ig-placeholder-icon{font-size:40px;line-height:1}\n.dsh-ig-placeholder-header{display:flex;align-items:center;gap:8px;justify-content:center}\n.dsh-ig-placeholder-title{font-size:16px;font-weight:600;color:var(--dsw-alias-label-primary,inherit);margin:0}\n.dsh-ig-placeholder-badge{font-size:11px;font-weight:500;background:rgba(37,99,235,0.1);color:#2563eb;padding:2px 8px;border-radius:12px}\n.dsh-ig-placeholder-desc{font-size:13px;line-height:1.6;color:var(--dsw-alias-label-secondary,#64748b);margin:0}\n.dsh-ig-placeholder-tip{margin-top:6px;padding:8px 12px;font-size:12px;background:var(--dsw-alias-bg-layer-3,#f8fafc);border-radius:8px;color:var(--dsw-alias-label-tertiary,#64748b);text-align:left}\n\n/* Empty State */\n.dsh-ig-gallery-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;min-height:360px;text-align:center;color:var(--dsw-alias-label-tertiary,#94a3b8)}\n.dsh-ig-gallery-empty-icon{display:block;width:44px;height:44px;margin-bottom:10px;color:var(--dsw-alias-label-tertiary,#94a3b8);opacity:.7}\n.dsh-ig-gallery-empty-title{font-size:15px;font-weight:600;color:var(--dsw-alias-label-primary,inherit);margin-bottom:4px}\n.dsh-ig-gallery-empty-desc{font-size:13px;max-width:360px;line-height:1.5}\n\n/* Pure Centered Lightbox */\n.dsh-ig-lightbox-backdrop{position:fixed;inset:0;z-index:99999;background:rgba(0,0,0,0.88);backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);display:flex;flex-direction:column;align-items:center;justify-content:center;padding:20px;box-sizing:border-box;cursor:zoom-out;animation:dsh-ig-fade .15s ease-out}\n.dsh-ig-lightbox-topbar{position:absolute;top:20px;left:24px;right:24px;display:flex;align-items:center;justify-content:space-between;z-index:10;pointer-events:none}\n.dsh-ig-lightbox-meta{display:flex;align-items:center;gap:8px;pointer-events:auto}\n.dsh-ig-tag{display:inline-block;padding:2px 6px;border-radius:4px;background:var(--dsw-alias-bg-layer-3,#edf0f3);color:var(--dsw-alias-label-secondary,inherit);font-weight:500;text-transform:uppercase;font-size:10px}\n.dsh-ig-tag-model{background:rgba(76,120,255,0.1);color:#4c78ff}\n.dsh-ig-lightbox-close-btn{appearance:none;border:0;background:rgba(255,255,255,0.15);color:#fff;border-radius:50%;width:34px;height:34px;font-size:16px;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;transition:background .15s;pointer-events:auto}\n.dsh-ig-lightbox-close-btn:hover{background:rgba(255,255,255,0.3)}\n.dsh-ig-lightbox-img-wrap{max-width:86vw;max-height:78vh;display:flex;align-items:center;justify-content:center;cursor:default}\n.dsh-ig-lightbox-img{max-width:100%;max-height:78vh;object-fit:contain;border-radius:8px;box-shadow:0 24px 60px rgba(0,0,0,0.7);user-select:none}\n.dsh-ig-lightbox-bottombar{position:absolute;bottom:24px;left:50%;transform:translateX(-50%);max-width:min(90vw,640px);background:rgba(20,22,26,0.85);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);border:1px solid rgba(255,255,255,0.15);border-radius:14px;padding:10px 16px;display:flex;flex-direction:column;gap:8px;color:#fff;box-shadow:0 16px 40px rgba(0,0,0,0.5);cursor:default}\n.dsh-ig-lightbox-prompt-text{font-size:13px;line-height:1.4;color:rgba(255,255,255,0.92);display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;word-break:break-word}\n.dsh-ig-lightbox-counter{display:inline-flex;align-items:center;padding:2px 8px;border-radius:4px;background:rgba(255,255,255,0.15);color:#fff;font-size:11.5px;font-weight:500;font-variant-numeric:tabular-nums}\n.dsh-ig-lightbox-nav-btn{position:fixed;top:50%;transform:translateY(-50%);width:44px;height:44px;border-radius:50%;border:1px solid rgba(255,255,255,0.18);background:rgba(255,255,255,0.12);color:#fff;display:flex;align-items:center;justify-content:center;cursor:pointer;z-index:100;backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);transition:background-color .15s ease,transform .15s ease,opacity .15s ease;outline:none}\n.dsh-ig-lightbox-nav-btn:hover:not(:disabled){background:rgba(255,255,255,0.28);transform:translateY(-50%) scale(1.08)}\n.dsh-ig-lightbox-nav-btn:disabled{opacity:0.2;cursor:not-allowed;pointer-events:none}\n.dsh-ig-lightbox-nav-prev{left:24px}\n.dsh-ig-lightbox-nav-next{right:24px}\n.dsh-ig-lightbox-loading{display:flex;align-items:center;justify-content:center;min-width:180px;min-height:180px}\n.dsh-ig-lightbox-spinner{width:36px;height:36px;border:3px solid rgba(255,255,255,0.2);border-top-color:#fff;border-radius:50%;animation:dsh-ig-spin .8s linear infinite}\n@keyframes dsh-ig-spin{to{transform:rotate(360deg)}}\n.dsh-ig-lightbox-btn{appearance:none;border:1px solid rgba(255,255,255,0.18);background:rgba(255,255,255,0.08);color:#fff;border-radius:6px;padding:5px 10px;font-size:12px;cursor:pointer;display:inline-flex;align-items:center;gap:5px;transition:background .15s,border-color .15s,color .15s}\n.dsh-ig-lightbox-btn:hover{background:rgba(255,255,255,0.22)}\n.dsh-ig-lightbox-btn-danger{border-color:rgba(239,68,68,0.4);color:#fca5a5}\n.dsh-ig-lightbox-btn-danger:hover{background:rgba(239,68,68,0.35)!important;color:#fff!important;border-color:rgba(239,68,68,0.7)!important}\n.dsh-ig-gallery-page-toast{position:fixed;bottom:24px;left:50%;transform:translateX(-50%);background:rgba(0,0,0,0.85);color:#fff;padding:6px 14px;border-radius:8px;font-size:13px;z-index:99999;animation:dsh-ig-fade .15s}\n\n/* Card selection and checkbox */\n.dsh-ig-gallery-card.is-selected{box-shadow:0 0 0 2px var(--dsw-alias-brand-primary,#2563eb);border-color:transparent}\n.dsh-ig-card-checkbox{position:absolute;top:8px;left:8px;width:22px;height:22px;border-radius:50%;border:1.5px solid rgba(255,255,255,0.7);background:rgba(0,0,0,0.35);backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px);color:#fff;display:inline-flex;align-items:center;justify-content:center;cursor:pointer;z-index:5;opacity:0;transition:opacity .15s ease,background-color .15s ease,border-color .15s ease;padding:0;outline:none}\n.dsh-ig-gallery-card:hover .dsh-ig-card-checkbox,.dsh-ig-gallery-card.is-manage-mode .dsh-ig-card-checkbox,.dsh-ig-card-checkbox.is-checked{opacity:1}\n.dsh-ig-card-checkbox.is-checked{background:var(--dsw-alias-brand-primary,#2563eb);border-color:var(--dsw-alias-brand-primary,#2563eb)}\n\n/* Studio button in toolbar */\n.dsh-ig-studio-btn{display:inline-flex;align-items:center;gap:6px;height:32px;padding:0 12px;box-sizing:border-box;border-radius:6px;border:1px solid var(--dsw-alias-border-subtle,rgba(0,0,0,0.12));background:var(--dsw-alias-bg-layer-2,#fff);color:var(--dsw-alias-label-primary,inherit);font-size:12.5px;font-weight:500;cursor:pointer;transition:border-color .15s,background .15s,color .15s}\n.dsh-ig-studio-btn:hover{background:var(--dsw-alias-bg-layer-3,#f3f4f6);border-color:var(--dsw-alias-border-default,rgba(0,0,0,0.2))}\n.dsh-ig-studio-btn.is-active{background:var(--dsw-alias-brand-primary,#2563eb);border-color:var(--dsw-alias-brand-primary,#2563eb);color:#fff}\n.dsh-ig-studio-btn-danger{color:#ef4444;border-color:rgba(239,68,68,0.35);background:rgba(239,68,68,0.06)}\n.dsh-ig-studio-btn-danger:hover{background:rgba(239,68,68,0.14);border-color:rgba(239,68,68,0.6);color:#dc2626}\n.dsh-ig-studio-btn-danger.is-active{background:#dc2626;border-color:#dc2626;color:#fff}\n\n/* Floating Batch Action Bar */\n.dsh-ig-batch-bar{position:fixed;bottom:24px;left:50%;transform:translateX(-50%);display:flex;align-items:center;gap:16px;background:rgba(20,24,32,0.92);backdrop-filter:blur(16px);-webkit-backdrop-filter:blur(16px);border:1px solid rgba(255,255,255,0.18);border-radius:40px;padding:8px 16px;box-shadow:0 16px 40px rgba(0,0,0,0.5);z-index:99990;animation:dsh-ig-slide-up .2s cubic-bezier(0.16,1,0.3,1);color:#fff}\n@keyframes dsh-ig-slide-up{from{transform:translate(-50%,20px);opacity:0}to{transform:translate(-50%,0);opacity:1}}\n.dsh-ig-batch-bar-left{display:flex;align-items:center;gap:10px}\n.dsh-ig-batch-bar-right{display:flex;align-items:center;gap:8px;border-left:1px solid rgba(255,255,255,0.15);padding-left:12px}\n.dsh-ig-batch-counter{font-size:13px;font-weight:600;color:rgba(255,255,255,0.95);margin-right:4px}\n.dsh-ig-batch-btn{appearance:none;border:1px solid rgba(255,255,255,0.15);background:rgba(255,255,255,0.08);color:#fff;border-radius:20px;padding:5px 12px;font-size:12px;cursor:pointer;display:inline-flex;align-items:center;gap:5px;transition:background .15s,border-color .15s,color .15s}\n.dsh-ig-batch-btn:hover:not(:disabled){background:rgba(255,255,255,0.2)}\n.dsh-ig-batch-btn:disabled{opacity:0.4;cursor:not-allowed}\n.dsh-ig-batch-btn.is-favorited{background:rgba(239,68,68,0.15);border-color:rgba(239,68,68,0.4);color:#fca5a5}\n.dsh-ig-batch-btn.is-favorited:hover:not(:disabled){background:rgba(239,68,68,0.25)!important;border-color:rgba(239,68,68,0.6)!important;color:#fff!important}\n.dsh-ig-batch-btn-download:hover:not(:disabled){background:rgba(59,130,246,0.2)!important;border-color:rgba(59,130,246,0.5)!important;color:#93c5fd!important}\n.dsh-ig-batch-divider{width:1px;height:16px;background:rgba(255,255,255,0.18);margin:0 2px;display:inline-block}\n.dsh-ig-batch-btn-danger{background:rgba(239,68,68,0.2);border-color:rgba(239,68,68,0.5);color:#fca5a5}\n.dsh-ig-batch-btn-danger:hover:not(:disabled){background:rgba(239,68,68,0.4)!important;border-color:rgba(239,68,68,0.8)!important;color:#fff!important}\n.dsh-ig-batch-btn-exit{border-color:transparent;background:transparent;color:rgba(255,255,255,0.7)}\n.dsh-ig-batch-btn-exit:hover{background:rgba(255,255,255,0.1);color:#fff}\n\n/* Batch Delete Confirmation Modal */\n.dsh-ig-modal-backdrop{position:fixed;inset:0;background:rgba(0,0,0,0.65);backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px);z-index:100005;display:flex;align-items:center;justify-content:center;padding:16px;animation:dsh-ig-fade .15s ease-out}\n.dsh-ig-modal-box{width:100%;max-width:440px;background:var(--dsw-alias-bg-layer-1,#1c1e24);border:1px solid var(--dsw-alias-border-subtle,rgba(255,255,255,0.12));border-radius:12px;padding:22px;box-sizing:border-box;box-shadow:0 20px 50px rgba(0,0,0,0.45);color:var(--dsw-alias-label-primary,#fff);animation:dsh-ig-scale-up .15s ease-out}\n@keyframes dsh-ig-scale-up{from{transform:scale(0.95);opacity:0}to{transform:scale(1);opacity:1}}\n.dsh-ig-modal-header{display:flex;align-items:flex-start;gap:12px;margin-bottom:12px}\n.dsh-ig-modal-icon-danger{width:36px;height:36px;border-radius:50%;background:rgba(239,68,68,0.12);color:#ef4444;display:flex;align-items:center;justify-content:center;flex-shrink:0}\n.dsh-ig-modal-title{font-size:16px;font-weight:600;line-height:1.4}\n.dsh-ig-modal-body{margin-bottom:20px;padding-left:48px}\n.dsh-ig-modal-desc{font-size:13.5px;color:var(--dsw-alias-label-secondary,rgba(255,255,255,0.7));margin:0 0 14px 0;line-height:1.5}\n.dsh-ig-modal-checkbox-label{display:flex;align-items:center;gap:8px;font-size:12.5px;color:var(--dsw-alias-label-primary,inherit);cursor:pointer;user-select:none}\n.dsh-ig-modal-checkbox-label input{margin:0;cursor:pointer;width:15px;height:15px}\n.dsh-ig-modal-footer{display:flex;align-items:center;justify-content:flex-end;gap:10px}\n.dsh-ig-modal-btn{height:34px;padding:0 14px;border-radius:6px;font-size:13px;font-weight:500;cursor:pointer;transition:background .15s,border-color .15s,color .15s;outline:none}\n.dsh-ig-modal-btn-cancel{background:transparent;border:1px solid var(--dsw-alias-border-subtle,rgba(255,255,255,0.2));color:var(--dsw-alias-label-primary,inherit)}\n.dsh-ig-modal-btn-cancel:hover{background:var(--dsw-alias-bg-layer-3,rgba(255,255,255,0.08))}\n.dsh-ig-modal-btn-danger{background:#dc2626;border:1px solid #dc2626;color:#fff}\n.dsh-ig-modal-btn-danger:hover{background:#b91c1c;border-color:#b91c1c}\n.dsh-ig-modal-btn-primary{background:linear-gradient(135deg,#3b82f6,#2563eb);border:1px solid #2563eb;color:#fff;display:inline-flex;align-items:center;gap:6px}\n.dsh-ig-modal-btn-primary:hover:not(:disabled){background:linear-gradient(135deg,#2563eb,#1d4ed8)}\n.dsh-ig-modal-btn-primary:disabled{opacity:0.5;cursor:not-allowed}\n.dsh-ig-regenerate-modal-box{max-width:520px}\n.dsh-ig-regenerate-modal-icon{width:36px;height:36px;border-radius:50%;background:rgba(59,130,246,0.15);color:#3b82f6;display:flex;align-items:center;justify-content:center;flex-shrink:0}\n.dsh-ig-regenerate-modal-title-wrap{display:flex;flex-direction:column;gap:4px}\n.dsh-ig-regenerate-modal-meta{display:flex;align-items:center;gap:6px;flex-wrap:wrap}\n.dsh-ig-regenerate-modal-textarea{width:100%;box-sizing:border-box;background:var(--dsw-alias-bg-layer-2,rgba(0,0,0,0.25));border:1px solid var(--dsw-alias-border-subtle,rgba(255,255,255,0.15));border-radius:8px;padding:10px 12px;font-size:13px;line-height:1.5;color:var(--dsw-alias-label-primary,#fff);resize:vertical;font-family:inherit;outline:none;transition:border-color .15s,box-shadow .15s}\n.dsh-ig-regenerate-modal-textarea:focus{border-color:#3b82f6;box-shadow:0 0 0 2px rgba(59,130,246,0.2)}\n.dsh-ig-lightbox-btn-regenerate:hover{color:#60a5fa!important;border-color:rgba(96,165,250,0.5)!important;background:rgba(96,165,250,0.12)!important}\n.dsh-ig-lightbox-generating-indicator{display:inline-flex;align-items:center;gap:8px;padding:4px 12px;background:rgba(37,99,235,0.2);border:1px solid rgba(59,130,246,0.5);border-radius:20px;font-size:12px;color:#93c5fd;animation:dsh-ig-fade .15s ease-out}\n.dsh-ig-lightbox-spinner-sm{width:12px;height:12px;border:2px solid rgba(147,197,253,0.3);border-top-color:#93c5fd;border-radius:50%;animation:dsh-ig-spin .8s linear infinite;flex-shrink:0}\n.dsh-ig-lightbox-abort-btn{appearance:none;background:transparent;border:0;color:#fca5a5;font-size:11.5px;cursor:pointer;padding:0 4px;margin-left:4px;text-decoration:underline;text-underline-offset:2px}\n.dsh-ig-lightbox-abort-btn:hover{color:#ef4444}\n\n/* Hide floating chat composer and width handles when the conversation-view\n gallery page is active. The right-sidebar variant (dsh-ig-page-in-sidebar)\n is excluded: the native conversation runs beside it and needs its composer\n and the column handles untouched. */\n[data-conversation-scroll]:has(.dsh-ig-gallery-page:not(.dsh-ig-page-in-sidebar)) [data-composer-seat]{display:none!important}\n:has(> [data-conversation-scroll]:has(.dsh-ig-gallery-page:not(.dsh-ig-page-in-sidebar))) > [class*=\"widthHandle\"],\n:has(.dsh-ig-gallery-page:not(.dsh-ig-page-in-sidebar)) [class*=\"widthHandle\"],\n.root:has(.dsh-ig-gallery-page:not(.dsh-ig-page-in-sidebar)) [class*=\"widthHandle\"]{display:none!important}\n";
@@ -42,6 +42,19 @@ export interface ProviderPillFace {
42
42
  locale?: LocaleService | undefined;
43
43
  credentialEvents?: PillCredentialEvents | undefined;
44
44
  }
45
+ /**
46
+ * Where `position: fixed` offsets start next to the pill, in viewport
47
+ * coordinates. The host composer wraps the tool row in a transformed box
48
+ * (`will-change: transform`), which makes fixed descendants resolve against
49
+ * that box instead of the viewport (#109): viewport coordinates then land the
50
+ * menu a sidebar-width off whenever the left sidebar is open. Two zero-size
51
+ * probes read the real origin, whichever CSS rule creates it.
52
+ */
53
+ export declare function fixedOriginNear(host: Element): {
54
+ left: number;
55
+ top: number;
56
+ bottom: number;
57
+ };
45
58
  /** Whether the user opted into the composer pill; hidden (not merely inert) when off. */
46
59
  export declare function pillVisible(value: PillSettings | undefined): boolean;
47
60
  /** Model (or ComfyUI workflow) a provider row will use with the persisted settings. */
@@ -1,19 +1,47 @@
1
1
  export declare const COMFYUI_PROMPT_PLACEHOLDER = "{{prompt}}";
2
2
  export declare const COMFYUI_SEED_PLACEHOLDER = "{{seed}}";
3
3
  export declare const COMFYUI_IMAGE_PLACEHOLDER = "{{image}}";
4
+ export declare const COMFYUI_WIDTH_PLACEHOLDER = "{{width}}";
5
+ export declare const COMFYUI_HEIGHT_PLACEHOLDER = "{{height}}";
6
+ /** Frame size used for {{width}} / {{height}} when a call names no size. */
7
+ export declare const COMFYUI_DEFAULT_SIZE: {
8
+ readonly width: 1024;
9
+ readonly height: 1024;
10
+ };
11
+ export interface ComfyUISize {
12
+ width: number;
13
+ height: number;
14
+ }
15
+ /**
16
+ * The frame size a call asks for: an exact `WIDTHxHEIGHT`, or an aspect ratio
17
+ * mapped to its bucket. Undefined when the call names neither.
18
+ */
19
+ export declare function comfyUIRequestedSize(size: string | undefined, aspectRatio: string | undefined): ComfyUISize | undefined;
20
+ /** What a workflow can take from a call: how many source images, and whether a frame size. */
21
+ export interface ComfyUIWorkflowSlots {
22
+ images: number;
23
+ size: boolean;
24
+ }
25
+ /** Inspect the placeholders of an imported workflow without preparing it. */
26
+ export declare function comfyUIWorkflowSlots(workflowJson: string): ComfyUIWorkflowSlots;
4
27
  type JsonRecord = Record<string, unknown>;
5
28
  /** Validate imported JSON without exposing workflow graph details to callers. */
6
29
  export declare function validateComfyUIWorkflowJson(workflowJson: string): void;
7
30
  /**
8
- * Parse, clone, and inject one prompt plus an optional randomized seed.
31
+ * Parse, clone, and inject one prompt plus an optional randomized seed, frame
32
+ * size and source images.
9
33
  *
10
- * Prompt and seed placeholders are replaced inside any string so existing
11
- * workflows that embed them in longer text keep working. The image
12
- * placeholder is stricter: it only matches a dedicated `inputs.image` field
13
- * whose value is exactly the placeholder, and at most one may exist, because
14
- * the field must carry a single uploaded file name.
34
+ * Prompt, seed, width and height placeholders are replaced inside any string
35
+ * so existing workflows that embed them in longer text keep working; a value
36
+ * that is exactly the seed, width or height placeholder becomes a number. The
37
+ * image placeholder is stricter: it only matches a dedicated `inputs.image`
38
+ * field whose value is exactly the placeholder. Several may exist (#117); they
39
+ * take the uploaded file names in node order, one each, and their number must
40
+ * match the number of source images.
15
41
  */
16
- export declare function prepareComfyUIWorkflow(workflowJson: string, prompt: string, seed?: number, image?: string): JsonRecord;
42
+ export declare function prepareComfyUIWorkflow(workflowJson: string, prompt: string, seed?: number, images?: string | readonly string[], size?: ComfyUISize): JsonRecord;
43
+ /** The source images a call brings must fill the workflow's {{image}} inputs one to one. */
44
+ export declare function assertImageCount(imageInputs: number, sourceImages: number): void;
17
45
  /** Random seed in the 32-bit range ComfyUI samplers accept. */
18
46
  export declare function randomSeed(): number;
19
47
  export {};
@@ -1,5 +1,6 @@
1
1
  /** ComfyUI text-to-image and image-to-image adapters using an imported API-format workflow. */
2
2
  import type { ImageMediaType } from '@deepseek-ai/dsh-attachment';
3
+ import { type ComfyUISize } from './comfyui-workflow.js';
3
4
  export interface GeneratedComfyUIImage {
4
5
  data: Uint8Array;
5
6
  mediaType: ImageMediaType;
@@ -21,12 +22,18 @@ export declare function generateComfyUIImage(input: ComfyUIJobInput & {
21
22
  workflowJson: string;
22
23
  prompt: string;
23
24
  maxBytes: number;
25
+ /** Frame size for {{width}} / {{height}}; the default applies when omitted. */
26
+ size?: ComfyUISize;
24
27
  }): Promise<GeneratedComfyUIImage>;
25
- /** Upload one source image, run the workflow, and return its first final image. */
28
+ /**
29
+ * Upload the source images, run the workflow, and return its first final
30
+ * image. The images fill the workflow's {{image}} inputs in node order.
31
+ */
26
32
  export declare function editComfyUIImage(input: ComfyUIJobInput & {
27
33
  workflowJson: string;
28
34
  prompt: string;
29
- sourceImage: ComfyUISourceImage;
35
+ sourceImages: readonly ComfyUISourceImage[];
30
36
  maxBytes: number;
37
+ size?: ComfyUISize;
31
38
  }): Promise<GeneratedComfyUIImage>;
32
39
  export {};
@@ -482,4 +482,10 @@ export declare function mergeComfyUIPrompt(preset: string | undefined, user: str
482
482
  /** Default models for the subscription channels; fixed by the bridge protocol. */
483
483
  export declare const DEFAULT_SUBSCRIPTION_MODELS: Record<SubscriptionProvider, string>;
484
484
  export declare const DEFAULT_MODELS: Record<ImageProvider, string>;
485
+ /**
486
+ * Official consoles where each API-key provider issues keys, linked next to
487
+ * the key field (#109). Relays (openai-compat), ComfyUI and the subscription
488
+ * channels have no single place to get one.
489
+ */
490
+ export declare const API_KEY_CONSOLE_URLS: Partial<Record<ImageProvider, string>>;
485
491
  export declare const DEFAULT_BASE_URLS: Record<ImageProvider, string>;
@@ -12,8 +12,25 @@ export declare const ANTIGRAVITY_REDIRECT_URI = "http://localhost:51121/oauth-ca
12
12
  export declare const ANTIGRAVITY_ENDPOINTS: readonly ["https://daily-cloudcode-pa.sandbox.googleapis.com", "https://autopush-cloudcode-pa.sandbox.googleapis.com", "https://daily-cloudcode-pa.googleapis.com", "https://cloudcode-pa.googleapis.com"];
13
13
  /** The image model served through this channel: Nano Banana 2. */
14
14
  export declare const ANTIGRAVITY_IMAGE_MODEL: string;
15
+ /**
16
+ * Antigravity client version carried in the browser User-Agent. Google's
17
+ * backend routes by this: a non-Antigravity or outdated UA lands in the
18
+ * enterprise-license check and fails with the misleading 403 #3501
19
+ * "You do not have a valid license of this product". The official
20
+ * auto-updater publishes the current version; changelog scrape and the
21
+ * hardcoded fallback keep the header sane when the updater is unreachable
22
+ * (same strategy as the reference implementation's version.js).
23
+ */
24
+ /**
25
+ * Oldest client version the backend still serves (#123): 2.x now gets HTTP 200
26
+ * with a "no longer supported" text and no image. Raise it when the backend
27
+ * asks for a newer client again.
28
+ */
29
+ export declare const MIN_ANTIGRAVITY_VERSION = "3.0.0";
15
30
  /** Current client version: the fetched one, or the fallback until fetched. */
16
31
  export declare function getAntigravityVersion(): string;
32
+ /** Numeric x.y.z comparison: negative, zero or positive like a sort comparator. */
33
+ export declare function compareVersions(left: string, right: string): number;
17
34
  /** Test seam: pin the version so unit tests never depend on the network. */
18
35
  export declare function setAntigravityVersionForTest(version: string | undefined): void;
19
36
  /** Fetch and cache the current version once; failures keep the fallback. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-image-gen",
3
- "version": "0.11.0",
3
+ "version": "0.11.1",
4
4
  "description": "Bring ChatGPT-like image generation to DeepSeek Harness — Gemini, OpenAI, Seedream, DashScope, local ComfyUI & more.",
5
5
  "type": "module",
6
6
  "author": "shanliuling",