| from fasthtml.common import * |
|
|
| def PageLayout(title: str, content: list): |
| """ |
| The Base Layout. |
| Injects PicoCSS (Styling), PrismJS (Syntax Highlight), and HTMX (Interactivity). |
| """ |
| return Html( |
| Head( |
| Title(title), |
| |
| Link(rel="stylesheet", href="https://cdn.jsdelivr.net/npm/@picocss/pico@latest/css/pico.min.css"), |
| |
| |
| Script(src="https://unpkg.com/htmx.org@1.9.10"), |
|
|
| |
| Link(rel="stylesheet", href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism-tomorrow.min.css"), |
| Script(src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/prism.min.js"), |
| Script(src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-json.min.js"), |
| |
| |
| Style(""" |
| body { max-width: 900px; margin: 0 auto; padding: 20px; background-color: #11191f; } |
| .json-box { border-radius: 8px; overflow: hidden; margin-top: 20px; } |
| h1 { color: #00E5FF; } |
| button.contrast { background-color: #00E5FF; border-color: #00E5FF; color: black; font-weight: bold; } |
| .error-box { background: #3d1a1a; color: #ff8080; padding: 15px; border-radius: 8px; border: 1px solid #ff4d4d; } |
| """) |
| ), |
| Body( |
| Header( |
| H1("🛡️ Structura"), |
| P("The Unbreakable Data Architect", style="opacity: 0.7;"), |
| style="border-bottom: 1px solid #333; padding-bottom: 20px; margin-bottom: 40px;" |
| ), |
| Main(*content), |
| Footer( |
| P("Powered by PydanticAI & Gemini 2.5", style="text-align: center; opacity: 0.5; font-size: 0.8rem; margin-top: 50px;") |
| ) |
| ) |
| ) |
|
|
| |
| def HeroSection(): |
| return Div( |
| H2("Turn Chaos into Structure"), |
| P("Paste messy emails, invoices, or resumes below. Get guaranteed, type-safe JSON back."), |
| style="margin-bottom: 30px;" |
| ) |
|
|
| def ExtractionForm(): |
| """ |
| The Interactive Form. |
| Uses HTMX (hx-post) to swap the result area without a page reload. |
| """ |
| return Form( |
| Label("Raw Input Data"), |
| Textarea( |
| name="text_input", |
| placeholder="e.g. Invoice #909 from CloudFix for $500...", |
| rows=8, |
| style="font-family: monospace; background: #0b0f13; color: white; border: 1px solid #333;" |
| ), |
| |
| Grid( |
| Label("Target Schema", |
| Select( |
| Option("Invoice Extraction", value="invoice"), |
| Option("Resume Parsing", value="resume"), |
| Option("Generic Data", value="generic"), |
| name="schema_type" |
| ) |
| ), |
| |
| Div() |
| ), |
| |
| Button("Extract Structure ⚡", cls="contrast", type="submit"), |
| |
| |
| hx_post="/extract", |
| hx_target="#result-area", |
| hx_swap="innerHTML", |
| hx_indicator="#loading" |
| ) |
|
|
| def LoadingIndicator(): |
| return Div("⚙️ AI is processing...", id="loading", cls="htmx-indicator", style="color: #00E5FF; margin-top: 10px;") |
|
|
| def SuccessDisplay(json_str: str): |
| return Div( |
| H4("✅ Validated Output", style="color: #4ade80; margin-bottom: 10px;"), |
| Div( |
| Pre(Code(json_str, cls="language-json")), |
| cls="json-box" |
| ), |
| P("Schema Verified by PydanticAI", style="color: grey; font-size: 0.8rem; margin-top: 10px; text-align: right;") |
| ) |
|
|
| def ErrorDisplay(error_msg: str): |
| return Div( |
| H4("❌ Extraction Failed"), |
| P(error_msg), |
| cls="error-box" |
| ) |
|
|