Raspberry Pi: The Full Stack - A Browser-Based AI Dashboard on a Pi 3
The Full Stack - A Browser-Based AI Dashboard on a Pi 3
Series: Building AI on the Raspberry Pi — Part 4 of 4 | Chromium Dashboard
A local Flask server, an HTML interface in Chromium, file upload, AI processing, and results rendered as a table. All of it running on a Raspberry Pi 3B+. Here is what we built.
We started this series with twelve lines of bash and a curl command. We are finishing it with a web application.
Flask is a lightweight Python web framework. It starts a local HTTP server on
port 5000 that Chromium connects to at
http://localhost:5000. The HTML page sends the CSV file to Flask via a form upload. Flask reads
the file, calls the Groq API, and sends the JSON result back. JavaScript in
the page renders it as a table.
The whole thing is local except for that one outbound API call to Groq. Your CSV file never leaves the Pi except to travel to the AI model for processing. Everything else — serving the page, receiving the upload, rendering the results — happens on the board.
The browser is the UI. Flask is the bridge. Groq is the engine. The Pi is the stage. It looks like a web app because it is one.
Setup: Two Libraries
pip3 install flask requests
That is the complete dependency list for this installment.
File Structure
Create a folder called
ai_dashboard
in your home directory. Inside it, you need two files:
ai_dashboard/
├── app.py ← the Flask server
└── templates/
└── index.html ← the dashboard page
The Flask Server
from flask import Flask, request, jsonify, render_template
import requests as http_requests
import json
app = Flask(__name__)
GROQ_API_KEY = "your_api_key_here"
GROQ_URL = "https://api.groq.com/openai/v1/chat/completions"
@app.route("/")
def index():
return render_template("index.html")
@app.route("/process", methods=["POST"])
def process():
if "csv_file" not in request.files:
return jsonify({"error": "No file uploaded"}), 400
file = request.files["csv_file"]
csv_data = file.read().decode("utf-8")
prompt = f"""Here is a CSV file with product inventory data:
{csv_data}
Return a JSON array where each item has:
- product (string)
- price (number)
- category (reasonable category based on product name)
Return only valid JSON. No explanation."""
headers = {
"Authorization": f"Bearer {GROQ_API_KEY}",
"Content-Type": "application/json"
}
payload = {
"model": "llama-3.3-70b-versatile",
"messages": [{"role": "user", "content": prompt}],
"temperature": 0.2
}
try:
response = http_requests.post(GROQ_URL, headers=headers, json=payload)
response.raise_for_status()
content = response.json()["choices"][0]["message"]["content"]
result = json.loads(content)
return jsonify({"success": True, "data": result})
except Exception as e:
return jsonify({"error": str(e)}), 500
if __name__ == "__main__":
app.run(host="127.0.0.1", port=5000, debug=False)
The Dashboard Page
Save this as
templates/index.html
inside your
ai_dashboard
folder:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>AI CSV Dashboard</title>
<style>
body { font-family: sans-serif; background: #0f1923; color: #e0e0e0;
margin: 0; padding: 32px 24px; }
h1 { font-size: 22px; color: #a8d8f0; margin-bottom: 4px; }
.sub { font-size: 13px; color: #666; margin-bottom: 28px; }
.card { background: #1a2535; border-radius: 6px; padding: 24px; margin-bottom: 20px; }
input[type="file"] { color: #a8d8f0; margin-bottom: 16px; display: block; }
button { background: #1a5c8a; color: white; border: none;
padding: 10px 24px; border-radius: 4px; font-size: 14px; cursor: pointer; }
button:hover { background: #2075b0; }
button:disabled { background: #333; cursor: default; }
#status { font-size: 13px; color: #888; margin-top: 12px; }
table { width: 100%; border-collapse: collapse; margin-top: 8px; }
th { text-align: left; padding: 10px 12px; background: #111e2b;
color: #a8d8f0; font-size: 13px; }
td { padding: 10px 12px; border-bottom: 1px solid #1e2f40; font-size: 14px; }
tr:last-child td { border-bottom: none; }
#download-btn { background: #2e5e2e; margin-top: 16px; }
#download-btn:hover { background: #3a7a3a; }
</style>
</head>
<body>
<h1>AI CSV Dashboard</h1>
<p class="sub">Powered by Groq + Llama 3.3 — running on Raspberry Pi</p>
<div class="card">
<input type="file" id="csv-input" accept=".csv">
<button onclick="processCSV()">Send to AI</button>
<div id="status">Choose a CSV file to get started.</div>
</div>
<div class="card" id="results-card" style="display:none">
<table id="results-table"></table>
<button id="download-btn" onclick="downloadJSON()">Download JSON</button>
</div>
<script>
let lastResult = null;
async function processCSV() {
const input = document.getElementById("csv-input");
const status = document.getElementById("status");
if (!input.files[0]) { status.textContent = "Please select a CSV file first."; return; }
status.textContent = "Sending to Groq API...";
const formData = new FormData();
formData.append("csv_file", input.files[0]);
try {
const response = await fetch("/process", { method: "POST", body: formData });
const result = await response.json();
if (result.error) {
status.textContent = "Error: " + result.error;
} else {
lastResult = result.data;
renderTable(result.data);
status.textContent = "Done. " + result.data.length + " items processed.";
document.getElementById("results-card").style.display = "block";
}
} catch (err) {
status.textContent = "Network error: " + err.message;
}
}
function renderTable(data) {
const table = document.getElementById("results-table");
const headers = Object.keys(data[0]);
let html = "<tr>" + headers.map(h => `<th>${h.toUpperCase()}</th>`).join("") + "</tr>";
data.forEach(row => {
html += "<tr>" + headers.map(h => `<td>${row[h]}</td>`).join("") + "</tr>";
});
table.innerHTML = html;
}
function downloadJSON() {
if (!lastResult) return;
const blob = new Blob([JSON.stringify(lastResult, null, 2)], {type: "application/json"});
const a = document.createElement("a");
a.href = URL.createObjectURL(blob);
a.download = "output.json";
a.click();
}
</script>
</body>
</html>
Running It
Navigate to your
ai_dashboard
folder in the terminal and start the server:
cd ~/ai_dashboard
python3 app.py
Open Chromium and go to
http://localhost:5000. The dashboard loads. Select your CSV, click Send to AI, and the results
render as a table in the browser. Click Download JSON to save the output.
The complete application is two files —
app.py
and
index.html. That is the entire stack.
What Just Happened
A Raspberry Pi 3B+ just served a web application to its own browser, processed a file through a frontier AI model, and rendered the results as a formatted data table. The Pi handled the HTTP routing, the file reading, and the UI. Groq handled the intelligence. The whole thing cost nothing to run except the electricity to power the board.
That is the thesis of this series proven at every level. Shell script. Python CLI. Desktop app. Web dashboard. The hardware constraint is real — you cannot run a meaningful AI model locally on a Pi 3. But you can build AI-powered applications on one, today, for free.
The Pi does not need to be the AI. It needs to be the builder. Those are different jobs. And the Pi 3 is very good at one of them.
Series complete. Four platforms. Four installments. One free API key. Any Pi that runs a desktop.
Tested on Raspberry Pi 3B+ and 3A+ running Raspberry Pi OS 64-bit. Also works on Pi 4 and Pi 5.
Aaron Rose is a software engineer and technology writer at tech-reader.blog.
Catch up on the latest explainer videos, podcasts, and industry discussions below.

