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
JavaScript
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