perf: improve admin page cold starts

This commit is contained in:
2026-06-22 12:05:17 +08:00
parent c5d9bac0ed
commit c8756be02e
8 changed files with 284 additions and 34 deletions
@@ -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": { "workflowRuns": {
"title": "Run History", "title": "Run History",
"description": "View AI workflow execution records and node logs", "description": "View AI workflow execution records and node logs",
@@ -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": { "workflowRuns": {
"title": "执行历史", "title": "执行历史",
"description": "查看 AI 工作流的运行记录与节点执行日志", "description": "查看 AI 工作流的运行记录与节点执行日志",
@@ -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": { "workflowRuns": {
"title": "執行歷史", "title": "執行歷史",
"description": "查看 AI 工作流的運行記錄與節點執行日誌", "description": "查看 AI 工作流的運行記錄與節點執行日誌",
+26 -9
View File
@@ -11,6 +11,7 @@ import { getAllMenusApi } from '#/api/core/menu';
import { BasicLayout, IFrameView } from '#/layouts'; import { BasicLayout, IFrameView } from '#/layouts';
import { useAppContextStore } from '#/store/app-context'; import { useAppContextStore } from '#/store/app-context';
import { createAdminRuntimePage } from './admin-runtime-page';
import { isLightMenuName } from './light-menu'; import { isLightMenuName } from './light-menu';
const forbiddenComponent = () => import('#/views/_core/fallback/forbidden.vue'); const forbiddenComponent = () => import('#/views/_core/fallback/forbidden.vue');
@@ -71,16 +72,29 @@ function createLightAiMenuRoute(
return route as RouteRecordStringComponent; return route as RouteRecordStringComponent;
} }
const PREFETCH_LIGHT_PAGE_KEYS = [ const ADMIN_RUNTIME_PAGE_KEYS = new Set([
'../views/_core/agent-chat/index.vue',
'../views/_core/menu/index.vue', '../views/_core/menu/index.vue',
'../views/_core/page-render/index.vue', '../views/_core/permission/index.vue',
'../views/_core/role/index.vue', '../views/_core/role/index.vue',
'../views/_core/user/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-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; let lightPagesPrefetched = false;
@@ -89,15 +103,16 @@ function scheduleIdleTask(callback: () => void) {
if (typeof window === 'undefined') return; if (typeof window === 'undefined') return;
const requestIdle = (window as any).requestIdleCallback; const requestIdle = (window as any).requestIdleCallback;
if (typeof requestIdle === 'function') { if (typeof requestIdle === 'function') {
requestIdle(callback, { timeout: 3000 }); requestIdle(callback, { timeout: 8000 });
return; return;
} }
window.setTimeout(callback, 1200); window.setTimeout(callback, 8000);
} }
function prefetchLightPages(pageMap: ComponentRecordType) { function prefetchLightPages(pageMap: ComponentRecordType) {
if (lightPagesPrefetched) return; if (lightPagesPrefetched) return;
lightPagesPrefetched = true; lightPagesPrefetched = true;
window.setTimeout(() => {
scheduleIdleTask(() => { scheduleIdleTask(() => {
for (const key of PREFETCH_LIGHT_PAGE_KEYS) { for (const key of PREFETCH_LIGHT_PAGE_KEYS) {
const loader = pageMap[key]; const loader = pageMap[key];
@@ -106,6 +121,7 @@ function prefetchLightPages(pageMap: ComponentRecordType) {
} }
} }
}); });
}, 12_000);
} }
const LIGHT_AI_PLATFORM_MENU_ROUTES = [ const LIGHT_AI_PLATFORM_MENU_ROUTES = [
@@ -225,7 +241,7 @@ function filterAvailableRoutes<
} }
async function generateAccess(options: GenerateMenuAndRoutesOptions) { 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/account-settings/index.vue',
'../views/_core/agent-chat/**/*.vue', '../views/_core/agent-chat/**/*.vue',
'../views/_core/announcement/index.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/index.vue',
'../views/ai-platform/workflow-runs/index.vue', '../views/ai-platform/workflow-runs/index.vue',
]); ]);
const pageMap = withAdminRuntimePages(rawPageMap);
const layoutMap: ComponentRecordType = { const layoutMap: ComponentRecordType = {
BasicLayout, BasicLayout,
@@ -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<RouteComponentModule>;
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<Component>();
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 };
-11
View File
@@ -29,14 +29,6 @@ function isMainHomeRoute(path: string) {
return /\/page-render\/main_home$/.test(path); 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() { function warmAdminRuntimeAfterPaint() {
const run = () => { const run = () => {
void ensureAdminRuntime(); void ensureAdminRuntime();
@@ -192,9 +184,6 @@ function setupAccessGuard(router: Router) {
// 是否已经生成过动态路由 // 是否已经生成过动态路由
if (accessStore.isAccessChecked) { if (accessStore.isAccessChecked) {
if (shouldLoadAdminRuntime(to.path)) {
await ensureAdminRuntime();
}
return true; return true;
} }
+6 -6
View File
@@ -68,11 +68,11 @@ function scheduleIdle(callback: () => void) {
const browserWindow = window as WindowWithIdleCallback; const browserWindow = window as WindowWithIdleCallback;
if (browserWindow.requestIdleCallback) { if (browserWindow.requestIdleCallback) {
browserWindow.requestIdleCallback(callback, { timeout: 3000 }); browserWindow.requestIdleCallback(callback, { timeout: 8000 });
return; return;
} }
window.setTimeout(callback, 1200); window.setTimeout(callback, 8000);
} }
function canPrefetch() { function canPrefetch() {
@@ -160,7 +160,7 @@ function prefetchAiPlatformPages(currentPath = '') {
} }
if (isSystemRoute(currentPath)) { if (isSystemRoute(currentPath)) {
delayedPrefetch(`core:${currentPath}`, corePrefetchTasks, 8000); delayedPrefetch(`core:${currentPath}`, corePrefetchTasks, 18_000);
return; return;
} }
@@ -168,7 +168,7 @@ function prefetchAiPlatformPages(currentPath = '') {
delayedPrefetch( delayedPrefetch(
`agent-chat:${currentPath}`, `agent-chat:${currentPath}`,
[aiPrefetchTasks.agentChat, aiPrefetchTasks.aiChatPanel], [aiPrefetchTasks.agentChat, aiPrefetchTasks.aiChatPanel],
6000, 14_000,
); );
return; return;
} }
@@ -177,12 +177,12 @@ function prefetchAiPlatformPages(currentPath = '') {
delayedPrefetch( delayedPrefetch(
`ai:${currentPath}`, `ai:${currentPath}`,
getAiRoutePrefetchTasks(currentPath), getAiRoutePrefetchTasks(currentPath),
6000, 14_000,
); );
return; return;
} }
delayedPrefetch('core:home', corePrefetchTasks, 10_000); delayedPrefetch('core:home', corePrefetchTasks, 22_000);
} }
export { prefetchAiPlatformPages }; export { prefetchAiPlatformPages };
+5 -1
View File
@@ -18,6 +18,10 @@ async function setupAdminRuntime() {
initialized = true; initialized = true;
} }
function isAdminRuntimeReady() {
return initialized;
}
async function ensureAdminRuntime() { async function ensureAdminRuntime() {
if (initialized) return; if (initialized) return;
@@ -28,4 +32,4 @@ async function ensureAdminRuntime() {
await initializing; await initializing;
} }
export { ensureAdminRuntime }; export { ensureAdminRuntime, isAdminRuntimeReady };