import { StoryHistoryRole, StoryMoment, StoryOption } from '../types'; import { sanitizePromptNarrativeText } from './narrativeLanguage'; const RECENT_ROUND_COUNT = 3; const MAX_SUMMARY_GROUPS = 6; const ACTION_SUMMARY_LIMIT = 24; const RESULT_SUMMARY_LIMIT = 80; type StoryHistoryRound = { actionText: string | null; resultTexts: string[]; }; export interface StoryPromptHistory { recentOriginalRounds: string[]; previousSummary: string | null; } function normalizeWhitespace(text: string) { return text.replace(/\s+/g, ' ').trim(); } function truncateText(text: string, limit: number) { if (text.length <= limit) return text; return `${text.slice(0, Math.max(0, limit - 1)).trimEnd()}...`; } function hasRoundContent(round: StoryHistoryRound) { return Boolean(round.actionText) || round.resultTexts.length > 0; } function resolveHistoryRole(entry: StoryMoment, index: number): StoryHistoryRole { if (entry.historyRole) { return entry.historyRole; } return index % 2 === 0 ? 'action' : 'result'; } function buildStoryRounds(history: StoryMoment[]): StoryHistoryRound[] { const rounds: StoryHistoryRound[] = []; let currentRound: StoryHistoryRound | null = null; for (const [index, entry] of history.entries()) { const historyRole = resolveHistoryRole(entry, index); const text = sanitizePromptNarrativeText( entry.text, historyRole === 'action' ? '玩家做出了新的决定。' : '这一轮的局势已经出现了新的变化。', ); if (!text) { continue; } if (historyRole === 'action') { if (currentRound && hasRoundContent(currentRound)) { rounds.push(currentRound); } currentRound = { actionText: text, resultTexts: [], }; continue; } if (!currentRound) { currentRound = { actionText: null, resultTexts: [], }; } currentRound.resultTexts.push(text); } if (currentRound && hasRoundContent(currentRound)) { rounds.push(currentRound); } return rounds; } function formatRoundOriginal(round: StoryHistoryRound) { const resultText = round.resultTexts.join('\n').trim() || '本轮没有明显的结果文本。'; return [ round.actionText ? `玩家行动:${round.actionText}` : '玩家行动:本轮没有明确的行动文本。', `剧情结果:${resultText}`, ].join('\n'); } function summarizeRound(round: StoryHistoryRound) { const actionText = round.actionText ? normalizeWhitespace(round.actionText) : ''; const resultText = normalizeWhitespace(round.resultTexts.join(' ')); if (actionText && resultText) { return `玩家选择了“${truncateText(actionText, ACTION_SUMMARY_LIMIT)}”,随后${truncateText(resultText, RESULT_SUMMARY_LIMIT)}`; } if (resultText) { return truncateText(resultText, RESULT_SUMMARY_LIMIT); } if (actionText) { return `玩家选择了“${truncateText(actionText, ACTION_SUMMARY_LIMIT)}”。`; } return '这一轮没有留下可用的剧情文本。'; } function _summarizeRoundGroup(rounds: StoryHistoryRound[]) { const firstRound = rounds[0]; if (!firstRound) { return '没有可供总结的剧情记录。'; } if (rounds.length === 1) { return summarizeRound(firstRound); } const secondRound = rounds[1]; if (rounds.length === 2 && secondRound) { return `${summarizeRound(firstRound)} ${summarizeRound(secondRound)}`; } const lastRound = rounds[rounds.length - 1] ?? firstRound; return `${summarizeRound(firstRound)} 之后又推进了${rounds.length - 2}轮相关剧情。${summarizeRound(lastRound)}`; } function summarizeOlderRounds(rounds: StoryHistoryRound[]) { if (rounds.length === 0) { return null; } const groupSize = Math.max(1, Math.ceil(rounds.length / MAX_SUMMARY_GROUPS)); const summaryLines: string[] = []; for (let index = 0; index < rounds.length; index += groupSize) { const group = rounds.slice(index, index + groupSize); summaryLines.push(`- ${group.map(summarizeRound).join(' ')}`); } return summaryLines.join('\n'); } function summarizeOlderRoundsCompact(rounds: StoryHistoryRound[]) { return summarizeOlderRounds(rounds); } export function createHistoryMoment( text: string, historyRole: StoryHistoryRole, options: StoryOption[] = [], ): StoryMoment { return { text, options, historyRole, }; } export function buildStoryPromptHistory(history: StoryMoment[]): StoryPromptHistory { const rounds = buildStoryRounds(history); if (rounds.length === 0) { return { recentOriginalRounds: [], previousSummary: null, }; } const recentRounds = rounds.slice(-RECENT_ROUND_COUNT); const previousRounds = rounds.slice(0, -RECENT_ROUND_COUNT); return { recentOriginalRounds: recentRounds.map(formatRoundOriginal), previousSummary: summarizeOlderRoundsCompact(previousRounds), }; }