132 lines
4.5 KiB
JavaScript
132 lines
4.5 KiB
JavaScript
/** Pure ordering and value helpers for the Workflow Recipe parameter view. */
|
|
|
|
import { translate } from './locales.js';
|
|
|
|
const t = (key, params) => translate(key, params);
|
|
|
|
export function topologicalSortNodes(workflowNodes, workflowLinks) {
|
|
const inDegree = new Map();
|
|
const adj = new Map();
|
|
const allIds = new Set();
|
|
|
|
for (const node of workflowNodes) {
|
|
const id = String(node.id);
|
|
allIds.add(id);
|
|
inDegree.set(id, 0);
|
|
adj.set(id, []);
|
|
}
|
|
|
|
const rawLinks = workflowLinks;
|
|
const linksArray = Array.isArray(rawLinks) ? rawLinks : (rawLinks && typeof rawLinks === 'object' ? Object.values(rawLinks) : []);
|
|
|
|
for (const link of linksArray) {
|
|
if (!Array.isArray(link) || link.length < 4) continue;
|
|
const originId = String(link[1]);
|
|
const targetId = String(link[3]);
|
|
if (allIds.has(originId) && allIds.has(targetId)) {
|
|
adj.get(originId).push(targetId);
|
|
inDegree.set(targetId, inDegree.get(targetId) + 1);
|
|
}
|
|
}
|
|
|
|
const queue = [];
|
|
for (const [id, deg] of inDegree.entries()) {
|
|
if (deg === 0) queue.push(id);
|
|
}
|
|
|
|
const sorted = [];
|
|
while (queue.length > 0) {
|
|
const u = queue.shift();
|
|
sorted.push(u);
|
|
for (const v of adj.get(u)) {
|
|
inDegree.set(v, inDegree.get(v) - 1);
|
|
if (inDegree.get(v) === 0) queue.push(v);
|
|
}
|
|
}
|
|
|
|
for (const id of allIds) {
|
|
if (inDegree.get(id) > 0) sorted.push(id);
|
|
}
|
|
|
|
return sorted;
|
|
}
|
|
|
|
export function parameterNodeOrder(recipe) {
|
|
const summaries = Array.isArray(recipe?.params?.nodes) ? recipe.params.nodes : [];
|
|
const workflowNodes = Array.isArray(recipe?.workflow?.nodes) ? recipe.workflow.nodes : [];
|
|
const byId = new Map(workflowNodes.map((node) => [String(node?.id), node]));
|
|
const summaryById = new Map(summaries.map((node) => [String(node?.id), node]));
|
|
const orderedIds = topologicalSortNodes(workflowNodes, recipe?.workflow?.links);
|
|
const result = [];
|
|
const seen = new Set();
|
|
for (const id of orderedIds) {
|
|
const summary = summaryById.get(id);
|
|
const workflowNode = byId.get(id);
|
|
if (summary || workflowNode) {
|
|
result.push({ summary: summary || { id, type: workflowNode?.type, title: workflowNode?.title, widgets: [] }, workflowNode });
|
|
seen.add(id);
|
|
}
|
|
}
|
|
for (const summary of summaries) {
|
|
const id = String(summary?.id);
|
|
if (!seen.has(id)) result.push({ summary, workflowNode: byId.get(id) });
|
|
}
|
|
return result;
|
|
}
|
|
|
|
export function isVolatileParameter(node, widget, index) {
|
|
const widgetName = String(widget?.name || '').toLowerCase();
|
|
if (/(^|[_\s-])(seed|noise_seed|random_seed|variation_seed|last_seed)([_\s-]|$)/i.test(widgetName)) return true;
|
|
const nodeType = String(node?.type || '').toLowerCase();
|
|
if (nodeType === 'ksampler') return index === 0;
|
|
if (nodeType === 'ksampleradvanced') return index === 1;
|
|
return false;
|
|
}
|
|
|
|
export function cloneJson(value) {
|
|
try { return JSON.parse(JSON.stringify(value)); } catch (error) { return null; }
|
|
}
|
|
|
|
export function editorValueText(value) {
|
|
if (typeof value === 'string') return value;
|
|
if (value === undefined) return '';
|
|
try { return JSON.stringify(value); } catch (error) { return String(value); }
|
|
}
|
|
|
|
export function parseEditorValue(raw, original) {
|
|
if (typeof original === 'number') {
|
|
const value = Number(raw);
|
|
if (!Number.isFinite(value)) throw new Error('invalid number');
|
|
return value;
|
|
}
|
|
if (typeof original === 'boolean') {
|
|
if (raw !== 'true' && raw !== 'false') throw new Error('invalid boolean');
|
|
return raw === 'true';
|
|
}
|
|
if (original !== null && typeof original === 'object') return JSON.parse(raw);
|
|
return raw;
|
|
}
|
|
|
|
export function promptRoleLabel(role) {
|
|
return t({
|
|
positive: 'recipePromptRolePositive',
|
|
negative: 'recipePromptRoleNegative',
|
|
both: 'recipePromptRoleBoth',
|
|
ignored: 'recipePromptRoleIgnored',
|
|
unknown: 'recipePromptRoleUnknown',
|
|
}[role] || 'recipePromptRoleUnknown');
|
|
}
|
|
|
|
export function formatRecipeResolution(res) {
|
|
if (!res) return '';
|
|
if (typeof res === 'string' || typeof res === 'number') return String(res);
|
|
if (Array.isArray(res) && res.length >= 2) return `${res[0]}x${res[1]}`;
|
|
if (typeof res === 'object') {
|
|
const w = res.width ?? res.w ?? res.x;
|
|
const h = res.height ?? res.h ?? res.y;
|
|
if (w && h) return `${w}x${h}`;
|
|
}
|
|
return '';
|
|
}
|
|
|