feat: first functioning code

This commit is contained in:
Enrico Buratto
2026-03-29 12:01:11 +02:00
parent f5516e4bb0
commit 07c3a6c56a
8 changed files with 2293 additions and 0 deletions

241
static/app.js Normal file
View File

@@ -0,0 +1,241 @@
(function () {
// Global connection rule: only allow downstream -> upstream.
// LiteGraph checks compatibility via `LiteGraph.isValidConnection(typeA, typeB)`.
if (window.LiteGraph) {
const prev = window.LiteGraph.isValidConnection;
window.LiteGraph.isValidConnection = function (typeA, typeB) {
if (typeA === "downstream" && typeB === "upstream") return true;
if (typeA === "upstream" && typeB === "downstream") return false;
if (typeA === "downstream" && typeB === "downstream") return false;
if (typeA === "upstream" && typeB === "upstream") return false;
return prev ? prev.call(this, typeA, typeB) : true;
};
}
const sidebar = document.getElementById("sidebar");
const hamburger = document.getElementById("hamburger");
const output = document.getElementById("outputText");
const btnCompile = document.getElementById("btnCompile");
const btnSave = document.getElementById("btnSave");
const canvasEl = document.getElementById("graphCanvas");
let lastSuccessfulYaml = null;
function setSidebarOpen(open) {
if (open) {
sidebar.classList.remove("hidden");
sidebar.setAttribute("aria-hidden", "false");
} else {
sidebar.classList.add("hidden");
sidebar.setAttribute("aria-hidden", "true");
}
}
function isSidebarOpen() {
return !sidebar.classList.contains("hidden");
}
hamburger.addEventListener("click", (e) => {
e.stopPropagation();
setSidebarOpen(!isSidebarOpen());
});
// Close sidebar when clicking outside of it (and not on hamburger)
document.addEventListener("click", (e) => {
if (!isSidebarOpen()) return;
const target = e.target;
if (target === hamburger) return;
if (sidebar.contains(target)) return;
setSidebarOpen(false);
});
// Prevent clicks inside sidebar from bubbling to document and closing it.
sidebar.addEventListener("click", (e) => {
e.stopPropagation();
});
function setOutput(text) {
output.value = text;
output.scrollTop = output.scrollHeight;
}
function downloadText(filename, text) {
const blob = new Blob([text], { type: "text/yaml;charset=utf-8" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
a.remove();
URL.revokeObjectURL(url);
}
// --- LiteGraph setup ---
if (!window.LiteGraph || !window.LGraph || !window.LGraphCanvas) {
setOutput("LiteGraph failed to load.");
return;
}
const graph = new LGraph();
const graphCanvas = new LGraphCanvas(canvasEl, graph);
graphCanvas.background_image = "";
function resizeCanvas() {
const rect = canvasEl.getBoundingClientRect();
canvasEl.width = Math.floor(rect.width * window.devicePixelRatio);
canvasEl.height = Math.floor(rect.height * window.devicePixelRatio);
graphCanvas.resize();
}
window.addEventListener("resize", resizeCanvas);
resizeCanvas();
graph.start();
// Node definitions
function defineEntityNode(typeName, title, fields, io) {
function EntityNode() {
this.title = title;
this.size = [260, 140];
this.properties = {};
// Name first
this.properties.name = "";
this.addWidget("text", "Name", this.properties.name, (v) => {
this.properties.name = v;
});
fields.forEach((f) => {
this.properties[f.key] = "";
this.addWidget("text", f.label, this.properties[f.key], (v) => {
this.properties[f.key] = v;
});
});
if (io === "downstream") {
this.addOutput("out", "downstream");
} else {
this.addInput("in", "upstream");
}
}
EntityNode.title = title;
// Enforce downstream -> upstream only.
// Some LiteGraph builds pass 0/"*" for generic types.
EntityNode.prototype.onConnectOutput = function (slot, type) {
return type === "upstream" || type === 0 || type === "*";
};
LiteGraph.registerNodeType(typeName, EntityNode);
}
defineEntityNode(
"downstream.sharepoint",
"Sharepoint",
[
{ key: "tenant_id", label: "Tenant ID" },
{ key: "graph_url", label: "Graph URL" },
{ key: "api_key", label: "API key" },
{ key: "files_to_exclude", label: "Files to exclude" },
],
"downstream"
);
defineEntityNode(
"downstream.confluence",
"Confluence",
[
{ key: "space_url", label: "Space url" },
{ key: "api_key", label: "API key" },
{ key: "pages_to_exclude", label: "Pages to exclude" },
],
"downstream"
);
defineEntityNode(
"upstream.azure_ai_search",
"Azure AI Search",
[
{ key: "tenant_url", label: "Tenant URL" },
{ key: "api_key", label: "API key" },
{ key: "index_name", label: "Index name" },
],
"upstream"
);
defineEntityNode(
"upstream.azure_vector_store",
"Azure Vector Store",
[
{ key: "tenant_url", label: "Tenant URL" },
{ key: "api_key", label: "API key" },
{ key: "index_name", label: "Index name" },
],
"upstream"
);
function spawnNode(typeName) {
const node = LiteGraph.createNode(typeName);
if (!node) {
setOutput(`Failed to create node type: ${typeName}`);
return;
}
node.pos = [graphCanvas.ds.offset[0] * -1 + 80, graphCanvas.ds.offset[1] * -1 + 80];
graph.add(node);
graphCanvas.selectNode(node);
}
// Entity buttons: spawn node and close sidebar
document.querySelectorAll(".entityBtn").forEach((btn) => {
btn.addEventListener("click", () => {
const entity = btn.getAttribute("data-entity");
spawnNode(entity);
setSidebarOpen(false);
});
});
async function doCompile() {
setOutput("Compiling...");
lastSuccessfulYaml = null;
const graphJson = graph.serialize();
const res = await fetch("/api/compile", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ graph: graphJson }),
});
const data = await res.json().catch(() => null);
if (!data) {
setOutput("Compile failed: invalid server response");
return { ok: false };
}
if (!data.ok) {
setOutput(data.error || "Compile failed");
return { ok: false };
}
lastSuccessfulYaml = data.yaml;
setOutput(data.yaml);
return { ok: true, yaml: data.yaml };
}
btnCompile.addEventListener("click", () => {
doCompile().catch((e) => setOutput(`Compile failed: ${e}`));
});
btnSave.addEventListener("click", () => {
(async () => {
if (!lastSuccessfulYaml) {
const r = await doCompile();
if (!r.ok) return;
}
downloadText("graph.yaml", lastSuccessfulYaml);
})().catch((e) => setOutput(`Save failed: ${e}`));
});
setOutput("Ready. Use hamburger to add entities from the sidebar.");
})();