mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-09-20 18:51:26 -03:00
feat(settings): empty filename template reverts to recorded original filename (#1071)
Redefine the empty download filename template from a no-op to a bulk revert: FilenameTemplateUseCase resolves the target from each model's recorded original_file_name sidecar entry (skipping models without one), which resolves follow-ups 1 and 2 with a single coherent semantic shared by the download and bulk-apply paths. Also replace the browser-native confirm() with a self-managed confirmation modal (filenameTemplateConfirmModal) that stacks above the settings modal, since ModalManager would close the settings modal when opening a registered one.
This commit is contained in:
@@ -2,8 +2,9 @@
|
|||||||
|
|
||||||
**Issue:** [#1071 — Lora Renaming](https://github.com/willmiao/ComfyUI-Lora-Manager/issues/1071)
|
**Issue:** [#1071 — Lora Renaming](https://github.com/willmiao/ComfyUI-Lora-Manager/issues/1071)
|
||||||
**Status:** Core feature **implemented** (2026-09-19, commit `2bc9860b`,
|
**Status:** Core feature **implemented** (2026-09-19, commit `2bc9860b`,
|
||||||
preceded by the settings-tab split in `327da046`). This document records the
|
preceded by the settings-tab split in `327da046`). Follow-ups 1 and 2 were
|
||||||
follow-up items deliberately left out of that commit, for a future session.
|
resolved together on 2026-09-19 by redefining the empty template as
|
||||||
|
"revert to recorded original filename" (see below). Follow-up 3 remains open.
|
||||||
|
|
||||||
## What shipped in `2bc9860b`
|
## What shipped in `2bc9860b`
|
||||||
|
|
||||||
@@ -30,47 +31,67 @@ metadata pointer updates, `original_file_name` recording, idempotency,
|
|||||||
conflict handling (failure counted, batch continues), empty-template no-op,
|
conflict handling (failure counted, batch continues), empty-template no-op,
|
||||||
GET variant.
|
GET variant.
|
||||||
|
|
||||||
## Follow-up 1 — Reword the empty-template validation message
|
## Follow-ups 1 & 2 — RESOLVED: empty template = revert to recorded original
|
||||||
|
|
||||||
**Problem:** the empty-template state currently reads "Valid (keep original
|
Follow-up 1 asked to reword the ambiguous "Valid (keep original filename)"
|
||||||
filename)" (`settings.filenameTemplates.validation.keepOriginal` in
|
empty-template message; Follow-up 2 asked for a bulk revert to the recorded
|
||||||
`locales/en.json`). "Original" is ambiguous — it reads as "restore the
|
`original_file_name`. Both were resolved by a single semantic change: **an
|
||||||
recorded original name", but the semantics are "leave the current filename
|
empty template now means "restore the recorded original filename"** instead of
|
||||||
untouched" (empty template = no-op, confirmed in E2E).
|
"leave the current filename untouched".
|
||||||
|
|
||||||
**Fix:** reword the `en.json` value, e.g. "Valid (leave files unchanged)",
|
Rationale: for never-renamed models a revert is a no-op (no recorded
|
||||||
then re-run `python scripts/sync_translation_keys.py` and retranslate the key
|
original), for renamed models it restores the pre-rename name, and new
|
||||||
in the 9 non-English locales (see `docs/i18n-translation-guidelines.md` §7 —
|
downloads with an empty template keep the download name as before — so the
|
||||||
the owner explicitly asking for translations is the trigger).
|
two contexts (download path and bulk apply) share one coherent meaning, and
|
||||||
|
no separate revert feature or `{recorded_original}` placeholder is needed.
|
||||||
|
|
||||||
## Follow-up 2 — Bulk revert to recorded original filename
|
Implemented changes:
|
||||||
|
|
||||||
**Problem:** renames record `original_file_name` in each model's
|
- `FilenameTemplateUseCase._process_model`: an empty template now resolves
|
||||||
`.metadata.json`, but there is no way to undo in bulk. Restoring today means
|
the target name from the sidecar's `original_file_name` via the injected
|
||||||
renaming each model by hand via the existing single-model rename dialog.
|
`metadata_loader` (default `load_local_metadata`); models without a
|
||||||
Note: the `{original_name}` template placeholder resolves to the **current**
|
recorded original or whose original matches the current name are skipped.
|
||||||
filename stem (`calculate_filename_for_model` reads `file_path` /
|
Cache entries do not project `original_file_name`, so the sidecar is read
|
||||||
`file_name` from the cache entry), so "set template to `{original_name}` and
|
per model.
|
||||||
apply" is **not** a revert path.
|
- `SettingsManager.js`: removed the empty-template early return and the
|
||||||
|
apply-button disable (`updateFilenameTemplateApplyButton` deleted — the
|
||||||
|
button is now always enabled). The browser-native `confirm()` was replaced
|
||||||
|
with `filenameTemplateConfirmModal`
|
||||||
|
(`templates/components/modals/confirm_modals.html`), a **self-managed**
|
||||||
|
modal (like `DirectoryPickerModal`, NOT registered with ModalManager):
|
||||||
|
ModalManager's "close current modal on open" behavior would kill the
|
||||||
|
settings modal underneath. It stacks via `z-index: 10010`
|
||||||
|
(`delete-modal.css`), handles ESC in capture phase with
|
||||||
|
`stopPropagation`, and shows apply vs revert wording
|
||||||
|
(`modals.filenameTemplateConfirm.titleApply` / `titleRevert` /
|
||||||
|
`revertButton`; messages reuse `settings.filenameTemplates.confirmApply` /
|
||||||
|
`confirmRevert`).
|
||||||
|
- `locales/en.json`: reworded `help` / `applyHelp`, replaced
|
||||||
|
`validation.keepOriginal` with `validation.restoreOriginal`
|
||||||
|
("Valid (empty template restores original filenames)"), added
|
||||||
|
`confirmRevert`, removed the now-unused `emptyTemplateInfo`. Other locales
|
||||||
|
re-synced with `[TODO: Translate]` placeholders — retranslation waits for
|
||||||
|
the feature owner's request per `docs/i18n-translation-guidelines.md` §7.
|
||||||
|
- Tests: revert / no-record-skip / same-name-skip cases in
|
||||||
|
`tests/services/test_use_cases.py`; modal confirm-and-revert and
|
||||||
|
cancel paths in
|
||||||
|
`tests/frontend/managers/settingsManager.filenameTemplates.test.js`.
|
||||||
|
|
||||||
**Suggested shape (smallest change):** add a new placeholder, e.g.
|
Sandbox E2E verified (standalone server, sandboxed settings + library under
|
||||||
`{recorded_original}`, resolved in `calculate_filename_for_model` from
|
`/tmp`, 2026-09-19): template apply renames and records
|
||||||
`model_data.get("original_file_name")` (cache entries need to carry the
|
`original_file_name`; empty-template apply reverts to the recorded name;
|
||||||
field — check whether the scanner cache surfaces sidecar extras; if not,
|
revert target occupied by a newer file counts as failure and keeps the
|
||||||
read it in the use case via the metadata loader). Setting the template to
|
current name; models without a recorded original are skipped;
|
||||||
`{recorded_original}` and running "Apply to Library Now" then reverts the
|
apply → revert → re-apply cycles repeat cleanly.
|
||||||
library. Models never renamed render an empty segment → skipped, which is
|
|
||||||
the desired behaviour.
|
|
||||||
|
|
||||||
Alternative shape: a dedicated revert endpoint + button. More code, clearer
|
Standing caveats (unchanged):
|
||||||
UX; only worth it if the placeholder approach proves too obscure.
|
|
||||||
|
|
||||||
**Caveats either way:**
|
- The revert target may collide with an existing file — the existing conflict
|
||||||
- The revert target may collide with an existing file (a file downloaded
|
|
||||||
after the rename may already carry that name) — the existing conflict
|
|
||||||
handling (count as failure, keep current name) covers this.
|
handling (count as failure, keep current name) covers this.
|
||||||
- `original_file_name` only exists for models renamed after `2bc9860b`;
|
- `original_file_name` only exists for models renamed after `2bc9860b`;
|
||||||
older renames have no recorded original and must be skipped.
|
older renames have no recorded original and are skipped.
|
||||||
|
- `original_file_name` is kept (not cleared) after a revert, so
|
||||||
|
apply → revert → re-apply stays repeatable.
|
||||||
|
|
||||||
## Follow-up 3 — Cross-page refresh after bulk apply
|
## Follow-up 3 — Cross-page refresh after bulk apply
|
||||||
|
|
||||||
|
|||||||
+7
-2
@@ -689,9 +689,9 @@
|
|||||||
"applyButton": "[TODO: Translate] Apply to Library Now",
|
"applyButton": "[TODO: Translate] Apply to Library Now",
|
||||||
"applyHelp": "[TODO: Translate] Renames all existing files of this model type according to the template. Warning: renaming changes the relative path seen by ComfyUI loaders, so existing workflows referencing the old filename may need to be updated. The original filename is preserved in each model's metadata.",
|
"applyHelp": "[TODO: Translate] Renames all existing files of this model type according to the template. Warning: renaming changes the relative path seen by ComfyUI loaders, so existing workflows referencing the old filename may need to be updated. The original filename is preserved in each model's metadata.",
|
||||||
"confirmApply": "[TODO: Translate] Rename all existing files of this model type according to the filename template? This changes the relative path seen by ComfyUI loaders. The original filename is preserved in each model's metadata.",
|
"confirmApply": "[TODO: Translate] Rename all existing files of this model type according to the filename template? This changes the relative path seen by ComfyUI loaders. The original filename is preserved in each model's metadata.",
|
||||||
"emptyTemplateInfo": "[TODO: Translate] No filename template configured for this model type — original filenames are kept, nothing to apply.",
|
"confirmRevert": "[TODO: Translate] Restore the recorded original filenames of all previously renamed files of this model type? This changes the relative path seen by ComfyUI loaders. Files without a recorded original filename are skipped.",
|
||||||
"validation": {
|
"validation": {
|
||||||
"keepOriginal": "[TODO: Translate] Valid (keep original filename)",
|
"restoreOriginal": "[TODO: Translate] Valid (empty template restores original filenames)",
|
||||||
"invalidChars": "[TODO: Translate] Invalid characters detected (a filename cannot contain / \\ < > : \" | ? *)",
|
"invalidChars": "[TODO: Translate] Invalid characters detected (a filename cannot contain / \\ < > : \" | ? *)",
|
||||||
"invalidPlaceholder": "[TODO: Translate] Invalid placeholder: {placeholder}",
|
"invalidPlaceholder": "[TODO: Translate] Invalid placeholder: {placeholder}",
|
||||||
"validTemplate": "[TODO: Translate] Valid template"
|
"validTemplate": "[TODO: Translate] Valid template"
|
||||||
@@ -1626,6 +1626,11 @@
|
|||||||
"tip": "Möchten Sie in Etappen prüfen? Wechseln Sie in den Massenmodus, wählen Sie die benötigten Modelle aus und nutzen Sie anschließend \"Auswahl auf Updates prüfen\".",
|
"tip": "Möchten Sie in Etappen prüfen? Wechseln Sie in den Massenmodus, wählen Sie die benötigten Modelle aus und nutzen Sie anschließend \"Auswahl auf Updates prüfen\".",
|
||||||
"action": "Alles prüfen"
|
"action": "Alles prüfen"
|
||||||
},
|
},
|
||||||
|
"filenameTemplateConfirm": {
|
||||||
|
"titleApply": "[TODO: Translate] Apply filename template to library?",
|
||||||
|
"titleRevert": "[TODO: Translate] Restore original filenames?",
|
||||||
|
"revertButton": "[TODO: Translate] Restore Original Filenames"
|
||||||
|
},
|
||||||
"bulkAddTags": {
|
"bulkAddTags": {
|
||||||
"title": "Tags zu mehreren Modellen hinzufügen",
|
"title": "Tags zu mehreren Modellen hinzufügen",
|
||||||
"description": "Tags hinzufügen zu",
|
"description": "Tags hinzufügen zu",
|
||||||
|
|||||||
+9
-4
@@ -683,15 +683,15 @@
|
|||||||
},
|
},
|
||||||
"filenameTemplates": {
|
"filenameTemplates": {
|
||||||
"title": "Filename Templates",
|
"title": "Filename Templates",
|
||||||
"help": "Configure filenames for downloaded models per model type. Leave empty to keep the original filename. The original filename is always preserved in the model's metadata.",
|
"help": "Configure filenames for downloaded models per model type. Leave empty to keep original filenames on download; applying an empty template restores the recorded original filenames of previously renamed models. The original filename is always preserved in the model's metadata.",
|
||||||
"availablePlaceholders": "Available placeholders:",
|
"availablePlaceholders": "Available placeholders:",
|
||||||
"templatePlaceholder": "Enter filename template (e.g., {base_model}-{model_name}-{version_name})",
|
"templatePlaceholder": "Enter filename template (e.g., {base_model}-{model_name}-{version_name})",
|
||||||
"applyButton": "Apply to Library Now",
|
"applyButton": "Apply to Library Now",
|
||||||
"applyHelp": "Renames all existing files of this model type according to the template. Warning: renaming changes the relative path seen by ComfyUI loaders, so existing workflows referencing the old filename may need to be updated. The original filename is preserved in each model's metadata.",
|
"applyHelp": "Renames all existing files of this model type according to the template; with an empty template, restores the recorded original filenames instead. Warning: renaming changes the relative path seen by ComfyUI loaders, so existing workflows referencing the old filename may need to be updated. The original filename is preserved in each model's metadata.",
|
||||||
"confirmApply": "Rename all existing files of this model type according to the filename template? This changes the relative path seen by ComfyUI loaders. The original filename is preserved in each model's metadata.",
|
"confirmApply": "Rename all existing files of this model type according to the filename template? This changes the relative path seen by ComfyUI loaders. The original filename is preserved in each model's metadata.",
|
||||||
"emptyTemplateInfo": "No filename template configured for this model type — original filenames are kept, nothing to apply.",
|
"confirmRevert": "Restore the recorded original filenames of all previously renamed files of this model type? This changes the relative path seen by ComfyUI loaders. Files without a recorded original filename are skipped.",
|
||||||
"validation": {
|
"validation": {
|
||||||
"keepOriginal": "Valid (keep original filename)",
|
"restoreOriginal": "Valid (empty template restores original filenames)",
|
||||||
"invalidChars": "Invalid characters detected (a filename cannot contain / \\ < > : \" | ? *)",
|
"invalidChars": "Invalid characters detected (a filename cannot contain / \\ < > : \" | ? *)",
|
||||||
"invalidPlaceholder": "Invalid placeholder: {placeholder}",
|
"invalidPlaceholder": "Invalid placeholder: {placeholder}",
|
||||||
"validTemplate": "Valid template"
|
"validTemplate": "Valid template"
|
||||||
@@ -1626,6 +1626,11 @@
|
|||||||
"tip": "To work in smaller batches, switch to bulk mode, choose the ones you need, then use \"Check Updates for Selected\".",
|
"tip": "To work in smaller batches, switch to bulk mode, choose the ones you need, then use \"Check Updates for Selected\".",
|
||||||
"action": "Check All"
|
"action": "Check All"
|
||||||
},
|
},
|
||||||
|
"filenameTemplateConfirm": {
|
||||||
|
"titleApply": "Apply filename template to library?",
|
||||||
|
"titleRevert": "Restore original filenames?",
|
||||||
|
"revertButton": "Restore Original Filenames"
|
||||||
|
},
|
||||||
"bulkAddTags": {
|
"bulkAddTags": {
|
||||||
"title": "Add Tags to Multiple Models",
|
"title": "Add Tags to Multiple Models",
|
||||||
"description": "Add tags to",
|
"description": "Add tags to",
|
||||||
|
|||||||
+7
-2
@@ -689,9 +689,9 @@
|
|||||||
"applyButton": "[TODO: Translate] Apply to Library Now",
|
"applyButton": "[TODO: Translate] Apply to Library Now",
|
||||||
"applyHelp": "[TODO: Translate] Renames all existing files of this model type according to the template. Warning: renaming changes the relative path seen by ComfyUI loaders, so existing workflows referencing the old filename may need to be updated. The original filename is preserved in each model's metadata.",
|
"applyHelp": "[TODO: Translate] Renames all existing files of this model type according to the template. Warning: renaming changes the relative path seen by ComfyUI loaders, so existing workflows referencing the old filename may need to be updated. The original filename is preserved in each model's metadata.",
|
||||||
"confirmApply": "[TODO: Translate] Rename all existing files of this model type according to the filename template? This changes the relative path seen by ComfyUI loaders. The original filename is preserved in each model's metadata.",
|
"confirmApply": "[TODO: Translate] Rename all existing files of this model type according to the filename template? This changes the relative path seen by ComfyUI loaders. The original filename is preserved in each model's metadata.",
|
||||||
"emptyTemplateInfo": "[TODO: Translate] No filename template configured for this model type — original filenames are kept, nothing to apply.",
|
"confirmRevert": "[TODO: Translate] Restore the recorded original filenames of all previously renamed files of this model type? This changes the relative path seen by ComfyUI loaders. Files without a recorded original filename are skipped.",
|
||||||
"validation": {
|
"validation": {
|
||||||
"keepOriginal": "[TODO: Translate] Valid (keep original filename)",
|
"restoreOriginal": "[TODO: Translate] Valid (empty template restores original filenames)",
|
||||||
"invalidChars": "[TODO: Translate] Invalid characters detected (a filename cannot contain / \\ < > : \" | ? *)",
|
"invalidChars": "[TODO: Translate] Invalid characters detected (a filename cannot contain / \\ < > : \" | ? *)",
|
||||||
"invalidPlaceholder": "[TODO: Translate] Invalid placeholder: {placeholder}",
|
"invalidPlaceholder": "[TODO: Translate] Invalid placeholder: {placeholder}",
|
||||||
"validTemplate": "[TODO: Translate] Valid template"
|
"validTemplate": "[TODO: Translate] Valid template"
|
||||||
@@ -1626,6 +1626,11 @@
|
|||||||
"tip": "¿Quieres hacerlo por partes? Activa el modo por lotes, selecciona los modelos que necesites y usa \"Comprobar actualizaciones para la selección\".",
|
"tip": "¿Quieres hacerlo por partes? Activa el modo por lotes, selecciona los modelos que necesites y usa \"Comprobar actualizaciones para la selección\".",
|
||||||
"action": "Comprobar todo"
|
"action": "Comprobar todo"
|
||||||
},
|
},
|
||||||
|
"filenameTemplateConfirm": {
|
||||||
|
"titleApply": "[TODO: Translate] Apply filename template to library?",
|
||||||
|
"titleRevert": "[TODO: Translate] Restore original filenames?",
|
||||||
|
"revertButton": "[TODO: Translate] Restore Original Filenames"
|
||||||
|
},
|
||||||
"bulkAddTags": {
|
"bulkAddTags": {
|
||||||
"title": "Añadir etiquetas a múltiples modelos",
|
"title": "Añadir etiquetas a múltiples modelos",
|
||||||
"description": "Añadir etiquetas a",
|
"description": "Añadir etiquetas a",
|
||||||
|
|||||||
+7
-2
@@ -689,9 +689,9 @@
|
|||||||
"applyButton": "[TODO: Translate] Apply to Library Now",
|
"applyButton": "[TODO: Translate] Apply to Library Now",
|
||||||
"applyHelp": "[TODO: Translate] Renames all existing files of this model type according to the template. Warning: renaming changes the relative path seen by ComfyUI loaders, so existing workflows referencing the old filename may need to be updated. The original filename is preserved in each model's metadata.",
|
"applyHelp": "[TODO: Translate] Renames all existing files of this model type according to the template. Warning: renaming changes the relative path seen by ComfyUI loaders, so existing workflows referencing the old filename may need to be updated. The original filename is preserved in each model's metadata.",
|
||||||
"confirmApply": "[TODO: Translate] Rename all existing files of this model type according to the filename template? This changes the relative path seen by ComfyUI loaders. The original filename is preserved in each model's metadata.",
|
"confirmApply": "[TODO: Translate] Rename all existing files of this model type according to the filename template? This changes the relative path seen by ComfyUI loaders. The original filename is preserved in each model's metadata.",
|
||||||
"emptyTemplateInfo": "[TODO: Translate] No filename template configured for this model type — original filenames are kept, nothing to apply.",
|
"confirmRevert": "[TODO: Translate] Restore the recorded original filenames of all previously renamed files of this model type? This changes the relative path seen by ComfyUI loaders. Files without a recorded original filename are skipped.",
|
||||||
"validation": {
|
"validation": {
|
||||||
"keepOriginal": "[TODO: Translate] Valid (keep original filename)",
|
"restoreOriginal": "[TODO: Translate] Valid (empty template restores original filenames)",
|
||||||
"invalidChars": "[TODO: Translate] Invalid characters detected (a filename cannot contain / \\ < > : \" | ? *)",
|
"invalidChars": "[TODO: Translate] Invalid characters detected (a filename cannot contain / \\ < > : \" | ? *)",
|
||||||
"invalidPlaceholder": "[TODO: Translate] Invalid placeholder: {placeholder}",
|
"invalidPlaceholder": "[TODO: Translate] Invalid placeholder: {placeholder}",
|
||||||
"validTemplate": "[TODO: Translate] Valid template"
|
"validTemplate": "[TODO: Translate] Valid template"
|
||||||
@@ -1626,6 +1626,11 @@
|
|||||||
"tip": "Besoin de procéder par étapes ? Passez en mode groupé, sélectionnez les modèles souhaités puis utilisez \"Vérifier les mises à jour pour la sélection\".",
|
"tip": "Besoin de procéder par étapes ? Passez en mode groupé, sélectionnez les modèles souhaités puis utilisez \"Vérifier les mises à jour pour la sélection\".",
|
||||||
"action": "Tout vérifier"
|
"action": "Tout vérifier"
|
||||||
},
|
},
|
||||||
|
"filenameTemplateConfirm": {
|
||||||
|
"titleApply": "[TODO: Translate] Apply filename template to library?",
|
||||||
|
"titleRevert": "[TODO: Translate] Restore original filenames?",
|
||||||
|
"revertButton": "[TODO: Translate] Restore Original Filenames"
|
||||||
|
},
|
||||||
"bulkAddTags": {
|
"bulkAddTags": {
|
||||||
"title": "Ajouter des tags à plusieurs modèles",
|
"title": "Ajouter des tags à plusieurs modèles",
|
||||||
"description": "Ajouter des tags à",
|
"description": "Ajouter des tags à",
|
||||||
|
|||||||
+7
-2
@@ -689,9 +689,9 @@
|
|||||||
"applyButton": "[TODO: Translate] Apply to Library Now",
|
"applyButton": "[TODO: Translate] Apply to Library Now",
|
||||||
"applyHelp": "[TODO: Translate] Renames all existing files of this model type according to the template. Warning: renaming changes the relative path seen by ComfyUI loaders, so existing workflows referencing the old filename may need to be updated. The original filename is preserved in each model's metadata.",
|
"applyHelp": "[TODO: Translate] Renames all existing files of this model type according to the template. Warning: renaming changes the relative path seen by ComfyUI loaders, so existing workflows referencing the old filename may need to be updated. The original filename is preserved in each model's metadata.",
|
||||||
"confirmApply": "[TODO: Translate] Rename all existing files of this model type according to the filename template? This changes the relative path seen by ComfyUI loaders. The original filename is preserved in each model's metadata.",
|
"confirmApply": "[TODO: Translate] Rename all existing files of this model type according to the filename template? This changes the relative path seen by ComfyUI loaders. The original filename is preserved in each model's metadata.",
|
||||||
"emptyTemplateInfo": "[TODO: Translate] No filename template configured for this model type — original filenames are kept, nothing to apply.",
|
"confirmRevert": "[TODO: Translate] Restore the recorded original filenames of all previously renamed files of this model type? This changes the relative path seen by ComfyUI loaders. Files without a recorded original filename are skipped.",
|
||||||
"validation": {
|
"validation": {
|
||||||
"keepOriginal": "[TODO: Translate] Valid (keep original filename)",
|
"restoreOriginal": "[TODO: Translate] Valid (empty template restores original filenames)",
|
||||||
"invalidChars": "[TODO: Translate] Invalid characters detected (a filename cannot contain / \\ < > : \" | ? *)",
|
"invalidChars": "[TODO: Translate] Invalid characters detected (a filename cannot contain / \\ < > : \" | ? *)",
|
||||||
"invalidPlaceholder": "[TODO: Translate] Invalid placeholder: {placeholder}",
|
"invalidPlaceholder": "[TODO: Translate] Invalid placeholder: {placeholder}",
|
||||||
"validTemplate": "[TODO: Translate] Valid template"
|
"validTemplate": "[TODO: Translate] Valid template"
|
||||||
@@ -1626,6 +1626,11 @@
|
|||||||
"tip": "רוצים לחלק למנות קטנות? עברו למצב בכמות גדולה, בחרו את המודלים הדרושים ואז השתמשו ב\"בדוק עדכונים לנבחרים\".",
|
"tip": "רוצים לחלק למנות קטנות? עברו למצב בכמות גדולה, בחרו את המודלים הדרושים ואז השתמשו ב\"בדוק עדכונים לנבחרים\".",
|
||||||
"action": "בדוק הכל"
|
"action": "בדוק הכל"
|
||||||
},
|
},
|
||||||
|
"filenameTemplateConfirm": {
|
||||||
|
"titleApply": "[TODO: Translate] Apply filename template to library?",
|
||||||
|
"titleRevert": "[TODO: Translate] Restore original filenames?",
|
||||||
|
"revertButton": "[TODO: Translate] Restore Original Filenames"
|
||||||
|
},
|
||||||
"bulkAddTags": {
|
"bulkAddTags": {
|
||||||
"title": "הוסף תגיות למספר מודלים",
|
"title": "הוסף תגיות למספר מודלים",
|
||||||
"description": "הוסף תגיות ל-",
|
"description": "הוסף תגיות ל-",
|
||||||
|
|||||||
+7
-2
@@ -689,9 +689,9 @@
|
|||||||
"applyButton": "[TODO: Translate] Apply to Library Now",
|
"applyButton": "[TODO: Translate] Apply to Library Now",
|
||||||
"applyHelp": "[TODO: Translate] Renames all existing files of this model type according to the template. Warning: renaming changes the relative path seen by ComfyUI loaders, so existing workflows referencing the old filename may need to be updated. The original filename is preserved in each model's metadata.",
|
"applyHelp": "[TODO: Translate] Renames all existing files of this model type according to the template. Warning: renaming changes the relative path seen by ComfyUI loaders, so existing workflows referencing the old filename may need to be updated. The original filename is preserved in each model's metadata.",
|
||||||
"confirmApply": "[TODO: Translate] Rename all existing files of this model type according to the filename template? This changes the relative path seen by ComfyUI loaders. The original filename is preserved in each model's metadata.",
|
"confirmApply": "[TODO: Translate] Rename all existing files of this model type according to the filename template? This changes the relative path seen by ComfyUI loaders. The original filename is preserved in each model's metadata.",
|
||||||
"emptyTemplateInfo": "[TODO: Translate] No filename template configured for this model type — original filenames are kept, nothing to apply.",
|
"confirmRevert": "[TODO: Translate] Restore the recorded original filenames of all previously renamed files of this model type? This changes the relative path seen by ComfyUI loaders. Files without a recorded original filename are skipped.",
|
||||||
"validation": {
|
"validation": {
|
||||||
"keepOriginal": "[TODO: Translate] Valid (keep original filename)",
|
"restoreOriginal": "[TODO: Translate] Valid (empty template restores original filenames)",
|
||||||
"invalidChars": "[TODO: Translate] Invalid characters detected (a filename cannot contain / \\ < > : \" | ? *)",
|
"invalidChars": "[TODO: Translate] Invalid characters detected (a filename cannot contain / \\ < > : \" | ? *)",
|
||||||
"invalidPlaceholder": "[TODO: Translate] Invalid placeholder: {placeholder}",
|
"invalidPlaceholder": "[TODO: Translate] Invalid placeholder: {placeholder}",
|
||||||
"validTemplate": "[TODO: Translate] Valid template"
|
"validTemplate": "[TODO: Translate] Valid template"
|
||||||
@@ -1626,6 +1626,11 @@
|
|||||||
"tip": "少しずつ確認したい場合は一括モードに切り替え、必要なモデルを選んで「選択項目の更新を確認」を使ってください。",
|
"tip": "少しずつ確認したい場合は一括モードに切り替え、必要なモデルを選んで「選択項目の更新を確認」を使ってください。",
|
||||||
"action": "すべて確認"
|
"action": "すべて確認"
|
||||||
},
|
},
|
||||||
|
"filenameTemplateConfirm": {
|
||||||
|
"titleApply": "[TODO: Translate] Apply filename template to library?",
|
||||||
|
"titleRevert": "[TODO: Translate] Restore original filenames?",
|
||||||
|
"revertButton": "[TODO: Translate] Restore Original Filenames"
|
||||||
|
},
|
||||||
"bulkAddTags": {
|
"bulkAddTags": {
|
||||||
"title": "複数モデルにタグを追加",
|
"title": "複数モデルにタグを追加",
|
||||||
"description": "タグを追加するモデル:",
|
"description": "タグを追加するモデル:",
|
||||||
|
|||||||
+7
-2
@@ -689,9 +689,9 @@
|
|||||||
"applyButton": "[TODO: Translate] Apply to Library Now",
|
"applyButton": "[TODO: Translate] Apply to Library Now",
|
||||||
"applyHelp": "[TODO: Translate] Renames all existing files of this model type according to the template. Warning: renaming changes the relative path seen by ComfyUI loaders, so existing workflows referencing the old filename may need to be updated. The original filename is preserved in each model's metadata.",
|
"applyHelp": "[TODO: Translate] Renames all existing files of this model type according to the template. Warning: renaming changes the relative path seen by ComfyUI loaders, so existing workflows referencing the old filename may need to be updated. The original filename is preserved in each model's metadata.",
|
||||||
"confirmApply": "[TODO: Translate] Rename all existing files of this model type according to the filename template? This changes the relative path seen by ComfyUI loaders. The original filename is preserved in each model's metadata.",
|
"confirmApply": "[TODO: Translate] Rename all existing files of this model type according to the filename template? This changes the relative path seen by ComfyUI loaders. The original filename is preserved in each model's metadata.",
|
||||||
"emptyTemplateInfo": "[TODO: Translate] No filename template configured for this model type — original filenames are kept, nothing to apply.",
|
"confirmRevert": "[TODO: Translate] Restore the recorded original filenames of all previously renamed files of this model type? This changes the relative path seen by ComfyUI loaders. Files without a recorded original filename are skipped.",
|
||||||
"validation": {
|
"validation": {
|
||||||
"keepOriginal": "[TODO: Translate] Valid (keep original filename)",
|
"restoreOriginal": "[TODO: Translate] Valid (empty template restores original filenames)",
|
||||||
"invalidChars": "[TODO: Translate] Invalid characters detected (a filename cannot contain / \\ < > : \" | ? *)",
|
"invalidChars": "[TODO: Translate] Invalid characters detected (a filename cannot contain / \\ < > : \" | ? *)",
|
||||||
"invalidPlaceholder": "[TODO: Translate] Invalid placeholder: {placeholder}",
|
"invalidPlaceholder": "[TODO: Translate] Invalid placeholder: {placeholder}",
|
||||||
"validTemplate": "[TODO: Translate] Valid template"
|
"validTemplate": "[TODO: Translate] Valid template"
|
||||||
@@ -1626,6 +1626,11 @@
|
|||||||
"tip": "나눠서 진행하고 싶다면 일괄 모드로 전환해 필요한 모델만 선택한 뒤 \"선택 항목 업데이트 확인\"을 사용하세요.",
|
"tip": "나눠서 진행하고 싶다면 일괄 모드로 전환해 필요한 모델만 선택한 뒤 \"선택 항목 업데이트 확인\"을 사용하세요.",
|
||||||
"action": "전체 확인"
|
"action": "전체 확인"
|
||||||
},
|
},
|
||||||
|
"filenameTemplateConfirm": {
|
||||||
|
"titleApply": "[TODO: Translate] Apply filename template to library?",
|
||||||
|
"titleRevert": "[TODO: Translate] Restore original filenames?",
|
||||||
|
"revertButton": "[TODO: Translate] Restore Original Filenames"
|
||||||
|
},
|
||||||
"bulkAddTags": {
|
"bulkAddTags": {
|
||||||
"title": "여러 모델에 태그 추가",
|
"title": "여러 모델에 태그 추가",
|
||||||
"description": "다음에 태그를 추가합니다:",
|
"description": "다음에 태그를 추가합니다:",
|
||||||
|
|||||||
+7
-2
@@ -689,9 +689,9 @@
|
|||||||
"applyButton": "[TODO: Translate] Apply to Library Now",
|
"applyButton": "[TODO: Translate] Apply to Library Now",
|
||||||
"applyHelp": "[TODO: Translate] Renames all existing files of this model type according to the template. Warning: renaming changes the relative path seen by ComfyUI loaders, so existing workflows referencing the old filename may need to be updated. The original filename is preserved in each model's metadata.",
|
"applyHelp": "[TODO: Translate] Renames all existing files of this model type according to the template. Warning: renaming changes the relative path seen by ComfyUI loaders, so existing workflows referencing the old filename may need to be updated. The original filename is preserved in each model's metadata.",
|
||||||
"confirmApply": "[TODO: Translate] Rename all existing files of this model type according to the filename template? This changes the relative path seen by ComfyUI loaders. The original filename is preserved in each model's metadata.",
|
"confirmApply": "[TODO: Translate] Rename all existing files of this model type according to the filename template? This changes the relative path seen by ComfyUI loaders. The original filename is preserved in each model's metadata.",
|
||||||
"emptyTemplateInfo": "[TODO: Translate] No filename template configured for this model type — original filenames are kept, nothing to apply.",
|
"confirmRevert": "[TODO: Translate] Restore the recorded original filenames of all previously renamed files of this model type? This changes the relative path seen by ComfyUI loaders. Files without a recorded original filename are skipped.",
|
||||||
"validation": {
|
"validation": {
|
||||||
"keepOriginal": "[TODO: Translate] Valid (keep original filename)",
|
"restoreOriginal": "[TODO: Translate] Valid (empty template restores original filenames)",
|
||||||
"invalidChars": "[TODO: Translate] Invalid characters detected (a filename cannot contain / \\ < > : \" | ? *)",
|
"invalidChars": "[TODO: Translate] Invalid characters detected (a filename cannot contain / \\ < > : \" | ? *)",
|
||||||
"invalidPlaceholder": "[TODO: Translate] Invalid placeholder: {placeholder}",
|
"invalidPlaceholder": "[TODO: Translate] Invalid placeholder: {placeholder}",
|
||||||
"validTemplate": "[TODO: Translate] Valid template"
|
"validTemplate": "[TODO: Translate] Valid template"
|
||||||
@@ -1626,6 +1626,11 @@
|
|||||||
"tip": "Хотите проверять по частям? Переключитесь в массовый режим, выберите нужные модели и используйте \"Проверить обновления для выбранных\".",
|
"tip": "Хотите проверять по частям? Переключитесь в массовый режим, выберите нужные модели и используйте \"Проверить обновления для выбранных\".",
|
||||||
"action": "Проверить всё"
|
"action": "Проверить всё"
|
||||||
},
|
},
|
||||||
|
"filenameTemplateConfirm": {
|
||||||
|
"titleApply": "[TODO: Translate] Apply filename template to library?",
|
||||||
|
"titleRevert": "[TODO: Translate] Restore original filenames?",
|
||||||
|
"revertButton": "[TODO: Translate] Restore Original Filenames"
|
||||||
|
},
|
||||||
"bulkAddTags": {
|
"bulkAddTags": {
|
||||||
"title": "Добавить теги к нескольким моделям",
|
"title": "Добавить теги к нескольким моделям",
|
||||||
"description": "Добавить теги к",
|
"description": "Добавить теги к",
|
||||||
|
|||||||
+7
-2
@@ -689,9 +689,9 @@
|
|||||||
"applyButton": "[TODO: Translate] Apply to Library Now",
|
"applyButton": "[TODO: Translate] Apply to Library Now",
|
||||||
"applyHelp": "[TODO: Translate] Renames all existing files of this model type according to the template. Warning: renaming changes the relative path seen by ComfyUI loaders, so existing workflows referencing the old filename may need to be updated. The original filename is preserved in each model's metadata.",
|
"applyHelp": "[TODO: Translate] Renames all existing files of this model type according to the template. Warning: renaming changes the relative path seen by ComfyUI loaders, so existing workflows referencing the old filename may need to be updated. The original filename is preserved in each model's metadata.",
|
||||||
"confirmApply": "[TODO: Translate] Rename all existing files of this model type according to the filename template? This changes the relative path seen by ComfyUI loaders. The original filename is preserved in each model's metadata.",
|
"confirmApply": "[TODO: Translate] Rename all existing files of this model type according to the filename template? This changes the relative path seen by ComfyUI loaders. The original filename is preserved in each model's metadata.",
|
||||||
"emptyTemplateInfo": "[TODO: Translate] No filename template configured for this model type — original filenames are kept, nothing to apply.",
|
"confirmRevert": "[TODO: Translate] Restore the recorded original filenames of all previously renamed files of this model type? This changes the relative path seen by ComfyUI loaders. Files without a recorded original filename are skipped.",
|
||||||
"validation": {
|
"validation": {
|
||||||
"keepOriginal": "[TODO: Translate] Valid (keep original filename)",
|
"restoreOriginal": "[TODO: Translate] Valid (empty template restores original filenames)",
|
||||||
"invalidChars": "[TODO: Translate] Invalid characters detected (a filename cannot contain / \\ < > : \" | ? *)",
|
"invalidChars": "[TODO: Translate] Invalid characters detected (a filename cannot contain / \\ < > : \" | ? *)",
|
||||||
"invalidPlaceholder": "[TODO: Translate] Invalid placeholder: {placeholder}",
|
"invalidPlaceholder": "[TODO: Translate] Invalid placeholder: {placeholder}",
|
||||||
"validTemplate": "[TODO: Translate] Valid template"
|
"validTemplate": "[TODO: Translate] Valid template"
|
||||||
@@ -1626,6 +1626,11 @@
|
|||||||
"tip": "想分批进行?切换到批量模式,选中需要的模型,然后使用“检查所选更新”。",
|
"tip": "想分批进行?切换到批量模式,选中需要的模型,然后使用“检查所选更新”。",
|
||||||
"action": "检查全部"
|
"action": "检查全部"
|
||||||
},
|
},
|
||||||
|
"filenameTemplateConfirm": {
|
||||||
|
"titleApply": "[TODO: Translate] Apply filename template to library?",
|
||||||
|
"titleRevert": "[TODO: Translate] Restore original filenames?",
|
||||||
|
"revertButton": "[TODO: Translate] Restore Original Filenames"
|
||||||
|
},
|
||||||
"bulkAddTags": {
|
"bulkAddTags": {
|
||||||
"title": "批量添加标签",
|
"title": "批量添加标签",
|
||||||
"description": "为多个模型添加标签",
|
"description": "为多个模型添加标签",
|
||||||
|
|||||||
+7
-2
@@ -689,9 +689,9 @@
|
|||||||
"applyButton": "[TODO: Translate] Apply to Library Now",
|
"applyButton": "[TODO: Translate] Apply to Library Now",
|
||||||
"applyHelp": "[TODO: Translate] Renames all existing files of this model type according to the template. Warning: renaming changes the relative path seen by ComfyUI loaders, so existing workflows referencing the old filename may need to be updated. The original filename is preserved in each model's metadata.",
|
"applyHelp": "[TODO: Translate] Renames all existing files of this model type according to the template. Warning: renaming changes the relative path seen by ComfyUI loaders, so existing workflows referencing the old filename may need to be updated. The original filename is preserved in each model's metadata.",
|
||||||
"confirmApply": "[TODO: Translate] Rename all existing files of this model type according to the filename template? This changes the relative path seen by ComfyUI loaders. The original filename is preserved in each model's metadata.",
|
"confirmApply": "[TODO: Translate] Rename all existing files of this model type according to the filename template? This changes the relative path seen by ComfyUI loaders. The original filename is preserved in each model's metadata.",
|
||||||
"emptyTemplateInfo": "[TODO: Translate] No filename template configured for this model type — original filenames are kept, nothing to apply.",
|
"confirmRevert": "[TODO: Translate] Restore the recorded original filenames of all previously renamed files of this model type? This changes the relative path seen by ComfyUI loaders. Files without a recorded original filename are skipped.",
|
||||||
"validation": {
|
"validation": {
|
||||||
"keepOriginal": "[TODO: Translate] Valid (keep original filename)",
|
"restoreOriginal": "[TODO: Translate] Valid (empty template restores original filenames)",
|
||||||
"invalidChars": "[TODO: Translate] Invalid characters detected (a filename cannot contain / \\ < > : \" | ? *)",
|
"invalidChars": "[TODO: Translate] Invalid characters detected (a filename cannot contain / \\ < > : \" | ? *)",
|
||||||
"invalidPlaceholder": "[TODO: Translate] Invalid placeholder: {placeholder}",
|
"invalidPlaceholder": "[TODO: Translate] Invalid placeholder: {placeholder}",
|
||||||
"validTemplate": "[TODO: Translate] Valid template"
|
"validTemplate": "[TODO: Translate] Valid template"
|
||||||
@@ -1626,6 +1626,11 @@
|
|||||||
"tip": "想分批處理?切換到批次模式,選擇需要的模型,然後使用「檢查所選更新」。",
|
"tip": "想分批處理?切換到批次模式,選擇需要的模型,然後使用「檢查所選更新」。",
|
||||||
"action": "全部檢查"
|
"action": "全部檢查"
|
||||||
},
|
},
|
||||||
|
"filenameTemplateConfirm": {
|
||||||
|
"titleApply": "[TODO: Translate] Apply filename template to library?",
|
||||||
|
"titleRevert": "[TODO: Translate] Restore original filenames?",
|
||||||
|
"revertButton": "[TODO: Translate] Restore Original Filenames"
|
||||||
|
},
|
||||||
"bulkAddTags": {
|
"bulkAddTags": {
|
||||||
"title": "新增標籤到多個模型",
|
"title": "新增標籤到多個模型",
|
||||||
"description": "新增標籤到",
|
"description": "新增標籤到",
|
||||||
|
|||||||
@@ -1,16 +1,20 @@
|
|||||||
"""Filename template use case: bulk-rename library models per the configured template."""
|
"""Filename template use case: bulk-rename library models per the configured template.
|
||||||
|
|
||||||
|
An empty template reverts previously renamed models to the original filename
|
||||||
|
recorded in their ``.metadata.json`` sidecar (``original_file_name``).
|
||||||
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
from typing import Any, Dict, List, Optional, Sequence
|
from typing import Any, Awaitable, Callable, Dict, List, Optional, Sequence
|
||||||
|
|
||||||
from ...utils.constants import AUTO_ORGANIZE_BATCH_SIZE
|
from ...utils.constants import AUTO_ORGANIZE_BATCH_SIZE
|
||||||
from ...utils.utils import calculate_filename_for_model
|
from ...utils.utils import calculate_filename_for_model
|
||||||
from ..model_file_service import AutoOrganizeResult, ProgressCallback
|
from ..model_file_service import AutoOrganizeResult, ProgressCallback
|
||||||
from ..model_lifecycle_service import ModelLifecycleService
|
from ..model_lifecycle_service import ModelLifecycleService, load_local_metadata
|
||||||
from ..settings_manager import get_settings_manager
|
from ..settings_manager import get_settings_manager
|
||||||
from .auto_organize_use_case import (
|
from .auto_organize_use_case import (
|
||||||
AutoOrganizeInProgressError,
|
AutoOrganizeInProgressError,
|
||||||
@@ -25,8 +29,10 @@ _PROGRESS_TYPE = "filename_template_progress"
|
|||||||
class FilenameTemplateUseCase:
|
class FilenameTemplateUseCase:
|
||||||
"""Apply the download filename template to existing library models.
|
"""Apply the download filename template to existing library models.
|
||||||
|
|
||||||
Shares the auto-organize lock (and its in-progress error) so a bulk
|
An empty template restores the recorded original filename instead of
|
||||||
rename never runs concurrently with an auto-organize operation.
|
rendering a template. Shares the auto-organize lock (and its in-progress
|
||||||
|
error) so a bulk rename never runs concurrently with an auto-organize
|
||||||
|
operation.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
@@ -36,11 +42,13 @@ class FilenameTemplateUseCase:
|
|||||||
lifecycle_service: ModelLifecycleService,
|
lifecycle_service: ModelLifecycleService,
|
||||||
lock_provider: AutoOrganizeLockProvider,
|
lock_provider: AutoOrganizeLockProvider,
|
||||||
model_type: str,
|
model_type: str,
|
||||||
|
metadata_loader: Callable[[str], Awaitable[Dict[str, Any]]] = load_local_metadata,
|
||||||
) -> None:
|
) -> None:
|
||||||
self._scanner = scanner
|
self._scanner = scanner
|
||||||
self._lifecycle_service = lifecycle_service
|
self._lifecycle_service = lifecycle_service
|
||||||
self._lock_provider = lock_provider
|
self._lock_provider = lock_provider
|
||||||
self._model_type = model_type
|
self._model_type = model_type
|
||||||
|
self._metadata_loader = metadata_loader
|
||||||
|
|
||||||
async def execute(
|
async def execute(
|
||||||
self,
|
self,
|
||||||
@@ -151,9 +159,10 @@ class FilenameTemplateUseCase:
|
|||||||
return
|
return
|
||||||
|
|
||||||
if not template:
|
if not template:
|
||||||
result.skipped_count += 1
|
# Empty template = revert to the original filename recorded
|
||||||
return
|
# by the first rename; models without a record are skipped.
|
||||||
|
new_stem = await self._resolve_recorded_original(file_path)
|
||||||
|
else:
|
||||||
new_stem = calculate_filename_for_model(model, self._model_type)
|
new_stem = calculate_filename_for_model(model, self._model_type)
|
||||||
if not new_stem:
|
if not new_stem:
|
||||||
result.skipped_count += 1
|
result.skipped_count += 1
|
||||||
@@ -184,6 +193,20 @@ class FilenameTemplateUseCase:
|
|||||||
self._add_result(result, model_name, False, f"Error: {exc}")
|
self._add_result(result, model_name, False, f"Error: {exc}")
|
||||||
result.failure_count += 1
|
result.failure_count += 1
|
||||||
|
|
||||||
|
async def _resolve_recorded_original(self, file_path: str) -> str:
|
||||||
|
"""Return the original filename stem recorded at the first rename.
|
||||||
|
|
||||||
|
Reads the ``.metadata.json`` sidecar; returns an empty string when no
|
||||||
|
sidecar or no ``original_file_name`` entry exists (models never
|
||||||
|
renamed, or renamed before the recording shipped).
|
||||||
|
"""
|
||||||
|
metadata_path = f"{os.path.splitext(file_path)[0]}.metadata.json"
|
||||||
|
metadata = await self._metadata_loader(metadata_path)
|
||||||
|
original = metadata.get("original_file_name")
|
||||||
|
if not isinstance(original, str):
|
||||||
|
return ""
|
||||||
|
return original.strip()
|
||||||
|
|
||||||
async def _emit_progress(
|
async def _emit_progress(
|
||||||
self,
|
self,
|
||||||
progress_callback: Optional[ProgressCallback],
|
progress_callback: Optional[ProgressCallback],
|
||||||
|
|||||||
@@ -27,6 +27,12 @@
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Self-managed by SettingsManager: stacks above the settings modal like the
|
||||||
|
directory picker (settings panels sit at 10000/10002). */
|
||||||
|
#filenameTemplateConfirmModal {
|
||||||
|
z-index: 10010;
|
||||||
|
}
|
||||||
|
|
||||||
.delete-modal-content {
|
.delete-modal-content {
|
||||||
max-width: 500px;
|
max-width: 500px;
|
||||||
width: 90%;
|
width: 90%;
|
||||||
|
|||||||
@@ -463,7 +463,6 @@ export class SettingsManager {
|
|||||||
const template = e.target.value;
|
const template = e.target.value;
|
||||||
settingsManager.validateFilenameTemplate(modelType, template);
|
settingsManager.validateFilenameTemplate(modelType, template);
|
||||||
settingsManager.updateFilenamePreview(modelType, template);
|
settingsManager.updateFilenamePreview(modelType, template);
|
||||||
settingsManager.updateFilenameTemplateApplyButton(modelType, template);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
filenameInput.addEventListener('blur', (e) => {
|
filenameInput.addEventListener('blur', (e) => {
|
||||||
@@ -2864,7 +2863,6 @@ export class SettingsManager {
|
|||||||
input.value = template;
|
input.value = template;
|
||||||
this.validateFilenameTemplate(modelType, template);
|
this.validateFilenameTemplate(modelType, template);
|
||||||
this.updateFilenamePreview(modelType, template);
|
this.updateFilenamePreview(modelType, template);
|
||||||
this.updateFilenameTemplateApplyButton(modelType, template);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2877,7 +2875,7 @@ export class SettingsManager {
|
|||||||
validationElement.className = 'template-validation';
|
validationElement.className = 'template-validation';
|
||||||
|
|
||||||
if (!template) {
|
if (!template) {
|
||||||
validationElement.innerHTML = `<i class="fas fa-check"></i> ${translate('settings.filenameTemplates.validation.keepOriginal', {}, 'Valid (keep original filename)')}`;
|
validationElement.innerHTML = `<i class="fas fa-check"></i> ${translate('settings.filenameTemplates.validation.restoreOriginal', {}, 'Valid (empty template restores original filenames)')}`;
|
||||||
validationElement.classList.add('valid');
|
validationElement.classList.add('valid');
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -2922,28 +2920,19 @@ export class SettingsManager {
|
|||||||
}
|
}
|
||||||
state.global.settings.download_filename_templates[modelType] = template;
|
state.global.settings.download_filename_templates[modelType] = template;
|
||||||
|
|
||||||
// Update preview and apply-button state
|
// Update preview
|
||||||
this.updateFilenamePreview(modelType, template);
|
this.updateFilenamePreview(modelType, template);
|
||||||
this.updateFilenameTemplateApplyButton(modelType, template);
|
|
||||||
|
|
||||||
// Save settings
|
// Save settings
|
||||||
this.saveFilenameTemplates();
|
this.saveFilenameTemplates();
|
||||||
}
|
}
|
||||||
|
|
||||||
updateFilenameTemplateApplyButton(modelType, template) {
|
|
||||||
const button = document.getElementById(`${modelType}ApplyFilenameTemplate`);
|
|
||||||
if (!button) return;
|
|
||||||
|
|
||||||
// An empty template keeps original filenames, so there is nothing to apply
|
|
||||||
button.disabled = !template;
|
|
||||||
}
|
|
||||||
|
|
||||||
updateFilenamePreview(modelType, template) {
|
updateFilenamePreview(modelType, template) {
|
||||||
const previewElement = document.getElementById(`${modelType}FilenamePreview`);
|
const previewElement = document.getElementById(`${modelType}FilenamePreview`);
|
||||||
if (!previewElement) return;
|
if (!previewElement) return;
|
||||||
|
|
||||||
if (!template) {
|
if (!template) {
|
||||||
// Empty template keeps the original filename untouched
|
// Empty template restores the recorded original filename
|
||||||
previewElement.textContent = 'V1.safetensors';
|
previewElement.textContent = 'V1.safetensors';
|
||||||
} else {
|
} else {
|
||||||
const exampleStem = template
|
const exampleStem = template
|
||||||
@@ -2972,14 +2961,96 @@ export class SettingsManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async applyFilenameTemplate(modelType) {
|
/**
|
||||||
const template = state.global.settings.download_filename_templates?.[modelType] || '';
|
* Confirm the bulk apply/revert via a dedicated modal. Self-managed (NOT
|
||||||
if (!template) {
|
* through ModalManager): it stacks above the settings modal, and
|
||||||
showToast('settings.filenameTemplates.emptyTemplateInfo', {}, 'info');
|
* ModalManager's "close current modal on open" behavior would kill the
|
||||||
return;
|
* settings modal underneath. Resolves true when the user confirms.
|
||||||
|
*/
|
||||||
|
confirmFilenameTemplateApply(isRevert) {
|
||||||
|
const modalElement = document.getElementById('filenameTemplateConfirmModal');
|
||||||
|
if (!modalElement) {
|
||||||
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!confirm(translate('settings.filenameTemplates.confirmApply', {}, 'Rename all existing files of this model type according to the filename template? The original filename is preserved in each model\'s metadata.'))) {
|
const titleElement = modalElement.querySelector('[data-role="title"]');
|
||||||
|
if (titleElement) {
|
||||||
|
titleElement.textContent = isRevert
|
||||||
|
? translate('modals.filenameTemplateConfirm.titleRevert', {}, 'Restore original filenames?')
|
||||||
|
: translate('modals.filenameTemplateConfirm.titleApply', {}, 'Apply filename template to library?');
|
||||||
|
}
|
||||||
|
|
||||||
|
const messageElement = modalElement.querySelector('[data-role="message"]');
|
||||||
|
if (messageElement) {
|
||||||
|
messageElement.textContent = isRevert
|
||||||
|
? translate('settings.filenameTemplates.confirmRevert', {}, 'Restore the recorded original filename for all previously renamed files of this model type? This changes the relative path seen by ComfyUI loaders. Files without a recorded original filename are skipped.')
|
||||||
|
: translate('settings.filenameTemplates.confirmApply', {}, 'Rename all existing files of this model type according to the filename template? This changes the relative path seen by ComfyUI loaders. The original filename is preserved in each model\'s metadata.');
|
||||||
|
}
|
||||||
|
|
||||||
|
const confirmButton = modalElement.querySelector('[data-action="confirm-filename-template"]');
|
||||||
|
const cancelButton = modalElement.querySelector('[data-action="cancel-filename-template"]');
|
||||||
|
if (!confirmButton || !cancelButton) {
|
||||||
|
return Promise.resolve(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
confirmButton.textContent = isRevert
|
||||||
|
? translate('modals.filenameTemplateConfirm.revertButton', {}, 'Restore Original Filenames')
|
||||||
|
: translate('settings.filenameTemplates.applyButton', {}, 'Apply to Library Now');
|
||||||
|
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
let resolved = false;
|
||||||
|
|
||||||
|
const cleanup = () => {
|
||||||
|
confirmButton.removeEventListener('click', handleConfirm);
|
||||||
|
cancelButton.removeEventListener('click', handleCancel);
|
||||||
|
document.removeEventListener('keydown', handleEscape, true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const finalize = (proceed) => {
|
||||||
|
if (resolved) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
resolved = true;
|
||||||
|
cleanup();
|
||||||
|
modalElement.classList.remove('show');
|
||||||
|
// Keep body.modal-open: the settings modal underneath is still open.
|
||||||
|
resolve(proceed);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleConfirm = (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
finalize(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCancel = (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
finalize(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Capture phase + stopPropagation so ESC never reaches the
|
||||||
|
// settings modal's own ESC handler underneath.
|
||||||
|
const handleEscape = (event) => {
|
||||||
|
if (event.key === 'Escape') {
|
||||||
|
event.stopPropagation();
|
||||||
|
finalize(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
confirmButton.addEventListener('click', handleConfirm);
|
||||||
|
cancelButton.addEventListener('click', handleCancel);
|
||||||
|
document.addEventListener('keydown', handleEscape, true);
|
||||||
|
|
||||||
|
modalElement.classList.add('show');
|
||||||
|
cancelButton.focus();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async applyFilenameTemplate(modelType) {
|
||||||
|
const template = state.global.settings.download_filename_templates?.[modelType] || '';
|
||||||
|
// An empty template reverts renamed models to their recorded original
|
||||||
|
// filename instead of rendering a template.
|
||||||
|
const confirmed = await this.confirmFilenameTemplateApply(!template);
|
||||||
|
if (!confirmed) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -82,6 +82,20 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Filename Template Apply/Revert Confirmation Modal
|
||||||
|
Self-managed by SettingsManager (NOT registered with ModalManager): it
|
||||||
|
stacks above the settings modal, like the directory picker. -->
|
||||||
|
<div id="filenameTemplateConfirmModal" class="modal delete-modal">
|
||||||
|
<div class="modal-content delete-modal-content">
|
||||||
|
<h2 data-role="title"></h2>
|
||||||
|
<p class="delete-message" data-role="message"></p>
|
||||||
|
<div class="modal-actions">
|
||||||
|
<button class="cancel-btn" data-action="cancel-filename-template">{{ t('common.actions.cancel') }}</button>
|
||||||
|
<button class="primary-btn" data-action="confirm-filename-template"></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Sidebar Folder Delete Confirmation Modal -->
|
<!-- Sidebar Folder Delete Confirmation Modal -->
|
||||||
<!-- Shared by two states: 'confirm' (model-free folder) and 'blocked' (the
|
<!-- Shared by two states: 'confirm' (model-free folder) and 'blocked' (the
|
||||||
subtree still holds models, so a cascade delete is refused). -->
|
subtree still holds models, so a cascade delete is refused). -->
|
||||||
|
|||||||
@@ -81,7 +81,6 @@ vi.mock('../../../static/js/managers/BannerService.js', () => ({
|
|||||||
|
|
||||||
import { SettingsManager } from '../../../static/js/managers/SettingsManager.js';
|
import { SettingsManager } from '../../../static/js/managers/SettingsManager.js';
|
||||||
import { state } from '../../../static/js/state/index.js';
|
import { state } from '../../../static/js/state/index.js';
|
||||||
import { showToast } from '../../../static/js/utils/uiHelpers.js';
|
|
||||||
import { resetAndReload, getModelApiClient } from '../../../static/js/api/modelApiFactory.js';
|
import { resetAndReload, getModelApiClient } from '../../../static/js/api/modelApiFactory.js';
|
||||||
|
|
||||||
const createManager = () => {
|
const createManager = () => {
|
||||||
@@ -109,6 +108,17 @@ const appendFilenameTemplateUi = (modelType = 'lora') => {
|
|||||||
`;
|
`;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const appendConfirmModal = () => {
|
||||||
|
document.body.insertAdjacentHTML('beforeend', `
|
||||||
|
<div id="filenameTemplateConfirmModal" class="modal delete-modal">
|
||||||
|
<h2 data-role="title"></h2>
|
||||||
|
<p data-role="message"></p>
|
||||||
|
<button data-action="cancel-filename-template"></button>
|
||||||
|
<button data-action="confirm-filename-template"></button>
|
||||||
|
</div>
|
||||||
|
`);
|
||||||
|
};
|
||||||
|
|
||||||
describe('SettingsManager filename templates', () => {
|
describe('SettingsManager filename templates', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
document.body.innerHTML = '';
|
document.body.innerHTML = '';
|
||||||
@@ -118,7 +128,7 @@ describe('SettingsManager filename templates', () => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
it('treats an empty template as valid (keep original filename)', () => {
|
it('treats an empty template as valid (restores original filenames)', () => {
|
||||||
appendFilenameTemplateUi();
|
appendFilenameTemplateUi();
|
||||||
const manager = createManager();
|
const manager = createManager();
|
||||||
|
|
||||||
@@ -126,7 +136,7 @@ describe('SettingsManager filename templates', () => {
|
|||||||
|
|
||||||
const validation = document.getElementById('loraFilenameValidation');
|
const validation = document.getElementById('loraFilenameValidation');
|
||||||
expect(validation.classList.contains('valid')).toBe(true);
|
expect(validation.classList.contains('valid')).toBe(true);
|
||||||
expect(validation.textContent).toContain('keep original filename');
|
expect(validation.textContent).toContain('restores original filenames');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('rejects templates with path separators or OS-illegal characters', () => {
|
it('rejects templates with path separators or OS-illegal characters', () => {
|
||||||
@@ -184,7 +194,7 @@ describe('SettingsManager filename templates', () => {
|
|||||||
expect(manager.saveSetting).not.toHaveBeenCalled();
|
expect(manager.saveSetting).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('previews the original filename when the template is empty', () => {
|
it('previews the recorded original filename when the template is empty', () => {
|
||||||
appendFilenameTemplateUi();
|
appendFilenameTemplateUi();
|
||||||
const manager = createManager();
|
const manager = createManager();
|
||||||
|
|
||||||
@@ -203,15 +213,44 @@ describe('SettingsManager filename templates', () => {
|
|||||||
.toBe('Flux.1 D-model-name-v3-a1b2c3d4e5.safetensors');
|
.toBe('Flux.1 D-model-name-v3-a1b2c3d4e5.safetensors');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('disables the apply button when the template is empty', () => {
|
it('applies an empty template as a revert after modal confirmation', async () => {
|
||||||
appendFilenameTemplateUi();
|
appendFilenameTemplateUi();
|
||||||
|
appendConfirmModal();
|
||||||
|
const manager = createManager();
|
||||||
|
const apiClient = { applyFilenameTemplate: vi.fn().mockResolvedValue() };
|
||||||
|
getModelApiClient.mockReturnValue(apiClient);
|
||||||
|
|
||||||
|
const applyPromise = manager.applyFilenameTemplate('lora');
|
||||||
|
|
||||||
|
const modal = document.getElementById('filenameTemplateConfirmModal');
|
||||||
|
expect(modal.classList.contains('show')).toBe(true);
|
||||||
|
expect(modal.querySelector('[data-role="title"]').textContent)
|
||||||
|
.toBe('Restore original filenames?');
|
||||||
|
expect(modal.querySelector('[data-role="message"]').textContent)
|
||||||
|
.toContain('Restore the recorded original filename');
|
||||||
|
expect(modal.querySelector('[data-action="confirm-filename-template"]').textContent)
|
||||||
|
.toBe('Restore Original Filenames');
|
||||||
|
|
||||||
|
modal.querySelector('[data-action="confirm-filename-template"]').click();
|
||||||
|
await applyPromise;
|
||||||
|
|
||||||
|
expect(getModelApiClient).toHaveBeenCalledWith('loras');
|
||||||
|
expect(apiClient.applyFilenameTemplate).toHaveBeenCalledWith();
|
||||||
|
expect(resetAndReload).toHaveBeenCalledWith(true);
|
||||||
|
expect(modal.classList.contains('show')).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not revert when the modal is cancelled', async () => {
|
||||||
|
appendFilenameTemplateUi();
|
||||||
|
appendConfirmModal();
|
||||||
const manager = createManager();
|
const manager = createManager();
|
||||||
|
|
||||||
manager.updateFilenameTemplateApplyButton('lora', '');
|
const applyPromise = manager.applyFilenameTemplate('lora');
|
||||||
expect(document.getElementById('loraApplyFilenameTemplate').disabled).toBe(true);
|
document.querySelector('[data-action="cancel-filename-template"]').click();
|
||||||
|
await applyPromise;
|
||||||
|
|
||||||
manager.updateFilenameTemplateApplyButton('lora', '{model_name}');
|
expect(getModelApiClient).not.toHaveBeenCalled();
|
||||||
expect(document.getElementById('loraApplyFilenameTemplate').disabled).toBe(false);
|
expect(resetAndReload).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('merges backend download_filename_templates over defaults', () => {
|
it('merges backend download_filename_templates over defaults', () => {
|
||||||
@@ -236,22 +275,10 @@ describe('SettingsManager filename templates', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('shows an info toast and does nothing when applying an empty template', async () => {
|
|
||||||
appendFilenameTemplateUi();
|
|
||||||
const manager = createManager();
|
|
||||||
vi.stubGlobal('confirm', vi.fn(() => true));
|
|
||||||
|
|
||||||
await manager.applyFilenameTemplate('lora');
|
|
||||||
|
|
||||||
expect(showToast).toHaveBeenCalledWith('settings.filenameTemplates.emptyTemplateInfo', {}, 'info');
|
|
||||||
expect(getModelApiClient).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('applies the template through the model API client and reloads', async () => {
|
it('applies the template through the model API client and reloads', async () => {
|
||||||
appendFilenameTemplateUi();
|
appendFilenameTemplateUi();
|
||||||
state.global.settings.download_filename_templates.lora = '{model_name}';
|
state.global.settings.download_filename_templates.lora = '{model_name}';
|
||||||
const manager = createManager();
|
const manager = createManager();
|
||||||
vi.stubGlobal('confirm', vi.fn(() => true));
|
|
||||||
const apiClient = { applyFilenameTemplate: vi.fn().mockResolvedValue() };
|
const apiClient = { applyFilenameTemplate: vi.fn().mockResolvedValue() };
|
||||||
getModelApiClient.mockReturnValue(apiClient);
|
getModelApiClient.mockReturnValue(apiClient);
|
||||||
|
|
||||||
@@ -262,15 +289,26 @@ describe('SettingsManager filename templates', () => {
|
|||||||
expect(resetAndReload).toHaveBeenCalledWith(true);
|
expect(resetAndReload).toHaveBeenCalledWith(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not apply when the confirm dialog is declined', async () => {
|
it('shows the apply wording for a non-empty template and honours cancellation', async () => {
|
||||||
appendFilenameTemplateUi();
|
appendFilenameTemplateUi();
|
||||||
|
appendConfirmModal();
|
||||||
state.global.settings.download_filename_templates.lora = '{model_name}';
|
state.global.settings.download_filename_templates.lora = '{model_name}';
|
||||||
const manager = createManager();
|
const manager = createManager();
|
||||||
vi.stubGlobal('confirm', vi.fn(() => false));
|
|
||||||
|
|
||||||
await manager.applyFilenameTemplate('lora');
|
const applyPromise = manager.applyFilenameTemplate('lora');
|
||||||
|
|
||||||
|
const modal = document.getElementById('filenameTemplateConfirmModal');
|
||||||
|
expect(modal.classList.contains('show')).toBe(true);
|
||||||
|
expect(modal.querySelector('[data-role="title"]').textContent)
|
||||||
|
.toBe('Apply filename template to library?');
|
||||||
|
expect(modal.querySelector('[data-role="message"]').textContent)
|
||||||
|
.toContain('Rename all existing files');
|
||||||
|
|
||||||
|
modal.querySelector('[data-action="cancel-filename-template"]').click();
|
||||||
|
await applyPromise;
|
||||||
|
|
||||||
expect(getModelApiClient).not.toHaveBeenCalled();
|
expect(getModelApiClient).not.toHaveBeenCalled();
|
||||||
expect(resetAndReload).not.toHaveBeenCalled();
|
expect(resetAndReload).not.toHaveBeenCalled();
|
||||||
|
expect(modal.classList.contains('show')).toBe(false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -549,12 +549,17 @@ def _make_filename_template_use_case(
|
|||||||
scanner: MockScanner,
|
scanner: MockScanner,
|
||||||
lifecycle: StubLifecycleService,
|
lifecycle: StubLifecycleService,
|
||||||
lock_provider: Optional[StubLockProvider] = None,
|
lock_provider: Optional[StubLockProvider] = None,
|
||||||
|
metadata_loader: Optional[Any] = None,
|
||||||
) -> FilenameTemplateUseCase:
|
) -> FilenameTemplateUseCase:
|
||||||
|
kwargs: Dict[str, Any] = {}
|
||||||
|
if metadata_loader is not None:
|
||||||
|
kwargs["metadata_loader"] = metadata_loader
|
||||||
return FilenameTemplateUseCase(
|
return FilenameTemplateUseCase(
|
||||||
scanner=scanner,
|
scanner=scanner,
|
||||||
lifecycle_service=lifecycle, # pyright: ignore[reportArgumentType]
|
lifecycle_service=lifecycle, # pyright: ignore[reportArgumentType]
|
||||||
lock_provider=lock_provider or StubLockProvider(),
|
lock_provider=lock_provider or StubLockProvider(),
|
||||||
model_type="lora",
|
model_type="lora",
|
||||||
|
**kwargs,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -600,7 +605,36 @@ async def test_filename_template_use_case_skips_unchanged_names() -> None:
|
|||||||
assert lifecycle.renames == []
|
assert lifecycle.renames == []
|
||||||
|
|
||||||
|
|
||||||
async def test_filename_template_use_case_skips_all_when_template_empty() -> None:
|
async def test_filename_template_use_case_reverts_to_recorded_original_when_template_empty() -> None:
|
||||||
|
_set_filename_template("")
|
||||||
|
scanner = MockScanner(cache=MockCache([
|
||||||
|
_filename_template_model("/library/alpha-renamed.safetensors", "Alpha"),
|
||||||
|
_filename_template_model("/library/beta.safetensors", "Beta"),
|
||||||
|
]))
|
||||||
|
lifecycle = StubLifecycleService()
|
||||||
|
|
||||||
|
async def metadata_loader(metadata_path: str) -> Dict[str, Any]:
|
||||||
|
if metadata_path == "/library/alpha-renamed.metadata.json":
|
||||||
|
return {"original_file_name": "alpha-original"}
|
||||||
|
return {}
|
||||||
|
|
||||||
|
use_case = _make_filename_template_use_case(
|
||||||
|
scanner, lifecycle, metadata_loader=metadata_loader
|
||||||
|
)
|
||||||
|
|
||||||
|
result = await use_case.execute(progress_callback=None)
|
||||||
|
|
||||||
|
assert result.success_count == 1
|
||||||
|
assert result.skipped_count == 1
|
||||||
|
assert lifecycle.renames == [
|
||||||
|
{
|
||||||
|
"file_path": "/library/alpha-renamed.safetensors",
|
||||||
|
"new_file_name": "alpha-original",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
async def test_filename_template_use_case_skips_revert_without_recorded_original() -> None:
|
||||||
_set_filename_template("")
|
_set_filename_template("")
|
||||||
scanner = MockScanner(cache=MockCache([
|
scanner = MockScanner(cache=MockCache([
|
||||||
_filename_template_model("/library/alpha.safetensors", "Alpha"),
|
_filename_template_model("/library/alpha.safetensors", "Alpha"),
|
||||||
@@ -614,6 +648,27 @@ async def test_filename_template_use_case_skips_all_when_template_empty() -> Non
|
|||||||
assert lifecycle.renames == []
|
assert lifecycle.renames == []
|
||||||
|
|
||||||
|
|
||||||
|
async def test_filename_template_use_case_skips_revert_matching_current_name() -> None:
|
||||||
|
_set_filename_template("")
|
||||||
|
scanner = MockScanner(cache=MockCache([
|
||||||
|
_filename_template_model("/library/alpha.safetensors", "Alpha"),
|
||||||
|
]))
|
||||||
|
lifecycle = StubLifecycleService()
|
||||||
|
|
||||||
|
async def metadata_loader(metadata_path: str) -> Dict[str, Any]:
|
||||||
|
return {"original_file_name": "alpha"}
|
||||||
|
|
||||||
|
use_case = _make_filename_template_use_case(
|
||||||
|
scanner, lifecycle, metadata_loader=metadata_loader
|
||||||
|
)
|
||||||
|
|
||||||
|
result = await use_case.execute(progress_callback=None)
|
||||||
|
|
||||||
|
assert result.success_count == 0
|
||||||
|
assert result.skipped_count == 1
|
||||||
|
assert lifecycle.renames == []
|
||||||
|
|
||||||
|
|
||||||
async def test_filename_template_use_case_counts_conflicts_as_failures() -> None:
|
async def test_filename_template_use_case_counts_conflicts_as_failures() -> None:
|
||||||
_set_filename_template("{model_name}")
|
_set_filename_template("{model_name}")
|
||||||
scanner = MockScanner(cache=MockCache([
|
scanner = MockScanner(cache=MockCache([
|
||||||
|
|||||||
Reference in New Issue
Block a user