Files
YG_FT/frontend/src/views/inference/InferenceCreateView.vue
caoxiaozhu 6f6609dae5 feat: 实现业务视图页面
登录、模型调优、评测、推理、对比、模型管理、数据集、数据处理、工具、系统(硬件/日志/训练日志)等全部业务页面视图。
2026-07-10 16:45:55 +08:00

194 lines
5.7 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
import { ref, reactive, computed, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { ElMessage, type FormInstance, type FormRules } from 'element-plus'
import PageCard from '@/components/PageCard.vue'
import { getModelList, getTrainedModels } from '@/api/modules/model'
import { getSystemInfo } from '@/api/modules/system'
import type { ModelItem, TrainedModel, GpuInfo } from '@/types'
const router = useRouter()
const formRef = ref<FormInstance>()
const submitting = ref(false)
const startupStatus = ref('')
const dbModels = ref<ModelItem[]>([])
const trainedModels = ref<TrainedModel[]>([])
const gpus = ref<GpuInfo[]>([])
/** 可选模型(下拉用,区分本地/已训练两类) */
interface SelectableModel {
/** 下拉唯一值db-{id} / trained-{id} */
key: string
id: string | number
name: string
source: 'database' | 'trained'
model_path: string
merged?: boolean
merging?: boolean
disabled?: boolean
}
/** 本地模型选项 */
const dbOptions = computed<SelectableModel[]>(() =>
dbModels.value.map((m) => ({
key: `db-${m.id}`,
id: m.id,
name: m.name,
source: 'database',
model_path: m.path || '',
})),
)
/** 已训练模型选项(未合并的禁用) */
const trainedOptions = computed<SelectableModel[]>(() =>
trainedModels.value.map((m) => ({
key: `trained-${m.id}`,
id: m.id,
name: m.name,
source: 'trained',
model_path: m.merged_path || m.base_model_path || '',
merged: m.merged,
merging: m.merging,
disabled: m.merged === false,
})),
)
/** key → 模型映射,便于取选中项 */
const modelMap = computed<Record<string, SelectableModel>>(() => {
const map: Record<string, SelectableModel> = {}
for (const m of [...dbOptions.value, ...trainedOptions.value]) map[m.key] = m
return map
})
const form = reactive({
name: '',
description: '',
/** 选中的模型 key单选 */
model_key: '',
/** 使用的 GPU */
gpu_id: 0,
})
const rules: FormRules = {
name: [{ required: true, message: '请输入推理名称', trigger: 'blur' }],
model_key: [{ required: true, message: '请选择模型', trigger: 'change' }],
}
/** 当前选中的模型对象 */
const selectedModel = computed(() => modelMap.value[form.model_key])
async function handleSubmit() {
if (!formRef.value) return
await formRef.value.validate(async (valid) => {
if (!valid) return
const m = selectedModel.value
if (!m) {
ElMessage.warning('请选择模型')
return
}
submitting.value = true
startupStatus.value = '正在启动模型服务...'
try {
// 当前为 mock 环境:不创建任务、不启动后端服务,
// 用假数据直通进入对话界面(模型名通过 query 传递)。
// 接入真实后端后,可在此恢复 createCompare / startModelsInBackground / monitorStartup 流程。
await new Promise((resolve) => setTimeout(resolve, 1200))
ElMessage.success('模型已启动')
router.push({
path: '/model-inference/chat/mock',
query: { model: m.name },
})
} finally {
submitting.value = false
startupStatus.value = ''
}
})
}
function handleCancel() {
router.back()
}
async function loadData() {
try {
const [db, trained, sys] = await Promise.all([
getModelList(),
getTrainedModels(),
getSystemInfo(),
])
dbModels.value = db || []
trainedModels.value = trained?.models || []
gpus.value = sys?.gpu || []
// 默认选中第一个 GPU
if (gpus.value.length > 0) form.gpu_id = 0
} catch {
// ignore
}
}
onMounted(loadData)
</script>
<template>
<PageCard title="新建推理">
<el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
<el-form-item label="推理名称" prop="name">
<el-input v-model="form.name" placeholder="请输入推理名称" maxlength="50" show-word-limit style="max-width: 400px" />
</el-form-item>
<el-form-item label="描述">
<el-input v-model="form.description" type="textarea" :rows="2" maxlength="200" show-word-limit style="max-width: 400px" />
</el-form-item>
<el-divider content-position="left">选择模型</el-divider>
<el-form-item label="选择模型" prop="model_key">
<el-select
v-model="form.model_key"
placeholder="请选择模型"
filterable
style="width: 400px"
>
<el-option-group label="本地模型">
<el-option
v-for="m in dbOptions"
:key="m.key"
:label="m.name"
:value="m.key"
/>
</el-option-group>
<el-option-group label="已训练模型">
<el-option
v-for="m in trainedOptions"
:key="m.key"
:label="m.name + (m.disabled ? '(未合并)' : '')"
:value="m.key"
:disabled="m.disabled"
/>
</el-option-group>
</el-select>
</el-form-item>
<el-form-item label="GPU">
<el-select v-model="form.gpu_id" style="width: 400px">
<el-option
v-for="(g, idx) in gpus"
:key="idx"
:label="`${g.name} (GPU${idx})`"
:value="idx"
/>
</el-select>
</el-form-item>
<el-form-item v-if="startupStatus" label="启动状态">
<el-alert :title="startupStatus" type="info" :closable="false" show-icon />
</el-form-item>
<el-form-item>
<el-button type="primary" :loading="submitting" @click="handleSubmit">开始推理</el-button>
<el-button @click="handleCancel">取消</el-button>
</el-form-item>
</el-form>
</PageCard>
</template>