from __future__ import annotations import html import json from typing import Any def _icon_for_source(source_type: str) -> str: st = (source_type or "").lower() if st in ("web", "url", "scrape"): return "language" if st == "pdf": return "picture_as_pdf" return "description" def render_doc_cards(documents: list[dict[str, Any]], *, rag_active: bool) -> str: if not documents: return ( '

No documents indexed yet. Paste a URL or upload a file.

' ) badge = ( 'RAG Active' if rag_active else 'No sources' ) cards: list[str] = [] for doc in documents: title = html.escape(str(doc.get("title") or "Untitled")) meta = html.escape(str(doc.get("meta") or "")) icon = _icon_for_source(str(doc.get("source_type") or "")) cards.append( f"""
{icon}

{title}

{meta}

""" ) return f'
{badge}
{"".join(cards)}
' def render_slide_canvas(preview_html: str, *, empty_message: str | None = None) -> str: if not preview_html or not preview_html.strip(): msg = html.escape(empty_message or "Generate slides to preview your lesson here.") return f'

{msg}

' return f'
{preview_html}
' def render_gallery_strip(image_paths: list[str]) -> str: if not image_paths: return "" items: list[str] = [] for index, path in enumerate(image_paths): if not path: continue src = html.escape(f"/file={path}") alt = html.escape(f"Slide {index + 1}") items.append( f'' f'{alt}' ) if not items: return "" return f'' def render_trace_details( *, trace_summary: str = "", trace_json: str = "", progress_log: str = "", ) -> str: blocks: list[str] = [] if trace_summary: safe = html.escape(trace_summary) blocks.append(f'
{safe}
') if progress_log: if "<" in progress_log and ">" in progress_log: blocks.append(f'
{progress_log}
') else: blocks.append( f'
{html.escape(progress_log)}
' ) if trace_json: try: parsed = json.loads(trace_json) pretty = html.escape(json.dumps(parsed, indent=2)) except json.JSONDecodeError: pretty = html.escape(trace_json) blocks.append(f'
{pretty}
') if not blocks: return "" return f'
{"".join(blocks)}
' def _file_url(path: str | None) -> str: if not path: return "" return html.escape(f"/file={path}") def render_echo_coach_panel( *, pace_score: int | None = None, wpm: float | None = None, tip: str | None = None, report_md: str | None = None, transcript_html: str | None = None, filler_chart: str | None = None, pace_chart: str | None = None, voiceout_path: str | None = None, listening: bool = False, ) -> str: if listening: return """
Recording…

Speak your lesson, then analyze for pace and filler feedback.

""" if pace_score is None and not tip and not report_md and not transcript_html: return """

Record a pitch below, then click Analyze pitch for metrics.

""" score = pace_score if pace_score is not None else "—" pace = f"{wpm:.0f}" if wpm is not None else "—" tip_html = html.escape(tip or "") report_block = "" if report_md: safe = html.escape(report_md[:1200]) report_block = f'
{safe}
' transcript_block = "" if transcript_html: transcript_block = f'
{transcript_html}
' charts: list[str] = [] filler_url = _file_url(filler_chart) pace_url = _file_url(pace_chart) if filler_url: charts.append( f'
Filler words
' f'Filler words chart
' ) if pace_url: charts.append( f'
Pace timeline
' f'Pace timeline chart
' ) charts_block = "" if charts: charts_block = f'
{"".join(charts)}
' voiceout_block = "" voiceout_url = _file_url(voiceout_path) if voiceout_url: voiceout_block = ( f'
' f'

VoiceOut feedback

' f'
' ) return f"""
Analysis results EchoCoach

Pace score

{score}

Pace (WPM)

{pace}

{f'

{tip_html}

' if tip_html else ''} {transcript_block} {charts_block} {voiceout_block} {report_block}
"""