Просмотр исходного кода

feat: add style-anchor generation API with positive/negative samples

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
nonvioce 6 месяцев назад
Родитель
Сommit
07b1f280fa
2 измененных файлов с 120 добавлено и 0 удалено
  1. 67 0
      src/app/api/novel/style-anchor/route.ts
  2. 53 0
      src/app/lib/chapter-prompts.ts

+ 67 - 0
src/app/api/novel/style-anchor/route.ts

@@ -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 });
+  }
+}

+ 53 - 0
src/app/lib/chapter-prompts.ts

@@ -300,3 +300,56 @@ export function buildIronRulesPrompt(projectInfo: {
   ...
 ]`;
 }
+
+// ─── Style Anchor Generation ─────────────────────────────────────
+
+export function buildStyleAnchorPrompt(styleInfo: {
+  primaryStyle: string;
+  supportStyle?: string;
+  intensity: number;
+  styleDescription: string;
+  novelType: string;
+  worldDescription: string;
+}): string {
+  return `你是一位专业的小说文风设计师。请为以下项目生成风格锚定文件。
+
+## 项目信息
+- 类型:${styleInfo.novelType}
+- 世界观:${styleInfo.worldDescription}
+- 主风格:${styleInfo.primaryStyle}
+- 辅助风格:${styleInfo.supportStyle || '无'}
+- 风格强度:${styleInfo.intensity}/10
+- 风格描述:${styleInfo.styleDescription || '未提供'}
+
+## 输出要求
+
+### 1. 正面样本(3段,每段200-300字)
+基于以上设定,写出3段符合风格的正文字本。
+- 样本1:动作/冲突场景
+- 样本2:日常/描写场景
+- 样本3:对话/心理场景
+每段后标注该样本体现了什么风格特征。
+
+### 2. 负面样本(3段,每段150-200字)
+写出3段有AI腔的失败文本,并标注问题。
+- 样本4:模板化转折句泛滥("不是...而是..."等对称套话)
+- 样本5:提示词腔/总结腔("核心在于/重点是/需要注意的是/值得一提的是")
+- 样本6:空洞的形容词堆砌 + 过度使用破折号/省略号
+每段后标注具体是什么AI腔特征。
+
+### 3. 禁用词/句式黑名单
+列出10-15个本项目必须禁止的具体词汇、句式或表达模式。
+
+## 输出格式
+只返回JSON(不要markdown代码块标记):
+{
+  "positiveSamples": [
+    { "content": "...", "annotation": "风格特征说明" }
+  ],
+  "negativeSamples": [
+    { "content": "...", "annotation": "AI腔特征说明" }
+  ],
+  "bannedPhrases": ["词1", "句式2", "模式3"],
+  "bannedPatterns": ["不仅...更...", "在这个充满...的...里"]
+}`;
+}