mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-03-26 23:48:52 -03:00
feat(download): add configurable base model download exclusions
This commit is contained in:
@@ -323,6 +323,24 @@
|
|||||||
"saveFailed": "Übersprungene Pfade konnten nicht gespeichert werden: {message}"
|
"saveFailed": "Übersprungene Pfade konnten nicht gespeichert werden: {message}"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"downloadSkipBaseModels": {
|
||||||
|
"label": "Downloads für Basismodelle überspringen",
|
||||||
|
"help": "Gilt für alle Download-Abläufe. Hier können nur unterstützte Basismodelle ausgewählt werden.",
|
||||||
|
"searchPlaceholder": "Basismodelle filtern...",
|
||||||
|
"empty": "Keine Basismodelle entsprechen der aktuellen Suche.",
|
||||||
|
"summary": {
|
||||||
|
"none": "Nichts ausgewählt",
|
||||||
|
"count": "{count} ausgewählt"
|
||||||
|
},
|
||||||
|
"actions": {
|
||||||
|
"edit": "Bearbeiten",
|
||||||
|
"collapse": "Einklappen",
|
||||||
|
"clear": "Löschen"
|
||||||
|
},
|
||||||
|
"validation": {
|
||||||
|
"saveFailed": "Ausgeschlossene Basismodelle konnten nicht gespeichert werden: {message}"
|
||||||
|
}
|
||||||
|
},
|
||||||
"layoutSettings": {
|
"layoutSettings": {
|
||||||
"displayDensity": "Anzeige-Dichte",
|
"displayDensity": "Anzeige-Dichte",
|
||||||
"displayDensityOptions": {
|
"displayDensityOptions": {
|
||||||
@@ -1467,6 +1485,7 @@
|
|||||||
"pleaseSelectVersion": "Bitte wählen Sie eine Version aus",
|
"pleaseSelectVersion": "Bitte wählen Sie eine Version aus",
|
||||||
"versionExists": "Diese Version existiert bereits in Ihrer Bibliothek",
|
"versionExists": "Diese Version existiert bereits in Ihrer Bibliothek",
|
||||||
"downloadCompleted": "Download erfolgreich abgeschlossen",
|
"downloadCompleted": "Download erfolgreich abgeschlossen",
|
||||||
|
"downloadSkippedByBaseModel": "Download übersprungen, weil das Basismodell {baseModel} ausgeschlossen ist",
|
||||||
"autoOrganizeSuccess": "Automatische Organisation für {count} {type} erfolgreich abgeschlossen",
|
"autoOrganizeSuccess": "Automatische Organisation für {count} {type} erfolgreich abgeschlossen",
|
||||||
"autoOrganizePartialSuccess": "Automatische Organisation abgeschlossen: {success} verschoben, {failures} fehlgeschlagen von insgesamt {total} Modellen",
|
"autoOrganizePartialSuccess": "Automatische Organisation abgeschlossen: {success} verschoben, {failures} fehlgeschlagen von insgesamt {total} Modellen",
|
||||||
"autoOrganizeFailed": "Automatische Organisation fehlgeschlagen: {error}",
|
"autoOrganizeFailed": "Automatische Organisation fehlgeschlagen: {error}",
|
||||||
|
|||||||
@@ -323,6 +323,24 @@
|
|||||||
"saveFailed": "Unable to save skip paths: {message}"
|
"saveFailed": "Unable to save skip paths: {message}"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"downloadSkipBaseModels": {
|
||||||
|
"label": "Skip downloads for base models",
|
||||||
|
"help": "When a model version uses one of these base models, LoRA Manager will skip the download before any file transfer starts. Applies to all download flows. Only supported base models can be selected here.",
|
||||||
|
"searchPlaceholder": "Filter base models...",
|
||||||
|
"empty": "No base models match the current search.",
|
||||||
|
"summary": {
|
||||||
|
"none": "None selected",
|
||||||
|
"count": "{count} selected"
|
||||||
|
},
|
||||||
|
"actions": {
|
||||||
|
"edit": "Edit",
|
||||||
|
"collapse": "Collapse",
|
||||||
|
"clear": "Clear"
|
||||||
|
},
|
||||||
|
"validation": {
|
||||||
|
"saveFailed": "Unable to save excluded base models: {message}"
|
||||||
|
}
|
||||||
|
},
|
||||||
"layoutSettings": {
|
"layoutSettings": {
|
||||||
"displayDensity": "Display Density",
|
"displayDensity": "Display Density",
|
||||||
"displayDensityOptions": {
|
"displayDensityOptions": {
|
||||||
@@ -1467,6 +1485,7 @@
|
|||||||
"pleaseSelectVersion": "Please select a version",
|
"pleaseSelectVersion": "Please select a version",
|
||||||
"versionExists": "This version already exists in your library",
|
"versionExists": "This version already exists in your library",
|
||||||
"downloadCompleted": "Download completed successfully",
|
"downloadCompleted": "Download completed successfully",
|
||||||
|
"downloadSkippedByBaseModel": "Skipped download because base model {baseModel} is excluded",
|
||||||
"autoOrganizeSuccess": "Auto-organize completed successfully for {count} {type}",
|
"autoOrganizeSuccess": "Auto-organize completed successfully for {count} {type}",
|
||||||
"autoOrganizePartialSuccess": "Auto-organize completed with {success} moved, {failures} failed out of {total} models",
|
"autoOrganizePartialSuccess": "Auto-organize completed with {success} moved, {failures} failed out of {total} models",
|
||||||
"autoOrganizeFailed": "Auto-organize failed: {error}",
|
"autoOrganizeFailed": "Auto-organize failed: {error}",
|
||||||
|
|||||||
@@ -323,6 +323,24 @@
|
|||||||
"saveFailed": "No se pudieron guardar las rutas a omitir: {message}"
|
"saveFailed": "No se pudieron guardar las rutas a omitir: {message}"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"downloadSkipBaseModels": {
|
||||||
|
"label": "Omitir descargas para modelos base",
|
||||||
|
"help": "Se aplica a todos los flujos de descarga. Aquí solo se pueden seleccionar modelos base compatibles.",
|
||||||
|
"searchPlaceholder": "Filtrar modelos base...",
|
||||||
|
"empty": "Ningún modelo base coincide con la búsqueda actual.",
|
||||||
|
"summary": {
|
||||||
|
"none": "Ninguno seleccionado",
|
||||||
|
"count": "{count} seleccionados"
|
||||||
|
},
|
||||||
|
"actions": {
|
||||||
|
"edit": "Editar",
|
||||||
|
"collapse": "Contraer",
|
||||||
|
"clear": "Limpiar"
|
||||||
|
},
|
||||||
|
"validation": {
|
||||||
|
"saveFailed": "No se pudieron guardar los modelos base excluidos: {message}"
|
||||||
|
}
|
||||||
|
},
|
||||||
"layoutSettings": {
|
"layoutSettings": {
|
||||||
"displayDensity": "Densidad de visualización",
|
"displayDensity": "Densidad de visualización",
|
||||||
"displayDensityOptions": {
|
"displayDensityOptions": {
|
||||||
@@ -1467,6 +1485,7 @@
|
|||||||
"pleaseSelectVersion": "Por favor selecciona una versión",
|
"pleaseSelectVersion": "Por favor selecciona una versión",
|
||||||
"versionExists": "Esta versión ya existe en tu biblioteca",
|
"versionExists": "Esta versión ya existe en tu biblioteca",
|
||||||
"downloadCompleted": "Descarga completada exitosamente",
|
"downloadCompleted": "Descarga completada exitosamente",
|
||||||
|
"downloadSkippedByBaseModel": "Descarga omitida porque el modelo base {baseModel} está excluido",
|
||||||
"autoOrganizeSuccess": "Auto-organización completada exitosamente para {count} {type}",
|
"autoOrganizeSuccess": "Auto-organización completada exitosamente para {count} {type}",
|
||||||
"autoOrganizePartialSuccess": "Auto-organización completada con {success} movidos, {failures} fallidos de un total de {total} modelos",
|
"autoOrganizePartialSuccess": "Auto-organización completada con {success} movidos, {failures} fallidos de un total de {total} modelos",
|
||||||
"autoOrganizeFailed": "Auto-organización fallida: {error}",
|
"autoOrganizeFailed": "Auto-organización fallida: {error}",
|
||||||
|
|||||||
@@ -323,6 +323,24 @@
|
|||||||
"saveFailed": "Impossible d'enregistrer les chemins à ignorer : {message}"
|
"saveFailed": "Impossible d'enregistrer les chemins à ignorer : {message}"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"downloadSkipBaseModels": {
|
||||||
|
"label": "Ignorer les téléchargements pour certains modèles de base",
|
||||||
|
"help": "S’applique à tous les flux de téléchargement. Seuls les modèles de base pris en charge peuvent être sélectionnés ici.",
|
||||||
|
"searchPlaceholder": "Filtrer les modèles de base...",
|
||||||
|
"empty": "Aucun modèle de base ne correspond à la recherche actuelle.",
|
||||||
|
"summary": {
|
||||||
|
"none": "Aucune sélection",
|
||||||
|
"count": "{count} sélectionnés"
|
||||||
|
},
|
||||||
|
"actions": {
|
||||||
|
"edit": "Modifier",
|
||||||
|
"collapse": "Réduire",
|
||||||
|
"clear": "Effacer"
|
||||||
|
},
|
||||||
|
"validation": {
|
||||||
|
"saveFailed": "Impossible d’enregistrer les modèles de base exclus : {message}"
|
||||||
|
}
|
||||||
|
},
|
||||||
"layoutSettings": {
|
"layoutSettings": {
|
||||||
"displayDensity": "Densité d'affichage",
|
"displayDensity": "Densité d'affichage",
|
||||||
"displayDensityOptions": {
|
"displayDensityOptions": {
|
||||||
@@ -1467,6 +1485,7 @@
|
|||||||
"pleaseSelectVersion": "Veuillez sélectionner une version",
|
"pleaseSelectVersion": "Veuillez sélectionner une version",
|
||||||
"versionExists": "Cette version existe déjà dans votre bibliothèque",
|
"versionExists": "Cette version existe déjà dans votre bibliothèque",
|
||||||
"downloadCompleted": "Téléchargement terminé avec succès",
|
"downloadCompleted": "Téléchargement terminé avec succès",
|
||||||
|
"downloadSkippedByBaseModel": "Téléchargement ignoré, car le modèle de base {baseModel} est exclu",
|
||||||
"autoOrganizeSuccess": "Auto-organisation terminée avec succès pour {count} {type}",
|
"autoOrganizeSuccess": "Auto-organisation terminée avec succès pour {count} {type}",
|
||||||
"autoOrganizePartialSuccess": "Auto-organisation terminée avec {success} déplacés, {failures} échecs sur {total} modèles",
|
"autoOrganizePartialSuccess": "Auto-organisation terminée avec {success} déplacés, {failures} échecs sur {total} modèles",
|
||||||
"autoOrganizeFailed": "Échec de l'auto-organisation : {error}",
|
"autoOrganizeFailed": "Échec de l'auto-organisation : {error}",
|
||||||
|
|||||||
@@ -323,6 +323,24 @@
|
|||||||
"saveFailed": "לא ניתן לשמור נתיבי דילוג: {message}"
|
"saveFailed": "לא ניתן לשמור נתיבי דילוג: {message}"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"downloadSkipBaseModels": {
|
||||||
|
"label": "דלג על הורדות עבור מודלי בסיס",
|
||||||
|
"help": "חל על כל תהליכי ההורדה. ניתן לבחור כאן רק מודלי בסיס נתמכים.",
|
||||||
|
"searchPlaceholder": "סנן מודלי בסיס...",
|
||||||
|
"empty": "אין מודלי בסיס התואמים לחיפוש הנוכחי.",
|
||||||
|
"summary": {
|
||||||
|
"none": "לא נבחר דבר",
|
||||||
|
"count": "{count} נבחרו"
|
||||||
|
},
|
||||||
|
"actions": {
|
||||||
|
"edit": "עריכה",
|
||||||
|
"collapse": "כווץ",
|
||||||
|
"clear": "נקה"
|
||||||
|
},
|
||||||
|
"validation": {
|
||||||
|
"saveFailed": "לא ניתן לשמור את מודלי הבסיס המוחרגים: {message}"
|
||||||
|
}
|
||||||
|
},
|
||||||
"layoutSettings": {
|
"layoutSettings": {
|
||||||
"displayDensity": "צפיפות תצוגה",
|
"displayDensity": "צפיפות תצוגה",
|
||||||
"displayDensityOptions": {
|
"displayDensityOptions": {
|
||||||
@@ -1467,6 +1485,7 @@
|
|||||||
"pleaseSelectVersion": "אנא בחר גרסה",
|
"pleaseSelectVersion": "אנא בחר גרסה",
|
||||||
"versionExists": "גרסה זו כבר קיימת בספרייה שלך",
|
"versionExists": "גרסה זו כבר קיימת בספרייה שלך",
|
||||||
"downloadCompleted": "ההורדה הושלמה בהצלחה",
|
"downloadCompleted": "ההורדה הושלמה בהצלחה",
|
||||||
|
"downloadSkippedByBaseModel": "ההורדה דולגה כי מודל הבסיס {baseModel} מוחרג",
|
||||||
"autoOrganizeSuccess": "הארגון האוטומטי הושלם בהצלחה עבור {count} {type}",
|
"autoOrganizeSuccess": "הארגון האוטומטי הושלם בהצלחה עבור {count} {type}",
|
||||||
"autoOrganizePartialSuccess": "הארגון האוטומטי הושלם עם {success} שהועברו, {failures} שנכשלו מתוך {total} מודלים",
|
"autoOrganizePartialSuccess": "הארגון האוטומטי הושלם עם {success} שהועברו, {failures} שנכשלו מתוך {total} מודלים",
|
||||||
"autoOrganizeFailed": "הארגון האוטומטי נכשל: {error}",
|
"autoOrganizeFailed": "הארגון האוטומטי נכשל: {error}",
|
||||||
|
|||||||
@@ -323,6 +323,24 @@
|
|||||||
"saveFailed": "スキップパスの保存に失敗しました:{message}"
|
"saveFailed": "スキップパスの保存に失敗しました:{message}"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"downloadSkipBaseModels": {
|
||||||
|
"label": "ベースモデルのダウンロードをスキップ",
|
||||||
|
"help": "すべてのダウンロードフローに適用されます。ここでは対応しているベースモデルのみ選択できます。",
|
||||||
|
"searchPlaceholder": "ベースモデルを絞り込む...",
|
||||||
|
"empty": "現在の検索に一致するベースモデルはありません。",
|
||||||
|
"summary": {
|
||||||
|
"none": "未選択",
|
||||||
|
"count": "{count} 件を選択"
|
||||||
|
},
|
||||||
|
"actions": {
|
||||||
|
"edit": "編集",
|
||||||
|
"collapse": "折りたたむ",
|
||||||
|
"clear": "クリア"
|
||||||
|
},
|
||||||
|
"validation": {
|
||||||
|
"saveFailed": "除外するベースモデルを保存できませんでした: {message}"
|
||||||
|
}
|
||||||
|
},
|
||||||
"layoutSettings": {
|
"layoutSettings": {
|
||||||
"displayDensity": "表示密度",
|
"displayDensity": "表示密度",
|
||||||
"displayDensityOptions": {
|
"displayDensityOptions": {
|
||||||
@@ -1467,6 +1485,7 @@
|
|||||||
"pleaseSelectVersion": "バージョンを選択してください",
|
"pleaseSelectVersion": "バージョンを選択してください",
|
||||||
"versionExists": "このバージョンは既にライブラリに存在します",
|
"versionExists": "このバージョンは既にライブラリに存在します",
|
||||||
"downloadCompleted": "ダウンロードが正常に完了しました",
|
"downloadCompleted": "ダウンロードが正常に完了しました",
|
||||||
|
"downloadSkippedByBaseModel": "ベースモデル {baseModel} が除外されているため、ダウンロードをスキップしました",
|
||||||
"autoOrganizeSuccess": "{count} {type} の自動整理が正常に完了しました",
|
"autoOrganizeSuccess": "{count} {type} の自動整理が正常に完了しました",
|
||||||
"autoOrganizePartialSuccess": "自動整理が完了しました:{total} モデル中 {success} 移動、{failures} 失敗",
|
"autoOrganizePartialSuccess": "自動整理が完了しました:{total} モデル中 {success} 移動、{failures} 失敗",
|
||||||
"autoOrganizeFailed": "自動整理に失敗しました:{error}",
|
"autoOrganizeFailed": "自動整理に失敗しました:{error}",
|
||||||
|
|||||||
@@ -323,6 +323,24 @@
|
|||||||
"saveFailed": "건너뛰기 경로를 저장할 수 없습니다: {message}"
|
"saveFailed": "건너뛰기 경로를 저장할 수 없습니다: {message}"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"downloadSkipBaseModels": {
|
||||||
|
"label": "기본 모델 다운로드 건너뛰기",
|
||||||
|
"help": "모든 다운로드 흐름에 적용됩니다. 여기서는 지원되는 기본 모델만 선택할 수 있습니다.",
|
||||||
|
"searchPlaceholder": "기본 모델 필터링...",
|
||||||
|
"empty": "현재 검색과 일치하는 기본 모델이 없습니다.",
|
||||||
|
"summary": {
|
||||||
|
"none": "선택 없음",
|
||||||
|
"count": "{count}개 선택됨"
|
||||||
|
},
|
||||||
|
"actions": {
|
||||||
|
"edit": "편집",
|
||||||
|
"collapse": "접기",
|
||||||
|
"clear": "지우기"
|
||||||
|
},
|
||||||
|
"validation": {
|
||||||
|
"saveFailed": "제외된 기본 모델을 저장할 수 없습니다: {message}"
|
||||||
|
}
|
||||||
|
},
|
||||||
"layoutSettings": {
|
"layoutSettings": {
|
||||||
"displayDensity": "표시 밀도",
|
"displayDensity": "표시 밀도",
|
||||||
"displayDensityOptions": {
|
"displayDensityOptions": {
|
||||||
@@ -1467,6 +1485,7 @@
|
|||||||
"pleaseSelectVersion": "버전을 선택해주세요",
|
"pleaseSelectVersion": "버전을 선택해주세요",
|
||||||
"versionExists": "이 버전은 이미 라이브러리에 있습니다",
|
"versionExists": "이 버전은 이미 라이브러리에 있습니다",
|
||||||
"downloadCompleted": "다운로드가 성공적으로 완료되었습니다",
|
"downloadCompleted": "다운로드가 성공적으로 완료되었습니다",
|
||||||
|
"downloadSkippedByBaseModel": "기본 모델 {baseModel}이(가) 제외되어 다운로드를 건너뛰었습니다",
|
||||||
"autoOrganizeSuccess": "{count}개의 {type}에 대해 자동 정리가 성공적으로 완료되었습니다",
|
"autoOrganizeSuccess": "{count}개의 {type}에 대해 자동 정리가 성공적으로 완료되었습니다",
|
||||||
"autoOrganizePartialSuccess": "자동 정리 완료: 전체 {total}개 중 {success}개 이동, {failures}개 실패",
|
"autoOrganizePartialSuccess": "자동 정리 완료: 전체 {total}개 중 {success}개 이동, {failures}개 실패",
|
||||||
"autoOrganizeFailed": "자동 정리 실패: {error}",
|
"autoOrganizeFailed": "자동 정리 실패: {error}",
|
||||||
|
|||||||
@@ -323,6 +323,24 @@
|
|||||||
"saveFailed": "Не удалось сохранить пути для пропуска: {message}"
|
"saveFailed": "Не удалось сохранить пути для пропуска: {message}"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"downloadSkipBaseModels": {
|
||||||
|
"label": "Пропускать загрузки для базовых моделей",
|
||||||
|
"help": "Применяется ко всем сценариям загрузки. Здесь можно выбрать только поддерживаемые базовые модели.",
|
||||||
|
"searchPlaceholder": "Фильтровать базовые модели...",
|
||||||
|
"empty": "Нет базовых моделей, соответствующих текущему поиску.",
|
||||||
|
"summary": {
|
||||||
|
"none": "Ничего не выбрано",
|
||||||
|
"count": "Выбрано: {count}"
|
||||||
|
},
|
||||||
|
"actions": {
|
||||||
|
"edit": "Изменить",
|
||||||
|
"collapse": "Свернуть",
|
||||||
|
"clear": "Очистить"
|
||||||
|
},
|
||||||
|
"validation": {
|
||||||
|
"saveFailed": "Не удалось сохранить исключённые базовые модели: {message}"
|
||||||
|
}
|
||||||
|
},
|
||||||
"layoutSettings": {
|
"layoutSettings": {
|
||||||
"displayDensity": "Плотность отображения",
|
"displayDensity": "Плотность отображения",
|
||||||
"displayDensityOptions": {
|
"displayDensityOptions": {
|
||||||
@@ -1467,6 +1485,7 @@
|
|||||||
"pleaseSelectVersion": "Пожалуйста, выберите версию",
|
"pleaseSelectVersion": "Пожалуйста, выберите версию",
|
||||||
"versionExists": "Эта версия уже существует в вашей библиотеке",
|
"versionExists": "Эта версия уже существует в вашей библиотеке",
|
||||||
"downloadCompleted": "Загрузка успешно завершена",
|
"downloadCompleted": "Загрузка успешно завершена",
|
||||||
|
"downloadSkippedByBaseModel": "Загрузка пропущена, потому что базовая модель {baseModel} исключена",
|
||||||
"autoOrganizeSuccess": "Автоматическая организация успешно завершена для {count} {type}",
|
"autoOrganizeSuccess": "Автоматическая организация успешно завершена для {count} {type}",
|
||||||
"autoOrganizePartialSuccess": "Автоматическая организация завершена: перемещено {success}, не удалось {failures} из {total} моделей",
|
"autoOrganizePartialSuccess": "Автоматическая организация завершена: перемещено {success}, не удалось {failures} из {total} моделей",
|
||||||
"autoOrganizeFailed": "Ошибка автоматической организации: {error}",
|
"autoOrganizeFailed": "Ошибка автоматической организации: {error}",
|
||||||
|
|||||||
@@ -323,6 +323,24 @@
|
|||||||
"saveFailed": "无法保存跳过路径:{message}"
|
"saveFailed": "无法保存跳过路径:{message}"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"downloadSkipBaseModels": {
|
||||||
|
"label": "跳过这些基础模型的下载",
|
||||||
|
"help": "适用于所有下载流程。这里只能选择受支持的基础模型。",
|
||||||
|
"searchPlaceholder": "筛选基础模型...",
|
||||||
|
"empty": "没有与当前搜索匹配的基础模型。",
|
||||||
|
"summary": {
|
||||||
|
"none": "未选择",
|
||||||
|
"count": "已选择 {count} 项"
|
||||||
|
},
|
||||||
|
"actions": {
|
||||||
|
"edit": "编辑",
|
||||||
|
"collapse": "收起",
|
||||||
|
"clear": "清空"
|
||||||
|
},
|
||||||
|
"validation": {
|
||||||
|
"saveFailed": "无法保存已排除的基础模型:{message}"
|
||||||
|
}
|
||||||
|
},
|
||||||
"layoutSettings": {
|
"layoutSettings": {
|
||||||
"displayDensity": "显示密度",
|
"displayDensity": "显示密度",
|
||||||
"displayDensityOptions": {
|
"displayDensityOptions": {
|
||||||
@@ -1467,6 +1485,7 @@
|
|||||||
"pleaseSelectVersion": "请选择版本",
|
"pleaseSelectVersion": "请选择版本",
|
||||||
"versionExists": "该版本已存在于你的库中",
|
"versionExists": "该版本已存在于你的库中",
|
||||||
"downloadCompleted": "下载成功完成",
|
"downloadCompleted": "下载成功完成",
|
||||||
|
"downloadSkippedByBaseModel": "由于基础模型 {baseModel} 已被排除,已跳过下载",
|
||||||
"autoOrganizeSuccess": "自动整理已成功完成,共 {count} 个 {type}",
|
"autoOrganizeSuccess": "自动整理已成功完成,共 {count} 个 {type}",
|
||||||
"autoOrganizePartialSuccess": "自动整理完成:已移动 {success} 个,{failures} 个失败,共 {total} 个模型",
|
"autoOrganizePartialSuccess": "自动整理完成:已移动 {success} 个,{failures} 个失败,共 {total} 个模型",
|
||||||
"autoOrganizeFailed": "自动整理失败:{error}",
|
"autoOrganizeFailed": "自动整理失败:{error}",
|
||||||
|
|||||||
@@ -323,6 +323,24 @@
|
|||||||
"saveFailed": "無法儲存跳過路徑:{message}"
|
"saveFailed": "無法儲存跳過路徑:{message}"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"downloadSkipBaseModels": {
|
||||||
|
"label": "跳過這些基礎模型的下載",
|
||||||
|
"help": "適用於所有下載流程。這裡只能選擇受支援的基礎模型。",
|
||||||
|
"searchPlaceholder": "篩選基礎模型...",
|
||||||
|
"empty": "沒有符合目前搜尋條件的基礎模型。",
|
||||||
|
"summary": {
|
||||||
|
"none": "未選擇",
|
||||||
|
"count": "已選擇 {count} 項"
|
||||||
|
},
|
||||||
|
"actions": {
|
||||||
|
"edit": "編輯",
|
||||||
|
"collapse": "收起",
|
||||||
|
"clear": "清空"
|
||||||
|
},
|
||||||
|
"validation": {
|
||||||
|
"saveFailed": "無法儲存已排除的基礎模型:{message}"
|
||||||
|
}
|
||||||
|
},
|
||||||
"layoutSettings": {
|
"layoutSettings": {
|
||||||
"displayDensity": "顯示密度",
|
"displayDensity": "顯示密度",
|
||||||
"displayDensityOptions": {
|
"displayDensityOptions": {
|
||||||
@@ -1467,6 +1485,7 @@
|
|||||||
"pleaseSelectVersion": "請選擇一個版本",
|
"pleaseSelectVersion": "請選擇一個版本",
|
||||||
"versionExists": "此版本已存在於您的庫中",
|
"versionExists": "此版本已存在於您的庫中",
|
||||||
"downloadCompleted": "下載成功完成",
|
"downloadCompleted": "下載成功完成",
|
||||||
|
"downloadSkippedByBaseModel": "由於基礎模型 {baseModel} 已被排除,已跳過下載",
|
||||||
"autoOrganizeSuccess": "自動整理已成功完成,共 {count} 個 {type} 已整理",
|
"autoOrganizeSuccess": "自動整理已成功完成,共 {count} 個 {type} 已整理",
|
||||||
"autoOrganizePartialSuccess": "自動整理完成:已移動 {success} 個,{failures} 個失敗,共 {total} 個模型",
|
"autoOrganizePartialSuccess": "自動整理完成:已移動 {success} 個,{failures} 個失敗,共 {total} 個模型",
|
||||||
"autoOrganizeFailed": "自動整理失敗:{error}",
|
"autoOrganizeFailed": "自動整理失敗:{error}",
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ from ..utils.models import LoraMetadata, CheckpointMetadata, EmbeddingMetadata
|
|||||||
from ..utils.constants import (
|
from ..utils.constants import (
|
||||||
CARD_PREVIEW_WIDTH,
|
CARD_PREVIEW_WIDTH,
|
||||||
DIFFUSION_MODEL_BASE_MODELS,
|
DIFFUSION_MODEL_BASE_MODELS,
|
||||||
|
SUPPORTED_DOWNLOAD_SKIP_BASE_MODELS,
|
||||||
VALID_LORA_TYPES,
|
VALID_LORA_TYPES,
|
||||||
)
|
)
|
||||||
from ..utils.civitai_utils import rewrite_preview_url
|
from ..utils.civitai_utils import rewrite_preview_url
|
||||||
@@ -228,7 +229,9 @@ class DownloadManager:
|
|||||||
# Update status based on result
|
# Update status based on result
|
||||||
if task_id in self._active_downloads:
|
if task_id in self._active_downloads:
|
||||||
self._active_downloads[task_id]["status"] = (
|
self._active_downloads[task_id]["status"] = (
|
||||||
"completed" if result["success"] else "failed"
|
result.get("status", "completed")
|
||||||
|
if result["success"]
|
||||||
|
else "failed"
|
||||||
)
|
)
|
||||||
if not result["success"]:
|
if not result["success"]:
|
||||||
self._active_downloads[task_id]["error"] = result.get(
|
self._active_downloads[task_id]["error"] = result.get(
|
||||||
@@ -352,10 +355,54 @@ class DownloadManager:
|
|||||||
"error": f'Model type "{model_type_from_info}" is not supported for download',
|
"error": f'Model type "{model_type_from_info}" is not supported for download',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
excluded_base_models = get_settings_manager().get_download_skip_base_models()
|
||||||
|
base_model_value = version_info.get("baseModel", "")
|
||||||
|
if (
|
||||||
|
isinstance(base_model_value, str)
|
||||||
|
and base_model_value in SUPPORTED_DOWNLOAD_SKIP_BASE_MODELS
|
||||||
|
and base_model_value in excluded_base_models
|
||||||
|
):
|
||||||
|
file_name = ""
|
||||||
|
files = version_info.get("files")
|
||||||
|
if isinstance(files, list):
|
||||||
|
primary_file = next(
|
||||||
|
(
|
||||||
|
file_info
|
||||||
|
for file_info in files
|
||||||
|
if isinstance(file_info, dict) and file_info.get("primary")
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
selected_file = primary_file
|
||||||
|
if selected_file is None:
|
||||||
|
selected_file = next(
|
||||||
|
(file_info for file_info in files if isinstance(file_info, dict)),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
if isinstance(selected_file, dict):
|
||||||
|
raw_file_name = selected_file.get("name", "")
|
||||||
|
if isinstance(raw_file_name, str):
|
||||||
|
file_name = raw_file_name.strip()
|
||||||
|
|
||||||
|
message = (
|
||||||
|
f"Skipped download for '{file_name or version_info.get('name') or f'model_version:{model_version_id or model_id}'}' "
|
||||||
|
f"because base model '{base_model_value}' is excluded in settings"
|
||||||
|
)
|
||||||
|
logger.info(message)
|
||||||
|
return {
|
||||||
|
"success": True,
|
||||||
|
"skipped": True,
|
||||||
|
"status": "skipped",
|
||||||
|
"reason": "base_model_excluded",
|
||||||
|
"message": message,
|
||||||
|
"base_model": base_model_value,
|
||||||
|
"file_name": file_name,
|
||||||
|
"download_id": download_id,
|
||||||
|
}
|
||||||
|
|
||||||
# Check if this checkpoint should be treated as a diffusion model based on baseModel
|
# Check if this checkpoint should be treated as a diffusion model based on baseModel
|
||||||
is_diffusion_model = False
|
is_diffusion_model = False
|
||||||
if model_type == "checkpoint":
|
if model_type == "checkpoint":
|
||||||
base_model_value = version_info.get("baseModel", "")
|
|
||||||
if base_model_value in DIFFUSION_MODEL_BASE_MODELS:
|
if base_model_value in DIFFUSION_MODEL_BASE_MODELS:
|
||||||
is_diffusion_model = True
|
is_diffusion_model = True
|
||||||
logger.info(
|
logger.info(
|
||||||
|
|||||||
@@ -11,7 +11,11 @@ from typing import Any, Awaitable, Dict, Iterable, List, Mapping, Optional, Sequ
|
|||||||
|
|
||||||
from platformdirs import user_config_dir
|
from platformdirs import user_config_dir
|
||||||
|
|
||||||
from ..utils.constants import DEFAULT_HASH_CHUNK_SIZE_MB, DEFAULT_PRIORITY_TAG_CONFIG
|
from ..utils.constants import (
|
||||||
|
DEFAULT_HASH_CHUNK_SIZE_MB,
|
||||||
|
DEFAULT_PRIORITY_TAG_CONFIG,
|
||||||
|
SUPPORTED_DOWNLOAD_SKIP_BASE_MODELS,
|
||||||
|
)
|
||||||
from ..utils.preview_selection import VALID_MATURE_BLUR_LEVELS
|
from ..utils.preview_selection import VALID_MATURE_BLUR_LEVELS
|
||||||
from ..utils.settings_paths import APP_NAME, ensure_settings_file, get_legacy_settings_path
|
from ..utils.settings_paths import APP_NAME, ensure_settings_file, get_legacy_settings_path
|
||||||
from ..utils.tag_priorities import (
|
from ..utils.tag_priorities import (
|
||||||
@@ -73,6 +77,7 @@ DEFAULT_SETTINGS: Dict[str, Any] = {
|
|||||||
"update_flag_strategy": "same_base",
|
"update_flag_strategy": "same_base",
|
||||||
"auto_organize_exclusions": [],
|
"auto_organize_exclusions": [],
|
||||||
"metadata_refresh_skip_paths": [],
|
"metadata_refresh_skip_paths": [],
|
||||||
|
"download_skip_base_models": [],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -276,6 +281,21 @@ class SettingsManager:
|
|||||||
self.settings["metadata_refresh_skip_paths"] = []
|
self.settings["metadata_refresh_skip_paths"] = []
|
||||||
inserted_defaults = True
|
inserted_defaults = True
|
||||||
|
|
||||||
|
if "download_skip_base_models" in self.settings:
|
||||||
|
normalized_skip_base_models = self.normalize_download_skip_base_models(
|
||||||
|
self.settings.get("download_skip_base_models")
|
||||||
|
)
|
||||||
|
if normalized_skip_base_models != self.settings.get(
|
||||||
|
"download_skip_base_models"
|
||||||
|
):
|
||||||
|
self.settings["download_skip_base_models"] = (
|
||||||
|
normalized_skip_base_models
|
||||||
|
)
|
||||||
|
updated_existing = True
|
||||||
|
else:
|
||||||
|
self.settings["download_skip_base_models"] = []
|
||||||
|
inserted_defaults = True
|
||||||
|
|
||||||
had_mature_level = "mature_blur_level" in self.settings
|
had_mature_level = "mature_blur_level" in self.settings
|
||||||
raw_mature_level = self.settings.get("mature_blur_level")
|
raw_mature_level = self.settings.get("mature_blur_level")
|
||||||
normalized_mature_level = self.normalize_mature_blur_level(raw_mature_level)
|
normalized_mature_level = self.normalize_mature_blur_level(raw_mature_level)
|
||||||
@@ -964,6 +984,45 @@ class SettingsManager:
|
|||||||
self._save_settings()
|
self._save_settings()
|
||||||
return skip_paths
|
return skip_paths
|
||||||
|
|
||||||
|
def normalize_download_skip_base_models(self, value: Any) -> List[str]:
|
||||||
|
if value is None:
|
||||||
|
return []
|
||||||
|
|
||||||
|
if isinstance(value, str):
|
||||||
|
candidates: Iterable[str] = (
|
||||||
|
value.replace("\n", ",").replace(";", ",").split(",")
|
||||||
|
)
|
||||||
|
elif isinstance(value, Sequence) and not isinstance(
|
||||||
|
value, (bytes, bytearray, str)
|
||||||
|
):
|
||||||
|
candidates = value
|
||||||
|
else:
|
||||||
|
return []
|
||||||
|
|
||||||
|
base_models: List[str] = []
|
||||||
|
seen = set()
|
||||||
|
for raw in candidates:
|
||||||
|
if not isinstance(raw, str):
|
||||||
|
continue
|
||||||
|
token = raw.strip()
|
||||||
|
if not token or token not in SUPPORTED_DOWNLOAD_SKIP_BASE_MODELS:
|
||||||
|
continue
|
||||||
|
if token in seen:
|
||||||
|
continue
|
||||||
|
seen.add(token)
|
||||||
|
base_models.append(token)
|
||||||
|
|
||||||
|
return base_models
|
||||||
|
|
||||||
|
def get_download_skip_base_models(self) -> List[str]:
|
||||||
|
base_models = self.normalize_download_skip_base_models(
|
||||||
|
self.settings.get("download_skip_base_models")
|
||||||
|
)
|
||||||
|
if base_models != self.settings.get("download_skip_base_models"):
|
||||||
|
self.settings["download_skip_base_models"] = base_models
|
||||||
|
self._save_settings()
|
||||||
|
return base_models
|
||||||
|
|
||||||
def get_extra_folder_paths(self) -> Dict[str, List[str]]:
|
def get_extra_folder_paths(self) -> Dict[str, List[str]]:
|
||||||
"""Get extra folder paths for the active library.
|
"""Get extra folder paths for the active library.
|
||||||
|
|
||||||
@@ -1032,6 +1091,8 @@ class SettingsManager:
|
|||||||
value = self.normalize_auto_organize_exclusions(value)
|
value = self.normalize_auto_organize_exclusions(value)
|
||||||
elif key == "metadata_refresh_skip_paths":
|
elif key == "metadata_refresh_skip_paths":
|
||||||
value = self.normalize_metadata_refresh_skip_paths(value)
|
value = self.normalize_metadata_refresh_skip_paths(value)
|
||||||
|
elif key == "download_skip_base_models":
|
||||||
|
value = self.normalize_download_skip_base_models(value)
|
||||||
elif key == "mature_blur_level":
|
elif key == "mature_blur_level":
|
||||||
value = self.normalize_mature_blur_level(value)
|
value = self.normalize_mature_blur_level(value)
|
||||||
self.settings[key] = value
|
self.settings[key] = value
|
||||||
|
|||||||
@@ -113,3 +113,59 @@ DIFFUSION_MODEL_BASE_MODELS = frozenset(
|
|||||||
"Qwen",
|
"Qwen",
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Supported baseModel values for download exclusion settings.
|
||||||
|
# Keep this aligned with static/js/utils/constants.js, excluding the generic "Other" value.
|
||||||
|
SUPPORTED_DOWNLOAD_SKIP_BASE_MODELS = frozenset(
|
||||||
|
[
|
||||||
|
"SD 1.4",
|
||||||
|
"SD 1.5",
|
||||||
|
"SD 1.5 LCM",
|
||||||
|
"SD 1.5 Hyper",
|
||||||
|
"SD 2.0",
|
||||||
|
"SD 2.1",
|
||||||
|
"SD 3",
|
||||||
|
"SD 3.5",
|
||||||
|
"SD 3.5 Medium",
|
||||||
|
"SD 3.5 Large",
|
||||||
|
"SD 3.5 Large Turbo",
|
||||||
|
"SDXL 1.0",
|
||||||
|
"SDXL Lightning",
|
||||||
|
"SDXL Hyper",
|
||||||
|
"Flux.1 D",
|
||||||
|
"Flux.1 S",
|
||||||
|
"Flux.1 Krea",
|
||||||
|
"Flux.1 Kontext",
|
||||||
|
"Flux.2 D",
|
||||||
|
"Flux.2 Klein 9B",
|
||||||
|
"Flux.2 Klein 9B-base",
|
||||||
|
"Flux.2 Klein 4B",
|
||||||
|
"Flux.2 Klein 4B-base",
|
||||||
|
"AuraFlow",
|
||||||
|
"Chroma",
|
||||||
|
"PixArt a",
|
||||||
|
"PixArt E",
|
||||||
|
"Hunyuan 1",
|
||||||
|
"Lumina",
|
||||||
|
"Kolors",
|
||||||
|
"NoobAI",
|
||||||
|
"Illustrious",
|
||||||
|
"Pony",
|
||||||
|
"HiDream",
|
||||||
|
"Qwen",
|
||||||
|
"ZImageTurbo",
|
||||||
|
"ZImageBase",
|
||||||
|
"SVD",
|
||||||
|
"LTXV",
|
||||||
|
"LTXV2",
|
||||||
|
"Wan Video",
|
||||||
|
"Wan Video 1.3B t2v",
|
||||||
|
"Wan Video 14B t2v",
|
||||||
|
"Wan Video 14B i2v 480p",
|
||||||
|
"Wan Video 14B i2v 720p",
|
||||||
|
"Wan Video 2.2 TI2V-5B",
|
||||||
|
"Wan Video 2.2 T2V-A14B",
|
||||||
|
"Wan Video 2.2 I2V-A14B",
|
||||||
|
"Hunyuan Video",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|||||||
@@ -430,6 +430,88 @@
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.base-model-skip-toggle {
|
||||||
|
min-width: 220px;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.base-model-skip-toggle-label {
|
||||||
|
opacity: 0.75;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.base-model-skip-panel {
|
||||||
|
margin-top: var(--space-2);
|
||||||
|
padding: 12px;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: var(--border-radius-xs);
|
||||||
|
background-color: var(--lora-surface);
|
||||||
|
}
|
||||||
|
|
||||||
|
.base-model-skip-toolbar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.base-model-skip-search {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
padding: 8px 10px;
|
||||||
|
border-radius: var(--border-radius-xs);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
background-color: var(--settings-bg);
|
||||||
|
color: var(--text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.base-model-skip-search:focus {
|
||||||
|
border-color: var(--lora-accent);
|
||||||
|
outline: none;
|
||||||
|
box-shadow: 0 0 0 2px rgba(var(--lora-accent-rgb, 79, 70, 229), 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.base-model-skip-list {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
||||||
|
gap: 8px;
|
||||||
|
max-height: 220px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.base-model-skip-option {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 8px 10px;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: var(--border-radius-xs);
|
||||||
|
background-color: var(--settings-bg);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: border-color 0.15s ease, background-color 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.base-model-skip-option:hover {
|
||||||
|
border-color: var(--lora-accent);
|
||||||
|
background-color: rgba(var(--lora-accent-rgb, 79, 70, 229), 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.base-model-skip-option input {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.base-model-skip-option span {
|
||||||
|
font-size: 0.9em;
|
||||||
|
line-height: 1.25;
|
||||||
|
}
|
||||||
|
|
||||||
|
.base-model-skip-empty {
|
||||||
|
padding: 8px 0 0;
|
||||||
|
font-size: 0.9em;
|
||||||
|
opacity: 0.75;
|
||||||
|
}
|
||||||
|
|
||||||
.priority-tags-input:focus {
|
.priority-tags-input:focus {
|
||||||
border-color: var(--lora-accent);
|
border-color: var(--lora-accent);
|
||||||
outline: none;
|
outline: none;
|
||||||
|
|||||||
@@ -492,7 +492,7 @@ export class DownloadManager {
|
|||||||
console.error('WebSocket error:', error);
|
console.error('WebSocket error:', error);
|
||||||
};
|
};
|
||||||
|
|
||||||
await this.apiClient.downloadModel(
|
const response = await this.apiClient.downloadModel(
|
||||||
modelId,
|
modelId,
|
||||||
versionId,
|
versionId,
|
||||||
modelRoot,
|
modelRoot,
|
||||||
@@ -502,6 +502,16 @@ export class DownloadManager {
|
|||||||
source
|
source
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (response?.skipped) {
|
||||||
|
this.loadingManager.setStatus(translate('modals.download.status.finalizing'));
|
||||||
|
updateProgress(100, 0, displayName);
|
||||||
|
showToast('toast.loras.downloadSkippedByBaseModel', { baseModel: response.base_model || 'Unknown' }, 'warning');
|
||||||
|
if (closeModal) {
|
||||||
|
modalManager.closeModal('downloadModal');
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
showToast('toast.loras.downloadCompleted', {}, 'success');
|
showToast('toast.loras.downloadCompleted', {}, 'success');
|
||||||
|
|
||||||
if (closeModal) {
|
if (closeModal) {
|
||||||
|
|||||||
@@ -139,6 +139,10 @@ export class SettingsManager {
|
|||||||
backendSettings?.metadata_refresh_skip_paths ?? defaults.metadata_refresh_skip_paths
|
backendSettings?.metadata_refresh_skip_paths ?? defaults.metadata_refresh_skip_paths
|
||||||
);
|
);
|
||||||
|
|
||||||
|
merged.download_skip_base_models = this.normalizeDownloadSkipBaseModels(
|
||||||
|
backendSettings?.download_skip_base_models ?? defaults.download_skip_base_models
|
||||||
|
);
|
||||||
|
|
||||||
merged.mature_blur_level = this.normalizeMatureBlurLevel(
|
merged.mature_blur_level = this.normalizeMatureBlurLevel(
|
||||||
backendSettings?.mature_blur_level ?? defaults.mature_blur_level
|
backendSettings?.mature_blur_level ?? defaults.mature_blur_level
|
||||||
);
|
);
|
||||||
@@ -179,6 +183,15 @@ export class SettingsManager {
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getAvailableDownloadSkipBaseModels() {
|
||||||
|
return MAPPABLE_BASE_MODELS.filter(model => model !== 'Other');
|
||||||
|
}
|
||||||
|
|
||||||
|
normalizeDownloadSkipBaseModels(value) {
|
||||||
|
const allowed = new Set(this.getAvailableDownloadSkipBaseModels());
|
||||||
|
return this.normalizePatternList(value).filter(model => allowed.has(model));
|
||||||
|
}
|
||||||
|
|
||||||
registerStartupMessages(messages = []) {
|
registerStartupMessages(messages = []) {
|
||||||
if (!Array.isArray(messages) || messages.length === 0) {
|
if (!Array.isArray(messages) || messages.length === 0) {
|
||||||
return;
|
return;
|
||||||
@@ -379,6 +392,36 @@ export class SettingsManager {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const downloadSkipBaseModelsContainer = document.getElementById('downloadSkipBaseModelsContainer');
|
||||||
|
if (downloadSkipBaseModelsContainer) {
|
||||||
|
downloadSkipBaseModelsContainer.addEventListener('change', (event) => {
|
||||||
|
if (event.target instanceof HTMLInputElement && event.target.name === 'downloadSkipBaseModel') {
|
||||||
|
this.saveDownloadSkipBaseModels();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const downloadSkipBaseModelsToggle = document.getElementById('downloadSkipBaseModelsToggle');
|
||||||
|
if (downloadSkipBaseModelsToggle) {
|
||||||
|
downloadSkipBaseModelsToggle.addEventListener('click', () => {
|
||||||
|
this.toggleDownloadSkipBaseModelsPanel();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const downloadSkipBaseModelsSearch = document.getElementById('downloadSkipBaseModelsSearch');
|
||||||
|
if (downloadSkipBaseModelsSearch) {
|
||||||
|
downloadSkipBaseModelsSearch.addEventListener('input', () => {
|
||||||
|
this.renderDownloadSkipBaseModels();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const downloadSkipBaseModelsClear = document.getElementById('downloadSkipBaseModelsClear');
|
||||||
|
if (downloadSkipBaseModelsClear) {
|
||||||
|
downloadSkipBaseModelsClear.addEventListener('click', () => {
|
||||||
|
this.clearDownloadSkipBaseModels();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
this.setupPriorityTagInputs();
|
this.setupPriorityTagInputs();
|
||||||
this.initializeNavigation();
|
this.initializeNavigation();
|
||||||
this.initializeSearch();
|
this.initializeSearch();
|
||||||
@@ -730,6 +773,13 @@ export class SettingsManager {
|
|||||||
metadataRefreshSkipPathsError.textContent = '';
|
metadataRefreshSkipPathsError.textContent = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.renderDownloadSkipBaseModels();
|
||||||
|
const downloadSkipBaseModelsError = document.getElementById('downloadSkipBaseModelsError');
|
||||||
|
if (downloadSkipBaseModelsError) {
|
||||||
|
downloadSkipBaseModelsError.textContent = '';
|
||||||
|
}
|
||||||
|
this.setDownloadSkipBaseModelsPanelOpen(false);
|
||||||
|
|
||||||
// Set video autoplay on hover setting
|
// Set video autoplay on hover setting
|
||||||
const autoplayOnHoverCheckbox = document.getElementById('autoplayOnHover');
|
const autoplayOnHoverCheckbox = document.getElementById('autoplayOnHover');
|
||||||
if (autoplayOnHoverCheckbox) {
|
if (autoplayOnHoverCheckbox) {
|
||||||
@@ -2170,6 +2220,190 @@ export class SettingsManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderDownloadSkipBaseModels() {
|
||||||
|
const container = document.getElementById('downloadSkipBaseModelsContainer');
|
||||||
|
const searchInput = document.getElementById('downloadSkipBaseModelsSearch');
|
||||||
|
const emptyState = document.getElementById('downloadSkipBaseModelsEmpty');
|
||||||
|
if (!container) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectedValues = this.normalizeDownloadSkipBaseModels(
|
||||||
|
state.global.settings.download_skip_base_models
|
||||||
|
);
|
||||||
|
const selected = new Set(selectedValues);
|
||||||
|
const options = this.getAvailableDownloadSkipBaseModels();
|
||||||
|
const query = (searchInput?.value || '').trim().toLowerCase();
|
||||||
|
const filteredOptions = query
|
||||||
|
? options.filter((baseModel) => baseModel.toLowerCase().includes(query))
|
||||||
|
: options;
|
||||||
|
|
||||||
|
container.innerHTML = filteredOptions.map((baseModel) => `
|
||||||
|
<label class="base-model-skip-option">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
name="downloadSkipBaseModel"
|
||||||
|
value="${baseModel}"
|
||||||
|
${selected.has(baseModel) ? 'checked' : ''}
|
||||||
|
>
|
||||||
|
<span>${baseModel}</span>
|
||||||
|
</label>
|
||||||
|
`).join('');
|
||||||
|
|
||||||
|
if (emptyState) {
|
||||||
|
emptyState.hidden = filteredOptions.length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.renderDownloadSkipBaseModelsSummary(selectedValues);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderDownloadSkipBaseModelsSummary(selectedValues = null) {
|
||||||
|
const summaryElement = document.getElementById('downloadSkipBaseModelsSummary');
|
||||||
|
if (!summaryElement) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const values = Array.isArray(selectedValues)
|
||||||
|
? selectedValues
|
||||||
|
: this.normalizeDownloadSkipBaseModels(state.global.settings.download_skip_base_models);
|
||||||
|
|
||||||
|
if (values.length === 0) {
|
||||||
|
summaryElement.textContent = translate(
|
||||||
|
'settings.downloadSkipBaseModels.summary.none',
|
||||||
|
{},
|
||||||
|
'None selected'
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (values.length <= 2) {
|
||||||
|
summaryElement.textContent = values.join(', ');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
summaryElement.textContent = translate(
|
||||||
|
'settings.downloadSkipBaseModels.summary.count',
|
||||||
|
{ count: values.length },
|
||||||
|
`${values.length} selected`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
setDownloadSkipBaseModelsPanelOpen(isOpen) {
|
||||||
|
const panel = document.getElementById('downloadSkipBaseModelsPanel');
|
||||||
|
const toggle = document.getElementById('downloadSkipBaseModelsToggle');
|
||||||
|
const toggleLabel = toggle?.querySelector('.base-model-skip-toggle-label');
|
||||||
|
if (!panel || !toggle) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
panel.hidden = !isOpen;
|
||||||
|
toggle.setAttribute('aria-expanded', isOpen ? 'true' : 'false');
|
||||||
|
if (toggleLabel) {
|
||||||
|
toggleLabel.textContent = isOpen
|
||||||
|
? translate('settings.downloadSkipBaseModels.actions.collapse', {}, 'Collapse')
|
||||||
|
: translate('settings.downloadSkipBaseModels.actions.edit', {}, 'Edit');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isOpen) {
|
||||||
|
const searchInput = document.getElementById('downloadSkipBaseModelsSearch');
|
||||||
|
searchInput?.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleDownloadSkipBaseModelsPanel() {
|
||||||
|
const panel = document.getElementById('downloadSkipBaseModelsPanel');
|
||||||
|
if (!panel) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.setDownloadSkipBaseModelsPanelOpen(panel.hidden);
|
||||||
|
}
|
||||||
|
|
||||||
|
async saveDownloadSkipBaseModels() {
|
||||||
|
const container = document.getElementById('downloadSkipBaseModelsContainer');
|
||||||
|
const errorElement = document.getElementById('downloadSkipBaseModelsError');
|
||||||
|
if (!container) return;
|
||||||
|
|
||||||
|
const selected = Array.from(
|
||||||
|
container.querySelectorAll('input[name="downloadSkipBaseModel"]:checked')
|
||||||
|
).map((input) => input.value);
|
||||||
|
const normalized = this.normalizeDownloadSkipBaseModels(selected);
|
||||||
|
const current = this.normalizeDownloadSkipBaseModels(state.global.settings.download_skip_base_models);
|
||||||
|
|
||||||
|
if (normalized.join('|') === current.join('|')) {
|
||||||
|
if (errorElement) {
|
||||||
|
errorElement.textContent = '';
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (errorElement) {
|
||||||
|
errorElement.textContent = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.saveSetting('download_skip_base_models', normalized);
|
||||||
|
this.renderDownloadSkipBaseModels();
|
||||||
|
|
||||||
|
showToast(
|
||||||
|
'toast.settings.settingsUpdated',
|
||||||
|
{ setting: translate('settings.downloadSkipBaseModels.label') },
|
||||||
|
'success'
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to save download skip base models:', error);
|
||||||
|
if (errorElement) {
|
||||||
|
errorElement.textContent = translate(
|
||||||
|
'settings.downloadSkipBaseModels.validation.saveFailed',
|
||||||
|
{ message: error.message },
|
||||||
|
`Unable to save excluded base models: ${error.message}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
showToast('toast.settings.settingSaveFailed', { message: error.message }, 'error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async clearDownloadSkipBaseModels() {
|
||||||
|
const searchInput = document.getElementById('downloadSkipBaseModelsSearch');
|
||||||
|
if (searchInput) {
|
||||||
|
searchInput.value = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
const current = this.normalizeDownloadSkipBaseModels(
|
||||||
|
state.global.settings.download_skip_base_models
|
||||||
|
);
|
||||||
|
if (current.length === 0) {
|
||||||
|
this.renderDownloadSkipBaseModels();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const errorElement = document.getElementById('downloadSkipBaseModelsError');
|
||||||
|
if (errorElement) {
|
||||||
|
errorElement.textContent = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.saveSetting('download_skip_base_models', []);
|
||||||
|
this.renderDownloadSkipBaseModels();
|
||||||
|
|
||||||
|
showToast(
|
||||||
|
'toast.settings.settingsUpdated',
|
||||||
|
{ setting: translate('settings.downloadSkipBaseModels.label') },
|
||||||
|
'success'
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
const errorElement = document.getElementById('downloadSkipBaseModelsError');
|
||||||
|
console.error('Failed to clear download skip base models:', error);
|
||||||
|
if (errorElement) {
|
||||||
|
errorElement.textContent = translate(
|
||||||
|
'settings.downloadSkipBaseModels.validation.saveFailed',
|
||||||
|
{ message: error.message },
|
||||||
|
`Unable to save excluded base models: ${error.message}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
showToast('toast.settings.settingSaveFailed', { message: error.message }, 'error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async saveMetadataRefreshSkipPaths() {
|
async saveMetadataRefreshSkipPaths() {
|
||||||
const input = document.getElementById('metadataRefreshSkipPaths');
|
const input = document.getElementById('metadataRefreshSkipPaths');
|
||||||
const errorElement = document.getElementById('metadataRefreshSkipPathsError');
|
const errorElement = document.getElementById('metadataRefreshSkipPathsError');
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ const DEFAULT_SETTINGS_BASE = Object.freeze({
|
|||||||
hide_early_access_updates: false,
|
hide_early_access_updates: false,
|
||||||
auto_organize_exclusions: [],
|
auto_organize_exclusions: [],
|
||||||
metadata_refresh_skip_paths: [],
|
metadata_refresh_skip_paths: [],
|
||||||
|
download_skip_base_models: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
export function createDefaultSettings() {
|
export function createDefaultSettings() {
|
||||||
|
|||||||
@@ -743,6 +743,46 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="setting-item">
|
||||||
|
<div class="setting-row">
|
||||||
|
<div class="setting-info">
|
||||||
|
<label for="downloadSkipBaseModelsToggle">
|
||||||
|
{{ t('settings.downloadSkipBaseModels.label') }}
|
||||||
|
<i class="fas fa-info-circle info-icon" data-tooltip="{{ t('settings.downloadSkipBaseModels.help') }}"></i>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="setting-control">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
id="downloadSkipBaseModelsToggle"
|
||||||
|
class="secondary-btn base-model-skip-toggle"
|
||||||
|
aria-expanded="false"
|
||||||
|
>
|
||||||
|
<span id="downloadSkipBaseModelsSummary">{{ t('settings.downloadSkipBaseModels.summary.none') }}</span>
|
||||||
|
<span class="base-model-skip-toggle-label">{{ t('settings.downloadSkipBaseModels.actions.edit') }}</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="downloadSkipBaseModelsPanel" class="base-model-skip-panel" hidden>
|
||||||
|
<div class="base-model-skip-toolbar">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="downloadSkipBaseModelsSearch"
|
||||||
|
class="base-model-skip-search"
|
||||||
|
placeholder="{{ t('settings.downloadSkipBaseModels.searchPlaceholder') }}"
|
||||||
|
/>
|
||||||
|
<button type="button" class="text-btn base-model-skip-clear" id="downloadSkipBaseModelsClear">
|
||||||
|
{{ t('settings.downloadSkipBaseModels.actions.clear') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div id="downloadSkipBaseModelsContainer" class="base-model-skip-list"></div>
|
||||||
|
<div id="downloadSkipBaseModelsEmpty" class="base-model-skip-empty" hidden>
|
||||||
|
{{ t('settings.downloadSkipBaseModels.empty') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="settings-input-error-message" id="downloadSkipBaseModelsError"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Priority Tags -->
|
<!-- Priority Tags -->
|
||||||
<div class="setting-item priority-tags-item">
|
<div class="setting-item priority-tags-item">
|
||||||
<div class="setting-row priority-tags-header-row">
|
<div class="setting-row priority-tags-header-row">
|
||||||
|
|||||||
@@ -0,0 +1,152 @@
|
|||||||
|
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
||||||
|
|
||||||
|
vi.mock('../../../static/js/managers/ModalManager.js', () => ({
|
||||||
|
modalManager: {
|
||||||
|
closeModal: vi.fn(),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock('../../../static/js/utils/uiHelpers.js', () => ({
|
||||||
|
showToast: vi.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock('../../../static/js/state/index.js', () => {
|
||||||
|
const settings = {};
|
||||||
|
return {
|
||||||
|
state: {
|
||||||
|
global: {
|
||||||
|
settings,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
createDefaultSettings: () => ({
|
||||||
|
language: 'en',
|
||||||
|
download_skip_base_models: [],
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
vi.mock('../../../static/js/api/modelApiFactory.js', () => ({
|
||||||
|
resetAndReload: vi.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock('../../../static/js/utils/constants.js', () => ({
|
||||||
|
DOWNLOAD_PATH_TEMPLATES: {},
|
||||||
|
DEFAULT_PATH_TEMPLATES: {},
|
||||||
|
MAPPABLE_BASE_MODELS: ['Flux.1 D', 'Pony', 'SDXL 1.0', 'Other'],
|
||||||
|
PATH_TEMPLATE_PLACEHOLDERS: {},
|
||||||
|
DEFAULT_PRIORITY_TAG_CONFIG: {
|
||||||
|
lora: 'character, style',
|
||||||
|
checkpoint: 'base, guide',
|
||||||
|
embedding: 'hint',
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock('../../../static/js/utils/i18nHelpers.js', () => ({
|
||||||
|
translate: (key, params, fallback) => {
|
||||||
|
if (key === 'settings.downloadSkipBaseModels.summary.none') {
|
||||||
|
return 'None selected';
|
||||||
|
}
|
||||||
|
if (key === 'settings.downloadSkipBaseModels.summary.count') {
|
||||||
|
return `${params?.count ?? 0} selected`;
|
||||||
|
}
|
||||||
|
return fallback ?? '';
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock('../../../static/js/i18n/index.js', () => ({
|
||||||
|
i18n: {
|
||||||
|
getCurrentLocale: () => 'en',
|
||||||
|
setLanguage: vi.fn().mockResolvedValue(),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock('../../../static/js/components/shared/ModelCard.js', () => ({
|
||||||
|
configureModelCardVideo: vi.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock('../../../static/js/managers/BannerService.js', () => ({
|
||||||
|
bannerService: {
|
||||||
|
registerBanner: vi.fn(),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock('../../../static/js/components/SidebarManager.js', () => ({
|
||||||
|
sidebarManager: {
|
||||||
|
setSidebarEnabled: vi.fn().mockResolvedValue(),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
import { SettingsManager } from '../../../static/js/managers/SettingsManager.js';
|
||||||
|
import { state } from '../../../static/js/state/index.js';
|
||||||
|
|
||||||
|
const createManager = () => {
|
||||||
|
const initSettingsSpy = vi
|
||||||
|
.spyOn(SettingsManager.prototype, 'initializeSettings')
|
||||||
|
.mockResolvedValue();
|
||||||
|
const initializeSpy = vi
|
||||||
|
.spyOn(SettingsManager.prototype, 'initialize')
|
||||||
|
.mockImplementation(() => {});
|
||||||
|
|
||||||
|
const manager = new SettingsManager();
|
||||||
|
|
||||||
|
initSettingsSpy.mockRestore();
|
||||||
|
initializeSpy.mockRestore();
|
||||||
|
|
||||||
|
return manager;
|
||||||
|
};
|
||||||
|
|
||||||
|
const appendDownloadSkipUi = () => {
|
||||||
|
document.body.innerHTML = `
|
||||||
|
<button id="downloadSkipBaseModelsToggle" aria-expanded="false">
|
||||||
|
<span id="downloadSkipBaseModelsSummary"></span>
|
||||||
|
<span class="base-model-skip-toggle-label"></span>
|
||||||
|
</button>
|
||||||
|
<div id="downloadSkipBaseModelsPanel" hidden>
|
||||||
|
<input id="downloadSkipBaseModelsSearch" />
|
||||||
|
<button id="downloadSkipBaseModelsClear" type="button">Clear</button>
|
||||||
|
<div id="downloadSkipBaseModelsContainer"></div>
|
||||||
|
<div id="downloadSkipBaseModelsEmpty" hidden></div>
|
||||||
|
</div>
|
||||||
|
<div id="downloadSkipBaseModelsError"></div>
|
||||||
|
`;
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('SettingsManager download skip base models UI', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
document.body.innerHTML = '';
|
||||||
|
vi.clearAllMocks();
|
||||||
|
state.global.settings = {
|
||||||
|
download_skip_base_models: [],
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders a compact summary for selected base models', () => {
|
||||||
|
appendDownloadSkipUi();
|
||||||
|
state.global.settings.download_skip_base_models = ['Flux.1 D', 'Pony'];
|
||||||
|
const manager = createManager();
|
||||||
|
|
||||||
|
manager.renderDownloadSkipBaseModels();
|
||||||
|
|
||||||
|
expect(document.getElementById('downloadSkipBaseModelsSummary').textContent).toBe('Flux.1 D, Pony');
|
||||||
|
expect(document.querySelectorAll('#downloadSkipBaseModelsContainer input')).toHaveLength(3);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('filters the list using the search input and shows an empty state', () => {
|
||||||
|
appendDownloadSkipUi();
|
||||||
|
state.global.settings.download_skip_base_models = ['Flux.1 D'];
|
||||||
|
const manager = createManager();
|
||||||
|
const searchInput = document.getElementById('downloadSkipBaseModelsSearch');
|
||||||
|
|
||||||
|
searchInput.value = 'pony';
|
||||||
|
manager.renderDownloadSkipBaseModels();
|
||||||
|
|
||||||
|
expect(document.querySelectorAll('#downloadSkipBaseModelsContainer input')).toHaveLength(1);
|
||||||
|
expect(document.querySelector('#downloadSkipBaseModelsContainer input').value).toBe('Pony');
|
||||||
|
|
||||||
|
searchInput.value = 'zzz';
|
||||||
|
manager.renderDownloadSkipBaseModels();
|
||||||
|
|
||||||
|
expect(document.querySelectorAll('#downloadSkipBaseModelsContainer input')).toHaveLength(0);
|
||||||
|
expect(document.getElementById('downloadSkipBaseModelsEmpty').hidden).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -719,3 +719,42 @@ def test_auto_organize_conflict_when_running(mock_service):
|
|||||||
await client.close()
|
await client.close()
|
||||||
|
|
||||||
asyncio.run(scenario())
|
asyncio.run(scenario())
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def test_download_model_returns_skipped_success(mock_service, download_manager_stub):
|
||||||
|
async def scenario():
|
||||||
|
download_manager_stub.last_progress_snapshot = None
|
||||||
|
|
||||||
|
async def fake_download(**kwargs):
|
||||||
|
download_manager_stub.calls.append(kwargs)
|
||||||
|
return {
|
||||||
|
"success": True,
|
||||||
|
"skipped": True,
|
||||||
|
"status": "skipped",
|
||||||
|
"reason": "base_model_excluded",
|
||||||
|
"message": "Skipped by settings",
|
||||||
|
"base_model": "SDXL 1.0",
|
||||||
|
"file_name": "demo.safetensors",
|
||||||
|
}
|
||||||
|
|
||||||
|
download_manager_stub.download_from_civitai = fake_download
|
||||||
|
|
||||||
|
client = await create_test_client(mock_service)
|
||||||
|
try:
|
||||||
|
response = await client.post(
|
||||||
|
"/api/lm/download-model",
|
||||||
|
json={"model_version_id": 123},
|
||||||
|
)
|
||||||
|
payload = await response.json()
|
||||||
|
|
||||||
|
assert response.status == 200
|
||||||
|
assert payload["success"] is True
|
||||||
|
assert payload["skipped"] is True
|
||||||
|
assert payload["reason"] == "base_model_excluded"
|
||||||
|
assert payload["base_model"] == "SDXL 1.0"
|
||||||
|
assert payload["file_name"] == "demo.safetensors"
|
||||||
|
finally:
|
||||||
|
await client.close()
|
||||||
|
|
||||||
|
asyncio.run(scenario())
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ def isolate_settings(monkeypatch, tmp_path):
|
|||||||
"embedding": "{base_model}/{first_tag}",
|
"embedding": "{base_model}/{first_tag}",
|
||||||
},
|
},
|
||||||
"base_model_path_mappings": {"BaseModel": "MappedModel"},
|
"base_model_path_mappings": {"BaseModel": "MappedModel"},
|
||||||
|
"download_skip_base_models": [],
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
monkeypatch.setattr(manager, "settings", default_settings)
|
monkeypatch.setattr(manager, "settings", default_settings)
|
||||||
@@ -443,3 +444,49 @@ def test_distribute_preview_to_entries_keeps_existing_file(tmp_path):
|
|||||||
|
|
||||||
assert targets[0] == str(existing_preview)
|
assert targets[0] == str(existing_preview)
|
||||||
assert Path(targets[1]).read_bytes() == b"preview"
|
assert Path(targets[1]).read_bytes() == b"preview"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_download_skips_excluded_base_model(monkeypatch, scanners, metadata_provider):
|
||||||
|
manager = DownloadManager()
|
||||||
|
get_settings_manager().settings["download_skip_base_models"] = ["SDXL 1.0"]
|
||||||
|
|
||||||
|
metadata_provider.get_model_version = AsyncMock(
|
||||||
|
return_value={
|
||||||
|
"id": 42,
|
||||||
|
"model": {"type": "LoRA", "tags": ["fantasy"]},
|
||||||
|
"baseModel": "SDXL 1.0",
|
||||||
|
"creator": {"username": "Author"},
|
||||||
|
"files": [
|
||||||
|
{
|
||||||
|
"type": "Model",
|
||||||
|
"primary": True,
|
||||||
|
"downloadUrl": "https://example.invalid/file.safetensors",
|
||||||
|
"name": "file.safetensors",
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
execute_download = AsyncMock()
|
||||||
|
monkeypatch.setattr(
|
||||||
|
DownloadManager, "_execute_download", execute_download, raising=False
|
||||||
|
)
|
||||||
|
|
||||||
|
result = await manager.download_from_civitai(
|
||||||
|
model_version_id=99,
|
||||||
|
use_default_paths=True,
|
||||||
|
progress_callback=None,
|
||||||
|
source=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["success"] is True
|
||||||
|
assert result["skipped"] is True
|
||||||
|
assert result["status"] == "skipped"
|
||||||
|
assert result["reason"] == "base_model_excluded"
|
||||||
|
assert result["base_model"] == "SDXL 1.0"
|
||||||
|
assert result["file_name"] == "file.safetensors"
|
||||||
|
assert "file.safetensors" in result["message"]
|
||||||
|
execute_download.assert_not_called()
|
||||||
|
assert manager._active_downloads[result["download_id"]]["status"] == "skipped"
|
||||||
|
|||||||
@@ -605,3 +605,28 @@ def test_delete_library_switches_active(manager, tmp_path):
|
|||||||
manager.delete_library("other")
|
manager.delete_library("other")
|
||||||
|
|
||||||
assert manager.get_active_library_name() == "default"
|
assert manager.get_active_library_name() == "default"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def test_download_skip_base_models_are_normalized(manager):
|
||||||
|
manager.settings["download_skip_base_models"] = [
|
||||||
|
"SDXL 1.0",
|
||||||
|
"Invalid",
|
||||||
|
"SDXL 1.0",
|
||||||
|
"Pony",
|
||||||
|
"Other",
|
||||||
|
]
|
||||||
|
|
||||||
|
result = manager.get_download_skip_base_models()
|
||||||
|
|
||||||
|
assert result == ["SDXL 1.0", "Pony"]
|
||||||
|
assert manager.settings["download_skip_base_models"] == ["SDXL 1.0", "Pony"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_setting_download_skip_base_models_normalizes_string_input(manager):
|
||||||
|
manager.set(
|
||||||
|
"download_skip_base_models",
|
||||||
|
"SDXL 1.0, Pony; Invalid\nSDXL 1.0"
|
||||||
|
)
|
||||||
|
|
||||||
|
assert manager.get("download_skip_base_models") == ["SDXL 1.0", "Pony"]
|
||||||
|
|||||||
Reference in New Issue
Block a user