DEV Community

Cover image for Chatbots Are Lazy Design: Building an 'Invisible UI' with In-Browser SLMs & WebGPU
Quentin Merle
Quentin Merle

Posted on Originally published at vibrisse-studio.dev AI-assisted

Chatbots Are Lazy Design: Building an 'Invisible UI' with In-Browser SLMs & WebGPU

☕ Deep Dive (18 min read) — Grab a coffee. We are skipping the AI hype to dissect real architecture, runnable WebGPU code, UX failure modes, and why the bottom-right floating chat bubble is an interface dead-end.


⚡ TL;DR & Quick Links

  • 🚀 Try the Live WebGPU App: invisible-ui-demo.vercel.app (Chrome 113+, Edge, Brave, Safari 18+)
  • 💻 GitHub Repository: github.com/QuentinMerle/invisible-ui-demo
  • 💡 The Core Thesis: Slapping a chat bubble onto a webpage offloads navigation friction back onto the user. By executing a Small Language Model (SmolLM2-360M) directly on the user's GPU via WebLLM, we can infer user intent from passive interaction cues (dwell time, cursor trajectory, text highlighting) to adapt UI workflows in real time— at 0 ms network latency, $0.00 cloud inference cost, and 100% GDPR privacy.
  • ⚠️ The Big UX Lesson: An interface mutating under the user's eyes feels like a bug. The winning pattern isn't aggressive morphing—it's Explicit Assent (the "Sommelier" model).

Table of Contents

  1. The Flaw of the Conversational Paradigm
  2. The PoC Architecture & Hard Constraints
  3. Demo 1 — LiquidStore: Detecting Intent Without Asking
  4. What Failed on the First Try: The Scroll-Only Trap
  5. The UX Uncanny Valley: Does Anyone Want Their Screen Mutating?
  6. Demo 2 — Auto-Hydrating Form: Killing the 12-Field Checkout
  7. Conference Slides vs. npm Reality: The In-Browser SLM Trap
  8. The FinOps Breakdown: Why Frontier Models Are an Anti-Pattern
  9. WebMCP: Making Pages Speak to Autonomous Agents
  10. What This Changes for Product Analytics and A/B Testing
  11. Key Takeaways & Run It Locally

In my previous article (Implementing Client-Side AI in E-commerce: WebLLM and Privacy-First Architecture), I focused on the plumbing: proving that running an LLM directly in the user's browser via WebGPU is viable—with zero server roundtrips, zero token bills, and absolute client-side privacy.

The plumbing worked. But once WebLLM was running in the browser, a practical product question emerged: what do we actually build with it in a real interface?
Right now, the industry's default reflex is almost always the same: a floating chat bubble in the bottom-right corner.
Yet anyone who has tried purchasing a complex product through a conversational widget knows the friction. Try configuring a camera with a bot: it asks vague questions, you type paragraphs, it misinterprets your constraints, you correct it, the viewport jumps around, and six messages later your cart is still empty. Two well-designed faceted filters would have solved the problem in thirty seconds.
The chatbot hadn't simplified anything. It replaced a clear visual workflow with slow text ping-pong, dumping the cognitive effort back onto the user.
Slapping a text box over an existing interface and praying an LLM guesses what someone wants is simply lazy design.


1. The Flaw of the Conversational Paradigm

Chatbots are fundamentally passive. They sit there waiting for input, assuming the user:

  1. Knows exactly what they want,
  2. Knows how to articulate it into a prompt,
  3. Actually wants to hold a conversation with an e-commerce website. In reality, users don't want to chat with an online store. They want to find what they need and check out.

Good interface design does the opposite of a chatbot: it removes friction before the user has to ask for help.

So I wanted to test an alternative thesis:

If a language model can run locally on the client's GPU, why wait for the user to type? Why not use an ultra-lightweight Small Language Model (SLM) to infer intent from micro-interactions (dwell time, cursor velocity, text selections) and adapt the interface dynamically—without a single keystroke?
Technically, it runs beautifully. But in practice, it surfaced an unexpected UX challenge.


2. The PoC Architecture & Hard Constraints

