"""Gradio theme + HTML builders shared by app.py. All the rich panels (the story, the safe-to-edit list, banners) are rendered as HTML so we control the look precisely — the custom design is a hackathon badge and an explicit requirement, not default Gradio. """ from __future__ import annotations import html from pathlib import Path import gradio as gr import config from schema import ProjectModel, ProjectStory THEME = gr.themes.Soft( primary_hue=gr.themes.colors.indigo, neutral_hue=gr.themes.colors.stone, font=[gr.themes.GoogleFont("Inter"), "system-ui", "sans-serif"], ).set( body_background_fill="#f7f4ef", block_background_fill="#ffffff", block_border_width="1px", block_radius="18px", ) def css() -> str: return (Path(__file__).parent / "styles.css").read_text() # Injected once into the page . Loads mermaid and watches the DOM, so any # diagram Gradio swaps in after an analysis is rendered automatically. MERMAID_HEAD = """ """ def _e(text: str) -> str: return html.escape(text or "") def header_html() -> str: return ( '
' 'StoryCode' 'You built it. Now understand it — as a story.' "
") def sources_html() -> str: chips = "".join(f'{_e(s)}' for s in config.SOURCE_HINTS) return ( '
' '
' "Where did your code come from?
" f'
{chips}
' '
Upload a .zip of your project (or one file). ' "Don’t paste passwords or API keys — we’ll flag and hide them " "if you do.
" "
") def banner(text: str, kind: str = "info") -> str: return f'
{text}
' if text else "" def story_html(s: ProjectStory) -> str: steps = "".join( f'
{_e(sec.heading)}
' f'
{_e(sec.body)}
' for sec in s.steps) return ( '
' f"

{_e(s.title)}

" f'
{_e(s.overview)}
' f"{steps}" "
") def plain_panel_html(s: ProjectStory) -> str: return ( '
' '
Plain English
' f'
{_e(s.plain_overview or s.overview)}
' "
") def safe_to_edit_html(model: ProjectModel) -> str: # Most dangerous first — that's what a nervous editor needs to see. order = {config.DANGER: 0, config.CAREFUL: 1, config.SAFE: 2} files = sorted(model.files, key=lambda f: (order.get(f.safety, 1), -f.fan_in, f.path)) rows = [] for f in files: emoji = config.SAFETY_EMOJI.get(f.safety, "🟠") label = config.SAFETY_LABEL.get(f.safety, "") rows.append( f'
' f'
{emoji}
' '
' f'{_e(f.path)}' f'{_e(label)}' f'
{_e(f.safety_reason)}
' "
") legend = ('
🟢 safe to change · 🟠 change carefully · ' "🔴 don’t touch unless you know why
") return legend + "".join(rows) def deps_html(model: ProjectModel) -> str: if not model.deps: return banner("No dependency files (requirements.txt / package.json) found.", "info") rows = [] for d in model.deps: flag = ' (check this)' if d.risky else "" rows.append( f'
📦
' f'{_e(d.name)}{flag}' f'
{_e(d.plain)}
') return "".join(rows)