224 lines
6.6 KiB
JavaScript
224 lines
6.6 KiB
JavaScript
|
|
/**
|
|||
|
|
* persona-studio · 代码生成引擎
|
|||
|
|
*
|
|||
|
|
* 从对话历史中提取需求 → 调用 model-router → 生成代码 → 写入 workspace
|
|||
|
|
*/
|
|||
|
|
const fs = require('fs');
|
|||
|
|
const path = require('path');
|
|||
|
|
const modelRouter = require('./model-router');
|
|||
|
|
const knowledgeExtractor = require('./knowledge-extractor');
|
|||
|
|
const patternAnalyzer = require('./pattern-analyzer');
|
|||
|
|
const evolutionLogger = require('./evolution-logger');
|
|||
|
|
|
|||
|
|
const WORKSPACE_DIR = path.join(__dirname, '..', '..', 'workspace');
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 从对话历史中提取项目需求摘要
|
|||
|
|
*/
|
|||
|
|
function extractRequirements(conversation) {
|
|||
|
|
const userMessages = conversation
|
|||
|
|
.filter(function (m) { return m.role === 'user'; })
|
|||
|
|
.map(function (m) { return m.content; });
|
|||
|
|
|
|||
|
|
return userMessages.join('\n');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 生成项目代码
|
|||
|
|
* @param {object} params
|
|||
|
|
* @param {string} params.dev_id - 开发编号
|
|||
|
|
* @param {Array} params.conversation - 对话历史
|
|||
|
|
* @returns {Promise<{projectName: string, files: string[], summary: string}>}
|
|||
|
|
*/
|
|||
|
|
async function generate({ dev_id, conversation }) {
|
|||
|
|
const requirements = extractRequirements(conversation);
|
|||
|
|
const projectName = 'project-' + Date.now();
|
|||
|
|
const projectDir = path.join(WORKSPACE_DIR, dev_id, projectName);
|
|||
|
|
const startTime = Date.now();
|
|||
|
|
|
|||
|
|
// 确保工作目录存在
|
|||
|
|
fs.mkdirSync(projectDir, { recursive: true });
|
|||
|
|
|
|||
|
|
const apiKey = process.env.MODEL_API_KEY || '';
|
|||
|
|
|
|||
|
|
if (!apiKey) {
|
|||
|
|
// 无 API 密钥时生成模板项目
|
|||
|
|
const result = generateTemplate(projectDir, projectName, requirements);
|
|||
|
|
triggerPostBuildEvolution(dev_id, conversation, result, startTime, true);
|
|||
|
|
return result;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
const { model, baseUrl } = modelRouter.selectModel('code_generation');
|
|||
|
|
|
|||
|
|
const codePrompt = [
|
|||
|
|
'你是一个代码生成引擎。根据以下需求生成完整的项目代码。',
|
|||
|
|
'输出格式要求:',
|
|||
|
|
'1. 先输出项目结构概览',
|
|||
|
|
'2. 然后逐个文件输出,每个文件用 ```filename.ext 和 ``` 包裹',
|
|||
|
|
'3. 最后输出一段使用说明',
|
|||
|
|
'',
|
|||
|
|
'需求描述:',
|
|||
|
|
requirements
|
|||
|
|
].join('\n');
|
|||
|
|
|
|||
|
|
const reply = await modelRouter.callModel({
|
|||
|
|
model,
|
|||
|
|
baseUrl,
|
|||
|
|
apiKey,
|
|||
|
|
messages: [{ role: 'user', content: codePrompt }],
|
|||
|
|
maxTokens: 4000,
|
|||
|
|
temperature: 0.3
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 解析代码块并写入文件
|
|||
|
|
const files = parseAndWriteFiles(projectDir, reply);
|
|||
|
|
|
|||
|
|
// 写入 README
|
|||
|
|
const readmePath = path.join(projectDir, 'README.md');
|
|||
|
|
if (!fs.existsSync(readmePath)) {
|
|||
|
|
fs.writeFileSync(readmePath, [
|
|||
|
|
'# ' + projectName,
|
|||
|
|
'',
|
|||
|
|
'## 需求描述',
|
|||
|
|
requirements.substring(0, 500),
|
|||
|
|
'',
|
|||
|
|
'## 生成说明',
|
|||
|
|
'由光湖 Persona Studio 铸渊自动生成',
|
|||
|
|
'生成时间:' + new Date().toISOString()
|
|||
|
|
].join('\n'), 'utf-8');
|
|||
|
|
files.push('README.md');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const result = {
|
|||
|
|
projectName,
|
|||
|
|
files,
|
|||
|
|
summary: `项目 ${projectName} 已生成,包含 ${files.length} 个文件。`
|
|||
|
|
};
|
|||
|
|
triggerPostBuildEvolution(dev_id, conversation, result, startTime, true);
|
|||
|
|
return result;
|
|||
|
|
} catch (err) {
|
|||
|
|
console.error('Code generation failed:', err.message);
|
|||
|
|
const result = generateTemplate(projectDir, projectName, requirements);
|
|||
|
|
triggerPostBuildEvolution(dev_id, conversation, result, startTime, false);
|
|||
|
|
return result;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 项目完成后触发自进化:知识提取 + 模式识别
|
|||
|
|
*/
|
|||
|
|
function triggerPostBuildEvolution(devId, conversation, result, startTime, success) {
|
|||
|
|
try {
|
|||
|
|
const buildTimeMs = Date.now() - startTime;
|
|||
|
|
|
|||
|
|
// 知识提取
|
|||
|
|
knowledgeExtractor.autoExtractKnowledge(devId, conversation, result.projectName);
|
|||
|
|
|
|||
|
|
// 模式识别
|
|||
|
|
const patterns = patternAnalyzer.analyzeAndUpdatePatterns(
|
|||
|
|
devId, conversation, result.files, buildTimeMs, success
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
// 进化日志
|
|||
|
|
evolutionLogger.logEvent('build_complete', '项目构建完成: ' + result.projectName, {
|
|||
|
|
dev_id: devId,
|
|||
|
|
project: result.projectName,
|
|||
|
|
files_count: (result.files || []).length,
|
|||
|
|
build_time_ms: buildTimeMs,
|
|||
|
|
success: success,
|
|||
|
|
patterns_detected: patterns
|
|||
|
|
});
|
|||
|
|
} catch (err) {
|
|||
|
|
console.error('Post-build evolution error:', err.message);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 解析 AI 回复中的代码块并写入文件
|
|||
|
|
*/
|
|||
|
|
function parseAndWriteFiles(projectDir, reply) {
|
|||
|
|
const files = [];
|
|||
|
|
const codeBlockRe = /```(\S+)\n([\s\S]*?)```/g;
|
|||
|
|
let match;
|
|||
|
|
|
|||
|
|
while ((match = codeBlockRe.exec(reply)) !== null) {
|
|||
|
|
let filename = match[1];
|
|||
|
|
const content = match[2];
|
|||
|
|
|
|||
|
|
// 跳过语言标识符(不是文件名的情况)
|
|||
|
|
if (['javascript', 'js', 'html', 'css', 'json', 'python', 'bash', 'sh', 'typescript', 'ts'].includes(filename)) {
|
|||
|
|
continue;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 安全检查:防止路径遍历
|
|||
|
|
filename = path.basename(filename);
|
|||
|
|
if (!filename || filename.startsWith('.')) continue;
|
|||
|
|
|
|||
|
|
const filePath = path.join(projectDir, filename);
|
|||
|
|
fs.writeFileSync(filePath, content, 'utf-8');
|
|||
|
|
files.push(filename);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return files;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 生成模板项目(无 API 密钥时的降级方案)
|
|||
|
|
*/
|
|||
|
|
function generateTemplate(projectDir, projectName, requirements) {
|
|||
|
|
const files = [];
|
|||
|
|
|
|||
|
|
// 生成 index.html
|
|||
|
|
const htmlContent = [
|
|||
|
|
'<!DOCTYPE html>',
|
|||
|
|
'<html lang="zh-CN">',
|
|||
|
|
'<head>',
|
|||
|
|
' <meta charset="UTF-8">',
|
|||
|
|
' <meta name="viewport" content="width=device-width, initial-scale=1.0">',
|
|||
|
|
' <title>' + projectName + '</title>',
|
|||
|
|
' <link rel="stylesheet" href="style.css">',
|
|||
|
|
'</head>',
|
|||
|
|
'<body>',
|
|||
|
|
' <h1>🌊 ' + projectName + '</h1>',
|
|||
|
|
' <p>由光湖 Persona Studio 生成</p>',
|
|||
|
|
' <script src="main.js"></script>',
|
|||
|
|
'</body>',
|
|||
|
|
'</html>'
|
|||
|
|
].join('\n');
|
|||
|
|
fs.writeFileSync(path.join(projectDir, 'index.html'), htmlContent, 'utf-8');
|
|||
|
|
files.push('index.html');
|
|||
|
|
|
|||
|
|
// 生成 style.css
|
|||
|
|
fs.writeFileSync(path.join(projectDir, 'style.css'), 'body { font-family: sans-serif; padding: 2rem; }\n', 'utf-8');
|
|||
|
|
files.push('style.css');
|
|||
|
|
|
|||
|
|
// 生成 main.js
|
|||
|
|
fs.writeFileSync(path.join(projectDir, 'main.js'), 'console.log("Project initialized by Persona Studio");\n', 'utf-8');
|
|||
|
|
files.push('main.js');
|
|||
|
|
|
|||
|
|
// 生成 README
|
|||
|
|
fs.writeFileSync(path.join(projectDir, 'README.md'), [
|
|||
|
|
'# ' + projectName,
|
|||
|
|
'',
|
|||
|
|
'## 需求描述',
|
|||
|
|
requirements.substring(0, 500),
|
|||
|
|
'',
|
|||
|
|
'> 模板项目(AI 模型尚未配置,请管理员设置 MODEL_API_KEY)',
|
|||
|
|
'',
|
|||
|
|
'生成时间:' + new Date().toISOString()
|
|||
|
|
].join('\n'), 'utf-8');
|
|||
|
|
files.push('README.md');
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
projectName,
|
|||
|
|
files,
|
|||
|
|
summary: `模板项目 ${projectName} 已生成(${files.length} 个文件)。待 API 密钥配置后可生成完整代码。`
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
module.exports = {
|
|||
|
|
generate,
|
|||
|
|
extractRequirements
|
|||
|
|
};
|