11<script setup lang="ts">
2- import { computed , onBeforeUnmount , ref , useTemplateRef } from ' vue'
3- import { Check , Operation } from ' @ element-plus/icons-vue '
2+ import { computed , ref , useTemplateRef } from ' vue'
3+ import type { SelectInstance } from ' element-plus'
44import { MODEL_STATUS } from ' @/api/enums'
55import type { ModelProviderItem , ModelItem } from ' @/api/types'
66import { groupBy } from ' lodash'
77import ModelParamsDialog from ' ./ModelParamsDialog.vue'
8+ import ModelCreateButton from ' @/views/model/create-model/ModelCreateButton.vue'
9+ import ModelApi from ' @/api/admin/workspace/model/model'
10+ import SystemSharedModelApi from ' @/api/admin/system/shared-resources/model'
11+ import { isWorkspaceResource , isSystemSharedResource } from ' @/utils/resource-context'
812defineOptions ({ name: ' ModelSelect' , inheritAttrs: false })
913
1014interface ModelOptionGroup {
@@ -19,28 +23,50 @@ const props = withDefaults(
1923 modelValue: string
2024 options: ModelItem []
2125 providerOptions: ModelProviderItem []
22- showModelParams? : boolean
26+ canEditParams? : boolean
27+ canAdd? : boolean
2328 modelParams? : Record <string , unknown >
2429 disabled? : boolean
2530 }>(),
2631 {
2732 modelValue: ' ' ,
2833 options : () => [],
2934 providerOptions : () => [],
30- showModelParams: false ,
35+ canEditParams: false ,
36+ canAdd: false ,
3137 modelParams : () => ({}),
3238 disabled: false ,
3339 },
3440)
35- const _options = computed (() => {
36- return groupBy (props .options , ' provider' )
37- })
41+
3842const emit = defineEmits <{
3943 change: [modelId : string ]
44+ refresh: []
4045 ' update:modelValue' : [modelId : string ]
4146 ' update:modelParams' : [settings : Record <string , unknown >]
4247}>()
4348
49+ const modelOptionGroups = computed <ModelOptionGroup []>(() => {
50+ const providerMap = new Map (props .providerOptions .map ((provider ) => [provider .provider , provider ]))
51+
52+ return Object .entries (_options .value ?? {}).map (([provider , models ]) => {
53+ const providerOption = providerMap .get (provider )
54+ return {
55+ icon: providerOption ?.icon ?? ' ' ,
56+ models: [... models ].sort ((left , right ) => Number (right .status === MODEL_STATUS .SUCCESS ) - Number (left .status === MODEL_STATUS .SUCCESS )),
57+ name: providerOption ?.name ?? provider ,
58+ provider ,
59+ }
60+ })
61+ })
62+
63+ const selectedProviderIcon = computed (
64+ () => modelOptionGroups .value .find (({ models }) => models .some (({ id }) => id === selectedModelId .value ))?.icon ?? ' ' ,
65+ )
66+ const _options = computed (() => {
67+ return groupBy (props .options , ' provider' )
68+ })
69+
4470const loading = ref (false )
4571
4672const selectedModelId = computed ({
@@ -52,17 +78,30 @@ const selectedModelId = computed({
5278 },
5379})
5480
81+ // 创建模型:根据当前资源范围传入完整 API,创建后由调用方刷新选项。
82+ const selectRef = useTemplateRef <SelectInstance >(' selectRef' )
83+ const allModelProvider: ModelProviderItem = { icon: ' ' , name: ' 全部模型' , provider: ' all' }
84+ const createModelApi = computed (() => {
85+ if (isWorkspaceResource ()) return ModelApi
86+ if (isSystemSharedResource ()) return SystemSharedModelApi
87+ return undefined
88+ })
89+
90+ function handleOpenCreateModel(open : () => void ) {
91+ if (props .disabled || ! props .canAdd || ! createModelApi .value ) return
92+ selectRef .value ?.blur ()
93+ open ()
94+ }
95+
5596// 模型参数:切换模型加载默认值,确认弹窗后回写配置。
5697const modelParamsDialogRef = useTemplateRef <InstanceType <typeof ModelParamsDialog >>(' modelParamsDialogRef' )
57- let modelParamsRequestId = 0
5898
5999function resetModelParams(modelId : string ) {
60- const requestId = ++ modelParamsRequestId
61- if (! props .showModelParams ) return
100+ if (! props .canEditParams ) return
62101 emit (' update:modelParams' , {})
63102 if (! modelId ) return
64103 modelParamsDialogRef .value ?.resetDefault (modelId ).then ((settings ) => {
65- if (requestId !== modelParamsRequestId || props .modelValue !== modelId || ! props .showModelParams ) return
104+ if (props .modelValue !== modelId || ! props .canEditParams ) return
66105 emit (' update:modelParams' , settings )
67106 })
68107}
@@ -71,36 +110,16 @@ function openModelParams() {
71110 if (! props .modelValue || props .disabled ) return
72111 modelParamsDialogRef .value ?.open (props .modelValue , props .modelParams )
73112}
74-
75- onBeforeUnmount (() => {
76- modelParamsRequestId += 1
77- })
78-
79- const modelOptionGroups = computed <ModelOptionGroup []>(() => {
80- const providerMap = new Map (props .providerOptions .map ((provider ) => [provider .provider , provider ]))
81-
82- return Object .entries (_options .value ?? {}).map (([provider , models ]) => {
83- const providerOption = providerMap .get (provider )
84- return {
85- icon: providerOption ?.icon ?? ' ' ,
86- models: [... models ].sort ((left , right ) => Number (right .status === MODEL_STATUS .SUCCESS ) - Number (left .status === MODEL_STATUS .SUCCESS )),
87- name: providerOption ?.name ?? provider ,
88- provider ,
89- }
90- })
91- })
92-
93- const selectedProviderIcon = computed (
94- () => modelOptionGroups .value .find (({ models }) => models .some (({ id }) => id === selectedModelId .value ))?.icon ?? ' ' ,
95- )
96113 </script >
97114
98115<template >
99- <div class =" flex w-full items-center gap-2 " >
116+ <div class =" relative w-full" :class = " { 'model-select--with-params': canEditParams } " >
100117 <el-select
118+ ref =" selectRef"
101119 v-model =" selectedModelId"
120+ placeholder =" 请选择模型"
102121 v-bind =" $attrs"
103- class =" min-w-0 flex-1 "
122+ class =" w-full "
104123 :disabled =" disabled"
105124 clearable
106125 filterable
@@ -120,7 +139,6 @@ const selectedProviderIcon = computed(
120139 <span class =" min-w-0 flex-1 truncate" :title =" model.name" >{{ model.name }}</span >
121140 <el-tag v-if =" model.source === 'shared'" size =" small" type =" info" >共享</el-tag >
122141 <span v-if =" model.status !== MODEL_STATUS.SUCCESS" class =" text-danger" >不可用</span >
123- <Check v-if =" model .id === selectedModelId " class="h-4 w-4 shrink-0 text-primary" />
124142 </div >
125143 </el-option >
126144 </el-option-group >
@@ -132,13 +150,34 @@ const selectedProviderIcon = computed(
132150 </div >
133151 </template >
134152
135- <template #empty >
136- <MkEmpty description="暂无可用模型" />
153+ <template v-if =" canAdd && createModelApi " #footer >
154+ <slot name =" footer" >
155+ <ModelCreateButton :api =" createModelApi " :current-provider =" allModelProvider " :providers =" providerOptions " @refresh =" emit (' refresh' )" >
156+ <template #default =" { open } " >
157+ <el-button type =" primary" link :disabled =" disabled" @click.stop =" handleOpenCreateModel(open)" >
158+ <MkIcon name="icon_add_outlined" />
159+ <span >添加模型</span >
160+ </el-button >
161+ </template >
162+ </ModelCreateButton >
163+ </slot >
137164 </template >
138165 </el-select >
139- <el-button v-if =" showModelParams" :disabled =" disabled || !modelValue" title =" 模型参数设置" aria-label =" 模型参数设置" @click =" openModelParams" >
140- <MkIcon :icon =" Operation " />
141- </el-button >
166+ <div v-if =" canEditParams" class =" absolute inset-y-px right-3 flex items-center gap-3" >
167+ <el-divider direction =" vertical" />
168+ <el-button link :disabled =" disabled || !modelValue" title =" 模型参数设置" @click.stop =" openModelParams" >
169+ <MkIcon name="icon_preferences_outlined" />
170+ </el-button >
171+ </div >
142172 </div >
143- <ModelParamsDialog v-if =" showModelParams " ref="modelParamsDialogRef" @submit =" emit (' update:modelParams' , $event )" />
173+ <ModelParamsDialog v-if =" canEditParams " ref="modelParamsDialogRef" @submit =" emit (' update:modelParams' , $event )" />
144174</template >
175+
176+ <style scoped>
177+ /* 参数入口与选择器共用外边框,为原生下拉箭头和清空按钮预留空间。 */
178+ .model-select--with-params {
179+ :deep(.el-select__wrapper) {
180+ padding-right : calc (var (--spacing ) * 14 );
181+ }
182+ }
183+ </style >
0 commit comments