import { createSignal, onMount, Show, For } from 'solid-js'; import { useNavigate } from '@solidjs/router'; import { useAuth } from '@/lib/auth'; import { IconUser, IconLock, IconKey, IconBrain, IconMail, IconSend, IconShield, IconDownload } from '@tabler/icons-solidjs'; import { TwoFactorAuth } from '@/components/TwoFactorAuth'; import { Button } from '@/components/ui/Button'; import { AIProviderIcon } from '@/components/AIProviderIcon'; import { useHaptics } from '@/lib/haptics'; import { getApiV1BaseUrl } from '@/lib/api-url'; interface BrowserExtensionApiKey { id: number; name: string; permissions: string[]; is_active: boolean; last_used?: string; } interface BrowserExtensionClient { id: number; extension_id: string; name: string; is_active: boolean; last_seen?: string; } export const Settings = () => { const { authState, updateProfile, changePassword } = useAuth(); const navigate = useNavigate(); const haptics = useHaptics(); const apiBaseUrl = getApiV1BaseUrl(); const [isLoading, setIsLoading] = createSignal(false); const [message, setMessage] = createSignal(''); const [profileData, setProfileData] = createSignal({ fullName: '', theme: 'dark', showBrowserSearch: true }); const [customColors, setCustomColors] = createSignal({ primary: '#5ab9ff', background: '#000000', foreground: '#ffffff', muted: '#262727', border: '#262626' }); // Apply color changes immediately to CSS custom properties const applyColorChange = (colorType: string, color: string) => { setCustomColors(prev => { const newColors = { ...prev, [colorType]: color }; // Save to localStorage for persistence localStorage.setItem('customColors', JSON.stringify(newColors)); localStorage.setItem('colorScheme', 'custom'); return newColors; }); // Apply immediately to CSS custom properties with proper HSL conversion const root = document.documentElement; // Convert hex to HSL for CSS variables const hexToHsl = (hex: string) => { const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); if (!result) return '0 0% 100%'; let r = parseInt(result[1], 16) / 255; let g = parseInt(result[2], 16) / 255; let b = parseInt(result[3], 16) / 255; const max = Math.max(r, g, b); const min = Math.min(r, g, b); let h = 0, s = 0, l = (max + min) / 2; if (max !== min) { const d = max - min; s = l > 0.5 ? d / (2 - max - min) : d / (max + min); switch (max) { case r: h = ((g - b) / d + (g < b ? 6 : 0)) / 6; break; case g: h = ((b - r) / d + 2) / 6; break; case b: h = ((r - g) / d + 4) / 6; break; } } return `${Math.round(h * 360)} ${Math.round(s * 100)}% ${Math.round(l * 100)}%`; }; const hslColor = hexToHsl(color); if (colorType === 'primary') { root.style.setProperty('--primary', hslColor); root.style.setProperty('--ring', hslColor); root.style.setProperty('--colors-primary', hslColor); } else if (colorType === 'background') { root.style.setProperty('--background', hslColor); root.style.setProperty('--colors-background', hslColor); } else if (colorType === 'foreground') { root.style.setProperty('--foreground', hslColor); root.style.setProperty('--colors-foreground', hslColor); } else if (colorType === 'muted') { root.style.setProperty('--muted', hslColor); root.style.setProperty('--colors-muted', hslColor); } else if (colorType === 'border') { root.style.setProperty('--border', color); root.style.setProperty('--colors-border', color); } }; const [passwordData, setPasswordData] = createSignal({ currentPassword: '', newPassword: '', confirmPassword: '' }); const [aiSettings, setAISettings] = createSignal({ mistral: { enabled: false, api_key: '', model: 'mistral-small-latest', model_thinking: 'mistral-large-latest' }, grok: { enabled: false, api_key: '', base_url: 'https://api.x.ai/v1', model: 'grok-4-1-fast-non-reasoning-latest', model_thinking: 'grok-4-1-fast-reasoning-latest' }, deepseek: { enabled: false, api_key: '', base_url: 'https://api.deepseek.com', model: 'deepseek-chat', model_thinking: 'deepseek-reasoner' }, ollama: { enabled: false, base_url: 'http://localhost:11434', model: 'llama3.1', model_thinking: 'llama3.1' }, longcat: { enabled: false, api_key: '', base_url: 'https://api.longcat.chat', openai_endpoint: 'https://api.longcat.chat/openai', anthropic_endpoint: 'https://api.longcat.chat/anthropic', model: 'LongCat-Flash-Chat', model_thinking: 'LongCat-Flash-Thinking', model_thinking_upgraded: 'LongCat-Flash-Thinking-2601', format: 'openai' }, openrouter: { enabled: false, api_key: '', base_url: 'https://openrouter.ai/api', model: 'openrouter/auto', model_thinking: 'openrouter/auto' } }); const [availableAIProviders, setAvailableAIProviders] = createSignal([]); const [emailSettings, setEmailSettings] = createSignal({ smtp_enabled: false, smtp_host: '', smtp_port: 587, smtp_username: '', smtp_password: '', smtp_from_email: '', smtp_from_name: 'Trackeep', smtp_encryption: 'tls' as 'none' | 'ssl' | 'tls', oauth_enabled: false, oauth_provider: 'google' as 'google' | 'microsoft' | 'github', oauth_client_id: '', oauth_client_secret: '', oauth_redirect_uri: '' }); const [searchSettings, setSearchSettings] = createSignal({ brave_api_key: '', brave_search_base_url: 'https://api.search.brave.com/res/v1/web/search', serper_api_key: '', serper_base_url: 'https://google.serper.dev/search', search_api_provider: 'brave', search_results_limit: 10, search_cache_ttl: 300, search_rate_limit: 100 }); const [emailSettingsExpanded, setEmailSettingsExpanded] = createSignal(true); const [aiLoading, setAiLoading] = createSignal(false); const [activeTab, setActiveTab] = createSignal('account'); const [browserExtensionApiKeys, setBrowserExtensionApiKeys] = createSignal([]); const [browserExtensions, setBrowserExtensions] = createSignal([]); const tabs = [ { id: 'account', name: 'Account', icon: IconUser }, { id: 'security', name: 'Security', icon: IconShield }, { id: 'ai', name: 'AI & Integration', icon: IconBrain }, { id: 'communication', name: 'Communication', icon: IconMail }, { id: 'search', name: 'Search API', icon: IconBrain }, { id: 'tools', name: 'Tools', icon: IconDownload } ]; onMount(() => { if (authState.user) { setProfileData({ fullName: authState.user.full_name, theme: authState.user.theme || 'dark', showBrowserSearch: localStorage.getItem('showBrowserSearch') !== 'false' }); } // Load saved custom colors const savedColors = localStorage.getItem('customColors'); const savedScheme = localStorage.getItem('colorScheme'); if (savedColors && savedScheme === 'custom') { try { const colors = JSON.parse(savedColors); setCustomColors(colors); // Apply the saved colors immediately Object.entries(colors).forEach(([colorType, color]) => { if (typeof color === 'string') { applyColorChange(colorType, color); } }); } catch (e) { console.error('Failed to load custom colors:', e); } } loadAISettings(); loadAvailableAIProviders(); loadSearchSettings(); loadBrowserExtensionAccess(); }); const loadAISettings = async () => { try { const endpoint = `${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/auth/ai/settings`; const response = await fetch(endpoint, { headers: { 'Authorization': `Bearer ${localStorage.getItem('token')}`, 'Content-Type': 'application/json' } }); if (response.ok) { const data = await response.json(); setAISettings(data); } } catch (error) { console.error('Failed to load AI settings:', error); } }; const loadAvailableAIProviders = async () => { try { const endpoint = `${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/ai/providers`; const response = await fetch(endpoint, { headers: { 'Authorization': `Bearer ${localStorage.getItem('token')}`, 'Content-Type': 'application/json' } }); if (response.ok) { const data = await response.json(); const providers = (data.providers || []) as { id: string }[]; setAvailableAIProviders(providers.map((p) => p.id)); } } catch (error) { console.error('Failed to load available AI providers:', error); setAvailableAIProviders(['mistral', 'grok', 'deepseek', 'ollama', 'longcat', 'openrouter']); } }; const loadSearchSettings = async () => { try { const endpoint = `${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/auth/search/settings`; const response = await fetch(endpoint, { headers: { 'Authorization': `Bearer ${localStorage.getItem('token')}`, 'Content-Type': 'application/json' } }); if (response.ok) { const data = await response.json(); setSearchSettings(data); } } catch (error) { console.error('Failed to load search settings:', error); } }; const loadBrowserExtensionAccess = async () => { try { const token = localStorage.getItem('trackeep_token') || localStorage.getItem('token'); const headers = { 'Authorization': `Bearer ${token}`, }; const [apiKeysResponse, extensionsResponse] = await Promise.all([ fetch(`${apiBaseUrl}/browser-extension/api-keys`, { headers }), fetch(`${apiBaseUrl}/browser-extension/extensions`, { headers }), ]); if (apiKeysResponse.ok) { const keys = await apiKeysResponse.json(); setBrowserExtensionApiKeys(Array.isArray(keys) ? keys : []); } if (extensionsResponse.ok) { const extensions = await extensionsResponse.json(); setBrowserExtensions(Array.isArray(extensions) ? extensions : []); } } catch (error) { console.error('Failed to load browser extension access:', error); setBrowserExtensionApiKeys([]); setBrowserExtensions([]); } }; const handleUpdateAISettings = async () => { setAiLoading(true); setMessage(''); try { const token = localStorage.getItem('token'); const response = await fetch(`${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/auth/ai/settings`, { method: 'PUT', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }, body: JSON.stringify(aiSettings()) }); if (response.ok) { setMessage('AI settings updated successfully!'); await loadAISettings(); // Reload to get masked keys } else { const error = await response.json(); setMessage(error.error || 'Failed to update AI settings'); } } catch (error) { setMessage('Failed to update AI settings'); } finally { setAiLoading(false); } }; const handleUpdateProfile = async () => { setIsLoading(true); setMessage(''); try { await updateProfile({ fullName: profileData().fullName, theme: profileData().theme }); // Save browser search setting to localStorage localStorage.setItem('showBrowserSearch', profileData().showBrowserSearch.toString()); setMessage('Profile updated successfully!'); haptics.success(); } catch (error) { setMessage(error instanceof Error ? error.message : 'Failed to update profile'); haptics.error(); } finally { setIsLoading(false); } }; const handleChangePassword = async () => { if (passwordData().newPassword !== passwordData().confirmPassword) { setMessage('New passwords do not match'); haptics.warning(); return; } setIsLoading(true); setMessage(''); try { await changePassword({ currentPassword: passwordData().currentPassword, newPassword: passwordData().newPassword }); setMessage('Password changed successfully!'); setPasswordData({ currentPassword: '', newPassword: '', confirmPassword: '' }); haptics.success(); } catch (error) { setMessage(error instanceof Error ? error.message : 'Failed to change password'); haptics.error(); } finally { setIsLoading(false); } }; const handleUpdateSearchSettings = async () => { setIsLoading(true); setMessage(''); try { const token = localStorage.getItem('token'); const response = await fetch(`${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/auth/search/settings`, { method: 'PUT', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }, body: JSON.stringify(searchSettings()) }); if (response.ok) { setMessage('Search settings updated successfully!'); await loadSearchSettings(); } else { const error = await response.json(); setMessage(error.error || 'Failed to update search settings'); } } catch (error) { setMessage('Failed to update search settings'); } finally { setIsLoading(false); } }; return (

Settings

Manage your account, preferences, and integrations

{message() && (
{message()}
)} {/* Tab Navigation */}
{/* Tab Content */}
{/* Account Tab */}

Profile Settings

Email cannot be changed

Username cannot be changed

{ const target = e.currentTarget as HTMLInputElement; if (target) setProfileData(prev => ({ ...prev, fullName: target.value })); }} placeholder="Enter your full name" class="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground focus-visible:outline-none focus-visible:ring-1.5 focus-visible:ring-ring" />
applyColorChange('primary', e.target.value)} class="h-10 w-20 rounded border border-input bg-background cursor-pointer" /> applyColorChange('primary', e.target.value)} placeholder="#5ab9ff" class="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground focus-visible:outline-none focus-visible:ring-1.5 focus-visible:ring-ring font-mono" />
{(color) => (

Change Password

{ const target = e.currentTarget as HTMLInputElement; if (target) setPasswordData(prev => ({ ...prev, currentPassword: target.value })); }} placeholder="Enter current password" class="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground focus-visible:outline-none focus-visible:ring-1.5 focus-visible:ring-ring" />
{ const target = e.currentTarget as HTMLInputElement; if (target) setPasswordData(prev => ({ ...prev, newPassword: target.value })); }} placeholder="Enter new password" class="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground focus-visible:outline-none focus-visible:ring-1.5 focus-visible:ring-ring" />
{ const target = e.currentTarget as HTMLInputElement; if (target) setPasswordData(prev => ({ ...prev, confirmPassword: target.value })); }} placeholder="Confirm new password" class="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground focus-visible:outline-none focus-visible:ring-1.5 focus-visible:ring-ring" />
{/* Security Tab */}
{/* Two-Factor Authentication Section */}

