File size: 4,984 Bytes
0c85e62 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 | // SPDX-FileCopyrightText: 2025-2026 Kforge Labs <https://github.com/malkuthro/ComfyUI-Koolook>
// SPDX-License-Identifier: GPL-3.0-only
import { getWorkflowGraph as defaultGetWorkflowGraph } from "./workflows_store.js";
const PUBLISH_ROUTE = "/koolook/api/setups";
const REVEAL_ROUTE = "/koolook/api/setups/reveal";
function cloneJson(value) {
return JSON.parse(JSON.stringify(value));
}
function normalizeTags(value) {
if (Array.isArray(value)) {
return value
.filter(item => typeof item === "string")
.map(item => item.trim())
.filter(Boolean);
}
if (typeof value !== "string") return [];
return value
.split(",")
.map(item => item.trim())
.filter(Boolean);
}
function sourcePath(dirPath, wfName) {
return [...dirPath, wfName].join("/");
}
function normalizeMetadata(metadata) {
return {
id: typeof metadata?.id === "string" ? metadata.id.trim() : "",
title: typeof metadata?.title === "string" ? metadata.title.trim() : "",
description: typeof metadata?.description === "string" ? metadata.description.trim() : "",
category: typeof metadata?.category === "string" ? metadata.category.trim() : "",
tags: normalizeTags(metadata?.tags),
previewImage: typeof metadata?.previewImage === "string" ? metadata.previewImage.trim() : "",
};
}
function asObject(value) {
return (value && typeof value === "object") ? value : {};
}
function asString(value) {
return typeof value === "string" ? value : "";
}
// Reduce a publish API response into the flat view-model the success card
// renders. Pure (no DOM) so the mapping is unit-testable; the modal renders
// it and wires actions from the boolean flags. Open folder / Copy path are
// only offered when the server told us where the setup landed.
export function buildPublishSuccessView(resultBody) {
const body = asObject(resultBody);
const setup = asObject(body.setup);
const storagePath = asString(body.storagePath);
return {
setupId: asString(setup.id),
title: asString(asObject(setup.metadata).title),
sourcePath: asString(asObject(setup.source).path),
validationStatus: asString(asObject(setup.validation).status),
storagePath,
canCopyPath: Boolean(storagePath),
canOpenFolder: Boolean(storagePath),
};
}
export async function revealPublishedSetupFolder({ fetchImpl = fetch } = {}) {
const response = await fetchImpl(REVEAL_ROUTE, { method: "POST" });
if (!response.ok) {
let reason = "";
try {
reason = (await response.text() || "").trim();
} catch (e) {
/* fall through to status text */
}
throw new Error(reason || response.statusText || `Reveal failed (${response.status}).`);
}
return response.json();
}
export async function publishSavedWorkflowSetup({
dirPath,
wfName,
visualGraph,
apiPrompt,
captureApiPrompt,
metadata,
inputContract,
outputContract,
getWorkflowGraph = defaultGetWorkflowGraph,
fetchImpl = fetch,
}) {
const path = Array.isArray(dirPath) ? dirPath : [];
const graph = (visualGraph && typeof visualGraph === "object")
? visualGraph
: getWorkflowGraph(path, wfName);
if (!graph || typeof graph !== "object") {
throw new Error("Saved workflow not found.");
}
let resolvedApiPrompt = (apiPrompt && typeof apiPrompt === "object") ? apiPrompt : null;
if (!resolvedApiPrompt && typeof captureApiPrompt === "function") {
resolvedApiPrompt = await captureApiPrompt(graph);
if (!resolvedApiPrompt || typeof resolvedApiPrompt !== "object") {
throw new Error("API prompt capture failed. Publish needs ComfyUI's API-format prompt for this workflow.");
}
}
if (!resolvedApiPrompt) {
throw new Error("API prompt capture is required before publishing this workflow.");
}
const payload = {
visualGraph: cloneJson(graph),
apiPrompt: cloneJson(resolvedApiPrompt),
metadata: normalizeMetadata(metadata || {}),
inputContract: cloneJson(inputContract || { inputs: [] }),
outputContract: cloneJson(outputContract || { outputs: [] }),
source: {
kind: "sidebar-workflow",
path: sourcePath(path, wfName),
inventoryPath: cloneJson(path),
name: typeof wfName === "string" ? wfName : "",
},
};
const response = await fetchImpl(PUBLISH_ROUTE, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
});
const body = await response.json().catch(() => ({}));
if (!response.ok || body.ok === false) {
const errors = Array.isArray(body.errors) ? body.errors : [`Publish failed (${response.status}).`];
throw new Error(errors.join("; "));
}
return body;
}
|