This is a submission for the Hacktoberfest Open-Source AI Challenge Week 4: Ghost in the Machine
What I Built
The last train left years ago. The announcements never stopped.
That is the idea behind Ghost Radio: a tiny fictional radio station transmitting from somewhere it shouldn't be.
I interpreted "Ghost in the Machine" as a signal that outlives its source. Not a simulation of a real person, and not a haunted interface full of jump scares. Just an ordinary place, an impossible incident, and a voice still telling the story.
Choose one of three stations:
- Platform Nine, 88.3 FM: an abandoned railway station.
- The Last Light, 94.7 FM: a remote lighthouse.
- Floor Thirteen, 103.1 FM: an empty apartment building.
Add an optional detail, press Tune in, and a local Gemma model writes an original 180–250-word broadcast with a beginning, a strange incident, and an ending.
The written version is the complete MVP. You can read the transcript, save it as text, and reopen it from the local archive. Optional ElevenLabs narration is a separate step: review the exact script, approve sending it, then listen when the audio is ready.
The interface stays small: one radio, one setting, one button. No accounts, microphone access, autoplay, flashing effects or endless chat.
Code
MasterFloppa
/
Ghost-Radio
A fictional late-night radio station: local AI stories and optional consent-gated ElevenLabs narration.
Ghost Radio
A fictional late-night station broadcasting from places it should not Original stories are written by a local open-weight model. ElevenLabs narration is optional and only happens after you approve sending the displayed script.
Built for Hacktoberfest Week 4: Ghost in the Machine.
What it does
- Choose an abandoned railway station, a remote lighthouse or an empty apartment building.
- Optionally add a creative detail. Click Tune in to generate one original fictional broadcast.
- The local model writes a complete 180–250-word story. No canned story is substituted if generation fails.
- Read and download the transcript, or reopen it later from Saved transmissions.
- Optionally create ElevenLabs narration after reviewing and approving the exact script.
- Play, pause, replay and download the saved audio. Playback never makes another synthesis request.
One screen. No chatbot, microphone access, cloned voices, accounts, autoplay, jump scares or flashing effects The broadcasts are fictional entertainment,…
The application is MIT licensed. It requires Node.js 22.12 or newer, Ollama, and a locally installed model.
ollama pull gemma3:4b
npm install
npm start
Open http://127.0.0.1:8789. If the model is already installed, skip the pull. Ollama must be running.
How I Built It
The stack is React + Vite, a small TypeScript/Hono API, and Gemma 3 4B through Ollama.
There are two separate paths:
Local storytelling: the model receives the selected station and optional detail, then returns a title and script. The server validates the output and its word count before saving it. An invalid-length story gets one repair attempt; a failure is shown explicitly rather than replaced with a canned story.
One real-model test added a line like "(Static hiss fades into music)" despite being asked for spoken prose. That would sound odd if a narrator read it aloud. I now remove standalone production cues and Markdown emphasis before counting the words, saving the transcript or requesting narration. Both reading and listening use the same script.
The handling is in src/server/ollama.ts:21–29 and src/server/ollama.ts:117–124.
Optional narration: ElevenLabs reads the finished script; it does not write the story. The API key stays on the server. Before any speech request, the user sees the exact text and approves sharing it and using credits.
Approval expires after two minutes and is tied to the script, voice and audio model. Cancelled, changed or already-used approvals are rejected. Duplicate in-flight requests share one synthesis, and saved recordings are reused rather than regenerated on replay.
Those boundaries are in src/server/service.ts:66–109.
Why Does Open Innovation Matter?
The open-weight model is the writer, not decoration. Gemma creates the broadcasts on my machine. This is not a closed story-generation API with an open-source interface around it.
The text experience works without an internet connection once the model is downloaded. No hosted AI key or per-story API charge is needed. ElevenLabs is optional and clearly identified as a cloud service.
The stories are portable. Transcripts are stored locally and can be downloaded as plain text. They are not trapped inside a subscription or a service that might disappear.
I can change the model without changing the experience. Settings accepts installed local models, while the word limits, storage and narration-consent rules stay in application code. Cloud-backed Ollama models are deliberately excluded.
The Spirit of It
In one test broadcast, the lighthouse keeper received a ship's arrival report forty years too late. The captain described the harbour as though nothing had changed.
The story closed with:
Sometimes, the sea remembers more than we do.
That is the mood I wanted: an ordinary transmission carrying something that should have faded away. The scene and the line are generated fiction, not a real historical account or a claimed audience reaction.
Prize Categories
- Best Use of Gemma: Gemma 3 4B generates the original broadcasts locally through Ollama.
- Best Use of GitHub Copilot: Copilot assisted with implementation, regression tests and browser validation.
Top comments (0)