Two-Factor Authentication

{/* AI & Integration Tab */}
{/* AI Settings Section */}

AI Settings

{/* AI Settings Summary */}
{(() => { const settings = aiSettings() || {}; const providers = availableAIProviders(); const enabledCount = Object.values(settings).filter((provider: any) => provider && provider.enabled).length; const totalAvailable = providers.length || Object.keys(settings).length; return `Active Providers: ${enabledCount} / ${totalAvailable}`; })()} {(() => { const settings = aiSettings() || {}; const enabledCount = Object.values(settings).filter((provider: any) => provider && provider.enabled).length; const totalAvailable = availableAIProviders().length || Object.keys(settings).length; if (totalAvailable === 0) { return 'No AI providers are available on the server. Check backend AI configuration.'; } if (enabledCount === 0) { return 'Providers are available but none are enabled. Enable at least one provider below.'; } return `AI is ready. ${enabledCount} provider${enabledCount > 1 ? 's' : ''} enabled.`; })()}
{/* Quick Setup Section */}

Quick Setup

Configure the most commonly used AI providers quickly:

{/* Detailed Configuration */}

Detailed Configuration

{/* Mistral Settings */}

Mistral AI

{ const settings = aiSettings(); setAISettings({ ...settings, mistral: { ...settings.mistral, enabled: e.currentTarget.checked } }); }} class="rounded border-input" />
{ const settings = aiSettings(); setAISettings({ ...settings, mistral: { ...settings.mistral, api_key: e.currentTarget.value } }); }} placeholder="Enter Mistral API key" required class="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 pr-10 text-sm text-foreground focus-visible:outline-none focus-visible:ring-1.5 focus-visible:ring-ring" />
{ const settings = aiSettings(); setAISettings({ ...settings, mistral: { ...settings.mistral, model: e.currentTarget.value } }); }} placeholder="mistral-small-latest" class="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground focus-visible:outline-none focus-visible:ring-1.5 focus-visible:ring-ring" />
{ const settings = aiSettings(); setAISettings({ ...settings, mistral: { ...settings.mistral, model_thinking: e.currentTarget.value } }); }} placeholder="mistral-large-latest" class="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground focus-visible:outline-none focus-visible:ring-1.5 focus-visible:ring-ring" />
{/* LongCat Settings */}

