232 lines
7.0 KiB
Vue
232 lines
7.0 KiB
Vue
|
|
<script setup lang="ts">
|
|||
|
|
import { ref, reactive, computed, onMounted } from 'vue'
|
|||
|
|
import { useRoute, useRouter } from 'vue-router'
|
|||
|
|
import { ElMessage, type FormInstance, type FormRules } from 'element-plus'
|
|||
|
|
import PageCard from '@/components/PageCard.vue'
|
|||
|
|
import {
|
|||
|
|
getModel,
|
|||
|
|
createModel,
|
|||
|
|
updateModel,
|
|||
|
|
getLocalModels,
|
|||
|
|
} from '@/api/modules/model'
|
|||
|
|
import { MODEL_TYPE_MAP } from '@/constants'
|
|||
|
|
import type { ModelForm, ModelSource } from '@/types'
|
|||
|
|
|
|||
|
|
const route = useRoute()
|
|||
|
|
const router = useRouter()
|
|||
|
|
|
|||
|
|
const formRef = ref<FormInstance>()
|
|||
|
|
const loading = ref(false)
|
|||
|
|
const submitting = ref(false)
|
|||
|
|
const isEdit = computed(() => !!route.params.id)
|
|||
|
|
const editId = computed(() => route.params.id as string | undefined)
|
|||
|
|
|
|||
|
|
const localModels = ref<{ path: string; name: string }[]>([])
|
|||
|
|
|
|||
|
|
const form = reactive<ModelForm>({
|
|||
|
|
name: '',
|
|||
|
|
type: 'LLM',
|
|||
|
|
purpose: 'inference',
|
|||
|
|
model_source: 'local',
|
|||
|
|
description: '',
|
|||
|
|
path: '',
|
|||
|
|
api_url: '',
|
|||
|
|
api_key: '',
|
|||
|
|
online_model_name: '',
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
const rules: FormRules = {
|
|||
|
|
name: [
|
|||
|
|
{ required: true, message: '请输入模型名称', trigger: 'blur' },
|
|||
|
|
{ max: 100, message: '不超过 100 字符', trigger: 'blur' },
|
|||
|
|
],
|
|||
|
|
type: [{ required: true, message: '请选择模型类型', trigger: 'change' }],
|
|||
|
|
purpose: [{ required: true, message: '请选择模型用途', trigger: 'change' }],
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/** 根据 model_source 动态校验 */
|
|||
|
|
function getSourceRules(): FormRules {
|
|||
|
|
if (form.model_source === 'local') {
|
|||
|
|
return { path: [{ required: true, message: '请选择本地模型路径', trigger: 'change' }] }
|
|||
|
|
}
|
|||
|
|
return {
|
|||
|
|
api_url: [{ required: true, message: '请输入 API 地址', trigger: 'blur' }],
|
|||
|
|
api_key: [{ required: true, message: '请输入 API Key', trigger: 'blur' }],
|
|||
|
|
online_model_name: [{ required: true, message: '请输入模型名称', trigger: 'blur' }],
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const typeOptions = Object.entries(MODEL_TYPE_MAP).map(([value, label]) => ({ value, label }))
|
|||
|
|
|
|||
|
|
async function loadEditData() {
|
|||
|
|
if (!editId.value) return
|
|||
|
|
loading.value = true
|
|||
|
|
try {
|
|||
|
|
const model: any = await getModel(editId.value)
|
|||
|
|
// 兼容旧数据:online → api
|
|||
|
|
const source: ModelSource = model.model_source === 'online' ? 'api' : model.model_source || 'local'
|
|||
|
|
Object.assign(form, {
|
|||
|
|
name: model.name || '',
|
|||
|
|
type: model.type || 'LLM',
|
|||
|
|
purpose: model.purpose || 'inference',
|
|||
|
|
model_source: source,
|
|||
|
|
description: model.description || '',
|
|||
|
|
path: model.path || '',
|
|||
|
|
api_url: model.api_url || '',
|
|||
|
|
api_key: model.api_key || '',
|
|||
|
|
online_model_name: model.model_name || '',
|
|||
|
|
})
|
|||
|
|
} catch {
|
|||
|
|
// ignore
|
|||
|
|
} finally {
|
|||
|
|
loading.value = false
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async function loadLocalModels() {
|
|||
|
|
try {
|
|||
|
|
const res = await getLocalModels()
|
|||
|
|
localModels.value = res?.models || []
|
|||
|
|
} catch {
|
|||
|
|
localModels.value = []
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async function handleSubmit() {
|
|||
|
|
if (!formRef.value) return
|
|||
|
|
await formRef.value.validate(async (valid) => {
|
|||
|
|
if (!valid) return
|
|||
|
|
// 来源相关字段校验
|
|||
|
|
if (form.model_source === 'local' && !form.path) {
|
|||
|
|
ElMessage.warning('请选择本地模型路径')
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
submitting.value = true
|
|||
|
|
try {
|
|||
|
|
const data: ModelForm = {
|
|||
|
|
name: form.name,
|
|||
|
|
type: form.type,
|
|||
|
|
purpose: form.purpose,
|
|||
|
|
model_source: form.model_source,
|
|||
|
|
description: form.description,
|
|||
|
|
}
|
|||
|
|
if (form.model_source === 'local') {
|
|||
|
|
data.path = form.path
|
|||
|
|
} else {
|
|||
|
|
data.api_url = form.api_url
|
|||
|
|
data.api_key = form.api_key
|
|||
|
|
data.online_model_name = form.online_model_name
|
|||
|
|
}
|
|||
|
|
if (isEdit.value && editId.value) {
|
|||
|
|
await updateModel(editId.value, data)
|
|||
|
|
ElMessage.success('更新成功')
|
|||
|
|
} else {
|
|||
|
|
await createModel(data)
|
|||
|
|
ElMessage.success('创建成功')
|
|||
|
|
}
|
|||
|
|
router.push('/model-manage')
|
|||
|
|
} catch {
|
|||
|
|
// ignore
|
|||
|
|
} finally {
|
|||
|
|
submitting.value = false
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function handleCancel() {
|
|||
|
|
router.back()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
onMounted(() => {
|
|||
|
|
loadLocalModels()
|
|||
|
|
loadEditData()
|
|||
|
|
})
|
|||
|
|
</script>
|
|||
|
|
|
|||
|
|
<template>
|
|||
|
|
<PageCard :title="isEdit ? '编辑模型' : '添加模型'" v-loading="loading">
|
|||
|
|
<el-form
|
|||
|
|
ref="formRef"
|
|||
|
|
:model="form"
|
|||
|
|
:rules="{ ...rules, ...getSourceRules() }"
|
|||
|
|
label-width="120px"
|
|||
|
|
style="max-width: 640px"
|
|||
|
|
>
|
|||
|
|
<el-form-item label="模型名称" prop="name">
|
|||
|
|
<el-input v-model="form.name" placeholder="请输入模型名称" maxlength="100" show-word-limit />
|
|||
|
|
</el-form-item>
|
|||
|
|
|
|||
|
|
<el-form-item label="模型类型" prop="type">
|
|||
|
|
<el-select v-model="form.type" placeholder="请选择模型类型" style="width: 100%">
|
|||
|
|
<el-option v-for="opt in typeOptions" :key="opt.value" :label="opt.label" :value="opt.value" />
|
|||
|
|
</el-select>
|
|||
|
|
</el-form-item>
|
|||
|
|
|
|||
|
|
<el-form-item label="模型用途" prop="purpose">
|
|||
|
|
<el-radio-group v-model="form.purpose">
|
|||
|
|
<el-radio value="training">训练基座</el-radio>
|
|||
|
|
<el-radio value="inference">推理对比</el-radio>
|
|||
|
|
<el-radio value="evaluation">评测</el-radio>
|
|||
|
|
</el-radio-group>
|
|||
|
|
</el-form-item>
|
|||
|
|
|
|||
|
|
<el-form-item label="模型来源" prop="model_source">
|
|||
|
|
<el-radio-group v-model="form.model_source">
|
|||
|
|
<el-radio value="local">本地模型</el-radio>
|
|||
|
|
<el-radio value="api">在线模型</el-radio>
|
|||
|
|
</el-radio-group>
|
|||
|
|
</el-form-item>
|
|||
|
|
|
|||
|
|
<!-- 本地模型 -->
|
|||
|
|
<template v-if="form.model_source === 'local'">
|
|||
|
|
<el-form-item label="本地模型路径" prop="path">
|
|||
|
|
<el-select
|
|||
|
|
v-model="form.path"
|
|||
|
|
placeholder="请选择本地模型路径"
|
|||
|
|
filterable
|
|||
|
|
style="width: 100%"
|
|||
|
|
>
|
|||
|
|
<el-option
|
|||
|
|
v-for="m in localModels"
|
|||
|
|
:key="m.path"
|
|||
|
|
:label="`${m.name} (${m.path})`"
|
|||
|
|
:value="m.path"
|
|||
|
|
/>
|
|||
|
|
</el-select>
|
|||
|
|
</el-form-item>
|
|||
|
|
</template>
|
|||
|
|
|
|||
|
|
<!-- 在线 API 模型 -->
|
|||
|
|
<template v-else>
|
|||
|
|
<el-form-item label="API 地址" prop="api_url">
|
|||
|
|
<el-input v-model="form.api_url" placeholder="如:https://api.openai.com/v1" />
|
|||
|
|
</el-form-item>
|
|||
|
|
<el-form-item label="API Key" prop="api_key">
|
|||
|
|
<el-input v-model="form.api_key" type="password" show-password placeholder="请输入 API Key" />
|
|||
|
|
</el-form-item>
|
|||
|
|
<el-form-item label="模型名称" prop="online_model_name">
|
|||
|
|
<el-input v-model="form.online_model_name" placeholder="如:gpt-4、qwen-turbo" />
|
|||
|
|
</el-form-item>
|
|||
|
|
</template>
|
|||
|
|
|
|||
|
|
<el-form-item label="模型描述">
|
|||
|
|
<el-input
|
|||
|
|
v-model="form.description"
|
|||
|
|
type="textarea"
|
|||
|
|
:rows="3"
|
|||
|
|
maxlength="500"
|
|||
|
|
show-word-limit
|
|||
|
|
placeholder="选填"
|
|||
|
|
/>
|
|||
|
|
</el-form-item>
|
|||
|
|
|
|||
|
|
<el-form-item>
|
|||
|
|
<el-button type="primary" :loading="submitting" @click="handleSubmit">
|
|||
|
|
{{ isEdit ? '保存' : '创建' }}
|
|||
|
|
</el-button>
|
|||
|
|
<el-button @click="handleCancel">取消</el-button>
|
|||
|
|
</el-form-item>
|
|||
|
|
</el-form>
|
|||
|
|
</PageCard>
|
|||
|
|
</template>
|