From 5628c2ed82e0d89e4285f574b21aad24c14a23be Mon Sep 17 00:00:00 2001 From: Lum1104 Date: Sat, 14 Mar 2026 21:11:52 +0800 Subject: [PATCH] feat(dashboard): add persona mode system with adaptive layouts Three persona modes change the dashboard experience: - Overview (non-technical): 2-column layout with graph + learn/chat, hides CodeViewer, filters graph to concept/module/file nodes only - Learn (junior): full 4-panel layout with tabbed Details/Tour panel - Deep Dive (experienced): full 4-panel layout with NodeInfo, tour tabs only shown when tour is explicitly active Co-Authored-By: Claude Opus 4.6 --- packages/dashboard/src/App.tsx | 164 ++++++++++++------ .../dashboard/src/components/GraphView.tsx | 24 ++- .../src/components/PersonaSelector.tsx | 44 +++++ packages/dashboard/src/store.ts | 9 + 4 files changed, 182 insertions(+), 59 deletions(-) create mode 100644 packages/dashboard/src/components/PersonaSelector.tsx diff --git a/packages/dashboard/src/App.tsx b/packages/dashboard/src/App.tsx index 75fdbb1..d169045 100644 --- a/packages/dashboard/src/App.tsx +++ b/packages/dashboard/src/App.tsx @@ -8,6 +8,7 @@ import NodeInfo from "./components/NodeInfo"; import ChatPanel from "./components/ChatPanel"; import LayerLegend from "./components/LayerLegend"; import LearnPanel from "./components/LearnPanel"; +import PersonaSelector from "./components/PersonaSelector"; function App() { const graph = useDashboardStore((s) => s.graph); @@ -15,6 +16,7 @@ function App() { const tourActive = useDashboardStore((s) => s.tourActive); const startTour = useDashboardStore((s) => s.startTour); const stopTour = useDashboardStore((s) => s.stopTour); + const persona = useDashboardStore((s) => s.persona); useEffect(() => { fetch("/knowledge-graph.json") @@ -31,9 +33,12 @@ function App() {
{/* Header */}
-

- UNDERSTAND ANYTHING -

+
+

+ UNDERSTAND ANYTHING +

+ +
{graph && ( <> @@ -52,64 +57,111 @@ function App() { {/* Search Bar */} - {/* 4-panel grid */} -
- {/* Top-left: Graph View */} -
- + {/* Persona-adaptive layout */} + {persona === "non-technical" ? ( + /* Non-technical: 2-column layout — graph + stacked learn/chat (no CodeViewer) */ +
+
+ +
+
+
+ +
+
+ +
+
+ ) : ( + /* Junior + Experienced: 4-panel grid */ +
+ {/* Top-left: Graph View */} +
+ +
- {/* Top-right: Code Viewer */} -
- -
+ {/* Top-right: Code Viewer */} +
+ +
- {/* Bottom-left: Chat */} -
- -
+ {/* Bottom-left: Chat */} +
+ +
- {/* Bottom-right: Node Info / Learn Panel */} -
- {hasTour ? ( - <> - {/* Tab bar */} -
- - -
- {/* Active panel */} -
- {tourActive ? : } -
- - ) : ( - - )} + {/* Bottom-right: persona-dependent */} +
+ {persona === "junior" ? ( + /* Junior: always show tabbed Details/Tour panel */ + hasTour ? ( + <> +
+ + +
+
+ {tourActive ? : } +
+ + ) : ( + + ) + ) : ( + /* Experienced: show tabbed panel only when tour is explicitly active */ + tourActive && hasTour ? ( + <> +
+ + +
+
+ +
+ + ) : ( + + ) + )} +
-
+ )}
); } diff --git a/packages/dashboard/src/components/GraphView.tsx b/packages/dashboard/src/components/GraphView.tsx index 8b58440..7583dbc 100644 --- a/packages/dashboard/src/components/GraphView.tsx +++ b/packages/dashboard/src/components/GraphView.tsx @@ -27,6 +27,7 @@ export default function GraphView() { const selectNode = useDashboardStore((s) => s.selectNode); const showLayers = useDashboardStore((s) => s.showLayers); const tourHighlightedNodeIds = useDashboardStore((s) => s.tourHighlightedNodeIds); + const persona = useDashboardStore((s) => s.persona); const { initialNodes, initialEdges } = useMemo(() => { if (!graph) @@ -35,7 +36,24 @@ export default function GraphView() { initialEdges: [] as Edge[], }; - const flowNodes: CustomFlowNode[] = graph.nodes.map((node) => { + // Filter nodes and edges based on persona + const filteredGraphNodes = + persona === "non-technical" + ? graph.nodes.filter( + (n) => + n.type === "concept" || n.type === "module" || n.type === "file", + ) + : graph.nodes; + + const filteredNodeIds = new Set(filteredGraphNodes.map((n) => n.id)); + const filteredGraphEdges = + persona === "non-technical" + ? graph.edges.filter( + (e) => filteredNodeIds.has(e.source) && filteredNodeIds.has(e.target), + ) + : graph.edges; + + const flowNodes: CustomFlowNode[] = filteredGraphNodes.map((node) => { const matchResult = searchResults.find((r) => r.nodeId === node.id); return { id: node.id, @@ -54,7 +72,7 @@ export default function GraphView() { }; }); - const flowEdges: Edge[] = graph.edges.map((edge, i) => ({ + const flowEdges: Edge[] = filteredGraphEdges.map((edge, i) => ({ id: `e-${i}`, source: edge.source, target: edge.target, @@ -164,7 +182,7 @@ export default function GraphView() { ]; return { initialNodes: allNodes, initialEdges: laid.edges }; - }, [graph, searchResults, selectedNodeId, showLayers, tourHighlightedNodeIds]); + }, [graph, searchResults, selectedNodeId, showLayers, tourHighlightedNodeIds, persona]); const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes); const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges); diff --git a/packages/dashboard/src/components/PersonaSelector.tsx b/packages/dashboard/src/components/PersonaSelector.tsx new file mode 100644 index 0000000..ec804cd --- /dev/null +++ b/packages/dashboard/src/components/PersonaSelector.tsx @@ -0,0 +1,44 @@ +import { useDashboardStore } from "../store"; +import type { Persona } from "../store"; + +const personas: { id: Persona; label: string; description: string }[] = [ + { + id: "non-technical", + label: "Overview", + description: "High-level architecture view", + }, + { + id: "junior", + label: "Learn", + description: "Full dashboard with guided learning", + }, + { + id: "experienced", + label: "Deep Dive", + description: "Code-focused with chat", + }, +]; + +export default function PersonaSelector() { + const persona = useDashboardStore((s) => s.persona); + const setPersona = useDashboardStore((s) => s.setPersona); + + return ( +
+ {personas.map((p) => ( + + ))} +
+ ); +} diff --git a/packages/dashboard/src/store.ts b/packages/dashboard/src/store.ts index fc6b167..65a8d54 100644 --- a/packages/dashboard/src/store.ts +++ b/packages/dashboard/src/store.ts @@ -7,6 +7,8 @@ import type { TourStep, } from "@understand-anything/core/types"; +export type Persona = "non-technical" | "junior" | "experienced"; + export interface ChatMessage { role: "user" | "assistant"; content: string; @@ -33,6 +35,8 @@ interface DashboardStore { currentTourStep: number; tourHighlightedNodeIds: string[]; + persona: Persona; + setGraph: (graph: KnowledgeGraph) => void; selectNode: (nodeId: string | null) => void; setSearchQuery: (query: string) => void; @@ -41,6 +45,7 @@ interface DashboardStore { clearChat: () => void; toggleLayers: () => void; explainNode: (nodeId: string) => Promise; + setPersona: (persona: Persona) => void; startTour: () => void; stopTour: () => void; @@ -145,6 +150,8 @@ export const useDashboardStore = create()((set, get) => ({ currentTourStep: 0, tourHighlightedNodeIds: [], + persona: "junior", + setGraph: (graph) => { const searchEngine = new SearchEngine(graph.nodes); const query = get().searchQuery; @@ -225,6 +232,8 @@ export const useDashboardStore = create()((set, get) => ({ toggleLayers: () => set((state) => ({ showLayers: !state.showLayers })), + setPersona: (persona) => set({ persona }), + explainNode: async (nodeId) => { const { apiKey, graph, nodeExplanationCache } = get(); if (!apiKey || !graph) return;