2024-11-05 12:38:31 +08:00
import { refreshAccessTokenOrRelogin } from './refresh-token'
2023-06-05 09:55:03 +08:00
import { API_PREFIX , IS_CE_EDITION , PUBLIC_API_PREFIX } from '@/config'
2023-05-15 08:51:32 +08:00
import Toast from '@/app/components/base/toast'
2024-06-24 12:29:14 +08:00
import type { AnnotationReply , MessageEnd , MessageReplace , ThoughtItem } from '@/app/components/base/chat/chat/type'
2024-01-23 19:31:56 +08:00
import type { VisionFile } from '@/types/app'
2024-04-08 18:51:46 +08:00
import type {
2024-05-27 21:57:08 +08:00
IterationFinishedResponse ,
2024-09-08 13:14:11 +08:00
IterationNextResponse ,
2024-05-27 21:57:08 +08:00
IterationStartedResponse ,
2024-04-08 18:51:46 +08:00
NodeFinishedResponse ,
NodeStartedResponse ,
2024-09-10 15:23:16 +08:00
ParallelBranchFinishedResponse ,
ParallelBranchStartedResponse ,
2024-04-08 18:51:46 +08:00
TextChunkResponse ,
TextReplaceResponse ,
WorkflowFinishedResponse ,
WorkflowStartedResponse ,
} from '@/types/workflow'
2024-05-15 16:14:49 +08:00
import { removeAccessToken } from '@/app/components/share/utils'
2023-05-15 08:51:32 +08:00
const TIME_OUT = 100000
const ContentType = {
json : 'application/json' ,
stream : 'text/event-stream' ,
2024-07-09 11:33:58 +08:00
audio : 'audio/mpeg' ,
2023-05-15 08:51:32 +08:00
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 = {
2023-06-12 16:37:03 +08:00
conversationId? : string
taskId? : string
2023-05-15 08:51:32 +08:00
messageId : string
errorMessage? : string
2023-08-18 10:39:05 +08:00
errorCode? : string
2023-05-15 08:51:32 +08:00
}
export type IOnData = ( message : string , isFirstMessage : boolean , moreInfo : IOnDataMoreInfo ) = > void
2023-07-27 13:27:34 +08:00
export type IOnThought = ( though : ThoughtItem ) = > void
2024-01-23 19:31:56 +08:00
export type IOnFile = ( file : VisionFile ) = > void
2023-09-09 19:17:12 +08:00
export type IOnMessageEnd = ( messageEnd : MessageEnd ) = > void
2023-11-06 19:36:32 +08:00
export type IOnMessageReplace = ( messageReplace : MessageReplace ) = > void
2023-12-18 15:41:24 +08:00
export type IOnAnnotationReply = ( messageReplace : AnnotationReply ) = > void
2024-04-08 18:51:46 +08:00
export type IOnCompleted = ( hasError? : boolean , errorMessage? : string ) = > void
2023-08-18 10:39:05 +08:00
export type IOnError = ( msg : string , code? : string ) = > void
2023-05-15 08:51:32 +08:00
2024-04-08 18:51:46 +08:00
export type IOnWorkflowStarted = ( workflowStarted : WorkflowStartedResponse ) = > void
export type IOnWorkflowFinished = ( workflowFinished : WorkflowFinishedResponse ) = > void
export type IOnNodeStarted = ( nodeStarted : NodeStartedResponse ) = > void
export type IOnNodeFinished = ( nodeFinished : NodeFinishedResponse ) = > void
2024-05-27 21:57:08 +08:00
export type IOnIterationStarted = ( workflowStarted : IterationStartedResponse ) = > void
2024-09-08 13:14:11 +08:00
export type IOnIterationNext = ( workflowStarted : IterationNextResponse ) = > void
2024-05-27 21:57:08 +08:00
export type IOnIterationFinished = ( workflowFinished : IterationFinishedResponse ) = > void
2024-09-10 15:23:16 +08:00
export type IOnParallelBranchStarted = ( parallelBranchStarted : ParallelBranchStartedResponse ) = > void
export type IOnParallelBranchFinished = ( parallelBranchFinished : ParallelBranchFinishedResponse ) = > void
2024-04-08 18:51:46 +08:00
export type IOnTextChunk = ( textChunk : TextChunkResponse ) = > void
2024-07-09 11:33:58 +08:00
export type IOnTTSChunk = ( messageId : string , audioStr : string , audioType? : string ) = > void
export type IOnTTSEnd = ( messageId : string , audioStr : string , audioType? : string ) = > void
2024-04-08 18:51:46 +08:00
export type IOnTextReplace = ( textReplace : TextReplaceResponse ) = > void
export type IOtherOptions = {
2023-05-15 08:51:32 +08:00
isPublicAPI? : boolean
2023-07-07 17:50:42 +08:00
bodyStringify? : boolean
2023-05-15 08:51:32 +08:00
needAllResponseContent? : boolean
2023-07-07 17:50:42 +08:00
deleteContentType? : boolean
2024-04-08 18:51:46 +08:00
silent? : boolean
2023-05-15 08:51:32 +08:00
onData? : IOnData // for stream
2023-07-27 13:27:34 +08:00
onThought? : IOnThought
2024-01-23 19:31:56 +08:00
onFile? : IOnFile
2023-09-09 19:17:12 +08:00
onMessageEnd? : IOnMessageEnd
2023-11-06 19:36:32 +08:00
onMessageReplace? : IOnMessageReplace
2023-05-15 08:51:32 +08:00
onError? : IOnError
onCompleted? : IOnCompleted // for stream
getAbortController ? : ( abortController : AbortController ) = > void
2024-04-08 18:51:46 +08:00
onWorkflowStarted? : IOnWorkflowStarted
onWorkflowFinished? : IOnWorkflowFinished
onNodeStarted? : IOnNodeStarted
onNodeFinished? : IOnNodeFinished
2024-05-27 21:57:08 +08:00
onIterationStart? : IOnIterationStarted
2024-09-08 13:14:11 +08:00
onIterationNext? : IOnIterationNext
2024-05-27 21:57:08 +08:00
onIterationFinish? : IOnIterationFinished
2024-09-10 15:23:16 +08:00
onParallelBranchStarted? : IOnParallelBranchStarted
onParallelBranchFinished? : IOnParallelBranchFinished
2024-04-08 18:51:46 +08:00
onTextChunk? : IOnTextChunk
2024-07-09 11:33:58 +08:00
onTTSChunk? : IOnTTSChunk
onTTSEnd? : IOnTTSEnd
2024-04-08 18:51:46 +08:00
onTextReplace? : IOnTextReplace
2023-05-15 08:51:32 +08:00
}
2023-09-15 20:54:20 +08:00
type ResponseError = {
code : string
message : string
status : number
}
type FetchOptionType = Omit < RequestInit , ' body ' > & {
params? : Record < string , any >
body? : BodyInit | Record < string , any > | null
}
2023-05-15 08:51:32 +08:00
function unicodeToChar ( text : string ) {
2023-07-27 13:27:34 +08:00
if ( ! text )
return ''
2023-05-15 08:51:32 +08:00
return text . replace ( /\\u[0-9a-f]{4}/g , ( _match , p1 ) = > {
return String . fromCharCode ( parseInt ( p1 , 16 ) )
} )
}
2024-05-15 16:14:49 +08:00
function requiredWebSSOLogin() {
globalThis . location . href = ` /webapp-signin?redirect_url= ${ globalThis . location . pathname } `
}
2023-05-15 08:51:32 +08:00
export function format ( text : string ) {
let res = text . trim ( )
2023-06-05 09:55:03 +08:00
if ( res . startsWith ( '\n' ) )
2023-05-15 08:51:32 +08:00
res = res . replace ( '\n' , '' )
2023-06-05 09:55:03 +08:00
2023-05-15 08:51:32 +08:00
return res . replaceAll ( '\n' , '<br/>' ) . replaceAll ( '```' , '' )
}
2024-04-08 18:51:46 +08:00
const handleStream = (
response : Response ,
onData : IOnData ,
onCompleted? : IOnCompleted ,
onThought? : IOnThought ,
onMessageEnd? : IOnMessageEnd ,
onMessageReplace? : IOnMessageReplace ,
onFile? : IOnFile ,
onWorkflowStarted? : IOnWorkflowStarted ,
onWorkflowFinished? : IOnWorkflowFinished ,
onNodeStarted? : IOnNodeStarted ,
onNodeFinished? : IOnNodeFinished ,
2024-05-27 21:57:08 +08:00
onIterationStart? : IOnIterationStarted ,
2024-09-08 13:14:11 +08:00
onIterationNext? : IOnIterationNext ,
2024-05-27 21:57:08 +08:00
onIterationFinish? : IOnIterationFinished ,
2024-09-10 15:23:16 +08:00
onParallelBranchStarted? : IOnParallelBranchStarted ,
onParallelBranchFinished? : IOnParallelBranchFinished ,
2024-04-08 18:51:46 +08:00
onTextChunk? : IOnTextChunk ,
2024-07-09 11:33:58 +08:00
onTTSChunk? : IOnTTSChunk ,
onTTSEnd? : IOnTTSEnd ,
2024-04-08 18:51:46 +08:00
onTextReplace? : IOnTextReplace ,
) = > {
2023-05-15 08:51:32 +08:00
if ( ! response . ok )
throw new Error ( 'Network response was not ok' )
2023-09-20 12:27:06 +08:00
const reader = response . body ? . getReader ( )
2023-05-15 08:51:32 +08:00
const decoder = new TextDecoder ( 'utf-8' )
let buffer = ''
2023-09-20 12:27:06 +08:00
let bufferObj : Record < string , any >
2023-05-15 08:51:32 +08:00
let isFirstMessage = true
function read() {
let hasError = false
2023-09-20 12:27:06 +08:00
reader ? . read ( ) . then ( ( result : any ) = > {
2023-05-15 08:51:32 +08:00
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:
2023-06-05 09:55:03 +08:00
try {
2023-09-20 12:27:06 +08:00
bufferObj = JSON . parse ( message . substring ( 6 ) ) as Record < string , any > // remove data: and parse as json
2023-06-05 09:55:03 +08:00
}
catch ( e ) {
// mute handle message cut off
onData ( '' , isFirstMessage , {
conversationId : bufferObj?.conversation_id ,
2024-01-26 15:08:37 +08:00
messageId : bufferObj?.message_id ,
2023-06-05 09:55:03 +08:00
} )
return
}
2023-05-22 16:05:08 +08:00
if ( bufferObj . status === 400 || ! bufferObj . event ) {
2023-05-15 08:51:32 +08:00
onData ( '' , false , {
conversationId : undefined ,
messageId : '' ,
2023-09-20 12:27:06 +08:00
errorMessage : bufferObj?.message ,
errorCode : bufferObj?.code ,
2023-05-15 08:51:32 +08:00
} )
hasError = true
2024-04-08 18:51:46 +08:00
onCompleted ? . ( true , bufferObj ? . message )
2023-05-15 08:51:32 +08:00
return
}
2024-01-23 19:31:56 +08:00
if ( bufferObj . event === 'message' || bufferObj . event === 'agent_message' ) {
2024-09-08 13:14:11 +08:00
// can not use format here. Because message is splitted.
2023-07-27 13:27:34 +08:00
onData ( unicodeToChar ( bufferObj . answer ) , isFirstMessage , {
conversationId : bufferObj.conversation_id ,
taskId : bufferObj.task_id ,
messageId : bufferObj.id ,
} )
isFirstMessage = false
}
else if ( bufferObj . event === 'agent_thought' ) {
2023-09-20 12:27:06 +08:00
onThought ? . ( bufferObj as ThoughtItem )
2023-07-27 13:27:34 +08:00
}
2024-01-23 19:31:56 +08:00
else if ( bufferObj . event === 'message_file' ) {
onFile ? . ( bufferObj as VisionFile )
}
2023-09-09 19:17:12 +08:00
else if ( bufferObj . event === 'message_end' ) {
2023-09-20 12:27:06 +08:00
onMessageEnd ? . ( bufferObj as MessageEnd )
2023-09-09 19:17:12 +08:00
}
2023-11-06 19:36:32 +08:00
else if ( bufferObj . event === 'message_replace' ) {
onMessageReplace ? . ( bufferObj as MessageReplace )
}
2024-04-08 18:51:46 +08:00
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 )
}
2024-05-27 21:57:08 +08:00
else if ( bufferObj . event === 'iteration_started' ) {
onIterationStart ? . ( bufferObj as IterationStartedResponse )
}
else if ( bufferObj . event === 'iteration_next' ) {
2024-09-08 13:14:11 +08:00
onIterationNext ? . ( bufferObj as IterationNextResponse )
2024-05-27 21:57:08 +08:00
}
else if ( bufferObj . event === 'iteration_completed' ) {
onIterationFinish ? . ( bufferObj as IterationFinishedResponse )
}
2024-09-10 15:23:16 +08:00
else if ( bufferObj . event === 'parallel_branch_started' ) {
onParallelBranchStarted ? . ( bufferObj as ParallelBranchStartedResponse )
}
else if ( bufferObj . event === 'parallel_branch_finished' ) {
onParallelBranchFinished ? . ( bufferObj as ParallelBranchFinishedResponse )
}
2024-04-08 18:51:46 +08:00
else if ( bufferObj . event === 'text_chunk' ) {
onTextChunk ? . ( bufferObj as TextChunkResponse )
}
else if ( bufferObj . event === 'text_replace' ) {
onTextReplace ? . ( bufferObj as TextReplaceResponse )
}
2024-07-09 11:33:58 +08:00
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 )
}
2023-05-15 08:51:32 +08:00
}
} )
buffer = lines [ lines . length - 1 ]
2023-06-05 09:55:03 +08:00
}
catch ( e ) {
2023-05-15 08:51:32 +08:00
onData ( '' , false , {
conversationId : undefined ,
messageId : '' ,
2023-06-05 09:55:03 +08:00
errorMessage : ` ${ e } ` ,
2023-05-15 08:51:32 +08:00
} )
hasError = true
2024-04-08 18:51:46 +08:00
onCompleted ? . ( true , e as string )
2023-05-15 08:51:32 +08:00
return
}
2023-06-05 09:55:03 +08:00
if ( ! hasError )
2023-05-15 08:51:32 +08:00
read ( )
} )
}
read ( )
}
2023-09-15 20:54:20 +08:00
const baseFetch = < T > (
2023-05-18 10:50:34 +08:00
url : string ,
2023-09-15 20:54:20 +08:00
fetchOptions : FetchOptionType ,
2023-05-18 10:50:34 +08:00
{
isPublicAPI = false ,
2023-07-07 17:50:42 +08:00
bodyStringify = true ,
2023-06-05 09:55:03 +08:00
needAllResponseContent ,
2023-07-07 17:50:42 +08:00
deleteContentType ,
2024-01-25 12:36:55 +08:00
getAbortController ,
2024-04-08 18:51:46 +08:00
silent ,
2023-06-05 09:55:03 +08:00
} : IOtherOptions ,
2023-09-15 20:54:20 +08:00
) : Promise < T > = > {
const options : typeof baseOptions & FetchOptionType = Object . assign ( { } , baseOptions , fetchOptions )
2024-01-25 12:36:55 +08:00
if ( getAbortController ) {
const abortController = new AbortController ( )
getAbortController ( abortController )
options . signal = abortController . signal
}
2023-05-15 08:51:32 +08:00
if ( isPublicAPI ) {
const sharedToken = globalThis . location . pathname . split ( '/' ) . slice ( - 1 ) [ 0 ]
2023-07-11 15:21:20 +08:00
const accessToken = localStorage . getItem ( 'token' ) || JSON . stringify ( { [ sharedToken ] : '' } )
let accessTokenJson = { [ sharedToken ] : '' }
try {
accessTokenJson = JSON . parse ( accessToken )
}
catch ( e ) {
}
options . headers . set ( 'Authorization' , ` Bearer ${ accessTokenJson [ sharedToken ] } ` )
2023-05-15 08:51:32 +08:00
}
2023-09-25 12:49:16 +08:00
else {
const accessToken = localStorage . getItem ( 'console_token' ) || ''
options . headers . set ( 'Authorization' , ` Bearer ${ accessToken } ` )
}
2023-05-15 08:51:32 +08:00
2023-07-07 17:50:42 +08:00
if ( deleteContentType ) {
options . headers . delete ( 'Content-Type' )
}
else {
const contentType = options . headers . get ( 'Content-Type' )
if ( ! contentType )
options . headers . set ( 'Content-Type' , ContentType . json )
}
2023-06-05 09:55:03 +08:00
const urlPrefix = isPublicAPI ? PUBLIC_API_PREFIX : API_PREFIX
2024-11-08 17:17:34 +08:00
let urlWithPrefix = ( url . startsWith ( 'http://' ) || url . startsWith ( 'https://' ) )
? url
: ` ${ urlPrefix } ${ url . startsWith ( '/' ) ? url : ` / ${ url } ` } `
2023-05-15 08:51:32 +08:00
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
}
2023-07-07 17:50:42 +08:00
if ( body && bodyStringify )
2023-05-15 08:51:32 +08:00
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 ) = > {
2023-11-21 20:25:23 +08:00
globalThis . fetch ( urlWithPrefix , options as RequestInit )
2023-09-15 20:54:20 +08:00
. then ( ( res ) = > {
2023-05-15 08:51:32 +08:00
const resClone = res . clone ( )
// Error handler
2023-09-15 20:54:20 +08:00
if ( ! /^(2|3)\d{2}$/ . test ( String ( res . status ) ) ) {
2023-05-15 08:51:32 +08:00
const bodyJson = res . json ( )
switch ( res . status ) {
2024-11-05 12:38:31 +08:00
case 401 :
return Promise . reject ( resClone )
2023-05-15 08:51:32 +08:00
case 403 :
2023-09-15 20:54:20 +08:00
bodyJson . then ( ( data : ResponseError ) = > {
2024-04-08 18:51:46 +08:00
if ( ! silent )
Toast . notify ( { type : 'error' , message : data.message } )
2023-09-15 20:54:20 +08:00
if ( data . code === 'already_setup' )
globalThis . location . href = ` ${ globalThis . location . origin } /signin `
2023-05-15 08:51:32 +08:00
} )
break
// fall through
default :
2023-09-15 20:54:20 +08:00
bodyJson . then ( ( data : ResponseError ) = > {
2024-04-08 18:51:46 +08:00
if ( ! silent )
Toast . notify ( { type : 'error' , message : data.message } )
2023-05-15 08:51:32 +08:00
} )
}
return Promise . reject ( resClone )
}
// handle delete api. Delete api not return content.
if ( res . status === 204 ) {
2023-06-05 09:55:03 +08:00
resolve ( { result : 'success' } )
2023-05-15 08:51:32 +08:00
return
}
// return data
2024-07-09 11:33:58 +08:00
if ( options . headers . get ( 'Content-type' ) === ContentType . download || options . headers . get ( 'Content-type' ) === ContentType . audio )
resolve ( needAllResponseContent ? resClone : res.blob ( ) )
2023-05-15 08:51:32 +08:00
2024-07-09 11:33:58 +08:00
else resolve ( needAllResponseContent ? resClone : res.json ( ) )
2023-05-15 08:51:32 +08:00
} )
. catch ( ( err ) = > {
2024-04-08 18:51:46 +08:00
if ( ! silent )
Toast . notify ( { type : 'error' , message : err } )
2023-05-15 08:51:32 +08:00
reject ( err )
} )
} ) ,
2023-09-15 20:54:20 +08:00
] ) as Promise < T >
2023-05-15 08:51:32 +08:00
}
2024-03-03 12:45:06 +08:00
export const upload = ( options : any , isPublicAPI? : boolean , url? : string , searchParams? : string ) : Promise < any > = > {
2023-11-13 22:32:39 +08:00
const urlPrefix = isPublicAPI ? PUBLIC_API_PREFIX : API_PREFIX
let token = ''
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 ) {
}
token = accessTokenJson [ sharedToken ]
}
else {
const accessToken = localStorage . getItem ( 'console_token' ) || ''
token = accessToken
}
2023-05-15 08:51:32 +08:00
const defaultOptions = {
method : 'POST' ,
2024-03-03 12:45:06 +08:00
url : ( url ? ` ${ urlPrefix } ${ url } ` : ` ${ urlPrefix } /files/upload ` ) + ( searchParams || '' ) ,
2023-09-25 12:49:16 +08:00
headers : {
2023-11-13 22:32:39 +08:00
Authorization : ` Bearer ${ token } ` ,
2023-09-25 12:49:16 +08:00
} ,
2023-05-15 08:51:32 +08:00
data : { } ,
}
options = {
. . . defaultOptions ,
. . . options ,
headers : { . . . defaultOptions . headers , . . . options . headers } ,
2023-06-05 09:55:03 +08:00
}
return new Promise ( ( resolve , reject ) = > {
2023-05-15 08:51:32 +08:00
const xhr = options . xhr
2023-06-05 09:55:03 +08:00
xhr . open ( options . method , options . url )
for ( const key in options . headers )
xhr . setRequestHeader ( key , options . headers [ key ] )
2023-05-15 08:51:32 +08:00
xhr . withCredentials = true
xhr . responseType = 'json'
xhr . onreadystatechange = function ( ) {
if ( xhr . readyState === 4 ) {
2023-06-05 09:55:03 +08:00
if ( xhr . status === 201 )
2023-05-15 08:51:32 +08:00
resolve ( xhr . response )
2023-06-05 09:55:03 +08:00
else
2023-05-15 08:51:32 +08:00
reject ( xhr )
}
}
xhr . upload . onprogress = options . onprogress
xhr . send ( options . data )
} )
}
2024-04-08 18:51:46 +08:00
export const ssePost = (
url : string ,
fetchOptions : FetchOptionType ,
2024-11-05 12:38:31 +08:00
otherOptions : IOtherOptions ,
) = > {
const {
2024-04-08 18:51:46 +08:00
isPublicAPI = false ,
onData ,
onCompleted ,
onThought ,
onFile ,
onMessageEnd ,
onMessageReplace ,
onWorkflowStarted ,
onWorkflowFinished ,
onNodeStarted ,
onNodeFinished ,
2024-05-27 21:57:08 +08:00
onIterationStart ,
onIterationNext ,
onIterationFinish ,
2024-09-10 15:23:16 +08:00
onParallelBranchStarted ,
onParallelBranchFinished ,
2024-04-08 18:51:46 +08:00
onTextChunk ,
2024-07-09 11:33:58 +08:00
onTTSChunk ,
onTTSEnd ,
2024-04-08 18:51:46 +08:00
onTextReplace ,
onError ,
getAbortController ,
2024-11-05 12:38:31 +08:00
} = otherOptions
2023-05-15 08:51:32 +08:00
const abortController = new AbortController ( )
const options = Object . assign ( { } , baseOptions , {
method : 'POST' ,
signal : abortController.signal ,
} , fetchOptions )
2023-07-07 17:50:42 +08:00
const contentType = options . headers . get ( 'Content-Type' )
if ( ! contentType )
options . headers . set ( 'Content-Type' , ContentType . json )
2023-05-15 08:51:32 +08:00
getAbortController ? . ( abortController )
const urlPrefix = isPublicAPI ? PUBLIC_API_PREFIX : API_PREFIX
2024-11-08 17:17:34 +08:00
const urlWithPrefix = ( url . startsWith ( 'http://' ) || url . startsWith ( 'https://' ) )
? url
: ` ${ urlPrefix } ${ url . startsWith ( '/' ) ? url : ` / ${ url } ` } `
2023-05-15 08:51:32 +08:00
const { body } = options
if ( body )
options . body = JSON . stringify ( body )
2023-09-15 20:54:20 +08:00
globalThis . fetch ( urlWithPrefix , options as RequestInit )
. then ( ( res ) = > {
if ( ! /^(2|3)\d{2}$/ . test ( String ( res . status ) ) ) {
2024-11-05 12:38:31 +08:00
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' )
}
2023-05-15 08:51:32 +08:00
return
}
return handleStream ( res , ( str : string , isFirstMessage : boolean , moreInfo : IOnDataMoreInfo ) = > {
if ( moreInfo . errorMessage ) {
2023-08-18 10:39:05 +08:00
onError ? . ( moreInfo . errorMessage , moreInfo . errorCode )
2024-08-12 11:25:10 +08:00
// 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' ) )
2023-07-13 10:32:45 +08:00
Toast . notify ( { type : 'error' , message : moreInfo.errorMessage } )
2023-05-15 08:51:32 +08:00
return
}
onData ? . ( str , isFirstMessage , moreInfo )
2024-09-10 15:23:16 +08:00
} , onCompleted , onThought , onMessageEnd , onMessageReplace , onFile , onWorkflowStarted , onWorkflowFinished , onNodeStarted , onNodeFinished , onIterationStart , onIterationNext , onIterationFinish , onParallelBranchStarted , onParallelBranchFinished , onTextChunk , onTTSChunk , onTTSEnd , onTextReplace )
2023-05-15 08:51:32 +08:00
} ) . catch ( ( e ) = > {
2024-08-12 11:25:10 +08:00
if ( e . toString ( ) !== 'AbortError: The user aborted a request.' && ! e . toString ( ) . errorMessage . includes ( 'TypeError: Cannot assign to read only property' ) )
2023-07-13 10:32:45 +08:00
Toast . notify ( { type : 'error' , message : e } )
2023-05-15 08:51:32 +08:00
onError ? . ( e )
} )
}
2023-09-15 20:54:20 +08:00
// base request
export const request = < T > ( url : string , options = { } , otherOptions? : IOtherOptions ) = > {
2024-11-05 12:38:31 +08:00
return new Promise < T > ( ( resolve , reject ) = > {
const otherOptionsForBaseFetch = otherOptions || { }
baseFetch < T > ( url , options , otherOptionsForBaseFetch ) . then ( resolve ) . catch ( ( errResp ) = > {
if ( errResp ? . status === 401 ) {
return refreshAccessTokenOrRelogin ( TIME_OUT ) . then ( ( ) = > {
baseFetch < T > ( url , options , otherOptionsForBaseFetch ) . then ( resolve ) . catch ( reject )
} ) . catch ( ( ) = > {
const {
isPublicAPI = false ,
silent ,
} = otherOptionsForBaseFetch
const bodyJson = errResp . json ( )
if ( isPublicAPI ) {
return bodyJson . then ( ( data : ResponseError ) = > {
if ( data . code === 'web_sso_auth_required' )
requiredWebSSOLogin ( )
if ( data . code === 'unauthorized' ) {
removeAccessToken ( )
globalThis . location . reload ( )
}
return Promise . reject ( data )
} )
}
const loginUrl = ` ${ globalThis . location . origin } /signin `
bodyJson . then ( ( data : ResponseError ) = > {
if ( data . code === 'init_validate_failed' && IS_CE_EDITION && ! silent )
Toast . notify ( { type : 'error' , message : data.message , duration : 4000 } )
else if ( data . code === 'not_init_validated' && IS_CE_EDITION )
globalThis . location . href = ` ${ globalThis . location . origin } /init `
else if ( data . code === 'not_setup' && IS_CE_EDITION )
globalThis . location . href = ` ${ globalThis . location . origin } /install `
else if ( location . pathname !== '/signin' || ! IS_CE_EDITION )
globalThis . location . href = loginUrl
else if ( ! silent )
Toast . notify ( { type : 'error' , message : data.message } )
} ) . catch ( ( ) = > {
// Handle any other errors
globalThis . location . href = loginUrl
} )
} )
}
else {
reject ( errResp )
}
} )
} )
2023-05-15 08:51:32 +08:00
}
2023-09-15 20:54:20 +08:00
// request methods
export const get = < T > ( url : string , options = { } , otherOptions? : IOtherOptions ) = > {
return request < T > ( url , Object . assign ( { } , options , { method : 'GET' } ) , otherOptions )
2023-05-15 08:51:32 +08:00
}
// For public API
2023-09-15 20:54:20 +08:00
export const getPublic = < T > ( url : string , options = { } , otherOptions? : IOtherOptions ) = > {
return get < T > ( url , options , { . . . otherOptions , isPublicAPI : true } )
2023-05-15 08:51:32 +08:00
}
2023-09-15 20:54:20 +08:00
export const post = < T > ( url : string , options = { } , otherOptions? : IOtherOptions ) = > {
return request < T > ( url , Object . assign ( { } , options , { method : 'POST' } ) , otherOptions )
2023-05-15 08:51:32 +08:00
}
2023-09-15 20:54:20 +08:00
export const postPublic = < T > ( url : string , options = { } , otherOptions? : IOtherOptions ) = > {
return post < T > ( url , options , { . . . otherOptions , isPublicAPI : true } )
2023-05-15 08:51:32 +08:00
}
2023-09-15 20:54:20 +08:00
export const put = < T > ( url : string , options = { } , otherOptions? : IOtherOptions ) = > {
return request < T > ( url , Object . assign ( { } , options , { method : 'PUT' } ) , otherOptions )
2023-05-15 08:51:32 +08:00
}
2023-09-15 20:54:20 +08:00
export const putPublic = < T > ( url : string , options = { } , otherOptions? : IOtherOptions ) = > {
return put < T > ( url , options , { . . . otherOptions , isPublicAPI : true } )
2023-05-15 08:51:32 +08:00
}
2023-09-15 20:54:20 +08:00
export const del = < T > ( url : string , options = { } , otherOptions? : IOtherOptions ) = > {
return request < T > ( url , Object . assign ( { } , options , { method : 'DELETE' } ) , otherOptions )
2023-05-15 08:51:32 +08:00
}
2023-09-15 20:54:20 +08:00
export const delPublic = < T > ( url : string , options = { } , otherOptions? : IOtherOptions ) = > {
return del < T > ( url , options , { . . . otherOptions , isPublicAPI : true } )
2023-05-15 08:51:32 +08:00
}
2023-09-15 20:54:20 +08:00
export const patch = < T > ( url : string , options = { } , otherOptions? : IOtherOptions ) = > {
return request < T > ( url , Object . assign ( { } , options , { method : 'PATCH' } ) , otherOptions )
2023-05-15 08:51:32 +08:00
}
2023-09-15 20:54:20 +08:00
export const patchPublic = < T > ( url : string , options = { } , otherOptions? : IOtherOptions ) = > {
return patch < T > ( url , options , { . . . otherOptions , isPublicAPI : true } )
2023-05-15 08:51:32 +08:00
}