dsh-image-gen 0.2.1 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +7 -6
- package/README.md +7 -6
- package/README.zh-CN.md +7 -6
- package/lib/client.js +186 -25
- package/lib/client.js.map +1 -1
- package/lib/index.js +274 -1
- package/lib/types/client/index.d.ts +22 -2
- package/lib/types/comfyui-workflow.d.ts +8 -0
- package/lib/types/comfyui.d.ts +15 -0
- package/lib/types/config.d.ts +14 -2
- package/lib/types/shared.d.ts +5 -1
- package/package.json +4 -2
- package/skills/install-dsh-image-gen/SKILL.md +22 -22
package/README.en.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
# 🎨 dsh-image-gen
|
|
8
8
|
|
|
9
|
-
**
|
|
9
|
+
**Complete conversational image capabilities for DeepSeek Harness: text-to-image, image-to-image, multi-image references, continuous editing, local ComfyUI, and gallery management.**
|
|
10
10
|
|
|
11
11
|
[](https://www.npmjs.com/package/dsh-image-gen)
|
|
12
12
|
[](https://github.com/deepseek-ai)
|
|
@@ -27,7 +27,7 @@ Install the image generation plugin by running: pnpm dsh plugin --profile web ad
|
|
|
27
27
|
|
|
28
28
|
<br />
|
|
29
29
|
|
|
30
|
-
<p align="center">After installation, enter your API
|
|
30
|
+
<p align="center">After installation, enter your API key or configure local ComfyUI in DSH Settings, then tell the Agent:</p>
|
|
31
31
|
|
|
32
32
|
```text
|
|
33
33
|
Draw a cyberpunk cat on a neon street in a rainy night.
|
|
@@ -56,7 +56,7 @@ DeepSeek Harness empowers agents to use tools for various tasks. This project ad
|
|
|
56
56
|
graph LR
|
|
57
57
|
A[User Prompt] --> B[DeepSeek Harness Agent]
|
|
58
58
|
B --> C[generate_image / edit_image]
|
|
59
|
-
C --> D[Gemini / OpenAI / Seedream / DashScope]
|
|
59
|
+
C --> D[Gemini / OpenAI / Seedream / DashScope / ComfyUI]
|
|
60
60
|
D --> E[Image Data]
|
|
61
61
|
E --> F[In-chat Conversation Stream]
|
|
62
62
|
```
|
|
@@ -91,12 +91,12 @@ pnpm dsh plugin --profile web add ./dsh-image-gen
|
|
|
91
91
|
|
|
92
92
|
</details>
|
|
93
93
|
|
|
94
|
-
### 2. Configure
|
|
94
|
+
### 2. Configure Provider & Workspace Settings
|
|
95
95
|
|
|
96
96
|
Open DSH Web (`http://localhost:3080`):
|
|
97
97
|
|
|
98
98
|
1. Navigate to **Settings → Plugins → Image generation**.
|
|
99
|
-
2. Select Provider,
|
|
99
|
+
2. Select a Provider. Enter an API key for cloud providers; for local ComfyUI, enter its URL and import an API Format Workflow JSON using `{{prompt}}` for the prompt and optional `{{seed}}` for the seed.
|
|
100
100
|
3. Optionally enable **Save to workspace** (enabled by default) and specify subfolder, then click **Save**.
|
|
101
101
|
|
|
102
102
|
<div align="center">
|
|
@@ -128,7 +128,7 @@ Click the **`[Gallery]`** Tab in the top navigation bar to browse and search all
|
|
|
128
128
|
- 💬 **In-Chat Image Generation**: Tell your Agent what you want to draw without switching tabs or tools.
|
|
129
129
|
- 🖼️ **Native Image Gallery**: Dedicated "Gallery" tab automatically collecting all generated images with keyword search, provider filtering, single-item deletion (with confirmation and persistent tombstones), and quick copy/download.
|
|
130
130
|
- 🔍 **Interactive Image Tools**: High-res fullscreen preview, one-click copy to clipboard, local download, and open in new tab.
|
|
131
|
-
- 🎨 **Multi-Provider Support**: Supports Google Gemini, OpenAI Images, OpenAI Compatible API, ByteDance Seedream / Volcengine Ark,
|
|
131
|
+
- 🎨 **Multi-Provider Support**: Supports Google Gemini, OpenAI Images, OpenAI Compatible API, ByteDance Seedream / Volcengine Ark, Aliyun DashScope (Wanx / Qwen-Image), and local ComfyUI workflows.
|
|
132
132
|
- 🔑 **BYOK (Bring Your Own Key)**: Uses your own API keys managed securely by DSH credentials service with write-only protection.
|
|
133
133
|
- 🖼️ **Durable Session Persistence**: Images integrate with DSH Attachment and conversation lifecycle, preserved across reloads.
|
|
134
134
|
- 💾 **Workspace File Output**: By default each generated image is also written as a file into the current session workspace; the tool result carries the absolute file path. Can be disabled or re-pointed in settings.
|
|
@@ -145,6 +145,7 @@ Click the **`[Gallery]`** Tab in the top navigation bar to browse and search all
|
|
|
145
145
|
| **OpenAI Compatible** | Custom | Custom Base URL |
|
|
146
146
|
| **ByteDance Seedream / Volcengine Ark** | `doubao-seedream-5-0-260128` | `https://ark.cn-beijing.volces.com/api/v3` |
|
|
147
147
|
| **Aliyun DashScope / Wanx** | `wanx2.1-t2i-turbo` | `https://dashscope.aliyuncs.com/api/v1` |
|
|
148
|
+
| **Local ComfyUI (text-to-image only)** | Imported API Format Workflow | `http://127.0.0.1:8188` |
|
|
148
149
|
|
|
149
150
|
---
|
|
150
151
|
|
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
# 🎨 dsh-image-gen
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
**为 DeepSeek Harness 提供完整的对话图像能力:文生图、图生图、多图参考、连续编辑、本地 ComfyUI 与画廊管理。**
|
|
10
10
|
|
|
11
11
|
[](https://www.npmjs.com/package/dsh-image-gen)
|
|
12
12
|
[](https://github.com/deepseek-ai)
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
|
|
29
29
|
<br />
|
|
30
30
|
|
|
31
|
-
<p align="center">安装完成后,在 DSH 设置中填入自己的 API Key,就可以直接对 Agent 说:</p>
|
|
31
|
+
<p align="center">安装完成后,在 DSH 设置中填入自己的 API Key 或配置本地 ComfyUI,就可以直接对 Agent 说:</p>
|
|
32
32
|
|
|
33
33
|
```text
|
|
34
34
|
帮我画一张雨夜霓虹街头的赛博朋克猫咪。
|
|
@@ -57,7 +57,7 @@ DeepSeek Harness 已经可以让 Agent 调用不同工具完成任务,本项
|
|
|
57
57
|
graph LR
|
|
58
58
|
A[用户 Prompt] --> B[DeepSeek Harness Agent]
|
|
59
59
|
B --> C[generate_image / edit_image]
|
|
60
|
-
C --> D[Gemini / OpenAI / Seedream / DashScope]
|
|
60
|
+
C --> D[Gemini / OpenAI / Seedream / DashScope / ComfyUI]
|
|
61
61
|
D --> E[图片数据]
|
|
62
62
|
E --> F[当前 Conversation 对话流]
|
|
63
63
|
```
|
|
@@ -92,12 +92,12 @@ pnpm dsh plugin --profile web add ./dsh-image-gen
|
|
|
92
92
|
|
|
93
93
|
</details>
|
|
94
94
|
|
|
95
|
-
### 2. 配置
|
|
95
|
+
### 2. 配置 Provider 与工作区设置
|
|
96
96
|
|
|
97
97
|
打开 DSH Web 页面(默认 `http://localhost:3080`):
|
|
98
98
|
|
|
99
99
|
1. 进入 **Settings → Plugins → Image generation**。
|
|
100
|
-
2. 选择 Provider
|
|
100
|
+
2. 选择 Provider;云端 Provider 填写 API Key,本地 ComfyUI 填写地址并导入 API Format Workflow JSON(提示词位置使用 `{{prompt}}`,种子可选用 `{{seed}}`)。
|
|
101
101
|
3. 可按需开启 **保存到工作区**(默认开启)并自定义子目录,点击 **保存** 即可。
|
|
102
102
|
|
|
103
103
|
<div align="center">
|
|
@@ -137,7 +137,7 @@ Agent 会调用 `edit_image`,复用当前会话中的图片继续修改。
|
|
|
137
137
|
- 💬 **原生对话生图与编辑**:直接在 DeepSeek Harness 对话中生成图片,也可以基于已有图片继续修改。
|
|
138
138
|
- 🔁 **连续多轮编辑**:支持复用当前会话中的上传图片、历史生成图和上一轮编辑结果继续迭代。
|
|
139
139
|
- 🖼️ **画廊与图片工具**:自动汇总历史图片,支持搜索、筛选、全屏预览、复制、下载与删除。
|
|
140
|
-
- 🎨 **多 Provider 支持**:兼容 Google Gemini、OpenAI Images / Compatible、Seedream、DashScope Qwen Image
|
|
140
|
+
- 🎨 **多 Provider 支持**:兼容 Google Gemini、OpenAI Images / Compatible、Seedream、DashScope Qwen Image,以及用户本地的 ComfyUI 工作流。
|
|
141
141
|
- 🔑 **BYOK + 原生设置**:API Key、Provider、模型和 Endpoint 都可以直接在 DSH 设置中配置。
|
|
142
142
|
- 💾 **会话与工作区保存**:图片接入 DSH Attachment / Conversation,并可自动保存到当前工作区。
|
|
143
143
|
|
|
@@ -152,6 +152,7 @@ Agent 会调用 `edit_image`,复用当前会话中的图片继续修改。
|
|
|
152
152
|
| **OpenAI Compatible** | 自定义 | 自定义 Base URL |
|
|
153
153
|
| **ByteDance Seedream / 火山方舟** | `doubao-seedream-5-0-260128` | `https://ark.cn-beijing.volces.com/api/v3` |
|
|
154
154
|
| **Aliyun DashScope / Qwen Image** | `qwen-image-3.0` | `https://dashscope.aliyuncs.com/api/v1` |
|
|
155
|
+
| **Local ComfyUI(仅文生图)** | 用户导入的 API Format Workflow | `http://127.0.0.1:8188` |
|
|
155
156
|
|
|
156
157
|
---
|
|
157
158
|
|
package/README.zh-CN.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
# 🎨 dsh-image-gen
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
**为 DeepSeek Harness 提供完整的对话图像能力:文生图、图生图、多图参考、连续编辑、本地 ComfyUI 与画廊管理。**
|
|
10
10
|
|
|
11
11
|
[](https://www.npmjs.com/package/dsh-image-gen)
|
|
12
12
|
[](https://github.com/deepseek-ai)
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
|
|
28
28
|
<br />
|
|
29
29
|
|
|
30
|
-
<p align="center">安装完成后,在 DSH 设置中填入自己的 API Key,就可以直接对 Agent 说:</p>
|
|
30
|
+
<p align="center">安装完成后,在 DSH 设置中填入自己的 API Key 或配置本地 ComfyUI,就可以直接对 Agent 说:</p>
|
|
31
31
|
|
|
32
32
|
```text
|
|
33
33
|
帮我画一张雨夜霓虹街头的赛博朋克猫咪。
|
|
@@ -53,7 +53,7 @@ DeepSeek Harness 已经可以让 Agent 调用不同工具完成任务,本项
|
|
|
53
53
|
graph LR
|
|
54
54
|
A[用户 Prompt] --> B[DeepSeek Harness Agent]
|
|
55
55
|
B --> C[generate_image 工具]
|
|
56
|
-
C --> D[Gemini / OpenAI / Seedream / DashScope]
|
|
56
|
+
C --> D[Gemini / OpenAI / Seedream / DashScope / ComfyUI]
|
|
57
57
|
D --> E[图片数据]
|
|
58
58
|
E --> F[当前 Conversation 对话流]
|
|
59
59
|
```
|
|
@@ -88,12 +88,12 @@ pnpm dsh plugin --profile web add ./dsh-image-gen
|
|
|
88
88
|
|
|
89
89
|
</details>
|
|
90
90
|
|
|
91
|
-
### 2. 配置
|
|
91
|
+
### 2. 配置 Provider 与工作区设置
|
|
92
92
|
|
|
93
93
|
打开 DSH Web 页面(默认 `http://localhost:3080`):
|
|
94
94
|
|
|
95
95
|
1. 进入 **Settings → Plugins → Image generation**。
|
|
96
|
-
2. 选择 Provider
|
|
96
|
+
2. 选择 Provider;云端 Provider 填写 API Key,本地 ComfyUI 填写地址并导入 API Format Workflow JSON(提示词位置使用 `{{prompt}}`,种子可选用 `{{seed}}`)。
|
|
97
97
|
3. 可按需开启 **保存到工作区**(默认开启)并自定义子目录,点击 **保存** 即可。
|
|
98
98
|
|
|
99
99
|
<div align="center">
|
|
@@ -125,7 +125,7 @@ pnpm dsh plugin --profile web add ./dsh-image-gen
|
|
|
125
125
|
- 💬 **对话中直接生图**:不需要切换到其他网站,也不需要手动复制 Prompt,直接告诉 Agent 你想画什么即可。
|
|
126
126
|
- 🖼️ **历史生图画廊**:顶栏自带「画廊」Tab,自动汇总所有历史生成的图片,支持关键词搜索、厂商筛选、单张删除(带防误触确认与持久化墓碑)与一键复制/下载。
|
|
127
127
|
- 🔍 **交互式图片工具**:支持点击全屏大图预览、一键复制图片到剪贴板、本地下载与新标签页打开。
|
|
128
|
-
- 🎨 **多 Provider 支持**:支持 Google Gemini、OpenAI Images、OpenAI Compatible API、ByteDance Seedream /
|
|
128
|
+
- 🎨 **多 Provider 支持**:支持 Google Gemini、OpenAI Images、OpenAI Compatible API、ByteDance Seedream / 火山方舟、阿里云 DashScope(通义万相 / Qwen-Image)以及用户本地的 ComfyUI 工作流。
|
|
129
129
|
- 🔑 **BYOK (自带 Key)**:插件使用你自己的 API Key。API Key 通过 DeepSeek Harness 的 `credentials` 服务管理,采用写保护隔离,不需要写进项目源码或配置文件,前端不存明文。
|
|
130
130
|
- 🖼️ **图片跟随会话保存**:生成结果会接入 DeepSeek Harness 的 Attachment / Conversation 体系,重新打开历史会话后,仍然可以看到之前生成的图片。
|
|
131
131
|
- 💾 **生成图片自动落盘到工作区**:默认在每次生成后把图片文件保存到当前会话工作区,工具结果会返回文件的绝对路径;可在设置中关闭或修改保存目录。
|
|
@@ -142,6 +142,7 @@ pnpm dsh plugin --profile web add ./dsh-image-gen
|
|
|
142
142
|
| **OpenAI Compatible** | 自定义 | 自定义 Base URL |
|
|
143
143
|
| **ByteDance Seedream / 火山方舟** | `doubao-seedream-5-0-260128` | `https://ark.cn-beijing.volces.com/api/v3` |
|
|
144
144
|
| **Aliyun DashScope / 通义万相** | `wanx2.1-t2i-turbo` | `https://dashscope.aliyuncs.com/api/v1` |
|
|
145
|
+
| **Local ComfyUI(仅文生图)** | 用户导入的 API Format Workflow | `http://127.0.0.1:8188` |
|
|
145
146
|
|
|
146
147
|
---
|
|
147
148
|
|
package/lib/client.js
CHANGED
|
@@ -17,19 +17,58 @@ window.__ModuleLoader__.load({
|
|
|
17
17
|
const DEFAULT_OPENAI_BASE_URL = "https://api.openai.com/v1";
|
|
18
18
|
const DEFAULT_SEEDREAM_BASE_URL = "https://ark.cn-beijing.volces.com/api/v3";
|
|
19
19
|
const DEFAULT_DASHSCOPE_ENDPOINT = "https://dashscope.aliyuncs.com/api/v1";
|
|
20
|
+
const DEFAULT_COMFYUI_BASE_URL = "http://127.0.0.1:8188";
|
|
21
|
+
const DEFAULT_COMFYUI_TIMEOUT_MS = 3e5;
|
|
20
22
|
const DEFAULT_MODELS = {
|
|
21
23
|
google: "gemini-3.1-flash-image",
|
|
22
24
|
openai: "gpt-image-2",
|
|
23
25
|
seedream: "doubao-seedream-5-0-260128",
|
|
24
|
-
dashscope: "qwen-image-3.0"
|
|
26
|
+
dashscope: "qwen-image-3.0",
|
|
27
|
+
comfyui: "API workflow"
|
|
25
28
|
};
|
|
26
29
|
const DEFAULT_BASE_URLS = {
|
|
27
30
|
google: DEFAULT_GOOGLE_ENDPOINT,
|
|
28
31
|
openai: DEFAULT_OPENAI_BASE_URL,
|
|
29
32
|
seedream: DEFAULT_SEEDREAM_BASE_URL,
|
|
30
|
-
dashscope: DEFAULT_DASHSCOPE_ENDPOINT
|
|
33
|
+
dashscope: DEFAULT_DASHSCOPE_ENDPOINT,
|
|
34
|
+
comfyui: DEFAULT_COMFYUI_BASE_URL
|
|
31
35
|
};
|
|
32
36
|
//#endregion
|
|
37
|
+
//#region lib/types/comfyui-workflow.js
|
|
38
|
+
/** Pure ComfyUI API-workflow validation and placeholder injection. */
|
|
39
|
+
const COMFYUI_PROMPT_PLACEHOLDER = "{{prompt}}";
|
|
40
|
+
/** Validate imported JSON without exposing workflow graph details to callers. */
|
|
41
|
+
function validateComfyUIWorkflowJson(workflowJson) {
|
|
42
|
+
parseWorkflow(workflowJson);
|
|
43
|
+
}
|
|
44
|
+
function parseWorkflow(workflowJson) {
|
|
45
|
+
if (workflowJson.trim().length === 0) throw new Error("Import a ComfyUI API workflow JSON file in Settings before generating");
|
|
46
|
+
if (new TextEncoder().encode(workflowJson).byteLength > 5242880) throw new Error("ComfyUI workflow file must be no larger than 5 MB");
|
|
47
|
+
let value;
|
|
48
|
+
try {
|
|
49
|
+
value = JSON.parse(workflowJson);
|
|
50
|
+
} catch {
|
|
51
|
+
throw new Error("ComfyUI workflow file is not valid JSON");
|
|
52
|
+
}
|
|
53
|
+
if (!isRecord(value) || Object.keys(value).length === 0) throw new Error("ComfyUI workflow must be a non-empty API-format JSON object");
|
|
54
|
+
if (!Object.values(value).some((node) => {
|
|
55
|
+
const inputs = record(node)?.inputs;
|
|
56
|
+
return inputs !== void 0 && containsPromptPlaceholder(inputs);
|
|
57
|
+
})) throw new Error(`ComfyUI workflow must contain ${COMFYUI_PROMPT_PLACEHOLDER} in a text input`);
|
|
58
|
+
return value;
|
|
59
|
+
}
|
|
60
|
+
function containsPromptPlaceholder(value) {
|
|
61
|
+
if (typeof value === "string") return value.includes(COMFYUI_PROMPT_PLACEHOLDER);
|
|
62
|
+
if (Array.isArray(value)) return value.some(containsPromptPlaceholder);
|
|
63
|
+
return isRecord(value) && Object.values(value).some(containsPromptPlaceholder);
|
|
64
|
+
}
|
|
65
|
+
function isRecord(value) {
|
|
66
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
67
|
+
}
|
|
68
|
+
function record(value) {
|
|
69
|
+
return isRecord(value) ? value : void 0;
|
|
70
|
+
}
|
|
71
|
+
//#endregion
|
|
33
72
|
//#region lib/types/client/gallery-store.js
|
|
34
73
|
const DB_NAME = "dsh_image_gen_db";
|
|
35
74
|
const DB_VERSION = 2;
|
|
@@ -906,6 +945,7 @@ window.__ModuleLoader__.load({
|
|
|
906
945
|
providerOpenAI: "OpenAI / 中转站",
|
|
907
946
|
providerSeedream: "字节 Seedream",
|
|
908
947
|
providerDashScope: "阿里 DashScope (通义万相 / Qwen)",
|
|
948
|
+
providerComfyUI: "本地 ComfyUI",
|
|
909
949
|
apiKeyLabel: "{provider} API Key",
|
|
910
950
|
apiKeyPlaceholder: "留空即可保留已配置的 Key",
|
|
911
951
|
apiKeyHint: "安全保存为 {key};页面不会读回明文。",
|
|
@@ -916,7 +956,17 @@ window.__ModuleLoader__.load({
|
|
|
916
956
|
endpointHintOpenAI: "中转站请填其 OpenAI 兼容的 /v1 地址。",
|
|
917
957
|
endpointHintSeedream: "火山方舟兼容的 /api/v3 地址。",
|
|
918
958
|
endpointHintDashScope: "阿里云百炼 DashScope 官方接口地址。",
|
|
959
|
+
endpointHintComfyUI: "正在运行且 DSH Host 可以访问的 ComfyUI 地址,默认使用本机 8188 端口。",
|
|
919
960
|
model: "模型",
|
|
961
|
+
workflow: "API Workflow",
|
|
962
|
+
workflowImport: "导入 JSON 文件",
|
|
963
|
+
workflowReplace: "替换 JSON 文件",
|
|
964
|
+
workflowMissing: "尚未导入工作流",
|
|
965
|
+
workflowImported: "已导入 {name}",
|
|
966
|
+
workflowHint: "请从 ComfyUI 导出 API Format JSON,并在提示词输入位置写入 {{prompt}};可用 {{seed}} 作为随机种子。",
|
|
967
|
+
workflowTooLarge: "工作流文件不能超过 5 MB。",
|
|
968
|
+
timeout: "生成超时(秒)",
|
|
969
|
+
timeoutHint: "包括提交、等待和下载图片;默认 300 秒。",
|
|
920
970
|
saveToWorkspace: "保存到工作区",
|
|
921
971
|
saveToWorkspaceHint: "每次生成后,把图片文件保存到当前会话工作区。",
|
|
922
972
|
folder: "工作区文件夹",
|
|
@@ -946,6 +996,7 @@ window.__ModuleLoader__.load({
|
|
|
946
996
|
providerOpenAI: "OpenAI / Relay",
|
|
947
997
|
providerSeedream: "ByteDance Seedream",
|
|
948
998
|
providerDashScope: "Aliyun DashScope (Wanx / Qwen)",
|
|
999
|
+
providerComfyUI: "Local ComfyUI",
|
|
949
1000
|
apiKeyLabel: "{provider} API Key",
|
|
950
1001
|
apiKeyPlaceholder: "Leave empty to keep configured key",
|
|
951
1002
|
apiKeyHint: "Securely saved as {key}; never read back in plaintext.",
|
|
@@ -956,7 +1007,17 @@ window.__ModuleLoader__.load({
|
|
|
956
1007
|
endpointHintOpenAI: "OpenAI-compatible /v1 base URL for relays.",
|
|
957
1008
|
endpointHintSeedream: "Volcengine Ark compatible /api/v3 base URL.",
|
|
958
1009
|
endpointHintDashScope: "Official Aliyun DashScope endpoint.",
|
|
1010
|
+
endpointHintComfyUI: "A running ComfyUI server reachable by the DSH Host; the default points to port 8188 on this computer.",
|
|
959
1011
|
model: "Model",
|
|
1012
|
+
workflow: "API Workflow",
|
|
1013
|
+
workflowImport: "Import JSON file",
|
|
1014
|
+
workflowReplace: "Replace JSON file",
|
|
1015
|
+
workflowMissing: "No workflow imported",
|
|
1016
|
+
workflowImported: "Imported {name}",
|
|
1017
|
+
workflowHint: "Export an API Format JSON from ComfyUI and place {{prompt}} in its prompt input. {{seed}} is available for a random seed.",
|
|
1018
|
+
workflowTooLarge: "Workflow files must be no larger than 5 MB.",
|
|
1019
|
+
timeout: "Generation timeout (seconds)",
|
|
1020
|
+
timeoutHint: "Covers submission, waiting, and image download; defaults to 300 seconds.",
|
|
960
1021
|
saveToWorkspace: "Save to workspace",
|
|
961
1022
|
saveToWorkspaceHint: "Write each generated image as a file into the session workspace.",
|
|
962
1023
|
folder: "Workspace folder",
|
|
@@ -996,6 +1057,12 @@ window.__ModuleLoader__.load({
|
|
|
996
1057
|
.dsh-ig-input{box-sizing:border-box;width:100%;padding:8px 12px;font-size:13px;border:1px solid var(--dsw-alias-border-l2,#d7dbe0);border-radius:8px;background:var(--dsw-alias-bg-layer-3,transparent);color:inherit;outline:none;transition:border-color .15s}
|
|
997
1058
|
.dsh-ig-input:focus{border-color:var(--dsw-alias-brand-primary,#4c78ff)}
|
|
998
1059
|
.dsh-ig-input-group{display:flex;gap:8px;align-items:center}
|
|
1060
|
+
.dsh-ig-file-row{display:flex;align-items:center;gap:10px;min-width:0}
|
|
1061
|
+
.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%)}
|
|
1062
|
+
.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}
|
|
1063
|
+
.dsh-ig-file-button:hover{background:var(--dsw-alias-bg-layer-2,#edf0f3);border-color:var(--dsw-alias-label-dimmed,#9ca3af)}
|
|
1064
|
+
.dsh-ig-file-button:focus-within{outline:2px solid var(--dsw-alias-brand-primary,#4c78ff);outline-offset:2px}
|
|
1065
|
+
.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}
|
|
999
1066
|
.dsh-ig-btn-reset{appearance: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:inherit;font-size:13px;cursor:pointer;white-space:nowrap;transition:background .15s,border-color .15s}
|
|
1000
1067
|
.dsh-ig-btn-reset:hover{background:var(--dsw-alias-bg-layer-2,#edf0f3);border-color:var(--dsw-alias-label-dimmed,#9ca3af)}
|
|
1001
1068
|
.dsh-ig-hint,.dsh-ig-status{margin:0;color:var(--dsw-alias-label-tertiary,#7b818b);font-size:12px;line-height:1.4}
|
|
@@ -1078,16 +1145,16 @@ window.__ModuleLoader__.load({
|
|
|
1078
1145
|
/** Required browser services. */
|
|
1079
1146
|
const inject = [
|
|
1080
1147
|
"slots",
|
|
1081
|
-
"connection",
|
|
1082
1148
|
"remote",
|
|
1149
|
+
"remote.credentials",
|
|
1083
1150
|
"settingsScope",
|
|
1084
1151
|
"locale"
|
|
1085
1152
|
];
|
|
1086
1153
|
/** Mount the settings card, generated-image card, and native conversation gallery view. */
|
|
1087
1154
|
function apply(ctx) {
|
|
1088
|
-
const { api } = ctx.get("connection");
|
|
1089
1155
|
const scope = ctx.settingsScope.bind({ namespace: IMAGE_GENERATION_NAMESPACE });
|
|
1090
1156
|
const locale = ctx.get("locale");
|
|
1157
|
+
const credentials = ctx.remote.credentials;
|
|
1091
1158
|
ctx.effect(() => {
|
|
1092
1159
|
const style = document.createElement("style");
|
|
1093
1160
|
style.dataset.plugin = "dsh-image-gen";
|
|
@@ -1103,7 +1170,7 @@ window.__ModuleLoader__.load({
|
|
|
1103
1170
|
key: IMAGE_GENERATION_NAMESPACE,
|
|
1104
1171
|
inject: () => ({
|
|
1105
1172
|
scope,
|
|
1106
|
-
credentials
|
|
1173
|
+
credentials,
|
|
1107
1174
|
locale
|
|
1108
1175
|
})
|
|
1109
1176
|
}, ImageGenerationSettingsCard));
|
|
@@ -1135,6 +1202,9 @@ window.__ModuleLoader__.load({
|
|
|
1135
1202
|
const [provider, setProvider] = (0, react.useState)("google");
|
|
1136
1203
|
const [model, setModel] = (0, react.useState)("");
|
|
1137
1204
|
const [baseURL, setBaseURL] = (0, react.useState)("");
|
|
1205
|
+
const [workflowJson, setWorkflowJson] = (0, react.useState)("");
|
|
1206
|
+
const [workflowName, setWorkflowName] = (0, react.useState)("");
|
|
1207
|
+
const [timeoutSeconds, setTimeoutSeconds] = (0, react.useState)(DEFAULT_COMFYUI_TIMEOUT_MS / 1e3);
|
|
1138
1208
|
const [saveToWorkspace, setSaveToWorkspace] = (0, react.useState)(true);
|
|
1139
1209
|
const [workspaceFolder, setWorkspaceFolder] = (0, react.useState)("dsh-image-gen");
|
|
1140
1210
|
const [key, setKey] = (0, react.useState)("");
|
|
@@ -1158,7 +1228,8 @@ window.__ModuleLoader__.load({
|
|
|
1158
1228
|
google: t("providerGoogle"),
|
|
1159
1229
|
openai: t("providerOpenAI"),
|
|
1160
1230
|
seedream: t("providerSeedream"),
|
|
1161
|
-
dashscope: t("providerDashScope")
|
|
1231
|
+
dashscope: t("providerDashScope"),
|
|
1232
|
+
comfyui: t("providerComfyUI")
|
|
1162
1233
|
};
|
|
1163
1234
|
(0, react.useEffect)(() => {
|
|
1164
1235
|
const value = snapshot.value;
|
|
@@ -1166,13 +1237,21 @@ window.__ModuleLoader__.load({
|
|
|
1166
1237
|
setProvider(next);
|
|
1167
1238
|
setModel(modelOf(next, value));
|
|
1168
1239
|
setBaseURL(baseURLOf(next, value));
|
|
1240
|
+
setWorkflowJson(value?.comfyuiWorkflowJson ?? "");
|
|
1241
|
+
setWorkflowName(value?.comfyuiWorkflowName ?? "");
|
|
1242
|
+
setTimeoutSeconds(Math.max(1, Math.round((value?.comfyuiTimeoutMs ?? 3e5) / 1e3)));
|
|
1169
1243
|
setSaveToWorkspace(value?.saveToWorkspace ?? true);
|
|
1170
1244
|
setWorkspaceFolder(value?.workspaceFolder ?? "dsh-image-gen");
|
|
1171
1245
|
}, [snapshot]);
|
|
1172
1246
|
(0, react.useEffect)(() => {
|
|
1247
|
+
const keyRef = KEY_REF[provider];
|
|
1248
|
+
if (keyRef === void 0) {
|
|
1249
|
+
setConfigured(void 0);
|
|
1250
|
+
return;
|
|
1251
|
+
}
|
|
1173
1252
|
let active = true;
|
|
1174
|
-
props.credentials.describe(
|
|
1175
|
-
if (active) setConfigured(response.
|
|
1253
|
+
props.credentials.describe([keyRef]).then((response) => {
|
|
1254
|
+
if (active) setConfigured(response.ok ? response.value?.[keyRef]?.configured ?? false : void 0);
|
|
1176
1255
|
}).catch(() => {
|
|
1177
1256
|
if (active) setConfigured(void 0);
|
|
1178
1257
|
});
|
|
@@ -1186,16 +1265,23 @@ window.__ModuleLoader__.load({
|
|
|
1186
1265
|
setMessage("");
|
|
1187
1266
|
try {
|
|
1188
1267
|
await props.scope.set("provider", provider);
|
|
1189
|
-
|
|
1190
|
-
|
|
1268
|
+
if (provider === "comfyui") {
|
|
1269
|
+
validateComfyUIWorkflowJson(workflowJson);
|
|
1270
|
+
await props.scope.set("comfyuiBaseURL", baseURL);
|
|
1271
|
+
await props.scope.set("comfyuiWorkflowJson", workflowJson);
|
|
1272
|
+
await props.scope.set("comfyuiWorkflowName", workflowName);
|
|
1273
|
+
await props.scope.set("comfyuiTimeoutMs", Math.max(1, Math.round(timeoutSeconds)) * 1e3);
|
|
1274
|
+
} else {
|
|
1275
|
+
await props.scope.set(provider === "google" ? "googleModel" : provider === "openai" ? "openaiModel" : provider === "seedream" ? "seedreamModel" : "dashscopeModel", model);
|
|
1276
|
+
await props.scope.set(provider === "google" ? "googleEndpoint" : provider === "openai" ? "openaiBaseURL" : provider === "seedream" ? "seedreamBaseURL" : "dashscopeEndpoint", baseURL);
|
|
1277
|
+
}
|
|
1191
1278
|
await props.scope.set("saveToWorkspace", saveToWorkspace);
|
|
1192
1279
|
await props.scope.set("workspaceFolder", workspaceFolder.trim());
|
|
1193
1280
|
if (key.trim().length > 0) {
|
|
1194
|
-
const
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
if (!response.result.ok) throw new Error(response.result.error.message);
|
|
1281
|
+
const keyRef = KEY_REF[provider];
|
|
1282
|
+
if (keyRef === void 0) throw new Error("ComfyUI does not use an API key in this version");
|
|
1283
|
+
const response = await props.credentials.set(keyRef, key.trim());
|
|
1284
|
+
if (!response.ok) throw new Error(response.error?.message ?? "Failed to save API key");
|
|
1199
1285
|
setKey("");
|
|
1200
1286
|
setConfigured(true);
|
|
1201
1287
|
}
|
|
@@ -1207,6 +1293,23 @@ window.__ModuleLoader__.load({
|
|
|
1207
1293
|
}
|
|
1208
1294
|
};
|
|
1209
1295
|
const keyStatus = configured === void 0 ? t("checkingKey") : configured ? t("keyConfigured") : t("keyNotConfigured");
|
|
1296
|
+
const workflowStatus = workflowName.length > 0 ? t("workflowImported", { name: workflowName }) : t("workflowMissing");
|
|
1297
|
+
const importWorkflow = async (event) => {
|
|
1298
|
+
const file = event.target.files?.[0];
|
|
1299
|
+
event.target.value = "";
|
|
1300
|
+
if (file === void 0) return;
|
|
1301
|
+
setMessage("");
|
|
1302
|
+
try {
|
|
1303
|
+
if (file.size > 5242880) throw new Error(t("workflowTooLarge"));
|
|
1304
|
+
const json = await file.text();
|
|
1305
|
+
validateComfyUIWorkflowJson(json);
|
|
1306
|
+
setWorkflowJson(json);
|
|
1307
|
+
setWorkflowName(file.name);
|
|
1308
|
+
setMessage(t("workflowImported", { name: file.name }));
|
|
1309
|
+
} catch (cause) {
|
|
1310
|
+
setMessage(cause instanceof Error ? cause.message : String(cause));
|
|
1311
|
+
}
|
|
1312
|
+
};
|
|
1210
1313
|
return (0, react_jsx_runtime.jsxs)("li", {
|
|
1211
1314
|
className: `dsh-ig-card ${open ? "dsh-ig-card-open" : ""}`,
|
|
1212
1315
|
children: [(0, react_jsx_runtime.jsxs)("button", {
|
|
@@ -1279,6 +1382,10 @@ window.__ModuleLoader__.load({
|
|
|
1279
1382
|
(0, react_jsx_runtime.jsx)("option", {
|
|
1280
1383
|
value: "dashscope",
|
|
1281
1384
|
children: t("providerDashScope")
|
|
1385
|
+
}),
|
|
1386
|
+
(0, react_jsx_runtime.jsx)("option", {
|
|
1387
|
+
value: "comfyui",
|
|
1388
|
+
children: t("providerComfyUI")
|
|
1282
1389
|
})
|
|
1283
1390
|
]
|
|
1284
1391
|
}),
|
|
@@ -1288,7 +1395,7 @@ window.__ModuleLoader__.load({
|
|
|
1288
1395
|
})
|
|
1289
1396
|
]
|
|
1290
1397
|
}),
|
|
1291
|
-
(0, react_jsx_runtime.jsxs)("label", {
|
|
1398
|
+
provider !== "comfyui" ? (0, react_jsx_runtime.jsxs)("label", {
|
|
1292
1399
|
className: "dsh-ig-field",
|
|
1293
1400
|
children: [
|
|
1294
1401
|
(0, react_jsx_runtime.jsx)("span", {
|
|
@@ -1307,10 +1414,10 @@ window.__ModuleLoader__.load({
|
|
|
1307
1414
|
}),
|
|
1308
1415
|
(0, react_jsx_runtime.jsx)("span", {
|
|
1309
1416
|
className: "dsh-ig-hint",
|
|
1310
|
-
children: t("apiKeyHint", { key: KEY_REF[provider] })
|
|
1417
|
+
children: t("apiKeyHint", { key: KEY_REF[provider] ?? "" })
|
|
1311
1418
|
})
|
|
1312
1419
|
]
|
|
1313
|
-
}),
|
|
1420
|
+
}) : null,
|
|
1314
1421
|
(0, react_jsx_runtime.jsxs)("label", {
|
|
1315
1422
|
className: "dsh-ig-field",
|
|
1316
1423
|
children: [
|
|
@@ -1340,11 +1447,11 @@ window.__ModuleLoader__.load({
|
|
|
1340
1447
|
}),
|
|
1341
1448
|
(0, react_jsx_runtime.jsx)("span", {
|
|
1342
1449
|
className: "dsh-ig-hint",
|
|
1343
|
-
children: provider === "google" ? t("endpointHintGoogle") : provider === "openai" ? t("endpointHintOpenAI") : provider === "seedream" ? t("endpointHintSeedream") : t("endpointHintDashScope")
|
|
1450
|
+
children: provider === "google" ? t("endpointHintGoogle") : provider === "openai" ? t("endpointHintOpenAI") : provider === "seedream" ? t("endpointHintSeedream") : provider === "dashscope" ? t("endpointHintDashScope") : t("endpointHintComfyUI")
|
|
1344
1451
|
})
|
|
1345
1452
|
]
|
|
1346
1453
|
}),
|
|
1347
|
-
(0, react_jsx_runtime.jsxs)("label", {
|
|
1454
|
+
provider !== "comfyui" ? (0, react_jsx_runtime.jsxs)("label", {
|
|
1348
1455
|
className: "dsh-ig-field",
|
|
1349
1456
|
children: [(0, react_jsx_runtime.jsx)("span", {
|
|
1350
1457
|
className: "dsh-ig-label",
|
|
@@ -1357,7 +1464,61 @@ window.__ModuleLoader__.load({
|
|
|
1357
1464
|
},
|
|
1358
1465
|
required: true
|
|
1359
1466
|
})]
|
|
1360
|
-
}),
|
|
1467
|
+
}) : (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsxs)("div", {
|
|
1468
|
+
className: "dsh-ig-field",
|
|
1469
|
+
children: [
|
|
1470
|
+
(0, react_jsx_runtime.jsx)("span", {
|
|
1471
|
+
className: "dsh-ig-label",
|
|
1472
|
+
children: t("workflow")
|
|
1473
|
+
}),
|
|
1474
|
+
(0, react_jsx_runtime.jsxs)("div", {
|
|
1475
|
+
className: "dsh-ig-file-row",
|
|
1476
|
+
children: [(0, react_jsx_runtime.jsxs)("label", {
|
|
1477
|
+
className: "dsh-ig-file-button",
|
|
1478
|
+
children: [(0, react_jsx_runtime.jsx)("input", {
|
|
1479
|
+
className: "dsh-ig-file-input",
|
|
1480
|
+
type: "file",
|
|
1481
|
+
accept: ".json,application/json",
|
|
1482
|
+
onChange: (event) => {
|
|
1483
|
+
importWorkflow(event);
|
|
1484
|
+
}
|
|
1485
|
+
}), workflowName.length > 0 ? t("workflowReplace") : t("workflowImport")]
|
|
1486
|
+
}), (0, react_jsx_runtime.jsx)("span", {
|
|
1487
|
+
className: "dsh-ig-file-name",
|
|
1488
|
+
title: workflowName,
|
|
1489
|
+
children: workflowName || t("workflowMissing")
|
|
1490
|
+
})]
|
|
1491
|
+
}),
|
|
1492
|
+
(0, react_jsx_runtime.jsx)("span", {
|
|
1493
|
+
className: "dsh-ig-hint",
|
|
1494
|
+
children: t("workflowHint")
|
|
1495
|
+
})
|
|
1496
|
+
]
|
|
1497
|
+
}), (0, react_jsx_runtime.jsxs)("label", {
|
|
1498
|
+
className: "dsh-ig-field",
|
|
1499
|
+
children: [
|
|
1500
|
+
(0, react_jsx_runtime.jsx)("span", {
|
|
1501
|
+
className: "dsh-ig-label",
|
|
1502
|
+
children: t("timeout")
|
|
1503
|
+
}),
|
|
1504
|
+
(0, react_jsx_runtime.jsx)("input", {
|
|
1505
|
+
className: "dsh-ig-input",
|
|
1506
|
+
type: "number",
|
|
1507
|
+
min: "1",
|
|
1508
|
+
max: "3600",
|
|
1509
|
+
step: "1",
|
|
1510
|
+
value: timeoutSeconds,
|
|
1511
|
+
onChange: (event) => {
|
|
1512
|
+
setTimeoutSeconds(Number(event.target.value));
|
|
1513
|
+
},
|
|
1514
|
+
required: true
|
|
1515
|
+
}),
|
|
1516
|
+
(0, react_jsx_runtime.jsx)("span", {
|
|
1517
|
+
className: "dsh-ig-hint",
|
|
1518
|
+
children: t("timeoutHint")
|
|
1519
|
+
})
|
|
1520
|
+
]
|
|
1521
|
+
})] }),
|
|
1361
1522
|
(0, react_jsx_runtime.jsxs)("div", {
|
|
1362
1523
|
className: "dsh-ig-field",
|
|
1363
1524
|
children: [(0, react_jsx_runtime.jsxs)("label", {
|
|
@@ -1403,11 +1564,11 @@ window.__ModuleLoader__.load({
|
|
|
1403
1564
|
children: [(0, react_jsx_runtime.jsx)("p", {
|
|
1404
1565
|
className: "dsh-ig-status",
|
|
1405
1566
|
role: "status",
|
|
1406
|
-
children: message || keyStatus
|
|
1567
|
+
children: message || (provider === "comfyui" ? workflowStatus : keyStatus)
|
|
1407
1568
|
}), (0, react_jsx_runtime.jsx)("button", {
|
|
1408
1569
|
className: "dsh-ig-save",
|
|
1409
1570
|
type: "submit",
|
|
1410
|
-
disabled: saving || !snapshot.writable,
|
|
1571
|
+
disabled: saving || !snapshot.writable || provider === "comfyui" && workflowJson.length === 0,
|
|
1411
1572
|
children: saving ? t("saving") : t("save")
|
|
1412
1573
|
})]
|
|
1413
1574
|
})
|
|
@@ -1656,11 +1817,11 @@ window.__ModuleLoader__.load({
|
|
|
1656
1817
|
});
|
|
1657
1818
|
}
|
|
1658
1819
|
function modelOf(provider, value) {
|
|
1659
|
-
const stored = provider === "google" ? value?.googleModel : provider === "openai" ? value?.openaiModel : provider === "seedream" ? value?.seedreamModel : value?.dashscopeModel;
|
|
1820
|
+
const stored = provider === "google" ? value?.googleModel : provider === "openai" ? value?.openaiModel : provider === "seedream" ? value?.seedreamModel : provider === "dashscope" ? value?.dashscopeModel : value?.comfyuiWorkflowName;
|
|
1660
1821
|
return typeof stored === "string" && stored.length > 0 ? stored : DEFAULT_MODELS[provider];
|
|
1661
1822
|
}
|
|
1662
1823
|
function baseURLOf(provider, value) {
|
|
1663
|
-
const stored = provider === "google" ? value?.googleEndpoint : provider === "openai" ? value?.openaiBaseURL : provider === "seedream" ? value?.seedreamBaseURL : value?.dashscopeEndpoint;
|
|
1824
|
+
const stored = provider === "google" ? value?.googleEndpoint : provider === "openai" ? value?.openaiBaseURL : provider === "seedream" ? value?.seedreamBaseURL : provider === "dashscope" ? value?.dashscopeEndpoint : value?.comfyuiBaseURL;
|
|
1664
1825
|
return typeof stored === "string" && stored.length > 0 ? stored : DEFAULT_BASE_URLS[provider];
|
|
1665
1826
|
}
|
|
1666
1827
|
function imageRef(block) {
|