mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-03-21 21:22:11 -03:00
feat(updates): improve check updates confirmation
This commit is contained in:
@@ -731,6 +731,12 @@
|
||||
"countMessage": "Modelle werden dauerhaft gelöscht.",
|
||||
"action": "Alle löschen"
|
||||
},
|
||||
"checkUpdates": {
|
||||
"title": "Alle {typePlural} auf Updates prüfen?",
|
||||
"message": "Damit werden alle {typePlural} in deiner Bibliothek auf Updates geprüft. Bei großen Sammlungen kann das etwas länger dauern.",
|
||||
"tip": "Du möchtest in Etappen prüfen? Wechsle in den Sammelmodus, wähle die benötigten Modelle aus und nutze anschließend \"Auswahl auf Updates prüfen\".",
|
||||
"action": "Alles prüfen"
|
||||
},
|
||||
"bulkAddTags": {
|
||||
"title": "Tags zu mehreren Modellen hinzufügen",
|
||||
"description": "Tags hinzufügen zu",
|
||||
|
||||
@@ -730,6 +730,12 @@
|
||||
"countMessage": "models will be permanently deleted.",
|
||||
"action": "Delete All"
|
||||
},
|
||||
"checkUpdates": {
|
||||
"title": "Check updates for all {typePlural}?",
|
||||
"message": "This checks every {typePlural} in your library for updates. Large collections may take a little longer.",
|
||||
"tip": "To work in smaller batches, switch to bulk mode, choose the ones you need, then use \"Check Updates for Selected\".",
|
||||
"action": "Check All"
|
||||
},
|
||||
"bulkAddTags": {
|
||||
"title": "Add Tags to Multiple Models",
|
||||
"description": "Add tags to",
|
||||
|
||||
@@ -730,6 +730,12 @@
|
||||
"countMessage": "modelos serán eliminados permanentemente.",
|
||||
"action": "Eliminar todo"
|
||||
},
|
||||
"checkUpdates": {
|
||||
"title": "¿Comprobar actualizaciones para todos los {typePlural}?",
|
||||
"message": "Esto comprobará las actualizaciones de todos los {typePlural} de tu biblioteca. En colecciones grandes puede tardar un poco más.",
|
||||
"tip": "¿Quieres hacerlo por partes? Activa el modo por lotes, selecciona los modelos que necesites y usa \"Comprobar actualizaciones para la selección\".",
|
||||
"action": "Comprobar todo"
|
||||
},
|
||||
"bulkAddTags": {
|
||||
"title": "Añadir etiquetas a múltiples modelos",
|
||||
"description": "Añadir etiquetas a",
|
||||
|
||||
@@ -730,6 +730,12 @@
|
||||
"countMessage": "modèles seront définitivement supprimés.",
|
||||
"action": "Tout supprimer"
|
||||
},
|
||||
"checkUpdates": {
|
||||
"title": "Vérifier les mises à jour pour tous les {typePlural} ?",
|
||||
"message": "Cette action vérifie les mises à jour pour tous les {typePlural} de votre bibliothèque. Les grandes collections peuvent prendre un peu plus de temps.",
|
||||
"tip": "Besoin de procéder par étapes ? Passez en mode lot, sélectionnez les modèles souhaités puis utilisez \"Vérifier les mises à jour pour la sélection\".",
|
||||
"action": "Tout vérifier"
|
||||
},
|
||||
"bulkAddTags": {
|
||||
"title": "Ajouter des tags à plusieurs modèles",
|
||||
"description": "Ajouter des tags à",
|
||||
|
||||
@@ -730,6 +730,12 @@
|
||||
"countMessage": "מודלים יימחקו לצמיתות.",
|
||||
"action": "מחק הכל"
|
||||
},
|
||||
"checkUpdates": {
|
||||
"title": "לבדוק עדכונים לכל ה-{typePlural}?",
|
||||
"message": "הפעולה תבדוק עדכונים עבור כל ה-{typePlural} בספרייה שלך. באוספים גדולים זה עלול לקחת מעט יותר זמן.",
|
||||
"tip": "רוצים לחלק למנות קטנות? עברו למצב קבוצתי, בחרו את המודלים הדרושים ואז השתמשו ב\"בדוק עדכונים לנבחרים\".",
|
||||
"action": "בדוק הכל"
|
||||
},
|
||||
"bulkAddTags": {
|
||||
"title": "הוסף תגיות למספר מודלים",
|
||||
"description": "הוסף תגיות ל-",
|
||||
|
||||
@@ -730,6 +730,12 @@
|
||||
"countMessage": "モデルが完全に削除されます。",
|
||||
"action": "すべて削除"
|
||||
},
|
||||
"checkUpdates": {
|
||||
"title": "すべての{type}の更新を確認しますか?",
|
||||
"message": "ライブラリ内のすべての{type}で更新を確認します。コレクションが大きい場合は時間がかかることがあります。",
|
||||
"tip": "少しずつ確認したい場合はバルクモードに切り替え、必要なモデルを選んで「選択項目の更新を確認」を使ってください。",
|
||||
"action": "すべて確認"
|
||||
},
|
||||
"bulkAddTags": {
|
||||
"title": "複数モデルにタグを追加",
|
||||
"description": "タグを追加するモデル:",
|
||||
|
||||
@@ -730,6 +730,12 @@
|
||||
"countMessage": "개의 모델이 영구적으로 삭제됩니다.",
|
||||
"action": "모두 삭제"
|
||||
},
|
||||
"checkUpdates": {
|
||||
"title": "{type} 전체 업데이트를 확인할까요?",
|
||||
"message": "라이브러리에 있는 모든 {type}의 업데이트를 확인합니다. 컬렉션이 클수록 시간이 조금 더 걸릴 수 있습니다.",
|
||||
"tip": "나눠서 진행하고 싶다면 벌크 모드로 전환해 필요한 모델만 선택한 뒤 \"선택 항목 업데이트 확인\"을 사용하세요.",
|
||||
"action": "전체 확인"
|
||||
},
|
||||
"bulkAddTags": {
|
||||
"title": "여러 모델에 태그 추가",
|
||||
"description": "다음에 태그를 추가합니다:",
|
||||
|
||||
@@ -730,6 +730,12 @@
|
||||
"countMessage": "моделей будут удалены навсегда.",
|
||||
"action": "Удалить все"
|
||||
},
|
||||
"checkUpdates": {
|
||||
"title": "Проверить обновления для всех {typePlural}?",
|
||||
"message": "Будут проверены обновления для всех {typePlural} в вашей библиотеке. Для больших коллекций это может занять немного больше времени.",
|
||||
"tip": "Хотите проверять по частям? Переключитесь в массовый режим, выберите нужные модели и используйте \"Проверить обновления для выбранных\".",
|
||||
"action": "Проверить всё"
|
||||
},
|
||||
"bulkAddTags": {
|
||||
"title": "Добавить теги к нескольким моделям",
|
||||
"description": "Добавить теги к",
|
||||
|
||||
@@ -730,6 +730,12 @@
|
||||
"countMessage": "模型将被永久删除。",
|
||||
"action": "全部删除"
|
||||
},
|
||||
"checkUpdates": {
|
||||
"title": "检查所有 {type} 的更新?",
|
||||
"message": "这会为库中的每个 {type} 检查更新,大型集合可能需要一些时间。",
|
||||
"tip": "想分批进行?切换到批量模式,选中需要的模型,然后使用“检查所选更新”。",
|
||||
"action": "检查全部"
|
||||
},
|
||||
"bulkAddTags": {
|
||||
"title": "批量添加标签",
|
||||
"description": "为多个模型添加标签",
|
||||
|
||||
@@ -730,6 +730,12 @@
|
||||
"countMessage": "模型將被永久刪除。",
|
||||
"action": "全部刪除"
|
||||
},
|
||||
"checkUpdates": {
|
||||
"title": "要檢查所有 {type} 的更新嗎?",
|
||||
"message": "這會為資料庫中的每個 {type} 檢查更新,大型收藏可能會花上一些時間。",
|
||||
"tip": "想分批處理?切換到批次模式,選擇需要的模型,然後使用「檢查所選更新」。",
|
||||
"action": "全部檢查"
|
||||
},
|
||||
"bulkAddTags": {
|
||||
"title": "新增標籤到多個模型",
|
||||
"description": "新增標籤到",
|
||||
|
||||
@@ -235,13 +235,22 @@ export class PageControls {
|
||||
this._updateCheckInProgress = true;
|
||||
setLoadingState(true);
|
||||
|
||||
const handleComplete = () => {
|
||||
this._updateCheckInProgress = false;
|
||||
setLoadingState(false);
|
||||
};
|
||||
|
||||
try {
|
||||
await performModelUpdateCheck();
|
||||
await performModelUpdateCheck({
|
||||
onComplete: handleComplete,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to check model updates:', error);
|
||||
} finally {
|
||||
if (this._updateCheckInProgress) {
|
||||
this._updateCheckInProgress = false;
|
||||
setLoadingState(false);
|
||||
}
|
||||
dropdownGroup?.classList.remove('active');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -195,6 +195,18 @@ export class ModalManager {
|
||||
});
|
||||
}
|
||||
|
||||
// Add checkUpdatesConfirmModal registration
|
||||
const checkUpdatesConfirmModal = document.getElementById('checkUpdatesConfirmModal');
|
||||
if (checkUpdatesConfirmModal) {
|
||||
this.registerModal('checkUpdatesConfirmModal', {
|
||||
element: checkUpdatesConfirmModal,
|
||||
onClose: () => {
|
||||
this.getModal('checkUpdatesConfirmModal').element.classList.remove('show');
|
||||
document.body.classList.remove('modal-open');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Add helpModal registration
|
||||
const helpModal = document.getElementById('helpModal');
|
||||
if (helpModal) {
|
||||
@@ -339,7 +351,8 @@ export class ModalManager {
|
||||
id === "duplicateDeleteModal" ||
|
||||
id === "modelDuplicateDeleteModal" ||
|
||||
id === "clearCacheModal" ||
|
||||
id === "bulkDeleteModal"
|
||||
id === "bulkDeleteModal" ||
|
||||
id === "checkUpdatesConfirmModal"
|
||||
) {
|
||||
modal.element.classList.add("show");
|
||||
} else {
|
||||
|
||||
@@ -3,6 +3,10 @@ import { translate } from './i18nHelpers.js';
|
||||
import { showToast } from './uiHelpers.js';
|
||||
import { getCompleteApiConfig, getCurrentModelType } from '../api/apiConfig.js';
|
||||
import { resetAndReload } from '../api/modelApiFactory.js';
|
||||
import { getStorageItem, setStorageItem } from './storageHelpers.js';
|
||||
import { modalManager } from '../managers/ModalManager.js';
|
||||
|
||||
const CHECK_UPDATES_CONFIRMATION_KEY = 'ack_check_updates_for_all_models';
|
||||
|
||||
/**
|
||||
* Perform a model update check using the shared backend endpoint.
|
||||
@@ -22,6 +26,12 @@ export async function performModelUpdateCheck({ onStart, onComplete } = {}) {
|
||||
return { status: 'unsupported', displayName, records: [], error: null };
|
||||
}
|
||||
|
||||
const proceed = await ensureCheckUpdatesConfirmation(displayName);
|
||||
if (!proceed) {
|
||||
onComplete?.({ status: 'cancelled', displayName, records: [], error: null });
|
||||
return { status: 'cancelled', displayName, records: [], error: null };
|
||||
}
|
||||
|
||||
const loadingMessage = translate(
|
||||
'globalContextMenu.checkModelUpdates.loading',
|
||||
{ type: displayName },
|
||||
@@ -83,3 +93,101 @@ export async function performModelUpdateCheck({ onStart, onComplete } = {}) {
|
||||
|
||||
return { status, displayName, records, error };
|
||||
}
|
||||
|
||||
function getTypePlural(displayName) {
|
||||
if (!displayName) {
|
||||
return 'models';
|
||||
}
|
||||
|
||||
const lower = displayName.toLowerCase();
|
||||
if (lower.endsWith('s')) {
|
||||
return displayName;
|
||||
}
|
||||
|
||||
return `${displayName}s`;
|
||||
}
|
||||
|
||||
async function ensureCheckUpdatesConfirmation(displayName) {
|
||||
const hasConfirmed = getStorageItem(CHECK_UPDATES_CONFIRMATION_KEY, false);
|
||||
if (hasConfirmed) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const modalElement = document.getElementById('checkUpdatesConfirmModal');
|
||||
if (!modalElement) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const typePlural = getTypePlural(displayName);
|
||||
|
||||
const titleElement = modalElement.querySelector('[data-role="title"]');
|
||||
if (titleElement) {
|
||||
titleElement.textContent = translate(
|
||||
'modals.checkUpdates.title',
|
||||
{ type: displayName, typePlural },
|
||||
`Check updates for all ${typePlural}?`
|
||||
);
|
||||
}
|
||||
|
||||
const messageElement = modalElement.querySelector('[data-role="message"]');
|
||||
if (messageElement) {
|
||||
messageElement.textContent = translate(
|
||||
'modals.checkUpdates.message',
|
||||
{ type: displayName, typePlural },
|
||||
`This checks every ${typePlural} in your library for updates. Large collections may take a little longer.`
|
||||
);
|
||||
}
|
||||
|
||||
const tipElement = modalElement.querySelector('[data-role="tip"]');
|
||||
if (tipElement) {
|
||||
tipElement.textContent = translate(
|
||||
'modals.checkUpdates.tip',
|
||||
{ type: displayName, typePlural },
|
||||
'To work in smaller batches, switch to bulk mode, pick the ones you need, then use "Check Updates for Selected".'
|
||||
);
|
||||
}
|
||||
|
||||
const confirmButton = modalElement.querySelector('[data-action="confirm-check-updates"]');
|
||||
const cancelButton = modalElement.querySelector('[data-action="cancel-check-updates"]');
|
||||
|
||||
if (!confirmButton || !cancelButton) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return new Promise((resolve) => {
|
||||
let resolved = false;
|
||||
|
||||
const cleanup = () => {
|
||||
confirmButton.removeEventListener('click', handleConfirm);
|
||||
cancelButton.removeEventListener('click', handleCancel);
|
||||
};
|
||||
|
||||
const finalize = (proceed) => {
|
||||
if (resolved) {
|
||||
return;
|
||||
}
|
||||
|
||||
resolved = true;
|
||||
cleanup();
|
||||
resolve(proceed);
|
||||
};
|
||||
|
||||
const handleConfirm = (event) => {
|
||||
event.preventDefault();
|
||||
setStorageItem(CHECK_UPDATES_CONFIRMATION_KEY, true);
|
||||
finalize(true);
|
||||
modalManager.closeModal('checkUpdatesConfirmModal');
|
||||
};
|
||||
|
||||
const handleCancel = (event) => {
|
||||
event.preventDefault();
|
||||
finalize(false);
|
||||
modalManager.closeModal('checkUpdatesConfirmModal');
|
||||
};
|
||||
|
||||
confirmButton.addEventListener('click', handleConfirm);
|
||||
cancelButton.addEventListener('click', handleCancel);
|
||||
|
||||
modalManager.showModal('checkUpdatesConfirmModal', null, () => finalize(false));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -68,3 +68,16 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Check Updates Confirmation Modal -->
|
||||
<div id="checkUpdatesConfirmModal" class="modal delete-modal">
|
||||
<div class="modal-content delete-modal-content">
|
||||
<h2 data-role="title"></h2>
|
||||
<p class="confirmation-message" data-role="message"></p>
|
||||
<p class="confirmation-tip" data-role="tip"></p>
|
||||
<div class="modal-actions">
|
||||
<button class="cancel-btn" data-action="cancel-check-updates">{{ t('common.actions.cancel') }}</button>
|
||||
<button class="primary-btn" data-action="confirm-check-updates">{{ t('modals.checkUpdates.action') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -75,6 +75,26 @@ vi.mock('../../../static/js/utils/storageHelpers.js', () => ({
|
||||
setSessionItem: vi.fn(),
|
||||
removeSessionItem: vi.fn(),
|
||||
getSessionItem: vi.fn(),
|
||||
getStorageItem: vi.fn((key, defaultValue = null) => {
|
||||
const value = localStorage.getItem(`lora_manager_${key}`);
|
||||
if (value === null) {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
try {
|
||||
return JSON.parse(value);
|
||||
} catch (error) {
|
||||
return value;
|
||||
}
|
||||
}),
|
||||
setStorageItem: vi.fn((key, value) => {
|
||||
const prefixedKey = `lora_manager_${key}`;
|
||||
if (typeof value === 'object' && value !== null) {
|
||||
localStorage.setItem(prefixedKey, JSON.stringify(value));
|
||||
} else {
|
||||
localStorage.setItem(prefixedKey, value);
|
||||
}
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock('../../../static/js/api/modelApiFactory.js', () => ({
|
||||
@@ -350,11 +370,15 @@ describe('Interaction-level regression coverage', () => {
|
||||
expect(cleanupItem.classList.contains('disabled')).toBe(false);
|
||||
expect(menu._cleanupInProgress).toBe(false);
|
||||
|
||||
localStorage.setItem('lora_manager_ack_check_updates_for_all_models', 'true');
|
||||
|
||||
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);
|
||||
|
||||
await flushAsyncTasks();
|
||||
|
||||
expect(global.fetch).toHaveBeenLastCalledWith('/api/lm/loras/updates/refresh', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
|
||||
Reference in New Issue
Block a user