| import { 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', |
| } |
|
|
| const baseOptions = { |
| method: 'GET', |
| mode: 'cors', |
| credentials: 'include', |
| 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 = () => void |
| export type IOnError = (msg: string) => void |
|
|
| type IOtherOptions = { |
| needAllResponseContent?: boolean |
| onData?: IOnData |
| onError?: IOnError |
| onCompleted?: IOnCompleted |
| } |
|
|
| function unicodeToChar(text: string) { |
| return text.replace(/\\u[0-9a-f]{4}/g, (_match, p1) => { |
| return String.fromCharCode(parseInt(p1, 16)) |
| }) |
| } |
|
|
| 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() { |
| 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) |
| return |
| bufferObj = JSON.parse(message) |
| 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}`, |
| }) |
| return |
| } |
|
|
| read() |
| }) |
| } |
| read() |
| } |
|
|
| const baseFetch = (url: string, fetchOptions: any, { needAllResponseContent }: IOtherOptions) => { |
| const options = Object.assign({}, baseOptions, fetchOptions) |
|
|
| const urlPrefix = API_PREFIX |
|
|
| let urlWithPrefix = `${urlPrefix}${url.startsWith('/') ? url : `/${url}`}` |
|
|
| const { method, params, body } = options |
| |
| 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) |
|
|
| |
| 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() |
| |
| if (!/^(2|3)\d{2}$/.test(res.status)) { |
| try { |
| const bodyJson = res.json() |
| switch (res.status) { |
| case 401: { |
| Toast.notify({ type: 'error', message: 'Invalid token' }) |
| return |
| } |
| default: |
| |
| new Promise(() => { |
| bodyJson.then((data: any) => { |
| Toast.notify({ type: 'error', message: data.message }) |
| }) |
| }) |
| } |
| } |
| catch (e) { |
| Toast.notify({ type: 'error', message: `${e}` }) |
| } |
|
|
| return Promise.reject(resClone) |
| } |
|
|
| |
| if (res.status === 204) { |
| resolve({ result: 'success' }) |
| return |
| } |
|
|
| |
| 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 ssePost = (url: string, fetchOptions: any, { onData, onCompleted, onError }: IOtherOptions) => { |
| const options = Object.assign({}, baseOptions, { |
| method: 'POST', |
| }, fetchOptions) |
|
|
| const urlPrefix = 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) => { |
| 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) => { |
| 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) |
| } |
|
|
| export const post = (url: string, options = {}, otherOptions?: IOtherOptions) => { |
| return request(url, Object.assign({}, options, { method: 'POST' }), otherOptions) |
| } |
|
|
| export const put = (url: string, options = {}, otherOptions?: IOtherOptions) => { |
| return request(url, Object.assign({}, options, { method: 'PUT' }), otherOptions) |
| } |
|
|
| export const del = (url: string, options = {}, otherOptions?: IOtherOptions) => { |
| return request(url, Object.assign({}, options, { method: 'DELETE' }), otherOptions) |
| } |
|
|