Files
YG_FT/frontend/src/views/inference/InferenceListView.vue

191 lines
5.9 KiB
Vue
Raw Normal View History

<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>