LongCat AI

{ const settings = aiSettings(); setAISettings({ ...settings, longcat: { ...settings.longcat, enabled: e.currentTarget.checked } }); }} class="rounded border-input" />
{ const settings = aiSettings(); setAISettings({ ...settings, longcat: { ...settings.longcat, api_key: e.currentTarget.value } }); }} placeholder="Enter LongCat API key" class="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 pr-10 text-sm text-foreground focus-visible:outline-none focus-visible:ring-1.5 focus-visible:ring-ring" />
{ const settings = aiSettings(); setAISettings({ ...settings, longcat: { ...settings.longcat, base_url: e.currentTarget.value } }); }} placeholder="https://api.longcat.chat" class="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground focus-visible:outline-none focus-visible:ring-1.5 focus-visible:ring-ring" />
{/* Grok Settings */}

Grok AI

{ const settings = aiSettings(); setAISettings({ ...settings, grok: { ...settings.grok, enabled: e.currentTarget.checked } }); }} class="rounded border-input" />
{ const settings = aiSettings(); setAISettings({ ...settings, grok: { ...settings.grok, api_key: e.currentTarget.value } }); }} placeholder="Enter Grok API key" class="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 pr-10 text-sm text-foreground focus-visible:outline-none focus-visible:ring-1.5 focus-visible:ring-ring" />
{ const settings = aiSettings(); setAISettings({ ...settings, grok: { ...settings.grok, base_url: e.currentTarget.value } }); }} placeholder="https://api.x.ai/v1" class="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground focus-visible:outline-none focus-visible:ring-1.5 focus-visible:ring-ring" />
{ const settings = aiSettings(); setAISettings({ ...settings, grok: { ...settings.grok, model: e.currentTarget.value } }); }} placeholder="grok-4-1-fast-non-reasoning-latest" class="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground focus-visible:outline-none focus-visible:ring-1.5 focus-visible:ring-ring" />
{ const settings = aiSettings(); setAISettings({ ...settings, grok: { ...settings.grok, model_thinking: e.currentTarget.value } }); }} placeholder="grok-4-1-fast-reasoning-latest" class="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground focus-visible:outline-none focus-visible:ring-1.5 focus-visible:ring-ring" />
{/* DeepSeek Settings */}

