Stijnus commited on
Commit
0e60d9c
·
1 Parent(s): 6e89710

UI bug fixes

Browse files
app/components/@settings/core/ControlPanel.tsx CHANGED
@@ -263,6 +263,27 @@ export const ControlPanel = ({ open, onClose }: ControlPanelProps) => {
263
  },
264
  };
265
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
266
  // Handlers
267
  const handleBack = () => {
268
  if (showTabManagement) {
@@ -405,8 +426,8 @@ export const ControlPanel = ({ open, onClose }: ControlPanelProps) => {
405
 
406
  <RadixDialog.Content
407
  aria-describedby={undefined}
408
- onEscapeKeyDown={onClose}
409
- onPointerDownOutside={onClose}
410
  className="relative z-[101]"
411
  >
412
  <motion.div
@@ -461,7 +482,7 @@ export const ControlPanel = ({ open, onClose }: ControlPanelProps) => {
461
 
462
  {/* Close Button */}
463
  <button
464
- onClick={onClose}
465
  className="flex items-center justify-center w-8 h-8 rounded-full bg-transparent hover:bg-purple-500/10 dark:hover:bg-purple-500/20 group transition-all duration-200"
466
  >
467
  <div className="i-ph:x w-4 h-4 text-gray-500 dark:text-gray-400 group-hover:text-purple-500 transition-colors" />
 
263
  },
264
  };
265
 
266
+ // Reset to default view when modal opens/closes
267
+ useEffect(() => {
268
+ if (!open) {
269
+ // Reset when closing
270
+ setActiveTab(null);
271
+ setLoadingTab(null);
272
+ setShowTabManagement(false);
273
+ } else {
274
+ // When opening, set to null to show the main view
275
+ setActiveTab(null);
276
+ }
277
+ }, [open]);
278
+
279
+ // Handle closing
280
+ const handleClose = () => {
281
+ setActiveTab(null);
282
+ setLoadingTab(null);
283
+ setShowTabManagement(false);
284
+ onClose();
285
+ };
286
+
287
  // Handlers
288
  const handleBack = () => {
289
  if (showTabManagement) {
 
426
 
427
  <RadixDialog.Content
428
  aria-describedby={undefined}
429
+ onEscapeKeyDown={handleClose}
430
+ onPointerDownOutside={handleClose}
431
  className="relative z-[101]"
432
  >
433
  <motion.div
 
482
 
483
  {/* Close Button */}
484
  <button
485
+ onClick={handleClose}
486
  className="flex items-center justify-center w-8 h-8 rounded-full bg-transparent hover:bg-purple-500/10 dark:hover:bg-purple-500/20 group transition-all duration-200"
487
  >
488
  <div className="i-ph:x w-4 h-4 text-gray-500 dark:text-gray-400 group-hover:text-purple-500 transition-colors" />
app/components/@settings/shared/components/TabManagement.tsx CHANGED
@@ -1,4 +1,4 @@
1
- import { useState } from 'react';
2
  import { motion } from 'framer-motion';
3
  import { useStore } from '@nanostores/react';
4
  import { Switch } from '~/components/ui/Switch';
@@ -8,6 +8,7 @@ import { TAB_LABELS } from '~/components/@settings/core/constants';
8
  import type { TabType } from '~/components/@settings/core/types';
9
  import { toast } from 'react-toastify';
10
  import { TbLayoutGrid } from 'react-icons/tb';
 
11
 
12
  // Define tab icons mapping
13
  const TAB_ICONS: Record<TabType, string> = {
@@ -55,6 +56,7 @@ const BetaLabel = () => (
55
  export const TabManagement = () => {
56
  const [searchQuery, setSearchQuery] = useState('');
57
  const tabConfiguration = useStore(tabConfigurationStore);
 
58
 
59
  const handleTabVisibilityChange = (tabId: TabType, checked: boolean) => {
60
  // Get current tab configuration
@@ -126,6 +128,13 @@ export const TabManagement = () => {
126
  // Filter tabs based on search query
127
  const filteredTabs = allTabs.filter((tab) => TAB_LABELS[tab.id].toLowerCase().includes(searchQuery.toLowerCase()));
128
 
 
 
 
 
 
 
 
129
  return (
130
  <div className="space-y-6">
131
  <motion.div
 
1
+ import { useState, useEffect } from 'react';
2
  import { motion } from 'framer-motion';
3
  import { useStore } from '@nanostores/react';
4
  import { Switch } from '~/components/ui/Switch';
 
8
  import type { TabType } from '~/components/@settings/core/types';
9
  import { toast } from 'react-toastify';
10
  import { TbLayoutGrid } from 'react-icons/tb';
11
+ import { useSettingsStore } from '~/lib/stores/settings';
12
 
13
  // Define tab icons mapping
14
  const TAB_ICONS: Record<TabType, string> = {
 
56
  export const TabManagement = () => {
57
  const [searchQuery, setSearchQuery] = useState('');
58
  const tabConfiguration = useStore(tabConfigurationStore);
59
+ const { setSelectedTab } = useSettingsStore();
60
 
61
  const handleTabVisibilityChange = (tabId: TabType, checked: boolean) => {
62
  // Get current tab configuration
 
128
  // Filter tabs based on search query
129
  const filteredTabs = allTabs.filter((tab) => TAB_LABELS[tab.id].toLowerCase().includes(searchQuery.toLowerCase()));
130
 
131
+ useEffect(() => {
132
+ // Reset to first tab when component unmounts
133
+ return () => {
134
+ setSelectedTab('user'); // Reset to user tab when unmounting
135
+ };
136
+ }, [setSelectedTab]);
137
+
138
  return (
139
  <div className="space-y-6">
140
  <motion.div
app/components/@settings/tabs/debug/DebugTab.tsx CHANGED
@@ -1103,15 +1103,18 @@ export default function DebugTab() {
1103
  // Add Ollama health check function
1104
  const checkOllamaStatus = useCallback(async () => {
1105
  try {
 
 
 
1106
  // First check if service is running
1107
- const versionResponse = await fetch('http://127.0.0.1:11434/api/version');
1108
 
1109
  if (!versionResponse.ok) {
1110
  throw new Error('Service not running');
1111
  }
1112
 
1113
  // Then fetch installed models
1114
- const modelsResponse = await fetch('http://127.0.0.1:11434/api/tags');
1115
 
1116
  const modelsData = (await modelsResponse.json()) as {
1117
  models: Array<{ name: string; size: string; quantization: string }>;
@@ -1130,7 +1133,7 @@ export default function DebugTab() {
1130
  models: undefined,
1131
  });
1132
  }
1133
- }, []);
1134
 
1135
  // Monitor isLocalModel changes and check status periodically
1136
  useEffect(() => {
 
1103
  // Add Ollama health check function
1104
  const checkOllamaStatus = useCallback(async () => {
1105
  try {
1106
+ const ollamaProvider = providers?.Ollama;
1107
+ const baseUrl = ollamaProvider?.settings?.baseUrl || 'http://127.0.0.1:11434';
1108
+
1109
  // First check if service is running
1110
+ const versionResponse = await fetch(`${baseUrl}/api/version`);
1111
 
1112
  if (!versionResponse.ok) {
1113
  throw new Error('Service not running');
1114
  }
1115
 
1116
  // Then fetch installed models
1117
+ const modelsResponse = await fetch(`${baseUrl}/api/tags`);
1118
 
1119
  const modelsData = (await modelsResponse.json()) as {
1120
  models: Array<{ name: string; size: string; quantization: string }>;
 
1133
  models: undefined,
1134
  });
1135
  }
1136
+ }, [providers]);
1137
 
1138
  // Monitor isLocalModel changes and check status periodically
1139
  useEffect(() => {
app/components/@settings/tabs/profile/ProfileTab.tsx CHANGED
@@ -1,13 +1,23 @@
1
- import { useState } from 'react';
2
  import { useStore } from '@nanostores/react';
3
  import { classNames } from '~/utils/classNames';
4
  import { profileStore, updateProfile } from '~/lib/stores/profile';
5
  import { toast } from 'react-toastify';
 
6
 
7
  export default function ProfileTab() {
8
  const profile = useStore(profileStore);
9
  const [isUploading, setIsUploading] = useState(false);
10
 
 
 
 
 
 
 
 
 
 
11
  const handleAvatarUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
12
  const file = e.target.files?.[0];
13
 
@@ -42,14 +52,11 @@ export default function ProfileTab() {
42
  };
43
 
44
  const handleProfileUpdate = (field: 'username' | 'bio', value: string) => {
 
45
  updateProfile({ [field]: value });
46
 
47
- // Only show toast for completed typing (after 1 second of no typing)
48
- const debounceToast = setTimeout(() => {
49
- toast.success(`${field.charAt(0).toUpperCase() + field.slice(1)} updated`);
50
- }, 1000);
51
-
52
- return () => clearTimeout(debounceToast);
53
  };
54
 
55
  return (
 
1
+ import { useState, useCallback } from 'react';
2
  import { useStore } from '@nanostores/react';
3
  import { classNames } from '~/utils/classNames';
4
  import { profileStore, updateProfile } from '~/lib/stores/profile';
5
  import { toast } from 'react-toastify';
6
+ import { debounce } from '~/utils/debounce';
7
 
8
  export default function ProfileTab() {
9
  const profile = useStore(profileStore);
10
  const [isUploading, setIsUploading] = useState(false);
11
 
12
+ // Create debounced update functions
13
+ const debouncedUpdate = useCallback(
14
+ debounce((field: 'username' | 'bio', value: string) => {
15
+ updateProfile({ [field]: value });
16
+ toast.success(`${field.charAt(0).toUpperCase() + field.slice(1)} updated`);
17
+ }, 1000),
18
+ [],
19
+ );
20
+
21
  const handleAvatarUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
22
  const file = e.target.files?.[0];
23
 
 
52
  };
53
 
54
  const handleProfileUpdate = (field: 'username' | 'bio', value: string) => {
55
+ // Update the store immediately for UI responsiveness
56
  updateProfile({ [field]: value });
57
 
58
+ // Debounce the toast notification
59
+ debouncedUpdate(field, value);
 
 
 
 
60
  };
61
 
62
  return (
app/components/@settings/tabs/providers/local/OllamaModelInstaller.tsx CHANGED
@@ -3,6 +3,7 @@ import { motion } from 'framer-motion';
3
  import { classNames } from '~/utils/classNames';
4
  import { Progress } from '~/components/ui/Progress';
5
  import { useToast } from '~/components/ui/use-toast';
 
6
 
7
  interface OllamaModelInstallerProps {
8
  onModelInstalled: () => void;
@@ -141,11 +142,15 @@ export default function OllamaModelInstaller({ onModelInstalled }: OllamaModelIn
141
  const [selectedTags, setSelectedTags] = useState<string[]>([]);
142
  const [models, setModels] = useState<ModelInfo[]>(POPULAR_MODELS);
143
  const { toast } = useToast();
 
 
 
 
144
 
145
  // Function to check installed models and their versions
146
  const checkInstalledModels = async () => {
147
  try {
148
- const response = await fetch('http://127.0.0.1:11434/api/tags', {
149
  method: 'GET',
150
  });
151
 
@@ -181,7 +186,7 @@ export default function OllamaModelInstaller({ onModelInstalled }: OllamaModelIn
181
  // Check installed models on mount and after installation
182
  useEffect(() => {
183
  checkInstalledModels();
184
- }, []);
185
 
186
  const handleCheckUpdates = async () => {
187
  setIsChecking(true);
@@ -224,7 +229,7 @@ export default function OllamaModelInstaller({ onModelInstalled }: OllamaModelIn
224
  setModelString('');
225
  setSearchQuery('');
226
 
227
- const response = await fetch('http://127.0.0.1:11434/api/pull', {
228
  method: 'POST',
229
  headers: {
230
  'Content-Type': 'application/json',
@@ -302,7 +307,7 @@ export default function OllamaModelInstaller({ onModelInstalled }: OllamaModelIn
302
  try {
303
  setModels((prev) => prev.map((m) => (m.name === modelToUpdate ? { ...m, status: 'updating' } : m)));
304
 
305
- const response = await fetch('http://127.0.0.1:11434/api/pull', {
306
  method: 'POST',
307
  headers: {
308
  'Content-Type': 'application/json',
 
3
  import { classNames } from '~/utils/classNames';
4
  import { Progress } from '~/components/ui/Progress';
5
  import { useToast } from '~/components/ui/use-toast';
6
+ import { useSettings } from '~/lib/hooks/useSettings';
7
 
8
  interface OllamaModelInstallerProps {
9
  onModelInstalled: () => void;
 
142
  const [selectedTags, setSelectedTags] = useState<string[]>([]);
143
  const [models, setModels] = useState<ModelInfo[]>(POPULAR_MODELS);
144
  const { toast } = useToast();
145
+ const { providers } = useSettings();
146
+
147
+ // Get base URL from provider settings
148
+ const baseUrl = providers?.Ollama?.settings?.baseUrl || 'http://127.0.0.1:11434';
149
 
150
  // Function to check installed models and their versions
151
  const checkInstalledModels = async () => {
152
  try {
153
+ const response = await fetch(`${baseUrl}/api/tags`, {
154
  method: 'GET',
155
  });
156
 
 
186
  // Check installed models on mount and after installation
187
  useEffect(() => {
188
  checkInstalledModels();
189
+ }, [baseUrl]);
190
 
191
  const handleCheckUpdates = async () => {
192
  setIsChecking(true);
 
229
  setModelString('');
230
  setSearchQuery('');
231
 
232
+ const response = await fetch(`${baseUrl}/api/pull`, {
233
  method: 'POST',
234
  headers: {
235
  'Content-Type': 'application/json',
 
307
  try {
308
  setModels((prev) => prev.map((m) => (m.name === modelToUpdate ? { ...m, status: 'updating' } : m)));
309
 
310
+ const response = await fetch(`${baseUrl}/api/pull`, {
311
  method: 'POST',
312
  headers: {
313
  'Content-Type': 'application/json',
app/components/@settings/tabs/settings/SettingsTab.tsx CHANGED
@@ -4,19 +4,8 @@ import { toast } from 'react-toastify';
4
  import { classNames } from '~/utils/classNames';
5
  import { Switch } from '~/components/ui/Switch';
6
  import type { UserProfile } from '~/components/@settings/core/types';
7
- import { useStore } from '@nanostores/react';
8
- import { shortcutsStore } from '~/lib/stores/settings';
9
  import { isMac } from '~/utils/os';
10
 
11
- // Helper to format shortcut key display
12
- const formatShortcutKey = (key: string) => {
13
- if (key === '`') {
14
- return '`';
15
- }
16
-
17
- return key.toUpperCase();
18
- };
19
-
20
  // Helper to get modifier key symbols/text
21
  const getModifierSymbol = (modifier: string): string => {
22
  switch (modifier) {
@@ -24,8 +13,6 @@ const getModifierSymbol = (modifier: string): string => {
24
  return isMac ? '⌘' : 'Win';
25
  case 'alt':
26
  return isMac ? '⌥' : 'Alt';
27
- case 'ctrl':
28
- return isMac ? '⌃' : 'Ctrl';
29
  case 'shift':
30
  return '⇧';
31
  default:
@@ -188,7 +175,7 @@ export default function SettingsTab() {
188
  </div>
189
  </motion.div>
190
 
191
- {/* Keyboard Shortcuts */}
192
  <motion.div
193
  className="bg-white dark:bg-[#0A0A0A] rounded-lg shadow-sm dark:shadow-none p-4"
194
  initial={{ opacity: 0, y: 20 }}
@@ -201,51 +188,26 @@ export default function SettingsTab() {
201
  </div>
202
 
203
  <div className="space-y-2">
204
- {Object.entries(useStore(shortcutsStore)).map(([name, shortcut]) => (
205
- <div
206
- key={name}
207
- className="flex items-center justify-between p-2 rounded-lg bg-[#FAFAFA] dark:bg-[#1A1A1A] hover:bg-purple-50 dark:hover:bg-purple-500/10 transition-colors"
208
- >
209
- <div className="flex flex-col">
210
- <span className="text-sm text-bolt-elements-textPrimary capitalize">
211
- {name.replace(/([A-Z])/g, ' $1').toLowerCase()}
212
- </span>
213
- {shortcut.description && (
214
- <span className="text-xs text-bolt-elements-textSecondary">{shortcut.description}</span>
215
- )}
216
- </div>
217
- <div className="flex items-center gap-1">
218
- {shortcut.ctrlOrMetaKey && (
219
- <kbd className="px-2 py-1 text-xs font-semibold text-bolt-elements-textSecondary bg-white dark:bg-[#0A0A0A] border border-[#E5E5E5] dark:border-[#1A1A1A] rounded shadow-sm">
220
- {getModifierSymbol(isMac ? 'meta' : 'ctrl')}
221
- </kbd>
222
- )}
223
- {shortcut.ctrlKey && (
224
- <kbd className="px-2 py-1 text-xs font-semibold text-bolt-elements-textSecondary bg-white dark:bg-[#0A0A0A] border border-[#E5E5E5] dark:border-[#1A1A1A] rounded shadow-sm">
225
- {getModifierSymbol('ctrl')}
226
- </kbd>
227
- )}
228
- {shortcut.metaKey && (
229
- <kbd className="px-2 py-1 text-xs font-semibold text-bolt-elements-textSecondary bg-white dark:bg-[#0A0A0A] border border-[#E5E5E5] dark:border-[#1A1A1A] rounded shadow-sm">
230
- {getModifierSymbol('meta')}
231
- </kbd>
232
- )}
233
- {shortcut.altKey && (
234
- <kbd className="px-2 py-1 text-xs font-semibold text-bolt-elements-textSecondary bg-white dark:bg-[#0A0A0A] border border-[#E5E5E5] dark:border-[#1A1A1A] rounded shadow-sm">
235
- {getModifierSymbol('alt')}
236
- </kbd>
237
- )}
238
- {shortcut.shiftKey && (
239
- <kbd className="px-2 py-1 text-xs font-semibold text-bolt-elements-textSecondary bg-white dark:bg-[#0A0A0A] border border-[#E5E5E5] dark:border-[#1A1A1A] rounded shadow-sm">
240
- {getModifierSymbol('shift')}
241
- </kbd>
242
- )}
243
- <kbd className="px-2 py-1 text-xs font-semibold text-bolt-elements-textSecondary bg-white dark:bg-[#0A0A0A] border border-[#E5E5E5] dark:border-[#1A1A1A] rounded shadow-sm">
244
- {formatShortcutKey(shortcut.key)}
245
- </kbd>
246
- </div>
247
  </div>
248
- ))}
249
  </div>
250
  </motion.div>
251
  </div>
 
4
  import { classNames } from '~/utils/classNames';
5
  import { Switch } from '~/components/ui/Switch';
6
  import type { UserProfile } from '~/components/@settings/core/types';
 
 
7
  import { isMac } from '~/utils/os';
8
 
 
 
 
 
 
 
 
 
 
9
  // Helper to get modifier key symbols/text
10
  const getModifierSymbol = (modifier: string): string => {
11
  switch (modifier) {
 
13
  return isMac ? '⌘' : 'Win';
14
  case 'alt':
15
  return isMac ? '⌥' : 'Alt';
 
 
16
  case 'shift':
17
  return '⇧';
18
  default:
 
175
  </div>
176
  </motion.div>
177
 
178
+ {/* Simplified Keyboard Shortcuts */}
179
  <motion.div
180
  className="bg-white dark:bg-[#0A0A0A] rounded-lg shadow-sm dark:shadow-none p-4"
181
  initial={{ opacity: 0, y: 20 }}
 
188
  </div>
189
 
190
  <div className="space-y-2">
191
+ <div className="flex items-center justify-between p-2 rounded-lg bg-[#FAFAFA] dark:bg-[#1A1A1A]">
192
+ <div className="flex flex-col">
193
+ <span className="text-sm text-bolt-elements-textPrimary">Toggle Theme</span>
194
+ <span className="text-xs text-bolt-elements-textSecondary">Switch between light and dark mode</span>
195
+ </div>
196
+ <div className="flex items-center gap-1">
197
+ <kbd className="px-2 py-1 text-xs font-semibold text-bolt-elements-textSecondary bg-white dark:bg-[#0A0A0A] border border-[#E5E5E5] dark:border-[#1A1A1A] rounded shadow-sm">
198
+ {getModifierSymbol('meta')}
199
+ </kbd>
200
+ <kbd className="px-2 py-1 text-xs font-semibold text-bolt-elements-textSecondary bg-white dark:bg-[#0A0A0A] border border-[#E5E5E5] dark:border-[#1A1A1A] rounded shadow-sm">
201
+ {getModifierSymbol('alt')}
202
+ </kbd>
203
+ <kbd className="px-2 py-1 text-xs font-semibold text-bolt-elements-textSecondary bg-white dark:bg-[#0A0A0A] border border-[#E5E5E5] dark:border-[#1A1A1A] rounded shadow-sm">
204
+ {getModifierSymbol('shift')}
205
+ </kbd>
206
+ <kbd className="px-2 py-1 text-xs font-semibold text-bolt-elements-textSecondary bg-white dark:bg-[#0A0A0A] border border-[#E5E5E5] dark:border-[#1A1A1A] rounded shadow-sm">
207
+ D
208
+ </kbd>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
209
  </div>
210
+ </div>
211
  </div>
212
  </motion.div>
213
  </div>
app/components/chat/BaseChat.tsx CHANGED
@@ -34,6 +34,7 @@ import ChatAlert from './ChatAlert';
34
  import type { ModelInfo } from '~/lib/modules/llm/types';
35
  import ProgressCompilation from './ProgressCompilation';
36
  import type { ProgressAnnotation } from '~/types/context';
 
37
 
38
  const TEXTAREA_MIN_HEIGHT = 76;
39
 
@@ -404,7 +405,7 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
404
  apiKeys={apiKeys}
405
  modelLoading={isModelLoading}
406
  />
407
- {(providerList || []).length > 0 && provider && (
408
  <APIKeyManager
409
  provider={provider}
410
  apiKey={apiKeys[provider.name] || ''}
 
34
  import type { ModelInfo } from '~/lib/modules/llm/types';
35
  import ProgressCompilation from './ProgressCompilation';
36
  import type { ProgressAnnotation } from '~/types/context';
37
+ import { LOCAL_PROVIDERS } from '~/lib/stores/settings';
38
 
39
  const TEXTAREA_MIN_HEIGHT = 76;
40
 
 
405
  apiKeys={apiKeys}
406
  modelLoading={isModelLoading}
407
  />
408
+ {(providerList || []).length > 0 && provider && !LOCAL_PROVIDERS.includes(provider.name) && (
409
  <APIKeyManager
410
  provider={provider}
411
  apiKey={apiKeys[provider.name] || ''}
app/lib/stores/settings.ts CHANGED
@@ -1,5 +1,4 @@
1
  import { atom, map } from 'nanostores';
2
- import { workbenchStore } from './workbench';
3
  import { PROVIDER_LIST } from '~/utils/constants';
4
  import type { IProviderConfig } from '~/types/model';
5
  import type {
@@ -11,7 +10,7 @@ import type {
11
  import { DEFAULT_TAB_CONFIG } from '~/components/@settings/core/constants';
12
  import Cookies from 'js-cookie';
13
  import { toggleTheme } from './theme';
14
- import { chatStore } from './chat';
15
 
16
  export interface Shortcut {
17
  key: string;
@@ -26,10 +25,8 @@ export interface Shortcut {
26
  }
27
 
28
  export interface Shortcuts {
29
- toggleTerminal: Shortcut;
30
  toggleTheme: Shortcut;
31
- toggleChat: Shortcut;
32
- toggleSettings: Shortcut;
33
  }
34
 
35
  export const URL_CONFIGURABLE_PROVIDERS = ['Ollama', 'LMStudio', 'OpenAILike'];
@@ -37,15 +34,8 @@ export const LOCAL_PROVIDERS = ['OpenAILike', 'LMStudio', 'Ollama'];
37
 
38
  export type ProviderSetting = Record<string, IProviderConfig>;
39
 
40
- // Define safer shortcuts that don't conflict with browser defaults
41
  export const shortcutsStore = map<Shortcuts>({
42
- toggleTerminal: {
43
- key: '`',
44
- ctrlOrMetaKey: true,
45
- action: () => workbenchStore.toggleTerminal(),
46
- description: 'Toggle terminal',
47
- isPreventDefault: true,
48
- },
49
  toggleTheme: {
50
  key: 'd',
51
  metaKey: true,
@@ -55,22 +45,13 @@ export const shortcutsStore = map<Shortcuts>({
55
  description: 'Toggle theme',
56
  isPreventDefault: true,
57
  },
58
- toggleChat: {
59
- key: 'j', // Changed from 'k' to 'j' to avoid conflicts
60
- ctrlOrMetaKey: true,
61
- altKey: true, // Added alt key to make it more unique
62
- action: () => chatStore.setKey('showChat', !chatStore.get().showChat),
63
- description: 'Toggle chat',
64
- isPreventDefault: true,
65
- },
66
- toggleSettings: {
67
- key: 's',
68
  ctrlOrMetaKey: true,
69
- altKey: true,
70
  action: () => {
71
- document.dispatchEvent(new CustomEvent('toggle-settings'));
72
  },
73
- description: 'Toggle settings',
74
  isPreventDefault: true,
75
  },
76
  });
@@ -319,3 +300,35 @@ export const setDeveloperMode = (value: boolean) => {
319
  localStorage.setItem(SETTINGS_KEYS.DEVELOPER_MODE, JSON.stringify(value));
320
  }
321
  };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import { atom, map } from 'nanostores';
 
2
  import { PROVIDER_LIST } from '~/utils/constants';
3
  import type { IProviderConfig } from '~/types/model';
4
  import type {
 
10
  import { DEFAULT_TAB_CONFIG } from '~/components/@settings/core/constants';
11
  import Cookies from 'js-cookie';
12
  import { toggleTheme } from './theme';
13
+ import { create } from 'zustand';
14
 
15
  export interface Shortcut {
16
  key: string;
 
25
  }
26
 
27
  export interface Shortcuts {
 
28
  toggleTheme: Shortcut;
29
+ toggleTerminal: Shortcut;
 
30
  }
31
 
32
  export const URL_CONFIGURABLE_PROVIDERS = ['Ollama', 'LMStudio', 'OpenAILike'];
 
34
 
35
  export type ProviderSetting = Record<string, IProviderConfig>;
36
 
37
+ // Simplified shortcuts store with only theme toggle
38
  export const shortcutsStore = map<Shortcuts>({
 
 
 
 
 
 
 
39
  toggleTheme: {
40
  key: 'd',
41
  metaKey: true,
 
45
  description: 'Toggle theme',
46
  isPreventDefault: true,
47
  },
48
+ toggleTerminal: {
49
+ key: '`',
 
 
 
 
 
 
 
 
50
  ctrlOrMetaKey: true,
 
51
  action: () => {
52
+ // This will be handled by the terminal component
53
  },
54
+ description: 'Toggle terminal',
55
  isPreventDefault: true,
56
  },
57
  });
 
300
  localStorage.setItem(SETTINGS_KEYS.DEVELOPER_MODE, JSON.stringify(value));
301
  }
302
  };
303
+
304
+ // First, let's define the SettingsStore interface
305
+ interface SettingsStore {
306
+ isOpen: boolean;
307
+ selectedTab: string;
308
+ openSettings: () => void;
309
+ closeSettings: () => void;
310
+ setSelectedTab: (tab: string) => void;
311
+ }
312
+
313
+ export const useSettingsStore = create<SettingsStore>((set) => ({
314
+ isOpen: false,
315
+ selectedTab: 'user', // Default tab
316
+
317
+ openSettings: () => {
318
+ set({
319
+ isOpen: true,
320
+ selectedTab: 'user', // Always open to user tab
321
+ });
322
+ },
323
+
324
+ closeSettings: () => {
325
+ set({
326
+ isOpen: false,
327
+ selectedTab: 'user', // Reset to user tab when closing
328
+ });
329
+ },
330
+
331
+ setSelectedTab: (tab: string) => {
332
+ set({ selectedTab: tab });
333
+ },
334
+ }));
app/utils/debounce.ts CHANGED
@@ -1,17 +1,13 @@
1
- export function debounce<Args extends any[]>(fn: (...args: Args) => void, delay = 100) {
2
- if (delay === 0) {
3
- return fn;
4
- }
5
 
6
- let timer: number | undefined;
 
 
 
 
7
 
8
- return function <U>(this: U, ...args: Args) {
9
- const context = this;
10
-
11
- clearTimeout(timer);
12
-
13
- timer = window.setTimeout(() => {
14
- fn.apply(context, args);
15
- }, delay);
16
  };
17
  }
 
1
+ export function debounce<T extends (...args: any[]) => any>(func: T, wait: number): (...args: Parameters<T>) => void {
2
+ let timeout: NodeJS.Timeout;
 
 
3
 
4
+ return function executedFunction(...args: Parameters<T>) {
5
+ const later = () => {
6
+ clearTimeout(timeout);
7
+ func(...args);
8
+ };
9
 
10
+ clearTimeout(timeout);
11
+ timeout = setTimeout(later, wait);
 
 
 
 
 
 
12
  };
13
  }