<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[noaibills]]></title><description><![CDATA[noaibills]]></description><link>https://noaibills.hashnode.dev</link><generator>RSS for Node</generator><lastBuildDate>Sun, 01 Nov 2026 04:49:34 GMT</lastBuildDate><atom:link href="https://noaibills.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Building NoAIBills: A Chrome Extension That Runs LLMs Locally Using WebGPU, Transformers.js, and Chrome's Prompt API]]></title><description><![CDATA[Discover NoAIBills, a Chrome extension for running local LLMs with WebGPU, ensuring privacy and simplicity without cloud dependency or installations]]></description><link>https://noaibills.hashnode.dev/building-noaibills-a-chrome-extension-that-runs-llms-locally-using-webgpu-transformersjs-and-chromes-prompt-api</link><guid isPermaLink="true">https://noaibills.hashnode.dev/building-noaibills-a-chrome-extension-that-runs-llms-locally-using-webgpu-transformersjs-and-chromes-prompt-api</guid><category><![CDATA[webgpu]]></category><category><![CDATA[chrome extension]]></category><category><![CDATA[AI]]></category><category><![CDATA[llm]]></category><dc:creator><![CDATA[Shankar Ganesh]]></dc:creator><pubDate>Fri, 13 Feb 2026 03:01:35 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1770951587148/491c93a1-3726-4dc4-915c-468de57a4a21.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>No external software, no cloud, no complexity—just install and start chatting.</strong></p>
<p><strong>TL;DR:</strong> I built a Chrome extension that runs Llama, DeepSeek, Qwen, and other LLMs entirely in-browser. No server, no Ollama, no API keys. Here's the full story—why I built it, how the architecture works, and what I learned shipping it.</p>
<p><strong>Try it:</strong> <a target="_blank" href="https://noaibills.app/?utm_source=hashnode&amp;utm_medium=blog&amp;utm_campaign=launch">noaibills.app</a></p>
<hr />
<h2 id="heading-the-backstory">The backstory</h2>
<p>I kept running into the same friction with AI tools.</p>
<p><strong>Privacy made me hesitate.</strong> Every time I wanted to draft something sensitive—work emails, code reviews, personal notes—I'd pause before hitting enter. "This is going to OpenAI's servers." That thought never stopped bothering me.</p>
<p><strong>Local AI meant yak-shaving.</strong> Ollama is fantastic if you're technical. But I've tried recommending it to non-dev friends. The moment you say "open terminal," you've lost them. And if you're on a work laptop where IT locks everything down? You're stuck.</p>
<p><strong>Subscriptions for occasional use felt wasteful.</strong> I don't use AI every day. Maybe a few times a week for grammar fixes, quick summaries, or getting unstuck on code. $20/month for that? Didn't sit right.</p>
<p>So I asked myself: what if the AI ran <em>inside</em> the browser? No installs. No backend. No monthly fee. Just a Chrome extension that works.</p>
<p>WebGPU has matured enough to make this viable. So I built it.</p>
<hr />
<h2 id="heading-what-noaibills-actually-is">What NoAIBills actually is</h2>
<p>It's a Chrome extension that runs open-source LLMs locally in your browser.</p>
<p><strong>Three inference backends:</strong></p>
<ul>
<li><strong>WebLLM (MLC):</strong> WebGPU-based. Fast on decent GPUs. Runs Llama 3.2, DeepSeek R1, Qwen, Mistral, Gemma, Hermes.</li>
<li><strong>Transformers.js:</strong> ONNX Runtime on CPU. Lighter footprint. Good for smaller models like SmolLM.</li>
<li><strong>Browser AI (Prompt API):</strong> Chrome's built-in Gemini Nano. Zero download—just works.</li>
</ul>
<p><strong>Key features:</strong></p>
<ul>
<li>All messages stored locally in IndexedDB. Export or delete anytime.</li>
<li>Works offline once models are cached.</li>
<li>Real-time memory monitor that alerts at 80% usage and helps you clear unused model weights.</li>
<li>Reasoning UI for models like DeepSeek R1 that emit <code>&lt;think&gt;...&lt;/think&gt;</code> blocks.</li>
</ul>
<p>One install. No config. No server.</p>
<hr />
<h2 id="heading-the-honest-positioning">The honest positioning</h2>
<p>Let me be upfront about what this is and isn't.</p>
<p><strong>Local AI is good enough for most things.</strong> I'm not claiming it replaces GPT-4. But for the 80% of tasks—drafts, summaries, quick coding questions—a 3B parameter model running locally is plenty.</p>
<p>Not positioned as a cloud LLM replacement—it's for local inference on basic text tasks (writing, communication, drafts) with zero internet dependency, no API costs, and complete privacy.</p>
<p>Core fit: organizations with data restrictions that block cloud AI and can't install desktop tools like Ollama/LMStudio. For quick drafts, grammar checks, and basic reasoning without budget or setup barriers.</p>
<p>Need real-time knowledge or complex reasoning? Use cloud models. This serves a different niche—<strong>not every problem needs a sledgehammer</strong> 😄.</p>
<hr />
<h2 id="heading-architecture-deep-dive">Architecture deep dive</h2>
<p>For those who want the technical details, here's how it all fits together.</p>
<h3 id="heading-the-core-pattern-one-transport-multiple-backends">The core pattern: one transport, multiple backends</h3>
<p>The app is a <strong>Next.js</strong> front end using <code>useChat</code> from the Vercel AI SDK. But instead of calling an API, it uses a custom <code>BrowserAIChatTransport</code> that runs <code>streamText</code> directly in the browser.</p>
<p>The transport:</p>
<ul>
<li>Reads provider and model from a Zustand store</li>
<li>Resolves the language model via a model manager</li>
<li>For reasoning models, wraps with <code>extractReasoningMiddleware</code> to parse think tags</li>
<li>Returns <code>result.toUIMessageStream()</code> so the UI stays unchanged</li>
</ul>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> baseModel = modelManager.getModel(provider, modelId);
<span class="hljs-keyword">const</span> model = isReasoningModel(modelId)
  ? wrapLanguageModel({
      model: baseModel,
      middleware: extractReasoningMiddleware({ tagName: <span class="hljs-string">"think"</span>, startWithReasoning: <span class="hljs-literal">true</span> }),
    })
  : baseModel;
