feat: 实现基础设施层

axios 请求封装及七个业务模块 API,Pinia 状态管理(auth/system/models/tools),Mock 适配器与数据,以及流式对话、轮询、倒计时组合式函数。
This commit is contained in:
caoxiaozhu
2026-07-10 16:45:06 +08:00
parent 8aa67003c8
commit ca9e05aa91
17 changed files with 1197 additions and 0 deletions

View File

@@ -0,0 +1,80 @@
import { get, post, del } from '../request'
import type { CompareTask, CompareModelRef } from '@/types'
/** 推理/对比任务列表 */
export const getCompareList = () => get<CompareTask[]>('/model-compare')
/** 任务详情 */
export const getCompare = (id: string | number) => get<CompareTask>(`/model-compare/${id}`)
/** 创建任务 */
export const createCompare = (data: Partial<CompareTask>) =>
post<{ id: string | number }>('/model-compare', data)
/** 删除任务 */
export const deleteCompare = (id: string | number) => del(`/model-compare/${id}`)
/** 更新任务加载状态 */
export const updateLoadStatus = (id: string | number, load_status: any) =>
post(`/model-compare/${id}/load-status`, { load_status })
/** 查询任务加载状态 */
export const getLoadStatus = (id: string | number) =>
get<{ all_ready: boolean; loaded_models: any[] }>(`/model-compare/${id}/load-status`)
/** 停止所有旧模型服务 */
export const stopAllModels = () => post('/model-compare/all/stop-all')
/** 启动单个模型服务 */
export const startModel = (id: string | number, data: Partial<CompareModelRef>) =>
post<{ pid: number; port: number }>(`/model-compare/${id}/start-model`, data)
/** 按 PID 停止模型进程 */
export const stopModelByPid = (pid: number) =>
post('/model-compare/stop-by-pid', { pid })
/** 加载任务 */
export const loadCompare = (id: string | number) => post(`/model-compare/${id}/load`)
/** 卸载任务 */
export const unloadCompare = (id: string | number) => post(`/model-compare/${id}/unload`)
/** 流式对话mock 模式下返回非流式响应,调用方需兼容) */
export const streamChat = async (data: any): Promise<any> => {
// Mock 环境:返回非流式响应对象,调用方检测 content-type 决定如何处理
const resp = await post<{ response: string }>('/model-compare/stream-chat', data)
return {
ok: true,
body: {
getReader: () => {
// 模拟流式读取:把整个响应切成小块逐个返回
const text = resp?.response || ''
const encoder = new TextEncoder()
const chunks = [text.slice(0, text.length / 3), text.slice(text.length / 3, 2 * text.length / 3), text.slice(2 * text.length / 3)]
let i = 0
return {
read: async () => {
if (i >= chunks.length) return { done: true, value: undefined }
const value = encoder.encode(chunks[i++])
return { done: false, value }
},
}
},
},
}
}
/** 非流式对话(按端口代理) */
export const chatWithPort = (data: any) => post('/model-compare/chat-with-port', data)
/** 批量对话API 类型模型) */
export const batchChat = (data: any) => post('/model-chat/batch', data)
/** 本地 transformers 模型对话 */
export const localChat = (data: any) => post('/model-chat/local/chat', data)
/** 预加载本地模型 */
export const preloadLocalModel = (data: any) => post('/model-chat/local/preload', data)
/** 预加载已训练模型 */
export const preloadTrainedModel = (data: any) => post('/model-chat/trained/preload', data)

View File

