perf(dashboard): reduce first-screen load (lazy-load VAD/Monaco/i18n/ApexCharts, gzip responses) (#10413)

* perf(dashboard): lazy-load VAD dependencies on demand

Load onnxruntime-web and vad-web via dynamic script injection when the
VAD initializes instead of render-blocking script tags in index.html,
so they no longer delay the first paint on every page load.

* perf(dashboard): shrink first-screen bundle and gzip responses

- Lazy-load Monaco Editor (~3.3 MB) via an async wrapper component
  instead of importing it in main.ts; the editor bundle now loads only
  when an editor is first mounted.
- Load i18n locale data on demand: each language becomes its own async
  chunk instead of bundling all four locales (~1.4 MB of JSON) into the
  entry.
- Register vue3-apexcharts locally in StatsPage instead of globally in
  main.ts, moving ~470 KB out of the entry chunk.
- Add Starlette GZipMiddleware to the dashboard ASGI app so static
  assets and API responses are compressed (SSE is excluded by default).

Entry chunk: 4.5 MB -> 648 KB (gzip: 1.25 MB -> 210 KB).
This commit is contained in:
Soulter
2026-10-06 15:08:06 +08:00
committed by GitHub
parent 55c2dd3d43
commit 93e446e9f7
23 changed files with 515 additions and 450 deletions
+4
View File
@@ -5,6 +5,7 @@ from types import SimpleNamespace
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
from starlette.exceptions import HTTPException as StarletteHTTPException
from starlette.middleware.gzip import GZipMiddleware
from astrbot.core import DEMO_MODE, LogBroker
from astrbot.core.core_lifecycle import AstrBotCoreLifecycle
@@ -95,6 +96,9 @@ def create_dashboard_asgi_app(
docs_url=f"{API_V1_PREFIX}/docs",
redoc_url=f"{API_V1_PREFIX}/redoc",
)
# Compress JS/CSS and other sizable responses; Starlette's GZipMiddleware
# skips text/event-stream, so SSE endpoints are unaffected.
app.add_middleware(GZipMiddleware, minimum_size=1024)
app.state.core_lifecycle = core_lifecycle
app.state.db = db
app.state.jwt_secret = jwt_secret
-3
View File
@@ -11,9 +11,6 @@
rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Outfit&family=Noto+Sans:wght@100..900&display=swap"
/>
<!-- VAD (Voice Activity Detection) Libraries -->
<script src="https://cdn.jsdelivr.net/npm/onnxruntime-web@1.22.0/dist/ort.wasm.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@ricky0123/vad-web@0.0.29/dist/bundle.min.js"></script>
<title>AstrBot Dashboard</title>
</head>
<body>
@@ -429,7 +429,7 @@
</template>
<script>
import { VueMonacoEditor } from '@guolao/vue-monaco-editor';
import { LazyMonacoEditor as VueMonacoEditor } from '@/components/shared/LazyMonacoEditor';
import { mcpApi } from '@/api/v1';
import { useI18n, useModuleI18n } from '@/i18n/composables';
import OutlinedActionListItem from '@/components/shared/OutlinedActionListItem.vue';
@@ -959,7 +959,7 @@
<script>
import { computed, nextTick, onMounted, reactive, ref, watch } from "vue";
import { VueMonacoEditor } from "@guolao/vue-monaco-editor";
import { LazyMonacoEditor as VueMonacoEditor } from '@/components/shared/LazyMonacoEditor';
import { skillApi, systemConfigApi } from "@/api/v1";
import { useI18n, useModuleI18n } from "@/i18n/composables";
import OutlinedActionListItem from "@/components/shared/OutlinedActionListItem.vue";
@@ -1,5 +1,5 @@
<script setup>
import { VueMonacoEditor } from '@guolao/vue-monaco-editor'
import { LazyMonacoEditor as VueMonacoEditor } from '@/components/shared/LazyMonacoEditor';
import { ref, computed } from 'vue'
import ConfigItemRenderer from './ConfigItemRenderer.vue'
import ConfigDefaultReset from './ConfigDefaultReset.vue'
@@ -1,6 +1,6 @@
<script setup>
import MarkdownIt from 'markdown-it'
import { VueMonacoEditor } from '@guolao/vue-monaco-editor'
import { LazyMonacoEditor as VueMonacoEditor } from '@/components/shared/LazyMonacoEditor';
import { ref, computed } from 'vue'
import ConfigItemRenderer from './ConfigItemRenderer.vue'
import TemplateListEditor from './TemplateListEditor.vue'
@@ -252,7 +252,7 @@
</template>
<script setup>
import { VueMonacoEditor } from '@guolao/vue-monaco-editor'
import { LazyMonacoEditor as VueMonacoEditor } from '@/components/shared/LazyMonacoEditor';
import ListConfigItem from './ListConfigItem.vue'
import FileConfigItem from './FileConfigItem.vue'
import ObjectEditor from './ObjectEditor.vue'
@@ -0,0 +1,12 @@
import { defineAsyncComponent } from 'vue';
import { setupMonaco } from '@/plugins/monaco';
/**
* Drop-in replacement for VueMonacoEditor that loads the Monaco bundle
* lazily on first render instead of from the entry chunk.
*/
export const LazyMonacoEditor = defineAsyncComponent(async () => {
const { VueMonacoEditor, loader } = await import('@guolao/vue-monaco-editor');
await setupMonaco(loader);
return VueMonacoEditor;
});
@@ -246,7 +246,7 @@
<script setup>
import { ref, computed, nextTick, watch } from 'vue'
import { VueMonacoEditor } from '@guolao/vue-monaco-editor'
import { LazyMonacoEditor as VueMonacoEditor } from '@/components/shared/LazyMonacoEditor';
import { useI18n, useModuleI18n } from '@/i18n/composables'
import { useToast } from '@/utils/toast'
import { statsApi, t2iApi } from '@/api/v1'
+44 -3
View File
@@ -24,6 +24,40 @@ interface VADInstance {
listening: boolean;
}
const VAD_BASE_ASSET_PATH = 'https://cdn.jsdelivr.net/npm/@ricky0123/vad-web@0.0.29/dist/';
const VAD_ONNX_WASM_BASE_PATH = 'https://cdn.jsdelivr.net/npm/onnxruntime-web@1.22.0/dist/';
const VAD_SCRIPT_URLS = [
`${VAD_ONNX_WASM_BASE_PATH}ort.wasm.min.js`,
`${VAD_BASE_ASSET_PATH}bundle.min.js`
];
let vadScriptsPromise: Promise<void> | null = null;
/**
* Lazily inject the VAD dependency scripts so they don't block the first paint.
* Scripts must load sequentially because vad-web expects onnxruntime on window.
*/
function ensureVADScriptsLoaded(): Promise<void> {
if (!vadScriptsPromise) {
vadScriptsPromise = (async () => {
for (const src of VAD_SCRIPT_URLS) {
await new Promise<void>((resolve, reject) => {
const script = document.createElement('script');
script.src = src;
script.onload = () => resolve();
script.onerror = () => reject(new Error(`Failed to load VAD dependency: ${src}`));
document.head.appendChild(script);
});
}
})();
// Allow retrying on the next init attempt if loading failed.
vadScriptsPromise.catch(() => {
vadScriptsPromise = null;
});
}
return vadScriptsPromise;
}
// 声明全局 vad 对象类型
declare global {
interface Window {
@@ -52,8 +86,15 @@ export function useVADRecording() {
// 初始化 VAD
async function initVAD() {
try {
await ensureVADScriptsLoaded();
} catch (error) {
console.error('Failed to load VAD library:', error);
return;
}
if (!window.vad) {
console.error('VAD library not loaded. Please ensure the scripts are included in index.html');
console.error('VAD library not loaded.');
return;
}
@@ -102,8 +143,8 @@ export function useVADRecording() {
minSpeechMs: 400,
submitUserSpeechOnPause: false,
model: 'v5',
baseAssetPath: 'https://cdn.jsdelivr.net/npm/@ricky0123/vad-web@0.0.29/dist/',
onnxWASMBasePath: 'https://cdn.jsdelivr.net/npm/onnxruntime-web@1.22.0/dist/'
baseAssetPath: VAD_BASE_ASSET_PATH,
onnxWASMBasePath: VAD_ONNX_WASM_BASE_PATH
});
isInitialized.value = true;
+9 -18
View File
@@ -1,5 +1,5 @@
import { ref, computed } from 'vue';
import { translations as staticTranslations } from './translations';
import { localeLoaders } from './translations';
import type { Locale } from './types';
// 全局状态
@@ -16,31 +16,22 @@ export async function initI18n(locale: Locale = 'zh-CN') {
}
// 加载静态翻译数据
loadTranslations(locale);
await loadTranslations(locale);
}
/**
* 加载翻译数据(现在从静态导入获取)
* 按需加载指定语言的翻译数据(每个语言包是独立的异步 chunk)
*/
function loadTranslations(locale: Locale) {
async function loadTranslations(locale: Locale) {
try {
const data = staticTranslations[locale];
if (data) {
translations.value = data;
} else {
console.warn(`Translations not found for locale: ${locale}`);
// 回退到中文
if (locale !== 'zh-CN') {
console.log('Falling back to zh-CN');
translations.value = staticTranslations['zh-CN'];
}
}
const loader = localeLoaders[locale] ?? localeLoaders['zh-CN'];
translations.value = await loader();
} catch (error) {
console.error(`Failed to load translations for ${locale}:`, error);
// 回退到中文
if (locale !== 'zh-CN') {
console.log('Falling back to zh-CN');
translations.value = staticTranslations['zh-CN'];
translations.value = await localeLoaders['zh-CN']();
}
}
}
@@ -91,7 +82,7 @@ export function useI18n() {
if (typeof document !== 'undefined') {
document.documentElement.lang = newLocale;
}
loadTranslations(newLocale);
await loadTranslations(newLocale);
// 保存到localStorage
localStorage.setItem('astrbot-locale', newLocale);
@@ -233,4 +224,4 @@ export async function setupI18n() {
: 'zh-CN';
await initI18n(initialLocale);
}
}
+88
View File
@@ -0,0 +1,88 @@
// Assembled translations for en-US (lazy-loaded per locale)
import enUSCommon from './core/common.json';
import enUSActions from './core/actions.json';
import enUSStatus from './core/status.json';
import enUSNavigation from './core/navigation.json';
import enUSHeader from './core/header.json';
import enUSShared from './core/shared.json';
import enUSChat from './features/chat.json';
import enUSExtension from './features/extension.json';
import enUSConversation from './features/conversation.json';
import enUSSessionManagement from './features/session-management.json';
import enUSToolUse from './features/tool-use.json';
import enUSProvider from './features/provider.json';
import enUSPlatform from './features/platform.json';
import enUSConfig from './features/config.json';
import enUSConfigMetadata from './features/config-metadata.json';
import enUSConsole from './features/console.json';
import enUSTrace from './features/trace.json';
import enUSAbout from './features/about.json';
import enUSSettings from './features/settings.json';
import enUSAuth from './features/auth.json';
import enUSChart from './features/chart.json';
import enUSDashboard from './features/dashboard.json';
import enUSCron from './features/cron.json';
import enUSStats from './features/stats.json';
import enUSAlkaidIndex from './features/alkaid/index.json';
import enUSAlkaidKnowledgeBase from './features/alkaid/knowledge-base.json';
import enUSAlkaidMemory from './features/alkaid/memory.json';
import enUSKnowledgeBaseIndex from './features/knowledge-base/index.json';
import enUSKnowledgeBaseDetail from './features/knowledge-base/detail.json';
import enUSKnowledgeBaseDocument from './features/knowledge-base/document.json';
import enUSPersona from './features/persona.json';
import enUSCommand from './features/command.json';
import enUSSubagent from './features/subagent.json';
import enUSWelcome from './features/welcome.json';
import enUSErrors from './messages/errors.json';
import enUSSuccess from './messages/success.json';
import enUSValidation from './messages/validation.json';
export default {
core: {
common: enUSCommon,
actions: enUSActions,
status: enUSStatus,
navigation: enUSNavigation,
header: enUSHeader,
shared: enUSShared
},
features: {
chat: enUSChat,
extension: enUSExtension,
conversation: enUSConversation,
'session-management': enUSSessionManagement,
tooluse: enUSToolUse,
provider: enUSProvider,
platform: enUSPlatform,
config: enUSConfig,
'config-metadata': enUSConfigMetadata,
console: enUSConsole,
trace: enUSTrace,
about: enUSAbout,
settings: enUSSettings,
auth: enUSAuth,
chart: enUSChart,
dashboard: enUSDashboard,
cron: enUSCron,
stats: enUSStats,
alkaid: {
index: enUSAlkaidIndex,
'knowledge-base': enUSAlkaidKnowledgeBase,
memory: enUSAlkaidMemory
},
'knowledge-base': {
index: enUSKnowledgeBaseIndex,
detail: enUSKnowledgeBaseDetail,
document: enUSKnowledgeBaseDocument
},
persona: enUSPersona,
command: enUSCommand,
subagent: enUSSubagent,
welcome: enUSWelcome
},
messages: {
errors: enUSErrors,
success: enUSSuccess,
validation: enUSValidation
}
};
+88
View File
@@ -0,0 +1,88 @@
// Assembled translations for ja-JP (lazy-loaded per locale)
import jaJPCommon from './core/common.json';
import jaJPActions from './core/actions.json';
import jaJPStatus from './core/status.json';
import jaJPNavigation from './core/navigation.json';
import jaJPHeader from './core/header.json';
import jaJPShared from './core/shared.json';
import jaJPChat from './features/chat.json';
import jaJPExtension from './features/extension.json';
import jaJPConversation from './features/conversation.json';
import jaJPSessionManagement from './features/session-management.json';
import jaJPToolUse from './features/tool-use.json';
import jaJPProvider from './features/provider.json';
import jaJPPlatform from './features/platform.json';
import jaJPConfig from './features/config.json';
import jaJPConfigMetadata from './features/config-metadata.json';
import jaJPConsole from './features/console.json';
import jaJPTrace from './features/trace.json';
import jaJPAbout from './features/about.json';
import jaJPSettings from './features/settings.json';
import jaJPAuth from './features/auth.json';
import jaJPChart from './features/chart.json';
import jaJPDashboard from './features/dashboard.json';
import jaJPCron from './features/cron.json';
import jaJPStats from './features/stats.json';
import jaJPAlkaidIndex from './features/alkaid/index.json';
import jaJPAlkaidKnowledgeBase from './features/alkaid/knowledge-base.json';
import jaJPAlkaidMemory from './features/alkaid/memory.json';
import jaJPKnowledgeBaseIndex from './features/knowledge-base/index.json';
import jaJPKnowledgeBaseDetail from './features/knowledge-base/detail.json';
import jaJPKnowledgeBaseDocument from './features/knowledge-base/document.json';
import jaJPPersona from './features/persona.json';
import jaJPCommand from './features/command.json';
import jaJPSubagent from './features/subagent.json';
import jaJPWelcome from './features/welcome.json';
import jaJPErrors from './messages/errors.json';
import jaJPSuccess from './messages/success.json';
import jaJPValidation from './messages/validation.json';
export default {
core: {
common: jaJPCommon,
actions: jaJPActions,
status: jaJPStatus,
navigation: jaJPNavigation,
header: jaJPHeader,
shared: jaJPShared
},
features: {
chat: jaJPChat,
extension: jaJPExtension,
conversation: jaJPConversation,
'session-management': jaJPSessionManagement,
tooluse: jaJPToolUse,
provider: jaJPProvider,
platform: jaJPPlatform,
config: jaJPConfig,
'config-metadata': jaJPConfigMetadata,
console: jaJPConsole,
trace: jaJPTrace,
about: jaJPAbout,
settings: jaJPSettings,
auth: jaJPAuth,
chart: jaJPChart,
dashboard: jaJPDashboard,
cron: jaJPCron,
stats: jaJPStats,
alkaid: {
index: jaJPAlkaidIndex,
'knowledge-base': jaJPAlkaidKnowledgeBase,
memory: jaJPAlkaidMemory
},
'knowledge-base': {
index: jaJPKnowledgeBaseIndex,
detail: jaJPKnowledgeBaseDetail,
document: jaJPKnowledgeBaseDocument
},
persona: jaJPPersona,
command: jaJPCommand,
subagent: jaJPSubagent,
welcome: jaJPWelcome
},
messages: {
errors: jaJPErrors,
success: jaJPSuccess,
validation: jaJPValidation
}
};
+88
View File
@@ -0,0 +1,88 @@
// Assembled translations for ru-RU (lazy-loaded per locale)
import ruRUCommon from './core/common.json';
import ruRUActions from './core/actions.json';
import ruRUStatus from './core/status.json';
import ruRUNavigation from './core/navigation.json';
import ruRUHeader from './core/header.json';
import ruRUShared from './core/shared.json';
import ruRUChat from './features/chat.json';
import ruRUExtension from './features/extension.json';
import ruRUConversation from './features/conversation.json';
import ruRUSessionManagement from './features/session-management.json';
import ruRUToolUse from './features/tool-use.json';
import ruRUProvider from './features/provider.json';
import ruRUPlatform from './features/platform.json';
import ruRUConfig from './features/config.json';
import ruRUConfigMetadata from './features/config-metadata.json';
import ruRUConsole from './features/console.json';
import ruRUTrace from './features/trace.json';
import ruRUAbout from './features/about.json';
import ruRUSettings from './features/settings.json';
import ruRUAuth from './features/auth.json';
import ruRUChart from './features/chart.json';
import ruRUDashboard from './features/dashboard.json';
import ruRUCron from './features/cron.json';
import ruRUStats from './features/stats.json';
import ruRUAlkaidIndex from './features/alkaid/index.json';
import ruRUAlkaidKnowledgeBase from './features/alkaid/knowledge-base.json';
import ruRUAlkaidMemory from './features/alkaid/memory.json';
import ruRUKnowledgeBaseIndex from './features/knowledge-base/index.json';
import ruRUKnowledgeBaseDetail from './features/knowledge-base/detail.json';
import ruRUKnowledgeBaseDocument from './features/knowledge-base/document.json';
import ruRUPersona from './features/persona.json';
import ruRUCommand from './features/command.json';
import ruRUSubagent from './features/subagent.json';
import ruRUWelcome from './features/welcome.json';
import ruRUErrors from './messages/errors.json';
import ruRUSuccess from './messages/success.json';
import ruRUValidation from './messages/validation.json';
export default {
core: {
common: ruRUCommon,
actions: ruRUActions,
status: ruRUStatus,
navigation: ruRUNavigation,
header: ruRUHeader,
shared: ruRUShared
},
features: {
chat: ruRUChat,
extension: ruRUExtension,
conversation: ruRUConversation,
'session-management': ruRUSessionManagement,
tooluse: ruRUToolUse,
provider: ruRUProvider,
platform: ruRUPlatform,
config: ruRUConfig,
'config-metadata': ruRUConfigMetadata,
console: ruRUConsole,
trace: ruRUTrace,
about: ruRUAbout,
settings: ruRUSettings,
auth: ruRUAuth,
chart: ruRUChart,
dashboard: ruRUDashboard,
cron: ruRUCron,
stats: ruRUStats,
alkaid: {
index: ruRUAlkaidIndex,
'knowledge-base': ruRUAlkaidKnowledgeBase,
memory: ruRUAlkaidMemory
},
'knowledge-base': {
index: ruRUKnowledgeBaseIndex,
detail: ruRUKnowledgeBaseDetail,
document: ruRUKnowledgeBaseDocument
},
persona: ruRUPersona,
command: ruRUCommand,
subagent: ruRUSubagent,
welcome: ruRUWelcome
},
messages: {
errors: ruRUErrors,
success: ruRUSuccess,
validation: ruRUValidation
}
};
+88
View File
@@ -0,0 +1,88 @@
// Assembled translations for zh-CN (lazy-loaded per locale)
import zhCNCommon from './core/common.json';
import zhCNActions from './core/actions.json';
import zhCNStatus from './core/status.json';
import zhCNNavigation from './core/navigation.json';
import zhCNHeader from './core/header.json';
import zhCNShared from './core/shared.json';
import zhCNChat from './features/chat.json';
import zhCNExtension from './features/extension.json';
import zhCNConversation from './features/conversation.json';
import zhCNSessionManagement from './features/session-management.json';
import zhCNToolUse from './features/tool-use.json';
import zhCNProvider from './features/provider.json';
import zhCNPlatform from './features/platform.json';
import zhCNConfig from './features/config.json';
import zhCNConfigMetadata from './features/config-metadata.json';
import zhCNConsole from './features/console.json';
import zhCNTrace from './features/trace.json';
import zhCNAbout from './features/about.json';
import zhCNSettings from './features/settings.json';
import zhCNAuth from './features/auth.json';
import zhCNChart from './features/chart.json';
import zhCNDashboard from './features/dashboard.json';
import zhCNCron from './features/cron.json';
import zhCNStats from './features/stats.json';
import zhCNAlkaidIndex from './features/alkaid/index.json';
import zhCNAlkaidKnowledgeBase from './features/alkaid/knowledge-base.json';
import zhCNAlkaidMemory from './features/alkaid/memory.json';
import zhCNKnowledgeBaseIndex from './features/knowledge-base/index.json';
import zhCNKnowledgeBaseDetail from './features/knowledge-base/detail.json';
import zhCNKnowledgeBaseDocument from './features/knowledge-base/document.json';
import zhCNPersona from './features/persona.json';
import zhCNCommand from './features/command.json';
import zhCNSubagent from './features/subagent.json';
import zhCNWelcome from './features/welcome.json';
import zhCNErrors from './messages/errors.json';
import zhCNSuccess from './messages/success.json';
import zhCNValidation from './messages/validation.json';
export default {
core: {
common: zhCNCommon,
actions: zhCNActions,
status: zhCNStatus,
navigation: zhCNNavigation,
header: zhCNHeader,
shared: zhCNShared
},
features: {
chat: zhCNChat,
extension: zhCNExtension,
conversation: zhCNConversation,
'session-management': zhCNSessionManagement,
tooluse: zhCNToolUse,
provider: zhCNProvider,
platform: zhCNPlatform,
config: zhCNConfig,
'config-metadata': zhCNConfigMetadata,
console: zhCNConsole,
trace: zhCNTrace,
about: zhCNAbout,
settings: zhCNSettings,
auth: zhCNAuth,
chart: zhCNChart,
dashboard: zhCNDashboard,
cron: zhCNCron,
stats: zhCNStats,
alkaid: {
index: zhCNAlkaidIndex,
'knowledge-base': zhCNAlkaidKnowledgeBase,
memory: zhCNAlkaidMemory
},
'knowledge-base': {
index: zhCNKnowledgeBaseIndex,
detail: zhCNKnowledgeBaseDetail,
document: zhCNKnowledgeBaseDocument
},
persona: zhCNPersona,
command: zhCNCommand,
subagent: zhCNSubagent,
welcome: zhCNWelcome
},
messages: {
errors: zhCNErrors,
success: zhCNSuccess,
validation: zhCNValidation
}
};
+12 -364
View File
@@ -1,368 +1,16 @@
// 静态导入所有翻译文件
// 这种方式确保构建时所有翻译都会被正确打包
// Per-locale translation loaders.
// Each locale is bundled into its own async chunk so only the active
// language is downloaded instead of all of them upfront.
// 中文翻译
import zhCNCommon from './locales/zh-CN/core/common.json';
import zhCNActions from './locales/zh-CN/core/actions.json';
import zhCNStatus from './locales/zh-CN/core/status.json';
import zhCNNavigation from './locales/zh-CN/core/navigation.json';
import zhCNHeader from './locales/zh-CN/core/header.json';
import zhCNShared from './locales/zh-CN/core/shared.json';
import type zhCN from './locales/zh-CN';
import zhCNChat from './locales/zh-CN/features/chat.json';
import zhCNExtension from './locales/zh-CN/features/extension.json';
import zhCNConversation from './locales/zh-CN/features/conversation.json';
import zhCNSessionManagement from './locales/zh-CN/features/session-management.json';
import zhCNToolUse from './locales/zh-CN/features/tool-use.json';
import zhCNProvider from './locales/zh-CN/features/provider.json';
import zhCNPlatform from './locales/zh-CN/features/platform.json';
import zhCNConfig from './locales/zh-CN/features/config.json';
import zhCNConfigMetadata from './locales/zh-CN/features/config-metadata.json';
import zhCNConsole from './locales/zh-CN/features/console.json';
import zhCNTrace from './locales/zh-CN/features/trace.json';
import zhCNAbout from './locales/zh-CN/features/about.json';
import zhCNSettings from './locales/zh-CN/features/settings.json';
import zhCNAuth from './locales/zh-CN/features/auth.json';
import zhCNChart from './locales/zh-CN/features/chart.json';
import zhCNDashboard from './locales/zh-CN/features/dashboard.json';
import zhCNCron from './locales/zh-CN/features/cron.json';
import zhCNStats from './locales/zh-CN/features/stats.json';
import zhCNAlkaidIndex from './locales/zh-CN/features/alkaid/index.json';
import zhCNAlkaidKnowledgeBase from './locales/zh-CN/features/alkaid/knowledge-base.json';
import zhCNAlkaidMemory from './locales/zh-CN/features/alkaid/memory.json';
import zhCNKnowledgeBaseIndex from './locales/zh-CN/features/knowledge-base/index.json';
import zhCNKnowledgeBaseDetail from './locales/zh-CN/features/knowledge-base/detail.json';
import zhCNKnowledgeBaseDocument from './locales/zh-CN/features/knowledge-base/document.json';
import zhCNPersona from './locales/zh-CN/features/persona.json';
import zhCNCommand from './locales/zh-CN/features/command.json';
import zhCNSubagent from './locales/zh-CN/features/subagent.json';
import zhCNWelcome from './locales/zh-CN/features/welcome.json';
export type Translations = typeof zhCN;
import zhCNErrors from './locales/zh-CN/messages/errors.json';
import zhCNSuccess from './locales/zh-CN/messages/success.json';
import zhCNValidation from './locales/zh-CN/messages/validation.json';
export const localeLoaders = {
'zh-CN': () => import('./locales/zh-CN').then((m) => m.default),
'en-US': () => import('./locales/en-US').then((m) => m.default),
'ru-RU': () => import('./locales/ru-RU').then((m) => m.default),
'ja-JP': () => import('./locales/ja-JP').then((m) => m.default)
} as const;
// English translation
import enUSCommon from './locales/en-US/core/common.json';
import enUSActions from './locales/en-US/core/actions.json';
import enUSStatus from './locales/en-US/core/status.json';
import enUSNavigation from './locales/en-US/core/navigation.json';
import enUSHeader from './locales/en-US/core/header.json';
import enUSShared from './locales/en-US/core/shared.json';
import enUSChat from './locales/en-US/features/chat.json';
import enUSExtension from './locales/en-US/features/extension.json';
import enUSConversation from './locales/en-US/features/conversation.json';
import enUSSessionManagement from './locales/en-US/features/session-management.json';
import enUSToolUse from './locales/en-US/features/tool-use.json';
import enUSProvider from './locales/en-US/features/provider.json';
import enUSPlatform from './locales/en-US/features/platform.json';
import enUSConfig from './locales/en-US/features/config.json';
import enUSConfigMetadata from './locales/en-US/features/config-metadata.json';
import enUSConsole from './locales/en-US/features/console.json';
import enUSTrace from './locales/en-US/features/trace.json';
import enUSAbout from './locales/en-US/features/about.json';
import enUSSettings from './locales/en-US/features/settings.json';
import enUSAuth from './locales/en-US/features/auth.json';
import enUSChart from './locales/en-US/features/chart.json';
import enUSDashboard from './locales/en-US/features/dashboard.json';
import enUSCron from './locales/en-US/features/cron.json';
import enUSStats from './locales/en-US/features/stats.json';
import enUSAlkaidIndex from './locales/en-US/features/alkaid/index.json';
import enUSAlkaidKnowledgeBase from './locales/en-US/features/alkaid/knowledge-base.json';
import enUSAlkaidMemory from './locales/en-US/features/alkaid/memory.json';
import enUSKnowledgeBaseIndex from './locales/en-US/features/knowledge-base/index.json';
import enUSKnowledgeBaseDetail from './locales/en-US/features/knowledge-base/detail.json';
import enUSKnowledgeBaseDocument from './locales/en-US/features/knowledge-base/document.json';
import enUSPersona from './locales/en-US/features/persona.json';
import enUSCommand from './locales/en-US/features/command.json';
import enUSSubagent from './locales/en-US/features/subagent.json';
import enUSWelcome from './locales/en-US/features/welcome.json';
import enUSErrors from './locales/en-US/messages/errors.json';
import enUSSuccess from './locales/en-US/messages/success.json';
import enUSValidation from './locales/en-US/messages/validation.json';
// Russian translation
import ruRUCommon from './locales/ru-RU/core/common.json';
import ruRUActions from './locales/ru-RU/core/actions.json';
import ruRUStatus from './locales/ru-RU/core/status.json';
import ruRUNavigation from './locales/ru-RU/core/navigation.json';
import ruRUHeader from './locales/ru-RU/core/header.json';
import ruRUShared from './locales/ru-RU/core/shared.json';
import ruRUChat from './locales/ru-RU/features/chat.json';
import ruRUExtension from './locales/ru-RU/features/extension.json';
import ruRUConversation from './locales/ru-RU/features/conversation.json';
import ruRUSessionManagement from './locales/ru-RU/features/session-management.json';
import ruRUToolUse from './locales/ru-RU/features/tool-use.json';
import ruRUProvider from './locales/ru-RU/features/provider.json';
import ruRUPlatform from './locales/ru-RU/features/platform.json';
import ruRUConfig from './locales/ru-RU/features/config.json';
import ruRUConfigMetadata from './locales/ru-RU/features/config-metadata.json';
import ruRUConsole from './locales/ru-RU/features/console.json';
import ruRUTrace from './locales/ru-RU/features/trace.json';
import ruRUAbout from './locales/ru-RU/features/about.json';
import ruRUSettings from './locales/ru-RU/features/settings.json';
import ruRUAuth from './locales/ru-RU/features/auth.json';
import ruRUChart from './locales/ru-RU/features/chart.json';
import ruRUDashboard from './locales/ru-RU/features/dashboard.json';
import ruRUCron from './locales/ru-RU/features/cron.json';
import ruRUStats from './locales/ru-RU/features/stats.json';
import ruRUAlkaidIndex from './locales/ru-RU/features/alkaid/index.json';
import ruRUAlkaidKnowledgeBase from './locales/ru-RU/features/alkaid/knowledge-base.json';
import ruRUAlkaidMemory from './locales/ru-RU/features/alkaid/memory.json';
import ruRUKnowledgeBaseIndex from './locales/ru-RU/features/knowledge-base/index.json';
import ruRUKnowledgeBaseDetail from './locales/ru-RU/features/knowledge-base/detail.json';
import ruRUKnowledgeBaseDocument from './locales/ru-RU/features/knowledge-base/document.json';
import ruRUPersona from './locales/ru-RU/features/persona.json';
import ruRUCommand from './locales/ru-RU/features/command.json';
import ruRUSubagent from './locales/ru-RU/features/subagent.json';
import ruRUWelcome from './locales/ru-RU/features/welcome.json';
import ruRUErrors from './locales/ru-RU/messages/errors.json';
import ruRUSuccess from './locales/ru-RU/messages/success.json';
import ruRUValidation from './locales/ru-RU/messages/validation.json';
// Japanese translation
import jaJPCommon from './locales/ja-JP/core/common.json';
import jaJPActions from './locales/ja-JP/core/actions.json';
import jaJPStatus from './locales/ja-JP/core/status.json';
import jaJPNavigation from './locales/ja-JP/core/navigation.json';
import jaJPHeader from './locales/ja-JP/core/header.json';
import jaJPShared from './locales/ja-JP/core/shared.json';
import jaJPChat from './locales/ja-JP/features/chat.json';
import jaJPExtension from './locales/ja-JP/features/extension.json';
import jaJPConversation from './locales/ja-JP/features/conversation.json';
import jaJPSessionManagement from './locales/ja-JP/features/session-management.json';
import jaJPToolUse from './locales/ja-JP/features/tool-use.json';
import jaJPProvider from './locales/ja-JP/features/provider.json';
import jaJPPlatform from './locales/ja-JP/features/platform.json';
import jaJPConfig from './locales/ja-JP/features/config.json';
import jaJPConfigMetadata from './locales/ja-JP/features/config-metadata.json';
import jaJPConsole from './locales/ja-JP/features/console.json';
import jaJPTrace from './locales/ja-JP/features/trace.json';
import jaJPAbout from './locales/ja-JP/features/about.json';
import jaJPSettings from './locales/ja-JP/features/settings.json';
import jaJPAuth from './locales/ja-JP/features/auth.json';
import jaJPChart from './locales/ja-JP/features/chart.json';
import jaJPDashboard from './locales/ja-JP/features/dashboard.json';
import jaJPCron from './locales/ja-JP/features/cron.json';
import jaJPStats from './locales/ja-JP/features/stats.json';
import jaJPAlkaidIndex from './locales/ja-JP/features/alkaid/index.json';
import jaJPAlkaidKnowledgeBase from './locales/ja-JP/features/alkaid/knowledge-base.json';
import jaJPAlkaidMemory from './locales/ja-JP/features/alkaid/memory.json';
import jaJPKnowledgeBaseIndex from './locales/ja-JP/features/knowledge-base/index.json';
import jaJPKnowledgeBaseDetail from './locales/ja-JP/features/knowledge-base/detail.json';
import jaJPKnowledgeBaseDocument from './locales/ja-JP/features/knowledge-base/document.json';
import jaJPPersona from './locales/ja-JP/features/persona.json';
import jaJPCommand from './locales/ja-JP/features/command.json';
import jaJPSubagent from './locales/ja-JP/features/subagent.json';
import jaJPWelcome from './locales/ja-JP/features/welcome.json';
import jaJPErrors from './locales/ja-JP/messages/errors.json';
import jaJPSuccess from './locales/ja-JP/messages/success.json';
import jaJPValidation from './locales/ja-JP/messages/validation.json';
// 组装翻译对象
export const translations = {
'zh-CN': {
core: {
common: zhCNCommon,
actions: zhCNActions,
status: zhCNStatus,
navigation: zhCNNavigation,
header: zhCNHeader,
shared: zhCNShared
},
features: {
chat: zhCNChat,
extension: zhCNExtension,
conversation: zhCNConversation,
'session-management': zhCNSessionManagement,
tooluse: zhCNToolUse,
provider: zhCNProvider,
platform: zhCNPlatform,
config: zhCNConfig,
'config-metadata': zhCNConfigMetadata,
console: zhCNConsole,
trace: zhCNTrace,
about: zhCNAbout,
settings: zhCNSettings,
auth: zhCNAuth,
chart: zhCNChart,
dashboard: zhCNDashboard,
cron: zhCNCron,
stats: zhCNStats,
alkaid: {
index: zhCNAlkaidIndex,
'knowledge-base': zhCNAlkaidKnowledgeBase,
memory: zhCNAlkaidMemory
},
'knowledge-base': {
index: zhCNKnowledgeBaseIndex,
detail: zhCNKnowledgeBaseDetail,
document: zhCNKnowledgeBaseDocument
},
persona: zhCNPersona,
command: zhCNCommand,
subagent: zhCNSubagent,
welcome: zhCNWelcome
},
messages: {
errors: zhCNErrors,
success: zhCNSuccess,
validation: zhCNValidation
}
},
'en-US': {
core: {
common: enUSCommon,
actions: enUSActions,
status: enUSStatus,
navigation: enUSNavigation,
header: enUSHeader,
shared: enUSShared
},
features: {
chat: enUSChat,
extension: enUSExtension,
conversation: enUSConversation,
'session-management': enUSSessionManagement,
tooluse: enUSToolUse,
provider: enUSProvider,
platform: enUSPlatform,
config: enUSConfig,
'config-metadata': enUSConfigMetadata,
console: enUSConsole,
trace: enUSTrace,
about: enUSAbout,
settings: enUSSettings,
auth: enUSAuth,
chart: enUSChart,
dashboard: enUSDashboard,
cron: enUSCron,
stats: enUSStats,
alkaid: {
index: enUSAlkaidIndex,
'knowledge-base': enUSAlkaidKnowledgeBase,
memory: enUSAlkaidMemory
},
'knowledge-base': {
index: enUSKnowledgeBaseIndex,
detail: enUSKnowledgeBaseDetail,
document: enUSKnowledgeBaseDocument
},
persona: enUSPersona,
command: enUSCommand,
subagent: enUSSubagent,
welcome: enUSWelcome
},
messages: {
errors: enUSErrors,
success: enUSSuccess,
validation: enUSValidation
}
},
'ru-RU': {
core: {
common: ruRUCommon,
actions: ruRUActions,
status: ruRUStatus,
navigation: ruRUNavigation,
header: ruRUHeader,
shared: ruRUShared
},
features: {
chat: ruRUChat,
extension: ruRUExtension,
conversation: ruRUConversation,
'session-management': ruRUSessionManagement,
tooluse: ruRUToolUse,
provider: ruRUProvider,
platform: ruRUPlatform,
config: ruRUConfig,
'config-metadata': ruRUConfigMetadata,
console: ruRUConsole,
trace: ruRUTrace,
about: ruRUAbout,
settings: ruRUSettings,
auth: ruRUAuth,
chart: ruRUChart,
dashboard: ruRUDashboard,
cron: ruRUCron,
stats: ruRUStats,
alkaid: {
index: ruRUAlkaidIndex,
'knowledge-base': ruRUAlkaidKnowledgeBase,
memory: ruRUAlkaidMemory
},
'knowledge-base': {
index: ruRUKnowledgeBaseIndex,
detail: ruRUKnowledgeBaseDetail,
document: ruRUKnowledgeBaseDocument
},
persona: ruRUPersona,
command: ruRUCommand,
subagent: ruRUSubagent,
welcome: ruRUWelcome
},
messages: {
errors: ruRUErrors,
success: ruRUSuccess,
validation: ruRUValidation
}
},
'ja-JP': {
core: {
common: jaJPCommon,
actions: jaJPActions,
status: jaJPStatus,
navigation: jaJPNavigation,
header: jaJPHeader,
shared: jaJPShared
},
features: {
chat: jaJPChat,
extension: jaJPExtension,
conversation: jaJPConversation,
'session-management': jaJPSessionManagement,
tooluse: jaJPToolUse,
provider: jaJPProvider,
platform: jaJPPlatform,
config: jaJPConfig,
'config-metadata': jaJPConfigMetadata,
console: jaJPConsole,
trace: jaJPTrace,
about: jaJPAbout,
settings: jaJPSettings,
auth: jaJPAuth,
chart: jaJPChart,
dashboard: jaJPDashboard,
cron: jaJPCron,
stats: jaJPStats,
alkaid: {
index: jaJPAlkaidIndex,
'knowledge-base': jaJPAlkaidKnowledgeBase,
memory: jaJPAlkaidMemory
},
'knowledge-base': {
index: jaJPKnowledgeBaseIndex,
detail: jaJPKnowledgeBaseDetail,
document: jaJPKnowledgeBaseDocument
},
persona: jaJPPersona,
command: jaJPCommand,
subagent: jaJPSubagent,
welcome: jaJPWelcome
},
messages: {
errors: jaJPErrors,
success: jaJPSuccess,
validation: jaJPValidation
}
}
};
export type TranslationData = typeof translations;
export type Locale = keyof typeof localeLoaders;
+6 -10
View File
@@ -3,14 +3,10 @@
* 国际化类型定义,从JSON文件自动推断,确保类型安全且自动同步
*/
// 直接导入已经组织好的翻译数据
import { translations } from './translations';
// 导出翻译数据常量,供类型推断使用
export const translationData = translations;
// 从实际的翻译数据推断完整的翻译结构类型
export type TranslationSchema = typeof translations[keyof typeof translations];
import type { Locale, Translations } from './translations';
export type TranslationSchema = Translations;
// TypeScript 助手:递归提取嵌套键路径
type NestedKeyOf<T> = T extends object
@@ -24,8 +20,8 @@ type NestedKeyOf<T> = T extends object
// 自动推断的翻译键联合类型 - 包含所有有效的点分隔键路径
export type TranslationKey = NestedKeyOf<TranslationSchema>;
// 语言环境类型 - 从实际的翻译数据键推断
export type Locale = keyof typeof translations;
// 语言环境类型 - 从可用语言加载器推断
export type { Locale } from './translations';
// 翻译函数类型
export type TranslationFunction = {
@@ -126,4 +122,4 @@ declare module '@vue/runtime-core' {
declare module 'vue-i18n' {
export interface DefineLocaleMessage extends TranslationSchema {}
}
*/
*/
-41
View File
@@ -6,49 +6,13 @@ import vuetify from './plugins/vuetify';
import confirmPlugin from './plugins/confirmPlugin';
import { setupI18n } from './i18n/composables';
import '@/scss/style.scss';
import VueApexCharts from 'vue3-apexcharts';
import print from 'vue3-print-nb';
import { loader } from '@guolao/vue-monaco-editor'
import * as monaco from 'monaco-editor/esm/vs/editor/editor.api';
import 'monaco-editor/esm/vs/basic-languages/dockerfile/dockerfile.contribution';
import 'monaco-editor/esm/vs/basic-languages/ini/ini.contribution';
import 'monaco-editor/esm/vs/basic-languages/javascript/javascript.contribution';
import 'monaco-editor/esm/vs/basic-languages/markdown/markdown.contribution';
import 'monaco-editor/esm/vs/basic-languages/powershell/powershell.contribution';
import 'monaco-editor/esm/vs/basic-languages/python/python.contribution';
import 'monaco-editor/esm/vs/basic-languages/shell/shell.contribution';
import 'monaco-editor/esm/vs/basic-languages/sql/sql.contribution';
import 'monaco-editor/esm/vs/basic-languages/typescript/typescript.contribution';
import 'monaco-editor/esm/vs/basic-languages/xml/xml.contribution';
import 'monaco-editor/esm/vs/basic-languages/yaml/yaml.contribution';
import 'monaco-editor/esm/vs/language/css/monaco.contribution';
import 'monaco-editor/esm/vs/language/html/monaco.contribution';
import 'monaco-editor/esm/vs/language/json/monaco.contribution';
import editorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker';
import jsonWorker from 'monaco-editor/esm/vs/language/json/json.worker?worker';
import cssWorker from 'monaco-editor/esm/vs/language/css/css.worker?worker';
import htmlWorker from 'monaco-editor/esm/vs/language/html/html.worker?worker';
import { setupHttpClient } from './api/http';
import { waitForRouterReadyInBackground } from './utils/routerReadiness.mjs';
setupHttpClient();
(self as any).MonacoEnvironment = {
getWorker(_: string, label: string) {
if (label === 'json') {
return new jsonWorker();
}
if (label === 'css' || label === 'scss' || label === 'less') {
return new cssWorker();
}
if (label === 'html' || label === 'handlebars' || label === 'razor') {
return new htmlWorker();
}
return new editorWorker();
},
};
/**
* 挂载后初始化主题并注册全局系统主题监听器。
* 职责:
@@ -119,7 +83,6 @@ setupI18n().then(async () => {
app.use(pinia);
app.use(router);
app.use(print);
app.use(VueApexCharts);
app.use(vuetify);
app.use(confirmPlugin);
await router.isReady();
@@ -135,7 +98,6 @@ setupI18n().then(async () => {
app.use(pinia);
app.use(router);
app.use(print);
app.use(VueApexCharts);
app.use(vuetify);
app.use(confirmPlugin);
app.mount('#app');
@@ -143,6 +105,3 @@ setupI18n().then(async () => {
setupThemeSync(pinia);
});
loader.config({ monaco })
+64
View File
@@ -0,0 +1,64 @@
import type { loader } from '@guolao/vue-monaco-editor';
type MonacoLoader = typeof loader;
let monacoSetupPromise: Promise<void> | null = null;
/**
* Load Monaco Editor on demand and wire it into @guolao/vue-monaco-editor.
* The editor bundle is several MB, so it is kept out of the entry chunk and
* only fetched the first time an editor is actually mounted.
*
* Args:
* loaderInstance: The loader exported by @guolao/vue-monaco-editor.
*
* Returns:
* A promise resolving once Monaco is configured on the loader.
*/
export function setupMonaco(loaderInstance: MonacoLoader): Promise<void> {
if (!monacoSetupPromise) {
monacoSetupPromise = (async () => {
const [monaco, editorWorkerModule, jsonWorkerModule, cssWorkerModule, htmlWorkerModule] =
await Promise.all([
import('monaco-editor/esm/vs/editor/editor.api'),
import('monaco-editor/esm/vs/editor/editor.worker?worker'),
import('monaco-editor/esm/vs/language/json/json.worker?worker'),
import('monaco-editor/esm/vs/language/css/css.worker?worker'),
import('monaco-editor/esm/vs/language/html/html.worker?worker'),
// Side-effect-only language contributions
import('monaco-editor/esm/vs/basic-languages/dockerfile/dockerfile.contribution'),
import('monaco-editor/esm/vs/basic-languages/ini/ini.contribution'),
import('monaco-editor/esm/vs/basic-languages/javascript/javascript.contribution'),
import('monaco-editor/esm/vs/basic-languages/markdown/markdown.contribution'),
import('monaco-editor/esm/vs/basic-languages/powershell/powershell.contribution'),
import('monaco-editor/esm/vs/basic-languages/python/python.contribution'),
import('monaco-editor/esm/vs/basic-languages/shell/shell.contribution'),
import('monaco-editor/esm/vs/basic-languages/sql/sql.contribution'),
import('monaco-editor/esm/vs/basic-languages/typescript/typescript.contribution'),
import('monaco-editor/esm/vs/basic-languages/xml/xml.contribution'),
import('monaco-editor/esm/vs/basic-languages/yaml/yaml.contribution'),
import('monaco-editor/esm/vs/language/css/monaco.contribution'),
import('monaco-editor/esm/vs/language/html/monaco.contribution'),
import('monaco-editor/esm/vs/language/json/monaco.contribution')
]);
(self as any).MonacoEnvironment = {
getWorker(_: string, label: string) {
if (label === 'json') {
return new jsonWorkerModule.default();
}
if (label === 'css' || label === 'scss' || label === 'less') {
return new cssWorkerModule.default();
}
if (label === 'html' || label === 'handlebars' || label === 'razor') {
return new htmlWorkerModule.default();
}
return new editorWorkerModule.default();
}
};
loaderInstance.config({ monaco });
})();
}
return monacoSetupPromise;
}
+1 -1
View File
@@ -261,7 +261,7 @@ import { configProfileApi, systemConfigApi } from '@/api/v1';
import AstrBotCoreConfigWrapper from '@/components/config/AstrBotCoreConfigWrapper.vue';
import ConfigProfileMenu from '@/components/config/ConfigProfileMenu.vue';
import StandaloneChat from '@/components/chat/StandaloneChat.vue';
import { VueMonacoEditor } from '@guolao/vue-monaco-editor'
import { LazyMonacoEditor as VueMonacoEditor } from '@/components/shared/LazyMonacoEditor';
import { useI18n, useModuleI18n } from '@/i18n/composables';
import {
askForConfirmation as askForConfirmationDialog,
+1 -1
View File
@@ -407,7 +407,7 @@
import { isCancel } from 'axios';
import { debounce } from 'lodash';
import { markRaw } from 'vue';
import { VueMonacoEditor } from '@guolao/vue-monaco-editor';
import { LazyMonacoEditor as VueMonacoEditor } from '@/components/shared/LazyMonacoEditor';
import { conversationApi } from '@/api/v1';
import { useCommonStore } from '@/stores/common';
import { useCustomizerStore } from '@/stores/customizer';
@@ -8,7 +8,7 @@ import {
watch,
} from "vue";
import { RouterLink, useRoute, useRouter } from "vue-router";
import { VueMonacoEditor } from "@guolao/vue-monaco-editor";
import { LazyMonacoEditor as VueMonacoEditor } from '@/components/shared/LazyMonacoEditor';
import {
Bot,
Braces,
+3 -2
View File
@@ -60,7 +60,7 @@
</div>
</div>
</div>
<apexchart
<VueApexCharts
type="area"
height="320"
:options="messageChartOptions"
@@ -110,7 +110,7 @@
<div class="section-title">{{ t('modelTrend.title') }}</div>
</div>
</div>
<apexchart
<VueApexCharts
type="bar"
height="420"
:options="providerChartOptions"
@@ -252,6 +252,7 @@
</template>
<script setup lang="ts">
import VueApexCharts from 'vue3-apexcharts'
import type { ApexOptions } from 'apexcharts'
import { Check, Copy, MessageCircle, MessageSquareText } from '@lucide/vue'
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'