| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364 | 
							- /* eslint-disable no-new, prefer-promise-reject-errors */
 
- import { API_PREFIX, IS_CE_EDITION, PUBLIC_API_PREFIX } from '@/config'
 
- import Toast from '@/app/components/base/toast'
 
- const TIME_OUT = 100000
 
- const ContentType = {
 
-   json: 'application/json',
 
-   stream: 'text/event-stream',
 
-   form: 'application/x-www-form-urlencoded; charset=UTF-8',
 
-   download: 'application/octet-stream', // for download
 
-   upload: 'multipart/form-data', // for upload
 
- }
 
- const baseOptions = {
 
-   method: 'GET',
 
-   mode: 'cors',
 
-   credentials: 'include', // always send cookies、HTTP Basic authentication.
 
-   headers: new Headers({
 
-     'Content-Type': ContentType.json,
 
-   }),
 
-   redirect: 'follow',
 
- }
 
- export type IOnDataMoreInfo = {
 
-   conversationId: string | undefined
 
-   messageId: string
 
-   errorMessage?: string
 
- }
 
- export type IOnData = (message: string, isFirstMessage: boolean, moreInfo: IOnDataMoreInfo) => void
 
- export type IOnCompleted = (hasError?: boolean) => void
 
- export type IOnError = (msg: string) => void
 
- type IOtherOptions = {
 
-   isPublicAPI?: boolean
 
-   needAllResponseContent?: boolean
 
-   onData?: IOnData // for stream
 
-   onError?: IOnError
 
-   onCompleted?: IOnCompleted // for stream
 
-   getAbortController?: (abortController: AbortController) => void
 
- }
 
- function unicodeToChar(text: string) {
 
-   return text.replace(/\\u[0-9a-f]{4}/g, (_match, p1) => {
 
-     return String.fromCharCode(parseInt(p1, 16))
 
-   })
 
- }
 
- export function format(text: string) {
 
-   let res = text.trim()
 
-   if (res.startsWith('\n'))
 
-     res = res.replace('\n', '')
 
-   return res.replaceAll('\n', '<br/>').replaceAll('```', '')
 
- }
 
- const handleStream = (response: any, onData: IOnData, onCompleted?: IOnCompleted) => {
 
-   if (!response.ok)
 
-     throw new Error('Network response was not ok')
 
-   const reader = response.body.getReader()
 
-   const decoder = new TextDecoder('utf-8')
 
-   let buffer = ''
 
-   let bufferObj: any
 
-   let isFirstMessage = true
 
-   function read() {
 
-     let hasError = false
 
-     reader.read().then((result: any) => {
 
-       if (result.done) {
 
-         onCompleted && onCompleted()
 
-         return
 
-       }
 
-       buffer += decoder.decode(result.value, { stream: true })
 
-       const lines = buffer.split('\n')
 
-       try {
 
-         lines.forEach((message) => {
 
-           if (message.startsWith('data: ')) { // check if it starts with data:
 
-             // console.log(message);
 
-             try {
 
-               bufferObj = JSON.parse(message.substring(6)) // remove data: and parse as json
 
-             }
 
-             catch (e) {
 
-               // mute handle message cut off
 
-               onData('', isFirstMessage, {
 
-                 conversationId: bufferObj?.conversation_id,
 
-                 messageId: bufferObj?.id,
 
-               })
 
-               return
 
-             }
 
-             if (bufferObj.status === 400 || !bufferObj.event) {
 
-               onData('', false, {
 
-                 conversationId: undefined,
 
-                 messageId: '',
 
-                 errorMessage: bufferObj.message,
 
-               })
 
-               hasError = true
 
-               onCompleted && onCompleted(true)
 
-               return
 
-             }
 
-             // can not use format here. Because message is splited.
 
-             onData(unicodeToChar(bufferObj.answer), isFirstMessage, {
 
-               conversationId: bufferObj.conversation_id,
 
-               messageId: bufferObj.id,
 
-             })
 
-             isFirstMessage = false
 
-           }
 
-         })
 
-         buffer = lines[lines.length - 1]
 
-       }
 
-       catch (e) {
 
-         onData('', false, {
 
-           conversationId: undefined,
 
-           messageId: '',
 
-           errorMessage: `${e}`,
 
-         })
 
-         hasError = true
 
-         onCompleted && onCompleted(true)
 
-         return
 
-       }
 
-       if (!hasError)
 
-         read()
 
-     })
 
-   }
 
-   read()
 
- }
 
