Skip to content

Commit ba4e788

Browse files
feat: improved providers list style
made the list 2 columns wide and separate out the experimental providers
1 parent fce8999 commit ba4e788

File tree

1 file changed

+93
-54
lines changed

1 file changed

+93
-54
lines changed

app/components/settings/providers/ProvidersTab.tsx

Lines changed: 93 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,17 @@ import type { IProviderConfig } from '~/types/model';
66
import { logStore } from '~/lib/stores/logs';
77

88
// Import a default fallback icon
9-
import DefaultIcon from '/icons/Default.svg'; // Adjust the path as necessary
9+
import DefaultIcon from '/icons/Default.svg';
10+
11+
// List of advanced providers with correct casing
12+
const ADVANCED_PROVIDERS = ['Ollama', 'OpenAILike', 'LMStudio'];
1013

1114
export default function ProvidersTab() {
1215
const { providers, updateProviderSettings, isLocalModel } = useSettings();
1316
const [filteredProviders, setFilteredProviders] = useState<IProviderConfig[]>([]);
17+
const [advancedProviders, setAdvancedProviders] = useState<IProviderConfig[]>([]);
18+
const [regularProviders, setRegularProviders] = useState<IProviderConfig[]>([]);
1419

15-
// Load base URLs from cookies
1620
const [searchTerm, setSearchTerm] = useState('');
1721

1822
useEffect(() => {
@@ -31,11 +35,77 @@ export default function ProvidersTab() {
3135
newFilteredProviders = newFilteredProviders.filter((provider) => !LOCAL_PROVIDERS.includes(provider.name));
3236
}
3337

34-
newFilteredProviders.sort((a, b) => a.name.localeCompare(b.name));
38+
// Split providers into regular and advanced
39+
const regular = newFilteredProviders.filter(
40+
(provider) => !ADVANCED_PROVIDERS.includes(provider.name)
41+
);
42+
const advanced = newFilteredProviders.filter(
43+
(provider) => ADVANCED_PROVIDERS.includes(provider.name)
44+
);
45+
46+
// Sort advanced providers in specific order - OpenAILike at the end
47+
const advancedOrder = ['Ollama', 'LMStudio', 'OpenAILike'];
48+
advanced.sort((a, b) => {
49+
return advancedOrder.indexOf(a.name) - advancedOrder.indexOf(b.name);
50+
});
51+
52+
// Sort regular providers alphabetically
53+
regular.sort((a, b) => a.name.localeCompare(b.name));
3554

55+
setRegularProviders(regular);
56+
setAdvancedProviders(advanced);
3657
setFilteredProviders(newFilteredProviders);
3758
}, [providers, searchTerm, isLocalModel]);
3859

60+
const ProviderCard = ({ provider }: { provider: IProviderConfig }) => (
61+
<div className="flex flex-col provider-item hover:bg-bolt-elements-bg-depth-3 p-4 rounded-lg border border-bolt-elements-borderColor">
62+
<div className="flex items-center justify-between mb-2">
63+
<div className="flex items-center gap-2">
64+
<img
65+
src={`/icons/${provider.name}.svg`}
66+
onError={(e) => {
67+
e.currentTarget.src = DefaultIcon;
68+
}}
69+
alt={`${provider.name} icon`}
70+
className="w-6 h-6 dark:invert"
71+
/>
72+
<span className="text-bolt-elements-textPrimary">{provider.name}</span>
73+
</div>
74+
<Switch
75+
className="ml-auto"
76+
checked={provider.settings.enabled}
77+
onCheckedChange={(enabled) => {
78+
updateProviderSettings(provider.name, { ...provider.settings, enabled });
79+
if (enabled) {
80+
logStore.logProvider(`Provider ${provider.name} enabled`, { provider: provider.name });
81+
} else {
82+
logStore.logProvider(`Provider ${provider.name} disabled`, { provider: provider.name });
83+
}
84+
}}
85+
/>
86+
</div>
87+
{URL_CONFIGURABLE_PROVIDERS.includes(provider.name) && provider.settings.enabled && (
88+
<div className="mt-2">
89+
<label className="block text-sm text-bolt-elements-textSecondary mb-1">Base URL:</label>
90+
<input
91+
type="text"
92+
value={provider.settings.baseUrl || ''}
93+
onChange={(e) => {
94+
const newBaseUrl = e.target.value;
95+
updateProviderSettings(provider.name, { ...provider.settings, baseUrl: newBaseUrl });
96+
logStore.logProvider(`Base URL updated for ${provider.name}`, {
97+
provider: provider.name,
98+
baseUrl: newBaseUrl,
99+
});
100+
}}
101+
placeholder={`Enter ${provider.name} base URL`}
102+
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"
103+
/>
104+
</div>
105+
)}
106+
</div>
107+
);
108+
39109
return (
40110
<div className="p-4">
41111
<div className="flex mb-4">
@@ -47,60 +117,29 @@ export default function ProvidersTab() {
47117
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"
48118
/>
49119
</div>
50-
{filteredProviders.map((provider) => (
51-
<div
52-
key={provider.name}
53-
className="flex flex-col mb-2 provider-item hover:bg-bolt-elements-bg-depth-3 p-4 rounded-lg border border-bolt-elements-borderColor "
54-
>
55-
<div className="flex items-center justify-between mb-2">
56-
<div className="flex items-center gap-2">
57-
<img
58-
src={`/icons/${provider.name}.svg`} // Attempt to load the specific icon
59-
onError={(e) => {
60-
// Fallback to default icon on error
61-
e.currentTarget.src = DefaultIcon;
62-
}}
63-
alt={`${provider.name} icon`}
64-
className="w-6 h-6 dark:invert"
65-
/>
66-
<span className="text-bolt-elements-textPrimary">{provider.name}</span>
67-
</div>
68-
<Switch
69-
className="ml-auto"
70-
checked={provider.settings.enabled}
71-
onCheckedChange={(enabled) => {
72-
updateProviderSettings(provider.name, { ...provider.settings, enabled });
73120

74-
if (enabled) {
75-
logStore.logProvider(`Provider ${provider.name} enabled`, { provider: provider.name });
76-
} else {
77-
logStore.logProvider(`Provider ${provider.name} disabled`, { provider: provider.name });
78-
}
79-
}}
80-
/>
121+
{/* Regular Providers Grid */}
122+
<div className="grid grid-cols-2 gap-4 mb-8">
123+
{regularProviders.map((provider) => (
124+
<ProviderCard key={provider.name} provider={provider} />
125+
))}
126+
</div>
127+
128+
{/* Advanced Providers Section */}
129+
{advancedProviders.length > 0 && (
130+
<div className="mb-4 border-t border-bolt-elements-borderColor pt-4">
131+
<h3 className="text-bolt-elements-textSecondary text-lg font-medium mb-2">Experimental Providers</h3>
132+
<p className="text-bolt-elements-textSecondary mb-6">
133+
These providers are experimental features that allow you to run AI models locally or connect to your own infrastructure.
134+
They require additional setup but offer more flexibility for advanced users.
135+
</p>
136+
<div className="grid grid-cols-2 gap-4">
137+
{advancedProviders.map((provider) => (
138+
<ProviderCard key={provider.name} provider={provider} />
139+
))}
81140
</div>
82-
{/* Base URL input for configurable providers */}
83-
{URL_CONFIGURABLE_PROVIDERS.includes(provider.name) && provider.settings.enabled && (
84-
<div className="mt-2">
85-
<label className="block text-sm text-bolt-elements-textSecondary mb-1">Base URL:</label>
86-
<input
87-
type="text"
88-
value={provider.settings.baseUrl || ''}
89-
onChange={(e) => {
90-
const newBaseUrl = e.target.value;
91-
updateProviderSettings(provider.name, { ...provider.settings, baseUrl: newBaseUrl });
92-
logStore.logProvider(`Base URL updated for ${provider.name}`, {
93-
provider: provider.name,
94-
baseUrl: newBaseUrl,
95-
});
96-
}}
97-
placeholder={`Enter ${provider.name} base URL`}
98-
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"
99-
/>
100-
</div>
101-
)}
102141
</div>
103-
))}
142+
)}
104143
</div>
105144
);
106145
}

0 commit comments

Comments
 (0)