xiongzhu 2 lat temu
rodzic
commit
f263faa035
2 zmienionych plików z 74 dodań i 69 usunięć
  1. 5 3
      src/chatapi/chatgpt-api.ts
  2. 69 66
      src/chatapi/fetch-sse.ts

+ 5 - 3
src/chatapi/chatgpt-api.ts

@@ -196,10 +196,11 @@ export class ChatGPTAPI {
             }
 
             if (this._debug) {
-                console.log(`sendMessage (${numTokens} tokens)`, body)
+                console.log(`sendMessage (${numTokens} tokens)`)
             }
 
             if (stream) {
+                let done = false
                 fetchSSE(
                     url,
                     {
@@ -208,8 +209,9 @@ export class ChatGPTAPI {
                         body: JSON.stringify(body),
                         signal: abortSignal,
                         onMessage: (data: string) => {
-                            const json = JSON.parse(data)
-                            if (data === '[DONE]' || json.choices[0]?.finish_reason === 'stop') {
+                            if(done) return
+                            if (data === '[DONE]') {
+                                done = true
                                 result.text = result.text.trim()
                                 return resolve(result)
                             }

+ 69 - 66
src/chatapi/fetch-sse.ts

@@ -5,85 +5,88 @@ import { fetch as globalFetch } from './fetch'
 import { streamAsyncIterable } from './stream-async-iterable'
 
 export async function fetchSSE(
-  url: string,
-  options: Parameters<typeof fetch>[1] & {
-    onMessage: (data: string) => void
-    onError?: (error: any) => void
-  },
-  fetch: types.FetchFn = globalFetch
+    url: string,
+    options: Parameters<typeof fetch>[1] & {
+        onMessage: (data: string) => void
+        onError?: (error: any) => void
+    },
+    fetch: types.FetchFn = globalFetch
 ) {
-  const { onMessage, onError, ...fetchOptions } = options
-  const res = await fetch(url, fetchOptions)
-  if (!res.ok) {
-    let reason: string
+    const { onMessage, onError, ...fetchOptions } = options
+    const res = await fetch(url, fetchOptions)
+    if (!res.ok) {
+        let reason: string
 
-    try {
-      reason = await res.text()
-    } catch (err) {
-      reason = res.statusText
+        try {
+            reason = await res.text()
+        } catch (err) {
+            reason = res.statusText
+        }
+
+        const msg = `ChatGPT error ${res.status}: ${reason}`
+        const error = new types.ChatGPTError(msg, { cause: res })
+        error.statusCode = res.status
+        error.statusText = res.statusText
+        throw error
     }
 
-    const msg = `ChatGPT error ${res.status}: ${reason}`
-    const error = new types.ChatGPTError(msg, { cause: res })
-    error.statusCode = res.status
-    error.statusText = res.statusText
-    throw error
-  }
+    const parser = createParser((event) => {
+        if (event.type === 'event') {
+            onMessage(event.data)
+        }
+    })
 
-  const parser = createParser((event) => {
-    if (event.type === 'event') {
-      onMessage(event.data)
-    }
-  })
+    // handle special response errors
+    const feed = (chunk: string) => {
+        let response = null
 
-  // handle special response errors
-  const feed = (chunk: string) => {
-    let response = null
+        try {
+            response = JSON.parse(chunk)
+        } catch {
+            // ignore
+        }
 
-    try {
-      response = JSON.parse(chunk)
-    } catch {
-      // ignore
-    }
+        if (response?.detail?.type === 'invalid_request_error') {
+            const msg = `ChatGPT error ${response.detail.message}: ${response.detail.code} (${response.detail.type})`
+            const error = new types.ChatGPTError(msg, { cause: response })
+            error.statusCode = response.detail.code
+            error.statusText = response.detail.message
 
-    if (response?.detail?.type === 'invalid_request_error') {
-      const msg = `ChatGPT error ${response.detail.message}: ${response.detail.code} (${response.detail.type})`
-      const error = new types.ChatGPTError(msg, { cause: response })
-      error.statusCode = response.detail.code
-      error.statusText = response.detail.message
+            if (onError) {
+                onError(error)
+            } else {
+                console.error(error)
+            }
 
-      if (onError) {
-        onError(error)
-      } else {
-        console.error(error)
-      }
+            // don't feed to the event parser
+            return
+        }
 
-      // don't feed to the event parser
-      return
+        parser.feed(chunk)
     }
 
-    parser.feed(chunk)
-  }
+    if (!res.body.getReader) {
+        // Vercel polyfills `fetch` with `node-fetch`, which doesn't conform to
+        // web standards, so this is a workaround...
+        const body: NodeJS.ReadableStream = res.body as any
 
-  if (!res.body.getReader) {
-    // Vercel polyfills `fetch` with `node-fetch`, which doesn't conform to
-    // web standards, so this is a workaround...
-    const body: NodeJS.ReadableStream = res.body as any
+        if (!body.on || !body.read) {
+            throw new types.ChatGPTError('unsupported "fetch" implementation')
+        }
 
-    if (!body.on || !body.read) {
-      throw new types.ChatGPTError('unsupported "fetch" implementation')
-    }
-
-    body.on('readable', () => {
-      let chunk: string | Buffer
-      while (null !== (chunk = body.read())) {
-        feed(chunk.toString())
-      }
-    })
-  } else {
-    for await (const chunk of streamAsyncIterable(res.body)) {
-      const str = new TextDecoder().decode(chunk)
-      feed(str)
+        body.on('readable', () => {
+            let chunk: string | Buffer
+            while (null !== (chunk = body.read())) {
+                feed(chunk.toString())
+            }
+        })
+        body.on('end', () => {
+            onMessage('[DONE]')
+        })
+    } else {
+        for await (const chunk of streamAsyncIterable(res.body)) {
+            const str = new TextDecoder().decode(chunk)
+            feed(str)
+        }
     }
-  }
 }