mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-03-25 15:15:44 -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"
|
"autoplayOnHoverHelp": "Video-Vorschauen nur beim Darüberfahren mit der Maus abspielen"
|
||||||
},
|
},
|
||||||
"autoOrganizeExclusions": {
|
"autoOrganizeExclusions": {
|
||||||
"label": "Auto-organize exclusions",
|
"label": "Auto-Organisierungs-Ausnahmen",
|
||||||
"placeholder": "Example: extras/*, */backups/*; *_temp.safetensors",
|
"placeholder": "Beispiel: curated/*, */backups/*; *_temp.safetensors",
|
||||||
"help": "Skip moving files that match these wildcard patterns. Separate multiple patterns with commas or semicolons.",
|
"help": "Dateien überspringen, die mit diesen Wildcard-Mustern übereinstimmen. Mehrere Muster mit Kommas oder Semikolons trennen.",
|
||||||
"validation": {
|
"validation": {
|
||||||
"noPatterns": "Enter at least one pattern separated by commas or semicolons.",
|
"noPatterns": "Geben Sie mindestens ein Muster ein, getrennt durch Kommas oder Semikolons.",
|
||||||
"saveFailed": "Unable to save exclusions: {message}"
|
"saveFailed": "Fehler beim Speichern der Ausschlüsse: {message}"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"layoutSettings": {
|
"layoutSettings": {
|
||||||
|
|||||||
@@ -254,7 +254,7 @@
|
|||||||
},
|
},
|
||||||
"autoOrganizeExclusions": {
|
"autoOrganizeExclusions": {
|
||||||
"label": "Auto-organize exclusions",
|
"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.",
|
"help": "Skip moving files that match these wildcard patterns. Separate multiple patterns with commas or semicolons.",
|
||||||
"validation": {
|
"validation": {
|
||||||
"noPatterns": "Enter at least one pattern separated by commas or semicolons.",
|
"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"
|
"autoplayOnHoverHelp": "Solo reproducir vistas previas de video al pasar el ratón sobre ellas"
|
||||||
},
|
},
|
||||||
"autoOrganizeExclusions": {
|
"autoOrganizeExclusions": {
|
||||||
"label": "Auto-organize exclusions",
|
"label": "Exclusiones de auto-organización",
|
||||||
"placeholder": "Example: extras/*, */backups/*; *_temp.safetensors",
|
"placeholder": "Ejemplo: curated/*, */backups/*; *_temp.safetensors",
|
||||||
"help": "Skip moving files that match these wildcard patterns. Separate multiple patterns with commas or semicolons.",
|
"help": "Omitir archivos que coincidan con estos patrones comodín. Separe múltiples patrones con comas o puntos y comas.",
|
||||||
"validation": {
|
"validation": {
|
||||||
"noPatterns": "Enter at least one pattern separated by commas or semicolons.",
|
"noPatterns": "Ingrese al menos un patrón separado por comas o puntos y comas.",
|
||||||
"saveFailed": "Unable to save exclusions: {message}"
|
"saveFailed": "No se pudieron guardar las exclusiones: {message}"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"layoutSettings": {
|
"layoutSettings": {
|
||||||
|
|||||||
@@ -253,12 +253,12 @@
|
|||||||
"autoplayOnHoverHelp": "Lire les aperçus vidéo uniquement lors du survol"
|
"autoplayOnHoverHelp": "Lire les aperçus vidéo uniquement lors du survol"
|
||||||
},
|
},
|
||||||
"autoOrganizeExclusions": {
|
"autoOrganizeExclusions": {
|
||||||
"label": "Auto-organize exclusions",
|
"label": "Exclusions de l'auto-organisation",
|
||||||
"placeholder": "Example: extras/*, */backups/*; *_temp.safetensors",
|
"placeholder": "Exemple : curated/*, */backups/*; *_temp.safetensors",
|
||||||
"help": "Skip moving files that match these wildcard patterns. Separate multiple patterns with commas or semicolons.",
|
"help": "Ignorer les fichiers correspondant à ces motifs génériques. Séparez plusieurs motifs par des virgules ou des points-virgules.",
|
||||||
"validation": {
|
"validation": {
|
||||||
"noPatterns": "Enter at least one pattern separated by commas or semicolons.",
|
"noPatterns": "Entrez au moins un motif séparé par des virgules ou des points-virgules.",
|
||||||
"saveFailed": "Unable to save exclusions: {message}"
|
"saveFailed": "Impossible d'enregistrer les exclusions : {message}"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"layoutSettings": {
|
"layoutSettings": {
|
||||||
|
|||||||
@@ -253,12 +253,12 @@
|
|||||||
"autoplayOnHoverHelp": "נגן תצוגות מקדימות של וידאו רק בעת ריחוף מעליהן"
|
"autoplayOnHoverHelp": "נגן תצוגות מקדימות של וידאו רק בעת ריחוף מעליהן"
|
||||||
},
|
},
|
||||||
"autoOrganizeExclusions": {
|
"autoOrganizeExclusions": {
|
||||||
"label": "Auto-organize exclusions",
|
"label": "יוצא דופן של ארגון אוטומטי",
|
||||||
"placeholder": "Example: extras/*, */backups/*; *_temp.safetensors",
|
"placeholder": "דוגמה: curated/*, */backups/*; *_temp.safetensors",
|
||||||
"help": "Skip moving files that match these wildcard patterns. Separate multiple patterns with commas or semicolons.",
|
"help": "דלג על העברת קבצים התואמים לתבניות אלו. הפרד תבניות מרובות בפסיקים או בנקודותיים.",
|
||||||
"validation": {
|
"validation": {
|
||||||
"noPatterns": "Enter at least one pattern separated by commas or semicolons.",
|
"noPatterns": "הזן לפחות תבנית אחת מופרדת בפסיקים או בנקודותיים.",
|
||||||
"saveFailed": "Unable to save exclusions: {message}"
|
"saveFailed": "לא ניתן לשמור את ההוצאות: {message}"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"layoutSettings": {
|
"layoutSettings": {
|
||||||
|
|||||||
@@ -253,12 +253,12 @@
|
|||||||
"autoplayOnHoverHelp": "動画プレビューはホバー時にのみ再生されます"
|
"autoplayOnHoverHelp": "動画プレビューはホバー時にのみ再生されます"
|
||||||
},
|
},
|
||||||
"autoOrganizeExclusions": {
|
"autoOrganizeExclusions": {
|
||||||
"label": "Auto-organize exclusions",
|
"label": "自動整理除外設定",
|
||||||
"placeholder": "Example: extras/*, */backups/*; *_temp.safetensors",
|
"placeholder": "例: curated/*, */backups/*; *_temp.safetensors",
|
||||||
"help": "Skip moving files that match these wildcard patterns. Separate multiple patterns with commas or semicolons.",
|
"help": "これらのワイルドカードパターンに一致するファイルの移動をスキップします。複数のパターンはカンマまたはセミコロンで区切ってください。",
|
||||||
"validation": {
|
"validation": {
|
||||||
"noPatterns": "Enter at least one pattern separated by commas or semicolons.",
|
"noPatterns": "カンマまたはセミコロンで区切られた少なくとも1つのパターンを入力してください。",
|
||||||
"saveFailed": "Unable to save exclusions: {message}"
|
"saveFailed": "除外設定を保存できませんでした: {message}"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"layoutSettings": {
|
"layoutSettings": {
|
||||||
|
|||||||
@@ -253,12 +253,12 @@
|
|||||||
"autoplayOnHoverHelp": "마우스를 올렸을 때만 비디오 미리보기를 재생합니다"
|
"autoplayOnHoverHelp": "마우스를 올렸을 때만 비디오 미리보기를 재생합니다"
|
||||||
},
|
},
|
||||||
"autoOrganizeExclusions": {
|
"autoOrganizeExclusions": {
|
||||||
"label": "Auto-organize exclusions",
|
"label": "자동 정리 제외 항목",
|
||||||
"placeholder": "Example: extras/*, */backups/*; *_temp.safetensors",
|
"placeholder": "예: curated/*, */backups/*; *_temp.safetensors",
|
||||||
"help": "Skip moving files that match these wildcard patterns. Separate multiple patterns with commas or semicolons.",
|
"help": "이 와일드카드 패턴과 일치하는 파일 이동을 건너뜁니다. 여러 패턴은 쉼표 또는 세미콜론으로 구분하십시오.",
|
||||||
"validation": {
|
"validation": {
|
||||||
"noPatterns": "Enter at least one pattern separated by commas or semicolons.",
|
"noPatterns": "쉼표 또는 세미콜론으로 구분된 최소한 하나의 패턴을 입력하십시오.",
|
||||||
"saveFailed": "Unable to save exclusions: {message}"
|
"saveFailed": "제외 항목을 저장할 수 없습니다: {message}"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"layoutSettings": {
|
"layoutSettings": {
|
||||||
|
|||||||
@@ -253,12 +253,12 @@
|
|||||||
"autoplayOnHoverHelp": "Воспроизводить превью видео только при наведении курсора"
|
"autoplayOnHoverHelp": "Воспроизводить превью видео только при наведении курсора"
|
||||||
},
|
},
|
||||||
"autoOrganizeExclusions": {
|
"autoOrganizeExclusions": {
|
||||||
"label": "Auto-organize exclusions",
|
"label": "Исключения автосортировки",
|
||||||
"placeholder": "Example: extras/*, */backups/*; *_temp.safetensors",
|
"placeholder": "Пример: curated/*, */backups/*; *_temp.safetensors",
|
||||||
"help": "Skip moving files that match these wildcard patterns. Separate multiple patterns with commas or semicolons.",
|
"help": "Пропускать перемещение файлов, соответствующих этим шаблонам. Разделяйте несколько шаблонов запятыми или точками с запятой.",
|
||||||
"validation": {
|
"validation": {
|
||||||
"noPatterns": "Enter at least one pattern separated by commas or semicolons.",
|
"noPatterns": "Введите хотя бы один шаблон, разделенный запятыми или точками с запятой.",
|
||||||
"saveFailed": "Unable to save exclusions: {message}"
|
"saveFailed": "Не удалось сохранить исключения: {message}"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"layoutSettings": {
|
"layoutSettings": {
|
||||||
|
|||||||
@@ -253,12 +253,12 @@
|
|||||||
"autoplayOnHoverHelp": "仅在悬停时播放视频预览"
|
"autoplayOnHoverHelp": "仅在悬停时播放视频预览"
|
||||||
},
|
},
|
||||||
"autoOrganizeExclusions": {
|
"autoOrganizeExclusions": {
|
||||||
"label": "Auto-organize exclusions",
|
"label": "自动整理排除项",
|
||||||
"placeholder": "Example: extras/*, */backups/*; *_temp.safetensors",
|
"placeholder": "示例: curated/*, */backups/*; *_temp.safetensors",
|
||||||
"help": "Skip moving files that match these wildcard patterns. Separate multiple patterns with commas or semicolons.",
|
"help": "跳过与这些通配符模式匹配的文件。多个模式用逗号或分号分隔。",
|
||||||
"validation": {
|
"validation": {
|
||||||
"noPatterns": "Enter at least one pattern separated by commas or semicolons.",
|
"noPatterns": "请输入至少一个用逗号或分号分隔的模式。",
|
||||||
"saveFailed": "Unable to save exclusions: {message}"
|
"saveFailed": "无法保存排除项:{message}"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"layoutSettings": {
|
"layoutSettings": {
|
||||||
|
|||||||
@@ -253,12 +253,12 @@
|
|||||||
"autoplayOnHoverHelp": "僅在滑鼠懸停時播放影片預覽"
|
"autoplayOnHoverHelp": "僅在滑鼠懸停時播放影片預覽"
|
||||||
},
|
},
|
||||||
"autoOrganizeExclusions": {
|
"autoOrganizeExclusions": {
|
||||||
"label": "Auto-organize exclusions",
|
"label": "自動整理排除項目",
|
||||||
"placeholder": "Example: extras/*, */backups/*; *_temp.safetensors",
|
"placeholder": "範例: curated/*, */backups/*; *_temp.safetensors",
|
||||||
"help": "Skip moving files that match these wildcard patterns. Separate multiple patterns with commas or semicolons.",
|
"help": "跳過符合這些萬用字元模式的檔案。多個模式請用逗號或分號分隔。",
|
||||||
"validation": {
|
"validation": {
|
||||||
"noPatterns": "Enter at least one pattern separated by commas or semicolons.",
|
"noPatterns": "請輸入至少一個以逗號或分號分隔的模式。",
|
||||||
"saveFailed": "Unable to save exclusions: {message}"
|
"saveFailed": "無法儲存排除項目:{message}"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"layoutSettings": {
|
"layoutSettings": {
|
||||||
|
|||||||
@@ -202,6 +202,7 @@ class SettingsHandler:
|
|||||||
"model_card_footer_action",
|
"model_card_footer_action",
|
||||||
"model_name_display",
|
"model_name_display",
|
||||||
"update_flag_strategy",
|
"update_flag_strategy",
|
||||||
|
"auto_organize_exclusions",
|
||||||
)
|
)
|
||||||
|
|
||||||
_PROXY_KEYS = {"proxy_enabled", "proxy_host", "proxy_port", "proxy_username", "proxy_password", "proxy_type"}
|
_PROXY_KEYS = {"proxy_enabled", "proxy_host", "proxy_port", "proxy_username", "proxy_password", "proxy_type"}
|
||||||
|
|||||||
@@ -117,20 +117,19 @@ class ModelFileService:
|
|||||||
else:
|
else:
|
||||||
result.operation_type = 'all'
|
result.operation_type = 'all'
|
||||||
|
|
||||||
|
model_roots = self.get_model_roots()
|
||||||
|
if not model_roots:
|
||||||
|
raise ValueError('No model roots configured')
|
||||||
|
|
||||||
if normalized_exclusions:
|
if normalized_exclusions:
|
||||||
all_models = [
|
all_models = [
|
||||||
model
|
model
|
||||||
for model in all_models
|
for model in all_models
|
||||||
if not self._should_exclude_model(
|
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
|
# Check if flat structure is configured for this model type
|
||||||
settings_manager = get_settings_manager()
|
settings_manager = get_settings_manager()
|
||||||
path_template = settings_manager.get_download_path_template(self.model_type)
|
path_template = settings_manager.get_download_path_template(self.model_type)
|
||||||
@@ -152,6 +151,33 @@ class ModelFileService:
|
|||||||
'operation_type': result.operation_type
|
'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
|
# Process models in batches
|
||||||
await self._process_models_in_batches(
|
await self._process_models_in_batches(
|
||||||
all_models,
|
all_models,
|
||||||
@@ -325,16 +351,35 @@ class ModelFileService:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
def _should_exclude_model(
|
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:
|
) -> bool:
|
||||||
if not file_path or not patterns:
|
if not file_path or not patterns:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
normalized_path = os.path.normpath(file_path).replace(os.sep, '/')
|
normalized_path = os.path.normpath(file_path).replace(os.sep, '/')
|
||||||
filename = os.path.basename(normalized_path)
|
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:
|
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 True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|||||||
@@ -233,6 +233,11 @@
|
|||||||
resize: vertical;
|
resize: vertical;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.auto-organize-exclusions-input {
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
.priority-tags-input:focus {
|
.priority-tags-input:focus {
|
||||||
border-color: var(--lora-accent);
|
border-color: var(--lora-accent);
|
||||||
outline: none;
|
outline: none;
|
||||||
@@ -261,6 +266,10 @@
|
|||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.auto-organize-exclusions-item {
|
||||||
|
gap: var(--space-2);
|
||||||
|
}
|
||||||
|
|
||||||
.priority-tags-example {
|
.priority-tags-example {
|
||||||
font-size: 0.85em;
|
font-size: 0.85em;
|
||||||
opacity: 0.8;
|
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.setupPriorityTagInputs();
|
||||||
|
|
||||||
this.initialized = true;
|
this.initialized = true;
|
||||||
|
|||||||
@@ -341,29 +341,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</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 -->
|
<!-- Default Path Customization Section -->
|
||||||
<div class="settings-section">
|
<div class="settings-section">
|
||||||
<h3>{{ t('settings.downloadPathTemplates.title') }}</h3>
|
<h3>{{ t('settings.downloadPathTemplates.title') }}</h3>
|
||||||
@@ -524,6 +501,25 @@
|
|||||||
</div>
|
</div>
|
||||||
</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 -->
|
<!-- Add Example Images Settings Section -->
|
||||||
<div class="settings-section">
|
<div class="settings-section">
|
||||||
<h3>{{ t('settings.sections.exampleImages') }}</h3>
|
<h3>{{ t('settings.sections.exampleImages') }}</h3>
|
||||||
|
|||||||
Reference in New Issue
Block a user