<template>
|
<ElDialog
|
:title="dialogTitle"
|
:model-value="visible"
|
width="920px"
|
align-center
|
@update:model-value="handleCancel"
|
@closed="handleClosed"
|
>
|
<ArtForm
|
ref="formRef"
|
v-model="form"
|
:items="formItems"
|
:rules="rules"
|
:span="12"
|
:gutter="20"
|
label-width="110px"
|
:show-reset="false"
|
:show-submit="false"
|
/>
|
|
<div
|
v-if="showRuntimeSection"
|
class="mt-2 rounded-2xl border border-[var(--art-border-color)] px-5 py-4"
|
>
|
<div class="mb-3 flex items-center justify-between gap-4">
|
<div>
|
<h4 class="text-base font-semibold text-[var(--art-gray-900)]">
|
{{ t('pages.system.aiParam.dialog.runtimeTitle') }}
|
</h4>
|
<p class="mt-1 text-sm text-[var(--art-gray-500)]">
|
{{ t('pages.system.aiParam.dialog.runtimeDescription') }}
|
</p>
|
</div>
|
<ElButton v-if="!isReadonly" :loading="validateLoading" @click="handleValidateDraft">
|
{{ t('pages.system.aiParam.dialog.validateDraft') }}
|
</ElButton>
|
</div>
|
|
<ElAlert
|
v-if="validateResultMessage"
|
class="!mb-4"
|
:type="validateAlertType"
|
:closable="false"
|
:title="validateResultMessage"
|
/>
|
|
<ElDescriptions :column="2" border>
|
<ElDescriptionsItem :label="t('pages.system.aiParam.dialog.labels.validateStatus')">
|
{{ form.validateStatus || '--' }}
|
</ElDescriptionsItem>
|
<ElDescriptionsItem :label="t('pages.system.aiParam.dialog.labels.lastValidateElapsedMs')">
|
{{
|
form.lastValidateElapsedMs !== null && form.lastValidateElapsedMs !== undefined
|
? `${form.lastValidateElapsedMs} ms`
|
: '--'
|
}}
|
</ElDescriptionsItem>
|
<ElDescriptionsItem :label="t('pages.system.aiParam.dialog.labels.lastValidateTime')">
|
{{ form['lastValidateTime$'] || '--' }}
|
</ElDescriptionsItem>
|
<ElDescriptionsItem :label="t('pages.system.aiParam.dialog.labels.updateBy')">
|
{{ form.updateBy || '--' }}
|
</ElDescriptionsItem>
|
<ElDescriptionsItem :label="t('pages.system.aiParam.dialog.labels.updateTime')" :span="2">
|
{{ form['updateTime$'] || '--' }}
|
</ElDescriptionsItem>
|
<ElDescriptionsItem
|
:label="t('pages.system.aiParam.dialog.labels.lastValidateMessage')"
|
:span="2"
|
>
|
<div class="whitespace-pre-wrap break-all text-sm leading-6 text-[var(--art-gray-700)]">
|
{{ form.lastValidateMessage || '--' }}
|
</div>
|
</ElDescriptionsItem>
|
</ElDescriptions>
|
</div>
|
|
<template #footer>
|
<span class="dialog-footer">
|
<ElButton @click="handleCancel">
|
{{ isReadonly ? t('common.actions.close') : t('common.cancel') }}
|
</ElButton>
|
<ElButton v-if="!isReadonly" type="primary" @click="handleSubmit">
|
{{ t('common.confirm') }}
|
</ElButton>
|
</span>
|
</template>
|
</ElDialog>
|
</template>
|
|
<script setup>
|
import { useI18n } from 'vue-i18n'
|
import ArtForm from '@/components/core/forms/art-form/index.vue'
|
import { fetchValidateAiParamDraft } from '@/api/ai-config'
|
import {
|
buildAiParamDialogModel,
|
buildAiParamSavePayload,
|
createAiParamFormState,
|
getAiParamProviderOptions,
|
getAiParamStatusOptions
|
} from '../aiParamPage.helpers'
|
|
const props = defineProps({
|
visible: { type: Boolean, default: false },
|
mode: { type: String, default: 'create' },
|
aiParamData: { type: Object, default: () => ({}) }
|
})
|
|
const emit = defineEmits(['submit', 'update:visible'])
|
const { t } = useI18n()
|
const formRef = ref()
|
const form = reactive(createAiParamFormState())
|
const validateLoading = ref(false)
|
const validateResult = ref(null)
|
|
const isReadonly = computed(() => props.mode === 'show')
|
const showRuntimeSection = computed(() => Boolean(form.id) || props.mode !== 'create')
|
const dialogTitle = computed(() => {
|
if (props.mode === 'edit') return t('pages.system.aiParam.dialog.titleEdit')
|
if (props.mode === 'show') return t('pages.system.aiParam.dialog.titleDetail')
|
return t('pages.system.aiParam.dialog.titleCreate')
|
})
|
|
const validateAlertType = computed(() =>
|
validateResult.value?.status === 'VALID' ? 'success' : 'warning'
|
)
|
|
const validateResultMessage = computed(() => {
|
if (!validateResult.value?.message) {
|
return ''
|
}
|
const suffix = [
|
validateResult.value.elapsedMs ? `${validateResult.value.elapsedMs} ms` : '',
|
validateResult.value.validatedAt || ''
|
]
|
.filter(Boolean)
|
.join(' · ')
|
return suffix ? `${validateResult.value.message} · ${suffix}` : validateResult.value.message
|
})
|
|
const rules = computed(() => ({
|
name: [{ required: true, message: t('pages.system.aiParam.dialog.validation.name'), trigger: 'blur' }],
|
providerType: [
|
{
|
required: true,
|
message: t('pages.system.aiParam.dialog.validation.providerType'),
|
trigger: 'change'
|
}
|
],
|
baseUrl: [{ required: true, message: t('pages.system.aiParam.dialog.validation.baseUrl'), trigger: 'blur' }],
|
apiKey: [{ required: true, message: t('pages.system.aiParam.dialog.validation.apiKey'), trigger: 'blur' }],
|
model: [{ required: true, message: t('pages.system.aiParam.dialog.validation.model'), trigger: 'blur' }]
|
}))
|
|
const formItems = computed(() => [
|
{
|
label: t('pages.system.aiParam.dialog.labels.name'),
|
key: 'name',
|
type: 'input',
|
props: {
|
clearable: true,
|
placeholder: t('pages.system.aiParam.dialog.placeholders.name'),
|
disabled: isReadonly.value
|
}
|
},
|
{
|
label: t('pages.system.aiParam.dialog.labels.providerType'),
|
key: 'providerType',
|
type: 'select',
|
props: {
|
options: getAiParamProviderOptions(),
|
disabled: isReadonly.value,
|
placeholder: t('pages.system.aiParam.dialog.placeholders.providerType')
|
}
|
},
|
{
|
label: t('pages.system.aiParam.dialog.labels.baseUrl'),
|
key: 'baseUrl',
|
type: 'input',
|
span: 24,
|
props: {
|
clearable: true,
|
placeholder: t('pages.system.aiParam.dialog.placeholders.baseUrl'),
|
disabled: isReadonly.value
|
}
|
},
|
{
|
label: t('pages.system.aiParam.dialog.labels.apiKey'),
|
key: 'apiKey',
|
type: 'input',
|
props: {
|
clearable: true,
|
placeholder: t('pages.system.aiParam.dialog.placeholders.apiKey'),
|
disabled: isReadonly.value,
|
type: isReadonly.value ? 'text' : 'password',
|
showPassword: !isReadonly.value
|
}
|
},
|
{
|
label: t('pages.system.aiParam.dialog.labels.model'),
|
key: 'model',
|
type: 'input',
|
props: {
|
clearable: true,
|
placeholder: t('pages.system.aiParam.dialog.placeholders.model'),
|
disabled: isReadonly.value
|
}
|
},
|
{
|
label: t('pages.system.aiParam.dialog.labels.temperature'),
|
key: 'temperature',
|
type: 'number',
|
props: {
|
min: 0,
|
max: 2,
|
step: 0.1,
|
precision: 2,
|
placeholder: t('pages.system.aiParam.dialog.placeholders.temperature'),
|
disabled: isReadonly.value
|
}
|
},
|
{
|
label: t('pages.system.aiParam.dialog.labels.topP'),
|
key: 'topP',
|
type: 'number',
|
props: {
|
min: 0,
|
max: 1,
|
step: 0.1,
|
precision: 2,
|
placeholder: t('pages.system.aiParam.dialog.placeholders.topP'),
|
disabled: isReadonly.value
|
}
|
},
|
{
|
label: t('pages.system.aiParam.dialog.labels.maxTokens'),
|
key: 'maxTokens',
|
type: 'number',
|
props: {
|
min: 1,
|
step: 1,
|
placeholder: t('pages.system.aiParam.dialog.placeholders.maxTokens'),
|
disabled: isReadonly.value
|
}
|
},
|
{
|
label: t('pages.system.aiParam.dialog.labels.timeoutMs'),
|
key: 'timeoutMs',
|
type: 'number',
|
props: {
|
min: 1000,
|
step: 1000,
|
placeholder: t('pages.system.aiParam.dialog.placeholders.timeoutMs'),
|
disabled: isReadonly.value
|
}
|
},
|
{
|
label: t('pages.system.aiParam.dialog.labels.streamingEnabled'),
|
key: 'streamingEnabled',
|
type: 'switch',
|
props: {
|
disabled: isReadonly.value,
|
activeText: t('common.status.enabled'),
|
inactiveText: t('common.status.disabled')
|
}
|
},
|
{
|
label: t('pages.system.aiParam.dialog.labels.status'),
|
key: 'status',
|
type: 'select',
|
props: {
|
disabled: isReadonly.value,
|
options: getAiParamStatusOptions(),
|
placeholder: t('pages.system.aiParam.dialog.placeholders.status')
|
}
|
},
|
{
|
label: t('pages.system.aiParam.dialog.labels.memo'),
|
key: 'memo',
|
type: 'input',
|
span: 24,
|
props: {
|
disabled: isReadonly.value,
|
type: 'textarea',
|
rows: 3,
|
placeholder: t('pages.system.aiParam.dialog.placeholders.memo')
|
}
|
}
|
])
|
|
function resetForm() {
|
Object.assign(form, createAiParamFormState())
|
validateResult.value = null
|
formRef.value?.clearValidate?.()
|
}
|
|
function loadFormData() {
|
Object.assign(form, buildAiParamDialogModel(props.aiParamData))
|
validateResult.value = null
|
}
|
|
async function handleValidateDraft() {
|
validateLoading.value = true
|
try {
|
const result = await fetchValidateAiParamDraft(buildAiParamSavePayload(form))
|
validateResult.value = result
|
Object.assign(form, {
|
validateStatus: result?.status || form.validateStatus,
|
lastValidateMessage: result?.message || '',
|
lastValidateElapsedMs: result?.elapsedMs ?? null,
|
'lastValidateTime$': result?.validatedAt || ''
|
})
|
} catch {
|
return
|
} finally {
|
validateLoading.value = false
|
}
|
}
|
|
async function handleSubmit() {
|
if (!formRef.value) return
|
try {
|
await formRef.value.validate()
|
emit('submit', buildAiParamSavePayload(form))
|
} catch {
|
return
|
}
|
}
|
|
function handleCancel() {
|
emit('update:visible', false)
|
}
|
|
function handleClosed() {
|
resetForm()
|
}
|
|
watch(
|
() => props.visible,
|
(visible) => {
|
if (visible) {
|
loadFormData()
|
nextTick(() => formRef.value?.clearValidate?.())
|
}
|
},
|
{ immediate: true }
|
)
|
|
watch(
|
() => props.aiParamData,
|
() => {
|
if (props.visible) {
|
loadFormData()
|
}
|
},
|
{ deep: true }
|
)
|
</script>
|