Stijnus
commited on
Commit
·
0e60d9c
1
Parent(s):
6e89710
UI bug fixes
Browse files- app/components/@settings/core/ControlPanel.tsx +24 -3
- app/components/@settings/shared/components/TabManagement.tsx +10 -1
- app/components/@settings/tabs/debug/DebugTab.tsx +6 -3
- app/components/@settings/tabs/profile/ProfileTab.tsx +14 -7
- app/components/@settings/tabs/providers/local/OllamaModelInstaller.tsx +9 -4
- app/components/@settings/tabs/settings/SettingsTab.tsx +20 -58
- app/components/chat/BaseChat.tsx +2 -1
- app/lib/stores/settings.ts +39 -26
- app/utils/debounce.ts +9 -13
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={
|
409 |
-
onPointerDownOutside={
|
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={
|
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(
|
1108 |
|
1109 |
if (!versionResponse.ok) {
|
1110 |
throw new Error('Service not running');
|
1111 |
}
|
1112 |
|
1113 |
// Then fetch installed models
|
1114 |
-
const modelsResponse = await fetch(
|
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 |
-
//
|
48 |
-
|
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(
|
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(
|
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(
|
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 |
-
|
205 |
-
<div
|
206 |
-
|
207 |
-
className="
|
208 |
-
>
|
209 |
-
|
210 |
-
|
211 |
-
|
212 |
-
|
213 |
-
|
214 |
-
|
215 |
-
|
216 |
-
|
217 |
-
|
218 |
-
|
219 |
-
|
220 |
-
|
221 |
-
|
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 {
|
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 |
-
|
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 |
-
//
|
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 |
-
|
59 |
-
key: '
|
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 |
-
|
72 |
},
|
73 |
-
description: 'Toggle
|
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<
|
2 |
-
|
3 |
-
return fn;
|
4 |
-
}
|
5 |
|
6 |
-
|
|
|
|
|
|
|
|
|
7 |
|
8 |
-
|
9 |
-
|
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 |
}
|