mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-10-30 18:56:20 +08:00
feat: Support Ollama model management (#7866)
Some checks failed
SonarCloud Scan / SonarCloud (push) Failing after -3s
Some checks failed
SonarCloud Scan / SonarCloud (push) Failing after -3s
This commit is contained in:
parent
f233ef7069
commit
af8eef4a91
32 changed files with 2076 additions and 27 deletions
112
backend/app/api/v1/ai_tool.go
Normal file
112
backend/app/api/v1/ai_tool.go
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
package v1
|
||||
|
||||
import (
|
||||
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/ai_tools/gpu"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/ai_tools/gpu/common"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/ai_tools/xpu"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// @Tags AITools
|
||||
// @Summary Create Ollama model
|
||||
// @Accept json
|
||||
// @Param request body dto.OllamaModelName true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /aitools/ollama/model [post]
|
||||
// @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"添加模型 [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.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, nil)
|
||||
}
|
||||
|
||||
// @Tags AITools
|
||||
// @Summary Page Ollama models
|
||||
// @Accept json
|
||||
// @Param request body dto.SearchWithPage true "request"
|
||||
// @Success 200 {object} dto.PageResult
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /aitools/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.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
|
||||
helper.SuccessWithData(c, dto.PageResult{
|
||||
Items: list,
|
||||
Total: total,
|
||||
})
|
||||
}
|
||||
|
||||
// @Tags AITools
|
||||
// @Summary Delete Ollama model
|
||||
// @Accept json
|
||||
// @Param request body dto.OllamaModelName true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /aitool/ollama/model/del [post]
|
||||
func (b *BaseApi) DeleteOllamaModel(c *gin.Context) {
|
||||
var req dto.OllamaModelName
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := AIToolService.Delete(req.Name); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
|
||||
helper.SuccessWithOutData(c)
|
||||
}
|
||||
|
||||
// @Tags AITools
|
||||
// @Summary Load gpu / xpu info
|
||||
// @Accept json
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /aitool/gpu/load [get]
|
||||
func (b *BaseApi) LoadGpuInfo(c *gin.Context) {
|
||||
ok, client := gpu.New()
|
||||
if ok {
|
||||
info, err := client.LoadGpuInfo()
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, info)
|
||||
return
|
||||
}
|
||||
xpuOK, xpuClient := xpu.New()
|
||||
if xpuOK {
|
||||
info, err := xpuClient.LoadGpuInfo()
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, info)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, &common.GpuInfo{})
|
||||
}
|
||||
|
|
@ -15,6 +15,8 @@ var (
|
|||
appService = service.NewIAppService()
|
||||
appInstallService = service.NewIAppInstalledService()
|
||||
|
||||
AIToolService = service.NewIAIToolService()
|
||||
|
||||
containerService = service.NewIContainerService()
|
||||
composeTemplateService = service.NewIComposeTemplateService()
|
||||
imageRepoService = service.NewIImageRepoService()
|
||||
|
|
|
|||
11
backend/app/dto/ai_tool.go
Normal file
11
backend/app/dto/ai_tool.go
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
package dto
|
||||
|
||||
type OllamaModelInfo struct {
|
||||
Name string `json:"name"`
|
||||
Size string `json:"size"`
|
||||
Modified string `json:"modified"`
|
||||
}
|
||||
|
||||
type OllamaModelName struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
166
backend/app/service/ai_tool.go
Normal file
166
backend/app/service/ai_tool.go
Normal file
|
|
@ -0,0 +1,166 @@
|
|||
package service
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||
"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"
|
||||
)
|
||||
|
||||
type AIToolService struct{}
|
||||
|
||||
type IAIToolService interface {
|
||||
Search(search dto.SearchWithPage) (int64, []dto.OllamaModelInfo, error)
|
||||
Create(name string) error
|
||||
Delete(name string) error
|
||||
}
|
||||
|
||||
func NewIAIToolService() IAIToolService {
|
||||
return &AIToolService{}
|
||||
}
|
||||
|
||||
func (u *AIToolService) Search(req dto.SearchWithPage) (int64, []dto.OllamaModelInfo, error) {
|
||||
ollamaBaseInfo, err := appInstallRepo.LoadBaseInfo("ollama", "")
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
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[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: "-"})
|
||||
}
|
||||
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++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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]
|
||||
}
|
||||
return int64(total), records, err
|
||||
}
|
||||
|
||||
func (u *AIToolService) Create(name string) error {
|
||||
if cmd.CheckIllegal(name) {
|
||||
return buserr.New(constant.ErrCmdIllegal)
|
||||
}
|
||||
ollamaBaseInfo, err := appInstallRepo.LoadBaseInfo("ollama", "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fileName := strings.ReplaceAll(name, ":", "-")
|
||||
logItem := path.Join(global.CONF.System.DataDir, "log", "AITools", fileName)
|
||||
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 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!")
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *AIToolService) Delete(name string) error {
|
||||
if cmd.CheckIllegal(name) {
|
||||
return buserr.New(constant.ErrCmdIllegal)
|
||||
}
|
||||
ollamaBaseInfo, err := appInstallRepo.LoadBaseInfo("ollama", "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
stdout, err := cmd.Execf("docker exec %s ollama list", ollamaBaseInfo.ContainerName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
isExist := false
|
||||
lines := strings.Split(stdout, "\n")
|
||||
for _, line := range lines {
|
||||
parts := strings.Fields(line)
|
||||
if len(parts) < 5 {
|
||||
continue
|
||||
}
|
||||
if parts[0] == "NAME" {
|
||||
continue
|
||||
}
|
||||
if parts[0] == name {
|
||||
isExist = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
|
@ -10,19 +10,19 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/shirou/gopsutil/v3/load"
|
||||
"github.com/shirou/gopsutil/v3/mem"
|
||||
"github.com/shirou/gopsutil/v3/net"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/ai_tools/gpu"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/ai_tools/xpu"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/copier"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/xpack"
|
||||
"github.com/shirou/gopsutil/v3/cpu"
|
||||
"github.com/shirou/gopsutil/v3/disk"
|
||||
"github.com/shirou/gopsutil/v3/host"
|
||||
"github.com/shirou/gopsutil/v3/load"
|
||||
"github.com/shirou/gopsutil/v3/mem"
|
||||
"github.com/shirou/gopsutil/v3/net"
|
||||
)
|
||||
|
||||
type DashboardService struct{}
|
||||
|
|
@ -341,7 +341,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
|
||||
}
|
||||
|
|
@ -359,7 +369,17 @@ func loadGPUInfo() []dto.GPUInfo {
|
|||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -479,6 +479,14 @@ 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)
|
||||
}
|
||||
|
||||
lines, isEndOfFile, total, err := files.ReadFileByLine(logFilePath, req.Page, req.PageSize, req.Latest)
|
||||
|
|
|
|||
|
|
@ -23,5 +23,6 @@ func commonGroups() []CommonRouter {
|
|||
&RuntimeRouter{},
|
||||
&ProcessRouter{},
|
||||
&WebsiteCARouter{},
|
||||
&AIToolsRouter{},
|
||||
}
|
||||
}
|
||||
|
|
|
|||
23
backend/router/ro_aitool.go
Normal file
23
backend/router/ro_aitool.go
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
package router
|
||||
|
||||
import (
|
||||
v1 "github.com/1Panel-dev/1Panel/backend/app/api/v1"
|
||||
"github.com/1Panel-dev/1Panel/backend/middleware"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type AIToolsRouter struct {
|
||||
}
|
||||
|
||||
func (a *AIToolsRouter) InitRouter(Router *gin.RouterGroup) {
|
||||
aiToolsRouter := Router.Group("aitools")
|
||||
aiToolsRouter.Use(middleware.JwtAuth()).Use(middleware.SessionAuth()).Use(middleware.PasswordExpired())
|
||||
|
||||
baseApi := v1.ApiGroupApp.BaseApi
|
||||
{
|
||||
aiToolsRouter.POST("/ollama/model", baseApi.CreateOllamaModel)
|
||||
aiToolsRouter.POST("/ollama/model/search", baseApi.SearchOllamaModel)
|
||||
aiToolsRouter.POST("/ollama/model/del", baseApi.DeleteOllamaModel)
|
||||
aiToolsRouter.GET("/gpu/load", baseApi.LoadGpuInfo)
|
||||
}
|
||||
}
|
||||
37
backend/utils/ai_tools/gpu/common/gpu_info.go
Normal file
37
backend/utils/ai_tools/gpu/common/gpu_info.go
Normal file
|
|
@ -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"`
|
||||
}
|
||||
65
backend/utils/ai_tools/gpu/gpu.go
Normal file
65
backend/utils/ai_tools/gpu/gpu.go
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
package gpu
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
_ "embed"
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/ai_tools/gpu/common"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/ai_tools/gpu/schema_v12"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
|
||||
)
|
||||
|
||||
type NvidiaSMI struct{}
|
||||
|
||||
func New() (bool, NvidiaSMI) {
|
||||
return cmd.Which("nvidia-smi"), NvidiaSMI{}
|
||||
}
|
||||
|
||||
func (n NvidiaSMI) LoadGpuInfo() (*common.GpuInfo, error) {
|
||||
std, err := cmd.ExecWithTimeOut("nvidia-smi -q -x", 5*time.Second)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("calling nvidia-smi failed, err: %v", std)
|
||||
}
|
||||
data := []byte(std)
|
||||
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)
|
||||
}
|
||||
58
backend/utils/ai_tools/gpu/schema_v12/parser.go
Normal file
58
backend/utils/ai_tools/gpu/schema_v12/parser.go
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
package schema_v12
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/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].EccMode.CurrentEcc
|
||||
if gpuItem.ECC == "Enabled" {
|
||||
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
|
||||
}
|
||||
294
backend/utils/ai_tools/gpu/schema_v12/types.go
Normal file
294
backend/utils/ai_tools/gpu/schema_v12/types.go
Normal file
|
|
@ -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"`
|
||||
}
|
||||
43
backend/utils/ai_tools/xpu/types.go
Normal file
43
backend/utils/ai_tools/xpu/types.go
Normal file
|
|
@ -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"`
|
||||
}
|
||||
256
backend/utils/ai_tools/xpu/xpu.go
Normal file
256
backend/utils/ai_tools/xpu/xpu.go
Normal file
|
|
@ -0,0 +1,256 @@
|
|||
package xpu
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/1Panel-dev/1Panel/backend/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 {
|
||||
global.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 {
|
||||
global.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 {
|
||||
global.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 {
|
||||
global.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 {
|
||||
global.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("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 {
|
||||
global.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 {
|
||||
global.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 {
|
||||
global.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 {
|
||||
global.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 {
|
||||
global.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()
|
||||
}
|
||||
49
backend/utils/ai_tools/xpu/xpu_info.go
Normal file
49
backend/utils/ai_tools/xpu/xpu_info.go
Normal file
|
|
@ -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"`
|
||||
}
|
||||
|
|
@ -29,14 +29,6 @@ func LoadRequestTransport() *http.Transport {
|
|||
}
|
||||
}
|
||||
|
||||
func LoadGpuInfo() []interface{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
func LoadXpuInfo() []interface{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
func StartClam(startClam model.Clam, isUpdate bool) (int, error) {
|
||||
return 0, buserr.New(constant.ErrXpackNotFound)
|
||||
}
|
||||
|
|
|
|||
82
frontend/src/api/interface/ai-tool.ts
Normal file
82
frontend/src/api/interface/ai-tool.ts
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
import { ReqPage } from '.';
|
||||
|
||||
export namespace AITool {
|
||||
export interface OllamaModelInfo {
|
||||
name: string;
|
||||
size: string;
|
||||
modified: 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;
|
||||
}
|
||||
}
|
||||
17
frontend/src/api/modules/ai-tool.ts
Normal file
17
frontend/src/api/modules/ai-tool.ts
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
import { AITool } from '@/api/interface/ai-tool';
|
||||
import http from '@/api';
|
||||
import { ResPage } from '../interface';
|
||||
|
||||
export const createOllamaModel = (name: string) => {
|
||||
return http.post(`/aitools/ollama/model`, { name: name });
|
||||
};
|
||||
export const deleteOllamaModel = (name: string) => {
|
||||
return http.post(`/aitools/ollama/model/del`, { name: name });
|
||||
};
|
||||
export const searchOllamaModel = (params: AITool.OllamaModelSearch) => {
|
||||
return http.post<ResPage<AITool.OllamaModelInfo>>(`/aitools/ollama/model/search`, params);
|
||||
};
|
||||
|
||||
export const loadGPUInfo = () => {
|
||||
return http.get<any>(`/aitools/gpu/load`);
|
||||
};
|
||||
|
|
@ -31,7 +31,7 @@
|
|||
>
|
||||
{{ $t('app.restart') }}
|
||||
</el-button>
|
||||
<el-divider direction="vertical" />
|
||||
<el-divider v-if="!hideSetting" direction="vertical" />
|
||||
<el-button
|
||||
type="primary"
|
||||
link
|
||||
|
|
@ -43,6 +43,7 @@
|
|||
</el-button>
|
||||
<el-divider v-if="data.app === 'OpenResty'" direction="vertical" />
|
||||
<el-button
|
||||
v-if="!hideSetting"
|
||||
type="primary"
|
||||
@click="setting"
|
||||
link
|
||||
|
|
@ -124,6 +125,10 @@ const props = defineProps({
|
|||
type: String,
|
||||
default: '',
|
||||
},
|
||||
hideSetting: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
|
||||
let key = ref('');
|
||||
|
|
|
|||
|
|
@ -72,6 +72,8 @@ const stopSignals = [
|
|||
'image pull successful!',
|
||||
'image push failed!',
|
||||
'image push successful!',
|
||||
'ollama pull failed!',
|
||||
'ollama pull successful!',
|
||||
];
|
||||
const emit = defineEmits(['update:loading', 'update:hasContent', 'update:isReading']);
|
||||
const tailLog = ref(false);
|
||||
|
|
@ -173,9 +175,11 @@ const getContent = async (pre: boolean) => {
|
|||
}
|
||||
if (res.data.lines && res.data.lines.length > 0) {
|
||||
res.data.lines = res.data.lines.map((line) =>
|
||||
line.replace(/\\u(\w{4})/g, function (match, grp) {
|
||||
return String.fromCharCode(parseInt(grp, 16));
|
||||
}),
|
||||
line
|
||||
.replace(/\\u(\w{4})/g, function (match, grp) {
|
||||
return String.fromCharCode(parseInt(grp, 16));
|
||||
})
|
||||
.replace(/\x1b\[[0-9;]*[A-Za-z?](?!\d)/g, ''),
|
||||
);
|
||||
const newLogs = res.data.lines;
|
||||
if (newLogs.length === readReq.pageSize && readReq.page < res.data.total) {
|
||||
|
|
|
|||
|
|
@ -323,6 +323,7 @@ const message = {
|
|||
firewall: '防火墙',
|
||||
ssl: '证书',
|
||||
database: '数据库',
|
||||
ai_tools: 'AI',
|
||||
container: '容器',
|
||||
cronjob: '计划任务',
|
||||
host: '主机',
|
||||
|
|
@ -574,6 +575,50 @@ const message = {
|
|||
remoteConnHelper2: '非容器或外部连接使用此地址',
|
||||
localIP: '本机 IP',
|
||||
},
|
||||
ai_tools: {
|
||||
model: {
|
||||
model: '模型',
|
||||
create: '添加模型',
|
||||
create_helper: '查找需要添加的模型',
|
||||
},
|
||||
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: '创建容器',
|
||||
edit: '编辑容器',
|
||||
|
|
|
|||
34
frontend/src/routers/modules/ai-tool.ts
Normal file
34
frontend/src/routers/modules/ai-tool.ts
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
import { Layout } from '@/routers/constant';
|
||||
|
||||
const databaseRouter = {
|
||||
sort: 4,
|
||||
path: '/ai-tools',
|
||||
component: Layout,
|
||||
redirect: '/ai-tools/model',
|
||||
meta: {
|
||||
icon: 'p-database',
|
||||
title: 'menu.ai_tools',
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: '/ai-tools/model',
|
||||
name: 'OllamaModel',
|
||||
component: () => import('@/views/ai-tools/model/index.vue'),
|
||||
meta: {
|
||||
title: 'ai_tools.model.model',
|
||||
requiresAuth: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/ai-tools/gpu',
|
||||
name: 'GPU',
|
||||
component: () => import('@/views/ai-tools/gpu/index.vue'),
|
||||
meta: {
|
||||
title: 'ai_tools.gpu.gpu',
|
||||
requiresAuth: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export default databaseRouter;
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import { Layout } from '@/routers/constant';
|
||||
|
||||
const containerRouter = {
|
||||
sort: 5,
|
||||
sort: 6,
|
||||
path: '/containers',
|
||||
component: Layout,
|
||||
redirect: '/containers/container',
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { Layout } from '@/routers/constant';
|
||||
|
||||
const cronRouter = {
|
||||
sort: 8,
|
||||
sort: 9,
|
||||
path: '/cronjobs',
|
||||
component: Layout,
|
||||
redirect: '/cronjobs',
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { Layout } from '@/routers/constant';
|
||||
|
||||
const databaseRouter = {
|
||||
sort: 4,
|
||||
sort: 5,
|
||||
path: '/databases',
|
||||
component: Layout,
|
||||
redirect: '/databases/mysql',
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { Layout } from '@/routers/constant';
|
||||
|
||||
const hostRouter = {
|
||||
sort: 6,
|
||||
sort: 7,
|
||||
path: '/hosts',
|
||||
component: Layout,
|
||||
redirect: '/hosts/security',
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { Layout } from '@/routers/constant';
|
||||
|
||||
const logsRouter = {
|
||||
sort: 8,
|
||||
sort: 10,
|
||||
path: '/logs',
|
||||
component: Layout,
|
||||
redirect: '/logs/operation',
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { Layout } from '@/routers/constant';
|
||||
|
||||
const settingRouter = {
|
||||
sort: 10,
|
||||
sort: 12,
|
||||
path: '/settings',
|
||||
component: Layout,
|
||||
redirect: '/settings/panel',
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { Layout } from '@/routers/constant';
|
||||
|
||||
const toolboxRouter = {
|
||||
sort: 7,
|
||||
sort: 8,
|
||||
path: '/toolbox',
|
||||
component: Layout,
|
||||
redirect: '/toolbox/supervisor',
|
||||
|
|
|
|||
367
frontend/src/views/ai-tools/gpu/index.vue
Normal file
367
frontend/src/views/ai-tools/gpu/index.vue
Normal file
|
|
@ -0,0 +1,367 @@
|
|||
<template>
|
||||
<div>
|
||||
<RouterButton
|
||||
:buttons="[
|
||||
{
|
||||
label: $t('ai_tools.gpu.gpu'),
|
||||
path: '/xpack/gpu',
|
||||
},
|
||||
]"
|
||||
/>
|
||||
|
||||
<div v-if="gpuType == 'nvidia'">
|
||||
<LayoutContent
|
||||
v-loading="loading"
|
||||
:title="$t('ai_tools.gpu.gpu')"
|
||||
:divider="true"
|
||||
v-if="gpuInfo.driverVersion.length !== 0 && !loading"
|
||||
>
|
||||
<template #toolbar>
|
||||
<el-row>
|
||||
<el-col :xs="24" :sm="16" :md="16" :lg="16" :xl="16" />
|
||||
<el-col :xs="24" :sm="8" :md="8" :lg="8" :xl="8">
|
||||
<TableSetting @search="refresh()" />
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
<template #main>
|
||||
<el-descriptions direction="vertical" :column="14" border>
|
||||
<el-descriptions-item :label="$t('ai_tools.gpu.driverVersion')" width="50%" :span="7">
|
||||
{{ gpuInfo.driverVersion }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item :label="$t('ai_tools.gpu.cudaVersion')" :span="7">
|
||||
{{ gpuInfo.cudaVersion }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<el-collapse v-model="activeNames" class="mt-5">
|
||||
<el-collapse-item v-for="item in gpuInfo.gpu" :key="item.index" :name="item.index">
|
||||
<template #title>
|
||||
<span class="name-class">{{ item.index + '. ' + item.productName }}</span>
|
||||
</template>
|
||||
<span class="title-class">{{ $t('ai_tools.gpu.base') }}</span>
|
||||
<el-descriptions direction="vertical" :column="6" border size="small" class="mt-2">
|
||||
<el-descriptions-item :label="$t('monitor.gpuUtil')">
|
||||
{{ item.gpuUtil }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item>
|
||||
<template #label>
|
||||
<div class="cell-item">
|
||||
{{ $t('monitor.temperature') }}
|
||||
<el-tooltip placement="top" :content="$t('ai_tools.gpu.temperatureHelper')">
|
||||
<el-icon class="icon-item"><InfoFilled /></el-icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
{{ item.temperature.replaceAll('C', '°C') }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item>
|
||||
<template #label>
|
||||
<div class="cell-item">
|
||||
{{ $t('monitor.performanceState') }}
|
||||
<el-tooltip
|
||||
placement="top"
|
||||
:content="$t('ai_tools.gpu.performanceStateHelper')"
|
||||
>
|
||||
<el-icon class="icon-item"><InfoFilled /></el-icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
{{ item.performanceState }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item :label="$t('monitor.powerUsage')">
|
||||
{{ item.powerDraw }} / {{ item.maxPowerLimit }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item :label="$t('monitor.memoryUsage')">
|
||||
{{ item.memUsed }} / {{ item.memTotal }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item :label="$t('monitor.fanSpeed')">
|
||||
{{ item.fanSpeed }}
|
||||
</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item :label="$t('ai_tools.gpu.busID')">
|
||||
{{ item.busID }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item>
|
||||
<template #label>
|
||||
<div class="cell-item">
|
||||
{{ $t('ai_tools.gpu.persistenceMode') }}
|
||||
<el-tooltip
|
||||
placement="top"
|
||||
:content="$t('ai_tools.gpu.persistenceModeHelper')"
|
||||
>
|
||||
<el-icon class="icon-item"><InfoFilled /></el-icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
{{ $t('ai_tools.gpu.' + item.persistenceMode.toLowerCase()) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item :label="$t('ai_tools.gpu.displayActive')">
|
||||
{{
|
||||
lowerCase(item.displayActive) === 'disabled'
|
||||
? $t('ai_tools.gpu.displayActiveF')
|
||||
: $t('ai_tools.gpu.displayActiveT')
|
||||
}}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item>
|
||||
<template #label>
|
||||
<div class="cell-item">
|
||||
Uncorr. ECC
|
||||
<el-tooltip placement="top" :content="$t('ai_tools.gpu.ecc')">
|
||||
<el-icon class="icon-item"><InfoFilled /></el-icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
{{ loadEcc(item.ecc) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item :label="$t('ai_tools.gpu.computeMode')">
|
||||
<template #label>
|
||||
<div class="cell-item">
|
||||
{{ $t('ai_tools.gpu.computeMode') }}
|
||||
<el-tooltip placement="top">
|
||||
<template #content>
|
||||
{{ $t('ai_tools.gpu.defaultHelper') }}
|
||||
<br />
|
||||
{{ $t('ai_tools.gpu.exclusiveProcessHelper') }}
|
||||
<br />
|
||||
{{ $t('ai_tools.gpu.exclusiveThreadHelper') }}
|
||||
<br />
|
||||
{{ $t('ai_tools.gpu.prohibitedHelper') }}
|
||||
</template>
|
||||
<el-icon class="icon-item"><InfoFilled /></el-icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
{{ loadComputeMode(item.computeMode) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="MIG.M">
|
||||
<template #label>
|
||||
<div class="cell-item">
|
||||
MIG M.
|
||||
<el-tooltip placement="top">
|
||||
<template #content>
|
||||
{{ $t('ai_tools.gpu.migModeHelper') }}
|
||||
</template>
|
||||
<el-icon class="icon-item"><InfoFilled /></el-icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
{{
|
||||
item.migMode === 'N/A'
|
||||
? $t('ai_tools.gpu.migModeNA')
|
||||
: $t('ai_tools.gpu.' + lowerCase(item.migMode))
|
||||
}}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<div class="mt-5">
|
||||
<span class="title-class">{{ $t('ai_tools.gpu.process') }}</span>
|
||||
</div>
|
||||
<el-table :data="item.processes" v-if="item.processes?.length !== 0">
|
||||
<el-table-column label="PID" prop="pid" />
|
||||
<el-table-column :label="$t('ai_tools.gpu.type')" prop="type">
|
||||
<template #default="{ row }">
|
||||
{{ loadProcessType(row.type) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('ai_tools.gpu.processName')" prop="processName" />
|
||||
<el-table-column :label="$t('ai_tools.gpu.processMemoryUsage')" prop="usedMemory" />
|
||||
</el-table>
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
</template>
|
||||
</LayoutContent>
|
||||
</div>
|
||||
<div v-else>
|
||||
<LayoutContent
|
||||
v-loading="loading"
|
||||
:title="$t('ai_tools.gpu.gpu')"
|
||||
:divider="true"
|
||||
v-if="xpuInfo.driverVersion.length !== 0 && !loading"
|
||||
>
|
||||
<template #toolbar>
|
||||
<el-row>
|
||||
<el-col :xs="24" :sm="16" :md="16" :lg="16" :xl="16" />
|
||||
<el-col :xs="24" :sm="8" :md="8" :lg="8" :xl="8">
|
||||
<TableSetting @search="refresh()" />
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
<template #main>
|
||||
<el-descriptions direction="vertical" :column="14" border>
|
||||
<el-descriptions-item :label="$t('ai_tools.gpu.driverVersion')" width="50%" :span="7">
|
||||
{{ xpuInfo.driverVersion }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<el-collapse v-model="activeNames" class="mt-5">
|
||||
<el-collapse-item
|
||||
v-for="item in xpuInfo.xpu"
|
||||
:key="item.basic.deviceID"
|
||||
:name="item.basic.deviceID"
|
||||
>
|
||||
<template #title>
|
||||
<span class="name-class">{{ item.basic.deviceID + '. ' + item.basic.deviceName }}</span>
|
||||
</template>
|
||||
<span class="title-class">{{ $t('ai_tools.gpu.base') }}</span>
|
||||
<el-descriptions direction="vertical" :column="6" border size="small" class="mt-2">
|
||||
<el-descriptions-item :label="$t('monitor.gpuUtil')">
|
||||
{{ item.stats.memoryUtil }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item>
|
||||
<template #label>
|
||||
<div class="cell-item">
|
||||
{{ $t('monitor.temperature') }}
|
||||
<el-tooltip placement="top" :content="$t('ai_tools.gpu.temperatureHelper')">
|
||||
<el-icon class="icon-item"><InfoFilled /></el-icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
{{ item.stats.temperature }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item :label="$t('monitor.powerUsage')">
|
||||
{{ item.stats.power }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item :label="$t('monitor.memoryUsage')">
|
||||
{{ item.stats.memoryUsed }} / {{ item.basic.memory }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item :label="$t('ai_tools.gpu.busID')">
|
||||
{{ item.basic.pciBdfAddress }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<div class="mt-5">
|
||||
<span class="title-class">{{ $t('ai_tools.gpu.process') }}</span>
|
||||
</div>
|
||||
<el-table :data="item.processes" v-if="item.processes?.length !== 0">
|
||||
<el-table-column label="PID" prop="pid" />
|
||||
<el-table-column :label="$t('ai_tools.gpu.processName')" prop="command" />
|
||||
<el-table-column :label="$t('ai_tools.gpu.shr')" prop="shr" />
|
||||
<el-table-column :label="$t('ai_tools.gpu.processMemoryUsage')" prop="memory" />
|
||||
</el-table>
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
</template>
|
||||
</LayoutContent>
|
||||
</div>
|
||||
<LayoutContent
|
||||
:title="$t('ai_tools.gpu.gpu')"
|
||||
:divider="true"
|
||||
v-if="gpuInfo.driverVersion.length === 0 && xpuInfo.driverVersion.length == 0 && !loading"
|
||||
>
|
||||
<template #main>
|
||||
<div class="app-warn">
|
||||
<div class="flx-center">
|
||||
<span>{{ $t('ai_tools.gpu.gpuHelper') }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<img src="@/assets/images/no_app.svg" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</LayoutContent>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { loadGPUInfo } from '@/api/modules/ai-tool';
|
||||
import { AITool } from '@/api/interface/ai-tool';
|
||||
import i18n from '@/lang';
|
||||
|
||||
const loading = ref();
|
||||
const activeNames = ref(0);
|
||||
const gpuInfo = ref<AITool.Info>({
|
||||
cudaVersion: '',
|
||||
driverVersion: '',
|
||||
type: 'nvidia',
|
||||
gpu: [],
|
||||
});
|
||||
const xpuInfo = ref<AITool.XpuInfo>({
|
||||
driverVersion: '',
|
||||
type: 'xpu',
|
||||
xpu: [],
|
||||
});
|
||||
const gpuType = ref('nvidia');
|
||||
|
||||
const search = async () => {
|
||||
loading.value = true;
|
||||
await loadGPUInfo()
|
||||
.then((res) => {
|
||||
loading.value = false;
|
||||
gpuType.value = res.data.type;
|
||||
if (res.data.type == 'nvidia') {
|
||||
gpuInfo.value = res.data;
|
||||
} else {
|
||||
xpuInfo.value = res.data;
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
};
|
||||
|
||||
const refresh = async () => {
|
||||
const res = await loadGPUInfo();
|
||||
gpuInfo.value = res.data;
|
||||
};
|
||||
|
||||
const lowerCase = (val: string) => {
|
||||
return val.toLowerCase();
|
||||
};
|
||||
|
||||
const loadComputeMode = (val: string) => {
|
||||
switch (val) {
|
||||
case 'Default':
|
||||
return i18n.global.t('ai_tools.gpu.default');
|
||||
case 'Exclusive Process':
|
||||
return i18n.global.t('ai_tools.gpu.exclusiveProcess');
|
||||
case 'Exclusive Thread':
|
||||
return i18n.global.t('ai_tools.gpu.exclusiveThread');
|
||||
case 'Prohibited':
|
||||
return i18n.global.t('ai_tools.gpu.prohibited');
|
||||
}
|
||||
};
|
||||
|
||||
const loadEcc = (val: string) => {
|
||||
if (val === 'N/A') {
|
||||
return i18n.global.t('ai_tools.gpu.migModeNA');
|
||||
}
|
||||
if (val === 'Disabled') {
|
||||
return i18n.global.t('ai_tools.gpu.disabled');
|
||||
}
|
||||
if (val === 'Enabled') {
|
||||
return i18n.global.t('ai_tools.gpu.enabled');
|
||||
}
|
||||
return val;
|
||||
};
|
||||
|
||||
const loadProcessType = (val: string) => {
|
||||
if (val === 'C' || val === 'G') {
|
||||
return i18n.global.t('ai_tools.gpu.type' + val);
|
||||
}
|
||||
if (val === 'C+G') {
|
||||
return i18n.global.t('ai_tools.gpu.typeCG');
|
||||
}
|
||||
return val;
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
search();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.name-class {
|
||||
font-size: 18px;
|
||||
font-weight: 500;
|
||||
}
|
||||
.title-class {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
}
|
||||
.cell-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
.icon-item {
|
||||
margin-left: 4px;
|
||||
margin-top: -1px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
94
frontend/src/views/ai-tools/model/add/index.vue
Normal file
94
frontend/src/views/ai-tools/model/add/index.vue
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
<template>
|
||||
<el-drawer
|
||||
v-model="drawerVisible"
|
||||
:destroy-on-close="true"
|
||||
:close-on-click-modal="false"
|
||||
:close-on-press-escape="false"
|
||||
size="30%"
|
||||
>
|
||||
<template #header>
|
||||
<DrawerHeader :header="$t('ai_tools.model.create')" :back="handleClose" />
|
||||
</template>
|
||||
<el-row type="flex" justify="center">
|
||||
<el-col :span="22">
|
||||
<el-alert type="info" :closable="false">
|
||||
<template #title>
|
||||
<span class="flx-align-center">
|
||||
{{ $t('ai_tools.model.create_helper') }}
|
||||
<el-link class="ml-5" icon="Position" @click="goSearch()" type="primary">
|
||||
{{ $t('firewall.quickJump') }}
|
||||
</el-link>
|
||||
</span>
|
||||
</template>
|
||||
</el-alert>
|
||||
<el-form ref="formRef" label-position="top" :model="form">
|
||||
<el-form-item :label="$t('commons.table.name')" :rules="Rules.requiredInput" prop="name">
|
||||
<el-input v-model.trim="form.name" />
|
||||
<span class="input-help" v-if="form.name">
|
||||
ollama pull {{ form.name.replaceAll('ollama run ', '').replaceAll('ollama pull ', '') }}
|
||||
</span>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="drawerVisible = false">
|
||||
{{ $t('commons.button.cancel') }}
|
||||
</el-button>
|
||||
<el-button type="primary" @click="onSubmit(formRef)">
|
||||
{{ $t('commons.button.add') }}
|
||||
</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-drawer>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref } from 'vue';
|
||||
import { Rules } from '@/global/form-rules';
|
||||
import i18n from '@/lang';
|
||||
import { ElForm } from 'element-plus';
|
||||
import DrawerHeader from '@/components/drawer-header/index.vue';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
import { createOllamaModel } from '@/api/modules/ai-tool';
|
||||
|
||||
const drawerVisible = ref(false);
|
||||
const form = reactive({
|
||||
name: '',
|
||||
});
|
||||
|
||||
const acceptParams = async (): Promise<void> => {
|
||||
form.name = '';
|
||||
drawerVisible.value = true;
|
||||
};
|
||||
const emit = defineEmits(['search', 'log']);
|
||||
|
||||
type FormInstance = InstanceType<typeof ElForm>;
|
||||
const formRef = ref<FormInstance>();
|
||||
|
||||
const onSubmit = async (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return;
|
||||
formEl.validate(async (valid) => {
|
||||
if (!valid) return;
|
||||
let itemName = form.name.replaceAll('ollama run ', '').replaceAll('ollama pull ', '');
|
||||
await createOllamaModel(itemName);
|
||||
drawerVisible.value = false;
|
||||
emit('search');
|
||||
emit('log', itemName);
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
});
|
||||
};
|
||||
|
||||
const goSearch = () => {
|
||||
window.open('https://ollama.com/search', '_blank', 'noopener,noreferrer');
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
drawerVisible.value = false;
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
acceptParams,
|
||||
});
|
||||
</script>
|
||||
264
frontend/src/views/ai-tools/model/index.vue
Normal file
264
frontend/src/views/ai-tools/model/index.vue
Normal file
|
|
@ -0,0 +1,264 @@
|
|||
<template>
|
||||
<div v-loading="loading">
|
||||
<RouterButton
|
||||
:buttons="[
|
||||
{
|
||||
label: i18n.global.t('ai_tools.model.model'),
|
||||
path: '/ai-tools/model',
|
||||
},
|
||||
]"
|
||||
/>
|
||||
<LayoutContent title="Ollama">
|
||||
<template #app>
|
||||
<AppStatus
|
||||
app-key="ollama"
|
||||
v-model:loading="loading"
|
||||
:hide-setting="true"
|
||||
v-model:mask-show="maskShow"
|
||||
@is-exist="checkExist"
|
||||
ref="appStatusRef"
|
||||
></AppStatus>
|
||||
</template>
|
||||
<template #toolbar>
|
||||
<div class="flex justify-between gap-2 flex-wrap sm:flex-row">
|
||||
<div class="flex flex-wrap gap-3">
|
||||
<el-button v-if="modelInfo.status === 'Running'" type="primary" @click="onCreate()">
|
||||
{{ $t('ai_tools.model.create') }}
|
||||
</el-button>
|
||||
<!-- <el-button @click="onLoadConn" type="primary" plain>
|
||||
{{ $t('database.databaseConnInfo') }}
|
||||
</el-button> -->
|
||||
<el-button icon="Position" @click="goDashboard()" type="primary" plain>OpenWebUI</el-button>
|
||||
</div>
|
||||
<div>
|
||||
<TableSearch @search="search()" v-model:searchName="searchName" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #main>
|
||||
<ComplexTable
|
||||
:pagination-config="paginationConfig"
|
||||
: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 :label="$t('file.size')" prop="size" />
|
||||
<el-table-column :label="$t('commons.button.log')">
|
||||
<template #default="{ row }">
|
||||
<el-button @click="onLoadLog(row.name)" link type="primary">
|
||||
{{ $t('website.check') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('commons.table.createdAt')" prop="modified" />
|
||||
<fu-table-operations
|
||||
:ellipsis="mobile ? 0 : 10"
|
||||
:min-width="mobile ? 'auto' : 400"
|
||||
:buttons="buttons"
|
||||
:label="$t('commons.table.operate')"
|
||||
fixed="right"
|
||||
fix
|
||||
/>
|
||||
</ComplexTable>
|
||||
</template>
|
||||
</LayoutContent>
|
||||
|
||||
<el-card v-if="modelInfo.status != 'Running' && !loading && maskShow" class="mask-prompt">
|
||||
<span>
|
||||
{{ $t('commons.service.serviceNotStarted', ['Ollama']) }}
|
||||
</span>
|
||||
</el-card>
|
||||
|
||||
<LayoutContent v-if="!modelInfo.isExist && !loading" title="Ollama" :divider="true">
|
||||
<template #main>
|
||||
<div class="app-warn">
|
||||
<div class="flex flex-col gap-2 items-center justify-center w-full sm:flex-row">
|
||||
<span>{{ $t('app.checkInstalledWarn', [$t('database.noMysql')]) }}</span>
|
||||
<span @click="goInstall('ollama')" class="flex items-center justify-center gap-0.5">
|
||||
<el-icon><Position /></el-icon>
|
||||
{{ $t('database.goInstall') }}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<img src="@/assets/images/no_app.svg" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</LayoutContent>
|
||||
|
||||
<el-dialog
|
||||
v-model="dashboardVisible"
|
||||
:title="$t('app.checkTitle')"
|
||||
width="30%"
|
||||
:close-on-click-modal="false"
|
||||
:destroy-on-close="true"
|
||||
>
|
||||
<div class="flex justify-center items-center gap-2 flex-wrap">
|
||||
{{ $t('app.checkInstalledWarn', ['OpenWebUI']) }}
|
||||
<el-link icon="Position" @click="goInstall('ollama-webui')" type="primary">
|
||||
{{ $t('database.goInstall') }}
|
||||
</el-link>
|
||||
</div>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="dashboardVisible = false">{{ $t('commons.button.cancel') }}</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<AddDialog ref="addRef" @search="search" @log="onLoadLog" />
|
||||
<Log ref="logRef" @close="search" />
|
||||
<PortJumpDialog ref="dialogPortJumpRef" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import AppStatus from '@/components/app-status/index.vue';
|
||||
import AddDialog from '@/views/ai-tools/model/add/index.vue';
|
||||
import Log from '@/components/log-dialog/index.vue';
|
||||
import PortJumpDialog from '@/components/port-jump/index.vue';
|
||||
import { computed, onMounted, reactive, ref } from 'vue';
|
||||
import i18n from '@/lang';
|
||||
import { App } from '@/api/interface/app';
|
||||
import { GlobalStore } from '@/store';
|
||||
import { deleteOllamaModel, searchOllamaModel } from '@/api/modules/ai-tool';
|
||||
import { AITool } from '@/api/interface/ai-tool';
|
||||
import { GetAppPort } from '@/api/modules/app';
|
||||
import router from '@/routers';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
const globalStore = GlobalStore();
|
||||
|
||||
const loading = ref(false);
|
||||
const maskShow = ref(true);
|
||||
|
||||
const addRef = ref();
|
||||
const logRef = ref();
|
||||
const openWebUIPort = ref();
|
||||
const dashboardVisible = ref(false);
|
||||
const dialogPortJumpRef = ref();
|
||||
|
||||
const appStatusRef = ref();
|
||||
|
||||
const data = ref();
|
||||
const paginationConfig = reactive({
|
||||
cacheSizeKey: 'model-page-size',
|
||||
currentPage: 1,
|
||||
pageSize: Number(localStorage.getItem('page-size')) || 10,
|
||||
total: 0,
|
||||
});
|
||||
const searchName = ref();
|
||||
|
||||
const modelInfo = reactive({
|
||||
status: '',
|
||||
container: '',
|
||||
isExist: null,
|
||||
version: '',
|
||||
});
|
||||
|
||||
const mobile = computed(() => {
|
||||
return globalStore.isMobile();
|
||||
});
|
||||
|
||||
const search = async () => {
|
||||
let params = {
|
||||
page: paginationConfig.currentPage,
|
||||
pageSize: paginationConfig.pageSize,
|
||||
info: searchName.value,
|
||||
};
|
||||
loading.value = true;
|
||||
await searchOllamaModel(params)
|
||||
.then((res) => {
|
||||
loading.value = false;
|
||||
data.value = res.data.items || [];
|
||||
paginationConfig.total = res.data.total;
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
};
|
||||
|
||||
const onCreate = async () => {
|
||||
addRef.value.acceptParams();
|
||||
};
|
||||
|
||||
const goDashboard = async () => {
|
||||
if (openWebUIPort.value === 0) {
|
||||
dashboardVisible.value = true;
|
||||
return;
|
||||
}
|
||||
dialogPortJumpRef.value.acceptParams({ port: openWebUIPort.value });
|
||||
};
|
||||
|
||||
const goInstall = (name: string) => {
|
||||
router.push({ name: 'AppAll', query: { install: name } });
|
||||
};
|
||||
|
||||
const loadWebUIPort = async () => {
|
||||
const res = await GetAppPort('ollama-webui', '');
|
||||
openWebUIPort.value = res.data;
|
||||
};
|
||||
|
||||
const checkExist = (data: App.CheckInstalled) => {
|
||||
modelInfo.isExist = data.isExist;
|
||||
modelInfo.status = data.status;
|
||||
modelInfo.version = data.version;
|
||||
modelInfo.container = data.containerName;
|
||||
};
|
||||
|
||||
const onDelete = async (row: AITool.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;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const onLoadLog = (name: string) => {
|
||||
logRef.value.acceptParams({ id: 0, type: 'ollama-model', name: name, tail: true });
|
||||
};
|
||||
|
||||
const buttons = [
|
||||
{
|
||||
label: i18n.global.t('commons.button.delete'),
|
||||
click: (row: AITool.OllamaModelInfo) => {
|
||||
onDelete(row);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
onMounted(() => {
|
||||
search();
|
||||
loadWebUIPort();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.iconInTable {
|
||||
margin-left: 5px;
|
||||
margin-top: 3px;
|
||||
}
|
||||
.jumpAdd {
|
||||
margin-top: 10px;
|
||||
margin-left: 15px;
|
||||
margin-bottom: 5px;
|
||||
font-size: 12px;
|
||||
}
|
||||
.tagClass {
|
||||
float: right;
|
||||
font-size: 12px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
</style>
|
||||
Loading…
Add table
Reference in a new issue