diff --git a/dashboard/src/components/extension/PluginPagesSwitcher.vue b/dashboard/src/components/extension/PluginPagesSwitcher.vue new file mode 100644 index 0000000000..e2b65a1583 --- /dev/null +++ b/dashboard/src/components/extension/PluginPagesSwitcher.vue @@ -0,0 +1,197 @@ + + + + + diff --git a/dashboard/src/composables/usePluginSidebarItems.ts b/dashboard/src/composables/usePluginSidebarItems.ts deleted file mode 100644 index af029d0804..0000000000 --- a/dashboard/src/composables/usePluginSidebarItems.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { reactive, shallowRef, onMounted, watch } from "vue"; -import { pluginApi } from "@/api/v1"; -import type { menu } from "@/layouts/full/vertical-sidebar/sidebarItem"; - -const DEFAULT_ICON = "mdi-puzzle"; -const GROUP_I18N_KEY = "core.navigation.pluginWebui"; -const GROUP_ICON = "mdi-puzzle-outline"; - -interface PluginEntry { - name: string; - display_name?: string | null; - activated: boolean; - pages: string[]; -} - -/** 模块级共享状态,由 useExtensionPage.getExtensions() 更新 */ -export const pluginSidebarState = reactive<{ - plugins: PluginEntry[]; -}>({ - plugins: [], -}); - -function buildPluginItems(plugins: PluginEntry[]): menu | null { - const activeWithPages = plugins.filter( - (p) => p.activated && Array.isArray(p.pages) && p.pages.length > 0, - ); - - if (activeWithPages.length === 0) return null; - - const children: menu[] = activeWithPages.map((p) => { - const displayName = p.display_name || p.name || "Unknown Plugin"; - const firstPage = p.pages[0]; - - return { - title: displayName, - icon: DEFAULT_ICON, - to: `/plugin-page/${encodeURIComponent(p.name)}/${encodeURIComponent(firstPage)}`, - isRawTitle: true, - }; - }); - - return { - title: GROUP_I18N_KEY, - icon: GROUP_ICON, - children, - }; -} - -let initialFetched = false; - -async function initPluginState() { - if (initialFetched) return; - initialFetched = true; - try { - const res = await pluginApi.list(); - if (res.data?.status === "ok") { - pluginSidebarState.plugins = res.data.data ?? []; - } - } catch { - // 静默失败,后续 getExtensions() 会补充 - } -} - -export function usePluginSidebarItems() { - const pluginItems = shallowRef(null); - - function refreshItems() { - pluginItems.value = buildPluginItems(pluginSidebarState.plugins); - } - - onMounted(async () => { - await initPluginState(); - refreshItems(); - }); - - watch( - () => pluginSidebarState.plugins, - () => { - refreshItems(); - }, - ); - - return { pluginItems }; -} diff --git a/dashboard/src/i18n/locales/en-US/core/navigation.json b/dashboard/src/i18n/locales/en-US/core/navigation.json index bbd7d5775c..62caec1ab0 100644 --- a/dashboard/src/i18n/locales/en-US/core/navigation.json +++ b/dashboard/src/i18n/locales/en-US/core/navigation.json @@ -16,7 +16,8 @@ "market": "Plugin Market", "mcp": "MCP Servers", "skills": "Skills", - "components": "Handlers" + "components": "Handlers", + "pluginPages": "Plugin Pages" }, "conversation": "Conversations", "sessionManagement": "Custom Rules", @@ -45,6 +46,5 @@ "configTabs": { "normal": "Normal Config", "system": "System Config" - }, - "pluginWebui": "Plugin Pages" + } } diff --git a/dashboard/src/i18n/locales/en-US/features/extension.json b/dashboard/src/i18n/locales/en-US/features/extension.json index 49ce481c60..525407436d 100644 --- a/dashboard/src/i18n/locales/en-US/features/extension.json +++ b/dashboard/src/i18n/locales/en-US/features/extension.json @@ -9,7 +9,8 @@ "market": "AstrBot Plugin Market" }, "titles": { - "installedAstrBotPlugins": "Installed" + "installedAstrBotPlugins": "Installed", + "pluginPages": "Plugin Pages" }, "failedPlugins": { "title": "Failed to Load Plugins ({count})", @@ -48,6 +49,8 @@ "openPages": "Pages", "openPage": "Open", "openWebui": "Open Plugin UI", +"expand": "Expand", +"collapse": "Collapse", "close": "Close", "save": "Save", "saveAndClose": "Save and Close", @@ -103,7 +106,9 @@ }, "empty": { "noPlugins": "No Extensions", - "noPluginsDesc": "Try installing extensions or adjusting the search" + "noPluginsDesc": "Try installing extensions or adjusting the search", + "noPluginPages": "No Plugin Pages", + "noPluginPagesDesc": "Install plugins that ship web pages and they will show up here" }, "detail": { "contents": "Plugin Handlers", diff --git a/dashboard/src/i18n/locales/ru-RU/core/navigation.json b/dashboard/src/i18n/locales/ru-RU/core/navigation.json index a8ff7c6cb3..1f2f29f133 100644 --- a/dashboard/src/i18n/locales/ru-RU/core/navigation.json +++ b/dashboard/src/i18n/locales/ru-RU/core/navigation.json @@ -13,7 +13,8 @@ "market": "Магазин плагинов", "mcp": "Серверы MCP", "skills": "Навыки", - "components": "Управление поведением" + "components": "Управление поведением", + "pluginPages": "Страницы плагинов" }, "config": "Конфигурация", "chat": "Чат", @@ -45,6 +46,5 @@ "configTabs": { "normal": "Обычная конфигурация", "system": "Системная конфигурация" - }, - "pluginWebui": "Страницы плагинов" + } } diff --git a/dashboard/src/i18n/locales/ru-RU/features/extension.json b/dashboard/src/i18n/locales/ru-RU/features/extension.json index 6eb112b0fd..1a9a53235f 100644 --- a/dashboard/src/i18n/locales/ru-RU/features/extension.json +++ b/dashboard/src/i18n/locales/ru-RU/features/extension.json @@ -9,7 +9,8 @@ "handlersOperation": "Управление поведением" }, "titles": { - "installedAstrBotPlugins": "Установленные" + "installedAstrBotPlugins": "Установленные", + "pluginPages": "Страницы плагинов" }, "failedPlugins": { "title": "Ошибка загрузки ({count})", @@ -48,6 +49,8 @@ "openPages": "Pages", "openPage": "Открыть", "openWebui": "Открыть UI плагина", +"expand": "Развернуть", +"collapse": "Свернуть", "close": "Закрыть", "save": "Сохранить", "saveAndClose": "Сохранить и закрыть", @@ -103,7 +106,9 @@ }, "empty": { "noPlugins": "Плагины не найдены", - "noPluginsDesc": "Попробуйте установить новые плагины или изменить поиск." + "noPluginsDesc": "Попробуйте установить новые плагины или изменить поиск.", + "noPluginPages": "Страницы плагинов не найдены", + "noPluginPagesDesc": "Установите плагины с веб-страницами, и они появятся здесь." }, "detail": { "contents": "Обработчики плагина", @@ -303,6 +308,10 @@ "invalidUrl": "Введите корректный URL", "invalidRepositoryUrl": "Введите корректный URL Git-репозитория", "enterJsonUrl": "Введите URL, возвращающий список плагинов в формате JSON", + "pluginNotFound": "Плагин не найден", + "pluginDisabled": "Этот плагин отключён. Включите его, прежде чем открывать страницу.", + "pluginPageNotFound": "Страница плагина не найдена", + "pluginPageLoadFailed": "Не удалось загрузить страницу плагина", "missingMarketPluginId": "Эта запись источника не содержит идентификаторов плагина, поэтому его нельзя установить из магазина", "sourceBindSuccess": "Источник плагина обновлен", "validatingPlugin": "Проверка плагина...", diff --git a/dashboard/src/i18n/locales/zh-CN/core/navigation.json b/dashboard/src/i18n/locales/zh-CN/core/navigation.json index faac87d4c6..1f1571b964 100644 --- a/dashboard/src/i18n/locales/zh-CN/core/navigation.json +++ b/dashboard/src/i18n/locales/zh-CN/core/navigation.json @@ -13,7 +13,8 @@ "market": "插件市场", "mcp": "MCP", "skills": "技能", - "components": "管理行为" + "components": "管理行为", + "pluginPages": "插件页面" }, "config": "配置文件", "chat": "聊天", @@ -45,6 +46,5 @@ "configTabs": { "normal": "普通配置", "system": "系统配置" - }, - "pluginWebui": "插件页面" + } } diff --git a/dashboard/src/i18n/locales/zh-CN/features/extension.json b/dashboard/src/i18n/locales/zh-CN/features/extension.json index 476bacd051..abeceab5bf 100644 --- a/dashboard/src/i18n/locales/zh-CN/features/extension.json +++ b/dashboard/src/i18n/locales/zh-CN/features/extension.json @@ -9,7 +9,8 @@ "handlersOperation": "管理行为" }, "titles": { - "installedAstrBotPlugins": "已安装" + "installedAstrBotPlugins": "已安装", + "pluginPages": "插件页面" }, "failedPlugins": { "title": "加载失败插件({count})", @@ -48,6 +49,8 @@ "openPages": "Pages", "openPage": "打开", "openWebui": "打开插件UI界面", + "expand": "展开", + "collapse": "收起", "close": "关闭", "save": "保存", "saveAndClose": "保存并关闭", @@ -103,7 +106,9 @@ }, "empty": { "noPlugins": "暂无插件", - "noPluginsDesc": "尝试安装插件或调整搜索条件" + "noPluginsDesc": "尝试安装插件或调整搜索条件", + "noPluginPages": "暂无插件页面", + "noPluginPagesDesc": "安装带有网页界面的插件后会显示在这里" }, "detail": { "contents": "插件行为", diff --git a/dashboard/src/layouts/full/FullLayout.vue b/dashboard/src/layouts/full/FullLayout.vue index 925b4974c5..1d8b71b814 100644 --- a/dashboard/src/layouts/full/FullLayout.vue +++ b/dashboard/src/layouts/full/FullLayout.vue @@ -21,16 +21,11 @@ const routerLoadingStore = useRouterLoadingStore(); const isCurrentChatRoute = computed( () => route.path === "/chat" || route.path.startsWith("/chat/"), ); -const isPluginPageRoute = computed( - () => route.path.startsWith("/plugin-page/"), -); const isProviderPageRoute = computed(() => route.path === "/providers"); const isViewportLockedRoute = computed( () => isCurrentChatRoute.value || isProviderPageRoute.value, ); -const isFullScreenRoute = computed( - () => isCurrentChatRoute.value || isPluginPageRoute.value, -); +const isFullScreenRoute = computed(() => isCurrentChatRoute.value); const shouldMountChat = ref(isCurrentChatRoute.value); const showSidebar = computed(() => !isCurrentChatRoute.value); @@ -141,7 +136,6 @@ onMounted(() => { height: '100%', width: '100%', overflow: isViewportLockedRoute ? 'hidden' : undefined, - position: isPluginPageRoute ? 'relative' : undefined, }" >
item.title === MORE_GROUP_KEY)) { - result.push(pluginItems.value); - } - - return result; + return applySidebarCustomization(sidebarItems); } function collectGroupValues(items, values = new Set()) { @@ -70,12 +50,6 @@ watch(openedItems, (val) => { localStorage.setItem('sidebar_openedItems', JSON.stringify(sanitizeOpenedItems(val, sidebarMenu.value))); }, { deep: true }); -// 当插件项变化时(如插件启用/停用),刷新菜单 -watch(pluginItems, () => { - sidebarMenu.value = buildSidebarMenu(); - openedItems.value = sanitizeOpenedItems(openedItems.value, sidebarMenu.value); -}); - function refreshSidebarMenu() { sidebarMenu.value = buildSidebarMenu(); openedItems.value = sanitizeOpenedItems(openedItems.value, sidebarMenu.value); diff --git a/dashboard/src/router/MainRoutes.ts b/dashboard/src/router/MainRoutes.ts index b75cea1620..4901f8d312 100644 --- a/dashboard/src/router/MainRoutes.ts +++ b/dashboard/src/router/MainRoutes.ts @@ -56,6 +56,13 @@ const MainRoutes = { props: { initialTab: 'market' }, meta: { extensionTab: 'installed', pluginView: 'market' } }, + { + name: 'ExtensionPluginPages', + path: 'pages/:pluginName?/:pageName?', + component: () => import('@/views/ExtensionPage.vue'), + props: { initialTab: 'installed' }, + meta: { extensionTab: 'pluginPages', pluginView: 'pluginPages' } + }, { name: 'ExtensionMcp', path: 'mcp', @@ -90,11 +97,6 @@ const MainRoutes = { } ] }, - { - name: 'PluginPage', - path: '/plugin-page/:pluginName/:pageName', - component: () => import('@/views/PluginPagePage.vue') - }, { path: '/extension/:pluginId', redirect: (to: RouteLocationNormalized) => ({ diff --git a/dashboard/src/views/ExtensionPage.vue b/dashboard/src/views/ExtensionPage.vue index f632dfa009..cc887a182d 100644 --- a/dashboard/src/views/ExtensionPage.vue +++ b/dashboard/src/views/ExtensionPage.vue @@ -4,6 +4,7 @@ import ConsoleDisplayer from "@/components/shared/ConsoleDisplayer.vue"; import ReadmeDialog from "@/components/shared/ReadmeDialog.vue"; import ProxySelector from "@/components/shared/ProxySelector.vue"; import UninstallConfirmDialog from "@/components/shared/UninstallConfirmDialog.vue"; +import PluginPagesSwitcher from "@/components/extension/PluginPagesSwitcher.vue"; import { useExtensionPage } from "./extension/useExtensionPage"; import { computed, defineAsyncComponent } from "vue"; import defaultPluginIcon from "/favicon.svg"; @@ -25,6 +26,12 @@ const MarketPluginsTab = defineAsyncComponent( const PluginDetailPage = defineAsyncComponent( () => import("./extension/PluginDetailPage.vue"), ); +const PluginPagesTab = defineAsyncComponent( + () => import("./extension/PluginPagesTab.vue"), +); +const PluginPagePage = defineAsyncComponent( + () => import("./PluginPagePage.vue"), +); const pageState = useExtensionPage(props.initialTab); const { pluginName, pluginDesc } = usePluginI18n(); @@ -292,6 +299,17 @@ const updateDialogPluginLogo = computed(() => {