<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: 927tanmay</title>
    <description>The latest articles on DEV Community by 927tanmay (@927tanmay).</description>
    <link>https://dev.to/927tanmay</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F398519%2F8db6194f-b540-4c2c-952f-7a712ab56d65.jpeg</url>
      <title>DEV Community: 927tanmay</title>
      <link>https://dev.to/927tanmay</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/927tanmay"/>
    <language>en</language>
    <item>
      <title>I Built a 3D AI Voice Avatar That Runs Entirely in Your Browser — No Servers, No API Keys, No GPU Cloud</title>
      <dc:creator>927tanmay</dc:creator>
      <pubDate>Tue, 11 Aug 2026 17:05:53 +0000</pubDate>
      <link>https://dev.to/927tanmay/i-built-a-3d-ai-voice-avatar-that-runs-entirely-in-your-browser-no-servers-no-api-keys-no-gpu-i4p</link>
      <guid>https://dev.to/927tanmay/i-built-a-3d-ai-voice-avatar-that-runs-entirely-in-your-browser-no-servers-no-api-keys-no-gpu-i4p</guid>
      <description>&lt;p&gt;&lt;em&gt;Speech recognition, voice synthesis, and lip-synced 3D animation — running inside Web Workers on your desktop browser. Here's how.&lt;/em&gt;&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; I open-sourced a React component that drops a fully conversational, lip-syncing 3D avatar into any web app. Built primarily as a drop-in 3D frontend for your existing cloud LLMs (OpenAI, Claude, custom backends), it handles speech recognition (Whisper), voice synthesis (Kokoro TTS), and real-time ARKit facial blendshape animation in-browser via WebAssembly and WebGPU. It can also run completely offline with on-device models. One &lt;code&gt;npm install&lt;/code&gt;, one component, done.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;🔗 &lt;strong&gt;&lt;a href="https://react-ai-voice-avatar.vercel.app/" rel="noopener noreferrer"&gt;Live Demo&lt;/a&gt;&lt;/strong&gt; · 📦 &lt;strong&gt;&lt;a href="https://www.npmjs.com/package/react-ai-voice-avatar" rel="noopener noreferrer"&gt;NPM Package&lt;/a&gt;&lt;/strong&gt; · 🐙 &lt;strong&gt;&lt;a href="https://github.com/927tanmay/react-ai-voice-avatar" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem That Kept Bugging Me
&lt;/h2&gt;

&lt;p&gt;Every time I explored building a conversational AI interface — the kind where a character actually &lt;em&gt;talks back to you&lt;/em&gt; — I hit the same wall:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cloud TTS APIs&lt;/strong&gt; charge per character and add 200–500ms of round-trip latency.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;WebSocket video streaming&lt;/strong&gt; from GPU servers is fragile, expensive, and adds heavy infrastructure overhead.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Existing avatar libraries&lt;/strong&gt; just give you a static 3D model. You still have to wire up speech, lip-sync, turn-taking, and microphone handling yourself (which takes months of integration).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I wanted something different: a single React component where I write &lt;code&gt;&amp;lt;AiVoiceAvatar/&amp;gt;&lt;/code&gt; and it just... works. The avatar listens, thinks, speaks with a natural voice, and moves its mouth in perfect sync — serving as the perfect visual layer for your AI backend.&lt;/p&gt;

&lt;p&gt;So, I built it.&lt;/p&gt;




&lt;h2&gt;
  
  
  What It Actually Does
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;react-ai-voice-avatar&lt;/code&gt;&lt;/strong&gt; is a React + React Three Fiber component that orchestrates an entire voice conversation pipeline inside the browser:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;🎤 Microphone → Whisper ASR → LLM Reasoning → Kokoro TTS → 3D Lip-Sync → 🔊 Speaker&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flpvb8f6hxbpbsegwg2sw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flpvb8f6hxbpbsegwg2sw.png" alt=" " width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Every heavy ML stage runs inside dedicated &lt;strong&gt;Web Workers&lt;/strong&gt;. This ensures the main thread stays buttery smooth at 60 FPS while the neural networks execute quietly in the background.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Two-Brain Architecture
&lt;/h3&gt;

