AddModelDialog.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399
  1. <template>
  2. <el-dialog
  3. v-model="dialogVisible"
  4. :title="isEdit ? '编辑模型配置' : '新增模型配置'"
  5. width="800px"
  6. :close-on-click-modal="false"
  7. :destroy-on-close="true"
  8. >
  9. <!-- 步骤导航 -->
  10. <div class="step-header">
  11. <div
  12. v-for="(step, index) in steps"
  13. :key="index"
  14. class="step-item"
  15. :class="{ active: currentStep === index + 1, completed: stepsCompleted[index] }"
  16. >
  17. <span class="step-number">{{ index + 1 }}</span>
  18. <span class="step-title">{{ step }}</span>
  19. </div>
  20. </div>
  21. <!-- 步骤内容 -->
  22. <el-form
  23. ref="formRef"
  24. :model="form"
  25. label-width="120px"
  26. style="margin-top: 20px"
  27. >
  28. <!-- 第一步:选择模型 -->
  29. <div v-if="currentStep === 1">
  30. <el-form-item label="选择模型服务商">
  31. <el-radio-group v-model="form.step1.selectedVendor">
  32. <el-radio v-for="vendor in vendors" :key="vendor.id" :label="vendor.id">
  33. {{ vendor.name }}
  34. </el-radio>
  35. </el-radio-group>
  36. </el-form-item>
  37. <el-form-item label="选择模型" prop="selectedModel">
  38. <el-select
  39. v-model="form.step1.selectedModel"
  40. placeholder="请选择模型"
  41. @change="onModelSelected"
  42. >
  43. <el-option
  44. v-for="model in modelsList"
  45. :key="model.id"
  46. :label="model.name"
  47. :value="model.id"
  48. />
  49. </el-select>
  50. </el-form-item>
  51. </div>
  52. <!-- 第二步:基本配置 -->
  53. <div v-if="currentStep === 2">
  54. <el-form-item label="模型名称" prop="modelName">
  55. <el-input v-model="form.step2.modelName" placeholder="请输入模型名称,如:OPENAI" />
  56. <p class="form-tip">给模型起一个易于识别的名字,便于在企业内部使用</p>
  57. </el-form-item>
  58. <el-form-item label="模型版本" prop="modelVersion">
  59. <el-input v-model="form.step2.modelVersion" placeholder="请输入模型版本" />
  60. <p class="form-tip">API调用时使用的模型版本标识符</p>
  61. </el-form-item>
  62. <el-form-item label="环境" prop="env">
  63. <el-select v-model="form.step2.env" placeholder="请选择环境">
  64. <el-option label="生产" value="PROD" />
  65. <el-option label="测试" value="TEST" />
  66. <el-option label="开发" value="DEV" />
  67. </el-select>
  68. </el-form-item>
  69. <el-form-item label="API基础地址" prop="apiBaseURL">
  70. <el-input v-model="form.step2.apiBaseURL" placeholder="默认使用服务商的API基础地址,可自定义" />
  71. </el-form-item>
  72. <el-form-item label="API密钥" prop="apiKey">
  73. <el-input v-model="form.step2.apiKey" placeholder="API安全密钥将使用安全算法进行加密存储" />
  74. </el-form-item>
  75. <el-form-item label="优先级" prop="priority">
  76. <el-input-number
  77. v-model="form.step2.priority"
  78. :min="0"
  79. :max="100"
  80. controls-position="right"
  81. />
  82. <p class="form-tip">数值越大优先级越高,范围0-100</p>
  83. </el-form-item>
  84. <el-form-item label="状态" prop="status">
  85. <el-switch v-model="form.step2.status" />
  86. <p class="form-tip" :class="{ 'text-gray': !form.step2.status }">
  87. {{ form.step2.status ? '启用' : '禁用' }}
  88. </p>
  89. </el-form-item>
  90. <el-form-item label="描述" prop="description">
  91. <el-input
  92. v-model="form.step2.description"
  93. type="textarea"
  94. :rows="3"
  95. placeholder="请输入模型描述"
  96. />
  97. </el-form-item>
  98. </div>
  99. <!-- 第三步:参数设置 -->
  100. <div v-if="currentStep === 3">
  101. <el-form-item label="快速添加参数模板">
  102. <el-select v-model="form.step3.selectedTemplate" placeholder="请选择模板" @change="addTemplateParams">
  103. <el-option
  104. v-for="template in parameterTemplates"
  105. :key="template.id"
  106. :label="template.name"
  107. :value="template.id"
  108. />
  109. </el-select>
  110. </el-form-item>
  111. <el-form-item label="参数列表">
  112. <el-table :data="form.step3.parameters" border style="width: 100%">
  113. <el-table-column prop="name" label="参数名" />
  114. <el-table-column prop="value" label="参数值">
  115. <template #default="scope">
  116. <el-input v-model="scope.row.value" />
  117. </template>
  118. </el-table-column>
  119. <el-table-column label="操作">
  120. <template #default="scope">
  121. <el-button
  122. size="small"
  123. type="danger"
  124. @click="deleteParameter(scope.$index)"
  125. >
  126. 删除
  127. </el-button>
  128. </template>
  129. </el-table-column>
  130. </el-table>
  131. </el-form-item>
  132. </div>
  133. </el-form>
  134. <!-- 操作按钮 -->
  135. <template #footer>
  136. <div class="dialog-footer">
  137. <el-button @click="prevStep" v-if="currentStep > 1">上一步</el-button>
  138. <el-button @click="cancelDialog">取消</el-button>
  139. <el-button
  140. type="primary"
  141. @click="nextStep"
  142. v-if="currentStep < 3"
  143. >
  144. 下一步
  145. </el-button>
  146. <el-button
  147. type="success"
  148. @click="submitForm"
  149. v-if="currentStep === 3"
  150. >
  151. 保存
  152. </el-button>
  153. </div>
  154. </template>
  155. </el-dialog>
  156. </template>
  157. <script setup>
  158. import { ref, reactive, watch, defineEmits, defineProps } from 'vue'
  159. import { ElMessage } from 'element-plus'
  160. const props = defineProps(['model'])
  161. const emit = defineEmits(['success'])
  162. // 弹窗控制
  163. const dialogVisible = ref(false)
  164. const currentStep = ref(1)
  165. const stepsCompleted = ref([false, false, false])
  166. const isEdit = ref(false)
  167. // 表单数据
  168. const formRef = ref()
  169. const form = reactive({
  170. step1: {
  171. selectedVendor: '',
  172. selectedModel: ''
  173. },
  174. step2: {
  175. modelName: '',
  176. modelVersion: '',
  177. env: '',
  178. apiBaseURL: '',
  179. apiKey: '',
  180. priority: 0,
  181. status: true,
  182. description: ''
  183. },
  184. step3: {
  185. selectedTemplate: '',
  186. parameters: []
  187. }
  188. })
  189. // 表单规则
  190. const rules = {
  191. selectedModel: [{ required: true, message: '请选择模型', trigger: 'change' }],
  192. modelName: [{ required: true, message: '请输入模型名称', trigger: 'blur' }],
  193. modelVersion: [{ required: true, message: '请输入模型版本', trigger: 'blur' }],
  194. env: [{ required: true, message: '请选择环境', trigger: 'change' }],
  195. apiBaseURL: [{ required: true, message: '请输入API基础地址', trigger: 'blur' }],
  196. apiKey: [{ required: true, message: '请输入API密钥', trigger: 'blur' }],
  197. priority: [
  198. { required: true, message: '请输入优先级', trigger: 'blur' },
  199. { type: 'number', min: 0, max: 100, message: '优先级范围0-100', trigger: 'blur' }
  200. ]
  201. }
  202. // 模拟数据
  203. const vendors = ref([
  204. { id: 'v1', name: '阿里云' },
  205. { id: 'v2', name: '腾讯云' }
  206. ])
  207. const modelsList = ref([
  208. { id: 'm1', name: 'Qwen-Plus' },
  209. { id: 'm2', name: 'Qwen-Max' }
  210. ])
  211. const parameterTemplates = ref([
  212. { id: 't1', name: '基础模板', parameters: [{ name: 'timeout', value: '60' }] },
  213. { id: 't2', name: '高级模板', parameters: [{ name: 'retries', value: '3' }] }
  214. ])
  215. // 方法
  216. const open = (row = null) => {
  217. dialogVisible.value = true
  218. isEdit.value = !!row
  219. if (row) {
  220. // 编辑模式初始化数据
  221. form.step1.selectedVendor = row.vendorId
  222. form.step1.selectedModel = row.modelId
  223. form.step2.modelName = row.modelName
  224. form.step2.modelVersion = row.modelVersion
  225. form.step2.env = row.env
  226. form.step2.apiBaseURL = row.apiBaseURL
  227. form.step2.apiKey = row.apiKey
  228. form.step2.priority = row.priority
  229. form.step2.status = row.status
  230. form.step2.description = row.description
  231. form.step3.parameters = row.parameters || []
  232. } else {
  233. resetForm()
  234. }
  235. }
  236. const onModelSelected = () => {
  237. stepsCompleted.value[0] = true
  238. }
  239. const nextStep = async () => {
  240. // const currentRules = getRulesForCurrentStep()
  241. // const isValid = await formRef.value.validate(currentRules)
  242. // if (!isValid) return
  243. currentStep.value++
  244. stepsCompleted.value[currentStep.value - 1] = true
  245. }
  246. const prevStep = () => {
  247. currentStep.value--
  248. }
  249. const cancelDialog = () => {
  250. dialogVisible.value = false
  251. resetForm()
  252. }
  253. const submitForm = async () => {
  254. const isValid = await formRef.value.validate()
  255. if (!isValid) return
  256. // 提交逻辑
  257. ElMessage.success('保存成功')
  258. dialogVisible.value = false
  259. emit('success')
  260. resetForm()
  261. }
  262. const addTemplateParams = () => {
  263. const template = parameterTemplates.value.find(t => t.id === form.step3.selectedTemplate)
  264. if (template) {
  265. form.step3.parameters.push(...template.parameters)
  266. }
  267. }
  268. const deleteParameter = (index) => {
  269. form.step3.parameters.splice(index, 1)
  270. }
  271. const resetForm = () => {
  272. currentStep.value = 1
  273. stepsCompleted.value = [false, false, false]
  274. formRef.value.resetFields()
  275. form.step1.selectedVendor = ''
  276. form.step1.selectedModel = ''
  277. form.step2.modelName = ''
  278. form.step2.modelVersion = ''
  279. form.step2.env = ''
  280. form.step2.apiBaseURL = ''
  281. form.step2.apiKey = ''
  282. form.step2.priority = 0
  283. form.step2.status = true
  284. form.step2.description = ''
  285. form.step3.selectedTemplate = ''
  286. form.step3.parameters = []
  287. }
  288. // 辅助函数
  289. const getRulesForCurrentStep = () => {
  290. const stepRules = {
  291. 1: { selectedModel: rules.selectedModel },
  292. 2: {
  293. modelName: rules.modelName,
  294. modelVersion: rules.modelVersion,
  295. env: rules.env,
  296. apiBaseURL: rules.apiBaseURL,
  297. apiKey: rules.apiKey,
  298. priority: rules.priority
  299. }
  300. }
  301. return stepRules[currentStep.value]
  302. }
  303. defineExpose({ open })
  304. </script>
  305. <style scoped>
  306. .step-header {
  307. display: flex;
  308. justify-content: space-between;
  309. margin-bottom: 20px;
  310. }
  311. .step-item {
  312. flex: 1;
  313. text-align: center;
  314. position: relative;
  315. padding-bottom: 10px;
  316. cursor: not-allowed;
  317. }
  318. .step-item.active, .step-item.completed {
  319. cursor: default;
  320. }
  321. .step-item.active .step-number {
  322. background-color: #409EFF;
  323. color: white;
  324. }
  325. .step-item.completed .step-number {
  326. background-color: #67C23A;
  327. color: white;
  328. }
  329. .step-number {
  330. display: inline-block;
  331. width: 24px;
  332. height: 24px;
  333. line-height: 24px;
  334. border-radius: 50%;
  335. background-color: #E4E7ED;
  336. margin-right: 8px;
  337. font-weight: bold;
  338. }
  339. .form-tip {
  340. margin-top: 4px;
  341. font-size: 12px;
  342. color: #999;
  343. }
  344. .text-gray {
  345. color: #999;
  346. }
  347. .dialog-footer {
  348. display: flex;
  349. justify-content: space-between;
  350. align-items: center;
  351. }
  352. </style>