diff --git a/web/apps/web-ele/src/locales/langs/en-US/ai-platform.json b/web/apps/web-ele/src/locales/langs/en-US/ai-platform.json index dec8431..a238b55 100644 --- a/web/apps/web-ele/src/locales/langs/en-US/ai-platform.json +++ b/web/apps/web-ele/src/locales/langs/en-US/ai-platform.json @@ -2444,6 +2444,50 @@ } } }, + "modelConfig": { + "providerManagement": "Model Providers", + "addProvider": "Add Provider", + "modelManagement": "Model Management", + "addModel": "Add Model", + "addedModels": "Added Models", + "availableModels": "Available Models", + "providerName": "Provider Name", + "providerType": "Provider Type", + "apiKey": "API Key", + "apiBase": "API Base", + "ollamaHost": "Ollama Host", + "providerDescription": "Provider Description", + "descriptionPlaceholder": "Enter description", + "modelName": "Model Name", + "displayName": "Display Name", + "modelType": "Model Type", + "chat": "Chat", + "embedding": "Embedding", + "rerank": "Rerank", + "chatModel": "Chat Model", + "embeddingModel": "Embedding Model", + "completionModel": "Completion Model", + "rerankModel": "Rerank Model", + "maxTokens": "Max Tokens", + "supportsVision": "Supports Vision", + "supportsFunction": "Supports Function Calling", + "enabled": "Enabled", + "test": "Test", + "editProvider": "Edit Provider", + "editModel": "Edit Model", + "selectProvider": "Select provider", + "sourceApi": "API", + "sourceDefault": "Default", + "searchModel": "Search model", + "refreshModels": "Refresh Models", + "addAll": "Add All", + "addDefaultModels": "Add Default Models", + "addSelected": "Add Selected", + "added": "Added", + "noDefaultModels": "No default models available", + "fetchFromApiSuccess": "Model list refreshed from API", + "fetchFromApiFallback": "API returned no models, using default model list" + }, "workflowRuns": { "title": "Run History", "description": "View AI workflow execution records and node logs", diff --git a/web/apps/web-ele/src/locales/langs/zh-CN/ai-platform.json b/web/apps/web-ele/src/locales/langs/zh-CN/ai-platform.json index e0f5f90..6294046 100644 --- a/web/apps/web-ele/src/locales/langs/zh-CN/ai-platform.json +++ b/web/apps/web-ele/src/locales/langs/zh-CN/ai-platform.json @@ -2444,6 +2444,50 @@ } } }, + "modelConfig": { + "providerManagement": "模型提供商", + "addProvider": "新增提供商", + "modelManagement": "模型管理", + "addModel": "新增模型", + "addedModels": "已添加模型", + "availableModels": "可用模型", + "providerName": "提供商名称", + "providerType": "提供商类型", + "apiKey": "API Key", + "apiBase": "API 地址", + "ollamaHost": "Ollama 地址", + "providerDescription": "提供商描述", + "descriptionPlaceholder": "请输入描述", + "modelName": "模型名称", + "displayName": "显示名称", + "modelType": "模型类型", + "chat": "对话", + "embedding": "向量", + "rerank": "重排", + "chatModel": "对话模型", + "embeddingModel": "向量模型", + "completionModel": "补全模型", + "rerankModel": "重排模型", + "maxTokens": "最大 Token", + "supportsVision": "支持视觉", + "supportsFunction": "支持函数调用", + "enabled": "已启用", + "test": "测试", + "editProvider": "编辑提供商", + "editModel": "编辑模型", + "selectProvider": "请选择提供商", + "sourceApi": "接口获取", + "sourceDefault": "默认列表", + "searchModel": "搜索模型", + "refreshModels": "刷新模型", + "addAll": "全部添加", + "addDefaultModels": "添加默认模型", + "addSelected": "添加选中", + "added": "已添加", + "noDefaultModels": "暂无可用默认模型", + "fetchFromApiSuccess": "已从接口刷新模型列表", + "fetchFromApiFallback": "接口未返回模型,已使用默认模型列表" + }, "workflowRuns": { "title": "执行历史", "description": "查看 AI 工作流的运行记录与节点执行日志", diff --git a/web/apps/web-ele/src/locales/langs/zh-TW/ai-platform.json b/web/apps/web-ele/src/locales/langs/zh-TW/ai-platform.json index 6200aad..6dcd488 100644 --- a/web/apps/web-ele/src/locales/langs/zh-TW/ai-platform.json +++ b/web/apps/web-ele/src/locales/langs/zh-TW/ai-platform.json @@ -2444,6 +2444,50 @@ } } }, + "modelConfig": { + "providerManagement": "模型提供商", + "addProvider": "新增提供商", + "modelManagement": "模型管理", + "addModel": "新增模型", + "addedModels": "已新增模型", + "availableModels": "可用模型", + "providerName": "提供商名稱", + "providerType": "提供商類型", + "apiKey": "API Key", + "apiBase": "API 位址", + "ollamaHost": "Ollama 位址", + "providerDescription": "提供商描述", + "descriptionPlaceholder": "請輸入描述", + "modelName": "模型名稱", + "displayName": "顯示名稱", + "modelType": "模型類型", + "chat": "對話", + "embedding": "向量", + "rerank": "重排", + "chatModel": "對話模型", + "embeddingModel": "向量模型", + "completionModel": "補全模型", + "rerankModel": "重排模型", + "maxTokens": "最大 Token", + "supportsVision": "支援視覺", + "supportsFunction": "支援函式呼叫", + "enabled": "已啟用", + "test": "測試", + "editProvider": "編輯提供商", + "editModel": "編輯模型", + "selectProvider": "請選擇提供商", + "sourceApi": "介面取得", + "sourceDefault": "預設清單", + "searchModel": "搜尋模型", + "refreshModels": "重新整理模型", + "addAll": "全部新增", + "addDefaultModels": "新增預設模型", + "addSelected": "新增選中", + "added": "已新增", + "noDefaultModels": "暫無可用預設模型", + "fetchFromApiSuccess": "已從介面重新整理模型清單", + "fetchFromApiFallback": "介面未返回模型,已使用預設模型清單" + }, "workflowRuns": { "title": "執行歷史", "description": "查看 AI 工作流的運行記錄與節點執行日誌", diff --git a/web/apps/web-ele/src/router/access.ts b/web/apps/web-ele/src/router/access.ts index f3f76e4..0cfe1c8 100644 --- a/web/apps/web-ele/src/router/access.ts +++ b/web/apps/web-ele/src/router/access.ts @@ -11,6 +11,7 @@ import { getAllMenusApi } from '#/api/core/menu'; import { BasicLayout, IFrameView } from '#/layouts'; import { useAppContextStore } from '#/store/app-context'; +import { createAdminRuntimePage } from './admin-runtime-page'; import { isLightMenuName } from './light-menu'; const forbiddenComponent = () => import('#/views/_core/fallback/forbidden.vue'); @@ -71,16 +72,29 @@ function createLightAiMenuRoute( return route as RouteRecordStringComponent; } -const PREFETCH_LIGHT_PAGE_KEYS = [ - '../views/_core/agent-chat/index.vue', +const ADMIN_RUNTIME_PAGE_KEYS = new Set([ '../views/_core/menu/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/index.vue', - '../views/ai-platform/model/index.vue', '../views/ai-platform/workflow-runs/index.vue', - '../views/ai-platform/workflow/index.vue', +]); + +function withAdminRuntimePages(pageMap: ComponentRecordType) { + return Object.fromEntries( + Object.entries(pageMap).map(([key, loader]) => [ + key, + ADMIN_RUNTIME_PAGE_KEYS.has(key) && typeof loader === 'function' + ? createAdminRuntimePage(loader as () => Promise<{ default: any }>) + : loader, + ]), + ) as ComponentRecordType; +} + +const PREFETCH_LIGHT_PAGE_KEYS = [ + '../views/_core/menu/index.vue', + '../views/_core/role/index.vue', + '../views/_core/user/index.vue', ]; let lightPagesPrefetched = false; @@ -89,23 +103,25 @@ function scheduleIdleTask(callback: () => void) { if (typeof window === 'undefined') return; const requestIdle = (window as any).requestIdleCallback; if (typeof requestIdle === 'function') { - requestIdle(callback, { timeout: 3000 }); + requestIdle(callback, { timeout: 8000 }); return; } - window.setTimeout(callback, 1200); + window.setTimeout(callback, 8000); } function prefetchLightPages(pageMap: ComponentRecordType) { if (lightPagesPrefetched) return; lightPagesPrefetched = true; - scheduleIdleTask(() => { - for (const key of PREFETCH_LIGHT_PAGE_KEYS) { - const loader = pageMap[key]; - if (typeof loader === 'function') { - void loader().catch(() => undefined); + window.setTimeout(() => { + scheduleIdleTask(() => { + for (const key of PREFETCH_LIGHT_PAGE_KEYS) { + const loader = pageMap[key]; + if (typeof loader === 'function') { + void loader().catch(() => undefined); + } } - } - }); + }); + }, 12_000); } const LIGHT_AI_PLATFORM_MENU_ROUTES = [ @@ -225,7 +241,7 @@ function filterAvailableRoutes< } async function generateAccess(options: GenerateMenuAndRoutesOptions) { - const pageMap: ComponentRecordType = import.meta.glob([ + const rawPageMap: ComponentRecordType = import.meta.glob([ '../views/_core/account-settings/index.vue', '../views/_core/agent-chat/**/*.vue', '../views/_core/announcement/index.vue', @@ -247,6 +263,7 @@ async function generateAccess(options: GenerateMenuAndRoutesOptions) { '../views/ai-platform/workflow/index.vue', '../views/ai-platform/workflow-runs/index.vue', ]); + const pageMap = withAdminRuntimePages(rawPageMap); const layoutMap: ComponentRecordType = { BasicLayout, diff --git a/web/apps/web-ele/src/router/admin-runtime-page.ts b/web/apps/web-ele/src/router/admin-runtime-page.ts new file mode 100644 index 0000000..eb70289 --- /dev/null +++ b/web/apps/web-ele/src/router/admin-runtime-page.ts @@ -0,0 +1,108 @@ +import type { Component } from 'vue'; + +import { defineComponent, h, onMounted, ref, shallowRef } from 'vue'; + +import { ensureAdminRuntime, isAdminRuntimeReady } from '#/runtime/admin'; + +type RouteComponentModule = { + default: Component; +}; + +type RouteComponentLoader = () => Promise; + +function renderSkeleton(message = 'Loading page...') { + const rows = Array.from({ length: 7 }, (_, index) => + h('div', { + key: index, + style: { + background: 'hsl(var(--muted))', + borderRadius: '6px', + height: index === 0 ? '28px' : '16px', + opacity: index === 0 ? '0.55' : '0.36', + width: index === 0 ? '220px' : `${82 - index * 6}%`, + }, + }), + ); + + return h( + 'div', + { + style: { + display: 'flex', + flexDirection: 'column', + gap: '14px', + height: '100%', + padding: '18px', + }, + }, + [ + h( + 'div', + { + style: { + color: 'hsl(var(--muted-foreground))', + fontSize: '13px', + }, + }, + message, + ), + h( + 'div', + { + style: { + background: 'hsl(var(--card))', + border: '1px solid hsl(var(--border))', + borderRadius: '8px', + display: 'flex', + flexDirection: 'column', + gap: '14px', + minHeight: '260px', + padding: '18px', + }, + }, + rows, + ), + ], + ); +} + +function createAdminRuntimePage(loader: RouteComponentLoader) { + return async () => ({ + default: defineComponent({ + name: 'AdminRuntimePage', + inheritAttrs: false, + setup(_, { attrs, slots }) { + const loadedComponent = shallowRef(); + const errorMessage = ref(''); + + async function loadPage() { + try { + if (!isAdminRuntimeReady()) { + await ensureAdminRuntime(); + } + const module = await loader(); + loadedComponent.value = module.default; + } catch (error) { + errorMessage.value = + error instanceof Error ? error.message : String(error); + } + } + + onMounted(() => { + void loadPage(); + }); + + return () => { + const Loaded = loadedComponent.value; + if (Loaded) return h(Loaded, attrs, slots); + if (errorMessage.value) { + return renderSkeleton(`Page load failed: ${errorMessage.value}`); + } + return renderSkeleton(); + }; + }, + }), + }); +} + +export { createAdminRuntimePage }; diff --git a/web/apps/web-ele/src/router/guard.ts b/web/apps/web-ele/src/router/guard.ts index 0925add..6a8a368 100644 --- a/web/apps/web-ele/src/router/guard.ts +++ b/web/apps/web-ele/src/router/guard.ts @@ -29,14 +29,6 @@ function isMainHomeRoute(path: string) { return /\/page-render\/main_home$/.test(path); } -function shouldLoadAdminRuntime(path: string) { - if (isMainHomeRoute(path)) return false; - if (path === '/agent-chat/codex') return false; - if (path.startsWith('/auth/')) return false; - if (path.startsWith('/file-preview/')) return false; - return true; -} - function warmAdminRuntimeAfterPaint() { const run = () => { void ensureAdminRuntime(); @@ -192,9 +184,6 @@ function setupAccessGuard(router: Router) { // 是否已经生成过动态路由 if (accessStore.isAccessChecked) { - if (shouldLoadAdminRuntime(to.path)) { - await ensureAdminRuntime(); - } return true; } diff --git a/web/apps/web-ele/src/router/prefetch.ts b/web/apps/web-ele/src/router/prefetch.ts index cba5098..28458b1 100644 --- a/web/apps/web-ele/src/router/prefetch.ts +++ b/web/apps/web-ele/src/router/prefetch.ts @@ -68,11 +68,11 @@ function scheduleIdle(callback: () => void) { const browserWindow = window as WindowWithIdleCallback; if (browserWindow.requestIdleCallback) { - browserWindow.requestIdleCallback(callback, { timeout: 3000 }); + browserWindow.requestIdleCallback(callback, { timeout: 8000 }); return; } - window.setTimeout(callback, 1200); + window.setTimeout(callback, 8000); } function canPrefetch() { @@ -160,7 +160,7 @@ function prefetchAiPlatformPages(currentPath = '') { } if (isSystemRoute(currentPath)) { - delayedPrefetch(`core:${currentPath}`, corePrefetchTasks, 8000); + delayedPrefetch(`core:${currentPath}`, corePrefetchTasks, 18_000); return; } @@ -168,7 +168,7 @@ function prefetchAiPlatformPages(currentPath = '') { delayedPrefetch( `agent-chat:${currentPath}`, [aiPrefetchTasks.agentChat, aiPrefetchTasks.aiChatPanel], - 6000, + 14_000, ); return; } @@ -177,12 +177,12 @@ function prefetchAiPlatformPages(currentPath = '') { delayedPrefetch( `ai:${currentPath}`, getAiRoutePrefetchTasks(currentPath), - 6000, + 14_000, ); return; } - delayedPrefetch('core:home', corePrefetchTasks, 10_000); + delayedPrefetch('core:home', corePrefetchTasks, 22_000); } export { prefetchAiPlatformPages }; diff --git a/web/apps/web-ele/src/runtime/admin.ts b/web/apps/web-ele/src/runtime/admin.ts index 4975cce..80787a5 100644 --- a/web/apps/web-ele/src/runtime/admin.ts +++ b/web/apps/web-ele/src/runtime/admin.ts @@ -18,6 +18,10 @@ async function setupAdminRuntime() { initialized = true; } +function isAdminRuntimeReady() { + return initialized; +} + async function ensureAdminRuntime() { if (initialized) return; @@ -28,4 +32,4 @@ async function ensureAdminRuntime() { await initializing; } -export { ensureAdminRuntime }; +export { ensureAdminRuntime, isAdminRuntimeReady };