workflow.js 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  1. /**
  2. * 工作流管理模块
  3. * 负责工作流的保存、删除、读取等操作
  4. */
  5. import { ipcMain } from 'electron';
  6. import { readdir, writeFile, readFile, mkdir, rm, stat } from 'fs/promises';
  7. import { join, dirname } from 'path';
  8. import { fileURLToPath } from 'url';
  9. const __filename = fileURLToPath(import.meta.url);
  10. const __dirname = dirname(__filename);
  11. /**
  12. * 获取 static/processing 目录下的所有文件夹
  13. * @returns {Promise<Array<{name: string, createdAt: Date}>>}
  14. */
  15. export async function getStaticFolders() {
  16. try {
  17. const staticPath = join(__dirname, '..', 'static', 'processing');
  18. const entries = await readdir(staticPath, { withFileTypes: true });
  19. const folders = [];
  20. for (const entry of entries) {
  21. if (entry.isDirectory()) {
  22. const folderPath = join(staticPath, entry.name);
  23. const stats = await stat(folderPath);
  24. folders.push({
  25. name: entry.name,
  26. createdAt: stats.birthtime || stats.mtime, // 使用创建时间,如果没有则使用修改时间
  27. });
  28. }
  29. }
  30. // 按创建时间排序,最新的在前
  31. return folders.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());
  32. } catch (error) {
  33. console.error('Failed to read static/processing folders:', error);
  34. return [];
  35. }
  36. }
  37. /**
  38. * 读取 processing.json 文件
  39. * @param {string} folderName - 工作流文件夹名称
  40. * @returns {Promise<Object|null>} 解析后的 JSON 对象,失败返回 null
  41. */
  42. export async function readProcessingJson(folderName) {
  43. try {
  44. const jsonPath = join(__dirname, '..', 'static', 'processing', folderName, 'processing.json');
  45. const jsonContent = await readFile(jsonPath, 'utf-8');
  46. // 解析 JSON(处理可能的格式问题,如注释、尾随逗号等)
  47. // 先尝试直接解析
  48. let parsed;
  49. try {
  50. parsed = JSON.parse(jsonContent);
  51. } catch (parseError) {
  52. // 如果直接解析失败,尝试清理注释和尾随逗号(简单处理)
  53. let cleaned = jsonContent
  54. .replace(/\/\/.*$/gm, '') // 移除单行注释
  55. .replace(/\/\*[\s\S]*?\*\//g, '') // 移除多行注释
  56. .replace(/,(\s*[}\]])/g, '$1'); // 移除尾随逗号(在 ] 或 } 之前的逗号)
  57. try {
  58. parsed = JSON.parse(cleaned);
  59. } catch (retryError) {
  60. console.error(`JSON 解析失败 [${folderName}]:`, parseError.message);
  61. console.error('原始内容:', jsonContent.substring(0, 500));
  62. throw new Error(`JSON 格式错误: ${parseError.message}`);
  63. }
  64. }
  65. // 处理不同的 JSON 格式
  66. // 如果直接是数组,包装成对象
  67. if (Array.isArray(parsed)) {
  68. return { actions: parsed };
  69. }
  70. // 如果已经是对象,直接返回
  71. if (parsed && typeof parsed === 'object') {
  72. // 如果已经有 actions 字段,直接返回
  73. if (parsed.actions) {
  74. return parsed;
  75. }
  76. // 如果没有 actions 字段,尝试查找数组字段
  77. for (const key in parsed) {
  78. if (Array.isArray(parsed[key])) {
  79. return { actions: parsed[key], ...parsed };
  80. }
  81. }
  82. }
  83. // 如果解析成功但没有 actions 字段,返回错误信息
  84. if (!parsed || (typeof parsed === 'object' && !parsed.actions && !Array.isArray(parsed))) {
  85. console.error(`processing.json 格式错误 [${folderName}]: 缺少 actions 字段`);
  86. return null;
  87. }
  88. return parsed;
  89. } catch (error) {
  90. console.error(`读取 processing.json 失败 [${folderName}]:`, error.message);
  91. return null;
  92. }
  93. }
  94. /**
  95. * 保存工作流到 static/processing 目录
  96. * @param {Object} workflowJson - 工作流 JSON 对象
  97. * @param {Array} imagesData - 图片数据数组(包含 base64 和 name)
  98. * @returns {Promise<{success: boolean, error?: string, folderName?: string, path?: string}>}
  99. */
  100. export async function saveWorkflow(workflowJson, imagesData = []) {
  101. try {
  102. // 支持新旧格式
  103. const hasActions = Array.isArray(workflowJson.actions) || Array.isArray(workflowJson);
  104. if (!workflowJson || typeof workflowJson !== 'object' || !hasActions) {
  105. return { success: false, error: '工作流格式错误:缺少 actions 数组' };
  106. }
  107. // 生成文件夹名称(使用时间戳)
  108. const now = new Date();
  109. const timestamp = now.getFullYear() +
  110. String(now.getMonth() + 1).padStart(2, '0') +
  111. String(now.getDate()).padStart(2, '0') + '_' +
  112. String(now.getHours()).padStart(2, '0') +
  113. String(now.getMinutes()).padStart(2, '0') +
  114. String(now.getSeconds()).padStart(2, '0');
  115. const folderName = workflowJson.name || `工作流_${timestamp}`;
  116. // 创建工作流文件夹
  117. const workflowPath = join(__dirname, '..', 'static', 'processing', folderName);
  118. await mkdir(workflowPath, { recursive: true });
  119. // 保存 processing.json
  120. const jsonPath = join(workflowPath, 'processing.json');
  121. const jsonContent = JSON.stringify(workflowJson, null, '\t');
  122. await writeFile(jsonPath, jsonContent, 'utf-8');
  123. // 保存图片
  124. if (imagesData && Array.isArray(imagesData) && imagesData.length > 0) {
  125. for (const imageData of imagesData) {
  126. if (imageData.base64 && imageData.name) {
  127. try {
  128. // 将base64转换为Buffer
  129. const imageBuffer = Buffer.from(imageData.base64, 'base64');
  130. const imagePath = join(workflowPath, imageData.name);
  131. await writeFile(imagePath, imageBuffer);
  132. // 图片已保存日志(不显示)
  133. } catch (imageError) {
  134. console.error(`保存图片失败 ${imageData.name}:`, imageError);
  135. }
  136. }
  137. }
  138. }
  139. console.log(`工作流已保存: ${folderName}`);
  140. return { success: true, folderName, path: workflowPath };
  141. } catch (error) {
  142. console.error('保存工作流失败:', error);
  143. return { success: false, error: error.message };
  144. }
  145. }
  146. /**
  147. * 删除工作流文件夹
  148. * @param {string} folderName - 工作流文件夹名称
  149. * @returns {Promise<{success: boolean, error?: string, folderName?: string}>}
  150. */
  151. export async function deleteWorkflow(folderName) {
  152. try {
  153. if (!folderName || typeof folderName !== 'string') {
  154. return { success: false, error: '文件夹名称无效' };
  155. }
  156. // 构建文件夹路径
  157. const workflowPath = join(__dirname, '..', 'static', 'processing', folderName);
  158. // 删除整个文件夹(包括所有内容)
  159. await rm(workflowPath, { recursive: true, force: true });
  160. console.log(`工作流已删除: ${folderName}`);
  161. return { success: true, folderName };
  162. } catch (error) {
  163. console.error('删除工作流失败:', error);
  164. return { success: false, error: error.message };
  165. }
  166. }
  167. /**
  168. * 注册工作流管理相关的 IPC handlers
  169. */
  170. export function registerIpcHandlers() {
  171. // 获取工作流文件夹列表
  172. ipcMain.handle('get-static-folders', async () => {
  173. return await getStaticFolders();
  174. });
  175. // 读取 processing.json 文件
  176. ipcMain.handle('read-processing-json', async (event, folderName) => {
  177. return await readProcessingJson(folderName);
  178. });
  179. // 保存工作流
  180. ipcMain.handle('save-workflow', async (event, workflowJson, imagesData) => {
  181. return await saveWorkflow(workflowJson, imagesData);
  182. });
  183. // 删除工作流
  184. ipcMain.handle('delete-workflow', async (event, folderName) => {
  185. return await deleteWorkflow(folderName);
  186. });
  187. }