Refactor: 重构配置文件管理,以支持更灵活的、会话粒度的(基于 umo part)配置文件隔离 (#2328)
* refactor: 重构配置文件管理,以支持更灵活的、基于 umo part 的配置文件隔离 * Refactor: 重构配置前端页面,新增数个配置项 (#2331) * refactor: 重构配置前端页面,新增数个配置项 * feat: 完善多配置文件结构 * perf: 系统配置入口 * fix: normal config item list not display * fix: 修复 axios 请求中的上下文引用问题
This commit is contained in:
+549
-142
@@ -1,124 +1,175 @@
|
||||
|
||||
|
||||
<template>
|
||||
<v-card style="margin-bottom: 16px;">
|
||||
<v-card-text style="padding: 0;">
|
||||
<div>
|
||||
<v-btn-group variant="outlined" divided>
|
||||
<v-btn icon="mdi-text-box-edit-outline" style="width: 80px;" :color="editorTab === 0 ? 'primary' : ''"
|
||||
@click="editorTab = 0">
|
||||
|
||||
<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="d-flex flex-row pr-4"
|
||||
style="margin-bottom: 16px; align-items: center; gap: 12px; justify-content: space-between; width: 100%;">
|
||||
<div class="d-flex flex-row align-center" style="gap: 12px;" >
|
||||
<v-select style="min-width: 130px;" v-model="selectedConfigID" :items="configSelectItems" item-title="name" v-if="!isSystemConfig"
|
||||
item-value="id" label="选择配置文件" hide-details density="compact" rounded="md" variant="outlined"
|
||||
@update:model-value="onConfigSelect">
|
||||
<template v-slot:item="{ props: itemProps, item }">
|
||||
<v-list-item v-bind="itemProps"
|
||||
:subtitle="item.raw.id === '_%manage%_' ? '管理所有配置文件' : formatUmop(item.raw.umop)"
|
||||
:class="item.raw.id === '_%manage%_' ? 'text-primary' : ''">
|
||||
</v-list-item>
|
||||
</template>
|
||||
</v-select>
|
||||
</div>
|
||||
|
||||
<v-btn-toggle v-model="configType" mandatory color="primary" variant="outlined" density="comfortable"
|
||||
rounded="md" @update:model-value="onConfigTypeToggle">
|
||||
<v-btn value="normal" prepend-icon="mdi-cog" size="large" >
|
||||
普通
|
||||
</v-btn>
|
||||
<v-btn icon="mdi-code-json" style="width: 80px;" :color="editorTab === 1 ? 'primary' : ''"
|
||||
@click="configToString(); editorTab = 1;"></v-btn>
|
||||
</v-btn-group>
|
||||
<v-btn v-if="editorTab === 1" style="margin-left: 16px;" size="small" @click="configToString()">{{ tm('editor.revertCode') }}</v-btn>
|
||||
<v-btn v-if="editorTab === 1 && config_data_has_changed" style="margin-left: 16px;" size="small"
|
||||
@click="applyStrConfig()">{{ tm('editor.applyConfig') }}</v-btn>
|
||||
<small v-if="editorTab === 1" style="margin-left: 16px;">💡 {{ tm('editor.applyTip') }}</small>
|
||||
<v-btn value="system" prepend-icon="mdi-cog-outline" size="large">
|
||||
系统
|
||||
</v-btn>
|
||||
</v-btn-toggle>
|
||||
</div>
|
||||
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
<v-progress-linear v-if="!fetched" indeterminate color="primary"></v-progress-linear>
|
||||
|
||||
<!-- 可视化编辑 -->
|
||||
<v-card v-if="editorTab === 0">
|
||||
<v-tabs v-model="tab" align-tabs="left" color="deep-purple-accent-4">
|
||||
<v-tab v-for="(val, key, index) in metadata" :key="index" :value="index"
|
||||
style="font-weight: 1000; font-size: 15px">
|
||||
{{ metadata[key]['name'] }}
|
||||
</v-tab>
|
||||
</v-tabs>
|
||||
<v-tabs-window v-model="tab">
|
||||
<v-tabs-window-item v-for="(val, key, index) in metadata" v-show="index == tab" :key="index">
|
||||
<v-container fluid>
|
||||
<div v-if="(selectedConfigID || isSystemConfig) && fetched" style="width: 100%;">
|
||||
<!-- 可视化编辑 -->
|
||||
<div :class="$vuetify.display.mobile ? '' : 'd-flex'">
|
||||
<v-tabs v-model="tab" :direction="$vuetify.display.mobile ? 'horizontal' : 'vertical'"
|
||||
:align-tabs="$vuetify.display.mobile ? 'left' : 'start'" color="deep-purple-accent-4" class="config-tabs">
|
||||
<v-tab v-for="(val, key, index) in metadata" :key="index" :value="index"
|
||||
style="font-weight: 1000; font-size: 15px">
|
||||
{{ metadata[key]['name'] }}
|
||||
</v-tab>
|
||||
</v-tabs>
|
||||
<v-tabs-window v-model="tab" class="config-tabs-window">
|
||||
<v-tabs-window-item v-for="(val, key, index) in metadata" v-show="index == tab" :key="index">
|
||||
<v-container fluid>
|
||||
<div v-for="(val2, key2, index2) in metadata[key]['metadata']" :key="key2">
|
||||
<!-- Support both traditional and JSON selector metadata -->
|
||||
<AstrBotConfigV4 :metadata="{ [key2]: metadata[key]['metadata'][key2] }" :iterable="config_data"
|
||||
:metadataKey="key2">
|
||||
</AstrBotConfigV4>
|
||||
</div>
|
||||
</v-container>
|
||||
</v-tabs-window-item>
|
||||
|
||||
<div v-for="(val2, key2, index2) in metadata[key]['metadata']">
|
||||
<!-- <h3>{{ metadata[key]['metadata'][key2]['description'] }}</h3> -->
|
||||
<div v-if="metadata[key]['metadata'][key2]?.config_template"
|
||||
v-show="key2 !== 'platform' && key2 !== 'provider'" style="border: 1px solid var(--v-theme-border); padding: 8px; margin-bottom: 16px; border-radius: 10px">
|
||||
<!-- 带有 config_template 的配置项 -->
|
||||
<v-list-item-title style="font-weight: bold;">
|
||||
{{ metadata[key]['metadata'][key2]['description'] }} ({{ key2 }})
|
||||
</v-list-item-title>
|
||||
<v-tabs style="margin-top: 16px;" align-tabs="left" color="deep-purple-accent-4"
|
||||
|
||||
v-model="config_template_tab">
|
||||
<v-tab v-if="metadata[key]['metadata'][key2]?.tmpl_display_title"
|
||||
v-for="(item, index) in config_data[key2]" :key="index" :value="index">
|
||||
{{ item[metadata[key]['metadata'][key2]?.tmpl_display_title] }}
|
||||
</v-tab>
|
||||
<v-tab v-else v-for="(item, index) in config_data[key2]" :key="index + '_'" :value="index">
|
||||
{{ item.id }}({{ item.type }})
|
||||
</v-tab>
|
||||
<v-menu>
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-btn variant="plain" size="large" v-bind="props">
|
||||
<v-icon>mdi-plus</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
<v-list @update:selected="addFromDefaultConfigTmpl($event, key, key2)">
|
||||
<v-list-item v-for="(item, index) in metadata[key]['metadata'][key2]?.config_template" :key="index"
|
||||
:value="index">
|
||||
<v-list-item-title>{{ index }}</v-list-item-title>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
</v-tabs>
|
||||
<v-tabs-window v-model="config_template_tab">
|
||||
<v-tabs-window-item v-for="(config_item, index) in config_data[key2]"
|
||||
v-show="config_template_tab === index" :key="index" :value="index">
|
||||
<div style="padding: 16px;">
|
||||
<v-btn variant="tonal" rounded="xl" color="error" @click="deleteItem(key2, index)">
|
||||
{{ tm('actions.delete') }}
|
||||
</v-btn>
|
||||
|
||||
<AstrBotConfig :metadata="metadata[key]['metadata']" :iterable="config_item" :metadataKey="key2">
|
||||
</AstrBotConfig>
|
||||
</div>
|
||||
</v-tabs-window-item>
|
||||
</v-tabs-window>
|
||||
<div style="margin-left: 16px; padding-bottom: 16px">
|
||||
<small>{{ tm('help.helpPrefix') }}
|
||||
<a href="https://astrbot.app/" target="_blank">{{ tm('help.documentation') }}</a>
|
||||
{{ tm('help.helpMiddle') }}
|
||||
<a href="https://qm.qq.com/cgi-bin/qm/qr?k=EYGsuUTfe00_iOu9JTXS7_TEpMkXOvwv&jump_from=webapi&authKey=uUEMKCROfsseS+8IzqPjzV3y1tzy4AkykwTib2jNkOFdzezF9s9XknqnIaf3CDft"
|
||||
target="_blank">{{ tm('help.support') }}</a>{{ tm('help.helpSuffix') }}
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<div v-else>
|
||||
<!-- 如果配置项是一个 object,那么 iterable 需要取到这个 object 的值,否则取到整个 config_data -->
|
||||
<div v-if="metadata[key]['metadata'][key2]['type'] == 'object'" style="border: 1px solid var(--v-theme-border); padding: 8px; margin-bottom: 16px; border-radius: 10px">
|
||||
<AstrBotConfig
|
||||
:metadata="metadata[key]['metadata']" :iterable="config_data[key2]" :metadataKey="key2">
|
||||
</AstrBotConfig>
|
||||
</v-tabs-window>
|
||||
</div>
|
||||
|
||||
<v-btn icon="mdi-content-save" size="x-large" style="position: fixed; right: 52px; bottom: 52px;"
|
||||
color="darkprimary" @click="updateConfig">
|
||||
</v-btn>
|
||||
|
||||
<v-btn icon="mdi-code-json" size="x-large" style="position: fixed; right: 52px; bottom: 124px;" color="primary"
|
||||
@click="configToString(); codeEditorDialog = true">
|
||||
</v-btn>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Full Screen Editor Dialog -->
|
||||
<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>编辑配置文件</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>
|
||||
|
||||
<!-- Config Management 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">配置文件管理</span>
|
||||
<v-btn icon="mdi-close" variant="text" @click="configManageDialog = false"></v-btn>
|
||||
</v-card-title>
|
||||
|
||||
<v-card-text>
|
||||
<small>AstrBot 支持针对不同消息平台实例分别设置配置文件。默认会使用 `default` 配置。</small>
|
||||
<div class="mt-6 mb-4">
|
||||
<v-btn prepend-icon="mdi-plus" @click="startCreateConfig" variant="tonal" color="primary">
|
||||
新建配置文件
|
||||
</v-btn>
|
||||
</div>
|
||||
|
||||
<!-- Config List -->
|
||||
<v-list lines="two">
|
||||
<v-list-item v-for="config in configInfoList" :key="config.id" :title="config.name">
|
||||
<v-list-item-subtitle>当前应用于: {{ formatUmop(config.umop) }} </v-list-item-subtitle>
|
||||
|
||||
<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>
|
||||
<AstrBotConfig v-else :metadata="metadata[key]['metadata']" :iterable="config_data" :metadataKey="key2">
|
||||
</AstrBotConfig>
|
||||
</div>
|
||||
</template>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
|
||||
<!-- Create/Edit Form -->
|
||||
<v-divider v-if="showConfigForm" class="my-6"></v-divider>
|
||||
|
||||
<div v-if="showConfigForm">
|
||||
<h3 class="mb-4">{{ isEditingConfig ? '编辑配置文件' : '新建配置文件' }}</h3>
|
||||
|
||||
<div class="mb-4">
|
||||
<small v-if="conflictMessage">⚠ {{ conflictMessage }}</small>
|
||||
</div>
|
||||
|
||||
<v-text-field v-model="configFormData.name" label="配置文件名称" variant="outlined" class="mb-4"
|
||||
hide-details></v-text-field>
|
||||
|
||||
<v-select v-model="configFormData.umop" :items="platformList" item-title="id" item-value="id" label="应用于平台"
|
||||
variant="outlined" hide-details multiple @update:model-value="checkPlatformConflictOnForm">
|
||||
<template v-slot:item="{ props: itemProps, item }">
|
||||
<v-list-item v-bind="itemProps" :subtitle="item.raw.type"></v-list-item>
|
||||
</template>
|
||||
</v-select>
|
||||
|
||||
</v-container>
|
||||
</v-tabs-window-item>
|
||||
|
||||
|
||||
<div style="margin-left: 16px; padding-bottom: 16px">
|
||||
<small>{{ tm('help.helpPrefix') }}
|
||||
<a href="https://astrbot.app/" target="_blank">{{ tm('help.documentation') }}</a>
|
||||
{{ tm('help.helpMiddle') }}
|
||||
<a href="https://qm.qq.com/cgi-bin/qm/qr?k=EYGsuUTfe00_iOu9JTXS7_TEpMkXOvwv&jump_from=webapi&authKey=uUEMKCROfsseS+8IzqPjzV3y1tzy4AkykwTib2jNkOFdzezF9s9XknqnIaf3CDft" target="_blank">{{ tm('help.support') }}</a>{{ tm('help.helpSuffix') }}
|
||||
</small>
|
||||
</div>
|
||||
|
||||
</v-tabs-window>
|
||||
</v-card>
|
||||
|
||||
<!-- 代码编辑 -->
|
||||
<v-card v-else style="background-color: #1e1e1e;">
|
||||
<VueMonacoEditor theme="vs-dark" language="json" height="80vh" style="padding-top: 16px; padding-bottom: 16px;"
|
||||
v-model:value="config_data_str">
|
||||
</VueMonacoEditor>
|
||||
</v-card>
|
||||
|
||||
<v-btn icon="mdi-content-save" size="x-large" style="position: fixed; right: 52px; bottom: 52px;" color="darkprimary"
|
||||
@click="updateConfig">
|
||||
</v-btn>
|
||||
<div class="d-flex justify-end mt-4" style="gap: 8px;">
|
||||
<v-btn variant="text" @click="cancelConfigForm">取消</v-btn>
|
||||
<v-btn color="primary" @click="saveConfigForm"
|
||||
:disabled="!configFormData.name || !configFormData.umop.length || !!conflictMessage">
|
||||
{{ isEditingConfig ? '更新' : '创建' }}
|
||||
</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 }}
|
||||
@@ -130,23 +181,22 @@
|
||||
|
||||
<script>
|
||||
import axios from 'axios';
|
||||
import AstrBotConfig from '@/components/shared/AstrBotConfig.vue';
|
||||
import AstrBotConfigV4 from '@/components/shared/AstrBotConfigV4.vue';
|
||||
import WaitingForRestart from '@/components/shared/WaitingForRestart.vue';
|
||||
import { VueMonacoEditor } from '@guolao/vue-monaco-editor'
|
||||
import config from '@/config';
|
||||
import { useI18n, useModuleI18n } from '@/i18n/composables';
|
||||
|
||||
export default {
|
||||
name: 'ConfigPage',
|
||||
components: {
|
||||
AstrBotConfig,
|
||||
AstrBotConfigV4,
|
||||
VueMonacoEditor,
|
||||
WaitingForRestart
|
||||
},
|
||||
setup() {
|
||||
const { t } = useI18n();
|
||||
const { tm } = useModuleI18n('features/config');
|
||||
|
||||
|
||||
return {
|
||||
t,
|
||||
tm
|
||||
@@ -154,7 +204,6 @@ export default {
|
||||
},
|
||||
|
||||
computed: {
|
||||
// 安全访问翻译的计算属性
|
||||
messages() {
|
||||
return {
|
||||
loadError: this.tm('messages.loadError'),
|
||||
@@ -163,7 +212,22 @@ export default {
|
||||
configApplied: this.tm('messages.configApplied'),
|
||||
configApplyError: this.tm('messages.configApplyError')
|
||||
};
|
||||
}
|
||||
},
|
||||
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: '管理配置文件...',
|
||||
umop: []
|
||||
});
|
||||
return items;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
config_data_str: function (val) {
|
||||
@@ -172,6 +236,10 @@ export default {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
codeEditorDialog: false,
|
||||
configManageDialog: false,
|
||||
showConfigForm: false,
|
||||
isEditingConfig: false,
|
||||
config_data_has_changed: false,
|
||||
config_data_str: "",
|
||||
config_data: {
|
||||
@@ -179,30 +247,79 @@ export default {
|
||||
},
|
||||
fetched: false,
|
||||
metadata: {},
|
||||
provider_config_tmpl: {},
|
||||
adapter_config_tmpl: {}, // 平台适配器
|
||||
save_message_snack: false,
|
||||
save_message: "",
|
||||
save_message_success: "",
|
||||
namespace: "",
|
||||
tab: 0,
|
||||
editorTab: 0, // 0: visual, 1: code
|
||||
|
||||
config_template_tab: 0,
|
||||
tab: 0, // 用于切换配置标签页
|
||||
|
||||
// 配置类型切换
|
||||
configType: 'normal', // 'normal' 或 'system'
|
||||
|
||||
// 系统配置开关
|
||||
isSystemConfig: false,
|
||||
|
||||
// 多配置文件管理
|
||||
selectedConfigID: null, // 用于存储当前选中的配置项信息
|
||||
configInfoList: [],
|
||||
platformList: [],
|
||||
configFormData: {
|
||||
name: '',
|
||||
umop: [],
|
||||
},
|
||||
editingConfigId: null,
|
||||
conflictMessage: '', // 冲突提示信息
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.getConfig();
|
||||
this.getConfigInfoList("default");
|
||||
// 初始化配置类型状态
|
||||
this.configType = this.isSystemConfig ? 'system' : 'normal';
|
||||
},
|
||||
methods: {
|
||||
getConfig() {
|
||||
// 获取配置
|
||||
axios.get('/api/config/get').then((res) => {
|
||||
getConfigInfoList(abconf_id) {
|
||||
// 获取配置列表
|
||||
axios.get('/api/config/abconfs').then((res) => {
|
||||
this.configInfoList = res.data.data.info_list;
|
||||
|
||||
if (abconf_id) {
|
||||
for (let i = 0; i < this.configInfoList.length; i++) {
|
||||
if (this.configInfoList[i].id === abconf_id) {
|
||||
this.selectedConfigID = this.configInfoList[i].id
|
||||
this.getConfig(abconf_id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}).catch((err) => {
|
||||
this.save_message = this.messages.loadError;
|
||||
this.save_message_snack = true;
|
||||
this.save_message_success = "error";
|
||||
});
|
||||
},
|
||||
getPlatformList() {
|
||||
axios.get('/api/config/platform/list').then((res) => {
|
||||
this.platformList = res.data.data.platforms;
|
||||
}).catch((err) => {
|
||||
console.error(this.t('status.dataError'), err);
|
||||
});
|
||||
},
|
||||
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.fetched = true
|
||||
this.metadata = res.data.data.metadata;
|
||||
this.provider_config_tmpl = res.data.data.provider_config_tmpl;
|
||||
this.adapter_config_tmpl = res.data.data.adapter_config_tmpl;
|
||||
}).catch((err) => {
|
||||
this.save_message = this.messages.loadError;
|
||||
this.save_message_snack = true;
|
||||
@@ -211,12 +328,28 @@ export default {
|
||||
},
|
||||
updateConfig() {
|
||||
if (!this.fetched) return;
|
||||
axios.post('/api/config/astrbot/update', this.config_data).then((res) => {
|
||||
|
||||
const postData = {
|
||||
config: this.config_data
|
||||
};
|
||||
|
||||
if (this.isSystemConfig) {
|
||||
postData.conf_id = 'default';
|
||||
} else {
|
||||
postData.conf_id = this.selectedConfigID;
|
||||
}
|
||||
|
||||
axios.post('/api/config/astrbot/update', postData).then((res) => {
|
||||
if (res.data.status === "ok") {
|
||||
this.save_message = res.data.message || this.messages.saveSuccess;
|
||||
this.save_message_snack = true;
|
||||
this.save_message_success = "success";
|
||||
this.$refs.wfr.check();
|
||||
|
||||
if (this.isSystemConfig) {
|
||||
axios.post('/api/stat/restart-core').then(() => {
|
||||
this.$refs.wfr.check();
|
||||
})
|
||||
}
|
||||
} else {
|
||||
this.save_message = res.data.message || this.messages.saveError;
|
||||
this.save_message_snack = true;
|
||||
@@ -245,27 +378,248 @@ export default {
|
||||
this.save_message_snack = true;
|
||||
}
|
||||
},
|
||||
addFromDefaultConfigTmpl(val, group_name, config_item_name) {
|
||||
console.log(val);
|
||||
createNewConfig() {
|
||||
// 修正为 umo part 形式
|
||||
// 暂时只支持 platform:: 形式
|
||||
const umo_parts = this.configFormData.umop.map(platform => platform + "::");
|
||||
|
||||
let tmpl = this.metadata[group_name]['metadata'][config_item_name]['config_template'][val];
|
||||
let new_tmpl_cfg = JSON.parse(JSON.stringify(tmpl));
|
||||
// new_tmpl_cfg.id = "new_" + val + "_" + this.config_data[config_item_name].length;
|
||||
this.config_data[config_item_name].push(new_tmpl_cfg);
|
||||
this.config_template_tab = this.config_data[config_item_name].length - 1;
|
||||
axios.post('/api/config/abconf/new', {
|
||||
umo_parts: umo_parts,
|
||||
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.save_message_snack = true;
|
||||
this.save_message_success = "error";
|
||||
});
|
||||
},
|
||||
deleteItem(config_item_name, index) {
|
||||
console.log(config_item_name, index);
|
||||
let new_list = [];
|
||||
for (let i = 0; i < this.config_data[config_item_name].length; i++) {
|
||||
if (i !== index) {
|
||||
new_list.push(this.config_data[config_item_name][i]);
|
||||
checkPlatformConflict(newPlatforms) {
|
||||
const conflictConfigs = [];
|
||||
|
||||
// 遍历现有的配置文件,排除名为 "default" 的配置
|
||||
for (const config of this.configInfoList) {
|
||||
if (config.name === 'default') {
|
||||
continue; // 跳过 default 配置
|
||||
}
|
||||
|
||||
if (config.umop && config.umop.length > 0) {
|
||||
// 获取现有配置的平台列表
|
||||
const existingPlatforms = config.umop.map(umop => {
|
||||
const platformPart = umop.split(":")[0];
|
||||
return platformPart === "" ? "*" : platformPart; // 空字符串表示所有平台
|
||||
});
|
||||
|
||||
// 检查是否有重复的平台
|
||||
const hasConflict = newPlatforms.some(newPlatform => {
|
||||
return existingPlatforms.includes(newPlatform) || existingPlatforms.includes("*");
|
||||
}) || (newPlatforms.includes("*") && existingPlatforms.length > 0);
|
||||
|
||||
if (hasConflict) {
|
||||
conflictConfigs.push(config);
|
||||
}
|
||||
}
|
||||
}
|
||||
this.config_data[config_item_name] = new_list;
|
||||
|
||||
if (this.config_template_tab > 0) {
|
||||
this.config_template_tab -= 1;
|
||||
return conflictConfigs;
|
||||
},
|
||||
onConfigSelect(value) {
|
||||
if (value === '_%manage%_') {
|
||||
this.configManageDialog = true;
|
||||
this.getPlatformList();
|
||||
// 重置选择到之前的值
|
||||
this.$nextTick(() => {
|
||||
this.selectedConfigID = this.selectedConfigInfo.id || 'default';
|
||||
});
|
||||
} else {
|
||||
this.getConfig(value);
|
||||
}
|
||||
},
|
||||
startCreateConfig() {
|
||||
this.showConfigForm = true;
|
||||
this.isEditingConfig = false;
|
||||
this.configFormData = {
|
||||
name: '',
|
||||
umop: [],
|
||||
};
|
||||
this.editingConfigId = null;
|
||||
this.conflictMessage = '';
|
||||
},
|
||||
startEditConfig(config) {
|
||||
this.showConfigForm = true;
|
||||
this.isEditingConfig = true;
|
||||
this.editingConfigId = config.id;
|
||||
this.configFormData = {
|
||||
name: config.name || '',
|
||||
umop: config.umop ? config.umop.map(part => part.split("::")[0]).filter(p => p) : [],
|
||||
};
|
||||
this.conflictMessage = '';
|
||||
},
|
||||
cancelConfigForm() {
|
||||
this.showConfigForm = false;
|
||||
this.isEditingConfig = false;
|
||||
this.editingConfigId = null;
|
||||
this.configFormData = {
|
||||
name: '',
|
||||
umop: [],
|
||||
};
|
||||
this.conflictMessage = '';
|
||||
},
|
||||
saveConfigForm() {
|
||||
if (!this.configFormData.name || !this.configFormData.umop.length) {
|
||||
this.save_message = "请填写配置名称和选择应用平台";
|
||||
this.save_message_snack = true;
|
||||
this.save_message_success = "error";
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.conflictMessage) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.isEditingConfig) {
|
||||
this.updateConfigInfo();
|
||||
} else {
|
||||
this.createNewConfig();
|
||||
}
|
||||
},
|
||||
confirmDeleteConfig(config) {
|
||||
if (confirm(`确定要删除配置文件 "${config.name}" 吗?此操作不可恢复。`)) {
|
||||
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.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.save_message_snack = true;
|
||||
this.save_message_success = "error";
|
||||
});
|
||||
},
|
||||
checkPlatformConflictOnForm() {
|
||||
if (!this.configFormData.umop || this.configFormData.umop.length === 0) {
|
||||
this.conflictMessage = '';
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查与其他配置文件的冲突
|
||||
let conflictConfigs = this.checkPlatformConflict(this.configFormData.umop);
|
||||
|
||||
// 如果是编辑模式,排除当前编辑的配置文件
|
||||
if (this.isEditingConfig && this.editingConfigId) {
|
||||
conflictConfigs = conflictConfigs.filter(config => config.id !== this.editingConfigId);
|
||||
}
|
||||
|
||||
if (conflictConfigs.length > 0) {
|
||||
const conflictNames = conflictConfigs.map(config => config.name).join(', ');
|
||||
this.conflictMessage = `提示:选择的平台与现有配置文件重复:${conflictNames}。AstrBot 将只会应用首个匹配的配置文件。`;
|
||||
} else {
|
||||
this.conflictMessage = '';
|
||||
}
|
||||
},
|
||||
updateConfigInfo() {
|
||||
// 修正为 umo part 形式
|
||||
// 暂时只支持 platform:: 形式
|
||||
const umo_parts = this.configFormData.umop.map(platform => platform + "::");
|
||||
|
||||
axios.post('/api/config/abconf/update', {
|
||||
id: this.editingConfigId,
|
||||
name: this.configFormData.name,
|
||||
umo_parts: umo_parts
|
||||
}).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.save_message_snack = true;
|
||||
this.save_message_success = "error";
|
||||
});
|
||||
},
|
||||
formatUmop(umop) {
|
||||
if (!umop) {
|
||||
return
|
||||
}
|
||||
let ret = ""
|
||||
for (let i = 0; i < umop.length; i++) {
|
||||
let platformPart = umop[i].split(":")[0];
|
||||
if (platformPart === "") {
|
||||
return "所有平台";
|
||||
} else {
|
||||
ret += platformPart + ",";
|
||||
}
|
||||
}
|
||||
ret = ret.slice(0, -1);
|
||||
return ret;
|
||||
},
|
||||
onConfigTypeToggle() {
|
||||
this.isSystemConfig = this.configType === 'system';
|
||||
this.tab = 0; // 重置标签页
|
||||
this.fetched = false; // 重置加载状态
|
||||
|
||||
if (this.isSystemConfig) {
|
||||
// 切换到系统配置
|
||||
this.getConfig();
|
||||
} else {
|
||||
// 切换回普通配置,如果有选中的配置文件则加载,否则加载default
|
||||
if (this.selectedConfigID) {
|
||||
this.getConfig(this.selectedConfigID);
|
||||
} else {
|
||||
this.getConfigInfoList("default");
|
||||
}
|
||||
}
|
||||
},
|
||||
onSystemConfigToggle() {
|
||||
// 保持向后兼容性,更新 configType
|
||||
this.configType = this.isSystemConfig ? 'system' : 'normal';
|
||||
|
||||
this.tab = 0; // 重置标签页
|
||||
this.fetched = false; // 重置加载状态
|
||||
|
||||
if (this.isSystemConfig) {
|
||||
// 切换到系统配置
|
||||
this.getConfig();
|
||||
} else {
|
||||
// 切换回普通配置,如果有选中的配置文件则加载,否则加载default
|
||||
if (this.selectedConfigID) {
|
||||
this.getConfig(this.selectedConfigID);
|
||||
} else {
|
||||
this.getConfigInfoList("default");
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -277,4 +631,57 @@ export default {
|
||||
.v-tab {
|
||||
text-transform: none !important;
|
||||
}
|
||||
|
||||
/* 按钮切换样式优化 */
|
||||
.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;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.config-tabs {
|
||||
display: flex;
|
||||
margin: 16px 16px 0 0;
|
||||
}
|
||||
|
||||
.config-panel {
|
||||
width: 750px;
|
||||
}
|
||||
|
||||
.config-tabs-window {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.config-tabs .v-tab {
|
||||
justify-content: flex-start !important;
|
||||
text-align: left;
|
||||
min-height: 48px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.config-tabs {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.v-container {
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.config-panel {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.config-tabs-window {
|
||||
margin-top: 16px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -151,8 +151,6 @@
|
||||
{{ save_message }}
|
||||
</v-snackbar>
|
||||
|
||||
<WaitingForRestart ref="wfr"></WaitingForRestart>
|
||||
|
||||
<!-- ID冲突确认对话框 -->
|
||||
<v-dialog v-model="showIdConflictDialog" max-width="450" persistent>
|
||||
<v-card>
|
||||
@@ -359,7 +357,6 @@ export default {
|
||||
this.loading = false;
|
||||
this.showPlatformCfg = false;
|
||||
this.getConfig();
|
||||
this.$refs.wfr.check();
|
||||
this.showSuccess(res.data.message || this.messages.updateSuccess);
|
||||
}).catch((err) => {
|
||||
this.loading = false;
|
||||
@@ -413,7 +410,6 @@ export default {
|
||||
if (confirm(`${this.messages.deleteConfirm} ${platform.id}?`)) {
|
||||
axios.post('/api/config/platform/delete', { id: platform.id }).then((res) => {
|
||||
this.getConfig();
|
||||
this.$refs.wfr.check();
|
||||
this.showSuccess(res.data.message || this.messages.deleteSuccess);
|
||||
}).catch((err) => {
|
||||
this.showError(err.response?.data?.message || err.message);
|
||||
@@ -429,7 +425,6 @@ export default {
|
||||
config: platform
|
||||
}).then((res) => {
|
||||
this.getConfig();
|
||||
this.$refs.wfr.check();
|
||||
this.showSuccess(res.data.message || this.messages.statusUpdateSuccess);
|
||||
}).catch((err) => {
|
||||
platform.enable = !platform.enable; // 发生错误时回滚状态
|
||||
|
||||
@@ -12,9 +12,6 @@
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<v-btn color="success" prepend-icon="mdi-cog" variant="tonal" class="me-2" @click="showSettingsDialog = true" rounded="xl" size="x-large">
|
||||
{{ tm('providers.settings') }}
|
||||
</v-btn>
|
||||
<v-btn color="primary" prepend-icon="mdi-plus" variant="tonal" @click="showAddProviderDialog = true" rounded="xl" size="x-large">
|
||||
{{ tm('providers.addProvider') }}
|
||||
</v-btn>
|
||||
@@ -250,49 +247,6 @@
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<!-- 设置对话框 -->
|
||||
<v-dialog v-model="showSettingsDialog" max-width="600px">
|
||||
<v-card>
|
||||
<v-card-title class="bg-primary text-white py-3 px-4" style="display: flex; align-items: center;">
|
||||
<v-icon color="white" class="me-2">mdi-cog</v-icon>
|
||||
<span>{{ tm('dialogs.settings.title') }}</span>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn icon variant="text" color="white" @click="showSettingsDialog = false">
|
||||
<v-icon>mdi-close</v-icon>
|
||||
</v-btn>
|
||||
</v-card-title>
|
||||
|
||||
<v-card-text class="pa-4">
|
||||
<v-list>
|
||||
<v-list-item>
|
||||
<v-switch
|
||||
style="padding: 12px;"
|
||||
v-model="sessionSeparationEnabled"
|
||||
color="primary"
|
||||
:loading="sessionSettingLoading"
|
||||
@change="updateSessionSeparation"
|
||||
hide-details
|
||||
>
|
||||
<template v-slot:label>
|
||||
<div>
|
||||
<div class="text-subtitle-1">{{ tm('dialogs.settings.sessionSeparation.title') }}</div>
|
||||
<div class="text-caption text-medium-emphasis">{{ tm('dialogs.settings.sessionSeparation.description') }}</div>
|
||||
</div>
|
||||
</template>
|
||||
</v-switch>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-card-text>
|
||||
|
||||
<v-card-actions class="pa-4">
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn variant="text" @click="showSettingsDialog = false">
|
||||
{{ tm('dialogs.settings.close') }}
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<!-- 消息提示 -->
|
||||
<v-snackbar :timeout="3000" elevation="24" :color="save_message_success" v-model="save_message_snack"
|
||||
location="top">
|
||||
@@ -365,11 +319,6 @@ export default {
|
||||
metadata: {},
|
||||
showProviderCfg: false,
|
||||
|
||||
// 设置对话框相关
|
||||
showSettingsDialog: false,
|
||||
sessionSeparationEnabled: false,
|
||||
sessionSettingLoading: false,
|
||||
|
||||
// ID冲突确认对话框
|
||||
showIdConflictDialog: false,
|
||||
conflictId: '',
|
||||
@@ -464,10 +413,8 @@ export default {
|
||||
add: this.tm('messages.success.add'),
|
||||
delete: this.tm('messages.success.delete'),
|
||||
statusUpdate: this.tm('messages.success.statusUpdate'),
|
||||
sessionSeparation: this.tm('messages.success.sessionSeparation')
|
||||
},
|
||||
error: {
|
||||
sessionSeparation: this.tm('messages.error.sessionSeparation'),
|
||||
fetchStatus: this.tm('messages.error.fetchStatus')
|
||||
},
|
||||
confirm: {
|
||||
@@ -502,7 +449,6 @@ export default {
|
||||
|
||||
mounted() {
|
||||
this.getConfig();
|
||||
this.getSessionSeparationStatus();
|
||||
},
|
||||
|
||||
methods: {
|
||||
@@ -722,32 +668,6 @@ export default {
|
||||
});
|
||||
},
|
||||
|
||||
// 获取会话隔离配置状态
|
||||
getSessionSeparationStatus() {
|
||||
axios.get('/api/config/provider/get_session_seperate').then((res) => {
|
||||
if (res.data && res.data.status === 'ok') {
|
||||
this.sessionSeparationEnabled = res.data.data.enable;
|
||||
}
|
||||
}).catch((err) => {
|
||||
this.showError(err.response?.data?.message || this.messages.error.sessionSeparation);
|
||||
});
|
||||
},
|
||||
|
||||
// 更新会话隔离配置
|
||||
updateSessionSeparation() {
|
||||
this.sessionSettingLoading = true;
|
||||
axios.post('/api/config/provider/set_session_seperate', {
|
||||
enable: this.sessionSeparationEnabled
|
||||
}).then((res) => {
|
||||
this.showSuccess(res.data.message || this.messages.success.sessionSeparation);
|
||||
this.sessionSettingLoading = false;
|
||||
}).catch((err) => {
|
||||
this.sessionSeparationEnabled = !this.sessionSeparationEnabled; // 发生错误时回滚状态
|
||||
this.showError(err.response?.data?.message || err.message);
|
||||
this.sessionSettingLoading = false;
|
||||
});
|
||||
},
|
||||
|
||||
showSuccess(message) {
|
||||
this.save_message = message;
|
||||
this.save_message_success = "success";
|
||||
|
||||
Reference in New Issue
Block a user