codacus commited on
Commit
b4d0597
·
1 Parent(s): 25f725f

remaining changes

Browse files
app/components/chat/BaseChat.tsx CHANGED
@@ -45,6 +45,7 @@ interface BaseChatProps {
45
  setModel?: (model: string) => void;
46
  provider?: ProviderInfo;
47
  setProvider?: (provider: ProviderInfo) => void;
 
48
  handleStop?: () => void;
49
  sendMessage?: (event: React.UIEvent, messageInput?: string) => void;
50
  handleInputChange?: (event: React.ChangeEvent<HTMLTextAreaElement>) => void;
@@ -70,6 +71,7 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
70
  setModel,
71
  provider,
72
  setProvider,
 
73
  input = '',
74
  enhancingPrompt,
75
  handleInputChange,
@@ -108,45 +110,7 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
108
  const [recognition, setRecognition] = useState<SpeechRecognition | null>(null);
109
  const [transcript, setTranscript] = useState('');
110
 
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
-
150
  console.log(transcript);
151
  useEffect(() => {
152
  // Load API keys from cookies on component mount
@@ -377,10 +341,10 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
377
  modelList={modelList}
378
  provider={provider}
379
  setProvider={setProvider}
380
- providerList={PROVIDER_LIST}
381
  apiKeys={apiKeys}
382
  />
383
- {enabledProviders.length > 0 && provider && (
384
  <APIKeyManager
385
  provider={provider}
386
  apiKey={apiKeys[provider.name] || ''}
@@ -476,7 +440,7 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
476
  <SendButton
477
  show={input.length > 0 || isStreaming || uploadedFiles.length > 0}
478
  isStreaming={isStreaming}
479
- disabled={enabledProviders.length === 0}
480
  onClick={(event) => {
481
  if (isStreaming) {
482
  handleStop?.();
@@ -536,7 +500,7 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
536
  !isModelSettingsCollapsed,
537
  })}
538
  onClick={() => setIsModelSettingsCollapsed(!isModelSettingsCollapsed)}
539
- disabled={enabledProviders.length === 0}
540
  >
541
  <div className={`i-ph:caret-${isModelSettingsCollapsed ? 'right' : 'down'} text-lg`} />
542
  {isModelSettingsCollapsed ? <span className="text-xs">{model}</span> : <span />}
 
45
  setModel?: (model: string) => void;
46
  provider?: ProviderInfo;
47
  setProvider?: (provider: ProviderInfo) => void;
48
+ providerList?: ProviderInfo[];
49
  handleStop?: () => void;
50
  sendMessage?: (event: React.UIEvent, messageInput?: string) => void;
51
  handleInputChange?: (event: React.ChangeEvent<HTMLTextAreaElement>) => void;
 
71
  setModel,
72
  provider,
73
  setProvider,
74
+ providerList,
75
  input = '',
76
  enhancingPrompt,
77
  handleInputChange,
 
110
  const [recognition, setRecognition] = useState<SpeechRecognition | null>(null);
111
  const [transcript, setTranscript] = useState('');
112
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
113
  // Update enabled providers when cookies change
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
114
  console.log(transcript);
115
  useEffect(() => {
116
  // Load API keys from cookies on component mount
 
341
  modelList={modelList}
342
  provider={provider}
343
  setProvider={setProvider}
344
+ providerList={providerList || PROVIDER_LIST}
345
  apiKeys={apiKeys}
346
  />
347
+ {(providerList || []).length > 0 && provider && (
348
  <APIKeyManager
349
  provider={provider}
350
  apiKey={apiKeys[provider.name] || ''}
 
440
  <SendButton
441
  show={input.length > 0 || isStreaming || uploadedFiles.length > 0}
442
  isStreaming={isStreaming}
443
+ disabled={!providerList || providerList.length === 0}
444
  onClick={(event) => {
445
  if (isStreaming) {
446
  handleStop?.();
 
500
  !isModelSettingsCollapsed,
501
  })}
502
  onClick={() => setIsModelSettingsCollapsed(!isModelSettingsCollapsed)}
503
+ disabled={!providerList || providerList.length === 0}
504
  >
505
  <div className={`i-ph:caret-${isModelSettingsCollapsed ? 'right' : 'down'} text-lg`} />
506
  {isModelSettingsCollapsed ? <span className="text-xs">{model}</span> : <span />}
app/components/chat/Chat.client.tsx CHANGED
@@ -19,6 +19,7 @@ import { BaseChat } from './BaseChat';
19
  import Cookies from 'js-cookie';
20
  import type { ProviderInfo } from '~/utils/types';
21
  import { debounce } from '~/utils/debounce';
 
22
 
23
  const toastAnimation = cssTransition({
24
  enter: 'animated fadeInRight',
@@ -91,6 +92,7 @@ export const ChatImpl = memo(
91
  const [chatStarted, setChatStarted] = useState(initialMessages.length > 0);
92
  const [uploadedFiles, setUploadedFiles] = useState<File[]>([]); // Move here
93
  const [imageDataList, setImageDataList] = useState<string[]>([]); // Move here
 
94
 
95
  const [model, setModel] = useState(() => {
96
  const savedModel = Cookies.get('selectedModel');
@@ -316,6 +318,7 @@ export const ChatImpl = memo(
316
  setModel={handleModelChange}
317
  provider={provider}
318
  setProvider={handleProviderChange}
 
319
  messageRef={messageRef}
320
  scrollRef={scrollRef}
321
  handleInputChange={(e) => {
 
19
  import Cookies from 'js-cookie';
20
  import type { ProviderInfo } from '~/utils/types';
21
  import { debounce } from '~/utils/debounce';
22
+ import { useSettings } from '~/lib/hooks/useSettings';
23
 
24
  const toastAnimation = cssTransition({
25
  enter: 'animated fadeInRight',
 
92
  const [chatStarted, setChatStarted] = useState(initialMessages.length > 0);
93
  const [uploadedFiles, setUploadedFiles] = useState<File[]>([]); // Move here
94
  const [imageDataList, setImageDataList] = useState<string[]>([]); // Move here
95
+ const { activeProviders } = useSettings();
96
 
97
  const [model, setModel] = useState(() => {
98
  const savedModel = Cookies.get('selectedModel');
 
318
  setModel={handleModelChange}
319
  provider={provider}
320
  setProvider={handleProviderChange}
321
+ providerList={activeProviders}
322
  messageRef={messageRef}
323
  scrollRef={scrollRef}
324
  handleInputChange={(e) => {
app/components/settings/SettingsWindow.tsx CHANGED
@@ -1,17 +1,16 @@
1
  import * as RadixDialog from '@radix-ui/react-dialog';
2
  import { motion } from 'framer-motion';
3
- import { useState } from 'react';
4
  import { classNames } from '~/utils/classNames';
5
  import { DialogTitle, dialogVariants, dialogBackdropVariants } from '~/components/ui/Dialog';
6
  import { IconButton } from '~/components/ui/IconButton';
7
- import { providersList } from '~/lib/stores/settings';
8
- import { db, getAll, deleteById } from '~/lib/persistence';
9
- import { toast } from 'react-toastify';
10
- import { useNavigate } from '@remix-run/react';
11
- import commit from '~/commit.json';
12
- import Cookies from 'js-cookie';
13
  import styles from './Settings.module.scss';
14
- import { Switch } from '~/components/ui/Switch';
 
 
 
 
 
15
 
16
  interface SettingsProps {
17
  open: boolean;
@@ -21,206 +20,27 @@ interface SettingsProps {
21
  type TabType = 'chat-history' | 'providers' | 'features' | 'debug' | 'connection';
22
 
23
  // Providers that support base URL configuration
24
- const URL_CONFIGURABLE_PROVIDERS = ['Ollama', 'LMStudio', 'OpenAILike'];
25
-
26
  export const SettingsWindow = ({ open, onClose }: SettingsProps) => {
27
- const navigate = useNavigate();
28
  const [activeTab, setActiveTab] = useState<TabType>('chat-history');
29
- const [isDebugEnabled, setIsDebugEnabled] = useState(() => {
30
- const savedDebugState = Cookies.get('isDebugEnabled');
31
- return savedDebugState === 'true';
32
- });
33
- const [searchTerm, setSearchTerm] = useState('');
34
- const [isDeleting, setIsDeleting] = useState(false);
35
- const [githubUsername, setGithubUsername] = useState(Cookies.get('githubUsername') || '');
36
- const [githubToken, setGithubToken] = useState(Cookies.get('githubToken') || '');
37
- const [isLocalModelsEnabled, setIsLocalModelsEnabled] = useState(() => {
38
- const savedLocalModelsState = Cookies.get('isLocalModelsEnabled');
39
- return savedLocalModelsState === 'true';
40
- });
41
-
42
- // Load base URLs from cookies
43
- const [baseUrls, setBaseUrls] = useState(() => {
44
- const savedUrls = Cookies.get('providerBaseUrls');
45
-
46
- if (savedUrls) {
47
- try {
48
- return JSON.parse(savedUrls);
49
- } catch (error) {
50
- console.error('Failed to parse base URLs from cookies:', error);
51
- return {
52
- Ollama: 'http://localhost:11434',
53
- LMStudio: 'http://localhost:1234',
54
- OpenAILike: '',
55
- };
56
- }
57
- }
58
-
59
- return {
60
- Ollama: 'http://localhost:11434',
61
- LMStudio: 'http://localhost:1234',
62
- OpenAILike: '',
63
- };
64
- });
65
-
66
- const handleBaseUrlChange = (provider: string, url: string) => {
67
- setBaseUrls((prev: Record<string, string>) => {
68
- const newUrls = { ...prev, [provider]: url };
69
- Cookies.set('providerBaseUrls', JSON.stringify(newUrls));
70
 
71
- return newUrls;
72
- });
73
- };
74
-
75
- const tabs: { id: TabType; label: string; icon: string }[] = [
76
- { id: 'chat-history', label: 'Chat History', icon: 'i-ph:book' },
77
- { id: 'providers', label: 'Providers', icon: 'i-ph:key' },
78
- { id: 'features', label: 'Features', icon: 'i-ph:star' },
79
- { id: 'connection', label: 'Connection', icon: 'i-ph:link' },
80
- ...(isDebugEnabled ? [{ id: 'debug' as TabType, label: 'Debug Tab', icon: 'i-ph:bug' }] : []),
 
 
 
 
 
81
  ];
82
 
83
- // Load providers from cookies on mount
84
- const [providers, setProviders] = useState(() => {
85
- const savedProviders = Cookies.get('providers');
86
-
87
- if (savedProviders) {
88
- try {
89
- const parsedProviders = JSON.parse(savedProviders);
90
-
91
- // Merge saved enabled states with the base provider list
92
- return providersList.map((provider) => ({
93
- ...provider,
94
- isEnabled: parsedProviders[provider.name] || false,
95
- }));
96
- } catch (error) {
97
- console.error('Failed to parse providers from cookies:', error);
98
- }
99
- }
100
-
101
- return providersList;
102
- });
103
-
104
- const handleToggleProvider = (providerName: string, enabled: boolean) => {
105
- setProviders((prevProviders) => {
106
- const newProviders = prevProviders.map((provider) =>
107
- provider.name === providerName ? { ...provider, isEnabled: enabled } : provider,
108
- );
109
-
110
- // Save to cookies
111
- const enabledStates = newProviders.reduce(
112
- (acc, provider) => ({
113
- ...acc,
114
- [provider.name]: provider.isEnabled,
115
- }),
116
- {},
117
- );
118
- Cookies.set('providers', JSON.stringify(enabledStates));
119
-
120
- return newProviders;
121
- });
122
- };
123
-
124
- const filteredProviders = providers
125
- .filter((provider) => {
126
- const isLocalModelProvider = ['OpenAILike', 'LMStudio', 'Ollama'].includes(provider.name);
127
- return isLocalModelsEnabled || !isLocalModelProvider;
128
- })
129
- .filter((provider) => provider.name.toLowerCase().includes(searchTerm.toLowerCase()))
130
- .sort((a, b) => a.name.localeCompare(b.name));
131
-
132
- const handleCopyToClipboard = () => {
133
- const debugInfo = {
134
- OS: navigator.platform,
135
- Browser: navigator.userAgent,
136
- ActiveFeatures: providers.filter((provider) => provider.isEnabled).map((provider) => provider.name),
137
- BaseURLs: {
138
- Ollama: process.env.REACT_APP_OLLAMA_URL,
139
- OpenAI: process.env.REACT_APP_OPENAI_URL,
140
- LMStudio: process.env.REACT_APP_LM_STUDIO_URL,
141
- },
142
- Version: versionHash,
143
- };
144
- navigator.clipboard.writeText(JSON.stringify(debugInfo, null, 2)).then(() => {
145
- alert('Debug information copied to clipboard!');
146
- });
147
- };
148
-
149
- const downloadAsJson = (data: any, filename: string) => {
150
- const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
151
- const url = URL.createObjectURL(blob);
152
- const link = document.createElement('a');
153
- link.href = url;
154
- link.download = filename;
155
- document.body.appendChild(link);
156
- link.click();
157
- document.body.removeChild(link);
158
- URL.revokeObjectURL(url);
159
- };
160
-
161
- const handleDeleteAllChats = async () => {
162
- if (!db) {
163
- toast.error('Database is not available');
164
- return;
165
- }
166
-
167
- try {
168
- setIsDeleting(true);
169
-
170
- const allChats = await getAll(db);
171
-
172
- // Delete all chats one by one
173
- await Promise.all(allChats.map((chat) => deleteById(db!, chat.id)));
174
-
175
- toast.success('All chats deleted successfully');
176
- navigate('/', { replace: true });
177
- } catch (error) {
178
- toast.error('Failed to delete chats');
179
- console.error(error);
180
- } finally {
181
- setIsDeleting(false);
182
- }
183
- };
184
-
185
- const handleExportAllChats = async () => {
186
- if (!db) {
187
- toast.error('Database is not available');
188
- return;
189
- }
190
-
191
- try {
192
- const allChats = await getAll(db);
193
- const exportData = {
194
- chats: allChats,
195
- exportDate: new Date().toISOString(),
196
- };
197
-
198
- downloadAsJson(exportData, `all-chats-${new Date().toISOString()}.json`);
199
- toast.success('Chats exported successfully');
200
- } catch (error) {
201
- toast.error('Failed to export chats');
202
- console.error(error);
203
- }
204
- };
205
-
206
- const versionHash = commit.commit; // Get the version hash from commit.json
207
-
208
- const handleSaveConnection = () => {
209
- Cookies.set('githubUsername', githubUsername);
210
- Cookies.set('githubToken', githubToken);
211
- toast.success('GitHub credentials saved successfully!');
212
- };
213
-
214
- const handleToggleDebug = (enabled: boolean) => {
215
- setIsDebugEnabled(enabled);
216
- Cookies.set('isDebugEnabled', String(enabled));
217
- };
218
-
219
- const handleToggleLocalModels = (enabled: boolean) => {
220
- setIsLocalModelsEnabled(enabled);
221
- Cookies.set('isLocalModelsEnabled', String(enabled));
222
- };
223
-
224
  return (
225
  <RadixDialog.Root open={open}>
226
  <RadixDialog.Portal>
@@ -284,190 +104,7 @@ export const SettingsWindow = ({ open, onClose }: SettingsProps) => {
284
  </div>
285
 
286
  <div className="flex-1 flex flex-col p-8 pt-10 bg-bolt-elements-background-depth-2">
287
- <div className="flex-1 overflow-y-auto">
288
- {activeTab === 'chat-history' && (
289
- <div className="p-4">
290
- <h3 className="text-lg font-medium text-bolt-elements-textPrimary mb-4">Chat History</h3>
291
- <button
292
- onClick={handleExportAllChats}
293
- className={classNames(
294
- 'bg-bolt-elements-button-primary-background',
295
- 'rounded-lg px-4 py-2 mb-4 transition-colors duration-200',
296
- 'hover:bg-bolt-elements-button-primary-backgroundHover',
297
- 'text-bolt-elements-button-primary-text',
298
- )}
299
- >
300
- Export All Chats
301
- </button>
302
-
303
- <div
304
- className={classNames(
305
- 'text-bolt-elements-textPrimary rounded-lg py-4 mb-4',
306
- styles['settings-danger-area'],
307
- )}
308
- >
309
- <h4 className="font-semibold">Danger Area</h4>
310
- <p className="mb-2">This action cannot be undone!</p>
311
- <button
312
- onClick={handleDeleteAllChats}
313
- disabled={isDeleting}
314
- className={classNames(
315
- 'bg-bolt-elements-button-danger-background',
316
- 'rounded-lg px-4 py-2 transition-colors duration-200',
317
- isDeleting
318
- ? 'opacity-50 cursor-not-allowed'
319
- : 'hover:bg-bolt-elements-button-danger-backgroundHover',
320
- 'text-bolt-elements-button-danger-text',
321
- )}
322
- >
323
- {isDeleting ? 'Deleting...' : 'Delete All Chats'}
324
- </button>
325
- </div>
326
- </div>
327
- )}
328
- {activeTab === 'providers' && (
329
- <div className="p-4">
330
- <div className="flex mb-4">
331
- <input
332
- type="text"
333
- placeholder="Search providers..."
334
- value={searchTerm}
335
- onChange={(e) => setSearchTerm(e.target.value)}
336
- 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"
337
- />
338
- </div>
339
- {filteredProviders.map((provider) => (
340
- <div
341
- key={provider.name}
342
- className="flex flex-col mb-2 provider-item hover:bg-bolt-elements-bg-depth-3 p-4 rounded-lg border border-bolt-elements-borderColor "
343
- >
344
- <div className="flex items-center justify-between mb-2">
345
- <span className="text-bolt-elements-textPrimary">{provider.name}</span>
346
- <Switch
347
- className="ml-auto"
348
- checked={provider.isEnabled}
349
- onCheckedChange={(enabled) => handleToggleProvider(provider.name, enabled)}
350
- />
351
- </div>
352
- {/* Base URL input for configurable providers */}
353
- {URL_CONFIGURABLE_PROVIDERS.includes(provider.name) && provider.isEnabled && (
354
- <div className="mt-2">
355
- <label className="block text-sm text-bolt-elements-textSecondary mb-1">Base URL:</label>
356
- <input
357
- type="text"
358
- value={baseUrls[provider.name]}
359
- onChange={(e) => handleBaseUrlChange(provider.name, e.target.value)}
360
- placeholder={`Enter ${provider.name} base URL`}
361
- 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"
362
- />
363
- </div>
364
- )}
365
- </div>
366
- ))}
367
- </div>
368
- )}
369
- {activeTab === 'features' && (
370
- <div className="p-4 bg-bolt-elements-bg-depth-2 border border-bolt-elements-borderColor rounded-lg mb-4">
371
- <div className="mb-6">
372
- <h3 className="text-lg font-medium text-bolt-elements-textPrimary mb-4">Optional Features</h3>
373
- <div className="flex items-center justify-between mb-2">
374
- <span className="text-bolt-elements-textPrimary">Debug Info</span>
375
- <Switch
376
- className="ml-auto"
377
- checked={isDebugEnabled}
378
- onCheckedChange={handleToggleDebug}
379
- />
380
- </div>
381
- </div>
382
-
383
- <div className="mb-6 border-t border-bolt-elements-borderColor pt-4">
384
- <h3 className="text-lg font-medium text-bolt-elements-textPrimary mb-4">Experimental Features</h3>
385
- <p className="text-sm text-bolt-elements-textSecondary mb-4">
386
- Disclaimer: Experimental features may be unstable and are subject to change.
387
- </p>
388
- <div className="flex items-center justify-between mb-2">
389
- <span className="text-bolt-elements-textPrimary">Enable Local Models</span>
390
- <Switch
391
- className="ml-auto"
392
- checked={isLocalModelsEnabled}
393
- onCheckedChange={handleToggleLocalModels}
394
- />
395
- </div>
396
- </div>
397
- </div>
398
- )}
399
- {activeTab === 'debug' && isDebugEnabled && (
400
- <div className="p-4">
401
- <h3 className="text-lg font-medium text-bolt-elements-textPrimary mb-4">Debug Tab</h3>
402
- <button
403
- onClick={handleCopyToClipboard}
404
- className="bg-blue-500 text-white rounded-lg px-4 py-2 hover:bg-blue-600 mb-4 transition-colors duration-200"
405
- >
406
- Copy to Clipboard
407
- </button>
408
-
409
- <h4 className="text-md font-medium text-bolt-elements-textPrimary">System Information</h4>
410
- <p className="text-bolt-elements-textSecondary">OS: {navigator.platform}</p>
411
- <p className="text-bolt-elements-textSecondary">Browser: {navigator.userAgent}</p>
412
-
413
- <h4 className="text-md font-medium text-bolt-elements-textPrimary mt-4">Active Features</h4>
414
- <ul>
415
- {providers
416
- .filter((provider) => provider.isEnabled)
417
- .map((provider) => (
418
- <li key={provider.name} className="text-bolt-elements-textSecondary">
419
- {provider.name}
420
- </li>
421
- ))}
422
- </ul>
423
-
424
- <h4 className="text-md font-medium text-bolt-elements-textPrimary mt-4">Base URLs</h4>
425
- <ul>
426
- <li className="text-bolt-elements-textSecondary">Ollama: {process.env.REACT_APP_OLLAMA_URL}</li>
427
- <li className="text-bolt-elements-textSecondary">OpenAI: {process.env.REACT_APP_OPENAI_URL}</li>
428
- <li className="text-bolt-elements-textSecondary">
429
- LM Studio: {process.env.REACT_APP_LM_STUDIO_URL}
430
- </li>
431
- </ul>
432
-
433
- <h4 className="text-md font-medium text-bolt-elements-textPrimary mt-4">Version Information</h4>
434
- <p className="text-bolt-elements-textSecondary">Version Hash: {versionHash}</p>
435
- </div>
436
- )}
437
- {activeTab === 'connection' && (
438
- <div className="p-4 mb-4 border border-bolt-elements-borderColor rounded-lg bg-bolt-elements-background-depth-3">
439
- <h3 className="text-lg font-medium text-bolt-elements-textPrimary mb-4">GitHub Connection</h3>
440
- <div className="flex mb-4">
441
- <div className="flex-1 mr-2">
442
- <label className="block text-sm text-bolt-elements-textSecondary mb-1">GitHub Username:</label>
443
- <input
444
- type="text"
445
- value={githubUsername}
446
- onChange={(e) => setGithubUsername(e.target.value)}
447
- 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"
448
- />
449
- </div>
450
- <div className="flex-1">
451
- <label className="block text-sm text-bolt-elements-textSecondary mb-1">Personal Access Token:</label>
452
- <input
453
- type="password"
454
- value={githubToken}
455
- onChange={(e) => setGithubToken(e.target.value)}
456
- 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"
457
- />
458
- </div>
459
- </div>
460
- <div className="flex mb-4">
461
- <button
462
- onClick={handleSaveConnection}
463
- className="bg-bolt-elements-button-primary-background rounded-lg px-4 py-2 mr-2 transition-colors duration-200 hover:bg-bolt-elements-button-primary-backgroundHover text-bolt-elements-button-primary-text"
464
- >
465
- Save Connection
466
- </button>
467
- </div>
468
- </div>
469
- )}
470
- </div>
471
  </div>
472
  </div>
473
  <RadixDialog.Close asChild onClick={onClose}>
 
1
  import * as RadixDialog from '@radix-ui/react-dialog';
2
  import { motion } from 'framer-motion';
3
+ import { useState, type ReactElement } from 'react';
4
  import { classNames } from '~/utils/classNames';
5
  import { DialogTitle, dialogVariants, dialogBackdropVariants } from '~/components/ui/Dialog';
6
  import { IconButton } from '~/components/ui/IconButton';
 
 
 
 
 
 
7
  import styles from './Settings.module.scss';
8
+ import ChatHistoryTab from './chat-history/ChatHistoryTab';
9
+ import ProvidersTab from './providers/ProvidersTab';
10
+ import { useSettings } from '~/lib/hooks/useSettings';
11
+ import FeaturesTab from './features/FeaturesTab';
12
+ import DebugTab from './debug/DebugTab';
13
+ import ConnectionsTab from './connections/ConnectionsTab';
14
 
15
  interface SettingsProps {
16
  open: boolean;
 
20
  type TabType = 'chat-history' | 'providers' | 'features' | 'debug' | 'connection';
21
 
22
  // Providers that support base URL configuration
 
 
23
  export const SettingsWindow = ({ open, onClose }: SettingsProps) => {
24
+ const { debug } = useSettings();
25
  const [activeTab, setActiveTab] = useState<TabType>('chat-history');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
 
27
+ const tabs: { id: TabType; label: string; icon: string; component?: ReactElement }[] = [
28
+ { id: 'chat-history', label: 'Chat History', icon: 'i-ph:book', component: <ChatHistoryTab /> },
29
+ { id: 'providers', label: 'Providers', icon: 'i-ph:key', component: <ProvidersTab /> },
30
+ { id: 'features', label: 'Features', icon: 'i-ph:star', component: <FeaturesTab /> },
31
+ { id: 'connection', label: 'Connection', icon: 'i-ph:link', component: <ConnectionsTab /> },
32
+ ...(debug
33
+ ? [
34
+ {
35
+ id: 'debug' as TabType,
36
+ label: 'Debug Tab',
37
+ icon: 'i-ph:bug',
38
+ component: <DebugTab />,
39
+ },
40
+ ]
41
+ : []),
42
  ];
43
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
  return (
45
  <RadixDialog.Root open={open}>
46
  <RadixDialog.Portal>
 
104
  </div>
105
 
106
  <div className="flex-1 flex flex-col p-8 pt-10 bg-bolt-elements-background-depth-2">
107
+ <div className="flex-1 overflow-y-auto">{tabs.find((tab) => tab.id === activeTab)?.component}</div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
108
  </div>
109
  </div>
110
  <RadixDialog.Close asChild onClick={onClose}>
app/lib/stores/settings.ts CHANGED
@@ -1,5 +1,7 @@
1
- import { map } from 'nanostores';
2
  import { workbenchStore } from './workbench';
 
 
3
 
4
  export interface Shortcut {
5
  key: string;
@@ -15,32 +17,18 @@ export interface Shortcuts {
15
  toggleTerminal: Shortcut;
16
  }
17
 
18
- export interface Provider {
19
- name: string;
20
- isEnabled: boolean;
21
  }
 
 
 
22
 
23
- export interface Settings {
24
- shortcuts: Shortcuts;
25
- providers: Provider[];
26
- }
27
 
28
- export const providersList: Provider[] = [
29
- { name: 'Groq', isEnabled: false },
30
- { name: 'HuggingFace', isEnabled: false },
31
- { name: 'OpenAI', isEnabled: false },
32
- { name: 'Anthropic', isEnabled: false },
33
- { name: 'OpenRouter', isEnabled: false },
34
- { name: 'Google', isEnabled: false },
35
- { name: 'Ollama', isEnabled: false },
36
- { name: 'OpenAILike', isEnabled: false },
37
- { name: 'Together', isEnabled: false },
38
- { name: 'Deepseek', isEnabled: false },
39
- { name: 'Mistral', isEnabled: false },
40
- { name: 'Cohere', isEnabled: false },
41
- { name: 'LMStudio', isEnabled: false },
42
- { name: 'xAI', isEnabled: false },
43
- ];
44
 
45
  export const shortcutsStore = map<Shortcuts>({
46
  toggleTerminal: {
@@ -50,14 +38,17 @@ export const shortcutsStore = map<Shortcuts>({
50
  },
51
  });
52
 
53
- export const settingsStore = map<Settings>({
54
- shortcuts: shortcutsStore.get(),
55
- providers: providersList,
 
 
 
 
 
56
  });
 
57
 
58
- shortcutsStore.subscribe((shortcuts) => {
59
- settingsStore.set({
60
- ...settingsStore.get(),
61
- shortcuts,
62
- });
63
- });
 
1
+ import { atom, map } from 'nanostores';
2
  import { workbenchStore } from './workbench';
3
+ import type { ProviderInfo } from '~/utils/types';
4
+ import { PROVIDER_LIST } from '~/utils/constants';
5
 
6
  export interface Shortcut {
7
  key: string;
 
17
  toggleTerminal: Shortcut;
18
  }
19
 
20
+ export interface IProviderSetting {
21
+ enabled?: boolean;
22
+ baseUrl?: string;
23
  }
24
+ export type IProviderConfig = ProviderInfo & {
25
+ settings: IProviderSetting;
26
+ };
27
 
28
+ export const URL_CONFIGURABLE_PROVIDERS = ['Ollama', 'LMStudio', 'OpenAILike'];
29
+ export const LOCAL_PROVIDERS = ['OpenAILike', 'LMStudio', 'Ollama'];
 
 
30
 
31
+ export type ProviderSetting = Record<string, IProviderConfig>;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32
 
33
  export const shortcutsStore = map<Shortcuts>({
34
  toggleTerminal: {
 
38
  },
39
  });
40
 
41
+ const initialProviderSettings: ProviderSetting = {};
42
+ PROVIDER_LIST.forEach((provider) => {
43
+ initialProviderSettings[provider.name] = {
44
+ ...provider,
45
+ settings: {
46
+ enabled: false,
47
+ },
48
+ };
49
  });
50
+ export const providersStore = map<ProviderSetting>(initialProviderSettings);
51
 
52
+ export const isDebugMode = atom(false);
53
+
54
+ export const isLocalModelsEnabled = atom(true);