import { NextRequest, NextResponse } from 'next/server'; import { isPathSafe } from '@/app/lib/novel-server-utils'; import { promises as fs } from 'fs'; import path from 'path'; export async function POST(request: NextRequest) { const body = await request.json().catch(() => null); if (!body) return NextResponse.json({ success: false, error: '无效请求' }, { status: 400 }); const { projectPath, chapterIndex, chapterContent, apiSettings } = body; if (!projectPath || !isPathSafe(projectPath) || !apiSettings || !chapterContent) { return NextResponse.json({ success: false, error: '缺少必要参数' }, { status: 400 }); } // Only trigger every 5 chapters, but allow override if (chapterIndex % 5 !== 0 && !body.force) { return NextResponse.json({ success: true, skipped: true }); } // Get recent chapters for context let recentContext = ''; const startChapter = Math.max(1, chapterIndex - 4); const chaptersDir = path.join(projectPath, 'chapters'); try { const files = await fs.readdir(chaptersDir); for (const file of files) { const match = file.match(/^chapter_(\d+)_/); if (match) { const idx = parseInt(match[1]); if (idx >= startChapter && idx <= chapterIndex) { const content = await fs.readFile(path.join(chaptersDir, file), 'utf-8'); recentContext += `\n### 第${idx}章\n${content.slice(0, 800)}\n`; } } } } catch { /* continue without context */ } const prompt = `你是一位付费阅读的资深读者。请对以下小说章节给出读者反馈。 ## 最近章节(摘要) ${recentContext.slice(0, 3000)} ## 当前章节 ${chapterContent.slice(0, 3000)} ## 评分维度(1-10分) 1. 节奏感:情节推进是否紧凑?是否拖沓或过赶? 2. 爽点密度:情绪释放是否到位?是否有满足感的峰值? 3. 情感共鸣:是否关心角色的命运?是否有打动人的时刻? 4. 阅读困惑:哪些地方可能需要回看才能理解?是否有逻辑断层? ## 输出格式 只返回 JSON: { "pace": 7, "excitementDensity": 6, "emotionalEngagement": 8, "confusion": 3, "suggestions": "具体的读者感受和建议,2-3句话" }`; try { const response = await fetch(apiSettings.apiUrl, { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${apiSettings.apiKey}` }, body: JSON.stringify({ model: apiSettings.model, messages: [{ role: 'user', content: prompt }], max_tokens: 500 }), signal: AbortSignal.timeout(55000), }); if (!response.ok) { return NextResponse.json({ success: false, error: `AI 请求失败: ${response.status}` }, { status: 502 }); } const data = await response.json(); const text = data.choices?.[0]?.message?.content || ''; const jsonMatch = text.match(/\{[\s\S]*\}/); let feedback; try { feedback = jsonMatch ? JSON.parse(jsonMatch[0]) : null; } catch { return NextResponse.json({ success: false, error: 'AI 返回了无效的 JSON 响应' }, { status: 502 }); } // Save feedback to file const feedbackDir = path.join(projectPath, 'logs', 'reader-feedback'); await fs.mkdir(feedbackDir, { recursive: true }); const range = `${startChapter}-${chapterIndex}`; await fs.writeFile( path.join(feedbackDir, `chapters-${range}.json`), JSON.stringify({ ...feedback, chapterRange: range }, null, 2), 'utf-8' ); return NextResponse.json({ success: true, feedback }); } catch (error) { return NextResponse.json({ success: false, error: (error as Error).message }, { status: 500 }); } }