mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-11-11 08:37:35 +00:00
Fix server/bridge mode leaving the web UI with 0 nodes and broken Query/Processes/embeddings by hydrating the worker-side LadybugDB and BM25 indexes after loading graph data from the backend. Also fix LadybugDB QueryResult API mismatch where result.getAll() does not exist in some @ladybugdb/wasm-core versions — falls back to getAllObjects() or getAllRows().
This commit is contained in:
parent
01ff2a7540
commit
11575cf6c8
4 changed files with 104 additions and 27 deletions
|
|
@ -40,6 +40,7 @@ const AppContent = () => {
|
|||
availableRepos,
|
||||
setAvailableRepos,
|
||||
switchRepo,
|
||||
hydrateWorkerFromServer,
|
||||
} = useAppState();
|
||||
|
||||
const graphCanvasRef = useRef<GraphCanvasHandle>(null);
|
||||
|
|
@ -157,21 +158,31 @@ const AppContent = () => {
|
|||
|
||||
// Transition directly to exploring view
|
||||
setViewMode('exploring');
|
||||
setProgress(null);
|
||||
|
||||
// Initialize agent if LLM is configured
|
||||
if (getActiveProviderConfig()) {
|
||||
initializeAgent(projectName);
|
||||
}
|
||||
// Hydrate the worker-side DB (LadybugDB + BM25) so Query/Processes/embeddings work
|
||||
hydrateWorkerFromServer(result.nodes, result.relationships, result.fileContents).then(() => {
|
||||
// Initialize agent if LLM is configured
|
||||
if (getActiveProviderConfig()) {
|
||||
initializeAgent(projectName);
|
||||
}
|
||||
|
||||
// Auto-start embeddings
|
||||
startEmbeddings().catch((err) => {
|
||||
if (err?.name === 'WebGPUNotAvailableError' || err?.message?.includes('WebGPU')) {
|
||||
startEmbeddings('wasm').catch(console.warn);
|
||||
} else {
|
||||
console.warn('Embeddings auto-start failed:', err);
|
||||
// Auto-start embeddings (now that LadybugDB is ready)
|
||||
startEmbeddings().catch((err) => {
|
||||
if (err?.name === 'WebGPUNotAvailableError' || err?.message?.includes('WebGPU')) {
|
||||
startEmbeddings('wasm').catch(console.warn);
|
||||
} else {
|
||||
console.warn('Embeddings auto-start failed:', err);
|
||||
}
|
||||
});
|
||||
}).catch((err) => {
|
||||
console.warn('Worker hydration failed (non-fatal):', err);
|
||||
// Still initialize agent even if hydration fails
|
||||
if (getActiveProviderConfig()) {
|
||||
initializeAgent(projectName);
|
||||
}
|
||||
});
|
||||
}, [setViewMode, setGraph, setFileContents, setProjectName, initializeAgent, startEmbeddings]);
|
||||
}, [setViewMode, setGraph, setFileContents, setProjectName, setProgress, initializeAgent, startEmbeddings, hydrateWorkerFromServer]);
|
||||
|
||||
// Auto-connect when ?server query param is present (bookmarkable shortcut)
|
||||
const autoConnectRan = useRef(false);
|
||||
|
|
|
|||
|
|
@ -190,7 +190,7 @@ export const loadGraphToLbug = async (
|
|||
for (const tableName of NODE_TABLES) {
|
||||
try {
|
||||
const countRes = await conn.query(`MATCH (n:${tableName}) RETURN count(n) AS cnt`);
|
||||
const countRows = await countRes.getAll();
|
||||
const countRows = await (countRes.getAll?.() ?? countRes.getAllObjects?.() ?? countRes.getAllRows?.() ?? []);
|
||||
const countRow = countRows[0];
|
||||
const count = countRow ? (countRow.cnt ?? countRow[0] ?? 0) : 0;
|
||||
totalNodes += Number(count);
|
||||
|
|
@ -293,8 +293,8 @@ export const executeQuery = async (cypher: string): Promise<any[]> => {
|
|||
});
|
||||
}
|
||||
|
||||
// Collect all rows
|
||||
const allRows = await result.getAll();
|
||||
// Collect all rows (handle API differences across LadybugDB versions)
|
||||
const allRows = await (result.getAll?.() ?? result.getAllObjects?.() ?? result.getAllRows?.() ?? []);
|
||||
const rows: any[] = [];
|
||||
for (const row of allRows) {
|
||||
// Convert tuple to named object if we have column names and row is array
|
||||
|
|
@ -331,7 +331,7 @@ export const getLbugStats = async (): Promise<{ nodes: number; edges: number }>
|
|||
for (const tableName of NODE_TABLES) {
|
||||
try {
|
||||
const nodeResult = await conn.query(`MATCH (n:${tableName}) RETURN count(n) AS cnt`);
|
||||
const nodeRows = await nodeResult.getAll();
|
||||
const nodeRows = await (nodeResult.getAll?.() ?? nodeResult.getAllObjects?.() ?? nodeResult.getAllRows?.() ?? []);
|
||||
const nodeRow = nodeRows[0];
|
||||
totalNodes += Number(nodeRow?.cnt ?? nodeRow?.[0] ?? 0);
|
||||
} catch {
|
||||
|
|
@ -343,7 +343,7 @@ export const getLbugStats = async (): Promise<{ nodes: number; edges: number }>
|
|||
let totalEdges = 0;
|
||||
try {
|
||||
const edgeResult = await conn.query(`MATCH ()-[r:${REL_TABLE_NAME}]->() RETURN count(r) AS cnt`);
|
||||
const edgeRows = await edgeResult.getAll();
|
||||
const edgeRows = await (edgeResult.getAll?.() ?? edgeResult.getAllObjects?.() ?? edgeResult.getAllRows?.() ?? []);
|
||||
const edgeRow = edgeRows[0];
|
||||
totalEdges = Number(edgeRow?.cnt ?? edgeRow?.[0] ?? 0);
|
||||
} catch {
|
||||
|
|
@ -408,7 +408,7 @@ export const executePrepared = async (
|
|||
|
||||
const result = await conn.execute(stmt, params);
|
||||
|
||||
const rows = await result.getAll();
|
||||
const rows = await (result.getAll?.() ?? result.getAllObjects?.() ?? result.getAllRows?.() ?? []);
|
||||
|
||||
await stmt.close();
|
||||
return rows;
|
||||
|
|
@ -472,7 +472,7 @@ export const testArrayParams = async (): Promise<{ success: boolean; error?: str
|
|||
for (const tableName of NODE_TABLES) {
|
||||
try {
|
||||
const nodeResult = await conn.query(`MATCH (n:${tableName}) RETURN n.id AS id LIMIT 1`);
|
||||
const nodeRows = await nodeResult.getAll();
|
||||
const nodeRows = await (nodeResult.getAll?.() ?? nodeResult.getAllObjects?.() ?? nodeResult.getAllRows?.() ?? []);
|
||||
const nodeRow = nodeRows[0];
|
||||
if (nodeRow) {
|
||||
testNodeId = nodeRow.id ?? nodeRow[0];
|
||||
|
|
@ -509,7 +509,7 @@ export const testArrayParams = async (): Promise<{ success: boolean; error?: str
|
|||
const verifyResult = await conn.query(
|
||||
`MATCH (e:${EMBEDDING_TABLE_NAME} {nodeId: '${testNodeId}'}) RETURN e.embedding AS emb`
|
||||
);
|
||||
const verifyRows = await verifyResult.getAll();
|
||||
const verifyRows = await (verifyResult.getAll?.() ?? verifyResult.getAllObjects?.() ?? verifyResult.getAllRows?.() ?? []);
|
||||
const verifyRow = verifyRows[0];
|
||||
const storedEmb = verifyRow?.emb ?? verifyRow?.[0];
|
||||
|
||||
|
|
|
|||
|
|
@ -125,6 +125,7 @@ interface AppState {
|
|||
runPipelineFromFiles: (files: FileEntry[], onProgress: (p: PipelineProgress) => void, clusteringConfig?: ProviderConfig) => Promise<PipelineResult>;
|
||||
runQuery: (cypher: string) => Promise<any[]>;
|
||||
isDatabaseReady: () => Promise<boolean>;
|
||||
hydrateWorkerFromServer: (nodes: any[], relationships: any[], fileContents: Record<string, string>) => Promise<void>;
|
||||
|
||||
// Embedding state
|
||||
embeddingStatus: EmbeddingStatus;
|
||||
|
|
@ -482,6 +483,16 @@ export const AppStateProvider = ({ children }: { children: ReactNode }) => {
|
|||
}
|
||||
}, []);
|
||||
|
||||
const hydrateWorkerFromServer = useCallback(async (
|
||||
nodes: any[],
|
||||
relationships: any[],
|
||||
fileContents: Record<string, string>
|
||||
): Promise<void> => {
|
||||
const api = apiRef.current;
|
||||
if (!api) throw new Error('Worker not initialized');
|
||||
await api.hydrateFromServerData(nodes, relationships, fileContents);
|
||||
}, []);
|
||||
|
||||
// Embedding methods
|
||||
const startEmbeddings = useCallback(async (forceDevice?: 'webgpu' | 'wasm'): Promise<void> => {
|
||||
const api = apiRef.current;
|
||||
|
|
@ -1018,15 +1029,23 @@ export const AppStateProvider = ({ children }: { children: ReactNode }) => {
|
|||
setFileContents(fileMap);
|
||||
|
||||
setViewMode('exploring');
|
||||
setProgress(null);
|
||||
|
||||
if (getActiveProviderConfig()) initializeAgent(pName);
|
||||
// Hydrate the worker-side DB (LadybugDB + BM25) so Query/Processes/embeddings work
|
||||
hydrateWorkerFromServer(result.nodes, result.relationships, result.fileContents).then(() => {
|
||||
if (getActiveProviderConfig()) initializeAgent(pName);
|
||||
|
||||
startEmbeddings().catch((err) => {
|
||||
if (err?.name === 'WebGPUNotAvailableError' || err?.message?.includes('WebGPU')) {
|
||||
startEmbeddings('wasm').catch(console.warn);
|
||||
} else {
|
||||
console.warn('Embeddings auto-start failed:', err);
|
||||
}
|
||||
startEmbeddings().catch((err) => {
|
||||
if (err?.name === 'WebGPUNotAvailableError' || err?.message?.includes('WebGPU')) {
|
||||
startEmbeddings('wasm').catch(console.warn);
|
||||
} else {
|
||||
console.warn('Embeddings auto-start failed:', err);
|
||||
}
|
||||
});
|
||||
}).catch((err) => {
|
||||
console.warn('Worker hydration failed (non-fatal):', err);
|
||||
// Still initialize agent even if hydration fails
|
||||
if (getActiveProviderConfig()) initializeAgent(pName);
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('Repo switch failed:', err);
|
||||
|
|
@ -1037,7 +1056,7 @@ export const AppStateProvider = ({ children }: { children: ReactNode }) => {
|
|||
});
|
||||
setTimeout(() => { setViewMode('exploring'); setProgress(null); }, 3000);
|
||||
}
|
||||
}, [serverBaseUrl, setProgress, setViewMode, setProjectName, setGraph, setFileContents, initializeAgent, startEmbeddings, setHighlightedNodeIds, clearAIToolHighlights, clearBlastRadius, setSelectedNode, setQueryResult, setCodeReferences, setCodePanelOpen, setCodeReferenceFocus]);
|
||||
}, [serverBaseUrl, setProgress, setViewMode, setProjectName, setGraph, setFileContents, initializeAgent, startEmbeddings, hydrateWorkerFromServer, setHighlightedNodeIds, clearAIToolHighlights, clearBlastRadius, setSelectedNode, setQueryResult, setCodeReferences, setCodePanelOpen, setCodeReferenceFocus]);
|
||||
|
||||
const removeCodeReference = useCallback((id: string) => {
|
||||
setCodeReferences(prev => {
|
||||
|
|
@ -1142,6 +1161,7 @@ export const AppStateProvider = ({ children }: { children: ReactNode }) => {
|
|||
runPipelineFromFiles,
|
||||
runQuery,
|
||||
isDatabaseReady,
|
||||
hydrateWorkerFromServer,
|
||||
// Embedding state and methods
|
||||
embeddingStatus,
|
||||
embeddingProgress,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
import * as Comlink from 'comlink';
|
||||
import { runIngestionPipeline, runPipelineFromFiles } from '../core/ingestion/pipeline';
|
||||
import { createKnowledgeGraph } from '../core/graph/graph';
|
||||
import type { GraphNode, GraphRelationship } from '../core/graph/types';
|
||||
import { PipelineProgress, SerializablePipelineResult, serializePipelineResult } from '../types/pipeline';
|
||||
import { FileEntry } from '../services/zip';
|
||||
import {
|
||||
|
|
@ -207,6 +209,50 @@ const workerApi = {
|
|||
return serializePipelineResult(result);
|
||||
},
|
||||
|
||||
/**
|
||||
* Hydrate the worker-side database and indexes from server-loaded data.
|
||||
* This is the missing step when using server/bridge mode — the main thread
|
||||
* builds the React graph, but the worker's LadybugDB + BM25 stay empty.
|
||||
*/
|
||||
async hydrateFromServerData(
|
||||
nodes: GraphNode[],
|
||||
relationships: GraphRelationship[],
|
||||
fileContents: Record<string, string>
|
||||
): Promise<void> {
|
||||
// 1. Build a KnowledgeGraph the same way the pipeline does
|
||||
const graph = createKnowledgeGraph();
|
||||
for (const node of nodes) graph.addNode(node);
|
||||
for (const rel of relationships) graph.addRelationship(rel);
|
||||
|
||||
// 2. Store file contents for grep/read tools
|
||||
storedFileContents = new Map(Object.entries(fileContents));
|
||||
|
||||
// 3. Build BM25 keyword index
|
||||
const bm25DocCount = buildBM25Index(storedFileContents);
|
||||
if (import.meta.env.DEV) {
|
||||
console.log(`🔍 BM25 index built (server mode): ${bm25DocCount} documents`);
|
||||
}
|
||||
|
||||
// 4. Set currentGraphResult so the agent context builder works
|
||||
currentGraphResult = { graph, fileContents: storedFileContents };
|
||||
|
||||
// 5. Load graph into LadybugDB for Cypher queries (optional — gracefully degrades)
|
||||
try {
|
||||
const lbug = await getLbugAdapter();
|
||||
await lbug.loadGraphToLbug(graph, storedFileContents);
|
||||
|
||||
if (import.meta.env.DEV) {
|
||||
const stats = await lbug.getLbugStats();
|
||||
console.log('✅ LadybugDB hydrated (server mode):', stats);
|
||||
}
|
||||
} catch (err) {
|
||||
// LadybugDB is optional — silently continue without it
|
||||
if (import.meta.env.DEV) {
|
||||
console.warn('⚠️ LadybugDB hydration failed (non-fatal):', err);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Execute a Cypher query against the LadybugDB database
|
||||
* @param cypher - The Cypher query string
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue