diff --git a/app/components/settings/chat-history/ChatHistoryTab.tsx b/app/components/settings/chat-history/ChatHistoryTab.tsx
new file mode 100644
index 0000000..e96f0d8
--- /dev/null
+++ b/app/components/settings/chat-history/ChatHistoryTab.tsx
@@ -0,0 +1,105 @@
+import { useNavigate } from '@remix-run/react';
+import React, { useState } from 'react';
+import { toast } from 'react-toastify';
+import { db, deleteById, getAll } from '~/lib/persistence';
+import { classNames } from '~/utils/classNames';
+import styles from '~/components/settings/Settings.module.scss';
+
+export default function ChatHistoryTab() {
+ const navigate = useNavigate();
+ const [isDeleting, setIsDeleting] = useState(false);
+ 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);
+ }
+ };
+
+ return (
+ <>
+
+
Chat History
+
+
+
+
Danger Area
+
This action cannot be undone!
+
+
+
+ >
+ );
+}
diff --git a/app/components/settings/connections/ConnectionsTab.tsx b/app/components/settings/connections/ConnectionsTab.tsx
new file mode 100644
index 0000000..32d0fa0
--- /dev/null
+++ b/app/components/settings/connections/ConnectionsTab.tsx
@@ -0,0 +1,48 @@
+import React, { useState } from 'react';
+import { toast } from 'react-toastify';
+import Cookies from 'js-cookie';
+
+export default function ConnectionsTab() {
+ const [githubUsername, setGithubUsername] = useState(Cookies.get('githubUsername') || '');
+ const [githubToken, setGithubToken] = useState(Cookies.get('githubToken') || '');
+
+ const handleSaveConnection = () => {
+ Cookies.set('githubUsername', githubUsername);
+ Cookies.set('githubToken', githubToken);
+ toast.success('GitHub credentials saved successfully!');
+ };
+
+ return (
+
+
GitHub Connection
+
+
+
+
+
+ );
+}
diff --git a/app/components/settings/debug/DebugTab.tsx b/app/components/settings/debug/DebugTab.tsx
new file mode 100644
index 0000000..7a84ec1
--- /dev/null
+++ b/app/components/settings/debug/DebugTab.tsx
@@ -0,0 +1,69 @@
+import React, { useCallback, useEffect, useState } from 'react';
+import { useSettings } from '~/lib/hooks/useSettings';
+import commit from '~/commit.json';
+
+const versionHash = commit.commit; // Get the version hash from commit.json
+
+export default function DebugTab() {
+ const { providers } = useSettings();
+ const [activeProviders, setActiveProviders] = useState([]);
+ useEffect(() => {
+ setActiveProviders(
+ Object.entries(providers)
+ .filter(([_key, provider]) => provider.settings.enabled)
+ .map(([_key, provider]) => provider.name),
+ );
+ }, [providers]);
+
+ const handleCopyToClipboard = useCallback(() => {
+ const debugInfo = {
+ OS: navigator.platform,
+ Browser: navigator.userAgent,
+ ActiveFeatures: activeProviders,
+ 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!');
+ });
+ }, [providers]);
+
+ return (
+
+
Debug Tab
+
+
+
System Information
+
OS: {navigator.platform}
+
Browser: {navigator.userAgent}
+
+
Active Features
+
+ {activeProviders.map((name) => (
+ -
+ {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}
+
+ );
+}
diff --git a/app/components/settings/features/FeaturesTab.tsx b/app/components/settings/features/FeaturesTab.tsx
new file mode 100644
index 0000000..0b4fa75
--- /dev/null
+++ b/app/components/settings/features/FeaturesTab.tsx
@@ -0,0 +1,29 @@
+import React from 'react';
+import { Switch } from '~/components/ui/Switch';
+import { useSettings } from '~/lib/hooks/useSettings';
+
+export default function FeaturesTab() {
+ const { debug, enableDebugMode, isLocalModel, enableLocalModels } = useSettings();
+ return (
+
+
+
Optional Features
+
+ Debug Info
+
+
+
+
+
+
Experimental Features
+
+ Disclaimer: Experimental features may be unstable and are subject to change.
+
+
+ Enable Local Models
+
+
+
+
+ );
+}
diff --git a/app/components/settings/providers/ProvidersTab.tsx b/app/components/settings/providers/ProvidersTab.tsx
new file mode 100644
index 0000000..0b87959
--- /dev/null
+++ b/app/components/settings/providers/ProvidersTab.tsx
@@ -0,0 +1,77 @@
+import React, { useEffect, useState } from 'react';
+import { Switch } from '~/components/ui/Switch';
+import { useSettings } from '~/lib/hooks/useSettings';
+import { LOCAL_PROVIDERS, URL_CONFIGURABLE_PROVIDERS, type IProviderConfig } from '~/lib/stores/settings';
+
+export default function ProvidersTab() {
+ const { providers, updateProviderSettings, isLocalModel } = useSettings();
+ const [filteredProviders, setFilteredProviders] = useState([]);
+
+ // Load base URLs from cookies
+ const [searchTerm, setSearchTerm] = useState('');
+
+ useEffect(() => {
+ let newFilteredProviders: IProviderConfig[] = Object.entries(providers).map(([key, value]) => ({
+ ...value,
+ name: key,
+ }));
+
+ if (searchTerm && searchTerm.length > 0) {
+ newFilteredProviders = newFilteredProviders.filter((provider) =>
+ provider.name.toLowerCase().includes(searchTerm.toLowerCase()),
+ );
+ }
+
+ if (!isLocalModel) {
+ newFilteredProviders = newFilteredProviders.filter((provider) => !LOCAL_PROVIDERS.includes(provider.name));
+ }
+
+ newFilteredProviders.sort((a, b) => a.name.localeCompare(b.name));
+
+ setFilteredProviders(newFilteredProviders);
+ }, [providers, searchTerm, isLocalModel]);
+
+ return (
+
+
+ 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}
+ updateProviderSettings(provider.name, { ...provider.settings, enabled })}
+ />
+
+ {/* Base URL input for configurable providers */}
+ {URL_CONFIGURABLE_PROVIDERS.includes(provider.name) && provider.settings.enabled && (
+
+
+
+ updateProviderSettings(provider.name, { ...provider.settings, baseUrl: 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"
+ />
+
+ )}
+
+ ))}
+
+ );
+}
diff --git a/app/lib/hooks/useSettings.tsx b/app/lib/hooks/useSettings.tsx
new file mode 100644
index 0000000..9b63430
--- /dev/null
+++ b/app/lib/hooks/useSettings.tsx
@@ -0,0 +1,103 @@
+import { useStore } from '@nanostores/react';
+import {
+ isDebugMode,
+ isLocalModelsEnabled,
+ LOCAL_PROVIDERS,
+ providersStore,
+ type IProviderSetting,
+} from '~/lib/stores/settings';
+import { useCallback, useEffect, useState } from 'react';
+import Cookies from 'js-cookie';
+import type { ProviderInfo } from '~/utils/types';
+
+export function useSettings() {
+ const providers = useStore(providersStore);
+ const debug = useStore(isDebugMode);
+ const isLocalModel = useStore(isLocalModelsEnabled);
+ const [activeProviders, setActiveProviders] = useState([]);
+
+ // reading values from cookies on mount
+ useEffect(() => {
+ const savedProviders = Cookies.get('providers');
+
+ if (savedProviders) {
+ try {
+ const parsedProviders: Record = JSON.parse(savedProviders);
+ Object.keys(parsedProviders).forEach((provider) => {
+ const currentProvider = providers[provider];
+ providersStore.setKey(provider, {
+ ...currentProvider,
+ settings: {
+ ...parsedProviders[provider],
+ enabled: parsedProviders[provider].enabled || true,
+ },
+ });
+ });
+ } catch (error) {
+ console.error('Failed to parse providers from cookies:', error);
+ }
+ }
+
+ // load debug mode from cookies
+ const savedDebugMode = Cookies.get('isDebugEnabled');
+
+ if (savedDebugMode) {
+ isDebugMode.set(savedDebugMode === 'true');
+ }
+
+ // load local models from cookies
+ const savedLocalModels = Cookies.get('isLocalModelsEnabled');
+
+ if (savedLocalModels) {
+ isLocalModelsEnabled.set(savedLocalModels === 'true');
+ }
+ }, []);
+
+ // writing values to cookies on change
+ useEffect(() => {
+ const providers = providersStore.get();
+ const providerSetting: Record = {};
+ Object.keys(providers).forEach((provider) => {
+ providerSetting[provider] = providers[provider].settings;
+ });
+ Cookies.set('providers', JSON.stringify(providerSetting));
+ }, [providers]);
+
+ useEffect(() => {
+ let active = Object.entries(providers)
+ .filter(([_key, provider]) => provider.settings.enabled)
+ .map(([_k, p]) => p);
+
+ if (!isLocalModel) {
+ active = active.filter((p) => !LOCAL_PROVIDERS.includes(p.name));
+ }
+
+ setActiveProviders(active);
+ }, [providers, isLocalModel]);
+
+ // helper function to update settings
+ const updateProviderSettings = useCallback((provider: string, config: IProviderSetting) => {
+ const settings = providers[provider].settings;
+ providersStore.setKey(provider, { ...providers[provider], settings: { ...settings, ...config } });
+ }, []);
+
+ const enableDebugMode = useCallback((enabled: boolean) => {
+ isDebugMode.set(enabled);
+ Cookies.set('isDebugEnabled', String(enabled));
+ }, []);
+
+ const enableLocalModels = useCallback((enabled: boolean) => {
+ isLocalModelsEnabled.set(enabled);
+ Cookies.set('isLocalModelsEnabled', String(enabled));
+ }, []);
+
+ return {
+ providers,
+ activeProviders,
+ updateProviderSettings,
+ debug,
+ enableDebugMode,
+ isLocalModel,
+ enableLocalModels,
+ };
+}