mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-03-21 21:22:11 -03:00
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
This commit is contained in:
@@ -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": {
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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"}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -341,29 +341,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settings-section">
|
||||
<h3>{{ t('settings.sections.autoOrganize') }}</h3>
|
||||
<div class="setting-item">
|
||||
<div class="setting-row">
|
||||
<div class="setting-info">
|
||||
<label for="autoOrganizeExclusions">{{ t('settings.autoOrganizeExclusions.label') }}</label>
|
||||
</div>
|
||||
<div class="setting-control">
|
||||
<textarea
|
||||
id="autoOrganizeExclusions"
|
||||
rows="3"
|
||||
placeholder="{{ t('settings.autoOrganizeExclusions.placeholder') }}"
|
||||
onblur="settingsManager.saveAutoOrganizeExclusions()"
|
||||
></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="input-help">
|
||||
{{ t('settings.autoOrganizeExclusions.help') }}
|
||||
</div>
|
||||
<div class="settings-input-error-message" id="autoOrganizeExclusionsError"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Default Path Customization Section -->
|
||||
<div class="settings-section">
|
||||
<h3>{{ t('settings.downloadPathTemplates.title') }}</h3>
|
||||
@@ -524,6 +501,25 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="setting-item priority-tags-item auto-organize-exclusions-item">
|
||||
<div class="setting-row priority-tags-header">
|
||||
<div class="setting-info priority-tags-info">
|
||||
<label>{{ t('settings.autoOrganizeExclusions.label') }}</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="input-help">
|
||||
{{ t('settings.autoOrganizeExclusions.help') }}
|
||||
</div>
|
||||
<textarea
|
||||
id="autoOrganizeExclusions"
|
||||
class="priority-tags-input auto-organize-exclusions-input"
|
||||
rows="3"
|
||||
placeholder="{{ t('settings.autoOrganizeExclusions.placeholder') }}"
|
||||
onblur="settingsManager.saveAutoOrganizeExclusions()"
|
||||
></textarea>
|
||||
<div class="settings-input-error-message" id="autoOrganizeExclusionsError"></div>
|
||||
</div>
|
||||
|
||||
<!-- Add Example Images Settings Section -->
|
||||
<div class="settings-section">
|
||||
<h3>{{ t('settings.sections.exampleImages') }}</h3>
|
||||
|
||||
Reference in New Issue
Block a user