Free course · by the SutraCore team

Free NVIDIA Jetson AI course: 30 days, 16 steps

Orin NX 16GBJetPack 6.2.1CUDA 12.6TensorRT 10.3Python 3.10PyTorch 2.8
Step 16 of 16Days 25–30 · Thu 19 – Tue 24 NovWeek 4Phase B · web

Next.js slice and demo

A minimal Next.js page that a non-coder can use: pick a template, upload a zip, watch training live, see the results, download the model. Then record the demo and write everything down.

Runs on
Laptop (Next.js dev server) talking to the Jetson API
Notebooks
None
Creates
apps/web/, a demo video, README.md, Month 2 list
Time
Days 25–27 build and demo · Day 28 review · Days 29–30 docs

Where Where to implement

Laptop

  • apps/web (Next.js App Router + Tailwind)
  • .env.local: NEXT_PUBLIC_API=http://<jetson-ip>:8000

Jetson

  • FastAPI + worker from Step 15 keep running
  • Add your laptop's address to CORS if you don't use localhost:3000

Do Do it

1Create the app

Laptop terminal
npx create-next-app@latest sutraai-web --typescript --app --tailwind --eslint --no-src-dir
cd sutraai-web
echo "NEXT_PUBLIC_API=http://<jetson-ip>:8000" > .env.local
npm run dev                           # http://localhost:3000

2One page: template → upload → train → results → download

Fileapp/page.tsx
"use client";
import { useState } from "react";

const API = process.env.NEXT_PUBLIC_API ?? "http://localhost:8000";
type Line = { epoch?: number; loss?: number; val_acc?: number; val_f1?: number; event?: string };
const TEMPLATES = [
  { id: "audio", name: "Bird sound (audio · MFCC)" },
  { id: "sensor", name: "Fall detection (accelerometer)" },
  { id: "image", name: "Defect QC (images)" },
  { id: "detect", name: "Driver drowsiness (object detection)" },
];

export default function Home() {
  const [template, setTemplate] = useState("audio");
  const [file, setFile] = useState<File | null>(null);
  const [pid, setPid] = useState<string | null>(null);
  const [lines, setLines] = useState<Line[]>([]);
  const [status, setStatus] = useState("Pick a template and a zip file.");
  const [metrics, setMetrics] = useState<any>(null);

  async function start() {
    if (!file) return setStatus("Choose a zip file first.");
    setLines([]); setMetrics(null); setStatus("Creating project…");
    const p = await fetch(`${API}/projects?template=${template}`, { method: "POST" }).then(r => r.json());
    setPid(p.project_id);
    setStatus("Uploading…");
    const fd = new FormData(); fd.append("file", file);
    await fetch(`${API}/projects/${p.project_id}/dataset`, { method: "POST", body: fd });
    const j = await fetch(`${API}/projects/${p.project_id}/train`, { method: "POST" }).then(r => r.json());
    setStatus("Training…");
    const es = new EventSource(`${API}/jobs/${j.job_id}/events`);
    es.onmessage = async (e) => {
      const msg: Line = JSON.parse(e.data);
      if (msg.event === "finished") {
        es.close(); setStatus("Done.");
        setMetrics(await fetch(`${API}/projects/${p.project_id}/metrics`).then(r => r.json()));
      } else if (msg.epoch) setLines(l => [...l, msg]);
    };
    es.onerror = () => { es.close(); setStatus("Lost the connection to the training job."); };
  }

  const last = lines[lines.length - 1];
  return (
    <main className="mx-auto max-w-2xl p-6 space-y-6">
      <h1 className="text-2xl font-semibold">SutraAI</h1>
      <div className="space-y-3">
        <label htmlFor="tpl" className="block text-sm font-medium">Template</label>
        <select id="tpl" className="w-full border rounded p-2" value={template} onChange={e => setTemplate(e.target.value)}>
          {TEMPLATES.map(t => <option key={t.id} value={t.id}>{t.name}</option>)}
        </select>
        <label htmlFor="zip" className="block text-sm font-medium">Dataset (.zip)</label>
        <input id="zip" type="file" accept=".zip" onChange={e => setFile(e.target.files?.[0] ?? null)} />
        <button onClick={start} className="rounded bg-blue-700 text-white px-4 py-2">Train</button>
        <p className="text-sm text-gray-600">{status}</p>
      </div>
      {last && (
        <div className="rounded border p-4">
          <p>Epoch {last.epoch} · loss {last.loss} · validation {last.val_acc ?? last.val_f1}</p>
          <progress className="w-full" value={last.epoch} max={100} />
        </div>
      )}
      {metrics && pid && (
        <div className="rounded border p-4 space-y-2">
          <p className="font-medium">Test score: {(metrics.test_acc ?? metrics.test_f1)?.toFixed(3)}</p>
          <div className="flex gap-3 flex-wrap">
            {["onnx", "tflite_int8", "c_array", "pytorch"].map(f => (
              <a key={f} className="underline" href={`${API}/projects/${pid}/download?fmt=${f}`}>Download {f}</a>
            ))}
          </div>
        </div>
      )}
    </main>
  );
}

Set the progress bar's max from the template's epoch count once it works. A loss/accuracy chart (recharts or a small SVG) is a good Day 26 addition, together with the Nemotron advisor text from Step 11 shown under the results.

3Demo day (Day 27)

  1. Run all four templates through the page, one after another.
  2. For the audio model, load the downloaded int8 file into the Step 10 FakeDevice and show it predicting.
  3. Record a 5-minute screen video: upload → live training → results → download → simulated device.

4Days 28–30: review and write it down

  • README.md: how to install on a fresh Jetson (Steps 01, 02, 14, 15 in order).
  • PIPELINE.md: every notebook, what it proved, and the numbers.
  • Month 2 list: full Studio UI (see the SutraAI UI Flow canvas), SOC API v1 (the Step 10 simulator over HTTP), public project pages, cloud deploy.
  • Show the demo to 1–2 people and note their questions.

Test Test it

  • A person who has never seen the project trains the audio template from the page without your help.
  • All four templates finish and offer downloads.
  • The downloaded int8 audio model gives the same predictions in FakeDevice as in NB51.
Pass: upload → train (live) → results → download works for all four templates, and the demo video is recorded.

Done Done when

  • Next.js page talks to the Jetson API (CORS OK)
  • Live epoch progress during training
  • Results and downloads shown when training finishes
  • All four templates work through the page
  • 5-minute demo video recorded
  • README.md, PIPELINE.md and the Month 2 list written

If it breaks

SymptomFix
CORS error in the browser consoleAdd your page's exact origin to allow_origins in app.py and restart the API.
EventSource closes at onceCheck the job id; open /jobs/<id>/events directly in the browser to see the stream.
Upload is very slowLarge zips over Wi-Fi: use Ethernet, or downsample images and audio before zipping.
Download link 404The export step didn't run for that format; check projects/<id>/exports/ on the Jetson.

Learn more