The constraint: everything must execute strictly on-device. Zero backend servers, zero cloud APIs, zero telemetry leaving the browser.
Not out of ideological purity, but out of technical necessity: if an interface requires an HTTP roundtrip to adapt in real time, network latency (300ms–1500ms) instantly kills the physical feel of the UI.

  • Frontend: React 19 + Vite + Tailwind CSS
  • Compute Engine: @mlc-ai/web-llm running over WebGPU
  • Model: SmolLM2-360M-Instruct-q4f16_1-MLC (~190 MB download, ~376 MB VRAM footprint)
  • Agent Protocol: WebMCP (navigator.modelContext)

3. Demo 1 — LiquidStore: Detecting Intent Without Asking

The Scenario: A product page for a high-end dual-boiler espresso machine. Two distinct buyer personas visit this page:

  1. The Tech-Obsessed Barista: Hunts for technical specs (bars of pressure, dual-boiler architecture, PID temperature precision).
  2. The Aesthetic Lifestyle Buyer: Drawn to countertop presence, copper finishes, kitchen styling, and ambient vibe.

A static, one-size-fits-all page serves neither well.

The Mechanics

A custom React hook (useImplicitIntent) aggregates three layers of interaction signals:

  • IntersectionObserver for viewport presence.
  • pointerover listeners for cursor dwell.
  • selectionchange events for highlighted text.

A 400ms ticker accumulates weighted scores inside a useRef (zero React re-renders) before triggering an arbitration.

Golden Architectural Rule: Never call an LLM as your first line of defense. Plain, deterministic JavaScript handles the heavy lifting first.

// Simplified logic from useImplicitIntent.js
const scores = { expert: 0, lifestyle: 0 };
// 400ms ticker — accumulates within a ref, zero re-render overhead
interval = setInterval(() => {
  visibleElements.forEach(({ profile, ratio }) => {
    scores[profile] += ratio * 0.35; // Macro: passive viewport presence
  });
  if (hoveredProfile) {
    scores[hoveredProfile] += 0.95;  // Micro: active cursor attention
  }
  const delta = Math.abs(scores.expert - scores.lifestyle);
  if (delta >= 2.8) {
    commitLockIn(scores);            // Decisive heuristic -> early lock-in (zero LLM needed)
  }
}, 400);
// Fallback after 6 seconds of active exploration:
commitLockIn(scores);
Enter fullscreen mode Exit fullscreen mode

When the behavioral signal is clear (delta >= 1.5), pure JS resolves the persona instantly. Zero inference needed.
When the signal is ambiguous—the visitor spent equal time reading technical tolerances and browsing lifestyle galleries—the local model (SmolLM2-360M via WebLLM) steps in as a neutral arbiter:

async function callLocalModel(scores) {
  const response = await engine.chat.completions.create({
    messages: [
      {
        role: "system",
        content: "You are a strict binary classifier. You only answer with \"expert\" or \"lifestyle\"."
      },
      {
        role: "user",
        content: `Behavioral scores — expert: ${scores.expert.toFixed(2)}, lifestyle: ${scores.lifestyle.toFixed(2)}.
Decide which persona dominates.
Answer STRICTLY with one of these two words: "expert" or "lifestyle".`
      }
    ],
    temperature: 0.1,
    max_tokens: 10
  });
  const raw = response.choices[0].message.content.trim().toLowerCase();
  return raw.includes('expert') ? 'expert' : 'lifestyle';
}
Enter fullscreen mode Exit fullscreen mode

The prompt is minimal, and the task is strictly binary. SmolLM2-360M resolves this arbitration in under 100ms on WebGPU.


4. What Failed on the First Try: The Scroll-Only Trap

My initial prototype tracked everything using IntersectionObserver. It was a complete failure.

An IntersectionObserver only fires when an element crosses a visibility boundary. The moment an engaged user stops scrolling to read a paragraph, the API stops firing entirely. Stationary dwell time was simply ignored.
More fundamentally, scrolling is merely a macro signal. An element can easily occupy 60% of the viewport while the user is looking at another tab or reading the top navigation.
The true proxy for visual attention on desktop computers is the cursor. Eye-tracking literature consistently demonstrates an approximate 75% correlation between mouse trajectories and gaze fixations.

