feat(ai): streamline Ollama model pool management (#14259)

This commit is contained in:
ssongliu
2026-10-30 23:50:11 +08:00
committed by GitHub
parent 9a49f38ffd
commit 93ebc6a8b9
20 changed files with 305 additions and 41 deletions
+4 -2
View File
@@ -318,6 +318,7 @@ type AgentAccountModelDiscoverReq struct {
}
type AgentAccountModelCreateReq struct {
TaskID string `json:"taskID"`
AccountID uint `json:"accountId" validate:"required"`
Model AgentAccountModel `json:"model" validate:"required"`
}
@@ -328,8 +329,9 @@ type AgentAccountModelUpdateReq struct {
}
type AgentAccountModelDeleteReq struct {
AccountID uint `json:"accountId" validate:"required"`
RecordID uint `json:"recordId" validate:"required"`
DeleteFiles bool `json:"deleteFiles"`
AccountID uint `json:"accountId" validate:"required"`
RecordID uint `json:"recordId" validate:"required"`
}
type AgentAccountCreateReq struct {
+76
View File
@@ -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
}
+98
View File
@@ -1,13 +1,19 @@
package service
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"net/http"
"strings"
"time"
"github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/1Panel-dev/1Panel/agent/app/model"
providercatalog "github.com/1Panel-dev/1Panel/agent/app/provider"
"github.com/1Panel-dev/1Panel/agent/app/repo"
"github.com/1Panel-dev/1Panel/agent/buserr"
"github.com/1Panel-dev/1Panel/agent/constant"
"github.com/1Panel-dev/1Panel/agent/global"
terminalai "github.com/1Panel-dev/1Panel/agent/utils/terminal/ai"
@@ -136,6 +142,62 @@ func syncOllamaModelAccount(install model.AppInstall, baseURL string, updateBase
return err
}
func updateOllamaModelPool(modelName string, remove bool) error {
install, err := appInstallRepo.LoadInstallAppByKeyAndName("ollama", "")
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil
}
if err != nil {
return err
}
if !install.SyncModelAccount {
return nil
}
agentAccountMutationMu.Lock()
defer agentAccountMutationMu.Unlock()
account, err := agentAccountRepo.GetFirst(repo.WithByAppInstallID(install.ID), repo.WithByProvider("ollama"))
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil
}
if err != nil {
return err
}
models, err := loadAgentAccountModels(account)
if err != nil {
return err
}
if remove {
for _, item := range models {
if !sameProviderModelID("ollama", item.ID, modelName) {
continue
}
if err := agentAccountModelRepo.DeleteByID(item.RecordID); err != nil {
return err
}
}
if sameProviderModelID("ollama", account.VerifyModel, modelName) {
account.VerifyModel = ""
if err := agentAccountRepo.Save(account); err != nil {
return err
}
}
if err := compactPersistedAgentAccountModelSortOrder(account.ID); err != nil {
return err
}
} else {
if _, exists := findAgentAccountModelForProvider("ollama", models, modelName); exists {
return nil
}
record := &model.AgentAccountModel{AccountID: account.ID, Model: modelName, Name: modelName, SortOrder: len(models) + 1}
if err := agentAccountModelRepo.Create(record); err != nil {
return err
}
}
terminalai.InvalidateTerminalRuntimeCache()
terminalai.InvalidateFileAIRuntimeCache()
return nil
}
func loadOllamaModelAccountAddress(install model.AppInstall) (string, string, error) {
addressType := install.ModelAccountBaseURLType
if addressType == "" {
@@ -200,3 +262,39 @@ func ResolveLocalModelAccountBaseURL(addressType, customURL, containerName, cont
return fmt.Sprintf("http://%s:%s/v1", containerName, containerPort), nil
}
}
func deleteRemoteOllamaModel(account *model.AgentAccount, modelName string) error {
body, err := json.Marshal(map[string]string{"model": modelName})
if err != nil {
return err
}
baseURL := strings.TrimSuffix(strings.TrimRight(account.BaseURL, "/"), "/v1")
req, err := http.NewRequest(http.MethodDelete, baseURL+"/api/delete", bytes.NewReader(body))
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+account.APIKey)
resp, err := (&http.Client{Timeout: 30 * time.Second}).Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusMultipleChoices {
return fmt.Errorf("delete Ollama model failed: %s", resp.Status)
}
return nil
}
func resolveExistingOllamaModel(account *model.AgentAccount, modelID string) (string, error) {
available, err := providercatalog.DiscoverModels(account.BaseURL, account.APIKey)
if err != nil {
return "", buserr.WithErr("ErrAgentAccountUnavailable", err)
}
for _, id := range available {
if sameProviderModelID("ollama", id, modelID) {
return id, nil
}
}
return "", buserr.New("ErrRecordNotFound")
}
+8
View File
@@ -1517,6 +1517,14 @@ func sameProviderModelID(provider, left, right string) bool {
}
leftComparable := providercatalog.NormalizeModelID(provider, leftTrimmed)
rightComparable := providercatalog.NormalizeModelID(provider, rightTrimmed)
if provider == "ollama" {
if leftComparable != "" && !strings.Contains(path.Base(leftComparable), ":") {
leftComparable += ":latest"
}
if rightComparable != "" && !strings.Contains(path.Base(rightComparable), ":") {
rightComparable += ":latest"
}
}
return leftComparable != "" && leftComparable == rightComparable
}
+24 -18
View File
@@ -117,6 +117,7 @@ func (u *AIToolService) Create(req dto.OllamaModelName) error {
}
taskItem, err := task.NewTaskWithOps(fmt.Sprintf("ollama-model-%s", req.Name), task.TaskPull, task.TaskScopeAI, req.TaskID, info.ID)
if err != nil {
_ = aiRepo.Update(info.ID, map[string]interface{}{"status": constant.StatusFailed, "message": err.Error()})
global.LOG.Errorf("new task for exec shell failed, err: %v", err)
return err
}
@@ -126,19 +127,17 @@ func (u *AIToolService) Create(req dto.OllamaModelName) error {
}, nil)
taskItem.AddSubTask(i18n.GetWithName("OllamaModelSize", req.Name), func(t *task.Task) error {
itemSize, err := loadModelSize(info.Name, containerName)
if len(itemSize) != 0 {
_ = aiRepo.Update(info.ID, map[string]interface{}{"status": constant.StatusSuccess, "size": itemSize})
} else {
_ = aiRepo.Update(info.ID, map[string]interface{}{"status": constant.StatusFailed, "message": err.Error()})
if err != nil {
return err
}
return nil
return aiRepo.Update(info.ID, map[string]interface{}{"status": constant.StatusSuccess, "size": itemSize})
}, nil)
taskItem.AddSubTask(i18n.GetMsgByKey("TaskSync"), func(t *task.Task) error {
return updateOllamaModelPool(info.Name, false)
}, nil)
if err := taskItem.Execute(); err != nil {
_ = aiRepo.Update(info.ID, map[string]interface{}{"status": constant.StatusFailed, "message": err.Error()})
}
if err := SyncOllamaModelAccount(false); err != nil {
global.LOG.Errorf("sync Ollama model account after pull: %v", err)
}
}()
return nil
}
@@ -167,6 +166,9 @@ func (u *AIToolService) Recreate(req dto.OllamaModelName) error {
if modelInfo.ID == 0 {
return buserr.New("ErrRecordNotFound")
}
if modelInfo.Status == constant.StatusWaiting {
return buserr.New("TaskIsExecuting")
}
containerName, err := LoadContainerName()
if err != nil {
return err
@@ -176,6 +178,7 @@ func (u *AIToolService) Recreate(req dto.OllamaModelName) error {
}
taskItem, err := task.NewTaskWithOps(fmt.Sprintf("ollama-model-%s", req.Name), task.TaskPull, task.TaskScopeAI, req.TaskID, modelInfo.ID)
if err != nil {
_ = aiRepo.Update(modelInfo.ID, map[string]interface{}{"status": constant.StatusFailed, "message": err.Error()})
global.LOG.Errorf("new task for exec shell failed, err: %v", err)
return err
}
@@ -185,19 +188,17 @@ func (u *AIToolService) Recreate(req dto.OllamaModelName) error {
}, nil)
taskItem.AddSubTask(i18n.GetWithName("OllamaModelSize", req.Name), func(t *task.Task) error {
itemSize, err := loadModelSize(modelInfo.Name, containerName)
if len(itemSize) != 0 {
_ = aiRepo.Update(modelInfo.ID, map[string]interface{}{"status": constant.StatusSuccess, "size": itemSize})
} else {
_ = aiRepo.Update(modelInfo.ID, map[string]interface{}{"status": constant.StatusFailed, "message": err.Error()})
if err != nil {
return err
}
return nil
return aiRepo.Update(modelInfo.ID, map[string]interface{}{"status": constant.StatusSuccess, "size": itemSize})
}, nil)
taskItem.AddSubTask(i18n.GetMsgByKey("TaskSync"), func(t *task.Task) error {
return updateOllamaModelPool(modelInfo.Name, false)
}, nil)
if err := taskItem.Execute(); err != nil {
_ = aiRepo.Update(modelInfo.ID, map[string]interface{}{"status": constant.StatusFailed, "message": err.Error()})
}
if err := SyncOllamaModelAccount(false); err != nil {
global.LOG.Errorf("sync Ollama model account after pull: %v", err)
}
}()
return nil
}
@@ -219,11 +220,16 @@ func (u *AIToolService) Delete(req dto.ForceDelete) error {
return fmt.Errorf("handle ollama rm %s failed, %v", item.Name, err)
}
}
_ = aiRepo.Delete(repo.WithByID(item.ID))
if err := aiRepo.Delete(repo.WithByID(item.ID)); err != nil {
return err
}
if err := updateOllamaModelPool(item.Name, true); err != nil {
return err
}
logItem := path.Join(global.Dir.DataDir, "log", "AITools", item.Name)
_ = os.Remove(logItem)
}
return SyncOllamaModelAccount(false)
return nil
}
func (u *AIToolService) SyncAccount(req dto.OllamaModelAccountSync) error {
+2
View File
@@ -677,6 +677,7 @@ export namespace AI {
}
export interface AgentAccountModelCreateReq {
taskID?: string;
accountId: number;
model: AgentAccountModel;
}
@@ -687,6 +688,7 @@ export namespace AI {
}
export interface AgentAccountModelDeleteReq {
deleteFiles?: boolean;
accountId: number;
recordId: number;
}
+3
View File
@@ -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',
+3
View File
@@ -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',
+2
View File
@@ -839,6 +839,8 @@ const message = {
discoverModelsFailedUseDefaults: 'دریافت مدل‌ها ناموفق بود. از مدل‌های پیش‌فرض استفاده می‌شود.',
accountModelsDuplicate: 'مدل‌های تکراری در کاتالوگ یافت شد',
modelPool: 'مجموعه مدل‌ها',
ollamaRegisterOnly: 'فقط مدل‌های موجود در این سرویس Ollama ثبت می‌شوند. دانلودی آغاز نخواهد شد.',
deleteModelFiles: 'فایل‌های مدل نیز از سرویس Ollama حذف شوند',
fallbackModels: 'مدل‌های جایگزین',
addFallbackModel: 'افزودن مدل جایگزین',
fallbackModelsEmpty: 'هیچ مدل جایگزینی پیکربندی نشده است',
+2
View File
@@ -843,6 +843,8 @@ const message = {
discoverModelsFailedUseDefaults: 'モデルを取得できませんでした。デフォルトモデルを使用しています。',
accountModelsDuplicate: 'カタログに重複したモデルがあります',
modelPool: 'モデルプール',
ollamaRegisterOnly: 'この Ollama サービスに存在するモデルのみを登録します。ダウンロードは開始されません。',
deleteModelFiles: 'Ollama サービス上のモデルファイルも削除する',
fallbackModels: 'フォールバックモデル',
addFallbackModel: 'フォールバックモデルを追加',
fallbackModelsEmpty: 'フォールバックモデルは未設定です',
+2
View File
@@ -833,6 +833,8 @@ const message = {
discoverModelsFailedUseDefaults: '모델을 가져오지 못했습니다. 기본 모델을 사용합니다.',
accountModelsDuplicate: '카탈로그에 중복된 모델이 있습니다',
modelPool: '모델 풀',
ollamaRegisterOnly: '이 Ollama 서비스에 이미 있는 모델만 등록합니다. 다운로드는 시작되지 않습니다.',
deleteModelFiles: 'Ollama 서비스의 모델 파일도 삭제',
fallbackModels: '폴백 모델',
addFallbackModel: '폴백 모델 추가',
fallbackModelsEmpty: '구성된 폴백 모델이 없습니다',
+2
View File
@@ -831,6 +831,8 @@ const message = {
discoverModelsFailedUseDefaults: 'ດຶງລາຍການໂມເດວບໍ່ສຳເລັດ. ກຳລັງໃຊ້ໂມເດວເລີ່ມຕົ້ນ.',
accountModelsDuplicate: 'ພົບໂມເດວທີ່ຊ້ຳກັນໃນລາຍການ',
modelPool: 'ກຸ່ມໂມເດວ (Model Pool)',
ollamaRegisterOnly: 'ລົງທະບຽນສະເພາະໂມເດວທີ່ມີຢູ່ໃນບໍລິການ Ollama ນີ້ແລ້ວ. ຈະບໍ່ເລີ່ມດາວໂຫຼດ.',
deleteModelFiles: 'ລຶບໄຟລ໌ໂມເດວອອກຈາກບໍລິການ Ollama ນຳ',
fallbackModels: 'ໂມເດວສຳຮອງ (Fallback)',
addFallbackModel: 'ເພີ່ມໂມເດວສຳຮອງ',
fallbackModelsEmpty: 'ຍັງບໍ່ໄດ້ຕັ້ງຄ່າໂມເດວສຳຮອງ',
+3
View File
@@ -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',
+3
View File
@@ -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',
+3
View File
@@ -841,6 +841,9 @@ const message = {
discoverModelsFailedUseDefaults: 'Не удалось получить модели. Используются модели по умолчанию.',
accountModelsDuplicate: 'В каталоге обнаружены повторяющиеся модели',
modelPool: 'Каталог моделей',
ollamaRegisterOnly:
'Регистрируются только модели, уже доступные в этом сервисе Ollama. Загрузка не запускается.',
deleteModelFiles: 'Также удалить файлы модели из сервиса Ollama',
fallbackModels: 'Резервные модели',
addFallbackModel: 'Добавить резервную модель',
fallbackModelsEmpty: 'Резервные модели не настроены',
+2
View File
@@ -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',
+2
View File
@@ -804,6 +804,8 @@ const message = {
discoverModelsFailedUseDefaults: '無法取得模型,已改用預設模型',
accountModelsDuplicate: '模型清單中有重複項目,請檢查後再試一次',
modelPool: '模型池',
ollamaRegisterOnly: '僅登記此 Ollama 服務上已存在的模型,不會觸發下載。',
deleteModelFiles: '同時刪除 Ollama 服務上的模型檔案',
fallbackModels: '備用模型',
addFallbackModel: '新增備用模型',
fallbackModelsEmpty: '尚未設定備用模型',
+2
View File
@@ -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 });