book-generator-api.ts 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674
  1. /**
  2. * 书籍生成模块 API
  3. */
  4. import { request, get, post } from './request';
  5. // ============ 类型定义 ============
  6. export interface Book {
  7. id: string;
  8. userId?: number;
  9. title: string;
  10. subtitle?: string;
  11. description: string;
  12. targetAudience: string;
  13. style: string;
  14. bookScale?: string;
  15. totalChapters: number;
  16. estimatedWords: number;
  17. progress: number;
  18. chapters: Chapter[];
  19. outline?: BookOutline;
  20. metadata?: BookMetadata;
  21. error?: string;
  22. isPublished?: boolean;
  23. genStage?: BookGenStage; // 线性阶段状态(替代旧 status 字段)
  24. failedStage?: string; // 失败阶段
  25. createdAt: string;
  26. updatedAt: string;
  27. }
  28. export interface BookOutline {
  29. mainTheme: string;
  30. structureLogic: string;
  31. chapters: OutlineChapter[];
  32. }
  33. export interface OutlineSubsection {
  34. title: string;
  35. summary?: string;
  36. }
  37. export interface OutlineSection {
  38. title: string;
  39. summary: string;
  40. subsections?: OutlineSubsection[];
  41. }
  42. export interface OutlineChapter {
  43. number: number;
  44. title: string;
  45. summary: string;
  46. keyPoints: string[];
  47. estimatedWords: number;
  48. stories?: string[];
  49. sections?: OutlineSection[];
  50. }
  51. export interface Chapter {
  52. id: string;
  53. bookId: string;
  54. number: number;
  55. title: string;
  56. content: string;
  57. summary?: string;
  58. wordCount: number;
  59. audioUrl?: string;
  60. audioDuration?: number;
  61. audioStatus?: string;
  62. videoUrl?: string;
  63. videoDuration?: number;
  64. videoStatus?: string;
  65. contentStatus?: string;
  66. contentError?: string;
  67. error?: string;
  68. level?: number; // 层级:1=章, 2=节, 3=小节
  69. parentId?: number | null; // 父节点ID(null表示章)
  70. genStage?: ChapterGenStage; // 线性阶段状态(替代旧 status 字段)
  71. }
  72. // ============ 线性阶段类型(与后端 stage-manager.ts 同步) ============
  73. export type BookGenStage =
  74. | 'draft'
  75. | 'outlining'
  76. | 'outline_completed'
  77. | 'content_generating'
  78. | 'content_completed'
  79. | 'audio_generating'
  80. | 'audio_completed'
  81. | 'video_generating'
  82. | 'video_completed'
  83. | 'failed';
  84. export type ChapterGenStage =
  85. | 'idle'
  86. | 'outline_completed'
  87. | 'content_generating'
  88. | 'content_completed'
  89. | 'audio_generating'
  90. | 'audio_completed'
  91. | 'video_generating'
  92. | 'video_completed'
  93. | 'failed';
  94. export interface BookMetadata {
  95. author?: string;
  96. foreword?: string;
  97. afterword?: string;
  98. }
  99. export interface CreateBookRequest {
  100. title: string;
  101. subtitle?: string;
  102. description: string;
  103. targetAudience?: string;
  104. style?: string;
  105. bookScale?: string;
  106. bookType?: string;
  107. genLevel?: number;
  108. language?: string;
  109. }
  110. export interface ProgressResponse {
  111. bookId: string;
  112. progress: number;
  113. genStage?: string;
  114. completedChapters: number;
  115. totalChapters: number;
  116. currentTask?: {
  117. type: string;
  118. message: string;
  119. };
  120. }
  121. // ============ API 函数 ============
  122. const BASE_URL = '/book-generator/langgraph';
  123. /**
  124. * 创建书籍
  125. */
  126. export async function createBook(data: CreateBookRequest): Promise<Book> {
  127. const result = await request<{ book: Book }>(`${BASE_URL}/books`, { method: 'POST', data: { ...data } });
  128. return result.book;
  129. }
  130. /**
  131. * 获取所有书籍
  132. */
  133. export async function getBooks(): Promise<Book[]> {
  134. const result = await request<{ books: Book[] }>(`${BASE_URL}/books`);
  135. return result.books || [];
  136. }
  137. /**
  138. * 获取公开书籍列表(首页专用)
  139. */
  140. export async function getPublicBooks(): Promise<Book[]> {
  141. const result = await request<{ books: Book[] }>(`${BASE_URL}/public-books`);
  142. return result.books || [];
  143. }
  144. /**
  145. * 获取书籍详情
  146. */
  147. export async function getBook(id: string): Promise<Book> {
  148. const result = await request<{ book: Book }>(`${BASE_URL}/books/${id}`);
  149. return result.book;
  150. }
  151. /**
  152. * 获取单个章节详情(从专辑控制器获取,只返回单章节内容)
  153. */
  154. export async function getChapter(chapterId: string): Promise<any> {
  155. return await request<any>(`/book-generator/books/chapters/${chapterId}`);
  156. }
  157. /**
  158. * 删除书籍
  159. */
  160. export async function deleteBook(id: string): Promise<void> {
  161. await request(`${BASE_URL}/books/${id}`, { method: 'DELETE' });
  162. }
  163. /**
  164. * 生成大纲
  165. */
  166. export async function generateOutline(id: string): Promise<BookOutline> {
  167. const result = await request<{ outline: BookOutline }>(`${BASE_URL}/books/${id}/outline`, { method: 'POST' });
  168. return result.outline;
  169. }
  170. /**
  171. * 生成单个章节
  172. */
  173. export async function generateChapter(id: string, chapterNumber: number): Promise<Chapter> {
  174. const result = await request<{ chapter: Chapter }>(`${BASE_URL}/books/${id}/chapters`, {
  175. method: 'POST',
  176. data: { chapterNumber },
  177. });
  178. return result.chapter;
  179. }
  180. /**
  181. * 生成全部章节
  182. */
  183. export async function generateAllChapters(id: string): Promise<Chapter[]> {
  184. const result = await request<{ chapters: Chapter[] }>(`${BASE_URL}/books/${id}/chapters`, { method: 'POST' });
  185. return result.chapters || [];
  186. }
  187. /**
  188. * 生成前言
  189. */
  190. export async function generateForeword(id: string): Promise<string> {
  191. const result = await request<{ foreword: string }>(`${BASE_URL}/books/${id}/foreword`, { method: 'POST' });
  192. return result.foreword;
  193. }
  194. /**
  195. * 生成后记
  196. */
  197. export async function generateAfterword(id: string): Promise<string> {
  198. const result = await request<{ afterword: string }>(`${BASE_URL}/books/${id}/afterword`, { method: 'POST' });
  199. return result.afterword;
  200. }
  201. // ============ 交互式生成 API ============
  202. /**
  203. * 启动深度规划(异步,立即返回)
  204. */
  205. export async function startInteractivePlan(bookId: string, feedback?: string): Promise<{ genStage: string }> {
  206. const result = await request<any>(`${BASE_URL}/books/${bookId}/interactive/plan`, {
  207. method: 'POST',
  208. data: feedback ? { feedback } : undefined,
  209. config: { timeout: 10000 },
  210. });
  211. return result;
  212. }
  213. /**
  214. * 轮询 Plan 生成状态与结果
  215. * 返回 { genStage, rawPlan?, planData?, error? }
  216. */
  217. export async function pollInteractivePlan(bookId: string): Promise<any> {
  218. const result = await request<any>(`${BASE_URL}/books/${bookId}/interactive/plan`, { method: 'GET', config: { timeout: 5000 } });
  219. return result;
  220. }
  221. /**
  222. * 保存用户修改后的 Plan
  223. */
  224. export async function updateInteractivePlan(bookId: string, plan: string): Promise<void> {
  225. await request(`${BASE_URL}/books/${bookId}/interactive/plan`, { method: 'PUT', data: { plan } });
  226. }
  227. /**
  228. * 启动大纲生成(异步,立即返回)
  229. */
  230. export async function startInteractiveOutline(bookId: string): Promise<{ genStage: string }> {
  231. const result = await request<any>(`${BASE_URL}/books/${bookId}/interactive/outline`, { method: 'POST', config: { timeout: 10000 } });
  232. return result;
  233. }
  234. /**
  235. * 轮询 Outline 生成状态与结果
  236. * 返回 { genStage, rawOutline?, outlineData?, error? }
  237. */
  238. export async function pollInteractiveOutline(bookId: string): Promise<any> {
  239. const result = await request<any>(`${BASE_URL}/books/${bookId}/interactive/outline`, { method: 'GET', config: { timeout: 5000 } });
  240. return result;
  241. }
  242. /**
  243. * 保存用户修改后的大纲
  244. */
  245. export async function updateInteractiveOutline(bookId: string, outline: any): Promise<void> {
  246. await request(`${BASE_URL}/books/${bookId}/interactive/outline`, { method: 'PUT', data: { outline } });
  247. }
  248. /**
  249. * 运行内容生成(交互模式 Step 4)
  250. */
  251. export async function generateInteractiveContent(bookId: string): Promise<{ bookId: string; genStage: string; jobId?: string }> {
  252. const result = await request<any>(`${BASE_URL}/books/${bookId}/interactive/generate`, { method: 'POST', config: { timeout: 10000 } });
  253. return result;
  254. }
  255. /**
  256. * 创建书籍但不自动生成(交互模式 Step 1 使用)
  257. */
  258. export async function createBookInteractive(data: CreateBookRequest): Promise<Book> {
  259. const result = await request<{ book: Book }>(`${BASE_URL}/books`, { method: 'POST', data: { ...data, immediateGenerate: false } });
  260. return result.book;
  261. }
  262. /**
  263. * 获取完整书籍内容
  264. */
  265. export async function getFullContent(id: string): Promise<string> {
  266. const result = await request<{ content: string }>(`${BASE_URL}/books/${id}/full-content`);
  267. return result.content;
  268. }
  269. /**
  270. * 获取生成进度
  271. */
  272. export async function getProgress(id: string): Promise<ProgressResponse> {
  273. const result = await request<ProgressResponse>(`${BASE_URL}/books/${id}/progress`);
  274. return result;
  275. }
  276. /**
  277. * 一键生成整本书
  278. */
  279. export async function generateBook(
  280. id: string,
  281. options?: {
  282. generateForeword?: boolean;
  283. generateAfterword?: boolean;
  284. }
  285. ): Promise<{
  286. success: boolean;
  287. completedChapters: number;
  288. failedChapters: number;
  289. foreword?: string;
  290. afterword?: string;
  291. errors: string[];
  292. }> {
  293. const result = await request<{
  294. success: boolean;
  295. completedChapters: number;
  296. failedChapters: number;
  297. foreword?: string;
  298. afterword?: string;
  299. errors: string[];
  300. }>(`${BASE_URL}/books/${id}/generate`, {
  301. method: 'POST',
  302. data: options || {},
  303. });
  304. return result;
  305. }
  306. /**
  307. * 工作流状态
  308. */
  309. export interface WorkflowProgress {
  310. phase: 'planning' | 'writing' | 'supplement' | 'done';
  311. currentNode: string;
  312. currentChapter: number;
  313. completedChapters: number;
  314. totalChapters: number;
  315. pendingChapters: number[];
  316. failedChapters: number[];
  317. progress: number;
  318. }
  319. /**
  320. * 获取工作流状态
  321. */
  322. export async function getWorkflowProgress(bookId: string): Promise<WorkflowProgress | null> {
  323. const result = await request<WorkflowProgress>(`${BASE_URL}/workflow/${bookId}`);
  324. return result;
  325. }
  326. // ============ LangGraph API ============
  327. /**
  328. * 使用 LangGraph 创建并生成书籍
  329. */
  330. export async function createBookWithLangGraph(data: {
  331. title: string;
  332. description: string;
  333. totalChapters?: number;
  334. }): Promise<{ bookId: string; taskId: string }> {
  335. const result = await request<{ bookId: string; taskId: string }>(`${BASE_URL}/books`, {
  336. method: 'POST',
  337. data,
  338. });
  339. return result;
  340. }
  341. /**
  342. * 使用 LangGraph 生成已有书籍
  343. */
  344. export async function generateWithLangGraph(bookId: string): Promise<{ bookId: string; taskId: string }> {
  345. const result = await request<{ bookId: string; taskId: string }>(`${BASE_URL}/books/${bookId}/generate`, {
  346. method: 'POST',
  347. });
  348. return result;
  349. }
  350. // ============ 音频生成 API ============
  351. /**
  352. * 生成单个章节音频(实际调用批量生成,后端会生成所有章节)
  353. */
  354. export async function generateChapterAudio(
  355. bookId: string,
  356. chapterNumber: number,
  357. voiceId: string = 'voice_01'
  358. ): Promise<{ taskId: string; totalSubsections: number }> {
  359. // 后端只有批量生成API,所以调用批量生成
  360. const result = await request<{ taskId: string; totalSubsections: number }>(
  361. `${BASE_URL}/books/${bookId}/audio`,
  362. {
  363. method: 'POST',
  364. data: { voiceId },
  365. }
  366. );
  367. return result;
  368. }
  369. /**
  370. * 批量生成书籍所有章节音频
  371. */
  372. export async function generateAllChaptersAudio(
  373. bookId: string,
  374. voiceId: string = 'voice_01'
  375. ): Promise<{ taskId: string; totalChapters: number }> {
  376. const result = await request<{ taskId: string; totalSubsections: number }>(
  377. `${BASE_URL}/books/${bookId}/audio`,
  378. {
  379. method: 'POST',
  380. data: { voiceId },
  381. }
  382. );
  383. return {
  384. taskId: result.taskId,
  385. totalChapters: result.totalSubsections,
  386. };
  387. }
  388. // ============ 视频生成 API ============
  389. /**
  390. * 取消书籍所有章节音频生成
  391. */
  392. export async function cancelAudioGeneration(
  393. bookId: string
  394. ): Promise<{ cancelledCount: number; rolledBackChapters: number[] }> {
  395. const result = await request<{ cancelledCount: number; rolledBackChapters: number[] }>(
  396. `${BASE_URL}/books/${bookId}/audio/cancel`,
  397. { method: 'POST' }
  398. );
  399. return result;
  400. }
  401. /**
  402. * 生成单个章节视频(从音频转视频)
  403. */
  404. export async function generateChapterVideo(
  405. bookId: string,
  406. chapterId: string | number
  407. ): Promise<{ projectId: number; chapterId: string }> {
  408. const result = await request<{ projectId: number; chapterId: string }>(
  409. `${BASE_URL}/books/${bookId}/chapters/${chapterId}/video`,
  410. {
  411. method: 'POST',
  412. }
  413. );
  414. return result;
  415. }
  416. /**
  417. * 批量生成书籍所有章节视频
  418. */
  419. export async function generateAllChaptersVideo(
  420. bookId: string
  421. ): Promise<{ taskId: string; totalChapters: number }> {
  422. const result = await request<{ taskId: string; totalChapters: number }>(
  423. `${BASE_URL}/books/${bookId}/videos`,
  424. {
  425. method: 'POST',
  426. }
  427. );
  428. return result;
  429. }
  430. /**
  431. * 合并叶节点音频到上级章节
  432. */
  433. export async function mergeChapterAudios(
  434. bookId: string
  435. ): Promise<{ taskId: string; processedParents: number; totalLeafNodes: number }> {
  436. const result = await request<{ taskId: string; processedParents: number; totalLeafNodes: number }>(
  437. `${BASE_URL}/books/${bookId}/merge-audio`,
  438. {
  439. method: 'POST',
  440. }
  441. );
  442. return result;
  443. }
  444. /**
  445. * 合并叶节点视频到上级章节
  446. */
  447. export async function mergeChapterVideos(
  448. bookId: string
  449. ): Promise<{ taskId: string; processedParents: number; totalLeafNodes: number }> {
  450. const result = await request<{ taskId: string; processedParents: number; totalLeafNodes: number }>(
  451. `${BASE_URL}/books/${bookId}/merge-video`,
  452. {
  453. method: 'POST',
  454. }
  455. );
  456. return result;
  457. }
  458. /**
  459. * 获取书籍音频生成状态(用于轮询)
  460. */
  461. export async function getAudioStatus(bookId: string): Promise<{
  462. bookId: string;
  463. total: number;
  464. completed: number;
  465. pending: number;
  466. allCompleted: boolean;
  467. }> {
  468. const result = await request<{
  469. bookId: string;
  470. total: number;
  471. completed: number;
  472. pending: number;
  473. allCompleted: boolean;
  474. }>(`${BASE_URL}/books/${bookId}/audio-status`);
  475. return result;
  476. }
  477. /**
  478. * 获取书籍视频生成状态(用于轮询)
  479. */
  480. export async function getVideoStatus(bookId: string): Promise<{
  481. bookId: string;
  482. total: number;
  483. completed: number;
  484. pending: number;
  485. allCompleted: boolean;
  486. }> {
  487. const result = await request<{
  488. bookId: string;
  489. total: number;
  490. completed: number;
  491. pending: number;
  492. allCompleted: boolean;
  493. }>(`${BASE_URL}/books/${bookId}/video-status`);
  494. return result;
  495. }
  496. /**
  497. * 重新生成单个章节内容(仅叶节点)
  498. */
  499. export async function regenerateChapterContent(
  500. bookId: string,
  501. chapterId: number
  502. ): Promise<{ chapterId: number; jobId: string }> {
  503. const result = await request<{ chapterId: number; jobId: string }>(
  504. `${BASE_URL}/books/${bookId}/chapters/${chapterId}/regenerate`,
  505. { method: 'POST' }
  506. );
  507. return result;
  508. }
  509. /**
  510. * 重新生成单个章节音频(仅叶节点)
  511. */
  512. export async function regenerateChapterAudio(
  513. bookId: string,
  514. chapterId: number
  515. ): Promise<{ chapterId: number }> {
  516. const result = await request<{ chapterId: number }>(
  517. `${BASE_URL}/books/${bookId}/chapters/${chapterId}/regenerate-audio`,
  518. { method: 'POST' }
  519. );
  520. return result;
  521. }
  522. // ============ 书籍发布 API ============
  523. /**
  524. * 切换书籍公开状态
  525. */
  526. export async function toggleBookPublish(bookId: string): Promise<{ isPublished: boolean }> {
  527. const result = await request<{ isPublished: boolean }>(
  528. `${BASE_URL}/books/${bookId}/publish`,
  529. { method: 'PUT' }
  530. );
  531. return result;
  532. }
  533. // ============ 阶段状态辅助函数(与后端 stage-manager.ts 同步) ============
  534. /**
  535. * 获取书籍阶段的用户可读描述
  536. */
  537. export function getBookStageDescription(genStage?: string): string {
  538. if (!genStage) return '未知';
  539. switch (genStage) {
  540. case 'draft': return '草稿';
  541. case 'outlining': return '正在生成大纲...';
  542. case 'outline_completed': return '大纲已完成';
  543. case 'content_generating': return '正在生成内容...';
  544. case 'content_completed': return '内容已完成,等待生成音频';
  545. case 'audio_generating': return '正在生成音频...';
  546. case 'audio_completed': return '音频已完成,等待生成视频';
  547. case 'video_generating': return '正在生成视频...';
  548. case 'video_completed': return '全部完成 ✓';
  549. case 'failed': return '生成失败';
  550. default: return genStage;
  551. }
  552. }
  553. /**
  554. * 获取章节阶段的用户可读描述
  555. */
  556. export function getChapterStageDescription(genStage?: string): string {
  557. if (!genStage) return '待生成';
  558. switch (genStage) {
  559. case 'idle': return '待生成';
  560. case 'outline_completed': return '大纲完成';
  561. case 'content_generating': return '生成中';
  562. case 'content_completed': return '内容完成';
  563. case 'audio_generating': return '生成音频中';
  564. case 'audio_completed': return '音频完成';
  565. case 'video_generating': return '生成视频中';
  566. case 'video_completed': return '已完成';
  567. case 'failed': return '失败';
  568. default: return genStage;
  569. }
  570. }
  571. /**
  572. * 判断书籍是否需要显示"生成音频"按钮
  573. */
  574. export function needsAudioGeneration(genStage?: string): boolean {
  575. return genStage === 'content_completed';
  576. }
  577. /**
  578. * 判断书籍是否需要显示"生成视频"按钮
  579. */
  580. export function needsVideoGeneration(genStage?: string): boolean {
  581. return genStage === 'audio_completed';
  582. }
  583. /**
  584. * 判断书籍是否全部完成(最终态)
  585. */
  586. export function isBookFullyCompleted(genStage?: string): boolean {
  587. return genStage === 'video_completed';
  588. }
  589. /**
  590. * 获取书籍完整信息(用于编辑)
  591. */
  592. export async function getBookForEdit(id: string): Promise<any> {
  593. return await request<any>(`/book-generator/books/${id}`);
  594. }
  595. /**
  596. * 更新书籍基础信息
  597. */
  598. export async function updateBook(id: string, data: {
  599. title?: string;
  600. description?: string;
  601. coverUrl?: string;
  602. targetAudience?: string;
  603. style?: string;
  604. bookScale?: string;
  605. }): Promise<{ id: number; title: string }> {
  606. return post(`/book-generator/books/${id}`, data);
  607. }