Files

214 lines
7.5 KiB
JavaScript

import { app } from "../../../scripts/app.js";
function hideInternalWidgets(node) {
["existing_project", "project_id"].forEach((name) => {
const w = node.widgets?.find((w) => w.name === name);
if (w) {
w.hidden = true;
w.computeSize = () => [0, -4];
}
});
}
function setNameDisabled(widget, disabled) {
if (!widget) return;
const el = widget.inputEl || widget.textareaEl || widget.element;
widget.readOnly = !!disabled;
if (el) {
el.readOnly = !!disabled;
el.disabled = !!disabled;
el.tabIndex = disabled ? -1 : 0;
const st = el.style;
if (st) {
st.pointerEvents = disabled ? "none" : "auto";
st.opacity = disabled ? "0.7" : "1";
st.cursor = disabled ? "not-allowed" : "";
}
}
widget.disabled = !!disabled;
}
function sanitizeFileName(name) {
return (name || "").replace(/[^a-zA-Z0-9._-]/g, "");
}
async function fetchProjectFiles() {
const res = await fetch("/vantage/projects", { credentials: "same-origin" });
console.log("[VantageJS] GET /vantage/projects ->", res.status);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = await res.json();
console.log("[VantageJS] list files:", data);
const files = Array.isArray(data.files) ? data.files.filter(Boolean) : [];
return ["none", ...files];
}
// Unified hook that wires node.onExecuted and socket "executed"
function hookAfterExec(node, handler) {
const orig = node.onExecuted?.bind(node);
node.onExecuted = function (output) {
console.log("[VantageJS] onExecuted payload:", output);
try {
handler(output);
} catch (e) {
console.warn(e);
}
return orig ? orig(output) : undefined;
};
const s = app?.socket;
if (s && !node.__vp_exec_hooked) {
node.__vp_exec_hooked = true;
s.on("executed", (msg) => {
// Docs show executed contains node id and output = ui payload when ui is returned by node. [web:3]
const nid = msg?.node_id ?? msg?.node;
if (nid !== node.id) return;
console.log("[VantageJS] socket executed:", msg);
try {
handler(msg?.output ?? msg);
} catch (e) {
console.warn(e);
}
});
}
}
app.registerExtension({
name: "VantageProjectNode",
async beforeRegisterNodeDef(nodeType, nodeData) {
if (nodeData.name !== "VantageProject") return;
const origOnNodeCreated = nodeType.prototype.onNodeCreated;
nodeType.prototype.onNodeCreated = function () {
const r = origOnNodeCreated ? origOnNodeCreated.apply(this, arguments) : undefined;
const getW = (n) => this.widgets?.find((w) => w.name === n);
const projectListW = getW("project_list");
const projectNameW = getW("project_name");
const promptW = getW("positive_text");
const existingW = getW("existing_project");
const idW = getW("project_id");
const startPromptW = getW("start_prompt");
hideInternalWidgets(this);
// Refresh dropdown and optionally select a file
this.refreshProjectList = async (preferredFile) => {
try {
const items = await fetchProjectFiles();
const ctrl = projectListW;
if (!ctrl) return;
// Support legacy and modern combo widgets
if (ctrl.options) ctrl.options.values = items;
ctrl.values = items;
// Selection logic
let nextVal = ctrl.value;
if (preferredFile && items.includes(preferredFile)) nextVal = preferredFile;
else if (!items.includes(nextVal)) nextVal = items[0] || "none";
const changed = nextVal !== ctrl.value;
ctrl.value = nextVal;
if (typeof ctrl.callback === "function") ctrl.callback(nextVal, this, "project_list");
this.onWidgetChanged?.(ctrl, nextVal, "project_list");
this.setDirtyCanvas(true, true);
requestAnimationFrame(() => this.setDirtyCanvas(true, true));
} catch (e) {
console.warn("[VantageJS] refresh failed:", e);
}
};
// Optional partial load helper
const doPartialLoad = async () => {
const fileName = projectListW?.value;
if (!fileName || fileName === "none") return;
const safe = sanitizeFileName(fileName);
try {
const res = await fetch(`/vantage/preview?file=${encodeURIComponent(safe)}`, {
credentials: "same-origin",
});
console.log("[VantageJS] preview", safe, "->", res.status);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = await res.json();
if (idW) idW.value = data.id || "";
if (projectNameW) projectNameW.value = data.name || safe.replace(/\.json$/i, "");
const uiPrompt = Array.isArray(data.prompt_lines)
? data.prompt_lines.map((x) => (x == null ? "" : String(x))).join("\n")
: data.prompt || "";
if (promptW) promptW.value = uiPrompt;
if (existingW) existingW.value = !!data.existing;
if (startPromptW) startPromptW.value = Number.isFinite(data.start_prompt) ? data.start_prompt : 0;
setNameDisabled(projectNameW, !!data.existing);
this.graph?.setDirtyCanvas?.(true, true);
} catch (e) {
console.warn("[VantageJS] preview failed:", e);
}
};
// Buttons
const addBtn = (label, fn) => {
const b = this.addWidget("button", label, null, () => fn());
b.serialize = false;
return b;
};
addBtn("Load Project", () => doPartialLoad());
addBtn("New Project", () => {
if (existingW) existingW.value = false;
if (idW) idW.value = "";
if (projectNameW) projectNameW.value = "";
if (promptW) promptW.value = "";
if (startPromptW) startPromptW.value = 0;
setNameDisabled(projectNameW, false);
this.graph?.setDirtyCanvas?.(true, true);
});
addBtn("Refresh List", () => this.refreshProjectList(projectListW?.value));
// Executed -> bind state (refresh list, set id, set existing true)
const postBind = (file, pid) => {
console.log("[VantageJS] postBind file=", file, "pid=", pid);
if (!file) return;
if (existingW) existingW.value = true;
if (idW && pid) idW.value = pid;
this.refreshProjectList?.(file);
setNameDisabled(projectNameW, true);
this.graph?.setDirtyCanvas?.(true, true);
requestAnimationFrame(() => this.graph?.setDirtyCanvas?.(true, true));
};
// Robust payload extraction:
// - Prefer UI payload from executed (msg.output object with id/file) per docs. [web:3]
// - Fallback: plain object typed output with id/file
// - Fallback: string or tuple[string] JSON
hookAfterExec(this, (output) => {
let obj = null;
// Prefer grouped UI: { project: [ {...} ] }
if (output && output.project && Array.isArray(output.project) && output.project.length) {
obj = output.project[0];
} else if (output && typeof output === "object" && (output.id || output.file)) {
obj = output;
} else if (output && output.ui && typeof output.ui === "object") {
const u = output.ui;
if (u.project && Array.isArray(u.project) && u.project.length) obj = u.project[0];
} else if (Array.isArray(output) && output.length && typeof output[0] === "string") {
try { obj = JSON.parse(output[0]); } catch {}
} else if (typeof output === "string") {
try { obj = JSON.parse(output); } catch {}
}
if (!obj) { console.warn("[VantageJS] no usable executed payload"); return; }
postBind(obj.file || null, obj.id || null);
});
return r;
};
},
});