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:
Will Miao
2025-11-20 18:33:48 +08:00
parent c533a8e7bf
commit 26e4895807
15 changed files with 140 additions and 79 deletions

View File

@@ -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": {

View File

@@ -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.",

View File

@@ -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": {

View File

@@ -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": {

View File

@@ -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": {

View File

@@ -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": {

View File

@@ -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": {

View File

@@ -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": {

View File

@@ -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": {

View File

@@ -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": {

View File

@@ -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"}

View File

@@ -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)
}
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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>