|
|
@@ -0,0 +1,397 @@
|
|
|
+<template>
|
|
|
+ <el-dialog
|
|
|
+ v-model="dialogVisible"
|
|
|
+ :title="isEdit ? '编辑模型配置' : '新增模型配置'"
|
|
|
+ width="800px"
|
|
|
+ :close-on-click-modal="false"
|
|
|
+ :destroy-on-close="true"
|
|
|
+ >
|
|
|
+ <!-- 步骤导航 -->
|
|
|
+ <div class="step-header">
|
|
|
+ <div
|
|
|
+ v-for="(step, index) in steps"
|
|
|
+ :key="index"
|
|
|
+ class="step-item"
|
|
|
+ :class="{ active: currentStep === index + 1, completed: stepsCompleted[index] }"
|
|
|
+ >
|
|
|
+ <span class="step-number">{{ index + 1 }}</span>
|
|
|
+ <span class="step-title">{{ step }}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 步骤内容 -->
|
|
|
+ <el-form
|
|
|
+ ref="formRef"
|
|
|
+ :model="form"
|
|
|
+ :rules="rules"
|
|
|
+ label-width="120px"
|
|
|
+ style="margin-top: 20px"
|
|
|
+ >
|
|
|
+ <!-- 第一步:选择模型 -->
|
|
|
+ <div v-if="currentStep === 1">
|
|
|
+ <el-form-item label="选择模型服务商">
|
|
|
+ <el-radio-group v-model="form.step1.selectedVendor">
|
|
|
+ <el-radio v-for="vendor in vendors" :key="vendor.id" :label="vendor.id">
|
|
|
+ {{ vendor.name }}
|
|
|
+ </el-radio>
|
|
|
+ </el-radio-group>
|
|
|
+ </el-form-item>
|
|
|
+
|
|
|
+ <el-form-item label="选择模型" prop="selectedModel">
|
|
|
+ <el-select
|
|
|
+ v-model="form.step1.selectedModel"
|
|
|
+ placeholder="请选择模型"
|
|
|
+ @change="onModelSelected"
|
|
|
+ >
|
|
|
+ <el-option
|
|
|
+ v-for="model in modelsList"
|
|
|
+ :key="model.id"
|
|
|
+ :label="model.name"
|
|
|
+ :value="model.id"
|
|
|
+ />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 第二步:基本配置 -->
|
|
|
+ <div v-if="currentStep === 2">
|
|
|
+ <el-form-item label="模型名称" prop="modelName">
|
|
|
+ <el-input v-model="form.step2.modelName" placeholder="请输入模型名称,如:智能客服助手" />
|
|
|
+ <p class="form-tip">给模型起一个易于识别的名字,便于在企业内部使用</p>
|
|
|
+ </el-form-item>
|
|
|
+
|
|
|
+ <el-form-item label="模型版本" prop="modelVersion">
|
|
|
+ <el-input v-model="form.step2.modelVersion" placeholder="请输入模型版本" />
|
|
|
+ <p class="form-tip">API调用时使用的模型版本标识符</p>
|
|
|
+ </el-form-item>
|
|
|
+
|
|
|
+ <el-form-item label="环境" prop="env">
|
|
|
+ <el-select v-model="form.step2.env" placeholder="请选择环境">
|
|
|
+ <el-option label="生产" value="PROD" />
|
|
|
+ <el-option label="测试" value="TEST" />
|
|
|
+ <el-option label="开发" value="DEV" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+
|
|
|
+ <el-form-item label="API基础地址" prop="apiBaseURL">
|
|
|
+ <el-input v-model="form.step2.apiBaseURL" placeholder="默认使用服务商的API基础地址,可自定义" />
|
|
|
+ </el-form-item>
|
|
|
+
|
|
|
+ <el-form-item label="API密钥" prop="apiKey">
|
|
|
+ <el-input v-model="form.step2.apiKey" placeholder="API安全密钥将使用安全算法进行加密存储" />
|
|
|
+ </el-form-item>
|
|
|
+
|
|
|
+ <el-form-item label="优先级" prop="priority">
|
|
|
+ <el-input-number
|
|
|
+ v-model="form.step2.priority"
|
|
|
+ :min="0"
|
|
|
+ :max="100"
|
|
|
+ controls-position="right"
|
|
|
+ />
|
|
|
+ <p class="form-tip">数值越大优先级越高,范围0-100</p>
|
|
|
+ </el-form-item>
|
|
|
+
|
|
|
+ <el-form-item label="状态" prop="status">
|
|
|
+ <el-switch v-model="form.step2.status" />
|
|
|
+ <p class="form-tip" :class="{ 'text-gray': !form.step2.status }">
|
|
|
+ {{ form.step2.status ? '启用' : '禁用' }}
|
|
|
+ </p>
|
|
|
+ </el-form-item>
|
|
|
+
|
|
|
+ <el-form-item label="描述" prop="description">
|
|
|
+ <el-input
|
|
|
+ v-model="form.step2.description"
|
|
|
+ type="textarea"
|
|
|
+ :rows="3"
|
|
|
+ placeholder="请输入模型描述"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 第三步:参数设置 -->
|
|
|
+ <div v-if="currentStep === 3">
|
|
|
+ <el-form-item label="快速添加参数模板">
|
|
|
+ <el-select v-model="form.step3.selectedTemplate" placeholder="请选择模板" @change="addTemplateParams">
|
|
|
+ <el-option
|
|
|
+ v-for="template in parameterTemplates"
|
|
|
+ :key="template.id"
|
|
|
+ :label="template.name"
|
|
|
+ :value="template.id"
|
|
|
+ />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+
|
|
|
+ <el-form-item label="参数列表">
|
|
|
+ <el-table :data="form.step3.parameters" border style="width: 100%">
|
|
|
+ <el-table-column prop="name" label="参数名" />
|
|
|
+ <el-table-column prop="value" label="参数值">
|
|
|
+ <template #default="scope">
|
|
|
+ <el-input v-model="scope.row.value" />
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="操作">
|
|
|
+ <template #default="scope">
|
|
|
+ <el-button
|
|
|
+ size="small"
|
|
|
+ type="danger"
|
|
|
+ @click="deleteParameter(scope.$index)"
|
|
|
+ >
|
|
|
+ 删除
|
|
|
+ </el-button>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ </el-table>
|
|
|
+ </el-form-item>
|
|
|
+ </div>
|
|
|
+ </el-form>
|
|
|
+
|
|
|
+ <!-- 操作按钮 -->
|
|
|
+ <template #footer>
|
|
|
+ <div class="dialog-footer">
|
|
|
+ <el-button @click="prevStep" v-if="currentStep > 1">上一步</el-button>
|
|
|
+ <el-button @click="cancelDialog">取消</el-button>
|
|
|
+ <el-button
|
|
|
+ type="primary"
|
|
|
+ @click="nextStep"
|
|
|
+ v-if="currentStep < 3"
|
|
|
+ >
|
|
|
+ 下一步
|
|
|
+ </el-button>
|
|
|
+ <el-button
|
|
|
+ type="success"
|
|
|
+ @click="submitForm"
|
|
|
+ v-if="currentStep === 3"
|
|
|
+ >
|
|
|
+ 保存
|
|
|
+ </el-button>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-dialog>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup>
|
|
|
+import { ref, reactive, watch, defineEmits, defineProps } from 'vue'
|
|
|
+import { ElMessage } from 'element-plus'
|
|
|
+
|
|
|
+const props = defineProps(['model'])
|
|
|
+const emit = defineEmits(['success'])
|
|
|
+
|
|
|
+// 弹窗控制
|
|
|
+const dialogVisible = ref(false)
|
|
|
+const currentStep = ref(1)
|
|
|
+const stepsCompleted = ref([false, false, false])
|
|
|
+const isEdit = ref(false)
|
|
|
+
|
|
|
+// 表单数据
|
|
|
+const formRef = ref()
|
|
|
+const form = reactive({
|
|
|
+ step1: {
|
|
|
+ selectedVendor: '',
|
|
|
+ selectedModel: ''
|
|
|
+ },
|
|
|
+ step2: {
|
|
|
+ modelName: '',
|
|
|
+ modelVersion: '',
|
|
|
+ env: '',
|
|
|
+ apiBaseURL: '',
|
|
|
+ apiKey: '',
|
|
|
+ priority: 0,
|
|
|
+ status: true,
|
|
|
+ description: ''
|
|
|
+ },
|
|
|
+ step3: {
|
|
|
+ selectedTemplate: '',
|
|
|
+ parameters: []
|
|
|
+ }
|
|
|
+})
|
|
|
+
|
|
|
+// 表单规则
|
|
|
+const rules = {
|
|
|
+ selectedModel: [{ required: true, message: '请选择模型', trigger: 'change' }],
|
|
|
+ modelName: [{ required: true, message: '请输入模型名称', trigger: 'blur' }],
|
|
|
+ modelVersion: [{ required: true, message: '请输入模型版本', trigger: 'blur' }],
|
|
|
+ env: [{ required: true, message: '请选择环境', trigger: 'change' }],
|
|
|
+ apiBaseURL: [{ required: true, message: '请输入API基础地址', trigger: 'blur' }],
|
|
|
+ apiKey: [{ required: true, message: '请输入API密钥', trigger: 'blur' }],
|
|
|
+ priority: [
|
|
|
+ { required: true, message: '请输入优先级', trigger: 'blur' },
|
|
|
+ { type: 'number', min: 0, max: 100, message: '优先级范围0-100', trigger: 'blur' }
|
|
|
+ ]
|
|
|
+}
|
|
|
+
|
|
|
+// 模拟数据
|
|
|
+const vendors = ref([
|
|
|
+ { id: 'v1', name: '阿里云' },
|
|
|
+ { id: 'v2', name: '腾讯云' }
|
|
|
+])
|
|
|
+
|
|
|
+const modelsList = ref([
|
|
|
+ { id: 'm1', name: 'Qwen-Plus' },
|
|
|
+ { id: 'm2', name: 'Qwen-Max' }
|
|
|
+])
|
|
|
+
|
|
|
+const parameterTemplates = ref([
|
|
|
+ { id: 't1', name: '基础模板', parameters: [{ name: 'timeout', value: '60' }] },
|
|
|
+ { id: 't2', name: '高级模板', parameters: [{ name: 'retries', value: '3' }] }
|
|
|
+])
|
|
|
+
|
|
|
+// 方法
|
|
|
+const open = (row = null) => {
|
|
|
+ dialogVisible.value = true
|
|
|
+ isEdit.value = !!row
|
|
|
+
|
|
|
+ if (row) {
|
|
|
+ // 编辑模式初始化数据
|
|
|
+ form.step1.selectedVendor = row.vendorId
|
|
|
+ form.step1.selectedModel = row.modelId
|
|
|
+ form.step2.modelName = row.modelName
|
|
|
+ form.step2.modelVersion = row.modelVersion
|
|
|
+ form.step2.env = row.env
|
|
|
+ form.step2.apiBaseURL = row.apiBaseURL
|
|
|
+ form.step2.apiKey = row.apiKey
|
|
|
+ form.step2.priority = row.priority
|
|
|
+ form.step2.status = row.status
|
|
|
+ form.step2.description = row.description
|
|
|
+ form.step3.parameters = row.parameters || []
|
|
|
+ } else {
|
|
|
+ resetForm()
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const onModelSelected = () => {
|
|
|
+ stepsCompleted.value[0] = true
|
|
|
+}
|
|
|
+
|
|
|
+const nextStep = async () => {
|
|
|
+ const currentRules = getRulesForCurrentStep()
|
|
|
+ const isValid = await formRef.value.validate(currentRules)
|
|
|
+ if (!isValid) return
|
|
|
+
|
|
|
+ currentStep.value++
|
|
|
+ stepsCompleted.value[currentStep.value - 1] = true
|
|
|
+}
|
|
|
+
|
|
|
+const prevStep = () => {
|
|
|
+ currentStep.value--
|
|
|
+}
|
|
|
+
|
|
|
+const cancelDialog = () => {
|
|
|
+ dialogVisible.value = false
|
|
|
+ resetForm()
|
|
|
+}
|
|
|
+
|
|
|
+const submitForm = async () => {
|
|
|
+ const isValid = await formRef.value.validate()
|
|
|
+ if (!isValid) return
|
|
|
+
|
|
|
+ // 提交逻辑
|
|
|
+ ElMessage.success('保存成功')
|
|
|
+ dialogVisible.value = false
|
|
|
+ emit('success')
|
|
|
+ resetForm()
|
|
|
+}
|
|
|
+
|
|
|
+const addTemplateParams = () => {
|
|
|
+ const template = parameterTemplates.value.find(t => t.id === form.step3.selectedTemplate)
|
|
|
+ if (template) {
|
|
|
+ form.step3.parameters.push(...template.parameters)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const deleteParameter = (index) => {
|
|
|
+ form.step3.parameters.splice(index, 1)
|
|
|
+}
|
|
|
+
|
|
|
+const resetForm = () => {
|
|
|
+ currentStep.value = 1
|
|
|
+ stepsCompleted.value = [false, false, false]
|
|
|
+ formRef.value.resetFields()
|
|
|
+ form.step1.selectedVendor = ''
|
|
|
+ form.step1.selectedModel = ''
|
|
|
+ form.step2.modelName = ''
|
|
|
+ form.step2.modelVersion = ''
|
|
|
+ form.step2.env = ''
|
|
|
+ form.step2.apiBaseURL = ''
|
|
|
+ form.step2.apiKey = ''
|
|
|
+ form.step2.priority = 0
|
|
|
+ form.step2.status = true
|
|
|
+ form.step2.description = ''
|
|
|
+ form.step3.selectedTemplate = ''
|
|
|
+ form.step3.parameters = []
|
|
|
+}
|
|
|
+
|
|
|
+// 辅助函数
|
|
|
+const getRulesForCurrentStep = () => {
|
|
|
+ const stepRules = {
|
|
|
+ 1: { selectedModel: rules.selectedModel },
|
|
|
+ 2: {
|
|
|
+ modelName: rules.modelName,
|
|
|
+ modelVersion: rules.modelVersion,
|
|
|
+ env: rules.env,
|
|
|
+ apiBaseURL: rules.apiBaseURL,
|
|
|
+ apiKey: rules.apiKey,
|
|
|
+ priority: rules.priority
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return stepRules[currentStep.value]
|
|
|
+}
|
|
|
+
|
|
|
+defineExpose({ open })
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+.step-header {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ margin-bottom: 20px;
|
|
|
+}
|
|
|
+
|
|
|
+.step-item {
|
|
|
+ flex: 1;
|
|
|
+ text-align: center;
|
|
|
+ position: relative;
|
|
|
+ padding-bottom: 10px;
|
|
|
+ cursor: not-allowed;
|
|
|
+}
|
|
|
+
|
|
|
+.step-item.active, .step-item.completed {
|
|
|
+ cursor: default;
|
|
|
+}
|
|
|
+
|
|
|
+.step-item.active .step-number {
|
|
|
+ background-color: #409EFF;
|
|
|
+ color: white;
|
|
|
+}
|
|
|
+
|
|
|
+.step-item.completed .step-number {
|
|
|
+ background-color: #67C23A;
|
|
|
+ color: white;
|
|
|
+}
|
|
|
+
|
|
|
+.step-number {
|
|
|
+ display: inline-block;
|
|
|
+ width: 24px;
|
|
|
+ height: 24px;
|
|
|
+ line-height: 24px;
|
|
|
+ border-radius: 50%;
|
|
|
+ background-color: #E4E7ED;
|
|
|
+ margin-right: 8px;
|
|
|
+ font-weight: bold;
|
|
|
+}
|
|
|
+
|
|
|
+.form-tip {
|
|
|
+ margin-top: 4px;
|
|
|
+ font-size: 12px;
|
|
|
+ color: #999;
|
|
|
+}
|
|
|
+
|
|
|
+.text-gray {
|
|
|
+ color: #999;
|
|
|
+}
|
|
|
+
|
|
|
+.dialog-footer {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+}
|
|
|
+</style>
|