diff --git a/apps/ai-game-creator-shell/src/features/project-workspace/directTurnPresentation.ts b/apps/ai-game-creator-shell/src/features/project-workspace/directTurnPresentation.ts index 20e17ca0e..a481c03ed 100644 --- a/apps/ai-game-creator-shell/src/features/project-workspace/directTurnPresentation.ts +++ b/apps/ai-game-creator-shell/src/features/project-workspace/directTurnPresentation.ts @@ -149,17 +149,30 @@ export function buildDirectChatTurns({ }): DirectChatTurn[] { const turns: DirectChatTurnEntries[] = []; let current: DirectChatTurnEntries | null = null; + // 分页切片的开头可能落在半截回合里(那一条用户条目还在更早的一屏):这些前导条目先攒着, + // 交给后面第一个用户条目开的回合,避免渲染出一个没有用户气泡的孤儿回合。 + const leadingEntries: DirectChatEntry[] = []; for (const entry of entries) { if (entry.kind === 'message' && entry.role === 'user') { current = newTurn(entry.itemId); turns.push(current); + if (leadingEntries.length > 0) { + current.entries.push(...leadingEntries); + leadingEntries.length = 0; + } } if (!current) { - current = newTurn(`history:${turns.length}`); - turns.push(current); + leadingEntries.push(entry); + continue; } current.entries.push(entry); } + if (!current && leadingEntries.length > 0) { + // 整份历史都没有用户条目:仍然保留一个回合承载正文。 + current = newTurn(`history:${turns.length}`); + current.entries.push(...leadingEntries); + turns.push(current); + } const entryIds = new Set(entries.map((entry) => entry.itemId)); localMessages.forEach((message, index) => { diff --git a/apps/ai-game-creator-shell/tests/directTurnPresentation.test.ts b/apps/ai-game-creator-shell/tests/directTurnPresentation.test.ts index a887f02c2..809041ff9 100644 --- a/apps/ai-game-creator-shell/tests/directTurnPresentation.test.ts +++ b/apps/ai-game-creator-shell/tests/directTurnPresentation.test.ts @@ -202,6 +202,27 @@ describe('DirectProject 聊天分区', () => { expect(turns[0]?.finals[0]).toMatchObject({ text: '只有回复' }); }); + it('分页切片开头落在半截回合里时并进后面那个回合,不生成没有用户气泡的孤儿回合', () => { + // 更早的一屏把上一条用户条目切在外面:这三条前导条目属于上一轮,但不能自成回合。 + const turns = buildDirectChatTurns({ + entries: [ + reasoningEntry('r-prev'), + toolEntry('t-prev'), + assistantEntry('a-prev', '上一轮的答复', 1_800_000_000_900), + userEntry('u2', 1_800_000_010_000), + assistantEntry('a2', '这一轮的答复', 1_800_000_011_000), + ], + }); + expect(turns.map((turn) => turn.key)).toEqual(['u2']); + expect(turns[0]?.users.map((block) => block.key)).toEqual(['u2:u2']); + expect(turns[0]?.process.map((block) => block.key)).toEqual([ + 'u2:r-prev', + 'u2:t-prev', + 'u2:a-prev', + ]); + expect(turns[0]?.finals.map((block) => block.key)).toEqual(['u2:a2']); + }); + it('条目转消息只保留用户 / 助手正文,并按身份去重', () => { const messages = directChatEntriesToMessages([ userEntry('u1'),