mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-10-30 16:05:30 +00:00
feat(ai): streamline Ollama model pool management (#14259)
This commit is contained in:
@@ -318,6 +318,7 @@ type AgentAccountModelDiscoverReq struct {
|
||||
}
|
||||
|
||||
type AgentAccountModelCreateReq struct {
|
||||
TaskID string `json:"taskID"`
|
||||
AccountID uint `json:"accountId" validate:"required"`
|
||||
Model AgentAccountModel `json:"model" validate:"required"`
|
||||
}
|
||||
@@ -328,8 +329,9 @@ type AgentAccountModelUpdateReq struct {
|
||||
}
|
||||
|
||||
type AgentAccountModelDeleteReq struct {
|
||||
AccountID uint `json:"accountId" validate:"required"`
|
||||
RecordID uint `json:"recordId" validate:"required"`
|
||||
DeleteFiles bool `json:"deleteFiles"`
|
||||
AccountID uint `json:"accountId" validate:"required"`
|
||||
RecordID uint `json:"recordId" validate:"required"`
|
||||
}
|
||||
|
||||
type AgentAccountCreateReq struct {
|
||||
|
||||
@@ -1298,6 +1298,31 @@ func (a AgentService) CreateAccountModel(req dto.AgentAccountModelCreateReq) err
|
||||
if _, ok := findAgentAccountModelForProvider(account.Provider, models, nextModel.ID); ok {
|
||||
return buserr.New("ErrRecordExist")
|
||||
}
|
||||
if account.Provider == "ollama" {
|
||||
if account.AppInstallID != 0 {
|
||||
install, err := appInstallRepo.GetFirst(repo.WithByID(account.AppInstallID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if install.App.Key != "ollama" || !install.SyncModelAccount {
|
||||
return buserr.New("ErrAgentAccountUnavailable")
|
||||
}
|
||||
localModels, err := aiRepo.List()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, item := range localModels {
|
||||
if sameProviderModelID("ollama", item.Name, nextModel.ID) {
|
||||
return (&AIToolService{}).Recreate(dto.OllamaModelName{Name: item.Name, TaskID: req.TaskID})
|
||||
}
|
||||
}
|
||||
return (&AIToolService{}).Create(dto.OllamaModelName{Name: nextModel.ID, TaskID: req.TaskID})
|
||||
}
|
||||
nextModel.ID, err = resolveExistingOllamaModel(account, nextModel.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
sortOrder := len(models) + 1
|
||||
record := &model.AgentAccountModel{
|
||||
AccountID: account.ID,
|
||||
@@ -1328,6 +1353,15 @@ func (a AgentService) UpdateAccountModel(req dto.AgentAccountModelUpdateReq) err
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if account.Provider == "ollama" && !sameProviderModelID("ollama", record.Model, nextModel.ID) {
|
||||
if account.AppInstallID != 0 {
|
||||
return buserr.New("ErrInvalidParams")
|
||||
}
|
||||
nextModel.ID, err = resolveExistingOllamaModel(account, nextModel.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
nextModel.RecordID = req.Model.RecordID
|
||||
for _, item := range models {
|
||||
if item.RecordID == req.Model.RecordID {
|
||||
@@ -1366,6 +1400,10 @@ func (a AgentService) UpdateAccountModel(req dto.AgentAccountModelUpdateReq) err
|
||||
}
|
||||
|
||||
func (a AgentService) DeleteAccountModel(req dto.AgentAccountModelDeleteReq) error {
|
||||
ollamaModelMu.Lock()
|
||||
defer ollamaModelMu.Unlock()
|
||||
agentAccountMutationMu.Lock()
|
||||
defer agentAccountMutationMu.Unlock()
|
||||
account, err := agentAccountRepo.GetFirst(repo.WithByID(req.AccountID))
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -1391,6 +1429,44 @@ func (a AgentService) DeleteAccountModel(req dto.AgentAccountModelDeleteReq) err
|
||||
if err := ensureAccountModelsNotBound(account, nextModels); err != nil {
|
||||
return err
|
||||
}
|
||||
if req.DeleteFiles && account.Provider != "ollama" {
|
||||
return buserr.New("ErrAgentProviderNotSupported")
|
||||
}
|
||||
if req.DeleteFiles {
|
||||
if account.AppInstallID != 0 {
|
||||
install, err := appInstallRepo.GetFirst(repo.WithByID(account.AppInstallID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if install.App.Key != "ollama" {
|
||||
return buserr.New("ErrAgentAccountUnavailable")
|
||||
}
|
||||
localModels, err := aiRepo.List()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
localIDs := make([]uint, 0)
|
||||
for _, item := range localModels {
|
||||
if !sameProviderModelID("ollama", item.Name, record.Model) {
|
||||
continue
|
||||
}
|
||||
if item.Status == constant.StatusWaiting {
|
||||
return buserr.New("TaskIsExecuting")
|
||||
}
|
||||
localIDs = append(localIDs, item.ID)
|
||||
}
|
||||
if err := cmd.NewCommandMgr().Run("docker", "exec", install.ContainerName, "ollama", "rm", record.Model); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(localIDs) > 0 {
|
||||
if err := aiRepo.Delete(repo.WithByIDs(localIDs)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else if err := deleteRemoteOllamaModel(account, record.Model); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := agentAccountModelRepo.DeleteByID(req.RecordID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -1,13 +1,19 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
providercatalog "github.com/1Panel-dev/1Panel/agent/app/provider"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
||||
"github.com/1Panel-dev/1Panel/agent/buserr"
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
terminalai "github.com/1Panel-dev/1Panel/agent/utils/terminal/ai"
|
||||
@@ -136,6 +142,62 @@ func syncOllamaModelAccount(install model.AppInstall, baseURL string, updateBase
|
||||
return err
|
||||
}
|
||||
|
||||
func updateOllamaModelPool(modelName string, remove bool) error {
|
||||
install, err := appInstallRepo.LoadInstallAppByKeyAndName("ollama", "")
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !install.SyncModelAccount {
|
||||
return nil
|
||||
}
|
||||
agentAccountMutationMu.Lock()
|
||||
defer agentAccountMutationMu.Unlock()
|
||||
account, err := agentAccountRepo.GetFirst(repo.WithByAppInstallID(install.ID), repo.WithByProvider("ollama"))
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
models, err := loadAgentAccountModels(account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if remove {
|
||||
for _, item := range models {
|
||||
if !sameProviderModelID("ollama", item.ID, modelName) {
|
||||
continue
|
||||
}
|
||||
if err := agentAccountModelRepo.DeleteByID(item.RecordID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if sameProviderModelID("ollama", account.VerifyModel, modelName) {
|
||||
account.VerifyModel = ""
|
||||
if err := agentAccountRepo.Save(account); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := compactPersistedAgentAccountModelSortOrder(account.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if _, exists := findAgentAccountModelForProvider("ollama", models, modelName); exists {
|
||||
return nil
|
||||
}
|
||||
record := &model.AgentAccountModel{AccountID: account.ID, Model: modelName, Name: modelName, SortOrder: len(models) + 1}
|
||||
if err := agentAccountModelRepo.Create(record); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
terminalai.InvalidateTerminalRuntimeCache()
|
||||
terminalai.InvalidateFileAIRuntimeCache()
|
||||
return nil
|
||||
}
|
||||
|
||||
func loadOllamaModelAccountAddress(install model.AppInstall) (string, string, error) {
|
||||
addressType := install.ModelAccountBaseURLType
|
||||
if addressType == "" {
|
||||
@@ -200,3 +262,39 @@ func ResolveLocalModelAccountBaseURL(addressType, customURL, containerName, cont
|
||||
return fmt.Sprintf("http://%s:%s/v1", containerName, containerPort), nil
|
||||
}
|
||||
}
|
||||
|
||||
func deleteRemoteOllamaModel(account *model.AgentAccount, modelName string) error {
|
||||
body, err := json.Marshal(map[string]string{"model": modelName})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
baseURL := strings.TrimSuffix(strings.TrimRight(account.BaseURL, "/"), "/v1")
|
||||
req, err := http.NewRequest(http.MethodDelete, baseURL+"/api/delete", bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Authorization", "Bearer "+account.APIKey)
|
||||
resp, err := (&http.Client{Timeout: 30 * time.Second}).Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusMultipleChoices {
|
||||
return fmt.Errorf("delete Ollama model failed: %s", resp.Status)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func resolveExistingOllamaModel(account *model.AgentAccount, modelID string) (string, error) {
|
||||
available, err := providercatalog.DiscoverModels(account.BaseURL, account.APIKey)
|
||||
if err != nil {
|
||||
return "", buserr.WithErr("ErrAgentAccountUnavailable", err)
|
||||
}
|
||||
for _, id := range available {
|
||||
if sameProviderModelID("ollama", id, modelID) {
|
||||
return id, nil
|
||||
}
|
||||
}
|
||||
return "", buserr.New("ErrRecordNotFound")
|
||||
}
|
||||
|
||||
@@ -1517,6 +1517,14 @@ func sameProviderModelID(provider, left, right string) bool {
|
||||
}
|
||||
leftComparable := providercatalog.NormalizeModelID(provider, leftTrimmed)
|
||||
rightComparable := providercatalog.NormalizeModelID(provider, rightTrimmed)
|
||||
if provider == "ollama" {
|
||||
if leftComparable != "" && !strings.Contains(path.Base(leftComparable), ":") {
|
||||
leftComparable += ":latest"
|
||||
}
|
||||
if rightComparable != "" && !strings.Contains(path.Base(rightComparable), ":") {
|
||||
rightComparable += ":latest"
|
||||
}
|
||||
}
|
||||
return leftComparable != "" && leftComparable == rightComparable
|
||||
}
|
||||
|
||||
|
||||
+24
-18
@@ -117,6 +117,7 @@ func (u *AIToolService) Create(req dto.OllamaModelName) error {
|
||||
}
|
||||
taskItem, err := task.NewTaskWithOps(fmt.Sprintf("ollama-model-%s", req.Name), task.TaskPull, task.TaskScopeAI, req.TaskID, info.ID)
|
||||
if err != nil {
|
||||
_ = aiRepo.Update(info.ID, map[string]interface{}{"status": constant.StatusFailed, "message": err.Error()})
|
||||
global.LOG.Errorf("new task for exec shell failed, err: %v", err)
|
||||
return err
|
||||
}
|
||||
@@ -126,19 +127,17 @@ func (u *AIToolService) Create(req dto.OllamaModelName) error {
|
||||
}, nil)
|
||||
taskItem.AddSubTask(i18n.GetWithName("OllamaModelSize", req.Name), func(t *task.Task) error {
|
||||
itemSize, err := loadModelSize(info.Name, containerName)
|
||||
if len(itemSize) != 0 {
|
||||
_ = aiRepo.Update(info.ID, map[string]interface{}{"status": constant.StatusSuccess, "size": itemSize})
|
||||
} else {
|
||||
_ = aiRepo.Update(info.ID, map[string]interface{}{"status": constant.StatusFailed, "message": err.Error()})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
return aiRepo.Update(info.ID, map[string]interface{}{"status": constant.StatusSuccess, "size": itemSize})
|
||||
}, nil)
|
||||
taskItem.AddSubTask(i18n.GetMsgByKey("TaskSync"), func(t *task.Task) error {
|
||||
return updateOllamaModelPool(info.Name, false)
|
||||
}, nil)
|
||||
if err := taskItem.Execute(); err != nil {
|
||||
_ = aiRepo.Update(info.ID, map[string]interface{}{"status": constant.StatusFailed, "message": err.Error()})
|
||||
}
|
||||
if err := SyncOllamaModelAccount(false); err != nil {
|
||||
global.LOG.Errorf("sync Ollama model account after pull: %v", err)
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
@@ -167,6 +166,9 @@ func (u *AIToolService) Recreate(req dto.OllamaModelName) error {
|
||||
if modelInfo.ID == 0 {
|
||||
return buserr.New("ErrRecordNotFound")
|
||||
}
|
||||
if modelInfo.Status == constant.StatusWaiting {
|
||||
return buserr.New("TaskIsExecuting")
|
||||
}
|
||||
containerName, err := LoadContainerName()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -176,6 +178,7 @@ func (u *AIToolService) Recreate(req dto.OllamaModelName) error {
|
||||
}
|
||||
taskItem, err := task.NewTaskWithOps(fmt.Sprintf("ollama-model-%s", req.Name), task.TaskPull, task.TaskScopeAI, req.TaskID, modelInfo.ID)
|
||||
if err != nil {
|
||||
_ = aiRepo.Update(modelInfo.ID, map[string]interface{}{"status": constant.StatusFailed, "message": err.Error()})
|
||||
global.LOG.Errorf("new task for exec shell failed, err: %v", err)
|
||||
return err
|
||||
}
|
||||
@@ -185,19 +188,17 @@ func (u *AIToolService) Recreate(req dto.OllamaModelName) error {
|
||||
}, nil)
|
||||
taskItem.AddSubTask(i18n.GetWithName("OllamaModelSize", req.Name), func(t *task.Task) error {
|
||||
itemSize, err := loadModelSize(modelInfo.Name, containerName)
|
||||
if len(itemSize) != 0 {
|
||||
_ = aiRepo.Update(modelInfo.ID, map[string]interface{}{"status": constant.StatusSuccess, "size": itemSize})
|
||||
} else {
|
||||
_ = aiRepo.Update(modelInfo.ID, map[string]interface{}{"status": constant.StatusFailed, "message": err.Error()})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
return aiRepo.Update(modelInfo.ID, map[string]interface{}{"status": constant.StatusSuccess, "size": itemSize})
|
||||
}, nil)
|
||||
taskItem.AddSubTask(i18n.GetMsgByKey("TaskSync"), func(t *task.Task) error {
|
||||
return updateOllamaModelPool(modelInfo.Name, false)
|
||||
}, nil)
|
||||
if err := taskItem.Execute(); err != nil {
|
||||
_ = aiRepo.Update(modelInfo.ID, map[string]interface{}{"status": constant.StatusFailed, "message": err.Error()})
|
||||
}
|
||||
if err := SyncOllamaModelAccount(false); err != nil {
|
||||
global.LOG.Errorf("sync Ollama model account after pull: %v", err)
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
@@ -219,11 +220,16 @@ func (u *AIToolService) Delete(req dto.ForceDelete) error {
|
||||
return fmt.Errorf("handle ollama rm %s failed, %v", item.Name, err)
|
||||
}
|
||||
}
|
||||
_ = aiRepo.Delete(repo.WithByID(item.ID))
|
||||
if err := aiRepo.Delete(repo.WithByID(item.ID)); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := updateOllamaModelPool(item.Name, true); err != nil {
|
||||
return err
|
||||
}
|
||||
logItem := path.Join(global.Dir.DataDir, "log", "AITools", item.Name)
|
||||
_ = os.Remove(logItem)
|
||||
}
|
||||
return SyncOllamaModelAccount(false)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *AIToolService) SyncAccount(req dto.OllamaModelAccountSync) error {
|
||||
|
||||
@@ -677,6 +677,7 @@ export namespace AI {
|
||||
}
|
||||
|
||||
export interface AgentAccountModelCreateReq {
|
||||
taskID?: string;
|
||||
accountId: number;
|
||||
model: AgentAccountModel;
|
||||
}
|
||||
@@ -687,6 +688,7 @@ export namespace AI {
|
||||
}
|
||||
|
||||
export interface AgentAccountModelDeleteReq {
|
||||
deleteFiles?: boolean;
|
||||
accountId: number;
|
||||
recordId: number;
|
||||
}
|
||||
|
||||
@@ -844,6 +844,9 @@ const message = {
|
||||
discoverModelsFailedUseDefaults: 'Failed to get models. Default models are being used.',
|
||||
accountModelsDuplicate: 'Duplicate models were found in the catalog',
|
||||
modelPool: 'Model Pool',
|
||||
ollamaRegisterOnly:
|
||||
'Only register models already available on this Ollama service. No download will be started.',
|
||||
deleteModelFiles: 'Also delete model files from the Ollama service',
|
||||
fallbackModels: 'Fallback Models',
|
||||
addFallbackModel: 'Add Fallback Model',
|
||||
fallbackModelsEmpty: 'No fallback models configured',
|
||||
|
||||
@@ -849,6 +849,9 @@ const message = {
|
||||
'No se pudieron obtener los modelos. Se usarán los modelos predeterminados.',
|
||||
accountModelsDuplicate: 'Existen modelos duplicados en el catálogo',
|
||||
modelPool: 'Pool de modelos',
|
||||
ollamaRegisterOnly:
|
||||
'Solo se registran modelos ya disponibles en este servicio Ollama. No se iniciará ninguna descarga.',
|
||||
deleteModelFiles: 'Eliminar también los archivos del modelo del servicio Ollama',
|
||||
fallbackModels: 'Modelos de respaldo',
|
||||
addFallbackModel: 'Agregar modelo de respaldo',
|
||||
fallbackModelsEmpty: 'No hay modelos de respaldo configurados',
|
||||
|
||||
@@ -839,6 +839,8 @@ const message = {
|
||||
discoverModelsFailedUseDefaults: 'دریافت مدلها ناموفق بود. از مدلهای پیشفرض استفاده میشود.',
|
||||
accountModelsDuplicate: 'مدلهای تکراری در کاتالوگ یافت شد',
|
||||
modelPool: 'مجموعه مدلها',
|
||||
ollamaRegisterOnly: 'فقط مدلهای موجود در این سرویس Ollama ثبت میشوند. دانلودی آغاز نخواهد شد.',
|
||||
deleteModelFiles: 'فایلهای مدل نیز از سرویس Ollama حذف شوند',
|
||||
fallbackModels: 'مدلهای جایگزین',
|
||||
addFallbackModel: 'افزودن مدل جایگزین',
|
||||
fallbackModelsEmpty: 'هیچ مدل جایگزینی پیکربندی نشده است',
|
||||
|
||||
@@ -843,6 +843,8 @@ const message = {
|
||||
discoverModelsFailedUseDefaults: 'モデルを取得できませんでした。デフォルトモデルを使用しています。',
|
||||
accountModelsDuplicate: 'カタログに重複したモデルがあります',
|
||||
modelPool: 'モデルプール',
|
||||
ollamaRegisterOnly: 'この Ollama サービスに存在するモデルのみを登録します。ダウンロードは開始されません。',
|
||||
deleteModelFiles: 'Ollama サービス上のモデルファイルも削除する',
|
||||
fallbackModels: 'フォールバックモデル',
|
||||
addFallbackModel: 'フォールバックモデルを追加',
|
||||
fallbackModelsEmpty: 'フォールバックモデルは未設定です',
|
||||
|
||||
@@ -833,6 +833,8 @@ const message = {
|
||||
discoverModelsFailedUseDefaults: '모델을 가져오지 못했습니다. 기본 모델을 사용합니다.',
|
||||
accountModelsDuplicate: '카탈로그에 중복된 모델이 있습니다',
|
||||
modelPool: '모델 풀',
|
||||
ollamaRegisterOnly: '이 Ollama 서비스에 이미 있는 모델만 등록합니다. 다운로드는 시작되지 않습니다.',
|
||||
deleteModelFiles: 'Ollama 서비스의 모델 파일도 삭제',
|
||||
fallbackModels: '폴백 모델',
|
||||
addFallbackModel: '폴백 모델 추가',
|
||||
fallbackModelsEmpty: '구성된 폴백 모델이 없습니다',
|
||||
|
||||
@@ -831,6 +831,8 @@ const message = {
|
||||
discoverModelsFailedUseDefaults: 'ດຶງລາຍການໂມເດວບໍ່ສຳເລັດ. ກຳລັງໃຊ້ໂມເດວເລີ່ມຕົ້ນ.',
|
||||
accountModelsDuplicate: 'ພົບໂມເດວທີ່ຊ້ຳກັນໃນລາຍການ',
|
||||
modelPool: 'ກຸ່ມໂມເດວ (Model Pool)',
|
||||
ollamaRegisterOnly: 'ລົງທະບຽນສະເພາະໂມເດວທີ່ມີຢູ່ໃນບໍລິການ Ollama ນີ້ແລ້ວ. ຈະບໍ່ເລີ່ມດາວໂຫຼດ.',
|
||||
deleteModelFiles: 'ລຶບໄຟລ໌ໂມເດວອອກຈາກບໍລິການ Ollama ນຳ',
|
||||
fallbackModels: 'ໂມເດວສຳຮອງ (Fallback)',
|
||||
addFallbackModel: 'ເພີ່ມໂມເດວສຳຮອງ',
|
||||
fallbackModelsEmpty: 'ຍັງບໍ່ໄດ້ຕັ້ງຄ່າໂມເດວສຳຮອງ',
|
||||
|
||||
@@ -853,6 +853,9 @@ const message = {
|
||||
discoverModelsFailedUseDefaults: 'Gagal mendapatkan model. Model lalai sedang digunakan.',
|
||||
accountModelsDuplicate: 'Terdapat model pendua dalam katalog',
|
||||
modelPool: 'Katalog model',
|
||||
ollamaRegisterOnly:
|
||||
'Daftar hanya model yang sudah tersedia pada perkhidmatan Ollama ini. Tiada muat turun akan dimulakan.',
|
||||
deleteModelFiles: 'Padam juga fail model daripada perkhidmatan Ollama',
|
||||
fallbackModels: 'Model fallback',
|
||||
addFallbackModel: 'Tambah model fallback',
|
||||
fallbackModelsEmpty: 'Tiada model fallback dikonfigurasikan',
|
||||
|
||||
@@ -846,6 +846,9 @@ const message = {
|
||||
discoverModelsFailedUseDefaults: 'Não foi possível obter os modelos. Os modelos padrão estão em uso.',
|
||||
accountModelsDuplicate: 'Há modelos duplicados no catálogo',
|
||||
modelPool: 'Catálogo de modelos',
|
||||
ollamaRegisterOnly:
|
||||
'Registre apenas modelos já disponíveis neste serviço Ollama. Nenhum download será iniciado.',
|
||||
deleteModelFiles: 'Excluir também os arquivos do modelo do serviço Ollama',
|
||||
fallbackModels: 'Modelos de fallback',
|
||||
addFallbackModel: 'Adicionar modelo de fallback',
|
||||
fallbackModelsEmpty: 'Nenhum modelo de fallback configurado',
|
||||
|
||||
@@ -841,6 +841,9 @@ const message = {
|
||||
discoverModelsFailedUseDefaults: 'Не удалось получить модели. Используются модели по умолчанию.',
|
||||
accountModelsDuplicate: 'В каталоге обнаружены повторяющиеся модели',
|
||||
modelPool: 'Каталог моделей',
|
||||
ollamaRegisterOnly:
|
||||
'Регистрируются только модели, уже доступные в этом сервисе Ollama. Загрузка не запускается.',
|
||||
deleteModelFiles: 'Также удалить файлы модели из сервиса Ollama',
|
||||
fallbackModels: 'Резервные модели',
|
||||
addFallbackModel: 'Добавить резервную модель',
|
||||
fallbackModelsEmpty: 'Резервные модели не настроены',
|
||||
|
||||
@@ -849,6 +849,8 @@ const message = {
|
||||
discoverModelsFailedUseDefaults: 'Modeller alınamadı. Varsayılan modeller kullanılıyor.',
|
||||
accountModelsDuplicate: 'Katalogda yinelenen modeller bulundu',
|
||||
modelPool: 'Model kataloğu',
|
||||
ollamaRegisterOnly: 'Yalnızca bu Ollama hizmetinde mevcut modeller kaydedilir. İndirme başlatılmaz.',
|
||||
deleteModelFiles: 'Model dosyalarını da Ollama hizmetinden sil',
|
||||
fallbackModels: 'Yedek modeller',
|
||||
addFallbackModel: 'Yedek model ekle',
|
||||
fallbackModelsEmpty: 'Yapılandırılmış yedek model yok',
|
||||
|
||||
@@ -804,6 +804,8 @@ const message = {
|
||||
discoverModelsFailedUseDefaults: '無法取得模型,已改用預設模型',
|
||||
accountModelsDuplicate: '模型清單中有重複項目,請檢查後再試一次',
|
||||
modelPool: '模型池',
|
||||
ollamaRegisterOnly: '僅登記此 Ollama 服務上已存在的模型,不會觸發下載。',
|
||||
deleteModelFiles: '同時刪除 Ollama 服務上的模型檔案',
|
||||
fallbackModels: '備用模型',
|
||||
addFallbackModel: '新增備用模型',
|
||||
fallbackModelsEmpty: '尚未設定備用模型',
|
||||
|
||||
@@ -821,6 +821,8 @@ const message = {
|
||||
discoverModelsFailedUseDefaults: '获取模型失败,已使用默认模型',
|
||||
accountModelsDuplicate: '模型池中存在重复模型,请检查后重试',
|
||||
modelPool: '模型池',
|
||||
ollamaRegisterOnly: '仅登记此 Ollama 服务上已存在的模型,不会触发下载。',
|
||||
deleteModelFiles: '同时删除 Ollama 服务上的模型文件',
|
||||
fallbackModels: '备用模型',
|
||||
addFallbackModel: '新增备用模型',
|
||||
fallbackModelsEmpty: '尚未配置备用模型',
|
||||
|
||||
@@ -26,10 +26,13 @@
|
||||
</template>
|
||||
</DrawerPro>
|
||||
|
||||
<DrawerPro v-model="editorOpen" :header="editorTitle" size="normal" @close="handleEditorClose">
|
||||
<el-form ref="formRef" :model="form" label-position="top" :rules="rules" v-loading="saving">
|
||||
<DialogPro v-model="editorOpen" :title="editorTitle" size="normal" @close="handleEditorClose">
|
||||
<el-form ref="formRef" :model="form" label-position="top" class="mt-5" :rules="rules" v-loading="saving">
|
||||
<el-form-item :label="$t('aiTools.model.model')" prop="id">
|
||||
<el-input v-model="form.id" clearable />
|
||||
<span v-if="account.provider === 'ollama'" class="input-help">
|
||||
{{ $t('aiTools.agents.ollamaRegisterOnly') }}
|
||||
</span>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('commons.table.name')" prop="name">
|
||||
<el-input v-model="form.name" />
|
||||
@@ -39,17 +42,28 @@
|
||||
<span class="dialog-footer">
|
||||
<el-button :disabled="saving" @click="editorOpen = false">{{ $t('commons.button.cancel') }}</el-button>
|
||||
<el-button v-permission :disabled="saving" type="primary" @click="submit">
|
||||
{{ $t('commons.button.confirm') }}
|
||||
{{ form.recordId ? $t('commons.button.confirm') : $t('commons.button.add') }}
|
||||
</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</DrawerPro>
|
||||
</DialogPro>
|
||||
<OllamaAdd ref="ollamaAddRef" :account-id="account.id" @log="onPullLog" />
|
||||
<TaskLog ref="taskLogRef" width="70%" @close="refresh" />
|
||||
<OpDialog ref="deleteRef" @search="refresh">
|
||||
<template #content>
|
||||
<el-checkbox v-if="account.provider === 'ollama'" v-model="deleteFiles" class="mt-4">
|
||||
{{ $t('aiTools.agents.deleteModelFiles') }}
|
||||
</el-checkbox>
|
||||
</template>
|
||||
</OpDialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, reactive, ref } from 'vue';
|
||||
import type { FormInstance } from 'element-plus';
|
||||
import { ElMessageBox } from 'element-plus';
|
||||
import OllamaAdd from '@/views/ai/model/ollama/add/index.vue';
|
||||
import TaskLog from '@/components/log/task/index.vue';
|
||||
import OpDialog from '@/components/del-dialog/index.vue';
|
||||
import {
|
||||
createAgentAccountModel,
|
||||
deleteAgentAccountModel,
|
||||
@@ -71,11 +85,17 @@ const loading = ref(false);
|
||||
const saving = ref(false);
|
||||
const formRef = ref<FormInstance>();
|
||||
const items = ref<AI.AgentAccountModel[]>([]);
|
||||
const ollamaAddRef = ref();
|
||||
const taskLogRef = ref();
|
||||
const deleteRef = ref();
|
||||
const deleteFiles = ref(false);
|
||||
const localOllama = computed(() => account.provider === 'ollama' && account.appInstallId > 0);
|
||||
|
||||
const account = reactive({
|
||||
id: 0,
|
||||
name: '',
|
||||
provider: '',
|
||||
appInstallId: 0,
|
||||
});
|
||||
|
||||
const form = reactive<AI.AgentAccountModel>({
|
||||
@@ -102,6 +122,7 @@ const buttons = [
|
||||
label: i18n.global.t('commons.button.edit'),
|
||||
permission: true,
|
||||
click: (row: AI.AgentAccountModel) => openEdit(row),
|
||||
show: () => !localOllama.value,
|
||||
},
|
||||
{
|
||||
label: i18n.global.t('commons.button.delete'),
|
||||
@@ -137,6 +158,10 @@ const resetForm = () => {
|
||||
};
|
||||
|
||||
const openCreate = () => {
|
||||
if (localOllama.value) {
|
||||
ollamaAddRef.value.acceptParams();
|
||||
return;
|
||||
}
|
||||
resetForm();
|
||||
editorOpen.value = true;
|
||||
};
|
||||
@@ -179,23 +204,31 @@ const submit = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
const onDelete = async (row: AI.AgentAccountModel) => {
|
||||
await ElMessageBox.confirm(
|
||||
i18n.global.t('commons.msg.delete', [row.name || row.id]),
|
||||
i18n.global.t('commons.button.delete'),
|
||||
{
|
||||
confirmButtonText: i18n.global.t('commons.button.confirm'),
|
||||
cancelButtonText: i18n.global.t('commons.button.cancel'),
|
||||
},
|
||||
);
|
||||
await deleteAgentAccountModel({
|
||||
accountId: account.id,
|
||||
recordId: row.recordId,
|
||||
});
|
||||
const refresh = async () => {
|
||||
await search();
|
||||
emit('updated');
|
||||
};
|
||||
|
||||
const onPullLog = (row: { taskID: string }) => {
|
||||
taskLogRef.value.openWithTaskID(row.taskID);
|
||||
};
|
||||
|
||||
const onDelete = (row: AI.AgentAccountModel) => {
|
||||
deleteFiles.value = false;
|
||||
deleteRef.value.acceptParams({
|
||||
title: i18n.global.t('commons.button.delete'),
|
||||
names: [row.name || row.id],
|
||||
msg: i18n.global.t('commons.msg.delete', [row.name || row.id]),
|
||||
api: () =>
|
||||
deleteAgentAccountModel({
|
||||
accountId: account.id,
|
||||
recordId: row.recordId,
|
||||
deleteFiles: deleteFiles.value,
|
||||
}),
|
||||
params: {},
|
||||
});
|
||||
};
|
||||
|
||||
const handleEditorClose = () => {
|
||||
formRef.value?.resetFields();
|
||||
resetForm();
|
||||
@@ -207,13 +240,15 @@ const handleClose = () => {
|
||||
account.id = 0;
|
||||
account.name = '';
|
||||
account.provider = '';
|
||||
account.appInstallId = 0;
|
||||
items.value = [];
|
||||
editorOpen.value = false;
|
||||
handleEditorClose();
|
||||
};
|
||||
|
||||
const openDrawer = async (row: AI.AgentAccountItem) => {
|
||||
readOnly.value = Boolean(row.appInstallId);
|
||||
readOnly.value = Boolean(row.appInstallId) && row.provider !== 'ollama';
|
||||
account.appInstallId = row.appInstallId || 0;
|
||||
account.id = row.id;
|
||||
account.name = row.name;
|
||||
account.provider = row.provider;
|
||||
|
||||
@@ -41,8 +41,9 @@ import { Rules } from '@/global/form-rules';
|
||||
import i18n from '@/lang';
|
||||
import { ElForm } from 'element-plus';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
import { createOllamaModel } from '@/api/modules/ai';
|
||||
import { createAgentAccountModel, createOllamaModel } from '@/api/modules/ai';
|
||||
import { newUUID } from '@/utils/id';
|
||||
const props = defineProps<{ accountId?: number }>();
|
||||
const drawerVisible = ref(false);
|
||||
const form = reactive({
|
||||
name: '',
|
||||
@@ -63,7 +64,14 @@ const onSubmit = async (formEl: FormInstance | undefined) => {
|
||||
formEl.validate(async (valid) => {
|
||||
if (!valid) return;
|
||||
let itemName = form.name.replaceAll('ollama run ', '').replaceAll('ollama pull ', '');
|
||||
await createOllamaModel(itemName, taskID).then(() => {
|
||||
const request = props.accountId
|
||||
? createAgentAccountModel({
|
||||
accountId: props.accountId,
|
||||
model: { recordId: 0, id: itemName, name: itemName },
|
||||
taskID,
|
||||
})
|
||||
: createOllamaModel(itemName, taskID);
|
||||
await request.then(() => {
|
||||
drawerVisible.value = false;
|
||||
emit('search');
|
||||
emit('log', { logFileExist: true, name: itemName, from: 'local', taskID: taskID });
|
||||
|
||||
Reference in New Issue
Block a user