feat: 实现业务视图页面
登录、模型调优、评测、推理、对比、模型管理、数据集、数据处理、工具、系统(硬件/日志/训练日志)等全部业务页面视图。
This commit is contained in:
190
frontend/src/views/inference/InferenceListView.vue
Normal file
190
frontend/src/views/inference/InferenceListView.vue
Normal file
@@ -0,0 +1,190 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import DataTablePage from '@/components/DataTablePage.vue'
|
||||
import {
|
||||
getCompareList,
|
||||
deleteCompare,
|
||||
getCompare,
|
||||
loadCompare,
|
||||
unloadCompare,
|
||||
stopModelByPid,
|
||||
} from '@/api/modules/compare'
|
||||
import type { CompareTask, LoadedModel } from '@/types'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const loading = ref(false)
|
||||
const dataList = ref<CompareTask[]>([])
|
||||
let refreshTimer: ReturnType<typeof setInterval> | null = null
|
||||
|
||||
async function loadData(silent = false) {
|
||||
if (!silent) {
|
||||
loading.value = true
|
||||
}
|
||||
try {
|
||||
dataList.value = (await getCompareList()) || []
|
||||
} catch {
|
||||
// ignore
|
||||
} finally {
|
||||
if (!silent) {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 解析 load_status 判断就绪状态 */
|
||||
function isStarting(row: any): boolean {
|
||||
const models = parseLoadedModels(row)
|
||||
return models.length > 0 && models.some((m) => m.status === 'starting')
|
||||
}
|
||||
|
||||
function isReady(row: any): boolean {
|
||||
const models = parseLoadedModels(row)
|
||||
if (models.length === 0) return row.status === 'loaded' || row.status === 'ready'
|
||||
return models.every((m) => m.status === 'ready' || m.status === 'running') && !isStarting(row)
|
||||
}
|
||||
|
||||
function parseLoadedModels(row: any): LoadedModel[] {
|
||||
if (!row.load_status) return []
|
||||
try {
|
||||
const parsed =
|
||||
typeof row.load_status === 'string' ? JSON.parse(row.load_status) : row.load_status
|
||||
return parsed.loaded_models || []
|
||||
} catch {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
/** 解析相关模型字段,返回模型名称列表 */
|
||||
function parseModelNames(row: any): string[] {
|
||||
if (!row.models) return []
|
||||
// 数组结构直接取
|
||||
if (Array.isArray(row.models)) {
|
||||
return row.models.map((m: any) => m.model_name || m.name).filter(Boolean)
|
||||
}
|
||||
// 字符串结构需先 JSON.parse
|
||||
try {
|
||||
const parsed = JSON.parse(row.models)
|
||||
if (Array.isArray(parsed)) {
|
||||
return parsed.map((m: any) => m.model_name || m.name).filter(Boolean)
|
||||
}
|
||||
} catch {
|
||||
// 非法 JSON,忽略
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
/** 加载推理任务 */
|
||||
async function handleLoad(row: any) {
|
||||
await loadCompare(row.id)
|
||||
ElMessage.info('正在加载模型,请稍候...')
|
||||
setTimeout(loadData, 1000)
|
||||
}
|
||||
|
||||
/** 卸载推理任务 */
|
||||
async function handleUnload(row: any) {
|
||||
await ElMessageBox.confirm('确定要停止模型服务吗?', '确认停止', { type: 'warning' })
|
||||
await unloadCompare(row.id)
|
||||
ElMessage.success('已停止模型服务')
|
||||
loadData()
|
||||
}
|
||||
|
||||
/** 删除(先停止进程) */
|
||||
async function handleDelete(row: any) {
|
||||
// 先尝试停止已加载的模型进程
|
||||
const task = await getCompare(row.id).catch(() => null)
|
||||
if (task?.load_status) {
|
||||
const models = parseLoadedModels(task as CompareTask)
|
||||
for (const m of models) {
|
||||
if (m.pid) {
|
||||
await stopModelByPid(m.pid).catch(() => {})
|
||||
}
|
||||
}
|
||||
}
|
||||
await deleteCompare(row.id)
|
||||
ElMessage.success('删除成功')
|
||||
}
|
||||
|
||||
/** 开始对话 */
|
||||
function startChat(row: any) {
|
||||
router.push(`/model-inference/chat/${row.id}`)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadData()
|
||||
refreshTimer = setInterval(() => loadData(true), 3000)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
if (refreshTimer) clearInterval(refreshTimer)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DataTablePage
|
||||
title="模型推理"
|
||||
:data="dataList"
|
||||
:loading="loading"
|
||||
searchable
|
||||
:search-fields="['name', 'model_name', 'description']"
|
||||
create-text="新建推理"
|
||||
create-to="/model-inference/create"
|
||||
:delete-fn="handleDelete"
|
||||
row-key="id"
|
||||
@refresh="loadData"
|
||||
>
|
||||
<template #columns>
|
||||
<el-table-column label="推理名称" align="center">
|
||||
<template #default="{ row }">{{ row.model_name || row.name || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="描述" align="center" show-overflow-tooltip>
|
||||
<template #default="{ row }">{{ row.description || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" align="center" width="120">
|
||||
<template #default="{ row }">
|
||||
<el-tag v-if="isStarting(row)" type="warning" size="small">启动中</el-tag>
|
||||
<el-tag v-else-if="isReady(row)" type="success" size="small">已就绪</el-tag>
|
||||
<el-tag v-else type="info" size="small">{{ row.status || '未启动' }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="相关模型" align="center" show-overflow-tooltip>
|
||||
<template #default="{ row }">
|
||||
{{ parseModelNames(row).join(',') || '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="创建时间" align="center" width="180">
|
||||
<template #default="{ row }">
|
||||
{{ row.create_time ? new Date(row.create_time).toLocaleString('zh-CN') : '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
</template>
|
||||
|
||||
<template #actions="{ row }">
|
||||
<div class="action-buttons">
|
||||
<template v-if="isStarting(row)">
|
||||
<el-button type="warning" link size="small" :loading="true" disabled>
|
||||
加载中
|
||||
</el-button>
|
||||
</template>
|
||||
<template v-else-if="isReady(row)">
|
||||
<el-button type="success" link size="small" @click="startChat(row)">
|
||||
<i class="fa fa-comments-o" style="margin-right: 4px" />对话
|
||||
</el-button>
|
||||
<el-button type="warning" link size="small" @click="handleUnload(row)">
|
||||
<i class="fa fa-stop-circle-o" style="margin-right: 4px" />停止
|
||||
</el-button>
|
||||
</template>
|
||||
<template v-else>
|
||||
<el-button type="primary" link size="small" @click="handleLoad(row)">
|
||||
<i class="fa fa-play-circle-o" style="margin-right: 4px" />加载
|
||||
</el-button>
|
||||
</template>
|
||||
<el-button type="danger" link size="small" @click="handleDelete(row)">
|
||||
<i class="fa fa-trash-o" style="margin-right: 4px" />删除
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</DataTablePage>
|
||||
</template>
|
||||
Reference in New Issue
Block a user