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
JavaScript
// 组件组合器 - 基于现有组件生成页面代码
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;