This commit is contained in:
2025-09-08 16:38:16 +08:00
parent 32ff7c0020
commit dca9726708

View File

@@ -0,0 +1,597 @@
<script lang="ts" setup>
import { ref, reactive, onMounted, nextTick, computed } from 'vue'
import arrow_down from '@/assets/svg/arrow-down.svg'
import dashboard_info from '@/assets/svg/dashboard-info.svg'
import icon_edit_outlined from '@/assets/svg/icon_edit_outlined.svg'
import icon_delete from '@/assets/svg/icon_delete.svg'
import icon_add_outlined from '@/assets/svg/icon_add_outlined.svg'
import ParamsForm from './ParamsForm.vue'
import { modelTypeOptions } from '@/entity/CommonEntity.ts'
import { base_model_options, get_supplier } from '@/entity/supplier'
import { useI18n } from 'vue-i18n'
withDefaults(
defineProps<{
activeName: string
editModel: boolean
}>(),
{
activeName: '',
editModel: false,
}
)
interface ParamsFormData {
key?: string
val?: string
name?: string
id?: string
}
const { t } = useI18n()
const modelForm = reactive({
id: '',
supplier: 0,
name: '',
model_type: 0,
base_model: '',
api_key: '',
api_domain: '',
config_list: [],
protocol: 1,
})
const isCreate = ref(false)
const modelRef = ref()
const paramsFormRef = ref()
const advancedSetting = ref([] as ParamsFormData[])
const paramsFormDrawer = ref(false)
const configExpand = ref(true)
let tempConfigMap = new Map<string, Array<any>>()
const modelSelected = computed(() => {
return !!modelForm.base_model
})
const currentSupplier = computed(() => {
if (!modelForm.supplier) {
return null
}
return get_supplier(modelForm.supplier)
})
const modelList = computed(() => {
if (!modelForm.supplier) {
return []
}
return base_model_options(modelForm.supplier, modelForm.model_type)
})
const handleParamsEdite = (ele?: any) => {
isCreate.value = false
paramsFormDrawer.value = true
nextTick(() => {
paramsFormRef.value.initForm(ele)
})
}
const handleParamsCreate = () => {
isCreate.value = true
paramsFormDrawer.value = true
nextTick(() => {
paramsFormRef.value.initForm()
})
}
const handleParamsDel = (item: any) => {
advancedSetting.value = advancedSetting.value.filter((ele) => ele.id !== item.id)
}
const currentPage = ref(1)
const advancedSettingPagination = computed(() => {
return advancedSetting.value.slice(currentPage.value * 5 - 5, currentPage.value * 5)
})
const handleCurrentChange = (val: any) => {
currentPage.value = val
}
const rules = computed(() => ({
model_type: [
{
required: true,
message: 'type',
trigger: 'change',
},
],
api_domain: [
{
required: true,
message: t('datasource.please_enter') + t('common.empty') + t('model.api_domain_name'),
trigger: 'blur',
},
],
base_model: [{ required: true, message: t('model.the_basic_model_de'), trigger: 'blur' }],
name: [
{ required: true, message: t('model.the_basic_model'), trigger: 'blur' },
{
max: 100,
message: t('model.length_max_error', { msg: t('model.model_name'), max: 100 }),
trigger: 'blur',
},
],
api_key: [
{
required: !currentSupplier.value?.is_private,
message: t('datasource.please_enter') + t('common.empty') + 'API Key',
trigger: 'blur',
},
],
}))
onMounted(() => {
setTimeout(() => {
modelRef.value.clearValidate()
}, 100)
})
const addParams = () => {
paramsFormRef.value.submit()
}
const duplicateName = async (item: any) => {
const arr = advancedSetting.value.filter((ele: any) => ele.id !== item.id)
const names = arr.map((ele: any) => ele.name)
const keys = arr.map((ele: any) => ele.key)
if (names.includes(item.name)) {
ElMessage.error(t('embedded.duplicate_name'))
return
}
if (keys.includes(item.key)) {
ElMessage.error(t('embedded.repeating_parameters'))
return
}
if (isCreate.value) {
advancedSetting.value.push({ ...item, id: +new Date() })
beforeClose()
tempConfigMap.set(`${modelForm.supplier}-${modelForm.base_model}`, [...advancedSetting.value])
return
}
for (const key in advancedSetting.value) {
const element = advancedSetting.value[key]
if (element.id === item.id) {
Object.assign(element, { ...item })
}
}
tempConfigMap.set(`${modelForm.supplier}-${modelForm.base_model}`, [...advancedSetting.value])
beforeClose()
}
const submit = (item: any) => {
duplicateName(item)
}
const beforeClose = () => {
paramsFormRef.value.close()
paramsFormDrawer.value = false
}
const supplierChang = (supplier: any) => {
modelForm.supplier = supplier.id
const config = supplier.model_config[modelForm.model_type || 0]
modelForm.api_domain = config.api_domain
modelForm.base_model = ''
modelForm.protocol = supplier.type === 'vllm' ? 2 : 1
advancedSetting.value = []
}
let curId = +new Date()
const initForm = (item?: any) => {
modelForm.id = ''
modelRef.value.clearValidate()
tempConfigMap = new Map<string, Array<any>>()
if (item) {
Object.assign(modelForm, { ...item })
if (item?.config_list?.length) {
advancedSetting.value = item.config_list
advancedSetting.value.forEach((ele: any) => {
if (!ele.id) {
ele.id = curId
curId += 1
}
})
} else {
advancedSetting.value = []
}
tempConfigMap.set(`${modelForm.supplier}-${modelForm.base_model}`, [...advancedSetting.value])
}
}
const formatAdvancedSetting = (list: Array<any>) => {
const setting_list = [
...list.map((item) => {
return { id: ++curId, name: item.name, key: item.key, val: item.val } as any
}),
]
advancedSetting.value = setting_list
}
const baseModelChange = (val: string) => {
if (!val || !modelForm.supplier) {
return
}
const current_model = modelList.value?.find((model: any) => model.name == val)
if (current_model) {
modelForm.api_domain = current_model.api_domain || getSupplierDomain() || ''
}
const current_config_list = tempConfigMap.get(`${modelForm.supplier}-${modelForm.base_model}`)
if (current_config_list) {
formatAdvancedSetting(current_config_list)
return
}
const defaultArgs = getModelDefaultArgs()
if (defaultArgs?.size) {
const defaultArgsList = [...defaultArgs.values()]
formatAdvancedSetting(defaultArgsList)
tempConfigMap.set(`${modelForm.supplier}-${modelForm.base_model}`, [...advancedSetting.value])
}
}
const getSupplierDomain = () => {
return currentSupplier.value?.model_config[modelForm.model_type || 0].api_domain
}
const getModelDefaultArgs = () => {
if (!modelForm.supplier || !modelForm.base_model) {
return null
}
const model_config = currentSupplier.value?.model_config[modelForm.model_type || 0]
const common_args = model_config?.common_args || []
const current_model = modelList.value?.find((model: any) => model.name == modelForm.base_model)
if (current_model?.args?.length) {
const modelArgs = current_model.args
common_args.push(...modelArgs)
}
const argMap = common_args.reduce((acc: any, item: any) => {
acc.set(item.key, { ...item, name: item.key })
return acc
}, new Map())
return argMap
}
const emits = defineEmits(['submit'])
const submitModel = () => {
modelRef.value.validate((res: any) => {
if (res) {
emits('submit', {
...modelForm,
config_list: [
...advancedSetting.value.map((item) => {
return { key: item.key, name: item.name, val: item.val }
}),
],
})
}
})
}
defineExpose({
initForm,
submitModel,
supplierChang,
})
</script>
<template>
<div class="model-form" :class="editModel && 'is-edit_model'">
<div v-if="!editModel" class="model-name">{{ activeName }}</div>
<div class="form-content">
<el-form
ref="modelRef"
:rules="rules"
label-position="top"
:model="modelForm"
style="width: 100%"
@submit.prevent
>
<el-form-item class="custom-require flex-inline" prop="name">
<template #label
><span class="custom-require_danger">{{ t('model.model_name') }}</span>
<el-tooltip effect="dark" :content="t('model.custom_model_name')" placement="right">
<el-icon style="margin-left: 4px" size="16">
<dashboard_info></dashboard_info>
</el-icon>
</el-tooltip>
</template>
<el-input
v-model="modelForm.name"
clearable
:placeholder="
$t('datasource.please_enter') + $t('common.empty') + $t('model.model_name')
"
/>
</el-form-item>
<el-form-item prop="type" :label="t('model.model_type')">
<el-select v-model="modelForm.model_type" style="width: 100%" disabled>
<el-option
v-for="item in modelTypeOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item class="custom-require" prop="base_model">
<template #label
><span class="custom-require_danger">{{ t('model.basic_model') }}</span>
<span class="enter">{{ t('model.enter_to_add') }}</span>
</template>
<el-select
v-model="modelForm['base_model']"
style="width: 100%"
filterable
allow-create
default-first-option
:reserve-keyword="false"
@change="baseModelChange"
>
<el-option
v-for="item in modelList"
:key="item.name"
:label="item.name"
:value="item.name"
/>
</el-select>
</el-form-item>
<el-form-item v-if="modelSelected" prop="api_domain" :label="t('model.api_domain_name')">
<el-input
v-model="modelForm.api_domain"
clearable
:placeholder="
$t('datasource.please_enter') + $t('common.empty') + $t('model.api_domain_name')
"
/>
</el-form-item>
<el-form-item v-if="modelSelected" prop="api_key" label="API Key">
<el-input
v-model="modelForm.api_key"
clearable
:placeholder="$t('datasource.please_enter') + $t('common.empty') + 'API Key'"
type="password"
show-password
/>
</el-form-item>
</el-form>
<div
v-if="modelSelected"
class="advance-setting"
:class="configExpand && 'expand'"
@click="configExpand = !configExpand"
>
{{ t('model.advanced_settings') }}
<el-icon size="16">
<arrow_down></arrow_down>
</el-icon>
</div>
<div v-if="modelSelected && configExpand" class="model-params">
{{ t('model.model_parameters') }}
<span class="btn" @click="handleParamsCreate">
<el-icon size="16">
<icon_add_outlined></icon_add_outlined>
</el-icon>
{{ t('model.add') }}
</span>
</div>
<div
v-if="modelSelected && configExpand"
class="params-table"
:class="!advancedSettingPagination.length && 'bottom-border'"
>
<el-table :data="advancedSettingPagination" style="width: 100%">
<el-table-column prop="key" :label="t('model.parameters')" width="280" />
<el-table-column prop="name" :label="t('model.display_name')" width="280" />
<el-table-column prop="val" show-overflow-tooltip :label="t('model.parameter_value')" />
<el-table-column
fixed="right"
width="80"
class-name="operation-column_text"
:label="$t('ds.actions')"
>
<template #default="scope">
<el-button text type="primary" @click="handleParamsEdite(scope.row)">
<el-icon size="16">
<icon_edit_outlined></icon_edit_outlined>
</el-icon>
</el-button>
<el-button text type="primary" @click="handleParamsDel(scope.row)">
<el-icon size="16">
<icon_delete></icon_delete>
</el-icon>
</el-button>
</template>
</el-table-column>
</el-table>
</div>
<div
v-if="modelSelected && advancedSetting.length > 5 && configExpand"
class="params-table_pagination"
>
<el-pagination
:default-page-size="5"
layout="prev, pager, next"
:total="advancedSetting.length"
@current-change="handleCurrentChange"
/>
</div>
</div>
<el-drawer
v-model="paramsFormDrawer"
:size="600"
:before-close="beforeClose"
:title="
isCreate
? $t('model.add') + $t('common.empty') + $t('model.parameters')
: $t('datasource.edit') + $t('common.empty') + $t('model.parameters')
"
>
<ParamsForm ref="paramsFormRef" @submit="submit"></ParamsForm>
<template #footer>
<el-button secondary @click="beforeClose"> {{ $t('common.cancel') }} </el-button>
<el-button type="primary" @click="addParams">
{{ isCreate ? t('model.add') : t('common.save') }}
</el-button>
</template>
</el-drawer>
</div>
</template>
<style lang="less" scoped>
.model-form {
width: calc(100% - 280px);
position: absolute;
right: 0;
top: 56px;
height: 100%;
&.is-edit_model {
width: 100%;
}
.model-name {
height: 56px;
width: 100%;
padding-left: 24px;
border-bottom: 1px solid #1f232926;
font-weight: 500;
font-size: 16px;
line-height: 24px;
display: flex;
align-items: center;
}
.form-content {
width: 800px;
margin: 0 auto;
padding-top: 24px;
overflow-y: auto;
height: calc(100% - 176px);
padding-bottom: 24px;
.ed-form-item--default {
margin-bottom: 16px;
&.is-error {
margin-bottom: 40px;
}
}
:deep(
.custom-require.ed-form-item.is-required:not(.is-no-asterisk).asterisk-right
> .ed-form-item__label:after
) {
display: none;
}
:deep(.flex-inline .ed-form-item__label) {
display: inline-flex;
align-items: center;
}
.enter {
font-weight: 400;
font-size: 14px;
line-height: 22px;
color: #ff8800;
margin-left: 8px;
}
.custom-require_danger::after {
color: var(--ed-color-danger);
content: '*';
margin-left: 2px;
}
.advance-setting {
display: flex;
align-items: center;
font-weight: 500;
font-size: 14px;
line-height: 22px;
cursor: pointer;
.ed-icon {
margin-left: 8px;
}
&.expand {
.ed-icon {
transform: rotate(180deg);
}
}
}
.model-params {
display: flex;
align-items: center;
justify-content: space-between;
font-weight: 400;
font-size: 14px;
line-height: 22px;
margin: 16px 0 8px 0;
.btn {
height: 26px;
display: flex;
align-items: center;
justify-content: center;
padding: 0 4px;
border-radius: 6px;
cursor: pointer;
&:hover {
background-color: #1f23291a;
}
}
.ed-icon {
margin-right: 4px;
}
}
.params-table {
border-radius: 6px;
border: 1px solid #dee0e3;
border-top: none;
border-bottom: none;
overflow-y: auto;
&.bottom-border {
border-bottom: 1px solid #dee0e3;
}
:deep(.ed-table .ed-table__cell) {
padding: 7px 0;
}
:deep(.ed-table .cell) {
line-height: 24px;
}
}
.params-table_pagination {
margin-top: 8px;
.ed-pagination {
justify-content: flex-end;
}
:deep(.ed-pager li.number:hover) {
background-color: var(--ed-color-primary-1a, #1cba901a);
}
}
.operation-column_text {
.ed-button {
color: #646a73;
height: 24px;
}
.ed-button:not(.is-disabled):hover {
background: #1f23291a;
}
.ed-button + .ed-button {
margin-left: 8px;
}
}
}
}
</style>