Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions agent/app/api/v2/ai.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ func (b *BaseApi) CreateOllamaModel(c *gin.Context) {
return
}

if err := aiToolService.Create(req.Name); err != nil {
if err := aiToolService.Create(req); err != nil {
helper.BadRequest(c, err)
return
}
Expand All @@ -46,7 +46,7 @@ func (b *BaseApi) RecreateOllamaModel(c *gin.Context) {
return
}

if err := aiToolService.Recreate(req.Name); err != nil {
if err := aiToolService.Recreate(req); err != nil {
helper.BadRequest(c, err)
return
}
Expand Down
3 changes: 2 additions & 1 deletion agent/app/dto/ai.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ type OllamaModelDropList struct {
}

type OllamaModelName struct {
Name string `json:"name"`
Name string `json:"name"`
TaskID string `json:"taskID"`
}

type OllamaBindDomain struct {
Expand Down
84 changes: 39 additions & 45 deletions agent/app/service/ai.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,20 @@ package service
import (
"context"
"fmt"
"io"
"os"
"os/exec"
"path"
"strings"
"time"

"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/app/task"
"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/i18n"
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
"github.com/1Panel-dev/1Panel/agent/utils/common"
"github.com/jinzhu/copier"
Expand All @@ -25,9 +26,9 @@ type AIToolService struct{}

type IAIToolService interface {
Search(search dto.SearchWithPage) (int64, []dto.OllamaModelInfo, error)
Create(name string) error
Create(req dto.OllamaModelName) error
Close(name string) error
Recreate(name string) error
Recreate(req dto.OllamaModelName) error
Delete(req dto.ForceDelete) error
Sync() ([]dto.OllamaModelDropList, error)
LoadDetail(name string) (string, error)
Expand Down Expand Up @@ -55,8 +56,8 @@ func (u *AIToolService) Search(req dto.SearchWithPage) (int64, []dto.OllamaModel
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 {
taskModel, _ := taskRepo.GetFirst(taskRepo.WithResourceID(item.ID), repo.WithByType(task.TaskScopeAI))
if len(taskModel.ID) != 0 {
item.LogFileExist = true
}
dtoLists = append(dtoLists, item)
Expand All @@ -79,37 +80,46 @@ func (u *AIToolService) LoadDetail(name string) (string, error) {
return stdout, err
}

func (u *AIToolService) Create(name string) error {
if cmd.CheckIllegal(name) {
func (u *AIToolService) Create(req dto.OllamaModelName) error {
if cmd.CheckIllegal(req.Name) {
return buserr.New("ErrCmdIllegal")
}
modelInfo, _ := aiRepo.Get(repo.WithByName(name))
modelInfo, _ := aiRepo.Get(repo.WithByName(req.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,
Name: req.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)
taskItem, err := task.NewTaskWithOps(fmt.Sprintf("ollama-model-%s", req.Name), task.TaskPull, task.TaskScopeAI, req.TaskID, info.ID)
if err != nil {
global.LOG.Errorf("new task for exec shell failed, err: %v", err)
return err
}
go pullOllamaModel(file, containerName, info)
go func() {
taskItem.AddSubTask(i18n.GetWithName("OllamaModelPull", req.Name), func(t *task.Task) error {
return cmd.ExecShellWithTask(taskItem, time.Hour, "docker", "exec", containerName, "ollama", "pull", info.Name)
}, nil)
taskItem.AddSubTask(i18n.GetWithName("OllamaModelSize", req.Name), func(t *task.Task) error {
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()})
}
return nil
}, nil)
_ = taskItem.Execute()
}()
return nil
}

Expand All @@ -128,11 +138,11 @@ func (u *AIToolService) Close(name string) error {
return nil
}

func (u *AIToolService) Recreate(name string) error {
if cmd.CheckIllegal(name) {
func (u *AIToolService) Recreate(req dto.OllamaModelName) error {
if cmd.CheckIllegal(req.Name) {
return buserr.New("ErrCmdIllegal")
}
modelInfo, _ := aiRepo.Get(repo.WithByName(name))
modelInfo, _ := aiRepo.Get(repo.WithByName(req.Name))
if modelInfo.ID == 0 {
return buserr.New("ErrRecordNotFound")
}
Expand All @@ -143,17 +153,17 @@ func (u *AIToolService) Recreate(name string) error {
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)
taskItem, err := task.NewTaskWithOps(fmt.Sprintf("ollama-model-%s", req.Name), task.TaskPull, task.TaskScopeAI, req.TaskID, modelInfo.ID)
if err != nil {
global.LOG.Errorf("new task for exec shell failed, err: %v", err)
return err
}
go pullOllamaModel(file, containerName, modelInfo)
go func() {
taskItem.AddSubTask(i18n.GetWithName("OllamaModelPull", req.Name), func(t *task.Task) error {
return cmd.ExecShellWithTask(taskItem, time.Hour, "docker", "exec", containerName, "ollama", "pull", req.Name)
}, nil)
_ = taskItem.Execute()
}()
return nil
}

Expand Down Expand Up @@ -354,22 +364,6 @@ func LoadContainerName() (string, error) {
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 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The provided Go code was written to perform operations on artificial intelligence tools. Please review the code snippet carefully and let me know if you need assistance with anything specifically from it.

To summarize:

  1. The code is well-written, but it does contain some errors such as duplicate function definitions, duplicated imports of copier.
  2. It also contains unnecessary comments that may be removed without loss in meaning.
  3. There are a couple of typos (* instead of %s), but they won't affect readability significantly.
  4. Some sections like log paths ("log"), which don't appear elsewhere, might need documentation about where these logs are sent and who reads them in order to fully utilize this code, especially when using AI services.

Expand Down
2 changes: 1 addition & 1 deletion agent/app/service/cronjob_helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ func (u *CronjobService) handleCurl(cronjob model.Cronjob, taskID string) error
}

taskItem.AddSubTask(i18n.GetWithName("HandleShell", cronjob.Name), func(t *task.Task) error {
if err := cmd.ExecShellWithTask(taskItem, 24*time.Hour, "bash", "-c", "curl", cronjob.URL); err != nil {
if err := cmd.ExecShellWithTask(taskItem, 24*time.Hour, "curl", cronjob.URL); err != nil {
return err
}
return nil
Expand Down
6 changes: 1 addition & 5 deletions agent/app/service/firewall.go
Original file line number Diff line number Diff line change
Expand Up @@ -610,11 +610,7 @@ func (u *FirewallService) updatePingStatus(enable string) error {
}

func (u *FirewallService) addPortsBeforeStart(client firewall.FirewallClient) error {
serverPort, err := settingRepo.Get(settingRepo.WithByKey("ServerPort"))
if err != nil {
return err
}
if err := client.Port(fireClient.FireInfo{Port: serverPort.Value, Protocol: "tcp", Strategy: "accept"}, "add"); err != nil {
if err := client.Port(fireClient.FireInfo{Port: global.CONF.Base.Port, Protocol: "tcp", Strategy: "accept"}, "add"); err != nil {
return err
}
if err := client.Port(fireClient.FireInfo{Port: "22", Protocol: "tcp", Strategy: "accept"}, "add"); err != nil {
Expand Down
1 change: 1 addition & 0 deletions agent/app/task/task.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ const (

const (
TaskScopeWebsite = "Website"
TaskScopeAI = "AI"
TaskScopeApp = "App"
TaskScopeRuntime = "Runtime"
TaskScopeDatabase = "Database"
Expand Down
1 change: 1 addition & 0 deletions agent/constant/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package constant

const (
StatusRunning = "Running"
StatusCanceled = "Canceled"
StatusDone = "Done"
StatusWaiting = "Waiting"
StatusSuccess = "Success"
Expand Down
4 changes: 4 additions & 0 deletions agent/i18n/lang/zh.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,10 @@ SubTask: "子任务"
RuntimeExtension: "运行环境扩展"
TaskIsExecuting: "任务正在运行"

# task - ai
OllamaModelPull: "拉取 Ollama 模型 {{ .name }} "
OllamaModelSize: "获取 Ollama 模型 {{ .name }} 大小 "

# task - snapshot
Snapshot: "快照"
SnapDBInfo: "写入 1Panel 数据库信息"
Expand Down
6 changes: 6 additions & 0 deletions agent/init/hook/hook.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ func Init() {
initGlobalData()
handleCronjobStatus()
handleSnapStatus()
handleOllamaModelStatus()

loadLocalDir()
}
Expand Down Expand Up @@ -82,6 +83,11 @@ func handleCronjobStatus() {
}
}

func handleOllamaModelStatus() {
message := "the task was interrupted due to the restart of the 1panel service"
_ = global.DB.Model(&model.OllamaModel{}).Where("status = ?", constant.StatusWaiting).Updates(map[string]interface{}{"status": constant.StatusCanceled, "message": message}).Error
}

func handleCronJobAlert(cronjob *model.Cronjob) {
pushAlert := dto.PushAlert{
TaskName: cronjob.Name,
Expand Down
18 changes: 17 additions & 1 deletion agent/utils/cmd/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,12 +146,27 @@ func ExecShell(outPath string, timeout time.Duration, name string, arg ...string

type CustomWriter struct {
taskItem *task.Task
buffer bytes.Buffer
}

func (cw *CustomWriter) Write(p []byte) (n int, err error) {
cw.taskItem.Log(string(p))
cw.buffer.Write(p)
lines := strings.Split(cw.buffer.String(), "\n")

for i := 0; i < len(lines)-1; i++ {
cw.taskItem.Log(lines[i])
}
cw.buffer.Reset()
cw.buffer.WriteString(lines[len(lines)-1])

return len(p), nil
}
func (cw *CustomWriter) Flush() {
if cw.buffer.Len() > 0 {
cw.taskItem.Log(cw.buffer.String())
cw.buffer.Reset()
}
}
func ExecShellWithTask(taskItem *task.Task, timeout time.Duration, name string, arg ...string) error {
env := os.Environ()
customWriter := &CustomWriter{taskItem: taskItem}
Expand All @@ -165,6 +180,7 @@ func ExecShellWithTask(taskItem *task.Task, timeout time.Duration, name string,
done := make(chan error, 1)
go func() {
done <- cmd.Wait()
customWriter.Flush()
}()
after := time.After(timeout)
select {
Expand Down
8 changes: 5 additions & 3 deletions core/init/router/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@ package router
import (
"encoding/base64"
"fmt"
"github.com/1Panel-dev/1Panel/core/app/repo"
"github.com/1Panel-dev/1Panel/core/utils/common"
"net/http"
"path"
"regexp"
"strconv"
"strings"

"github.com/1Panel-dev/1Panel/core/app/repo"
"github.com/1Panel-dev/1Panel/core/utils/common"

"github.com/1Panel-dev/1Panel/core/app/service"
"github.com/1Panel-dev/1Panel/core/cmd/server/res"
"github.com/1Panel-dev/1Panel/core/constant"
Expand Down Expand Up @@ -155,7 +157,7 @@ func checkSession(c *gin.Context) bool {
func setWebStatic(rootRouter *gin.RouterGroup) {
rootRouter.StaticFS("/public", http.FS(web.Favicon))
rootRouter.StaticFS("/favicon.ico", http.FS(web.Favicon))
rootRouter.Static("/api/v2/images", "./uploads")
rootRouter.Static("/api/v2/images", path.Join(global.CONF.Base.InstallDir, "1panel/uploads/theme"))
rootRouter.Use(func(c *gin.Context) {
c.Next()
})
Expand Down
8 changes: 4 additions & 4 deletions frontend/src/api/modules/ai.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ 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 createOllamaModel = (name: string, taskID: string) => {
return http.post(`/ai/ollama/model`, { name: name, taskID: taskID });
};
export const recreateOllamaModel = (name: string) => {
return http.post(`/ai/ollama/model/recreate`, { name: name });
export const recreateOllamaModel = (name: string, taskID: string) => {
return http.post(`/ai/ollama/model/recreate`, { name: name, taskID: taskID });
};
export const deleteOllamaModel = (ids: Array<number>, force: boolean) => {
return http.post(`/ai/ollama/model/del`, { ids: ids, forceDelete: force });
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/lang/modules/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,8 @@ const message = {
},
msg: {
noneData: 'No data available',
disConn:
'Please click the disconnect button directly to terminate the terminal connection, avoiding the use of exit commands like {0}.',
delete: 'This operation delete cannot be rolled back. Do you want to continue?',
clean: 'This operation clean cannot be rolled back. Do you want to continue?',
deleteSuccess: 'Delete Success',
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/lang/modules/ja.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,8 @@ const message = {
},
msg: {
noneData: '利用可能なデータはありません',
disConn:
'端末接続を切断するには、{0} のような終了コマンドを使用せずに、直接切断ボタンをクリックしてください',
delete: `この操作削除は元に戻すことはできません。続けたいですか?`,
clean: `この操作は取り消すことはできません。続けたいですか?`,
deleteSuccess: '正常に削除されました',
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/lang/modules/ko.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,8 @@ const message = {
},
msg: {
noneData: '데이터가 없습니다',
disConn:
'종료 명령어인 {0} 등을 사용하지 않고 직접 연결 끊기 버튼을 클릭하여 터미널 연결을 종료해 주십시오.',
delete: `이 작업은 되돌릴 수 없습니다. 계속하시겠습니까?`,
clean: `이 작업은 되돌릴 수 없습니다. 계속하시겠습니까?`,
deleteSuccess: '삭제 완료',
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/lang/modules/ms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,8 @@ const message = {
},
msg: {
noneData: 'Tiada data tersedia',
disConn:
'Sila klik butang putus sambungan secara langsung untuk menamatkan sambungan terminal, mengelakkan penggunaan arahan keluar seperti {0}.',
delete: 'Operasi ini tidak boleh diundur. Adakah anda mahu meneruskan?',
clean: 'Operasi ini tidak boleh diundur. Adakah anda mahu meneruskan?',
deleteSuccess: 'Berjaya dipadam',
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/lang/modules/pt-br.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,8 @@ const message = {
},
msg: {
noneData: 'Nenhum dado disponível',
disConn:
'Por favor, clique diretamente no botão de desconexão para encerrar a conexão do terminal, evitando o uso de comandos de saída como {0}.',
delete: 'Esta operação de exclusão não pode ser desfeita. Deseja continuar?',
clean: 'Esta operação de limpeza não pode ser desfeita. Deseja continuar?',
deleteSuccess: 'Excluído com sucesso',
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/lang/modules/ru.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,8 @@ const message = {
},
msg: {
noneData: 'Нет данных',
disConn:
'Пожалуйста, нажмите кнопку отключения, чтобы разорвать соединение с терминалом, избегая использования команд выхода, таких как {0}.',
delete: 'Эта операция удаления не может быть отменена. Хотите продолжить?',
clean: 'Эта операция очистки не может быть отменена. Хотите продолжить?',
deleteSuccess: 'Успешно удалено',
Expand Down
Loading
Loading