- Add 30s timeout to all frontend fetch calls (safeFetch)
- Add 20s asyncio.timeout around lock+LLM in chat, ai-respond, auto-respond
- Reduce OpenRouter timeout from 60s to 25s
- Reduce Inworld TTS timeout from 60s to 25s
- Return graceful fallback responses on timeout instead of hanging
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add ads playback system with backend endpoints and frontend UI
- Diversify AI callers: randomize voices per session, expand jobs/problems/interests/quirks/locations
- Update website tagline and descriptions to "biologically questionable organisms"
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Expanded meta description with keywords
- Canonical URL
- JSON-LD PodcastSeries structured data
- RSS alternate link for feed discovery
- robots.txt allowing all crawlers
- sitemap.xml
- llms.txt for LLM indexing
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When off air, callers hear a message and get disconnected. When on
air, calls route normally. Toggle button added to frontend header
with pulsing red ON AIR indicator.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Warm rustic bar palette (dark wood browns, orange neon accent, cream
text) replacing the previous navy/pink theme. Added ?v=2 to all
cover art URLs to force new image.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fetches episodes from Castopod RSS feed, renders episode cards with
play buttons, and provides a sticky bottom audio player with progress
bar and seeking. Falls back to CORS proxy if direct fetch fails.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Static site for lukeattheroost.com with cover art, phone number,
subscribe buttons (Spotify, YouTube, Apple, RSS), and OG meta tags.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Remove gpt-4o-realtime (WebSocket-only) from OpenRouter models
- Increase OpenRouter timeout to 60s and max_tokens to 150
- Handle empty LLM responses
- Fix publish_episode.py for current Castopod API fields
- Add port conflict check and graceful shutdown to run.sh
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add streamSid tracking and per-caller send locks for SignalWire
- Improve TTS streaming with real-time pacing and detailed logging
- Block host audio to caller during TTS playback
- Randomize caller names between sessions from name pools
- Update page title and show phone number in UI
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Replace aggressive sentence-count limiting with ensure_complete_thought()
which only trims if the LLM was actually cut off mid-sentence
- Softer prompt guidance for natural brevity instead of rigid sentence count
- max_tokens at 100 as natural length cap
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- max_tokens back to 150 so LLM can finish thoughts
- New limit_sentences() keeps only first 2 complete sentences
- Never cuts mid-sentence — always ends at punctuation
- Applied to both chat and auto-respond paths
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Log chunk count and peak audio level on recording stop
- Add null check on _recorded_audio in callback
- Small delay after stopping piggybacked recording for callback to finish
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Reduce max_tokens from 100 to 75 for shorter output
- Add truncate_to_complete_sentence() to trim at last punctuation
- Applied to both chat and auto-respond paths
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Much stronger prompt language: "no more than 2 sentences EVER"
- Added "DO NOT ramble" instruction
- Reduced max_tokens back to 100 as hard limit
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Increase max_tokens from 100 to 150 to avoid mid-sentence truncation
- Tighten prompt to 1-2 short sentences with emphasis on completing them
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Mute host mic forwarding while TTS is streaming to prevent interleaving
both audio sources into the same playback buffer
- Replace nearest-neighbor downsampling with box-filter averaging on both
server (host mic) and browser (caller mic) for anti-aliased resampling
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
TTS audio was sent as a single huge WebSocket frame that overflowed the
browser's 3s ring buffer. Now streams in 60ms chunks at real-time rate.
Also increased browser ring buffer from 3s to 10s as safety net.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The auto-respond function played AI TTS to the local Loopback channel
but didn't send it over WebSocket to the live caller in the browser.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Replace per-callback async task spawning with persistent queue-based sender
- Buffer host mic to 60ms chunks (was 21ms) to reduce WebSocket frame rate
- Reduce server ring buffer prebuffer from 150ms to 80ms
- Reduce browser playback jitter buffer from 150ms to 100ms
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When a live caller is on air, the host stream already has an InputStream
open. Opening a second one for push-to-talk recording causes a conflict.
Now recording piggybacks on the host stream callback instead.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Reduce capture chunk from 4096 to 640 samples (256ms → 40ms)
- Replace BufferSource scheduling with AudioWorklet playback ring buffer
- Add 80ms jitter buffer with linear interpolation upsampling
- Reduce host mic and live caller stream blocksizes from 4096/2048 to 1024
- Replace librosa.resample with numpy interpolation in send_audio_to_caller
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Use persistent callback-based output stream instead of opening/closing per chunk
- Replace librosa.resample with simple decimation in real-time audio callbacks
- Move host stream initialization to background thread to avoid blocking
- Change live caller channel default to 9
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>