@@ -13,6 +13,10 @@ import {
13
13
import { modelSettings } from '@/lib/predefined'
14
14
import { fetchModels } from './models'
15
15
import { ExtensionManager } from '@/lib/extension'
16
+ import { fetch as fetchTauri } from '@tauri-apps/plugin-http'
17
+
18
+ // Ensure we have access to the global fetch for non-Tauri requests
19
+ const globalFetch = globalThis . fetch || window . fetch
16
20
17
21
export const getProviders = async ( ) : Promise < ModelProvider [ ] > => {
18
22
const engines = ! localStorage . getItem ( 'migration_completed' )
@@ -163,32 +167,80 @@ export const getProviders = async (): Promise<ModelProvider[]> => {
163
167
return runtimeProviders . concat ( builtinProviders as ModelProvider [ ] )
164
168
}
165
169
170
+ /**
171
+ * Checks if a URL is a local provider (localhost or 127.0.0.1) or uses specific ports (Ollama/LM Studio)
172
+ * @param url The URL to check
173
+ * @returns boolean indicating if it's a local provider or uses known local AI ports
174
+ */
175
+ const shouldBypassPreflightCheck = ( url : string ) : boolean => {
176
+ try {
177
+ const urlObj = new URL ( url )
178
+ const isLocalHost = urlObj . hostname === 'localhost' ||
179
+ urlObj . hostname === '127.0.0.1' ||
180
+ urlObj . hostname === '0.0.0.0'
181
+
182
+ // Check for specific ports used by local AI providers
183
+ const port = parseInt ( urlObj . port )
184
+ const isOllamaPort = port === 11434
185
+ const isLMStudioPort = port === 1234
186
+
187
+ return isLocalHost || isOllamaPort || isLMStudioPort
188
+ } catch {
189
+ return false
190
+ }
191
+ }
192
+
166
193
/**
167
194
* Fetches models from a provider's API endpoint
195
+ * Uses Tauri's HTTP client for local providers or when skipPreflight is true to bypass CORS issues
168
196
* @param provider The provider object containing base_url and api_key
197
+ * @param skipPreflight Whether to skip CORS preflight by using Tauri fetch (default: false)
169
198
* @returns Promise<string[]> Array of model IDs
170
199
*/
171
200
export const fetchModelsFromProvider = async (
172
- provider : ModelProvider
201
+ provider : ModelProvider ,
202
+ skipPreflight : boolean = false
173
203
) : Promise < string [ ] > => {
174
- if ( ! provider . base_url || ! provider . api_key ) {
175
- throw new Error ( 'Provider must have base_url and api_key configured' )
204
+ if ( ! provider . base_url ) {
205
+ throw new Error ( 'Provider must have base_url configured' )
176
206
}
177
207
208
+ // For local providers, we don't require API key as they often don't use authentication
209
+ const isPreflightCheckBypassed = shouldBypassPreflightCheck ( provider . base_url )
210
+
211
+ // Determine whether to use Tauri fetch to bypass CORS preflight
212
+ const shouldUseTauriFetch = isPreflightCheckBypassed || skipPreflight
213
+
178
214
try {
179
- const response = await fetch ( `${ provider . base_url } /models` , {
215
+ const headers : Record < string , string > = {
216
+ 'Content-Type' : 'application/json' ,
217
+ }
218
+
219
+ // Only add authentication headers if API key is provided
220
+ if ( provider . api_key ) {
221
+ headers [ 'x-api-key' ] = provider . api_key
222
+ headers [ 'Authorization' ] = `Bearer ${ provider . api_key } `
223
+ }
224
+
225
+ // Use Tauri's fetch for local providers or when skipPreflight is true to avoid CORS issues
226
+ // Use regular fetch for remote providers to maintain normal browser behavior
227
+ const fetchFunction = shouldUseTauriFetch ? fetchTauri : globalFetch
228
+ const response = await fetchFunction ( `${ provider . base_url } /models` , {
180
229
method : 'GET' ,
181
- headers : {
182
- 'x-api-key' : provider . api_key ,
183
- 'Authorization' : `Bearer ${ provider . api_key } ` ,
184
- 'Content-Type' : 'application/json' ,
185
- } ,
230
+ headers,
186
231
} )
187
232
188
233
if ( ! response . ok ) {
189
- throw new Error (
190
- `Failed to fetch models: ${ response . status } ${ response . statusText } `
191
- )
234
+ // Provide more specific error messages for local providers
235
+ if ( isPreflightCheckBypassed ) {
236
+ throw new Error (
237
+ `Failed to connect to local provider at ${ provider . base_url } . Please ensure the service is running and accessible.`
238
+ )
239
+ } else {
240
+ throw new Error (
241
+ `Failed to fetch models: ${ response . status } ${ response . statusText } `
242
+ )
243
+ }
192
244
}
193
245
194
246
const data = await response . json ( )
@@ -213,6 +265,16 @@ export const fetchModelsFromProvider = async (
213
265
}
214
266
} catch ( error ) {
215
267
console . error ( 'Error fetching models from provider:' , error )
268
+
269
+ // Provide helpful error messages for common local provider issues
270
+ if ( isPreflightCheckBypassed && error instanceof Error ) {
271
+ if ( error . message . includes ( 'fetch' ) ) {
272
+ throw new Error (
273
+ `Cannot connect to ${ provider . provider } at ${ provider . base_url } . Please check that the service is running and accessible.`
274
+ )
275
+ }
276
+ }
277
+
216
278
throw error
217
279
}
218
280
}
0 commit comments