DeepSeek AI

{ const settings = aiSettings(); setAISettings({ ...settings, deepseek: { ...settings.deepseek, enabled: e.currentTarget.checked } }); }} class="rounded border-input" />
{ const settings = aiSettings(); setAISettings({ ...settings, deepseek: { ...settings.deepseek, api_key: e.currentTarget.value } }); }} placeholder="Enter DeepSeek API key" class="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 pr-10 text-sm text-foreground focus-visible:outline-none focus-visible:ring-1.5 focus-visible:ring-ring" />
{ const settings = aiSettings(); setAISettings({ ...settings, deepseek: { ...settings.deepseek, base_url: e.currentTarget.value } }); }} placeholder="https://api.deepseek.com" class="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground focus-visible:outline-none focus-visible:ring-1.5 focus-visible:ring-ring" />
{ const settings = aiSettings(); setAISettings({ ...settings, deepseek: { ...settings.deepseek, model: e.currentTarget.value } }); }} placeholder="deepseek-chat" class="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground focus-visible:outline-none focus-visible:ring-1.5 focus-visible:ring-ring" />
{ const settings = aiSettings(); setAISettings({ ...settings, deepseek: { ...settings.deepseek, model_thinking: e.currentTarget.value } }); }} placeholder="deepseek-reasoner" class="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground focus-visible:outline-none focus-visible:ring-1.5 focus-visible:ring-ring" />
{/* Ollama Settings */}

