diff --git a/app/ui/features/settings/models.js b/app/ui/features/settings/models.js index d88225a3..97f2e0d0 100644 --- a/app/ui/features/settings/models.js +++ b/app/ui/features/settings/models.js @@ -15,7 +15,7 @@ export const widgetControl = (w, current, set) => { // Nothing chosen shows as nothing chosen: showing the first file would look picked while the run has none // (and with one file there is nothing to change to, so it could never be picked). const options = [...(unset ? [{ value: "", label: "— choose —" }] : []), ...choices.map((v) => ({ value: v, label: String(v) }))]; - return choices.length ? c.select.md({ label, value: unset ? "" : current, options, onChange: (v) => { if (v !== "") set(v); } }) + return choices.length ? c.select.md({ label, value: unset ? "" : current, options, onChange: (v) => { if (v !== "") set(choices.find((x) => String(x) === String(v)) ?? v); } }) // the choice itself: a select gives back text, and ComfyUI refuses "8" for 8 : c.hint.default({ text: "No choices available — nothing found in the models folder." }); } if (w.type === "INT" || w.type === "FLOAT") { diff --git a/core/graph.py b/core/graph.py index c75c8934..96f485e1 100644 --- a/core/graph.py +++ b/core/graph.py @@ -291,6 +291,7 @@ def build(slots: Sequence[dict], schemas: Optional[Schemas] = None) -> Tuple[dic continue inputs[name] = [source, index] else: + value = as_choice(value, limits.get(name)) problem = unacceptable(name, value, limits.get(name)) if problem: # An input the app writes under a name the person sees (Length, Width) is refused by that name. @@ -311,6 +312,15 @@ def build(slots: Sequence[dict], schemas: Optional[Schemas] = None) -> Tuple[dic return prompt, problems +def as_choice(value: Any, bounds: Optional[dict]) -> Any: + """A combo choice saved as text ("8") back to the choice it names (8). A web page's select hands back + text whatever it was given, and ComfyUI refuses "8" from a list holding the number 8.""" + choices = (bounds or {}).get("choices") + if isinstance(value, str) and choices and value not in choices: + return next((c for c in choices if not isinstance(c, str) and str(c) == value), value) + return value + + def unacceptable(name: str, value: Any, bounds: Optional[dict]) -> Optional[str]: """Why this literal will not do, or None. diff --git a/core/tests/test_graph.py b/core/tests/test_graph.py index 5f9c9169..ee45843b 100644 --- a/core/tests/test_graph.py +++ b/core/tests/test_graph.py @@ -595,3 +595,10 @@ def test_a_swap_keeps_only_the_roles_the_new_node_can_still_take(): swapped, problems = graph.replace(slots, "empty", "Empty", SCHEMAS) assert problems == [] assert graph.slots_by_id(swapped)["empty"]["roles"] == [{"at": "project.video", "input": "width"}] + + +def test_a_numeric_combo_choice_saved_as_text_is_sent_as_the_number(): + bounds = {"choices": ["auto", 8, 10]} + assert graph.as_choice("8", bounds) == 8 and graph.unacceptable("bit_depth", 8, bounds) is None + assert graph.as_choice("auto", bounds) == "auto" + assert graph.as_choice("9", bounds) == "9", "not a choice: left as it is, and refused by name"