- const baseFetch = (
 
-   url: string,
 
-   fetchOptions: any,
 
-   {
 
-     isPublicAPI = false,
 
-     needAllResponseContent,
 
-   }: IOtherOptions,
 
- ) => {
 
-   const options = Object.assign({}, baseOptions, fetchOptions)
 
-   if (isPublicAPI) {
 
-     const sharedToken = globalThis.location.pathname.split('/').slice(-1)[0]
 
-     options.headers.set('Authorization', `bearer ${sharedToken}`)
 
-   }
 
-   const urlPrefix = isPublicAPI ? PUBLIC_API_PREFIX : API_PREFIX
 
-   let urlWithPrefix = `${urlPrefix}${url.startsWith('/') ? url : `/${url}`}`
 
-   const { method, params, body } = options
 
-   // handle query
 
-   if (method === 'GET' && params) {
 
-     const paramsArray: string[] = []
 
-     Object.keys(params).forEach(key =>
 
-       paramsArray.push(`${key}=${encodeURIComponent(params[key])}`),
 
-     )
 
-     if (urlWithPrefix.search(/\?/) === -1)
 
-       urlWithPrefix += `?${paramsArray.join('&')}`
 
-     else
 
-       urlWithPrefix += `&${paramsArray.join('&')}`
 
-     delete options.params
 
-   }
 
-   if (body)
 
-     options.body = JSON.stringify(body)
 
-   // Handle timeout
 
-   return Promise.race([
 
-     new Promise((resolve, reject) => {
 
-       setTimeout(() => {
 
-         reject(new Error('request timeout'))
 
-       }, TIME_OUT)
 
-     }),
 
-     new Promise((resolve, reject) => {
 
-       globalThis.fetch(urlWithPrefix, options)
 
-         .then((res: any) => {
 
-           const resClone = res.clone()
 
-           // Error handler
 
-           if (!/^(2|3)\d{2}$/.test(res.status)) {
 
-             const bodyJson = res.json()
 
-             switch (res.status) {
 
-               case 401: {
 
-                 if (isPublicAPI) {
 
-                   Toast.notify({ type: 'error', message: 'Invalid token' })
 
-                   return
 
-                 }
 
-                 const loginUrl = `${globalThis.location.origin}/signin`
 
-                 if (IS_CE_EDITION) {
 
-                   bodyJson.then((data: any) => {
 
-                     if (data.code === 'not_setup') {
 
-                       globalThis.location.href = `${globalThis.location.origin}/install`
 
-                     }
 
-                     else {
 
-                       if (location.pathname === '/signin') {
 
-                         bodyJson.then((data: any) => {
 
-                           Toast.notify({ type: 'error', message: data.message })
 
-                         })
 
-                       }
 
-                       else {
 
-                         globalThis.location.href = loginUrl
 
-                       }
 
-                     }
 
-                   })
 
-                   return Promise.reject()
 
-                 }
 
-                 globalThis.location.href = loginUrl
 
-                 break
 
-               }
 
-               case 403:
 
-                 new Promise(() => {
 
-                   bodyJson.then((data: any) => {
 
-                     Toast.notify({ type: 'error', message: data.message })
 
-                     if (data.code === 'already_setup')
 
-                       globalThis.location.href = `${globalThis.location.origin}/signin`
 
-                   })
 
-                 })
 
-                 break
 
-               // fall through
 
-               default:
 
-                 new Promise(() => {
 
-                   bodyJson.then((data: any) => {
 
-                     Toast.notify({ type: 'error', message: data.message })
 
-                   })
 
-                 })
 
-             }
 
-             return Promise.reject(resClone)
 
-           }
 
-           // handle delete api. Delete api not return content.
 
-           if (res.status === 204) {
 
-             resolve({ result: 'success' })
 
-             return
 
-           }
 
-           // return data
 
-           const data = options.headers.get('Content-type') === ContentType.download ? res.blob() : res.json()
 
-           resolve(needAllResponseContent ? resClone : data)
 
-         })
 
-         .catch((err) => {
 
-           Toast.notify({ type: 'error', message: err })
 
-           reject(err)
 
-         })
 
-     }),
 
-   ])
 
- }
 
