This is a submission for the Hacktoberfest Open-Source AI Challenge Week 2: Side Hustle

The problem

Table Topics is the impromptu round of Toastmasters: you get a prompt you've never seen, and you stand up and speak for one to two minutes. No notes, no prep.

I'm competing soon, and my weakness is exactly that - I ramble, I overrun the time, and I can't hear my own filler words. A human coach costs money per session and can't sit with me at 11pm the week before a contest. The feedback tools that exist want my audio on their servers and charge per minute, which is exactly wrong for daily drills.

So I built Confident Speaker Coach: open a link, get a topic, speak, and get scored on the actual Toastmasters ballot - Content 55, Delivery 30, Language 15 - plus one audience-member's honest reaction, the single biggest fix, and one concrete upgrade. The coach reads the verdict back to me out loud.

Demo

No hosted demo, on purpose: the whole point is that it runs on your machine, so the demo is the repo plus the video. It takes one command:

Run it yourself

git clone https://github.com/viveknshah/confident-speaker-coach cd confident-speaker-coach docker compose up # pulls qwen2.5:3b-instruct (~1.9 GB) on first run # open http://localhost:3000 - Chrome or Edge for the mic 

No Docker? ollama pull qwen2.5:3b-instruct && node server.js - Node 18+, zero npm dependencies. Swap the model with OLLAMA_MODEL (I used gemma3:4b for a while). Once the model is pulled, the coaching loop runs locally - note the typed fallback is the fully-offline input path; Chrome's live transcription uses the browser's speech service.

What it does

  1. Gives you a Table Topics prompt.
  2. Runs a contest timer with the official 1:00-2:30 window, and transcribes your speech live in the browser (Web Speech API; typed fallback in other browsers).
  3. Scores the speech: the app measures length, words per minute and filler words in code - those are facts, and I don't trust a model to guess them. A local open-weight model judges the content, structure and language, and its ballot scores get clamped to the real 55/30/15 weights.
  4. Tells you the verdict the way my club's coaches do: length first, then the ballot, then how it landed for an audience member, then the one biggest fix and one concrete upgrade for your next attempt.
  5. Speaks a 3-sentence verdict back out loud, so practice feels like a person is in the room. It uses the browser's built-in voice by default; set an ElevenLabs key and it upgrades to a natural voice, with the key kept server-side.

The open-source AI at the core

The coach is an open-weight model - qwen2.5:3b-instruct - running in Ollama on the server. Remove it and the product is a kitchen timer. Every part of the feedback that requires judgment (ballot scoring, the audience reaction, the fix, the rewritten beat) comes from that model, constrained to strict JSON so the UI can render it directly.

Why a 3B model: it's small enough for a cheap 4 GB CPU box, and it's reliable at structured output for its size, which matters because the UI renders the fields it returns. Don't like my choice? OLLAMA_MODEL=gemma3:4b docker compose up - the whole coaching brain is one environment variable.

The division of labour is deliberate:

Measured in code (facts) Judged by the model (opinion)
Speech length vs the 1:00-2:30 contest rule Content and structure quality
Words per minute Language and word choice
Filler-word counts Audience reaction, biggest fix, one upgrade

Small models are generous graders. Mine can't inflate the numbers, because the numbers come from a stopwatch and a word counter.

Why open innovation is what makes this work

  • Cost is why the business works. Unlimited practice is the product. A club of 30 members running 10 drills a week each is thousands of model calls a month - free on open weights, a growing invoice on a per-token API. The subscription math only closes at zero marginal cost.
  • Privacy is a feature, not a compliance line. Table Topics speeches are personal - people talk about their jobs, their families, their failures. On a self-hosted club instance, the transcripts and scores never leave infrastructure the club controls. (One honest caveat: if you use the mic, Chrome's live transcription uses the browser's speech service - the typed fallback keeps everything on the box.)
  • No lock-in. The model, the prompt and the scoring rules are all in the repo. A club can fine-tune for its own contest format, swap models as better open weights land, and never renegotiate with me or anyone else.

Who pays

Toastmasters has over 265,000 members across 13,800+ clubs, and every one of them practices Table Topics weekly with no good way to get feedback between meetings. Clubs pay a monthly subscription for the hosted version - shared practice, member progress, their own topics. Individuals self-host free; the whole thing is one docker compose up, because the people who most need speaking practice are often the people least able to pay per session.

