book-generator.workflow.ts 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316
  1. /**
  2. * 书籍生成工作流引擎
  3. * 参考 LangGraph 思路:状态机 + 条件边 + 节点执行
  4. */
  5. import { Book, BookOutline, Chapter, GenerateTask } from './book-generator.types';
  6. import { bookGeneratorService } from './book-generator.service';
  7. import { bookStore } from './book-generator.store';
  8. // ============ 工作流状态 ============
  9. /** 工作流节点类型 */
  10. export type WorkflowNode = 'idle' | 'planning' | 'writing_chapter' | 'writing_foreword' | 'writing_afterword' | 'finished' | 'failed';
  11. /** 工作流边类型 */
  12. export type WorkflowEdge =
  13. | 'plan_next' // 规划下一步
  14. | 'write_next_chapter' // 写下一章
  15. | 'finish_chapter' // 本章写完
  16. | 'all_chapters_done' // 全部章节写完
  17. | 'finish_foreword' // 前言写完
  18. | 'finish_afterword' // 后记写完
  19. | 'error'; // 发生错误
  20. /** 工作流状态 */
  21. export interface WorkflowState {
  22. bookId: string;
  23. currentNode: WorkflowNode;
  24. currentChapter: number; // 当前在写第几章
  25. totalChapters: number;
  26. completedChapters: number;
  27. pendingChapters: number[]; // 待生成的章节列表
  28. failedChapters: number[]; // 失败的章节列表
  29. phase: 'planning' | 'writing' | 'supplement' | 'done';
  30. error?: string;
  31. startedAt: Date;
  32. updatedAt: Date;
  33. }
  34. // ============ 工作流引擎 ============
  35. class BookWorkflowEngine {
  36. private workflows: Map<string, WorkflowState> = new Map();
  37. /**
  38. * 启动工作流
  39. */
  40. startWorkflow(bookId: string, totalChapters: number): WorkflowState {
  41. const state: WorkflowState = {
  42. bookId,
  43. currentNode: 'idle',
  44. currentChapter: 0,
  45. totalChapters,
  46. completedChapters: 0,
  47. pendingChapters: Array.from({ length: totalChapters }, (_, i) => i + 1),
  48. failedChapters: [],
  49. phase: 'planning',
  50. startedAt: new Date(),
  51. updatedAt: new Date(),
  52. };
  53. this.workflows.set(bookId, state);
  54. return state;
  55. }
  56. /**
  57. * 获取工作流状态
  58. */
  59. getState(bookId: string): WorkflowState | undefined {
  60. return this.workflows.get(bookId);
  61. }
  62. /**
  63. * 更新工作流状态
  64. */
  65. private updateState(bookId: string, updates: Partial<WorkflowState>): WorkflowState | undefined {
  66. const state = this.workflows.get(bookId);
  67. if (!state) return undefined;
  68. Object.assign(state, updates, { updatedAt: new Date() });
  69. return state;
  70. }
  71. /**
  72. * 执行规划阶段
  73. */
  74. async executePlanningPhase(bookId: string): Promise<{ success: boolean; outline?: BookOutline; error?: string }> {
  75. const state = this.workflows.get(bookId);
  76. if (!state) return { success: false, error: '工作流不存在' };
  77. this.updateState(bookId, { currentNode: 'planning', phase: 'planning' });
  78. try {
  79. const outline = await bookGeneratorService.generateOutline(bookId);
  80. // 更新工作流状态
  81. this.updateState(bookId, {
  82. currentNode: 'idle',
  83. phase: 'writing',
  84. pendingChapters: outline.chapters.map(c => c.number),
  85. });
  86. return { success: true, outline };
  87. } catch (error) {
  88. const errorMessage = error instanceof Error ? error.message : '规划失败';
  89. this.updateState(bookId, { currentNode: 'failed', error: errorMessage });
  90. return { success: false, error: errorMessage };
  91. }
  92. }
  93. /**
  94. * 执行章节生成 - 单步
  95. * 返回下一步应该执行什么
  96. */
  97. async executeNextChapter(bookId: string): Promise<{
  98. done: boolean;
  99. chapter?: Chapter;
  100. nextChapter?: number;
  101. error?: string;
  102. }> {
  103. const state = this.workflows.get(bookId);
  104. if (!state) return { done: false, error: '工作流不存在' };
  105. if (state.pendingChapters.length === 0) {
  106. return { done: true };
  107. }
  108. // 取下一个待生成的章节
  109. const nextChapterNum = state.pendingChapters[0];
  110. this.updateState(bookId, {
  111. currentNode: 'writing_chapter',
  112. currentChapter: nextChapterNum,
  113. });
  114. try {
  115. const chapter = await bookGeneratorService.generateChapter(bookId, nextChapterNum);
  116. // 更新工作流状态
  117. const newPending = state.pendingChapters.filter(n => n !== nextChapterNum);
  118. this.updateState(bookId, {
  119. currentNode: 'idle',
  120. completedChapters: state.completedChapters + 1,
  121. pendingChapters: newPending,
  122. });
  123. if (newPending.length === 0) {
  124. // 全部章节完成
  125. this.updateState(bookId, { phase: 'supplement' });
  126. return { done: true, chapter };
  127. }
  128. return { done: false, chapter, nextChapter: newPending[0] };
  129. } catch (error) {
  130. const errorMessage = error instanceof Error ? error.message : '生成失败';
  131. // 标记本章失败,继续下一章
  132. const newPending = state.pendingChapters.filter(n => n !== nextChapterNum);
  133. const newFailed = [...state.failedChapters, nextChapterNum];
  134. this.updateState(bookId, {
  135. currentNode: 'idle',
  136. pendingChapters: newPending,
  137. failedChapters: newFailed,
  138. });
  139. if (newPending.length === 0) {
  140. return { done: true, error: `第${nextChapterNum}章失败` };
  141. }
  142. return { done: false, error: `第${nextChapterNum}章失败: ${errorMessage}` };
  143. }
  144. }
  145. /**
  146. * 执行全部章节生成(自动循环)
  147. */
  148. async executeAllChapters(bookId: string, onProgress?: (progress: number, chapterNum: number) => void): Promise<{
  149. success: boolean;
  150. completedCount: number;
  151. failedCount: number;
  152. errors: string[];
  153. }> {
  154. const state = this.workflows.get(bookId);
  155. if (!state) return { success: false, completedCount: 0, failedCount: 0, errors: ['工作流不存在'] };
  156. const errors: string[] = [];
  157. while (true) {
  158. const currentState = this.workflows.get(bookId);
  159. if (!currentState || currentState.pendingChapters.length === 0) {
  160. break;
  161. }
  162. const nextChapter = currentState.pendingChapters[0];
  163. if (onProgress) {
  164. const progress = Math.round((currentState.completedChapters / currentState.totalChapters) * 100);
  165. onProgress(progress, nextChapter);
  166. }
  167. try {
  168. await bookGeneratorService.generateChapter(bookId, nextChapter);
  169. const updated = this.workflows.get(bookId);
  170. if (updated) {
  171. this.updateState(bookId, {
  172. completedChapters: updated.completedChapters + 1,
  173. pendingChapters: updated.pendingChapters.filter(n => n !== nextChapter),
  174. });
  175. }
  176. } catch (error) {
  177. const errorMessage = error instanceof Error ? error.message : '生成失败';
  178. errors.push(`第${nextChapter}章: ${errorMessage}`);
  179. const updated = this.workflows.get(bookId);
  180. if (updated) {
  181. this.updateState(bookId, {
  182. pendingChapters: updated.pendingChapters.filter(n => n !== nextChapter),
  183. failedChapters: [...updated.failedChapters, nextChapter],
  184. });
  185. }
  186. }
  187. }
  188. this.updateState(bookId, { phase: 'supplement' });
  189. const finalState = this.workflows.get(bookId);
  190. return {
  191. success: errors.length === 0,
  192. completedCount: finalState?.completedChapters || 0,
  193. failedCount: finalState?.failedChapters?.length || 0,
  194. errors,
  195. };
  196. }
  197. /**
  198. * 生成前言
  199. */
  200. async executeForeword(bookId: string): Promise<{ success: boolean; foreword?: string; error?: string }> {
  201. this.updateState(bookId, { currentNode: 'writing_foreword' });
  202. try {
  203. const foreword = await bookGeneratorService.generateForeword(bookId);
  204. this.updateState(bookId, { currentNode: 'idle' });
  205. return { success: true, foreword };
  206. } catch (error) {
  207. const errorMessage = error instanceof Error ? error.message : '生成前言失败';
  208. return { success: false, error: errorMessage };
  209. }
  210. }
  211. /**
  212. * 生成后记
  213. */
  214. async executeAfterword(bookId: string): Promise<{ success: boolean; afterword?: string; error?: string }> {
  215. this.updateState(bookId, { currentNode: 'writing_afterword' });
  216. try {
  217. const afterword = await bookGeneratorService.generateAfterword(bookId);
  218. this.updateState(bookId, { currentNode: 'idle' });
  219. return { success: true, afterword };
  220. } catch (error) {
  221. const errorMessage = error instanceof Error ? error.message : '生成后记失败';
  222. return { success: false, error: errorMessage };
  223. }
  224. }
  225. /**
  226. * 完成工作流
  227. */
  228. finishWorkflow(bookId: string): WorkflowState | undefined {
  229. const state = this.workflows.get(bookId);
  230. if (!state) return undefined;
  231. this.updateState(bookId, { currentNode: 'finished', phase: 'done' });
  232. return this.workflows.get(bookId);
  233. }
  234. /**
  235. * 获取工作流进度
  236. */
  237. getProgress(bookId: string): {
  238. phase: string;
  239. currentNode: string;
  240. currentChapter: number;
  241. completedChapters: number;
  242. totalChapters: number;
  243. pendingChapters: number[];
  244. failedChapters: number[];
  245. progress: number;
  246. } | null {
  247. const state = this.workflows.get(bookId);
  248. if (!state) return null;
  249. return {
  250. phase: state.phase,
  251. currentNode: state.currentNode,
  252. currentChapter: state.currentChapter,
  253. completedChapters: state.completedChapters,
  254. totalChapters: state.totalChapters,
  255. pendingChapters: state.pendingChapters,
  256. failedChapters: state.failedChapters,
  257. progress: Math.round((state.completedChapters / state.totalChapters) * 100),
  258. };
  259. }
  260. /**
  261. * 取消工作流
  262. */
  263. cancelWorkflow(bookId: string): boolean {
  264. return this.workflows.delete(bookId);
  265. }
  266. }
  267. // 导出单例
  268. export const workflowEngine = new BookWorkflowEngine();