@@ -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