&lt;p&gt;While the package supports fully offline execution, it was built first and foremost to plug into your existing cloud infrastructure:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Brain Mode&lt;/th&gt;
&lt;th&gt;How It Works&lt;/th&gt;
&lt;th&gt;Best Used For&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;🧠 Connected Brain (Primary)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Route transcribed speech to your existing backend (OpenAI, Claude, custom FastAPI, Vercel AI SDK, etc.) using the &lt;code&gt;onSubmit&lt;/code&gt; prop.&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Production web apps, SaaS products, and enterprise AI assistants.&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;🔒 On-Device Brain (Offline)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A 0.5B parameter LLM (Qwen 2.5) runs via WebGPU locally. No API keys or network calls required.&lt;/td&gt;
&lt;td&gt;Privacy-sensitive web apps, kiosks, or demo environments with spotty WiFi.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For the &lt;strong&gt;Connected Brain&lt;/strong&gt;, the avatar handles all the complex frontend tasks: listening, transcribing, audio playing, and lip animation. Your backend just streams the text back:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Connected Brain: Your backend handles the thinking&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;AiVoiceAvatar&lt;/span&gt;
  &lt;span class="na"&gt;avatarPreset&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"ananya"&lt;/span&gt;
  &lt;span class="na"&gt;ttsVoice&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"af_heart"&lt;/span&gt;
  &lt;span class="na"&gt;onSubmit&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userSpeech&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/chat&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;userSpeech&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Streams natively!&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. The 3D character loads over CDN, the TTS and ASR models download once (cached in IndexedDB permanently), and your app gets an interactive 3D character connected directly to your existing AI API.&lt;/p&gt;




&lt;h2&gt;
  
  
  Under the Hood: How It Works
&lt;/h2&gt;

&lt;p&gt;I spent weeks getting this architecture right. Here are the most interesting technical hurdles I had to cross.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Web Worker Isolation
&lt;/h3&gt;

&lt;p&gt;Running Whisper or Kokoro on the main thread would freeze the web app instantly. To fix this, every model runs in isolation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;ML Pipeline Worker:&lt;/strong&gt; Handles Whisper ASR transcription and (optionally) the local Qwen LLM. Streams LLM tokens back to the main thread as they're generated.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Kokoro TTS Worker:&lt;/strong&gt; Converts text chunks into 24kHz Float32 audio. Runs the Kokoro-82M ONNX model using multi-threaded WASM or WebGPU acceleration.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These workers are pre-bundled using esbuild at build time and stringified into the package. &lt;strong&gt;You don't have to host worker files or configure Webpack.&lt;/strong&gt; It works instantly with Vite, Next.js, or Create React App.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Real-Time Lip Synchronization (80 FPS)
&lt;/h3&gt;

&lt;p&gt;This was the hardest part. The avatar needs to move its mouth naturally and in perfect sync with the audio stream. I built a &lt;strong&gt;dual-source blending&lt;/strong&gt; approach:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Phoneme Timing Engine:&lt;/strong&gt; We extract phoneme-level timing data from Kokoro and map it to 15 standard viseme shapes. This gives us &lt;em&gt;predictive&lt;/em&gt; mouth shapes that lead the audio slightly, mimicking real human speech.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Audio Amplitude Fallback:&lt;/strong&gt; A Web Audio API &lt;code&gt;AnalyserNode&lt;/code&gt; reads real-time frequency data, providing a secondary signal blended for amplitude-driven jaw movement.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Procedural Facial Dynamics:&lt;/strong&gt; The avatar has continuous idle micro-animations (randomized Poisson interval blinks, breathing, head drift) so it feels alive even when silent.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All of this targets the &lt;strong&gt;52 standard Apple ARKit blendshapes&lt;/strong&gt;, meaning any humanoid &lt;code&gt;.glb&lt;/code&gt; model rigged with these morph targets works out of the box.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Safari &amp;amp; WASM Limits: The Invisible OOM Safety Net
&lt;/h3&gt;

&lt;p&gt;Safari and restricted browser runtimes impose strict WebAssembly memory limits. Running heavier TTS models (~90MB) under tight WASM memory constraints can occasionally trigger &lt;code&gt;Out of memory&lt;/code&gt; errors.&lt;/p&gt;

