UNPKG

gitdb-database

Version:

A production-ready CLI tool for managing a NoSQL database using GitHub repositories as storage

566 lines (561 loc) 19.4 kB
import { spawn } from 'child_process'; import { promises as fs } from 'fs'; import path from 'path'; export class LocalLLM { config; historyPath; isModelLoaded = false; constructor(config) { this.config = config; this.historyPath = path.join(process.env.HOME || process.env.USERPROFILE || '', '.gitdb', 'ai-history.json'); } /** * Initialize the local LLM */ async initialize() { try { // Check if model file exists await fs.access(this.config.modelPath); this.isModelLoaded = true; console.log('🤖 Local LLM initialized successfully'); } catch (error) { console.log('⚠️ Model not found, using fallback pattern matching'); this.isModelLoaded = false; } } /** * Process natural language query using local LLM */ async processQuery(query, context) { if (this.isModelLoaded) { return this.processWithLLM(query, context); } else { const patterns = await this.processWithPatterns(query, context); return patterns.length > 0 ? patterns[0] : { command: 'help', confidence: 0.3, explanation: 'No matching command found', parameters: {}, suggestedQuery: 'help' }; } } /** * Process query using local LLM */ async processWithLLM(query, context) { const prompt = this.buildPrompt(query, context); try { let response; switch (this.config.modelType) { case 'llama': response = await this.runLlama(prompt); break; case 'gpt4all': response = await this.runGPT4All(prompt); break; case 'mistral': response = await this.runMistral(prompt); break; default: throw new Error(`Unsupported model type: ${this.config.modelType}`); } return await this.parseLLMResponse(response, query); } catch (error) { console.warn('LLM processing failed, falling back to patterns:', error); const patterns = await this.processWithPatterns(query, context); return patterns.length > 0 ? patterns[0] : { command: 'help', confidence: 0.3, explanation: 'No matching command found', parameters: {}, suggestedQuery: 'help' }; } } /** * Process query using pattern matching (fallback) */ async processWithPatterns(query, context) { const normalizedQuery = query.toLowerCase().trim(); // Enhanced pattern matching with better parameter extraction const patterns = [ { patterns: ['show', 'list', 'display', 'get all', 'see all', 'view all'], command: 'show docs', description: 'Show all documents in current collection', confidence: 0.8, extractParams: (q) => this.extractCollectionFromQuery(q) }, { patterns: ['find', 'search', 'look for', 'get', 'find all', 'search for'], command: 'findone', description: 'Find documents matching criteria', confidence: 0.85, extractParams: (q) => this.extractQueryParams(q) }, { patterns: ['add', 'insert', 'create', 'new', 'add new'], command: 'insert', description: 'Insert new document', confidence: 0.9, extractParams: (q) => this.extractDocumentData(q) }, { patterns: ['update', 'modify', 'change', 'edit'], command: 'update', description: 'Update existing document', confidence: 0.85, extractParams: (q) => this.extractUpdateParams(q) }, { patterns: ['delete', 'remove', 'drop'], command: 'delete', description: 'Delete document', confidence: 0.9, extractParams: (q) => this.extractIdFromQuery(q) }, { patterns: ['count', 'how many', 'total', 'number of'], command: 'count', description: 'Count documents', confidence: 0.8, extractParams: (q) => this.extractCountParams(q) }, { patterns: ['collections', 'show collections', 'list collections'], command: 'show collections', description: 'List all collections', confidence: 0.9, extractParams: () => ({}) }, { patterns: ['use', 'switch to', 'change to', 'select'], command: 'use', description: 'Switch to collection', confidence: 0.85, extractParams: (q) => this.extractCollectionFromQuery(q) }, { patterns: ['create collection', 'new collection', 'add collection'], command: 'create-collection', description: 'Create new collection', confidence: 0.9, extractParams: (q) => this.extractCollectionName(q) }, { patterns: ['rollback', 'undo', 'revert', 'go back', 'restore'], command: 'rollback', description: 'Rollback to previous version', confidence: 0.9, extractParams: (q) => this.extractRollbackParams(q) }, { patterns: ['optimize', 'performance', 'speed up', 'supermode', 'enable supermode'], command: 'supermode enable', description: 'Enable SuperMode optimizations', confidence: 0.85, extractParams: () => ({}) }, { patterns: ['summarize', 'summary', 'overview', 'report'], command: 'summarize', description: 'Generate database summary', confidence: 0.9, extractParams: () => ({}) }, { patterns: ['doctor', 'health', 'diagnostics', 'check health'], command: 'doctor', description: 'Run health diagnostics', confidence: 0.9, extractParams: () => ({}) } ]; const matches = []; for (const pattern of patterns) { for (const keyword of pattern.patterns) { if (normalizedQuery.includes(keyword)) { const parameters = pattern.extractParams(normalizedQuery); const suggestedQuery = this.generateSuggestedQuery(pattern.command, parameters); matches.push({ command: pattern.command, confidence: pattern.confidence, explanation: pattern.description, parameters, suggestedQuery }); } } } if (matches.length === 0) { matches.push({ command: 'help', confidence: 0.3, explanation: 'No matching command found. Try rephrasing your query.', parameters: {}, suggestedQuery: 'help' }); } return matches; } /** * Extract collection name from query */ extractCollectionFromQuery(query) { const collectionMatch = query.match(/(?:in|from|to|collection|use)\s+(\w+)/); if (collectionMatch) { return { collection: collectionMatch[1] }; } return {}; } /** * Extract query parameters for find operations */ extractQueryParams(query) { const params = {}; // Age ranges const ageMatch = query.match(/(?:age|years?)\s+(?:over|above|more than|greater than)\s+(\d+)/); if (ageMatch) { params.age = { $gte: parseInt(ageMatch[1]) }; } const ageUnderMatch = query.match(/(?:age|years?)\s+(?:under|below|less than|younger than)\s+(\d+)/); if (ageUnderMatch) { params.age = { $lt: parseInt(ageUnderMatch[1]) }; } // Email patterns if (query.includes('email') && query.includes('gmail')) { params.email = { $regex: '.*@gmail.com' }; } // Status values const statusMatch = query.match(/(?:status|state)\s+(?:is|equals?)\s+(\w+)/); if (statusMatch) { params.status = statusMatch[1]; } // Name patterns const nameMatch = query.match(/(?:name|user)\s+(?:is|equals?)\s+(\w+)/); if (nameMatch) { params.name = nameMatch[1]; } // Active/inactive if (query.includes('active')) { params.status = 'active'; } else if (query.includes('inactive')) { params.status = 'inactive'; } return params; } /** * Extract document data for insert operations */ extractDocumentData(query) { const data = {}; // Extract name const nameMatch = query.match(/(?:named|name|user)\s+(\w+)/); if (nameMatch) { data.name = nameMatch[1]; } // Extract age const ageMatch = query.match(/(?:age|years?)\s+(\d+)/); if (ageMatch) { data.age = parseInt(ageMatch[1]); } // Extract email const emailMatch = query.match(/(?:email|e-mail)\s+([^\s]+@[^\s]+)/); if (emailMatch) { data.email = emailMatch[1]; } return data; } /** * Extract update parameters */ extractUpdateParams(query) { const params = {}; // Extract ID const idMatch = query.match(/(?:id|document)\s+(\w+)/); if (idMatch) { params.id = idMatch[1]; } // Extract update data const data = this.extractDocumentData(query); if (Object.keys(data).length > 0) { params.data = data; } return params; } /** * Extract ID for delete operations */ extractIdFromQuery(query) { const idMatch = query.match(/(?:id|document)\s+(\w+)/); if (idMatch) { return { id: idMatch[1] }; } return {}; } /** * Extract count parameters */ extractCountParams(query) { return this.extractQueryParams(query); } /** * Extract collection name for create operations */ extractCollectionName(query) { const nameMatch = query.match(/(?:collection|named)\s+(\w+)/); if (nameMatch) { return { name: nameMatch[1] }; } return {}; } /** * Extract rollback parameters */ extractRollbackParams(query) { const params = {}; // Extract collection const collectionMatch = query.match(/(?:collection|from)\s+(\w+)/); if (collectionMatch) { params.collection = collectionMatch[1]; } // Extract version/commit const versionMatch = query.match(/(?:version|commit)\s+(\w+)/); if (versionMatch) { params.version = versionMatch[1]; } return params; } /** * Generate suggested query from command and parameters */ generateSuggestedQuery(command, parameters) { let suggestion = command; if (parameters.collection) { suggestion += ` ${parameters.collection}`; } else if (parameters.id) { suggestion += ` ${parameters.id}`; } else if (parameters.name) { suggestion += ` ${parameters.name}`; } else if (parameters.data) { suggestion += ` ${JSON.stringify(parameters.data)}`; } else if (Object.keys(parameters).length > 0) { // Convert parameters to query const query = {}; for (const [key, value] of Object.entries(parameters)) { if (key !== 'collection' && key !== 'id' && key !== 'name') { query[key] = value; } } if (Object.keys(query).length > 0) { suggestion += ` ${JSON.stringify(query)}`; } } return suggestion; } /** * Build prompt for LLM */ buildPrompt(query, context) { const systemPrompt = `You are an AI assistant for GitDB, a GitHub-backed NoSQL database CLI. Convert natural language queries to GitDB commands. Available commands: - show docs: Show all documents in current collection - findone <query>: Find documents matching criteria - insert <json>: Insert new document - update <id> <json>: Update existing document - delete <id>: Delete document - count [query]: Count documents - show collections: List all collections - use <collection>: Switch to collection - create-collection <name>: Create new collection - rollback <collection>: Rollback to previous version - supermode enable: Enable performance optimizations - summarize: Generate database summary - doctor: Run health diagnostics Query operators: $gte, $lt, $in, $regex, $exists Respond with JSON format: { "command": "command_name", "confidence": 0.85, "explanation": "description", "parameters": {}, "suggestedQuery": "full_command" }`; return `${systemPrompt} User query: "${query}" Context: ${JSON.stringify(context || {})} Response:`; } /** * Run LLaMA model */ async runLlama(prompt) { return new Promise((resolve, reject) => { const llama = spawn('./llama', [ '--model', this.config.modelPath, '--prompt', prompt, '--n-predict', this.config.maxTokens.toString(), '--temp', this.config.temperature.toString() ]); let output = ''; llama.stdout.on('data', (data) => { output += data.toString(); }); llama.stderr.on('data', (data) => { console.warn('LLaMA stderr:', data.toString()); }); llama.on('close', (code) => { if (code === 0) { resolve(output); } else { reject(new Error(`LLaMA process exited with code ${code}`)); } }); }); } /** * Run GPT4All model */ async runGPT4All(prompt) { return new Promise((resolve, reject) => { const gpt4all = spawn('./gpt4all', [ '--model', this.config.modelPath, '--prompt', prompt, '--max-tokens', this.config.maxTokens.toString(), '--temperature', this.config.temperature.toString() ]); let output = ''; gpt4all.stdout.on('data', (data) => { output += data.toString(); }); gpt4all.stderr.on('data', (data) => { console.warn('GPT4All stderr:', data.toString()); }); gpt4all.on('close', (code) => { if (code === 0) { resolve(output); } else { reject(new Error(`GPT4All process exited with code ${code}`)); } }); }); } /** * Run Mistral model */ async runMistral(prompt) { return new Promise((resolve, reject) => { const mistral = spawn('./mistral', [ '--model', this.config.modelPath, '--prompt', prompt, '--max-tokens', this.config.maxTokens.toString(), '--temperature', this.config.temperature.toString() ]); let output = ''; mistral.stdout.on('data', (data) => { output += data.toString(); }); mistral.stderr.on('data', (data) => { console.warn('Mistral stderr:', data.toString()); }); mistral.on('close', (code) => { if (code === 0) { resolve(output); } else { reject(new Error(`Mistral process exited with code ${code}`)); } }); }); } /** * Parse LLM response */ async parseLLMResponse(response, originalQuery) { try { // Try to extract JSON from response const jsonMatch = response.match(/\{[\s\S]*\}/); if (jsonMatch) { const parsed = JSON.parse(jsonMatch[0]); return { command: parsed.command || 'help', confidence: parsed.confidence || 0.5, explanation: parsed.explanation || 'AI-generated command', parameters: parsed.parameters || {}, suggestedQuery: parsed.suggestedQuery || parsed.command }; } } catch (error) { console.warn('Failed to parse LLM response as JSON:', error); } // Fallback to pattern matching const patterns = await this.processWithPatterns(originalQuery); return patterns[0] || { command: 'help', confidence: 0.3, explanation: 'No matching command found', parameters: {}, suggestedQuery: 'help' }; } /** * Save query to history for learning */ async saveToHistory(query, response) { try { const history = await this.loadHistory(); history.push({ query, response, timestamp: new Date().toISOString() }); // Keep only last 1000 entries if (history.length > 1000) { history.splice(0, history.length - 1000); } await this.saveHistory(history); } catch (error) { console.warn('Failed to save to history:', error); } } /** * Load query history */ async loadHistory() { try { const data = await fs.readFile(this.historyPath, 'utf8'); return JSON.parse(data); } catch (error) { return []; } } /** * Save query history */ async saveHistory(history) { try { const dir = path.dirname(this.historyPath); await fs.mkdir(dir, { recursive: true }); await fs.writeFile(this.historyPath, JSON.stringify(history, null, 2)); } catch (error) { console.warn('Failed to save history:', error); } } } //# sourceMappingURL=local-llm.js.map