2026-07-10 16:45:55 +08:00
|
|
|
<script setup lang="ts">
|
|
|
|
|
import { ref, reactive, computed, onMounted } from 'vue'
|
|
|
|
|
import { useRoute, useRouter } from 'vue-router'
|
|
|
|
|
import { ElMessage } from 'element-plus'
|
|
|
|
|
import PageCard from '@/components/PageCard.vue'
|
|
|
|
|
import { getTrainedModels, mergeModel } from '@/api/modules/model'
|
|
|
|
|
import { TRAIN_METHOD_MAP } from '@/constants'
|
|
|
|
|
import type { TrainedModel } from '@/types'
|
|
|
|
|
|
|
|
|
|
const route = useRoute()
|
|
|
|
|
const router = useRouter()
|
|
|
|
|
const merging = ref(false)
|
|
|
|
|
|
|
|
|
|
const modelName = computed(() => (route.query.model as string) || '')
|
|
|
|
|
const method = computed(() => (route.query.method as string) || 'lora')
|
|
|
|
|
|
|
|
|
|
const trainedModels = ref<TrainedModel[]>([])
|
|
|
|
|
const currentModel = computed(() => trainedModels.value.find((m) => m.name === modelName.value))
|
|
|
|
|
|
|
|
|
|
const form = reactive({
|
2026-08-04 16:59:34 +08:00
|
|
|
trained_model_id: '',
|
2026-07-10 16:45:55 +08:00
|
|
|
model_name: modelName.value,
|
|
|
|
|
train_method: method.value,
|
|
|
|
|
base_model_path: '',
|
2026-08-04 16:59:34 +08:00
|
|
|
adapter_path: '',
|
|
|
|
|
compute_node_id: '',
|
2026-07-10 16:45:55 +08:00
|
|
|
})
|
|
|
|
|
|
|
|
|
|
async function loadModel() {
|
|
|
|
|
try {
|
|
|
|
|
const res = await getTrainedModels()
|
|
|
|
|
trainedModels.value = res?.models || []
|
|
|
|
|
const target = trainedModels.value.find((m) => m.name === modelName.value)
|
2026-08-04 16:59:34 +08:00
|
|
|
form.trained_model_id = target?.id == null ? '' : String(target.id)
|
2026-07-10 16:45:55 +08:00
|
|
|
form.base_model_path = target?.base_model_path || ''
|
2026-08-04 16:59:34 +08:00
|
|
|
form.adapter_path = target?.artifact_dir || target?.adapter_path || target?.merged_path || ''
|
|
|
|
|
form.compute_node_id = target?.compute_node_id || ''
|
2026-07-10 16:45:55 +08:00
|
|
|
} catch {
|
|
|
|
|
// ignore
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function handleMerge() {
|
2026-08-04 16:59:34 +08:00
|
|
|
if (!form.model_name || !form.base_model_path || !form.adapter_path) {
|
2026-07-10 16:45:55 +08:00
|
|
|
ElMessage.warning('缺少模型信息')
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
merging.value = true
|
|
|
|
|
try {
|
|
|
|
|
await mergeModel({
|
2026-08-04 16:59:34 +08:00
|
|
|
trained_model_id: form.trained_model_id || form.model_name,
|
2026-07-10 16:45:55 +08:00
|
|
|
model_name: form.model_name,
|
|
|
|
|
train_method: form.train_method,
|
|
|
|
|
base_model_path: form.base_model_path,
|
2026-08-04 16:59:34 +08:00
|
|
|
adapter_path: form.adapter_path,
|
|
|
|
|
compute_node_id: form.compute_node_id,
|
|
|
|
|
output_model_name: `${form.model_name}-merged`,
|
2026-07-10 16:45:55 +08:00
|
|
|
})
|
2026-08-21 10:48:58 +08:00
|
|
|
ElMessage.success('合并任务已提交,完成后会自动更新状态')
|
|
|
|
|
router.push({ path: '/model-manage', query: { tab: 'trained' } })
|
2026-07-10 16:45:55 +08:00
|
|
|
} catch {
|
|
|
|
|
// ignore
|
|
|
|
|
} finally {
|
|
|
|
|
merging.value = false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function handleCancel() {
|
|
|
|
|
router.back()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
onMounted(loadModel)
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<template>
|
|
|
|
|
<PageCard title="合并权重">
|
|
|
|
|
<el-alert
|
|
|
|
|
type="info"
|
|
|
|
|
:closable="false"
|
|
|
|
|
show-icon
|
|
|
|
|
title="将 LoRA 适配器权重合并到基座模型,合并后可直接用于推理部署。"
|
|
|
|
|
style="margin-bottom: 20px"
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
<el-form label-width="120px" style="max-width: 600px">
|
|
|
|
|
<el-form-item label="模型名称">
|
|
|
|
|
<el-input v-model="form.model_name" disabled />
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item label="训练方法">
|
|
|
|
|
<el-input :model-value="TRAIN_METHOD_MAP[form.train_method] || form.train_method" disabled />
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item label="基座模型路径">
|
|
|
|
|
<el-input v-model="form.base_model_path" placeholder="基座模型路径" />
|
|
|
|
|
</el-form-item>
|
2026-08-04 16:59:34 +08:00
|
|
|
<el-form-item label="Adapter 路径">
|
|
|
|
|
<el-input v-model="form.adapter_path" placeholder="LoRA Adapter 权重目录" />
|
|
|
|
|
</el-form-item>
|
2026-07-10 16:45:55 +08:00
|
|
|
|
|
|
|
|
<el-form-item>
|
|
|
|
|
<el-button type="primary" :loading="merging" @click="handleMerge">
|
|
|
|
|
{{ merging ? '合并中...' : '开始合并' }}
|
|
|
|
|
</el-button>
|
|
|
|
|
<el-button @click="handleCancel">取消</el-button>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
</el-form>
|
|
|
|
|
|
|
|
|
|
<div v-if="currentModel" class="model-status">
|
|
|
|
|
<el-tag v-if="currentModel.merging" type="warning">合并中</el-tag>
|
|
|
|
|
<el-tag v-else-if="currentModel.merged" type="success">已合并</el-tag>
|
|
|
|
|
<el-tag v-else type="info">未合并</el-tag>
|
|
|
|
|
</div>
|
|
|
|
|
</PageCard>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
.model-status {
|
|
|
|
|
margin-top: 20px;
|
|
|
|
|
}
|
|
|
|
|
</style>
|