File size: 4,758 Bytes
71d239c | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 | """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 <head>. Loads mermaid and watches the DOM, so any
# diagram Gradio swaps in after an analysis is rendered automatically.
MERMAID_HEAD = """
<script type="module">
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs';
mermaid.initialize({ startOnLoad: false, theme: 'neutral',
flowchart: { curve: 'basis', useMaxWidth: true } });
async function draw() {
const blocks = document.querySelectorAll('.mermaid:not([data-processed])');
if (blocks.length) { try { await mermaid.run({ nodes: blocks }); } catch (e) {} }
}
const obs = new MutationObserver(() => draw());
obs.observe(document.body, { childList: true, subtree: true });
window.addEventListener('load', draw);
</script>
"""
def _e(text: str) -> str:
return html.escape(text or "")
def header_html() -> str:
return (
'<div id="sc-header">'
'<span class="mark">Story<span class="accent">Code</span></span>'
'<span class="tag">You built it. Now understand it — as a story.</span>'
"</div>")
def sources_html() -> str:
chips = "".join(f'<span class="sc-chip">{_e(s)}</span>' for s in config.SOURCE_HINTS)
return (
'<div class="sc-card">'
'<div style="font-weight:600;color:#1d2430;margin-bottom:8px;">'
"Where did your code come from?</div>"
f'<div class="sc-sources">{chips}</div>'
'<div class="sc-privacy">Upload a <strong>.zip</strong> of your project (or one file). '
"Don’t paste passwords or API keys — <strong>we’ll flag and hide them</strong> "
"if you do.</div>"
"</div>")
def banner(text: str, kind: str = "info") -> str:
return f'<div class="sc-banner {kind}">{text}</div>' if text else ""
def story_html(s: ProjectStory) -> str:
steps = "".join(
f'<div class="sc-step"><div class="h">{_e(sec.heading)}</div>'
f'<div class="b">{_e(sec.body)}</div></div>'
for sec in s.steps)
return (
'<div class="sc-story sc-card">'
f"<h2>{_e(s.title)}</h2>"
f'<div class="overview">{_e(s.overview)}</div>'
f"{steps}"
"</div>")
def plain_panel_html(s: ProjectStory) -> str:
return (
'<div class="sc-plain">'
'<div class="label">Plain English</div>'
f'<div class="body">{_e(s.plain_overview or s.overview)}</div>'
"</div>")
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'<div class="sc-file {f.safety}">'
f'<div class="dot">{emoji}</div>'
'<div class="body">'
f'<span class="name">{_e(f.path)}</span>'
f'<span class="verdict">{_e(label)}</span>'
f'<div class="reason">{_e(f.safety_reason)}</div>'
"</div></div>")
legend = ('<div class="sc-legend">🟢 safe to change · 🟠 change carefully · '
"🔴 don’t touch unless you know why</div>")
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 = ' <span style="color:#c2503f;font-size:12px;">(check this)</span>' if d.risky else ""
rows.append(
f'<div class="sc-file"><div class="dot">📦</div><div class="body">'
f'<span class="name">{_e(d.name)}</span>{flag}'
f'<div class="reason">{_e(d.plain)}</div></div></div>')
return "".join(rows)
|