@@ -29,6 +29,7 @@ import {
2929 DropdownMenuTrigger ,
3030} from ' @/components/ui/dropdown-menu'
3131import { Textarea } from ' @/components/ui/textarea'
32+ import { buildAIHeaders , resolveEndpointUrl , useAIFetch } from ' @/composables/useAIFetch'
3233import useAIConfigStore from ' @/stores/aiConfig'
3334import { useEditorStore } from ' @/stores/editor'
3435import { useQuickCommands } from ' @/stores/quickCommands'
@@ -57,8 +58,7 @@ const inputHistory = ref<string[]>([])
5758const historyIndex = ref <number | null >(null )
5859
5960const configVisible = ref (false )
60- const loading = ref (false )
61- const fetchController = ref <AbortController | null >(null )
61+ const { loading, abort : abortFetch, fetchSSE } = useAIFetch ()
6262const copiedIndex = ref <number | null >(null )
6363const insertedIndex = ref <number | null >(null )
6464const memoryKey = ` ai_memory_context `
@@ -273,10 +273,7 @@ function insertToDocument(text: string, index: number) {
273273}
274274
275275async function resetMessages() {
276- if (fetchController .value ) {
277- fetchController .value .abort ()
278- fetchController .value = null
279- }
276+ abortFetch ()
280277
281278 if (currentConversationId .value ) {
282279 conversationList .value = conversationList .value .filter (c => c .id !== currentConversationId .value )
@@ -292,11 +289,7 @@ async function resetMessages() {
292289}
293290
294291function pauseStreaming() {
295- if (fetchController .value ) {
296- fetchController .value .abort ()
297- fetchController .value = null
298- }
299- loading .value = false
292+ abortFetch ()
300293 const last = messages .value [messages .value .length - 1 ]
301294 if (last ?.role === ` assistant ` )
302295 last .done = true
@@ -386,78 +379,42 @@ async function streamResponse(replyMessageProxy: ChatMessage) {
386379 max_tokens: maxToken .value ,
387380 stream: true ,
388381 }
389- const headers: Record <string , string > = { ' Content-Type' : ` application/json ` }
390- if (apiKey .value && type .value !== ` default ` )
391- headers .Authorization = ` Bearer ${apiKey .value } `
392-
393- fetchController .value = new AbortController ()
394- const signal = fetchController .value .signal
382+ const headers = buildAIHeaders (apiKey .value , type .value )
383+ const url = resolveEndpointUrl (endpoint .value , ` chat ` )
395384
396385 try {
397- const url = new URL (endpoint .value )
398- if (! url .pathname .endsWith (` /chat/completions ` ))
399- url .pathname = url .pathname .replace (/ \/ ? $ / , ` /chat/completions ` )
400-
401- const res = await window .fetch (url .toString (), {
402- method: ` POST ` ,
403- headers ,
404- body: JSON .stringify (payload ),
405- signal ,
406- })
407- if (! res .ok || ! res .body )
408- throw new Error (` 响应错误:${res .status } ${res .statusText } ` )
409-
410- const reader = res .body .getReader ()
411- const decoder = new TextDecoder (` utf-8 ` )
412- let buffer = ` `
413-
414- while (true ) {
415- const { value, done } = await reader .read ()
416- if (done ) {
386+ await fetchSSE (url , headers , payload , {
387+ onDelta(content ) {
388+ const last = messages .value [messages .value .length - 1 ]
389+ if (last !== replyMessageProxy )
390+ return
391+ last .content += content
392+ scrollToBottom ()
393+ },
394+ onReasoningDelta(reasoning ) {
395+ const last = messages .value [messages .value .length - 1 ]
396+ if (last !== replyMessageProxy )
397+ return
398+ last .reasoning = (last .reasoning || ` ` ) + reasoning
399+ scrollToBottom ()
400+ },
401+ onDone() {
417402 const last = messages .value [messages .value .length - 1 ]
418403 if (last .role === ` assistant ` ) {
419404 last .done = true
420- await scrollToBottom (true )
421- }
422- break
423- }
424-
425- buffer += decoder .decode (value , { stream: true })
426- const lines = buffer .split (` \n ` )
427- buffer = lines .pop () || ` `
428-
429- for (const line of lines ) {
430- if (! line .trim () || line .trim () === ` data: [DONE] ` )
431- continue
432- try {
433- const json = JSON .parse (line .replace (/ ^ data: / , ` ` ))
434- const delta = json .choices ?.[0 ]?.delta || {}
435- const last = messages .value [messages .value .length - 1 ]
436- if (last !== replyMessageProxy )
437- return
438- if (delta .content )
439- last .content += delta .content
440- else if (delta .reasoning_content )
441- last .reasoning = (last .reasoning || ` ` ) + delta .reasoning_content
442- await scrollToBottom ()
443- }
444- catch {
405+ scrollToBottom (true )
445406 }
446- }
447- }
407+ },
408+ })
448409 }
449410 catch (e ) {
450- if ((e as Error ).name !== ` AbortError ` ) {
451- messages .value [messages .value .length - 1 ].content
452- = ` ❌ 请求失败: ${(e as Error ).message } `
453- }
411+ messages .value [messages .value .length - 1 ].content
412+ = ` ❌ 请求失败: ${(e as Error ).message } `
454413 await scrollToBottom (true )
455414 }
456415 finally {
457416 await store .setJSON (memoryKey , messages .value )
458417 await autoSaveCurrentConversation ()
459- loading .value = false
460- fetchController .value = null
461418 }
462419}
463420
0 commit comments