To capture a dependable signal, we had to fuse three measurement tiers:

  1. The Macro Signal (Scroll / Viewport): Baseline weight (0.35 per 400ms tick) proportional to the section's visible ratio.
  2. The Micro Signal (Cursor / Hover): When the pointer enters an annotated element ([data-profile]), weight jumps to 0.95—nearly 3x passive visibility. Lingering over "9.0 BAR" or "PID ±0.5°C" is an active intent signal.
  3. The Engagement Signal (Text Highlighting): If a user selects a technical term ("Dual boiler"), the engine applies an immediate +1.2 boost. Text selection is the strongest deliberate analytical gesture on the desktop web short of an explicit click.

5. The UX Uncanny Valley: Does Anyone Want Their Screen Mutating?

Once the pipeline was hooked up, I started testing it. My immediate reaction as a user was visceral:
Having an interface reshape itself while you are reading feels terrible.

When a headline morphs mid-sentence, when the CTA button changes colors unexpectedly, or when review cards swap places with technical specs without a click, your brain does not think "Wow, intelligent AI!"

Your brain thinks:

  • "Wait, did I accidentally click something?"
  • "Is my browser glitching?"
  • "Where did the paragraph I was reading go?"

This is the Uncanny Valley of UI design. It directly violates Jakob Nielsen's foundational heuristic: User Control and Freedom.
In our prototype, the first safety net was a 6-second observation window backed by an irreversible commit lock-in. The engine observes the opening seconds, locks its decision permanently, and stops. No jitter, no flip-flopping.
Yet for production software, a timer isn't enough. Three far more graceful architectural patterns emerge:

Pattern A: The Intent Passport (Cross-Page Transition)

The golden rule: the page currently being viewed NEVER shifts by a single pixel. The engine monitors behavior on the product page. When the user navigates forward—clicking "Add to Cart" or navigating to checkout—the destination page renders directly in its tailored state. The human mind expects a fresh layout upon navigation; it rejects one shifting under its gaze.

Pattern B: Below-the-Fold Progressive Enrichment

Never mutate anything inside the active viewport. If the user is reading the hero section, the engine may reorganize technical spec tables or customer reviews located further down the page. When the user scrolls down, the content feels organically tailored without any visual jump.

Pattern C: Explicit Assent (AI Proposes, the Human Decides)

This is the pattern we implemented in the live PoC.
Instead of forcibly swapping components, the SLM works silently in the background. Once intent is resolved, it surfaces a discrete contextual badge floating at the bottom:

⚙️ "Looks like you're analyzing engineering specs. [Switch to Workshop View]"

✨ "Interested in premium finishes and aesthetics? [Explore the Copper Edition]"

The user clicks if they want. If ignored, the badge gently fades away without interrupting their flow.

Explicit Assent

The Restaurant Analogy

Model Analogy What the User Experiences
The Chatbot A lazy waiter handing you a blank sheet of paper: "Write down what you want to eat." Cognitive load. The user must articulate everything from scratch.
Direct Morphing An overzealous waiter snatching your plate mid-chew and swapping it for another dish without warning. Disorientation & rejection. "Wait, what just happened? Did the site break?"
Explicit Assent An attentive sommelier. Having observed your entrée choice, they step forward quietly: "If you enjoy crisp minerality, this vintage pairs beautifully with your dish." Perceived value & comfort. The user feels understood while retaining complete autonomy.

6. Demo 2 — Auto-Hydrating Form: Killing the 12-Field Checkout

Mobile checkout abandonment sits around 85%. Tedious input forms remain the primary culprit.

The Approach: At checkout, the user encounters a single input area:

"Paste your email signature or any unstructured address text."
The user pastes a messy corporate email signature containing a name, title, direct phone number, multi-line office address, and two emails.

Quentin Merle
Staff AI Engineer | Vibrisse Studio Inc.
42 Sugar Maple Road, Suite 300, Saint-Odilon, QC G0S 1M0
Direct: +1 (581) 555-0194 | quentin@vibrisse-studio.dev
Enter fullscreen mode Exit fullscreen mode

