dsh-image-gen 0.2.4 → 0.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +2 -2
- package/README.md +2 -2
- package/README.zh-CN.md +2 -2
- package/lib/client.js +318 -114
- package/lib/client.js.map +1 -1
- package/lib/index.js +238 -41
- package/lib/types/client/gallery-store.d.ts +2 -0
- package/lib/types/client/gallery-view.d.ts +2 -0
- package/lib/types/client/image-result-node.d.ts +18 -1
- package/lib/types/client/index.d.ts +3 -1
- package/lib/types/comfyui-workflow.d.ts +13 -2
- package/lib/types/comfyui.d.ts +22 -5
- package/lib/types/config.d.ts +15 -6
- package/lib/types/shared.d.ts +31 -0
- package/package.json +3 -2
package/lib/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { credentialRef } from "@deepseek-ai/dsh-credentials";
|
|
2
|
-
import
|
|
2
|
+
import * as dshSettings from "@deepseek-ai/dsh-settings";
|
|
3
3
|
import { defineTool } from "@deepseek-ai/dsh-tools";
|
|
4
4
|
import z from "@deepseek-ai/schemastery";
|
|
5
5
|
import { mkdir, readFile, realpath, rename, stat, unlink, writeFile } from "node:fs/promises";
|
|
@@ -26,11 +26,58 @@ const DEFAULT_SEEDREAM_BASE_URL = "https://ark.cn-beijing.volces.com/api/v3";
|
|
|
26
26
|
const DEFAULT_DASHSCOPE_ENDPOINT = "https://dashscope.aliyuncs.com/api/v1";
|
|
27
27
|
const DEFAULT_COMFYUI_BASE_URL = "http://127.0.0.1:8188";
|
|
28
28
|
const DEFAULT_COMFYUI_TIMEOUT_MS = 3e5;
|
|
29
|
+
const DEFAULT_COMFYUI_WORKFLOW_LABEL = "API workflow";
|
|
29
30
|
/** Default model names. */
|
|
30
31
|
const DEFAULT_GOOGLE_MODEL = "gemini-3.1-flash-image";
|
|
31
32
|
const DEFAULT_OPENAI_MODEL = "gpt-image-2";
|
|
32
33
|
const DEFAULT_SEEDREAM_MODEL = "doubao-seedream-5-0-260128";
|
|
33
34
|
const DEFAULT_DASHSCOPE_MODEL = "qwen-image-3.0";
|
|
35
|
+
/** Named workflows, falling back to the legacy single-workflow fields when the list is empty. */
|
|
36
|
+
function resolveComfyUIWorkflows(source) {
|
|
37
|
+
const named = [];
|
|
38
|
+
for (const entry of source.comfyuiWorkflows ?? []) {
|
|
39
|
+
const name = typeof entry?.name === "string" ? entry.name.trim() : "";
|
|
40
|
+
const json = typeof entry?.json === "string" ? entry.json : "";
|
|
41
|
+
if (name.length > 0 && json.trim().length > 0) {
|
|
42
|
+
const presetPrompt = typeof entry.presetPrompt === "string" ? entry.presetPrompt.trim() : "";
|
|
43
|
+
named.push(presetPrompt.length > 0 ? {
|
|
44
|
+
name,
|
|
45
|
+
json,
|
|
46
|
+
presetPrompt
|
|
47
|
+
} : {
|
|
48
|
+
name,
|
|
49
|
+
json
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
if (named.length > 0) return named;
|
|
54
|
+
const legacyJson = typeof source.comfyuiWorkflowJson === "string" ? source.comfyuiWorkflowJson : "";
|
|
55
|
+
if (legacyJson.trim().length === 0) return [];
|
|
56
|
+
const legacyName = typeof source.comfyuiWorkflowName === "string" ? source.comfyuiWorkflowName.trim() : "";
|
|
57
|
+
return [{
|
|
58
|
+
name: legacyName.length > 0 ? legacyName : DEFAULT_COMFYUI_WORKFLOW_LABEL,
|
|
59
|
+
json: legacyJson
|
|
60
|
+
}];
|
|
61
|
+
}
|
|
62
|
+
/** The workflow ComfyUI calls use by default: the configured active name, else the first entry. */
|
|
63
|
+
function activeComfyUIWorkflow(source) {
|
|
64
|
+
const workflows = resolveComfyUIWorkflows(source);
|
|
65
|
+
if (workflows.length === 0) return void 0;
|
|
66
|
+
const activeName = typeof source.comfyuiActiveWorkflow === "string" ? source.comfyuiActiveWorkflow.trim() : "";
|
|
67
|
+
return workflows.find((workflow) => workflow.name === activeName) ?? workflows[0];
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Combine a workflow's preset with the user prompt: preset first, user second,
|
|
71
|
+
* joined by one comma — never doubled when the preset already ends in a
|
|
72
|
+
* separator, and reduced to the non-empty side when the other is blank.
|
|
73
|
+
*/
|
|
74
|
+
function mergeComfyUIPrompt(preset, user) {
|
|
75
|
+
const presetText = typeof preset === "string" ? preset.trim().replace(/[,;\s]+$/, "") : "";
|
|
76
|
+
const userText = user.trim();
|
|
77
|
+
if (presetText.length === 0) return userText;
|
|
78
|
+
if (userText.length === 0) return presetText;
|
|
79
|
+
return `${presetText}, ${userText}`;
|
|
80
|
+
}
|
|
34
81
|
//#endregion
|
|
35
82
|
//#region lib/types/config.js
|
|
36
83
|
/** User-facing configuration for supported image providers. */
|
|
@@ -56,6 +103,12 @@ const Config = z.object({
|
|
|
56
103
|
dashscopeEndpoint: z.string().default(DEFAULT_DASHSCOPE_ENDPOINT),
|
|
57
104
|
dashscopeModel: z.string().default(DEFAULT_DASHSCOPE_MODEL),
|
|
58
105
|
comfyuiBaseURL: z.string().default(DEFAULT_COMFYUI_BASE_URL),
|
|
106
|
+
comfyuiWorkflows: z.array(z.object({
|
|
107
|
+
name: z.string(),
|
|
108
|
+
json: z.string(),
|
|
109
|
+
presetPrompt: z.string().default("")
|
|
110
|
+
})).default([]),
|
|
111
|
+
comfyuiActiveWorkflow: z.string().default(""),
|
|
59
112
|
comfyuiWorkflowJson: z.string().default(""),
|
|
60
113
|
comfyuiWorkflowName: z.string().default(""),
|
|
61
114
|
comfyuiTimeoutMs: z.number().min(1e3).max(36e5).default(DEFAULT_COMFYUI_TIMEOUT_MS),
|
|
@@ -86,13 +139,17 @@ function resolveProvider(config) {
|
|
|
86
139
|
endpoint: config.dashscopeEndpoint ?? "https://dashscope.aliyuncs.com/api/v1",
|
|
87
140
|
imageSize: "1024*1024"
|
|
88
141
|
};
|
|
89
|
-
case "comfyui":
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
142
|
+
case "comfyui": {
|
|
143
|
+
const workflows = resolveComfyUIWorkflows(config);
|
|
144
|
+
const workflow = activeComfyUIWorkflow(config);
|
|
145
|
+
return {
|
|
146
|
+
provider: "comfyui",
|
|
147
|
+
baseURL: config.comfyuiBaseURL ?? "http://127.0.0.1:8188",
|
|
148
|
+
workflows,
|
|
149
|
+
...workflow === void 0 ? {} : { workflow },
|
|
150
|
+
timeoutMs: config.comfyuiTimeoutMs ?? 3e5
|
|
151
|
+
};
|
|
152
|
+
}
|
|
96
153
|
case "google": return {
|
|
97
154
|
provider: "google",
|
|
98
155
|
apiKeyEnv: GOOGLE_API_KEY_ENV,
|
|
@@ -103,43 +160,82 @@ function resolveProvider(config) {
|
|
|
103
160
|
};
|
|
104
161
|
}
|
|
105
162
|
}
|
|
163
|
+
/** The workflow a ComfyUI call runs: the requested name when given, else the active one. */
|
|
164
|
+
function selectComfyUIWorkflow(active, requested) {
|
|
165
|
+
if (active.workflow === void 0) throw new Error("ComfyUI image generation requires an imported workflow; import one in Settings > Plugins > Image generation.");
|
|
166
|
+
if (typeof requested !== "string" || requested.trim().length === 0) return active.workflow;
|
|
167
|
+
const name = requested.trim();
|
|
168
|
+
const workflow = active.workflows.find((candidate) => candidate.name === name);
|
|
169
|
+
if (workflow === void 0) throw new Error(`No ComfyUI workflow named "${name}" is configured. Available workflows: ${active.workflows.map((entry) => entry.name).join(", ")}.`);
|
|
170
|
+
return workflow;
|
|
171
|
+
}
|
|
106
172
|
//#endregion
|
|
107
173
|
//#region lib/types/comfyui-workflow.js
|
|
108
174
|
/** Pure ComfyUI API-workflow validation and placeholder injection. */
|
|
109
175
|
const COMFYUI_PROMPT_PLACEHOLDER = "{{prompt}}";
|
|
110
176
|
const COMFYUI_SEED_PLACEHOLDER = "{{seed}}";
|
|
111
|
-
|
|
112
|
-
|
|
177
|
+
const COMFYUI_IMAGE_PLACEHOLDER = "{{image}}";
|
|
178
|
+
/** Legacy single-percent placeholders from early releases, still accepted. */
|
|
179
|
+
const LEGACY_PROMPT_PLACEHOLDER = "%prompt%";
|
|
180
|
+
const LEGACY_SEED_PLACEHOLDER = "%seed%";
|
|
181
|
+
const LEGACY_IMAGE_PLACEHOLDER = "%image%";
|
|
182
|
+
/** LoadImage-style input key that receives the uploaded source image name. */
|
|
183
|
+
const IMAGE_INPUT_KEY = "image";
|
|
184
|
+
/**
|
|
185
|
+
* Parse, clone, and inject one prompt plus an optional randomized seed.
|
|
186
|
+
*
|
|
187
|
+
* Prompt and seed placeholders are replaced inside any string so existing
|
|
188
|
+
* workflows that embed them in longer text keep working. The image
|
|
189
|
+
* placeholder is stricter: it only matches a dedicated `inputs.image` field
|
|
190
|
+
* whose value is exactly the placeholder, and at most one may exist, because
|
|
191
|
+
* the field must carry a single uploaded file name.
|
|
192
|
+
*/
|
|
193
|
+
function prepareComfyUIWorkflow(workflowJson, prompt, seed = randomSeed(), image) {
|
|
113
194
|
const workflow = parseWorkflow(workflowJson);
|
|
114
195
|
let promptReplacements = 0;
|
|
115
196
|
const inject = (value) => {
|
|
116
197
|
if (typeof value === "string") {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
return prompt;
|
|
120
|
-
}
|
|
121
|
-
if (value === "{{seed}}") return seed;
|
|
122
|
-
if (value.includes("{{prompt}}")) {
|
|
198
|
+
let replaced = value;
|
|
199
|
+
if (replaced.includes("{{prompt}}") || replaced.includes(LEGACY_PROMPT_PLACEHOLDER)) {
|
|
123
200
|
promptReplacements += 1;
|
|
124
|
-
|
|
201
|
+
replaced = replaced.replaceAll(COMFYUI_PROMPT_PLACEHOLDER, prompt).replaceAll(LEGACY_PROMPT_PLACEHOLDER, prompt);
|
|
125
202
|
}
|
|
126
|
-
|
|
203
|
+
if (replaced === "{{seed}}" || replaced === LEGACY_SEED_PLACEHOLDER) return seed;
|
|
204
|
+
if (replaced.includes("{{seed}}") || replaced.includes(LEGACY_SEED_PLACEHOLDER)) replaced = replaced.replaceAll(COMFYUI_SEED_PLACEHOLDER, String(seed)).replaceAll(LEGACY_SEED_PLACEHOLDER, String(seed));
|
|
205
|
+
return replaced;
|
|
127
206
|
}
|
|
128
207
|
if (Array.isArray(value)) return value.map(inject);
|
|
129
208
|
if (!isRecord(value)) return value;
|
|
130
209
|
return Object.fromEntries(Object.entries(value).map(([key, child]) => [key, inject(child)]));
|
|
131
210
|
};
|
|
211
|
+
let imageInputs = 0;
|
|
132
212
|
const prepared = Object.fromEntries(Object.entries(workflow).map(([nodeId, value]) => {
|
|
133
213
|
const node = record$4(value);
|
|
134
214
|
const inputs = node === void 0 ? void 0 : record$4(node.inputs);
|
|
135
|
-
|
|
215
|
+
if (inputs === void 0) return [nodeId, value];
|
|
216
|
+
const nextInputs = inject(inputs);
|
|
217
|
+
if (isImagePlaceholder(nextInputs[IMAGE_INPUT_KEY])) {
|
|
218
|
+
imageInputs += 1;
|
|
219
|
+
if (image !== void 0) nextInputs[IMAGE_INPUT_KEY] = image;
|
|
220
|
+
}
|
|
221
|
+
return [nodeId, {
|
|
136
222
|
...node,
|
|
137
|
-
inputs:
|
|
223
|
+
inputs: nextInputs
|
|
138
224
|
}];
|
|
139
225
|
}));
|
|
140
226
|
if (promptReplacements === 0) throw new Error(`ComfyUI workflow must contain ${COMFYUI_PROMPT_PLACEHOLDER} in a text input`);
|
|
227
|
+
if (image === void 0) {
|
|
228
|
+
if (imageInputs > 0) throw new Error(`ComfyUI workflow contains an ${COMFYUI_IMAGE_PLACEHOLDER} image input, which requires edit_image with a source image`);
|
|
229
|
+
return prepared;
|
|
230
|
+
}
|
|
231
|
+
if (imageInputs === 0) throw new Error(`ComfyUI workflow must contain exactly one ${COMFYUI_IMAGE_PLACEHOLDER} image input to edit images`);
|
|
232
|
+
if (imageInputs > 1) throw new Error(`ComfyUI workflow must contain exactly one ${COMFYUI_IMAGE_PLACEHOLDER} image input; found ${String(imageInputs)}`);
|
|
141
233
|
return prepared;
|
|
142
234
|
}
|
|
235
|
+
/** Random seed in the 32-bit range ComfyUI samplers accept. */
|
|
236
|
+
function randomSeed() {
|
|
237
|
+
return Math.floor(Math.random() * 4294967296);
|
|
238
|
+
}
|
|
143
239
|
function parseWorkflow(workflowJson) {
|
|
144
240
|
if (workflowJson.trim().length === 0) throw new Error("Import a ComfyUI API workflow JSON file in Settings before generating");
|
|
145
241
|
if (new TextEncoder().encode(workflowJson).byteLength > 5242880) throw new Error("ComfyUI workflow file must be no larger than 5 MB");
|
|
@@ -153,11 +249,14 @@ function parseWorkflow(workflowJson) {
|
|
|
153
249
|
if (!Object.values(value).some((node) => {
|
|
154
250
|
const inputs = record$4(node)?.inputs;
|
|
155
251
|
return inputs !== void 0 && containsPromptPlaceholder(inputs);
|
|
156
|
-
})) throw new Error(`ComfyUI workflow must contain ${COMFYUI_PROMPT_PLACEHOLDER} in a text input`);
|
|
252
|
+
})) throw new Error(`ComfyUI workflow must contain ${COMFYUI_PROMPT_PLACEHOLDER} (or ${LEGACY_PROMPT_PLACEHOLDER}) in a text input`);
|
|
157
253
|
return value;
|
|
158
254
|
}
|
|
255
|
+
function isImagePlaceholder(value) {
|
|
256
|
+
return value === "{{image}}" || value === LEGACY_IMAGE_PLACEHOLDER;
|
|
257
|
+
}
|
|
159
258
|
function containsPromptPlaceholder(value) {
|
|
160
|
-
if (typeof value === "string") return value.includes(
|
|
259
|
+
if (typeof value === "string") return value.includes("{{prompt}}") || value.includes(LEGACY_PROMPT_PLACEHOLDER);
|
|
161
260
|
if (Array.isArray(value)) return value.some(containsPromptPlaceholder);
|
|
162
261
|
return isRecord(value) && Object.values(value).some(containsPromptPlaceholder);
|
|
163
262
|
}
|
|
@@ -167,19 +266,37 @@ function isRecord(value) {
|
|
|
167
266
|
function record$4(value) {
|
|
168
267
|
return isRecord(value) ? value : void 0;
|
|
169
268
|
}
|
|
170
|
-
function randomSeed() {
|
|
171
|
-
return Math.floor(Math.random() * 4294967296);
|
|
172
|
-
}
|
|
173
269
|
//#endregion
|
|
174
270
|
//#region lib/types/comfyui.js
|
|
175
271
|
const ERROR_LIMIT$3 = 4096;
|
|
176
272
|
const POLL_INTERVAL_MS = 500;
|
|
177
273
|
const MAX_HISTORY_BYTES = 16777216;
|
|
178
|
-
/** Run one ComfyUI workflow and return its first final image. */
|
|
274
|
+
/** Run one ComfyUI text-to-image workflow and return its first final image. */
|
|
179
275
|
async function generateComfyUIImage(input) {
|
|
276
|
+
const seed = randomSeed();
|
|
277
|
+
const workflow = prepareComfyUIWorkflow(input.workflowJson, input.prompt, seed);
|
|
278
|
+
return {
|
|
279
|
+
...await runJob(input, async (baseURL, signal) => {
|
|
280
|
+
return await downloadOutput(baseURL, await waitForOutput(baseURL, await submitWorkflow(baseURL, workflow, signal), signal), input.maxBytes, signal);
|
|
281
|
+
}),
|
|
282
|
+
seed
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
/** Upload one source image, run the workflow, and return its first final image. */
|
|
286
|
+
async function editComfyUIImage(input) {
|
|
287
|
+
const seed = randomSeed();
|
|
288
|
+
return {
|
|
289
|
+
...await runJob(input, async (baseURL, signal) => {
|
|
290
|
+
const imageName = await uploadSourceImage(baseURL, input.sourceImage, signal);
|
|
291
|
+
return await downloadOutput(baseURL, await waitForOutput(baseURL, await submitWorkflow(baseURL, prepareComfyUIWorkflow(input.workflowJson, input.prompt, seed, imageName), signal), signal), input.maxBytes, signal);
|
|
292
|
+
}),
|
|
293
|
+
seed
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
/** Share abort forwarding, the single timeout, and error normalization. */
|
|
297
|
+
async function runJob(input, run) {
|
|
180
298
|
input.signal.throwIfAborted();
|
|
181
299
|
const baseURL = comfyUIBaseURL(input.baseURL);
|
|
182
|
-
const workflow = prepareComfyUIWorkflow(input.workflowJson, input.prompt);
|
|
183
300
|
const controller = new AbortController();
|
|
184
301
|
const forwardAbort = () => {
|
|
185
302
|
controller.abort(input.signal.reason);
|
|
@@ -189,7 +306,7 @@ async function generateComfyUIImage(input) {
|
|
|
189
306
|
controller.abort(/* @__PURE__ */ new Error("ComfyUI generation timed out"));
|
|
190
307
|
}, input.timeoutMs);
|
|
191
308
|
try {
|
|
192
|
-
return await
|
|
309
|
+
return await run(baseURL, controller.signal);
|
|
193
310
|
} catch (error) {
|
|
194
311
|
input.signal.throwIfAborted();
|
|
195
312
|
if (controller.signal.aborted) throw new Error(`ComfyUI generation timed out after ${String(input.timeoutMs)} ms`);
|
|
@@ -200,6 +317,33 @@ async function generateComfyUIImage(input) {
|
|
|
200
317
|
input.signal.removeEventListener("abort", forwardAbort);
|
|
201
318
|
}
|
|
202
319
|
}
|
|
320
|
+
/** Upload the source image and return the LoadImage-compatible name ComfyUI stored it under. */
|
|
321
|
+
async function uploadSourceImage(baseURL, image, signal) {
|
|
322
|
+
const filename = uploadFilename(image.mediaType);
|
|
323
|
+
const bytes = new Uint8Array(image.data.byteLength);
|
|
324
|
+
bytes.set(image.data);
|
|
325
|
+
const form = new FormData();
|
|
326
|
+
form.append("image", new Blob([bytes], { type: image.mediaType }), filename);
|
|
327
|
+
const response = await fetch(endpoint(baseURL, "upload/image"), {
|
|
328
|
+
method: "POST",
|
|
329
|
+
redirect: "error",
|
|
330
|
+
signal,
|
|
331
|
+
headers: { accept: "application/json" },
|
|
332
|
+
body: form
|
|
333
|
+
});
|
|
334
|
+
const text = await readBoundedText$3(response, ERROR_LIMIT$3);
|
|
335
|
+
if (!response.ok) throw new Error(`ComfyUI image upload failed (${response.status}): ${text}`);
|
|
336
|
+
const payload = parseJsonRecord(text, "ComfyUI /upload/image returned invalid JSON");
|
|
337
|
+
if (typeof payload.name !== "string" || payload.name.length === 0) throw new Error(`ComfyUI /upload/image returned no name: ${text}`);
|
|
338
|
+
const subfolder = typeof payload.subfolder === "string" ? payload.subfolder : "";
|
|
339
|
+
return subfolder.length > 0 ? `${subfolder}/${payload.name}` : payload.name;
|
|
340
|
+
}
|
|
341
|
+
/** LoadImage-style inputs need a file name with a decodable extension. */
|
|
342
|
+
function uploadFilename(mediaType) {
|
|
343
|
+
const extension = mediaType === "image/png" ? "png" : mediaType === "image/jpeg" ? "jpg" : mediaType === "image/webp" ? "webp" : void 0;
|
|
344
|
+
if (extension === void 0) throw new Error(`ComfyUI edit_image accepts PNG, JPEG, or WebP source images; got ${mediaType}`);
|
|
345
|
+
return `dsh-image-gen-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}.${extension}`;
|
|
346
|
+
}
|
|
203
347
|
async function submitWorkflow(baseURL, workflow, signal) {
|
|
204
348
|
const response = await fetch(endpoint(baseURL, "prompt"), {
|
|
205
349
|
method: "POST",
|
|
@@ -1242,7 +1386,7 @@ const inject = [
|
|
|
1242
1386
|
];
|
|
1243
1387
|
function apply(ctx, config = {}) {
|
|
1244
1388
|
let current = () => config;
|
|
1245
|
-
|
|
1389
|
+
installImageSettings(ctx, config, {
|
|
1246
1390
|
setSource: (source) => {
|
|
1247
1391
|
current = source;
|
|
1248
1392
|
},
|
|
@@ -1287,19 +1431,26 @@ function apply(ctx, config = {}) {
|
|
|
1287
1431
|
size: {
|
|
1288
1432
|
type: "string",
|
|
1289
1433
|
description: "Optional dimensions or size tier for OpenAI, Seedream, or DashScope."
|
|
1434
|
+
},
|
|
1435
|
+
workflow: {
|
|
1436
|
+
type: "string",
|
|
1437
|
+
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."
|
|
1290
1438
|
}
|
|
1291
1439
|
},
|
|
1292
1440
|
output: imageOutput("Generated"),
|
|
1293
1441
|
async execute(args, exec) {
|
|
1294
1442
|
const active = resolveProvider(current());
|
|
1295
|
-
if (active.provider === "comfyui")
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1443
|
+
if (active.provider === "comfyui") {
|
|
1444
|
+
const workflow = selectComfyUIWorkflow(active, args.workflow);
|
|
1445
|
+
return saveGenerated(ctx, await generateComfyUIImage({
|
|
1446
|
+
baseURL: active.baseURL,
|
|
1447
|
+
workflowJson: workflow.json,
|
|
1448
|
+
prompt: mergeComfyUIPrompt(workflow.presetPrompt, args.prompt),
|
|
1449
|
+
timeoutMs: active.timeoutMs,
|
|
1450
|
+
maxBytes: ctx.attachments.imageLimits.maxImageBytes,
|
|
1451
|
+
signal: exec.signal
|
|
1452
|
+
}), active.provider, workflow.name, "API workflow", current(), exec);
|
|
1453
|
+
}
|
|
1303
1454
|
const credential = await ctx.credentials.resolve(credentialRef(active.apiKeyEnv));
|
|
1304
1455
|
if (credential === void 0 || credential.value.length === 0) throw new Error(`generate_image requires the ${active.apiKeyEnv} credential; configure it in Settings > Plugins > Image generation.`);
|
|
1305
1456
|
if (active.provider === "google") {
|
|
@@ -1394,14 +1545,15 @@ function apply(ctx, config = {}) {
|
|
|
1394
1545
|
size: {
|
|
1395
1546
|
type: "string",
|
|
1396
1547
|
description: "Optional output size for OpenAI, Seedream, or DashScope."
|
|
1548
|
+
},
|
|
1549
|
+
workflow: {
|
|
1550
|
+
type: "string",
|
|
1551
|
+
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."
|
|
1397
1552
|
}
|
|
1398
1553
|
},
|
|
1399
1554
|
output: imageOutput("Edited"),
|
|
1400
1555
|
async execute(args, exec) {
|
|
1401
1556
|
const active = resolveProvider(current());
|
|
1402
|
-
if (active.provider === "comfyui") throw new Error("edit_image is not yet supported by the ComfyUI provider; switch providers or use generate_image");
|
|
1403
|
-
const credential = await ctx.credentials.resolve(credentialRef(active.apiKeyEnv));
|
|
1404
|
-
if (credential === void 0 || credential.value.length === 0) throw new Error(`edit_image requires the ${active.apiKeyEnv} credential; configure it in Settings > Plugins > Image generation.`);
|
|
1405
1557
|
const sourceImages = await resolveReferenceImages({
|
|
1406
1558
|
...exec.agent === void 0 ? {} : { agent: exec.agent },
|
|
1407
1559
|
attachments: ctx.attachments,
|
|
@@ -1412,6 +1564,26 @@ function apply(ctx, config = {}) {
|
|
|
1412
1564
|
maxBytes: ctx.attachments.imageLimits.maxImageBytes,
|
|
1413
1565
|
signal: exec.signal
|
|
1414
1566
|
});
|
|
1567
|
+
if (active.provider === "comfyui") {
|
|
1568
|
+
if (sourceImages.length > 1) throw new Error(`ComfyUI edit_image supports exactly one source image per call; this call resolved ${String(sourceImages.length)} images. Call edit_image again with source_attachment_id set to the single attachment ID of the image to edit.`);
|
|
1569
|
+
const sourceImage = sourceImages[0];
|
|
1570
|
+
if (sourceImage === void 0) throw new Error("edit_image requires a reference image");
|
|
1571
|
+
const workflow = selectComfyUIWorkflow(active, args.workflow);
|
|
1572
|
+
return saveGenerated(ctx, await editComfyUIImage({
|
|
1573
|
+
baseURL: active.baseURL,
|
|
1574
|
+
workflowJson: workflow.json,
|
|
1575
|
+
prompt: mergeComfyUIPrompt(workflow.presetPrompt, args.prompt),
|
|
1576
|
+
sourceImage: {
|
|
1577
|
+
data: sourceImage.data,
|
|
1578
|
+
mediaType: sourceImage.mediaType
|
|
1579
|
+
},
|
|
1580
|
+
timeoutMs: active.timeoutMs,
|
|
1581
|
+
maxBytes: ctx.attachments.imageLimits.maxImageBytes,
|
|
1582
|
+
signal: exec.signal
|
|
1583
|
+
}), active.provider, workflow.name, "API workflow", current(), exec);
|
|
1584
|
+
}
|
|
1585
|
+
const credential = await ctx.credentials.resolve(credentialRef(active.apiKeyEnv));
|
|
1586
|
+
if (credential === void 0 || credential.value.length === 0) throw new Error(`edit_image requires the ${active.apiKeyEnv} credential; configure it in Settings > Plugins > Image generation.`);
|
|
1415
1587
|
if (active.provider === "google") {
|
|
1416
1588
|
const aspectRatio = args.aspect_ratio ?? active.aspectRatio;
|
|
1417
1589
|
const imageSize = args.image_size ?? active.imageSize;
|
|
@@ -1545,6 +1717,7 @@ function imageOutput(verb) {
|
|
|
1545
1717
|
output: value.output,
|
|
1546
1718
|
...verb === "Edited" ? { operation: "edit" } : {},
|
|
1547
1719
|
...typeof value.savedTo === "string" ? { savedTo: value.savedTo } : {},
|
|
1720
|
+
...typeof value.seed === "number" ? { seed: value.seed } : {},
|
|
1548
1721
|
prompt: args.prompt
|
|
1549
1722
|
})
|
|
1550
1723
|
};
|
|
@@ -1574,7 +1747,8 @@ async function saveGenerated(ctx, generated, provider, model, output, config, ex
|
|
|
1574
1747
|
attachment,
|
|
1575
1748
|
provider,
|
|
1576
1749
|
model,
|
|
1577
|
-
output
|
|
1750
|
+
output,
|
|
1751
|
+
...typeof generated.seed === "number" ? { seed: generated.seed } : {}
|
|
1578
1752
|
};
|
|
1579
1753
|
if (config.saveToWorkspace === false) return value;
|
|
1580
1754
|
const workspaceRoot = exec.agent?.session.header.cwd;
|
|
@@ -1606,5 +1780,28 @@ function imagePresentation(result) {
|
|
|
1606
1780
|
}]
|
|
1607
1781
|
};
|
|
1608
1782
|
}
|
|
1783
|
+
/**
|
|
1784
|
+
* Wire the settings namespace across both dsh-settings API generations.
|
|
1785
|
+
* A namespace import keeps module loading safe on either version; the branch
|
|
1786
|
+
* picks the service method (0.1.2+) or the legacy top-level relay (<= rc.2),
|
|
1787
|
+
* and falls back to the composition entry with a warning when neither exists
|
|
1788
|
+
* so an incompatible host degrades the settings UI instead of failing boot.
|
|
1789
|
+
*/
|
|
1790
|
+
function installImageSettings(ctx, config, hooks) {
|
|
1791
|
+
const namespace = dshSettings;
|
|
1792
|
+
if (typeof namespace.SettingsProvider?.prototype?.installSection === "function") {
|
|
1793
|
+
ctx.inject(["settings"], (settingsCtx) => {
|
|
1794
|
+
settingsCtx.settings.installSection(ctx, IMAGE_GENERATION_NAMESPACE, Config, config, hooks);
|
|
1795
|
+
});
|
|
1796
|
+
return;
|
|
1797
|
+
}
|
|
1798
|
+
const legacyInstall = namespace.installSettingsSection;
|
|
1799
|
+
const legacyNamespace = namespace.settingsNamespace;
|
|
1800
|
+
if (typeof legacyInstall === "function" && typeof legacyNamespace === "function") {
|
|
1801
|
+
legacyInstall(ctx, legacyNamespace(IMAGE_GENERATION_NAMESPACE), Config, config, hooks);
|
|
1802
|
+
return;
|
|
1803
|
+
}
|
|
1804
|
+
ctx.logger.warn("dsh-image-gen: this DSH exposes neither settings API generation; settings UI stays on the composition entry");
|
|
1805
|
+
}
|
|
1609
1806
|
//#endregion
|
|
1610
1807
|
export { Config, IMAGE_ROUTE, apply, imageAttachmentFromMeta, inject, name };
|
|
@@ -15,6 +15,8 @@ export interface GalleryItem {
|
|
|
15
15
|
aspectRatio?: string;
|
|
16
16
|
imageSize?: string;
|
|
17
17
|
output?: string;
|
|
18
|
+
/** Workflow seed reported by the ComfyUI provider. Optional so records written by older versions stay readable; the object store is schemaless, so no IndexedDB version bump is needed. */
|
|
19
|
+
seed?: number;
|
|
18
20
|
}
|
|
19
21
|
type GalleryListener = () => void;
|
|
20
22
|
/**
|
|
@@ -18,6 +18,7 @@ declare const DICT: {
|
|
|
18
18
|
readonly filterOpenAI: "OpenAI / 中转站";
|
|
19
19
|
readonly filterSeedream: "字节 Seedream";
|
|
20
20
|
readonly filterDashScope: "阿里 DashScope";
|
|
21
|
+
readonly filterComfyUI: "本地 ComfyUI";
|
|
21
22
|
readonly emptyTitle: "暂无生图记录";
|
|
22
23
|
readonly emptyDesc: "在对话中让 Agent 生图后,生成的图片会自动收录到这里。";
|
|
23
24
|
readonly noMatchTitle: "未找到匹配结果";
|
|
@@ -44,6 +45,7 @@ declare const DICT: {
|
|
|
44
45
|
readonly filterOpenAI: "OpenAI / Relay";
|
|
45
46
|
readonly filterSeedream: "ByteDance Seedream";
|
|
46
47
|
readonly filterDashScope: "Aliyun DashScope";
|
|
48
|
+
readonly filterComfyUI: "Local ComfyUI";
|
|
47
49
|
readonly emptyTitle: "No images generated yet";
|
|
48
50
|
readonly emptyDesc: "Images generated during conversations will automatically appear here.";
|
|
49
51
|
readonly noMatchTitle: "No matching images";
|
|
@@ -8,6 +8,8 @@ export interface ImageResultPresentation {
|
|
|
8
8
|
readonly model: string;
|
|
9
9
|
readonly output: string;
|
|
10
10
|
readonly savedTo?: string;
|
|
11
|
+
/** Workflow seed reported by the ComfyUI provider, when available. */
|
|
12
|
+
readonly seed?: number;
|
|
11
13
|
}
|
|
12
14
|
interface ImageResultState {
|
|
13
15
|
readonly turn: number;
|
|
@@ -46,8 +48,23 @@ interface ConversationNodeDefinitionLike<State> {
|
|
|
46
48
|
}, match: MatchLike): State;
|
|
47
49
|
buildViewNode(context: ContextLike<State>): Record<string, unknown> | null;
|
|
48
50
|
}
|
|
49
|
-
/**
|
|
51
|
+
/** Options controlling where the artifact anchors as its Turn progresses. */
|
|
52
|
+
export interface ImageResultNodeOptions {
|
|
53
|
+
/**
|
|
54
|
+
* Whether the host transcript view folds completed-Turn process content
|
|
55
|
+
* (Compact mode). Omission or a thrown/unavailable read falls back to
|
|
56
|
+
* Compact-safe anchoring, which keeps the artifact always visible.
|
|
57
|
+
*/
|
|
58
|
+
readonly isCompactTranscript?: () => boolean;
|
|
59
|
+
}
|
|
60
|
+
/** Default definition: unknown transcript mode falls back to Compact-safe anchoring. */
|
|
50
61
|
export declare const imageResultDefinition: ConversationNodeDefinitionLike<ImageResultState>;
|
|
62
|
+
/**
|
|
63
|
+
* One image result row per Turn, anchored at its own tool/result position while
|
|
64
|
+
* the Turn runs, then optionally re-anchored beside the final answer once the
|
|
65
|
+
* Turn closes (only needed under the folding Compact transcript view).
|
|
66
|
+
*/
|
|
67
|
+
export declare function createImageResultDefinition(options?: ImageResultNodeOptions): ConversationNodeDefinitionLike<ImageResultState>;
|
|
51
68
|
/** Parse the plugin-owned durable presentation metadata from a Tool result event. */
|
|
52
69
|
export declare function imageResultFromMeta(value: unknown): ImageResultPresentation | undefined;
|
|
53
70
|
export {};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Context } from '@deepseek-ai/cordis';
|
|
2
2
|
import type { SettingsScope } from '@deepseek-ai/dsh-client-runtime/client';
|
|
3
3
|
import type { InjectFace, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
|
|
4
|
-
import { type ImageProvider } from '../shared.js';
|
|
4
|
+
import { type ComfyUIWorkflowEntry, type ImageProvider } from '../shared.js';
|
|
5
5
|
import { type LocaleService } from './gallery-view.js';
|
|
6
6
|
import { type ImageResultPresentation } from './image-result-node.js';
|
|
7
7
|
type Provider = ImageProvider;
|
|
@@ -16,6 +16,8 @@ interface ImageSettings {
|
|
|
16
16
|
dashscopeEndpoint?: string;
|
|
17
17
|
dashscopeModel?: string;
|
|
18
18
|
comfyuiBaseURL?: string;
|
|
19
|
+
comfyuiWorkflows?: ComfyUIWorkflowEntry[];
|
|
20
|
+
comfyuiActiveWorkflow?: string;
|
|
19
21
|
comfyuiWorkflowJson?: string;
|
|
20
22
|
comfyuiWorkflowName?: string;
|
|
21
23
|
comfyuiTimeoutMs?: number;
|
|
@@ -1,8 +1,19 @@
|
|
|
1
1
|
export declare const COMFYUI_PROMPT_PLACEHOLDER = "{{prompt}}";
|
|
2
2
|
export declare const COMFYUI_SEED_PLACEHOLDER = "{{seed}}";
|
|
3
|
+
export declare const COMFYUI_IMAGE_PLACEHOLDER = "{{image}}";
|
|
3
4
|
type JsonRecord = Record<string, unknown>;
|
|
4
5
|
/** Validate imported JSON without exposing workflow graph details to callers. */
|
|
5
6
|
export declare function validateComfyUIWorkflowJson(workflowJson: string): void;
|
|
6
|
-
/**
|
|
7
|
-
|
|
7
|
+
/**
|
|
8
|
+
* Parse, clone, and inject one prompt plus an optional randomized seed.
|
|
9
|
+
*
|
|
10
|
+
* Prompt and seed placeholders are replaced inside any string so existing
|
|
11
|
+
* workflows that embed them in longer text keep working. The image
|
|
12
|
+
* placeholder is stricter: it only matches a dedicated `inputs.image` field
|
|
13
|
+
* whose value is exactly the placeholder, and at most one may exist, because
|
|
14
|
+
* the field must carry a single uploaded file name.
|
|
15
|
+
*/
|
|
16
|
+
export declare function prepareComfyUIWorkflow(workflowJson: string, prompt: string, seed?: number, image?: string): JsonRecord;
|
|
17
|
+
/** Random seed in the 32-bit range ComfyUI samplers accept. */
|
|
18
|
+
export declare function randomSeed(): number;
|
|
8
19
|
export {};
|
package/lib/types/comfyui.d.ts
CHANGED
|
@@ -1,15 +1,32 @@
|
|
|
1
|
-
/** ComfyUI text-to-image
|
|
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
3
|
export interface GeneratedComfyUIImage {
|
|
4
4
|
data: Uint8Array;
|
|
5
5
|
mediaType: ImageMediaType;
|
|
6
|
+
/** Concrete seed injected into the workflow, surfaced for provenance metadata. */
|
|
7
|
+
seed: number;
|
|
6
8
|
}
|
|
7
|
-
/**
|
|
8
|
-
export
|
|
9
|
+
/** Source image bytes a ComfyUI workflow accepts through its image input. */
|
|
10
|
+
export interface ComfyUISourceImage {
|
|
11
|
+
data: Uint8Array;
|
|
12
|
+
mediaType: ImageMediaType;
|
|
13
|
+
}
|
|
14
|
+
interface ComfyUIJobInput {
|
|
9
15
|
baseURL: string;
|
|
16
|
+
timeoutMs: number;
|
|
17
|
+
signal: AbortSignal;
|
|
18
|
+
}
|
|
19
|
+
/** Run one ComfyUI text-to-image workflow and return its first final image. */
|
|
20
|
+
export declare function generateComfyUIImage(input: ComfyUIJobInput & {
|
|
10
21
|
workflowJson: string;
|
|
11
22
|
prompt: string;
|
|
12
|
-
timeoutMs: number;
|
|
13
23
|
maxBytes: number;
|
|
14
|
-
signal: AbortSignal;
|
|
15
24
|
}): Promise<GeneratedComfyUIImage>;
|
|
25
|
+
/** Upload one source image, run the workflow, and return its first final image. */
|
|
26
|
+
export declare function editComfyUIImage(input: ComfyUIJobInput & {
|
|
27
|
+
workflowJson: string;
|
|
28
|
+
prompt: string;
|
|
29
|
+
sourceImage: ComfyUISourceImage;
|
|
30
|
+
maxBytes: number;
|
|
31
|
+
}): Promise<GeneratedComfyUIImage>;
|
|
32
|
+
export {};
|
package/lib/types/config.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/** User-facing configuration for supported image providers. */
|
|
2
2
|
import z from '@deepseek-ai/schemastery';
|
|
3
|
-
import { DEFAULT_DASHSCOPE_ENDPOINT, DEFAULT_DASHSCOPE_MODEL, DEFAULT_COMFYUI_BASE_URL, DEFAULT_COMFYUI_TIMEOUT_MS, DEFAULT_COMFYUI_WORKFLOW_LABEL, DEFAULT_GOOGLE_ENDPOINT, DEFAULT_GOOGLE_MODEL, DEFAULT_OPENAI_BASE_URL, DEFAULT_OPENAI_MODEL, DEFAULT_SEEDREAM_BASE_URL, DEFAULT_SEEDREAM_MODEL, IMAGE_PROVIDERS, type ImageProvider } from './shared.js';
|
|
4
|
-
export { DEFAULT_DASHSCOPE_ENDPOINT, DEFAULT_DASHSCOPE_MODEL, DEFAULT_COMFYUI_BASE_URL, DEFAULT_COMFYUI_TIMEOUT_MS, DEFAULT_COMFYUI_WORKFLOW_LABEL, DEFAULT_GOOGLE_ENDPOINT, DEFAULT_GOOGLE_MODEL, DEFAULT_OPENAI_BASE_URL, DEFAULT_OPENAI_MODEL, DEFAULT_SEEDREAM_BASE_URL, DEFAULT_SEEDREAM_MODEL, IMAGE_PROVIDERS, type ImageProvider, };
|
|
3
|
+
import { DEFAULT_DASHSCOPE_ENDPOINT, DEFAULT_DASHSCOPE_MODEL, DEFAULT_COMFYUI_BASE_URL, DEFAULT_COMFYUI_TIMEOUT_MS, DEFAULT_COMFYUI_WORKFLOW_LABEL, DEFAULT_GOOGLE_ENDPOINT, DEFAULT_GOOGLE_MODEL, DEFAULT_OPENAI_BASE_URL, DEFAULT_OPENAI_MODEL, DEFAULT_SEEDREAM_BASE_URL, DEFAULT_SEEDREAM_MODEL, IMAGE_PROVIDERS, activeComfyUIWorkflow, resolveComfyUIWorkflows, type ComfyUIWorkflowEntry, type ImageProvider } from './shared.js';
|
|
4
|
+
export { DEFAULT_DASHSCOPE_ENDPOINT, DEFAULT_DASHSCOPE_MODEL, DEFAULT_COMFYUI_BASE_URL, DEFAULT_COMFYUI_TIMEOUT_MS, DEFAULT_COMFYUI_WORKFLOW_LABEL, DEFAULT_GOOGLE_ENDPOINT, DEFAULT_GOOGLE_MODEL, DEFAULT_OPENAI_BASE_URL, DEFAULT_OPENAI_MODEL, DEFAULT_SEEDREAM_BASE_URL, DEFAULT_SEEDREAM_MODEL, IMAGE_PROVIDERS, activeComfyUIWorkflow, resolveComfyUIWorkflows, type ComfyUIWorkflowEntry, type ImageProvider, };
|
|
5
5
|
/** Default workspace subfolder that receives generated image files. */
|
|
6
6
|
export declare const DEFAULT_WORKSPACE_FOLDER = "dsh-image-gen";
|
|
7
7
|
/** Google API credential reference. */
|
|
@@ -29,9 +29,13 @@ export interface Config {
|
|
|
29
29
|
dashscopeEndpoint?: string;
|
|
30
30
|
dashscopeModel?: string;
|
|
31
31
|
comfyuiBaseURL?: string;
|
|
32
|
-
/** ComfyUI
|
|
32
|
+
/** Named ComfyUI workflows managed by the Web settings page. */
|
|
33
|
+
comfyuiWorkflows?: ComfyUIWorkflowEntry[];
|
|
34
|
+
/** Name of the workflow ComfyUI calls use by default. */
|
|
35
|
+
comfyuiActiveWorkflow?: string;
|
|
36
|
+
/** Legacy single-workflow storage; synced to the active entry for downgrades. */
|
|
33
37
|
comfyuiWorkflowJson?: string;
|
|
34
|
-
/** Original imported file name
|
|
38
|
+
/** Original imported file name of the legacy single workflow. */
|
|
35
39
|
comfyuiWorkflowName?: string;
|
|
36
40
|
comfyuiTimeoutMs?: number;
|
|
37
41
|
/** Also write every generated image as a file under the session workspace. */
|
|
@@ -70,7 +74,12 @@ export declare function resolveProvider(config: Config): {
|
|
|
70
74
|
} | {
|
|
71
75
|
provider: 'comfyui';
|
|
72
76
|
baseURL: string;
|
|
73
|
-
|
|
74
|
-
|
|
77
|
+
workflows: ComfyUIWorkflowEntry[];
|
|
78
|
+
workflow?: ComfyUIWorkflowEntry;
|
|
75
79
|
timeoutMs: number;
|
|
76
80
|
};
|
|
81
|
+
/** The workflow a ComfyUI call runs: the requested name when given, else the active one. */
|
|
82
|
+
export declare function selectComfyUIWorkflow(active: {
|
|
83
|
+
workflows: ComfyUIWorkflowEntry[];
|
|
84
|
+
workflow?: ComfyUIWorkflowEntry;
|
|
85
|
+
}, requested?: string): ComfyUIWorkflowEntry;
|
package/lib/types/shared.d.ts
CHANGED
|
@@ -20,5 +20,36 @@ export declare const DEFAULT_GOOGLE_MODEL = "gemini-3.1-flash-image";
|
|
|
20
20
|
export declare const DEFAULT_OPENAI_MODEL = "gpt-image-2";
|
|
21
21
|
export declare const DEFAULT_SEEDREAM_MODEL = "doubao-seedream-5-0-260128";
|
|
22
22
|
export declare const DEFAULT_DASHSCOPE_MODEL = "qwen-image-3.0";
|
|
23
|
+
/** One named ComfyUI API-format workflow imported through settings. */
|
|
24
|
+
export interface ComfyUIWorkflowEntry {
|
|
25
|
+
/** Unique human-readable label; used as the result model and by tool calls. */
|
|
26
|
+
name: string;
|
|
27
|
+
/** API-format workflow JSON with {{prompt}} / {{seed}} and optional {{image}} placeholders. */
|
|
28
|
+
json: string;
|
|
29
|
+
/** Optional preset prepended to the user prompt on every call of this workflow. */
|
|
30
|
+
presetPrompt?: string;
|
|
31
|
+
}
|
|
32
|
+
/** Raw ComfyUI workflow fields as persisted through DSH Settings. */
|
|
33
|
+
export interface ComfyUIWorkflowSource {
|
|
34
|
+
/** Named workflows managed by the Web settings page. */
|
|
35
|
+
comfyuiWorkflows?: readonly ComfyUIWorkflowEntry[];
|
|
36
|
+
/** Name of the entry ComfyUI calls use by default. */
|
|
37
|
+
comfyuiActiveWorkflow?: string;
|
|
38
|
+
/** Legacy single-workflow storage; synced to the active entry for downgrades. */
|
|
39
|
+
comfyuiWorkflowJson?: string;
|
|
40
|
+
comfyuiWorkflowName?: string;
|
|
41
|
+
}
|
|
42
|
+
/** Named workflows, falling back to the legacy single-workflow fields when the list is empty. */
|
|
43
|
+
export declare function resolveComfyUIWorkflows(source: ComfyUIWorkflowSource): ComfyUIWorkflowEntry[];
|
|
44
|
+
/** The workflow ComfyUI calls use by default: the configured active name, else the first entry. */
|
|
45
|
+
export declare function activeComfyUIWorkflow(source: ComfyUIWorkflowSource): ComfyUIWorkflowEntry | undefined;
|
|
46
|
+
/** Derive a workflow label that does not collide with the given existing names. */
|
|
47
|
+
export declare function uniqueComfyUIWorkflowName(name: string, existing: readonly string[]): string;
|
|
48
|
+
/**
|
|
49
|
+
* Combine a workflow's preset with the user prompt: preset first, user second,
|
|
50
|
+
* joined by one comma — never doubled when the preset already ends in a
|
|
51
|
+
* separator, and reduced to the non-empty side when the other is blank.
|
|
52
|
+
*/
|
|
53
|
+
export declare function mergeComfyUIPrompt(preset: string | undefined, user: string): string;
|
|
23
54
|
export declare const DEFAULT_MODELS: Record<ImageProvider, string>;
|
|
24
55
|
export declare const DEFAULT_BASE_URLS: Record<ImageProvider, string>;
|