Skip to content

Commit 31e7b48

Browse files
authored
Merge pull request #580 from oTToDev-CE/feat/add-tabbed-setting-modal
feat: Added a tabbed setting modal
2 parents 1774bf6 + a2acc77 commit 31e7b48

File tree

10 files changed

+670
-24
lines changed

10 files changed

+670
-24
lines changed

.github/workflows/commit.yaml

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
name: Update Commit Hash File
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
8+
permissions:
9+
contents: write
10+
11+
jobs:
12+
update-commit:
13+
runs-on: ubuntu-latest
14+
15+
steps:
16+
- name: Checkout the code
17+
uses: actions/checkout@v3
18+
19+
- name: Get the latest commit hash
20+
run: echo "COMMIT_HASH=$(git rev-parse HEAD)" >> $GITHUB_ENV
21+
22+
- name: Update commit file
23+
run: |
24+
echo "{ \"commit\": \"$COMMIT_HASH\" }" > app/commit.json
25+
26+
- name: Commit and push the update
27+
run: |
28+
git config --global user.name "github-actions[bot]"
29+
git config --global user.email "github-actions[bot]@users.noreply.github.com"
30+
git add app/commit.json
31+
git commit -m "chore: update commit hash to $COMMIT_HASH"
32+
git push

app/commit.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{ "commit": "228cf1f34fd64b6960460f84c9db47bd7ef03150" }

app/components/chat/BaseChat.tsx

