Skip to main content
Glama
backgroundEmbeddingManager.ts8.41 kB
/** * @fileOverview: Background embedding generation manager * @module: BackgroundEmbeddingManager * @keyFunctions: * - triggerEmbeddingGeneration(): Start non-blocking embedding generation * - getGenerationStatus(): Check if embeddings are being generated * - isGenerating(): Quick check if generation is in progress * @context: Manages background embedding generation to avoid blocking tool execution */ import { logger } from '../utils/logger'; import { LocalEmbeddingGenerator } from './embeddingGenerator'; import { LocalEmbeddingStorage } from './embeddingStorage'; import { ProjectIdentifier } from './projectIdentifier'; export interface GenerationStatus { projectId: string; projectPath: string; isGenerating: boolean; startedAt?: Date; progress?: { totalFiles: number; processedFiles: number; totalChunks: number; embeddings: number; }; error?: string; completedAt?: Date; } /** * Singleton manager for background embedding generation */ export class BackgroundEmbeddingManager { private static instance: BackgroundEmbeddingManager; private activeGenerations: Map<string, GenerationStatus> = new Map(); private embeddingGenerator: LocalEmbeddingGenerator; private embeddingStorage: LocalEmbeddingStorage; private constructor() { this.embeddingGenerator = new LocalEmbeddingGenerator(); this.embeddingStorage = new LocalEmbeddingStorage(); } static getInstance(): BackgroundEmbeddingManager { if (!BackgroundEmbeddingManager.instance) { BackgroundEmbeddingManager.instance = new BackgroundEmbeddingManager(); } return BackgroundEmbeddingManager.instance; } /** * Trigger background embedding generation for a project (non-blocking) * Returns immediately after starting the generation */ async triggerEmbeddingGeneration( projectPath: string, options?: { force?: boolean; batchSize?: number; rateLimit?: number; } ): Promise<{ started: boolean; reason: string; projectId: string }> { try { // Check if local embeddings are enabled if (!LocalEmbeddingStorage.isEnabled()) { return { started: false, reason: 'Local embeddings not enabled (set USE_LOCAL_EMBEDDINGS=true)', projectId: '', }; } // Identify the project const projectIdentifier = ProjectIdentifier.getInstance(); const projectInfo = await projectIdentifier.identifyProject(projectPath); if (!projectInfo) { return { started: false, reason: 'Could not identify project', projectId: '', }; } const projectId = projectInfo.id; // Check if already generating if (this.isGenerating(projectId)) { const status = this.activeGenerations.get(projectId)!; return { started: false, reason: `Already generating embeddings (started ${Math.round((Date.now() - status.startedAt!.getTime()) / 1000)}s ago)`, projectId, }; } // Check if embeddings already exist (unless force is true) if (!options?.force) { const stats = await this.embeddingStorage.getProjectStats(projectId); if (stats && stats.totalChunks > 0) { return { started: false, reason: `Embeddings already exist (${stats.totalChunks} chunks from ${stats.totalFiles} files)`, projectId, }; } } // Start generation in background this.startBackgroundGeneration(projectId, projectPath, options); return { started: true, reason: 'Background embedding generation started', projectId, }; } catch (error) { const errorMsg = error instanceof Error ? error.message : String(error); logger.error('❌ Failed to trigger background embedding generation', { projectPath, error: errorMsg, }); return { started: false, reason: `Failed to start: ${errorMsg}`, projectId: '', }; } } /** * Start background generation (fire and forget) */ private startBackgroundGeneration( projectId: string, projectPath: string, options?: { batchSize?: number; rateLimit?: number; } ): void { // Mark as generating this.activeGenerations.set(projectId, { projectId, projectPath, isGenerating: true, startedAt: new Date(), }); logger.info('🚀 Starting background embedding generation', { projectId, projectPath, }); // Run generation in background (don't await) this.embeddingGenerator .generateProjectEmbeddings(projectId, projectPath, { batchSize: options?.batchSize || 10, rateLimit: options?.rateLimit || 1000, maxChunkSize: 1500, filePatterns: [ '**/*.{ts,tsx,js,jsx,py,go,rs,java,cpp,c,h,hpp,cs,rb,php,swift,kt,scala,clj,hs,ml,r,sql,sh,bash,zsh,md}', ], }) .then(progress => { // Update status with completion this.activeGenerations.set(projectId, { projectId, projectPath, isGenerating: false, startedAt: this.activeGenerations.get(projectId)?.startedAt, completedAt: new Date(), progress: { totalFiles: progress.totalFiles, processedFiles: progress.processedFiles, totalChunks: progress.totalChunks, embeddings: progress.embeddings, }, }); logger.info('✅ Background embedding generation completed', { projectId, filesProcessed: progress.processedFiles, chunksCreated: progress.totalChunks, embeddings: progress.embeddings, errors: progress.errors.length, duration: this.activeGenerations.get(projectId)?.completedAt ? Math.round( (this.activeGenerations.get(projectId)!.completedAt!.getTime() - this.activeGenerations.get(projectId)!.startedAt!.getTime()) / 1000 ) : 0, }); // Clean up after 5 minutes setTimeout( () => { this.activeGenerations.delete(projectId); }, 5 * 60 * 1000 ); }) .catch(error => { const errorMsg = error instanceof Error ? error.message : String(error); logger.error('❌ Background embedding generation failed', { projectId, error: errorMsg, }); // Update status with error this.activeGenerations.set(projectId, { projectId, projectPath, isGenerating: false, startedAt: this.activeGenerations.get(projectId)?.startedAt, completedAt: new Date(), error: errorMsg, }); // Clean up after 1 minute on error setTimeout(() => { this.activeGenerations.delete(projectId); }, 60 * 1000); }); } /** * Check if embeddings are currently being generated for a project */ isGenerating(projectId: string): boolean { const status = this.activeGenerations.get(projectId); return status?.isGenerating === true; } /** * Get generation status for a project */ getGenerationStatus(projectId: string): GenerationStatus | null { return this.activeGenerations.get(projectId) || null; } /** * Get all active generations */ getAllActiveGenerations(): GenerationStatus[] { return Array.from(this.activeGenerations.values()); } /** * Wait for generation to complete (with timeout) */ async waitForGeneration( projectId: string, timeoutMs: number = 60000 ): Promise<GenerationStatus | null> { const startTime = Date.now(); while (this.isGenerating(projectId)) { if (Date.now() - startTime > timeoutMs) { logger.warn('⏱️ Timeout waiting for embedding generation', { projectId, timeoutMs, }); return null; } // Wait 1 second before checking again await new Promise(resolve => setTimeout(resolve, 1000)); } return this.getGenerationStatus(projectId); } } // Export singleton instance getter export function getBackgroundEmbeddingManager(): BackgroundEmbeddingManager { return BackgroundEmbeddingManager.getInstance(); }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/sbarron/AmbianceMCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server