( aiMessageId: string, setMessages: SetMessagesFn, flushIntervalMs: number = DEFAULT_FLUSH_INTERVAL_MS, )
| 122 | |
| 123 | /** Batched updater that queues updates and flushes at a regular interval. */ |
| 124 | export const createBatchedMessageUpdater = ( |
| 125 | aiMessageId: string, |
| 126 | setMessages: SetMessagesFn, |
| 127 | flushIntervalMs: number = DEFAULT_FLUSH_INTERVAL_MS, |
| 128 | ): BatchedMessageUpdater => { |
| 129 | // Queue of message updater functions to be applied on next flush |
| 130 | const pendingUpdaters: Array<(msg: ChatMessage) => ChatMessage> = [] |
| 131 | let intervalId: ReturnType<typeof setInterval> | null = null |
| 132 | let isDisposed = false |
| 133 | |
| 134 | const flush = () => { |
| 135 | if (pendingUpdaters.length === 0) return |
| 136 | |
| 137 | // Capture and clear the queue atomically |
| 138 | const updaters = pendingUpdaters.splice(0, pendingUpdaters.length) |
| 139 | |
| 140 | // Compose all pending updaters into a single transform |
| 141 | const composedUpdater = (msg: ChatMessage): ChatMessage => { |
| 142 | return updaters.reduce((m, fn) => fn(m), msg) |
| 143 | } |
| 144 | |
| 145 | // Apply composed update to the target message |
| 146 | setMessages((prev) => |
| 147 | prev.map((msg) => (msg.id === aiMessageId ? composedUpdater(msg) : msg)), |
| 148 | ) |
| 149 | } |
| 150 | |
| 151 | const dispose = () => { |
| 152 | if (isDisposed) return |
| 153 | // Flush any pending updates before disposing to prevent data loss |
| 154 | flush() |
| 155 | isDisposed = true |
| 156 | if (intervalId !== null) { |
| 157 | clearInterval(intervalId) |
| 158 | intervalId = null |
| 159 | } |
| 160 | } |
| 161 | |
| 162 | // Start the periodic flush interval |
| 163 | intervalId = setInterval(flush, flushIntervalMs) |
| 164 | |
| 165 | const updateAiMessage = (updater: (msg: ChatMessage) => ChatMessage) => { |
| 166 | if (isDisposed) { |
| 167 | // If disposed, apply immediately as fallback |
| 168 | setMessages((prev) => |
| 169 | prev.map((msg) => (msg.id === aiMessageId ? updater(msg) : msg)), |
| 170 | ) |
| 171 | return |
| 172 | } |
| 173 | pendingUpdaters.push(updater) |
| 174 | } |
| 175 | |
| 176 | const updateAiMessageBlocks = ( |
| 177 | blockUpdater: (blocks: ContentBlock[]) => ContentBlock[], |
| 178 | ) => { |
| 179 | updateAiMessage((msg) => ({ |
| 180 | ...msg, |
| 181 | blocks: blockUpdater(msg.blocks ?? []), |
no outgoing calls