Expose knowledge graph data in the Memory view through a localized, resilient dashboard panel. - Add protected knowledge graph API routes and access helpers. - Add a Memory view tab, graph panel, loading/error states, and localized labels. - Document graph navigation and cover access, route, hook, and component behavior. Files changed: .changeset/fn-9038-knowledge-graph-tab.md | 7 + docs/dashboard-guide.md | 2 + docs/knowledge-graph.md | 10 ++ packages/dashboard/app/api/legacy.ts | 17 ++- packages/dashboard/app/api/system/index.ts | 1 + .../dashboard/app/api/system/knowledge-graph.ts | 23 +++ .../app/components/KnowledgeGraphPanel.css | 35 +++++ .../app/components/KnowledgeGraphPanel.tsx | 68 +++++++++ packages/dashboard/app/components/MemoryView.tsx | 15 +- .../__tests__/KnowledgeGraphPanel.test.tsx | 49 +++++++ .../app/components/__tests__/MemoryView.test.tsx | 13 ++ .../app/hooks/__tests__/useKnowledgeGraph.test.ts | 66 +++++++++ packages/dashboard/app/hooks/useKnowledgeGraph.ts | 94 ++++++++++++ .../src/__tests__/knowledge-graph-access.test.ts | 88 ++++++++++++ packages/dashboard/src/knowledge-graph-access.ts | 160 +++++++++++++++++++++ .../register-knowledge-graph-routes.test.ts | 75 ++++++++++ .../src/routes/register-knowledge-routes.ts | 120 ++++++++++++++++ packages/i18n/locales/en/app.json | 15 +- packages/i18n/locales/es/app.json | 15 +- packages/i18n/locales/fr/app.json | 15 +- packages/i18n/locales/ko/app.json | 15 +- packages/i18n/locales/pt-BR/app.json | 15 +- packages/i18n/locales/zh-CN/app.json | 15 +- packages/i18n/locales/zh-TW/app.json | 15 +- 24 files changed, 939 insertions(+), 9 deletions(-) Fusion-Task-Id: FN-9038 Fusion-Task-Lineage: 4200e214-ad22-4f92-b980-c9bcb8ffd3b9 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
24 lines
3.9 KiB
TypeScript
24 lines
3.9 KiB
TypeScript
import { api } from "../client/client.js";
|
|
import { withProjectId } from "../client/health.js";
|
|
|
|
export type KnowledgeGraphNode = { id: string; kind: "file" | "module" | "symbol" | "doc-concept" | "rationale"; name: string; owner: "file" | "derived"; ownerPath: string; source: { path: string; line: number; column: number }; attributes: Record<string, string> };
|
|
export type KnowledgeGraphEdge = { id: string; kind: "contains" | "imports" | "re-exports" | "relates-to" | "rationale-supports"; from: string; to: string; provenance: "extracted" | "inferred"; owner: "file" | "derived"; ownerPath: string; source: { path: string; line: number; column: number }; attributes: Record<string, string> };
|
|
export type KnowledgeGraphNeighbor = { node: KnowledgeGraphNode; distance: number; edges: KnowledgeGraphEdge[] };
|
|
export type KnowledgeGraphNodeQuery = { kinds?: string[]; pathPrefix?: string; idPrefix?: string; namePattern?: string; fnxcArea?: string; symbolKind?: string; owner?: string; limit?: number; offset?: number };
|
|
export type KnowledgeGraphStatus = { available: boolean; recoveryReason?: string | null; graphDir?: string; nodeCount?: number; edgeCount?: number; nodeKindCounts?: Record<string, number>; edgeKindCounts?: Record<string, number>; provenanceCounts?: Record<string, number>; ownerCounts?: Record<string, number>; fnxcAreas?: string[]; pathLimits: { defaultMaxHops: number; maxHops: number; maxExpansions: number } };
|
|
export type KnowledgeGraphPathResult =
|
|
| { outcome: "found"; path: { nodes: KnowledgeGraphNode[]; edges: KnowledgeGraphEdge[] }; hops: number; maxHops: number; expansions: number; truncated: false }
|
|
| { outcome: "not-found"; path: null; maxHops: number; expansions: number; truncated: false }
|
|
| { outcome: "limit-reached"; path: null; maxHops: number; expansions: number; truncated: true; limit: "max-hops" | "max-expansions" };
|
|
|
|
function query(params: Record<string, string | number | string[] | undefined>): string {
|
|
const entries = Object.entries(params).flatMap(([key, value]) => Array.isArray(value) ? value.filter(Boolean).map(item => [key, item] as const) : value === undefined || value === "" ? [] : [[key, String(value)] as const]);
|
|
return entries.length ? `?${entries.map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`).join("&")}` : "";
|
|
}
|
|
export function fetchKnowledgeGraphStatus(projectId?: string): Promise<KnowledgeGraphStatus> { return api(withProjectId("/knowledge/graph/status", projectId)); }
|
|
export function queryKnowledgeGraphNodes(projectId: string | undefined, filters: KnowledgeGraphNodeQuery = {}): Promise<{ nodes: KnowledgeGraphNode[]; total: number; limit: number; offset: number }> { return api(withProjectId(`/knowledge/graph/nodes${query(filters)}`, projectId)); }
|
|
export function fetchKnowledgeGraphNode(projectId: string | undefined, id: string): Promise<{ node: KnowledgeGraphNode; outgoing: KnowledgeGraphEdge[]; incoming: KnowledgeGraphEdge[]; outgoingTotal: number; incomingTotal: number }> { return api(withProjectId(`/knowledge/graph/node${query({ id })}`, projectId)); }
|
|
export function fetchKnowledgeGraphNeighbors(projectId: string | undefined, options: { nodeId: string; direction?: "out" | "in" | "both"; edgeKinds?: string[]; depth?: number; limit?: number }): Promise<{ neighbors: KnowledgeGraphNeighbor[]; total: number }> { return api(withProjectId(`/knowledge/graph/neighbors${query(options)}`, projectId)); }
|
|
export function fetchKnowledgeGraphPath(projectId: string | undefined, options: { fromId: string; toId: string; maxHops?: number }): Promise<KnowledgeGraphPathResult> { return api(withProjectId(`/knowledge/graph/path${query(options)}`, projectId)); }
|
|
export function buildKnowledgeGraphArtifacts(projectId?: string, force = false): Promise<{ changed: boolean; nodes: number; edges: number; stats: Record<string, unknown> }> { return api(withProjectId("/knowledge/graph/build", projectId), { method: "POST", body: JSON.stringify({ force }) }); }
|