From 4b817ebdce9e6d50b828331dad4dcacb77e073d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filipe=20Gi=C3=A1como?= <36394761+filipegiacomo@users.noreply.github.com> Date: Wed, 12 Feb 2025 11:39:37 -0300 Subject: [PATCH 01/18] Update amazon-bedrock.ts New Claude 3.5 Sonnet v2 Anthropogenic Model --- app/lib/modules/llm/providers/amazon-bedrock.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/lib/modules/llm/providers/amazon-bedrock.ts b/app/lib/modules/llm/providers/amazon-bedrock.ts index f01b13ac..6a4cbc96 100644 --- a/app/lib/modules/llm/providers/amazon-bedrock.ts +++ b/app/lib/modules/llm/providers/amazon-bedrock.ts @@ -20,6 +20,12 @@ export default class AmazonBedrockProvider extends BaseProvider { }; staticModels: ModelInfo[] = [ + { + name: 'anthropic.claude-3-5-sonnet-20241022-v2:0', + label: 'Claude 3.5 Sonnet v2 (Bedrock)', + provider: 'AmazonBedrock', + maxTokenAllowed: 200000, + }, { name: 'anthropic.claude-3-5-sonnet-20240620-v1:0', label: 'Claude 3.5 Sonnet (Bedrock)', From b25db9b27e0ae6185f93eda886f55a68af838fed Mon Sep 17 00:00:00 2001 From: Kamil Furtak Date: Sat, 15 Feb 2025 13:37:35 +0100 Subject: [PATCH 02/18] add model search --- app/components/chat/ModelSelector.tsx | 129 +++++++++++++++++++++----- 1 file changed, 106 insertions(+), 23 deletions(-) diff --git a/app/components/chat/ModelSelector.tsx b/app/components/chat/ModelSelector.tsx index 521ccac3..918b681d 100644 --- a/app/components/chat/ModelSelector.tsx +++ b/app/components/chat/ModelSelector.tsx @@ -1,6 +1,7 @@ import type { ProviderInfo } from '~/types/model'; -import { useEffect } from 'react'; +import { useEffect, useState, useRef } from 'react'; import type { ModelInfo } from '~/lib/modules/llm/types'; +import { classNames } from '~/utils/classNames'; interface ModelSelectorProps { model?: string; @@ -22,12 +23,30 @@ export const ModelSelector = ({ providerList, modelLoading, }: ModelSelectorProps) => { - // Load enabled providers from cookies + const [modelSearchQuery, setModelSearchQuery] = useState(''); + const [isModelDropdownOpen, setIsModelDropdownOpen] = useState(false); + const searchInputRef = useRef(null); + + // Filter models based on search query + const filteredModels = [...modelList] + .filter((e) => e.provider === provider?.name && e.name) + .filter( + (model) => + model.label.toLowerCase().includes(modelSearchQuery.toLowerCase()) || + model.name.toLowerCase().includes(modelSearchQuery.toLowerCase()), + ); + + // Focus search input when dropdown opens + useEffect(() => { + if (isModelDropdownOpen && searchInputRef.current) { + searchInputRef.current.focus(); + } + }, [isModelDropdownOpen]); // Update enabled providers when cookies change useEffect(() => { // If current provider is disabled, switch to first enabled provider - if (providerList.length == 0) { + if (providerList.length === 0) { return; } @@ -80,27 +99,91 @@ export const ModelSelector = ({ ))} - setModelSearchQuery(e.target.value)} + placeholder="Search models..." + className={classNames( + 'w-full pl-8 pr-3 py-1.5 rounded-md text-sm', + 'bg-bolt-elements-background-depth-2 border border-bolt-elements-borderColor', + 'text-bolt-elements-textPrimary placeholder:text-bolt-elements-textTertiary', + 'focus:outline-none focus:ring-2 focus:ring-bolt-elements-focus', + 'transition-all', + )} + onClick={(e) => e.stopPropagation()} + /> +
+ +
+ + + +
+ {modelLoading === 'all' || modelLoading === provider?.name ? ( +
Loading...
+ ) : filteredModels.length === 0 ? ( +
No models found
+ ) : ( + filteredModels.map((modelOption, index) => ( +
{ + e.stopPropagation(); + setModel?.(modelOption.name); + setIsModelDropdownOpen(false); + setModelSearchQuery(''); + }} + > + {modelOption.label} +
+ )) + )} +
+ )} - + ); }; From d56708966ac3b592e8e945281660ff05eae37803 Mon Sep 17 00:00:00 2001 From: Kamil Furtak Date: Sat, 15 Feb 2025 13:45:26 +0100 Subject: [PATCH 03/18] fix srollbar --- app/components/chat/ModelSelector.tsx | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/app/components/chat/ModelSelector.tsx b/app/components/chat/ModelSelector.tsx index 918b681d..4cad4d44 100644 --- a/app/components/chat/ModelSelector.tsx +++ b/app/components/chat/ModelSelector.tsx @@ -150,9 +150,21 @@ export const ModelSelector = ({
{modelLoading === 'all' || modelLoading === provider?.name ? ( From cb58db3bf02cdc0749cd59129358f9f1354c6178 Mon Sep 17 00:00:00 2001 From: Kamil Furtak Date: Sat, 15 Feb 2025 13:51:11 +0100 Subject: [PATCH 04/18] fix ui add keyboard events --- app/components/chat/ModelSelector.tsx | 103 ++++++++++++++++++++++++-- 1 file changed, 96 insertions(+), 7 deletions(-) diff --git a/app/components/chat/ModelSelector.tsx b/app/components/chat/ModelSelector.tsx index 4cad4d44..13d2622a 100644 --- a/app/components/chat/ModelSelector.tsx +++ b/app/components/chat/ModelSelector.tsx @@ -1,5 +1,6 @@ import type { ProviderInfo } from '~/types/model'; import { useEffect, useState, useRef } from 'react'; +import type { KeyboardEvent } from 'react'; import type { ModelInfo } from '~/lib/modules/llm/types'; import { classNames } from '~/utils/classNames'; @@ -25,7 +26,9 @@ export const ModelSelector = ({ }: ModelSelectorProps) => { const [modelSearchQuery, setModelSearchQuery] = useState(''); const [isModelDropdownOpen, setIsModelDropdownOpen] = useState(false); + const [focusedIndex, setFocusedIndex] = useState(-1); const searchInputRef = useRef(null); + const optionsRef = useRef<(HTMLDivElement | null)[]>([]); // Filter models based on search query const filteredModels = [...modelList] @@ -36,6 +39,11 @@ export const ModelSelector = ({ model.name.toLowerCase().includes(modelSearchQuery.toLowerCase()), ); + // Reset focused index when search query changes or dropdown opens/closes + useEffect(() => { + setFocusedIndex(-1); + }, [modelSearchQuery, isModelDropdownOpen]); + // Focus search input when dropdown opens useEffect(() => { if (isModelDropdownOpen && searchInputRef.current) { @@ -43,6 +51,73 @@ export const ModelSelector = ({ } }, [isModelDropdownOpen]); + // Handle keyboard navigation + const handleKeyDown = (e: KeyboardEvent) => { + if (!isModelDropdownOpen) { + return; + } + + switch (e.key) { + case 'ArrowDown': + e.preventDefault(); + setFocusedIndex((prev) => { + const next = prev + 1; + + if (next >= filteredModels.length) { + return 0; + } + + return next; + }); + break; + + case 'ArrowUp': + e.preventDefault(); + setFocusedIndex((prev) => { + const next = prev - 1; + + if (next < 0) { + return filteredModels.length - 1; + } + + return next; + }); + break; + + case 'Enter': + e.preventDefault(); + + if (focusedIndex >= 0 && focusedIndex < filteredModels.length) { + const selectedModel = filteredModels[focusedIndex]; + setModel?.(selectedModel.name); + setIsModelDropdownOpen(false); + setModelSearchQuery(''); + } + + break; + + case 'Escape': + e.preventDefault(); + setIsModelDropdownOpen(false); + setModelSearchQuery(''); + break; + + case 'Tab': + if (!e.shiftKey && focusedIndex === filteredModels.length - 1) { + setIsModelDropdownOpen(false); + } + + break; + } + }; + + // Focus the selected option + useEffect(() => { + if (focusedIndex >= 0 && optionsRef.current[focusedIndex]) { + optionsRef.current[focusedIndex]?.scrollIntoView({ block: 'nearest' }); + } + }, [focusedIndex]); + // Update enabled providers when cookies change useEffect(() => { // If current provider is disabled, switch to first enabled provider @@ -100,7 +175,7 @@ export const ModelSelector = ({ ))} -
+
setIsModelDropdownOpen(!isModelDropdownOpen)} + role="combobox" + aria-expanded={isModelDropdownOpen} + aria-controls="model-listbox" + aria-haspopup="listbox" >
{modelList.find((m) => m.name === model)?.label || 'Select model'} @@ -123,7 +202,11 @@ export const ModelSelector = ({
{isModelDropdownOpen && ( -
+
e.stopPropagation()} + role="searchbox" + aria-label="Search models" />
@@ -150,8 +235,6 @@ export const ModelSelector = ({
(
(optionsRef.current[index] = el)} key={index} + role="option" + aria-selected={model === modelOption.name} className={classNames( 'px-3 py-2 text-sm cursor-pointer', 'hover:bg-bolt-elements-background-depth-3', 'text-bolt-elements-textPrimary', - model === modelOption.name ? 'bg-bolt-elements-background-depth-2' : undefined, + 'outline-none', + model === modelOption.name || focusedIndex === index + ? 'bg-bolt-elements-background-depth-2' + : undefined, + focusedIndex === index ? 'ring-1 ring-inset ring-bolt-elements-focus' : undefined, )} onClick={(e) => { e.stopPropagation(); @@ -187,6 +275,7 @@ export const ModelSelector = ({ setIsModelDropdownOpen(false); setModelSearchQuery(''); }} + tabIndex={focusedIndex === index ? 0 : -1} > {modelOption.label}
From db5f30e1ee7addf702526aaf4a8b52f885f3c10a Mon Sep 17 00:00:00 2001 From: Stijnus <72551117+Stijnus@users.noreply.github.com> Date: Sat, 15 Feb 2025 15:12:08 +0100 Subject: [PATCH 05/18] Update settings.ts --- app/lib/stores/settings.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/lib/stores/settings.ts b/app/lib/stores/settings.ts index d8b1ca18..f8e36f65 100644 --- a/app/lib/stores/settings.ts +++ b/app/lib/stores/settings.ts @@ -174,12 +174,12 @@ const getInitialSettings = () => { }; return { - latestBranch: getStoredBoolean(SETTINGS_KEYS.LATEST_BRANCH, false), - autoSelectTemplate: getStoredBoolean(SETTINGS_KEYS.AUTO_SELECT_TEMPLATE, false), - contextOptimization: getStoredBoolean(SETTINGS_KEYS.CONTEXT_OPTIMIZATION, false), + latestBranch: getStoredBoolean(SETTINGS_KEYS.LATEST_BRANCH, true), + autoSelectTemplate: getStoredBoolean(SETTINGS_KEYS.AUTO_SELECT_TEMPLATE, true), + contextOptimization: getStoredBoolean(SETTINGS_KEYS.CONTEXT_OPTIMIZATION, true), eventLogs: getStoredBoolean(SETTINGS_KEYS.EVENT_LOGS, true), localModels: getStoredBoolean(SETTINGS_KEYS.LOCAL_MODELS, true), - promptId: isBrowser ? localStorage.getItem(SETTINGS_KEYS.PROMPT_ID) || 'default' : 'default', + promptId: isBrowser ? localStorage.getItem(SETTINGS_KEYS.PROMPT_ID) || 'optimized' : 'optimized', developerMode: getStoredBoolean(SETTINGS_KEYS.DEVELOPER_MODE, false), }; }; From 24bf34c683a5d234008b5c27f77315b3d2c98a13 Mon Sep 17 00:00:00 2001 From: Kamil Furtak Date: Sat, 15 Feb 2025 15:59:37 +0100 Subject: [PATCH 06/18] fix: Size of dropdowns should be always the same and not break into 2 lines --- app/components/chat/ModelSelector.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/components/chat/ModelSelector.tsx b/app/components/chat/ModelSelector.tsx index 13d2622a..d332b5cd 100644 --- a/app/components/chat/ModelSelector.tsx +++ b/app/components/chat/ModelSelector.tsx @@ -181,7 +181,7 @@ export const ModelSelector = ({ 'w-full p-2 rounded-lg border border-bolt-elements-borderColor', 'bg-bolt-elements-prompt-background text-bolt-elements-textPrimary', 'focus-within:outline-none focus-within:ring-2 focus-within:ring-bolt-elements-focus', - 'transition-all cursor-pointer', + 'transition-all cursor-pointer truncate', isModelDropdownOpen ? 'ring-2 ring-bolt-elements-focus' : undefined, )} onClick={() => setIsModelDropdownOpen(!isModelDropdownOpen)} From 0f6bfca9bc5ea7ad5672f3b36b350ce899650c48 Mon Sep 17 00:00:00 2001 From: Kamil Furtak Date: Sat, 15 Feb 2025 16:07:33 +0100 Subject: [PATCH 07/18] remove truncate --- app/components/chat/ModelSelector.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/components/chat/ModelSelector.tsx b/app/components/chat/ModelSelector.tsx index d332b5cd..13d2622a 100644 --- a/app/components/chat/ModelSelector.tsx +++ b/app/components/chat/ModelSelector.tsx @@ -181,7 +181,7 @@ export const ModelSelector = ({ 'w-full p-2 rounded-lg border border-bolt-elements-borderColor', 'bg-bolt-elements-prompt-background text-bolt-elements-textPrimary', 'focus-within:outline-none focus-within:ring-2 focus-within:ring-bolt-elements-focus', - 'transition-all cursor-pointer truncate', + 'transition-all cursor-pointer', isModelDropdownOpen ? 'ring-2 ring-bolt-elements-focus' : undefined, )} onClick={() => setIsModelDropdownOpen(!isModelDropdownOpen)} From 95de84c41a0eedb028f6fafa8e8ff0c7a43cedef Mon Sep 17 00:00:00 2001 From: Kamil Furtak Date: Sat, 15 Feb 2025 16:15:36 +0100 Subject: [PATCH 08/18] add whitespace-nowrap --- app/components/chat/ModelSelector.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/components/chat/ModelSelector.tsx b/app/components/chat/ModelSelector.tsx index 13d2622a..cbb4b171 100644 --- a/app/components/chat/ModelSelector.tsx +++ b/app/components/chat/ModelSelector.tsx @@ -190,7 +190,7 @@ export const ModelSelector = ({ aria-controls="model-listbox" aria-haspopup="listbox" > -
+
{modelList.find((m) => m.name === model)?.label || 'Select model'} Date: Sat, 15 Feb 2025 16:42:40 +0100 Subject: [PATCH 09/18] fix truncation --- app/components/chat/ModelSelector.tsx | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/app/components/chat/ModelSelector.tsx b/app/components/chat/ModelSelector.tsx index cbb4b171..49f1bfab 100644 --- a/app/components/chat/ModelSelector.tsx +++ b/app/components/chat/ModelSelector.tsx @@ -3,6 +3,7 @@ import { useEffect, useState, useRef } from 'react'; import type { KeyboardEvent } from 'react'; import type { ModelInfo } from '~/lib/modules/llm/types'; import { classNames } from '~/utils/classNames'; +import * as React from 'react'; interface ModelSelectorProps { model?: string; @@ -190,11 +191,11 @@ export const ModelSelector = ({ aria-controls="model-listbox" aria-haspopup="listbox" > -
- {modelList.find((m) => m.name === model)?.label || 'Select model'} - +
{modelList.find((m) => m.name === model)?.label || 'Select model'}
+
From f94be5b383f699c0c3f8fefadf6c406a93284cc7 Mon Sep 17 00:00:00 2001 From: Kamil Furtak Date: Sat, 15 Feb 2025 16:50:15 +0100 Subject: [PATCH 10/18] remove transparency --- app/components/chat/ModelSelector.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/components/chat/ModelSelector.tsx b/app/components/chat/ModelSelector.tsx index 49f1bfab..880f900f 100644 --- a/app/components/chat/ModelSelector.tsx +++ b/app/components/chat/ModelSelector.tsx @@ -204,7 +204,7 @@ export const ModelSelector = ({ {isModelDropdownOpen && (
From 2056625cbdfc6396dbdb804ce0b737caf635ed8a Mon Sep 17 00:00:00 2001 From: Kamil Furtak Date: Sat, 15 Feb 2025 16:55:55 +0100 Subject: [PATCH 11/18] Enhance accessibility for ModelSelector dropdown Added keyboard support for toggling the dropdown using Enter or Space keys, improving accessibility. Also set appropriate focus properties by adding tabindex to the combobox element. --- app/components/chat/ModelSelector.tsx | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/components/chat/ModelSelector.tsx b/app/components/chat/ModelSelector.tsx index 880f900f..4743199a 100644 --- a/app/components/chat/ModelSelector.tsx +++ b/app/components/chat/ModelSelector.tsx @@ -186,10 +186,17 @@ export const ModelSelector = ({ isModelDropdownOpen ? 'ring-2 ring-bolt-elements-focus' : undefined, )} onClick={() => setIsModelDropdownOpen(!isModelDropdownOpen)} + onKeyDown={(e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + setIsModelDropdownOpen(!isModelDropdownOpen); + } + }} role="combobox" aria-expanded={isModelDropdownOpen} aria-controls="model-listbox" aria-haspopup="listbox" + tabIndex={0} >
{modelList.find((m) => m.name === model)?.label || 'Select model'}
From e717d251912437a86758a5bb6d8a9023fc08deb3 Mon Sep 17 00:00:00 2001 From: Kamil Furtak Date: Sat, 15 Feb 2025 16:59:58 +0100 Subject: [PATCH 12/18] Add click outside handler to close ModelSelector dropdown --- app/components/chat/ModelSelector.tsx | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/app/components/chat/ModelSelector.tsx b/app/components/chat/ModelSelector.tsx index 4743199a..b80bfc8b 100644 --- a/app/components/chat/ModelSelector.tsx +++ b/app/components/chat/ModelSelector.tsx @@ -30,6 +30,20 @@ export const ModelSelector = ({ const [focusedIndex, setFocusedIndex] = useState(-1); const searchInputRef = useRef(null); const optionsRef = useRef<(HTMLDivElement | null)[]>([]); + const dropdownRef = useRef(null); + + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) { + setIsModelDropdownOpen(false); + setModelSearchQuery(''); + } + }; + + document.addEventListener('mousedown', handleClickOutside); + + return () => document.removeEventListener('mousedown', handleClickOutside); + }, []); // Filter models based on search query const filteredModels = [...modelList] @@ -176,7 +190,7 @@ export const ModelSelector = ({ ))} -
+
Date: Sat, 15 Feb 2025 17:28:17 +0100 Subject: [PATCH 13/18] Several UI fixes --- .../shared/components/TabManagement.tsx | 257 ++++++++++++------ .../@settings/tabs/debug/DebugTab.tsx | 153 ++++++----- .../providers/local/LocalProvidersTab.tsx | 68 ++++- .../providers/local/OllamaModelInstaller.tsx | 13 +- .../@settings/tabs/settings/SettingsTab.tsx | 61 +---- app/lib/stores/settings.ts | 2 +- 6 files changed, 345 insertions(+), 209 deletions(-) diff --git a/app/components/@settings/shared/components/TabManagement.tsx b/app/components/@settings/shared/components/TabManagement.tsx index ec6acece..67311c43 100644 --- a/app/components/@settings/shared/components/TabManagement.tsx +++ b/app/components/@settings/shared/components/TabManagement.tsx @@ -1,7 +1,7 @@ import { useState } from 'react'; import { motion } from 'framer-motion'; import { useStore } from '@nanostores/react'; -import { Switch } from '@radix-ui/react-switch'; +import { Switch } from '~/components/ui/Switch'; import { classNames } from '~/utils/classNames'; import { tabConfigurationStore } from '~/lib/stores/settings'; import { TAB_LABELS } from '~/components/@settings/core/constants'; @@ -177,92 +177,193 @@ export const TabManagement = () => { {/* Tab Grid */}
- {filteredTabs.map((tab, index) => ( - - {/* Status Badges */} -
- {DEFAULT_USER_TABS.includes(tab.id) && ( - + {/* Default Section Header */} + {filteredTabs.some((tab) => DEFAULT_USER_TABS.includes(tab.id)) && ( +
+
+ Default Tabs +
+ )} + + {/* Default Tabs */} + {filteredTabs + .filter((tab) => DEFAULT_USER_TABS.includes(tab.id)) + .map((tab, index) => ( + + {/* Status Badges */} +
+ Default - )} - {OPTIONAL_USER_TABS.includes(tab.id) && ( - - Optional - - )} -
+
-
- -
-
-
- - -
-
-
-
-

- {TAB_LABELS[tab.id]} -

- {BETA_TABS.has(tab.id) && } -
-

- {tab.visible ? 'Visible in user mode' : 'Hidden in user mode'} -

+
+ +
+
- handleTabVisibilityChange(tab.id, checked)} - disabled={!DEFAULT_USER_TABS.includes(tab.id) && !OPTIONAL_USER_TABS.includes(tab.id)} - className={classNames( - 'relative inline-flex h-5 w-9 items-center rounded-full', - 'transition-colors duration-200', - tab.visible ? 'bg-purple-500' : 'bg-bolt-elements-background-depth-4', - { - 'opacity-50 cursor-not-allowed': + + +
+
+
+
+

+ {TAB_LABELS[tab.id]} +

+ {BETA_TABS.has(tab.id) && } +
+

+ {tab.visible ? 'Visible in user mode' : 'Hidden in user mode'} +

+
+ { + const isDisabled = + !DEFAULT_USER_TABS.includes(tab.id) && !OPTIONAL_USER_TABS.includes(tab.id); + + if (!isDisabled) { + handleTabVisibilityChange(tab.id, checked); + } + }} + className={classNames('data-[state=checked]:bg-purple-500 ml-4', { + 'opacity-50 pointer-events-none': !DEFAULT_USER_TABS.includes(tab.id) && !OPTIONAL_USER_TABS.includes(tab.id), - }, - )} - /> + })} + /> +
-
+ + + ))} + + {/* Optional Section Header */} + {filteredTabs.some((tab) => OPTIONAL_USER_TABS.includes(tab.id)) && ( +
+
+ Optional Tabs +
+ )} + + {/* Optional Tabs */} + {filteredTabs + .filter((tab) => OPTIONAL_USER_TABS.includes(tab.id)) + .map((tab, index) => ( - - ))} + key={tab.id} + className={classNames( + 'rounded-lg border bg-bolt-elements-background text-bolt-elements-textPrimary', + 'bg-bolt-elements-background-depth-2', + 'hover:bg-bolt-elements-background-depth-3', + 'transition-all duration-200', + 'relative overflow-hidden group', + )} + initial={{ opacity: 0, y: 20 }} + animate={{ opacity: 1, y: 0 }} + transition={{ delay: index * 0.1 }} + whileHover={{ scale: 1.02 }} + > + {/* Status Badges */} +
+ + Optional + +
+ +
+ +
+
+
+ + +
+
+
+
+

+ {TAB_LABELS[tab.id]} +

+ {BETA_TABS.has(tab.id) && } +
+

+ {tab.visible ? 'Visible in user mode' : 'Hidden in user mode'} +

+
+ { + const isDisabled = + !DEFAULT_USER_TABS.includes(tab.id) && !OPTIONAL_USER_TABS.includes(tab.id); + + if (!isDisabled) { + handleTabVisibilityChange(tab.id, checked); + } + }} + className={classNames('data-[state=checked]:bg-purple-500 ml-4', { + 'opacity-50 pointer-events-none': + !DEFAULT_USER_TABS.includes(tab.id) && !OPTIONAL_USER_TABS.includes(tab.id), + })} + /> +
+
+
+ + + + ))}
diff --git a/app/components/@settings/tabs/debug/DebugTab.tsx b/app/components/@settings/tabs/debug/DebugTab.tsx index ae7a8339..cfaeb966 100644 --- a/app/components/@settings/tabs/debug/DebugTab.tsx +++ b/app/components/@settings/tabs/debug/DebugTab.tsx @@ -1270,60 +1270,32 @@ export default function DebugTab() {
{/* Quick Stats Banner */}
- {/* Ollama Service Status Card */} -
+ {/* Errors Card */} +
-
-
Ollama Service
+
+
Errors
-
- - {status.status === 'Running' &&
} - {status.status === 'Not Running' &&
} - {status.status === 'Disabled' &&
} - {status.status} + 0 ? 'text-red-500' : 'text-green-500')} + > + {errorLogs.length}
0 ? 'i-ph:warning text-red-500' : 'i-ph:check-circle text-green-500', + )} /> - {status.message} -
- {ollamaStatus.models && ollamaStatus.models.length > 0 && ( -
-
-
- Installed Models -
- {ollamaStatus.models.map((model) => ( -
-
- {model.name} - - ({Math.round(parseInt(model.size) / 1024 / 1024)}MB, {model.quantization}) - -
- ))} -
- )} -
-
- Last checked: {ollamaStatus.lastChecked.toLocaleTimeString()} + {errorLogs.length > 0 ? 'Errors detected' : 'No errors detected'}
{/* Memory Usage Card */} -
+
Memory Usage
@@ -1360,7 +1332,7 @@ export default function DebugTab() {
{/* Page Load Time Card */} -
+
Page Load Time
@@ -1386,7 +1358,7 @@ export default function DebugTab() {
{/* Network Speed Card */} -
+
Network Speed
@@ -1411,27 +1383,80 @@ export default function DebugTab() {
- {/* Errors Card */} -
-
-
-
Errors
+ {/* Ollama Service Card - Now spans all 4 columns */} +
+
+
+
+
+
Ollama Service
+
{status.message}
+
+
+
+
+
+ + {status.status} + +
+
+
+ {ollamaStatus.lastChecked.toLocaleTimeString()} +
+
-
- 0 ? 'text-red-500' : 'text-green-500')} - > - {errorLogs.length} - -
-
-
0 ? 'i-ph:warning text-red-500' : 'i-ph:check-circle text-green-500', - )} - /> - {errorLogs.length > 0 ? 'Errors detected' : 'No errors detected'} + +
+ {status.status === 'Running' && ollamaStatus.models && ollamaStatus.models.length > 0 ? ( + <> +
+
+
+ Installed Models + + {ollamaStatus.models.length} + +
+
+
+
+ {ollamaStatus.models.map((model) => ( +
+
+
+ {model.name} +
+ + {Math.round(parseInt(model.size) / 1024 / 1024)}MB + +
+ ))} +
+
+ + ) : ( +
+
+
+ {status.message} +
+
+ )}
diff --git a/app/components/@settings/tabs/providers/local/LocalProvidersTab.tsx b/app/components/@settings/tabs/providers/local/LocalProvidersTab.tsx index df986302..70e8d2f5 100644 --- a/app/components/@settings/tabs/providers/local/LocalProvidersTab.tsx +++ b/app/components/@settings/tabs/providers/local/LocalProvidersTab.tsx @@ -471,6 +471,60 @@ export default function LocalProvidersTab() { />
+ {/* URL Configuration Section */} + + {provider.settings.enabled && ( + +
+ + {editingProvider === provider.name ? ( + { + if (e.key === 'Enter') { + handleUpdateBaseUrl(provider, e.currentTarget.value); + } else if (e.key === 'Escape') { + setEditingProvider(null); + } + }} + onBlur={(e) => handleUpdateBaseUrl(provider, e.target.value)} + autoFocus + /> + ) : ( +
setEditingProvider(provider.name)} + className={classNames( + 'w-full px-3 py-2 rounded-lg text-sm cursor-pointer', + 'bg-bolt-elements-background-depth-3 border border-bolt-elements-borderColor', + 'hover:border-purple-500/30 hover:bg-bolt-elements-background-depth-4', + 'transition-all duration-200', + )} + > +
+
+ {provider.settings.baseUrl || OLLAMA_API_URL} +
+
+ )} +
+ + )} + + {/* Ollama Models Section */} {provider.settings.enabled && ( @@ -505,7 +559,19 @@ export default function LocalProvidersTab() {

No models installed yet

-

Install your first model below

+

+ Browse models at{' '} + + ollama.com/library +

+ {' '} + and copy model names to install +

) : ( ollamaModels.map((model) => ( diff --git a/app/components/@settings/tabs/providers/local/OllamaModelInstaller.tsx b/app/components/@settings/tabs/providers/local/OllamaModelInstaller.tsx index b31bb744..18b8655d 100644 --- a/app/components/@settings/tabs/providers/local/OllamaModelInstaller.tsx +++ b/app/components/@settings/tabs/providers/local/OllamaModelInstaller.tsx @@ -429,16 +429,16 @@ export default function OllamaModelInstaller({ onModelInstalled }: OllamaModelIn }} disabled={isInstalling} /> -

+

Browse models at{' '} ollama.com/library -

+
{' '} and copy model names to install

@@ -448,10 +448,11 @@ export default function OllamaModelInstaller({ onModelInstalled }: OllamaModelIn onClick={() => handleInstallModel(modelString)} disabled={!modelString || isInstalling} className={classNames( - 'rounded-xl px-6 py-3', - 'bg-purple-500 text-white', + 'rounded-lg px-4 py-2', + 'bg-purple-500 text-white text-sm', 'hover:bg-purple-600', 'transition-all duration-200', + 'flex items-center gap-2', { 'opacity-50 cursor-not-allowed': !modelString || isInstalling }, )} whileHover={{ scale: 1.02 }} @@ -459,7 +460,7 @@ export default function OllamaModelInstaller({ onModelInstalled }: OllamaModelIn > {isInstalling ? (
-
+
Installing...
) : ( diff --git a/app/components/@settings/tabs/settings/SettingsTab.tsx b/app/components/@settings/tabs/settings/SettingsTab.tsx index 28a6e1ca..a14937a4 100644 --- a/app/components/@settings/tabs/settings/SettingsTab.tsx +++ b/app/components/@settings/tabs/settings/SettingsTab.tsx @@ -3,7 +3,6 @@ import { motion } from 'framer-motion'; import { toast } from 'react-toastify'; import { classNames } from '~/utils/classNames'; import { Switch } from '~/components/ui/Switch'; -import { themeStore, kTheme } from '~/lib/stores/theme'; import type { UserProfile } from '~/components/@settings/core/types'; import { useStore } from '@nanostores/react'; import { shortcutsStore } from '~/lib/stores/settings'; @@ -41,7 +40,6 @@ export default function SettingsTab() { return saved ? JSON.parse(saved) : { - theme: 'system', notifications: true, language: 'en', timezone: Intl.DateTimeFormat().resolvedOptions().timeZone, @@ -52,22 +50,6 @@ export default function SettingsTab() { setCurrentTimezone(Intl.DateTimeFormat().resolvedOptions().timeZone); }, []); - // Apply theme when settings changes - useEffect(() => { - if (settings.theme === 'system') { - // Remove theme override - localStorage.removeItem(kTheme); - - const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches; - document.querySelector('html')?.setAttribute('data-theme', prefersDark ? 'dark' : 'light'); - themeStore.set(prefersDark ? 'dark' : 'light'); - } else { - themeStore.set(settings.theme); - localStorage.setItem(kTheme, settings.theme); - document.querySelector('html')?.setAttribute('data-theme', settings.theme); - } - }, [settings.theme]); - // Save settings automatically when they change useEffect(() => { try { @@ -77,7 +59,6 @@ export default function SettingsTab() { // Merge with new settings const updatedProfile = { ...existingProfile, - theme: settings.theme, notifications: settings.notifications, language: settings.language, timezone: settings.timezone, @@ -93,7 +74,7 @@ export default function SettingsTab() { return (
- {/* Theme & Language */} + {/* Language & Notifications */}
- Appearance -
- -
-
-
- -
-
- {(['light', 'dark', 'system'] as const).map((theme) => ( - - ))} -
+ Preferences
diff --git a/app/lib/stores/settings.ts b/app/lib/stores/settings.ts index f8e36f65..112efdca 100644 --- a/app/lib/stores/settings.ts +++ b/app/lib/stores/settings.ts @@ -179,7 +179,7 @@ const getInitialSettings = () => { contextOptimization: getStoredBoolean(SETTINGS_KEYS.CONTEXT_OPTIMIZATION, true), eventLogs: getStoredBoolean(SETTINGS_KEYS.EVENT_LOGS, true), localModels: getStoredBoolean(SETTINGS_KEYS.LOCAL_MODELS, true), - promptId: isBrowser ? localStorage.getItem(SETTINGS_KEYS.PROMPT_ID) || 'optimized' : 'optimized', + promptId: isBrowser ? localStorage.getItem(SETTINGS_KEYS.PROMPT_ID) || 'default' : 'default', developerMode: getStoredBoolean(SETTINGS_KEYS.DEVELOPER_MODE, false), }; }; From 70b723d5140cdcfa08019551966173804f81290e Mon Sep 17 00:00:00 2001 From: KevIsDev Date: Mon, 17 Feb 2025 01:49:33 +0000 Subject: [PATCH 14/18] fix: debounce profile update notifications to prevent toast spam a new toast was being triggered for every character input. --- .../@settings/tabs/profile/ProfileTab.tsx | 39 +++++++++++++------ 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/app/components/@settings/tabs/profile/ProfileTab.tsx b/app/components/@settings/tabs/profile/ProfileTab.tsx index 6ea19fe4..21e917bd 100644 --- a/app/components/@settings/tabs/profile/ProfileTab.tsx +++ b/app/components/@settings/tabs/profile/ProfileTab.tsx @@ -1,4 +1,4 @@ -import { useState } from 'react'; +import { useState, useCallback, useEffect } from 'react'; import { useStore } from '@nanostores/react'; import { classNames } from '~/utils/classNames'; import { profileStore, updateProfile } from '~/lib/stores/profile'; @@ -7,6 +7,32 @@ import { toast } from 'react-toastify'; export default function ProfileTab() { const profile = useStore(profileStore); const [isUploading, setIsUploading] = useState(false); + const [toastTimeout, setToastTimeout] = useState(null); + + const handleProfileUpdate = useCallback( + (field: 'username' | 'bio', value: string) => { + updateProfile({ [field]: value }); + + if (toastTimeout) { + clearTimeout(toastTimeout); + } + + const timeout = setTimeout(() => { + toast.success(`${field.charAt(0).toUpperCase() + field.slice(1)} updated`); + }, 1000); + + setToastTimeout(timeout); + }, + [toastTimeout], + ); + + useEffect(() => { + return () => { + if (toastTimeout) { + clearTimeout(toastTimeout); + } + }; + }, [toastTimeout]); const handleAvatarUpload = async (e: React.ChangeEvent) => { const file = e.target.files?.[0]; @@ -41,17 +67,6 @@ export default function ProfileTab() { } }; - const handleProfileUpdate = (field: 'username' | 'bio', value: string) => { - updateProfile({ [field]: value }); - - // Only show toast for completed typing (after 1 second of no typing) - const debounceToast = setTimeout(() => { - toast.success(`${field.charAt(0).toUpperCase() + field.slice(1)} updated`); - }, 1000); - - return () => clearTimeout(debounceToast); - }; - return (
From 0e60d9cca8412b15cd6422c460fc25d82fb083c4 Mon Sep 17 00:00:00 2001 From: Stijnus <72551117+Stijnus@users.noreply.github.com> Date: Tue, 18 Feb 2025 14:13:13 +0100 Subject: [PATCH 15/18] UI bug fixes --- .../@settings/core/ControlPanel.tsx | 27 ++++++- .../shared/components/TabManagement.tsx | 11 ++- .../@settings/tabs/debug/DebugTab.tsx | 9 ++- .../@settings/tabs/profile/ProfileTab.tsx | 21 +++-- .../providers/local/OllamaModelInstaller.tsx | 13 +++- .../@settings/tabs/settings/SettingsTab.tsx | 78 +++++-------------- app/components/chat/BaseChat.tsx | 3 +- app/lib/stores/settings.ts | 65 +++++++++------- app/utils/debounce.ts | 22 +++--- 9 files changed, 133 insertions(+), 116 deletions(-) diff --git a/app/components/@settings/core/ControlPanel.tsx b/app/components/@settings/core/ControlPanel.tsx index c0e19035..0d90975c 100644 --- a/app/components/@settings/core/ControlPanel.tsx +++ b/app/components/@settings/core/ControlPanel.tsx @@ -263,6 +263,27 @@ export const ControlPanel = ({ open, onClose }: ControlPanelProps) => { }, }; + // Reset to default view when modal opens/closes + useEffect(() => { + if (!open) { + // Reset when closing + setActiveTab(null); + setLoadingTab(null); + setShowTabManagement(false); + } else { + // When opening, set to null to show the main view + setActiveTab(null); + } + }, [open]); + + // Handle closing + const handleClose = () => { + setActiveTab(null); + setLoadingTab(null); + setShowTabManagement(false); + onClose(); + }; + // Handlers const handleBack = () => { if (showTabManagement) { @@ -405,8 +426,8 @@ export const ControlPanel = ({ open, onClose }: ControlPanelProps) => { { {/* Close Button */}