@@ -0,0 +1,40 @@
import { get, post, put, del } from '../request'
import type { DatasetItem } from '@/types'
/** 数据集列表 */
export const getDatasetList = () => get<DatasetItem[]>('/dataset-manage')
/** 数据集详情 */
export const getDataset = (id: string | number) => get<DatasetItem>(`/dataset-manage/${id}`)
/** 创建数据集 */
export const createDataset = (data: Partial<DatasetItem>) =>
post<{ id: string | number }>('/dataset-manage', data)
/** 更新数据集 */
export const updateDataset = (id: string | number, data: Partial<DatasetItem>) =>
put(`/dataset-manage/${id}`, data)
/** 删除数据集 */
export const deleteDataset = (id: string | number) => del(`/dataset-manage/${id}`)
/** 上传数据集文件multipart字段名 files */
export const uploadDatasetFiles = (datasetId: string | number, files: File[]) => {
const formData = new FormData()
files.forEach((f) => formData.append('files', f))
return post(`/dataset-manage/upload/${datasetId}`, formData, {
headers: { 'Content-Type': 'multipart/form-data' },
})
}
/** 预览数据集文件内容 */
export const previewDatasetFile = (fileId: string | number) =>
get<{ content: string }>(`/dataset-manage/preview/${fileId}`)
/** 下载文件 URL */
export const downloadFileUrl = (datasetId: string | number, fileId: string | number) =>
`/api/dataset-manage/download/${datasetId}/${fileId}`
/** 打包下载数据集 URL */
export const downloadDatasetUrl = (datasetId: string | number) =>
`/api/dataset-manage/download/${datasetId}`

View File

@@ -0,0 +1,27 @@
import { get, post, put, del } from '../request'
import type { EvalTask, Dimension } from '@/types'
/** 评测任务列表 */
export const getEvalList = () => get<EvalTask[]>('/model-eval')
/** 删除评测任务 */
export const deleteEval = (id: string | number) => del(`/model-eval/${id}`)
/** 启动评测 */
export const startEval = (data: any) => post('/model-eval/start', data)
/** 评测维度列表 */
export const getDimensionList = () => get<Dimension[]>('/dimension')
/** 评测维度详情 */
export const getDimension = (id: string | number) => get<Dimension>(`/dimension/${id}`)
/** 创建维度 */
export const createDimension = (data: Partial<Dimension>) => post('/dimension', data)
/** 编辑维度 */
export const updateDimension = (id: string | number, data: Partial<Dimension>) =>
put(`/dimension/${id}`, data)
/** 删除维度 */
export const deleteDimension = (id: string | number) => del(`/dimension/${id}`)

View File

@@ -0,0 +1,40 @@
import { get, post, put, del } from '../request'
import type { FineTuneTask, TrainingProgress } from '@/types'
/** 训练任务列表 */
export const getFineTuneList = () => get<FineTuneTask[]>('/fine-tune')
/** 训练任务详情 */
export const getFineTune = (id: string | number) => get<FineTuneTask>(`/fine-tune/${id}`)
/** 任务名查重 */
export const checkFineTuneName = (name: string) =>
get<{ exists: boolean }>('/fine-tune/check-name', { name })
/** 创建训练任务记录(第一步) */
export const createFineTune = (data: Partial<FineTuneTask>) =>
post<{ id: string | number }>('/fine-tune', data)
/** 启动训练(第二步) */
export const startFineTune = (data: any) => post('/fine-tune/start', data)
/** 更新训练任务 */
export const updateFineTune = (id: string | number, data: Partial<FineTuneTask>) =>
put(`/fine-tune/${id}`, data)
/** 停止训练任务 */
export const stopFineTune = (id: string | number) => post(`/fine-tune/stop/${id}`)
/** 删除训练任务 */
export const deleteFineTune = (id: string | number) => del(`/fine-tune/${id}`)
/** 获取训练进度 */
export const getFineTuneProgress = (id: string | number) =>
get<TrainingProgress>(`/fine-tune/progress/${id}`)
/** 启动 TensorBoard */
export const startTensorboard = () => post('/fine-tune/tensorboard/start')
/** 提交 Web 日志 */
export const sendWebLog = (level: string, message: string, page: string) =>
post('/web-log', { level, message, page, timestamp: new Date().toISOString() })

View File

@@ -0,0 +1,18 @@
import { get } from '../request'
import type { LogFile, LogContent, TrainingLogFile } from '@/types'
/** 按日期获取系统日志文件列表 */
export const getLogFiles = (date: string) =>
get<LogFile[]>('/log-files', { date })
/** 获取系统日志内容 */
export const getLogContent = (file: string) =>
get<LogContent>('/log-content', { file })
/** 训练日志文件列表 */
export const getTrainingLogFiles = () =>
get<TrainingLogFile[]>('/training-log-files')
/** 训练日志内容 */
export const getTrainingLogContent = (file: string) =>
get<LogContent>('/training-log-content', { file })

View File

