feat(#72): implement interactive graph visualization component

- Create KnowledgeGraphViewer component with @xyflow/react
- Implement three layout types: force-directed, hierarchical (ELK), circular
- Add node sizing based on connection count (40px-120px range)
- Apply PDA-friendly status colors (green=published, blue=draft, gray=archived)
- Highlight orphan nodes with distinct color
- Add interactive features: zoom, pan, click-to-navigate
- Implement filters: status, tags, show/hide orphans
- Add statistics display and legend panel
- Create comprehensive test suite (16 tests, all passing)
- Add fetchKnowledgeGraph API function
- Create /knowledge/graph page
- Performance tested with 500+ nodes
- All quality gates passed (tests, typecheck, lint)

Refs #72

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Jason Woltje
2026-02-02 15:38:16 -06:00
parent 5d348526de
commit 0e64dc8525
28 changed files with 1590 additions and 0 deletions

View File

@@ -318,6 +318,59 @@ export async function fetchEntryGraph(
return apiGet(`/api/knowledge/entries/${slug}/graph?${params.toString()}`);
}
/**
* Fetch full knowledge graph
*/
export async function fetchKnowledgeGraph(filters?: {
tags?: string[];
status?: string;
limit?: number;
}): Promise<{
nodes: {
id: string;
slug: string;
title: string;
summary: string | null;
status?: string;
tags: {
id: string;
name: string;
slug: string;
color: string | null;
}[];
depth: number;
isOrphan?: boolean;
}[];
edges: {
id: string;
sourceId: string;
targetId: string;
linkText: string;
}[];
stats: {
totalNodes: number;
totalEdges: number;
orphanCount: number;
};
}> {
const params = new URLSearchParams();
if (filters?.tags && filters.tags.length > 0) {
filters.tags.forEach((tag) => {
params.append("tags", tag);
});
}
if (filters?.status) {
params.append("status", filters.status);
}
if (filters?.limit !== undefined) {
params.append("limit", filters.limit.toString());
}
const queryString = params.toString();
const endpoint = queryString ? `/api/knowledge/graph?${queryString}` : "/api/knowledge/graph";
return apiGet(endpoint);
}
/**
* Mock entries for development (until backend endpoints are ready)
*/