feat: 更新后端平台模块、前端组件及构建产物,新增工作计划文档

- 更新 backend 平台 API endpoints 及 platform_store
- 更新前端 ComputeNodesView、DataProcessCreateView、FineTuneCreateView 等组件
- 更新前端 API 模块(compute、fineTune)
- 重构 frontend/dist 构建产物(新 hash)
- 新增 docs/2026-07-24-work-plan.md 工作计划文档

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
wuyongtao
2026-07-24 10:27:52 +08:00
parent b28cfbc6fa
commit 2b10c013ce
151 changed files with 925 additions and 181 deletions

View File

@@ -6,9 +6,11 @@ import PageCard from '@/components/PageCard.vue'
import ModelSelectDialog from '@/components/ModelSelectDialog.vue'
import {
createFineTune,
preflightFineTune,
startFineTune,
updateFineTune,
checkFineTuneName,
type FineTunePreflightResult,
} from '@/api/modules/fineTune'
import { getModelList } from '@/api/modules/model'
import { getDatasetList } from '@/api/modules/dataset'
@@ -27,6 +29,8 @@ import type { ModelItem, DatasetItem, GpuInfo } from '@/types'
const router = useRouter()
const formRef = ref<FormInstance>()
const submitting = ref(false)
const preflightLoading = ref(false)
const preflightResult = ref<FineTunePreflightResult | null>(null)
const models = ref<ModelItem[]>([])
const datasets = ref<DatasetItem[]>([])
@@ -60,6 +64,12 @@ const modelDialogTitle = computed(() => selectedModel.value?.name || '')
/** 训练命令与提交载荷共用同一份表单模型。 */
const commandPreview = computed(() => buildFineTuneCommand(form, selectedGpus.value))
const remoteCommandPreview = computed(() => {
const command = preflightResult.value?.preview?.command
if (Array.isArray(command) && command.length) return command.join(' ')
return preflightResult.value?.preview?.command_text || ''
})
/** GPU 多选切换 */
function toggleGpu(index: number) {
const idx = selectedGpus.value.indexOf(index)
@@ -184,6 +194,11 @@ async function handleSubmit() {
}
const payload = buildFineTunePayload(form, selectedGpus.value)
const preflight = await runPreflight(payload)
if (!preflight?.valid) {
ElMessage.error('训练预检未通过,请先处理预检问题')
return
}
const createRes = await createFineTune(toCreateFineTunePayload(payload))
const taskId = createRes.id
@@ -203,6 +218,43 @@ async function handleSubmit() {
})
}
async function runPreflight(payload = buildFineTunePayload(form, selectedGpus.value)) {
preflightLoading.value = true
try {
const result = await preflightFineTune(payload)
preflightResult.value = result
if (result.valid) {
ElMessage.success('训练预检通过')
} else {
ElMessage.warning('训练预检未通过')
}
return result
} catch {
preflightResult.value = {
valid: false,
errors: ['预检接口调用失败,请检查后端服务和算力节点连接'],
warnings: [],
diagnostics: [{ level: 'error', title: '预检调用失败', suggestion: '请确认后端服务可访问 Compute API并检查浏览器或后端日志。' }],
}
ElMessage.error('训练预检失败')
return preflightResult.value
} finally {
preflightLoading.value = false
}
}
async function handlePreflightClick() {
if (!formRef.value) return
await formRef.value.validate(async (valid) => {
if (!valid) return
if (selectedGpus.value.length === 0) {
ElMessage.warning('请至少选择一个 GPU')
return
}
await runPreflight()
})
}
function handleCancel() {
router.back()
}
@@ -437,9 +489,47 @@ onMounted(() => {
<!-- 训练命令预览 -->
<el-divider content-position="left">训练命令预览</el-divider>
<div class="preflight-actions">
<el-button type="primary" plain :loading="preflightLoading" @click="handlePreflightClick">
<i class="fa fa-check-circle-o" style="margin-right: 4px;" /> 预检训练配置
</el-button>
<span class="preflight-hint">预检会调用后端调度和 Compute validate提前检查数据格式模型路径GPU 条件和真实训练命令</span>
</div>
<div class="command-preview-wrapper">
<pre class="command-preview">{{ commandPreview }}</pre>
</div>
<div v-if="preflightResult" class="preflight-panel" :class="{ 'is-valid': preflightResult.valid, 'is-invalid': !preflightResult.valid }">
<div class="preflight-header">
<strong>{{ preflightResult.valid ? '预检通过' : '预检未通过' }}</strong>
<span v-if="preflightResult.node">
调度节点{{ preflightResult.node.code || preflightResult.node.name || preflightResult.node.id }}
</span>
</div>
<div v-if="remoteCommandPreview" class="preflight-section">
<span class="section-label">后端真实命令</span>
<pre class="command-preview remote">{{ remoteCommandPreview }}</pre>
</div>
<div v-if="preflightResult.errors?.length" class="preflight-section">
<span class="section-label">错误</span>
<ul>
<li v-for="item in preflightResult.errors" :key="item">{{ item }}</li>
</ul>
</div>
<div v-if="preflightResult.warnings?.length" class="preflight-section">
<span class="section-label">警告</span>
<ul>
<li v-for="item in preflightResult.warnings" :key="item">{{ item }}</li>
</ul>
</div>
<div v-if="preflightResult.diagnostics?.length" class="preflight-section">
<span class="section-label">诊断建议</span>
<ul>
<li v-for="item in preflightResult.diagnostics" :key="item.title">
<strong>{{ item.title }}</strong>{{ item.suggestion }}
</li>
</ul>
</div>
</div>
<div class="form-actions-wrapper">
<el-button type="primary" :loading="submitting" @click="handleSubmit">创建并启动训练</el-button>
@@ -604,6 +694,67 @@ onMounted(() => {
margin-bottom: 22px;
}
.preflight-actions {
display: flex;
align-items: center;
gap: 12px;
margin: 0 0 12px 80px;
}
.preflight-hint {
color: #64748b;
font-size: 12px;
}
.preflight-panel {
margin: -8px 0 24px 80px;
border: 1px solid #e5e7eb;
border-radius: 8px;
padding: 14px 16px;
background: #f8fafc;
&.is-valid {
border-color: #bbf7d0;
background: #f0fdf4;
}
&.is-invalid {
border-color: #fecaca;
background: #fef2f2;
}
}
.preflight-header {
display: flex;
align-items: center;
gap: 16px;
margin-bottom: 10px;
color: #111827;
span {
color: #64748b;
font-size: 12px;
}
}
.preflight-section {
margin-top: 10px;
ul {
margin: 6px 0 0;
padding-left: 18px;
color: #374151;
line-height: 1.7;
}
}
.section-label {
display: inline-flex;
color: #475569;
font-size: 12px;
font-weight: 700;
}
.field-tip {
color: #909399;
font-size: 12px;
@@ -637,6 +788,11 @@ onMounted(() => {
overflow-x: auto;
width: 100%;
margin: 0;
&.remote {
margin-top: 6px;
background: #111827;
}
}
.model-picker-input {