UNPKG

sushil-gitmate

Version:

Professional Git workflow automation powered by AI. Streamline your development process with natural language commands and intelligent automation.

981 lines (856 loc) 40.4 kB
import OpenAI from 'openai'; import Anthropic from '@anthropic-ai/sdk'; import configManager from '../utils/configManager.js'; import logger from '../utils/logger.js'; const serviceName = 'AIServiceFactory'; export const AI_PROVIDERS = { OPENAI: 'openai', ANTHROPIC: 'anthropic', MISTRAL: 'mistral' }; // Conversation types we'll handle const CONVERSATION_TYPES = { GREETING: 'greeting', GIT_OPERATION: 'git_operation', GITHUB_OPERATION: 'github_operation', UNRELATED: 'unrelated', THANKS: 'thanks', HELP: 'help', ERROR: 'error' }; let currentProvider = AI_PROVIDERS.MISTRAL; let openaiClient = null; let anthropicClient = null; let mistralClient = null; // In-memory cache for prompt/response pairs (session cache) const aiResponseCache = new Map(); export function setProvider(provider) { if (Object.values(AI_PROVIDERS).includes(provider)) { currentProvider = provider; logger.info(`AI provider switched to: ${provider}`, { service: serviceName }); return true; } logger.error(`Invalid AI provider: ${provider}`, { service: serviceName }); return false; } export function getCurrentProvider() { return currentProvider; } async function getOpenAIClient() { if (!openaiClient) { const apiKey = await configManager.getAPIKey(); if (!apiKey) { throw new Error('OpenAI API key not configured. Run "gitmate init" to set up your configuration.'); } openaiClient = new OpenAI({ apiKey }); } return openaiClient; } async function getAnthropicClient() { if (!anthropicClient) { const apiKey = await configManager.getAPIKey(); if (!apiKey) { throw new Error('Anthropic API key not configured. Run "gitmate init" to set up your configuration.'); } anthropicClient = new Anthropic({ apiKey }); } return anthropicClient; } async function getMistralClient() { if (!mistralClient) { const mistralProxyUrl ='https://gitbot-1-24a9.onrender.com/api/mistral'; // const mistralProxyUrl = 'http://localhost:3000/api/mistral'; mistralClient = { async chat(messages, options = {}) { const response = await fetch(mistralProxyUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ messages, options }) }); if (!response.ok) { throw new Error(`Mistral Proxy error: ${response.status} ${await response.text()}`); } const data = await response.json(); // Better response handling if (data.choices && data.choices[0] && data.choices[0].message && data.choices[0].message.content) { return data.choices[0].message.content; } else if (data.choices && data.choices[0] && data.choices[0].text) { return data.choices[0].text; } else if (typeof data === 'string') { return data; } else { throw new Error('Invalid response format from Mistral proxy'); } } }; } return mistralClient; } // Helper function to detect conversation type function detectConversationType(query) { const lowerQuery = query.toLowerCase().trim(); // Greetings const greetings = ['hello', 'hi', 'hey', 'greetings', 'good morning', 'good afternoon', 'good evening']; if (greetings.some(g => lowerQuery.includes(g))) { return { type: CONVERSATION_TYPES.GREETING, response: null, immediate: true }; } // Thanks const thanks = ['thank', 'thanks', 'appreciate', 'thx']; if (thanks.some(t => lowerQuery.includes(t))) { return { type: CONVERSATION_TYPES.THANKS, response: null, immediate: true }; } // Help requests const helpKeywords = ['help', 'what can you do', 'how to', 'examples', 'commands']; if (helpKeywords.some(h => lowerQuery.includes(h))) { return { type: CONVERSATION_TYPES.HELP, response: null, immediate: true }; } // Show/display operations - check these before help keywords if (lowerQuery.includes('show') || lowerQuery.includes('display')) { if (lowerQuery.includes('status') || lowerQuery.includes('state')) { return { type: CONVERSATION_TYPES.GIT_OPERATION, response: null, immediate: false }; } if (lowerQuery.includes('diff') || lowerQuery.includes('difference')) { return { type: CONVERSATION_TYPES.GIT_OPERATION, response: null, immediate: false }; } if (lowerQuery.includes('remote') || lowerQuery.includes('remotes')) { return { type: CONVERSATION_TYPES.GIT_OPERATION, response: null, immediate: false }; } if (lowerQuery.includes('branch') || lowerQuery.includes('branches')) { return { type: CONVERSATION_TYPES.GIT_OPERATION, response: null, immediate: false }; } if (lowerQuery.includes('log') || lowerQuery.includes('history') || lowerQuery.includes('commits')) { return { type: CONVERSATION_TYPES.GIT_OPERATION, response: null, immediate: false }; } if (lowerQuery.includes('change') || lowerQuery.includes('changes')) { return { type: CONVERSATION_TYPES.GIT_OPERATION, response: null, immediate: false }; } } // List operations - be more specific if (lowerQuery.includes('list')) { // List repositories if (lowerQuery.includes('repo') || lowerQuery.includes('repository') || (lowerQuery.includes('all') && lowerQuery.includes('my') && lowerQuery.includes('repo'))) { return { type: CONVERSATION_TYPES.GITHUB_OPERATION, response: null, immediate: false }; } // List branches if (lowerQuery.includes('branch') || lowerQuery.includes('branches')) { return { type: CONVERSATION_TYPES.GIT_OPERATION, response: null, immediate: false }; } // List remotes if (lowerQuery.includes('remote') || lowerQuery.includes('remotes')) { return { type: CONVERSATION_TYPES.GIT_OPERATION, response: null, immediate: false }; } // List changes (Git status) if (lowerQuery.includes('change') || lowerQuery.includes('changes') || lowerQuery.includes('modified') || lowerQuery.includes('staged')) { return { type: CONVERSATION_TYPES.GIT_OPERATION, response: null, immediate: false }; } // List commits/log if (lowerQuery.includes('commit') || lowerQuery.includes('log') || lowerQuery.includes('history') || lowerQuery.includes('commits')) { return { type: CONVERSATION_TYPES.GIT_OPERATION, response: null, immediate: false }; } } // Git operations const gitKeywords = ['git', 'push', 'pull', 'commit', 'branch', 'merge', 'rebase', 'checkout', 'switch', 'add', 'stash', 'reset', 'revert']; if (gitKeywords.some(k => lowerQuery.includes(k))) { return { type: CONVERSATION_TYPES.GIT_OPERATION, response: null, immediate: false }; } // GitHub operations const githubKeywords = ['github', 'pull request', 'pr', 'issue', 'repository', 'repo', 'fork', 'clone']; if (githubKeywords.some(k => lowerQuery.includes(k))) { return { type: CONVERSATION_TYPES.GITHUB_OPERATION, response: null, immediate: false }; } // Pull request specific detection if (lowerQuery.includes('pr') || lowerQuery.includes('pull request') || lowerQuery.includes('merge request') || lowerQuery.includes('create pr') || lowerQuery.includes('create pull request') || lowerQuery.includes('create merge request')) { return { type: CONVERSATION_TYPES.GITHUB_OPERATION, response: null, immediate: false }; } // Check if it's unrelated to Git/GitHub const unrelatedKeywords = ['weather', 'time', 'date', 'calculator', 'math', 'translate', 'search']; if (unrelatedKeywords.some(k => lowerQuery.includes(k))) { return { type: CONVERSATION_TYPES.UNRELATED, response: null, immediate: true }; } // Default to Git operation for ambiguous cases return { type: CONVERSATION_TYPES.GIT_OPERATION, response: null, immediate: false }; } export const aiService = { async checkStatus() { try { // Prefer Mistral if its API key is present let apiKey = "mistral"; if (apiKey) { const client = await getMistralClient(); await client.chat([{ role: 'user', content: 'Hello' }], { max_tokens: 10 }); return true; } // Check if we have local config // const isConfigured = await configManager.isConfigured(); // if (!isConfigured) { // logger.warn('GitMate is not configured. Users can set AI_PROVIDER and API key environment variables or run "gitmate init" to set up.', { service: serviceName }); // return false; // } // const configProvider = await configManager.getAIProvider(); // const configApiKey = await configManager.getAPIKey(); // if (!configApiKey) { // logger.warn('API key not configured. Users can set AI_PROVIDER and API key environment variables or run "gitmate init" to set up.', { service: serviceName }); // return false; // } // Test the connection const client = await getMistralClient(); await client.chat([{ role: 'user', content: 'Hello' }], { max_tokens: 10 }); return true; } catch (error) { logger.error('AI service status check failed:', { message: error.message, service: serviceName }); return false; } }, async generateResponse(prompt, options = {}) { try { // Check cache first const cacheKey = JSON.stringify({ prompt, options }); if (aiResponseCache.has(cacheKey)) { return aiResponseCache.get(cacheKey); } const provider = await configManager.getAIProvider(); let response; if (provider === AI_PROVIDERS.OPENAI) { const client = await getOpenAIClient(); response = await client.chat.completions.create({ model: options.model || 'gpt-4', messages: [{ role: 'user', content: prompt }], max_tokens: options.max_tokens || 1000, temperature: options.temperature || 0.7 }); response = response.choices[0].message.content; } else if (provider === AI_PROVIDERS.ANTHROPIC) { const client = await getAnthropicClient(); const anthropicResponse = await client.messages.create({ model: options.model || 'claude-3-sonnet-20240229', max_tokens: options.max_tokens || 1000, messages: [{ role: 'user', content: prompt }] }); response = anthropicResponse.content[0].text; } else if (provider === AI_PROVIDERS.MISTRAL) { const client = await getMistralClient(); response = await client.chat( [{ role: 'user', content: prompt }], { model: options.model || 'mistral-large-latest', max_tokens: options.max_tokens || 1000, temperature: options.temperature || 0.7 } ); } else { throw new Error(`Unsupported AI provider: ${provider}`); } aiResponseCache.set(cacheKey, response); return response; } catch (error) { if (error.message && error.message.includes('429')) { logger.error('AI service rate limit reached (429):', { message: error.message, service: serviceName }); return '⚠️ The AI service is currently overloaded or your usage limit has been reached. Please wait a few minutes and try again, or consider upgrading your service tier.'; } logger.error('AI service response generation failed:', { message: error.message, service: serviceName }); throw error; } }, async handleConversation(query, username = 'there') { try { // Check cache first const cacheKey = `conversation:${query}`; if (aiResponseCache.has(cacheKey)) { return aiResponseCache.get(cacheKey); } const { type } = detectConversationType(query); // Handle simple cases without calling the AI switch (type) { case CONVERSATION_TYPES.GREETING: return `Hello ${username}! 👋 I'm GitMate, your Git assistant. How can I help you with version control today?`; case CONVERSATION_TYPES.THANKS: return `You're welcome, ${username}! 😊 Let me know if you need any more help with Git or GitHub.`; case CONVERSATION_TYPES.UNRELATED: return `Hey ${username}, I'm specialized in Git operations. I can help you with version control, repositories, and GitHub-related tasks. What would you like to do with your code?`; case CONVERSATION_TYPES.HELP: return this.generateCommandHelp(); } // For Git/GitHub operations or complex cases, use the AI const prompt = `You are GitMate, a friendly Git/GitHub assistant. The user "${username}" asked: "${query}" Your response should be: 1. For Git/GitHub operations: Explain what will happen or ask for clarification 2. For greetings: Friendly response that invites Git-related questions 3. For unrelated questions: Politely explain you focus on Git/GitHub 4. For thanks: Warm acknowledgment 5. Always be concise and helpful Response:`; const response = await this.generateResponse(prompt, { max_tokens: 300 }); aiResponseCache.set(cacheKey, response); return response; } catch (error) { logger.error('Conversation handling failed:', { message: error.message, service: serviceName }); return `Sorry ${username}, I'm having trouble understanding. Could you rephrase your request in terms of Git or GitHub operations?`; } }, async parseIntent(query) { try { const cacheKey = `intent_${query.toLowerCase().trim()}`; const cached = aiResponseCache.get(cacheKey); if (cached) { return cached; } // Enhanced prompt with better intent classification and reduced bias const prompt = `You are an intelligent intent parser for Git and GitHub commands. Analyze this query: "${query}" IMPORTANT: Respond with ONLY a valid JSON object. No text, no explanation, no markdown, no code blocks. Required JSON format: { "intent": "intent_name", "entities": {}, "confidence": 0.9 } CRITICAL RULES: 1. DO NOT default to create_repo unless explicitly creating a repository 2. "List", "show", "display" commands should be list_repos, list_branches, git_status, etc. 3. "Push" commands should be push_changes 4. "Pull" commands should be pull_changes 5. "Commit" commands should be git_commit 6. "Add" commands should be git_add 7. "Status" or "what's changed" should be git_status 8. "Diff" or "differences" should be git_diff 9. "Branch" listing should be list_branches 10. "PR" or "pull request" should be create_pr 11. "Help" or "what can you do" should be help 12. "Login", "authenticate" should be greeting 13. "Clone" should be clone_repo 14. "Delete" should be unknown (not implemented) BRANCH NAME EXTRACTION: - Extract branch names from phrases like "to branch called X", "to X branch", "on branch X", "push to X" - For push_changes: if a specific branch is mentioned, include it in entities.branch - For create_branch: if a branch name is mentioned, include it in entities.branch - For checkout_branch: if a branch name is mentioned, include it in entities.branch Available intents: list_repos, list_branches, git_status, git_diff, git_log, push_changes, pull_changes, git_commit, git_add, create_branch, checkout_branch, merge_branch, clone_repo, create_repo, create_pr, revert_commit, greeting, thanks, unrelated, help, error, unknown EXACT MAPPINGS: - "list my repositories" → list_repos - "show my repos" → list_repos - "what repositories do I have" → list_repos - "display my repos" → list_repos - "show repositories" → list_repos - "list all repos" → list_repos - "create a new repository" → create_repo - "create repository" → create_repo - "new repository" → create_repo - "make a repo" → create_repo - "create a repo" → create_repo - "show branches" → list_branches - "list branches" → list_branches - "show status" → git_status - "what's changed" → git_status - "current status" → git_status - "show diff" → git_diff - "show differences" → git_diff - "show log" → git_log - "show history" → git_log - "push changes" → push_changes - "push my changes" → push_changes - "pull changes" → pull_changes - "pull latest" → pull_changes - "commit" → git_commit - "add files" → git_add - "stage changes" → git_add - "create branch" → create_branch - "checkout branch" → checkout_branch - "switch branch" → checkout_branch - "merge branch" → merge_branch - "clone repo" → clone_repo - "create pull request" → create_pr - "open a PR" → create_pr - "create pr" → create_pr - "submit a pull request" → create_pr - "make a merge request" → create_pr - "revert commit" → revert_commit - "login" → greeting - "authenticate" → greeting - "help" → help - "what can you do" → help BRANCH EXAMPLES: - "push to branch called feature-update" → {"intent": "push_changes", "entities": {"branch": "feature-update"}} - "push to main branch" → {"intent": "push_changes", "entities": {"branch": "main"}} - "create a new branch called feature-x" → {"intent": "create_branch", "entities": {"branch": "feature-x"}} - "switch to develop branch" → {"intent": "checkout_branch", "entities": {"branch": "develop"}} IMPORTANT: Only use create_repo when explicitly creating a new repository. For listing, showing, or managing existing repositories, use list_repos. JSON response:`; const response = await this.generateResponse(prompt, { max_tokens: 200, temperature: 0.1 }); let parsed; try { // Handle markdown-wrapped JSON responses let jsonContent = response.trim(); if (jsonContent.startsWith('```json')) { jsonContent = jsonContent.replace(/^```json\s*/, '').replace(/\s*```$/, ''); } else if (jsonContent.startsWith('```')) { jsonContent = jsonContent.replace(/^```\s*/, '').replace(/\s*```$/, ''); } parsed = JSON.parse(jsonContent); } catch (parseError) { // Try to extract JSON from the response const jsonMatch = response.match(/\{[\s\S]*\}/); if (jsonMatch) { try { parsed = JSON.parse(jsonMatch[0]); } catch (secondError) { parsed = this.fallbackIntentDetection(query); } } else { // Fallback to keyword-based intent detection parsed = this.fallbackIntentDetection(query); } } // Add default values for common entities parsed.entities = parsed.entities || {}; if (parsed.intent === 'push_changes' && !parsed.entities.branch) { parsed.entities.branch = 'current'; } if (!parsed.entities.remote && (parsed.intent === 'push_changes' || parsed.intent === 'pull_changes')) { parsed.entities.remote = 'origin'; } // Validate and correct obvious misclassifications parsed = this.validateAndCorrectIntent(query, parsed); // Additional validation: if we got list_repos but the query doesn't match repository listing patterns if (parsed.intent === 'list_repos') { const lowerQuery = query.toLowerCase(); const isRepoListing = lowerQuery.includes('repo') || lowerQuery.includes('repository'); const isListingAction = lowerQuery.includes('list') || lowerQuery.includes('show') || lowerQuery.includes('display') || lowerQuery.includes('what'); // If it's not clearly a repository listing command, try to determine the correct intent if (!isRepoListing || !isListingAction) { const correctedIntent = this.determineCorrectIntent(query); if (correctedIntent && correctedIntent !== 'list_repos') { parsed.intent = correctedIntent; parsed.entities = {}; } } } // Fix git_add misclassifications for git_init if (parsed.intent === 'git_add' && query.toLowerCase().includes('init')) { parsed.intent = 'git_init'; parsed.entities = {}; } // Fix unknown intent for authentication and help if (parsed.intent === 'unknown') { const lowerQuery = query.toLowerCase(); if (lowerQuery.includes('login') || lowerQuery.includes('logout') || lowerQuery.includes('authenticate')) { parsed.intent = 'greeting'; } else if (lowerQuery.includes('help') || lowerQuery.includes('what can') || lowerQuery.includes('how do i')) { parsed.intent = 'help'; } } // Fix greeting intent for help queries if (parsed.intent === 'greeting' && query.toLowerCase().includes('how do i')) { parsed.intent = 'help'; } // Fix pull_changes for pull request queries if (parsed.intent === 'pull_changes' && (query.toLowerCase().includes('pull request') || query.toLowerCase().includes('pr'))) { parsed.intent = 'create_pr'; } aiResponseCache.set(cacheKey, parsed); return parsed; } catch (error) { if (error.message && error.message.includes('429')) { logger.error('AI service rate limit reached (429):', { message: error.message, service: serviceName }); return { intent: 'error', entities: { error: '⚠️ The AI service is currently overloaded. Please wait a few minutes and try again.' } }; } logger.error('Intent parsing failed:', { message: error.message, service: serviceName }); return { intent: 'unknown', entities: { error: error.message } }; } }, // New method to validate and correct obvious misclassifications validateAndCorrectIntent(query, parsed) { const lowerQuery = query.toLowerCase(); // Fix common misclassifications if (parsed.intent === 'create_repo') { // Check if this should actually be list_repos if (lowerQuery.includes('list') || lowerQuery.includes('show') || lowerQuery.includes('display') || lowerQuery.includes('what') || lowerQuery.includes('my repositories') || lowerQuery.includes('my repos')) { parsed.intent = 'list_repos'; parsed.entities = {}; // Clear incorrect entities } } // Fix git status misclassifications if (parsed.intent === 'create_repo' && (lowerQuery.includes('status') || lowerQuery.includes('changed') || lowerQuery.includes('what\'s changed') || lowerQuery.includes('current status'))) { parsed.intent = 'git_status'; parsed.entities = {}; } // Fix git diff misclassifications if (parsed.intent === 'create_repo' && (lowerQuery.includes('diff') || lowerQuery.includes('differences'))) { parsed.intent = 'git_diff'; parsed.entities = {}; } // Fix list_branches misclassifications if (parsed.intent === 'create_repo' && (lowerQuery.includes('branches') && (lowerQuery.includes('list') || lowerQuery.includes('show')))) { parsed.intent = 'list_branches'; parsed.entities = {}; } // Fix push_changes misclassifications if (parsed.intent === 'create_repo' && lowerQuery.includes('push')) { parsed.intent = 'push_changes'; parsed.entities = { branch: 'current', remote: 'origin' }; } // Fix pull_changes misclassifications if (parsed.intent === 'create_repo' && lowerQuery.includes('pull')) { parsed.intent = 'pull_changes'; parsed.entities = { remote: 'origin' }; } // Fix create_pr misclassifications if (parsed.intent === 'create_repo' && (lowerQuery.includes('pull request') || lowerQuery.includes('pr') || lowerQuery.includes('merge request'))) { parsed.intent = 'create_pr'; parsed.entities = {}; } // Fix help misclassifications if (parsed.intent === 'create_repo' && (lowerQuery.includes('help') || lowerQuery.includes('what can') || lowerQuery.includes('capabilities'))) { parsed.intent = 'help'; parsed.entities = {}; } // Fix git_add misclassifications if (parsed.intent === 'push_changes' && (lowerQuery.includes('stage') || lowerQuery.includes('add') && !lowerQuery.includes('push'))) { parsed.intent = 'git_add'; parsed.entities = {}; } return parsed; }, // New method to determine correct intent when list_repos is incorrectly returned determineCorrectIntent(query) { const lowerQuery = query.toLowerCase(); // Git status and changes if (lowerQuery.includes('status') || lowerQuery.includes('what\'s changed') || lowerQuery.includes('current status') || lowerQuery.includes('changed') || lowerQuery.includes('what changed')) { return 'git_status'; } // Git diff if (lowerQuery.includes('diff') || lowerQuery.includes('difference') || lowerQuery.includes('differences')) { return 'git_diff'; } // Branch operations if (lowerQuery.includes('branch')) { if (lowerQuery.includes('list') || lowerQuery.includes('show') || lowerQuery.includes('display')) { return 'list_branches'; } if (lowerQuery.includes('create') || lowerQuery.includes('new')) { return 'create_branch'; } if (lowerQuery.includes('checkout') || lowerQuery.includes('switch')) { return 'checkout_branch'; } } // Git operations if (lowerQuery.includes('push')) { return 'push_changes'; } if (lowerQuery.includes('pull') || lowerQuery.includes('sync')) { return 'pull_changes'; } if (lowerQuery.includes('commit')) { return 'git_commit'; } if (lowerQuery.includes('add') || lowerQuery.includes('stage')) { return 'git_add'; } // Pull request operations - PRIORITIZE over pull operations if (lowerQuery.includes('pr') || lowerQuery.includes('pull request') || lowerQuery.includes('merge request') || lowerQuery.includes('create a pull') || lowerQuery.includes('submit a pull')) { return 'create_pr'; } // Git operations - PRIORITIZE these over generic repo operations if (lowerQuery.includes('push')) { return 'push_changes'; } if (lowerQuery.includes('pull') || lowerQuery.includes('sync')) { return 'pull_changes'; } // Clone operations if (lowerQuery.includes('clone')) { return 'clone_repo'; } // Git init - PRIORITIZE over git_add if (lowerQuery.includes('init') || lowerQuery.includes('initialize') || (lowerQuery.includes('git') && lowerQuery.includes('here'))) { return 'git_init'; } // Authentication and help if (lowerQuery.includes('login') || lowerQuery.includes('authenticate') || lowerQuery.includes('logged in') || lowerQuery.includes('logout') || lowerQuery.includes('am i logged')) { return 'greeting'; } if (lowerQuery.includes('help') || lowerQuery.includes('what can') || lowerQuery.includes('capabilities') || lowerQuery.includes('how do i')) { return 'help'; } return null; // Could not determine }, async generateConfirmation(parsed, username = 'there') { try { // Handle non-Git operations if (['greeting', 'thanks', 'unrelated', 'help'].includes(parsed.intent)) { return this.handleConversation(parsed.intent, username); } // Create specific confirmation messages based on intent const intentMessages = { 'list_repos': `Hi ${username}, I'll fetch and display your GitHub repositories. Ready to proceed?`, 'list_branches': `Hi ${username}, I'll show you all the branches in this repository. Ready to proceed?`, 'git_status': `Hi ${username}, I'll show you the current status of your Git repository (staged, modified, and untracked files). Ready to proceed?`, 'git_diff': `Hi ${username}, I'll show you the differences in your working directory. Ready to proceed?`, 'git_log': `Hi ${username}, I'll show you the commit history. Ready to proceed?`, 'get_remotes': `Hi ${username}, I'll show you the configured remote repositories. Ready to proceed?`, 'create_repo': `Hi ${username}, I'll create a new repository for you. Ready to proceed?`, 'push_changes': `Hi ${username}, I'll push your changes to the remote repository. Ready to proceed?`, 'git_commit': `Hi ${username}, I'll commit your staged changes. Ready to proceed?`, 'git_add': `Hi ${username}, I'll stage the specified files. Ready to proceed?`, 'create_branch': `Hi ${username}, I'll create a new branch for you. Ready to proceed?`, 'checkout_branch': `Hi ${username}, I'll switch to the specified branch. Ready to proceed?`, 'pull_changes': `Hi ${username}, I'll pull the latest changes from the remote. Ready to proceed?`, 'merge_branch': `Hi ${username}, I'll merge the specified branch. Ready to proceed?`, 'clone_repo': `Hi ${username}, I'll clone the repository for you. Ready to proceed?`, 'revert_commit': `Hi ${username}, I'll revert the specified commit. Ready to proceed?`, 'create_pr': `Hi ${username}, I'll create a pull request for you. Ready to proceed?`, 'set_default_branch': `Hi ${username}, I'll set the default branch. Ready to proceed?`, 'configure_git_user': `Hi ${username}, I'll configure your Git user settings. Ready to proceed?`, 'get_current_branch': `Hi ${username}, I'll show you the current branch. Ready to proceed?`, 'add_remote': `Hi ${username}, I'll add a new remote repository. Ready to proceed?`, 'get_diff_between_branches': `Hi ${username}, I'll show you the differences between branches. Ready to proceed?`, 'create_and_checkout_branch': `Hi ${username}, I'll create and switch to a new branch. Ready to proceed?`, 'git_init': `Hi ${username}, I'll initialize a new Git repository. Ready to proceed?`, 'git_revert_last_commit': `Hi ${username}, I'll revert the last commit. Ready to proceed?` }; // Return specific message if available, otherwise generic return intentMessages[parsed.intent] || `Hi ${username}, I'll perform the ${parsed.intent} operation. Ready to proceed?`; } catch (error) { logger.error('Confirmation generation failed:', { message: error.message, service: serviceName }); return `Hi ${username}, I'll perform the ${parsed.intent} operation. Ready to proceed?`; } }, async generateCommitMessage(diff) { try { if (!diff?.trim()) { return 'chore: no changes detected'; } const prompt = `Analyze this Git diff and generate a concise, conventional commit message: ${diff.substring(0, 2000)} Generate a commit message in the format: <type>(<scope>): <description> Common types: - feat: New feature - fix: Bug fix - docs: Documentation changes - style: Code style/formatting - refactor: Code refactoring - perf: Performance improvement - test: Test changes - chore: Maintenance/boring tasks Commit message:`; const response = await this.generateResponse(prompt, { max_tokens: 100, temperature: 0.3 }); return response.trim().replace(/^["']|["']$/g, ''); } catch (error) { logger.error('Commit message generation failed:', { message: error.message, service: serviceName }); return 'fix: code changes'; } }, async generateGitignore(projectDescription) { try { const prompt = `Generate a comprehensive .gitignore file for this project: Project: ${projectDescription} Include patterns for: - Operating system files - IDE/editor files - Build artifacts - Dependencies - Logs - Environment files - Project-specific exclusions .gitignore:`; return await this.generateResponse(prompt, { max_tokens: 800, temperature: 0.1 }); } catch (error) { logger.error('Gitignore generation failed:', { message: error.message, service: serviceName }); return '# Default gitignore\n*.log\n*.tmp\nnode_modules/\n.env\n'; } }, async generateCommandHelp() { try { const prompt = `Generate a helpful message showing common GitMate commands and examples. Make it friendly, concise, and format it nicely with emojis where appropriate. Include basic Git operations and GitHub-related commands.`; return await this.generateResponse(prompt, { max_tokens: 400 }); } catch (error) { logger.error('Help generation failed:', { message: error.message, service: serviceName }); return `Here are some common Git commands I can help with: - git push/pull - git commit - git branch - git merge - GitHub PRs/issues Ask me about any of these!`; } }, fallbackIntentDetection(query) { const lowerQuery = query.toLowerCase().trim(); // Helper function to extract branch name const extractBranchName = (query) => { // Look for patterns like "branch called X", "to X branch", "on branch X", "push to X" const branchCalledMatch = query.match(/branch\s+called\s+['"]?([^'"\s]+)['"]?/i); if (branchCalledMatch) return branchCalledMatch[1]; const toBranchMatch = query.match(/to\s+['"]?([^'"\s]+)\s+branch/i); if (toBranchMatch) return toBranchMatch[1]; const onBranchMatch = query.match(/on\s+branch\s+['"]?([^'"\s]+)['"]?/i); if (onBranchMatch) return onBranchMatch[1]; const pushToMatch = query.match(/push\s+to\s+['"]?([^'"\s]+)['"]?/i); if (pushToMatch) return pushToMatch[1]; return null; }; // Repository-related intents - PRIORITIZE list over create if (lowerQuery.includes('repo') || lowerQuery.includes('repository')) { if (lowerQuery.includes('list') || lowerQuery.includes('show') || lowerQuery.includes('display') || lowerQuery.includes('what') || lowerQuery.includes('my repositories') || lowerQuery.includes('my repos')) { return { intent: 'list_repos', entities: {}, confidence: 0.9 }; } if (lowerQuery.includes('create') || lowerQuery.includes('new') || lowerQuery.includes('make')) { return { intent: 'create_repo', entities: {}, confidence: 0.9 }; } if (lowerQuery.includes('clone') || lowerQuery.includes('download')) { return { intent: 'clone_repo', entities: {}, confidence: 0.8 }; } } // Branch-related intents if (lowerQuery.includes('branch')) { if (lowerQuery.includes('list') || lowerQuery.includes('show') || lowerQuery.includes('display')) { return { intent: 'list_branches', entities: {}, confidence: 0.9 }; } if (lowerQuery.includes('create') || lowerQuery.includes('new')) { const branchName = extractBranchName(query); return { intent: 'create_branch', entities: branchName ? { branch: branchName } : {}, confidence: 0.8 }; } if (lowerQuery.includes('checkout') || lowerQuery.includes('switch')) { const branchName = extractBranchName(query); return { intent: 'checkout_branch', entities: branchName ? { branch: branchName } : {}, confidence: 0.8 }; } if (lowerQuery.includes('merge')) { return { intent: 'merge_branch', entities: {}, confidence: 0.8 }; } } // Status and diff intents - PRIORITIZE these over generic repo operations if (lowerQuery.includes('status') || lowerQuery.includes('what\'s changed') || lowerQuery.includes('current status') || (lowerQuery.includes('show') && lowerQuery.includes('change'))) { return { intent: 'git_status', entities: {}, confidence: 0.9 }; } if (lowerQuery.includes('diff') || lowerQuery.includes('difference') || lowerQuery.includes('differences')) { return { intent: 'git_diff', entities: {}, confidence: 0.9 }; } if (lowerQuery.includes('log') || lowerQuery.includes('history') || lowerQuery.includes('commits')) { return { intent: 'git_log', entities: {}, confidence: 0.8 }; } // Git operations - PRIORITIZE these over generic repo operations if (lowerQuery.includes('push')) { const branchName = extractBranchName(query); return { intent: 'push_changes', entities: { branch: branchName || 'current', remote: 'origin' }, confidence: 0.9 }; } if (lowerQuery.includes('pull') || lowerQuery.includes('sync')) { return { intent: 'pull_changes', entities: { remote: 'origin' }, confidence: 0.9 }; } if (lowerQuery.includes('commit')) { return { intent: 'git_commit', entities: {}, confidence: 0.8 }; } if (lowerQuery.includes('add') || lowerQuery.includes('stage')) { return { intent: 'git_add', entities: {}, confidence: 0.8 }; } if (lowerQuery.includes('revert')) { return { intent: 'revert_commit', entities: {}, confidence: 0.8 }; } // Pull request intents if (lowerQuery.includes('pr') || lowerQuery.includes('pull request') || lowerQuery.includes('merge request')) { return { intent: 'create_pr', entities: {}, confidence: 0.9 }; } // Help and authentication intents if (lowerQuery.includes('help') || lowerQuery.includes('what can') || lowerQuery.includes('capabilities')) { return { intent: 'help', entities: {}, confidence: 0.9 }; } if (lowerQuery.includes('login') || lowerQuery.includes('authenticate') || lowerQuery.includes('logged in')) { return { intent: 'greeting', entities: {}, confidence: 0.8 }; } // Greeting intents if (lowerQuery.includes('hello') || lowerQuery.includes('hi') || lowerQuery.includes('hey')) { return { intent: 'greeting', entities: {}, confidence: 0.9 }; } return { intent: 'unknown', entities: { error: 'Could not determine intent' }, confidence: 0.0 }; }, async handleUserQuery(query, username = null) { try { // First detect the conversation type const { type, response, immediate } = detectConversationType(query); // If we have an immediate response, return it directly if (immediate && response) { return { response, requiresConfirmation: false }; } if (immediate && type === CONVERSATION_TYPES.HELP) { const helpResponse = await this.generateCommandHelp(); return { response: helpResponse, requiresConfirmation: false }; } if (immediate && type === CONVERSATION_TYPES.UNRELATED) { return { response: response || "I'm specialized in Git operations. I can help you with version control, repositories, and GitHub-related tasks.", requiresConfirmation: false }; } // For Git operations, parse the intent and prepare confirmation if (type === CONVERSATION_TYPES.GIT_OPERATION || type === CONVERSATION_TYPES.GITHUB_OPERATION) { const parsedIntent = await this.parseIntent(query); const confirmation = await this.generateConfirmation(parsedIntent, username); return { response: confirmation, intent: parsedIntent, requiresConfirmation: true }; } // For greetings and thanks, handle immediately if (type === CONVERSATION_TYPES.GREETING || type === CONVERSATION_TYPES.THANKS) { const conversationResponse = await this.handleConversation(type, username); return { response: conversationResponse, requiresConfirmation: false }; } // Fallback: treat as Git operation const parsedIntent = await this.parseIntent(query); const confirmation = await this.generateConfirmation(parsedIntent, username); return { response: confirmation, intent: parsedIntent, requiresConfirmation: true }; } catch (error) { logger.error('Error handling user query:', { message: error.message, service: serviceName }); return { response: "Sorry, I encountered an error processing your request. Could you try again?", requiresConfirmation: false }; } } };