feat: add "Respect Recommended Strength" feature to LoRA Randomizer

Add support for respecting recommended strength values from LoRA usage_tips
when randomizing LoRA selection.

Features:
- New toggle setting to enable/disable recommended strength respect (default off)
- Scale range slider (0-2, default 0.5-1.0) to adjust recommended values
- Uses recommended strength × random(scale) when feature enabled
- Fallbacks to original Model/Clip Strength range when no recommendation exists
- Clip strength recommendations only apply when using Custom Range mode

Backend changes:
- Parse usage_tips JSON string to extract strength/clipStrength
- Apply scale factor to recommended values during randomization
- Pass new parameters through API route and node

Frontend changes:
- Update RandomizerConfig type with new properties
- Add new UI section with toggle and dual-range slider
- Wire up state management and event handlers
- No layout shift (removed description text)

Tests:
- Add tests for enabled/disabled recommended strength in API routes
- Add test verifying config passed to service
- All existing tests pass

Build: Include compiled Vue widgets
This commit is contained in:
Will Miao
2026-01-14 16:34:24 +08:00
parent 4951ff358e
commit fc8240e99e
12 changed files with 441 additions and 85 deletions

View File

@@ -51,6 +51,9 @@ def randomizer_config_fixed():
"clip_strength_min": 0.5,
"clip_strength_max": 1.0,
"roll_mode": "fixed",
"use_recommended_strength": False,
"recommended_strength_scale_min": 0.5,
"recommended_strength_scale_max": 1.0,
}
@@ -68,6 +71,9 @@ def randomizer_config_always():
"clip_strength_min": 0.5,
"clip_strength_max": 1.0,
"roll_mode": "always",
"use_recommended_strength": False,
"recommended_strength_scale_min": 0.5,
"recommended_strength_scale_max": 1.0,
}
@@ -319,3 +325,81 @@ async def test_execution_stack_always_from_input_loras_not_ui_loras(
assert execution_stack[0][2] == 0.8
assert execution_stack[1][1] == 0.6
assert execution_stack[1][2] == 0.6
@pytest.fixture
def randomizer_config_with_recommended_strength():
"""Randomizer config with recommended strength enabled"""
return {
"count_mode": "fixed",
"count_fixed": 3,
"count_min": 2,
"count_max": 5,
"model_strength_min": 0.5,
"model_strength_max": 1.0,
"use_same_clip_strength": True,
"clip_strength_min": 0.5,
"clip_strength_max": 1.0,
"roll_mode": "always",
"use_recommended_strength": True,
"recommended_strength_scale_min": 0.6,
"recommended_strength_scale_max": 0.8,
}
@pytest.mark.asyncio
async def test_recommended_strength_config_passed_to_service(
randomizer_node,
sample_loras,
randomizer_config_with_recommended_strength,
mock_scanner,
monkeypatch,
):
"""Test that recommended strength config is passed to service when enabled"""
from py.services.lora_service import LoraService
from unittest.mock import AsyncMock, patch
# Mock LoraService.get_random_loras to verify parameters
mock_get_random_loras = AsyncMock(
return_value=[
{
"name": "new_lora.safetensors",
"strength": 0.7,
"clipStrength": 0.7,
"active": True,
"expanded": False,
"locked": False,
}
]
)
with patch.object(LoraService, "__init__", return_value=None):
with patch.object(LoraService, "get_random_loras", mock_get_random_loras):
monkeypatch.setattr(
service_registry.ServiceRegistry,
"get_lora_scanner",
AsyncMock(return_value=mock_scanner),
)
mock_scanner._cache.raw_data = [
{
"file_name": "new_lora.safetensors",
"file_path": "/path/to/new_lora.safetensors",
"folder": "",
}
]
result = await randomizer_node.randomize(
randomizer_config_with_recommended_strength,
sample_loras,
pool_config=None,
)
# Verify service was called
assert mock_get_random_loras.called
# Verify recommended strength parameters were passed
call_kwargs = mock_get_random_loras.call_args[1]
assert call_kwargs["use_recommended_strength"] is True
assert call_kwargs["recommended_strength_scale_min"] == 0.6
assert call_kwargs["recommended_strength_scale_max"] == 0.8

View File

@@ -21,8 +21,10 @@ class StubLoraService:
def __init__(self):
self.random_loras = []
self.last_get_random_loras_kwargs = {}
async def get_random_loras(self, **kwargs):
self.last_get_random_loras_kwargs = kwargs
return self.random_loras
@@ -201,3 +203,56 @@ async def test_get_random_loras_error(routes, monkeypatch):
assert response.status == 500
assert payload["success"] is False
assert "error" in payload
async def test_get_random_loras_with_recommended_strength_enabled(routes):
"""Test random LoRAs with recommended strength feature enabled"""
request = DummyRequest(
json_data={
"count": 5,
"model_strength_min": 0.5,
"model_strength_max": 1.0,
"use_same_clip_strength": True,
"use_recommended_strength": True,
"recommended_strength_scale_min": 0.6,
"recommended_strength_scale_max": 0.8,
"locked_loras": [],
}
)
response = await routes.get_random_loras(request)
payload = json.loads(response.text)
assert response.status == 200
assert payload["success"] is True
# Verify parameters were passed to service
kwargs = routes.service.last_get_random_loras_kwargs
assert kwargs["use_recommended_strength"] is True
assert kwargs["recommended_strength_scale_min"] == 0.6
assert kwargs["recommended_strength_scale_max"] == 0.8
async def test_get_random_loras_with_recommended_strength_disabled(routes):
"""Test random LoRAs with recommended strength feature disabled (default)"""
request = DummyRequest(
json_data={
"count": 5,
"model_strength_min": 0.5,
"model_strength_max": 1.0,
"use_same_clip_strength": True,
"locked_loras": [],
}
)
response = await routes.get_random_loras(request)
payload = json.loads(response.text)
assert response.status == 200
assert payload["success"] is True
# Verify default parameters were passed to service
kwargs = routes.service.last_get_random_loras_kwargs
assert kwargs["use_recommended_strength"] is False
assert kwargs["recommended_strength_scale_min"] == 0.5
assert kwargs["recommended_strength_scale_max"] == 1.0