From 77950e48c76f4a3b29d01831d43039caba29888a Mon Sep 17 00:00:00 2001
From: wwf <1971391498@qq.com>
Date: 星期二, 18 十一月 2025 14:12:42 +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