mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-06-19 22:50:41 +08:00
681 lines
22 KiB
Vue
681 lines
22 KiB
Vue
<script lang="ts" setup>
|
|
import { computed, onMounted, watch } from 'vue'
|
|
import { useToast } from 'vue-toastification'
|
|
import { useI18n } from 'vue-i18n'
|
|
import { useSetupWizard } from '@/composables/useSetupWizard'
|
|
import { useLlmProviderDirectory } from '@/composables/useLlmProviderDirectory'
|
|
import { openSharedDialog } from '@/composables/useSharedDialog'
|
|
|
|
const LlmProviderAuthDialog = defineAsyncComponent(() => import('@/components/dialog/LlmProviderAuthDialog.vue'))
|
|
|
|
const { t } = useI18n()
|
|
const $toast = useToast()
|
|
const { wizardData, validationErrors } = useSetupWizard()
|
|
|
|
const providerRef = computed({
|
|
get: () => wizardData.value.agent.provider,
|
|
set: value => {
|
|
wizardData.value.agent.provider = value || ''
|
|
},
|
|
})
|
|
|
|
const apiKeyRef = computed({
|
|
get: () => wizardData.value.agent.apiKey,
|
|
set: value => {
|
|
wizardData.value.agent.apiKey = value || ''
|
|
},
|
|
})
|
|
|
|
const baseUrlRef = computed({
|
|
get: () => wizardData.value.agent.baseUrl,
|
|
set: value => {
|
|
wizardData.value.agent.baseUrl = value || ''
|
|
},
|
|
})
|
|
|
|
const baseUrlPresetRef = computed({
|
|
get: () => wizardData.value.agent.baseUrlPreset,
|
|
set: value => {
|
|
wizardData.value.agent.baseUrlPreset = value || ''
|
|
},
|
|
})
|
|
|
|
const userAgentRef = computed({
|
|
get: () => wizardData.value.agent.userAgent,
|
|
set: value => {
|
|
wizardData.value.agent.userAgent = value || ''
|
|
},
|
|
})
|
|
|
|
const modelRef = computed({
|
|
get: () => wizardData.value.agent.model,
|
|
set: value => {
|
|
wizardData.value.agent.model = value || ''
|
|
},
|
|
})
|
|
|
|
const maxContextTokensRef = computed({
|
|
get: () => wizardData.value.agent.maxContextTokens,
|
|
set: value => {
|
|
wizardData.value.agent.maxContextTokens = value || 0
|
|
},
|
|
})
|
|
|
|
const authConnectedRef = computed({
|
|
get: () => wizardData.value.agent.authConnected,
|
|
set: value => {
|
|
wizardData.value.agent.authConnected = Boolean(value)
|
|
},
|
|
})
|
|
|
|
const {
|
|
providerItems,
|
|
baseUrlPresetItems,
|
|
models: llmModels,
|
|
selectedProvider,
|
|
selectedModel,
|
|
loadingProviders,
|
|
loadingModels,
|
|
providerConnected,
|
|
showBaseUrlField,
|
|
showApiKeyField,
|
|
canRefreshModels,
|
|
setBaseUrlPreset,
|
|
authDialogVisible,
|
|
authPolling,
|
|
authPopupBlocked,
|
|
authSession,
|
|
handleProviderSelection,
|
|
applyModelMetadata,
|
|
loadProviders,
|
|
loadModels,
|
|
openAuthPage,
|
|
startAuth,
|
|
pollAuthSession,
|
|
disconnectAuth,
|
|
closeAuthDialog,
|
|
} = useLlmProviderDirectory({
|
|
provider: providerRef,
|
|
apiKey: apiKeyRef,
|
|
baseUrl: baseUrlRef,
|
|
baseUrlPreset: baseUrlPresetRef,
|
|
userAgent: userAgentRef,
|
|
model: modelRef,
|
|
maxContextTokens: maxContextTokensRef,
|
|
authConnected: authConnectedRef,
|
|
})
|
|
|
|
let authDialogController: ReturnType<typeof openSharedDialog> | null = null
|
|
|
|
// 生成 LLM 授权共享弹窗所需的最新状态。
|
|
function getProviderAuthDialogProps() {
|
|
return {
|
|
authSession: authSession.value,
|
|
polling: authPolling.value,
|
|
popupBlocked: authPopupBlocked.value,
|
|
}
|
|
}
|
|
|
|
// 打开或刷新 LLM 授权共享弹窗。
|
|
function openProviderAuthDialog() {
|
|
const dialogProps = getProviderAuthDialogProps()
|
|
if (authDialogController) {
|
|
authDialogController.updateProps(dialogProps)
|
|
return
|
|
}
|
|
|
|
authDialogController = openSharedDialog(
|
|
LlmProviderAuthDialog,
|
|
dialogProps,
|
|
{
|
|
close: () => {
|
|
closeAuthDialog()
|
|
authDialogController = null
|
|
},
|
|
openAuthPage,
|
|
poll: () => {
|
|
void pollAuthSession()
|
|
},
|
|
'update:modelValue': (value: boolean) => {
|
|
if (!value) {
|
|
closeAuthDialog()
|
|
authDialogController = null
|
|
}
|
|
},
|
|
},
|
|
{ closeOn: ['close', 'update:modelValue'] },
|
|
)
|
|
}
|
|
|
|
// 关闭 LLM 授权共享弹窗控制器。
|
|
function closeProviderAuthDialog() {
|
|
authDialogController?.close()
|
|
authDialogController = null
|
|
}
|
|
|
|
const jobIntervalItems = computed(() => [
|
|
{ title: t('setting.system.aiAgentJobIntervalDisabled'), value: 0 },
|
|
{ title: t('setting.system.aiAgentJobInterval1h'), value: 1 },
|
|
{ title: t('setting.system.aiAgentJobInterval3h'), value: 3 },
|
|
{ title: t('setting.system.aiAgentJobInterval6h'), value: 6 },
|
|
{ title: t('setting.system.aiAgentJobInterval12h'), value: 12 },
|
|
{ title: t('setting.system.aiAgentJobInterval24h'), value: 24 },
|
|
{ title: t('setting.system.aiAgentJobInterval1w'), value: 168 },
|
|
{ title: t('setting.system.aiAgentJobInterval1M'), value: 720 },
|
|
])
|
|
|
|
const thinkingLevelItems = computed(() => [
|
|
{ title: t('setting.system.llmThinkingLevelOff'), value: 'off' },
|
|
{ title: t('setting.system.llmThinkingLevelAuto'), value: 'auto' },
|
|
{ title: t('setting.system.llmThinkingLevelMinimal'), value: 'minimal' },
|
|
{ title: t('setting.system.llmThinkingLevelLow'), value: 'low' },
|
|
{ title: t('setting.system.llmThinkingLevelMedium'), value: 'medium' },
|
|
{ title: t('setting.system.llmThinkingLevelHigh'), value: 'high' },
|
|
{ title: t('setting.system.llmThinkingLevelMax'), value: 'max' },
|
|
{ title: t('setting.system.llmThinkingLevelXhigh'), value: 'xhigh' },
|
|
])
|
|
|
|
const audioProviderItems = computed(() => [
|
|
{ title: t('setting.system.audioProviderOpenAiAudio'), value: 'openai' },
|
|
{ title: t('setting.system.audioProviderChatAudio'), value: 'openai_chat_audio' },
|
|
{ title: t('setting.system.audioProviderMimo'), value: 'mimo' },
|
|
{ title: t('setting.system.audioProviderMinimax'), value: 'minimax' },
|
|
])
|
|
|
|
const providerAuthMethods = computed(() => selectedProvider.value?.oauth_methods || [])
|
|
const providerAuthLabel = computed(() => selectedProvider.value?.auth_status?.label || '')
|
|
const selectedModelInfo = computed(() => {
|
|
if (!selectedModel.value?.context_tokens_k) return ''
|
|
return t('setting.system.llmModelResolvedHint', {
|
|
context: selectedModel.value.context_tokens_k,
|
|
source: selectedModel.value.source || 'models.dev',
|
|
})
|
|
})
|
|
|
|
async function refreshModels(forceRefresh = true) {
|
|
try {
|
|
await loadModels(forceRefresh)
|
|
} catch (error) {
|
|
$toast.error(error instanceof Error ? error.message : String(error))
|
|
console.log('Load LLM models failed:', error)
|
|
}
|
|
}
|
|
|
|
async function handleProviderChanged() {
|
|
handleProviderSelection(true)
|
|
if (canRefreshModels.value) {
|
|
await refreshModels(false)
|
|
}
|
|
}
|
|
|
|
function handleModelChanged() {
|
|
applyModelMetadata()
|
|
}
|
|
|
|
async function startProviderAuth(methodId: string) {
|
|
try {
|
|
await startAuth(methodId)
|
|
} catch (error) {
|
|
$toast.error(error instanceof Error ? error.message : String(error))
|
|
}
|
|
}
|
|
|
|
async function disconnectProviderAuth() {
|
|
try {
|
|
await disconnectAuth()
|
|
$toast.success(t('setting.system.llmProviderDisconnected'))
|
|
} catch (error) {
|
|
$toast.error(error instanceof Error ? error.message : String(error))
|
|
}
|
|
}
|
|
|
|
watch(authDialogVisible, visible => {
|
|
if (visible) {
|
|
openProviderAuthDialog()
|
|
return
|
|
}
|
|
|
|
closeProviderAuthDialog()
|
|
})
|
|
|
|
watch([authSession, authPolling, authPopupBlocked], () => {
|
|
authDialogController?.updateProps(getProviderAuthDialogProps())
|
|
})
|
|
|
|
onMounted(async () => {
|
|
try {
|
|
await loadProviders()
|
|
if (wizardData.value.agent.enabled && canRefreshModels.value) {
|
|
await refreshModels(false)
|
|
}
|
|
} catch (error) {
|
|
console.log('Load LLM providers failed:', error)
|
|
}
|
|
})
|
|
</script>
|
|
|
|
<template>
|
|
<VCard variant="outlined">
|
|
<VCardText>
|
|
<div class="text-center mb-6">
|
|
<h3 class="text-h4 mb-2">{{ t('setupWizard.agent.title') }}</h3>
|
|
<p class="text-body-1 text-medium-emphasis">{{ t('setupWizard.agent.description') }}</p>
|
|
</div>
|
|
|
|
<VRow>
|
|
<VCol cols="12">
|
|
<VAlert type="info" variant="tonal" class="mb-4">
|
|
<VAlertTitle>{{ t('setupWizard.agent.info') }}</VAlertTitle>
|
|
{{ t('setupWizard.agent.infoDesc') }}
|
|
</VAlert>
|
|
</VCol>
|
|
|
|
<VCol cols="12">
|
|
<VSwitch
|
|
v-model="wizardData.agent.enabled"
|
|
:label="t('setting.system.aiAgentEnable')"
|
|
:hint="t('setting.system.aiAgentEnableHint')"
|
|
persistent-hint
|
|
color="primary"
|
|
/>
|
|
</VCol>
|
|
|
|
<template v-if="wizardData.agent.enabled">
|
|
<VCol cols="12" md="3">
|
|
<VSwitch
|
|
v-model="wizardData.agent.global"
|
|
:label="t('setting.system.aiAgentGlobal')"
|
|
:hint="t('setting.system.aiAgentGlobalHint')"
|
|
persistent-hint
|
|
color="primary"
|
|
/>
|
|
</VCol>
|
|
|
|
<VCol cols="12" md="3">
|
|
<VSwitch
|
|
v-model="wizardData.agent.verbose"
|
|
:label="t('setting.system.aiAgentVerbose')"
|
|
:hint="t('setting.system.aiAgentVerboseHint')"
|
|
persistent-hint
|
|
color="primary"
|
|
/>
|
|
</VCol>
|
|
|
|
<VCol cols="12" md="6">
|
|
<VAutocomplete
|
|
v-model="wizardData.agent.provider"
|
|
:label="t('setting.system.llmProvider')"
|
|
:hint="t('setting.system.llmProviderHint')"
|
|
:items="providerItems"
|
|
:loading="loadingProviders"
|
|
:error="validationErrors.agent.provider"
|
|
:error-messages="validationErrors.agent.provider ? [t('setupWizard.agent.providerRequired')] : []"
|
|
persistent-hint
|
|
prepend-inner-icon="mdi-robot-outline"
|
|
@update:model-value="handleProviderChanged"
|
|
/>
|
|
</VCol>
|
|
|
|
<VCol v-if="showBaseUrlField" cols="12" md="6">
|
|
<VCombobox
|
|
:model-value="wizardData.agent.baseUrl"
|
|
@update:model-value="(value: any) => {
|
|
if (typeof value === 'object' && value !== null) {
|
|
setBaseUrlPreset(value.id, value.value);
|
|
} else {
|
|
setBaseUrlPreset('', value || '');
|
|
}
|
|
}"
|
|
:label="t('setting.system.llmBaseUrl')"
|
|
:hint="t('setting.system.llmBaseUrlHint')"
|
|
:placeholder="selectedProvider?.default_base_url || 'https://api.deepseek.com'"
|
|
:items="baseUrlPresetItems"
|
|
item-title="title"
|
|
item-value="value"
|
|
persistent-hint
|
|
prepend-inner-icon="mdi-link-variant"
|
|
>
|
|
<template #item="{ props, item }">
|
|
<VListItem v-bind="props" :subtitle="item.raw.subtitle" />
|
|
</template>
|
|
</VCombobox>
|
|
</VCol>
|
|
|
|
<VCol v-if="showApiKeyField" cols="12" md="6">
|
|
<VTextField
|
|
v-model="wizardData.agent.apiKey"
|
|
:label="selectedProvider?.api_key_label || t('setting.system.llmApiKey')"
|
|
:hint="selectedProvider?.api_key_hint || t('setting.system.llmApiKeyHint')"
|
|
:placeholder="t('setting.system.llmApiKeyPlaceholder')"
|
|
:error="validationErrors.agent.apiKey"
|
|
:error-messages="
|
|
validationErrors.agent.apiKey ? [t('setupWizard.agent.authOrApiKeyRequired')] : []
|
|
"
|
|
persistent-hint
|
|
prepend-inner-icon="mdi-key-variant"
|
|
type="password"
|
|
/>
|
|
</VCol>
|
|
|
|
<VCol v-if="providerAuthMethods.length > 0" cols="12">
|
|
<VAlert type="info" variant="tonal">
|
|
<div class="d-flex flex-column ga-3">
|
|
<div>
|
|
<div class="text-subtitle-2">{{ t('setting.system.llmProviderAuth') }}</div>
|
|
<div class="text-body-2">
|
|
{{ selectedProvider?.description || t('setting.system.llmProviderAuthHint') }}
|
|
</div>
|
|
<div v-if="providerConnected" class="text-body-2 mt-2">
|
|
{{ t('setting.system.llmProviderConnectedAs', { label: providerAuthLabel || selectedProvider?.name }) }}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="d-flex flex-wrap ga-2">
|
|
<VBtn
|
|
v-for="method in providerAuthMethods"
|
|
:key="method.id"
|
|
color="primary"
|
|
variant="tonal"
|
|
prepend-icon="mdi-account-arrow-right-outline"
|
|
@click="startProviderAuth(method.id)"
|
|
>
|
|
{{ method.label }}
|
|
</VBtn>
|
|
|
|
<VBtn
|
|
v-if="providerConnected"
|
|
color="error"
|
|
variant="text"
|
|
prepend-icon="mdi-link-off"
|
|
@click="disconnectProviderAuth"
|
|
>
|
|
{{ t('setting.system.llmProviderDisconnect') }}
|
|
</VBtn>
|
|
</div>
|
|
</div>
|
|
</VAlert>
|
|
</VCol>
|
|
|
|
<VCol cols="12" md="6">
|
|
<VCombobox
|
|
:model-value="wizardData.agent.model"
|
|
@update:model-value="(val: any) => {
|
|
wizardData.agent.model = typeof val === 'object' && val !== null ? val.id : val;
|
|
handleModelChanged();
|
|
}"
|
|
:label="t('setting.system.llmModel')"
|
|
:hint="t('setting.system.llmModelHint')"
|
|
:items="llmModels"
|
|
item-title="name"
|
|
item-value="id"
|
|
:loading="loadingModels"
|
|
:error="validationErrors.agent.model"
|
|
:error-messages="validationErrors.agent.model ? [t('setupWizard.agent.modelRequired')] : []"
|
|
persistent-hint
|
|
prepend-inner-icon="mdi-brain"
|
|
>
|
|
<template #append-inner>
|
|
<VBtn
|
|
variant="text"
|
|
icon="mdi-refresh"
|
|
size="small"
|
|
:disabled="!canRefreshModels"
|
|
@click="refreshModels(true)"
|
|
/>
|
|
</template>
|
|
</VCombobox>
|
|
|
|
<VAlert v-if="selectedModelInfo" type="info" variant="tonal" density="compact" class="mt-2">
|
|
{{ selectedModelInfo }}
|
|
</VAlert>
|
|
</VCol>
|
|
|
|
<VCol cols="12" md="6">
|
|
<VTextField
|
|
v-model.number="wizardData.agent.maxContextTokens"
|
|
:label="t('setting.system.llmMaxContextTokens')"
|
|
:hint="t('setting.system.llmMaxContextTokensHint')"
|
|
:error="validationErrors.agent.maxContextTokens"
|
|
:error-messages="
|
|
validationErrors.agent.maxContextTokens ? [t('setupWizard.agent.maxContextTokensRequired')] : []
|
|
"
|
|
persistent-hint
|
|
prepend-inner-icon="mdi-counter"
|
|
type="number"
|
|
min="1"
|
|
/>
|
|
</VCol>
|
|
|
|
<VCol v-if="showBaseUrlField" cols="12" md="6">
|
|
<VTextField
|
|
v-model="wizardData.agent.userAgent"
|
|
:label="t('setting.system.llmUserAgent')"
|
|
:hint="t('setting.system.llmUserAgentHint')"
|
|
persistent-hint
|
|
prepend-inner-icon="mdi-card-account-details-outline"
|
|
/>
|
|
</VCol>
|
|
|
|
<VCol cols="12" md="6">
|
|
<VSelect
|
|
v-model="wizardData.agent.thinkingLevel"
|
|
:label="t('setting.system.llmThinking')"
|
|
:hint="t('setting.system.llmThinkingHint')"
|
|
:items="thinkingLevelItems"
|
|
persistent-hint
|
|
color="primary"
|
|
/>
|
|
</VCol>
|
|
|
|
<VCol cols="12">
|
|
<VSwitch
|
|
v-model="wizardData.agent.supportImageInput"
|
|
:label="t('setting.system.llmSupportImageInput')"
|
|
:hint="t('setting.system.llmSupportImageInputHint')"
|
|
persistent-hint
|
|
color="primary"
|
|
/>
|
|
</VCol>
|
|
|
|
<VCol cols="12">
|
|
<VSwitch
|
|
v-model="wizardData.agent.supportAudioInput"
|
|
:label="t('setting.system.llmSupportAudioInput')"
|
|
:hint="t('setting.system.llmSupportAudioInputHint')"
|
|
persistent-hint
|
|
color="primary"
|
|
/>
|
|
</VCol>
|
|
|
|
<template v-if="wizardData.agent.supportAudioInput">
|
|
<VCol cols="12" md="6">
|
|
<VSelect
|
|
v-model="wizardData.agent.audioInputProvider"
|
|
:label="t('setting.system.audioInputProvider')"
|
|
:hint="t('setting.system.audioInputProviderHint')"
|
|
:items="audioProviderItems"
|
|
persistent-hint
|
|
prepend-inner-icon="mdi-microphone-message"
|
|
/>
|
|
</VCol>
|
|
|
|
<VCol cols="12" md="6">
|
|
<VTextField
|
|
v-model="wizardData.agent.audioInputModel"
|
|
:label="t('setting.system.audioInputModel')"
|
|
:hint="t('setting.system.audioInputModelHint')"
|
|
persistent-hint
|
|
prepend-inner-icon="mdi-waveform"
|
|
/>
|
|
</VCol>
|
|
|
|
<VCol cols="12" md="6">
|
|
<VTextField
|
|
v-model="wizardData.agent.audioInputApiKey"
|
|
:label="t('setting.system.audioInputApiKey')"
|
|
:hint="t('setting.system.audioInputApiKeyHint')"
|
|
persistent-hint
|
|
prepend-inner-icon="mdi-key-variant"
|
|
type="password"
|
|
/>
|
|
</VCol>
|
|
|
|
<VCol cols="12" md="6">
|
|
<VTextField
|
|
v-model="wizardData.agent.audioInputBaseUrl"
|
|
:label="t('setting.system.audioInputBaseUrl')"
|
|
:hint="t('setting.system.audioInputBaseUrlHint')"
|
|
persistent-hint
|
|
prepend-inner-icon="mdi-link-variant"
|
|
/>
|
|
</VCol>
|
|
|
|
<VCol cols="12" md="6">
|
|
<VTextField
|
|
v-model="wizardData.agent.audioInputLanguage"
|
|
:label="t('setting.system.audioInputLanguage')"
|
|
:hint="t('setting.system.audioInputLanguageHint')"
|
|
persistent-hint
|
|
prepend-inner-icon="mdi-translate"
|
|
/>
|
|
</VCol>
|
|
</template>
|
|
|
|
<VCol cols="12">
|
|
<VSwitch
|
|
v-model="wizardData.agent.supportAudioOutput"
|
|
:label="t('setting.system.llmSupportAudioOutput')"
|
|
:hint="t('setting.system.llmSupportAudioOutputHint')"
|
|
persistent-hint
|
|
color="primary"
|
|
/>
|
|
</VCol>
|
|
|
|
<template v-if="wizardData.agent.supportAudioOutput">
|
|
<VCol cols="12" md="6">
|
|
<VSelect
|
|
v-model="wizardData.agent.audioOutputProvider"
|
|
:label="t('setting.system.audioOutputProvider')"
|
|
:hint="t('setting.system.audioOutputProviderHint')"
|
|
:items="audioProviderItems"
|
|
persistent-hint
|
|
prepend-inner-icon="mdi-account-voice"
|
|
/>
|
|
</VCol>
|
|
|
|
<VCol cols="12" md="6">
|
|
<VTextField
|
|
v-model="wizardData.agent.audioOutputModel"
|
|
:label="t('setting.system.audioOutputModel')"
|
|
:hint="t('setting.system.audioOutputModelHint')"
|
|
persistent-hint
|
|
prepend-inner-icon="mdi-waveform"
|
|
/>
|
|
</VCol>
|
|
|
|
<VCol cols="12" md="6">
|
|
<VTextField
|
|
v-model="wizardData.agent.audioOutputApiKey"
|
|
:label="t('setting.system.audioOutputApiKey')"
|
|
:hint="t('setting.system.audioOutputApiKeyHint')"
|
|
persistent-hint
|
|
prepend-inner-icon="mdi-key-variant"
|
|
type="password"
|
|
/>
|
|
</VCol>
|
|
|
|
<VCol cols="12" md="6">
|
|
<VTextField
|
|
v-model="wizardData.agent.audioOutputBaseUrl"
|
|
:label="t('setting.system.audioOutputBaseUrl')"
|
|
:hint="t('setting.system.audioOutputBaseUrlHint')"
|
|
persistent-hint
|
|
prepend-inner-icon="mdi-link-variant"
|
|
/>
|
|
</VCol>
|
|
|
|
<VCol cols="12" md="6">
|
|
<VTextField
|
|
v-model="wizardData.agent.audioOutputVoice"
|
|
:label="t('setting.system.audioOutputVoice')"
|
|
:hint="t('setting.system.audioOutputVoiceHint')"
|
|
persistent-hint
|
|
prepend-inner-icon="mdi-account-voice"
|
|
/>
|
|
</VCol>
|
|
|
|
<VCol cols="12">
|
|
<VSwitch
|
|
v-model="wizardData.agent.audioOutputIncludeText"
|
|
:label="t('setting.system.audioOutputIncludeText')"
|
|
:hint="t('setting.system.audioOutputIncludeTextHint')"
|
|
persistent-hint
|
|
color="primary"
|
|
/>
|
|
</VCol>
|
|
</template>
|
|
|
|
<VCol cols="12">
|
|
<VSelect
|
|
v-model="wizardData.agent.jobInterval"
|
|
:label="t('setting.system.aiAgentJobInterval')"
|
|
:hint="t('setting.system.aiAgentJobIntervalHint')"
|
|
:items="jobIntervalItems"
|
|
persistent-hint
|
|
prepend-inner-icon="mdi-timer-outline"
|
|
/>
|
|
</VCol>
|
|
|
|
<VCol cols="12">
|
|
<VSwitch
|
|
v-model="wizardData.agent.retryTransfer"
|
|
:label="t('setting.system.aiAgentRetryTransfer')"
|
|
:hint="t('setting.system.aiAgentRetryTransferHint')"
|
|
persistent-hint
|
|
color="primary"
|
|
/>
|
|
</VCol>
|
|
|
|
<VCol cols="12">
|
|
<VSwitch
|
|
v-model="wizardData.agent.recommendEnabled"
|
|
:label="t('setting.system.aiRecommendEnabled')"
|
|
:hint="t('setting.system.aiRecommendEnabledHint')"
|
|
persistent-hint
|
|
color="primary"
|
|
/>
|
|
</VCol>
|
|
|
|
<VCol v-if="wizardData.agent.recommendEnabled" cols="12" md="6">
|
|
<VTextarea
|
|
v-model="wizardData.agent.recommendUserPreference"
|
|
:label="t('setting.system.aiRecommendUserPreference')"
|
|
:hint="t('setting.system.aiRecommendUserPreferenceHint')"
|
|
persistent-hint
|
|
prepend-inner-icon="mdi-account-heart-outline"
|
|
rows="2"
|
|
auto-grow
|
|
/>
|
|
</VCol>
|
|
|
|
<VCol v-if="wizardData.agent.recommendEnabled" cols="12" md="6">
|
|
<VTextField
|
|
v-model.number="wizardData.agent.recommendMaxItems"
|
|
:label="t('setting.system.aiRecommendMaxItems')"
|
|
:hint="t('setting.system.aiRecommendMaxItemsHint')"
|
|
:error="validationErrors.agent.recommendMaxItems"
|
|
:error-messages="
|
|
validationErrors.agent.recommendMaxItems ? [t('setupWizard.agent.recommendMaxItemsRequired')] : []
|
|
"
|
|
persistent-hint
|
|
prepend-inner-icon="mdi-format-list-numbered"
|
|
type="number"
|
|
min="1"
|
|
/>
|
|
</VCol>
|
|
</template>
|
|
</VRow>
|
|
</VCardText>
|
|
</VCard>
|
|
</template>
|