first relase
This commit is contained in:
parent
d3a42ba6e9
commit
c6afce22ed
288 changed files with 55505 additions and 192 deletions
460
service/base.ts
Normal file
460
service/base.ts
Normal file
|
|
@ -0,0 +1,460 @@
|
|||
import { removeAccessToken } from '@/app/components/share/utils';
|
||||
import { API_PREFIX, IS_CE_EDITION, PUBLIC_API_PREFIX } from '@/config';
|
||||
|
||||
import { asyncRunSafe } from '@/utils';
|
||||
import { basePath } from '@/utils/var';
|
||||
import type { FetchOptionType, ResponseError } from './fetch';
|
||||
import { ContentType, base, getAccessToken, getBaseOptions } from './fetch';
|
||||
const TIME_OUT = 100000;
|
||||
|
||||
export type IOnDataMoreInfo = {
|
||||
conversationId?: string;
|
||||
taskId?: string;
|
||||
messageId: string;
|
||||
errorMessage?: string;
|
||||
errorCode?: string;
|
||||
};
|
||||
|
||||
export type IOnData = (
|
||||
message: string,
|
||||
isFirstMessage: boolean,
|
||||
moreInfo: IOnDataMoreInfo,
|
||||
) => void;
|
||||
|
||||
const baseFetch = base;
|
||||
|
||||
type UploadOptions = {
|
||||
xhr: XMLHttpRequest;
|
||||
method: string;
|
||||
url?: string;
|
||||
headers?: Record<string, string>;
|
||||
data: FormData;
|
||||
onprogress?: (this: XMLHttpRequest, ev: ProgressEvent<EventTarget>) => void;
|
||||
};
|
||||
|
||||
type UploadResponse = {
|
||||
id: string;
|
||||
[key: string]: unknown;
|
||||
};
|
||||
|
||||
export const upload = async (
|
||||
options: UploadOptions,
|
||||
isPublicAPI?: boolean,
|
||||
url?: string,
|
||||
searchParams?: string,
|
||||
): Promise<UploadResponse> => {
|
||||
const urlPrefix = isPublicAPI ? PUBLIC_API_PREFIX : API_PREFIX;
|
||||
const token = await getAccessToken(isPublicAPI);
|
||||
const defaultOptions = {
|
||||
method: 'POST',
|
||||
url:
|
||||
(url ? `${urlPrefix}${url}` : `${urlPrefix}/files/upload`) +
|
||||
(searchParams || ''),
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
};
|
||||
const mergedOptions = {
|
||||
...defaultOptions,
|
||||
...options,
|
||||
url: options.url || defaultOptions.url,
|
||||
headers: { ...defaultOptions.headers, ...options.headers } as Record<
|
||||
string,
|
||||
string
|
||||
>,
|
||||
};
|
||||
return new Promise((resolve, reject) => {
|
||||
const xhr = mergedOptions.xhr;
|
||||
xhr.open(mergedOptions.method, mergedOptions.url);
|
||||
for (const key in mergedOptions.headers)
|
||||
xhr.setRequestHeader(key, mergedOptions.headers[key]);
|
||||
|
||||
xhr.withCredentials = true;
|
||||
xhr.responseType = 'json';
|
||||
xhr.onreadystatechange = function () {
|
||||
if (xhr.readyState === 4) {
|
||||
if (xhr.status === 201) resolve(xhr.response);
|
||||
else reject(xhr);
|
||||
}
|
||||
};
|
||||
if (mergedOptions.onprogress)
|
||||
xhr.upload.onprogress = mergedOptions.onprogress;
|
||||
xhr.send(mergedOptions.data);
|
||||
});
|
||||
};
|
||||
|
||||
export const ssePost = async (
|
||||
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,
|
||||
onDataSourceNodeProcessing,
|
||||
onDataSourceNodeCompleted,
|
||||
onDataSourceNodeError,
|
||||
} = otherOptions;
|
||||
const abortController = new AbortController();
|
||||
|
||||
const token = localStorage.getItem('console_token');
|
||||
|
||||
const baseOptions = getBaseOptions();
|
||||
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 = await getAccessToken(isPublicAPI);
|
||||
(options.headers as Headers).set('Authorization', `Bearer ${accessToken}`);
|
||||
|
||||
globalThis
|
||||
.fetch(urlWithPrefix, options as RequestInit)
|
||||
.then((res) => {
|
||||
if (!/^[23]\d{2}$/.test(String(res.status))) {
|
||||
if (res.status === 401) {
|
||||
if (isPublicAPI) {
|
||||
res.json().then((data: { code?: string; message?: string }) => {
|
||||
if (isPublicAPI) {
|
||||
if (data.code === 'web_app_access_denied')
|
||||
requiredWebSSOLogin(data.message, 403);
|
||||
|
||||
if (data.code === 'web_sso_auth_required') {
|
||||
removeAccessToken();
|
||||
requiredWebSSOLogin();
|
||||
}
|
||||
|
||||
if (data.code === 'unauthorized') {
|
||||
removeAccessToken();
|
||||
requiredWebSSOLogin();
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
refreshAccessTokenOrRelogin(TIME_OUT)
|
||||
.then(() => {
|
||||
ssePost(url, fetchOptions, otherOptions);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
});
|
||||
}
|
||||
} 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,
|
||||
onDataSourceNodeProcessing,
|
||||
onDataSourceNodeCompleted,
|
||||
onDataSourceNodeError,
|
||||
);
|
||||
})
|
||||
.catch((e) => {
|
||||
if (
|
||||
e.toString() !== 'AbortError: The user aborted a request.' &&
|
||||
!e.toString().includes('TypeError: Cannot assign to read only property')
|
||||
)
|
||||
Toast.notify({ type: 'error', message: e });
|
||||
onError?.(e);
|
||||
});
|
||||
};
|
||||
|
||||
// base request
|
||||
export 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}${basePath}/signin`;
|
||||
if (parseErr) {
|
||||
globalThis.location.href = loginUrl;
|
||||
return Promise.reject(err);
|
||||
}
|
||||
if (/\/login/.test(url)) return Promise.reject(errRespData);
|
||||
// special code
|
||||
const { code, message } = errRespData;
|
||||
// webapp sso
|
||||
if (code === 'web_app_access_denied') {
|
||||
requiredWebSSOLogin(message, 403);
|
||||
return Promise.reject(err);
|
||||
}
|
||||
if (code === 'web_sso_auth_required') {
|
||||
removeAccessToken();
|
||||
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();
|
||||
requiredWebSSOLogin();
|
||||
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}${basePath}/init`;
|
||||
return Promise.reject(err);
|
||||
}
|
||||
if (code === 'not_setup' && IS_CE_EDITION) {
|
||||
globalThis.location.href = `${globalThis.location.origin}${basePath}/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 !== `${basePath}/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 methods
|
||||
export const get = <T>(
|
||||
url: string,
|
||||
options = {},
|
||||
otherOptions?: IOtherOptions,
|
||||
) => {
|
||||
return request<T>(
|
||||
url,
|
||||
Object.assign({}, options, { method: 'GET' }),
|
||||
otherOptions,
|
||||
);
|
||||
};
|
||||
|
||||
// For public API
|
||||
export const getPublic = <T>(
|
||||
url: string,
|
||||
options = {},
|
||||
otherOptions?: IOtherOptions,
|
||||
) => {
|
||||
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,
|
||||
) => {
|
||||
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 });
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue