diff --git a/agent/app/api/v2/ai.go b/agent/app/api/v2/ai.go new file mode 100644 index 000000000..3addb056a --- /dev/null +++ b/agent/app/api/v2/ai.go @@ -0,0 +1,256 @@ +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/utils/ai_tools/gpu" + "github.com/1Panel-dev/1Panel/agent/utils/ai_tools/gpu/common" + "github.com/1Panel-dev/1Panel/agent/utils/ai_tools/xpu" + "github.com/gin-gonic/gin" +) + +// @Tags AI +// @Summary Create Ollama model +// @Accept json +// @Param request body dto.OllamaModelName true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /ai/ollama/model [post] +// @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"添加 Ollama 模型 [name]","formatEN":"add Ollama model [name]"} +func (b *BaseApi) CreateOllamaModel(c *gin.Context) { + var req dto.OllamaModelName + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := aiToolService.Create(req.Name); err != nil { + helper.BadRequest(c, err) + return + } + helper.SuccessWithData(c, nil) +} + +// @Tags AI +// @Summary Rereate Ollama model +// @Accept json +// @Param request body dto.OllamaModelName true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /ai/ollama/model/recreate [post] +// @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"添加 Ollama 模型重试 [name]","formatEN":"re-add Ollama model [name]"} +func (b *BaseApi) RecreateOllamaModel(c *gin.Context) { + var req dto.OllamaModelName + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := aiToolService.Recreate(req.Name); err != nil { + helper.BadRequest(c, err) + return + } + helper.SuccessWithData(c, nil) +} + +// @Tags AI +// @Summary Close Ollama model conn +// @Accept json +// @Param request body dto.OllamaModelName true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /ai/ollama/model/close [post] +// @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"关闭 Ollama 模型连接 [name]","formatEN":"close conn for Ollama model [name]"} +func (b *BaseApi) CloseOllamaModel(c *gin.Context) { + var req dto.OllamaModelName + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := aiToolService.Close(req.Name); err != nil { + helper.BadRequest(c, err) + return + } + helper.SuccessWithData(c, nil) +} + +// @Tags AI +// @Summary Sync Ollama model list +// @Success 200 {array} dto.OllamaModelDropList +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /ai/ollama/model/sync [post] +// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"同步 Ollama 模型列表","formatEN":"sync Ollama model list"} +func (b *BaseApi) SyncOllamaModel(c *gin.Context) { + list, err := aiToolService.Sync() + if err != nil { + helper.BadRequest(c, err) + return + } + helper.SuccessWithData(c, list) +} + +// @Tags AI +// @Summary Page Ollama models +// @Accept json +// @Param request body dto.SearchWithPage true "request" +// @Success 200 {object} dto.PageResult +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /ai/ollama/model/search [post] +func (b *BaseApi) SearchOllamaModel(c *gin.Context) { + var req dto.SearchWithPage + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + total, list, err := aiToolService.Search(req) + if err != nil { + helper.BadRequest(c, err) + return + } + + helper.SuccessWithData(c, dto.PageResult{ + Items: list, + Total: total, + }) +} + +// @Tags AI +// @Summary Page Ollama models +// @Accept json +// @Param request body dto.OllamaModelName true "request" +// @Success 200 {string} details +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /ai/ollama/model/load [post] +func (b *BaseApi) LoadOllamaModelDetail(c *gin.Context) { + var req dto.OllamaModelName + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + detail, err := aiToolService.LoadDetail(req.Name) + if err != nil { + helper.BadRequest(c, err) + return + } + + helper.SuccessWithData(c, detail) +} + +// @Tags AI +// @Summary Delete Ollama model +// @Accept json +// @Param request body dto.ForceDelete true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /ai/ollama/model/del [post] +// @x-panel-log {"bodyKeys":["ids"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"ids","isList":true,"db":"ollama_models","output_column":"name","output_value":"names"}],"formatZH":"删除 Ollama 模型 [names]","formatEN":"remove Ollama model [names]"} +func (b *BaseApi) DeleteOllamaModel(c *gin.Context) { + var req dto.ForceDelete + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := aiToolService.Delete(req); err != nil { + helper.BadRequest(c, err) + return + } + + helper.SuccessWithOutData(c) +} + +// @Tags AI +// @Summary Load gpu / xpu info +// @Accept json +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /ai/gpu/load [get] +func (b *BaseApi) LoadGpuInfo(c *gin.Context) { + ok, client := gpu.New() + if ok { + info, err := client.LoadGpuInfo() + if err != nil { + helper.BadRequest(c, err) + return + } + helper.SuccessWithData(c, info) + return + } + xpuOK, xpuClient := xpu.New() + if xpuOK { + info, err := xpuClient.LoadGpuInfo() + if err != nil { + helper.BadRequest(c, err) + return + } + helper.SuccessWithData(c, info) + return + } + helper.SuccessWithData(c, &common.GpuInfo{}) +} + +// @Tags AI +// @Summary Bind domain +// @Accept json +// @Param request body dto.OllamaBindDomain true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /ai/domain/bind [post] +func (b *BaseApi) BindDomain(c *gin.Context) { + var req dto.OllamaBindDomain + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if err := aiToolService.BindDomain(req); err != nil { + helper.BadRequest(c, err) + return + } + helper.SuccessWithOutData(c) +} + +// @Tags AI +// @Summary Get bind domain +// @Accept json +// @Param request body dto.OllamaBindDomainReq true "request" +// @Success 200 {object} dto.OllamaBindDomainRes +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /ai/domain/get [post] +func (b *BaseApi) GetBindDomain(c *gin.Context) { + var req dto.OllamaBindDomainReq + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + res, err := aiToolService.GetBindDomain(req) + if err != nil { + helper.BadRequest(c, err) + return + } + helper.SuccessWithData(c, res) +} + +// Tags AI +// Summary Update bind domain +// Accept json +// Param request body dto.OllamaBindDomain true "request" +// Success 200 +// Security ApiKeyAuth +// Security Timestamp +// Router /ai/domain/update [post] +func (b *BaseApi) UpdateBindDomain(c *gin.Context) { + var req dto.OllamaBindDomain + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if err := aiToolService.UpdateBindDomain(req); err != nil { + helper.BadRequest(c, err) + return + } + helper.SuccessWithOutData(c) +} diff --git a/agent/app/api/v2/entry.go b/agent/app/api/v2/entry.go index d622c8972..324c3100f 100644 --- a/agent/app/api/v2/entry.go +++ b/agent/app/api/v2/entry.go @@ -16,6 +16,8 @@ var ( appService = service.NewIAppService() appInstallService = service.NewIAppInstalledService() + aiToolService = service.NewIAIToolService() + containerService = service.NewIContainerService() composeTemplateService = service.NewIComposeTemplateService() imageRepoService = service.NewIImageRepoService() diff --git a/agent/app/api/v2/terminal.go b/agent/app/api/v2/terminal.go index 59b8149e0..360861dfe 100644 --- a/agent/app/api/v2/terminal.go +++ b/agent/app/api/v2/terminal.go @@ -10,6 +10,7 @@ import ( "time" "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/app/service" "github.com/1Panel-dev/1Panel/agent/global" "github.com/1Panel-dev/1Panel/agent/utils/cmd" "github.com/1Panel-dev/1Panel/agent/utils/terminal" @@ -165,6 +166,70 @@ func (b *BaseApi) ContainerWsSsh(c *gin.Context) { } } +func (b *BaseApi) OllamaWsSsh(c *gin.Context) { + wsConn, err := upGrader.Upgrade(c.Writer, c.Request, nil) + if err != nil { + global.LOG.Errorf("gin context http handler failed, err: %v", err) + return + } + defer wsConn.Close() + + if global.CONF.Base.IsDemo { + if wshandleError(wsConn, errors.New(" demo server, prohibit this operation!")) { + return + } + } + + cols, err := strconv.Atoi(c.DefaultQuery("cols", "80")) + if wshandleError(wsConn, errors.WithMessage(err, "invalid param cols in request")) { + return + } + rows, err := strconv.Atoi(c.DefaultQuery("rows", "40")) + if wshandleError(wsConn, errors.WithMessage(err, "invalid param rows in request")) { + return + } + name := c.Query("name") + if cmd.CheckIllegal(name) { + if wshandleError(wsConn, errors.New(" The command contains illegal characters.")) { + return + } + } + container, err := service.LoadContainerName() + if wshandleError(wsConn, errors.WithMessage(err, " load container name for ollama failed")) { + return + } + commands := []string{"ollama", "run", name} + + pidMap := loadMapFromDockerTop(container) + fmt.Println("pidMap") + for k, v := range pidMap { + fmt.Println(k, v) + } + itemCmds := append([]string{"exec", "-it", container}, commands...) + slave, err := terminal.NewCommand(itemCmds) + if wshandleError(wsConn, err) { + return + } + defer killBash(container, strings.Join(commands, " "), pidMap) + defer slave.Close() + + tty, err := terminal.NewLocalWsSession(cols, rows, wsConn, slave, false) + if wshandleError(wsConn, err) { + return + } + + quitChan := make(chan bool, 3) + tty.Start(quitChan) + go slave.Wait(quitChan) + + <-quitChan + + global.LOG.Info("websocket finished") + if wshandleError(wsConn, err) { + return + } +} + func wshandleError(ws *websocket.Conn, err error) bool { if err != nil { global.LOG.Errorf("handler ws faled:, err: %v", err) diff --git a/agent/app/dto/ai.go b/agent/app/dto/ai.go new file mode 100644 index 000000000..6c2065cae --- /dev/null +++ b/agent/app/dto/ai.go @@ -0,0 +1,44 @@ +package dto + +import "time" + +type OllamaModelInfo struct { + ID uint `json:"id"` + Name string `json:"name"` + Size string `json:"size"` + From string `json:"from"` + LogFileExist bool `json:"logFileExist"` + + Status string `json:"status"` + Message string `json:"message"` + CreatedAt time.Time `json:"createdAt"` +} + +type OllamaModelDropList struct { + ID uint `json:"id"` + Name string `json:"name"` +} + +type OllamaModelName struct { + Name string `json:"name"` +} + +type OllamaBindDomain struct { + Domain string `json:"domain" validate:"required"` + AppInstallID uint `json:"appInstallID" validate:"required"` + SSLID uint `json:"sslID"` + WebsiteID uint `json:"websiteID"` + IPList string `json:"ipList"` +} + +type OllamaBindDomainReq struct { + AppInstallID uint `json:"appInstallID" validate:"required"` +} + +type OllamaBindDomainRes struct { + Domain string `json:"domain"` + SSLID uint `json:"sslID"` + AllowIPs []string `json:"allowIPs"` + WebsiteID uint `json:"websiteID"` + ConnUrl string `json:"connUrl"` +} diff --git a/agent/app/dto/common_req.go b/agent/app/dto/common_req.go index 0315f1091..89f419400 100644 --- a/agent/app/dto/common_req.go +++ b/agent/app/dto/common_req.go @@ -71,3 +71,8 @@ type UpdateGroup struct { type OperateWithTask struct { TaskID string `json:"taskID"` } + +type ForceDelete struct { + IDs []uint `json:"ids"` + ForceDelete bool `json:"forceDelete"` +} diff --git a/agent/app/model/ai.go b/agent/app/model/ai.go new file mode 100644 index 000000000..2165e96e8 --- /dev/null +++ b/agent/app/model/ai.go @@ -0,0 +1,11 @@ +package model + +type OllamaModel struct { + BaseModel + + Name string `json:"name"` + Size string `json:"size"` + From string `json:"from"` + Status string `json:"status"` + Message string `json:"message"` +} diff --git a/agent/app/repo/ai.go b/agent/app/repo/ai.go new file mode 100644 index 000000000..f0368f896 --- /dev/null +++ b/agent/app/repo/ai.go @@ -0,0 +1,69 @@ +package repo + +import ( + "github.com/1Panel-dev/1Panel/agent/app/model" + "github.com/1Panel-dev/1Panel/agent/global" +) + +type AiRepo struct{} + +type IAiRepo interface { + Get(opts ...DBOption) (model.OllamaModel, error) + List(opts ...DBOption) ([]model.OllamaModel, error) + Page(limit, offset int, opts ...DBOption) (int64, []model.OllamaModel, error) + Create(cronjob *model.OllamaModel) error + Update(id uint, vars map[string]interface{}) error + Delete(opts ...DBOption) error +} + +func NewIAiRepo() IAiRepo { + return &AiRepo{} +} + +func (u *AiRepo) Get(opts ...DBOption) (model.OllamaModel, error) { + var item model.OllamaModel + db := global.DB + for _, opt := range opts { + db = opt(db) + } + err := db.First(&item).Error + return item, err +} + +func (u *AiRepo) List(opts ...DBOption) ([]model.OllamaModel, error) { + var list []model.OllamaModel + db := global.DB.Model(&model.OllamaModel{}) + for _, opt := range opts { + db = opt(db) + } + err := db.Find(&list).Error + return list, err +} + +func (u *AiRepo) Page(page, size int, opts ...DBOption) (int64, []model.OllamaModel, error) { + var list []model.OllamaModel + db := global.DB.Model(&model.OllamaModel{}) + for _, opt := range opts { + db = opt(db) + } + count := int64(0) + db = db.Count(&count) + err := db.Limit(size).Offset(size * (page - 1)).Find(&list).Error + return count, list, err +} + +func (u *AiRepo) Create(item *model.OllamaModel) error { + return global.DB.Create(item).Error +} + +func (u *AiRepo) Update(id uint, vars map[string]interface{}) error { + return global.DB.Model(&model.OllamaModel{}).Where("id = ?", id).Updates(vars).Error +} + +func (u *AiRepo) Delete(opts ...DBOption) error { + db := global.DB + for _, opt := range opts { + db = opt(db) + } + return db.Delete(&model.OllamaModel{}).Error +} diff --git a/agent/app/service/ai.go b/agent/app/service/ai.go new file mode 100644 index 000000000..f00b1d151 --- /dev/null +++ b/agent/app/service/ai.go @@ -0,0 +1,393 @@ +package service + +import ( + "context" + "fmt" + "io" + "os" + "os/exec" + "path" + "strings" + + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/app/dto/request" + "github.com/1Panel-dev/1Panel/agent/app/model" + "github.com/1Panel-dev/1Panel/agent/app/repo" + "github.com/1Panel-dev/1Panel/agent/buserr" + "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/utils/cmd" + "github.com/1Panel-dev/1Panel/agent/utils/common" + "github.com/jinzhu/copier" +) + +type AIToolService struct{} + +type IAIToolService interface { + Search(search dto.SearchWithPage) (int64, []dto.OllamaModelInfo, error) + Create(name string) error + Close(name string) error + Recreate(name string) error + Delete(req dto.ForceDelete) error + Sync() ([]dto.OllamaModelDropList, error) + LoadDetail(name string) (string, error) + BindDomain(req dto.OllamaBindDomain) error + GetBindDomain(req dto.OllamaBindDomainReq) (*dto.OllamaBindDomainRes, error) + UpdateBindDomain(req dto.OllamaBindDomain) error +} + +func NewIAIToolService() IAIToolService { + return &AIToolService{} +} + +func (u *AIToolService) Search(req dto.SearchWithPage) (int64, []dto.OllamaModelInfo, error) { + var options []repo.DBOption + if len(req.Info) != 0 { + options = append(options, repo.WithByLikeName(req.Info)) + } + total, list, err := aiRepo.Page(req.Page, req.PageSize, options...) + if err != nil { + return 0, nil, err + } + var dtoLists []dto.OllamaModelInfo + for _, itemModel := range list { + var item dto.OllamaModelInfo + if err := copier.Copy(&item, &itemModel); err != nil { + return 0, nil, buserr.WithDetail("ErrStructTransform", err.Error(), nil) + } + logPath := path.Join(global.Dir.DataDir, "log", "AITools", itemModel.Name) + if _, err := os.Stat(logPath); err == nil { + item.LogFileExist = true + } + dtoLists = append(dtoLists, item) + } + return int64(total), dtoLists, err +} + +func (u *AIToolService) LoadDetail(name string) (string, error) { + if cmd.CheckIllegal(name) { + return "", buserr.New("ErrCmdIllegal") + } + containerName, err := LoadContainerName() + if err != nil { + return "", err + } + stdout, err := cmd.Execf("docker exec %s ollama show %s", containerName, name) + if err != nil { + return "", err + } + return stdout, err +} + +func (u *AIToolService) Create(name string) error { + if cmd.CheckIllegal(name) { + return buserr.New("ErrCmdIllegal") + } + modelInfo, _ := aiRepo.Get(repo.WithByName(name)) + if modelInfo.ID != 0 { + return buserr.New("ErrRecordExist") + } + containerName, err := LoadContainerName() + if err != nil { + return err + } + logItem := path.Join(global.Dir.DataDir, "log", "AITools", name) + if _, err := os.Stat(path.Dir(logItem)); err != nil && os.IsNotExist(err) { + if err = os.MkdirAll(path.Dir(logItem), os.ModePerm); err != nil { + return err + } + } + info := model.OllamaModel{ + Name: name, + From: "local", + Status: constant.StatusWaiting, + } + if err := aiRepo.Create(&info); err != nil { + return err + } + file, err := os.OpenFile(logItem, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666) + if err != nil { + return err + } + go pullOllamaModel(file, containerName, info) + return nil +} + +func (u *AIToolService) Close(name string) error { + if cmd.CheckIllegal(name) { + return buserr.New("ErrCmdIllegal") + } + containerName, err := LoadContainerName() + if err != nil { + return err + } + stdout, err := cmd.Execf("docker exec %s ollama stop %s", containerName, name) + if err != nil { + return fmt.Errorf("handle ollama stop %s failed, stdout: %s, err: %v", name, stdout, err) + } + return nil +} + +func (u *AIToolService) Recreate(name string) error { + if cmd.CheckIllegal(name) { + return buserr.New("ErrCmdIllegal") + } + modelInfo, _ := aiRepo.Get(repo.WithByName(name)) + if modelInfo.ID == 0 { + return buserr.New("ErrRecordNotFound") + } + containerName, err := LoadContainerName() + if err != nil { + return err + } + if err := aiRepo.Update(modelInfo.ID, map[string]interface{}{"status": constant.StatusWaiting, "from": "local"}); err != nil { + return err + } + logItem := path.Join(global.Dir.DataDir, "log", "AITools", name) + if _, err := os.Stat(path.Dir(logItem)); err != nil && os.IsNotExist(err) { + if err = os.MkdirAll(path.Dir(logItem), os.ModePerm); err != nil { + return err + } + } + file, err := os.OpenFile(logItem, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666) + if err != nil { + return err + } + go pullOllamaModel(file, containerName, modelInfo) + return nil +} + +func (u *AIToolService) Delete(req dto.ForceDelete) error { + ollamaList, _ := aiRepo.List(repo.WithByIDs(req.IDs)) + if len(ollamaList) == 0 { + return buserr.New("ErrRecordNotFound") + } + containerName, err := LoadContainerName() + if err != nil && !req.ForceDelete { + return err + } + for _, item := range ollamaList { + if item.Status != constant.StatusDeleted { + stdout, err := cmd.Execf("docker exec %s ollama rm %s", containerName, item.Name) + if err != nil && !req.ForceDelete { + return fmt.Errorf("handle ollama rm %s failed, stdout: %s, err: %v", item.Name, stdout, err) + } + } + _ = aiRepo.Delete(repo.WithByID(item.ID)) + logItem := path.Join(global.Dir.DataDir, "log", "AITools", item.Name) + _ = os.Remove(logItem) + } + return nil +} + +func (u *AIToolService) Sync() ([]dto.OllamaModelDropList, error) { + containerName, err := LoadContainerName() + if err != nil { + return nil, err + } + stdout, err := cmd.Execf("docker exec %s ollama list", containerName) + if err != nil { + return nil, err + } + var list []model.OllamaModel + lines := strings.Split(stdout, "\n") + for _, line := range lines { + parts := strings.Fields(line) + if len(parts) < 5 { + continue + } + if parts[0] == "NAME" { + continue + } + list = append(list, model.OllamaModel{Name: parts[0], Size: parts[2] + " " + parts[3]}) + } + listInDB, _ := aiRepo.List() + var dropList []dto.OllamaModelDropList + for _, itemModel := range listInDB { + isExit := false + for i := 0; i < len(list); i++ { + if list[i].Name == itemModel.Name { + _ = aiRepo.Update(itemModel.ID, map[string]interface{}{"status": constant.StatusSuccess, "message": "", "size": list[i].Size}) + list = append(list[:i], list[(i+1):]...) + isExit = true + break + } + } + if !isExit && itemModel.Status != constant.StatusWaiting { + _ = aiRepo.Update(itemModel.ID, map[string]interface{}{"status": constant.StatusDeleted, "message": "not exist", "size": ""}) + dropList = append(dropList, dto.OllamaModelDropList{ID: itemModel.ID, Name: itemModel.Name}) + continue + } + } + for _, item := range list { + item.Status = constant.StatusSuccess + item.From = "remote" + _ = aiRepo.Create(&item) + } + + return dropList, nil +} + +func (u *AIToolService) BindDomain(req dto.OllamaBindDomain) error { + nginxInstall, _ := getAppInstallByKey(constant.AppOpenresty) + if nginxInstall.ID == 0 { + return buserr.New("ErrOpenrestyInstall") + } + var ( + ipList []string + err error + ) + if len(req.IPList) > 0 { + ipList, err = common.HandleIPList(req.IPList) + if err != nil { + return err + } + } + createWebsiteReq := request.WebsiteCreate{ + Domains: []request.WebsiteDomain{{Domain: req.Domain}}, + Alias: strings.ToLower(req.Domain), + Type: constant.Deployment, + AppType: constant.InstalledApp, + AppInstallID: req.AppInstallID, + } + websiteService := NewIWebsiteService() + if err := websiteService.CreateWebsite(createWebsiteReq); err != nil { + return err + } + website, err := websiteRepo.GetFirst(websiteRepo.WithAlias(strings.ToLower(req.Domain))) + if err != nil { + return err + } + if len(ipList) > 0 { + if err = ConfigAllowIPs(ipList, website); err != nil { + return err + } + } + if req.SSLID > 0 { + sslReq := request.WebsiteHTTPSOp{ + WebsiteID: website.ID, + Enable: true, + Type: "existed", + WebsiteSSLID: req.SSLID, + HttpConfig: "HTTPSOnly", + } + if _, err = websiteService.OpWebsiteHTTPS(context.Background(), sslReq); err != nil { + return err + } + } + if err = ConfigAIProxy(website); err != nil { + return err + } + return nil +} + +func (u *AIToolService) GetBindDomain(req dto.OllamaBindDomainReq) (*dto.OllamaBindDomainRes, error) { + install, err := appInstallRepo.GetFirst(repo.WithByID(req.AppInstallID)) + if err != nil { + return nil, err + } + res := &dto.OllamaBindDomainRes{} + website, _ := websiteRepo.GetFirst(websiteRepo.WithAppInstallId(install.ID)) + if website.ID == 0 { + return res, nil + } + res.WebsiteID = website.ID + res.Domain = website.PrimaryDomain + if website.WebsiteSSLID > 0 { + res.SSLID = website.WebsiteSSLID + } + res.ConnUrl = fmt.Sprintf("%s://%s", strings.ToLower(website.Protocol), website.PrimaryDomain) + res.AllowIPs = GetAllowIps(website) + return res, nil +} + +func (u *AIToolService) UpdateBindDomain(req dto.OllamaBindDomain) error { + nginxInstall, _ := getAppInstallByKey(constant.AppOpenresty) + if nginxInstall.ID == 0 { + return buserr.New("ErrOpenrestyInstall") + } + var ( + ipList []string + err error + ) + if len(req.IPList) > 0 { + ipList, err = common.HandleIPList(req.IPList) + if err != nil { + return err + } + } + websiteService := NewIWebsiteService() + website, err := websiteRepo.GetFirst(repo.WithByID(req.WebsiteID)) + if err != nil { + return err + } + if err = ConfigAllowIPs(ipList, website); err != nil { + return err + } + if req.SSLID > 0 { + sslReq := request.WebsiteHTTPSOp{ + WebsiteID: website.ID, + Enable: true, + Type: "existed", + WebsiteSSLID: req.SSLID, + HttpConfig: "HTTPSOnly", + } + if _, err = websiteService.OpWebsiteHTTPS(context.Background(), sslReq); err != nil { + return err + } + return nil + } + if website.WebsiteSSLID > 0 && req.SSLID == 0 { + sslReq := request.WebsiteHTTPSOp{ + WebsiteID: website.ID, + Enable: false, + } + if _, err = websiteService.OpWebsiteHTTPS(context.Background(), sslReq); err != nil { + return err + } + } + return nil +} + +func LoadContainerName() (string, error) { + ollamaBaseInfo, err := appInstallRepo.LoadBaseInfo("ollama", "") + if err != nil { + return "", fmt.Errorf("ollama service is not found, err: %v", err) + } + if ollamaBaseInfo.Status != constant.Running { + return "", fmt.Errorf("container %s of ollama is not running, please check and retry!", ollamaBaseInfo.ContainerName) + } + return ollamaBaseInfo.ContainerName, nil +} + +func pullOllamaModel(file *os.File, containerName string, info model.OllamaModel) { + defer file.Close() + cmd := exec.Command("docker", "exec", containerName, "ollama", "pull", info.Name) + multiWriter := io.MultiWriter(os.Stdout, file) + cmd.Stdout = multiWriter + cmd.Stderr = multiWriter + _ = cmd.Run() + itemSize, err := loadModelSize(info.Name, containerName) + if len(itemSize) != 0 { + _ = aiRepo.Update(info.ID, map[string]interface{}{"status": constant.StatusSuccess, "size": itemSize}) + } else { + _ = aiRepo.Update(info.ID, map[string]interface{}{"status": constant.StatusFailed, "message": err.Error()}) + } + _, _ = file.WriteString("ollama pull completed!") +} + +func loadModelSize(name string, containerName string) (string, error) { + stdout, err := cmd.Execf("docker exec %s ollama list | grep %s", containerName, name) + if err != nil { + return "", err + } + lines := strings.Split(string(stdout), "\n") + for _, line := range lines { + parts := strings.Fields(line) + if len(parts) < 5 { + continue + } + return parts[2] + " " + parts[3], nil + } + return "", fmt.Errorf("no such model %s in ollama list, std: %s", name, string(stdout)) +} diff --git a/agent/app/service/dashboard.go b/agent/app/service/dashboard.go index 4a8efc361..c3ea58a4b 100644 --- a/agent/app/service/dashboard.go +++ b/agent/app/service/dashboard.go @@ -17,10 +17,11 @@ import ( "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/ai_tools/gpu" + "github.com/1Panel-dev/1Panel/agent/utils/ai_tools/xpu" "github.com/1Panel-dev/1Panel/agent/utils/cmd" "github.com/1Panel-dev/1Panel/agent/utils/common" "github.com/1Panel-dev/1Panel/agent/utils/copier" - "github.com/1Panel-dev/1Panel/agent/utils/xpack" "github.com/shirou/gopsutil/v3/cpu" "github.com/shirou/gopsutil/v3/disk" "github.com/shirou/gopsutil/v3/host" @@ -492,7 +493,17 @@ func loadDiskInfo() []dto.DiskInfo { } func loadGPUInfo() []dto.GPUInfo { - list := xpack.LoadGpuInfo() + ok, client := gpu.New() + var list []interface{} + if ok { + info, err := client.LoadGpuInfo() + if err != nil || len(info.GPUs) == 0 { + return nil + } + for _, item := range info.GPUs { + list = append(list, item) + } + } if len(list) == 0 { return nil } @@ -542,7 +553,17 @@ func ArryContains(arr []string, element string) bool { } func loadXpuInfo() []dto.XPUInfo { - list := xpack.LoadXpuInfo() + var list []interface{} + ok, xpuClient := xpu.New() + if ok { + xpus, err := xpuClient.LoadDashData() + if err != nil || len(xpus) == 0 { + return nil + } + for _, item := range xpus { + list = append(list, item) + } + } if len(list) == 0 { return nil } diff --git a/agent/app/service/entry.go b/agent/app/service/entry.go index 8cb7d0a0e..2d2f6210b 100644 --- a/agent/app/service/entry.go +++ b/agent/app/service/entry.go @@ -11,6 +11,8 @@ var ( launcherRepo = repo.NewILauncherRepo() appInstallResourceRepo = repo.NewIAppInstallResourceRpo() + aiRepo = repo.NewIAiRepo() + mysqlRepo = repo.NewIMysqlRepo() postgresqlRepo = repo.NewIPostgresqlRepo() databaseRepo = repo.NewIDatabaseRepo() diff --git a/agent/app/service/website_utils.go b/agent/app/service/website_utils.go index cd1c60884..648bdc398 100644 --- a/agent/app/service/website_utils.go +++ b/agent/app/service/website_utils.go @@ -1231,3 +1231,55 @@ func openProxyCache(website model.Website) error { proxyCachePath := fmt.Sprintf("/www/sites/%s/cache levels=1:2 keys_zone=proxy_cache_zone_of_%s:5m max_size=1g inactive=24h", website.Alias, website.Alias) return updateNginxConfig("", []dto.NginxParam{{Name: "proxy_cache_path", Params: []string{proxyCachePath}}}, &website) } + +func ConfigAllowIPs(ips []string, website model.Website) error { + nginxFull, err := getNginxFull(&website) + if err != nil { + return err + } + nginxConfig := nginxFull.SiteConfig + config := nginxFull.SiteConfig.Config + server := config.FindServers()[0] + server.RemoveDirective("allow", nil) + server.RemoveDirective("deny", nil) + if len(ips) > 0 { + server.UpdateAllowIPs(ips) + } + if err := nginx.WriteConfig(config, nginx.IndentedStyle); err != nil { + return err + } + return nginxCheckAndReload(nginxConfig.OldContent, config.FilePath, nginxFull.Install.ContainerName) +} + +func GetAllowIps(website model.Website) []string { + nginxFull, err := getNginxFull(&website) + if err != nil { + return nil + } + config := nginxFull.SiteConfig.Config + server := config.FindServers()[0] + dirs := server.GetDirectives() + var ips []string + for _, dir := range dirs { + if dir.GetName() == "allow" { + ips = append(ips, dir.GetParameters()...) + } + } + return ips +} + +func ConfigAIProxy(website model.Website) error { + nginxFull, err := getNginxFull(&website) + if err != nil { + return nil + } + config := nginxFull.SiteConfig.Config + server := config.FindServers()[0] + dirs := server.GetDirectives() + for _, dir := range dirs { + if dir.GetName() == "location" && dir.GetParameters()[0] == "/" { + server.UpdateRootProxyForAi([]string{fmt.Sprintf("http://%s", website.Proxy)}) + } + } + return nil +} diff --git a/agent/init/migration/migrate.go b/agent/init/migration/migrate.go index f5021fbba..142da1dbf 100644 --- a/agent/init/migration/migrate.go +++ b/agent/init/migration/migrate.go @@ -24,6 +24,7 @@ func InitAgentDB() { migrations.InitBackup, migrations.UpdateAppTag, migrations.UpdateApp, + migrations.AddOllamaModel, }) if err := m.Migrate(); err != nil { global.LOG.Error(err) diff --git a/agent/init/migration/migrations/init.go b/agent/init/migration/migrations/init.go index 8ca7c17cd..553743913 100644 --- a/agent/init/migration/migrations/init.go +++ b/agent/init/migration/migrations/init.go @@ -265,3 +265,13 @@ var UpdateApp = &gormigrate.Migration{ return nil }, } + +var AddOllamaModel = &gormigrate.Migration{ + ID: "20250218-add-ollama-model", + Migrate: func(tx *gorm.DB) error { + if err := tx.AutoMigrate(&model.OllamaModel{}); err != nil { + return err + } + return nil + }, +} diff --git a/agent/router/ai.go b/agent/router/ai.go new file mode 100644 index 000000000..2ade7abc0 --- /dev/null +++ b/agent/router/ai.go @@ -0,0 +1,29 @@ +package router + +import ( + v1 "github.com/1Panel-dev/1Panel/agent/app/api/v2" + "github.com/gin-gonic/gin" +) + +type AIToolsRouter struct { +} + +func (a *AIToolsRouter) InitRouter(Router *gin.RouterGroup) { + aiToolsRouter := Router.Group("ai") + + baseApi := v1.ApiGroupApp.BaseApi + { + aiToolsRouter.GET("/ollama/exec", baseApi.OllamaWsSsh) + aiToolsRouter.POST("/ollama/close", baseApi.CloseOllamaModel) + aiToolsRouter.POST("/ollama/model", baseApi.CreateOllamaModel) + aiToolsRouter.POST("/ollama/model/recreate", baseApi.RecreateOllamaModel) + aiToolsRouter.POST("/ollama/model/search", baseApi.SearchOllamaModel) + aiToolsRouter.POST("/ollama/model/sync", baseApi.SyncOllamaModel) + aiToolsRouter.POST("/ollama/model/load", baseApi.LoadOllamaModelDetail) + aiToolsRouter.POST("/ollama/model/del", baseApi.DeleteOllamaModel) + aiToolsRouter.GET("/gpu/load", baseApi.LoadGpuInfo) + aiToolsRouter.POST("/domain/bind", baseApi.BindDomain) + aiToolsRouter.POST("/domain/get", baseApi.GetBindDomain) + aiToolsRouter.POST("/domain/update", baseApi.UpdateBindDomain) + } +} diff --git a/agent/router/common.go b/agent/router/common.go index f54a07ddc..ace1b56d9 100644 --- a/agent/router/common.go +++ b/agent/router/common.go @@ -21,5 +21,6 @@ func commonGroups() []CommonRouter { &RuntimeRouter{}, &ProcessRouter{}, &WebsiteCARouter{}, + &AIToolsRouter{}, } } diff --git a/agent/utils/ai_tools/gpu/common/gpu_info.go b/agent/utils/ai_tools/gpu/common/gpu_info.go new file mode 100644 index 000000000..3d827eb40 --- /dev/null +++ b/agent/utils/ai_tools/gpu/common/gpu_info.go @@ -0,0 +1,37 @@ +package common + +type GpuInfo struct { + CudaVersion string `json:"cudaVersion"` + DriverVersion string `json:"driverVersion"` + Type string `json:"type"` + + GPUs []GPU `json:"gpu"` +} + +type GPU struct { + Index uint `json:"index"` + ProductName string `json:"productName"` + PersistenceMode string `json:"persistenceMode"` + BusID string `json:"busID"` + DisplayActive string `json:"displayActive"` + ECC string `json:"ecc"` + FanSpeed string `json:"fanSpeed"` + + Temperature string `json:"temperature"` + PerformanceState string `json:"performanceState"` + PowerDraw string `json:"powerDraw"` + MaxPowerLimit string `json:"maxPowerLimit"` + MemUsed string `json:"memUsed"` + MemTotal string `json:"memTotal"` + GPUUtil string `json:"gpuUtil"` + ComputeMode string `json:"computeMode"` + MigMode string `json:"migMode"` + Processes []Process `json:"processes"` +} + +type Process struct { + Pid string `json:"pid"` + Type string `json:"type"` + ProcessName string `json:"processName"` + UsedMemory string `json:"usedMemory"` +} diff --git a/agent/utils/ai_tools/gpu/gpu.go b/agent/utils/ai_tools/gpu/gpu.go new file mode 100644 index 000000000..ac21c32a8 --- /dev/null +++ b/agent/utils/ai_tools/gpu/gpu.go @@ -0,0 +1,65 @@ +package gpu + +import ( + "bytes" + _ "embed" + "encoding/xml" + "errors" + "fmt" + "io" + "strings" + "time" + + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/utils/ai_tools/gpu/common" + "github.com/1Panel-dev/1Panel/agent/utils/ai_tools/gpu/schema_v12" + "github.com/1Panel-dev/1Panel/agent/utils/cmd" +) + +type NvidiaSMI struct{} + +func New() (bool, NvidiaSMI) { + return cmd.Which("nvidia-smi"), NvidiaSMI{} +} + +func (n NvidiaSMI) LoadGpuInfo() (*common.GpuInfo, error) { + itemData, err := cmd.ExecWithTimeOut("nvidia-smi -q -x", 5*time.Second) + if err != nil { + return nil, fmt.Errorf("calling nvidia-smi failed, err: %w", err) + } + data := []byte(itemData) + schema := "v11" + + buf := bytes.NewBuffer(data) + decoder := xml.NewDecoder(buf) + for { + token, err := decoder.Token() + if err != nil { + if errors.Is(err, io.EOF) { + break + } + return nil, fmt.Errorf("reading token failed: %w", err) + } + d, ok := token.(xml.Directive) + if !ok { + continue + } + directive := string(d) + if !strings.HasPrefix(directive, "DOCTYPE") { + continue + } + parts := strings.Split(directive, " ") + s := strings.Trim(parts[len(parts)-1], "\" ") + if strings.HasPrefix(s, "nvsmi_device_") && strings.HasSuffix(s, ".dtd") { + schema = strings.TrimSuffix(strings.TrimPrefix(s, "nvsmi_device_"), ".dtd") + } else { + global.LOG.Debugf("Cannot find schema version in %q", directive) + } + break + } + + if schema != "v12" { + return &common.GpuInfo{}, nil + } + return schema_v12.Parse(data) +} diff --git a/agent/utils/ai_tools/gpu/schema_v12/parser.go b/agent/utils/ai_tools/gpu/schema_v12/parser.go new file mode 100644 index 000000000..cc2c629fa --- /dev/null +++ b/agent/utils/ai_tools/gpu/schema_v12/parser.go @@ -0,0 +1,55 @@ +package schema_v12 + +import ( + "encoding/xml" + + "github.com/1Panel-dev/1Panel/agent/utils/ai_tools/gpu/common" +) + +func Parse(buf []byte) (*common.GpuInfo, error) { + var ( + s smi + info common.GpuInfo + ) + if err := xml.Unmarshal(buf, &s); err != nil { + return nil, err + } + + info.Type = "nvidia" + info.CudaVersion = s.CudaVersion + info.DriverVersion = s.DriverVersion + if len(s.Gpu) == 0 { + return &info, nil + } + for i := 0; i < len(s.Gpu); i++ { + var gpuItem common.GPU + gpuItem.Index = uint(i) + gpuItem.ProductName = s.Gpu[i].ProductName + gpuItem.PersistenceMode = s.Gpu[i].PersistenceMode + gpuItem.BusID = s.Gpu[i].ID + gpuItem.DisplayActive = s.Gpu[i].DisplayActive + gpuItem.ECC = s.Gpu[i].EccErrors.Volatile.DramUncorrectable + gpuItem.FanSpeed = s.Gpu[i].FanSpeed + + gpuItem.Temperature = s.Gpu[i].Temperature.GpuTemp + gpuItem.PerformanceState = s.Gpu[i].PerformanceState + gpuItem.PowerDraw = s.Gpu[i].GpuPowerReadings.PowerDraw + gpuItem.MaxPowerLimit = s.Gpu[i].GpuPowerReadings.MaxPowerLimit + gpuItem.MemUsed = s.Gpu[i].FbMemoryUsage.Used + gpuItem.MemTotal = s.Gpu[i].FbMemoryUsage.Total + gpuItem.GPUUtil = s.Gpu[i].Utilization.GpuUtil + gpuItem.ComputeMode = s.Gpu[i].ComputeMode + gpuItem.MigMode = s.Gpu[i].MigMode.CurrentMig + + for _, process := range s.Gpu[i].Processes.ProcessInfo { + gpuItem.Processes = append(gpuItem.Processes, common.Process{ + Pid: process.Pid, + Type: process.Type, + ProcessName: process.ProcessName, + UsedMemory: process.UsedMemory, + }) + } + info.GPUs = append(info.GPUs, gpuItem) + } + return &info, nil +} diff --git a/agent/utils/ai_tools/gpu/schema_v12/types.go b/agent/utils/ai_tools/gpu/schema_v12/types.go new file mode 100644 index 000000000..d878ca8ba --- /dev/null +++ b/agent/utils/ai_tools/gpu/schema_v12/types.go @@ -0,0 +1,294 @@ +package schema_v12 + +type smi struct { + AttachedGpus string `xml:"attached_gpus"` + CudaVersion string `xml:"cuda_version"` + DriverVersion string `xml:"driver_version"` + Gpu []struct { + ID string `xml:"id,attr"` + AccountedProcesses struct{} `xml:"accounted_processes"` + AccountingMode string `xml:"accounting_mode"` + AccountingModeBufferSize string `xml:"accounting_mode_buffer_size"` + AddressingMode string `xml:"addressing_mode"` + ApplicationsClocks struct { + GraphicsClock string `xml:"graphics_clock"` + MemClock string `xml:"mem_clock"` + } `xml:"applications_clocks"` + Bar1MemoryUsage struct { + Free string `xml:"free"` + Total string `xml:"total"` + Used string `xml:"used"` + } `xml:"bar1_memory_usage"` + BoardID string `xml:"board_id"` + BoardPartNumber string `xml:"board_part_number"` + CcProtectedMemoryUsage struct { + Free string `xml:"free"` + Total string `xml:"total"` + Used string `xml:"used"` + } `xml:"cc_protected_memory_usage"` + ClockPolicy struct { + AutoBoost string `xml:"auto_boost"` + AutoBoostDefault string `xml:"auto_boost_default"` + } `xml:"clock_policy"` + Clocks struct { + GraphicsClock string `xml:"graphics_clock"` + MemClock string `xml:"mem_clock"` + SmClock string `xml:"sm_clock"` + VideoClock string `xml:"video_clock"` + } `xml:"clocks"` + ClocksEventReasons struct { + ClocksEventReasonApplicationsClocksSetting string `xml:"clocks_event_reason_applications_clocks_setting"` + ClocksEventReasonDisplayClocksSetting string `xml:"clocks_event_reason_display_clocks_setting"` + ClocksEventReasonGpuIdle string `xml:"clocks_event_reason_gpu_idle"` + ClocksEventReasonHwPowerBrakeSlowdown string `xml:"clocks_event_reason_hw_power_brake_slowdown"` + ClocksEventReasonHwSlowdown string `xml:"clocks_event_reason_hw_slowdown"` + ClocksEventReasonHwThermalSlowdown string `xml:"clocks_event_reason_hw_thermal_slowdown"` + ClocksEventReasonSwPowerCap string `xml:"clocks_event_reason_sw_power_cap"` + ClocksEventReasonSwThermalSlowdown string `xml:"clocks_event_reason_sw_thermal_slowdown"` + ClocksEventReasonSyncBoost string `xml:"clocks_event_reason_sync_boost"` + } `xml:"clocks_event_reasons"` + ComputeMode string `xml:"compute_mode"` + DefaultApplicationsClocks struct { + GraphicsClock string `xml:"graphics_clock"` + MemClock string `xml:"mem_clock"` + } `xml:"default_applications_clocks"` + DeferredClocks struct { + MemClock string `xml:"mem_clock"` + } `xml:"deferred_clocks"` + DisplayActive string `xml:"display_active"` + DisplayMode string `xml:"display_mode"` + DriverModel struct { + CurrentDm string `xml:"current_dm"` + PendingDm string `xml:"pending_dm"` + } `xml:"driver_model"` + EccErrors struct { + Aggregate struct { + DramCorrectable string `xml:"dram_correctable"` + DramUncorrectable string `xml:"dram_uncorrectable"` + SramCorrectable string `xml:"sram_correctable"` + SramUncorrectable string `xml:"sram_uncorrectable"` + } `xml:"aggregate"` + Volatile struct { + DramCorrectable string `xml:"dram_correctable"` + DramUncorrectable string `xml:"dram_uncorrectable"` + SramCorrectable string `xml:"sram_correctable"` + SramUncorrectable string `xml:"sram_uncorrectable"` + } `xml:"volatile"` + } `xml:"ecc_errors"` + EccMode struct { + CurrentEcc string `xml:"current_ecc"` + PendingEcc string `xml:"pending_ecc"` + } `xml:"ecc_mode"` + EncoderStats struct { + AverageFps string `xml:"average_fps"` + AverageLatency string `xml:"average_latency"` + SessionCount string `xml:"session_count"` + } `xml:"encoder_stats"` + Fabric struct { + State string `xml:"state"` + Status string `xml:"status"` + } `xml:"fabric"` + FanSpeed string `xml:"fan_speed"` + FbMemoryUsage struct { + Free string `xml:"free"` + Reserved string `xml:"reserved"` + Total string `xml:"total"` + Used string `xml:"used"` + } `xml:"fb_memory_usage"` + FbcStats struct { + AverageFps string `xml:"average_fps"` + AverageLatency string `xml:"average_latency"` + SessionCount string `xml:"session_count"` + } `xml:"fbc_stats"` + GpuFruPartNumber string `xml:"gpu_fru_part_number"` + GpuModuleID string `xml:"gpu_module_id"` + GpuOperationMode struct { + CurrentGom string `xml:"current_gom"` + PendingGom string `xml:"pending_gom"` + } `xml:"gpu_operation_mode"` + GpuPartNumber string `xml:"gpu_part_number"` + GpuPowerReadings struct { + CurrentPowerLimit string `xml:"current_power_limit"` + DefaultPowerLimit string `xml:"default_power_limit"` + MaxPowerLimit string `xml:"max_power_limit"` + MinPowerLimit string `xml:"min_power_limit"` + PowerDraw string `xml:"power_draw"` + PowerState string `xml:"power_state"` + RequestedPowerLimit string `xml:"requested_power_limit"` + } `xml:"gpu_power_readings"` + GpuResetStatus struct { + DrainAndResetRecommended string `xml:"drain_and_reset_recommended"` + ResetRequired string `xml:"reset_required"` + } `xml:"gpu_reset_status"` + GpuVirtualizationMode struct { + HostVgpuMode string `xml:"host_vgpu_mode"` + VirtualizationMode string `xml:"virtualization_mode"` + } `xml:"gpu_virtualization_mode"` + GspFirmwareVersion string `xml:"gsp_firmware_version"` + Ibmnpu struct { + RelaxedOrderingMode string `xml:"relaxed_ordering_mode"` + } `xml:"ibmnpu"` + InforomVersion struct { + EccObject string `xml:"ecc_object"` + ImgVersion string `xml:"img_version"` + OemObject string `xml:"oem_object"` + PwrObject string `xml:"pwr_object"` + } `xml:"inforom_version"` + MaxClocks struct { + GraphicsClock string `xml:"graphics_clock"` + MemClock string `xml:"mem_clock"` + SmClock string `xml:"sm_clock"` + VideoClock string `xml:"video_clock"` + } `xml:"max_clocks"` + MaxCustomerBoostClocks struct { + GraphicsClock string `xml:"graphics_clock"` + } `xml:"max_customer_boost_clocks"` + MigDevices struct { + MigDevice []struct { + Index string `xml:"index"` + GpuInstanceID string `xml:"gpu_instance_id"` + ComputeInstanceID string `xml:"compute_instance_id"` + EccErrorCount struct { + Text string `xml:",chardata" json:"text"` + VolatileCount struct { + SramUncorrectable string `xml:"sram_uncorrectable"` + } `xml:"volatile_count" json:"volatile_count"` + } `xml:"ecc_error_count" json:"ecc_error_count"` + FbMemoryUsage struct { + Total string `xml:"total"` + Reserved string `xml:"reserved"` + Used string `xml:"used"` + Free string `xml:"free"` + } `xml:"fb_memory_usage" json:"fb_memory_usage"` + Bar1MemoryUsage struct { + Total string `xml:"total"` + Used string `xml:"used"` + Free string `xml:"free"` + } `xml:"bar1_memory_usage" json:"bar1_memory_usage"` + } `xml:"mig_device" json:"mig_device"` + } `xml:"mig_devices" json:"mig_devices"` + MigMode struct { + CurrentMig string `xml:"current_mig"` + PendingMig string `xml:"pending_mig"` + } `xml:"mig_mode"` + MinorNumber string `xml:"minor_number"` + ModulePowerReadings struct { + CurrentPowerLimit string `xml:"current_power_limit"` + DefaultPowerLimit string `xml:"default_power_limit"` + MaxPowerLimit string `xml:"max_power_limit"` + MinPowerLimit string `xml:"min_power_limit"` + PowerDraw string `xml:"power_draw"` + PowerState string `xml:"power_state"` + RequestedPowerLimit string `xml:"requested_power_limit"` + } `xml:"module_power_readings"` + MultigpuBoard string `xml:"multigpu_board"` + Pci struct { + AtomicCapsInbound string `xml:"atomic_caps_inbound"` + AtomicCapsOutbound string `xml:"atomic_caps_outbound"` + PciBridgeChip struct { + BridgeChipFw string `xml:"bridge_chip_fw"` + BridgeChipType string `xml:"bridge_chip_type"` + } `xml:"pci_bridge_chip"` + PciBus string `xml:"pci_bus"` + PciBusID string `xml:"pci_bus_id"` + PciDevice string `xml:"pci_device"` + PciDeviceID string `xml:"pci_device_id"` + PciDomain string `xml:"pci_domain"` + PciGpuLinkInfo struct { + LinkWidths struct { + CurrentLinkWidth string `xml:"current_link_width"` + MaxLinkWidth string `xml:"max_link_width"` + } `xml:"link_widths"` + PcieGen struct { + CurrentLinkGen string `xml:"current_link_gen"` + DeviceCurrentLinkGen string `xml:"device_current_link_gen"` + MaxDeviceLinkGen string `xml:"max_device_link_gen"` + MaxHostLinkGen string `xml:"max_host_link_gen"` + MaxLinkGen string `xml:"max_link_gen"` + } `xml:"pcie_gen"` + } `xml:"pci_gpu_link_info"` + PciSubSystemID string `xml:"pci_sub_system_id"` + ReplayCounter string `xml:"replay_counter"` + ReplayRolloverCounter string `xml:"replay_rollover_counter"` + RxUtil string `xml:"rx_util"` + TxUtil string `xml:"tx_util"` + } `xml:"pci"` + PerformanceState string `xml:"performance_state"` + PersistenceMode string `xml:"persistence_mode"` + PowerReadings struct { + PowerState string `xml:"power_state"` + PowerManagement string `xml:"power_management"` + PowerDraw string `xml:"power_draw"` + PowerLimit string `xml:"power_limit"` + DefaultPowerLimit string `xml:"default_power_limit"` + EnforcedPowerLimit string `xml:"enforced_power_limit"` + MinPowerLimit string `xml:"min_power_limit"` + MaxPowerLimit string `xml:"max_power_limit"` + } `xml:"power_readings"` + Processes struct { + ProcessInfo []struct { + Pid string `xml:"pid"` + Type string `xml:"type"` + ProcessName string `xml:"process_name"` + UsedMemory string `xml:"used_memory"` + } `xml:"process_info"` + } `xml:"processes"` + ProductArchitecture string `xml:"product_architecture"` + ProductBrand string `xml:"product_brand"` + ProductName string `xml:"product_name"` + RemappedRows struct { + // Manually added + Correctable string `xml:"remapped_row_corr"` + Uncorrectable string `xml:"remapped_row_unc"` + Pending string `xml:"remapped_row_pending"` + Failure string `xml:"remapped_row_failure"` + } `xml:"remapped_rows"` + RetiredPages struct { + DoubleBitRetirement struct { + RetiredCount string `xml:"retired_count"` + RetiredPagelist string `xml:"retired_pagelist"` + } `xml:"double_bit_retirement"` + MultipleSingleBitRetirement struct { + RetiredCount string `xml:"retired_count"` + RetiredPagelist string `xml:"retired_pagelist"` + } `xml:"multiple_single_bit_retirement"` + PendingBlacklist string `xml:"pending_blacklist"` + PendingRetirement string `xml:"pending_retirement"` + } `xml:"retired_pages"` + Serial string `xml:"serial"` + SupportedClocks struct { + SupportedMemClock []struct { + SupportedGraphicsClock []string `xml:"supported_graphics_clock"` + Value string `xml:"value"` + } `xml:"supported_mem_clock"` + } `xml:"supported_clocks"` + SupportedGpuTargetTemp struct { + GpuTargetTempMax string `xml:"gpu_target_temp_max"` + GpuTargetTempMin string `xml:"gpu_target_temp_min"` + } `xml:"supported_gpu_target_temp"` + Temperature struct { + GpuTargetTemperature string `xml:"gpu_target_temperature"` + GpuTemp string `xml:"gpu_temp"` + GpuTempMaxGpuThreshold string `xml:"gpu_temp_max_gpu_threshold"` + GpuTempMaxMemThreshold string `xml:"gpu_temp_max_mem_threshold"` + GpuTempMaxThreshold string `xml:"gpu_temp_max_threshold"` + GpuTempSlowThreshold string `xml:"gpu_temp_slow_threshold"` + GpuTempTlimit string `xml:"gpu_temp_tlimit"` + MemoryTemp string `xml:"memory_temp"` + } `xml:"temperature"` + Utilization struct { + DecoderUtil string `xml:"decoder_util"` + EncoderUtil string `xml:"encoder_util"` + GpuUtil string `xml:"gpu_util"` + JpegUtil string `xml:"jpeg_util"` + MemoryUtil string `xml:"memory_util"` + OfaUtil string `xml:"ofa_util"` + } `xml:"utilization"` + UUID string `xml:"uuid"` + VbiosVersion string `xml:"vbios_version"` + Voltage struct { + GraphicsVolt string `xml:"graphics_volt"` + } `xml:"voltage"` + } `xml:"gpu"` + Timestamp string `xml:"timestamp"` +} diff --git a/agent/utils/ai_tools/xpu/types.go b/agent/utils/ai_tools/xpu/types.go new file mode 100644 index 000000000..e78f6a7a5 --- /dev/null +++ b/agent/utils/ai_tools/xpu/types.go @@ -0,0 +1,43 @@ +package xpu + +type DeviceUtilByProc struct { + DeviceID int `json:"device_id"` + MemSize float64 `json:"mem_size"` + ProcessID int `json:"process_id"` + ProcessName string `json:"process_name"` + SharedMemSize float64 `json:"shared_mem_size"` +} + +type DeviceUtilByProcList struct { + DeviceUtilByProcList []DeviceUtilByProc `json:"device_util_by_proc_list"` +} + +type Device struct { + DeviceFunctionType string `json:"device_function_type"` + DeviceID int `json:"device_id"` + DeviceName string `json:"device_name"` + DeviceType string `json:"device_type"` + DrmDevice string `json:"drm_device"` + PciBdfAddress string `json:"pci_bdf_address"` + PciDeviceID string `json:"pci_device_id"` + UUID string `json:"uuid"` + VendorName string `json:"vendor_name"` + + MemoryPhysicalSizeByte string `json:"memory_physical_size_byte"` + MemoryFreeSizeByte string `json:"memory_free_size_byte"` + DriverVersion string `json:"driver_version"` +} + +type DeviceInfo struct { + DeviceList []Device `json:"device_list"` +} + +type DeviceLevelMetric struct { + MetricsType string `json:"metrics_type"` + Value float64 `json:"value"` +} + +type DeviceStats struct { + DeviceID int `json:"device_id"` + DeviceLevel []DeviceLevelMetric `json:"device_level"` +} diff --git a/agent/utils/ai_tools/xpu/xpu.go b/agent/utils/ai_tools/xpu/xpu.go new file mode 100644 index 000000000..1c9b5a11a --- /dev/null +++ b/agent/utils/ai_tools/xpu/xpu.go @@ -0,0 +1,254 @@ +package xpu + +import ( + "encoding/json" + "fmt" + "sort" + "strconv" + "sync" + "time" + + baseGlobal "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/utils/cmd" +) + +type XpuSMI struct{} + +func New() (bool, XpuSMI) { + return cmd.Which("xpu-smi"), XpuSMI{} +} + +func (x XpuSMI) loadDeviceData(device Device, wg *sync.WaitGroup, res *[]XPUSimpleInfo, mu *sync.Mutex) { + defer wg.Done() + + var xpu XPUSimpleInfo + xpu.DeviceID = device.DeviceID + xpu.DeviceName = device.DeviceName + + var xpuData, statsData string + var xpuErr, statsErr error + + var wgCmd sync.WaitGroup + wgCmd.Add(2) + + go func() { + defer wgCmd.Done() + xpuData, xpuErr = cmd.ExecWithTimeOut(fmt.Sprintf("xpu-smi discovery -d %d -j", device.DeviceID), 5*time.Second) + }() + + go func() { + defer wgCmd.Done() + statsData, statsErr = cmd.ExecWithTimeOut(fmt.Sprintf("xpu-smi stats -d %d -j", device.DeviceID), 5*time.Second) + }() + + wgCmd.Wait() + + if xpuErr != nil { + baseGlobal.LOG.Errorf("calling xpu-smi discovery failed for device %d, err: %v\n", device.DeviceID, xpuErr) + return + } + + var info Device + if err := json.Unmarshal([]byte(xpuData), &info); err != nil { + baseGlobal.LOG.Errorf("xpuData json unmarshal failed for device %d, err: %v\n", device.DeviceID, err) + return + } + + bytes, err := strconv.ParseInt(info.MemoryPhysicalSizeByte, 10, 64) + if err != nil { + baseGlobal.LOG.Errorf("Error parsing memory size for device %d, err: %v\n", device.DeviceID, err) + return + } + xpu.Memory = fmt.Sprintf("%.1f MB", float64(bytes)/(1024*1024)) + + if statsErr != nil { + baseGlobal.LOG.Errorf("calling xpu-smi stats failed for device %d, err: %v\n", device.DeviceID, statsErr) + return + } + + var stats DeviceStats + if err := json.Unmarshal([]byte(statsData), &stats); err != nil { + baseGlobal.LOG.Errorf("statsData json unmarshal failed for device %d, err: %v\n", device.DeviceID, err) + return + } + + for _, stat := range stats.DeviceLevel { + switch stat.MetricsType { + case "XPUM_STATS_POWER": + xpu.Power = fmt.Sprintf("%.1fW", stat.Value) + case "XPUM_STATS_GPU_CORE_TEMPERATURE": + xpu.Temperature = fmt.Sprintf("%.1f°C", stat.Value) + case "XPUM_STATS_MEMORY_USED": + xpu.MemoryUsed = fmt.Sprintf("%.1fMB", stat.Value) + case "XPUM_STATS_MEMORY_UTILIZATION": + xpu.MemoryUtil = fmt.Sprintf("%.1f%%", stat.Value) + } + } + + mu.Lock() + *res = append(*res, xpu) + mu.Unlock() +} + +func (x XpuSMI) LoadDashData() ([]XPUSimpleInfo, error) { + data, err := cmd.ExecWithTimeOut("xpu-smi discovery -j", 5*time.Second) + if err != nil { + return nil, fmt.Errorf("calling xpu-smi failed, err: %w", err) + } + + var deviceInfo DeviceInfo + if err := json.Unmarshal([]byte(data), &deviceInfo); err != nil { + return nil, fmt.Errorf("deviceInfo json unmarshal failed, err: %w", err) + } + + var res []XPUSimpleInfo + var wg sync.WaitGroup + var mu sync.Mutex + + for _, device := range deviceInfo.DeviceList { + wg.Add(1) + go x.loadDeviceData(device, &wg, &res, &mu) + } + + wg.Wait() + + sort.Slice(res, func(i, j int) bool { + return res[i].DeviceID < res[j].DeviceID + }) + return res, nil +} + +func (x XpuSMI) LoadGpuInfo() (*XpuInfo, error) { + data, err := cmd.ExecWithTimeOut("xpu-smi discovery -j", 5*time.Second) + if err != nil { + return nil, fmt.Errorf("calling xpu-smi failed, err: %w", err) + } + var deviceInfo DeviceInfo + if err := json.Unmarshal([]byte(data), &deviceInfo); err != nil { + return nil, fmt.Errorf("deviceInfo json unmarshal failed, err: %w", err) + } + res := &XpuInfo{ + Type: "xpu", + } + + var wg sync.WaitGroup + var mu sync.Mutex + + for _, device := range deviceInfo.DeviceList { + wg.Add(1) + go x.loadDeviceInfo(device, &wg, res, &mu) + } + + wg.Wait() + + processData, err := cmd.ExecWithTimeOut(fmt.Sprintf("xpu-smi ps -j"), 5*time.Second) + if err != nil { + return nil, fmt.Errorf("calling xpu-smi ps failed, err: %w", err) + } + var psList DeviceUtilByProcList + if err := json.Unmarshal([]byte(processData), &psList); err != nil { + return nil, fmt.Errorf("processData json unmarshal failed, err: %w", err) + } + for _, ps := range psList.DeviceUtilByProcList { + process := Process{ + PID: ps.ProcessID, + Command: ps.ProcessName, + } + if ps.SharedMemSize > 0 { + process.SHR = fmt.Sprintf("%.1f MB", ps.SharedMemSize/1024) + } + if ps.MemSize > 0 { + process.Memory = fmt.Sprintf("%.1f MB", ps.MemSize/1024) + } + for index, xpu := range res.Xpu { + if xpu.Basic.DeviceID == ps.DeviceID { + res.Xpu[index].Processes = append(res.Xpu[index].Processes, process) + } + } + } + + return res, nil +} + +func (x XpuSMI) loadDeviceInfo(device Device, wg *sync.WaitGroup, res *XpuInfo, mu *sync.Mutex) { + defer wg.Done() + + xpu := Xpu{ + Basic: Basic{ + DeviceID: device.DeviceID, + DeviceName: device.DeviceName, + VendorName: device.VendorName, + PciBdfAddress: device.PciBdfAddress, + }, + } + + var xpuData, statsData string + var xpuErr, statsErr error + + var wgCmd sync.WaitGroup + wgCmd.Add(2) + + go func() { + defer wgCmd.Done() + xpuData, xpuErr = cmd.ExecWithTimeOut(fmt.Sprintf("xpu-smi discovery -d %d -j", device.DeviceID), 5*time.Second) + }() + + go func() { + defer wgCmd.Done() + statsData, statsErr = cmd.ExecWithTimeOut(fmt.Sprintf("xpu-smi stats -d %d -j", device.DeviceID), 5*time.Second) + }() + + wgCmd.Wait() + + if xpuErr != nil { + baseGlobal.LOG.Errorf("calling xpu-smi discovery failed for device %d, err: %v\n", device.DeviceID, xpuErr) + return + } + + var info Device + if err := json.Unmarshal([]byte(xpuData), &info); err != nil { + baseGlobal.LOG.Errorf("xpuData json unmarshal failed for device %d, err: %v\n", device.DeviceID, err) + return + } + + res.DriverVersion = info.DriverVersion + xpu.Basic.DriverVersion = info.DriverVersion + + bytes, err := strconv.ParseInt(info.MemoryPhysicalSizeByte, 10, 64) + if err != nil { + baseGlobal.LOG.Errorf("Error parsing memory size for device %d, err: %v\n", device.DeviceID, err) + return + } + xpu.Basic.Memory = fmt.Sprintf("%.1f MB", float64(bytes)/(1024*1024)) + xpu.Basic.FreeMemory = info.MemoryFreeSizeByte + + if statsErr != nil { + baseGlobal.LOG.Errorf("calling xpu-smi stats failed for device %d, err: %v\n", device.DeviceID, statsErr) + return + } + + var stats DeviceStats + if err := json.Unmarshal([]byte(statsData), &stats); err != nil { + baseGlobal.LOG.Errorf("statsData json unmarshal failed for device %d, err: %v\n", device.DeviceID, err) + return + } + + for _, stat := range stats.DeviceLevel { + switch stat.MetricsType { + case "XPUM_STATS_POWER": + xpu.Stats.Power = fmt.Sprintf("%.1fW", stat.Value) + case "XPUM_STATS_GPU_FREQUENCY": + xpu.Stats.Frequency = fmt.Sprintf("%.1fMHz", stat.Value) + case "XPUM_STATS_GPU_CORE_TEMPERATURE": + xpu.Stats.Temperature = fmt.Sprintf("%.1f°C", stat.Value) + case "XPUM_STATS_MEMORY_USED": + xpu.Stats.MemoryUsed = fmt.Sprintf("%.1fMB", stat.Value) + case "XPUM_STATS_MEMORY_UTILIZATION": + xpu.Stats.MemoryUtil = fmt.Sprintf("%.1f%%", stat.Value) + } + } + + mu.Lock() + res.Xpu = append(res.Xpu, xpu) + mu.Unlock() +} diff --git a/agent/utils/ai_tools/xpu/xpu_info.go b/agent/utils/ai_tools/xpu/xpu_info.go new file mode 100644 index 000000000..9c7d45656 --- /dev/null +++ b/agent/utils/ai_tools/xpu/xpu_info.go @@ -0,0 +1,49 @@ +package xpu + +type XpuInfo struct { + Type string `json:"type"` + DriverVersion string `json:"driverVersion"` + + Xpu []Xpu `json:"xpu"` +} + +type Xpu struct { + Basic Basic `json:"basic"` + Stats Stats `json:"stats"` + Processes []Process `json:"processes"` +} + +type Basic struct { + DeviceID int `json:"deviceID"` + DeviceName string `json:"deviceName"` + VendorName string `json:"vendorName"` + DriverVersion string `json:"driverVersion"` + Memory string `json:"memory"` + FreeMemory string `json:"freeMemory"` + PciBdfAddress string `json:"pciBdfAddress"` +} + +type Stats struct { + Power string `json:"power"` + Frequency string `json:"frequency"` + Temperature string `json:"temperature"` + MemoryUsed string `json:"memoryUsed"` + MemoryUtil string `json:"memoryUtil"` +} + +type Process struct { + PID int `json:"pid"` + Command string `json:"command"` + SHR string `json:"shr"` + Memory string `json:"memory"` +} + +type XPUSimpleInfo struct { + DeviceID int `json:"deviceID"` + DeviceName string `json:"deviceName"` + Memory string `json:"memory"` + Temperature string `json:"temperature"` + MemoryUsed string `json:"memoryUsed"` + Power string `json:"power"` + MemoryUtil string `json:"memoryUtil"` +} diff --git a/agent/utils/common/common.go b/agent/utils/common/common.go index aaee4bf59..41a9302cd 100644 --- a/agent/utils/common/common.go +++ b/agent/utils/common/common.go @@ -16,6 +16,7 @@ import ( "github.com/gin-gonic/gin" + "github.com/1Panel-dev/1Panel/agent/buserr" "github.com/1Panel-dev/1Panel/agent/utils/cmd" "golang.org/x/net/idna" ) @@ -357,3 +358,22 @@ func GetLang(c *gin.Context) string { } return lang } + +func HandleIPList(content string) ([]string, error) { + ipList := strings.Split(content, "\n") + var res []string + for _, ip := range ipList { + if ip == "" { + continue + } + if net.ParseIP(ip) != nil { + res = append(res, ip) + continue + } + if _, _, err := net.ParseCIDR(ip); err != nil { + return nil, buserr.New("ErrParseIP") + } + res = append(res, ip) + } + return res, nil +} diff --git a/agent/utils/nginx/components/server.go b/agent/utils/nginx/components/server.go index a5b0446e8..9ed53569a 100644 --- a/agent/utils/nginx/components/server.go +++ b/agent/utils/nginx/components/server.go @@ -260,6 +260,53 @@ func (s *Server) UpdateRoot(path string) { s.UpdateDirective("root", []string{path}) } +func (s *Server) UpdateRootProxyForAi(proxy []string) { + newDir := Directive{ + Name: "location", + Parameters: []string{"/"}, + Block: &Block{}, + } + block := &Block{} + block.Directives = []IDirective{ + &Directive{ + Name: "proxy_buffering", + Parameters: []string{ + "off", + }, + }, + &Directive{ + Name: "proxy_cache", + Parameters: []string{ + "off", + }, + }, + &Directive{ + Name: "proxy_http_version", + Parameters: []string{ + "1.1", + }, + }, + &Directive{ + Name: "proxy_set_header", + Parameters: []string{ + "Connection", "''", + }, + }, + &Directive{ + Name: "chunked_transfer_encoding", + Parameters: []string{ + "off", + }, + }, + } + block.Directives = append(block.Directives, &Directive{ + Name: "proxy_pass", + Parameters: proxy, + }) + newDir.Block = block + s.UpdateDirectiveBySecondKey("location", "/", newDir) +} + func (s *Server) UpdateRootLocation() { newDir := Directive{ Name: "location", @@ -393,3 +440,30 @@ func (s *Server) AddHTTP2HTTPS() { newDir.Block = block s.UpdateDirectiveBySecondKey("if", "($scheme", newDir) } + +func (s *Server) UpdateAllowIPs(ips []string) { + index := -1 + for i, directive := range s.Directives { + if directive.GetName() == "location" && directive.GetParameters()[0] == "/" { + index = i + break + } + } + ipDirectives := make([]IDirective, 0) + for _, ip := range ips { + ipDirectives = append(ipDirectives, &Directive{ + Name: "allow", + Parameters: []string{ip}, + }) + } + ipDirectives = append(ipDirectives, &Directive{ + Name: "deny", + Parameters: []string{"all"}, + }) + if index != -1 { + newDirectives := append(ipDirectives, s.Directives[index:]...) + s.Directives = append(s.Directives[:index], newDirectives...) + } else { + s.Directives = append(s.Directives, ipDirectives...) + } +} diff --git a/core/constant/common.go b/core/constant/common.go index bcf6a1132..bcd05de1d 100644 --- a/core/constant/common.go +++ b/core/constant/common.go @@ -47,6 +47,10 @@ var WebUrlMap = map[string]struct{}{ "/apps/upgrade": {}, "/apps/setting": {}, + "/ai": {}, + "/ai/model": {}, + "/ai/gpu": {}, + "/containers": {}, "/containers/container": {}, "/containers/image": {}, diff --git a/frontend/src/api/interface/ai.ts b/frontend/src/api/interface/ai.ts new file mode 100644 index 000000000..a120bea66 --- /dev/null +++ b/frontend/src/api/interface/ai.ts @@ -0,0 +1,111 @@ +import { ReqPage } from '.'; + +export namespace AI { + export interface OllamaModelInfo { + id: number; + name: string; + size: string; + from: string; + logFileExist: boolean; + status: string; + message: string; + createdAt: Date; + } + export interface OllamaModelDropInfo { + id: number; + name: string; + } + export interface OllamaModelSearch extends ReqPage { + info: string; + } + + export interface Info { + cudaVersion: string; + driverVersion: string; + type: string; + gpu: GPU[]; + } + export interface GPU { + index: number; + productName: string; + persistenceMode: string; + busID: string; + displayActive: string; + ecc: string; + fanSpeed: string; + + temperature: string; + performanceState: string; + powerDraw: string; + maxPowerLimit: string; + memUsed: string; + memTotal: string; + gpuUtil: string; + computeMode: string; + migMode: string; + processes: Process[]; + } + export interface Process { + pid: string; + type: string; + processName: string; + usedMemory: string; + } + + export interface XpuInfo { + type: string; + driverVersion: string; + xpu: Xpu[]; + } + + interface Xpu { + basic: Basic; + stats: Stats; + processes: XpuProcess[]; + } + + interface Basic { + deviceID: number; + deviceName: string; + vendorName: string; + driverVersion: string; + memory: string; + freeMemory: string; + pciBdfAddress: string; + } + + interface Stats { + power: string; + frequency: string; + temperature: string; + memoryUsed: string; + memoryUtil: string; + } + + interface XpuProcess { + pid: number; + command: string; + shr: string; + memory: string; + } + + export interface BindDomain { + domain: string; + sslID: number; + ipList: string; + appInstallID: number; + websiteID?: number; + } + + export interface BindDomainReq { + appInstallID: number; + } + + export interface BindDomainRes { + domain: string; + sslID: number; + allowIPs: string[]; + websiteID?: number; + connUrl: string; + } +} diff --git a/frontend/src/api/modules/ai.ts b/frontend/src/api/modules/ai.ts new file mode 100644 index 000000000..464f553cd --- /dev/null +++ b/frontend/src/api/modules/ai.ts @@ -0,0 +1,41 @@ +import { AI } from '@/api/interface/ai'; +import http from '@/api'; +import { ResPage } from '../interface'; + +export const createOllamaModel = (name: string) => { + return http.post(`/ai/ollama/model`, { name: name }); +}; +export const recreateOllamaModel = (name: string) => { + return http.post(`/ai/ollama/model/recreate`, { name: name }); +}; +export const deleteOllamaModel = (ids: Array, force: boolean) => { + return http.post(`/ai/ollama/model/del`, { ids: ids, forceDelete: force }); +}; +export const searchOllamaModel = (params: AI.OllamaModelSearch) => { + return http.post>(`/ai/ollama/model/search`, params); +}; +export const loadOllamaModel = (name: string) => { + return http.post(`/ai/ollama/model/load`, { name: name }); +}; +export const syncOllamaModel = () => { + return http.post>(`/ai/ollama/model/sync`); +}; +export const closeOllamaModel = (name: string) => { + return http.post(`/ai/ollama/close`, { name: name }); +}; + +export const loadGPUInfo = () => { + return http.get(`/ai/gpu/load`); +}; + +export const bindDomain = (req: AI.BindDomain) => { + return http.post(`/ai/domain/bind`, req); +}; + +export const getBindDomain = (req: AI.BindDomainReq) => { + return http.post(`/ai/domain/get`, req); +}; + +export const updateBindDomain = (req: AI.BindDomain) => { + return http.post(`/ai/domain/update`, req); +}; diff --git a/frontend/src/assets/iconfont/iconfont.css b/frontend/src/assets/iconfont/iconfont.css index 9f71214b1..43fa56b79 100644 --- a/frontend/src/assets/iconfont/iconfont.css +++ b/frontend/src/assets/iconfont/iconfont.css @@ -1,9 +1,9 @@ @font-face { font-family: "iconfont"; /* Project id 4776196 */ - src: url('iconfont.woff2?t=1740384606757') format('woff2'), - url('iconfont.woff?t=1740384606757') format('woff'), - url('iconfont.ttf?t=1740384606757') format('truetype'), - url('iconfont.svg?t=1740384606757#iconfont') format('svg'); + src: url('iconfont.woff2?t=1740392092454') format('woff2'), + url('iconfont.woff?t=1740392092454') format('woff'), + url('iconfont.ttf?t=1740392092454') format('truetype'), + url('iconfont.svg?t=1740392092454#iconfont') format('svg'); } .iconfont { @@ -14,6 +14,10 @@ -moz-osx-font-smoothing: grayscale; } +.p-jiqiren2:before { + content: "\e61b"; +} + .p-terminal2:before { content: "\e82f"; } diff --git a/frontend/src/assets/iconfont/iconfont.js b/frontend/src/assets/iconfont/iconfont.js index 92eec1736..6fdd959b6 100644 --- a/frontend/src/assets/iconfont/iconfont.js +++ b/frontend/src/assets/iconfont/iconfont.js @@ -1 +1 @@ -window._iconfont_svg_string_4776196='',(h=>{var l=(a=(a=document.getElementsByTagName("script"))[a.length-1]).getAttribute("data-injectcss"),a=a.getAttribute("data-disable-injectsvg");if(!a){var c,t,p,z,v,i=function(l,a){a.parentNode.insertBefore(l,a)};if(l&&!h.__iconfont__svg__cssinject__){h.__iconfont__svg__cssinject__=!0;try{document.write("")}catch(l){console&&console.log(l)}}c=function(){var l,a=document.createElement("div");a.innerHTML=h._iconfont_svg_string_4776196,(a=a.getElementsByTagName("svg")[0])&&(a.setAttribute("aria-hidden","true"),a.style.position="absolute",a.style.width=0,a.style.height=0,a.style.overflow="hidden",a=a,(l=document.body).firstChild?i(a,l.firstChild):l.appendChild(a))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(c,0):(t=function(){document.removeEventListener("DOMContentLoaded",t,!1),c()},document.addEventListener("DOMContentLoaded",t,!1)):document.attachEvent&&(p=c,z=h.document,v=!1,m(),z.onreadystatechange=function(){"complete"==z.readyState&&(z.onreadystatechange=null,d())})}function d(){v||(v=!0,p())}function m(){try{z.documentElement.doScroll("left")}catch(l){return void setTimeout(m,50)}d()}})(window); \ No newline at end of file +window._iconfont_svg_string_4776196='',(h=>{var l=(a=(a=document.getElementsByTagName("script"))[a.length-1]).getAttribute("data-injectcss"),a=a.getAttribute("data-disable-injectsvg");if(!a){var c,t,p,z,v,i=function(l,a){a.parentNode.insertBefore(l,a)};if(l&&!h.__iconfont__svg__cssinject__){h.__iconfont__svg__cssinject__=!0;try{document.write("")}catch(l){console&&console.log(l)}}c=function(){var l,a=document.createElement("div");a.innerHTML=h._iconfont_svg_string_4776196,(a=a.getElementsByTagName("svg")[0])&&(a.setAttribute("aria-hidden","true"),a.style.position="absolute",a.style.width=0,a.style.height=0,a.style.overflow="hidden",a=a,(l=document.body).firstChild?i(a,l.firstChild):l.appendChild(a))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(c,0):(t=function(){document.removeEventListener("DOMContentLoaded",t,!1),c()},document.addEventListener("DOMContentLoaded",t,!1)):document.attachEvent&&(p=c,z=h.document,v=!1,m(),z.onreadystatechange=function(){"complete"==z.readyState&&(z.onreadystatechange=null,d())})}function d(){v||(v=!0,p())}function m(){try{z.documentElement.doScroll("left")}catch(l){return void setTimeout(m,50)}d()}})(window); \ No newline at end of file diff --git a/frontend/src/assets/iconfont/iconfont.json b/frontend/src/assets/iconfont/iconfont.json index c9418f627..3d8a75f37 100644 --- a/frontend/src/assets/iconfont/iconfont.json +++ b/frontend/src/assets/iconfont/iconfont.json @@ -5,6 +5,13 @@ "css_prefix_text": "p-", "description": "", "glyphs": [ + { + "icon_id": "10505865", + "name": "机器人", + "font_class": "jiqiren2", + "unicode": "e61b", + "unicode_decimal": 58907 + }, { "icon_id": "5127551", "name": "terminal", diff --git a/frontend/src/assets/iconfont/iconfont.svg b/frontend/src/assets/iconfont/iconfont.svg index 57ce7ab90..fb9331c2d 100644 --- a/frontend/src/assets/iconfont/iconfont.svg +++ b/frontend/src/assets/iconfont/iconfont.svg @@ -14,6 +14,8 @@ /> + + diff --git a/frontend/src/assets/iconfont/iconfont.ttf b/frontend/src/assets/iconfont/iconfont.ttf index 4842a94fd..3d2d80598 100644 Binary files a/frontend/src/assets/iconfont/iconfont.ttf and b/frontend/src/assets/iconfont/iconfont.ttf differ diff --git a/frontend/src/assets/iconfont/iconfont.woff b/frontend/src/assets/iconfont/iconfont.woff index 5bd37d10c..139f90339 100644 Binary files a/frontend/src/assets/iconfont/iconfont.woff and b/frontend/src/assets/iconfont/iconfont.woff differ diff --git a/frontend/src/assets/iconfont/iconfont.woff2 b/frontend/src/assets/iconfont/iconfont.woff2 index ef57a3691..85aafba97 100644 Binary files a/frontend/src/assets/iconfont/iconfont.woff2 and b/frontend/src/assets/iconfont/iconfont.woff2 differ diff --git a/frontend/src/components/app-status/index.vue b/frontend/src/components/app-status/index.vue index 30b3d3b97..b563982ba 100644 --- a/frontend/src/components/app-status/index.vue +++ b/frontend/src/components/app-status/index.vue @@ -30,7 +30,7 @@ > {{ $t('commons.operate.restart') }} - + { - console.log(params); upgradeInfo.value = params.upgradeInfo; upgradeVersion.value = params.upgradeVersion; drawerVisible.value = true; diff --git a/frontend/src/lang/modules/en.ts b/frontend/src/lang/modules/en.ts index 820b3dc0a..17255a35c 100644 --- a/frontend/src/lang/modules/en.ts +++ b/frontend/src/lang/modules/en.ts @@ -9,6 +9,7 @@ const message = { lingxia: 'Lingxia', colon: ': ', button: { + run: 'Run', prev: 'Previous', next: 'Next', create: 'Create ', @@ -333,6 +334,7 @@ const message = { firewall: 'Firewall', ssl: 'Certificate', database: 'Database', + aiTools: 'AI', container: 'Container', cronjob: 'Cronjob', host: 'Host', @@ -589,6 +591,67 @@ const message = { remoteConnHelper2: 'Use this address for non-container or external connections', localIP: 'Local IP', }, + aiTools: { + model: { + model: 'Model', + create: 'Add Model', + create_helper: 'Pull "{0}"', + ollama_doc: 'You can visit the Ollama official website to search and find more models.', + container_conn_helper: 'Use this address for inter-container access or connection', + ollama_sync: 'Syncing Ollama model found the following models do not exist, do you want to delete them?', + from_remote: 'This model was not downloaded via 1Panel, no related pull logs.', + no_logs: 'The pull logs for this model have been deleted and cannot be viewed.', + }, + proxy: { + proxy: 'AI Proxy Enhancement', + proxyHelper1: 'Bind domain and enable HTTPS for enhanced transmission security', + proxyHelper2: 'Limit IP access to prevent exposure on the public internet', + proxyHelper3: 'Enable streaming', + proxyHelper4: 'Once created, you can view and manage it in the website list', + proxyHelper5: + 'After enabling, you can disable external access to the port in the App Store - Installed - Ollama - Parameters to improve security.', + proxyHelper6: 'To disable proxy configuration, you can delete it from the website list.', + whiteListHelper: 'Restrict access to only IPs in the whitelist', + }, + gpu: { + gpu: 'GPU Monitor', + base: 'Basic Information', + gpuHelper: 'NVIDIA-SMI or XPU-SMI command not detected on the current system. Please check and try again!', + driverVersion: 'Driver Version', + cudaVersion: 'CUDA Version', + process: 'Process Information', + type: 'Type', + typeG: 'Graphics', + typeC: 'Compute', + typeCG: 'Compute + Graphics', + processName: 'Process Name', + processMemoryUsage: 'Memory Usage', + temperatureHelper: 'High GPU temperature can cause GPU frequency throttling', + performanceStateHelper: 'From P0 (maximum performance) to P12 (minimum performance)', + busID: 'Bus ID', + persistenceMode: 'Persistence Mode', + enabled: 'Enabled', + disabled: 'Disabled', + persistenceModeHelper: + 'Persistence mode allows quicker task responses but increases standby power consumption.', + displayActive: 'Graphics Card Initialized', + displayActiveT: 'Yes', + displayActiveF: 'No', + ecc: 'Error Correction and Check Technology', + computeMode: 'Compute Mode', + default: 'Default', + exclusiveProcess: 'Exclusive Process', + exclusiveThread: 'Exclusive Thread', + prohibited: 'Prohibited', + defaultHelper: 'Default: Processes can execute concurrently', + exclusiveProcessHelper: + 'Exclusive Process: Only one CUDA context can use the GPU, but can be shared by multiple threads', + exclusiveThreadHelper: 'Exclusive Thread: Only one thread in a CUDA context can use the GPU', + prohibitedHelper: 'Prohibited: Processes are not allowed to execute simultaneously', + migModeHelper: 'Used to create MIG instances for physical isolation of the GPU at the user level.', + migModeNA: 'Not Supported', + }, + }, container: { create: 'Create', createByCommand: 'Create by command', @@ -1807,7 +1870,6 @@ const message = { waf: 'Upgrading to the professional version can provide features such as interception map, logs, block records, geographical location blocking, custom rules, custom interception pages, etc.', tamper: 'Upgrading to the professional version can protect websites from unauthorized modifications or tampering.', tamperHelper: 'Operation failed, the file or folder has tamper protection enabled. Please check and try again!', - gpu: 'Upgrading to the professional version can help users visually monitor important parameters of GPU such as workload, temperature, memory usage in real time.', setting: 'Upgrading to the professional version allows customization of panel logo, welcome message, and other information.', monitor: @@ -2995,44 +3057,6 @@ const message = { disableHelper: 'The anti-tampering function of website {0} is about to be disabled. Do you want to continue?', }, - gpu: { - gpu: 'GPU Monitor', - base: 'Basic Information', - gpuHelper: 'NVIDIA-SMI or XPU-SMI command not detected on the current system. Please check and try again!', - driverVersion: 'Driver Version', - cudaVersion: 'CUDA Version', - process: 'Process Information', - type: 'Type', - typeG: 'Graphics', - typeC: 'Compute', - typeCG: 'Compute + Graphics', - processName: 'Process Name', - processMemoryUsage: 'Memory Usage', - temperatureHelper: 'High GPU temperature can cause GPU frequency throttling', - performanceStateHelper: 'From P0 (maximum performance) to P12 (minimum performance)', - busID: 'Bus ID', - persistenceMode: 'Persistence Mode', - enabled: 'Enabled', - disabled: 'Disabled', - persistenceModeHelper: - 'Persistence mode allows quicker task responses but increases standby power consumption.', - displayActive: 'Graphics Card Initialized', - displayActiveT: 'Yes', - displayActiveF: 'No', - ecc: 'Error Correction and Check Technology', - computeMode: 'Compute Mode', - default: 'Default', - exclusiveProcess: 'Exclusive Process', - exclusiveThread: 'Exclusive Thread', - prohibited: 'Prohibited', - defaultHelper: 'Default: Processes can execute concurrently', - exclusiveProcessHelper: - 'Exclusive Process: Only one CUDA context can use the GPU, but can be shared by multiple threads', - exclusiveThreadHelper: 'Exclusive Thread: Only one thread in a CUDA context can use the GPU', - prohibitedHelper: 'Prohibited: Processes are not allowed to execute simultaneously', - migModeHelper: 'Used to create MIG instances for physical isolation of the GPU at the user level.', - migModeNA: 'Not Supported', - }, setting: { setting: 'Panel Settings', title: 'Panel Description', @@ -3076,11 +3100,6 @@ const message = { tamperContent4: 'Record file access and operation logs for subsequent auditing and analysis by administrators, as well as to identify potential security threats.', - gpuTitle1: 'Overview Monitoring', - gpuContent1: 'Display the current GPU usage on the overview page.', - gpuTitle2: 'GPU Details', - gpuContent2: 'Show GPU parameters in finer detail.', - settingTitle1: 'Custom Welcome Message', settingContent1: 'Set a custom welcome message on the 1Panel login page.', settingTitle2: 'Custom Logo', diff --git a/frontend/src/lang/modules/ja.ts b/frontend/src/lang/modules/ja.ts index 1d224a3fe..0b06ccfde 100644 --- a/frontend/src/lang/modules/ja.ts +++ b/frontend/src/lang/modules/ja.ts @@ -9,6 +9,7 @@ const message = { fit2cloud: 'FIT2CLOUD', lingxia: 'Lingxia', button: { + run: '実行', create: '作成する', add: '追加', save: '保存', @@ -327,6 +328,7 @@ const message = { firewall: 'ファイアウォール', ssl: '証明書|証明書', database: 'データベース|データベース', + aiTools: 'AI', container: 'コンテナ|コンテナ', cronjob: 'クロンジョブ|クロンの仕事', host: 'ホスト|ホスト', @@ -581,6 +583,67 @@ const message = { 'この接続アドレスは、非コンテナまたは外部アプリケーションで実行されているアプリケーションで使用できます。', localIP: 'ローカルIP', }, + aiTools: { + model: { + model: 'モデル', + create: 'モデルを追加', + create_helper: 'を取得 "{0}"', + ollama_doc: 'Ollama の公式ウェブサイトを訪れて、さらに多くのモデルを検索して見つけることができます。', + container_conn_helper: 'コンテナ間のアクセスまたは接続にこのアドレスを使用', + ollama_sync: 'Ollamaモデルの同期中に、以下のモデルが存在しないことが判明しました。削除しますか?', + from_remote: 'このモデルは1Panelを介してダウンロードされておらず、関連するプルログはありません。', + no_logs: 'このモデルのプルログは削除されており、関連するログを表示できません。', + }, + proxy: { + proxy: 'AI プロキシ強化', + proxyHelper1: 'ドメインをバインドし、HTTPS を有効にして通信のセキュリティを強化', + proxyHelper2: 'IP アクセスを制限し、パブリックインターネットでの露出を防止', + proxyHelper3: 'ストリーミングを有効にする', + proxyHelper4: '作成後、ウェブサイトリストで確認および管理できます', + proxyHelper5: + '有効にすると、アプリストア - インストール済み - Ollama - パラメータでポートの外部アクセスを無効にし、セキュリティを向上させることができます。', + proxyHelper6: 'プロキシ設定を無効にするには、ウェブサイトリストから削除できます。', + whiteListHelper: 'ホワイトリスト内のIPのみアクセスを許可する', + }, + gpu: { + gpu: 'GPUモニター', + base: '基本情報', + gpuHelper: + '現在のシステムでNVIDIA-SMIまたはXPU-SMIコマンドが検出されませんでした。確認して再試行してください!', + driverVersion: 'ドライバーバージョン', + cudaVersion: 'CUDAバージョン', + process: 'プロセス情報', + type: 'タイプ', + typeG: 'グラフィックス', + typeC: 'コンピュート', + typeCG: 'コンピュート + グラフィックス', + processName: 'プロセス名', + processMemoryUsage: 'メモリ使用量', + temperatureHelper: '高いGPU温度はGPUの周波数制限を引き起こす可能性があります', + performanceStateHelper: 'P0(最大性能)からP12(最小性能)まで', + busID: 'バスID', + persistenceMode: '永続モード', + enabled: '有効', + disabled: '無効', + persistenceModeHelper: '永続モードはタスクの応答速度を速くしますが、待機時の消費電力が増加します。', + displayActive: 'グラフィックカード初期化済み', + displayActiveT: 'はい', + displayActiveF: 'いいえ', + ecc: 'エラー訂正およびチェック技術', + computeMode: 'コンピュートモード', + default: 'デフォルト', + exclusiveProcess: '専用プロセス', + exclusiveThread: '専用スレッド', + prohibited: '禁止', + defaultHelper: 'デフォルト:プロセスは並行して実行できます', + exclusiveProcessHelper: + '専用プロセス:1つのCUDAコンテキストのみがGPUを使用できますが、複数のスレッドで共有できます', + exclusiveThreadHelper: '専用スレッド:CUDAコンテキスト内の1つのスレッドのみがGPUを使用できます', + prohibitedHelper: '禁止:プロセスは同時に実行できません', + migModeHelper: 'ユーザーレベルでGPUの物理的分離を行うためのMIGインスタンスを作成するために使用されます。', + migModeNA: 'サポートされていません', + }, + }, container: { create: 'コンテナを作成します', edit: 'コンテナを編集します', @@ -1670,7 +1733,6 @@ const message = { introduce: '機能の紹介', waf: 'プロフェッショナルバージョンにアップグレードすると、インターセプトマップ、ログ、ブロックレコード、地理的位置ブロッキング、カスタムルール、カスタムインターセプトページなどの機能を提供できます。', tamper: 'プロのバージョンにアップグレードすると、不正な変更や改ざんからWebサイトを保護できます。', - gpu: 'プロのバージョンにアップグレードすることで、ユーザーはワークロード、温度、メモリ使用量などのGPUの重要なパラメーターをリアルタイムで視覚的に監視するのに役立ちます。', setting: 'プロのバージョンにアップグレードすることで、パネルロゴ、ウェルカムメッセージ、その他の情報のカスタマイズが可能になります。', monitor: @@ -2806,44 +2868,6 @@ const message = { 'ウェブサイト {0} の改ざん防止機能が有効になろうとしています。セキュリティを強化するために続行しますか?', disableHelper: 'ウェブサイト {0} の改ざん防止機能が無効になろうとしています。続行しますか?', }, - gpu: { - gpu: 'GPUモニター', - base: '基本情報', - gpuHelper: - '現在のシステムでNVIDIA-SMIまたはXPU-SMIコマンドが検出されませんでした。確認して再試行してください!', - driverVersion: 'ドライバーバージョン', - cudaVersion: 'CUDAバージョン', - process: 'プロセス情報', - type: 'タイプ', - typeG: 'グラフィックス', - typeC: 'コンピュート', - typeCG: 'コンピュート + グラフィックス', - processName: 'プロセス名', - processMemoryUsage: 'メモリ使用量', - temperatureHelper: '高いGPU温度はGPUの周波数制限を引き起こす可能性があります', - performanceStateHelper: 'P0(最大性能)からP12(最小性能)まで', - busID: 'バスID', - persistenceMode: '永続モード', - enabled: '有効', - disabled: '無効', - persistenceModeHelper: '永続モードはタスクの応答速度を速くしますが、待機時の消費電力が増加します。', - displayActive: 'グラフィックカード初期化済み', - displayActiveT: 'はい', - displayActiveF: 'いいえ', - ecc: 'エラー訂正およびチェック技術', - computeMode: 'コンピュートモード', - default: 'デフォルト', - exclusiveProcess: '専用プロセス', - exclusiveThread: '専用スレッド', - prohibited: '禁止', - defaultHelper: 'デフォルト:プロセスは並行して実行できます', - exclusiveProcessHelper: - '専用プロセス:1つのCUDAコンテキストのみがGPUを使用できますが、複数のスレッドで共有できます', - exclusiveThreadHelper: '専用スレッド:CUDAコンテキスト内の1つのスレッドのみがGPUを使用できます', - prohibitedHelper: '禁止:プロセスは同時に実行できません', - migModeHelper: 'ユーザーレベルでGPUの物理的分離を行うためのMIGインスタンスを作成するために使用されます。', - migModeNA: 'サポートされていません', - }, setting: { setting: 'パネル設定', title: 'パネルの説明', @@ -2887,10 +2911,6 @@ const message = { tamperTitle4: 'ログ記録と分析', tamperContent4: 'ファイルのアクセスおよび操作ログを記録し、後の監査および分析に使用、また潜在的なセキュリティ脅威を特定。', - gpuTitle1: '概要ページモニタリング', - gpuContent1: '概要ページでGPUの現在の使用状況を表示します。', - gpuTitle2: 'GPU詳細情報', - gpuContent2: 'GPUの各パラメータをより詳細に表示します。', settingTitle1: 'カスタムウェルカムメッセージ', settingContent1: '1Panelのログインページにカスタムウェルカムメッセージを設定。', diff --git a/frontend/src/lang/modules/ko.ts b/frontend/src/lang/modules/ko.ts index befbb5fae..8996281ae 100644 --- a/frontend/src/lang/modules/ko.ts +++ b/frontend/src/lang/modules/ko.ts @@ -9,6 +9,7 @@ const message = { fit2cloud: 'FIT2CLOUD', lingxia: 'Lingxia', button: { + run: '실행', create: '생성', add: '추가', save: '저장', @@ -328,6 +329,7 @@ const message = { firewall: '방화벽', ssl: '인증서 | 인증서들', database: '데이터베이스 | 데이터베이스들', + aiTools: 'AI', container: '컨테이너 | 컨테이너들', cronjob: '크론 작업 | 크론 작업들', host: '호스트 | 호스트들', @@ -577,6 +579,66 @@ const message = { '이 연결 주소는 컨테이너 외부 또는 외부 애플리케이션에서 실행 중인 애플리케이션에서 사용할 수 있습니다.', localIP: '로컬 IP', }, + aiTools: { + model: { + model: '모델', + create: '모델 추가', + create_helper: '가져오기 "{0}"', + ollama_doc: 'Ollama 공식 웹사이트를 방문하여 더 많은 모델을 검색하고 찾을 수 있습니다.', + container_conn_helper: '컨테이너 간 접근 또는 연결에 이 주소를 사용', + ollama_sync: 'Ollama 모델 동기화 중 다음 모델이 존재하지 않음을 발견했습니다. 삭제하시겠습니까?', + from_remote: '이 모델은 1Panel을 통해 다운로드되지 않았으며 관련 풀 로그가 없습니다.', + no_logs: '이 모델의 풀 로그가 삭제되어 관련 로그를 볼 수 없습니다.', + }, + proxy: { + proxy: 'AI 프록시 강화', + proxyHelper1: '도메인을 바인딩하고 HTTPS를 활성화하여 전송 보안을 강화', + proxyHelper2: 'IP 접근을 제한하여 공용 인터넷에서의 노출을 방지', + proxyHelper3: '스트리밍을 활성화', + proxyHelper4: '생성 후, 웹사이트 목록에서 이를 보고 관리할 수 있습니다', + proxyHelper5: + '활성화한 후, 앱 스토어 - 설치됨 - Ollama - 매개변수에서 포트 외부 접근을 비활성화하여 보안을 강화할 수 있습니다.', + proxyHelper6: '프록시 구성을 비활성화하려면 웹사이트 목록에서 삭제할 수 있습니다.', + whiteListHelper: '화이트리스트에 있는 IP만 접근 허용', + }, + gpu: { + gpu: 'GPU 모니터', + base: '기본 정보', + gpuHelper: '현재 시스템에서 NVIDIA-SMI 또는 XPU-SMI 명령이 감지되지 않았습니다. 확인 후 다시 시도하세요!', + driverVersion: '드라이버 버전', + cudaVersion: 'CUDA 버전', + process: '프로세스 정보', + type: '유형', + typeG: '그래픽', + typeC: '연산', + typeCG: '연산 + 그래픽', + processName: '프로세스 이름', + processMemoryUsage: '메모리 사용량', + temperatureHelper: 'GPU 온도가 높으면 GPU 주파수 제한이 발생할 수 있습니다.', + performanceStateHelper: 'P0(최대 성능)부터 P12(최소 성능)까지', + busID: '버스 ID', + persistenceMode: '지속 모드', + enabled: '활성화됨', + disabled: '비활성화됨', + persistenceModeHelper: '지속 모드는 작업 응답 속도를 빠르게 하지만 대기 전력 소비를 증가시킵니다.', + displayActive: '그래픽 카드 초기화됨', + displayActiveT: '예', + displayActiveF: '아니요', + ecc: '오류 감지 및 수정 기술', + computeMode: '연산 모드', + default: '기본값', + exclusiveProcess: '단독 프로세스', + exclusiveThread: '단독 스레드', + prohibited: '금지됨', + defaultHelper: '기본값: 프로세스가 동시에 실행될 수 있음', + exclusiveProcessHelper: + '단독 프로세스: 하나의 CUDA 컨텍스트만 GPU 를 사용할 수 있지만, 여러 스레드에서 공유 가능', + exclusiveThreadHelper: '단독 스레드: CUDA 컨텍스트의 하나의 스레드만 GPU 를 사용할 수 있음', + prohibitedHelper: '금지됨: 프로세스가 동시에 실행되는 것이 허용되지 않음', + migModeHelper: '사용자 수준에서 GPU 를 물리적으로 분리하는 MIG 인스턴스를 생성하는 데 사용됩니다.', + migModeNA: '지원되지 않음', + }, + }, container: { create: '컨테이너 만들기', edit: '컨테이너 편집', @@ -1643,7 +1705,6 @@ const message = { introduce: '기능 소개', waf: '전문 버전으로 업그레이드하면 차단 맵, 로그, 차단 기록, 지리적 위치 차단, 사용자 정의 규칙, 사용자 정의 차단 페이지 등의 기능을 제공받을 수 있습니다.', tamper: '전문 버전으로 업그레이드하면 웹사이트를 무단 수정이나 변조로부터 보호할 수 있습니다.', - gpu: '전문 버전으로 업그레이드하면 GPU 의 작업 부하, 온도, 메모리 사용량 등 중요한 매개변수를 실시간으로 시각적으로 모니터링할 수 있습니다.', setting: '전문 버전으로 업그레이드하면 패널 로고, 환영 메시지 등 정보를 사용자 정의할 수 있습니다.', monitor: '전문 버전으로 업그레이드하면 웹사이트의 실시간 상태, 방문자 트렌드, 방문자 출처, 요청 로그 등 정보를 확인할 수 있습니다.', @@ -2763,43 +2824,6 @@ const message = { '웹사이트 {0}의 방지 조작 기능을 활성화하여 웹사이트 보안을 강화하려고 합니다. 계속하시겠습니까?', disableHelper: '웹사이트 {0}의 방지 조작 기능을 비활성화하려고 합니다. 계속하시겠습니까?', }, - gpu: { - gpu: 'GPU 모니터', - base: '기본 정보', - gpuHelper: '현재 시스템에서 NVIDIA-SMI 또는 XPU-SMI 명령이 감지되지 않았습니다. 확인 후 다시 시도하세요!', - driverVersion: '드라이버 버전', - cudaVersion: 'CUDA 버전', - process: '프로세스 정보', - type: '유형', - typeG: '그래픽', - typeC: '연산', - typeCG: '연산 + 그래픽', - processName: '프로세스 이름', - processMemoryUsage: '메모리 사용량', - temperatureHelper: 'GPU 온도가 높으면 GPU 주파수 제한이 발생할 수 있습니다.', - performanceStateHelper: 'P0(최대 성능)부터 P12(최소 성능)까지', - busID: '버스 ID', - persistenceMode: '지속 모드', - enabled: '활성화됨', - disabled: '비활성화됨', - persistenceModeHelper: '지속 모드는 작업 응답 속도를 빠르게 하지만 대기 전력 소비를 증가시킵니다.', - displayActive: '그래픽 카드 초기화됨', - displayActiveT: '예', - displayActiveF: '아니요', - ecc: '오류 감지 및 수정 기술', - computeMode: '연산 모드', - default: '기본값', - exclusiveProcess: '단독 프로세스', - exclusiveThread: '단독 스레드', - prohibited: '금지됨', - defaultHelper: '기본값: 프로세스가 동시에 실행될 수 있음', - exclusiveProcessHelper: - '단독 프로세스: 하나의 CUDA 컨텍스트만 GPU 를 사용할 수 있지만, 여러 스레드에서 공유 가능', - exclusiveThreadHelper: '단독 스레드: CUDA 컨텍스트의 하나의 스레드만 GPU 를 사용할 수 있음', - prohibitedHelper: '금지됨: 프로세스가 동시에 실행되는 것이 허용되지 않음', - migModeHelper: '사용자 수준에서 GPU 를 물리적으로 분리하는 MIG 인스턴스를 생성하는 데 사용됩니다.', - migModeNA: '지원되지 않음', - }, setting: { setting: '패널 설정', title: '패널 설명', @@ -2840,11 +2864,6 @@ const message = { tamperContent4: '파일 접근 및 작업 로그를 기록하여 관리자가 감사 및 분석을 수행할 수 있도록 하고, 잠재적 보안 위협을 식별합니다.', - gpuTitle1: '개요 모니터링', - gpuContent1: '개요 페이지에서 현재 GPU 사용량을 표시합니다.', - gpuTitle2: 'GPU 세부 정보', - gpuContent2: 'GPU 매개변수를 더 세부적으로 표시합니다.', - settingTitle1: '사용자 정의 환영 메시지', settingContent1: '1Panel 로그인 페이지에 사용자 정의 환영 메시지를 설정합니다.', settingTitle2: '사용자 정의 로고', diff --git a/frontend/src/lang/modules/ms.ts b/frontend/src/lang/modules/ms.ts index fdb83ef2d..bc748eab3 100644 --- a/frontend/src/lang/modules/ms.ts +++ b/frontend/src/lang/modules/ms.ts @@ -9,6 +9,7 @@ const message = { fit2cloud: 'FIT2CLOUD', lingxia: 'Lingxia', button: { + run: 'Jalankan', create: 'Cipta', add: 'Tambah', save: 'Simpan', @@ -334,6 +335,7 @@ const message = { firewall: 'Firewall', ssl: 'Certificate | Certificates', database: 'Database | Databases', + aiTools: 'AI', container: 'Container | Containers', cronjob: 'Cron Job | Cron Jobs', host: 'Host | Hosts', @@ -592,6 +594,68 @@ const message = { 'Alamat sambungan ini boleh digunakan oleh aplikasi yang berjalan di luar kontena atau aplikasi luaran.', localIP: 'IP Tempatan', }, + aiTools: { + model: { + model: 'Model', + create: 'Tambah Model', + create_helper: 'Tarik "{0}"', + ollama_doc: 'Anda boleh melawat laman web rasmi Ollama untuk mencari dan menemui lebih banyak model.', + container_conn_helper: 'Gunakan alamat ini untuk akses atau sambungan antara kontena', + ollama_sync: + 'Sincronizando o modelo Ollama, encontrou que os seguintes modelos não existem, deseja excluí-los?', + from_remote: 'Este modelo não foi baixado via 1Panel, sem logs de pull relacionados.', + no_logs: 'Os logs de pull deste modelo foram excluídos e não podem ser visualizados.', + }, + proxy: { + proxy: 'Peningkatan Proksi AI', + proxyHelper1: 'Ikatkan domain dan aktifkan HTTPS untuk meningkatkan keselamatan penghantaran', + proxyHelper2: 'Hadkan akses IP untuk mengelakkan pendedahan di internet awam', + proxyHelper3: 'Aktifkan penstriman', + proxyHelper4: 'Setelah selesai, anda boleh melihat dan mengurusnya dalam senarai laman web', + proxyHelper5: + 'Selepas diaktifkan, anda boleh melumpuhkan akses luaran ke port dalam App Store - Dipasang - Ollama - Parameter untuk meningkatkan keselamatan.', + proxyHelper6: 'Untuk melumpuhkan konfigurasi proksi, anda boleh memadamnya dari senarai laman web.', + whiteListHelper: 'Hadkan akses kepada hanya IP dalam senarai putih', + }, + gpu: { + gpu: 'Monitor GPU', + base: 'Maklumat Asas', + gpuHelper: 'Perintah NVIDIA-SMI atau XPU-SMI tidak dikesan pada sistem semasa. Sila periksa dan cuba lagi!', + driverVersion: 'Versi Pemacu', + cudaVersion: 'Versi CUDA', + process: 'Maklumat Proses', + type: 'Jenis', + typeG: 'Grafik', + typeC: 'Pengiraan', + typeCG: 'Pengiraan + Grafik', + processName: 'Nama Proses', + processMemoryUsage: 'Penggunaan Memori', + temperatureHelper: 'Suhu GPU yang tinggi boleh menyebabkan pelambatan frekuensi GPU', + performanceStateHelper: 'Dari P0 (prestasi maksimum) hingga P12 (prestasi minimum)', + busID: 'ID Bas', + persistenceMode: 'Mod Ketekalan', + enabled: 'Diaktifkan', + disabled: 'Dilumpuhkan', + persistenceModeHelper: + 'Mod ketekalan membolehkan respons tugas lebih cepat tetapi meningkatkan penggunaan kuasa sedia.', + displayActive: 'Kad Grafik Dimulakan', + displayActiveT: 'Ya', + displayActiveF: 'Tidak', + ecc: 'Teknologi Pemeriksaan dan Pembetulan Ralat', + computeMode: 'Mod Pengiraan', + default: 'Asal', + exclusiveProcess: 'Proses Eksklusif', + exclusiveThread: 'Thread Eksklusif', + prohibited: 'Dilarang', + defaultHelper: 'Asal: Proses boleh dilaksanakan secara serentak', + exclusiveProcessHelper: + 'Proses Eksklusif: Hanya satu konteks CUDA boleh menggunakan GPU, tetapi boleh dikongsi oleh berbilang thread', + exclusiveThreadHelper: 'Thread Eksklusif: Hanya satu thread dalam konteks CUDA boleh menggunakan GPU', + prohibitedHelper: 'Dilarang: Proses tidak dibenarkan dilaksanakan serentak', + migModeHelper: 'Digunakan untuk membuat contoh MIG bagi pengasingan fizikal GPU pada tahap pengguna.', + migModeNA: 'Tidak Disokong', + }, + }, container: { create: 'Cipta kontena', edit: 'Sunting kontena', @@ -1726,7 +1790,6 @@ const message = { introduce: 'Pengenalan Ciri', waf: 'Menaik taraf ke versi profesional boleh menyediakan ciri seperti peta pencegahan, log, rekod blok, sekatan lokasi geografi, peraturan tersuai, halaman pencegahan tersuai, dan sebagainya.', tamper: 'Menaik taraf ke versi profesional boleh melindungi laman web daripada pengubahsuaian atau manipulasi tanpa kebenaran.', - gpu: 'Menaik taraf ke versi profesional boleh membantu pengguna memantau parameter penting GPU secara visual seperti beban kerja, suhu, penggunaan memori secara masa nyata.', setting: 'Menaik taraf ke versi profesional membolehkan penyesuaian logo panel, mesej selamat datang, dan maklumat lain.', monitor: @@ -2871,44 +2934,6 @@ const message = { 'Ciri anti-pemalsuan laman web {0} akan diaktifkan untuk meningkatkan keselamatan laman web. Adakah anda mahu meneruskan?', disableHelper: 'Ciri anti-pemalsuan laman web {0} akan dilumpuhkan. Adakah anda mahu meneruskan?', }, - gpu: { - gpu: 'Monitor GPU', - base: 'Maklumat Asas', - gpuHelper: 'Perintah NVIDIA-SMI atau XPU-SMI tidak dikesan pada sistem semasa. Sila periksa dan cuba lagi!', - driverVersion: 'Versi Pemacu', - cudaVersion: 'Versi CUDA', - process: 'Maklumat Proses', - type: 'Jenis', - typeG: 'Grafik', - typeC: 'Pengiraan', - typeCG: 'Pengiraan + Grafik', - processName: 'Nama Proses', - processMemoryUsage: 'Penggunaan Memori', - temperatureHelper: 'Suhu GPU yang tinggi boleh menyebabkan pelambatan frekuensi GPU', - performanceStateHelper: 'Dari P0 (prestasi maksimum) hingga P12 (prestasi minimum)', - busID: 'ID Bas', - persistenceMode: 'Mod Ketekalan', - enabled: 'Diaktifkan', - disabled: 'Dilumpuhkan', - persistenceModeHelper: - 'Mod ketekalan membolehkan respons tugas lebih cepat tetapi meningkatkan penggunaan kuasa sedia.', - displayActive: 'Kad Grafik Dimulakan', - displayActiveT: 'Ya', - displayActiveF: 'Tidak', - ecc: 'Teknologi Pemeriksaan dan Pembetulan Ralat', - computeMode: 'Mod Pengiraan', - default: 'Asal', - exclusiveProcess: 'Proses Eksklusif', - exclusiveThread: 'Thread Eksklusif', - prohibited: 'Dilarang', - defaultHelper: 'Asal: Proses boleh dilaksanakan secara serentak', - exclusiveProcessHelper: - 'Proses Eksklusif: Hanya satu konteks CUDA boleh menggunakan GPU, tetapi boleh dikongsi oleh berbilang thread', - exclusiveThreadHelper: 'Thread Eksklusif: Hanya satu thread dalam konteks CUDA boleh menggunakan GPU', - prohibitedHelper: 'Dilarang: Proses tidak dibenarkan dilaksanakan serentak', - migModeHelper: 'Digunakan untuk membuat contoh MIG bagi pengasingan fizikal GPU pada tahap pengguna.', - migModeNA: 'Tidak Disokong', - }, setting: { setting: 'Tetapan Panel', title: 'Deskripsi Panel', @@ -2952,11 +2977,6 @@ const message = { tamperContent4: 'Rekod log akses dan operasi fail untuk audit dan analisis selanjutnya oleh pentadbir serta mengenal pasti potensi ancaman keselamatan.', - gpuTitle1: 'Pemantauan Gambaran Keseluruhan', - gpuContent1: 'Papar penggunaan semasa GPU pada halaman gambaran keseluruhan.', - gpuTitle2: 'Butiran GPU', - gpuContent2: 'Tunjukkan parameter GPU dengan lebih terperinci.', - settingTitle1: 'Mesej Selamat Datang Tersuai', settingContent1: 'Tetapkan mesej selamat datang tersuai pada halaman log masuk 1Panel.', settingTitle2: 'Logo Tersuai', diff --git a/frontend/src/lang/modules/pt-br.ts b/frontend/src/lang/modules/pt-br.ts index d1a1c67f6..91822ca82 100644 --- a/frontend/src/lang/modules/pt-br.ts +++ b/frontend/src/lang/modules/pt-br.ts @@ -9,6 +9,7 @@ const message = { fit2cloud: 'FIT2CLOUD', lingxia: 'Lingxia', button: { + run: 'Executar', create: 'Criar', add: 'Adicionar', save: 'Salvar', @@ -332,6 +333,7 @@ const message = { firewall: 'Firewall', ssl: 'Certificado | Certificados', database: 'Banco de Dados | Bancos de Dados', + aiTools: 'AI', container: 'Container | Containers', cronjob: 'Tarefa Cron | Tarefas Cron', host: 'Host | Hosts', @@ -589,6 +591,67 @@ const message = { 'Este endereço de conexão pode ser utilizado por aplicações que estão fora do contêiner ou por aplicações externas.', localIP: 'IP local', }, + aiTools: { + model: { + model: 'Modelo', + create: 'Adicionar Modelo', + create_helper: 'Puxar "{0}"', + ollama_doc: 'Você pode visitar o site oficial da Ollama para pesquisar e encontrar mais modelos.', + container_conn_helper: 'Use este endereço para acesso ou conexão entre contêineres', + ollama_sync: + 'Menyelaraskan model Ollama mendapati model berikut tidak wujud, adakah anda ingin memadamnya?', + from_remote: 'Model ini tidak dimuat turun melalui 1Panel, tiada log pengambilan berkaitan.', + no_logs: 'Log pengambilan untuk model ini telah dipadam dan tidak dapat dilihat.', + }, + proxy: { + proxy: 'Melhoria de Proxy AI', + proxyHelper1: 'Vincule o domínio e habilite o HTTPS para aumentar a segurança na transmissão', + proxyHelper2: 'Limite o acesso por IP para evitar exposição na internet pública', + proxyHelper3: 'Habilite a transmissão em fluxo', + proxyHelper4: 'Após a criação, você pode visualizar e gerenciar no lista de sites', + proxyHelper6: 'Para desativar a configuração de proxy, você pode excluí-la da lista de sites.', + whiteListHelper: 'Restringir o acesso apenas aos IPs na lista branca', + }, + gpu: { + gpu: 'Monitor de GPU', + base: 'Informações Básicas', + gpuHelper: + 'Comando NVIDIA-SMI ou XPU-SMI não detectado no sistema atual. Por favor, verifique e tente novamente!', + driverVersion: 'Versão do Driver', + cudaVersion: 'Versão do CUDA', + process: 'Informações do Processo', + type: 'Tipo', + typeG: 'Gráficos', + typeC: 'Cálculo', + typeCG: 'Cálculo + Gráficos', + processName: 'Nome do Processo', + processMemoryUsage: 'Uso de Memória', + temperatureHelper: 'Temperaturas altas da GPU podem causar limitação de frequência da GPU.', + performanceStateHelper: 'De P0 (máximo desempenho) a P12 (mínimo desempenho).', + busID: 'ID do Barramento', + persistenceMode: 'Modo de Persistência', + enabled: 'Ativado', + disabled: 'Desativado', + persistenceModeHelper: + 'O modo de persistência permite respostas mais rápidas às tarefas, mas aumenta o consumo de energia em standby.', + displayActive: 'Placa Gráfica Inicializada', + displayActiveT: 'Sim', + displayActiveF: 'Não', + ecc: 'Tecnologia de Correção e Verificação de Erros', + computeMode: 'Modo de Cálculo', + default: 'Padrão', + exclusiveProcess: 'Processo Exclusivo', + exclusiveThread: 'Thread Exclusivo', + prohibited: 'Proibido', + defaultHelper: 'Padrão: Processos podem ser executados simultaneamente.', + exclusiveProcessHelper: + 'Processo Exclusivo: Apenas um contexto CUDA pode usar a GPU, mas pode ser compartilhado por múltiplas threads.', + exclusiveThreadHelper: 'Thread Exclusivo: Apenas uma thread em um contexto CUDA pode usar a GPU.', + prohibitedHelper: 'Proibido: Não é permitido que processos sejam executados simultaneamente.', + migModeHelper: 'Usado para criar instâncias MIG para isolamento físico da GPU no nível do usuário.', + migModeNA: 'Não Suportado', + }, + }, container: { create: 'Criar contêiner', edit: 'Editar contêiner', @@ -1714,7 +1777,6 @@ const message = { introduce: 'Introdução de recursos', waf: 'O upgrade para a versão profissional pode fornecer recursos como mapa de intercepção, logs, registros de bloqueio, bloqueio por localização geográfica, regras personalizadas, páginas de intercepção personalizadas, etc.', tamper: 'O upgrade para a versão profissional pode proteger sites contra modificações ou adulterações não autorizadas.', - gpu: 'O upgrade para a versão profissional pode ajudar os usuários a monitorar visualmente parâmetros importantes da GPU, como carga de trabalho, temperatura e uso de memória em tempo real.', setting: 'O upgrade para a versão profissional permite a personalização do logo do painel, mensagem de boas-vindas e outras informações.', monitor: @@ -2875,45 +2937,6 @@ const message = { 'A função de anti-alteração do site {0} está prestes a ser ativada para aumentar a segurança do site. Deseja continuar?', disableHelper: 'A função de anti-alteração do site {0} está prestes a ser desativada. Deseja continuar?', }, - gpu: { - gpu: 'Monitor de GPU', - base: 'Informações Básicas', - gpuHelper: - 'Comando NVIDIA-SMI ou XPU-SMI não detectado no sistema atual. Por favor, verifique e tente novamente!', - driverVersion: 'Versão do Driver', - cudaVersion: 'Versão do CUDA', - process: 'Informações do Processo', - type: 'Tipo', - typeG: 'Gráficos', - typeC: 'Cálculo', - typeCG: 'Cálculo + Gráficos', - processName: 'Nome do Processo', - processMemoryUsage: 'Uso de Memória', - temperatureHelper: 'Temperaturas altas da GPU podem causar limitação de frequência da GPU.', - performanceStateHelper: 'De P0 (máximo desempenho) a P12 (mínimo desempenho).', - busID: 'ID do Barramento', - persistenceMode: 'Modo de Persistência', - enabled: 'Ativado', - disabled: 'Desativado', - persistenceModeHelper: - 'O modo de persistência permite respostas mais rápidas às tarefas, mas aumenta o consumo de energia em standby.', - displayActive: 'Placa Gráfica Inicializada', - displayActiveT: 'Sim', - displayActiveF: 'Não', - ecc: 'Tecnologia de Correção e Verificação de Erros', - computeMode: 'Modo de Cálculo', - default: 'Padrão', - exclusiveProcess: 'Processo Exclusivo', - exclusiveThread: 'Thread Exclusivo', - prohibited: 'Proibido', - defaultHelper: 'Padrão: Processos podem ser executados simultaneamente.', - exclusiveProcessHelper: - 'Processo Exclusivo: Apenas um contexto CUDA pode usar a GPU, mas pode ser compartilhado por múltiplas threads.', - exclusiveThreadHelper: 'Thread Exclusivo: Apenas uma thread em um contexto CUDA pode usar a GPU.', - prohibitedHelper: 'Proibido: Não é permitido que processos sejam executados simultaneamente.', - migModeHelper: 'Usado para criar instâncias MIG para isolamento físico da GPU no nível do usuário.', - migModeNA: 'Não Suportado', - }, setting: { setting: 'Configurações do Painel', title: 'Descrição do Painel', @@ -2958,11 +2981,6 @@ const message = { tamperContent4: 'Registra logs de acesso e operações em arquivos para auditoria e análise posteriores, ajudando administradores a identificar ameaças de segurança potenciais.', - gpuTitle1: 'Monitoramento Geral', - gpuContent1: 'Exibir o uso atual do GPU na página de visão geral.', - gpuTitle2: 'Detalhes do GPU', - gpuContent2: 'Mostrar os parâmetros do GPU com mais detalhes.', - settingTitle1: 'Mensagem de Boas-vindas Personalizada', settingContent1: 'Defina uma mensagem de boas-vindas personalizada na página de login do 1Panel.', settingTitle2: 'Logo Personalizado', diff --git a/frontend/src/lang/modules/ru.ts b/frontend/src/lang/modules/ru.ts index e2ad3b9cb..df1c68951 100644 --- a/frontend/src/lang/modules/ru.ts +++ b/frontend/src/lang/modules/ru.ts @@ -9,6 +9,7 @@ const message = { fit2cloud: 'FIT2CLOUD', lingxia: 'Lingxia', button: { + run: 'Запуск', create: 'Создать ', add: 'Добавить ', save: 'Сохранить ', @@ -328,6 +329,7 @@ const message = { firewall: 'Firewall', ssl: 'Сертификат | Сертификаты', database: 'База данных | Базы данных', + aiTools: 'AI', container: 'Контейнер | Контейнеры', cronjob: 'Cron | Задачи Cron', host: 'Хост | Хосты', @@ -586,6 +588,69 @@ const message = { 'Этот адрес подключения может использоваться приложениями, работающими вне контейнера или внешними приложениями.', localIP: 'Локальный IP', }, + aiTools: { + model: { + model: 'Модель', + create: 'Добавить модель', + create_helper: 'Загрузить "{0}"', + ollama_doc: 'Вы можете посетить официальный сайт Ollama, чтобы искать и находить больше моделей.', + container_conn_helper: 'Используйте этот адрес для доступа или подключения между контейнерами', + ollama_sync: + 'Синхронизация модели Ollama обнаружила, что следующие модели не существуют, хотите удалить их?', + from_remote: 'Эта модель не была загружена через 1Panel, нет связанных журналов извлечения.', + no_logs: 'Журналы извлечения для этой модели были удалены и не могут быть просмотрены.', + }, + proxy: { + proxy: 'Усиление AI-прокси', + proxyHelper1: 'Привяжите домен и включите HTTPS для повышения безопасности передачи данных', + proxyHelper2: 'Ограничьте доступ по IP, чтобы предотвратить утечку данных в публичной сети', + proxyHelper3: 'Включите потоковую передачу', + proxyHelper4: 'После создания вы можете просматривать и управлять этим в списке сайтов', + proxyHelper5: + 'После включения вы можете отключить внешний доступ к порту в Магазине приложений - Установленные - Ollama - Параметры для повышения безопасности.', + proxyHelper6: 'Чтобы отключить настройку прокси, вы можете удалить её из списка сайтов.', + whiteListHelper: 'Ограничить доступ только для IP-адресов из белого списка', + }, + gpu: { + gpu: 'Мониторинг GPU', + base: 'Основная информация', + gpuHelper: 'Команда NVIDIA-SMI или XPU-SMI не обнаружена в текущей системе. Проверьте и попробуйте снова!', + driverVersion: 'Версия драйвера', + cudaVersion: 'Версия CUDA', + process: 'Информация о процессе', + type: 'Тип', + typeG: 'Графика', + typeC: 'Вычисления', + typeCG: 'Вычисления + Графика', + processName: 'Имя процесса', + processMemoryUsage: 'Использование памяти', + temperatureHelper: 'Высокая температура GPU может вызвать снижение частоты GPU', + performanceStateHelper: 'От P0 (максимальная производительность) до P12 (минимальная производительность)', + busID: 'ID шины', + persistenceMode: 'Режим постоянства', + enabled: 'Включен', + disabled: 'Выключен', + persistenceModeHelper: + 'Режим постоянства позволяет быстрее реагировать на задачи, но увеличивает потребление энергии в режиме ожидания.', + displayActive: 'Инициализация видеокарты', + displayActiveT: 'Да', + displayActiveF: 'Нет', + ecc: 'Технология проверки и коррекции ошибок (ECC)', + computeMode: 'Режим вычислений', + default: 'По умолчанию', + exclusiveProcess: 'Исключительный процесс', + exclusiveThread: 'Исключительный поток', + prohibited: 'Запрещено', + defaultHelper: 'По умолчанию: процессы могут выполняться одновременно', + exclusiveProcessHelper: + 'Исключительный процесс: только один контекст CUDA может использовать GPU, но его могут разделять несколько потоков', + exclusiveThreadHelper: 'Исключительный поток: только один поток в контексте CUDA может использовать GPU', + prohibitedHelper: 'Запрещено: процессам не разрешено выполняться одновременно', + migModeHelper: + 'Используется для создания MIG-инстансов для физической изоляции GPU на уровне пользователя.', + migModeNA: 'Не поддерживается', + }, + }, container: { create: 'Создать контейнер', edit: 'Редактировать контейнер', @@ -1710,7 +1775,6 @@ const message = { introduce: 'Описание функций', waf: 'Обновление до профессиональной версии предоставляет такие функции, как карта перехватов, логи, записи блокировок, блокировка по географическому положению, пользовательские правила, пользовательские страницы перехвата и т.д.', tamper: 'Обновление до профессиональной версии может защитить веб-сайты от несанкционированных изменений или подделок.', - gpu: 'Обновление до профессиональной версии помогает пользователям визуально отслеживать важные параметры GPU, такие как нагрузка, температура, использование памяти в реальном времени.', setting: 'Обновление до профессиональной версии позволяет настраивать логотип панели, приветственное сообщение и другую информацию.', monitor: @@ -2864,45 +2928,6 @@ const message = { 'Функция защиты от модификации для сайта {0} будет включена для повышения безопасности сайта. Вы хотите продолжить?', disableHelper: 'Функция защиты от модификации для сайта {0} будет отключена. Вы хотите продолжить?', }, - gpu: { - gpu: 'Мониторинг GPU', - base: 'Основная информация', - gpuHelper: 'Команда NVIDIA-SMI или XPU-SMI не обнаружена в текущей системе. Проверьте и попробуйте снова!', - driverVersion: 'Версия драйвера', - cudaVersion: 'Версия CUDA', - process: 'Информация о процессе', - type: 'Тип', - typeG: 'Графика', - typeC: 'Вычисления', - typeCG: 'Вычисления + Графика', - processName: 'Имя процесса', - processMemoryUsage: 'Использование памяти', - temperatureHelper: 'Высокая температура GPU может вызвать снижение частоты GPU', - performanceStateHelper: 'От P0 (максимальная производительность) до P12 (минимальная производительность)', - busID: 'ID шины', - persistenceMode: 'Режим постоянства', - enabled: 'Включен', - disabled: 'Выключен', - persistenceModeHelper: - 'Режим постоянства позволяет быстрее реагировать на задачи, но увеличивает потребление энергии в режиме ожидания.', - displayActive: 'Инициализация видеокарты', - displayActiveT: 'Да', - displayActiveF: 'Нет', - ecc: 'Технология проверки и коррекции ошибок (ECC)', - computeMode: 'Режим вычислений', - default: 'По умолчанию', - exclusiveProcess: 'Исключительный процесс', - exclusiveThread: 'Исключительный поток', - prohibited: 'Запрещено', - defaultHelper: 'По умолчанию: процессы могут выполняться одновременно', - exclusiveProcessHelper: - 'Исключительный процесс: только один контекст CUDA может использовать GPU, но его могут разделять несколько потоков', - exclusiveThreadHelper: 'Исключительный поток: только один поток в контексте CUDA может использовать GPU', - prohibitedHelper: 'Запрещено: процессам не разрешено выполняться одновременно', - migModeHelper: - 'Используется для создания MIG-инстансов для физической изоляции GPU на уровне пользователя.', - migModeNA: 'Не поддерживается', - }, setting: { setting: 'Настройки Панели', title: 'Описание Панели', @@ -2947,11 +2972,6 @@ const message = { tamperContent4: 'Записывайте логи доступа и операций с файлами для последующего аудита и анализа администраторами, а также для выявления потенциальных угроз безопасности.', - gpuTitle1: 'Мониторинг обзора', - gpuContent1: 'Отображение текущего использования GPU на странице обзора.', - gpuTitle2: 'Детали GPU', - gpuContent2: 'Показать параметры GPU с большей точностью.', - settingTitle1: 'Пользовательское Приветственное Сообщение', settingContent1: 'Установите пользовательское приветственное сообщение на странице входа в 1Panel.', settingTitle2: 'Пользовательский Логотип', diff --git a/frontend/src/lang/modules/tw.ts b/frontend/src/lang/modules/tw.ts index bc7ec539f..5b1967861 100644 --- a/frontend/src/lang/modules/tw.ts +++ b/frontend/src/lang/modules/tw.ts @@ -9,6 +9,7 @@ const message = { lingxia: '凌霞', colon: ': ', button: { + run: '執行', prev: '上一步', next: '下一步', create: '創建', @@ -327,6 +328,7 @@ const message = { firewall: '防火墻', ssl: '證書', database: '數據庫', + aiTools: 'AI', container: '容器', cronjob: '計劃任務', host: '主機', @@ -570,6 +572,64 @@ const message = { remoteConnHelper2: '非容器或外部連接使用此地址', localIP: '本機 IP', }, + aiTools: { + model: { + model: '模型', + create: '新增模型', + create_helper: '拉取 "{0}"', + ollama_doc: '您可以瀏覽 Ollama 官方網站,搜尋並查找更多模型。', + container_conn_helper: '容器間瀏覽或連接使用此地址', + ollama_sync: '同步 Ollama 模型發現下列模型不存在,是否刪除?', + from_remote: '該模型並非透過 1Panel 下載,無相關拉取日誌。', + no_logs: '該模型的拉取日誌已被刪除,無法查看相關日誌。', + }, + proxy: { + proxy: 'AI 代理增強', + proxyHelper1: '綁定域名並啟用 HTTPS,提高傳輸安全性', + proxyHelper2: '限制 IP 瀏覽,防止在網路上暴露', + proxyHelper3: '啟用即時串流', + proxyHelper4: '創建後,您可以在網站列表中查看並管理', + proxyHelper5: '啟用後,您可以在應用商店 - 已安裝 - Ollama - 參數中取消埠外部瀏覽以提高安全性', + proxyHelper6: '如需關閉代理配置,可以在網站列表中刪除', + whiteListHelper: '限制僅白名單中的 IP 可瀏覽', + }, + gpu: { + gpu: 'GPU 監控', + base: '基礎資訊', + gpuHelper: '目前系統未檢測到 NVIDIA-SMI 或者 XPU-SMI 指令,請檢查後重試!', + driverVersion: '驅動版本', + cudaVersion: 'CUDA 版本', + process: '行程資訊', + type: '類型', + typeG: '圖形', + typeC: '計算', + typeCG: '計算+圖形', + processName: '行程名稱', + processMemoryUsage: '記憶體使用', + temperatureHelper: 'GPU 溫度過高會導致 GPU 頻率下降', + performanceStateHelper: '從 P0 (最大性能) 到 P12 (最小性能)', + busID: '匯流排地址', + persistenceMode: '持續模式', + enabled: '開啟', + disabled: '關閉', + persistenceModeHelper: '持續模式能更加快速地響應任務,但相應待機功耗也會增加', + displayActive: '顯卡初始化', + displayActiveT: '是', + displayActiveF: '否', + ecc: '是否開啟錯誤檢查和糾正技術', + computeMode: '計算模式', + default: '預設', + exclusiveProcess: '行程排他', + exclusiveThread: '執行緒排他', + prohibited: '禁止', + defaultHelper: '預設: 行程可以並發執行', + exclusiveProcessHelper: '行程排他: 只有一個 CUDA 上下文可以使用 GPU, 但可以由多個執行緒共享', + exclusiveThreadHelper: '執行緒排他: 只有一個執行緒在 CUDA 上下文中可以使用 GPU', + prohibitedHelper: '禁止: 不允許行程同時執行', + migModeHelper: '用於建立 MIG 實例,在用戶層實現 GPU 的物理隔離。', + migModeNA: '不支援', + }, + }, container: { create: '創建容器', createByCommand: '命令創建', @@ -1697,7 +1757,6 @@ const message = { waf: '升級專業版可以獲得攔截地圖、日誌、封鎖記錄、地理位置封禁、自定義規則、自定義攔截頁面等功能。', tamper: '升級專業版可以保護網站免受未經授權的修改或篡改。', tamperHelper: '操作失敗,該文件或文件夾已經開啟防篡改,請檢查後重試!', - gpu: '升級專業版可以幫助用戶實時直觀查看到 GPU 的工作負載、溫度、顯存等重要參數。', setting: '升級專業版可以自定義面板 Logo、歡迎簡介等信息。', monitor: '升級專業版可以查看網站的即時狀態、訪客趨勢、訪客來源、請求日誌等資訊。 ', alert: '陞級專業版可通過簡訊接收告警資訊,並查看告警日誌,全面掌控各類關鍵事件,確保系統運行無憂。', @@ -2774,42 +2833,6 @@ const message = { enableHelper: '即將啟用 {0} 網站的防篡改功能,以提升網站安全性,是否繼續?', disableHelper: '即將關閉 {0} 網站的防篡改功能,是否繼續?', }, - gpu: { - gpu: 'GPU 监控', - base: '基礎資訊', - gpuHelper: '目前系統未檢測到 NVIDIA-SMI或者XPU-SMI 指令,請檢查後重試!', - driverVersion: '驅動版本', - cudaVersion: 'CUDA 版本', - process: '行程資訊', - type: '類型', - typeG: '圖形', - typeC: '計算', - typeCG: '計算+圖形', - processName: '行程名稱', - processMemoryUsage: '顯存使用', - temperatureHelper: 'GPU 溫度過高會導致 GPU 頻率下降', - performanceStateHelper: '從 P0 (最大性能) 到 P12 (最小性能)', - busID: '總線地址', - persistenceMode: '持續模式', - enabled: '開啟', - disabled: '關閉', - persistenceModeHelper: '持續模式能更加快速地響應任務,但相應待機功耗也會增加', - displayActive: '顯卡初始化', - displayActiveT: '是', - displayActiveF: '否', - ecc: '是否開啟錯誤檢查和紀正技術', - computeMode: '計算模式', - default: '預設', - exclusiveProcess: '行程排他', - exclusiveThread: '線程排他', - prohibited: '禁止', - defaultHelper: '預設: 行程可以並發執行', - exclusiveProcessHelper: '行程排他: 只有一個 CUDA 上下文可以使用 GPU, 但可以由多個線程共享', - exclusiveThreadHelper: '線程排他: 只有一個線程在 CUDA 上下文中可以使用 GPU', - prohibitedHelper: '禁止: 不允許行程同時執行', - migModeHelper: '用於建立 MIG 實例,在用戶層實現 GPU 的物理隔離。', - migModeNA: '不支援', - }, setting: { setting: '界面設定', title: '面板描述', @@ -2847,11 +2870,6 @@ const message = { tamperTitle4: '日誌紀錄與分析', tamperContent4: '紀錄檔案瀏覽和操作日誌,以便管理員進行後續的審計和分析,以及發現潛在的安全威脅。', - gpuTitle1: '概覽頁監控', - gpuContent1: '在概覽頁上顯示 GPU 當前使用情況。', - gpuTitle2: 'GPU 詳細資訊', - gpuContent2: '更加細緻地顯示 GPU 各項參數。', - settingTitle1: '自訂歡迎語', settingContent1: '在 1Panel 登入頁上設定自訂的歡迎語。', settingTitle2: '自訂 Logo', diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts index 065e71474..29ba22db9 100644 --- a/frontend/src/lang/modules/zh.ts +++ b/frontend/src/lang/modules/zh.ts @@ -9,6 +9,7 @@ const message = { lingxia: '凌霞', colon: ': ', button: { + run: '运行', prev: '上一步', next: '下一步', create: '创建', @@ -325,6 +326,7 @@ const message = { firewall: '防火墙', ssl: '证书', database: '数据库', + aiTools: 'AI', container: '容器', cronjob: '计划任务', host: '主机', @@ -568,6 +570,65 @@ const message = { remoteConnHelper2: '非容器或外部连接使用此地址', localIP: '本机 IP', }, + aiTools: { + model: { + model: '模型', + create: '添加模型', + create_helper: '拉取 "{0}"', + ollama_doc: '您可以访问 Ollama 官网,搜索并查找更多模型。', + container_conn_helper: '容器间访问或连接使用此地址', + ollama_sync: '同步 Ollama 模型发现下列模型不存在,是否删除?', + from_remote: '该模型并非通过 1Panel 下载,无相关拉取日志。', + no_logs: '该模型的拉取日志已被删除,无法查看相关日志。', + }, + proxy: { + proxy: 'AI 代理增强', + proxyHelper1: '绑定域名并开启 HTTPS,增强传输安全性', + proxyHelper2: '限制 IP 访问,防止在公网暴露', + proxyHelper3: '开启流式传输', + proxyHelper4: '创建完成之后可以在网站列表中查看并管理', + proxyHelper5: '创建完成之后可以在应用商店 - 已安装 - ollama - 参数中取消端口外部访问以提高安全性', + proxyHelper6: '如需关闭代理配置,可以在网站列表中删除', + whiteListHelper: '限制仅白名单中的 IP 可访问', + }, + gpu: { + gpu: 'GPU 监控', + base: '基础信息', + gpuHelper: '当前系统未检测到 NVIDIA-SMI或者XPU-SMI 指令,请检查后重试!', + driverVersion: '驱动版本', + cudaVersion: 'CUDA 版本', + process: '进程信息', + type: '类型', + typeG: '图形', + typeC: '计算', + typeCG: '计算+图形', + processName: '进程名称', + processMemoryUsage: '显存使用', + temperatureHelper: 'GPU 温度过高会导致 GPU 频率下降', + performanceStateHelper: '从 P0 (最大性能) 到 P12 (最小性能)', + busID: '总线地址', + persistenceMode: '持续模式', + enabled: '开启', + disabled: '关闭', + persistenceModeHelper: '持续模式能更加快速地响应任务,但相应待机功耗也会增加', + displayActive: '显卡初始化', + displayActiveT: '是', + displayActiveF: '否', + ecc: '是否开启错误检查和纠正技术', + computeMode: '计算模式', + default: '默认', + exclusiveProcess: '进程排他', + exclusiveThread: '线程排他', + prohibited: '禁止', + defaultHelper: '默认: 进程可以并发执行', + exclusiveProcessHelper: '进程排他: 只有一个 CUDA 上下文可以使用 GPU, 但可以由多个线程共享', + exclusiveThreadHelper: '线程排他: 只有一个线程在 CUDA 上下文中可以使用 GPU', + prohibitedHelper: '禁止: 不允许进程同时执行', + migModeHelper: '用于创建 MIG 实例,在用户层实现 GPU 的物理隔离。', + migModeNA: '不支持', + shr: '共享显存', + }, + }, container: { create: '创建容器', createByCommand: '命令创建', @@ -1665,7 +1726,6 @@ const message = { waf: '升级专业版可以获得拦截地图、日志、封锁记录、地理位置封禁、自定义规则、自定义拦截页面等功能。', tamper: '升级专业版可以保护网站免受未经授权的修改或篡改。', tamperHelper: '操作失败,该文件或文件夹已经开启防篡改,请检查后重试!', - gpu: '升级专业版可以帮助用户实时直观查看到 GPU 的工作负载、温度、显存等重要参数。', setting: '升级专业版可以自定义面板 Logo、欢迎简介等信息。', monitor: '升级专业版可以查看网站的实时状态、访客趋势、访客来源、请求日志等信息。', alert: '升级专业版可通过短信接收告警信息,并查看告警日志,全面掌控各类关键事件,确保系统运行无忧。', @@ -2803,38 +2863,6 @@ const message = { enableHelper: '即将启用下列网站的防篡改功能,以提升网站安全性,是否继续?', disableHelper: '即将关闭下列网站的防篡改功能,是否继续?', }, - gpu: { - gpu: 'GPU 监控', - base: '基础信息', - gpuHelper: '当前系统未检测到 NVIDIA-SMI 指令,请检查后重试!', - driverVersion: '驱动版本', - cudaVersion: 'CUDA 版本', - process: '进程信息', - typeG: '图形', - typeC: '计算', - typeCG: '计算+图形', - processName: '进程名称', - processMemoryUsage: '显存使用', - temperatureHelper: 'GPU 温度过高会导致 GPU 频率下降', - performanceStateHelper: '从 P0 (最大性能) 到 P12 (最小性能)', - busID: '总线地址', - persistenceMode: '持续模式', - persistenceModeHelper: '持续模式能更加快速地响应任务,但相应待机功耗也会增加', - displayActive: '显卡初始化', - displayActiveT: '是', - displayActiveF: '否', - ecc: '是否开启错误检查和纠正技术', - computeMode: '计算模式', - exclusiveProcess: '进程排他', - exclusiveThread: '线程排他', - prohibited: '禁止', - defaultHelper: '默认: 进程可以并发执行', - exclusiveProcessHelper: '进程排他: 只有一个 CUDA 上下文可以使用 GPU, 但可以由多个线程共享', - exclusiveThreadHelper: '线程排他: 只有一个线程在 CUDA 上下文中可以使用 GPU', - prohibitedHelper: '禁止: 不允许进程同时执行', - migModeHelper: '用于创建 MIG 实例,在用户层实现 GPU 的物理隔离。', - migModeNA: '不支持', - }, setting: { setting: '界面设置', title: '面板描述', @@ -2870,11 +2898,6 @@ const message = { tamperTitle4: '日志记录与分析', tamperContent4: '记录文件访问和操作日志,以便管理员进行后续的审计和分析,以及发现潜在的安全威胁。', - gpuTitle1: '概览页监控', - gpuContent1: '在概览页上显示 GPU 当前使用情况。', - gpuTitle2: 'GPU 详细信息', - gpuContent2: '更加细粒度的显示出 GPU 各项参数。', - settingTitle1: '自定义欢迎语', settingContent1: '在 1Panel 登录页上设置自定义的欢迎语。', settingTitle2: '自定义 Logo', diff --git a/frontend/src/routers/modules/ai.ts b/frontend/src/routers/modules/ai.ts new file mode 100644 index 000000000..2baa7c76d --- /dev/null +++ b/frontend/src/routers/modules/ai.ts @@ -0,0 +1,34 @@ +import { Layout } from '@/routers/constant'; + +const databaseRouter = { + sort: 4, + path: '/ai', + component: Layout, + redirect: '/ai/model', + meta: { + icon: 'p-jiqiren2', + title: 'menu.aiTools', + }, + children: [ + { + path: '/ai/model', + name: 'OllamaModel', + component: () => import('@/views/ai/model/index.vue'), + meta: { + title: 'aiTools.model.model', + requiresAuth: true, + }, + }, + { + path: '/ai/gpu', + name: 'GPU', + component: () => import('@/views/ai/gpu/index.vue'), + meta: { + title: 'aiTools.gpu.gpu', + requiresAuth: true, + }, + }, + ], +}; + +export default databaseRouter; diff --git a/frontend/src/routers/modules/container.ts b/frontend/src/routers/modules/container.ts index d95b073e3..376f881b3 100644 --- a/frontend/src/routers/modules/container.ts +++ b/frontend/src/routers/modules/container.ts @@ -1,7 +1,7 @@ import { Layout } from '@/routers/constant'; const containerRouter = { - sort: 5, + sort: 6, path: '/containers', component: Layout, redirect: '/containers/container', diff --git a/frontend/src/routers/modules/database.ts b/frontend/src/routers/modules/database.ts index 84787705b..f1261dc07 100644 --- a/frontend/src/routers/modules/database.ts +++ b/frontend/src/routers/modules/database.ts @@ -1,7 +1,7 @@ import { Layout } from '@/routers/constant'; const databaseRouter = { - sort: 4, + sort: 5, path: '/databases', component: Layout, redirect: '/databases/mysql', diff --git a/frontend/src/routers/modules/host.ts b/frontend/src/routers/modules/host.ts index 21149ef97..34293a11f 100644 --- a/frontend/src/routers/modules/host.ts +++ b/frontend/src/routers/modules/host.ts @@ -1,7 +1,7 @@ import { Layout } from '@/routers/constant'; const hostRouter = { - sort: 6, + sort: 7, path: '/hosts', component: Layout, redirect: '/hosts/security', diff --git a/frontend/src/routers/modules/terminal.ts b/frontend/src/routers/modules/terminal.ts index 5a8bc1a00..e3ac89537 100644 --- a/frontend/src/routers/modules/terminal.ts +++ b/frontend/src/routers/modules/terminal.ts @@ -1,7 +1,7 @@ import { Layout } from '@/routers/constant'; const terminalRouter = { - sort: 7, + sort: 8, path: '/terminal', component: Layout, redirect: '/terminal', diff --git a/frontend/src/routers/modules/toolbox.ts b/frontend/src/routers/modules/toolbox.ts index 5ae5c9e4c..e4cd657a0 100644 --- a/frontend/src/routers/modules/toolbox.ts +++ b/frontend/src/routers/modules/toolbox.ts @@ -1,7 +1,7 @@ import { Layout } from '@/routers/constant'; const toolboxRouter = { - sort: 8, + sort: 9, path: '/toolbox', component: Layout, redirect: '/toolbox/supervisor', diff --git a/frontend/src/views/ai/gpu/index.vue b/frontend/src/views/ai/gpu/index.vue new file mode 100644 index 000000000..721965527 --- /dev/null +++ b/frontend/src/views/ai/gpu/index.vue @@ -0,0 +1,367 @@ + + + + + diff --git a/frontend/src/views/ai/model/add/index.vue b/frontend/src/views/ai/model/add/index.vue new file mode 100644 index 000000000..61904739c --- /dev/null +++ b/frontend/src/views/ai/model/add/index.vue @@ -0,0 +1,88 @@ + + + diff --git a/frontend/src/views/ai/model/conn/index.vue b/frontend/src/views/ai/model/conn/index.vue new file mode 100644 index 000000000..6b413bab0 --- /dev/null +++ b/frontend/src/views/ai/model/conn/index.vue @@ -0,0 +1,139 @@ + + + + + diff --git a/frontend/src/views/ai/model/del/index.vue b/frontend/src/views/ai/model/del/index.vue new file mode 100644 index 000000000..b804e902f --- /dev/null +++ b/frontend/src/views/ai/model/del/index.vue @@ -0,0 +1,105 @@ + + + diff --git a/frontend/src/views/ai/model/domain/index.vue b/frontend/src/views/ai/model/domain/index.vue new file mode 100644 index 000000000..6e0f76c33 --- /dev/null +++ b/frontend/src/views/ai/model/domain/index.vue @@ -0,0 +1,260 @@ + + + + + diff --git a/frontend/src/views/ai/model/index.vue b/frontend/src/views/ai/model/index.vue new file mode 100644 index 000000000..9e7e48c85 --- /dev/null +++ b/frontend/src/views/ai/model/index.vue @@ -0,0 +1,457 @@ + + + + + diff --git a/frontend/src/views/ai/model/terminal/index.vue b/frontend/src/views/ai/model/terminal/index.vue new file mode 100644 index 000000000..993053a79 --- /dev/null +++ b/frontend/src/views/ai/model/terminal/index.vue @@ -0,0 +1,76 @@ + + +