UNPKG

component-genie-mcp

Version:

A simplified Model Context Protocol server for querying React components with complete content access

582 lines (485 loc) 17.4 kB
// 组件组合器 - 基于现有组件生成页面代码 import fs from 'fs/promises'; import path from 'path'; class ComponentComposer { constructor(projectRoot) { this.projectRoot = projectRoot; this.patterns = new Map(); this.componentUsageExamples = new Map(); this.initializePatterns(); } // 初始化常见的组合模式 initializePatterns() { this.patterns.set('data-table', { name: '数据表格页面', description: '包含搜索、表格、分页的数据展示页面', keywords: ['表格', '列表', '数据', 'table', 'list'], components: ['Table', 'SearchInput', 'Pagination', 'Button'], layout: 'list-page', complexity: 'medium' }); this.patterns.set('form-page', { name: '表单页面', description: '包含表单字段、验证、提交的数据输入页面', keywords: ['表单', '输入', '提交', 'form', 'input'], components: ['Form', 'Input', 'Button', 'Card'], layout: 'form-page', complexity: 'low' }); this.patterns.set('dashboard', { name: '仪表盘页面', description: '包含统计卡片、图表的数据概览页面', keywords: ['仪表盘', '统计', '图表', 'dashboard', 'chart'], components: ['Card', 'Chart', 'Grid', 'Badge'], layout: 'dashboard', complexity: 'high' }); this.patterns.set('detail-page', { name: '详情页面', description: '展示单个实体详细信息的页面', keywords: ['详情', '查看', '展示', 'detail', 'view'], components: ['Card', 'Tabs', 'Button', 'Badge'], layout: 'detail-page', complexity: 'low' }); } // 分析用户需求并推荐组件组合 async analyzeAndRecommend(userDescription) { const analysis = this.analyzeUserIntent(userDescription); const recommendations = await this.generateRecommendations(analysis); return { analysis, recommendations: recommendations.slice(0, 3), // 返回前3个推荐 fallbackComponents: this.suggestFallbackComponents(analysis) }; } // 分析用户意图 analyzeUserIntent(description) { const lowerDesc = description.toLowerCase(); const keywords = lowerDesc.split(/[\s,,。!?]+/).filter(w => w.length > 0); const intentScores = new Map(); // 计算与每个模式的匹配度 for (const [patternId, pattern] of this.patterns) { let score = 0; // 关键词匹配 for (const keyword of pattern.keywords) { if (keywords.some(k => k.includes(keyword) || keyword.includes(k))) { score += 10; } } // 描述匹配 if (lowerDesc.includes(pattern.name.toLowerCase())) { score += 15; } intentScores.set(patternId, score); } // 识别具体功能需求 const features = this.identifyFeatures(lowerDesc); return { keywords, intentScores: Object.fromEntries(intentScores), features, complexity: this.estimateComplexity(features) }; } // 识别功能特征 identifyFeatures(description) { const features = { search: /搜索|查找|筛选|过滤/.test(description), pagination: /分页|翻页/.test(description), sort: /排序|按.*排/.test(description), filter: /筛选|过滤|条件/.test(description), create: /新增|添加|创建|新建/.test(description), edit: /编辑|修改|更新/.test(description), delete: /删除|移除/.test(description), export: /导出|下载/.test(description), import: /导入|上传/.test(description), chart: /图表|统计|可视化/.test(description), tabs: /标签页|选项卡|tab/.test(description), modal: /弹窗|模态框|对话框/.test(description), responsive: /响应式|移动端|手机/.test(description) }; return features; } // 估算复杂度 estimateComplexity(features) { const featureCount = Object.values(features).filter(Boolean).length; if (featureCount <= 2) return 'low'; if (featureCount <= 5) return 'medium'; return 'high'; } // 生成推荐方案 async generateRecommendations(analysis) { const recommendations = []; // 基于意图得分排序 const sortedIntents = Object.entries(analysis.intentScores) .sort(([,a], [,b]) => b - a) .filter(([,score]) => score > 0); for (const [patternId, score] of sortedIntents.slice(0, 3)) { const pattern = this.patterns.get(patternId); const recommendation = await this.createRecommendation(pattern, analysis, score); recommendations.push(recommendation); } // 如果没有明确的模式匹配,提供通用组合 if (recommendations.length === 0) { recommendations.push(await this.createGenericRecommendation(analysis)); } return recommendations; } // 创建具体推荐 async createRecommendation(pattern, analysis, score) { const components = [...pattern.components]; // 根据功能特征添加额外组件 if (analysis.features.search && !components.includes('SearchInput')) { components.push('SearchInput'); } if (analysis.features.modal && !components.includes('Modal')) { components.push('Modal'); } if (analysis.features.pagination && !components.includes('Pagination')) { components.push('Pagination'); } // 获取组件使用示例 const componentExamples = await this.getComponentExamples(components); return { id: pattern.name, name: pattern.name, description: pattern.description, confidence: Math.min(0.9, score / 30), // 归一化到0-0.9 components, layout: pattern.layout, complexity: pattern.complexity, estimatedTime: this.estimateImplementationTime(pattern.complexity, components.length), codeGeneration: { canGenerate: true, examples: componentExamples } }; } // 获取组件使用示例 async getComponentExamples(components) { const examples = {}; for (const component of components) { const example = await this.findComponentExample(component); if (example) { examples[component] = example; } } return examples; } // 查找组件使用示例 async findComponentExample(componentName) { const possiblePaths = [ `src/components/${componentName}/index.tsx`, `src/components/${componentName}.tsx`, `src/components/ui/${componentName.toLowerCase()}.tsx`, `src/examples/${componentName}Example.tsx` ]; for (const relativePath of possiblePaths) { const fullPath = path.join(this.projectRoot, relativePath); try { const content = await fs.readFile(fullPath, 'utf-8'); return { path: relativePath, content: this.extractUsageExample(content, componentName) }; } catch (error) { // 文件不存在,继续尝试下一个 continue; } } return null; } // 提取使用示例 extractUsageExample(content, componentName) { // 简单提取:找到组件的基本使用方式 const lines = content.split('\n'); const usageLines = []; let inExample = false; for (const line of lines) { // 寻找组件使用示例 if (line.includes(`<${componentName}`) || line.includes(`{${componentName}`)) { inExample = true; } if (inExample) { usageLines.push(line); // 简单的结束判断 if (line.includes(`</${componentName}>`) || line.includes('/>')) { break; } } // 限制示例长度 if (usageLines.length > 10) break; } return usageLines.join('\n').trim(); } // 估算实现时间 estimateImplementationTime(complexity, componentCount) { const baseTime = { low: 15, medium: 30, high: 60 }; const additionalTime = Math.max(0, componentCount - 3) * 5; const totalMinutes = baseTime[complexity] + additionalTime; return `${totalMinutes}-${totalMinutes + 15}分钟`; } // 生成代码 async generateCode(recommendation, customization = {}) { const { components, layout, examples } = recommendation.codeGeneration; // 生成基本的页面结构 const pageCode = this.generatePageStructure(layout, components, examples, customization); return { mainFile: pageCode, additionalFiles: await this.generateAdditionalFiles(components, customization), imports: this.generateImports(components), dependencies: this.identifyDependencies(components) }; } // 生成页面结构 generatePageStructure(layout, components, examples, customization) { const { pageName = 'NewPage', title = '新页面', description = '这是一个新页面' } = customization; let code = `import React, { useState } from 'react';\n`; // 添加组件导入 code += this.generateImports(components); code += '\n'; // 生成页面组件 code += `export function ${pageName}() {\n`; code += ` const [loading, setLoading] = useState(false);\n\n`; code += ` return (\n`; code += ` <div className="container mx-auto p-6 space-y-6">\n`; code += ` {/* 页面标题 */}\n`; code += ` <div>\n`; code += ` <h1 className="text-3xl font-bold">${title}</h1>\n`; code += ` <p className="text-muted-foreground">${description}</p>\n`; code += ` </div>\n\n`; // 根据布局类型生成不同的结构 switch (layout) { case 'list-page': code += this.generateListLayout(components, examples); break; case 'form-page': code += this.generateFormLayout(components, examples); break; case 'dashboard': code += this.generateDashboardLayout(components, examples); break; case 'detail-page': code += this.generateDetailLayout(components, examples); break; default: code += this.generateGenericLayout(components, examples); } code += ` </div>\n`; code += ` );\n`; code += `}\n`; return code; } // 生成列表页面布局 generateListLayout(components, examples) { let code = ` {/* 操作区 */}\n`; code += ` <Card>\n`; code += ` <CardContent className="p-4">\n`; code += ` <div className="flex items-center justify-between">\n`; if (components.includes('SearchInput')) { code += ` <SearchInput placeholder="搜索..." />\n`; } if (components.includes('Button')) { code += ` <Button>新增</Button>\n`; } code += ` </div>\n`; code += ` </CardContent>\n`; code += ` </Card>\n\n`; if (components.includes('Table')) { code += ` {/* 数据表格 */}\n`; code += ` <Card>\n`; code += ` <CardContent>\n`; code += ` <Table>\n`; code += ` {/* 表格内容 */}\n`; code += ` </Table>\n`; code += ` </CardContent>\n`; code += ` </Card>\n\n`; } if (components.includes('Pagination')) { code += ` {/* 分页 */}\n`; code += ` <Pagination />\n\n`; } return code; } // 生成表单页面布局 generateFormLayout(components, examples) { let code = ` {/* 表单区域 */}\n`; code += ` <Card className="max-w-2xl">\n`; code += ` <CardHeader>\n`; code += ` <CardTitle>表单标题</CardTitle>\n`; code += ` </CardHeader>\n`; code += ` <CardContent>\n`; code += ` <form className="space-y-4">\n`; if (components.includes('Input')) { code += ` <div>\n`; code += ` <label className="block text-sm font-medium mb-2">输入字段</label>\n`; code += ` <Input placeholder="请输入..." />\n`; code += ` </div>\n`; } if (components.includes('Button')) { code += ` <div className="flex gap-2">\n`; code += ` <Button type="submit">提交</Button>\n`; code += ` <Button variant="outline">取消</Button>\n`; code += ` </div>\n`; } code += ` </form>\n`; code += ` </CardContent>\n`; code += ` </Card>\n\n`; return code; } // 生成仪表盘布局 generateDashboardLayout(components, examples) { let code = ` {/* 统计卡片 */}\n`; code += ` <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">\n`; for (let i = 0; i < 4; i++) { if (components.includes('Card')) { code += ` <Card>\n`; code += ` <CardHeader>\n`; code += ` <CardTitle>指标 ${i + 1}</CardTitle>\n`; code += ` </CardHeader>\n`; code += ` <CardContent>\n`; code += ` <div className="text-2xl font-bold">1,234</div>\n`; code += ` </CardContent>\n`; code += ` </Card>\n`; } } code += ` </div>\n\n`; if (components.includes('Chart')) { code += ` {/* 图表区域 */}\n`; code += ` <div className="grid grid-cols-1 lg:grid-cols-2 gap-6">\n`; code += ` <Card>\n`; code += ` <CardHeader>\n`; code += ` <CardTitle>图表1</CardTitle>\n`; code += ` </CardHeader>\n`; code += ` <CardContent>\n`; code += ` {/* 图表组件 */}\n`; code += ` </CardContent>\n`; code += ` </Card>\n`; code += ` </div>\n\n`; } return code; } // 生成详情页面布局 generateDetailLayout(components, examples) { let code = ` {/* 详情内容 */}\n`; if (components.includes('Tabs')) { code += ` <Tabs defaultValue="basic">\n`; code += ` <TabsList>\n`; code += ` <TabsTrigger value="basic">基本信息</TabsTrigger>\n`; code += ` <TabsTrigger value="advanced">详细信息</TabsTrigger>\n`; code += ` </TabsList>\n`; code += ` <TabsContent value="basic">\n`; } if (components.includes('Card')) { code += ` <Card>\n`; code += ` <CardContent className="p-6">\n`; code += ` {/* 详情信息 */}\n`; code += ` </CardContent>\n`; code += ` </Card>\n`; } if (components.includes('Tabs')) { code += ` </TabsContent>\n`; code += ` </Tabs>\n`; } code += `\n`; return code; } // 生成通用布局 generateGenericLayout(components, examples) { let code = ` {/* 内容区域 */}\n`; code += ` <div className="space-y-6">\n`; for (const component of components) { code += ` {/* ${component} */}\n`; code += ` <${component} />\n`; } code += ` </div>\n\n`; return code; } // 生成导入语句 generateImports(components) { const imports = []; // UI组件导入 const uiComponents = components.filter(c => ['Button', 'Input', 'Card', 'Table', 'Badge'].includes(c) ); if (uiComponents.length > 0) { imports.push(`import { ${uiComponents.join(', ')} } from '@/components/ui';`); } // 其他组件导入 const otherComponents = components.filter(c => !['Button', 'Input', 'Card', 'Table', 'Badge'].includes(c) ); for (const component of otherComponents) { imports.push(`import { ${component} } from '@/components/${component}';`); } return imports.join('\n'); } // 识别依赖 identifyDependencies(components) { const deps = new Set(['react']); if (components.includes('Chart')) { deps.add('recharts'); } if (components.includes('Form')) { deps.add('react-hook-form'); deps.add('zod'); } return Array.from(deps); } // 生成额外文件 async generateAdditionalFiles(components, customization) { const files = {}; // 如果包含表单,生成验证schema if (components.includes('Form')) { files['schema.ts'] = this.generateFormSchema(customization); } // 如果包含表格,生成列配置 if (components.includes('Table')) { files['columns.ts'] = this.generateTableColumns(customization); } return files; } generateFormSchema(customization) { return `import { z } from 'zod'; export const formSchema = z.object({ // 在此添加字段验证 name: z.string().min(1, '名称不能为空'), email: z.string().email('请输入有效的邮箱地址'), }); export type FormData = z.infer<typeof formSchema>;`; } generateTableColumns(customization) { return `export const columns = [ { key: 'id', label: 'ID', sortable: true, }, { key: 'name', label: '名称', sortable: true, }, { key: 'status', label: '状态', sortable: true, }, // 添加更多列... ];`; } } export default ComponentComposer;