Spaces:
Build error
Build error
| <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> | |