| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591 | import { API_PREFIX, IS_CE_EDITION, PUBLIC_API_PREFIX } from '@/config'import { refreshAccessTokenOrRelogin } from './refresh-token'import Toast from '@/app/components/base/toast'import type { AnnotationReply, MessageEnd, MessageReplace, ThoughtItem } from '@/app/components/base/chat/chat/type'import type { VisionFile } from '@/types/app'import type {  AgentLogResponse,  IterationFinishedResponse,  IterationNextResponse,  IterationStartedResponse,  LoopFinishedResponse,  LoopNextResponse,  LoopStartedResponse,  NodeFinishedResponse,  NodeStartedResponse,  ParallelBranchFinishedResponse,  ParallelBranchStartedResponse,  TextChunkResponse,  TextReplaceResponse,  WorkflowFinishedResponse,  WorkflowStartedResponse,} from '@/types/workflow'import { removeAccessToken } from '@/app/components/share/utils'import type { FetchOptionType, ResponseError } from './fetch'import { ContentType, base, baseOptions, getAccessToken } from './fetch'import { asyncRunSafe } from '@/utils'const TIME_OUT = 100000export type IOnDataMoreInfo = {  conversationId?: string  taskId?: string  messageId: string  errorMessage?: string  errorCode?: string}export type IOnData = (message: string, isFirstMessage: boolean, moreInfo: IOnDataMoreInfo) => voidexport type IOnThought = (though: ThoughtItem) => voidexport type IOnFile = (file: VisionFile) => voidexport type IOnMessageEnd = (messageEnd: MessageEnd) => voidexport type IOnMessageReplace = (messageReplace: MessageReplace) => voidexport type IOnAnnotationReply = (messageReplace: AnnotationReply) => voidexport type IOnCompleted = (hasError?: boolean, errorMessage?: string) => voidexport type IOnError = (msg: string, code?: string) => voidexport type IOnWorkflowStarted = (workflowStarted: WorkflowStartedResponse) => voidexport type IOnWorkflowFinished = (workflowFinished: WorkflowFinishedResponse) => voidexport type IOnNodeStarted = (nodeStarted: NodeStartedResponse) => voidexport type IOnNodeFinished = (nodeFinished: NodeFinishedResponse) => voidexport type IOnIterationStarted = (workflowStarted: IterationStartedResponse) => voidexport type IOnIterationNext = (workflowStarted: IterationNextResponse) => voidexport type IOnNodeRetry = (nodeFinished: NodeFinishedResponse) => voidexport type IOnIterationFinished = (workflowFinished: IterationFinishedResponse) => voidexport type IOnParallelBranchStarted = (parallelBranchStarted: ParallelBranchStartedResponse) => voidexport type IOnParallelBranchFinished = (parallelBranchFinished: ParallelBranchFinishedResponse) => voidexport type IOnTextChunk = (textChunk: TextChunkResponse) => voidexport type IOnTTSChunk = (messageId: string, audioStr: string, audioType?: string) => voidexport type IOnTTSEnd = (messageId: string, audioStr: string, audioType?: string) => voidexport type IOnTextReplace = (textReplace: TextReplaceResponse) => voidexport type IOnLoopStarted = (workflowStarted: LoopStartedResponse) => voidexport type IOnLoopNext = (workflowStarted: LoopNextResponse) => voidexport type IOnLoopFinished = (workflowFinished: LoopFinishedResponse) => voidexport type IOnAgentLog = (agentLog: AgentLogResponse) => voidexport type IOtherOptions = {  isPublicAPI?: boolean  isMarketplaceAPI?: boolean  bodyStringify?: boolean  needAllResponseContent?: boolean  deleteContentType?: boolean  fileName?: string  silent?: boolean  onData?: IOnData // for stream  onThought?: IOnThought  onFile?: IOnFile  onMessageEnd?: IOnMessageEnd  onMessageReplace?: IOnMessageReplace  onError?: IOnError  onCompleted?: IOnCompleted // for stream  getAbortController?: (abortController: AbortController) => void  onWorkflowStarted?: IOnWorkflowStarted  onWorkflowFinished?: IOnWorkflowFinished  onNodeStarted?: IOnNodeStarted  onNodeFinished?: IOnNodeFinished  onIterationStart?: IOnIterationStarted  onIterationNext?: IOnIterationNext  onIterationFinish?: IOnIterationFinished  onNodeRetry?: IOnNodeRetry  onParallelBranchStarted?: IOnParallelBranchStarted  onParallelBranchFinished?: IOnParallelBranchFinished  onTextChunk?: IOnTextChunk  onTTSChunk?: IOnTTSChunk  onTTSEnd?: IOnTTSEnd  onTextReplace?: IOnTextReplace  onLoopStart?: IOnLoopStarted  onLoopNext?: IOnLoopNext  onLoopFinish?: IOnLoopFinished  onAgentLog?: IOnAgentLog}function unicodeToChar(text: string) {  if (!text)    return ''  return text.replace(/\\u[0-9a-f]{4}/g, (_match, p1) => {    return String.fromCharCode(Number.parseInt(p1, 16))  })}function requiredWebSSOLogin() {  globalThis.location.href = `/webapp-signin?redirect_url=${globalThis.location.pathname}`}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: Response,  onData: IOnData,  onCompleted?: IOnCompleted,  onThought?: IOnThought,  onMessageEnd?: IOnMessageEnd,  onMessageReplace?: IOnMessageReplace,  onFile?: IOnFile,  onWorkflowStarted?: IOnWorkflowStarted,  onWorkflowFinished?: IOnWorkflowFinished,  onNodeStarted?: IOnNodeStarted,  onNodeFinished?: IOnNodeFinished,  onIterationStart?: IOnIterationStarted,  onIterationNext?: IOnIterationNext,  onIterationFinish?: IOnIterationFinished,  onLoopStart?: IOnLoopStarted,  onLoopNext?: IOnLoopNext,  onLoopFinish?: IOnLoopFinished,  onNodeRetry?: IOnNodeRetry,  onParallelBranchStarted?: IOnParallelBranchStarted,  onParallelBranchFinished?: IOnParallelBranchFinished,  onTextChunk?: IOnTextChunk,  onTTSChunk?: IOnTTSChunk,  onTTSEnd?: IOnTTSEnd,  onTextReplace?: IOnTextReplace,  onAgentLog?: IOnAgentLog,) => {  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: Record<string, 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:            try {              bufferObj = JSON.parse(message.substring(6)) as Record<string, any>// remove data: and parse as json            }            catch (e) {              // mute handle message cut off              onData('', isFirstMessage, {                conversationId: bufferObj?.conversation_id,                messageId: bufferObj?.message_id,              })              return            }            if (bufferObj.status === 400 || !bufferObj.event) {              onData('', false, {                conversationId: undefined,                messageId: '',                errorMessage: bufferObj?.message,                errorCode: bufferObj?.code,              })              hasError = true              onCompleted?.(true, bufferObj?.message)              return            }            if (bufferObj.event === 'message' || bufferObj.event === 'agent_message') {              // can not use format here. Because message is splitted.              onData(unicodeToChar(bufferObj.answer), isFirstMessage, {                conversationId: bufferObj.conversation_id,                taskId: bufferObj.task_id,                messageId: bufferObj.id,              })              isFirstMessage = false            }            else if (bufferObj.event === 'agent_thought') {              onThought?.(bufferObj as ThoughtItem)            }            else if (bufferObj.event === 'message_file') {              onFile?.(bufferObj as VisionFile)            }            else if (bufferObj.event === 'message_end') {              onMessageEnd?.(bufferObj as MessageEnd)            }            else if (bufferObj.event === 'message_replace') {              onMessageReplace?.(bufferObj as MessageReplace)            }            else if (bufferObj.event === 'workflow_started') {              onWorkflowStarted?.(bufferObj as WorkflowStartedResponse)            }            else if (bufferObj.event === 'workflow_finished') {              onWorkflowFinished?.(bufferObj as WorkflowFinishedResponse)            }            else if (bufferObj.event === 'node_started') {              onNodeStarted?.(bufferObj as NodeStartedResponse)            }            else if (bufferObj.event === 'node_finished') {              onNodeFinished?.(bufferObj as NodeFinishedResponse)            }            else if (bufferObj.event === 'iteration_started') {              onIterationStart?.(bufferObj as IterationStartedResponse)            }            else if (bufferObj.event === 'iteration_next') {              onIterationNext?.(bufferObj as IterationNextResponse)            }            else if (bufferObj.event === 'iteration_completed') {              onIterationFinish?.(bufferObj as IterationFinishedResponse)            }            else if (bufferObj.event === 'loop_started') {              onLoopStart?.(bufferObj as LoopStartedResponse)            }            else if (bufferObj.event === 'loop_next') {              onLoopNext?.(bufferObj as LoopNextResponse)            }            else if (bufferObj.event === 'loop_completed') {              onLoopFinish?.(bufferObj as LoopFinishedResponse)            }            else if (bufferObj.event === 'node_retry') {              onNodeRetry?.(bufferObj as NodeFinishedResponse)            }            else if (bufferObj.event === 'parallel_branch_started') {              onParallelBranchStarted?.(bufferObj as ParallelBranchStartedResponse)            }            else if (bufferObj.event === 'parallel_branch_finished') {              onParallelBranchFinished?.(bufferObj as ParallelBranchFinishedResponse)            }            else if (bufferObj.event === 'text_chunk') {              onTextChunk?.(bufferObj as TextChunkResponse)            }            else if (bufferObj.event === 'text_replace') {              onTextReplace?.(bufferObj as TextReplaceResponse)            }            else if (bufferObj.event === 'agent_log') {              onAgentLog?.(bufferObj as AgentLogResponse)            }            else if (bufferObj.event === 'tts_message') {              onTTSChunk?.(bufferObj.message_id, bufferObj.audio, bufferObj.audio_type)            }            else if (bufferObj.event === 'tts_message_end') {              onTTSEnd?.(bufferObj.message_id, bufferObj.audio)            }          }        })        buffer = lines[lines.length - 1]      }      catch (e) {        onData('', false, {          conversationId: undefined,          messageId: '',          errorMessage: `${e}`,        })        hasError = true        onCompleted?.(true, e as string)        return      }      if (!hasError)        read()    })  }  read()}const baseFetch = baseexport const upload = (options: any, isPublicAPI?: boolean, url?: string, searchParams?: string): Promise<any> => {  const urlPrefix = isPublicAPI ? PUBLIC_API_PREFIX : API_PREFIX  const token = getAccessToken(isPublicAPI)  const defaultOptions = {    method: 'POST',    url: (url ? `${urlPrefix}${url}` : `${urlPrefix}/files/upload`) + (searchParams || ''),    headers: {      Authorization: `Bearer ${token}`,    },    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 || xhr.status === 200)          resolve(xhr.response)        else          reject(xhr)      }    }    xhr.upload.onprogress = options.onprogress    xhr.send(options.data)  })}export const ssePost = (  url: string,  fetchOptions: FetchOptionType,  otherOptions: IOtherOptions,) => {  const {    isPublicAPI = false,    onData,    onCompleted,    onThought,    onFile,    onMessageEnd,    onMessageReplace,    onWorkflowStarted,    onWorkflowFinished,    onNodeStarted,    onNodeFinished,    onIterationStart,    onIterationNext,    onIterationFinish,    onNodeRetry,    onParallelBranchStarted,    onParallelBranchFinished,    onTextChunk,    onTTSChunk,    onTTSEnd,    onTextReplace,    onAgentLog,    onError,    getAbortController,    onLoopStart,    onLoopNext,    onLoopFinish,  } = otherOptions  const abortController = new AbortController()  const token = localStorage.getItem('console_token')  const options = Object.assign({}, baseOptions, {    method: 'POST',    signal: abortController.signal,    headers: new Headers({      Authorization: `Bearer ${token}`,    }),  } as RequestInit, fetchOptions)  const contentType = (options.headers as Headers).get('Content-Type')  if (!contentType)    (options.headers as Headers).set('Content-Type', ContentType.json)  getAbortController?.(abortController)  const urlPrefix = isPublicAPI ? PUBLIC_API_PREFIX : API_PREFIX  const urlWithPrefix = (url.startsWith('http://') || url.startsWith('https://'))    ? url    : `${urlPrefix}${url.startsWith('/') ? url : `/${url}`}`  const { body } = options  if (body)    options.body = JSON.stringify(body)  const accessToken = getAccessToken(isPublicAPI)  ;(options.headers as Headers).set('Authorization', `Bearer ${accessToken}`)  globalThis.fetch(urlWithPrefix, options as RequestInit)    .then((res) => {      if (!/^(2|3)\d{2}$/.test(String(res.status))) {        if (res.status === 401) {          refreshAccessTokenOrRelogin(TIME_OUT).then(() => {            ssePost(url, fetchOptions, otherOptions)          }).catch(() => {            res.json().then((data: any) => {              if (isPublicAPI) {                if (data.code === 'web_sso_auth_required')                  requiredWebSSOLogin()                if (data.code === 'unauthorized') {                  removeAccessToken()                  globalThis.location.reload()                }              }            })          })        }        else {          res.json().then((data) => {            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) {          onError?.(moreInfo.errorMessage, moreInfo.errorCode)          // TypeError: Cannot assign to read only property ... will happen in page leave, so it should be ignored.          if (moreInfo.errorMessage !== 'AbortError: The user aborted a request.' && !moreInfo.errorMessage.includes('TypeError: Cannot assign to read only property'))            Toast.notify({ type: 'error', message: moreInfo.errorMessage })          return        }        onData?.(str, isFirstMessage, moreInfo)      },      onCompleted,      onThought,      onMessageEnd,      onMessageReplace,      onFile,      onWorkflowStarted,      onWorkflowFinished,      onNodeStarted,      onNodeFinished,      onIterationStart,      onIterationNext,      onIterationFinish,      onLoopStart,      onLoopNext,      onLoopFinish,      onNodeRetry,      onParallelBranchStarted,      onParallelBranchFinished,      onTextChunk,      onTTSChunk,      onTTSEnd,      onTextReplace,      onAgentLog,      )    }).catch((e) => {      if (e.toString() !== 'AbortError: The user aborted a request.' && !e.toString().errorMessage.includes('TypeError: Cannot assign to read only property'))        Toast.notify({ type: 'error', message: e })      onError?.(e)    })}// base requestexport const request = async<T>(url: string, options = {}, otherOptions?: IOtherOptions) => {  try {    const otherOptionsForBaseFetch = otherOptions || {}    const [err, resp] = await asyncRunSafe<T>(baseFetch(url, options, otherOptionsForBaseFetch))    if (err === null)      return resp    const errResp: Response = err as any    if (errResp.status === 401) {      const [parseErr, errRespData] = await asyncRunSafe<ResponseError>(errResp.json())      const loginUrl = `${globalThis.location.origin}/signin`      if (parseErr) {        globalThis.location.href = loginUrl        return Promise.reject(err)      }      // special code      const { code, message } = errRespData      // webapp sso      if (code === 'web_sso_auth_required') {        requiredWebSSOLogin()        return Promise.reject(err)      }      if (code === 'unauthorized_and_force_logout') {        localStorage.removeItem('console_token')        localStorage.removeItem('refresh_token')        globalThis.location.reload()        return Promise.reject(err)      }      const {        isPublicAPI = false,        silent,      } = otherOptionsForBaseFetch      if (isPublicAPI && code === 'unauthorized') {        removeAccessToken()        globalThis.location.reload()        return Promise.reject(err)      }      if (code === 'init_validate_failed' && IS_CE_EDITION && !silent) {        Toast.notify({ type: 'error', message, duration: 4000 })        return Promise.reject(err)      }      if (code === 'not_init_validated' && IS_CE_EDITION) {        globalThis.location.href = `${globalThis.location.origin}/init`        return Promise.reject(err)      }      if (code === 'not_setup' && IS_CE_EDITION) {        globalThis.location.href = `${globalThis.location.origin}/install`        return Promise.reject(err)      }      // refresh token      const [refreshErr] = await asyncRunSafe(refreshAccessTokenOrRelogin(TIME_OUT))      if (refreshErr === null)        return baseFetch<T>(url, options, otherOptionsForBaseFetch)      if (location.pathname !== '/signin' || !IS_CE_EDITION) {        globalThis.location.href = loginUrl        return Promise.reject(err)      }      if (!silent) {        Toast.notify({ type: 'error', message })        return Promise.reject(err)      }      globalThis.location.href = loginUrl      return Promise.reject(err)    }    else {      return Promise.reject(err)    }  }  catch (error) {    console.error(error)    return Promise.reject(error)  }}// request methodsexport const get = <T>(url: string, options = {}, otherOptions?: IOtherOptions) => {  return request<T>(url, Object.assign({}, options, { method: 'GET' }), otherOptions)}// For public APIexport const getPublic = <T>(url: string, options = {}, otherOptions?: IOtherOptions) => {  return get<T>(url, options, { ...otherOptions, isPublicAPI: true })}// For Marketplace APIexport const getMarketplace = <T>(url: string, options = {}, otherOptions?: IOtherOptions) => {  return get<T>(url, options, { ...otherOptions, isMarketplaceAPI: true })}export const post = <T>(url: string, options = {}, otherOptions?: IOtherOptions) => {  return request<T>(url, Object.assign({}, options, { method: 'POST' }), otherOptions)}// For Marketplace APIexport const postMarketplace = <T>(url: string, options = {}, otherOptions?: IOtherOptions) => {  return post<T>(url, options, { ...otherOptions, isMarketplaceAPI: true })}export const postPublic = <T>(url: string, options = {}, otherOptions?: IOtherOptions) => {  return post<T>(url, options, { ...otherOptions, isPublicAPI: true })}export const put = <T>(url: string, options = {}, otherOptions?: IOtherOptions) => {  return request<T>(url, Object.assign({}, options, { method: 'PUT' }), otherOptions)}export const putPublic = <T>(url: string, options = {}, otherOptions?: IOtherOptions) => {  return put<T>(url, options, { ...otherOptions, isPublicAPI: true })}export const del = <T>(url: string, options = {}, otherOptions?: IOtherOptions) => {  return request<T>(url, Object.assign({}, options, { method: 'DELETE' }), otherOptions)}export const delPublic = <T>(url: string, options = {}, otherOptions?: IOtherOptions) => {  return del<T>(url, options, { ...otherOptions, isPublicAPI: true })}export const patch = <T>(url: string, options = {}, otherOptions?: IOtherOptions) => {  return request<T>(url, Object.assign({}, options, { method: 'PATCH' }), otherOptions)}export const patchPublic = <T>(url: string, options = {}, otherOptions?: IOtherOptions) => {  return patch<T>(url, options, { ...otherOptions, isPublicAPI: true })}// request methodsexport const download = <T>(url: string, options = {}, otherOptions?: IOtherOptions) => {  return request<T>(url, Object.assign({}, options, { method: 'GET', headers: { 'content-type': ContentType.download } }), otherOptions)}
 |