fix(chat): prevent streaming text from appearing in bursts after citations#7745
fix(chat): prevent streaming text from appearing in bursts after citations#7745
Conversation
Greptile OverviewGreptile SummaryFixes streaming regression by introducing a Key changes:
Technical approach: Instead of creating new array copies on each update (memory overhead), uses an integer version counter (primitive value) that gets copied during shallow cloning, triggering proper React re-renders. Confidence Score: 4/5
Important Files Changed
Sequence DiagramsequenceDiagram
participant Stream as Backend Stream
participant Hook as useChatController
participant Tree as MessageTree
participant List as MessageList
participant AI as AIMessage (React.memo)
Stream->>Hook: packet arrives
Hook->>Hook: packets.push(packet)
Hook->>Hook: packetsVersion++
Hook->>Tree: upsertToCompleteMessageTree({packets, packetsVersion})
Tree->>Tree: shallow copy message ({...msg})
Note over Tree: packetsVersion copied (primitive)<br/>packets ref copied (same array)
Tree->>List: messageTree updated
List->>AI: render with rawPackets, packetsVersion
AI->>AI: arePropsEqual(prev, next)
Note over AI: prev.packetsVersion !== next.packetsVersion<br/>returns false → re-render!
AI->>AI: process new packets from lastProcessedIndex
|
0af3591 to
9b69c36
Compare
The AIMessage component uses React.memo with a custom arePropsEqual that compared rawPackets.length. However, the packets array was being mutated in place with .push() and the same reference was passed to the store on each update. This caused prev.rawPackets and next.rawPackets to be the same object, making the length comparison always return true and preventing re-renders during streaming. Fix: Add a packetsVersion counter that increments with each packet. The memo comparison now checks this version instead of array length. This approach was chosen over creating a new array ([...packets]) on each update because it's O(1) vs O(n) per packet - significant for long responses with hundreds of packets.
9b69c36 to
f5ce38d
Compare
|
@greptile |
Additional Comments (1)
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time! Prompt To Fix With AIThis is a comment left during a code review.
Path: web/src/app/chat/shared/[chatId]/SharedChatDisplay.tsx
Line: 98:98
Comment:
also pass `packetsVersion={message.packetsVersion}` here for consistency with `MessageList.tsx`
```suggestion
rawPackets={message.packets}
packetsVersion={message.packetsVersion}
```
<sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub>
How can I resolve this? If you propose a fix, please make it concise. |
|
Failing exa test is unrelated. going to merge |
Description
Fixes a regression where AI response text appeared in large bursts instead of streaming smoothly.
Root Cause
The
AIMessagecomponent usesReact.memowith a customarePropsEqualfunction that comparedprev.rawPackets.length === next.rawPackets.length. However, the packets array was being mutated in place with.push()and the same array reference was passed to the store on each update.This violated React's immutability expectation:
prev.rawPacketsandnext.rawPacketspointed to the same object, so the length comparison always returnedtrue(comparing identical objects), preventing re-renders during streaming.Solution
Added a
packetsVersioncounter that increments with each new packet. The memo comparison now checksprev.packetsVersion === next.packetsVersioninstead of comparing array lengths.Why Version Counter Over Array Copying?
An alternative fix would be to create a new array on each update:
packets: [...packets]. However:[...packets]packetsVersion++For a response with 500 packets, array copying would perform ~125,000 copy operations total, while the version counter approach performs 500 increments. This matters for long AI responses.
How Has This Been Tested?
Additional Options