Skip to main content
Resume
View Resume

Instant Dashboard

An AI-powered web app that converts raw JSON into a rendered HTML dashboard in real time. Paste JSON, describe what you want in plain English, and get a working dashboard streamed live to your browser.

The problem

Raw JSON data is useful but not readable to most people who need to make decisions from it. Building a dashboard from scratch takes time. Business intelligence tools have costs and setup overhead. The gap between "here is the data" and "here is something useful to look at" is wider than it should be.

Instant Dashboard closes that gap with a single interaction: paste your JSON, describe what you want, get a visual result.

What it does

You paste structured JSON data into the app. You describe your preferences in plain English -- chart types, colors, what to emphasize, whether you want a dark theme. The app sends this to a language model, and the response streams back to your browser character by character, appearing as live code in a code view panel.

When the generation finishes, the app validates the HTML and renders it in a sandboxed preview iframe. The result is a self-contained HTML dashboard with inline CSS that you can download and use anywhere, with no external dependencies.

No account needed. No data stored. The whole interaction is stateless.

The streaming architecture

Streaming was the key technical decision. Rather than waiting for the entire dashboard to generate and then showing it, the app forwards each token from the LLM to the browser the moment it arrives using Server-Sent Events (SSE).

Browser
  POST /api/generate-stream
       |
  Flask opens SSE response (text/event-stream)
       |
  LLM generates tokens
       |
  Each token: emit "data: {chunk: '...'}\n\n"
       |
  Generation complete: validate full HTML
       |
  Valid:   emit "event: done" with final HTML
  Invalid + retries remaining: emit "event: retry", loop
  Invalid + no retries: emit "event: error"

One important detail: the X-Accel-Buffering: no response header disables Nginx proxy buffering. Without this, a proxy layer would accumulate the entire response before forwarding it to the browser, which would break the streaming effect entirely.

Auto-retry on bad output

Language models occasionally produce output that doesn't quite match the instructions -- malformed HTML, markdown fences that weren't stripped, incomplete documents. The server automatically retries up to three times before surfacing an error. Each retry emits a status event so the frontend can show "Retrying..." rather than just appearing stuck.

Validation pipeline

The generated HTML goes through a few checks before reaching the iframe:

  1. Strip markdown code fences (models sometimes wrap output in ```html blocks even when told not to)
  2. Verify a <!DOCTYPE html> declaration is present
  3. Check for <html> and <body> structure
  4. Remove javascript: URI schemes from links as a safety measure

The iframe uses sandbox="allow-same-origin allow-scripts" so inline scripts in the generated dashboard are isolated from the parent page.

The system prompt

The LLM is instructed with a specific set of constraints that make the output reliable:

  • Return only a valid, complete <!DOCTYPE html> document -- no markdown, no explanation
  • Use only inline CSS; no CDN dependencies
  • Use only data from the provided JSON -- no made-up values
  • Display "Data Unavailable" for missing fields rather than guessing
  • Follow the user's visual instructions precisely

Getting these constraints right was iterative. Early versions produced partial documents, or added explanatory text before the HTML, or included CDN links that wouldn't load in the sandboxed iframe. Each constraint was added to fix a specific observed failure mode.

Frontend state management

The generator page manages a single state object. Every action updates this state, then a render function reconciles the UI from it. This made the streaming states (idle, streaming, retrying, done, error) easier to manage cleanly than scattering DOM updates across event handlers.

Provider flexibility

The LLM integration is fully configurable through environment variables. Any OpenAI-compatible API endpoint works -- model name, API URL, and API key are all external to the code. This made it easy to test with different models and switch to whichever was producing the best results at the time.

Tech stack

LayerTechnology
BackendPython, Flask
StreamingServer-Sent Events (SSE)
LLMOpenAI-compatible API (configurable)
HTML validationCustom pipeline
FrontendVanilla JS (ES Modules), Vanilla CSS
HostingVercel (serverless Python)