From a430284aa21e3ae1f0d5654e55b2ad2852519cc2 Mon Sep 17 00:00:00 2001
From: wwf <yearningwang@iqtogether.com>
Date: 星期三, 04 六月 2025 15:17:49 +0800
Subject: [PATCH] 初始化

---
 service/base.ts |  268 +++++++++++++++++++++++++++++++++++------------------
 1 files changed, 175 insertions(+), 93 deletions(-)

diff --git a/service/base.ts b/service/base.ts
index 43e65f8..22b1a43 100644
--- a/service/base.ts
+++ b/service/base.ts
@@ -1,16 +1,12 @@
-import { API_PREFIX, IS_CE_EDITION, PUBLIC_API_PREFIX, PUBLIC_WEB_PREFIX, WEB_PREFIX } from '@/config'
 import { refreshAccessTokenOrRelogin } from './refresh-token'
+import { API_PREFIX, IS_CE_EDITION, PUBLIC_API_PREFIX } from '@/config'
 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,
@@ -21,10 +17,27 @@
   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 = 100000
+
+const ContentType = {
+  json: 'application/json',
+  stream: 'text/event-stream',
+  audio: 'audio/mpeg',
+  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銆丠TTP Basic authentication.
+  headers: new Headers({
+    'Content-Type': ContentType.json,
+  }),
+  redirect: 'follow',
+}
 
 export type IOnDataMoreInfo = {
   conversationId?: string
@@ -57,14 +70,9 @@
 export type IOnTTSChunk = (messageId: string, audioStr: string, audioType?: string) => void
 export type IOnTTSEnd = (messageId: string, audioStr: string, audioType?: string) => void
 export type IOnTextReplace = (textReplace: TextReplaceResponse) => void
-export type IOnLoopStarted = (workflowStarted: LoopStartedResponse) => void
-export type IOnLoopNext = (workflowStarted: LoopNextResponse) => void
-export type IOnLoopFinished = (workflowFinished: LoopFinishedResponse) => void
-export type IOnAgentLog = (agentLog: AgentLogResponse) => void
 
 export type IOtherOptions = {
   isPublicAPI?: boolean
-  isMarketplaceAPI?: boolean
   bodyStringify?: boolean
   needAllResponseContent?: boolean
   deleteContentType?: boolean
@@ -92,10 +100,17 @@
   onTTSChunk?: IOnTTSChunk
   onTTSEnd?: IOnTTSEnd
   onTextReplace?: IOnTextReplace
-  onLoopStart?: IOnLoopStarted
-  onLoopNext?: IOnLoopNext
-  onLoopFinish?: IOnLoopFinished
-  onAgentLog?: IOnAgentLog
+}
+
+type ResponseError = {
+  code: string
+  message: string
+  status: number
+}
+
+type FetchOptionType = Omit<RequestInit, 'body'> & {
+  params?: Record<string, any>
+  body?: BodyInit | Record<string, any> | null
 }
 
 function unicodeToChar(text: string) {
@@ -103,12 +118,30 @@
     return ''
 
   return text.replace(/\\u[0-9a-f]{4}/g, (_match, p1) => {
-    return String.fromCharCode(Number.parseInt(p1, 16))
+    return String.fromCharCode(parseInt(p1, 16))
   })
 }
 
 function requiredWebSSOLogin() {
-  globalThis.location.href = `${PUBLIC_WEB_PREFIX}/webapp-signin?redirect_url=${globalThis.location.pathname}`
+  globalThis.location.href = `/webapp-signin?redirect_url=${globalThis.location.pathname}`
+}
+
+function getAccessToken(isPublicAPI?: boolean) {
+  if (isPublicAPI) {
+    const sharedToken = globalThis.location.pathname.split('/').slice(-1)[0]
+    const accessToken = localStorage.getItem('token') || JSON.stringify({ [sharedToken]: '' })
+    let accessTokenJson = { [sharedToken]: '' }
+    try {
+      accessTokenJson = JSON.parse(accessToken)
+    }
+    catch (e) {
+
+    }
+    return accessTokenJson[sharedToken]
+  }
+  else {
+    return localStorage.getItem('console_token') || ''
+  }
 }
 
 export function format(text: string) {
@@ -134,9 +167,6 @@
   onIterationStart?: IOnIterationStarted,
   onIterationNext?: IOnIterationNext,
   onIterationFinish?: IOnIterationFinished,
-  onLoopStart?: IOnLoopStarted,
-  onLoopNext?: IOnLoopNext,
-  onLoopFinish?: IOnLoopFinished,
   onNodeRetry?: IOnNodeRetry,
   onParallelBranchStarted?: IOnParallelBranchStarted,
   onParallelBranchFinished?: IOnParallelBranchFinished,
@@ -144,7 +174,6 @@
   onTTSChunk?: IOnTTSChunk,
   onTTSEnd?: IOnTTSEnd,
   onTextReplace?: IOnTextReplace,
-  onAgentLog?: IOnAgentLog,
 ) => {
   if (!response.ok)
     throw new Error('Network response was not ok')
@@ -169,7 +198,7 @@
             try {
               bufferObj = JSON.parse(message.substring(6)) as Record<string, any>// remove data: and parse as json
             }
-            catch {
+            catch (e) {
               // mute handle message cut off
               onData('', isFirstMessage, {
                 conversationId: bufferObj?.conversation_id,
@@ -230,15 +259,6 @@
             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)
             }
@@ -253,9 +273,6 @@
             }
             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)
@@ -284,11 +301,119 @@
   read()
 }
 
-const baseFetch = base
+const baseFetch = <T>(
+  url: string,
+  fetchOptions: FetchOptionType,
+  {
+    isPublicAPI = false,
+    bodyStringify = true,
+    needAllResponseContent,
+    deleteContentType,
+    getAbortController,
+    silent,
+  }: IOtherOptions,
+): Promise<T> => {
+  const options: typeof baseOptions & FetchOptionType = Object.assign({}, baseOptions, fetchOptions)
+  if (getAbortController) {
+    const abortController = new AbortController()
+    getAbortController(abortController)
+    options.signal = abortController.signal
+  }
+  const accessToken = getAccessToken(isPublicAPI)
+  options.headers.set('Authorization', `Bearer ${accessToken}`)
 
-export const upload = async (options: any, isPublicAPI?: boolean, url?: string, searchParams?: string): Promise<any> => {
+  if (deleteContentType) {
+    options.headers.delete('Content-Type')
+  }
+  else {
+    const contentType = options.headers.get('Content-Type')
+    if (!contentType)
+      options.headers.set('Content-Type', ContentType.json)
+  }
+
   const urlPrefix = isPublicAPI ? PUBLIC_API_PREFIX : API_PREFIX
-  const token = await getAccessToken(isPublicAPI)
+  let urlWithPrefix = (url.startsWith('http://') || url.startsWith('https://'))
+    ? url
+    : `${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 && bodyStringify)
+    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 as RequestInit)
+        .then((res) => {
+          const resClone = res.clone()
+          // Error handler
+          if (!/^(2|3)\d{2}$/.test(String(res.status))) {
+            const bodyJson = res.json()
+            switch (res.status) {
+              case 401:
+                return Promise.reject(resClone)
+              case 403:
+                bodyJson.then((data: ResponseError) => {
+                  if (!silent)
+                    Toast.notify({ type: 'error', message: data.message })
+                  if (data.code === 'already_setup')
+                    globalThis.location.href = `${globalThis.location.origin}/signin`
+                })
+                break
+              // fall through
+              default:
+                bodyJson.then((data: ResponseError) => {
+                  if (!silent)
+                    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
+          if (options.headers.get('Content-type') === ContentType.download || options.headers.get('Content-type') === ContentType.audio)
+            resolve(needAllResponseContent ? resClone : res.blob())
+
+          else resolve(needAllResponseContent ? resClone : res.json())
+        })
+        .catch((err) => {
+          if (!silent)
+            Toast.notify({ type: 'error', message: err })
+          reject(err)
+        })
+    }),
+  ]) as Promise<T>
+}
+
+export 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 || ''),
@@ -323,7 +448,7 @@
   })
 }
 
-export const ssePost = async (
+export const ssePost = (
   url: string,
   fetchOptions: FetchOptionType,
   otherOptions: IOtherOptions,
@@ -350,28 +475,19 @@
     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)
+  }, fetchOptions)
 
-  const contentType = (options.headers as Headers).get('Content-Type')
+  const contentType = options.headers.get('Content-Type')
   if (!contentType)
-    (options.headers as Headers).set('Content-Type', ContentType.json)
+    options.headers.set('Content-Type', ContentType.json)
 
   getAbortController?.(abortController)
 
@@ -384,12 +500,12 @@
   if (body)
     options.body = JSON.stringify(body)
 
-  const accessToken = await getAccessToken(isPublicAPI)
-    ; (options.headers as Headers).set('Authorization', `Bearer ${accessToken}`)
+  const accessToken = getAccessToken(isPublicAPI)
+  options.headers.set('Authorization', `Bearer ${accessToken}`)
 
   globalThis.fetch(urlWithPrefix, options as RequestInit)
     .then((res) => {
-      if (!/^[23]\d{2}$/.test(String(res.status))) {
+      if (!/^(2|3)\d{2}$/.test(String(res.status))) {
         if (res.status === 401) {
           refreshAccessTokenOrRelogin(TIME_OUT).then(() => {
             ssePost(url, fetchOptions, otherOptions)
@@ -424,31 +540,7 @@
           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,
-      )
+      }, onCompleted, onThought, onMessageEnd, onMessageReplace, onFile, onWorkflowStarted, onWorkflowFinished, onNodeStarted, onNodeFinished, onIterationStart, onIterationNext, onIterationFinish, onNodeRetry, onParallelBranchStarted, onParallelBranchFinished, onTextChunk, onTTSChunk, onTTSEnd, onTextReplace)
     }).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 })
@@ -466,7 +558,7 @@
     const errResp: Response = err as any
     if (errResp.status === 401) {
       const [parseErr, errRespData] = await asyncRunSafe<ResponseError>(errResp.json())
-      const loginUrl = `${WEB_PREFIX}/signin`
+      const loginUrl = `${globalThis.location.origin}/signin`
       if (parseErr) {
         globalThis.location.href = loginUrl
         return Promise.reject(err)
@@ -498,11 +590,11 @@
         return Promise.reject(err)
       }
       if (code === 'not_init_validated' && IS_CE_EDITION) {
-        globalThis.location.href = `${WEB_PREFIX}/init`
+        globalThis.location.href = `${globalThis.location.origin}/init`
         return Promise.reject(err)
       }
       if (code === 'not_setup' && IS_CE_EDITION) {
-        globalThis.location.href = `${WEB_PREFIX}/install`
+        globalThis.location.href = `${globalThis.location.origin}/install`
         return Promise.reject(err)
       }
 
@@ -510,7 +602,7 @@
       const [refreshErr] = await asyncRunSafe(refreshAccessTokenOrRelogin(TIME_OUT))
       if (refreshErr === null)
         return baseFetch<T>(url, options, otherOptionsForBaseFetch)
-      if (!location.pathname.includes('/signin') || !IS_CE_EDITION) {
+      if (location.pathname !== '/signin' || !IS_CE_EDITION) {
         globalThis.location.href = loginUrl
         return Promise.reject(err)
       }
@@ -541,18 +633,8 @@
   return get<T>(url, options, { ...otherOptions, isPublicAPI: true })
 }
 
-// For Marketplace API
-export 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 API
-export 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) => {

--
Gitblit v1.8.0