sync-genstage.ts 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. /**
  2. * 状态同步脚本:将现有数据从旧字段同步到 genStage
  3. * 使用 stage-manager 的安全函数
  4. * Run: npx ts-node prisma/sync-genstage.ts
  5. */
  6. import { PrismaClient } from '@prisma/client';
  7. import {
  8. BookGenStage,
  9. ChapterGenStage,
  10. bookStageIndex,
  11. chapterStageIndex,
  12. safeTransitionChapter,
  13. safeTransitionBook,
  14. checkAndRegressBook,
  15. tryAdvanceBook,
  16. } from '../src/modules/book-generator/stage-manager';
  17. const prisma = new PrismaClient();
  18. async function syncChapter(chapter: any): Promise<void> {
  19. const chapterAny = chapter as any;
  20. let targetStage: ChapterGenStage;
  21. // 根据旧字段计算新的 genStage
  22. if (chapterAny.videoStatus === 'completed') {
  23. targetStage = 'video_completed';
  24. } else if (chapterAny.videoStatus === 'generating') {
  25. targetStage = 'video_generating';
  26. } else if (chapterAny.audioStatus === 'completed') {
  27. targetStage = 'audio_completed';
  28. } else if (chapterAny.audioStatus === 'generating') {
  29. targetStage = 'audio_generating';
  30. } else if (chapterAny.contentStatus === 'completed') {
  31. targetStage = 'content_completed';
  32. } else if (chapterAny.contentStatus === 'generating') {
  33. targetStage = 'content_generating';
  34. } else if (chapterAny.status === 'completed') {
  35. // 只有章级别的 status
  36. targetStage = 'content_completed';
  37. } else if (chapterAny.status === 'failed' || chapterAny.contentStatus === 'failed' || chapterAny.audioStatus === 'failed' || chapterAny.videoStatus === 'failed') {
  38. targetStage = 'failed';
  39. } else {
  40. targetStage = 'idle';
  41. }
  42. const currentStage = chapterAny.genStage as ChapterGenStage;
  43. // 如果相同则跳过
  44. if (currentStage === targetStage) {
  45. console.log(` Chapter ${chapter.id}: 无需更新 (${currentStage})`);
  46. return;
  47. }
  48. // 比较阶段索引决定用哪个函数
  49. const currentIdx = chapterStageIndex(currentStage);
  50. const targetIdx = chapterStageIndex(targetStage);
  51. try {
  52. if (targetIdx > currentIdx) {
  53. // 前进用 safeTransitionChapter(内部已处理乐观锁)
  54. await safeTransitionChapter(chapter.id, currentStage, targetStage);
  55. } else {
  56. // 回退/同级也用 safeTransitionChapter(重新生成场景)
  57. await safeTransitionChapter(chapter.id, currentStage, targetStage);
  58. }
  59. console.log(` Chapter ${chapter.id}: ${currentStage} → ${targetStage}`);
  60. } catch (error: any) {
  61. // 并发冲突:跳过此记录
  62. console.log(` Chapter ${chapter.id}: 跳过 (${error.message})`);
  63. }
  64. }
  65. async function syncBook(book: any): Promise<void> {
  66. let targetStage: BookGenStage;
  67. switch (book.status) {
  68. case 'draft':
  69. targetStage = 'draft';
  70. break;
  71. case 'planning':
  72. targetStage = 'outline_completed';
  73. break;
  74. case 'generating':
  75. targetStage = inferBookGenStage(book);
  76. break;
  77. case 'completed':
  78. targetStage = inferBookGenStage(book);
  79. break;
  80. case 'failed':
  81. case 'interrupted':
  82. targetStage = 'failed';
  83. break;
  84. default:
  85. targetStage = 'draft';
  86. }
  87. const currentStage = (book.genStage || 'draft') as BookGenStage;
  88. if (currentStage === targetStage) {
  89. console.log(` Book ${book.id}: 无需更新 (${currentStage})`);
  90. return;
  91. }
  92. try {
  93. await safeTransitionBook(book.id, currentStage, targetStage);
  94. console.log(` Book ${book.id}: ${currentStage} → ${targetStage}`);
  95. } catch (error: any) {
  96. console.log(` Book ${book.id}: 跳过 (${error.message})`);
  97. }
  98. }
  99. function inferBookGenStage(book: any): BookGenStage {
  100. // 根据 chapters 状态推断
  101. const chapters = book.chapters || [];
  102. const hasVideo = chapters.some((c: any) => c.genStage === 'video_completed' || c.videoStatus === 'completed');
  103. if (hasVideo) return 'video_completed';
  104. const hasAudio = chapters.some((c: any) => c.genStage === 'audio_completed' || c.audioStatus === 'completed');
  105. if (hasAudio) return 'audio_completed';
  106. const hasContent = chapters.some((c: any) => c.genStage === 'content_completed' || c.contentStatus === 'completed');
  107. if (hasContent) return 'content_generating';
  108. return 'content_generating';
  109. }
  110. async function main() {
  111. console.log('开始状态同步...\n');
  112. // 同步 BookChapter
  113. console.log('=== 同步 BookChapter ===');
  114. const chapters = await prisma.bookChapter.findMany();
  115. for (const chapter of chapters) {
  116. await syncChapter(chapter);
  117. }
  118. console.log(`BookChapter 同步完成: ${chapters.length} 条\n`);
  119. // 同步 Book
  120. console.log('=== 同步 Book ===');
  121. const books = await prisma.book.findMany({ include: { chapters: true } });
  122. for (const book of books) {
  123. await syncBook(book);
  124. }
  125. console.log(`Book 同步完成: ${books.length} 条\n`);
  126. // 执行 Book 联动回退检查
  127. console.log('=== 检查 Book 联动回退 ===');
  128. for (const book of books) {
  129. await checkAndRegressBook(book.id);
  130. }
  131. console.log('✅ 状态同步完成!');
  132. }
  133. main()
  134. .catch(console.error)
  135. .finally(() => prisma.$disconnect());