feat: 实现基础设施层
axios 请求封装及七个业务模块 API,Pinia 状态管理(auth/system/models/tools),Mock 适配器与数据,以及流式对话、轮询、倒计时组合式函数。
This commit is contained in:
268
frontend/src/mock/adapter.ts
Normal file
268
frontend/src/mock/adapter.ts
Normal file
@@ -0,0 +1,268 @@
|
||||
/**
|
||||
* Axios Mock Adapter
|
||||
* 拦截所有 API 请求并返回 mock 数据
|
||||
* 通过 URL + method 路由到对应的 mock 响应
|
||||
*/
|
||||
import type { AxiosInstance, AxiosRequestConfig } from 'axios'
|
||||
import {
|
||||
mockLoginOk,
|
||||
mockHealth,
|
||||
mockSystemInfo,
|
||||
mockModels,
|
||||
mockTrainedModels,
|
||||
mockLocalModels,
|
||||
mockDatasets,
|
||||
mockFineTuneList,
|
||||
mockCompareList,
|
||||
mockEvalList,
|
||||
mockDimensions,
|
||||
mockLogFiles,
|
||||
mockTrainingLogFiles,
|
||||
mockLogContent,
|
||||
} from './data'
|
||||
|
||||
/** 模拟网络延迟 */
|
||||
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
|
||||
}
|
||||
|
||||
/** 通过路径 + 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') {
|
||||
const found = mockDatasets.find((x) => String(x.id) === m[1])
|
||||
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\/([^/]+)$/)
|
||||
if (m && method === 'get') return ok(mockLogContent)
|
||||
|
||||
// ==================== 训练任务 ====================
|
||||
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') {
|
||||
const task = mockFineTuneList.find((t) => String(t.id) === m[1])
|
||||
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') {
|
||||
const found = mockFineTuneList.find((x) => String(x.id) === m[1])
|
||||
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') {
|
||||
const found = mockCompareList.find((x) => String(x.id) === m[1])
|
||||
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 })
|
||||
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)
|
||||
if (url === '/training-log-content' && method === 'get') return ok(mockLogContent)
|
||||
|
||||
// 未匹配的请求 → 兜底返回空成功(避免阻断 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) {
|
||||
instance.defaults.adapter = async (config: AxiosRequestConfig) => {
|
||||
try {
|
||||
const response = await handleMock(config)
|
||||
return response
|
||||
} catch (e: any) {
|
||||
return fail(e.message || 'Mock 错误', 500, config)
|
||||
}
|
||||
}
|
||||
}
|
||||
230
frontend/src/mock/data.ts
Normal file
230
frontend/src/mock/data.ts
Normal file
@@ -0,0 +1,230 @@
|
||||
/**
|
||||
* 全量 Mock 数据
|
||||
* 为前端开发提供不依赖后端的模拟数据
|
||||
*/
|
||||
|
||||
import type {
|
||||
FineTuneTask,
|
||||
ModelItem,
|
||||
TrainedModel,
|
||||
DatasetItem,
|
||||
CompareTask,
|
||||
EvalTask,
|
||||
Dimension,
|
||||
SystemInfo,
|
||||
HealthMetrics,
|
||||
LogFile,
|
||||
TrainingLogFile,
|
||||
LogContent,
|
||||
} from '@/types'
|
||||
|
||||
// ============ 认证 ============
|
||||
export const mockLoginOk = { code: 0, message: 'ok', data: { token: 'mock-token' } }
|
||||
|
||||
// ============ 系统监控 ============
|
||||
export const mockHealth: HealthMetrics = {
|
||||
cpu_percent: 32,
|
||||
memory_percent: 58,
|
||||
disk_percent: 45,
|
||||
}
|
||||
|
||||
export const mockSystemInfo: SystemInfo = {
|
||||
cpu: {
|
||||
percent: 32,
|
||||
cores: 8,
|
||||
percents: [25, 38, 42, 30, 28, 36, 40, 18],
|
||||
},
|
||||
memory: {
|
||||
used_gb: 9.2,
|
||||
total_gb: 16,
|
||||
percent: 58,
|
||||
available_gb: 6.8,
|
||||
cached_gb: 2.3,
|
||||
},
|
||||
disk: {
|
||||
used_gb: 230,
|
||||
total_gb: 512,
|
||||
percent: 45,
|
||||
},
|
||||
gpu: [
|
||||
{ name: 'NVIDIA A800', gpu_percent: 0, memory_used_gb: 0.0, memory_total_gb: 80, temperature: 32, power_w: 38, fan_speed: 0, clock_mhz: 1410, driver_version: '535.86.10' },
|
||||
{ name: 'NVIDIA A800', gpu_percent: 28, memory_used_gb: 22.5, memory_total_gb: 80, temperature: 52, power_w: 165, fan_speed: 32, clock_mhz: 1410, driver_version: '535.86.10' },
|
||||
{ name: 'NVIDIA A800', gpu_percent: 73, memory_used_gb: 58.7, memory_total_gb: 80, temperature: 68, power_w: 320, fan_speed: 58, clock_mhz: 1410, driver_version: '535.86.10' },
|
||||
{ name: 'NVIDIA A800', gpu_percent: 0, memory_used_gb: 0.0, memory_total_gb: 80, temperature: 34, power_w: 42, fan_speed: 0, clock_mhz: 1410, driver_version: '535.86.10' },
|
||||
{ name: 'NVIDIA A800', gpu_percent: 46, memory_used_gb: 36.8, memory_total_gb: 80, temperature: 60, power_w: 210, fan_speed: 42, clock_mhz: 1410, driver_version: '535.86.10' },
|
||||
{ name: 'NVIDIA A800', gpu_percent: 0, memory_used_gb: 0.0, memory_total_gb: 80, temperature: 33, power_w: 40, fan_speed: 0, clock_mhz: 1410, driver_version: '535.86.10' },
|
||||
{ name: 'NVIDIA A800', gpu_percent: 85, memory_used_gb: 68.2, memory_total_gb: 80, temperature: 73, power_w: 365, fan_speed: 62, clock_mhz: 1410, driver_version: '535.86.10' },
|
||||
{ name: 'NVIDIA A800', gpu_percent: 15, memory_used_gb: 12.0, memory_total_gb: 80, temperature: 45, power_w: 95, fan_speed: 22, clock_mhz: 1410, driver_version: '535.86.10' },
|
||||
],
|
||||
network: {
|
||||
download_mb: 1024,
|
||||
upload_mb: 256,
|
||||
},
|
||||
system: {
|
||||
uptime_seconds: 86400 * 3 + 3600 * 7 + 1800,
|
||||
process_count: 256,
|
||||
os: 'Ubuntu 22.04 LTS',
|
||||
},
|
||||
}
|
||||
|
||||
// ============ 模型管理 ============
|
||||
export const mockModels: ModelItem[] = [
|
||||
{ id: 1, name: 'Qwen2.5-7B-Instruct', type: 'LLM', purpose: 'training', model_source: 'local', description: 'Qwen2.5 7B 指令微调基座', path: '/data/models/qwen2.5-7b', create_time: '2025-12-10T08:30:00Z' },
|
||||
{ id: 2, name: 'Qwen2.5-14B-Instruct', type: 'LLM', purpose: 'training', model_source: 'local', description: 'Qwen2.5 14B 指令微调基座', path: '/data/models/qwen2.5-14b', create_time: '2025-12-12T10:15:00Z' },
|
||||
{ id: 3, name: 'Llama3-8B-Instruct', type: 'LLM', purpose: 'inference', model_source: 'local', description: 'Llama3 8B 推理模型', path: '/data/models/llama3-8b', create_time: '2025-12-15T14:20:00Z' },
|
||||
{ id: 4, name: 'DeepSeek-V2-Lite', type: 'LLM', purpose: 'inference', model_source: 'local', description: 'DeepSeek V2 Lite', path: '/data/models/deepseek-v2-lite', create_time: '2026-01-05T09:00:00Z' },
|
||||
{ id: 5, name: 'GPT-4o', type: 'LLM', purpose: 'evaluation', model_source: 'api', description: 'OpenAI GPT-4o 在线模型', api_url: 'https://api.openai.com/v1', api_key: 'sk-***', online_model_name: 'gpt-4o', create_time: '2026-01-08T11:30:00Z' },
|
||||
{ id: 6, name: 'Claude-3.5-Sonnet', type: 'LLM', purpose: 'evaluation', model_source: 'api', description: 'Anthropic Claude 3.5 Sonnet', api_url: 'https://api.anthropic.com', api_key: 'sk-***', online_model_name: 'claude-3-5-sonnet-20241022', create_time: '2026-01-10T16:45:00Z' },
|
||||
{ id: 7, name: 'BGE-large-zh', type: 'Embedding', purpose: 'inference', model_source: 'local', description: '中文 embedding 模型', path: '/data/models/bge-large-zh', create_time: '2026-01-12T13:00:00Z' },
|
||||
]
|
||||
|
||||
export const mockTrainedModels: { models: TrainedModel[] } = {
|
||||
models: [
|
||||
{ id: 1, name: 'qwen-ft-finance-001', train_methods: [{ name: 'lora' }], base_model_path: '/data/models/qwen2.5-7b', merged: true, merging: false, merged_path: '/data/saves/qwen-ft-finance-001-merged', create_time: '2026-01-15T10:30:00Z' },
|
||||
{ id: 2, name: 'qwen-ft-legal-002', train_methods: [{ name: 'lora' }], base_model_path: '/data/models/qwen2.5-7b', merged: false, merging: true, create_time: '2026-01-18T14:20:00Z' },
|
||||
{ id: 3, name: 'llama3-ft-customer-service', train_methods: [{ name: 'qlora' }], base_model_path: '/data/models/llama3-8b', merged: true, merging: false, merged_path: '/data/saves/llama3-ft-customer-service-merged', create_time: '2026-01-22T09:45:00Z' },
|
||||
{ id: 4, name: 'qwen-ft-medical-003', train_methods: [{ name: 'lora' }], base_model_path: '/data/models/qwen2.5-14b', merged: false, merging: false, create_time: '2026-02-01T16:10:00Z' },
|
||||
],
|
||||
}
|
||||
|
||||
export const mockLocalModels = {
|
||||
models: [
|
||||
{ path: '/data/models/qwen2.5-7b', name: 'Qwen2.5-7B-Instruct' },
|
||||
{ path: '/data/models/qwen2.5-14b', name: 'Qwen2.5-14B-Instruct' },
|
||||
{ path: '/data/models/llama3-8b', name: 'Llama3-8B-Instruct' },
|
||||
{ path: '/data/models/deepseek-v2-lite', name: 'DeepSeek-V2-Lite' },
|
||||
{ path: '/data/models/bge-large-zh', name: 'BGE-large-zh' },
|
||||
],
|
||||
}
|
||||
|
||||
// ============ 数据集 ============
|
||||
export const mockDatasets: DatasetItem[] = [
|
||||
{ id: 1, name: '金融问答-训练集', type: 'train', storage_type: 'local', size: '128 MB', count: 8560, description: '金融领域问答对', create_time: '2025-12-20T08:00:00Z' },
|
||||
{ id: 2, name: '法律文书-训练集', type: 'train', storage_type: 'local', size: '256 MB', count: 15230, description: '法律文书数据集', create_time: '2025-12-25T10:30:00Z' },
|
||||
{ id: 3, name: '客服对话-训练集', type: 'train', storage_type: 'minio', size: '512 MB', count: 24500, description: '客服对话记录', create_time: '2026-01-05T14:20:00Z' },
|
||||
{ id: 4, name: '金融评测集', type: 'eval', storage_type: 'local', size: '32 MB', count: 1200, description: '金融领域评测', create_time: '2026-01-10T09:15:00Z' },
|
||||
{ id: 5, name: '通用能力评测', type: 'eval', storage_type: 'local', size: '64 MB', count: 3500, description: '通用能力评测数据集', create_time: '2026-01-12T11:30:00Z' },
|
||||
{ id: 6, name: '医疗问答-训练集', type: 'train', storage_type: 'local', size: '180 MB', count: 9800, description: '医疗问答对', create_time: '2026-02-01T15:00:00Z' },
|
||||
]
|
||||
|
||||
// ============ 训练任务 ============
|
||||
export const mockFineTuneList: FineTuneTask[] = [
|
||||
{ id: 1, name: 'finance-sft-001', description: '金融领域 SFT 训练', status: 'completed', train_type: 'SFT', train_method: 'lora', template: 'qwen', base_model: 1, train_dataset_id: 1, gpus: [0], progress: 100, train_duration: '2小时18分钟', create_time: '2026-01-15T08:00:00Z' },
|
||||
{ id: 2, name: 'legal-sft-002', description: '法律文书 SFT', status: 'completed', train_type: 'SFT', train_method: 'lora', template: 'qwen', base_model: 1, train_dataset_id: 2, gpus: [1], progress: 100, train_duration: '1小时46分钟', create_time: '2026-01-18T10:00:00Z' },
|
||||
{ id: 3, name: 'medical-cpt-001', description: '医疗领域继续预训练', status: 'running', train_type: 'CPT', train_method: 'lora', template: 'qwen2_5', base_model: 2, train_dataset_id: 6, gpus: [0, 1], progress: 64, train_duration: '36分钟', create_time: '2026-02-05T09:00:00Z' },
|
||||
{ id: 4, name: 'service-dpo-001', description: '客服对话偏好训练', status: 'pending', train_type: 'DPO', train_method: 'lora', template: 'qwen', base_model: 1, train_dataset_id: 3, gpus: [2], progress: 0, train_duration: '-', create_time: '2026-02-08T14:00:00Z' },
|
||||
{ id: 5, name: 'finance-sft-002', description: '金融领域二轮微调', status: 'failed', train_type: 'SFT', train_method: 'lora', template: 'qwen', base_model: 1, train_dataset_id: 1, gpus: [3], progress: 32, train_duration: '18分钟', create_time: '2026-02-10T11:00:00Z' },
|
||||
{ id: 6, name: 'general-sft-001', description: '通用能力微调', status: 'completed', train_type: 'SFT', train_method: 'full', template: 'llama3', base_model: 3, train_dataset_id: 3, gpus: [0, 2], progress: 100, train_duration: '3小时05分钟', create_time: '2026-02-12T13:00:00Z' },
|
||||
]
|
||||
|
||||
// ============ 模型推理/对比 ============
|
||||
export const mockCompareList: CompareTask[] = [
|
||||
{
|
||||
id: 1,
|
||||
name: '金融问答对比',
|
||||
model_name: '金融问答对比',
|
||||
description: '对比基座模型与微调模型',
|
||||
status: 'loaded',
|
||||
models: JSON.stringify([
|
||||
{ model_id: 1, model_name: 'Qwen2.5-7B-Instruct', model_path: '/data/models/qwen2.5-7b', gpu_id: 0, source: 'database', port: 18001 },
|
||||
{ model_id: 101, model_name: 'qwen-ft-finance-001', model_path: '/data/saves/qwen-ft-finance-001-merged', gpu_id: 1, source: 'trained', port: 18002 },
|
||||
]),
|
||||
load_status: JSON.stringify({
|
||||
loaded_models: [
|
||||
{ model_id: 1, model_name: 'Qwen2.5-7B-Instruct', status: 'ready', pid: 12345, port: 18001 },
|
||||
{ model_id: 101, model_name: 'qwen-ft-finance-001', status: 'ready', pid: 12346, port: 18002 },
|
||||
],
|
||||
}),
|
||||
create_time: '2026-02-15T10:00:00Z',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: '客服场景推理',
|
||||
model_name: '客服场景推理',
|
||||
description: '客服对话推理测试',
|
||||
status: 'pending',
|
||||
models: JSON.stringify([{ model_id: 3, model_name: 'Llama3-8B-Instruct', model_path: '/data/models/llama3-8b', gpu_id: 2, source: 'database' }]),
|
||||
load_status: JSON.stringify({ loaded_models: [] }),
|
||||
create_time: '2026-02-18T11:00:00Z',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: '法律文书推理',
|
||||
model_name: '法律文书推理',
|
||||
description: '法律文书推理测试',
|
||||
status: 'loaded',
|
||||
models: JSON.stringify([{ model_id: 102, model_name: 'qwen-ft-legal-002', model_path: '/data/saves/qwen-ft-legal-002-merged', gpu_id: 3, source: 'trained' }]),
|
||||
load_status: JSON.stringify({
|
||||
loaded_models: [
|
||||
{ model_id: 102, model_name: 'qwen-ft-legal-002', status: 'ready', pid: 12350, port: 18003 },
|
||||
],
|
||||
}),
|
||||
create_time: '2026-02-20T14:00:00Z',
|
||||
},
|
||||
]
|
||||
|
||||
// ============ 模型评测 ============
|
||||
export const mockEvalList: EvalTask[] = [
|
||||
{ id: 1, eval_task_name: '金融模型评测-v1', model_name: 'qwen-ft-finance-001', dataset: '金融评测集', metric: 'accuracy', score: 87.5, status: 'completed', create_time: '2026-02-01T10:00:00Z' },
|
||||
{ id: 2, eval_task_name: '客服模型评测-v1', model_name: 'llama3-ft-customer-service', dataset: '通用能力评测', metric: 'rouge-1', score: 0.82, status: 'completed', create_time: '2026-02-05T11:00:00Z' },
|
||||
{ id: 3, eval_task_name: '基线对比评测', model_name: 'Qwen2.5-7B-Instruct', dataset: '金融评测集', metric: 'accuracy', score: 72.3, status: 'running', create_time: '2026-02-10T09:00:00Z' },
|
||||
]
|
||||
|
||||
export const mockDimensions: Dimension[] = [
|
||||
{ id: 1, name: '回答准确性', type: 'classification', description: '评估模型回答是否准确', eval_model: 'GPT-4o', eval_method: 'standard', eval_prompt: '# 角色\n你是专业的评估专家...', is_active: true, is_default: true, create_time: '2025-12-01T08:00:00Z' },
|
||||
{ id: 2, name: '综合评分', type: 'metric', description: '0-5 分综合评分', eval_model: 'Claude-3.5-Sonnet', eval_method: 'metric_standard', eval_prompt: '# 角色\n你是专业评分专家...', is_active: true, is_default: false, score_min: 0, score_max: 5, pass_threshold: 3.5, create_time: '2025-12-05T09:00:00Z' },
|
||||
{ id: 3, name: '语义相似度', type: 'metric', description: '生成文本与参考答案的语义相似度', eval_model: 'GPT-4o', eval_method: 'semantic', eval_prompt: '# 角色\n你是语义相似度评估专家...', is_active: true, is_default: false, score_min: 0, score_max: 1, pass_threshold: 0.7, create_time: '2025-12-08T10:00:00Z' },
|
||||
{ id: 4, name: 'BLEU-4 相似度', type: 'text_similarity', description: '使用 BLEU-4 评估文本相似度', is_active: true, is_default: false, eval_method: ['bleu_4'], bleu_n: 4, output_precision: 3, create_time: '2025-12-10T11:00:00Z' },
|
||||
{ id: 5, name: 'ROUGE 多指标', type: 'text_similarity', description: 'ROUGE-1/2/4 多指标评估', is_active: false, is_default: false, eval_method: ['rouge_1', 'rouge_2', 'rouge_4'], bleu_n: 1, output_precision: 3, create_time: '2025-12-12T13:00:00Z' },
|
||||
]
|
||||
|
||||
// ============ 日志 ============
|
||||
export const mockLogFiles: LogFile[] = [
|
||||
{ file: 'system-2026-02-15.log', name: '系统日志-2026-02-15', size: '2.3 MB' },
|
||||
{ file: 'error-2026-02-15.log', name: '错误日志-2026-02-15', size: '156 KB' },
|
||||
{ file: 'system-2026-02-14.log', name: '系统日志-2026-02-14', size: '3.1 MB' },
|
||||
]
|
||||
|
||||
export const mockTrainingLogFiles: TrainingLogFile[] = [
|
||||
{ file: 'qwen-ft-finance-001_pid12345.log', name: 'finance-sft-001', size: '4.5 MB', pid: 12345, date: '2026-02-15' },
|
||||
{ file: 'llama3-ft-customer-service_pid12346.log', name: 'service-dpo-001', size: '2.1 MB', pid: 12346, date: '2026-02-18' },
|
||||
{ file: 'qwen-ft-legal-002_pid12350.log', name: 'legal-sft-002', size: '5.8 MB', pid: 12350, date: '2026-02-20' },
|
||||
]
|
||||
|
||||
const fakeLogLines = [
|
||||
"[2026-02-15 08:30:12] INFO: Loading model from /data/models/qwen2.5-7b",
|
||||
"[2026-02-15 08:30:13] INFO: Loading dataset finance-train-001 (8560 samples)",
|
||||
"[2026-02-15 08:30:15] INFO: Training started with batch_size=8, learning_rate=2e-5",
|
||||
"[2026-02-15 08:32:45] INFO: {'loss': 2.341, 'grad_norm': 1.234, 'learning_rate': 1.95e-05, 'epoch': 0.05}",
|
||||
"[2026-02-15 08:34:15] INFO: {'loss': 1.892, 'grad_norm': 0.987, 'learning_rate': 1.88e-05, 'epoch': 0.10}",
|
||||
"[2026-02-15 08:35:42] INFO: {'loss': 1.543, 'grad_norm': 0.876, 'learning_rate': 1.79e-05, 'epoch': 0.15}",
|
||||
"[2026-02-15 08:37:10] INFO: {'loss': 1.287, 'grad_norm': 0.765, 'learning_rate': 1.70e-05, 'epoch': 0.20}",
|
||||
"[2026-02-15 08:38:55] INFO: {'loss': 1.056, 'grad_norm': 0.654, 'learning_rate': 1.60e-05, 'epoch': 0.25}",
|
||||
"[2026-02-15 08:40:30] WARN: Gradient norm exceeds threshold (0.654 > 0.5)",
|
||||
"[2026-02-15 08:42:00] INFO: {'loss': 0.892, 'grad_norm': 0.543, 'learning_rate': 1.50e-05, 'epoch': 0.30}",
|
||||
"[2026-02-15 08:45:15] INFO: Saved checkpoint to /data/checkpoints/finance-sft-001-step-100",
|
||||
"[2026-02-15 08:46:30] INFO: {'loss': 0.754, 'grad_norm': 0.432, 'learning_rate': 1.40e-05, 'epoch': 0.35}",
|
||||
"[2026-02-15 08:48:00] INFO: {'loss': 0.623, 'grad_norm': 0.398, 'learning_rate': 1.30e-05, 'epoch': 0.40}",
|
||||
"[2026-02-15 08:50:15] INFO: {'loss': 0.512, 'grad_norm': 0.345, 'learning_rate': 1.20e-05, 'epoch': 0.45}",
|
||||
"[2026-02-15 08:52:30] ERROR: Failed to save model: No space left on device",
|
||||
"[2026-02-15 08:52:31] INFO: Retrying save with compressed format...",
|
||||
"[2026-02-15 08:53:00] INFO: Model saved successfully (size: 14.2 GB)",
|
||||
"[2026-02-15 08:55:00] INFO: {'loss': 0.421, 'grad_norm': 0.298, 'learning_rate': 1.10e-05, 'epoch': 0.50}",
|
||||
"[2026-02-15 08:57:30] INFO: {'loss': 0.356, 'grad_norm': 0.256, 'learning_rate': 1.00e-05, 'epoch': 0.55}",
|
||||
"[2026-02-15 09:00:00] INFO: Training completed successfully",
|
||||
"",
|
||||
"***** train metrics *****",
|
||||
" epoch = 1.0",
|
||||
" total_flos = 1234567890",
|
||||
" train_loss = 0.342",
|
||||
" train_runtime = 1785.2",
|
||||
" train_samples_per_second = 4.79",
|
||||
" train_steps_per_second = 0.60",
|
||||
"***** train metrics end *****",
|
||||
]
|
||||
|
||||
export const mockLogContent: LogContent = {
|
||||
file: 'system-2026-02-15.log',
|
||||
size: '2.3 MB',
|
||||
content: fakeLogLines.join('\n'),
|
||||
}
|
||||
Reference in New Issue
Block a user