import type { IOnCompleted, IOnData, IOnError, IOnMessageReplace, IOtherOptions } from './base' import type { FormData as HumanInputFormData } from '@/app/(humanInputLayout)/form/[token]/form' import type { FeedbackType } from '@/app/components/base/chat/chat/type' import type { ChatConfig } from '@/app/components/base/chat/types' import type { AccessMode } from '@/models/access-control' import type { AppConversationData, AppData, AppMeta, ConversationItem } from '@/models/share' import { WEB_APP_SHARE_CODE_HEADER_NAME } from '@/config' import { del as consoleDel, get as consoleGet, patch as consolePatch, post as consolePost, delPublic as del, getPublic as get, patchPublic as patch, postPublic as post, ssePost, upload, } from './base' import { getWebAppAccessToken } from './webapp-auth' export enum AppSourceType { webApp = 'webApp', installedApp = 'installedApp', tryApp = 'tryApp', } const apiPrefix = { [AppSourceType.webApp]: '', [AppSourceType.installedApp]: 'installed-apps', [AppSourceType.tryApp]: 'trial-apps', } function getIsPublicAPI(appSourceType: AppSourceType) { return appSourceType === AppSourceType.webApp } function getAction(action: 'get' | 'post' | 'del' | 'patch', appSourceType: AppSourceType) { const isNeedLogin = !getIsPublicAPI(appSourceType) switch (action) { case 'get': return isNeedLogin ? consoleGet : get case 'post': return isNeedLogin ? consolePost : post case 'patch': return isNeedLogin ? consolePatch : patch case 'del': return isNeedLogin ? consoleDel : del } } export function getUrl(url: string, appSourceType: AppSourceType, appId: string) { const hasPrefix = appSourceType !== AppSourceType.webApp return hasPrefix ? `${apiPrefix[appSourceType]}/${appId}/${url.startsWith('/') ? url.slice(1) : url}` : url } export const stopChatMessageResponding = async ( appId: string, taskId: string, appSourceType: AppSourceType, installedAppId = '', ) => { return getAction( 'post', appSourceType, )(getUrl(`chat-messages/${taskId}/stop`, appSourceType, installedAppId)) } export const sendCompletionMessage = async ( body: Record, { onData, onCompleted, onError, onMessageReplace, getAbortController, }: { onData: IOnData onCompleted: IOnCompleted onError: IOnError onMessageReplace: IOnMessageReplace getAbortController?: (abortController: AbortController) => void }, appSourceType: AppSourceType, installedAppId = '', ) => { return ssePost( getUrl('completion-messages', appSourceType, installedAppId), { body: { ...body, response_mode: 'streaming', }, }, { onData, onCompleted, isPublicAPI: getIsPublicAPI(appSourceType), onError, onMessageReplace, getAbortController, }, ) } export const sendWorkflowMessage = async ( body: Record, otherOptions: IOtherOptions, appSourceType: AppSourceType, appId = '', ) => { return ssePost( getUrl('workflows/run', appSourceType, appId), { body: { ...body, response_mode: 'streaming', }, }, { ...otherOptions, isPublicAPI: getIsPublicAPI(appSourceType), }, ) } export const stopWorkflowMessage = async ( _appId: string, taskId: string, appSourceType: AppSourceType, installedAppId = '', ) => { if (!taskId) return return getAction( 'post', appSourceType, )(getUrl(`workflows/tasks/${taskId}/stop`, appSourceType, installedAppId)) } export const fetchAppInfo = async () => { return get('/site') as Promise } export const fetchConversations = async ( appSourceType: AppSourceType, installedAppId = '', last_id?: string, pinned?: boolean, limit?: number, ) => { return getAction('get', appSourceType)(getUrl('conversations', appSourceType, installedAppId), { params: { limit: limit || 20, ...(last_id ? { last_id } : {}), ...(pinned !== undefined ? { pinned } : {}), }, }) as Promise } export const pinConversation = async ( appSourceType: AppSourceType, installedAppId = '', id: string, ) => { return getAction( 'patch', appSourceType, )(getUrl(`conversations/${id}/pin`, appSourceType, installedAppId)) } export const unpinConversation = async ( appSourceType: AppSourceType, installedAppId = '', id: string, ) => { return getAction( 'patch', appSourceType, )(getUrl(`conversations/${id}/unpin`, appSourceType, installedAppId)) } export const delConversation = async ( appSourceType: AppSourceType, installedAppId = '', id: string, ) => { return getAction( 'del', appSourceType, )(getUrl(`conversations/${id}`, appSourceType, installedAppId)) } export const renameConversation = async ( appSourceType: AppSourceType, installedAppId = '', id: string, name: string, ) => { return getAction('post', appSourceType)( getUrl(`conversations/${id}/name`, appSourceType, installedAppId), { body: { name } }, ) } export const generationConversationName = async ( appSourceType: AppSourceType, installedAppId = '', id: string, ) => { return getAction('post', appSourceType)( getUrl(`conversations/${id}/name`, appSourceType, installedAppId), { body: { auto_generate: true } }, ) as Promise } export const fetchChatList = async ( conversationId: string, appSourceType: AppSourceType, installedAppId = '', ) => { return getAction('get', appSourceType)(getUrl('messages', appSourceType, installedAppId), { params: { conversation_id: conversationId, limit: 20, last_id: '' }, }) as any } // Abandoned API interface // export const fetchAppVariables = async () => { // return get(`variables`) // } // init value. wait for server update export const fetchAppParams = async (appSourceType: AppSourceType, appId = '') => { return getAction( 'get', appSourceType, )(getUrl('parameters', appSourceType, appId)) as Promise } export const fetchWebSAMLSSOUrl = async (appCode: string, redirectUrl: string) => { return getAction('get', AppSourceType.webApp)( getUrl('/enterprise/sso/saml/login', AppSourceType.webApp, ''), { params: { app_code: appCode, redirect_url: redirectUrl, }, }, ) as Promise<{ url: string }> } export const fetchWebOIDCSSOUrl = async (appCode: string, redirectUrl: string) => { return getAction('get', AppSourceType.webApp)( getUrl('/enterprise/sso/oidc/login', AppSourceType.webApp, ''), { params: { app_code: appCode, redirect_url: redirectUrl, }, }, ) as Promise<{ url: string }> } export const fetchWebOAuth2SSOUrl = async (appCode: string, redirectUrl: string) => { return getAction('get', AppSourceType.webApp)( getUrl('/enterprise/sso/oauth2/login', AppSourceType.webApp, ''), { params: { app_code: appCode, redirect_url: redirectUrl, }, }, ) as Promise<{ url: string }> } export const fetchMembersSAMLSSOUrl = async (appCode: string, redirectUrl: string) => { return getAction('get', AppSourceType.webApp)( getUrl('/enterprise/sso/members/saml/login', AppSourceType.webApp, ''), { params: { app_code: appCode, redirect_url: redirectUrl, }, }, ) as Promise<{ url: string }> } export const fetchMembersOIDCSSOUrl = async (appCode: string, redirectUrl: string) => { return getAction('get', AppSourceType.webApp)( getUrl('/enterprise/sso/members/oidc/login', AppSourceType.webApp, ''), { params: { app_code: appCode, redirect_url: redirectUrl, }, }, ) as Promise<{ url: string }> } export const fetchMembersOAuth2SSOUrl = async (appCode: string, redirectUrl: string) => { return getAction('get', AppSourceType.webApp)( getUrl('/enterprise/sso/members/oauth2/login', AppSourceType.webApp, ''), { params: { app_code: appCode, redirect_url: redirectUrl, }, }, ) as Promise<{ url: string }> } export const fetchAppMeta = async (appSourceType: AppSourceType, installedAppId = '') => { return getAction( 'get', appSourceType, )(getUrl('meta', appSourceType, installedAppId)) as Promise } export const updateFeedback = async ( { url, body }: { url: string; body: FeedbackType }, appSourceType: AppSourceType, installedAppId = '', ) => { return getAction('post', appSourceType)(getUrl(url, appSourceType, installedAppId), { body }) } export const fetchMoreLikeThis = async ( messageId: string, appSourceType: AppSourceType, installedAppId = '', ) => { return getAction('get', appSourceType)( getUrl(`/messages/${messageId}/more-like-this`, appSourceType, installedAppId), { params: { response_mode: 'blocking', }, }, ) } export const saveMessage = ( messageId: string, appSourceType: AppSourceType, installedAppId = '', ) => { return getAction('post', appSourceType)( getUrl('/saved-messages', appSourceType, installedAppId), { body: { message_id: messageId } }, ) } export const fetchSavedMessage = async (appSourceType: AppSourceType, installedAppId = '') => { return getAction('get', appSourceType)( getUrl('/saved-messages', appSourceType, installedAppId), {}, { silent: true, }, ) } export const removeMessage = ( messageId: string, appSourceType: AppSourceType, installedAppId = '', ) => { return getAction( 'del', appSourceType, )(getUrl(`/saved-messages/${messageId}`, appSourceType, installedAppId)) } export const fetchSuggestedQuestions = ( messageId: string, appSourceType: AppSourceType, installedAppId = '', ) => { return getAction( 'get', appSourceType, )(getUrl(`/messages/${messageId}/suggested-questions`, appSourceType, installedAppId)) } export const audioToText = ( appSourceType: AppSourceType, appId: string | undefined, body: FormData, signal?: AbortSignal, ) => { if (appSourceType !== AppSourceType.webApp && !appId) throw new Error('An app ID is required for console speech-to-text requests.') return getAction('post', appSourceType)( getUrl('/audio-to-text', appSourceType, appId ?? ''), { body, signal }, { bodyStringify: false, deleteContentType: true }, ) as Promise<{ text: string }> } export const textToAudioStream = ( url: string, appSourceType: AppSourceType, header: { content_type: string }, body: { streaming: boolean voice?: string message_id?: string text?: string | null | undefined }, ) => { return getAction('post', appSourceType)(url, { body, header }, { needAllResponseContent: true }) } export const fetchAccessToken = async ({ userId, appCode, }: { userId?: string appCode: string }) => { const headers = new Headers() headers.append(WEB_APP_SHARE_CODE_HEADER_NAME, appCode) const accessToken = getWebAppAccessToken() if (accessToken) headers.append('Authorization', `Bearer ${accessToken}`) const params = new URLSearchParams() if (userId) params.append('user_id', userId) const url = `/passport?${params.toString()}` return get<{ access_token: string }>(url, { headers }) as Promise<{ access_token: string }> } export const getUserCanAccess = (appId: string, isInstalledApp: boolean) => { if (isInstalledApp) return consoleGet<{ result: boolean }>(`/enterprise/webapp/permission?appId=${appId}`) return get<{ result: boolean }>(`/webapp/permission?appId=${appId}`) } export const getAppAccessModeByAppCode = (appCode: string) => { return get<{ accessMode: AccessMode }>(`/webapp/access-mode?appCode=${appCode}`) } export const getHumanInputForm = (token: string) => { return get(`/form/human_input/${token}`) } type HumanInputFormUploadTokenResponse = { upload_token: string expires_at: number } type HumanInputFormLocalFileUploadResponse = { created_at: number created_by: string extension: string id: string mime_type: string name: string preview_url: string | null size: number source_url: string } type HumanInputFormRemoteFileUploadResponse = { created_at: number created_by: string extension: string id: string mime_type: string name: string size: number url: string } type HumanInputFormLocalFileUploadParams = { formToken: string file: File onProgressCallback: (progress: number) => void onSuccessCallback: (res: HumanInputFormLocalFileUploadResponse) => void onErrorCallback: (error?: unknown) => void } const humanInputFormUploadTokenCache = new Map() const UPLOAD_TOKEN_REFRESH_BUFFER_SECONDS = 30 const getHumanInputFormUploadToken = async (formToken: string) => { const cachedToken = humanInputFormUploadTokenCache.get(formToken) const now = Math.floor(Date.now() / 1000) if (cachedToken && cachedToken.expires_at > now + UPLOAD_TOKEN_REFRESH_BUFFER_SECONDS) return cachedToken.upload_token const tokenResponse = await post( `/form/human_input/${formToken}/upload-token`, ) humanInputFormUploadTokenCache.set(formToken, tokenResponse) return tokenResponse.upload_token } const uploadHumanInputFormFile = async ( formToken: string, formData: FormData, onProgress?: (e: ProgressEvent) => void, ) => { const uploadToken = await getHumanInputFormUploadToken(formToken) return upload( { xhr: new XMLHttpRequest(), data: formData, headers: { Authorization: `bearer ${uploadToken}`, }, onprogress: onProgress, }, true, '/human-input-forms/files', ) } export const uploadHumanInputFormLocalFile = async ({ formToken, file, onProgressCallback, onSuccessCallback, onErrorCallback, }: HumanInputFormLocalFileUploadParams) => { const formData = new FormData() formData.append('file', file) const onProgress = (e: ProgressEvent) => { if (e.lengthComputable) { const percent = Math.floor((e.loaded / e.total) * 100) onProgressCallback(percent) } } try { const response = (await uploadHumanInputFormFile( formToken, formData, onProgress, )) as HumanInputFormLocalFileUploadResponse onSuccessCallback(response) } catch (error) { onErrorCallback(error) } } export const uploadHumanInputFormRemoteFileInfo = async ( formToken: string, url: string, ): Promise => { const formData = new FormData() formData.append('url', url) return uploadHumanInputFormFile( formToken, formData, ) as Promise } export const submitHumanInputForm = ( token: string, data: { inputs: Record action: string }, ) => { return post(`/form/human_input/${token}`, { body: data }) }