adaptive-model / lib /markdown_renderer.py
joduor's picture
feat: interactive figures β€” editable chart bars, number/range/currency inputs, clickable cards+tables, figures component
f349151 verified
Raw
History Blame Contribute Delete
4.78 kB
"""
Renders a ui_spec dict to GitHub-flavored markdown.
Used by the Custom GPT Action path (Path A) where real interactive UI
is not available. Interactive behaviour degrades to descriptive markdown.
"""
def render(spec: dict | None) -> str | None:
if not spec:
return None
component = spec.get("component", "")
props = spec.get("props", {})
renderers = {
"form": _form,
"chart": _chart,
"card": _card,
"table": _table,
"figures": _figures,
}
fn = renderers.get(component)
return fn(props) if fn else f"*[{component} β€” view in full UI for interactivity]*"
# ── form ─────────────────────────────────────────────────────────────────────
def _form(props: dict) -> str:
lines: list[str] = []
title = props.get("title")
if title:
lines.append(f"**{title}**\n")
for f in props.get("fields", []):
label = f.get("label") or f.get("name", "")
ftype = f.get("type", "text")
required = " *(required)*" if f.get("required") else ""
hint = f" β€” {f['hint']}" if f.get("hint") else ""
lines.append(f"- **{label}** `{ftype}`{required}{hint}")
submit = props.get("submitLabel", "Submit")
lines.append(f"\n> *(Use the full UI widget to fill and submit β€” **{submit}**)*")
return "\n".join(lines)
# ── chart ─────────────────────────────────────────────────────────────────────
def _chart(props: dict) -> str:
title = props.get("title", "Chart")
chart_type = props.get("type", "")
lines = [f"**{title}** *(interactive {chart_type} chart in full UI)*\n"]
data = props.get("data", {})
labels = data.get("labels", [])
datasets = data.get("datasets", [])
if labels and datasets:
header = "| Label | " + " | ".join(ds.get("label", "Value") for ds in datasets) + " |"
sep = "|---|" + "---|" * len(datasets)
lines += [header, sep]
for i, lbl in enumerate(labels):
cells = [str(ds.get("data", [])[i]) if i < len(ds.get("data", [])) else "" for ds in datasets]
lines.append(f"| {lbl} | " + " | ".join(cells) + " |")
return "\n".join(lines)
# ── card ──────────────────────────────────────────────────────────────────────
def _card(props: dict) -> str:
lines: list[str] = []
if title := props.get("title"):
lines.append(f"### {title}")
if body := props.get("body"):
lines.append(body)
for item in props.get("items", []):
if isinstance(item, dict):
key = item.get("label") or item.get("key", "")
lines.append(f"- **{key}**: {item.get('value', '')}")
else:
lines.append(f"- {item}")
return "\n".join(lines)
# ── table ─────────────────────────────────────────────────────────────────────
def _table(props: dict) -> str:
columns = props.get("columns", [])
rows = props.get("rows", [])
if not columns:
return "*[empty table]*"
header = "| " + " | ".join(columns) + " |"
sep = "|" + "---|" * len(columns)
lines = [header, sep]
for row in rows:
if isinstance(row, dict):
cells = [str(row.get(col, "")) for col in columns]
elif isinstance(row, list):
cells = [str(c) for c in row]
else:
cells = [str(row)]
lines.append("| " + " | ".join(cells) + " |")
return "\n".join(lines)
# ── figures ───────────────────────────────────────────────────────────────────
def _figures(props: dict) -> str:
lines: list[str] = []
if title := props.get("title"):
lines.append(f"**{title}**\n")
for f in props.get("fields", []):
label = f.get("label") or f.get("name", "")
value = f.get("value", 0)
unit = f" {f['unit']}" if f.get("unit") else ""
lines.append(f"- **{label}**: {value}{unit}")
cta = props.get("cta", "Analyze my figures")
lines.append(f"\n> *(Enter figures in the widget then click **{cta}**)*")
return "\n".join(lines)