Skip to content

Commit 75ec49b

Browse files
feat: providers list is now 2 columns
The providers list in the settings modal is now 2 columns allowing you to see more at once.
2 parents d4400a5 + bbd9a17 commit 75ec49b

File tree

1 file changed

+97
-72
lines changed

1 file changed

+97
-72
lines changed

app/components/settings/providers/ProvidersTab.tsx

Lines changed: 97 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,87 @@ export default function ProvidersTab() {
3434

3535
newFilteredProviders.sort((a, b) => a.name.localeCompare(b.name));
3636

37-
setFilteredProviders(newFilteredProviders);
37+
// Split providers into regular and URL-configurable
38+
const regular = newFilteredProviders.filter(p => !URL_CONFIGURABLE_PROVIDERS.includes(p.name));
39+
const urlConfigurable = newFilteredProviders.filter(p => URL_CONFIGURABLE_PROVIDERS.includes(p.name));
40+
41+
setFilteredProviders([...regular, ...urlConfigurable]);
3842
}, [providers, searchTerm, isLocalModel]);
3943

44+
const renderProviderCard = (provider: IProviderConfig) => {
45+
const envBaseUrlKey = providerBaseUrlEnvKeys[provider.name].baseUrlKey;
46+
const envBaseUrl = envBaseUrlKey ? import.meta.env[envBaseUrlKey] : undefined;
47+
const isUrlConfigurable = URL_CONFIGURABLE_PROVIDERS.includes(provider.name);
48+
49+
return (
50+
<div
51+
key={provider.name}
52+
className="flex flex-col provider-item hover:bg-bolt-elements-bg-depth-3 p-4 rounded-lg border border-bolt-elements-borderColor"
53+
>
54+
<div className="flex items-center justify-between mb-2">
55+
<div className="flex items-center gap-2">
56+
<img
57+
src={`/icons/${provider.name}.svg`}
58+
onError={(e) => {
59+
e.currentTarget.src = DefaultIcon;
60+
}}
61+
alt={`${provider.name} icon`}
62+
className="w-6 h-6 dark:invert"
63+
/>
64+
<span className="text-bolt-elements-textPrimary">{provider.name}</span>
65+
</div>
66+
<Switch
67+
className="ml-auto"
68+
checked={provider.settings.enabled}
69+
onCheckedChange={(enabled) => {
70+
updateProviderSettings(provider.name, { ...provider.settings, enabled });
71+
72+
if (enabled) {
73+
logStore.logProvider(`Provider ${provider.name} enabled`, { provider: provider.name });
74+
} else {
75+
logStore.logProvider(`Provider ${provider.name} disabled`, { provider: provider.name });
76+
}
77+
}}
78+
/>
79+
</div>
80+
{isUrlConfigurable && provider.settings.enabled && (
81+
<div className="mt-2">
82+
{envBaseUrl && (
83+
<label className="block text-xs text-bolt-elements-textSecondary text-green-300 mb-2">
84+
Set On (.env) : {envBaseUrl}
85+
</label>
86+
)}
87+
<label className="block text-sm text-bolt-elements-textSecondary mb-2">
88+
{envBaseUrl ? 'Override Base Url' : 'Base URL '}:{' '}
89+
</label>
90+
<input
91+
type="text"
92+
value={provider.settings.baseUrl || ''}
93+
onChange={(e) => {
94+
let newBaseUrl: string | undefined = e.target.value;
95+
96+
if (newBaseUrl && newBaseUrl.trim().length === 0) {
97+
newBaseUrl = undefined;
98+
}
99+
100+
updateProviderSettings(provider.name, { ...provider.settings, baseUrl: newBaseUrl });
101+
logStore.logProvider(`Base URL updated for ${provider.name}`, {
102+
provider: provider.name,
103+
baseUrl: newBaseUrl,
104+
});
105+
}}
106+
placeholder={`Enter ${provider.name} base URL`}
107+
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"
108+
/>
109+
</div>
110+
)}
111+
</div>
112+
);
113+
};
114+
115+
const regularProviders = filteredProviders.filter(p => !URL_CONFIGURABLE_PROVIDERS.includes(p.name));
116+
const urlConfigurableProviders = filteredProviders.filter(p => URL_CONFIGURABLE_PROVIDERS.includes(p.name));
117+
40118
return (
41119
<div className="p-4">
42120
<div className="flex mb-4">
@@ -48,77 +126,24 @@ export default function ProvidersTab() {
48126
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"
49127
/>
50128
</div>
51-
{filteredProviders.map((provider) => {
52-
const envBaseUrlKey = providerBaseUrlEnvKeys[provider.name].baseUrlKey;
53-
const envBaseUrl = envBaseUrlKey ? import.meta.env[envBaseUrlKey] : undefined;
54-
55-
return (
56-
<div
57-
key={provider.name}
58-
className="flex flex-col mb-2 provider-item hover:bg-bolt-elements-bg-depth-3 p-4 rounded-lg border border-bolt-elements-borderColor "
59-
>
60-
<div className="flex items-center justify-between mb-2">
61-
<div className="flex items-center gap-2">
62-
<img
63-
src={`/icons/${provider.name}.svg`} // Attempt to load the specific icon
64-
onError={(e) => {
65-
// Fallback to default icon on error
66-
e.currentTarget.src = DefaultIcon;
67-
}}
68-
alt={`${provider.name} icon`}
69-
className="w-6 h-6 dark:invert"
70-
/>
71-
<span className="text-bolt-elements-textPrimary">{provider.name}</span>
72-
</div>
73-
<Switch
74-
className="ml-auto"
75-
checked={provider.settings.enabled}
76-
onCheckedChange={(enabled) => {
77-
updateProviderSettings(provider.name, { ...provider.settings, enabled });
78-
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-
{/* Base URL input for configurable providers */}
88-
{URL_CONFIGURABLE_PROVIDERS.includes(provider.name) && provider.settings.enabled && (
89-
<div className="mt-2">
90-
{envBaseUrl && (
91-
<label className="block text-xs text-bolt-elements-textSecondary text-green-300 mb-2">
92-
Set On (.env) : {envBaseUrl}
93-
</label>
94-
)}
95-
<label className="block text-sm text-bolt-elements-textSecondary mb-2">
96-
{envBaseUrl ? 'Override Base Url' : 'Base URL '}:{' '}
97-
</label>
98-
<input
99-
type="text"
100-
value={provider.settings.baseUrl || ''}
101-
onChange={(e) => {
102-
let newBaseUrl: string | undefined = e.target.value;
103-
104-
if (newBaseUrl && newBaseUrl.trim().length === 0) {
105-
newBaseUrl = undefined;
106-
}
107-
108-
updateProviderSettings(provider.name, { ...provider.settings, baseUrl: newBaseUrl });
109-
logStore.logProvider(`Base URL updated for ${provider.name}`, {
110-
provider: provider.name,
111-
baseUrl: newBaseUrl,
112-
});
113-
}}
114-
placeholder={`Enter ${provider.name} base URL`}
115-
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"
116-
/>
117-
</div>
118-
)}
129+
130+
{/* Regular Providers Grid */}
131+
<div className="grid grid-cols-2 gap-4 mb-8">
132+
{regularProviders.map(renderProviderCard)}
133+
</div>
134+
135+
{/* URL Configurable Providers Section */}
136+
{urlConfigurableProviders.length > 0 && (
137+
<div className="mt-8">
138+
<h3 className="text-lg font-semibold mb-2 text-bolt-elements-textPrimary">Experimental Providers</h3>
139+
<p className="text-sm text-bolt-elements-textSecondary mb-4">
140+
These providers are experimental and allow you to run AI models locally or connect to your own infrastructure. They require additional setup but offer more flexibility.
141+
</p>
142+
<div className="space-y-4">
143+
{urlConfigurableProviders.map(renderProviderCard)}
119144
</div>
120-
);
121-
})}
145+
</div>
146+
)}
122147
</div>
123148
);
124-
}
149+
}

0 commit comments

Comments
 (0)