Lines changed: 59 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -88,13 +88,65 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
8888
ref,
8989
) => {
9090
const TEXTAREA_MAX_HEIGHT = chatStarted ? 400 : 200;
91-
const [apiKeys, setApiKeys] = useState<Record<string, string>>({});
91+
const [apiKeys, setApiKeys] = useState<Record<string, string>>(() => {
92+
const savedKeys = Cookies.get('apiKeys');
93+
94+
if (savedKeys) {
95+
try {
96+
return JSON.parse(savedKeys);
97+
} catch (error) {
98+
console.error('Failed to parse API keys from cookies:', error);
99+
return {};
100+
}
101+
}
102+
103+
return {};
104+
});
92105
const [modelList, setModelList] = useState(MODEL_LIST);
93106
const [isModelSettingsCollapsed, setIsModelSettingsCollapsed] = useState(false);
94107
const [isListening, setIsListening] = useState(false);
95108
const [recognition, setRecognition] = useState<SpeechRecognition | null>(null);
96109
const [transcript, setTranscript] = useState('');
97110

111+
// Load enabled providers from cookies
112+
const [enabledProviders, setEnabledProviders] = useState(() => {
113+
const savedProviders = Cookies.get('providers');
114+
115+
if (savedProviders) {
116+
try {
117+
const parsedProviders = JSON.parse(savedProviders);
118+
return PROVIDER_LIST.filter((p) => parsedProviders[p.name]);
119+
} catch (error) {
120+
console.error('Failed to parse providers from cookies:', error);
121+
return PROVIDER_LIST;
122+
}
123+
}
124+
125+
return PROVIDER_LIST;
126+
});
127+
128+
// Update enabled providers when cookies change
129+
useEffect(() => {
130+
const updateProvidersFromCookies = () => {
131+
const savedProviders = Cookies.get('providers');
132+
133+
if (savedProviders) {
134+
try {
135+
const parsedProviders = JSON.parse(savedProviders);
136+
setEnabledProviders(PROVIDER_LIST.filter((p) => parsedProviders[p.name]));
137+
} catch (error) {
138+
console.error('Failed to parse providers from cookies:', error);
139+
}
140+
}
141+
};
142+
143+
updateProvidersFromCookies();
144+
145+
const interval = setInterval(updateProvidersFromCookies, 1000);
146+
147+
return () => clearInterval(interval);
148+
}, [PROVIDER_LIST]);
149+
98150
console.log(transcript);
99151
useEffect(() => {
100152
// Load API keys from cookies on component mount
@@ -184,23 +236,6 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
184236
}
185237
};
186238

187-
const updateApiKey = (provider: string, key: string) => {
188-
try {
189-
const updatedApiKeys = { ...apiKeys, [provider]: key };
190-
setApiKeys(updatedApiKeys);
191-
192-
// Save updated API keys to cookies with 30 day expiry and secure settings
193-
Cookies.set('apiKeys', JSON.stringify(updatedApiKeys), {
194-
expires: 30, // 30 days
195-
secure: true, // Only send over HTTPS
196-
sameSite: 'strict', // Protect against CSRF
197-
path: '/', // Accessible across the site
198-
});
199-
} catch (error) {
200-
console.error('Error saving API keys to cookies:', error);
201-
}
202-
};
203-
204239
const handleFileUpload = () => {
205240
const input = document.createElement('input');
206241
input.type = 'file';
@@ -360,11 +395,15 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
360395
providerList={PROVIDER_LIST}
361396
apiKeys={apiKeys}
362397
/>
363-
{provider && (
398+
{enabledProviders.length > 0 && provider && (
364399
<APIKeyManager
365400
provider={provider}
366401
apiKey={apiKeys[provider.name] || ''}
367-
setApiKey={(key) => updateApiKey(provider.name, key)}
402+
setApiKey={(key) => {
403+
const newApiKeys = { ...apiKeys, [provider.name]: key };
404+
setApiKeys(newApiKeys);
405+
Cookies.set('apiKeys', JSON.stringify(newApiKeys));
406+
}}
368407
/>
369408
)}
370409
</div>

app/components/chat/ModelSelector.tsx

Lines changed: 71 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import type { ProviderInfo } from '~/types/model';
22
import type { ModelInfo } from '~/utils/types';
3+
import { useEffect, useState } from 'react';
4+
import Cookies from 'js-cookie';
35

46
interface ModelSelectorProps {
57
model?: string;
@@ -19,12 +21,79 @@ export const ModelSelector = ({
1921
modelList,
2022
providerList,
2123
}: ModelSelectorProps) => {
24+
// Load enabled providers from cookies
25+
const [enabledProviders, setEnabledProviders] = useState(() => {
26+
const savedProviders = Cookies.get('providers');
27+
28+
if (savedProviders) {
29+
try {
30+
const parsedProviders = JSON.parse(savedProviders);
31+
return providerList.filter((p) => parsedProviders[p.name]);
32+
} catch (error) {
33+
console.error('Failed to parse providers from cookies:', error);
34+
return providerList;
35+
}
36+
}
37+
38+
return providerList;
39+
});
40+
41+
// Update enabled providers when cookies change
42+
useEffect(() => {
43+
// Function to update providers from cookies
44+
const updateProvidersFromCookies = () => {
45+
const savedProviders = Cookies.get('providers');
46+
47+
if (savedProviders) {
48+
try {
49+
const parsedProviders = JSON.parse(savedProviders);
50+
const newEnabledProviders = providerList.filter((p) => parsedProviders[p.name]);
51+
setEnabledProviders(newEnabledProviders);
52+
53+
// If current provider is disabled, switch to first enabled provider
54+
if (provider && !parsedProviders[provider.name] && newEnabledProviders.length > 0) {
55+
const firstEnabledProvider = newEnabledProviders[0];
56+
setProvider?.(firstEnabledProvider);
57+
58+
// Also update the model to the first available one for the new provider
59+
const firstModel = modelList.find((m) => m.provider === firstEnabledProvider.name);
60+
61+
if (firstModel) {
62+
setModel?.(firstModel.name);
63+
}
64+
}
65+
} catch (error) {
66+
console.error('Failed to parse providers from cookies:', error);
67+
}
68+
}
69+
};
70+
71+
// Initial update
72+
updateProvidersFromCookies();
73+
74+
// Set up an interval to check for cookie changes
75+
const interval = setInterval(updateProvidersFromCookies, 1000);
76+
77+
return () => clearInterval(interval);
78+
}, [providerList, provider, setProvider, modelList, setModel]);
79+
80+
if (enabledProviders.length === 0) {
81+
return (
82+
<div className="mb-2 p-4 rounded-lg border border-bolt-elements-borderColor bg-bolt-elements-prompt-background text-bolt-elements-textPrimary">
83+
<p className="text-center">
84+
No providers are currently enabled. Please enable at least one provider in the settings to start using the
85+
chat.
86+
</p>
87+
</div>
88+
);
89+
}
90+
2291
return (
2392
<div className="mb-2 flex gap-2 flex-col sm:flex-row">
2493
<select
2594
value={provider?.name ?? ''}
2695
onChange={(e) => {
27-
const newProvider = providerList.find((p: ProviderInfo) => p.name === e.target.value);
96+
const newProvider = enabledProviders.find((p: ProviderInfo) => p.name === e.target.value);
2897

2998
if (newProvider && setProvider) {
3099
setProvider(newProvider);
@@ -38,7 +107,7 @@ export const ModelSelector = ({
38107
}}
39108
className="flex-1 p-2 rounded-lg border border-bolt-elements-borderColor bg-bolt-elements-prompt-background text-bolt-elements-textPrimary focus:outline-none focus:ring-2 focus:ring-bolt-elements-focus transition-all"
40109
>
41-
{providerList.map((provider: ProviderInfo) => (
110+
{enabledProviders.map((provider: ProviderInfo) => (
42111
<option key={provider.name} value={provider.name}>
43112
{provider.name}
44113
</option>

app/components/sidebar/Menu.client.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import { useCallback, useEffect, useRef, useState } from 'react';
33
import { toast } from 'react-toastify';
44
import { Dialog, DialogButton, DialogDescription, DialogRoot, DialogTitle } from '~/components/ui/Dialog';
55
import { ThemeSwitch } from '~/components/ui/ThemeSwitch';
6+
import { Settings } from '~/components/ui/Settings';
7+
import { SettingsButton } from '~/components/ui/SettingsButton';
68
import { db, deleteById, getAll, chatId, type ChatHistoryItem, useChatHistory } from '~/lib/persistence';
79
import { cubicEasingFn } from '~/utils/easings';
810
import { logger } from '~/utils/logger';
@@ -39,6 +41,7 @@ export const Menu = () => {
3941
const [list, setList] = useState<ChatHistoryItem[]>([]);
4042
const [open, setOpen] = useState(false);
4143
const [dialogContent, setDialogContent] = useState<DialogContent>(null);
44+
const [isSettingsOpen, setIsSettingsOpen] = useState(false);
4245

4346
const { filteredItems: filteredList, handleSearchChange } = useSearchFilter({
4447
items: list,
@@ -200,10 +203,12 @@ export const Menu = () => {
200203
</Dialog>
201204
</DialogRoot>
202205
</div>
203-
<div className="flex items-center border-t border-bolt-elements-borderColor p-4">
204-
<ThemeSwitch className="ml-auto" />
206+
<div className="flex items-center justify-between border-t border-bolt-elements-borderColor p-4">
207+
<SettingsButton onClick={() => setIsSettingsOpen(true)} />
208+
<ThemeSwitch />
205209
</div>
206210
</div>
211+
<Settings open={isSettingsOpen} onClose={() => setIsSettingsOpen(false)} />
207212
</motion.div>
208213
);
209214
};

0 commit comments

Comments
 (0)