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 +3 -2
- package/README.md +3 -2
- package/lib/client.js +62 -29
- package/lib/index.js +222 -54
- package/lib/types/client/plugin-i18n.d.ts +4 -2
- package/lib/types/client/plugin-style.d.ts +1 -1
- package/lib/types/client/provider-pill.d.ts +13 -0
- package/lib/types/comfyui-workflow.d.ts +35 -7
- package/lib/types/comfyui.d.ts +9 -2
- package/lib/types/shared.d.ts +6 -0
- package/lib/types/subscription/vendors/antigravity.d.ts +17 -0
- package/package.json +1 -1
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** | ✅ | ✅ | ✅
|
|
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,
|
|
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
|
-
|
|
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}}
|
|
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}}
|
|
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
|
-
|
|
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
|
|
130914
|
-
bottom:
|
|
130940
|
+
left,
|
|
130941
|
+
bottom: origin.bottom - rect.top + 6
|
|
130915
130942
|
};
|
|
130916
130943
|
return {
|
|
130917
|
-
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-
|
|
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.
|
|
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
|
|
7268
|
-
* workflows that embed them in longer text keep working
|
|
7269
|
-
*
|
|
7270
|
-
*
|
|
7271
|
-
*
|
|
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(),
|
|
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 (
|
|
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
|
-
|
|
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
|
-
/**
|
|
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
|
|
7398
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
32221
|
-
data:
|
|
32222
|
-
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}}
|
|
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}}
|
|
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
|
|
11
|
-
* workflows that embed them in longer text keep working
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
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,
|
|
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 {};
|
package/lib/types/comfyui.d.ts
CHANGED
|
@@ -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
|
-
/**
|
|
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
|
-
|
|
35
|
+
sourceImages: readonly ComfyUISourceImage[];
|
|
30
36
|
maxBytes: number;
|
|
37
|
+
size?: ComfyUISize;
|
|
31
38
|
}): Promise<GeneratedComfyUIImage>;
|
|
32
39
|
export {};
|
package/lib/types/shared.d.ts
CHANGED
|
@@ -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