UNPKG

mini-claude-code

Version:

Advanced AI-powered coding assistant with streaming responses, context memory, intelligent auto-completion, error handling, test generation, and task planning

426 lines (363 loc) 14.3 kB
const DeepSeekAI = require('./deepseek-ai'); const chalk = require('chalk'); class NaturalLanguageProcessor { constructor(toolManager) { this.toolManager = toolManager; this.ai = new DeepSeekAI(); this.name = 'NaturalLanguageProcessor'; // 指令模式映射 this.commandPatterns = { // 文件操作 read: ['read', 'show', 'display', '读取', '显示', '查看'], write: ['write', 'create', 'make', '写入', '创建', '生成'], edit: ['edit', 'modify', 'change', 'update', '编辑', '修改', '更改'], delete: ['delete', 'remove', '删除', '移除'], // 搜索操作 search: ['search', 'find', 'grep', '搜索', '查找', '找'], // 命令执行 run: ['run', 'execute', 'exec', '执行', '运行'], // 项目操作 analyze: ['analyze', 'scan', 'check', '分析', '扫描', '检查'], list: ['list', 'ls', 'dir', '列出', '显示文件'], // AI 操作 explain: ['explain', 'describe', 'tell me about', '解释', '说明', '介绍'], generate: ['generate', 'code', 'build', '生成', '代码'], review: ['review', 'check code', '审查', '检查代码'], fix: ['fix', 'solve', 'debug', '修复', '解决', '调试'] }; // 文件类型检测 this.fileTypes = { component: ['component', 'comp', '组件'], page: ['page', 'view', '页面', '视图'], service: ['service', 'api', '服务', '接口'], util: ['util', 'helper', 'tool', '工具', '辅助'], test: ['test', 'spec', '测试'], config: ['config', 'setting', '配置'], style: ['style', 'css', 'scss', '样式'], doc: ['doc', 'readme', 'md', '文档'] }; } /** * 处理自然语言输入 */ async processInput(input, context = null) { try { console.log(`🤔 Processing: "${input}"`); // 首先尝试本地模式匹配(快速响应) const localResult = this.parseLocally(input); if (localResult.confidence > 0.8) { console.log(`⚡ Using local parsing (confidence: ${(localResult.confidence * 100).toFixed(0)}%)`); return await this.executeAction(localResult.action, localResult.parameters, context); } // 如果本地解析置信度不高,使用 AI 解析 if (this.ai.isAvailable()) { console.log(`🧠 Using AI parsing...`); const aiResult = await this.ai.parseCommand(input, context?.projectInfo); if (aiResult.success) { return await this.executeAction(aiResult.action, aiResult.parameters, context); } else { console.log(`❌ AI parsing failed: ${aiResult.error}`); // 回退到本地解析 return await this.executeAction(localResult.action, localResult.parameters, context); } } else { console.log(`⚠️ AI not available, using local parsing`); return await this.executeAction(localResult.action, localResult.parameters, context); } } catch (error) { return { success: false, error: error.message, suggestion: 'Try being more specific about what you want to do.' }; } } /** * 本地解析自然语言(基于规则) */ parseLocally(input) { const lowerInput = input.toLowerCase(); let action = 'chat'; let parameters = { message: input }; let confidence = 0.1; // 文件路径检测 const filePathMatch = input.match(/[\w\-\.\/]+\.(js|ts|jsx|tsx|py|java|cpp|c|html|css|json|md|txt|yml|yaml)[\w\-\.\/]*/gi); const filePath = filePathMatch ? filePathMatch[0] : null; // 文件名检测 const fileNameMatch = input.match(/[\w\-\.]+\.(js|ts|jsx|tsx|py|java|cpp|c|html|css|json|md|txt|yml|yaml)/gi); const fileName = fileNameMatch ? fileNameMatch[0] : null; // 检测操作类型 for (const [actionType, patterns] of Object.entries(this.commandPatterns)) { for (const pattern of patterns) { if (lowerInput.includes(pattern)) { action = actionType; confidence = Math.max(confidence, 0.6); break; } } if (confidence >= 0.6) break; } // 根据操作类型设置参数 switch (action) { case 'read': if (filePath) { parameters = { filePath }; confidence = 0.9; } else { parameters = { filePath: this.extractFileName(input) || '.' }; confidence = 0.5; } action = 'read_file'; break; case 'write': if (this.containsCodeBlock(input)) { const { fileName: extractedName, content } = this.extractCodeAndFileName(input); parameters = { filePath: extractedName || fileName || 'output.txt', content: content || input }; confidence = 0.8; } else { // 检测是否是创建文件 const fileType = this.detectFileType(input); if (fileType) { parameters = { fileType, fileName: this.extractFileName(input) || 'NewFile' }; action = 'create_file'; confidence = 0.7; } else { parameters = { filePath: fileName || 'output.txt', content: this.extractContent(input) }; action = 'write_file'; confidence = 0.6; } } break; case 'edit': if (filePath) { const { oldText, newText } = this.extractEditParams(input); parameters = { filePath, oldText, newText }; confidence = oldText && newText ? 0.8 : 0.5; } action = 'edit_file'; break; case 'search': const searchTerm = this.extractSearchTerm(input); parameters = { searchTerm, path: this.extractPath(input) || '.' }; confidence = searchTerm ? 0.8 : 0.4; action = 'search_files'; break; case 'run': const command = this.extractCommand(input); parameters = { command }; confidence = command ? 0.8 : 0.4; action = 'run_command'; break; case 'analyze': parameters = { path: this.extractPath(input) || '.' }; confidence = 0.7; action = 'analyze_project'; break; case 'list': parameters = { path: this.extractPath(input) || '.' }; confidence = 0.7; action = 'list_files'; break; default: // 默认为对话 action = 'chat'; parameters = { message: input }; confidence = 0.1; } return { action, parameters, confidence }; } /** * 执行解析后的操作 */ async executeAction(action, parameters, context) { try { switch (action) { case 'read_file': return await this.toolManager.execute('fs', 'readFile', parameters.filePath); case 'write_file': return await this.toolManager.execute('fs', 'writeFile', parameters.filePath, parameters.content); case 'edit_file': return await this.toolManager.execute('fs', 'editFile', parameters.filePath, parameters.oldText, parameters.newText); case 'create_file': const codeGen = this.toolManager.tools.codeGenerator || require('./code-generator'); const result = await codeGen.generateFile(parameters.fileType, parameters.fileName, context?.projectInfo); if (result.success) { return await this.toolManager.execute('fs', 'writeFile', result.filePath, result.content); } return result; case 'search_files': return await this.toolManager.execute('fs', 'searchInFiles', parameters.path, parameters.searchTerm); case 'run_command': return await this.toolManager.execute('cmd', 'run', parameters.command); case 'list_files': return await this.toolManager.execute('fs', 'listFiles', parameters.path); case 'analyze_project': return await this.toolManager.tools.analyzer.analyzeProject(parameters.path); case 'chat': // 如果是对话,尝试使用 AI 流式响应 if (this.ai.isAvailable()) { console.log(chalk.blue('\n🤖 AI Assistant:')); let responseStarted = false; let fullResponse = ''; try { // 使用工具管理器的增强上下文聊天功能 const contextPrompt = this.toolManager.memory.generateContextPrompt(parameters.message, context?.projectInfo); const result = await this.ai.chatStream(parameters.message, { systemPrompt: `你是一个友好的编程助手。 ${contextPrompt} 请基于以上上下文信息提供有帮助的、专业的回答。如果用户询问过类似问题,请参考历史对话。如果有相关代码片段,可以引用它们。`, temperature: 0.7, timeout: 45000 }, async (data) => { if (!responseStarted) { process.stdout.write(chalk.green('')); responseStarted = true; } if (data.content) { process.stdout.write(data.content); fullResponse = data.fullResponse; } if (data.isComplete) { process.stdout.write('\n'); } }); // 保存对话到记忆系统 if (result.success) { await this.toolManager.memory.addConversation(parameters.message, result.response, { projectPath: context?.projectInfo?.path || process.cwd(), language: context?.projectInfo?.languages ? Object.keys(context.projectInfo.languages)[0] : null, framework: context?.projectInfo?.frameworks?.length > 0 ? context.projectInfo.frameworks[0].name : null, commandType: 'natural_language_chat' }); return { success: true, response: result.response, isStreaming: true }; } else { return result; } } catch (error) { return { success: false, error: error.message }; } } else { return { success: true, response: `我收到了你的消息:"${parameters.message}"。但是 AI 功能暂时不可用,请使用具体的命令,或者输入 "help" 查看可用命令。`, isLocal: true }; } default: return { success: false, error: `Unknown action: ${action}`, suggestion: 'Try using a more specific command or type "help" for available commands.' }; } } catch (error) { return { success: false, error: error.message, action, parameters }; } } // 辅助方法 containsCodeBlock(input) { return input.includes('```') || input.includes('function') || input.includes('class') || input.includes('import'); } extractCodeAndFileName(input) { const codeBlockMatch = input.match(/```[\w]*\n?([\s\S]*?)```/); const fileNameMatch = input.match(/(?:文件名|filename|name|保存为)\s*[::]?\s*([^\s]+)/i); return { content: codeBlockMatch ? codeBlockMatch[1].trim() : null, fileName: fileNameMatch ? fileNameMatch[1] : null }; } detectFileType(input) { const lowerInput = input.toLowerCase(); for (const [type, patterns] of Object.entries(this.fileTypes)) { for (const pattern of patterns) { if (lowerInput.includes(pattern)) { return type; } } } return null; } extractFileName(input) { const fileMatch = input.match(/[\w\-\.]+\.[a-z]+/gi); return fileMatch ? fileMatch[0] : null; } extractContent(input) { // 提取引号内的内容或代码块 const quotedMatch = input.match(/["']([^"']+)["']/); if (quotedMatch) return quotedMatch[1]; const codeMatch = input.match(/```[\w]*\n?([\s\S]*?)```/); if (codeMatch) return codeMatch[1].trim(); // 移除操作词后的内容 const cleanInput = input.replace(/(write|create|make|写入|创建|生成)\s+/i, '').trim(); return cleanInput; } extractEditParams(input) { // 尝试提取 "替换 A 为 B" 模式 const replaceMatch = input.match(/(替换|replace|change)\s+["']?([^"']+)["']?\s+(为|to|with)\s+["']?([^"']+)["']?/i); if (replaceMatch) { return { oldText: replaceMatch[2], newText: replaceMatch[4] }; } // 尝试提取 "A -> B" 模式 const arrowMatch = input.match(/["']?([^"']+)["']?\s*->\s*["']?([^"']+)["']?/); if (arrowMatch) { return { oldText: arrowMatch[1], newText: arrowMatch[2] }; } return { oldText: null, newText: null }; } extractSearchTerm(input) { // 提取引号内的搜索词 const quotedMatch = input.match(/["']([^"']+)["']/); if (quotedMatch) return quotedMatch[1]; // 移除搜索关键词后的内容 const cleanInput = input.replace(/(search|find|grep|搜索|查找|找)\s+/i, '').trim(); return cleanInput.split(/\s+/)[0]; } extractCommand(input) { // 提取引号内的命令 const quotedMatch = input.match(/["']([^"']+)["']/); if (quotedMatch) return quotedMatch[1]; // 移除运行关键词后的内容 const cleanInput = input.replace(/(run|execute|exec|执行|运行)\s+/i, '').trim(); return cleanInput; } extractPath(input) { // 提取路径 const pathMatch = input.match(/[\w\-\.\/]+\/[\w\-\.\/]*|\.{1,2}\/[\w\-\.\/]*/); return pathMatch ? pathMatch[0] : null; } /** * 获取处理统计 */ getStats() { return { aiAvailable: this.ai.isAvailable(), supportedActions: Object.keys(this.commandPatterns), supportedFileTypes: Object.keys(this.fileTypes) }; } } module.exports = NaturalLanguageProcessor;