diff --git a/src/config/vector-db.config.ts b/src/config/vector-db.config.ts new file mode 100644 index 0000000..563974e --- /dev/null +++ b/src/config/vector-db.config.ts @@ -0,0 +1,585 @@ +import * as vscode from "vscode"; +import { Logger, LogLevel } from "../infrastructure/logger/logger"; + +export interface VectorDbConfig { + enabled: boolean; + embeddingModel: "gemini" | "openai" | "local"; + maxTokens: number; + batchSize: number; + searchResultLimit: number; + enableBackgroundProcessing: boolean; + enableProgressNotifications: boolean; + progressLocation: "notification" | "statusBar"; + debounceDelay: number; + performanceMode: "balanced" | "performance" | "memory"; + fallbackToKeywordSearch: boolean; + cacheEnabled: boolean; + logLevel: "debug" | "info" | "warn" | "error"; +} + +export interface PerformanceThresholds { + maxEmbeddingTime: number; // milliseconds + maxSearchTime: number; // milliseconds + maxMemoryUsage: number; // MB + maxFileSize: number; // bytes + maxConcurrentOperations: number; +} + +export interface FeatureFlags { + enableVectorSearch: boolean; + enableSemanticSimilarity: boolean; + enableSmartRanking: boolean; + enableRealtimeSync: boolean; + enableBulkOperations: boolean; + enableAnalytics: boolean; +} + +/** + * VectorDbConfigurationManager handles all configuration for the vector database system + * with intelligent defaults, validation, and performance optimization + */ +export class VectorDbConfigurationManager implements vscode.Disposable { + private logger: Logger; + private disposables: vscode.Disposable[] = []; + private cachedConfig: VectorDbConfig | null = null; + private configChangeListeners: ((config: VectorDbConfig) => void)[] = []; + + private readonly DEFAULT_CONFIG: VectorDbConfig = { + enabled: true, + embeddingModel: "gemini", + maxTokens: 6000, + batchSize: 10, + searchResultLimit: 8, + enableBackgroundProcessing: true, + enableProgressNotifications: true, + progressLocation: "notification", + debounceDelay: 1000, + performanceMode: "balanced", + fallbackToKeywordSearch: true, + cacheEnabled: true, + logLevel: "info", + }; + + private readonly PERFORMANCE_THRESHOLDS: Record = { + balanced: { + maxEmbeddingTime: 5000, + maxSearchTime: 2000, + maxMemoryUsage: 512, + maxFileSize: 1024 * 1024, // 1MB + maxConcurrentOperations: 3, + }, + performance: { + maxEmbeddingTime: 3000, + maxSearchTime: 1000, + maxMemoryUsage: 1024, + maxFileSize: 2 * 1024 * 1024, // 2MB + maxConcurrentOperations: 5, + }, + memory: { + maxEmbeddingTime: 10000, + maxSearchTime: 5000, + maxMemoryUsage: 256, + maxFileSize: 512 * 1024, // 512KB + maxConcurrentOperations: 2, + }, + }; + + constructor() { + this.logger = Logger.initialize("VectorDbConfigurationManager", { + minLevel: LogLevel.INFO, + }); + + this.setupConfigurationWatcher(); + this.validateConfiguration(); + } + + /** + * Get the current vector database configuration + */ + getConfig(): VectorDbConfig { + if (this.cachedConfig) { + return { ...this.cachedConfig }; + } + + const workspaceConfig = vscode.workspace.getConfiguration("codebuddy.vectorDb"); + + this.cachedConfig = { + enabled: workspaceConfig.get("enabled", this.DEFAULT_CONFIG.enabled), + embeddingModel: workspaceConfig.get("embeddingModel", this.DEFAULT_CONFIG.embeddingModel), + maxTokens: workspaceConfig.get("maxTokens", this.DEFAULT_CONFIG.maxTokens), + batchSize: workspaceConfig.get("batchSize", this.DEFAULT_CONFIG.batchSize), + searchResultLimit: workspaceConfig.get("searchResultLimit", this.DEFAULT_CONFIG.searchResultLimit), + enableBackgroundProcessing: workspaceConfig.get( + "enableBackgroundProcessing", + this.DEFAULT_CONFIG.enableBackgroundProcessing + ), + enableProgressNotifications: workspaceConfig.get( + "enableProgressNotifications", + this.DEFAULT_CONFIG.enableProgressNotifications + ), + progressLocation: workspaceConfig.get("progressLocation", this.DEFAULT_CONFIG.progressLocation), + debounceDelay: workspaceConfig.get("debounceDelay", this.DEFAULT_CONFIG.debounceDelay), + performanceMode: workspaceConfig.get("performanceMode", this.DEFAULT_CONFIG.performanceMode), + fallbackToKeywordSearch: workspaceConfig.get( + "fallbackToKeywordSearch", + this.DEFAULT_CONFIG.fallbackToKeywordSearch + ), + cacheEnabled: workspaceConfig.get("cacheEnabled", this.DEFAULT_CONFIG.cacheEnabled), + logLevel: workspaceConfig.get("logLevel", this.DEFAULT_CONFIG.logLevel), + }; + + return { ...this.cachedConfig }; + } + + /** + * Get performance thresholds based on current performance mode + */ + getPerformanceThresholds(): PerformanceThresholds { + const config = this.getConfig(); + return { ...this.PERFORMANCE_THRESHOLDS[config.performanceMode] }; + } + + /** + * Get feature flags based on current configuration + */ + getFeatureFlags(): FeatureFlags { + const config = this.getConfig(); + const workspaceConfig = vscode.workspace.getConfiguration("codebuddy.vectorDb.features"); + + return { + enableVectorSearch: config.enabled && workspaceConfig.get("enableVectorSearch", true), + enableSemanticSimilarity: config.enabled && workspaceConfig.get("enableSemanticSimilarity", true), + enableSmartRanking: config.enabled && workspaceConfig.get("enableSmartRanking", true), + enableRealtimeSync: config.enabled && workspaceConfig.get("enableRealtimeSync", true), + enableBulkOperations: config.enabled && workspaceConfig.get("enableBulkOperations", true), + enableAnalytics: workspaceConfig.get("enableAnalytics", false), + }; + } + + /** + * Update a specific configuration value + */ + async updateConfig( + key: K, + value: VectorDbConfig[K], + target: vscode.ConfigurationTarget = vscode.ConfigurationTarget.Workspace + ): Promise { + try { + const workspaceConfig = vscode.workspace.getConfiguration("codebuddy.vectorDb"); + await workspaceConfig.update(key, value, target); + + this.logger.info(`Configuration updated: ${key} = ${value}`); + + // Clear cache to force refresh + this.cachedConfig = null; + + // Notify listeners + const newConfig = this.getConfig(); + this.notifyConfigChange(newConfig); + } catch (error) { + this.logger.error(`Failed to update configuration ${key}:`, error); + throw error; + } + } + + /** + * Reset configuration to defaults + */ + async resetToDefaults(target: vscode.ConfigurationTarget = vscode.ConfigurationTarget.Workspace): Promise { + try { + const workspaceConfig = vscode.workspace.getConfiguration("codebuddy.vectorDb"); + + for (const [key, value] of Object.entries(this.DEFAULT_CONFIG)) { + await workspaceConfig.update(key, value, target); + } + + this.logger.info("Configuration reset to defaults"); + + // Clear cache and notify + this.cachedConfig = null; + const newConfig = this.getConfig(); + this.notifyConfigChange(newConfig); + + vscode.window.showInformationMessage("Vector database configuration reset to defaults"); + } catch (error) { + this.logger.error("Failed to reset configuration:", error); + vscode.window.showErrorMessage("Failed to reset configuration"); + throw error; + } + } + + /** + * Validate current configuration and show warnings if needed + */ + validateConfiguration(): boolean { + const config = this.getConfig(); + const issues: string[] = []; + + // Validate batch size + if (config.batchSize < 1 || config.batchSize > 50) { + issues.push(`Batch size ${config.batchSize} is outside recommended range (1-50)`); + } + + // Validate max tokens + if (config.maxTokens < 1000 || config.maxTokens > 32000) { + issues.push(`Max tokens ${config.maxTokens} is outside recommended range (1000-32000)`); + } + + // Validate debounce delay + if (config.debounceDelay < 100 || config.debounceDelay > 10000) { + issues.push(`Debounce delay ${config.debounceDelay}ms is outside recommended range (100-10000ms)`); + } + + // Validate search result limit + if (config.searchResultLimit < 1 || config.searchResultLimit > 20) { + issues.push(`Search result limit ${config.searchResultLimit} is outside recommended range (1-20)`); + } + + if (issues.length > 0) { + this.logger.warn("Configuration validation issues found:", issues); + + const message = `Vector database configuration issues detected:\\n${issues.join("\\n")}`; + vscode.window.showWarningMessage(message, "Fix Configuration", "Ignore").then((action) => { + if (action === "Fix Configuration") { + this.showConfigurationWizard(); + } + }); + + return false; + } + + return true; + } + + /** + * Show configuration wizard for guided setup + */ + async showConfigurationWizard(): Promise { + const config = this.getConfig(); + + // Step 1: Performance Mode + const performanceMode = await vscode.window.showQuickPick( + [ + { + label: "Balanced", + description: "Good balance of performance and memory usage", + detail: "Recommended for most users", + }, + { + label: "Performance", + description: "Optimized for speed, uses more memory", + detail: "Best for powerful machines", + }, + { + label: "Memory", + description: "Optimized for low memory usage, slower operations", + detail: "Best for resource-constrained environments", + }, + ], + { + placeHolder: "Select performance mode", + title: "Vector Database Configuration (1/4)", + } + ); + + if (!performanceMode) return; + + // Step 2: Background Processing + const backgroundProcessing = await vscode.window.showQuickPick( + [ + { + label: "Enable", + description: "Process embeddings in background during idle time", + picked: config.enableBackgroundProcessing, + }, + { + label: "Disable", + description: "Only process embeddings when explicitly requested", + picked: !config.enableBackgroundProcessing, + }, + ], + { + placeHolder: "Enable background processing?", + title: "Vector Database Configuration (2/4)", + } + ); + + if (!backgroundProcessing) return; + + // Step 3: Progress Notifications + const progressNotifications = await vscode.window.showQuickPick( + [ + { + label: "Notification Panel", + description: "Show progress in notification panel", + picked: config.progressLocation === "notification", + }, + { + label: "Status Bar", + description: "Show progress in status bar only", + picked: config.progressLocation === "statusBar", + }, + { + label: "Disabled", + description: "No progress notifications", + picked: !config.enableProgressNotifications, + }, + ], + { + placeHolder: "How should progress be displayed?", + title: "Vector Database Configuration (3/4)", + } + ); + + if (!progressNotifications) return; + + // Step 4: Batch Size + const batchSizeInput = await vscode.window.showInputBox({ + prompt: "Enter batch size for embedding operations (1-50)", + value: config.batchSize.toString(), + validateInput: (value) => { + const num = parseInt(value); + if (isNaN(num) || num < 1 || num > 50) { + return "Please enter a number between 1 and 50"; + } + return null; + }, + title: "Vector Database Configuration (4/4)", + }); + + if (!batchSizeInput) return; + + // Apply configuration + try { + await this.updateConfig("performanceMode", performanceMode.label.toLowerCase() as any); + await this.updateConfig("enableBackgroundProcessing", backgroundProcessing.label === "Enable"); + + if (progressNotifications.label === "Disabled") { + await this.updateConfig("enableProgressNotifications", false); + } else { + await this.updateConfig("enableProgressNotifications", true); + await this.updateConfig( + "progressLocation", + progressNotifications.label === "Notification Panel" ? "notification" : "statusBar" + ); + } + + await this.updateConfig("batchSize", parseInt(batchSizeInput)); + + vscode.window.showInformationMessage("Vector database configuration updated successfully!"); + } catch (error) { + vscode.window.showErrorMessage("Failed to update configuration"); + } + } + + /** + * Auto-tune configuration based on system capabilities and workspace size + */ + async autoTuneConfiguration(): Promise { + try { + this.logger.info("Starting auto-tune configuration..."); + + // Analyze workspace + const workspaceStats = await this.analyzeWorkspace(); + + // Determine optimal configuration + const optimalConfig = this.calculateOptimalConfig(workspaceStats); + + // Apply configuration + for (const [key, value] of Object.entries(optimalConfig)) { + await this.updateConfig(key as keyof VectorDbConfig, value); + } + + this.logger.info("Auto-tune configuration completed"); + vscode.window.showInformationMessage(`Configuration auto-tuned for ${workspaceStats.totalFiles} files`); + } catch (error) { + this.logger.error("Auto-tune failed:", error); + vscode.window.showErrorMessage("Failed to auto-tune configuration"); + } + } + + /** + * Analyze workspace to determine optimal settings + */ + private async analyzeWorkspace(): Promise<{ + totalFiles: number; + averageFileSize: number; + codeFileTypes: string[]; + estimatedMemoryUsage: number; + }> { + const workspaceFolders = vscode.workspace.workspaceFolders; + if (!workspaceFolders) { + return { totalFiles: 0, averageFileSize: 0, codeFileTypes: [], estimatedMemoryUsage: 0 }; + } + + let totalFiles = 0; + let totalSize = 0; + const fileTypes = new Set(); + + for (const folder of workspaceFolders) { + const pattern = new vscode.RelativePattern(folder, "**/*.{ts,tsx,js,jsx,py,java,cpp,c,cs,go,rs,php,rb}"); + const files = await vscode.workspace.findFiles(pattern, "**/node_modules/**"); + + totalFiles += files.length; + + for (const file of files.slice(0, 100)) { + // Sample first 100 files + try { + const stat = await vscode.workspace.fs.stat(file); + totalSize += stat.size; + + const ext = file.fsPath.split(".").pop(); + if (ext) fileTypes.add(ext); + } catch (error) { + // Ignore files that can't be read + } + } + } + + const averageFileSize = totalFiles > 0 ? totalSize / Math.min(totalFiles, 100) : 0; + const estimatedMemoryUsage = (totalFiles * averageFileSize * 2) / (1024 * 1024); // Rough estimate in MB + + return { + totalFiles, + averageFileSize, + codeFileTypes: Array.from(fileTypes), + estimatedMemoryUsage, + }; + } + + /** + * Calculate optimal configuration based on workspace analysis + */ + private calculateOptimalConfig(stats: { + totalFiles: number; + averageFileSize: number; + estimatedMemoryUsage: number; + }): Partial { + const config: Partial = {}; + + // Determine performance mode + if (stats.estimatedMemoryUsage > 1000) { + config.performanceMode = "memory"; + config.batchSize = 5; + } else if (stats.estimatedMemoryUsage < 200) { + config.performanceMode = "performance"; + config.batchSize = 15; + } else { + config.performanceMode = "balanced"; + config.batchSize = 10; + } + + // Adjust batch size based on file count + if (stats.totalFiles > 5000) { + config.batchSize = Math.min(config.batchSize || 10, 8); + } else if (stats.totalFiles < 100) { + config.batchSize = Math.max(config.batchSize || 10, 5); + } + + // Adjust debounce delay based on project size + if (stats.totalFiles > 1000) { + config.debounceDelay = 2000; // Longer delay for large projects + } else { + config.debounceDelay = 1000; // Standard delay + } + + return config; + } + + /** + * Setup configuration watcher + */ + private setupConfigurationWatcher(): void { + const configWatcher = vscode.workspace.onDidChangeConfiguration((event) => { + if (event.affectsConfiguration("codebuddy.vectorDb")) { + this.logger.info("Vector database configuration changed"); + + // Clear cache + this.cachedConfig = null; + + // Validate new configuration + this.validateConfiguration(); + + // Notify listeners + const newConfig = this.getConfig(); + this.notifyConfigChange(newConfig); + } + }); + + this.disposables.push(configWatcher); + } + + /** + * Add configuration change listener + */ + onConfigChange(listener: (config: VectorDbConfig) => void): vscode.Disposable { + this.configChangeListeners.push(listener); + + return { + dispose: () => { + const index = this.configChangeListeners.indexOf(listener); + if (index >= 0) { + this.configChangeListeners.splice(index, 1); + } + }, + }; + } + + /** + * Notify all listeners of configuration changes + */ + private notifyConfigChange(config: VectorDbConfig): void { + for (const listener of this.configChangeListeners) { + try { + listener(config); + } catch (error) { + this.logger.error("Error in config change listener:", error); + } + } + } + + /** + * Export current configuration + */ + exportConfiguration(): string { + const config = this.getConfig(); + return JSON.stringify(config, null, 2); + } + + /** + * Import configuration from JSON + */ + async importConfiguration( + configJson: string, + target: vscode.ConfigurationTarget = vscode.ConfigurationTarget.Workspace + ): Promise { + try { + const config = JSON.parse(configJson) as Partial; + + // Validate imported config + for (const [key, value] of Object.entries(config)) { + if (key in this.DEFAULT_CONFIG) { + await this.updateConfig(key as keyof VectorDbConfig, value, target); + } + } + + vscode.window.showInformationMessage("Configuration imported successfully"); + } catch (error) { + this.logger.error("Failed to import configuration:", error); + vscode.window.showErrorMessage("Failed to import configuration: Invalid JSON"); + throw error; + } + } + + /** + * Dispose of all resources + */ + dispose(): void { + this.logger.info("Disposing Vector Database Configuration Manager"); + + this.disposables.forEach((d) => d.dispose()); + this.disposables.length = 0; + this.configChangeListeners.length = 0; + this.cachedConfig = null; + } +} diff --git a/src/extension.ts b/src/extension.ts index 40a5571..fa4db60 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -2,11 +2,7 @@ import * as fs from "fs"; import * as path from "path"; import * as vscode from "vscode"; import { Orchestrator } from "./agents/orchestrator"; -import { - APP_CONFIG, - CODEBUDDY_ACTIONS, - generativeAiModels, -} from "./application/constant"; +import { APP_CONFIG, CODEBUDDY_ACTIONS, generativeAiModels } from "./application/constant"; import { Comments } from "./commands/comment"; import { ExplainCode } from "./commands/explain"; import { FixError } from "./commands/fixError"; @@ -31,6 +27,9 @@ import { GroqWebViewProvider } from "./webview-providers/groq"; import { WebViewProviderManager } from "./webview-providers/manager"; import { architecturalRecommendationCommand } from "./commands/architectural-recommendation"; import { PersistentCodebaseUnderstandingService } from "./services/persistent-codebase-understanding.service"; +import { VectorDbSyncService } from "./services/vector-db-sync.service"; +import { VectorDatabaseService } from "./services/vector-database.service"; +import { VectorDbWorkerManager } from "./services/vector-db-worker-manager"; import { generateDocumentationCommand, regenerateDocumentationCommand, @@ -57,6 +56,112 @@ let quickFixCodeAction: vscode.Disposable; let agentEventEmmitter: EventEmitter; let orchestrator = Orchestrator.getInstance(); +// Global references for Phase 4 components +let vectorDbSyncService: VectorDbSyncService | undefined; +let vectorDbWorkerManager: VectorDbWorkerManager | undefined; + +/** + * Initialize Phase 4 Vector Database Orchestration + * This sets up the comprehensive vector database system with multi-phase embedding + */ +async function initializeVectorDatabaseOrchestration(context: vscode.ExtensionContext): Promise { + try { + console.log("๐Ÿš€ Starting Phase 4 Vector Database Orchestration..."); + + // Get Gemini API key for consistent embeddings + const { apiKey: geminiApiKey } = getAPIKeyAndModel("Gemini"); + if (!geminiApiKey) { + console.warn("Gemini API key not found, skipping vector database initialization"); + return; + } + + // Initialize worker manager for non-blocking operations + vectorDbWorkerManager = new VectorDbWorkerManager(context); + await vectorDbWorkerManager.initialize(); + console.log("โœ“ Vector database worker manager initialized"); + + // Initialize vector database service + const vectorDatabaseService = new VectorDatabaseService(context, geminiApiKey); + await vectorDatabaseService.initialize(); + console.log("โœ“ Vector database service initialized"); + + // Initialize sync service for real-time file monitoring + // Use CodeIndexingService as the indexer for the sync service + const { CodeIndexingService } = await import("./services/code-indexing"); + const { CodeIndexingAdapter } = await import("./services/vector-db-sync.service"); + const codeIndexingService = CodeIndexingService.createInstance(); + const codeIndexingAdapter = new CodeIndexingAdapter(codeIndexingService); + + vectorDbSyncService = new VectorDbSyncService(vectorDatabaseService, codeIndexingAdapter); + await vectorDbSyncService.initialize(); + console.log("โœ“ Vector database sync service initialized"); + + // Register commands for user control + registerVectorDatabaseCommands(context); + + // Show success notification + vscode.window.setStatusBarMessage("$(check) CodeBuddy: Vector database orchestration ready", 5000); + + console.log("๐ŸŽ‰ Phase 4 Vector Database Orchestration completed successfully"); + } catch (error: any) { + console.error("Failed to initialize Phase 4 orchestration:", error); + const errorMessage = error instanceof Error ? error.message : String(error); + vscode.window.showWarningMessage( + `CodeBuddy: Vector database initialization failed: ${errorMessage}. Using fallback search mode.` + ); + } +} + +/** + * Register vector database related commands + */ +function registerVectorDatabaseCommands(context: vscode.ExtensionContext): void { + // Command to force full reindex + const forceReindexCommand = vscode.commands.registerCommand("codebuddy.vectorDb.forceReindex", async () => { + if (!vectorDbSyncService) { + vscode.window.showErrorMessage("Vector database not initialized"); + return; + } + + const confirm = await vscode.window.showWarningMessage( + "This will clear all embeddings and reindex the entire codebase. Continue?", + "Yes, Reindex", + "Cancel" + ); + + if (confirm === "Yes, Reindex") { + try { + // Force a full reindex using the available method + await vectorDbSyncService.performFullReindex(); + vscode.window.showInformationMessage("Full reindex completed successfully"); + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); + vscode.window.showErrorMessage(`Reindex failed: ${errorMessage}`); + } + } + }); + + // Command to show vector database stats + const showStatsCommand = vscode.commands.registerCommand("codebuddy.vectorDb.showStats", async () => { + if (!vectorDbSyncService) { + vscode.window.showErrorMessage("Vector database not initialized"); + return; + } + + const stats = vectorDbSyncService.getStats(); + const message = `**Vector Database Statistics**\n\n +โ€ข Files Monitored: ${stats.filesMonitored}\n +โ€ข Sync Operations: ${stats.syncOperations}\n +โ€ข Failed Operations: ${stats.failedOperations}\n +โ€ข Queue Size: ${stats.queueSize}\n +โ€ข Last Sync: ${stats.lastSync || "Never"}`; + + vscode.window.showInformationMessage(message); + }); + + context.subscriptions.push(forceReindexCommand, showStatsCommand); +} + export async function activate(context: vscode.ExtensionContext) { try { orchestrator.start(); @@ -67,14 +172,17 @@ export async function activate(context: vscode.ExtensionContext) { // Initialize persistent codebase understanding service try { - const persistentCodebaseService = - PersistentCodebaseUnderstandingService.getInstance(); + const persistentCodebaseService = PersistentCodebaseUnderstandingService.getInstance(); await persistentCodebaseService.initialize(); console.log("Persistent codebase understanding service initialized"); } catch (error) { - console.warn("Failed to initialize persistent codebase service:", error); + const errorMessage = error instanceof Error ? error.message : String(error); + console.warn(`Failed to initialize persistent codebase service: ${errorMessage}`, error); } + // Phase 4: Initialize Vector Database Orchestration + await initializeVectorDatabaseOrchestration(context); + // TODO for RAG codeIndexing incase user allows // const index = CodeIndexingService.createInstance(); // Get each of the folders and call the next line for each @@ -101,99 +209,54 @@ export async function activate(context: vscode.ExtensionContext) { } = CODEBUDDY_ACTIONS; const getComment = new Comments(CODEBUDDY_ACTIONS.comment, context); const getInLineChat = new InLineChat(CODEBUDDY_ACTIONS.inlineChat, context); - const generateOptimizeCode = new OptimizeCode( - CODEBUDDY_ACTIONS.optimize, - context, - ); - const generateRefactoredCode = new RefactorCode( - CODEBUDDY_ACTIONS.refactor, - context, - ); + const generateOptimizeCode = new OptimizeCode(CODEBUDDY_ACTIONS.optimize, context); + const generateRefactoredCode = new RefactorCode(CODEBUDDY_ACTIONS.refactor, context); const explainCode = new ExplainCode(CODEBUDDY_ACTIONS.explain, context); const generateReview = new ReviewCode(CODEBUDDY_ACTIONS.review, context); - const generateMermaidDiagram = new GenerateMermaidDiagram( - CODEBUDDY_ACTIONS.generateDiagram, - context, - ); - const generateCommitMessage = new GenerateCommitMessage( - CODEBUDDY_ACTIONS.commitMessage, - context, - ); - const generateInterviewQuestions = new InterviewMe( - CODEBUDDY_ACTIONS.interviewMe, - context, - ); + const generateMermaidDiagram = new GenerateMermaidDiagram(CODEBUDDY_ACTIONS.generateDiagram, context); + const generateCommitMessage = new GenerateCommitMessage(CODEBUDDY_ACTIONS.commitMessage, context); + const generateInterviewQuestions = new InterviewMe(CODEBUDDY_ACTIONS.interviewMe, context); const reviewPRCommand = new ReviewPR(CODEBUDDY_ACTIONS.reviewPR, context); const actionMap = { [comment]: async () => { - await getComment.execute( - undefined, - "๐Ÿ’ญ Add a helpful comment to explain the code logic", - ); + await getComment.execute(undefined, "๐Ÿ’ญ Add a helpful comment to explain the code logic"); }, [review]: async () => { - await generateReview.execute( - undefined, - "๐Ÿ” Perform a thorough code review to ensure best practices", - ); + await generateReview.execute(undefined, "๐Ÿ” Perform a thorough code review to ensure best practices"); }, [refactor]: async () => { - await generateRefactoredCode.execute( - undefined, - " ๐Ÿ”„ Improve code readability and maintainability", - ); + await generateRefactoredCode.execute(undefined, " ๐Ÿ”„ Improve code readability and maintainability"); }, [optimize]: async () => { - await generateOptimizeCode.execute( - undefined, - "โšก optimize for performance and efficiency", - ); + await generateOptimizeCode.execute(undefined, "โšก optimize for performance and efficiency"); }, [interviewMe]: async () => { await generateInterviewQuestions.execute( undefined, - "๐Ÿ“š Prepare for technical interviews with relevant questions", + "๐Ÿ“š Prepare for technical interviews with relevant questions" ); }, [fix]: (errorMessage: string) => { - new FixError(CODEBUDDY_ACTIONS.fix, context, errorMessage).execute( - errorMessage, - "๐Ÿ”ง Debug and fix the issue", - ); + new FixError(CODEBUDDY_ACTIONS.fix, context, errorMessage).execute(errorMessage, "๐Ÿ”ง Debug and fix the issue"); }, [explain]: async () => { - await explainCode.execute( - undefined, - "๐Ÿ’ฌ Get a clear and concise explanation of the code concept", - ); + await explainCode.execute(undefined, "๐Ÿ’ฌ Get a clear and concise explanation of the code concept"); }, [commitMessage]: async () => { - await generateCommitMessage.execute( - undefined, - "๐Ÿงช generating commit message", - ); + await generateCommitMessage.execute(undefined, "๐Ÿงช generating commit message"); }, [generateDiagram]: async () => { - await generateMermaidDiagram.execute( - undefined, - "๐Ÿ“ˆ Visualize the code with a Mermaid diagram", - ); + await generateMermaidDiagram.execute(undefined, "๐Ÿ“ˆ Visualize the code with a Mermaid diagram"); }, [inlineChat]: async () => { - await getInLineChat.execute( - undefined, - "๐Ÿ’ฌ Discuss and reason about your code with me", - ); + await getInLineChat.execute(undefined, "๐Ÿ’ฌ Discuss and reason about your code with me"); }, [restart]: async () => { await restartExtension(context); }, [reviewPR]: async () => { - await reviewPRCommand.execute( - undefined, - "๐Ÿ” Conducting comprehensive pull request review", - ); + await reviewPRCommand.execute(undefined, "๐Ÿ” Conducting comprehensive pull request review"); }, [codebaseAnalysis]: async () => { await architecturalRecommendationCommand(); @@ -208,8 +271,7 @@ export async function activate(context: vscode.ExtensionContext) { await openDocumentationCommand(); }, "CodeBuddy.showCacheStatus": async () => { - const persistentCodebaseService = - PersistentCodebaseUnderstandingService.getInstance(); + const persistentCodebaseService = PersistentCodebaseUnderstandingService.getInstance(); const summary = await persistentCodebaseService.getAnalysisSummary(); const stats = summary.stats; @@ -236,14 +298,9 @@ export async function activate(context: vscode.ExtensionContext) { message += `โ€ข Newest: ${new Date(stats.newestSnapshot).toLocaleString()}\n`; } - const panel = vscode.window.createWebviewPanel( - "cacheStatus", - "CodeBuddy Cache Status", - vscode.ViewColumn.One, - { - enableScripts: false, - }, - ); + const panel = vscode.window.createWebviewPanel("cacheStatus", "CodeBuddy Cache Status", vscode.ViewColumn.One, { + enableScripts: false, + }); panel.webview.html = ` @@ -276,20 +333,17 @@ export async function activate(context: vscode.ExtensionContext) { const choice = await vscode.window.showWarningMessage( "Are you sure you want to clear the codebase analysis cache? This will require re-analysis next time.", "Clear Cache", - "Cancel", + "Cancel" ); if (choice === "Clear Cache") { try { - const persistentCodebaseService = - PersistentCodebaseUnderstandingService.getInstance(); + const persistentCodebaseService = PersistentCodebaseUnderstandingService.getInstance(); await persistentCodebaseService.clearCache(); - vscode.window.showInformationMessage( - "โœ… Codebase analysis cache cleared successfully", - ); + vscode.window.showInformationMessage("โœ… Codebase analysis cache cleared successfully"); } catch (error) { vscode.window.showErrorMessage( - `โŒ Failed to clear cache: ${error instanceof Error ? error.message : "Unknown error"}`, + `โŒ Failed to clear cache: ${error instanceof Error ? error.message : "Unknown error"}` ); } } @@ -298,13 +352,12 @@ export async function activate(context: vscode.ExtensionContext) { const choice = await vscode.window.showInformationMessage( "This will refresh the codebase analysis. It may take some time.", "Refresh Now", - "Cancel", + "Cancel" ); if (choice === "Refresh Now") { try { - const persistentCodebaseService = - PersistentCodebaseUnderstandingService.getInstance(); + const persistentCodebaseService = PersistentCodebaseUnderstandingService.getInstance(); await vscode.window.withProgress( { @@ -313,24 +366,21 @@ export async function activate(context: vscode.ExtensionContext) { cancellable: true, }, async (progress, token) => { - const analysis = - await persistentCodebaseService.forceRefreshAnalysis(token); + const analysis = await persistentCodebaseService.forceRefreshAnalysis(token); if (analysis && !token.isCancellationRequested) { vscode.window.showInformationMessage( - `โœ… Analysis refreshed successfully! Found ${analysis.summary.totalFiles} files. Analysis completed at ${new Date(analysis.analysisMetadata.createdAt).toLocaleString()}.`, + `โœ… Analysis refreshed successfully! Found ${analysis.summary.totalFiles} files. Analysis completed at ${new Date(analysis.analysisMetadata.createdAt).toLocaleString()}.` ); } - }, + } ); } catch (error) { if (error instanceof Error && error.message.includes("cancelled")) { - vscode.window.showInformationMessage( - "Analysis refresh cancelled", - ); + vscode.window.showInformationMessage("Analysis refresh cancelled"); } else { vscode.window.showErrorMessage( - `โŒ Failed to refresh analysis: ${error instanceof Error ? error.message : "Unknown error"}`, + `โŒ Failed to refresh analysis: ${error instanceof Error ? error.message : "Unknown error"}` ); } } @@ -338,22 +388,17 @@ export async function activate(context: vscode.ExtensionContext) { }, }; - let subscriptions: vscode.Disposable[] = Object.entries(actionMap).map( - ([action, handler]) => { - console.log(`Registering command: ${action}`); - return vscode.commands.registerCommand(action, handler); - }, - ); + let subscriptions: vscode.Disposable[] = Object.entries(actionMap).map(([action, handler]) => { + console.log(`Registering command: ${action}`); + return vscode.commands.registerCommand(action, handler); + }); console.log(`Total commands registered: ${subscriptions.length}`); const selectedGenerativeAiModel = getConfigValue("generativeAi.option"); const quickFix = new CodeActionsProvider(); - quickFixCodeAction = vscode.languages.registerCodeActionsProvider( - { scheme: "file", language: "*" }, - quickFix, - ); + quickFixCodeAction = vscode.languages.registerCodeActionsProvider({ scheme: "file", language: "*" }, quickFix); agentEventEmmitter = new EventEmitter(); @@ -397,26 +442,19 @@ export async function activate(context: vscode.ExtensionContext) { const modelConfig = modelConfigurations[selectedGenerativeAiModel]; const apiKey = getConfigValue(modelConfig.key); const apiModel = getConfigValue(modelConfig.model); - providerManager.initializeProvider( - selectedGenerativeAiModel, - apiKey, - apiModel, - true, - ); + providerManager.initializeProvider(selectedGenerativeAiModel, apiKey, apiModel, true); } context.subscriptions.push( ...subscriptions, quickFixCodeAction, agentEventEmmitter, orchestrator, - providerManager, + providerManager // secretStorageService, ); } catch (error) { Memory.clear(); - vscode.window.showErrorMessage( - "An Error occured while setting up generative AI model", - ); + vscode.window.showErrorMessage("An Error occured while setting up generative AI model"); console.log(error); } } @@ -432,7 +470,7 @@ async function restartExtension(context: vscode.ExtensionContext) { const choice = await vscode.window.showInformationMessage( "Are you sure you want to restart the CodeBuddy extension?", "Restart", - "Cancel", + "Cancel" ); if (choice === "Restart") { @@ -462,14 +500,12 @@ async function restartExtension(context: vscode.ExtensionContext) { await new Promise((resolve) => setTimeout(resolve, 500)); progress.report({ increment: 100, message: "Reloading..." }); await vscode.commands.executeCommand("workbench.action.reloadWindow"); - }, + } ); } } catch (error) { console.error("Error restarting extension:", error); - vscode.window.showErrorMessage( - "Failed to restart extension. Please reload VS Code manually.", - ); + vscode.window.showErrorMessage("Failed to restart extension. Please reload VS Code manually."); } } @@ -479,10 +515,23 @@ export function deactivate(context: vscode.ExtensionContext) { // Clear database history before deactivation clearFileStorageData(); + // Phase 4: Dispose vector database components + try { + if (vectorDbSyncService) { + vectorDbSyncService.dispose(); + console.log("โœ“ Vector database sync service disposed"); + } + if (vectorDbWorkerManager) { + vectorDbWorkerManager.dispose(); + console.log("โœ“ Vector database worker manager disposed"); + } + } catch (error) { + console.warn("Error disposing Phase 4 vector components:", error); + } + // Shutdown persistent codebase service try { - const persistentCodebaseService = - PersistentCodebaseUnderstandingService.getInstance(); + const persistentCodebaseService = PersistentCodebaseUnderstandingService.getInstance(); persistentCodebaseService.shutdown(); console.log("Persistent codebase service shutdown"); } catch (error) { @@ -507,8 +556,7 @@ export function deactivate(context: vscode.ExtensionContext) { */ function clearFileStorageData() { try { - const workSpaceRoot = - vscode.workspace.workspaceFolders?.[0]?.uri.fsPath ?? ""; + const workSpaceRoot = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath ?? ""; const codeBuddyPath = path.join(workSpaceRoot, ".codebuddy"); if (fs.existsSync(codeBuddyPath)) { diff --git a/src/infrastructure/configuration-observer.ts b/src/infrastructure/configuration-observer.ts new file mode 100644 index 0000000..6fd3560 --- /dev/null +++ b/src/infrastructure/configuration-observer.ts @@ -0,0 +1,199 @@ +import * as vscode from "vscode"; +import { Logger, LogLevel } from "../infrastructure/logger/logger"; + +/** + * Generic observer interface + */ +export interface Observer { + update(data: T): void; +} + +/** + * Observable configuration manager using the Observer pattern + */ +export class ConfigurationObservable implements vscode.Disposable { + private observers: Observer[] = []; + private logger: Logger; + private disposables: vscode.Disposable[] = []; + + constructor(private configurationSection: string) { + this.logger = Logger.initialize("ConfigurationObservable", { + minLevel: LogLevel.INFO, + }); + + // Watch for configuration changes + const configWatcher = vscode.workspace.onDidChangeConfiguration((event) => { + if (event.affectsConfiguration(this.configurationSection)) { + this.notifyObservers(); + } + }); + + this.disposables.push(configWatcher); + } + + /** + * Subscribe an observer to configuration changes + */ + subscribe(observer: Observer): vscode.Disposable { + this.observers.push(observer); + this.logger.info(`Observer subscribed to ${this.configurationSection} changes`); + + return { + dispose: () => { + this.unsubscribe(observer); + }, + }; + } + + /** + * Unsubscribe an observer from configuration changes + */ + unsubscribe(observer: Observer): void { + const index = this.observers.indexOf(observer); + if (index >= 0) { + this.observers.splice(index, 1); + this.logger.info(`Observer unsubscribed from ${this.configurationSection} changes`); + } + } + + /** + * Notify all observers of configuration changes + */ + private notifyObservers(): void { + const config = this.getCurrentConfiguration(); + this.logger.info(`Notifying ${this.observers.length} observers of configuration change`); + + for (const observer of this.observers) { + try { + observer.update(config); + } catch (error) { + this.logger.error("Error in configuration observer:", error); + } + } + } + + /** + * Get current configuration - to be overridden by subclasses + */ + protected getCurrentConfiguration(): T { + const config = vscode.workspace.getConfiguration(this.configurationSection); + return config as unknown as T; + } + + /** + * Manually trigger notification (useful for testing or forced updates) + */ + notify(): void { + this.notifyObservers(); + } + + /** + * Get the number of current observers + */ + getObserverCount(): number { + return this.observers.length; + } + + /** + * Dispose of all resources + */ + dispose(): void { + this.logger.info("Disposing configuration observable"); + this.observers.length = 0; + this.disposables.forEach((d) => d.dispose()); + this.disposables.length = 0; + } +} + +/** + * Specific observable for Vector Database Configuration + */ +export class VectorDbConfigObservable extends ConfigurationObservable { + constructor() { + super("codebuddy.vectorDb"); + } + + protected getCurrentConfiguration(): any { + const config = vscode.workspace.getConfiguration("codebuddy.vectorDb"); + + return { + enabled: config.get("enabled", true), + embeddingModel: config.get("embeddingModel", "gemini"), + maxTokens: config.get("maxTokens", 6000), + batchSize: config.get("batchSize", 10), + searchResultLimit: config.get("searchResultLimit", 8), + enableBackgroundProcessing: config.get("enableBackgroundProcessing", true), + enableProgressNotifications: config.get("enableProgressNotifications", true), + progressLocation: config.get("progressLocation", "notification"), + debounceDelay: config.get("debounceDelay", 1000), + performanceMode: config.get("performanceMode", "balanced"), + fallbackToKeywordSearch: config.get("fallbackToKeywordSearch", true), + cacheEnabled: config.get("cacheEnabled", true), + logLevel: config.get("logLevel", "info"), + slowSearchThreshold: config.get("slowSearchThreshold", 2000), + }; + } +} + +/** + * Configuration change listener that implements Observer pattern + */ +export class ConfigurationChangeListener implements Observer { + private logger: Logger; + + constructor( + private name: string, + private callback: (config: any) => void + ) { + this.logger = Logger.initialize(`ConfigListener_${name}`, { + minLevel: LogLevel.INFO, + }); + } + + update(config: any): void { + this.logger.info(`Configuration changed for ${this.name}`); + try { + this.callback(config); + } catch (error) { + this.logger.error(`Error in configuration callback for ${this.name}:`, error); + } + } +} + +/** + * Factory for creating configuration observers + */ +export class ConfigurationObserverFactory { + private static observables = new Map>(); + + /** + * Get or create an observable for a configuration section + */ + static getObservable(section: string): ConfigurationObservable { + if (!this.observables.has(section)) { + if (section === "codebuddy.vectorDb") { + this.observables.set(section, new VectorDbConfigObservable()); + } else { + this.observables.set(section, new ConfigurationObservable(section)); + } + } + return this.observables.get(section) as ConfigurationObservable; + } + + /** + * Create a configuration listener + */ + static createListener(name: string, callback: (config: T) => void): Observer { + return new ConfigurationChangeListener(name, callback); + } + + /** + * Dispose of all observables + */ + static disposeAll(): void { + for (const observable of this.observables.values()) { + observable.dispose(); + } + this.observables.clear(); + } +} diff --git a/src/infrastructure/dependency-container.ts b/src/infrastructure/dependency-container.ts new file mode 100644 index 0000000..9f36c47 --- /dev/null +++ b/src/infrastructure/dependency-container.ts @@ -0,0 +1,143 @@ +import { IDependencyContainer } from "../interfaces/services.interface"; +import { Logger, LogLevel } from "../infrastructure/logger/logger"; + +/** + * Simple dependency injection container for vector database services + */ +export class DependencyContainer implements IDependencyContainer { + private services = new Map any; singleton: boolean; instance?: any }>(); + private logger: Logger; + + constructor() { + this.logger = Logger.initialize("DependencyContainer", { + minLevel: LogLevel.INFO, + }); + } + + /** + * Register a transient service (new instance each time) + */ + register(token: string, factory: () => T): void { + this.services.set(token, { factory, singleton: false }); + this.logger.info(`Registered transient service: ${token}`); + } + + /** + * Register a singleton service (same instance always) + */ + registerSingleton(token: string, factory: () => T): void { + this.services.set(token, { factory, singleton: true }); + this.logger.info(`Registered singleton service: ${token}`); + } + + /** + * Resolve a service from the container + */ + resolve(token: string): T { + const serviceInfo = this.services.get(token); + if (!serviceInfo) { + throw new Error(`Service '${token}' is not registered in the container`); + } + + if (serviceInfo.singleton) { + if (!serviceInfo.instance) { + serviceInfo.instance = serviceInfo.factory(); + this.logger.info(`Created singleton instance for: ${token}`); + } + return serviceInfo.instance; + } else { + this.logger.debug(`Creating new instance for: ${token}`); + return serviceInfo.factory(); + } + } + + /** + * Check if a service is registered + */ + isRegistered(token: string): boolean { + return this.services.has(token); + } + + /** + * Get all registered service tokens + */ + getRegisteredServices(): string[] { + return Array.from(this.services.keys()); + } + + /** + * Dispose of all singleton instances and clear the container + */ + dispose(): void { + this.logger.info("Disposing dependency container..."); + + // Dispose of singleton instances that have a dispose method + for (const [token, serviceInfo] of this.services) { + if (serviceInfo.singleton && serviceInfo.instance) { + const instance = serviceInfo.instance; + if (typeof instance.dispose === "function") { + try { + instance.dispose(); + this.logger.info(`Disposed singleton service: ${token}`); + } catch (error) { + this.logger.error(`Error disposing service ${token}:`, error); + } + } + } + } + + this.services.clear(); + this.logger.info("Dependency container disposed"); + } +} + +/** + * Service tokens for dependency injection + */ +export const ServiceTokens = { + // Core services + VECTOR_DATABASE_SERVICE: "IVectorDatabaseService", + VECTOR_DB_WORKER_MANAGER: "IVectorDbWorkerManager", + VECTOR_DB_SYNC_SERVICE: "IVectorDbSync", + + // Context and search + SMART_CONTEXT_EXTRACTOR: "ISmartContextExtractor", + CODE_INDEXER: "ICodeIndexer", + + // User interaction + USER_FEEDBACK_SERVICE: "IUserFeedbackService", + CONFIGURATION_MANAGER: "IConfigurationManager", + + // Orchestration + EMBEDDING_ORCHESTRATOR: "IEmbeddingOrchestrator", + + // External dependencies + EXTENSION_CONTEXT: "vscode.ExtensionContext", + API_KEY: "ApiKey", + LOGGER: "Logger", +} as const; + +/** + * Global dependency container instance + */ +let globalContainer: DependencyContainer | null = null; + +/** + * Get or create the global dependency container + */ +export function getContainer(): DependencyContainer { + if (!globalContainer) { + globalContainer = new DependencyContainer(); + } + return globalContainer; +} + +/** + * Dispose of the global container + */ +export function disposeContainer(): void { + if (globalContainer) { + globalContainer.dispose(); + globalContainer = null; + } +} diff --git a/src/interfaces/services.interface.ts b/src/interfaces/services.interface.ts new file mode 100644 index 0000000..f31200e --- /dev/null +++ b/src/interfaces/services.interface.ts @@ -0,0 +1,321 @@ +import * as vscode from "vscode"; +import { IVectorDbSync, SyncStats } from "./vector-db.interface"; + +/** + * Interface for Vector Database Service operations + */ +export interface IVectorDatabaseService { + /** + * Initialize the vector database + */ + initialize(): Promise; + + /** + * Store embeddings for a code chunk + */ + storeEmbedding( + id: string, + embedding: number[], + metadata: { + filePath: string; + content: string; + language: string; + chunkIndex?: number; + } + ): Promise; + + /** + * Search for similar code using vector similarity + */ + searchSimilar( + queryEmbedding: number[], + options?: { + limit?: number; + threshold?: number; + filters?: Record; + } + ): Promise; + + /** + * Delete embeddings for a file + */ + deleteEmbeddings(filePath: string): Promise; + + /** + * Get collection statistics + */ + getStats(): Promise; + + /** + * Health check for the database + */ + isHealthy(): Promise; + + /** + * Dispose of resources + */ + dispose(): void; +} + +/** + * Search result from vector database + */ +export interface SearchResult { + id: string; + score: number; + metadata: { + filePath: string; + content: string; + language: string; + chunkIndex?: number; + }; +} + +/** + * Database statistics + */ +export interface DatabaseStats { + totalDocuments: number; + totalEmbeddings: number; + collectionSize: number; + lastUpdated: Date; +} + +/** + * Interface for Vector Database Worker Manager + */ +export interface IVectorDbWorkerManager { + /** + * Initialize the worker manager + */ + initialize(): Promise; + + /** + * Process a file for embedding generation + */ + processFile(filePath: string, content: string): Promise; + + /** + * Get worker status and statistics + */ + getStatus(): WorkerStatus; + + /** + * Queue a file for processing + */ + queueFile(filePath: string, priority?: "high" | "normal" | "low"): Promise; + + /** + * Cancel processing for a specific file + */ + cancelFile(filePath: string): Promise; + + /** + * Dispose of all workers and resources + */ + dispose(): void; +} + +/** + * Result from file processing + */ +export interface ProcessResult { + filePath: string; + success: boolean; + embeddings?: number[][]; + chunks?: string[]; + error?: string; + processingTime: number; +} + +/** + * Worker manager status + */ +export interface WorkerStatus { + activeWorkers: number; + queueSize: number; + processedFiles: number; + failedFiles: number; + isProcessing: boolean; + averageProcessingTime: number; +} + +/** + * Interface for Smart Context Extraction Service + */ +export interface ISmartContextExtractor { + /** + * Extract relevant context using vector search + */ + extractRelevantContextWithVector(query: string, currentFilePath?: string): Promise; + + /** + * Extract traditional context as fallback + */ + extractTraditionalContext(query: string, currentFilePath?: string): Promise; + + /** + * Configure extraction parameters + */ + configure(options: ContextExtractionOptions): void; + + /** + * Dispose of resources + */ + dispose(): void; +} + +/** + * Context extraction result + */ +export interface ContextResult { + content: string; + sources: Array<{ + filePath: string; + relevanceScore: number; + clickableReference: string; + }>; + totalTokens: number; + searchMethod: "vector" | "keyword" | "hybrid" | "fallback"; + metrics?: { + searchTime: number; + processingTime: number; + filesScanned: number; + }; +} + +/** + * Context extraction options + */ +export interface ContextExtractionOptions { + maxFiles?: number; + maxTokens?: number; + includeContent?: boolean; + searchMethod?: "vector" | "keyword" | "hybrid"; + enableFallback?: boolean; +} + +/** + * Interface for User Feedback Service + */ +export interface IUserFeedbackService { + /** + * Update status bar with information + */ + updateStatus(status: { text: string; tooltip: string }): void; + + /** + * Show success message + */ + showSuccess(message: string, actions?: string[]): Thenable; + + /** + * Show warning message + */ + showWarning(message: string, actions?: string[]): Thenable; + + /** + * Show error message + */ + showError(message: string, actions?: string[]): Thenable; + + /** + * Show sync status + */ + showSyncStatus(filesQueued: number, processing?: boolean): void; + + /** + * Show search metrics + */ + showSearchMetrics(resultsCount: number, searchTime: number): void; + + /** + * Check if vector database is enabled + */ + isVectorDbEnabled(): boolean; + + /** + * Get progress notification preference + */ + getProgressNotificationPreference(): vscode.ProgressLocation; + + /** + * Dispose of resources + */ + dispose(): void; +} + +/** + * Interface for Configuration Management + */ +export interface IConfigurationManager { + /** + * Get current configuration + */ + getConfig(): T; + + /** + * Update a configuration value + */ + updateConfig(key: K, value: T[K], target?: vscode.ConfigurationTarget): Promise; + + /** + * Validate current configuration + */ + validateConfiguration(): boolean; + + /** + * Reset configuration to defaults + */ + resetToDefaults(target?: vscode.ConfigurationTarget): Promise; + + /** + * Export configuration as JSON + */ + exportConfiguration(): string; + + /** + * Import configuration from JSON + */ + importConfiguration(configJson: string, target?: vscode.ConfigurationTarget): Promise; + + /** + * Listen for configuration changes + */ + onConfigChange(listener: (config: T) => void): vscode.Disposable; + + /** + * Dispose of resources + */ + dispose(): void; +} + +/** + * Dependency injection container interface + */ +export interface IDependencyContainer { + /** + * Register a service with the container + */ + register(token: string, factory: () => T): void; + + /** + * Register a singleton service + */ + registerSingleton(token: string, factory: () => T): void; + + /** + * Resolve a service from the container + */ + resolve(token: string): T; + + /** + * Check if a service is registered + */ + isRegistered(token: string): boolean; + + /** + * Dispose of all services + */ + dispose(): void; +} diff --git a/src/interfaces/vector-db.interface.ts b/src/interfaces/vector-db.interface.ts new file mode 100644 index 0000000..ead2af6 --- /dev/null +++ b/src/interfaces/vector-db.interface.ts @@ -0,0 +1,281 @@ +import * as vscode from "vscode"; + +/** + * Interface for code indexing and embedding generation + */ +export interface ICodeIndexer { + /** + * Generate embeddings for code content + */ + generateEmbeddings(content: string, metadata?: any): Promise; + + /** + * Index a single file + */ + indexFile(filePath: string, content: string): Promise; + + /** + * Index multiple files in batch + */ + indexFiles(files: Array<{ path: string; content: string }>): Promise; + + /** + * Remove a file from the index + */ + removeFromIndex(filePath: string): Promise; + + /** + * Update an existing file in the index + */ + updateFileIndex(filePath: string, content: string): Promise; + + /** + * Search for similar code based on query + */ + searchSimilar(query: string, options?: SearchOptions): Promise; + + /** + * Get indexing statistics + */ + getIndexStats(): Promise; + + /** + * Check if a file is indexed + */ + isFileIndexed(filePath: string): Promise; + + /** + * Clear all indexed data + */ + clearIndex(): Promise; + + /** + * Dispose of resources + */ + dispose(): void; +} + +/** + * Search options for similarity search + */ +export interface SearchOptions { + /** Maximum number of results to return */ + limit?: number; + /** Minimum similarity threshold */ + threshold?: number; + /** File types to include in search */ + fileTypes?: string[]; + /** Whether to include file content in results */ + includeContent?: boolean; +} + +/** + * Search result from similarity search + */ +export interface SearchResult { + /** File path */ + filePath: string; + /** Similarity score (0-1) */ + similarity: number; + /** File content (if requested) */ + content?: string; + /** Metadata about the match */ + metadata?: { + lineStart?: number; + lineEnd?: number; + functionName?: string; + className?: string; + }; +} + +/** + * Indexing statistics + */ +export interface IndexStats { + /** Total number of indexed files */ + totalFiles: number; + /** Total number of code chunks indexed */ + totalChunks: number; + /** Size of index in bytes */ + indexSize: number; + /** Last update timestamp */ + lastUpdated: Date; + /** Status of the index */ + status: "ready" | "indexing" | "error"; +} + +/** + * Interface for vector database synchronization + */ +export interface IVectorDbSync { + /** + * Initialize the sync service + */ + initialize(): Promise; + + /** + * Start monitoring for file changes + */ + startMonitoring(): Promise; + + /** + * Stop monitoring for file changes + */ + stopMonitoring(): void; + + /** + * Perform a full reindex of the workspace + */ + performFullReindex(): Promise; + + /** + * Get synchronization statistics + */ + getStats(): SyncStats; + + /** + * Check if sync is active + */ + isActive(): boolean; + + /** + * Dispose of resources + */ + dispose(): void; +} + +/** + * Synchronization statistics + */ +export interface SyncStats { + /** Number of files being monitored */ + filesMonitored: number; + /** Number of sync operations performed */ + syncOperations: number; + /** Number of failed operations */ + failedOperations: number; + /** Current queue size */ + queueSize: number; + /** Last sync timestamp */ + lastSync?: string; + /** Sync status */ + status: "idle" | "syncing" | "error"; +} + +/** + * Interface for smart context extraction + */ +export interface ISmartContextExtractor { + /** + * Extract relevant context using vector search + */ + extractRelevantContextWithVector(query: string, currentFilePath?: string): Promise; + + /** + * Extract context using traditional methods as fallback + */ + extractTraditionalContext(query: string, currentFilePath?: string): Promise; + + /** + * Configure extraction options + */ + configure(options: ContextExtractionOptions): void; + + /** + * Dispose of resources + */ + dispose(): void; +} + +/** + * Options for context extraction + */ +export interface ContextExtractionOptions { + /** Maximum number of files to include */ + maxFiles?: number; + /** Maximum tokens per context */ + maxTokens?: number; + /** Whether to include file content */ + includeContent?: boolean; + /** Search method preference */ + searchMethod?: "vector" | "keyword" | "hybrid"; +} + +/** + * Result from smart context extraction + */ +export interface SmartContextResult { + /** Extracted context content */ + content: string; + /** Source files used */ + sources: Array<{ + filePath: string; + relevanceScore: number; + clickableReference: string; + }>; + /** Total tokens in the result */ + totalTokens: number; + /** Search method used */ + searchMethod: "vector" | "keyword" | "hybrid" | "fallback"; + /** Performance metrics */ + metrics?: { + searchTime: number; + processingTime: number; + filesScanned: number; + }; +} + +/** + * Interface for embedding orchestration + */ +export interface IEmbeddingOrchestrator { + /** + * Initialize the orchestrator + */ + initialize(): Promise; + + /** + * Start orchestration process + */ + start(): Promise; + + /** + * Stop orchestration process + */ + stop(): Promise; + + /** + * Get orchestration status + */ + getStatus(): OrchestrationStatus; + + /** + * Force immediate embedding of high-priority files + */ + forceImmediateEmbedding(filePaths: string[]): Promise; + + /** + * Dispose of resources + */ + dispose(): void; +} + +/** + * Orchestration status + */ +export interface OrchestrationStatus { + /** Current phase */ + currentPhase: "idle" | "immediate" | "background" | "on-demand"; + /** Files in queue */ + queueSize: number; + /** Processing status */ + isProcessing: boolean; + /** Last activity timestamp */ + lastActivity?: Date; + /** Performance metrics */ + metrics: { + filesProcessed: number; + averageProcessingTime: number; + errorCount: number; + }; +} diff --git a/src/services/user-feedback.service.ts b/src/services/user-feedback.service.ts new file mode 100644 index 0000000..acc3b06 --- /dev/null +++ b/src/services/user-feedback.service.ts @@ -0,0 +1,464 @@ +import * as vscode from "vscode"; +import { Logger, LogLevel } from "../infrastructure/logger/logger"; + +export interface UserFeedbackOptions { + enableStatusBar?: boolean; + enableProgressNotifications?: boolean; + enableToastNotifications?: boolean; + progressLocation?: vscode.ProgressLocation; +} + +export interface ProgressInfo { + operation: string; + progress: number; + message?: string; + increment?: number; +} + +export interface StatusInfo { + text: string; + tooltip?: string; + backgroundColor?: vscode.ThemeColor; + command?: string; +} + +/** + * UserFeedbackService manages all user feedback for the vector database system + * including status bar indicators, progress notifications, and settings integration + */ +export class UserFeedbackService implements vscode.Disposable { + private logger: Logger; + private statusBarItem: vscode.StatusBarItem; + private progressTokens: Map = new Map(); + private disposables: vscode.Disposable[] = []; + private readonly options: Required; + + constructor(options: UserFeedbackOptions = {}) { + this.logger = Logger.initialize("UserFeedbackService", { + minLevel: LogLevel.INFO, + }); + + this.options = { + enableStatusBar: options.enableStatusBar ?? true, + enableProgressNotifications: options.enableProgressNotifications ?? true, + enableToastNotifications: options.enableToastNotifications ?? true, + progressLocation: options.progressLocation ?? vscode.ProgressLocation.Notification, + }; + + // Create status bar item + this.statusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, 100); + this.statusBarItem.command = "codebuddy.vectorDb.showStats"; + this.disposables.push(this.statusBarItem); + + this.initializeStatusBar(); + } + + /** + * Initialize the status bar with default state + */ + private initializeStatusBar(): void { + if (!this.options.enableStatusBar) { + return; + } + + this.updateStatus({ + text: "$(database) CodeBuddy Vector DB", + tooltip: "CodeBuddy Vector Database - Click for statistics", + }); + } + + /** + * Update status bar with current information + */ + updateStatus(status: StatusInfo): void { + if (!this.options.enableStatusBar) { + return; + } + + this.statusBarItem.text = status.text; + this.statusBarItem.tooltip = status.tooltip || "CodeBuddy Vector Database"; + this.statusBarItem.backgroundColor = status.backgroundColor; + + if (status.command) { + this.statusBarItem.command = status.command; + } + + this.statusBarItem.show(); + } + + /** + * Show initialization progress + */ + async showInitializationProgress(phases: Array<{ name: string; action: () => Promise }>): Promise { + if (!this.options.enableProgressNotifications) { + // Still update status bar + this.updateStatus({ + text: "$(loading~spin) CodeBuddy: Initializing...", + tooltip: "Vector database is initializing", + backgroundColor: new vscode.ThemeColor("statusBarItem.warningBackground"), + }); + + // Execute phases without progress UI + for (const phase of phases) { + await phase.action(); + } + return; + } + + await vscode.window.withProgress( + { + location: this.options.progressLocation, + title: "CodeBuddy Vector Database", + cancellable: false, + }, + async (progress) => { + const totalPhases = phases.length; + const increment = 100 / totalPhases; + + for (let i = 0; i < phases.length; i++) { + const phase = phases[i]; + + progress.report({ + increment: i === 0 ? 0 : increment, + message: `${phase.name}...`, + }); + + this.updateStatus({ + text: `$(loading~spin) CodeBuddy: ${phase.name}`, + tooltip: `Initializing: ${phase.name}`, + backgroundColor: new vscode.ThemeColor("statusBarItem.warningBackground"), + }); + + try { + await phase.action(); + } catch (error) { + this.logger.error(`Phase ${phase.name} failed:`, error); + throw error; + } + } + + progress.report({ + increment: increment, + message: "Complete!", + }); + } + ); + } + + /** + * Show embedding progress for batch operations + */ + async showEmbeddingProgress( + operationId: string, + totalFiles: number, + onProgress: (progress: ProgressInfo) => Promise + ): Promise { + if (!this.options.enableProgressNotifications) { + return; + } + + const tokenSource = new vscode.CancellationTokenSource(); + this.progressTokens.set(operationId, tokenSource); + + try { + await vscode.window.withProgress( + { + location: this.options.progressLocation, + title: "CodeBuddy Embedding", + cancellable: true, + }, + async (progress, token) => { + token.onCancellationRequested(() => { + tokenSource.cancel(); + }); + + let processedFiles = 0; + + // Set up progress callback + const progressCallback = async (info: ProgressInfo) => { + if (token.isCancellationRequested) { + return; + } + + processedFiles++; + const percentage = Math.round((processedFiles / totalFiles) * 100); + + progress.report({ + increment: info.increment || 100 / totalFiles, + message: `${info.message || info.operation} (${processedFiles}/${totalFiles})`, + }); + + this.updateStatus({ + text: `$(sync~spin) CodeBuddy: ${percentage}%`, + tooltip: `Embedding progress: ${processedFiles}/${totalFiles} files`, + backgroundColor: new vscode.ThemeColor("statusBarItem.warningBackground"), + }); + }; + + await onProgress({ operation: "embedding", progress: 0 }); + } + ); + } finally { + this.progressTokens.delete(operationId); + tokenSource.dispose(); + } + } + + /** + * Show quick status message in status bar + */ + showStatusMessage(message: string, timeout: number = 3000): void { + if (!this.options.enableStatusBar) { + return; + } + + const originalText = this.statusBarItem.text; + const originalTooltip = this.statusBarItem.tooltip; + const originalBackground = this.statusBarItem.backgroundColor; + + this.statusBarItem.text = message; + this.statusBarItem.tooltip = message; + + setTimeout(() => { + this.statusBarItem.text = originalText; + this.statusBarItem.tooltip = originalTooltip; + this.statusBarItem.backgroundColor = originalBackground; + }, timeout); + } + + /** + * Show success notification + */ + showSuccess(message: string, actions?: string[]): Thenable { + if (!this.options.enableToastNotifications) { + this.logger.info(`Success: ${message}`); + return Promise.resolve(undefined); + } + + this.updateStatus({ + text: "$(check) CodeBuddy: Ready", + tooltip: message, + backgroundColor: undefined, + }); + + return vscode.window.showInformationMessage(message, ...(actions || [])); + } + + /** + * Show warning notification + */ + showWarning(message: string, actions?: string[]): Thenable { + if (!this.options.enableToastNotifications) { + this.logger.warn(message); + return Promise.resolve(undefined); + } + + this.updateStatus({ + text: "$(warning) CodeBuddy: Warning", + tooltip: message, + backgroundColor: new vscode.ThemeColor("statusBarItem.warningBackground"), + }); + + return vscode.window.showWarningMessage(message, ...(actions || [])); + } + + /** + * Show error notification + */ + showError(message: string, actions?: string[]): Thenable { + if (!this.options.enableToastNotifications) { + this.logger.error(message); + return Promise.resolve(undefined); + } + + this.updateStatus({ + text: "$(error) CodeBuddy: Error", + tooltip: message, + backgroundColor: new vscode.ThemeColor("statusBarItem.errorBackground"), + }); + + return vscode.window.showErrorMessage(message, ...(actions || [])); + } + + /** + * Show sync status for file operations + */ + showSyncStatus(filesQueued: number, processing: boolean = false): void { + if (!this.options.enableStatusBar) { + return; + } + + if (processing) { + this.updateStatus({ + text: `$(sync~spin) CodeBuddy: Processing ${filesQueued} files`, + tooltip: `Vector database sync in progress: ${filesQueued} files queued`, + backgroundColor: new vscode.ThemeColor("statusBarItem.warningBackground"), + }); + } else if (filesQueued > 0) { + this.updateStatus({ + text: `$(sync) CodeBuddy: ${filesQueued} queued`, + tooltip: `${filesQueued} files queued for vector database sync`, + backgroundColor: new vscode.ThemeColor("statusBarItem.warningBackground"), + }); + } else { + this.updateStatus({ + text: "$(check) CodeBuddy: Synced", + tooltip: "Vector database is up to date", + backgroundColor: undefined, + }); + } + } + + /** + * Show search performance metrics + */ + showSearchMetrics(resultsCount: number, searchTime: number): void { + const message = `Found ${resultsCount} results in ${searchTime}ms`; + + this.showStatusMessage(`$(search) ${message}`, 2000); + + // Configurable threshold for slow search warning + const slowSearchThreshold = vscode.workspace + .getConfiguration("codebuddy.vectorDb") + .get("slowSearchThreshold", 2000); // Default 2 seconds + + if (this.options.enableToastNotifications && searchTime > slowSearchThreshold) { + this.showWarning(`Search took ${searchTime}ms - consider reindexing for better performance`); + } + } + + /** + * Cancel a specific progress operation + */ + cancelProgress(operationId: string): void { + const tokenSource = this.progressTokens.get(operationId); + if (tokenSource) { + tokenSource.cancel(); + this.progressTokens.delete(operationId); + } + } + + /** + * Cancel all progress operations + */ + cancelAllProgress(): void { + for (const [operationId, tokenSource] of this.progressTokens) { + tokenSource.cancel(); + } + this.progressTokens.clear(); + } + + /** + * Check if user has enabled vector database features in settings + */ + isVectorDbEnabled(): boolean { + return vscode.workspace.getConfiguration("codebuddy").get("vectorDb.enabled", true); + } + + /** + * Get user preference for progress notifications + */ + getProgressNotificationPreference(): vscode.ProgressLocation { + const preference = vscode.workspace + .getConfiguration("codebuddy") + .get("vectorDb.progressLocation", "notification") as string; + + if (preference === "statusBar") { + return vscode.ProgressLocation.Window; + } else if (preference === "notification") { + return vscode.ProgressLocation.Notification; + } else { + return vscode.ProgressLocation.Notification; + } + } + + /** + * Get user preference for embedding batch size + */ + getEmbeddingBatchSize(): number { + return vscode.workspace.getConfiguration("codebuddy").get("vectorDb.batchSize", 10); + } + + /** + * Check if background processing is enabled + */ + isBackgroundProcessingEnabled(): boolean { + return vscode.workspace.getConfiguration("codebuddy").get("vectorDb.backgroundProcessing", true); + } + + /** + * Register configuration change listeners + */ + registerConfigurationListeners(): vscode.Disposable { + return vscode.workspace.onDidChangeConfiguration((event) => { + if (event.affectsConfiguration("codebuddy.vectorDb")) { + this.logger.info("Vector database configuration changed"); + + // Update options based on new configuration + this.options.enableProgressNotifications = vscode.workspace + .getConfiguration("codebuddy") + .get("vectorDb.showProgress", true); + + this.options.progressLocation = this.getProgressNotificationPreference(); + + // Show notification about configuration change + if (this.options.enableToastNotifications) { + this.showSuccess("Vector database settings updated"); + } + } + }); + } + + /** + * Show vector database settings panel + */ + async showSettingsPanel(): Promise { + const options = [ + "Enable Vector Database", + "Configure Progress Notifications", + "Set Batch Size", + "Toggle Background Processing", + "View Statistics", + "Force Reindex", + ]; + + const choice = await vscode.window.showQuickPick(options, { + placeHolder: "Select a vector database setting to configure", + }); + + switch (choice) { + case "Enable Vector Database": + await vscode.commands.executeCommand("workbench.action.openSettings", "codebuddy.vectorDb.enabled"); + break; + case "Configure Progress Notifications": + await vscode.commands.executeCommand("workbench.action.openSettings", "codebuddy.vectorDb.progressLocation"); + break; + case "Set Batch Size": + await vscode.commands.executeCommand("workbench.action.openSettings", "codebuddy.vectorDb.batchSize"); + break; + case "Toggle Background Processing": + await vscode.commands.executeCommand( + "workbench.action.openSettings", + "codebuddy.vectorDb.backgroundProcessing" + ); + break; + case "View Statistics": + await vscode.commands.executeCommand("codebuddy.vectorDb.showStats"); + break; + case "Force Reindex": + await vscode.commands.executeCommand("codebuddy.vectorDb.forceReindex"); + break; + } + } + + /** + * Dispose of all resources + */ + dispose(): void { + this.logger.info("Disposing User Feedback Service"); + + this.cancelAllProgress(); + this.disposables.forEach((d) => d.dispose()); + this.disposables.length = 0; + } +} diff --git a/src/test/suite/phase4-integration.test.ts b/src/test/suite/phase4-integration.test.ts new file mode 100644 index 0000000..a43ddbb --- /dev/null +++ b/src/test/suite/phase4-integration.test.ts @@ -0,0 +1,203 @@ +import * as assert from "assert"; +import * as vscode from "vscode"; +import { VectorDbConfigurationManager } from "../../config/vector-db.config"; +import { UserFeedbackService } from "../../services/user-feedback.service"; + +suite("Phase 4 Integration Tests", () => { + let configManager: VectorDbConfigurationManager; + let userFeedback: UserFeedbackService; + + setup(() => { + configManager = new VectorDbConfigurationManager(); + userFeedback = new UserFeedbackService(); + }); + + teardown(() => { + configManager?.dispose(); + userFeedback?.dispose(); + }); + + suite("VectorDbConfigurationManager", () => { + test("should initialize with default configuration", () => { + const config = configManager.getConfig(); + + assert.strictEqual(config.enabled, true); + assert.strictEqual(config.embeddingModel, "gemini"); + assert.strictEqual(config.maxTokens, 6000); + assert.strictEqual(config.batchSize, 10); + assert.strictEqual(config.searchResultLimit, 8); + assert.strictEqual(config.enableBackgroundProcessing, true); + assert.strictEqual(config.enableProgressNotifications, true); + assert.strictEqual(config.progressLocation, "notification"); + assert.strictEqual(config.debounceDelay, 1000); + assert.strictEqual(config.performanceMode, "balanced"); + assert.strictEqual(config.fallbackToKeywordSearch, true); + assert.strictEqual(config.cacheEnabled, true); + assert.strictEqual(config.logLevel, "info"); + }); + + test("should provide performance thresholds", () => { + const thresholds = configManager.getPerformanceThresholds(); + + assert.ok(thresholds.maxEmbeddingTime > 0); + assert.ok(thresholds.maxSearchTime > 0); + assert.ok(thresholds.maxMemoryUsage > 0); + assert.ok(thresholds.maxFileSize > 0); + assert.ok(thresholds.maxConcurrentOperations > 0); + }); + + test("should provide feature flags", () => { + const flags = configManager.getFeatureFlags(); + + assert.strictEqual(typeof flags.enableVectorSearch, "boolean"); + assert.strictEqual(typeof flags.enableSemanticSimilarity, "boolean"); + assert.strictEqual(typeof flags.enableSmartRanking, "boolean"); + assert.strictEqual(typeof flags.enableRealtimeSync, "boolean"); + assert.strictEqual(typeof flags.enableBulkOperations, "boolean"); + assert.strictEqual(typeof flags.enableAnalytics, "boolean"); + }); + + test("should validate configuration", () => { + const isValid = configManager.validateConfiguration(); + assert.strictEqual(typeof isValid, "boolean"); + }); + + test("should handle configuration changes", async () => { + let changeNotified = false; + const disposable = configManager.onConfigChange(() => { + changeNotified = true; + }); + + await configManager.updateConfig("batchSize", 15); + const config = configManager.getConfig(); + + assert.strictEqual(config.batchSize, 15); + disposable.dispose(); + }); + + test("should export and import configuration", async () => { + const exported = configManager.exportConfiguration(); + assert.ok(exported.length > 0); + + const config = JSON.parse(exported); + assert.strictEqual(typeof config, "object"); + assert.ok("enabled" in config); + assert.ok("embeddingModel" in config); + }); + + test("should auto-tune configuration", async () => { + // This will run auto-tune which analyzes the workspace + // Since we're in test environment, it should handle empty workspace gracefully + await configManager.autoTuneConfiguration(); + + const config = configManager.getConfig(); + assert.ok(config); + }); + }); + + suite("UserFeedbackService", () => { + test("should initialize properly", () => { + assert.ok(userFeedback); + }); + + test("should handle status updates", () => { + userFeedback.updateStatus({ + text: "$(sync~spin) Processing...", + tooltip: "Vector database processing", + }); + + userFeedback.updateStatus({ + text: "$(check) Ready", + tooltip: "Vector database ready", + }); + + // Test passed if no errors thrown + assert.ok(true); + }); + + test("should handle notifications", async () => { + await userFeedback.showSuccess("Test success message"); + await userFeedback.showWarning("Test warning message"); + await userFeedback.showError("Test error message"); + + // Test passed if no errors thrown + assert.ok(true); + }); + + test("should handle sync status", () => { + userFeedback.showSyncStatus(5, true); + userFeedback.showSyncStatus(0, false); + + // Test passed if no errors thrown + assert.ok(true); + }); + + test("should handle search metrics", () => { + userFeedback.showSearchMetrics(10, 250); + + // Test passed if no errors thrown + assert.ok(true); + }); + + test("should handle configuration preferences", () => { + const isEnabled = userFeedback.isVectorDbEnabled(); + assert.strictEqual(typeof isEnabled, "boolean"); + + const progressLocation = userFeedback.getProgressNotificationPreference(); + assert.ok(progressLocation !== undefined); + + const batchSize = userFeedback.getEmbeddingBatchSize(); + assert.strictEqual(typeof batchSize, "number"); + assert.ok(batchSize > 0); + + const backgroundProcessing = userFeedback.isBackgroundProcessingEnabled(); + assert.strictEqual(typeof backgroundProcessing, "boolean"); + }); + }); + + suite("Integration", () => { + test("should integrate configuration manager with user feedback", async () => { + // Test that configuration changes can trigger user feedback + const config = configManager.getConfig(); + + if (config.enableProgressNotifications) { + userFeedback.showSyncStatus(1, true); + userFeedback.showSyncStatus(0, false); + } + + // Test passed if no errors thrown + assert.ok(true); + }); + + test("should handle performance mode changes", async () => { + const originalMode = configManager.getConfig().performanceMode; + + // Test switching performance modes + const modes = ["balanced", "performance", "memory"] as const; + for (const mode of modes) { + await configManager.updateConfig("performanceMode", mode); + const thresholds = configManager.getPerformanceThresholds(); + assert.ok(thresholds); + + const config = configManager.getConfig(); + assert.strictEqual(config.performanceMode, mode); + } + + // Restore original mode + await configManager.updateConfig("performanceMode", originalMode); + }); + + test("should handle error scenarios gracefully", async () => { + // Test invalid configuration values + try { + await configManager.updateConfig("batchSize", -1); + const isValid = configManager.validateConfiguration(); + // Should still work but report validation issues + assert.strictEqual(typeof isValid, "boolean"); + } catch (error) { + // Error handling is acceptable + assert.ok(error instanceof Error); + } + }); + }); +}); diff --git a/src/webview-providers/base.ts b/src/webview-providers/base.ts index 473515a..a97d2bc 100644 --- a/src/webview-providers/base.ts +++ b/src/webview-providers/base.ts @@ -16,9 +16,12 @@ import { formatText, getAPIKeyAndModel, getGenerativeAiModel } from "../utils/ut import { getWebviewContent } from "../webview/chat"; import { VectorDatabaseService } from "../services/vector-database.service"; import { VectorDbWorkerManager } from "../services/vector-db-worker-manager"; +import { VectorDbSyncService } from "../services/vector-db-sync.service"; import { SmartContextExtractor } from "../services/smart-context-extractor"; import { SmartEmbeddingOrchestrator } from "../services/smart-embedding-orchestrator"; import { ContextRetriever } from "../services/context-retriever"; +import { UserFeedbackService } from "../services/user-feedback.service"; +import { VectorDbConfigurationManager } from "../config/vector-db.config"; let _view: vscode.WebviewView | undefined; export abstract class BaseWebViewProvider implements vscode.Disposable { @@ -38,11 +41,16 @@ export abstract class BaseWebViewProvider implements vscode.Disposable { private readonly codebaseUnderstanding: CodebaseUnderstandingService; private readonly inputValidator: InputValidator; - // Vector database components - protected readonly vectorDbWorkerManager: VectorDbWorkerManager; - protected readonly vectorDatabaseService: VectorDatabaseService; - protected readonly smartContextExtractor: SmartContextExtractor; - protected readonly smartEmbeddingOrchestrator: SmartEmbeddingOrchestrator; + // Vector database services + protected vectorDbService?: VectorDatabaseService; + protected vectorDbSyncService?: VectorDbSyncService; + protected vectorWorkerManager?: VectorDbWorkerManager; + protected smartContextExtractor?: SmartContextExtractor; + protected smartEmbeddingOrchestrator?: SmartEmbeddingOrchestrator; + protected contextRetriever?: ContextRetriever; + protected userFeedbackService?: UserFeedbackService; + protected configManager?: VectorDbConfigurationManager; + protected codeIndexingService?: any; // CodeIndexingService - will be properly typed when available constructor( private readonly _extensionUri: vscode.Uri, @@ -64,45 +72,106 @@ export abstract class BaseWebViewProvider implements vscode.Disposable { this.codebaseUnderstanding = CodebaseUnderstandingService.getInstance(); this.inputValidator = InputValidator.getInstance(); - // Initialize vector database components + // Initialize vector database components with Phase 4 orchestration const { apiKey: geminiApiKey } = getAPIKeyAndModel("Gemini"); - this.vectorDbWorkerManager = new VectorDbWorkerManager(context); - this.vectorDatabaseService = new VectorDatabaseService(context, geminiApiKey); + + // Initialize configuration manager first + this.configManager = new VectorDbConfigurationManager(); + + // Initialize user feedback service + this.userFeedbackService = new UserFeedbackService(); + + // Initialize vector services + this.vectorWorkerManager = new VectorDbWorkerManager(context); + this.vectorDbService = new VectorDatabaseService(context, geminiApiKey); + + // Initialize code indexing service (temporary stub) + this.codeIndexingService = this.vectorWorkerManager; // Use worker manager as code indexer for now + this.smartEmbeddingOrchestrator = new SmartEmbeddingOrchestrator( + context, + this.vectorDbService, + this.vectorWorkerManager + ); + this.vectorDbSyncService = new VectorDbSyncService(this.vectorDbService, this.codeIndexingService); this.smartContextExtractor = new SmartContextExtractor( - this.vectorDatabaseService, - undefined, // contextRetriever will be set later if needed + this.vectorDbService, + this.contextRetriever, this.codebaseUnderstanding, this.questionClassifier ); - this.smartEmbeddingOrchestrator = new SmartEmbeddingOrchestrator( - context, - this.vectorDatabaseService, - this.vectorDbWorkerManager - ); + this.contextRetriever = new ContextRetriever(); // Don't register disposables here - do it lazily when webview is resolved } /** - * Initialize vector database components for enhanced context extraction + * Initialize vector database components with Phase 4 orchestration */ protected async initializeVectorComponents(): Promise { try { - this.logger.info("Initializing vector database components..."); + this.logger.info("Starting Phase 4 vector database orchestration..."); + + // Phase 4.1: Initialize worker manager (non-blocking architecture) + await this.vectorWorkerManager?.initialize(); + this.logger.info("โœ“ Vector database worker manager initialized"); - // Initialize the vector database worker manager - await this.vectorDbWorkerManager.initialize(); + // Phase 4.2: Initialize vector database service + await this.vectorDbService?.initialize(); + this.logger.info("โœ“ Vector database service initialized"); - // Initialize the vector database service - await this.vectorDatabaseService.initialize(); + // Phase 4.3: Start smart embedding orchestrator (multi-phase strategy) + await this.smartEmbeddingOrchestrator?.initialize(); + this.logger.info("โœ“ Smart embedding orchestrator started"); - // Start the smart embedding orchestrator for background processing - await this.smartEmbeddingOrchestrator.initialize(); + // Phase 4.4: Initialize sync service for real-time file monitoring + await this.vectorDbSyncService?.initialize(); + this.logger.info("โœ“ Vector database sync service initialized"); - this.logger.info("Vector database components initialized successfully"); + // Phase 4.5: Trigger immediate embedding phase for essential files + await this.executeImmediateEmbeddingPhase(); + this.logger.info("โœ“ Immediate embedding phase completed"); + + this.logger.info("๐Ÿš€ Phase 4 vector database orchestration completed successfully"); } catch (error) { - this.logger.error("Failed to initialize vector database components", error); - // Don't throw - continue with fallback functionality + this.logger.error("Failed to initialize Phase 4 orchestration:", error); + // Continue with graceful degradation + await this.handleVectorInitializationError(error); + } + } + + /** + * Execute immediate embedding phase for essential files + */ + private async executeImmediateEmbeddingPhase(): Promise { + try { + // The orchestrator's initialize method handles the immediate phase internally + // No need to call it separately - it's already handled in the orchestrator initialization + + // Show user feedback + vscode.window.setStatusBarMessage("$(check) CodeBuddy: Essential files indexed and ready", 5000); + } catch (error) { + this.logger.warn("Immediate embedding phase failed, continuing with fallback:", error); + } + } + + /** + * Handle vector database initialization errors gracefully + */ + private async handleVectorInitializationError(error: any): Promise { + this.logger.warn("Vector database initialization failed, enabling fallback mode"); + + // Show user notification + const action = await vscode.window.showWarningMessage( + "Vector database initialization failed. CodeBuddy will use keyword-based search as fallback.", + "Retry", + "Continue" + ); + + if (action === "Retry") { + // Retry initialization after a delay + setTimeout(() => { + this.initializeVectorComponents(); + }, 10000); } } @@ -419,12 +488,12 @@ export abstract class BaseWebViewProvider implements vscode.Disposable { let fallbackContext = ""; try { - const vectorResult = await this.smartContextExtractor.extractRelevantContextWithVector( + const vectorResult = await this.smartContextExtractor?.extractRelevantContextWithVector( message, vscode.window.activeTextEditor?.document.fileName ); - if (vectorResult.content && vectorResult.sources.length > 0) { + if (vectorResult?.content && vectorResult.sources.length > 0) { vectorContext = `\n**Semantic Context** (${vectorResult.searchMethod} search results):\n${vectorResult.sources .map( (source) => @@ -475,7 +544,7 @@ IMPORTANT: Please provide a complete response. Do not truncate your answer mid-s // Dispose vector database components try { this.smartEmbeddingOrchestrator?.dispose(); - this.vectorDbWorkerManager?.dispose(); + this.vectorWorkerManager?.dispose(); } catch (error) { this.logger.error("Error disposing vector database components", error); }