mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-09-20 18:51:26 -03:00
fix(recipes): allow download for version-only recipe LoRAs (no modelId/hash)
Page-imported recipes can carry an exact CivitAI modelVersionId but no
modelId and no hash (CivitAI exposes no sha256 for e.g. Krea versions).
canDownloadLora() required (modelId && versionId) or a hash, so such
entries were misclassified as unrepairable and offered Reconnect instead
of Download.
- canDownloadLora: treat a bare version id as downloadable (it uniquely
pins the file; the model id is resolved on demand at download time).
A model id without an exact version id stays non-downloadable to avoid
silently grabbing the latest version.
- resolveLoraDownloadIdentifiers: when a hash is absent but a version id
exists, resolve the owning model id via /civitai/model/version/{id}
(same endpoint the bulk download missing flow uses). Hash-only and
direct (modelId+versionId) paths are unchanged.
This commit is contained in:
@@ -153,6 +153,16 @@ const hashInvalidLora = {
|
||||
hashInvalid: true,
|
||||
};
|
||||
|
||||
// Mirrors the shape served for page-imported recipes whose CivitAI version
|
||||
// exposes no sha256: an exact modelVersionId but no modelId and no hash.
|
||||
const versionOnlyLora = {
|
||||
name: 'version-lora',
|
||||
modelName: 'Version Only LoRA',
|
||||
inLibrary: false,
|
||||
modelVersionId: 3221586,
|
||||
modelVersionName: 'V1 KREA-2',
|
||||
};
|
||||
|
||||
const recipeWithResources = {
|
||||
id: 'recipe-resources',
|
||||
file_path: '/recipes/resources.json',
|
||||
@@ -171,6 +181,7 @@ const recipeWithResources = {
|
||||
hashInvalidLora,
|
||||
{ name: 'mystery-lora', modelName: 'Mystery LoRA', inLibrary: false },
|
||||
hashOnlyLora,
|
||||
versionOnlyLora,
|
||||
],
|
||||
};
|
||||
|
||||
@@ -281,6 +292,57 @@ describe('RecipeModal resource item interactions', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('renders a download action (not reconnect) for a version-only LoRA', async () => {
|
||||
const recipeModal = await createRecipeModal();
|
||||
recipeModal.showRecipeDetails(recipeWithResources);
|
||||
await flushWiring();
|
||||
|
||||
const item = document.querySelector('[data-lora-index="6"]');
|
||||
expect(item).not.toBeNull();
|
||||
expect(item.classList.contains('missing-locally')).toBe(true);
|
||||
// Missing from the local library (badge) but still downloadable by its
|
||||
// exact CivitAI version id, so the row offers Download, not Reconnect.
|
||||
expect(item.querySelector('.missing-badge')).not.toBeNull();
|
||||
expect(item.querySelector('.lora-download')).not.toBeNull();
|
||||
expect(item.querySelector('.lora-reconnect')).toBeNull();
|
||||
});
|
||||
|
||||
it('downloads a version-only LoRA by resolving the model id from the version endpoint', async () => {
|
||||
const recipeModal = await createRecipeModal();
|
||||
const requests = [];
|
||||
// Isolated copy keeps mutations out of the shared fixture.
|
||||
const isolatedRecipe = JSON.parse(JSON.stringify(recipeWithResources));
|
||||
fetchRecipeDetailsMock.mockResolvedValue(isolatedRecipe);
|
||||
global.fetch = vi.fn(async (url) => {
|
||||
requests.push(String(url));
|
||||
if (String(url).includes('/civitai/model/version/3221586')) {
|
||||
return {
|
||||
ok: true,
|
||||
json: async () => ({ id: 3221586, modelId: 56789, name: 'V1 KREA-2' }),
|
||||
};
|
||||
}
|
||||
return { ok: true, json: async () => ({}) };
|
||||
});
|
||||
recipeModal.showRecipeDetails(isolatedRecipe);
|
||||
await flushWiring();
|
||||
|
||||
const item = document.querySelector('[data-lora-index="6"]');
|
||||
item.querySelector('.lora-download').click();
|
||||
|
||||
await vi.waitFor(() => {
|
||||
expect(downloadVersionWithDefaultsMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
expect(
|
||||
requests.some(u => u.includes('/civitai/model/version/3221586'))
|
||||
).toBe(true);
|
||||
expect(downloadVersionWithDefaultsMock).toHaveBeenCalledWith(
|
||||
'loras',
|
||||
56789,
|
||||
3221586,
|
||||
expect.objectContaining({ source: 'recipe-modal' })
|
||||
);
|
||||
});
|
||||
|
||||
it('does not navigate when a missing LoRA row is clicked', async () => {
|
||||
const recipeModal = await createRecipeModal();
|
||||
const navigateSpy = vi
|
||||
|
||||
Reference in New Issue
Block a user