mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-10-26 08:00:12 +00:00
fix: align AI menu order and labels (#14192)
This commit is contained in:
@@ -114,6 +114,18 @@ func (b *BaseApi) SearchOllamaModel(c *gin.Context) {
|
||||
})
|
||||
}
|
||||
|
||||
func (b *BaseApi) SyncOllamaModelAccount(c *gin.Context) {
|
||||
var req dto.OllamaModelAccountSync
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
if err := aiToolService.SyncAccount(req); err != nil {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags AI
|
||||
// @Summary Page Ollama models
|
||||
// @Accept json
|
||||
|
||||
@@ -24,6 +24,12 @@ type OllamaModelName struct {
|
||||
TaskID string `json:"taskID"`
|
||||
}
|
||||
|
||||
type OllamaModelAccountSync struct {
|
||||
ID uint `json:"id" validate:"required"`
|
||||
ModelAccountBaseURLType string `json:"modelAccountBaseURLType" validate:"required,oneof=container localhost systemIP custom"`
|
||||
ModelAccountBaseURL string `json:"modelAccountBaseURL"`
|
||||
}
|
||||
|
||||
type OllamaBindDomain struct {
|
||||
Domain string `json:"domain" validate:"required"`
|
||||
AppInstallID uint `json:"appInstallID" validate:"required"`
|
||||
|
||||
@@ -23,6 +23,7 @@ type AppInstallCreate struct {
|
||||
Services map[string]string `json:"services"`
|
||||
TaskID string `json:"taskID"`
|
||||
|
||||
SyncModelAccount *bool `json:"syncModelAccount"`
|
||||
ModelAccountBaseURLType string `json:"modelAccountBaseURLType" validate:"omitempty,oneof=container localhost systemIP custom"`
|
||||
ModelAccountBaseURL string `json:"modelAccountBaseURL"`
|
||||
|
||||
@@ -127,9 +128,12 @@ type AppInstallDelete struct {
|
||||
}
|
||||
|
||||
type AppInstalledUpdate struct {
|
||||
InstallId uint `json:"installId" validate:"required"`
|
||||
Params map[string]interface{} `json:"params" validate:"required"`
|
||||
TaskID string `json:"-"`
|
||||
SyncModelAccount *bool `json:"syncModelAccount"`
|
||||
ModelAccountBaseURLType string `json:"modelAccountBaseURLType" validate:"omitempty,oneof=container localhost systemIP custom"`
|
||||
ModelAccountBaseURL string `json:"modelAccountBaseURL"`
|
||||
InstallId uint `json:"installId" validate:"required"`
|
||||
Params map[string]interface{} `json:"params" validate:"required"`
|
||||
TaskID string `json:"-"`
|
||||
AppContainerConfig
|
||||
}
|
||||
|
||||
|
||||
@@ -50,19 +50,21 @@ type TagDTO struct {
|
||||
}
|
||||
|
||||
type AppInstalledCheck struct {
|
||||
IsExist bool `json:"isExist"`
|
||||
Name string `json:"name"`
|
||||
App string `json:"app"`
|
||||
Version string `json:"version"`
|
||||
Status string `json:"status"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
LastBackupAt string `json:"lastBackupAt"`
|
||||
AppInstallID uint `json:"appInstallId"`
|
||||
ContainerName string `json:"containerName"`
|
||||
InstallPath string `json:"installPath"`
|
||||
HttpPort int `json:"httpPort"`
|
||||
HttpsPort int `json:"httpsPort"`
|
||||
WebsiteDir string `json:"websiteDir"`
|
||||
ModelAccountBaseURLType string `json:"modelAccountBaseURLType"`
|
||||
ModelAccountBaseURL string `json:"modelAccountBaseURL"`
|
||||
IsExist bool `json:"isExist"`
|
||||
Name string `json:"name"`
|
||||
App string `json:"app"`
|
||||
Version string `json:"version"`
|
||||
Status string `json:"status"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
LastBackupAt string `json:"lastBackupAt"`
|
||||
AppInstallID uint `json:"appInstallId"`
|
||||
ContainerName string `json:"containerName"`
|
||||
InstallPath string `json:"installPath"`
|
||||
HttpPort int `json:"httpPort"`
|
||||
HttpsPort int `json:"httpsPort"`
|
||||
WebsiteDir string `json:"websiteDir"`
|
||||
}
|
||||
|
||||
type AppDetailDTO struct {
|
||||
@@ -184,7 +186,13 @@ type AppParam struct {
|
||||
}
|
||||
|
||||
type AppConfig struct {
|
||||
Params []AppParam `json:"params"`
|
||||
RawCompose string `json:"rawCompose"`
|
||||
AppKey string `json:"appKey"`
|
||||
Name string `json:"name"`
|
||||
HttpPort int `json:"httpPort"`
|
||||
SyncModelAccount bool `json:"syncModelAccount"`
|
||||
ModelAccountBaseURLType string `json:"modelAccountBaseURLType"`
|
||||
ModelAccountBaseURL string `json:"modelAccountBaseURL"`
|
||||
Params []AppParam `json:"params"`
|
||||
RawCompose string `json:"rawCompose"`
|
||||
request.AppContainerConfig
|
||||
}
|
||||
|
||||
@@ -28,6 +28,10 @@ type AppInstall struct {
|
||||
Favorite bool `json:"favorite"`
|
||||
SortOrder int `json:"sortOrder" gorm:"default:0"`
|
||||
|
||||
SyncModelAccount bool `json:"syncModelAccount"`
|
||||
ModelAccountBaseURLType string `json:"modelAccountBaseURLType"`
|
||||
ModelAccountBaseURL string `json:"modelAccountBaseURL"`
|
||||
|
||||
App App `json:"app" gorm:"-:migration"`
|
||||
}
|
||||
|
||||
|
||||
@@ -117,6 +117,13 @@ func SyncOllamaModelAccount(createIfMissing bool) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !install.SyncModelAccount {
|
||||
return nil
|
||||
}
|
||||
return syncOllamaModelAccount(install, fmt.Sprintf("http://%s:11434/v1", install.ContainerName), false, createIfMissing)
|
||||
}
|
||||
|
||||
func syncOllamaModelAccount(install model.AppInstall, baseURL string, updateBaseURL, createIfMissing bool) error {
|
||||
models, err := aiRepo.List(repo.WithByStatus(constant.StatusSuccess), repo.WithOrderAsc("name"))
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -125,10 +132,34 @@ func SyncOllamaModelAccount(createIfMissing bool) error {
|
||||
for _, item := range models {
|
||||
items = append(items, dto.AgentAccountModel{ID: item.Name, Name: item.Name})
|
||||
}
|
||||
_, err = SyncLocalModelAccount(install, 0, fmt.Sprintf("http://%s:11434/v1", install.ContainerName), items, false, createIfMissing)
|
||||
_, err = SyncLocalModelAccount(install, 0, baseURL, items, updateBaseURL, createIfMissing)
|
||||
return err
|
||||
}
|
||||
|
||||
func loadOllamaModelAccountAddress(install model.AppInstall) (string, string, error) {
|
||||
addressType := install.ModelAccountBaseURLType
|
||||
if addressType == "" {
|
||||
addressType = "container"
|
||||
}
|
||||
account, err := agentAccountRepo.GetFirst(repo.WithByAppInstallID(install.ID), repo.WithByProvider("ollama"))
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return addressType, install.ModelAccountBaseURL, nil
|
||||
}
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
if addressType != "custom" {
|
||||
expectedURL, err := ResolveLocalModelAccountBaseURL(addressType, "", install.ContainerName, "11434", install.HttpPort)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
if strings.TrimRight(account.BaseURL, "/") != expectedURL {
|
||||
addressType = "custom"
|
||||
}
|
||||
}
|
||||
return addressType, account.BaseURL, nil
|
||||
}
|
||||
|
||||
func CleanupLocalModelAccount(installID uint) {
|
||||
if installID == 0 {
|
||||
return
|
||||
|
||||
@@ -35,6 +35,7 @@ type IAIToolService interface {
|
||||
Recreate(req dto.OllamaModelName) error
|
||||
Delete(req dto.ForceDelete) error
|
||||
Sync() ([]dto.OllamaModelDropList, error)
|
||||
SyncAccount(req dto.OllamaModelAccountSync) error
|
||||
LoadDetail(name string) (string, error)
|
||||
BindDomain(req dto.OllamaBindDomain) error
|
||||
GetBindDomain(req dto.OllamaBindDomainReq) (*dto.OllamaBindDomainRes, error)
|
||||
@@ -259,6 +260,27 @@ func (u *AIToolService) Sync() ([]dto.OllamaModelDropList, error) {
|
||||
return dropList, SyncOllamaModelAccount(false)
|
||||
}
|
||||
|
||||
func (u *AIToolService) SyncAccount(req dto.OllamaModelAccountSync) error {
|
||||
install, err := appInstallRepo.GetFirst(repo.WithByID(req.ID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if install.App.Key != "ollama" {
|
||||
return buserr.New("ErrInvalidParams")
|
||||
}
|
||||
baseURL, err := ResolveLocalModelAccountBaseURL(req.ModelAccountBaseURLType, req.ModelAccountBaseURL, install.ContainerName, "11434", install.HttpPort)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := syncOllamaModelAccount(install, baseURL, true, true); err != nil {
|
||||
return err
|
||||
}
|
||||
return appInstallRepo.BatchUpdateBy(map[string]interface{}{
|
||||
"model_account_base_url_type": req.ModelAccountBaseURLType,
|
||||
"model_account_base_url": baseURL,
|
||||
}, repo.WithByID(install.ID))
|
||||
}
|
||||
|
||||
func (u *AIToolService) BindDomain(req dto.OllamaBindDomain) error {
|
||||
nginxInstall, _ := getAppInstallByKey(constant.AppOpenresty)
|
||||
if nginxInstall.ID == 0 {
|
||||
|
||||
@@ -501,6 +501,14 @@ func (a AppService) installWithHooks(req request.AppInstallCreate, executeScript
|
||||
|
||||
modelAccountBaseURL := ""
|
||||
if app.Key == "ollama" {
|
||||
appInstall.SyncModelAccount = req.SyncModelAccount == nil || *req.SyncModelAccount
|
||||
appInstall.ModelAccountBaseURLType = req.ModelAccountBaseURLType
|
||||
if appInstall.ModelAccountBaseURLType == "" {
|
||||
appInstall.ModelAccountBaseURLType = "container"
|
||||
}
|
||||
appInstall.ModelAccountBaseURL = req.ModelAccountBaseURL
|
||||
}
|
||||
if app.Key == "ollama" && appInstall.SyncModelAccount {
|
||||
modelAccountBaseURL, err = ResolveLocalModelAccountBaseURL(req.ModelAccountBaseURLType, req.ModelAccountBaseURL, containerName, "11434", httpPort)
|
||||
if err != nil {
|
||||
return
|
||||
@@ -617,7 +625,7 @@ func (a AppService) installWithHooks(req request.AppInstallCreate, executeScript
|
||||
return err
|
||||
}
|
||||
updateToolApp(appInstall)
|
||||
if app.Key == "ollama" {
|
||||
if app.Key == "ollama" && appInstall.SyncModelAccount {
|
||||
_, err := SyncLocalModelAccount(*appInstall, 0, modelAccountBaseURL, nil, false, true)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -159,6 +159,14 @@ func (a *AppInstallService) CheckExist(req request.AppInstalledInfo) (*response.
|
||||
}
|
||||
|
||||
res.ContainerName = appInstall.ContainerName
|
||||
res.ModelAccountBaseURLType = appInstall.ModelAccountBaseURLType
|
||||
res.ModelAccountBaseURL = appInstall.ModelAccountBaseURL
|
||||
if app.Key == "ollama" {
|
||||
res.ModelAccountBaseURLType, res.ModelAccountBaseURL, err = loadOllamaModelAccountAddress(appInstall)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
res.Name = appInstall.Name
|
||||
res.Version = appInstall.Version
|
||||
res.CreatedAt = appInstall.CreatedAt
|
||||
@@ -438,6 +446,32 @@ func (a *AppInstallService) Update(req request.AppInstalledUpdate) error {
|
||||
}
|
||||
}
|
||||
|
||||
modelAccountBaseURL := ""
|
||||
if installed.App.Key == "ollama" {
|
||||
if req.SyncModelAccount != nil {
|
||||
installed.SyncModelAccount = *req.SyncModelAccount
|
||||
}
|
||||
if req.ModelAccountBaseURLType != "" {
|
||||
installed.ModelAccountBaseURLType = req.ModelAccountBaseURLType
|
||||
installed.ModelAccountBaseURL = req.ModelAccountBaseURL
|
||||
} else if installed.SyncModelAccount {
|
||||
installed.ModelAccountBaseURLType, installed.ModelAccountBaseURL, err = loadOllamaModelAccountAddress(oldInstalled)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if installed.SyncModelAccount {
|
||||
containerName := installed.ContainerName
|
||||
if req.Advanced && req.ContainerName != "" {
|
||||
containerName = req.ContainerName
|
||||
}
|
||||
modelAccountBaseURL, err = ResolveLocalModelAccountBaseURL(installed.ModelAccountBaseURLType, installed.ModelAccountBaseURL, containerName, "11434", installed.HttpPort)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
backupDockerCompose := installed.DockerCompose
|
||||
if req.Advanced {
|
||||
composeMap := make(map[string]interface{})
|
||||
@@ -563,6 +597,9 @@ func (a *AppInstallService) Update(req request.AppInstalledUpdate) error {
|
||||
if proxyChanged && website.ID != 0 && currentProxyErr != nil {
|
||||
global.LOG.Error(buserr.WithErr("ErrUpdateBuWebsite", currentProxyErr).Error())
|
||||
}
|
||||
if installed.App.Key == "ollama" && installed.SyncModelAccount {
|
||||
return syncOllamaModelAccount(installed, modelAccountBaseURL, true, true)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -934,6 +971,18 @@ func (a *AppInstallService) GetParams(id uint) (*response.AppConfig, error) {
|
||||
config := getAppCommonConfig(envs)
|
||||
config.DockerCompose = install.DockerCompose
|
||||
res.Params = params
|
||||
res.AppKey = install.App.Key
|
||||
res.Name = install.Name
|
||||
res.HttpPort = install.HttpPort
|
||||
res.SyncModelAccount = install.SyncModelAccount
|
||||
res.ModelAccountBaseURLType = install.ModelAccountBaseURLType
|
||||
res.ModelAccountBaseURL = install.ModelAccountBaseURL
|
||||
if install.App.Key == "ollama" {
|
||||
res.ModelAccountBaseURLType, res.ModelAccountBaseURL, err = loadOllamaModelAccountAddress(install)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if config.ContainerName == "" {
|
||||
config.ContainerName = install.ContainerName
|
||||
}
|
||||
|
||||
@@ -118,6 +118,7 @@ func agentDBMigrations() []*gormigrate.Migration {
|
||||
migrations.AddNativeAgentInstalls,
|
||||
migrations.AddQuickDirectory,
|
||||
migrations.AddLocalModelAccountLink,
|
||||
migrations.AddOllamaModelAccountSync,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1899,9 +1899,43 @@ var AddQuickDirectory = &gormigrate.Migration{
|
||||
var AddLocalModelAccountLink = &gormigrate.Migration{
|
||||
ID: "20261023-add-local-model-account-link",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
if err := tx.AutoMigrate(&model.AgentAccount{}); err != nil {
|
||||
return tx.AutoMigrate(&model.AgentAccount{})
|
||||
},
|
||||
}
|
||||
|
||||
var AddOllamaModelAccountSync = &gormigrate.Migration{
|
||||
ID: "20261026-add-ollama-model-account-sync",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
if err := tx.AutoMigrate(&model.AppInstall{}); err != nil {
|
||||
return err
|
||||
}
|
||||
return service.SyncOllamaModelAccount(true)
|
||||
var accounts []model.AgentAccount
|
||||
if err := tx.Where("provider = ? AND app_install_id > 0", "ollama").Find(&accounts).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
for _, account := range accounts {
|
||||
var install model.AppInstall
|
||||
if err := tx.Preload("App").First(&install, account.AppInstallID).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
continue
|
||||
}
|
||||
return err
|
||||
}
|
||||
if install.App.Key != "ollama" {
|
||||
continue
|
||||
}
|
||||
addressType := "custom"
|
||||
if strings.TrimRight(account.BaseURL, "/") == fmt.Sprintf("http://%s:11434/v1", install.ContainerName) {
|
||||
addressType = "container"
|
||||
}
|
||||
if err := tx.Model(&model.AppInstall{}).Where("id = ?", account.AppInstallID).Updates(map[string]interface{}{
|
||||
"sync_model_account": true,
|
||||
"model_account_base_url_type": addressType,
|
||||
"model_account_base_url": account.BaseURL,
|
||||
}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ func (a *AIToolsRouter) InitRouter(Router *gin.RouterGroup) {
|
||||
aiToolsRouter.POST("/ollama/model/recreate", baseApi.RecreateOllamaModel)
|
||||
aiToolsRouter.POST("/ollama/model/search", baseApi.SearchOllamaModel)
|
||||
aiToolsRouter.POST("/ollama/model/sync", baseApi.SyncOllamaModel)
|
||||
aiToolsRouter.POST("/ollama/account/sync", baseApi.SyncOllamaModelAccount)
|
||||
aiToolsRouter.POST("/ollama/model/load", baseApi.LoadOllamaModelDetail)
|
||||
aiToolsRouter.POST("/ollama/model/del", baseApi.DeleteOllamaModel)
|
||||
aiToolsRouter.POST("/domain/bind", baseApi.BindDomain)
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import { ReqPage } from '.';
|
||||
|
||||
export namespace AI {
|
||||
export interface ModelAccountSync {
|
||||
id: number;
|
||||
modelAccountBaseURLType: 'container' | 'localhost' | 'systemIP' | 'custom';
|
||||
modelAccountBaseURL: string;
|
||||
}
|
||||
export type AgentType = 'openclaw' | 'copaw' | 'hermes-agent' | 'deepseek-harness' | 'octop';
|
||||
|
||||
export interface OllamaModelInfo {
|
||||
|
||||
@@ -136,6 +136,7 @@ export namespace App {
|
||||
}
|
||||
|
||||
export interface AppInstall {
|
||||
syncModelAccount?: boolean;
|
||||
modelAccountBaseURLType?: string;
|
||||
modelAccountBaseURL?: string;
|
||||
appDetailId: number;
|
||||
@@ -229,6 +230,8 @@ export namespace App {
|
||||
name: string;
|
||||
version: string;
|
||||
isExist: boolean;
|
||||
modelAccountBaseURLType: string;
|
||||
modelAccountBaseURL: string;
|
||||
app: string;
|
||||
status: string;
|
||||
createdAt: string;
|
||||
@@ -303,6 +306,12 @@ export namespace App {
|
||||
}
|
||||
|
||||
export interface AppConfig {
|
||||
appKey: string;
|
||||
name: string;
|
||||
httpPort: number;
|
||||
syncModelAccount: boolean;
|
||||
modelAccountBaseURLType: string;
|
||||
modelAccountBaseURL: string;
|
||||
params: InstallParams[];
|
||||
rawCompose?: string;
|
||||
cpuQuota: number;
|
||||
|
||||
@@ -22,6 +22,10 @@ export const loadOllamaModel = (name: string) => {
|
||||
export const syncOllamaModel = () => {
|
||||
return http.post<Array<AI.OllamaModelDropInfo>>(`/ai/ollama/model/sync`);
|
||||
};
|
||||
|
||||
export const syncOllamaModelAccount = (req: AI.ModelAccountSync) => {
|
||||
return http.post('/ai/ollama/account/sync', req);
|
||||
};
|
||||
export const closeOllamaModel = (name: string, currentNode?: string) => {
|
||||
return http.post(
|
||||
`/ai/ollama/close`,
|
||||
|
||||
@@ -1063,6 +1063,7 @@ const message = {
|
||||
vllmCommandPortHelper:
|
||||
'The startup command must use port {0}; otherwise, the service will be inaccessible.',
|
||||
ascendVisibleDevices: 'Ascend visible devices (ASCEND_RT_VISIBLE_DEVICES)',
|
||||
syncModelAccount: 'Sync to model account',
|
||||
modelAccountAddressHelper:
|
||||
'Container address is for agent containers; 127.0.0.1, system IP, or custom address is for AI Gateway or external services.',
|
||||
containerAddress: 'Container address',
|
||||
|
||||
@@ -1066,6 +1066,7 @@ const message = {
|
||||
vllmCommandPortHelper:
|
||||
'El comando de inicio debe usar el puerto {0}; de lo contrario, no se podrá acceder al servicio.',
|
||||
ascendVisibleDevices: 'Dispositivos Ascend visibles (ASCEND_RT_VISIBLE_DEVICES)',
|
||||
syncModelAccount: 'Sincronizar con cuenta de modelo',
|
||||
modelAccountAddressHelper:
|
||||
'La dirección del contenedor es para contenedores de agentes; 127.0.0.1, IP del sistema o dirección personalizada es para AI Gateway o servicios externos.',
|
||||
containerAddress: 'Dirección del contenedor',
|
||||
|
||||
@@ -1048,6 +1048,7 @@ const message = {
|
||||
vllmCommandPortHelper:
|
||||
'فرمان راهاندازی باید از پورت {0} استفاده کند؛ در غیر این صورت سرویس قابل دسترسی نخواهد بود.',
|
||||
ascendVisibleDevices: 'دستگاههای قابل مشاهده Ascend (ASCEND_RT_VISIBLE_DEVICES)',
|
||||
syncModelAccount: 'همگامسازی با حساب مدل',
|
||||
modelAccountAddressHelper:
|
||||
'آدرس کانتینر برای کانتینرهای عامل است؛ 127.0.0.1، IP سیستم یا آدرس سفارشی برای دروازه AI یا خدمات خارجی است.',
|
||||
containerAddress: 'آدرس کانتینر',
|
||||
|
||||
@@ -1052,6 +1052,7 @@ const message = {
|
||||
vllmCommandPortHelper:
|
||||
'起動コマンドではポート {0} を使用する必要があります。使用しない場合、サービスにアクセスできません。',
|
||||
ascendVisibleDevices: 'Ascend 可視デバイス(ASCEND_RT_VISIBLE_DEVICES)',
|
||||
syncModelAccount: 'モデルアカウントに同期',
|
||||
modelAccountAddressHelper:
|
||||
'コンテナアドレスはエージェントコンテナ向けです。127.0.0.1、システム IP、カスタムアドレスは AI Gateway または外部サービス向けです。',
|
||||
containerAddress: 'コンテナアドレス',
|
||||
|
||||
@@ -1041,6 +1041,7 @@ const message = {
|
||||
vllmProfileVisionExp: 'DeepSeek V4 Flash Vision Exp',
|
||||
vllmCommandPortHelper: '시작 명령은 {0} 포트를 사용해야 하며, 그렇지 않으면 서비스에 접근할 수 없습니다.',
|
||||
ascendVisibleDevices: 'Ascend 표시 장치 (ASCEND_RT_VISIBLE_DEVICES)',
|
||||
syncModelAccount: '모델 계정에 동기화',
|
||||
modelAccountAddressHelper:
|
||||
'컨테이너 주소는 에이전트 컨테이너 호출에 적합합니다. 127.0.0.1, 시스템 IP 또는 사용자 지정 주소는 AI Gateway 또는 외부 서비스 호출에 적합합니다.',
|
||||
containerAddress: '컨테이너 주소',
|
||||
|
||||
@@ -1038,6 +1038,7 @@ const message = {
|
||||
vllmProfileVisionExp: 'DeepSeek V4 Flash Vision Exp',
|
||||
vllmCommandPortHelper: 'ຄຳສັ່ງເລີ່ມຕົ້ນຕ້ອງໃຊ້ພອດ {0}; ບໍ່ດັ່ງນັ້ນຈະບໍ່ສາມາດເຂົ້າເຖິງບໍລິການໄດ້.',
|
||||
ascendVisibleDevices: 'ອຸປະກອນ Ascend ທີ່ເຫັນໄດ້ (ASCEND_RT_VISIBLE_DEVICES)',
|
||||
syncModelAccount: 'ຊິ້ງຄ໌ໄປຍັງບັນຊີໂມເດວ',
|
||||
modelAccountAddressHelper:
|
||||
'ທີ່ຢູ່ຄອນເທນເນີແມ່ນສຳລັບຄອນເທນເນີຕົວແທນ; 127.0.0.1, IP ລະບົບ, ຫຼື ທີ່ຢູ່ກຳນົດເອງແມ່ນສຳລັບ AI Gateway ຫຼື ບໍລິການພາຍນອກ.',
|
||||
containerAddress: 'ທີ່ຢູ່ຄອນເທນເນີ',
|
||||
|
||||
@@ -1075,6 +1075,7 @@ const message = {
|
||||
vllmCommandPortHelper:
|
||||
'Perintah permulaan mesti menggunakan port {0}; jika tidak, perkhidmatan tidak dapat diakses.',
|
||||
ascendVisibleDevices: 'Peranti Ascend boleh dilihat (ASCEND_RT_VISIBLE_DEVICES)',
|
||||
syncModelAccount: 'Segerakkan ke akaun model',
|
||||
modelAccountAddressHelper:
|
||||
'Alamat kontena sesuai untuk kontena agen; 127.0.0.1, IP sistem, atau alamat tersuai sesuai untuk AI Gateway atau perkhidmatan luaran.',
|
||||
containerAddress: 'Alamat kontena',
|
||||
|
||||
@@ -1070,6 +1070,7 @@ const message = {
|
||||
vllmCommandPortHelper:
|
||||
'O comando de inicialização deve usar a porta {0}; caso contrário, o serviço ficará inacessível.',
|
||||
ascendVisibleDevices: 'Dispositivos Ascend visíveis (ASCEND_RT_VISIBLE_DEVICES)',
|
||||
syncModelAccount: 'Sincronizar com conta de modelo',
|
||||
modelAccountAddressHelper:
|
||||
'Use o endereço do contêiner para agentes; 127.0.0.1, IP do sistema ou endereço personalizado para AI Gateway ou serviços externos.',
|
||||
containerAddress: 'Endereço do contêiner',
|
||||
|
||||
@@ -1063,6 +1063,7 @@ const message = {
|
||||
vllmProfileVisionExp: 'DeepSeek V4 Flash Vision Exp',
|
||||
vllmCommandPortHelper: 'Команда запуска должна использовать порт {0}, иначе сервис будет недоступен.',
|
||||
ascendVisibleDevices: 'Видимые устройства Ascend (ASCEND_RT_VISIBLE_DEVICES)',
|
||||
syncModelAccount: 'Синхронизировать с аккаунтом модели',
|
||||
modelAccountAddressHelper:
|
||||
'Адрес контейнера подходит для контейнеров агентов; 127.0.0.1, системный IP или пользовательский адрес подходит для AI Gateway или внешних сервисов.',
|
||||
containerAddress: 'Адрес контейнера',
|
||||
|
||||
@@ -1074,6 +1074,7 @@ const message = {
|
||||
vllmCommandPortHelper:
|
||||
'Başlatma komutu {0} numaralı bağlantı noktasını kullanmalıdır; aksi halde hizmete erişilemez.',
|
||||
ascendVisibleDevices: 'Görünür Ascend cihazları (ASCEND_RT_VISIBLE_DEVICES)',
|
||||
syncModelAccount: 'Model hesabına senkronize et',
|
||||
modelAccountAddressHelper:
|
||||
'Konteyner adresi ajan konteynerleri içindir; 127.0.0.1, sistem IP veya özel adres AI Gateway ya da harici servisler içindir.',
|
||||
containerAddress: 'Konteyner adresi',
|
||||
|
||||
@@ -798,7 +798,7 @@ const message = {
|
||||
contextWindow: '上下文視窗',
|
||||
useModelDefault: '使用模型預設值',
|
||||
reasoning: '推理模型',
|
||||
verified: '驗證狀態',
|
||||
verified: '驗證成功',
|
||||
verifySkipped: '略過驗證',
|
||||
skillsTab: 'Skill',
|
||||
pluginsTab: '外掛',
|
||||
@@ -1004,6 +1004,7 @@ const message = {
|
||||
vllmProfileVisionExp: 'DeepSeek V4 Flash Vision Exp',
|
||||
vllmCommandPortHelper: '啟動命令必須使用 {0} 連接埠,否則服務將無法存取。',
|
||||
ascendVisibleDevices: 'Ascend 可見裝置(ASCEND_RT_VISIBLE_DEVICES)',
|
||||
syncModelAccount: '同步到模型帳號',
|
||||
modelAccountAddressHelper:
|
||||
'容器地址適合智能體容器呼叫;127.0.0.1、本機 IP 或自訂地址適合 AI 閘道或外部服務呼叫。',
|
||||
containerAddress: '容器地址',
|
||||
|
||||
@@ -815,7 +815,7 @@ const message = {
|
||||
contextWindow: '上下文窗口',
|
||||
useModelDefault: '使用模型默认值',
|
||||
reasoning: '推理模型',
|
||||
verified: '验证状态',
|
||||
verified: '验证成功',
|
||||
verifySkipped: '不验证',
|
||||
skillsTab: '技能',
|
||||
pluginsTab: '插件',
|
||||
@@ -1017,6 +1017,7 @@ const message = {
|
||||
vllmProfileVisionExp: 'DeepSeek V4 Flash Vision Exp',
|
||||
vllmCommandPortHelper: '启动命令必须使用 {0} 端口,否则服务将无法访问。',
|
||||
ascendVisibleDevices: 'Ascend 可见设备(ASCEND_RT_VISIBLE_DEVICES)',
|
||||
syncModelAccount: '同步到模型账号',
|
||||
modelAccountAddressHelper:
|
||||
'容器地址适合智能体容器调用;127.0.0.1、本机 IP 或自定义地址适合 AI 网关或外部服务调用。',
|
||||
containerAddress: '容器地址',
|
||||
|
||||
@@ -1,33 +1,38 @@
|
||||
<template>
|
||||
<div class="form-section-title">{{ $t('aiTools.agents.account') }}</div>
|
||||
<span class="input-help model-account-helper">{{ $t('aiTools.model.modelAccountAddressHelper') }}</span>
|
||||
<el-form-item
|
||||
:label="$t('aiTools.agents.allowedOrigins')"
|
||||
prop="modelAccountBaseURLType"
|
||||
:rules="Rules.requiredSelect"
|
||||
>
|
||||
<el-radio-group v-model="form.modelAccountBaseURLType">
|
||||
<el-radio-button
|
||||
v-for="item in addressTypes"
|
||||
:key="item.value"
|
||||
:value="item.value"
|
||||
:disabled="hostMode && item.value === 'container'"
|
||||
>
|
||||
{{ item.label }}
|
||||
</el-radio-button>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="form.modelAccountBaseURLType === 'custom'"
|
||||
label="Base URL"
|
||||
prop="modelAccountBaseURL"
|
||||
:rules="customURLRules"
|
||||
>
|
||||
<el-input v-model.trim="form.modelAccountBaseURL" placeholder="http://192.168.1.100:8000/v1" />
|
||||
</el-form-item>
|
||||
<el-form-item v-else label="Base URL">
|
||||
<el-input :model-value="baseURL" readonly />
|
||||
<el-form-item v-if="showSync">
|
||||
<el-checkbox v-model="form.syncModelAccount">{{ $t('aiTools.model.syncModelAccount') }}</el-checkbox>
|
||||
</el-form-item>
|
||||
<template v-if="!showSync || form.syncModelAccount">
|
||||
<span class="input-help model-account-helper">{{ $t('aiTools.model.modelAccountAddressHelper') }}</span>
|
||||
<el-form-item
|
||||
:label="$t('aiTools.agents.allowedOrigins')"
|
||||
prop="modelAccountBaseURLType"
|
||||
:rules="Rules.requiredSelect"
|
||||
>
|
||||
<el-radio-group v-model="form.modelAccountBaseURLType">
|
||||
<el-radio-button
|
||||
v-for="item in addressTypes"
|
||||
:key="item.value"
|
||||
:value="item.value"
|
||||
:disabled="hostMode && item.value === 'container'"
|
||||
>
|
||||
{{ item.label }}
|
||||
</el-radio-button>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="form.modelAccountBaseURLType === 'custom'"
|
||||
label="Base URL"
|
||||
prop="modelAccountBaseURL"
|
||||
:rules="customURLRules"
|
||||
>
|
||||
<el-input v-model.trim="form.modelAccountBaseURL" placeholder="http://192.168.1.100:8000/v1" />
|
||||
</el-form-item>
|
||||
<el-form-item v-else label="Base URL">
|
||||
<el-input :model-value="baseURL" readonly />
|
||||
</el-form-item>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
@@ -39,6 +44,7 @@ import i18n from '@/lang';
|
||||
const props = defineProps<{
|
||||
form: {
|
||||
name: string;
|
||||
syncModelAccount?: boolean;
|
||||
containerName: string;
|
||||
modelAccountBaseURLType: string;
|
||||
modelAccountBaseURL: string;
|
||||
@@ -46,6 +52,7 @@ const props = defineProps<{
|
||||
hostPort: number;
|
||||
containerPort: string;
|
||||
hostMode?: boolean;
|
||||
showSync?: boolean;
|
||||
}>();
|
||||
const systemIP = ref('');
|
||||
const addressTypes = computed(() => [
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
v-if="visible && view && view !== VllmPlaceholder && !failed && !missing"
|
||||
ref="contentRef"
|
||||
v-bind="
|
||||
view === EngineConnection
|
||||
view === EngineConnection || view === AppParams
|
||||
? {}
|
||||
: view === ContainerLogs
|
||||
? { highlightDiff: 210 }
|
||||
@@ -100,6 +100,7 @@ import { hasPermission } from '@/utils/rbac';
|
||||
import { hasManagePermissionAccess } from '@/utils/permission';
|
||||
import { newUUID } from '@/utils/id';
|
||||
import OllamaModels from '../../ollama/models/index.vue';
|
||||
import AppParams from '@/views/app-store/installed/detail/index.vue';
|
||||
import EngineConnection from '../connection/index.vue';
|
||||
import TensorForm from '../../tensorrt/operate/index.vue';
|
||||
import VllmPlaceholder from '../../vllm/index.vue';
|
||||
@@ -196,9 +197,11 @@ const open = async (
|
||||
await openUpgrade(data.appInstallId, version);
|
||||
return;
|
||||
}
|
||||
const component = action === 'connection' ? EngineConnection : action === 'log' ? ContainerLogs : undefined;
|
||||
const component = { config: AppParams, connection: EngineConnection, log: ContainerLogs }[action];
|
||||
view.value = component ? markRaw(component) : undefined;
|
||||
await nextTick();
|
||||
if (version !== request || !visible.value) return;
|
||||
if (action === 'config') await contentRef.value?.acceptParams({ id: data.appInstallId, edit: true });
|
||||
if (action === 'connection')
|
||||
contentRef.value?.acceptParams({
|
||||
name: data.name,
|
||||
@@ -304,7 +307,9 @@ watch([currentNode, () => route.fullPath, isProductPro, isFxplay], () => {
|
||||
});
|
||||
const operationDisabled = (row: Deployment) =>
|
||||
deleteLoading.value ||
|
||||
['Installing', 'Starting', 'ReStarting', 'Restarting', 'Waiting', 'Uninstalling'].includes(row.status);
|
||||
['Installing', 'Starting', 'ReStarting', 'Restarting', 'Rebuilding', 'Waiting', 'Uninstalling'].includes(
|
||||
row.status,
|
||||
);
|
||||
const deleteDeployment = async () => {
|
||||
const version = request;
|
||||
const row = deleting.value;
|
||||
@@ -367,7 +372,7 @@ const returnToManagement = async () => {
|
||||
const previousView = view.value;
|
||||
await nextTick();
|
||||
if (view.value !== previousView) return;
|
||||
if (actionName.value === 'task' || actionName.value === 'upgrade') emit('search');
|
||||
if (['task', 'upgrade', 'config'].includes(actionName.value)) emit('search');
|
||||
if (deleted.value || standalone.value) visible.value = false;
|
||||
else if (instance.value) open(instance.value);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,156 @@
|
||||
<template>
|
||||
<DialogPro
|
||||
v-model="visible"
|
||||
:title="$t('commons.button.sync') + ' - ' + form.name"
|
||||
:size="isMobile ? 'full' : 'normal'"
|
||||
:show-close="!saving"
|
||||
@close="close"
|
||||
>
|
||||
<div v-loading="loading">
|
||||
<el-form v-if="form.id" ref="formRef" :model="form" label-position="top" :disabled="saving">
|
||||
<AccountAddress
|
||||
ref="addressRef"
|
||||
:form="form"
|
||||
:host-port="form.hostPort"
|
||||
:container-port="form.containerPort"
|
||||
:host-mode="form.hostMode"
|
||||
/>
|
||||
</el-form>
|
||||
<el-alert v-else-if="!loading" type="error" :closable="false" :title="$t('aiTools.model.loadFailed')" />
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button :disabled="saving" @click="close">{{ $t('commons.button.cancel') }}</el-button>
|
||||
<el-button v-permission type="primary" :loading="saving" :disabled="loading || !form.id" @click="submit">
|
||||
{{ $t('commons.button.sync') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</DialogPro>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onBeforeUnmount, reactive, ref, watch } from 'vue';
|
||||
import type { FormInstance } from 'element-plus';
|
||||
import type { AI } from '@/api/interface/ai';
|
||||
import { syncOllamaModelAccount } from '@/api/modules/ai';
|
||||
import { checkAppInstalled } from '@/api/modules/app';
|
||||
import { useGlobalStore } from '@/composables/useGlobalStore';
|
||||
import { useRoute } from 'vue-router';
|
||||
import i18n from '@/lang';
|
||||
import { MsgError, MsgSuccess } from '@/utils/message';
|
||||
import AccountAddress from '../account-address/index.vue';
|
||||
import { loadVllmAPI, readPages, type Deployment } from '../../deployment';
|
||||
|
||||
const emit = defineEmits(['search']);
|
||||
const { currentNode, isMobile } = useGlobalStore();
|
||||
const route = useRoute();
|
||||
const visible = ref(false);
|
||||
const loading = ref(false);
|
||||
const saving = ref(false);
|
||||
const formRef = ref<FormInstance>();
|
||||
const addressRef = ref<InstanceType<typeof AccountAddress>>();
|
||||
const form = reactive({
|
||||
id: 0,
|
||||
engine: 'ollama' as 'ollama' | 'vllm',
|
||||
name: '',
|
||||
containerName: '',
|
||||
hostPort: 0,
|
||||
containerPort: '',
|
||||
hostMode: false,
|
||||
modelAccountBaseURLType: 'container' as AI.ModelAccountSync['modelAccountBaseURLType'],
|
||||
modelAccountBaseURL: '',
|
||||
});
|
||||
let request = 0;
|
||||
|
||||
const close = () => {
|
||||
request++;
|
||||
visible.value = false;
|
||||
saving.value = false;
|
||||
};
|
||||
|
||||
const open = async (row: Deployment) => {
|
||||
if (row.engine !== 'ollama' && row.engine !== 'vllm') return;
|
||||
const version = ++request;
|
||||
form.id = 0;
|
||||
form.name = row.name;
|
||||
form.engine = row.engine;
|
||||
visible.value = true;
|
||||
loading.value = true;
|
||||
try {
|
||||
if (row.engine === 'ollama') {
|
||||
const { data: item } = await checkAppInstalled('ollama', row.name);
|
||||
if (version !== request) return;
|
||||
if (!item.isExist || item.appInstallId !== row.id) {
|
||||
MsgError(i18n.global.t('commons.res.notFound'));
|
||||
return;
|
||||
}
|
||||
Object.assign(form, {
|
||||
id: item.appInstallId,
|
||||
containerName: item.containerName,
|
||||
hostPort: item.httpPort,
|
||||
containerPort: '11434',
|
||||
hostMode: false,
|
||||
modelAccountBaseURLType: item.modelAccountBaseURLType || 'container',
|
||||
modelAccountBaseURL: item.modelAccountBaseURL || '',
|
||||
});
|
||||
} else {
|
||||
const api = await loadVllmAPI();
|
||||
if (version !== request) return;
|
||||
const items = await readPages((page) => api.pageVllm({ ...page, info: row.name }));
|
||||
if (version !== request) return;
|
||||
const item = items.find((item) => item.id === row.id);
|
||||
if (!item) {
|
||||
MsgError(i18n.global.t('commons.res.notFound'));
|
||||
return;
|
||||
}
|
||||
const hostMode = item.appVersion.toLowerCase().startsWith('nvidia-gb10-dspark-');
|
||||
Object.assign(form, {
|
||||
id: item.id,
|
||||
containerName: item.containerName,
|
||||
hostPort: item.port,
|
||||
containerPort:
|
||||
item.command.match(/--port\s+(\d+)/)?.[1] || (item.imageType === 'intel' ? '8001' : '8000'),
|
||||
hostMode,
|
||||
modelAccountBaseURLType:
|
||||
hostMode && item.modelAccountBaseURLType === 'container'
|
||||
? 'systemIP'
|
||||
: item.modelAccountBaseURLType || (hostMode ? 'systemIP' : 'container'),
|
||||
modelAccountBaseURL: item.modelAccountBaseURL || '',
|
||||
});
|
||||
}
|
||||
} finally {
|
||||
if (version === request) loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const submit = async () => {
|
||||
if (loading.value || saving.value || !form.id || !formRef.value) return;
|
||||
const version = request;
|
||||
saving.value = true;
|
||||
try {
|
||||
const valid = await formRef.value.validate().catch(() => false);
|
||||
if (!valid || version !== request || !visible.value) return;
|
||||
const req: AI.ModelAccountSync = {
|
||||
id: form.id,
|
||||
modelAccountBaseURLType: form.modelAccountBaseURLType,
|
||||
modelAccountBaseURL: addressRef.value.baseURL,
|
||||
};
|
||||
if (form.engine === 'ollama') {
|
||||
await syncOllamaModelAccount(req);
|
||||
} else {
|
||||
const api = await loadVllmAPI();
|
||||
if (version !== request || !visible.value) return;
|
||||
await api.operateVllm({ ...req, operate: 'sync', taskID: '' });
|
||||
}
|
||||
if (version !== request) return;
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
emit('search');
|
||||
close();
|
||||
} finally {
|
||||
if (version === request) saving.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
watch([currentNode, () => route.fullPath], close);
|
||||
onBeforeUnmount(close);
|
||||
defineExpose({ open });
|
||||
</script>
|
||||
@@ -17,6 +17,7 @@ export interface Deployment {
|
||||
|
||||
interface VllmItem {
|
||||
appInstallId: number;
|
||||
agentAccountId: number;
|
||||
path: string;
|
||||
upgradable: boolean;
|
||||
id: number;
|
||||
@@ -25,6 +26,9 @@ interface VllmItem {
|
||||
appVersion: string;
|
||||
port: number;
|
||||
modelAccountBaseURL: string;
|
||||
modelAccountBaseURLType: AI.ModelAccountSync['modelAccountBaseURLType'];
|
||||
command: string;
|
||||
imageType: string;
|
||||
containerName: string;
|
||||
modelDir: string;
|
||||
status: string;
|
||||
@@ -32,7 +36,9 @@ interface VllmItem {
|
||||
|
||||
interface VllmAPI {
|
||||
pageVllm: (req: ReqPage & { info: string }) => Promise<ResultData<ResPage<VllmItem>>>;
|
||||
operateVllm: (req: { id: number; operate: string; taskID: string; forceDelete?: boolean }) => Promise<unknown>;
|
||||
operateVllm: (
|
||||
req: { id: number; operate: string; taskID: string; forceDelete?: boolean } & Partial<AI.ModelAccountSync>,
|
||||
) => Promise<unknown>;
|
||||
}
|
||||
|
||||
const vllmModules = import.meta.glob<VllmAPI>('@/xpack/api/modules/vllm.ts');
|
||||
|
||||
@@ -281,7 +281,7 @@
|
||||
:buttons="deploymentButtons"
|
||||
:label="$t('commons.table.operate')"
|
||||
fixed="right"
|
||||
:ellipsis="4"
|
||||
:ellipsis="2"
|
||||
width="240"
|
||||
/>
|
||||
</ComplexTable>
|
||||
@@ -305,6 +305,7 @@
|
||||
<AppResources :key="`resources:${currentNode}:${route.fullPath}`" ref="appResourcesRef" @close="search" />
|
||||
<TaskLog ref="taskRef" width="70%" @close="search" />
|
||||
<ModelPool ref="poolRef" @updated="search" />
|
||||
<SyncService ref="syncRef" @search="search" />
|
||||
<component :is="templateView" v-if="templateView" ref="templateRef" />
|
||||
</div>
|
||||
</template>
|
||||
@@ -338,6 +339,7 @@ import AppUpgrade from '@/views/app-store/installed/upgrade/index.vue';
|
||||
import AppDelete from '@/views/app-store/installed/delete/index.vue';
|
||||
import AppResources from '@/views/app-store/installed/check/index.vue';
|
||||
import ModelPool from './components/model-pool/index.vue';
|
||||
import SyncService from './components/sync/index.vue';
|
||||
import ProviderLabel from '@/components/agent-provider-label/index.vue';
|
||||
import { loadModelOverview, loadVllmAPI, readPages, vllmAvailable, type Deployment } from './deployment';
|
||||
|
||||
@@ -366,6 +368,7 @@ const upgradeRef = ref();
|
||||
const appDeleteRef = ref();
|
||||
const appResourcesRef = ref();
|
||||
const poolRef = ref();
|
||||
const syncRef = ref<InstanceType<typeof SyncService>>();
|
||||
const templateRef = ref();
|
||||
const templateView = shallowRef();
|
||||
const loading = ref(false);
|
||||
@@ -566,7 +569,9 @@ const taskRef = ref();
|
||||
const pending = ref('');
|
||||
const operationDisabled = (row: Deployment) =>
|
||||
Boolean(pending.value) ||
|
||||
['Installing', 'Starting', 'ReStarting', 'Restarting', 'Waiting', 'Uninstalling'].includes(row.status);
|
||||
['Installing', 'Starting', 'ReStarting', 'Restarting', 'Rebuilding', 'Waiting', 'Uninstalling'].includes(
|
||||
row.status,
|
||||
);
|
||||
const statusDisabled = (row: Deployment) =>
|
||||
operationDisabled(row) || !hasManagePermissionAccess() || (row.engine === 'ollama' && !hasPermission('app_manage'));
|
||||
const operate = async (row: Deployment, operation: 'start' | 'stop' | 'restart') => {
|
||||
@@ -601,23 +606,30 @@ const operate = async (row: Deployment, operation: 'start' | 'stop' | 'restart')
|
||||
}
|
||||
};
|
||||
const deploymentButtons = [
|
||||
{
|
||||
label: i18n.global.t('commons.button.edit'),
|
||||
permission: true,
|
||||
show: (row: Deployment) => row.engine === 'vllm' || (row.engine === 'ollama' && hasPermission('app_manage')),
|
||||
disabled: operationDisabled,
|
||||
click: (row: Deployment) => manageRef.value.open(row, 'config', true),
|
||||
},
|
||||
{
|
||||
label: i18n.global.t('aiTools.model.manage'),
|
||||
show: (row: Deployment) => row.engine !== 'vllm',
|
||||
click: manageDeployment,
|
||||
},
|
||||
{
|
||||
label: i18n.global.t('commons.button.edit'),
|
||||
permission: true,
|
||||
show: (row: Deployment) => row.engine === 'vllm',
|
||||
disabled: operationDisabled,
|
||||
click: (row: Deployment) => manageRef.value.open(row, 'config', true),
|
||||
},
|
||||
{
|
||||
label: i18n.global.t('menu.monitor'),
|
||||
show: (row: Deployment) => row.engine === 'vllm',
|
||||
click: (row: Deployment) => manageRef.value.open(row, 'monitor', true),
|
||||
},
|
||||
{
|
||||
label: i18n.global.t('commons.button.sync'),
|
||||
permission: true,
|
||||
show: (row: Deployment) => row.engine === 'ollama' || row.engine === 'vllm',
|
||||
disabled: operationDisabled,
|
||||
click: (row: Deployment) => syncRef.value.open(row),
|
||||
},
|
||||
{
|
||||
label: i18n.global.t('commons.button.log'),
|
||||
click: (row: Deployment) => manageRef.value.open(row, 'log', true),
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
<template #after-params>
|
||||
<AccountAddress
|
||||
ref="accountRef"
|
||||
show-sync
|
||||
:form="form"
|
||||
:host-port="Number(form.params.PANEL_APP_PORT_HTTP) || 11434"
|
||||
container-port="11434"
|
||||
@@ -51,6 +52,7 @@ const form = ref({
|
||||
name: '',
|
||||
params: {} as Record<string, any>,
|
||||
containerName: '',
|
||||
syncModelAccount: true,
|
||||
modelAccountBaseURLType: 'container',
|
||||
modelAccountBaseURL: '',
|
||||
});
|
||||
@@ -68,6 +70,7 @@ const openCreate = async () => {
|
||||
formRef.value.resetForm();
|
||||
formRef.value.setFormData({
|
||||
restartPolicy: 'unless-stopped',
|
||||
syncModelAccount: true,
|
||||
modelAccountBaseURLType: 'container',
|
||||
modelAccountBaseURL: '',
|
||||
});
|
||||
|
||||
@@ -98,6 +98,15 @@
|
||||
<el-input v-else v-model.trim="paramModel.params[p.key]" :disabled="!p.edit"></el-input>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<AccountAddress
|
||||
v-if="appKey === 'ollama'"
|
||||
ref="accountRef"
|
||||
show-sync
|
||||
:form="paramModel"
|
||||
:host-port="Number(paramModel.params.PANEL_APP_PORT_HTTP) || paramModel.httpPort"
|
||||
container-port="11434"
|
||||
:host-mode="paramModel.isHostMode"
|
||||
/>
|
||||
<el-form-item prop="advanced">
|
||||
<el-checkbox v-model="paramModel.advanced" :label="$t('app.advanced')" size="large" />
|
||||
</el-form-item>
|
||||
@@ -179,11 +188,12 @@
|
||||
</DrawerPro>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import AccountAddress from '@/views/ai/model/components/account-address/index.vue';
|
||||
import Diff from '@/views/app-store/installed/upgrade/diff/index.vue';
|
||||
|
||||
import { App } from '@/api/interface/app';
|
||||
import { getAppInstallParams, updateAppInstallParams, updateInstallConfig } from '@/api/modules/app';
|
||||
import { reactive, ref } from 'vue';
|
||||
import { onBeforeUnmount, reactive, ref } from 'vue';
|
||||
import { FormInstance } from 'element-plus';
|
||||
import { Rules, checkNumberRange } from '@/global/form-rules';
|
||||
import { MsgError, MsgSuccess } from '@/utils/message';
|
||||
@@ -192,10 +202,12 @@ import { splitHttp, checkIpV4V6, checkDomain } from '@/utils/validate';
|
||||
import i18n from '@/lang';
|
||||
import { loadResourceLimit } from '@/api/modules/container';
|
||||
import { Container } from '@/api/interface/container';
|
||||
import { useGlobalStore } from '@/composables/useGlobalStore';
|
||||
|
||||
interface ParamProps {
|
||||
id: number;
|
||||
app: any;
|
||||
app?: any;
|
||||
edit?: boolean;
|
||||
}
|
||||
const paramData = ref<ParamProps>({
|
||||
id: 0,
|
||||
@@ -207,6 +219,7 @@ interface EditForm extends App.InstallParams {
|
||||
}
|
||||
|
||||
const emit = defineEmits(['close']);
|
||||
const { currentNode } = useGlobalStore();
|
||||
const open = ref(false);
|
||||
const loading = ref(false);
|
||||
const params = ref<EditForm[]>();
|
||||
@@ -222,10 +235,9 @@ const rules = reactive({
|
||||
containerName: [Rules.containerName],
|
||||
restartPolicy: [Rules.requiredSelect],
|
||||
});
|
||||
const submitModel = reactive<any>({
|
||||
webUI: '',
|
||||
});
|
||||
const appType = ref('');
|
||||
const appKey = ref('');
|
||||
const accountRef = ref<InstanceType<typeof AccountAddress>>();
|
||||
const appConfigUpdate = ref<App.AppConfigUpdate>({
|
||||
installID: 0,
|
||||
webUI: '',
|
||||
@@ -285,15 +297,15 @@ function checkPort(port: string) {
|
||||
}
|
||||
|
||||
const acceptParams = async (props: ParamProps) => {
|
||||
submitModel.installId = props.id;
|
||||
params.value = [];
|
||||
paramData.value.id = props.id;
|
||||
paramModel.params = {};
|
||||
edit.value = false;
|
||||
rules.params = {};
|
||||
await get();
|
||||
if (!(await get())) return;
|
||||
open.value = true;
|
||||
openConfig.value = false;
|
||||
if (props.edit) editParam();
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
@@ -358,6 +370,7 @@ const get = async () => {
|
||||
paramModel.allowPort = res.data.allowPort;
|
||||
paramModel.containerName = res.data.containerName;
|
||||
paramModel.advanced = false;
|
||||
paramModel.editCompose = false;
|
||||
paramModel.dockerCompose = res.data.dockerCompose;
|
||||
paramModel.isHostMode = res.data.hostMode;
|
||||
paramModel.specifyIP = res.data.specifyIP;
|
||||
@@ -372,7 +385,15 @@ const get = async () => {
|
||||
webUI.protocol = httpConfig.proto + '://';
|
||||
}
|
||||
appType.value = res.data.type;
|
||||
appKey.value = res.data.appKey;
|
||||
paramModel.name = res.data.name;
|
||||
paramModel.httpPort = res.data.httpPort;
|
||||
paramModel.syncModelAccount = res.data.syncModelAccount;
|
||||
paramModel.modelAccountBaseURLType = res.data.modelAccountBaseURLType || 'container';
|
||||
paramModel.modelAccountBaseURL = res.data.modelAccountBaseURL;
|
||||
return true;
|
||||
} catch (error) {
|
||||
return false;
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
@@ -380,6 +401,8 @@ const get = async () => {
|
||||
|
||||
const submit = async (formEl: FormInstance) => {
|
||||
if (!formEl) return;
|
||||
const node = currentNode.value;
|
||||
const installId = paramData.value.id;
|
||||
await formEl.validate((valid) => {
|
||||
if (!valid) {
|
||||
return;
|
||||
@@ -389,7 +412,13 @@ const submit = async (formEl: FormInstance) => {
|
||||
cancelButtonText: i18n.global.t('commons.button.cancel'),
|
||||
type: 'info',
|
||||
}).then(async () => {
|
||||
submitModel.params = paramModel.params;
|
||||
if (!open.value || node !== currentNode.value || installId !== paramData.value.id) return;
|
||||
const submitModel: Record<string, any> = { installId, params: paramModel.params };
|
||||
if (appKey.value === 'ollama') {
|
||||
submitModel.syncModelAccount = paramModel.syncModelAccount;
|
||||
submitModel.modelAccountBaseURLType = paramModel.modelAccountBaseURLType;
|
||||
submitModel.modelAccountBaseURL = accountRef.value.baseURL;
|
||||
}
|
||||
if (paramModel.advanced) {
|
||||
submitModel.advanced = paramModel.advanced;
|
||||
submitModel.memoryLimit = paramModel.memoryLimit;
|
||||
@@ -469,6 +498,10 @@ const changeUnit = () => {
|
||||
};
|
||||
|
||||
defineExpose({ acceptParams });
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
open.value = false;
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
Reference in New Issue
Block a user