Browse Source

merge darkmode

alexchenzl 5 months ago
parent
commit
7cd949bc0c

+ 1 - 1
README.md

@@ -152,7 +152,7 @@ We're actively developing Nanobrowser with exciting features on the horizon, wel
 **We need your help to make Nanobrowser even better!**  Contributions of all kinds are welcome:
 
 *  **Share Prompts & Use Cases** 
-   * Join our [Discord server]([YOUR_DISCORD_INVITE_LINK])
+   * Join our [Discord server](https://discord.gg/NN3ABHggMK).
    * share how you're using Nanobrowser.  Help us build a library of useful prompts and real-world use cases.
 *  **Provide Feedback** 
    * Try Nanobrowser and give us feedback on its performance or suggest improvements in our [Discord server](https://discord.gg/NN3ABHggMK).

+ 16 - 2
chrome-extension/src/background/agent/agents/planner.ts

@@ -12,10 +12,24 @@ const logger = createLogger('PlannerAgent');
 export const plannerOutputSchema = z.object({
   observation: z.string(),
   challenges: z.string(),
-  done: z.boolean(),
+  done: z.union([
+    z.boolean(),
+    z.string().transform(val => {
+      if (val.toLowerCase() === 'true') return true;
+      if (val.toLowerCase() === 'false') return false;
+      throw new Error('Invalid boolean string');
+    }),
+  ]),
   next_steps: z.string(),
   reasoning: z.string(),
-  web_task: z.boolean(),
+  web_task: z.union([
+    z.boolean(),
+    z.string().transform(val => {
+      if (val.toLowerCase() === 'true') return true;
+      if (val.toLowerCase() === 'false') return false;
+      throw new Error('Invalid boolean string');
+    }),
+  ]),
 });
 
 export type PlannerOutput = z.infer<typeof plannerOutputSchema>;

+ 8 - 1
chrome-extension/src/background/agent/agents/validator.ts

@@ -10,7 +10,14 @@ const logger = createLogger('ValidatorAgent');
 
 // Define Zod schema for validator output
 export const validatorOutputSchema = z.object({
-  is_valid: z.boolean(), // indicates if the output is correct
+  is_valid: z.union([
+    z.boolean(),
+    z.string().transform(val => {
+      if (val.toLowerCase() === 'true') return true;
+      if (val.toLowerCase() === 'false') return false;
+      throw new Error('Invalid boolean string');
+    }),
+  ]), // indicates if the output is correct
   reason: z.string(), // explains why it is valid or not
   answer: z.string(), // the final answer to the task if it is valid
 });

+ 3 - 3
chrome-extension/src/background/agent/prompts/validator.ts

@@ -60,9 +60,9 @@ SPECIAL CASES:
 
 RESPONSE FORMAT: You must ALWAYS respond with valid JSON in this exact format:
 {
-  "is_valid": boolean,  // true if task is completed correctly
-  "reason": string      // clear explanation of validation result
-  "answer": string      // empty string if is_valid is false; human-readable final answer and should not be empty if is_valid is true
+  "is_valid": true or false,  // Boolean value (not a string) indicating if task is completed correctly
+  "reason": string,           // clear explanation of validation result
+  "answer": string            // empty string if is_valid is false; human-readable final answer and should not be empty if is_valid is true
 }
 
 ANSWER FORMATTING GUIDELINES:

+ 14 - 6
packages/ui/lib/components/Button.tsx

@@ -14,16 +14,24 @@ export function Button({ theme, variant = 'primary', className, disabled, childr
         'py-1 px-4 rounded shadow transition-all',
         {
           // Primary variant
-          'bg-blue-500 hover:bg-blue-600 text-white hover:scale-105': variant === 'primary' && !disabled,
-          'bg-gray-500 text-gray-700 cursor-not-allowed': variant === 'primary' && disabled,
+          'bg-blue-500 hover:bg-blue-600 text-white hover:scale-105':
+            variant === 'primary' && !disabled && theme !== 'dark',
+          'bg-blue-600 hover:bg-blue-700 text-white hover:scale-105':
+            variant === 'primary' && !disabled && theme === 'dark',
+          'bg-gray-400 text-gray-600 cursor-not-allowed': variant === 'primary' && disabled,
 
           // Secondary variant
-          'bg-gray-100 hover:bg-gray-200 text-gray-800 hover:scale-105': variant === 'secondary' && !disabled,
-          'bg-gray-500 text-gray-700 cursor-not-allowed': variant === 'secondary' && disabled,
+          'bg-gray-300 hover:bg-gray-400 text-gray-800 hover:scale-105': variant === 'secondary' && !disabled,
+          'bg-gray-100 text-gray-400 cursor-not-allowed': variant === 'secondary' && disabled,
 
           // Danger variant
-          'bg-red-500 hover:bg-red-600 text-white hover:scale-105': variant === 'danger' && !disabled,
-          'bg-red-300 text-red-100 cursor-not-allowed': variant === 'danger' && disabled,
+          // Note: bg-red-400 causes the button to appear black (RGB 0,0,0) for unknown reasons
+          // Using bg-red-500 with opacity to achieve a softer look
+          'bg-red-600 bg-opacity-80 hover:bg-red-700 hover:bg-opacity-90 text-white hover:scale-105':
+            variant === 'danger' && !disabled && theme !== 'dark',
+          'bg-red-500 bg-opacity-70 hover:bg-red-700 hover:bg-opacity-90 text-white hover:scale-105':
+            variant === 'danger' && !disabled && theme === 'dark',
+          'bg-red-300 bg-opacity-80 text-red-100 cursor-not-allowed': variant === 'danger' && disabled,
         },
         className,
       )}

+ 20 - 0
pages/options/src/Options.css

@@ -24,3 +24,23 @@ code {
   border-radius: 0.25rem;
   padding: 0.2rem 0.5rem;
 }
+
+/* Dark mode support */
+@media (prefers-color-scheme: dark) {
+  code {
+    background: rgba(30, 58, 138, 0.4);
+    color: #7dd3fc;
+  }
+  
+  .dark-mode-text {
+    color: #e2e8f0 !important; /* slate-200 */
+  }
+  
+  .dark-mode-bg {
+    background-color: #1e293b !important; /* slate-800 */
+  }
+  
+  .dark-mode-border {
+    border-color: #475569 !important; /* slate-600 */
+  }
+}

+ 28 - 11
pages/options/src/Options.tsx

@@ -1,29 +1,46 @@
-import { useState } from 'react';
+import { useState, useEffect } from 'react';
 import '@src/Options.css';
 import { Button } from '@extension/ui';
 import { withErrorBoundary, withSuspense } from '@extension/shared';
 import { GeneralSettings } from './components/GeneralSettings';
 import { ModelSettings } from './components/ModelSettings';
+
 const Options = () => {
   const [activeTab, setActiveTab] = useState('models');
+  const [isDarkMode, setIsDarkMode] = useState(false);
+
+  // Check for dark mode preference
+  useEffect(() => {
+    const darkModeMediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
+    setIsDarkMode(darkModeMediaQuery.matches);
+
+    const handleChange = (e: MediaQueryListEvent) => {
+      setIsDarkMode(e.matches);
+    };
+
+    darkModeMediaQuery.addEventListener('change', handleChange);
+    return () => darkModeMediaQuery.removeEventListener('change', handleChange);
+  }, []);
 
   const renderTabContent = () => {
     switch (activeTab) {
       case 'general':
-        return <GeneralSettings />;
+        return <GeneralSettings isDarkMode={isDarkMode} />;
       case 'models':
-        return <ModelSettings />;
+        return <ModelSettings isDarkMode={isDarkMode} />;
       default:
         return null;
     }
   };
 
   return (
-    <div className="min-h-screen min-w-[768px] flex bg-[url('/bg.jpg')] bg-cover bg-center text-gray-900">
+    <div
+      className={`flex min-h-screen min-w-[768px] ${isDarkMode ? 'bg-slate-900' : "bg-[url('/bg.jpg')] bg-cover bg-center"} ${isDarkMode ? 'text-gray-200' : 'text-gray-900'}`}>
       {/* Vertical Navigation Bar */}
-      <nav className="w-48 border-r border-white/20 backdrop-blur-sm bg-[#0EA5E9]/10">
+      <nav
+        className={`w-48 border-r ${isDarkMode ? 'border-slate-700 bg-slate-800/80' : 'border-white/20 bg-[#0EA5E9]/10'} backdrop-blur-sm`}>
         <div className="p-4">
-          <h1 className="text-xl font-bold mb-6 text-gray-800">Settings</h1>
+          <h1 className={`mb-6 text-xl font-bold ${isDarkMode ? 'text-gray-200' : 'text-gray-800'}`}>Settings</h1>
           <ul className="space-y-2">
             {[
               { id: 'general', icon: '⚙️', label: 'General' },
@@ -32,11 +49,11 @@ const Options = () => {
               <li key={item.id}>
                 <Button
                   onClick={() => setActiveTab(item.id)}
-                  className={`w-full text-left px-4 py-2 rounded-lg flex items-center space-x-2 
+                  className={`flex w-full items-center space-x-2 rounded-lg px-4 py-2 text-left 
                     ${
                       activeTab !== item.id
-                        ? 'bg-[#0EA5E9]/15 backdrop-blur-sm font-medium text-gray-700 hover:text-white'
-                        : 'backdrop-blur-sm text-white'
+                        ? `${isDarkMode ? 'bg-slate-700/70 text-gray-300 hover:text-white' : 'bg-[#0EA5E9]/15 font-medium text-gray-700 hover:text-white'} backdrop-blur-sm`
+                        : `${isDarkMode ? 'bg-sky-800/50' : ''} text-white backdrop-blur-sm`
                     }`}>
                   <span>{item.icon}</span>
                   <span>{item.label}</span>
@@ -48,8 +65,8 @@ const Options = () => {
       </nav>
 
       {/* Main Content Area */}
-      <main className="flex-1 p-8 backdrop-blur-sm bg-white/10">
-        <div className="min-w-[512px] max-w-[1024px] mx-auto">{renderTabContent()}</div>
+      <main className={`flex-1 ${isDarkMode ? 'bg-slate-800/50' : 'bg-white/10'} p-8 backdrop-blur-sm`}>
+        <div className="mx-auto min-w-[512px] max-w-screen-lg">{renderTabContent()}</div>
       </main>
     </div>
   );

+ 92 - 37
pages/options/src/components/GeneralSettings.tsx

@@ -1,7 +1,11 @@
 import { useState, useEffect } from 'react';
 import { type GeneralSettingsConfig, generalSettingsStore, DEFAULT_GENERAL_SETTINGS } from '@extension/storage';
 
-export const GeneralSettings = () => {
+interface GeneralSettingsProps {
+  isDarkMode?: boolean;
+}
+
+export const GeneralSettings = ({ isDarkMode = false }: GeneralSettingsProps) => {
   const [settings, setSettings] = useState<GeneralSettingsConfig>(DEFAULT_GENERAL_SETTINGS);
 
   useEffect(() => {
@@ -17,99 +21,150 @@ export const GeneralSettings = () => {
 
   return (
     <section className="space-y-6">
-      <div className="bg-white rounded-lg p-6 shadow-sm border border-blue-100 text-left">
-        <h2 className="text-xl font-semibold mb-4 text-gray-800 text-left">General</h2>
+      <div
+        className={`rounded-lg border ${isDarkMode ? 'border-slate-700 bg-slate-800' : 'border-blue-100 bg-white'} p-6 text-left shadow-sm`}>
+        <h2 className={`mb-4 text-left text-xl font-semibold ${isDarkMode ? 'text-gray-200' : 'text-gray-800'}`}>
+          General
+        </h2>
 
         <div className="space-y-4">
           <div className="flex items-center justify-between">
             <div>
-              <h3 className="text-lg font-medium text-gray-700">Max Steps per Task</h3>
-              <p className="text-sm font-normal text-gray-500">Step limit per task</p>
+              <h3 className={`text-lg font-medium ${isDarkMode ? 'text-gray-300' : 'text-gray-700'}`}>
+                Max Steps per Task
+              </h3>
+              <p className={`text-sm font-normal ${isDarkMode ? 'text-gray-400' : 'text-gray-500'}`}>
+                Step limit per task
+              </p>
             </div>
+            <label htmlFor="maxSteps" className="sr-only">
+              Max Steps per Task
+            </label>
             <input
+              id="maxSteps"
               type="number"
-              min="1"
+              min={1}
+              max={100}
               value={settings.maxSteps}
-              onChange={e => updateSetting('maxSteps', Number.parseInt(e.target.value))}
-              className="w-24 px-3 py-2 border rounded-md"
+              onChange={e => updateSetting('maxSteps', parseInt(e.target.value, 10))}
+              className={`w-20 rounded-md border ${isDarkMode ? 'border-slate-600 bg-slate-700 text-gray-200' : 'border-gray-300 bg-white text-gray-700'} px-3 py-2`}
             />
           </div>
 
           <div className="flex items-center justify-between">
             <div>
-              <h3 className="text-lg font-medium text-gray-700">Max Actions per Step</h3>
-              <p className="text-sm font-normal text-gray-500">Action limit per step</p>
+              <h3 className={`text-lg font-medium ${isDarkMode ? 'text-gray-300' : 'text-gray-700'}`}>
+                Max Actions per Step
+              </h3>
+              <p className={`text-sm font-normal ${isDarkMode ? 'text-gray-400' : 'text-gray-500'}`}>
+                Action limit per step
+              </p>
             </div>
+            <label htmlFor="maxActionsPerStep" className="sr-only">
+              Max Actions per Step
+            </label>
             <input
+              id="maxActionsPerStep"
               type="number"
-              min="1"
+              min={1}
+              max={100}
               value={settings.maxActionsPerStep}
-              onChange={e => updateSetting('maxActionsPerStep', Number.parseInt(e.target.value))}
-              className="w-24 px-3 py-2 border rounded-md"
+              onChange={e => updateSetting('maxActionsPerStep', parseInt(e.target.value, 10))}
+              className={`w-20 rounded-md border ${isDarkMode ? 'border-slate-600 bg-slate-700 text-gray-200' : 'border-gray-300 bg-white text-gray-700'} px-3 py-2`}
             />
           </div>
 
           <div className="flex items-center justify-between">
             <div>
-              <h3 className="text-lg font-medium text-gray-700">Failure Tolerance</h3>
-              <p className="text-sm font-normal text-gray-500">
-                How many consecutive failures in a Task before stopping
+              <h3 className={`text-lg font-medium ${isDarkMode ? 'text-gray-300' : 'text-gray-700'}`}>
+                Failure Tolerance
+              </h3>
+              <p className={`text-sm font-normal ${isDarkMode ? 'text-gray-400' : 'text-gray-500'}`}>
+                How many consecutive failures before stopping
               </p>
             </div>
+            <label htmlFor="maxFailures" className="sr-only">
+              Failure Tolerance
+            </label>
             <input
+              id="maxFailures"
               type="number"
-              min="1"
+              min={1}
+              max={10}
               value={settings.maxFailures}
-              onChange={e => updateSetting('maxFailures', Number.parseInt(e.target.value))}
-              className="w-24 px-3 py-2 border rounded-md"
+              onChange={e => updateSetting('maxFailures', parseInt(e.target.value, 10))}
+              className={`w-20 rounded-md border ${isDarkMode ? 'border-slate-600 bg-slate-700 text-gray-200' : 'border-gray-300 bg-white text-gray-700'} px-3 py-2`}
             />
           </div>
 
           <div className="flex items-center justify-between">
             <div>
-              <h3 className="text-lg font-medium text-gray-700">Enable Vision</h3>
-              <p className="text-sm font-normal text-gray-500">
-                Use vision capabilities (Note: Vision uses more tokens)
+              <h3 className={`text-lg font-medium ${isDarkMode ? 'text-gray-300' : 'text-gray-700'}`}>Enable Vision</h3>
+              <p className={`text-sm font-normal ${isDarkMode ? 'text-gray-400' : 'text-gray-500'}`}>
+                Use vision capabilities (uses more tokens)
               </p>
             </div>
-            <label className="relative inline-flex items-center cursor-pointer">
+            <div className="relative inline-flex cursor-pointer items-center">
               <input
+                id="useVision"
                 type="checkbox"
                 checked={settings.useVision}
                 onChange={e => updateSetting('useVision', e.target.checked)}
-                className="sr-only peer"
+                className="peer sr-only"
               />
-              <div className="w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-blue-300 rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-blue-600"></div>
-            </label>
+              <label
+                htmlFor="useVision"
+                className={`peer h-6 w-11 rounded-full ${isDarkMode ? 'bg-slate-600' : 'bg-gray-200'} after:absolute after:left-[2px] after:top-[2px] after:size-5 after:rounded-full after:border after:border-gray-300 after:bg-white after:transition-all after:content-[''] peer-checked:bg-blue-600 peer-checked:after:translate-x-full peer-checked:after:border-white peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-blue-300`}>
+                <span className="sr-only">Enable Vision</span>
+              </label>
+            </div>
           </div>
 
           <div className="flex items-center justify-between">
             <div>
-              <h3 className="text-lg font-medium text-gray-700">Enable Vision for Planner</h3>
-              <p className="text-sm font-normal text-gray-500">Use vision in planner (Note: Vision uses more tokens)</p>
+              <h3 className={`text-lg font-medium ${isDarkMode ? 'text-gray-300' : 'text-gray-700'}`}>
+                Enable Vision for Planner
+              </h3>
+              <p className={`text-sm font-normal ${isDarkMode ? 'text-gray-400' : 'text-gray-500'}`}>
+                Use vision in planner (uses more tokens)
+              </p>
             </div>
-            <label className="relative inline-flex items-center cursor-pointer">
+            <div className="relative inline-flex cursor-pointer items-center">
               <input
+                id="useVisionForPlanner"
                 type="checkbox"
                 checked={settings.useVisionForPlanner}
                 onChange={e => updateSetting('useVisionForPlanner', e.target.checked)}
-                className="sr-only peer"
+                className="peer sr-only"
               />
-              <div className="w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-blue-300 rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-blue-600"></div>
-            </label>
+              <label
+                htmlFor="useVisionForPlanner"
+                className={`peer h-6 w-11 rounded-full ${isDarkMode ? 'bg-slate-600' : 'bg-gray-200'} after:absolute after:left-[2px] after:top-[2px] after:size-5 after:rounded-full after:border after:border-gray-300 after:bg-white after:transition-all after:content-[''] peer-checked:bg-blue-600 peer-checked:after:translate-x-full peer-checked:after:border-white peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-blue-300`}>
+                <span className="sr-only">Enable Vision for Planner</span>
+              </label>
+            </div>
           </div>
 
           <div className="flex items-center justify-between">
             <div>
-              <h3 className="text-lg font-medium text-gray-700">Replanning Frequency</h3>
-              <p className="text-sm font-normal text-gray-500">Reconsider and update the plan every [Number] steps</p>
+              <h3 className={`text-lg font-medium ${isDarkMode ? 'text-gray-300' : 'text-gray-700'}`}>
+                Replanning Frequency
+              </h3>
+              <p className={`text-sm font-normal ${isDarkMode ? 'text-gray-400' : 'text-gray-500'}`}>
+                Reconsider and update the plan every [Number] steps
+              </p>
             </div>
+            <label htmlFor="planningInterval" className="sr-only">
+              Replanning Frequency
+            </label>
             <input
+              id="planningInterval"
               type="number"
-              min="1"
+              min={1}
+              max={20}
               value={settings.planningInterval}
-              onChange={e => updateSetting('planningInterval', Number.parseInt(e.target.value))}
-              className="w-24 px-3 py-2 border rounded-md"
+              onChange={e => updateSetting('planningInterval', parseInt(e.target.value, 10))}
+              className={`w-20 rounded-md border ${isDarkMode ? 'border-slate-600 bg-slate-700 text-gray-200' : 'border-gray-300 bg-white text-gray-700'} px-3 py-2`}
             />
           </div>
         </div>

+ 145 - 53
pages/options/src/components/ModelSettings.tsx

@@ -14,7 +14,11 @@ import {
   llmProviderParameters,
 } from '@extension/storage';
 
-export const ModelSettings = () => {
+interface ModelSettingsProps {
+  isDarkMode?: boolean;
+}
+
+export const ModelSettings = ({ isDarkMode = false }: ModelSettingsProps) => {
   const [providers, setProviders] = useState<
     Record<
       string,
@@ -260,6 +264,7 @@ export const ModelSettings = () => {
     // For deletion, we only care if it's in storage and not modified
     if (isInStorage && !isModified) {
       return {
+        theme: isDarkMode ? 'dark' : 'light',
         variant: 'danger' as const,
         children: 'Delete',
         disabled: false,
@@ -271,6 +276,7 @@ export const ModelSettings = () => {
     const hasInput = isCustom || Boolean(providers[provider]?.apiKey?.trim());
 
     return {
+      theme: isDarkMode ? 'dark' : 'light',
       variant: 'primary' as const,
       children: 'Save',
       disabled: !hasInput || !isModified,
@@ -489,21 +495,26 @@ export const ModelSettings = () => {
   };
 
   const renderModelSelect = (agentName: AgentNameEnum) => (
-    <div className="bg-white p-4 rounded-lg border border-gray-200">
-      <h3 className="text-lg font-medium text-gray-700 mb-2">
+    <div
+      className={`rounded-lg border ${isDarkMode ? 'border-gray-700 bg-slate-800' : 'border-gray-200 bg-gray-50'} p-4`}>
+      <h3 className={`mb-2 text-lg font-medium ${isDarkMode ? 'text-gray-300' : 'text-gray-700'}`}>
         {agentName.charAt(0).toUpperCase() + agentName.slice(1)}
       </h3>
-      <p className="text-sm font-normal text-gray-500 mb-4">{getAgentDescription(agentName)}</p>
+      <p className={`mb-4 text-sm font-normal ${isDarkMode ? 'text-gray-400' : 'text-gray-500'}`}>
+        {getAgentDescription(agentName)}
+      </p>
 
       <div className="space-y-4">
         {/* Model Selection */}
         <div className="flex items-center">
-          <label htmlFor={`${agentName}-model`} className="w-24 text-sm font-medium text-gray-700">
+          <label
+            htmlFor={`${agentName}-model`}
+            className={`w-24 text-sm font-medium ${isDarkMode ? 'text-gray-300' : 'text-gray-700'}`}>
             Model
           </label>
           <select
             id={`${agentName}-model`}
-            className="flex-1 px-3 py-2 border rounded-md"
+            className={`flex-1 rounded-md border ${isDarkMode ? 'border-slate-600 bg-slate-700 text-gray-200' : 'border-gray-300 bg-white text-gray-700'} px-3 py-2`}
             disabled={getAvailableModels().length <= 1}
             value={
               selectedModels[agentName]
@@ -524,10 +535,12 @@ export const ModelSettings = () => {
 
         {/* Temperature Slider */}
         <div className="flex items-center">
-          <label htmlFor={`${agentName}-temperature`} className="w-24 text-sm font-medium text-gray-700">
+          <label
+            htmlFor={`${agentName}-temperature`}
+            className={`w-24 text-sm font-medium ${isDarkMode ? 'text-gray-300' : 'text-gray-700'}`}>
             Temperature
           </label>
-          <div className="flex-1 flex items-center space-x-2">
+          <div className="flex flex-1 items-center space-x-2">
             <input
               id={`${agentName}-temperature`}
               type="range"
@@ -536,10 +549,15 @@ export const ModelSettings = () => {
               step="0.01"
               value={modelParameters[agentName].temperature}
               onChange={e => handleParameterChange(agentName, 'temperature', Number.parseFloat(e.target.value))}
-              className="flex-1"
+              style={{
+                background: `linear-gradient(to right, ${isDarkMode ? '#3b82f6' : '#60a5fa'} 0%, ${isDarkMode ? '#3b82f6' : '#60a5fa'} ${(modelParameters[agentName].temperature / 2) * 100}%, ${isDarkMode ? '#475569' : '#cbd5e1'} ${(modelParameters[agentName].temperature / 2) * 100}%, ${isDarkMode ? '#475569' : '#cbd5e1'} 100%)`,
+              }}
+              className={`flex-1 ${isDarkMode ? 'accent-blue-500' : 'accent-blue-400'} appearance-none h-1 rounded-full`}
             />
             <div className="flex items-center space-x-2">
-              <span className="text-sm text-gray-600 w-12">{modelParameters[agentName].temperature.toFixed(2)}</span>
+              <span className={`w-12 text-sm ${isDarkMode ? 'text-gray-300' : 'text-gray-600'}`}>
+                {modelParameters[agentName].temperature.toFixed(2)}
+              </span>
               <input
                 type="number"
                 min="0"
@@ -552,7 +570,7 @@ export const ModelSettings = () => {
                     handleParameterChange(agentName, 'temperature', value);
                   }
                 }}
-                className="w-20 px-2 py-1 text-sm border rounded-md"
+                className={`w-20 rounded-md border ${isDarkMode ? 'border-slate-600 bg-slate-700 text-gray-200' : 'border-gray-300 bg-white text-gray-700'} px-2 py-1 text-sm`}
                 aria-label={`${agentName} temperature number input`}
               />
             </div>
@@ -561,10 +579,12 @@ export const ModelSettings = () => {
 
         {/* Top P Slider */}
         <div className="flex items-center">
-          <label htmlFor={`${agentName}-topP`} className="w-24 text-sm font-medium text-gray-700">
+          <label
+            htmlFor={`${agentName}-topP`}
+            className={`w-24 text-sm font-medium ${isDarkMode ? 'text-gray-300' : 'text-gray-700'}`}>
             Top P
           </label>
-          <div className="flex-1 flex items-center space-x-2">
+          <div className="flex flex-1 items-center space-x-2">
             <input
               id={`${agentName}-topP`}
               type="range"
@@ -573,10 +593,15 @@ export const ModelSettings = () => {
               step="0.001"
               value={modelParameters[agentName].topP}
               onChange={e => handleParameterChange(agentName, 'topP', Number.parseFloat(e.target.value))}
-              className="flex-1"
+              style={{
+                background: `linear-gradient(to right, ${isDarkMode ? '#3b82f6' : '#60a5fa'} 0%, ${isDarkMode ? '#3b82f6' : '#60a5fa'} ${modelParameters[agentName].topP * 100}%, ${isDarkMode ? '#475569' : '#cbd5e1'} ${modelParameters[agentName].topP * 100}%, ${isDarkMode ? '#475569' : '#cbd5e1'} 100%)`,
+              }}
+              className={`flex-1 ${isDarkMode ? 'accent-blue-500' : 'accent-blue-400'} appearance-none h-1 rounded-full`}
             />
             <div className="flex items-center space-x-2">
-              <span className="text-sm text-gray-600 w-12">{modelParameters[agentName].topP.toFixed(3)}</span>
+              <span className={`w-12 text-sm ${isDarkMode ? 'text-gray-300' : 'text-gray-600'}`}>
+                {modelParameters[agentName].topP.toFixed(3)}
+              </span>
               <input
                 type="number"
                 min="0"
@@ -589,7 +614,7 @@ export const ModelSettings = () => {
                     handleParameterChange(agentName, 'topP', value);
                   }
                 }}
-                className="w-20 px-2 py-1 text-sm border rounded-md"
+                className={`w-20 rounded-md border ${isDarkMode ? 'border-slate-600 bg-slate-700 text-gray-200' : 'border-gray-300 bg-white text-gray-700'} px-2 py-1 text-sm`}
                 aria-label={`${agentName} top P number input`}
               />
             </div>
@@ -820,11 +845,14 @@ export const ModelSettings = () => {
   return (
     <section className="space-y-6">
       {/* LLM Providers Section */}
-      <div className="bg-white rounded-lg p-6 shadow-sm border border-blue-100 text-left">
-        <h2 className="text-xl font-semibold mb-4 text-gray-800 text-left">LLM Providers</h2>
+      <div
+        className={`rounded-lg border ${isDarkMode ? 'border-slate-700 bg-slate-800' : 'border-blue-100 bg-gray-50'} p-6 text-left shadow-sm`}>
+        <h2 className={`mb-4 text-xl font-semibold ${isDarkMode ? 'text-gray-200' : 'text-gray-800'}`}>
+          LLM Providers
+        </h2>
         <div className="space-y-6">
           {getSortedProviders().length === 0 ? (
-            <div className="text-center py-8 text-gray-500">
+            <div className="py-8 text-center text-gray-500">
               <p className="mb-4">No providers configured yet. Add a provider to get started.</p>
             </div>
           ) : (
@@ -832,9 +860,11 @@ export const ModelSettings = () => {
               <div
                 key={providerId}
                 id={`provider-${providerId}`}
-                className={`space-y-4 ${modifiedProviders.has(providerId) && !providersFromStorage.has(providerId) ? 'bg-blue-50 p-4 rounded-lg border border-blue-100' : ''}`}>
+                className={`space-y-4 ${modifiedProviders.has(providerId) && !providersFromStorage.has(providerId) ? `rounded-lg border p-4 ${isDarkMode ? 'border-blue-700 bg-slate-700' : 'border-blue-200 bg-blue-50/70'}` : ''}`}>
                 <div className="flex items-center justify-between">
-                  <h3 className="text-lg font-medium text-gray-700">{providerConfig.name || providerId}</h3>
+                  <h3 className={`text-lg font-medium ${isDarkMode ? 'text-gray-300' : 'text-gray-700'}`}>
+                    {providerConfig.name || providerId}
+                  </h3>
                   <div className="flex space-x-2">
                     {/* Show Cancel button for newly added providers */}
                     {modifiedProviders.has(providerId) && !providersFromStorage.has(providerId) && (
@@ -857,7 +887,7 @@ export const ModelSettings = () => {
 
                 {/* Show message for newly added providers */}
                 {modifiedProviders.has(providerId) && !providersFromStorage.has(providerId) && (
-                  <div className="text-sm text-blue-600 mb-2">
+                  <div className={`mb-2 text-sm ${isDarkMode ? 'text-teal-300' : 'text-teal-700'}`}>
                     <p>This provider is newly added. Enter your API key and click Save to configure it.</p>
                   </div>
                 )}
@@ -867,7 +897,9 @@ export const ModelSettings = () => {
                   {providerConfig.type === ProviderTypeEnum.CustomOpenAI && (
                     <div className="flex flex-col">
                       <div className="flex items-center">
-                        <label htmlFor={`${providerId}-name`} className="w-20 text-sm font-medium text-gray-700">
+                        <label
+                          htmlFor={`${providerId}-name`}
+                          className={`w-20 text-sm font-medium ${isDarkMode ? 'text-gray-300' : 'text-gray-700'}`}>
                           Name
                         </label>
                         <input
@@ -879,13 +911,23 @@ export const ModelSettings = () => {
                             console.log('Name input changed:', e.target.value);
                             handleNameChange(providerId, e.target.value);
                           }}
-                          className={`flex-1 p-2 rounded-md bg-gray-50 border ${nameErrors[providerId] ? 'border-red-300 focus:border-red-400 focus:ring-2 focus:ring-red-200' : 'border-blue-300 focus:border-blue-400 focus:ring-2 focus:ring-blue-200'} outline-none`}
+                          className={`flex-1 rounded-md border p-2 ${
+                            nameErrors[providerId]
+                              ? isDarkMode
+                                ? 'border-red-700 bg-slate-700 text-gray-200 focus:border-red-600 focus:ring-2 focus:ring-red-900'
+                                : 'border-red-300 bg-gray-50 focus:border-red-400 focus:ring-2 focus:ring-red-200'
+                              : isDarkMode
+                                ? 'border-blue-700 bg-slate-700 text-gray-200 focus:border-blue-600 focus:ring-2 focus:ring-blue-900'
+                                : 'border-blue-300 bg-gray-50 focus:border-blue-400 focus:ring-2 focus:ring-blue-200'
+                          } outline-none`}
                         />
                       </div>
                       {nameErrors[providerId] ? (
-                        <p className="text-xs text-red-500 ml-20 mt-1">{nameErrors[providerId]}</p>
+                        <p className={`ml-20 mt-1 text-xs ${isDarkMode ? 'text-red-400' : 'text-red-500'}`}>
+                          {nameErrors[providerId]}
+                        </p>
                       ) : (
-                        <p className="text-xs text-blue-500 ml-20 mt-1">
+                        <p className={`ml-20 mt-1 text-xs ${isDarkMode ? 'text-gray-400' : 'text-gray-500'}`}>
                           Provider name (spaces are not allowed when saving)
                         </p>
                       )}
@@ -894,7 +936,9 @@ export const ModelSettings = () => {
 
                   {/* API Key input with label */}
                   <div className="flex items-center">
-                    <label htmlFor={`${providerId}-api-key`} className="w-20 text-sm font-medium text-gray-700">
+                    <label
+                      htmlFor={`${providerId}-api-key`}
+                      className={`w-20 text-sm font-medium ${isDarkMode ? 'text-gray-300' : 'text-gray-700'}`}>
                       Key{providerConfig.type !== ProviderTypeEnum.CustomOpenAI ? '*' : ''}
                     </label>
                     <input
@@ -907,7 +951,7 @@ export const ModelSettings = () => {
                       }
                       value={providerConfig.apiKey || ''}
                       onChange={e => handleApiKeyChange(providerId, e.target.value, providerConfig.baseUrl)}
-                      className="flex-1 p-2 rounded-md bg-gray-50 border border-gray-200 focus:border-blue-400 focus:ring-2 focus:ring-blue-200 outline-none"
+                      className={`flex-1 rounded-md border ${isDarkMode ? 'border-slate-600 bg-slate-700 text-gray-200 focus:border-blue-500 focus:ring-2 focus:ring-blue-800' : 'border-gray-300 bg-white text-gray-700 focus:border-blue-400 focus:ring-2 focus:ring-blue-200'} p-2 outline-none`}
                     />
                   </div>
 
@@ -916,8 +960,14 @@ export const ModelSettings = () => {
                     providerConfig.type === ProviderTypeEnum.Ollama) && (
                     <div className="flex flex-col">
                       <div className="flex items-center">
-                        <label htmlFor={`${providerId}-base-url`} className="w-20 text-sm font-medium text-gray-700">
-                          Base URL{providerConfig.type === ProviderTypeEnum.CustomOpenAI ? '*' : ''}
+                        <label
+                          htmlFor={`${providerId}-base-url`}
+                          className={`w-20 text-sm font-medium ${isDarkMode ? 'text-gray-300' : 'text-gray-700'}`}>
+                          Base URL
+                          {providerConfig.type === ProviderTypeEnum.CustomOpenAI ||
+                          providerConfig.type === ProviderTypeEnum.Ollama
+                            ? '*'
+                            : ''}
                         </label>
                         <input
                           id={`${providerId}-base-url`}
@@ -929,7 +979,7 @@ export const ModelSettings = () => {
                           }
                           value={providerConfig.baseUrl || ''}
                           onChange={e => handleApiKeyChange(providerId, providerConfig.apiKey || '', e.target.value)}
-                          className="flex-1 p-2 rounded-md bg-gray-50 border border-gray-200 focus:border-blue-400 focus:ring-2 focus:ring-blue-200 outline-none"
+                          className={`flex-1 rounded-md border ${isDarkMode ? 'border-slate-600 bg-slate-700 text-gray-200 focus:border-blue-500 focus:ring-2 focus:ring-blue-800' : 'border-gray-300 bg-white text-gray-700 focus:border-blue-400 focus:ring-2 focus:ring-blue-200'} p-2 outline-none`}
                         />
                       </div>
                     </div>
@@ -937,11 +987,14 @@ export const ModelSettings = () => {
 
                   {/* Models input field with tags */}
                   <div className="flex items-start">
-                    <label htmlFor={`${providerId}-models`} className="w-20 text-sm font-medium text-gray-700 pt-2">
+                    <label
+                      htmlFor={`${providerId}-models`}
+                      className={`w-20 pt-2 text-sm font-medium ${isDarkMode ? 'text-gray-300' : 'text-gray-700'}`}>
                       Models
                     </label>
                     <div className="flex-1">
-                      <div className="flex flex-wrap items-center gap-2 p-2 bg-gray-50 border border-gray-200 rounded-md min-h-[42px]">
+                      <div
+                        className={`flex min-h-[42px] flex-wrap items-center gap-2 rounded-md border ${isDarkMode ? 'border-slate-600 bg-slate-700 text-gray-200' : 'border-gray-300 bg-white text-gray-700'} p-2`}>
                         {/* Display existing models as tags */}
                         {(() => {
                           // Get models from provider config or default models
@@ -953,12 +1006,12 @@ export const ModelSettings = () => {
                           return models.map(model => (
                             <div
                               key={model}
-                              className="flex items-center bg-blue-100 text-blue-800 px-2 py-1 rounded-full text-sm">
+                              className={`flex items-center rounded-full ${isDarkMode ? 'bg-blue-900 text-blue-100' : 'bg-blue-100 text-blue-800'} px-2 py-1 text-sm`}>
                               <span>{model}</span>
                               <button
                                 type="button"
                                 onClick={() => removeModel(providerId, model)}
-                                className="ml-1 text-blue-600 hover:text-blue-800 font-bold"
+                                className={`ml-1 font-bold ${isDarkMode ? 'text-blue-300 hover:text-blue-100' : 'text-blue-600 hover:text-blue-800'}`}
                                 aria-label={`Remove ${model}`}>
                                 ×
                               </button>
@@ -974,25 +1027,31 @@ export const ModelSettings = () => {
                           value={newModelInputs[providerId] || ''}
                           onChange={e => handleModelsChange(providerId, e.target.value)}
                           onKeyDown={e => handleKeyDown(e, providerId)}
-                          className="flex-1 min-w-[150px] outline-none bg-transparent border-none p-1"
+                          className={`min-w-[150px] flex-1 border-none ${isDarkMode ? 'bg-transparent text-gray-200' : 'bg-transparent text-gray-700'} p-1 outline-none`}
                         />
                       </div>
-                      <p className="text-xs text-gray-500 mt-1">Type and Press Enter or Space to add a model</p>
+                      <p className={`mt-1 text-xs ${isDarkMode ? 'text-gray-400' : 'text-gray-500'}`}>
+                        Type and Press Enter or Space to add a model
+                      </p>
                     </div>
                   </div>
 
                   {/* Ollama reminder at the bottom of the section */}
                   {providerConfig.type === ProviderTypeEnum.Ollama && (
-                    <div className="mt-4 p-3 bg-amber-50 border border-amber-200 rounded-md">
-                      <p className="text-sm text-amber-700">
+                    <div
+                      className={`mt-4 rounded-md border ${isDarkMode ? 'border-slate-600 bg-slate-700' : 'border-blue-100 bg-blue-50'} p-3`}>
+                      <p className={`text-sm ${isDarkMode ? 'text-gray-200' : 'text-gray-700'}`}>
                         <strong>Remember:</strong> Add{' '}
-                        <code className="bg-amber-100 px-1 py-0.5 rounded">OLLAMA_ORIGINS=chrome-extension://*</code>{' '}
+                        <code
+                          className={`rounded italic ${isDarkMode ? 'bg-slate-600 px-1 py-0.5' : 'bg-blue-100 px-1 py-0.5'}`}>
+                          OLLAMA_ORIGINS=chrome-extension://*
+                        </code>{' '}
                         environment variable for the Ollama server.
                         <a
                           href="https://github.com/ollama/ollama/issues/6489"
                           target="_blank"
                           rel="noopener noreferrer"
-                          className="text-blue-600 hover:text-blue-800 ml-1">
+                          className={`ml-1 ${isDarkMode ? 'text-blue-400 hover:text-blue-300' : 'text-blue-600 hover:text-blue-800'}`}>
                           Learn more
                         </a>
                       </p>
@@ -1002,30 +1061,40 @@ export const ModelSettings = () => {
 
                 {/* Add divider except for the last item */}
                 {Object.keys(providers).indexOf(providerId) < Object.keys(providers).length - 1 && (
-                  <div className="border-t border-gray-200 mt-4" />
+                  <div className={`mt-4 border-t ${isDarkMode ? 'border-gray-700' : 'border-gray-200'}`} />
                 )}
               </div>
             ))
           )}
 
           {/* Add Provider button and dropdown */}
-          <div className="pt-4 relative provider-selector-container">
+          <div className="provider-selector-container relative pt-4">
             <Button
               variant="secondary"
               onClick={() => setIsProviderSelectorOpen(prev => !prev)}
-              className="w-full flex items-center justify-center">
+              className={`flex w-full items-center justify-center font-medium ${
+                isDarkMode
+                  ? 'bg-blue-600 hover:bg-blue-500 border-blue-700 text-white'
+                  : 'bg-blue-100 hover:bg-blue-200 border-blue-200 text-blue-800'
+              }`}>
               <span className="mr-2">+</span> Add Provider
             </Button>
 
             {isProviderSelectorOpen && (
-              <div className="absolute z-10 mt-2 w-full bg-white rounded-md shadow-lg border border-gray-200 overflow-hidden">
+              <div
+                className={`absolute z-10 mt-2 w-full overflow-hidden rounded-md border ${
+                  isDarkMode
+                    ? 'border-blue-600 bg-slate-700 shadow-lg shadow-slate-900/50'
+                    : 'border-blue-200 bg-white shadow-xl shadow-blue-100/50'
+                }`}>
                 <div className="py-1">
                   {/* Check if all default providers are already added */}
                   {(providersFromStorage.has(OPENAI_PROVIDER) || modifiedProviders.has(OPENAI_PROVIDER)) &&
                     (providersFromStorage.has(ANTHROPIC_PROVIDER) || modifiedProviders.has(ANTHROPIC_PROVIDER)) &&
                     (providersFromStorage.has(GEMINI_PROVIDER) || modifiedProviders.has(GEMINI_PROVIDER)) &&
                     (providersFromStorage.has(OLLAMA_PROVIDER) || modifiedProviders.has(OLLAMA_PROVIDER)) && (
-                      <div className="px-4 py-2 text-sm text-gray-500">
+                      <div
+                        className={`px-4 py-3 text-sm font-medium ${isDarkMode ? 'text-blue-300' : 'text-blue-600'}`}>
                         All default providers already added. You can still add a custom provider.
                       </div>
                     )}
@@ -1033,7 +1102,11 @@ export const ModelSettings = () => {
                   {!providersFromStorage.has(OPENAI_PROVIDER) && !modifiedProviders.has(OPENAI_PROVIDER) && (
                     <button
                       type="button"
-                      className="w-full text-left px-4 py-3 text-sm text-gray-700 hover:bg-blue-50 transition-colors duration-150 flex items-center"
+                      className={`flex w-full items-center px-4 py-3 text-left text-sm ${
+                        isDarkMode
+                          ? 'text-blue-200 hover:bg-blue-600/30 hover:text-white'
+                          : 'text-blue-700 hover:bg-blue-100 hover:text-blue-800'
+                      } transition-colors duration-150`}
                       onClick={() => handleProviderSelection(OPENAI_PROVIDER)}>
                       <span className="font-medium">OpenAI</span>
                     </button>
@@ -1042,7 +1115,11 @@ export const ModelSettings = () => {
                   {!providersFromStorage.has(ANTHROPIC_PROVIDER) && !modifiedProviders.has(ANTHROPIC_PROVIDER) && (
                     <button
                       type="button"
-                      className="w-full text-left px-4 py-3 text-sm text-gray-700 hover:bg-blue-50 transition-colors duration-150 flex items-center"
+                      className={`flex w-full items-center px-4 py-3 text-left text-sm ${
+                        isDarkMode
+                          ? 'text-blue-200 hover:bg-blue-600/30 hover:text-white'
+                          : 'text-blue-700 hover:bg-blue-100 hover:text-blue-800'
+                      } transition-colors duration-150`}
                       onClick={() => handleProviderSelection(ANTHROPIC_PROVIDER)}>
                       <span className="font-medium">Anthropic</span>
                     </button>
@@ -1051,7 +1128,11 @@ export const ModelSettings = () => {
                   {!providersFromStorage.has(GEMINI_PROVIDER) && !modifiedProviders.has(GEMINI_PROVIDER) && (
                     <button
                       type="button"
-                      className="w-full text-left px-4 py-3 text-sm text-gray-700 hover:bg-blue-50 transition-colors duration-150 flex items-center"
+                      className={`flex w-full items-center px-4 py-3 text-left text-sm ${
+                        isDarkMode
+                          ? 'text-blue-200 hover:bg-blue-600/30 hover:text-white'
+                          : 'text-blue-700 hover:bg-blue-100 hover:text-blue-800'
+                      } transition-colors duration-150`}
                       onClick={() => handleProviderSelection(GEMINI_PROVIDER)}>
                       <span className="font-medium">Gemini</span>
                     </button>
@@ -1060,7 +1141,11 @@ export const ModelSettings = () => {
                   {!providersFromStorage.has(OLLAMA_PROVIDER) && !modifiedProviders.has(OLLAMA_PROVIDER) && (
                     <button
                       type="button"
-                      className="w-full text-left px-4 py-3 text-sm text-gray-700 hover:bg-blue-50 transition-colors duration-150 flex items-center"
+                      className={`flex w-full items-center px-4 py-3 text-left text-sm ${
+                        isDarkMode
+                          ? 'text-blue-200 hover:bg-blue-600/30 hover:text-white'
+                          : 'text-blue-700 hover:bg-blue-100 hover:text-blue-800'
+                      } transition-colors duration-150`}
                       onClick={() => handleProviderSelection(OLLAMA_PROVIDER)}>
                       <span className="font-medium">Ollama</span>
                     </button>
@@ -1068,7 +1153,11 @@ export const ModelSettings = () => {
 
                   <button
                     type="button"
-                    className="w-full text-left px-4 py-3 text-sm text-gray-700 hover:bg-blue-50 transition-colors duration-150 flex items-center"
+                    className={`flex w-full items-center px-4 py-3 text-left text-sm ${
+                      isDarkMode
+                        ? 'text-blue-200 hover:bg-blue-600/30 hover:text-white'
+                        : 'text-blue-700 hover:bg-blue-100 hover:text-blue-800'
+                    } transition-colors duration-150`}
                     onClick={() => handleProviderSelection('custom')}>
                     <span className="font-medium">Custom OpenAI-compatible</span>
                   </button>
@@ -1080,8 +1169,11 @@ export const ModelSettings = () => {
       </div>
 
       {/* Updated Agent Models Section */}
-      <div className="bg-white rounded-lg p-6 shadow-sm border border-blue-100 text-left">
-        <h2 className="text-xl font-semibold mb-4 text-gray-800 text-left">Model Selection</h2>
+      <div
+        className={`rounded-lg border ${isDarkMode ? 'border-slate-700 bg-slate-800' : 'border-blue-100 bg-gray-50'} p-6 text-left shadow-sm`}>
+        <h2 className={`mb-4 text-left text-xl font-semibold ${isDarkMode ? 'text-gray-200' : 'text-gray-800'}`}>
+          Model Selection
+        </h2>
         <div className="space-y-4">
           {[AgentNameEnum.Planner, AgentNameEnum.Navigator, AgentNameEnum.Validator].map(agentName => (
             <div key={agentName}>{renderModelSelect(agentName)}</div>

+ 60 - 0
pages/side-panel/src/SidePanel.css

@@ -12,6 +12,14 @@ code {
   color: #0369a1;
 }
 
+/* Dark mode support for code */
+@media (prefers-color-scheme: dark) {
+  code {
+    background: rgba(30, 58, 138, 0.4);
+    color: #7dd3fc;
+  }
+}
+
 /* Scrollbar styles */
 ::-webkit-scrollbar {
   width: 8px;
@@ -30,6 +38,21 @@ code {
   background: #0284c7;
 }
 
+/* Dark mode scrollbar */
+@media (prefers-color-scheme: dark) {
+  ::-webkit-scrollbar-track {
+    background: #1e293b;
+  }
+  
+  ::-webkit-scrollbar-thumb {
+    background: #0ea5e9;
+  }
+  
+  ::-webkit-scrollbar-thumb:hover {
+    background: #38bdf8;
+  }
+}
+
 /* Used classes from the component */
 .header {
   display: flex;
@@ -40,6 +63,13 @@ code {
   border-bottom: 1px solid rgba(14, 165, 233, 0.2);
 }
 
+/* Dark mode header border */
+@media (prefers-color-scheme: dark) {
+  .header {
+    border-bottom: 1px solid rgba(14, 165, 233, 0.3);
+  }
+}
+
 .header-logo {
   display: flex;
   align-items: center;
@@ -103,3 +133,33 @@ code {
 .scrollbar-gutter-stable::-webkit-scrollbar-thumb:hover {
   background: #38bdf8;  /* sky-400 - slightly darker on hover */
 }
+
+/* Dark mode scrollbar for scrollbar-gutter-stable */
+@media (prefers-color-scheme: dark) {
+  .scrollbar-gutter-stable::-webkit-scrollbar-track {
+    background: #1e293b;  /* slate-800 */
+  }
+  
+  .scrollbar-gutter-stable::-webkit-scrollbar-thumb {
+    background: #0ea5e9;  /* sky-500 */
+  }
+  
+  .scrollbar-gutter-stable::-webkit-scrollbar-thumb:hover {
+    background: #38bdf8;  /* sky-400 */
+  }
+}
+
+/* Dark mode text and background colors */
+@media (prefers-color-scheme: dark) {
+  .dark-mode-text {
+    color: #e2e8f0 !important; /* slate-200 */
+  }
+  
+  .dark-mode-bg {
+    background-color: #1e293b !important; /* slate-800 */
+  }
+  
+  .dark-mode-border {
+    border-color: #475569 !important; /* slate-600 */
+  }
+}

+ 36 - 11
pages/side-panel/src/SidePanel.tsx

@@ -22,12 +22,26 @@ const SidePanel = () => {
   const [chatSessions, setChatSessions] = useState<Array<{ id: string; title: string; createdAt: number }>>([]);
   const [isFollowUpMode, setIsFollowUpMode] = useState(false);
   const [isHistoricalSession, setIsHistoricalSession] = useState(false);
+  const [isDarkMode, setIsDarkMode] = useState(false);
   const sessionIdRef = useRef<string | null>(null);
   const portRef = useRef<chrome.runtime.Port | null>(null);
   const heartbeatIntervalRef = useRef<number | null>(null);
   const messagesEndRef = useRef<HTMLDivElement>(null);
   const setInputTextRef = useRef<((text: string) => void) | null>(null);
 
+  // Check for dark mode preference
+  useEffect(() => {
+    const darkModeMediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
+    setIsDarkMode(darkModeMediaQuery.matches);
+
+    const handleChange = (e: MediaQueryListEvent) => {
+      setIsDarkMode(e.matches);
+    };
+
+    darkModeMediaQuery.addEventListener('change', handleChange);
+    return () => darkModeMediaQuery.removeEventListener('change', handleChange);
+  }, []);
+
   useEffect(() => {
     sessionIdRef.current = currentSessionId;
   }, [currentSessionId]);
@@ -470,14 +484,15 @@ const SidePanel = () => {
 
   return (
     <div>
-      <div className="flex flex-col h-[100vh] bg-[url('/bg.jpg')] bg-cover bg-no-repeat overflow-hidden border border-[rgb(186,230,253)] rounded-2xl">
+      <div
+        className={`flex flex-col h-[100vh] ${isDarkMode ? 'bg-slate-900' : "bg-[url('/bg.jpg')] bg-cover bg-no-repeat"} overflow-hidden border ${isDarkMode ? 'border-sky-800' : 'border-[rgb(186,230,253)]'} rounded-2xl`}>
         <header className="header relative">
           <div className="header-logo">
             {showHistory ? (
               <button
                 type="button"
                 onClick={handleBackToChat}
-                className="text-sky-400 hover:text-sky-500 cursor-pointer"
+                className={`${isDarkMode ? 'text-sky-400 hover:text-sky-300' : 'text-sky-400 hover:text-sky-500'} cursor-pointer`}
                 aria-label="Back to chat">
                 ← Back
               </button>
@@ -492,7 +507,7 @@ const SidePanel = () => {
                   type="button"
                   onClick={handleNewChat}
                   onKeyDown={e => e.key === 'Enter' && handleNewChat()}
-                  className="header-icon text-sky-400 hover:text-sky-500 cursor-pointer"
+                  className={`header-icon ${isDarkMode ? 'text-sky-400 hover:text-sky-300' : 'text-sky-400 hover:text-sky-500'} cursor-pointer`}
                   aria-label="New Chat"
                   tabIndex={0}>
                   <PiPlusBold size={20} />
@@ -501,7 +516,7 @@ const SidePanel = () => {
                   type="button"
                   onClick={handleLoadHistory}
                   onKeyDown={e => e.key === 'Enter' && handleLoadHistory()}
-                  className="header-icon text-sky-400 hover:text-sky-500 cursor-pointer"
+                  className={`header-icon ${isDarkMode ? 'text-sky-400 hover:text-sky-300' : 'text-sky-400 hover:text-sky-500'} cursor-pointer`}
                   aria-label="Load History"
                   tabIndex={0}>
                   <GrHistory size={20} />
@@ -512,14 +527,14 @@ const SidePanel = () => {
               href="https://discord.gg/NN3ABHggMK"
               target="_blank"
               rel="noopener noreferrer"
-              className="header-icon text-sky-400 hover:text-sky-500">
+              className={`header-icon ${isDarkMode ? 'text-sky-400 hover:text-sky-300' : 'text-sky-400 hover:text-sky-500'}`}>
               <RxDiscordLogo size={20} />
             </a>
             <button
               type="button"
               onClick={() => chrome.runtime.openOptionsPage()}
               onKeyDown={e => e.key === 'Enter' && chrome.runtime.openOptionsPage()}
-              className="header-icon text-sky-400 hover:text-sky-500 cursor-pointer"
+              className={`header-icon ${isDarkMode ? 'text-sky-400 hover:text-sky-300' : 'text-sky-400 hover:text-sky-500'} cursor-pointer`}
               aria-label="Settings"
               tabIndex={0}>
               <FiSettings size={20} />
@@ -533,13 +548,15 @@ const SidePanel = () => {
               onSessionSelect={handleSessionSelect}
               onSessionDelete={handleSessionDelete}
               visible={true}
+              isDarkMode={isDarkMode}
             />
           </div>
         ) : (
           <>
             {messages.length === 0 && (
               <>
-                <div className="border-t border-sky-100 backdrop-blur-sm p-2 shadow-sm mb-2">
+                <div
+                  className={`border-t ${isDarkMode ? 'border-sky-900' : 'border-sky-100'} backdrop-blur-sm p-2 shadow-sm mb-2`}>
                   <ChatInput
                     onSendMessage={handleSendMessage}
                     onStopTask={handleStopTask}
@@ -548,19 +565,26 @@ const SidePanel = () => {
                     setContent={setter => {
                       setInputTextRef.current = setter;
                     }}
+                    isDarkMode={isDarkMode}
                   />
                 </div>
                 <div>
-                  <TemplateList templates={defaultTemplates} onTemplateSelect={handleTemplateSelect} />
+                  <TemplateList
+                    templates={defaultTemplates}
+                    onTemplateSelect={handleTemplateSelect}
+                    isDarkMode={isDarkMode}
+                  />
                 </div>
               </>
             )}
-            <div className="flex-1 overflow-y-scroll overflow-x-hidden scrollbar-gutter-stable p-4 scroll-smooth">
-              <MessageList messages={messages} />
+            <div
+              className={`flex-1 overflow-y-scroll overflow-x-hidden scrollbar-gutter-stable p-4 scroll-smooth ${isDarkMode ? 'bg-slate-900 bg-opacity-80' : ''}`}>
+              <MessageList messages={messages} isDarkMode={isDarkMode} />
               <div ref={messagesEndRef} />
             </div>
             {messages.length > 0 && (
-              <div className="border-t border-sky-100 backdrop-blur-sm p-2 shadow-sm">
+              <div
+                className={`border-t ${isDarkMode ? 'border-sky-900' : 'border-sky-100'} backdrop-blur-sm p-2 shadow-sm`}>
                 <ChatInput
                   onSendMessage={handleSendMessage}
                   onStopTask={handleStopTask}
@@ -569,6 +593,7 @@ const SidePanel = () => {
                   setContent={setter => {
                     setInputTextRef.current = setter;
                   }}
+                  isDarkMode={isDarkMode}
                 />
               </div>
             )}

+ 42 - 47
pages/side-panel/src/components/ChatHistoryList.tsx

@@ -12,66 +12,61 @@ interface ChatHistoryListProps {
   onSessionSelect: (sessionId: string) => void;
   onSessionDelete: (sessionId: string) => void;
   visible: boolean;
+  isDarkMode?: boolean;
 }
 
-const ChatHistoryList: React.FC<ChatHistoryListProps> = ({ sessions, onSessionSelect, onSessionDelete, visible }) => {
+const ChatHistoryList: React.FC<ChatHistoryListProps> = ({
+  sessions,
+  onSessionSelect,
+  onSessionDelete,
+  visible,
+  isDarkMode = false,
+}) => {
   if (!visible) return null;
 
   const formatDate = (timestamp: number) => {
-    const now = Date.now();
-    const deltaSeconds = Math.floor((now - timestamp) / 1000);
-
-    // Less than 1 minute
-    if (deltaSeconds < 60) {
-      return `${deltaSeconds} seconds ago`;
-    }
-
-    // Less than 1 hour
-    const deltaMinutes = Math.floor(deltaSeconds / 60);
-    if (deltaMinutes < 60) {
-      return `${deltaMinutes} ${deltaMinutes === 1 ? 'minute' : 'minutes'} ago`;
-    }
-
-    // Less than 24 hours
-    const deltaHours = Math.floor(deltaMinutes / 60);
-    if (deltaHours < 24) {
-      const remainingMinutes = deltaMinutes % 60;
-      const hourText = `${deltaHours} ${deltaHours === 1 ? 'hour' : 'hours'}`;
-      const minuteText =
-        remainingMinutes > 0 ? ` ${remainingMinutes} ${remainingMinutes === 1 ? 'minute' : 'minutes'}` : '';
-      return `${hourText}${minuteText} ago`;
-    }
-
-    // More than 24 hours - use standard date format
-    return new Date(timestamp).toLocaleString();
+    const date = new Date(timestamp);
+    return date.toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' });
   };
 
   return (
-    <div className="h-full w-full overflow-y-auto p-4">
+    <div className="h-full overflow-y-auto p-4">
+      <h2 className={`mb-4 text-lg font-semibold ${isDarkMode ? 'text-gray-200' : 'text-gray-800'}`}>Chat History</h2>
       {sessions.length === 0 ? (
-        <div className="p-4 text-gray-500 text-center backdrop-blur-sm bg-white/30 rounded-lg">No chat history</div>
+        <div
+          className={`rounded-lg ${isDarkMode ? 'bg-slate-800 text-gray-400' : 'bg-white/30 text-gray-500'} p-4 text-center backdrop-blur-sm`}>
+          No chat history available
+        </div>
       ) : (
-        <div className="space-y-3">
+        <div className="space-y-2">
           {sessions.map(session => (
             <div
               key={session.id}
-              className="group backdrop-blur-sm bg-white/50 hover:bg-white/70 rounded-lg border border-sky-100 shadow-sm transition-all">
-              <div className="p-4 flex items-center">
-                <button
-                  type="button"
-                  className="flex-1 min-w-0 cursor-pointer text-left"
-                  onClick={() => onSessionSelect(session.id)}>
-                  <p className="text-sm font-medium text-gray-900 truncate">{session.title}</p>
-                  <p className="text-xs text-gray-500 mt-1">{formatDate(session.createdAt)}</p>
-                </button>
-                <button
-                  type="button"
-                  onClick={() => onSessionDelete(session.id)}
-                  className="ml-3 text-gray-400 hover:text-red-500 opacity-0 group-hover:opacity-100 transition-opacity"
-                  aria-label="Delete chat session">
-                  <FaTrash size={14} />
-                </button>
-              </div>
+              className={`group relative rounded-lg ${
+                isDarkMode ? 'bg-slate-800 hover:bg-slate-700' : 'bg-white/50 hover:bg-white/70'
+              } p-3 transition-all backdrop-blur-sm`}>
+              <button onClick={() => onSessionSelect(session.id)} className="w-full text-left" type="button">
+                <h3 className={`text-sm font-medium ${isDarkMode ? 'text-gray-200' : 'text-gray-900'}`}>
+                  {session.title}
+                </h3>
+                <p className={`mt-1 text-xs ${isDarkMode ? 'text-gray-400' : 'text-gray-500'}`}>
+                  {formatDate(session.createdAt)}
+                </p>
+              </button>
+              <button
+                onClick={e => {
+                  e.stopPropagation();
+                  onSessionDelete(session.id);
+                }}
+                className={`absolute right-2 top-2 rounded p-1 opacity-0 transition-opacity group-hover:opacity-100 ${
+                  isDarkMode
+                    ? 'bg-slate-700 text-red-400 hover:bg-slate-600'
+                    : 'bg-white text-red-500 hover:bg-gray-100'
+                }`}
+                aria-label="Delete session"
+                type="button">
+                <FaTrash size={14} />
+              </button>
             </div>
           ))}
         </div>

+ 24 - 7
pages/side-panel/src/components/ChatInput.tsx

@@ -6,9 +6,17 @@ interface ChatInputProps {
   disabled: boolean;
   showStopButton: boolean;
   setContent?: (setter: (text: string) => void) => void;
+  isDarkMode?: boolean;
 }
 
-export default function ChatInput({ onSendMessage, onStopTask, disabled, showStopButton, setContent }: ChatInputProps) {
+export default function ChatInput({
+  onSendMessage,
+  onStopTask,
+  disabled,
+  showStopButton,
+  setContent,
+  isDarkMode = false,
+}: ChatInputProps) {
   const [text, setText] = useState('');
   const textareaRef = useRef<HTMLTextAreaElement>(null);
 
@@ -65,7 +73,7 @@ export default function ChatInput({ onSendMessage, onStopTask, disabled, showSto
   return (
     <form
       onSubmit={handleSubmit}
-      className="border rounded-lg overflow-hidden hover:border-sky-400 focus-within:border-sky-400 transition-colors"
+      className={`overflow-hidden rounded-lg border transition-colors focus-within:border-sky-400 hover:border-sky-400 ${isDarkMode ? 'border-slate-700' : ''}`}
       aria-label="Chat input form">
       <div className="flex flex-col">
         <textarea
@@ -75,28 +83,37 @@ export default function ChatInput({ onSendMessage, onStopTask, disabled, showSto
           onKeyDown={handleKeyDown}
           disabled={disabled}
           rows={4}
-          className={`w-full p-3 resize-none border-none focus:outline-none ${
-            disabled ? 'bg-gray-100 text-gray-500' : 'bg-white'
+          className={`w-full resize-none border-none p-3 focus:outline-none ${
+            disabled
+              ? isDarkMode
+                ? 'bg-slate-800 text-gray-400'
+                : 'bg-gray-100 text-gray-500'
+              : isDarkMode
+                ? 'bg-slate-800 text-gray-200'
+                : 'bg-white'
           }`}
           placeholder="What can I help with?"
           aria-label="Message input"
         />
 
-        <div className={`flex items-center justify-between px-3 py-1.5 ${disabled ? 'bg-gray-100' : 'bg-white'}`}>
+        <div
+          className={`flex items-center justify-between px-3 py-1.5 ${
+            disabled ? (isDarkMode ? 'bg-slate-800' : 'bg-gray-100') : isDarkMode ? 'bg-slate-800' : 'bg-white'
+          }`}>
           <div className="flex gap-2 text-gray-500">{/* Icons can go here */}</div>
 
           {showStopButton ? (
             <button
               type="button"
               onClick={onStopTask}
-              className="px-3 py-1 bg-red-500 text-white rounded-md hover:bg-red-600 transition-colors">
+              className="rounded-md bg-red-500 px-3 py-1 text-white transition-colors hover:bg-red-600">
               Stop
             </button>
           ) : (
             <button
               type="submit"
               disabled={disabled}
-              className="px-3 py-1 bg-[#19C2FF] text-white rounded-md hover:bg-[#0073DC] transition-colors disabled:opacity-50">
+              className={`rounded-md bg-[#19C2FF] px-3 py-1 text-white transition-colors hover:bg-[#0073DC] ${disabled ? 'opacity-50' : ''}`}>
               Send
             </button>
           )}

+ 26 - 13
pages/side-panel/src/components/MessageList.tsx

@@ -4,16 +4,18 @@ import { memo } from 'react';
 
 interface MessageListProps {
   messages: Message[];
+  isDarkMode?: boolean;
 }
 
-export default memo(function MessageList({ messages }: MessageListProps) {
+export default memo(function MessageList({ messages, isDarkMode = false }: MessageListProps) {
   return (
-    <div className="space-y-4 max-w-full">
+    <div className="max-w-full space-y-4">
       {messages.map((message, index) => (
         <MessageBlock
           key={`${message.actor}-${message.timestamp}-${index}`}
           message={message}
           isSameActor={index > 0 ? messages[index - 1].actor === message.actor : false}
+          isDarkMode={isDarkMode}
         />
       ))}
     </div>
@@ -23,9 +25,10 @@ export default memo(function MessageList({ messages }: MessageListProps) {
 interface MessageBlockProps {
   message: Message;
   isSameActor: boolean;
+  isDarkMode?: boolean;
 }
 
-function MessageBlock({ message, isSameActor }: MessageBlockProps) {
+function MessageBlock({ message, isSameActor, isDarkMode = false }: MessageBlockProps) {
   if (!message.actor) {
     console.error('No actor found');
     return <div />;
@@ -35,32 +38,42 @@ function MessageBlock({ message, isSameActor }: MessageBlockProps) {
 
   return (
     <div
-      className={`flex gap-3 px-4 max-w-full ${
-        !isSameActor ? 'border-t border-sky-200/50 mt-4 pt-4 first:border-t-0 first:mt-0 first:pt-0' : ''
+      className={`flex max-w-full gap-3 px-4 ${
+        !isSameActor
+          ? `mt-4 border-t ${isDarkMode ? 'border-sky-800/50' : 'border-sky-200/50'} pt-4 first:mt-0 first:border-t-0 first:pt-0`
+          : ''
       }`}>
       {!isSameActor && (
         <div
-          className="w-8 h-8 rounded-full flex-shrink-0 flex items-center justify-center"
+          className="flex size-8 shrink-0 items-center justify-center rounded-full"
           style={{ backgroundColor: actor.iconBackground }}>
-          <img src={actor.icon} alt={actor.name} className="w-6 h-6" />
+          <img src={actor.icon} alt={actor.name} className="size-6" />
         </div>
       )}
       {isSameActor && <div className="w-8" />}
 
-      <div className="flex-1 min-w-0">
-        {!isSameActor && <div className="font-semibold text-sm text-gray-900 mb-1">{actor.name}</div>}
+      <div className="min-w-0 flex-1">
+        {!isSameActor && (
+          <div className={`mb-1 text-sm font-semibold ${isDarkMode ? 'text-gray-200' : 'text-gray-900'}`}>
+            {actor.name}
+          </div>
+        )}
 
         <div className="space-y-0.5">
-          <div className="text-sm text-gray-700 whitespace-pre-wrap break-words">
+          <div className={`whitespace-pre-wrap break-words text-sm ${isDarkMode ? 'text-gray-300' : 'text-gray-700'}`}>
             {isProgress ? (
-              <div className="h-1 bg-gray-200 rounded overflow-hidden">
-                <div className="h-full bg-blue-500 animate-progress" />
+              <div className={`h-1 overflow-hidden rounded ${isDarkMode ? 'bg-gray-700' : 'bg-gray-200'}`}>
+                <div className="animate-progress h-full bg-blue-500" />
               </div>
             ) : (
               message.content
             )}
           </div>
-          {!isProgress && <div className="text-xs text-gray-300 text-right">{formatTimestamp(message.timestamp)}</div>}
+          {!isProgress && (
+            <div className={`text-right text-xs ${isDarkMode ? 'text-gray-500' : 'text-gray-300'}`}>
+              {formatTimestamp(message.timestamp)}
+            </div>
+          )}
         </div>
       </div>
     </div>

+ 16 - 24
pages/side-panel/src/components/TemplateList.tsx

@@ -8,33 +8,25 @@ interface Template {
 interface TemplateListProps {
   templates: Template[];
   onTemplateSelect: (content: string) => void;
+  isDarkMode?: boolean;
 }
 
-const TemplateList: React.FC<TemplateListProps> = ({ templates, onTemplateSelect }) => {
+const TemplateList: React.FC<TemplateListProps> = ({ templates, onTemplateSelect, isDarkMode = false }) => {
   return (
-    <div className="w-full px-4 pt-2 pb-4">
-      <h2 className="text-base font-semibold text-gray-700 mb-3 px-1">Quick Start</h2>
-      {templates.length === 0 ? (
-        <div className="p-4 text-gray-500 text-center backdrop-blur-sm bg-white/30 rounded-lg">
-          No templates available
-        </div>
-      ) : (
-        <div className="space-y-2">
-          {templates.map(template => (
-            <button
-              key={template.id}
-              onClick={() => onTemplateSelect(template.content)}
-              onKeyDown={e => e.key === 'Enter' && onTemplateSelect(template.content)}
-              className="w-full group backdrop-blur-sm bg-white/50 hover:bg-white/70 rounded-lg border border-sky-100 shadow-sm transition-all text-left"
-              type="button">
-              <div className="p-3">
-                <p className="text-sm font-medium text-gray-900">{template.title}</p>
-                <p className="text-xs text-gray-500 mt-1 truncate">{template.content}</p>
-              </div>
-            </button>
-          ))}
-        </div>
-      )}
+    <div className="p-4">
+      <h3 className={`mb-3 text-sm font-medium ${isDarkMode ? 'text-gray-200' : 'text-gray-700'}`}>Templates</h3>
+      <div className="grid grid-cols-1 gap-3 sm:grid-cols-2">
+        {templates.map(template => (
+          <button
+            key={template.id}
+            onClick={() => onTemplateSelect(template.content)}
+            className={`rounded-lg p-3 text-left transition-colors ${
+              isDarkMode ? 'bg-slate-800 text-gray-200 hover:bg-slate-700' : 'bg-white text-gray-700 hover:bg-sky-50'
+            } border ${isDarkMode ? 'border-slate-700' : 'border-sky-100'}`}>
+            <div className="text-sm font-medium">{template.title}</div>
+          </button>
+        ))}
+      </div>
     </div>
   );
 };