From 8249fa76ca764486734652a325caa3694be0d8f6 Mon Sep 17 00:00:00 2001 From: Paul Robello Date: Sun, 22 Feb 2026 10:08:20 -0800 Subject: [PATCH] feat(web): integrate backend mode into app state and loading flow --- gitnexus-web/src/App.tsx | 71 +++++++++++++++++++++++++- gitnexus-web/src/hooks/useAppState.tsx | 21 +++++++- 2 files changed, 90 insertions(+), 2 deletions(-) diff --git a/gitnexus-web/src/App.tsx b/gitnexus-web/src/App.tsx index d609be146..2cf655866 100644 --- a/gitnexus-web/src/App.tsx +++ b/gitnexus-web/src/App.tsx @@ -11,6 +11,9 @@ import { FileTreePanel } from './components/FileTreePanel'; import { CodeReferencesPanel } from './components/CodeReferencesPanel'; import { FileEntry } from './services/zip'; import { getActiveProviderConfig } from './core/llm/settings-service'; +import { useBackend } from './hooks/useBackend'; +import { fetchGraph } from './services/backend'; +import { createKnowledgeGraph } from './core/graph/graph'; const AppContent = () => { const { @@ -33,8 +36,12 @@ const AppContent = () => { codeReferences, selectedNode, isCodePanelOpen, + setBackendMode, + setBackendRepo, } = useAppState(); + const backend = useBackend(); + const graphCanvasRef = useRef(null); const handleFileSelect = useCallback(async (file: File) => { @@ -129,6 +136,59 @@ const AppContent = () => { graphCanvasRef.current?.focusNode(nodeId); }, []); + const handleSelectBackendRepo = useCallback(async (repoName: string) => { + setViewMode('loading'); + setProjectName(repoName); + setProgress({ phase: 'extracting', percent: 50, message: 'Loading from server...', detail: 'Fetching graph data' }); + + try { + const graphData = await fetchGraph(repoName); + + // Build KnowledgeGraph from server data + const graph = createKnowledgeGraph(); + for (const node of graphData.nodes) { + graph.addNode(node as any); + } + for (const rel of graphData.relationships) { + graph.addRelationship(rel as any); + } + setGraph(graph); + + // Extract file contents from File nodes (content is in node properties) + const contents = new Map(); + for (const node of graphData.nodes) { + const n = node as any; + if (n.label === 'File' && n.properties?.content && n.properties?.filePath) { + contents.set(n.properties.filePath, n.properties.content); + } + } + setFileContents(contents); + + // Enter backend mode + setBackendMode(true); + setBackendRepo(repoName); + backend.selectRepo(repoName); + setViewMode('exploring'); + + // Initialize agent if LLM configured + if (getActiveProviderConfig()) { + initializeAgent(repoName); + } + } catch (error) { + console.error('Backend load error:', error); + setProgress({ + phase: 'error', + percent: 0, + message: 'Error loading from server', + detail: error instanceof Error ? error.message : 'Unknown error', + }); + setTimeout(() => { + setViewMode('onboarding'); + setProgress(null); + }, 3000); + } + }, [setViewMode, setGraph, setFileContents, setProgress, setProjectName, setBackendMode, setBackendRepo, backend, initializeAgent]); + // Handle settings saved - refresh and reinitialize agent // NOTE: Must be defined BEFORE any conditional returns (React hooks rule) const handleSettingsSaved = useCallback(() => { @@ -138,7 +198,16 @@ const AppContent = () => { // Render based on view mode if (viewMode === 'onboarding') { - return ; + return ( + + ); } if (viewMode === 'loading' && progress) { diff --git a/gitnexus-web/src/hooks/useAppState.tsx b/gitnexus-web/src/hooks/useAppState.tsx index 0242ccd92..5bbccca5a 100644 --- a/gitnexus-web/src/hooks/useAppState.tsx +++ b/gitnexus-web/src/hooks/useAppState.tsx @@ -160,6 +160,12 @@ interface AppState { clearAICodeReferences: () => void; clearCodeReferences: () => void; codeReferenceFocus: CodeReferenceFocus | null; + + // Backend mode + isBackendMode: boolean; + backendRepo: string | null; + setBackendMode: (mode: boolean) => void; + setBackendRepo: (repo: string | null) => void; } const AppStateContext = createContext(null); @@ -291,6 +297,10 @@ export const AppStateProvider = ({ children }: { children: ReactNode }) => { const [isCodePanelOpen, setCodePanelOpen] = useState(false); const [codeReferenceFocus, setCodeReferenceFocus] = useState(null); + // Backend mode + const [isBackendMode, setIsBackendMode] = useState(false); + const [backendRepo, setBackendRepo] = useState(null); + const normalizePath = useCallback((p: string) => { return p.replace(/\\/g, '/').replace(/^\.?\//, ''); }, []); @@ -454,10 +464,14 @@ export const AppStateProvider = ({ children }: { children: ReactNode }) => { }, []); const runQuery = useCallback(async (cypher: string): Promise => { + if (isBackendMode && backendRepo) { + const { runCypherQuery } = await import('../services/backend'); + return runCypherQuery(backendRepo, cypher); + } const api = apiRef.current; if (!api) throw new Error('Worker not initialized'); return api.runQuery(cypher); - }, []); + }, [isBackendMode, backendRepo]); const isDatabaseReady = useCallback(async (): Promise => { const api = apiRef.current; @@ -1092,6 +1106,11 @@ export const AppStateProvider = ({ children }: { children: ReactNode }) => { clearAICodeReferences, clearCodeReferences, codeReferenceFocus, + // Backend mode + isBackendMode, + backendRepo, + setBackendMode: setIsBackendMode, + setBackendRepo, }; return (