import type { ComponentRecordType, GenerateMenuAndRoutesOptions, RouteRecordStringComponent, } from '@vben/types'; import { generateAccessible } from '@vben/access'; import { preferences } from '@vben/preferences'; import { getAllMenusApi } from '#/api/core/menu'; import { BasicLayout, IFrameView } from '#/layouts'; import { useAppContextStore } from '#/store/app-context'; import { isLightMenuName } from './light-menu'; const forbiddenComponent = () => import('#/views/_core/fallback/forbidden.vue'); const LEGACY_COMPONENT_MAP: Record = { '/online-dev/page-render/index': '/_core/page-render/index', 'online-dev/page-render/index': '/_core/page-render/index', }; function normalizeViewPath(path: string) { const mappedPath = LEGACY_COMPONENT_MAP[path] || path; const normalizedPath = mappedPath.replace(/^(\.\/|\.\.\/)+/, ''); const viewPath = normalizedPath.startsWith('/') ? normalizedPath : `/${normalizedPath}`; return viewPath.replace(/^\/views/, ''); } function normalizeRouteComponent(component?: string) { if (!component) return component; return LEGACY_COMPONENT_MAP[component] || component; } function hasPageComponent( component: string | undefined, pageMap: ComponentRecordType, layoutMap: ComponentRecordType, ) { if (!component) return true; if (layoutMap[component]) return true; const normalizedPath = normalizeViewPath(component); const pageKey = normalizedPath.endsWith('.vue') ? normalizedPath : `${normalizedPath}.vue`; return Boolean(pageMap[pageKey]); } function normalizePageMap(pageMap: ComponentRecordType) { return Object.fromEntries( Object.entries(pageMap).map(([key, value]) => [ normalizeViewPath(key), value, ]), ) as ComponentRecordType; } function isLightMenuRoute(route: { name?: string }) { return isLightMenuName(route.name); } function createLightAiMenuRoute( route: Pick & { meta: RouteRecordStringComponent['meta']; }, ): RouteRecordStringComponent { return route as RouteRecordStringComponent; } const LIGHT_AI_PLATFORM_MENU_ROUTES = [ createLightAiMenuRoute({ component: '/_core/agent-chat/index', meta: { fullPathKey: true, icon: 'lucide:code', order: 60, title: 'menu-title.codex', }, name: 'CodexAgentChat', path: '/agent-chat/codex', }), createLightAiMenuRoute({ component: '/ai-platform/model/index', meta: { fullPathKey: true, icon: 'lucide:settings', order: 50, title: 'menu-title.modelConfig', }, name: 'AIModelConfig', path: '/ai-platform/model', }), createLightAiMenuRoute({ component: '/ai-platform/workflow-runs/index', meta: { fullPathKey: true, icon: 'lucide:history', order: 40, title: 'menu-title.workflowRunHistory', }, name: 'AIWorkflowRuns', path: '/ai-platform/workflow-runs', }), ]; function appendLightAiPlatformMenus(routes: RouteRecordStringComponent[]) { return routes.map((route) => { const children = route.children ? appendLightAiPlatformMenus(route.children) : undefined; if (route.name !== 'AIPlatform') { return children ? { ...route, children } : route; } const existingNames = new Set(children?.map((item) => item.name)); const missingRoutes = LIGHT_AI_PLATFORM_MENU_ROUTES.filter( (item) => !existingNames.has(item.name), ); const nextChildren = [...(children || []), ...missingRoutes].sort( (a, b) => Number(a.meta?.order || 0) - Number(b.meta?.order || 0), ); return { ...route, children: nextChildren }; }); } function filterAvailableRoutes< T extends { children?: T[]; component?: string; name?: string; path?: string; query?: unknown; }, >( routes: T[], pageMap: ComponentRecordType, layoutMap: ComponentRecordType, ): T[] { return routes .map((route) => { const children = route.children ? filterAvailableRoutes(route.children, pageMap, layoutMap) : undefined; const routeAllowed = isLightMenuRoute(route); if (!routeAllowed && !children?.length) { return undefined; } if ( !route.component && route.children && !children?.length ) { return undefined; } if (!routeAllowed && children?.length) { const groupRoute = { ...route, children }; delete groupRoute.component; return groupRoute; } const component = normalizeRouteComponent(route.component); if (!hasPageComponent(component, pageMap, layoutMap)) { if (!children?.length) return undefined; const groupRoute = { ...route, children }; delete groupRoute.component; return groupRoute; } const nextRoute = component && component !== route.component ? { ...route, component } : route; return children ? { ...nextRoute, children } : nextRoute; }) .filter(Boolean) as T[]; } async function generateAccess(options: GenerateMenuAndRoutesOptions) { const pageMap: ComponentRecordType = import.meta.glob([ '../views/_core/account-settings/index.vue', '../views/_core/agent-chat/**/*.vue', '../views/_core/announcement/index.vue', '../views/_core/announcement/list.vue', '../views/_core/authentication/login.vue', '../views/_core/fallback/**/*.vue', '../views/_core/menu/index.vue', '../views/_core/message/index.vue', '../views/_core/page-render/index.vue', '../views/_core/permission/index.vue', '../views/_core/role/index.vue', '../views/_core/user/index.vue', '../views/ai-platform/agent/editor/index.vue', '../views/ai-platform/agent/index.vue', '../views/ai-platform/knowledge/detail/index.vue', '../views/ai-platform/knowledge/index.vue', '../views/ai-platform/model/index.vue', '../views/ai-platform/workflow/editor/index.vue', '../views/ai-platform/workflow/index.vue', '../views/ai-platform/workflow-runs/index.vue', ]); const layoutMap: ComponentRecordType = { BasicLayout, IFrameView, Layout: BasicLayout, 'Layout.vue': BasicLayout, '/Layout.vue': BasicLayout, }; const routePageMap = normalizePageMap(pageMap); return await generateAccessible(preferences.app.accessMode, { ...options, fetchMenuListAsync: async () => { const appContextStore = useAppContextStore(); const menus = appContextStore.appCode ? await getAllMenusApi(appContextStore.appCode) : await getAllMenusApi(); const lightMenus = filterAvailableRoutes(menus, routePageMap, layoutMap); return appendLightAiPlatformMenus(lightMenus); }, forbiddenComponent, layoutMap, pageMap, }); } export { generateAccess };