# retoor <retoor@molodetz.nl>
from __future__ import annotations
from .schema import InteractionRequest, WidgetModel, flatten_widgets
def project_markdown(request: InteractionRequest, interaction_id: str) -> str:
lines: list[str] = [f"### {request.title}", ""]
if request.description:
lines.append(request.description)
lines.append("")
for widget in request.widgets:
lines.extend(_widget_lines(widget))
actions = f"[{request.submit_label}]"
if request.cancelable:
actions += f" [{request.cancel_label}]"
lines.append("")
lines.append(actions)
lines.append(f"<!-- ai-interaction:{interaction_id} -->")
return "\n".join(lines).strip() + "\n"
def project_plain_menu(request: InteractionRequest) -> str:
lines: list[str] = [request.title]
if request.description:
lines.append(request.description)
lines.append("")
widgets = [
w
for w in flatten_widgets(request.widgets)
if w.type != "//"
]
if len(widgets) == 1 and widgets[0].type == "confirm":
lines.append(f"Reply: yes | no | cancel")
return "\n".join(lines).strip() + "\n"
if len(widgets) == 1 and widgets[0].type in ("choice", "select"):
options = widgets[0].options[:12]
lines.append(f"{widgets[0].label}")
for index, option in enumerate(options, start=1):
lines.append(f"{index}. {option.label} (`{option.value}`)")
lines.append("")
lines.append(f"Reply with a number (1-{len(options)}) or `cancel`.")
return "\n".join(lines).strip() + "\n"
for widget in widgets:
if widget.type in ("choice", "choice_multi", "select"):
lines.append(f"{widget.label}:")
for index, option in enumerate(widget.options[:12], start=1):
lines.append(f" {index}. {option.label} (`{option.value}`)")
elif widget.type == "confirm":
lines.append(f"{widget.label}: yes | no")
else:
lines.append(f"{widget.label}: (free text)")
lines.append("")
lines.append("Reply with values, or `cancel`.")
return "\n".join(lines).strip() + "\n"
def _widget_lines(widget: WidgetModel, indent: str = "") -> list[str]:
if widget.type == "//":
return [f"{indent}{widget.text or widget.label}", ""]
if widget.type == "group":
lines = [f"{indent}**{widget.label}**", ""]
for child in widget.widgets:
lines.extend(_widget_lines(child, indent + " "))
return lines
req = "required" if widget.required else "optional"
lines = [f"{indent}- [ ] **{widget.name}** ({req}) — {widget.label}"]
if widget.help:
lines.append(f"{indent} _{widget.help}_")
if widget.type == "confirm":
default = widget.default
yes = "" if default is True else " "
no = "" if default is False else " "
lines.append(f"{indent} - ({yes}) `true` — Yes")
lines.append(f"{indent} - ({no}) `false` — No")
elif widget.type in ("choice", "select"):
default = str(widget.default) if widget.default is not None else ""
for option in widget.options:
mark = "" if option.value == default else " "
desc = f"{option.description}" if option.description else ""
lines.append(
f"{indent} - ({mark}) `{option.value}` — {option.label}{desc}"
)
elif widget.type == "choice_multi":
defaults = set(widget.default or []) if isinstance(widget.default, list) else set()
for option in widget.options:
mark = "x" if option.value in defaults else " "
desc = f"{option.description}" if option.description else ""
lines.append(
f"{indent} - [{mark}] `{option.value}` — {option.label}{desc}"
)
elif widget.type == "text":
extra = f", max {widget.max_length}" if widget.max_length else ""
lines.append(f"{indent} - _free text{extra}_")
elif widget.type == "number":
parts = []
if widget.min is not None:
parts.append(f"min {widget.min}")
if widget.max is not None:
parts.append(f"max {widget.max}")
hint = (", " + ", ".join(parts)) if parts else ""
lines.append(f"{indent} - _number{hint}_")
elif widget.type == "date":
lines.append(f"{indent} - _date DD/MM/YYYY_")
return lines