Reorganiza backend
This commit is contained in:
@@ -1,10 +1,14 @@
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { Renderer } from "./renderer";
|
||||
|
||||
function sleep(ms: number): Promise<void> {
|
||||
return new Promise((r) => setTimeout(r, ms));
|
||||
}
|
||||
|
||||
export default function App() {
|
||||
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||||
const rendererRef = useRef<Renderer | null>(null);
|
||||
const [status, setStatus] = useState("Loading node positions…");
|
||||
const [status, setStatus] = useState("Waiting for backend…");
|
||||
const [nodeCount, setNodeCount] = useState(0);
|
||||
const [stats, setStats] = useState({
|
||||
fps: 0,
|
||||
@@ -16,7 +20,7 @@ export default function App() {
|
||||
const [error, setError] = useState("");
|
||||
const [hoveredNode, setHoveredNode] = useState<{ x: number; y: number; screenX: number; screenY: number } | null>(null);
|
||||
const [selectedNodes, setSelectedNodes] = useState<Set<number>>(new Set());
|
||||
const [backendStats, setBackendStats] = useState<{ nodes: number; edges: number; parsed_triples: number } | null>(null);
|
||||
const [backendStats, setBackendStats] = useState<{ nodes: number; edges: number; backend?: string } | null>(null);
|
||||
|
||||
// Store mouse position in a ref so it can be accessed in render loop without re-renders
|
||||
const mousePos = useRef({ x: 0, y: 0 });
|
||||
@@ -36,68 +40,79 @@ export default function App() {
|
||||
|
||||
let cancelled = false;
|
||||
|
||||
// Optional: fetch backend stats (proxied via Vite) so you can confirm backend is up.
|
||||
fetch("/api/stats")
|
||||
.then((r) => (r.ok ? r.json() : null))
|
||||
.then((j) => {
|
||||
if (!j || cancelled) return;
|
||||
if (typeof j.nodes === "number" && typeof j.edges === "number" && typeof j.parsed_triples === "number") {
|
||||
setBackendStats({ nodes: j.nodes, edges: j.edges, parsed_triples: j.parsed_triples });
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
// Backend is optional; ignore failures.
|
||||
});
|
||||
|
||||
// Fetch CSVs, parse, and init renderer
|
||||
(async () => {
|
||||
try {
|
||||
setStatus("Fetching data files…");
|
||||
const [nodesResponse, edgesResponse] = await Promise.all([
|
||||
fetch("/node_positions.csv"),
|
||||
fetch("/edges.csv"),
|
||||
]);
|
||||
if (!nodesResponse.ok) throw new Error(`Failed to fetch nodes: ${nodesResponse.status}`);
|
||||
if (!edgesResponse.ok) throw new Error(`Failed to fetch edges: ${edgesResponse.status}`);
|
||||
// Wait for backend (docker-compose also gates startup via healthcheck, but this
|
||||
// handles running the frontend standalone).
|
||||
const deadline = performance.now() + 180_000;
|
||||
let attempt = 0;
|
||||
while (performance.now() < deadline) {
|
||||
attempt++;
|
||||
setStatus(`Waiting for backend… (attempt ${attempt})`);
|
||||
try {
|
||||
const res = await fetch("/api/health");
|
||||
if (res.ok) break;
|
||||
} catch {
|
||||
// ignore and retry
|
||||
}
|
||||
await sleep(1000);
|
||||
if (cancelled) return;
|
||||
}
|
||||
|
||||
const [nodesText, edgesText] = await Promise.all([
|
||||
nodesResponse.text(),
|
||||
edgesResponse.text(),
|
||||
]);
|
||||
setStatus("Fetching graph…");
|
||||
const graphRes = await fetch("/api/graph");
|
||||
if (!graphRes.ok) throw new Error(`Failed to fetch graph: ${graphRes.status}`);
|
||||
const graph = await graphRes.json();
|
||||
if (cancelled) return;
|
||||
|
||||
setStatus("Parsing positions…");
|
||||
const nodeLines = nodesText.split("\n").slice(1).filter(l => l.trim().length > 0);
|
||||
const count = nodeLines.length;
|
||||
const nodes = Array.isArray(graph.nodes) ? graph.nodes : [];
|
||||
const edges = Array.isArray(graph.edges) ? graph.edges : [];
|
||||
const meta = graph.meta || null;
|
||||
const count = nodes.length;
|
||||
|
||||
// Build positions from backend-provided node coordinates.
|
||||
setStatus("Preparing buffers…");
|
||||
const xs = new Float32Array(count);
|
||||
const ys = new Float32Array(count);
|
||||
for (let i = 0; i < count; i++) {
|
||||
const nx = nodes[i]?.x;
|
||||
const ny = nodes[i]?.y;
|
||||
xs[i] = typeof nx === "number" ? nx : 0;
|
||||
ys[i] = typeof ny === "number" ? ny : 0;
|
||||
}
|
||||
const vertexIds = new Uint32Array(count);
|
||||
for (let i = 0; i < count; i++) {
|
||||
const parts = nodeLines[i].split(",");
|
||||
vertexIds[i] = parseInt(parts[0], 10);
|
||||
xs[i] = parseFloat(parts[1]);
|
||||
ys[i] = parseFloat(parts[2]);
|
||||
const id = nodes[i]?.id;
|
||||
vertexIds[i] = typeof id === "number" ? id >>> 0 : i;
|
||||
}
|
||||
|
||||
setStatus("Parsing edges…");
|
||||
const edgeLines = edgesText.split("\n").slice(1).filter(l => l.trim().length > 0);
|
||||
const edgeData = new Uint32Array(edgeLines.length * 2);
|
||||
for (let i = 0; i < edgeLines.length; i++) {
|
||||
const parts = edgeLines[i].split(",");
|
||||
edgeData[i * 2] = parseInt(parts[0], 10);
|
||||
edgeData[i * 2 + 1] = parseInt(parts[1], 10);
|
||||
// Build edges as vertex-id pairs.
|
||||
const edgeData = new Uint32Array(edges.length * 2);
|
||||
for (let i = 0; i < edges.length; i++) {
|
||||
const s = edges[i]?.source;
|
||||
const t = edges[i]?.target;
|
||||
edgeData[i * 2] = typeof s === "number" ? s >>> 0 : 0;
|
||||
edgeData[i * 2 + 1] = typeof t === "number" ? t >>> 0 : 0;
|
||||
}
|
||||
|
||||
if (cancelled) return;
|
||||
// Use /api/graph meta; don't do a second expensive backend call.
|
||||
if (meta && typeof meta.nodes === "number" && typeof meta.edges === "number") {
|
||||
setBackendStats({
|
||||
nodes: meta.nodes,
|
||||
edges: meta.edges,
|
||||
backend: typeof meta.backend === "string" ? meta.backend : undefined,
|
||||
});
|
||||
} else {
|
||||
setBackendStats({ nodes: nodes.length, edges: edges.length });
|
||||
}
|
||||
|
||||
setStatus("Building spatial index…");
|
||||
await new Promise(r => setTimeout(r, 0));
|
||||
await new Promise((r) => setTimeout(r, 0));
|
||||
|
||||
const buildMs = renderer.init(xs, ys, vertexIds, edgeData);
|
||||
setNodeCount(renderer.getNodeCount());
|
||||
setStatus("");
|
||||
console.log(`Init complete: ${count.toLocaleString()} nodes, ${edgeLines.length.toLocaleString()} edges in ${buildMs.toFixed(0)}ms`);
|
||||
console.log(`Init complete: ${count.toLocaleString()} nodes, ${edges.length.toLocaleString()} edges in ${buildMs.toFixed(0)}ms`);
|
||||
} catch (e) {
|
||||
if (!cancelled) {
|
||||
setError(e instanceof Error ? e.message : String(e));
|
||||
@@ -295,7 +310,7 @@ export default function App() {
|
||||
<div style={{ color: "#f80" }}>Selected: {selectedNodes.size}</div>
|
||||
{backendStats && (
|
||||
<div style={{ color: "#8f8" }}>
|
||||
Backend: {backendStats.nodes.toLocaleString()} nodes, {backendStats.edges.toLocaleString()} edges
|
||||
Backend{backendStats.backend ? ` (${backendStats.backend})` : ""}: {backendStats.nodes.toLocaleString()} nodes, {backendStats.edges.toLocaleString()} edges
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user