feat(settings): skip previously downloaded model versions

This commit is contained in:
Will Miao
2026-04-03 19:01:19 +08:00
parent 33a7f07558
commit d36b16c213
18 changed files with 302 additions and 11 deletions

View File

@@ -341,6 +341,10 @@
"saveFailed": "Ausgeschlossene Basismodelle konnten nicht gespeichert werden: {message}" "saveFailed": "Ausgeschlossene Basismodelle konnten nicht gespeichert werden: {message}"
} }
}, },
"skipPreviouslyDownloadedModelVersions": {
"label": "Bereits heruntergeladene Modellversionen überspringen",
"help": "Wenn aktiviert, überspringt LoRA Manager den Download einer Modellversion, wenn der Download-Verlaufsdienst diese spezifische Version als bereits heruntergeladen erfasst hat. Gilt für alle Download-Abläufe."
},
"layoutSettings": { "layoutSettings": {
"displayDensity": "Anzeige-Dichte", "displayDensity": "Anzeige-Dichte",
"displayDensityOptions": { "displayDensityOptions": {
@@ -827,7 +831,7 @@
}, },
"contextMenu": { "contextMenu": {
"moveToOtherTypeFolder": "In {otherType}-Ordner verschieben", "moveToOtherTypeFolder": "In {otherType}-Ordner verschieben",
"sendToWorkflow": "[TODO: Translate] Send to Workflow" "sendToWorkflow": "An Workflow senden"
} }
}, },
"embeddings": { "embeddings": {

View File

@@ -325,7 +325,7 @@
}, },
"downloadSkipBaseModels": { "downloadSkipBaseModels": {
"label": "Skip downloads for base models", "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.", "help": "When enabled, versions using the selected base models will be skipped.",
"searchPlaceholder": "Filter base models...", "searchPlaceholder": "Filter base models...",
"empty": "No base models match the current search.", "empty": "No base models match the current search.",
"summary": { "summary": {
@@ -341,6 +341,10 @@
"saveFailed": "Unable to save excluded base models: {message}" "saveFailed": "Unable to save excluded base models: {message}"
} }
}, },
"skipPreviouslyDownloadedModelVersions": {
"label": "Skip previously downloaded model versions",
"help": "When enabled, versions downloaded before will be skipped."
},
"layoutSettings": { "layoutSettings": {
"displayDensity": "Display Density", "displayDensity": "Display Density",
"displayDensityOptions": { "displayDensityOptions": {

View File

@@ -341,6 +341,10 @@
"saveFailed": "No se pudieron guardar los modelos base excluidos: {message}" "saveFailed": "No se pudieron guardar los modelos base excluidos: {message}"
} }
}, },
"skipPreviouslyDownloadedModelVersions": {
"label": "Omitir versiones de modelos previamente descargadas",
"help": "Cuando está habilitado, LoRA Manager omitirá la descarga de una versión de modelo si el servicio de historial de descargas registra esa versión exacta como ya descargada. Aplica a todos los flujos de descarga."
},
"layoutSettings": { "layoutSettings": {
"displayDensity": "Densidad de visualización", "displayDensity": "Densidad de visualización",
"displayDensityOptions": { "displayDensityOptions": {
@@ -827,7 +831,7 @@
}, },
"contextMenu": { "contextMenu": {
"moveToOtherTypeFolder": "Mover a la carpeta {otherType}", "moveToOtherTypeFolder": "Mover a la carpeta {otherType}",
"sendToWorkflow": "[TODO: Translate] Send to Workflow" "sendToWorkflow": "Enviar al flujo de trabajo"
} }
}, },
"embeddings": { "embeddings": {

View File

@@ -341,6 +341,10 @@
"saveFailed": "Impossible denregistrer les modèles de base exclus : {message}" "saveFailed": "Impossible denregistrer les modèles de base exclus : {message}"
} }
}, },
"skipPreviouslyDownloadedModelVersions": {
"label": "Ignorer les versions de modèles précédemment téléchargées",
"help": "Lorsque activé, LoRA Manager ignorera le téléchargement d'une version de modèle si le service d'historique des téléchargements enregistre cette version exacte comme déjà téléchargée. S'applique à tous les flux de téléchargement."
},
"layoutSettings": { "layoutSettings": {
"displayDensity": "Densité d'affichage", "displayDensity": "Densité d'affichage",
"displayDensityOptions": { "displayDensityOptions": {
@@ -827,7 +831,7 @@
}, },
"contextMenu": { "contextMenu": {
"moveToOtherTypeFolder": "Déplacer vers le dossier {otherType}", "moveToOtherTypeFolder": "Déplacer vers le dossier {otherType}",
"sendToWorkflow": "[TODO: Translate] Send to Workflow" "sendToWorkflow": "Envoyer vers le workflow"
} }
}, },
"embeddings": { "embeddings": {

View File

@@ -341,6 +341,10 @@
"saveFailed": "לא ניתן לשמור את מודלי הבסיס המוחרגים: {message}" "saveFailed": "לא ניתן לשמור את מודלי הבסיס המוחרגים: {message}"
} }
}, },
"skipPreviouslyDownloadedModelVersions": {
"label": "דלג על גרסאות מודלים שהורדו בעבר",
"help": "כאשר מופעל, LoRA Manager ידלג על הורדת גרסת מודל אם שירות היסטוריית ההורדות רושם את הגרסה המדויקת הזו ככבר שהורדה. חל על כל תהליכי ההורדה."
},
"layoutSettings": { "layoutSettings": {
"displayDensity": "צפיפות תצוגה", "displayDensity": "צפיפות תצוגה",
"displayDensityOptions": { "displayDensityOptions": {
@@ -827,7 +831,7 @@
}, },
"contextMenu": { "contextMenu": {
"moveToOtherTypeFolder": "העבר לתיקיית {otherType}", "moveToOtherTypeFolder": "העבר לתיקיית {otherType}",
"sendToWorkflow": "[TODO: Translate] Send to Workflow" "sendToWorkflow": "שלח ל-workflow"
} }
}, },
"embeddings": { "embeddings": {

View File

@@ -341,6 +341,10 @@
"saveFailed": "除外するベースモデルを保存できませんでした: {message}" "saveFailed": "除外するベースモデルを保存できませんでした: {message}"
} }
}, },
"skipPreviouslyDownloadedModelVersions": {
"label": "以前にダウンロードしたモデルバージョンをスキップ",
"help": "有効にすると、ダウンロード履歴サービスがそのバージョンが既にダウンロード済みと記録している場合、LoRA Managerはそのモデルバージョンのダウンロードをスキップします。すべてのダウンロードフローに適用されます。"
},
"layoutSettings": { "layoutSettings": {
"displayDensity": "表示密度", "displayDensity": "表示密度",
"displayDensityOptions": { "displayDensityOptions": {
@@ -827,7 +831,7 @@
}, },
"contextMenu": { "contextMenu": {
"moveToOtherTypeFolder": "{otherType} フォルダに移動", "moveToOtherTypeFolder": "{otherType} フォルダに移動",
"sendToWorkflow": "[TODO: Translate] Send to Workflow" "sendToWorkflow": "ワークフローに送信"
} }
}, },
"embeddings": { "embeddings": {

View File

@@ -341,6 +341,10 @@
"saveFailed": "제외된 기본 모델을 저장할 수 없습니다: {message}" "saveFailed": "제외된 기본 모델을 저장할 수 없습니다: {message}"
} }
}, },
"skipPreviouslyDownloadedModelVersions": {
"label": "이전에 다운로드한 모델 버전 건너뛰기",
"help": "활성화하면 다운로드 기록 서비스가 해당 버전이 이미 다운로드되었음을 기록한 경우 LoRA Manager는 해당 모델 버전 다운로드를 건너뜁니다. 모든 다운로드 플로우에 적용됩니다."
},
"layoutSettings": { "layoutSettings": {
"displayDensity": "표시 밀도", "displayDensity": "표시 밀도",
"displayDensityOptions": { "displayDensityOptions": {
@@ -827,7 +831,7 @@
}, },
"contextMenu": { "contextMenu": {
"moveToOtherTypeFolder": "{otherType} 폴더로 이동", "moveToOtherTypeFolder": "{otherType} 폴더로 이동",
"sendToWorkflow": "[TODO: Translate] Send to Workflow" "sendToWorkflow": "워크플로우로 전송"
} }
}, },
"embeddings": { "embeddings": {

View File

@@ -341,6 +341,10 @@
"saveFailed": "Не удалось сохранить исключённые базовые модели: {message}" "saveFailed": "Не удалось сохранить исключённые базовые модели: {message}"
} }
}, },
"skipPreviouslyDownloadedModelVersions": {
"label": "Пропускать ранее загруженные версии моделей",
"help": "Если включено, LoRA Manager будет пропускать загрузку версии модели, если сервис истории загрузок записал, что эта конкретная версия уже загружена. Применяется ко всем потокам загрузки."
},
"layoutSettings": { "layoutSettings": {
"displayDensity": "Плотность отображения", "displayDensity": "Плотность отображения",
"displayDensityOptions": { "displayDensityOptions": {
@@ -827,7 +831,7 @@
}, },
"contextMenu": { "contextMenu": {
"moveToOtherTypeFolder": "Переместить в папку {otherType}", "moveToOtherTypeFolder": "Переместить в папку {otherType}",
"sendToWorkflow": "[TODO: Translate] Send to Workflow" "sendToWorkflow": "Отправить в workflow"
} }
}, },
"embeddings": { "embeddings": {

View File

@@ -341,6 +341,10 @@
"saveFailed": "无法保存已排除的基础模型:{message}" "saveFailed": "无法保存已排除的基础模型:{message}"
} }
}, },
"skipPreviouslyDownloadedModelVersions": {
"label": "跳过已下载的模型版本",
"help": "启用后如果下载历史服务记录显示该版本已下载LoRA Manager 将跳过下载该模型版本。适用于所有下载流程。"
},
"layoutSettings": { "layoutSettings": {
"displayDensity": "显示密度", "displayDensity": "显示密度",
"displayDensityOptions": { "displayDensityOptions": {
@@ -827,7 +831,7 @@
}, },
"contextMenu": { "contextMenu": {
"moveToOtherTypeFolder": "移动到 {otherType} 文件夹", "moveToOtherTypeFolder": "移动到 {otherType} 文件夹",
"sendToWorkflow": "[TODO: Translate] Send to Workflow" "sendToWorkflow": "发送到工作流"
} }
}, },
"embeddings": { "embeddings": {

View File

@@ -341,6 +341,10 @@
"saveFailed": "無法儲存已排除的基礎模型:{message}" "saveFailed": "無法儲存已排除的基礎模型:{message}"
} }
}, },
"skipPreviouslyDownloadedModelVersions": {
"label": "跳過已下載的模型版本",
"help": "啟用後如果下載歷史服務記錄顯示該版本已下載LoRA Manager 將跳過下載該模型版本。適用於所有下載流程。"
},
"layoutSettings": { "layoutSettings": {
"displayDensity": "顯示密度", "displayDensity": "顯示密度",
"displayDensityOptions": { "displayDensityOptions": {
@@ -827,7 +831,7 @@
}, },
"contextMenu": { "contextMenu": {
"moveToOtherTypeFolder": "移動到 {otherType} 資料夾", "moveToOtherTypeFolder": "移動到 {otherType} 資料夾",
"sendToWorkflow": "[TODO: Translate] Send to Workflow" "sendToWorkflow": "傳送到工作流"
} }
}, },
"embeddings": { "embeddings": {

View File

@@ -64,6 +64,19 @@ class DownloadManager:
"""Get the checkpoint scanner from registry""" """Get the checkpoint scanner from registry"""
return await ServiceRegistry.get_checkpoint_scanner() return await ServiceRegistry.get_checkpoint_scanner()
async def _has_been_downloaded(self, model_type: str, model_version_id: int) -> bool:
try:
history_service = await ServiceRegistry.get_downloaded_version_history_service()
return await history_service.has_been_downloaded(model_type, model_version_id)
except Exception as exc:
logger.debug(
"Failed to read download history for %s version %s: %s",
model_type,
model_version_id,
exc,
)
return False
async def download_from_civitai( async def download_from_civitai(
self, self,
model_id: int = None, model_id: int = None,
@@ -355,6 +368,57 @@ class DownloadManager:
"error": f'Model type "{model_type_from_info}" is not supported for download', "error": f'Model type "{model_type_from_info}" is not supported for download',
} }
resolved_version_id = model_version_id
raw_version_id = version_info.get("id")
if resolved_version_id is None and raw_version_id is not None:
try:
resolved_version_id = int(raw_version_id)
except (TypeError, ValueError):
resolved_version_id = None
if (
get_settings_manager().get_skip_previously_downloaded_model_versions()
and resolved_version_id is not None
and await self._has_been_downloaded(model_type, resolved_version_id)
):
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:{resolved_version_id}'}' "
f"because version {resolved_version_id} was already downloaded before"
)
logger.info(message)
return {
"success": True,
"skipped": True,
"status": "skipped",
"reason": "previously_downloaded_version",
"message": message,
"model_version_id": resolved_version_id,
"file_name": file_name,
"download_id": download_id,
}
excluded_base_models = get_settings_manager().get_download_skip_base_models() excluded_base_models = get_settings_manager().get_download_skip_base_models()
base_model_value = version_info.get("baseModel", "") base_model_value = version_info.get("baseModel", "")
if ( if (

View File

@@ -91,6 +91,7 @@ DEFAULT_SETTINGS: Dict[str, Any] = {
"update_flag_strategy": "same_base", "update_flag_strategy": "same_base",
"auto_organize_exclusions": [], "auto_organize_exclusions": [],
"metadata_refresh_skip_paths": [], "metadata_refresh_skip_paths": [],
"skip_previously_downloaded_model_versions": False,
"download_skip_base_models": [], "download_skip_base_models": [],
} }
@@ -314,6 +315,10 @@ class SettingsManager:
self.settings["download_skip_base_models"] = [] self.settings["download_skip_base_models"] = []
inserted_defaults = True inserted_defaults = True
if "skip_previously_downloaded_model_versions" not in self.settings:
self.settings["skip_previously_downloaded_model_versions"] = False
inserted_defaults = True
had_mature_level = "mature_blur_level" in self.settings had_mature_level = "mature_blur_level" in self.settings
raw_mature_level = self.settings.get("mature_blur_level") raw_mature_level = self.settings.get("mature_blur_level")
normalized_mature_level = self.normalize_mature_blur_level(raw_mature_level) normalized_mature_level = self.normalize_mature_blur_level(raw_mature_level)
@@ -1090,6 +1095,17 @@ class SettingsManager:
self._save_settings() self._save_settings()
return base_models return base_models
def get_skip_previously_downloaded_model_versions(self) -> bool:
value = self.settings.get("skip_previously_downloaded_model_versions", False)
if isinstance(value, bool):
return value
normalized = False
if isinstance(value, str):
normalized = value.strip().lower() in {"1", "true", "yes", "on"}
self.settings["skip_previously_downloaded_model_versions"] = normalized
self._save_settings()
return normalized
def get_extra_folder_paths(self) -> Dict[str, List[str]]: def get_extra_folder_paths(self) -> Dict[str, List[str]]:
"""Get extra folder paths for the active library. """Get extra folder paths for the active library.

View File

@@ -146,6 +146,10 @@ export class SettingsManager {
backendSettings?.metadata_refresh_skip_paths ?? defaults.metadata_refresh_skip_paths backendSettings?.metadata_refresh_skip_paths ?? defaults.metadata_refresh_skip_paths
); );
merged.skip_previously_downloaded_model_versions =
backendSettings?.skip_previously_downloaded_model_versions
?? defaults.skip_previously_downloaded_model_versions;
merged.download_skip_base_models = this.normalizeDownloadSkipBaseModels( merged.download_skip_base_models = this.normalizeDownloadSkipBaseModels(
backendSettings?.download_skip_base_models ?? defaults.download_skip_base_models backendSettings?.download_skip_base_models ?? defaults.download_skip_base_models
); );
@@ -836,6 +840,12 @@ export class SettingsManager {
hideEarlyAccessUpdatesCheckbox.checked = state.global.settings.hide_early_access_updates || false; hideEarlyAccessUpdatesCheckbox.checked = state.global.settings.hide_early_access_updates || false;
} }
const skipPreviouslyDownloadedModelVersionsCheckbox = document.getElementById('skipPreviouslyDownloadedModelVersions');
if (skipPreviouslyDownloadedModelVersionsCheckbox) {
skipPreviouslyDownloadedModelVersionsCheckbox.checked =
state.global.settings.skip_previously_downloaded_model_versions || false;
}
// Set optimize example images setting // Set optimize example images setting
const optimizeExampleImagesCheckbox = document.getElementById('optimizeExampleImages'); const optimizeExampleImagesCheckbox = document.getElementById('optimizeExampleImages');
if (optimizeExampleImagesCheckbox) { if (optimizeExampleImagesCheckbox) {

View File

@@ -38,6 +38,7 @@ const DEFAULT_SETTINGS_BASE = Object.freeze({
hide_early_access_updates: false, hide_early_access_updates: false,
auto_organize_exclusions: [], auto_organize_exclusions: [],
metadata_refresh_skip_paths: [], metadata_refresh_skip_paths: [],
skip_previously_downloaded_model_versions: false,
download_skip_base_models: [], download_skip_base_models: [],
}); });

