Add File
This commit is contained in:
597
frontend/src/views/system/model/ModelForm.vue
Normal file
597
frontend/src/views/system/model/ModelForm.vue
Normal 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>
|
||||
Reference in New Issue
Block a user