refactor: 训练日志组件化与 Mock 数据增强

拆分 TrainingTaskOverview 组件与 trainingLogModel 状态模型,TrainingLogView 大幅瘦身;Mock 新增按文件路由的训练日志内容与更真实的 GPU 进程占用数据,adapter 类型收敛为 AxiosAdapter,配套新增 mock 内容回归脚本。
This commit is contained in:
caoxiaozhu
2026-07-13 15:29:49 +08:00
parent e580ec4791
commit e212de1693
7 changed files with 1473 additions and 914 deletions

View File

@@ -0,0 +1,77 @@
import assert from 'node:assert/strict'
import { readFile } from 'node:fs/promises'
import { fileURLToPath } from 'node:url'
import path from 'node:path'
const scriptDir = path.dirname(fileURLToPath(import.meta.url))
const mockDataPath = path.resolve(scriptDir, '../src/mock/data.ts')
const mockAdapterPath = path.resolve(scriptDir, '../src/mock/adapter.ts')
const modelPath = path.resolve(scriptDir, '../src/views/system/training-log/trainingLogModel.ts')
const [mockData, mockAdapter, modelSource] = await Promise.all([
readFile(mockDataPath, 'utf8'),
readFile(mockAdapterPath, 'utf8'),
readFile(modelPath, 'utf8'),
])
function objectFromMarker(source, marker) {
const markerIndex = source.indexOf(marker)
assert.notEqual(markerIndex, -1, `未找到 Mock 数据标记:${marker}`)
const openBrace = source.lastIndexOf('{', markerIndex)
assert.notEqual(openBrace, -1, `Mock 数据缺少对象起点:${marker}`)
let depth = 0
for (let index = openBrace; index < source.length; index += 1) {
if (source[index] === '{') depth += 1
if (source[index] === '}') depth -= 1
if (depth === 0) return source.slice(openBrace, index + 1)
}
assert.fail(`Mock 数据缺少对象终点:${marker}`)
}
const taskNames = [
'finance-sft-001',
'legal-sft-002',
'medical-cpt-001',
'service-dpo-001',
'finance-sft-002',
'general-sft-001',
]
const commonTrainingFields = [
'output_model_name',
'batch_size',
'learning_rate',
'n_epochs',
'save_steps',
'lr_scheduler_type',
'max_length',
'warmup_ratio',
'weight_decay',
]
for (const taskName of taskNames) {
const task = objectFromMarker(mockData, `name: '${taskName}'`)
for (const field of commonTrainingFields) {
assert.match(task, new RegExp(`\\b${field}\\s*:`), `${taskName} 缺少训练参数:${field}`)
}
if (/train_method:\s*'lora'/.test(task)) {
for (const field of ['lora_rank', 'lora_alpha', 'lora_dropout']) {
assert.match(task, new RegExp(`\\b${field}\\s*:`), `${taskName} 缺少 LoRA 参数:${field}`)
}
}
}
const medicalTask = objectFromMarker(mockData, "name: 'medical-cpt-001'")
assert.match(medicalTask, /\bprocess_id\s*:/, '运行中的医疗训练任务缺少进程 ID')
assert.match(mockData, /medical-cpt-001_pid28741\.log/, '医疗训练任务缺少 PID 对应的日志文件')
assert.match(mockData, /export const mockTrainingLogContents/, '训练日志没有按文件提供独立 Mock 内容')
assert.match(mockData, /Num examples\s*=\s*9,800/, '医疗训练日志缺少样本规模信息')
assert.match(mockData, /Total optimization steps\s*=\s*921/, '医疗训练日志缺少真实训练步数信息')
assert.match(mockData, /\\'epoch\\':\s*1\.92/, '医疗训练日志的 Epoch 未与 64% 进度对齐')
assert.match(mockData, /\\'loss\\':\s*1\.146/, '医疗训练日志缺少当前 Loss 指标')
assert.match(mockAdapter, /const file = String\(params\.file/, '训练日志接口没有读取请求中的日志文件名')
assert.match(mockAdapter, /mockTrainingLogContents\[file\]/, '训练日志接口没有按请求文件返回对应内容')
assert.match(modelSource, /epoch:\s*number\[\]/, '训练指标模型没有保留逐步 Epoch')
console.log('训练日志 Mock 数据回归检查通过')