In ~400ms to 700ms on local WebGPU, SmolLM2-360M extracts the fields, populates the form inputs, and renders visual confidence indicators:

  • Green indicator: High confidence extraction (Name, Street, Postal Code).
  • Amber highlight: Ambiguous field (e.g., distinguishing between a personal mobile and a corporate switchboard number), inviting the user's eye to verify before paying.

The Privacy Moat: The extracted payload (names, home addresses, phone numbers) never leaves the browser. No third-party API or backend server ever sees the data transit. This eliminates GDPR/PIPEDA liability for address parsing.

Auto-Hydrating Form: Killing the 12-Field Checkout


7. Conference Slides vs. npm Reality: The In-Browser SLM Trap

On tech conference slides, the theoretical darling for this task is Google's FunctionGemma 270M: an ultra-compact model purpose-built for function calling and tool_calls.

Once you leave the slides and install @mlc-ai/web-llm, you run straight into a wall:

  1. No official WebGPU binary exists for FunctionGemma 270M in MLC-AI's model registry. Google released gemma-2-2b-it, but no ready-to-run 270M WebGPU build.
  2. The in-browser function calling trap: In WebLLM's source (functionCallingModelIds), native support for the OpenAI tools schema is restricted to Hermes-2-Pro (7B/8B) or Hermes-3 (3B). Forcing a mobile shopper to download 2 GB to 4.5 GB of weights into their browser cache just to parse a shipping address is a non-starter.

The Engineering Solution: Grammar-Constrained Outputs (XGrammar)

The practical production pattern does not rely on multi-gigabyte models with conversational function calling layers. It pairs Grammar-Constrained Generation (Structured Outputs) with an ultra-lightweight SLM.
WebLLM embeds @mlc-ai/web-xgrammar. Instead of hoping the model doesn't hallucinate conversational filler, WebGPU constrains token-by-token sampling at the logits level. Generating invalid JSON becomes mathematically impossible.
With SmolLM2-360M-Instruct-q4f16_1-MLC:

  • Download size: ~190 MB
  • VRAM footprint: ~376 MB
  • Cold start: ~3 seconds on WebGPU (subsequent loads are instant via Cache API / IndexedDB)
// Structured extraction via local WebLLM with JSON grammar constraint
const response = await engine.chat.completions.create({
  messages: [
    { 
      role: "system", 
      content: "You are a strict JSON extractor. No conversation, output only a valid JSON object matching the requested shipping schema." 
    },
    { 
      role: "user", 
      content: `Extract shipping fields from this raw text:\n"""${rawText}"""` 
    }
  ],
  temperature: 0.1,
  max_tokens: 350
});
const shippingData = JSON.parse(response.choices[0].message.content.trim());
Enter fullscreen mode Exit fullscreen mode

8. The FinOps Breakdown: Why Frontier Models Are an Anti-Pattern

A common pushback is: "WebGPU isn't on 100% of devices yet. Can't we just call an LLM API in the cloud?"
Yes. But this is where teams fall into the Frontier Model Trap, defaulting to gpt-6 or claude-5-5-sonnet.

For micro-UI tasks, that is both an architectural mistake and a FinOps disaster:

  1. A sledgehammer for a thumbtack: Binary classification or address extraction requires zero world knowledge or complex multi-step reasoning. A 360M model achieves >98% accuracy on these tasks.
  2. The economic reality: Calling frontier models costs $2.50 to $15.00 per million tokens, with Time-To-First-Token (TTFT) latency between 800ms and 2,500ms. On an e-commerce platform with 200,000 monthly visitors, your bill explodes into thousands of dollars just to adapt a few buttons!
Dimension WebGPU Local (SmolLM2-360M) Edge Micro-API (Groq / Gemini Flash-Lite) Frontier Cloud (GPT-4o / Claude Sonnet)
Inference Cost $0.00 (Zero bill, infinite scale) ~$0.00005 / request (negligible) ~$0.01 / request (FinOps anti-pattern)
Network Latency 0 ms (on-device WebGPU) 80 to 150 ms (Global edge) 800 to 2,500 ms (ruins real-time UX)
Privacy / GDPR 100% on-device (Zero transit) Encrypted transit to provider Encrypted transit to provider
Device Reach Modern WebGPU browsers 100% of browsers (via Edge Worker) 100% of browsers
Server Footprint 0% (Compute offloaded to client) Minimal (Stateless Edge Worker) Minimal (Stateless Edge Worker)