First user: me, the night I built it. I gave it a real Table Topics attempt - "What is overrated, and what is underrated?" - and rambled for two minutes about movie hype. The coach scored me 28/100 and it was right: it clocked my 37-words-per-minute crawl (I was thinking, not speaking), called the speech disjointed, and its one fix was "define overrated vs underrated up front, then structure around it." That's exactly what a human evaluator would have told me, at 11pm, for free, with the audio never leaving my laptop.

How it's built

Zero-dependency Node 18 server (no npm install), static frontend, Docker compose with Ollama and the model pull baked in:

Browser (mic, live STT, timer) --> Node server --> Ollama (qwen2.5:3b-instruct, strict JSON) | +--> optional ElevenLabs TTS (browser voice by default) 

/api/topic, /api/coach, /api/speak (optional rate-limited ElevenLabs proxy; browser voice when unset). Tests cover the timing rules, filler measurement, score clamping and the API in mock mode.

What's honestly rough

  • A 3B model is a decent club coach, not a world champion judge. The measured facts keep it honest; the prose feedback is where bigger open models (or a fine-tune on real ballots) would help.
  • Live transcription uses the browser's Web Speech API, so Chrome/Edge only for now - swapping in local Whisper is the obvious next step, and it's open-weight too.
  • What broke in my real run: this laptop's Ollama GPU runner was broken - every coaching call failed with CUDA error: shared object initialization failed - so the verified setup is a CPU-only Ollama server. Related: gemma3:4b on CPU managed ~1.3 tok/s (a verdict would take 6+ minutes), which is why the demo ships qwen2.5:3b-instruct (~3 tok/s warm, ~3 min per verdict - the video captions the thinking pause instead of editing it out).

My Agent Session

Confident Speaker Coach: CPU-only run and end-to-end verification
You

Run the Confident Speaker Coach runbook Part 1: run it on the laptop (repo https://github.com/viveknshah/confident-speaker-coach).

Agent

Checked pre-reqs: Docker 29.7.2, Ollama 0.40.3, Node 24 available; installed Git 2.55 via winget, cloned the repo. Zero-dependency Node app, tests 4/4 pass.

▸ shell docker --version; ollama --version; node --version; git clone repo; node --te...
$ docker --version; ollama --version; node --version; git clone repo; node --test test/*.test.js
Docker 29.7.2, Ollama 0.40.3, Node v24.19.0; tests 4 pass, 0 fail
Agent

Started the coach with the already-pulled gemma3:4b. Every /api/coach call failed: Ollama 500 CUDA error shared object initialization failed. The desktop app's GPU runner is broken on this machine.

▸ shell OLLAMA_MODEL=gemma3:4b node server.js; POST /api/coach
$ OLLAMA_MODEL=gemma3:4b node server.js; POST /api/coach
502 Bad Gateway; server log: Ollama 500 CUDA error: shared object initialization failed
Agent

Quit the Ollama tray app, started a CPU-only server with OLLAMA_NUM_GPU=0 and pulled the repo-default qwen2.5:3b-instruct (1.9 GB) in the background. Gemma on CPU managed only 1.3 tok/s, too slow for a coaching verdict.

▸ shell OLLAMA_NUM_GPU=0 ollama serve; ollama pull qwen2.5:3b-instruct; chat speed pr...
$ OLLAMA_NUM_GPU=0 ollama serve; ollama pull qwen2.5:3b-instruct; chat speed probes
qwen pulled; gemma3:4b 1.3 tok/s on CPU, qwen ~3 tok/s warm
Agent

Full end-to-end coaching round through qwen: 301-word Table Topics speech, 85 s. Returned 61/100 (C35/D11/L15), honest mid-range scoring with code-measured facts (212 wpm fast, fillers mapped, delivery penalties applied). Took ~3 min warm, so the demo captions the thinking pause. Page verified in browser with zero console errors.

▸ shell POST /api/coach full transcript; browser snapshot + console check
$ POST /api/coach full transcript; browser snapshot + console check
scores C35/D11/L15 total 61; length 1:25; footer Model qwen2.5:3b-instruct; no JS errors

Prize Categories

Entering overall judging only. ElevenLabs stays in the code as an optional voice upgrade, but the demo ships on the browser voice - no partner category claim.

Built with Ollama + qwen2.5.