/** * 工作流管理模块 * 负责工作流的保存、删除、读取等操作 */ import { ipcMain } from 'electron'; import { readdir, writeFile, readFile, mkdir, rm, stat } from 'fs/promises'; import { join, dirname } from 'path'; import { fileURLToPath } from 'url'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); /** * 获取 static/processing 目录下的所有文件夹 * @returns {Promise>} */ export async function getStaticFolders() { try { const staticPath = join(__dirname, '..', 'static', 'processing'); const entries = await readdir(staticPath, { withFileTypes: true }); const folders = []; for (const entry of entries) { if (entry.isDirectory()) { const folderPath = join(staticPath, entry.name); const stats = await stat(folderPath); folders.push({ name: entry.name, createdAt: stats.birthtime || stats.mtime, // 使用创建时间,如果没有则使用修改时间 }); } } // 按创建时间排序,最新的在前 return folders.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime()); } catch (error) { return []; } } /** * 读取 processing.json 文件 * @param {string} folderName - 工作流文件夹名称 * @returns {Promise} 解析后的 JSON 对象,失败返回 null */ export async function readProcessingJson(folderName) { try { const jsonPath = join(__dirname, '..', 'static', 'processing', folderName, 'processing.json'); const jsonContent = await readFile(jsonPath, 'utf-8'); // 解析 JSON(处理可能的格式问题,如注释、尾随逗号等) // 先尝试直接解析 let parsed; try { parsed = JSON.parse(jsonContent); } catch (parseError) { // 如果直接解析失败,尝试清理注释和尾随逗号(简单处理) let cleaned = jsonContent .replace(/\/\/.*$/gm, '') // 移除单行注释 .replace(/\/\*[\s\S]*?\*\//g, '') // 移除多行注释 .replace(/,(\s*[}\]])/g, '$1'); // 移除尾随逗号(在 ] 或 } 之前的逗号) try { parsed = JSON.parse(cleaned); } catch (retryError) { throw new Error(`JSON 格式错误: ${parseError.message}`); } } // 处理不同的 JSON 格式 // 如果直接是数组,包装成对象 if (Array.isArray(parsed)) { return { actions: parsed }; } // 如果已经是对象,直接返回 if (parsed && typeof parsed === 'object') { // 如果已经有 actions 字段,直接返回 if (parsed.actions) { return parsed; } // 如果没有 actions 字段,尝试查找数组字段 for (const key in parsed) { if (Array.isArray(parsed[key])) { return { actions: parsed[key], ...parsed }; } } } // 如果解析成功但没有 actions 字段,返回错误信息 if (!parsed || (typeof parsed === 'object' && !parsed.actions && !Array.isArray(parsed))) { return null; } return parsed; } catch (error) { return null; } } /** * 保存工作流到 static/processing 目录 * @param {Object} workflowJson - 工作流 JSON 对象 * @param {Array} imagesData - 图片数据数组(包含 base64 和 name) * @returns {Promise<{success: boolean, error?: string, folderName?: string, path?: string}>} */ export async function saveWorkflow(workflowJson, imagesData = []) { try { // 支持新旧格式 const hasActions = Array.isArray(workflowJson.actions) || Array.isArray(workflowJson); if (!workflowJson || typeof workflowJson !== 'object' || !hasActions) { return { success: false, error: '工作流格式错误:缺少 actions 数组' }; } // 生成文件夹名称(使用时间戳) const now = new Date(); const timestamp = now.getFullYear() + String(now.getMonth() + 1).padStart(2, '0') + String(now.getDate()).padStart(2, '0') + '_' + String(now.getHours()).padStart(2, '0') + String(now.getMinutes()).padStart(2, '0') + String(now.getSeconds()).padStart(2, '0'); const folderName = workflowJson.name || `工作流_${timestamp}`; // 创建工作流文件夹 const workflowPath = join(__dirname, '..', 'static', 'processing', folderName); await mkdir(workflowPath, { recursive: true }); // 保存 processing.json const jsonPath = join(workflowPath, 'processing.json'); const jsonContent = JSON.stringify(workflowJson, null, '\t'); await writeFile(jsonPath, jsonContent, 'utf-8'); // 保存图片 if (imagesData && Array.isArray(imagesData) && imagesData.length > 0) { for (const imageData of imagesData) { if (imageData.base64 && imageData.name) { try { // 将base64转换为Buffer const imageBuffer = Buffer.from(imageData.base64, 'base64'); const imagePath = join(workflowPath, imageData.name); await writeFile(imagePath, imageBuffer); } catch (imageError) { // 保存图片失败,忽略错误 } } } } return { success: true, folderName, path: workflowPath }; } catch (error) { return { success: false, error: error.message }; } } /** * 删除工作流文件夹 * @param {string} folderName - 工作流文件夹名称 * @returns {Promise<{success: boolean, error?: string, folderName?: string}>} */ export async function deleteWorkflow(folderName) { try { if (!folderName || typeof folderName !== 'string') { return { success: false, error: '文件夹名称无效' }; } // 构建文件夹路径 const workflowPath = join(__dirname, '..', 'static', 'processing', folderName); // 删除整个文件夹(包括所有内容) await rm(workflowPath, { recursive: true, force: true }); return { success: true, folderName }; } catch (error) { return { success: false, error: error.message }; } } /** * 读取 bp.json 文件(蓝图节点位置信息) * @param {string} folderName - 工作流文件夹名称 * @returns {Promise} 解析后的 JSON 对象,失败返回 null */ export async function readBlueprintJson(folderName) { try { const jsonPath = join(__dirname, '..', 'static', 'processing', folderName, 'bp.json'); try { const jsonContent = await readFile(jsonPath, 'utf-8'); const parsed = JSON.parse(jsonContent); return parsed; } catch (readError) { // 如果文件不存在,返回 null(不是错误) if (readError.code === 'ENOENT') { return null; } throw readError; } } catch (error) { return null; } } /** * 保存 bp.json 文件(蓝图节点位置信息) * @param {string} folderName - 工作流文件夹名称 * @param {Object} blueprintData - 蓝图数据 {nodePositions: {nodeId: {x, y}}} * @returns {Promise<{success: boolean, error?: string}>} */ export async function saveBlueprintJson(folderName, blueprintData) { try { if (!folderName || typeof folderName !== 'string') { return { success: false, error: '文件夹名称无效' }; } const workflowPath = join(__dirname, '..', 'static', 'processing', folderName); const jsonPath = join(workflowPath, 'bp.json'); const jsonContent = JSON.stringify(blueprintData, null, '\t'); await writeFile(jsonPath, jsonContent, 'utf-8'); return { success: true }; } catch (error) { return { success: false, error: error.message }; } } /** * 保存 processing.json 文件到现有工作流文件夹 * @param {string} folderName - 工作流文件夹名称 * @param {Object} workflowData - 工作流数据 * @returns {Promise<{success: boolean, error?: string}>} */ export async function saveProcessingJson(folderName, workflowData) { try { if (!folderName || typeof folderName !== 'string') { return { success: false, error: '文件夹名称无效' }; } const workflowPath = join(__dirname, '..', 'static', 'processing', folderName); const jsonPath = join(workflowPath, 'processing.json'); const jsonContent = JSON.stringify(workflowData, null, '\t'); await writeFile(jsonPath, jsonContent, 'utf-8'); return { success: true }; } catch (error) { return { success: false, error: error.message }; } } /** * 注册工作流管理相关的 IPC handlers */ export function registerIpcHandlers() { // 获取工作流文件夹列表 ipcMain.handle('get-static-folders', async () => { return await getStaticFolders(); }); // 读取 processing.json 文件 ipcMain.handle('read-processing-json', async (event, folderName) => { return await readProcessingJson(folderName); }); // 保存 processing.json 文件到现有工作流文件夹 ipcMain.handle('save-processing-json', async (event, folderName, workflowData) => { return await saveProcessingJson(folderName, workflowData); }); // 保存工作流(创建新文件夹) ipcMain.handle('save-workflow', async (event, workflowJson, imagesData) => { return await saveWorkflow(workflowJson, imagesData); }); // 删除工作流 ipcMain.handle('delete-workflow', async (event, folderName) => { return await deleteWorkflow(folderName); }); // 读取 bp.json 文件 ipcMain.handle('read-blueprint-json', async (event, folderName) => { return await readBlueprintJson(folderName); }); // 保存 bp.json 文件 ipcMain.handle('save-blueprint-json', async (event, folderName, blueprintData) => { return await saveBlueprintJson(folderName, blueprintData); }); }