From 4ab839956220797ef4ff0f91a4ce4b35f57e556b Mon Sep 17 00:00:00 2001 From: zhengkunwang223 <1paneldev@sina.com> Date: Fri, 29 May 2026 18:48:53 +0800 Subject: [PATCH] feat: add some translate --- agent/app/service/app_install.go | 7 +- agent/app/service/app_utils.go | 30 ++++++- agent/app/service/vllm_upgrade.go | 126 +++++++++++++++++++++++++++ agent/cmd/server/conf/app.yaml | 2 +- agent/init/hook/hook.go | 20 +++++ agent/server/server.go | 18 ++++ core/cmd/server/conf/app.yaml | 2 +- frontend/src/lang/modules/en.ts | 6 ++ frontend/src/lang/modules/es-es.ts | 6 ++ frontend/src/lang/modules/ja.ts | 6 ++ frontend/src/lang/modules/ko.ts | 6 ++ frontend/src/lang/modules/ms.ts | 6 ++ frontend/src/lang/modules/pt-br.ts | 6 ++ frontend/src/lang/modules/ru.ts | 6 ++ frontend/src/lang/modules/tr.ts | 6 ++ frontend/src/lang/modules/zh-Hant.ts | 6 ++ frontend/src/lang/modules/zh.ts | 6 ++ 17 files changed, 259 insertions(+), 6 deletions(-) create mode 100644 agent/app/service/vllm_upgrade.go diff --git a/agent/app/service/app_install.go b/agent/app/service/app_install.go index 6e2d7453268b..b89b03f86df1 100644 --- a/agent/app/service/app_install.go +++ b/agent/app/service/app_install.go @@ -589,7 +589,12 @@ func (a *AppInstallService) GetUpdateVersions(req request.AppUpdateVersion) ([]d if common.IsCrossVersion(install.Version, detail.Version) && !app.CrossVersionUpdate { continue } - if common.CompareVersion(detail.Version, install.Version) { + canUpgrade := common.CompareVersion(detail.Version, install.Version) + if app.Key == vllmAppKeyForUpgrade { + currentImage := loadVllmImageFromEnv(install.Env) + canUpgrade = isVllmUpgradeCandidate(install.Version, detail.Version, currentImage) + } + if canUpgrade { var newCompose string if req.UpdateVersion != "" && req.UpdateVersion == detail.Version && detail.DockerCompose == "" && !app.IsLocalApp() { filename := filepath.Base(detail.DownloadUrl) diff --git a/agent/app/service/app_utils.go b/agent/app/service/app_utils.go index d7f94d7b125c..7cfc60264cd3 100644 --- a/agent/app/service/app_utils.go +++ b/agent/app/service/app_utils.go @@ -682,6 +682,9 @@ func upgradeInstall(req request.AppInstallUpgrade) error { if err != nil { return err } + if install.App.Key == vllmAppKeyForUpgrade && !isVllmUpgradeVersionAllowed(install.Version, detail.Version, loadVllmImageFromEnv(install.Env)) { + return errors.New("vLLM can only upgrade within the same image type") + } if install.Version == detail.Version { return errors.New("two version is same") } @@ -748,8 +751,25 @@ func upgradeInstall(req request.AppInstallUpgrade) error { if err != nil { return err } + if install.App.Key == vllmAppKeyForUpgrade { + envs := make(map[string]interface{}) + if err = json.Unmarshal([]byte(install.Env), &envs); err != nil { + return err + } + image := buildVllmUpgradeImage(loadVllmImageFromEnv(install.Env), oldVersion, detail.Version) + envs[vllmImageEnvKey] = image + paramByte, err := json.Marshal(envs) + if err != nil { + return err + } + install.Env = string(paramByte) + content = setVllmImageInEnvContent(content, image) + } if req.PullImage { composeContent := []byte(detail.DockerCompose) + if install.App.Key == vllmAppKeyForUpgrade { + composeContent = []byte(install.DockerCompose) + } if req.DockerCompose != "" { composeContent = []byte(req.DockerCompose) } @@ -811,9 +831,13 @@ func upgradeInstall(req request.AppInstallUpgrade) error { return err } if req.DockerCompose == "" { - newCompose, err = getUpgradeCompose(install, detail) - if err != nil { - return err + if install.App.Key == vllmAppKeyForUpgrade { + newCompose = install.DockerCompose + } else { + newCompose, err = getUpgradeCompose(install, detail) + if err != nil { + return err + } } } else { newCompose = req.DockerCompose diff --git a/agent/app/service/vllm_upgrade.go b/agent/app/service/vllm_upgrade.go new file mode 100644 index 000000000000..dd25a8c97fad --- /dev/null +++ b/agent/app/service/vllm_upgrade.go @@ -0,0 +1,126 @@ +package service + +import ( + "encoding/json" + "strings" + + "github.com/1Panel-dev/1Panel/agent/utils/common" +) + +const ( + vllmAppKeyForUpgrade = "vllm" + vllmImageEnvKey = "IMAGE" + vllmImageTypeNvidia = "nvidia" + vllmImageTypeIntel = "intel" +) + +func resolveVllmVersionFamily(version, image string) string { + normalizedVersion := strings.ToLower(strings.TrimSpace(version)) + if strings.HasPrefix(normalizedVersion, vllmImageTypeIntel+"-") { + return vllmImageTypeIntel + } + if strings.HasPrefix(normalizedVersion, vllmImageTypeNvidia+"-") { + return vllmImageTypeNvidia + } + normalizedImage := strings.ToLower(strings.TrimSpace(image)) + if strings.Contains(normalizedImage, "intel/") || strings.Contains(normalizedImage, "llm-scaler-vllm") { + return vllmImageTypeIntel + } + return vllmImageTypeNvidia +} + +func trimVllmVersionFamily(version string) string { + trimmed := strings.TrimSpace(version) + normalized := strings.ToLower(trimmed) + for _, family := range []string{vllmImageTypeNvidia, vllmImageTypeIntel} { + prefix := family + "-" + if strings.HasPrefix(normalized, prefix) { + return strings.TrimSpace(trimmed[len(prefix):]) + } + } + return trimmed +} + +func buildDefaultVllmImageByVersion(version string) string { + tag := trimVllmVersionFamily(version) + if resolveVllmVersionFamily(version, "") == vllmImageTypeIntel { + return "intel/llm-scaler-vllm:" + tag + } + if tag != "" && !strings.HasPrefix(strings.ToLower(tag), "v") { + tag = "v" + tag + } + return "vllm/vllm-openai:" + tag +} + +func isVllmUpgradeVersionAllowed(currentVersion, targetVersion, currentImage string) bool { + currentFamily := resolveVllmVersionFamily(currentVersion, currentImage) + targetFamily := resolveVllmVersionFamily(targetVersion, "") + return currentFamily == targetFamily +} + +func hasVllmVersionFamilyPrefix(version string) bool { + normalized := strings.ToLower(strings.TrimSpace(version)) + return strings.HasPrefix(normalized, vllmImageTypeNvidia+"-") || strings.HasPrefix(normalized, vllmImageTypeIntel+"-") +} + +func isVllmUpgradeCandidate(currentVersion, targetVersion, currentImage string) bool { + if strings.TrimSpace(currentVersion) == strings.TrimSpace(targetVersion) { + return false + } + if !isVllmUpgradeVersionAllowed(currentVersion, targetVersion, currentImage) { + return false + } + if common.CompareVersion(targetVersion, currentVersion) { + return true + } + return !hasVllmVersionFamilyPrefix(currentVersion) && + resolveVllmVersionFamily(targetVersion, "") == vllmImageTypeNvidia && + trimVllmVersionFamily(currentVersion) == trimVllmVersionFamily(targetVersion) +} + +func buildVllmUpgradeImage(currentImage, currentVersion, targetVersion string) string { + trimmedImage := strings.TrimSpace(currentImage) + if trimmedImage == "" || trimmedImage == buildDefaultVllmImageByVersion(currentVersion) { + return buildDefaultVllmImageByVersion(targetVersion) + } + return trimmedImage +} + +func loadVllmImageFromEnv(raw string) string { + envs := make(map[string]interface{}) + if strings.TrimSpace(raw) == "" { + return "" + } + if err := json.Unmarshal([]byte(raw), &envs); err != nil { + return "" + } + if image, ok := envs[vllmImageEnvKey].(string); ok { + return strings.TrimSpace(image) + } + return "" +} + +func setVllmImageInEnvContent(content []byte, image string) []byte { + normalizedImage := strings.TrimSpace(image) + if normalizedImage == "" { + return content + } + lines := strings.Split(string(content), "\n") + replaced := false + for index, line := range lines { + if strings.HasPrefix(line, vllmImageEnvKey+"=") { + lines[index] = vllmImageEnvKey + "=" + normalizedImage + replaced = true + break + } + } + if !replaced { + if len(lines) > 0 && lines[len(lines)-1] == "" { + lines[len(lines)-1] = vllmImageEnvKey + "=" + normalizedImage + lines = append(lines, "") + } else { + lines = append(lines, vllmImageEnvKey+"="+normalizedImage) + } + } + return []byte(strings.Join(lines, "\n")) +} diff --git a/agent/cmd/server/conf/app.yaml b/agent/cmd/server/conf/app.yaml index 3cddcfca35ea..a4f52b67be92 100644 --- a/agent/cmd/server/conf/app.yaml +++ b/agent/cmd/server/conf/app.yaml @@ -4,7 +4,7 @@ base: is_demo: false is_offline: false is_fxplay: false - is_enterprise: false + is_enterprise: true log: level: debug diff --git a/agent/init/hook/hook.go b/agent/init/hook/hook.go index ac8e4b3abfa4..57fb7fe83fed 100644 --- a/agent/init/hook/hook.go +++ b/agent/init/hook/hook.go @@ -16,18 +16,38 @@ import ( ) func Init() { + global.LOG.Info("agent hook: init global data start") initGlobalData() + global.LOG.Info("agent hook: init global data done") + global.LOG.Info("agent hook: handle cronjob status start") handleCronjobStatus() + global.LOG.Info("agent hook: handle cronjob status done") + global.LOG.Info("agent hook: handle clam status start") handleClamStatus() + global.LOG.Info("agent hook: handle clam status done") + global.LOG.Info("agent hook: handle record status start") handleRecordStatus() + global.LOG.Info("agent hook: handle record status done") + global.LOG.Info("agent hook: handle snapshot status start") handleSnapStatus() + global.LOG.Info("agent hook: handle snapshot status done") + global.LOG.Info("agent hook: handle ollama model status start") handleOllamaModelStatus() + global.LOG.Info("agent hook: handle ollama model status done") + global.LOG.Info("agent hook: load local dir start") loadLocalDir() + global.LOG.Info("agent hook: load local dir done") + global.LOG.Info("agent hook: init docker config start") initDockerConf() + global.LOG.Info("agent hook: init docker config done") + global.LOG.Info("agent hook: init alert task start") initAlertTask() + global.LOG.Info("agent hook: init alert task done") + global.LOG.Info("agent hook: init monitor db start") initMonitorDB() + global.LOG.Info("agent hook: init monitor db done") } func initGlobalData() { diff --git a/agent/server/server.go b/agent/server/server.go index 26eabb93ed52..fa4003ac547c 100644 --- a/agent/server/server.go +++ b/agent/server/server.go @@ -95,19 +95,32 @@ func Start() { viper.Init() dir.Init() log.Init() + global.LOG.Info("agent startup: logger initialized") db.Init() + global.LOG.Info("agent startup: database initialized") migration.Init() + global.LOG.Info("agent startup: migration initialized") i18n.Init() + global.LOG.Info("agent startup: i18n initialized") cache.Init() + global.LOG.Info("agent startup: cache initialized") app.Init() + global.LOG.Info("agent startup: app initialized") lang.Init() + global.LOG.Info("agent startup: language initialized") validator.Init() + global.LOG.Info("agent startup: validator initialized") cron.Run() + global.LOG.Info("agent startup: cron initialized") hook.Init() + global.LOG.Info("agent startup: hook initialized") go firewall.Init() + global.LOG.Info("agent startup: firewall init scheduled") InitOthers() + global.LOG.Info("agent startup: edition initialized") rootRouter := router.Routers() + global.LOG.Info("agent startup: router initialized") server := &http.Server{ Handler: rootRouter, @@ -120,6 +133,7 @@ func Start() { } if global.IsMaster { + global.LOG.Infof("agent startup: master mode, preparing unix socket %s", masterSocketPath) if err := prepareMasterSocketDir(masterSocketDir); err != nil { panic(err) } @@ -132,11 +146,14 @@ func Start() { _ = listener.Close() panic(err) } + global.LOG.Infof("agent startup: listening on unix socket %s", masterSocketPath) business.Init() + global.LOG.Info("agent startup: business initialized") _ = server.Serve(listener) return } else { server.Addr = fmt.Sprintf("0.0.0.0:%s", global.CONF.Base.Port) + global.LOG.Infof("agent startup: node mode, preparing https listener %s", server.Addr) settingRepo := repo.NewISettingRepo() certItem, err := settingRepo.Get(settingRepo.WithByKey("ServerCrt")) if err != nil { @@ -166,6 +183,7 @@ func Start() { server.TLSConfig.ClientCAs = caCertPool } business.Init() + global.LOG.Info("agent startup: business initialized") global.LOG.Infof("listen at https://0.0.0.0:%s", global.CONF.Base.Port) if err := server.ListenAndServeTLS("", ""); err != nil { panic(err) diff --git a/core/cmd/server/conf/app.yaml b/core/cmd/server/conf/app.yaml index a1a166a29faa..c114bfea251a 100644 --- a/core/cmd/server/conf/app.yaml +++ b/core/cmd/server/conf/app.yaml @@ -4,7 +4,7 @@ base: is_demo: false is_offline: false is_fxplay: false - is_enterprise: false + is_enterprise: true port: 9999 username: admin password: admin123 diff --git a/frontend/src/lang/modules/en.ts b/frontend/src/lang/modules/en.ts index 6e31c031e4e5..b78195e09259 100644 --- a/frontend/src/lang/modules/en.ts +++ b/frontend/src/lang/modules/en.ts @@ -967,6 +967,10 @@ const message = { requestBodySize: 'Request Body Size', requestBodyTruncated: 'Truncated', requestBodyHash: 'Request Body Hash', + responseBody: 'Response Body', + responseBodySize: 'Response Body Size', + responseBodyTruncated: 'Response Body Truncated', + responseBodyHash: 'Response Body Hash', statusTitle: 'AI Gateway Status', serviceEnabled: 'Service Auto-start', proxyEnabled: 'Gateway Enabled', @@ -996,6 +1000,8 @@ const message = { inputTokens: 'Input Tokens', outputTokens: 'Output Tokens', totalToken: 'Total Tokens', + cachedToken: 'Cached Tokens', + cacheHitRate: 'Cache Hit Rate', activeUsers: 'Active Users', activeModels: 'Active Models', failedRequests: 'Failed Requests', diff --git a/frontend/src/lang/modules/es-es.ts b/frontend/src/lang/modules/es-es.ts index 1390aafeca6e..87bb06681c69 100644 --- a/frontend/src/lang/modules/es-es.ts +++ b/frontend/src/lang/modules/es-es.ts @@ -982,6 +982,10 @@ const message = { requestBodySize: 'Tamaño del cuerpo de solicitud', requestBodyTruncated: 'Truncado', requestBodyHash: 'Hash del cuerpo de solicitud', + responseBody: 'Cuerpo de respuesta', + responseBodySize: 'Tamaño del cuerpo de respuesta', + responseBodyTruncated: 'Cuerpo de respuesta truncado', + responseBodyHash: 'Hash del cuerpo de respuesta', statusTitle: 'Estado del gateway de IA', serviceEnabled: 'Inicio automático del servicio', proxyEnabled: 'Gateway habilitado', @@ -1010,6 +1014,8 @@ const message = { inputTokens: 'Tokens de entrada', outputTokens: 'Tokens de salida', totalToken: 'Tokens totales', + cachedToken: 'Tokens en caché', + cacheHitRate: 'Tasa de acierto de caché', activeUsers: 'Usuarios activos', activeModels: 'Modelos activos', failedRequests: 'Solicitudes fallidas', diff --git a/frontend/src/lang/modules/ja.ts b/frontend/src/lang/modules/ja.ts index a7340299df9d..3196d91811dc 100644 --- a/frontend/src/lang/modules/ja.ts +++ b/frontend/src/lang/modules/ja.ts @@ -972,6 +972,10 @@ const message = { requestBodySize: 'リクエスト本文サイズ', requestBodyTruncated: '切り詰め', requestBodyHash: 'リクエスト本文 Hash', + responseBody: 'レスポンス本文', + responseBodySize: 'レスポンス本文サイズ', + responseBodyTruncated: 'レスポンス本文の切り詰め', + responseBodyHash: 'レスポンス本文 Hash', statusTitle: 'AI ゲートウェイ状態', serviceEnabled: 'サービス自動起動', proxyEnabled: 'ゲートウェイ有効', @@ -1000,6 +1004,8 @@ const message = { inputTokens: '入力 Token', outputTokens: '出力 Token', totalToken: '合計 Token', + cachedToken: 'キャッシュ Token', + cacheHitRate: 'キャッシュヒット率', activeUsers: 'アクティブユーザー', activeModels: 'アクティブモデル', failedRequests: '失敗リクエスト', diff --git a/frontend/src/lang/modules/ko.ts b/frontend/src/lang/modules/ko.ts index 0a13c0857021..bc765de0863f 100644 --- a/frontend/src/lang/modules/ko.ts +++ b/frontend/src/lang/modules/ko.ts @@ -956,6 +956,10 @@ const message = { requestBodySize: '요청 본문 크기', requestBodyTruncated: '잘림 여부', requestBodyHash: '요청 본문 Hash', + responseBody: '응답 본문', + responseBodySize: '응답 본문 크기', + responseBodyTruncated: '응답 본문 잘림 여부', + responseBodyHash: '응답 본문 Hash', statusTitle: 'AI 게이트웨이 상태', serviceEnabled: '서비스 자동 시작', proxyEnabled: '게이트웨이 활성화', @@ -984,6 +988,8 @@ const message = { inputTokens: '입력 Token', outputTokens: '출력 Token', totalToken: '총 Token', + cachedToken: '캐시 Token', + cacheHitRate: '캐시 적중률', activeUsers: '활성 사용자', activeModels: '활성 모델', failedRequests: '실패한 요청', diff --git a/frontend/src/lang/modules/ms.ts b/frontend/src/lang/modules/ms.ts index 78cf154f4bf5..7cf8ff96a870 100644 --- a/frontend/src/lang/modules/ms.ts +++ b/frontend/src/lang/modules/ms.ts @@ -981,6 +981,10 @@ const message = { requestBodySize: 'Saiz Badan Permintaan', requestBodyTruncated: 'Dipotong', requestBodyHash: 'Hash Badan Permintaan', + responseBody: 'Badan Respons', + responseBodySize: 'Saiz Badan Respons', + responseBodyTruncated: 'Badan Respons Dipotong', + responseBodyHash: 'Hash Badan Respons', statusTitle: 'Status gateway AI', serviceEnabled: 'Auto mula perkhidmatan', proxyEnabled: 'Gateway diaktifkan', @@ -1009,6 +1013,8 @@ const message = { inputTokens: 'Token input', outputTokens: 'Token output', totalToken: 'Jumlah Token', + cachedToken: 'Token cache', + cacheHitRate: 'Kadar hit cache', activeUsers: 'Pengguna aktif', activeModels: 'Model aktif', failedRequests: 'Permintaan gagal', diff --git a/frontend/src/lang/modules/pt-br.ts b/frontend/src/lang/modules/pt-br.ts index 86f74026f909..86225f2cc71a 100644 --- a/frontend/src/lang/modules/pt-br.ts +++ b/frontend/src/lang/modules/pt-br.ts @@ -977,6 +977,10 @@ const message = { requestBodySize: 'Tamanho do corpo da requisição', requestBodyTruncated: 'Truncado', requestBodyHash: 'Hash do corpo da requisição', + responseBody: 'Corpo da resposta', + responseBodySize: 'Tamanho do corpo da resposta', + responseBodyTruncated: 'Corpo da resposta truncado', + responseBodyHash: 'Hash do corpo da resposta', statusTitle: 'Status do gateway de IA', serviceEnabled: 'Inicialização automática do serviço', proxyEnabled: 'Gateway habilitado', @@ -1005,6 +1009,8 @@ const message = { inputTokens: 'Token de entrada', outputTokens: 'Token de saída', totalToken: 'Total de Token', + cachedToken: 'Tokens em cache', + cacheHitRate: 'Taxa de acerto do cache', activeUsers: 'Usuários ativos', activeModels: 'Modelos ativos', failedRequests: 'Requisições com falha', diff --git a/frontend/src/lang/modules/ru.ts b/frontend/src/lang/modules/ru.ts index 5f882cb739ae..9b690357fc3d 100644 --- a/frontend/src/lang/modules/ru.ts +++ b/frontend/src/lang/modules/ru.ts @@ -971,6 +971,10 @@ const message = { requestBodySize: 'Размер тела запроса', requestBodyTruncated: 'Обрезано', requestBodyHash: 'Hash тела запроса', + responseBody: 'Тело ответа', + responseBodySize: 'Размер тела ответа', + responseBodyTruncated: 'Тело ответа обрезано', + responseBodyHash: 'Hash тела ответа', statusTitle: 'Статус AI-шлюза', serviceEnabled: 'Автозапуск сервиса', proxyEnabled: 'Шлюз включен', @@ -999,6 +1003,8 @@ const message = { inputTokens: 'Входные Token', outputTokens: 'Выходные Token', totalToken: 'Всего Token', + cachedToken: 'Кэшированные Token', + cacheHitRate: 'Попадания в кэш', activeUsers: 'Активные пользователи', activeModels: 'Активные модели', failedRequests: 'Неуспешные запросы', diff --git a/frontend/src/lang/modules/tr.ts b/frontend/src/lang/modules/tr.ts index 887454f46b5e..373a3eaf6c59 100644 --- a/frontend/src/lang/modules/tr.ts +++ b/frontend/src/lang/modules/tr.ts @@ -979,6 +979,10 @@ const message = { requestBodySize: 'İstek Gövdesi Boyutu', requestBodyTruncated: 'Kısaltıldı', requestBodyHash: 'İstek Gövdesi Hash', + responseBody: 'Yanıt Gövdesi', + responseBodySize: 'Yanıt Gövdesi Boyutu', + responseBodyTruncated: 'Yanıt Gövdesi Kısaltıldı', + responseBodyHash: 'Yanıt Gövdesi Hash', statusTitle: 'AI Ağ Geçidi durumu', serviceEnabled: 'Servis otomatik başlatma', proxyEnabled: 'Ağ geçidi etkin', @@ -1007,6 +1011,8 @@ const message = { inputTokens: 'Girdi Token', outputTokens: 'Çıktı Token', totalToken: 'Toplam Token', + cachedToken: 'Önbellek Token', + cacheHitRate: 'Önbellek İsabet Oranı', activeUsers: 'Aktif kullanıcılar', activeModels: 'Aktif modeller', failedRequests: 'Başarısız istekler', diff --git a/frontend/src/lang/modules/zh-Hant.ts b/frontend/src/lang/modules/zh-Hant.ts index 2d930db79de9..805454b7685e 100644 --- a/frontend/src/lang/modules/zh-Hant.ts +++ b/frontend/src/lang/modules/zh-Hant.ts @@ -916,6 +916,10 @@ const message = { requestBodySize: '請求體大小', requestBodyTruncated: '是否截斷', requestBodyHash: '請求體 Hash', + responseBody: '響應體', + responseBodySize: '響應體大小', + responseBodyTruncated: '響應體是否截斷', + responseBodyHash: '響應體 Hash', statusTitle: 'AI 閘道狀態', serviceEnabled: '服務自啟', proxyEnabled: '閘道啟用', @@ -944,6 +948,8 @@ const message = { inputTokens: '輸入 Token', outputTokens: '輸出 Token', totalToken: '總 Token', + cachedToken: '快取 Token', + cacheHitRate: '快取命中率', activeUsers: '活躍使用者', activeModels: '活躍模型', failedRequests: '失敗請求', diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts index 68372861e458..0f0fe845ae5d 100644 --- a/frontend/src/lang/modules/zh.ts +++ b/frontend/src/lang/modules/zh.ts @@ -909,6 +909,10 @@ const message = { requestBodySize: '请求体大小', requestBodyTruncated: '是否截断', requestBodyHash: '请求体 Hash', + responseBody: '响应体', + responseBodySize: '响应体大小', + responseBodyTruncated: '响应体是否截断', + responseBodyHash: '响应体 Hash', statusTitle: 'AI 网关状态', serviceEnabled: '服务自启', proxyEnabled: '网关启用', @@ -938,6 +942,8 @@ const message = { inputTokens: '输入 Token', outputTokens: '输出 Token', totalToken: '总 Token', + cachedToken: '缓存 Token', + cacheHitRate: '缓存命中率', activeUsers: '活跃用户', activeModels: '活跃模型', failedRequests: '失败请求',