UNPKG

bytefun

Version:

一个打通了原型设计、UI设计与代码转换、跨平台原生代码开发等的平台

1,564 lines (1,275 loc) 67.2 kB
import * as vscode from 'vscode'; import * as fs from 'fs'; import * as path from 'path'; import * as os from 'os'; import * as https from 'https'; import * as yauzl from 'yauzl'; import { WorkspaceUtils } from './workspaceUtils'; export interface ProjectInfo { uid: string; projectID: string; projectName: string; enName: string; treeProjectId: number; projectPageList?: any[]; } export interface ChatMessage { id: string; role: 'user' | 'assistant'; content: string; timestamp: number; roleType: 'product' | 'design' | 'frontEndDev' | 'backEndDev' | 'operations'; type?: 'product_overview' | 'ui_overview' | 'code_overview' | 'test_overview' | 'data_overview' | 'normal'; } export interface ChatSession { id: string; title: string; roleType: 'product' | 'design' | 'frontEndDev' | 'backEndDev' | 'operations'; messages: ChatMessage[]; lastMessageTime: number; uid: string; } // 团队聊天消息接口 export interface TeamChatMessage { id: string; role: 'user' | 'assistant'; content: string; timestamp: number; senderRole: 'product' | 'design' | 'frontEndDev' | 'backEndDev' | 'operations'; // 发送者角色 type?: 'product_overview' | 'ui_overview' | 'code_overview' | 'test_overview' | 'data_overview' | 'normal'; } // 团队聊天会话接口 export interface TeamChatSession { id: string; title: string; type: 'team'; messages: TeamChatMessage[]; lastMessageTime: number; uid: string; } export class ProjectManager { private static instance: ProjectManager; private projectsBasePath: string; private chatDataBasePath: string; private constructor() { this.projectsBasePath = this.getDefaultProjectsPath(); this.chatDataBasePath = this.getChatDataBasePath(); this.ensureProjectsDirectory(); this.ensureChatDataDirectory(); } public static getInstance(): ProjectManager { if (!ProjectManager.instance) { ProjectManager.instance = new ProjectManager(); } return ProjectManager.instance; } /** * 打开项目(主要方法) * @param projectInfo 项目信息 * @param zipUrl ZIP文件下载地址,默认为baseCode.zip */ public async openInVsCode( projectInfo: ProjectInfo, zipUrl: string = 'https://oss.bytefungo.com/dev/zip/baseCode.zip' ): Promise<void> { const { projectName, enName } = projectInfo; const projectFolderName = enName || projectName; const projectPath = this.getProjectPath(projectFolderName); try { // 检查项目是否已存在 if (this.projectExists(projectInfo.projectID, projectPath)) { // 步骤4:将项目ID写入项目根目录的.bytefun文件夹的project.json文件 this.writeProjectIDToProjectJson(projectPath, projectInfo.projectID, projectInfo.projectName, projectInfo.treeProjectId, projectInfo.uid); // 初始化所有角色的默认聊天会话(如果还没有的话) this.initializeDefaultChatSessions(projectInfo.uid, projectInfo.projectID); // 初始化团队聊天会话(如果还没有的话) this.createDefaultTeamChatSession(projectInfo.uid, projectInfo.projectID); await this.handleCreatePageTSFiles(projectInfo, projectPath); // 在新的VsCode窗口中打开项目projectPath this.openInVsCodeNewWindow(projectPath); return; } // 显示下载进度通知 await vscode.window.withProgress({ location: vscode.ProgressLocation.Notification, title: `正在准备项目 "${projectName}"`, cancellable: false }, async (progress) => { // 步骤1: 下载ZIP文件 progress.report({ increment: 0, message: '正在下载项目模板...' }); const tempZipPath = path.join(os.tmpdir(), `${projectFolderName}_${Date.now()}.zip`); await this.downloadZip(zipUrl, tempZipPath); // 步骤2: 解压文件 progress.report({ increment: 50, message: '正在解压项目文件...' }); await this.extractZip(tempZipPath, projectPath); // 步骤3: 清理临时文件 progress.report({ increment: 80, message: '正在清理临时文件...' }); try { fs.unlinkSync(tempZipPath); } catch (error) { console.warn(`⚠️ [ProjectManager] 删除临时文件失败: ${error}`); } // 步骤4:将项目ID写入项目根目录的.bytefun文件夹的project.json文件 this.writeProjectIDToProjectJson(projectPath, projectInfo.projectID, projectInfo.projectName, projectInfo.treeProjectId, projectInfo.uid); // 步骤4.5: 初始化所有角色的默认聊天会话 this.initializeDefaultChatSessions(projectInfo.uid, projectInfo.projectID); // 步骤4.6: 初始化团队聊天会话 this.createDefaultTeamChatSession(projectInfo.uid, projectInfo.projectID); // 步骤4.7: 将MCP服务配置写入Cursor的MCP配置文件 this.writeMcpConfiguration(); // 步骤5: 将prpjectPageList里面的页面,调用handleCreatePageTSFiles方法,创建页面ts文件 await this.handleCreatePageTSFiles(projectInfo, projectPath); // 步骤6: 在新的VsCode窗口中打开项目projectPath this.openInVsCodeNewWindow(projectPath); // 步骤N: 项目准备完成 progress.report({ increment: 100, message: '项目准备完成!' }); }); } catch (error) { console.error(`❌ [ProjectManager] 处理项目失败: ${error}`); // 显示错误通知 vscode.window.showErrorMessage( `处理项目 "${projectName}" 时发生错误: ${error}`, '重试', '取消' ).then(selection => { if (selection === '重试') { this.openInVsCode(projectInfo, zipUrl); } }); throw error; } } private writeProjectIDToProjectJson(projectPath: string, projectID: string, projectName: string, treeProjectId: number, uid: string): void { // 检查.bytefun文件夹是否存在 const bytefunDir = path.join(projectPath, '.bytefun'); if (!fs.existsSync(bytefunDir)) { fs.mkdirSync(bytefunDir, { recursive: true }); } // 检查project.json文件是否存在,如果不存在就先创建project.json文件,先不写入 const projectJsonPath = path.join(bytefunDir, 'project.json'); if (!fs.existsSync(projectJsonPath)) { const initialProjectJson = { packageName: 'com.mycompany.myapp', versionName: '1.0.0', versionCode: 1, projectID: projectID, projectName: projectName, treeProjectId: treeProjectId, uid: uid }; fs.writeFileSync(projectJsonPath, JSON.stringify(initialProjectJson, null, 2), 'utf8'); } // 检查project.json文件是否存在 const projectJson = { packageName: 'com.mycompany.myapp', versionName: '1.0.0', versionCode: 1, projectID: projectID, projectName: projectName, treeProjectId: treeProjectId, uid: uid }; fs.writeFileSync(projectJsonPath, JSON.stringify(projectJson, null, 2), 'utf8'); } // 在新的VsCode窗口中打开项目projectPath private async openInVsCodeNewWindow(projectPath: string): Promise<void> { try { // 检查项目路径是否存在 if (!fs.existsSync(projectPath)) { console.error(`❌ [ProjectManager] 项目路径不存在: ${projectPath}`); vscode.window.showErrorMessage(`项目路径不存在: ${projectPath}`); return; } // 使用VSCode命令在新窗口中打开文件夹 const projectUri = vscode.Uri.file(projectPath); // 在新窗口中打开文件夹 (第二个参数为true表示在新窗口中打开) await vscode.commands.executeCommand('vscode.openFolder', projectUri, true); } catch (error) { console.error(`❌ [ProjectManager] 在新窗口中打开项目失败: ${error}`); // 显示错误通知 vscode.window.showErrorMessage( `在新窗口中打开项目失败: ${error}`, '重试', '在当前窗口打开' ).then(selection => { if (selection === '重试') { // 重试在新窗口中打开 this.openInVsCodeNewWindow(projectPath); } else if (selection === '在当前窗口打开') { // 在当前窗口中打开(替换当前工作区) this.openInCurrentWindow(projectPath); } }); } } // 处理创建页面TS文件 public async handleCreatePageTSFiles(projectData: any, workspacePath?: string) { if (!projectData || !projectData.projectPageList) { return; } try { // 解析页面列表 const pageList = typeof projectData.projectPageList === 'string' ? JSON.parse(projectData.projectPageList) : projectData.projectPageList; if (!Array.isArray(pageList) || pageList.length === 0) { return; } if (!workspacePath) { // 获取工作区根目录 workspacePath = WorkspaceUtils.getProjectRootPath() || undefined; if (!workspacePath) { return; } } // 遍历页面列表 for (const page of pageList) { const pageName = page.enName || page.name; if (pageName && page.type === 2) { // 只处理页面类型 const srcPath = path.join(workspacePath, 'src'); await this.createPageFiles(srcPath, pageName); } } } catch (error) { console.error('❌ [ByteFun Sidebar] 创建页面TS文件失败:', error); } } // 创建单个页面的文件 private async createPageFiles(srcPath: string, pageName: string) { // 将页面名称转换为合适的文件名(首字母小写) let lowerPageFileName = pageName.charAt(0).toLowerCase() + pageName.slice(1); if (!lowerPageFileName.endsWith('Page')) { lowerPageFileName = lowerPageFileName + 'Page'; } const upperPageFileName = lowerPageFileName.charAt(0).toLowerCase() + lowerPageFileName.slice(1); const pageFolderPath = path.join(srcPath, lowerPageFileName); // 检查src文件夹中是否已存在pageFileName.ts文件 const srcDir = path.dirname(srcPath); // 获取src目录路径 if (this.findPageFileInSrc(srcDir, `${lowerPageFileName}.ts`)) { return; } try { // 创建页面文件夹 fs.mkdirSync(pageFolderPath, { recursive: true }); // 创建页面主文件 (.ts) const pageFilePath = path.join(pageFolderPath, `${lowerPageFileName}.ts`); const pageContent = `import Page from "../../lib/uilib/Page" export default class ${upperPageFileName} extends Page { protected onPageCreate(): void { super.onPageCreate() this.initData() this.initView() } private initData(): void { } private initView(): void { } }`; fs.writeFileSync(pageFilePath, pageContent, 'utf8'); } catch (error) { console.error('❌ [ByteFun Sidebar] 创建页面文件失败:', lowerPageFileName, error); } } // 在src文件夹中递归查找指定文件名 private findPageFileInSrc(srcDir: string, fileName: string): boolean { try { if (!fs.existsSync(srcDir)) { return false; } const items = fs.readdirSync(srcDir); for (const item of items) { const itemPath = path.join(srcDir, item); const stat = fs.statSync(itemPath); if (stat.isDirectory()) { // 递归搜索子目录 if (this.findPageFileInSrc(itemPath, fileName)) { return true; } } else if (stat.isFile() && item === fileName) { // 找到匹配的文件 return true; } } return false; } catch (error) { console.error('❌ [ByteFun Sidebar] 搜索页面文件时出错:', error); return false; } } // 在当前窗口中打开项目文件夹(备用方法) private async openInCurrentWindow(projectPath: string): Promise<void> { try { const projectUri = vscode.Uri.file(projectPath); // 在当前窗口中打开文件夹(第二个参数为false表示在当前窗口中打开,替换当前工作区) await vscode.commands.executeCommand('vscode.openFolder', projectUri, false); } catch (error) { console.error(`❌ [ProjectManager] 在当前窗口中打开项目失败: ${error}`); vscode.window.showErrorMessage(`打开项目失败: ${error}`); } } /** * 获取不同操作系统的默认项目路径 */ private getDefaultProjectsPath(): string { const platform = os.platform(); const homeDir = os.homedir(); switch (platform) { case 'win32': // Windows // 优先使用D盘,如果不存在则使用C盘 const dDrive = 'D:\\ByteFunProject'; const cDrive = path.join(homeDir, 'ByteFunProject'); try { // 检查D盘是否存在 if (fs.existsSync('D:\\')) { return dDrive; } } catch (error) { } return cDrive; case 'darwin': // macOS return path.join(homeDir, 'ByteFunProject'); case 'linux': // Linux return path.join(homeDir, 'ByteFunProject'); default: return path.join(homeDir, 'ByteFunProject'); } } /** * 确保项目目录存在 */ private ensureProjectsDirectory(): void { try { if (!fs.existsSync(this.projectsBasePath)) { fs.mkdirSync(this.projectsBasePath, { recursive: true }); } } catch (error) { console.error(`❌ [ProjectManager] 创建项目目录失败: ${error}`); // 如果创建失败,回退到用户目录 this.projectsBasePath = path.join(os.homedir(), 'ByteFunProject'); if (!fs.existsSync(this.projectsBasePath)) { fs.mkdirSync(this.projectsBasePath, { recursive: true }); } } } /** * 获取项目路径 */ public getProjectsBasePath(): string { return this.projectsBasePath; } /** * 设置自定义项目路径 */ public setProjectsBasePath(customPath: string): void { this.projectsBasePath = customPath; this.ensureProjectsDirectory(); } /** * 根据projectID检查项目是否存在 */ public projectExists(projectID: string, projectPath: string): boolean { // 检查.bytefun文件夹的project.json文件是否存在 const bytefunDir = path.join(projectPath, '.bytefun'); const projectJsonPath = path.join(bytefunDir, 'project.json'); if (!fs.existsSync(projectJsonPath)) { return false; } const projectJson = JSON.parse(fs.readFileSync(projectJsonPath, 'utf8')); return projectJson.projectID === projectID; } /** * 获取项目完整路径 */ public getProjectPath(projectName: string): string { return path.join(this.projectsBasePath, projectName); } /** * 下载ZIP文件 */ private async downloadZip(url: string, outputPath: string): Promise<void> { return new Promise((resolve, reject) => { const file = fs.createWriteStream(outputPath); https.get(url, (response) => { if (response.statusCode !== 200) { reject(new Error(`下载失败,状态码: ${response.statusCode}`)); return; } const totalSize = parseInt(response.headers['content-length'] || '0', 10); let downloadedSize = 0; response.on('data', (chunk) => { downloadedSize += chunk.length; if (totalSize > 0) { const progress = Math.round((downloadedSize / totalSize) * 100); } }); response.pipe(file); file.on('finish', () => { file.close(); resolve(); }); file.on('error', (err) => { fs.unlink(outputPath, () => { }); // 删除不完整的文件 reject(err); }); }).on('error', (err) => { reject(err); }); }); } /** * 解压ZIP文件 */ private async extractZip(zipPath: string, extractPath: string): Promise<void> { return new Promise((resolve, reject) => { yauzl.open(zipPath, { lazyEntries: true }, (err: Error | null, zipfile?: yauzl.ZipFile) => { if (err) { reject(err); return; } if (!zipfile) { reject(new Error('无法打开ZIP文件')); return; } // 确保解压目录存在 if (!fs.existsSync(extractPath)) { fs.mkdirSync(extractPath, { recursive: true }); } zipfile.readEntry(); zipfile.on('entry', (entry: yauzl.Entry) => { const entryPath = path.join(extractPath, entry.fileName); if (/\/$/.test(entry.fileName)) { // 目录 if (!fs.existsSync(entryPath)) { fs.mkdirSync(entryPath, { recursive: true }); } zipfile.readEntry(); } else { // 文件 const entryDir = path.dirname(entryPath); if (!fs.existsSync(entryDir)) { fs.mkdirSync(entryDir, { recursive: true }); } zipfile.openReadStream(entry, (err: Error | null, readStream?: NodeJS.ReadableStream) => { if (err) { reject(err); return; } if (!readStream) { reject(new Error('无法读取ZIP条目')); return; } const writeStream = fs.createWriteStream(entryPath); readStream.pipe(writeStream); writeStream.on('close', () => { zipfile.readEntry(); }); writeStream.on('error', (err: Error) => { reject(err); }); }); } }); zipfile.on('end', () => { // 删除 __MACOSX 文件夹(如果存在) this.removeMacOSXFolder(extractPath); resolve(); }); zipfile.on('error', (err: Error) => { reject(err); }); }); }); } /** * 删除 __MACOSX 文件夹(macOS 创建 ZIP 文件时产生的元数据文件夹) */ private removeMacOSXFolder(extractPath: string): void { try { const macOSXPath = path.join(extractPath, '__MACOSX'); if (fs.existsSync(macOSXPath)) { // 递归删除整个 __MACOSX 文件夹 fs.rmSync(macOSXPath, { recursive: true, force: true }); } else { } } catch (error) { console.warn(`⚠️ [ProjectManager] 删除 __MACOSX 文件夹失败: ${error}`); // 不抛出错误,因为删除 __MACOSX 失败不应该影响项目创建流程 } } /** * 将MCP服务配置写入Cursor的MCP配置文件 */ private writeMcpConfiguration(): void { try { // 获取Cursor MCP配置文件路径 const mcpConfigPath = this.getCursorMcpConfigPath(); // 要添加的MCP服务配置 const bytefunMcpConfig = { "command": "npx", "args": ["bytefun-ai-mcp"], "env": { "NODE_ENV": "production" } }; // 确保目录存在 const configDir = path.dirname(mcpConfigPath); if (!fs.existsSync(configDir)) { fs.mkdirSync(configDir, { recursive: true }); } // 读取现有配置或创建新配置 let existingConfig: any = {}; if (fs.existsSync(mcpConfigPath)) { try { const configContent = fs.readFileSync(mcpConfigPath, 'utf8'); if (configContent.trim()) { existingConfig = JSON.parse(configContent); } else { existingConfig = {}; } } catch (error) { console.warn('⚠️ [ProjectManager] 解析现有MCP配置失败,将重新创建配置:', error); existingConfig = {}; } } else { } // 确保配置对象是有效的对象类型 if (!existingConfig || typeof existingConfig !== 'object' || Array.isArray(existingConfig)) { existingConfig = {}; } // 确保存在 mcpServers 字段,并且是对象类型 if (!existingConfig.mcpServers || typeof existingConfig.mcpServers !== 'object' || Array.isArray(existingConfig.mcpServers)) { existingConfig.mcpServers = {}; } // 检查是否已经存在 bytefun-ai-mcp 配置 if (existingConfig.mcpServers['bytefun-ai-mcp']) { return; } // 添加bytefun-ai-mcp配置到mcpServers中 existingConfig.mcpServers['bytefun-ai-mcp'] = bytefunMcpConfig; // 写入配置文件 fs.writeFileSync(mcpConfigPath, JSON.stringify(existingConfig, null, 4), 'utf8'); } catch (error) { console.error('❌ [ProjectManager] 配置MCP服务失败:', error); // 这里不抛出错误,因为MCP配置失败不应该阻止项目创建 } } /** * 获取Cursor MCP配置文件路径 */ private getCursorMcpConfigPath(): string { const platform = os.platform(); const homeDir = os.homedir(); switch (platform) { case 'win32': // Windows // Windows: %APPDATA%\Cursor\User\mcp.json return path.join(homeDir, 'AppData', 'Roaming', 'Cursor', 'User', 'mcp.json'); case 'darwin': // macOS // macOS: ~/.cursor/mcp.json return path.join(homeDir, '.cursor', 'mcp.json'); case 'linux': // Linux // Linux: ~/.config/Cursor/User/mcp.json return path.join(homeDir, '.config', 'Cursor', 'User', 'mcp.json'); default: // 默认使用类似 Linux 的路径 return path.join(homeDir, '.config', 'Cursor', 'User', 'mcp.json'); } } /** * 获取聊天数据存储路径 */ private getChatDataBasePath(): string { const platform = os.platform(); const homeDir = os.homedir(); switch (platform) { case 'win32': // Windows return path.join(homeDir, 'AppData', 'Local', 'ByteFun', 'ChatData'); case 'darwin': // macOS return path.join(homeDir, 'Library', 'Application Support', 'ByteFun', 'ChatData'); case 'linux': // Linux return path.join(homeDir, '.local', 'share', 'ByteFun', 'ChatData'); default: return path.join(homeDir, '.bytefun', 'ChatData'); } } /** * 确保聊天数据目录存在 */ private ensureChatDataDirectory(): void { try { if (!fs.existsSync(this.chatDataBasePath)) { fs.mkdirSync(this.chatDataBasePath, { recursive: true }); } } catch (error) { console.error(`❌ [ProjectManager] 创建聊天数据目录失败: ${error}`); } } /** * 获取用户项目聊天数据文件路径 */ private getUserProjectChatDataPath(uid: string, projectID: string): string { // 使用 uid_projectID 的组合来确保不同用户、不同项目的聊天记录隔离 const fileName = `${uid}_${projectID}_chats.json`; return path.join(this.chatDataBasePath, fileName); } /** * 聊天会话自定义排序 * Overview类型的会话固定在第二位置,其他会话按时间排序 */ private sortChatSessions(sessions: ChatSession[]): ChatSession[] { // Overview消息类型 const overviewTypes = ['product_overview', 'ui_overview', 'code_overview', 'test_overview', 'data_overview']; // 分离overview会话和普通会话 const overviewSessions: ChatSession[] = []; const normalSessions: ChatSession[] = []; sessions.forEach(session => { // 检查会话中是否包含overview类型的消息 const hasOverviewMessage = session.messages.some(message => message.type && overviewTypes.includes(message.type) ); if (hasOverviewMessage) { overviewSessions.push(session); } else { normalSessions.push(session); } }); // 对普通会话按时间排序(最新的在前) normalSessions.sort((a, b) => b.lastMessageTime - a.lastMessageTime); // 对overview会话也按时间排序 overviewSessions.sort((a, b) => b.lastMessageTime - a.lastMessageTime); // 重新组合:overview会话在前,普通会话在后 const result: ChatSession[] = [...overviewSessions, ...normalSessions]; return result; } /** * 获取用户项目的所有聊天会话 */ public getUserProjectChatSessions(uid: string, projectID: string): ChatSession[] { try { const chatDataPath = this.getUserProjectChatDataPath(uid, projectID); if (!fs.existsSync(chatDataPath)) { return []; } const chatData = fs.readFileSync(chatDataPath, 'utf8'); const sessions: ChatSession[] = JSON.parse(chatData); const sortedSessions = this.sortChatSessions(sessions); return sortedSessions; } catch (error) { return []; } } /** * 保存用户项目聊天会话 */ public saveUserProjectChatSessions(uid: string, projectID: string, sessions: ChatSession[]): void { try { let sessionsToSave = sessions; // 检查哪些角色类型有概览消息 const overviewTypes = ['product_overview', 'ui_overview', 'code_overview', 'test_overview', 'data_overview']; const roleTypesWithOverview = new Set<string>(); sessions.forEach(session => { const hasOverview = session.messages.some(message => message.type && overviewTypes.includes(message.type) ); if (hasOverview) { roleTypesWithOverview.add(session.roleType); } }); if (roleTypesWithOverview.size > 0) { const cleanedSessions: ChatSession[] = []; sessions.forEach(session => { // 只清理包含概览消息的角色的介绍消息 if (roleTypesWithOverview.has(session.roleType)) { const originalMessageCount = session.messages.length; const messages = session.messages.filter(message => !message.id.startsWith('msg_intro_')); if (messages.length < originalMessageCount) { } // 如果清理后会话为空,则不保留该会话 if (messages.length > 0) { cleanedSessions.push({ ...session, messages }); } else { } } else { // 如果角色的会话没有概览消息,则保持原样 cleanedSessions.push(session); } }); sessionsToSave = cleanedSessions; } const chatDataPath = this.getUserProjectChatDataPath(uid, projectID); fs.writeFileSync(chatDataPath, JSON.stringify(sessionsToSave, null, 2), 'utf8'); } catch (error) { console.error(`❌ [ProjectManager] 保存用户项目聊天数据失败: ${error}`); } } /** * 创建新的聊天会话 */ public createChatSession( uid: string, projectID: string, roleType: 'product' | 'design' | 'frontEndDev' | 'backEndDev' | 'operations', title?: string ): ChatSession { const roleNames = { product: '产品经理', design: '设计师', frontEndDev: '开发工程师', backEndDev: '测试工程师', operations: '运营专员' }; const session: ChatSession = { id: `session_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`, title: title || `与${roleNames[roleType]}的对话`, roleType, messages: [], lastMessageTime: Date.now(), uid }; const sessions = this.getUserProjectChatSessions(uid, projectID); sessions.unshift(session); this.saveUserProjectChatSessions(uid, projectID, sessions); return session; } /** * 添加消息到聊天会话 */ public addMessageToSession( uid: string, projectID: string, sessionId: string, role: 'user' | 'assistant', content: string, roleType: 'product' | 'design' | 'frontEndDev' | 'backEndDev' | 'operations' ): void { const sessions = this.getUserProjectChatSessions(uid, projectID); const session = sessions.find(s => s.id === sessionId); if (!session) { console.error(`❌ [ProjectManager] 找不到聊天会话: ${sessionId}`); return; } const message: ChatMessage = { id: `msg_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`, role, content, timestamp: Date.now(), roleType, type: 'normal' }; session.messages.push(message); session.lastMessageTime = Date.now(); this.saveUserProjectChatSessions(uid, projectID, sessions); } /** * 删除聊天会话 */ public deleteChatSession(uid: string, projectID: string, sessionId: string): void { const sessions = this.getUserProjectChatSessions(uid, projectID); const filteredSessions = sessions.filter(s => s.id !== sessionId); this.saveUserProjectChatSessions(uid, projectID, filteredSessions); } /** * 获取当前项目的 UID */ public getUID(): string | null { try { const workspaceRoot = WorkspaceUtils.getProjectRootPath(); if (!workspaceRoot) { return null; } const projectJsonPath = path.join(workspaceRoot, '.bytefun', 'project.json'); if (!fs.existsSync(projectJsonPath)) { return null; } const projectConfig = JSON.parse(fs.readFileSync(projectJsonPath, 'utf8')); return projectConfig.uid; } catch (error) { console.error(`❌ [ProjectManager] 获取当前项目 ID 失败: ${error}`); return null; } } /** * 获取当前项目的 ProjectID */ public getCurrentProjectID(): string | null { try { const workspaceRoot = WorkspaceUtils.getProjectRootPath(); if (!workspaceRoot) { return null; } const projectJsonPath = path.join(workspaceRoot, '.bytefun', 'project.json'); if (!fs.existsSync(projectJsonPath)) { return null; } const projectConfig = JSON.parse(fs.readFileSync(projectJsonPath, 'utf8')); return projectConfig.projectID; } catch (error) { console.error(`❌ [ProjectManager] 获取当前项目 ID 失败: ${error}`); return null; } } /** * 初始化用户项目的默认聊天会话(为所有角色创建包含自我介绍的会话) */ public initializeDefaultChatSessions(uid: string, projectID: string): void { try { // 检查是否已有聊天记录 const existingSessions = this.getUserProjectChatSessions(uid, projectID); if (existingSessions.length > 0) { return; } // 定义所有角色及其自我介绍 const roleDefinitions = { product: { name: '产品经理', icon: '📊', introduction: '👋 你好!我是产品经理,我可以帮助你进行需求分析、产品规划、用户体验设计等工作。' }, design: { name: '设计师', icon: '🎨', introduction: '👋 你好!我是设计师,我可以帮助你进行UI/UX设计、视觉设计、交互设计等工作。' }, frontEndDev: { name: '前端开发工程师', icon: '💻', introduction: '👋 你好!我是前端开发工程师,我可以帮助你解决前端的技术问题、代码实现、架构设计等问题。' }, backEndDev: { name: '后端开发工程师', icon: '🧪', introduction: '👋 你好!我是后端开发工程师,我可以帮助你解决后端的技术问题、代码实现、架构设计等问题。' }, test: { name: '测试工程师', icon: '🧪', introduction: '👋 你好!我是测试工程师,我可以帮助你进行测试用例设计、质量保证、Bug分析等工作。' }, operations: { name: '运营专员', icon: '📈', introduction: '👋 你好!我是运营专员,我可以帮助你进行用户运营、数据分析、活动策划等工作。' } }; const sessions: ChatSession[] = []; // 为每个角色创建初始会话 for (const [roleType, roleInfo] of Object.entries(roleDefinitions)) { const session: ChatSession = { id: `session_${roleType}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`, title: `与${roleInfo.name}的对话`, roleType: roleType as 'product' | 'design' | 'frontEndDev' | 'backEndDev' | 'operations', messages: [ { id: `msg_intro_${roleType}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`, role: 'assistant', content: roleInfo.introduction, timestamp: Date.now(), roleType: roleType as 'product' | 'design' | 'frontEndDev' | 'backEndDev' | 'operations', type: 'normal' } ], lastMessageTime: Date.now(), uid }; sessions.push(session); } // 保存所有初始会话 this.saveUserProjectChatSessions(uid, projectID, sessions); } catch (error) { console.error(`❌ [ProjectManager] 初始化默认聊天会话失败: ${error}`); } } /** * 清空指定角色的所有聊天记录 */ public clearRoleChatHistory( uid: string, projectID: string, roleType: 'product' | 'design' | 'frontEndDev' | 'backEndDev' | 'operations' ): void { try { const sessions = this.getUserProjectChatSessions(uid, projectID); // 过滤掉指定角色的所有会话 const filteredSessions = sessions.filter(session => session.roleType !== roleType); this.saveUserProjectChatSessions(uid, projectID, filteredSessions); } catch (error) { console.error(`❌ [ProjectManager] 清空角色聊天记录失败: ${error}`); throw error; } } /** * 为指定角色创建默认聊天会话(包含自我介绍) */ public createDefaultChatSessionForRole( uid: string, projectID: string, roleType: 'product' | 'design' | 'frontEndDev' | 'backEndDev' | 'operations' ): void { try { // 定义角色信息 const roleDefinitions = { product: { name: '产品经理', icon: '📊', introduction: '👋 你好!我是产品经理,我可以帮助你进行需求分析、产品规划、用户体验设计等工作。' }, design: { name: '设计师', icon: '🎨', introduction: '👋 你好!我是设计师,我可以帮助你进行UI/UX设计、视觉设计、交互设计等工作。' }, frontEndDev: { name: '前端开发工程师', icon: '💻', introduction: '👋 你好!我是前端开发工程师,我可以帮助你解决前端的技术问题、代码实现、架构设计等问题。' }, backEndDev: { name: '后端开发工程师', icon: '🧪', introduction: '👋 你好!我是后端开发工程师,我可以帮助你解决后端的技术问题、代码实现、架构设计等问题。' }, test: { name: '测试工程师', icon: '🧪', introduction: '👋 你好!我是测试工程师,我可以帮助你进行测试用例设计、质量保证、Bug分析等工作。' }, operations: { name: '运营专员', icon: '📈', introduction: '👋 你好!我是运营专员,我可以帮助你进行用户运营、数据分析、活动策划等工作。' } }; const roleInfo = roleDefinitions[roleType]; if (!roleInfo) { console.error(`❌ [ProjectManager] 未知角色类型: ${roleType}`); return; } // 获取现有会话 const sessions = this.getUserProjectChatSessions(uid, projectID); // 创建新的默认会话 const session: ChatSession = { id: `session_${roleType}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`, title: `与${roleInfo.name}的对话`, roleType: roleType, messages: [ { id: `msg_intro_${roleType}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`, role: 'assistant', content: roleInfo.introduction, timestamp: Date.now(), roleType: roleType, type: 'normal' } ], lastMessageTime: Date.now(), uid }; // 添加到会话列表并保存 sessions.unshift(session); this.saveUserProjectChatSessions(uid, projectID, sessions); } catch (error) { console.error(`❌ [ProjectManager] 为角色创建默认会话失败: ${error}`); throw error; } } /** * 团队聊天会话自定义排序 * Overview类型的会话固定在第二位置,其他会话按时间排序 */ private sortTeamChatSessions(sessions: TeamChatSession[]): TeamChatSession[] { // Overview消息类型 const overviewTypes = ['product_overview', 'ui_overview', 'code_overview', 'test_overview', 'data_overview']; // 分离overview会话和普通会话 const overviewSessions: TeamChatSession[] = []; const normalSessions: TeamChatSession[] = []; sessions.forEach(session => { // 检查会话中是否包含overview类型的消息 const hasOverviewMessage = session.messages.some(message => message.type && overviewTypes.includes(message.type) ); if (hasOverviewMessage) { overviewSessions.push(session); } else { normalSessions.push(session); } }); // 对普通会话按时间排序(最新的在前) normalSessions.sort((a, b) => b.lastMessageTime - a.lastMessageTime); // 对overview会话也按时间排序 overviewSessions.sort((a, b) => b.lastMessageTime - a.lastMessageTime); // 重新组合:第一个是最新的普通会话,第二个是overview会话,其余按顺序排列 const result: TeamChatSession[] = []; if (normalSessions.length > 0) { // 添加第一个普通会话 result.push(normalSessions[0]); } if (overviewSessions.length > 0) { // 添加overview会话到第二位置 result.push(...overviewSessions); } if (normalSessions.length > 1) { // 添加剩余的普通会话 result.push(...normalSessions.slice(1)); } return result; } /** * 获取用户项目团队聊天数据文件路径 */ private getUserProjectTeamChatDataPath(uid: string, projectID: string): string { // 使用 uid_projectID_team 的组合来存储团队聊天记录 const fileName = `${uid}_${projectID}_team_chats.json`; return path.join(this.chatDataBasePath, fileName); } /** * 获取用户项目的团队聊天会话 */ public getUserProjectTeamChatSessions(uid: string, projectID: string): TeamChatSession[] { try { const teamChatDataPath = this.getUserProjectTeamChatDataPath(uid, projectID); if (!fs.existsSync(teamChatDataPath)) { return []; } const teamChatData = fs.readFileSync(teamChatDataPath, 'utf8'); const sessions: TeamChatSession[] = JSON.parse(teamChatData); return this.sortTeamChatSessions(sessions); } catch (error) { console.error(`❌ [ProjectManager] 读取用户项目团队聊天数据失败: ${error}`); return []; } } /** * 保存用户项目团队聊天会话 */ public saveUserProjectTeamChatSessions(uid: string, projectID: string, sessions: TeamChatSession[]): void { try { const teamChatDataPath = this.getUserProjectTeamChatDataPath(uid, projectID); fs.writeFileSync(teamChatDataPath, JSON.stringify(sessions, null, 2), 'utf8'); } catch (error) { console.error(`❌ [ProjectManager] 保存用户项目团队聊天数据失败: ${error}`); } } /** * 创建新的团队聊天会话 */ public createTeamChatSession(uid: string, projectID: string, title?: string): TeamChatSession { const session: TeamChatSession = { id: `team_session_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`, title: title || '团队协作对话', type: 'team', messages: [], lastMessageTime: Date.now(), uid }; const sessions = this.getUserProjectTeamChatSessions(uid, projectID); sessions.unshift(session); this.saveUserProjectTeamChatSessions(uid, projectID, sessions); return session; } /** * 添加消息到团队聊天会话 */ public addMessageToTeamSession( uid: string, projectID: string, sessionId: string, role: 'user' | 'assistant', content: string, senderRole: 'product' | 'design' | 'frontEndDev' | 'backEndDev' | 'operations' ): void { const sessions = this.getUserProjectTeamChatSessions(uid, projectID); const session = sessions.find(s => s.id === sessionId); if (!session) { console.error(`❌ [ProjectManager] 找不到团队聊天会话: ${sessionId}`); return; } const message: TeamChatMessage = { id: `team_msg_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`, role, content, timestamp: Date.now(), senderRole, type: 'normal' }; session.messages.push(message); session.lastMessageTime = Date.now(); this.saveUserProjectTeamChatSessions(uid, projectID, sessions); } /** * 删除团队聊天会话 */ public deleteTeamChatSession(uid: string, projectID: string, sessionId: string): void { const sessions = this.getUserProjectTeamChatSessions(uid, projectID); const filteredSessions = sessions.filter(s => s.id !== sessionId); this.saveUserProjectTeamChatSessions(uid, projectID, filteredSessions); } /** * 清空团队聊天记录 */ public clearTeamChatHistory(uid: string, projectID: string): void { try { this.saveUserProjectTeamChatSessions(uid, projectID, []); } catch (error) { console.error(`❌ [ProjectManager] 🔍 清空团队聊天记录失败: ${error}`); throw error; } } /** * 创建默认团队聊天会话(包含团队介绍) */ public createDefaultTeamChatSession(uid: string, projectID: string): void { try { // 检查是否已有团队聊天记录 const existingSessions = this.getUserProjectTeamChatSessions(uid, projectID); if (existingSessions.length > 0) { return; } // 创建团队会话 const session: TeamChatSession = { id: `team_session_default_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`, title: '团队协作对话', type: 'team', messages: [ { id: `team_msg_intro_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`, role: 'assistant', content: '🎉 欢迎来到团队协作对话!\n\n这里是所有团队成员的协作空间,包括:\n📊 产品经理 - 负责需求和规划\n🎨 设计师 - 负责视觉和交互设计\n💻 开发工程师 - 负责技术实现\n🧪 测试工程师 - 负责质量保证\n📈 运营专员 - 负责用户运营\n\n大家可以在这里自由交流,分享想法,协作完成项目!', timestamp: Date.now(), senderRole: 'product', // 默认由产品经理发送欢迎消息 type: 'normal' } ], lastMessageTime: Date.now(), uid }; this.saveUserProjectTeamChatSessions(uid, projectID, [session]); } catch (error) { console.error(`❌ [ProjectManager] 初始化默认团队聊天会话失败: ${error}`); } } /** * 原子性地添加消息到角色和团队的聊天会话中 */ public addMessageToRoleAndTeam( uid: string, projectID: string, roleType: 'product' | 'design' | 'frontEndDev' | 'backEndDev' | 'operations', content: string ): void { try { // 获取角色会话 const sessions = this.getUserProjectChatSessions(uid, projectID); let roleSession = sessions.find(s => s.roleType === roleType); if (!roleSession) { roleSession = this.createChatSession(uid, projectID, roleType); } const roleMessage: ChatMessage = { id: `msg_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`, role: 'assistant', content, timestamp: Date.now(), roleType, type: 'normal' }; roleSession.messages.push(roleMessage); roleSession.lastMessageTime = Date.now(); // 获取团队会话 const teamSessions = this.getUserProjectTeamChatSessions(uid, projectID); let teamSession = teamSessions[0]; if (!teamSession) { teamSession = this.createTeamChatSession(uid, projectID); } const teamMessage: TeamChatMessage = { id: `team_msg_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`, role: 'assistant', content, timestamp: Date.now(), senderRole: roleType, type: 'normal' }; teamSession.messages.push(teamMessage); teamSession.lastMessageTime = Date.now(); // 一次性保存所有更新 this.saveUserProjectChatSessions(uid, projectID, sessions); this.saveUserProjectTeamChatSessions(uid, projectID, teamSessions); } catch (error) { console.error('🤖 [流程-C] [ProjectManager] ❌ 原子性添加MCP消息失败:', error); throw error; } } /** * 原子性地添加用户消息到角色和团队的聊天会话中 */ public addMessageToRoleAndTeamFromUser( uid: string, projectID: string, roleType: 'product' | 'design' | 'frontEndDev' | 'backEndDev' | 'operations', content: string ): void { try { // 获取角色会话 const sessions = this.getUserProjectChatSessions(uid, projectID); let roleSession = sessions.find(s => s.roleType === roleType); if (!roleSession) { roleSession = this.createChatSession(uid, projectID, roleType); } const roleMessage: ChatMessage = { id: `msg_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`, role: 'user', content, timestamp: Date.now(), roleType, type: 'normal' }; roleSession.messages.push(roleMessage); roleSession.lastMessageTime = Date.now(); // 获取团队会话 const teamSessions = this.getUserProjectTeamChatSessions(uid, projectID); let teamSession = teamSessions[0]; if (!teamSession) { teamSession = this.createTeamChatSession(uid, projectID); } const teamMessage: TeamChatMessage = { id: `team_msg_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`, role: 'user', content, timestamp: Date.now(), senderRole: roleType, type: 'normal' }; teamSession.messages.push(teamMessage); teamSession.lastMessageTime = Date.now(); // 一次性保存所有更新 this.saveUserProjectChatSessions(uid, projectID, sessions); this.saveUserProjectTeamChatSessions(uid, projectID, teamSessions); } catch (error) { console.error('💬 [流程-B] [ProjectManager] ❌ 原子性添加用户消息失败:', error); throw error; } } /** * 处理代码ZIP文件的下载和解压 * @param zipData ZIP文件数据数组,包含platform、name、url字段 * @param workspacePath 工作区路径,如果不提供则使用当前工作区 */ public async handleCodeZipDownload( zipData: Array<{ platform: string; name: string; url: string }>, workspacePath?: string ): Promise<void> { try { // 获取工作区路径 let targetWorkspacePath = workspacePath; if (!targetWorkspacePath) { targetWorkspacePath = WorkspaceUtils.getProjectRootPath() || undefined; if (!targetWorkspacePath) { throw new Error('未找到工作区,无法下载代码ZIP文件'); } } // 显示下载进度通知 await vscode.window.withProgress({ location: vscode.ProgressLocation.Notification, title: '正在下载代码文件', cancellable: false }, async (progress) => { const totalFiles = zipData.length; let completedFiles = 0; for (const zipIte