View File

@@ -735,6 +735,24 @@
</div> </div>
</div> </div>
<div class="setting-item">
<div class="setting-row">
<div class="setting-info">
<label for="skipPreviouslyDownloadedModelVersions">
{{ t('settings.skipPreviouslyDownloadedModelVersions.label') }}
<i class="fas fa-info-circle info-icon" data-tooltip="{{ t('settings.skipPreviouslyDownloadedModelVersions.help') }}"></i>
</label>
</div>
<div class="setting-control">
<label class="toggle-switch">
<input type="checkbox" id="skipPreviouslyDownloadedModelVersions"
onchange="settingsManager.saveToggleSetting('skipPreviouslyDownloadedModelVersions', 'skip_previously_downloaded_model_versions')">
<span class="toggle-slider"></span>
</label>
</div>
</div>
</div>
<div class="setting-item"> <div class="setting-item">
<div class="setting-row"> <div class="setting-row">
<div class="setting-info"> <div class="setting-info">

View File

@@ -20,6 +20,7 @@ vi.mock('../../../static/js/state/index.js', () => {
}, },
createDefaultSettings: () => ({ createDefaultSettings: () => ({
language: 'en', language: 'en',
skip_previously_downloaded_model_versions: false,
download_skip_base_models: [], download_skip_base_models: [],
}), }),
}; };
@@ -117,6 +118,7 @@ describe('SettingsManager download skip base models UI', () => {
document.body.innerHTML = ''; document.body.innerHTML = '';
vi.clearAllMocks(); vi.clearAllMocks();
state.global.settings = { state.global.settings = {
skip_previously_downloaded_model_versions: false,
download_skip_base_models: [], download_skip_base_models: [],
}; };
}); });
@@ -150,4 +152,31 @@ describe('SettingsManager download skip base models UI', () => {
expect(document.querySelectorAll('#downloadSkipBaseModelsContainer input')).toHaveLength(0); expect(document.querySelectorAll('#downloadSkipBaseModelsContainer input')).toHaveLength(0);
expect(document.getElementById('downloadSkipBaseModelsEmpty').hidden).toBe(false); expect(document.getElementById('downloadSkipBaseModelsEmpty').hidden).toBe(false);
}); });
it('initializes the previously-downloaded-version toggle from settings', () => {
document.body.innerHTML = '<input id="skipPreviouslyDownloadedModelVersions" type="checkbox" />';
state.global.settings.skip_previously_downloaded_model_versions = true;
const manager = createManager();
manager.loadSettingsToUI();
expect(document.getElementById('skipPreviouslyDownloadedModelVersions').checked).toBe(true);
});
it('saves the previously-downloaded-version toggle with the expected setting key', async () => {
document.body.innerHTML = '<input id="skipPreviouslyDownloadedModelVersions" type="checkbox" checked />';
const manager = createManager();
manager.saveSetting = vi.fn().mockResolvedValue();
manager.applyFrontendSettings = vi.fn();
await manager.saveToggleSetting(
'skipPreviouslyDownloadedModelVersions',
'skip_previously_downloaded_model_versions',
);
expect(manager.saveSetting).toHaveBeenCalledWith(
'skip_previously_downloaded_model_versions',
true,
);
});
}); });

