| <template> |
| |
| <div style="display: flex; flex-direction: column; align-items: center;"> |
| <div v-if="selectedConfigID || isSystemConfig" class="mt-4 config-panel" |
| style="display: flex; flex-direction: column; align-items: start;"> |
| |
| <div class="config-toolbar d-flex flex-row pr-4" |
| style="margin-bottom: 16px; align-items: center; gap: 12px; width: 100%; justify-content: space-between;"> |
| <div class="config-toolbar-controls d-flex flex-row align-center" style="gap: 12px;"> |
| <v-select class="config-select" style="min-width: 130px;" :model-value="selectedConfigID" :items="configSelectItems" item-title="name" :disabled="initialConfigId !== null" |
| v-if="!isSystemConfig" item-value="id" :label="tm('configSelection.selectConfig')" hide-details density="compact" rounded="md" |
| variant="outlined" @update:model-value="onConfigSelect"> |
| </v-select> |
| <v-text-field |
| class="config-search-input" |
| :model-value="configSearchKeyword" |
| @update:model-value="onConfigSearchInput" |
| prepend-inner-icon="mdi-magnify" |
| :label="tm('search.placeholder')" |
| clearable |
| hide-details |
| density="compact" |
| rounded="md" |
| variant="outlined" |
| style="min-width: 280px;" |
| /> |
| |
| |
| </div> |
| </div> |
| <v-slide-y-transition> |
| <div v-if="fetched && hasUnsavedChanges" class="unsaved-changes-banner-wrap"> |
| <v-banner |
| icon="$warning" |
| lines="one" |
| class="unsaved-changes-banner my-4" |
| > |
| {{ tm('messages.unsavedChangesNotice') }} |
| </v-banner> |
| </div> |
| </v-slide-y-transition> |
| |
| |
| <v-slide-y-transition mode="out-in"> |
| <div v-if="(selectedConfigID || isSystemConfig) && fetched" :key="configContentKey" class="config-content" style="width: 100%;"> |
| |
| <AstrBotCoreConfigWrapper |
| :metadata="metadata" |
| :config_data="config_data" |
| :search-keyword="configSearchKeyword" |
| /> |
| |
| <v-tooltip :text="tm('actions.save')" location="left"> |
| <template v-slot:activator="{ props }"> |
| <v-btn v-bind="props" icon="mdi-content-save" size="x-large" style="position: fixed; right: 52px; bottom: 52px;" |
| color="darkprimary" @click="updateConfig"> |
| </v-btn> |
| </template> |
| </v-tooltip> |
| |
| <v-tooltip :text="tm('codeEditor.title')" location="left"> |
| <template v-slot:activator="{ props }"> |
| <v-btn v-bind="props" icon="mdi-code-json" size="x-large" style="position: fixed; right: 52px; bottom: 124px;" color="primary" |
| @click="configToString(); codeEditorDialog = true"> |
| </v-btn> |
| </template> |
| </v-tooltip> |
| |
| <v-tooltip text="测试当前配置" location="left" v-if="!isSystemConfig"> |
| <template v-slot:activator="{ props }"> |
| <v-btn v-bind="props" icon="mdi-chat-processing" size="x-large" |
| style="position: fixed; right: 52px; bottom: 196px;" color="secondary" |
| @click="openTestChat"> |
| </v-btn> |
| </template> |
| </v-tooltip> |
| |
| </div> |
| </v-slide-y-transition> |
| |
| </div> |
| </div> |
| |
| |
| |
| <v-dialog v-model="codeEditorDialog" fullscreen transition="dialog-bottom-transition" scrollable> |
| <v-card> |
| <v-toolbar color="primary" dark> |
| <v-btn icon @click="codeEditorDialog = false"> |
| <v-icon>mdi-close</v-icon> |
| </v-btn> |
| <v-toolbar-title>{{ tm('codeEditor.title') }}</v-toolbar-title> |
| <v-spacer></v-spacer> |
| <v-toolbar-items style="display: flex; align-items: center;"> |
| <v-btn style="margin-left: 16px;" size="small" @click="configToString()">{{ |
| tm('editor.revertCode') }}</v-btn> |
| <v-btn v-if="config_data_has_changed" style="margin-left: 16px;" size="small" @click="applyStrConfig()">{{ |
| tm('editor.applyConfig') }}</v-btn> |
| <small style="margin-left: 16px;">💡 {{ tm('editor.applyTip') }}</small> |
| </v-toolbar-items> |
| </v-toolbar> |
| <v-card-text class="pa-0"> |
| <VueMonacoEditor language="json" theme="vs-dark" style="height: calc(100vh - 64px);" |
| v-model:value="config_data_str"> |
| </VueMonacoEditor> |
| </v-card-text> |
| </v-card> |
| </v-dialog> |
| |
| |
| <v-dialog v-model="configManageDialog" max-width="800px"> |
| <v-card> |
| <v-card-title class="d-flex align-center justify-space-between"> |
| <span class="text-h4">{{ tm('configManagement.title') }}</span> |
| <v-btn icon="mdi-close" variant="text" @click="configManageDialog = false"></v-btn> |
| </v-card-title> |
| |
| <v-card-text> |
| <small>{{ tm('configManagement.description') }}</small> |
| <div class="mt-6 mb-4"> |
| <v-btn prepend-icon="mdi-plus" @click="startCreateConfig" variant="tonal" color="primary"> |
| {{ tm('configManagement.newConfig') }} |
| </v-btn> |
| </div> |
| |
| |
| <v-list lines="two"> |
| <v-list-item v-for="config in configInfoList" :key="config.id" :title="config.name"> |
| <template v-slot:append v-if="config.id !== 'default'"> |
| <div class="d-flex align-center" style="gap: 8px;"> |
| <v-btn icon="mdi-pencil" size="small" variant="text" color="warning" |
| @click="startEditConfig(config)"></v-btn> |
| <v-btn icon="mdi-delete" size="small" variant="text" color="error" |
| @click="confirmDeleteConfig(config)"></v-btn> |
| </div> |
| </template> |
| </v-list-item> |
| </v-list> |
| |
| |
| <v-divider v-if="showConfigForm" class="my-6"></v-divider> |
| |
| <div v-if="showConfigForm"> |
| <h3 class="mb-4">{{ isEditingConfig ? tm('configManagement.editConfig') : tm('configManagement.newConfig') }}</h3> |
| |
| <h4>{{ tm('configManagement.configName') }}</h4> |
| |
| <v-text-field v-model="configFormData.name" :label="tm('configManagement.fillConfigName')" variant="outlined" class="mt-4 mb-4" |
| hide-details></v-text-field> |
| |
| <div class="d-flex justify-end mt-4" style="gap: 8px;"> |
| <v-btn variant="text" @click="cancelConfigForm">{{ tm('buttons.cancel') }}</v-btn> |
| <v-btn color="primary" @click="saveConfigForm" |
| :disabled="!configFormData.name"> |
| {{ isEditingConfig ? tm('buttons.update') : tm('buttons.create') }} |
| </v-btn> |
| </div> |
| </div> |
| </v-card-text> |
| </v-card> |
| </v-dialog> |
| |
| <v-snackbar :timeout="3000" elevation="24" :color="save_message_success" v-model="save_message_snack"> |
| {{ save_message }} |
| </v-snackbar> |
| |
| <WaitingForRestart ref="wfr"></WaitingForRestart> |
| |
| |
| <v-overlay |
| v-model="testChatDrawer" |
| class="test-chat-overlay" |
| location="right" |
| transition="slide-x-reverse-transition" |
| :scrim="true" |
| @click:outside="closeTestChat" |
| > |
| <v-card class="test-chat-card" elevation="12"> |
| <div class="test-chat-header"> |
| <div> |
| <span class="text-h6">测试配置</span> |
| <div v-if="selectedConfigInfo.name" class="text-caption text-grey"> |
| {{ selectedConfigInfo.name }} ({{ testConfigId }}) |
| </div> |
| </div> |
| <v-btn icon variant="text" @click="closeTestChat"> |
| <v-icon>mdi-close</v-icon> |
| </v-btn> |
| </div> |
| <v-divider></v-divider> |
| <div class="test-chat-content"> |
| <StandaloneChat v-if="testChatDrawer" :configId="testConfigId" /> |
| </div> |
| </v-card> |
| </v-overlay> |
| |
| |
| <UnsavedChangesConfirmDialog ref="unsavedChangesDialog" /> |
| |
| </template> |
| |
| |
| <script> |
| import axios from 'axios'; |
| import AstrBotCoreConfigWrapper from '@/components/config/AstrBotCoreConfigWrapper.vue'; |
| import WaitingForRestart from '@/components/shared/WaitingForRestart.vue'; |
| import StandaloneChat from '@/components/chat/StandaloneChat.vue'; |
| import { VueMonacoEditor } from '@guolao/vue-monaco-editor' |
| import { useI18n, useModuleI18n } from '@/i18n/composables'; |
| import { restartAstrBot as restartAstrBotRuntime } from '@/utils/restartAstrBot'; |
| import { |
| askForConfirmation as askForConfirmationDialog, |
| useConfirmDialog |
| } from '@/utils/confirmDialog'; |
| import UnsavedChangesConfirmDialog from '@/components/config/UnsavedChangesConfirmDialog.vue'; |
| import { normalizeTextInput } from '@/utils/inputValue'; |
| |
| export default { |
| name: 'ConfigPage', |
| components: { |
| AstrBotCoreConfigWrapper, |
| VueMonacoEditor, |
| WaitingForRestart, |
| StandaloneChat, |
| UnsavedChangesConfirmDialog |
| }, |
| props: { |
| initialConfigId: { |
| type: String, |
| default: null |
| } |
| }, |
| setup() { |
| const { t } = useI18n(); |
| const { tm } = useModuleI18n('features/config'); |
| const confirmDialog = useConfirmDialog(); |
| |
| return { |
| t, |
| tm, |
| confirmDialog |
| }; |
| }, |
| |
| |
| async beforeRouteLeave(to, from, next) { |
| if (this.hasUnsavedChanges) { |
| const confirmed = await this.$refs.unsavedChangesDialog?.open({ |
| title: this.tm('unsavedChangesWarning.dialogTitle'), |
| message: this.tm('unsavedChangesWarning.leavePage'), |
| confirmHint: `${this.tm('unsavedChangesWarning.options.saveAndSwitch')}:${this.tm('unsavedChangesWarning.options.confirm')}`, |
| cancelHint: `${this.tm('unsavedChangesWarning.options.discardAndSwitch')}:${this.tm('unsavedChangesWarning.options.cancel')}`, |
| closeHint: `${this.tm('unsavedChangesWarning.options.closeCard')}:"x"` |
| }); |
| |
| if (confirmed === 'close') { |
| next(false); |
| } else if (confirmed) { |
| const result = await this.updateConfig(); |
| if (this.isSystemConfig) { |
| next(false); |
| } else { |
| if (result?.success) { |
| await new Promise(resolve => setTimeout(resolve, 800)); |
| next(); |
| } else { |
| next(false); |
| } |
| } |
| } else { |
| this.hasUnsavedChanges = false; |
| next(); |
| } |
| } else { |
| next(); |
| } |
| }, |
| |
| computed: { |
| messages() { |
| return { |
| loadError: this.tm('messages.loadError'), |
| saveSuccess: this.tm('messages.saveSuccess'), |
| saveError: this.tm('messages.saveError'), |
| configApplied: this.tm('messages.configApplied'), |
| configApplyError: this.tm('messages.configApplyError') |
| }; |
| }, |
| |
| configHasChanges() { |
| if (!this.originalConfigData || !this.config_data) return false; |
| return JSON.stringify(this.originalConfigData) !== JSON.stringify(this.config_data); |
| }, |
| configInfoNameList() { |
| return this.configInfoList.map(info => info.name); |
| }, |
| selectedConfigInfo() { |
| return this.configInfoList.find(info => info.id === this.selectedConfigID) || {}; |
| }, |
| configSelectItems() { |
| const items = [...this.configInfoList]; |
| items.push({ |
| id: '_%manage%_', |
| name: this.tm('configManagement.manageConfigs'), |
| umop: [] |
| }); |
| return items; |
| }, |
| hasUnsavedChanges() { |
| if (!this.fetched) { |
| return false; |
| } |
| return this.getConfigSnapshot(this.config_data) !== this.lastSavedConfigSnapshot; |
| } |
| }, |
| watch: { |
| config_data_str(val) { |
| this.config_data_has_changed = true; |
| }, |
| config_data: { |
| deep: true, |
| handler() { |
| if (this.fetched) { |
| this.hasUnsavedChanges = this.configHasChanges; |
| } |
| } |
| }, |
| async '$route.fullPath'(newVal) { |
| await this.syncConfigTypeFromHash(newVal); |
| }, |
| initialConfigId(newVal) { |
| if (!newVal) { |
| return; |
| } |
| if (this.selectedConfigID !== newVal) { |
| this.getConfigInfoList(newVal); |
| } |
| } |
| }, |
| data() { |
| return { |
| codeEditorDialog: false, |
| configManageDialog: false, |
| showConfigForm: false, |
| isEditingConfig: false, |
| config_data_has_changed: false, |
| config_data_str: "", |
| config_data: { |
| config: {} |
| }, |
| fetched: false, |
| metadata: {}, |
| save_message_snack: false, |
| save_message: "", |
| save_message_success: "", |
| configContentKey: 0, |
| lastSavedConfigSnapshot: '', |
| |
| |
| configType: 'normal', |
| configSearchKeyword: '', |
| |
| |
| isSystemConfig: false, |
| |
| |
| selectedConfigID: null, |
| currentConfigId: null, |
| configInfoList: [], |
| configFormData: { |
| name: '', |
| }, |
| editingConfigId: null, |
| |
| |
| testChatDrawer: false, |
| testConfigId: null, |
| |
| |
| hasUnsavedChanges: false, |
| |
| originalConfigData: null, |
| } |
| }, |
| mounted() { |
| const hashConfigType = this.extractConfigTypeFromHash( |
| this.$route?.fullPath || '' |
| ); |
| this.configType = hashConfigType || 'normal'; |
| this.isSystemConfig = this.configType === 'system'; |
| |
| const targetConfigId = this.initialConfigId || 'default'; |
| this.getConfigInfoList(targetConfigId); |
| |
| this.configType = this.isSystemConfig ? 'system' : 'normal'; |
| |
| |
| window.addEventListener('astrbot-locale-changed', this.handleLocaleChange); |
| |
| |
| this.$watch('config_data', (newVal) => { |
| if (!this.originalConfigData && newVal) { |
| this.originalConfigData = JSON.parse(JSON.stringify(newVal)); |
| } |
| }, { immediate: false, deep: true }); |
| }, |
| |
| beforeUnmount() { |
| |
| window.removeEventListener('astrbot-locale-changed', this.handleLocaleChange); |
| }, |
| methods: { |
| |
| handleLocaleChange() { |
| |
| if (this.selectedConfigID) { |
| this.getConfig(this.selectedConfigID); |
| } else if (this.isSystemConfig) { |
| this.getConfig(); |
| } |
| }, |
| |
| }, |
| methods: { |
| onConfigSearchInput(value) { |
| this.configSearchKeyword = normalizeTextInput(value); |
| }, |
| extractConfigTypeFromHash(hash) { |
| const rawHash = String(hash || ''); |
| const lastHashIndex = rawHash.lastIndexOf('#'); |
| if (lastHashIndex === -1) { |
| return null; |
| } |
| const cleanHash = rawHash.slice(lastHashIndex + 1); |
| return cleanHash === 'system' || cleanHash === 'normal' ? cleanHash : null; |
| }, |
| async syncConfigTypeFromHash(hash) { |
| const configType = this.extractConfigTypeFromHash(hash); |
| if (!configType || configType === this.configType) { |
| return false; |
| } |
| |
| this.configType = configType; |
| await this.onConfigTypeToggle(); |
| return true; |
| }, |
| getConfigInfoList(abconf_id) { |
| |
| axios.get('/api/config/abconfs').then((res) => { |
| this.configInfoList = res.data.data.info_list; |
| |
| if (abconf_id) { |
| let matched = false; |
| for (let i = 0; i < this.configInfoList.length; i++) { |
| if (this.configInfoList[i].id === abconf_id) { |
| this.selectedConfigID = this.configInfoList[i].id; |
| this.currentConfigId = this.configInfoList[i].id; |
| this.getConfig(abconf_id); |
| matched = true; |
| break; |
| } |
| } |
| |
| if (!matched && this.configInfoList.length) { |
| |
| this.selectedConfigID = this.configInfoList[0].id; |
| this.currentConfigId = this.configInfoList[0].id; |
| this.getConfig(this.selectedConfigID); |
| } |
| } |
| }).catch((err) => { |
| this.save_message = this.messages.loadError; |
| this.save_message_snack = true; |
| this.save_message_success = "error"; |
| }); |
| }, |
| getConfig(abconf_id) { |
| this.fetched = false |
| const params = {}; |
| |
| if (this.isSystemConfig) { |
| params.system_config = '1'; |
| } else { |
| params.id = abconf_id || this.selectedConfigID; |
| } |
| |
| axios.get('/api/config/abconf', { |
| params: params |
| }).then((res) => { |
| this.config_data = res.data.data.config; |
| this.lastSavedConfigSnapshot = this.getConfigSnapshot(this.config_data); |
| this.fetched = true |
| this.metadata = res.data.data.metadata; |
| this.configContentKey += 1; |
| |
| this.$nextTick(() => { |
| this.originalConfigData = JSON.parse(JSON.stringify(this.config_data)); |
| this.hasUnsavedChanges = false; |
| if (!this.isSystemConfig) { |
| this.currentConfigId = abconf_id || this.selectedConfigID; |
| } |
| }); |
| }).catch((err) => { |
| this.save_message = this.messages.loadError; |
| this.save_message_snack = true; |
| this.save_message_success = "error"; |
| }); |
| }, |
| updateConfig() { |
| if (!this.fetched) return; |
| |
| const postData = { |
| config: JSON.parse(JSON.stringify(this.config_data)), |
| }; |
| |
| if (this.isSystemConfig) { |
| postData.conf_id = 'default'; |
| } else { |
| postData.conf_id = this.selectedConfigID; |
| } |
| |
| return axios.post('/api/config/astrbot/update', postData).then((res) => { |
| if (res.data.status === "ok") { |
| this.lastSavedConfigSnapshot = this.getConfigSnapshot(this.config_data); |
| this.save_message = res.data.message || this.messages.saveSuccess; |
| this.save_message_snack = true; |
| this.save_message_success = "success"; |
| this.onConfigSaved(); |
| |
| if (this.isSystemConfig) { |
| restartAstrBotRuntime(this.$refs.wfr).catch(() => {}) |
| } |
| return { success: true }; |
| } else { |
| this.save_message = res.data.message || this.messages.saveError; |
| this.save_message_snack = true; |
| this.save_message_success = "error"; |
| return { success: false }; |
| } |
| }).catch((err) => { |
| this.save_message = this.messages.saveError; |
| this.save_message_snack = true; |
| this.save_message_success = "error"; |
| return { success: false }; |
| }); |
| }, |
| |
| onConfigSaved() { |
| this.hasUnsavedChanges = false; |
| this.originalConfigData = JSON.parse(JSON.stringify(this.config_data)); |
| }, |
| |
| configToString() { |
| this.config_data_str = JSON.stringify(this.config_data, null, 2); |
| this.config_data_has_changed = false; |
| }, |
| applyStrConfig() { |
| try { |
| this.config_data = JSON.parse(this.config_data_str); |
| this.config_data_has_changed = false; |
| this.save_message_success = "success"; |
| this.save_message = this.messages.configApplied; |
| this.save_message_snack = true; |
| } catch (e) { |
| this.save_message_success = "error"; |
| this.save_message = this.messages.configApplyError; |
| this.save_message_snack = true; |
| } |
| }, |
| createNewConfig() { |
| axios.post('/api/config/abconf/new', { |
| name: this.configFormData.name |
| }).then((res) => { |
| if (res.data.status === "ok") { |
| this.save_message = res.data.message; |
| this.save_message_snack = true; |
| this.save_message_success = "success"; |
| this.getConfigInfoList(res.data.data.conf_id); |
| this.cancelConfigForm(); |
| } else { |
| this.save_message = res.data.message; |
| this.save_message_snack = true; |
| this.save_message_success = "error"; |
| } |
| }).catch((err) => { |
| console.error(err); |
| this.save_message = this.tm('configManagement.createFailed'); |
| this.save_message_snack = true; |
| this.save_message_success = "error"; |
| }); |
| }, |
| async onConfigSelect(value) { |
| if (value === '_%manage%_') { |
| this.configManageDialog = true; |
| |
| this.$nextTick(() => { |
| this.selectedConfigID = this.selectedConfigInfo.id || 'default'; |
| this.getConfig(this.selectedConfigID); |
| }); |
| } else { |
| |
| if (this.hasUnsavedChanges) { |
| |
| const prevConfigId = this.isSystemConfig ? 'default' : (this.currentConfigId || this.selectedConfigID || 'default'); |
| const message = this.tm('unsavedChangesWarning.switchConfig'); |
| const saveAndSwitch = await this.$refs.unsavedChangesDialog?.open({ |
| title: this.tm('unsavedChangesWarning.dialogTitle'), |
| message: message, |
| confirmHint: `${this.tm('unsavedChangesWarning.options.saveAndSwitch')}:${this.tm('unsavedChangesWarning.options.confirm')}`, |
| cancelHint: `${this.tm('unsavedChangesWarning.options.discardAndSwitch')}:${this.tm('unsavedChangesWarning.options.cancel')}`, |
| closeHint: `${this.tm('unsavedChangesWarning.options.closeCard')}:"x"` |
| }); |
| |
| if (saveAndSwitch === 'close') { |
| return; |
| } |
| if (saveAndSwitch) { |
| |
| const currentSelectedId = this.selectedConfigID; |
| |
| this.selectedConfigID = prevConfigId; |
| const result = await this.updateConfig(); |
| this.selectedConfigID = currentSelectedId; |
| if (result?.success) { |
| this.selectedConfigID = value; |
| this.getConfig(value); |
| } |
| return; |
| } else { |
| |
| this.selectedConfigID = value; |
| this.getConfig(value); |
| } |
| } else { |
| |
| this.selectedConfigID = value; |
| this.getConfig(value); |
| } |
| } |
| }, |
| startCreateConfig() { |
| this.showConfigForm = true; |
| this.isEditingConfig = false; |
| this.configFormData = { |
| name: '', |
| }; |
| this.editingConfigId = null; |
| }, |
| startEditConfig(config) { |
| this.showConfigForm = true; |
| this.isEditingConfig = true; |
| this.editingConfigId = config.id; |
| |
| this.configFormData = { |
| name: config.name || '', |
| }; |
| }, |
| cancelConfigForm() { |
| this.showConfigForm = false; |
| this.isEditingConfig = false; |
| this.editingConfigId = null; |
| this.configFormData = { |
| name: '', |
| }; |
| }, |
| saveConfigForm() { |
| if (!this.configFormData.name) { |
| this.save_message = this.tm('configManagement.pleaseEnterName'); |
| this.save_message_snack = true; |
| this.save_message_success = "error"; |
| return; |
| } |
| |
| if (this.isEditingConfig) { |
| this.updateConfigInfo(); |
| } else { |
| this.createNewConfig(); |
| } |
| }, |
| async confirmDeleteConfig(config) { |
| const message = this.tm('configManagement.confirmDelete').replace('{name}', config.name); |
| if (await askForConfirmationDialog(message, this.confirmDialog)) { |
| this.deleteConfig(config.id); |
| } |
| }, |
| deleteConfig(configId) { |
| axios.post('/api/config/abconf/delete', { |
| id: configId |
| }).then((res) => { |
| if (res.data.status === "ok") { |
| this.save_message = res.data.message; |
| this.save_message_snack = true; |
| this.save_message_success = "success"; |
| this.cancelConfigForm(); |
| |
| this.getConfigInfoList("default"); |
| } else { |
| this.save_message = res.data.message; |
| this.save_message_snack = true; |
| this.save_message_success = "error"; |
| } |
| }).catch((err) => { |
| console.error(err); |
| this.save_message = this.tm('configManagement.deleteFailed'); |
| this.save_message_snack = true; |
| this.save_message_success = "error"; |
| }); |
| }, |
| updateConfigInfo() { |
| axios.post('/api/config/abconf/update', { |
| id: this.editingConfigId, |
| name: this.configFormData.name |
| }).then((res) => { |
| if (res.data.status === "ok") { |
| this.save_message = res.data.message; |
| this.save_message_snack = true; |
| this.save_message_success = "success"; |
| this.getConfigInfoList(this.editingConfigId); |
| this.cancelConfigForm(); |
| } else { |
| this.save_message = res.data.message; |
| this.save_message_snack = true; |
| this.save_message_success = "error"; |
| } |
| }).catch((err) => { |
| console.error(err); |
| this.save_message = this.tm('configManagement.updateFailed'); |
| this.save_message_snack = true; |
| this.save_message_success = "error"; |
| }); |
| }, |
| async onConfigTypeToggle() { |
| |
| if (this.hasUnsavedChanges) { |
| const message = this.tm('unsavedChangesWarning.leavePage'); |
| const saveAndSwitch = await this.$refs.unsavedChangesDialog?.open({ |
| title: this.tm('unsavedChangesWarning.dialogTitle'), |
| message: message, |
| confirmHint: `${this.tm('unsavedChangesWarning.options.saveAndSwitch')}:${this.tm('unsavedChangesWarning.options.confirm')}`, |
| cancelHint: `${this.tm('unsavedChangesWarning.options.discardAndSwitch')}:${this.tm('unsavedChangesWarning.options.cancel')}`, |
| closeHint: `${this.tm('unsavedChangesWarning.options.closeCard')}:"x"` |
| }); |
| |
| if (saveAndSwitch === 'close') { |
| |
| const originalHash = this.isSystemConfig ? '#system' : '#normal'; |
| this.$router.replace('/config' + originalHash); |
| this.configType = this.isSystemConfig ? 'system' : 'normal'; |
| return; |
| } |
| if (saveAndSwitch) { |
| await this.updateConfig(); |
| |
| if (this.isSystemConfig) { |
| this.$router.replace('/config#system'); |
| return; |
| } |
| } |
| } |
| this.isSystemConfig = this.configType === 'system'; |
| this.fetched = false; |
| |
| if (this.isSystemConfig) { |
| |
| this.getConfig(); |
| } else { |
| |
| if (this.selectedConfigID) { |
| this.getConfig(this.selectedConfigID); |
| } else { |
| this.getConfigInfoList("default"); |
| } |
| } |
| }, |
| onSystemConfigToggle() { |
| |
| this.configType = this.isSystemConfig ? 'system' : 'normal'; |
| |
| this.onConfigTypeToggle(); |
| }, |
| openTestChat() { |
| if (!this.selectedConfigID) { |
| this.save_message = "请先选择一个配置文件"; |
| this.save_message_snack = true; |
| this.save_message_success = "warning"; |
| return; |
| } |
| this.testConfigId = this.selectedConfigID; |
| this.testChatDrawer = true; |
| }, |
| closeTestChat() { |
| this.testChatDrawer = false; |
| this.testConfigId = null; |
| }, |
| getConfigSnapshot(config) { |
| return JSON.stringify(config ?? {}); |
| } |
| }, |
| } |
| |
| </script> |
| |
| <style> |
| .v-tab { |
| text-transform: none !important; |
| } |
| |
| .unsaved-changes-banner { |
| border-radius: 8px; |
| } |
| |
| .v-theme--light .unsaved-changes-banner { |
| background-color: #f1f4f9 !important; |
| } |
| |
| .v-theme--dark .unsaved-changes-banner { |
| background-color: #2d2d2d !important; |
| } |
| |
| .unsaved-changes-banner-wrap { |
| position: sticky; |
| top: calc(var(--v-layout-top, 64px)); |
| z-index: 20; |
| width: 100%; |
| margin-bottom: 6px; |
| } |
| |
| |
| .v-btn-toggle .v-btn { |
| transition: all 0.3s ease !important; |
| } |
| |
| .v-btn-toggle .v-btn:not(.v-btn--active) { |
| opacity: 0.7; |
| } |
| |
| .v-btn-toggle .v-btn.v-btn--active { |
| opacity: 1; |
| font-weight: 600; |
| } |
| |
| |
| .text-warning code { |
| background-color: rgba(255, 193, 7, 0.1); |
| color: #e65100; |
| padding: 2px 4px; |
| border-radius: 4px; |
| font-size: 0.8rem; |
| font-weight: 500; |
| } |
| |
| .text-warning strong { |
| color: #f57c00; |
| } |
| |
| .text-warning small { |
| color: #6c757d; |
| font-style: italic; |
| } |
| |
| @media (min-width: 768px) { |
| .config-panel { |
| width: 750px; |
| } |
| } |
| |
| @media (max-width: 767px) { |
| .v-container { |
| padding: 4px; |
| } |
| |
| .config-panel { |
| width: 100%; |
| } |
| |
| .config-toolbar { |
| padding-right: 0 !important; |
| } |
| |
| .config-toolbar-controls { |
| width: 100%; |
| flex-wrap: wrap; |
| } |
| |
| .config-select, |
| .config-search-input { |
| width: 100%; |
| min-width: 0 !important; |
| } |
| } |
| |
| |
| .test-chat-overlay { |
| align-items: stretch; |
| justify-content: flex-end; |
| } |
| |
| .test-chat-card { |
| width: clamp(320px, 50vw, 720px); |
| height: calc(100vh - 32px); |
| display: flex; |
| flex-direction: column; |
| margin: 16px; |
| } |
| |
| .test-chat-header { |
| display: flex; |
| align-items: center; |
| justify-content: space-between; |
| padding: 16px 20px 12px 20px; |
| } |
| |
| .test-chat-content { |
| flex: 1; |
| overflow: hidden; |
| padding: 0; |
| border-radius: 0 0 16px 16px; |
| } |
| </style> |
| |