| """ |
| 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]*" |
|
|
|
|
| |
|
|
| 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) |
|
|
|
|
| |
|
|
| 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) |
|
|
|
|
| |
|
|
| 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) |
|
|
|
|
| |
|
|
| 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) |
|
|
|
|
| |
|
|
| 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) |
|
|