UNPKG

giga-code

Version:

A personal AI CLI assistant powered by Grok for local development.

342 lines 14.2 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.RAGService = void 0; const generative_ai_1 = require("@google/generative-ai"); const fs = __importStar(require("fs")); const path = __importStar(require("path")); const crypto = __importStar(require("crypto")); class RAGService { constructor(projectPath, config) { this.genAI = null; this.vectorIndex = null; this.dbPath = path.join(projectPath, '.giga', 'embeddings'); this.indexPath = path.join(this.dbPath, 'vectors.json'); // Default configuration this.config = { enabled: true, embeddingProvider: 'gemini', embeddingModel: 'gemini-embedding-001', searchThreshold: 0.20, maxResults: 5, chunkingStrategy: 'logical', includePatterns: ['**/*.ts', '**/*.js', '**/*.tsx', '**/*.jsx', '**/*.py', '**/*.java', '**/*.cpp', '**/*.c', '**/*.h'], excludePatterns: ['**/node_modules/**', '**/dist/**', '**/build/**', '**/.git/**', '**/coverage/**'], ...config }; // Initialize Gemini AI if using Gemini embeddings if (this.config.embeddingProvider === 'gemini') { // Try to load API key from various sources let apiKey = process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY; // If not found in env, try loading from settings if (!apiKey) { try { const { loadApiKeys } = require('../utils/api-keys'); const apiKeys = loadApiKeys(); apiKey = apiKeys.googleApiKey; } catch (error) { console.warn('Failed to load API keys:', error); } } if (apiKey) { this.genAI = new generative_ai_1.GoogleGenerativeAI(apiKey); } else { console.warn('Google API key not found. RAG embeddings require a Google API key.'); } } } async initialize() { try { // Ensure database directory exists if (!fs.existsSync(this.dbPath)) { fs.mkdirSync(this.dbPath, { recursive: true }); } // Load existing index or create new one await this.loadIndex(); } catch (error) { console.error('Failed to initialize RAG service:', error); throw error; } } async loadIndex() { if (fs.existsSync(this.indexPath)) { try { const indexData = fs.readFileSync(this.indexPath, 'utf-8'); this.vectorIndex = JSON.parse(indexData); } catch (error) { console.warn('Failed to load existing index, creating new one:', error); this.createEmptyIndex(); } } else { this.createEmptyIndex(); } } createEmptyIndex() { this.vectorIndex = { documents: [], metadata: { count: 0, lastUpdated: new Date().toISOString(), version: '1.0.0' } }; } async saveIndex() { if (!this.vectorIndex) return; this.vectorIndex.metadata.lastUpdated = new Date().toISOString(); this.vectorIndex.metadata.count = this.vectorIndex.documents.length; fs.writeFileSync(this.indexPath, JSON.stringify(this.vectorIndex, null, 2)); } truncateText(text, maxChars = 25000) { if (text.length <= maxChars) { return text; } return text.substring(0, maxChars) + '...'; } async generateEmbedding(text) { if (this.config.embeddingProvider === 'gemini' && this.genAI) { try { const model = this.genAI.getGenerativeModel({ model: this.config.embeddingModel }); const result = await model.embedContent(this.truncateText(text)); return result.embedding.values; } catch (error) { console.error('Failed to generate Gemini embedding:', error); // Fall back to simple embedding return this.createSimpleEmbedding(text); } } else { // Fall back to simple embedding if no API key or different provider return this.createSimpleEmbedding(text); } } createSimpleEmbedding(text) { // Create a simple feature vector with same dimensions as Gemini (3072) const words = text.toLowerCase().split(/\s+/); const embedding = new Array(3072).fill(0); // Match Gemini dimension size // Basic text features embedding[0] = Math.min(words.length / 100, 1); // Document length (normalized) embedding[1] = Math.min((text.match(/function/g) || []).length / 10, 1); // Function count embedding[2] = Math.min((text.match(/class/g) || []).length / 10, 1); // Class count embedding[3] = Math.min((text.match(/import/g) || []).length / 10, 1); // Import count embedding[4] = Math.min((text.match(/export/g) || []).length / 10, 1); // Export count embedding[5] = Math.min((text.match(/interface/g) || []).length / 10, 1); // Interface count embedding[6] = Math.min((text.match(/type/g) || []).length / 10, 1); // Type count embedding[7] = Math.min((text.match(/const/g) || []).length / 10, 1); // Const count embedding[8] = Math.min((text.match(/let/g) || []).length / 10, 1); // Let count embedding[9] = Math.min((text.match(/var/g) || []).length / 10, 1); // Var count // Character-based features for better diversity for (let i = 10; i < embedding.length; i++) { const charIndex = i % text.length; const charCode = text.charCodeAt(charIndex) || 0; embedding[i] = (charCode / 255) * 0.01; // Very small normalized character features } return embedding; } cosineSimilarity(a, b) { if (a.length !== b.length) { console.warn('Embedding dimension mismatch'); return 0; } const dotProduct = a.reduce((sum, val, i) => sum + val * b[i], 0); const magnitudeA = Math.sqrt(a.reduce((sum, val) => sum + val * val, 0)); const magnitudeB = Math.sqrt(b.reduce((sum, val) => sum + val * val, 0)); if (magnitudeA === 0 || magnitudeB === 0) return 0; return dotProduct / (magnitudeA * magnitudeB); } async indexChunks(chunks) { if (!this.vectorIndex) { await this.initialize(); } if (!this.vectorIndex || chunks.length === 0) { return; } try { console.log(`🚀 Indexing ${chunks.length} code chunks...`); // Process chunks in batches to avoid overwhelming the embedding API const batchSize = 5; // Smaller batch size for API rate limits for (let i = 0; i < chunks.length; i += batchSize) { const batch = chunks.slice(i, i + batchSize); for (const chunk of batch) { // Check if chunk already exists const existingIndex = this.vectorIndex.documents.findIndex(doc => doc.id === chunk.id); // Generate embedding const embedding = await this.generateEmbedding(chunk.content); const storedDoc = { id: chunk.id, content: chunk.content, embedding, metadata: { filePath: chunk.filePath, type: chunk.type, name: chunk.name || '', startLine: chunk.startLine || 0, endLine: chunk.endLine || 0, ...chunk.metadata } }; if (existingIndex >= 0) { // Update existing document this.vectorIndex.documents[existingIndex] = storedDoc; } else { // Add new document this.vectorIndex.documents.push(storedDoc); } } // Save progress and add delay to respect API rate limits await this.saveIndex(); if (i + batchSize < chunks.length) { console.log(`📊 Progress: ${i + batch.length}/${chunks.length} chunks indexed`); await new Promise(resolve => setTimeout(resolve, 200)); } } await this.saveIndex(); console.log(`✅ Indexed ${chunks.length} code chunks`); } catch (error) { console.error('Failed to index chunks:', error); throw error; } } async search(query, maxResults) { if (!this.vectorIndex) { await this.initialize(); } if (!this.vectorIndex || this.vectorIndex.documents.length === 0) { return []; } try { // Generate embedding for the query const queryEmbedding = await this.generateEmbedding(query); // Calculate similarity scores for all documents const searchResults = []; for (const doc of this.vectorIndex.documents) { const similarity = this.cosineSimilarity(queryEmbedding, doc.embedding); const distance = 1 - similarity; // Convert similarity to distance if (similarity >= this.config.searchThreshold) { const chunk = { id: doc.id, content: doc.content, filePath: doc.metadata.filePath, type: doc.metadata.type, name: doc.metadata.name || undefined, startLine: doc.metadata.startLine || undefined, endLine: doc.metadata.endLine || undefined, metadata: doc.metadata }; searchResults.push({ chunk, score: similarity, distance }); } } // Sort by similarity score (highest first) and limit results return searchResults .sort((a, b) => b.score - a.score) .slice(0, maxResults || this.config.maxResults); } catch (error) { console.error('Failed to search:', error); return []; } } async deleteChunk(chunkId) { if (!this.vectorIndex) { await this.initialize(); } if (!this.vectorIndex) { return; } try { const index = this.vectorIndex.documents.findIndex(doc => doc.id === chunkId); if (index >= 0) { this.vectorIndex.documents.splice(index, 1); await this.saveIndex(); } } catch (error) { console.error('Failed to delete chunk:', error); } } async deleteByFilePath(filePath) { if (!this.vectorIndex) { await this.initialize(); } if (!this.vectorIndex) { return; } try { const initialCount = this.vectorIndex.documents.length; this.vectorIndex.documents = this.vectorIndex.documents.filter(doc => doc.metadata.filePath !== filePath); const deletedCount = initialCount - this.vectorIndex.documents.length; if (deletedCount > 0) { await this.saveIndex(); console.log(`🗑️ Deleted ${deletedCount} chunks from ${filePath}`); } } catch (error) { console.error('Failed to delete chunks by file path:', error); } } async getCollectionInfo() { if (!this.vectorIndex) { await this.initialize(); } const count = this.vectorIndex?.documents.length || 0; return { count, config: this.config }; } async clearCollection() { try { this.createEmptyIndex(); await this.saveIndex(); console.log('🗑️ Cleared vector index'); } catch (error) { console.error('Failed to clear collection:', error); } } updateConfig(newConfig) { this.config = { ...this.config, ...newConfig }; } getConfig() { return { ...this.config }; } static generateChunkId(filePath, type, name, startLine) { const identifier = `${filePath}:${type}:${name || 'anonymous'}:${startLine || 0}`; return crypto.createHash('sha256').update(identifier).digest('hex').substring(0, 16); } } exports.RAGService = RAGService; //# sourceMappingURL=rag-service.js.map