&lt;p&gt;Instead of letting the component crash or fail silently, I built an &lt;strong&gt;automatic failover system&lt;/strong&gt;. If Kokoro initialization encounters memory restrictions, the engine transparently switches to a lightweight MMS TTS model (~30MB). The voice quality drops slightly, but the avatar keeps talking without breaking the user session. No crashes, no frozen UI, and zero developer intervention required.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Hard Interruption
&lt;/h3&gt;

&lt;p&gt;Real conversations are messy. If a user taps "Stop" mid-sentence, everything must halt instantly. I implemented a coordinated interrupt system across both Web Workers that clears all internal queues, aborts in-flight LLM generation via a sentinel error, and flushes the audio context.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Developer Experience
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Zero Configuration, Genuinely
&lt;/h3&gt;

&lt;p&gt;I'm allergic to "zero config" tools that require 14 setup steps. Here is the actual install process:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;react-ai-voice-avatar three @react-three/fiber @react-three/drei

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;AiVoiceAvatar&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-ai-voice-avatar&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Inside your R3F Canvas:&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;AiVoiceAvatar&lt;/span&gt; &lt;span class="na"&gt;avatarPreset&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"ananya"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No Vite &lt;code&gt;optimizeDeps&lt;/code&gt; overrides. No worker file hosting. The NPM footprint is only ~3.3 MB (including pre-bundled workers and viseme mapping). The heavy 3D models and neural network weights load on-demand over the network and cache in the browser.&lt;/p&gt;

&lt;h3&gt;
  
  
  Imperative Control
&lt;/h3&gt;

&lt;p&gt;For times when you need programmatic control, the component exposes a clean Ref API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;avatarRef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useRef&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;AiVoiceAvatarHandle&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Make the avatar speak programmatically&lt;/span&gt;
&lt;span class="nx"&gt;avatarRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;speak&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hello! How can I help you?&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Submit text as if the user spoke it&lt;/span&gt;
&lt;span class="nx"&gt;avatarRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;sendText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;What's the weather like?&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Hard-interrupt mid-speech&lt;/span&gt;
&lt;span class="nx"&gt;avatarRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;interrupt&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  What Surprised Me Building This
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Numbers break TTS models:&lt;/strong&gt; Kokoro chokes on symbols like &lt;code&gt;%&lt;/code&gt; or &lt;code&gt;$&lt;/code&gt;. I had to build a &lt;code&gt;sanitizeForSpeech&lt;/code&gt; preprocessor that translates "95% complete" to "ninety-five percent complete" before it hits the model.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Web Worker Bundling is a Nightmare:&lt;/strong&gt; Standard worker approaches break in library distribution. My solution (pre-compiling with esbuild and reconstructing as a Blob URL at runtime) isn't elegant, but it is universally compatible across all modern web bundlers.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;The core engine is stable and production-ready for web interfaces. Next on the roadmap:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Hindi &amp;amp; Indic Language Voices:&lt;/strong&gt; The phoneme engine supports retroflex and aspirated consonants, and the &lt;code&gt;visemeTable&lt;/code&gt; has Devanagari mappings. We just need to train the TTS voices!&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ready Player Me Integration:&lt;/strong&gt; Official support for RPM avatars.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Conversation Memory:&lt;/strong&gt; APIs like &lt;code&gt;addContext()&lt;/code&gt; for injecting dynamic knowledge mid-conversation.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Try It Out
&lt;/h2&gt;

&lt;p&gt;If you want to see the 80FPS lip-sync and streaming TTS integration in action, check out the demo:&lt;/p&gt;

&lt;h3&gt;
  
  
  🌐 &lt;a href="https://react-ai-voice-avatar.vercel.app/" rel="noopener noreferrer"&gt;Live Demo — react-ai-voice-avatar.vercel.app&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://github.com/927tanmay/react-ai-voice-avatar" rel="noopener noreferrer"&gt;GitHub repo&lt;/a&gt; includes four complete example apps, ranging from a 30-line quickstart to a full hybrid-cloud OpenAI streaming integration.&lt;/p&gt;

&lt;p&gt;If you build something with this, I'd genuinely love to see it. Open an issue, tag me, or drop a comment below,  or &lt;strong&gt;&lt;a href="https://www.linkedin.com/in/927tanmay/" rel="noopener noreferrer"&gt;connect with me on LinkedIn&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;




</description>
      <category>ai</category>
      <category>opensource</category>
      <category>webgpu</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
