mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-02-28 19:14:13 +08:00
388 lines
11 KiB
Go
388 lines
11 KiB
Go
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 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, Port: 80}},
|
|
Alias: strings.ToLower(req.Domain),
|
|
Type: constant.Deployment,
|
|
AppType: constant.InstalledApp,
|
|
AppInstallID: req.AppInstallID,
|
|
}
|
|
if req.SSLID > 0 {
|
|
createWebsiteReq.WebsiteSSLID = req.SSLID
|
|
createWebsiteReq.EnableSSL = true
|
|
}
|
|
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 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
|
|
ssl, _ := websiteSSLRepo.GetFirst(repo.WithByID(website.WebsiteSSLID))
|
|
res.AcmeAccountID = ssl.AcmeAccountID
|
|
}
|
|
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))
|
|
}
|