diff --git a/.github/actions/setup-and-build/commit.yaml b/.github/workflows/commit.yaml similarity index 87% rename from .github/actions/setup-and-build/commit.yaml rename to .github/workflows/commit.yaml index e920a88..2f194cd 100644 --- a/.github/actions/setup-and-build/commit.yaml +++ b/.github/workflows/commit.yaml @@ -21,12 +21,12 @@ jobs: - name: Update commit file run: | - echo "{ \"commit\": \"$COMMIT_HASH\" }" > commit.json + echo "{ \"commit\": \"$COMMIT_HASH\" }" > app/commit.json - name: Commit and push the update run: | git config --global user.name "github-actions[bot]" git config --global user.email "github-actions[bot]@users.noreply.github.com" - git add commit.json + git add app/commit.json git commit -m "chore: update commit hash to $COMMIT_HASH" git push \ No newline at end of file diff --git a/app/commit.json b/app/commit.json new file mode 100644 index 0000000..8b96fc7 --- /dev/null +++ b/app/commit.json @@ -0,0 +1 @@ +{ "commit": "228cf1f34fd64b6960460f84c9db47bd7ef03150" } diff --git a/app/components/chat/BaseChat.tsx b/app/components/chat/BaseChat.tsx index 3b4be60..67b7688 100644 --- a/app/components/chat/BaseChat.tsx +++ b/app/components/chat/BaseChat.tsx @@ -89,6 +89,7 @@ export const BaseChat = React.forwardRef( const TEXTAREA_MAX_HEIGHT = chatStarted ? 400 : 200; const [apiKeys, setApiKeys] = useState>(() => { const savedKeys = Cookies.get('apiKeys'); + if (savedKeys) { try { return JSON.parse(savedKeys); @@ -97,6 +98,7 @@ export const BaseChat = React.forwardRef( return {}; } } + return {}; }); const [modelList, setModelList] = useState(MODEL_LIST); @@ -108,15 +110,17 @@ export const BaseChat = React.forwardRef( // Load enabled providers from cookies const [enabledProviders, setEnabledProviders] = useState(() => { const savedProviders = Cookies.get('providers'); + if (savedProviders) { try { const parsedProviders = JSON.parse(savedProviders); - return PROVIDER_LIST.filter(p => parsedProviders[p.name]); + return PROVIDER_LIST.filter((p) => parsedProviders[p.name]); } catch (error) { console.error('Failed to parse providers from cookies:', error); return PROVIDER_LIST; } } + return PROVIDER_LIST; }); @@ -124,10 +128,11 @@ export const BaseChat = React.forwardRef( useEffect(() => { const updateProvidersFromCookies = () => { const savedProviders = Cookies.get('providers'); + if (savedProviders) { try { const parsedProviders = JSON.parse(savedProviders); - setEnabledProviders(PROVIDER_LIST.filter(p => parsedProviders[p.name])); + setEnabledProviders(PROVIDER_LIST.filter((p) => parsedProviders[p.name])); } catch (error) { console.error('Failed to parse providers from cookies:', error); } @@ -135,7 +140,9 @@ export const BaseChat = React.forwardRef( }; updateProvidersFromCookies(); + const interval = setInterval(updateProvidersFromCookies, 1000); + return () => clearInterval(interval); }, [PROVIDER_LIST]); @@ -228,23 +235,6 @@ export const BaseChat = React.forwardRef( } }; - const updateApiKey = (provider: string, key: string) => { - try { - const updatedApiKeys = { ...apiKeys, [provider]: key }; - setApiKeys(updatedApiKeys); - - // Save updated API keys to cookies with 30 day expiry and secure settings - Cookies.set('apiKeys', JSON.stringify(updatedApiKeys), { - expires: 30, // 30 days - secure: true, // Only send over HTTPS - sameSite: 'strict', // Protect against CSRF - path: '/', // Accessible across the site - }); - } catch (error) { - console.error('Error saving API keys to cookies:', error); - } - }; - const handleFileUpload = () => { const input = document.createElement('input'); input.type = 'file'; diff --git a/app/components/chat/ModelSelector.tsx b/app/components/chat/ModelSelector.tsx index ae8a8c5..435f4ba 100644 --- a/app/components/chat/ModelSelector.tsx +++ b/app/components/chat/ModelSelector.tsx @@ -24,15 +24,17 @@ export const ModelSelector = ({ // Load enabled providers from cookies const [enabledProviders, setEnabledProviders] = useState(() => { const savedProviders = Cookies.get('providers'); + if (savedProviders) { try { const parsedProviders = JSON.parse(savedProviders); - return providerList.filter(p => parsedProviders[p.name]); + return providerList.filter((p) => parsedProviders[p.name]); } catch (error) { console.error('Failed to parse providers from cookies:', error); return providerList; } } + return providerList; }); @@ -41,19 +43,21 @@ export const ModelSelector = ({ // Function to update providers from cookies const updateProvidersFromCookies = () => { const savedProviders = Cookies.get('providers'); + if (savedProviders) { try { const parsedProviders = JSON.parse(savedProviders); - const newEnabledProviders = providerList.filter(p => parsedProviders[p.name]); + const newEnabledProviders = providerList.filter((p) => parsedProviders[p.name]); setEnabledProviders(newEnabledProviders); // If current provider is disabled, switch to first enabled provider if (provider && !parsedProviders[provider.name] && newEnabledProviders.length > 0) { const firstEnabledProvider = newEnabledProviders[0]; setProvider?.(firstEnabledProvider); - + // Also update the model to the first available one for the new provider - const firstModel = modelList.find(m => m.provider === firstEnabledProvider.name); + const firstModel = modelList.find((m) => m.provider === firstEnabledProvider.name); + if (firstModel) { setModel?.(firstModel.name); } @@ -77,7 +81,8 @@ export const ModelSelector = ({ return (

- No providers are currently enabled. Please enable at least one provider in the settings to start using the chat. + No providers are currently enabled. Please enable at least one provider in the settings to start using the + chat.

); diff --git a/app/components/ui/Settings.tsx b/app/components/ui/Settings.tsx index fb4c4c5..e58e8b5 100644 --- a/app/components/ui/Settings.tsx +++ b/app/components/ui/Settings.tsx @@ -1,14 +1,14 @@ import * as RadixDialog from '@radix-ui/react-dialog'; import { motion } from 'framer-motion'; -import { useState, useEffect }from 'react'; +import { useState } from 'react'; import { classNames } from '~/utils/classNames'; -import { Dialog, DialogTitle, dialogVariants, dialogBackdropVariants } from './Dialog'; +import { DialogTitle, dialogVariants, dialogBackdropVariants } from './Dialog'; import { IconButton } from './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 commit from '~/commit.json'; import Cookies from 'js-cookie'; interface SettingsProps { @@ -31,6 +31,7 @@ export const Settings = ({ open, onClose }: SettingsProps) => { // Load base URLs from cookies const [baseUrls, setBaseUrls] = useState(() => { const savedUrls = Cookies.get('providerBaseUrls'); + if (savedUrls) { try { return JSON.parse(savedUrls); @@ -43,6 +44,7 @@ export const Settings = ({ open, onClose }: SettingsProps) => { }; } } + return { Ollama: 'http://localhost:11434', LMStudio: 'http://localhost:1234', @@ -51,9 +53,10 @@ export const Settings = ({ open, onClose }: SettingsProps) => { }); const handleBaseUrlChange = (provider: string, url: string) => { - setBaseUrls(prev => { + setBaseUrls((prev: Record) => { const newUrls = { ...prev, [provider]: url }; Cookies.set('providerBaseUrls', JSON.stringify(newUrls)); + return newUrls; }); }; @@ -62,46 +65,52 @@ export const Settings = ({ open, onClose }: SettingsProps) => { { 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', label: 'Debug Tab', icon: 'i-ph:bug' }] : []), + ...(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 => ({ + return providersList.map((provider) => ({ ...provider, - isEnabled: parsedProviders[provider.name] || false + isEnabled: parsedProviders[provider.name] || false, })); } catch (error) { console.error('Failed to parse providers from cookies:', error); } } + return providersList; }); const handleToggleProvider = (providerName: string) => { setProviders((prevProviders) => { const newProviders = prevProviders.map((provider) => - provider.name === providerName ? { ...provider, isEnabled: !provider.isEnabled } : provider + provider.name === providerName ? { ...provider, isEnabled: !provider.isEnabled } : provider, ); - + // Save to cookies - const enabledStates = newProviders.reduce((acc, provider) => ({ - ...acc, - [provider.name]: provider.isEnabled - }), {}); + 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())) + .filter((provider) => provider.name.toLowerCase().includes(searchTerm.toLowerCase())) .sort((a, b) => a.name.localeCompare(b.name)); const handleCopyToClipboard = () => { @@ -141,11 +150,12 @@ export const Settings = ({ open, onClose }: SettingsProps) => { try { setIsDeleting(true); + const allChats = await getAll(db); - + // Delete all chats one by one - await Promise.all(allChats.map(chat => deleteById(db!, chat.id))); - + await Promise.all(allChats.map((chat) => deleteById(db!, chat.id))); + toast.success('All chats deleted successfully'); navigate('/', { replace: true }); } catch (error) { @@ -168,7 +178,7 @@ export const Settings = ({ open, onClose }: SettingsProps) => { chats: allChats, exportDate: new Date().toISOString(), }; - + downloadAsJson(exportData, `all-chats-${new Date().toISOString()}.json`); toast.success('Chats exported successfully'); } catch (error) { @@ -207,9 +217,7 @@ export const Settings = ({ open, onClose }: SettingsProps) => { onClick={() => setActiveTab(tab.id)} className={classNames( 'w-full flex items-center gap-2 px-4 py-3 rounded-lg text-left text-sm transition-all mb-2', - activeTab === tab.id - ? 'bg-blue-600 text-white' - : 'bg-gray-600 text-gray-200 hover:bg-blue-500' + activeTab === tab.id ? 'bg-blue-600 text-white' : 'bg-gray-600 text-gray-200 hover:bg-blue-500', )} >
@@ -239,7 +247,7 @@ export const Settings = ({ open, onClose }: SettingsProps) => {
Settings
- {activeTab === 'chat-history' && ( + {activeTab === 'chat-history' && (

Chat History

- +

Danger Area

This action cannot be undone!

@@ -256,8 +264,8 @@ export const Settings = ({ open, onClose }: SettingsProps) => { onClick={handleDeleteAllChats} disabled={isDeleting} className={classNames( - "bg-red-700 text-white rounded-lg px-4 py-2 transition-colors duration-200", - isDeleting ? "opacity-50 cursor-not-allowed" : "hover:bg-red-800" + 'bg-red-700 text-white rounded-lg px-4 py-2 transition-colors duration-200', + isDeleting ? 'opacity-50 cursor-not-allowed' : 'hover:bg-red-800', )} > {isDeleting ? 'Deleting...' : 'Delete All Chats'} @@ -297,7 +305,7 @@ export const Settings = ({ open, onClose }: SettingsProps) => { >
- + {/* Base URL input for configurable providers */} {URL_CONFIGURABLE_PROVIDERS.includes(provider.name) && provider.isEnabled && (
@@ -334,9 +342,7 @@ export const Settings = ({ open, onClose }: SettingsProps) => { >
-
- {/* Your feature content here */} -
+
{/* Your feature content here */}
)} {activeTab === 'debug' && isDebugEnabled && ( @@ -348,7 +354,7 @@ export const Settings = ({ open, onClose }: SettingsProps) => { > Copy to Clipboard - +

System Information

OS: {navigator.platform}

Browser: {navigator.userAgent}

@@ -358,7 +364,9 @@ export const Settings = ({ open, onClose }: SettingsProps) => { {providers .filter((provider) => provider.isEnabled) .map((provider) => ( -
  • {provider.name}
  • +
  • + {provider.name} +
  • ))} @@ -384,4 +392,4 @@ export const Settings = ({ open, onClose }: SettingsProps) => { ); -}; \ No newline at end of file +}; diff --git a/app/components/ui/SettingsButton.tsx b/app/components/ui/SettingsButton.tsx index 24ba00b..906fe52 100644 --- a/app/components/ui/SettingsButton.tsx +++ b/app/components/ui/SettingsButton.tsx @@ -15,4 +15,4 @@ export const SettingsButton = memo(({ onClick }: SettingsButtonProps) => { className="text-[#666] hover:text-bolt-elements-textPrimary hover:bg-bolt-elements-item-backgroundActive/10 transition-colors" /> ); -}); \ No newline at end of file +}); diff --git a/app/components/ui/SettingsSlider.tsx b/app/components/ui/SettingsSlider.tsx index f2fba88..1a5d410 100644 --- a/app/components/ui/SettingsSlider.tsx +++ b/app/components/ui/SettingsSlider.tsx @@ -26,24 +26,24 @@ export const SettingsSlider = memo(({ selected, options, setSelected }: Sett
    ); -}); \ No newline at end of file +}); diff --git a/commit.json b/commit.json deleted file mode 100644 index f98c1f4..0000000 --- a/commit.json +++ /dev/null @@ -1 +0,0 @@ -{ "commit": "228cf1f34fd64b6960460f84c9db47bd7ef03150" } \ No newline at end of file