A fully autonomous AI radio station that broadcasts 24/7, monitors real-time news, and takes live caller interactions — built for the Combine Firecrawl Search with ElevenAgents hackathon.
Pulse is not a chatbot. It is a persistent, streaming media system: a complete radio newsroom in code form. An AI presenter named Pulse speaks continuously with editorial personality, covering AI, startups, and technology. News agents scan sources in real time, an editorial pipeline curates and enriches stories, a schedule planner fills the day, and listeners can call in live to challenge, question, or redirect the broadcast.
- Firecrawl Search — turns any website into clean, LLM-ready data. The Search API lets agents search the web and get structured content back in a single call, powering the real-time news pipeline with fresh knowledge about anything on the internet.
- ElevenLabs Conversational AI (ElevenAgents) — natural, human-sounding voice agents that access external tools and take actions. Every voice on Pulse (presenter, co-host, guest, screener) is an ElevenAgent with its own personality, voice, and tool set.
- Google Gemini — text LLM backbone (Pro for editorial reasoning and schedule planning, Flash Lite for fast enrichment and research, embeddings for deduplication).
- Key Features
- Architecture
- The Agent System
- News Pipeline Deep-Dive
- Tech Stack
- Getting Started
- Usage
- API Reference
- WebSocket Protocol
- Frontend
- Deployment
Pulse speaks without stopping. The presenter maintains a coherent narrative across topics, transitions naturally between stories, references earlier coverage, and adapts tone to urgency. Audio streams as 24kHz 16-bit PCM over WebSocket to all connected listeners simultaneously.
Four independent scouts poll news sources in parallel:
- Firecrawl Scanner — searches the live web via Firecrawl Search API for keyword-matched articles, returning structured, LLM-ready content in a single call. This is the primary source of real-time news intelligence.
- RSS Scanner — TechCrunch, The Verge, and configurable feeds
- Reddit Scout — r/MachineLearning, r/artificial, scored by upvote velocity
- Trending Scout — Google Search grounding via Gemini for emerging stories
Candidates flow into an Editor Agent that deduplicates (embedding-based cosine similarity), assigns confidence levels (confirmed / developing / rumor), detects breaking news, and prioritizes by relevance.
Every editorial brief passes through an Article Enricher that fetches all source URLs, extracts content, and generates a structured report:
- 4–8 sentence broadcast summary
- Key findings with specific facts, numbers, and dates
- 3–5 analysis angles (implications, controversies, comparisons)
- 10–15 sequential turn prompts — non-overlapping mini-briefs that guide the presenter through a multi-minute deep dive without repeating a single fact
If source content is thin, a Research Agent automatically performs a Google Search deep-dive to supplement.
A Schedule Planner Agent generates the next 2–3 hours of programming from available briefs and music tracks. It produces a mix of topic blocks, music transitions, guest interviews, and call-in segments — respecting narrative continuity, avoiding topic repetition, and alternating formats. The planner runs automatically after each editorial cycle, adapting to new stories as they arrive.
An Auto-Pilot orchestrates the full pipeline: scan every 60 minutes, process into briefs, deduplicate, enrich, plan schedule, and inject the highest-priority story if the presenter is idle.
Listeners connect from the webapp via microphone (or camera). The system routes calls based on line status:
- Lines open (during scheduled call-in segments): caller audio goes directly to the presenter's ElevenLabs session. Pulse hears the caller in real time, responds naturally, and integrates the interaction on-air with full barge-in support.
- Lines closed: a Screener Agent (voice: Jessica) greets the caller privately, captures their message via a
relay_messagetool, and stores structured data (greetings, questions, news tips, song requests) for the presenter. Screener audio never reaches the broadcast.
The admin (or schedule planner) can launch a guest expert interview. A second ElevenLabs Conversational AI session is created with a distinct voice and personality. Presenter and guest exchange turns naturally — each hearing the other's transcript and responding conversationally — until the segment ends.
After the presenter exhausts all turn prompts for a story, a co-host named Nova (voice: Leda) automatically joins. They exchange takes on the topic for up to 3 turns — agreeing, pushing back, adding angles — before the presenter wraps up. This prevents dead air and adds editorial depth between stories.
Powered by the ElevenLabs Music API. When a caller explicitly requests music during a live call, the presenter invokes a tool that generates an original instrumental track. The prompt is first enhanced by an LLM to avoid copyright issues, then sent to ElevenLabs. Output is converted to 24kHz mono WAV for broadcast compatibility.
Pre-generated tracks can also be scheduled as transition blocks between news segments.
A Music Scheduler generates a fresh library of 10 tracks every 72 hours at 3 AM, cycling through 20 predefined radio styles (ambient, lo-fi, jazz, synthwave, etc.). Tracks are generated sequentially via ElevenLabs Music API with a 5-second cooldown between each to respect rate limits. The admin can also trigger a batch manually from the Music Library header. This ensures the station always has varied, recent music for transitions without human intervention.
The scheduler tracks the remaining time in every topic and guest block. 30 seconds before a block ends, it sends a private wrap-up cue to the presenter ("You have ~30 seconds, start wrapping up"), giving Pulse time to close the segment gracefully instead of being cut off mid-sentence.
If the content finishes early (the co-host wraps up, the guest leaves, or the presenter exhausts all turn prompts), the scheduler calculates the remaining block time and automatically plays a random track from the music library as fill music. The fill stops cleanly when the next block begins.
A full admin panel provides real-time control:
- Live transcript with role-colored entries (presenter, caller, guest, co-host, system)
- Injection controls — send breaking news, soft notes, or co-anchor cues
- News desk — scan sources, filter briefs by status, research stories, send to air
- NLE-style schedule timeline — drag-and-drop blocks, zoom, playhead tracking, auto-generate
- Music library — browse generated tracks, preview, add to schedule
The frontend is a full-screen dark-theme radio player with:
- Animated organic blob visualizer (canvas-based, beat-reactive)
- Glass morphism UI with ambient vermillion glow
- Now Playing card with confidence badges and source attribution
- Horizontal breaking news ticker
- News image overlay with carousel for multi-image stories
- Call-in flow: setup modal, full-screen connecting animation, floating live call panel
┌─────────────────────────────┐
│ Web Frontend │
│ Next.js 16 · React 19 · TS │
└──────────────┬──────────────┘
│ WebSocket
┌──────────────▼──────────────┐
│ Agent Server │
│ Hono · TypeScript · Node │
├─────────────────────────────┤
│ │
│ ElevenLabs Voice Sessions │
│ ├─ Presenter (Pulse/Daniel) │
│ ├─ Co-Host (Nova/Lily) │
│ ├─ Guest (configurable) │
│ └─ Screener (Jessica) │
│ │
│ News Agents │
│ ├─ Firecrawl Scanner │
│ ├─ RSS Scanner │
│ ├─ Reddit Scout │
│ ├─ Trending Scout │
│ ├─ Editor Agent │
│ ├─ Research Agent │
│ ├─ Article Enricher │
│ └─ News Dedup (embeddings) │
│ │
│ Orchestration │
│ ├─ Auto-Pilot │
│ ├─ Schedule Planner │
│ ├─ Scheduler (15s loop) │
│ └─ Daily Memory │
│ │
│ Media │
│ ├─ Music Player (WAV) │
│ └─ Music Generator (11Labs) │
│ │
│ Persistence │
│ ├─ SQLite (WAL mode) │
│ └─ Operation Locks │
│ │
└──────────┬──────────────────┘
│
┌────────────────┼────────────────┐
│ │ │
Google APIs External APIs Local Storage
├─ Gemini Pro ├─ RSS Feeds ├─ data/pulse.db
├─ Gemini Flash ├─ Reddit JSON └─ media/*.wav
├─ Embeddings ├─ Firecrawl
├─ Google Search ├─ ElevenLabs
The system is split into two fully independent services — a Next.js frontend and a Hono backend — connected through a single WebSocket that carries four types of real-time data: PCM audio, transcript entries, caller audio, and status updates. Everything below describes what lives inside each layer.
The listener-facing page is a full-screen dark radio player with an animated organic blob visualizer, a play/pause + volume bar, a "Now Playing" card showing the current story (with confidence badge and source attribution), a horizontal breaking news ticker, and an image overlay carousel for stories that include visuals. The call-in flow lets a listener pick a name, choose audio or video mode, see a connecting animation, and then talk to the presenter through a floating live-call panel with waveform, timer, and mute/camera controls.
The admin panel is a separate route (/admin) with five control areas: a live transcript feed color-coded by role, injection buttons (breaking news, soft note, co-anchor cue), a news desk to scan sources and manage editorial briefs, an NLE-style schedule timeline with drag-and-drop blocks, zoom, and playhead, and a music library for browsing, previewing, and scheduling AI-generated tracks.
A single persistent WebSocket at /ws/radio handles everything:
- Audio (server → client) — PCM 24kHz 16-bit mono chunks, base64-encoded. The listener page decodes and plays them through the Web Audio API.
- Transcripts (server → client) — every spoken sentence with its role (pulse, caller, guest, cohost, producer, system) for the live rolling transcript.
- Caller audio (client → server) — PCM 16kHz from the listener's microphone when they call in.
- Status updates (bidirectional) — radio state changes, schedule updates, call-line status, guest/co-host session events, and image payloads for visual overlays.
Four concurrent ElevenLabs Conversational AI sessions run inside the backend, each with a distinct personality and voice:
- Presenter (Pulse, voice: Daniel) — the main host. Receives editorial briefs as production cues, speaks continuously, handles caller audio with barge-in, and coordinates with the co-host and guest sessions. This session is always active while the radio is on.
- Co-Host (Nova, voice: Lily) — a secondary voice that joins automatically when the presenter finishes all turn prompts for a topic. They exchange up to 3 turns of editorial banter (agreeing, challenging, adding angles) before handing back.
- Guest (configurable voice) — launched by the admin or schedule planner for expert interview segments. A separate ElevenLabs session with a custom system instruction describing the guest's expertise and personality.
- Screener (Jessica) — active when call-in lines are closed. Greets callers privately, captures their messages via the
relay_messagetool (greetings, questions, news tips, song requests), and relays structured data back to the presenter as a soft note. Its audio never reaches the broadcast.
All sessions are auto-provisioned at server startup via the ElevenLabs REST API (agent-provision.ts). Each agent gets a unique agent ID, custom system prompt, and voice override.
News flows through four stages:
-
Scouts — four independent agents poll external sources in parallel:
- Firecrawl Scanner searches the live web via Firecrawl Search API, returning structured, LLM-ready content with metadata and images. This is the main discovery engine — a single call yields clean markdown from any website.
- RSS Scanner parses configured feeds (TechCrunch, The Verge, etc.) using
rss-parserand filters by URL to avoid duplicates. - Reddit Scout hits the public Reddit JSON API for monitored subreddits and scores posts by upvote velocity to detect trending discussions.
- Trending Scout uses Gemini 3.1 Flash Lite with the
googleSearchgrounding tool to surface stories that are gaining traction on the open web.
-
Editor Agent — receives all candidates, removes duplicates via embedding-based cosine similarity (
text-embedding-004, threshold 0.85), assigns a confidence level (confirmed / developing / rumor), detects whether a story qualifies as breaking news, and ranks everything by relevance and urgency. Usesgemini-3.1-pro-previewfor complex editorial reasoning. -
Article Enricher — fetches every source URL from the brief, extracts the page content, and generates a structured broadcast report: a 4–8 sentence summary, key findings with dates and numbers, 3–5 analysis angles, and 10–15 sequential turn prompts — non-overlapping mini-briefs that guide the presenter through a multi-minute deep dive. Uses
gemini-3.1-flash-lite-preview. -
Research Agent — activated when the enricher determines source content is too thin. Performs a Google Search deep-dive via Gemini's grounding tool and feeds the results back into the enrichment report.
Three components keep the station running autonomously:
- Auto-Pilot — a timer-based orchestrator that fires every 60 minutes. It triggers all scouts, runs the editor pipeline, enriches new briefs, invokes the schedule planner, and injects the highest-priority story if the presenter is idle.
- Schedule Planner — an AI agent (
gemini-3.1-pro-previewwith structured JSON output) that generates the next 2–3 hours of programming. It mixes topic blocks, music transitions, guest interviews, and call-in segments, respecting narrative continuity and avoiding topic repetition. - Scheduler — a tight 15-second execution loop that walks the timeline, starts each block when its time arrives, and advances the playhead. It coordinates with the presenter, music player, and guest/co-host sessions. It also sets a wrap-up timer for topic and guest blocks: 30 seconds before the block ends, it sends a private cue to the presenter so Pulse can close the segment naturally. If content finishes early (co-host wraps, guest leaves, turn prompts exhausted), the scheduler calculates remaining time and plays a random fill music track from the library until the next block starts.
- Music Scheduler — generates 10 fresh tracks every 72 hours at 3 AM using ElevenLabs Music API, cycling through 20 radio styles (ambient, lo-fi, jazz, synthwave, etc.). Runs sequentially with a 5-second cooldown between tracks. Can also be triggered manually via the admin panel.
A Daily Memory module logs each day's show in the SQLite database: topics covered, caller interactions, music played, and key moments. This is loaded into the presenter's context the next day for editorial continuity.
- Music Player — streams pre-generated WAV files (24kHz mono) into the audio output, mixed alongside the presenter voice. Used for transitions, intros, scheduled music blocks, and automatic fill music when content ends early.
- Music Generator — connects to the ElevenLabs Music API to generate original instrumental tracks on demand. The raw prompt is first enhanced by an LLM to improve musical quality and avoid copyright patterns, then sent to ElevenLabs. Output is converted to 24kHz mono for broadcast compatibility.
- Music Scheduler — batch generator that produces 10 tracks every 72 hours at 3 AM across 20 radio styles. Ensures the station always has fresh, varied music without manual intervention.
| API | Role in the system |
|---|---|
| Search API | Firecrawl Scanner (web search with structured results), Research Agent (deep-dive discovery) |
| Scrape API | Article Enricher (full-page markdown extraction), Research Agent (source content fetching) |
| API | Role in the system |
|---|---|
| Conversational AI (WebSocket) | All real-time voice — presenter, co-host, guest, screener, producer |
| Music API | AI music generation (on-demand + batch scheduling) |
| API | Role in the system |
|---|---|
Gemini 3.1 Pro (gemini-3.1-pro-preview) |
Editor Agent (editorial reasoning), Schedule Planner (structured output) |
Gemini 3.1 Flash Lite (gemini-3.1-flash-lite-preview) |
Article Enricher, Research Agent, Trending Scout (fast inference) |
Text Embeddings (text-embedding-004) |
News deduplication via cosine similarity |
| Google Search (grounding tool) | Trending Scout + Research Agent web lookups |
| Source | Protocol | Used by |
|---|---|---|
| Firecrawl | Search + Scrape API | Firecrawl Scanner, Article Enricher, Research Agent |
| RSS Feeds (TechCrunch, The Verge, etc.) | HTTP/XML | RSS Scanner |
| Public JSON API | Reddit Scout | |
| Google Search | Gemini grounding tool | Trending Scout, Research Agent |
All runtime state is stored in a single SQLite database at agent-server/data/pulse.db (WAL mode for concurrent reads, gitignored):
| Table | Contents |
|---|---|
schedules |
One row per day (date PK, blocks as JSON) |
candidates |
Raw news candidates per station |
briefs |
Editorial briefs per station |
embeddings |
Embedding vectors for deduplication |
stations |
Station configuration |
daily_memory |
Show logs for editorial continuity |
music_library |
Generated track metadata |
op_locks |
Persistent operation locks (scan, process, auto-generate, music-batch) |
Generated WAV tracks are stored as files under agent-server/media/.
Operation Locks prevent concurrent execution of long-running operations. Locks are persisted in SQLite, surviving page reloads and server restarts (with automatic cleanup of stale locks >10 minutes). The frontend polls GET /radio/locks every 2 seconds and disables buttons accordingly.
For a detailed breakdown of every agent — how it works, what sources it uses, a usefulness rating, a full pipeline walkthrough, and a 24/7 cost estimate — see docs/news-pipeline.md.
| Agent | Source | Model | Method |
|---|---|---|---|
| Firecrawl Scanner | Live web | None | Firecrawl Search API, structured markdown + metadata |
| RSS Scanner | Configurable feeds | None | rss-parser library, URL dedup |
| Reddit Scout | Subreddits | None | Public JSON API, upvote scoring |
| Trending Scout | Google Search | gemini-3.1-flash-lite-preview |
Gemini with googleSearch tool |
| Agent | Role | Model |
|---|---|---|
| Editor Agent | Dedup, prioritize, assign confidence, detect breaking | gemini-3.1-pro-preview |
| Research Agent | Deep-dive via Google Search when info is thin | gemini-3.1-flash-lite-preview + googleSearch |
| Article Enricher | Fetch sources, generate broadcast report + turn prompts | gemini-3.1-flash-lite-preview |
| News Dedup | Embedding-based cosine similarity (>0.85 = duplicate) | text-embedding-004 |
| Agent | Role | Model |
|---|---|---|
| Schedule Planner | Generate 2–3 hour schedule from briefs + tracks | gemini-3.1-pro-preview (structured output) |
| Auto-Pilot | Orchestrate scan → process → enrich → plan → inject cycle | Timer-based coordination |
| Session | Voice | Role |
|---|---|---|
| Presenter (Pulse) | Daniel | Main host — editorial analysis, deep dives, caller interaction |
| Co-Host (Nova) | Lily | Post-topic discussion partner, 3-turn exchanges |
| Guest | Configurable | Expert interviews with distinct personality |
| Screener | Jessica | Captures caller messages when lines are closed |
All live sessions use ElevenLabs Conversational AI agents, auto-provisioned at startup via the REST API.
- Next.js 16 (App Router, SSR, standalone output for Docker)
- React 19 with context-based state management
- Tailwind CSS v4 (exclusive styling — no CSS modules)
- Lucide React for icons
- Web Audio API for PCM playback at 24kHz
- Custom fonts: Syne, Space Grotesk, Instrument Serif
- Hono with
@hono/node-serverand@hono/node-ws - Firecrawl Search API + Scrape API for real-time web intelligence
- ElevenLabs Conversational AI (WebSocket) + Music API for voice agents and music generation
- @google/genai SDK for Gemini text LLM tasks (editorial reasoning, enrichment, embeddings)
- rss-parser for RSS feed ingestion
- better-sqlite3 for persistence (WAL mode, single file at
data/pulse.db) - TypeScript throughout
| API | Purpose |
|---|---|
| Search API | Real-time web search with structured content (Firecrawl Scanner, Research Agent) |
| Scrape API | Full-page markdown extraction for article enrichment (Article Enricher, Research Agent) |
| Service | Purpose |
|---|---|
| Conversational AI (WebSocket) | All real-time voice sessions (presenter, guest, co-host, screener, producer) |
| Music API | AI music generation (on-demand + 72-hour batch) |
| Model | Purpose |
|---|---|
gemini-3.1-pro-preview |
Editor Agent, Schedule Planner (complex reasoning) |
gemini-3.1-flash-lite-preview |
Enricher, Research, Trending Scout (fast inference) |
text-embedding-004 |
News deduplication via cosine similarity |
- Node.js 18+ (22 recommended)
- npm
- A Firecrawl API key (firecrawl.dev)
- An ElevenLabs API key (elevenlabs.io)
- A Google Cloud account with Gemini API enabled
git clone https://github.com/Abel1011/pulsefm.git
cd pulsefm
# Backend
cd agent-server
npm install
# Frontend
cd ../web
npm installCreate agent-server/.env:
# Firecrawl (required — web search + scraping for news pipeline)
FIRECRAWL_API_KEY=your_firecrawl_api_key
# ElevenLabs (required — voice agents + music generation)
ELEVENLABS_API_KEY=your_elevenlabs_api_key
# Gemini API (required — text LLM tasks)
GOOGLE_API_KEY=your_gemini_api_key
# Vertex AI (alternative to API key)
# GOOGLE_GENAI_USE_VERTEXAI=true
# GOOGLE_CLOUD_PROJECT=your_project_id
# GOOGLE_CLOUD_LOCATION=us-central1
PORT=3001# Terminal 1 — Backend
cd agent-server
npm run dev
# Terminal 2 — Frontend
cd web
npm run dev- Frontend: http://localhost:3000
- Backend: http://localhost:3001
- Admin panel: http://localhost:3000/admin
- Open the admin panel at
/admin - Click Start to begin broadcasting
- The auto-pilot will scan for news, process briefs, and generate a schedule
- Open the listener page at
/and press play to hear the stream
- Start radio — begins the presenter session and auto-pilot
- Auto-pilot runs — scans sources every 60 min, processes briefs, generates schedule
- Monitor transcript — real-time feed of everything said on air
- Inject editorially — send breaking news alerts, soft notes, or co-anchor cues
- Manage schedule — drag blocks on timeline, add/edit/delete, auto-generate
- News desk — manually scan, research individual briefs, send to air
- Launch guests — start expert interview segments with configurable voice
- Music — generate original tracks via ElevenLabs Music API, preview, schedule or play immediately
- Open the page — dark immersive UI with animated visualizer
- Press play — audio streams immediately
- Follow along — now playing card, breaking news ticker, news images
- Call in — click the call button, enter your name, choose audio/video, go live
- Talk to Pulse — interrupt naturally (barge-in supported), debate a story, request a topic
| Method | Endpoint | Description |
|---|---|---|
| POST | /radio/start |
Start broadcasting (initializes presenter, scheduler, auto-pilot) |
| POST | /radio/stop |
Stop broadcasting (closes all sessions, freezes schedule) |
| GET | /radio/status |
Returns { presenting, listeners, transcript, guest, activeBlockType } |
| POST | /radio/inject |
Inject content: { type: 'breaking'|'soft'|'co-anchor', text, imageUrl?, turnPrompts? } |
| POST | /radio/inject-news |
Smart inject: direct if topic active, auto-creates block if idle |
| GET | /radio/locks |
Returns { scan, process, 'auto-generate', 'music-batch' } — current operation lock states |
| Method | Endpoint | Description |
|---|---|---|
| POST | /radio/guest/start |
{ name, expertise, topic, voice? } — launches guest session |
| POST | /radio/guest/stop |
Ends guest segment |
| GET | /radio/guest/status |
Active guest details |
| Method | Endpoint | Description |
|---|---|---|
| POST | /news/:stationId/scan |
Run all scouts |
| POST | /news/:stationId/process |
Editor + enrichment pipeline |
| GET | /news/:stationId/briefs |
List briefs (?pending=true for unsent only) |
| POST | /news/:stationId/briefs/:id/research |
Deep research via Google Search |
| POST | /news/:stationId/briefs/:id/send |
Mark as sent to air |
| POST | /news/:stationId/briefs/:id/conclude |
Mark as concluded |
| Method | Endpoint | Description |
|---|---|---|
| GET | /schedule/:date |
Day schedule (YYYY-MM-DD) |
| POST | /schedule/:date/blocks |
Create block |
| PATCH | /schedule/:date/blocks/:id |
Update block |
| DELETE | /schedule/:date/blocks/:id |
Delete block |
| POST | /schedule/:date/blocks/:id/execute |
Execute immediately |
| POST | /schedule/:date/blocks/:id/skip |
Skip block |
| POST | /schedule/:date/auto-generate |
AI-generate next 2–3 hours (?scanFirst=true) |
| Method | Endpoint | Description |
|---|---|---|
| GET | /media/tracks |
List available WAV tracks |
| POST | /radio/music/generate |
{ prompt, durationSeconds?, bpm? } — generate via ElevenLabs Music API |
| GET | /radio/music/status |
Current generation status |
| GET | /radio/music/list |
All generated tracks with metadata |
| POST | /radio/music/play |
{ filename } — play track on air |
| POST | /radio/music/generate-batch |
Trigger daily batch generation (409 if already running) |
| GET | /radio/music/batch-status |
Batch generation progress |
| GET | /radio/music/file/:filename |
Stream WAV file |
Connect to ws[s]://host/ws/radio for bidirectional communication.
| Type | Payload | Description |
|---|---|---|
audio |
{ data: base64 } |
PCM 24kHz 16-bit mono audio chunk |
transcript |
{ text, role } |
Live transcript (roles: pulse, caller, guest, cohost, producer, system) |
turn-complete |
— | Presenter finished a turn |
interrupted |
— | Presenter was interrupted (barge-in) |
status |
{ presenting, callsOpen } |
Radio state update |
news-image |
{ imageUrl, imageUrls?, headline } |
Breaking news visual |
guest-started |
{ name, expertise } |
Guest segment began |
guest-ended |
— | Guest segment ended |
cohost-started |
{ name } |
Co-host discussion began |
cohost-ended |
— | Co-host discussion ended |
calls-open / calls-closed |
— | Call-in line status |
call-accepted |
{ mode: 'live'|'screener' } |
Caller connection established |
call-rejected |
{ reason } |
Caller denied |
screener-audio |
{ data: base64 } |
Private screener audio (caller only) |
audio-reset |
— | Flush audio queues (after producer interruption) |
schedule-update |
{ blockId, block } |
Schedule changed |
| Type | Payload | Description |
|---|---|---|
call-start |
{ name, mode } |
Initiate call |
caller-audio |
{ data: base64 } |
PCM 16kHz caller audio chunk |
call-end |
— | Hang up |
The UI follows an "On Air" design language — dark, warm, immersive:
- Base palette: warm blacks (#0C0A09), subtle surfaces (#1C1917), vermillion accent (#E54D2E)
- Typography: Syne (headlines), Space Grotesk (body), Instrument Serif (brand)
- Glass morphism: backdrop-blur panels for modals, controls, and overlays
- Ambient glow: pulsing vermillion gradients that react to broadcast state
- Confidence badges: green (confirmed), yellow (developing), orange (rumor)
| Component | Description |
|---|---|
AudioVisualizer |
Canvas-rendered organic blob with spectral tendrils, constellation particles, and beat-reactive pulsing |
RadioPlayer |
Play/pause with 12-bar VU meter volume control |
NowPlaying |
Current segment info with confidence badge and source list |
NewsTicker |
Horizontal scrolling ticker with breaking news highlight |
NewsImageOverlay |
Floating card with image carousel, auto-dismiss after 30s |
CallSetupModal |
Pre-call form (name + audio/video selection) |
LiveCallPanel |
Floating panel during active call with waveform, timer, mute/camera controls |
ScheduleTimeline |
NLE-style horizontal timeline with drag-drop, zoom, playhead, and auto-generate |
NewsPanel |
Editorial brief management with scan, filter, research, and inject actions |
Both services are containerized with multi-stage Docker builds. A docker-compose.yml at the root makes it easy to run everything with a single command.
- Copy
agent-server/.env.exampletoagent-server/.envand fill in your API keys. - Run:
docker compose up --build- Frontend: http://localhost:3000
- Backend: http://localhost:3001
- Admin panel: http://localhost:3000/admin
The SQLite database (data/pulse.db) and media (generated WAV tracks) are persisted in named Docker volumes.
Each service can also be built and run independently:
# Backend
cd agent-server
docker build -t pulse-backend .
docker run -p 3001:3001 --env-file .env pulse-backend
# Frontend
cd web
docker build -t pulse-web .
docker run -p 3000:3000 -e NEXT_PUBLIC_API_URL=http://localhost:3001 pulse-webThe frontend reads NEXT_PUBLIC_API_URL at runtime (injected via server-side rendering in layout.tsx) and derives WebSocket URLs automatically (http:// becomes ws://, https:// becomes wss://).
pulsefm/
├── agent-server/ # Backend
│ ├── src/
│ │ ├── index.ts # Entry point, routes, WebSocket, orchestration
│ │ ├── lib/
│ │ │ ├── elevenlabs-live.ts # ElevenLabs Conversational AI wrapper
│ │ │ ├── agent-provision.ts # Auto-provision ElevenLabs agents at startup
│ │ │ ├── presenter.ts # Presenter agent (Pulse)
│ │ │ ├── cohost.ts # Co-host agent (Nova)
│ │ │ ├── guest.ts # Guest expert sessions
│ │ │ ├── screener.ts # Phone screener agent
│ │ │ ├── scheduler.ts # 15-second execution loop
│ │ │ ├── auto-pilot.ts # News pipeline orchestrator
│ │ │ ├── music-player.ts # WAV streaming + fill music
│ │ │ ├── music-generator.ts # ElevenLabs Music API generation
│ │ │ ├── music-scheduler.ts # 72-hour batch generation (10 tracks, 20 styles)
│ │ │ ├── db.ts # SQLite database singleton (WAL mode)
│ │ │ ├── op-locks.ts # Persistent operation locks
│ │ │ ├── daily-memory.ts # Show history (SQLite)
│ │ │ ├── news-dedup.ts # Embedding-based deduplication
│ │ │ ├── news-store.ts # Brief/candidate persistence (SQLite)
│ │ │ ├── schedule-store.ts # Schedule persistence (SQLite)
│ │ │ ├── station-store.ts # Station config (SQLite)
│ │ │ └── agents/
│ │ │ ├── rss-scanner.ts
│ │ │ ├── reddit-scout.ts
│ │ │ ├── trending-scout.ts
│ │ │ ├── firecrawl-scanner.ts
│ │ │ ├── editor-agent.ts
│ │ │ ├── research-agent.ts
│ │ │ ├── article-enricher.ts
│ │ │ └── schedule-planner.ts
│ │ ├── routes/
│ │ │ ├── schedule.ts
│ │ │ └── news.ts
│ │ └── types/
│ │ ├── schedule.ts
│ │ ├── news.ts
│ │ └── station.ts
│ ├── data/ # Runtime state (gitignored)
│ ├── media/ # WAV tracks
│ └── Dockerfile
├── web/ # Frontend
│ ├── src/
│ │ ├── app/
│ │ │ ├── layout.tsx # Root layout + runtime config injection
│ │ │ ├── page.tsx # Listener page
│ │ │ └── admin/page.tsx # Admin control room
│ │ ├── components/
│ │ │ ├── AudioVisualizer.tsx
│ │ │ ├── RadioPlayer.tsx
│ │ │ ├── NowPlaying.tsx
│ │ │ ├── NewsTicker.tsx
│ │ │ ├── CallInButton.tsx
│ │ │ ├── LiveCallPanel.tsx
│ │ │ ├── RadioProvider.tsx
│ │ │ ├── MediaProvider.tsx
│ │ │ └── admin/
│ │ │ ├── ScheduleTimeline.tsx
│ │ │ ├── BlockEditor.tsx
│ │ │ └── NewsPanel.tsx
│ │ ├── services/
│ │ │ ├── radio-service.ts
│ │ │ └── schedule-service.ts
│ │ ├── hooks/
│ │ │ └── use-transcript-stream.ts
│ │ ├── lib/
│ │ │ └── config.ts # Runtime API URL resolution
│ │ └── types/
│ │ ├── radio.ts
│ │ └── schedule.ts
│ └── Dockerfile
├── docker-compose.yml # Run both services
└── README.md
This project was built for the Combine Firecrawl Search with ElevenAgents hackathon.