ShopTRAINING/UI/src/views/TrainingView.vue

177 lines
5.9 KiB
Vue
Raw Normal View History

<template>
<el-row :gutter="20">
<!-- 左侧训练控制 -->
<el-col :span="8">
<el-card>
<template #header>
<span>启动模型训练</span>
</template>
<el-form :model="form" label-width="80px">
<el-form-item label="产品">
<el-select v-model="form.product_id" placeholder="请选择产品" filterable>
<el-option v-for="item in products" :key="item.product_id" :label="item.product_name" :value="item.product_id" />
</el-select>
</el-form-item>
<el-form-item label="模型类型">
<el-select v-model="form.model_type" placeholder="请选择模型">
<el-option label="mLSTM" value="mlstm" />
<el-option label="Transformer" value="transformer" />
<el-option label="KAN" value="kan" />
<el-option label="优化版KAN" value="optimized_kan" />
</el-select>
</el-form-item>
<el-form-item label="训练轮次">
<el-input-number v-model="form.epochs" :min="1" :max="1000" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="startTraining" :loading="trainingLoading">启动训练</el-button>
</el-form-item>
</el-form>
</el-card>
</el-col>
<!-- 右侧任务状态 -->
<el-col :span="16">
<el-card>
<template #header>
<span>训练任务队列</span>
</template>
<el-table :data="trainingTasks" stripe>
<el-table-column prop="task_id" label="任务ID" width="120" show-overflow-tooltip></el-table-column>
<el-table-column prop="product_id" label="产品ID" width="100"></el-table-column>
<el-table-column prop="model_type" label="模型类型" width="120"></el-table-column>
<el-table-column prop="status" label="状态" width="100">
<template #default="{ row }">
<el-tag :type="statusTag(row.status)">{{ statusText(row.status) }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="start_time" label="创建时间">
<template #default="{ row }">
{{ formatDateTime(row.start_time) }}
</template>
</el-table-column>
<el-table-column label="详情">
<template #default="{ row }">
<el-popover placement="left" trigger="hover" width="400">
<template #reference>
<el-button type="text" size="small">查看</el-button>
</template>
<div v-if="row.status === 'completed'">
<h4>评估指标</h4>
<pre>{{ JSON.stringify(row.metrics, null, 2) }}</pre>
</div>
<div v-if="row.status === 'failed'">
<h4>错误信息</h4>
<p>{{ row.error }}</p>
</div>
<div v-if="row.status === 'running' || row.status === 'pending'">
<p>任务正在进行中...</p>
</div>
</el-popover>
</template>
</el-table-column>
</el-table>
</el-card>
</el-col>
</el-row>
</template>
<script setup>
import { ref, onMounted, onUnmounted, reactive } from 'vue'
import axios from 'axios'
import { ElMessage, ElPopover, ElButton, ElTag } from 'element-plus'
const products = ref([])
const trainingLoading = ref(false)
const form = reactive({
product_id: '',
model_type: 'mlstm',
epochs: 50,
})
const trainingTasks = ref([])
let pollInterval = null;
const fetchProducts = async () => {
try {
const response = await axios.get('/api/products')
if (response.data.status === 'success') {
products.value = response.data.data
}
} catch (error) {
ElMessage.error('获取产品列表失败')
console.error(error)
}
}
const fetchTrainingTasks = async () => {
try {
const response = await axios.get('/api/training')
if (response.data.status === 'success') {
trainingTasks.value = response.data.data
}
} catch (error) {
// 第一次加载或轮询失败时提示,避免重复提示
if (!pollInterval) ElMessage.error('获取训练任务列表失败');
console.error('获取训练任务列表失败', error)
}
}
const startTraining = async () => {
if (!form.product_id || !form.model_type) {
ElMessage.warning('请选择产品和模型类型')
return
}
trainingLoading.value = true
try {
const response = await axios.post('/api/training', form)
if (response.data.task_id) {
ElMessage.success(`训练任务 ${response.data.task_id} 已启动`)
// 立即刷新一次列表
fetchTrainingTasks();
} else {
ElMessage.error(response.data.error || '启动训练失败')
}
} catch (error) {
const errorMsg = error.response?.data?.error || '启动训练请求失败';
ElMessage.error(errorMsg);
console.error(error);
} finally {
trainingLoading.value = false
}
}
const statusTag = (status) => {
if (status === 'completed') return 'success'
if (status === 'running') return 'primary'
if (status === 'pending') return 'warning'
if (status === 'failed') return 'danger'
return 'info'
}
const statusText = (status) => {
const map = {
'pending': '等待中',
'running': '进行中',
'completed': '已完成',
'failed': '失败'
};
return map[status] || '未知';
}
const formatDateTime = (isoString) => {
if (!isoString) return 'N/A';
return new Date(isoString).toLocaleString();
}
onMounted(() => {
fetchProducts()
fetchTrainingTasks() // 初始加载
pollInterval = setInterval(fetchTrainingTasks, 5000) // 每5秒轮询
})
onUnmounted(() => {
if (pollInterval) {
clearInterval(pollInterval)
}
})
</script>