<span class="hljs-keyword">const</span> result = streamText({ model, messages: modelMessages, ...streamOptions });
<span class="hljs-keyword">return</span> result.toUIMessageStream();
</code></pre>
<p>One transport. Three providers. The UI doesn't know the difference.</p>
<h3 id="heading-model-management">Model management</h3>
<p>The model manager handles:</p>
<ul>
<li>Instantiating the right adapter for each provider</li>
<li>Caching instances by <code>provider:modelId</code> so switching threads doesn't re-download</li>
<li>Progress callbacks for the loading UI</li>
</ul>
<p>All model IDs live in a single <code>models</code> module—filtered for low VRAM, tagged for capabilities like "supports reasoning" or "supports vision."</p>
<h3 id="heading-the-loading-experience">The loading experience</h3>
<p>Browser models need to download weights—sometimes gigabytes. Nobody wants to stare at a blank screen.</p>
<p>I built a <code>useModelInitialization</code> hook that:</p>
<ol>
<li>Checks if the model is cached (<code>availability()</code> returns <code>"available"</code>)</li>
<li>If not, triggers a minimal <code>streamText</code> call to start the download</li>
<li>Merges progress from both the model manager and stream into one smooth progress bar</li>
</ol>
<h3 id="heading-chrome-extension-quirks">Chrome extension quirks</h3>
<p>Making it work as an extension was the fiddly part.</p>
<p><strong>Static export.</strong> Next.js builds with <code>output: "export"</code>. The side panel loads <code>ui/index.html</code>.</p>
<p><strong>CSP restrictions.</strong> Chrome extensions don't allow inline scripts. I wrote a post-build script that extracts inline scripts, saves them as separate files, and rewrites the HTML.</p>
<p><strong>WASM loading.</strong> Transformers.js needs ONNX Runtime WASM files. Can't load from CDN in an extension. So the build copies them into <code>extension/transformers/</code> and sets <code>web_accessible_resources</code>.</p>
<p>One codebase. One build. Dev runs at localhost, production becomes an extension.</p>
<h3 id="heading-persistence">Persistence</h3>
<p>Chat history lives in IndexedDB via <strong>Dexie</strong>. Each conversation stores: id, title, model, provider, created date, and the full messages array.</p>
<p>When you load a conversation, it rehydrates everything—including which model you were using—so you can continue right where you left off.</p>
<hr />
<h2 id="heading-what-i-learned-building-this">What I learned building this</h2>
<p><strong>Abstract the backend early.</strong> The single transport pattern was a lifesaver. Adding a new provider is just "wire up the adapter, add the model IDs." No UI changes.</p>
<p><strong>Browser limitations are solvable.</strong> CSP, WASM loading, storage quotas—annoying but not blockers. Budget time for post-build scripts.</p>
<p><strong>Progress feedback is everything.</strong> Users will wait for a 2GB download if they can see it happening. A blank screen? They'll close the tab.</p>
<p><strong>Distribution is harder than building.</strong> Chrome Web Store organic discovery is basically zero. You have to bring your own traffic.</p>
<hr />
<h2 id="heading-the-business-side">The business side</h2>
<p>I made it free—for now.</p>
<p>The deal: use it in exchange for your email and anonymous usage stats (via GA4). Your actual conversations never leave your device.</p>
<p>Why free? Because I don't know enough yet. Who wants this? What do they use it for? Would they pay? Charging upfront would slow down learning.</p>
<p>I'm optimizing for information, not revenue. Once I understand the audience better, I'll figure out if there's a paid tier worth building.</p>
<hr />
<h2 id="heading-try-it-out">Try it out</h2>
<p>If you've read this far, you're probably curious.</p>
<p>👉 <a target="_blank" href="https://noaibills.app/?utm_source=hashnode&amp;utm_medium=blog&amp;utm_campaign=launch">noaibills.app</a></p>
<p>Install it, play with the models, and let me know what you think. I'm genuinely interested in feedback—what works, what doesn't, what you'd want to see.</p>
<p>And if you're building something similar with WebGPU or browser-based inference, I'd love to chat. Drop a comment or reach out.</p>
]]></content:encoded></item></channel></rss>