@fre4x/comfyui 1.1.8 → 1.1.9
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.md +70 -33
- package/dist/index.js +1706 -1015
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -6891,8 +6891,8 @@ var require_dist = __commonJS({
|
|
|
6891
6891
|
});
|
|
6892
6892
|
|
|
6893
6893
|
// src/index.ts
|
|
6894
|
-
import * as fs3 from "node:fs/promises";
|
|
6895
6894
|
import { realpathSync } from "node:fs";
|
|
6895
|
+
import * as fs3 from "node:fs/promises";
|
|
6896
6896
|
import path3 from "node:path";
|
|
6897
6897
|
import { fileURLToPath } from "node:url";
|
|
6898
6898
|
|
|
@@ -6932,7 +6932,7 @@ function createInternalError(error48) {
|
|
|
6932
6932
|
type: "text",
|
|
6933
6933
|
text: `Internal Error: ${message}
|
|
6934
6934
|
|
|
6935
|
-
Suggestion:
|
|
6935
|
+
Suggestion: Use the error details above to correct tool arguments and retry the tool call.`
|
|
6936
6936
|
}
|
|
6937
6937
|
]
|
|
6938
6938
|
};
|
|
@@ -6944,6 +6944,11 @@ function formatListItems(items, renderer, emptyMessage = "_No results found._")
|
|
|
6944
6944
|
return emptyMessage;
|
|
6945
6945
|
return items.map((item, i) => renderer(item, i)).join("\n\n");
|
|
6946
6946
|
}
|
|
6947
|
+
function truncateToLimit(text, maxChars = 12e3) {
|
|
6948
|
+
if (text.length <= maxChars)
|
|
6949
|
+
return text;
|
|
6950
|
+
return text.slice(0, maxChars) + "\n\n_...response truncated to fit context window. Use pagination to retrieve more results._";
|
|
6951
|
+
}
|
|
6947
6952
|
function formatPaginationFooter(offset, limit, total) {
|
|
6948
6953
|
const start = offset + 1;
|
|
6949
6954
|
const end = Math.min(offset + limit, total);
|
|
@@ -30516,6 +30521,10 @@ var StdioServerTransport = class {
|
|
|
30516
30521
|
}
|
|
30517
30522
|
};
|
|
30518
30523
|
|
|
30524
|
+
// src/api.ts
|
|
30525
|
+
import * as fs from "node:fs/promises";
|
|
30526
|
+
import path from "node:path";
|
|
30527
|
+
|
|
30519
30528
|
// src/errors.ts
|
|
30520
30529
|
var ComfyApiError = class extends Error {
|
|
30521
30530
|
constructor(message, statusCode, details) {
|
|
@@ -30539,7 +30548,9 @@ var WorkflowInputError = class extends Error {
|
|
|
30539
30548
|
};
|
|
30540
30549
|
var WorkflowTimeoutError = class extends Error {
|
|
30541
30550
|
constructor(promptId, timeoutSeconds, queueSnapshot) {
|
|
30542
|
-
super(
|
|
30551
|
+
super(
|
|
30552
|
+
`Timed out waiting for prompt_id '${promptId}'. Call comfyui_wait_for_workflow again with this prompt_id`
|
|
30553
|
+
);
|
|
30543
30554
|
this.promptId = promptId;
|
|
30544
30555
|
this.timeoutSeconds = timeoutSeconds;
|
|
30545
30556
|
this.queueSnapshot = queueSnapshot;
|
|
@@ -30550,10 +30561,6 @@ var WorkflowTimeoutError = class extends Error {
|
|
|
30550
30561
|
queueSnapshot;
|
|
30551
30562
|
};
|
|
30552
30563
|
|
|
30553
|
-
// src/api.ts
|
|
30554
|
-
import * as fs from "node:fs/promises";
|
|
30555
|
-
import path from "node:path";
|
|
30556
|
-
|
|
30557
30564
|
// src/mock.ts
|
|
30558
30565
|
var IS_MOCK = process.env.MOCK === "true";
|
|
30559
30566
|
var MOCK_FIXTURES = {
|
|
@@ -30701,25 +30708,77 @@ var MOCK_FIXTURES = {
|
|
|
30701
30708
|
messages: []
|
|
30702
30709
|
}
|
|
30703
30710
|
}
|
|
30711
|
+
},
|
|
30712
|
+
models: {
|
|
30713
|
+
checkpoint: [
|
|
30714
|
+
"v1-5-pruned-emaonly.ckpt",
|
|
30715
|
+
"sd_xl_base_1.0.safetensors",
|
|
30716
|
+
"cyberrealisticPony_v160.safetensors"
|
|
30717
|
+
],
|
|
30718
|
+
lora: ["mock_style.safetensors", "mock_character.safetensors"],
|
|
30719
|
+
vae: ["ae.safetensors", "sdxl_vae.safetensors"],
|
|
30720
|
+
controlnet: ["control_v11p_sd15_canny.pth"],
|
|
30721
|
+
unet: ["flux1-dev.safetensors"],
|
|
30722
|
+
clip: ["clip_l.safetensors", "t5xxl_fp16.safetensors"],
|
|
30723
|
+
upscale: ["RealESRGAN_x4plus.pth"]
|
|
30704
30724
|
}
|
|
30705
30725
|
};
|
|
30706
30726
|
|
|
30707
30727
|
// src/api.ts
|
|
30708
|
-
var
|
|
30728
|
+
var MAX_UPLOAD_BYTES = 20 * 1024 * 1024;
|
|
30729
|
+
var MODEL_FOLDER_BY_TYPE = {
|
|
30730
|
+
checkpoint: ["checkpoints"],
|
|
30731
|
+
lora: ["loras"],
|
|
30732
|
+
vae: ["vae"],
|
|
30733
|
+
controlnet: ["controlnet"],
|
|
30734
|
+
unet: ["diffusion_models", "unet"],
|
|
30735
|
+
clip: ["clip"],
|
|
30736
|
+
upscale: ["upscale_models"]
|
|
30737
|
+
};
|
|
30738
|
+
var MIME_BY_EXTENSION = {
|
|
30739
|
+
".png": "image/png",
|
|
30740
|
+
".jpg": "image/jpeg",
|
|
30741
|
+
".jpeg": "image/jpeg",
|
|
30742
|
+
".webp": "image/webp",
|
|
30743
|
+
".gif": "image/gif",
|
|
30744
|
+
".bmp": "image/bmp",
|
|
30745
|
+
".tif": "image/tiff",
|
|
30746
|
+
".tiff": "image/tiff"
|
|
30747
|
+
};
|
|
30748
|
+
function getComfyServerUrl() {
|
|
30749
|
+
return process.env.COMFYUI_SERVER_URL || "http://localhost:8188";
|
|
30750
|
+
}
|
|
30751
|
+
function buildComfyHeaders(extra) {
|
|
30752
|
+
const headers = new Headers();
|
|
30753
|
+
headers.set("Accept", "application/json");
|
|
30754
|
+
const apiKey = process.env.COMFY_API_KEY?.trim();
|
|
30755
|
+
if (apiKey) {
|
|
30756
|
+
const headerName = process.env.COMFY_API_HEADER?.trim() || "X-API-Key";
|
|
30757
|
+
const value = headerName.toLowerCase() === "authorization" && !/^Bearer\s+/i.test(apiKey) ? `Bearer ${apiKey}` : apiKey;
|
|
30758
|
+
headers.set(headerName, value);
|
|
30759
|
+
}
|
|
30760
|
+
if (extra) {
|
|
30761
|
+
new Headers(extra).forEach((value, key) => {
|
|
30762
|
+
headers.set(key, value);
|
|
30763
|
+
});
|
|
30764
|
+
}
|
|
30765
|
+
return headers;
|
|
30766
|
+
}
|
|
30767
|
+
function isStringArray(value) {
|
|
30768
|
+
return Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
30769
|
+
}
|
|
30709
30770
|
async function fetchComfyJson(apiPath, options) {
|
|
30710
|
-
const
|
|
30771
|
+
const serverUrl = getComfyServerUrl();
|
|
30772
|
+
const url2 = new URL(apiPath, serverUrl).toString();
|
|
30711
30773
|
let response;
|
|
30712
30774
|
try {
|
|
30713
30775
|
response = await fetch(url2, {
|
|
30714
30776
|
...options,
|
|
30715
|
-
headers:
|
|
30716
|
-
Accept: "application/json",
|
|
30717
|
-
...options?.headers || {}
|
|
30718
|
-
}
|
|
30777
|
+
headers: buildComfyHeaders(options?.headers)
|
|
30719
30778
|
});
|
|
30720
30779
|
} catch (error48) {
|
|
30721
30780
|
throw new Error(
|
|
30722
|
-
`Failed to connect to ComfyUI server at ${
|
|
30781
|
+
`Failed to connect to ComfyUI server at ${serverUrl}: ${error48 instanceof Error ? error48.message : String(error48)}`
|
|
30723
30782
|
);
|
|
30724
30783
|
}
|
|
30725
30784
|
if (response.status === 404) {
|
|
@@ -30750,9 +30809,11 @@ async function fetchImageAsset(filename, type, subfolder) {
|
|
|
30750
30809
|
if (subfolder) params.set("subfolder", subfolder);
|
|
30751
30810
|
const url2 = new URL(
|
|
30752
30811
|
`/view?${params.toString()}`,
|
|
30753
|
-
|
|
30812
|
+
getComfyServerUrl()
|
|
30754
30813
|
).toString();
|
|
30755
|
-
const response = await fetch(url2
|
|
30814
|
+
const response = await fetch(url2, {
|
|
30815
|
+
headers: buildComfyHeaders()
|
|
30816
|
+
});
|
|
30756
30817
|
if (!response.ok)
|
|
30757
30818
|
throw new Error(`Failed to fetch image: ${response.statusText}`);
|
|
30758
30819
|
const arrayBuffer = await response.arrayBuffer();
|
|
@@ -30779,27 +30840,27 @@ function sanitizePathSegment(value, fallback) {
|
|
|
30779
30840
|
const normalized = value.trim().replace(/[\\/]+/g, "_").replace(/\.\.+/g, "_").replace(/_+/g, "_").replace(/[^A-Za-z0-9._-]/g, "_");
|
|
30780
30841
|
return normalized.length > 0 ? normalized : fallback;
|
|
30781
30842
|
}
|
|
30843
|
+
function getWorkspaceOutputRelativePath(promptId, image) {
|
|
30844
|
+
return path.join(
|
|
30845
|
+
"comfyui",
|
|
30846
|
+
sanitizePathSegment(promptId, "unknown-prompt"),
|
|
30847
|
+
...getSafePathSegments(image.subfolder),
|
|
30848
|
+
path.basename(image.filename)
|
|
30849
|
+
);
|
|
30850
|
+
}
|
|
30782
30851
|
async function saveToWorkspace(promptId, image, asset) {
|
|
30783
30852
|
const workspaceDir = getSessionWorkspaceDir();
|
|
30784
30853
|
if (!workspaceDir) {
|
|
30785
30854
|
return void 0;
|
|
30786
30855
|
}
|
|
30787
|
-
const
|
|
30788
|
-
const safeFileName = path.basename(image.filename);
|
|
30789
|
-
const safeSubfolderSegments = getSafePathSegments(image.subfolder);
|
|
30790
|
-
const relativePath = path.join(
|
|
30791
|
-
"comfyui",
|
|
30792
|
-
safePromptId,
|
|
30793
|
-
...safeSubfolderSegments,
|
|
30794
|
-
safeFileName
|
|
30795
|
-
);
|
|
30856
|
+
const relativePath = getWorkspaceOutputRelativePath(promptId, image);
|
|
30796
30857
|
const workspacePath = path.join(workspaceDir, relativePath);
|
|
30797
30858
|
await fs.mkdir(path.dirname(workspacePath), { recursive: true });
|
|
30798
30859
|
await fs.writeFile(workspacePath, asset.buffer);
|
|
30799
30860
|
return {
|
|
30800
30861
|
workspace_path: workspacePath,
|
|
30801
30862
|
relative_path: relativePath,
|
|
30802
|
-
filename:
|
|
30863
|
+
filename: path.basename(image.filename),
|
|
30803
30864
|
mime_type: asset.mimeType,
|
|
30804
30865
|
subfolder: image.subfolder
|
|
30805
30866
|
};
|
|
@@ -30830,17 +30891,58 @@ async function getQueueSnapshot() {
|
|
|
30830
30891
|
return void 0;
|
|
30831
30892
|
}
|
|
30832
30893
|
}
|
|
30894
|
+
function extractPromptIdsFromQueueEntries(entries) {
|
|
30895
|
+
const ids = [];
|
|
30896
|
+
for (const entry of entries) {
|
|
30897
|
+
if (!Array.isArray(entry) || entry.length === 0) {
|
|
30898
|
+
continue;
|
|
30899
|
+
}
|
|
30900
|
+
if (typeof entry[1] === "string" && entry[1].length > 0) {
|
|
30901
|
+
ids.push(entry[1]);
|
|
30902
|
+
continue;
|
|
30903
|
+
}
|
|
30904
|
+
if (typeof entry[0] === "string" && entry[0].length > 0) {
|
|
30905
|
+
ids.push(entry[0]);
|
|
30906
|
+
}
|
|
30907
|
+
}
|
|
30908
|
+
return ids;
|
|
30909
|
+
}
|
|
30910
|
+
function summarizeQueue(queueSnapshot) {
|
|
30911
|
+
if (!queueSnapshot) {
|
|
30912
|
+
return void 0;
|
|
30913
|
+
}
|
|
30914
|
+
return {
|
|
30915
|
+
running: queueSnapshot.queue_running.length,
|
|
30916
|
+
pending: queueSnapshot.queue_pending.length,
|
|
30917
|
+
running_prompt_ids: extractPromptIdsFromQueueEntries(
|
|
30918
|
+
queueSnapshot.queue_running
|
|
30919
|
+
),
|
|
30920
|
+
pending_prompt_ids: extractPromptIdsFromQueueEntries(
|
|
30921
|
+
queueSnapshot.queue_pending
|
|
30922
|
+
)
|
|
30923
|
+
};
|
|
30924
|
+
}
|
|
30833
30925
|
function formatQueueSnapshotText(queueSnapshot) {
|
|
30834
30926
|
if (!queueSnapshot) {
|
|
30835
30927
|
return "";
|
|
30836
30928
|
}
|
|
30929
|
+
if ("running" in queueSnapshot && "pending" in queueSnapshot) {
|
|
30930
|
+
return `Queue snapshot: ${queueSnapshot.running} running, ${queueSnapshot.pending} pending.`;
|
|
30931
|
+
}
|
|
30837
30932
|
return `Queue snapshot: ${queueSnapshot.queue_running.length} running, ${queueSnapshot.queue_pending.length} pending.`;
|
|
30838
30933
|
}
|
|
30839
30934
|
function getPromptQueueVisibility(promptId, queueSnapshot) {
|
|
30840
30935
|
if (!queueSnapshot) {
|
|
30841
30936
|
return null;
|
|
30842
30937
|
}
|
|
30843
|
-
|
|
30938
|
+
if ("running_prompt_ids" in queueSnapshot) {
|
|
30939
|
+
return queueSnapshot.running_prompt_ids.includes(promptId) || queueSnapshot.pending_prompt_ids.includes(promptId);
|
|
30940
|
+
}
|
|
30941
|
+
const summary = summarizeQueue(queueSnapshot);
|
|
30942
|
+
if (!summary) {
|
|
30943
|
+
return null;
|
|
30944
|
+
}
|
|
30945
|
+
return summary.running_prompt_ids.includes(promptId) || summary.pending_prompt_ids.includes(promptId);
|
|
30844
30946
|
}
|
|
30845
30947
|
async function waitForWorkflowResult(prompt_id, timeout_seconds) {
|
|
30846
30948
|
const start = Date.now();
|
|
@@ -30861,9 +30963,270 @@ async function waitForWorkflowResult(prompt_id, timeout_seconds) {
|
|
|
30861
30963
|
}
|
|
30862
30964
|
await new Promise((r) => setTimeout(r, 1e3));
|
|
30863
30965
|
}
|
|
30864
|
-
const queueSnapshot = await getQueueSnapshot();
|
|
30966
|
+
const queueSnapshot = summarizeQueue(await getQueueSnapshot());
|
|
30865
30967
|
throw new WorkflowTimeoutError(prompt_id, timeout_seconds, queueSnapshot);
|
|
30866
30968
|
}
|
|
30969
|
+
function parseInterruptedFlag(body) {
|
|
30970
|
+
if (!body || typeof body !== "object" || !("interrupted" in body)) {
|
|
30971
|
+
return void 0;
|
|
30972
|
+
}
|
|
30973
|
+
const interrupted = body.interrupted;
|
|
30974
|
+
return typeof interrupted === "boolean" ? interrupted : void 0;
|
|
30975
|
+
}
|
|
30976
|
+
async function interruptWorkflow(options) {
|
|
30977
|
+
let interrupted = true;
|
|
30978
|
+
if (!IS_MOCK) {
|
|
30979
|
+
const interruptBody = await fetchComfyJson("/interrupt", {
|
|
30980
|
+
method: "POST",
|
|
30981
|
+
...options.promptId ? {
|
|
30982
|
+
body: JSON.stringify({ prompt_id: options.promptId }),
|
|
30983
|
+
headers: { "Content-Type": "application/json" }
|
|
30984
|
+
} : {}
|
|
30985
|
+
});
|
|
30986
|
+
const parsed = parseInterruptedFlag(interruptBody);
|
|
30987
|
+
if (parsed !== void 0) {
|
|
30988
|
+
interrupted = parsed;
|
|
30989
|
+
}
|
|
30990
|
+
if (options.clearQueue) {
|
|
30991
|
+
await fetchComfyJson("/queue", {
|
|
30992
|
+
method: "POST",
|
|
30993
|
+
body: JSON.stringify({ clear: true }),
|
|
30994
|
+
headers: { "Content-Type": "application/json" }
|
|
30995
|
+
});
|
|
30996
|
+
}
|
|
30997
|
+
}
|
|
30998
|
+
const queue = summarizeQueue(await getQueueSnapshot());
|
|
30999
|
+
return {
|
|
31000
|
+
interrupted,
|
|
31001
|
+
prompt_id: options.promptId,
|
|
31002
|
+
queue_cleared: options.clearQueue,
|
|
31003
|
+
...queue ? { queue } : {}
|
|
31004
|
+
};
|
|
31005
|
+
}
|
|
31006
|
+
function mimeTypeFromFilename(filename) {
|
|
31007
|
+
const extension = path.extname(filename).toLowerCase();
|
|
31008
|
+
return MIME_BY_EXTENSION[extension] || "image/png";
|
|
31009
|
+
}
|
|
31010
|
+
async function getUploadRoots() {
|
|
31011
|
+
const candidates = [
|
|
31012
|
+
process.cwd(),
|
|
31013
|
+
process.env.MCP_WORKSPACE_DIR,
|
|
31014
|
+
process.env.COMFYUI_UPLOAD_ROOT
|
|
31015
|
+
].filter((value) => Boolean(value));
|
|
31016
|
+
const roots = [];
|
|
31017
|
+
for (const candidate of candidates) {
|
|
31018
|
+
try {
|
|
31019
|
+
roots.push(await fs.realpath(candidate));
|
|
31020
|
+
} catch {
|
|
31021
|
+
roots.push(path.resolve(candidate));
|
|
31022
|
+
}
|
|
31023
|
+
}
|
|
31024
|
+
return [...new Set(roots)];
|
|
31025
|
+
}
|
|
31026
|
+
function isPathInsideRoot(target, root) {
|
|
31027
|
+
return target === root || target.startsWith(`${root}${path.sep}`);
|
|
31028
|
+
}
|
|
31029
|
+
async function resolveReadableUploadPath(filePath) {
|
|
31030
|
+
let resolved;
|
|
31031
|
+
try {
|
|
31032
|
+
resolved = await fs.realpath(filePath);
|
|
31033
|
+
} catch {
|
|
31034
|
+
throw new WorkflowInputError(
|
|
31035
|
+
`Image file not found: ${filePath}`,
|
|
31036
|
+
"file_path",
|
|
31037
|
+
"not_found"
|
|
31038
|
+
);
|
|
31039
|
+
}
|
|
31040
|
+
const roots = await getUploadRoots();
|
|
31041
|
+
if (!roots.some((root) => isPathInsideRoot(resolved, root))) {
|
|
31042
|
+
throw new WorkflowInputError(
|
|
31043
|
+
"file_path must be inside the workspace or COMFYUI_UPLOAD_ROOT.",
|
|
31044
|
+
"file_path"
|
|
31045
|
+
);
|
|
31046
|
+
}
|
|
31047
|
+
return resolved;
|
|
31048
|
+
}
|
|
31049
|
+
async function readPreviousRunImage(params) {
|
|
31050
|
+
if (IS_MOCK) {
|
|
31051
|
+
return createMockImageAsset();
|
|
31052
|
+
}
|
|
31053
|
+
const workspaceDir = getSessionWorkspaceDir();
|
|
31054
|
+
if (workspaceDir) {
|
|
31055
|
+
const candidate = path.join(
|
|
31056
|
+
workspaceDir,
|
|
31057
|
+
getWorkspaceOutputRelativePath(params.promptId, {
|
|
31058
|
+
filename: params.filename,
|
|
31059
|
+
subfolder: params.subfolder
|
|
31060
|
+
})
|
|
31061
|
+
);
|
|
31062
|
+
try {
|
|
31063
|
+
const resolved = await fs.realpath(candidate);
|
|
31064
|
+
let root;
|
|
31065
|
+
try {
|
|
31066
|
+
root = await fs.realpath(workspaceDir);
|
|
31067
|
+
} catch {
|
|
31068
|
+
root = path.resolve(workspaceDir);
|
|
31069
|
+
}
|
|
31070
|
+
if (isPathInsideRoot(resolved, root)) {
|
|
31071
|
+
return {
|
|
31072
|
+
buffer: await fs.readFile(resolved),
|
|
31073
|
+
mimeType: mimeTypeFromFilename(params.filename)
|
|
31074
|
+
};
|
|
31075
|
+
}
|
|
31076
|
+
} catch {
|
|
31077
|
+
}
|
|
31078
|
+
}
|
|
31079
|
+
return fetchImageAsset(
|
|
31080
|
+
params.filename,
|
|
31081
|
+
params.sourceType,
|
|
31082
|
+
params.subfolder
|
|
31083
|
+
);
|
|
31084
|
+
}
|
|
31085
|
+
async function readUploadImageSource(params) {
|
|
31086
|
+
const sources = [
|
|
31087
|
+
params.filePath ? "file_path" : void 0,
|
|
31088
|
+
params.promptId ? "prompt_id" : void 0
|
|
31089
|
+
].filter((value) => Boolean(value));
|
|
31090
|
+
if (sources.length !== 1) {
|
|
31091
|
+
throw new WorkflowInputError(
|
|
31092
|
+
"Provide exactly one image source: file_path, or prompt_id + filename from a previous run.",
|
|
31093
|
+
sources[1] ?? "file_path"
|
|
31094
|
+
);
|
|
31095
|
+
}
|
|
31096
|
+
if (params.promptId) {
|
|
31097
|
+
if (!params.filename?.trim()) {
|
|
31098
|
+
throw new WorkflowInputError(
|
|
31099
|
+
"filename is required when uploading from a previous prompt_id.",
|
|
31100
|
+
"filename"
|
|
31101
|
+
);
|
|
31102
|
+
}
|
|
31103
|
+
const asset = await readPreviousRunImage({
|
|
31104
|
+
promptId: params.promptId,
|
|
31105
|
+
filename: params.filename,
|
|
31106
|
+
subfolder: params.subfolder,
|
|
31107
|
+
sourceType: params.sourceType ?? "output"
|
|
31108
|
+
});
|
|
31109
|
+
if (asset.buffer.byteLength > MAX_UPLOAD_BYTES) {
|
|
31110
|
+
throw new WorkflowInputError(
|
|
31111
|
+
`Image exceeds the ${MAX_UPLOAD_BYTES} byte upload limit.`,
|
|
31112
|
+
"prompt_id"
|
|
31113
|
+
);
|
|
31114
|
+
}
|
|
31115
|
+
const filename2 = sanitizePathSegment(
|
|
31116
|
+
params.filename,
|
|
31117
|
+
`upload-${Date.now()}.png`
|
|
31118
|
+
);
|
|
31119
|
+
return {
|
|
31120
|
+
buffer: asset.buffer,
|
|
31121
|
+
filename: filename2,
|
|
31122
|
+
mimeType: asset.mimeType
|
|
31123
|
+
};
|
|
31124
|
+
}
|
|
31125
|
+
if (!params.filePath) {
|
|
31126
|
+
throw new WorkflowInputError(
|
|
31127
|
+
"Provide exactly one image source: file_path, or prompt_id + filename from a previous run.",
|
|
31128
|
+
"file_path"
|
|
31129
|
+
);
|
|
31130
|
+
}
|
|
31131
|
+
const resolvedPath = await resolveReadableUploadPath(params.filePath);
|
|
31132
|
+
const buffer = await fs.readFile(resolvedPath);
|
|
31133
|
+
if (buffer.byteLength > MAX_UPLOAD_BYTES) {
|
|
31134
|
+
throw new WorkflowInputError(
|
|
31135
|
+
`Image exceeds the ${MAX_UPLOAD_BYTES} byte upload limit.`,
|
|
31136
|
+
"file_path"
|
|
31137
|
+
);
|
|
31138
|
+
}
|
|
31139
|
+
const filename = sanitizePathSegment(
|
|
31140
|
+
params.filename || path.basename(resolvedPath),
|
|
31141
|
+
`upload-${Date.now()}.png`
|
|
31142
|
+
);
|
|
31143
|
+
return {
|
|
31144
|
+
buffer,
|
|
31145
|
+
filename,
|
|
31146
|
+
mimeType: mimeTypeFromFilename(filename)
|
|
31147
|
+
};
|
|
31148
|
+
}
|
|
31149
|
+
async function uploadImage(params) {
|
|
31150
|
+
if (IS_MOCK) {
|
|
31151
|
+
return {
|
|
31152
|
+
name: params.filename,
|
|
31153
|
+
subfolder: "",
|
|
31154
|
+
type: params.imageType
|
|
31155
|
+
};
|
|
31156
|
+
}
|
|
31157
|
+
const formData = new FormData();
|
|
31158
|
+
const bytes = new Uint8Array(params.buffer);
|
|
31159
|
+
formData.append(
|
|
31160
|
+
"image",
|
|
31161
|
+
new Blob([bytes], { type: params.mimeType }),
|
|
31162
|
+
params.filename
|
|
31163
|
+
);
|
|
31164
|
+
formData.append("type", params.imageType);
|
|
31165
|
+
if (params.overwrite) {
|
|
31166
|
+
formData.append("overwrite", "true");
|
|
31167
|
+
}
|
|
31168
|
+
const uploaded = await fetchComfyJson("/upload/image", {
|
|
31169
|
+
method: "POST",
|
|
31170
|
+
body: formData
|
|
31171
|
+
});
|
|
31172
|
+
if (uploaded && typeof uploaded === "object" && "name" in uploaded && typeof uploaded.name === "string") {
|
|
31173
|
+
const record2 = uploaded;
|
|
31174
|
+
return {
|
|
31175
|
+
name: uploaded.name,
|
|
31176
|
+
subfolder: typeof record2.subfolder === "string" ? record2.subfolder : "",
|
|
31177
|
+
type: typeof record2.type === "string" ? record2.type : params.imageType
|
|
31178
|
+
};
|
|
31179
|
+
}
|
|
31180
|
+
return {
|
|
31181
|
+
name: params.filename,
|
|
31182
|
+
subfolder: "",
|
|
31183
|
+
type: params.imageType
|
|
31184
|
+
};
|
|
31185
|
+
}
|
|
31186
|
+
async function getModelList(modelType) {
|
|
31187
|
+
if (IS_MOCK) {
|
|
31188
|
+
return {
|
|
31189
|
+
models: MOCK_FIXTURES.models[modelType] ?? [],
|
|
31190
|
+
models_endpoint_available: true
|
|
31191
|
+
};
|
|
31192
|
+
}
|
|
31193
|
+
const models = /* @__PURE__ */ new Set();
|
|
31194
|
+
let sawSuccessfulFolder = false;
|
|
31195
|
+
for (const folder of MODEL_FOLDER_BY_TYPE[modelType]) {
|
|
31196
|
+
try {
|
|
31197
|
+
const listed = await fetchComfyJson(`/models/${folder}`);
|
|
31198
|
+
if (!isStringArray(listed)) {
|
|
31199
|
+
continue;
|
|
31200
|
+
}
|
|
31201
|
+
sawSuccessfulFolder = true;
|
|
31202
|
+
for (const item of listed) {
|
|
31203
|
+
models.add(item);
|
|
31204
|
+
}
|
|
31205
|
+
} catch (error48) {
|
|
31206
|
+
if (error48 instanceof ComfyApiError && error48.statusCode === 404) {
|
|
31207
|
+
continue;
|
|
31208
|
+
}
|
|
31209
|
+
throw error48;
|
|
31210
|
+
}
|
|
31211
|
+
}
|
|
31212
|
+
return {
|
|
31213
|
+
models: [...models].sort(
|
|
31214
|
+
(left, right) => left.localeCompare(right, void 0, { numeric: true })
|
|
31215
|
+
),
|
|
31216
|
+
models_endpoint_available: sawSuccessfulFolder
|
|
31217
|
+
};
|
|
31218
|
+
}
|
|
31219
|
+
|
|
31220
|
+
// src/types.ts
|
|
31221
|
+
var COMFY_MODEL_TYPES = [
|
|
31222
|
+
"checkpoint",
|
|
31223
|
+
"lora",
|
|
31224
|
+
"vae",
|
|
31225
|
+
"controlnet",
|
|
31226
|
+
"unet",
|
|
31227
|
+
"clip",
|
|
31228
|
+
"upscale"
|
|
31229
|
+
];
|
|
30867
31230
|
|
|
30868
31231
|
// src/workflow/common.ts
|
|
30869
31232
|
function isRecord(value) {
|
|
@@ -30988,9 +31351,6 @@ function getInputOptionsSummary(inputDefinition, previewLimit = 8) {
|
|
|
30988
31351
|
options_truncated: typeData.length > previewLimit
|
|
30989
31352
|
};
|
|
30990
31353
|
}
|
|
30991
|
-
function isPlainObject3(value) {
|
|
30992
|
-
return isRecord(value) && !Array.isArray(value);
|
|
30993
|
-
}
|
|
30994
31354
|
function getValueTypeName(value) {
|
|
30995
31355
|
if (Array.isArray(value)) {
|
|
30996
31356
|
return "array";
|
|
@@ -31001,906 +31361,1014 @@ function getValueTypeName(value) {
|
|
|
31001
31361
|
return typeof value;
|
|
31002
31362
|
}
|
|
31003
31363
|
|
|
31004
|
-
// src/workflow/
|
|
31005
|
-
|
|
31006
|
-
|
|
31007
|
-
import { homedir } from "node:os";
|
|
31008
|
-
import path2 from "node:path";
|
|
31009
|
-
|
|
31010
|
-
// src/converter.ts
|
|
31011
|
-
var WIDGET_TYPES = /* @__PURE__ */ new Set(["INT", "FLOAT", "STRING", "BOOLEAN"]);
|
|
31012
|
-
var UI_CONTROL_VALUES = /* @__PURE__ */ new Set([
|
|
31013
|
-
"fixed",
|
|
31014
|
-
"increment",
|
|
31015
|
-
"decrement",
|
|
31016
|
-
"randomize"
|
|
31017
|
-
]);
|
|
31018
|
-
function isRecord2(value) {
|
|
31019
|
-
return typeof value === "object" && value !== null;
|
|
31020
|
-
}
|
|
31021
|
-
function isWebUIFormat(json2) {
|
|
31022
|
-
return isRecord2(json2) && Array.isArray(json2.nodes) && Array.isArray(json2.links);
|
|
31023
|
-
}
|
|
31024
|
-
function isLinkData(link) {
|
|
31025
|
-
return Array.isArray(link) && link.length >= 6 && typeof link[0] === "number" && typeof link[1] === "number" && typeof link[2] === "number" && typeof link[3] === "number" && typeof link[4] === "number" && typeof link[5] === "string";
|
|
31364
|
+
// src/workflow/editable.ts
|
|
31365
|
+
function isConnectionValue(value) {
|
|
31366
|
+
return Array.isArray(value) && value.length >= 2 && (typeof value[0] === "string" || typeof value[0] === "number") && typeof value[1] === "number";
|
|
31026
31367
|
}
|
|
31027
|
-
function
|
|
31028
|
-
if (
|
|
31029
|
-
|
|
31368
|
+
function inferEditableRole(nodeId, classType, inputName, consumerMap) {
|
|
31369
|
+
if (classType === "CLIPTextEncode" && inputName === "text") {
|
|
31370
|
+
const consumers = consumerMap.get(nodeId) ?? [];
|
|
31371
|
+
if (consumers.some((consumer) => consumer.inputName === "positive")) {
|
|
31372
|
+
return "positive_prompt";
|
|
31373
|
+
}
|
|
31374
|
+
if (consumers.some((consumer) => consumer.inputName === "negative")) {
|
|
31375
|
+
return "negative_prompt";
|
|
31376
|
+
}
|
|
31377
|
+
return "prompt_text";
|
|
31030
31378
|
}
|
|
31031
|
-
|
|
31032
|
-
|
|
31033
|
-
|
|
31034
|
-
|
|
31035
|
-
|
|
31036
|
-
|
|
31379
|
+
if (inputName === "value") {
|
|
31380
|
+
const consumers = consumerMap.get(nodeId) ?? [];
|
|
31381
|
+
const textConsumers = consumers.filter(
|
|
31382
|
+
(consumer) => consumer.classType === "CLIPTextEncode" && consumer.inputName === "text"
|
|
31383
|
+
);
|
|
31384
|
+
if (textConsumers.length > 0) {
|
|
31385
|
+
for (const consumer of textConsumers) {
|
|
31386
|
+
const clipConsumers = consumerMap.get(consumer.nodeId) ?? [];
|
|
31387
|
+
if (clipConsumers.some(
|
|
31388
|
+
(clipConsumer) => clipConsumer.inputName === "positive"
|
|
31389
|
+
)) {
|
|
31390
|
+
return "positive_prompt";
|
|
31391
|
+
}
|
|
31392
|
+
if (clipConsumers.some(
|
|
31393
|
+
(clipConsumer) => clipConsumer.inputName === "negative"
|
|
31394
|
+
)) {
|
|
31395
|
+
return "negative_prompt";
|
|
31396
|
+
}
|
|
31397
|
+
}
|
|
31398
|
+
return "prompt_text";
|
|
31037
31399
|
}
|
|
31038
|
-
orderedEntries.push([inputName, inputDefinition]);
|
|
31039
|
-
seen.add(inputName);
|
|
31040
31400
|
}
|
|
31041
|
-
|
|
31042
|
-
|
|
31401
|
+
switch (inputName) {
|
|
31402
|
+
case "seed":
|
|
31403
|
+
return "seed";
|
|
31404
|
+
case "steps":
|
|
31405
|
+
return "steps";
|
|
31406
|
+
case "cfg":
|
|
31407
|
+
return "cfg";
|
|
31408
|
+
case "sampler_name":
|
|
31409
|
+
return "sampler";
|
|
31410
|
+
case "scheduler":
|
|
31411
|
+
return "scheduler";
|
|
31412
|
+
case "denoise":
|
|
31413
|
+
return "denoise";
|
|
31414
|
+
case "width":
|
|
31415
|
+
return "width";
|
|
31416
|
+
case "height":
|
|
31417
|
+
return "height";
|
|
31418
|
+
case "batch_size":
|
|
31419
|
+
return "batch_size";
|
|
31420
|
+
case "ckpt_name":
|
|
31421
|
+
return "checkpoint";
|
|
31422
|
+
case "filename_prefix":
|
|
31423
|
+
return "filename_prefix";
|
|
31424
|
+
case "text":
|
|
31425
|
+
return "text";
|
|
31426
|
+
default:
|
|
31427
|
+
return void 0;
|
|
31428
|
+
}
|
|
31429
|
+
}
|
|
31430
|
+
var GLOBAL_OVERRIDE_ROLES = /* @__PURE__ */ new Set([
|
|
31431
|
+
"positive_prompt",
|
|
31432
|
+
"negative_prompt",
|
|
31433
|
+
"seed",
|
|
31434
|
+
"steps",
|
|
31435
|
+
"cfg",
|
|
31436
|
+
"checkpoint",
|
|
31437
|
+
"width",
|
|
31438
|
+
"height",
|
|
31439
|
+
"denoise",
|
|
31440
|
+
"sampler",
|
|
31441
|
+
"scheduler",
|
|
31442
|
+
"batch_size",
|
|
31443
|
+
"filename_prefix"
|
|
31444
|
+
]);
|
|
31445
|
+
function collectRoleEntries(editableInputs) {
|
|
31446
|
+
const roleEntries = /* @__PURE__ */ new Map();
|
|
31447
|
+
for (const entry of editableInputs) {
|
|
31448
|
+
if (!entry.role || !GLOBAL_OVERRIDE_ROLES.has(entry.role)) {
|
|
31043
31449
|
continue;
|
|
31044
31450
|
}
|
|
31045
|
-
|
|
31451
|
+
const entries = roleEntries.get(entry.role) ?? [];
|
|
31452
|
+
entries.push(entry);
|
|
31453
|
+
roleEntries.set(entry.role, entries);
|
|
31046
31454
|
}
|
|
31047
|
-
return
|
|
31455
|
+
return roleEntries;
|
|
31048
31456
|
}
|
|
31049
|
-
function
|
|
31050
|
-
|
|
31457
|
+
function applyUniqueGlobalRoleAliases(editableInputs) {
|
|
31458
|
+
for (const [role, entries] of collectRoleEntries(editableInputs)) {
|
|
31459
|
+
if (entries.length !== 1) {
|
|
31460
|
+
continue;
|
|
31461
|
+
}
|
|
31462
|
+
const entry = entries[0];
|
|
31463
|
+
if (!entry.aliases.includes(role)) {
|
|
31464
|
+
entry.aliases.push(role);
|
|
31465
|
+
}
|
|
31466
|
+
}
|
|
31051
31467
|
}
|
|
31052
|
-
function
|
|
31053
|
-
const
|
|
31054
|
-
|
|
31055
|
-
}
|
|
31056
|
-
function isSkippableUiArtifact(value, expectedTypeName) {
|
|
31057
|
-
if (expectedTypeName !== "STRING" && typeof value === "string" && UI_CONTROL_VALUES.has(value)) {
|
|
31058
|
-
return true;
|
|
31059
|
-
}
|
|
31060
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
31061
|
-
}
|
|
31062
|
-
function normalizeWidgetValue(value, typeName, extraInfo) {
|
|
31063
|
-
const defaultValue = extraInfo?.default;
|
|
31064
|
-
const shouldUseDefaultForNumeric = (typeName === "INT" || typeName === "FLOAT") && (value === "" || value === void 0 || value === null);
|
|
31065
|
-
if (shouldUseDefaultForNumeric && defaultValue !== void 0) {
|
|
31066
|
-
return defaultValue;
|
|
31067
|
-
}
|
|
31068
|
-
return value;
|
|
31069
|
-
}
|
|
31070
|
-
function convertWebUIToAPIWithMetadata(webUIJson, objectInfo) {
|
|
31071
|
-
const prompt = {};
|
|
31072
|
-
const widgetPathMap = {};
|
|
31073
|
-
const links = webUIJson.links || [];
|
|
31074
|
-
const nodes = webUIJson.nodes || [];
|
|
31075
|
-
const serverNodeIds = /* @__PURE__ */ new Set();
|
|
31076
|
-
for (const node of nodes) {
|
|
31077
|
-
if (!node.id || !node.type || !objectInfo[node.type]) {
|
|
31078
|
-
continue;
|
|
31079
|
-
}
|
|
31080
|
-
serverNodeIds.add(String(node.id));
|
|
31468
|
+
function getEditableAliases(canonicalPath, nodeId, inputName, role, widgetPathMap) {
|
|
31469
|
+
const aliases = /* @__PURE__ */ new Set([`${nodeId}.${inputName}`]);
|
|
31470
|
+
if (role) {
|
|
31471
|
+
aliases.add(`${nodeId}.${role}`);
|
|
31081
31472
|
}
|
|
31082
|
-
const
|
|
31083
|
-
|
|
31084
|
-
|
|
31085
|
-
linkMap.set(link[0], link);
|
|
31473
|
+
for (const [widgetPath, resolvedPath] of Object.entries(widgetPathMap)) {
|
|
31474
|
+
if (resolvedPath === canonicalPath) {
|
|
31475
|
+
aliases.add(widgetPath);
|
|
31086
31476
|
}
|
|
31087
31477
|
}
|
|
31088
|
-
|
|
31089
|
-
|
|
31090
|
-
|
|
31091
|
-
|
|
31092
|
-
|
|
31093
|
-
|
|
31094
|
-
|
|
31095
|
-
|
|
31096
|
-
|
|
31097
|
-
|
|
31098
|
-
|
|
31099
|
-
|
|
31100
|
-
|
|
31101
|
-
|
|
31102
|
-
|
|
31103
|
-
|
|
31104
|
-
|
|
31105
|
-
|
|
31106
|
-
originNodeId,
|
|
31107
|
-
originSlot
|
|
31108
|
-
];
|
|
31109
|
-
connectedInputNames.add(input.name);
|
|
31110
|
-
}
|
|
31111
|
-
}
|
|
31478
|
+
aliases.delete(canonicalPath);
|
|
31479
|
+
return [...aliases];
|
|
31480
|
+
}
|
|
31481
|
+
function buildEditableInputIndex(editableInputs) {
|
|
31482
|
+
const entriesByPath = /* @__PURE__ */ new Map();
|
|
31483
|
+
const aliasToPath = /* @__PURE__ */ new Map();
|
|
31484
|
+
const ambiguousAliases = /* @__PURE__ */ new Map();
|
|
31485
|
+
const pathsByNode = /* @__PURE__ */ new Map();
|
|
31486
|
+
for (const entry of editableInputs) {
|
|
31487
|
+
entriesByPath.set(entry.path, entry);
|
|
31488
|
+
const nodeEntries = pathsByNode.get(entry.node_id) ?? [];
|
|
31489
|
+
nodeEntries.push(entry);
|
|
31490
|
+
pathsByNode.set(entry.node_id, nodeEntries);
|
|
31491
|
+
for (const alias of [entry.path, ...entry.aliases]) {
|
|
31492
|
+
const existing = aliasToPath.get(alias);
|
|
31493
|
+
if (!existing) {
|
|
31494
|
+
aliasToPath.set(alias, entry.path);
|
|
31495
|
+
continue;
|
|
31112
31496
|
}
|
|
31113
|
-
|
|
31114
|
-
|
|
31115
|
-
|
|
31116
|
-
|
|
31117
|
-
|
|
31118
|
-
for (const [inputName, inputDataRaw] of getOrderedInputEntries(
|
|
31119
|
-
group,
|
|
31120
|
-
orderedNames
|
|
31121
|
-
)) {
|
|
31122
|
-
const [inputTypeData, inputExtraInfo] = inputDataRaw;
|
|
31123
|
-
if (!isWidgetInput(inputTypeData)) {
|
|
31124
|
-
continue;
|
|
31125
|
-
}
|
|
31126
|
-
const typeName = getWidgetTypeName(inputTypeData);
|
|
31127
|
-
while (widgetIndex < widgets.length && isSkippableUiArtifact(widgets[widgetIndex], typeName)) {
|
|
31128
|
-
widgetIndex++;
|
|
31129
|
-
}
|
|
31130
|
-
const isConnectedWidget = connectedInputNames.has(inputName);
|
|
31131
|
-
if (widgetIndex >= widgets.length) {
|
|
31132
|
-
if (!isConnectedWidget && inputExtraInfo?.default !== void 0) {
|
|
31133
|
-
promptNode.inputs[inputName] = inputExtraInfo.default;
|
|
31134
|
-
}
|
|
31135
|
-
continue;
|
|
31136
|
-
}
|
|
31137
|
-
const widgetSourceIndex = widgetIndex;
|
|
31138
|
-
const value = normalizeWidgetValue(
|
|
31139
|
-
widgets[widgetSourceIndex],
|
|
31140
|
-
typeName,
|
|
31141
|
-
inputExtraInfo
|
|
31142
|
-
);
|
|
31143
|
-
widgetIndex++;
|
|
31144
|
-
if (!isConnectedWidget) {
|
|
31145
|
-
promptNode.inputs[inputName] = value;
|
|
31146
|
-
widgetPathMap[`${nodeIdStr}.widgets[${widgetSourceIndex}]`] = `${nodeIdStr}.inputs.${inputName}`;
|
|
31147
|
-
}
|
|
31148
|
-
}
|
|
31149
|
-
};
|
|
31150
|
-
processInputGroup(
|
|
31151
|
-
objInfo.input?.required,
|
|
31152
|
-
objInfo.input_order?.required
|
|
31153
|
-
);
|
|
31154
|
-
processInputGroup(
|
|
31155
|
-
objInfo.input?.optional,
|
|
31156
|
-
objInfo.input_order?.optional
|
|
31497
|
+
if (existing === entry.path) {
|
|
31498
|
+
continue;
|
|
31499
|
+
}
|
|
31500
|
+
const ambiguous = new Set(
|
|
31501
|
+
ambiguousAliases.get(alias) ?? [existing]
|
|
31157
31502
|
);
|
|
31503
|
+
ambiguous.add(entry.path);
|
|
31504
|
+
ambiguousAliases.set(alias, [...ambiguous].sort());
|
|
31505
|
+
aliasToPath.delete(alias);
|
|
31158
31506
|
}
|
|
31159
|
-
|
|
31507
|
+
}
|
|
31508
|
+
for (const [role, entries] of collectRoleEntries(editableInputs)) {
|
|
31509
|
+
if (entries.length <= 1) {
|
|
31510
|
+
continue;
|
|
31511
|
+
}
|
|
31512
|
+
const paths = [...new Set(entries.map((entry) => entry.path))].sort();
|
|
31513
|
+
ambiguousAliases.set(role, paths);
|
|
31514
|
+
aliasToPath.delete(role);
|
|
31160
31515
|
}
|
|
31161
31516
|
return {
|
|
31162
|
-
|
|
31163
|
-
|
|
31517
|
+
entriesByPath,
|
|
31518
|
+
aliasToPath,
|
|
31519
|
+
ambiguousAliases,
|
|
31520
|
+
pathsByNode
|
|
31164
31521
|
};
|
|
31165
31522
|
}
|
|
31166
|
-
|
|
31167
|
-
|
|
31168
|
-
|
|
31169
|
-
|
|
31523
|
+
function getPathSegmentHelp(prompt, path4) {
|
|
31524
|
+
const parts = path4.split(".");
|
|
31525
|
+
if (parts.length === 0 || parts[0].length === 0) {
|
|
31526
|
+
return void 0;
|
|
31527
|
+
}
|
|
31528
|
+
const nodeId = parts[0];
|
|
31529
|
+
return {
|
|
31530
|
+
nodeId,
|
|
31531
|
+
nodeExists: isRecord(prompt[nodeId])
|
|
31532
|
+
};
|
|
31170
31533
|
}
|
|
31171
|
-
function
|
|
31172
|
-
|
|
31534
|
+
function describeAvailableNodePaths(editableIndex, nodeId) {
|
|
31535
|
+
if (!editableIndex) {
|
|
31536
|
+
return "";
|
|
31537
|
+
}
|
|
31538
|
+
const entries = editableIndex.pathsByNode.get(nodeId) ?? [];
|
|
31539
|
+
if (entries.length === 0) {
|
|
31540
|
+
return "";
|
|
31541
|
+
}
|
|
31542
|
+
return entries.map((entry) => {
|
|
31543
|
+
const aliasSuffix = entry.aliases.length > 0 ? ` (aliases: ${entry.aliases.join(", ")})` : "";
|
|
31544
|
+
return `${entry.path}${aliasSuffix}`;
|
|
31545
|
+
}).join("\n");
|
|
31173
31546
|
}
|
|
31174
|
-
function
|
|
31175
|
-
|
|
31176
|
-
|
|
31177
|
-
if (!isComfyPromptNode(rawNode) || isUiOnlyPromptNode(nodeId, rawNode)) {
|
|
31178
|
-
continue;
|
|
31179
|
-
}
|
|
31180
|
-
runnablePrompt[nodeId] = rawNode;
|
|
31547
|
+
function getPreferredOverridePath(entry) {
|
|
31548
|
+
if (entry.role && entry.aliases.includes(entry.role)) {
|
|
31549
|
+
return entry.role;
|
|
31181
31550
|
}
|
|
31182
|
-
|
|
31183
|
-
|
|
31184
|
-
|
|
31185
|
-
"workflow"
|
|
31186
|
-
);
|
|
31551
|
+
const roleAlias = entry.role ? `${entry.node_id}.${entry.role}` : void 0;
|
|
31552
|
+
if (roleAlias && entry.aliases.includes(roleAlias)) {
|
|
31553
|
+
return roleAlias;
|
|
31187
31554
|
}
|
|
31188
|
-
|
|
31555
|
+
const inputAlias = `${entry.node_id}.${entry.input_name}`;
|
|
31556
|
+
if (entry.aliases.includes(inputAlias)) {
|
|
31557
|
+
return inputAlias;
|
|
31558
|
+
}
|
|
31559
|
+
const nonWidgetAlias = entry.aliases.find(
|
|
31560
|
+
(alias) => !alias.includes(".widgets[")
|
|
31561
|
+
);
|
|
31562
|
+
return nonWidgetAlias ?? entry.path;
|
|
31189
31563
|
}
|
|
31190
|
-
function
|
|
31191
|
-
|
|
31192
|
-
|
|
31193
|
-
path2.join(process.cwd(), "workflows"),
|
|
31194
|
-
path2.join(homedir(), ".fre4x-comfyui", "workflows"),
|
|
31195
|
-
path2.join(
|
|
31196
|
-
homedir(),
|
|
31197
|
-
"comfy",
|
|
31198
|
-
"ComfyUI",
|
|
31199
|
-
"user",
|
|
31200
|
-
"default",
|
|
31201
|
-
"workflows"
|
|
31202
|
-
)
|
|
31203
|
-
];
|
|
31204
|
-
if (extraPath) {
|
|
31205
|
-
directories.push(extraPath);
|
|
31564
|
+
function formatEditableInputOptionsText(entry) {
|
|
31565
|
+
if (entry.options_count === void 0 || entry.options_preview === void 0 || entry.options_preview.length === 0) {
|
|
31566
|
+
return "";
|
|
31206
31567
|
}
|
|
31207
|
-
|
|
31208
|
-
|
|
31209
|
-
|
|
31568
|
+
const preview = entry.options_preview.map((option) => formatValuePreview(option)).join(", ");
|
|
31569
|
+
const truncatedSuffix = entry.options_truncated ? ", ..." : "";
|
|
31570
|
+
return ` | options: ${preview}${truncatedSuffix} (${entry.options_count} total)`;
|
|
31210
31571
|
}
|
|
31211
|
-
function
|
|
31212
|
-
|
|
31213
|
-
|
|
31214
|
-
return [];
|
|
31572
|
+
function getEditableInputGroup(entry) {
|
|
31573
|
+
if (entry.role === "positive_prompt" || entry.role === "negative_prompt" || entry.role === "prompt_text") {
|
|
31574
|
+
return "prompts";
|
|
31215
31575
|
}
|
|
31216
|
-
if (
|
|
31217
|
-
return
|
|
31576
|
+
if (entry.role === "checkpoint" || entry.input_name === "ckpt_name" || entry.input_name === "model_name" || entry.input_name === "vae_name" || entry.class_type.includes("Checkpoint") || entry.class_type.includes("VAE")) {
|
|
31577
|
+
return "models";
|
|
31218
31578
|
}
|
|
31219
|
-
|
|
31220
|
-
|
|
31221
|
-
function normalizeWorkflowFileName(fileName) {
|
|
31222
|
-
const trimmed = fileName.trim();
|
|
31223
|
-
if (trimmed.length === 0) {
|
|
31224
|
-
throw new WorkflowInputError(
|
|
31225
|
-
"Filename cannot be empty.",
|
|
31226
|
-
"output_file_name"
|
|
31227
|
-
);
|
|
31579
|
+
if (entry.role === "seed" || entry.role === "steps" || entry.role === "cfg" || entry.role === "sampler" || entry.role === "scheduler" || entry.role === "denoise" || entry.class_type.includes("Sampler")) {
|
|
31580
|
+
return "sampling";
|
|
31228
31581
|
}
|
|
31229
|
-
if (
|
|
31230
|
-
|
|
31231
|
-
"Absolute paths are not allowed.",
|
|
31232
|
-
"output_file_name"
|
|
31233
|
-
);
|
|
31582
|
+
if (entry.role === "width" || entry.role === "height" || entry.role === "batch_size") {
|
|
31583
|
+
return "image";
|
|
31234
31584
|
}
|
|
31235
|
-
|
|
31236
|
-
|
|
31237
|
-
async function resolveWorkflowReference(args) {
|
|
31238
|
-
if (args.workflow_file_path) {
|
|
31239
|
-
const targetPath = args.workflow_file_path;
|
|
31240
|
-
try {
|
|
31241
|
-
const resolvedPath = path2.resolve(targetPath);
|
|
31242
|
-
const content = await fs2.readFile(resolvedPath, "utf8");
|
|
31243
|
-
return {
|
|
31244
|
-
targetPath: resolvedPath,
|
|
31245
|
-
content
|
|
31246
|
-
};
|
|
31247
|
-
} catch {
|
|
31248
|
-
if (path2.basename(targetPath) === targetPath) {
|
|
31249
|
-
const candidates = getWorkflowNameCandidates(targetPath);
|
|
31250
|
-
for (const dir of getWorkflowDirectories()) {
|
|
31251
|
-
for (const candidate of candidates) {
|
|
31252
|
-
const candidatePath = path2.join(dir, candidate);
|
|
31253
|
-
try {
|
|
31254
|
-
const content = await fs2.readFile(
|
|
31255
|
-
candidatePath,
|
|
31256
|
-
"utf8"
|
|
31257
|
-
);
|
|
31258
|
-
return {
|
|
31259
|
-
targetPath: candidatePath,
|
|
31260
|
-
content
|
|
31261
|
-
};
|
|
31262
|
-
} catch {
|
|
31263
|
-
}
|
|
31264
|
-
}
|
|
31265
|
-
}
|
|
31266
|
-
}
|
|
31267
|
-
throw new WorkflowInputError(
|
|
31268
|
-
`Workflow file not found: ${targetPath}`,
|
|
31269
|
-
"workflow_file_path",
|
|
31270
|
-
"not_found"
|
|
31271
|
-
);
|
|
31272
|
-
}
|
|
31585
|
+
if (entry.input_name === "lora_name" || entry.input_name.startsWith("strength_") || entry.class_type.includes("Lora")) {
|
|
31586
|
+
return "loras";
|
|
31273
31587
|
}
|
|
31274
|
-
if (
|
|
31275
|
-
|
|
31276
|
-
if (requestedId.length === 0) {
|
|
31277
|
-
throw new WorkflowInputError(
|
|
31278
|
-
"Workflow ID cannot be empty.",
|
|
31279
|
-
"workflow_id"
|
|
31280
|
-
);
|
|
31281
|
-
}
|
|
31282
|
-
const objectInfoAvailability = await tryGetObjectInfo();
|
|
31283
|
-
for (const dir of getWorkflowDirectories()) {
|
|
31284
|
-
try {
|
|
31285
|
-
const entries = await fs2.readdir(dir, { withFileTypes: true });
|
|
31286
|
-
for (const entry of entries) {
|
|
31287
|
-
if (!entry.isFile() || !entry.name.endsWith(".json")) {
|
|
31288
|
-
continue;
|
|
31289
|
-
}
|
|
31290
|
-
const fullPath = path2.join(dir, entry.name);
|
|
31291
|
-
const inspected = await inspectWorkflowFile(
|
|
31292
|
-
fullPath,
|
|
31293
|
-
objectInfoAvailability.objectInfo
|
|
31294
|
-
);
|
|
31295
|
-
if (!inspected) {
|
|
31296
|
-
continue;
|
|
31297
|
-
}
|
|
31298
|
-
if (requestedId === inspected.id || requestedId === inspected.name || requestedId === entry.name.replace(/\.json$/, "")) {
|
|
31299
|
-
return {
|
|
31300
|
-
targetPath: fullPath,
|
|
31301
|
-
content: inspected.content
|
|
31302
|
-
};
|
|
31303
|
-
}
|
|
31304
|
-
}
|
|
31305
|
-
} catch {
|
|
31306
|
-
}
|
|
31307
|
-
}
|
|
31308
|
-
throw new WorkflowInputError(
|
|
31309
|
-
`Workflow ID not found: ${args.workflow_id}`,
|
|
31310
|
-
"workflow_id",
|
|
31311
|
-
"not_found"
|
|
31312
|
-
);
|
|
31588
|
+
if (entry.class_type.includes("ControlNet") || entry.class_type.includes("IPAdapter") || entry.class_type.includes("FreeU") || entry.class_type.includes("FaceDetailer")) {
|
|
31589
|
+
return "control";
|
|
31313
31590
|
}
|
|
31314
|
-
|
|
31315
|
-
|
|
31316
|
-
|
|
31317
|
-
|
|
31591
|
+
if (entry.role === "filename_prefix" || entry.input_name === "filename" || entry.input_name === "path" || entry.input_name === "extension" || entry.class_type.includes("Save") || entry.class_type.includes("PromptSaver")) {
|
|
31592
|
+
return "output";
|
|
31593
|
+
}
|
|
31594
|
+
return "other";
|
|
31318
31595
|
}
|
|
31319
|
-
|
|
31320
|
-
|
|
31321
|
-
|
|
31322
|
-
|
|
31323
|
-
|
|
31324
|
-
|
|
31596
|
+
function getEditableInputPriority(entry) {
|
|
31597
|
+
const rolePriority = {
|
|
31598
|
+
positive_prompt: 300,
|
|
31599
|
+
negative_prompt: 295,
|
|
31600
|
+
prompt_text: 290,
|
|
31601
|
+
checkpoint: 260,
|
|
31602
|
+
seed: 250,
|
|
31603
|
+
steps: 240,
|
|
31604
|
+
cfg: 235,
|
|
31605
|
+
sampler: 230,
|
|
31606
|
+
scheduler: 225,
|
|
31607
|
+
width: 220,
|
|
31608
|
+
height: 219,
|
|
31609
|
+
batch_size: 218,
|
|
31610
|
+
denoise: 215,
|
|
31611
|
+
filename_prefix: 190,
|
|
31612
|
+
text: 180
|
|
31613
|
+
};
|
|
31614
|
+
let priority = rolePriority[entry.role ?? ""] ?? 0;
|
|
31615
|
+
if (entry.input_name === "lora_name") {
|
|
31616
|
+
priority = Math.max(priority, 170);
|
|
31325
31617
|
}
|
|
31326
|
-
|
|
31327
|
-
|
|
31328
|
-
|
|
31329
|
-
if (
|
|
31330
|
-
|
|
31331
|
-
|
|
31332
|
-
|
|
31333
|
-
|
|
31334
|
-
|
|
31618
|
+
if (entry.input_name.startsWith("strength_")) {
|
|
31619
|
+
priority = Math.max(priority, 160);
|
|
31620
|
+
}
|
|
31621
|
+
if (entry.class_type.includes("PromptSaver") || entry.class_type === "SaveImage") {
|
|
31622
|
+
priority -= 70;
|
|
31623
|
+
}
|
|
31624
|
+
if (entry.class_type === "PrimitiveStringMultiline" && (entry.role === "positive_prompt" || entry.role === "negative_prompt")) {
|
|
31625
|
+
priority += 30;
|
|
31626
|
+
}
|
|
31627
|
+
return priority;
|
|
31628
|
+
}
|
|
31629
|
+
function getHighSignalEditableInputs(editableInputs, limit = 12) {
|
|
31630
|
+
const seen = /* @__PURE__ */ new Set();
|
|
31631
|
+
const prioritized = [...editableInputs].sort((left, right) => {
|
|
31632
|
+
const priorityDiff = getEditableInputPriority(right) - getEditableInputPriority(left);
|
|
31633
|
+
if (priorityDiff !== 0) {
|
|
31634
|
+
return priorityDiff;
|
|
31335
31635
|
}
|
|
31336
|
-
|
|
31337
|
-
|
|
31338
|
-
|
|
31339
|
-
|
|
31636
|
+
return getPreferredOverridePath(left).localeCompare(
|
|
31637
|
+
getPreferredOverridePath(right),
|
|
31638
|
+
void 0,
|
|
31639
|
+
{ numeric: true }
|
|
31340
31640
|
);
|
|
31341
|
-
|
|
31342
|
-
|
|
31641
|
+
}).filter((entry) => {
|
|
31642
|
+
const preferredPath = getPreferredOverridePath(entry);
|
|
31643
|
+
if (seen.has(preferredPath)) {
|
|
31644
|
+
return false;
|
|
31645
|
+
}
|
|
31646
|
+
seen.add(preferredPath);
|
|
31647
|
+
return true;
|
|
31648
|
+
});
|
|
31649
|
+
return prioritized.slice(0, limit);
|
|
31650
|
+
}
|
|
31651
|
+
function summarizeEditableInputGroups(editableInputs) {
|
|
31652
|
+
const labels = {
|
|
31653
|
+
prompts: "Prompts",
|
|
31654
|
+
models: "Models and checkpoints",
|
|
31655
|
+
sampling: "Sampling",
|
|
31656
|
+
image: "Image size and batching",
|
|
31657
|
+
loras: "LoRAs and strengths",
|
|
31658
|
+
control: "Conditioning and adapters",
|
|
31659
|
+
output: "Output and persistence",
|
|
31660
|
+
other: "Other"
|
|
31661
|
+
};
|
|
31662
|
+
const summaries = /* @__PURE__ */ new Map();
|
|
31663
|
+
for (const entry of editableInputs) {
|
|
31664
|
+
const group = getEditableInputGroup(entry);
|
|
31665
|
+
const existing = summaries.get(group) ?? {
|
|
31666
|
+
group,
|
|
31667
|
+
label: labels[group],
|
|
31668
|
+
count: 0,
|
|
31669
|
+
sample_paths: []
|
|
31670
|
+
};
|
|
31671
|
+
existing.count += 1;
|
|
31672
|
+
if (existing.sample_paths.length < 3) {
|
|
31673
|
+
existing.sample_paths.push(getPreferredOverridePath(entry));
|
|
31674
|
+
}
|
|
31675
|
+
summaries.set(group, existing);
|
|
31343
31676
|
}
|
|
31344
|
-
const
|
|
31345
|
-
|
|
31346
|
-
|
|
31347
|
-
|
|
31348
|
-
|
|
31349
|
-
|
|
31677
|
+
const order = [
|
|
31678
|
+
"prompts",
|
|
31679
|
+
"models",
|
|
31680
|
+
"sampling",
|
|
31681
|
+
"image",
|
|
31682
|
+
"loras",
|
|
31683
|
+
"control",
|
|
31684
|
+
"output",
|
|
31685
|
+
"other"
|
|
31686
|
+
];
|
|
31687
|
+
return order.map((group) => summaries.get(group)).filter(
|
|
31688
|
+
(summary) => Boolean(summary)
|
|
31689
|
+
);
|
|
31690
|
+
}
|
|
31691
|
+
function formatEditableInputGroupsText(groupSummaries) {
|
|
31692
|
+
if (groupSummaries.length === 0) {
|
|
31693
|
+
return "No editable input groups were detected.";
|
|
31350
31694
|
}
|
|
31351
|
-
return {
|
|
31352
|
-
|
|
31353
|
-
|
|
31354
|
-
|
|
31355
|
-
};
|
|
31695
|
+
return formatListItems(groupSummaries, (summary) => {
|
|
31696
|
+
const examples = summary.sample_paths.length > 0 ? ` | examples: ${summary.sample_paths.join(", ")}` : "";
|
|
31697
|
+
return `${summary.label}: ${summary.count}${examples}`;
|
|
31698
|
+
});
|
|
31356
31699
|
}
|
|
31357
|
-
|
|
31358
|
-
const
|
|
31359
|
-
|
|
31360
|
-
|
|
31361
|
-
|
|
31362
|
-
|
|
31363
|
-
|
|
31364
|
-
|
|
31365
|
-
|
|
31366
|
-
|
|
31700
|
+
function buildOverrideExamples(editableInputs, limit = 5, options = {}) {
|
|
31701
|
+
const valueMode = options.valueMode ?? "full";
|
|
31702
|
+
const examples = {};
|
|
31703
|
+
const preferredPaths = [];
|
|
31704
|
+
for (const entry of getHighSignalEditableInputs(editableInputs, limit)) {
|
|
31705
|
+
const preferredPath = getPreferredOverridePath(entry);
|
|
31706
|
+
preferredPaths.push(preferredPath);
|
|
31707
|
+
if (valueMode === "keys") {
|
|
31708
|
+
continue;
|
|
31709
|
+
}
|
|
31710
|
+
examples[preferredPath] = valueMode === "preview" ? entry.value_preview : entry.value;
|
|
31367
31711
|
}
|
|
31368
|
-
const { prompt, sourceFormat, widgetPathMap } = await normalizeWorkflowPrompt(parsed, objectInfo);
|
|
31369
31712
|
return {
|
|
31370
|
-
|
|
31371
|
-
|
|
31372
|
-
sourceFormat,
|
|
31373
|
-
widgetPathMap
|
|
31713
|
+
examples,
|
|
31714
|
+
preferred_paths: preferredPaths
|
|
31374
31715
|
};
|
|
31375
31716
|
}
|
|
31376
|
-
|
|
31377
|
-
|
|
31378
|
-
|
|
31379
|
-
|
|
31380
|
-
|
|
31381
|
-
|
|
31382
|
-
|
|
31383
|
-
|
|
31384
|
-
|
|
31717
|
+
function formatOverrideExamplesText(examples) {
|
|
31718
|
+
return JSON.stringify(examples, null, 2);
|
|
31719
|
+
}
|
|
31720
|
+
function analyzeEditableInputs(prompt, objectInfo, widgetPathMap = {}) {
|
|
31721
|
+
const consumerMap = /* @__PURE__ */ new Map();
|
|
31722
|
+
for (const [nodeId, node] of Object.entries(prompt)) {
|
|
31723
|
+
for (const [inputName, value] of Object.entries(node.inputs)) {
|
|
31724
|
+
if (!isConnectionValue(value)) {
|
|
31725
|
+
continue;
|
|
31726
|
+
}
|
|
31727
|
+
const sourceNodeId = String(value[0]);
|
|
31728
|
+
const consumers = consumerMap.get(sourceNodeId) ?? [];
|
|
31729
|
+
consumers.push({
|
|
31730
|
+
inputName,
|
|
31731
|
+
classType: node.class_type,
|
|
31732
|
+
nodeId
|
|
31733
|
+
});
|
|
31734
|
+
consumerMap.set(sourceNodeId, consumers);
|
|
31735
|
+
}
|
|
31385
31736
|
}
|
|
31386
|
-
|
|
31387
|
-
|
|
31388
|
-
|
|
31389
|
-
|
|
31390
|
-
|
|
31391
|
-
|
|
31737
|
+
const editableInputs = [];
|
|
31738
|
+
for (const [nodeId, node] of Object.entries(prompt)) {
|
|
31739
|
+
for (const [inputName, value] of Object.entries(node.inputs)) {
|
|
31740
|
+
if (isConnectionValue(value)) {
|
|
31741
|
+
continue;
|
|
31742
|
+
}
|
|
31743
|
+
editableInputs.push({
|
|
31744
|
+
path: `${nodeId}.inputs.${inputName}`,
|
|
31745
|
+
node_id: nodeId,
|
|
31746
|
+
class_type: node.class_type,
|
|
31747
|
+
input_name: inputName,
|
|
31748
|
+
value,
|
|
31749
|
+
value_preview: formatValuePreview(value),
|
|
31750
|
+
role: inferEditableRole(
|
|
31751
|
+
nodeId,
|
|
31752
|
+
node.class_type,
|
|
31753
|
+
inputName,
|
|
31754
|
+
consumerMap
|
|
31755
|
+
),
|
|
31756
|
+
aliases: [],
|
|
31757
|
+
expected_type: getExpectedTypeName(
|
|
31758
|
+
getInputDefinition(objectInfo, prompt, nodeId, inputName)
|
|
31759
|
+
),
|
|
31760
|
+
...getInputOptionsSummary(
|
|
31761
|
+
getInputDefinition(objectInfo, prompt, nodeId, inputName)
|
|
31762
|
+
)
|
|
31763
|
+
});
|
|
31392
31764
|
}
|
|
31393
31765
|
}
|
|
31394
|
-
|
|
31395
|
-
|
|
31396
|
-
|
|
31397
|
-
|
|
31398
|
-
|
|
31399
|
-
|
|
31400
|
-
|
|
31401
|
-
|
|
31402
|
-
|
|
31403
|
-
|
|
31404
|
-
|
|
31405
|
-
|
|
31406
|
-
|
|
31407
|
-
|
|
31408
|
-
return {
|
|
31409
|
-
id: defaultId,
|
|
31410
|
-
name,
|
|
31411
|
-
path: fullPath,
|
|
31412
|
-
source_format: "webui",
|
|
31413
|
-
validation_status: "conversion_validated",
|
|
31414
|
-
content,
|
|
31415
|
-
isWebUI: true
|
|
31416
|
-
};
|
|
31417
|
-
} catch (error48) {
|
|
31418
|
-
return {
|
|
31419
|
-
id: defaultId,
|
|
31420
|
-
name,
|
|
31421
|
-
path: fullPath,
|
|
31422
|
-
source_format: "webui",
|
|
31423
|
-
validation_status: "conversion_failed",
|
|
31424
|
-
content,
|
|
31425
|
-
error: summarizeErrorMessage(error48),
|
|
31426
|
-
isWebUI: true
|
|
31427
|
-
};
|
|
31428
|
-
}
|
|
31429
|
-
}
|
|
31430
|
-
try {
|
|
31431
|
-
await normalizeWorkflowPrompt(parsed);
|
|
31432
|
-
return {
|
|
31433
|
-
id: defaultId,
|
|
31434
|
-
name,
|
|
31435
|
-
path: fullPath,
|
|
31436
|
-
source_format: "api",
|
|
31437
|
-
validation_status: "ready",
|
|
31438
|
-
content,
|
|
31439
|
-
isWebUI: false
|
|
31440
|
-
};
|
|
31441
|
-
} catch {
|
|
31442
|
-
return void 0;
|
|
31443
|
-
}
|
|
31766
|
+
const withAliases = editableInputs.map((entry) => ({
|
|
31767
|
+
...entry,
|
|
31768
|
+
aliases: getEditableAliases(
|
|
31769
|
+
entry.path,
|
|
31770
|
+
entry.node_id,
|
|
31771
|
+
entry.input_name,
|
|
31772
|
+
entry.role,
|
|
31773
|
+
widgetPathMap
|
|
31774
|
+
)
|
|
31775
|
+
}));
|
|
31776
|
+
applyUniqueGlobalRoleAliases(withAliases);
|
|
31777
|
+
return withAliases.sort(
|
|
31778
|
+
(left, right) => left.path.localeCompare(right.path, void 0, { numeric: true })
|
|
31779
|
+
);
|
|
31444
31780
|
}
|
|
31445
|
-
function
|
|
31446
|
-
|
|
31447
|
-
|
|
31781
|
+
function formatWorkflowInspectionText(params) {
|
|
31782
|
+
const textSections = [...params.introLines];
|
|
31783
|
+
if (params.warning) {
|
|
31784
|
+
textSections.push(params.warning);
|
|
31448
31785
|
}
|
|
31449
|
-
|
|
31450
|
-
|
|
31786
|
+
textSections.push(
|
|
31787
|
+
`High-signal overrides (${params.highSignalInputs.length}):`,
|
|
31788
|
+
formatEditableInputsText(params.highSignalInputs, {
|
|
31789
|
+
includeOptions: true
|
|
31790
|
+
}),
|
|
31791
|
+
"Editable input groups:",
|
|
31792
|
+
formatEditableInputGroupsText(params.editableInputGroups)
|
|
31793
|
+
);
|
|
31794
|
+
if (params.verbose) {
|
|
31795
|
+
textSections.push(
|
|
31796
|
+
`Editable inputs (${params.editableInputs.length} total):`,
|
|
31797
|
+
formatEditableInputsText(params.editableInputs)
|
|
31798
|
+
);
|
|
31799
|
+
} else if (params.editableInputs.length > params.highSignalInputs.length) {
|
|
31800
|
+
textSections.push(
|
|
31801
|
+
`Pass verbose=true to list all ${params.editableInputs.length} editable inputs.`
|
|
31802
|
+
);
|
|
31451
31803
|
}
|
|
31452
|
-
|
|
31453
|
-
}
|
|
31454
|
-
|
|
31455
|
-
|
|
31804
|
+
textSections.push(
|
|
31805
|
+
`Copy-ready override keys: ${params.preferredOverridePaths.length > 0 ? params.preferredOverridePaths.join(", ") : "(none)"}`,
|
|
31806
|
+
"Example values (truncated):",
|
|
31807
|
+
formatOverrideExamplesText(params.overrideExamples)
|
|
31808
|
+
);
|
|
31809
|
+
return textSections.join("\n");
|
|
31456
31810
|
}
|
|
31457
|
-
|
|
31458
|
-
|
|
31459
|
-
|
|
31460
|
-
|
|
31811
|
+
function formatEditableInputsText(editableInputs, options = {}) {
|
|
31812
|
+
if (editableInputs.length === 0) {
|
|
31813
|
+
return "No literal editable inputs were detected.";
|
|
31814
|
+
}
|
|
31815
|
+
return formatListItems(editableInputs, (entry) => {
|
|
31816
|
+
const role = entry.role ? ` [${entry.role}]` : "";
|
|
31817
|
+
const classType = entry.class_type ? ` (${entry.class_type})` : "";
|
|
31818
|
+
const expectedType = entry.expected_type ? ` <${entry.expected_type}>` : "";
|
|
31819
|
+
const aliasSuffix = entry.aliases.length > 0 ? ` | aliases: ${entry.aliases.join(", ")}` : "";
|
|
31820
|
+
const optionsSuffix = options.includeOptions ? formatEditableInputOptionsText(entry) : "";
|
|
31821
|
+
return `${entry.path}${role}${classType}${expectedType}: ${entry.value_preview}${aliasSuffix}${optionsSuffix}`;
|
|
31822
|
+
});
|
|
31461
31823
|
}
|
|
31462
|
-
function
|
|
31463
|
-
|
|
31464
|
-
|
|
31465
|
-
|
|
31466
|
-
|
|
31824
|
+
function formatInputDefinitionSummary(inputName, inputDefinition) {
|
|
31825
|
+
const [typeData, extraInfo] = inputDefinition;
|
|
31826
|
+
const typeLabel = Array.isArray(typeData) ? `COMBO (${typeData.length} options)` : String(typeData).toUpperCase();
|
|
31827
|
+
const details = [];
|
|
31828
|
+
if (Array.isArray(typeData)) {
|
|
31829
|
+
details.push(
|
|
31830
|
+
`options: ${typeData.slice(0, 5).map((option) => JSON.stringify(option)).join(", ")}${typeData.length > 5 ? ", ..." : ""}`
|
|
31831
|
+
);
|
|
31832
|
+
}
|
|
31833
|
+
if (isRecord(extraInfo)) {
|
|
31834
|
+
if (extraInfo.default !== void 0) {
|
|
31835
|
+
details.push(`default: ${formatValuePreview(extraInfo.default)}`);
|
|
31467
31836
|
}
|
|
31468
|
-
if (
|
|
31469
|
-
|
|
31837
|
+
if (typeof extraInfo.min === "number") {
|
|
31838
|
+
details.push(`min: ${extraInfo.min}`);
|
|
31470
31839
|
}
|
|
31471
|
-
|
|
31472
|
-
|
|
31473
|
-
if (inputName === "value") {
|
|
31474
|
-
const consumers = consumerMap.get(nodeId) ?? [];
|
|
31475
|
-
const textConsumers = consumers.filter(
|
|
31476
|
-
(consumer) => consumer.classType === "CLIPTextEncode" && consumer.inputName === "text"
|
|
31477
|
-
);
|
|
31478
|
-
if (textConsumers.length > 0) {
|
|
31479
|
-
for (const consumer of textConsumers) {
|
|
31480
|
-
const clipConsumers = consumerMap.get(consumer.nodeId) ?? [];
|
|
31481
|
-
if (clipConsumers.some(
|
|
31482
|
-
(clipConsumer) => clipConsumer.inputName === "positive"
|
|
31483
|
-
)) {
|
|
31484
|
-
return "positive_prompt";
|
|
31485
|
-
}
|
|
31486
|
-
if (clipConsumers.some(
|
|
31487
|
-
(clipConsumer) => clipConsumer.inputName === "negative"
|
|
31488
|
-
)) {
|
|
31489
|
-
return "negative_prompt";
|
|
31490
|
-
}
|
|
31491
|
-
}
|
|
31492
|
-
return "prompt_text";
|
|
31840
|
+
if (typeof extraInfo.max === "number") {
|
|
31841
|
+
details.push(`max: ${extraInfo.max}`);
|
|
31493
31842
|
}
|
|
31494
31843
|
}
|
|
31495
|
-
|
|
31496
|
-
case "seed":
|
|
31497
|
-
return "seed";
|
|
31498
|
-
case "steps":
|
|
31499
|
-
return "steps";
|
|
31500
|
-
case "cfg":
|
|
31501
|
-
return "cfg";
|
|
31502
|
-
case "sampler_name":
|
|
31503
|
-
return "sampler";
|
|
31504
|
-
case "scheduler":
|
|
31505
|
-
return "scheduler";
|
|
31506
|
-
case "denoise":
|
|
31507
|
-
return "denoise";
|
|
31508
|
-
case "width":
|
|
31509
|
-
return "width";
|
|
31510
|
-
case "height":
|
|
31511
|
-
return "height";
|
|
31512
|
-
case "batch_size":
|
|
31513
|
-
return "batch_size";
|
|
31514
|
-
case "ckpt_name":
|
|
31515
|
-
return "checkpoint";
|
|
31516
|
-
case "filename_prefix":
|
|
31517
|
-
return "filename_prefix";
|
|
31518
|
-
case "text":
|
|
31519
|
-
return "text";
|
|
31520
|
-
default:
|
|
31521
|
-
return void 0;
|
|
31522
|
-
}
|
|
31844
|
+
return `${inputName} <${typeLabel}>${details.length > 0 ? ` | ${details.join(" | ")}` : ""}`;
|
|
31523
31845
|
}
|
|
31524
|
-
function
|
|
31525
|
-
const
|
|
31526
|
-
|
|
31527
|
-
|
|
31528
|
-
|
|
31529
|
-
|
|
31530
|
-
|
|
31531
|
-
|
|
31532
|
-
|
|
31846
|
+
function formatNodeDefinitionText(nodeClass, node) {
|
|
31847
|
+
const requiredInputs = Object.entries(node.input.required ?? {});
|
|
31848
|
+
const optionalInputs = Object.entries(node.input.optional ?? {});
|
|
31849
|
+
const lines = [
|
|
31850
|
+
`Node: ${nodeClass}`,
|
|
31851
|
+
`Display name: ${node.display_name || node.name || nodeClass}`,
|
|
31852
|
+
`Category: ${node.category || "uncategorized"}`
|
|
31853
|
+
];
|
|
31854
|
+
if (typeof node.description === "string" && node.description.trim().length > 0) {
|
|
31855
|
+
lines.push(`Description: ${node.description.trim()}`);
|
|
31533
31856
|
}
|
|
31534
|
-
|
|
31535
|
-
|
|
31857
|
+
lines.push(
|
|
31858
|
+
`Required inputs (${requiredInputs.length}): ${requiredInputs.length > 0 ? requiredInputs.map(
|
|
31859
|
+
([inputName, inputDefinition]) => formatInputDefinitionSummary(
|
|
31860
|
+
inputName,
|
|
31861
|
+
inputDefinition
|
|
31862
|
+
)
|
|
31863
|
+
).join("\n") : "None."}`
|
|
31864
|
+
);
|
|
31865
|
+
lines.push(
|
|
31866
|
+
`Optional inputs (${optionalInputs.length}): ${optionalInputs.length > 0 ? optionalInputs.map(
|
|
31867
|
+
([inputName, inputDefinition]) => formatInputDefinitionSummary(
|
|
31868
|
+
inputName,
|
|
31869
|
+
inputDefinition
|
|
31870
|
+
)
|
|
31871
|
+
).join("\n") : "None."}`
|
|
31872
|
+
);
|
|
31873
|
+
lines.push(`Outputs: ${node.output.join(", ") || "None."}`);
|
|
31874
|
+
return lines.join("\n");
|
|
31536
31875
|
}
|
|
31537
|
-
function
|
|
31538
|
-
const
|
|
31539
|
-
|
|
31540
|
-
|
|
31541
|
-
|
|
31542
|
-
|
|
31543
|
-
|
|
31544
|
-
|
|
31545
|
-
|
|
31546
|
-
|
|
31547
|
-
|
|
31548
|
-
const existing = aliasToPath.get(alias);
|
|
31549
|
-
if (!existing) {
|
|
31550
|
-
aliasToPath.set(alias, entry.path);
|
|
31551
|
-
continue;
|
|
31552
|
-
}
|
|
31553
|
-
if (existing === entry.path) {
|
|
31554
|
-
continue;
|
|
31555
|
-
}
|
|
31556
|
-
const ambiguous = new Set(
|
|
31557
|
-
ambiguousAliases.get(alias) ?? [existing]
|
|
31558
|
-
);
|
|
31559
|
-
ambiguous.add(entry.path);
|
|
31560
|
-
ambiguousAliases.set(alias, [...ambiguous].sort());
|
|
31561
|
-
aliasToPath.delete(alias);
|
|
31562
|
-
}
|
|
31876
|
+
function formatNodeListText(classes, rawInfo, pagination) {
|
|
31877
|
+
const text = `Available nodes (${pagination.total} total):
|
|
31878
|
+
` + formatListItems(classes, (nodeClass) => {
|
|
31879
|
+
const node = rawInfo[nodeClass];
|
|
31880
|
+
const displayName = node.display_name && node.display_name !== nodeClass ? ` \u2014 ${node.display_name}` : "";
|
|
31881
|
+
const category = node.category ? ` | category: ${node.category}` : "";
|
|
31882
|
+
const description = typeof node.description === "string" && node.description.trim().length > 0 ? ` | ${node.description.trim()}` : "";
|
|
31883
|
+
return `${nodeClass}${displayName}${category}${description}`;
|
|
31884
|
+
});
|
|
31885
|
+
if (!pagination.hasMore) {
|
|
31886
|
+
return text;
|
|
31563
31887
|
}
|
|
31564
|
-
return {
|
|
31565
|
-
|
|
31566
|
-
|
|
31567
|
-
|
|
31568
|
-
|
|
31569
|
-
|
|
31888
|
+
return `${text}
|
|
31889
|
+
|
|
31890
|
+
${formatPaginationFooter(
|
|
31891
|
+
pagination.offset,
|
|
31892
|
+
pagination.limit,
|
|
31893
|
+
pagination.total
|
|
31894
|
+
)}`;
|
|
31570
31895
|
}
|
|
31571
|
-
|
|
31572
|
-
|
|
31573
|
-
|
|
31574
|
-
|
|
31575
|
-
|
|
31576
|
-
|
|
31577
|
-
|
|
31578
|
-
|
|
31579
|
-
|
|
31580
|
-
|
|
31581
|
-
|
|
31582
|
-
|
|
31583
|
-
|
|
31584
|
-
|
|
31585
|
-
|
|
31586
|
-
|
|
31587
|
-
|
|
31588
|
-
return "";
|
|
31589
|
-
}
|
|
31590
|
-
return entries.map((entry) => {
|
|
31591
|
-
const aliasSuffix = entry.aliases.length > 0 ? ` (aliases: ${entry.aliases.join(", ")})` : "";
|
|
31592
|
-
return `${entry.path}${aliasSuffix}`;
|
|
31593
|
-
}).join("\n");
|
|
31896
|
+
|
|
31897
|
+
// src/workflow/load.ts
|
|
31898
|
+
import * as crypto from "node:crypto";
|
|
31899
|
+
import * as fs2 from "node:fs/promises";
|
|
31900
|
+
import { homedir } from "node:os";
|
|
31901
|
+
import path2 from "node:path";
|
|
31902
|
+
|
|
31903
|
+
// src/converter.ts
|
|
31904
|
+
var WIDGET_TYPES = /* @__PURE__ */ new Set(["INT", "FLOAT", "STRING", "BOOLEAN"]);
|
|
31905
|
+
var UI_CONTROL_VALUES = /* @__PURE__ */ new Set([
|
|
31906
|
+
"fixed",
|
|
31907
|
+
"increment",
|
|
31908
|
+
"decrement",
|
|
31909
|
+
"randomize"
|
|
31910
|
+
]);
|
|
31911
|
+
function isRecord2(value) {
|
|
31912
|
+
return typeof value === "object" && value !== null;
|
|
31594
31913
|
}
|
|
31595
|
-
function
|
|
31596
|
-
|
|
31597
|
-
if (roleAlias && entry.aliases.includes(roleAlias)) {
|
|
31598
|
-
return roleAlias;
|
|
31599
|
-
}
|
|
31600
|
-
const inputAlias = `${entry.node_id}.${entry.input_name}`;
|
|
31601
|
-
if (entry.aliases.includes(inputAlias)) {
|
|
31602
|
-
return inputAlias;
|
|
31603
|
-
}
|
|
31604
|
-
const nonWidgetAlias = entry.aliases.find(
|
|
31605
|
-
(alias) => !alias.includes(".widgets[")
|
|
31606
|
-
);
|
|
31607
|
-
return nonWidgetAlias ?? entry.path;
|
|
31914
|
+
function isWebUIFormat(json2) {
|
|
31915
|
+
return isRecord2(json2) && Array.isArray(json2.nodes) && Array.isArray(json2.links);
|
|
31608
31916
|
}
|
|
31609
|
-
function
|
|
31610
|
-
|
|
31611
|
-
return "";
|
|
31612
|
-
}
|
|
31613
|
-
const preview = entry.options_preview.map((option) => formatValuePreview(option)).join(", ");
|
|
31614
|
-
const truncatedSuffix = entry.options_truncated ? ", ..." : "";
|
|
31615
|
-
return ` | options: ${preview}${truncatedSuffix} (${entry.options_count} total)`;
|
|
31917
|
+
function isLinkData(link) {
|
|
31918
|
+
return Array.isArray(link) && link.length >= 6 && typeof link[0] === "number" && typeof link[1] === "number" && typeof link[2] === "number" && typeof link[3] === "number" && typeof link[4] === "number" && typeof link[5] === "string";
|
|
31616
31919
|
}
|
|
31617
|
-
function
|
|
31618
|
-
if (
|
|
31619
|
-
return
|
|
31620
|
-
}
|
|
31621
|
-
if (entry.role === "checkpoint" || entry.input_name === "ckpt_name" || entry.input_name === "model_name" || entry.input_name === "vae_name" || entry.class_type.includes("Checkpoint") || entry.class_type.includes("VAE")) {
|
|
31622
|
-
return "models";
|
|
31623
|
-
}
|
|
31624
|
-
if (entry.role === "seed" || entry.role === "steps" || entry.role === "cfg" || entry.role === "sampler" || entry.role === "scheduler" || entry.role === "denoise" || entry.class_type.includes("Sampler")) {
|
|
31625
|
-
return "sampling";
|
|
31626
|
-
}
|
|
31627
|
-
if (entry.role === "width" || entry.role === "height" || entry.role === "batch_size") {
|
|
31628
|
-
return "image";
|
|
31629
|
-
}
|
|
31630
|
-
if (entry.input_name === "lora_name" || entry.input_name.startsWith("strength_") || entry.class_type.includes("Lora")) {
|
|
31631
|
-
return "loras";
|
|
31920
|
+
function getOrderedInputEntries(group, orderedNames) {
|
|
31921
|
+
if (!group) {
|
|
31922
|
+
return [];
|
|
31632
31923
|
}
|
|
31633
|
-
|
|
31634
|
-
|
|
31924
|
+
const seen = /* @__PURE__ */ new Set();
|
|
31925
|
+
const orderedEntries = [];
|
|
31926
|
+
for (const inputName of orderedNames ?? []) {
|
|
31927
|
+
const inputDefinition = group[inputName];
|
|
31928
|
+
if (!inputDefinition) {
|
|
31929
|
+
continue;
|
|
31930
|
+
}
|
|
31931
|
+
orderedEntries.push([inputName, inputDefinition]);
|
|
31932
|
+
seen.add(inputName);
|
|
31635
31933
|
}
|
|
31636
|
-
|
|
31637
|
-
|
|
31934
|
+
for (const [inputName, inputDefinition] of Object.entries(group)) {
|
|
31935
|
+
if (seen.has(inputName)) {
|
|
31936
|
+
continue;
|
|
31937
|
+
}
|
|
31938
|
+
orderedEntries.push([inputName, inputDefinition]);
|
|
31638
31939
|
}
|
|
31639
|
-
return
|
|
31940
|
+
return orderedEntries;
|
|
31640
31941
|
}
|
|
31641
|
-
function
|
|
31642
|
-
|
|
31643
|
-
|
|
31644
|
-
|
|
31645
|
-
|
|
31646
|
-
|
|
31647
|
-
|
|
31648
|
-
|
|
31649
|
-
|
|
31650
|
-
|
|
31651
|
-
scheduler: 225,
|
|
31652
|
-
width: 220,
|
|
31653
|
-
height: 219,
|
|
31654
|
-
batch_size: 218,
|
|
31655
|
-
denoise: 215,
|
|
31656
|
-
filename_prefix: 190,
|
|
31657
|
-
text: 180
|
|
31658
|
-
};
|
|
31659
|
-
let priority = rolePriority[entry.role ?? ""] ?? 0;
|
|
31660
|
-
if (entry.input_name === "lora_name") {
|
|
31661
|
-
priority = Math.max(priority, 170);
|
|
31942
|
+
function getWidgetTypeName(inputTypeData) {
|
|
31943
|
+
return Array.isArray(inputTypeData) ? "COMBO" : String(inputTypeData).toUpperCase();
|
|
31944
|
+
}
|
|
31945
|
+
function isWidgetInput(inputTypeData) {
|
|
31946
|
+
const typeName = getWidgetTypeName(inputTypeData);
|
|
31947
|
+
return typeName === "COMBO" || WIDGET_TYPES.has(typeName);
|
|
31948
|
+
}
|
|
31949
|
+
function isSkippableUiArtifact(value, expectedTypeName) {
|
|
31950
|
+
if (expectedTypeName !== "STRING" && typeof value === "string" && UI_CONTROL_VALUES.has(value)) {
|
|
31951
|
+
return true;
|
|
31662
31952
|
}
|
|
31663
|
-
|
|
31664
|
-
|
|
31953
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
31954
|
+
}
|
|
31955
|
+
function normalizeWidgetValue(value, typeName, extraInfo) {
|
|
31956
|
+
const defaultValue = extraInfo?.default;
|
|
31957
|
+
const shouldUseDefaultForNumeric = (typeName === "INT" || typeName === "FLOAT") && (value === "" || value === void 0 || value === null);
|
|
31958
|
+
if (shouldUseDefaultForNumeric && defaultValue !== void 0) {
|
|
31959
|
+
return defaultValue;
|
|
31665
31960
|
}
|
|
31666
|
-
|
|
31667
|
-
|
|
31961
|
+
return value;
|
|
31962
|
+
}
|
|
31963
|
+
function convertWebUIToAPIWithMetadata(webUIJson, objectInfo) {
|
|
31964
|
+
const prompt = {};
|
|
31965
|
+
const widgetPathMap = {};
|
|
31966
|
+
const links = webUIJson.links || [];
|
|
31967
|
+
const nodes = webUIJson.nodes || [];
|
|
31968
|
+
const serverNodeIds = /* @__PURE__ */ new Set();
|
|
31969
|
+
for (const node of nodes) {
|
|
31970
|
+
if (!node.id || !node.type || !objectInfo[node.type]) {
|
|
31971
|
+
continue;
|
|
31972
|
+
}
|
|
31973
|
+
serverNodeIds.add(String(node.id));
|
|
31668
31974
|
}
|
|
31669
|
-
|
|
31670
|
-
|
|
31975
|
+
const linkMap = /* @__PURE__ */ new Map();
|
|
31976
|
+
for (const link of links) {
|
|
31977
|
+
if (isLinkData(link) && serverNodeIds.has(String(link[3]))) {
|
|
31978
|
+
linkMap.set(link[0], link);
|
|
31979
|
+
}
|
|
31671
31980
|
}
|
|
31672
|
-
|
|
31673
|
-
|
|
31674
|
-
|
|
31675
|
-
|
|
31676
|
-
|
|
31677
|
-
const
|
|
31678
|
-
|
|
31679
|
-
|
|
31981
|
+
for (const node of nodes) {
|
|
31982
|
+
if (!node.id || !node.type) continue;
|
|
31983
|
+
const objInfo = objectInfo[node.type];
|
|
31984
|
+
if (!objInfo) continue;
|
|
31985
|
+
const nodeIdStr = String(node.id);
|
|
31986
|
+
const promptNode = {
|
|
31987
|
+
class_type: node.type,
|
|
31988
|
+
inputs: {}
|
|
31989
|
+
};
|
|
31990
|
+
const connectedInputNames = /* @__PURE__ */ new Set();
|
|
31991
|
+
if (Array.isArray(node.inputs)) {
|
|
31992
|
+
for (const input of node.inputs) {
|
|
31993
|
+
if (input.name && input.link !== null && input.link !== void 0) {
|
|
31994
|
+
const linkData = linkMap.get(input.link);
|
|
31995
|
+
if (Array.isArray(linkData) && linkData.length >= 6 && String(linkData[3]) === nodeIdStr) {
|
|
31996
|
+
const originNodeId = String(linkData[1]);
|
|
31997
|
+
const originSlot = linkData[2];
|
|
31998
|
+
promptNode.inputs[input.name] = [
|
|
31999
|
+
originNodeId,
|
|
32000
|
+
originSlot
|
|
32001
|
+
];
|
|
32002
|
+
connectedInputNames.add(input.name);
|
|
32003
|
+
}
|
|
32004
|
+
}
|
|
32005
|
+
}
|
|
31680
32006
|
}
|
|
31681
|
-
|
|
31682
|
-
|
|
31683
|
-
|
|
31684
|
-
|
|
31685
|
-
|
|
31686
|
-
|
|
31687
|
-
|
|
31688
|
-
|
|
31689
|
-
|
|
32007
|
+
if (objInfo) {
|
|
32008
|
+
const widgets = Array.isArray(node.widgets_values) ? node.widgets_values : [];
|
|
32009
|
+
let widgetIndex = 0;
|
|
32010
|
+
const processInputGroup = (group, orderedNames) => {
|
|
32011
|
+
for (const [inputName, inputDataRaw] of getOrderedInputEntries(
|
|
32012
|
+
group,
|
|
32013
|
+
orderedNames
|
|
32014
|
+
)) {
|
|
32015
|
+
const [inputTypeData, inputExtraInfo] = inputDataRaw;
|
|
32016
|
+
if (!isWidgetInput(inputTypeData)) {
|
|
32017
|
+
continue;
|
|
32018
|
+
}
|
|
32019
|
+
const typeName = getWidgetTypeName(inputTypeData);
|
|
32020
|
+
while (widgetIndex < widgets.length && isSkippableUiArtifact(widgets[widgetIndex], typeName)) {
|
|
32021
|
+
widgetIndex++;
|
|
32022
|
+
}
|
|
32023
|
+
const isConnectedWidget = connectedInputNames.has(inputName);
|
|
32024
|
+
if (widgetIndex >= widgets.length) {
|
|
32025
|
+
if (!isConnectedWidget && inputExtraInfo?.default !== void 0) {
|
|
32026
|
+
promptNode.inputs[inputName] = inputExtraInfo.default;
|
|
32027
|
+
}
|
|
32028
|
+
continue;
|
|
32029
|
+
}
|
|
32030
|
+
const widgetSourceIndex = widgetIndex;
|
|
32031
|
+
const value = normalizeWidgetValue(
|
|
32032
|
+
widgets[widgetSourceIndex],
|
|
32033
|
+
typeName,
|
|
32034
|
+
inputExtraInfo
|
|
32035
|
+
);
|
|
32036
|
+
widgetIndex++;
|
|
32037
|
+
if (!isConnectedWidget) {
|
|
32038
|
+
promptNode.inputs[inputName] = value;
|
|
32039
|
+
widgetPathMap[`${nodeIdStr}.widgets[${widgetSourceIndex}]`] = `${nodeIdStr}.inputs.${inputName}`;
|
|
32040
|
+
}
|
|
32041
|
+
}
|
|
32042
|
+
};
|
|
32043
|
+
processInputGroup(
|
|
32044
|
+
objInfo.input?.required,
|
|
32045
|
+
objInfo.input_order?.required
|
|
32046
|
+
);
|
|
32047
|
+
processInputGroup(
|
|
32048
|
+
objInfo.input?.optional,
|
|
32049
|
+
objInfo.input_order?.optional
|
|
32050
|
+
);
|
|
31690
32051
|
}
|
|
31691
|
-
|
|
31692
|
-
|
|
31693
|
-
|
|
31694
|
-
|
|
31695
|
-
|
|
31696
|
-
function summarizeEditableInputGroups(editableInputs) {
|
|
31697
|
-
const labels = {
|
|
31698
|
-
prompts: "Prompts",
|
|
31699
|
-
models: "Models and checkpoints",
|
|
31700
|
-
sampling: "Sampling",
|
|
31701
|
-
image: "Image size and batching",
|
|
31702
|
-
loras: "LoRAs and strengths",
|
|
31703
|
-
control: "Conditioning and adapters",
|
|
31704
|
-
output: "Output and persistence",
|
|
31705
|
-
other: "Other"
|
|
32052
|
+
prompt[nodeIdStr] = promptNode;
|
|
32053
|
+
}
|
|
32054
|
+
return {
|
|
32055
|
+
prompt,
|
|
32056
|
+
widgetPathMap
|
|
31706
32057
|
};
|
|
31707
|
-
|
|
31708
|
-
|
|
31709
|
-
|
|
31710
|
-
|
|
31711
|
-
|
|
31712
|
-
|
|
31713
|
-
|
|
31714
|
-
|
|
31715
|
-
|
|
31716
|
-
|
|
31717
|
-
|
|
31718
|
-
|
|
32058
|
+
}
|
|
32059
|
+
|
|
32060
|
+
// src/workflow/load.ts
|
|
32061
|
+
function isComfyPromptNode(value) {
|
|
32062
|
+
return isRecord(value) && typeof value.class_type === "string" && isRecord(value.inputs);
|
|
32063
|
+
}
|
|
32064
|
+
function isUiOnlyPromptNode(nodeId, node) {
|
|
32065
|
+
return nodeId === "meta" || ["Note", "MarkdownNote", "PrimitiveNode"].includes(node.class_type);
|
|
32066
|
+
}
|
|
32067
|
+
function extractRunnablePromptNodes(prompt) {
|
|
32068
|
+
const runnablePrompt = {};
|
|
32069
|
+
for (const [nodeId, rawNode] of Object.entries(prompt)) {
|
|
32070
|
+
if (!isComfyPromptNode(rawNode) || isUiOnlyPromptNode(nodeId, rawNode)) {
|
|
32071
|
+
continue;
|
|
31719
32072
|
}
|
|
31720
|
-
|
|
32073
|
+
runnablePrompt[nodeId] = rawNode;
|
|
31721
32074
|
}
|
|
31722
|
-
|
|
31723
|
-
|
|
31724
|
-
|
|
31725
|
-
|
|
31726
|
-
|
|
31727
|
-
|
|
31728
|
-
|
|
31729
|
-
|
|
31730
|
-
|
|
32075
|
+
if (Object.keys(runnablePrompt).length === 0) {
|
|
32076
|
+
throw new WorkflowInputError(
|
|
32077
|
+
"Workflow prompt does not contain any runnable ComfyUI nodes.",
|
|
32078
|
+
"workflow"
|
|
32079
|
+
);
|
|
32080
|
+
}
|
|
32081
|
+
return runnablePrompt;
|
|
32082
|
+
}
|
|
32083
|
+
function getWorkflowDirectories(extraPath) {
|
|
32084
|
+
const directories = [
|
|
32085
|
+
process.cwd(),
|
|
32086
|
+
path2.join(process.cwd(), "workflows"),
|
|
32087
|
+
path2.join(homedir(), ".fre4x-comfyui", "workflows"),
|
|
32088
|
+
path2.join(
|
|
32089
|
+
homedir(),
|
|
32090
|
+
"comfy",
|
|
32091
|
+
"ComfyUI",
|
|
32092
|
+
"user",
|
|
32093
|
+
"default",
|
|
32094
|
+
"workflows"
|
|
32095
|
+
)
|
|
32096
|
+
];
|
|
32097
|
+
if (extraPath) {
|
|
32098
|
+
directories.push(extraPath);
|
|
32099
|
+
}
|
|
32100
|
+
return [
|
|
32101
|
+
...new Set(directories.map((directory) => path2.resolve(directory)))
|
|
31731
32102
|
];
|
|
31732
|
-
return order.map((group) => summaries.get(group)).filter(
|
|
31733
|
-
(summary) => Boolean(summary)
|
|
31734
|
-
);
|
|
31735
32103
|
}
|
|
31736
|
-
function
|
|
31737
|
-
|
|
31738
|
-
|
|
32104
|
+
function getWorkflowNameCandidates(name) {
|
|
32105
|
+
const trimmed = name.trim();
|
|
32106
|
+
if (trimmed.length === 0) {
|
|
32107
|
+
return [];
|
|
31739
32108
|
}
|
|
31740
|
-
|
|
31741
|
-
|
|
31742
|
-
|
|
31743
|
-
}
|
|
32109
|
+
if (trimmed.endsWith(".json")) {
|
|
32110
|
+
return [trimmed];
|
|
32111
|
+
}
|
|
32112
|
+
return [trimmed, `${trimmed}.json`];
|
|
31744
32113
|
}
|
|
31745
|
-
function
|
|
31746
|
-
const
|
|
31747
|
-
|
|
31748
|
-
|
|
31749
|
-
|
|
31750
|
-
|
|
31751
|
-
|
|
32114
|
+
function normalizeWorkflowFileName(fileName) {
|
|
32115
|
+
const trimmed = fileName.trim();
|
|
32116
|
+
if (trimmed.length === 0) {
|
|
32117
|
+
throw new WorkflowInputError(
|
|
32118
|
+
"Filename cannot be empty.",
|
|
32119
|
+
"output_file_name"
|
|
32120
|
+
);
|
|
31752
32121
|
}
|
|
31753
|
-
|
|
31754
|
-
|
|
31755
|
-
|
|
31756
|
-
|
|
32122
|
+
if (path2.isAbsolute(trimmed)) {
|
|
32123
|
+
throw new WorkflowInputError(
|
|
32124
|
+
"Absolute paths are not allowed.",
|
|
32125
|
+
"output_file_name"
|
|
32126
|
+
);
|
|
32127
|
+
}
|
|
32128
|
+
return trimmed.endsWith(".json") ? trimmed : `${trimmed}.json`;
|
|
31757
32129
|
}
|
|
31758
|
-
function
|
|
31759
|
-
|
|
32130
|
+
function getWorkflowReference(args) {
|
|
32131
|
+
const explicitRefs = [
|
|
32132
|
+
args.workflow_ref?.trim(),
|
|
32133
|
+
args.workflow_file_path?.trim(),
|
|
32134
|
+
args.workflow_id?.trim()
|
|
32135
|
+
].filter((value) => Boolean(value));
|
|
32136
|
+
const uniqueRefs = [...new Set(explicitRefs)];
|
|
32137
|
+
if (uniqueRefs.length > 1) {
|
|
32138
|
+
throw new WorkflowInputError(
|
|
32139
|
+
"Provide only one of workflow_ref, workflow_file_path, or workflow_id.",
|
|
32140
|
+
"workflow_ref"
|
|
32141
|
+
);
|
|
32142
|
+
}
|
|
32143
|
+
if (uniqueRefs.length === 1) {
|
|
32144
|
+
return uniqueRefs[0];
|
|
32145
|
+
}
|
|
32146
|
+
const defaultWorkflow = process.env.COMFYUI_DEFAULT_WORKFLOW?.trim();
|
|
32147
|
+
return defaultWorkflow && defaultWorkflow.length > 0 ? defaultWorkflow : void 0;
|
|
31760
32148
|
}
|
|
31761
|
-
function
|
|
31762
|
-
const
|
|
31763
|
-
|
|
31764
|
-
|
|
31765
|
-
|
|
31766
|
-
|
|
32149
|
+
async function resolveWorkflowReference(args) {
|
|
32150
|
+
const requested = getWorkflowReference(args);
|
|
32151
|
+
if (!requested) {
|
|
32152
|
+
throw new WorkflowInputError(
|
|
32153
|
+
"Provide workflow_ref, a stored workflow path/id, or set COMFYUI_DEFAULT_WORKFLOW.",
|
|
32154
|
+
"workflow_ref"
|
|
32155
|
+
);
|
|
32156
|
+
}
|
|
32157
|
+
try {
|
|
32158
|
+
const resolvedPath = path2.resolve(requested);
|
|
32159
|
+
const content = await fs2.readFile(resolvedPath, "utf8");
|
|
32160
|
+
return {
|
|
32161
|
+
targetPath: resolvedPath,
|
|
32162
|
+
content
|
|
32163
|
+
};
|
|
32164
|
+
} catch {
|
|
32165
|
+
}
|
|
32166
|
+
if (path2.basename(requested) === requested) {
|
|
32167
|
+
const candidates = getWorkflowNameCandidates(requested);
|
|
32168
|
+
for (const dir of getWorkflowDirectories()) {
|
|
32169
|
+
for (const candidate of candidates) {
|
|
32170
|
+
const candidatePath = path2.join(dir, candidate);
|
|
32171
|
+
try {
|
|
32172
|
+
const content = await fs2.readFile(candidatePath, "utf8");
|
|
32173
|
+
return {
|
|
32174
|
+
targetPath: candidatePath,
|
|
32175
|
+
content
|
|
32176
|
+
};
|
|
32177
|
+
} catch {
|
|
32178
|
+
}
|
|
31767
32179
|
}
|
|
31768
|
-
const sourceNodeId = String(value[0]);
|
|
31769
|
-
const consumers = consumerMap.get(sourceNodeId) ?? [];
|
|
31770
|
-
consumers.push({
|
|
31771
|
-
inputName,
|
|
31772
|
-
classType: node.class_type,
|
|
31773
|
-
nodeId
|
|
31774
|
-
});
|
|
31775
|
-
consumerMap.set(sourceNodeId, consumers);
|
|
31776
32180
|
}
|
|
31777
32181
|
}
|
|
31778
|
-
const
|
|
31779
|
-
for (const
|
|
31780
|
-
|
|
31781
|
-
|
|
31782
|
-
|
|
32182
|
+
const objectInfoAvailability = await tryGetObjectInfo();
|
|
32183
|
+
for (const dir of getWorkflowDirectories()) {
|
|
32184
|
+
try {
|
|
32185
|
+
const entries = await fs2.readdir(dir, { withFileTypes: true });
|
|
32186
|
+
for (const entry of entries) {
|
|
32187
|
+
if (!entry.isFile() || !entry.name.endsWith(".json")) {
|
|
32188
|
+
continue;
|
|
32189
|
+
}
|
|
32190
|
+
const fullPath = path2.join(dir, entry.name);
|
|
32191
|
+
const inspected = await inspectWorkflowFile(
|
|
32192
|
+
fullPath,
|
|
32193
|
+
objectInfoAvailability.objectInfo
|
|
32194
|
+
);
|
|
32195
|
+
if (!inspected) {
|
|
32196
|
+
continue;
|
|
32197
|
+
}
|
|
32198
|
+
if (requested === inspected.id || requested === inspected.name || requested === entry.name.replace(/\.json$/, "")) {
|
|
32199
|
+
return {
|
|
32200
|
+
targetPath: fullPath,
|
|
32201
|
+
content: inspected.content
|
|
32202
|
+
};
|
|
32203
|
+
}
|
|
31783
32204
|
}
|
|
31784
|
-
|
|
31785
|
-
path: `${nodeId}.inputs.${inputName}`,
|
|
31786
|
-
node_id: nodeId,
|
|
31787
|
-
class_type: node.class_type,
|
|
31788
|
-
input_name: inputName,
|
|
31789
|
-
value,
|
|
31790
|
-
value_preview: formatValuePreview(value),
|
|
31791
|
-
role: inferEditableRole(
|
|
31792
|
-
nodeId,
|
|
31793
|
-
node.class_type,
|
|
31794
|
-
inputName,
|
|
31795
|
-
consumerMap
|
|
31796
|
-
),
|
|
31797
|
-
aliases: [],
|
|
31798
|
-
expected_type: getExpectedTypeName(
|
|
31799
|
-
getInputDefinition(objectInfo, prompt, nodeId, inputName)
|
|
31800
|
-
),
|
|
31801
|
-
...getInputOptionsSummary(
|
|
31802
|
-
getInputDefinition(objectInfo, prompt, nodeId, inputName)
|
|
31803
|
-
)
|
|
31804
|
-
});
|
|
32205
|
+
} catch {
|
|
31805
32206
|
}
|
|
31806
32207
|
}
|
|
31807
|
-
|
|
31808
|
-
|
|
31809
|
-
|
|
31810
|
-
|
|
31811
|
-
entry.node_id,
|
|
31812
|
-
entry.input_name,
|
|
31813
|
-
entry.role,
|
|
31814
|
-
widgetPathMap
|
|
31815
|
-
)
|
|
31816
|
-
})).sort(
|
|
31817
|
-
(left, right) => left.path.localeCompare(right.path, void 0, { numeric: true })
|
|
32208
|
+
throw new WorkflowInputError(
|
|
32209
|
+
path2.isAbsolute(requested) || requested.includes(path2.sep) ? `Workflow file not found: ${requested}` : `Workflow not found: ${requested}`,
|
|
32210
|
+
args.workflow_ref ? "workflow_ref" : args.workflow_file_path ? "workflow_file_path" : "workflow_id",
|
|
32211
|
+
"not_found"
|
|
31818
32212
|
);
|
|
31819
32213
|
}
|
|
31820
|
-
function
|
|
31821
|
-
if (
|
|
31822
|
-
|
|
32214
|
+
async function normalizeWorkflowPrompt(workflow, objectInfo) {
|
|
32215
|
+
if (!isRecord(workflow)) {
|
|
32216
|
+
throw new WorkflowInputError(
|
|
32217
|
+
"Workflow must be a JSON object.",
|
|
32218
|
+
"workflow"
|
|
32219
|
+
);
|
|
31823
32220
|
}
|
|
31824
|
-
|
|
31825
|
-
|
|
31826
|
-
|
|
31827
|
-
|
|
31828
|
-
|
|
31829
|
-
|
|
31830
|
-
|
|
31831
|
-
|
|
32221
|
+
let sourceFormat = "api";
|
|
32222
|
+
let widgetPathMap = {};
|
|
32223
|
+
let normalizedWorkflow = workflow;
|
|
32224
|
+
if (isWebUIFormat(normalizedWorkflow)) {
|
|
32225
|
+
if (!objectInfo) {
|
|
32226
|
+
throw new WorkflowInputError(
|
|
32227
|
+
"Web UI workflow normalization requires ComfyUI node definitions. Start the ComfyUI server or save the workflow in API format first.",
|
|
32228
|
+
"workflow"
|
|
32229
|
+
);
|
|
32230
|
+
}
|
|
32231
|
+
sourceFormat = "webui";
|
|
32232
|
+
const converted = convertWebUIToAPIWithMetadata(
|
|
32233
|
+
normalizedWorkflow,
|
|
32234
|
+
objectInfo
|
|
32235
|
+
);
|
|
32236
|
+
normalizedWorkflow = converted.prompt;
|
|
32237
|
+
widgetPathMap = converted.widgetPathMap;
|
|
32238
|
+
}
|
|
32239
|
+
const promptCandidate = isRecord(normalizedWorkflow.prompt) && normalizedWorkflow.prompt ? normalizedWorkflow.prompt : normalizedWorkflow;
|
|
32240
|
+
if (!isRecord(promptCandidate)) {
|
|
32241
|
+
throw new WorkflowInputError(
|
|
32242
|
+
"Workflow prompt must be a JSON object.",
|
|
32243
|
+
"workflow"
|
|
32244
|
+
);
|
|
32245
|
+
}
|
|
32246
|
+
return {
|
|
32247
|
+
prompt: extractRunnablePromptNodes(promptCandidate),
|
|
32248
|
+
sourceFormat,
|
|
32249
|
+
widgetPathMap
|
|
32250
|
+
};
|
|
31832
32251
|
}
|
|
31833
|
-
function
|
|
31834
|
-
const
|
|
31835
|
-
|
|
31836
|
-
|
|
31837
|
-
|
|
31838
|
-
|
|
31839
|
-
|
|
32252
|
+
async function loadWorkflow(args, objectInfo) {
|
|
32253
|
+
const { targetPath, content } = await resolveWorkflowReference(args);
|
|
32254
|
+
let parsed;
|
|
32255
|
+
try {
|
|
32256
|
+
parsed = JSON.parse(content);
|
|
32257
|
+
} catch (error48) {
|
|
32258
|
+
throw new WorkflowInputError(
|
|
32259
|
+
`Invalid JSON in ${targetPath}: ${error48 instanceof Error ? error48.message : String(error48)}`,
|
|
32260
|
+
args.workflow_file_path ? "workflow_file_path" : "workflow_id"
|
|
31840
32261
|
);
|
|
31841
32262
|
}
|
|
31842
|
-
|
|
31843
|
-
|
|
31844
|
-
|
|
32263
|
+
const { prompt, sourceFormat, widgetPathMap } = await normalizeWorkflowPrompt(parsed, objectInfo);
|
|
32264
|
+
return {
|
|
32265
|
+
targetPath,
|
|
32266
|
+
prompt,
|
|
32267
|
+
sourceFormat,
|
|
32268
|
+
widgetPathMap
|
|
32269
|
+
};
|
|
32270
|
+
}
|
|
32271
|
+
function buildDiscoveredOverrideKeys(prompt, objectInfo, widgetPathMap = {}) {
|
|
32272
|
+
const editableInputs = analyzeEditableInputs(
|
|
32273
|
+
prompt,
|
|
32274
|
+
objectInfo,
|
|
32275
|
+
widgetPathMap
|
|
32276
|
+
);
|
|
32277
|
+
return buildOverrideExamples(editableInputs, 5, { valueMode: "keys" }).preferred_paths;
|
|
32278
|
+
}
|
|
32279
|
+
async function inspectWorkflowFile(fullPath, objectInfo) {
|
|
32280
|
+
const content = await fs2.readFile(fullPath, "utf8");
|
|
32281
|
+
const defaultName = path2.basename(fullPath, ".json");
|
|
32282
|
+
const defaultId = getWorkflowId(fullPath);
|
|
32283
|
+
let parsed;
|
|
32284
|
+
try {
|
|
32285
|
+
parsed = JSON.parse(content);
|
|
32286
|
+
} catch {
|
|
32287
|
+
return void 0;
|
|
32288
|
+
}
|
|
32289
|
+
let name = defaultName;
|
|
32290
|
+
if (isRecord(parsed)) {
|
|
32291
|
+
if (typeof parsed.name === "string" && parsed.name.trim().length > 0) {
|
|
32292
|
+
name = parsed.name;
|
|
32293
|
+
} else if (isRecord(parsed.workflow) && typeof parsed.workflow.name === "string" && parsed.workflow.name.trim().length > 0) {
|
|
32294
|
+
name = parsed.workflow.name;
|
|
31845
32295
|
}
|
|
31846
|
-
|
|
31847
|
-
|
|
32296
|
+
}
|
|
32297
|
+
if (isWebUIFormat(parsed)) {
|
|
32298
|
+
if (!objectInfo) {
|
|
32299
|
+
return {
|
|
32300
|
+
id: defaultId,
|
|
32301
|
+
name,
|
|
32302
|
+
path: fullPath,
|
|
32303
|
+
source_format: "webui",
|
|
32304
|
+
validation_status: "conversion_requires_node_schemas",
|
|
32305
|
+
content,
|
|
32306
|
+
isWebUI: true
|
|
32307
|
+
};
|
|
31848
32308
|
}
|
|
31849
|
-
|
|
31850
|
-
|
|
32309
|
+
try {
|
|
32310
|
+
const normalized = await normalizeWorkflowPrompt(
|
|
32311
|
+
parsed,
|
|
32312
|
+
objectInfo
|
|
32313
|
+
);
|
|
32314
|
+
return {
|
|
32315
|
+
id: defaultId,
|
|
32316
|
+
name,
|
|
32317
|
+
path: fullPath,
|
|
32318
|
+
source_format: "webui",
|
|
32319
|
+
validation_status: "conversion_validated",
|
|
32320
|
+
content,
|
|
32321
|
+
isWebUI: true,
|
|
32322
|
+
override_keys: buildDiscoveredOverrideKeys(
|
|
32323
|
+
normalized.prompt,
|
|
32324
|
+
objectInfo,
|
|
32325
|
+
normalized.widgetPathMap
|
|
32326
|
+
)
|
|
32327
|
+
};
|
|
32328
|
+
} catch (error48) {
|
|
32329
|
+
return {
|
|
32330
|
+
id: defaultId,
|
|
32331
|
+
name,
|
|
32332
|
+
path: fullPath,
|
|
32333
|
+
source_format: "webui",
|
|
32334
|
+
validation_status: "conversion_failed",
|
|
32335
|
+
content,
|
|
32336
|
+
error: summarizeErrorMessage(error48),
|
|
32337
|
+
isWebUI: true
|
|
32338
|
+
};
|
|
31851
32339
|
}
|
|
31852
32340
|
}
|
|
31853
|
-
|
|
31854
|
-
|
|
31855
|
-
|
|
31856
|
-
|
|
31857
|
-
|
|
31858
|
-
|
|
31859
|
-
|
|
31860
|
-
|
|
31861
|
-
|
|
31862
|
-
|
|
31863
|
-
|
|
31864
|
-
|
|
31865
|
-
|
|
31866
|
-
|
|
31867
|
-
`Required inputs (${requiredInputs.length}): ${requiredInputs.length > 0 ? requiredInputs.map(
|
|
31868
|
-
([inputName, inputDefinition]) => formatInputDefinitionSummary(
|
|
31869
|
-
inputName,
|
|
31870
|
-
inputDefinition
|
|
31871
|
-
)
|
|
31872
|
-
).join("\n") : "None."}`
|
|
31873
|
-
);
|
|
31874
|
-
lines.push(
|
|
31875
|
-
`Optional inputs (${optionalInputs.length}): ${optionalInputs.length > 0 ? optionalInputs.map(
|
|
31876
|
-
([inputName, inputDefinition]) => formatInputDefinitionSummary(
|
|
31877
|
-
inputName,
|
|
31878
|
-
inputDefinition
|
|
32341
|
+
try {
|
|
32342
|
+
const normalized = await normalizeWorkflowPrompt(parsed, objectInfo);
|
|
32343
|
+
return {
|
|
32344
|
+
id: defaultId,
|
|
32345
|
+
name,
|
|
32346
|
+
path: fullPath,
|
|
32347
|
+
source_format: "api",
|
|
32348
|
+
validation_status: "ready",
|
|
32349
|
+
content,
|
|
32350
|
+
isWebUI: false,
|
|
32351
|
+
override_keys: buildDiscoveredOverrideKeys(
|
|
32352
|
+
normalized.prompt,
|
|
32353
|
+
objectInfo,
|
|
32354
|
+
normalized.widgetPathMap
|
|
31879
32355
|
)
|
|
31880
|
-
|
|
31881
|
-
|
|
31882
|
-
|
|
31883
|
-
|
|
32356
|
+
};
|
|
32357
|
+
} catch {
|
|
32358
|
+
return void 0;
|
|
32359
|
+
}
|
|
31884
32360
|
}
|
|
31885
|
-
function
|
|
31886
|
-
|
|
31887
|
-
|
|
31888
|
-
const node = rawInfo[nodeClass];
|
|
31889
|
-
const displayName = node.display_name && node.display_name !== nodeClass ? ` \u2014 ${node.display_name}` : "";
|
|
31890
|
-
const category = node.category ? ` | category: ${node.category}` : "";
|
|
31891
|
-
const description = typeof node.description === "string" && node.description.trim().length > 0 ? ` | ${node.description.trim()}` : "";
|
|
31892
|
-
return `${nodeClass}${displayName}${category}${description}`;
|
|
31893
|
-
});
|
|
31894
|
-
if (!pagination.hasMore) {
|
|
31895
|
-
return text;
|
|
32361
|
+
function deriveSavedWorkflowFileName(sourcePath, requestedName) {
|
|
32362
|
+
if (requestedName) {
|
|
32363
|
+
return normalizeWorkflowFileName(requestedName);
|
|
31896
32364
|
}
|
|
31897
|
-
|
|
31898
|
-
|
|
31899
|
-
|
|
31900
|
-
|
|
31901
|
-
|
|
31902
|
-
|
|
31903
|
-
)
|
|
32365
|
+
if (sourcePath) {
|
|
32366
|
+
return normalizeWorkflowFileName(path2.basename(sourcePath));
|
|
32367
|
+
}
|
|
32368
|
+
return `workflow-${Date.now()}.json`;
|
|
32369
|
+
}
|
|
32370
|
+
function getWorkflowId(absolutePath) {
|
|
32371
|
+
return crypto.createHash("sha256").update(absolutePath).digest("hex").substring(0, 8);
|
|
31904
32372
|
}
|
|
31905
32373
|
|
|
31906
32374
|
// src/zod.ts
|
|
@@ -31909,9 +32377,8 @@ var z3 = zNamespace.z ?? zNamespace.default ?? zNamespace;
|
|
|
31909
32377
|
|
|
31910
32378
|
// src/workflow/overrides.ts
|
|
31911
32379
|
var INPUT_SEGMENT = "inputs";
|
|
31912
|
-
var
|
|
31913
|
-
|
|
31914
|
-
'Dot-notation overrides as an object or JSON string. e.g. {"6.seed": 7}'
|
|
32380
|
+
var OverridesSchema = z3.record(z3.string(), z3.unknown()).describe(
|
|
32381
|
+
'Small override object only. e.g. {"positive_prompt":"...","seed":7}. Never paste full workflow JSON.'
|
|
31915
32382
|
);
|
|
31916
32383
|
function validateOverrideValueType(path4, value, inputDefinition) {
|
|
31917
32384
|
if (!inputDefinition) {
|
|
@@ -32013,25 +32480,7 @@ function parseOverrides(rawOverrides) {
|
|
|
32013
32480
|
if (rawOverrides === void 0) {
|
|
32014
32481
|
return void 0;
|
|
32015
32482
|
}
|
|
32016
|
-
|
|
32017
|
-
return rawOverrides;
|
|
32018
|
-
}
|
|
32019
|
-
let parsed;
|
|
32020
|
-
try {
|
|
32021
|
-
parsed = JSON.parse(rawOverrides);
|
|
32022
|
-
} catch (error48) {
|
|
32023
|
-
throw new WorkflowInputError(
|
|
32024
|
-
`Overrides must be a valid JSON object string: ${error48 instanceof Error ? error48.message : String(error48)}`,
|
|
32025
|
-
"overrides"
|
|
32026
|
-
);
|
|
32027
|
-
}
|
|
32028
|
-
if (!isPlainObject3(parsed)) {
|
|
32029
|
-
throw new WorkflowInputError(
|
|
32030
|
-
"Overrides JSON string must decode to an object.",
|
|
32031
|
-
"overrides"
|
|
32032
|
-
);
|
|
32033
|
-
}
|
|
32034
|
-
return parsed;
|
|
32483
|
+
return rawOverrides;
|
|
32035
32484
|
}
|
|
32036
32485
|
function validatePromptPreflight(prompt, objectInfo, widgetPathMap = {}) {
|
|
32037
32486
|
if (!objectInfo) {
|
|
@@ -32155,7 +32604,7 @@ ${detailSummary}` : error48.message
|
|
|
32155
32604
|
error48.promptId,
|
|
32156
32605
|
error48.queueSnapshot
|
|
32157
32606
|
);
|
|
32158
|
-
const promptVisibilityText = promptVisibility === true ? "The prompt is still visible in the ComfyUI queue snapshot." : promptVisibility === false ? "The prompt is not visible in the ComfyUI queue snapshot;
|
|
32607
|
+
const promptVisibilityText = promptVisibility === true ? "The prompt is still visible in the ComfyUI queue snapshot." : promptVisibility === false ? "The prompt is not visible in the ComfyUI queue snapshot; call comfyui_wait_for_workflow again with this prompt_id, or comfyui_interrupt_workflow if the run should stop." : "ComfyUI queue state was unavailable at timeout.";
|
|
32159
32608
|
return {
|
|
32160
32609
|
content: [
|
|
32161
32610
|
{
|
|
@@ -32163,8 +32612,7 @@ ${detailSummary}` : error48.message
|
|
|
32163
32612
|
text: [
|
|
32164
32613
|
`${error48.message}.`,
|
|
32165
32614
|
queueSummary,
|
|
32166
|
-
promptVisibilityText
|
|
32167
|
-
`You can re-attach by calling 'comfyui_wait_for_workflow' again with prompt_id '${error48.promptId}'.`
|
|
32615
|
+
promptVisibilityText
|
|
32168
32616
|
].filter((line) => line && line.length > 0).join("\n")
|
|
32169
32617
|
}
|
|
32170
32618
|
],
|
|
@@ -32179,6 +32627,11 @@ ${detailSummary}` : error48.message
|
|
|
32179
32627
|
}
|
|
32180
32628
|
return createInternalError(error48);
|
|
32181
32629
|
}
|
|
32630
|
+
var WorkflowRefFields = {
|
|
32631
|
+
workflow_ref: z3.string().optional().describe("Path, filename, or discover id."),
|
|
32632
|
+
workflow_file_path: z3.string().optional().describe("Deprecated alias of workflow_ref."),
|
|
32633
|
+
workflow_id: z3.string().optional().describe("Deprecated alias of workflow_ref.")
|
|
32634
|
+
};
|
|
32182
32635
|
var InspectNodeSchema = paginationSchema.extend({
|
|
32183
32636
|
node_class: z3.string().optional().describe(
|
|
32184
32637
|
"Specific node class to inspect. If omitted, lists all available nodes."
|
|
@@ -32191,33 +32644,126 @@ var DiscoverWorkflowsSchema = paginationSchema.extend({
|
|
|
32191
32644
|
directory_path: z3.string().optional().describe("Custom directory path to scan for workflows.")
|
|
32192
32645
|
});
|
|
32193
32646
|
var WorkflowRunSchema = z3.object({
|
|
32194
|
-
|
|
32195
|
-
workflow_file_path: z3.string().optional().describe(
|
|
32196
|
-
"Path or filename of workflow JSON. Scans default dirs if simple name provided."
|
|
32197
|
-
),
|
|
32198
|
-
workflow_id: z3.string().optional().describe("Workflow identifier returned by discover_workflows."),
|
|
32647
|
+
...WorkflowRefFields,
|
|
32199
32648
|
overrides: OverridesSchema.optional(),
|
|
32200
|
-
await: z3.boolean().default(true).describe(
|
|
32201
|
-
|
|
32649
|
+
await: z3.boolean().default(true).describe(
|
|
32650
|
+
"Wait briefly for images. Prefer short timeout polls if the host cuts long waits."
|
|
32651
|
+
),
|
|
32652
|
+
timeout: z3.number().int().min(1).default(25).describe(
|
|
32653
|
+
"Wait poll seconds (default 25). Call wait again if timed out."
|
|
32654
|
+
),
|
|
32655
|
+
max_images: z3.number().int().min(0).max(8).default(2).describe(
|
|
32656
|
+
"Native images to return (0=refs only). Prefer output over temp; extras stay in image_refs."
|
|
32657
|
+
)
|
|
32202
32658
|
});
|
|
32203
32659
|
var GetWorkflowSchema = z3.object({
|
|
32204
|
-
|
|
32205
|
-
|
|
32206
|
-
|
|
32660
|
+
...WorkflowRefFields,
|
|
32661
|
+
include_prompt: z3.boolean().default(false).describe("Include normalized API JSON in structuredContent."),
|
|
32662
|
+
verbose: z3.boolean().default(false).describe("Include every editable input, not just high-signal ones.")
|
|
32207
32663
|
});
|
|
32208
32664
|
var SaveWorkflowSchema = z3.object({
|
|
32209
|
-
|
|
32210
|
-
workflow_file_path: z3.string().optional().describe("Existing workflow path or filename to save."),
|
|
32211
|
-
workflow_id: z3.string().optional().describe("Existing workflow identifier to save."),
|
|
32665
|
+
...WorkflowRefFields,
|
|
32212
32666
|
overrides: OverridesSchema.optional(),
|
|
32213
32667
|
output_file_name: z3.string().optional().describe("Filename to write locally. (default: anchored to ./)"),
|
|
32214
32668
|
overwrite: z3.boolean().default(false).describe("Replace an existing local file when true."),
|
|
32215
|
-
include_prompt: z3.boolean().default(false).describe("Include saved API JSON in structuredContent.")
|
|
32669
|
+
include_prompt: z3.boolean().default(false).describe("Include saved API JSON in structuredContent."),
|
|
32670
|
+
verbose: z3.boolean().default(false).describe("Include every editable input, not just high-signal ones.")
|
|
32216
32671
|
});
|
|
32217
32672
|
var WaitForWorkflowSchema = z3.object({
|
|
32218
32673
|
prompt_id: z3.string().describe("The prompt_id of the submitted workflow."),
|
|
32219
|
-
timeout: z3.number().int().min(1).default(
|
|
32674
|
+
timeout: z3.number().int().min(1).default(25).describe(
|
|
32675
|
+
"Wait poll seconds (default 25). Call again with the same prompt_id if timed out."
|
|
32676
|
+
),
|
|
32677
|
+
max_images: z3.number().int().min(0).max(8).default(2).describe(
|
|
32678
|
+
"Native images to return (0=refs only). Prefer output over temp; extras stay in image_refs."
|
|
32679
|
+
)
|
|
32680
|
+
});
|
|
32681
|
+
var InterruptWorkflowSchema = z3.object({
|
|
32682
|
+
prompt_id: z3.string().optional().describe("Running prompt to interrupt. Omit to stop the current job."),
|
|
32683
|
+
clear_queue: z3.boolean().default(false).describe("Clear pending queue items when true.")
|
|
32684
|
+
});
|
|
32685
|
+
var UploadImageSchema = z3.object({
|
|
32686
|
+
file_path: z3.string().optional().describe(
|
|
32687
|
+
"Workspace image path. Use when the file is already on disk."
|
|
32688
|
+
),
|
|
32689
|
+
prompt_id: z3.string().optional().describe(
|
|
32690
|
+
"Previous wait/run prompt_id. Server copies that output into input without image bytes in the model."
|
|
32691
|
+
),
|
|
32692
|
+
filename: z3.string().optional().describe(
|
|
32693
|
+
"Required with prompt_id (ComfyUI output filename). Optional destination name for file_path."
|
|
32694
|
+
),
|
|
32695
|
+
subfolder: z3.string().optional().describe("ComfyUI subfolder for a prompt_id source."),
|
|
32696
|
+
source_type: z3.enum(["output", "input", "temp"]).default("output").describe("ComfyUI folder to read when using prompt_id."),
|
|
32697
|
+
image_type: z3.enum(["input", "temp"]).default("input").describe("ComfyUI upload folder type."),
|
|
32698
|
+
overwrite: z3.boolean().default(true).describe("Replace an existing file when true.")
|
|
32699
|
+
});
|
|
32700
|
+
var ListModelsSchema = paginationSchema.extend({
|
|
32701
|
+
model_type: z3.enum(COMFY_MODEL_TYPES).default("checkpoint").describe("Model folder to list.")
|
|
32220
32702
|
});
|
|
32703
|
+
function hasStoredWorkflowRef(args) {
|
|
32704
|
+
return Boolean(getWorkflowReference(args));
|
|
32705
|
+
}
|
|
32706
|
+
function collectHistoryImages(outputs) {
|
|
32707
|
+
const images = [];
|
|
32708
|
+
for (const [nodeId, output] of Object.entries(outputs)) {
|
|
32709
|
+
if (!output || !Array.isArray(output.images)) {
|
|
32710
|
+
continue;
|
|
32711
|
+
}
|
|
32712
|
+
for (const image of output.images) {
|
|
32713
|
+
if (!image.filename || !image.type) {
|
|
32714
|
+
continue;
|
|
32715
|
+
}
|
|
32716
|
+
images.push({
|
|
32717
|
+
filename: image.filename,
|
|
32718
|
+
type: image.type,
|
|
32719
|
+
subfolder: image.subfolder ?? "",
|
|
32720
|
+
nodeId
|
|
32721
|
+
});
|
|
32722
|
+
}
|
|
32723
|
+
}
|
|
32724
|
+
return images;
|
|
32725
|
+
}
|
|
32726
|
+
function rankImageType(type) {
|
|
32727
|
+
if (type === "output") {
|
|
32728
|
+
return 0;
|
|
32729
|
+
}
|
|
32730
|
+
if (type === "temp") {
|
|
32731
|
+
return 1;
|
|
32732
|
+
}
|
|
32733
|
+
return 2;
|
|
32734
|
+
}
|
|
32735
|
+
function selectImagesForDisplay(images, maxImages) {
|
|
32736
|
+
if (maxImages <= 0) {
|
|
32737
|
+
return [];
|
|
32738
|
+
}
|
|
32739
|
+
return [...images].sort((left, right) => {
|
|
32740
|
+
const typeDiff = rankImageType(left.type) - rankImageType(right.type);
|
|
32741
|
+
if (typeDiff !== 0) {
|
|
32742
|
+
return typeDiff;
|
|
32743
|
+
}
|
|
32744
|
+
return left.nodeId.localeCompare(right.nodeId, void 0, {
|
|
32745
|
+
numeric: true
|
|
32746
|
+
});
|
|
32747
|
+
}).slice(0, maxImages);
|
|
32748
|
+
}
|
|
32749
|
+
function formatUploadReuseHint(promptId, image) {
|
|
32750
|
+
const parts = [
|
|
32751
|
+
`prompt_id='${promptId}'`,
|
|
32752
|
+
`filename='${image.filename}'`,
|
|
32753
|
+
`source_type='${image.type}'`
|
|
32754
|
+
];
|
|
32755
|
+
if (image.subfolder) {
|
|
32756
|
+
parts.push(`subfolder='${image.subfolder}'`);
|
|
32757
|
+
}
|
|
32758
|
+
return `Reuse in img2img with comfyui_upload_image: ${parts.join(" ")}`;
|
|
32759
|
+
}
|
|
32760
|
+
function toPublicEditableInput(entry, verbose) {
|
|
32761
|
+
if (verbose) {
|
|
32762
|
+
return entry;
|
|
32763
|
+
}
|
|
32764
|
+
const { value: _value, ...rest } = entry;
|
|
32765
|
+
return rest;
|
|
32766
|
+
}
|
|
32221
32767
|
async function handleInspectNode(args) {
|
|
32222
32768
|
try {
|
|
32223
32769
|
const rawInfo = await getObjectInfo();
|
|
@@ -32248,20 +32794,28 @@ async function handleInspectNode(args) {
|
|
|
32248
32794
|
});
|
|
32249
32795
|
}
|
|
32250
32796
|
const paginated = applyPagination(classes, args);
|
|
32251
|
-
const resultNodes =
|
|
32252
|
-
|
|
32253
|
-
|
|
32254
|
-
|
|
32797
|
+
const resultNodes = paginated.items.map(
|
|
32798
|
+
(nodeClass) => {
|
|
32799
|
+
const node = rawInfo[nodeClass];
|
|
32800
|
+
return {
|
|
32801
|
+
class_type: nodeClass,
|
|
32802
|
+
display_name: node?.display_name || nodeClass,
|
|
32803
|
+
category: node?.category || ""
|
|
32804
|
+
};
|
|
32805
|
+
}
|
|
32806
|
+
);
|
|
32255
32807
|
return {
|
|
32256
32808
|
content: [
|
|
32257
32809
|
{
|
|
32258
32810
|
type: "text",
|
|
32259
|
-
text:
|
|
32260
|
-
|
|
32261
|
-
|
|
32262
|
-
|
|
32263
|
-
|
|
32264
|
-
|
|
32811
|
+
text: truncateToLimit(
|
|
32812
|
+
formatNodeListText(paginated.items, rawInfo, {
|
|
32813
|
+
total: paginated.total,
|
|
32814
|
+
offset: paginated.offset,
|
|
32815
|
+
limit: paginated.limit,
|
|
32816
|
+
hasMore: paginated.hasMore
|
|
32817
|
+
})
|
|
32818
|
+
)
|
|
32265
32819
|
}
|
|
32266
32820
|
],
|
|
32267
32821
|
structuredContent: {
|
|
@@ -32331,6 +32885,10 @@ Hint: If your workflows are stored elsewhere, provide a custom path using the 'd
|
|
|
32331
32885
|
if (workflow.error) {
|
|
32332
32886
|
line += ` (Validation error: ${workflow.error})`;
|
|
32333
32887
|
}
|
|
32888
|
+
const exampleKeys = workflow.override_keys ?? [];
|
|
32889
|
+
if (exampleKeys.length > 0) {
|
|
32890
|
+
line += ` | overrides: ${exampleKeys.join(", ")}`;
|
|
32891
|
+
}
|
|
32334
32892
|
return line;
|
|
32335
32893
|
})
|
|
32336
32894
|
];
|
|
@@ -32361,7 +32919,8 @@ Hint: If your workflows are stored elsewhere, provide a custom path using the 'd
|
|
|
32361
32919
|
isWebUI: workflow.isWebUI,
|
|
32362
32920
|
source_format: workflow.source_format,
|
|
32363
32921
|
validation_status: workflow.validation_status,
|
|
32364
|
-
...workflow.error ? { error: workflow.error } : {}
|
|
32922
|
+
...workflow.error ? { error: workflow.error } : {},
|
|
32923
|
+
...workflow.override_keys && workflow.override_keys.length > 0 ? { override_keys: workflow.override_keys } : {}
|
|
32365
32924
|
})),
|
|
32366
32925
|
skipped_non_workflow_files: skippedNonWorkflowFiles,
|
|
32367
32926
|
pagination: {
|
|
@@ -32380,61 +32939,98 @@ async function handleWaitForWorkflow(args) {
|
|
|
32380
32939
|
args.timeout
|
|
32381
32940
|
);
|
|
32382
32941
|
const outputsRecord = historyEntry.outputs || {};
|
|
32942
|
+
const collectedImages = collectHistoryImages(outputsRecord);
|
|
32943
|
+
const displayImages = selectImagesForDisplay(
|
|
32944
|
+
collectedImages,
|
|
32945
|
+
args.max_images
|
|
32946
|
+
);
|
|
32947
|
+
const displayKeys = new Set(
|
|
32948
|
+
displayImages.map(
|
|
32949
|
+
(image) => `${image.nodeId}\0${image.type}\0${image.subfolder}\0${image.filename}`
|
|
32950
|
+
)
|
|
32951
|
+
);
|
|
32952
|
+
const orderedImages = [
|
|
32953
|
+
...displayImages,
|
|
32954
|
+
...collectedImages.filter(
|
|
32955
|
+
(image) => !displayKeys.has(
|
|
32956
|
+
`${image.nodeId}\0${image.type}\0${image.subfolder}\0${image.filename}`
|
|
32957
|
+
)
|
|
32958
|
+
)
|
|
32959
|
+
];
|
|
32383
32960
|
const workspaceOutputs = [];
|
|
32961
|
+
const imageRefs = [];
|
|
32384
32962
|
const content = [];
|
|
32385
|
-
|
|
32386
|
-
|
|
32387
|
-
|
|
32388
|
-
|
|
32389
|
-
|
|
32390
|
-
|
|
32391
|
-
|
|
32392
|
-
|
|
32393
|
-
|
|
32394
|
-
|
|
32395
|
-
|
|
32396
|
-
|
|
32397
|
-
|
|
32398
|
-
|
|
32399
|
-
|
|
32400
|
-
|
|
32401
|
-
|
|
32402
|
-
|
|
32403
|
-
|
|
32404
|
-
|
|
32405
|
-
|
|
32406
|
-
|
|
32407
|
-
|
|
32408
|
-
|
|
32409
|
-
|
|
32410
|
-
|
|
32411
|
-
|
|
32412
|
-
|
|
32413
|
-
|
|
32414
|
-
|
|
32415
|
-
|
|
32416
|
-
|
|
32417
|
-
|
|
32963
|
+
const summaryLines = [
|
|
32964
|
+
`Workflow ${args.prompt_id} completed.`,
|
|
32965
|
+
`Images available: ${collectedImages.length}; returning ${displayImages.length} native image(s) (max_images=${args.max_images}). Prefer output over temp.`
|
|
32966
|
+
];
|
|
32967
|
+
for (const image of orderedImages) {
|
|
32968
|
+
const imageKey = `${image.nodeId}\0${image.type}\0${image.subfolder}\0${image.filename}`;
|
|
32969
|
+
const shouldFetch = displayKeys.has(imageKey);
|
|
32970
|
+
const imageRef = {
|
|
32971
|
+
prompt_id: args.prompt_id,
|
|
32972
|
+
filename: image.filename,
|
|
32973
|
+
type: image.type,
|
|
32974
|
+
...image.subfolder ? { subfolder: image.subfolder } : {}
|
|
32975
|
+
};
|
|
32976
|
+
if (shouldFetch) {
|
|
32977
|
+
try {
|
|
32978
|
+
const asset = IS_MOCK ? createMockImageAsset() : await fetchImageAsset(
|
|
32979
|
+
image.filename,
|
|
32980
|
+
image.type,
|
|
32981
|
+
image.subfolder || void 0
|
|
32982
|
+
);
|
|
32983
|
+
content.push({
|
|
32984
|
+
type: "image",
|
|
32985
|
+
data: asset.buffer.toString("base64"),
|
|
32986
|
+
mimeType: asset.mimeType
|
|
32987
|
+
});
|
|
32988
|
+
summaryLines.push(`Fetched image: ${image.filename}`);
|
|
32989
|
+
const summary = await saveToWorkspace(
|
|
32990
|
+
args.prompt_id,
|
|
32991
|
+
{
|
|
32992
|
+
filename: image.filename,
|
|
32993
|
+
subfolder: image.subfolder
|
|
32994
|
+
},
|
|
32995
|
+
asset
|
|
32996
|
+
);
|
|
32997
|
+
if (summary) {
|
|
32998
|
+
workspaceOutputs.push(summary);
|
|
32999
|
+
imageRef.workspace_path = summary.workspace_path;
|
|
33000
|
+
imageRef.relative_path = summary.relative_path;
|
|
33001
|
+
summaryLines.push(
|
|
33002
|
+
`Copied image to workspace: ${summary.relative_path}`
|
|
33003
|
+
);
|
|
32418
33004
|
}
|
|
33005
|
+
} catch (error48) {
|
|
33006
|
+
summaryLines.push(
|
|
33007
|
+
`Failed to fetch image ${image.filename}: ${String(error48)}`
|
|
33008
|
+
);
|
|
32419
33009
|
}
|
|
32420
33010
|
}
|
|
33011
|
+
summaryLines.push(formatUploadReuseHint(args.prompt_id, image));
|
|
33012
|
+
imageRefs.push(imageRef);
|
|
32421
33013
|
}
|
|
32422
|
-
if (IS_MOCK &&
|
|
33014
|
+
if (IS_MOCK && collectedImages.length === 0) {
|
|
32423
33015
|
for (const [, output] of Object.entries(outputsRecord)) {
|
|
32424
33016
|
if (output && Array.isArray(output.images)) {
|
|
32425
33017
|
for (const image of output.images) {
|
|
32426
|
-
|
|
32427
|
-
`;
|
|
33018
|
+
summaryLines.push(`Mock image: ${image.filename}`);
|
|
32428
33019
|
}
|
|
32429
33020
|
}
|
|
32430
33021
|
}
|
|
32431
33022
|
}
|
|
32432
|
-
content.unshift({ type: "text", text:
|
|
33023
|
+
content.unshift({ type: "text", text: `${summaryLines.join("\n")}
|
|
33024
|
+
` });
|
|
32433
33025
|
return {
|
|
32434
33026
|
content,
|
|
32435
33027
|
structuredContent: {
|
|
32436
|
-
|
|
32437
|
-
|
|
33028
|
+
prompt_id: args.prompt_id,
|
|
33029
|
+
status: historyEntry.status,
|
|
33030
|
+
image_refs: imageRefs,
|
|
33031
|
+
workspace_outputs: workspaceOutputs,
|
|
33032
|
+
images_returned: content.filter((item) => item.type === "image").length,
|
|
33033
|
+
images_available: collectedImages.length
|
|
32438
33034
|
}
|
|
32439
33035
|
};
|
|
32440
33036
|
} catch (error48) {
|
|
@@ -32455,34 +33051,29 @@ async function handleGetWorkflow(args) {
|
|
|
32455
33051
|
);
|
|
32456
33052
|
const highSignalInputs = getHighSignalEditableInputs(editableInputs);
|
|
32457
33053
|
const editableInputGroups = summarizeEditableInputGroups(editableInputs);
|
|
32458
|
-
const overrideExamples = buildOverrideExamples(editableInputs
|
|
33054
|
+
const overrideExamples = buildOverrideExamples(editableInputs, 5, {
|
|
33055
|
+
valueMode: "preview"
|
|
33056
|
+
});
|
|
32459
33057
|
const workflowId = getWorkflowId(loadedWorkflow.targetPath);
|
|
32460
|
-
const
|
|
32461
|
-
|
|
32462
|
-
|
|
32463
|
-
|
|
32464
|
-
|
|
32465
|
-
|
|
32466
|
-
|
|
32467
|
-
|
|
32468
|
-
|
|
32469
|
-
|
|
32470
|
-
|
|
32471
|
-
|
|
32472
|
-
|
|
32473
|
-
|
|
32474
|
-
`Editable inputs (${editableInputs.length} total):`,
|
|
32475
|
-
formatEditableInputsText(editableInputs),
|
|
32476
|
-
"Copy-ready override example:",
|
|
32477
|
-
formatOverrideExamplesText(overrideExamples.examples)
|
|
32478
|
-
);
|
|
32479
|
-
const text = textSections.join("\n");
|
|
33058
|
+
const text = formatWorkflowInspectionText({
|
|
33059
|
+
introLines: [
|
|
33060
|
+
`Workflow ${workflowId} loaded from ${loadedWorkflow.targetPath}.`,
|
|
33061
|
+
loadedWorkflow.sourceFormat === "webui" ? "Source format: Web UI (normalized to API format)." : "Source format: API format."
|
|
33062
|
+
],
|
|
33063
|
+
warning: objectInfoAvailability.warning,
|
|
33064
|
+
highSignalInputs,
|
|
33065
|
+
editableInputGroups,
|
|
33066
|
+
editableInputs,
|
|
33067
|
+
overrideExamples: overrideExamples.examples,
|
|
33068
|
+
preferredOverridePaths: overrideExamples.preferred_paths,
|
|
33069
|
+
verbose: args.verbose
|
|
33070
|
+
});
|
|
33071
|
+
const publicInputs = (args.verbose ? editableInputs : highSignalInputs).map((entry) => toPublicEditableInput(entry, args.verbose));
|
|
32480
33072
|
const structuredContent = {
|
|
32481
33073
|
workflow_id: workflowId,
|
|
32482
33074
|
path: loadedWorkflow.targetPath,
|
|
32483
33075
|
source_format: loadedWorkflow.sourceFormat,
|
|
32484
|
-
editable_inputs:
|
|
32485
|
-
high_signal_inputs: highSignalInputs,
|
|
33076
|
+
editable_inputs: publicInputs,
|
|
32486
33077
|
input_groups: editableInputGroups,
|
|
32487
33078
|
override_examples: overrideExamples.examples,
|
|
32488
33079
|
preferred_override_paths: overrideExamples.preferred_paths
|
|
@@ -32503,33 +33094,21 @@ async function handleGetWorkflow(args) {
|
|
|
32503
33094
|
}
|
|
32504
33095
|
async function handleSaveWorkflow(args) {
|
|
32505
33096
|
try {
|
|
32506
|
-
let prompt;
|
|
32507
|
-
let widgetPathMap = {};
|
|
32508
|
-
let sourcePath;
|
|
32509
33097
|
const parsedOverrides = parseOverrides(args.overrides);
|
|
32510
33098
|
const objectInfoAvailability = await tryGetObjectInfo();
|
|
32511
|
-
if (args
|
|
32512
|
-
|
|
32513
|
-
|
|
32514
|
-
|
|
32515
|
-
);
|
|
32516
|
-
prompt = normalized.prompt;
|
|
32517
|
-
widgetPathMap = normalized.widgetPathMap;
|
|
32518
|
-
} else {
|
|
32519
|
-
if (!args.workflow_file_path && !args.workflow_id) {
|
|
32520
|
-
return createValidationError(
|
|
32521
|
-
"workflow",
|
|
32522
|
-
"Provide workflow JSON or an existing workflow reference."
|
|
32523
|
-
);
|
|
32524
|
-
}
|
|
32525
|
-
const loadedWorkflow = await loadWorkflow(
|
|
32526
|
-
args,
|
|
32527
|
-
objectInfoAvailability.objectInfo
|
|
33099
|
+
if (!hasStoredWorkflowRef(args)) {
|
|
33100
|
+
return createValidationError(
|
|
33101
|
+
"workflow_ref",
|
|
33102
|
+
"Provide a stored workflow_ref (path, filename, or discover id)."
|
|
32528
33103
|
);
|
|
32529
|
-
prompt = loadedWorkflow.prompt;
|
|
32530
|
-
sourcePath = loadedWorkflow.targetPath;
|
|
32531
|
-
widgetPathMap = loadedWorkflow.widgetPathMap;
|
|
32532
33104
|
}
|
|
33105
|
+
const loadedWorkflow = await loadWorkflow(
|
|
33106
|
+
args,
|
|
33107
|
+
objectInfoAvailability.objectInfo
|
|
33108
|
+
);
|
|
33109
|
+
const prompt = loadedWorkflow.prompt;
|
|
33110
|
+
const sourcePath = loadedWorkflow.targetPath;
|
|
33111
|
+
const widgetPathMap = loadedWorkflow.widgetPathMap;
|
|
32533
33112
|
const editableInputs = analyzeEditableInputs(
|
|
32534
33113
|
prompt,
|
|
32535
33114
|
objectInfoAvailability.objectInfo,
|
|
@@ -32584,15 +33163,19 @@ async function handleSaveWorkflow(args) {
|
|
|
32584
33163
|
const editableInputGroups = summarizeEditableInputGroups(
|
|
32585
33164
|
updatedEditableInputs
|
|
32586
33165
|
);
|
|
32587
|
-
const overrideExamples = buildOverrideExamples(
|
|
33166
|
+
const overrideExamples = buildOverrideExamples(
|
|
33167
|
+
updatedEditableInputs,
|
|
33168
|
+
5,
|
|
33169
|
+
{ valueMode: "preview" }
|
|
33170
|
+
);
|
|
32588
33171
|
const workflowId = getWorkflowId(outputPath);
|
|
33172
|
+
const publicInputs = (args.verbose ? updatedEditableInputs : highSignalInputs).map((entry) => toPublicEditableInput(entry, args.verbose));
|
|
32589
33173
|
const structuredContent = {
|
|
32590
33174
|
workflow_id: workflowId,
|
|
32591
33175
|
path: outputPath,
|
|
32592
33176
|
source_path: sourcePath,
|
|
32593
33177
|
overwritten: existedBeforeSave,
|
|
32594
|
-
editable_inputs:
|
|
32595
|
-
high_signal_inputs: highSignalInputs,
|
|
33178
|
+
editable_inputs: publicInputs,
|
|
32596
33179
|
input_groups: editableInputGroups,
|
|
32597
33180
|
override_examples: overrideExamples.examples,
|
|
32598
33181
|
preferred_override_paths: overrideExamples.preferred_paths
|
|
@@ -32604,26 +33187,19 @@ async function handleSaveWorkflow(args) {
|
|
|
32604
33187
|
structuredContent.prompt = prompt;
|
|
32605
33188
|
}
|
|
32606
33189
|
const persistedChanges = parsedOverrides && Object.keys(parsedOverrides).length > 0 ? `Persisted ${Object.keys(parsedOverrides).length} override(s).` : "No overrides were applied.";
|
|
32607
|
-
const
|
|
32608
|
-
|
|
32609
|
-
|
|
32610
|
-
|
|
32611
|
-
|
|
32612
|
-
|
|
32613
|
-
|
|
32614
|
-
|
|
32615
|
-
|
|
32616
|
-
|
|
32617
|
-
|
|
32618
|
-
|
|
32619
|
-
|
|
32620
|
-
formatEditableInputGroupsText(editableInputGroups),
|
|
32621
|
-
`Editable inputs (${updatedEditableInputs.length} total):`,
|
|
32622
|
-
formatEditableInputsText(updatedEditableInputs),
|
|
32623
|
-
"Copy-ready override example:",
|
|
32624
|
-
formatOverrideExamplesText(overrideExamples.examples)
|
|
32625
|
-
);
|
|
32626
|
-
const text = textSections.join("\n");
|
|
33190
|
+
const text = formatWorkflowInspectionText({
|
|
33191
|
+
introLines: [
|
|
33192
|
+
`Saved workflow ${workflowId} to ${outputPath}.`,
|
|
33193
|
+
persistedChanges
|
|
33194
|
+
],
|
|
33195
|
+
warning: objectInfoAvailability.warning,
|
|
33196
|
+
highSignalInputs,
|
|
33197
|
+
editableInputGroups,
|
|
33198
|
+
editableInputs: updatedEditableInputs,
|
|
33199
|
+
overrideExamples: overrideExamples.examples,
|
|
33200
|
+
preferredOverridePaths: overrideExamples.preferred_paths,
|
|
33201
|
+
verbose: args.verbose
|
|
33202
|
+
});
|
|
32627
33203
|
return {
|
|
32628
33204
|
content: [{ type: "text", text }],
|
|
32629
33205
|
structuredContent
|
|
@@ -32634,30 +33210,14 @@ async function handleSaveWorkflow(args) {
|
|
|
32634
33210
|
}
|
|
32635
33211
|
async function handleWorkflowRun(args) {
|
|
32636
33212
|
try {
|
|
32637
|
-
|
|
32638
|
-
|
|
32639
|
-
|
|
32640
|
-
|
|
32641
|
-
const normalized = await normalizeWorkflowPrompt(
|
|
32642
|
-
args.workflow,
|
|
32643
|
-
objectInfo
|
|
33213
|
+
if (!hasStoredWorkflowRef(args)) {
|
|
33214
|
+
return createValidationError(
|
|
33215
|
+
"workflow_ref",
|
|
33216
|
+
"Provide a stored workflow_ref (path, filename, or discover id), or set COMFYUI_DEFAULT_WORKFLOW."
|
|
32644
33217
|
);
|
|
32645
|
-
loadedWorkflow = {
|
|
32646
|
-
targetPath: "[inline workflow]",
|
|
32647
|
-
prompt: normalized.prompt,
|
|
32648
|
-
sourceFormat: normalized.sourceFormat,
|
|
32649
|
-
widgetPathMap: normalized.widgetPathMap
|
|
32650
|
-
};
|
|
32651
|
-
} else {
|
|
32652
|
-
if (!args.workflow_file_path && !args.workflow_id) {
|
|
32653
|
-
return createValidationError(
|
|
32654
|
-
"workflow",
|
|
32655
|
-
"Provide workflow JSON or an existing workflow reference."
|
|
32656
|
-
);
|
|
32657
|
-
}
|
|
32658
|
-
objectInfo = await getObjectInfo();
|
|
32659
|
-
loadedWorkflow = await loadWorkflow(args, objectInfo);
|
|
32660
33218
|
}
|
|
33219
|
+
const objectInfo = await getObjectInfo();
|
|
33220
|
+
const loadedWorkflow = await loadWorkflow(args, objectInfo);
|
|
32661
33221
|
const { prompt } = loadedWorkflow;
|
|
32662
33222
|
const promptObj = { ...prompt };
|
|
32663
33223
|
const parsedOverrides = parseOverrides(args.overrides);
|
|
@@ -32700,10 +33260,11 @@ async function handleWorkflowRun(args) {
|
|
|
32700
33260
|
if (args.await) {
|
|
32701
33261
|
return await handleWaitForWorkflow({
|
|
32702
33262
|
prompt_id: result.prompt_id,
|
|
32703
|
-
timeout: args.timeout
|
|
33263
|
+
timeout: args.timeout,
|
|
33264
|
+
max_images: args.max_images
|
|
32704
33265
|
});
|
|
32705
33266
|
}
|
|
32706
|
-
const queueSnapshot = await getQueueSnapshot();
|
|
33267
|
+
const queueSnapshot = summarizeQueue(await getQueueSnapshot());
|
|
32707
33268
|
return {
|
|
32708
33269
|
content: [
|
|
32709
33270
|
{
|
|
@@ -32713,7 +33274,7 @@ async function handleWorkflowRun(args) {
|
|
|
32713
33274
|
`ComfyUI queue number: ${result.number} (queue number is not an ETA).`,
|
|
32714
33275
|
`Preflight checked ${preflight.checked_inputs} literal inputs (${preflight.combo_inputs} combo-backed).`,
|
|
32715
33276
|
formatQueueSnapshotText(queueSnapshot),
|
|
32716
|
-
`
|
|
33277
|
+
`If host MCP timeouts cut long waits, poll with comfyui_wait_for_workflow (default ~25s) until complete.`
|
|
32717
33278
|
].filter((line) => line && line.length > 0).join("\n")
|
|
32718
33279
|
}
|
|
32719
33280
|
],
|
|
@@ -32727,6 +33288,107 @@ async function handleWorkflowRun(args) {
|
|
|
32727
33288
|
return handleComfyError(error48);
|
|
32728
33289
|
}
|
|
32729
33290
|
}
|
|
33291
|
+
async function handleInterruptWorkflow(args) {
|
|
33292
|
+
try {
|
|
33293
|
+
const result = await interruptWorkflow({
|
|
33294
|
+
promptId: args.prompt_id,
|
|
33295
|
+
clearQueue: args.clear_queue
|
|
33296
|
+
});
|
|
33297
|
+
const queueSummary = formatQueueSnapshotText(result.queue);
|
|
33298
|
+
const targetText = args.prompt_id ? result.interrupted ? `Interrupted prompt_id '${args.prompt_id}'.` : `Prompt_id '${args.prompt_id}' is not currently running; no interrupt was sent.` : result.interrupted ? "Interrupted the currently running workflow." : "No running workflow was interrupted.";
|
|
33299
|
+
const queueClearedText = result.queue_cleared ? "Pending queue cleared." : "Pending queue was left unchanged.";
|
|
33300
|
+
return {
|
|
33301
|
+
content: [
|
|
33302
|
+
{
|
|
33303
|
+
type: "text",
|
|
33304
|
+
text: [targetText, queueClearedText, queueSummary].filter((line) => line && line.length > 0).join("\n")
|
|
33305
|
+
}
|
|
33306
|
+
],
|
|
33307
|
+
structuredContent: {
|
|
33308
|
+
interrupted: result.interrupted,
|
|
33309
|
+
prompt_id: result.prompt_id ?? null,
|
|
33310
|
+
queue_cleared: result.queue_cleared,
|
|
33311
|
+
queue: result.queue ?? null
|
|
33312
|
+
}
|
|
33313
|
+
};
|
|
33314
|
+
} catch (error48) {
|
|
33315
|
+
return handleComfyError(error48);
|
|
33316
|
+
}
|
|
33317
|
+
}
|
|
33318
|
+
async function handleUploadImage(args) {
|
|
33319
|
+
try {
|
|
33320
|
+
const source = await readUploadImageSource({
|
|
33321
|
+
filePath: args.file_path,
|
|
33322
|
+
promptId: args.prompt_id,
|
|
33323
|
+
filename: args.filename,
|
|
33324
|
+
subfolder: args.subfolder,
|
|
33325
|
+
sourceType: args.source_type
|
|
33326
|
+
});
|
|
33327
|
+
const uploaded = await uploadImage({
|
|
33328
|
+
buffer: source.buffer,
|
|
33329
|
+
filename: source.filename,
|
|
33330
|
+
mimeType: source.mimeType,
|
|
33331
|
+
imageType: args.image_type,
|
|
33332
|
+
overwrite: args.overwrite
|
|
33333
|
+
});
|
|
33334
|
+
const loadImageName = uploaded.subfolder ? `${uploaded.subfolder}/${uploaded.name}` : uploaded.name;
|
|
33335
|
+
return {
|
|
33336
|
+
content: [
|
|
33337
|
+
{
|
|
33338
|
+
type: "text",
|
|
33339
|
+
text: [
|
|
33340
|
+
`Uploaded image '${uploaded.name}' to ComfyUI ${uploaded.type}.`,
|
|
33341
|
+
`Use this filename in LoadImage overrides: ${loadImageName}`
|
|
33342
|
+
].join("\n")
|
|
33343
|
+
}
|
|
33344
|
+
],
|
|
33345
|
+
structuredContent: {
|
|
33346
|
+
name: uploaded.name,
|
|
33347
|
+
subfolder: uploaded.subfolder,
|
|
33348
|
+
type: uploaded.type,
|
|
33349
|
+
load_image_name: loadImageName
|
|
33350
|
+
}
|
|
33351
|
+
};
|
|
33352
|
+
} catch (error48) {
|
|
33353
|
+
return handleComfyError(error48);
|
|
33354
|
+
}
|
|
33355
|
+
}
|
|
33356
|
+
async function handleListModels(args) {
|
|
33357
|
+
try {
|
|
33358
|
+
const listedModels = await getModelList(args.model_type);
|
|
33359
|
+
const paginated = applyPagination(listedModels.models, args);
|
|
33360
|
+
const footer = paginated.hasMore ? formatPaginationFooter(
|
|
33361
|
+
paginated.offset,
|
|
33362
|
+
paginated.limit,
|
|
33363
|
+
paginated.total
|
|
33364
|
+
) : "";
|
|
33365
|
+
const listed = paginated.items.length > 0 ? formatListItems(paginated.items, (model) => model) : "_No models found._";
|
|
33366
|
+
const text = listedModels.models_endpoint_available ? `Available ${args.model_type} models (${paginated.total} total):
|
|
33367
|
+
${listed}${footer}` : `Could not list ${args.model_type} models: this ComfyUI server does not expose /models/{folder} (HTTP 404).
|
|
33368
|
+
Upgrade ComfyUI, or inspect combo-backed options via comfyui_inspect_node.`;
|
|
33369
|
+
return {
|
|
33370
|
+
content: [
|
|
33371
|
+
{
|
|
33372
|
+
type: "text",
|
|
33373
|
+
text: truncateToLimit(text)
|
|
33374
|
+
}
|
|
33375
|
+
],
|
|
33376
|
+
structuredContent: {
|
|
33377
|
+
model_type: args.model_type,
|
|
33378
|
+
models: paginated.items,
|
|
33379
|
+
models_endpoint_available: listedModels.models_endpoint_available,
|
|
33380
|
+
pagination: {
|
|
33381
|
+
total: paginated.total,
|
|
33382
|
+
offset: paginated.offset,
|
|
33383
|
+
limit: paginated.limit,
|
|
33384
|
+
has_more: paginated.hasMore
|
|
33385
|
+
}
|
|
33386
|
+
}
|
|
33387
|
+
};
|
|
33388
|
+
} catch (error48) {
|
|
33389
|
+
return handleComfyError(error48);
|
|
33390
|
+
}
|
|
33391
|
+
}
|
|
32730
33392
|
function createServer() {
|
|
32731
33393
|
const server = new McpServer({
|
|
32732
33394
|
name: "@fre4x/comfyui",
|
|
@@ -32746,7 +33408,7 @@ function createServer() {
|
|
|
32746
33408
|
);
|
|
32747
33409
|
server.tool(
|
|
32748
33410
|
"comfyui_workflow_run",
|
|
32749
|
-
"Run a
|
|
33411
|
+
"Run a stored workflow_ref with small overrides.",
|
|
32750
33412
|
WorkflowRunSchema.shape,
|
|
32751
33413
|
handleWorkflowRun
|
|
32752
33414
|
);
|
|
@@ -32758,7 +33420,7 @@ function createServer() {
|
|
|
32758
33420
|
);
|
|
32759
33421
|
server.tool(
|
|
32760
33422
|
"comfyui_save_workflow",
|
|
32761
|
-
"Save a
|
|
33423
|
+
"Save a stored workflow_ref with optional overrides.",
|
|
32762
33424
|
SaveWorkflowSchema.shape,
|
|
32763
33425
|
handleSaveWorkflow
|
|
32764
33426
|
);
|
|
@@ -32768,6 +33430,35 @@ function createServer() {
|
|
|
32768
33430
|
WaitForWorkflowSchema.shape,
|
|
32769
33431
|
handleWaitForWorkflow
|
|
32770
33432
|
);
|
|
33433
|
+
server.tool(
|
|
33434
|
+
"comfyui_interrupt_workflow",
|
|
33435
|
+
"Interrupt the running workflow and optionally clear the queue.",
|
|
33436
|
+
InterruptWorkflowSchema.shape,
|
|
33437
|
+
{
|
|
33438
|
+
destructiveHint: true,
|
|
33439
|
+
openWorldHint: true
|
|
33440
|
+
},
|
|
33441
|
+
handleInterruptWorkflow
|
|
33442
|
+
);
|
|
33443
|
+
server.tool(
|
|
33444
|
+
"comfyui_upload_image",
|
|
33445
|
+
"Upload via file_path or previous prompt_id+filename. Image bytes never pass through the model.",
|
|
33446
|
+
UploadImageSchema.shape,
|
|
33447
|
+
{
|
|
33448
|
+
openWorldHint: true
|
|
33449
|
+
},
|
|
33450
|
+
handleUploadImage
|
|
33451
|
+
);
|
|
33452
|
+
server.tool(
|
|
33453
|
+
"comfyui_list_models",
|
|
33454
|
+
"List installed checkpoints, LoRAs, VAEs, and other models.",
|
|
33455
|
+
ListModelsSchema.shape,
|
|
33456
|
+
{
|
|
33457
|
+
readOnlyHint: true,
|
|
33458
|
+
openWorldHint: true
|
|
33459
|
+
},
|
|
33460
|
+
handleListModels
|
|
33461
|
+
);
|
|
32771
33462
|
return server;
|
|
32772
33463
|
}
|
|
32773
33464
|
function isMainModule(url2) {
|