Files
SQLBot/frontend/src/views/system/model/ModelForm.vue
T
2025-07-28 10:10:00 +08:00

490 lines
12 KiB
Vue

<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 } 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: [],
})
const isCreate = ref(false)
const modelRef = ref()
const paramsFormRef = ref()
const advancedSetting = ref([] as ParamsFormData[])
const paramsFormDrawer = ref(false)
const configExpand = ref(false)
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 = {
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' }],
api_key: [
{
required: true,
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()
return
}
for (const key in advancedSetting.value) {
const element = advancedSetting.value[key]
if (element.id === item.id) {
Object.assign(element, { ...item })
}
}
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 = ''
}
let curId = +new Date()
const initForm = (item?: any) => {
modelForm.id = ''
modelRef.value.clearValidate()
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
}
})
}
}
}
const emits = defineEmits(['submit'])
const submitModel = () => {
modelRef.value.validate((res: any) => {
if (res) {
emits('submit', {
...modelForm,
config_list: [...advancedSetting.value],
})
}
})
}
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"
: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"
>
<el-option v-for="item in modelList" :key="item" :label="item" :value="item" />
</el-select>
</el-form-item>
<el-form-item prop="api_domain" :label="t('model.api_domain_name')">
<el-input
v-model="modelForm.api_domain"
:placeholder="
$t('datasource.please_enter') + $t('common.empty') + $t('model.api_domain_name')
"
/>
</el-form-item>
<el-form-item prop="api_key" label="API Key">
<el-input
v-model="modelForm.api_key"
:placeholder="$t('datasource.please_enter') + $t('common.empty') + 'API Key'"
type="password"
show-password
/>
</el-form-item>
</el-form>
<div
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="configExpand" class="model-params">
{{ t('model.model_parameters') }}
<span class="add" @click="handleParamsCreate">
<el-icon size="16">
<icon_add_outlined></icon_add_outlined>
</el-icon>
{{ t('model.add') }}
</span>
</div>
<div v-if="configExpand" class="params-table">
<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" :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="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% - 120px);
.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;
.add {
display: flex;
align-items: center;
cursor: pointer;
}
.ed-icon {
margin-right: 4px;
}
}
.params-table {
border-radius: 6px;
border: 1px solid #dee0e3;
border-top: none;
border-bottom: none;
overflow-y: auto;
: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: #1cba901a;
}
}
.operation-column_text {
.ed-button {
color: #646a73;
height: 24px;
}
.ed-button:not(.is-disabled):hover {
background: #1f23291a;
}
.ed-button + .ed-button {
margin-left: 4px;
}
}
}
}
</style>