type CustomWorldCreatorInputMode = 'freeform' | 'card'; export interface CreatorFactionSeedRecord { id: string; name: string; publicGoal: string; tension: string; notes: string; locked?: boolean; } export interface CreatorCharacterSeedRecord { id: string; name: string; role: string; publicMask: string; hiddenHook: string; relationToPlayer: string; notes: string; locked?: boolean; } export interface CreatorLandmarkSeedRecord { id: string; name: string; purpose: string; mood: string; secret: string; locked?: boolean; } export interface CustomWorldCreatorIntentRecord { sourceMode: CustomWorldCreatorInputMode; rawSettingText: string; worldHook: string; themeKeywords: string[]; toneDirectives: string[]; playerPremise: string; openingSituation: string; coreConflicts: string[]; keyFactions: CreatorFactionSeedRecord[]; keyCharacters: CreatorCharacterSeedRecord[]; keyLandmarks: CreatorLandmarkSeedRecord[]; iconicElements: string[]; forbiddenDirectives: string[]; } export type ExtractedCreatorIntentPatch = Partial< Pick< CustomWorldCreatorIntentRecord, | 'rawSettingText' | 'worldHook' | 'themeKeywords' | 'toneDirectives' | 'playerPremise' | 'openingSituation' | 'coreConflicts' | 'keyFactions' | 'keyCharacters' | 'keyLandmarks' | 'iconicElements' | 'forbiddenDirectives' > > & { replaceFields?: Array< | 'rawSettingText' | 'worldHook' | 'themeKeywords' | 'toneDirectives' | 'playerPremise' | 'openingSituation' | 'coreConflicts' | 'keyFactions' | 'keyCharacters' | 'keyLandmarks' | 'iconicElements' | 'forbiddenDirectives' >; }; const THEME_LEXICON = [ '武侠', '修仙', '仙侠', '赛博', '蒸汽', '废土', '悬疑', '宫廷', '海岛', '边境', '宗教', '朝堂', '奇谭', '妖异', '科幻', '神秘', '冒险', '克苏鲁', '侦探', ]; const TONE_LEXICON = [ '冷峻', '克制', '压抑', '浪漫', '潮湿', '荒凉', '悬疑', '紧张', '明快', '史诗', '残酷', '诡异', '黑暗', '肃杀', '温柔', '宏大', '宿命', '神秘', ]; const RELATIONSHIP_TERMS = [ '宿敌', '盟友', '导师', '师父', '搭档', '同伴', '恋人', '家人', '兄弟', '姐妹', '父亲', '母亲', '向导', '引路人', '守望者', '巡夜人', ]; const META_MESSAGE_PATTERN = /^(请)?(总结|梳理|归纳|收一下|概括)|继续补充锚点|继续收集锚点/u; function toText(value: unknown) { return typeof value === 'string' ? value.replace(/\s+/gu, ' ').trim() : ''; } function toStringArray(value: unknown, maxCount = 8) { if (!Array.isArray(value)) { return []; } return [...new Set(value.map((item) => toText(item)).filter(Boolean))].slice( 0, maxCount, ); } function slugify(value: string) { const normalized = value .trim() .toLowerCase() .replace(/[^a-z0-9\u4e00-\u9fa5]+/gu, '-') .replace(/^-+|-+$/gu, ''); return normalized || 'entry'; } function createSeedId(prefix: string, label: string, index: number) { return `${prefix}-${slugify(label || `${prefix}-${index + 1}`)}-${index + 1}`; } function clampText(value: string, maxLength: number) { const normalized = value.trim().replace(/\s+/gu, ' '); if (!normalized) { return ''; } if (normalized.length <= maxLength) { return normalized; } return `${normalized.slice(0, Math.max(0, maxLength - 1)).trim()}…`; } function splitSentences(text: string) { return text .split(/[。!?;\n]/u) .map((sentence) => sentence.trim()) .filter(Boolean); } function splitList(text: string, maxCount = 8) { const normalized = text .replace(/[“”"'`]/gu, '') .replace(/^(改成|改为|换成|重设|重新设定|覆盖为|更新为)/u, '') .replace(/^(包括|比如|例如|像是|例如说)/u, '') .replace(/^(是|为|有|偏|走|要|想要)/u, '') .trim(); if (!normalized) { return []; } return [ ...new Set( normalized .split(/[、,,\/|;;]/u) .map((item) => item.trim()) .filter((item) => item.length >= 2 && item.length <= 24), ), ].slice(0, maxCount); } function mergeStringArray( base: string[], patch: string[] | undefined, maxCount: number, ) { if (!patch || patch.length === 0) { return [...base]; } return [ ...new Set([...base, ...patch.map((item) => toText(item)).filter(Boolean)]), ].slice(0, maxCount); } function mergeNarrativeText(base: string, patch: string | undefined) { const nextText = toText(patch); if (!nextText) { return base; } if (!base) { return nextText; } if (base.includes(nextText)) { return base; } return `${base}\n${nextText}`.trim(); } function normalizeCreatorFactionSeed( value: unknown, index: number, ): CreatorFactionSeedRecord | null { if (!value || typeof value !== 'object') { return null; } const item = value as Record; const name = toText(item.name); const publicGoal = toText(item.publicGoal); const tension = toText(item.tension); const notes = toText(item.notes); if (!name && !publicGoal && !tension && !notes) { return null; } return { id: toText(item.id) || createSeedId('creator-faction', name || publicGoal, index), name, publicGoal, tension, notes, locked: Boolean(item.locked), }; } function normalizeCreatorCharacterSeed( value: unknown, index: number, ): CreatorCharacterSeedRecord | null { if (!value || typeof value !== 'object') { return null; } const item = value as Record; const name = toText(item.name); const role = toText(item.role); const publicMask = toText(item.publicMask); const hiddenHook = toText(item.hiddenHook); const relationToPlayer = toText(item.relationToPlayer); const notes = toText(item.notes); if ( !name && !role && !publicMask && !hiddenHook && !relationToPlayer && !notes ) { return null; } return { id: toText(item.id) || createSeedId('creator-character', name || role || publicMask, index), name, role, publicMask, hiddenHook, relationToPlayer, notes, locked: Boolean(item.locked), }; } function normalizeCreatorLandmarkSeed( value: unknown, index: number, ): CreatorLandmarkSeedRecord | null { if (!value || typeof value !== 'object') { return null; } const item = value as Record; const name = toText(item.name); const purpose = toText(item.purpose); const mood = toText(item.mood); const secret = toText(item.secret); if (!name && !purpose && !mood && !secret) { return null; } return { id: toText(item.id) || createSeedId('creator-landmark', name || purpose || mood, index), name, purpose, mood, secret, locked: Boolean(item.locked), }; } function normalizeAnchorArray( value: unknown, normalizer: (value: unknown, index: number) => T | null, maxCount: number, ) { if (!Array.isArray(value)) { return []; } return value .map((item, index) => normalizer(item, index)) .filter((item): item is T => Boolean(item)) .slice(0, maxCount); } function mergeSeedArray( base: T[], patch: T[] | undefined, maxCount: number, mergeEntry: (current: T, next: T) => T, ) { if (!patch || patch.length === 0) { return [...base]; } const nextItems = [...base]; patch.forEach((entry) => { const normalizedName = toText(entry.name); const existingIndex = nextItems.findIndex( (item) => item.id === entry.id || (normalizedName && toText(item.name).toLowerCase() === normalizedName.toLowerCase()), ); if (existingIndex >= 0) { nextItems[existingIndex] = mergeEntry(nextItems[existingIndex], entry); return; } nextItems.push(entry); }); return nextItems.slice(0, maxCount); } function mergeCharacterSeed( current: CreatorCharacterSeedRecord, next: CreatorCharacterSeedRecord, ): CreatorCharacterSeedRecord { return { ...current, ...next, id: next.id || current.id, name: toText(next.name) || current.name, role: toText(next.role) || current.role, publicMask: toText(next.publicMask) || current.publicMask, hiddenHook: toText(next.hiddenHook) || current.hiddenHook, relationToPlayer: toText(next.relationToPlayer) || current.relationToPlayer, notes: toText(next.notes) || current.notes, locked: typeof next.locked === 'boolean' ? next.locked : Boolean(current.locked), }; } function mergeFactionSeed( current: CreatorFactionSeedRecord, next: CreatorFactionSeedRecord, ): CreatorFactionSeedRecord { return { ...current, ...next, id: next.id || current.id, name: toText(next.name) || current.name, publicGoal: toText(next.publicGoal) || current.publicGoal, tension: toText(next.tension) || current.tension, notes: toText(next.notes) || current.notes, locked: typeof next.locked === 'boolean' ? next.locked : Boolean(current.locked), }; } function mergeLandmarkSeed( current: CreatorLandmarkSeedRecord, next: CreatorLandmarkSeedRecord, ): CreatorLandmarkSeedRecord { return { ...current, ...next, id: next.id || current.id, name: toText(next.name) || current.name, purpose: toText(next.purpose) || current.purpose, mood: toText(next.mood) || current.mood, secret: toText(next.secret) || current.secret, locked: typeof next.locked === 'boolean' ? next.locked : Boolean(current.locked), }; } export function createEmptyCreatorIntentRecord( sourceMode: CustomWorldCreatorInputMode = 'freeform', ): CustomWorldCreatorIntentRecord { return { sourceMode, rawSettingText: '', worldHook: '', themeKeywords: [], toneDirectives: [], playerPremise: '', openingSituation: '', coreConflicts: [], keyFactions: [], keyCharacters: [], keyLandmarks: [], iconicElements: [], forbiddenDirectives: [], }; } export function normalizeCreatorIntentRecord( value: unknown, fallbackMode: CustomWorldCreatorInputMode = 'freeform', ): CustomWorldCreatorIntentRecord | null { if (!value || typeof value !== 'object') { return null; } const item = value as Record; const sourceMode = item.sourceMode === 'card' || item.sourceMode === 'freeform' ? item.sourceMode : fallbackMode; const rawSettingText = toText(item.rawSettingText); const worldHook = toText(item.worldHook); const playerPremise = toText(item.playerPremise); const openingSituation = toText(item.openingSituation); const themeKeywords = toStringArray(item.themeKeywords, 8); const toneDirectives = toStringArray(item.toneDirectives, 8); const coreConflicts = toStringArray(item.coreConflicts, 6); const iconicElements = toStringArray(item.iconicElements, 8); const forbiddenDirectives = toStringArray(item.forbiddenDirectives, 8); const keyFactions = normalizeAnchorArray( item.keyFactions, normalizeCreatorFactionSeed, 6, ); const keyCharacters = normalizeAnchorArray( item.keyCharacters, normalizeCreatorCharacterSeed, 8, ); const keyLandmarks = normalizeAnchorArray( item.keyLandmarks, normalizeCreatorLandmarkSeed, 8, ); if ( !rawSettingText && !worldHook && themeKeywords.length === 0 && toneDirectives.length === 0 && !playerPremise && !openingSituation && coreConflicts.length === 0 && keyFactions.length === 0 && keyCharacters.length === 0 && keyLandmarks.length === 0 && iconicElements.length === 0 && forbiddenDirectives.length === 0 ) { return null; } return { sourceMode, rawSettingText, worldHook, themeKeywords, toneDirectives, playerPremise, openingSituation, coreConflicts, keyFactions, keyCharacters, keyLandmarks, iconicElements, forbiddenDirectives, }; } export function mergeCreatorIntentRecord( current: CustomWorldCreatorIntentRecord | null | undefined, patch: ExtractedCreatorIntentPatch | null | undefined, fallbackMode: CustomWorldCreatorInputMode = 'freeform', ) { if (!patch) { return ( normalizeCreatorIntentRecord(current, fallbackMode) ?? createEmptyCreatorIntentRecord(fallbackMode) ); } const base = normalizeCreatorIntentRecord(current, fallbackMode) ?? createEmptyCreatorIntentRecord(fallbackMode); const replaceFields = new Set(patch.replaceFields ?? []); const patchIntent = normalizeCreatorIntentRecord( { sourceMode: base.sourceMode, ...patch, }, base.sourceMode, ) ?? createEmptyCreatorIntentRecord(base.sourceMode); return { ...base, rawSettingText: replaceFields.has('rawSettingText') ? toText(patchIntent.rawSettingText) || base.rawSettingText : mergeNarrativeText(base.rawSettingText, patchIntent.rawSettingText), worldHook: toText(patchIntent.worldHook) || base.worldHook, themeKeywords: replaceFields.has('themeKeywords') ? [...patchIntent.themeKeywords] : mergeStringArray(base.themeKeywords, patchIntent.themeKeywords, 8), toneDirectives: replaceFields.has('toneDirectives') ? [...patchIntent.toneDirectives] : mergeStringArray(base.toneDirectives, patchIntent.toneDirectives, 8), playerPremise: toText(patchIntent.playerPremise) || base.playerPremise, openingSituation: toText(patchIntent.openingSituation) || base.openingSituation, coreConflicts: replaceFields.has('coreConflicts') ? [...patchIntent.coreConflicts] : mergeStringArray(base.coreConflicts, patchIntent.coreConflicts, 6), keyFactions: replaceFields.has('keyFactions') ? [...patchIntent.keyFactions] : mergeSeedArray( base.keyFactions, patchIntent.keyFactions, 6, mergeFactionSeed, ), keyCharacters: replaceFields.has('keyCharacters') ? [...patchIntent.keyCharacters] : mergeSeedArray( base.keyCharacters, patchIntent.keyCharacters, 8, mergeCharacterSeed, ), keyLandmarks: replaceFields.has('keyLandmarks') ? [...patchIntent.keyLandmarks] : mergeSeedArray( base.keyLandmarks, patchIntent.keyLandmarks, 8, mergeLandmarkSeed, ), iconicElements: replaceFields.has('iconicElements') ? [...patchIntent.iconicElements] : mergeStringArray(base.iconicElements, patchIntent.iconicElements, 8), forbiddenDirectives: replaceFields.has('forbiddenDirectives') ? [...patchIntent.forbiddenDirectives] : mergeStringArray( base.forbiddenDirectives, patchIntent.forbiddenDirectives, 8, ), } satisfies CustomWorldCreatorIntentRecord; } export function hasMeaningfulCreatorIntentRecord( intent: CustomWorldCreatorIntentRecord | null | undefined, ) { return Boolean( intent && (intent.rawSettingText || intent.worldHook || intent.themeKeywords.length > 0 || intent.toneDirectives.length > 0 || intent.playerPremise || intent.openingSituation || intent.coreConflicts.length > 0 || intent.keyFactions.length > 0 || intent.keyCharacters.length > 0 || intent.keyLandmarks.length > 0 || intent.iconicElements.length > 0 || intent.forbiddenDirectives.length > 0), ); } function buildAnchorLine(label: string, content: string) { return content ? `${label}:${content}` : ''; } export function buildCreatorIntentDisplayText( intent: CustomWorldCreatorIntentRecord | null | undefined, ) { if (!hasMeaningfulCreatorIntentRecord(intent)) { return ''; } const lines = [ intent?.worldHook ? `世界一句话:${intent.worldHook}` : '', buildAnchorLine('玩家身份', intent?.playerPremise || ''), buildAnchorLine('开局处境', intent?.openingSituation || ''), buildAnchorLine('核心冲突', intent?.coreConflicts.join('、') || ''), buildAnchorLine( '主题气质', [...(intent?.themeKeywords ?? []), ...(intent?.toneDirectives ?? [])] .filter(Boolean) .join('、'), ), buildAnchorLine('标志性要素', intent?.iconicElements.join('、') || ''), ].filter(Boolean); return lines.join('\n'); } export function buildDraftTitleFromIntent( intent: CustomWorldCreatorIntentRecord | null | undefined, ) { return ( clampText(intent?.worldHook || '', 24) || clampText(intent?.rawSettingText || '', 24) || '未命名草稿' ); } export function buildDraftSummaryFromIntent( intent: CustomWorldCreatorIntentRecord | null | undefined, ) { const summary = buildCreatorIntentDisplayText(intent); if (summary) { return clampText(summary.replace(/\n+/gu, ' · '), 180); } return ( clampText(intent?.rawSettingText || '', 180) || '还在收集你的世界锚点。' ); } export function buildAnchorPackFromIntent( intent: CustomWorldCreatorIntentRecord | null | undefined, options: { completedKeys?: string[]; missingKeys?: string[]; } = {}, ) { return { worldSummary: clampText( intent?.worldHook || intent?.rawSettingText || '', 96, ), creatorIntentSummary: clampText(buildDraftSummaryFromIntent(intent), 180), completedKeys: [...(options.completedKeys ?? [])], missingKeys: [...(options.missingKeys ?? [])], keyCharacterAnchors: intent?.keyCharacters.map((entry) => ({ id: entry.id, name: entry.name || '未命名关键人物', summary: clampText( [entry.role, entry.relationToPlayer, entry.hiddenHook] .filter(Boolean) .join(';'), 60, ), })) ?? [], motifDirectives: [ ...(intent?.themeKeywords ?? []), ...(intent?.toneDirectives ?? []), ...(intent?.iconicElements ?? []), ].slice(0, 12), }; } function findSentenceByPattern(text: string, pattern: RegExp) { return splitSentences(text).find((sentence) => pattern.test(sentence)) ?? ''; } function extractAfterCue(text: string, cues: string[]) { const sentences = splitSentences(text); for (const sentence of sentences) { for (const cue of cues) { const index = sentence.indexOf(cue); if (index < 0) { continue; } const candidate = sentence .slice(index + cue.length) .replace(/^[::,,是为偏走要想]+/u, '') .trim(); if (candidate) { return candidate; } } } return ''; } function escapeRegExp(value: string) { return value.replace(/[.*+?^${}()|[\]\\]/gu, '\\$&'); } function isExplicitRewrite(text: string, cues: string[]) { const rewritePattern = /(改成|改为|换成|重设|重新设定|覆盖为|更新为)/u; return cues.some((cue) => { const escapedCue = escapeRegExp(cue); return ( new RegExp( `${escapedCue}[^。!?;\\n]{0,28}${rewritePattern.source}`, 'u', ).test(text) || new RegExp( `${rewritePattern.source}[^。!?;\\n]{0,28}${escapedCue}`, 'u', ).test(text) ); }); } function extractWorldHook(text: string, contextText: string) { const explicit = extractAfterCue(text, ['世界一句话', '核心幻想', '一句话概括', '一句话']) || extractAfterCue(text, ['这个世界', '整体设定', '世界设定']); if (explicit) { return clampText(explicit, 72); } const firstSentence = splitSentences(contextText)[0] ?? ''; if (firstSentence.length >= 8 && !META_MESSAGE_PATTERN.test(firstSentence)) { return clampText(firstSentence, 72); } return ''; } function extractThemeKeywords(text: string) { const explicitSource = extractAfterCue(text, [ '主题关键词', '关键词', '主题', '题材', ]).replace( /(?:,|,).*(气质|风格|基调|氛围|核心冲突|冲突|玩家|开局|不要|避免).*/u, '', ); const explicit = splitList(explicitSource); const inferred = THEME_LEXICON.filter((entry) => text.includes(entry)); return [...new Set([...explicit, ...inferred])].slice(0, 8); } function extractToneDirectives(text: string) { const explicit = splitList( extractAfterCue(text, ['气质', '风格', '基调', '氛围', '风味']), ); const inferred = TONE_LEXICON.filter((entry) => text.includes(entry)); return [...new Set([...explicit, ...inferred])].slice(0, 8); } function extractPlayerPremise(text: string) { const explicit = extractAfterCue(text, [ '玩家是', '玩家身份是', '主角是', '你扮演', '玩家身份', ]) || findSentenceByPattern(text, /(玩家|主角|你扮演|身份|视角)/u); return clampText(explicit, 96); } function extractOpeningSituation(text: string) { const explicit = extractAfterCue(text, [ '开局是', '开局', '故事开场', '开场', '一开始', '起始', ]) || findSentenceByPattern(text, /(开局|开场|一开始|故事开始|起始|初始)/u); return clampText(explicit, 96); } function extractCoreConflicts(text: string) { const explicit = splitList( extractAfterCue(text, ['核心冲突', '冲突', '危机', '主要矛盾']), 6, ); const inferred = splitSentences(text) .filter((sentence) => /(冲突|危机|争夺|战争|对抗|灾变|失衡|威胁|追杀|背叛|悬念)/u.test( sentence, ), ) .map((sentence) => clampText(sentence, 72)); return [...new Set([...explicit, ...inferred])].slice(0, 6); } function extractForbiddenDirectives(text: string) { return splitSentences(text) .filter((sentence) => /(不要|避免|禁止|不能|别出现)/u.test(sentence)) .map((sentence) => clampText( sentence.replace(/^(不要|避免|禁止|不能|别出现)/u, '').trim() || sentence, 48, ), ) .filter(Boolean) .slice(0, 8); } function extractIconicElements(text: string) { const explicit = splitList( extractAfterCue(text, [ '标志性元素', '标志性要素', '标志元素', '视觉符号', '核心意象', '一眼能认出来的设定', ]), ); return explicit.slice(0, 8); } function extractCharacterName(sentence: string) { const matchers = [ /叫([A-Za-z0-9\u4e00-\u9fa5·-]{2,12})/u, /名为([A-Za-z0-9\u4e00-\u9fa5·-]{2,12})/u, /([A-Za-z0-9\u4e00-\u9fa5·-]{2,12})(?:是|作为|担任)/u, ]; for (const matcher of matchers) { const matched = sentence.match(matcher); const candidate = toText(matched?.[1]); if ( candidate && !['玩家', '主角', '世界', '故事', '开局', '气质'].includes(candidate) ) { return candidate; } } return ''; } function extractRelationToPlayer(sentence: string) { const explicit = sentence.match( /(与玩家[^,。;]+|和玩家[^,。;]+|对玩家[^,。;]+|主角的[^,。;]+)/u, ); if (explicit?.[1]) { return clampText(explicit[1], 48); } const relationKeyword = RELATIONSHIP_TERMS.find((entry) => sentence.includes(entry), ); return relationKeyword ?? ''; } function extractHiddenHook(sentence: string) { const explicit = sentence.match( /(其实[^,。;]+|暗地里[^,。;]+|暗线是[^,。;]+|秘密是[^,。;]+|真实身份[^,。;]+|真正目的[^,。;]+)/u, ); return clampText(toText(explicit?.[1]), 64); } function extractRole(sentence: string, name: string) { if (!name) { return ''; } const escapedName = name.replace(/[.*+?^${}()|[\]\\]/gu, '\\$&'); const matcher = new RegExp(`${escapedName}(?:是|作为|担任)([^,。;]+)`, 'u'); const matched = sentence.match(matcher); return clampText(toText(matched?.[1]), 48); } function extractCharacterSeeds(text: string) { const candidateSentences = splitSentences(text).filter((sentence) => /(关键人物|关键角色|人物|角色|宿敌|盟友|导师|搭档|同伴|恋人|家人|与玩家|对玩家)/u.test( sentence, ), ); return candidateSentences .map((sentence, index) => { const name = extractCharacterName(sentence); const relationToPlayer = extractRelationToPlayer(sentence); const hiddenHook = extractHiddenHook(sentence); const role = extractRole(sentence, name); if (!name && !role && !relationToPlayer && !hiddenHook) { return null; } return { id: createSeedId( 'creator-character', name || role || hiddenHook, index, ), name, role, publicMask: '', hiddenHook, relationToPlayer, notes: '', } satisfies CreatorCharacterSeedRecord; }) .filter((entry): entry is CreatorCharacterSeedRecord => Boolean(entry)) .slice(0, 3); } function shouldAppendRawSettingText(text: string) { return text.length >= 8 && !META_MESSAGE_PATTERN.test(text); } export function extractCreatorIntentPatch(params: { currentIntent: CustomWorldCreatorIntentRecord | null | undefined; latestUserMessage: string; recentMessages?: string[]; }) { const currentIntent = normalizeCreatorIntentRecord(params.currentIntent) ?? createEmptyCreatorIntentRecord('freeform'); const latestUserMessage = toText(params.latestUserMessage); const recentMessages = (params.recentMessages ?? []) .map((entry) => toText(entry)) .filter(Boolean) .slice(-10); const contextText = [...recentMessages, latestUserMessage].join('\n'); if (!latestUserMessage) { return {} satisfies ExtractedCreatorIntentPatch; } const patch: ExtractedCreatorIntentPatch = {}; const markReplace = ( field: NonNullable[number], ) => { patch.replaceFields = [...new Set([...(patch.replaceFields ?? []), field])]; }; if (shouldAppendRawSettingText(latestUserMessage)) { patch.rawSettingText = latestUserMessage; } const worldHook = extractWorldHook( latestUserMessage, currentIntent.worldHook ? latestUserMessage : contextText, ); if (worldHook) { patch.worldHook = worldHook; if ( isExplicitRewrite(latestUserMessage, [ '世界一句话', '核心幻想', '一句话概括', '这个世界', '世界设定', ]) ) { markReplace('worldHook'); } } const themeKeywords = extractThemeKeywords(latestUserMessage); if (themeKeywords.length > 0) { patch.themeKeywords = themeKeywords; if ( isExplicitRewrite(latestUserMessage, [ '主题关键词', '关键词', '主题', '题材', ]) ) { markReplace('themeKeywords'); } } const toneDirectives = extractToneDirectives(latestUserMessage); if (toneDirectives.length > 0) { patch.toneDirectives = toneDirectives; if ( isExplicitRewrite(latestUserMessage, ['气质', '风格', '基调', '氛围']) ) { markReplace('toneDirectives'); } } const playerPremise = extractPlayerPremise(latestUserMessage); if (playerPremise) { patch.playerPremise = playerPremise; if ( isExplicitRewrite(latestUserMessage, ['玩家', '玩家身份', '主角', '身份']) ) { markReplace('playerPremise'); } } const openingSituation = extractOpeningSituation(latestUserMessage); if (openingSituation) { patch.openingSituation = openingSituation; if ( isExplicitRewrite(latestUserMessage, ['开局', '故事开场', '开场', '起始']) ) { markReplace('openingSituation'); } } const coreConflicts = extractCoreConflicts(latestUserMessage); if (coreConflicts.length > 0) { patch.coreConflicts = coreConflicts; if ( isExplicitRewrite(latestUserMessage, [ '核心冲突', '冲突', '危机', '主要矛盾', ]) ) { markReplace('coreConflicts'); } } const keyCharacters = extractCharacterSeeds(latestUserMessage); if (keyCharacters.length > 0) { patch.keyCharacters = keyCharacters; if ( isExplicitRewrite(latestUserMessage, [ '关键人物', '关键角色', '人物', '角色', ]) ) { markReplace('keyCharacters'); } } const iconicElements = extractIconicElements(latestUserMessage); if (iconicElements.length > 0) { patch.iconicElements = iconicElements; if ( isExplicitRewrite(latestUserMessage, [ '标志性元素', '标志性要素', '标志元素', '视觉符号', '核心意象', ]) ) { markReplace('iconicElements'); } } const forbiddenDirectives = extractForbiddenDirectives(latestUserMessage); if (forbiddenDirectives.length > 0) { patch.forbiddenDirectives = forbiddenDirectives; if ( isExplicitRewrite(latestUserMessage, ['禁忌', '禁止事项', '不要', '避免']) ) { markReplace('forbiddenDirectives'); } } return patch; }