handle abort properly + finally clause to resolve (#6227)

This commit is contained in:
Dinh Long Nguyen 2025-08-19 14:45:57 +07:00 committed by GitHub
parent 55390de070
commit 9ea9b7d87d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -309,7 +309,7 @@ export const useChat = () => {
let pendingDeltaCount = 0 let pendingDeltaCount = 0
const reasoningProcessor = new ReasoningProcessor() const reasoningProcessor = new ReasoningProcessor()
const scheduleFlush = () => { const scheduleFlush = () => {
if (rafScheduled) return if (rafScheduled || abortController.signal.aborted) return
rafScheduled = true rafScheduled = true
const doSchedule = (cb: () => void) => { const doSchedule = (cb: () => void) => {
if (typeof requestAnimationFrame !== 'undefined') { if (typeof requestAnimationFrame !== 'undefined') {
@ -321,6 +321,12 @@ export const useChat = () => {
} }
} }
doSchedule(() => { doSchedule(() => {
// Check abort status before executing the scheduled callback
if (abortController.signal.aborted) {
rafScheduled = false
return
}
const currentContent = newAssistantThreadContent( const currentContent = newAssistantThreadContent(
activeThread.id, activeThread.id,
accumulatedText, accumulatedText,
@ -367,41 +373,63 @@ export const useChat = () => {
pendingDeltaCount = 0 pendingDeltaCount = 0
rafScheduled = false rafScheduled = false
} }
for await (const part of completion) { try {
// Error message for await (const part of completion) {
if (!part.choices) { // Check if aborted before processing each part
throw new Error( if (abortController.signal.aborted) {
'message' in part break
? (part.message as string) }
: (JSON.stringify(part) ?? '')
) // Error message
if (!part.choices) {
throw new Error(
'message' in part
? (part.message as string)
: (JSON.stringify(part) ?? '')
)
}
if (part.choices[0]?.delta?.tool_calls) {
extractToolCall(part, currentCall, toolCalls)
// Schedule a flush to reflect tool update
scheduleFlush()
}
const deltaReasoning =
reasoningProcessor.processReasoningChunk(part)
if (deltaReasoning) {
accumulatedText += deltaReasoning
pendingDeltaCount += 1
// Schedule flush for reasoning updates
scheduleFlush()
}
const deltaContent = part.choices[0]?.delta?.content || ''
if (deltaContent) {
accumulatedText += deltaContent
pendingDeltaCount += 1
// Batch UI update on next animation frame
scheduleFlush()
}
}
} finally {
// Always clean up scheduled RAF when stream ends (either normally or via abort)
if (rafHandle !== undefined) {
if (typeof cancelAnimationFrame !== 'undefined') {
cancelAnimationFrame(rafHandle)
} else {
clearTimeout(rafHandle)
}
rafHandle = undefined
rafScheduled = false
} }
if (part.choices[0]?.delta?.tool_calls) { // Only finalize and flush if not aborted
extractToolCall(part, currentCall, toolCalls) if (!abortController.signal.aborted) {
// Schedule a flush to reflect tool update // Finalize reasoning (close any open think tags)
scheduleFlush() accumulatedText += reasoningProcessor.finalize()
} // Ensure any pending buffered content is rendered at the end
const deltaReasoning = flushIfPending()
reasoningProcessor.processReasoningChunk(part)
if (deltaReasoning) {
accumulatedText += deltaReasoning
pendingDeltaCount += 1
// Schedule flush for reasoning updates
scheduleFlush()
}
const deltaContent = part.choices[0]?.delta?.content || ''
if (deltaContent) {
accumulatedText += deltaContent
pendingDeltaCount += 1
// Batch UI update on next animation frame
scheduleFlush()
} }
} }
// Finalize reasoning (close any open think tags)
accumulatedText += reasoningProcessor.finalize()
// Ensure any pending buffered content is rendered at the end
flushIfPending()
} }
} catch (error) { } catch (error) {
const errorMessage = const errorMessage =