mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-03-24 14:42:11 -03:00
feat: add model updates check to global context menu
Add a new "Check Model Updates" option to the global context menu that allows users to manually check for model updates. This includes: - Adding refreshUpdates endpoint to API configuration - Implementing checkModelUpdates method with proper loading states - Adding internationalization support for update messages - Handling success/error states with appropriate user feedback - Automatically reloading models after update check completes The feature provides users with manual control over update checks and improves visibility into model update availability.
This commit is contained in:
@@ -139,6 +139,13 @@
|
|||||||
"missingPath": "Bitte legen Sie einen Speicherort fest, bevor Sie Beispielbilder herunterladen.",
|
"missingPath": "Bitte legen Sie einen Speicherort fest, bevor Sie Beispielbilder herunterladen.",
|
||||||
"unavailable": "Beispielbild-Downloads sind noch nicht verfügbar. Versuchen Sie es erneut, nachdem die Seite vollständig geladen ist."
|
"unavailable": "Beispielbild-Downloads sind noch nicht verfügbar. Versuchen Sie es erneut, nachdem die Seite vollständig geladen ist."
|
||||||
},
|
},
|
||||||
|
"checkModelUpdates": {
|
||||||
|
"label": "Auf Updates prüfen",
|
||||||
|
"loading": "Prüfe auf {type}-Updates...",
|
||||||
|
"success": "{count} Update(s) für {type} gefunden",
|
||||||
|
"none": "Alle {type} sind auf dem neuesten Stand",
|
||||||
|
"error": "Fehler beim Prüfen auf {type}-Updates: {message}"
|
||||||
|
},
|
||||||
"cleanupExampleImages": {
|
"cleanupExampleImages": {
|
||||||
"label": "Beispielbild-Ordner bereinigen",
|
"label": "Beispielbild-Ordner bereinigen",
|
||||||
"success": "{count} Ordner wurden in den Papierkorb verschoben",
|
"success": "{count} Ordner wurden in den Papierkorb verschoben",
|
||||||
|
|||||||
@@ -139,6 +139,13 @@
|
|||||||
"missingPath": "Set a download location before downloading example images.",
|
"missingPath": "Set a download location before downloading example images.",
|
||||||
"unavailable": "Example image downloads aren't available yet. Try again after the page finishes loading."
|
"unavailable": "Example image downloads aren't available yet. Try again after the page finishes loading."
|
||||||
},
|
},
|
||||||
|
"checkModelUpdates": {
|
||||||
|
"label": "Check for updates",
|
||||||
|
"loading": "Checking for {type} updates...",
|
||||||
|
"success": "Found {count} update(s) for {type}s",
|
||||||
|
"none": "All {type}s are up to date",
|
||||||
|
"error": "Failed to check for {type} updates: {message}"
|
||||||
|
},
|
||||||
"cleanupExampleImages": {
|
"cleanupExampleImages": {
|
||||||
"label": "Clean up example image folders",
|
"label": "Clean up example image folders",
|
||||||
"success": "Moved {count} folder(s) to the deleted folder",
|
"success": "Moved {count} folder(s) to the deleted folder",
|
||||||
|
|||||||
@@ -139,6 +139,13 @@
|
|||||||
"missingPath": "Establece una ubicación de descarga antes de descargar imágenes de ejemplo.",
|
"missingPath": "Establece una ubicación de descarga antes de descargar imágenes de ejemplo.",
|
||||||
"unavailable": "Las descargas de imágenes de ejemplo aún no están disponibles. Intenta de nuevo después de que la página termine de cargar."
|
"unavailable": "Las descargas de imágenes de ejemplo aún no están disponibles. Intenta de nuevo después de que la página termine de cargar."
|
||||||
},
|
},
|
||||||
|
"checkModelUpdates": {
|
||||||
|
"label": "Buscar actualizaciones",
|
||||||
|
"loading": "Buscando actualizaciones de {type}...",
|
||||||
|
"success": "Se encontraron {count} actualización(es) para {type}",
|
||||||
|
"none": "Todos los {type} están actualizados",
|
||||||
|
"error": "Error al buscar actualizaciones de {type}: {message}"
|
||||||
|
},
|
||||||
"cleanupExampleImages": {
|
"cleanupExampleImages": {
|
||||||
"label": "Limpiar carpetas de imágenes de ejemplo",
|
"label": "Limpiar carpetas de imágenes de ejemplo",
|
||||||
"success": "Se movieron {count} carpeta(s) a la carpeta de eliminados",
|
"success": "Se movieron {count} carpeta(s) a la carpeta de eliminados",
|
||||||
|
|||||||
@@ -139,6 +139,13 @@
|
|||||||
"missingPath": "Définissez un emplacement de téléchargement avant de télécharger les images d'exemple.",
|
"missingPath": "Définissez un emplacement de téléchargement avant de télécharger les images d'exemple.",
|
||||||
"unavailable": "Le téléchargement des images d'exemple n'est pas encore disponible. Réessayez après le chargement complet de la page."
|
"unavailable": "Le téléchargement des images d'exemple n'est pas encore disponible. Réessayez après le chargement complet de la page."
|
||||||
},
|
},
|
||||||
|
"checkModelUpdates": {
|
||||||
|
"label": "Vérifier les mises à jour",
|
||||||
|
"loading": "Recherche de mises à jour pour {type}...",
|
||||||
|
"success": "{count} mise(s) à jour trouvée(s) pour {type}",
|
||||||
|
"none": "Tous les {type} sont à jour",
|
||||||
|
"error": "Échec de la vérification des mises à jour pour {type} : {message}"
|
||||||
|
},
|
||||||
"cleanupExampleImages": {
|
"cleanupExampleImages": {
|
||||||
"label": "Supprimer les dossiers d'exemples orphelins",
|
"label": "Supprimer les dossiers d'exemples orphelins",
|
||||||
"success": "{count} dossier(s) déplacé(s) vers le dossier supprimé",
|
"success": "{count} dossier(s) déplacé(s) vers le dossier supprimé",
|
||||||
|
|||||||
@@ -139,6 +139,13 @@
|
|||||||
"missingPath": "הגדר מיקום הורדה לפני הורדת תמונות דוגמה.",
|
"missingPath": "הגדר מיקום הורדה לפני הורדת תמונות דוגמה.",
|
||||||
"unavailable": "הורדות תמונות דוגמה אינן זמינות עדיין. נסה שוב לאחר שהדף מסיים להיטען."
|
"unavailable": "הורדות תמונות דוגמה אינן זמינות עדיין. נסה שוב לאחר שהדף מסיים להיטען."
|
||||||
},
|
},
|
||||||
|
"checkModelUpdates": {
|
||||||
|
"label": "בדוק עדכונים",
|
||||||
|
"loading": "בודק עדכונים עבור {type}...",
|
||||||
|
"success": "נמצאו {count} עדכונים עבור {type}",
|
||||||
|
"none": "כל ה-{type} מעודכנים",
|
||||||
|
"error": "נכשל בבדיקת העדכונים עבור {type}: {message}"
|
||||||
|
},
|
||||||
"cleanupExampleImages": {
|
"cleanupExampleImages": {
|
||||||
"label": "נקה תיקיות תמונות דוגמה",
|
"label": "נקה תיקיות תמונות דוגמה",
|
||||||
"success": "הועברו {count} תיקיות לתיקיית המחוקים",
|
"success": "הועברו {count} תיקיות לתיקיית המחוקים",
|
||||||
|
|||||||
@@ -139,6 +139,13 @@
|
|||||||
"missingPath": "例画像をダウンロードする前にダウンロード場所を設定してください。",
|
"missingPath": "例画像をダウンロードする前にダウンロード場所を設定してください。",
|
||||||
"unavailable": "例画像のダウンロードはまだ利用できません。ページの読み込みが完了してから再度お試しください。"
|
"unavailable": "例画像のダウンロードはまだ利用できません。ページの読み込みが完了してから再度お試しください。"
|
||||||
},
|
},
|
||||||
|
"checkModelUpdates": {
|
||||||
|
"label": "アップデートを確認",
|
||||||
|
"loading": "{type} のアップデートを確認中…",
|
||||||
|
"success": "{type} のアップデートが {count} 件見つかりました",
|
||||||
|
"none": "すべての {type} は最新です",
|
||||||
|
"error": "{type} のアップデート確認に失敗しました: {message}"
|
||||||
|
},
|
||||||
"cleanupExampleImages": {
|
"cleanupExampleImages": {
|
||||||
"label": "例画像フォルダをクリーンアップ",
|
"label": "例画像フォルダをクリーンアップ",
|
||||||
"success": "{count} 個のフォルダを削除フォルダに移動しました",
|
"success": "{count} 個のフォルダを削除フォルダに移動しました",
|
||||||
|
|||||||
@@ -139,6 +139,13 @@
|
|||||||
"missingPath": "예시 이미지를 다운로드하기 전에 다운로드 위치를 설정하세요.",
|
"missingPath": "예시 이미지를 다운로드하기 전에 다운로드 위치를 설정하세요.",
|
||||||
"unavailable": "예시 이미지 다운로드는 아직 사용할 수 없습니다. 페이지 로딩이 완료된 후 다시 시도하세요."
|
"unavailable": "예시 이미지 다운로드는 아직 사용할 수 없습니다. 페이지 로딩이 완료된 후 다시 시도하세요."
|
||||||
},
|
},
|
||||||
|
"checkModelUpdates": {
|
||||||
|
"label": "업데이트 확인",
|
||||||
|
"loading": "{type} 업데이트를 확인 중...",
|
||||||
|
"success": "{type} 업데이트 {count}개를 찾았습니다",
|
||||||
|
"none": "모든 {type}가 최신 상태입니다",
|
||||||
|
"error": "{type} 업데이트 확인 실패: {message}"
|
||||||
|
},
|
||||||
"cleanupExampleImages": {
|
"cleanupExampleImages": {
|
||||||
"label": "예시 이미지 폴더 정리",
|
"label": "예시 이미지 폴더 정리",
|
||||||
"success": "{count}개의 폴더가 삭제 폴더로 이동되었습니다",
|
"success": "{count}개의 폴더가 삭제 폴더로 이동되었습니다",
|
||||||
|
|||||||
@@ -139,6 +139,13 @@
|
|||||||
"missingPath": "Укажите место загрузки перед загрузкой примеров изображений.",
|
"missingPath": "Укажите место загрузки перед загрузкой примеров изображений.",
|
||||||
"unavailable": "Загрузка примеров изображений пока недоступна. Попробуйте снова после полной загрузки страницы."
|
"unavailable": "Загрузка примеров изображений пока недоступна. Попробуйте снова после полной загрузки страницы."
|
||||||
},
|
},
|
||||||
|
"checkModelUpdates": {
|
||||||
|
"label": "Проверить обновления",
|
||||||
|
"loading": "Проверка обновлений для {type}...",
|
||||||
|
"success": "Найдено {count} обновлений для {type}",
|
||||||
|
"none": "Все {type} актуальны",
|
||||||
|
"error": "Не удалось проверить обновления для {type}: {message}"
|
||||||
|
},
|
||||||
"cleanupExampleImages": {
|
"cleanupExampleImages": {
|
||||||
"label": "Очистить папки с примерами изображений",
|
"label": "Очистить папки с примерами изображений",
|
||||||
"success": "Перемещено {count} папок в папку удалённых",
|
"success": "Перемещено {count} папок в папку удалённых",
|
||||||
|
|||||||
@@ -139,6 +139,13 @@
|
|||||||
"missingPath": "请先设置下载位置后再下载示例图片。",
|
"missingPath": "请先设置下载位置后再下载示例图片。",
|
||||||
"unavailable": "示例图片下载当前不可用。请在页面加载完成后重试。"
|
"unavailable": "示例图片下载当前不可用。请在页面加载完成后重试。"
|
||||||
},
|
},
|
||||||
|
"checkModelUpdates": {
|
||||||
|
"label": "检查更新",
|
||||||
|
"loading": "正在检查 {type} 更新...",
|
||||||
|
"success": "找到 {count} 条 {type} 更新",
|
||||||
|
"none": "所有 {type} 均已是最新版本",
|
||||||
|
"error": "检查 {type} 更新失败:{message}"
|
||||||
|
},
|
||||||
"cleanupExampleImages": {
|
"cleanupExampleImages": {
|
||||||
"label": "清理示例图片文件夹",
|
"label": "清理示例图片文件夹",
|
||||||
"success": "已将 {count} 个文件夹移动到已删除文件夹",
|
"success": "已将 {count} 个文件夹移动到已删除文件夹",
|
||||||
|
|||||||
@@ -139,6 +139,13 @@
|
|||||||
"missingPath": "請先設定下載位置再下載範例圖片。",
|
"missingPath": "請先設定下載位置再下載範例圖片。",
|
||||||
"unavailable": "範例圖片下載目前尚不可用。請在頁面載入完成後再試一次。"
|
"unavailable": "範例圖片下載目前尚不可用。請在頁面載入完成後再試一次。"
|
||||||
},
|
},
|
||||||
|
"checkModelUpdates": {
|
||||||
|
"label": "檢查更新",
|
||||||
|
"loading": "正在檢查 {type} 更新...",
|
||||||
|
"success": "找到 {count} 個 {type} 更新",
|
||||||
|
"none": "所有 {type} 都是最新版本",
|
||||||
|
"error": "檢查 {type} 更新失敗:{message}"
|
||||||
|
},
|
||||||
"cleanupExampleImages": {
|
"cleanupExampleImages": {
|
||||||
"label": "清理範例圖片資料夾",
|
"label": "清理範例圖片資料夾",
|
||||||
"success": "已將 {count} 個資料夾移至已刪除資料夾",
|
"success": "已將 {count} 個資料夾移至已刪除資料夾",
|
||||||
|
|||||||
@@ -76,6 +76,7 @@ export function getApiEndpoints(modelType) {
|
|||||||
fetchAllCivitai: `/api/lm/${modelType}/fetch-all-civitai`,
|
fetchAllCivitai: `/api/lm/${modelType}/fetch-all-civitai`,
|
||||||
relinkCivitai: `/api/lm/${modelType}/relink-civitai`,
|
relinkCivitai: `/api/lm/${modelType}/relink-civitai`,
|
||||||
civitaiVersions: `/api/lm/${modelType}/civitai/versions`,
|
civitaiVersions: `/api/lm/${modelType}/civitai/versions`,
|
||||||
|
refreshUpdates: `/api/lm/${modelType}/updates/refresh`,
|
||||||
|
|
||||||
// Preview management
|
// Preview management
|
||||||
replacePreview: `/api/lm/${modelType}/replace-preview`,
|
replacePreview: `/api/lm/${modelType}/replace-preview`,
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
import { BaseContextMenu } from './BaseContextMenu.js';
|
import { BaseContextMenu } from './BaseContextMenu.js';
|
||||||
import { showToast } from '../../utils/uiHelpers.js';
|
import { showToast } from '../../utils/uiHelpers.js';
|
||||||
import { state } from '../../state/index.js';
|
import { state } from '../../state/index.js';
|
||||||
|
import { translate } from '../../utils/i18nHelpers.js';
|
||||||
|
import { getCompleteApiConfig, getCurrentModelType } from '../../api/apiConfig.js';
|
||||||
|
import { resetAndReload } from '../../api/modelApiFactory.js';
|
||||||
|
|
||||||
export class GlobalContextMenu extends BaseContextMenu {
|
export class GlobalContextMenu extends BaseContextMenu {
|
||||||
constructor() {
|
constructor() {
|
||||||
super('globalContextMenu');
|
super('globalContextMenu');
|
||||||
this._cleanupInProgress = false;
|
this._cleanupInProgress = false;
|
||||||
|
this._updateCheckInProgress = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
showMenu(x, y, origin = null) {
|
showMenu(x, y, origin = null) {
|
||||||
@@ -25,6 +29,11 @@ export class GlobalContextMenu extends BaseContextMenu {
|
|||||||
console.error('Failed to trigger example images download:', error);
|
console.error('Failed to trigger example images download:', error);
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
case 'check-model-updates':
|
||||||
|
this.checkModelUpdates(menuItem).catch((error) => {
|
||||||
|
console.error('Failed to check model updates:', error);
|
||||||
|
});
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
console.warn(`Unhandled global context menu action: ${action}`);
|
console.warn(`Unhandled global context menu action: ${action}`);
|
||||||
break;
|
break;
|
||||||
@@ -101,4 +110,74 @@ export class GlobalContextMenu extends BaseContextMenu {
|
|||||||
menuItem?.classList.remove('disabled');
|
menuItem?.classList.remove('disabled');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async checkModelUpdates(menuItem) {
|
||||||
|
if (this._updateCheckInProgress) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const modelType = getCurrentModelType();
|
||||||
|
const apiConfig = getCompleteApiConfig(modelType);
|
||||||
|
|
||||||
|
if (!apiConfig?.endpoints?.refreshUpdates) {
|
||||||
|
console.warn('Refresh updates endpoint not configured for model type:', modelType);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._updateCheckInProgress = true;
|
||||||
|
menuItem?.classList.add('disabled');
|
||||||
|
|
||||||
|
const displayName = apiConfig.config?.displayName ?? 'Model';
|
||||||
|
const loadingMessage = translate(
|
||||||
|
'globalContextMenu.checkModelUpdates.loading',
|
||||||
|
{ type: displayName },
|
||||||
|
`Checking for ${displayName} updates...`
|
||||||
|
);
|
||||||
|
|
||||||
|
state.loadingManager?.showSimpleLoading?.(loadingMessage);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(apiConfig.endpoints.refreshUpdates, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ force: false })
|
||||||
|
});
|
||||||
|
|
||||||
|
let payload = {};
|
||||||
|
try {
|
||||||
|
payload = await response.json();
|
||||||
|
} catch {
|
||||||
|
payload = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!response.ok || payload.success !== true) {
|
||||||
|
const errorMessage = payload?.error || response.statusText || 'Unknown error';
|
||||||
|
throw new Error(errorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
const records = Array.isArray(payload.records) ? payload.records : [];
|
||||||
|
|
||||||
|
if (records.length > 0) {
|
||||||
|
showToast('globalContextMenu.checkModelUpdates.success', { count: records.length, type: displayName }, 'success');
|
||||||
|
} else {
|
||||||
|
showToast('globalContextMenu.checkModelUpdates.none', { type: displayName }, 'info');
|
||||||
|
}
|
||||||
|
|
||||||
|
await resetAndReload(false);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error checking model updates:', error);
|
||||||
|
showToast(
|
||||||
|
'globalContextMenu.checkModelUpdates.error',
|
||||||
|
{ message: error?.message ?? 'Unknown error', type: displayName },
|
||||||
|
'error'
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
state.loadingManager?.hide?.();
|
||||||
|
if (typeof state.loadingManager?.restoreProgressBar === 'function') {
|
||||||
|
state.loadingManager.restoreProgressBar();
|
||||||
|
}
|
||||||
|
menuItem?.classList.remove('disabled');
|
||||||
|
this._updateCheckInProgress = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -87,6 +87,9 @@
|
|||||||
<div class="context-menu-item" data-action="download-example-images">
|
<div class="context-menu-item" data-action="download-example-images">
|
||||||
<i class="fas fa-download"></i> <span>{{ t('globalContextMenu.downloadExampleImages.label') }}</span>
|
<i class="fas fa-download"></i> <span>{{ t('globalContextMenu.downloadExampleImages.label') }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="context-menu-item" data-action="check-model-updates">
|
||||||
|
<i class="fas fa-sync-alt"></i> <span>{{ t('globalContextMenu.checkModelUpdates.label') }}</span>
|
||||||
|
</div>
|
||||||
<div class="context-menu-item" data-action="cleanup-example-images-folders">
|
<div class="context-menu-item" data-action="cleanup-example-images-folders">
|
||||||
<i class="fas fa-trash-restore"></i> <span>{{ t('globalContextMenu.cleanupExampleImages.label') }}</span>
|
<i class="fas fa-trash-restore"></i> <span>{{ t('globalContextMenu.cleanupExampleImages.label') }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { describe, it, beforeEach, afterEach, expect, vi } from 'vitest';
|
import { describe, it, beforeEach, afterEach, expect, vi } from 'vitest';
|
||||||
|
|
||||||
const showToastMock = vi.fn();
|
const showToastMock = vi.fn();
|
||||||
|
const translateMock = vi.fn((key, params, fallback) => (typeof fallback === 'string' ? fallback : key));
|
||||||
const copyToClipboardMock = vi.fn();
|
const copyToClipboardMock = vi.fn();
|
||||||
const getNSFWLevelNameMock = vi.fn((level) => {
|
const getNSFWLevelNameMock = vi.fn((level) => {
|
||||||
if (level >= 16) return 'XXX';
|
if (level >= 16) return 'XXX';
|
||||||
@@ -27,6 +28,7 @@ const loadingManagerStub = {
|
|||||||
showSimpleLoading: vi.fn(),
|
showSimpleLoading: vi.fn(),
|
||||||
hide: vi.fn(),
|
hide: vi.fn(),
|
||||||
show: vi.fn(),
|
show: vi.fn(),
|
||||||
|
restoreProgressBar: vi.fn(),
|
||||||
};
|
};
|
||||||
|
|
||||||
const stateStub = {
|
const stateStub = {
|
||||||
@@ -40,6 +42,11 @@ const downloadExampleImagesApiMock = vi.fn();
|
|||||||
const replaceModelPreviewMock = vi.fn();
|
const replaceModelPreviewMock = vi.fn();
|
||||||
const refreshSingleModelMetadataMock = vi.fn();
|
const refreshSingleModelMetadataMock = vi.fn();
|
||||||
const resetAndReloadMock = vi.fn();
|
const resetAndReloadMock = vi.fn();
|
||||||
|
const getCompleteApiConfigMock = vi.fn(() => ({
|
||||||
|
config: { displayName: 'LoRA' },
|
||||||
|
endpoints: { refreshUpdates: '/api/lm/loras/updates/refresh' },
|
||||||
|
}));
|
||||||
|
const getCurrentModelTypeMock = vi.fn(() => 'loras');
|
||||||
|
|
||||||
const getModelApiClientMock = vi.fn(() => ({
|
const getModelApiClientMock = vi.fn(() => ({
|
||||||
saveModelMetadata: saveModelMetadataMock,
|
saveModelMetadata: saveModelMetadataMock,
|
||||||
@@ -75,6 +82,16 @@ vi.mock('../../../static/js/api/modelApiFactory.js', () => ({
|
|||||||
resetAndReload: resetAndReloadMock,
|
resetAndReload: resetAndReloadMock,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
vi.mock('../../../static/js/api/apiConfig.js', () => ({
|
||||||
|
getCompleteApiConfig: getCompleteApiConfigMock,
|
||||||
|
getCurrentModelType: getCurrentModelTypeMock,
|
||||||
|
MODEL_TYPES: {
|
||||||
|
LORA: 'loras',
|
||||||
|
CHECKPOINT: 'checkpoints',
|
||||||
|
EMBEDDING: 'embeddings',
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
vi.mock('../../../static/js/state/index.js', () => ({
|
vi.mock('../../../static/js/state/index.js', () => ({
|
||||||
state: stateStub,
|
state: stateStub,
|
||||||
}));
|
}));
|
||||||
@@ -92,6 +109,10 @@ vi.mock('../../../static/js/api/recipeApi.js', () => ({
|
|||||||
updateRecipeMetadata: updateRecipeMetadataMock,
|
updateRecipeMetadata: updateRecipeMetadataMock,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
vi.mock('../../../static/js/utils/i18nHelpers.js', () => ({
|
||||||
|
translate: translateMock,
|
||||||
|
}));
|
||||||
|
|
||||||
async function flushAsyncTasks() {
|
async function flushAsyncTasks() {
|
||||||
await Promise.resolve();
|
await Promise.resolve();
|
||||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||||
@@ -106,6 +127,13 @@ describe('Interaction-level regression coverage', () => {
|
|||||||
saveModelMetadataMock.mockResolvedValue(undefined);
|
saveModelMetadataMock.mockResolvedValue(undefined);
|
||||||
downloadExampleImagesApiMock.mockResolvedValue(undefined);
|
downloadExampleImagesApiMock.mockResolvedValue(undefined);
|
||||||
updateRecipeMetadataMock.mockResolvedValue({ success: true });
|
updateRecipeMetadataMock.mockResolvedValue({ success: true });
|
||||||
|
resetAndReloadMock.mockResolvedValue(undefined);
|
||||||
|
getCompleteApiConfigMock.mockReturnValue({
|
||||||
|
config: { displayName: 'LoRA' },
|
||||||
|
endpoints: { refreshUpdates: '/api/lm/loras/updates/refresh' },
|
||||||
|
});
|
||||||
|
getCurrentModelTypeMock.mockReturnValue('loras');
|
||||||
|
translateMock.mockImplementation((key, params, fallback) => (typeof fallback === 'string' ? fallback : key));
|
||||||
global.modalManager = modalManagerMock;
|
global.modalManager = modalManagerMock;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -275,6 +303,7 @@ describe('Interaction-level regression coverage', () => {
|
|||||||
<div id="globalContextMenu" class="context-menu">
|
<div id="globalContextMenu" class="context-menu">
|
||||||
<div class="context-menu-item" data-action="download-example-images"></div>
|
<div class="context-menu-item" data-action="download-example-images"></div>
|
||||||
<div class="context-menu-item" data-action="cleanup-example-images-folders"></div>
|
<div class="context-menu-item" data-action="cleanup-example-images-folders"></div>
|
||||||
|
<div class="context-menu-item" data-action="check-model-updates"></div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -297,10 +326,15 @@ describe('Interaction-level regression coverage', () => {
|
|||||||
expect(downloadItem.classList.contains('disabled')).toBe(false);
|
expect(downloadItem.classList.contains('disabled')).toBe(false);
|
||||||
expect(document.getElementById('globalContextMenu').style.display).toBe('none');
|
expect(document.getElementById('globalContextMenu').style.display).toBe('none');
|
||||||
|
|
||||||
global.fetch = vi.fn().mockResolvedValue({
|
global.fetch = vi.fn()
|
||||||
ok: true,
|
.mockResolvedValueOnce({
|
||||||
json: async () => ({ success: true, moved_total: 2 }),
|
ok: true,
|
||||||
});
|
json: async () => ({ success: true, moved_total: 2 }),
|
||||||
|
})
|
||||||
|
.mockResolvedValueOnce({
|
||||||
|
ok: true,
|
||||||
|
json: async () => ({ success: true, records: [{ id: 1 }] }),
|
||||||
|
});
|
||||||
|
|
||||||
menu.showMenu(240, 320);
|
menu.showMenu(240, 320);
|
||||||
const cleanupItem = document.querySelector('[data-action="cleanup-example-images-folders"]');
|
const cleanupItem = document.querySelector('[data-action="cleanup-example-images-folders"]');
|
||||||
@@ -315,5 +349,30 @@ describe('Interaction-level regression coverage', () => {
|
|||||||
await flushAsyncTasks();
|
await flushAsyncTasks();
|
||||||
expect(cleanupItem.classList.contains('disabled')).toBe(false);
|
expect(cleanupItem.classList.contains('disabled')).toBe(false);
|
||||||
expect(menu._cleanupInProgress).toBe(false);
|
expect(menu._cleanupInProgress).toBe(false);
|
||||||
|
|
||||||
|
menu.showMenu(360, 420);
|
||||||
|
const checkUpdatesItem = document.querySelector('[data-action="check-model-updates"]');
|
||||||
|
checkUpdatesItem.dispatchEvent(new Event('click', { bubbles: true }));
|
||||||
|
expect(checkUpdatesItem.classList.contains('disabled')).toBe(true);
|
||||||
|
|
||||||
|
expect(global.fetch).toHaveBeenLastCalledWith('/api/lm/loras/updates/refresh', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ force: false }),
|
||||||
|
});
|
||||||
|
|
||||||
|
const updateResponse = await global.fetch.mock.results[1].value;
|
||||||
|
await updateResponse.json();
|
||||||
|
await flushAsyncTasks();
|
||||||
|
|
||||||
|
expect(showToastMock).toHaveBeenCalledWith(
|
||||||
|
'globalContextMenu.checkModelUpdates.success',
|
||||||
|
{ count: 1, type: 'LoRA' },
|
||||||
|
'success'
|
||||||
|
);
|
||||||
|
expect(loadingManagerStub.showSimpleLoading).toHaveBeenCalledWith('Checking for LoRA updates...');
|
||||||
|
expect(loadingManagerStub.hide).toHaveBeenCalled();
|
||||||
|
expect(resetAndReloadMock).toHaveBeenCalledWith(false);
|
||||||
|
expect(checkUpdatesItem.classList.contains('disabled')).toBe(false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user