@@ -0,0 +1,48 @@
import { get, post, put, del } from '../request'
import type { ModelItem, ModelForm, TrainedModel } from '@/types'
/** 模型列表 */
export const getModelList = () => get<ModelItem[]>('/model-manage')
/** 模型详情 */
export const getModel = (id: string | number) => get<ModelItem>(`/model-manage/${id}`)
/** 按名称查模型 */
export const getModelByName = (name: string) => get<ModelItem>(`/model-manage/name/${name}`)
/** 本地模型路径列表 */
export const getLocalModels = () =>
get<{ models: { path: string; name: string }[] }>('/model-manage/local-models')
/** 已训练模型列表 */
export const getTrainedModels = () =>
get<{ models: TrainedModel[] }>('/model-manage/trained-models')
/** 创建模型 */
export const createModel = (data: ModelForm) => post('/model-manage', data)
/** 编辑模型 */
export const updateModel = (id: string | number, data: ModelForm) =>
put(`/model-manage/${id}`, data)
/** 删除模型 */
export const deleteModel = (id: string | number) => del(`/model-manage/${id}`)
/** 删除已训练模型(合并权重) */
export const deleteTrainedModel = (id: string | number, type: 'merged' | 'lora' = 'merged') =>
del(`/model-manage/trained-models/${id}`, { type })
/** 更新模型用途 */
export const updateModelPurpose = (id: string | number, purpose: string) =>
put(`/model-manage/${id}/purpose`, { purpose })
/** 合并 LoRA 权重 */
export const mergeModel = (data: {
model_name: string
train_method: string
base_model_path: string
}) => post('/model-manage/merge', data)
/** 导出已训练模型权重 */
export const exportModelUrl = (modelName: string) =>
`/api/model-manage/trained-models/${encodeURIComponent(modelName)}/export`

View File

@@ -0,0 +1,12 @@
import { get, post } from '../request'
import type { SystemInfo, HealthMetrics } from '@/types'
/** 系统信息CPU/内存/磁盘/GPU/网络/系统) */
export const getSystemInfo = () => get<SystemInfo>('/system-info')
/** 健康指标(顶部栏轻量指标) */
export const getHealth = () => get<HealthMetrics>('/health')
/** 登录 */
export const login = (username: string, password: string) =>
post('/login', { username, password })

View File

@@ -0,0 +1,73 @@
import axios, { type AxiosInstance, type AxiosRequestConfig } from 'axios'
import { ElMessage } from 'element-plus'
/**
* 后端统一响应格式
* code === 0 表示成功data 为业务数据
*/
export interface ApiResult<T = any> {
code: number
message?: string
data: T
}
const service: AxiosInstance = axios.create({
// 统一走相对路径,由 Vite 代理转发到 http://localhost:7861
baseURL: '/api',
timeout: 30000,
})
// 安装 mock adapter拦截所有 axios 请求返回 mock 数据,方便前端独立开发调试)
import { installMockAdapter } from '@/mock/adapter'
installMockAdapter(service)
// 请求拦截器
service.interceptors.request.use(
(config) => config,
(error) => Promise.reject(error),
)
// 响应拦截器:统一解包 { code, data, message }
service.interceptors.response.use(
(response) => {
const res = response.data as ApiResult
// 二进制流等非 JSON 响应直接返回
if (response.config.responseType === 'blob' || response.config.responseType === 'arraybuffer') {
return response
}
if (res.code === 0) {
return res.data
}
// 业务错误
const message = res.message || '请求失败'
ElMessage.error(message)
return Promise.reject(new Error(message))
},
(error) => {
const message = error.response?.data?.message || error.message || '网络异常'
ElMessage.error(message)
return Promise.reject(error)
},
)
/** GET 请求,返回已解包的 data */
export function get<T = any>(url: string, params?: any, config?: AxiosRequestConfig): Promise<T> {
return service.get(url, { params, ...config }) as unknown as Promise<T>
}
/** POST 请求 */
export function post<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T> {
return service.post(url, data, config) as unknown as Promise<T>
}
/** PUT 请求 */
export function put<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T> {
return service.put(url, data, config) as unknown as Promise<T>
}
/** DELETE 请求 */
export function del<T = any>(url: string, params?: any, config?: AxiosRequestConfig): Promise<T> {
return service.delete(url, { params, ...config }) as unknown as Promise<T>
}
export default service