mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-06-09 20:39:25 -03:00
feat(update): add per-folder update check via sidebar context menu (#944)
This commit is contained in:
@@ -963,6 +963,13 @@
|
|||||||
"empty": {
|
"empty": {
|
||||||
"noFolders": "Keine Ordner gefunden",
|
"noFolders": "Keine Ordner gefunden",
|
||||||
"dragHint": "Elemente hierher ziehen, um Ordner zu erstellen"
|
"dragHint": "Elemente hierher ziehen, um Ordner zu erstellen"
|
||||||
|
},
|
||||||
|
"folderUpdateCheck": {
|
||||||
|
"label": "Auf Updates in diesem Ordner prüfen",
|
||||||
|
"loading": "Prüfe {type}-Updates in diesem Ordner...",
|
||||||
|
"success": "{count} Update(s) für {type}s in diesem Ordner gefunden",
|
||||||
|
"none": "Alle {type}s in diesem Ordner sind aktuell",
|
||||||
|
"error": "Fehler beim Prüfen des Ordners auf {type}-Updates: {message}"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"statistics": {
|
"statistics": {
|
||||||
|
|||||||
@@ -963,6 +963,13 @@
|
|||||||
"empty": {
|
"empty": {
|
||||||
"noFolders": "No folders found",
|
"noFolders": "No folders found",
|
||||||
"dragHint": "Drag items here to create folders"
|
"dragHint": "Drag items here to create folders"
|
||||||
|
},
|
||||||
|
"folderUpdateCheck": {
|
||||||
|
"label": "Check for updates in this folder",
|
||||||
|
"loading": "Checking {type} updates for this folder...",
|
||||||
|
"success": "Found {count} update(s) for {type}s in this folder",
|
||||||
|
"none": "All {type}s in this folder are up to date",
|
||||||
|
"error": "Failed to check folder for {type} updates: {message}"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"statistics": {
|
"statistics": {
|
||||||
|
|||||||
@@ -963,6 +963,13 @@
|
|||||||
"empty": {
|
"empty": {
|
||||||
"noFolders": "No se encontraron carpetas",
|
"noFolders": "No se encontraron carpetas",
|
||||||
"dragHint": "Arrastra elementos aquí para crear carpetas"
|
"dragHint": "Arrastra elementos aquí para crear carpetas"
|
||||||
|
},
|
||||||
|
"folderUpdateCheck": {
|
||||||
|
"label": "Buscar actualizaciones en esta carpeta",
|
||||||
|
"loading": "Buscando actualizaciones de {type} en esta carpeta...",
|
||||||
|
"success": "Se encontraron {count} actualización(es) para {type}s en esta carpeta",
|
||||||
|
"none": "Todos los {type}s en esta carpeta están actualizados",
|
||||||
|
"error": "Error al buscar actualizaciones de {type} en la carpeta: {message}"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"statistics": {
|
"statistics": {
|
||||||
|
|||||||
@@ -963,6 +963,13 @@
|
|||||||
"empty": {
|
"empty": {
|
||||||
"noFolders": "Aucun dossier trouvé",
|
"noFolders": "Aucun dossier trouvé",
|
||||||
"dragHint": "Faites glisser des éléments ici pour créer des dossiers"
|
"dragHint": "Faites glisser des éléments ici pour créer des dossiers"
|
||||||
|
},
|
||||||
|
"folderUpdateCheck": {
|
||||||
|
"label": "Vérifier les mises à jour dans ce dossier",
|
||||||
|
"loading": "Vérification des mises à jour {type} dans ce dossier...",
|
||||||
|
"success": "{count} mise(s) à jour trouvée(s) pour les {type}s dans ce dossier",
|
||||||
|
"none": "Tous les {type}s dans ce dossier sont à jour",
|
||||||
|
"error": "Échec de la vérification des mises à jour {type} dans ce dossier : {message}"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"statistics": {
|
"statistics": {
|
||||||
|
|||||||
@@ -963,6 +963,13 @@
|
|||||||
"empty": {
|
"empty": {
|
||||||
"noFolders": "לא נמצאו תיקיות",
|
"noFolders": "לא נמצאו תיקיות",
|
||||||
"dragHint": "גרור פריטים לכאן כדי ליצור תיקיות"
|
"dragHint": "גרור פריטים לכאן כדי ליצור תיקיות"
|
||||||
|
},
|
||||||
|
"folderUpdateCheck": {
|
||||||
|
"label": "בדוק עדכונים בתיקייה זו",
|
||||||
|
"loading": "בודק עדכוני {type} בתיקייה זו...",
|
||||||
|
"success": "נמצאו {count} עדכון/ים עבור {type}s בתיקייה זו",
|
||||||
|
"none": "כל ה-{type}s בתיקייה זו מעודכנים",
|
||||||
|
"error": "נכשל בבדיקת עדכוני {type} בתיקייה: {message}"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"statistics": {
|
"statistics": {
|
||||||
|
|||||||
@@ -963,6 +963,13 @@
|
|||||||
"empty": {
|
"empty": {
|
||||||
"noFolders": "フォルダが見つかりません",
|
"noFolders": "フォルダが見つかりません",
|
||||||
"dragHint": "ここへアイテムをドラッグしてフォルダを作成します"
|
"dragHint": "ここへアイテムをドラッグしてフォルダを作成します"
|
||||||
|
},
|
||||||
|
"folderUpdateCheck": {
|
||||||
|
"label": "このフォルダのアップデートを確認",
|
||||||
|
"loading": "このフォルダの{type}アップデートを確認中...",
|
||||||
|
"success": "このフォルダの{type}sに{count}件のアップデートが見つかりました",
|
||||||
|
"none": "このフォルダのすべての{type}sは最新です",
|
||||||
|
"error": "フォルダの{type}アップデート確認に失敗しました: {message}"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"statistics": {
|
"statistics": {
|
||||||
|
|||||||
@@ -963,6 +963,13 @@
|
|||||||
"empty": {
|
"empty": {
|
||||||
"noFolders": "폴더를 찾을 수 없습니다",
|
"noFolders": "폴더를 찾을 수 없습니다",
|
||||||
"dragHint": "항목을 여기로 드래그하여 폴더를 만듭니다"
|
"dragHint": "항목을 여기로 드래그하여 폴더를 만듭니다"
|
||||||
|
},
|
||||||
|
"folderUpdateCheck": {
|
||||||
|
"label": "이 폴더의 업데이트 확인",
|
||||||
|
"loading": "이 폴더의 {type} 업데이트를 확인하는 중...",
|
||||||
|
"success": "이 폴더에서 {type}s에 대한 {count}개 업데이트를 찾았습니다",
|
||||||
|
"none": "이 폴더의 모든 {type}s가 최신 상태입니다",
|
||||||
|
"error": "폴더의 {type} 업데이트 확인 실패: {message}"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"statistics": {
|
"statistics": {
|
||||||
|
|||||||
@@ -963,6 +963,13 @@
|
|||||||
"empty": {
|
"empty": {
|
||||||
"noFolders": "Папки не найдены",
|
"noFolders": "Папки не найдены",
|
||||||
"dragHint": "Перетащите элементы сюда, чтобы создать папки"
|
"dragHint": "Перетащите элементы сюда, чтобы создать папки"
|
||||||
|
},
|
||||||
|
"folderUpdateCheck": {
|
||||||
|
"label": "Проверить обновления в этой папке",
|
||||||
|
"loading": "Проверка обновлений {type} в этой папке...",
|
||||||
|
"success": "Найдено {count} обновление(й) для {type}s в этой папке",
|
||||||
|
"none": "Все {type}s в этой папке актуальны",
|
||||||
|
"error": "Не удалось проверить папку на наличие обновлений {type}: {message}"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"statistics": {
|
"statistics": {
|
||||||
|
|||||||
@@ -963,6 +963,13 @@
|
|||||||
"empty": {
|
"empty": {
|
||||||
"noFolders": "未找到文件夹",
|
"noFolders": "未找到文件夹",
|
||||||
"dragHint": "拖拽项目到此处以创建文件夹"
|
"dragHint": "拖拽项目到此处以创建文件夹"
|
||||||
|
},
|
||||||
|
"folderUpdateCheck": {
|
||||||
|
"label": "检查此文件夹的更新",
|
||||||
|
"loading": "正在检查此文件夹中的{type}更新...",
|
||||||
|
"success": "在此文件夹中找到 {count} 个{type}更新",
|
||||||
|
"none": "此文件夹中的所有{type}都是最新版本",
|
||||||
|
"error": "检查文件夹{type}更新失败: {message}"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"statistics": {
|
"statistics": {
|
||||||
|
|||||||
@@ -963,6 +963,13 @@
|
|||||||
"empty": {
|
"empty": {
|
||||||
"noFolders": "未找到資料夾",
|
"noFolders": "未找到資料夾",
|
||||||
"dragHint": "將項目拖到此處以建立資料夾"
|
"dragHint": "將項目拖到此處以建立資料夾"
|
||||||
|
},
|
||||||
|
"folderUpdateCheck": {
|
||||||
|
"label": "檢查此資料夾的更新",
|
||||||
|
"loading": "正在檢查此資料夾中的{type}更新...",
|
||||||
|
"success": "在此資料夾中找到 {count} 個{type}更新",
|
||||||
|
"none": "此資料夾中的所有{type}都是最新版本",
|
||||||
|
"error": "檢查資料夾{type}更新失敗: {message}"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"statistics": {
|
"statistics": {
|
||||||
|
|||||||
@@ -1960,6 +1960,10 @@ class ModelUpdateHandler:
|
|||||||
if target_model_ids:
|
if target_model_ids:
|
||||||
target_model_ids = sorted(set(target_model_ids))
|
target_model_ids = sorted(set(target_model_ids))
|
||||||
|
|
||||||
|
folder_path: Optional[str] = payload.get("folder_path")
|
||||||
|
if folder_path is not None and not isinstance(folder_path, str):
|
||||||
|
folder_path = None
|
||||||
|
|
||||||
provider = await self._get_civitai_provider()
|
provider = await self._get_civitai_provider()
|
||||||
if provider is None:
|
if provider is None:
|
||||||
return web.json_response(
|
return web.json_response(
|
||||||
@@ -1974,6 +1978,7 @@ class ModelUpdateHandler:
|
|||||||
provider,
|
provider,
|
||||||
force_refresh=force_refresh,
|
force_refresh=force_refresh,
|
||||||
target_model_ids=target_model_ids or None,
|
target_model_ids=target_model_ids or None,
|
||||||
|
folder_path=folder_path,
|
||||||
)
|
)
|
||||||
if self._service.scanner.is_cancelled():
|
if self._service.scanner.is_cancelled():
|
||||||
return web.json_response(
|
return web.json_response(
|
||||||
|
|||||||
@@ -689,6 +689,7 @@ class ModelUpdateService:
|
|||||||
*,
|
*,
|
||||||
force_refresh: bool = False,
|
force_refresh: bool = False,
|
||||||
target_model_ids: Optional[Sequence[int]] = None,
|
target_model_ids: Optional[Sequence[int]] = None,
|
||||||
|
folder_path: Optional[str] = None,
|
||||||
) -> Dict[int, ModelUpdateRecord]:
|
) -> Dict[int, ModelUpdateRecord]:
|
||||||
"""Refresh update information for every model present in the cache."""
|
"""Refresh update information for every model present in the cache."""
|
||||||
scanner.reset_cancellation()
|
scanner.reset_cancellation()
|
||||||
@@ -703,6 +704,7 @@ class ModelUpdateService:
|
|||||||
local_versions = await self._collect_local_versions(
|
local_versions = await self._collect_local_versions(
|
||||||
scanner,
|
scanner,
|
||||||
target_model_ids=target_filter,
|
target_model_ids=target_filter,
|
||||||
|
folder_path=folder_path,
|
||||||
)
|
)
|
||||||
total_models = len(local_versions)
|
total_models = len(local_versions)
|
||||||
if total_models == 0:
|
if total_models == 0:
|
||||||
@@ -1276,6 +1278,7 @@ class ModelUpdateService:
|
|||||||
scanner,
|
scanner,
|
||||||
*,
|
*,
|
||||||
target_model_ids: Optional[Sequence[int]] = None,
|
target_model_ids: Optional[Sequence[int]] = None,
|
||||||
|
folder_path: Optional[str] = None,
|
||||||
) -> Dict[int, List[int]]:
|
) -> Dict[int, List[int]]:
|
||||||
cache = await scanner.get_cached_data()
|
cache = await scanner.get_cached_data()
|
||||||
mapping: Dict[int, set[int]] = {}
|
mapping: Dict[int, set[int]] = {}
|
||||||
@@ -1288,7 +1291,19 @@ class ModelUpdateService:
|
|||||||
if not target_set:
|
if not target_set:
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
normalized_folder = None
|
||||||
|
if folder_path is not None:
|
||||||
|
normalized_folder = folder_path.replace("\\", "/").strip("/")
|
||||||
|
|
||||||
for item in cache.raw_data:
|
for item in cache.raw_data:
|
||||||
|
# Apply folder filter first (cheapest check)
|
||||||
|
if normalized_folder is not None:
|
||||||
|
if not isinstance(item, dict):
|
||||||
|
continue
|
||||||
|
item_folder = (item.get("folder") or "").replace("\\", "/").strip("/")
|
||||||
|
if item_folder != normalized_folder and not item_folder.startswith(normalized_folder + "/"):
|
||||||
|
continue
|
||||||
|
|
||||||
civitai = item.get("civitai") if isinstance(item, dict) else None
|
civitai = item.get("civitai") if isinstance(item, dict) else None
|
||||||
if not isinstance(civitai, dict):
|
if not isinstance(civitai, dict):
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -745,3 +745,8 @@
|
|||||||
.sidebar-tree-container {
|
.sidebar-tree-container {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Folder context menu - positioned relative to sidebar */
|
||||||
|
#sidebarFolderContextMenu {
|
||||||
|
z-index: var(--z-modal, 1002);
|
||||||
|
}
|
||||||
|
|||||||
@@ -766,6 +766,49 @@ export class BaseModelApiClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async refreshUpdatesForFolder(folderPath, { force = false } = {}) {
|
||||||
|
if (!folderPath) {
|
||||||
|
throw new Error('No folder path provided');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
state.loadingManager.show('Checking for updates...', 0);
|
||||||
|
state.loadingManager.showCancelButton(() => this.cancelTask());
|
||||||
|
|
||||||
|
const response = await fetch(this.apiConfig.endpoints.refreshUpdates, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({
|
||||||
|
folder_path: folderPath,
|
||||||
|
force
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
let payload = {};
|
||||||
|
try {
|
||||||
|
payload = await response.json();
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('Unable to parse refresh updates response as JSON', error);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!response.ok || payload?.success !== true) {
|
||||||
|
if (payload?.status === 'cancelled') {
|
||||||
|
showToast('toast.api.operationCancelled', {}, 'info');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const message = payload?.error || response.statusText || 'Failed to refresh updates';
|
||||||
|
throw new Error(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
return payload;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error refreshing updates for folder:', error);
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
state.loadingManager.hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async fetchCivitaiVersions(modelId, source = null) {
|
async fetchCivitaiVersions(modelId, source = null) {
|
||||||
try {
|
try {
|
||||||
let requestUrl = `${this.apiConfig.endpoints.civitaiVersions}/${modelId}`;
|
let requestUrl = `${this.apiConfig.endpoints.civitaiVersions}/${modelId}`;
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { translate } from '../utils/i18nHelpers.js';
|
|||||||
import { state } from '../state/index.js';
|
import { state } from '../state/index.js';
|
||||||
import { bulkManager } from '../managers/BulkManager.js';
|
import { bulkManager } from '../managers/BulkManager.js';
|
||||||
import { showToast } from '../utils/uiHelpers.js';
|
import { showToast } from '../utils/uiHelpers.js';
|
||||||
|
import { performFolderUpdateCheck } from '../utils/updateCheckHelpers.js';
|
||||||
import { escapeHtml, escapeAttribute } from './shared/utils.js';
|
import { escapeHtml, escapeAttribute } from './shared/utils.js';
|
||||||
|
|
||||||
export class SidebarManager {
|
export class SidebarManager {
|
||||||
@@ -41,6 +42,7 @@ export class SidebarManager {
|
|||||||
|
|
||||||
// Bind methods
|
// Bind methods
|
||||||
this.handleTreeClick = this.handleTreeClick.bind(this);
|
this.handleTreeClick = this.handleTreeClick.bind(this);
|
||||||
|
this.handleTreeContextMenu = this.handleTreeContextMenu.bind(this);
|
||||||
this.handleBreadcrumbClick = this.handleBreadcrumbClick.bind(this);
|
this.handleBreadcrumbClick = this.handleBreadcrumbClick.bind(this);
|
||||||
this.handleDocumentClick = this.handleDocumentClick.bind(this);
|
this.handleDocumentClick = this.handleDocumentClick.bind(this);
|
||||||
this.handleSidebarHeaderClick = this.handleSidebarHeaderClick.bind(this);
|
this.handleSidebarHeaderClick = this.handleSidebarHeaderClick.bind(this);
|
||||||
@@ -185,6 +187,8 @@ export class SidebarManager {
|
|||||||
}
|
}
|
||||||
if (folderTree) {
|
if (folderTree) {
|
||||||
folderTree.removeEventListener('click', this.handleTreeClick);
|
folderTree.removeEventListener('click', this.handleTreeClick);
|
||||||
|
folderTree.removeEventListener('contextmenu', this.handleTreeContextMenu);
|
||||||
|
folderTree.removeEventListener('dragover', this.handleFolderDragOver);
|
||||||
}
|
}
|
||||||
if (sidebarBreadcrumbNav) {
|
if (sidebarBreadcrumbNav) {
|
||||||
sidebarBreadcrumbNav.removeEventListener('click', this.handleBreadcrumbClick);
|
sidebarBreadcrumbNav.removeEventListener('click', this.handleBreadcrumbClick);
|
||||||
@@ -977,6 +981,7 @@ export class SidebarManager {
|
|||||||
const folderTree = document.getElementById('sidebarFolderTree');
|
const folderTree = document.getElementById('sidebarFolderTree');
|
||||||
if (folderTree) {
|
if (folderTree) {
|
||||||
folderTree.addEventListener('click', this.handleTreeClick);
|
folderTree.addEventListener('click', this.handleTreeClick);
|
||||||
|
folderTree.addEventListener('contextmenu', this.handleTreeContextMenu);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Breadcrumb click handler
|
// Breadcrumb click handler
|
||||||
@@ -1027,6 +1032,19 @@ export class SidebarManager {
|
|||||||
if (displayModeToggleBtn) {
|
if (displayModeToggleBtn) {
|
||||||
displayModeToggleBtn.addEventListener('click', this.handleDisplayModeToggle);
|
displayModeToggleBtn.addEventListener('click', this.handleDisplayModeToggle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sidebar folder context menu click handler
|
||||||
|
const sidebarFolderMenu = document.getElementById('sidebarFolderContextMenu');
|
||||||
|
if (sidebarFolderMenu) {
|
||||||
|
sidebarFolderMenu.addEventListener('click', (e) => {
|
||||||
|
const item = e.target.closest('.context-menu-item');
|
||||||
|
if (!item) return;
|
||||||
|
const action = item.dataset.action;
|
||||||
|
if (action) {
|
||||||
|
this.handleFolderContextMenuAction(action);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleDocumentClick(event) {
|
handleDocumentClick(event) {
|
||||||
@@ -1398,6 +1416,82 @@ export class SidebarManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleTreeContextMenu(event) {
|
||||||
|
const nodeContent = event.target.closest('.sidebar-tree-node, .sidebar-folder-item');
|
||||||
|
if (!nodeContent) return;
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
|
||||||
|
const path = nodeContent.dataset.path;
|
||||||
|
if (path === undefined || path === null || path === '') return;
|
||||||
|
|
||||||
|
this._showFolderContextMenu(event.clientX, event.clientY, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
_showFolderContextMenu(x, y, path) {
|
||||||
|
this._closeFolderContextMenu();
|
||||||
|
|
||||||
|
const menu = document.getElementById('sidebarFolderContextMenu');
|
||||||
|
if (!menu) return;
|
||||||
|
|
||||||
|
menu.style.left = `${x}px`;
|
||||||
|
menu.style.top = `${y}px`;
|
||||||
|
menu.style.display = 'block';
|
||||||
|
menu.dataset.folderPath = path;
|
||||||
|
|
||||||
|
this._folderContextOpen = true;
|
||||||
|
|
||||||
|
// Close on next click outside
|
||||||
|
this._folderContextCloseHandler = (e) => {
|
||||||
|
if (!menu.contains(e.target)) {
|
||||||
|
this._closeFolderContextMenu();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
setTimeout(() => {
|
||||||
|
document.addEventListener('click', this._folderContextCloseHandler);
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
_closeFolderContextMenu() {
|
||||||
|
const menu = document.getElementById('sidebarFolderContextMenu');
|
||||||
|
if (menu) {
|
||||||
|
menu.style.display = 'none';
|
||||||
|
delete menu.dataset.folderPath;
|
||||||
|
}
|
||||||
|
if (this._folderContextCloseHandler) {
|
||||||
|
document.removeEventListener('click', this._folderContextCloseHandler);
|
||||||
|
this._folderContextCloseHandler = null;
|
||||||
|
}
|
||||||
|
this._folderContextOpen = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
handleFolderContextMenuAction(action) {
|
||||||
|
const menu = document.getElementById('sidebarFolderContextMenu');
|
||||||
|
if (!menu) return;
|
||||||
|
|
||||||
|
const path = menu.dataset.folderPath;
|
||||||
|
this._closeFolderContextMenu();
|
||||||
|
|
||||||
|
if (!path) return;
|
||||||
|
|
||||||
|
this._performFolderAction(action, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
async _performFolderAction(action, path) {
|
||||||
|
switch (action) {
|
||||||
|
case 'check-folder-updates':
|
||||||
|
try {
|
||||||
|
await performFolderUpdateCheck(path);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Folder update check failed:', error);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
console.warn('Unknown folder action:', action);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
handleBreadcrumbClick(event) {
|
handleBreadcrumbClick(event) {
|
||||||
const breadcrumbItem = event.target.closest('.sidebar-breadcrumb-item');
|
const breadcrumbItem = event.target.closest('.sidebar-breadcrumb-item');
|
||||||
const dropdownItem = event.target.closest('.breadcrumb-dropdown-item');
|
const dropdownItem = event.target.closest('.breadcrumb-dropdown-item');
|
||||||
|
|||||||
@@ -100,6 +100,90 @@ export async function performModelUpdateCheck({ onStart, onComplete } = {}) {
|
|||||||
return { status, displayName, records, error };
|
return { status, displayName, records, error };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform a model update check scoped to a specific folder.
|
||||||
|
* @param {string} folderPath - The relative folder path to check.
|
||||||
|
* @param {Object} [options]
|
||||||
|
* @param {Function} [options.onComplete] - Callback invoked after the request settles.
|
||||||
|
* @returns {Promise<{status: string, records: Array, error: Error | null}>}
|
||||||
|
*/
|
||||||
|
export async function performFolderUpdateCheck(folderPath, { onComplete } = {}) {
|
||||||
|
const modelType = getCurrentModelType();
|
||||||
|
const apiConfig = getCompleteApiConfig(modelType);
|
||||||
|
const apiClient = getModelApiClient(modelType);
|
||||||
|
const displayName = apiConfig?.config?.displayName ?? 'Model';
|
||||||
|
|
||||||
|
if (!apiConfig?.endpoints?.refreshUpdates) {
|
||||||
|
console.warn('Refresh updates endpoint not configured for model type:', modelType);
|
||||||
|
onComplete?.({ status: 'unsupported', records: [], error: null });
|
||||||
|
return { status: 'unsupported', records: [], error: null };
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadingMessage = translate(
|
||||||
|
'sidebar.folderUpdateCheck.loading',
|
||||||
|
{ type: displayName },
|
||||||
|
`Checking ${displayName} updates for this folder...`
|
||||||
|
);
|
||||||
|
|
||||||
|
state.loadingManager?.showSimpleLoading?.(loadingMessage);
|
||||||
|
state.loadingManager?.showCancelButton?.(() => apiClient.cancelTask());
|
||||||
|
|
||||||
|
let status = 'success';
|
||||||
|
let records = [];
|
||||||
|
let error = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(apiConfig.endpoints.refreshUpdates, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ folder_path: folderPath, force: false })
|
||||||
|
});
|
||||||
|
|
||||||
|
let payload = {};
|
||||||
|
try {
|
||||||
|
payload = await response.json();
|
||||||
|
} catch {
|
||||||
|
payload = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!response.ok || payload.success !== true) {
|
||||||
|
if (payload?.status === 'cancelled') {
|
||||||
|
showToast('toast.api.operationCancelled', {}, 'info');
|
||||||
|
return { status: 'cancelled', records: [], error: null };
|
||||||
|
}
|
||||||
|
const errorMessage = payload?.error || response.statusText || 'Unknown error';
|
||||||
|
throw new Error(errorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
records = Array.isArray(payload.records) ? payload.records : [];
|
||||||
|
|
||||||
|
if (records.length > 0) {
|
||||||
|
showToast('sidebar.folderUpdateCheck.success', { count: records.length, type: displayName }, 'success');
|
||||||
|
} else {
|
||||||
|
showToast('sidebar.folderUpdateCheck.none', { type: displayName }, 'info');
|
||||||
|
}
|
||||||
|
|
||||||
|
await resetAndReload(false);
|
||||||
|
} catch (err) {
|
||||||
|
status = 'error';
|
||||||
|
error = err instanceof Error ? err : new Error(String(err));
|
||||||
|
console.error('Error checking folder model updates:', error);
|
||||||
|
showToast(
|
||||||
|
'sidebar.folderUpdateCheck.error',
|
||||||
|
{ message: error?.message ?? 'Unknown error', type: displayName },
|
||||||
|
'error'
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
state.loadingManager?.hide?.();
|
||||||
|
if (typeof state.loadingManager?.restoreProgressBar === 'function') {
|
||||||
|
state.loadingManager.restoreProgressBar();
|
||||||
|
}
|
||||||
|
onComplete?.({ status, records, error });
|
||||||
|
}
|
||||||
|
|
||||||
|
return { status, records, error };
|
||||||
|
}
|
||||||
|
|
||||||
function getTypePlural(displayName) {
|
function getTypePlural(displayName) {
|
||||||
if (!displayName) {
|
if (!displayName) {
|
||||||
return 'models';
|
return 'models';
|
||||||
|
|||||||
@@ -150,6 +150,13 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Sidebar Folder Context Menu -->
|
||||||
|
<div id="sidebarFolderContextMenu" class="context-menu">
|
||||||
|
<div class="context-menu-item" data-action="check-folder-updates">
|
||||||
|
<i class="fas fa-bell"></i> <span>{{ t('sidebar.folderUpdateCheck.label') }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div id="nsfwLevelSelector" class="nsfw-level-selector">
|
<div id="nsfwLevelSelector" class="nsfw-level-selector">
|
||||||
<div class="nsfw-level-header">
|
<div class="nsfw-level-header">
|
||||||
<h3>{{ t('modals.contentRating.title') }}</h3>
|
<h3>{{ t('modals.contentRating.title') }}</h3>
|
||||||
|
|||||||
Reference in New Issue
Block a user