feat(web): integrate backend mode into app state and loading flow

This commit is contained in:
Paul Robello 2026-02-22 10:08:20 -08:00
parent 0e37652360
commit 8249fa76ca
2 changed files with 90 additions and 2 deletions

View file

@ -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<GraphCanvasHandle>(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<string, string>();
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 <DropZone onFileSelect={handleFileSelect} onGitClone={handleGitClone} />;
return (
<DropZone
onFileSelect={handleFileSelect}
onGitClone={handleGitClone}
backendRepos={backend.repos}
isBackendConnected={backend.isConnected}
backendUrl={backend.backendUrl}
onSelectBackendRepo={handleSelectBackendRepo}
/>
);
}
if (viewMode === 'loading' && progress) {

View file

@ -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<AppState | null>(null);
@ -291,6 +297,10 @@ export const AppStateProvider = ({ children }: { children: ReactNode }) => {
const [isCodePanelOpen, setCodePanelOpen] = useState(false);
const [codeReferenceFocus, setCodeReferenceFocus] = useState<CodeReferenceFocus | null>(null);
// Backend mode
const [isBackendMode, setIsBackendMode] = useState(false);
const [backendRepo, setBackendRepo] = useState<string | null>(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<any[]> => {
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<boolean> => {
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 (