import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse, AxiosError, } from 'axios'; import { ApiRespProviderRequesters, ApiRespProviderRequester, ApiRespProviderLLMModels, ApiRespProviderLLMModel, LLMModel, ApiRespPipelines, Pipeline, ApiRespPlatformAdapters, ApiRespPlatformAdapter, ApiRespPlatformBots, ApiRespPlatformBot, Bot, ApiRespPlugins, ApiRespPlugin, ApiRespPluginConfig, PluginReorderElement, AsyncTaskCreatedResp, ApiRespSystemInfo, ApiRespAsyncTasks, ApiRespAsyncTask, ApiRespUserToken, MarketPluginResponse, GetPipelineResponseData, GetPipelineMetadataResponseData } from '@/app/infra/entities/api'; import { notification } from 'antd'; type JSONValue = string | number | boolean | JSONObject | JSONArray | null; interface JSONObject { [key: string]: JSONValue; } type JSONArray = Array; export interface ResponseData { code: number; message: string; data: T; timestamp: number; } export interface RequestConfig extends AxiosRequestConfig { isSSR?: boolean; // 服务端渲染标识 retry?: number; // 重试次数 } class HttpClient { private instance: AxiosInstance; private disableToken: boolean = false; // 暂不需要SSR // private ssrInstance: AxiosInstance | null = null constructor(baseURL?: string, disableToken?: boolean) { this.instance = axios.create({ baseURL: baseURL || this.getBaseUrl(), timeout: 15000, headers: { 'Content-Type': 'application/json', }, }); this.disableToken = disableToken || false; this.initInterceptors(); } // 兜底URL,如果使用未配置会走到这里 private getBaseUrl(): string { return 'http://localhost:5300'; // NOT IMPLEMENT if (typeof window === 'undefined') { // 服务端环境 return ''; } // 客户端环境 return ''; } // 获取Session private async getSession() { // NOT IMPLEMENT return ''; } // 同步获取Session private getSessionSync() { // NOT IMPLEMENT return localStorage.getItem('token'); } // 拦截器配置 private initInterceptors() { // 请求拦截 this.instance.interceptors.request.use( async (config) => { // 服务端请求自动携带 cookie, Langbot暂时用不到SSR相关 // if (typeof window === 'undefined' && config.isSSR) { } // cookie not required // const { cookies } = await import('next/headers') // config.headers.Cookie = cookies().toString() // 客户端添加认证头 if (typeof window !== 'undefined' && !this.disableToken) { const session = this.getSessionSync(); config.headers.Authorization = `Bearer ${session}`; } return config; }, (error) => Promise.reject(error), ); // 响应拦截 this.instance.interceptors.response.use( (response: AxiosResponse) => { // 响应拦截处理写在这里,暂无业务需要 return response; }, (error: AxiosError) => { // 统一错误处理 if (error.response) { const { status, data } = error.response; const errMessage = data?.message || error.message; switch (status) { case 401: window.location.href = '/login'; break; case 403: console.error('Permission denied:', errMessage); break; case 500: // TODO 弹Toast窗 // NOTE: move to component layer for customized message? notification.error({ message: '服务器错误', description: errMessage, placement: 'bottomRight', }); console.error('Server error:', errMessage); break; } return Promise.reject({ code: data?.code || status, message: errMessage, data: data?.data || null, }); } return Promise.reject({ code: -1, message: error.message || 'Network Error', data: null, }); }, ); } // 转换下划线为驼峰 private convertKeysToCamel(obj: JSONValue): JSONValue { if (Array.isArray(obj)) { return obj.map((v) => this.convertKeysToCamel(v)); } else if (obj !== null && typeof obj === 'object') { return Object.keys(obj).reduce((acc, key) => { const camelKey = key.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase(), ); acc[camelKey] = this.convertKeysToCamel((obj as JSONObject)[key]); return acc; }, {} as JSONObject); } return obj; } // 核心请求方法 public async request(config: RequestConfig): Promise { try { // 这里未来如果需要SSR可以将前面替换为SSR的instance const instance = config.isSSR ? this.instance : this.instance; const response = await instance.request>(config); return response.data.data; } catch (error) { return this.handleError(error as object); } } private handleError(error: object): never { if (axios.isCancel(error)) { throw { code: -2, message: 'Request canceled', data: null }; } throw error; } // 快捷方法 public get( url: string, params?: object, config?: RequestConfig, ) { return this.request({ method: 'get', url, params, ...config }); } public post(url: string, data?: object, config?: RequestConfig) { return this.request({ method: 'post', url, data, ...config }); } public put(url: string, data?: object, config?: RequestConfig) { return this.request({ method: 'put', url, data, ...config }); } public delete(url: string, config?: RequestConfig) { return this.request({ method: 'delete', url, ...config }); } // real api request implementation // ============ Provider API ============ public getProviderRequesters(): Promise { return this.get('/api/v1/provider/requesters'); } public getProviderRequester(name: string): Promise { return this.get(`/api/v1/provider/requesters/${name}`); } public getProviderRequesterIconURL(name: string): string { return this.instance.defaults.baseURL + `/api/v1/provider/requesters/${name}/icon`; } // ============ Provider Model LLM ============ public getProviderLLMModels(): Promise { return this.get('/api/v1/provider/models/llm'); } public getProviderLLMModel(uuid: string): Promise { return this.get(`/api/v1/provider/models/llm/${uuid}`); } public createProviderLLMModel(model: LLMModel): Promise { return this.post('/api/v1/provider/models/llm', model); } public deleteProviderLLMModel(uuid: string): Promise { return this.delete(`/api/v1/provider/models/llm/${uuid}`); } // ============ Pipeline API ============ public getGeneralPipelineMetadata(): Promise { // as designed, this method will be deprecated, and only for developer to check the prefered config schema return this.get('/api/v1/pipelines/_/metadata'); } public getPipelines(): Promise { return this.get('/api/v1/pipelines'); } public getPipeline(uuid: string): Promise { return this.get(`/api/v1/pipelines/${uuid}`); } public createPipeline(pipeline: Pipeline): Promise { return this.post('/api/v1/pipelines', pipeline); } public updatePipeline(uuid: string, pipeline: Pipeline): Promise { return this.put(`/api/v1/pipelines/${uuid}`, pipeline); } public deletePipeline(uuid: string): Promise { return this.delete(`/api/v1/pipelines/${uuid}`); } // ============ Platform API ============ public getAdapters(): Promise { return this.get('/api/v1/platform/adapters'); } public getAdapter(name: string): Promise { return this.get(`/api/v1/platform/adapters/${name}`); } public getAdapterIconURL(name: string): string { return this.instance.defaults.baseURL + `/api/v1/platform/adapters/${name}/icon`; } // ============ Platform Bots ============ public getBots(): Promise { return this.get('/api/v1/platform/bots'); } public getBot(uuid: string): Promise { return this.get(`/api/v1/platform/bots/${uuid}`); } public createBot(bot: Bot): Promise { return this.post('/api/v1/platform/bots', bot); } public updateBot(uuid: string, bot: Bot): Promise { return this.put(`/api/v1/platform/bots/${uuid}`, bot); } public deleteBot(uuid: string): Promise { return this.delete(`/api/v1/platform/bots/${uuid}`); } // ============ Plugins API ============ public getPlugins(): Promise { return this.get('/api/v1/plugins'); } public getPlugin(author: string, name: string): Promise { return this.get(`/api/v1/plugins/${author}/${name}`); } public getPluginConfig( author: string, name: string, ): Promise { return this.get(`/api/v1/plugins/${author}/${name}/config`); } public updatePluginConfig( author: string, name: string, config: object, ): Promise { return this.put(`/api/v1/plugins/${author}/${name}/config`, config); } public togglePlugin( author: string, name: string, target_enabled: boolean, ): Promise { return this.put(`/api/v1/plugins/${author}/${name}/toggle`, { target_enabled, }); } public reorderPlugins(plugins: PluginReorderElement[]): Promise { return this.post('/api/v1/plugins/reorder', plugins); } public updatePlugin( author: string, name: string, ): Promise { return this.post(`/api/v1/plugins/${author}/${name}/update`); } public getMarketPlugins( page: number, page_size: number, query: string, ): Promise { return this.post(`/api/v1/market/plugins`, { page, page_size, query, sort_by: 'stars', sort_order: 'DESC', }); } public installPluginFromGithub( source: string, ): Promise { return this.post('/api/v1/plugins/install/github', { source }); } public removePlugin( author: string, name: string, ): Promise { return this.delete(`/api/v1/plugins/${author}/${name}`); } // ============ System API ============ public getSystemInfo(): Promise { return this.get('/api/v1/system/info'); } public getAsyncTasks(): Promise { return this.get('/api/v1/system/tasks'); } public getAsyncTask(id: number): Promise { return this.get(`/api/v1/system/tasks/${id}`); } // ============ User API ============ public checkIfInited(): Promise<{ initialized: boolean }> { return this.get('/api/v1/user/init'); } public initUser(user: string, password: string): Promise { return this.post('/api/v1/user/init', { user, password }); } public authUser(user: string, password: string): Promise { return this.post('/api/v1/user/auth', { user, password }); } public checkUserToken(): Promise { return this.get('/api/v1/user/check-token'); } } // export const httpClient = new HttpClient("https://version-4.langbot.dev"); export const httpClient = new HttpClient("http://localhost:5300"); // 临时写法,未来两种Client都继承自HttpClient父类,不允许共享方法 export const spaceClient = new HttpClient('https://space.langbot.app');