Compare commits

...
35 changed files with 542 additions and 73 deletions
+12
View File
@@ -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 // @Tags AI
// @Summary Page Ollama models // @Summary Page Ollama models
// @Accept json // @Accept json
+6
View File
@@ -24,6 +24,12 @@ type OllamaModelName struct {
TaskID string `json:"taskID"` 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 { type OllamaBindDomain struct {
Domain string `json:"domain" validate:"required"` Domain string `json:"domain" validate:"required"`
AppInstallID uint `json:"appInstallID" validate:"required"` AppInstallID uint `json:"appInstallID" validate:"required"`
+7 -3
View File
@@ -23,6 +23,7 @@ type AppInstallCreate struct {
Services map[string]string `json:"services"` Services map[string]string `json:"services"`
TaskID string `json:"taskID"` TaskID string `json:"taskID"`
SyncModelAccount *bool `json:"syncModelAccount"`
ModelAccountBaseURLType string `json:"modelAccountBaseURLType" validate:"omitempty,oneof=container localhost systemIP custom"` ModelAccountBaseURLType string `json:"modelAccountBaseURLType" validate:"omitempty,oneof=container localhost systemIP custom"`
ModelAccountBaseURL string `json:"modelAccountBaseURL"` ModelAccountBaseURL string `json:"modelAccountBaseURL"`
@@ -127,9 +128,12 @@ type AppInstallDelete struct {
} }
type AppInstalledUpdate struct { type AppInstalledUpdate struct {
InstallId uint `json:"installId" validate:"required"` SyncModelAccount *bool `json:"syncModelAccount"`
Params map[string]interface{} `json:"params" validate:"required"` ModelAccountBaseURLType string `json:"modelAccountBaseURLType" validate:"omitempty,oneof=container localhost systemIP custom"`
TaskID string `json:"-"` ModelAccountBaseURL string `json:"modelAccountBaseURL"`
InstallId uint `json:"installId" validate:"required"`
Params map[string]interface{} `json:"params" validate:"required"`
TaskID string `json:"-"`
AppContainerConfig AppContainerConfig
} }
+23 -15
View File
@@ -50,19 +50,21 @@ type TagDTO struct {
} }
type AppInstalledCheck struct { type AppInstalledCheck struct {
IsExist bool `json:"isExist"` ModelAccountBaseURLType string `json:"modelAccountBaseURLType"`
Name string `json:"name"` ModelAccountBaseURL string `json:"modelAccountBaseURL"`
App string `json:"app"` IsExist bool `json:"isExist"`
Version string `json:"version"` Name string `json:"name"`
Status string `json:"status"` App string `json:"app"`
CreatedAt time.Time `json:"createdAt"` Version string `json:"version"`
LastBackupAt string `json:"lastBackupAt"` Status string `json:"status"`
AppInstallID uint `json:"appInstallId"` CreatedAt time.Time `json:"createdAt"`
ContainerName string `json:"containerName"` LastBackupAt string `json:"lastBackupAt"`
InstallPath string `json:"installPath"` AppInstallID uint `json:"appInstallId"`
HttpPort int `json:"httpPort"` ContainerName string `json:"containerName"`
HttpsPort int `json:"httpsPort"` InstallPath string `json:"installPath"`
WebsiteDir string `json:"websiteDir"` HttpPort int `json:"httpPort"`
HttpsPort int `json:"httpsPort"`
WebsiteDir string `json:"websiteDir"`
} }
type AppDetailDTO struct { type AppDetailDTO struct {
@@ -184,7 +186,13 @@ type AppParam struct {
} }
type AppConfig struct { type AppConfig struct {
Params []AppParam `json:"params"` AppKey string `json:"appKey"`
RawCompose string `json:"rawCompose"` 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 request.AppContainerConfig
} }
+4
View File
@@ -28,6 +28,10 @@ type AppInstall struct {
Favorite bool `json:"favorite"` Favorite bool `json:"favorite"`
SortOrder int `json:"sortOrder" gorm:"default:0"` 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"` App App `json:"app" gorm:"-:migration"`
} }
+32 -1
View File
@@ -117,6 +117,13 @@ func SyncOllamaModelAccount(createIfMissing bool) error {
if err != nil { if err != nil {
return err 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")) models, err := aiRepo.List(repo.WithByStatus(constant.StatusSuccess), repo.WithOrderAsc("name"))
if err != nil { if err != nil {
return err return err
@@ -125,10 +132,34 @@ func SyncOllamaModelAccount(createIfMissing bool) error {
for _, item := range models { for _, item := range models {
items = append(items, dto.AgentAccountModel{ID: item.Name, Name: item.Name}) 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 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) { func CleanupLocalModelAccount(installID uint) {
if installID == 0 { if installID == 0 {
return return
+22
View File
@@ -35,6 +35,7 @@ type IAIToolService interface {
Recreate(req dto.OllamaModelName) error Recreate(req dto.OllamaModelName) error
Delete(req dto.ForceDelete) error Delete(req dto.ForceDelete) error
Sync() ([]dto.OllamaModelDropList, error) Sync() ([]dto.OllamaModelDropList, error)
SyncAccount(req dto.OllamaModelAccountSync) error
LoadDetail(name string) (string, error) LoadDetail(name string) (string, error)
BindDomain(req dto.OllamaBindDomain) error BindDomain(req dto.OllamaBindDomain) error
GetBindDomain(req dto.OllamaBindDomainReq) (*dto.OllamaBindDomainRes, error) GetBindDomain(req dto.OllamaBindDomainReq) (*dto.OllamaBindDomainRes, error)
@@ -259,6 +260,27 @@ func (u *AIToolService) Sync() ([]dto.OllamaModelDropList, error) {
return dropList, SyncOllamaModelAccount(false) 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 { func (u *AIToolService) BindDomain(req dto.OllamaBindDomain) error {
nginxInstall, _ := getAppInstallByKey(constant.AppOpenresty) nginxInstall, _ := getAppInstallByKey(constant.AppOpenresty)
if nginxInstall.ID == 0 { if nginxInstall.ID == 0 {
+9 -1
View File
@@ -501,6 +501,14 @@ func (a AppService) installWithHooks(req request.AppInstallCreate, executeScript
modelAccountBaseURL := "" modelAccountBaseURL := ""
if app.Key == "ollama" { 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) modelAccountBaseURL, err = ResolveLocalModelAccountBaseURL(req.ModelAccountBaseURLType, req.ModelAccountBaseURL, containerName, "11434", httpPort)
if err != nil { if err != nil {
return return
@@ -617,7 +625,7 @@ func (a AppService) installWithHooks(req request.AppInstallCreate, executeScript
return err return err
} }
updateToolApp(appInstall) updateToolApp(appInstall)
if app.Key == "ollama" { if app.Key == "ollama" && appInstall.SyncModelAccount {
_, err := SyncLocalModelAccount(*appInstall, 0, modelAccountBaseURL, nil, false, true) _, err := SyncLocalModelAccount(*appInstall, 0, modelAccountBaseURL, nil, false, true)
return err return err
} }
+49
View File
@@ -159,6 +159,14 @@ func (a *AppInstallService) CheckExist(req request.AppInstalledInfo) (*response.
} }
res.ContainerName = appInstall.ContainerName 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.Name = appInstall.Name
res.Version = appInstall.Version res.Version = appInstall.Version
res.CreatedAt = appInstall.CreatedAt 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 backupDockerCompose := installed.DockerCompose
if req.Advanced { if req.Advanced {
composeMap := make(map[string]interface{}) composeMap := make(map[string]interface{})
@@ -563,6 +597,9 @@ func (a *AppInstallService) Update(req request.AppInstalledUpdate) error {
if proxyChanged && website.ID != 0 && currentProxyErr != nil { if proxyChanged && website.ID != 0 && currentProxyErr != nil {
global.LOG.Error(buserr.WithErr("ErrUpdateBuWebsite", currentProxyErr).Error()) global.LOG.Error(buserr.WithErr("ErrUpdateBuWebsite", currentProxyErr).Error())
} }
if installed.App.Key == "ollama" && installed.SyncModelAccount {
return syncOllamaModelAccount(installed, modelAccountBaseURL, true, true)
}
return nil return nil
} }
@@ -934,6 +971,18 @@ func (a *AppInstallService) GetParams(id uint) (*response.AppConfig, error) {
config := getAppCommonConfig(envs) config := getAppCommonConfig(envs)
config.DockerCompose = install.DockerCompose config.DockerCompose = install.DockerCompose
res.Params = params 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 == "" { if config.ContainerName == "" {
config.ContainerName = install.ContainerName config.ContainerName = install.ContainerName
} }
+1
View File
@@ -118,6 +118,7 @@ func agentDBMigrations() []*gormigrate.Migration {
migrations.AddNativeAgentInstalls, migrations.AddNativeAgentInstalls,
migrations.AddQuickDirectory, migrations.AddQuickDirectory,
migrations.AddLocalModelAccountLink, migrations.AddLocalModelAccountLink,
migrations.AddOllamaModelAccountSync,
} }
} }
+36 -2
View File
@@ -1899,9 +1899,43 @@ var AddQuickDirectory = &gormigrate.Migration{
var AddLocalModelAccountLink = &gormigrate.Migration{ var AddLocalModelAccountLink = &gormigrate.Migration{
ID: "20261023-add-local-model-account-link", ID: "20261023-add-local-model-account-link",
Migrate: func(tx *gorm.DB) error { 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 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
}, },
} }
+1
View File
@@ -19,6 +19,7 @@ func (a *AIToolsRouter) InitRouter(Router *gin.RouterGroup) {
aiToolsRouter.POST("/ollama/model/recreate", baseApi.RecreateOllamaModel) aiToolsRouter.POST("/ollama/model/recreate", baseApi.RecreateOllamaModel)
aiToolsRouter.POST("/ollama/model/search", baseApi.SearchOllamaModel) aiToolsRouter.POST("/ollama/model/search", baseApi.SearchOllamaModel)
aiToolsRouter.POST("/ollama/model/sync", baseApi.SyncOllamaModel) 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/load", baseApi.LoadOllamaModelDetail)
aiToolsRouter.POST("/ollama/model/del", baseApi.DeleteOllamaModel) aiToolsRouter.POST("/ollama/model/del", baseApi.DeleteOllamaModel)
aiToolsRouter.POST("/domain/bind", baseApi.BindDomain) aiToolsRouter.POST("/domain/bind", baseApi.BindDomain)
+1
View File
@@ -236,6 +236,7 @@ var WebUrlMap = map[string]struct{}{
} }
var DynamicRoutes = []string{ var DynamicRoutes = []string{
`^/ai/benchmark/[0-9]+$`,
`^/containers/composeDetail/[^/]+$`, `^/containers/composeDetail/[^/]+$`,
`^/databases/mysql/setting/[^/]+/[^/]+$`, `^/databases/mysql/setting/[^/]+/[^/]+$`,
`^/databases/postgresql/setting/[^/]+/[^/]+$`, `^/databases/postgresql/setting/[^/]+/[^/]+$`,
+5
View File
@@ -1,6 +1,11 @@
import { ReqPage } from '.'; import { ReqPage } from '.';
export namespace AI { 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 type AgentType = 'openclaw' | 'copaw' | 'hermes-agent' | 'deepseek-harness' | 'octop';
export interface OllamaModelInfo { export interface OllamaModelInfo {
+9
View File
@@ -136,6 +136,7 @@ export namespace App {
} }
export interface AppInstall { export interface AppInstall {
syncModelAccount?: boolean;
modelAccountBaseURLType?: string; modelAccountBaseURLType?: string;
modelAccountBaseURL?: string; modelAccountBaseURL?: string;
appDetailId: number; appDetailId: number;
@@ -229,6 +230,8 @@ export namespace App {
name: string; name: string;
version: string; version: string;
isExist: boolean; isExist: boolean;
modelAccountBaseURLType: string;
modelAccountBaseURL: string;
app: string; app: string;
status: string; status: string;
createdAt: string; createdAt: string;
@@ -303,6 +306,12 @@ export namespace App {
} }
export interface AppConfig { export interface AppConfig {
appKey: string;
name: string;
httpPort: number;
syncModelAccount: boolean;
modelAccountBaseURLType: string;
modelAccountBaseURL: string;
params: InstallParams[]; params: InstallParams[];
rawCompose?: string; rawCompose?: string;
cpuQuota: number; cpuQuota: number;
+4
View File
@@ -22,6 +22,10 @@ export const loadOllamaModel = (name: string) => {
export const syncOllamaModel = () => { export const syncOllamaModel = () => {
return http.post<Array<AI.OllamaModelDropInfo>>(`/ai/ollama/model/sync`); return http.post<Array<AI.OllamaModelDropInfo>>(`/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) => { export const closeOllamaModel = (name: string, currentNode?: string) => {
return http.post( return http.post(
`/ai/ollama/close`, `/ai/ollama/close`,
+4
View File
@@ -1063,6 +1063,7 @@ const message = {
vllmCommandPortHelper: vllmCommandPortHelper:
'The startup command must use port {0}; otherwise, the service will be inaccessible.', 'The startup command must use port {0}; otherwise, the service will be inaccessible.',
ascendVisibleDevices: 'Ascend visible devices (ASCEND_RT_VISIBLE_DEVICES)', ascendVisibleDevices: 'Ascend visible devices (ASCEND_RT_VISIBLE_DEVICES)',
syncModelAccount: 'Sync to model account',
modelAccountAddressHelper: modelAccountAddressHelper:
'Container address is for agent containers; 127.0.0.1, system IP, or custom address is for AI Gateway or external services.', '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', containerAddress: 'Container address',
@@ -1904,6 +1905,9 @@ const message = {
statusUnavailable: 'Service status unavailable. Refresh to retry.', statusUnavailable: 'Service status unavailable. Refresh to retry.',
}, },
benchmarkPlan: { benchmarkPlan: {
prefixTokens: 'Prefix tokens',
seed: 'Random seed',
report: 'Report',
prepareTimeoutHelp: 'Preparation time limit, excluded from the overall timeout.', prepareTimeoutHelp: 'Preparation time limit, excluded from the overall timeout.',
pointTimeoutHelp: pointTimeoutHelp:
'Separate timer per point, including startup, warmups and all requests; capped by the remaining overall time.', 'Separate timer per point, including startup, warmups and all requests; capped by the remaining overall time.',
+4
View File
@@ -1066,6 +1066,7 @@ const message = {
vllmCommandPortHelper: vllmCommandPortHelper:
'El comando de inicio debe usar el puerto {0}; de lo contrario, no se podrá acceder al servicio.', '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)', ascendVisibleDevices: 'Dispositivos Ascend visibles (ASCEND_RT_VISIBLE_DEVICES)',
syncModelAccount: 'Sincronizar con cuenta de modelo',
modelAccountAddressHelper: 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.', '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', containerAddress: 'Dirección del contenedor',
@@ -1920,6 +1921,9 @@ const message = {
statusUnavailable: 'Service status unavailable. Refresh to retry.', statusUnavailable: 'Service status unavailable. Refresh to retry.',
}, },
benchmarkPlan: { benchmarkPlan: {
prefixTokens: 'Tokens de prefijo',
seed: 'Semilla aleatoria',
report: 'Informe',
prepareTimeoutHelp: 'Límite de preparación, excluido del tiempo límite total.', prepareTimeoutHelp: 'Límite de preparación, excluido del tiempo límite total.',
pointTimeoutHelp: pointTimeoutHelp:
'Tiempo independiente por punto, con inicio, calentamiento y solicitudes; limitado por el tiempo total restante.', 'Tiempo independiente por punto, con inicio, calentamiento y solicitudes; limitado por el tiempo total restante.',
+4
View File
@@ -1048,6 +1048,7 @@ const message = {
vllmCommandPortHelper: vllmCommandPortHelper:
'فرمان راه‌اندازی باید از پورت {0} استفاده کند؛ در غیر این صورت سرویس قابل دسترسی نخواهد بود.', 'فرمان راه‌اندازی باید از پورت {0} استفاده کند؛ در غیر این صورت سرویس قابل دسترسی نخواهد بود.',
ascendVisibleDevices: 'دستگاه‌های قابل مشاهده Ascend (ASCEND_RT_VISIBLE_DEVICES)', ascendVisibleDevices: 'دستگاه‌های قابل مشاهده Ascend (ASCEND_RT_VISIBLE_DEVICES)',
syncModelAccount: 'همگام‌سازی با حساب مدل',
modelAccountAddressHelper: modelAccountAddressHelper:
'آدرس کانتینر برای کانتینرهای عامل است؛ 127.0.0.1، IP سیستم یا آدرس سفارشی برای دروازه AI یا خدمات خارجی است.', 'آدرس کانتینر برای کانتینرهای عامل است؛ 127.0.0.1، IP سیستم یا آدرس سفارشی برای دروازه AI یا خدمات خارجی است.',
containerAddress: 'آدرس کانتینر', containerAddress: 'آدرس کانتینر',
@@ -1884,6 +1885,9 @@ const message = {
statusUnavailable: 'Service status unavailable. Refresh to retry.', statusUnavailable: 'Service status unavailable. Refresh to retry.',
}, },
benchmarkPlan: { benchmarkPlan: {
prefixTokens: 'تعداد توکن\u200cهای پیشوند',
seed: 'بذر تصادفی',
report: 'گزارش',
prepareTimeoutHelp: 'مهلت آماده‌سازی در مهلت کلی آزمون محاسبه نمی‌شود.', prepareTimeoutHelp: 'مهلت آماده‌سازی در مهلت کلی آزمون محاسبه نمی‌شود.',
pointTimeoutHelp: pointTimeoutHelp:
'زمان‌سنج مستقل هر نقطه، شامل راه‌اندازی، گرم‌کردن و همه درخواست‌ها؛ محدود به زمان کلی باقی‌مانده.', 'زمان‌سنج مستقل هر نقطه، شامل راه‌اندازی، گرم‌کردن و همه درخواست‌ها؛ محدود به زمان کلی باقی‌مانده.',
+4
View File
@@ -1052,6 +1052,7 @@ const message = {
vllmCommandPortHelper: vllmCommandPortHelper:
'起動コマンドではポート {0} を使用する必要があります。使用しない場合、サービスにアクセスできません。', '起動コマンドではポート {0} を使用する必要があります。使用しない場合、サービスにアクセスできません。',
ascendVisibleDevices: 'Ascend 可視デバイス(ASCEND_RT_VISIBLE_DEVICES)', ascendVisibleDevices: 'Ascend 可視デバイス(ASCEND_RT_VISIBLE_DEVICES)',
syncModelAccount: 'モデルアカウントに同期',
modelAccountAddressHelper: modelAccountAddressHelper:
'コンテナアドレスはエージェントコンテナ向けです。127.0.0.1、システム IP、カスタムアドレスは AI Gateway または外部サービス向けです。', 'コンテナアドレスはエージェントコンテナ向けです。127.0.0.1、システム IP、カスタムアドレスは AI Gateway または外部サービス向けです。',
containerAddress: 'コンテナアドレス', containerAddress: 'コンテナアドレス',
@@ -1889,6 +1890,9 @@ const message = {
statusUnavailable: 'Service status unavailable. Refresh to retry.', statusUnavailable: 'Service status unavailable. Refresh to retry.',
}, },
benchmarkPlan: { benchmarkPlan: {
prefixTokens: 'プレフィックストークン数',
seed: '乱数シード',
report: 'レポート',
prepareTimeoutHelp: '準備時間の上限です。テスト全体のタイムアウトには含みません。', prepareTimeoutHelp: '準備時間の上限です。テスト全体のタイムアウトには含みません。',
pointTimeoutHelp: pointTimeoutHelp:
'各測定点で個別に計時し、起動、ウォームアップ、全リクエストを含みます。全体の残り時間にも制限されます。', '各測定点で個別に計時し、起動、ウォームアップ、全リクエストを含みます。全体の残り時間にも制限されます。',
+4
View File
@@ -1041,6 +1041,7 @@ const message = {
vllmProfileVisionExp: 'DeepSeek V4 Flash Vision Exp', vllmProfileVisionExp: 'DeepSeek V4 Flash Vision Exp',
vllmCommandPortHelper: '시작 명령은 {0} 포트를 사용해야 하며, 그렇지 않으면 서비스에 접근할 수 없습니다.', vllmCommandPortHelper: '시작 명령은 {0} 포트를 사용해야 하며, 그렇지 않으면 서비스에 접근할 수 없습니다.',
ascendVisibleDevices: 'Ascend 표시 장치 (ASCEND_RT_VISIBLE_DEVICES)', ascendVisibleDevices: 'Ascend 표시 장치 (ASCEND_RT_VISIBLE_DEVICES)',
syncModelAccount: '모델 계정에 동기화',
modelAccountAddressHelper: modelAccountAddressHelper:
'컨테이너 주소는 에이전트 컨테이너 호출에 적합합니다. 127.0.0.1, 시스템 IP 또는 사용자 지정 주소는 AI Gateway 또는 외부 서비스 호출에 적합합니다.', '컨테이너 주소는 에이전트 컨테이너 호출에 적합합니다. 127.0.0.1, 시스템 IP 또는 사용자 지정 주소는 AI Gateway 또는 외부 서비스 호출에 적합합니다.',
containerAddress: '컨테이너 주소', containerAddress: '컨테이너 주소',
@@ -1869,6 +1870,9 @@ const message = {
statusUnavailable: 'Service status unavailable. Refresh to retry.', statusUnavailable: 'Service status unavailable. Refresh to retry.',
}, },
benchmarkPlan: { benchmarkPlan: {
prefixTokens: '접두사 토큰 수',
seed: '난수 시드',
report: '보고서',
prepareTimeoutHelp: '준비 시간 제한이며 전체 시간 제한에서 제외됩니다.', prepareTimeoutHelp: '준비 시간 제한이며 전체 시간 제한에서 제외됩니다.',
pointTimeoutHelp: '측정점별로 시작, 워밍업 및 모든 요청 시간을 측정하며, 남은 전체 시간의 제한도 받습니다.', pointTimeoutHelp: '측정점별로 시작, 워밍업 및 모든 요청 시간을 측정하며, 남은 전체 시간의 제한도 받습니다.',
totalTimeoutHelp: '준비 후 시작하며 모든 측정점과 대기 시간을 포함합니다. 시간 초과 시 테스트를 중단합니다.', totalTimeoutHelp: '준비 후 시작하며 모든 측정점과 대기 시간을 포함합니다. 시간 초과 시 테스트를 중단합니다.',
+4
View File
@@ -1038,6 +1038,7 @@ const message = {
vllmProfileVisionExp: 'DeepSeek V4 Flash Vision Exp', vllmProfileVisionExp: 'DeepSeek V4 Flash Vision Exp',
vllmCommandPortHelper: 'ຄຳສັ່ງເລີ່ມຕົ້ນຕ້ອງໃຊ້ພອດ {0}; ບໍ່ດັ່ງນັ້ນຈະບໍ່ສາມາດເຂົ້າເຖິງບໍລິການໄດ້.', vllmCommandPortHelper: 'ຄຳສັ່ງເລີ່ມຕົ້ນຕ້ອງໃຊ້ພອດ {0}; ບໍ່ດັ່ງນັ້ນຈະບໍ່ສາມາດເຂົ້າເຖິງບໍລິການໄດ້.',
ascendVisibleDevices: 'ອຸປະກອນ Ascend ທີ່ເຫັນໄດ້ (ASCEND_RT_VISIBLE_DEVICES)', ascendVisibleDevices: 'ອຸປະກອນ Ascend ທີ່ເຫັນໄດ້ (ASCEND_RT_VISIBLE_DEVICES)',
syncModelAccount: 'ຊິ້ງຄ໌ໄປຍັງບັນຊີໂມເດວ',
modelAccountAddressHelper: modelAccountAddressHelper:
'ທີ່ຢູ່ຄອນເທນເນີແມ່ນສຳລັບຄອນເທນເນີຕົວແທນ; 127.0.0.1, IP ລະບົບ, ຫຼື ທີ່ຢູ່ກຳນົດເອງແມ່ນສຳລັບ AI Gateway ຫຼື ບໍລິການພາຍນອກ.', 'ທີ່ຢູ່ຄອນເທນເນີແມ່ນສຳລັບຄອນເທນເນີຕົວແທນ; 127.0.0.1, IP ລະບົບ, ຫຼື ທີ່ຢູ່ກຳນົດເອງແມ່ນສຳລັບ AI Gateway ຫຼື ບໍລິການພາຍນອກ.',
containerAddress: 'ທີ່ຢູ່ຄອນເທນເນີ', containerAddress: 'ທີ່ຢູ່ຄອນເທນເນີ',
@@ -1859,6 +1860,9 @@ const message = {
statusUnavailable: 'Service status unavailable. Refresh to retry.', statusUnavailable: 'Service status unavailable. Refresh to retry.',
}, },
benchmarkPlan: { benchmarkPlan: {
prefixTokens: 'ຈຳນວນ Token ຄຳນຳໜ້າ',
seed: 'ຄ່າເລີ່ມຕົ້ນການສຸ່ມ',
report: 'ລາຍງານ',
prepareTimeoutHelp: 'ເວລາຈຳກັດການກຽມການ ບໍ່ນັບໃນເວລາທັງຮອບ.', prepareTimeoutHelp: 'ເວລາຈຳກັດການກຽມການ ບໍ່ນັບໃນເວລາທັງຮອບ.',
pointTimeoutHelp: 'ຈັບເວລາແຍກຕໍ່ຈຸດ ລວມການເລີ່ມ, ອຸ່ນເຄື່ອງ ແລະ ທຸກຄຳຮ້ອງຂໍ; ຈຳກັດດ້ວຍເວລາທັງຮອບທີ່ເຫຼືອ.', pointTimeoutHelp: 'ຈັບເວລາແຍກຕໍ່ຈຸດ ລວມການເລີ່ມ, ອຸ່ນເຄື່ອງ ແລະ ທຸກຄຳຮ້ອງຂໍ; ຈຳກັດດ້ວຍເວລາທັງຮອບທີ່ເຫຼືອ.',
totalTimeoutHelp: 'ເລີ່ມຫຼັງກຽມການ ລວມທຸກຈຸດແລະໄລຍະຫ່າງ. ຢຸດທົດສອບເມື່ອໝົດເວລາ.', totalTimeoutHelp: 'ເລີ່ມຫຼັງກຽມການ ລວມທຸກຈຸດແລະໄລຍະຫ່າງ. ຢຸດທົດສອບເມື່ອໝົດເວລາ.',
+4
View File
@@ -1075,6 +1075,7 @@ const message = {
vllmCommandPortHelper: vllmCommandPortHelper:
'Perintah permulaan mesti menggunakan port {0}; jika tidak, perkhidmatan tidak dapat diakses.', 'Perintah permulaan mesti menggunakan port {0}; jika tidak, perkhidmatan tidak dapat diakses.',
ascendVisibleDevices: 'Peranti Ascend boleh dilihat (ASCEND_RT_VISIBLE_DEVICES)', ascendVisibleDevices: 'Peranti Ascend boleh dilihat (ASCEND_RT_VISIBLE_DEVICES)',
syncModelAccount: 'Segerakkan ke akaun model',
modelAccountAddressHelper: modelAccountAddressHelper:
'Alamat kontena sesuai untuk kontena agen; 127.0.0.1, IP sistem, atau alamat tersuai sesuai untuk AI Gateway atau perkhidmatan luaran.', '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', containerAddress: 'Alamat kontena',
@@ -1917,6 +1918,9 @@ const message = {
statusUnavailable: 'Service status unavailable. Refresh to retry.', statusUnavailable: 'Service status unavailable. Refresh to retry.',
}, },
benchmarkPlan: { benchmarkPlan: {
prefixTokens: 'Token awalan',
seed: 'Benih rawak',
report: 'Laporan',
prepareTimeoutHelp: 'Had masa persediaan, tidak termasuk had masa keseluruhan.', prepareTimeoutHelp: 'Had masa persediaan, tidak termasuk had masa keseluruhan.',
pointTimeoutHelp: pointTimeoutHelp:
'Pemasa berasingan setiap titik, termasuk permulaan, pemanasan dan semua permintaan; dihadkan oleh baki masa keseluruhan.', 'Pemasa berasingan setiap titik, termasuk permulaan, pemanasan dan semua permintaan; dihadkan oleh baki masa keseluruhan.',
+4
View File
@@ -1070,6 +1070,7 @@ const message = {
vllmCommandPortHelper: vllmCommandPortHelper:
'O comando de inicialização deve usar a porta {0}; caso contrário, o serviço ficará inacessível.', '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)', ascendVisibleDevices: 'Dispositivos Ascend visíveis (ASCEND_RT_VISIBLE_DEVICES)',
syncModelAccount: 'Sincronizar com conta de modelo',
modelAccountAddressHelper: 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.', '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', containerAddress: 'Endereço do contêiner',
@@ -1919,6 +1920,9 @@ const message = {
statusUnavailable: 'Service status unavailable. Refresh to retry.', statusUnavailable: 'Service status unavailable. Refresh to retry.',
}, },
benchmarkPlan: { benchmarkPlan: {
prefixTokens: 'Tokens de prefixo',
seed: 'Semente aleatória',
report: 'Relatório',
prepareTimeoutHelp: 'Limite da preparação, excluído do tempo limite total.', prepareTimeoutHelp: 'Limite da preparação, excluído do tempo limite total.',
pointTimeoutHelp: pointTimeoutHelp:
'Tempo separado por ponto, incluindo início, aquecimento e solicitações; limitado pelo tempo total restante.', 'Tempo separado por ponto, incluindo início, aquecimento e solicitações; limitado pelo tempo total restante.',
+4
View File
@@ -1063,6 +1063,7 @@ const message = {
vllmProfileVisionExp: 'DeepSeek V4 Flash Vision Exp', vllmProfileVisionExp: 'DeepSeek V4 Flash Vision Exp',
vllmCommandPortHelper: 'Команда запуска должна использовать порт {0}, иначе сервис будет недоступен.', vllmCommandPortHelper: 'Команда запуска должна использовать порт {0}, иначе сервис будет недоступен.',
ascendVisibleDevices: 'Видимые устройства Ascend (ASCEND_RT_VISIBLE_DEVICES)', ascendVisibleDevices: 'Видимые устройства Ascend (ASCEND_RT_VISIBLE_DEVICES)',
syncModelAccount: 'Синхронизировать с аккаунтом модели',
modelAccountAddressHelper: modelAccountAddressHelper:
'Адрес контейнера подходит для контейнеров агентов; 127.0.0.1, системный IP или пользовательский адрес подходит для AI Gateway или внешних сервисов.', 'Адрес контейнера подходит для контейнеров агентов; 127.0.0.1, системный IP или пользовательский адрес подходит для AI Gateway или внешних сервисов.',
containerAddress: 'Адрес контейнера', containerAddress: 'Адрес контейнера',
@@ -1908,6 +1909,9 @@ const message = {
statusUnavailable: 'Service status unavailable. Refresh to retry.', statusUnavailable: 'Service status unavailable. Refresh to retry.',
}, },
benchmarkPlan: { benchmarkPlan: {
prefixTokens: 'Токены префикса',
seed: 'Начальное значение генератора случайных чисел',
report: 'Отчёт',
prepareTimeoutHelp: 'Лимит подготовки, не входит в общий тайм-аут.', prepareTimeoutHelp: 'Лимит подготовки, не входит в общий тайм-аут.',
pointTimeoutHelp: pointTimeoutHelp:
'Отдельный отсчёт для каждой точки, включая запуск, прогрев и запросы; ограничен оставшимся общим временем.', 'Отдельный отсчёт для каждой точки, включая запуск, прогрев и запросы; ограничен оставшимся общим временем.',
+4
View File
@@ -1074,6 +1074,7 @@ const message = {
vllmCommandPortHelper: vllmCommandPortHelper:
'Başlatma komutu {0} numaralı bağlantı noktasını kullanmalıdır; aksi halde hizmete erişilemez.', '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)', ascendVisibleDevices: 'Görünür Ascend cihazları (ASCEND_RT_VISIBLE_DEVICES)',
syncModelAccount: 'Model hesabına senkronize et',
modelAccountAddressHelper: modelAccountAddressHelper:
'Konteyner adresi ajan konteynerleri içindir; 127.0.0.1, sistem IP veya özel adres AI Gateway ya da harici servisler içindir.', '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', containerAddress: 'Konteyner adresi',
@@ -1916,6 +1917,9 @@ const message = {
statusUnavailable: 'Service status unavailable. Refresh to retry.', statusUnavailable: 'Service status unavailable. Refresh to retry.',
}, },
benchmarkPlan: { benchmarkPlan: {
prefixTokens: 'Önek token sayısı',
seed: 'Rastgelelik tohumu',
report: 'Rapor',
prepareTimeoutHelp: 'Hazırlık süresi sınırıdır; toplam zaman aşımına dahil değildir.', prepareTimeoutHelp: 'Hazırlık süresi sınırıdır; toplam zaman aşımına dahil değildir.',
pointTimeoutHelp: pointTimeoutHelp:
'Her nokta ayrı ölçülür; başlangıç, ısınma ve tüm istekler dahildir. Kalan toplam süreyle sınırlıdır.', 'Her nokta ayrı ölçülür; başlangıç, ısınma ve tüm istekler dahildir. Kalan toplam süreyle sınırlıdır.',
+5 -1
View File
@@ -798,7 +798,7 @@ const message = {
contextWindow: '上下文視窗', contextWindow: '上下文視窗',
useModelDefault: '使用模型預設值', useModelDefault: '使用模型預設值',
reasoning: '推理模型', reasoning: '推理模型',
verified: '驗證狀態', verified: '驗證成功',
verifySkipped: '略過驗證', verifySkipped: '略過驗證',
skillsTab: 'Skill', skillsTab: 'Skill',
pluginsTab: '外掛', pluginsTab: '外掛',
@@ -1004,6 +1004,7 @@ const message = {
vllmProfileVisionExp: 'DeepSeek V4 Flash Vision Exp', vllmProfileVisionExp: 'DeepSeek V4 Flash Vision Exp',
vllmCommandPortHelper: '啟動命令必須使用 {0} 連接埠,否則服務將無法存取。', vllmCommandPortHelper: '啟動命令必須使用 {0} 連接埠,否則服務將無法存取。',
ascendVisibleDevices: 'Ascend 可見裝置(ASCEND_RT_VISIBLE_DEVICES)', ascendVisibleDevices: 'Ascend 可見裝置(ASCEND_RT_VISIBLE_DEVICES)',
syncModelAccount: '同步到模型帳號',
modelAccountAddressHelper: modelAccountAddressHelper:
'容器地址適合智能體容器呼叫;127.0.0.1、本機 IP 或自訂地址適合 AI 閘道或外部服務呼叫。', '容器地址適合智能體容器呼叫;127.0.0.1、本機 IP 或自訂地址適合 AI 閘道或外部服務呼叫。',
containerAddress: '容器地址', containerAddress: '容器地址',
@@ -1805,6 +1806,9 @@ const message = {
statusUnavailable: '無法取得服務狀態,請重新整理後重試。', statusUnavailable: '無法取得服務狀態,請重新整理後重試。',
}, },
benchmarkPlan: { benchmarkPlan: {
prefixTokens: '前綴 Token 數',
seed: '隨機種子',
report: '報告',
prepareTimeoutHelp: '準備階段的時間上限,不計入整輪逾時。', prepareTimeoutHelp: '準備階段的時間上限,不計入整輪逾時。',
pointTimeoutHelp: '每個測點獨立計時,含啟動、預熱和全部請求,受整輪剩餘時間限制。', pointTimeoutHelp: '每個測點獨立計時,含啟動、預熱和全部請求,受整輪剩餘時間限制。',
totalTimeoutHelp: '準備完成後計時,涵蓋所有測點及間隔,逾時後停止。', totalTimeoutHelp: '準備完成後計時,涵蓋所有測點及間隔,逾時後停止。',
+5 -1
View File
@@ -815,7 +815,7 @@ const message = {
contextWindow: '上下文窗口', contextWindow: '上下文窗口',
useModelDefault: '使用模型默认值', useModelDefault: '使用模型默认值',
reasoning: '推理模型', reasoning: '推理模型',
verified: '验证状态', verified: '验证成功',
verifySkipped: '不验证', verifySkipped: '不验证',
skillsTab: '技能', skillsTab: '技能',
pluginsTab: '插件', pluginsTab: '插件',
@@ -1017,6 +1017,7 @@ const message = {
vllmProfileVisionExp: 'DeepSeek V4 Flash Vision Exp', vllmProfileVisionExp: 'DeepSeek V4 Flash Vision Exp',
vllmCommandPortHelper: '启动命令必须使用 {0} 端口,否则服务将无法访问。', vllmCommandPortHelper: '启动命令必须使用 {0} 端口,否则服务将无法访问。',
ascendVisibleDevices: 'Ascend 可见设备(ASCEND_RT_VISIBLE_DEVICES)', ascendVisibleDevices: 'Ascend 可见设备(ASCEND_RT_VISIBLE_DEVICES)',
syncModelAccount: '同步到模型账号',
modelAccountAddressHelper: modelAccountAddressHelper:
'容器地址适合智能体容器调用;127.0.0.1、本机 IP 或自定义地址适合 AI 网关或外部服务调用。', '容器地址适合智能体容器调用;127.0.0.1、本机 IP 或自定义地址适合 AI 网关或外部服务调用。',
containerAddress: '容器地址', containerAddress: '容器地址',
@@ -1814,6 +1815,9 @@ const message = {
statusUnavailable: '无法获取服务状态,请刷新重试。', statusUnavailable: '无法获取服务状态,请刷新重试。',
}, },
benchmarkPlan: { benchmarkPlan: {
prefixTokens: '前缀 Token 数',
seed: '随机种子',
report: '报告',
prepareTimeoutHelp: '准备阶段的时间上限,不计入整轮超时。', prepareTimeoutHelp: '准备阶段的时间上限,不计入整轮超时。',
pointTimeoutHelp: '每个测点独立计时,含启动、预热和全部请求,受整轮剩余时间限制。', pointTimeoutHelp: '每个测点独立计时,含启动、预热和全部请求,受整轮剩余时间限制。',
totalTimeoutHelp: '准备完成后计时,覆盖所有测点及间隔,超时后停止。', totalTimeoutHelp: '准备完成后计时,覆盖所有测点及间隔,超时后停止。',
@@ -1,33 +1,38 @@
<template> <template>
<div class="form-section-title">{{ $t('aiTools.agents.account') }}</div> <div class="form-section-title">{{ $t('aiTools.agents.account') }}</div>
<span class="input-help model-account-helper">{{ $t('aiTools.model.modelAccountAddressHelper') }}</span> <el-form-item v-if="showSync">
<el-form-item <el-checkbox v-model="form.syncModelAccount">{{ $t('aiTools.model.syncModelAccount') }}</el-checkbox>
:label="$t('aiTools.agents.allowedOrigins')"
prop="modelAccountBaseURLType"
:rules="Rules.requiredSelect"
>
<el-radio-group v-model="form.modelAccountBaseURLType">
<el-radio-button
v-for="item in addressTypes"
:key="item.value"
:value="item.value"
:disabled="hostMode && item.value === 'container'"
>
{{ item.label }}
</el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item
v-if="form.modelAccountBaseURLType === 'custom'"
label="Base URL"
prop="modelAccountBaseURL"
:rules="customURLRules"
>
<el-input v-model.trim="form.modelAccountBaseURL" placeholder="http://192.168.1.100:8000/v1" />
</el-form-item>
<el-form-item v-else label="Base URL">
<el-input :model-value="baseURL" readonly />
</el-form-item> </el-form-item>
<template v-if="!showSync || form.syncModelAccount">
<span class="input-help model-account-helper">{{ $t('aiTools.model.modelAccountAddressHelper') }}</span>
<el-form-item
:label="$t('aiTools.agents.allowedOrigins')"
prop="modelAccountBaseURLType"
:rules="Rules.requiredSelect"
>
<el-radio-group v-model="form.modelAccountBaseURLType">
<el-radio-button
v-for="item in addressTypes"
:key="item.value"
:value="item.value"
:disabled="hostMode && item.value === 'container'"
>
{{ item.label }}
</el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item
v-if="form.modelAccountBaseURLType === 'custom'"
label="Base URL"
prop="modelAccountBaseURL"
:rules="customURLRules"
>
<el-input v-model.trim="form.modelAccountBaseURL" placeholder="http://192.168.1.100:8000/v1" />
</el-form-item>
<el-form-item v-else label="Base URL">
<el-input :model-value="baseURL" readonly />
</el-form-item>
</template>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
@@ -39,6 +44,7 @@ import i18n from '@/lang';
const props = defineProps<{ const props = defineProps<{
form: { form: {
name: string; name: string;
syncModelAccount?: boolean;
containerName: string; containerName: string;
modelAccountBaseURLType: string; modelAccountBaseURLType: string;
modelAccountBaseURL: string; modelAccountBaseURL: string;
@@ -46,6 +52,7 @@ const props = defineProps<{
hostPort: number; hostPort: number;
containerPort: string; containerPort: string;
hostMode?: boolean; hostMode?: boolean;
showSync?: boolean;
}>(); }>();
const systemIP = ref(''); const systemIP = ref('');
const addressTypes = computed(() => [ const addressTypes = computed(() => [
@@ -57,7 +57,7 @@
v-if="visible && view && view !== VllmPlaceholder && !failed && !missing" v-if="visible && view && view !== VllmPlaceholder && !failed && !missing"
ref="contentRef" ref="contentRef"
v-bind=" v-bind="
view === EngineConnection view === EngineConnection || view === AppParams
? {} ? {}
: view === ContainerLogs : view === ContainerLogs
? { highlightDiff: 210 } ? { highlightDiff: 210 }
@@ -100,6 +100,7 @@ import { hasPermission } from '@/utils/rbac';
import { hasManagePermissionAccess } from '@/utils/permission'; import { hasManagePermissionAccess } from '@/utils/permission';
import { newUUID } from '@/utils/id'; import { newUUID } from '@/utils/id';
import OllamaModels from '../../ollama/models/index.vue'; import OllamaModels from '../../ollama/models/index.vue';
import AppParams from '@/views/app-store/installed/detail/index.vue';
import EngineConnection from '../connection/index.vue'; import EngineConnection from '../connection/index.vue';
import TensorForm from '../../tensorrt/operate/index.vue'; import TensorForm from '../../tensorrt/operate/index.vue';
import VllmPlaceholder from '../../vllm/index.vue'; import VllmPlaceholder from '../../vllm/index.vue';
@@ -196,9 +197,11 @@ const open = async (
await openUpgrade(data.appInstallId, version); await openUpgrade(data.appInstallId, version);
return; return;
} }
const component = action === 'connection' ? EngineConnection : action === 'log' ? ContainerLogs : undefined; const component = { config: AppParams, connection: EngineConnection, log: ContainerLogs }[action];
view.value = component ? markRaw(component) : undefined; view.value = component ? markRaw(component) : undefined;
await nextTick(); await nextTick();
if (version !== request || !visible.value) return;
if (action === 'config') await contentRef.value?.acceptParams({ id: data.appInstallId, edit: true });
if (action === 'connection') if (action === 'connection')
contentRef.value?.acceptParams({ contentRef.value?.acceptParams({
name: data.name, name: data.name,
@@ -304,7 +307,9 @@ watch([currentNode, () => route.fullPath, isProductPro, isFxplay], () => {
}); });
const operationDisabled = (row: Deployment) => const operationDisabled = (row: Deployment) =>
deleteLoading.value || deleteLoading.value ||
['Installing', 'Starting', 'ReStarting', 'Restarting', 'Waiting', 'Uninstalling'].includes(row.status); ['Installing', 'Starting', 'ReStarting', 'Restarting', 'Rebuilding', 'Waiting', 'Uninstalling'].includes(
row.status,
);
const deleteDeployment = async () => { const deleteDeployment = async () => {
const version = request; const version = request;
const row = deleting.value; const row = deleting.value;
@@ -367,7 +372,7 @@ const returnToManagement = async () => {
const previousView = view.value; const previousView = view.value;
await nextTick(); await nextTick();
if (view.value !== previousView) return; if (view.value !== previousView) return;
if (actionName.value === 'task' || actionName.value === 'upgrade') emit('search'); if (['task', 'upgrade', 'config'].includes(actionName.value)) emit('search');
if (deleted.value || standalone.value) visible.value = false; if (deleted.value || standalone.value) visible.value = false;
else if (instance.value) open(instance.value); else if (instance.value) open(instance.value);
}; };
@@ -0,0 +1,156 @@
<template>
<DialogPro
v-model="visible"
:title="$t('commons.button.sync') + ' - ' + form.name"
:size="isMobile ? 'full' : 'normal'"
:show-close="!saving"
@close="close"
>
<div v-loading="loading">
<el-form v-if="form.id" ref="formRef" :model="form" label-position="top" :disabled="saving">
<AccountAddress
ref="addressRef"
:form="form"
:host-port="form.hostPort"
:container-port="form.containerPort"
:host-mode="form.hostMode"
/>
</el-form>
<el-alert v-else-if="!loading" type="error" :closable="false" :title="$t('aiTools.model.loadFailed')" />
</div>
<template #footer>
<el-button :disabled="saving" @click="close">{{ $t('commons.button.cancel') }}</el-button>
<el-button v-permission type="primary" :loading="saving" :disabled="loading || !form.id" @click="submit">
{{ $t('commons.button.sync') }}
</el-button>
</template>
</DialogPro>
</template>
<script setup lang="ts">
import { onBeforeUnmount, reactive, ref, watch } from 'vue';
import type { FormInstance } from 'element-plus';
import type { AI } from '@/api/interface/ai';
import { syncOllamaModelAccount } from '@/api/modules/ai';
import { checkAppInstalled } from '@/api/modules/app';
import { useGlobalStore } from '@/composables/useGlobalStore';
import { useRoute } from 'vue-router';
import i18n from '@/lang';
import { MsgError, MsgSuccess } from '@/utils/message';
import AccountAddress from '../account-address/index.vue';
import { loadVllmAPI, readPages, type Deployment } from '../../deployment';
const emit = defineEmits(['search']);
const { currentNode, isMobile } = useGlobalStore();
const route = useRoute();
const visible = ref(false);
const loading = ref(false);
const saving = ref(false);
const formRef = ref<FormInstance>();
const addressRef = ref<InstanceType<typeof AccountAddress>>();
const form = reactive({
id: 0,
engine: 'ollama' as 'ollama' | 'vllm',
name: '',
containerName: '',
hostPort: 0,
containerPort: '',
hostMode: false,
modelAccountBaseURLType: 'container' as AI.ModelAccountSync['modelAccountBaseURLType'],
modelAccountBaseURL: '',
});
let request = 0;
const close = () => {
request++;
visible.value = false;
saving.value = false;
};
const open = async (row: Deployment) => {
if (row.engine !== 'ollama' && row.engine !== 'vllm') return;
const version = ++request;
form.id = 0;
form.name = row.name;
form.engine = row.engine;
visible.value = true;
loading.value = true;
try {
if (row.engine === 'ollama') {
const { data: item } = await checkAppInstalled('ollama', row.name);
if (version !== request) return;
if (!item.isExist || item.appInstallId !== row.id) {
MsgError(i18n.global.t('commons.res.notFound'));
return;
}
Object.assign(form, {
id: item.appInstallId,
containerName: item.containerName,
hostPort: item.httpPort,
containerPort: '11434',
hostMode: false,
modelAccountBaseURLType: item.modelAccountBaseURLType || 'container',
modelAccountBaseURL: item.modelAccountBaseURL || '',
});
} else {
const api = await loadVllmAPI();
if (version !== request) return;
const items = await readPages((page) => api.pageVllm({ ...page, info: row.name }));
if (version !== request) return;
const item = items.find((item) => item.id === row.id);
if (!item) {
MsgError(i18n.global.t('commons.res.notFound'));
return;
}
const hostMode = item.appVersion.toLowerCase().startsWith('nvidia-gb10-dspark-');
Object.assign(form, {
id: item.id,
containerName: item.containerName,
hostPort: item.port,
containerPort:
item.command.match(/--port\s+(\d+)/)?.[1] || (item.imageType === 'intel' ? '8001' : '8000'),
hostMode,
modelAccountBaseURLType:
hostMode && item.modelAccountBaseURLType === 'container'
? 'systemIP'
: item.modelAccountBaseURLType || (hostMode ? 'systemIP' : 'container'),
modelAccountBaseURL: item.modelAccountBaseURL || '',
});
}
} finally {
if (version === request) loading.value = false;
}
};
const submit = async () => {
if (loading.value || saving.value || !form.id || !formRef.value) return;
const version = request;
saving.value = true;
try {
const valid = await formRef.value.validate().catch(() => false);
if (!valid || version !== request || !visible.value) return;
const req: AI.ModelAccountSync = {
id: form.id,
modelAccountBaseURLType: form.modelAccountBaseURLType,
modelAccountBaseURL: addressRef.value.baseURL,
};
if (form.engine === 'ollama') {
await syncOllamaModelAccount(req);
} else {
const api = await loadVllmAPI();
if (version !== request || !visible.value) return;
await api.operateVllm({ ...req, operate: 'sync', taskID: '' });
}
if (version !== request) return;
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
emit('search');
close();
} finally {
if (version === request) saving.value = false;
}
};
watch([currentNode, () => route.fullPath], close);
onBeforeUnmount(close);
defineExpose({ open });
</script>
+7 -1
View File
@@ -17,6 +17,7 @@ export interface Deployment {
interface VllmItem { interface VllmItem {
appInstallId: number; appInstallId: number;
agentAccountId: number;
path: string; path: string;
upgradable: boolean; upgradable: boolean;
id: number; id: number;
@@ -25,6 +26,9 @@ interface VllmItem {
appVersion: string; appVersion: string;
port: number; port: number;
modelAccountBaseURL: string; modelAccountBaseURL: string;
modelAccountBaseURLType: AI.ModelAccountSync['modelAccountBaseURLType'];
command: string;
imageType: string;
containerName: string; containerName: string;
modelDir: string; modelDir: string;
status: string; status: string;
@@ -32,7 +36,9 @@ interface VllmItem {
interface VllmAPI { interface VllmAPI {
pageVllm: (req: ReqPage & { info: string }) => Promise<ResultData<ResPage<VllmItem>>>; pageVllm: (req: ReqPage & { info: string }) => Promise<ResultData<ResPage<VllmItem>>>;
operateVllm: (req: { id: number; operate: string; taskID: string; forceDelete?: boolean }) => Promise<unknown>; operateVllm: (
req: { id: number; operate: string; taskID: string; forceDelete?: boolean } & Partial<AI.ModelAccountSync>,
) => Promise<unknown>;
} }
const vllmModules = import.meta.glob<VllmAPI>('@/xpack/api/modules/vllm.ts'); const vllmModules = import.meta.glob<VllmAPI>('@/xpack/api/modules/vllm.ts');
+21 -9
View File
@@ -281,7 +281,7 @@
:buttons="deploymentButtons" :buttons="deploymentButtons"
:label="$t('commons.table.operate')" :label="$t('commons.table.operate')"
fixed="right" fixed="right"
:ellipsis="4" :ellipsis="2"
width="240" width="240"
/> />
</ComplexTable> </ComplexTable>
@@ -305,6 +305,7 @@
<AppResources :key="`resources:${currentNode}:${route.fullPath}`" ref="appResourcesRef" @close="search" /> <AppResources :key="`resources:${currentNode}:${route.fullPath}`" ref="appResourcesRef" @close="search" />
<TaskLog ref="taskRef" width="70%" @close="search" /> <TaskLog ref="taskRef" width="70%" @close="search" />
<ModelPool ref="poolRef" @updated="search" /> <ModelPool ref="poolRef" @updated="search" />
<SyncService ref="syncRef" @search="search" />
<component :is="templateView" v-if="templateView" ref="templateRef" /> <component :is="templateView" v-if="templateView" ref="templateRef" />
</div> </div>
</template> </template>
@@ -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 AppDelete from '@/views/app-store/installed/delete/index.vue';
import AppResources from '@/views/app-store/installed/check/index.vue'; import AppResources from '@/views/app-store/installed/check/index.vue';
import ModelPool from './components/model-pool/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 ProviderLabel from '@/components/agent-provider-label/index.vue';
import { loadModelOverview, loadVllmAPI, readPages, vllmAvailable, type Deployment } from './deployment'; import { loadModelOverview, loadVllmAPI, readPages, vllmAvailable, type Deployment } from './deployment';
@@ -366,6 +368,7 @@ const upgradeRef = ref();
const appDeleteRef = ref(); const appDeleteRef = ref();
const appResourcesRef = ref(); const appResourcesRef = ref();
const poolRef = ref(); const poolRef = ref();
const syncRef = ref<InstanceType<typeof SyncService>>();
const templateRef = ref(); const templateRef = ref();
const templateView = shallowRef(); const templateView = shallowRef();
const loading = ref(false); const loading = ref(false);
@@ -566,7 +569,9 @@ const taskRef = ref();
const pending = ref(''); const pending = ref('');
const operationDisabled = (row: Deployment) => const operationDisabled = (row: Deployment) =>
Boolean(pending.value) || 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) => const statusDisabled = (row: Deployment) =>
operationDisabled(row) || !hasManagePermissionAccess() || (row.engine === 'ollama' && !hasPermission('app_manage')); operationDisabled(row) || !hasManagePermissionAccess() || (row.engine === 'ollama' && !hasPermission('app_manage'));
const operate = async (row: Deployment, operation: 'start' | 'stop' | 'restart') => { const operate = async (row: Deployment, operation: 'start' | 'stop' | 'restart') => {
@@ -601,23 +606,30 @@ const operate = async (row: Deployment, operation: 'start' | 'stop' | 'restart')
} }
}; };
const deploymentButtons = [ 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'), label: i18n.global.t('aiTools.model.manage'),
show: (row: Deployment) => row.engine !== 'vllm', show: (row: Deployment) => row.engine !== 'vllm',
click: manageDeployment, 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'), label: i18n.global.t('menu.monitor'),
show: (row: Deployment) => row.engine === 'vllm', show: (row: Deployment) => row.engine === 'vllm',
click: (row: Deployment) => manageRef.value.open(row, 'monitor', true), 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'), label: i18n.global.t('commons.button.log'),
click: (row: Deployment) => manageRef.value.open(row, 'log', true), click: (row: Deployment) => manageRef.value.open(row, 'log', true),
@@ -9,6 +9,7 @@
<template #after-params> <template #after-params>
<AccountAddress <AccountAddress
ref="accountRef" ref="accountRef"
show-sync
:form="form" :form="form"
:host-port="Number(form.params.PANEL_APP_PORT_HTTP) || 11434" :host-port="Number(form.params.PANEL_APP_PORT_HTTP) || 11434"
container-port="11434" container-port="11434"
@@ -51,6 +52,7 @@ const form = ref({
name: '', name: '',
params: {} as Record<string, any>, params: {} as Record<string, any>,
containerName: '', containerName: '',
syncModelAccount: true,
modelAccountBaseURLType: 'container', modelAccountBaseURLType: 'container',
modelAccountBaseURL: '', modelAccountBaseURL: '',
}); });
@@ -68,6 +70,7 @@ const openCreate = async () => {
formRef.value.resetForm(); formRef.value.resetForm();
formRef.value.setFormData({ formRef.value.setFormData({
restartPolicy: 'unless-stopped', restartPolicy: 'unless-stopped',
syncModelAccount: true,
modelAccountBaseURLType: 'container', modelAccountBaseURLType: 'container',
modelAccountBaseURL: '', modelAccountBaseURL: '',
}); });
@@ -98,6 +98,15 @@
<el-input v-else v-model.trim="paramModel.params[p.key]" :disabled="!p.edit"></el-input> <el-input v-else v-model.trim="paramModel.params[p.key]" :disabled="!p.edit"></el-input>
</el-form-item> </el-form-item>
</div> </div>
<AccountAddress
v-if="appKey === 'ollama'"
ref="accountRef"
show-sync
:form="paramModel"
:host-port="Number(paramModel.params.PANEL_APP_PORT_HTTP) || paramModel.httpPort"
container-port="11434"
:host-mode="paramModel.isHostMode"
/>
<el-form-item prop="advanced"> <el-form-item prop="advanced">
<el-checkbox v-model="paramModel.advanced" :label="$t('app.advanced')" size="large" /> <el-checkbox v-model="paramModel.advanced" :label="$t('app.advanced')" size="large" />
</el-form-item> </el-form-item>
@@ -179,11 +188,12 @@
</DrawerPro> </DrawerPro>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import AccountAddress from '@/views/ai/model/components/account-address/index.vue';
import Diff from '@/views/app-store/installed/upgrade/diff/index.vue'; import Diff from '@/views/app-store/installed/upgrade/diff/index.vue';
import { App } from '@/api/interface/app'; import { App } from '@/api/interface/app';
import { getAppInstallParams, updateAppInstallParams, updateInstallConfig } from '@/api/modules/app'; import { getAppInstallParams, updateAppInstallParams, updateInstallConfig } from '@/api/modules/app';
import { reactive, ref } from 'vue'; import { onBeforeUnmount, reactive, ref } from 'vue';
import { FormInstance } from 'element-plus'; import { FormInstance } from 'element-plus';
import { Rules, checkNumberRange } from '@/global/form-rules'; import { Rules, checkNumberRange } from '@/global/form-rules';
import { MsgError, MsgSuccess } from '@/utils/message'; import { MsgError, MsgSuccess } from '@/utils/message';
@@ -192,10 +202,12 @@ import { splitHttp, checkIpV4V6, checkDomain } from '@/utils/validate';
import i18n from '@/lang'; import i18n from '@/lang';
import { loadResourceLimit } from '@/api/modules/container'; import { loadResourceLimit } from '@/api/modules/container';
import { Container } from '@/api/interface/container'; import { Container } from '@/api/interface/container';
import { useGlobalStore } from '@/composables/useGlobalStore';
interface ParamProps { interface ParamProps {
id: number; id: number;
app: any; app?: any;
edit?: boolean;
} }
const paramData = ref<ParamProps>({ const paramData = ref<ParamProps>({
id: 0, id: 0,
@@ -207,6 +219,7 @@ interface EditForm extends App.InstallParams {
} }
const emit = defineEmits(['close']); const emit = defineEmits(['close']);
const { currentNode } = useGlobalStore();
const open = ref(false); const open = ref(false);
const loading = ref(false); const loading = ref(false);
const params = ref<EditForm[]>(); const params = ref<EditForm[]>();
@@ -222,10 +235,9 @@ const rules = reactive({
containerName: [Rules.containerName], containerName: [Rules.containerName],
restartPolicy: [Rules.requiredSelect], restartPolicy: [Rules.requiredSelect],
}); });
const submitModel = reactive<any>({
webUI: '',
});
const appType = ref(''); const appType = ref('');
const appKey = ref('');
const accountRef = ref<InstanceType<typeof AccountAddress>>();
const appConfigUpdate = ref<App.AppConfigUpdate>({ const appConfigUpdate = ref<App.AppConfigUpdate>({
installID: 0, installID: 0,
webUI: '', webUI: '',
@@ -285,15 +297,15 @@ function checkPort(port: string) {
} }
const acceptParams = async (props: ParamProps) => { const acceptParams = async (props: ParamProps) => {
submitModel.installId = props.id;
params.value = []; params.value = [];
paramData.value.id = props.id; paramData.value.id = props.id;
paramModel.params = {}; paramModel.params = {};
edit.value = false; edit.value = false;
rules.params = {}; rules.params = {};
await get(); if (!(await get())) return;
open.value = true; open.value = true;
openConfig.value = false; openConfig.value = false;
if (props.edit) editParam();
}; };
const handleClose = () => { const handleClose = () => {
@@ -358,6 +370,7 @@ const get = async () => {
paramModel.allowPort = res.data.allowPort; paramModel.allowPort = res.data.allowPort;
paramModel.containerName = res.data.containerName; paramModel.containerName = res.data.containerName;
paramModel.advanced = false; paramModel.advanced = false;
paramModel.editCompose = false;
paramModel.dockerCompose = res.data.dockerCompose; paramModel.dockerCompose = res.data.dockerCompose;
paramModel.isHostMode = res.data.hostMode; paramModel.isHostMode = res.data.hostMode;
paramModel.specifyIP = res.data.specifyIP; paramModel.specifyIP = res.data.specifyIP;
@@ -372,7 +385,15 @@ const get = async () => {
webUI.protocol = httpConfig.proto + '://'; webUI.protocol = httpConfig.proto + '://';
} }
appType.value = res.data.type; appType.value = res.data.type;
appKey.value = res.data.appKey;
paramModel.name = res.data.name;
paramModel.httpPort = res.data.httpPort;
paramModel.syncModelAccount = res.data.syncModelAccount;
paramModel.modelAccountBaseURLType = res.data.modelAccountBaseURLType || 'container';
paramModel.modelAccountBaseURL = res.data.modelAccountBaseURL;
return true;
} catch (error) { } catch (error) {
return false;
} finally { } finally {
loading.value = false; loading.value = false;
} }
@@ -380,6 +401,8 @@ const get = async () => {
const submit = async (formEl: FormInstance) => { const submit = async (formEl: FormInstance) => {
if (!formEl) return; if (!formEl) return;
const node = currentNode.value;
const installId = paramData.value.id;
await formEl.validate((valid) => { await formEl.validate((valid) => {
if (!valid) { if (!valid) {
return; return;
@@ -389,7 +412,13 @@ const submit = async (formEl: FormInstance) => {
cancelButtonText: i18n.global.t('commons.button.cancel'), cancelButtonText: i18n.global.t('commons.button.cancel'),
type: 'info', type: 'info',
}).then(async () => { }).then(async () => {
submitModel.params = paramModel.params; if (!open.value || node !== currentNode.value || installId !== paramData.value.id) return;
const submitModel: Record<string, any> = { installId, params: paramModel.params };
if (appKey.value === 'ollama') {
submitModel.syncModelAccount = paramModel.syncModelAccount;
submitModel.modelAccountBaseURLType = paramModel.modelAccountBaseURLType;
submitModel.modelAccountBaseURL = accountRef.value.baseURL;
}
if (paramModel.advanced) { if (paramModel.advanced) {
submitModel.advanced = paramModel.advanced; submitModel.advanced = paramModel.advanced;
submitModel.memoryLimit = paramModel.memoryLimit; submitModel.memoryLimit = paramModel.memoryLimit;
@@ -469,6 +498,10 @@ const changeUnit = () => {
}; };
defineExpose({ acceptParams }); defineExpose({ acceptParams });
onBeforeUnmount(() => {
open.value = false;
});
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>