VerdantClaw-Marimo / index.html
TheEdict's picture
Add public folder
d2978d5 verified
Raw
History Blame Contribute Delete
5.15 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>HuggingClaw Graph Viewer</title>
<script src="https://d3js.org/d3.v7.min.js"></script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: #1a1a2e;
color: #eee;
overflow: hidden;
}
#graph {
width: 100vw;
height: 100vh;
}
.controls {
position: absolute;
top: 20px;
left: 20px;
background: rgba(0, 0, 0, 0.8);
padding: 15px;
border-radius: 8px;
z-index: 100;
}
.controls h1 {
font-size: 18px;
margin-bottom: 10px;
}
.controls button {
background: #4a4a6a;
color: #eee;
border: none;
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
margin: 5px 0;
width: 100%;
}
.controls button:hover {
background: #5a5a7a;
}
.stats {
position: absolute;
bottom: 20px;
left: 20px;
background: rgba(0, 0, 0, 0.8);
padding: 15px;
border-radius: 8px;
font-size: 14px;
}
.node circle {
stroke: #fff;
stroke-width: 2px;
}
.link {
stroke: #666;
stroke-opacity: 0.6;
}
.label {
font-size: 12px;
fill: #eee;
pointer-events: none;
}
</style>
</head>
<body>
<div id="graph"></div>
<div class="controls">
<h1>🕸️ Graph Viewer</h1>
<button onclick="loadGraph()">Refresh</button>
<button onclick="resetZoom()">Reset Zoom</button>
</div>
<div class="stats">
<div>Nodes: <span id="node-count">0</span></div>
<div>Edges: <span id="edge-count">0</span></div>
</div>
<script>
const width = window.innerWidth;
const height = window.innerHeight;
const svg = d3.select('#graph')
.append('svg')
.attr('width', width)
.attr('height', height)
.call(d3.zoom().on('zoom', (event) => {
g.attr('transform', event.transform);
}));
const g = svg.append('g');
const simulation = d3.forceSimulation()
.force('link', d3.forceLink().id(d => d.id).distance(100))
.force('charge', d3.forceManyBody().strength(-300))
.force('center', d3.forceCenter(width / 2, height / 2))
.force('collide', d3.forceCollide(30));
let nodes = [];
let links = [];
async function loadGraph() {
try {
const response = await fetch('/api/graph');
const data = await response.json();
nodes = data.nodes || [];
links = data.edges || [];
updateGraph();
} catch (error) {
console.error('Failed to load graph:', error);
}
}
function updateGraph() {
// Update stats
document.getElementById('node-count').textContent = nodes.length;
document.getElementById('edge-count').textContent = links.length;
// Clear previous graph
g.selectAll('*').remove();
// Draw links
const link = g.selectAll('.link')
.data(links)
.enter()
.append('line')
.attr('class', 'link');
// Draw nodes
const node = g.selectAll('.node')
.data(nodes)
.enter()
.append('g')
.attr('class', 'node')
.call(d3.drag()
.on('start', dragstarted)
.on('drag', dragged)
.on('end', dragended));
node.append('circle')
.attr('r', 15)
.attr('fill', d => {
if (d.layer === 'requirements') return '#4a90d9';
if (d.layer === 'epistemic') return '#50c878';
return '#9b59b6';
});
node.append('text')
.attr('class', 'label')
.attr('dx', 20)
.attr('dy', 4)
.text(d => d.id || d.title || 'Node');
// Start simulation
simulation.nodes(nodes).on('tick', () => {
link
.attr('x1', d => d.source.x)
.attr('y1', d => d.source.y)
.attr('x2', d => d.target.x)
.attr('y2', d => d.target.y);
node.attr('transform', d => `translate(${d.x},${d.y})`);
});
simulation.force('link').links(links);
simulation.alpha(1).restart();
}
function resetZoom() {
svg.transition().call(
d3.zoom().transform,
d3.zoomIdentity
);
}
function dragstarted(event, d) {
if (!event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
function dragged(event, d) {
d.fx = event.x;
d.fy = event.y;
}
function dragended(event, d) {
if (!event.active) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
// Load graph on startup
loadGraph();
// Auto-refresh every 10 seconds
setInterval(loadGraph, 10000);
</script>
</body>
</html>