View File

@@ -38,6 +38,7 @@ def isolate_settings(monkeypatch, tmp_path):
"embedding": "{base_model}/{first_tag}", "embedding": "{base_model}/{first_tag}",
}, },
"base_model_path_mappings": {"BaseModel": "MappedModel"}, "base_model_path_mappings": {"BaseModel": "MappedModel"},
"skip_previously_downloaded_model_versions": False,
"download_skip_base_models": [], "download_skip_base_models": [],
} }
) )
@@ -454,7 +455,7 @@ async def test_download_skips_excluded_base_model(monkeypatch, scanners, metadat
metadata_provider.get_model_version = AsyncMock( metadata_provider.get_model_version = AsyncMock(
return_value={ return_value={
"id": 42, "id": 99,
"model": {"type": "LoRA", "tags": ["fantasy"]}, "model": {"type": "LoRA", "tags": ["fantasy"]},
"baseModel": "SDXL 1.0", "baseModel": "SDXL 1.0",
"creator": {"username": "Author"}, "creator": {"username": "Author"},
@@ -490,3 +491,104 @@ async def test_download_skips_excluded_base_model(monkeypatch, scanners, metadat
assert "file.safetensors" in result["message"] assert "file.safetensors" in result["message"]
execute_download.assert_not_called() execute_download.assert_not_called()
assert manager._active_downloads[result["download_id"]]["status"] == "skipped" assert manager._active_downloads[result["download_id"]]["status"] == "skipped"
@pytest.mark.asyncio
async def test_download_skips_previously_downloaded_version(monkeypatch, scanners, metadata_provider):
manager = DownloadManager()
get_settings_manager().settings["skip_previously_downloaded_model_versions"] = True
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",
}
],
}
)
history_service = AsyncMock()
history_service.has_been_downloaded = AsyncMock(return_value=True)
monkeypatch.setattr(
ServiceRegistry,
"get_downloaded_version_history_service",
AsyncMock(return_value=history_service),
)
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"] == "previously_downloaded_version"
assert result["model_version_id"] == 99
assert result["file_name"] == "file.safetensors"
history_service.has_been_downloaded.assert_awaited_once_with("lora", 99)
execute_download.assert_not_called()
assert manager._active_downloads[result["download_id"]]["status"] == "skipped"
@pytest.mark.asyncio
async def test_download_proceeds_when_history_skip_disabled(monkeypatch, scanners, metadata_provider):
manager = DownloadManager()
get_settings_manager().settings["skip_previously_downloaded_model_versions"] = False
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",
}
],
}
)
history_service = AsyncMock()
history_service.has_been_downloaded = AsyncMock(return_value=True)
monkeypatch.setattr(
ServiceRegistry,
"get_downloaded_version_history_service",
AsyncMock(return_value=history_service),
)
execute_download = AsyncMock(return_value={"success": True, "download_id": "done"})
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.get("skipped") is not True
history_service.has_been_downloaded.assert_not_called()
execute_download.assert_awaited_once()

View File

@@ -829,3 +829,14 @@ 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") 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"] assert manager.get("download_skip_base_models") == ["SDXL 1.0", "Pony"]
def test_skip_previously_downloaded_model_versions_defaults_false(manager):
assert manager.get_skip_previously_downloaded_model_versions() is False
def test_skip_previously_downloaded_model_versions_coerces_string_input(manager):
manager.settings["skip_previously_downloaded_model_versions"] = "true"
assert manager.get_skip_previously_downloaded_model_versions() is True
assert manager.settings["skip_previously_downloaded_model_versions"] is True