|
@@ -0,0 +1,926 @@
|
|
|
|
|
+/**
|
|
|
|
|
+ * 学习路径生成服务
|
|
|
|
|
+ * 支持两种模式:
|
|
|
|
|
+ * 1. progressive(渐进确认) - 用户在每步确认后继续
|
|
|
|
|
+ * 2. multi-agent(多Agent并行) - 全自动并行生成
|
|
|
|
|
+ */
|
|
|
|
|
+
|
|
|
|
|
+import axios from 'axios';
|
|
|
|
|
+import { config } from '../../config';
|
|
|
|
|
+import { prisma } from '../../models';
|
|
|
|
|
+
|
|
|
|
|
+const AVAILABLE_MODELS = [
|
|
|
|
|
+ 'qwen-plus',
|
|
|
|
|
+ 'qwen-max',
|
|
|
|
|
+ 'qwen-turbo',
|
|
|
|
|
+ 'MiniMax-M2.5',
|
|
|
|
|
+ 'tongyi-xiaomi-analysis-pro',
|
|
|
|
|
+ 'tongyi-xiaomi-analysis-flash',
|
|
|
|
|
+ 'MiniMax-M2.1',
|
|
|
|
|
+];
|
|
|
|
|
+
|
|
|
|
|
+function getRandomModel(): string {
|
|
|
|
|
+ return AVAILABLE_MODELS[Math.floor(Math.random() * AVAILABLE_MODELS.length)];
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 调用 DashScope API
|
|
|
|
|
+ */
|
|
|
|
|
+async function callLLM(prompt: string): Promise<string> {
|
|
|
|
|
+ const apiKey = config.dashscope.apiKey;
|
|
|
|
|
+ if (!apiKey) {
|
|
|
|
|
+ throw new Error('未配置 AI API Key');
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ const response = await axios.post(
|
|
|
|
|
+ 'https://dashscope.aliyuncs.com/api/v1/services/aigc/text-generation/generation',
|
|
|
|
|
+ {
|
|
|
|
|
+ model: getRandomModel(),
|
|
|
|
|
+ input: { prompt },
|
|
|
|
|
+ parameters: { result_format: 'message' },
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ headers: {
|
|
|
|
|
+ 'Authorization': `Bearer ${apiKey}`,
|
|
|
|
|
+ 'Content-Type': 'application/json',
|
|
|
|
|
+ },
|
|
|
|
|
+ timeout: 180000,
|
|
|
|
|
+ }
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ const data = response.data;
|
|
|
|
|
+ if (data.code) {
|
|
|
|
|
+ throw new Error(data.message || `AI 调用失败: ${data.code}`);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return data.output?.choices?.[0]?.message?.content || '';
|
|
|
|
|
+ } catch (error: any) {
|
|
|
|
|
+ console.error('❌ LLM 调用失败:', error.message);
|
|
|
|
|
+ throw new Error(error.message || 'AI 生成失败');
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 解析 AI 返回的 JSON 列表
|
|
|
|
|
+ */
|
|
|
|
|
+function parseJsonList(aiResponse: string): string[] {
|
|
|
|
|
+ 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
|
|
|
|
|
+ */
|
|
|
|
|
+export async function createLearningPath(userId: number | null, topic: string, mode: 'progressive' | 'multi-agent' = 'progressive') {
|
|
|
|
|
+ const task = await 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);
|
|
|
|
|
+ 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);
|
|
|
|
|
+ prisma.learningPath.update({
|
|
|
|
|
+ where: { id: task.id },
|
|
|
|
|
+ data: { status: 'failed', errorMsg: error.message },
|
|
|
|
|
+ }).catch(console.error);
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return task;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 获取学习路径详情
|
|
|
|
|
+ */
|
|
|
|
|
+export async function getLearningPathDetail(id: number) {
|
|
|
|
|
+ const task = await 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;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 获取用户的任务列表
|
|
|
|
|
+ */
|
|
|
|
|
+export async function getUserTasks(userId: number) {
|
|
|
|
|
+ return prisma.learningPath.findMany({
|
|
|
|
|
+ where: { userId },
|
|
|
|
|
+ orderBy: { createdAt: 'desc' },
|
|
|
|
|
+ take: 20,
|
|
|
|
|
+ });
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 获取渐进模式的待确认状态
|
|
|
|
|
+ */
|
|
|
|
|
+export async function getPendingConfirmation(taskId: number) {
|
|
|
|
|
+ const task = await 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;
|
|
|
|
|
+ }, {} as Record<number, { id: number; name: string; status: string }[]>),
|
|
|
|
|
+ // 待确认的小节
|
|
|
|
|
+ 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;
|
|
|
|
|
+ }, {} as Record<number, { id: number; name: string; status: string }[]>),
|
|
|
|
|
+ };
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 确认并继续生成
|
|
|
|
|
+ * @param taskId 任务ID
|
|
|
|
|
+ * @param step 当前步骤: subjects | chapters | sections
|
|
|
|
|
+ * @param confirmedIds 确认的ID列表
|
|
|
|
|
+ * @param modifiedItems 修改的项 [{id, name}]
|
|
|
|
|
+ */
|
|
|
|
|
+export async function confirmAndContinue(taskId: number, step: string, confirmedIds: number[], modifiedItems: { id: number; name: string }[] = []) {
|
|
|
|
|
+ const task = await prisma.learningPath.findUnique({ where: { id: taskId } });
|
|
|
|
|
+ if (!task) throw new Error('任务不存在');
|
|
|
|
|
+
|
|
|
|
|
+ // 处理修改项
|
|
|
|
|
+ for (const item of modifiedItems) {
|
|
|
|
|
+ if (step === 'subjects') {
|
|
|
|
|
+ await prisma.subject.update({ where: { id: item.id }, data: { name: item.name } });
|
|
|
|
|
+ } else if (step === 'chapters') {
|
|
|
|
|
+ await prisma.chapter.update({ where: { id: item.id }, data: { name: item.name } });
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 标记确认的项
|
|
|
|
|
+ if (step === 'subjects') {
|
|
|
|
|
+ await prisma.subject.updateMany({
|
|
|
|
|
+ where: { id: { in: confirmedIds }, learningPathId: taskId },
|
|
|
|
|
+ data: { confirmationStatus: 'confirmed' },
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // 更新任务步骤
|
|
|
|
|
+ await prisma.learningPath.update({
|
|
|
|
|
+ where: { id: taskId },
|
|
|
|
|
+ data: { currentStep: 'chapters' },
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // 开始生成章节
|
|
|
|
|
+ generateChaptersStep(taskId, confirmedIds).catch(console.error);
|
|
|
|
|
+
|
|
|
|
|
+ return { currentStep: 'chapters' };
|
|
|
|
|
+
|
|
|
|
|
+ } else if (step === 'chapters') {
|
|
|
|
|
+ await prisma.chapter.updateMany({
|
|
|
|
|
+ where: { id: { in: confirmedIds } },
|
|
|
|
|
+ data: { confirmationStatus: 'confirmed' },
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ await prisma.learningPath.update({
|
|
|
|
|
+ where: { id: taskId },
|
|
|
|
|
+ data: { currentStep: 'sections' },
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // 开始生成小节
|
|
|
|
|
+ generateSectionsStep(taskId, confirmedIds).catch(console.error);
|
|
|
|
|
+
|
|
|
|
|
+ return { currentStep: 'sections' };
|
|
|
|
|
+
|
|
|
|
|
+ } else if (step === 'sections') {
|
|
|
|
|
+ await prisma.section.updateMany({
|
|
|
|
|
+ where: { id: { in: confirmedIds } },
|
|
|
|
|
+ data: { confirmationStatus: 'confirmed' },
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ await prisma.learningPath.update({
|
|
|
|
|
+ where: { id: taskId },
|
|
|
|
|
+ data: { currentStep: 'content' },
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // 开始生成详细内容
|
|
|
|
|
+ generateContentStep(taskId, confirmedIds).catch(console.error);
|
|
|
|
|
+
|
|
|
|
|
+ return { currentStep: 'content' };
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return { currentStep: task.currentStep };
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 重新生成某个节点
|
|
|
|
|
+ */
|
|
|
|
|
+export async function regenerateNode(taskId: number, type: 'chapter' | 'section', id: number, instruction?: string) {
|
|
|
|
|
+ if (type === 'chapter') {
|
|
|
|
|
+ const chapter = await prisma.chapter.findUnique({ where: { id } });
|
|
|
|
|
+ if (!chapter) throw new Error('章节不存在');
|
|
|
|
|
+
|
|
|
|
|
+ await prisma.chapter.update({ where: { id }, data: { status: 'generating', confirmationStatus: 'regenerating' } });
|
|
|
|
|
+
|
|
|
|
|
+ // 重新生成小节
|
|
|
|
|
+ const subject = await prisma.subject.findUnique({ where: { id: chapter.subjectId } });
|
|
|
|
|
+ const task = await 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 prisma.contentBlock.deleteMany({ where: { section: { chapterId: id } } });
|
|
|
|
|
+ await prisma.section.deleteMany({ where: { chapterId: id } });
|
|
|
|
|
+
|
|
|
|
|
+ // 创建新的小节
|
|
|
|
|
+ await Promise.all(
|
|
|
|
|
+ sectionNames.map((name, index) =>
|
|
|
|
|
+ prisma.section.create({
|
|
|
|
|
+ data: {
|
|
|
|
|
+ chapterId: id,
|
|
|
|
|
+ name,
|
|
|
|
|
+ orderIndex: index,
|
|
|
|
|
+ status: 'pending',
|
|
|
|
|
+ confirmationStatus: 'pending',
|
|
|
|
|
+ aiResponse: sectionsResponse,
|
|
|
|
|
+ },
|
|
|
|
|
+ })
|
|
|
|
|
+ )
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ await prisma.chapter.update({ where: { id }, data: { status: 'completed', confirmationStatus: 'confirmed' } });
|
|
|
|
|
+
|
|
|
|
|
+ return { success: true, newSections: sectionNames };
|
|
|
|
|
+
|
|
|
|
|
+ } else if (type === 'section') {
|
|
|
|
|
+ const section = await prisma.section.findUnique({
|
|
|
|
|
+ where: { id },
|
|
|
|
|
+ include: { chapter: { include: { subject: true } } },
|
|
|
|
|
+ });
|
|
|
|
|
+ if (!section) throw new Error('小节不存在');
|
|
|
|
|
+
|
|
|
|
|
+ const task = await prisma.learningPath.findUnique({ where: { id: taskId } });
|
|
|
|
|
+
|
|
|
|
|
+ await 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 prisma.contentBlock.deleteMany({ where: { sectionId: id } });
|
|
|
|
|
+
|
|
|
|
|
+ // 创建新内容
|
|
|
|
|
+ await prisma.contentBlock.create({
|
|
|
|
|
+ data: {
|
|
|
|
|
+ sectionId: id,
|
|
|
|
|
+ content: contentResponse,
|
|
|
|
|
+ orderIndex: 0,
|
|
|
|
|
+ wordCount: contentResponse.length,
|
|
|
|
|
+ },
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ await prisma.section.update({ where: { id }, data: { status: 'completed', confirmationStatus: 'confirmed' } });
|
|
|
|
|
+
|
|
|
|
|
+ return { success: true };
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return { success: false };
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 获取多Agent模式的状态
|
|
|
|
|
+ */
|
|
|
|
|
+export async function getAgentStatus(taskId: number) {
|
|
|
|
|
+ const task = await 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: number) {
|
|
|
|
|
+ const task = await prisma.learningPath.findUnique({ where: { id: taskId } });
|
|
|
|
|
+ if (!task) throw new Error('任务不存在');
|
|
|
|
|
+
|
|
|
|
|
+ console.log(`📚 渐进模式 - 生成学科列表: ${task.topic}`);
|
|
|
|
|
+
|
|
|
|
|
+ await 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) =>
|
|
|
|
|
+ prisma.subject.create({
|
|
|
|
|
+ data: {
|
|
|
|
|
+ learningPathId: taskId,
|
|
|
|
|
+ name,
|
|
|
|
|
+ orderIndex: index,
|
|
|
|
|
+ status: 'completed',
|
|
|
|
|
+ confirmationStatus: 'pending',
|
|
|
|
|
+ aiResponse: subjectsResponse,
|
|
|
|
|
+ },
|
|
|
|
|
+ })
|
|
|
|
|
+ )
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ await prisma.learningPath.update({
|
|
|
|
|
+ where: { id: taskId },
|
|
|
|
|
+ data: { progress: 10 },
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ console.log('📚 学科列表生成完成,等待用户确认');
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 渐进模式 Step 2: 生成章节 (完全并行)
|
|
|
|
|
+ */
|
|
|
|
|
+async function generateChaptersStep(taskId: number, subjectIds: number[]) {
|
|
|
|
|
+ const task = await prisma.learningPath.findUnique({ where: { id: taskId } });
|
|
|
|
|
+ if (!task) throw new Error('任务不存在');
|
|
|
|
|
+
|
|
|
|
|
+ console.log(`📖 渐进模式 - 并行生成章节 for subjects: ${subjectIds}`);
|
|
|
|
|
+
|
|
|
|
|
+ const subjects = await 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) =>
|
|
|
|
|
+ prisma.chapter.create({
|
|
|
|
|
+ data: {
|
|
|
|
|
+ subjectId: subject.id,
|
|
|
|
|
+ name,
|
|
|
|
|
+ orderIndex: index,
|
|
|
|
|
+ status: 'completed',
|
|
|
|
|
+ confirmationStatus: 'pending',
|
|
|
|
|
+ aiResponse: chaptersResponse,
|
|
|
|
|
+ },
|
|
|
|
|
+ })
|
|
|
|
|
+ )
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ await prisma.subject.update({
|
|
|
|
|
+ where: { id: subject.id },
|
|
|
|
|
+ data: { status: 'completed' },
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // 更新进度
|
|
|
|
|
+ await 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: number, chapterIds: number[]) {
|
|
|
|
|
+ const task = await prisma.learningPath.findUnique({ where: { id: taskId } });
|
|
|
|
|
+ if (!task) throw new Error('任务不存在');
|
|
|
|
|
+
|
|
|
|
|
+ console.log(`📑 渐进模式 - 并行生成小节 for chapters: ${chapterIds}`);
|
|
|
|
|
+
|
|
|
|
|
+ const chapters = await 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) =>
|
|
|
|
|
+ prisma.section.create({
|
|
|
|
|
+ data: {
|
|
|
|
|
+ chapterId: chapter.id,
|
|
|
|
|
+ name,
|
|
|
|
|
+ orderIndex: index,
|
|
|
|
|
+ status: 'completed',
|
|
|
|
|
+ confirmationStatus: 'pending',
|
|
|
|
|
+ aiResponse: sectionsResponse,
|
|
|
|
|
+ },
|
|
|
|
|
+ })
|
|
|
|
|
+ )
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ await prisma.chapter.update({
|
|
|
|
|
+ where: { id: chapter.id },
|
|
|
|
|
+ data: { status: 'completed' },
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // 更新进度
|
|
|
|
|
+ await 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: number, sectionIds: number[]) {
|
|
|
|
|
+ const task = await prisma.learningPath.findUnique({ where: { id: taskId } });
|
|
|
|
|
+ if (!task) throw new Error('任务不存在');
|
|
|
|
|
+
|
|
|
|
|
+ console.log(`📝 渐进模式 - 并行生成详细内容 for sections: ${sectionIds}`);
|
|
|
|
|
+
|
|
|
|
|
+ const sections = await 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 prisma.contentBlock.create({
|
|
|
|
|
+ data: {
|
|
|
|
|
+ sectionId: section.id,
|
|
|
|
|
+ content: contentResponse,
|
|
|
|
|
+ orderIndex: 0,
|
|
|
|
|
+ wordCount: contentResponse.length,
|
|
|
|
|
+ },
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // 更新小节状态
|
|
|
|
|
+ await prisma.section.update({
|
|
|
|
|
+ where: { id: section.id },
|
|
|
|
|
+ data: { status: 'completed', confirmationStatus: 'completed' },
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // 更新进度 (动态计算已完成的小节数)
|
|
|
|
|
+ const completedSections = k + 1;
|
|
|
|
|
+ await prisma.learningPath.update({
|
|
|
|
|
+ where: { id: taskId },
|
|
|
|
|
+ data: { progress: 50 + Math.floor((completedSections / totalSections) * 50) },
|
|
|
|
|
+ });
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error(`❌ 生成内容失败 ${section.name}:`, error);
|
|
|
|
|
+ }
|
|
|
|
|
+ }));
|
|
|
|
|
+
|
|
|
|
|
+ // 统计并完成
|
|
|
|
|
+ const totalBlocks = await prisma.contentBlock.count({
|
|
|
|
|
+ where: {
|
|
|
|
|
+ section: {
|
|
|
|
|
+ chapter: {
|
|
|
|
|
+ subject: { learningPathId: taskId }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ await 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: number) {
|
|
|
|
|
+ const task = await prisma.learningPath.findUnique({ where: { id: taskId } });
|
|
|
|
|
+ if (!task) throw new Error('任务不存在');
|
|
|
|
|
+
|
|
|
|
|
+ console.log(`🚀 多Agent模式 - 开始生成学习路径: ${task.topic}`);
|
|
|
|
|
+
|
|
|
|
|
+ await 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) =>
|
|
|
|
|
+ prisma.subject.create({
|
|
|
|
|
+ data: {
|
|
|
|
|
+ learningPathId: taskId,
|
|
|
|
|
+ name,
|
|
|
|
|
+ orderIndex: index,
|
|
|
|
|
+ status: 'pending',
|
|
|
|
|
+ aiResponse: subjectsResponse,
|
|
|
|
|
+ },
|
|
|
|
|
+ })
|
|
|
|
|
+ )
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ await 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) =>
|
|
|
|
|
+ prisma.chapter.create({
|
|
|
|
|
+ data: {
|
|
|
|
|
+ subjectId: subject.id,
|
|
|
|
|
+ name,
|
|
|
|
|
+ orderIndex: index,
|
|
|
|
|
+ status: 'pending',
|
|
|
|
|
+ aiResponse: chaptersResponse,
|
|
|
|
|
+ },
|
|
|
|
|
+ })
|
|
|
|
|
+ )
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ await 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) =>
|
|
|
|
|
+ prisma.section.create({
|
|
|
|
|
+ data: {
|
|
|
|
|
+ chapterId: chapter.id,
|
|
|
|
|
+ name,
|
|
|
|
|
+ orderIndex: index,
|
|
|
|
|
+ status: 'pending',
|
|
|
|
|
+ aiResponse: sectionsResponse,
|
|
|
|
|
+ },
|
|
|
|
|
+ })
|
|
|
|
|
+ )
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ await 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 prisma.contentBlock.create({
|
|
|
|
|
+ data: {
|
|
|
|
|
+ sectionId: section.id,
|
|
|
|
|
+ content: contentResponse,
|
|
|
|
|
+ orderIndex: 0,
|
|
|
|
|
+ wordCount: contentResponse.length,
|
|
|
|
|
+ },
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ await prisma.section.update({
|
|
|
|
|
+ where: { id: section.id },
|
|
|
|
|
+ data: { status: 'completed' },
|
|
|
|
|
+ });
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error(`❌ 生成内容失败 ${section.name}:`, error);
|
|
|
|
|
+ }
|
|
|
|
|
+ }));
|
|
|
|
|
+ }));
|
|
|
|
|
+
|
|
|
|
|
+ await prisma.subject.update({
|
|
|
|
|
+ where: { id: subject.id },
|
|
|
|
|
+ data: { status: 'completed' },
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // 更新学科Agent进度
|
|
|
|
|
+ const subjectProgress = Math.floor(((i + 1) / subjects.length) * 40) + 10;
|
|
|
|
|
+ await 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 prisma.contentBlock.count({
|
|
|
|
|
+ where: {
|
|
|
|
|
+ section: {
|
|
|
|
|
+ chapter: {
|
|
|
|
|
+ subject: { learningPathId: taskId }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ await 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: any) {
|
|
|
|
|
+ console.error('❌ 多Agent生成失败:', error);
|
|
|
|
|
+ await prisma.learningPath.update({
|
|
|
|
|
+ where: { id: taskId },
|
|
|
|
|
+ data: { status: 'failed', errorMsg: error.message },
|
|
|
|
|
+ });
|
|
|
|
|
+ throw error;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|