2026-07-10 16:45:06 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* Axios Mock Adapter
|
|
|
|
|
|
* 拦截所有 API 请求并返回 mock 数据
|
|
|
|
|
|
* 通过 URL + method 路由到对应的 mock 响应
|
|
|
|
|
|
*/
|
2026-07-13 15:29:49 +08:00
|
|
|
|
import type { AxiosAdapter, AxiosInstance, AxiosRequestConfig } from 'axios'
|
2026-07-10 16:45:06 +08:00
|
|
|
|
import {
|
|
|
|
|
|
mockLoginOk,
|
|
|
|
|
|
mockHealth,
|
|
|
|
|
|
mockSystemInfo,
|
|
|
|
|
|
mockModels,
|
|
|
|
|
|
mockTrainedModels,
|
|
|
|
|
|
mockLocalModels,
|
|
|
|
|
|
mockDatasets,
|
2026-07-11 10:39:26 +08:00
|
|
|
|
mockDatasetPreviews,
|
2026-07-10 16:45:06 +08:00
|
|
|
|
mockFineTuneList,
|
|
|
|
|
|
mockCompareList,
|
|
|
|
|
|
mockEvalList,
|
2026-07-12 15:39:59 +08:00
|
|
|
|
mockEvalDetails,
|
2026-07-10 16:45:06 +08:00
|
|
|
|
mockDimensions,
|
|
|
|
|
|
mockLogFiles,
|
|
|
|
|
|
mockTrainingLogFiles,
|
|
|
|
|
|
mockLogContent,
|
2026-07-13 15:29:49 +08:00
|
|
|
|
mockTrainingLogContents,
|
2026-07-10 16:45:06 +08:00
|
|
|
|
} from './data'
|
2026-07-13 10:31:20 +08:00
|
|
|
|
import {
|
|
|
|
|
|
activateDatasetVersion,
|
|
|
|
|
|
appendDatasetVersion,
|
|
|
|
|
|
createInitialVersionState,
|
|
|
|
|
|
getActiveDatasetVersion,
|
|
|
|
|
|
} from './datasetVersions'
|
|
|
|
|
|
import type { StoredDatasetVersion, StoredDatasetVersionState } from './datasetVersions'
|
2026-07-10 16:45:06 +08:00
|
|
|
|
|
|
|
|
|
|
/** 模拟网络延迟 */
|
|
|
|
|
|
function delay(ms = 200): Promise<void> {
|
|
|
|
|
|
return new Promise((resolve) => setTimeout(resolve, ms))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** 构造 Axios 风格的成功响应 */
|
|
|
|
|
|
function ok(data: any, config: AxiosRequestConfig = {}) {
|
|
|
|
|
|
return {
|
|
|
|
|
|
data: { code: 0, message: 'ok', data },
|
|
|
|
|
|
status: 200,
|
|
|
|
|
|
statusText: 'OK',
|
|
|
|
|
|
headers: {},
|
|
|
|
|
|
config,
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** 构造失败响应 */
|
|
|
|
|
|
function fail(message: string, code = 500, config: AxiosRequestConfig = {}) {
|
|
|
|
|
|
return {
|
|
|
|
|
|
data: { code, message },
|
|
|
|
|
|
status: code,
|
|
|
|
|
|
statusText: message,
|
|
|
|
|
|
headers: {},
|
|
|
|
|
|
config,
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** 简易 URL 解析(去掉 baseURL 前缀) */
|
|
|
|
|
|
function stripBaseURL(url: string): string {
|
|
|
|
|
|
// url 可能带查询参数
|
|
|
|
|
|
const [path] = url.split('?')
|
|
|
|
|
|
return path
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-13 10:31:20 +08:00
|
|
|
|
function datasetFallbackContent(fileId: string) {
|
|
|
|
|
|
const fallbackKey = fileId.endsWith('-readme') ? 'mock-readme' : 'mock-jsonl'
|
|
|
|
|
|
return mockDatasetPreviews[fileId] ?? mockDatasetPreviews[fallbackKey] ?? ''
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function versionStorageKey(fileId: string) {
|
|
|
|
|
|
return `mock:dataset-versions:${fileId}`
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function persistVersionState(fileId: string, state: StoredDatasetVersionState) {
|
|
|
|
|
|
localStorage.setItem(versionStorageKey(fileId), JSON.stringify(state))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function getVersionState(fileId: string): StoredDatasetVersionState {
|
|
|
|
|
|
const persisted = localStorage.getItem(versionStorageKey(fileId))
|
|
|
|
|
|
if (persisted) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const state = JSON.parse(persisted) as StoredDatasetVersionState
|
|
|
|
|
|
if (state.versions?.length && state.active_version_id) return state
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
// 版本数据损坏时回退到初始版本
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const legacyContent = localStorage.getItem(`mock:dataset-file:${fileId}`)
|
|
|
|
|
|
const state = createInitialVersionState(legacyContent ?? datasetFallbackContent(fileId))
|
|
|
|
|
|
persistVersionState(fileId, state)
|
|
|
|
|
|
return state
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function versionMetadata(version: StoredDatasetVersion) {
|
|
|
|
|
|
const { content: _content, ...metadata } = version
|
|
|
|
|
|
return metadata
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-10 16:45:06 +08:00
|
|
|
|
/** 通过路径 + method 匹配 mock 响应 */
|
|
|
|
|
|
async function handleMock(config: AxiosRequestConfig) {
|
|
|
|
|
|
await delay(150) // 模拟网络延迟
|
|
|
|
|
|
|
|
|
|
|
|
const url = stripBaseURL(config.url || '')
|
|
|
|
|
|
const method = (config.method || 'get').toLowerCase()
|
|
|
|
|
|
const params = config.params || {}
|
|
|
|
|
|
const body = typeof config.data === 'string' ? safeJSON(config.data) : config.data || {}
|
|
|
|
|
|
|
|
|
|
|
|
// ==================== 认证 ====================
|
|
|
|
|
|
if (url === '/login' && method === 'post') {
|
|
|
|
|
|
if (body.username === 'admin' && body.password === 'admin') {
|
|
|
|
|
|
return ok(mockLoginOk.data)
|
|
|
|
|
|
}
|
|
|
|
|
|
return fail('账号或密码错误', 401)
|
|
|
|
|
|
}
|
|
|
|
|
|
if (url === '/web-log' && method === 'post') {
|
|
|
|
|
|
return ok({ received: true })
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ==================== 系统监控 ====================
|
|
|
|
|
|
if (url === '/health' && method === 'get') return ok(mockHealth)
|
|
|
|
|
|
if (url === '/system-info' && method === 'get') return ok(mockSystemInfo)
|
|
|
|
|
|
|
|
|
|
|
|
// ==================== 模型管理 ====================
|
|
|
|
|
|
if (url === '/model-manage' && method === 'get') return ok(mockModels)
|
|
|
|
|
|
if (url === '/model-manage/local-models' && method === 'get') return ok(mockLocalModels)
|
|
|
|
|
|
if (url === '/model-manage/trained-models' && method === 'get') return ok(mockTrainedModels)
|
|
|
|
|
|
if (url === '/model-manage/merge' && method === 'post') {
|
|
|
|
|
|
return ok({ merged: true, path: '/data/saves/' + body.model_name + '-merged' })
|
|
|
|
|
|
}
|
|
|
|
|
|
// 模型详情 / 编辑 / 删除 / 用途更新
|
|
|
|
|
|
let m = url.match(/^\/model-manage\/([^/]+)$/)
|
|
|
|
|
|
if (m && method === 'get') {
|
|
|
|
|
|
const id = m[1]
|
|
|
|
|
|
const found = mockModels.find((x) => String(x.id) === id || x.name === id)
|
|
|
|
|
|
return found ? ok(found) : fail('模型不存在', 404)
|
|
|
|
|
|
}
|
|
|
|
|
|
m = url.match(/^\/model-manage\/([^/]+)$/)
|
|
|
|
|
|
if (m && (method === 'put' || method === 'delete')) {
|
|
|
|
|
|
return ok({ id: m[1], ...body })
|
|
|
|
|
|
}
|
|
|
|
|
|
m = url.match(/^\/model-manage\/name\/([^/]+)$/)
|
|
|
|
|
|
if (m && method === 'get') {
|
|
|
|
|
|
const name = decodeURIComponent(m[1])
|
|
|
|
|
|
const found = mockModels.find((x) => x.name === name)
|
|
|
|
|
|
return found ? ok(found) : fail('模型不存在', 404)
|
|
|
|
|
|
}
|
|
|
|
|
|
m = url.match(/^\/model-manage\/trained-models\/([^/]+)$/)
|
|
|
|
|
|
if (m && method === 'delete') {
|
|
|
|
|
|
return ok({ deleted: m[1] })
|
|
|
|
|
|
}
|
|
|
|
|
|
m = url.match(/^\/model-manage\/([^/]+)\/purpose$/)
|
|
|
|
|
|
if (m && method === 'put') {
|
|
|
|
|
|
return ok({ id: m[1], purpose: body.purpose })
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ==================== 数据集 ====================
|
|
|
|
|
|
if (url === '/dataset-manage' && method === 'get') return ok(mockDatasets)
|
|
|
|
|
|
if (url === '/dataset-manage' && method === 'post') {
|
|
|
|
|
|
const newId = Math.max(...mockDatasets.map((d) => Number(d.id))) + 1
|
|
|
|
|
|
return ok({ id: newId })
|
|
|
|
|
|
}
|
|
|
|
|
|
m = url.match(/^\/dataset-manage\/([^/]+)$/)
|
|
|
|
|
|
if (m && method === 'get') {
|
2026-07-13 15:29:49 +08:00
|
|
|
|
const datasetId = m[1]
|
|
|
|
|
|
const found = mockDatasets.find((x) => String(x.id) === datasetId)
|
2026-07-10 16:45:06 +08:00
|
|
|
|
return found ? ok(found) : fail('数据集不存在', 404)
|
|
|
|
|
|
}
|
|
|
|
|
|
if (m && (method === 'put' || method === 'delete')) {
|
|
|
|
|
|
return ok({ id: m[1] })
|
|
|
|
|
|
}
|
|
|
|
|
|
m = url.match(/^\/dataset-manage\/upload\/([^/]+)$/)
|
|
|
|
|
|
if (m && method === 'post') return ok({ uploaded: true })
|
|
|
|
|
|
m = url.match(/^\/dataset-manage\/preview\/([^/]+)$/)
|
2026-07-11 10:39:26 +08:00
|
|
|
|
if (m && method === 'get') {
|
|
|
|
|
|
const fileId = decodeURIComponent(m[1])
|
2026-07-13 10:31:20 +08:00
|
|
|
|
return ok({ content: getActiveDatasetVersion(getVersionState(fileId)).content })
|
|
|
|
|
|
}
|
|
|
|
|
|
if (m && method === 'put') return fail('历史版本不可覆盖,请创建新版本', 405)
|
|
|
|
|
|
|
|
|
|
|
|
m = url.match(/^\/dataset-manage\/versions\/([^/]+)$/)
|
|
|
|
|
|
if (m && method === 'get') {
|
|
|
|
|
|
const fileId = decodeURIComponent(m[1])
|
|
|
|
|
|
const state = getVersionState(fileId)
|
|
|
|
|
|
return ok({
|
|
|
|
|
|
versions: state.versions.map(versionMetadata).sort((a, b) => b.version - a.version),
|
|
|
|
|
|
active_version_id: state.active_version_id,
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
if (m && method === 'post') {
|
|
|
|
|
|
const fileId = decodeURIComponent(m[1])
|
|
|
|
|
|
if (typeof body.content !== 'string') return fail('文件内容格式不正确', 400)
|
|
|
|
|
|
const currentState = getVersionState(fileId)
|
|
|
|
|
|
if (body.expected_current_version_id !== currentState.active_version_id) {
|
|
|
|
|
|
return fail('当前版本已被其他用户更新,请刷新后重试', 409)
|
|
|
|
|
|
}
|
|
|
|
|
|
if (body.base_version_id !== currentState.active_version_id) {
|
|
|
|
|
|
return fail('只能基于当前版本创建新版本', 409)
|
|
|
|
|
|
}
|
|
|
|
|
|
if (body.content === getActiveDatasetVersion(currentState).content) {
|
|
|
|
|
|
return fail('数据内容没有变化,无需创建新版本', 400)
|
|
|
|
|
|
}
|
|
|
|
|
|
const nextState = appendDatasetVersion(
|
|
|
|
|
|
currentState,
|
|
|
|
|
|
body.content,
|
|
|
|
|
|
new Date().toISOString(),
|
|
|
|
|
|
typeof body.description === 'string' ? body.description : '在线编辑',
|
|
|
|
|
|
)
|
|
|
|
|
|
try {
|
|
|
|
|
|
persistVersionState(fileId, nextState)
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
return fail('文件内容过大,浏览器 Mock 存储空间不足', 413)
|
|
|
|
|
|
}
|
|
|
|
|
|
const version = getActiveDatasetVersion(nextState)
|
|
|
|
|
|
return ok({ version: versionMetadata(version), content: version.content })
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
m = url.match(/^\/dataset-manage\/versions\/([^/]+)\/active$/)
|
|
|
|
|
|
if (m && method === 'put') {
|
|
|
|
|
|
const fileId = decodeURIComponent(m[1])
|
|
|
|
|
|
const currentState = getVersionState(fileId)
|
|
|
|
|
|
if (body.expected_current_version_id !== currentState.active_version_id) {
|
|
|
|
|
|
return fail('当前版本已被其他用户更新,请刷新后重试', 409)
|
|
|
|
|
|
}
|
|
|
|
|
|
let nextState: StoredDatasetVersionState
|
|
|
|
|
|
try {
|
|
|
|
|
|
nextState = activateDatasetVersion(currentState, String(body.version_id || ''))
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
return fail(error instanceof Error ? error.message : '版本不存在', 404)
|
|
|
|
|
|
}
|
|
|
|
|
|
try {
|
|
|
|
|
|
persistVersionState(fileId, nextState)
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
return fail('浏览器 Mock 存储空间不足', 413)
|
|
|
|
|
|
}
|
|
|
|
|
|
const version = getActiveDatasetVersion(nextState)
|
|
|
|
|
|
return ok({ version: versionMetadata(version), content: version.content })
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
m = url.match(/^\/dataset-manage\/versions\/([^/]+)\/([^/]+)$/)
|
|
|
|
|
|
if (m && method === 'get') {
|
|
|
|
|
|
const fileId = decodeURIComponent(m[1])
|
|
|
|
|
|
const versionId = decodeURIComponent(m[2])
|
|
|
|
|
|
const version = getVersionState(fileId).versions.find((item) => item.id === versionId)
|
|
|
|
|
|
return version
|
|
|
|
|
|
? ok({ version: versionMetadata(version), content: version.content })
|
|
|
|
|
|
: fail('数据集版本不存在', 404)
|
2026-07-11 10:39:26 +08:00
|
|
|
|
}
|
2026-07-10 16:45:06 +08:00
|
|
|
|
|
|
|
|
|
|
// ==================== 训练任务 ====================
|
|
|
|
|
|
if (url === '/fine-tune' && method === 'get') return ok(mockFineTuneList)
|
|
|
|
|
|
if (url === '/fine-tune' && method === 'post') {
|
|
|
|
|
|
return ok({ id: Math.floor(Math.random() * 10000) + 100 })
|
|
|
|
|
|
}
|
|
|
|
|
|
if (url === '/fine-tune/start' && method === 'post') {
|
|
|
|
|
|
return ok({ started: true })
|
|
|
|
|
|
}
|
|
|
|
|
|
if (url === '/fine-tune/check-name' && method === 'get') {
|
|
|
|
|
|
return ok({ exists: false })
|
|
|
|
|
|
}
|
|
|
|
|
|
m = url.match(/^\/fine-tune\/progress\/([^/]+)$/)
|
|
|
|
|
|
if (m && method === 'get') {
|
2026-07-13 15:29:49 +08:00
|
|
|
|
const taskId = m[1]
|
|
|
|
|
|
const task = mockFineTuneList.find((t) => String(t.id) === taskId)
|
2026-07-10 16:45:06 +08:00
|
|
|
|
if (!task) return fail('任务不存在', 404)
|
|
|
|
|
|
if (task.status === 'running') {
|
|
|
|
|
|
return ok({
|
|
|
|
|
|
status: task.status,
|
|
|
|
|
|
progress: task.progress ?? 0,
|
|
|
|
|
|
step: `${Math.floor((task.progress ?? 0) * 10)}/1000`,
|
|
|
|
|
|
speed: '1.23s/it',
|
|
|
|
|
|
eta: '00:23:45',
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
return ok({ status: task.status, progress: task.progress ?? 0 })
|
|
|
|
|
|
}
|
|
|
|
|
|
m = url.match(/^\/fine-tune\/([^/]+)$/)
|
|
|
|
|
|
if (m && method === 'get') {
|
2026-07-13 15:29:49 +08:00
|
|
|
|
const taskId = m[1]
|
|
|
|
|
|
const found = mockFineTuneList.find((x) => String(x.id) === taskId)
|
2026-07-10 16:45:06 +08:00
|
|
|
|
return found ? ok(found) : fail('任务不存在', 404)
|
|
|
|
|
|
}
|
|
|
|
|
|
m = url.match(/^\/fine-tune\/stop\/([^/]+)$/)
|
|
|
|
|
|
if (m && method === 'post') return ok({ stopped: true })
|
|
|
|
|
|
m = url.match(/^\/fine-tune\/([^/]+)$/)
|
|
|
|
|
|
if (m && (method === 'put' || method === 'delete')) {
|
|
|
|
|
|
return ok({ id: m[1] })
|
|
|
|
|
|
}
|
|
|
|
|
|
if (url === '/fine-tune/tensorboard/start' && method === 'post') {
|
|
|
|
|
|
return ok({ url: 'http://10.10.10.77:6006' })
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ==================== 模型推理/对比 ====================
|
|
|
|
|
|
if (url === '/model-compare' && method === 'get') return ok(mockCompareList)
|
|
|
|
|
|
if (url === '/model-compare' && method === 'post') {
|
|
|
|
|
|
return ok({ id: Math.floor(Math.random() * 10000) + 100 })
|
|
|
|
|
|
}
|
|
|
|
|
|
m = url.match(/^\/model-compare\/([^/]+)$/)
|
|
|
|
|
|
if (m && method === 'get') {
|
2026-07-13 15:29:49 +08:00
|
|
|
|
const compareId = m[1]
|
|
|
|
|
|
const found = mockCompareList.find((x) => String(x.id) === compareId)
|
2026-07-10 16:45:06 +08:00
|
|
|
|
return found ? ok(found) : fail('任务不存在', 404)
|
|
|
|
|
|
}
|
|
|
|
|
|
if (m && method === 'delete') return ok({ deleted: m[1] })
|
|
|
|
|
|
m = url.match(/^\/model-compare\/([^/]+)\/load-status$/)
|
|
|
|
|
|
if (m && method === 'get') {
|
|
|
|
|
|
return ok({ all_ready: true, loaded_models: [] })
|
|
|
|
|
|
}
|
|
|
|
|
|
if (m && method === 'post') return ok({ updated: true })
|
|
|
|
|
|
m = url.match(/^\/model-compare\/([^/]+)\/load$/)
|
|
|
|
|
|
if (m && method === 'post') return ok({ loading: true })
|
|
|
|
|
|
m = url.match(/^\/model-compare\/([^/]+)\/unload$/)
|
|
|
|
|
|
if (m && method === 'post') return ok({ stopped: true })
|
|
|
|
|
|
m = url.match(/^\/model-compare\/([^/]+)\/start-model$/)
|
|
|
|
|
|
if (m && method === 'post') {
|
|
|
|
|
|
return ok({ pid: 12345 + Math.floor(Math.random() * 100), port: 18000 + Math.floor(Math.random() * 1000) })
|
|
|
|
|
|
}
|
|
|
|
|
|
if (url === '/model-compare/all/stop-all' && method === 'post') return ok({ stopped: true })
|
|
|
|
|
|
if (url === '/model-compare/stop-by-pid' && method === 'post') return ok({ stopped: true })
|
|
|
|
|
|
if (url === '/model-compare/chat-with-port' && method === 'post') {
|
|
|
|
|
|
// 模拟推理回答(用于对比结果页)
|
|
|
|
|
|
const userQ = body?.messages?.find((m: any) => m.role === 'user')?.content || '你的问题'
|
|
|
|
|
|
const answer = `这是一个针对「${userQ.slice(0, 30)}」的模拟回答。\n\n在真实环境中,对比结果页会通过端口代理调用对应模型服务,返回该模型的真实推理结果。\n\n模型参数量、温度、Top-p 等参数都会影响输出内容。\n\n- 模型:${body?.model_name || '未知'}\n- 端口:${body?.port || '-'}\n- 参数:temperature=${body?.temperature ?? 0.7}, max_tokens=${body?.max_tokens ?? 2048}`
|
|
|
|
|
|
return ok({ response: answer, content: answer })
|
|
|
|
|
|
}
|
|
|
|
|
|
if (url === '/model-compare/stream-chat' && method === 'post') {
|
|
|
|
|
|
// 模拟流式对话(前端 useStreamChat 会按块读取)
|
|
|
|
|
|
const userQ = body?.user_question || ''
|
|
|
|
|
|
const sysPrompt = body?.system_prompt || ''
|
|
|
|
|
|
const answer = `${sysPrompt ? '【系统提示】' + sysPrompt.slice(0, 50) + '\n\n' : ''}关于「${userQ}」的回答:\n\n这是一段**模拟流式输出**。在真实部署中,后端会通过 SSE/WebSocket 逐字推送 token。\n\n## 模型参数\n- 温度(temperature):${body?.temperature ?? 0.7}\n- 最大长度(max_tokens):${body?.max_tokens ?? 2048}\n\n## 思考过程\n让我先分析这个问题...\n- 识别用户意图\n- 检索相关知识\n- 生成回答\n\n回答已生成。以上为前端 Mock 演示内容。`
|
|
|
|
|
|
return ok({ response: answer })
|
|
|
|
|
|
}
|
|
|
|
|
|
if (url === '/model-compare/test-stream' && method === 'post') {
|
|
|
|
|
|
return ok({ response: '测试流式输出' })
|
|
|
|
|
|
}
|
|
|
|
|
|
if (url === '/model-chat/batch' && method === 'post') return ok({ responses: [] })
|
|
|
|
|
|
if (url === '/model-chat/local/chat' && method === 'post') return ok({ response: '本地模型回复' })
|
|
|
|
|
|
if (url === '/model-chat/local/preload' && method === 'post') return ok({ loaded: true })
|
|
|
|
|
|
if (url === '/model-chat/trained/preload' && method === 'post') return ok({ loaded: true })
|
|
|
|
|
|
|
|
|
|
|
|
// ==================== 模型评测 ====================
|
|
|
|
|
|
if (url === '/model-eval' && method === 'get') return ok(mockEvalList)
|
|
|
|
|
|
if (url === '/model-eval' && method === 'delete') return ok({ deleted: true })
|
|
|
|
|
|
if (url === '/model-eval/start' && method === 'post') return ok({ task_id: Math.floor(Math.random() * 1000) + 1 })
|
2026-07-12 15:39:59 +08:00
|
|
|
|
m = url.match(/^\/model-eval\/([^/]+)$/)
|
|
|
|
|
|
const evalTaskId = m?.[1]
|
|
|
|
|
|
if (evalTaskId && method === 'get') {
|
|
|
|
|
|
const found = mockEvalDetails.find((item) => String(item.id) === evalTaskId)
|
|
|
|
|
|
return found ? ok(found) : fail('评测任务不存在', 404)
|
|
|
|
|
|
}
|
2026-07-10 16:45:06 +08:00
|
|
|
|
if (url === '/dimension' && method === 'get') return ok(mockDimensions)
|
|
|
|
|
|
if (url === '/dimension' && method === 'post') {
|
|
|
|
|
|
return ok({ id: Math.floor(Math.random() * 1000) + 100 })
|
|
|
|
|
|
}
|
|
|
|
|
|
m = url.match(/^\/dimension\/([^/]+)$/)
|
|
|
|
|
|
if (m && method === 'get') {
|
|
|
|
|
|
const found = mockDimensions.find((x) => String(x.id) === m[1])
|
|
|
|
|
|
return found ? ok(found) : fail('维度不存在', 404)
|
|
|
|
|
|
}
|
|
|
|
|
|
if (m && (method === 'put' || method === 'delete')) {
|
|
|
|
|
|
return ok({ id: m[1] })
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ==================== 日志 ====================
|
|
|
|
|
|
if (url === '/log-files' && method === 'get') return ok(mockLogFiles)
|
|
|
|
|
|
if (url === '/log-content' && method === 'get') return ok(mockLogContent)
|
|
|
|
|
|
if (url === '/training-log-files' && method === 'get') return ok(mockTrainingLogFiles)
|
2026-07-13 15:29:49 +08:00
|
|
|
|
if (url === '/training-log-content' && method === 'get') {
|
|
|
|
|
|
const file = String(params.file || '')
|
|
|
|
|
|
return ok(mockTrainingLogContents[file] || {
|
|
|
|
|
|
file,
|
|
|
|
|
|
size: '0 KB',
|
|
|
|
|
|
content: `[Mock] 未找到训练日志内容:${file}`,
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
2026-07-10 16:45:06 +08:00
|
|
|
|
|
|
|
|
|
|
// 未匹配的请求 → 兜底返回空成功(避免阻断 UI)
|
|
|
|
|
|
console.warn('[Mock] 未匹配路由:', method.toUpperCase(), url, params)
|
|
|
|
|
|
return ok({ mocked: true, url, method, params, body })
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function safeJSON(str: string) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
return JSON.parse(str)
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
return {}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** 给 axios instance 安装 mock adapter */
|
|
|
|
|
|
export function installMockAdapter(instance: AxiosInstance) {
|
2026-07-13 15:29:49 +08:00
|
|
|
|
const adapter = async (config: AxiosRequestConfig) => {
|
2026-07-10 16:45:06 +08:00
|
|
|
|
try {
|
|
|
|
|
|
const response = await handleMock(config)
|
|
|
|
|
|
return response
|
2026-07-13 15:29:49 +08:00
|
|
|
|
} catch (error: unknown) {
|
|
|
|
|
|
return fail(error instanceof Error ? error.message : 'Mock 错误', 500, config)
|
2026-07-10 16:45:06 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-07-13 15:29:49 +08:00
|
|
|
|
instance.defaults.adapter = adapter as AxiosAdapter
|
2026-07-11 10:39:26 +08:00
|
|
|
|
}
|