|
|
@@ -0,0 +1,67 @@
|
|
|
+import { NextRequest, NextResponse } from 'next/server';
|
|
|
+import { buildStyleAnchorPrompt } from '@/app/lib/chapter-prompts';
|
|
|
+import type { StyleAnchorSample } from '@/app/lib/novel-types';
|
|
|
+
|
|
|
+export async function POST(request: NextRequest) {
|
|
|
+ const body = await request.json().catch(() => null);
|
|
|
+ if (!body) return NextResponse.json({ success: false, error: '无效的请求体' }, { status: 400 });
|
|
|
+
|
|
|
+ const { styleInfo, apiSettings } = body;
|
|
|
+ if (!styleInfo || !apiSettings) {
|
|
|
+ return NextResponse.json({ success: false, error: '缺少参数' }, { status: 400 });
|
|
|
+ }
|
|
|
+
|
|
|
+ const prompt = buildStyleAnchorPrompt(styleInfo);
|
|
|
+
|
|
|
+ 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: 3000,
|
|
|
+ }),
|
|
|
+ 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 || '';
|
|
|
+
|
|
|
+ // Parse JSON
|
|
|
+ const jsonMatch = text.match(/```(?:json)?\s*([\s\S]*?)```/) || [null, text];
|
|
|
+ const anchor = JSON.parse(jsonMatch[1].trim());
|
|
|
+
|
|
|
+ // Format as markdown
|
|
|
+ let md = '# 风格锚定\n\n';
|
|
|
+ md += '## 正面样本(应模仿)\n\n';
|
|
|
+ for (let i = 0; i < (anchor.positiveSamples?.length || 0); i++) {
|
|
|
+ const s = anchor.positiveSamples[i] as StyleAnchorSample;
|
|
|
+ md += `### 样本 ${i + 1}\n\n${s.content}\n\n> ${s.annotation}\n\n`;
|
|
|
+ }
|
|
|
+ md += '## 负面样本(应避免)\n\n';
|
|
|
+ for (let i = 0; i < (anchor.negativeSamples?.length || 0); i++) {
|
|
|
+ const s = anchor.negativeSamples[i] as StyleAnchorSample;
|
|
|
+ md += `### 样本 ${i + 4}\n\n${s.content}\n\n> ${s.annotation}\n\n`;
|
|
|
+ }
|
|
|
+ md += '## 禁用词/句式\n\n';
|
|
|
+ for (const phrase of (anchor.bannedPhrases || [])) {
|
|
|
+ md += `- ${phrase}\n`;
|
|
|
+ }
|
|
|
+ md += '\n## 禁用模式\n\n';
|
|
|
+ for (const pattern of (anchor.bannedPatterns || [])) {
|
|
|
+ md += `- ${pattern}\n`;
|
|
|
+ }
|
|
|
+
|
|
|
+ return NextResponse.json({ success: true, anchor, markdown: md });
|
|
|
+ } catch (error) {
|
|
|
+ return NextResponse.json({ success: false, error: (error as Error).message }, { status: 500 });
|
|
|
+ }
|
|
|
+}
|