@wavequery/conductor
Version:
Modular LLM orchestration framework
270 lines • 9.88 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.GraphRenderer = void 0;
const d3 = __importStar(require("d3"));
class GraphRenderer {
constructor(containerId, config) {
const container = document.getElementById(containerId);
if (!container)
throw new Error("Container not found");
this.container = container;
this.config = config;
this.initialize();
}
initialize() {
this.setupSVG();
this.setupSimulation();
this.setupEventListeners();
}
setupSVG() {
const { width, height } = this.container.getBoundingClientRect();
this.svg = d3
.select(this.container)
.append("svg")
.attr("width", width)
.attr("height", height)
.attr("class", `theme-${this.config.theme}`);
// Create defs for markers and filters
const defs = this.svg.append("defs");
this.createMarkers(defs);
this.createFilters(defs);
this.mainGroup = this.svg.append("g").attr("class", "main-group");
if (this.config.interactive) {
this.setupZoom();
}
}
createMarkers(defs) {
const types = ["flow", "data", "control"];
types.forEach((type) => {
defs
.append("marker")
.attr("id", `arrow-${type}`)
.attr("viewBox", "0 -5 10 10")
.attr("refX", 20)
.attr("refY", 0)
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("orient", "auto")
.append("path")
.attr("d", "M0,-5L10,0L0,5")
.attr("class", `arrow-${type}`);
});
}
createFilters(defs) {
const filter = defs
.append("filter")
.attr("id", "glow")
.attr("x", "-50%")
.attr("y", "-50%")
.attr("width", "200%")
.attr("height", "200%");
filter
.append("feGaussianBlur")
.attr("stdDeviation", "3")
.attr("result", "coloredBlur");
const feMerge = filter.append("feMerge");
feMerge.append("feMergeNode").attr("in", "coloredBlur");
feMerge.append("feMergeNode").attr("in", "SourceGraphic");
}
setupSimulation() {
const { width, height } = this.container.getBoundingClientRect();
this.simulation = d3
.forceSimulation()
.force("link", d3.forceLink().id((d) => d.id))
.force("charge", d3.forceManyBody().strength(-1000))
.force("center", d3.forceCenter(width / 2, height / 2))
.on("tick", () => this.handleTick());
}
setupZoom() {
const zoom = d3
.zoom()
.scaleExtent([0.1, 4])
.on("zoom", (event) => {
this.mainGroup.attr("transform", event.transform.toString());
});
this.svg.call(zoom);
}
setupEventListeners() {
window.addEventListener("resize", this.handleResize.bind(this));
}
handleResize() {
const { width, height } = this.container.getBoundingClientRect();
this.svg.attr("width", width).attr("height", height);
if (this.config.fitView) {
this.fitViewToContent();
}
}
handleTick() {
// Update edge positions
this.edges
.select("path")
.attr("d", (d) => this.calculateEdgePath(d));
// Update node positions
this.nodes.attr("transform", (d) => `translate(${d.x || 0},${d.y || 0})`);
// Update edge labels
this.edges.select("text").attr("transform", (d) => {
const source = d.source;
const target = d.target;
return `translate(${(source.x + target.x) / 2},${(source.y + target.y) / 2})`;
});
}
calculateEdgePath(edge) {
const source = edge.source;
const target = edge.target;
const dx = target.x - source.x;
const dy = target.y - source.y;
const dr = Math.sqrt(dx * dx + dy * dy);
return `M${source.x},${source.y}A${dr},${dr} 0 0,1 ${target.x},${target.y}`;
}
render(graph) {
// Clear existing elements
this.mainGroup.selectAll("*").remove();
const edgeGroup = this.mainGroup.append("g").attr("class", "edges");
const nodeGroup = this.mainGroup.append("g").attr("class", "nodes");
this.renderEdges(graph.edges, edgeGroup);
this.renderNodes(graph.nodes, nodeGroup);
// Update simulation with new data
this.simulation.nodes(graph.nodes).force("link", d3
.forceLink(graph.edges)
.id((d) => d.id)
.distance(100)
.strength(0.5));
if (this.config.autoLayout) {
this.simulation.alpha(1).restart();
}
// Fit view after initial render
if (this.config.fitView) {
setTimeout(() => this.fitViewToContent(), 100);
}
}
renderNodes(nodes, container) {
this.nodes = container
.selectAll(".node")
.data(nodes, (d) => d.id)
.join((enter) => this.createNodes(enter), (update) => this.updateNodes(update), (exit) => this.removeNodes(exit));
}
createNodes(enter) {
const nodeGroups = enter
.append("g")
.attr("class", (d) => `node node-type-${d.type}`)
.call(this.setupDragBehavior());
// Add circle
nodeGroups.append("circle").attr("r", 20).attr("class", "node-circle");
// Add label
nodeGroups
.append("text")
.attr("dy", 30)
.attr("text-anchor", "middle")
.attr("class", "node-label")
.text((d) => d.label);
// Add status indicator
nodeGroups
.append("circle")
.attr("class", "status-indicator")
.attr("r", 5)
.attr("cy", -20);
return nodeGroups;
}
updateNodes(update) {
update.attr("class", (d) => `node node-type-${d.type}`);
update.select(".node-label").text((d) => d.label);
update
.select(".status-indicator")
.attr("class", (d) => `status-indicator status-${d.status}`);
return update;
}
removeNodes(exit) {
exit.transition().duration(300).style("opacity", 0).remove();
}
setupDragBehavior() {
return d3
.drag()
.on("start", (event, d) => {
if (!event.active)
this.simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
})
.on("drag", (event, d) => {
d.fx = event.x;
d.fy = event.y;
})
.on("end", (event, d) => {
if (!event.active)
this.simulation.alphaTarget(0);
});
}
renderEdges(edges, container) {
this.edges = container
.selectAll(".edge")
.data(edges, (d) => d.id)
.join((enter) => this.createEdges(enter), (update) => this.updateEdges(update), (exit) => this.removeEdges(exit));
}
createEdges(enter) {
const edgeGroups = enter
.append("g")
.attr("class", (d) => `edge edge-type-${d.type}`);
edgeGroups
.append("path")
.attr("class", "edge-path")
.attr("marker-end", (d) => `url(#arrow-${d.type})`);
edgeGroups
.append("text")
.attr("class", "edge-label")
.attr("text-anchor", "middle")
.text((d) => d.label || "");
return edgeGroups;
}
updateEdges(update) {
update.attr("class", (d) => `edge edge-type-${d.type}`);
update.select(".edge-label").text((d) => d.label || "");
return update;
}
removeEdges(exit) {
exit.transition().duration(300).style("opacity", 0).remove();
}
fitViewToContent() {
const bounds = this.mainGroup.node()?.getBBox();
if (!bounds)
return;
const { width, height } = this.container.getBoundingClientRect();
const scale = Math.min(width / bounds.width, height / bounds.height) * 0.9;
const transform = d3.zoomIdentity
.translate(width / 2 - (bounds.x + bounds.width / 2) * scale, height / 2 - (bounds.y + bounds.height / 2) * scale)
.scale(scale);
this.svg
.transition()
.duration(750)
.call(d3.zoom().transform, transform);
}
dispose() {
this.simulation.stop();
this.svg.remove();
window.removeEventListener("resize", this.handleResize);
}
}
exports.GraphRenderer = GraphRenderer;
//# sourceMappingURL=graph-renderer.js.map