feat: add some translate (#12904)

This commit is contained in:
CityFun
2026-05-29 22:06:26 +08:00
committed by GitHub
parent 243a2fe6f3
commit 9cca14c382
17 changed files with 259 additions and 6 deletions
+6 -1
View File
@@ -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)
+27 -3
View File
@@ -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
+126
View File
@@ -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"))
}
+1 -1
View File
@@ -4,7 +4,7 @@ base:
is_demo: false
is_offline: false
is_fxplay: false
is_enterprise: false
is_enterprise: true
log:
level: debug
+20
View File
@@ -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() {
+18
View File
@@ -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)
+1 -1
View File
@@ -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
+6
View File
@@ -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',
+6
View File
@@ -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',
+6
View File
@@ -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: '失敗リクエスト',
+6
View File
@@ -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: '실패한 요청',
+6
View File
@@ -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',
+6
View File
@@ -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',
+6
View File
@@ -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: 'Неуспешные запросы',
+6
View File
@@ -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',
+6
View File
@@ -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: '失敗請求',
+6
View File
@@ -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: '失败请求',