* Added option to have a default choice when no other choices are available after conditions (in choices and wildcards).

This commit is contained in:
Antonio Cordero Balcazar
2026-06-08 16:49:15 +02:00
parent 423faf1336
commit dbe8a2b05d
7 changed files with 45 additions and 7 deletions
+2
View File
@@ -53,6 +53,7 @@ The choice options are as follows:
* `'identifiers'`: comma separated labels for the choice (optional, quotes can be single or double). Only makes sense inside a wildcard definition. Can be used when specifying the wildcard to select this specific choice. It's case insensitive.
* `n`: weight of the choice (optional, default 1).
* `if condition`: filters out the choice if the condition is false (optional; this is an extension to the *Dynamic Prompts* syntax). Same conditions as in the `if` command.
* `else`: flag to indicate this is the choice to use if no other choice is available after conditions. It won't be considered if other choices are available. Also optional.
* `::`: end of choice options (not optional if any options)
Whitespace is allowed between parameters/options.
@@ -142,6 +143,7 @@ The choices of the wildcard follow the same format as in the choices construct,
If using the object format for a choice you can use the following in addition to the standard `weight` and `text`/`content`:
* `if`: the condition (a string)
* `else`: flag (boolean) to indicate this is the choice to use if no other choice is available after conditions. It won't be considered if other choices are available.
* `labels`: list of labels (an array of strings)
* `command`: indicates the content is a command (a boolean)
+5 -4
View File
@@ -165,27 +165,28 @@ extranetworktag: "<" /(?!ppp:)\w+:/ encontent ">"
choicesoptions_sep: "$$" plain
// choice options
choice: [ [ _WHITESPACE? choiceiscmd ] [ _WHITESPACE? choicelabels ] [ _WHITESPACE? choiceweight ] [ _WHITESPACE? choiceif ] _WHITESPACE? "::" ] choicevalue
choice: [ [ _WHITESPACE? choiceiscmd ] [ _WHITESPACE? choicelabels ] [ _WHITESPACE? choiceweight ] [ _WHITESPACE? ( choiceif | choiceelse ) ] _WHITESPACE? "::" ] choicevalue
choiceiscmd: /%/ // the option text is a special command
choicelabels: ( /"/ IDENTIFIER ( _WHITESPACE? "," _WHITESPACE? IDENTIFIER )* /"/ )
| ( /'/ IDENTIFIER ( _WHITESPACE? "," _WHITESPACE? IDENTIFIER )* /'/ )
choiceweight: NUMBER
choiceif: "if" _WHITESPACE condition
choiceelse: "else"
choicevalue: content_choice
//#endif
//#if ALLOW_CHOICES
// choices construct
choices: "{" [ choicesoptions_sampler | ( choicesoptions _WHITESPACE? "$$" ) ] choice ( "|" choice )* "}"
choices: "{" [ choicesoptions_sampler | ( choicesoptions "$$" ) ] choice ( "|" choice )* "}"
//#endif
//#if ALLOW_WILDCARDS
// wildcard definition options
wcdefoptions: [ choicesoptions_sampler ] [ _WHITESPACE? choicesoptions_flags ] [ _WHITESPACE? choicesoptions_range ] [ _WHITESPACE? wcdescription ] [ _WHITESPACE? choicesoptions_sep ]
wcdefoptions: [ choicesoptions_sampler ] [ _WHITESPACE? choicesoptions_flags ] [ _WHITESPACE? choicesoptions_range ] [ _WHITESPACE? wcdescription ] [ _WHITESPACE? choicesoptions_sep ] "$$"
wcdescription: STRING
// wildcards construct
wildcard: "__" [ choicesoptions_sampler | ( choicesoptions _WHITESPACE? "$$" ) ] wildcard_name [ wc_filter ] [ wildcardvar ] "__"
wildcard: "__" [ choicesoptions_sampler | ( choicesoptions "$$" ) ] wildcard_name [ wc_filter ] [ wildcardvar ] "__"
wc_filter_nums: IDENTIFIER | INDEX | (INDEX /-/ INDEX)
//#if ALLOW_COMMVARS
wildcard_name.2: ( WC_NAME_PLAIN_START | variableuse | commandecho ) ( WC_NAME_PLAIN | variableuse | commandecho )*
+15 -2
View File
@@ -1891,8 +1891,12 @@ class TreeProcessor(lark.visitors.Interpreter):
included_choices = 0
excluded_choices = 0
excluded_weights_sum = 0
else_choice = None
for i, c in enumerate(expanded_choice_values):
c["choice_index"] = i # we index them to later sort the results
if c.get("else", False):
else_choice = c
continue
weight = float(c.get("weight", 1.0))
condition = c.get("if", None)
if weight > 0 and (condition is None or self.__eval_condition(condition)):
@@ -1903,6 +1907,10 @@ class TreeProcessor(lark.visitors.Interpreter):
weights.append(-1)
excluded_choices += 1
excluded_weights_sum += weight
if not available_choices and else_choice is not None:
available_choices = [else_choice]
weights = [float(else_choice.get("weight", 1.0))]
included_choices = 1
if excluded_choices > 0: # we need to redistribute the excluded weights
weights = [weight + excluded_weights_sum / included_choices for weight in weights if weight >= 0]
weights = np.array(weights)
@@ -2114,7 +2122,12 @@ class TreeProcessor(lark.visitors.Interpreter):
c_label_obj = choice.children[1]
choice_dict["labels"] = [str(x).lower() for x in c_label_obj.children[1:-1]] if c_label_obj is not None else []
choice_dict["weight"] = float(choice.children[2].children[0]) if choice.children[2] is not None else 1.0
choice_dict["if"] = choice.children[3].children[0] if choice.children[3] is not None else None
ifelse = choice.children[3]
if ifelse is not None:
if len(ifelse.children) and ifelse.children[0] is not None:
choice_dict["if"] = ifelse.children[0]
else:
choice_dict["else"] = True
choice_dict["content"] = choice.children[-1]
return choice_dict
@@ -2242,7 +2255,7 @@ class TreeProcessor(lark.visitors.Interpreter):
parse_prompt(
self.state,
"as wildcard options",
wildcard.unprocessed_choices[0][:-2].strip(),
wildcard.unprocessed_choices[0],
self.state.parsers["wcdefoptions"],
True,
),
+1 -1
View File
@@ -268,7 +268,7 @@ class PPPWildcards:
Returns:
bool: Whether the dictionary is a valid choice options dictionary or not.
"""
return all(k in ["command", "labels", "weight", "if", "content", "text"] for k in d.keys())
return all(k in ["command", "labels", "weight", "if", "else", "content", "text"] for k in d.keys())
def __get_choices(self, obj: object, full_path: Path | None, key_parts: list[str]) -> list:
"""
+7
View File
@@ -103,6 +103,13 @@ class TestChoices(TestPromptPostProcessorBase):
ppp="nocup",
)
def test_ch_choices_if_default(self): # choices with if and a default
self.process(
InputTuple("the choice is: {if false::choice1|if _is_sd1::choice2|else::choice3}", ""),
OutputTuple("the choice is: choice3", ""),
ppp="nocup",
)
def test_ch_choices_set_if_multiple(self): # choices with if user variable and multiple selection
self.process(
InputTuple("${var=test}the choices are: {2$$, $$3::choice1|2 if not var eq 'test'::choice2|choice3}", ""),
+7
View File
@@ -337,6 +337,13 @@ class TestWildcards(TestPromptPostProcessorBase):
ppp="nocup",
)
def test_wc_wildcard_emptydefault(self): # empty wildcard with default
self.process(
InputTuple("the choices are: __yaml/empty_default_wildcard__", ""),
OutputTuple("the choices are: 6", ""),
ppp="nocup",
)
def test_wc_wildcard4_yaml(self): # simple yaml wildcard with one option
self.process(
InputTuple("the choices are: __yaml/wildcard4__", ""),
+8
View File
@@ -111,6 +111,14 @@ yaml:
- if _sd in ("test1", "test2")::4
- if (false or false)::5
empty_default_wildcard:
- if false::1
- if false::2
- if false::3
- if _sd in ("test1", "test2")::4
- if (false or false)::5
- else::6
circular1:
- 5::__yaml/circular2__
- choice1