index.ts 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  1. /**
  2. * LangGraph 书籍生成器 - 主入口(策略门面)
  3. *
  4. * 使用策略模式,通过 strategies/selector.ts 配置切换不同生成方案
  5. *
  6. * 当前支持策略:
  7. * 1. sequential - 串行(原始方案)
  8. * 2. one-step-outline - 一步大纲 + 并行内容
  9. * 3. per-chapter - 逐章内聚
  10. * 4. deep-plan-parallel - DeepPlan+RichOutline+Concurrent+Edit(当前推荐,默认策略)
  11. *
  12. * 大纲层级(genLevel):
  13. * 1 = 仅章(短文/小说/故事类)
  14. * 2 = 章→节(科普/商业/大众类)
  15. * 3 = 章→节→小节(教材/技术教程/专业类)
  16. *
  17. * 成本追踪:通过 AsyncLocalStorage 上下文,所有 LLM/TTS 调用自动携带 bookId/chapterId
  18. */
  19. import { bookStore } from './book-generator.store';
  20. import { getScaleConfig } from './book-type-config';
  21. import { getCurrentStrategy, setCurrentStrategy, getAllStrategies, getCurrentStrategyName } from './strategies/selector';
  22. import type { StrategyName } from './strategies/selector';
  23. import { runWithContext } from '../../services/llm-context';
  24. // ============ 书籍类型 → 大纲层级映射 ============
  25. const BOOK_TYPE_GEN_LEVEL: Record<string, number> = {
  26. '教材': 3,
  27. '技术教程': 3,
  28. '小说': 1,
  29. '传记': 1,
  30. '商业': 2,
  31. '科普': 2,
  32. '历史': 2,
  33. '学术': 3,
  34. '心理学': 2,
  35. };
  36. /**
  37. * 将书籍类型映射为大纲层级
  38. */
  39. export function mapBookTypeToGenLevel(bookType: string): number {
  40. return BOOK_TYPE_GEN_LEVEL[bookType] ?? 2;
  41. }
  42. /**
  43. * 根据书籍规模和话题文本自动推荐大纲层级
  44. * 增强版:如未指定 genLevel,会从话题文本推断书籍类型→层级
  45. */
  46. export function resolveGenLevel(bookScale: string, userGenLevel?: number | string, topic?: string): number {
  47. if (userGenLevel !== undefined && userGenLevel !== null) {
  48. const n = typeof userGenLevel === 'string' ? parseInt(userGenLevel) : userGenLevel;
  49. if ([1, 2, 3].includes(n)) return n;
  50. }
  51. const config = getScaleConfig(bookScale);
  52. if (!config) return 2;
  53. if (config.isShortArticle) return 1;
  54. // 从话题文本推断书籍类型,增强 genLevel 推荐准确度
  55. if (topic) {
  56. const text = topic.toLowerCase();
  57. if (/教材|教程|编程|开发|实战|教学|课程/i.test(text)) return 3;
  58. if (/小说|故事|传记|散文|诗歌/i.test(text)) return 1;
  59. }
  60. return 2;
  61. }
  62. // ============ 策略切换 API(外部可调用) ============
  63. export { setCurrentStrategy, getCurrentStrategyName, getAllStrategies } from './strategies/selector';
  64. export type { StrategyName } from './strategies/selector';
  65. // ============ 主类(策略门面) ============
  66. export class LangGraphBookGenerator {
  67. async generate(
  68. bookId: string,
  69. topic: string,
  70. bookScale: string = '1000',
  71. genLevel?: number,
  72. userId?: number,
  73. userSpecifiedVoiceSpeed?: boolean,
  74. ): Promise<void> {
  75. const userSpecified = genLevel !== undefined;
  76. const level = genLevel ?? resolveGenLevel(bookScale, undefined, topic);
  77. const strategy = getCurrentStrategy();
  78. console.log(`[LangGraph] 开始生成: bookId=${bookId}, userId=${userId ?? '-'}, scale=${bookScale}, genLevel=${level}, strategy=${strategy.name}, userSpecifiedVoiceSpeed=${userSpecifiedVoiceSpeed}`);
  79. // 设置异步上下文,后续所有 LLM/TTS 调用自动携带 userId / bookId
  80. await runWithContext({ userId, bookId: parseInt(bookId) }, async () => {
  81. await strategy.generate(bookId, topic, bookScale, level, userSpecified ? level : undefined, userSpecifiedVoiceSpeed);
  82. });
  83. }
  84. }
  85. export const langGraphGenerator = new LangGraphBookGenerator();
  86. // 重新导出 book-type-config 中的函数供外部使用
  87. export { getScaleConfig } from './book-type-config';
  88. // ============ 独立大纲生成函数(供 API 预览调用,不依赖完整策略) ============
  89. import { callLLMWithMessages } from '../../services/llm';
  90. import { buildOutlineMessages } from './prompts/builder';
  91. import { parseOutline } from './parsers/outline.parser';
  92. /**
  93. * 独立的大纲生成函数(供 API 预览调用,不依赖策略系统)
  94. * 用于让用户在正式开始生成前预览大纲结构
  95. */
  96. export async function generateOutline(bookId: string, topic: string, bookScale: string = '1000'): Promise<any> {
  97. console.log('[generateOutline] 预览大纲, bookId:', bookId, 'scale:', bookScale);
  98. const messages = buildOutlineMessages(topic, bookScale);
  99. const response = await callLLMWithMessages(messages);
  100. const outline = parseOutline(response);
  101. if (!outline) {
  102. throw new Error('大纲解析失败');
  103. }
  104. return outline;
  105. }