#!/usr/bin/env node /** * 排查「段落间距」:对比 调用记录里 outline.suggestedContent 与 node_slides.content.paragraphs * 在服务器上执行(需能连到数据库): * cd /var/www/wildgrowth-backend/backend && node scripts/inspect-paragraphs-server.js * 本地(有 .env): * cd backend && node scripts/inspect-paragraphs-server.js */ const { PrismaClient } = require('@prisma/client'); const prisma = new PrismaClient(); async function main() { const argId = process.argv[2] ? process.argv[2].trim() : null; let task; if (argId) { // 先按 taskId 查,再按 courseId 查 task = await prisma.courseGenerationTask.findFirst({ where: { id: argId }, include: { course: { select: { id: true, title: true } } }, }); if (!task) { task = await prisma.courseGenerationTask.findFirst({ where: { courseId: argId }, orderBy: { createdAt: 'desc' }, include: { course: { select: { id: true, title: true } } }, }); } if (!task) { console.log('未找到 taskId 或 courseId 为', argId, '的任务'); return; } } else { // 取最新一条已完成任务 task = await prisma.courseGenerationTask.findFirst({ where: { status: 'completed' }, orderBy: { createdAt: 'desc' }, include: { course: { select: { id: true, title: true } } }, }); if (!task) { console.log('没有已完成的生成任务'); return; } } console.log('=== 任务信息 ==='); console.log('taskId:', task.id); console.log('courseId:', task.courseId); console.log('courseTitle:', task.course?.title ?? ''); console.log(''); const outline = task.outline; if (!outline || !outline.chapters || !Array.isArray(outline.chapters)) { console.log('任务无 outline 或 chapters'); return; } // 取第一个小节对应的 suggestedContent(调用记录里显示的那类内容) let firstSuggestedContent = null; let firstNodeTitle = ''; for (const ch of outline.chapters) { if (ch.nodes && Array.isArray(ch.nodes)) { for (const node of ch.nodes) { if (node.suggestedContent) { firstSuggestedContent = node.suggestedContent; firstNodeTitle = node.title || ''; break; } } if (firstSuggestedContent) break; } } if (!firstSuggestedContent) { console.log('outline 中未找到 suggestedContent'); } else { console.log('=== outline 中第一个小节的 suggestedContent(调用记录里看到的内容)==='); console.log('小节标题:', firstNodeTitle); console.log('长度:', firstSuggestedContent.length); const idx = firstSuggestedContent.indexOf('\n'); console.log('第一个 \\n 位置:', idx === -1 ? '无' : idx); const doubleNewlineCount = (firstSuggestedContent.match(/\n\s*\n/g) || []).length; console.log('双换行 \\n\\n 出现次数:', doubleNewlineCount); const splitByDouble = firstSuggestedContent.split(/\n\s*\n/).map((p) => p.trim()).filter((p) => p.length > 0); console.log('按 /\\n\\s*\\n/ 拆分后段落数:', splitByDouble.length); console.log('前 350 字符(repr):', JSON.stringify(firstSuggestedContent.slice(0, 350))); console.log(''); } // 该课程下第一个节点的 node_slides 的 content.paragraphs const firstNode = await prisma.courseNode.findFirst({ where: { courseId: task.courseId }, orderBy: { orderIndex: 'asc' }, include: { slides: { orderBy: { orderIndex: 'asc' }, take: 1 } }, }); if (!firstNode || !firstNode.slides || firstNode.slides.length === 0) { console.log('该课程下没有节点或没有 node_slides'); return; } const slide = firstNode.slides[0]; const content = slide.content; console.log('=== 该课程第一个节点的 node_slides.content(实际落库)==='); console.log('节点标题:', firstNode.title); console.log('slide id:', slide.id); if (content && typeof content === 'object' && Array.isArray(content.paragraphs)) { console.log('paragraphs 数量:', content.paragraphs.length); content.paragraphs.forEach((p, i) => { console.log(` [${i}] 长度: ${p.length}, 前80字: ${JSON.stringify(p.slice(0, 80))}`); }); } else { console.log('content 或 content.paragraphs 不存在:', content ? Object.keys(content) : 'null'); } } main() .then(() => process.exit(0)) .catch((e) => { console.error(e); process.exit(1); }) .finally(() => prisma.$disconnect());