Ollama (Local AI)

{ const settings = aiSettings(); setAISettings({ ...settings, ollama: { ...settings.ollama, enabled: e.currentTarget.checked } }); }} class="rounded border-input" />
{ const settings = aiSettings(); setAISettings({ ...settings, ollama: { ...settings.ollama, base_url: e.currentTarget.value } }); }} placeholder="http://localhost:11434" class="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground focus-visible:outline-none focus-visible:ring-1.5 focus-visible:ring-ring" />
{ const settings = aiSettings(); setAISettings({ ...settings, ollama: { ...settings.ollama, model: e.currentTarget.value } }); }} placeholder="llama3.1" class="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground focus-visible:outline-none focus-visible:ring-1.5 focus-visible:ring-ring" />
{ const settings = aiSettings(); setAISettings({ ...settings, ollama: { ...settings.ollama, model_thinking: e.currentTarget.value } }); }} placeholder="llama3.1" class="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground focus-visible:outline-none focus-visible:ring-1.5 focus-visible:ring-ring" />
{/* OpenRouter Settings */}

OpenRouter

{ const settings = aiSettings(); setAISettings({ ...settings, openrouter: { ...settings.openrouter, enabled: e.currentTarget.checked } }); }} class="rounded border-input" />
{ const settings = aiSettings(); setAISettings({ ...settings, openrouter: { ...settings.openrouter, api_key: e.currentTarget.value } }); }} placeholder="Enter OpenRouter API key" class="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 pr-10 text-sm text-foreground focus-visible:outline-none focus-visible:ring-1.5 focus-visible:ring-ring" />
{ const settings = aiSettings(); setAISettings({ ...settings, openrouter: { ...settings.openrouter, base_url: e.currentTarget.value } }); }} placeholder="https://openrouter.ai/api" class="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground focus-visible:outline-none focus-visible:ring-1.5 focus-visible:ring-ring" />
{ const settings = aiSettings(); setAISettings({ ...settings, openrouter: { ...settings.openrouter, model: e.currentTarget.value } }); }} placeholder="openrouter/auto" class="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground focus-visible:outline-none focus-visible:ring-1.5 focus-visible:ring-ring" />
{ const settings = aiSettings(); setAISettings({ ...settings, openrouter: { ...settings.openrouter, model_thinking: e.currentTarget.value } }); }} placeholder="openrouter/auto" class="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground focus-visible:outline-none focus-visible:ring-1.5 focus-visible:ring-ring" />
{/* Communication Tab */}
{/* Email & OAuth Settings */}

Email Settings

{/* SMTP Configuration */}

SMTP Configuration

