Selaa lähdekoodia

fix dark mode and other styling/linting issues

Chris Watson 5 kuukautta sitten
vanhempi
commit
417aa478d0

+ 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>

+ 182 - 191
pages/options/src/components/ModelSettings.tsx

@@ -8,7 +8,11 @@ import {
   llmProviderModelNames,
 } from '@extension/storage';
 
-export const ModelSettings = () => {
+interface ModelSettingsProps {
+  isDarkMode?: boolean;
+}
+
+export const ModelSettings = ({ isDarkMode = false }: ModelSettingsProps) => {
   const [apiKeys, setApiKeys] = useState<Record<LLMProviderEnum, { apiKey: string; baseUrl?: string }>>(
     {} as Record<LLMProviderEnum, { apiKey: string; baseUrl?: string }>,
   );
@@ -32,65 +36,67 @@ export const ModelSettings = () => {
         for (const provider of providers) {
           const config = await llmProviderStore.getProvider(provider);
           if (config) {
-            keys[provider] = config;
+            keys[provider] = {
+              apiKey: config.apiKey || '',
+              baseUrl: config.baseUrl,
+            };
           }
         }
+
         setApiKeys(keys);
       } catch (error) {
-        console.error('Error loading API keys:', error);
-        setApiKeys({} as Record<LLMProviderEnum, { apiKey: string; baseUrl?: string }>);
+        console.error('Failed to load API keys:', error);
       }
     };
 
-    loadApiKeys();
-  }, []);
-
-  // Load existing agent models on mount
-  useEffect(() => {
     const loadAgentModels = async () => {
       try {
         const models: Record<AgentNameEnum, string> = {
-          [AgentNameEnum.Planner]: '',
           [AgentNameEnum.Navigator]: '',
+          [AgentNameEnum.Planner]: '',
           [AgentNameEnum.Validator]: '',
         };
 
         for (const agent of Object.values(AgentNameEnum)) {
-          const config = await agentModelStore.getAgentModel(agent);
-          if (config) {
-            models[agent] = config.modelName;
+          const model = await agentModelStore.getAgentModel(agent);
+          if (model) {
+            models[agent] = model.modelName;
           }
         }
+
         setSelectedModels(models);
       } catch (error) {
-        console.error('Error loading agent models:', error);
+        console.error('Failed to load agent models:', error);
       }
     };
 
+    loadApiKeys();
     loadAgentModels();
   }, []);
 
   const handleApiKeyChange = (provider: LLMProviderEnum, apiKey: string, baseUrl?: string) => {
-    setModifiedProviders(prev => new Set(prev).add(provider));
     setApiKeys(prev => ({
       ...prev,
-      [provider]: {
-        apiKey: apiKey.trim(),
-        baseUrl: baseUrl !== undefined ? baseUrl.trim() : prev[provider]?.baseUrl,
-      },
+      [provider]: { apiKey, baseUrl },
     }));
+
+    setModifiedProviders(prev => {
+      const newSet = new Set(prev);
+      newSet.add(provider);
+      return newSet;
+    });
   };
 
   const handleSave = async (provider: LLMProviderEnum) => {
     try {
       await llmProviderStore.setProvider(provider, apiKeys[provider]);
       setModifiedProviders(prev => {
-        const next = new Set(prev);
-        next.delete(provider);
-        return next;
+        const newSet = new Set(prev);
+        newSet.delete(provider);
+        return newSet;
       });
     } catch (error) {
-      console.error('Error saving API key:', error);
+      console.error('Failed to save API key:', error);
     }
   };
 
@@ -98,99 +104,162 @@ export const ModelSettings = () => {
     try {
       await llmProviderStore.removeProvider(provider);
       setApiKeys(prev => {
-        const next = { ...prev };
-        delete next[provider];
-        return next;
+        const newKeys = { ...prev };
+        delete newKeys[provider];
+        return newKeys;
+      });
+      setModifiedProviders(prev => {
+        const newSet = new Set(prev);
+        newSet.delete(provider);
+        return newSet;
       });
     } catch (error) {
-      console.error('Error deleting API key:', error);
+      console.error('Failed to delete API key:', error);
     }
   };
 
   const getButtonProps = (provider: LLMProviderEnum) => {
-    const hasStoredKey = Boolean(apiKeys[provider]?.apiKey);
     const isModified = modifiedProviders.has(provider);
-    const hasInput = Boolean(apiKeys[provider]?.apiKey?.trim());
-
-    if (hasStoredKey && !isModified) {
-      return {
-        variant: 'danger' as const,
-        children: 'Delete',
-        disabled: false,
-      };
-    }
+    const hasApiKey = apiKeys[provider]?.apiKey;
 
     return {
-      variant: 'primary' as const,
-      children: 'Save',
-      disabled: !hasInput || !isModified,
+      saveButton: {
+        disabled: !isModified || !hasApiKey,
+        className: `rounded-md px-3 py-1 text-sm font-medium ${
+          !isModified || !hasApiKey
+            ? `${isDarkMode ? 'bg-slate-700 text-gray-400' : 'bg-gray-200 text-gray-500'}`
+            : `${isDarkMode ? 'bg-blue-600 text-white hover:bg-blue-700' : 'bg-blue-500 text-white hover:bg-blue-600'}`
+        }`,
+      },
+      deleteButton: {
+        disabled: !hasApiKey,
+        className: `ml-2 rounded-md px-3 py-1 text-sm font-medium ${
+          !hasApiKey
+            ? `${isDarkMode ? 'bg-slate-700 text-gray-400' : 'bg-gray-200 text-gray-500'}`
+            : `${isDarkMode ? 'bg-red-600 text-white hover:bg-red-700' : 'bg-red-500 text-white hover:bg-red-600'}`
+        }`,
+      },
     };
   };
 
   const getAvailableModels = () => {
     const models: string[] = [];
-    Object.entries(apiKeys).forEach(([provider, config]) => {
-      if (config.apiKey) {
-        models.push(...(llmProviderModelNames[provider as LLMProviderEnum] || []));
+    Object.values(LLMProviderEnum).forEach(provider => {
+      if (apiKeys[provider]?.apiKey) {
+        models.push(...(llmProviderModelNames[provider] || []));
       }
     });
-    return models.length ? models : [''];
+    return models;
   };
 
   const handleModelChange = async (agentName: AgentNameEnum, model: string) => {
-    setSelectedModels(prev => ({
-      ...prev,
-      [agentName]: model,
-    }));
-
     try {
-      if (model) {
-        // Determine provider from model name
-        let provider: LLMProviderEnum | undefined;
-        for (const [providerKey, models] of Object.entries(llmProviderModelNames)) {
-          if (models.includes(model)) {
-            provider = providerKey as LLMProviderEnum;
-            break;
-          }
+      // Determine provider from model name
+      let provider: LLMProviderEnum | undefined;
+      for (const [providerKey, models] of Object.entries(llmProviderModelNames)) {
+        if (models.includes(model)) {
+          provider = providerKey as LLMProviderEnum;
+          break;
         }
+      }
 
-        if (provider) {
-          await agentModelStore.setAgentModel(agentName, {
-            provider,
-            modelName: model,
-          });
-        }
-      } else {
-        // Reset storage if no model is selected
-        await agentModelStore.resetAgentModel(agentName);
+      if (provider) {
+        await agentModelStore.setAgentModel(agentName, {
+          provider,
+          modelName: model,
+        });
+        setSelectedModels(prev => ({
+          ...prev,
+          [agentName]: model,
+        }));
       }
     } catch (error) {
-      console.error('Error saving agent model:', error);
+      console.error(`Failed to set model for ${agentName}:`, error);
     }
   };
 
+  const renderApiKeyInput = (provider: LLMProviderEnum) => {
+    const buttonProps = getButtonProps(provider);
+    const needsBaseUrl = provider === LLMProviderEnum.OpenAI || provider === LLMProviderEnum.Anthropic;
+
+    return (
+      <div key={provider} className="mb-6">
+        <div className="mb-2 flex items-center justify-between">
+          <h3 className={`text-lg font-medium ${isDarkMode ? 'text-gray-300' : 'text-gray-700'}`}>{provider}</h3>
+          <div>
+            <Button
+              onClick={() => handleSave(provider)}
+              disabled={buttonProps.saveButton.disabled}
+              className={buttonProps.saveButton.className}>
+              Save
+            </Button>
+            <Button
+              onClick={() => handleDelete(provider)}
+              disabled={buttonProps.deleteButton.disabled}
+              className={buttonProps.deleteButton.className}>
+              Delete
+            </Button>
+          </div>
+        </div>
+
+        <div className="space-y-2">
+          <div>
+            <label
+              htmlFor={`${provider}-api-key`}
+              className={`mb-1 block text-sm font-medium ${isDarkMode ? 'text-gray-300' : 'text-gray-700'}`}>
+              API Key
+            </label>
+            <input
+              id={`${provider}-api-key`}
+              type="password"
+              value={apiKeys[provider]?.apiKey || ''}
+              onChange={e => handleApiKeyChange(provider, e.target.value, apiKeys[provider]?.baseUrl)}
+              className={`w-full 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`}
+              placeholder={`Enter your ${provider} API key`}
+            />
+          </div>
+
+          {needsBaseUrl && (
+            <div>
+              <label
+                htmlFor={`${provider}-base-url`}
+                className={`mb-1 block text-sm font-medium ${isDarkMode ? 'text-gray-300' : 'text-gray-700'}`}>
+                Base URL (Optional)
+              </label>
+              <input
+                id={`${provider}-base-url`}
+                type="text"
+                value={apiKeys[provider]?.baseUrl || ''}
+                onChange={e => handleApiKeyChange(provider, apiKeys[provider]?.apiKey || '', e.target.value)}
+                className={`w-full 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`}
+                placeholder={`Enter custom base URL for ${provider} (optional)`}
+              />
+            </div>
+          )}
+        </div>
+      </div>
+    );
+  };
+
   const renderModelSelect = (agentName: AgentNameEnum) => (
-    <div className="flex items-center justify-between">
-      <div>
-        <h3 className="text-lg font-medium text-gray-700">{agentName.charAt(0).toUpperCase() + agentName.slice(1)}</h3>
-        <p className="text-sm font-normal text-gray-500">{getAgentDescription(agentName)}</p>
+    <div key={agentName} className="mb-6">
+      <div className="mb-2">
+        <h3 className={`text-lg font-medium ${isDarkMode ? 'text-gray-300' : 'text-gray-700'}`}>{agentName}</h3>
+        <p className={`text-sm ${isDarkMode ? 'text-gray-400' : 'text-gray-500'}`}>{getAgentDescription(agentName)}</p>
       </div>
+
       <select
-        className="w-64 px-3 py-2 border rounded-md"
-        disabled={getAvailableModels().length <= 1}
+        id={`${agentName}-model`}
         value={selectedModels[agentName] || ''}
-        onChange={e => handleModelChange(agentName, e.target.value)}>
-        <option key="default" value="">
-          Choose model
-        </option>
-        {getAvailableModels().map(
-          model =>
-            model && (
-              <option key={model} value={model}>
-                {model}
-              </option>
-            ),
-        )}
+        onChange={e => handleModelChange(agentName, e.target.value)}
+        className={`w-full 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 === 0}>
+        <option value="">Select a model</option>
+        {getAvailableModels().map(model => (
+          <option key={model} value={model}>
+            {model}
+          </option>
+        ))}
       </select>
     </div>
   );
@@ -198,125 +267,47 @@ export const ModelSettings = () => {
   const getAgentDescription = (agentName: AgentNameEnum) => {
     switch (agentName) {
       case AgentNameEnum.Navigator:
-        return 'Navigates websites and performs actions';
+        return 'Handles browsing and interacting with web pages';
       case AgentNameEnum.Planner:
-        return 'Develops and refines strategies to complete tasks';
+        return 'Creates and updates the plan for completing tasks';
       case AgentNameEnum.Validator:
-        return 'Checks if tasks are completed successfully';
+        return 'Validates the results of actions and task completion';
       default:
         return '';
     }
   };
 
   return (
-    <section className="space-y-6">
-      {/* API Keys 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">API Keys</h2>
-        <div className="space-y-6">
-          {/* OpenAI Section */}
-          <div className="space-y-4">
-            <div className="flex items-center justify-between">
-              <h3 className="text-lg font-medium text-gray-700">OpenAI</h3>
-              <Button
-                {...getButtonProps(LLMProviderEnum.OpenAI)}
-                size="sm"
-                onClick={() =>
-                  apiKeys[LLMProviderEnum.OpenAI]?.apiKey && !modifiedProviders.has(LLMProviderEnum.OpenAI)
-                    ? handleDelete(LLMProviderEnum.OpenAI)
-                    : handleSave(LLMProviderEnum.OpenAI)
-                }
-              />
-            </div>
-            <div className="space-y-3">
-              <input
-                type="password"
-                placeholder="OpenAI API key"
-                value={apiKeys[LLMProviderEnum.OpenAI]?.apiKey || ''}
-                onChange={e => handleApiKeyChange(LLMProviderEnum.OpenAI, e.target.value)}
-                className="w-full p-2 rounded-md bg-gray-50 border border-gray-200 focus:border-blue-400 focus:ring-2 focus:ring-blue-200 outline-none"
-              />
-              <input
-                type="text"
-                placeholder="Custom Base URL (Optional)"
-                value={apiKeys[LLMProviderEnum.OpenAI]?.baseUrl || ''}
-                onChange={e =>
-                  handleApiKeyChange(
-                    LLMProviderEnum.OpenAI,
-                    apiKeys[LLMProviderEnum.OpenAI]?.apiKey || '',
-                    e.target.value,
-                  )
-                }
-                className="w-full p-2 rounded-md bg-gray-50 border border-gray-200 focus:border-blue-400 focus:ring-2 focus:ring-blue-200 outline-none"
-              />
-            </div>
-          </div>
-
-          <div className="border-t border-gray-200"></div>
+    <div className="space-y-8">
+      <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-xl font-semibold ${isDarkMode ? 'text-gray-200' : 'text-gray-800'}`}>
+          LLM Provider API Keys
+        </h2>
+        <p className={`mb-4 text-sm ${isDarkMode ? 'text-gray-400' : 'text-gray-500'}`}>
+          Configure your API keys for the LLM providers you want to use. Your keys are stored locally and never sent to
+          our servers.
+        </p>
 
-          {/* Anthropic Section */}
-          <div className="space-y-4">
-            <div className="flex items-center justify-between">
-              <h3 className="text-lg font-medium text-gray-700">Anthropic</h3>
-              <Button
-                {...getButtonProps(LLMProviderEnum.Anthropic)}
-                size="sm"
-                onClick={() =>
-                  apiKeys[LLMProviderEnum.Anthropic]?.apiKey && !modifiedProviders.has(LLMProviderEnum.Anthropic)
-                    ? handleDelete(LLMProviderEnum.Anthropic)
-                    : handleSave(LLMProviderEnum.Anthropic)
-                }
-              />
-            </div>
-            <div className="space-y-3">
-              <input
-                type="password"
-                placeholder="Anthropic API key"
-                value={apiKeys[LLMProviderEnum.Anthropic]?.apiKey || ''}
-                onChange={e => handleApiKeyChange(LLMProviderEnum.Anthropic, e.target.value)}
-                className="w-full p-2 rounded-md bg-gray-50 border border-gray-200 focus:border-blue-400 focus:ring-2 focus:ring-blue-200 outline-none"
-              />
-            </div>
-          </div>
+        {Object.values(LLMProviderEnum).map(provider => renderApiKeyInput(provider))}
+      </div>
 
-          <div className="border-t border-gray-200" />
+      <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-xl font-semibold ${isDarkMode ? 'text-gray-200' : 'text-gray-800'}`}>Agent Models</h2>
+        <p className={`mb-4 text-sm ${isDarkMode ? 'text-gray-400' : 'text-gray-500'}`}>
+          Select which models to use for each agent. You must configure at least one LLM provider above.
+        </p>
 
-          {/* Gemini Section */}
-          <div className="space-y-4">
-            <div className="flex items-center justify-between">
-              <h3 className="text-lg font-medium text-gray-700">Gemini</h3>
-              <Button
-                {...getButtonProps(LLMProviderEnum.Gemini)}
-                size="sm"
-                onClick={() =>
-                  apiKeys[LLMProviderEnum.Gemini]?.apiKey && !modifiedProviders.has(LLMProviderEnum.Gemini)
-                    ? handleDelete(LLMProviderEnum.Gemini)
-                    : handleSave(LLMProviderEnum.Gemini)
-                }
-              />
-            </div>
-            <div className="space-y-3">
-              <input
-                type="password"
-                placeholder="Gemini API key"
-                value={apiKeys[LLMProviderEnum.Gemini]?.apiKey || ''}
-                onChange={e => handleApiKeyChange(LLMProviderEnum.Gemini, e.target.value)}
-                className="w-full p-2 rounded-md bg-gray-50 border border-gray-200 focus:border-blue-400 focus:ring-2 focus:ring-blue-200 outline-none"
-              />
-            </div>
+        {getAvailableModels().length === 0 && (
+          <div
+            className={`mb-4 rounded-md ${isDarkMode ? 'bg-slate-700 text-gray-300' : 'bg-yellow-50 text-yellow-800'} p-3`}>
+            <p className="text-sm">Please configure at least one LLM provider API key to select models.</p>
           </div>
-        </div>
-      </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="space-y-4">
-          {[AgentNameEnum.Planner, AgentNameEnum.Navigator, AgentNameEnum.Validator].map(agentName => (
-            <div key={agentName}>{renderModelSelect(agentName)}</div>
-          ))}
-        </div>
+        {Object.values(AgentNameEnum).map(agentName => renderModelSelect(agentName))}
       </div>
-    </section>
+    </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>
   );
 };