import * as RadixDialog from '@radix-ui/react-dialog'; import { motion } from 'framer-motion'; import { useState } from 'react'; import { classNames } from '~/utils/classNames'; import { DialogTitle, dialogVariants, dialogBackdropVariants } from '~/components/ui/Dialog'; import { IconButton } from '~/components/ui/IconButton'; import { providersList } from '~/lib/stores/settings'; import { db, getAll, deleteById } from '~/lib/persistence'; import { toast } from 'react-toastify'; import { useNavigate } from '@remix-run/react'; import commit from '~/commit.json'; import Cookies from 'js-cookie'; import styles from './Settings.module.scss'; import { Switch } from '~/components/ui/Switch'; interface SettingsProps { open: boolean; onClose: () => void; } type TabType = 'chat-history' | 'providers' | 'features' | 'debug'; // Providers that support base URL configuration const URL_CONFIGURABLE_PROVIDERS = ['Ollama', 'LMStudio', 'OpenAILike']; export const SettingsWindow = ({ open, onClose }: SettingsProps) => { const navigate = useNavigate(); const [activeTab, setActiveTab] = useState('chat-history'); const [isDebugEnabled, setIsDebugEnabled] = useState(false); const [searchTerm, setSearchTerm] = useState(''); const [isDeleting, setIsDeleting] = useState(false); const [isJustSayEnabled, setIsJustSayEnabled] = useState(false); // Load base URLs from cookies const [baseUrls, setBaseUrls] = useState(() => { const savedUrls = Cookies.get('providerBaseUrls'); if (savedUrls) { try { return JSON.parse(savedUrls); } catch (error) { console.error('Failed to parse base URLs from cookies:', error); return { Ollama: 'http://localhost:11434', LMStudio: 'http://localhost:1234', OpenAILike: '', }; } } return { Ollama: 'http://localhost:11434', LMStudio: 'http://localhost:1234', OpenAILike: '', }; }); const handleBaseUrlChange = (provider: string, url: string) => { setBaseUrls((prev: Record) => { const newUrls = { ...prev, [provider]: url }; Cookies.set('providerBaseUrls', JSON.stringify(newUrls)); return newUrls; }); }; const tabs: { id: TabType; label: string; icon: string }[] = [ { id: 'chat-history', label: 'Chat History', icon: 'i-ph:book' }, { id: 'providers', label: 'Providers', icon: 'i-ph:key' }, { id: 'features', label: 'Features', icon: 'i-ph:star' }, ...(isDebugEnabled ? [{ id: 'debug' as TabType, label: 'Debug Tab', icon: 'i-ph:bug' }] : []), ]; // Load providers from cookies on mount const [providers, setProviders] = useState(() => { const savedProviders = Cookies.get('providers'); if (savedProviders) { try { const parsedProviders = JSON.parse(savedProviders); // Merge saved enabled states with the base provider list return providersList.map((provider) => ({ ...provider, isEnabled: parsedProviders[provider.name] || false, })); } catch (error) { console.error('Failed to parse providers from cookies:', error); } } return providersList; }); const handleToggleProvider = (providerName: string, enabled: boolean) => { setProviders((prevProviders) => { const newProviders = prevProviders.map((provider) => provider.name === providerName ? { ...provider, isEnabled: enabled } : provider, ); // Save to cookies const enabledStates = newProviders.reduce( (acc, provider) => ({ ...acc, [provider.name]: provider.isEnabled, }), {}, ); Cookies.set('providers', JSON.stringify(enabledStates)); return newProviders; }); }; const filteredProviders = providers .filter((provider) => provider.name.toLowerCase().includes(searchTerm.toLowerCase())) .sort((a, b) => a.name.localeCompare(b.name)); const handleCopyToClipboard = () => { const debugInfo = { OS: navigator.platform, Browser: navigator.userAgent, ActiveFeatures: providers.filter((provider) => provider.isEnabled).map((provider) => provider.name), BaseURLs: { Ollama: process.env.REACT_APP_OLLAMA_URL, OpenAI: process.env.REACT_APP_OPENAI_URL, LMStudio: process.env.REACT_APP_LM_STUDIO_URL, }, Version: versionHash, }; navigator.clipboard.writeText(JSON.stringify(debugInfo, null, 2)).then(() => { alert('Debug information copied to clipboard!'); }); }; const downloadAsJson = (data: any, filename: string) => { const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' }); const url = URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; link.download = filename; document.body.appendChild(link); link.click(); document.body.removeChild(link); URL.revokeObjectURL(url); }; const handleDeleteAllChats = async () => { if (!db) { toast.error('Database is not available'); return; } try { setIsDeleting(true); const allChats = await getAll(db); // Delete all chats one by one await Promise.all(allChats.map((chat) => deleteById(db!, chat.id))); toast.success('All chats deleted successfully'); navigate('/', { replace: true }); } catch (error) { toast.error('Failed to delete chats'); console.error(error); } finally { setIsDeleting(false); } }; const handleExportAllChats = async () => { if (!db) { toast.error('Database is not available'); return; } try { const allChats = await getAll(db); const exportData = { chats: allChats, exportDate: new Date().toISOString(), }; downloadAsJson(exportData, `all-chats-${new Date().toISOString()}.json`); toast.success('Chats exported successfully'); } catch (error) { toast.error('Failed to export chats'); console.error(error); } }; const versionHash = commit.commit; // Get the version hash from commit.json return (
Settings {tabs.map((tab) => ( ))}
{activeTab === 'chat-history' && (

Chat History

Danger Area

This action cannot be undone!

)} {activeTab === 'providers' && (
setSearchTerm(e.target.value)} className="w-full bg-white dark:bg-bolt-elements-background-depth-4 relative px-2 py-1.5 rounded-md focus:outline-none placeholder-bolt-elements-textTertiary text-bolt-elements-textPrimary dark:text-bolt-elements-textPrimary border border-bolt-elements-borderColor" />
{filteredProviders.map((provider) => (
{provider.name} handleToggleProvider(provider.name, enabled)} />
{/* Base URL input for configurable providers */} {URL_CONFIGURABLE_PROVIDERS.includes(provider.name) && provider.isEnabled && (
handleBaseUrlChange(provider.name, e.target.value)} placeholder={`Enter ${provider.name} base URL`} className="w-full bg-white dark:bg-bolt-elements-background-depth-4 relative px-2 py-1.5 rounded-md focus:outline-none placeholder-bolt-elements-textTertiary text-bolt-elements-textPrimary dark:text-bolt-elements-textPrimary border border-bolt-elements-borderColor" />
)}
))}
)} {activeTab === 'features' && (

Feature Settings

Debug Info
)} {activeTab === 'features' && (

Experimental Area

Replace with local models
)} {activeTab === 'debug' && isDebugEnabled && (

Debug Tab

System Information

OS: {navigator.platform}

Browser: {navigator.userAgent}

Active Features

    {providers .filter((provider) => provider.isEnabled) .map((provider) => (
  • {provider.name}
  • ))}

Base URLs

  • Ollama: {process.env.REACT_APP_OLLAMA_URL}
  • OpenAI: {process.env.REACT_APP_OPENAI_URL}
  • LM Studio: {process.env.REACT_APP_LM_STUDIO_URL}

Version Information

Version Hash: {versionHash}

)}
); };