{ setEmailSettings(prev => ({ ...prev, smtp_enabled: e.currentTarget.checked })); }} class="rounded border-input" />
setEmailSettings(prev => ({ ...prev, smtp_host: e.currentTarget.value }))} placeholder="smtp.gmail.com" class="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground focus-visible:outline-none focus-visible:ring-1.5 focus-visible:ring-ring" />
setEmailSettings(prev => ({ ...prev, smtp_port: parseInt(e.currentTarget.value) }))} placeholder="587" class="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground focus-visible:outline-none focus-visible:ring-1.5 focus-visible:ring-ring" />
setEmailSettings(prev => ({ ...prev, smtp_username: e.currentTarget.value }))} placeholder="your-email@gmail.com" class="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground focus-visible:outline-none focus-visible:ring-1.5 focus-visible:ring-ring" />
setEmailSettings(prev => ({ ...prev, smtp_password: e.currentTarget.value }))} placeholder="App password" class="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground focus-visible:outline-none focus-visible:ring-1.5 focus-visible:ring-ring" />
setEmailSettings(prev => ({ ...prev, smtp_from_email: e.currentTarget.value }))} placeholder="noreply@trackeep.com" class="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground focus-visible:outline-none focus-visible:ring-1.5 focus-visible:ring-ring" />
setEmailSettings(prev => ({ ...prev, smtp_from_name: e.currentTarget.value }))} placeholder="Trackeep" class="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground focus-visible:outline-none focus-visible:ring-1.5 focus-visible:ring-ring" />
{/* Search API Tab */}

Browser Search API Configuration

setSearchSettings(prev => ({ ...prev, brave_api_key: e.currentTarget.value }))} placeholder="Enter Brave API key" required class="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground focus-visible:outline-none focus-visible:ring-1.5 focus-visible:ring-ring" />
setSearchSettings(prev => ({ ...prev, brave_search_base_url: e.currentTarget.value }))} placeholder="https://api.search.brave.com/res/v1/web/search" class="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground focus-visible:outline-none focus-visible:ring-1.5 focus-visible:ring-ring" />
setSearchSettings(prev => ({ ...prev, serper_api_key: e.currentTarget.value }))} placeholder="Enter Serper API key" required class="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground focus-visible:outline-none focus-visible:ring-1.5 focus-visible:ring-ring" />
setSearchSettings(prev => ({ ...prev, serper_base_url: e.currentTarget.value }))} placeholder="https://google.serper.dev/search" class="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground focus-visible:outline-none focus-visible:ring-1.5 focus-visible:ring-ring" />
setSearchSettings(prev => ({ ...prev, search_results_limit: parseInt(e.currentTarget.value) || 10 }))} min="1" max="50" class="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground focus-visible:outline-none focus-visible:ring-1.5 focus-visible:ring-ring" />
setSearchSettings(prev => ({ ...prev, search_cache_ttl: parseInt(e.currentTarget.value) || 300 }))} min="0" max="3600" class="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground focus-visible:outline-none focus-visible:ring-1.5 focus-visible:ring-ring" />
setSearchSettings(prev => ({ ...prev, search_rate_limit: parseInt(e.currentTarget.value) || 100 }))} min="1" max="1000" class="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground focus-visible:outline-none focus-visible:ring-1.5 focus-visible:ring-ring" />
{/* Tools Tab */}

Browser Extension

The extension authenticates with a Trackeep browser-extension API key. Download the extension, then connect it with a key from your account.

Active API Keys

{browserExtensionApiKeys().filter((key) => key.is_active).length}

{browserExtensionApiKeys()[0]?.name || 'No extension key created yet'}

Connected Extensions

{browserExtensions().filter((extension) => extension.is_active).length}

{browserExtensions()[0]?.name || 'No extension connected yet'}

Last Activity

{browserExtensions()[0]?.last_seen ? new Date(browserExtensions()[0].last_seen as string).toLocaleString() : browserExtensionApiKeys()[0]?.last_used ? new Date(browserExtensionApiKeys()[0].last_used as string).toLocaleString() : 'No extension activity yet'}

Paste an API key into the extension options after installation.

Connection flow

1. Download and unpack the extension.
2. Create or reuse a browser-extension API key in Trackeep.
3. Paste that key into the extension settings to connect bookmarks, files, notes, and tasks.

Installation Instructions:

Step 1: Download the ZIP file using the button above
Step 2: Extract the ZIP file to a folder on your computer
Brave/Chrome: Go to Settings → Extensions → Enable Developer mode → Load unpacked → Select the extracted folder
Firefox: Go to about:debugging#/runtime/this-firefox → Load Temporary Add-on → Select manifest.json in the extracted folder

Note: The extension must be loaded as unpacked for proper functionality. Do not attempt to install the ZIP file directly.

); };