Compare commits

...
4 Commits
Author SHA1 Message Date
ssongliu 93ebc6a8b9 feat(ai): streamline Ollama model pool management (#14259) 2026-10-30 23:50:11 +08:00
王贺andzhengkunwang223 9a49f38ffd fix(network): disable MPTCP by default (#14258)
Set multipathtcp=0 in the core and agent module defaults so Linux listeners use standard TCP without requiring service environment changes.

Co-authored-by: zhengkunwang223 <31820853+zhengkunwang223@users.noreply.github.com>
2026-10-30 23:31:05 +08:00
ssongliu 7156addf79 feat(ai): refine model pools, navigation and GPU deployment configuration (#14257) 2026-10-30 23:00:23 +08:00
ssongliu c6329a62db feat(dashboard): refine monitoring, quick access and installation messages (#14256) 2026-10-30 22:46:20 +08:00
58 changed files with 867 additions and 371 deletions
+16 -13
View File
@@ -318,6 +318,7 @@ type AgentAccountModelDiscoverReq struct {
}
type AgentAccountModelCreateReq struct {
TaskID string `json:"taskID"`
AccountID uint `json:"accountId" validate:"required"`
Model AgentAccountModel `json:"model" validate:"required"`
}
@@ -328,8 +329,9 @@ type AgentAccountModelUpdateReq struct {
}
type AgentAccountModelDeleteReq struct {
AccountID uint `json:"accountId" validate:"required"`
RecordID uint `json:"recordId" validate:"required"`
DeleteFiles bool `json:"deleteFiles"`
AccountID uint `json:"accountId" validate:"required"`
RecordID uint `json:"recordId" validate:"required"`
}
type AgentAccountCreateReq struct {
@@ -347,17 +349,18 @@ type AgentAccountCreateReq struct {
}
type AgentAccountUpdateReq struct {
ID uint `json:"id" validate:"required"`
Name string `json:"name" validate:"required"`
APIKey string `json:"apiKey" validate:"required"`
RememberAPIKey bool `json:"rememberApiKey"`
BaseURL string `json:"baseURL"`
APIType string `json:"apiType" validate:"required"`
AuthMode string `json:"authMode"`
VerifyModel string `json:"verifyModel"`
ValidateAvailability *bool `json:"validateAvailability"`
Remark string `json:"remark"`
SyncAgents bool `json:"syncAgents"`
ID uint `json:"id" validate:"required"`
Name string `json:"name" validate:"required"`
APIKey string `json:"apiKey" validate:"required"`
RememberAPIKey bool `json:"rememberApiKey"`
BaseURL string `json:"baseURL"`
APIType string `json:"apiType" validate:"required"`
AuthMode string `json:"authMode"`
VerifyModel string `json:"verifyModel"`
ValidateAvailability *bool `json:"validateAvailability"`
Remark string `json:"remark"`
SyncAgents bool `json:"syncAgents"`
Models []AgentAccountModel `json:"models"`
}
type AgentAccountVerifyReq struct {
+9 -2
View File
@@ -18,8 +18,9 @@ type IAgentAccountRepo interface {
Save(account *model.AgentAccount) error
DeleteByID(id uint) error
List(opts ...DBOption) ([]model.AgentAccount, error)
Count() (int64, error)
CountTextByProviders(providers []string) (map[string]int64, error)
SaveLocal(account *model.AgentAccount, models []model.AgentAccountModel) error
SaveWithModels(account *model.AgentAccount, models []model.AgentAccountModel) error
}
func NewIAgentAccountRepo() IAgentAccountRepo {
@@ -69,6 +70,12 @@ func (a AgentAccountRepo) List(opts ...DBOption) ([]model.AgentAccount, error) {
return accounts, nil
}
func (a AgentAccountRepo) Count() (int64, error) {
var count int64
err := getDb().Model(&model.AgentAccount{}).Count(&count).Error
return count, err
}
func (a AgentAccountRepo) CountTextByProviders(providers []string) (map[string]int64, error) {
normalizedProviders := normalizeProviders(providers)
counts := make(map[string]int64, len(normalizedProviders))
@@ -99,7 +106,7 @@ func (a AgentAccountRepo) CountTextByProviders(providers []string) (map[string]i
return counts, nil
}
func (a AgentAccountRepo) SaveLocal(account *model.AgentAccount, models []model.AgentAccountModel) error {
func (a AgentAccountRepo) SaveWithModels(account *model.AgentAccount, models []model.AgentAccountModel) error {
return global.DB.Transaction(func(tx *gorm.DB) error {
if err := tx.Save(account).Error; err != nil {
return err
+1 -1
View File
@@ -86,10 +86,10 @@ func (u *LauncherRepo) ListQuickJump(withAll bool) []model.QuickJump {
}
if !withAll && len(quicks) == 0 {
return []model.QuickJump{
{Name: "ModelServices", Title: "aiTools.model.model", Recommend: 0, IsShow: true, Router: "/ai/model"},
{Name: "Agent", Title: "aiTools.agents.agent", Recommend: 1, IsShow: true, Router: "/ai/agents/agent"},
{Name: "Website", Title: "menu.website", Recommend: 10, IsShow: true, Router: "/websites"},
{Name: "Database", Title: "menu.database", Recommend: 30, IsShow: true, Router: "/databases"},
{Name: "Cronjob", Title: "menu.cronjob", Recommend: 50, IsShow: false, Router: "/cronjobs"},
{Name: "AppInstalled", Title: "home.appInstalled", Recommend: 70, IsShow: true, Router: "/apps/installed"},
}
}
+101 -2
View File
@@ -1105,9 +1105,23 @@ func (a AgentService) UpdateAccount(req dto.AgentAccountUpdateReq) error {
if err != nil {
return err
}
if req.Models != nil {
if account.AppInstallID != 0 {
return buserr.WithDetail("ErrInvalidParams", "local model pool is managed by the deployment", nil)
}
models, err = normalizeAgentAccountModels(account, req.Models)
if err != nil {
return err
}
if err := ensureAccountModelsNotBound(account, models); err != nil {
return err
}
}
requestedVerifyModel := req.VerifyModel
if strings.TrimSpace(requestedVerifyModel) == "" {
requestedVerifyModel = account.VerifyModel
if _, ok := findAgentAccountModelForProvider(provider, models, account.VerifyModel); ok {
requestedVerifyModel = account.VerifyModel
}
}
verifyModel := ""
if account.AppInstallID == 0 || len(models) > 0 {
@@ -1131,7 +1145,16 @@ func (a AgentService) UpdateAccount(req dto.AgentAccountUpdateReq) error {
account.Remark = req.Remark
account.Verified = true
if err := agentAccountRepo.Save(account); err != nil {
if req.Models != nil {
rows := make([]model.AgentAccountModel, 0, len(models))
for _, item := range models {
rows = append(rows, model.AgentAccountModel{Model: item.ID, Name: item.Name})
}
err = agentAccountRepo.SaveWithModels(account, rows)
} else {
err = agentAccountRepo.Save(account)
}
if err != nil {
return err
}
terminalai.InvalidateTerminalRuntimeCache()
@@ -1275,6 +1298,31 @@ func (a AgentService) CreateAccountModel(req dto.AgentAccountModelCreateReq) err
if _, ok := findAgentAccountModelForProvider(account.Provider, models, nextModel.ID); ok {
return buserr.New("ErrRecordExist")
}
if account.Provider == "ollama" {
if account.AppInstallID != 0 {
install, err := appInstallRepo.GetFirst(repo.WithByID(account.AppInstallID))
if err != nil {
return err
}
if install.App.Key != "ollama" || !install.SyncModelAccount {
return buserr.New("ErrAgentAccountUnavailable")
}
localModels, err := aiRepo.List()
if err != nil {
return err
}
for _, item := range localModels {
if sameProviderModelID("ollama", item.Name, nextModel.ID) {
return (&AIToolService{}).Recreate(dto.OllamaModelName{Name: item.Name, TaskID: req.TaskID})
}
}
return (&AIToolService{}).Create(dto.OllamaModelName{Name: nextModel.ID, TaskID: req.TaskID})
}
nextModel.ID, err = resolveExistingOllamaModel(account, nextModel.ID)
if err != nil {
return err
}
}
sortOrder := len(models) + 1
record := &model.AgentAccountModel{
AccountID: account.ID,
@@ -1305,6 +1353,15 @@ func (a AgentService) UpdateAccountModel(req dto.AgentAccountModelUpdateReq) err
if err != nil {
return err
}
if account.Provider == "ollama" && !sameProviderModelID("ollama", record.Model, nextModel.ID) {
if account.AppInstallID != 0 {
return buserr.New("ErrInvalidParams")
}
nextModel.ID, err = resolveExistingOllamaModel(account, nextModel.ID)
if err != nil {
return err
}
}
nextModel.RecordID = req.Model.RecordID
for _, item := range models {
if item.RecordID == req.Model.RecordID {
@@ -1343,6 +1400,10 @@ func (a AgentService) UpdateAccountModel(req dto.AgentAccountModelUpdateReq) err
}
func (a AgentService) DeleteAccountModel(req dto.AgentAccountModelDeleteReq) error {
ollamaModelMu.Lock()
defer ollamaModelMu.Unlock()
agentAccountMutationMu.Lock()
defer agentAccountMutationMu.Unlock()
account, err := agentAccountRepo.GetFirst(repo.WithByID(req.AccountID))
if err != nil {
return err
@@ -1368,6 +1429,44 @@ func (a AgentService) DeleteAccountModel(req dto.AgentAccountModelDeleteReq) err
if err := ensureAccountModelsNotBound(account, nextModels); err != nil {
return err
}
if req.DeleteFiles && account.Provider != "ollama" {
return buserr.New("ErrAgentProviderNotSupported")
}
if req.DeleteFiles {
if account.AppInstallID != 0 {
install, err := appInstallRepo.GetFirst(repo.WithByID(account.AppInstallID))
if err != nil {
return err
}
if install.App.Key != "ollama" {
return buserr.New("ErrAgentAccountUnavailable")
}
localModels, err := aiRepo.List()
if err != nil {
return err
}
localIDs := make([]uint, 0)
for _, item := range localModels {
if !sameProviderModelID("ollama", item.Name, record.Model) {
continue
}
if item.Status == constant.StatusWaiting {
return buserr.New("TaskIsExecuting")
}
localIDs = append(localIDs, item.ID)
}
if err := cmd.NewCommandMgr().Run("docker", "exec", install.ContainerName, "ollama", "rm", record.Model); err != nil {
return err
}
if len(localIDs) > 0 {
if err := aiRepo.Delete(repo.WithByIDs(localIDs)); err != nil {
return err
}
}
} else if err := deleteRemoteOllamaModel(account, record.Model); err != nil {
return err
}
}
if err := agentAccountModelRepo.DeleteByID(req.RecordID); err != nil {
return err
}
+99 -1
View File
@@ -1,13 +1,19 @@
package service
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"net/http"
"strings"
"time"
"github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/1Panel-dev/1Panel/agent/app/model"
providercatalog "github.com/1Panel-dev/1Panel/agent/app/provider"
"github.com/1Panel-dev/1Panel/agent/app/repo"
"github.com/1Panel-dev/1Panel/agent/buserr"
"github.com/1Panel-dev/1Panel/agent/constant"
"github.com/1Panel-dev/1Panel/agent/global"
terminalai "github.com/1Panel-dev/1Panel/agent/utils/terminal/ai"
@@ -88,7 +94,7 @@ func SyncLocalModelAccount(install model.AppInstall, accountID uint, baseURL str
}
}
if modelsChanged {
err = agentAccountRepo.SaveLocal(account, rows)
err = agentAccountRepo.SaveWithModels(account, rows)
} else if accountChanged {
err = agentAccountRepo.Save(account)
} else {
@@ -136,6 +142,62 @@ func syncOllamaModelAccount(install model.AppInstall, baseURL string, updateBase
return err
}
func updateOllamaModelPool(modelName string, remove bool) error {
install, err := appInstallRepo.LoadInstallAppByKeyAndName("ollama", "")
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil
}
if err != nil {
return err
}
if !install.SyncModelAccount {
return nil
}
agentAccountMutationMu.Lock()
defer agentAccountMutationMu.Unlock()
account, err := agentAccountRepo.GetFirst(repo.WithByAppInstallID(install.ID), repo.WithByProvider("ollama"))
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil
}
if err != nil {
return err
}
models, err := loadAgentAccountModels(account)
if err != nil {
return err
}
if remove {
for _, item := range models {
if !sameProviderModelID("ollama", item.ID, modelName) {
continue
}
if err := agentAccountModelRepo.DeleteByID(item.RecordID); err != nil {
return err
}
}
if sameProviderModelID("ollama", account.VerifyModel, modelName) {
account.VerifyModel = ""
if err := agentAccountRepo.Save(account); err != nil {
return err
}
}
if err := compactPersistedAgentAccountModelSortOrder(account.ID); err != nil {
return err
}
} else {
if _, exists := findAgentAccountModelForProvider("ollama", models, modelName); exists {
return nil
}
record := &model.AgentAccountModel{AccountID: account.ID, Model: modelName, Name: modelName, SortOrder: len(models) + 1}
if err := agentAccountModelRepo.Create(record); err != nil {
return err
}
}
terminalai.InvalidateTerminalRuntimeCache()
terminalai.InvalidateFileAIRuntimeCache()
return nil
}
func loadOllamaModelAccountAddress(install model.AppInstall) (string, string, error) {
addressType := install.ModelAccountBaseURLType
if addressType == "" {
@@ -200,3 +262,39 @@ func ResolveLocalModelAccountBaseURL(addressType, customURL, containerName, cont
return fmt.Sprintf("http://%s:%s/v1", containerName, containerPort), nil
}
}
func deleteRemoteOllamaModel(account *model.AgentAccount, modelName string) error {
body, err := json.Marshal(map[string]string{"model": modelName})
if err != nil {
return err
}
baseURL := strings.TrimSuffix(strings.TrimRight(account.BaseURL, "/"), "/v1")
req, err := http.NewRequest(http.MethodDelete, baseURL+"/api/delete", bytes.NewReader(body))
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+account.APIKey)
resp, err := (&http.Client{Timeout: 30 * time.Second}).Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusMultipleChoices {
return fmt.Errorf("delete Ollama model failed: %s", resp.Status)
}
return nil
}
func resolveExistingOllamaModel(account *model.AgentAccount, modelID string) (string, error) {
available, err := providercatalog.DiscoverModels(account.BaseURL, account.APIKey)
if err != nil {
return "", buserr.WithErr("ErrAgentAccountUnavailable", err)
}
for _, id := range available {
if sameProviderModelID("ollama", id, modelID) {
return id, nil
}
}
return "", buserr.New("ErrRecordNotFound")
}
+8
View File
@@ -1517,6 +1517,14 @@ func sameProviderModelID(provider, left, right string) bool {
}
leftComparable := providercatalog.NormalizeModelID(provider, leftTrimmed)
rightComparable := providercatalog.NormalizeModelID(provider, rightTrimmed)
if provider == "ollama" {
if leftComparable != "" && !strings.Contains(path.Base(leftComparable), ":") {
leftComparable += ":latest"
}
if rightComparable != "" && !strings.Contains(path.Base(rightComparable), ":") {
rightComparable += ":latest"
}
}
return leftComparable != "" && leftComparable == rightComparable
}
+24 -18
View File
@@ -117,6 +117,7 @@ func (u *AIToolService) Create(req dto.OllamaModelName) error {
}
taskItem, err := task.NewTaskWithOps(fmt.Sprintf("ollama-model-%s", req.Name), task.TaskPull, task.TaskScopeAI, req.TaskID, info.ID)
if err != nil {
_ = aiRepo.Update(info.ID, map[string]interface{}{"status": constant.StatusFailed, "message": err.Error()})
global.LOG.Errorf("new task for exec shell failed, err: %v", err)
return err
}
@@ -126,19 +127,17 @@ func (u *AIToolService) Create(req dto.OllamaModelName) error {
}, 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()})
if err != nil {
return err
}
return nil
return aiRepo.Update(info.ID, map[string]interface{}{"status": constant.StatusSuccess, "size": itemSize})
}, nil)
taskItem.AddSubTask(i18n.GetMsgByKey("TaskSync"), func(t *task.Task) error {
return updateOllamaModelPool(info.Name, false)
}, nil)
if err := taskItem.Execute(); err != nil {
_ = aiRepo.Update(info.ID, map[string]interface{}{"status": constant.StatusFailed, "message": err.Error()})
}
if err := SyncOllamaModelAccount(false); err != nil {
global.LOG.Errorf("sync Ollama model account after pull: %v", err)
}
}()
return nil
}
@@ -167,6 +166,9 @@ func (u *AIToolService) Recreate(req dto.OllamaModelName) error {
if modelInfo.ID == 0 {
return buserr.New("ErrRecordNotFound")
}
if modelInfo.Status == constant.StatusWaiting {
return buserr.New("TaskIsExecuting")
}
containerName, err := LoadContainerName()
if err != nil {
return err
@@ -176,6 +178,7 @@ func (u *AIToolService) Recreate(req dto.OllamaModelName) error {
}
taskItem, err := task.NewTaskWithOps(fmt.Sprintf("ollama-model-%s", req.Name), task.TaskPull, task.TaskScopeAI, req.TaskID, modelInfo.ID)
if err != nil {
_ = aiRepo.Update(modelInfo.ID, map[string]interface{}{"status": constant.StatusFailed, "message": err.Error()})
global.LOG.Errorf("new task for exec shell failed, err: %v", err)
return err
}
@@ -185,19 +188,17 @@ func (u *AIToolService) Recreate(req dto.OllamaModelName) error {
}, nil)
taskItem.AddSubTask(i18n.GetWithName("OllamaModelSize", req.Name), func(t *task.Task) error {
itemSize, err := loadModelSize(modelInfo.Name, containerName)
if len(itemSize) != 0 {
_ = aiRepo.Update(modelInfo.ID, map[string]interface{}{"status": constant.StatusSuccess, "size": itemSize})
} else {
_ = aiRepo.Update(modelInfo.ID, map[string]interface{}{"status": constant.StatusFailed, "message": err.Error()})
if err != nil {
return err
}
return nil
return aiRepo.Update(modelInfo.ID, map[string]interface{}{"status": constant.StatusSuccess, "size": itemSize})
}, nil)
taskItem.AddSubTask(i18n.GetMsgByKey("TaskSync"), func(t *task.Task) error {
return updateOllamaModelPool(modelInfo.Name, false)
}, nil)
if err := taskItem.Execute(); err != nil {
_ = aiRepo.Update(modelInfo.ID, map[string]interface{}{"status": constant.StatusFailed, "message": err.Error()})
}
if err := SyncOllamaModelAccount(false); err != nil {
global.LOG.Errorf("sync Ollama model account after pull: %v", err)
}
}()
return nil
}
@@ -219,11 +220,16 @@ func (u *AIToolService) Delete(req dto.ForceDelete) error {
return fmt.Errorf("handle ollama rm %s failed, %v", item.Name, err)
}
}
_ = aiRepo.Delete(repo.WithByID(item.ID))
if err := aiRepo.Delete(repo.WithByID(item.ID)); err != nil {
return err
}
if err := updateOllamaModelPool(item.Name, true); err != nil {
return err
}
logItem := path.Join(global.Dir.DataDir, "log", "AITools", item.Name)
_ = os.Remove(logItem)
}
return SyncOllamaModelAccount(false)
return nil
}
func (u *AIToolService) SyncAccount(req dto.OllamaModelAccountSync) error {
+7 -1
View File
@@ -414,7 +414,7 @@ func (u *DashboardService) ChangeQuick(req dto.ChangeQuicks) error {
if showCount == 0 {
return buserr.New("ErrMinQuickJump")
}
if showCount > 4 {
if showCount > 5 {
return buserr.New("ErrMaxQuickJump")
}
@@ -709,6 +709,12 @@ func loadQuickJump(base *dto.DashboardBase) {
quicks := launcherRepo.ListQuickJump(false)
for i := 0; i < len(quicks); i++ {
switch quicks[i].Name {
case "ModelServices":
count, err := agentAccountRepo.Count()
if err != nil {
global.LOG.Errorf("load model service count: %v", err)
}
quicks[i].Detail = fmt.Sprintf("%d", count)
case "File":
count, err := launcherRepo.CountQuickDirectories()
if err != nil {
+64 -58
View File
@@ -1,6 +1,7 @@
package service
import (
"context"
"fmt"
"os"
"path/filepath"
@@ -9,6 +10,7 @@ import (
"strings"
"github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/1Panel-dev/1Panel/agent/utils/ai_tools/accelerator"
"github.com/1Panel-dev/1Panel/agent/utils/re"
"github.com/compose-spec/compose-go/v2/interpolation"
"github.com/compose-spec/compose-go/v2/template"
@@ -16,76 +18,72 @@ import (
func LoadModelGPUDevices() dto.ModelGPUDevices {
result := dto.ModelGPUDevices{Devices: []dto.ModelGPUDevice{}, Warnings: []string{}}
uuidPattern := re.GetRegex(re.NvidiaGPUUUIDPattern)
renderPattern := re.GetRegex(re.IntelGPURenderNamePattern)
ascendPattern := re.GetRegex(re.AscendGPUDeviceNamePattern)
for _, directory := range []string{"/proc/driver/nvidia/gpus", "/sys/class/drm", "/dev"} {
entries, err := os.ReadDir(directory)
if err != nil {
if !os.IsNotExist(err) {
result.Warnings = append(result.Warnings, err.Error())
}
ok, client := accelerator.New()
if !ok {
return result
}
snapshot, err := client.Collect(context.Background())
if err != nil {
result.Warnings = append(result.Warnings, err.Error())
return result
}
result.Warnings = append(result.Warnings, snapshot.Info.Warnings...)
for _, monitored := range snapshot.Devices {
if monitored.ParentID != "" {
continue
}
for _, entry := range entries {
var device dto.ModelGPUDevice
var devicePath string
switch directory {
case "/proc/driver/nvidia/gpus":
informationPath := filepath.Join(directory, entry.Name(), "information")
content, err := os.ReadFile(informationPath)
device := dto.ModelGPUDevice{Available: true}
devicePath := ""
switch {
case monitored.GPU != nil:
if monitored.Vendor == "nvidia" && !re.GetRegex(re.NvidiaGPUUUIDPattern).MatchString(monitored.GPU.UUID) {
result.Warnings = append(result.Warnings, fmt.Sprintf("invalid NVIDIA GPU UUID from nvidia-smi: %s", monitored.GPU.UUID))
continue
}
device.ID = monitored.ID
device.Name = fmt.Sprintf("GPU %d · %s", monitored.Index, monitored.Name)
device.Type = monitored.Vendor
device.UUID = monitored.GPU.UUID
case monitored.XPU != nil:
devicePath = monitored.XPU.Basic.DrmDevice
if !re.GetRegex(re.IntelGPUDevicePathPattern).MatchString(devicePath) {
directory := filepath.Join("/sys/bus/pci/devices", filepath.Base(monitored.BusID), "drm")
entries, err := os.ReadDir(directory)
if err != nil {
result.Warnings = append(result.Warnings, err.Error())
result.Warnings = append(result.Warnings, fmt.Sprintf("resolve Intel GPU %d render device: %v", monitored.Index, err))
continue
}
fields := map[string]string{}
for _, line := range strings.Split(string(content), "\n") {
key, value, found := strings.Cut(line, ":")
if found {
fields[strings.TrimSpace(key)] = strings.TrimSpace(value)
devicePath = ""
for _, entry := range entries {
if re.GetRegex(re.IntelGPURenderNamePattern).MatchString(entry.Name()) {
devicePath = filepath.Join("/dev/dri", entry.Name())
break
}
}
uuid := fields["GPU UUID"]
minor, err := strconv.Atoi(fields["Device Minor"])
if !uuidPattern.MatchString(uuid) || err != nil || minor < 0 {
result.Warnings = append(result.Warnings, fmt.Sprintf("invalid NVIDIA GPU UUID or device minor in %s", informationPath))
if devicePath == "" {
result.Warnings = append(result.Warnings, fmt.Sprintf("Intel GPU %d has no render device", monitored.Index))
continue
}
name := fields["Model"]
if name == "" {
name = "NVIDIA"
}
device = dto.ModelGPUDevice{ID: "nvidia:" + uuid, Name: name + " · " + entry.Name(), Type: "nvidia", UUID: uuid}
devicePath = filepath.Join("/dev", "nvidia"+strconv.Itoa(minor))
case "/sys/class/drm":
if !renderPattern.MatchString(entry.Name()) {
continue
}
vendor, err := os.ReadFile(filepath.Join(directory, entry.Name(), "device/vendor"))
if err != nil {
result.Warnings = append(result.Warnings, err.Error())
continue
}
if strings.TrimSpace(string(vendor)) != "0x8086" {
continue
}
devicePath = filepath.Join("/dev/dri", entry.Name())
device = dto.ModelGPUDevice{ID: "intel:" + devicePath, Name: "Intel · " + entry.Name(), Type: "intel"}
case "/dev":
if !ascendPattern.MatchString(entry.Name()) {
continue
}
devicePath = filepath.Join(directory, entry.Name())
device = dto.ModelGPUDevice{ID: "ascend:" + strings.TrimPrefix(entry.Name(), "davinci"), Name: "Ascend · " + entry.Name(), Type: "ascend"}
}
device.ID = "intel:" + devicePath
device.Name = fmt.Sprintf("XPU %d · %s", monitored.Index, monitored.Name)
device.Type = "intel"
case monitored.NPU != nil:
device.ID = fmt.Sprintf("ascend:%d", monitored.Index)
device.Name = fmt.Sprintf("NPU %d · Chip %d · %s", monitored.NPUIndex, monitored.ChipIndex, monitored.Name)
device.Type = "ascend"
devicePath = fmt.Sprintf("/dev/davinci%d", monitored.Index)
default:
continue
}
if devicePath != "" {
info, err := os.Stat(devicePath)
device.Available = err == nil && info.Mode()&os.ModeCharDevice != 0
if err != nil {
result.Warnings = append(result.Warnings, err.Error())
} else {
device.Available = info.Mode()&os.ModeCharDevice != 0
}
result.Devices = append(result.Devices, device)
}
result.Devices = append(result.Devices, device)
}
return result
}
@@ -649,9 +647,17 @@ func resolveModelGPUValue(value string, params map[string]interface{}) (string,
func loadModelAscendDeviceIDs() []string {
ids := []string{}
for _, device := range LoadModelGPUDevices().Devices {
if device.Type == "ascend" && device.Available {
ids = append(ids, strings.TrimPrefix(device.ID, "ascend:"))
entries, err := os.ReadDir("/dev")
if err != nil {
return ids
}
for _, entry := range entries {
if !re.GetRegex(re.AscendGPUDeviceNamePattern).MatchString(entry.Name()) {
continue
}
info, err := os.Stat(filepath.Join("/dev", entry.Name()))
if err == nil && info.Mode()&os.ModeCharDevice != 0 {
ids = append(ids, strings.TrimPrefix(entry.Name(), "davinci"))
}
}
slices.SortFunc(ids, func(a, b string) int {
+2
View File
@@ -2,6 +2,8 @@ module github.com/1Panel-dev/1Panel/agent
go 1.27.3
godebug multipathtcp=0
replace github.com/go-acme/lego/v5 => github.com/1Panel-dev/lego/v5 v5.3.1
require (
+2 -1
View File
@@ -46,7 +46,7 @@ ErrHttpReqNotFound: 'Requested resource not found: {{ .err }}'
# quick jump
ErrMinQuickJump: 'Set at least one quick jump entry'
ErrMaxQuickJump: 'At most 4 quick jump entries'
ErrMaxQuickJump: 'At most 5 quick jump entries'
# common
ErrShutDown: 'Command stopped manually'
@@ -252,6 +252,7 @@ ErrSSLCertificateNotFound: 'The certificate file does not exist'
ErrSSLKeyFormat: 'Private key file verification failed'
ErrSSLCertificateFormat: 'Invalid certificate format; use PEM'
ErrEabKidOrEabHmacKeyCannotBlank: 'EabKid or EabHmacKey cannot be blank'
ErrOpenrestyInstall: "Please install OpenResty from the App Store first"
ErrOpenrestyNotFound: 'HTTP mode requires OpenResty'
ApplySSLStart: 'Starting certificate request. Domain: [{{ .domain }}], method: [{{ .type }}]'
dnsAccount: 'DNS Auto'
+2 -1
View File
@@ -46,7 +46,7 @@ ErrHttpReqNotFound: 'No se pudo encontrar el recurso solicitado {{ .err }}'
# salto rápido
ErrMinQuickJump: 'Agrega al menos una entrada rápida'
ErrMaxQuickJump: 'Máximo 4 entradas rápidas'
ErrMaxQuickJump: 'Máximo 5 entradas rápidas'
# común
ErrShutDown: 'Comando detenido manualmente'
@@ -252,6 +252,7 @@ ErrSSLCertificateNotFound: 'El archivo de certificado no existe'
ErrSSLKeyFormat: 'Error en la verificación del archivo de clave privada'
ErrSSLCertificateFormat: 'Formato de certificado incorrecto, use formato pem'
ErrEabKidOrEabHmacKeyCannotBlank: 'EabKid o EabHmacKey no pueden estar en blanco'
ErrOpenrestyInstall: "Instale primero OpenResty desde la tienda de aplicaciones"
ErrOpenrestyNotFound: 'El modo HTTP requiere OpenResty'
ApplySSLStart: 'Iniciando solicitud de certificado, dominio [{{ .domain }}] método de solicitud [{{ .type }}]'
dnsAccount: 'DNS Automático'
+2 -1
View File
@@ -46,7 +46,7 @@ ErrHttpReqNotFound: 'منبع درخواستی یافت نشد: {{ .err }}'
# پرش سریع
ErrMinQuickJump: 'حداقل یک ورودی پرش سریع تنظیم کنید'
ErrMaxQuickJump: 'حداکثر ۴ ورودی پرش سریع'
ErrMaxQuickJump: 'حداکثر ۵ ورودی پرش سریع'
# عمومی
ErrShutDown: 'دستور به صورت دستی متوقف شد'
@@ -252,6 +252,7 @@ ErrSSLCertificateNotFound: 'فایل گواهی وجود ندارد'
ErrSSLKeyFormat: 'تأیید فایل کلید خصوصی ناموفق بود'
ErrSSLCertificateFormat: 'فرمت گواهی نامعتبر است؛ از PEM استفاده کنید'
ErrEabKidOrEabHmacKeyCannotBlank: 'EabKid یا EabHmacKey نمی‌توانند خالی باشند'
ErrOpenrestyInstall: "لطفاً ابتدا OpenResty را از فروشگاه برنامه‌ها نصب کنید"
ErrOpenrestyNotFound: 'حالت HTTP نیاز به OpenResty دارد'
ApplySSLStart: 'شروع درخواست گواهی. دامنه: [{{ .domain }}]، روش: [{{ .type }}]'
dnsAccount: 'خودکار DNS'
+2 -1
View File
@@ -46,7 +46,7 @@ ErrHttpReqNotFound: '要求されたリソース {{ .err }} が見つかりま
# クイックジャンプ
ErrMinQuickJump: 'クイックアクセスを1件以上追加'
ErrMaxQuickJump: '最大4件のクイックアクセス'
ErrMaxQuickJump: '最大5件のクイックアクセス'
# 共通
ErrShutDown: 'コマンドは手動で停止されました'
@@ -252,6 +252,7 @@ ErrSSLCertificateNotFound: '証明書ファイルが存在しません'
ErrSSLKeyFormat: '秘密鍵ファイルの検証に失敗しました'
ErrSSLCertificateFormat: '証明書ファイルの形式が正しくありません。pem 形式を使用してください'
ErrEabKidOrEabHmacKeyCannotBlank: 'EabKid または EabHmacKey は空白にできません'
ErrOpenrestyInstall: "先にアプリストアから OpenResty をインストールしてください"
ErrOpenrestyNotFound: 'HTTP モードでは OpenResty が必要です'
ApplySSLStart: '証明書の申請を開始します。ドメイン名 [{{ .domain }}] 申請方法 [{{ .type }}]'
dnsAccount: 'DNS 自動'
+2 -1
View File
@@ -46,7 +46,7 @@ ErrHttpReqNotFound: '요청한 리소스 {{ .err }}을 찾을 수 없습니다.'
# 빠른 이동
ErrMinQuickJump: '퀵 액세스를 하나 이상 추가하세요'
ErrMaxQuickJump: '퀵 액세스는 최대 4개'
ErrMaxQuickJump: '퀵 액세스는 최대 5개'
# 공통
ErrShutDown: '명령이 수동으로 중단되었습니다'
@@ -252,6 +252,7 @@ ErrSSLCertificateNotFound: '인증서 파일이 존재하지 않습니다'
ErrSSLKeyFormat: '개인 키 파일 검증에 실패했습니다'
ErrSSLCertificateFormat: '인증서 파일 형식이 올바르지 않습니다. pem 형식을 사용하세요.'
ErrEabKidOrEabHmacKeyCannotBlank: 'EabKid 또는 EabHmacKey는 비워둘 수 없습니다'
ErrOpenrestyInstall: "먼저 앱 스토어에서 OpenResty를 설치하세요"
ErrOpenrestyNotFound: 'HTTP 모드에서는 OpenResty가 필요합니다'
ApplySSLStart: '인증서 신청 시작, 도메인 이름 [{{ .domain }}] 신청 방법 [{{ .type }}]'
dnsAccount: 'DNS 자동'
+2 -1
View File
@@ -32,7 +32,7 @@ ErrApiConfigKeyTimeInvalid: 'ເວລາ (Timestamp) ຂອງ API ບໍ່ຖ
ErrShutDown: 'ຄຳສັ່ງຖືກຢຸດດ້ວຍຕົນເອງ'
ErrMinQuickJump: 'ກະລຸນາຕັ້ງຄ່າລາຍການທາງລັດຢ່າງໜ້ອຍໜຶ່ງລາຍການ'
ErrMaxQuickJump: 'ສາມາດຕັ້ງຄ່າລາຍການທາງລັດໄດ້ສູງສຸດ 4 ລາຍການ'
ErrMaxQuickJump: 'ສາມາດຕັ້ງຄ່າລາຍການທາງລັດໄດ້ສູງສຸດ 5 ລາຍການ'
#common
ErrUsernameIsExist: 'ຊື່ຜູ້ໃຊ້ມີຢູ່ໃນລະບົບແລ້ວ'
@@ -242,6 +242,7 @@ ErrSSLCertificateNotFound: 'ບໍ່ມີໄຟລ໌ໃບຮັບຮອງ
ErrSSLKeyFormat: 'ການກວດສອບໄຟລ໌ກຸນແຈສ່ວນຕົວລົ້ມເຫຼວ'
ErrSSLCertificateFormat: 'ຮູບແບບໃບຮັບຮອງບໍ່ຖືກຕ້ອງ; ກະລຸນາໃຊ້ PEM'
ErrEabKidOrEabHmacKeyCannotBlank: 'EabKid ຫຼື EabHmacKey ບໍ່ສາມາດປະຫວ່າງໄດ້'
ErrOpenrestyInstall: "ກະລຸນາຕິດຕັ້ງ OpenResty ຈາກຮ້ານແອັບກ່ອນ"
ErrOpenrestyNotFound: 'ໂໝດ HTTP ຕ້ອງການ OpenResty'
ApplySSLStart: 'ກຳລັງເລີ່ມການຮ້ອງຂໍໃບຮັບຮອງ. ໂດເມນ: [{{ .domain }}], ວິທີການ: [{{ .type }}]'
dnsAccount: 'DNS ອັດຕະໂນມັດ'
+2 -1
View File
@@ -46,7 +46,7 @@ ErrHttpReqNotFound: 'Sumber yang diminta {{ .err }} tidak dapat ditemui'
# lompat pantas
ErrMinQuickJump: 'Tambah sekurang-kurangnya satu pintasan pantas'
ErrMaxQuickJump: 'Maksimum 4 pintasan pantas'
ErrMaxQuickJump: 'Maksimum 5 pintasan pantas'
# umum
ErrShutDown: 'Arahan diberhentikan manual'
@@ -252,6 +252,7 @@ ErrSSLCertificateNotFound: 'Fail sijil tidak wujud'
ErrSSLKeyFormat: 'Pengesahan fail kunci peribadi gagal'
ErrSSLCertificateFormat: 'Format fail sijil tidak betul, sila gunakan format pem'
ErrEabKidOrEabHmacKeyCannotBlank: 'EabKid atau EabHmacKey tidak boleh kosong'
ErrOpenrestyInstall: "Sila pasang OpenResty daripada gedung aplikasi terlebih dahulu"
ErrOpenrestyNotFound: 'Mod HTTP memerlukan OpenResty dipasang terlebih dahulu'
ApplySSLStart: 'Mula memohon sijil, nama domain [{{ .domain }}] kaedah permohonan [{{ .type }}]'
dnsAccount: 'DNS automatik'
+2 -1
View File
@@ -46,7 +46,7 @@ ErrHttpReqNotFound: 'O recurso solicitado {{ .err }} não pôde ser encontrado'
# atalho rápido
ErrMinQuickJump: 'Adicione pelo menos um atalho rápido'
ErrMaxQuickJump: 'Máximo 4 atalhos rápidos'
ErrMaxQuickJump: 'Máximo 5 atalhos rápidos'
# comum
ErrShutDown: 'Comando parado manualmente'
@@ -252,6 +252,7 @@ ErrSSLCertificateNotFound: 'O arquivo de certificado não existe'
ErrSSLKeyFormat: 'Falha na verificação do arquivo de chave privada'
ErrSSLCertificateFormat: 'O formato do arquivo de certificado está incorreto, use o formato pem'
ErrEabKidOrEabHmacKeyCannotBlank: 'EabKid ou EabHmacKey não podem estar em branco'
ErrOpenrestyInstall: "Instale primeiro o OpenResty pela loja de aplicativos"
ErrOpenrestyNotFound: 'O modo HTTP requer o OpenResty instalado'
ApplySSLStart: 'Comece a solicitar um certificado, nome de domínio [{{ .domain }}] método de solicitação [{{ .type }}]'
dnsAccount: 'DNS Automático'
+2 -1
View File
@@ -46,7 +46,7 @@ ErrHttpReqNotFound: 'Запрошенный ресурс {{ .err }} не най
# быстрый переход
ErrMinQuickJump: 'Добавьте хотя бы одну быструю ссылку'
ErrMaxQuickJump: 'Не более 4 быстрых ссылок'
ErrMaxQuickJump: 'Не более 5 быстрых ссылок'
# общее
ErrShutDown: 'Команда остановлена вручную'
@@ -252,6 +252,7 @@ ErrSSLCertificateNotFound: 'Файл сертификата не существ
ErrSSLKeyFormat: 'Проверка файла закрытого ключа не удалась'
ErrSSLCertificateFormat: 'Неверный формат файла сертификата, используйте формат pem'
ErrEabKidOrEabHmacKeyCannotBlank: 'EabKid или EabHmacKey не могут быть пустыми'
ErrOpenrestyInstall: "Сначала установите OpenResty из магазина приложений"
ErrOpenrestyNotFound: 'Для режима HTTP требуется OpenResty'
ApplySSLStart: 'Начать подачу заявки на сертификат, доменное имя [{{ .domain }}], метод подачи заявки [{{ .type }}]'
dnsAccount: 'DNS Авто'
+2 -1
View File
@@ -46,7 +46,7 @@ ErrHttpReqNotFound: 'İstenen kaynak {{ .err }} bulunamadı'
# hızlı geçiş
ErrMinQuickJump: 'En az bir hızlı erişim ekle'
ErrMaxQuickJump: '4 hızlı erişim hakkı var'
ErrMaxQuickJump: '5 hızlı erişim hakkı var'
# genel
ErrShutDown: 'Komut manuel durduruldu'
@@ -252,6 +252,7 @@ ErrSSLCertificateNotFound: 'Sertifika dosyası mevcut değil'
ErrSSLKeyFormat: 'Özel anahtar dosyası doğrulama başarısız'
ErrSSLCertificateFormat: 'Sertifika dosyası formatı yanlış, lütfen pem formatını kullanın'
ErrEabKidOrEabHmacKeyCannotBlank: 'EabKid veya EabHmacKey boş olamaz'
ErrOpenrestyInstall: "Lütfen önce uygulama mağazasından OpenResty yükleyin"
ErrOpenrestyNotFound: 'HTTP modu için OpenResty gereklidir'
ApplySSLStart: 'Sertifika başvurusu başlatılıyor, alan adı [{{ .domain }}] başvuru yöntemi [{{ .type }}]'
dnsAccount: 'DNS Otomatik'
+2 -1
View File
@@ -46,7 +46,7 @@ ErrHttpReqNotFound: '無法找到要求的資源{{ .err }}'
# 快速跳轉
ErrMinQuickJump: '請至少設定一個快速跳轉入口!'
ErrMaxQuickJump: '最多可設定四個快速跳轉入口!'
ErrMaxQuickJump: '最多可設定五個快速跳轉入口!'
# 通用
ErrShutDown: '指令被手動結束!'
@@ -252,6 +252,7 @@ ErrSSLCertificateNotFound: '憑證檔案不存在'
ErrSSLKeyFormat: '私鑰檔案驗證失敗'
ErrSSLCertificateFormat: '憑證檔案格式錯誤,請使用 pem 格式'
ErrEabKidOrEabHmacKeyCannotBlank: 'EabKid 或 EabHmacKey 不可為空'
ErrOpenrestyInstall: "請先在應用程式商店安裝 OpenResty"
ErrOpenrestyNotFound: 'HTTP 模式需要先安裝 OpenResty'
ApplySSLStart: '開始申請憑證,網域名稱[{{ .domain }}] 申請方式[{{ .type }}]'
dnsAccount: 'DNS 自動'
+2 -1
View File
@@ -46,7 +46,7 @@ ErrHttpReqNotFound: "无法找到请求的资源 {{ .err }}"
# 快速跳转
ErrMinQuickJump: "至少设置 1 个快速跳转入口"
ErrMaxQuickJump: "最多设置 4 个快速跳转入口"
ErrMaxQuickJump: "最多设置 5 个快速跳转入口"
# 通用
ErrShutDown: "命令已手动终止"
@@ -252,6 +252,7 @@ ErrSSLCertificateNotFound: '证书文件不存在'
ErrSSLKeyFormat: '私钥文件校验失败'
ErrSSLCertificateFormat: '证书文件格式错误,请使用 pem 格式'
ErrEabKidOrEabHmacKeyCannotBlank: 'EabKid 或 EabHmacKey 不能为空'
ErrOpenrestyInstall: "请先在应用商店安装 OpenResty"
ErrOpenrestyNotFound: 'Http 模式需要首先安装 Openresty'
ApplySSLStart: '开始申请证书,域名 [{{ .domain }}],方式 [{{ .type }}]'
dnsAccount: "DNS 自动"
+1
View File
@@ -119,6 +119,7 @@ func agentDBMigrations() []*gormigrate.Migration {
migrations.AddQuickDirectory,
migrations.AddLocalModelAccountLink,
migrations.AddOllamaModelAccountSync,
migrations.AddModelQuickJump,
}
}
+13
View File
@@ -1939,3 +1939,16 @@ var AddOllamaModelAccountSync = &gormigrate.Migration{
return nil
},
}
var AddModelQuickJump = &gormigrate.Migration{
ID: "20261030-add-model-quick-jump",
Migrate: func(tx *gorm.DB) error {
return tx.Where("name = ?", "ModelServices").FirstOrCreate(&model.QuickJump{
Name: "ModelServices",
Title: "aiTools.model.model",
Recommend: 0,
IsShow: true,
Router: "/ai/model",
}).Error
},
}
+2 -1
View File
@@ -72,7 +72,8 @@ type Device struct {
}
type Basic struct {
UUID string `json:"uuid"`
DrmDevice string `json:"-"`
UUID string `json:"uuid"`
DeviceID int `json:"deviceID"`
DeviceName string `json:"deviceName"`
+4
View File
@@ -93,6 +93,7 @@ func (c Client) loadDeviceInfo(ctx context.Context, device discoveryDevice, wg *
xpu := Device{
ProcessStatus: "unavailable",
Basic: Basic{
DrmDevice: device.DrmDevice,
UUID: device.UUID,
DeviceID: device.DeviceID,
DeviceName: device.DeviceName,
@@ -137,6 +138,9 @@ func (c Client) loadDeviceInfo(ctx context.Context, device discoveryDevice, wg *
return
}
if info.DrmDevice != "" {
xpu.Basic.DrmDevice = info.DrmDevice
}
xpu.Basic.DriverVersion = info.DriverVersion
bytes, err := strconv.ParseInt(info.MemoryPhysicalSizeByte, 10, 64)
+1
View File
@@ -71,6 +71,7 @@ var WebUrlMap = map[string]struct{}{
"/ai/skills-hub": {},
"/ai/skills-hub/targets": {},
"/ai/benchmark": {},
"/ai/model": {},
"/ai/model/account": {},
"/ai/model/local": {},
"/ai/model/local/templates": {},
+2
View File
@@ -2,6 +2,8 @@ module github.com/1Panel-dev/1Panel/core
go 1.27.3
godebug multipathtcp=0
require (
github.com/beevik/etree v1.7.0
github.com/coreos/go-oidc/v3 v3.20.0
+3
View File
@@ -677,6 +677,7 @@ export namespace AI {
}
export interface AgentAccountModelCreateReq {
taskID?: string;
accountId: number;
model: AgentAccountModel;
}
@@ -687,6 +688,7 @@ export namespace AI {
}
export interface AgentAccountModelDeleteReq {
deleteFiles?: boolean;
accountId: number;
recordId: number;
}
@@ -737,6 +739,7 @@ export namespace AI {
baseURL: string;
apiType: string;
authMode: string;
models?: AgentAccountModel[];
verifyModel: string;
validateAvailability: boolean;
remark: string;
@@ -92,12 +92,15 @@ const expiresInfo = ref(0);
const expiresAlertVisible = ref(false);
const expiresAlertKey = computed(() => (isEnterprise.value ? 'xpack.expiresEnterpriseAlert' : 'xpack.expiresProAlert'));
const handleChange = (label: string) => {
const handleChange = async (label: string) => {
const btn = buttonArray.value.find((btn) => btn.label === label);
if (!btn) return;
if (btn.path) routerToPathWithQuery(btn.path, { uncached: 'true' });
else if (btn.name) routerToNameWithQuery(btn.name, { uncached: 'true' });
activeName.value = btn.label;
try {
if (btn.path) await routerToPathWithQuery(btn.path, { uncached: 'true' });
else if (btn.name) await routerToNameWithQuery(btn.name, { uncached: 'true' });
} finally {
syncActiveName();
}
};
onMounted(() => {
@@ -154,7 +157,9 @@ function syncActiveName() {
if (buttonArray.value.length) {
let isPathExist = false;
const btn = buttonArray.value.find((btn) => {
return btn.path && router.currentRoute.value.path.startsWith(btn.path);
return btn.path
? router.currentRoute.value.path.startsWith(btn.path)
: btn.name && router.currentRoute.value.name === btn.name;
});
if (btn) {
isPathExist = true;
+12 -3
View File
@@ -498,7 +498,7 @@ const message = {
quickDirDelete: 'Delete ({0})?',
quickDir: 'Quick Access',
minQuickJump: 'Add at least one quick access entry',
maxQuickJump: 'You can add up to four quick access entries',
maxQuickJump: 'You can add up to five quick access entries',
database: 'Database - All',
restart_1panel: 'Restart panel',
restart_system: 'Restart server',
@@ -844,6 +844,9 @@ const message = {
discoverModelsFailedUseDefaults: 'Failed to get models. Default models are being used.',
accountModelsDuplicate: 'Duplicate models were found in the catalog',
modelPool: 'Model Pool',
ollamaRegisterOnly:
'Only register models already available on this Ollama service. No download will be started.',
deleteModelFiles: 'Also delete model files from the Ollama service',
fallbackModels: 'Fallback Models',
addFallbackModel: 'Add Fallback Model',
fallbackModelsEmpty: 'No fallback models configured',
@@ -1092,14 +1095,19 @@ const message = {
container_conn_helper: 'Use this address for inter-container access or connection',
from_remote: 'This model was not downloaded via 1Panel, no related pull logs.',
no_logs: 'The pull logs for this model have been deleted and cannot be viewed.',
vllmVersionHelper: 'For FusionXpark GB10 servers, please select the -cu130 version.',
vllmVersionHelper: {
default: 'Official standard version with CUDA 13.0, for newer drivers and newer GPU architectures.',
cuda: 'Legacy architecture compatibility version with CUDA 12.9, for older GPU architectures using CUDA versions below 13.0.',
ubuntu: 'NVIDIA GB10 standard version with CUDA 13.0, for NVIDIA GB10 devices.',
dspark: 'NVIDIA GB10 dual-device custom version, using a custom image maintained by 1Panel.',
},
vllmDeployProfile: 'Deployment model',
vllmProfileFlash0731: 'DeepSeek V4 Flash 0731',
vllmProfileVisionExp: 'DeepSeek V4 Flash Vision Exp',
vllmCommandPortHelper:
'The startup command must use port {0}; otherwise, the service will be inaccessible.',
ascendVisibleDevices: 'Ascend visible devices (ASCEND_RT_VISIBLE_DEVICES)',
syncModelAccount: 'Sync to model account',
syncModelAccount: 'Sync to model service',
modelAccountAddressHelper:
'Container address is for agent containers; 127.0.0.1, system IP, or custom address is for AI Gateway or external services.',
containerAddress: 'Container address',
@@ -1757,6 +1765,7 @@ const message = {
migModeNA: 'Not Supported',
current: 'Real-time Status',
history: 'Monitoring Data',
viewHistory: 'View monitoring data',
notSupport: 'The current version or driver does not support displaying this parameter.',
},
mcp: {
+13 -3
View File
@@ -497,7 +497,7 @@ const message = {
quickDirDelete: '¿Confirmar la eliminación de ({0})?',
quickDir: 'Acceso rápido',
minQuickJump: 'Agrega al menos una entrada de acceso rápido',
maxQuickJump: 'Puedes agregar hasta cuatro entradas de acceso rápido',
maxQuickJump: 'Puedes agregar hasta cinco entradas de acceso rápido',
database: 'Database - All',
restart_1panel: 'Reiniciar panel',
restart_system: 'Reiniciar servidor',
@@ -849,6 +849,9 @@ const message = {
'No se pudieron obtener los modelos. Se usarán los modelos predeterminados.',
accountModelsDuplicate: 'Existen modelos duplicados en el catálogo',
modelPool: 'Pool de modelos',
ollamaRegisterOnly:
'Solo se registran modelos ya disponibles en este servicio Ollama. No se iniciará ninguna descarga.',
deleteModelFiles: 'Eliminar también los archivos del modelo del servicio Ollama',
fallbackModels: 'Modelos de respaldo',
addFallbackModel: 'Agregar modelo de respaldo',
fallbackModelsEmpty: 'No hay modelos de respaldo configurados',
@@ -1094,14 +1097,20 @@ const message = {
container_conn_helper: 'Utilice esta dirección para el acceso o conexión entre contenedores',
from_remote: 'Este modelo no fue descargado vía 1Panel, no hay registros de descarga relacionados.',
no_logs: 'Los registros de descarga de este modelo han sido eliminados y no se pueden consultar.',
vllmVersionHelper: 'Para servidores FusionXpark GB10, seleccione la versión -cu130.',
vllmVersionHelper: {
default:
'Versión oficial estándar con CUDA 13.0, para controladores recientes y GPU de nuevas arquitecturas.',
cuda: 'Versión compatible con arquitecturas antiguas con CUDA 12.9, para GPU antiguas que utilizan versiones de CUDA anteriores a 13.0.',
ubuntu: 'Versión estándar de NVIDIA GB10 con CUDA 13.0, para dispositivos NVIDIA GB10.',
dspark: 'Versión personalizada de NVIDIA GB10 para dos dispositivos, con una imagen mantenida por 1Panel.',
},
vllmDeployProfile: 'Modelo de despliegue',
vllmProfileFlash0731: 'DeepSeek V4 Flash 0731',
vllmProfileVisionExp: 'DeepSeek V4 Flash Vision Exp',
vllmCommandPortHelper:
'El comando de inicio debe usar el puerto {0}; de lo contrario, no se podrá acceder al servicio.',
ascendVisibleDevices: 'Dispositivos Ascend visibles (ASCEND_RT_VISIBLE_DEVICES)',
syncModelAccount: 'Sincronizar con cuenta de modelo',
syncModelAccount: 'Sincronizar con servicio de modelo',
modelAccountAddressHelper:
'La dirección del contenedor es para contenedores de agentes; 127.0.0.1, IP del sistema o dirección personalizada es para AI Gateway o servicios externos.',
containerAddress: 'Dirección del contenedor',
@@ -1769,6 +1778,7 @@ const message = {
migModeNA: 'No Compatible',
current: 'Estado en tiempo real',
history: 'Datos de monitoreo',
viewHistory: 'Ver datos de monitoreo',
notSupport: 'La versión actual o el controlador no admiten mostrar este parámetro.',
processCount: 'Procesos',
},
+11 -3
View File
@@ -493,7 +493,7 @@ const message = {
quickDirDelete: 'حذف ({0}) را تأیید می‌کنید؟',
quickDir: 'دسترسی سریع',
minQuickJump: 'حداقل یک ورودی دسترسی سریع اضافه کنید',
maxQuickJump: 'می‌توانید حداکثر چهار ورودی دسترسی سریع اضافه کنید',
maxQuickJump: 'می‌توانید حداکثر پنج ورودی دسترسی سریع اضافه کنید',
database: 'پایگاه داده - همه',
restart_1panel: 'راه‌اندازی مجدد پنل',
restart_system: 'راه‌اندازی مجدد سرور',
@@ -839,6 +839,8 @@ const message = {
discoverModelsFailedUseDefaults: 'دریافت مدل‌ها ناموفق بود. از مدل‌های پیش‌فرض استفاده می‌شود.',
accountModelsDuplicate: 'مدل‌های تکراری در کاتالوگ یافت شد',
modelPool: 'مجموعه مدل‌ها',
ollamaRegisterOnly: 'فقط مدل‌های موجود در این سرویس Ollama ثبت می‌شوند. دانلودی آغاز نخواهد شد.',
deleteModelFiles: 'فایل‌های مدل نیز از سرویس Ollama حذف شوند',
fallbackModels: 'مدل‌های جایگزین',
addFallbackModel: 'افزودن مدل جایگزین',
fallbackModelsEmpty: 'هیچ مدل جایگزینی پیکربندی نشده است',
@@ -1076,14 +1078,19 @@ const message = {
container_conn_helper: 'از این آدرس برای دسترسی بین کانتینرها یا اتصال استفاده کنید',
from_remote: 'این مدل از طریق 1Panel دانلود نشده است، لاگ مربوط به دریافت وجود ندارد.',
no_logs: 'لاگ دریافت این مدل حذف شده است و قابل مشاهده نیست.',
vllmVersionHelper: 'برای سرورهای FusionXpark GB10، لطفاً نسخه -cu130 را انتخاب کنید.',
vllmVersionHelper: {
default: 'نسخه استاندارد رسمی با CUDA 13.0، مناسب درایورهای جدید و معماری‌های جدید GPU.',
cuda: 'نسخه سازگار با معماری‌های قدیمی با CUDA 12.9، مناسب GPUهای قدیمی که از نسخه‌های CUDA پایین‌تر از 13.0 استفاده می‌کنند.',
ubuntu: 'نسخه استاندارد NVIDIA GB10 با CUDA 13.0، مناسب دستگاه‌های NVIDIA GB10.',
dspark: 'نسخه سفارشی NVIDIA GB10 برای دو دستگاه، با ایمیج سفارشی که توسط 1Panel نگهداری می‌شود.',
},
vllmDeployProfile: 'مدل استقرار',
vllmProfileFlash0731: 'DeepSeek V4 Flash 0731',
vllmProfileVisionExp: 'DeepSeek V4 Flash Vision Exp',
vllmCommandPortHelper:
'فرمان راه‌اندازی باید از پورت {0} استفاده کند؛ در غیر این صورت سرویس قابل دسترسی نخواهد بود.',
ascendVisibleDevices: 'دستگاه‌های قابل مشاهده Ascend (ASCEND_RT_VISIBLE_DEVICES)',
syncModelAccount: 'همگام‌سازی با حساب مدل',
syncModelAccount: 'همگام‌سازی با سرویس مدل',
modelAccountAddressHelper:
'آدرس کانتینر برای کانتینرهای عامل است؛ 127.0.0.1، IP سیستم یا آدرس سفارشی برای دروازه AI یا خدمات خارجی است.',
containerAddress: 'آدرس کانتینر',
@@ -1739,6 +1746,7 @@ const message = {
migModeNA: 'پشتیبانی نمی‌شود',
current: 'وضعیت بلادرنگ',
history: 'داده‌های نظارتی',
viewHistory: 'مشاهده داده‌های نظارتی',
notSupport: 'نسخه فعلی یا درایور از نمایش این پارامتر پشتیبانی نمی‌کند.',
},
mcp: {
+12 -3
View File
@@ -496,7 +496,7 @@ const message = {
quickDirDelete: '({0})を削除しますか?',
quickDir: 'クイックアクセス',
minQuickJump: 'クイックアクセス項目を1つ以上追加してください',
maxQuickJump: '最大4件のクイックアクセス項目を追加できます',
maxQuickJump: '最大5件のクイックアクセス項目を追加できます',
database: 'データベース - すべて',
restart_1panel: 'パネルを再起動します',
restart_system: 'サーバーを再起動します',
@@ -843,6 +843,8 @@ const message = {
discoverModelsFailedUseDefaults: 'モデルを取得できませんでした。デフォルトモデルを使用しています。',
accountModelsDuplicate: 'カタログに重複したモデルがあります',
modelPool: 'モデルプール',
ollamaRegisterOnly: 'この Ollama サービスに存在するモデルのみを登録します。ダウンロードは開始されません。',
deleteModelFiles: 'Ollama サービス上のモデルファイルも削除する',
fallbackModels: 'フォールバックモデル',
addFallbackModel: 'フォールバックモデルを追加',
fallbackModelsEmpty: 'フォールバックモデルは未設定です',
@@ -1080,14 +1082,20 @@ const message = {
container_conn_helper: 'コンテナ間のアクセスまたは接続にこのアドレスを使用',
from_remote: 'このモデルは1Panelを介してダウンロードされておらず、関連するプルログはありません。',
no_logs: 'このモデルのプルログは削除されており、関連するログを表示できません。',
vllmVersionHelper: 'FusionXpark GB10 サーバーでは -cu130 バージョンを選択してください。',
vllmVersionHelper: {
default:
'公式の標準バージョン。CUDA 13.0 を使用し、新しいドライバーと新しい GPU アーキテクチャに対応します。',
cuda: '旧アーキテクチャ向け互換バージョン。CUDA 12.9 を使用し、CUDA 13.0 未満を使用する旧世代 GPU に対応します。',
ubuntu: 'NVIDIA GB10 標準版。CUDA 13.0 を使用し、NVIDIA GB10 デバイスに対応します。',
dspark: 'NVIDIA GB10 の 2 台構成向けカスタム版。1Panel が独自に保守するカスタムイメージです。',
},
vllmDeployProfile: 'デプロイモデル',
vllmProfileFlash0731: 'DeepSeek V4 Flash 0731',
vllmProfileVisionExp: 'DeepSeek V4 Flash Vision Exp',
vllmCommandPortHelper:
'起動コマンドではポート {0} を使用する必要があります。使用しない場合、サービスにアクセスできません。',
ascendVisibleDevices: 'Ascend 可視デバイス(ASCEND_RT_VISIBLE_DEVICES)',
syncModelAccount: 'モデルアカウントに同期',
syncModelAccount: 'モデルサービスに同期',
modelAccountAddressHelper:
'コンテナアドレスはエージェントコンテナ向けです。127.0.0.1、システム IP、カスタムアドレスは AI Gateway または外部サービス向けです。',
containerAddress: 'コンテナアドレス',
@@ -1739,6 +1747,7 @@ const message = {
migModeNA: 'サポートされていません',
current: 'リアルタイム状態',
history: '監視データ',
viewHistory: '監視データを表示',
notSupport: '現在のバージョンまたはドライバーはこのパラメータの表示をサポートしていません。',
processCount: 'プロセス数',
},
+11 -3
View File
@@ -491,7 +491,7 @@ const message = {
quickDirDelete: '({0})을(를) 삭제하시겠습니까?',
quickDir: '빠른 액세스',
minQuickJump: '최소 하나의 빠른 액세스 항목을 추가하세요',
maxQuickJump: '빠른 액세스를 최대 4개까지 추가할 수 있습니다',
maxQuickJump: '빠른 액세스를 최대 5개까지 추가할 수 있습니다',
database: '데이터베이스 - 전체',
restart_1panel: '패널 재시작',
restart_system: '서버 재시작',
@@ -833,6 +833,8 @@ const message = {
discoverModelsFailedUseDefaults: '모델을 가져오지 못했습니다. 기본 모델을 사용합니다.',
accountModelsDuplicate: '카탈로그에 중복된 모델이 있습니다',
modelPool: '모델 풀',
ollamaRegisterOnly: '이 Ollama 서비스에 이미 있는 모델만 등록합니다. 다운로드는 시작되지 않습니다.',
deleteModelFiles: 'Ollama 서비스의 모델 파일도 삭제',
fallbackModels: '폴백 모델',
addFallbackModel: '폴백 모델 추가',
fallbackModelsEmpty: '구성된 폴백 모델이 없습니다',
@@ -1070,13 +1072,18 @@ const message = {
container_conn_helper: '컨테이너 간 접근 또는 연결에 이 주소를 사용',
from_remote: '이 모델은 1Panel을 통해 다운로드되지 않았으며 관련 풀 로그가 없습니다.',
no_logs: '이 모델의 풀 로그가 삭제되어 관련 로그를 볼 수 없습니다.',
vllmVersionHelper: 'FusionXpark GB10 서버는 -cu130 버전을 선택하세요.',
vllmVersionHelper: {
default: '공식 표준 버전으로, CUDA 13.0을 사용하며 최신 드라이버와 새로운 GPU 아키텍처에 적합합니다.',
cuda: '이전 아키텍처 호환 버전으로, CUDA 12.9를 사용하며 CUDA 13.0 미만을 사용하는 이전 GPU 아키텍처에 적합합니다.',
ubuntu: 'NVIDIA GB10 표준 버전으로, CUDA 13.0을 사용하며 NVIDIA GB10 장치에 적합합니다.',
dspark: 'NVIDIA GB10 장치 2대 구성용 맞춤 버전으로, 1Panel이 자체 유지 관리하는 맞춤 이미지입니다.',
},
vllmDeployProfile: '배포 모델',
vllmProfileFlash0731: 'DeepSeek V4 Flash 0731',
vllmProfileVisionExp: 'DeepSeek V4 Flash Vision Exp',
vllmCommandPortHelper: '시작 명령은 {0} 포트를 사용해야 하며, 그렇지 않으면 서비스에 접근할 수 없습니다.',
ascendVisibleDevices: 'Ascend 표시 장치 (ASCEND_RT_VISIBLE_DEVICES)',
syncModelAccount: '모델 계정에 동기화',
syncModelAccount: '모델 서비스에 동기화',
modelAccountAddressHelper:
'컨테이너 주소는 에이전트 컨테이너 호출에 적합합니다. 127.0.0.1, 시스템 IP 또는 사용자 지정 주소는 AI Gateway 또는 외부 서비스 호출에 적합합니다.',
containerAddress: '컨테이너 주소',
@@ -1724,6 +1731,7 @@ const message = {
migModeNA: '지원되지 않음',
current: '실시간 상태',
history: '모니터링 데이터',
viewHistory: '모니터링 데이터 보기',
notSupport: '현재 버전 또는 드라이버는 이 매개변수 표시를 지원하지 않습니다.',
processCount: '프로세스 수',
},
+11 -3
View File
@@ -492,7 +492,7 @@ const message = {
quickDirDelete: 'ຢືນຢັນການລຶບ ({0}) ຫຼືບໍ່?',
quickDir: 'ເຂົ້າເຖິງດ່ວນ',
minQuickJump: 'ເພີ່ມການເຂົ້າເຖິງດ່ວນຢ່າງໜ້ອຍໜຶ່ງລາຍການ',
maxQuickJump: 'ທ່ານສາມາດເພີ່ມການເຂົ້າເຖິງດ່ວນໄດ້ສູງສຸດສີ່ລາຍການ',
maxQuickJump: 'ທ່ານສາມາດເພີ່ມການເຂົ້າເຖິງດ່ວນໄດ້ສູງສຸດຫ້າລາຍການ',
database: 'ຖານຂໍ້ມູນ - ທັງໝົດ',
restart_1panel: 'ຣີສະຕາດແພນເນວ',
restart_system: 'ຣີສະຕາດເຊີເວີ',
@@ -831,6 +831,8 @@ const message = {
discoverModelsFailedUseDefaults: 'ດຶງລາຍການໂມເດວບໍ່ສຳເລັດ. ກຳລັງໃຊ້ໂມເດວເລີ່ມຕົ້ນ.',
accountModelsDuplicate: 'ພົບໂມເດວທີ່ຊ້ຳກັນໃນລາຍການ',
modelPool: 'ກຸ່ມໂມເດວ (Model Pool)',
ollamaRegisterOnly: 'ລົງທະບຽນສະເພາະໂມເດວທີ່ມີຢູ່ໃນບໍລິການ Ollama ນີ້ແລ້ວ. ຈະບໍ່ເລີ່ມດາວໂຫຼດ.',
deleteModelFiles: 'ລຶບໄຟລ໌ໂມເດວອອກຈາກບໍລິການ Ollama ນຳ',
fallbackModels: 'ໂມເດວສຳຮອງ (Fallback)',
addFallbackModel: 'ເພີ່ມໂມເດວສຳຮອງ',
fallbackModelsEmpty: 'ຍັງບໍ່ໄດ້ຕັ້ງຄ່າໂມເດວສຳຮອງ',
@@ -1067,13 +1069,18 @@ const message = {
container_conn_helper: 'ໃຊ້ທີ່ຢູ່ນີ້ສຳລັບການເຂົ້າເຖິງ ຫຼື ເຊື່ອມຕໍ່ລະຫວ່າງຄອນເທນເນີ',
from_remote: 'ໂມເດວນີ້ບໍ່ໄດ້ດາວໂຫຼດຜ່ານ 1Panel, ບໍ່ມີລັອກການດຶງຂໍ້ມູນທີ່ກ່ຽວຂ້ອງ.',
no_logs: 'ລັອກການດຶງຂໍ້ມູນຂອງໂມເດວນີ້ຖືກລຶບແລ້ວ ແລະ ບໍ່ສາມາດເບິ່ງໄດ້.',
vllmVersionHelper: 'ສຳລັບເຊີເວີ FusionXpark GB10, ກະລຸນາເລືອກເວີຊັນ -cu130.',
vllmVersionHelper: {
default: 'ເວີຊັນມາດຕະຖານທາງການທີ່ໃຊ້ CUDA 13.0, ເໝາະສຳລັບໄດຣເວີໃໝ່ ແລະ GPU ສະຖາປັດຕະຍະກຳໃໝ່.',
cuda: 'ເວີຊັນຮອງຮັບສະຖາປັດຕະຍະກຳເກົ່າທີ່ໃຊ້ CUDA 12.9, ເໝາະສຳລັບ GPU ເກົ່າທີ່ໃຊ້ CUDA ຕ່ຳກວ່າ 13.0.',
ubuntu: 'ເວີຊັນມາດຕະຖານ NVIDIA GB10 ທີ່ໃຊ້ CUDA 13.0, ເໝາະສຳລັບອຸປະກອນ NVIDIA GB10.',
dspark: 'ເວີຊັນປັບແຕ່ງ NVIDIA GB10 ສຳລັບສອງອຸປະກອນ, ໃຊ້ອິມເມຈປັບແຕ່ງທີ່ 1Panel ດູແລເອງ.',
},
vllmDeployProfile: 'ໂມເດວສຳລັບຕິດຕັ້ງ',
vllmProfileFlash0731: 'DeepSeek V4 Flash 0731',
vllmProfileVisionExp: 'DeepSeek V4 Flash Vision Exp',
vllmCommandPortHelper: 'ຄຳສັ່ງເລີ່ມຕົ້ນຕ້ອງໃຊ້ພອດ {0}; ບໍ່ດັ່ງນັ້ນຈະບໍ່ສາມາດເຂົ້າເຖິງບໍລິການໄດ້.',
ascendVisibleDevices: 'ອຸປະກອນ Ascend ທີ່ເຫັນໄດ້ (ASCEND_RT_VISIBLE_DEVICES)',
syncModelAccount: 'ຊິ້ງຄ໌ໄປຍັງບັນຊີໂມເດວ',
syncModelAccount: 'ຊິ້ງຄ໌ໄປຍັງບໍລິການໂມເດວ',
modelAccountAddressHelper:
'ທີ່ຢູ່ຄອນເທນເນີແມ່ນສຳລັບຄອນເທນເນີຕົວແທນ; 127.0.0.1, IP ລະບົບ, ຫຼື ທີ່ຢູ່ກຳນົດເອງແມ່ນສຳລັບ AI Gateway ຫຼື ບໍລິການພາຍນອກ.',
containerAddress: 'ທີ່ຢູ່ຄອນເທນເນີ',
@@ -1716,6 +1723,7 @@ const message = {
migModeNA: 'ບໍ່ຮອງຮັບ',
current: 'ສະຖານະແບບທັນທີ',
history: 'ຂໍ້ມູນການຕິດຕາມ',
viewHistory: 'ເບິ່ງຂໍ້ມູນການຕິດຕາມ',
notSupport: 'ເວີຊັນ ຫຼື driver ປັດຈຸບັນບໍ່ຮອງຮັບການສະແດງຄ່ານີ້.',
},
mcp: {
+12 -3
View File
@@ -503,7 +503,7 @@ const message = {
quickDirDelete: 'Sahkan pemadaman ({0})?',
quickDir: 'Akses Pantas',
minQuickJump: 'Tambah sekurang-kurangnya satu entri akses pantas',
maxQuickJump: 'Anda boleh menambah sehingga empat entri akses pantas',
maxQuickJump: 'Anda boleh menambah sehingga lima entri akses pantas',
database: 'Pangkalan Data - Semua',
restart_1panel: 'Mulakan semula panel',
restart_system: 'Mulakan semula pelayan',
@@ -853,6 +853,9 @@ const message = {
discoverModelsFailedUseDefaults: 'Gagal mendapatkan model. Model lalai sedang digunakan.',
accountModelsDuplicate: 'Terdapat model pendua dalam katalog',
modelPool: 'Katalog model',
ollamaRegisterOnly:
'Daftar hanya model yang sudah tersedia pada perkhidmatan Ollama ini. Tiada muat turun akan dimulakan.',
deleteModelFiles: 'Padam juga fail model daripada perkhidmatan Ollama',
fallbackModels: 'Model fallback',
addFallbackModel: 'Tambah model fallback',
fallbackModelsEmpty: 'Tiada model fallback dikonfigurasikan',
@@ -1102,14 +1105,19 @@ const message = {
container_conn_helper: 'Gunakan alamat ini untuk akses atau sambungan antara kontena',
from_remote: 'Model ini tidak dimuat turun melalui 1Panel; tiada log muat turun berkaitan.',
no_logs: 'Log muat turun model ini telah dipadam dan tidak boleh dilihat.',
vllmVersionHelper: 'Untuk pelayan FusionXpark GB10, sila pilih versi -cu130.',
vllmVersionHelper: {
default: 'Versi standard rasmi dengan CUDA 13.0, untuk pemacu baharu dan seni bina GPU baharu.',
cuda: 'Versi keserasian seni bina lama dengan CUDA 12.9, untuk GPU lama yang menggunakan versi CUDA di bawah 13.0.',
ubuntu: 'Versi standard NVIDIA GB10 dengan CUDA 13.0, untuk peranti NVIDIA GB10.',
dspark: 'Versi tersuai NVIDIA GB10 untuk dua peranti, menggunakan imej tersuai yang diselenggara oleh 1Panel.',
},
vllmDeployProfile: 'Model penggunaan',
vllmProfileFlash0731: 'DeepSeek V4 Flash 0731',
vllmProfileVisionExp: 'DeepSeek V4 Flash Vision Exp',
vllmCommandPortHelper:
'Perintah permulaan mesti menggunakan port {0}; jika tidak, perkhidmatan tidak dapat diakses.',
ascendVisibleDevices: 'Peranti Ascend boleh dilihat (ASCEND_RT_VISIBLE_DEVICES)',
syncModelAccount: 'Segerakkan ke akaun model',
syncModelAccount: 'Segerakkan ke perkhidmatan model',
modelAccountAddressHelper:
'Alamat kontena sesuai untuk kontena agen; 127.0.0.1, IP sistem, atau alamat tersuai sesuai untuk AI Gateway atau perkhidmatan luaran.',
containerAddress: 'Alamat kontena',
@@ -1770,6 +1778,7 @@ const message = {
migModeNA: 'Tidak Disokong',
current: 'Status Masa Nyata',
history: 'Data Pemantauan',
viewHistory: 'Lihat data pemantauan',
notSupport: 'Versi atau pemacu semasa tidak menyokong paparan parameter ini.',
processCount: 'Bilangan proses',
},
+12 -3
View File
@@ -496,7 +496,7 @@ const message = {
quickDirDelete: 'Confirmar a exclusão de ({0})?',
quickDir: 'Acesso rápido',
minQuickJump: 'Adicione pelo menos uma entrada de acesso rápido',
maxQuickJump: 'Você pode adicionar até quatro entradas de acesso rápido',
maxQuickJump: 'Você pode adicionar até cinco entradas de acesso rápido',
database: 'Banco de Dados - Todos',
restart_1panel: 'Reiniciar painel',
restart_system: 'Reiniciar servidor',
@@ -846,6 +846,9 @@ const message = {
discoverModelsFailedUseDefaults: 'Não foi possível obter os modelos. Os modelos padrão estão em uso.',
accountModelsDuplicate: 'Há modelos duplicados no catálogo',
modelPool: 'Catálogo de modelos',
ollamaRegisterOnly:
'Registre apenas modelos já disponíveis neste serviço Ollama. Nenhum download será iniciado.',
deleteModelFiles: 'Excluir também os arquivos do modelo do serviço Ollama',
fallbackModels: 'Modelos de fallback',
addFallbackModel: 'Adicionar modelo de fallback',
fallbackModelsEmpty: 'Nenhum modelo de fallback configurado',
@@ -1098,14 +1101,19 @@ const message = {
container_conn_helper: 'Use este endereço para acesso ou conexão entre contêineres',
from_remote: 'Este modelo não foi baixado pelo 1Panel; não há logs de download relacionados.',
no_logs: 'Os logs de download deste modelo foram excluídos e não podem ser visualizados.',
vllmVersionHelper: 'Para servidores FusionXpark GB10, selecione a versão -cu130.',
vllmVersionHelper: {
default: 'Versão padrão oficial com CUDA 13.0, para drivers recentes e GPUs de novas arquiteturas.',
cuda: 'Versão de compatibilidade com arquiteturas antigas com CUDA 12.9, para GPUs antigas que usam versões do CUDA inferiores à 13.0.',
ubuntu: 'Versão padrão do NVIDIA GB10 com CUDA 13.0, para dispositivos NVIDIA GB10.',
dspark: 'Versão personalizada do NVIDIA GB10 para dois dispositivos, com uma imagem mantida pelo 1Panel.',
},
vllmDeployProfile: 'Modelo de implantação',
vllmProfileFlash0731: 'DeepSeek V4 Flash 0731',
vllmProfileVisionExp: 'DeepSeek V4 Flash Vision Exp',
vllmCommandPortHelper:
'O comando de inicialização deve usar a porta {0}; caso contrário, o serviço ficará inacessível.',
ascendVisibleDevices: 'Dispositivos Ascend visíveis (ASCEND_RT_VISIBLE_DEVICES)',
syncModelAccount: 'Sincronizar com conta de modelo',
syncModelAccount: 'Sincronizar com serviço de modelo',
modelAccountAddressHelper:
'Use o endereço do contêiner para agentes; 127.0.0.1, IP do sistema ou endereço personalizado para AI Gateway ou serviços externos.',
containerAddress: 'Endereço do contêiner',
@@ -1770,6 +1778,7 @@ const message = {
migModeNA: 'Não Suportado',
current: 'Status em tempo real',
history: 'Dados de monitoramento',
viewHistory: 'Ver dados de monitoramento',
notSupport: 'A versão atual ou o driver não suportam exibir este parâmetro.',
processCount: 'Qtd. de processos',
},
+13 -3
View File
@@ -494,7 +494,7 @@ const message = {
quickDirDelete: 'Подтвердить удаление ({0})?',
quickDir: 'Быстрый доступ',
minQuickJump: 'Добавьте хотя бы одну запись быстрого доступа',
maxQuickJump: 'Вы можете добавить до четырех записей быстрого доступа',
maxQuickJump: 'Вы можете добавить до пяти записей быстрого доступа',
database: 'База данных - Все',
restart_1panel: 'Перезапустить панель',
restart_system: 'Перезапустить сервер',
@@ -841,6 +841,9 @@ const message = {
discoverModelsFailedUseDefaults: 'Не удалось получить модели. Используются модели по умолчанию.',
accountModelsDuplicate: 'В каталоге обнаружены повторяющиеся модели',
modelPool: 'Каталог моделей',
ollamaRegisterOnly:
'Регистрируются только модели, уже доступные в этом сервисе Ollama. Загрузка не запускается.',
deleteModelFiles: 'Также удалить файлы модели из сервиса Ollama',
fallbackModels: 'Резервные модели',
addFallbackModel: 'Добавить резервную модель',
fallbackModelsEmpty: 'Резервные модели не настроены',
@@ -1091,13 +1094,19 @@ const message = {
container_conn_helper: 'Используйте этот адрес для доступа или подключения между контейнерами',
from_remote: 'Эта модель не была загружена через 1Panel, нет связанных журналов извлечения.',
no_logs: 'Журналы извлечения для этой модели были удалены и не могут быть просмотрены.',
vllmVersionHelper: 'Для серверов FusionXpark GB10 выберите версию -cu130.',
vllmVersionHelper: {
default:
'Официальная стандартная версия с CUDA 13.0 для новых драйверов и видеокарт с новой архитектурой.',
cuda: 'Версия совместимости со старыми архитектурами с CUDA 12.9 для видеокарт, использующих версии CUDA ниже 13.0.',
ubuntu: 'Стандартная версия NVIDIA GB10 с CUDA 13.0 для устройств NVIDIA GB10.',
dspark: 'Специальная версия NVIDIA GB10 для двух устройств с образом, поддерживаемым 1Panel.',
},
vllmDeployProfile: 'Модель развертывания',
vllmProfileFlash0731: 'DeepSeek V4 Flash 0731',
vllmProfileVisionExp: 'DeepSeek V4 Flash Vision Exp',
vllmCommandPortHelper: 'Команда запуска должна использовать порт {0}, иначе сервис будет недоступен.',
ascendVisibleDevices: 'Видимые устройства Ascend (ASCEND_RT_VISIBLE_DEVICES)',
syncModelAccount: 'Синхронизировать с аккаунтом модели',
syncModelAccount: 'Синхронизировать с сервисом модели',
modelAccountAddressHelper:
'Адрес контейнера подходит для контейнеров агентов; 127.0.0.1, системный IP или пользовательский адрес подходит для AI Gateway или внешних сервисов.',
containerAddress: 'Адрес контейнера',
@@ -1759,6 +1768,7 @@ const message = {
migModeNA: 'Не Поддерживается',
current: 'Состояние в реальном времени',
history: 'Данные мониторинга',
viewHistory: 'Просмотреть данные мониторинга',
notSupport: 'Текущая версия или драйвер не поддерживает отображение этого параметра.',
processCount: 'Кол-во процессов',
},
+11 -3
View File
@@ -499,7 +499,7 @@ const message = {
quickDirDelete: '({0}) silinsin mi?',
quickDir: 'Hızlı Erişim',
minQuickJump: 'En az bir hızlı erişim girdisi ekleyin',
maxQuickJump: 'En fazla dört hızlı erişim girdisi ekleyebilirsiniz',
maxQuickJump: 'En fazla beş hızlı erişim girdisi ekleyebilirsiniz',
database: 'Veritabanı - Tümü',
restart_1panel: 'Paneli yeniden başlat',
restart_system: 'Sunucuyu yeniden başlat',
@@ -849,6 +849,8 @@ const message = {
discoverModelsFailedUseDefaults: 'Modeller alınamadı. Varsayılan modeller kullanılıyor.',
accountModelsDuplicate: 'Katalogda yinelenen modeller bulundu',
modelPool: 'Model kataloğu',
ollamaRegisterOnly: 'Yalnızca bu Ollama hizmetinde mevcut modeller kaydedilir. İndirme başlatılmaz.',
deleteModelFiles: 'Model dosyalarını da Ollama hizmetinden sil',
fallbackModels: 'Yedek modeller',
addFallbackModel: 'Yedek model ekle',
fallbackModelsEmpty: 'Yapılandırılmış yedek model yok',
@@ -1101,14 +1103,19 @@ const message = {
container_conn_helper: 'Konteynerler arası erişim veya bağlantı için bu adresi kullanın',
from_remote: 'Bu model 1Panel aracılığıyla indirilmedi, ilgili çekme logları yok.',
no_logs: 'Bu modelin çekme logları silindi ve görüntülenemiyor.',
vllmVersionHelper: 'FusionXpark GB10 sunucuları için lütfen -cu130 sürümünü seçin.',
vllmVersionHelper: {
default: 'Yeni sürücüler ve yeni GPU mimarileri için CUDA 13.0 içeren resmi standart sürüm.',
cuda: '13.0 altındaki CUDA sürümlerini kullanan eski GPU mimarileri için CUDA 12.9 içeren uyumluluk sürümü.',
ubuntu: 'NVIDIA GB10 cihazları için CUDA 13.0 içeren NVIDIA GB10 standart sürümü.',
dspark: '1Panel tarafından bakımı yapılan özel imajı kullanan, iki cihaz için NVIDIA GB10 özel sürümü.',
},
vllmDeployProfile: 'Dağıtım modeli',
vllmProfileFlash0731: 'DeepSeek V4 Flash 0731',
vllmProfileVisionExp: 'DeepSeek V4 Flash Vision Exp',
vllmCommandPortHelper:
'Başlatma komutu {0} numaralı bağlantı noktasını kullanmalıdır; aksi halde hizmete erişilemez.',
ascendVisibleDevices: 'Görünür Ascend cihazları (ASCEND_RT_VISIBLE_DEVICES)',
syncModelAccount: 'Model hesabına senkronize et',
syncModelAccount: 'Model hizmetine senkronize et',
modelAccountAddressHelper:
'Konteyner adresi ajan konteynerleri içindir; 127.0.0.1, sistem IP veya özel adres AI Gateway ya da harici servisler içindir.',
containerAddress: 'Konteyner adresi',
@@ -1766,6 +1773,7 @@ const message = {
migModeNA: 'Desteklenmiyor',
current: 'Gerçek Zamanlı Durum',
history: 'İzleme Verileri',
viewHistory: 'İzleme verilerini görüntüle',
notSupport: 'Mevcut sürüm veya sürücü bu parametrenin görüntülenmesini desteklemiyor.',
processCount: 'Süreç sayısı',
},
+11 -3
View File
@@ -477,7 +477,7 @@ const message = {
quickDirDelete: '確定刪除({0})',
quickDir: '快捷目錄',
minQuickJump: '請至少設定一個快速跳轉入口',
maxQuickJump: '最多可設定四個快速跳轉入口',
maxQuickJump: '最多可設定五個快速跳轉入口',
database: '資料庫 - 全部',
restart_1panel: '重新啟動面板',
restart_system: '重新啟動伺服器',
@@ -804,6 +804,8 @@ const message = {
discoverModelsFailedUseDefaults: '無法取得模型,已改用預設模型',
accountModelsDuplicate: '模型清單中有重複項目,請檢查後再試一次',
modelPool: '模型池',
ollamaRegisterOnly: '僅登記此 Ollama 服務上已存在的模型,不會觸發下載。',
deleteModelFiles: '同時刪除 Ollama 服務上的模型檔案',
fallbackModels: '備用模型',
addFallbackModel: '新增備用模型',
fallbackModelsEmpty: '尚未設定備用模型',
@@ -1030,13 +1032,18 @@ const message = {
container_conn_helper: '容器間瀏覽或連接使用此地址',
from_remote: '該模型並非透過 1Panel 下載,無相關拉取日誌。',
no_logs: '該模型的拉取日誌已被刪除,無法檢視相關日誌。',
vllmVersionHelper: 'FusionXpark GB10 伺服器請選擇 -cu130 版本',
vllmVersionHelper: {
default: '官方標準版本,CUDA 13.0,適用於新驅動程式和新架構顯示卡。',
cuda: '舊架構相容版本,CUDA 12.9,適用於 CUDA 版本低於 13.0 的舊架構顯示卡。',
ubuntu: 'NVIDIA GB10 標準版,CUDA 13.0,適用於 NVIDIA GB10 裝置。',
dspark: 'NVIDIA GB10 雙裝置客製版,由 1Panel 自行維護的客製映像。',
},
vllmDeployProfile: '部署模型',
vllmProfileFlash0731: 'DeepSeek V4 Flash 0731',
vllmProfileVisionExp: 'DeepSeek V4 Flash Vision Exp',
vllmCommandPortHelper: '啟動命令必須使用 {0} 連接埠,否則服務將無法存取。',
ascendVisibleDevices: 'Ascend 可見裝置(ASCEND_RT_VISIBLE_DEVICES)',
syncModelAccount: '同步到模型帳號',
syncModelAccount: '同步到模型服務',
modelAccountAddressHelper:
'容器地址適合智能體容器呼叫;127.0.0.1、本機 IP 或自訂地址適合 AI 閘道或外部服務呼叫。',
containerAddress: '容器地址',
@@ -1664,6 +1671,7 @@ const message = {
migModeNA: '不支援',
current: '即時狀態',
history: '監控資料',
viewHistory: '查看監控資料',
notSupport: '當前版本或驅動不支援顯示該參數。',
processCount: '行程數',
},
+11 -3
View File
@@ -477,7 +477,7 @@ const message = {
quickDirDelete: '确定删除({0})',
quickDir: '快捷目录',
minQuickJump: '请至少设置一个快速跳转入口!',
maxQuickJump: '最多可设置四个快速跳转入口!',
maxQuickJump: '最多可设置五个快速跳转入口!',
database: '数据库 - 所有',
restart_1panel: '重启面板',
restart_system: '重启服务器',
@@ -821,6 +821,8 @@ const message = {
discoverModelsFailedUseDefaults: '获取模型失败,已使用默认模型',
accountModelsDuplicate: '模型池中存在重复模型,请检查后重试',
modelPool: '模型池',
ollamaRegisterOnly: '仅登记此 Ollama 服务上已存在的模型,不会触发下载。',
deleteModelFiles: '同时删除 Ollama 服务上的模型文件',
fallbackModels: '备用模型',
addFallbackModel: '新增备用模型',
fallbackModelsEmpty: '尚未配置备用模型',
@@ -1043,13 +1045,18 @@ const message = {
container_conn_helper: '容器间访问或连接使用此地址',
from_remote: '该模型并非通过 1Panel 下载,无相关拉取日志。',
no_logs: '该模型的拉取日志已被删除,无法查看相关日志。',
vllmVersionHelper: 'FusionXpark GB10 服务器请选择 -cu130 版本',
vllmVersionHelper: {
default: '官方标准版本,CUDA 13.0,适用于新驱动和新架构显卡。',
cuda: '旧架构兼容版本,CUDA 12.9,适用于 CUDA 版本低于 13.0 的旧架构显卡。',
ubuntu: 'NVIDIA GB10 标准版,CUDA 13.0,适用于 NVIDIA GB10 设备。',
dspark: 'NVIDIA GB10 双设备定制版,1Panel 自行维护的定制镜像。',
},
vllmDeployProfile: '部署模型',
vllmProfileFlash0731: 'DeepSeek V4 Flash 0731',
vllmProfileVisionExp: 'DeepSeek V4 Flash Vision Exp',
vllmCommandPortHelper: '启动命令必须使用 {0} 端口,否则服务将无法访问。',
ascendVisibleDevices: 'Ascend 可见设备(ASCEND_RT_VISIBLE_DEVICES)',
syncModelAccount: '同步到模型账号',
syncModelAccount: '同步到模型服务',
modelAccountAddressHelper:
'容器地址适合智能体容器调用;127.0.0.1、本机 IP 或自定义地址适合 AI 网关或外部服务调用。',
containerAddress: '容器地址',
@@ -1679,6 +1686,7 @@ const message = {
migModeNA: '不支持',
current: '实时状态',
history: '监控数据',
viewHistory: '查看监控数据',
notSupport: '当前版本或驱动不支持显示该参数。',
},
mcp: {
+26 -2
View File
@@ -31,8 +31,15 @@ const clearLoginStatus = () => {
};
router.beforeEach(async (to, from) => {
const { entrance, isEnterprise, isEnterpriseLicenseLoaded, isEnterpriseLicensed, isLogin, isOnRestart } =
useGlobalStore();
const {
entrance,
isEnterprise,
isEnterpriseLicenseLoaded,
isEnterpriseLicensed,
isProductPro,
isLogin,
isOnRestart,
} = useGlobalStore();
NProgress.start();
axiosCanceler.removeAllPending();
@@ -133,6 +140,23 @@ router.beforeEach(async (to, from) => {
removePanelCache('tabs', activeMenuKey);
}
if (to.name === 'ModelFiles') {
if (!isEnterprise.value) {
try {
await loadProductProFromDB();
} catch (error) {
console.warn('Failed to refresh model marketplace license status', error);
}
}
if (!isProductPro.value) {
if (cachedRoute === to.path) {
removePanelCache('tabs', activeMenuKey);
}
NProgress.done();
return { path: '/ai/model/account', query: to.query, replace: true };
}
}
if (!hasRouteAccess(to)) {
MsgError(i18n.global.t('commons.res.forbidden'));
NProgress.done();
+27 -15
View File
@@ -6,30 +6,20 @@ const aiRouter = {
path: '/ai',
name: 'AI-Menu',
component: Layout,
redirect: '/ai/model/account',
redirect: '/ai/model',
meta: {
icon: 'p-jiqiren2',
title: 'menu.aiTools',
},
children: [
{
path: '/ai/model/account',
path: '/ai/model',
name: 'AIModel',
component: () => import('@/views/ai/model/index.vue'),
redirect: '/ai/model/files',
meta: {
icon: 'p-moxing-menu',
title: 'aiTools.model.model',
permission: 'ai_model_view',
},
},
{
path: '/ai/model/local',
hidden: true,
name: 'LocalModel',
component: () => import('@/views/ai/model/index.vue'),
meta: {
title: 'aiTools.model.deployments',
activeMenu: '/ai/model/account',
activeMenu: '/ai/model',
permission: 'ai_model_view',
},
},
@@ -40,7 +30,29 @@ const aiRouter = {
component: () => import('@/views/ai/model/index.vue'),
meta: {
title: 'aiTools.model.files',
activeMenu: '/ai/model/account',
activeMenu: '/ai/model',
permission: 'ai_model_view',
},
},
{
path: '/ai/model/account',
hidden: true,
name: 'ModelServices',
component: () => import('@/views/ai/model/index.vue'),
meta: {
title: 'aiTools.model.services',
activeMenu: '/ai/model',
permission: 'ai_model_view',
},
},
{
path: '/ai/model/local',
hidden: true,
name: 'LocalModel',
component: () => import('@/views/ai/model/index.vue'),
meta: {
title: 'aiTools.model.deployments',
activeMenu: '/ai/model',
permission: 'ai_model_view',
},
},
@@ -91,7 +91,7 @@
<div class="model-discovery">
<div class="model-discovery__header">
<el-radio-group v-model="modelConfigMode" size="small" @change="handleModelConfigModeChange">
<el-radio-button value="discover">
<el-radio-button v-if="supportsModelDiscovery || defaultModels.length" value="discover">
{{
$t(
supportsModelDiscovery
@@ -284,6 +284,7 @@ const { isIntl } = useGlobalStore();
const form = reactive({
id: 0,
appInstallId: 0,
provider: 'custom',
name: '',
baseURL: '',
@@ -308,14 +309,16 @@ const selectedAPIConfig = computed(() =>
);
const isImageAPIType = computed(() => form.apiType.endsWith('-images'));
const isEmbeddingAPIType = computed(() => form.apiType === 'openai-embeddings');
const supportsModelDiscovery = computed(() => !form.id && Boolean(selectedAPIConfig.value?.supportsModelDiscovery));
const supportsModelDiscovery = computed(() => Boolean(selectedAPIConfig.value?.supportsModelDiscovery));
const defaultModels = computed(() => {
if (selectedAPIConfig.value?.models.length) {
return selectedAPIConfig.value.models;
}
return isImageAPIType.value ? [] : selectedProvider.value?.models || [];
});
const showModelDiscovery = computed(() => !form.id && (supportsModelDiscovery.value || defaultModels.value.length > 0));
const showModelDiscovery = computed(
() => !form.appInstallId && (Boolean(form.id) || supportsModelDiscovery.value || defaultModels.value.length > 0),
);
const showInitialModel = computed(
() =>
!form.id &&
@@ -330,7 +333,8 @@ const showVerifyModelSelect = computed(
() =>
requiresVerifyModel.value &&
verifyModelOptions.value.length > 0 &&
(Boolean(form.id) || (!showModelDiscovery.value && !showInitialModel.value)),
!showModelDiscovery.value &&
!showInitialModel.value,
);
const apiTypeOptions = computed(() => selectedProvider.value?.apiTypes.map((item) => item.apiType) || []);
const authModeOptions = computed(() => selectedAPIConfig.value?.authModes || []);
@@ -580,20 +584,18 @@ const submit = async () => {
return;
}
let accountModels = initialModel ? [initialModel] : [];
if (!form.id) {
if (showModelDiscovery.value && modelConfigMode.value === 'manual') {
const models = normalizeManualModels();
if (!models) {
return;
}
accountModels = models;
} else if (showModelDiscovery.value) {
accountModels = discoveredModels.value;
}
if (accountModels.length === 0) {
MsgError(i18n.global.t('aiTools.agents.accountModelsRequired'));
if (showModelDiscovery.value && modelConfigMode.value === 'manual') {
const models = normalizeManualModels();
if (!models) {
return;
}
accountModels = models;
} else if (showModelDiscovery.value) {
accountModels = discoveredModels.value;
}
if ((!form.id || showModelDiscovery.value) && accountModels.length === 0) {
MsgError(i18n.global.t('aiTools.agents.accountModelsRequired'));
return;
}
let verifyModel = '';
if (requiresVerifyModel.value) {
@@ -623,6 +625,7 @@ const submit = async () => {
validateAvailability: form.validateAvailability,
remark: form.remark,
syncAgents: form.syncAgents,
models: showModelDiscovery.value ? accountModels : undefined,
});
} else {
await createAgentAccount({
@@ -653,6 +656,7 @@ const handleClose = () => {
formRef.value?.resetFields();
loading.value = false;
form.id = 0;
form.appInstallId = 0;
form.provider = 'custom';
form.name = '';
form.baseURL = '';
@@ -671,6 +675,7 @@ const handleClose = () => {
interface OpenParams {
id?: number;
appInstallId?: number;
provider?: string;
name?: string;
baseURL?: string;
@@ -691,6 +696,7 @@ const openDrawer = async (params?: OpenParams) => {
}
if (params?.id) {
form.id = params.id;
form.appInstallId = params.appInstallId || 0;
form.provider = params.provider || '';
form.name = params.name || '';
form.baseURL = params.baseURL || '';
@@ -703,12 +709,19 @@ const openDrawer = async (params?: OpenParams) => {
? String(params.apiType)
: provider?.defaultApiType || '';
form.authMode = params.authMode || selectedAPIConfig.value?.defaultAuthMode || '';
resetModelDiscovery();
modelConfigMode.value = 'manual';
manualModels.value = editModels.value.length
? editModels.value.map((item) => ({ ...item }))
: [buildInitialModel()];
form.verifyModel = params.verifyModel || editModels.value[0]?.id || '';
manualVerifyIndex.value = manualModels.value.findIndex((item) => item.id === form.verifyModel);
form.remark = params.remark || '';
form.syncAgents = false;
return;
}
form.id = 0;
form.appInstallId = 0;
form.name = '';
form.baseURL = '';
form.apiKey = '';
@@ -773,7 +786,7 @@ const handleAPITypeChange = () => {
};
watch([() => form.apiKey, () => form.baseURL], () => {
if (showModelDiscovery.value) {
if (!form.id && showModelDiscovery.value) {
loadDefaultModels();
}
});
@@ -13,7 +13,12 @@
<template v-else-if="visible && instance && !loading && view !== VllmPlaceholder">
<div v-if="instance.engine !== 'ollama'" class="service-card">
<div class="flex flex-wrap gap-3 items-center mb-4">
<strong>{{ engineLabels[instance.engine] }} {{ instance.version }}</strong>
<strong>
{{ engineLabels[instance.engine] }}
{{
instance.engine === 'vllm' ? formatVllmVersion(instance.version) : instance.version
}}
</strong>
<Status :status="instance.status" />
</div>
<div class="service-actions">
@@ -110,7 +115,7 @@ import TensorForm from '../../tensorrt/operate/index.vue';
import VllmPlaceholder from '../../vllm/index.vue';
import ContainerLogs from '@/components/log/container-drawer/index.vue';
import TaskLog from '@/components/log/task/index.vue';
import { readPages, loadVllmAPI, vllmAvailable, type Deployment } from '../../deployment';
import { formatVllmVersion, readPages, loadVllmAPI, vllmAvailable, type Deployment } from '../../deployment';
const emit = defineEmits(['search', 'upgrade']);
const { isMobile, currentNode, isProductPro, isFxplay } = useGlobalStore();
@@ -1,5 +1,5 @@
<template>
<details open class="form-section">
<details ref="sectionRef" open class="form-section">
<summary class="form-section-header">
<span>{{ $t('aiTools.model.gpuAcceleration') }}</span>
<el-switch
@@ -126,6 +126,7 @@ import i18n from '@/lang';
const gpuTypeLabels: Record<string, string> = { nvidia: 'NVIDIA', intel: 'Intel', ascend: 'Ascend', amd: 'AMD' };
const props = withDefaults(defineProps<{ imageType?: string }>(), { imageType: 'nvidia' });
const model = defineModel<AI.ModelGPUConfig>({ required: true });
const sectionRef = ref<HTMLDetailsElement>();
const loading = ref(false);
const gpuWarnings = ref<string[]>([]);
const gpuDevices = ref<AI.ModelGPUDevice[]>();
@@ -200,6 +201,9 @@ const changeMode = () => {
const changeEnabled = () => {
model.value.error = undefined;
if (model.value.enabled && sectionRef.value) {
sectionRef.value.open = true;
}
if (!model.value.enabled) {
model.value.mode = 'all';
model.value.count = undefined;
@@ -26,10 +26,13 @@
</template>
</DrawerPro>
<DrawerPro v-model="editorOpen" :header="editorTitle" size="normal" @close="handleEditorClose">
<el-form ref="formRef" :model="form" label-position="top" :rules="rules" v-loading="saving">
<DialogPro v-model="editorOpen" :title="editorTitle" size="normal" @close="handleEditorClose">
<el-form ref="formRef" :model="form" label-position="top" class="mt-5" :rules="rules" v-loading="saving">
<el-form-item :label="$t('aiTools.model.model')" prop="id">
<el-input v-model="form.id" clearable />
<span v-if="account.provider === 'ollama'" class="input-help">
{{ $t('aiTools.agents.ollamaRegisterOnly') }}
</span>
</el-form-item>
<el-form-item :label="$t('commons.table.name')" prop="name">
<el-input v-model="form.name" />
@@ -39,17 +42,28 @@
<span class="dialog-footer">
<el-button :disabled="saving" @click="editorOpen = false">{{ $t('commons.button.cancel') }}</el-button>
<el-button v-permission :disabled="saving" type="primary" @click="submit">
{{ $t('commons.button.confirm') }}
{{ form.recordId ? $t('commons.button.confirm') : $t('commons.button.add') }}
</el-button>
</span>
</template>
</DrawerPro>
</DialogPro>
<OllamaAdd ref="ollamaAddRef" :account-id="account.id" @log="onPullLog" />
<TaskLog ref="taskLogRef" width="70%" @close="refresh" />
<OpDialog ref="deleteRef" @search="refresh">
<template #content>
<el-checkbox v-if="account.provider === 'ollama'" v-model="deleteFiles" class="mt-4">
{{ $t('aiTools.agents.deleteModelFiles') }}
</el-checkbox>
</template>
</OpDialog>
</template>
<script setup lang="ts">
import { computed, reactive, ref } from 'vue';
import type { FormInstance } from 'element-plus';
import { ElMessageBox } from 'element-plus';
import OllamaAdd from '@/views/ai/model/ollama/add/index.vue';
import TaskLog from '@/components/log/task/index.vue';
import OpDialog from '@/components/del-dialog/index.vue';
import {
createAgentAccountModel,
deleteAgentAccountModel,
@@ -71,11 +85,17 @@ const loading = ref(false);
const saving = ref(false);
const formRef = ref<FormInstance>();
const items = ref<AI.AgentAccountModel[]>([]);
const ollamaAddRef = ref();
const taskLogRef = ref();
const deleteRef = ref();
const deleteFiles = ref(false);
const localOllama = computed(() => account.provider === 'ollama' && account.appInstallId > 0);
const account = reactive({
id: 0,
name: '',
provider: '',
appInstallId: 0,
});
const form = reactive<AI.AgentAccountModel>({
@@ -102,6 +122,7 @@ const buttons = [
label: i18n.global.t('commons.button.edit'),
permission: true,
click: (row: AI.AgentAccountModel) => openEdit(row),
show: () => !localOllama.value,
},
{
label: i18n.global.t('commons.button.delete'),
@@ -137,6 +158,10 @@ const resetForm = () => {
};
const openCreate = () => {
if (localOllama.value) {
ollamaAddRef.value.acceptParams();
return;
}
resetForm();
editorOpen.value = true;
};
@@ -179,23 +204,31 @@ const submit = async () => {
}
};
const onDelete = async (row: AI.AgentAccountModel) => {
await ElMessageBox.confirm(
i18n.global.t('commons.msg.delete', [row.name || row.id]),
i18n.global.t('commons.button.delete'),
{
confirmButtonText: i18n.global.t('commons.button.confirm'),
cancelButtonText: i18n.global.t('commons.button.cancel'),
},
);
await deleteAgentAccountModel({
accountId: account.id,
recordId: row.recordId,
});
const refresh = async () => {
await search();
emit('updated');
};
const onPullLog = (row: { taskID: string }) => {
taskLogRef.value.openWithTaskID(row.taskID);
};
const onDelete = (row: AI.AgentAccountModel) => {
deleteFiles.value = false;
deleteRef.value.acceptParams({
title: i18n.global.t('commons.button.delete'),
names: [row.name || row.id],
msg: i18n.global.t('commons.msg.delete', [row.name || row.id]),
api: () =>
deleteAgentAccountModel({
accountId: account.id,
recordId: row.recordId,
deleteFiles: deleteFiles.value,
}),
params: {},
});
};
const handleEditorClose = () => {
formRef.value?.resetFields();
resetForm();
@@ -207,13 +240,15 @@ const handleClose = () => {
account.id = 0;
account.name = '';
account.provider = '';
account.appInstallId = 0;
items.value = [];
editorOpen.value = false;
handleEditorClose();
};
const openDrawer = async (row: AI.AgentAccountItem) => {
readOnly.value = Boolean(row.appInstallId);
readOnly.value = Boolean(row.appInstallId) && row.provider !== 'ollama';
account.appInstallId = row.appInstallId || 0;
account.id = row.id;
account.name = row.name;
account.provider = row.provider;
@@ -102,7 +102,7 @@ const open = async (row: Deployment) => {
MsgError(i18n.global.t('commons.res.notFound'));
return;
}
const hostMode = item.appVersion.toLowerCase().startsWith('nvidia-gb10-dspark-');
const hostMode = item.appVersion.trim().toLowerCase().startsWith('nvidia-gb10-dspark-');
Object.assign(form, {
id: item.id,
containerName: item.containerName,
@@ -44,6 +44,12 @@ interface VllmAPI {
const vllmModules = import.meta.glob<VllmAPI>('@/xpack/api/modules/vllm.ts');
export const vllmAvailable = Object.keys(vllmModules).length > 0;
export function formatVllmVersion(version: string) {
return String(version || '')
.trim()
.replace(/^(nvidia|intel|ascend)-/i, '');
}
export async function loadVllmAPI() {
return Object.values(vllmModules)[0]();
}
+30 -4
View File
@@ -131,13 +131,25 @@
>
<span class="cell-ellipsis" :title="model.id">{{ model.id }}</span>
<el-button
v-if="index === 1 && row.models.length > 2"
v-if="!row.appInstallId && index === 1 && row.models.length > 2"
size="small"
class="model-more"
@click="poolRef.open(row)"
>
+{{ row.models.length - 2 }}
</el-button>
<el-tooltip
v-if="row.appInstallId && index === 1 && row.models.length > 2"
:content="
row.models
.slice(2)
.map((item) => item.id)
.join(', ')
"
placement="top"
>
<el-tag size="small" class="model-more">+{{ row.models.length - 2 }}</el-tag>
</el-tooltip>
</div>
<span v-if="!row.models?.length">—</span>
</template>
@@ -169,6 +181,7 @@
<fu-table-operations
:buttons="accountButtons"
:label="$t('commons.table.operate')"
align="left"
fixed="right"
:ellipsis="3"
width="220"
@@ -190,7 +203,12 @@
<el-table-column :label="$t('app.version')" min-width="150">
<template #default="{ row }">
<div class="flex items-center gap-2">
<span>{{ row.version || '—' }}</span>
<span>
{{
(row.engine === 'vllm' ? formatVllmVersion(row.version) : row.version) ||
'—'
}}
</span>
<el-button
v-if="row.upgradable && hasPermission('app_manage')"
v-permission
@@ -344,7 +362,14 @@ import ModelPool from './components/model-pool/index.vue';
import TerminalDialog from '@/views/container/container/terminal/index.vue';
import SyncService from './components/sync/index.vue';
import ProviderLabel from '@/components/agent-provider-label/index.vue';
import { loadModelOverview, loadVllmAPI, readPages, vllmAvailable, type Deployment } from './deployment';
import {
formatVllmVersion,
loadModelOverview,
loadVllmAPI,
readPages,
vllmAvailable,
type Deployment,
} from './deployment';
const route = useRoute();
const router = useRouter();
@@ -547,6 +572,7 @@ const accountButtons = [
},
{
label: i18n.global.t('aiTools.agents.modelPool'),
disabled: (row: AI.AgentAccountItem) => row.provider === 'vllm',
click: (row: AI.AgentAccountItem) => poolRef.value.open(row),
},
{
@@ -592,7 +618,7 @@ const operate = async (row: Deployment, operation: 'start' | 'stop' | 'restart')
const taskID = newUUID();
await (await loadVllmAPI()).operateVllm({ id: row.id, operate: operation, taskID });
if (node !== currentNode.value) return;
if (row.version.toLowerCase().startsWith('nvidia-gb10-dspark-')) {
if (row.version.trim().toLowerCase().startsWith('nvidia-gb10-dspark-')) {
await search();
taskRef.value.openWithTaskID(taskID);
return;
@@ -41,8 +41,9 @@ import { Rules } from '@/global/form-rules';
import i18n from '@/lang';
import { ElForm } from 'element-plus';
import { MsgSuccess } from '@/utils/message';
import { createOllamaModel } from '@/api/modules/ai';
import { createAgentAccountModel, createOllamaModel } from '@/api/modules/ai';
import { newUUID } from '@/utils/id';
const props = defineProps<{ accountId?: number }>();
const drawerVisible = ref(false);
const form = reactive({
name: '',
@@ -63,7 +64,14 @@ const onSubmit = async (formEl: FormInstance | undefined) => {
formEl.validate(async (valid) => {
if (!valid) return;
let itemName = form.name.replaceAll('ollama run ', '').replaceAll('ollama pull ', '');
await createOllamaModel(itemName, taskID).then(() => {
const request = props.accountId
? createAgentAccountModel({
accountId: props.accountId,
model: { recordId: 0, id: itemName, name: itemName },
taskID,
})
: createOllamaModel(itemName, taskID);
await request.then(() => {
drawerVisible.value = false;
emit('search');
emit('log', { logFileExist: true, name: itemName, from: 'local', taskID: taskID });
@@ -8,7 +8,7 @@
<AppInstallForm ref="formRef" v-model="form" :loading="loading" sectioned hide-gpu-config>
<template #after-params>
<GPUConfig ref="gpuRef" v-model="form.gpu" />
<details open class="form-section">
<details ref="syncSectionRef" open class="form-section">
<summary class="form-section-header">
<span>{{ $t('aiTools.model.syncModelAccount') }}</span>
<el-switch
@@ -16,6 +16,7 @@
@keydown.stop
v-model="form.syncModelAccount"
:aria-label="$t('aiTools.model.syncModelAccount')"
@change="(enabled) => enabled && syncSectionRef && (syncSectionRef.open = true)"
/>
</summary>
<div v-show="form.syncModelAccount" class="form-section-body">
@@ -64,6 +65,7 @@ const open = ref(false);
const loading = ref(false);
const formRef = ref<InstanceType<typeof AppInstallForm>>();
const accountRef = ref();
const syncSectionRef = ref<HTMLDetailsElement>();
const gpuRef = ref<InstanceType<typeof GPUConfig>>();
const form = ref({
gpu: {
@@ -121,7 +121,7 @@
</div>
</component>
<GPUConfig v-if="showModelGPU" ref="gpuRef" v-model="paramModel.gpu" />
<details v-if="appKey === 'ollama'" open class="form-section">
<details v-if="appKey === 'ollama'" ref="syncSectionRef" open class="form-section">
<summary class="form-section-header">
<span>{{ $t('aiTools.model.syncModelAccount') }}</span>
<el-switch
@@ -129,6 +129,7 @@
@keydown.stop
v-model="paramModel.syncModelAccount"
:aria-label="$t('aiTools.model.syncModelAccount')"
@change="(enabled) => enabled && syncSectionRef && (syncSectionRef.open = true)"
/>
</summary>
<div v-show="paramModel.syncModelAccount" class="form-section-body">
@@ -298,6 +299,7 @@ const appType = ref('');
const appKey = ref('');
const showModelGPU = computed(() => props.modelGPU && appKey.value === 'ollama');
const accountRef = ref<InstanceType<typeof AccountAddress>>();
const syncSectionRef = ref<HTMLDetailsElement>();
const gpuRef = ref<InstanceType<typeof GPUConfig>>();
const appConfigUpdate = ref<App.AppConfigUpdate>({
installID: 0,
+126 -152
View File
@@ -47,7 +47,7 @@
<template #body>
<div class="h-overview">
<el-row>
<el-col :span="6" v-for="item in baseInfo.quickJump" :key="item.name">
<el-col class="overview-item" v-for="item in baseInfo.quickJump" :key="item.name">
<span>{{ $t(item.title, 2) }}</span>
<div class="count">
<QuickDirectories
@@ -86,92 +86,6 @@
/>
</template>
</CardWithHeader>
<CardWithHeader
:header="$t('menu.monitor')"
class="card-interval chart-card"
@mouseenter="refreshOptionsOnHover"
>
<template #header-r>
<el-radio-group
style="float: right; margin-left: 5px"
v-model="chartOption"
@change="changeOption"
>
<el-radio-button value="network">{{ $t('home.network') }}</el-radio-button>
<el-radio-button value="io">{{ $t('home.io') }}</el-radio-button>
</el-radio-group>
<el-select
v-if="chartOption === 'network'"
@change="onLoadBaseInfo(false, 'network')"
v-model="searchInfo.netOption"
class="p-w-200 float-right"
>
<template #prefix>{{ $t('home.networkCard') }}</template>
<el-option
v-for="item in netOptions"
:key="item"
:label="item == 'all' ? $t('commons.table.all') : item"
:value="item"
/>
</el-select>
<el-select
v-if="chartOption === 'io'"
v-model="searchInfo.ioOption"
@change="onLoadBaseInfo(false, 'io')"
class="p-w-200 float-right"
>
<template #prefix>{{ $t('home.disk') }}</template>
<el-option
v-for="item in ioOptions"
:key="item"
:label="item == 'all' ? $t('commons.table.all') : item"
:value="item"
/>
</el-select>
</template>
<template #body>
<div class="monitor-chart-content">
<div class="monitor-tags" :style="monitorTagsStyle" v-if="chartOption === 'network'">
<el-tag>
{{ $t('monitor.up') }}: {{ computeSizeFromKBs(currentChartInfo.netBytesSent) }}
</el-tag>
<el-tag>
{{ $t('monitor.down') }}: {{ computeSizeFromKBs(currentChartInfo.netBytesRecv) }}
</el-tag>
<el-tag>{{ $t('home.totalSend') }}: {{ computeSize(currentInfo.netBytesSent) }}</el-tag>
<el-tag>{{ $t('home.totalRecv') }}: {{ computeSize(currentInfo.netBytesRecv) }}</el-tag>
</div>
<div class="monitor-tags" :style="monitorTagsStyle" v-if="chartOption === 'io'">
<el-tag>{{ $t('monitor.read') }}: {{ currentChartInfo.ioReadBytes }} MB</el-tag>
<el-tag>{{ $t('monitor.write') }}: {{ currentChartInfo.ioWriteBytes }} MB</el-tag>
<el-tag>
{{ $t('home.rwPerSecond') }}: {{ currentChartInfo.ioCount }}
{{ $t('commons.units.time') }}/s
</el-tag>
<el-tag>{{ $t('home.ioDelay') }}: {{ currentChartInfo.ioTime }} ms</el-tag>
</div>
<div v-if="chartOption === 'io'" class="mobile-monitor-chart">
<v-charts
height="383px"
id="ioChart"
type="line"
:option="chartsOption['ioChart']"
:dataZoom="true"
/>
</div>
<div v-if="chartOption === 'network'" class="mobile-monitor-chart">
<v-charts
height="383px"
id="networkChart"
type="line"
:option="chartsOption['networkChart']"
:dataZoom="true"
/>
</div>
</div>
</template>
</CardWithHeader>
</el-col>
<el-col :xs="24" :sm="24" :md="8" :lg="8" :xl="8" class="dashboard-right">
<el-carousel
@@ -438,6 +352,82 @@
</el-col>
</el-row>
<CardWithHeader :header="$t('menu.monitor')" class="card-interval" @mouseenter="refreshOptionsOnHover">
<template #body>
<div class="monitor-grid">
<section class="monitor-panel">
<div class="monitor-panel-header">
<span>{{ $t('home.network') }}</span>
<el-select
@change="onLoadBaseInfo(false, 'network')"
v-model="searchInfo.netOption"
class="monitor-select"
>
<template #prefix>{{ $t('home.networkCard') }}</template>
<el-option
v-for="item in netOptions"
:key="item"
:label="item == 'all' ? $t('commons.table.all') : item"
:value="item"
/>
</el-select>
</div>
<div class="monitor-tags">
<el-tag>
{{ $t('monitor.up') }}: {{ computeSizeFromKBs(currentChartInfo.netBytesSent) }}
</el-tag>
<el-tag>
{{ $t('monitor.down') }}: {{ computeSizeFromKBs(currentChartInfo.netBytesRecv) }}
</el-tag>
<el-tag>{{ $t('home.totalSend') }}: {{ computeSize(currentInfo.netBytesSent) }}</el-tag>
<el-tag>{{ $t('home.totalRecv') }}: {{ computeSize(currentInfo.netBytesRecv) }}</el-tag>
</div>
<v-charts
height="383px"
id="networkChart"
type="line"
:option="chartsOption['networkChart']"
:dataZoom="true"
/>
</section>
<section class="monitor-panel">
<div class="monitor-panel-header">
<span>{{ $t('home.io') }}</span>
<el-select
v-model="searchInfo.ioOption"
@change="onLoadBaseInfo(false, 'io')"
class="monitor-select"
>
<template #prefix>{{ $t('home.disk') }}</template>
<el-option
v-for="item in ioOptions"
:key="item"
:label="item == 'all' ? $t('commons.table.all') : item"
:value="item"
/>
</el-select>
</div>
<div class="monitor-tags">
<el-tag>{{ $t('monitor.read') }}: {{ currentChartInfo.ioReadBytes }} MB</el-tag>
<el-tag>{{ $t('monitor.write') }}: {{ currentChartInfo.ioWriteBytes }} MB</el-tag>
<el-tag>
{{ $t('home.rwPerSecond') }}: {{ currentChartInfo.ioCount }}
{{ $t('commons.units.time') }}/s
</el-tag>
<el-tag>{{ $t('home.ioDelay') }}: {{ currentChartInfo.ioTime }} ms</el-tag>
</div>
<v-charts
height="383px"
id="ioChart"
type="line"
:option="chartsOption['ioChart']"
:dataZoom="true"
/>
</section>
</div>
</template>
</CardWithHeader>
<QuickJump @search="onLoadBaseInfo(false, 'all')" ref="quickJumpRef" />
<DialogPro v-model="welcomeOpen" size="w-70" id="welcomeDialog">
@@ -501,12 +491,8 @@ const DASHBOARD_CACHE_TTL = {
netOptions: 60 * 60 * 1000,
ioOptions: 60 * 60 * 1000,
};
const monitorChartGrid = { left: 65, right: 65, bottom: '20%' };
const monitorChartGrid = { left: 55, right: 25, bottom: '20%' };
const monitorChartLegend = { top: 0, bottom: 'auto' };
const monitorTagsStyle = {
left: `${monitorChartGrid.left}px`,
right: `${monitorChartGrid.right}px`,
};
const monitorChartEmptyLength = 20;
const loadMonitorEmptyData = () => Array.from({ length: monitorChartEmptyLength }, () => null);
const loadMonitorEmptyTime = () => Array.from({ length: monitorChartEmptyLength }, () => '');
@@ -553,11 +539,9 @@ const isSafety = ref();
const welcomeOpen = ref();
const shadowContainer = ref();
const chartOption = ref('network');
const statusOption = ref(localStorage.getItem('dashboard_status_view') === 'gpu' ? 'gpu' : 'system');
let timer: ReturnType<typeof setInterval> | null = null;
let initialSampleTimer: ReturnType<typeof setTimeout> | null = null;
let isInit = ref<boolean>(true);
let isStatusInit = ref<boolean>(true);
let isActive = ref(true);
let isCurrentActive = ref(true);
@@ -587,6 +571,7 @@ const quickJumpPermissionMap = Object.fromEntries(
['Cronjob', 'cronjob_view'],
['AppInstalled', 'app_view'],
['File', 'host_file_view'],
['ModelServices', 'ai_model_view'],
].map(([name, permission]) => [name, useCan(permission)]),
) as Record<string, ReturnType<typeof useCan>>;
@@ -723,11 +708,6 @@ const updateCurrentInfo = (data: Dashboard.CurrentInfo) => {
};
};
const changeOption = async () => {
isInit.value = true;
loadData();
};
const applyDefaultNetOption = () => {
if (!netOptions.value || netOptions.value.length === 0) return;
const defaultNet = defaultNetwork.value || netOptions.value[0];
@@ -1032,12 +1012,9 @@ const saveMemo = async () => {
}
};
const loadData = async () => {
if (chartOption.value === 'io') {
chartsOption.value['ioChart'] = loadIOChartOption();
} else {
chartsOption.value['networkChart'] = loadNetworkChartOption();
}
const loadData = () => {
chartsOption.value['ioChart'] = loadIOChartOption();
chartsOption.value['networkChart'] = loadNetworkChartOption();
};
const hideEntrance = () => {
@@ -1205,15 +1182,6 @@ onBeforeUnmount(() => {
font-size: 12px;
}
.monitor-chart-content {
position: relative;
margin-top: 60px;
}
.mobile-monitor-chart {
margin-top: 40px;
}
@media only screen and (max-width: 767px) {
.dashboard-entrance-alert {
padding-right: 40px;
@@ -1255,6 +1223,11 @@ onBeforeUnmount(() => {
.h-overview {
text-align: center;
.overview-item {
flex: 1;
min-width: 0;
}
span:first-child {
font-size: 14px;
color: var(--el-text-color-regular);
@@ -1355,49 +1328,54 @@ onBeforeUnmount(() => {
border-top: var(--panel-border);
}
.monitor-tags {
position: absolute;
top: -10px;
.monitor-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 24px;
}
.monitor-panel {
min-width: 0;
}
.monitor-panel-header {
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
gap: 10px;
gap: 8px;
margin-bottom: 12px;
font-weight: 500;
}
@media only screen and (max-width: 1024px) {
.monitor-chart-content {
margin-top: 20px;
}
.monitor-select {
width: 200px;
max-width: 100%;
}
.monitor-chart-content .monitor-tags {
position: static;
display: grid;
.monitor-tags {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 8px;
margin-bottom: 16px;
:deep(.el-tag) {
width: 100%;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 8px;
margin-bottom: 12px;
:deep(.el-tag) {
width: 100%;
max-width: 100%;
height: auto;
min-height: 24px;
justify-content: center;
padding-block: 3px;
line-height: 1.3;
text-align: center;
white-space: normal;
overflow-wrap: anywhere;
}
}
.mobile-monitor-chart {
margin-top: 0;
max-width: 100%;
height: auto;
min-height: 24px;
justify-content: center;
padding-block: 3px;
line-height: 1.3;
text-align: center;
white-space: normal;
overflow-wrap: anywhere;
}
}
@media only screen and (min-width: 768px) and (max-width: 1024px) {
.monitor-chart-content .monitor-tags {
grid-template-columns: repeat(4, minmax(0, 1fr));
@media only screen and (max-width: 991px) {
.monitor-grid {
grid-template-columns: minmax(0, 1fr);
}
}
@@ -1420,10 +1398,6 @@ onBeforeUnmount(() => {
}
}
.chart-card {
min-height: 383px;
}
.memo-container {
height: 270px;
}
+1 -1
View File
@@ -59,7 +59,7 @@ const onChangeShow = async () => {
MsgInfo(i18n.global.t('home.minQuickJump'));
return;
}
if (i > 4) {
if (i > 5) {
MsgInfo(i18n.global.t('home.maxQuickJump'));
return;
}
+1 -1
View File
@@ -16,7 +16,7 @@
</el-icon>
</el-button>
<el-button link type="primary" size="small" @click="$emit('open')">
{{ $t('aiTools.gpu.gpu') }}
{{ $t('aiTools.gpu.viewHistory') }}
<el-icon><ArrowRight /></el-icon>
</el-button>
</div>
+6 -2
View File
@@ -318,7 +318,11 @@
<span class="input-help">{{ computeSize(item.used) }} / {{ computeSize(item.total) }}</span>
</el-col>
</template>
<GPUStatus v-if="view === 'gpu'" :current="currentInfo" @open="routerToName('GPU')" />
<GPUStatus
v-if="view === 'gpu'"
:current="currentInfo"
@open="routerToNameWithQuery('GPUHistory', { uncached: 'true' })"
/>
<el-col
:xs="6"
:sm="6"
@@ -348,7 +352,7 @@ import i18n from '@/lang';
import { nextTick, onBeforeUnmount, ref, watch } from 'vue';
import GPUStatus from './gpu.vue';
import { useMediaQuery } from '@vueuse/core';
import { routerToFileWithPath, routerToName } from '@/utils/router';
import { routerToFileWithPath, routerToNameWithQuery } from '@/utils/router';
import { stopProcess } from '@/api/modules/process';
import { loadTopCPU, loadTopMem } from '@/api/modules/dashboard';
import { MsgSuccess } from '@/utils/message';