Merge pull request #1132 from willmiao/fix/diffusion-routing-default

fix(downloads): route unknown checkpoint baseModels to diffusion models by default
This commit is contained in:
pixelpaws
2026-10-02 17:09:56 +08:00
committed by GitHub
23 changed files with 426 additions and 18 deletions
+28
View File
@@ -103,6 +103,15 @@ Locales: `en`, `zh-CN`, `zh-TW`, `ja`, `ko`, `fr`, `de`, `es`, `ru`, `he` (RTL).
> failure-table conventions; reuse each locale's existing renderings of those sibling keys). > failure-table conventions; reuse each locale's existing renderings of those sibling keys).
> All 9 locales are translated in the same pass (terminology in §2, "Sidecar storage feature"). > All 9 locales are translated in the same pass (terminology in §2, "Sidecar storage feature").
> **Status (2026-10, unknown base model routing):** the download-routing inversion added
> 4 keys (`settings.unknownBaseModelRouting.label`, `.help`, `.options.diffusionModel`,
> `.options.checkpoint`) — the option labels reuse each locale's existing
> `checkpoints.modelTypes.diffusion_model` / `.checkpoint` renderings. The same pass also
> translated the leftover `doctor.issues.sidecar_mirror_orphans.title` ("Centralized
> Sidecars", §2 "Sidecar storage feature" terminology). All 9 locales are translated,
> so the "no remaining placeholders" claim holds again. Terminology in §2, "Download
> routing feature".
--- ---
## 1. Hard rules (do not violate) ## 1. Hard rules (do not violate)
@@ -543,6 +552,25 @@ short and imperative and do not append a keyboard hint in any locale.
The grip itself is an icon and is never translated. The grip itself is an icon and is never translated.
### Download routing feature (unknown base model routing)
The `settings.unknownBaseModelRouting.*` keys (Settings → Library → Folder Settings) decide
which library a checkpoint download lands in when CivitAI reports a baseModel that is in
neither the known-checkpoint nor the known-diffusion-model list. The option labels reuse
each locale's `checkpoints.modelTypes.diffusion_model` / `.checkpoint` renderings
(model-type names, R3 — ja/ko keep the Latin loanword), pluralized only where the locale
pluralizes (de Diffusionsmodelle, es Modelos de difusión, fr Modèles de diffusion,
ru Диффузионные модели, he מודלי דיפוזיה; CJK stays singular, "Checkpoint(s)" follows
`header.navigation.checkpoints`).
| Term | Rendering |
|---|---|
| routing (noun, of a download into a library) | zh-CN 路由 · zh-TW 路由 · ja 振り分け · ko 라우팅 · fr routage · de Routing · es enrutamiento · ru маршрутизация · he ניתוב |
| unknown base model | zh-CN 未知基础模型 · zh-TW 未知基礎模型 · ja 不明なベースモデル · ko 알 수 없는 베이스 모델 · fr modèle de base inconnu · de unbekanntes Basismodell · es modelo base desconocido · ru неизвестная базовая модель · he מודל בסיס לא מוכר |
The baseModel family names in the help text (`SD 1.x/2.x/3.x, SDXL, Pony, Illustrious,
NoobAI`) are CivitAI baseModel values and stay verbatim in every locale.
--- ---
## 3. Cross-cutting confusion hot-spots (must-fix list) ## 3. Cross-cutting confusion hot-spots (must-fix list)
+9 -1
View File
@@ -353,6 +353,14 @@
"placeholder": "Leer lassen, um aria2c aus dem PATH zu verwenden" "placeholder": "Leer lassen, um aria2c aus dem PATH zu verwenden"
}, },
"aria2HelpLink": "Erfahren Sie, wie Sie das aria2-Download-Backend einrichten", "aria2HelpLink": "Erfahren Sie, wie Sie das aria2-Download-Backend einrichten",
"unknownBaseModelRouting": {
"label": "Routing unbekannter Basismodelle",
"help": "Legt fest, wohin Checkpoint-Downloads gehen, wenn CivitAI ein Basismodell meldet, das weder ein bekanntes Checkpoint (SD 1.x/2.x/3.x, SDXL, Pony, Illustrious, NoobAI) noch ein bekanntes Diffusionsmodell ist. Neue Diffusionsarchitekturen erscheinen häufig, daher ist das Routing zu Diffusionsmodellen meist korrekt.",
"options": {
"diffusionModel": "Diffusionsmodelle",
"checkpoint": "Checkpoints"
}
},
"civitaiHostBanner": { "civitaiHostBanner": {
"title": "CivitAI-Host-Einstellung verfügbar", "title": "CivitAI-Host-Einstellung verfügbar",
"content": "CivitAI verwendet jetzt civitai.com für SFW-Inhalte und civitai.red für uneingeschränkte Inhalte. In den Einstellungen können Sie ändern, welche Seite standardmäßig geöffnet wird.", "content": "CivitAI verwendet jetzt civitai.com für SFW-Inhalte und civitai.red für uneingeschränkte Inhalte. In den Einstellungen können Sie ändern, welche Seite standardmäßig geöffnet wird.",
@@ -2770,7 +2778,7 @@
"title": "Konflikte durch doppelte Dateinamen" "title": "Konflikte durch doppelte Dateinamen"
}, },
"sidecar_mirror_orphans": { "sidecar_mirror_orphans": {
"title": "[TODO: Translate] Centralized Sidecars" "title": "Zentralisierte Sidecar-Dateien"
}, },
"ui_version": { "ui_version": {
"title": "UI-Version" "title": "UI-Version"
+8
View File
@@ -353,6 +353,14 @@
"placeholder": "Leave empty to use aria2c from PATH" "placeholder": "Leave empty to use aria2c from PATH"
}, },
"aria2HelpLink": "Learn how to set up the aria2 download backend", "aria2HelpLink": "Learn how to set up the aria2 download backend",
"unknownBaseModelRouting": {
"label": "Unknown base model routing",
"help": "Decides where checkpoint downloads go when CivitAI reports a base model that is neither a known checkpoint (SD 1.x/2.x/3.x, SDXL, Pony, Illustrious, NoobAI) nor a known diffusion model. New diffusion architectures appear frequently, so routing them to diffusion models is usually correct.",
"options": {
"diffusionModel": "Diffusion models",
"checkpoint": "Checkpoints"
}
},
"civitaiHostBanner": { "civitaiHostBanner": {
"title": "CivitAI host preference available", "title": "CivitAI host preference available",
"content": "CivitAI now uses civitai.com for SFW content and civitai.red for unrestricted content. You can change which site opens by default in Settings.", "content": "CivitAI now uses civitai.com for SFW content and civitai.red for unrestricted content. You can change which site opens by default in Settings.",
+9 -1
View File
@@ -353,6 +353,14 @@
"placeholder": "Déjalo vacío para usar aria2c desde el PATH" "placeholder": "Déjalo vacío para usar aria2c desde el PATH"
}, },
"aria2HelpLink": "Aprende a configurar el backend de descarga aria2", "aria2HelpLink": "Aprende a configurar el backend de descarga aria2",
"unknownBaseModelRouting": {
"label": "Enrutamiento de modelos base desconocidos",
"help": "Decide a dónde van las descargas de Checkpoint cuando CivitAI informa de un modelo base que no es ni un Checkpoint conocido (SD 1.x/2.x/3.x, SDXL, Pony, Illustrious, NoobAI) ni un modelo de difusión conocido. Las nuevas arquitecturas de difusión aparecen con frecuencia, por lo que enrutarlas a modelos de difusión suele ser correcto.",
"options": {
"diffusionModel": "Modelos de difusión",
"checkpoint": "Checkpoints"
}
},
"civitaiHostBanner": { "civitaiHostBanner": {
"title": "Preferencia de host de CivitAI disponible", "title": "Preferencia de host de CivitAI disponible",
"content": "CivitAI ahora usa civitai.com para contenido SFW y civitai.red para contenido sin restricciones. Puedes cambiar en Ajustes qué sitio se abre por defecto.", "content": "CivitAI ahora usa civitai.com para contenido SFW y civitai.red para contenido sin restricciones. Puedes cambiar en Ajustes qué sitio se abre por defecto.",
@@ -2770,7 +2778,7 @@
"title": "Conflictos de nombres de archivo duplicados" "title": "Conflictos de nombres de archivo duplicados"
}, },
"sidecar_mirror_orphans": { "sidecar_mirror_orphans": {
"title": "[TODO: Translate] Centralized Sidecars" "title": "Archivos sidecar centralizados"
}, },
"ui_version": { "ui_version": {
"title": "Versión de la interfaz" "title": "Versión de la interfaz"
+9 -1
View File
@@ -353,6 +353,14 @@
"placeholder": "Laisser vide pour utiliser aria2c depuis le PATH" "placeholder": "Laisser vide pour utiliser aria2c depuis le PATH"
}, },
"aria2HelpLink": "Apprenez à configurer le backend de téléchargement aria2", "aria2HelpLink": "Apprenez à configurer le backend de téléchargement aria2",
"unknownBaseModelRouting": {
"label": "Routage des modèles de base inconnus",
"help": "Détermine où vont les téléchargements de Checkpoint lorsque CivitAI signale un modèle de base qui n’est ni un Checkpoint connu (SD 1.x/2.x/3.x, SDXL, Pony, Illustrious, NoobAI) ni un modèle de diffusion connu. De nouvelles architectures de diffusion apparaissent fréquemment, donc les router vers les modèles de diffusion est généralement correct.",
"options": {
"diffusionModel": "Modèles de diffusion",
"checkpoint": "Checkpoints"
}
},
"civitaiHostBanner": { "civitaiHostBanner": {
"title": "Préférence d’hôte CivitAI disponible", "title": "Préférence d’hôte CivitAI disponible",
"content": "CivitAI utilise désormais civitai.com pour le contenu SFW et civitai.red pour le contenu sans restriction. Vous pouvez modifier dans les paramètres le site ouvert par défaut.", "content": "CivitAI utilise désormais civitai.com pour le contenu SFW et civitai.red pour le contenu sans restriction. Vous pouvez modifier dans les paramètres le site ouvert par défaut.",
@@ -2770,7 +2778,7 @@
"title": "Conflits de noms de fichiers en double" "title": "Conflits de noms de fichiers en double"
}, },
"sidecar_mirror_orphans": { "sidecar_mirror_orphans": {
"title": "[TODO: Translate] Centralized Sidecars" "title": "Fichiers sidecar centralisés"
}, },
"ui_version": { "ui_version": {
"title": "Version de l'interface" "title": "Version de l'interface"
+9 -1
View File
@@ -353,6 +353,14 @@
"placeholder": "השאר ריק כדי להשתמש ב-aria2c מתוך ה-PATH" "placeholder": "השאר ריק כדי להשתמש ב-aria2c מתוך ה-PATH"
}, },
"aria2HelpLink": "למד כיצד להגדיר את מנוע ההורדה aria2", "aria2HelpLink": "למד כיצד להגדיר את מנוע ההורדה aria2",
"unknownBaseModelRouting": {
"label": "ניתוב מודל בסיס לא מוכר",
"help": "קובע לאן מועברות הורדות של Checkpoint כאשר CivitAI מדווח על מודל בסיס שהוא לא Checkpoint מוכר (SD 1.x/2.x/3.x, SDXL, Pony, Illustrious, NoobAI) ולא מודל דיפוזיה מוכר. ארכיטקטורות דיפוזיה חדשות מופיעות לעיתים קרובות, ולכן ניתוב שלהן למודלי דיפוזיה הוא בדרך כלל הנכון.",
"options": {
"diffusionModel": "מודלי דיפוזיה",
"checkpoint": "Checkpoints"
}
},
"civitaiHostBanner": { "civitaiHostBanner": {
"title": "העדפת מארח CivitAI זמינה", "title": "העדפת מארח CivitAI זמינה",
"content": "CivitAI משתמש כעת ב-civitai.com עבור תוכן SFW וב-civitai.red עבור תוכן ללא הגבלות. ניתן לשנות בהגדרות איזה אתר ייפתח כברירת מחדל.", "content": "CivitAI משתמש כעת ב-civitai.com עבור תוכן SFW וב-civitai.red עבור תוכן ללא הגבלות. ניתן לשנות בהגדרות איזה אתר ייפתח כברירת מחדל.",
@@ -2770,7 +2778,7 @@
"title": "התנגשויות שמות קבצים כפולים" "title": "התנגשויות שמות קבצים כפולים"
}, },
"sidecar_mirror_orphans": { "sidecar_mirror_orphans": {
"title": "[TODO: Translate] Centralized Sidecars" "title": "קבצי לוואי מרוכזים"
}, },
"ui_version": { "ui_version": {
"title": "גרסת הממשק" "title": "גרסת הממשק"
+9 -1
View File
@@ -353,6 +353,14 @@
"placeholder": "空欄のままにすると PATH 上の aria2c を使用します" "placeholder": "空欄のままにすると PATH 上の aria2c を使用します"
}, },
"aria2HelpLink": "aria2 ダウンロードバックエンドの設定方法", "aria2HelpLink": "aria2 ダウンロードバックエンドの設定方法",
"unknownBaseModelRouting": {
"label": "不明なベースモデルの振り分け",
"help": "CivitAIが報告するベースモデルが既知のCheckpoint(SD 1.x/2.x/3.x、SDXL、Pony、Illustrious、NoobAI)でも既知のDiffusion Modelでもない場合に、Checkpointのダウンロードの保存先を決定します。新しい拡散アーキテクチャは頻繁に登場するため、通常はDiffusion Modelへの振り分けが正しい選択です。",
"options": {
"diffusionModel": "Diffusion Model",
"checkpoint": "Checkpoint"
}
},
"civitaiHostBanner": { "civitaiHostBanner": {
"title": "CivitAI ホスト設定を利用できます", "title": "CivitAI ホスト設定を利用できます",
"content": "CivitAI は現在、SFW コンテンツには civitai.com、制限なしコンテンツには civitai.red を使用しています。設定で既定で開くサイトを変更できます。", "content": "CivitAI は現在、SFW コンテンツには civitai.com、制限なしコンテンツには civitai.red を使用しています。設定で既定で開くサイトを変更できます。",
@@ -2770,7 +2778,7 @@
"title": "ファイル名重複競合" "title": "ファイル名重複競合"
}, },
"sidecar_mirror_orphans": { "sidecar_mirror_orphans": {
"title": "[TODO: Translate] Centralized Sidecars" "title": "集中保存のサイドカーファイル"
}, },
"ui_version": { "ui_version": {
"title": "UI バージョン" "title": "UI バージョン"
+9 -1
View File
@@ -353,6 +353,14 @@
"placeholder": "비워 두면 PATH의 aria2c를 사용합니다" "placeholder": "비워 두면 PATH의 aria2c를 사용합니다"
}, },
"aria2HelpLink": "aria2 다운로드 백엔드 설정 방법 알아보기", "aria2HelpLink": "aria2 다운로드 백엔드 설정 방법 알아보기",
"unknownBaseModelRouting": {
"label": "알 수 없는 베이스 모델 라우팅",
"help": "CivitAI가 보고한 베이스 모델이 알려진 Checkpoint(SD 1.x/2.x/3.x, SDXL, Pony, Illustrious, NoobAI)도 알려진 Diffusion Model도 아닐 때 Checkpoint 다운로드의 저장 위치를 결정합니다. 새로운 확산 아키텍처는 자주 등장하므로 일반적으로 Diffusion Model로 라우팅하는 것이 올바릅니다.",
"options": {
"diffusionModel": "Diffusion Model",
"checkpoint": "Checkpoint"
}
},
"civitaiHostBanner": { "civitaiHostBanner": {
"title": "CivitAI 호스트 기본 설정 사용 가능", "title": "CivitAI 호스트 기본 설정 사용 가능",
"content": "이제 CivitAI는 SFW 콘텐츠에 civitai.com을, 무제한 콘텐츠에 civitai.red를 사용합니다. 설정에서 기본으로 열 사이트를 변경할 수 있습니다.", "content": "이제 CivitAI는 SFW 콘텐츠에 civitai.com을, 무제한 콘텐츠에 civitai.red를 사용합니다. 설정에서 기본으로 열 사이트를 변경할 수 있습니다.",
@@ -2770,7 +2778,7 @@
"title": "파일명 중복 충돌" "title": "파일명 중복 충돌"
}, },
"sidecar_mirror_orphans": { "sidecar_mirror_orphans": {
"title": "[TODO: Translate] Centralized Sidecars" "title": "중앙 집중식 사이드카 파일"
}, },
"ui_version": { "ui_version": {
"title": "UI 버전" "title": "UI 버전"
+9 -1
View File
@@ -353,6 +353,14 @@
"placeholder": "Оставьте пустым, чтобы использовать aria2c из PATH" "placeholder": "Оставьте пустым, чтобы использовать aria2c из PATH"
}, },
"aria2HelpLink": "Узнайте, как настроить сервер загрузки aria2", "aria2HelpLink": "Узнайте, как настроить сервер загрузки aria2",
"unknownBaseModelRouting": {
"label": "Маршрутизация неизвестных базовых моделей",
"help": "Определяет, куда отправляются загрузки Checkpoint, когда CivitAI сообщает базовую модель, которая не является ни известным Checkpoint (SD 1.x/2.x/3.x, SDXL, Pony, Illustrious, NoobAI), ни известной диффузионной моделью. Новые диффузионные архитектуры появляются часто, поэтому маршрутизация их в диффузионные модели обычно правильная.",
"options": {
"diffusionModel": "Диффузионные модели",
"checkpoint": "Checkpoints"
}
},
"civitaiHostBanner": { "civitaiHostBanner": {
"title": "Доступна настройка хоста CivitAI", "title": "Доступна настройка хоста CivitAI",
"content": "Теперь CivitAI использует civitai.com для контента SFW и civitai.red для контента без ограничений. В настройках можно изменить, какой сайт открывать по умолчанию.", "content": "Теперь CivitAI использует civitai.com для контента SFW и civitai.red для контента без ограничений. В настройках можно изменить, какой сайт открывать по умолчанию.",
@@ -2770,7 +2778,7 @@
"title": "Конфликты дублирующихся имён файлов" "title": "Конфликты дублирующихся имён файлов"
}, },
"sidecar_mirror_orphans": { "sidecar_mirror_orphans": {
"title": "[TODO: Translate] Centralized Sidecars" "title": "Централизованные sidecar-файлы"
}, },
"ui_version": { "ui_version": {
"title": "Версия интерфейса" "title": "Версия интерфейса"
+9 -1
View File
@@ -353,6 +353,14 @@
"placeholder": "留空则使用 PATH 中的 aria2c" "placeholder": "留空则使用 PATH 中的 aria2c"
}, },
"aria2HelpLink": "了解如何配置 aria2 下载后端", "aria2HelpLink": "了解如何配置 aria2 下载后端",
"unknownBaseModelRouting": {
"label": "未知基础模型路由",
"help": "当 CivitAI 报告的基础模型既不是已知的 Checkpoint(SD 1.x/2.x/3.x、SDXL、Pony、Illustrious、NoobAI),也不是已知的扩散模型时,决定 Checkpoint 下载的保存位置。新的扩散架构频繁出现,因此将其路由到扩散模型通常是正确的。",
"options": {
"diffusionModel": "扩散模型",
"checkpoint": "Checkpoint"
}
},
"civitaiHostBanner": { "civitaiHostBanner": {
"title": "已提供 CivitAI 站点偏好设置", "title": "已提供 CivitAI 站点偏好设置",
"content": "CivitAI 现在使用 civitai.com 提供 SFW 内容,使用 civitai.red 提供无限制内容。你可以在设置中更改默认打开的站点。", "content": "CivitAI 现在使用 civitai.com 提供 SFW 内容,使用 civitai.red 提供无限制内容。你可以在设置中更改默认打开的站点。",
@@ -2770,7 +2778,7 @@
"title": "文件名重复冲突" "title": "文件名重复冲突"
}, },
"sidecar_mirror_orphans": { "sidecar_mirror_orphans": {
"title": "[TODO: Translate] Centralized Sidecars" "title": "集中存储的附属文件"
}, },
"ui_version": { "ui_version": {
"title": "UI 版本" "title": "UI 版本"
+9 -1
View File
@@ -353,6 +353,14 @@
"placeholder": "留空則使用 PATH 中的 aria2c" "placeholder": "留空則使用 PATH 中的 aria2c"
}, },
"aria2HelpLink": "了解如何設定 aria2 下載後端", "aria2HelpLink": "了解如何設定 aria2 下載後端",
"unknownBaseModelRouting": {
"label": "未知基礎模型路由",
"help": "當 CivitAI 回報的基礎模型既不是已知的 Checkpoint(SD 1.x/2.x/3.x、SDXL、Pony、Illustrious、NoobAI),也不是已知的擴散模型時,決定 Checkpoint 下載的儲存位置。新的擴散架構頻繁出現,因此將其路由到擴散模型通常是正確的。",
"options": {
"diffusionModel": "擴散模型",
"checkpoint": "Checkpoint"
}
},
"civitaiHostBanner": { "civitaiHostBanner": {
"title": "已提供 CivitAI 站點偏好設定", "title": "已提供 CivitAI 站點偏好設定",
"content": "CivitAI 現在使用 civitai.com 提供 SFW 內容,使用 civitai.red 提供無限制內容。您可以在設定中變更預設開啟的站點。", "content": "CivitAI 現在使用 civitai.com 提供 SFW 內容,使用 civitai.red 提供無限制內容。您可以在設定中變更預設開啟的站點。",
@@ -2770,7 +2778,7 @@
"title": "檔案名稱重複衝突" "title": "檔案名稱重複衝突"
}, },
"sidecar_mirror_orphans": { "sidecar_mirror_orphans": {
"title": "[TODO: Translate] Centralized Sidecars" "title": "集中儲存的附屬檔案"
}, },
"ui_version": { "ui_version": {
"title": "UI 版本" "title": "UI 版本"
@@ -55,6 +55,11 @@ class DownloadRoutingHandler:
status=400, status=400,
) )
# CivitAI ModelType.UNet downloads go through the checkpoint branch,
# same as in the download manager.
if model_type.lower() == "unet":
model_type = "checkpoint"
if model_type.lower() in VALID_OTHER_CIVITAI_TYPES: if model_type.lower() in VALID_OTHER_CIVITAI_TYPES:
from ...services.settings_manager import get_settings_manager from ...services.settings_manager import get_settings_manager
@@ -96,10 +101,15 @@ class DownloadRoutingHandler:
} }
) )
from ...services.settings_manager import get_settings_manager
is_diffusion = is_diffusion_model_download( is_diffusion = is_diffusion_model_download(
model_type, model_type,
file_types=(str(t) for t in file_types), file_types=(str(t) for t in file_types),
base_model=base_model, base_model=base_model,
unknown_base_model_default=get_settings_manager().get(
"unknown_base_model_routing", "diffusion_model"
),
) )
return web.json_response( return web.json_response(
{ {
+8 -1
View File
@@ -1523,7 +1523,11 @@ class DownloadManager:
return {"success": False, "error": "Failed to fetch model metadata"} return {"success": False, "error": "Failed to fetch model metadata"}
model_type_from_info = version_info.get("model", {}).get("type", "").lower() model_type_from_info = version_info.get("model", {}).get("type", "").lower()
if model_type_from_info == "checkpoint": # CivitAI ModelType has no model-level diffusion variant: DiT
# models are uploaded as "Checkpoint" or "UNet". Both go through
# the checkpoint branch so the standard diffusion routing chain
# (file type -> baseModel lists -> unknown default) applies.
if model_type_from_info in ("checkpoint", "unet"):
model_type = "checkpoint" model_type = "checkpoint"
elif model_type_from_info in VALID_LORA_TYPES: elif model_type_from_info in VALID_LORA_TYPES:
model_type = "lora" model_type = "lora"
@@ -1670,6 +1674,9 @@ class DownloadManager:
model_type, model_type,
file_types=(f.get("type", "") for f in version_info.get("files", [])), file_types=(f.get("type", "") for f in version_info.get("files", [])),
base_model=base_model_value, base_model=base_model_value,
unknown_base_model_default=get_settings_manager().get(
"unknown_base_model_routing", "diffusion_model"
),
) )
# Existence check after the metadata fetch (#1058): # Existence check after the metadata fetch (#1058):
+22 -2
View File
@@ -13,6 +13,7 @@ import logging
from typing import Iterable, Optional from typing import Iterable, Optional
from ..utils.constants import ( from ..utils.constants import (
CHECKPOINT_BASE_MODELS,
CIVITAI_FILE_TYPE_TO_OTHER_SUB_TYPE, CIVITAI_FILE_TYPE_TO_OTHER_SUB_TYPE,
CIVITAI_TYPE_TO_OTHER_SUB_TYPE, CIVITAI_TYPE_TO_OTHER_SUB_TYPE,
DIFFUSION_MODEL_BASE_MODELS, DIFFUSION_MODEL_BASE_MODELS,
@@ -24,17 +25,27 @@ logger = logging.getLogger(__name__)
# model (loaded via UNETLoader in ComfyUI) rather than a full checkpoint. # model (loaded via UNETLoader in ComfyUI) rather than a full checkpoint.
DIFFUSION_FILE_TYPES = frozenset({"UNet", "Diffusion Model"}) DIFFUSION_FILE_TYPES = frozenset({"UNet", "Diffusion Model"})
# Allowed values for the "unknown_base_model_routing" setting / the
# unknown_base_model_default parameter below.
ROUTING_DIFFUSION_MODEL = "diffusion_model"
ROUTING_CHECKPOINT = "checkpoint"
def is_diffusion_model_download( def is_diffusion_model_download(
model_type: str, model_type: str,
file_types: Iterable[str] = (), file_types: Iterable[str] = (),
base_model: str = "", base_model: str = "",
unknown_base_model_default: str = ROUTING_DIFFUSION_MODEL,
) -> bool: ) -> bool:
"""Return True when a download should be routed to the unet roots. """Return True when a download should be routed to the unet roots.
Only applies to downloads initiated from the checkpoint library. Only applies to downloads initiated from the checkpoint library.
Priority: (1) any file has type "UNet" or "Diffusion Model" (the more Priority: (1) any file has type "UNet" or "Diffusion Model" (the more
direct signal from CivitAI), (2) baseModel is a known diffusion model. direct signal from CivitAI), (2) baseModel is a known diffusion model,
(3) baseModel is a known full checkpoint -> not diffusion, (4) unknown
or empty baseModel -> the ``unknown_base_model_default`` setting, which
defaults to diffusion because the set of true checkpoint families is
closed while new DiT base models appear all the time.
""" """
if model_type != "checkpoint": if model_type != "checkpoint":
return False return False
@@ -54,7 +65,16 @@ def is_diffusion_model_download(
) )
return True return True
return False if base_model in CHECKPOINT_BASE_MODELS:
return False
is_diffusion = unknown_base_model_default != ROUTING_CHECKPOINT
logger.info(
"baseModel '%s' is unknown, routing to %s folder (unknown_base_model_routing)",
base_model,
"unet" if is_diffusion else "checkpoint",
)
return is_diffusion
def resolve_other_download_sub_type( def resolve_other_download_sub_type(
+15
View File
@@ -130,6 +130,9 @@ DEFAULT_SETTINGS: Dict[str, Any] = {
"metadata_refresh_skip_paths": [], "metadata_refresh_skip_paths": [],
"skip_previously_downloaded_model_versions": False, "skip_previously_downloaded_model_versions": False,
"download_skip_base_models": [], "download_skip_base_models": [],
# Routing target for checkpoint downloads whose baseModel is neither a
# known diffusion model nor a known full checkpoint (CHECKPOINT_BASE_MODELS).
"unknown_base_model_routing": "diffusion_model",
"backup_auto_enabled": True, "backup_auto_enabled": True,
"backup_retention_count": 5, "backup_retention_count": 5,
"use_new_license_icons": True, "use_new_license_icons": True,
@@ -1628,6 +1631,16 @@ class SettingsManager:
return normalized return normalized
return "alongside" return "alongside"
@staticmethod
def _normalize_unknown_base_model_routing(value: Any) -> str:
"""Return a valid unknown-base-model routing target, falling back to ``diffusion_model``."""
if isinstance(value, str):
normalized = value.strip().lower()
if normalized in ("diffusion_model", "checkpoint"):
return normalized
return "diffusion_model"
def _refresh_sidecar_storage_config(self) -> None: def _refresh_sidecar_storage_config(self) -> None:
"""Rebuild dependent config state after sidecar storage settings change.""" """Rebuild dependent config state after sidecar storage settings change."""
@@ -1840,6 +1853,8 @@ class SettingsManager:
self._migrate_recipes_directory(current_recipes_dir, target_recipes_dir) self._migrate_recipes_directory(current_recipes_dir, target_recipes_dir)
elif key == "sidecar_storage_mode": elif key == "sidecar_storage_mode":
value = self._normalize_sidecar_storage_mode(value) value = self._normalize_sidecar_storage_mode(value)
elif key == "unknown_base_model_routing":
value = self._normalize_unknown_base_model_routing(value)
elif key == "sidecar_storage_path": elif key == "sidecar_storage_path":
value = self._normalize_recipes_path_value(value) value = self._normalize_recipes_path_value(value)
self.settings[key] = value self.settings[key] = value
+58
View File
@@ -352,12 +352,15 @@ DIFFUSION_MODEL_BASE_MODELS = frozenset(
"PixArt E", "PixArt E",
# Video diffusion models # Video diffusion models
"CogVideoX", "CogVideoX",
"Flux 3 Video",
"Hunyuan Video", "Hunyuan Video",
"LTXV", "LTXV",
"LTXV2", "LTXV2",
"LTXV 2.3", "LTXV 2.3",
"LTXV 2.5",
"Mochi", "Mochi",
"SVD", "SVD",
"SVD XT",
"Wan Video", "Wan Video",
"Wan Video 1.3B t2v", "Wan Video 1.3B t2v",
"Wan Video 14B t2v", "Wan Video 14B t2v",
@@ -368,11 +371,19 @@ DIFFUSION_MODEL_BASE_MODELS = frozenset(
"Wan Video 2.2 T2V-A14B", "Wan Video 2.2 T2V-A14B",
"Wan Video 2.5 T2V", "Wan Video 2.5 T2V",
"Wan Video 2.5 I2V", "Wan Video 2.5 I2V",
"Wan Video 2.7",
"Wan Video 3.0",
# Other diffusion models # Other diffusion models
"Boogu",
"Ernie", "Ernie",
"Ernie Turbo", "Ernie Turbo",
"HiDream-O1",
"Ming Image Design 0.1",
"Ming Image Design Layer 0.1",
"MiniMax H3",
"Nucleus", "Nucleus",
"Qwen", "Qwen",
"Wan Image 2.7",
"ZImageBase", "ZImageBase",
"ZImageTurbo", "ZImageTurbo",
# Krea 2 — loaded via UNETLoader in ComfyUI # Krea 2 — loaded via UNETLoader in ComfyUI
@@ -380,6 +391,53 @@ DIFFUSION_MODEL_BASE_MODELS = frozenset(
] ]
) )
# baseModel values from CivitAI that are true full checkpoints (loaded via
# CheckpointLoaderSimple in ComfyUI). New DiT families appear on CivitAI all
# the time, so download routing inverts the fallback: anything NOT in this
# closed set (and not a known diffusion model) is treated as a diffusion
# model by default (see py/services/download_routing.py). Cross-checked
# against CivitAI's official baseModelRecords (packages/civitai-shared
# src/basemodel.constants.ts), not only the download skip list.
# "Pony V7" is deliberately excluded: verified via the live API (model
# 1901521) to be AuraFlow-architecture shipping .gguf variants (UNETLoader),
# so it follows the unknown-base-model default (diffusion).
CHECKPOINT_BASE_MODELS = frozenset(
[
# Stable Diffusion 1.x
"SD 1.4",
"SD 1.5",
"SD 1.5 LCM",
"SD 1.5 Hyper",
# Stable Diffusion 2.x
"SD 2.0",
"SD 2.0 768",
"SD 2.1",
"SD 2.1 768",
"SD 2.1 Unclip",
# Stable Diffusion 3.x
"SD 3",
"SD 3.5",
"SD 3.5 Medium",
"SD 3.5 Large",
"SD 3.5 Large Turbo",
# SDXL and its full-checkpoint derivatives
"SDXL 0.9",
"SDXL 1.0",
"SDXL 1.0 LCM",
"SDXL Lightning",
"SDXL Hyper",
"SDXL Turbo",
"SDXL Distilled",
"Pony",
"Illustrious",
"NoobAI",
# Other full-checkpoint families
"Playground v2",
# Stable Cascade is unCLIP-style but loads via CheckpointLoader
"Stable Cascade",
]
)
# Supported baseModel values for download exclusion settings. # Supported baseModel values for download exclusion settings.
# Keep this aligned with static/js/utils/constants.js, excluding the generic "Other" value. # Keep this aligned with static/js/utils/constants.js, excluding the generic "Other" value.
SUPPORTED_DOWNLOAD_SKIP_BASE_MODELS = frozenset( SUPPORTED_DOWNLOAD_SKIP_BASE_MODELS = frozenset(
+5
View File
@@ -1053,6 +1053,11 @@ export class SettingsManager {
downloadBackendSelect.value = state.global.settings.download_backend || 'python'; downloadBackendSelect.value = state.global.settings.download_backend || 'python';
} }
const unknownBaseModelRoutingSelect = document.getElementById('unknownBaseModelRouting');
if (unknownBaseModelRoutingSelect) {
unknownBaseModelRoutingSelect.value = state.global.settings.unknown_base_model_routing || 'diffusion_model';
}
const aria2cPathInput = document.getElementById('aria2cPath'); const aria2cPathInput = document.getElementById('aria2cPath');
if (aria2cPathInput) { if (aria2cPathInput) {
aria2cPathInput.value = state.global.settings.aria2c_path || ''; aria2cPathInput.value = state.global.settings.aria2c_path || '';
+1
View File
@@ -60,6 +60,7 @@ const DEFAULT_SETTINGS_BASE = Object.freeze({
metadata_refresh_skip_paths: [], metadata_refresh_skip_paths: [],
skip_previously_downloaded_model_versions: false, skip_previously_downloaded_model_versions: false,
download_skip_base_models: [], download_skip_base_models: [],
unknown_base_model_routing: 'diffusion_model',
backup_auto_enabled: true, backup_auto_enabled: true,
backup_retention_count: 5, backup_retention_count: 5,
sidecar_storage_mode: 'alongside', sidecar_storage_mode: 'alongside',
@@ -26,6 +26,11 @@
{{ sm.setting_select('defaultUnetRoot', 'default_unet_root', 'settings.folderSettings.defaultUnetRoot', [], 'settings.folderSettings.defaultUnetRootHelp') }} {{ sm.setting_select('defaultUnetRoot', 'default_unet_root', 'settings.folderSettings.defaultUnetRoot', [], 'settings.folderSettings.defaultUnetRootHelp') }}
{{ sm.setting_select('unknownBaseModelRouting', 'unknown_base_model_routing', 'settings.unknownBaseModelRouting.label', [
('diffusion_model', 'settings.unknownBaseModelRouting.options.diffusionModel'),
('checkpoint', 'settings.unknownBaseModelRouting.options.checkpoint'),
], 'settings.unknownBaseModelRouting.help') }}
{{ sm.setting_select('defaultEmbeddingRoot', 'default_embedding_root', 'settings.folderSettings.defaultEmbeddingRoot', [], 'settings.folderSettings.defaultEmbeddingRootHelp') }} {{ sm.setting_select('defaultEmbeddingRoot', 'default_embedding_root', 'settings.folderSettings.defaultEmbeddingRoot', [], 'settings.folderSettings.defaultEmbeddingRootHelp') }}
{{ sm.setting_toggle('enableOtherModels', 'enable_other_models', 'settings.folderSettings.enableOtherModels', 'settings.folderSettings.enableOtherModelsHelp') }} {{ sm.setting_toggle('enableOtherModels', 'enable_other_models', 'settings.folderSettings.enableOtherModels', 'settings.folderSettings.enableOtherModelsHelp') }}
@@ -73,6 +73,57 @@ async def test_regular_checkpoint_stays_on_checkpoint_root():
assert payload["root_kind"] == "checkpoint" assert payload["root_kind"] == "checkpoint"
@pytest.mark.asyncio
async def test_unknown_base_model_routes_to_unet_by_default():
handler = DownloadRoutingHandler()
response = await handler.get_download_routing(
FakeRequest(
{
"model_type": "checkpoint",
"base_model": "Brand New Arch",
"file_types": ["Model"],
}
)
)
payload = json.loads(response.text)
assert payload["is_diffusion_model"] is True
assert payload["root_kind"] == "unet"
@pytest.mark.asyncio
async def test_unknown_base_model_honors_checkpoint_routing_setting():
get_settings_manager().settings["unknown_base_model_routing"] = "checkpoint"
handler = DownloadRoutingHandler()
response = await handler.get_download_routing(
FakeRequest(
{
"model_type": "checkpoint",
"base_model": "Brand New Arch",
"file_types": ["Model"],
}
)
)
payload = json.loads(response.text)
assert payload["is_diffusion_model"] is False
assert payload["root_kind"] == "checkpoint"
@pytest.mark.asyncio
async def test_unet_model_type_uses_checkpoint_branch():
"""CivitAI ModelType.UNet has no branch of its own: it goes through the
checkpoint branch, where its UNet-typed files route to the unet roots."""
handler = DownloadRoutingHandler()
response = await handler.get_download_routing(
FakeRequest(
{"model_type": "unet", "base_model": "MiniMax H3", "file_types": ["UNet"]}
)
)
payload = json.loads(response.text)
assert payload["is_diffusion_model"] is True
assert payload["root_kind"] == "unet"
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_lora_is_never_diffusion(): async def test_lora_is_never_diffusion():
handler = DownloadRoutingHandler() handler = DownloadRoutingHandler()
+49 -1
View File
@@ -2200,6 +2200,54 @@ async def test_download_uses_raw_file_name_from_mini_endpoint(
] ]
@pytest.mark.asyncio
async def test_unet_model_type_download_lands_in_unet_roots(
monkeypatch, scanners, metadata_provider, tmp_path
):
"""CivitAI ModelType.UNet goes through the checkpoint branch: its
UNet-typed files route to the unet roots like any diffusion model."""
manager = DownloadManager()
unet_root = tmp_path / "unet"
get_settings_manager().settings["default_unet_root"] = str(unet_root)
metadata_provider.payload = {
"id": 42,
"model": {"type": "UNet", "tags": ["fantasy"]},
"baseModel": "MiniMax H3",
"creator": {"username": "Author"},
"files": [
{
"id": 1001,
"type": "UNet",
"primary": True,
"name": "minimax_h3.safetensors",
"downloadUrl": "https://example.invalid/file.safetensors",
}
],
}
captured = {}
async def fake_execute_download(self, **kwargs):
captured["file_path"] = kwargs["metadata"].file_path
return {"success": True}
monkeypatch.setattr(
DownloadManager, "_execute_download", fake_execute_download, raising=False
)
result = await manager.download_from_civitai(
model_version_id=42,
save_dir=str(tmp_path),
use_default_paths=True,
progress_callback=None,
source=None,
)
assert result["success"] is True, result
assert captured["file_path"].startswith(str(unet_root))
assert captured["file_path"].endswith("minimax_h3.safetensors")
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_download_falls_back_to_rest_name_when_mini_fails( async def test_download_falls_back_to_rest_name_when_mini_fails(
monkeypatch, scanners, metadata_provider, tmp_path monkeypatch, scanners, metadata_provider, tmp_path
@@ -2209,7 +2257,7 @@ async def test_download_falls_back_to_rest_name_when_mini_fails(
metadata_provider.payload = { metadata_provider.payload = {
"id": 42, "id": 42,
"model": {"type": "Checkpoint", "tags": ["fantasy"]}, "model": {"type": "Checkpoint", "tags": ["fantasy"]},
"baseModel": "BaseModel", "baseModel": "SDXL 1.0",
"creator": {"username": "Author"}, "creator": {"username": "Author"},
"files": [ "files": [
{ {
+70 -5
View File
@@ -20,9 +20,68 @@ def test_base_model_fallback_routes_to_unet():
) )
def test_regular_checkpoint_stays_on_checkpoint_roots(): @pytest.mark.parametrize("base_model", ["SVD XT", "Boogu", "Wan Video 3.0", "Flux 3 Video"])
def test_new_diffusion_families_route_to_unet(base_model):
"""DiT families from CivitAI's official baseModel list that plain
"Model"-typed files must not strand on the checkpoint roots."""
assert is_diffusion_model_download(
"checkpoint", file_types=["Model"], base_model=base_model
)
def test_minimax_h3_routes_to_unet():
"""CivitAI model 2877206: type "Checkpoint", baseModel "MiniMax H3",
all file entries typed "Model"."""
assert is_diffusion_model_download(
"checkpoint", file_types=["Model"], base_model="MiniMax H3"
)
@pytest.mark.parametrize(
"base_model",
[
"SDXL 1.0",
"Illustrious",
"SD 1.5",
"SD 2.1 768",
"SD 3.5 Large",
"SDXL Turbo",
"Playground v2",
"Stable Cascade",
],
)
def test_known_checkpoint_base_models_stay_on_checkpoint_roots(base_model):
"""CHECKPOINT_BASE_MODELS members never route to unet, even when the
unknown-base-model default is diffusion."""
assert not is_diffusion_model_download( assert not is_diffusion_model_download(
"checkpoint", file_types=["Model"], base_model="SDXL 1.0" "checkpoint", file_types=["Model"], base_model=base_model
)
def test_unknown_base_model_defaults_to_unet():
"""New DiT families appear faster than the allowlist can track them, so
unknown baseModels route to unet by default."""
assert is_diffusion_model_download(
"checkpoint", file_types=["Model"], base_model="Brand New Arch"
)
def test_unknown_base_model_honors_checkpoint_setting():
assert not is_diffusion_model_download(
"checkpoint",
file_types=["Model"],
base_model="Brand New Arch",
unknown_base_model_default="checkpoint",
)
def test_unknown_base_model_checkpoint_setting_still_loses_to_file_type():
"""The file-type signal stays first regardless of the setting."""
assert is_diffusion_model_download(
"checkpoint",
file_types=["Diffusion Model"],
base_model="SDXL 1.0",
unknown_base_model_default="checkpoint",
) )
@@ -35,9 +94,15 @@ def test_non_checkpoint_types_never_route_to_unet():
) )
def test_empty_inputs_stay_on_checkpoint_roots(): def test_empty_base_model_follows_unknown_default():
assert not is_diffusion_model_download("checkpoint") assert is_diffusion_model_download("checkpoint")
assert not is_diffusion_model_download("checkpoint", file_types=[], base_model="") assert is_diffusion_model_download("checkpoint", file_types=[], base_model="")
assert not is_diffusion_model_download(
"checkpoint",
file_types=[],
base_model="",
unknown_base_model_default="checkpoint",
)
from py.services.download_routing import resolve_other_download_sub_type from py.services.download_routing import resolve_other_download_sub_type
+15
View File
@@ -1300,6 +1300,21 @@ def test_setting_download_skip_base_models_normalizes_string_input(manager):
assert manager.get("download_skip_base_models") == ["SDXL 1.0", "Pony"] assert manager.get("download_skip_base_models") == ["SDXL 1.0", "Pony"]
def test_unknown_base_model_routing_defaults_to_diffusion_model(manager):
assert manager.get("unknown_base_model_routing") == "diffusion_model"
def test_unknown_base_model_routing_is_normalized(manager):
manager.set("unknown_base_model_routing", "CHECKPOINT")
assert manager.get("unknown_base_model_routing") == "checkpoint"
manager.set("unknown_base_model_routing", "bogus")
assert manager.get("unknown_base_model_routing") == "diffusion_model"
manager.set("unknown_base_model_routing", None)
assert manager.get("unknown_base_model_routing") == "diffusion_model"
def test_skip_previously_downloaded_model_versions_defaults_false(manager): def test_skip_previously_downloaded_model_versions_defaults_false(manager):
assert manager.get_skip_previously_downloaded_model_versions() is False assert manager.get_skip_previously_downloaded_model_versions() is False