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