(chunk: string, isError?: boolean)
| 277 | |
| 278 | // Create a function to update the assistant's message |
| 279 | const updateAssistantMessage = (chunk: string, isError?: boolean) => { |
| 280 | assistantMessage.content += chunk |
| 281 | |
| 282 | // Start thinking timer on first sight of think tag |
| 283 | if (assistantMessage.content.includes('<think>') && !thinkingStartTime.current) { |
| 284 | thinkingStartTime.current = Date.now() |
| 285 | } |
| 286 | |
| 287 | // Use the new robust COT parsing function |
| 288 | const cotResult = parseCOTContent(assistantMessage.content) |
| 289 | |
| 290 | // Update thinking state |
| 291 | assistantMessage.isThinking = cotResult.isThinking |
| 292 | |
| 293 | // Only calculate time and extract thinking content once when thinking is complete |
| 294 | if (cotResult.hasValidThinkBlock && !thinkingProcessed.current) { |
| 295 | if (thinkingStartTime.current && !assistantMessage.thinkingTime) { |
| 296 | const duration = (Date.now() - thinkingStartTime.current) / 1000 |
| 297 | assistantMessage.thinkingTime = parseFloat(duration.toFixed(2)) |
| 298 | } |
| 299 | thinkingProcessed.current = true |
| 300 | } |
| 301 | |
| 302 | // Update content based on parsing results |
| 303 | assistantMessage.thinkingContent = cotResult.thinkingContent |
| 304 | // Only fallback to full content if not in a thinking state. |
| 305 | if (cotResult.isThinking) { |
| 306 | assistantMessage.displayContent = '' |
| 307 | } else { |
| 308 | assistantMessage.displayContent = cotResult.displayContent || assistantMessage.content |
| 309 | } |
| 310 | |
| 311 | // Detect if the assistant message contains a complete mermaid code block |
| 312 | // Simple heuristic: look for ```mermaid ... ``` |
| 313 | const mermaidBlockRegex = /```mermaid\s+([\s\S]+?)```/g |
| 314 | let mermaidRendered = false |
| 315 | let match |
| 316 | while ((match = mermaidBlockRegex.exec(assistantMessage.content)) !== null) { |
| 317 | // If the block is not too short, consider it complete |
| 318 | if (match[1] && match[1].trim().length > 10) { |
| 319 | mermaidRendered = true |
| 320 | break |
| 321 | } |
| 322 | } |
| 323 | assistantMessage.mermaidRendered = mermaidRendered |
| 324 | |
| 325 | // Detect if the assistant message contains complete LaTeX formulas |
| 326 | const latexRendered = detectLatexCompleteness(assistantMessage.content) |
| 327 | assistantMessage.latexRendered = latexRendered |
| 328 | |
| 329 | // Single unified update to avoid race conditions |
| 330 | setMessages((prev) => { |
| 331 | const newMessages = [...prev] |
| 332 | const lastMessage = newMessages[newMessages.length - 1] |
| 333 | if (lastMessage && lastMessage.id === assistantMessage.id) { |
| 334 | // Update all properties at once to maintain consistency |
| 335 | Object.assign(lastMessage, { |
| 336 | content: assistantMessage.content, |
no test coverage detected