- export const upload = (options: any): Promise<any> => {
 
-   const defaultOptions = {
 
-     method: 'POST',
 
-     url: `${API_PREFIX}/files/upload`,
 
-     headers: {},
 
-     data: {},
 
-   }
 
-   options = {
 
-     ...defaultOptions,
 
-     ...options,
 
-     headers: { ...defaultOptions.headers, ...options.headers },
 
-   }
 
-   return new Promise((resolve, reject) => {
 
-     const xhr = options.xhr
 
-     xhr.open(options.method, options.url)
 
-     for (const key in options.headers)
 
-       xhr.setRequestHeader(key, options.headers[key])
 
-     xhr.withCredentials = true
 
-     xhr.responseType = 'json'
 
-     xhr.onreadystatechange = function () {
 
-       if (xhr.readyState === 4) {
 
-         if (xhr.status === 201)
 
-           resolve(xhr.response)
 
-         else
 
-           reject(xhr)
 
-       }
 
-     }
 
-     xhr.upload.onprogress = options.onprogress
 
-     xhr.send(options.data)
 
-   })
 
- }
 
- export const ssePost = (url: string, fetchOptions: any, { isPublicAPI = false, onData, onCompleted, onError, getAbortController }: IOtherOptions) => {
 
-   const abortController = new AbortController()
 
-   const options = Object.assign({}, baseOptions, {
 
-     method: 'POST',
 
-     signal: abortController.signal,
 
-   }, fetchOptions)
 
-   getAbortController?.(abortController)
 
-   const urlPrefix = isPublicAPI ? PUBLIC_API_PREFIX : API_PREFIX
 
-   const urlWithPrefix = `${urlPrefix}${url.startsWith('/') ? url : `/${url}`}`
 
-   const { body } = options
 
-   if (body)
 
-     options.body = JSON.stringify(body)
 
-   globalThis.fetch(urlWithPrefix, options)
 
-     .then((res: any) => {
 
-       // debugger
 
-       if (!/^(2|3)\d{2}$/.test(res.status)) {
 
-         new Promise(() => {
 
-           res.json().then((data: any) => {
 
-             Toast.notify({ type: 'error', message: data.message || 'Server Error' })
 
-           })
 
-         })
 
-         onError?.('Server Error')
 
-         return
 
-       }
 
-       return handleStream(res, (str: string, isFirstMessage: boolean, moreInfo: IOnDataMoreInfo) => {
 
-         if (moreInfo.errorMessage) {
 
-           Toast.notify({ type: 'error', message: moreInfo.errorMessage })
 
-           return
 
-         }
 
-         onData?.(str, isFirstMessage, moreInfo)
 
-       }, onCompleted)
 
-     }).catch((e) => {
 
-       // debugger
 
-       Toast.notify({ type: 'error', message: e })
 
-       onError?.(e)
 
-     })
 
- }
 
- export const request = (url: string, options = {}, otherOptions?: IOtherOptions) => {
 
-   return baseFetch(url, options, otherOptions || {})
 
- }
 
- export const get = (url: string, options = {}, otherOptions?: IOtherOptions) => {
 
-   return request(url, Object.assign({}, options, { method: 'GET' }), otherOptions)
 
- }
 
- // For public API
 
- export const getPublic = (url: string, options = {}, otherOptions?: IOtherOptions) => {
 
-   return get(url, options, { ...otherOptions, isPublicAPI: true })
 
- }
 
- export const post = (url: string, options = {}, otherOptions?: IOtherOptions) => {
 
-   return request(url, Object.assign({}, options, { method: 'POST' }), otherOptions)
 
- }
 
- export const postPublic = (url: string, options = {}, otherOptions?: IOtherOptions) => {
 
-   return post(url, options, { ...otherOptions, isPublicAPI: true })
 
- }
 
- export const put = (url: string, options = {}, otherOptions?: IOtherOptions) => {
 
-   return request(url, Object.assign({}, options, { method: 'PUT' }), otherOptions)
 
- }
 
- export const putPublic = (url: string, options = {}, otherOptions?: IOtherOptions) => {
 
-   return put(url, options, { ...otherOptions, isPublicAPI: true })
 
- }
 
- export const del = (url: string, options = {}, otherOptions?: IOtherOptions) => {
 
-   return request(url, Object.assign({}, options, { method: 'DELETE' }), otherOptions)
 
- }
 
- export const delPublic = (url: string, options = {}, otherOptions?: IOtherOptions) => {
 
-   return del(url, options, { ...otherOptions, isPublicAPI: true })
 
- }
 
- export const patch = (url: string, options = {}, otherOptions?: IOtherOptions) => {
 
-   return request(url, Object.assign({}, options, { method: 'PATCH' }), otherOptions)
 
- }
 
- export const patchPublic = (url: string, options = {}, otherOptions?: IOtherOptions) => {
 
-   return patch(url, options, { ...otherOptions, isPublicAPI: true })
 
- }
 
 
  |