From 26e4895807efd17e6e4384f24a18cc59272f9b06 Mon Sep 17 00:00:00 2001 From: Will Miao Date: Thu, 20 Nov 2025 18:33:48 +0800 Subject: [PATCH] feat(auto-organize): improve exclusion handling and progress reporting - Add auto_organize_exclusions to settings handler proxy keys - Refactor model file service to handle exclusions relative to model roots - Improve auto-organize progress reporting for empty operations - Fix exclusion pattern matching to consider relative paths within model roots - Ensure proper validation when no model roots are configured - Add comprehensive cleanup reporting for empty auto-organize operations --- locales/de.json | 10 +-- locales/en.json | 2 +- locales/es.json | 10 +-- locales/fr.json | 10 +-- locales/he.json | 10 +-- locales/ja.json | 10 +-- locales/ko.json | 10 +-- locales/ru.json | 10 +-- locales/zh-CN.json | 10 +-- locales/zh-TW.json | 10 +-- py/routes/handlers/misc_handlers.py | 1 + py/services/model_file_service.py | 65 ++++++++++++++++--- .../css/components/modal/settings-modal.css | 9 +++ static/js/managers/SettingsManager.js | 10 +++ .../components/modals/settings_modal.html | 42 ++++++------ 15 files changed, 140 insertions(+), 79 deletions(-) diff --git a/locales/de.json b/locales/de.json index 92351d03..3ff8cdd4 100644 --- a/locales/de.json +++ b/locales/de.json @@ -253,12 +253,12 @@ "autoplayOnHoverHelp": "Video-Vorschauen nur beim Darüberfahren mit der Maus abspielen" }, "autoOrganizeExclusions": { - "label": "Auto-organize exclusions", - "placeholder": "Example: extras/*, */backups/*; *_temp.safetensors", - "help": "Skip moving files that match these wildcard patterns. Separate multiple patterns with commas or semicolons.", + "label": "Auto-Organisierungs-Ausnahmen", + "placeholder": "Beispiel: curated/*, */backups/*; *_temp.safetensors", + "help": "Dateien überspringen, die mit diesen Wildcard-Mustern übereinstimmen. Mehrere Muster mit Kommas oder Semikolons trennen.", "validation": { - "noPatterns": "Enter at least one pattern separated by commas or semicolons.", - "saveFailed": "Unable to save exclusions: {message}" + "noPatterns": "Geben Sie mindestens ein Muster ein, getrennt durch Kommas oder Semikolons.", + "saveFailed": "Fehler beim Speichern der Ausschlüsse: {message}" } }, "layoutSettings": { diff --git a/locales/en.json b/locales/en.json index 7da54814..4596e45f 100644 --- a/locales/en.json +++ b/locales/en.json @@ -254,7 +254,7 @@ }, "autoOrganizeExclusions": { "label": "Auto-organize exclusions", - "placeholder": "Example: extras/*, */backups/*; *_temp.safetensors", + "placeholder": "Example: curated/*, */backups/*; *_temp.safetensors", "help": "Skip moving files that match these wildcard patterns. Separate multiple patterns with commas or semicolons.", "validation": { "noPatterns": "Enter at least one pattern separated by commas or semicolons.", diff --git a/locales/es.json b/locales/es.json index 8abea518..dcdfb26b 100644 --- a/locales/es.json +++ b/locales/es.json @@ -253,12 +253,12 @@ "autoplayOnHoverHelp": "Solo reproducir vistas previas de video al pasar el ratón sobre ellas" }, "autoOrganizeExclusions": { - "label": "Auto-organize exclusions", - "placeholder": "Example: extras/*, */backups/*; *_temp.safetensors", - "help": "Skip moving files that match these wildcard patterns. Separate multiple patterns with commas or semicolons.", + "label": "Exclusiones de auto-organización", + "placeholder": "Ejemplo: curated/*, */backups/*; *_temp.safetensors", + "help": "Omitir archivos que coincidan con estos patrones comodín. Separe múltiples patrones con comas o puntos y comas.", "validation": { - "noPatterns": "Enter at least one pattern separated by commas or semicolons.", - "saveFailed": "Unable to save exclusions: {message}" + "noPatterns": "Ingrese al menos un patrón separado por comas o puntos y comas.", + "saveFailed": "No se pudieron guardar las exclusiones: {message}" } }, "layoutSettings": { diff --git a/locales/fr.json b/locales/fr.json index 1aa700ac..90452dc9 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -253,12 +253,12 @@ "autoplayOnHoverHelp": "Lire les aperçus vidéo uniquement lors du survol" }, "autoOrganizeExclusions": { - "label": "Auto-organize exclusions", - "placeholder": "Example: extras/*, */backups/*; *_temp.safetensors", - "help": "Skip moving files that match these wildcard patterns. Separate multiple patterns with commas or semicolons.", + "label": "Exclusions de l'auto-organisation", + "placeholder": "Exemple : curated/*, */backups/*; *_temp.safetensors", + "help": "Ignorer les fichiers correspondant à ces motifs génériques. Séparez plusieurs motifs par des virgules ou des points-virgules.", "validation": { - "noPatterns": "Enter at least one pattern separated by commas or semicolons.", - "saveFailed": "Unable to save exclusions: {message}" + "noPatterns": "Entrez au moins un motif séparé par des virgules ou des points-virgules.", + "saveFailed": "Impossible d'enregistrer les exclusions : {message}" } }, "layoutSettings": { diff --git a/locales/he.json b/locales/he.json index 8945d9ea..33b5bb9f 100644 --- a/locales/he.json +++ b/locales/he.json @@ -253,12 +253,12 @@ "autoplayOnHoverHelp": "נגן תצוגות מקדימות של וידאו רק בעת ריחוף מעליהן" }, "autoOrganizeExclusions": { - "label": "Auto-organize exclusions", - "placeholder": "Example: extras/*, */backups/*; *_temp.safetensors", - "help": "Skip moving files that match these wildcard patterns. Separate multiple patterns with commas or semicolons.", + "label": "יוצא דופן של ארגון אוטומטי", + "placeholder": "דוגמה: curated/*, */backups/*; *_temp.safetensors", + "help": "דלג על העברת קבצים התואמים לתבניות אלו. הפרד תבניות מרובות בפסיקים או בנקודותיים.", "validation": { - "noPatterns": "Enter at least one pattern separated by commas or semicolons.", - "saveFailed": "Unable to save exclusions: {message}" + "noPatterns": "הזן לפחות תבנית אחת מופרדת בפסיקים או בנקודותיים.", + "saveFailed": "לא ניתן לשמור את ההוצאות: {message}" } }, "layoutSettings": { diff --git a/locales/ja.json b/locales/ja.json index db880232..178ca774 100644 --- a/locales/ja.json +++ b/locales/ja.json @@ -253,12 +253,12 @@ "autoplayOnHoverHelp": "動画プレビューはホバー時にのみ再生されます" }, "autoOrganizeExclusions": { - "label": "Auto-organize exclusions", - "placeholder": "Example: extras/*, */backups/*; *_temp.safetensors", - "help": "Skip moving files that match these wildcard patterns. Separate multiple patterns with commas or semicolons.", + "label": "自動整理除外設定", + "placeholder": "例: curated/*, */backups/*; *_temp.safetensors", + "help": "これらのワイルドカードパターンに一致するファイルの移動をスキップします。複数のパターンはカンマまたはセミコロンで区切ってください。", "validation": { - "noPatterns": "Enter at least one pattern separated by commas or semicolons.", - "saveFailed": "Unable to save exclusions: {message}" + "noPatterns": "カンマまたはセミコロンで区切られた少なくとも1つのパターンを入力してください。", + "saveFailed": "除外設定を保存できませんでした: {message}" } }, "layoutSettings": { diff --git a/locales/ko.json b/locales/ko.json index 2d00250c..394c2bed 100644 --- a/locales/ko.json +++ b/locales/ko.json @@ -253,12 +253,12 @@ "autoplayOnHoverHelp": "마우스를 올렸을 때만 비디오 미리보기를 재생합니다" }, "autoOrganizeExclusions": { - "label": "Auto-organize exclusions", - "placeholder": "Example: extras/*, */backups/*; *_temp.safetensors", - "help": "Skip moving files that match these wildcard patterns. Separate multiple patterns with commas or semicolons.", + "label": "자동 정리 제외 항목", + "placeholder": "예: curated/*, */backups/*; *_temp.safetensors", + "help": "이 와일드카드 패턴과 일치하는 파일 이동을 건너뜁니다. 여러 패턴은 쉼표 또는 세미콜론으로 구분하십시오.", "validation": { - "noPatterns": "Enter at least one pattern separated by commas or semicolons.", - "saveFailed": "Unable to save exclusions: {message}" + "noPatterns": "쉼표 또는 세미콜론으로 구분된 최소한 하나의 패턴을 입력하십시오.", + "saveFailed": "제외 항목을 저장할 수 없습니다: {message}" } }, "layoutSettings": { diff --git a/locales/ru.json b/locales/ru.json index 4fa68fc4..6c3e1587 100644 --- a/locales/ru.json +++ b/locales/ru.json @@ -253,12 +253,12 @@ "autoplayOnHoverHelp": "Воспроизводить превью видео только при наведении курсора" }, "autoOrganizeExclusions": { - "label": "Auto-organize exclusions", - "placeholder": "Example: extras/*, */backups/*; *_temp.safetensors", - "help": "Skip moving files that match these wildcard patterns. Separate multiple patterns with commas or semicolons.", + "label": "Исключения автосортировки", + "placeholder": "Пример: curated/*, */backups/*; *_temp.safetensors", + "help": "Пропускать перемещение файлов, соответствующих этим шаблонам. Разделяйте несколько шаблонов запятыми или точками с запятой.", "validation": { - "noPatterns": "Enter at least one pattern separated by commas or semicolons.", - "saveFailed": "Unable to save exclusions: {message}" + "noPatterns": "Введите хотя бы один шаблон, разделенный запятыми или точками с запятой.", + "saveFailed": "Не удалось сохранить исключения: {message}" } }, "layoutSettings": { diff --git a/locales/zh-CN.json b/locales/zh-CN.json index 4ac873af..1bc61118 100644 --- a/locales/zh-CN.json +++ b/locales/zh-CN.json @@ -253,12 +253,12 @@ "autoplayOnHoverHelp": "仅在悬停时播放视频预览" }, "autoOrganizeExclusions": { - "label": "Auto-organize exclusions", - "placeholder": "Example: extras/*, */backups/*; *_temp.safetensors", - "help": "Skip moving files that match these wildcard patterns. Separate multiple patterns with commas or semicolons.", + "label": "自动整理排除项", + "placeholder": "示例: curated/*, */backups/*; *_temp.safetensors", + "help": "跳过与这些通配符模式匹配的文件。多个模式用逗号或分号分隔。", "validation": { - "noPatterns": "Enter at least one pattern separated by commas or semicolons.", - "saveFailed": "Unable to save exclusions: {message}" + "noPatterns": "请输入至少一个用逗号或分号分隔的模式。", + "saveFailed": "无法保存排除项:{message}" } }, "layoutSettings": { diff --git a/locales/zh-TW.json b/locales/zh-TW.json index 6afad627..a524c3e2 100644 --- a/locales/zh-TW.json +++ b/locales/zh-TW.json @@ -253,12 +253,12 @@ "autoplayOnHoverHelp": "僅在滑鼠懸停時播放影片預覽" }, "autoOrganizeExclusions": { - "label": "Auto-organize exclusions", - "placeholder": "Example: extras/*, */backups/*; *_temp.safetensors", - "help": "Skip moving files that match these wildcard patterns. Separate multiple patterns with commas or semicolons.", + "label": "自動整理排除項目", + "placeholder": "範例: curated/*, */backups/*; *_temp.safetensors", + "help": "跳過符合這些萬用字元模式的檔案。多個模式請用逗號或分號分隔。", "validation": { - "noPatterns": "Enter at least one pattern separated by commas or semicolons.", - "saveFailed": "Unable to save exclusions: {message}" + "noPatterns": "請輸入至少一個以逗號或分號分隔的模式。", + "saveFailed": "無法儲存排除項目:{message}" } }, "layoutSettings": { diff --git a/py/routes/handlers/misc_handlers.py b/py/routes/handlers/misc_handlers.py index 1e00ab75..d24473c3 100644 --- a/py/routes/handlers/misc_handlers.py +++ b/py/routes/handlers/misc_handlers.py @@ -202,6 +202,7 @@ class SettingsHandler: "model_card_footer_action", "model_name_display", "update_flag_strategy", + "auto_organize_exclusions", ) _PROXY_KEYS = {"proxy_enabled", "proxy_host", "proxy_port", "proxy_username", "proxy_password", "proxy_type"} diff --git a/py/services/model_file_service.py b/py/services/model_file_service.py index 83aa5a01..2e6a11d9 100644 --- a/py/services/model_file_service.py +++ b/py/services/model_file_service.py @@ -117,19 +117,18 @@ class ModelFileService: else: result.operation_type = 'all' + model_roots = self.get_model_roots() + if not model_roots: + raise ValueError('No model roots configured') + if normalized_exclusions: all_models = [ model for model in all_models if not self._should_exclude_model( - model.get('file_path'), normalized_exclusions + model.get('file_path'), normalized_exclusions, model_roots ) ] - - # Get model roots for this scanner - model_roots = self.get_model_roots() - if not model_roots: - raise ValueError('No model roots configured') # Check if flat structure is configured for this model type settings_manager = get_settings_manager() @@ -151,7 +150,34 @@ class ModelFileService: 'skipped': 0, 'operation_type': result.operation_type }) - + + if result.total == 0: + if progress_callback: + await asyncio.sleep(0.1) + payload = { + 'type': 'auto_organize_progress', + 'total': 0, + 'processed': 0, + 'success': 0, + 'failures': 0, + 'skipped': 0, + 'operation_type': result.operation_type + } + await progress_callback.on_progress({**payload, 'status': 'processing'}) + await progress_callback.on_progress({ + **payload, + 'status': 'cleaning', + 'message': 'Cleaning up empty directories...' + }) + result.cleanup_counts = {} + await progress_callback.on_progress({ + **payload, + 'status': 'completed', + 'cleanup': result.cleanup_counts + }) + + return result + # Process models in batches await self._process_models_in_batches( all_models, @@ -325,16 +351,35 @@ class ModelFileService: return None def _should_exclude_model( - self, file_path: Optional[str], patterns: Sequence[str] + self, + file_path: Optional[str], + patterns: Sequence[str], + model_roots: Sequence[str], ) -> bool: if not file_path or not patterns: return False normalized_path = os.path.normpath(file_path).replace(os.sep, '/') filename = os.path.basename(normalized_path) + relative_path = None + + if model_roots: + root = self._find_model_root(file_path, list(model_roots)) + if root: + normalized_root = os.path.normpath(root) + try: + relative = os.path.relpath(file_path, normalized_root) + except ValueError: + relative = None + if relative is not None: + relative_path = relative.replace(os.sep, '/') for pattern in patterns: - if fnmatch.fnmatch(filename, pattern) or fnmatch.fnmatch(normalized_path, pattern): + if fnmatch.fnmatch(filename, pattern): + return True + if relative_path and fnmatch.fnmatch(relative_path, pattern): + return True + if fnmatch.fnmatch(normalized_path, pattern): return True return False @@ -493,4 +538,4 @@ class ModelMoveService: 'results': [], 'success_count': 0, 'failure_count': len(file_paths) - } \ No newline at end of file + } diff --git a/static/css/components/modal/settings-modal.css b/static/css/components/modal/settings-modal.css index e3fb5fdc..a8ee97fe 100644 --- a/static/css/components/modal/settings-modal.css +++ b/static/css/components/modal/settings-modal.css @@ -233,6 +233,11 @@ resize: vertical; } +.auto-organize-exclusions-input { + width: 100%; + box-sizing: border-box; +} + .priority-tags-input:focus { border-color: var(--lora-accent); outline: none; @@ -261,6 +266,10 @@ margin-bottom: 0; } +.auto-organize-exclusions-item { + gap: var(--space-2); +} + .priority-tags-example { font-size: 0.85em; opacity: 0.8; diff --git a/static/js/managers/SettingsManager.js b/static/js/managers/SettingsManager.js index e954b712..2a7aa6b1 100644 --- a/static/js/managers/SettingsManager.js +++ b/static/js/managers/SettingsManager.js @@ -349,6 +349,16 @@ export class SettingsManager { } }); + const autoOrganizeInput = document.getElementById('autoOrganizeExclusions'); + if (autoOrganizeInput) { + autoOrganizeInput.addEventListener('keydown', (event) => { + if (event.key === 'Enter' && !event.shiftKey) { + event.preventDefault(); + this.saveAutoOrganizeExclusions(); + } + }); + } + this.setupPriorityTagInputs(); this.initialized = true; diff --git a/templates/components/modals/settings_modal.html b/templates/components/modals/settings_modal.html index 9b1f1874..86d3b63f 100644 --- a/templates/components/modals/settings_modal.html +++ b/templates/components/modals/settings_modal.html @@ -341,29 +341,6 @@ -
-

{{ t('settings.sections.autoOrganize') }}

-
-
-
- -
-
- -
-
-
- {{ t('settings.autoOrganizeExclusions.help') }} -
-
-
-
-

{{ t('settings.downloadPathTemplates.title') }}

@@ -524,6 +501,25 @@
+
+
+
+ +
+
+
+ {{ t('settings.autoOrganizeExclusions.help') }} +
+ +
+
+

{{ t('settings.sections.exampleImages') }}