diff --git a/ui/src/api/API_README.md b/ui/src/api/API_README.md index 95259558ae6..6e1bbd6c3a8 100644 --- a/ui/src/api/API_README.md +++ b/ui/src/api/API_README.md @@ -5,6 +5,7 @@ Admin API;Chat 目录作为独立体系预留。 ```text src/api/ +├── constants.ts # Admin 与 Chat 的 API base 路径常量 ├── admin/ │ ├── auth/ # Admin 登录认证与当前用户接口 │ │ └── types.ts # 认证 API 与认证 Store 共用类型 @@ -154,8 +155,12 @@ API 枚举与类型统一在 `src/api` 范围内管理,相关规则由本文 ## 请求约定 -- Admin 业务接口只声明相对资源路径,`/admin/api` 等部署前缀由 `core/request.ts` 统一处理。 -- Admin Router 与请求客户端直接读取 `window.MaxKB` 运行时路径配置;`Window` 和 +- `constants.ts` 统一导出 `ADMIN_API_BASE_PATH` 和 `CHAT_API_BASE_PATH`,分别优先读取 + `window.MaxKB.prefix` 和 `window.MaxKB.chatPrefix`,再回退到 `VITE_BASE_PATH` 和各自默认路径; + 去掉末尾斜杠后追加 `/api`。Admin、Chat 请求客户端和会话流式请求复用这些常量。 +- Admin 普通业务接口只声明相对资源路径,由 `core/request.ts` 的 Axios baseURL 处理部署前缀; + 流式接口显式传入对应的 API base 常量。 +- Admin Router 直接读取 `window.MaxKB` 运行时路径配置,请求客户端通过上述常量读取;`Window` 和 `MaxKBRuntimeConfig` 的全局类型统一声明在根目录 `env.d.ts`。 - Admin 普通 JSON 请求使用 Axios;`request.ts` 导出 Axios 实例以及 `promise`、`get`、 `post`、`put`、`del`、流式响应 `postStream` 和 Blob 文件 `downloadRequest` 请求封装。 @@ -171,6 +176,7 @@ API 枚举与类型统一在 `src/api` 范围内管理,相关规则由本文 登录页。 - loading 不作为 API 函数参数,由调用接口的页面或 Store 管理。 - 流式 POST 请求使用 `postStream` 返回原始 `Response`,由业务组件按具体协议解析数据块; + 参数顺序为 `postStream(base, path, data?, config?)`,`config.signal` 用于取消请求。 鉴权、语言请求头和错误状态仍由请求基础设施统一处理。 - 上传、下载和其他特殊请求在真实需求出现时独立设计,不提前塞入普通 JSON 请求客户端。 diff --git a/ui/src/api/admin/core/request.ts b/ui/src/api/admin/core/request.ts index 5fbcfbb6f54..7c85eaddfff 100644 --- a/ui/src/api/admin/core/request.ts +++ b/ui/src/api/admin/core/request.ts @@ -6,9 +6,9 @@ import { useStore } from '@/stores' import type { ApiResponse, LoadingTarget } from './types' import type { Dict } from '@/api/types' import { MsgError } from '@/utils/message' +import { ADMIN_API_BASE_PATH } from '@/api/constants' const DEFAULT_TIMEOUT = 30 * 60 * 1_000 // 30 minutes -const ADMIN_BASE_PATH = window.MaxKB?.prefix || import.meta.env.VITE_BASE_PATH || '/admin/' interface ExportRequestConfig extends AxiosRequestConfig { skipGlobalErrorMessage?: boolean @@ -128,7 +128,7 @@ async function downloadExportResponse(response: AxiosResponse, fileName: s return true } -export const request = axios.create({ baseURL: `${ADMIN_BASE_PATH.replace(/\/+$/, '')}/api`, timeout: DEFAULT_TIMEOUT, withCredentials: false }) +export const request = axios.create({ baseURL: ADMIN_API_BASE_PATH, timeout: DEFAULT_TIMEOUT, withCredentials: false }) request.interceptors.request.use(setRequestHeaders) @@ -195,29 +195,9 @@ export function get(url: string, params?: Dict, loading?: return promise(request.get>(url, { params, timeout }), loading) } -/** 发送指定方法的 Blob 请求并触发浏览器下载。 */ -export async function downloadRequest( - url: string, - method: string, - data?: unknown, - params?: Dict, - loading?: LoadingTarget, -): Promise { - startLoading(loading) - try { - const response = await request.request({ - url, - method, - data, - params, - responseType: 'blob', - skipGlobalErrorMessage: true, - } as ExportRequestConfig) - - return downloadExportResponse(response, 'download') - } finally { - finishLoading(loading) - } +/** 发送 POST 请求。 */ +export function post(url: string, data?: TData, params?: Dict, loading?: LoadingTarget, timeout?: number) { + return promise(request.post>(url, data, { params, timeout }), loading) } /** 发送 GET 请求并将 Blob 响应下载为文件。 */ @@ -250,13 +230,33 @@ export async function postExportExcel( } } -/** 发送 POST 请求。 */ -export function post(url: string, data?: TData, params?: Dict, loading?: LoadingTarget, timeout?: number) { - return promise(request.post>(url, data, { params, timeout }), loading) +/** 发送指定方法的 Blob 请求并触发浏览器下载。 */ +export async function downloadRequest( + url: string, + method: string, + data?: unknown, + params?: Dict, + loading?: LoadingTarget, +): Promise { + startLoading(loading) + try { + const response = await request.request({ + url, + method, + data, + params, + responseType: 'blob', + skipGlobalErrorMessage: true, + } as ExportRequestConfig) + + return downloadExportResponse(response, 'download') + } finally { + finishLoading(loading) + } } /** 发送 POST 请求并返回可逐块读取的原始响应。 */ -export function postStream(base: string, path: string, data?: unknown) { +export function postStream(base: string, path: string, data?: unknown, config?: StreamRequestConfig): Promise { const { auth, user } = useStore() const headers: Record = { 'Content-Type': 'application/json' } if (auth.token) { @@ -269,6 +269,7 @@ export function postStream(base: string, path: string, data?: unknown) { method: 'POST', headers, body: data === undefined ? undefined : JSON.stringify(data), + signal: config?.signal, }) } diff --git a/ui/src/api/admin/workspace/application/application.ts b/ui/src/api/admin/workspace/application/application.ts index 99e590a5e01..4112bad256f 100644 --- a/ui/src/api/admin/workspace/application/application.ts +++ b/ui/src/api/admin/workspace/application/application.ts @@ -2,6 +2,7 @@ import { del, get, getExportFile, post, postStream, put } from '../../core/reque import type { ParamsPage, ResponsePage } from '../../core/types' import type { ApplicationDetail, ApplicationFormPayload, Dict, PromptGeneratePayload } from '@/api/types' import { getWorkspaceId } from '@/utils/resource-context' +import { ADMIN_API_BASE_PATH } from '@/api/constants' const getPrefix = () => { const workspaceId = getWorkspaceId() @@ -74,7 +75,7 @@ const putApplicationPublish = (applicationId: string) => { /** 使用指定模型流式生成或优化系统提示词。 */ const postPromptGenerate = (applicationId: string, modelId: string, payload: PromptGeneratePayload, signal?: AbortSignal) => { - return postStream(`${getPrefix()}/${applicationId}/model/${modelId}/prompt_generate`, payload, { signal }) + return postStream(ADMIN_API_BASE_PATH, `${getPrefix()}/${applicationId}/model/${modelId}/prompt_generate`, payload, { signal }) } export default { diff --git a/ui/src/api/chat/core/request.ts b/ui/src/api/chat/core/request.ts index b9728d770bb..5f6a9229892 100644 --- a/ui/src/api/chat/core/request.ts +++ b/ui/src/api/chat/core/request.ts @@ -9,9 +9,9 @@ import { useStore } from '@/stores' import type { ApiResponse, LoadingTarget } from './types' import type { Dict } from '@/api/types' import { MsgError } from '@/utils/message' +import { CHAT_API_BASE_PATH } from '@/api/constants' const DEFAULT_TIMEOUT = 30 * 60 * 1_000 // 30 minutes -const CHAT_BASE_PATH = window.MaxKB?.chatPrefix || import.meta.env.VITE_BASE_PATH || '/chat/' function setRequestHeaders(config: InternalAxiosRequestConfig) { const { auth, user } = useStore() @@ -64,7 +64,7 @@ async function getResponseErrorMessage(error: unknown) { } export const request = axios.create({ - baseURL: `${CHAT_BASE_PATH.replace(/\/+$/, '')}/api`, + baseURL: CHAT_API_BASE_PATH, timeout: DEFAULT_TIMEOUT, withCredentials: false, }) diff --git a/ui/src/api/constants.ts b/ui/src/api/constants.ts new file mode 100644 index 00000000000..08c27516a2a --- /dev/null +++ b/ui/src/api/constants.ts @@ -0,0 +1,6 @@ +/** Admin 与 Chat 的 API 部署路径配置。 */ +const trimTrailingSlash = (value: string) => value.replace(/\/+$/, '') + +export const ADMIN_API_BASE_PATH = trimTrailingSlash(window.MaxKB?.prefix || import.meta.env.VITE_BASE_PATH || '/admin/') + '/api' + +export const CHAT_API_BASE_PATH = trimTrailingSlash(window.MaxKB?.chatPrefix || import.meta.env.VITE_BASE_PATH || '/chat/') + '/api' diff --git a/ui/src/components/conversation/api/index.ts b/ui/src/components/conversation/api/index.ts index 6d1df02cb67..7560c8a7d6c 100644 --- a/ui/src/components/conversation/api/index.ts +++ b/ui/src/components/conversation/api/index.ts @@ -21,13 +21,7 @@ import { } from '@/api/chat/core/request' import { useStore } from '@/stores' import { getWorkspaceId } from '@/utils/resource-context' - -const trimTrailingSlash = (value: string) => value.replace(/\/+$/, '') -const adminApiBase = - trimTrailingSlash(window.MaxKB?.prefix || import.meta.env.VITE_BASE_PATH || '/admin/') + '/api' - -const chatApiBase = - trimTrailingSlash(window.MaxKB?.chatPrefix || import.meta.env.VITE_BASE_PATH || '/chat/') + '/api' +import { ADMIN_API_BASE_PATH as adminApiBase, CHAT_API_BASE_PATH as chatApiBase } from '@/api/constants' /** 发送流式 POST 请求,返回原始 `Response` 供 SSE 读取。 */ function postStream(base: string, path: string, data?: unknown) {