Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 22 additions & 11 deletions gitnexus-web/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ const AppContent = () => {
availableRepos,
setAvailableRepos,
switchRepo,
hydrateWorkerFromServer,
} = useAppState();

const graphCanvasRef = useRef<GraphCanvasHandle>(null);
Expand Down Expand Up @@ -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);
Expand Down
16 changes: 8 additions & 8 deletions gitnexus-web/src/core/lbug/lbug-adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand All @@ -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 {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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];
Expand Down Expand Up @@ -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];

Expand Down
36 changes: 28 additions & 8 deletions gitnexus-web/src/hooks/useAppState.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand All @@ -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 => {
Expand Down Expand Up @@ -1142,6 +1161,7 @@ export const AppStateProvider = ({ children }: { children: ReactNode }) => {
runPipelineFromFiles,
runQuery,
isDatabaseReady,
hydrateWorkerFromServer,
// Embedding state and methods
embeddingStatus,
embeddingProgress,
Expand Down
46 changes: 46 additions & 0 deletions gitnexus-web/src/workers/ingestion.worker.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -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
Expand Down
Loading