feat: expand novel IP and production workflows

This commit is contained in:
www
2026-09-18 08:14:05 +02:00
parent b2ae4600b4
commit d9c81a3ac0
235 changed files with 117971 additions and 2721 deletions
+324 -1
View File
@@ -1,4 +1,4 @@
import { PrismaClient } from '@prisma/client';
import { Prisma, PrismaClient } from '@prisma/client';
import { hash } from 'bcryptjs';
import { DEFAULT_AI_ROUTER_CONFIG } from '../src/ai-router/ai-router.types';
@@ -17,6 +17,296 @@ const mockProviders = [
['EmbeddingProvider', 'mock-embedding', 'Mock Embedding Provider', 'mock-embedding-v1']
] as const;
const defaultAgentPrompts = [
{
agent_name: 'NovelIdeaCoachAgent',
provider_type: 'TextProvider',
default_provider_code: 'volcengine-doubao-seed20-mini-text',
system_prompt: '你是网文平台金牌主编、商业化小说策划、听书编导和短剧改编策划。你的用户是不会写小说的运营,你必须把他的白话需求变成可选择的高质量小说方案。',
user_prompt_template: [
'请根据运营输入生成 3 个高商业化小说方向卡。必须严格输出紧凑 JSON,不要 Markdown,不要解释。',
'运营不是专业作家,所以每个方案要短、准、好判断:为什么能火、主角是谁、开篇怎么抓人。',
'本步骤只做方向筛选,不写卷纲和章节蓝图;后续 IP 圣经 Agent 会补完整结构。',
'必须兼顾后续听书和短剧改编:人物关系清楚、场景可视化、每章能有钩子。',
'输出 JSON 字段:proposals。proposals 是数组,每个元素包含:',
'id, title, genre, novel_scale, target_audience, target_words, target_chapters, style_code, logline, opening_hook, main_character, core_conflict, selling_points, risk_notes, adaptation_targets, writing_rules, forbidden_rules, score, operator_reason。',
'要求:',
'1. title 要像真实网文书名,不要泛泛而谈。',
'2. logline 不超过 35 字,opening_hook 不超过 55 字。',
'3. main_character 和 core_conflict 各不超过 35 字。',
'4. selling_points 只给 3 条,每条不超过 10 字;risk_notes 只给 2 条,每条不超过 12 字。',
'5. writing_rules 只给 2 条,forbidden_rules 只给 2 条,每条不超过 12 字。',
'6. target_words 和 target_chapters 必须是数字或 null。',
'7. adaptation_targets 只输出 ["听书","短剧"]operator_reason 不超过 35 字,score 是 0-100。',
'小说长度:{{novel_scale}}',
'目标读者:{{target_audience}}',
'题材:{{genre}}',
'风格代码:{{style_code}}',
'目标字数:{{target_words}}',
'目标章节数:{{target_chapters}}',
'后续用途:{{adaptation_targets}}',
'运营灵感:{{inspiration_text}}',
'必须包含:{{must_have_text}}',
'禁止内容:{{avoid_text}}',
'参考风格/作品:{{reference_titles}}'
].join('\n\n'),
output_schema_json: {
type: 'object',
required: ['proposals'],
properties: {
proposals: {
type: 'array',
items: {
type: 'object',
required: ['title', 'logline', 'opening_hook', 'main_character', 'core_conflict', 'selling_points', 'score'],
properties: {
id: { type: 'string' },
title: { type: 'string' },
genre: { type: 'string' },
novel_scale: { type: 'string' },
target_audience: { type: 'string' },
target_words: { type: 'number' },
target_chapters: { type: 'number' },
style_code: { type: 'string' },
logline: { type: 'string' },
opening_hook: { type: 'string' },
main_character: { type: 'string' },
core_conflict: { type: 'string' },
selling_points: { type: 'array' },
risk_notes: { type: 'array' },
adaptation_targets: { type: 'array' },
writing_rules: { type: 'array' },
forbidden_rules: { type: 'array' },
score: { type: 'number' },
operator_reason: { type: 'string' }
}
}
}
}
},
temperature: 0.72,
max_output_tokens: 1200
},
{
agent_name: 'IPBibleAgent',
provider_type: 'NovelProvider',
default_provider_code: 'openai-responses-novel',
system_prompt: '你是专业长篇小说总策划、网文主编、文学作家、剧作结构师和影视导演。你的任务是建立稳定、可长期创作、可听书、可短剧改编的 IP 圣经。',
user_prompt_template: [
'请根据用户 Brief 生成 IP 圣经。必须严格输出 JSON,不要 Markdown。',
'JSON 字段:core_logline, theme, worldbuilding, main_characters, supporting_characters, antagonist_system, volume_structure, foreshadow_plan, writing_rules, forbidden_rules, adaptation_rules。',
'要求:主线清晰,人设稳定,有禁止改动项,有伏笔规划,考虑听书和 AI 短剧改编。',
'用户 Brief{{brief}}',
'小说类型:{{novel_scale}}',
'题材:{{genre}}',
'风格:{{style}}',
'禁止内容:{{avoid}}'
].join('\n\n'),
output_schema_json: {
type: 'object',
required: ['core_logline', 'theme', 'worldbuilding', 'main_characters', 'volume_structure', 'writing_rules', 'forbidden_rules'],
properties: {
core_logline: { type: 'string' },
theme: { type: 'string' },
worldbuilding: { type: 'object' },
main_characters: { type: 'array' },
supporting_characters: { type: 'array' },
antagonist_system: { type: 'object' },
volume_structure: { type: 'array' },
foreshadow_plan: { type: 'array' },
writing_rules: { type: 'array' },
forbidden_rules: { type: 'array' },
adaptation_rules: { type: 'object' }
}
},
temperature: 0.6,
max_output_tokens: 6000
},
{
agent_name: 'ChapterCardAgent',
provider_type: 'NovelProvider',
default_provider_code: 'openai-responses-novel',
system_prompt: '你是长篇小说章节导演。你的任务是设计下一章章节卡,不写正文,只输出可执行的章节方案。',
user_prompt_template: [
'请生成第 {{chapter_no}} 章章节卡。必须严格输出 JSON,不要 Markdown。',
'本章必须承接上一章,推动主线、人物变化或伏笔进展,不得水剧情。',
'IP 圣经摘要:{{ip_bible_summary}}',
'当前卷纲:{{volume_outline}}',
'最近章节摘要:{{recent_summaries}}',
'人物当前状态:{{character_states}}',
'当前伏笔:{{active_foreshadows}}',
'禁止事项:{{forbidden_rules}}',
'请输出:chapter_no, title, chapter_goal, scenes, must_happen, must_not_happen, foreshadows_to_add, foreshadows_to_advance, foreshadows_to_resolve, ending_hook, adaptation_notes。'
].join('\n\n'),
output_schema_json: {
type: 'object',
required: ['chapter_no', 'title', 'chapter_goal', 'scenes', 'must_happen', 'must_not_happen', 'ending_hook'],
properties: {
chapter_no: { type: 'number' },
title: { type: 'string' },
chapter_goal: { type: 'string' },
scenes: { type: 'array' },
must_happen: { type: 'array' },
must_not_happen: { type: 'array' },
foreshadows_to_add: { type: 'array' },
foreshadows_to_advance: { type: 'array' },
foreshadows_to_resolve: { type: 'array' },
ending_hook: { type: 'string' },
adaptation_notes: { type: 'object' }
}
},
temperature: 0.45,
max_output_tokens: 4000
},
{
agent_name: 'NovelWriterAgent',
provider_type: 'NovelProvider',
default_provider_code: 'anthropic-claude-novel',
system_prompt: '你是专业小说家。你必须严格按章节卡写正文,不改变 IP 圣经,不改变人物状态,不提前揭露秘密。',
user_prompt_template: [
'请创作第 {{chapter_no}} 章正文,只输出正文,不要解释。',
'目标字数:{{target_words}}',
'文风规则:{{style_rules}}',
'IP 圣经摘要:{{ip_bible_summary}}',
'人物档案:{{character_profiles}}',
'人物当前状态:{{character_states}}',
'最近章节摘要:{{recent_summaries}}',
'章节卡:{{chapter_card}}',
'禁止规则:{{forbidden_rules}}',
'要求:画面感强、对白符合身份、每场戏推动剧情/人物/伏笔,不能流水账,不能狗血。'
].join('\n\n'),
output_schema_json: null,
temperature: 0.75,
max_output_tokens: 9000
},
{
agent_name: 'NovelPolishAgent',
provider_type: 'NovelProvider',
default_provider_code: 'anthropic-claude-novel',
system_prompt: '你是小说修辞和节奏编辑。你只负责润色文风、节奏、对白和画面感,不得改变剧情事实。',
user_prompt_template: [
'请润色下面章节,输出完整润色稿,不要解释。',
'目标文风:{{style_rules}}',
'章节卡:{{chapter_card}}',
'禁止改变:{{forbidden_rules}}',
'原稿:{{draft_text}}'
].join('\n\n'),
output_schema_json: null,
temperature: 0.65,
max_output_tokens: 9000
},
{
agent_name: 'ContinuityCheckAgent',
provider_type: 'NovelProvider',
default_provider_code: 'openai-responses-novel',
system_prompt: '你是严苛的小说连续性审稿人,只检查冲突,不负责夸奖。',
user_prompt_template: [
'请检查本章是否违背设定、人设、时间线、地点、伏笔。必须严格输出 JSON,不要 Markdown。',
'IP 圣经:{{ip_bible}}',
'人物状态:{{character_states}}',
'伏笔库:{{active_foreshadows}}',
'章节卡:{{chapter_card}}',
'本章正文:{{chapter_text}}',
'请输出:has_conflict, conflicts, character_drift, timeline_errors, setting_errors, foreshadow_errors, fix_suggestions。'
].join('\n\n'),
output_schema_json: {
type: 'object',
required: ['has_conflict', 'conflicts', 'fix_suggestions'],
properties: {
has_conflict: { type: 'boolean' },
conflicts: { type: 'array' },
character_drift: { type: 'array' },
timeline_errors: { type: 'array' },
setting_errors: { type: 'array' },
foreshadow_errors: { type: 'array' },
fix_suggestions: { type: 'array' }
}
},
temperature: 0.2,
max_output_tokens: 3000
},
{
agent_name: 'QualityCheckAgent',
provider_type: 'NovelProvider',
default_provider_code: 'openai-responses-novel',
system_prompt: '你是严苛的小说主编和质量评分官。你只输出评分、问题和修复策略。',
user_prompt_template: [
'请为本章评分。必须严格输出 JSON,不要 Markdown。',
'评分维度:剧情推进、人物一致性、上下文连续性、文风质量、情绪张力、伏笔管理、场景画面感、听书适配度、短剧适配度。',
'IP 圣经:{{ip_bible}}',
'章节卡:{{chapter_card}}',
'连续性检查:{{continuity_check}}',
'本章正文:{{chapter_text}}',
'请输出:pass, total_score, scores, problems, must_fix, optional_suggestions, rewrite_required, rewrite_strategy。'
].join('\n\n'),
output_schema_json: {
type: 'object',
required: ['pass', 'total_score', 'scores', 'problems', 'rewrite_required'],
properties: {
pass: { type: 'boolean' },
total_score: { type: 'number' },
scores: { type: 'object' },
problems: { type: 'array' },
must_fix: { type: 'array' },
optional_suggestions: { type: 'array' },
rewrite_required: { type: 'boolean' },
rewrite_strategy: { type: 'array' }
}
},
temperature: 0.2,
max_output_tokens: 3000
},
{
agent_name: 'RepairAgent',
provider_type: 'NovelProvider',
default_provider_code: 'anthropic-claude-novel',
system_prompt: '你是小说修稿编辑。你只修复质检指出的问题,不新增无关设定,不破坏上下文。',
user_prompt_template: [
'请根据质检报告修复本章,输出完整修复稿,不要解释。',
'IP 圣经摘要:{{ip_bible_summary}}',
'章节卡:{{chapter_card}}',
'原正文:{{chapter_text}}',
'质检报告:{{quality_report}}',
'连续性检查:{{continuity_check}}'
].join('\n\n'),
output_schema_json: null,
temperature: 0.55,
max_output_tokens: 9000
},
{
agent_name: 'MemoryUpdateAgent',
provider_type: 'NovelProvider',
default_provider_code: 'volcengine-doubao-seed20-pro-novel',
system_prompt: '你是小说连续性档案管理员。你必须准确、简洁,不得加入正文没有发生的内容,不得猜测未来。',
user_prompt_template: [
'请根据本章正文更新记忆库。必须严格输出 JSON,不要 Markdown。',
'已有角色状态:{{character_states}}',
'已有伏笔库:{{active_foreshadows}}',
'本章正文:{{chapter_text}}',
'请输出:chapter_summary, character_updates, relationship_updates, world_updates, timeline_update, location_update, new_foreshadows, updated_foreshadows, resolved_foreshadows, next_chapter_must_continue, forbidden_to_forget。'
].join('\n\n'),
output_schema_json: {
type: 'object',
required: ['chapter_summary', 'character_updates', 'timeline_update', 'next_chapter_must_continue'],
properties: {
chapter_summary: { type: 'string' },
character_updates: { type: 'array' },
relationship_updates: { type: 'array' },
world_updates: { type: 'array' },
timeline_update: { type: 'string' },
location_update: { type: 'string' },
new_foreshadows: { type: 'array' },
updated_foreshadows: { type: 'array' },
resolved_foreshadows: { type: 'array' },
next_chapter_must_continue: { type: 'array' },
forbidden_to_forget: { type: 'array' }
}
},
temperature: 0.25,
max_output_tokens: 3500
}
] as const;
async function main() {
const adminPassword = process.env.SEED_ADMIN_PASSWORD || 'Admin123!';
const adminPasswordHash = await hash(adminPassword, 12);
@@ -84,6 +374,39 @@ async function main() {
});
}
for (const prompt of defaultAgentPrompts) {
await prisma.agentPrompt.upsert({
where: {
agent_name_version: {
agent_name: prompt.agent_name,
version: 1
}
},
update: {
provider_type: prompt.provider_type,
default_provider_code: prompt.default_provider_code ?? null,
system_prompt: prompt.system_prompt,
user_prompt_template: prompt.user_prompt_template,
output_schema_json: prompt.output_schema_json ?? Prisma.JsonNull,
temperature: prompt.temperature,
max_output_tokens: prompt.max_output_tokens,
is_active: true
},
create: {
agent_name: prompt.agent_name,
version: 1,
provider_type: prompt.provider_type,
default_provider_code: prompt.default_provider_code ?? null,
system_prompt: prompt.system_prompt,
user_prompt_template: prompt.user_prompt_template,
output_schema_json: prompt.output_schema_json ?? Prisma.JsonNull,
temperature: prompt.temperature,
max_output_tokens: prompt.max_output_tokens,
is_active: true
}
});
}
await prisma.systemConfig.upsert({
where: { config_key: 'system_a.current_stage' },
update: {