mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-10-25 06:56:32 +08:00
fix: Adjust the synchronization method for the Ollama model (#7895)
This commit is contained in:
parent
e492c6573c
commit
39385ea0a0
28 changed files with 958 additions and 173 deletions
|
|
@ -32,6 +32,44 @@ func (b *BaseApi) CreateOllamaModel(c *gin.Context) {
|
|||
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":"添加模型重试 [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.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, 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.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, list)
|
||||
}
|
||||
|
||||
// @Tags AI
|
||||
// @Summary Page Ollama models
|
||||
// @Accept json
|
||||
|
|
@ -84,19 +122,19 @@ func (b *BaseApi) LoadOllamaModelDetail(c *gin.Context) {
|
|||
// @Tags AI
|
||||
// @Summary Delete Ollama model
|
||||
// @Accept json
|
||||
// @Param request body dto.OllamaModelName true "request"
|
||||
// @Param request body dto.ForceDelete true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /ai/ollama/model/del [post]
|
||||
// @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"删除模型 [name]","formatEN":"remove Ollama model [name]"}
|
||||
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"ollama_models","output_column":"name","output_value":"name"}],"formatZH":"删除 ollama 模型 [name]","formatEN":"remove ollama model [name]"}
|
||||
func (b *BaseApi) DeleteOllamaModel(c *gin.Context) {
|
||||
var req dto.OllamaModelName
|
||||
var req dto.ForceDelete
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := AIToolService.Delete(req.Name); err != nil {
|
||||
if err := AIToolService.Delete(req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,22 @@
|
|||
package dto
|
||||
|
||||
import "time"
|
||||
|
||||
type OllamaModelInfo struct {
|
||||
Name string `json:"name"`
|
||||
Size string `json:"size"`
|
||||
Modified string `json:"modified"`
|
||||
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 {
|
||||
|
|
|
|||
|
|
@ -52,3 +52,8 @@ type OperationWithNameAndType struct {
|
|||
Name string `json:"name"`
|
||||
Type string `json:"type" validate:"required"`
|
||||
}
|
||||
|
||||
type ForceDelete struct {
|
||||
IDs []uint `json:"ids"`
|
||||
ForceDelete bool `json:"forceDelete"`
|
||||
}
|
||||
|
|
|
|||
11
backend/app/model/ai.go
Normal file
11
backend/app/model/ai.go
Normal file
|
|
@ -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"`
|
||||
}
|
||||
69
backend/app/repo/ai.go
Normal file
69
backend/app/repo/ai.go
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
package repo
|
||||
|
||||
import (
|
||||
"github.com/1Panel-dev/1Panel/backend/app/model"
|
||||
"github.com/1Panel-dev/1Panel/backend/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
|
||||
}
|
||||
|
|
@ -12,10 +12,14 @@ import (
|
|||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto/request"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/model"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/repo"
|
||||
"github.com/1Panel-dev/1Panel/backend/buserr"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
|
||||
"github.com/jinzhu/copier"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type AIToolService struct{}
|
||||
|
|
@ -23,7 +27,9 @@ type AIToolService struct{}
|
|||
type IAIToolService interface {
|
||||
Search(search dto.SearchWithPage) (int64, []dto.OllamaModelInfo, error)
|
||||
Create(name string) error
|
||||
Delete(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)
|
||||
|
|
@ -35,78 +41,38 @@ func NewIAIToolService() IAIToolService {
|
|||
}
|
||||
|
||||
func (u *AIToolService) Search(req dto.SearchWithPage) (int64, []dto.OllamaModelInfo, error) {
|
||||
ollamaBaseInfo, err := appInstallRepo.LoadBaseInfo("ollama", "")
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
if ollamaBaseInfo.Status != constant.Running {
|
||||
return 0, nil, nil
|
||||
}
|
||||
stdout, err := cmd.Execf("docker exec %s ollama list", ollamaBaseInfo.ContainerName)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
var list []dto.OllamaModelInfo
|
||||
modelMaps := make(map[string]struct{})
|
||||
lines := strings.Split(stdout, "\n")
|
||||
for _, line := range lines {
|
||||
parts := strings.Fields(line)
|
||||
if len(parts) < 5 {
|
||||
continue
|
||||
}
|
||||
if parts[0] == "NAME" {
|
||||
continue
|
||||
}
|
||||
modelMaps[strings.ReplaceAll(parts[0], ":", "-")] = struct{}{}
|
||||
list = append(list, dto.OllamaModelInfo{Name: parts[0], Size: parts[2] + " " + parts[3], Modified: strings.Join(parts[4:], " ")})
|
||||
}
|
||||
entries, _ := os.ReadDir(path.Join(global.CONF.System.DataDir, "log", "AITools"))
|
||||
for _, item := range entries {
|
||||
if _, ok := modelMaps[item.Name()]; ok {
|
||||
continue
|
||||
}
|
||||
if _, ok := modelMaps[item.Name()+":latest"]; ok {
|
||||
continue
|
||||
}
|
||||
list = append(list, dto.OllamaModelInfo{Name: item.Name(), Size: "-", Modified: "-"})
|
||||
}
|
||||
var options []repo.DBOption
|
||||
if len(req.Info) != 0 {
|
||||
length, count := len(list), 0
|
||||
for count < length {
|
||||
if !strings.Contains(list[count].Name, req.Info) {
|
||||
list = append(list[:count], list[(count+1):]...)
|
||||
length--
|
||||
} else {
|
||||
count++
|
||||
}
|
||||
}
|
||||
options = append(options, commonRepo.WithLikeName(req.Info))
|
||||
}
|
||||
|
||||
var records []dto.OllamaModelInfo
|
||||
total, start, end := len(list), (req.Page-1)*req.PageSize, req.Page*req.PageSize
|
||||
if start > total {
|
||||
records = make([]dto.OllamaModelInfo, 0)
|
||||
} else {
|
||||
if end >= total {
|
||||
end = total
|
||||
}
|
||||
records = list[start:end]
|
||||
total, list, err := aiRepo.Page(req.Page, req.PageSize, options...)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
return int64(total), records, err
|
||||
var dtoLists []dto.OllamaModelInfo
|
||||
for _, itemModel := range list {
|
||||
var item dto.OllamaModelInfo
|
||||
if err := copier.Copy(&item, &itemModel); err != nil {
|
||||
return 0, nil, errors.WithMessage(constant.ErrStructTransform, err.Error())
|
||||
}
|
||||
logPath := path.Join(global.CONF.System.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(constant.ErrCmdIllegal)
|
||||
}
|
||||
ollamaBaseInfo, err := appInstallRepo.LoadBaseInfo("ollama", "")
|
||||
containerName, err := loadContainerName()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if ollamaBaseInfo.Status != constant.Running {
|
||||
return "", nil
|
||||
}
|
||||
stdout, err := cmd.Execf("docker exec %s ollama show %s", ollamaBaseInfo.ContainerName, name)
|
||||
stdout, err := cmd.Execf("docker exec %s ollama show %s", containerName, name)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
|
@ -117,15 +83,52 @@ func (u *AIToolService) Create(name string) error {
|
|||
if cmd.CheckIllegal(name) {
|
||||
return buserr.New(constant.ErrCmdIllegal)
|
||||
}
|
||||
ollamaBaseInfo, err := appInstallRepo.LoadBaseInfo("ollama", "")
|
||||
modelInfo, _ := aiRepo.Get(commonRepo.WithByName(name))
|
||||
if modelInfo.ID != 0 {
|
||||
return constant.ErrRecordExist
|
||||
}
|
||||
containerName, err := loadContainerName()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if ollamaBaseInfo.Status != constant.Running {
|
||||
return nil
|
||||
logItem := path.Join(global.CONF.System.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
|
||||
}
|
||||
}
|
||||
fileName := strings.ReplaceAll(name, ":", "-")
|
||||
logItem := path.Join(global.CONF.System.DataDir, "log", "AITools", fileName)
|
||||
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) Recreate(name string) error {
|
||||
if cmd.CheckIllegal(name) {
|
||||
return buserr.New(constant.ErrCmdIllegal)
|
||||
}
|
||||
modelInfo, _ := aiRepo.Get(commonRepo.WithByName(name))
|
||||
if modelInfo.ID == 0 {
|
||||
return constant.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.CONF.System.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
|
||||
|
|
@ -135,40 +138,41 @@ func (u *AIToolService) Create(name string) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go func() {
|
||||
defer file.Close()
|
||||
cmd := exec.Command("docker", "exec", ollamaBaseInfo.ContainerName, "ollama", "run", name)
|
||||
multiWriter := io.MultiWriter(os.Stdout, file)
|
||||
cmd.Stdout = multiWriter
|
||||
cmd.Stderr = multiWriter
|
||||
if err := cmd.Run(); err != nil {
|
||||
global.LOG.Errorf("ollama pull %s failed, err: %v", name, err)
|
||||
_, _ = file.WriteString("ollama pull failed!")
|
||||
return
|
||||
}
|
||||
global.LOG.Infof("ollama pull %s successful!", name)
|
||||
_, _ = file.WriteString("ollama pull successful!")
|
||||
}()
|
||||
|
||||
go pullOllamaModel(file, containerName, modelInfo)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *AIToolService) Delete(name string) error {
|
||||
if cmd.CheckIllegal(name) {
|
||||
return buserr.New(constant.ErrCmdIllegal)
|
||||
func (u *AIToolService) Delete(req dto.ForceDelete) error {
|
||||
ollamaList, _ := aiRepo.List(commonRepo.WithIdsIn(req.IDs))
|
||||
if len(ollamaList) == 0 {
|
||||
return constant.ErrRecordNotFound
|
||||
}
|
||||
ollamaBaseInfo, err := appInstallRepo.LoadBaseInfo("ollama", "")
|
||||
if err != nil {
|
||||
containerName, err := loadContainerName()
|
||||
if err != nil && !req.ForceDelete {
|
||||
return err
|
||||
}
|
||||
if ollamaBaseInfo.Status != constant.Running {
|
||||
return nil
|
||||
for _, item := range ollamaList {
|
||||
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(commonRepo.WithByID(item.ID))
|
||||
logItem := path.Join(global.CONF.System.DataDir, "log", "AITools", item.Name)
|
||||
_ = os.Remove(logItem)
|
||||
}
|
||||
stdout, err := cmd.Execf("docker exec %s ollama list", ollamaBaseInfo.ContainerName)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *AIToolService) Sync() ([]dto.OllamaModelDropList, error) {
|
||||
containerName, err := loadContainerName()
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
isExist := false
|
||||
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)
|
||||
|
|
@ -178,25 +182,33 @@ func (u *AIToolService) Delete(name string) error {
|
|||
if parts[0] == "NAME" {
|
||||
continue
|
||||
}
|
||||
if parts[0] == name {
|
||||
isExist = true
|
||||
break
|
||||
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)
|
||||
}
|
||||
|
||||
if isExist {
|
||||
stdout, err := cmd.Execf("docker exec %s ollama rm %s", ollamaBaseInfo.ContainerName, name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("handle ollama rm %s failed, stdout: %s, err: %v", name, stdout, err)
|
||||
}
|
||||
}
|
||||
logItem := path.Join(global.CONF.System.DataDir, "log", "AITools", name)
|
||||
_ = os.Remove(logItem)
|
||||
logItem2 := path.Join(global.CONF.System.DataDir, "log", "AITools", strings.TrimSuffix(name, ":latest"))
|
||||
if logItem2 != logItem {
|
||||
_ = os.Remove(logItem2)
|
||||
}
|
||||
return nil
|
||||
return dropList, nil
|
||||
}
|
||||
|
||||
func (u *AIToolService) BindDomain(req dto.OllamaBindDomain) error {
|
||||
|
|
@ -318,3 +330,46 @@ func (u *AIToolService) UpdateBindDomain(req dto.OllamaBindDomain) error {
|
|||
}
|
||||
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))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@ var (
|
|||
appInstallRepo = repo.NewIAppInstallRepo()
|
||||
appInstallResourceRepo = repo.NewIAppInstallResourceRpo()
|
||||
|
||||
aiRepo = repo.NewIAiRepo()
|
||||
|
||||
mysqlRepo = repo.NewIMysqlRepo()
|
||||
postgresqlRepo = repo.NewIPostgresqlRepo()
|
||||
databaseRepo = repo.NewIDatabaseRepo()
|
||||
|
|
|
|||
|
|
@ -480,13 +480,7 @@ func (f *FileService) ReadLogByLine(req request.FileReadByLineReq) (*response.Fi
|
|||
case "image-pull", "image-push", "image-build", "compose-create":
|
||||
logFilePath = path.Join(global.CONF.System.TmpDir, fmt.Sprintf("docker_logs/%s", req.Name))
|
||||
case "ollama-model":
|
||||
fileName := strings.ReplaceAll(req.Name, ":", "-")
|
||||
if _, err := os.Stat(fileName); err != nil {
|
||||
if strings.HasSuffix(req.Name, ":latest") {
|
||||
fileName = strings.TrimSuffix(req.Name, ":latest")
|
||||
}
|
||||
}
|
||||
logFilePath = path.Join(global.CONF.System.DataDir, "log", "AITools", fileName)
|
||||
logFilePath = path.Join(global.CONF.System.DataDir, "log", "AITools", req.Name)
|
||||
}
|
||||
|
||||
lines, isEndOfFile, total, err := files.ReadFileByLine(logFilePath, req.Page, req.PageSize, req.Latest)
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ const (
|
|||
StatusWaiting = "Waiting"
|
||||
StatusSuccess = "Success"
|
||||
StatusFailed = "Failed"
|
||||
StatusDeleted = "Deleted"
|
||||
StatusUploading = "Uploading"
|
||||
StatusEnable = "Enable"
|
||||
StatusDisable = "Disable"
|
||||
|
|
|
|||
|
|
@ -102,6 +102,7 @@ func Init() {
|
|||
|
||||
migrations.UpdateAppTag,
|
||||
migrations.UpdateApp,
|
||||
migrations.AddOllamaModel,
|
||||
})
|
||||
if err := m.Migrate(); err != nil {
|
||||
global.LOG.Error(err)
|
||||
|
|
|
|||
|
|
@ -380,3 +380,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
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,9 @@ func (a *AIToolsRouter) InitRouter(Router *gin.RouterGroup) {
|
|||
baseApi := v1.ApiGroupApp.BaseApi
|
||||
{
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -185,7 +185,7 @@ const docTemplate = `{
|
|||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.OllamaModelName"
|
||||
"$ref": "#/definitions/dto.ForceDelete"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
|
@ -195,12 +195,21 @@ const docTemplate = `{
|
|||
}
|
||||
},
|
||||
"x-panel-log": {
|
||||
"BeforeFunctions": [],
|
||||
"bodyKeys": [
|
||||
"name"
|
||||
"BeforeFunctions": [
|
||||
{
|
||||
"db": "ollama_models",
|
||||
"input_column": "id",
|
||||
"input_value": "id",
|
||||
"isList": false,
|
||||
"output_column": "name",
|
||||
"output_value": "name"
|
||||
}
|
||||
],
|
||||
"formatEN": "remove Ollama model [name]",
|
||||
"formatZH": "删除模型 [name]",
|
||||
"bodyKeys": [
|
||||
"id"
|
||||
],
|
||||
"formatEN": "remove ollama model [name]",
|
||||
"formatZH": "删除 ollama 模型 [name]",
|
||||
"paramKeys": []
|
||||
}
|
||||
}
|
||||
|
|
@ -243,6 +252,50 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"/ai/ollama/model/recreate": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
},
|
||||
{
|
||||
"Timestamp": []
|
||||
}
|
||||
],
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"AI"
|
||||
],
|
||||
"summary": "Rereate Ollama model",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "request",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.OllamaModelName"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
},
|
||||
"x-panel-log": {
|
||||
"BeforeFunctions": [],
|
||||
"bodyKeys": [
|
||||
"name"
|
||||
],
|
||||
"formatEN": "re-add Ollama model [name]",
|
||||
"formatZH": "添加模型重试 [name]",
|
||||
"paramKeys": []
|
||||
}
|
||||
}
|
||||
},
|
||||
"/ai/ollama/model/search": {
|
||||
"post": {
|
||||
"security": [
|
||||
|
|
@ -281,6 +334,40 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"/ai/ollama/model/sync": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
},
|
||||
{
|
||||
"Timestamp": []
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"AI"
|
||||
],
|
||||
"summary": "Sync Ollama model list",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/dto.OllamaModelDropList"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"x-panel-log": {
|
||||
"BeforeFunctions": [],
|
||||
"bodyKeys": [],
|
||||
"formatEN": "sync Ollama model list",
|
||||
"formatZH": "同步 Ollama 模型列表",
|
||||
"paramKeys": []
|
||||
}
|
||||
}
|
||||
},
|
||||
"/apps/checkupdate": {
|
||||
"get": {
|
||||
"security": [
|
||||
|
|
@ -19234,6 +19321,20 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"dto.ForceDelete": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"forceDelete": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"ids": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.ForwardRuleOperate": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -20583,6 +20684,17 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"dto.OllamaModelDropList": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.OllamaModelName": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
|
|||
|
|
@ -182,7 +182,7 @@
|
|||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.OllamaModelName"
|
||||
"$ref": "#/definitions/dto.ForceDelete"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
|
@ -192,12 +192,21 @@
|
|||
}
|
||||
},
|
||||
"x-panel-log": {
|
||||
"BeforeFunctions": [],
|
||||
"bodyKeys": [
|
||||
"name"
|
||||
"BeforeFunctions": [
|
||||
{
|
||||
"db": "ollama_models",
|
||||
"input_column": "id",
|
||||
"input_value": "id",
|
||||
"isList": false,
|
||||
"output_column": "name",
|
||||
"output_value": "name"
|
||||
}
|
||||
],
|
||||
"formatEN": "remove Ollama model [name]",
|
||||
"formatZH": "删除模型 [name]",
|
||||
"bodyKeys": [
|
||||
"id"
|
||||
],
|
||||
"formatEN": "remove ollama model [name]",
|
||||
"formatZH": "删除 ollama 模型 [name]",
|
||||
"paramKeys": []
|
||||
}
|
||||
}
|
||||
|
|
@ -240,6 +249,50 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"/ai/ollama/model/recreate": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
},
|
||||
{
|
||||
"Timestamp": []
|
||||
}
|
||||
],
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"AI"
|
||||
],
|
||||
"summary": "Rereate Ollama model",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "request",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.OllamaModelName"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
},
|
||||
"x-panel-log": {
|
||||
"BeforeFunctions": [],
|
||||
"bodyKeys": [
|
||||
"name"
|
||||
],
|
||||
"formatEN": "re-add Ollama model [name]",
|
||||
"formatZH": "添加模型重试 [name]",
|
||||
"paramKeys": []
|
||||
}
|
||||
}
|
||||
},
|
||||
"/ai/ollama/model/search": {
|
||||
"post": {
|
||||
"security": [
|
||||
|
|
@ -278,6 +331,40 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"/ai/ollama/model/sync": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
},
|
||||
{
|
||||
"Timestamp": []
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"AI"
|
||||
],
|
||||
"summary": "Sync Ollama model list",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/dto.OllamaModelDropList"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"x-panel-log": {
|
||||
"BeforeFunctions": [],
|
||||
"bodyKeys": [],
|
||||
"formatEN": "sync Ollama model list",
|
||||
"formatZH": "同步 Ollama 模型列表",
|
||||
"paramKeys": []
|
||||
}
|
||||
}
|
||||
},
|
||||
"/apps/checkupdate": {
|
||||
"get": {
|
||||
"security": [
|
||||
|
|
@ -19231,6 +19318,20 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"dto.ForceDelete": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"forceDelete": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"ids": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.ForwardRuleOperate": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -20580,6 +20681,17 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"dto.OllamaModelDropList": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.OllamaModelName": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
|
|||
|
|
@ -1567,6 +1567,15 @@ definitions:
|
|||
- type
|
||||
- vars
|
||||
type: object
|
||||
dto.ForceDelete:
|
||||
properties:
|
||||
forceDelete:
|
||||
type: boolean
|
||||
ids:
|
||||
items:
|
||||
type: integer
|
||||
type: array
|
||||
type: object
|
||||
dto.ForwardRuleOperate:
|
||||
properties:
|
||||
rules:
|
||||
|
|
@ -2484,6 +2493,13 @@ definitions:
|
|||
websiteID:
|
||||
type: integer
|
||||
type: object
|
||||
dto.OllamaModelDropList:
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
name:
|
||||
type: string
|
||||
type: object
|
||||
dto.OllamaModelName:
|
||||
properties:
|
||||
name:
|
||||
|
|
@ -6579,7 +6595,7 @@ paths:
|
|||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/dto.OllamaModelName'
|
||||
$ref: '#/definitions/dto.ForceDelete'
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
|
|
@ -6590,11 +6606,17 @@ paths:
|
|||
tags:
|
||||
- AI
|
||||
x-panel-log:
|
||||
BeforeFunctions: []
|
||||
BeforeFunctions:
|
||||
- db: ollama_models
|
||||
input_column: id
|
||||
input_value: id
|
||||
isList: false
|
||||
output_column: name
|
||||
output_value: name
|
||||
bodyKeys:
|
||||
- name
|
||||
formatEN: remove Ollama model [name]
|
||||
formatZH: 删除模型 [name]
|
||||
- id
|
||||
formatEN: remove ollama model [name]
|
||||
formatZH: 删除 ollama 模型 [name]
|
||||
paramKeys: []
|
||||
/ai/ollama/model/load:
|
||||
post:
|
||||
|
|
@ -6618,6 +6640,33 @@ paths:
|
|||
summary: Page Ollama models
|
||||
tags:
|
||||
- AI
|
||||
/ai/ollama/model/recreate:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
parameters:
|
||||
- description: request
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/dto.OllamaModelName'
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
- Timestamp: []
|
||||
summary: Rereate Ollama model
|
||||
tags:
|
||||
- AI
|
||||
x-panel-log:
|
||||
BeforeFunctions: []
|
||||
bodyKeys:
|
||||
- name
|
||||
formatEN: re-add Ollama model [name]
|
||||
formatZH: 添加模型重试 [name]
|
||||
paramKeys: []
|
||||
/ai/ollama/model/search:
|
||||
post:
|
||||
consumes:
|
||||
|
|
@ -6640,6 +6689,27 @@ paths:
|
|||
summary: Page Ollama models
|
||||
tags:
|
||||
- AI
|
||||
/ai/ollama/model/sync:
|
||||
post:
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
items:
|
||||
$ref: '#/definitions/dto.OllamaModelDropList'
|
||||
type: array
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
- Timestamp: []
|
||||
summary: Sync Ollama model list
|
||||
tags:
|
||||
- AI
|
||||
x-panel-log:
|
||||
BeforeFunctions: []
|
||||
bodyKeys: []
|
||||
formatEN: sync Ollama model list
|
||||
formatZH: 同步 Ollama 模型列表
|
||||
paramKeys: []
|
||||
/apps/{key}:
|
||||
get:
|
||||
consumes:
|
||||
|
|
|
|||
|
|
@ -2,9 +2,18 @@ import { ReqPage } from '.';
|
|||
|
||||
export namespace AI {
|
||||
export interface OllamaModelInfo {
|
||||
id: number;
|
||||
name: string;
|
||||
size: string;
|
||||
modified: 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;
|
||||
|
|
|
|||
|
|
@ -5,8 +5,11 @@ import { ResPage } from '../interface';
|
|||
export const createOllamaModel = (name: string) => {
|
||||
return http.post(`/ai/ollama/model`, { name: name });
|
||||
};
|
||||
export const deleteOllamaModel = (name: string) => {
|
||||
return http.post(`/ai/ollama/model/del`, { name: name });
|
||||
export const recreateOllamaModel = (name: string) => {
|
||||
return http.post(`/ai/ollama/model/recreate`, { name: name });
|
||||
};
|
||||
export const deleteOllamaModel = (ids: Array<number>, force: boolean) => {
|
||||
return http.post(`/ai/ollama/model/del`, { ids: ids, forceDelete: force });
|
||||
};
|
||||
export const searchOllamaModel = (params: AI.OllamaModelSearch) => {
|
||||
return http.post<ResPage<AI.OllamaModelInfo>>(`/ai/ollama/model/search`, params);
|
||||
|
|
@ -14,6 +17,9 @@ export const searchOllamaModel = (params: AI.OllamaModelSearch) => {
|
|||
export const loadOllamaModel = (name: string) => {
|
||||
return http.post<string>(`/ai/ollama/model/load`, { name: name });
|
||||
};
|
||||
export const syncOllamaModel = () => {
|
||||
return http.post<Array<AI.OllamaModelDropInfo>>(`/ai/ollama/model/sync`);
|
||||
};
|
||||
|
||||
export const loadGPUInfo = () => {
|
||||
return http.get<any>(`/ai/gpu/load`);
|
||||
|
|
|
|||
|
|
@ -72,8 +72,7 @@ const stopSignals = [
|
|||
'image pull successful!',
|
||||
'image push failed!',
|
||||
'image push successful!',
|
||||
'ollama pull failed!',
|
||||
'ollama pull successful!',
|
||||
'ollama pull completed!',
|
||||
];
|
||||
const emit = defineEmits(['update:loading', 'update:hasContent', 'update:isReading']);
|
||||
const tailLog = ref(false);
|
||||
|
|
|
|||
|
|
@ -600,6 +600,9 @@ const message = {
|
|||
create_helper: 'Pull "{0}" from Ollama.com',
|
||||
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.',
|
||||
},
|
||||
gpu: {
|
||||
gpu: 'GPU Monitor',
|
||||
|
|
|
|||
|
|
@ -600,6 +600,9 @@ const message = {
|
|||
create_helper: 'Ollama.com から "{0}" を取得',
|
||||
ollama_doc: 'Ollama の公式ウェブサイトを訪れて、さらに多くのモデルを検索して見つけることができます。',
|
||||
container_conn_helper: 'コンテナ間のアクセスまたは接続にこのアドレスを使用',
|
||||
ollama_sync: 'Ollamaモデルの同期中に、以下のモデルが存在しないことが判明しました。削除しますか?',
|
||||
from_remote: 'このモデルは1Panelを介してダウンロードされておらず、関連するプルログはありません。',
|
||||
no_logs: 'このモデルのプルログは削除されており、関連するログを表示できません。',
|
||||
},
|
||||
gpu: {
|
||||
gpu: 'GPUモニター',
|
||||
|
|
|
|||
|
|
@ -596,6 +596,9 @@ const message = {
|
|||
create_helper: 'Ollama.com에서 "{0}" 가져오기',
|
||||
ollama_doc: 'Ollama 공식 웹사이트를 방문하여 더 많은 모델을 검색하고 찾을 수 있습니다.',
|
||||
container_conn_helper: '컨테이너 간 접근 또는 연결에 이 주소를 사용',
|
||||
ollama_sync: 'Ollama 모델 동기화 중 다음 모델이 존재하지 않음을 발견했습니다. 삭제하시겠습니까?',
|
||||
from_remote: '이 모델은 1Panel을 통해 다운로드되지 않았으며 관련 풀 로그가 없습니다.',
|
||||
no_logs: '이 모델의 풀 로그가 삭제되어 관련 로그를 볼 수 없습니다.',
|
||||
},
|
||||
gpu: {
|
||||
gpu: 'GPU 모니터',
|
||||
|
|
|
|||
|
|
@ -611,6 +611,10 @@ const message = {
|
|||
create_helper: 'Tarik "{0}" dari Ollama.com',
|
||||
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.',
|
||||
},
|
||||
gpu: {
|
||||
gpu: 'Monitor GPU',
|
||||
|
|
|
|||
|
|
@ -608,6 +608,10 @@ const message = {
|
|||
create_helper: 'Puxar "{0}" do Ollama.com',
|
||||
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.',
|
||||
},
|
||||
gpu: {
|
||||
gpu: 'Monitor de GPU',
|
||||
|
|
|
|||
|
|
@ -606,6 +606,10 @@ const message = {
|
|||
create_helper: 'Загрузить "{0}" с Ollama.com',
|
||||
ollama_doc: 'Вы можете посетить официальный сайт Ollama, чтобы искать и находить больше моделей.',
|
||||
container_conn_helper: 'Используйте этот адрес для доступа или подключения между контейнерами',
|
||||
ollama_sync:
|
||||
'Синхронизация модели Ollama обнаружила, что следующие модели не существуют, хотите удалить их?',
|
||||
from_remote: 'Эта модель не была загружена через 1Panel, нет связанных журналов извлечения.',
|
||||
no_logs: 'Журналы извлечения для этой модели были удалены и не могут быть просмотрены.',
|
||||
},
|
||||
gpu: {
|
||||
gpu: 'Мониторинг GPU',
|
||||
|
|
|
|||
|
|
@ -581,6 +581,9 @@ const message = {
|
|||
create_helper: '從 Ollama.com 拉取 "{0}"',
|
||||
ollama_doc: '您可以訪問 Ollama 官方網站,搜索並查找更多模型。',
|
||||
container_conn_helper: '容器間訪問或連接使用此地址',
|
||||
ollama_sync: '同步 Ollama 模型發現下列模型不存在,是否刪除?',
|
||||
from_remote: '該模型並非通過 1Panel 下載,無相關拉取日誌。',
|
||||
no_logs: '該模型的拉取日誌已被刪除,無法查看相關日誌。',
|
||||
},
|
||||
gpu: {
|
||||
gpu: 'GPU 监控',
|
||||
|
|
|
|||
|
|
@ -582,6 +582,9 @@ const message = {
|
|||
create_helper: '从 Ollama.com 拉取 "{0}"',
|
||||
ollama_doc: '您可以访问 Ollama 官网,搜索并查找更多模型。',
|
||||
container_conn_helper: '容器间访问或连接使用此地址',
|
||||
ollama_sync: '同步 Ollama 模型发现下列模型不存在,是否删除?',
|
||||
from_remote: '该模型并非通过 1Panel 下载,无相关拉取日志。',
|
||||
no_logs: '该模型的拉取日志已被删除,无法查看相关日志。',
|
||||
},
|
||||
gpu: {
|
||||
gpu: 'GPU 监控',
|
||||
|
|
|
|||
118
frontend/src/views/ai/model/del/index.vue
Normal file
118
frontend/src/views/ai/model/del/index.vue
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
<template>
|
||||
<div>
|
||||
<el-dialog
|
||||
v-model="open"
|
||||
:title="$t('commons.button.sync')"
|
||||
width="30%"
|
||||
:close-on-click-modal="false"
|
||||
@close="handleClose"
|
||||
>
|
||||
<div v-loading="loading">
|
||||
<el-row type="flex" justify="center">
|
||||
<el-col :span="22">
|
||||
<el-alert class="mt-2" :show-icon="true" type="warning" :closable="false">
|
||||
{{ $t('ai_tools.model.ollama_sync') }}
|
||||
</el-alert>
|
||||
<el-checkbox
|
||||
class="mt-2"
|
||||
v-model="checkAll"
|
||||
:indeterminate="isIndeterminate"
|
||||
@change="handleCheckAllChange"
|
||||
>
|
||||
{{ $t('setting.all') }}
|
||||
</el-checkbox>
|
||||
<el-checkbox-group v-model="checkedItems" @change="handleCheckedChange">
|
||||
<el-checkbox
|
||||
v-for="(item, index) in list"
|
||||
:key="index"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-checkbox-group>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="handleClose()" :disabled="loading">
|
||||
{{ $t('commons.button.cancel') }}
|
||||
</el-button>
|
||||
<el-button type="primary" @click="onConfirm" :disabled="loading">
|
||||
{{ $t('commons.button.confirm') }}
|
||||
</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { AI } from '@/api/interface/ai';
|
||||
import { deleteOllamaModel } from '@/api/modules/ai';
|
||||
import i18n from '@/lang';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
import { CheckboxValueType } from 'element-plus';
|
||||
import { onMounted, ref } from 'vue';
|
||||
|
||||
defineOptions({ name: 'OpDialog' });
|
||||
|
||||
const checkAll = ref(false);
|
||||
const isIndeterminate = ref(true);
|
||||
const checkedItems = ref([]);
|
||||
const list = ref([]);
|
||||
|
||||
const loading = ref();
|
||||
const open = ref();
|
||||
|
||||
interface DialogProps {
|
||||
list: Array<AI.OllamaModelDropInfo>;
|
||||
}
|
||||
const acceptParams = (props: DialogProps): void => {
|
||||
list.value = props.list;
|
||||
checkAll.value = true;
|
||||
handleCheckAllChange(true);
|
||||
open.value = true;
|
||||
};
|
||||
|
||||
const emit = defineEmits(['search']);
|
||||
|
||||
const handleCheckAllChange = (val: CheckboxValueType) => {
|
||||
checkedItems.value = [];
|
||||
if (val) {
|
||||
for (const item of list.value) {
|
||||
checkedItems.value.push(item.id);
|
||||
}
|
||||
}
|
||||
isIndeterminate.value = false;
|
||||
};
|
||||
const handleCheckedChange = (value: CheckboxValueType[]) => {
|
||||
const checkedCount = value.length;
|
||||
checkAll.value = checkedCount === list.value.length;
|
||||
isIndeterminate.value = checkedCount > 0 && checkedCount < list.value.length;
|
||||
};
|
||||
|
||||
const onConfirm = async () => {
|
||||
loading.value = true;
|
||||
await deleteOllamaModel(checkedItems.value, true)
|
||||
.then(() => {
|
||||
emit('search');
|
||||
MsgSuccess(i18n.global.t('commons.msg.deleteSuccess'));
|
||||
open.value = false;
|
||||
loading.value = false;
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
emit('search');
|
||||
open.value = false;
|
||||
};
|
||||
|
||||
onMounted(() => {});
|
||||
|
||||
defineExpose({
|
||||
acceptParams,
|
||||
});
|
||||
</script>
|
||||
|
|
@ -33,6 +33,9 @@
|
|||
<el-button :disabled="modelInfo.status !== 'Running'" @click="onLoadConn" type="primary" plain>
|
||||
{{ $t('database.databaseConnInfo') }}
|
||||
</el-button>
|
||||
<el-button :disabled="modelInfo.status !== 'Running'" type="primary" plain @click="onSync()">
|
||||
{{ $t('database.loadFromRemote') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
:disabled="modelInfo.status !== 'Running'"
|
||||
icon="Position"
|
||||
|
|
@ -42,6 +45,9 @@
|
|||
>
|
||||
OpenWebUI
|
||||
</el-button>
|
||||
<el-button plain :disabled="selects.length === 0" type="primary" @click="onDelete(null)">
|
||||
{{ $t('commons.button.delete') }}
|
||||
</el-button>
|
||||
</div>
|
||||
<div>
|
||||
<TableSearch @search="search()" v-model:searchName="searchName" />
|
||||
|
|
@ -51,36 +57,61 @@
|
|||
<template #main v-if="modelInfo.isExist">
|
||||
<ComplexTable
|
||||
:pagination-config="paginationConfig"
|
||||
v-model:selects="selects"
|
||||
:class="{ mask: maskShow }"
|
||||
@sort-change="search"
|
||||
@search="search"
|
||||
:data="data"
|
||||
>
|
||||
<el-table-column :label="$t('commons.table.name')" prop="name" min-width="90">
|
||||
<el-table-column type="selection" :selectable="selectable" fix />
|
||||
<el-table-column :label="$t('ai_tools.model.model')" prop="name" min-width="90">
|
||||
<template #default="{ row }">
|
||||
<el-text
|
||||
v-if="row.size !== '-'"
|
||||
type="primary"
|
||||
class="cursor-pointer"
|
||||
@click="onLoad(row.name)"
|
||||
>
|
||||
<el-text v-if="row.size" type="primary" class="cursor-pointer" @click="onLoad(row.name)">
|
||||
{{ row.name }}
|
||||
</el-text>
|
||||
<span v-else>{{ row.name }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('file.size')" prop="size" />
|
||||
<el-table-column :label="$t('file.size')" prop="size">
|
||||
<template #default="{ row }">
|
||||
<span>{{ row.size || '-' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('commons.table.status')" prop="status">
|
||||
<template #default="{ row }">
|
||||
<el-tag v-if="row.status === 'Success'" type="success">
|
||||
{{ $t('commons.status.success') }}
|
||||
</el-tag>
|
||||
<el-tag v-if="row.status === 'Deleted'" type="info">
|
||||
{{ $t('database.isDelete') }}
|
||||
</el-tag>
|
||||
<el-tag v-if="row.status === 'Failed'" type="danger">
|
||||
{{ $t('commons.status.failed') }}
|
||||
</el-tag>
|
||||
<el-tag v-if="row.status === 'Waiting'">
|
||||
<el-icon v-if="row.status === 'Waiting'" class="is-loading">
|
||||
<Loading />
|
||||
</el-icon>
|
||||
{{ $t('commons.status.waiting') }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('commons.button.log')">
|
||||
<template #default="{ row }">
|
||||
<el-button @click="onLoadLog(row.name)" link type="primary">
|
||||
<el-button @click="onLoadLog(row)" link type="primary">
|
||||
{{ $t('website.check') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('commons.table.createdAt')" prop="modified" />
|
||||
<el-table-column
|
||||
min-width="100"
|
||||
:label="$t('commons.table.createdAt')"
|
||||
prop="createdAt"
|
||||
:formatter="dateFormat"
|
||||
/>
|
||||
<fu-table-operations
|
||||
:ellipsis="mobile ? 0 : 10"
|
||||
:min-width="mobile ? 'auto' : 400"
|
||||
:min-width="mobile ? 'auto' : 100"
|
||||
:buttons="buttons"
|
||||
:label="$t('commons.table.operate')"
|
||||
fixed="right"
|
||||
|
|
@ -116,8 +147,21 @@
|
|||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<OpDialog ref="opRef" @search="search" @submit="onSubmitDelete()">
|
||||
<template #content>
|
||||
<el-form class="mt-4 mb-1" ref="deleteForm" label-position="left">
|
||||
<el-form-item>
|
||||
<el-checkbox v-model="forceDelete" :label="$t('website.forceDelete')" />
|
||||
<span class="input-help">
|
||||
{{ $t('website.forceDeleteHelper') }}
|
||||
</span>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</template>
|
||||
</OpDialog>
|
||||
<AddDialog ref="addRef" @search="search" @log="onLoadLog" />
|
||||
<Log ref="logRef" @close="search" />
|
||||
<Del ref="delRef" @search="search" />
|
||||
<Conn ref="connRef" />
|
||||
<CodemirrorDialog ref="detailRef" />
|
||||
<PortJumpDialog ref="dialogPortJumpRef" />
|
||||
|
|
@ -129,6 +173,7 @@
|
|||
import AppStatus from '@/components/app-status/index.vue';
|
||||
import AddDialog from '@/views/ai/model/add/index.vue';
|
||||
import Conn from '@/views/ai/model/conn/index.vue';
|
||||
import Del from '@/views/ai/model/del/index.vue';
|
||||
import Log from '@/components/log-dialog/index.vue';
|
||||
import PortJumpDialog from '@/components/port-jump/index.vue';
|
||||
import CodemirrorDialog from '@/components/codemirror-dialog/index.vue';
|
||||
|
|
@ -136,19 +181,28 @@ import { computed, onMounted, reactive, ref } from 'vue';
|
|||
import i18n from '@/lang';
|
||||
import { App } from '@/api/interface/app';
|
||||
import { GlobalStore } from '@/store';
|
||||
import { deleteOllamaModel, loadOllamaModel, searchOllamaModel } from '@/api/modules/ai';
|
||||
import {
|
||||
deleteOllamaModel,
|
||||
loadOllamaModel,
|
||||
recreateOllamaModel,
|
||||
searchOllamaModel,
|
||||
syncOllamaModel,
|
||||
} from '@/api/modules/ai';
|
||||
import { AI } from '@/api/interface/ai';
|
||||
import { GetAppPort } from '@/api/modules/app';
|
||||
import { dateFormat } from '@/utils/util';
|
||||
import router from '@/routers';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
import { MsgInfo, MsgSuccess } from '@/utils/message';
|
||||
import BindDomain from '@/views/ai/model/domain/index.vue';
|
||||
const globalStore = GlobalStore();
|
||||
|
||||
const loading = ref(false);
|
||||
const selects = ref<any>([]);
|
||||
const maskShow = ref(true);
|
||||
const addRef = ref();
|
||||
const logRef = ref();
|
||||
const detailRef = ref();
|
||||
const delRef = ref();
|
||||
const connRef = ref();
|
||||
const openWebUIPort = ref();
|
||||
const dashboardVisible = ref(false);
|
||||
|
|
@ -165,6 +219,10 @@ const paginationConfig = reactive({
|
|||
const searchName = ref();
|
||||
const appInstallID = ref(0);
|
||||
|
||||
const opRef = ref();
|
||||
const operateIDs = ref();
|
||||
const forceDelete = ref();
|
||||
|
||||
const modelInfo = reactive({
|
||||
status: '',
|
||||
container: '',
|
||||
|
|
@ -177,6 +235,10 @@ const mobile = computed(() => {
|
|||
return globalStore.isMobile();
|
||||
});
|
||||
|
||||
function selectable(row) {
|
||||
return row.status !== 'Waiting';
|
||||
}
|
||||
|
||||
const search = async () => {
|
||||
let params = {
|
||||
page: paginationConfig.currentPage,
|
||||
|
|
@ -199,6 +261,23 @@ const onCreate = async () => {
|
|||
addRef.value.acceptParams();
|
||||
};
|
||||
|
||||
const onSync = async () => {
|
||||
loading.value = true;
|
||||
await syncOllamaModel()
|
||||
.then((res) => {
|
||||
loading.value = false;
|
||||
if (res.data) {
|
||||
delRef.value.acceptParams({ list: res.data });
|
||||
} else {
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
search();
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
};
|
||||
|
||||
const onLoadConn = async () => {
|
||||
connRef.value.acceptParams({ port: modelInfo.port, containerName: modelInfo.container });
|
||||
};
|
||||
|
|
@ -246,35 +325,87 @@ const checkExist = (data: App.CheckInstalled) => {
|
|||
}
|
||||
};
|
||||
|
||||
const onSubmitDelete = async () => {
|
||||
loading.value = true;
|
||||
await deleteOllamaModel(operateIDs.value, forceDelete.value)
|
||||
.then(() => {
|
||||
loading.value = false;
|
||||
MsgSuccess(i18n.global.t('commons.msg.deleteSuccess'));
|
||||
search();
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
};
|
||||
|
||||
const onReCreate = async (name: string) => {
|
||||
loading.value = true;
|
||||
await recreateOllamaModel(name)
|
||||
.then(() => {
|
||||
loading.value = false;
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
search();
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
};
|
||||
|
||||
const onDelete = async (row: AI.OllamaModelInfo) => {
|
||||
ElMessageBox.confirm(i18n.global.t('commons.msg.delete'), i18n.global.t('commons.button.delete'), {
|
||||
confirmButtonText: i18n.global.t('commons.button.confirm'),
|
||||
cancelButtonText: i18n.global.t('commons.button.cancel'),
|
||||
type: 'info',
|
||||
}).then(async () => {
|
||||
loading.value = true;
|
||||
await deleteOllamaModel(row.name)
|
||||
.then(() => {
|
||||
loading.value = false;
|
||||
search();
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
let names = [];
|
||||
let ids = [];
|
||||
if (row) {
|
||||
ids = [row.id];
|
||||
names = [row.name];
|
||||
} else {
|
||||
for (const item of selects.value) {
|
||||
names.push(item.name);
|
||||
ids.push(item.id);
|
||||
}
|
||||
}
|
||||
operateIDs.value = ids;
|
||||
opRef.value.acceptParams({
|
||||
title: i18n.global.t('commons.button.delete'),
|
||||
names: names,
|
||||
msg: i18n.global.t('commons.msg.operatorHelper', [
|
||||
i18n.global.t('cronjob.cronTask'),
|
||||
i18n.global.t('commons.button.delete'),
|
||||
]),
|
||||
api: null,
|
||||
params: null,
|
||||
});
|
||||
};
|
||||
|
||||
const onLoadLog = (name: string) => {
|
||||
logRef.value.acceptParams({ id: 0, type: 'ollama-model', name: name, tail: true });
|
||||
const onLoadLog = (row: AI.OllamaModelInfo) => {
|
||||
if (row.from === 'remote') {
|
||||
MsgInfo(i18n.global.t('ai_tools.model.from_remote'));
|
||||
return;
|
||||
}
|
||||
if (!row.logFileExist) {
|
||||
MsgInfo(i18n.global.t('ai_tools.model.no_logs'));
|
||||
return;
|
||||
}
|
||||
logRef.value.acceptParams({ id: 0, type: 'ollama-model', name: row.name, tail: true });
|
||||
};
|
||||
|
||||
const buttons = [
|
||||
{
|
||||
label: i18n.global.t('commons.button.retry'),
|
||||
click: (row: AI.OllamaModelInfo) => {
|
||||
onReCreate(row.name);
|
||||
},
|
||||
disabled: (row: any) => {
|
||||
return row.status === 'Success' || row.status === 'Waiting';
|
||||
},
|
||||
},
|
||||
{
|
||||
label: i18n.global.t('commons.button.delete'),
|
||||
click: (row: AI.OllamaModelInfo) => {
|
||||
onDelete(row);
|
||||
},
|
||||
disabled: (row: any) => {
|
||||
return row.status !== 'Success';
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue