type ParsedRole = { id: string; name: string; title: string; role: string; description: string; visualDescription: string; actionDescription: string; sceneVisualDescription: string; backstory: string; personality: string; motivation: string; tags: string[]; }; type ParsedLandmarkConnection = { targetLandmarkId: string; summary: string; relativePosition: string; }; type ParsedLandmark = { id: string; name: string; description: string; visualDescription: string; dangerLevel: string; sceneNpcIds: string[]; connections: ParsedLandmarkConnection[]; }; type ParsedProfile = { name: string; settingText: string; summary: string; tone: string; playerGoal: string; playableNpcs: ParsedRole[]; storyNpcs: ParsedRole[]; landmarks: ParsedLandmark[]; }; export const CUSTOM_WORLD_ENTITY_GENERATOR_SYSTEM_PROMPT = '你是游戏世界编辑器的实体生成器。你必须只返回可解析 JSON,不要输出解释、前言或 Markdown。'; function buildRoleReferenceText(roles: ParsedRole[], emptyText: string) { if (roles.length === 0) { return emptyText; } return roles .slice(0, 12) .map( (role, index) => `${index + 1}. ${role.name} / ${role.title || role.role} / 身份:${ role.role || '未写' } / 描述:${role.description || '未写'} / 背景:${ role.backstory || '未写' } / 性格:${role.personality || '未写'} / 动机:${ role.motivation || '未写' } / 形象:${role.visualDescription || '未写'} / 动作表现:${ role.actionDescription || '未写' } / 场景画面:${role.sceneVisualDescription || '未写'} / 标签:${ role.tags.join('、') || '暂无' }`, ) .join('\n'); } function buildLandmarkReferenceText(profile: ParsedProfile) { if (profile.landmarks.length === 0) { return '当前还没有场景设定。'; } const storyNpcById = new Map(profile.storyNpcs.map((npc) => [npc.id, npc])); const landmarkById = new Map( profile.landmarks.map((landmark) => [landmark.id, landmark]), ); return profile.landmarks .slice(0, 12) .map((landmark, index) => { const sceneNpcNames = landmark.sceneNpcIds .map((npcId) => storyNpcById.get(npcId)?.name ?? '') .filter(Boolean) .join('、'); const connectionNames = landmark.connections .map((connection) => { const targetName = landmarkById.get(connection.targetLandmarkId)?.name || connection.targetLandmarkId; return `${targetName}(${connection.relativePosition} / ${ connection.summary || '无说明' })`; }) .join('、'); return `${index + 1}. ${landmark.name} / 危险度:${ landmark.dangerLevel || 'medium' } / 描述:${landmark.description || '未写'} / 画面:${ landmark.visualDescription || '未写' } / 场景角色:${ sceneNpcNames || '暂无' } / 连接:${connectionNames || '暂无'}`; }) .join('\n'); } export function buildPlayablePrompt(profile: ParsedProfile) { return [ `世界名:${profile.name}`, `当前世界设定:${profile.settingText || profile.summary || '未提供额外设定。'}`, `世界摘要:${profile.summary || '未填写'}`, `世界基调:${profile.tone || '未填写'}`, `玩家主线目标:${profile.playerGoal || '未填写'}`, `当前可扮演角色设定:\n${buildRoleReferenceText(profile.playableNpcs, '当前还没有可扮演角色。')}`, `当前场景角色设定:\n${buildRoleReferenceText(profile.storyNpcs, '当前还没有场景角色。')}`, `当前场景设定:\n${buildLandmarkReferenceText(profile)}`, '请基于上面全部上下文,生成 1 名新的“可扮演角色”。', '要求:', '- 必须与当前世界设定、已有可扮演角色、已有场景角色、已有场景形成互补,不要重复定位。', '- 必须保留明确的协作价值、成长空间和入队理由。', '- 不要生成泛用模板角色,必须让角色与当前世界的具体势力、地点、冲突或禁忌发生绑定。', '- visualDescription 只写与角色设定相关的外形、服装、材质、武器、体态、色彩和识别特征,禁止写角色以外的周边环境等与角色不想管的设定。', '- actionDescription 只写这个角色的动作表现与战斗气质,不要写镜头切换或参数。', '- sceneVisualDescription 只写该角色首次登场或主要活动区域的场景画面感,不要写“提示词”字样。', '- 只返回 JSON,不要输出解释或 Markdown。', 'JSON 结构:', '{', ' "playableNpc": {', ' "name": "角色名",', ' "title": "称号",', ' "role": "身份",', ' "description": "一句到两句定位描述",', ' "visualDescription": "角色形象描述",', ' "actionDescription": "动作表现描述",', ' "sceneVisualDescription": "角色关联场景画面描述",', ' "backstory": "背景经历",', ' "personality": "性格特点",', ' "motivation": "当前动机",', ' "combatStyle": "战斗风格",', ' "initialAffinity": 22,', ' "relationshipHooks": ["关系钩子1", "关系钩子2", "关系钩子3"],', ' "tags": ["标签1", "标签2", "标签3"],', ' "publicSummary": "公开背景摘要",', ' "chapterTeasers": ["表层来意", "旧事裂痕", "隐藏执念", "最终底牌"],', ' "chapterContents": ["对应正文1", "对应正文2", "对应正文3", "对应正文4"],', ' "skills": [', ' { "name": "技能1", "summary": "说明", "style": "风格" },', ' { "name": "技能2", "summary": "说明", "style": "风格" },', ' { "name": "技能3", "summary": "说明", "style": "风格" }', ' ],', ' "initialItems": [', ' { "name": "物品1", "category": "武器", "quantity": 1, "rarity": "rare", "description": "说明", "tags": ["标签"] },', ' { "name": "物品2", "category": "专属物品", "quantity": 1, "rarity": "rare", "description": "说明", "tags": ["标签"] },', ' { "name": "物品3", "category": "消耗品", "quantity": 2, "rarity": "uncommon", "description": "说明", "tags": ["标签"] }', ' ]', ' }', '}', ].join('\n'); } export function buildStoryPrompt(profile: ParsedProfile) { return [ `世界名:${profile.name}`, `当前世界设定:${profile.settingText || profile.summary || '未提供额外设定。'}`, `世界摘要:${profile.summary || '未填写'}`, `世界基调:${profile.tone || '未填写'}`, `玩家主线目标:${profile.playerGoal || '未填写'}`, `当前可扮演角色设定:\n${buildRoleReferenceText(profile.playableNpcs, '当前还没有可扮演角色。')}`, `当前场景角色设定:\n${buildRoleReferenceText(profile.storyNpcs, '当前还没有场景角色。')}`, `当前场景设定:\n${buildLandmarkReferenceText(profile)}`, '请基于上面全部上下文,生成 1 名新的“场景角色”。', '要求:', '- 必须与当前世界设定、已有可扮演角色、已有场景角色、已有场景形成互补,不要重复定位。', '- 必须像能直接进入游戏的场景角色,而不是抽象设定条目。', '- 角色应与具体场景、关系链或局势变化发生绑定。', '- visualDescription 只写与角色设定匹配的外形、服装、材质、武器、体态、色彩和识别特征,不要写“提示词”、镜头参数或构图规则。', '- actionDescription 只写这个角色的动作表现与战斗气质,不要写镜头切换或参数。', '- sceneVisualDescription 只写该角色首次登场或主要活动区域的场景画面感,不要写“提示词”字样。', '- 只返回 JSON,不要输出解释或 Markdown。', 'JSON 结构:', '{', ' "storyNpc": {', ' "name": "角色名",', ' "title": "称号",', ' "role": "身份",', ' "description": "一句到两句定位描述",', ' "visualDescription": "角色形象描述",', ' "actionDescription": "动作表现描述",', ' "sceneVisualDescription": "角色关联场景画面描述",', ' "backstory": "背景经历",', ' "personality": "性格特点",', ' "motivation": "当前动机",', ' "combatStyle": "战斗风格",', ' "initialAffinity": 6,', ' "relationshipHooks": ["关系钩子1", "关系钩子2", "关系钩子3"],', ' "tags": ["标签1", "标签2", "标签3"],', ' "publicSummary": "公开背景摘要",', ' "chapterTeasers": ["表层来意", "旧事裂痕", "隐藏执念", "最终底牌"],', ' "chapterContents": ["对应正文1", "对应正文2", "对应正文3", "对应正文4"],', ' "skills": [', ' { "name": "技能1", "summary": "说明", "style": "风格" },', ' { "name": "技能2", "summary": "说明", "style": "风格" },', ' { "name": "技能3", "summary": "说明", "style": "风格" }', ' ],', ' "initialItems": [', ' { "name": "物品1", "category": "武器", "quantity": 1, "rarity": "rare", "description": "说明", "tags": ["标签"] },', ' { "name": "物品2", "category": "专属物品", "quantity": 1, "rarity": "rare", "description": "说明", "tags": ["标签"] },', ' { "name": "物品3", "category": "消耗品", "quantity": 2, "rarity": "uncommon", "description": "说明", "tags": ["标签"] }', ' ]', ' }', '}', ].join('\n'); } export function buildLandmarkPrompt(profile: ParsedProfile) { return [ `世界名:${profile.name}`, `当前世界设定:${profile.settingText || profile.summary || '未提供额外设定。'}`, `世界摘要:${profile.summary || '未填写'}`, `世界基调:${profile.tone || '未填写'}`, `玩家主线目标:${profile.playerGoal || '未填写'}`, `当前可扮演角色设定:\n${buildRoleReferenceText(profile.playableNpcs, '当前还没有可扮演角色。')}`, `当前场景角色设定:\n${buildRoleReferenceText(profile.storyNpcs, '当前还没有场景角色。')}`, `当前场景设定:\n${buildLandmarkReferenceText(profile)}`, '请基于上面全部上下文,生成 1 个新的“场景”。', '要求:', '- 必须与当前世界设定、已有可扮演角色、已有场景角色、已有场景形成互补,不要重复。', '- 必须给出适合出现在这个新场景里的 sceneNpcNames,且只能从已有场景角色里选择至少 3 个名字。', '- 必须给出 connections,且 targetLandmarkName 只能引用已有场景名,不要连向自己。', '- visualDescription 只写这个场景的空间层次、地面、主体建筑或自然景观、氛围、色彩和可见装置,不要写“提示词”、镜头参数或构图规则。', '- 只返回 JSON,不要输出解释或 Markdown。', 'JSON 结构:', '{', ' "landmark": {', ' "name": "场景名",', ' "description": "场景描述",', ' "visualDescription": "场景画面描述",', ' "dangerLevel": "low|medium|high|extreme",', ' "sceneNpcNames": ["场景角色1", "场景角色2", "场景角色3"],', ' "connections": [', ' { "targetLandmarkName": "已有场景名", "relativePosition": "forward", "summary": "通路说明" },', ' { "targetLandmarkName": "已有场景名", "relativePosition": "inside", "summary": "通路说明" }', ' ]', ' }', '}', ].join('\n'); }