UNPKG

@lobehub/chat

Version:

Lobe Chat - an open-source, high-performance chatbot framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.

111 lines (96 loc) 3.38 kB
/** * file copy from https://github.com/Azure/fetch-event-source/blob/45ac3cfffd30b05b79fbf95c21e67d4ef59aa56a/src/fetch.ts * and remove some code */ import { EventSourceMessage, getBytes, getLines, getMessages } from './parse'; export const EventStreamContentType = 'text/event-stream'; const LastEventId = 'last-event-id'; // eslint-disable-next-line no-undef export interface FetchEventSourceInit extends RequestInit { /** The Fetch function to use. Defaults to window.fetch */ fetch?: typeof fetch; /** * The request headers. FetchEventSource only supports the Record<string,string> format. */ headers?: Record<string, string>; /** * Called when a response finishes. If you don't expect the server to kill * the connection, you can throw an exception here and retry using onerror. */ onclose?: () => void; /** * Called when there is any error making the request / processing messages / * handling callbacks etc. Use this to control the retry strategy: if the * error is fatal, rethrow the error inside the callback to stop the entire * operation. Otherwise, you can return an interval (in milliseconds) after * which the request will automatically retry (with the last-event-id). * If this callback is not specified, or it returns undefined, fetchEventSource * will treat every error as retriable and will try again after 1 second. */ onerror?: (err: any) => number | null | undefined | void; /** * Called when a message is received. NOTE: Unlike the default browser * EventSource.onmessage, this callback is called for _all_ events, * even ones with a custom `event` field. */ onmessage?: (ev: EventSourceMessage) => void; /** * Called when a response is received. Use this to validate that the response * actually matches what you expect (and throw if it doesn't.) If not provided, * will default to a basic validation to ensure the content-type is text/event-stream. */ onopen: (response: Response) => Promise<void>; } export function fetchEventSource( // eslint-disable-next-line no-undef input: RequestInfo, { signal: inputSignal, headers: inputHeaders, onopen: inputOnOpen, onmessage, onclose, onerror, fetch: inputFetch, ...rest }: FetchEventSourceInit, ) { return new Promise<void>((resolve) => { // make a copy of the input headers since we may modify it below: const headers = { ...inputHeaders }; if (!headers.accept) { headers.accept = EventStreamContentType; } const fetch = inputFetch ?? window.fetch; async function create() { try { const response = await fetch(input, { ...rest, headers, signal: inputSignal, }); await inputOnOpen(response); await getBytes( response.body!, getLines( getMessages((id) => { if (id) { // store the id and send it back on the next retry: headers[LastEventId] = id; } else { // don't send the last-event-id header anymore: delete headers[LastEventId]; } }, onmessage), ), ); onclose?.(); resolve(); } catch (err) { onerror?.(err); resolve(); } } create(); }); }