refactor(ai): move model preparation to XPack and reorganize model views (#14306)

This commit is contained in:
ssongliu
2026-11-05 16:27:06 +08:00
committed by GitHub
parent d658395188
commit fe3953963e
69 changed files with 564 additions and 6393 deletions
+3 -4
View File
@@ -18,10 +18,9 @@ var (
appInstallService = service.NewIAppInstalledService()
appIgnoreUpgradeService = service.NewIAppIgnoreUpgradeService()
modelDownloaderService = service.NewIModelDownloaderService()
aiToolService = service.NewIAIToolService()
mcpServerService = service.NewIMcpServerService()
agentService = service.NewIAgentService()
aiToolService = service.NewIAIToolService()
mcpServerService = service.NewIMcpServerService()
agentService = service.NewIAgentService()
containerService = service.NewIContainerService()
composeTemplateService = service.NewIComposeTemplateService()
-399
View File
@@ -1,399 +0,0 @@
package v2
import (
"github.com/1Panel-dev/1Panel/agent/app/api/v2/helper"
"github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/1Panel-dev/1Panel/agent/app/service"
"github.com/gin-gonic/gin"
)
func handleModelDownloaderError(c *gin.Context, err error) {
if service.IsModelDownloaderInputError(err) {
helper.BadRequest(c, err)
return
}
helper.InternalServer(c, err)
}
// @Tags AI
// @Summary Search Hugging Face models
// @Accept json
// @Param request body dto.ModelSearch true "request"
// @Produce json
// @Success 200 {object} dto.PageResult{items=[]dto.ModelItem}
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /ai/model/downloader/hf/search [post]
func (b *BaseApi) SearchHFModels(c *gin.Context) {
var req dto.ModelSearch
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
total, items, err := modelDownloaderService.SearchHFModels(req)
if err != nil {
handleModelDownloaderError(c, err)
return
}
helper.SuccessWithData(c, dto.PageResult{Total: total, Items: items})
}
// @Tags AI
// @Summary Get Hugging Face model details
// @Accept json
// @Param request body dto.ModelInfoReq true "request"
// @Produce json
// @Success 200 {object} dto.ModelInfo
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /ai/model/downloader/hf/info [post]
func (b *BaseApi) GetHFModelInfo(c *gin.Context) {
var req dto.ModelInfoReq
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
item, err := modelDownloaderService.GetHFModelInfo(req)
if err != nil {
handleModelDownloaderError(c, err)
return
}
helper.SuccessWithData(c, item)
}
// @Tags AI
// @Summary Download Hugging Face model
// @Description Creates an asynchronous download task. Query /ai/model/downloader/tasks/search for progress.
// @Accept json
// @Param request body dto.ModelDownload true "request"
// @Produce json
// @Success 200 {object} dto.ModelDownloadTaskItem
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /ai/model/downloader/hf/download [post]
// @x-panel-log {"bodyKeys":["repoID","revision"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"下载 Hugging Face 模型 [repoID]","formatEN":"download Hugging Face model [repoID]"}
func (b *BaseApi) StartHFModelDownload(c *gin.Context) {
var req dto.ModelDownload
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
task, err := modelDownloaderService.StartDownload(req)
if err != nil {
handleModelDownloaderError(c, err)
return
}
helper.SuccessWithData(c, task)
}
// @Tags AI
// @Summary Search ModelScope models
// @Accept json
// @Param request body dto.ModelSearch true "request"
// @Produce json
// @Success 200 {object} dto.PageResult{items=[]dto.ModelItem}
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /ai/model/downloader/modelscope/search [post]
func (b *BaseApi) SearchModelScopeModels(c *gin.Context) {
var req dto.ModelSearch
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
total, items, err := modelDownloaderService.SearchModelScopeModels(req)
if err != nil {
handleModelDownloaderError(c, err)
return
}
helper.SuccessWithData(c, dto.PageResult{Total: total, Items: items})
}
// @Tags AI
// @Summary Get ModelScope model details
// @Accept json
// @Param request body dto.ModelInfoReq true "request"
// @Produce json
// @Success 200 {object} dto.ModelInfo
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /ai/model/downloader/modelscope/info [post]
func (b *BaseApi) GetModelScopeModelInfo(c *gin.Context) {
var req dto.ModelInfoReq
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
item, err := modelDownloaderService.GetModelScopeModelInfo(req)
if err != nil {
handleModelDownloaderError(c, err)
return
}
helper.SuccessWithData(c, item)
}
// @Tags AI
// @Summary Download ModelScope model
// @Description Creates an asynchronous download task. Query /ai/model/downloader/tasks/search for progress.
// @Accept json
// @Param request body dto.ModelDownload true "request"
// @Produce json
// @Success 200 {object} dto.ModelDownloadTaskItem
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /ai/model/downloader/modelscope/download [post]
// @x-panel-log {"bodyKeys":["repoID","revision"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"下载 ModelScope 模型 [repoID]","formatEN":"download ModelScope model [repoID]"}
func (b *BaseApi) StartModelScopeModelDownload(c *gin.Context) {
var req dto.ModelDownload
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
task, err := modelDownloaderService.StartModelScopeDownload(req)
if err != nil {
handleModelDownloaderError(c, err)
return
}
helper.SuccessWithData(c, task)
}
// @Tags AI
// @Summary Get model downloader settings
// @Produce json
// @Success 200 {object} dto.ModelDownloaderSetting
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /ai/model/downloader/settings [get]
func (b *BaseApi) GetModelDownloaderSettings(c *gin.Context) {
settings, err := modelDownloaderService.GetSettings()
if err != nil {
handleModelDownloaderError(c, err)
return
}
helper.SuccessWithData(c, settings)
}
// @Tags AI
// @Summary Update model downloader settings
// @Description Updates the download directory, source endpoints and tokens. The featuredModelsSync field is ignored; use /ai/model/downloader/featured/sync/status to update automatic synchronization.
// @Accept json
// @Param request body dto.ModelDownloaderSetting true "request"
// @Produce json
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /ai/model/downloader/settings [post]
// @x-panel-log {"bodyKeys":["modelDir"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新模型下载设置 [modelDir]","formatEN":"update model downloader settings [modelDir]"}
func (b *BaseApi) UpdateModelDownloaderSettings(c *gin.Context) {
var req dto.ModelDownloaderSetting
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := modelDownloaderService.UpdateSettings(req); err != nil {
handleModelDownloaderError(c, err)
return
}
helper.Success(c)
}
// @Tags AI
// @Summary Page model download tasks
// @Description Set includeLocal to true to include locally downloaded models.
// @Accept json
// @Param request body dto.ModelDownloadTaskSearch true "request"
// @Produce json
// @Success 200 {object} dto.PageResult{items=[]dto.ModelDownloadTaskItem}
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /ai/model/downloader/tasks/search [post]
func (b *BaseApi) PageModelDownloadTasks(c *gin.Context) {
var req dto.ModelDownloadTaskSearch
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
total, items, err := modelDownloaderService.PageTasks(req)
if err != nil {
handleModelDownloaderError(c, err)
return
}
helper.SuccessWithData(c, dto.PageResult{Items: items, Total: total})
}
// @Tags AI
// @Summary Cancel model download task
// @Description Only waiting or downloading tasks can be canceled. The withFile field is ignored.
// @Accept json
// @Param request body dto.ModelDownloadTaskOperate true "request"
// @Produce json
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /ai/model/downloader/tasks/cancel [post]
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"model_download_tasks","output_column":"repo_id","output_value":"repoID"}],"formatZH":"取消模型下载任务 [repoID]","formatEN":"cancel model download task [repoID]"}
func (b *BaseApi) CancelModelDownloadTask(c *gin.Context) {
var req dto.ModelDownloadTaskOperate
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := modelDownloaderService.CancelTask(req); err != nil {
handleModelDownloaderError(c, err)
return
}
helper.Success(c)
}
// @Tags AI
// @Summary Retry model download task
// @Description Only failed or canceled tasks can be retried. Returns the task while the download runs asynchronously.
// @Accept json
// @Param request body dto.ModelDownloadTaskRetry true "request"
// @Produce json
// @Success 200 {object} dto.ModelDownloadTaskItem
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /ai/model/downloader/tasks/retry [post]
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"model_download_tasks","output_column":"repo_id","output_value":"repoID"}],"formatZH":"重试模型下载任务 [repoID]","formatEN":"retry model download task [repoID]"}
func (b *BaseApi) RetryModelDownloadTask(c *gin.Context) {
var req dto.ModelDownloadTaskRetry
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
task, err := modelDownloaderService.RetryTask(req)
if err != nil {
handleModelDownloaderError(c, err)
return
}
helper.SuccessWithData(c, task)
}
// @Tags AI
// @Summary Remove model download task
// @Description Waiting or downloading tasks cannot be removed. Set withFile to true to also delete downloaded files.
// @Accept json
// @Param request body dto.ModelDownloadTaskOperate true "request"
// @Produce json
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /ai/model/downloader/tasks/remove [post]
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"model_download_tasks","output_column":"repo_id","output_value":"repoID"}],"formatZH":"移除模型下载任务 [repoID]","formatEN":"remove model download task [repoID]"}
func (b *BaseApi) RemoveModelDownloadTask(c *gin.Context) {
var req dto.ModelDownloadTaskOperate
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := modelDownloaderService.RemoveTask(req); err != nil {
handleModelDownloaderError(c, err)
return
}
helper.Success(c)
}
// @Tags AI
// @Summary Page locally downloaded models
// @Accept json
// @Param request body dto.LocalModelSearch true "request"
// @Produce json
// @Success 200 {object} dto.PageResult{items=[]dto.LocalModelItem}
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /ai/model/downloader/local/search [post]
func (b *BaseApi) PageLocalDownloadedModels(c *gin.Context) {
var req dto.LocalModelSearch
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
total, items, err := modelDownloaderService.ListLocalModels(req)
if err != nil {
handleModelDownloaderError(c, err)
return
}
helper.SuccessWithData(c, dto.PageResult{Items: items, Total: total})
}
func (b *BaseApi) ListLocalModelOptions(c *gin.Context) {
items, err := modelDownloaderService.ListLocalModelOptions()
if err != nil {
handleModelDownloaderError(c, err)
return
}
helper.SuccessWithData(c, items)
}
// @Tags AI
// @Summary Delete locally downloaded model
// @Accept json
// @Param request body dto.LocalModelDelete true "request"
// @Produce json
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /ai/model/downloader/local/delete [post]
// @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"删除本地模型 [name]","formatEN":"delete local model [name]"}
func (b *BaseApi) DeleteLocalDownloadedModel(c *gin.Context) {
var req dto.LocalModelDelete
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := modelDownloaderService.DeleteLocalModel(req); err != nil {
handleModelDownloaderError(c, err)
return
}
helper.Success(c)
}
// @Tags AI
// @Summary Synchronize featured models
// @Description Synchronizes the featured model catalog immediately, independently of the automatic synchronization setting. Does nothing in offline mode.
// @Accept json
// @Produce json
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /ai/model/downloader/featured/sync [post]
func (b *BaseApi) SyncFeaturedModels(c *gin.Context) {
if err := modelDownloaderService.SyncFeaturedModels(); err != nil {
helper.InternalServer(c, err)
return
}
helper.Success(c)
}
// @Tags AI
// @Summary Page featured models
// @Accept json
// @Param request body dto.FeaturedModelSearch true "request"
// @Produce json
// @Success 200 {object} dto.FeaturedModelPage{items=[]dto.FeaturedModel}
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /ai/model/downloader/featured/search [post]
func (b *BaseApi) PageFeaturedModels(c *gin.Context) {
var req dto.FeaturedModelSearch
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
result, err := modelDownloaderService.PageFeaturedModels(req)
if err != nil {
helper.InternalServer(c, err)
return
}
helper.SuccessWithData(c, result)
}
// @Tags AI
// @Summary Update featured model automatic synchronization
// @Description Set value to Enable or Disable. This updates the automatic synchronization setting without triggering an immediate synchronization.
// @Accept json
// @Param request body dto.ModelDownloaderSyncSetting true "request"
// @Produce json
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /ai/model/downloader/featured/sync/status [post]
// @x-panel-log {"bodyKeys":["value"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新推荐模型自动同步 [value]","formatEN":"update featured model automatic synchronization [value]"}
func (b *BaseApi) UpdateFeaturedModelsSync(c *gin.Context) {
var req dto.ModelDownloaderSyncSetting
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := modelDownloaderService.UpdateFeaturedModelsSync(req); err != nil {
handleModelDownloaderError(c, err)
return
}
helper.Success(c)
}
-153
View File
@@ -1,153 +0,0 @@
package dto
type ModelSearch struct {
Query string `json:"query"`
Sort string `json:"sort" validate:"omitempty,oneof=trending downloads updated created likes most_params least_params"`
Page int `json:"page" validate:"omitempty,min=1"`
PageSize int `json:"pageSize" validate:"omitempty,min=1,max=100"`
Limit int `json:"limit" validate:"omitempty,min=1,max=100"`
}
type ModelInfoReq struct {
RepoID string `json:"repoID" validate:"required"`
Revision string `json:"revision"`
}
type ModelDownload struct {
DownloadType string `json:"downloadType" validate:"omitempty,oneof=all tokenizer"`
RepoID string `json:"repoID" validate:"required"`
Revision string `json:"revision"`
}
type ModelDownloaderSyncSetting struct {
Value string `json:"value" validate:"required,oneof=Enable Disable"`
}
type ModelDownloaderSetting struct {
FeaturedModelsSync string `json:"featuredModelsSync"`
ModelDir string `json:"modelDir" validate:"required"`
HFEndpoint string `json:"hfEndpoint"`
HFToken string `json:"hfToken"`
ModelScopeEndpoint string `json:"modelScopeEndpoint"`
ModelScopeToken string `json:"modelScopeToken"`
}
type ModelItem struct {
RepoID string `json:"repoID"`
Name string `json:"name"`
Downloads int64 `json:"downloads"`
Likes int64 `json:"likes"`
Size int64 `json:"size"`
SizeFormatted string `json:"sizeFormatted"`
}
type ModelFile struct {
Name string `json:"name"`
Size int64 `json:"size"`
SizeFormatted string `json:"sizeFormatted"`
}
type ModelInfo struct {
ModelItem
ModelCard string `json:"modelCard"`
Files []ModelFile `json:"files"`
}
type ModelDownloadTaskSearch struct {
IDs []uint `json:"ids"`
IncludeLocal bool `json:"includeLocal"`
DownloadType string `json:"downloadType" validate:"omitempty,oneof=all tokenizer"`
PageInfo
Info string `json:"info"`
Status string `json:"status"`
}
type ModelDownloadTaskOperate struct {
WithFile bool `json:"withFile"`
ID uint `json:"id" validate:"required"`
}
type ModelDownloadTaskRetry struct {
ID uint `json:"id" validate:"required"`
}
type ModelDownloadTaskItem struct {
LocalModel *LocalModelItem `json:"localModel,omitempty"`
DownloadType string `json:"downloadType"`
ID uint `json:"id"`
Source string `json:"source"`
RepoID string `json:"repoID"`
Revision string `json:"revision"`
ModelName string `json:"modelName"`
TargetDir string `json:"targetDir"`
Status string `json:"status"`
Progress float64 `json:"progress"`
TotalSize int64 `json:"totalSize"`
DownloadedSize int64 `json:"downloadedSize"`
ErrorMessage string `json:"errorMessage"`
CreatedAt string `json:"createdAt"`
StartedAt string `json:"startedAt"`
CompletedAt string `json:"completedAt"`
CanCancel bool `json:"canCancel"`
CanRetry bool `json:"canRetry"`
CanRemoveRecord bool `json:"canRemoveRecord"`
}
type LocalModelOption struct {
Name string `json:"name"`
Organization string `json:"organization"`
Path string `json:"path"`
}
type LocalModelSearch struct {
DownloadType string `json:"downloadType" validate:"omitempty,oneof=all tokenizer"`
PageInfo
Info string `json:"info"`
}
type LocalModelItem struct {
DownloadType string `json:"downloadType"`
RepoID string `json:"repoID"`
Source string `json:"source"`
Revision string `json:"revision"`
Name string `json:"name"`
Path string `json:"path"`
Size int64 `json:"size"`
SizeFormatted string `json:"sizeFormatted"`
CreatedAt string `json:"createdAt"`
}
type LocalModelDelete struct {
WithFile bool `json:"withFile"`
DownloadType string `json:"downloadType" validate:"omitempty,oneof=all tokenizer"`
Name string `json:"name" validate:"required"`
}
type FeaturedModel struct {
Key string `json:"key" yaml:"key"`
Sort int `json:"sort" yaml:"sort"`
Family string `json:"family" yaml:"category"`
RepoID string `json:"repoID" yaml:"name"`
WeightProvider string `json:"weightProvider" yaml:"weightProvider"`
Sources []string `json:"sources" yaml:"sources"`
SourceModelIDs map[string]string `json:"sourceModelIds" yaml:"sourceModelIds"`
Architecture string `json:"architecture" yaml:"modelType"`
BackboneParams string `json:"backboneParams" yaml:"moeBackboneParameters"`
ActiveParams string `json:"activeParams" yaml:"activeParameters"`
Engram string `json:"engram" yaml:"engramParameters"`
WeightGB float64 `json:"weightGB" yaml:"approximateWeightSizeGB"`
Description map[string]string `json:"description" yaml:"description"`
}
type FeaturedModelSearch struct {
PageInfo
Family string `json:"family"`
WeightProvider string `json:"weightProvider"`
Info string `json:"info"`
}
type FeaturedModelPage struct {
PageResult
Families []string `json:"families"`
WeightProviders []string `json:"weightProviders"`
}
-18
View File
@@ -1,18 +0,0 @@
package model
type FeaturedModel struct {
BaseModel
Key string `json:"key"`
Sort int `json:"sort"`
Family string `json:"family"`
RepoID string `json:"repoID"`
WeightProvider string `json:"weightProvider"`
Sources []string `json:"sources" gorm:"serializer:json"`
SourceModelIDs map[string]string `json:"sourceModelIds" gorm:"serializer:json"`
Architecture string `json:"architecture"`
BackboneParams string `json:"backboneParams"`
ActiveParams string `json:"activeParams"`
Engram string `json:"engram"`
WeightGB float64 `json:"weightGB"`
Description string `json:"description"`
}
-20
View File
@@ -1,20 +0,0 @@
package model
import "time"
type ModelDownloadTask struct {
DownloadType string `json:"downloadType"`
BaseModel
Source string `json:"source" gorm:"type:varchar(32);not null;index"`
RepoID string `json:"repoID" gorm:"type:varchar(255);not null;index"`
Revision string `json:"revision" gorm:"type:varchar(128)"`
ModelName string `json:"modelName" gorm:"type:varchar(255);not null;index"`
TargetDir string `json:"targetDir" gorm:"type:text"`
Status string `json:"status" gorm:"type:varchar(32);not null;index"`
Progress float64 `json:"progress"`
TotalSize int64 `json:"totalSize"`
DownloadedSize int64 `json:"downloadedSize"`
ErrorMessage string `json:"errorMessage" gorm:"type:text"`
StartedAt *time.Time `json:"startedAt"`
CompletedAt *time.Time `json:"completedAt"`
}
-70
View File
@@ -1,70 +0,0 @@
package repo
import (
"strings"
"github.com/1Panel-dev/1Panel/agent/app/model"
"github.com/1Panel-dev/1Panel/agent/global"
"gorm.io/gorm"
)
type FeaturedModelRepo struct{}
type IFeaturedModelRepo interface {
Page(page, size int, family, weightProvider, info string) (int64, []model.FeaturedModel, error)
Families() ([]string, error)
WeightProviders() ([]string, error)
SyncAll(models []model.FeaturedModel, version string) error
}
func NewIFeaturedModelRepo() IFeaturedModelRepo {
return &FeaturedModelRepo{}
}
func (r *FeaturedModelRepo) Page(page, size int, family, weightProvider, info string) (int64, []model.FeaturedModel, error) {
var total int64
var items []model.FeaturedModel
db := global.DB.Model(&model.FeaturedModel{})
if family != "" {
db = db.Where("family = ?", family)
}
if weightProvider != "" {
db = db.Where("weight_provider = ?", weightProvider)
}
if info != "" {
db = db.Where("LOWER(repo_id) LIKE ?", "%"+strings.ToLower(info)+"%")
}
if err := db.Count(&total).Error; err != nil {
return 0, nil, err
}
if err := db.Order("sort asc").Order("id asc").Limit(size).Offset(size * (page - 1)).Find(&items).Error; err != nil {
return 0, nil, err
}
return total, items, nil
}
func (r *FeaturedModelRepo) Families() ([]string, error) {
families := make([]string, 0)
err := global.DB.Model(&model.FeaturedModel{}).Distinct("family").Order("family asc").Pluck("family", &families).Error
return families, err
}
func (r *FeaturedModelRepo) WeightProviders() ([]string, error) {
providers := make([]string, 0)
err := global.DB.Model(&model.FeaturedModel{}).Where("weight_provider <> ?", "").Distinct("weight_provider").Order("weight_provider asc").Pluck("weight_provider", &providers).Error
return providers, err
}
func (r *FeaturedModelRepo) SyncAll(models []model.FeaturedModel, version string) error {
return global.DB.Transaction(func(tx *gorm.DB) error {
if err := tx.Where("1 = 1").Delete(&model.FeaturedModel{}).Error; err != nil {
return err
}
if err := tx.Create(&models).Error; err != nil {
return err
}
return tx.Where("key = ?", "FeaturedModelsVersion").
Assign(model.Setting{Value: version}).
FirstOrCreate(&model.Setting{Key: "FeaturedModelsVersion"}).Error
})
}
-126
View File
@@ -1,126 +0,0 @@
package repo
import (
"github.com/1Panel-dev/1Panel/agent/app/model"
"github.com/1Panel-dev/1Panel/agent/global"
"gorm.io/gorm"
)
type ModelDownloadTaskRepo struct{}
type IModelDownloadTaskRepo interface {
Page(page, size int, info, status string, opts ...DBOption) (int64, []model.ModelDownloadTask, error)
GetFirst(opts ...DBOption) (model.ModelDownloadTask, error)
List(opts ...DBOption) ([]model.ModelDownloadTask, error)
Create(task *model.ModelDownloadTask) error
Update(id uint, values map[string]interface{}) error
UpdateBy(values map[string]interface{}, opts ...DBOption) error
DeleteBy(opts ...DBOption) error
WithByDownloadType(downloadType string) DBOption
WithBySource(source string) DBOption
WithByRepoID(repoID string) DBOption
WithByTargetDir(targetDir string) DBOption
WithByStatusIn(status []string) DBOption
}
func NewIModelDownloadTaskRepo() IModelDownloadTaskRepo {
return &ModelDownloadTaskRepo{}
}
func (m *ModelDownloadTaskRepo) WithBySource(source string) DBOption {
return func(g *gorm.DB) *gorm.DB {
return g.Where("source = ?", source)
}
}
func (m *ModelDownloadTaskRepo) WithByRepoID(repoID string) DBOption {
return func(g *gorm.DB) *gorm.DB {
return g.Where("repo_id = ?", repoID)
}
}
func (m *ModelDownloadTaskRepo) WithByTargetDir(targetDir string) DBOption {
return func(g *gorm.DB) *gorm.DB {
return g.Where("target_dir = ?", targetDir)
}
}
func (m *ModelDownloadTaskRepo) WithByStatusIn(status []string) DBOption {
return func(g *gorm.DB) *gorm.DB {
return g.Where("status in ?", status)
}
}
func (m *ModelDownloadTaskRepo) Page(page, size int, info, status string, opts ...DBOption) (int64, []model.ModelDownloadTask, error) {
var (
total int64
list []model.ModelDownloadTask
)
db := global.DB.Model(&model.ModelDownloadTask{})
for _, opt := range opts {
db = opt(db)
}
if info != "" {
db = db.Where("repo_id LIKE ? OR model_name LIKE ?", "%"+info+"%", "%"+info+"%")
}
if status != "" {
db = db.Where("status = ?", status)
}
if err := db.Count(&total).Error; err != nil {
return 0, nil, err
}
if err := db.Order("created_at desc").Limit(size).Offset(size * (page - 1)).Find(&list).Error; err != nil {
return 0, nil, err
}
return total, list, nil
}
func (m *ModelDownloadTaskRepo) GetFirst(opts ...DBOption) (model.ModelDownloadTask, error) {
var item model.ModelDownloadTask
db := global.DB.Model(&model.ModelDownloadTask{})
for _, opt := range opts {
db = opt(db)
}
err := db.First(&item).Error
return item, err
}
func (m *ModelDownloadTaskRepo) Create(task *model.ModelDownloadTask) error {
return global.DB.Create(task).Error
}
func (m *ModelDownloadTaskRepo) List(opts ...DBOption) ([]model.ModelDownloadTask, error) {
var items []model.ModelDownloadTask
db := global.DB.Model(&model.ModelDownloadTask{})
for _, opt := range opts {
db = opt(db)
}
err := db.Find(&items).Error
return items, err
}
func (m *ModelDownloadTaskRepo) Update(id uint, values map[string]interface{}) error {
return global.DB.Model(&model.ModelDownloadTask{}).Where("id = ?", id).Updates(values).Error
}
func (m *ModelDownloadTaskRepo) UpdateBy(values map[string]interface{}, opts ...DBOption) error {
db := global.DB.Model(&model.ModelDownloadTask{})
for _, opt := range opts {
db = opt(db)
}
return db.Updates(values).Error
}
func (m *ModelDownloadTaskRepo) DeleteBy(opts ...DBOption) error {
db := global.DB.Model(&model.ModelDownloadTask{})
for _, opt := range opts {
db = opt(db)
}
return db.Delete(&model.ModelDownloadTask{}).Error
}
func (m *ModelDownloadTaskRepo) WithByDownloadType(downloadType string) DBOption {
return func(g *gorm.DB) *gorm.DB {
return g.Where("download_type = ?", downloadType)
}
}
-2
View File
@@ -4,8 +4,6 @@ import "github.com/1Panel-dev/1Panel/agent/app/repo"
var (
vmRepo = repo.NewIVMRepo()
modelDownloadTaskRepo = repo.NewIModelDownloadTaskRepo()
featuredModelRepo = repo.NewIFeaturedModelRepo()
appRepo = repo.NewIAppRepo()
appTagRepo = repo.NewIAppTagRepo()
appDetailRepo = repo.NewIAppDetailRepo()
-132
View File
@@ -1,132 +0,0 @@
package service
import (
"encoding/json"
"errors"
"fmt"
"strings"
"sync"
"github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/1Panel-dev/1Panel/agent/app/model"
"github.com/1Panel-dev/1Panel/agent/constant"
"github.com/1Panel-dev/1Panel/agent/global"
"github.com/1Panel-dev/1Panel/agent/utils/req_helper"
"gopkg.in/yaml.v3"
"gorm.io/gorm"
)
var featuredModelsSyncMu sync.Mutex
func (m *ModelDownloaderService) PageFeaturedModels(req dto.FeaturedModelSearch) (*dto.FeaturedModelPage, error) {
total, models, err := featuredModelRepo.Page(req.Page, req.PageSize, strings.TrimSpace(req.Family), strings.TrimSpace(req.WeightProvider), strings.TrimSpace(req.Info))
if err != nil {
return nil, err
}
items := make([]dto.FeaturedModel, 0, len(models))
for _, item := range models {
var description map[string]string
if err := json.Unmarshal([]byte(item.Description), &description); err != nil {
return nil, fmt.Errorf("parse featured model %s description failed: %w", item.Key, err)
}
items = append(items, dto.FeaturedModel{
Key: item.Key, Sort: item.Sort, Family: item.Family, RepoID: item.RepoID, Sources: item.Sources,
SourceModelIDs: item.SourceModelIDs, WeightProvider: item.WeightProvider,
Architecture: item.Architecture, BackboneParams: item.BackboneParams,
ActiveParams: item.ActiveParams, Engram: item.Engram, WeightGB: item.WeightGB,
Description: description,
})
}
families, err := featuredModelRepo.Families()
if err != nil {
return nil, err
}
weightProviders, err := featuredModelRepo.WeightProviders()
if err != nil {
return nil, err
}
return &dto.FeaturedModelPage{PageResult: dto.PageResult{Total: total, Items: items}, Families: families, WeightProviders: weightProviders}, nil
}
func (m *ModelDownloaderService) SyncFeaturedModels() error {
if global.CONF.Base.IsOffline {
return nil
}
featuredModelsSyncMu.Lock()
defer featuredModelsSyncMu.Unlock()
localVersion, err := settingRepo.GetValueByKey("FeaturedModelsVersion")
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
return err
}
data, version, err := req_helper.LoadAIResource("featured-models", localVersion)
if err != nil || data == nil {
return err
}
var remote struct {
Models []dto.FeaturedModel `yaml:"models"`
}
if err := yaml.Unmarshal(data, &remote); err != nil {
return fmt.Errorf("parse featured models failed: %w", err)
}
if len(remote.Models) == 0 {
return errors.New("featured models data is empty")
}
keys := make(map[string]bool, len(remote.Models))
models := make([]model.FeaturedModel, 0, len(remote.Models))
for i := range remote.Models {
item := &remote.Models[i]
item.Key = strings.TrimSpace(item.Key)
item.Family = strings.TrimSpace(item.Family)
item.RepoID = strings.TrimSpace(item.RepoID)
item.WeightProvider = strings.TrimSpace(item.WeightProvider)
if item.WeightProvider == "" {
if provider, _, ok := strings.Cut(item.RepoID, "/"); ok {
item.WeightProvider = provider
}
}
if item.Key == "" || item.Family == "" || item.RepoID == "" || (item.Architecture != "MoE" && item.Architecture != "Dense") || item.WeightGB < 0 {
return fmt.Errorf("invalid featured model: %s", item.Key)
}
if keys[item.Key] {
return fmt.Errorf("duplicate featured model key: %s", item.Key)
}
keys[item.Key] = true
if len(item.Sources) == 0 {
item.Sources = []string{"modelscope"}
}
for _, source := range item.Sources {
if source != "modelscope" && source != "huggingface" {
return fmt.Errorf("invalid featured model %s source: %s", item.Key, source)
}
}
description, err := json.Marshal(item.Description)
if err != nil {
return err
}
models = append(models, model.FeaturedModel{
Key: item.Key, Sort: item.Sort, Family: item.Family, RepoID: item.RepoID, Sources: item.Sources,
SourceModelIDs: item.SourceModelIDs, WeightProvider: item.WeightProvider,
Architecture: item.Architecture, BackboneParams: item.BackboneParams,
ActiveParams: item.ActiveParams, Engram: item.Engram, WeightGB: item.WeightGB,
Description: string(description),
})
}
return featuredModelRepo.SyncAll(models, version)
}
func SyncFeaturedModels() {
if global.CONF.Base.IsOffline {
return
}
status, err := settingRepo.GetValueByKey("FeaturedModelsSync")
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
global.LOG.Errorf("load featured model sync setting failed: %v", err)
return
}
if err == nil && status != constant.StatusEnable {
return
}
if err := NewIModelDownloaderService().SyncFeaturedModels(); err != nil {
global.LOG.Errorf("sync featured models failed: %v", err)
}
}
File diff suppressed because it is too large Load Diff
@@ -1,63 +0,0 @@
package service
import (
"os"
"path/filepath"
"sort"
"strings"
"github.com/1Panel-dev/1Panel/agent/app/dto"
)
func listLocalModelOptions(root string) ([]dto.LocalModelOption, error) {
items := make([]dto.LocalModelOption, 0)
entries, err := os.ReadDir(root)
if os.IsNotExist(err) {
return items, nil
}
if err != nil {
return nil, err
}
var scan func(string, string) error
scan = func(path, organization string) error {
children, err := os.ReadDir(path)
if err != nil {
return err
}
for _, child := range children {
if child.IsDir() || child.Type()&os.ModeSymlink != 0 || strings.HasPrefix(child.Name(), ".") {
continue
}
name := child.Name()
if name == "config.json" || name == "params.json" {
items = append(items, dto.LocalModelOption{Name: filepath.Base(path), Organization: organization, Path: path})
return nil
}
}
if organization == "" {
for _, child := range children {
if child.IsDir() && !strings.HasPrefix(child.Name(), ".") {
if err := scan(filepath.Join(path, child.Name()), filepath.Base(path)); err != nil {
return err
}
}
}
}
return nil
}
for _, entry := range entries {
if entry.IsDir() && !strings.HasPrefix(entry.Name(), ".") {
if err := scan(filepath.Join(root, entry.Name()), ""); err != nil {
return nil, err
}
}
}
sort.Slice(items, func(i, j int) bool {
left, right := strings.ToLower(items[i].Name), strings.ToLower(items[j].Name)
if left == right {
return items[i].Path < items[j].Path
}
return left < right
})
return items, nil
}
@@ -1,149 +0,0 @@
package service
import (
"encoding/json"
"io/fs"
"os"
"path/filepath"
"strings"
"github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/1Panel-dev/1Panel/agent/global"
"github.com/1Panel-dev/1Panel/agent/utils/files"
)
const tokenizerDownloadMaxSize = 256 << 20
type tokenizerDownloadMetadata struct {
Source string `json:"source"`
RepoID string `json:"repoID"`
Revision string `json:"revision"`
}
func filterTokenizerDownloadFiles(items []dto.ModelFile) []dto.ModelFile {
result := make([]dto.ModelFile, 0)
for _, item := range items {
name := item.Name
if !filepath.IsLocal(name) || strings.ContainsAny(name, "\\\x00\r\n") || filepath.ToSlash(filepath.Clean(name)) != name {
continue
}
if strings.Contains(name, "/") {
if strings.HasPrefix(name, "chat_templates/") && strings.HasSuffix(name, ".jinja") {
result = append(result, item)
}
continue
}
switch name {
case "config.json", "tokenizer_config.json", "tokenizer.json", "special_tokens_map.json", "added_tokens.json", "vocab.json", "vocab.txt", "merges.txt", "tekken.json":
result = append(result, item)
default:
if strings.HasSuffix(name, ".model") || strings.HasPrefix(name, "tokenizer.model") || strings.HasSuffix(name, ".tiktoken") || strings.HasSuffix(name, ".py") || strings.HasSuffix(name, ".jinja") {
result = append(result, item)
}
}
}
return result
}
func validateTokenizerDownloadFiles(items []dto.ModelFile) error {
if len(items) == 0 || len(items) > 512 {
return newModelDownloaderInputError("tokenizer must contain between 1 and 512 files")
}
names := make(map[string]bool, len(items))
var size int64
vocabulary := false
for _, item := range items {
if names[item.Name] || item.Size < 0 || item.Size > tokenizerDownloadMaxSize-size {
return newModelDownloaderInputError("invalid tokenizer files or total size exceeds 256 MiB")
}
size += item.Size
names[item.Name] = true
if item.Name == "tokenizer.json" || item.Name == "tekken.json" || item.Name == "vocab.txt" || strings.HasSuffix(item.Name, ".model") || strings.HasPrefix(item.Name, "tokenizer.model") || strings.HasSuffix(item.Name, ".tiktoken") {
vocabulary = true
}
}
if !vocabulary && !(names["vocab.json"] && names["merges.txt"]) {
return newModelDownloaderInputError("repository has no supported tokenizer vocabulary in its root directory")
}
return nil
}
func validateDownloadedTokenizer(root string) error {
var items []dto.ModelFile
err := filepath.WalkDir(root, func(path string, entry fs.DirEntry, err error) error {
if err != nil {
return err
}
if entry.IsDir() {
return nil
}
stat, err := entry.Info()
if err != nil {
return err
}
if !stat.Mode().IsRegular() || stat.Size() > tokenizerDownloadMaxSize {
return newModelDownloaderInputError("invalid tokenizer file")
}
name, err := filepath.Rel(root, path)
if err != nil {
return err
}
items = append(items, dto.ModelFile{Name: filepath.ToSlash(name), Size: stat.Size()})
if strings.HasSuffix(name, ".json") {
content, err := os.ReadFile(path)
if err != nil {
return err
}
if !json.Valid(content) {
return newModelDownloaderInputError("invalid tokenizer JSON: %s", name)
}
}
return nil
})
if err != nil {
return err
}
return validateTokenizerDownloadFiles(items)
}
func listDownloadedTokenizers(info string) ([]dto.LocalModelItem, error) {
items := make([]dto.LocalModelItem, 0)
root := filepath.Join(global.CONF.Base.InstallDir, "1panel/ai/tokenizer")
directories, err := listModelDownloadDirectories(root)
if err != nil {
return nil, err
}
for _, item := range directories {
if _, err := normalizeModelDownloaderRepoID(item.Name); err != nil {
continue
}
path, err := filepath.EvalSymlinks(item.Path)
if err != nil {
return nil, err
}
content, err := os.ReadFile(filepath.Join(path, ".1panel-tokenizer"))
if os.IsNotExist(err) {
continue
}
if err != nil {
return nil, err
}
var metadata tokenizerDownloadMetadata
if err := json.Unmarshal(content, &metadata); err != nil {
return nil, err
}
if info != "" && !strings.Contains(strings.ToLower(metadata.RepoID), strings.ToLower(info)) {
continue
}
stat, err := os.Stat(path)
if err != nil {
return nil, err
}
size, err := files.NewFileOp().GetDirSize(path)
if err != nil {
return nil, err
}
items = append(items, dto.LocalModelItem{Name: item.Name, Path: path, RepoID: metadata.RepoID, Source: metadata.Source, Revision: metadata.Revision, DownloadType: "tokenizer", Size: size, SizeFormatted: formatBytes(size), CreatedAt: stat.ModTime().Format("2006-01-02 15:04:05")})
}
return items, nil
}
+8 -8
View File
@@ -8,7 +8,7 @@
"formatZH": "更新 Ollama 绑定域名 [domain]",
"formatEN": "update Ollama bind domain [domain]"
},
"/ai/model/downloader/featured/sync/status": {
"/xpack/model/downloader/featured/sync/status": {
"bodyKeys": [
"value"
],
@@ -17,7 +17,7 @@
"formatZH": "更新推荐模型自动同步 [value]",
"formatEN": "update featured model automatic synchronization [value]"
},
"/ai/model/downloader/hf/download": {
"/xpack/model/downloader/hf/download": {
"bodyKeys": [
"repoID",
"revision"
@@ -27,7 +27,7 @@
"formatZH": "下载 Hugging Face 模型 [repoID]",
"formatEN": "download Hugging Face model [repoID]"
},
"/ai/model/downloader/local/delete": {
"/xpack/model/downloader/local/delete": {
"bodyKeys": [
"name"
],
@@ -36,7 +36,7 @@
"formatZH": "删除本地模型 [name]",
"formatEN": "delete local model [name]"
},
"/ai/model/downloader/modelscope/download": {
"/xpack/model/downloader/modelscope/download": {
"bodyKeys": [
"repoID",
"revision"
@@ -46,7 +46,7 @@
"formatZH": "下载 ModelScope 模型 [repoID]",
"formatEN": "download ModelScope model [repoID]"
},
"/ai/model/downloader/settings": {
"/xpack/model/downloader/settings": {
"bodyKeys": [
"modelDir"
],
@@ -55,7 +55,7 @@
"formatZH": "更新模型下载设置 [modelDir]",
"formatEN": "update model downloader settings [modelDir]"
},
"/ai/model/downloader/tasks/cancel": {
"/xpack/model/downloader/tasks/cancel": {
"bodyKeys": [
"id"
],
@@ -73,7 +73,7 @@
"formatZH": "取消模型下载任务 [repoID]",
"formatEN": "cancel model download task [repoID]"
},
"/ai/model/downloader/tasks/remove": {
"/xpack/model/downloader/tasks/remove": {
"bodyKeys": [
"id"
],
@@ -91,7 +91,7 @@
"formatZH": "移除模型下载任务 [repoID]",
"formatEN": "remove model download task [repoID]"
},
"/ai/model/downloader/tasks/retry": {
"/xpack/model/downloader/tasks/retry": {
"bodyKeys": [
"id"
],
-6
View File
@@ -26,12 +26,6 @@ func Run() {
interval model.Setting
status model.Setting
)
if !global.CONF.Base.IsOffline {
if _, err := global.Cron.AddFunc("@every 24h", service.SyncFeaturedModels); err != nil {
global.LOG.Errorf("add featured model sync job failed: %v", err)
}
go service.SyncFeaturedModels()
}
go syncBeforeStart()
if err := global.DB.Where("key = ?", "MonitorStatus").Find(&status).Error; err != nil {
global.LOG.Errorf("load monitor status from db failed, err: %v", err)
-6
View File
@@ -10,12 +10,6 @@ import (
)
func Init() {
global.LOG.Info("recover interrupted model download tasks start")
if err := service.NewIModelDownloaderService().RecoverInterruptedTasks(); err != nil {
global.LOG.Warnf("recover interrupted model download tasks failed, err: %v", err)
}
global.LOG.Info("recover interrupted model download tasks done")
go syncApp()
go syncInstalledApp()
go syncRuntime()
-20
View File
@@ -18,38 +18,18 @@ import (
)
func Init() {
global.LOG.Info("agent hook: init global data start")
initGlobalData()
global.LOG.Info("agent hook: init global data done")
global.LOG.Info("agent hook: handle cronjob status start")
handleCronjobStatus()
global.LOG.Info("agent hook: handle cronjob status done")
global.LOG.Info("agent hook: handle clam status start")
handleClamStatus()
global.LOG.Info("agent hook: handle clam status done")
global.LOG.Info("agent hook: handle record status start")
handleRecordStatus()
global.LOG.Info("agent hook: handle record status done")
global.LOG.Info("agent hook: handle snapshot status start")
handleSnapStatus()
global.LOG.Info("agent hook: handle snapshot status done")
global.LOG.Info("agent hook: handle ollama model status start")
handleOllamaModelStatus()
global.LOG.Info("agent hook: handle ollama model status done")
global.LOG.Info("agent hook: load local dir start")
loadLocalDir()
global.LOG.Info("agent hook: load local dir done")
global.LOG.Info("agent hook: init docker config start")
initDockerConf()
global.LOG.Info("agent hook: init docker config done")
global.LOG.Info("agent hook: init alert task start")
initAlertTask()
global.LOG.Info("agent hook: init alert task done")
global.LOG.Info("agent hook: init monitor db start")
initMonitorDB()
global.LOG.Info("agent hook: init monitor db done")
}
func initGlobalData() {
-2
View File
@@ -119,9 +119,7 @@ func agentDBMigrations() []*gormigrate.Migration {
migrations.AddOllamaModelAccountSync,
migrations.AddContainerImageCheckUpdates,
migrations.AddModelQuickJump,
migrations.MigrateModelDownloader,
migrations.MigrateVMDatabase,
migrations.AddFeaturedModelWeightProvider,
}
}
@@ -1,140 +0,0 @@
package migrations
import (
"errors"
"net/url"
"os"
"path/filepath"
"github.com/1Panel-dev/1Panel/agent/app/model"
"github.com/1Panel-dev/1Panel/agent/global"
"github.com/glebarez/sqlite"
"github.com/go-gormigrate/gormigrate/v2"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
var MigrateModelDownloader = &gormigrate.Migration{
ID: "20261102-migrate-model-downloader",
Migrate: func(db *gorm.DB) error {
return migrateModelDownloader(db, filepath.Join(global.Dir.DbDir, "xpack.db"))
},
}
var AddFeaturedModelWeightProvider = &gormigrate.Migration{
ID: "20261103-add-featured-model-weight-provider",
Migrate: func(tx *gorm.DB) error {
if err := tx.AutoMigrate(&model.FeaturedModel{}); err != nil {
return err
}
if err := tx.Model(&model.FeaturedModel{}).
Where("(weight_provider IS NULL OR weight_provider = '') AND instr(repo_id, '/') > 0").
Update("weight_provider", gorm.Expr("substr(repo_id, 1, instr(repo_id, '/') - 1)")).Error; err != nil {
return err
}
return tx.Model(&model.Setting{}).Where("key = ?", "FeaturedModelsVersion").Update("value", "").Error
},
}
func migrateModelDownloader(db *gorm.DB, legacyPath string) error {
var legacy *gorm.DB
if _, err := os.Stat(legacyPath); err == nil {
source := url.URL{Scheme: "file", Path: legacyPath, RawQuery: "mode=ro"}
legacy, err = gorm.Open(sqlite.Open(source.String()), &gorm.Config{DisableForeignKeyConstraintWhenMigrating: true})
if err != nil {
return err
}
sqlDB, err := legacy.DB()
if err != nil {
return err
}
defer sqlDB.Close()
} else if !errors.Is(err, os.ErrNotExist) {
return err
}
return db.Transaction(func(tx *gorm.DB) error {
if err := tx.AutoMigrate(&model.ModelDownloadTask{}, &model.FeaturedModel{}); err != nil {
return err
}
resetVersion := false
if legacy != nil {
if legacy.Migrator().HasTable(&model.ModelDownloadTask{}) {
var tasks []model.ModelDownloadTask
if err := legacy.FindInBatches(&tasks, 100, func(_ *gorm.DB, _ int) error {
for i := range tasks {
if tasks[i].DownloadType == "" {
tasks[i].DownloadType = "all"
}
}
return tx.Clauses(clause.OnConflict{DoNothing: true}).Create(&tasks).Error
}).Error; err != nil {
return err
}
}
if legacy.Migrator().HasTable(&model.FeaturedModel{}) {
resetVersion = !legacy.Migrator().HasColumn(&model.FeaturedModel{}, "Sources") || !legacy.Migrator().HasColumn(&model.FeaturedModel{}, "SourceModelIDs")
var models []model.FeaturedModel
if err := legacy.FindInBatches(&models, 100, func(_ *gorm.DB, _ int) error {
for i := range models {
if len(models[i].Sources) == 0 {
models[i].Sources = []string{"modelscope"}
}
}
return tx.Clauses(clause.OnConflict{DoNothing: true}).Create(&models).Error
}).Error; err != nil {
return err
}
} else {
resetVersion = true
}
if legacy.Migrator().HasTable("agent_settings") {
var settings []model.Setting
keys := []string{"ModelDownloaderDir", "ModelDownloaderHFEndpoint", "ModelDownloaderHFToken", "ModelDownloaderModelScopeEndpoint", "ModelDownloaderModelScopeToken", "FeaturedModelsSync", "FeaturedModelsVersion"}
if err := legacy.Table("agent_settings").Where("key IN ?", keys).Find(&settings).Error; err != nil {
return err
}
for _, setting := range settings {
if setting.Key == "FeaturedModelsVersion" && resetVersion {
setting.Value = ""
}
if err := tx.Where("key = ?", setting.Key).FirstOrCreate(&model.Setting{Key: setting.Key, Value: setting.Value, About: setting.About}).Error; err != nil {
return err
}
}
}
}
var count int64
if err := tx.Model(&model.FeaturedModel{}).Count(&count).Error; err != nil {
return err
}
if count > 0 {
return nil
}
if err := tx.Model(&model.Setting{}).Where("key = ?", "FeaturedModelsVersion").Update("value", "").Error; err != nil {
return err
}
models := []model.FeaturedModel{
{Family: "Qwen", RepoID: "Qwen/Qwen3.6-35B-A3B", Architecture: "MoE", BackboneParams: "35B", ActiveParams: "3B", Engram: "N/A", WeightGB: 67},
{Family: "Qwen", RepoID: "Qwen/Qwen3.6-35B-A3B-FP8", Architecture: "MoE", BackboneParams: "35B", ActiveParams: "3B", Engram: "N/A", WeightGB: 35},
{Family: "Qwen", RepoID: "nvidia/Qwen3.6-35B-A3B-NVFP4", Architecture: "MoE", BackboneParams: "35B", ActiveParams: "3B", Engram: "N/A", WeightGB: 22},
{Family: "Qwen", RepoID: "Qwen/Qwen3.8-27B", Architecture: "Dense", BackboneParams: "N/A", ActiveParams: "N/A", Engram: "N/A", WeightGB: 55},
{Family: "Qwen", RepoID: "Qwen/Qwen3.8-27B-FP8", Architecture: "Dense", BackboneParams: "N/A", ActiveParams: "N/A", Engram: "N/A", WeightGB: 28},
{Family: "Qwen", RepoID: "nvidia/Qwen3.8-27B-NVFP4", Architecture: "Dense", BackboneParams: "N/A", ActiveParams: "N/A", Engram: "N/A", WeightGB: 22},
{Family: "Qwen", RepoID: "Qwen/Qwen3.8-Flash-Next", Architecture: "MoE", BackboneParams: "125B", ActiveParams: "6B", Engram: "51B", WeightGB: 335},
{Family: "Qwen", RepoID: "Qwen/Qwen3.8-Flash-Next-FP8", Architecture: "MoE", BackboneParams: "125B", ActiveParams: "6B", Engram: "51B", WeightGB: 172},
{Family: "Qwen", RepoID: "nvidia/Qwen3.8-Flash-Next-FP4-Local", Architecture: "MoE", BackboneParams: "125B", ActiveParams: "6B", Engram: "51B", WeightGB: 130},
{Family: "DeepSeek", RepoID: "deepseek-ai/DeepSeek-V4-Flash-0731", Architecture: "MoE", BackboneParams: "284B", ActiveParams: "13B", Engram: "N/A", WeightGB: 167},
{Family: "DeepSeek", RepoID: "deepseek-ai/DeepSeek-V4-Flash-Vision-Exp", Architecture: "MoE", BackboneParams: "305B", ActiveParams: "undisclosed", Engram: "N/A", WeightGB: 167},
{Family: "DeepSeek", RepoID: "deepseek-ai/DeepSeek-V4.1-Flash-1017", Architecture: "MoE", BackboneParams: "552B", ActiveParams: "8B/16B", Engram: "196B", WeightGB: 510},
{Family: "GLM", RepoID: "zai-org/GLM-5.3-Flash", Architecture: "MoE", BackboneParams: "320B", ActiveParams: "18B", Engram: "N/A", WeightGB: 306},
{Family: "GLM", RepoID: "nvidia/GLM-5.3-Flash-FP4-Local", Architecture: "MoE", BackboneParams: "320B", ActiveParams: "18B", Engram: "N/A", WeightGB: 185},
}
for i := range models {
models[i].Key = models[i].RepoID
models[i].Sort = i + 1
models[i].Description = "{}"
models[i].Sources = []string{"modelscope"}
}
return tx.Create(&models).Error
})
}
-3
View File
@@ -60,9 +60,6 @@ func OperationResolveMeta() gin.HandlerFunc {
return
}
reqPath := strings.TrimPrefix(c.Request.URL.Path, "/api/v2")
if strings.HasPrefix(reqPath, "/xpack/model/downloader/") {
reqPath = strings.Replace(reqPath, "/xpack/model/downloader/", "/ai/model/downloader/", 1)
}
meta, ok := metaMap[reqPath]
if !ok || len(meta.BeforeFunctions) == 0 {
c.Next()
-1
View File
@@ -24,7 +24,6 @@ func commonGroups() []CommonRouter {
&ProcessRouter{},
&WebsiteCARouter{},
&AIToolsRouter{},
&ModelDownloaderRouter{},
&GroupRouter{},
&AlertRouter{},
&RuntimeDiagnosticsRouter{},
-33
View File
@@ -1,33 +0,0 @@
package router
import (
v2 "github.com/1Panel-dev/1Panel/agent/app/api/v2"
"github.com/gin-gonic/gin"
)
type ModelDownloaderRouter struct{}
func (m *ModelDownloaderRouter) InitRouter(Router *gin.RouterGroup) {
baseApi := v2.ApiGroupApp.BaseApi
for _, prefix := range []string{"ai/model/downloader", "xpack/model/downloader"} {
router := Router.Group(prefix)
router.POST("/featured/search", baseApi.PageFeaturedModels)
router.POST("/featured/sync", baseApi.SyncFeaturedModels)
router.POST("/featured/sync/status", baseApi.UpdateFeaturedModelsSync)
router.POST("/hf/search", baseApi.SearchHFModels)
router.POST("/hf/info", baseApi.GetHFModelInfo)
router.POST("/hf/download", baseApi.StartHFModelDownload)
router.POST("/modelscope/search", baseApi.SearchModelScopeModels)
router.POST("/modelscope/info", baseApi.GetModelScopeModelInfo)
router.POST("/modelscope/download", baseApi.StartModelScopeModelDownload)
router.GET("/settings", baseApi.GetModelDownloaderSettings)
router.POST("/settings", baseApi.UpdateModelDownloaderSettings)
router.POST("/tasks/search", baseApi.PageModelDownloadTasks)
router.POST("/tasks/cancel", baseApi.CancelModelDownloadTask)
router.POST("/tasks/retry", baseApi.RetryModelDownloadTask)
router.POST("/tasks/remove", baseApi.RemoveModelDownloadTask)
router.POST("/local/search", baseApi.PageLocalDownloadedModels)
router.GET("/local/options", baseApi.ListLocalModelOptions)
router.POST("/local/delete", baseApi.DeleteLocalDownloadedModel)
}
}
+1 -18
View File
@@ -97,36 +97,23 @@ func Start() {
viper.Init()
dir.Init()
log.Init()
global.LOG.Info("agent startup: logger initialized")
if err := files.CleanupInterruptedDownloads(); err != nil {
global.LOG.Warnf("clean interrupted remote downloads: %s", err)
}
db.Init()
global.LOG.Info("agent startup: database initialized")
migration.Init()
global.LOG.Info("agent startup: migration initialized")
i18n.Init()
global.LOG.Info("agent startup: i18n initialized")
cache.Init()
global.LOG.Info("agent startup: cache initialized")
app.Init()
global.LOG.Info("agent startup: app initialized")
lang.Init()
global.LOG.Info("agent startup: language initialized")
validator.Init()
global.LOG.Info("agent startup: validator initialized")
cron.Run()
global.LOG.Info("agent startup: cron initialized")
hook.Init()
global.LOG.Info("agent startup: hook initialized")
go firewall.Init()
global.LOG.Info("agent startup: firewall init scheduled")
InitOthers()
global.LOG.Info("agent startup: edition initialized")
kvm.Init()
rootRouter := router.Routers()
global.LOG.Info("agent startup: router initialized")
server := &http.Server{
Handler: rootRouter,
@@ -139,7 +126,6 @@ func Start() {
}
if global.IsMaster {
global.LOG.Infof("agent startup: master mode, preparing unix socket %s", masterSocketPath)
if err := prepareMasterSocketDir(masterSocketDir); err != nil {
panic(err)
}
@@ -152,14 +138,12 @@ func Start() {
_ = listener.Close()
panic(err)
}
global.LOG.Infof("agent startup: listening on unix socket %s", masterSocketPath)
business.Init()
global.LOG.Info("agent startup: business initialized")
global.LOG.Infof("agent startup: listening on unix socket %s", masterSocketPath)
_ = server.Serve(listener)
return
} else {
server.Addr = fmt.Sprintf("0.0.0.0:%s", global.CONF.Base.Port)
global.LOG.Infof("agent startup: node mode, preparing https listener %s", server.Addr)
settingRepo := repo.NewISettingRepo()
certItem, err := settingRepo.Get(settingRepo.WithByKey("ServerCrt"))
if err != nil {
@@ -189,7 +173,6 @@ func Start() {
server.TLSConfig.ClientCAs = caCertPool
}
business.Init()
global.LOG.Info("agent startup: business initialized")
global.LOG.Infof("listen at https://0.0.0.0:%s", global.CONF.Base.Port)
if err := server.ListenAndServeTLS("", ""); err != nil {
panic(err)
+19 -19
View File
@@ -3337,7 +3337,7 @@ const docTemplate = `{
]
}
},
"/ai/model/downloader/featured/search": {
"/xpack/model/downloader/featured/search": {
"post": {
"consumes": [
"application/json"
@@ -3393,7 +3393,7 @@ const docTemplate = `{
]
}
},
"/ai/model/downloader/featured/sync": {
"/xpack/model/downloader/featured/sync": {
"post": {
"consumes": [
"application/json"
@@ -3421,7 +3421,7 @@ const docTemplate = `{
]
}
},
"/ai/model/downloader/featured/sync/status": {
"/xpack/model/downloader/featured/sync/status": {
"post": {
"consumes": [
"application/json"
@@ -3469,12 +3469,12 @@ const docTemplate = `{
}
}
},
"/ai/model/downloader/hf/download": {
"/xpack/model/downloader/hf/download": {
"post": {
"consumes": [
"application/json"
],
"description": "Creates an asynchronous download task. Query /ai/model/downloader/tasks/search for progress.",
"description": "Creates an asynchronous download task. Query /xpack/model/downloader/tasks/search for progress.",
"parameters": [
{
"description": "request",
@@ -3521,7 +3521,7 @@ const docTemplate = `{
}
}
},
"/ai/model/downloader/hf/info": {
"/xpack/model/downloader/hf/info": {
"post": {
"consumes": [
"application/json"
@@ -3562,7 +3562,7 @@ const docTemplate = `{
]
}
},
"/ai/model/downloader/hf/search": {
"/xpack/model/downloader/hf/search": {
"post": {
"consumes": [
"application/json"
@@ -3618,7 +3618,7 @@ const docTemplate = `{
]
}
},
"/ai/model/downloader/local/delete": {
"/xpack/model/downloader/local/delete": {
"post": {
"consumes": [
"application/json"
@@ -3665,7 +3665,7 @@ const docTemplate = `{
}
}
},
"/ai/model/downloader/local/search": {
"/xpack/model/downloader/local/search": {
"post": {
"consumes": [
"application/json"
@@ -3721,12 +3721,12 @@ const docTemplate = `{
]
}
},
"/ai/model/downloader/modelscope/download": {
"/xpack/model/downloader/modelscope/download": {
"post": {
"consumes": [
"application/json"
],
"description": "Creates an asynchronous download task. Query /ai/model/downloader/tasks/search for progress.",
"description": "Creates an asynchronous download task. Query /xpack/model/downloader/tasks/search for progress.",
"parameters": [
{
"description": "request",
@@ -3773,7 +3773,7 @@ const docTemplate = `{
}
}
},
"/ai/model/downloader/modelscope/info": {
"/xpack/model/downloader/modelscope/info": {
"post": {
"consumes": [
"application/json"
@@ -3814,7 +3814,7 @@ const docTemplate = `{
]
}
},
"/ai/model/downloader/modelscope/search": {
"/xpack/model/downloader/modelscope/search": {
"post": {
"consumes": [
"application/json"
@@ -3870,7 +3870,7 @@ const docTemplate = `{
]
}
},
"/ai/model/downloader/settings": {
"/xpack/model/downloader/settings": {
"get": {
"produces": [
"application/json"
@@ -3900,7 +3900,7 @@ const docTemplate = `{
"consumes": [
"application/json"
],
"description": "Updates the download directory, source endpoints and tokens. The featuredModelsSync field is ignored; use /ai/model/downloader/featured/sync/status to update automatic synchronization.",
"description": "Updates the download directory, source endpoints and tokens. The featuredModelsSync field is ignored; use /xpack/model/downloader/featured/sync/status to update automatic synchronization.",
"parameters": [
{
"description": "request",
@@ -3943,7 +3943,7 @@ const docTemplate = `{
}
}
},
"/ai/model/downloader/tasks/cancel": {
"/xpack/model/downloader/tasks/cancel": {
"post": {
"consumes": [
"application/json"
@@ -4000,7 +4000,7 @@ const docTemplate = `{
}
}
},
"/ai/model/downloader/tasks/remove": {
"/xpack/model/downloader/tasks/remove": {
"post": {
"consumes": [
"application/json"
@@ -4057,7 +4057,7 @@ const docTemplate = `{
}
}
},
"/ai/model/downloader/tasks/retry": {
"/xpack/model/downloader/tasks/retry": {
"post": {
"consumes": [
"application/json"
@@ -4117,7 +4117,7 @@ const docTemplate = `{
}
}
},
"/ai/model/downloader/tasks/search": {
"/xpack/model/downloader/tasks/search": {
"post": {
"consumes": [
"application/json"
+19 -19
View File
@@ -3330,7 +3330,7 @@
]
}
},
"/ai/model/downloader/featured/search": {
"/xpack/model/downloader/featured/search": {
"post": {
"consumes": [
"application/json"
@@ -3386,7 +3386,7 @@
]
}
},
"/ai/model/downloader/featured/sync": {
"/xpack/model/downloader/featured/sync": {
"post": {
"consumes": [
"application/json"
@@ -3414,7 +3414,7 @@
]
}
},
"/ai/model/downloader/featured/sync/status": {
"/xpack/model/downloader/featured/sync/status": {
"post": {
"consumes": [
"application/json"
@@ -3462,12 +3462,12 @@
}
}
},
"/ai/model/downloader/hf/download": {
"/xpack/model/downloader/hf/download": {
"post": {
"consumes": [
"application/json"
],
"description": "Creates an asynchronous download task. Query /ai/model/downloader/tasks/search for progress.",
"description": "Creates an asynchronous download task. Query /xpack/model/downloader/tasks/search for progress.",
"parameters": [
{
"description": "request",
@@ -3514,7 +3514,7 @@
}
}
},
"/ai/model/downloader/hf/info": {
"/xpack/model/downloader/hf/info": {
"post": {
"consumes": [
"application/json"
@@ -3555,7 +3555,7 @@
]
}
},
"/ai/model/downloader/hf/search": {
"/xpack/model/downloader/hf/search": {
"post": {
"consumes": [
"application/json"
@@ -3611,7 +3611,7 @@
]
}
},
"/ai/model/downloader/local/delete": {
"/xpack/model/downloader/local/delete": {
"post": {
"consumes": [
"application/json"
@@ -3658,7 +3658,7 @@
}
}
},
"/ai/model/downloader/local/search": {
"/xpack/model/downloader/local/search": {
"post": {
"consumes": [
"application/json"
@@ -3714,12 +3714,12 @@
]
}
},
"/ai/model/downloader/modelscope/download": {
"/xpack/model/downloader/modelscope/download": {
"post": {
"consumes": [
"application/json"
],
"description": "Creates an asynchronous download task. Query /ai/model/downloader/tasks/search for progress.",
"description": "Creates an asynchronous download task. Query /xpack/model/downloader/tasks/search for progress.",
"parameters": [
{
"description": "request",
@@ -3766,7 +3766,7 @@
}
}
},
"/ai/model/downloader/modelscope/info": {
"/xpack/model/downloader/modelscope/info": {
"post": {
"consumes": [
"application/json"
@@ -3807,7 +3807,7 @@
]
}
},
"/ai/model/downloader/modelscope/search": {
"/xpack/model/downloader/modelscope/search": {
"post": {
"consumes": [
"application/json"
@@ -3863,7 +3863,7 @@
]
}
},
"/ai/model/downloader/settings": {
"/xpack/model/downloader/settings": {
"get": {
"produces": [
"application/json"
@@ -3893,7 +3893,7 @@
"consumes": [
"application/json"
],
"description": "Updates the download directory, source endpoints and tokens. The featuredModelsSync field is ignored; use /ai/model/downloader/featured/sync/status to update automatic synchronization.",
"description": "Updates the download directory, source endpoints and tokens. The featuredModelsSync field is ignored; use /xpack/model/downloader/featured/sync/status to update automatic synchronization.",
"parameters": [
{
"description": "request",
@@ -3936,7 +3936,7 @@
}
}
},
"/ai/model/downloader/tasks/cancel": {
"/xpack/model/downloader/tasks/cancel": {
"post": {
"consumes": [
"application/json"
@@ -3993,7 +3993,7 @@
}
}
},
"/ai/model/downloader/tasks/remove": {
"/xpack/model/downloader/tasks/remove": {
"post": {
"consumes": [
"application/json"
@@ -4050,7 +4050,7 @@
}
}
},
"/ai/model/downloader/tasks/retry": {
"/xpack/model/downloader/tasks/retry": {
"post": {
"consumes": [
"application/json"
@@ -4110,7 +4110,7 @@
}
}
},
"/ai/model/downloader/tasks/search": {
"/xpack/model/downloader/tasks/search": {
"post": {
"consumes": [
"application/json"
+8 -8
View File
@@ -8,7 +8,7 @@
"formatZH": "更新 Ollama 绑定域名 [domain]",
"formatEN": "update Ollama bind domain [domain]"
},
"/ai/model/downloader/featured/sync/status": {
"/xpack/model/downloader/featured/sync/status": {
"bodyKeys": [
"value"
],
@@ -17,7 +17,7 @@
"formatZH": "更新推荐模型自动同步 [value]",
"formatEN": "update featured model automatic synchronization [value]"
},
"/ai/model/downloader/hf/download": {
"/xpack/model/downloader/hf/download": {
"bodyKeys": [
"repoID",
"revision"
@@ -27,7 +27,7 @@
"formatZH": "下载 Hugging Face 模型 [repoID]",
"formatEN": "download Hugging Face model [repoID]"
},
"/ai/model/downloader/local/delete": {
"/xpack/model/downloader/local/delete": {
"bodyKeys": [
"name"
],
@@ -36,7 +36,7 @@
"formatZH": "删除本地模型 [name]",
"formatEN": "delete local model [name]"
},
"/ai/model/downloader/modelscope/download": {
"/xpack/model/downloader/modelscope/download": {
"bodyKeys": [
"repoID",
"revision"
@@ -46,7 +46,7 @@
"formatZH": "下载 ModelScope 模型 [repoID]",
"formatEN": "download ModelScope model [repoID]"
},
"/ai/model/downloader/settings": {
"/xpack/model/downloader/settings": {
"bodyKeys": [
"modelDir"
],
@@ -55,7 +55,7 @@
"formatZH": "更新模型下载设置 [modelDir]",
"formatEN": "update model downloader settings [modelDir]"
},
"/ai/model/downloader/tasks/cancel": {
"/xpack/model/downloader/tasks/cancel": {
"bodyKeys": [
"id"
],
@@ -73,7 +73,7 @@
"formatZH": "取消模型下载任务 [repoID]",
"formatEN": "cancel model download task [repoID]"
},
"/ai/model/downloader/tasks/remove": {
"/xpack/model/downloader/tasks/remove": {
"bodyKeys": [
"id"
],
@@ -91,7 +91,7 @@
"formatZH": "移除模型下载任务 [repoID]",
"formatEN": "remove model download task [repoID]"
},
"/ai/model/downloader/tasks/retry": {
"/xpack/model/downloader/tasks/retry": {
"bodyKeys": [
"id"
],
-3
View File
@@ -325,9 +325,6 @@ func loadLogInfo(path string) string {
func normalizeOperationPath(reqPath string) string {
pathItem := strings.TrimPrefix(reqPath, "/api/v2")
pathItem = strings.TrimPrefix(pathItem, "/api/v2/core")
if strings.HasPrefix(pathItem, "/xpack/model/downloader/") {
pathItem = strings.Replace(pathItem, "/xpack/model/downloader/", "/ai/model/downloader/", 1)
}
return pathItem
}
@@ -1,145 +0,0 @@
import { ReqPage, ResPage } from '@/api/interface';
export namespace ModelDownloader {
export interface FeaturedModelSearch extends ReqPage {
family: string;
weightProvider: string;
info: string;
}
export interface FeaturedModelPage extends ResPage<FeaturedModel> {
families: string[];
weightProviders: string[];
}
export interface FeaturedModel {
description: Record<string, string> | null;
family: string;
repoID: string;
weightProvider: string;
sources: Source[];
sourceModelIds?: Partial<Record<Source, string>> | null;
architecture: 'MoE' | 'Dense';
backboneParams: string;
activeParams: string;
engram: string;
weightGB: number;
}
export type Source = 'huggingface' | 'modelscope';
export type DownloadType = 'all' | 'tokenizer';
export interface ModelSearch extends Partial<ReqPage> {
query: string;
sort: 'trending' | 'downloads' | 'updated' | 'created' | 'likes' | 'most_params' | 'least_params';
limit?: number;
}
export interface ModelInfoReq {
repoID: string;
revision?: string;
}
export interface ModelDownload {
downloadType?: DownloadType;
repoID: string;
revision?: string;
}
export interface Setting {
featuredModelsSync?: 'Enable' | 'Disable';
modelDir: string;
hfEndpoint: string;
hfToken: string;
modelScopeEndpoint: string;
modelScopeToken: string;
}
export interface ModelItem {
repoID: string;
name: string;
downloads: number;
likes: number;
size: number;
sizeFormatted: string;
}
export interface ModelFile {
name: string;
size: number;
sizeFormatted: string;
}
export interface ModelInfo extends ModelItem {
modelCard: string;
files: ModelFile[];
}
export interface TaskSearch extends ReqPage {
ids?: number[];
includeLocal?: boolean;
downloadType?: DownloadType | '';
info: string;
status: string;
}
export interface TaskOperate {
withFile?: boolean;
id: number;
}
export interface TaskRetry {
id: number;
}
export interface TaskItem {
localModel?: LocalModelItem;
downloadType: DownloadType;
id: number;
source: string;
repoID: string;
revision: string;
modelName: string;
targetDir: string;
status: string;
progress: number;
totalSize: number;
downloadedSize: number;
errorMessage: string;
createdAt: string;
startedAt: string;
completedAt: string;
canCancel: boolean;
canRetry: boolean;
canRemoveRecord: boolean;
}
export interface LocalModelOption {
name: string;
organization: string;
path: string;
}
export interface LocalModelSearch extends ReqPage {
downloadType?: DownloadType | '';
info: string;
}
export interface LocalModelItem {
downloadType: DownloadType;
repoID: string;
source: Source;
revision: string;
name: string;
path: string;
size: number;
sizeFormatted: string;
createdAt: string;
}
export interface LocalModelDelete {
withFile?: boolean;
downloadType?: DownloadType;
name: string;
}
}
@@ -1,80 +0,0 @@
import http from '@/api';
import { ResPage } from '@/api/interface';
import { ModelDownloader } from '@/api/interface/model-downloader';
import { TimeoutEnum } from '@/enums/http-enum';
export const searchHFModels = (req: ModelDownloader.ModelSearch) => {
return http.post<ResPage<ModelDownloader.ModelItem>>('/ai/model/downloader/hf/search', req, TimeoutEnum.T_40S);
};
export const getHFModelInfo = (req: ModelDownloader.ModelInfoReq) => {
return http.post<ModelDownloader.ModelInfo>('/ai/model/downloader/hf/info', req, TimeoutEnum.T_40S);
};
export const startHFModelDownload = (req: ModelDownloader.ModelDownload) => {
return http.post<ModelDownloader.TaskItem>('/ai/model/downloader/hf/download', req, TimeoutEnum.T_40S);
};
export const searchModelScopeModels = (req: ModelDownloader.ModelSearch) => {
return http.post<ResPage<ModelDownloader.ModelItem>>(
'/ai/model/downloader/modelscope/search',
req,
TimeoutEnum.T_40S,
);
};
export const getModelScopeModelInfo = (req: ModelDownloader.ModelInfoReq) => {
return http.post<ModelDownloader.ModelInfo>('/ai/model/downloader/modelscope/info', req, TimeoutEnum.T_40S);
};
export const startModelScopeModelDownload = (req: ModelDownloader.ModelDownload) => {
return http.post<ModelDownloader.TaskItem>('/ai/model/downloader/modelscope/download', req, TimeoutEnum.T_40S);
};
export const getModelDownloaderSettings = () => {
return http.get<ModelDownloader.Setting>('/ai/model/downloader/settings');
};
export const updateModelDownloaderSettings = (req: ModelDownloader.Setting) => {
return http.post('/ai/model/downloader/settings', req);
};
export const pageModelDownloadTasks = (req: ModelDownloader.TaskSearch) => {
return http.post<ResPage<ModelDownloader.TaskItem>>('/ai/model/downloader/tasks/search', req);
};
export const cancelModelDownloadTask = (req: ModelDownloader.TaskOperate) => {
return http.post('/ai/model/downloader/tasks/cancel', req);
};
export const retryModelDownloadTask = (req: ModelDownloader.TaskRetry) => {
return http.post<ModelDownloader.TaskItem>('/ai/model/downloader/tasks/retry', req);
};
export const removeModelDownloadTask = (req: ModelDownloader.TaskOperate) => {
return http.post('/ai/model/downloader/tasks/remove', req);
};
export const pageLocalDownloadedModels = (req: ModelDownloader.LocalModelSearch) => {
return http.post<ResPage<ModelDownloader.LocalModelItem>>('/ai/model/downloader/local/search', req);
};
export const listLocalModelOptions = () => {
return http.get<ModelDownloader.LocalModelOption[]>('/ai/model/downloader/local/options');
};
export const deleteLocalDownloadedModel = (req: ModelDownloader.LocalModelDelete) => {
return http.post('/ai/model/downloader/local/delete', req);
};
export const syncFeaturedModels = () => {
return http.post('/ai/model/downloader/featured/sync', {}, TimeoutEnum.T_60S);
};
export const pageFeaturedModels = (req: ModelDownloader.FeaturedModelSearch) => {
return http.post<ModelDownloader.FeaturedModelPage>('/ai/model/downloader/featured/search', req);
};
export const updateFeaturedModelsSync = (value: string) => {
return http.post('/ai/model/downloader/featured/sync/status', { value });
};
+16 -19
View File
@@ -4021,9 +4021,9 @@ const message = {
fileExchange: 'Upgrade to Commercial Edition to transfer files quickly between servers.',
app: 'Upgrade to Commercial Edition to view service details, anomaly monitoring, and more in the mobile app.',
cluster: 'Upgrade to Commercial Edition to manage MySQL/Postgres/Redis primary-replica clusters.',
vllm: 'Upgrade to Commercial Edition to manage vLLM services in 1Panel with centralized deployment, configuration, lifecycle operations, and task tracking.',
vllm: 'Upgrade to the commercial edition to deploy vLLM instances, monitor services, view logs, and manage daily operations in one place.',
'model-downloader':
'Upgrade to Commercial Edition to search, download, and manage local model files from HuggingFace, ModelScope, or compatible mirrors with centralized model directories and download tasks.',
'Upgrade to the commercial edition to browse featured models, manage downloads, reuse vLLM Recipes, and configure download sources and access credentials.',
},
clean: {
scan: 'Start scanning',
@@ -7101,30 +7101,27 @@ const message = {
clusterTitle3: 'Replication Status',
clusterContent3:
'Displays master-slave replication status and delay information, assisting in troubleshooting synchronization issues',
vllmTitle1: 'Centralized Management',
vllmTitle1: 'Model Deployment',
vllmContent1:
'Bring scattered vLLM services into 1Panel for unified visibility of instances, versions, status, and runtime paths.',
vllmTitle2: 'Fast Deployment',
'Select a model directory and container image, configure GPUs, ports, and startup parameters, and create vLLM instances.',
vllmTitle2: 'Service Monitoring',
vllmContent2:
'Use a visual form to configure version selection, model mounts, and startup parameters with less manual orchestration.',
vllmTitle3: 'Operational Efficiency',
'View instance status, request counts, throughput, and latency to monitor model service performance.',
vllmTitle3: 'Daily Operations',
vllmContent3:
'Handle create, edit, start, stop, restart, delete, and task tracking from one page to simplify local model operations.',
vllmTitle4: 'Controlled Configuration',
vllmContent4:
'Standardize ports, containers, startup commands, and Compose settings through advanced options for easier delivery and later adjustments.',
modelDownloaderTitle1: 'Model Discovery',
'Start, stop, restart, and delete instances, view logs, open terminals, and track operation tasks.',
modelDownloaderTitle1: 'Featured Models',
modelDownloaderContent1:
'Search HuggingFace model repositories directly and view downloads, likes, model details, and file lists.',
modelDownloaderTitle2: 'Accelerated Downloads',
'Browse featured models, filter by family and weight provider, view model details, and start downloads.',
modelDownloaderTitle2: 'Model Downloads',
modelDownloaderContent2:
'Configure a HuggingFace acceleration endpoint and HuggingFace Token for the official site, mirrors, and private models.',
modelDownloaderTitle3: 'Task Tracking',
'Download models from Hugging Face and ModelScope, track progress, cancel or retry tasks, and manage local files.',
modelDownloaderTitle3: 'vLLM Recipes',
modelDownloaderContent3:
'Review download queues, task status, progress, and errors, with support for canceling, retrying, and clearing records.',
modelDownloaderTitle4: 'Local Management',
'Browse and sync official Recipes, manage custom recipes, match models, and reuse deployment parameters.',
modelDownloaderTitle4: 'Configuration',
modelDownloaderContent4:
'Manage local model directories in one place, including paths, sizes, timestamps, and deleting unused model files.',
'Configure the model download directory, source endpoints, access tokens, and automatic featured model synchronization.',
},
node: {
master: 'Main Node',
+16 -19
View File
@@ -4100,9 +4100,9 @@ const message = {
app: 'La edición comercial permite ver información de servicio, monitoreo, etc. vía app móvil.',
cluster:
'Actualizar a la edición comercial permite gestionar clústeres primario-réplica de MySQL/Postgres/Redis.',
vllm: 'Actualiza a la edición comercial para gestionar servicios vLLM en 1Panel con despliegue, configuración, operaciones del ciclo de vida y seguimiento de tareas centralizados.',
vllm: 'Actualiza a la edición comercial para desplegar vLLM, monitorizar servicios, consultar registros y gestionar las operaciones diarias desde un solo lugar.',
'model-downloader':
'Actualiza a la edición comercial para buscar, descargar y gestionar archivos de modelos locales desde HuggingFace, ModelScope o espejos compatibles, con directorios de modelos y tareas de descarga centralizados.',
'Actualiza a la edición comercial para explorar modelos destacados, gestionar descargas, reutilizar vLLM Recipes y configurar los orígenes de descarga y las credenciales de acceso.',
offLine: 'Sin conexión',
},
clean: {
@@ -7222,30 +7222,27 @@ const message = {
clusterTitle3: 'Estado de Replicación',
clusterContent3:
'Muestra estado de replicación maestro-esclavo y retrasos, ayudando a diagnosticar problemas de sincronización',
modelDownloaderTitle1: 'Descubrimiento de modelos',
modelDownloaderTitle1: 'Modelos destacados',
modelDownloaderContent1:
'Busca repositorios de modelos de HuggingFace directamente y consulta descargas, me gusta, detalles y archivos.',
modelDownloaderTitle2: 'Descargas aceleradas',
'Explora modelos destacados, filtra por familia y proveedor de pesos, consulta los detalles e inicia descargas.',
modelDownloaderTitle2: 'Descarga de modelos',
modelDownloaderContent2:
'Configura un endpoint de aceleración de HuggingFace y HuggingFace Token para el sitio oficial, espejos y modelos privados.',
modelDownloaderTitle3: 'Seguimiento de tareas',
'Descarga modelos de Hugging Face y ModelScope, consulta el progreso, cancela o reintenta tareas y gestiona los archivos locales.',
modelDownloaderTitle3: 'vLLM Recipes',
modelDownloaderContent3:
'Consulta la cola, el estado, el progreso y los errores de descarga, con soporte para cancelar, reintentar y limpiar registros.',
modelDownloaderTitle4: 'Gestión local',
'Explora y sincroniza Recipes oficiales, gestiona recetas personalizadas y reutiliza parámetros de despliegue compatibles con cada modelo.',
modelDownloaderTitle4: 'Configuración',
modelDownloaderContent4:
'Gestiona directorios de modelos locales en un solo lugar, incluyendo rutas, tamaños, fechas y eliminación de modelos no usados.',
vllmTitle1: 'Gestión centralizada',
'Configura el directorio de descarga, las direcciones de origen, los tokens de acceso y la sincronización automática de modelos destacados.',
vllmTitle1: 'Despliegue de modelos',
vllmContent1:
'Centralice la gestión de instancias vLLM distribuidas y supervise versiones, estado de ejecución y directorios de trabajo desde una única plataforma.',
vllmTitle2: 'Despliegue rápido',
'Selecciona el directorio del modelo y la imagen del contenedor, configura GPU, puertos y parámetros de inicio, y crea instancias de vLLM.',
vllmTitle2: 'Monitorización del servicio',
vllmContent2:
'Configure y despliegue servicios vLLM en minutos mediante formularios visuales, sin necesidad de complejas operaciones manuales.',
vllmTitle3: 'Operación eficiente',
'Consulta el estado de las instancias, el número de solicitudes, el rendimiento y la latencia del servicio de modelos.',
vllmTitle3: 'Operaciones diarias',
vllmContent3:
'Realice la creación, edición, inicio, detención, reinicio y eliminación de instancias desde una sola interfaz para simplificar la operación diaria.',
vllmTitle4: 'Control avanzado',
vllmContent4:
'Personalice puertos, contenedores, comandos de arranque y archivos Compose con opciones avanzadas para lograr implementaciones más flexibles y estandarizadas.',
'Inicia, detén, reinicia y elimina instancias, consulta registros, abre terminales y realiza el seguimiento de las tareas.',
},
node: {
master: 'Nodo Principal',
+16 -19
View File
@@ -3983,9 +3983,9 @@ const message = {
fileExchange: 'برای انتقال سریع فایل‌ها بین سرورها به نسخه تجاری ارتقاء دهید.',
app: 'برای مشاهده جزئیات سرویس، نظارت بر ناهنجاری‌ها و موارد دیگر در برنامه موبایل به نسخه تجاری ارتقاء دهید.',
cluster: 'برای مدیریت خوشه‌های اصلی-برده MySQL/Postgres/Redis به نسخه تجاری ارتقاء دهید.',
vllm: 'برای مدیریت سرویس‌های vLLM در 1Panel با استقرار متمرکز، پیکربندی، عملیات چرخه حیات و ردیابی وظایف به نسخه تجاری ارتقاء دهید.',
vllm: 'برای استقرار vLLM، پایش سرویس‌ها، مشاهده گزارش‌ها و مدیریت یکپارچه عملیات روزانه، به نسخه تجاری ارتقا دهید.',
'model-downloader':
'برای جستجو، دانلود و مدیریت فایل‌های مدل محلی از HuggingFace، ModelScope یا آینه‌های سازگار با دایرکتوری‌های مدل متمرکز و وظایف دانلود به نسخه تجاری ارتقاء دهید.',
'برای مرور مدل‌های منتخب، مدیریت دانلودها، استفاده مجدد از vLLM Recipes و تنظیم منابع دانلود و اطلاعات دسترسی، به نسخه تجاری ارتقا دهید.',
},
clean: {
scan: 'شروع اسکن',
@@ -7056,30 +7056,27 @@ const message = {
clusterTitle3: 'وضعیت تکرار',
clusterContent3:
'وضعیت تکرار اصلی-برده و اطلاعات تأخیر را نمایش می‌دهد و به رفع مشکلات همگام‌سازی کمک می‌کند',
vllmTitle1: 'مدیریت متمرکز',
vllmTitle1: 'استقرار مدل',
vllmContent1:
'سرویس‌های vLLM پراکنده را به 1Panel بیاورید تا دید یکپارچه از نمونه‌ها، نسخه‌ها، وضعیت و مسیرهای زمان اجرا داشته باشید.',
vllmTitle2: 'استقرار سریع',
'پوشه مدل و ایمیج کانتینر را انتخاب کنید و با تنظیم GPU، درگاه‌ها و پارامترهای راه‌اندازی، نمونه‌های vLLM بسازید.',
vllmTitle2: 'پایش سرویس',
vllmContent2:
'از یک فرم بصری برای پیکربندی انتخاب نسخه، نصب مدل و پارامترهای راه‌اندازی با هماهنگی دستی کمتر استفاده کنید.',
vllmTitle3: 'کارایی عملیاتی',
'وضعیت نمونه‌ها، تعداد درخواست‌ها، توان عملیاتی و تأخیر را برای پایش عملکرد سرویس مدل مشاهده کنید.',
vllmTitle3: 'عملیات روزانه',
vllmContent3:
'عملیات ایجاد، ویرایش، شروع، توقف، راه‌اندازی مجدد، حذف و ردیابی وظایف را از یک صفحه مدیریت کنید تا عملیات مدل محلی ساده‌تر شود.',
vllmTitle4: 'پیکربندی کنترل شده',
vllmContent4:
'پورت‌ها، کانتینرها، دستورات راه‌اندازی و تنظیمات Compose را از طریق گزینه‌های پیشرفته استاندارد کنید تا تحویل و تنظیمات بعدی آسان‌تر شود.',
modelDownloaderTitle1: 'کشف مدل',
'نمونه‌ها را راه‌اندازی، متوقف، بازراه‌اندازی یا حذف کنید، گزارش‌ها را ببینید، ترمینال را باز کنید و کارهای عملیاتی را پیگیری کنید.',
modelDownloaderTitle1: 'مدل‌های منتخب',
modelDownloaderContent1:
'مستقیماً مخازن مدل HuggingFace را جستجو کنید و دانلودها، لایک‌ها، جزئیات مدل و لیست فایل‌ها را مشاهده کنید.',
modelDownloaderTitle2: 'دانلود شتاب‌دهنده',
'مدل‌های منتخب را مرور کنید، بر اساس خانواده و ارائه‌دهنده وزن فیلتر کنید، جزئیات را ببینید و دانلود را آغاز کنید.',
modelDownloaderTitle2: 'دانلود مدل',
modelDownloaderContent2:
'یک نقطه پایانی شتاب HuggingFace و توکن HuggingFace را برای سایت رسمی، آینه‌ها و مدل‌های خصوصی پیکربندی کنید.',
modelDownloaderTitle3: 'ردیابی وظایف',
'مدل‌ها را از Hugging Face و ModelScope دانلود کنید، پیشرفت را ببینید، کارها را لغو یا دوباره اجرا کنید و فایل‌های محلی را مدیریت کنید.',
modelDownloaderTitle3: 'vLLM Recipes',
modelDownloaderContent3:
'صف‌های دانلود، وضعیت وظایف، پیشرفت و خطاها را مرور کنید و از لغو، تلاش مجدد و پاک‌سازی رکوردها پشتیبانی کنید.',
modelDownloaderTitle4: 'مدیریت محلی',
'Recipes رسمی را مرور و همگام‌سازی کنید، دستورهای سفارشی را مدیریت کنید و پارامترهای استقرار متناسب با مدل را دوباره به کار ببرید.',
modelDownloaderTitle4: 'پیکربندی',
modelDownloaderContent4:
'دایرکتوری‌های مدل محلی را در یک مکان مدیریت کنید، از جمله مسیرها، اندازه‌ها، مهرهای زمانی و حذف فایل‌های مدل استفاده نشده.',
'پوشه دانلود مدل، نشانی منابع، توکن‌های دسترسی و همگام‌سازی خودکار مدل‌های منتخب را تنظیم کنید.',
},
node: {
master: 'گره اصلی',
+16 -19
View File
@@ -4000,9 +4000,9 @@ const message = {
'商用版にアップグレードすると、PHP 拡張機能テンプレートを1つ以上のターゲットノードに同期できます。',
fileExchange: '商用版にアップグレードすると、複数サーバー間でファイルをすばやく転送できます。',
cluster: '商用版にアップグレードすると、MySQL/Postgres/Redis のプライマリ/レプリカ構成を管理できます。',
vllm: '商用版にアップグレードすると、1Panel で vLLM サービスのデプロイ、設定、ライフサイクル操作、タスク追跡を一元管理できます。',
vllm: '商用版にアップグレードすると、vLLM のデプロイ、稼働監視、ログ確認、日常運用を一元管理できます。',
'model-downloader':
'商用版にアップグレードすると、HuggingFace、ModelScope、または互換ミラーからローカルモデルファイルを検索、ダウンロード、管理できます。モデルディレクトリとダウンロードタスクを一元管理できます。',
'商用版にアップグレードすると、おすすめモデルの閲覧、ダウンロードの管理、vLLM Recipes の再利用、ダウンロード元と認証情報の設定ができます。',
exceptionalHelper:
'ライセンス同期検証が異常です。手動で同期して商用版機能が正常に動作していることを確認してください。詳細: ',
tamperHelper: '操作失敗。ファイル/フォルダの改ざん保護を確認してください。',
@@ -7086,30 +7086,27 @@ const message = {
clusterTitle3: 'レプリケーション状態',
clusterContent3:
'マスタースレーブレプリケーション状態と遅延情報を表示し、同期の問題を解決するのに役立ちます',
modelDownloaderTitle1: 'モデル検索',
modelDownloaderTitle1: 'おすすめモデル',
modelDownloaderContent1:
'HuggingFace のモデルリポジトリを直接検索し、ダウンロード数、いいね、モデル詳細、ファイル一覧を確認できます。',
modelDownloaderTitle2: '高速ダウンロード',
'おすすめモデルをシリーズや重みの提供元で絞り込み、詳細を確認してダウンロードできます。',
modelDownloaderTitle2: 'モデルのダウンロード',
modelDownloaderContent2:
'HuggingFace アクセラレーションエンドポイントと HuggingFace Token を設定し、公式サイト、ミラー、プライベートモデルに対応します。',
modelDownloaderTitle3: 'タスク追跡',
'Hugging Face と ModelScope からモデルをダウンロードし、進捗の確認、キャンセル、再試行、ローカルファイルの管理ができます。',
modelDownloaderTitle3: 'vLLM Recipes',
modelDownloaderContent3:
'ダウンロードキュー、タスク状態、進捗、エラーを一元確認し、キャンセル、再試行、記録の削除を行えます。',
modelDownloaderTitle4: 'ローカル管理',
'公式 Recipes の閲覧・同期、カスタムレシピの管理、モデルに合うレシピの検索、デプロイパラメーターの再利用ができます。',
modelDownloaderTitle4: '設定',
modelDownloaderContent4:
'ローカルモデルディレクトリを一元管理し、パス、サイズ、日時の確認と不要なモデルファイルの削除を行えます。',
vllmTitle1: '一元管理',
'モデルの保存先、ダウンロード元の URL、アクセストークン、おすすめモデルの自動同期を設定できます。',
vllmTitle1: 'モデルのデプロイ',
vllmContent1:
'分散した vLLM サービスを 1Panel に統合し、インスタンス、バージョン、状態、実行ディレクトリを一元的に確認できます。',
vllmTitle2: '高速デプロイ',
'モデルのディレクトリとコンテナーイメージを選び、GPU、ポート、起動パラメーターを設定して vLLM インスタンスを作成できます。',
vllmTitle2: '稼働監視',
vllmContent2:
'ビジュアルフォームでバージョン選択、モデルディレクトリマウント、起動パラメータ設定を完了し、手動構成コストを削減します。',
vllmTitle3: '運用効率',
'インスタンスの状態、リクエスト数、スループット、レイテンシーを確認し、モデルサービスの稼働状況を把握できます。',
vllmTitle3: '日常運用',
vllmContent3:
'同じページで作成、編集、起動、停止、再起動、削除、タスク追跡を完結し、ローカルモデルサービスの保守効率を向上させます。',
vllmTitle4: '設定の可控性',
vllmContent4:
'高度な設定でポート、コンテナ、起動コマンド、Compose 設定を一元管理し、標準化されたデリバリーと后续調整を容易にします。',
'インスタンスの起動・停止・再起動・削除、ログの確認、ターミナルへのアクセス、操作タスクの追跡ができます。',
},
node: {
master: '主ノード',
+16 -19
View File
@@ -3931,9 +3931,9 @@ const message = {
fileExchange: '상용 버전으로 업그레이드하면 여러 서버 간에 파일을 빠르게 전송할 수 있습니다.',
app: '상용 버전으로 업그레이드하면 모바일 앱에서 서비스 정보와 이상 모니터링을 확인할 수 있습니다.',
cluster: '상용 버전으로 업그레이드하면 MySQL/Postgres/Redis 기본-복제 클러스터를 관리할 수 있습니다.',
vllm: '상용 버전으로 업그레이드하면 1Panel에서 vLLM 서비스의 배포, 구성, 수명 주기 작업 및 작업 추적을 중앙에서 관리할 수 있습니다.',
vllm: '상용 버전으로 업그레이드하면 vLLM 배포, 서비스 모니터링, 로그 조회 및 일상 운영을 한곳에서 관리할 수 있습니다.',
'model-downloader':
'상용 버전으로 업그레이드하면 HuggingFace, ModelScope 또는 호환 미러에서 로컬 모델 파일을 검색, 다운로드 및 관리하고 모델 디렉터리와 다운로드 작업을 통합 관리할 수 있습니다.',
'상용 버전으로 업그레이드하면 추천 모델 조회, 다운로드 관리, vLLM Recipes 재사용 및 다운로드 소스와 인증 정보 설정을 이용할 수 있습니다.',
exceptionalHelper:
'라이선스 동기화 검증이 비정상입니다. 수동으로 동기화해 상용 버전 기능이 정상적으로 동작하는지 확인하세요. 상세: ',
tamperHelper: '작업 실패, 파일/폴더에 변조 방지가 활성화되어 있습니다. 확인 후 다시 시도하세요!',
@@ -6941,30 +6941,27 @@ const message = {
clusterTitle3: '복제 상태',
clusterContent3:
'마스터-슬레이브 복제 상태 및 지연 정보를 표시하여 동기화 문제를 해결하는 데 도움을 줍니다',
modelDownloaderTitle1: '모델 탐색',
modelDownloaderTitle1: '추천 모델',
modelDownloaderContent1:
'HuggingFace 모델 저장소를 직접 검색하고 다운로드 수, 좋아요, 모델 상세 정보, 파일 목록을 확인합니다.',
modelDownloaderTitle2: '가속 다운로드',
'추천 모델을 모델 계열과 가중치 제공자로 필터링하고, 모델 정보를 확인하여 다운로드할 수 있습니다.',
modelDownloaderTitle2: '모델 다운로드',
modelDownloaderContent2:
'HuggingFace 가속 엔드포인트와 HuggingFace Token을 설정하여 공식 사이트, 미러, 비공개 모델에 대응합니다.',
modelDownloaderTitle3: '작업 추적',
'Hugging Face와 ModelScope에서 모델을 다운로드하고 진행 상황 확인, 취소, 재시도 및 로컬 파일 관리를 수행합니다.',
modelDownloaderTitle3: 'vLLM Recipes',
modelDownloaderContent3:
'다운로드 대기열, 작업 상태, 진행률, 오류를 한곳에서 확인하고 취소, 재시도, 기록 정리를 지원합니다.',
modelDownloaderTitle4: '로컬 관리',
'공식 Recipes를 조회하고 동기화하며, 사용자 정의 레시피를 관리하고 모델에 맞는 배포 매개변수를 재사용합니다.',
modelDownloaderTitle4: '설정',
modelDownloaderContent4:
'로컬 모델 디렉토리를 중앙에서 관리하고 경로, 크기, 시간을 확인하며 불필요한 모델 파일을 삭제합니다.',
vllmTitle1: '통합 관리',
'모델 다운로드 디렉터리, 소스 주소, 액세스 토큰 및 추천 모델 자동 동기화를 설정합니다.',
vllmTitle1: '모델 배포',
vllmContent1:
'분산된 vLLM 서비스를 1Panel에 통합하여 인스턴스、버전、상태、실행 디렉토리를 집중 조회할 수 있습니다.',
vllmTitle2: '빠른 배포',
'모델 디렉터리와 컨테이너 이미지를 선택하고 GPU, 포트 및 시작 매개변수를 설정하여 vLLM 인스턴스를 생성합니다.',
vllmTitle2: '서비스 모니터링',
vllmContent2:
'시각적 양식으로 버전 선택、모델 디렉토리 마운트、시작 매개변수 설정을 완료하여 수동 구성 비용을 줄입니다.',
vllmTitle3: '운영 효율성',
'인스턴스 상태, 요청 수, 처리량 및 지연 시간을 확인하여 모델 서비스의 운영 상태를 파악합니다.',
vllmTitle3: '일상 운영',
vllmContent3:
'같은 페이지에서 생성、편집、시작、중지、재시작、삭제、작업 추적을 완료하여 로컬 모델 서비스 유지보수 효율을 향상시킵니다.',
vllmTitle4: '구성 제어',
vllmContent4:
'고급 설정으로 포트、컨테이너、시작 명령、Compose 구성을 통합 관리하여 표준화된 배포와 후속 조정을 용이하게 합니다.',
'인스턴스 시작, 중지, 재시작 및 삭제를 수행하고 로그 조회, 터미널 접속 및 작업 추적을 지원합니다.',
},
node: {
master: '주 노드',
+20 -18
View File
@@ -3908,9 +3908,9 @@ const message = {
fileExchange: 'ອັບເກຣດເປັນເວີຊັນ Commercial ເພື່ອໂອນຍ້າຍໄຟລ໌ລະຫວ່າງເຊີບເວີຢ່າງວ່ອງໄວ.',
app: 'ອັບເກຣດເປັນເວີຊັນ Commercial ເພື່ອເບິ່ງລາຍລະອຽດບໍລິການ, ການຕິດຕາມສິ່ງຜິດປົກກະຕິ ແລະ ອື່ນໆໃນແອັບມືຖື.',
cluster: 'ອັບເກຣດເປັນເວີຊັນ Commercial ເພື່ອຈັດການ MySQL/Postgres/Redis primary-replica clusters.',
vllm: 'ອັບເກຣດເປັນເວີຊັນ Commercial ເພື່ອຈັດການບໍລິການ vLLM ໃນ 1Panel ດ້ວຍການຕິດຕັ້ງ, ຕັ້ງຄ່າ, ການເຮັດວຽກ lifecycle ແລະ ການຕິດຕາມໜ້າວຽກແບບລວມສູນ.',
vllm: 'ອັບເກຣດເປັນລຸ້ນທຸລະກິດເພື່ອຕິດຕັ້ງ vLLM, ຕິດຕາມບໍລິການ, ເບິ່ງບັນທຶກ ແລະຈັດການວຽກປະຈຳວັນໃນບ່ອນດຽວ.',
'model-downloader':
'ອັບເກຣດເປັນເວີຊັນ Commercial ເພື່ອຄົ້ນຫາ, ດາວໂຫຼດ ແລະ ຈັດການໄຟລ໌ໂມເດວທ້ອງຖິ່ນຈາກ HuggingFace, ModelScope ຫຼື mirror ທີ່ຮອງຮັບ ພ້ອມກັບໄດເຣັກທໍຣີໂມເດວ ແລະ ໜ້າວຽກດາວໂຫຼດແບບລວມສູນ.',
'ອັບເກຣດເປັນລຸ້ນທຸລະກິດເພື່ອເບິ່ງໂມເດລແນະນຳ, ຈັດການການດາວໂຫຼດ, ໃຊ້ vLLM Recipes ຄືນ ແລະຕັ້ງຄ່າແຫຼ່ງດາວໂຫຼດກັບຂໍ້ມູນຢືນຢັນການເຂົ້າເຖິງ.',
},
clean: {
scan: 'ເລີ່ມສະແກນ',
@@ -6878,23 +6878,25 @@ const message = {
clusterContent2: 'ຈັດການຫຼາຍໂນດ Master-Slave ໃນໜ້າດຽວ, ເບິ່ງບົດບາດ ແລະ ສະຖານະການທຳງານ.',
clusterTitle3: 'ສະຖານະການກັອບປີ້ຂໍ້ມູນ',
clusterContent3: 'ສະແດງສະຖານະການ Replication ແລະ ຂໍ້ມູນການດີເລ (Delay) ເພື່ອຊ່ວຍແກ້ໄຂບັນຫາ.',
vllmTitle1: 'ຈັດການແບບລວມສູນ',
vllmContent1: 'ລວມບໍລິການ vLLM ທີ່ກະຈັດກະຈາຍເຂົ້າມາໃນ 1Panel ເພື່ອເບິ່ງສະຖານະ, ເວີຊັນ ແລະ ເສັ້ນທາງທຳງານ.',
vllmTitle2: 'ຕິດຕັ້ງວ່ອງໄວ',
vllmContent2: 'ໃຊ້ຟອມເພື່ອເລືອກເວີຊັນ, ເທົາມໍເດລ ແລະ ຕັ້ງຄ່າການເລີ່ມຕົ້ນໄດ້ງ່າຍ.',
vllmTitle3: 'ປະສິດທິພາບການດຳເນີນງານ',
vllmContent3: 'ຈັດການສ້າງ, ແກ້ໄຂ, ເລີ່ມ, ຢຸດ ແລະ ລຶບ ໄດ້ໃນໜ້າດຽວ.',
vllmTitle4: 'ຄວບຄຸມການຕັ້ງຄ່າ',
vllmContent4: 'ກຳນົດພອດ, ຄອນເທນເນີ ແລະ ຄຳສັ່ງເລີ່ມຕົ້ນໃຫ້ເປັນມາດຕະຖານ.',
modelDownloaderTitle1: 'ຄົ້ນຫາມໍເດລ',
modelDownloaderContent1: 'ຄົ້ນຫາຄັງມໍເດລ HuggingFace ໂດຍກົງ ແລະ ເບິ່ງຍອດດາວໂຫຼດ, ໄລ້ ແລະ ລາຍລະອຽດໄຟລ໌.',
modelDownloaderTitle2: 'ດາວໂຫຼດເລັ່ງດ່ວນ',
vllmTitle1: 'ຕິດຕັ້ງໂມເດລ',
vllmContent1:
'ເລືອກໂຟນເດີໂມເດລ ແລະອິມເມຈຄອນເທນເນີ, ຕັ້ງຄ່າ GPU, ພອດ ແລະພາລາມິເຕີເລີ່ມຕົ້ນ ເພື່ອສ້າງອິນສະແຕນ vLLM.',
vllmTitle2: 'ຕິດຕາມບໍລິການ',
vllmContent2: 'ເບິ່ງສະຖານະອິນສະແຕນ, ຈຳນວນຄຳຮ້ອງຂໍ, ອັດຕາປະມວນຜົນ ແລະຄວາມລ່າຊ້າຂອງບໍລິການໂມເດລ.',
vllmTitle3: 'ການດູແລປະຈຳວັນ',
vllmContent3: 'ເລີ່ມ, ຢຸດ, ເລີ່ມໃໝ່ ແລະລຶບອິນສະແຕນ, ເບິ່ງບັນທຶກ, ເປີດເທີມິນັລ ແລະຕິດຕາມວຽກ.',
modelDownloaderTitle1: 'ໂມເດລແນະນຳ',
modelDownloaderContent1:
'ເບິ່ງໂມເດລແນະນຳ, ກັ່ນຕອງຕາມຕະກູນໂມເດລ ແລະຜູ້ໃຫ້ນ້ຳໜັກ, ເບິ່ງລາຍລະອຽດ ແລະເລີ່ມດາວໂຫຼດ.',
modelDownloaderTitle2: 'ດາວໂຫຼດໂມເດລ',
modelDownloaderContent2:
'ຕັ້ງຄ່າ ຈຸດເຊື່ອມຕໍ່ເລັ່ງຄວາມໄວ HuggingFace ແລະ HuggingFace Token ສຳລັບມໍເດລສ່ວນຕົວ.',
modelDownloaderTitle3: 'ຕິດຕາມງານ',
modelDownloaderContent3: 'ກວດສອບຄິວດາວໂຫຼດ, ສະຖານະ, ຄວາມຄືບໜ້າ ແລະ ຂໍ້ຜິດພາດ.',
modelDownloaderTitle4: 'ຈັດການໃນເຄື່ອງ',
modelDownloaderContent4: 'ຈັດການໄດເຣັກທໍຣີມໍເດລໃນເຄື່ອງ, ລວມມີຂະໜາດ, ເວລາ ແລະ ການລຶບໄຟລ໌ທີ່ບໍ່ໄດ້ໃຊ້.',
'ດາວໂຫຼດໂມເດລຈາກ Hugging Face ແລະ ModelScope, ຕິດຕາມຄວາມຄືບໜ້າ, ຍົກເລີກ ຫຼືລອງໃໝ່ ແລະຈັດການໄຟລ໌ໃນເຄື່ອງ.',
modelDownloaderTitle3: 'vLLM Recipes',
modelDownloaderContent3:
'ເບິ່ງ ແລະຊິງຄ໌ Recipes ທາງການ, ຈັດການສູດທີ່ກຳນົດເອງ, ຈັບຄູ່ໂມເດລ ແລະນຳພາລາມິເຕີການຕິດຕັ້ງມາໃຊ້ຄືນ.',
modelDownloaderTitle4: 'ການຕັ້ງຄ່າ',
modelDownloaderContent4:
'ຕັ້ງຄ່າໂຟນເດີດາວໂຫຼດໂມເດລ, ທີ່ຢູ່ແຫຼ່ງດາວໂຫຼດ, ໂທເຄັນເຂົ້າເຖິງ ແລະການຊິງຄ໌ໂມເດລແນະນຳອັດຕະໂນມັດ.',
},
node: {
master: 'ໂນດຫຼັກ (Master)',
+16 -19
View File
@@ -4088,9 +4088,9 @@ const message = {
fileExchange: 'Naik taraf ke Edisi Komersial untuk memindahkan fail dengan pantas antara pelayan.',
app: 'Naik taraf ke Edisi Komersial untuk melihat maklumat perkhidmatan dan pemantauan anomali dalam aplikasi mudah alih.',
cluster: 'Naik taraf ke Edisi Komersial untuk mengurus kelompok utama-replika MySQL/Postgres/Redis.',
vllm: 'Naik taraf ke Edisi Komersial untuk mengurus perkhidmatan vLLM secara berpusat dalam 1Panel, termasuk penggunaan, konfigurasi, operasi kitar hayat dan penjejakan tugas.',
vllm: 'Naik taraf kepada edisi komersial untuk menggunakan instans vLLM, memantau perkhidmatan, melihat log dan mengurus operasi harian di satu tempat.',
'model-downloader':
'Naik taraf kepada edisi komersial untuk mencari, memuat turun dan mengurus fail model tempatan daripada HuggingFace, ModelScope atau cermin serasi, dengan direktori model dan tugasan muat turun berpusat.',
'Naik taraf kepada edisi komersial untuk menyemak model pilihan, mengurus muat turun, menggunakan semula vLLM Recipes serta menetapkan sumber muat turun dan kelayakan akses.',
exceptionalHelper:
'Pengesahan penyegerakan lesen tidak normal. Klik butang sync secara manual untuk pastikan fungsi Edisi Komersial berjalan baik. butiran: ',
tamperHelper: 'Operasi gagal, fail atau folder mempunyai perlindungan gangguan. Sila semak dan cuba lagi!',
@@ -7205,30 +7205,27 @@ const message = {
clusterTitle3: 'Status Replikasi',
clusterContent3:
'Memaparkan status replikasi utama-hamba dan maklumat kelewatan, membantu menyelesaikan masalah sinkronisasi',
modelDownloaderTitle1: 'Penemuan Model',
modelDownloaderTitle1: 'Model Pilihan',
modelDownloaderContent1:
'Cari repositori model HuggingFace secara langsung dan lihat muat turun, suka, butiran model dan senarai fail.',
modelDownloaderTitle2: 'Muat Turun Dipercepat',
'Semak model pilihan, tapis mengikut keluarga dan penyedia pemberat, lihat butiran model dan mulakan muat turun.',
modelDownloaderTitle2: 'Muat Turun Model',
modelDownloaderContent2:
'Konfigurasikan titik akhir pecutan HuggingFace dan HuggingFace Token untuk laman rasmi, cermin dan model persendirian.',
modelDownloaderTitle3: 'Penjejakan Tugas',
'Muat turun model daripada Hugging Face dan ModelScope, pantau kemajuan, batalkan atau cuba semula tugas dan urus fail setempat.',
modelDownloaderTitle3: 'vLLM Recipes',
modelDownloaderContent3:
'Semak baris gilir muat turun, status tugas, kemajuan dan ralat, dengan sokongan batal, cuba semula dan bersihkan rekod.',
modelDownloaderTitle4: 'Pengurusan Tempatan',
'Semak dan segerakkan Recipes rasmi, urus resipi tersuai, padankan model dan guna semula parameter penggunaan.',
modelDownloaderTitle4: 'Konfigurasi',
modelDownloaderContent4:
'Urus direktori model tempatan di satu tempat, termasuk laluan, saiz, masa dan pemadaman fail model yang tidak digunakan.',
vllmTitle1: 'Pengurusan Berpusat',
'Tetapkan direktori muat turun model, alamat sumber, token akses dan penyegerakan automatik model pilihan.',
vllmTitle1: 'Penggunaan Model',
vllmContent1:
'Integrasikan instans vLLM yang diedarkan ke dalam 1Panel dan pantau versi, status operasi serta direktori kerja secara berpusat.',
vllmTitle2: 'Penerapan Pantas',
'Pilih direktori model dan imej kontena, tetapkan GPU, port dan parameter permulaan, kemudian cipta instans vLLM.',
vllmTitle2: 'Pemantauan Perkhidmatan',
vllmContent2:
'Gunakan borang visual untuk memilih versi, memasang direktori model dan mengkonfigurasi parameter permulaan, sekali gus mengurangkan kerumitan konfigurasi manual.',
vllmTitle3: 'Operasi yang Cekap',
'Lihat status instans, jumlah permintaan, daya pemprosesan dan kependaman untuk memantau prestasi perkhidmatan model.',
vllmTitle3: 'Operasi Harian',
vllmContent3:
'Urus penciptaan, penyuntingan, permulaan, pemberhentian, mulakan semula, penghapusan instans serta pemantauan tugas dalam satu antara muka bagi meningkatkan kecekapan operasi perkhidmatan model tempatan.',
vllmTitle4: 'Konfigurasi Fleksibel',
vllmContent4:
'Urus port, kontena, arahan permulaan dan konfigurasi Compose secara berpusat melalui tetapan lanjutan untuk memudahkan penerapan piawai serta pelarasan selepas pelaksanaan.',
'Mulakan, hentikan, mulakan semula dan padam instans, lihat log, buka terminal dan jejak tugas operasi.',
},
node: {
master: 'Nod Utama',
+16 -19
View File
@@ -4094,9 +4094,9 @@ const message = {
fileExchange: 'Atualize para a edição comercial para transferir arquivos rapidamente entre vários servidores.',
app: 'Faça upgrade para a edição comercial para visualizar informações do serviço e monitoramento de anomalias no aplicativo móvel.',
cluster: 'Atualize para a edição comercial para gerenciar clusters primário-réplica de MySQL/Postgres/Redis.',
vllm: 'Atualize para a edição comercial para gerenciar serviços vLLM de forma centralizada no 1Panel, incluindo implantação, configuração, operações de ciclo de vida e acompanhamento de tarefas.',
vllm: 'Atualize para a edição comercial para implantar vLLM, monitorar serviços, consultar logs e gerenciar as operações diárias em um só lugar.',
'model-downloader':
'Atualize para a edição comercial para pesquisar, baixar e gerenciar arquivos de modelos locais do HuggingFace, ModelScope ou mirrors compatíveis, com diretórios de modelos e tarefas de download centralizados.',
'Atualize para a edição comercial para explorar modelos em destaque, gerenciar downloads, reutilizar vLLM Recipes e configurar fontes de download e credenciais de acesso.',
exceptionalHelper:
'A verificação da sincronização da licença está anormal. Clique em sincronizar manualmente para garantir o funcionamento da edição comercial. detalhe: ',
tamperHelper:
@@ -7243,30 +7243,27 @@ const message = {
clusterTitle3: 'Estado de Replicação',
clusterContent3:
'Exibe o estado de replicação mestre-escravo e informações de atraso, auxiliando na resolução de problemas de sincronização',
modelDownloaderTitle1: 'Descoberta de modelos',
modelDownloaderTitle1: 'Modelos em destaque',
modelDownloaderContent1:
'Pesquise repositórios de modelos do HuggingFace diretamente e veja downloads, curtidas, detalhes do modelo e lista de arquivos.',
modelDownloaderTitle2: 'Downloads acelerados',
'Explore modelos em destaque, filtre por família e provedor de pesos, consulte os detalhes e inicie downloads.',
modelDownloaderTitle2: 'Download de modelos',
modelDownloaderContent2:
'Configure um endpoint de aceleração do HuggingFace e HuggingFace Token para o site oficial, espelhos e modelos privados.',
modelDownloaderTitle3: 'Acompanhamento de tarefas',
'Baixe modelos do Hugging Face e ModelScope, acompanhe o progresso, cancele ou repita tarefas e gerencie arquivos locais.',
modelDownloaderTitle3: 'vLLM Recipes',
modelDownloaderContent3:
'Veja filas de download, status, progresso e erros, com suporte para cancelar, tentar novamente e limpar registros.',
modelDownloaderTitle4: 'Gerenciamento local',
'Explore e sincronize Recipes oficiais, gerencie receitas personalizadas e reutilize parâmetros de implantação compatíveis com cada modelo.',
modelDownloaderTitle4: 'Configuração',
modelDownloaderContent4:
'Gerencie diretórios de modelos locais em um só lugar, incluindo caminhos, tamanhos, horários e exclusão de modelos não usados.',
vllmTitle1: 'Gestão centralizada',
'Configure o diretório de download, os endereços das fontes, os tokens de acesso e a sincronização automática dos modelos em destaque.',
vllmTitle1: 'Implantação de modelos',
vllmContent1:
'Integre instâncias vLLM distribuídas ao 1Panel e monitore versões, status operacional e diretórios de execução de forma centralizada.',
vllmTitle2: 'Implantação rápida',
'Selecione o diretório do modelo e a imagem do contêiner, configure GPUs, portas e parâmetros de inicialização e crie instâncias do vLLM.',
vllmTitle2: 'Monitoramento do serviço',
vllmContent2:
'Utilize formulários visuais para selecionar versões, montar diretórios de modelos e configurar parâmetros de inicialização, reduzindo a complexidade da configuração manual.',
vllmTitle3: 'Eficiência operacional',
'Consulte o estado das instâncias, a quantidade de requisições, a vazão e a latência do serviço de modelos.',
vllmTitle3: 'Operações diárias',
vllmContent3:
'Gerencie a criação, edição, inicialização, parada, reinicialização, exclusão de instâncias e o acompanhamento de tarefas em uma única interface, aumentando a eficiência operacional dos serviços de modelos locais.',
vllmTitle4: 'Configuração flexível',
vllmContent4:
'Gerencie portas, contêineres, comandos de inicialização e configurações do Compose de forma unificada por meio de opções avançadas, facilitando implantações padronizadas e ajustes posteriores.',
'Inicie, pare, reinicie e exclua instâncias, consulte logs, abra terminais e acompanhe tarefas operacionais.',
},
node: {
master: 'Nó Principal',
+16 -19
View File
@@ -4070,9 +4070,9 @@ const message = {
fileExchange: 'Обновите до Коммерческой версии, чтобы быстро передавать файлы между несколькими серверами.',
app: 'Обновите до коммерческой версии, чтобы просматривать информацию о сервисах, мониторинг аномалий и т.д. через мобильное приложение.',
cluster: 'Обновление до коммерческой версии позволяет управлять кластерами мастер-слейв MySQL/Postgres/Redis.',
vllm: 'Обновите до коммерческой версии, чтобы централизованно управлять сервисами vLLM в 1Panel: развертыванием, настройкой, жизненным циклом и отслеживанием задач.',
vllm: 'Перейдите на коммерческую версию для развёртывания vLLM, мониторинга сервисов, просмотра журналов и централизованного повседневного управления.',
'model-downloader':
'Обновите до коммерческой версии, чтобы искать, загружать и управлять локальными файлами моделей из HuggingFace, ModelScope или совместимых зеркал, централизованно управляя каталогами моделей и задачами загрузки.',
'Перейдите на коммерческую версию, чтобы просматривать рекомендуемые модели, управлять загрузками, повторно использовать vLLM Recipes и настраивать источники загрузки и данные доступа.',
exceptionalHelper:
'Проверка синхронизации лицензии выполнена с ошибкой. Нажмите синхронизацию вручную для корректной работы коммерческой версии. Детали: ',
tamperHelper: 'Операция не выполнена: для файла или папки включена защита от изменений. Проверьте и повторите!',
@@ -7215,30 +7215,27 @@ const message = {
clusterTitle3: 'Состояние репликации',
clusterContent3:
'Отображает состояние репликации мастер-слейв и информацию о задержке, помогая в устранении проблем синхронизации',
modelDownloaderTitle1: 'Поиск моделей',
modelDownloaderTitle1: 'Рекомендуемые модели',
modelDownloaderContent1:
'Ищите репозитории моделей HuggingFace напрямую и просматривайте загрузки, лайки, сведения о модели и список файлов.',
modelDownloaderTitle2: 'Ускоренные загрузки',
'Просматривайте рекомендуемые модели, фильтруйте их по семейству и поставщику весов, изучайте сведения и запускайте загрузку.',
modelDownloaderTitle2: 'Загрузка моделей',
modelDownloaderContent2:
'Настройте endpoint ускорения HuggingFace и HuggingFace Token для официального сайта, зеркал и приватных моделей.',
modelDownloaderTitle3: 'Отслеживание задач',
'Загружайте модели из Hugging Face и ModelScope, отслеживайте прогресс, отменяйте или повторяйте задачи и управляйте локальными файлами.',
modelDownloaderTitle3: 'vLLM Recipes',
modelDownloaderContent3:
'Просматривайте очередь загрузок, статус, прогресс и ошибки, отменяйте, повторяйте и очищайте записи.',
modelDownloaderTitle4: 'Локальное управление',
'Просматривайте и синхронизируйте официальные Recipes, управляйте собственными рецептами и повторно используйте параметры развёртывания для подходящих моделей.',
modelDownloaderTitle4: 'Настройки',
modelDownloaderContent4:
'Управляйте локальными каталогами моделей в одном месте: пути, размеры, время и удаление неиспользуемых файлов моделей.',
vllmTitle1: 'Централизованное управление',
'Настройте каталог загрузки моделей, адреса источников, токены доступа и автоматическую синхронизацию рекомендуемых моделей.',
vllmTitle1: 'Развёртывание моделей',
vllmContent1:
'Интегрируйте распределённые экземпляры vLLM в 1Panel и централизованно отслеживайте версии, состояние работы и рабочие каталоги.',
vllmTitle2: 'Быстрое развёртывание',
'Выберите каталог модели и образ контейнера, настройте GPU, порты и параметры запуска и создайте экземпляры vLLM.',
vllmTitle2: 'Мониторинг сервиса',
vllmContent2:
'Выбирайте версии, подключайте каталоги моделей и настраивайте параметры запуска через визуальную форму, сокращая необходимость ручной настройки.',
vllmTitle3: 'Высокая операционная эффективность',
'Просматривайте состояние экземпляров, число запросов, пропускную способность и задержку сервиса моделей.',
vllmTitle3: 'Повседневное управление',
vllmContent3:
'Создавайте, редактируйте, запускайте, останавливайте, перезапускайте и удаляйте экземпляры, а также отслеживайте задачи через единый интерфейс, повышая эффективность управления локальными сервисами моделей.',
vllmTitle4: 'Гибкая настройка',
vllmContent4:
'Управляйте портами, контейнерами, командами запуска и конфигурациями Compose через расширенные параметры, упрощая стандартизированное развёртывание и последующую настройку.',
'Запускайте, останавливайте, перезапускайте и удаляйте экземпляры, просматривайте журналы, открывайте терминалы и отслеживайте задачи.',
},
node: {
master: 'Главный узел',
+16 -19
View File
@@ -4084,9 +4084,9 @@ const message = {
fileExchange: 'Ticari sürüme yükselterek birden fazla sunucu arasında dosyaları hızlıca aktarabilirsiniz.',
app: 'Ticari sürüme yükselterek mobil uygulamada hizmet bilgilerini ve anormallik izlemeyi görüntüleyebilirsiniz.',
cluster: 'Ticari sürüme yükselterek MySQL/Postgres/Redis birincil-kopya kümelerini yönetebilirsiniz.',
vllm: 'Ticari sürüme yükselterek vLLM hizmetlerini 1Panel içinde merkezi olarak dağıtabilir, yapılandırabilir, yaşam döngüsünü yönetebilir ve görevleri izleyebilirsiniz.',
vllm: 'vLLM dağıtımı, hizmet izleme, günlük görüntüleme ve günlük işlemleri tek yerden yönetmek için ticari sürüme yükseltin.',
'model-downloader':
'Ticari sürüme yükselterek HuggingFace, ModelScope veya uyumlu aynalardan yerel model dosyalarını arayabilir, indirebilir ve yönetebilir; model dizinlerini ve indirme görevlerini merkezi olarak takip edebilirsiniz.',
'Öne çıkan modelleri incelemek, indirmeleri yönetmek, vLLM Recipes içeriklerini yeniden kullanmak ve indirme kaynakları ile erişim bilgilerini yapılandırmak için ticari sürüme yükseltin.',
},
clean: {
scan: 'Taramayı başlat',
@@ -7218,30 +7218,27 @@ const message = {
clusterTitle3: 'Çoğaltma Durumu',
clusterContent3:
'Ana-çalışan çoğaltma durumunu ve gecikme bilgilerini görüntüleyerek senkronizasyon sorunlarını gidermeye yardımcı olur',
modelDownloaderTitle1: 'Model Keşfi',
modelDownloaderTitle1: 'Öne Çıkan Modeller',
modelDownloaderContent1:
'HuggingFace model depolarını doğrudan arayın; indirme sayısı, beğeni, model ayrıntıları ve dosya listesini görüntüleyin.',
modelDownloaderTitle2: 'Hızlandırılmış İndirme',
'Öne çıkan modelleri inceleyin, aile ve ağırlık sağlayıcısına göre filtreleyin, model ayrıntılarını görüntüleyin ve indirmeyi başlatın.',
modelDownloaderTitle2: 'Model İndirme',
modelDownloaderContent2:
'Resmi site, aynalar ve özel modeller için HuggingFace hızlandırma endpointi ve HuggingFace Token yapılandırın.',
modelDownloaderTitle3: 'Görev Takibi',
'Hugging Face ve ModelScope üzerinden model indirin, ilerlemeyi izleyin, görevleri iptal edin veya yeniden deneyin ve yerel dosyaları yönetin.',
modelDownloaderTitle3: 'vLLM Recipes',
modelDownloaderContent3:
'İndirme kuyruğunu, görev durumunu, ilerlemeyi ve hataları görüntüleyin; iptal, yeniden deneme ve kayıt temizlemeyi destekler.',
modelDownloaderTitle4: 'Yerel Yönetim',
'Resmî Recipes içeriklerini inceleyip eşitleyin, özel tarifleri yönetin ve modellere uygun dağıtım parametrelerini yeniden kullanın.',
modelDownloaderTitle4: 'Yapılandırma',
modelDownloaderContent4:
'Yerel model dizinlerini tek yerden yönetin; yolları, boyutları, zamanları görüntüleyin ve kullanılmayan model dosyalarını silin.',
vllmTitle1: 'Merkezi Yönetim',
'Model indirme dizinini, kaynak adreslerini, erişim belirteçlerini ve öne çıkan modellerin otomatik eşitlenmesini yapılandırın.',
vllmTitle1: 'Model Dağıtımı',
vllmContent1:
"Dağıtık vLLM örneklerini 1Panel'e entegre edin ve sürümleri, çalışma durumlarını ve çalışma dizinlerini merkezi olarak yönetin.",
vllmTitle2: 'Hızlı Dağıtım',
'Model dizinini ve konteyner imajını seçin, GPU, port ve başlatma parametrelerini yapılandırın ve vLLM örnekleri oluşturun.',
vllmTitle2: 'Hizmet İzleme',
vllmContent2:
'Görsel formlar aracılığıyla sürüm seçimi, model dizini bağlama ve başlatma parametrelerinin yapılandırılmasını tamamlayarak manuel yapılandırma karmaşıklığını azaltın.',
vllmTitle3: 'Operasyonel Verimlilik',
'Model hizmetinin performansını izlemek için örnek durumunu, istek sayısını, işlem hacmini ve gecikmeyi görüntüleyin.',
vllmTitle3: 'Günlük İşlemler',
vllmContent3:
'Örnek oluşturma, düzenleme, başlatma, durdurma, yeniden başlatma, silme ve görev takibini tek bir arayüzden gerçekleştirerek yerel model hizmetlerinin yönetim verimliliğini artırın.',
vllmTitle4: 'Esnek Yapılandırma',
vllmContent4:
'Gelişmiş ayarlar aracılığıyla portları, konteynerleri, başlatma komutlarını ve Compose yapılandırmalarını merkezi olarak yönetin; standartlaştırılmış dağıtım ve sonraki ayarlamaları kolaylaştırın.',
'Örnekleri başlatın, durdurun, yeniden başlatın ve silin; günlükleri görüntüleyin, terminalleri açın ve işlem görevlerini takip edin.',
},
node: {
master: 'Ana Düğüm',
+16 -19
View File
@@ -3755,9 +3755,8 @@ const message = {
fileExchange: '升級商業版可以在多台伺服器之間快速傳輸檔案。',
app: '升級商業版可透過手機APP,檢視服務資訊、異常監控等。',
cluster: '升級商業版可以管理 MySQL/Postgres/Redis 主從叢集。',
vllm: '升級商業版可將 vLLM 服務統一納入 1Panel 管理,集中完成部署、設定調整、狀態維運與任務追蹤,降低本地模型服務維護成本。',
'model-downloader':
'升級商業版可從 HuggingFace、ModelScope 或相容鏡像站搜尋、下載並管理本機模型檔案,統一維護本機模型目錄與下載任務。',
vllm: '升級商業版可部署 vLLM 執行個體、監控服務執行情況,並集中完成日誌查看與日常維運。',
'model-downloader': '升級商業版可瀏覽精選模型、管理模型下載、重用 vLLM Recipes,並統一設定下載來源與存取憑證。',
},
clean: {
scan: '開始掃描',
@@ -6588,23 +6587,21 @@ const message = {
clusterContent2: '統一頁面集中管理多個主從節點,檢視其角色、執行狀態等資訊',
clusterTitle3: '複製狀態',
clusterContent3: '展示主從複製狀態與延遲資訊,輔助排查同步異常問題',
vllmTitle1: '統一納管',
vllmContent1: '將分散的 vLLM 服務統一接入 1Panel,集中檢視實例、版本、狀態與執行目錄。',
vllmTitle2: '快速部署',
vllmContent2: '透過視覺化表單完成版本選擇、模型目錄掛載與啟動參數設定,減少手動編排成本。',
vllmTitle3: '維運便捷',
vllmContent3: '在同一頁面完成建立、編輯、啟停、重啟、刪除與任務追蹤,提升本地模型服務維護效率。',
vllmTitle4: '設定可控',
vllmContent4: '結合進階設定統一管理連接埠、容器、啟動命令與 Compose 設定,便於標準化交付與後續調整。',
modelDownloaderTitle1: '模型發現',
modelDownloaderContent1: '直接搜尋 HuggingFace 模型倉庫,檢視下載量、點讚數、模型詳情與檔案列表。',
modelDownloaderTitle2: '加速下載',
vllmTitle1: '模型部署',
vllmContent1: '選擇模型目錄與執行映像,設定 GPU、連接埠和啟動參數,快速建立 vLLM 執行個體。',
vllmTitle2: '執行監控',
vllmContent2: '查看執行個體狀態、請求量、吞吐量和延遲,掌握模型服務執行情況。',
vllmTitle3: '日常維運',
vllmContent3: '啟停、重新啟動和刪除執行個體,查看日誌、進入終端並追蹤操作任務。',
modelDownloaderTitle1: '精選模型',
modelDownloaderContent1: '瀏覽精選模型,依模型系列和權重提供方篩選,查看模型資訊並開始下載。',
modelDownloaderTitle2: '模型下載',
modelDownloaderContent2:
'支援設定 HuggingFace 加速地址與 HuggingFace Token,適配官方站、鏡像站和私有模型存取。',
modelDownloaderTitle3: '任務追蹤',
modelDownloaderContent3: '統一檢視下載佇列、任務狀態、進度、錯誤資訊,並支援取消、重試和記錄清理。',
modelDownloaderTitle4: '本地管理',
modelDownloaderContent4: '集中維護本地模型目錄,檢視模型路徑、大小和時間,支援刪除不再需要的模型檔案。',
'從 Hugging Face 和 ModelScope 下載模型,查看任務進度,支援取消、重試和本機檔案管理。',
modelDownloaderTitle3: 'vLLM Recipes',
modelDownloaderContent3: '瀏覽及同步官方 Recipes,管理自訂配方,匹配模型並重用部署參數。',
modelDownloaderTitle4: '設定',
modelDownloaderContent4: '統一設定模型下載目錄、下載來源網址、存取 Token 和精選模型自動同步。',
},
node: {
master: '主節點',
+16 -19
View File
@@ -3785,9 +3785,8 @@ const message = {
fileExchange: '升级商业版可以在多台服务器之间快速传输文件。',
app: '升级商业版可通过手机 APP,查看服务信息、异常监控等。',
cluster: '升级商业版可以管理 MySQL/Postgres/Redis 主从集群。',
vllm: '升级商业版可将 vLLM 服务统一纳入 1Panel 管理,集中完成部署、配置调整、状态运维与任务追踪,降低本地模型服务维护成本。',
'model-downloader':
'升级商业版可从 HuggingFace、ModelScope 或兼容镜像站搜索、下载并管理本地模型文件,统一维护本地模型目录与下载任务。',
vllm: '升级商业版可部署 vLLM 实例、监控服务运行情况,并集中完成日志查看与日常运维。',
'model-downloader': '升级商业版可浏览精选模型、管理模型下载、复用 vLLM Recipes,并统一配置下载源与访问凭据。',
},
clean: {
scan: '开始扫描',
@@ -5546,23 +5545,21 @@ const message = {
clusterContent2: '统一页面集中管理多个主从节点,查看其角色、运行状态等信息',
clusterTitle3: '复制状态',
clusterContent3: '展示主从复制状态与延迟信息,辅助排查同步异常问题',
vllmTitle1: '统一纳管',
vllmContent1: '将分散的 vLLM 服务统一接入 1Panel,集中查看实例、版本、状态与运行目录。',
vllmTitle2: '快速部署',
vllmContent2: '通过可视化表单完成版本选择、模型目录挂载与启动参数配置,减少手工编排成本。',
vllmTitle3: '运维便捷',
vllmContent3: '在同一页面完成创建、编辑、启停、重启、删除与任务追踪,提升本地模型服务维护效率。',
vllmTitle4: '配置可控',
vllmContent4: '结合高级设置统一管理端口、容器、启动命令与 Compose 配置,便于标准化交付与后续调整。',
modelDownloaderTitle1: '模型发现',
modelDownloaderContent1: '直接搜索 HuggingFace 模型仓库,查看下载量、点赞数、模型详情与文件列表。',
modelDownloaderTitle2: '加速下载',
vllmTitle1: '模型部署',
vllmContent1: '选择模型目录与运行镜像,配置 GPU、端口和启动参数,快速创建 vLLM 实例。',
vllmTitle2: '运行监控',
vllmContent2: '查看实例运行状态、请求量、吞吐量和延迟,掌握模型服务运行情况。',
vllmTitle3: '日常运维',
vllmContent3: '启停、重启和删除实例,查看日志、进入终端并跟踪操作任务。',
modelDownloaderTitle1: '精选模型',
modelDownloaderContent1: '浏览精选模型,按模型系列和权重提供方筛选,查看模型信息并发起下载。',
modelDownloaderTitle2: '模型下载',
modelDownloaderContent2:
'支持配置 HuggingFace 加速地址与 HuggingFace Token,适配官方站、镜像站和私有模型访问。',
modelDownloaderTitle3: '任务追踪',
modelDownloaderContent3: '统一查看下载队列、任务状态、进度、错误信息,并支持取消、重试和记录清理。',
modelDownloaderTitle4: '本地管理',
modelDownloaderContent4: '集中维护本地模型目录,查看模型路径、大小和时间,支持删除不再需要的模型文件。',
'从 Hugging Face 和 ModelScope 下载模型,查看任务进度,支持取消、重试和本地文件管理。',
modelDownloaderTitle3: 'vLLM Recipes',
modelDownloaderContent3: '浏览和同步官方 Recipes,管理自定义配方,匹配模型并复用部署参数。',
modelDownloaderTitle4: '配置',
modelDownloaderContent4: '统一配置模型下载目录、下载源地址、访问 Token 和精选模型自动同步。',
},
node: {
master: '主节点',
+2 -35
View File
@@ -6,7 +6,7 @@ const aiRouter = {
path: '/ai',
name: 'AI-Menu',
component: Layout,
redirect: '/ai/model',
redirect: '/ai/model/account',
meta: {
icon: 'p-jiqiren2',
title: 'menu.aiTools',
@@ -15,35 +15,13 @@ const aiRouter = {
{
path: '/ai/model/local/ollama',
name: 'OllamaModels',
component: () => import('@/views/ai/model/ollama/index.vue'),
component: () => import('@/views/ai/ollama/index.vue'),
meta: {
icon: 'p-moxing-menu',
title: 'aiTools.model.ollamaManagement',
permission: 'ai_model_view',
},
},
{
path: '/ai/model',
name: 'AIModel',
redirect: '/ai/model/files',
meta: {
icon: 'p-moxing-menu',
title: 'aiTools.model.vllmManagement',
activeMenu: '/ai/model',
permission: 'ai_model_view',
},
},
{
path: '/ai/model/files',
hidden: true,
name: 'ModelFiles',
component: () => import('@/views/ai/model/index.vue'),
meta: {
title: 'aiTools.model.files',
activeMenu: '/ai/model',
permission: 'ai_model_view',
},
},
{
path: '/ai/model/account',
name: 'ModelServices',
@@ -55,17 +33,6 @@ const aiRouter = {
permission: 'ai_model_view',
},
},
{
path: '/ai/model/local',
hidden: true,
name: 'LocalModel',
component: () => import('@/views/ai/model/index.vue'),
meta: {
title: 'aiTools.model.deployments',
activeMenu: '/ai/model',
permission: 'ai_model_view',
},
},
{
path: '/ai/agents/agent',
name: 'Agents',
+1 -1
View File
@@ -39,7 +39,7 @@ export const jumpToInstall = (type: string, key: string) => {
});
return true;
case 'vllm':
if (globalStore.isProductPro) {
if (globalStore.isProductPro && router.hasRoute('LocalModel')) {
router.push({
path: '/ai/model/local',
query: {
+13
View File
@@ -0,0 +1,13 @@
import type { ReqPage, ResPage, ResultData } from '@/api/interface';
export async function readPages<T>(search: (page: ReqPage) => Promise<ResultData<ResPage<T>>>) {
const items: T[] = [];
for (let page = 1; ; page++) {
const res = await search({ page, pageSize: 100 });
const batch = res.data.items || [];
items.push(...batch);
if (items.length >= res.data.total || batch.length === 0) {
return items;
}
}
}
@@ -156,7 +156,7 @@ import {
} from '@/utils/agent';
import { App } from '@/api/interface/app';
import AdvancedSetting from '@/components/advanced-setting/index.vue';
import AccountAddDialog from '@/views/ai/model/components/account-form/index.vue';
import AccountAddDialog from '@/views/ai/model/account-form/index.vue';
import { useGlobalStore } from '@/composables/useGlobalStore';
import i18n from '@/lang';
@@ -258,7 +258,7 @@ defineExpose({ init, loading });
</script>
<style lang="scss" scoped>
@use '@/views/ai/model/components/form-section.scss';
@use '@/components/form-section/index.scss';
.gpu-capabilities-label {
display: inline-flex;
align-items: flex-start;
@@ -328,7 +328,22 @@ import { useGlobalStore } from '@/composables/useGlobalStore';
import ProviderLogo from '@/components/agent-provider-logo/index.vue';
import DrawerPro from '@/components/drawer-pro/index.vue';
import { loadVllmAPI, readPages, vllmAvailable } from '../../deployment';
import { readPages } from '@/utils/pagination';
import type { ReqPage, ResPage, ResultData } from '@/api/interface';
const vllmModules = import.meta.glob<{
pageVllm: (req: ReqPage & { info: string }) => Promise<
ResultData<
ResPage<{
id: number;
name: string;
modelAccountBaseURL: string;
servedModelName: string;
}>
>
>;
}>('@/xpack/api/modules/vllm.ts');
const vllmLoader = Object.values(vllmModules)[0];
const props = defineProps({ inline: Boolean });
const emit = defineEmits(['search', 'close']);
@@ -858,8 +873,8 @@ watch([open, () => form.provider, connectionMode], async ([visible, provider, mo
loadingEngines.value = true;
try {
let engines: typeof localEngines.value = [];
if (provider === 'vllm' && isProductPro.value && vllmAvailable) {
const api = await loadVllmAPI();
if (provider === 'vllm' && isProductPro.value && vllmLoader) {
const api = await vllmLoader();
if (cancelled) return;
const items = await readPages((page) => api.pageVllm({ ...page, info: '' }));
engines = items.map((item) => ({
@@ -1,150 +0,0 @@
<template>
<DrawerPro
v-model="visible"
:header="$t(enginesOnly ? 'aiTools.model.addInstance' : 'aiTools.model.addService')"
:size="isMobile ? 'full' : '50%'"
>
<DockerStatus
v-if="visible && selected !== 'account'"
v-model:is-active="dockerActive"
v-model:is-exist="dockerExists"
/>
<div v-loading="initializing" class="service-form">
<el-alert v-if="loadFailed" :title="$t('aiTools.model.loadFailed')" type="error" :closable="false" />
<AccountForm
v-if="activeForm === 'account'"
ref="accountRef"
inline
@search="emit('search')"
@close="visible = false"
/>
<component
:is="vllmForm"
v-if="activeForm === 'vllm' && vllmForm"
ref="vllmRef"
inline
@search="emit('search')"
@close="visible = false"
@task="openTask"
/>
<template v-if="selected === 'vllm' && (!isProductPro || !vllmAvailable)">
<el-alert :title="$t('license.vllm')" type="info" :closable="false" />
<el-button class="mt-4" type="primary" @click="openVllmLicense">
{{ $t('license.levelUpPro') }}
</el-button>
</template>
</div>
<template v-if="activeForm" #footer>
<el-button :disabled="currentForm?.loading" @click="visible = false">
{{ $t('commons.button.cancel') }}
</el-button>
<el-button
v-permission
type="primary"
:loading="initializing || currentForm?.loading"
:disabled="initializing || loadFailed || !currentForm || currentForm.disabled"
@click="currentForm.submit()"
>
{{ $t('commons.button.confirm') }}
</el-button>
</template>
</DrawerPro>
<AccountForm ref="editAccountRef" @search="emit('search')" />
<TaskLog ref="taskRef" @close="emit('search')" />
</template>
<script setup lang="ts">
import { computed, nextTick, ref, shallowRef, watch } from 'vue';
import type { AI } from '@/api/interface/ai';
import { useGlobalStore } from '@/composables/useGlobalStore';
import { loadOptionalComponent } from '@/extensions/optional';
import AccountForm from '../account-form/index.vue';
import DockerStatus from '@/views/container/docker-status/index.vue';
import TaskLog from '@/components/log/task/index.vue';
import { vllmAvailable } from '../../deployment';
const props = defineProps({ enginesOnly: Boolean });
const emit = defineEmits(['search', 'manage']);
const { isProductPro, isMobile } = useGlobalStore();
const visible = ref(false);
const selected = ref<'account' | 'vllm'>('account');
const modelDir = ref('');
const recipe = ref<{ id?: number; imageType: string }>();
const dockerActive = ref(false);
const dockerExists = ref(true);
const accountRef = ref();
const editAccountRef = ref();
const vllmRef = ref();
const taskRef = ref();
const vllmForm = shallowRef();
const initializing = ref(false);
const loadFailed = ref(false);
const activeForm = computed(() => {
if (!visible.value) return '';
if (selected.value === 'account') return 'account';
if (!dockerActive.value || !dockerExists.value) return '';
if (selected.value === 'vllm' && isProductPro.value && vllmAvailable) return 'vllm';
return '';
});
const currentForm = computed(() => {
if (activeForm.value === 'account') return accountRef.value;
if (activeForm.value === 'vllm') return vllmRef.value;
return undefined;
});
watch(
activeForm,
async (form, _, onCleanup) => {
let cancelled = false;
onCleanup(() => (cancelled = true));
loadFailed.value = false;
initializing.value = Boolean(form);
if (!form) return;
try {
if (form === 'vllm' && !vllmForm.value) {
const component = await loadOptionalComponent('/src/xpack/views/vllm/operate/index.vue');
if (cancelled) return;
vllmForm.value = component;
}
await nextTick();
if (cancelled) return;
if (form === 'account') await accountRef.value.open();
if (form === 'vllm') await vllmRef.value.openCreate(modelDir.value, recipe.value);
} catch {
if (!cancelled) loadFailed.value = true;
} finally {
if (!cancelled) initializing.value = false;
}
},
{ flush: 'post' },
);
const open = (engine?: 'vllm', defaultModelDir = '', defaultRecipe?: { id?: number; imageType: string }) => {
loadFailed.value = false;
modelDir.value = defaultModelDir;
recipe.value = defaultRecipe;
selected.value = engine || (props.enginesOnly ? 'vllm' : 'account');
dockerActive.value = false;
dockerExists.value = true;
visible.value = true;
};
const openAccount = (account: AI.AgentAccountItem) => {
editAccountRef.value.open({ ...account, baseURL: account.baseUrl });
};
const openVllmLicense = () => {
visible.value = false;
emit('manage', { id: 0, name: '' });
};
const openTask = (taskID: string) => taskRef.value.openWithTaskID(taskID);
defineExpose({ open, openAccount });
</script>
<style scoped>
.service-form {
min-height: 100px;
}
</style>
@@ -1,333 +0,0 @@
<template>
<DrawerPro
v-model="drawerVisible"
:header="header"
:resource="instance?.name"
:size="isMobile ? 'full' : 'large'"
@close="emit('search')"
>
<template #content>
<div class="deployment-action" v-loading="loading">
<el-alert v-if="failed" :title="$t('aiTools.model.loadFailed')" type="error" :closable="false" />
<el-empty v-else-if="missing" :description="$t('commons.res.notFound')" />
<template v-else-if="visible && instance && !loading && view !== VllmPlaceholder">
<div class="service-card">
<div class="flex flex-wrap gap-3 items-center mb-4">
<strong>vLLM {{ formatVllmVersion(instance.appVersion) }}</strong>
<Status :status="instance.status" />
</div>
<div class="service-actions">
<el-button
v-for="button in visibleButtons"
:key="button.label"
:disabled="
deleteLoading ||
(button.disabled?.(instance) ?? false) ||
(button.permission && !hasManagePermissionAccess())
"
@click="button.click(instance)"
>
{{ button.label }}
</el-button>
</div>
</div>
<el-descriptions :column="1" border>
<el-descriptions-item :label="$t('commons.table.name')">
{{ instance.name }}
</el-descriptions-item>
<el-descriptions-item :label="$t('menu.container')">
{{ instance.containerName }}
</el-descriptions-item>
<el-descriptions-item :label="$t('aiTools.model.modelDir')">
{{ instance.modelDir || '—' }}
</el-descriptions-item>
</el-descriptions>
</template>
<VllmPlaceholder v-if="visible && view === VllmPlaceholder" />
</div>
</template>
</DrawerPro>
<component
:is="view"
v-if="visible && view && view !== VllmPlaceholder && !failed && !missing"
ref="contentRef"
v-bind="
view === EngineConnection
? {}
: view === ContainerLogs
? { highlightDiff: 210 }
: view === TaskLog
? { width: '70%' }
: ['config', 'monitor'].includes(actionName)
? {}
: { dialog: true }
"
@close="returnToManagement"
@search="emit('search')"
@task="openTask"
/>
<DialogPro
v-model="deleteVisible"
:title="$t('commons.button.delete') + ' - ' + (deleting?.name || '')"
size="small"
>
<p>{{ $t('commons.msg.delete', [deleting?.name || '']) }}</p>
<el-checkbox v-model="forceDelete" :label="$t('website.forceDelete')" />
<template #footer>
<el-button :disabled="deleteLoading" @click="deleteVisible = false">
{{ $t('commons.button.cancel') }}
</el-button>
<el-button v-permission type="primary" :loading="deleteLoading" @click="deleteDeployment">
{{ $t('commons.button.confirm') }}
</el-button>
</template>
</DialogPro>
</template>
<script setup lang="ts">
import { computed, markRaw, nextTick, ref, shallowRef, watch } from 'vue';
import { useRoute } from 'vue-router';
import { useGlobalStore } from '@/composables/useGlobalStore';
import { loadOptionalComponent } from '@/extensions/optional';
import { searchAppInstalled } from '@/api/modules/app';
import i18n from '@/lang';
import { MsgError } from '@/utils/message';
import { hasPermission } from '@/utils/rbac';
import { hasManagePermissionAccess } from '@/utils/permission';
import { newUUID } from '@/utils/id';
import EngineConnection from '../connection/index.vue';
import VllmPlaceholder from '../../vllm/index.vue';
import ContainerLogs from '@/components/log/container-drawer/index.vue';
import TaskLog from '@/components/log/task/index.vue';
import { formatVllmVersion, readPages, loadVllmAPI, vllmAvailable, type Deployment } from '../../deployment';
const emit = defineEmits(['search', 'upgrade']);
const { isMobile, currentNode, isProductPro } = useGlobalStore();
const route = useRoute();
const visible = ref(false);
const standalone = ref(false);
const drawerVisible = computed({
get: () => visible.value && !standalone.value,
set: (value) => {
visible.value = value;
},
});
const loading = ref(false);
const failed = ref(false);
const missing = ref(false);
const header = ref('');
type DeploymentDetails = Pick<Deployment, 'id' | 'name'> & Partial<Deployment>;
const instance = ref<DeploymentDetails>();
const actionName = ref('manage');
const deleteVisible = ref(false);
const deleting = ref<DeploymentDetails>();
const forceDelete = ref(false);
const deleteLoading = ref(false);
const deleted = ref(false);
const view = shallowRef();
const contentRef = ref();
let request = 0;
const openTask = async (taskID: string) => {
emit('search');
visible.value = true;
actionName.value = 'task';
view.value = markRaw(TaskLog);
await nextTick();
contentRef.value?.openWithTaskID(taskID);
};
const openUpgrade = async (installId: number, version: number) => {
const installed = await readPages((page) =>
searchAppInstalled({ ...page, name: instance.value.name, update: true }),
);
if (version !== request || !visible.value) return;
const app = installed.find((item) => item.id === installId);
if (!app) {
missing.value = true;
if (standalone.value) MsgError(i18n.global.t('commons.res.notFound'));
return;
}
if (standalone.value) visible.value = false;
emit('upgrade', app);
};
const open = async (row: DeploymentDetails, action = 'manage', outside = false) => {
const version = ++request;
standalone.value = outside;
instance.value = { ...row };
actionName.value = action;
deleted.value = false;
header.value = `vLLM ${i18n.global.t('aiTools.model.manage')}`;
view.value = undefined;
failed.value = false;
missing.value = false;
visible.value = true;
loading.value = true;
try {
if (!isProductPro.value || !vllmAvailable) {
view.value = markRaw(VllmPlaceholder);
return;
}
const api = await loadVllmAPI();
if (version !== request || !visible.value) return;
const items = await readPages((page) => api.pageVllm({ ...page, info: row.name }));
const detail = items.find((item) => item.id === row.id);
if (version !== request || !visible.value) return;
if (!detail) {
missing.value = true;
if (standalone.value) MsgError(i18n.global.t('commons.res.notFound'));
return;
}
instance.value = detail;
if (detail.taskID && ['config', 'upgrade', 'delete'].includes(action)) {
await openTask(detail.taskID);
return;
}
if (action === 'connection') {
view.value = markRaw(EngineConnection);
await nextTick();
contentRef.value?.acceptParams({
containerName: detail.containerName,
port: detail.port,
containerPort: '',
endpoint: detail.modelAccountBaseURL,
engine: 'vllm',
name: row.name,
modelDir: detail.modelDir,
});
} else if (action === 'delete') {
deleting.value = instance.value;
forceDelete.value = false;
deleteVisible.value = true;
} else if (action === 'config') {
const component = await loadOptionalComponent('/src/xpack/views/vllm/operate/index.vue');
if (version !== request || !visible.value) return;
view.value = markRaw(component);
await nextTick();
await contentRef.value?.openEdit(detail);
} else if (action === 'monitor') {
const component = await loadOptionalComponent('/src/xpack/views/vllm/monitor/index.vue');
if (version !== request || !visible.value) return;
view.value = markRaw(component);
await nextTick();
contentRef.value?.acceptParams(detail);
} else if (action === 'upgrade') {
await openUpgrade(detail.appInstallId, version);
} else if (action === 'log') {
view.value = markRaw(detail.taskID ? TaskLog : ContainerLogs);
await nextTick();
if (detail.taskID) {
contentRef.value?.openWithTaskID(detail.taskID);
} else {
contentRef.value?.acceptParams({
containerID: detail.containerName,
container: detail.containerName,
});
}
}
} catch {
if (version === request) {
failed.value = true;
if (standalone.value) MsgError(i18n.global.t('aiTools.model.loadFailed'));
}
} finally {
if (version === request) loading.value = false;
}
};
watch(visible, (value) => {
if (!value) request++;
});
watch([currentNode, () => route.fullPath, isProductPro], () => {
request++;
visible.value = false;
deleteVisible.value = false;
deleting.value = undefined;
});
const operationDisabled = (row: DeploymentDetails) => Boolean(deleteLoading.value || row.taskID);
const deleteDeployment = async () => {
const version = request;
const row = deleting.value;
if (!row) return;
deleteLoading.value = true;
try {
const taskID = newUUID();
await (
await loadVllmAPI()
).operateVllm({ id: row.id, operate: 'delete', taskID, forceDelete: forceDelete.value });
if (version !== request || !visible.value) return;
await openTask(taskID);
if (version !== request || !visible.value) return;
deleted.value = true;
deleteVisible.value = false;
emit('search');
} finally {
deleteLoading.value = false;
}
};
const deploymentButtons = [
{
label: i18n.global.t('commons.button.edit'),
permission: true,
disabled: operationDisabled,
click: (row: DeploymentDetails) => open(row, 'config'),
},
{
label: i18n.global.t('menu.monitor'),
click: (row: DeploymentDetails) => open(row, 'monitor'),
},
{
label: i18n.global.t('commons.button.upgrade'),
permission: true,
show: (row: DeploymentDetails) => row.upgradable && hasPermission('app_manage'),
disabled: operationDisabled,
click: (row: DeploymentDetails) => open(row, 'upgrade'),
},
{
label: i18n.global.t('commons.button.delete'),
permission: true,
disabled: (row: DeploymentDetails) => operationDisabled(row),
click: (row: DeploymentDetails) => {
deleting.value = row;
forceDelete.value = false;
deleteVisible.value = true;
},
},
];
const visibleButtons = computed(() =>
instance.value ? deploymentButtons.filter((button) => !button.show || button.show(instance.value)) : [],
);
const returnToManagement = async () => {
const previousView = view.value;
await nextTick();
if (view.value !== previousView) return;
if (['task', 'upgrade', 'config'].includes(actionName.value)) emit('search');
if (deleted.value || standalone.value) visible.value = false;
else if (instance.value) open(instance.value);
};
defineExpose({ open });
</script>
<style scoped lang="scss">
.service-card {
border: 1px solid var(--el-border-color-light);
border-radius: 8px;
padding: 16px;
margin-bottom: 20px;
background: var(--el-fill-color-extra-light);
}
.service-actions {
display: flex;
flex-wrap: wrap;
gap: 8px;
.el-button {
margin-left: 0;
}
}
.deployment-action {
min-height: 180px;
overflow-wrap: anywhere;
:deep(.content-container__main > .el-card) {
border: none;
box-shadow: none;
--el-card-padding: 0px;
}
}
</style>
@@ -1,128 +0,0 @@
<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 { 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,
name: '',
containerName: '',
hostPort: 0,
containerPort: '',
hostMode: false,
modelAccountBaseURLType: 'systemIP' as AI.ModelAccountSync['modelAccountBaseURLType'],
modelAccountBaseURL: '',
});
let request = 0;
const close = () => {
request++;
visible.value = false;
saving.value = false;
};
const open = async (row: Deployment) => {
const version = ++request;
form.id = 0;
form.name = row.name;
visible.value = true;
loading.value = true;
try {
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.trim().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 || 'systemIP',
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,
};
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>
@@ -0,0 +1,72 @@
<template>
<DrawerPro v-model="visible" :header="$t('aiTools.model.addService')" :size="isMobile ? 'full' : '50%'">
<div v-loading="initializing" class="service-form">
<el-alert v-if="loadFailed" :title="$t('aiTools.model.loadFailed')" type="error" :closable="false" />
<AccountForm v-if="visible" ref="accountRef" inline @search="emit('search')" @close="visible = false" />
</div>
<template v-if="visible" #footer>
<el-button :disabled="accountRef?.loading" @click="visible = false">
{{ $t('commons.button.cancel') }}
</el-button>
<el-button
v-permission
type="primary"
:loading="initializing || accountRef?.loading"
:disabled="initializing || loadFailed || !accountRef || accountRef.disabled"
@click="accountRef.submit()"
>
{{ $t('commons.button.confirm') }}
</el-button>
</template>
</DrawerPro>
<AccountForm ref="editAccountRef" @search="emit('search')" />
</template>
<script setup lang="ts">
import { nextTick, ref, watch } from 'vue';
import type { AI } from '@/api/interface/ai';
import { useGlobalStore } from '@/composables/useGlobalStore';
import AccountForm from '../account-form/index.vue';
const emit = defineEmits(['search']);
const { isMobile } = useGlobalStore();
const visible = ref(false);
const accountRef = ref();
const editAccountRef = ref();
const initializing = ref(false);
const loadFailed = ref(false);
watch(
visible,
async (value, _, onCleanup) => {
let cancelled = false;
onCleanup(() => (cancelled = true));
loadFailed.value = false;
initializing.value = value;
if (!value) return;
try {
await nextTick();
if (cancelled) return;
await accountRef.value.open();
} catch {
if (!cancelled) loadFailed.value = true;
} finally {
if (!cancelled) initializing.value = false;
}
},
{ flush: 'post' },
);
const open = () => {
loadFailed.value = false;
visible.value = true;
};
const openAccount = (account: AI.AgentAccountItem) => {
editAccountRef.value.open({ ...account, baseURL: account.baseUrl });
};
defineExpose({ open, openAccount });
</script>
<style scoped>
.service-form {
min-height: 100px;
}
</style>
-64
View File
@@ -1,64 +0,0 @@
import type { ReqPage, ResPage, ResultData } from '@/api/interface';
import type { AI } from '@/api/interface/ai';
export interface Deployment {
taskID?: string;
appInstallId: number;
agentAccountId: number;
path: string;
upgradable: boolean;
id: number;
servedModelName: string;
name: string;
appVersion: string;
port: number;
modelAccountBaseURL: string;
modelAccountBaseURLType: AI.ModelAccountSync['modelAccountBaseURLType'];
command: string;
imageType: string;
containerName: string;
modelDir: string;
status: string;
}
interface VllmAPI {
listVllmCommandTemplates: () => Promise<ResultData<Array<{ model: string }>>>;
pageVllm: (req: ReqPage & { info: string }) => Promise<ResultData<ResPage<Deployment>>>;
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');
export const vllmAvailable = Object.keys(vllmModules).length > 0;
export function formatVllmVersion(version: string) {
return String(version || '')
.trim()
.replace(/^(nvidia|intel|ascend)-/i, '');
}
export async function loadVllmAPI() {
return Object.values(vllmModules)[0]();
}
export async function readPages<T>(search: (page: ReqPage) => Promise<ResultData<ResPage<T>>>) {
const items: T[] = [];
for (let page = 1; ; page++) {
const res = await search({ page, pageSize: 100 });
const batch = res.data.items || [];
items.push(...batch);
if (items.length >= res.data.total || batch.length === 0) {
return items;
}
}
}
export function matchesModelRecipe(recipeModel: string, modelPath: string, repoID = '') {
const path = modelPath.replace(/\/+$/, '');
const name = path.split('/').pop() || '';
const model = recipeModel.trim().replace(/\/+$/, '');
return Boolean(
model && [repoID || path.split('/').slice(-2).join('/'), path, name, `/models/${name}`].includes(model),
);
}
+191 -518
View File
@@ -1,363 +1,190 @@
<template>
<div class="model-page">
<RouterButton :buttons="tabs" />
<component :is="downloaderView" v-if="isFiles" @deploy="(modelDir) => createRef.open('vllm', modelDir)" />
<template v-else>
<LayoutContent v-loading="loading">
<template #leftToolBar>
<el-button v-permission type="primary" @click="createRef.open()">
{{ $t(isLocal ? 'aiTools.model.addInstance' : 'aiTools.model.addService') }}
</el-button>
</template>
<template #rightToolBar>
<el-select
v-if="!isLocal"
v-model="apiType"
clearable
filterable
placeholder=""
class="model-filter"
@change="resetPageAndSearch"
<RouterButton :buttons="[{ label: $t('aiTools.model.services'), path: '/ai/model/account' }]" />
<LayoutContent v-loading="loading">
<template #leftToolBar>
<el-button v-permission type="primary" @click="createRef.open()">
{{ $t('aiTools.model.addService') }}
</el-button>
</template>
<template #rightToolBar>
<el-select
v-model="apiType"
clearable
filterable
placeholder=""
class="model-filter"
@change="resetPageAndSearch"
>
<template #prefix>{{ 'API ' + $t('commons.table.type') }}</template>
<el-option v-for="item in apiTypeOptions" :key="item" :label="item" :value="item" />
</el-select>
<el-select
v-model="provider"
clearable
filterable
class="model-filter"
placeholder=""
@change="resetPageAndSearch"
>
<template #prefix>{{ $t('aiTools.agents.provider') }}</template>
<el-option
v-for="item in providerOptions"
:key="item.value"
:label="item.label"
:value="item.value"
>
<template #prefix>{{ 'API ' + $t('commons.table.type') }}</template>
<el-option v-for="item in apiTypeOptions" :key="item" :label="item" :value="item" />
</el-select>
<el-select
v-if="!isLocal"
v-model="provider"
clearable
filterable
class="model-filter"
placeholder=""
@change="resetPageAndSearch"
>
<template #prefix>{{ $t('aiTools.agents.provider') }}</template>
<el-option
v-for="item in providerOptions"
:key="item.value"
:label="item.label"
:value="item.value"
>
<ProviderLabel :provider="item.value" :display-name="item.label" />
</el-option>
</el-select>
<TableSearch v-model:searchName="searchName" class="model-search" @search="resetPageAndSearch" />
<TableRefresh @search="search" />
<TableSetting v-if="isLocal" title="model-deployment-refresh" @search="!loading && search()" />
<fu-table-column-select
v-else
:columns="accountColumns"
trigger="hover"
:title="$t('commons.table.selectColumn')"
:only-icon="true"
/>
</template>
<template #prompt>
<el-alert
type="info"
:closable="false"
:title="$t(isLocal ? 'aiTools.model.addInstanceHelper' : 'aiTools.model.serviceHelper')"
/>
</template>
<template #main>
<ComplexTable
v-if="!isLocal"
:data="connections"
row-key="id"
:pagination-config="pagination"
:columns="accountColumns"
local-key="modelServiceColumn"
@search="search"
>
<el-table-column prop="name" :label="$t('commons.table.name')" min-width="220" fixed="left">
<template #default="{ row }">
<div class="service-name">
<div class="service-title cell-ellipsis" :title="row.name">{{ row.name }}</div>
<el-tag class="service-source-tag" type="info" size="small">
{{
$t(
!row.appInstallId
? 'aiTools.model.cloudAccount'
: 'aiTools.model.localModel',
)
}}
</el-tag>
</div>
</template>
</el-table-column>
<el-table-column prop="provider" :label="$t('aiTools.model.engineProvider')" min-width="180">
<template #default="{ row }">
<div class="cell-ellipsis">
<ProviderLabel :provider="row.provider" :display-name="row.providerName" />
</div>
</template>
</el-table-column>
<el-table-column
prop="apiType"
:label="'API ' + $t('commons.table.type')"
min-width="200"
show-overflow-tooltip
>
<template #default="{ row }">
{{ row.apiType || '—' }}
</template>
</el-table-column>
<el-table-column prop="models" :label="$t('aiTools.model.model')" min-width="200">
<template #default="{ row }">
<div
v-for="(model, index) in (row.models || []).slice(0, 2)"
:key="model.id"
class="model-line"
>
<span class="cell-ellipsis" :title="model.id">{{ model.id }}</span>
<el-button
v-if="!row.appInstallId && index === 1 && row.models.length > 2"
size="small"
class="model-more"
@click="poolRef.open(row)"
>
+{{ row.models.length - 2 }}
</el-button>
<el-tooltip
v-if="row.appInstallId && index === 1 && row.models.length > 2"
:content="
row.models
.slice(2)
.map((item) => item.id)
.join(', ')
"
placement="top"
>
<el-button size="small" class="model-more">
+{{ row.models.length - 2 }}
</el-button>
</el-tooltip>
</div>
<span v-if="!row.models?.length">—</span>
</template>
</el-table-column>
<el-table-column prop="verified" :label="$t('aiTools.model.connectionStatus')" min-width="130">
<template #default="{ row }">
<el-tag :type="connectionStatus(row).type">
{{ connectionStatus(row).label }}
<ProviderLabel :provider="item.value" :display-name="item.label" />
</el-option>
</el-select>
<TableSearch v-model:searchName="searchName" class="model-search" @search="resetPageAndSearch" />
<TableRefresh @search="search" />
<fu-table-column-select
:columns="accountColumns"
trigger="hover"
:title="$t('commons.table.selectColumn')"
:only-icon="true"
/>
</template>
<template #prompt>
<el-alert type="info" :closable="false" :title="$t('aiTools.model.serviceHelper')" />
</template>
<template #main>
<ComplexTable
:data="connections"
row-key="id"
:pagination-config="pagination"
:columns="accountColumns"
local-key="modelServiceColumn"
@search="search"
>
<el-table-column prop="name" :label="$t('commons.table.name')" min-width="220" fixed="left">
<template #default="{ row }">
<div class="service-name">
<div class="service-title cell-ellipsis" :title="row.name">{{ row.name }}</div>
<el-tag class="service-source-tag" type="info" size="small">
{{
$t(
!row.appInstallId
? 'aiTools.model.cloudAccount'
: 'aiTools.model.localModel',
)
}}
</el-tag>
</template>
</el-table-column>
<el-table-column
prop="baseUrl"
:label="$t('aiTools.model.endpoint')"
min-width="280"
show-overflow-tooltip
>
<template #default="{ row }">
{{ row.baseUrl || '—' }}
</template>
</el-table-column>
<el-table-column
prop="createdAt"
:label="$t('commons.table.createdAt')"
:formatter="dateFormat"
min-width="180"
show-overflow-tooltip
/>
<fu-table-operations
:buttons="accountButtons"
:label="$t('commons.table.operate')"
align="left"
fixed="right"
:ellipsis="3"
width="220"
/>
</ComplexTable>
<ComplexTable
v-else
:data="deployments"
:pagination-config="pagination"
:row-style="{ height: '63px' }"
@search="search"
</div>
</template>
</el-table-column>
<el-table-column prop="provider" :label="$t('aiTools.model.engineProvider')" min-width="180">
<template #default="{ row }">
<div class="cell-ellipsis">
<ProviderLabel :provider="row.provider" :display-name="row.providerName" />
</div>
</template>
</el-table-column>
<el-table-column
prop="apiType"
:label="'API ' + $t('commons.table.type')"
min-width="200"
show-overflow-tooltip
>
<el-table-column :label="$t('commons.table.name')" min-width="190">
<template #default="{ row }">
<el-button link type="primary" @click="manageRef.open(row, 'connection', true)">
{{ row.name }}
</el-button>
</template>
</el-table-column>
<el-table-column :label="$t('app.version')" min-width="150">
<template #default="{ row }">
<div class="flex items-center gap-2">
<span>{{ formatVllmVersion(row.appVersion) || '—' }}</span>
<el-button
v-if="row.upgradable && hasPermission('app_manage')"
v-permission
link
type="primary"
class="shrink-0"
:disabled="operationDisabled(row)"
@click="manageRef.open(row, 'upgrade', true)"
>
{{ $t('commons.button.upgrade') }}
</el-button>
</div>
</template>
</el-table-column>
<el-table-column :label="$t('commons.table.port')" min-width="120">
<template #default="{ row }">
<template #default="{ row }">
{{ row.apiType || '—' }}
</template>
</el-table-column>
<el-table-column prop="models" :label="$t('aiTools.model.model')" min-width="200">
<template #default="{ row }">
<div
v-for="(model, index) in (row.models || []).slice(0, 2)"
:key="model.id"
class="model-line"
>
<span class="cell-ellipsis" :title="model.id">{{ model.id }}</span>
<el-button
v-if="row.port > 0"
icon="Position"
plain
v-if="!row.appInstallId && index === 1 && row.models.length > 2"
size="small"
@click="portJumpRef.acceptParams({ port: row.port, ip: '', protocol: 'http' })"
class="model-more"
@click="poolRef.open(row)"
>
{{ row.port }}
+{{ row.models.length - 2 }}
</el-button>
<span v-else>—</span>
</template>
</el-table-column>
<el-table-column :label="$t('aiTools.model.model')" min-width="200">
<template #default="{ row }">
<div class="cell-ellipsis" :title="row.servedModelName">
{{ row.servedModelName || '—' }}
</div>
</template>
</el-table-column>
<el-table-column :label="$t('commons.table.status')" min-width="150">
<template #default="{ row }">
<el-dropdown placement="bottom" :disabled="statusDisabled(row)">
<Status v-permission :status="row.status" operate :disabled="statusDisabled(row)" />
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item
:disabled="statusDisabled(row) || row.status === 'Running'"
@click="operate(row, 'start')"
>
{{ $t('commons.operate.start') }}
</el-dropdown-item>
<el-dropdown-item
:disabled="
statusDisabled(row) || ['Stopped', 'Exited'].includes(row.status)
"
@click="operate(row, 'stop')"
>
{{ $t('commons.operate.stop') }}
</el-dropdown-item>
<el-dropdown-item
:disabled="statusDisabled(row)"
@click="operate(row, 'restart')"
>
{{ $t('commons.button.restart') }}
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</template>
</el-table-column>
<el-table-column :label="$t('commons.button.log')" min-width="90">
<template #default="{ row }">
<el-button @click="manageRef.open(row, 'log', true)" link type="primary">
{{ $t('website.check') }}
</el-button>
</template>
</el-table-column>
<el-table-column :label="$t('container.source')" min-width="170">
<template #default="{ row }">
<template v-if="row.status === 'Running' && row.containerName">
<ContainerResource
v-if="resourceStats[row.containerName]"
:stats="resourceStats[row.containerName]"
/>
<el-icon
v-else-if="resourceStats[row.containerName] === undefined"
class="is-loading"
>
<Loading />
</el-icon>
<el-tooltip v-else :content="$t('aiTools.model.loadFailed')">
<span>—</span>
</el-tooltip>
</template>
<span v-else>—</span>
</template>
</el-table-column>
<fu-table-operations
:buttons="deploymentButtons"
:label="$t('commons.table.operate')"
fixed="right"
:ellipsis="2"
width="240"
/>
</ComplexTable>
</template>
</LayoutContent>
</template>
<CreateService ref="createRef" :engines-only="isLocal || isFiles" @search="search" @manage="manageDeployment" />
<ManageDeployment
ref="manageRef"
@search="search"
@upgrade="(app) => upgradeRef.acceptParams(app, 'upgrade')"
/>
<AppUpgrade ref="upgradeRef" @close="search" />
<TaskLog ref="taskRef" width="70%" @close="search" />
<el-tooltip
v-if="row.appInstallId && index === 1 && row.models.length > 2"
:content="
row.models
.slice(2)
.map((item) => item.id)
.join(', ')
"
placement="top"
>
<el-button size="small" class="model-more">+{{ row.models.length - 2 }}</el-button>
</el-tooltip>
</div>
<span v-if="!row.models?.length">—</span>
</template>
</el-table-column>
<el-table-column prop="verified" :label="$t('aiTools.model.connectionStatus')" min-width="130">
<template #default="{ row }">
<el-tag :type="connectionStatus(row).type">
{{ connectionStatus(row).label }}
</el-tag>
</template>
</el-table-column>
<el-table-column
prop="baseUrl"
:label="$t('aiTools.model.endpoint')"
min-width="280"
show-overflow-tooltip
>
<template #default="{ row }">
{{ row.baseUrl || '—' }}
</template>
</el-table-column>
<el-table-column
prop="createdAt"
:label="$t('commons.table.createdAt')"
:formatter="dateFormat"
min-width="180"
show-overflow-tooltip
/>
<fu-table-operations
:buttons="accountButtons"
:label="$t('commons.table.operate')"
align="left"
fixed="right"
:ellipsis="3"
width="220"
/>
</ComplexTable>
</template>
</LayoutContent>
<CreateService ref="createRef" @search="search" />
<ModelPool ref="poolRef" @updated="search" />
<TerminalDialog :key="`terminal:${currentNode}:${route.fullPath}`" ref="terminalRef" />
<SyncService ref="syncRef" @search="search" />
<PortJump ref="portJumpRef" />
</div>
</template>
<script setup lang="ts">
import { computed, defineAsyncComponent, nextTick, reactive, ref, toRef, watch } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { computed, reactive, ref, toRef, watch } from 'vue';
import { useRoute } from 'vue-router';
import { ElMessageBox } from 'element-plus';
import type { AI } from '@/api/interface/ai';
import type { Container } from '@/api/interface/container';
import { containerListStats } from '@/api/modules/container';
import ContainerResource from '@/components/container-resource/index.vue';
import PortJump from '@/components/port-jump/index.vue';
import type { FuTableColumnConfig } from '@/components/table/shared';
import { deleteAgentAccount, getAgentProviders, pageAgentAccounts } from '@/api/modules/ai';
import { useGlobalStore } from '@/composables/useGlobalStore';
import { getAgentProviderDisplayName, isAgentAccountVerificationSkipped } from '@/utils/agent';
import i18n from '@/lang';
import { hasPermission } from '@/utils/rbac';
import { hasManagePermissionAccess } from '@/utils/permission';
import { newUUID } from '@/utils/id';
import { dateFormat } from '@/utils/date';
import { useSearchState } from '@/utils/search-reset';
import TaskLog from '@/components/log/task/index.vue';
import CreateService from './components/create/index.vue';
import ManageDeployment from './components/deployment-manage/index.vue';
import AppUpgrade from '@/views/app-store/installed/upgrade/index.vue';
import ModelPool from './components/model-pool/index.vue';
import TerminalDialog from '@/views/container/container/terminal/index.vue';
import SyncService from './components/sync/index.vue';
import CreateService from './create/index.vue';
import ModelPool from './model-pool/index.vue';
import ProviderLabel from '@/components/agent-provider-label/index.vue';
import { formatVllmVersion, loadVllmAPI, vllmAvailable, type Deployment } from './deployment';
const route = useRoute();
const router = useRouter();
const pagePath = route.path;
const isCurrentPage = computed(() => route.path === pagePath);
const { isProductPro, isIntl, currentNode, isAdminOrNodeAdmin } = useGlobalStore();
const isFiles = pagePath === '/ai/model/files';
const isLocal = pagePath === '/ai/model/local';
const tabs = computed(() =>
isFiles || isLocal
? [
{ label: i18n.global.t('aiTools.model.files'), path: '/ai/model/files' },
{ label: i18n.global.t('aiTools.model.deployments'), path: '/ai/model/local' },
]
: [{ label: i18n.global.t('aiTools.model.services'), path: '/ai/model/account' }],
);
const downloaderView = defineAsyncComponent(() => import('@/views/model-downloader/index.vue'));
const { isIntl, currentNode } = useGlobalStore();
const createRef = ref();
const manageRef = ref();
const upgradeRef = ref();
const poolRef = ref();
const terminalRef = ref<InstanceType<typeof TerminalDialog>>();
const syncRef = ref<InstanceType<typeof SyncService>>();
const portJumpRef = ref<InstanceType<typeof PortJump>>();
const loading = ref(false);
const connections = ref<AI.AgentAccountItem[]>([]);
const accountColumns = ref<FuTableColumnConfig[]>([]);
@@ -365,7 +192,6 @@ const providers = ref<AI.ProviderInfo[]>([]);
watch(
currentNode,
async (_, __, onCleanup) => {
if (isLocal || isFiles) return;
let cancelled = false;
onCleanup(() => (cancelled = true));
providers.value = [];
@@ -378,32 +204,29 @@ watch(
},
{ immediate: true },
);
const deployments = ref<Deployment[]>([]);
const apiType = ref('');
const provider = ref('');
const searchName = ref('');
const pagination = reactive({ currentPage: 1, pageSize: 10, total: 0 });
if (!isLocal && !isFiles) {
useSearchState(
'ai/agents/model/index',
{
search: searchName,
apiType,
provider,
page: toRef(pagination, 'currentPage'),
pageSize: toRef(pagination, 'pageSize'),
useSearchState(
'ai/agents/model/index',
{
search: searchName,
apiType,
provider,
page: toRef(pagination, 'currentPage'),
pageSize: toRef(pagination, 'pageSize'),
},
{
onRevisit: () => {
if (typeof route.query.name === 'string' && route.query.name !== searchName.value) {
searchName.value = route.query.name;
resetPage();
}
return search();
},
{
onRevisit: () => {
if (typeof route.query.name === 'string' && route.query.name !== searchName.value) {
searchName.value = route.query.name;
resetPage();
}
return search();
},
},
);
}
},
);
const apiTypeOptions = computed(() =>
Array.from(new Set(providers.value.flatMap((item) => item.apiTypes.map((api) => api.apiType)))).sort(),
);
@@ -415,36 +238,6 @@ const providerOptions = computed(() =>
label: getAgentProviderDisplayName(item.provider, item.displayName),
})),
);
const resourceStats = ref<Record<string, Container.ContainerListStats | null>>({});
watch(
[deployments, isCurrentPage, currentNode],
async ([rows, active, node], _, onCleanup) => {
resourceStats.value = {};
const controller = new AbortController();
onCleanup(() => {
controller.abort();
});
if (!active || !isLocal) return;
const names = [
...new Set(
rows.filter((row) => row.status === 'Running' && row.containerName).map((row) => row.containerName),
),
];
if (!names.length) return;
try {
const { data } = await containerListStats(node, undefined, {
signal: controller.signal,
skipErrorMessage: true,
});
if (controller.signal.aborted) return;
const statsByName = new Map((data || []).map((item) => [item.containerName, item]));
resourceStats.value = Object.fromEntries(names.map((name) => [name, statsByName.get(name) || null]));
} catch {
if (!controller.signal.aborted) resourceStats.value = Object.fromEntries(names.map((name) => [name, null]));
}
},
{ immediate: true },
);
const resetPage = () => {
pagination.currentPage = 1;
};
@@ -461,37 +254,19 @@ const resetPageIfNeeded = () => {
let searchVersion = 0;
const search = async () => {
const version = ++searchVersion;
if (!isCurrentPage.value || isFiles) return;
if (!isCurrentPage.value) return;
loading.value = true;
try {
if (isLocal) {
if (!isProductPro.value || !vllmAvailable) {
deployments.value = [];
pagination.total = 0;
return;
}
const api = await loadVllmAPI();
if (version !== searchVersion) return;
const { data } = await api.pageVllm({
page: pagination.currentPage,
pageSize: pagination.pageSize,
info: searchName.value,
});
if (version !== searchVersion) return;
deployments.value = data.items || [];
pagination.total = data.total;
} else {
const result = await pageAgentAccounts({
page: pagination.currentPage,
pageSize: pagination.pageSize,
apiType: apiType.value,
provider: provider.value,
name: searchName.value,
});
if (version !== searchVersion) return;
connections.value = result.data.items || [];
pagination.total = result.data.total;
}
const result = await pageAgentAccounts({
page: pagination.currentPage,
pageSize: pagination.pageSize,
apiType: apiType.value,
provider: provider.value,
name: searchName.value,
});
if (version !== searchVersion) return;
connections.value = result.data.items || [];
pagination.total = result.data.total;
const previousPage = pagination.currentPage;
resetPageIfNeeded();
if (previousPage !== pagination.currentPage) await search();
@@ -499,7 +274,6 @@ const search = async () => {
if (version !== searchVersion) return;
connections.value = [];
pagination.total = 0;
deployments.value = [];
} finally {
if (version === searchVersion) loading.value = false;
}
@@ -514,7 +288,6 @@ const connectionStatus = (row: AI.AgentAccountItem) => {
label: i18n.global.t(row.verified ? 'aiTools.agents.verified' : 'aiTools.model.unverified'),
};
};
const manageDeployment = (row: Deployment) => manageRef.value.open(row);
const accountButtons = [
{
label: i18n.global.t('commons.button.edit'),
@@ -540,120 +313,20 @@ const accountButtons = [
},
},
];
const taskRef = ref();
const pending = ref(false);
const operationDisabled = (row: Deployment) => Boolean(pending.value || row.taskID);
const statusDisabled = (row: Deployment) => operationDisabled(row) || !hasManagePermissionAccess();
const operate = async (row: Deployment, operation: 'start' | 'stop' | 'restart') => {
if (statusDisabled(row)) return;
const node = currentNode.value;
await ElMessageBox.confirm(
i18n.global.t('app.operatorHelper', [i18n.global.t('commons.operate.' + operation)]),
i18n.global.t('commons.operate.' + operation),
{ type: 'warning' },
);
if (node !== currentNode.value || statusDisabled(row)) return;
pending.value = true;
try {
const taskID = newUUID();
await (await loadVllmAPI()).operateVllm({ id: row.id, operate: operation, taskID });
if (node !== currentNode.value) return;
taskRef.value.openWithTaskID(taskID);
await search();
} finally {
pending.value = false;
}
};
const deploymentButtons = [
{
label: i18n.global.t('commons.button.edit'),
permission: true,
disabled: operationDisabled,
click: (row: Deployment) => manageRef.value.open(row, 'config', true),
},
{
label: i18n.global.t('menu.monitor'),
click: (row: Deployment) => manageRef.value.open(row, 'monitor', true),
},
{
label: i18n.global.t('menu.terminal'),
disabled: (row: Deployment) =>
operationDisabled(row) || row.status !== 'Running' || !row.containerName || !isAdminOrNodeAdmin.value,
click: (row: Deployment) =>
terminalRef.value?.acceptParams({
containerID: row.containerName,
title: row.name,
node: currentNode.value,
}),
},
{
label: i18n.global.t('commons.button.sync'),
permission: true,
disabled: operationDisabled,
click: (row: Deployment) => syncRef.value.open(row),
},
{
label: i18n.global.t('commons.button.delete'),
permission: true,
disabled: (row: Deployment) => operationDisabled(row),
click: (row: Deployment) => manageRef.value.open(row, 'delete', true),
},
];
watch(
[
() => route.path,
() => route.query.tab,
() => route.query.name,
() => route.query.open,
() => route.query.engine,
currentNode,
isProductPro,
],
async (_, previous, onCleanup) => {
let cancelled = false;
onCleanup(() => (cancelled = true));
[() => route.path, () => route.query.name, currentNode],
async (_, previous) => {
if (!isCurrentPage.value) {
searchVersion++;
loading.value = false;
return;
}
if (isLocal && route.query.tab === 'downloader') {
await router.replace('/ai/model/files');
return;
}
if (
isLocal &&
route.query.open === 'create' &&
route.query.engine !== 'vllm' &&
(route.query.engine === 'ollama' || route.query.tab === 'ollama')
) {
await router.replace({ name: 'AppAll', query: { install: 'ollama' } });
return;
}
if (isLocal || isFiles) {
apiType.value = '';
provider.value = '';
searchName.value = '';
if (previous.length && previous[0] !== pagePath) return;
if (typeof route.query.name === 'string' && route.query.name !== searchName.value) {
searchName.value = route.query.name;
resetPage();
} else {
if (previous.length && previous[0] !== pagePath) return;
if (typeof route.query.name === 'string' && route.query.name !== searchName.value) {
searchName.value = route.query.name;
resetPage();
}
}
await search();
if (cancelled) return;
if (isCurrentPage.value && isLocal && route.query.open === 'create' && route.query.engine === 'vllm') {
await nextTick();
if (cancelled) return;
await createRef.value.open('vllm');
if (cancelled) return;
const query = { ...route.query };
delete query.open;
delete query.tab;
await router.replace({ path: route.path, query });
}
},
{ immediate: true },
);
@@ -61,7 +61,7 @@
<script setup lang="ts">
import { computed, reactive, ref } from 'vue';
import type { FormInstance } from 'element-plus';
import OllamaAdd from '@/views/ai/model/ollama/add/index.vue';
import OllamaAdd from '@/views/ai/ollama/add/index.vue';
import TaskLog from '@/components/log/task/index.vue';
import OpDialog from '@/components/del-dialog/index.vue';
import {
@@ -1,99 +0,0 @@
<template>
<div>
<LayoutContent title="vLLM">
<template #leftToolBar>
<el-button type="primary" @click="goLicense">
{{ $t('license.levelUpPro') }}
</el-button>
</template>
<template #prompt>
<el-alert type="info" :closable="false">
<template #title>
<span>{{ $t('license.vllm') }}</span>
</template>
</el-alert>
</template>
<template #main>
<div class="vllm-placeholder">
<div class="vllm-placeholder__intro">
<div class="vllm-placeholder__title">{{ $t('license.introduce') }}</div>
<div class="vllm-placeholder__desc">{{ $t('license.vllm') }}</div>
</div>
<el-row :gutter="16">
<el-col v-for="item in features" :key="item.title" :xs="24" :sm="12" :xl="6">
<el-card class="feature-card" shadow="hover">
<template #header>
<div class="feature-card__header">
<svg-icon class="feature-card__icon" :iconName="item.icon" />
<span>{{ $t(item.title) }}</span>
</div>
</template>
<div class="feature-card__content">{{ $t(item.content) }}</div>
</el-card>
</el-col>
</el-row>
</div>
</template>
</LayoutContent>
</div>
</template>
<script setup lang="ts">
import { routerToName } from '@/utils/router';
const features = [
{ icon: 'p-ai-1', title: 'xpack.helper.vllmTitle1', content: 'xpack.helper.vllmContent1' },
{ icon: 'p-ai-2', title: 'xpack.helper.vllmTitle2', content: 'xpack.helper.vllmContent2' },
{ icon: 'p-ai-3', title: 'xpack.helper.vllmTitle3', content: 'xpack.helper.vllmContent3' },
{ icon: 'p-ai-4', title: 'xpack.helper.vllmTitle4', content: 'xpack.helper.vllmContent4' },
];
const goLicense = () => {
routerToName('License');
};
</script>
<style scoped lang="scss">
.vllm-placeholder {
padding-bottom: 16px;
}
.vllm-placeholder__intro {
max-width: 760px;
margin-bottom: 20px;
}
.vllm-placeholder__title {
font-size: 28px;
font-weight: 500;
line-height: 1.4;
}
.vllm-placeholder__desc {
margin-top: 12px;
color: var(--el-text-color-regular);
line-height: 1.7;
}
.feature-card {
height: 100%;
border: var(--panel-border);
}
.feature-card__header {
display: flex;
align-items: center;
gap: 10px;
font-size: 16px;
font-weight: 500;
}
.feature-card__icon {
font-size: 18px;
}
.feature-card__content {
color: var(--el-text-color-regular);
line-height: 1.7;
}
</style>
@@ -151,13 +151,13 @@
<script lang="ts" setup>
import { useSearchState } from '@/utils/search-reset';
import AddDialog from '@/views/ai/model/ollama/add/index.vue';
import AddDialog from '@/views/ai/ollama/add/index.vue';
import TaskLog from '@/components/log/task/index.vue';
import Terminal from '@/views/ai/model/ollama/terminal/index.vue';
import Terminal from '@/views/ai/ollama/terminal/index.vue';
import { toRef, reactive, ref } from 'vue';
import { ElMessageBox } from 'element-plus';
import AppStatus from '@/components/app-status/index.vue';
import Connection from '@/views/ai/model/components/connection/index.vue';
import Connection from '@/views/ai/components/connection/index.vue';
import PortJumpDialog from '@/components/port-jump/index.vue';
import { App } from '@/api/interface/app';
import i18n from '@/lang';
@@ -167,7 +167,7 @@ import { getAppPort } from '@/api/modules/app';
import { dateFormat } from '@/utils/date';
import { newUUID } from '@/utils/id';
import { MsgInfo, MsgSuccess } from '@/utils/message';
import BindDomain from '@/views/ai/model/ollama/domain/index.vue';
import BindDomain from '@/views/ai/ollama/domain/index.vue';
import { useGlobalStore } from '@/composables/useGlobalStore';
import { routerToNameWithQuery } from '@/utils/router';
@@ -157,7 +157,7 @@
</template>
<script lang="ts" setup>
import { expandInvalidFormSection } from '@/views/ai/model/components/form-section';
import { expandInvalidFormSection } from '@/components/form-section';
import { App } from '@/api/interface/app';
import { getAppByKey, getAppDetail, getAppInstalledByID } from '@/api/modules/app';
import { getAppStoreConfig } from '@/api/modules/setting';
@@ -476,5 +476,5 @@ defineExpose({
</script>
<style lang="scss" scoped>
@use '@/views/ai/model/components/form-section.scss';
@use '@/components/form-section/index.scss';
</style>
@@ -235,9 +235,9 @@
</DrawerPro>
</template>
<script lang="ts" setup>
import { expandInvalidFormSection } from '@/views/ai/model/components/form-section';
import AccountAddress from '@/views/ai/model/components/account-address/index.vue';
import GPUConfig from '@/views/ai/model/components/gpu-config/index.vue';
import { expandInvalidFormSection } from '@/components/form-section';
import AccountAddress from '@/views/ai/components/account-address/index.vue';
import GPUConfig from '@/views/ai/components/gpu-config/index.vue';
import Diff from '@/views/app-store/installed/upgrade/diff/index.vue';
import { App } from '@/api/interface/app';
@@ -583,7 +583,7 @@ onBeforeUnmount(() => {
</script>
<style lang="scss" scoped>
@use '@/views/ai/model/components/form-section.scss';
@use '@/components/form-section/index.scss';
.change-button {
margin-top: 5px;
}
@@ -1,90 +0,0 @@
<template>
<DialogPro
v-model="open"
:title="$t('commons.button.delete') + ' - ' + name"
size="small"
:show-close="!loading"
:close-on-press-escape="!loading"
>
<el-form v-loading="loading" @submit.prevent="submit">
<el-form-item>
<el-checkbox
v-model="withFile"
:label="$t('modelDownloadExtra.deleteFiles', [contentLabel])"
:disabled="loading || fileOnly"
/>
<span class="input-help break-all">
{{
$t('modelDownloadExtra.deleteFilesPath', [
contentLabel,
item && ('id' in item ? item.targetDir : item.path),
])
}}
</span>
</el-form-item>
<el-form-item>
<div class="text-xs break-all">
<span>{{ $t('commons.msg.operateConfirm') }}</span>
<span class="text-red-500 font-medium">{{ name }}</span>
</div>
<el-input v-model="deleteInfo" :placeholder="name" :disabled="loading" />
</el-form-item>
</el-form>
<template #footer>
<el-button :disabled="loading" @click="open = false">{{ $t('commons.button.cancel') }}</el-button>
<el-button type="primary" :loading="loading" :disabled="deleteInfo !== name || loading" @click="submit">
{{ $t('commons.button.confirm') }}
</el-button>
</template>
</DialogPro>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue';
import type { ModelDownloader } from '@/api/interface/model-downloader';
import { deleteLocalDownloadedModel, removeModelDownloadTask } from '@/api/modules/model-downloader';
import { MsgSuccess } from '@/utils/message';
import i18n from '@/lang';
const emit = defineEmits<{ search: [] }>();
const open = ref(false);
const loading = ref(false);
const withFile = ref(false);
const deleteInfo = ref('');
const item = ref<ModelDownloader.TaskItem | ModelDownloader.LocalModelItem>();
const name = computed(() => item.value?.repoID || (item.value && 'name' in item.value ? item.value.name : ''));
const contentLabel = computed(() =>
i18n.global.t(item.value?.downloadType === 'tokenizer' ? 'aiTools.benchmark.tokenizer' : 'aiTools.model.model'),
);
const fileOnly = computed(() => item.value?.downloadType === 'all' && !('id' in item.value));
const acceptParams = (row: ModelDownloader.TaskItem | ModelDownloader.LocalModelItem) => {
item.value = row;
withFile.value = fileOnly.value;
deleteInfo.value = '';
open.value = true;
};
const submit = async () => {
if (!item.value || deleteInfo.value !== name.value || loading.value) return;
loading.value = true;
try {
if ('id' in item.value) {
await removeModelDownloadTask({ id: item.value.id, withFile: withFile.value });
} else {
await deleteLocalDownloadedModel({
name: item.value.name,
downloadType: item.value.downloadType,
withFile: withFile.value,
});
}
open.value = false;
MsgSuccess(i18n.global.t('commons.msg.deleteSuccess'));
emit('search');
} finally {
loading.value = false;
}
};
defineExpose({ acceptParams });
</script>
@@ -1,107 +0,0 @@
<template>
<DialogPro
v-model="open"
:title="
$t(
form.downloadType === 'tokenizer'
? 'modelDownloadExtra.downloadTokenizer'
: 'modelDownloadExtra.downloadModel',
)
"
size="small"
:show-close="!loading"
:close-on-press-escape="!loading"
>
<el-form label-position="top" @submit.prevent="submit">
<el-form-item :label="$t('aiTools.model.modelName')">
<div class="download-repo">{{ downloadRepoID }}</div>
</el-form-item>
<el-form-item :label="$t('aiTools.model.modelSource')">
<SourceSelect v-model="form.source" :sources="sources" :disabled="loading" />
</el-form-item>
</el-form>
<template #footer>
<el-button :disabled="loading" @click="open = false">{{ $t('commons.button.cancel') }}</el-button>
<el-button type="primary" :loading="loading" :disabled="loading" @click="submit">
{{ $t('commons.button.confirm') }}
</el-button>
</template>
</DialogPro>
</template>
<script setup lang="ts">
import { computed, reactive, ref, watch } from 'vue';
import type { ModelDownloader } from '@/api/interface/model-downloader';
import { startHFModelDownload, startModelScopeModelDownload } from '@/api/modules/model-downloader';
import { useGlobalStore } from '@/composables/useGlobalStore';
import { MsgSuccess } from '@/utils/message';
import i18n from '@/lang';
import SourceSelect from '../source-select.vue';
const emit = defineEmits<{ search: [] }>();
const { currentNode } = useGlobalStore();
const open = ref(false);
const loading = ref(false);
const sources = ref<ModelDownloader.Source[]>([]);
const sourceModelIds = ref<ModelDownloader.FeaturedModel['sourceModelIds']>();
const form = reactive({
repoID: '',
downloadType: 'all' as ModelDownloader.DownloadType,
source: 'modelscope' as ModelDownloader.Source,
});
const downloadRepoID = computed(() => sourceModelIds.value?.[form.source]?.trim() || form.repoID);
const acceptParams = (
repoID: string,
downloadType: ModelDownloader.DownloadType,
availableSources: ModelDownloader.Source[],
modelIds?: ModelDownloader.FeaturedModel['sourceModelIds'],
) => {
if (!repoID.trim() || !availableSources.length || loading.value) return;
sources.value = availableSources;
sourceModelIds.value = modelIds;
Object.assign(form, {
repoID: repoID.trim(),
downloadType,
source: availableSources[0],
});
open.value = true;
};
const submit = async () => {
if (!open.value || loading.value) return;
const node = currentNode.value;
loading.value = true;
try {
const req = {
repoID: downloadRepoID.value,
downloadType: form.downloadType,
};
if (form.source === 'modelscope') await startModelScopeModelDownload(req);
else await startHFModelDownload(req);
if (node !== currentNode.value) return;
open.value = false;
MsgSuccess(i18n.global.t('aiTools.model.downloadQueued'));
emit('search');
} finally {
loading.value = false;
}
};
watch(currentNode, () => (open.value = false), { flush: 'sync' });
defineExpose({ acceptParams });
</script>
<style scoped lang="scss">
.download-repo {
width: 100%;
padding: 12px;
border-radius: var(--el-border-radius-base);
background: var(--el-fill-color-light);
color: var(--el-text-color-primary);
font-weight: 500;
line-height: 1.6;
overflow-wrap: anywhere;
}
</style>
File diff suppressed because it is too large Load Diff
@@ -1,105 +0,0 @@
<template>
<div class="source-options" role="group" :aria-label="$t('aiTools.model.modelSource')">
<el-button
v-for="source in sources"
:key="source"
class="source-option"
:class="{ 'is-selected': modelValue === source }"
:aria-pressed="modelValue === source"
:disabled="disabled"
@click="modelValue = source"
>
<svg-icon :iconName="source === 'modelscope' ? 'p-modelscope' : 'p-huggingface1'" class="source-icon" />
<span class="source-content">
<span class="source-heading">
<span class="source-name">{{ source === 'modelscope' ? 'ModelScope' : 'Hugging Face' }}</span>
<el-tag v-if="showDescription && source === 'modelscope'" size="small" effect="plain">
{{ $t('home.recommend') }}
</el-tag>
</span>
<span v-if="showDescription" class="source-description">
{{
$t(
'aiTools.model.' +
(source === 'modelscope' ? 'modelScopeSearchHelper' : 'huggingFaceSearchHelper'),
)
}}
</span>
</span>
</el-button>
</div>
</template>
<script setup lang="ts">
import type { ModelDownloader } from '@/api/interface/model-downloader';
defineProps<{ sources: ModelDownloader.Source[]; disabled?: boolean; showDescription?: boolean }>();
const modelValue = defineModel<ModelDownloader.Source>({ required: true });
</script>
<style scoped lang="scss">
.source-options {
display: grid;
grid-auto-flow: column;
grid-auto-columns: minmax(0, 1fr);
gap: 6px;
width: 100%;
}
.source-option {
min-width: 0;
height: auto;
margin: 0;
padding: 10px 8px;
border-radius: 6px;
text-align: left;
white-space: normal;
:deep(> span) {
width: 100%;
display: flex;
align-items: center;
gap: 6px;
min-width: 0;
}
&.is-selected {
border-color: var(--el-color-primary);
background: var(--el-color-primary-light-9);
}
}
.source-icon {
width: 18px;
height: 18px;
padding: 0;
flex-shrink: 0;
}
.source-name {
font-size: 12px;
font-weight: 600;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.source-content {
min-width: 0;
}
.source-heading {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 8px;
}
.source-description {
display: block;
margin-top: 6px;
color: var(--el-text-color-secondary);
font-size: 12px;
line-height: 1.5;
}
</style>