From fbf66766c2e1a4c8b5ba9d88a1b84b4fe137aff2 Mon Sep 17 00:00:00 2001 From: ssongliu Date: Mon, 26 Oct 2026 14:03:40 +0800 Subject: [PATCH] fix: align AI menu order and labels (#14192) --- agent/app/api/v2/ai.go | 12 ++ agent/app/dto/ai.go | 6 + agent/app/dto/request/app.go | 10 +- agent/app/dto/response/app.go | 38 +++-- agent/app/model/app_install.go | 4 + agent/app/service/agents_local.go | 33 +++- agent/app/service/ai.go | 22 +++ agent/app/service/app.go | 10 +- agent/app/service/app_install.go | 49 ++++++ agent/init/migration/migrate.go | 1 + agent/init/migration/migrations/init.go | 38 ++++- agent/router/ro_ai.go | 1 + frontend/src/api/interface/ai.ts | 5 + frontend/src/api/interface/app.ts | 9 + frontend/src/api/modules/ai.ts | 4 + frontend/src/lang/modules/en.ts | 1 + frontend/src/lang/modules/es-es.ts | 1 + frontend/src/lang/modules/fa.ts | 1 + frontend/src/lang/modules/ja.ts | 1 + frontend/src/lang/modules/ko.ts | 1 + frontend/src/lang/modules/lo.ts | 1 + frontend/src/lang/modules/ms.ts | 1 + frontend/src/lang/modules/pt-br.ts | 1 + frontend/src/lang/modules/ru.ts | 1 + frontend/src/lang/modules/tr.ts | 1 + frontend/src/lang/modules/zh-Hant.ts | 3 +- frontend/src/lang/modules/zh.ts | 3 +- .../components/account-address/index.vue | 61 ++++--- .../components/deployment-manage/index.vue | 13 +- .../views/ai/model/components/sync/index.vue | 156 ++++++++++++++++++ frontend/src/views/ai/model/deployment.ts | 8 +- frontend/src/views/ai/model/index.vue | 30 +++- .../views/ai/model/ollama/operate/index.vue | 3 + .../app-store/installed/detail/index.vue | 49 +++++- 34 files changed, 505 insertions(+), 73 deletions(-) create mode 100644 frontend/src/views/ai/model/components/sync/index.vue diff --git a/agent/app/api/v2/ai.go b/agent/app/api/v2/ai.go index 0bb1a0832..b9a782933 100644 --- a/agent/app/api/v2/ai.go +++ b/agent/app/api/v2/ai.go @@ -114,6 +114,18 @@ func (b *BaseApi) SearchOllamaModel(c *gin.Context) { }) } +func (b *BaseApi) SyncOllamaModelAccount(c *gin.Context) { + var req dto.OllamaModelAccountSync + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if err := aiToolService.SyncAccount(req); err != nil { + helper.BadRequest(c, err) + return + } + helper.Success(c) +} + // @Tags AI // @Summary Page Ollama models // @Accept json diff --git a/agent/app/dto/ai.go b/agent/app/dto/ai.go index 9e9525f75..942f13194 100644 --- a/agent/app/dto/ai.go +++ b/agent/app/dto/ai.go @@ -24,6 +24,12 @@ type OllamaModelName struct { TaskID string `json:"taskID"` } +type OllamaModelAccountSync struct { + ID uint `json:"id" validate:"required"` + ModelAccountBaseURLType string `json:"modelAccountBaseURLType" validate:"required,oneof=container localhost systemIP custom"` + ModelAccountBaseURL string `json:"modelAccountBaseURL"` +} + type OllamaBindDomain struct { Domain string `json:"domain" validate:"required"` AppInstallID uint `json:"appInstallID" validate:"required"` diff --git a/agent/app/dto/request/app.go b/agent/app/dto/request/app.go index 94207bf6a..4435a3999 100644 --- a/agent/app/dto/request/app.go +++ b/agent/app/dto/request/app.go @@ -23,6 +23,7 @@ type AppInstallCreate struct { Services map[string]string `json:"services"` TaskID string `json:"taskID"` + SyncModelAccount *bool `json:"syncModelAccount"` ModelAccountBaseURLType string `json:"modelAccountBaseURLType" validate:"omitempty,oneof=container localhost systemIP custom"` ModelAccountBaseURL string `json:"modelAccountBaseURL"` @@ -127,9 +128,12 @@ type AppInstallDelete struct { } type AppInstalledUpdate struct { - InstallId uint `json:"installId" validate:"required"` - Params map[string]interface{} `json:"params" validate:"required"` - TaskID string `json:"-"` + SyncModelAccount *bool `json:"syncModelAccount"` + ModelAccountBaseURLType string `json:"modelAccountBaseURLType" validate:"omitempty,oneof=container localhost systemIP custom"` + ModelAccountBaseURL string `json:"modelAccountBaseURL"` + InstallId uint `json:"installId" validate:"required"` + Params map[string]interface{} `json:"params" validate:"required"` + TaskID string `json:"-"` AppContainerConfig } diff --git a/agent/app/dto/response/app.go b/agent/app/dto/response/app.go index 9c99cff72..7974e7681 100644 --- a/agent/app/dto/response/app.go +++ b/agent/app/dto/response/app.go @@ -50,19 +50,21 @@ type TagDTO struct { } type AppInstalledCheck struct { - IsExist bool `json:"isExist"` - Name string `json:"name"` - App string `json:"app"` - Version string `json:"version"` - Status string `json:"status"` - CreatedAt time.Time `json:"createdAt"` - LastBackupAt string `json:"lastBackupAt"` - AppInstallID uint `json:"appInstallId"` - ContainerName string `json:"containerName"` - InstallPath string `json:"installPath"` - HttpPort int `json:"httpPort"` - HttpsPort int `json:"httpsPort"` - WebsiteDir string `json:"websiteDir"` + ModelAccountBaseURLType string `json:"modelAccountBaseURLType"` + ModelAccountBaseURL string `json:"modelAccountBaseURL"` + IsExist bool `json:"isExist"` + Name string `json:"name"` + App string `json:"app"` + Version string `json:"version"` + Status string `json:"status"` + CreatedAt time.Time `json:"createdAt"` + LastBackupAt string `json:"lastBackupAt"` + AppInstallID uint `json:"appInstallId"` + ContainerName string `json:"containerName"` + InstallPath string `json:"installPath"` + HttpPort int `json:"httpPort"` + HttpsPort int `json:"httpsPort"` + WebsiteDir string `json:"websiteDir"` } type AppDetailDTO struct { @@ -184,7 +186,13 @@ type AppParam struct { } type AppConfig struct { - Params []AppParam `json:"params"` - RawCompose string `json:"rawCompose"` + AppKey string `json:"appKey"` + Name string `json:"name"` + HttpPort int `json:"httpPort"` + SyncModelAccount bool `json:"syncModelAccount"` + ModelAccountBaseURLType string `json:"modelAccountBaseURLType"` + ModelAccountBaseURL string `json:"modelAccountBaseURL"` + Params []AppParam `json:"params"` + RawCompose string `json:"rawCompose"` request.AppContainerConfig } diff --git a/agent/app/model/app_install.go b/agent/app/model/app_install.go index 5c94cbdf5..c5778909a 100644 --- a/agent/app/model/app_install.go +++ b/agent/app/model/app_install.go @@ -28,6 +28,10 @@ type AppInstall struct { Favorite bool `json:"favorite"` SortOrder int `json:"sortOrder" gorm:"default:0"` + SyncModelAccount bool `json:"syncModelAccount"` + ModelAccountBaseURLType string `json:"modelAccountBaseURLType"` + ModelAccountBaseURL string `json:"modelAccountBaseURL"` + App App `json:"app" gorm:"-:migration"` } diff --git a/agent/app/service/agents_local.go b/agent/app/service/agents_local.go index f2f7ae90d..f05d5d240 100644 --- a/agent/app/service/agents_local.go +++ b/agent/app/service/agents_local.go @@ -117,6 +117,13 @@ func SyncOllamaModelAccount(createIfMissing bool) error { if err != nil { return err } + if !install.SyncModelAccount { + return nil + } + return syncOllamaModelAccount(install, fmt.Sprintf("http://%s:11434/v1", install.ContainerName), false, createIfMissing) +} + +func syncOllamaModelAccount(install model.AppInstall, baseURL string, updateBaseURL, createIfMissing bool) error { models, err := aiRepo.List(repo.WithByStatus(constant.StatusSuccess), repo.WithOrderAsc("name")) if err != nil { return err @@ -125,10 +132,34 @@ func SyncOllamaModelAccount(createIfMissing bool) error { for _, item := range models { items = append(items, dto.AgentAccountModel{ID: item.Name, Name: item.Name}) } - _, err = SyncLocalModelAccount(install, 0, fmt.Sprintf("http://%s:11434/v1", install.ContainerName), items, false, createIfMissing) + _, err = SyncLocalModelAccount(install, 0, baseURL, items, updateBaseURL, createIfMissing) return err } +func loadOllamaModelAccountAddress(install model.AppInstall) (string, string, error) { + addressType := install.ModelAccountBaseURLType + if addressType == "" { + addressType = "container" + } + account, err := agentAccountRepo.GetFirst(repo.WithByAppInstallID(install.ID), repo.WithByProvider("ollama")) + if errors.Is(err, gorm.ErrRecordNotFound) { + return addressType, install.ModelAccountBaseURL, nil + } + if err != nil { + return "", "", err + } + if addressType != "custom" { + expectedURL, err := ResolveLocalModelAccountBaseURL(addressType, "", install.ContainerName, "11434", install.HttpPort) + if err != nil { + return "", "", err + } + if strings.TrimRight(account.BaseURL, "/") != expectedURL { + addressType = "custom" + } + } + return addressType, account.BaseURL, nil +} + func CleanupLocalModelAccount(installID uint) { if installID == 0 { return diff --git a/agent/app/service/ai.go b/agent/app/service/ai.go index ce567f63c..f2cf3eb17 100644 --- a/agent/app/service/ai.go +++ b/agent/app/service/ai.go @@ -35,6 +35,7 @@ type IAIToolService interface { Recreate(req dto.OllamaModelName) error Delete(req dto.ForceDelete) error Sync() ([]dto.OllamaModelDropList, error) + SyncAccount(req dto.OllamaModelAccountSync) error LoadDetail(name string) (string, error) BindDomain(req dto.OllamaBindDomain) error GetBindDomain(req dto.OllamaBindDomainReq) (*dto.OllamaBindDomainRes, error) @@ -259,6 +260,27 @@ func (u *AIToolService) Sync() ([]dto.OllamaModelDropList, error) { return dropList, SyncOllamaModelAccount(false) } +func (u *AIToolService) SyncAccount(req dto.OllamaModelAccountSync) error { + install, err := appInstallRepo.GetFirst(repo.WithByID(req.ID)) + if err != nil { + return err + } + if install.App.Key != "ollama" { + return buserr.New("ErrInvalidParams") + } + baseURL, err := ResolveLocalModelAccountBaseURL(req.ModelAccountBaseURLType, req.ModelAccountBaseURL, install.ContainerName, "11434", install.HttpPort) + if err != nil { + return err + } + if err := syncOllamaModelAccount(install, baseURL, true, true); err != nil { + return err + } + return appInstallRepo.BatchUpdateBy(map[string]interface{}{ + "model_account_base_url_type": req.ModelAccountBaseURLType, + "model_account_base_url": baseURL, + }, repo.WithByID(install.ID)) +} + func (u *AIToolService) BindDomain(req dto.OllamaBindDomain) error { nginxInstall, _ := getAppInstallByKey(constant.AppOpenresty) if nginxInstall.ID == 0 { diff --git a/agent/app/service/app.go b/agent/app/service/app.go index 3ae08288a..891adfd87 100644 --- a/agent/app/service/app.go +++ b/agent/app/service/app.go @@ -501,6 +501,14 @@ func (a AppService) installWithHooks(req request.AppInstallCreate, executeScript modelAccountBaseURL := "" if app.Key == "ollama" { + appInstall.SyncModelAccount = req.SyncModelAccount == nil || *req.SyncModelAccount + appInstall.ModelAccountBaseURLType = req.ModelAccountBaseURLType + if appInstall.ModelAccountBaseURLType == "" { + appInstall.ModelAccountBaseURLType = "container" + } + appInstall.ModelAccountBaseURL = req.ModelAccountBaseURL + } + if app.Key == "ollama" && appInstall.SyncModelAccount { modelAccountBaseURL, err = ResolveLocalModelAccountBaseURL(req.ModelAccountBaseURLType, req.ModelAccountBaseURL, containerName, "11434", httpPort) if err != nil { return @@ -617,7 +625,7 @@ func (a AppService) installWithHooks(req request.AppInstallCreate, executeScript return err } updateToolApp(appInstall) - if app.Key == "ollama" { + if app.Key == "ollama" && appInstall.SyncModelAccount { _, err := SyncLocalModelAccount(*appInstall, 0, modelAccountBaseURL, nil, false, true) return err } diff --git a/agent/app/service/app_install.go b/agent/app/service/app_install.go index c4bac8d1d..4dbc5fdcd 100644 --- a/agent/app/service/app_install.go +++ b/agent/app/service/app_install.go @@ -159,6 +159,14 @@ func (a *AppInstallService) CheckExist(req request.AppInstalledInfo) (*response. } res.ContainerName = appInstall.ContainerName + res.ModelAccountBaseURLType = appInstall.ModelAccountBaseURLType + res.ModelAccountBaseURL = appInstall.ModelAccountBaseURL + if app.Key == "ollama" { + res.ModelAccountBaseURLType, res.ModelAccountBaseURL, err = loadOllamaModelAccountAddress(appInstall) + if err != nil { + return nil, err + } + } res.Name = appInstall.Name res.Version = appInstall.Version res.CreatedAt = appInstall.CreatedAt @@ -438,6 +446,32 @@ func (a *AppInstallService) Update(req request.AppInstalledUpdate) error { } } + modelAccountBaseURL := "" + if installed.App.Key == "ollama" { + if req.SyncModelAccount != nil { + installed.SyncModelAccount = *req.SyncModelAccount + } + if req.ModelAccountBaseURLType != "" { + installed.ModelAccountBaseURLType = req.ModelAccountBaseURLType + installed.ModelAccountBaseURL = req.ModelAccountBaseURL + } else if installed.SyncModelAccount { + installed.ModelAccountBaseURLType, installed.ModelAccountBaseURL, err = loadOllamaModelAccountAddress(oldInstalled) + if err != nil { + return err + } + } + if installed.SyncModelAccount { + containerName := installed.ContainerName + if req.Advanced && req.ContainerName != "" { + containerName = req.ContainerName + } + modelAccountBaseURL, err = ResolveLocalModelAccountBaseURL(installed.ModelAccountBaseURLType, installed.ModelAccountBaseURL, containerName, "11434", installed.HttpPort) + if err != nil { + return err + } + } + } + backupDockerCompose := installed.DockerCompose if req.Advanced { composeMap := make(map[string]interface{}) @@ -563,6 +597,9 @@ func (a *AppInstallService) Update(req request.AppInstalledUpdate) error { if proxyChanged && website.ID != 0 && currentProxyErr != nil { global.LOG.Error(buserr.WithErr("ErrUpdateBuWebsite", currentProxyErr).Error()) } + if installed.App.Key == "ollama" && installed.SyncModelAccount { + return syncOllamaModelAccount(installed, modelAccountBaseURL, true, true) + } return nil } @@ -934,6 +971,18 @@ func (a *AppInstallService) GetParams(id uint) (*response.AppConfig, error) { config := getAppCommonConfig(envs) config.DockerCompose = install.DockerCompose res.Params = params + res.AppKey = install.App.Key + res.Name = install.Name + res.HttpPort = install.HttpPort + res.SyncModelAccount = install.SyncModelAccount + res.ModelAccountBaseURLType = install.ModelAccountBaseURLType + res.ModelAccountBaseURL = install.ModelAccountBaseURL + if install.App.Key == "ollama" { + res.ModelAccountBaseURLType, res.ModelAccountBaseURL, err = loadOllamaModelAccountAddress(install) + if err != nil { + return nil, err + } + } if config.ContainerName == "" { config.ContainerName = install.ContainerName } diff --git a/agent/init/migration/migrate.go b/agent/init/migration/migrate.go index 86f9a8920..b075fe247 100644 --- a/agent/init/migration/migrate.go +++ b/agent/init/migration/migrate.go @@ -118,6 +118,7 @@ func agentDBMigrations() []*gormigrate.Migration { migrations.AddNativeAgentInstalls, migrations.AddQuickDirectory, migrations.AddLocalModelAccountLink, + migrations.AddOllamaModelAccountSync, } } diff --git a/agent/init/migration/migrations/init.go b/agent/init/migration/migrations/init.go index 3b520fbc9..8bf0a030a 100644 --- a/agent/init/migration/migrations/init.go +++ b/agent/init/migration/migrations/init.go @@ -1899,9 +1899,43 @@ var AddQuickDirectory = &gormigrate.Migration{ var AddLocalModelAccountLink = &gormigrate.Migration{ ID: "20261023-add-local-model-account-link", Migrate: func(tx *gorm.DB) error { - if err := tx.AutoMigrate(&model.AgentAccount{}); err != nil { + return tx.AutoMigrate(&model.AgentAccount{}) + }, +} + +var AddOllamaModelAccountSync = &gormigrate.Migration{ + ID: "20261026-add-ollama-model-account-sync", + Migrate: func(tx *gorm.DB) error { + if err := tx.AutoMigrate(&model.AppInstall{}); err != nil { return err } - return service.SyncOllamaModelAccount(true) + var accounts []model.AgentAccount + if err := tx.Where("provider = ? AND app_install_id > 0", "ollama").Find(&accounts).Error; err != nil { + return err + } + for _, account := range accounts { + var install model.AppInstall + if err := tx.Preload("App").First(&install, account.AppInstallID).Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + continue + } + return err + } + if install.App.Key != "ollama" { + continue + } + addressType := "custom" + if strings.TrimRight(account.BaseURL, "/") == fmt.Sprintf("http://%s:11434/v1", install.ContainerName) { + addressType = "container" + } + if err := tx.Model(&model.AppInstall{}).Where("id = ?", account.AppInstallID).Updates(map[string]interface{}{ + "sync_model_account": true, + "model_account_base_url_type": addressType, + "model_account_base_url": account.BaseURL, + }).Error; err != nil { + return err + } + } + return nil }, } diff --git a/agent/router/ro_ai.go b/agent/router/ro_ai.go index 99a057e8d..584a12719 100644 --- a/agent/router/ro_ai.go +++ b/agent/router/ro_ai.go @@ -19,6 +19,7 @@ func (a *AIToolsRouter) InitRouter(Router *gin.RouterGroup) { aiToolsRouter.POST("/ollama/model/recreate", baseApi.RecreateOllamaModel) aiToolsRouter.POST("/ollama/model/search", baseApi.SearchOllamaModel) aiToolsRouter.POST("/ollama/model/sync", baseApi.SyncOllamaModel) + aiToolsRouter.POST("/ollama/account/sync", baseApi.SyncOllamaModelAccount) aiToolsRouter.POST("/ollama/model/load", baseApi.LoadOllamaModelDetail) aiToolsRouter.POST("/ollama/model/del", baseApi.DeleteOllamaModel) aiToolsRouter.POST("/domain/bind", baseApi.BindDomain) diff --git a/frontend/src/api/interface/ai.ts b/frontend/src/api/interface/ai.ts index b1157258a..0f0a209bd 100644 --- a/frontend/src/api/interface/ai.ts +++ b/frontend/src/api/interface/ai.ts @@ -1,6 +1,11 @@ import { ReqPage } from '.'; export namespace AI { + export interface ModelAccountSync { + id: number; + modelAccountBaseURLType: 'container' | 'localhost' | 'systemIP' | 'custom'; + modelAccountBaseURL: string; + } export type AgentType = 'openclaw' | 'copaw' | 'hermes-agent' | 'deepseek-harness' | 'octop'; export interface OllamaModelInfo { diff --git a/frontend/src/api/interface/app.ts b/frontend/src/api/interface/app.ts index 7e37f1c0e..a0ca8c3d5 100644 --- a/frontend/src/api/interface/app.ts +++ b/frontend/src/api/interface/app.ts @@ -136,6 +136,7 @@ export namespace App { } export interface AppInstall { + syncModelAccount?: boolean; modelAccountBaseURLType?: string; modelAccountBaseURL?: string; appDetailId: number; @@ -229,6 +230,8 @@ export namespace App { name: string; version: string; isExist: boolean; + modelAccountBaseURLType: string; + modelAccountBaseURL: string; app: string; status: string; createdAt: string; @@ -303,6 +306,12 @@ export namespace App { } export interface AppConfig { + appKey: string; + name: string; + httpPort: number; + syncModelAccount: boolean; + modelAccountBaseURLType: string; + modelAccountBaseURL: string; params: InstallParams[]; rawCompose?: string; cpuQuota: number; diff --git a/frontend/src/api/modules/ai.ts b/frontend/src/api/modules/ai.ts index 96b041dcb..22f60b4e6 100644 --- a/frontend/src/api/modules/ai.ts +++ b/frontend/src/api/modules/ai.ts @@ -22,6 +22,10 @@ export const loadOllamaModel = (name: string) => { export const syncOllamaModel = () => { return http.post>(`/ai/ollama/model/sync`); }; + +export const syncOllamaModelAccount = (req: AI.ModelAccountSync) => { + return http.post('/ai/ollama/account/sync', req); +}; export const closeOllamaModel = (name: string, currentNode?: string) => { return http.post( `/ai/ollama/close`, diff --git a/frontend/src/lang/modules/en.ts b/frontend/src/lang/modules/en.ts index f641e58fd..07cd13979 100644 --- a/frontend/src/lang/modules/en.ts +++ b/frontend/src/lang/modules/en.ts @@ -1063,6 +1063,7 @@ const message = { vllmCommandPortHelper: 'The startup command must use port {0}; otherwise, the service will be inaccessible.', ascendVisibleDevices: 'Ascend visible devices (ASCEND_RT_VISIBLE_DEVICES)', + syncModelAccount: 'Sync to model account', modelAccountAddressHelper: 'Container address is for agent containers; 127.0.0.1, system IP, or custom address is for AI Gateway or external services.', containerAddress: 'Container address', diff --git a/frontend/src/lang/modules/es-es.ts b/frontend/src/lang/modules/es-es.ts index 29b424e4b..847a66ba8 100644 --- a/frontend/src/lang/modules/es-es.ts +++ b/frontend/src/lang/modules/es-es.ts @@ -1066,6 +1066,7 @@ const message = { vllmCommandPortHelper: 'El comando de inicio debe usar el puerto {0}; de lo contrario, no se podrá acceder al servicio.', ascendVisibleDevices: 'Dispositivos Ascend visibles (ASCEND_RT_VISIBLE_DEVICES)', + syncModelAccount: 'Sincronizar con cuenta de modelo', modelAccountAddressHelper: 'La dirección del contenedor es para contenedores de agentes; 127.0.0.1, IP del sistema o dirección personalizada es para AI Gateway o servicios externos.', containerAddress: 'Dirección del contenedor', diff --git a/frontend/src/lang/modules/fa.ts b/frontend/src/lang/modules/fa.ts index 6b1b703a6..d1bec2dc8 100644 --- a/frontend/src/lang/modules/fa.ts +++ b/frontend/src/lang/modules/fa.ts @@ -1048,6 +1048,7 @@ const message = { vllmCommandPortHelper: 'فرمان راه‌اندازی باید از پورت {0} استفاده کند؛ در غیر این صورت سرویس قابل دسترسی نخواهد بود.', ascendVisibleDevices: 'دستگاه‌های قابل مشاهده Ascend (ASCEND_RT_VISIBLE_DEVICES)', + syncModelAccount: 'همگام‌سازی با حساب مدل', modelAccountAddressHelper: 'آدرس کانتینر برای کانتینرهای عامل است؛ 127.0.0.1، IP سیستم یا آدرس سفارشی برای دروازه AI یا خدمات خارجی است.', containerAddress: 'آدرس کانتینر', diff --git a/frontend/src/lang/modules/ja.ts b/frontend/src/lang/modules/ja.ts index 458fb9991..61a50d56d 100644 --- a/frontend/src/lang/modules/ja.ts +++ b/frontend/src/lang/modules/ja.ts @@ -1052,6 +1052,7 @@ const message = { vllmCommandPortHelper: '起動コマンドではポート {0} を使用する必要があります。使用しない場合、サービスにアクセスできません。', ascendVisibleDevices: 'Ascend 可視デバイス(ASCEND_RT_VISIBLE_DEVICES)', + syncModelAccount: 'モデルアカウントに同期', modelAccountAddressHelper: 'コンテナアドレスはエージェントコンテナ向けです。127.0.0.1、システム IP、カスタムアドレスは AI Gateway または外部サービス向けです。', containerAddress: 'コンテナアドレス', diff --git a/frontend/src/lang/modules/ko.ts b/frontend/src/lang/modules/ko.ts index 5e671f91d..b076b9ada 100644 --- a/frontend/src/lang/modules/ko.ts +++ b/frontend/src/lang/modules/ko.ts @@ -1041,6 +1041,7 @@ const message = { vllmProfileVisionExp: 'DeepSeek V4 Flash Vision Exp', vllmCommandPortHelper: '시작 명령은 {0} 포트를 사용해야 하며, 그렇지 않으면 서비스에 접근할 수 없습니다.', ascendVisibleDevices: 'Ascend 표시 장치 (ASCEND_RT_VISIBLE_DEVICES)', + syncModelAccount: '모델 계정에 동기화', modelAccountAddressHelper: '컨테이너 주소는 에이전트 컨테이너 호출에 적합합니다. 127.0.0.1, 시스템 IP 또는 사용자 지정 주소는 AI Gateway 또는 외부 서비스 호출에 적합합니다.', containerAddress: '컨테이너 주소', diff --git a/frontend/src/lang/modules/lo.ts b/frontend/src/lang/modules/lo.ts index a587ec322..59cfb6e4e 100644 --- a/frontend/src/lang/modules/lo.ts +++ b/frontend/src/lang/modules/lo.ts @@ -1038,6 +1038,7 @@ const message = { vllmProfileVisionExp: 'DeepSeek V4 Flash Vision Exp', vllmCommandPortHelper: 'ຄຳສັ່ງເລີ່ມຕົ້ນຕ້ອງໃຊ້ພອດ {0}; ບໍ່ດັ່ງນັ້ນຈະບໍ່ສາມາດເຂົ້າເຖິງບໍລິການໄດ້.', ascendVisibleDevices: 'ອຸປະກອນ Ascend ທີ່ເຫັນໄດ້ (ASCEND_RT_VISIBLE_DEVICES)', + syncModelAccount: 'ຊິ້ງຄ໌ໄປຍັງບັນຊີໂມເດວ', modelAccountAddressHelper: 'ທີ່ຢູ່ຄອນເທນເນີແມ່ນສຳລັບຄອນເທນເນີຕົວແທນ; 127.0.0.1, IP ລະບົບ, ຫຼື ທີ່ຢູ່ກຳນົດເອງແມ່ນສຳລັບ AI Gateway ຫຼື ບໍລິການພາຍນອກ.', containerAddress: 'ທີ່ຢູ່ຄອນເທນເນີ', diff --git a/frontend/src/lang/modules/ms.ts b/frontend/src/lang/modules/ms.ts index 63592bc6d..d4828f4b1 100644 --- a/frontend/src/lang/modules/ms.ts +++ b/frontend/src/lang/modules/ms.ts @@ -1075,6 +1075,7 @@ const message = { vllmCommandPortHelper: 'Perintah permulaan mesti menggunakan port {0}; jika tidak, perkhidmatan tidak dapat diakses.', ascendVisibleDevices: 'Peranti Ascend boleh dilihat (ASCEND_RT_VISIBLE_DEVICES)', + syncModelAccount: 'Segerakkan ke akaun model', modelAccountAddressHelper: 'Alamat kontena sesuai untuk kontena agen; 127.0.0.1, IP sistem, atau alamat tersuai sesuai untuk AI Gateway atau perkhidmatan luaran.', containerAddress: 'Alamat kontena', diff --git a/frontend/src/lang/modules/pt-br.ts b/frontend/src/lang/modules/pt-br.ts index a5d158f5e..733cdeacb 100644 --- a/frontend/src/lang/modules/pt-br.ts +++ b/frontend/src/lang/modules/pt-br.ts @@ -1070,6 +1070,7 @@ const message = { vllmCommandPortHelper: 'O comando de inicialização deve usar a porta {0}; caso contrário, o serviço ficará inacessível.', ascendVisibleDevices: 'Dispositivos Ascend visíveis (ASCEND_RT_VISIBLE_DEVICES)', + syncModelAccount: 'Sincronizar com conta de modelo', modelAccountAddressHelper: 'Use o endereço do contêiner para agentes; 127.0.0.1, IP do sistema ou endereço personalizado para AI Gateway ou serviços externos.', containerAddress: 'Endereço do contêiner', diff --git a/frontend/src/lang/modules/ru.ts b/frontend/src/lang/modules/ru.ts index 22db387e7..3a84e1793 100644 --- a/frontend/src/lang/modules/ru.ts +++ b/frontend/src/lang/modules/ru.ts @@ -1063,6 +1063,7 @@ const message = { vllmProfileVisionExp: 'DeepSeek V4 Flash Vision Exp', vllmCommandPortHelper: 'Команда запуска должна использовать порт {0}, иначе сервис будет недоступен.', ascendVisibleDevices: 'Видимые устройства Ascend (ASCEND_RT_VISIBLE_DEVICES)', + syncModelAccount: 'Синхронизировать с аккаунтом модели', modelAccountAddressHelper: 'Адрес контейнера подходит для контейнеров агентов; 127.0.0.1, системный IP или пользовательский адрес подходит для AI Gateway или внешних сервисов.', containerAddress: 'Адрес контейнера', diff --git a/frontend/src/lang/modules/tr.ts b/frontend/src/lang/modules/tr.ts index d669c93ee..90c8d2bec 100644 --- a/frontend/src/lang/modules/tr.ts +++ b/frontend/src/lang/modules/tr.ts @@ -1074,6 +1074,7 @@ const message = { vllmCommandPortHelper: 'Başlatma komutu {0} numaralı bağlantı noktasını kullanmalıdır; aksi halde hizmete erişilemez.', ascendVisibleDevices: 'Görünür Ascend cihazları (ASCEND_RT_VISIBLE_DEVICES)', + syncModelAccount: 'Model hesabına senkronize et', modelAccountAddressHelper: 'Konteyner adresi ajan konteynerleri içindir; 127.0.0.1, sistem IP veya özel adres AI Gateway ya da harici servisler içindir.', containerAddress: 'Konteyner adresi', diff --git a/frontend/src/lang/modules/zh-Hant.ts b/frontend/src/lang/modules/zh-Hant.ts index 4e459b6e1..bb6bdea17 100644 --- a/frontend/src/lang/modules/zh-Hant.ts +++ b/frontend/src/lang/modules/zh-Hant.ts @@ -798,7 +798,7 @@ const message = { contextWindow: '上下文視窗', useModelDefault: '使用模型預設值', reasoning: '推理模型', - verified: '驗證狀態', + verified: '驗證成功', verifySkipped: '略過驗證', skillsTab: 'Skill', pluginsTab: '外掛', @@ -1004,6 +1004,7 @@ const message = { vllmProfileVisionExp: 'DeepSeek V4 Flash Vision Exp', vllmCommandPortHelper: '啟動命令必須使用 {0} 連接埠,否則服務將無法存取。', ascendVisibleDevices: 'Ascend 可見裝置(ASCEND_RT_VISIBLE_DEVICES)', + syncModelAccount: '同步到模型帳號', modelAccountAddressHelper: '容器地址適合智能體容器呼叫;127.0.0.1、本機 IP 或自訂地址適合 AI 閘道或外部服務呼叫。', containerAddress: '容器地址', diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts index d67cea269..2fc74a781 100644 --- a/frontend/src/lang/modules/zh.ts +++ b/frontend/src/lang/modules/zh.ts @@ -815,7 +815,7 @@ const message = { contextWindow: '上下文窗口', useModelDefault: '使用模型默认值', reasoning: '推理模型', - verified: '验证状态', + verified: '验证成功', verifySkipped: '不验证', skillsTab: '技能', pluginsTab: '插件', @@ -1017,6 +1017,7 @@ const message = { vllmProfileVisionExp: 'DeepSeek V4 Flash Vision Exp', vllmCommandPortHelper: '启动命令必须使用 {0} 端口,否则服务将无法访问。', ascendVisibleDevices: 'Ascend 可见设备(ASCEND_RT_VISIBLE_DEVICES)', + syncModelAccount: '同步到模型账号', modelAccountAddressHelper: '容器地址适合智能体容器调用;127.0.0.1、本机 IP 或自定义地址适合 AI 网关或外部服务调用。', containerAddress: '容器地址', diff --git a/frontend/src/views/ai/model/components/account-address/index.vue b/frontend/src/views/ai/model/components/account-address/index.vue index f9070da68..f11581e4f 100644 --- a/frontend/src/views/ai/model/components/account-address/index.vue +++ b/frontend/src/views/ai/model/components/account-address/index.vue @@ -1,33 +1,38 @@ diff --git a/frontend/src/views/ai/model/deployment.ts b/frontend/src/views/ai/model/deployment.ts index 3c4336895..a90be739c 100644 --- a/frontend/src/views/ai/model/deployment.ts +++ b/frontend/src/views/ai/model/deployment.ts @@ -17,6 +17,7 @@ export interface Deployment { interface VllmItem { appInstallId: number; + agentAccountId: number; path: string; upgradable: boolean; id: number; @@ -25,6 +26,9 @@ interface VllmItem { appVersion: string; port: number; modelAccountBaseURL: string; + modelAccountBaseURLType: AI.ModelAccountSync['modelAccountBaseURLType']; + command: string; + imageType: string; containerName: string; modelDir: string; status: string; @@ -32,7 +36,9 @@ interface VllmItem { interface VllmAPI { pageVllm: (req: ReqPage & { info: string }) => Promise>>; - operateVllm: (req: { id: number; operate: string; taskID: string; forceDelete?: boolean }) => Promise; + operateVllm: ( + req: { id: number; operate: string; taskID: string; forceDelete?: boolean } & Partial, + ) => Promise; } const vllmModules = import.meta.glob('@/xpack/api/modules/vllm.ts'); diff --git a/frontend/src/views/ai/model/index.vue b/frontend/src/views/ai/model/index.vue index 1eb26b0ae..3ca006fcc 100644 --- a/frontend/src/views/ai/model/index.vue +++ b/frontend/src/views/ai/model/index.vue @@ -281,7 +281,7 @@ :buttons="deploymentButtons" :label="$t('commons.table.operate')" fixed="right" - :ellipsis="4" + :ellipsis="2" width="240" /> @@ -305,6 +305,7 @@ + @@ -338,6 +339,7 @@ import AppUpgrade from '@/views/app-store/installed/upgrade/index.vue'; import AppDelete from '@/views/app-store/installed/delete/index.vue'; import AppResources from '@/views/app-store/installed/check/index.vue'; import ModelPool from './components/model-pool/index.vue'; +import SyncService from './components/sync/index.vue'; import ProviderLabel from '@/components/agent-provider-label/index.vue'; import { loadModelOverview, loadVllmAPI, readPages, vllmAvailable, type Deployment } from './deployment'; @@ -366,6 +368,7 @@ const upgradeRef = ref(); const appDeleteRef = ref(); const appResourcesRef = ref(); const poolRef = ref(); +const syncRef = ref>(); const templateRef = ref(); const templateView = shallowRef(); const loading = ref(false); @@ -566,7 +569,9 @@ const taskRef = ref(); const pending = ref(''); const operationDisabled = (row: Deployment) => Boolean(pending.value) || - ['Installing', 'Starting', 'ReStarting', 'Restarting', 'Waiting', 'Uninstalling'].includes(row.status); + ['Installing', 'Starting', 'ReStarting', 'Restarting', 'Rebuilding', 'Waiting', 'Uninstalling'].includes( + row.status, + ); const statusDisabled = (row: Deployment) => operationDisabled(row) || !hasManagePermissionAccess() || (row.engine === 'ollama' && !hasPermission('app_manage')); const operate = async (row: Deployment, operation: 'start' | 'stop' | 'restart') => { @@ -601,23 +606,30 @@ const operate = async (row: Deployment, operation: 'start' | 'stop' | 'restart') } }; const deploymentButtons = [ + { + label: i18n.global.t('commons.button.edit'), + permission: true, + show: (row: Deployment) => row.engine === 'vllm' || (row.engine === 'ollama' && hasPermission('app_manage')), + disabled: operationDisabled, + click: (row: Deployment) => manageRef.value.open(row, 'config', true), + }, { label: i18n.global.t('aiTools.model.manage'), show: (row: Deployment) => row.engine !== 'vllm', click: manageDeployment, }, - { - label: i18n.global.t('commons.button.edit'), - permission: true, - show: (row: Deployment) => row.engine === 'vllm', - disabled: operationDisabled, - click: (row: Deployment) => manageRef.value.open(row, 'config', true), - }, { label: i18n.global.t('menu.monitor'), show: (row: Deployment) => row.engine === 'vllm', click: (row: Deployment) => manageRef.value.open(row, 'monitor', true), }, + { + label: i18n.global.t('commons.button.sync'), + permission: true, + show: (row: Deployment) => row.engine === 'ollama' || row.engine === 'vllm', + disabled: operationDisabled, + click: (row: Deployment) => syncRef.value.open(row), + }, { label: i18n.global.t('commons.button.log'), click: (row: Deployment) => manageRef.value.open(row, 'log', true), diff --git a/frontend/src/views/ai/model/ollama/operate/index.vue b/frontend/src/views/ai/model/ollama/operate/index.vue index b4310c09a..bcdc5baf7 100644 --- a/frontend/src/views/ai/model/ollama/operate/index.vue +++ b/frontend/src/views/ai/model/ollama/operate/index.vue @@ -9,6 +9,7 @@