bytefun
Version:
一个打通了原型设计、UI设计与代码转换、跨平台原生代码开发等的平台
1,564 lines (1,275 loc) • 67.2 kB
text/typescript
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