fix: preserve intermediate text between tool calls, restore thinking blocks on refresh

Two bugs fixed:

1. Intermediate assistant text (e.g. "项目已经有 node_modules,直接启动") was
   lost between tool calls because content_start didn't flush streamingText.
   Now we flush accumulated text as assistant_text before switching to the
   next content block.

2. History reload (page refresh) now restores thinking blocks alongside
   tool_use, tool_result, and text blocks. Previously thinking blocks were
   intentionally dropped — now they're preserved for full conversation replay.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
程序员阿江(Relakkes) 2026-04-06 23:01:37 +08:00
parent 83559ce7be
commit 5e71da88c3
2 changed files with 28 additions and 4 deletions

View File

@ -57,7 +57,7 @@ describe('chatStore history mapping', () => {
})
})
it('drops persisted thinking blocks when restoring transcript history', () => {
it('preserves thinking blocks when restoring transcript history', () => {
const messages: MessageEntry[] = [
{
id: 'assistant-1',
@ -83,11 +83,11 @@ describe('chatStore history mapping', () => {
const mapped = mapHistoryMessagesToUiMessages(messages)
expect(mapped.map((message) => message.type)).toEqual([
'thinking',
'assistant_text',
'tool_use',
'tool_result',
])
expect(mapped.some((message) => message.type === 'thinking')).toBe(false)
})
it('sends permission mode updates to the active session only', () => {

View File

@ -202,7 +202,23 @@ export const useChatStore = create<ChatStore>((set, get) => ({
}
break
case 'content_start':
case 'content_start': {
// Flush any accumulated streamingText as assistant_text BEFORE
// switching to the next block. This preserves intermediate text
// segments between tool calls (e.g. "项目已经有 node_modules直接启动").
const pendingText = get().streamingText.trim()
if (pendingText) {
set((s) => ({
messages: [...s.messages, {
id: nextId(),
type: 'assistant_text',
content: pendingText,
timestamp: Date.now(),
}],
streamingText: '',
}))
}
if (msg.blockType === 'text') {
set({ streamingText: '', chatState: 'streaming', activeThinkingId: null })
} else if (msg.blockType === 'tool_use') {
@ -215,6 +231,7 @@ export const useChatStore = create<ChatStore>((set, get) => ({
})
}
break
}
case 'content_delta':
if (msg.text !== undefined) {
@ -408,7 +425,14 @@ export function mapHistoryMessagesToUiMessages(messages: MessageEntry[]): UIMess
if (msg.type === 'assistant' && Array.isArray(msg.content)) {
for (const block of msg.content as AssistantHistoryBlock[]) {
if (block.type === 'text' && block.text) {
if (block.type === 'thinking' && block.thinking) {
uiMessages.push({
id: nextId(),
type: 'thinking',
content: block.thinking,
timestamp,
})
} else if (block.type === 'text' && block.text) {
uiMessages.push({
id: nextId(),
type: 'assistant_text',