mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-05-07 00:46:44 -03:00
Compare commits
2 Commits
ceeab0c998
...
a5191414cc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a5191414cc | ||
|
|
5b065b47d4 |
@@ -292,13 +292,13 @@
|
||||
"blurNsfwContentHelp": "Nicht jugendfreie (NSFW) Vorschaubilder unscharf stellen",
|
||||
"showOnlySfw": "Nur SFW-Ergebnisse anzeigen",
|
||||
"showOnlySfwHelp": "Alle NSFW-Inhalte beim Durchsuchen und Suchen herausfiltern",
|
||||
"matureBlurThreshold": "[TODO: Translate] Mature Blur Threshold",
|
||||
"matureBlurThresholdHelp": "[TODO: Translate] Set which rating level starts blur filtering when NSFW blur is enabled.",
|
||||
"matureBlurThreshold": "Schwelle für Unschärfe bei jugendgefährdenden Inhalten",
|
||||
"matureBlurThresholdHelp": "Legen Sie fest, ab welcher Altersfreigabe die Unschärfe beginnt, wenn NSFW-Unschärfe aktiviert ist.",
|
||||
"matureBlurThresholdOptions": {
|
||||
"pg13": "[TODO: Translate] PG13 and above",
|
||||
"r": "[TODO: Translate] R and above (default)",
|
||||
"x": "[TODO: Translate] X and above",
|
||||
"xxx": "[TODO: Translate] XXX only"
|
||||
"pg13": "PG13 und höher",
|
||||
"r": "R und höher (Standard)",
|
||||
"x": "X und höher",
|
||||
"xxx": "Nur XXX"
|
||||
}
|
||||
},
|
||||
"videoSettings": {
|
||||
@@ -323,6 +323,24 @@
|
||||
"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": {
|
||||
"displayDensity": "Anzeige-Dichte",
|
||||
"displayDensityOptions": {
|
||||
@@ -1467,6 +1485,7 @@
|
||||
"pleaseSelectVersion": "Bitte wählen Sie eine Version aus",
|
||||
"versionExists": "Diese Version existiert bereits in Ihrer Bibliothek",
|
||||
"downloadCompleted": "Download erfolgreich abgeschlossen",
|
||||
"downloadSkippedByBaseModel": "Download übersprungen, weil das Basismodell {baseModel} ausgeschlossen ist",
|
||||
"autoOrganizeSuccess": "Automatische Organisation für {count} {type} erfolgreich abgeschlossen",
|
||||
"autoOrganizePartialSuccess": "Automatische Organisation abgeschlossen: {success} verschoben, {failures} fehlgeschlagen von insgesamt {total} Modellen",
|
||||
"autoOrganizeFailed": "Automatische Organisation fehlgeschlagen: {error}",
|
||||
|
||||
@@ -323,6 +323,24 @@
|
||||
"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": {
|
||||
"displayDensity": "Display Density",
|
||||
"displayDensityOptions": {
|
||||
@@ -1467,6 +1485,7 @@
|
||||
"pleaseSelectVersion": "Please select a version",
|
||||
"versionExists": "This version already exists in your library",
|
||||
"downloadCompleted": "Download completed successfully",
|
||||
"downloadSkippedByBaseModel": "Skipped download because base model {baseModel} is excluded",
|
||||
"autoOrganizeSuccess": "Auto-organize completed successfully for {count} {type}",
|
||||
"autoOrganizePartialSuccess": "Auto-organize completed with {success} moved, {failures} failed out of {total} models",
|
||||
"autoOrganizeFailed": "Auto-organize failed: {error}",
|
||||
|
||||
@@ -292,13 +292,13 @@
|
||||
"blurNsfwContentHelp": "Difuminar imágenes de vista previa de contenido para adultos (NSFW)",
|
||||
"showOnlySfw": "Mostrar solo resultados SFW",
|
||||
"showOnlySfwHelp": "Filtrar todo el contenido NSFW al navegar y buscar",
|
||||
"matureBlurThreshold": "[TODO: Translate] Mature Blur Threshold",
|
||||
"matureBlurThresholdHelp": "[TODO: Translate] Set which rating level starts blur filtering when NSFW blur is enabled.",
|
||||
"matureBlurThreshold": "Umbral de difuminado para contenido adulto",
|
||||
"matureBlurThresholdHelp": "Establecer a partir de qué nivel de clasificación comienza el filtrado por difuminado cuando el difuminado NSFW está habilitado.",
|
||||
"matureBlurThresholdOptions": {
|
||||
"pg13": "[TODO: Translate] PG13 and above",
|
||||
"r": "[TODO: Translate] R and above (default)",
|
||||
"x": "[TODO: Translate] X and above",
|
||||
"xxx": "[TODO: Translate] XXX only"
|
||||
"pg13": "PG13 y superior",
|
||||
"r": "R y superior (predeterminado)",
|
||||
"x": "X y superior",
|
||||
"xxx": "Solo XXX"
|
||||
}
|
||||
},
|
||||
"videoSettings": {
|
||||
@@ -323,6 +323,24 @@
|
||||
"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": {
|
||||
"displayDensity": "Densidad de visualización",
|
||||
"displayDensityOptions": {
|
||||
@@ -1467,6 +1485,7 @@
|
||||
"pleaseSelectVersion": "Por favor selecciona una versión",
|
||||
"versionExists": "Esta versión ya existe en tu biblioteca",
|
||||
"downloadCompleted": "Descarga completada exitosamente",
|
||||
"downloadSkippedByBaseModel": "Descarga omitida porque el modelo base {baseModel} está excluido",
|
||||
"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",
|
||||
"autoOrganizeFailed": "Auto-organización fallida: {error}",
|
||||
|
||||
@@ -292,13 +292,13 @@
|
||||
"blurNsfwContentHelp": "Flouter les images d'aperçu de contenu pour adultes (NSFW)",
|
||||
"showOnlySfw": "Afficher uniquement les résultats SFW",
|
||||
"showOnlySfwHelp": "Filtrer tout le contenu NSFW lors de la navigation et de la recherche",
|
||||
"matureBlurThreshold": "[TODO: Translate] Mature Blur Threshold",
|
||||
"matureBlurThresholdHelp": "[TODO: Translate] Set which rating level starts blur filtering when NSFW blur is enabled.",
|
||||
"matureBlurThreshold": "Seuil de floutage pour contenu adulte",
|
||||
"matureBlurThresholdHelp": "Définir à partir de quel niveau de classification le floutage s'applique lorsque le floutage NSFW est activé.",
|
||||
"matureBlurThresholdOptions": {
|
||||
"pg13": "[TODO: Translate] PG13 and above",
|
||||
"r": "[TODO: Translate] R and above (default)",
|
||||
"x": "[TODO: Translate] X and above",
|
||||
"xxx": "[TODO: Translate] XXX only"
|
||||
"pg13": "PG13 et plus",
|
||||
"r": "R et plus (par défaut)",
|
||||
"x": "X et plus",
|
||||
"xxx": "XXX uniquement"
|
||||
}
|
||||
},
|
||||
"videoSettings": {
|
||||
@@ -323,6 +323,24 @@
|
||||
"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": {
|
||||
"displayDensity": "Densité d'affichage",
|
||||
"displayDensityOptions": {
|
||||
@@ -1467,6 +1485,7 @@
|
||||
"pleaseSelectVersion": "Veuillez sélectionner une version",
|
||||
"versionExists": "Cette version existe déjà dans votre bibliothèque",
|
||||
"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}",
|
||||
"autoOrganizePartialSuccess": "Auto-organisation terminée avec {success} déplacés, {failures} échecs sur {total} modèles",
|
||||
"autoOrganizeFailed": "Échec de l'auto-organisation : {error}",
|
||||
|
||||
@@ -292,13 +292,13 @@
|
||||
"blurNsfwContentHelp": "טשטש תמונות תצוגה מקדימה של תוכן למבוגרים (NSFW)",
|
||||
"showOnlySfw": "הצג רק תוצאות SFW",
|
||||
"showOnlySfwHelp": "סנן את כל התוכן ה-NSFW בעת גלישה וחיפוש",
|
||||
"matureBlurThreshold": "[TODO: Translate] Mature Blur Threshold",
|
||||
"matureBlurThresholdHelp": "[TODO: Translate] Set which rating level starts blur filtering when NSFW blur is enabled.",
|
||||
"matureBlurThreshold": "סף טשטוש תוכן מבוגרים",
|
||||
"matureBlurThresholdHelp": "הגדר מאיזו רמת דירוג מתחיל סינון הטשטוש כאשר טשטוש NSFW מופעל.",
|
||||
"matureBlurThresholdOptions": {
|
||||
"pg13": "[TODO: Translate] PG13 and above",
|
||||
"r": "[TODO: Translate] R and above (default)",
|
||||
"x": "[TODO: Translate] X and above",
|
||||
"xxx": "[TODO: Translate] XXX only"
|
||||
"pg13": "PG13 ומעלה",
|
||||
"r": "R ומעלה (ברירת מחדל)",
|
||||
"x": "X ומעלה",
|
||||
"xxx": "XXX בלבד"
|
||||
}
|
||||
},
|
||||
"videoSettings": {
|
||||
@@ -323,6 +323,24 @@
|
||||
"saveFailed": "לא ניתן לשמור נתיבי דילוג: {message}"
|
||||
}
|
||||
},
|
||||
"downloadSkipBaseModels": {
|
||||
"label": "דלג על הורדות עבור מודלי בסיס",
|
||||
"help": "חל על כל תהליכי ההורדה. ניתן לבחור כאן רק מודלי בסיס נתמכים.",
|
||||
"searchPlaceholder": "סנן מודלי בסיס...",
|
||||
"empty": "אין מודלי בסיס התואמים לחיפוש הנוכחי.",
|
||||
"summary": {
|
||||
"none": "לא נבחר דבר",
|
||||
"count": "{count} נבחרו"
|
||||
},
|
||||
"actions": {
|
||||
"edit": "עריכה",
|
||||
"collapse": "כווץ",
|
||||
"clear": "נקה"
|
||||
},
|
||||
"validation": {
|
||||
"saveFailed": "לא ניתן לשמור את מודלי הבסיס המוחרגים: {message}"
|
||||
}
|
||||
},
|
||||
"layoutSettings": {
|
||||
"displayDensity": "צפיפות תצוגה",
|
||||
"displayDensityOptions": {
|
||||
@@ -1467,6 +1485,7 @@
|
||||
"pleaseSelectVersion": "אנא בחר גרסה",
|
||||
"versionExists": "גרסה זו כבר קיימת בספרייה שלך",
|
||||
"downloadCompleted": "ההורדה הושלמה בהצלחה",
|
||||
"downloadSkippedByBaseModel": "ההורדה דולגה כי מודל הבסיס {baseModel} מוחרג",
|
||||
"autoOrganizeSuccess": "הארגון האוטומטי הושלם בהצלחה עבור {count} {type}",
|
||||
"autoOrganizePartialSuccess": "הארגון האוטומטי הושלם עם {success} שהועברו, {failures} שנכשלו מתוך {total} מודלים",
|
||||
"autoOrganizeFailed": "הארגון האוטומטי נכשל: {error}",
|
||||
|
||||
@@ -292,13 +292,13 @@
|
||||
"blurNsfwContentHelp": "成人向け(NSFW)コンテンツのプレビュー画像をぼかします",
|
||||
"showOnlySfw": "SFWコンテンツのみ表示",
|
||||
"showOnlySfwHelp": "閲覧と検索時にすべてのNSFWコンテンツを除外します",
|
||||
"matureBlurThreshold": "[TODO: Translate] Mature Blur Threshold",
|
||||
"matureBlurThresholdHelp": "[TODO: Translate] Set which rating level starts blur filtering when NSFW blur is enabled.",
|
||||
"matureBlurThreshold": "成人コンテンツぼかし閾値",
|
||||
"matureBlurThresholdHelp": "NSFWぼかしが有効な場合、どのレーティングレベルからぼかしフィルタリングを開始するかを設定します。",
|
||||
"matureBlurThresholdOptions": {
|
||||
"pg13": "[TODO: Translate] PG13 and above",
|
||||
"r": "[TODO: Translate] R and above (default)",
|
||||
"x": "[TODO: Translate] X and above",
|
||||
"xxx": "[TODO: Translate] XXX only"
|
||||
"pg13": "PG13 以上",
|
||||
"r": "R 以上(デフォルト)",
|
||||
"x": "X 以上",
|
||||
"xxx": "XXX のみ"
|
||||
}
|
||||
},
|
||||
"videoSettings": {
|
||||
@@ -323,6 +323,24 @@
|
||||
"saveFailed": "スキップパスの保存に失敗しました:{message}"
|
||||
}
|
||||
},
|
||||
"downloadSkipBaseModels": {
|
||||
"label": "ベースモデルのダウンロードをスキップ",
|
||||
"help": "すべてのダウンロードフローに適用されます。ここでは対応しているベースモデルのみ選択できます。",
|
||||
"searchPlaceholder": "ベースモデルを絞り込む...",
|
||||
"empty": "現在の検索に一致するベースモデルはありません。",
|
||||
"summary": {
|
||||
"none": "未選択",
|
||||
"count": "{count} 件を選択"
|
||||
},
|
||||
"actions": {
|
||||
"edit": "編集",
|
||||
"collapse": "折りたたむ",
|
||||
"clear": "クリア"
|
||||
},
|
||||
"validation": {
|
||||
"saveFailed": "除外するベースモデルを保存できませんでした: {message}"
|
||||
}
|
||||
},
|
||||
"layoutSettings": {
|
||||
"displayDensity": "表示密度",
|
||||
"displayDensityOptions": {
|
||||
@@ -1467,6 +1485,7 @@
|
||||
"pleaseSelectVersion": "バージョンを選択してください",
|
||||
"versionExists": "このバージョンは既にライブラリに存在します",
|
||||
"downloadCompleted": "ダウンロードが正常に完了しました",
|
||||
"downloadSkippedByBaseModel": "ベースモデル {baseModel} が除外されているため、ダウンロードをスキップしました",
|
||||
"autoOrganizeSuccess": "{count} {type} の自動整理が正常に完了しました",
|
||||
"autoOrganizePartialSuccess": "自動整理が完了しました:{total} モデル中 {success} 移動、{failures} 失敗",
|
||||
"autoOrganizeFailed": "自動整理に失敗しました:{error}",
|
||||
|
||||
@@ -292,13 +292,13 @@
|
||||
"blurNsfwContentHelp": "성인(NSFW) 콘텐츠 미리보기 이미지를 블러 처리합니다",
|
||||
"showOnlySfw": "SFW 결과만 표시",
|
||||
"showOnlySfwHelp": "탐색 및 검색 시 모든 NSFW 콘텐츠를 필터링합니다",
|
||||
"matureBlurThreshold": "[TODO: Translate] Mature Blur Threshold",
|
||||
"matureBlurThresholdHelp": "[TODO: Translate] Set which rating level starts blur filtering when NSFW blur is enabled.",
|
||||
"matureBlurThreshold": "성인 콘텐츠 블러 임계값",
|
||||
"matureBlurThresholdHelp": "NSFW 블러가 활성화될 때 어떤 등급 레벨부터 블러 필터링을 시작할지 설정합니다.",
|
||||
"matureBlurThresholdOptions": {
|
||||
"pg13": "[TODO: Translate] PG13 and above",
|
||||
"r": "[TODO: Translate] R and above (default)",
|
||||
"x": "[TODO: Translate] X and above",
|
||||
"xxx": "[TODO: Translate] XXX only"
|
||||
"pg13": "PG13 이상",
|
||||
"r": "R 이상(기본값)",
|
||||
"x": "X 이상",
|
||||
"xxx": "XXX만"
|
||||
}
|
||||
},
|
||||
"videoSettings": {
|
||||
@@ -323,6 +323,24 @@
|
||||
"saveFailed": "건너뛰기 경로를 저장할 수 없습니다: {message}"
|
||||
}
|
||||
},
|
||||
"downloadSkipBaseModels": {
|
||||
"label": "기본 모델 다운로드 건너뛰기",
|
||||
"help": "모든 다운로드 흐름에 적용됩니다. 여기서는 지원되는 기본 모델만 선택할 수 있습니다.",
|
||||
"searchPlaceholder": "기본 모델 필터링...",
|
||||
"empty": "현재 검색과 일치하는 기본 모델이 없습니다.",
|
||||
"summary": {
|
||||
"none": "선택 없음",
|
||||
"count": "{count}개 선택됨"
|
||||
},
|
||||
"actions": {
|
||||
"edit": "편집",
|
||||
"collapse": "접기",
|
||||
"clear": "지우기"
|
||||
},
|
||||
"validation": {
|
||||
"saveFailed": "제외된 기본 모델을 저장할 수 없습니다: {message}"
|
||||
}
|
||||
},
|
||||
"layoutSettings": {
|
||||
"displayDensity": "표시 밀도",
|
||||
"displayDensityOptions": {
|
||||
@@ -1467,6 +1485,7 @@
|
||||
"pleaseSelectVersion": "버전을 선택해주세요",
|
||||
"versionExists": "이 버전은 이미 라이브러리에 있습니다",
|
||||
"downloadCompleted": "다운로드가 성공적으로 완료되었습니다",
|
||||
"downloadSkippedByBaseModel": "기본 모델 {baseModel}이(가) 제외되어 다운로드를 건너뛰었습니다",
|
||||
"autoOrganizeSuccess": "{count}개의 {type}에 대해 자동 정리가 성공적으로 완료되었습니다",
|
||||
"autoOrganizePartialSuccess": "자동 정리 완료: 전체 {total}개 중 {success}개 이동, {failures}개 실패",
|
||||
"autoOrganizeFailed": "자동 정리 실패: {error}",
|
||||
|
||||
@@ -292,13 +292,13 @@
|
||||
"blurNsfwContentHelp": "Размывать превью изображений контента для взрослых (NSFW)",
|
||||
"showOnlySfw": "Показывать только SFW результаты",
|
||||
"showOnlySfwHelp": "Фильтровать весь NSFW контент при просмотре и поиске",
|
||||
"matureBlurThreshold": "[TODO: Translate] Mature Blur Threshold",
|
||||
"matureBlurThresholdHelp": "[TODO: Translate] Set which rating level starts blur filtering when NSFW blur is enabled.",
|
||||
"matureBlurThreshold": "Порог размытия взрослого контента",
|
||||
"matureBlurThresholdHelp": "Установить, с какого уровня рейтинга начинается размытие при включенном размытии NSFW.",
|
||||
"matureBlurThresholdOptions": {
|
||||
"pg13": "[TODO: Translate] PG13 and above",
|
||||
"r": "[TODO: Translate] R and above (default)",
|
||||
"x": "[TODO: Translate] X and above",
|
||||
"xxx": "[TODO: Translate] XXX only"
|
||||
"pg13": "PG13 и выше",
|
||||
"r": "R и выше (по умолчанию)",
|
||||
"x": "X и выше",
|
||||
"xxx": "Только XXX"
|
||||
}
|
||||
},
|
||||
"videoSettings": {
|
||||
@@ -323,6 +323,24 @@
|
||||
"saveFailed": "Не удалось сохранить пути для пропуска: {message}"
|
||||
}
|
||||
},
|
||||
"downloadSkipBaseModels": {
|
||||
"label": "Пропускать загрузки для базовых моделей",
|
||||
"help": "Применяется ко всем сценариям загрузки. Здесь можно выбрать только поддерживаемые базовые модели.",
|
||||
"searchPlaceholder": "Фильтровать базовые модели...",
|
||||
"empty": "Нет базовых моделей, соответствующих текущему поиску.",
|
||||
"summary": {
|
||||
"none": "Ничего не выбрано",
|
||||
"count": "Выбрано: {count}"
|
||||
},
|
||||
"actions": {
|
||||
"edit": "Изменить",
|
||||
"collapse": "Свернуть",
|
||||
"clear": "Очистить"
|
||||
},
|
||||
"validation": {
|
||||
"saveFailed": "Не удалось сохранить исключённые базовые модели: {message}"
|
||||
}
|
||||
},
|
||||
"layoutSettings": {
|
||||
"displayDensity": "Плотность отображения",
|
||||
"displayDensityOptions": {
|
||||
@@ -1467,6 +1485,7 @@
|
||||
"pleaseSelectVersion": "Пожалуйста, выберите версию",
|
||||
"versionExists": "Эта версия уже существует в вашей библиотеке",
|
||||
"downloadCompleted": "Загрузка успешно завершена",
|
||||
"downloadSkippedByBaseModel": "Загрузка пропущена, потому что базовая модель {baseModel} исключена",
|
||||
"autoOrganizeSuccess": "Автоматическая организация успешно завершена для {count} {type}",
|
||||
"autoOrganizePartialSuccess": "Автоматическая организация завершена: перемещено {success}, не удалось {failures} из {total} моделей",
|
||||
"autoOrganizeFailed": "Ошибка автоматической организации: {error}",
|
||||
|
||||
@@ -292,13 +292,13 @@
|
||||
"blurNsfwContentHelp": "模糊成熟(NSFW)内容预览图片",
|
||||
"showOnlySfw": "仅显示 SFW 结果",
|
||||
"showOnlySfwHelp": "浏览和搜索时过滤所有 NSFW 内容",
|
||||
"matureBlurThreshold": "[TODO: Translate] Mature Blur Threshold",
|
||||
"matureBlurThresholdHelp": "[TODO: Translate] Set which rating level starts blur filtering when NSFW blur is enabled.",
|
||||
"matureBlurThreshold": "成人内容模糊阈值",
|
||||
"matureBlurThresholdHelp": "设置当启用 NSFW 模糊时,从哪个评级级别开始模糊过滤。",
|
||||
"matureBlurThresholdOptions": {
|
||||
"pg13": "[TODO: Translate] PG13 and above",
|
||||
"r": "[TODO: Translate] R and above (default)",
|
||||
"x": "[TODO: Translate] X and above",
|
||||
"xxx": "[TODO: Translate] XXX only"
|
||||
"pg13": "PG13 及以上",
|
||||
"r": "R 及以上(默认)",
|
||||
"x": "X 及以上",
|
||||
"xxx": "仅 XXX"
|
||||
}
|
||||
},
|
||||
"videoSettings": {
|
||||
@@ -323,6 +323,24 @@
|
||||
"saveFailed": "无法保存跳过路径:{message}"
|
||||
}
|
||||
},
|
||||
"downloadSkipBaseModels": {
|
||||
"label": "跳过这些基础模型的下载",
|
||||
"help": "适用于所有下载流程。这里只能选择受支持的基础模型。",
|
||||
"searchPlaceholder": "筛选基础模型...",
|
||||
"empty": "没有与当前搜索匹配的基础模型。",
|
||||
"summary": {
|
||||
"none": "未选择",
|
||||
"count": "已选择 {count} 项"
|
||||
},
|
||||
"actions": {
|
||||
"edit": "编辑",
|
||||
"collapse": "收起",
|
||||
"clear": "清空"
|
||||
},
|
||||
"validation": {
|
||||
"saveFailed": "无法保存已排除的基础模型:{message}"
|
||||
}
|
||||
},
|
||||
"layoutSettings": {
|
||||
"displayDensity": "显示密度",
|
||||
"displayDensityOptions": {
|
||||
@@ -1467,6 +1485,7 @@
|
||||
"pleaseSelectVersion": "请选择版本",
|
||||
"versionExists": "该版本已存在于你的库中",
|
||||
"downloadCompleted": "下载成功完成",
|
||||
"downloadSkippedByBaseModel": "由于基础模型 {baseModel} 已被排除,已跳过下载",
|
||||
"autoOrganizeSuccess": "自动整理已成功完成,共 {count} 个 {type}",
|
||||
"autoOrganizePartialSuccess": "自动整理完成:已移动 {success} 个,{failures} 个失败,共 {total} 个模型",
|
||||
"autoOrganizeFailed": "自动整理失败:{error}",
|
||||
|
||||
@@ -292,13 +292,13 @@
|
||||
"blurNsfwContentHelp": "模糊成熟(NSFW)內容預覽圖片",
|
||||
"showOnlySfw": "僅顯示 SFW 結果",
|
||||
"showOnlySfwHelp": "瀏覽和搜尋時過濾所有 NSFW 內容",
|
||||
"matureBlurThreshold": "[TODO: Translate] Mature Blur Threshold",
|
||||
"matureBlurThresholdHelp": "[TODO: Translate] Set which rating level starts blur filtering when NSFW blur is enabled.",
|
||||
"matureBlurThreshold": "成人內容模糊閾值",
|
||||
"matureBlurThresholdHelp": "設定當啟用 NSFW 模糊時,從哪個評級級別開始模糊過濾。",
|
||||
"matureBlurThresholdOptions": {
|
||||
"pg13": "[TODO: Translate] PG13 and above",
|
||||
"r": "[TODO: Translate] R and above (default)",
|
||||
"x": "[TODO: Translate] X and above",
|
||||
"xxx": "[TODO: Translate] XXX only"
|
||||
"pg13": "PG13 及以上",
|
||||
"r": "R 及以上(預設)",
|
||||
"x": "X 及以上",
|
||||
"xxx": "僅 XXX"
|
||||
}
|
||||
},
|
||||
"videoSettings": {
|
||||
@@ -323,6 +323,24 @@
|
||||
"saveFailed": "無法儲存跳過路徑:{message}"
|
||||
}
|
||||
},
|
||||
"downloadSkipBaseModels": {
|
||||
"label": "跳過這些基礎模型的下載",
|
||||
"help": "適用於所有下載流程。這裡只能選擇受支援的基礎模型。",
|
||||
"searchPlaceholder": "篩選基礎模型...",
|
||||
"empty": "沒有符合目前搜尋條件的基礎模型。",
|
||||
"summary": {
|
||||
"none": "未選擇",
|
||||
"count": "已選擇 {count} 項"
|
||||
},
|
||||
"actions": {
|
||||
"edit": "編輯",
|
||||
"collapse": "收起",
|
||||
"clear": "清空"
|
||||
},
|
||||
"validation": {
|
||||
"saveFailed": "無法儲存已排除的基礎模型:{message}"
|
||||
}
|
||||
},
|
||||
"layoutSettings": {
|
||||
"displayDensity": "顯示密度",
|
||||
"displayDensityOptions": {
|
||||
@@ -1467,6 +1485,7 @@
|
||||
"pleaseSelectVersion": "請選擇一個版本",
|
||||
"versionExists": "此版本已存在於您的庫中",
|
||||
"downloadCompleted": "下載成功完成",
|
||||
"downloadSkippedByBaseModel": "由於基礎模型 {baseModel} 已被排除,已跳過下載",
|
||||
"autoOrganizeSuccess": "自動整理已成功完成,共 {count} 個 {type} 已整理",
|
||||
"autoOrganizePartialSuccess": "自動整理完成:已移動 {success} 個,{failures} 個失敗,共 {total} 個模型",
|
||||
"autoOrganizeFailed": "自動整理失敗:{error}",
|
||||
|
||||
@@ -13,6 +13,7 @@ from ..utils.models import LoraMetadata, CheckpointMetadata, EmbeddingMetadata
|
||||
from ..utils.constants import (
|
||||
CARD_PREVIEW_WIDTH,
|
||||
DIFFUSION_MODEL_BASE_MODELS,
|
||||
SUPPORTED_DOWNLOAD_SKIP_BASE_MODELS,
|
||||
VALID_LORA_TYPES,
|
||||
)
|
||||
from ..utils.civitai_utils import rewrite_preview_url
|
||||
@@ -228,7 +229,9 @@ class DownloadManager:
|
||||
# Update status based on result
|
||||
if task_id in self._active_downloads:
|
||||
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"]:
|
||||
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',
|
||||
}
|
||||
|
||||
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
|
||||
is_diffusion_model = False
|
||||
if model_type == "checkpoint":
|
||||
base_model_value = version_info.get("baseModel", "")
|
||||
if base_model_value in DIFFUSION_MODEL_BASE_MODELS:
|
||||
is_diffusion_model = True
|
||||
logger.info(
|
||||
|
||||
@@ -11,7 +11,11 @@ from typing import Any, Awaitable, Dict, Iterable, List, Mapping, Optional, Sequ
|
||||
|
||||
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.settings_paths import APP_NAME, ensure_settings_file, get_legacy_settings_path
|
||||
from ..utils.tag_priorities import (
|
||||
@@ -73,6 +77,7 @@ DEFAULT_SETTINGS: Dict[str, Any] = {
|
||||
"update_flag_strategy": "same_base",
|
||||
"auto_organize_exclusions": [],
|
||||
"metadata_refresh_skip_paths": [],
|
||||
"download_skip_base_models": [],
|
||||
}
|
||||
|
||||
|
||||
@@ -276,6 +281,21 @@ class SettingsManager:
|
||||
self.settings["metadata_refresh_skip_paths"] = []
|
||||
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
|
||||
raw_mature_level = self.settings.get("mature_blur_level")
|
||||
normalized_mature_level = self.normalize_mature_blur_level(raw_mature_level)
|
||||
@@ -964,6 +984,45 @@ class SettingsManager:
|
||||
self._save_settings()
|
||||
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]]:
|
||||
"""Get extra folder paths for the active library.
|
||||
|
||||
@@ -1032,6 +1091,8 @@ class SettingsManager:
|
||||
value = self.normalize_auto_organize_exclusions(value)
|
||||
elif key == "metadata_refresh_skip_paths":
|
||||
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":
|
||||
value = self.normalize_mature_blur_level(value)
|
||||
self.settings[key] = value
|
||||
|
||||
@@ -113,3 +113,59 @@ DIFFUSION_MODEL_BASE_MODELS = frozenset(
|
||||
"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;
|
||||
}
|
||||
|
||||
.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 {
|
||||
border-color: var(--lora-accent);
|
||||
outline: none;
|
||||
|
||||
@@ -492,7 +492,7 @@ export class DownloadManager {
|
||||
console.error('WebSocket error:', error);
|
||||
};
|
||||
|
||||
await this.apiClient.downloadModel(
|
||||
const response = await this.apiClient.downloadModel(
|
||||
modelId,
|
||||
versionId,
|
||||
modelRoot,
|
||||
@@ -502,6 +502,16 @@ export class DownloadManager {
|
||||
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');
|
||||
|
||||
if (closeModal) {
|
||||
|
||||
@@ -139,6 +139,10 @@ export class SettingsManager {
|
||||
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(
|
||||
backendSettings?.mature_blur_level ?? defaults.mature_blur_level
|
||||
);
|
||||
@@ -179,6 +183,15 @@ export class SettingsManager {
|
||||
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 = []) {
|
||||
if (!Array.isArray(messages) || messages.length === 0) {
|
||||
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.initializeNavigation();
|
||||
this.initializeSearch();
|
||||
@@ -730,6 +773,13 @@ export class SettingsManager {
|
||||
metadataRefreshSkipPathsError.textContent = '';
|
||||
}
|
||||
|
||||
this.renderDownloadSkipBaseModels();
|
||||
const downloadSkipBaseModelsError = document.getElementById('downloadSkipBaseModelsError');
|
||||
if (downloadSkipBaseModelsError) {
|
||||
downloadSkipBaseModelsError.textContent = '';
|
||||
}
|
||||
this.setDownloadSkipBaseModelsPanelOpen(false);
|
||||
|
||||
// Set video autoplay on hover setting
|
||||
const autoplayOnHoverCheckbox = document.getElementById('autoplayOnHover');
|
||||
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() {
|
||||
const input = document.getElementById('metadataRefreshSkipPaths');
|
||||
const errorElement = document.getElementById('metadataRefreshSkipPathsError');
|
||||
|
||||
@@ -38,6 +38,7 @@ const DEFAULT_SETTINGS_BASE = Object.freeze({
|
||||
hide_early_access_updates: false,
|
||||
auto_organize_exclusions: [],
|
||||
metadata_refresh_skip_paths: [],
|
||||
download_skip_base_models: [],
|
||||
});
|
||||
|
||||
export function createDefaultSettings() {
|
||||
|
||||
@@ -743,6 +743,46 @@
|
||||
</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 -->
|
||||
<div class="setting-item priority-tags-item">
|
||||
<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()
|
||||
|
||||
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}",
|
||||
},
|
||||
"base_model_path_mappings": {"BaseModel": "MappedModel"},
|
||||
"download_skip_base_models": [],
|
||||
}
|
||||
)
|
||||
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 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")
|
||||
|
||||
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