If you must support legacy devices without WebGPU, deploy a lightweight Serverless Edge Function (Cloudflare Worker or Vercel Edge) routing to a micro-model (gemini-2.0-flash-lite or llama-3.1-8b on Groq). Never call AI APIs directly from client code to avoid leaking API credentials.


9. WebMCP: Making Pages Speak to Autonomous Agents

There is a second half to this story.
WebMCP (Web Model Context Protocol) is an emerging standard under incubation within the W3C Machine Learning Community Group (accessible via Chrome Origin Trials). The core idea: rather than forcing external AI agents to scrape the DOM and guess CSS selectors, the webpage formally exposes its semantic tools via navigator.modelContext.

// Exposing structured capabilities directly to external AI agents
navigator.modelContext.register({
  name: "switch_product_profile",
  description: "Adapt the product page layout based on the requested user profile",
  parameters: {
    profile: { type: "string", enum: ["expert", "lifestyle", "general"] }
  },
  handler: ({ profile }) => {
    setIntent(profile);
    return { success: true, appliedLayout: profile };
  }
});
Enter fullscreen mode Exit fullscreen mode

When a user browses with an autonomous agent (Claude in Chrome or Gemini in Android), the agent can execute switch_product_profile({ profile: "expert" }) cleanly. No brittle scraping.

  • For humans (implicit): In-browser SLMs observe passive interaction cues and adapt the UI seamlessly.
  • For AI agents (explicit): Autonomous agents query and manipulate the page cleanly via WebMCP.

10. What This Changes for Product Analytics and A/B Testing

Traditional A/B testing splits traffic blindly across a heterogeneous population: 5,000 visitors to Variant A, 5,000 to Variant B. Three weeks later, Variant B converted 3% better. But for whom? The technical buyers? The lifestyle buyers? The signal is drowned in aggregate noise.

Behavioral telemetry inverts this paradigm.
Micro-dwell telemetry, hover trajectories, and scroll cadences qualify visitor intent within the first five seconds of their session.
Testing becomes conditional:

  • An experimental "Technical Spec Deep-Dive" variant is served exclusively to visitors whose expert score crossed the confidence threshold.
  • The test cohort is smaller, but vastly more homogeneous: statistical significance is reached in days rather than weeks.
  • Hypotheses become surgical: "For verified technical personas, does displaying the pump pressure curve before the pricing block lift checkout conversion?" A chatbot gathers zero signal until the user actively types. An invisible UI qualifies and measures engagement from the very first second.

11. Key Takeaways & Run It Locally

Running a Small Language Model locally in the browser isn't an experimental novelty—it is viable production infrastructure today.

  • SLMs belong behind the scenes: Small models shine as silent intent routers and data extractors, not conversational chatbots.
  • Respect the user: Propose, never impose. Explicit assent beats aggressive real-time morphing every single time.
  • Zero-cost scale: Offloading micro-inference to client-side WebGPU cuts server bills to zero and guarantees uncompromising client privacy.

Run the Code Locally

# Clone the repository
git clone https://github.com/QuentinMerle/invisible-ui-demo.git
# Enter project directory
cd invisible-ui-demo
# Install dependencies
npm install
# Launch Vite dev server
npm run dev
Enter fullscreen mode Exit fullscreen mode

☕ Curious to hear your thoughts: What's your take on in-browser SLMs vs. conversational bots? Have you experimented with WebGPU or intent-driven UI patterns? Drop your feedback, critiques, and intuitions in the comments below!


🍁 Proudly engineered from the Beauce region (Quebec) by Quentin Merle at Vibrisse Studio.

Stack: React 19 · Vite · TailwindCSS · WebLLM · SmolLM2-360M · WebMCP

Top comments (0)