From 7345d83376710eed6a3a303f0c4507d480cbf7a0 Mon Sep 17 00:00:00 2001 From: Lum1104 Date: Sat, 14 Mar 2026 17:58:01 +0800 Subject: [PATCH] feat(dashboard): add search bar, node info, and main 4-panel layout Co-Authored-By: Claude Opus 4.6 --- .../dashboard/public/knowledge-graph.json | 27 ++++ packages/dashboard/src/App.tsx | 67 +++++++++- .../dashboard/src/components/NodeInfo.tsx | 126 ++++++++++++++++++ .../dashboard/src/components/SearchBar.tsx | 37 +++++ 4 files changed, 255 insertions(+), 2 deletions(-) create mode 100644 packages/dashboard/public/knowledge-graph.json create mode 100644 packages/dashboard/src/components/NodeInfo.tsx create mode 100644 packages/dashboard/src/components/SearchBar.tsx diff --git a/packages/dashboard/public/knowledge-graph.json b/packages/dashboard/public/knowledge-graph.json new file mode 100644 index 0000000..6587753 --- /dev/null +++ b/packages/dashboard/public/knowledge-graph.json @@ -0,0 +1,27 @@ +{ + "version": "1.0.0", + "project": { + "name": "sample-project", + "languages": ["typescript"], + "frameworks": ["express"], + "description": "A sample Express API", + "analyzedAt": "2026-03-14T00:00:00.000Z", + "gitCommitHash": "abc123" + }, + "nodes": [ + {"id": "file:src/index.ts", "type": "file", "name": "index.ts", "filePath": "src/index.ts", "summary": "Application entry point, starts the Express server", "tags": ["entry", "server"], "complexity": "simple"}, + {"id": "file:src/auth/login.ts", "type": "file", "name": "login.ts", "filePath": "src/auth/login.ts", "summary": "Handles user authentication and login flow", "tags": ["auth", "login"], "complexity": "moderate"}, + {"id": "func:src/auth/login.ts:handleLogin", "type": "function", "name": "handleLogin", "filePath": "src/auth/login.ts", "lineRange": [10, 35], "summary": "Validates credentials and returns a JWT token", "tags": ["auth", "jwt"], "complexity": "moderate"}, + {"id": "func:src/auth/login.ts:validateEmail", "type": "function", "name": "validateEmail", "filePath": "src/auth/login.ts", "lineRange": [37, 42], "summary": "Checks if an email address is valid using regex", "tags": ["validation", "email"], "complexity": "simple"}, + {"id": "file:src/db/connection.ts", "type": "file", "name": "connection.ts", "filePath": "src/db/connection.ts", "summary": "Database connection pool using PostgreSQL", "tags": ["database", "postgres"], "complexity": "moderate"} + ], + "edges": [ + {"source": "file:src/index.ts", "target": "file:src/auth/login.ts", "type": "imports", "direction": "forward", "weight": 0.7}, + {"source": "file:src/auth/login.ts", "target": "func:src/auth/login.ts:handleLogin", "type": "contains", "direction": "forward", "weight": 1.0}, + {"source": "file:src/auth/login.ts", "target": "func:src/auth/login.ts:validateEmail", "type": "contains", "direction": "forward", "weight": 1.0}, + {"source": "func:src/auth/login.ts:handleLogin", "target": "func:src/auth/login.ts:validateEmail", "type": "calls", "direction": "forward", "description": "handleLogin calls validateEmail to check the email format", "weight": 0.8}, + {"source": "file:src/auth/login.ts", "target": "file:src/db/connection.ts", "type": "imports", "direction": "forward", "weight": 0.7} + ], + "layers": [], + "tour": [] +} diff --git a/packages/dashboard/src/App.tsx b/packages/dashboard/src/App.tsx index b7cb4e3..61a3c20 100644 --- a/packages/dashboard/src/App.tsx +++ b/packages/dashboard/src/App.tsx @@ -1,7 +1,70 @@ +import { useEffect } from "react"; +import type { KnowledgeGraph } from "@understand-anything/core"; +import { useDashboardStore } from "./store"; +import GraphView from "./components/GraphView"; +import CodeViewer from "./components/CodeViewer"; +import SearchBar from "./components/SearchBar"; +import NodeInfo from "./components/NodeInfo"; + function App() { + const graph = useDashboardStore((s) => s.graph); + const setGraph = useDashboardStore((s) => s.setGraph); + + useEffect(() => { + fetch("/knowledge-graph.json") + .then((res) => res.json()) + .then((data: KnowledgeGraph) => setGraph(data)) + .catch((err) => console.error("Failed to load knowledge graph:", err)); + }, [setGraph]); + + const nodeCount = graph?.nodes.length ?? 0; + const edgeCount = graph?.edges.length ?? 0; + return ( -
-

Understand Anything

+
+ {/* Header */} +
+

+ UNDERSTAND ANYTHING +

+
+ {graph && ( + <> + {graph.project.name} + | + {nodeCount} nodes + | + {edgeCount} edges + + )} +
+
+ + {/* Search Bar */} + + + {/* 4-panel grid */} +
+ {/* Top-left: Graph View */} +
+ +
+ + {/* Top-right: Code Viewer */} +
+ +
+ + {/* Bottom-left: Chat placeholder */} +
+

Chat panel (coming soon)

+
+ + {/* Bottom-right: Node Info */} +
+ +
+
); } diff --git a/packages/dashboard/src/components/NodeInfo.tsx b/packages/dashboard/src/components/NodeInfo.tsx new file mode 100644 index 0000000..3b6b80c --- /dev/null +++ b/packages/dashboard/src/components/NodeInfo.tsx @@ -0,0 +1,126 @@ +import { useDashboardStore } from "../store"; + +const typeBadgeColors: Record = { + file: "bg-blue-800 text-blue-200", + function: "bg-green-800 text-green-200", + class: "bg-purple-800 text-purple-200", + module: "bg-orange-800 text-orange-200", + concept: "bg-pink-800 text-pink-200", +}; + +const complexityBadgeColors: Record = { + simple: "bg-green-700 text-green-200", + moderate: "bg-yellow-700 text-yellow-200", + complex: "bg-red-700 text-red-200", +}; + +export default function NodeInfo() { + const graph = useDashboardStore((s) => s.graph); + const selectedNodeId = useDashboardStore((s) => s.selectedNodeId); + + const node = graph?.nodes.find((n) => n.id === selectedNodeId) ?? null; + + if (!node) { + return ( +
+

Select a node to see details

+
+ ); + } + + const connections = (graph?.edges ?? []).filter( + (e) => e.source === node.id || e.target === node.id, + ); + + const typeBadge = typeBadgeColors[node.type] ?? typeBadgeColors.file; + const complexityBadge = + complexityBadgeColors[node.complexity] ?? complexityBadgeColors.simple; + + return ( +
+
+ + {node.type} + + + {node.complexity} + +
+ +

{node.name}

+ +

+ {node.summary} +

+ + {node.filePath && ( +
+ File:{" "} + {node.filePath} + {node.lineRange && ( + + (L{node.lineRange[0]}-{node.lineRange[1]}) + + )} +
+ )} + + {node.languageNotes && ( +
+ {node.languageNotes} +
+ )} + + {node.tags.length > 0 && ( +
+

+ Tags +

+
+ {node.tags.map((tag) => ( + + {tag} + + ))} +
+
+ )} + + {connections.length > 0 && ( +
+

+ Connections ({connections.length}) +

+
+ {connections.map((edge, i) => { + const isSource = edge.source === node.id; + const otherId = isSource ? edge.target : edge.source; + const otherNode = graph?.nodes.find((n) => n.id === otherId); + const arrow = isSource ? "\u2192" : "\u2190"; + + return ( +
+ {arrow} + {edge.type} + + {otherNode?.name ?? otherId} + +
+ ); + })} +
+
+ )} +
+ ); +} diff --git a/packages/dashboard/src/components/SearchBar.tsx b/packages/dashboard/src/components/SearchBar.tsx new file mode 100644 index 0000000..362b520 --- /dev/null +++ b/packages/dashboard/src/components/SearchBar.tsx @@ -0,0 +1,37 @@ +import { useDashboardStore } from "../store"; + +export default function SearchBar() { + const searchQuery = useDashboardStore((s) => s.searchQuery); + const searchResults = useDashboardStore((s) => s.searchResults); + const setSearchQuery = useDashboardStore((s) => s.setSearchQuery); + + return ( +
+ + + + setSearchQuery(e.target.value)} + placeholder="Search nodes by name, summary, or tags..." + className="flex-1 bg-gray-700 text-white text-sm rounded px-3 py-1.5 border border-gray-600 focus:outline-none focus:border-blue-500 placeholder-gray-400" + /> + {searchQuery.trim() && ( + + {searchResults.length} result{searchResults.length !== 1 ? "s" : ""} + + )} +
+ ); +}