mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-05-06 16:36:45 -03:00
fix(recipes): preserve legacy gen params in modal flows
This commit is contained in:
@@ -1165,6 +1165,113 @@ describe('Interaction-level regression coverage', () => {
|
||||
expect(otherParamsText).not.toContain('cfg_scale');
|
||||
});
|
||||
|
||||
it('filters dirty generation params from recipe modal display', async () => {
|
||||
document.body.innerHTML = `
|
||||
<div id="recipeModal" class="modal">
|
||||
<div id="recipeModalTitle"></div>
|
||||
<div id="recipePreviewContainer"></div>
|
||||
<div id="recipeTagsCompact"></div>
|
||||
<div id="recipeTagsTooltip"><div id="recipeTagsTooltipContent"></div></div>
|
||||
<div id="recipePrompt"></div>
|
||||
<textarea id="recipePromptInput"></textarea>
|
||||
<div id="recipeNegativePrompt"></div>
|
||||
<textarea id="recipeNegativePromptInput"></textarea>
|
||||
<div class="other-params" id="recipeOtherParams"></div>
|
||||
<div id="recipeCheckpoint"></div>
|
||||
<div id="recipeResourceDivider"></div>
|
||||
<div id="recipeLorasList"></div>
|
||||
<span id="recipeLorasCount"></span>
|
||||
<button id="viewRecipeLorasBtn"></button>
|
||||
<button id="copyRecipeSyntaxBtn"></button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
const { RecipeModal } = await import('../../../static/js/components/RecipeModal.js');
|
||||
const recipeModal = new RecipeModal();
|
||||
|
||||
recipeModal.showRecipeDetails({
|
||||
id: '',
|
||||
file_path: '/recipes/dirty-gen-params.json',
|
||||
title: 'Dirty Gen Params Recipe',
|
||||
tags: [],
|
||||
file_url: '',
|
||||
preview_url: '',
|
||||
source_path: '',
|
||||
gen_params: {
|
||||
Prompt: 'visible prompt',
|
||||
negativePrompt: 'visible negative',
|
||||
Sampler: 'euler',
|
||||
cfgScale: 7,
|
||||
Version: 'ComfyUI',
|
||||
raw_metadata: { prompt: 'hidden prompt' },
|
||||
RNG: 'cpu',
|
||||
},
|
||||
loras: [],
|
||||
});
|
||||
|
||||
const otherParamsText = document.getElementById('recipeOtherParams').textContent;
|
||||
expect(document.getElementById('recipePrompt').textContent).toContain('visible prompt');
|
||||
expect(document.getElementById('recipeNegativePrompt').textContent).toContain('visible negative');
|
||||
expect(otherParamsText).toContain('sampler:');
|
||||
expect(otherParamsText).toContain('cfg_scale:');
|
||||
expect(otherParamsText).not.toContain('Version');
|
||||
expect(otherParamsText).not.toContain('raw_metadata');
|
||||
expect(otherParamsText).not.toContain('RNG');
|
||||
});
|
||||
|
||||
it('prefers canonical generation params over legacy aliases in modal display', async () => {
|
||||
document.body.innerHTML = `
|
||||
<div id="recipeModal" class="modal">
|
||||
<div id="recipeModalTitle"></div>
|
||||
<div id="recipePreviewContainer"></div>
|
||||
<div id="recipeTagsCompact"></div>
|
||||
<div id="recipeTagsTooltip"><div id="recipeTagsTooltipContent"></div></div>
|
||||
<div id="recipePrompt"></div>
|
||||
<textarea id="recipePromptInput"></textarea>
|
||||
<div id="recipeNegativePrompt"></div>
|
||||
<textarea id="recipeNegativePromptInput"></textarea>
|
||||
<div class="other-params" id="recipeOtherParams"></div>
|
||||
<div id="recipeCheckpoint"></div>
|
||||
<div id="recipeResourceDivider"></div>
|
||||
<div id="recipeLorasList"></div>
|
||||
<span id="recipeLorasCount"></span>
|
||||
<button id="viewRecipeLorasBtn"></button>
|
||||
<button id="copyRecipeSyntaxBtn"></button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
const { RecipeModal } = await import('../../../static/js/components/RecipeModal.js');
|
||||
const recipeModal = new RecipeModal();
|
||||
|
||||
recipeModal.showRecipeDetails({
|
||||
id: '',
|
||||
file_path: '/recipes/canonical-wins.json',
|
||||
title: 'Canonical Wins Recipe',
|
||||
tags: [],
|
||||
file_url: '',
|
||||
preview_url: '',
|
||||
source_path: '',
|
||||
gen_params: {
|
||||
Prompt: 'stale prompt',
|
||||
prompt: 'fresh prompt',
|
||||
negativePrompt: 'stale negative',
|
||||
negative_prompt: 'fresh negative',
|
||||
cfgScale: 3,
|
||||
cfg_scale: 7,
|
||||
},
|
||||
loras: [],
|
||||
});
|
||||
|
||||
const otherParamsText = document.getElementById('recipeOtherParams').textContent;
|
||||
expect(document.getElementById('recipePrompt').textContent).toContain('fresh prompt');
|
||||
expect(document.getElementById('recipePrompt').textContent).not.toContain('stale prompt');
|
||||
expect(document.getElementById('recipeNegativePrompt').textContent).toContain('fresh negative');
|
||||
expect(document.getElementById('recipeNegativePrompt').textContent).not.toContain('stale negative');
|
||||
expect(otherParamsText).toContain('cfg_scale:');
|
||||
expect(otherParamsText).toContain('7');
|
||||
expect(otherParamsText).not.toContain('3');
|
||||
});
|
||||
|
||||
it('replaces cached checkpoint and loras with hydrated resources', async () => {
|
||||
fetchRecipeDetailsMock.mockResolvedValueOnce({
|
||||
id: 'recipe-resources',
|
||||
@@ -1854,6 +1961,8 @@ describe('Interaction-level regression coverage', () => {
|
||||
negative_prompt: 'keep negative',
|
||||
steps: 30,
|
||||
cfg_scale: 7,
|
||||
raw_metadata: { prompt: 'preserve me' },
|
||||
Version: 'ComfyUI',
|
||||
},
|
||||
loras: [],
|
||||
});
|
||||
@@ -1882,6 +1991,8 @@ describe('Interaction-level regression coverage', () => {
|
||||
negative_prompt: 'keep negative',
|
||||
steps: 30,
|
||||
cfg_scale: 7,
|
||||
raw_metadata: { prompt: 'preserve me' },
|
||||
Version: 'ComfyUI',
|
||||
},
|
||||
},
|
||||
{ listFilePath: '/recipes/prompt.json' }
|
||||
|
||||
@@ -402,6 +402,61 @@ async def test_get_recipe_by_id_merges_recipe_json_details(recipe_scanner):
|
||||
assert recipe["gen_params"]["prompt"] == "prompt from json"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_recipe_by_id_normalizes_gen_params_aliases_without_dropping_metadata(
|
||||
recipe_scanner,
|
||||
):
|
||||
scanner, _ = recipe_scanner
|
||||
recipes_dir = Path(scanner.recipes_dir)
|
||||
recipe_id = "dirty-json-gen-params"
|
||||
recipe_json_path = recipes_dir / f"{recipe_id}.recipe.json"
|
||||
recipe_json_path.write_text(
|
||||
json.dumps(
|
||||
{
|
||||
"id": recipe_id,
|
||||
"file_path": "/tmp/dirty-json-gen-params.png",
|
||||
"title": "Dirty Recipe",
|
||||
"gen_params": {
|
||||
"Prompt": "prompt from json",
|
||||
"negativePrompt": "negative from json",
|
||||
"cfgScale": 7,
|
||||
"raw_metadata": {"prompt": "nested"},
|
||||
"Version": "ComfyUI",
|
||||
"RNG": "cpu",
|
||||
},
|
||||
"loras": [],
|
||||
}
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
scanner._cache.raw_data = [
|
||||
{
|
||||
"id": recipe_id,
|
||||
"file_path": "/tmp/dirty-json-gen-params.png",
|
||||
"title": "Cached Recipe",
|
||||
"folder": "",
|
||||
"modified": 0.0,
|
||||
"created_date": 0.0,
|
||||
"loras": [],
|
||||
"gen_params": {"prompt": "cached prompt", "raw_metadata": {"bad": True}},
|
||||
}
|
||||
]
|
||||
|
||||
recipe = await scanner.get_recipe_by_id(recipe_id)
|
||||
|
||||
assert recipe is not None
|
||||
assert recipe["gen_params"]["Prompt"] == "prompt from json"
|
||||
assert recipe["gen_params"]["negativePrompt"] == "negative from json"
|
||||
assert recipe["gen_params"]["cfgScale"] == 7
|
||||
assert recipe["gen_params"]["raw_metadata"] == {"prompt": "nested"}
|
||||
assert recipe["gen_params"]["Version"] == "ComfyUI"
|
||||
assert recipe["gen_params"]["RNG"] == "cpu"
|
||||
assert recipe["gen_params"]["prompt"] == "prompt from json"
|
||||
assert recipe["gen_params"]["negative_prompt"] == "negative from json"
|
||||
assert recipe["gen_params"]["cfg_scale"] == 7
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_recipe_by_id_prefers_json_file_path(recipe_scanner):
|
||||
scanner, _ = recipe_scanner
|
||||
@@ -528,6 +583,40 @@ async def test_get_paginated_data_filters_by_checkpoint_hash(recipe_scanner):
|
||||
assert [item["id"] for item in result["items"]] == ["checkpoint-match"]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_paginated_data_normalizes_gen_params_aliases_without_dropping_metadata(
|
||||
recipe_scanner,
|
||||
):
|
||||
scanner, _ = recipe_scanner
|
||||
await scanner.add_recipe(
|
||||
{
|
||||
"id": "dirty-listing",
|
||||
"file_path": str(Path(config.loras_roots[0]) / "dirty-listing.webp"),
|
||||
"title": "Dirty Listing",
|
||||
"modified": 0.0,
|
||||
"created_date": 0.0,
|
||||
"loras": [],
|
||||
"gen_params": {
|
||||
"Prompt": "a beautiful forest landscape",
|
||||
"cfgScale": 7,
|
||||
"Version": "ComfyUI",
|
||||
"raw_metadata": {"bad": True},
|
||||
},
|
||||
}
|
||||
)
|
||||
await asyncio.sleep(0)
|
||||
|
||||
result = await scanner.get_paginated_data(page=1, page_size=10)
|
||||
item = next(entry for entry in result["items"] if entry["id"] == "dirty-listing")
|
||||
|
||||
assert item["gen_params"]["Prompt"] == "a beautiful forest landscape"
|
||||
assert item["gen_params"]["cfgScale"] == 7
|
||||
assert item["gen_params"]["Version"] == "ComfyUI"
|
||||
assert item["gen_params"]["raw_metadata"] == {"bad": True}
|
||||
assert item["gen_params"]["prompt"] == "a beautiful forest landscape"
|
||||
assert item["gen_params"]["cfg_scale"] == 7
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_recipes_for_checkpoint_matches_hash_case_insensitively(recipe_scanner):
|
||||
scanner, _ = recipe_scanner
|
||||
|
||||
Reference in New Issue
Block a user