fix(dashboard): keep content frame fixed and remove sidebar customization

This commit is contained in:
Soulter
2026-10-09 22:46:48 +08:00
parent 33ef43be3f
commit 156f97bf97
12 changed files with 48 additions and 594 deletions
+5
View File
@@ -4,3 +4,8 @@
- Raise the model image input cap from 32 MiB to 64 MiB. Larger originals are skipped before reading image bytes, with a model notice retaining their paths and suggesting the file-reading tool or a smaller upload; accepted inputs still produce images strictly below 512 KiB.
- Local Agent input images are always prepared as JPEG/PNG files strictly below 512 KiB. Compliant local images are reused without copying. Transparent previews retain PNG alpha; animations become 3×3 montages. Original attachment paths remain available to tools, and event-owned previews are deleted after use without a shared conversion cache.
- Configuration mapping: **Enable image compression** (`provider_settings.image_compress_enabled`) is replaced by always-on preparation; **JPEG quality** (`provider_settings.image_compress_options.quality`) is replaced by automatic size control; **Maximum edge length** is renamed to **Input image maximum edge length** (`provider_settings.image_compress_options.max_size`). User attachments in CUA sessions follow the same limits.
- WebUI: Keep content corners and borders fixed while pages scroll inside the card. Add 6px right and bottom gaps on desktop; mobile keeps a full-width, flat layout. (#10479)
- WebUI: **System Settings → Appearance → Customize Sidebar → removed**; the sidebar uses the default menu layout. Theme colors and extension collapsing, pinning, and grouping remain available.
- WebUI:页面在内容卡片内部滚动,圆角和边线保持固定。桌面端右侧和底部各保留 6px 间隔,移动端保持全宽平面布局。 (#10479)
- WebUI:**系统设置 → 外观 → 自定义侧边栏 → 已移除**;侧边栏使用默认菜单布局。仍可调整主题颜色,以及折叠、置顶和按插件分组扩展功能。
@@ -1,271 +0,0 @@
<template>
<div style="margin-top: 16px;">
<v-btn
color="primary"
variant="tonal"
size="small"
@click="openDialog"
style="margin-bottom: 8px;"
>
{{ t('features.settings.sidebar.customize.title') }}
</v-btn>
<v-dialog v-model="dialog" max-width="700px">
<v-card>
<v-card-title class="text-h3 pa-4 pb-0 pl-6 d-flex justify-space-between align-center">
<span>{{ t('features.settings.sidebar.customize.title') }}</span>
<v-btn
icon="mdi-close"
variant="text"
@click="dialog = false"
></v-btn>
</v-card-title>
<v-card-text>
<p class="text-body-2 mb-4">{{ t('features.settings.sidebar.customize.subtitle') }}</p>
<v-row>
<v-col cols="12" md="6">
<div class="mb-2 font-weight-medium">{{ t('features.settings.sidebar.customize.mainItems') }}</div>
<v-list
density="compact"
class="custom-list"
@dragover.prevent
@drop="handleDropToList($event, 'main')"
>
<v-list-item
v-for="(item, index) in mainItems"
:key="item.title"
class="mb-1 draggable-item"
draggable="true"
@dragstart="handleDragStart($event, 'main', index)"
@dragover.prevent
@drop.stop="handleDrop($event, 'main', index)"
>
<template v-slot:prepend>
<v-icon v-if="typeof item.icon === 'string'" :icon="item.icon" size="small" class="mr-2" />
<component :is="item.icon" v-else-if="item.icon" :size="18" class="mr-2" />
</template>
<v-list-item-title>{{ t(item.title) }}</v-list-item-title>
<template v-slot:append>
<v-btn
icon="mdi-arrow-right"
variant="text"
size="x-small"
@click="moveToMore(index)"
></v-btn>
</template>
</v-list-item>
</v-list>
</v-col>
<v-col cols="12" md="6">
<div class="mb-2 font-weight-medium">{{ t('features.settings.sidebar.customize.moreItems') }}</div>
<v-list
density="compact"
class="custom-list"
@dragover.prevent
@drop="handleDropToList($event, 'more')"
>
<v-list-item
v-for="(item, index) in moreItems"
:key="item.title"
class="mb-1 draggable-item"
draggable="true"
@dragstart="handleDragStart($event, 'more', index)"
@dragover.prevent
@drop.stop="handleDrop($event, 'more', index)"
>
<template v-slot:prepend>
<v-icon v-if="typeof item.icon === 'string'" :icon="item.icon" size="small" class="mr-2" />
<component :is="item.icon" v-else-if="item.icon" :size="18" class="mr-2" />
</template>
<v-list-item-title>{{ t(item.title) }}</v-list-item-title>
<template v-slot:append>
<v-btn
icon="mdi-arrow-left"
variant="text"
size="x-small"
@click="moveToMain(index)"
></v-btn>
</template>
</v-list-item>
</v-list>
</v-col>
</v-row>
</v-card-text>
<v-card-actions>
<v-btn
color="error"
variant="text"
@click="resetToDefault"
>
{{ t('features.settings.sidebar.customize.reset') }}
</v-btn>
<v-spacer></v-spacer>
<v-btn
color="primary"
variant="tonal"
@click="saveCustomization"
>
{{ t('core.actions.save') }}
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { useI18n } from '@/i18n/composables';
import sidebarItems from '@/layouts/full/vertical-sidebar/sidebarItem';
import {
getSidebarCustomization,
setSidebarCustomization,
clearSidebarCustomization,
resolveSidebarItems
} from '@/utils/sidebarCustomization';
const { t } = useI18n();
const dialog = ref(false);
const mainItems = ref([]);
const moreItems = ref([]);
const draggedItem = ref(null);
function initializeItems() {
const customization = getSidebarCustomization();
const { mainItems: resolvedMain, moreItems: resolvedMore } = resolveSidebarItems(
sidebarItems,
customization
);
mainItems.value = resolvedMain;
moreItems.value = resolvedMore;
}
function openDialog() {
initializeItems();
dialog.value = true;
}
function handleDragStart(event, listType, index) {
draggedItem.value = {
type: listType,
index: index,
item: listType === 'main' ? mainItems.value[index] : moreItems.value[index]
};
event.dataTransfer.effectAllowed = 'move';
}
function handleDrop(event, targetListType, targetIndex) {
event.preventDefault();
if (!draggedItem.value) return;
const sourceListType = draggedItem.value.type;
const sourceIndex = draggedItem.value.index;
const item = draggedItem.value.item;
// Remove from source
if (sourceListType === 'main') {
mainItems.value.splice(sourceIndex, 1);
} else {
moreItems.value.splice(sourceIndex, 1);
}
// Add to target
if (targetListType === 'main') {
mainItems.value.splice(targetIndex, 0, item);
} else {
moreItems.value.splice(targetIndex, 0, item);
}
draggedItem.value = null;
}
function handleDropToList(event, targetListType) {
event.preventDefault();
if (!draggedItem.value) return;
const sourceListType = draggedItem.value.type;
const sourceIndex = draggedItem.value.index;
const item = draggedItem.value.item;
// Remove from source
if (sourceListType === 'main') {
mainItems.value.splice(sourceIndex, 1);
} else {
moreItems.value.splice(sourceIndex, 1);
}
// Add to target list at the end
if (targetListType === 'main') {
mainItems.value.push(item);
} else {
moreItems.value.push(item);
}
draggedItem.value = null;
}
function moveToMore(index) {
const item = mainItems.value.splice(index, 1)[0];
moreItems.value.push(item);
}
function moveToMain(index) {
const item = moreItems.value.splice(index, 1)[0];
mainItems.value.push(item);
}
function saveCustomization() {
const config = {
mainItems: mainItems.value.map(item => item.title),
moreItems: moreItems.value.map(item => item.title)
};
setSidebarCustomization(config);
// Notify the sidebar to reload
window.dispatchEvent(new CustomEvent('sidebar-customization-changed'));
dialog.value = false;
}
function resetToDefault() {
clearSidebarCustomization();
initializeItems();
// Notify the sidebar to reload
window.dispatchEvent(new CustomEvent('sidebar-customization-changed'));
}
onMounted(() => {
initializeItems();
});
</script>
<style scoped>
.draggable-item {
cursor: move;
border: 1px solid rgba(var(--v-border-color), var(--v-border-opacity));
border-radius: 4px;
background-color: rgba(var(--v-theme-surface));
transition: all 0.2s;
}
.draggable-item:hover {
background-color: rgba(var(--v-theme-primary), 0.1);
border-color: rgba(var(--v-theme-primary), 0.3);
}
.custom-list {
min-height: 200px;
border: 1px dashed rgba(var(--v-border-color), var(--v-border-opacity));
border-radius: 4px;
padding: 8px;
}
</style>
@@ -148,16 +148,6 @@
}
}
},
"sidebar": {
"title": "Sidebar",
"customize": {
"title": "Customize Sidebar",
"subtitle": "Drag to reorder modules, or move modules in/out of the \"More Features\" group. Settings are saved locally in your browser.",
"reset": "Reset to Default",
"mainItems": "Main Modules",
"moreItems": "More Features"
}
},
"backup": {
"dialog": {
"title": "Backup Manager"
@@ -148,16 +148,6 @@
}
}
},
"sidebar": {
"title": "サイドバー",
"customize": {
"title": "サイドバーをカスタマイズ",
"subtitle": "ドラッグしてモジュールを並べ替えるか、「その他の機能」グループへ移動またはグループから取り出します。設定はブラウザー内にのみ保存されます。",
"reset": "既定に戻す",
"mainItems": "メインモジュール",
"moreItems": "その他の機能"
}
},
"backup": {
"dialog": {
"title": "バックアップ管理"
@@ -148,16 +148,6 @@
}
}
},
"sidebar": {
"title": "Боковая панель",
"customize": {
"title": "Настройка меню",
"subtitle": "Перетаскивайте элементы, чтобы изменить их порядок или скрыть в группе «Дополнительно». Настройки сохраняются локально в браузере.",
"reset": "Сбросить порядок",
"mainItems": "Основные разделы",
"moreItems": "Дополнительно"
}
},
"backup": {
"dialog": {
"title": "Резервное копирование"
@@ -148,16 +148,6 @@
}
}
},
"sidebar": {
"title": "侧边栏",
"customize": {
"title": "自定义侧边栏",
"subtitle": "拖拽以调整模块顺序,或者将模块移入/移出\"更多功能\"分组。设置仅保存在浏览器本地。",
"reset": "恢复默认",
"mainItems": "主要模块",
"moreItems": "更多功能"
}
},
"backup": {
"dialog": {
"title": "备份管理"
+37 -69
View File
@@ -127,13 +127,7 @@ onMounted(() => {
/>
<VerticalSidebarVue v-if="showSidebar" />
<VerticalHeaderVue />
<v-main
:class="{ 'chat-main': isCurrentChatRoute }"
:style="{
height: isViewportLockedRoute ? '100vh' : undefined,
overflow: isViewportLockedRoute ? 'hidden' : undefined,
}"
>
<v-main :class="{ 'chat-main': isCurrentChatRoute }">
<v-container
fluid
class="page-wrapper"
@@ -143,23 +137,14 @@ onMounted(() => {
isProviderPageRoute || isPlatformPageRoute,
'fullscreen-container': isFullScreenRoute,
}"
:style="{
height:
isFullScreenRoute || isProviderPageRoute || isPlatformPageRoute
? '100%'
: 'calc(100% - 8px)',
padding: isFullScreenRoute ? '0' : undefined,
minHeight:
isFullScreenRoute || isProviderPageRoute || isPlatformPageRoute
? 'unset'
: undefined,
}"
>
<div
class="page-content"
:class="{
'page-content--locked': isViewportLockedRoute || isPluginViewRoute,
}"
:style="{
height: '100%',
width: '100%',
overflow: isViewportLockedRoute ? 'hidden' : undefined,
padding: isFullScreenRoute ? '0' : undefined,
position: isPluginViewRoute ? 'relative' : undefined,
}"
>
@@ -185,18 +170,6 @@ onMounted(() => {
</template>
<style scoped>
.chat-mode-container {
min-height: unset !important;
height: 100% !important;
overflow: hidden !important;
}
.viewport-locked-container {
min-height: unset !important;
height: 100% !important;
overflow: hidden !important;
}
.chat-main {
padding-top: 0 !important;
}
@@ -219,31 +192,32 @@ onMounted(() => {
:global(.v-main) {
height: calc(100vh - var(--astrbot-toolbar-height, 40px)) !important;
padding-top: 0 !important;
overflow-x: hidden !important;
overflow-y: auto !important;
scrollbar-width: none;
overflow: hidden !important;
/* The document no longer scrolls; the content area does. Sticky offsets that were
written for the document layout must be measured from the content area's own top. */
--v-layout-top: 0px !important;
}
:global(.v-main::-webkit-scrollbar) {
width: 0;
background: transparent;
}
/* The content area is the opaque card next to the sidebar. */
/* Keep the card frame fixed while its content scrolls inside the rounded edges. */
:global(.page-wrapper) {
--astrbot-content-gap: 6px;
height: calc(100vh - var(--astrbot-toolbar-height, 40px) - var(--astrbot-content-gap)) !important;
width: calc(100% - var(--astrbot-content-gap));
min-height: 0;
margin: 0;
padding: 0 !important;
overflow: hidden;
border-left: 1px solid rgba(var(--v-theme-on-surface), 0.1);
border-top-left-radius: 12px;
border-radius: 12px;
}
/* On small screens there is no permanent sidebar to separate from, so the
card's left edge treatments would only read as stray lines. */
@media (max-width: 959.98px) {
:global(.page-wrapper) {
--astrbot-content-gap: 0px;
border-left: 0;
border-top-left-radius: 0;
border-radius: 0;
}
}
@@ -253,19 +227,23 @@ onMounted(() => {
border-top: 1px solid rgba(var(--v-theme-on-surface), 0.1);
}
/* Normal pages grow with their content so the main area has something to scroll. */
:global(.page-wrapper:not(.viewport-locked-container):not(.chat-mode-container):not(.fullscreen-container)) {
height: auto !important;
min-height: calc(100vh - var(--astrbot-toolbar-height, 40px));
.page-content {
height: 100%;
width: 100%;
padding: 8px;
overflow-x: hidden;
overflow-y: auto;
scrollbar-width: none;
}
/* Viewport-locked and full-screen pages keep a fixed height and scroll internally.
Clipping them keeps full-bleed children from painting outside the rounded corner. */
:global(.viewport-locked-container),
:global(.chat-mode-container),
:global(.fullscreen-container) {
height: calc(100vh - var(--astrbot-toolbar-height, 40px)) !important;
overflow: hidden !important;
.page-content::-webkit-scrollbar {
width: 0;
background: transparent;
}
/* Chat, workspace and plugin pages manage their own internal scrolling. */
.page-content--locked {
overflow: hidden;
}
/* macOS desktop vibrancy: the window material shows through wherever the UI stays
@@ -290,14 +268,9 @@ onMounted(() => {
--astrbot-vibrancy-tint: rgba(26, 26, 26, 0.92);
}
/* Off macOS the sidebar column is opaque; extend its color behind the content
card's rounded corner so the notch does not contrast with the sidebar. */
/* Extend the sidebar surface behind every corner and the right/bottom gaps. */
:global(html:not([data-astrbot-desktop-platform='macos']) .v-main) {
background-image: linear-gradient(
to right,
rgb(var(--v-theme-surface)) 0 calc(var(--v-layout-left) + 12px),
transparent calc(var(--v-layout-left) + 12px) 100%
) !important;
background: rgb(var(--v-theme-surface)) !important;
}
:global(html[data-astrbot-desktop-platform='macos']),
@@ -307,14 +280,9 @@ onMounted(() => {
background: transparent !important;
}
/* The sidebar tint lives on the main area's own background (behind everything), covering
the sidebar column plus a small overhang that reaches the content corner. */
/* Keep the macOS tint continuous behind the sidebar and the card gaps. */
:global(html[data-astrbot-desktop-platform='macos'] .v-main) {
background: linear-gradient(
to right,
var(--astrbot-vibrancy-tint, transparent) 0 calc(var(--v-layout-left) + 12px),
transparent calc(var(--v-layout-left) + 12px) 100%
) !important;
background: var(--astrbot-vibrancy-tint, transparent) !important;
}
/* Vuetify paints its own surface behind every list, which would sit on top of the
@@ -41,10 +41,6 @@ export interface menu {
export const SYSTEM_GROUP_KEY = 'core.navigation.groups.system';
export const EXTENSION_GROUP_KEY = 'core.navigation.groups.extension';
// Kept for the legacy sidebar customization storage; the default layout no
// longer uses a collapsible "more" group.
export const MORE_GROUP_KEY = 'core.navigation.groups.more';
// 注意:这个文件现在包含i18n键值而不是直接的文本
// 在组件中使用时需要通过t()函数进行翻译
// 所有键名都使用 core.navigation.* 格式
-195
View File
@@ -1,195 +0,0 @@
// Utility for managing sidebar customization in localStorage
const STORAGE_KEY = 'astrbot_sidebar_customization';
/**
* Get the customized sidebar configuration from localStorage
* @returns {Object|null} The customization config or null if not set
*/
export function getSidebarCustomization() {
try {
const stored = localStorage.getItem(STORAGE_KEY);
return stored ? JSON.parse(stored) : null;
} catch (error) {
console.error('Error reading sidebar customization:', error);
return null;
}
}
/**
* Save the sidebar customization to localStorage
* @param {Object} config - The customization configuration
* @param {Array} config.mainItems - Array of item titles for main sidebar
* @param {Array} config.moreItems - Array of item titles for "More Features" group
*/
export function setSidebarCustomization(config) {
try {
localStorage.setItem(STORAGE_KEY, JSON.stringify(config));
} catch (error) {
console.error('Error saving sidebar customization:', error);
}
}
/**
* Clear the sidebar customization (reset to default)
*/
export function clearSidebarCustomization() {
try {
localStorage.removeItem(STORAGE_KEY);
} catch (error) {
console.error('Error clearing sidebar customization:', error);
}
}
/**
* 解析侧边栏默认项与用户定制,返回主区/更多区及可选的合并结果
* @param {Array} defaultItems - 默认侧边栏结构
* @param {Object|null} customization - 用户定制(mainItems/moreItems)
* @param {Object} options
* @param {boolean} [options.cloneItems=false] - 是否克隆条目以避免外部引用被修改
* @param {boolean} [options.assembleMoreGroup=false] - 是否组装带更多分组的整体数组
* @returns {{ mainItems: Array, moreItems: Array, merged?: Array }}
*/
import { MORE_GROUP_KEY } from "@/layouts/full/vertical-sidebar/sidebarItem";
export function resolveSidebarItems(defaultItems, customization, options = {}) {
const { cloneItems = false, assembleMoreGroup = false } = options;
const normalizeKeys = (keys = []) => {
const list = Array.isArray(keys) ? keys : [];
const deduped = [];
const seen = new Set();
list.forEach((key) => {
if (typeof key !== 'string') return;
if (seen.has(key)) return;
seen.add(key);
deduped.push(key);
});
return deduped;
};
const all = new Map();
const defaultMain = [];
const defaultMore = [];
const defaultMoreGroup = defaultItems.find(item => item.children && item.title === MORE_GROUP_KEY);
// 收集所有条目,按 title 建索引
defaultItems.forEach(item => {
if (item.children && item.title === MORE_GROUP_KEY) {
item.children.forEach(child => {
all.set(child.title, cloneItems ? { ...child } : child);
defaultMore.push(child.title);
});
} else {
all.set(item.title, cloneItems ? { ...item } : item);
defaultMain.push(item.title);
}
});
const hasCustomization = Boolean(customization);
let mainKeys = hasCustomization ? normalizeKeys(customization.mainItems || []) : [...defaultMain];
let moreKeys = hasCustomization ? normalizeKeys(customization.moreItems || []) : [...defaultMore];
if (hasCustomization) {
mainKeys = mainKeys.filter(title => all.has(title));
moreKeys = moreKeys.filter(title => all.has(title));
}
if (hasCustomization) {
// 如果同一项同时出现在主区与更多区,主区优先。
const mainSet = new Set(mainKeys);
moreKeys = moreKeys.filter(title => !mainSet.has(title));
}
const used = hasCustomization
? new Set([...mainKeys, ...moreKeys])
: new Set(defaultMain.concat(defaultMore));
const mainItems = mainKeys
.map(title => all.get(title))
.filter(Boolean);
if (hasCustomization) {
// 补充新增默认主区项
defaultMain.forEach(title => {
if (!used.has(title)) {
const item = all.get(title);
if (item) mainItems.push(item);
}
});
}
const moreItems = moreKeys
.map(title => all.get(title))
.filter(Boolean);
if (hasCustomization) {
// 补充新增默认更多区项
defaultMore.forEach(title => {
if (!used.has(title)) {
const item = all.get(title);
if (item) moreItems.push(item);
}
});
}
let merged;
if (assembleMoreGroup) {
const children = cloneItems ? moreItems.map(item => ({ ...item })) : [...moreItems];
if (children.length > 0) {
merged = [
...mainItems,
{
title: MORE_GROUP_KEY,
icon: defaultMoreGroup?.icon || 'mdi-dots-horizontal',
children
}
];
} else {
merged = [...mainItems];
}
}
return {
mainItems,
moreItems,
merged,
normalizedMainKeys: [...mainKeys],
normalizedMoreKeys: [...moreKeys]
};
}
/**
* 应用侧边栏定制,返回包含更多分组的完整结构
* @param {Array} defaultItems - 默认侧边栏结构
* @returns {Array} 自定义后的结构(新数组,不修改入参)
*/
export function applySidebarCustomization(defaultItems) {
const customization = getSidebarCustomization();
const {
merged,
normalizedMainKeys,
normalizedMoreKeys
} = resolveSidebarItems(defaultItems, customization, {
cloneItems: true,
assembleMoreGroup: true
});
if (customization) {
const rawMainKeys = Array.isArray(customization.mainItems) ? customization.mainItems : [];
const rawMoreKeys = Array.isArray(customization.moreItems) ? customization.moreItems : [];
const hasChanged =
JSON.stringify(rawMainKeys) !== JSON.stringify(normalizedMainKeys) ||
JSON.stringify(rawMoreKeys) !== JSON.stringify(normalizedMoreKeys);
if (hasChanged) {
setSidebarCustomization({
mainItems: normalizedMainKeys,
moreItems: normalizedMoreKeys
});
}
}
return merged || defaultItems;
}
-11
View File
@@ -98,16 +98,6 @@
</div>
<div class="settings-section__content">
<div class="settings-list-card">
<div class="settings-item">
<div class="settings-item__label">
<div class="settings-item__title">{{ tm('sidebar.customize.title') }}</div>
<div class="settings-item__subtitle">{{ tm('sidebar.customize.subtitle') }}</div>
</div>
<div class="settings-item__control">
<SidebarCustomizer />
</div>
</div>
<div class="settings-item settings-item--color">
<div class="settings-item__label">
<div class="settings-item__title">{{ tm('theme.customize.title') }}</div>
@@ -490,7 +480,6 @@ import { apiKeyApi, systemConfigApi } from '@/api/v1';
import AstrBotConfigV4 from '@/components/shared/AstrBotConfigV4.vue';
import WaitingForRestart from '@/components/shared/WaitingForRestart.vue';
import ProxySelector from '@/components/shared/ProxySelector.vue';
import SidebarCustomizer from '@/components/shared/SidebarCustomizer.vue';
import BackupDialog from '@/components/shared/BackupDialog.vue';
import StorageCleanupPanel from '@/components/shared/StorageCleanupPanel.vue';
import DashboardTwoFactorDialog from '@/components/shared/DashboardTwoFactorDialog.vue';
+3 -2
View File
@@ -4,7 +4,7 @@ The AstrBot admin panel features plugin management, log viewing, visual configur
## Navigation and Previous Entry Points
These paths use the current default sidebar. If you customized it, open `System Settings → Appearance → Customize Sidebar` at the bottom of the sidebar to review or reset the layout.
These paths use the current default sidebar with a fixed menu order. Extension items still support collapsing, pinning, and grouping by plugin.
| Previous entry point or name | Current entry point |
| --- | --- |
@@ -17,6 +17,7 @@ These paths use the current default sidebar. If you customized it, open `System
| Config → System Config | System Settings → General, Appearance, Network, or Security, depending on the setting |
| Commands / Command Management | Extensions → Handlers → Command |
| Standalone MCP / Skills entries | Extensions → MCP Servers / Skills |
| System Settings → Appearance → Customize Sidebar | Removed; the sidebar uses the default menu layout |
| Custom Rules / Future Tasks / SubAgents | Custom Rules is under the sidebar `System` group; Future Tasks and SubAgents are under the `Extensions` group |
Old log, trace, conversation, and statistics URLs still redirect to the corresponding tabs. Agent runners are now saved in each profile; they are no longer created as model providers. See [Agent Runners](./agent-runner.md) for the setup steps.
@@ -94,7 +95,7 @@ Use the `{}` icon labeled `Edit Configuration File` to edit the current profile
Global settings are under `System Settings` at the bottom of the sidebar:
- `General`: timezone, external callback address, logs, and cache.
- `Appearance`: sidebar and theme.
- `Appearance`: theme colors.
- `Network`: HTTP proxy, Python package sources, and GitHub proxy. For the address to use when AstrBot runs in Docker, see [Deploy with Docker](/en/deploy/astrbot/docker.md).
- `Security`: WebUI HTTPS, login rate limits, and TOTP.
- `Maintenance`: backup, restore, and restart.
+3 -2
View File
@@ -4,7 +4,7 @@ AstrBot 管理面板具有管理插件、查看日志、可视化配置、查看
## 菜单与旧入口对照
以下入口对应当前 WebUI 的默认侧边栏。若你调整过侧边栏,可在左下角 `系统设置 → 外观 → 自定义侧边栏` 中查看或恢复默认布局。
以下入口对应当前 WebUI 的默认侧边栏。侧边栏使用固定的菜单顺序;扩展功能仍支持折叠、置顶和按插件分组。
| 旧入口或称呼 | 当前入口 |
| --- | --- |
@@ -17,6 +17,7 @@ AstrBot 管理面板具有管理插件、查看日志、可视化配置、查看
| 配置文件 → 系统配置 | 左下角系统设置 → 常规、外观、网络或安全,按配置用途选择 |
| 指令管理 | 插件 → 管理行为 → 指令 |
| 独立 MCP / 技能入口 | 插件 → MCP / 技能 |
| 系统设置 → 外观 → 自定义侧边栏 | 已移除;侧边栏使用默认菜单布局 |
| 自定义规则 / 未来任务 / 子代理编排 | 自定义规则位于侧边栏 `系统` 分组,未来任务与子代理编排位于 `扩展功能` 分组 |
旧日志、追踪、对话和统计页面地址仍会跳转到对应的新标签页。Agent 执行器现在随配置文件保存,不再通过新增模型提供商创建;操作步骤见 [Agent 执行器](./agent-runner.md)。
@@ -94,7 +95,7 @@ ChatUI 支持以下常用能力:
全局设置位于左下角 `系统设置`,不在配置文件选择器中:
- `常规`:时区、外部回调地址、日志和缓存。
- `外观`:侧边栏和主题。
- `外观`:主题颜色。
- `网络`:HTTP 代理、Python 依赖源和 GitHub 加速地址。Docker 下的代理填法见 [Docker 部署](/deploy/astrbot/docker.md)。
- `安全`:WebUI HTTPS、登录限速和 TOTP。
- `维护`:备份、恢复和重启。