"use strict"; /** * 学习路径生成服务 * 支持两种模式: * 1. progressive(渐进确认) - 用户在每步确认后继续 * 2. multi-agent(多Agent并行) - 全自动并行生成 */ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.createLearningPath = createLearningPath; exports.getLearningPathDetail = getLearningPathDetail; exports.getUserTasks = getUserTasks; exports.getPendingConfirmation = getPendingConfirmation; exports.confirmAndContinue = confirmAndContinue; exports.regenerateNode = regenerateNode; exports.getAgentStatus = getAgentStatus; const axios_1 = __importDefault(require("axios")); const config_1 = require("../../config"); const models_1 = require("../../models"); /** * 获取可用模型列表 */ function getAvailableModels() { return config_1.config.models.getModelsByType('text').filter((m) => m.enabled !== false); } function getRandomModel() { const models = getAvailableModels(); return models[Math.floor(Math.random() * models.length)].id; } /** * 调用 LLM API */ async function callLLM(prompt) { const modelId = getRandomModel(); const modelConfig = config_1.config.models.getModel(modelId); if (!modelConfig) { throw new Error(`模型 ${modelId} 不存在`); } const { apiKey, baseUrl } = modelConfig; if (!apiKey || !baseUrl) { throw new Error(`模型 ${modelId} 缺少 API 配置`); } try { const response = await axios_1.default.post(`${baseUrl}/chat/completions`, { model: modelId, messages: [{ role: 'user', content: prompt }], }, { headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json', }, timeout: 180000, }); const data = response.data; return data.choices?.[0]?.message?.content || ''; } catch (error) { console.error('❌ LLM 调用失败:', error.message); throw new Error(error.message || 'AI 生成失败'); } } /** * 解析 AI 返回的 JSON 列表 */ function parseJsonList(aiResponse) { const jsonMatch = aiResponse.match(/\[[\s\S]*\]/); if (jsonMatch) { try { const parsed = JSON.parse(jsonMatch[0]); if (Array.isArray(parsed)) { return parsed.map(item => { if (typeof item === 'string') return item; if (typeof item === 'object' && item !== null) { return item.title || item.name || item.chapter_title || item.section_title || item.section || item.chapter || JSON.stringify(item); } return String(item); }); } } catch (e) { } } const lines = aiResponse.split(/[,,\n]/).filter(line => line.trim().length > 0); return lines.map(line => line.replace(/^[\d一二三四五六七八九十]+[.、::]\s*/, '').trim()).filter(line => line.length > 0); } // ============ 通用函数 ============ /** * 创建学习路径任务 * @param userId 用户ID * @param topic 学习主题 * @param mode 生成模式: progressive | multi-agent */ async function createLearningPath(userId, topic, mode = 'progressive') { const task = await models_1.prisma.learningPath.create({ data: { userId, topic, status: 'pending', progress: 0, generationMode: mode, currentStep: 'pending', }, }); // 根据模式选择生成方式 if (mode === 'multi-agent') { // 多Agent模式:直接开始全量生成 generateMultiAgent(task.id).catch(error => { console.error('❌ 多Agent生成失败:', error); models_1.prisma.learningPath.update({ where: { id: task.id }, data: { status: 'failed', errorMsg: error.message }, }).catch(console.error); }); } else { // 渐进模式:先生成学科列表等待用户确认 generateSubjectsStep(task.id).catch(error => { console.error('❌ 学科生成失败:', error); models_1.prisma.learningPath.update({ where: { id: task.id }, data: { status: 'failed', errorMsg: error.message }, }).catch(console.error); }); } return task; } /** * 获取学习路径详情 */ async function getLearningPathDetail(id) { const task = await models_1.prisma.learningPath.findUnique({ where: { id }, include: { subjects: { orderBy: { orderIndex: 'asc' }, include: { chapters: { orderBy: { orderIndex: 'asc' }, include: { sections: { orderBy: { orderIndex: 'asc' }, include: { contentBlocks: { orderBy: { orderIndex: 'asc' }, }, }, }, }, }, }, }, }, }); return task; } /** * 获取用户的任务列表 */ async function getUserTasks(userId) { return models_1.prisma.learningPath.findMany({ where: { userId }, orderBy: { createdAt: 'desc' }, take: 20, }); } /** * 获取渐进模式的待确认状态 */ async function getPendingConfirmation(taskId) { const task = await models_1.prisma.learningPath.findUnique({ where: { id: taskId }, include: { subjects: { orderBy: { orderIndex: 'asc' }, include: { chapters: { orderBy: { orderIndex: 'asc' }, include: { sections: { orderBy: { orderIndex: 'asc' }, }, }, }, }, }, }, }); if (!task) return null; return { id: task.id, topic: task.topic, status: task.status, progress: task.progress, currentStep: task.currentStep, generationMode: task.generationMode, // 待确认的学科 pendingSubjects: task.subjects .filter(s => s.confirmationStatus === 'pending') .map(s => ({ id: s.id, name: s.name, status: s.status })), // 待确认的章节 pendingChapters: task.subjects.reduce((acc, subject) => { acc[subject.id] = subject.chapters .filter(c => c.confirmationStatus === 'pending') .map(c => ({ id: c.id, name: c.name, status: c.status })); return acc; }, {}), // 待确认的小节 pendingSections: task.subjects.reduce((acc, subject) => { subject.chapters.forEach(chapter => { acc[chapter.id] = chapter.sections .filter(s => s.confirmationStatus === 'pending') .map(s => ({ id: s.id, name: s.name, status: s.status })); }); return acc; }, {}), }; } /** * 确认并继续生成 * @param taskId 任务ID * @param step 当前步骤: subjects | chapters | sections * @param confirmedIds 确认的ID列表 * @param modifiedItems 修改的项 [{id, name}] */ async function confirmAndContinue(taskId, step, confirmedIds, modifiedItems = []) { const task = await models_1.prisma.learningPath.findUnique({ where: { id: taskId } }); if (!task) throw new Error('任务不存在'); // 处理修改项 for (const item of modifiedItems) { if (step === 'subjects') { await models_1.prisma.subject.update({ where: { id: item.id }, data: { name: item.name } }); } else if (step === 'chapters') { await models_1.prisma.chapter.update({ where: { id: item.id }, data: { name: item.name } }); } } // 标记确认的项 if (step === 'subjects') { await models_1.prisma.subject.updateMany({ where: { id: { in: confirmedIds }, learningPathId: taskId }, data: { confirmationStatus: 'confirmed' }, }); // 更新任务步骤 await models_1.prisma.learningPath.update({ where: { id: taskId }, data: { currentStep: 'chapters' }, }); // 开始生成章节 generateChaptersStep(taskId, confirmedIds).catch(console.error); return { currentStep: 'chapters' }; } else if (step === 'chapters') { await models_1.prisma.chapter.updateMany({ where: { id: { in: confirmedIds } }, data: { confirmationStatus: 'confirmed' }, }); await models_1.prisma.learningPath.update({ where: { id: taskId }, data: { currentStep: 'sections' }, }); // 开始生成小节 generateSectionsStep(taskId, confirmedIds).catch(console.error); return { currentStep: 'sections' }; } else if (step === 'sections') { await models_1.prisma.section.updateMany({ where: { id: { in: confirmedIds } }, data: { confirmationStatus: 'confirmed' }, }); await models_1.prisma.learningPath.update({ where: { id: taskId }, data: { currentStep: 'content' }, }); // 开始生成详细内容 generateContentStep(taskId, confirmedIds).catch(console.error); return { currentStep: 'content' }; } return { currentStep: task.currentStep }; } /** * 重新生成某个节点 */ async function regenerateNode(taskId, type, id, instruction) { if (type === 'chapter') { const chapter = await models_1.prisma.chapter.findUnique({ where: { id } }); if (!chapter) throw new Error('章节不存在'); await models_1.prisma.chapter.update({ where: { id }, data: { status: 'generating', confirmationStatus: 'regenerating' } }); // 重新生成小节 const subject = await models_1.prisma.subject.findUnique({ where: { id: chapter.subjectId } }); const task = await models_1.prisma.learningPath.findUnique({ where: { id: taskId } }); const sectionsPrompt = `你是课程规划专家。请为"${chapter.name}"这一章生成学习小节列表。 要求: 1. 生成4-6个小节 2. 每个小节应该讲解一个具体的知识点 3. 内容要循序渐进,由浅入深 ${instruction ? `用户额外要求:${instruction}` : ''} 请以JSON数组格式返回,只返回纯JSON数组`; const sectionsResponse = await callLLM(sectionsPrompt); const sectionNames = parseJsonList(sectionsResponse); // 删除旧的小节 await models_1.prisma.contentBlock.deleteMany({ where: { section: { chapterId: id } } }); await models_1.prisma.section.deleteMany({ where: { chapterId: id } }); // 创建新的小节 await Promise.all(sectionNames.map((name, index) => models_1.prisma.section.create({ data: { chapterId: id, name, orderIndex: index, status: 'pending', confirmationStatus: 'pending', aiResponse: sectionsResponse, }, }))); await models_1.prisma.chapter.update({ where: { id }, data: { status: 'completed', confirmationStatus: 'confirmed' } }); return { success: true, newSections: sectionNames }; } else if (type === 'section') { const section = await models_1.prisma.section.findUnique({ where: { id }, include: { chapter: { include: { subject: true } } }, }); if (!section) throw new Error('小节不存在'); const task = await models_1.prisma.learningPath.findUnique({ where: { id: taskId } }); await models_1.prisma.section.update({ where: { id }, data: { status: 'generating', confirmationStatus: 'regenerating' } }); const contentPrompt = `你是专业的教学老师。请详细讲解以下知识点: 知识点:${section.name} 所属章节:${section.chapter.name} 所属学科:${section.chapter.subject.name} 学习主题:${task?.topic} ${instruction ? `用户额外要求:${instruction}` : ''} 要求: 1. 用通俗易懂的语言讲解这个知识点 2. 包含"是什么"、"为什么"、"怎么用"三个部分 3. 可以给出具体的例子、代码、公式等 4. 内容要详实、深入,不少于500字 5. 直接返回正文内容,不需要标题`; const contentResponse = await callLLM(contentPrompt); // 删除旧内容 await models_1.prisma.contentBlock.deleteMany({ where: { sectionId: id } }); // 创建新内容 await models_1.prisma.contentBlock.create({ data: { sectionId: id, content: contentResponse, orderIndex: 0, wordCount: contentResponse.length, }, }); await models_1.prisma.section.update({ where: { id }, data: { status: 'completed', confirmationStatus: 'confirmed' } }); return { success: true }; } return { success: false }; } /** * 获取多Agent模式的状态 */ async function getAgentStatus(taskId) { const task = await models_1.prisma.learningPath.findUnique({ where: { id: taskId } }); if (!task) return null; const agentStatus = task.agentStatus ? JSON.parse(task.agentStatus) : null; return { mode: task.generationMode, status: task.status, progress: task.progress, agents: agentStatus, }; } // ============ 渐进模式函数 ============ /** * 渐进模式 Step 1: 生成学科列表 */ async function generateSubjectsStep(taskId) { const task = await models_1.prisma.learningPath.findUnique({ where: { id: taskId } }); if (!task) throw new Error('任务不存在'); console.log(`📚 渐进模式 - 生成学科列表: ${task.topic}`); await models_1.prisma.learningPath.update({ where: { id: taskId }, data: { status: 'generating', currentStep: 'subjects' }, }); const subjectsPrompt = `用户想要学习主题:"${task.topic}" 请作为一位专业的课程规划专家,为用户规划完整的学习路径。 要求: 1. 生成该主题下的主要学科/专业课程列表(6-10门核心学科) 2. 每门学科应该是构建该领域知识体系所必需的 3. 按照合理的学习顺序排列(从基础到进阶) 请以JSON数组格式返回学科列表,不要加标题不要加说明,只返回纯JSON数组,例如: ["计算机原理", "程序设计基础", "数据结构与算法", "操作系统", "计算机网络", "数据库原理", "软件工程"]`; const subjectsResponse = await callLLM(subjectsPrompt); const subjectNames = parseJsonList(subjectsResponse); console.log('📚 学科列表:', subjectNames); // 创建学科记录(带pending确认状态) await Promise.all(subjectNames.map((name, index) => models_1.prisma.subject.create({ data: { learningPathId: taskId, name, orderIndex: index, status: 'completed', confirmationStatus: 'pending', aiResponse: subjectsResponse, }, }))); await models_1.prisma.learningPath.update({ where: { id: taskId }, data: { progress: 10 }, }); console.log('📚 学科列表生成完成,等待用户确认'); } /** * 渐进模式 Step 2: 生成章节 (完全并行) */ async function generateChaptersStep(taskId, subjectIds) { const task = await models_1.prisma.learningPath.findUnique({ where: { id: taskId } }); if (!task) throw new Error('任务不存在'); console.log(`📖 渐进模式 - 并行生成章节 for subjects: ${subjectIds}`); const subjects = await models_1.prisma.subject.findMany({ where: { id: { in: subjectIds } }, orderBy: { orderIndex: 'asc' }, }); // 并行生成所有学科的章节 await Promise.all(subjects.map(async (subject, i) => { console.log(`📖 生成学科章节: ${subject.name}`); const chaptersPrompt = `你是课程规划专家。请为"${subject.name}"这门学科生成学习章节列表。 要求: 1. 生成6-10个核心章节 2. 章节应该覆盖该学科的主要知识领域 3. 按照合理的教学顺序排列 请以JSON数组格式返回,只返回纯JSON数组`; try { const chaptersResponse = await callLLM(chaptersPrompt); const chapterNames = parseJsonList(chaptersResponse); console.log(`📖 ${subject.name} - 章节:`, chapterNames); // 创建章节记录 await Promise.all(chapterNames.map((name, index) => models_1.prisma.chapter.create({ data: { subjectId: subject.id, name, orderIndex: index, status: 'completed', confirmationStatus: 'pending', aiResponse: chaptersResponse, }, }))); await models_1.prisma.subject.update({ where: { id: subject.id }, data: { status: 'completed' }, }); // 更新进度 await models_1.prisma.learningPath.update({ where: { id: taskId }, data: { progress: 10 + Math.floor(((i + 1) / subjects.length) * 20) }, }); } catch (error) { console.error(`❌ 生成章节失败 ${subject.name}:`, error); } })); console.log('📖 所有章节生成完成,等待用户确认'); } /** * 渐进模式 Step 3: 生成小节 (完全并行) */ async function generateSectionsStep(taskId, chapterIds) { const task = await models_1.prisma.learningPath.findUnique({ where: { id: taskId } }); if (!task) throw new Error('任务不存在'); console.log(`📑 渐进模式 - 并行生成小节 for chapters: ${chapterIds}`); const chapters = await models_1.prisma.chapter.findMany({ where: { id: { in: chapterIds } }, orderBy: { orderIndex: 'asc' }, include: { subject: true }, }); // 并行生成所有章节的小节 await Promise.all(chapters.map(async (chapter, j) => { console.log(`📑 生成章节小节: ${chapter.name}`); const sectionsPrompt = `你是课程规划专家。请为"${chapter.name}"这一章生成学习小节列表。 要求: 1. 生成4-6个小节 2. 每个小节应该讲解一个具体的知识点 3. 内容要循序渐进,由浅入深 请以JSON数组格式返回,只返回纯JSON数组`; try { const sectionsResponse = await callLLM(sectionsPrompt); const sectionNames = parseJsonList(sectionsResponse); console.log(`📑 ${chapter.name} - 小节:`, sectionNames); // 创建小节记录 await Promise.all(sectionNames.map((name, index) => models_1.prisma.section.create({ data: { chapterId: chapter.id, name, orderIndex: index, status: 'completed', confirmationStatus: 'pending', aiResponse: sectionsResponse, }, }))); await models_1.prisma.chapter.update({ where: { id: chapter.id }, data: { status: 'completed' }, }); // 更新进度 await models_1.prisma.learningPath.update({ where: { id: taskId }, data: { progress: 30 + Math.floor(((j + 1) / chapters.length) * 20) }, }); } catch (error) { console.error(`❌ 生成小节失败 ${chapter.name}:`, error); } })); console.log('📑 所有小节生成完成,等待用户确认'); } /** * 渐进模式 Step 4: 生成详细内容 (完全并行) */ async function generateContentStep(taskId, sectionIds) { const task = await models_1.prisma.learningPath.findUnique({ where: { id: taskId } }); if (!task) throw new Error('任务不存在'); console.log(`📝 渐进模式 - 并行生成详细内容 for sections: ${sectionIds}`); const sections = await models_1.prisma.section.findMany({ where: { id: { in: sectionIds } }, orderBy: { orderIndex: 'asc' }, include: { chapter: { include: { subject: true } } }, }); const totalSections = sections.length; // 并行生成所有小节的详细内容 await Promise.all(sections.map(async (section, k) => { console.log(`📝 生成详细内容: ${section.name}`); const contentPrompt = `你是专业的教学老师。请详细讲解以下知识点: 知识点:${section.name} 所属章节:${section.chapter.name} 所属学科:${section.chapter.subject.name} 学习主题:${task.topic} 要求: 1. 用通俗易懂的语言讲解这个知识点 2. 包含"是什么"、"为什么"、"怎么用"三个部分 3. 可以给出具体的例子、代码、公式等 4. 内容要详实、深入,不少于500字 5. 直接返回正文内容,不需要标题`; try { const contentResponse = await callLLM(contentPrompt); // 创建内容块 await models_1.prisma.contentBlock.create({ data: { sectionId: section.id, content: contentResponse, orderIndex: 0, wordCount: contentResponse.length, }, }); // 更新小节状态 await models_1.prisma.section.update({ where: { id: section.id }, data: { status: 'completed', confirmationStatus: 'completed' }, }); // 更新进度 (动态计算已完成的小节数) const completedSections = k + 1; await models_1.prisma.learningPath.update({ where: { id: taskId }, data: { progress: 50 + Math.floor((completedSections / totalSections) * 50) }, }); } catch (error) { console.error(`❌ 生成内容失败 ${section.name}:`, error); } })); // 统计并完成 const totalBlocks = await models_1.prisma.contentBlock.count({ where: { section: { chapter: { subject: { learningPathId: taskId } } } } }); await models_1.prisma.learningPath.update({ where: { id: taskId }, data: { status: 'completed', progress: 100, currentStep: 'completed', totalCount: totalBlocks, completedCount: totalBlocks, }, }); console.log(`✅ 渐进模式生成完成: ${task.topic}`); } // ============ 多Agent模式函数 ============ /** * 多Agent模式:全自动并行生成 */ async function generateMultiAgent(taskId) { const task = await models_1.prisma.learningPath.findUnique({ where: { id: taskId } }); if (!task) throw new Error('任务不存在'); console.log(`🚀 多Agent模式 - 开始生成学习路径: ${task.topic}`); await models_1.prisma.learningPath.update({ where: { id: taskId }, data: { status: 'generating', agentStatus: JSON.stringify([ { name: '规划Agent', status: 'running', progress: 0 }, ]), }, }); try { // Step 1: 生成学科列表(规划Agent) const subjectsPrompt = `用户想要学习主题:"${task.topic}" 请作为一位专业的课程规划专家,为用户规划完整的学习路径。 要求: 1. 生成该主题下的主要学科/专业课程列表(6-10门核心学科) 2. 每门学科应该是构建该领域知识体系所必需的 3. 按照合理的学习顺序排列(从基础到进阶) 请以JSON数组格式返回学科列表,不要加标题不要加说明,只返回纯JSON数组,例如: ["计算机原理", "程序设计基础", "数据结构与算法", "操作系统", "计算机网络", "数据库原理", "软件工程"]`; console.log('📚 规划Agent - 生成学科列表...'); const subjectsResponse = await callLLM(subjectsPrompt); const subjectNames = parseJsonList(subjectsResponse); console.log('📚 学科列表:', subjectNames); // 创建学科记录 const subjects = await Promise.all(subjectNames.map((name, index) => models_1.prisma.subject.create({ data: { learningPathId: taskId, name, orderIndex: index, status: 'pending', aiResponse: subjectsResponse, }, }))); await models_1.prisma.learningPath.update({ where: { id: taskId }, data: { agentStatus: JSON.stringify([ { name: '规划Agent', status: 'completed', progress: 100 }, { name: '学科Agent群', status: 'running', progress: 0 }, ]), progress: 10, }, }); // Step 2: 并行为每个学科生成章节(学科Agent群) await Promise.all(subjects.map(async (subject, i) => { console.log(`📖 学科Agent[${i}] - 生成章节: ${subject.name}`); const chaptersPrompt = `你是课程规划专家。请为"${subject.name}"这门学科生成学习章节列表。 要求: 1. 生成6-10个核心章节 2. 章节应该覆盖该学科的主要知识领域 3. 按照合理的教学顺序排列 请以JSON数组格式返回,只返回纯JSON数组`; const chaptersResponse = await callLLM(chaptersPrompt); const chapterNames = parseJsonList(chaptersResponse); const chapters = await Promise.all(chapterNames.map((name, index) => models_1.prisma.chapter.create({ data: { subjectId: subject.id, name, orderIndex: index, status: 'pending', aiResponse: chaptersResponse, }, }))); await models_1.prisma.subject.update({ where: { id: subject.id }, data: { status: 'generating' }, }); // Step 3: 并行为每个章节生成小节 await Promise.all(chapters.map(async (chapter) => { const sectionsPrompt = `你是课程规划专家。请为"${chapter.name}"这一章生成学习小节列表。 要求: 1. 生成4-6个小节 2. 每个小节应该讲解一个具体的知识点 3. 内容要循序渐进,由浅入深 请以JSON数组格式返回,只返回纯JSON数组`; const sectionsResponse = await callLLM(sectionsPrompt); const sectionNames = parseJsonList(sectionsResponse); const sections = await Promise.all(sectionNames.map((name, index) => models_1.prisma.section.create({ data: { chapterId: chapter.id, name, orderIndex: index, status: 'pending', aiResponse: sectionsResponse, }, }))); await models_1.prisma.chapter.update({ where: { id: chapter.id }, data: { status: 'completed' }, }); // Step 4: 并行为每个小节生成详细内容 await Promise.all(sections.map(async (section) => { const contentPrompt = `你是专业的教学老师。请详细讲解以下知识点: 知识点:${section.name} 所属章节:${chapter.name} 所属学科:${subject.name} 学习主题:${task.topic} 要求: 1. 用通俗易懂的语言讲解这个知识点 2. 包含"是什么"、"为什么"、"怎么用"三个部分 3. 可以给出具体的例子、代码、公式等 4. 内容要详实、深入,不少于500字 5. 直接返回正文内容,不需要标题`; try { const contentResponse = await callLLM(contentPrompt); await models_1.prisma.contentBlock.create({ data: { sectionId: section.id, content: contentResponse, orderIndex: 0, wordCount: contentResponse.length, }, }); await models_1.prisma.section.update({ where: { id: section.id }, data: { status: 'completed' }, }); } catch (error) { console.error(`❌ 生成内容失败 ${section.name}:`, error); } })); })); await models_1.prisma.subject.update({ where: { id: subject.id }, data: { status: 'completed' }, }); // 更新学科Agent进度 const subjectProgress = Math.floor(((i + 1) / subjects.length) * 40) + 10; await models_1.prisma.learningPath.update({ where: { id: taskId }, data: { progress: subjectProgress, agentStatus: JSON.stringify([ { name: '规划Agent', status: 'completed', progress: 100 }, { name: '学科Agent群', status: 'running', progress: subjectProgress }, ]), }, }); })); // 统计并完成 const totalBlocks = await models_1.prisma.contentBlock.count({ where: { section: { chapter: { subject: { learningPathId: taskId } } } } }); await models_1.prisma.learningPath.update({ where: { id: taskId }, data: { status: 'completed', progress: 100, totalCount: totalBlocks, completedCount: totalBlocks, agentStatus: JSON.stringify([ { name: '规划Agent', status: 'completed', progress: 100 }, { name: '学科Agent群', status: 'completed', progress: 100 }, { name: '整合Agent', status: 'completed', progress: 100 }, ]), }, }); console.log(`✅ 多Agent模式生成完成: ${task.topic}`); } catch (error) { console.error('❌ 多Agent生成失败:', error); await models_1.prisma.learningPath.update({ where: { id: taskId }, data: { status: 'failed', errorMsg: error.message }, }); throw error; } }