chore: import upstream snapshot with attribution
OpenSSF Scorecard / scorecard (push) Failing after 0s
DCO / dco (push) Failing after 0s
CodeQL SAST / analyze (push) Failing after 1s
Deploy Pages / deploy (push) Failing after 1s

This commit is contained in:
wehub-resource-sync
2026-07-13 12:28:05 +08:00
commit 41cb1c0170
1830 changed files with 38276124 additions and 0 deletions
+131
View File
@@ -0,0 +1,131 @@
import { useCallback, useState } from "react";
import type { GraphData } from "../lib/types";
export interface LoadProgress {
receivedBytes: number;
totalBytes: number | null;
}
interface UseGraphDataResult {
data: GraphData | null;
loading: boolean;
error: string | null;
progress: LoadProgress;
fetchOverview: (
project: string,
maxNodes?: number,
graph?: "code" | "missed",
) => void;
fetchDetail: (project: string, centerNode: string) => void;
}
/* Node budget: how many nodes the layout endpoint is asked for. The default
* keeps first paint fast; the user can raise it in 5k steps up to the hard
* ceiling (mirrors HARD_MAX_NODES in src/ui/layout3d.c). Edges always follow
* the budget — the server returns every edge between the loaded nodes. */
export const GRAPH_RENDER_NODE_LIMIT = 5000;
export const GRAPH_NODE_BUDGET_STEP = 5000;
export const GRAPH_NODE_BUDGET_MAX = 10_000_000;
export function clampNodeBudget(value: number): number {
if (!Number.isFinite(value)) return GRAPH_RENDER_NODE_LIMIT;
const stepped =
Math.round(value / GRAPH_NODE_BUDGET_STEP) * GRAPH_NODE_BUDGET_STEP;
if (stepped < GRAPH_NODE_BUDGET_STEP) return GRAPH_NODE_BUDGET_STEP;
if (stepped > GRAPH_NODE_BUDGET_MAX) return GRAPH_NODE_BUDGET_MAX;
return stepped;
}
/** Which graph to lay out: the code graph (default) or the missed graph —
* only files the indexer could not fully cover, as their file structure. */
export type GraphVariant = "code" | "missed";
export async function fetchLayout(
project: string,
maxNodes = GRAPH_RENDER_NODE_LIMIT,
onProgress?: (progress: LoadProgress) => void,
graph: GraphVariant = "code",
): Promise<GraphData> {
const params = new URLSearchParams({ project, max_nodes: String(maxNodes) });
if (graph === "missed") params.set("graph", "missed");
const res = await fetch(`/api/layout?${params}`);
if (!res.ok) {
const body = await res.json().catch(() => ({ error: res.statusText }));
throw new Error(body.error ?? `HTTP ${res.status}`);
}
/* Stream the body when possible so large budgets show live download
* progress instead of a silent stall. */
if (!res.body || !onProgress) {
return res.json();
}
const lengthHeader = res.headers.get("content-length");
const totalBytes = lengthHeader ? parseInt(lengthHeader, 10) || null : null;
const reader = res.body.getReader();
const chunks: Uint8Array[] = [];
let receivedBytes = 0;
for (;;) {
const { done, value } = await reader.read();
if (done) break;
chunks.push(value);
receivedBytes += value.length;
onProgress({ receivedBytes, totalBytes });
}
const merged = new Uint8Array(receivedBytes);
let offset = 0;
for (const chunk of chunks) {
merged.set(chunk, offset);
offset += chunk.length;
}
return JSON.parse(new TextDecoder().decode(merged));
}
const NO_PROGRESS: LoadProgress = { receivedBytes: 0, totalBytes: null };
export function useGraphData(): UseGraphDataResult {
const [data, setData] = useState<GraphData | null>(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [progress, setProgress] = useState<LoadProgress>(NO_PROGRESS);
const fetchOverview = useCallback(
async (project: string, maxNodes?: number, graph: GraphVariant = "code") => {
setLoading(true);
setError(null);
setProgress(NO_PROGRESS);
try {
const result = await fetchLayout(project, maxNodes, setProgress, graph);
setData(result);
} catch (e) {
setError(e instanceof Error ? e.message : "Failed to fetch layout");
} finally {
setLoading(false);
}
},
[],
);
const fetchDetail = useCallback(
async (project: string, _centerNode: string) => {
setLoading(true);
setError(null);
setProgress(NO_PROGRESS);
try {
/* TODO: detail level with center_node filtering */
const result = await fetchLayout(project, undefined, setProgress);
setData(result);
} catch (e) {
setError(e instanceof Error ? e.message : "Failed to fetch layout");
} finally {
setLoading(false);
}
},
[],
);
return { data, loading, error, progress, fetchOverview, fetchDetail };
}