learning-path.service.js 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803
  1. "use strict";
  2. /**
  3. * 学习路径生成服务
  4. * 支持两种模式:
  5. * 1. progressive(渐进确认) - 用户在每步确认后继续
  6. * 2. multi-agent(多Agent并行) - 全自动并行生成
  7. */
  8. var __importDefault = (this && this.__importDefault) || function (mod) {
  9. return (mod && mod.__esModule) ? mod : { "default": mod };
  10. };
  11. Object.defineProperty(exports, "__esModule", { value: true });
  12. exports.createLearningPath = createLearningPath;
  13. exports.getLearningPathDetail = getLearningPathDetail;
  14. exports.getUserTasks = getUserTasks;
  15. exports.getPendingConfirmation = getPendingConfirmation;
  16. exports.confirmAndContinue = confirmAndContinue;
  17. exports.regenerateNode = regenerateNode;
  18. exports.getAgentStatus = getAgentStatus;
  19. const axios_1 = __importDefault(require("axios"));
  20. const config_1 = require("../../config");
  21. const models_1 = require("../../models");
  22. /**
  23. * 获取可用模型列表
  24. */
  25. function getAvailableModels() {
  26. return config_1.config.models.getModelsByType('text').filter((m) => m.enabled !== false);
  27. }
  28. function getRandomModel() {
  29. const models = getAvailableModels();
  30. return models[Math.floor(Math.random() * models.length)].id;
  31. }
  32. /**
  33. * 调用 LLM API
  34. */
  35. async function callLLM(prompt) {
  36. const modelId = getRandomModel();
  37. const modelConfig = config_1.config.models.getModel(modelId);
  38. if (!modelConfig) {
  39. throw new Error(`模型 ${modelId} 不存在`);
  40. }
  41. const { apiKey, baseUrl } = modelConfig;
  42. if (!apiKey || !baseUrl) {
  43. throw new Error(`模型 ${modelId} 缺少 API 配置`);
  44. }
  45. try {
  46. const response = await axios_1.default.post(`${baseUrl}/chat/completions`, {
  47. model: modelId,
  48. messages: [{ role: 'user', content: prompt }],
  49. }, {
  50. headers: {
  51. 'Authorization': `Bearer ${apiKey}`,
  52. 'Content-Type': 'application/json',
  53. },
  54. timeout: 180000,
  55. });
  56. const data = response.data;
  57. return data.choices?.[0]?.message?.content || '';
  58. }
  59. catch (error) {
  60. console.error('❌ LLM 调用失败:', error.message);
  61. throw new Error(error.message || 'AI 生成失败');
  62. }
  63. }
  64. /**
  65. * 解析 AI 返回的 JSON 列表
  66. */
  67. function parseJsonList(aiResponse) {
  68. const jsonMatch = aiResponse.match(/\[[\s\S]*\]/);
  69. if (jsonMatch) {
  70. try {
  71. const parsed = JSON.parse(jsonMatch[0]);
  72. if (Array.isArray(parsed)) {
  73. return parsed.map(item => {
  74. if (typeof item === 'string')
  75. return item;
  76. if (typeof item === 'object' && item !== null) {
  77. return item.title || item.name || item.chapter_title || item.section_title || item.section || item.chapter || JSON.stringify(item);
  78. }
  79. return String(item);
  80. });
  81. }
  82. }
  83. catch (e) { }
  84. }
  85. const lines = aiResponse.split(/[,,\n]/).filter(line => line.trim().length > 0);
  86. return lines.map(line => line.replace(/^[\d一二三四五六七八九十]+[.、::]\s*/, '').trim()).filter(line => line.length > 0);
  87. }
  88. // ============ 通用函数 ============
  89. /**
  90. * 创建学习路径任务
  91. * @param userId 用户ID
  92. * @param topic 学习主题
  93. * @param mode 生成模式: progressive | multi-agent
  94. */
  95. async function createLearningPath(userId, topic, mode = 'progressive') {
  96. const task = await models_1.prisma.learningPath.create({
  97. data: {
  98. userId,
  99. topic,
  100. status: 'pending',
  101. progress: 0,
  102. generationMode: mode,
  103. currentStep: 'pending',
  104. },
  105. });
  106. // 根据模式选择生成方式
  107. if (mode === 'multi-agent') {
  108. // 多Agent模式:直接开始全量生成
  109. generateMultiAgent(task.id).catch(error => {
  110. console.error('❌ 多Agent生成失败:', error);
  111. models_1.prisma.learningPath.update({
  112. where: { id: task.id },
  113. data: { status: 'failed', errorMsg: error.message },
  114. }).catch(console.error);
  115. });
  116. }
  117. else {
  118. // 渐进模式:先生成学科列表等待用户确认
  119. generateSubjectsStep(task.id).catch(error => {
  120. console.error('❌ 学科生成失败:', error);
  121. models_1.prisma.learningPath.update({
  122. where: { id: task.id },
  123. data: { status: 'failed', errorMsg: error.message },
  124. }).catch(console.error);
  125. });
  126. }
  127. return task;
  128. }
  129. /**
  130. * 获取学习路径详情
  131. */
  132. async function getLearningPathDetail(id) {
  133. const task = await models_1.prisma.learningPath.findUnique({
  134. where: { id },
  135. include: {
  136. subjects: {
  137. orderBy: { orderIndex: 'asc' },
  138. include: {
  139. chapters: {
  140. orderBy: { orderIndex: 'asc' },
  141. include: {
  142. sections: {
  143. orderBy: { orderIndex: 'asc' },
  144. include: {
  145. contentBlocks: {
  146. orderBy: { orderIndex: 'asc' },
  147. },
  148. },
  149. },
  150. },
  151. },
  152. },
  153. },
  154. },
  155. });
  156. return task;
  157. }
  158. /**
  159. * 获取用户的任务列表
  160. */
  161. async function getUserTasks(userId) {
  162. return models_1.prisma.learningPath.findMany({
  163. where: { userId },
  164. orderBy: { createdAt: 'desc' },
  165. take: 20,
  166. });
  167. }
  168. /**
  169. * 获取渐进模式的待确认状态
  170. */
  171. async function getPendingConfirmation(taskId) {
  172. const task = await models_1.prisma.learningPath.findUnique({
  173. where: { id: taskId },
  174. include: {
  175. subjects: {
  176. orderBy: { orderIndex: 'asc' },
  177. include: {
  178. chapters: {
  179. orderBy: { orderIndex: 'asc' },
  180. include: {
  181. sections: {
  182. orderBy: { orderIndex: 'asc' },
  183. },
  184. },
  185. },
  186. },
  187. },
  188. },
  189. });
  190. if (!task)
  191. return null;
  192. return {
  193. id: task.id,
  194. topic: task.topic,
  195. status: task.status,
  196. progress: task.progress,
  197. currentStep: task.currentStep,
  198. generationMode: task.generationMode,
  199. // 待确认的学科
  200. pendingSubjects: task.subjects
  201. .filter(s => s.confirmationStatus === 'pending')
  202. .map(s => ({ id: s.id, name: s.name, status: s.status })),
  203. // 待确认的章节
  204. pendingChapters: task.subjects.reduce((acc, subject) => {
  205. acc[subject.id] = subject.chapters
  206. .filter(c => c.confirmationStatus === 'pending')
  207. .map(c => ({ id: c.id, name: c.name, status: c.status }));
  208. return acc;
  209. }, {}),
  210. // 待确认的小节
  211. pendingSections: task.subjects.reduce((acc, subject) => {
  212. subject.chapters.forEach(chapter => {
  213. acc[chapter.id] = chapter.sections
  214. .filter(s => s.confirmationStatus === 'pending')
  215. .map(s => ({ id: s.id, name: s.name, status: s.status }));
  216. });
  217. return acc;
  218. }, {}),
  219. };
  220. }
  221. /**
  222. * 确认并继续生成
  223. * @param taskId 任务ID
  224. * @param step 当前步骤: subjects | chapters | sections
  225. * @param confirmedIds 确认的ID列表
  226. * @param modifiedItems 修改的项 [{id, name}]
  227. */
  228. async function confirmAndContinue(taskId, step, confirmedIds, modifiedItems = []) {
  229. const task = await models_1.prisma.learningPath.findUnique({ where: { id: taskId } });
  230. if (!task)
  231. throw new Error('任务不存在');
  232. // 处理修改项
  233. for (const item of modifiedItems) {
  234. if (step === 'subjects') {
  235. await models_1.prisma.subject.update({ where: { id: item.id }, data: { name: item.name } });
  236. }
  237. else if (step === 'chapters') {
  238. await models_1.prisma.chapter.update({ where: { id: item.id }, data: { name: item.name } });
  239. }
  240. }
  241. // 标记确认的项
  242. if (step === 'subjects') {
  243. await models_1.prisma.subject.updateMany({
  244. where: { id: { in: confirmedIds }, learningPathId: taskId },
  245. data: { confirmationStatus: 'confirmed' },
  246. });
  247. // 更新任务步骤
  248. await models_1.prisma.learningPath.update({
  249. where: { id: taskId },
  250. data: { currentStep: 'chapters' },
  251. });
  252. // 开始生成章节
  253. generateChaptersStep(taskId, confirmedIds).catch(console.error);
  254. return { currentStep: 'chapters' };
  255. }
  256. else if (step === 'chapters') {
  257. await models_1.prisma.chapter.updateMany({
  258. where: { id: { in: confirmedIds } },
  259. data: { confirmationStatus: 'confirmed' },
  260. });
  261. await models_1.prisma.learningPath.update({
  262. where: { id: taskId },
  263. data: { currentStep: 'sections' },
  264. });
  265. // 开始生成小节
  266. generateSectionsStep(taskId, confirmedIds).catch(console.error);
  267. return { currentStep: 'sections' };
  268. }
  269. else if (step === 'sections') {
  270. await models_1.prisma.section.updateMany({
  271. where: { id: { in: confirmedIds } },
  272. data: { confirmationStatus: 'confirmed' },
  273. });
  274. await models_1.prisma.learningPath.update({
  275. where: { id: taskId },
  276. data: { currentStep: 'content' },
  277. });
  278. // 开始生成详细内容
  279. generateContentStep(taskId, confirmedIds).catch(console.error);
  280. return { currentStep: 'content' };
  281. }
  282. return { currentStep: task.currentStep };
  283. }
  284. /**
  285. * 重新生成某个节点
  286. */
  287. async function regenerateNode(taskId, type, id, instruction) {
  288. if (type === 'chapter') {
  289. const chapter = await models_1.prisma.chapter.findUnique({ where: { id } });
  290. if (!chapter)
  291. throw new Error('章节不存在');
  292. await models_1.prisma.chapter.update({ where: { id }, data: { status: 'generating', confirmationStatus: 'regenerating' } });
  293. // 重新生成小节
  294. const subject = await models_1.prisma.subject.findUnique({ where: { id: chapter.subjectId } });
  295. const task = await models_1.prisma.learningPath.findUnique({ where: { id: taskId } });
  296. const sectionsPrompt = `你是课程规划专家。请为"${chapter.name}"这一章生成学习小节列表。
  297. 要求:
  298. 1. 生成4-6个小节
  299. 2. 每个小节应该讲解一个具体的知识点
  300. 3. 内容要循序渐进,由浅入深
  301. ${instruction ? `用户额外要求:${instruction}` : ''}
  302. 请以JSON数组格式返回,只返回纯JSON数组`;
  303. const sectionsResponse = await callLLM(sectionsPrompt);
  304. const sectionNames = parseJsonList(sectionsResponse);
  305. // 删除旧的小节
  306. await models_1.prisma.contentBlock.deleteMany({ where: { section: { chapterId: id } } });
  307. await models_1.prisma.section.deleteMany({ where: { chapterId: id } });
  308. // 创建新的小节
  309. await Promise.all(sectionNames.map((name, index) => models_1.prisma.section.create({
  310. data: {
  311. chapterId: id,
  312. name,
  313. orderIndex: index,
  314. status: 'pending',
  315. confirmationStatus: 'pending',
  316. aiResponse: sectionsResponse,
  317. },
  318. })));
  319. await models_1.prisma.chapter.update({ where: { id }, data: { status: 'completed', confirmationStatus: 'confirmed' } });
  320. return { success: true, newSections: sectionNames };
  321. }
  322. else if (type === 'section') {
  323. const section = await models_1.prisma.section.findUnique({
  324. where: { id },
  325. include: { chapter: { include: { subject: true } } },
  326. });
  327. if (!section)
  328. throw new Error('小节不存在');
  329. const task = await models_1.prisma.learningPath.findUnique({ where: { id: taskId } });
  330. await models_1.prisma.section.update({ where: { id }, data: { status: 'generating', confirmationStatus: 'regenerating' } });
  331. const contentPrompt = `你是专业的教学老师。请详细讲解以下知识点:
  332. 知识点:${section.name}
  333. 所属章节:${section.chapter.name}
  334. 所属学科:${section.chapter.subject.name}
  335. 学习主题:${task?.topic}
  336. ${instruction ? `用户额外要求:${instruction}` : ''}
  337. 要求:
  338. 1. 用通俗易懂的语言讲解这个知识点
  339. 2. 包含"是什么"、"为什么"、"怎么用"三个部分
  340. 3. 可以给出具体的例子、代码、公式等
  341. 4. 内容要详实、深入,不少于500字
  342. 5. 直接返回正文内容,不需要标题`;
  343. const contentResponse = await callLLM(contentPrompt);
  344. // 删除旧内容
  345. await models_1.prisma.contentBlock.deleteMany({ where: { sectionId: id } });
  346. // 创建新内容
  347. await models_1.prisma.contentBlock.create({
  348. data: {
  349. sectionId: id,
  350. content: contentResponse,
  351. orderIndex: 0,
  352. wordCount: contentResponse.length,
  353. },
  354. });
  355. await models_1.prisma.section.update({ where: { id }, data: { status: 'completed', confirmationStatus: 'confirmed' } });
  356. return { success: true };
  357. }
  358. return { success: false };
  359. }
  360. /**
  361. * 获取多Agent模式的状态
  362. */
  363. async function getAgentStatus(taskId) {
  364. const task = await models_1.prisma.learningPath.findUnique({ where: { id: taskId } });
  365. if (!task)
  366. return null;
  367. const agentStatus = task.agentStatus ? JSON.parse(task.agentStatus) : null;
  368. return {
  369. mode: task.generationMode,
  370. status: task.status,
  371. progress: task.progress,
  372. agents: agentStatus,
  373. };
  374. }
  375. // ============ 渐进模式函数 ============
  376. /**
  377. * 渐进模式 Step 1: 生成学科列表
  378. */
  379. async function generateSubjectsStep(taskId) {
  380. const task = await models_1.prisma.learningPath.findUnique({ where: { id: taskId } });
  381. if (!task)
  382. throw new Error('任务不存在');
  383. console.log(`📚 渐进模式 - 生成学科列表: ${task.topic}`);
  384. await models_1.prisma.learningPath.update({
  385. where: { id: taskId },
  386. data: { status: 'generating', currentStep: 'subjects' },
  387. });
  388. const subjectsPrompt = `用户想要学习主题:"${task.topic}"
  389. 请作为一位专业的课程规划专家,为用户规划完整的学习路径。
  390. 要求:
  391. 1. 生成该主题下的主要学科/专业课程列表(6-10门核心学科)
  392. 2. 每门学科应该是构建该领域知识体系所必需的
  393. 3. 按照合理的学习顺序排列(从基础到进阶)
  394. 请以JSON数组格式返回学科列表,不要加标题不要加说明,只返回纯JSON数组,例如:
  395. ["计算机原理", "程序设计基础", "数据结构与算法", "操作系统", "计算机网络", "数据库原理", "软件工程"]`;
  396. const subjectsResponse = await callLLM(subjectsPrompt);
  397. const subjectNames = parseJsonList(subjectsResponse);
  398. console.log('📚 学科列表:', subjectNames);
  399. // 创建学科记录(带pending确认状态)
  400. await Promise.all(subjectNames.map((name, index) => models_1.prisma.subject.create({
  401. data: {
  402. learningPathId: taskId,
  403. name,
  404. orderIndex: index,
  405. status: 'completed',
  406. confirmationStatus: 'pending',
  407. aiResponse: subjectsResponse,
  408. },
  409. })));
  410. await models_1.prisma.learningPath.update({
  411. where: { id: taskId },
  412. data: { progress: 10 },
  413. });
  414. console.log('📚 学科列表生成完成,等待用户确认');
  415. }
  416. /**
  417. * 渐进模式 Step 2: 生成章节 (完全并行)
  418. */
  419. async function generateChaptersStep(taskId, subjectIds) {
  420. const task = await models_1.prisma.learningPath.findUnique({ where: { id: taskId } });
  421. if (!task)
  422. throw new Error('任务不存在');
  423. console.log(`📖 渐进模式 - 并行生成章节 for subjects: ${subjectIds}`);
  424. const subjects = await models_1.prisma.subject.findMany({
  425. where: { id: { in: subjectIds } },
  426. orderBy: { orderIndex: 'asc' },
  427. });
  428. // 并行生成所有学科的章节
  429. await Promise.all(subjects.map(async (subject, i) => {
  430. console.log(`📖 生成学科章节: ${subject.name}`);
  431. const chaptersPrompt = `你是课程规划专家。请为"${subject.name}"这门学科生成学习章节列表。
  432. 要求:
  433. 1. 生成6-10个核心章节
  434. 2. 章节应该覆盖该学科的主要知识领域
  435. 3. 按照合理的教学顺序排列
  436. 请以JSON数组格式返回,只返回纯JSON数组`;
  437. try {
  438. const chaptersResponse = await callLLM(chaptersPrompt);
  439. const chapterNames = parseJsonList(chaptersResponse);
  440. console.log(`📖 ${subject.name} - 章节:`, chapterNames);
  441. // 创建章节记录
  442. await Promise.all(chapterNames.map((name, index) => models_1.prisma.chapter.create({
  443. data: {
  444. subjectId: subject.id,
  445. name,
  446. orderIndex: index,
  447. status: 'completed',
  448. confirmationStatus: 'pending',
  449. aiResponse: chaptersResponse,
  450. },
  451. })));
  452. await models_1.prisma.subject.update({
  453. where: { id: subject.id },
  454. data: { status: 'completed' },
  455. });
  456. // 更新进度
  457. await models_1.prisma.learningPath.update({
  458. where: { id: taskId },
  459. data: { progress: 10 + Math.floor(((i + 1) / subjects.length) * 20) },
  460. });
  461. }
  462. catch (error) {
  463. console.error(`❌ 生成章节失败 ${subject.name}:`, error);
  464. }
  465. }));
  466. console.log('📖 所有章节生成完成,等待用户确认');
  467. }
  468. /**
  469. * 渐进模式 Step 3: 生成小节 (完全并行)
  470. */
  471. async function generateSectionsStep(taskId, chapterIds) {
  472. const task = await models_1.prisma.learningPath.findUnique({ where: { id: taskId } });
  473. if (!task)
  474. throw new Error('任务不存在');
  475. console.log(`📑 渐进模式 - 并行生成小节 for chapters: ${chapterIds}`);
  476. const chapters = await models_1.prisma.chapter.findMany({
  477. where: { id: { in: chapterIds } },
  478. orderBy: { orderIndex: 'asc' },
  479. include: { subject: true },
  480. });
  481. // 并行生成所有章节的小节
  482. await Promise.all(chapters.map(async (chapter, j) => {
  483. console.log(`📑 生成章节小节: ${chapter.name}`);
  484. const sectionsPrompt = `你是课程规划专家。请为"${chapter.name}"这一章生成学习小节列表。
  485. 要求:
  486. 1. 生成4-6个小节
  487. 2. 每个小节应该讲解一个具体的知识点
  488. 3. 内容要循序渐进,由浅入深
  489. 请以JSON数组格式返回,只返回纯JSON数组`;
  490. try {
  491. const sectionsResponse = await callLLM(sectionsPrompt);
  492. const sectionNames = parseJsonList(sectionsResponse);
  493. console.log(`📑 ${chapter.name} - 小节:`, sectionNames);
  494. // 创建小节记录
  495. await Promise.all(sectionNames.map((name, index) => models_1.prisma.section.create({
  496. data: {
  497. chapterId: chapter.id,
  498. name,
  499. orderIndex: index,
  500. status: 'completed',
  501. confirmationStatus: 'pending',
  502. aiResponse: sectionsResponse,
  503. },
  504. })));
  505. await models_1.prisma.chapter.update({
  506. where: { id: chapter.id },
  507. data: { status: 'completed' },
  508. });
  509. // 更新进度
  510. await models_1.prisma.learningPath.update({
  511. where: { id: taskId },
  512. data: { progress: 30 + Math.floor(((j + 1) / chapters.length) * 20) },
  513. });
  514. }
  515. catch (error) {
  516. console.error(`❌ 生成小节失败 ${chapter.name}:`, error);
  517. }
  518. }));
  519. console.log('📑 所有小节生成完成,等待用户确认');
  520. }
  521. /**
  522. * 渐进模式 Step 4: 生成详细内容 (完全并行)
  523. */
  524. async function generateContentStep(taskId, sectionIds) {
  525. const task = await models_1.prisma.learningPath.findUnique({ where: { id: taskId } });
  526. if (!task)
  527. throw new Error('任务不存在');
  528. console.log(`📝 渐进模式 - 并行生成详细内容 for sections: ${sectionIds}`);
  529. const sections = await models_1.prisma.section.findMany({
  530. where: { id: { in: sectionIds } },
  531. orderBy: { orderIndex: 'asc' },
  532. include: { chapter: { include: { subject: true } } },
  533. });
  534. const totalSections = sections.length;
  535. // 并行生成所有小节的详细内容
  536. await Promise.all(sections.map(async (section, k) => {
  537. console.log(`📝 生成详细内容: ${section.name}`);
  538. const contentPrompt = `你是专业的教学老师。请详细讲解以下知识点:
  539. 知识点:${section.name}
  540. 所属章节:${section.chapter.name}
  541. 所属学科:${section.chapter.subject.name}
  542. 学习主题:${task.topic}
  543. 要求:
  544. 1. 用通俗易懂的语言讲解这个知识点
  545. 2. 包含"是什么"、"为什么"、"怎么用"三个部分
  546. 3. 可以给出具体的例子、代码、公式等
  547. 4. 内容要详实、深入,不少于500字
  548. 5. 直接返回正文内容,不需要标题`;
  549. try {
  550. const contentResponse = await callLLM(contentPrompt);
  551. // 创建内容块
  552. await models_1.prisma.contentBlock.create({
  553. data: {
  554. sectionId: section.id,
  555. content: contentResponse,
  556. orderIndex: 0,
  557. wordCount: contentResponse.length,
  558. },
  559. });
  560. // 更新小节状态
  561. await models_1.prisma.section.update({
  562. where: { id: section.id },
  563. data: { status: 'completed', confirmationStatus: 'completed' },
  564. });
  565. // 更新进度 (动态计算已完成的小节数)
  566. const completedSections = k + 1;
  567. await models_1.prisma.learningPath.update({
  568. where: { id: taskId },
  569. data: { progress: 50 + Math.floor((completedSections / totalSections) * 50) },
  570. });
  571. }
  572. catch (error) {
  573. console.error(`❌ 生成内容失败 ${section.name}:`, error);
  574. }
  575. }));
  576. // 统计并完成
  577. const totalBlocks = await models_1.prisma.contentBlock.count({
  578. where: {
  579. section: {
  580. chapter: {
  581. subject: { learningPathId: taskId }
  582. }
  583. }
  584. }
  585. });
  586. await models_1.prisma.learningPath.update({
  587. where: { id: taskId },
  588. data: {
  589. status: 'completed',
  590. progress: 100,
  591. currentStep: 'completed',
  592. totalCount: totalBlocks,
  593. completedCount: totalBlocks,
  594. },
  595. });
  596. console.log(`✅ 渐进模式生成完成: ${task.topic}`);
  597. }
  598. // ============ 多Agent模式函数 ============
  599. /**
  600. * 多Agent模式:全自动并行生成
  601. */
  602. async function generateMultiAgent(taskId) {
  603. const task = await models_1.prisma.learningPath.findUnique({ where: { id: taskId } });
  604. if (!task)
  605. throw new Error('任务不存在');
  606. console.log(`🚀 多Agent模式 - 开始生成学习路径: ${task.topic}`);
  607. await models_1.prisma.learningPath.update({
  608. where: { id: taskId },
  609. data: {
  610. status: 'generating',
  611. agentStatus: JSON.stringify([
  612. { name: '规划Agent', status: 'running', progress: 0 },
  613. ]),
  614. },
  615. });
  616. try {
  617. // Step 1: 生成学科列表(规划Agent)
  618. const subjectsPrompt = `用户想要学习主题:"${task.topic}"
  619. 请作为一位专业的课程规划专家,为用户规划完整的学习路径。
  620. 要求:
  621. 1. 生成该主题下的主要学科/专业课程列表(6-10门核心学科)
  622. 2. 每门学科应该是构建该领域知识体系所必需的
  623. 3. 按照合理的学习顺序排列(从基础到进阶)
  624. 请以JSON数组格式返回学科列表,不要加标题不要加说明,只返回纯JSON数组,例如:
  625. ["计算机原理", "程序设计基础", "数据结构与算法", "操作系统", "计算机网络", "数据库原理", "软件工程"]`;
  626. console.log('📚 规划Agent - 生成学科列表...');
  627. const subjectsResponse = await callLLM(subjectsPrompt);
  628. const subjectNames = parseJsonList(subjectsResponse);
  629. console.log('📚 学科列表:', subjectNames);
  630. // 创建学科记录
  631. const subjects = await Promise.all(subjectNames.map((name, index) => models_1.prisma.subject.create({
  632. data: {
  633. learningPathId: taskId,
  634. name,
  635. orderIndex: index,
  636. status: 'pending',
  637. aiResponse: subjectsResponse,
  638. },
  639. })));
  640. await models_1.prisma.learningPath.update({
  641. where: { id: taskId },
  642. data: {
  643. agentStatus: JSON.stringify([
  644. { name: '规划Agent', status: 'completed', progress: 100 },
  645. { name: '学科Agent群', status: 'running', progress: 0 },
  646. ]),
  647. progress: 10,
  648. },
  649. });
  650. // Step 2: 并行为每个学科生成章节(学科Agent群)
  651. await Promise.all(subjects.map(async (subject, i) => {
  652. console.log(`📖 学科Agent[${i}] - 生成章节: ${subject.name}`);
  653. const chaptersPrompt = `你是课程规划专家。请为"${subject.name}"这门学科生成学习章节列表。
  654. 要求:
  655. 1. 生成6-10个核心章节
  656. 2. 章节应该覆盖该学科的主要知识领域
  657. 3. 按照合理的教学顺序排列
  658. 请以JSON数组格式返回,只返回纯JSON数组`;
  659. const chaptersResponse = await callLLM(chaptersPrompt);
  660. const chapterNames = parseJsonList(chaptersResponse);
  661. const chapters = await Promise.all(chapterNames.map((name, index) => models_1.prisma.chapter.create({
  662. data: {
  663. subjectId: subject.id,
  664. name,
  665. orderIndex: index,
  666. status: 'pending',
  667. aiResponse: chaptersResponse,
  668. },
  669. })));
  670. await models_1.prisma.subject.update({
  671. where: { id: subject.id },
  672. data: { status: 'generating' },
  673. });
  674. // Step 3: 并行为每个章节生成小节
  675. await Promise.all(chapters.map(async (chapter) => {
  676. const sectionsPrompt = `你是课程规划专家。请为"${chapter.name}"这一章生成学习小节列表。
  677. 要求:
  678. 1. 生成4-6个小节
  679. 2. 每个小节应该讲解一个具体的知识点
  680. 3. 内容要循序渐进,由浅入深
  681. 请以JSON数组格式返回,只返回纯JSON数组`;
  682. const sectionsResponse = await callLLM(sectionsPrompt);
  683. const sectionNames = parseJsonList(sectionsResponse);
  684. const sections = await Promise.all(sectionNames.map((name, index) => models_1.prisma.section.create({
  685. data: {
  686. chapterId: chapter.id,
  687. name,
  688. orderIndex: index,
  689. status: 'pending',
  690. aiResponse: sectionsResponse,
  691. },
  692. })));
  693. await models_1.prisma.chapter.update({
  694. where: { id: chapter.id },
  695. data: { status: 'completed' },
  696. });
  697. // Step 4: 并行为每个小节生成详细内容
  698. await Promise.all(sections.map(async (section) => {
  699. const contentPrompt = `你是专业的教学老师。请详细讲解以下知识点:
  700. 知识点:${section.name}
  701. 所属章节:${chapter.name}
  702. 所属学科:${subject.name}
  703. 学习主题:${task.topic}
  704. 要求:
  705. 1. 用通俗易懂的语言讲解这个知识点
  706. 2. 包含"是什么"、"为什么"、"怎么用"三个部分
  707. 3. 可以给出具体的例子、代码、公式等
  708. 4. 内容要详实、深入,不少于500字
  709. 5. 直接返回正文内容,不需要标题`;
  710. try {
  711. const contentResponse = await callLLM(contentPrompt);
  712. await models_1.prisma.contentBlock.create({
  713. data: {
  714. sectionId: section.id,
  715. content: contentResponse,
  716. orderIndex: 0,
  717. wordCount: contentResponse.length,
  718. },
  719. });
  720. await models_1.prisma.section.update({
  721. where: { id: section.id },
  722. data: { status: 'completed' },
  723. });
  724. }
  725. catch (error) {
  726. console.error(`❌ 生成内容失败 ${section.name}:`, error);
  727. }
  728. }));
  729. }));
  730. await models_1.prisma.subject.update({
  731. where: { id: subject.id },
  732. data: { status: 'completed' },
  733. });
  734. // 更新学科Agent进度
  735. const subjectProgress = Math.floor(((i + 1) / subjects.length) * 40) + 10;
  736. await models_1.prisma.learningPath.update({
  737. where: { id: taskId },
  738. data: {
  739. progress: subjectProgress,
  740. agentStatus: JSON.stringify([
  741. { name: '规划Agent', status: 'completed', progress: 100 },
  742. { name: '学科Agent群', status: 'running', progress: subjectProgress },
  743. ]),
  744. },
  745. });
  746. }));
  747. // 统计并完成
  748. const totalBlocks = await models_1.prisma.contentBlock.count({
  749. where: {
  750. section: {
  751. chapter: {
  752. subject: { learningPathId: taskId }
  753. }
  754. }
  755. }
  756. });
  757. await models_1.prisma.learningPath.update({
  758. where: { id: taskId },
  759. data: {
  760. status: 'completed',
  761. progress: 100,
  762. totalCount: totalBlocks,
  763. completedCount: totalBlocks,
  764. agentStatus: JSON.stringify([
  765. { name: '规划Agent', status: 'completed', progress: 100 },
  766. { name: '学科Agent群', status: 'completed', progress: 100 },
  767. { name: '整合Agent', status: 'completed', progress: 100 },
  768. ]),
  769. },
  770. });
  771. console.log(`✅ 多Agent模式生成完成: ${task.topic}`);
  772. }
  773. catch (error) {
  774. console.error('❌ 多Agent生成失败:', error);
  775. await models_1.prisma.learningPath.update({
  776. where: { id: taskId },
  777. data: { status: 'failed', errorMsg: error.message },
  778. });
  779. throw error;
  780. }
  781. }