1
1
/* eslint-disable react-hooks/rules-of-hooks */
2
2
3
- import { useState } from 'react ' ;
4
-
3
+ import { isAbortError } from '@ai-sdk/provider-utils ' ;
4
+ import { useCallback , useRef , useState } from 'react' ;
5
5
import { generateId } from '../shared/generate-id' ;
6
6
import { readDataStream } from '../shared/read-data-stream' ;
7
7
import { CreateMessage , Message } from '../shared/types' ;
8
+ import { abort } from 'node:process' ;
8
9
9
10
export type AssistantStatus = 'in_progress' | 'awaiting_message' ;
10
11
@@ -42,6 +43,11 @@ export type UseAssistantHelpers = {
42
43
} ,
43
44
) => Promise < void > ;
44
45
46
+ /**
47
+ Abort the current request immediately, keep the generated tokens if any.
48
+ */
49
+ stop : ( ) => void ;
50
+
45
51
/**
46
52
* setState-powered method to update the input value.
47
53
*/
@@ -135,6 +141,16 @@ export function useAssistant({
135
141
setInput ( event . target . value ) ;
136
142
} ;
137
143
144
+ // Abort controller to cancel the current API call.
145
+ const abortControllerRef = useRef < AbortController | null > ( null ) ;
146
+
147
+ const stop = useCallback ( ( ) => {
148
+ if ( abortControllerRef . current ) {
149
+ abortControllerRef . current . abort ( ) ;
150
+ abortControllerRef . current = null ;
151
+ }
152
+ } , [ ] ) ;
153
+
138
154
const append = async (
139
155
message : Message | CreateMessage ,
140
156
requestOptions ?: {
@@ -153,10 +169,15 @@ export function useAssistant({
153
169
154
170
setInput ( '' ) ;
155
171
172
+ const abortController = new AbortController ( ) ;
173
+
156
174
try {
175
+ abortControllerRef . current = abortController ;
176
+
157
177
const result = await fetch ( api , {
158
178
method : 'POST' ,
159
179
credentials,
180
+ signal : abortController . signal ,
160
181
headers : { 'Content-Type' : 'application/json' , ...headers } ,
161
182
body : JSON . stringify ( {
162
183
...body ,
@@ -240,14 +261,21 @@ export function useAssistant({
240
261
}
241
262
}
242
263
} catch ( error ) {
264
+ // Ignore abort errors as they are expected when the user cancels the request:
265
+ if ( isAbortError ( error ) && abortController . signal . aborted ) {
266
+ abortControllerRef . current = null ;
267
+ return ;
268
+ }
269
+
243
270
if ( onError && error instanceof Error ) {
244
271
onError ( error ) ;
245
272
}
246
273
247
274
setError ( error as Error ) ;
275
+ } finally {
276
+ abortControllerRef . current = null ;
277
+ setStatus ( 'awaiting_message' ) ;
248
278
}
249
-
250
- setStatus ( 'awaiting_message' ) ;
251
279
} ;
252
280
253
281
const submitMessage = async (
@@ -276,6 +304,7 @@ export function useAssistant({
276
304
submitMessage,
277
305
status,
278
306
error,
307
+ stop,
279
308
} ;
280
309
}
281
310
0 commit comments