mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-10-28 16:00:18 +00:00
Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
50955e20b7 | ||
|
|
bd67614a53 | ||
|
|
14dd3135ec |
@@ -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
|
||||||
|
|||||||
@@ -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"`
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -118,6 +118,7 @@ func agentDBMigrations() []*gormigrate.Migration {
|
|||||||
migrations.AddNativeAgentInstalls,
|
migrations.AddNativeAgentInstalls,
|
||||||
migrations.AddQuickDirectory,
|
migrations.AddQuickDirectory,
|
||||||
migrations.AddLocalModelAccountLink,
|
migrations.AddLocalModelAccountLink,
|
||||||
|
migrations.AddOllamaModelAccountSync,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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/[^/]+/[^/]+$`,
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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`,
|
||||||
|
|||||||
@@ -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.',
|
||||||
|
|||||||
@@ -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.',
|
||||||
|
|||||||
@@ -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:
|
||||||
'زمانسنج مستقل هر نقطه، شامل راهاندازی، گرمکردن و همه درخواستها؛ محدود به زمان کلی باقیمانده.',
|
'زمانسنج مستقل هر نقطه، شامل راهاندازی، گرمکردن و همه درخواستها؛ محدود به زمان کلی باقیمانده.',
|
||||||
|
|||||||
@@ -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:
|
||||||
'各測定点で個別に計時し、起動、ウォームアップ、全リクエストを含みます。全体の残り時間にも制限されます。',
|
'各測定点で個別に計時し、起動、ウォームアップ、全リクエストを含みます。全体の残り時間にも制限されます。',
|
||||||
|
|||||||
@@ -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: '준비 후 시작하며 모든 측정점과 대기 시간을 포함합니다. 시간 초과 시 테스트를 중단합니다.',
|
||||||
|
|||||||
@@ -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: 'ເລີ່ມຫຼັງກຽມການ ລວມທຸກຈຸດແລະໄລຍະຫ່າງ. ຢຸດທົດສອບເມື່ອໝົດເວລາ.',
|
||||||
|
|||||||
@@ -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.',
|
||||||
|
|||||||
@@ -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.',
|
||||||
|
|||||||
@@ -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:
|
||||||
'Отдельный отсчёт для каждой точки, включая запуск, прогрев и запросы; ограничен оставшимся общим временем.',
|
'Отдельный отсчёт для каждой точки, включая запуск, прогрев и запросы; ограничен оставшимся общим временем.',
|
||||||
|
|||||||
@@ -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.',
|
||||||
|
|||||||
@@ -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: '準備完成後計時,涵蓋所有測點及間隔,逾時後停止。',
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -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');
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user