"Make it dark."
It sounds simple, right? But making a website respond to that command not by clicking a button, but by understanding the intent and executing code is where the magic of modern AI Engineering lies.
I wanted an Agent that could control the environment it inhabited. If a user says "This is too bright," the Agent should dim the lights, not just apologize.
▶️ Watch the Demo
Watch the Agent toggle themes in real-time.
Traditional theme switchers are boring. You click a button. The colors change. That's it.
I wanted something more expressive. What if someone could say "give me that matrix look" and the AI would understand that means green, dark, and techy? What about "I want something warm" mapping to an orange palette?
This isn't just about themes it's about teaching AI to be a silent operator that controls the UI without being asked for permission at every step.
🏗️ The Foundation: CSS Tokens
Before AI could paint, I needed a canvas that accepted paint. My early experimentation revealed a critical flaw: hardcoded colors.
I spent days migrating the entire site from utility classes like text-gray-900 to semantic tokens like text-foreground. This was the unglamorous but essential plumbing work.
:root {/* Light theme colors */
--bg-light: #FAFAFA;
--text-primary-light: #1E293B;
/* Dark theme colors */
--bg-dark: #1A1A2E;
--text-primary-dark: #F8FAFC;
/* Brand colors - Digital Blue */
--primary: #3B82F6;
--accent: #06B6D4;
}
/* Additional palettes override these */
[data-palette="nature-green"] {
--primary: #65A30D;
--accent: #4ADE80;
}With four distinct palettes (Digital Blue, Warm Tech, Nature Green, Solar Yellow) each with light and dark variants, the AI now had 8 possible combinations to work with.
🧠 The Tool Call Breakthrough
This was the moment everything clicked. I defined a change_theme tool in Gemini's system prompt that could independently set mode, palette, or both:
{
name: 'change_theme',
description: 'Universal theme control.',
parameters: { mode: ['light', 'dark'],
palette: ['digital-blue', 'warm-tech', 'nature-green', 'solar-yellow']
}}The magic is in the separation. When a user says "make it dark but keep the colors," the AI calls:
change_theme({ mode: 'dark' })
But "give me that matrix vibe" triggers:
change_theme({ mode: 'dark', palette: 'nature-green' })
🐛 The UX Nightmare: Premature Actions
A fascinating bug emerged during testing. The AI was too fast.
If a user asked "Show me your projects," the AI would trigger the navigation tool immediately. The page would redirect before the AI could finish saying "Here are my projects..."
The Problem: Actions were executing during the stream, not after.
The Fix: Response Buffering. I rewrote the client-side hook to buffer the tool execution. We now wait for the text stream to fully complete before firing the action.
🗣️ The Conversational Confirmation Pattern
Initially, I tried a "silent operator" approach where the AI would just execute actions without saying anything. But it felt robotic users didn't know if the command worked until they saw the change.
The solution was Conversational Confirmation. When the AI executes a tool, it also gives a friendly acknowledgment: "Okay, switching to dark mode now!" or "Sure, let me give you that nature green look!"
This creates a natural, human-like interaction. The AI feels like a helpful assistant rather than a cold command executor.
📚 Critical Lessons
State Sync is Hard: The ChatContext shares state between the floating chat and the full /chat page.
Latency Matters: I chose Gemini 2.5 Flash over Pro specifically for sub-500ms tool execution.
Orthogonal Controls: Separating mode and palette into independent parameters gives exponential flexibility with linear complexity.
Lazy Load Heavy Components: Using next/dynamic to lazy load FloatingChat improved LCP significantly.
🧪 Try It Yourself
Right now, go to aniketppatil.com/chat and try saying:
"Make it dark"
"I want warm colors"
"Give me that matrix vibe"
"Switch to light mode with blue theme"
The future isn't just about clicking buttons; it's about asking for what you want and having the interface reshape itself around you.