diff --git a/locales/de.json b/locales/de.json
index 030d92e7..c7c73326 100644
--- a/locales/de.json
+++ b/locales/de.json
@@ -1941,6 +1941,13 @@
"conflictsResolveFailed": "Auflösung der Dateinamenskonflikte fehlgeschlagen: {message}"
}
},
+ "conflictConfirm": {
+ "title": "Dateinamenskonflikte auflösen",
+ "message": "Umbenennen durch Anhängen eines 4-stelligen Hashs an jeden doppelten Dateinamen.",
+ "note": "Dieser Vorgang benennt Dateien auf der Festplatte um. Modellreferenzen in vorhandenen Workflows müssen möglicherweise aktualisiert werden, wenn Sie das A1111-Syntaxformat verwenden.",
+ "confirm": "Dateien umbenennen",
+ "cancel": "Abbrechen"
+ },
"banners": {
"versionMismatch": {
"title": "Anwendungs-Update erkannt",
diff --git a/locales/en.json b/locales/en.json
index df07ed78..d241cebe 100644
--- a/locales/en.json
+++ b/locales/en.json
@@ -1941,6 +1941,13 @@
"conflictsResolveFailed": "Failed to resolve filename conflicts: {message}"
}
},
+ "conflictConfirm": {
+ "title": "Resolve Filename Conflicts",
+ "message": "Renaming by appending a 4-character hash to each duplicate filename.",
+ "note": "This operation renames files on disk. Model references in existing workflows may need updating if you use the A1111 syntax format.",
+ "confirm": "Rename Files",
+ "cancel": "Cancel"
+ },
"banners": {
"versionMismatch": {
"title": "Application Update Detected",
diff --git a/locales/es.json b/locales/es.json
index 4ec1c110..e8375dfe 100644
--- a/locales/es.json
+++ b/locales/es.json
@@ -1941,6 +1941,13 @@
"conflictsResolveFailed": "Error al resolver conflictos de nombre de archivo: {message}"
}
},
+ "conflictConfirm": {
+ "title": "Resolver conflictos de nombres de archivo",
+ "message": "Renombrar añadiendo un hash de 4 caracteres a cada nombre de archivo duplicado.",
+ "note": "Esta operación renombra archivos en el disco. Es posible que las referencias a modelos en flujos de trabajo existentes deban actualizarse si usas el formato de sintaxis A1111.",
+ "confirm": "Renombrar archivos",
+ "cancel": "Cancelar"
+ },
"banners": {
"versionMismatch": {
"title": "Actualización de la aplicación detectada",
diff --git a/locales/fr.json b/locales/fr.json
index 72ce096c..e5c73852 100644
--- a/locales/fr.json
+++ b/locales/fr.json
@@ -1941,6 +1941,13 @@
"conflictsResolveFailed": "Échec de la résolution des conflits de nom de fichier : {message}"
}
},
+ "conflictConfirm": {
+ "title": "Résoudre les conflits de noms de fichiers",
+ "message": "Renommer en ajoutant un hachage de 4 caractères à chaque nom de fichier en double.",
+ "note": "Cette opération renomme les fichiers sur le disque. Les références de modèle dans les workflows existants peuvent nécessiter une mise à jour si vous utilisez le format de syntaxe A1111.",
+ "confirm": "Renommer les fichiers",
+ "cancel": "Annuler"
+ },
"banners": {
"versionMismatch": {
"title": "Mise à jour de l'application détectée",
diff --git a/locales/he.json b/locales/he.json
index a4206f75..25560175 100644
--- a/locales/he.json
+++ b/locales/he.json
@@ -1941,6 +1941,13 @@
"conflictsResolveFailed": "פתרון התנגשויות שמות קבצים נכשל: {message}"
}
},
+ "conflictConfirm": {
+ "title": "פתור התנגשויות בשמות קבצים",
+ "message": "שינוי שם על ידי הוספת האש באורך 4 תווים לכל שם קובץ כפול.",
+ "note": "פעולה זו משנה שמות של קבצים בדיסק. ייתכן שיהיה צורך לעדכן הפניות למודלים בזרימות עבודה קיימות אם אתה משתמש בפורמט התחביר A1111.",
+ "confirm": "שנה שמות קבצים",
+ "cancel": "ביטול"
+ },
"banners": {
"versionMismatch": {
"title": "זוהה עדכון יישום",
diff --git a/locales/ja.json b/locales/ja.json
index 4c76216f..1a182dd2 100644
--- a/locales/ja.json
+++ b/locales/ja.json
@@ -1941,6 +1941,13 @@
"conflictsResolveFailed": "ファイル名競合の解決に失敗しました: {message}"
}
},
+ "conflictConfirm": {
+ "title": "ファイル名の競合を解決",
+ "message": "重複したファイル名に4文字のハッシュを追加してリネームします。",
+ "note": "この操作はディスク上のファイルをリネームします。A1111 構文形式を使用している場合、既存のワークフロー内のモデル参照を更新する必要があるかもしれません。",
+ "confirm": "ファイルをリネーム",
+ "cancel": "キャンセル"
+ },
"banners": {
"versionMismatch": {
"title": "アプリケーション更新が検出されました",
diff --git a/locales/ko.json b/locales/ko.json
index 903ea104..80eae705 100644
--- a/locales/ko.json
+++ b/locales/ko.json
@@ -1941,6 +1941,13 @@
"conflictsResolveFailed": "파일명 충돌 해결 실패: {message}"
}
},
+ "conflictConfirm": {
+ "title": "파일명 충돌 해결",
+ "message": "중복 파일명에 4자리 해시를 추가하여 이름을 변경합니다.",
+ "note": "이 작업은 디스크에 있는 파일의 이름을 변경합니다. A1111 구문 형식을 사용하는 경우 기존 워크플로우의 모델 참조를 업데이트해야 할 수 있습니다.",
+ "confirm": "파일 이름 변경",
+ "cancel": "취소"
+ },
"banners": {
"versionMismatch": {
"title": "애플리케이션 업데이트 감지",
diff --git a/locales/ru.json b/locales/ru.json
index bddeca81..06d97d2c 100644
--- a/locales/ru.json
+++ b/locales/ru.json
@@ -1941,6 +1941,13 @@
"conflictsResolveFailed": "Не удалось разрешить конфликты имён файлов: {message}"
}
},
+ "conflictConfirm": {
+ "title": "Разрешить конфликты имён файлов",
+ "message": "Переименование с добавлением 4-символьного хеша к каждому дублирующемуся имени файла.",
+ "note": "Эта операция переименовывает файлы на диске. Если вы используете синтаксис A1111, ссылки на модели в существующих рабочих процессах могут потребовать обновления.",
+ "confirm": "Переименовать файлы",
+ "cancel": "Отмена"
+ },
"banners": {
"versionMismatch": {
"title": "Обнаружено обновление приложения",
diff --git a/locales/zh-CN.json b/locales/zh-CN.json
index 11143c17..d0bee6b7 100644
--- a/locales/zh-CN.json
+++ b/locales/zh-CN.json
@@ -1941,6 +1941,13 @@
"conflictsResolveFailed": "解决文件名冲突失败:{message}"
}
},
+ "conflictConfirm": {
+ "title": "解决文件名冲突",
+ "message": "通过在每个重复文件名后附加 4 位哈希值来重命名文件。",
+ "note": "此操作会重命名磁盘上的文件。如果使用 A1111 语法格式,现有工作流中的模型引用可能需要更新。",
+ "confirm": "重命名文件",
+ "cancel": "取消"
+ },
"banners": {
"versionMismatch": {
"title": "检测到应用更新",
diff --git a/locales/zh-TW.json b/locales/zh-TW.json
index 68c1cfbd..94856a2b 100644
--- a/locales/zh-TW.json
+++ b/locales/zh-TW.json
@@ -1941,6 +1941,13 @@
"conflictsResolveFailed": "解決檔案名稱衝突失敗:{message}"
}
},
+ "conflictConfirm": {
+ "title": "解決檔案名稱衝突",
+ "message": "通過在每個重複檔案名稱後附加 4 位元哈希值來重新命名檔案。",
+ "note": "此操作會重新命名磁碟上的檔案。如果使用 A1111 語法格式,現有工作流程中的模型參考可能需要更新。",
+ "confirm": "重新命名檔案",
+ "cancel": "取消"
+ },
"banners": {
"versionMismatch": {
"title": "偵測到應用程式更新",
diff --git a/py/routes/handlers/misc_handlers.py b/py/routes/handlers/misc_handlers.py
index 14624a70..25fb5be5 100644
--- a/py/routes/handlers/misc_handlers.py
+++ b/py/routes/handlers/misc_handlers.py
@@ -1063,12 +1063,22 @@ class DoctorHandler:
"total_conflict_files": total_conflict_files,
}
]
- for conflict in all_conflicts:
+
+ # Show at most 5 conflict groups inline; note any remainder.
+ MAX_VISIBLE_CONFLICTS = 5
+ visible_conflicts = all_conflicts[:MAX_VISIBLE_CONFLICTS]
+ for conflict in visible_conflicts:
details.append(
- f"[{conflict['label']}] '{conflict['filename']}' "
+ f"'{conflict['filename']}' "
f"found in {len(conflict['paths'])} locations"
)
+ hidden_count = len(all_conflicts) - MAX_VISIBLE_CONFLICTS
+ if hidden_count > 0:
+ details.append(
+ f"...and {hidden_count} more duplicate filename group(s)"
+ )
+
return {
"id": "filename_conflicts",
"title": "Duplicate Filename Conflicts",
@@ -1079,7 +1089,11 @@ class DoctorHandler:
{
"id": "resolve-filename-conflicts",
"label": "Resolve Conflicts",
- }
+ },
+ {
+ "id": "open-settings-syntax-format",
+ "label": "Switch to Full Path Syntax",
+ },
],
}
diff --git a/static/css/components/modal/delete-modal.css b/static/css/components/modal/delete-modal.css
index 7a1334a4..68e32f82 100644
--- a/static/css/components/modal/delete-modal.css
+++ b/static/css/components/modal/delete-modal.css
@@ -33,6 +33,39 @@
animation: modalFadeIn 0.2s ease-out;
}
+#resolveFilenameConflictsModal .confirmation-message {
+ color: var(--text-color);
+ margin: var(--space-2) 0;
+ font-size: 1em;
+ line-height: 1.5;
+}
+
+#resolveFilenameConflictsModal .resolve-conflicts-detail {
+ color: var(--text-color);
+ margin: var(--space-2) 0;
+ font-size: 0.95em;
+ line-height: 1.5;
+}
+
+#resolveFilenameConflictsModal .resolve-conflicts-detail code {
+ background: var(--lora-surface);
+ padding: 2px 6px;
+ border-radius: 3px;
+ font-family: monospace;
+ border: 1px solid var(--lora-border);
+}
+
+#resolveFilenameConflictsModal .resolve-conflicts-impact {
+ background: var(--lora-surface);
+ border: 1px solid var(--lora-border);
+ border-radius: var(--border-radius-sm);
+ padding: var(--space-2);
+ margin: var(--space-2) 0;
+ color: var(--text-color);
+ text-align: left;
+ line-height: 1.5;
+}
+
.delete-model-info,
.exclude-model-info {
/* Update info display styling */
diff --git a/static/css/components/modal/settings-modal.css b/static/css/components/modal/settings-modal.css
index a0f960b0..66fc53a6 100644
--- a/static/css/components/modal/settings-modal.css
+++ b/static/css/components/modal/settings-modal.css
@@ -1369,3 +1369,14 @@ input:checked + .toggle-slider:before {
background: var(--lora-error);
color: white;
}
+
+/* Highlight animation for setting items targeted from Doctor actions */
+@keyframes settings-highlight-pulse {
+ 0%, 100% { box-shadow: 0 0 0 0 rgba(from var(--lora-accent) r g b / 0.4); }
+ 50% { box-shadow: 0 0 0 4px rgba(from var(--lora-accent) r g b / 0.2); }
+}
+
+.settings-setting-highlight {
+ animation: settings-highlight-pulse 1.5s ease-in-out 3;
+ border-radius: var(--border-radius-xs);
+}
diff --git a/static/js/managers/DoctorManager.js b/static/js/managers/DoctorManager.js
index 0aaefd2b..fbb365c9 100644
--- a/static/js/managers/DoctorManager.js
+++ b/static/js/managers/DoctorManager.js
@@ -324,11 +324,42 @@ export class DoctorManager {
}
}, 100);
break;
+ case 'open-settings-syntax-format':
+ modalManager.showModal('settingsModal');
+ window.setTimeout(() => {
+ // Switch to Interface section
+ document.querySelectorAll('.settings-section').forEach((s) => s.classList.remove('active'));
+ const interfaceSection = document.getElementById('section-interface');
+ if (interfaceSection) {
+ interfaceSection.classList.add('active');
+ }
+ document.querySelectorAll('.settings-nav-item').forEach((n) => n.classList.remove('active'));
+ const interfaceNav = document.querySelector('.settings-nav-item[data-section="interface"]');
+ if (interfaceNav) {
+ interfaceNav.classList.add('active');
+ }
+
+ // Focus and scroll to the LoRA Syntax Format dropdown
+ const select = document.getElementById('loraSyntaxFormat');
+ if (select) {
+ select.focus();
+ select.scrollIntoView({ behavior: 'smooth', block: 'center' });
+ // Add temporary highlight animation
+ const settingItem = select.closest('.setting-item');
+ if (settingItem) {
+ settingItem.classList.add('settings-setting-highlight');
+ setTimeout(() => {
+ settingItem.classList.remove('settings-setting-highlight');
+ }, 4500);
+ }
+ }
+ }, 100);
+ break;
case 'repair-cache':
await this.repairCache();
break;
case 'resolve-filename-conflicts':
- await this.resolveFilenameConflicts();
+ await this.promptResolveConflicts();
break;
case 'reload-page':
this.reloadUi();
@@ -358,6 +389,62 @@ export class DoctorManager {
}
}
+ _getConflictStats() {
+ const conflict = (this.lastDiagnostics?.diagnostics || []).find(
+ (d) => d.id === 'filename_conflicts'
+ );
+ if (!conflict || !Array.isArray(conflict.details)) {
+ return { groups: 0, files: 0 };
+ }
+ const summary = conflict.details.find(
+ (d) => d && typeof d === 'object' && d.conflict_groups !== undefined
+ );
+ return {
+ groups: summary?.conflict_groups || 0,
+ files: summary?.total_conflict_files || 0,
+ };
+ }
+
+ async promptResolveConflicts() {
+ const stats = this._getConflictStats();
+ if (stats.groups === 0) {
+ return;
+ }
+
+ const detailEl = document.getElementById('resolveConflictsDetail');
+ if (detailEl) {
+ detailEl.innerHTML = translate(
+ 'conflictConfirm.detail',
+ {},
+ 'Example: Add_Details_v1.2 \u2192 Add_Details_v1.2-a3f7'
+ );
+ }
+
+ const impactEl = document.getElementById('resolveConflictsImpact');
+ if (impactEl) {
+ impactEl.innerHTML = translate(
+ 'conflictConfirm.impact',
+ { count: stats.files, groups: stats.groups },
+ `Will rename ${stats.files} file(s) across ${stats.groups} duplicate group(s).`
+ );
+ }
+
+ this._confirmResolveResolve = null;
+ modalManager.showModal('resolveFilenameConflictsModal');
+ return new Promise((resolve) => {
+ this._confirmResolveResolve = resolve;
+ });
+ }
+
+ async confirmResolveConflicts() {
+ modalManager.closeModal('resolveFilenameConflictsModal');
+ if (this._confirmResolveResolve) {
+ this._confirmResolveResolve(true);
+ this._confirmResolveResolve = null;
+ }
+ await this.resolveFilenameConflicts();
+ }
+
async resolveFilenameConflicts() {
try {
this.setLoading(true);
@@ -449,3 +536,8 @@ export class DoctorManager {
}
export const doctorManager = new DoctorManager();
+
+// Make available globally for HTML onclick handlers
+if (typeof window !== 'undefined') {
+ window.doctorManager = doctorManager;
+}
diff --git a/static/js/managers/ModalManager.js b/static/js/managers/ModalManager.js
index c4653655..414fe682 100644
--- a/static/js/managers/ModalManager.js
+++ b/static/js/managers/ModalManager.js
@@ -316,6 +316,19 @@ export class ModalManager {
});
}
+ // Register resolveFilenameConflictsModal
+ const resolveFilenameConflictsModal = document.getElementById('resolveFilenameConflictsModal');
+ if (resolveFilenameConflictsModal) {
+ this.registerModal('resolveFilenameConflictsModal', {
+ element: resolveFilenameConflictsModal,
+ onClose: () => {
+ this.getModal('resolveFilenameConflictsModal').element.classList.remove('show');
+ document.body.classList.remove('modal-open');
+ },
+ closeOnOutsideClick: true
+ });
+ }
+
document.addEventListener('keydown', this.boundHandleEscape);
this.initialized = true;
}
@@ -396,7 +409,8 @@ export class ModalManager {
id === "modelDuplicateDeleteModal" ||
id === "clearCacheModal" ||
id === "bulkDeleteModal" ||
- id === "checkUpdatesConfirmModal"
+ id === "checkUpdatesConfirmModal" ||
+ id === "resolveFilenameConflictsModal"
) {
modal.element.classList.add("show");
} else {
diff --git a/templates/components/modals/confirm_modals.html b/templates/components/modals/confirm_modals.html
index 6c4fedea..d7440885 100644
--- a/templates/components/modals/confirm_modals.html
+++ b/templates/components/modals/confirm_modals.html
@@ -108,4 +108,21 @@
+
+
+
+