codacus commited on
Commit
26a3bcf
·
2 Parent(s): e74d6ca 283eb22

Merge branch 'env-file-fix-old' into env-file-fix

Browse files
app/commit.json CHANGED
@@ -1 +1 @@
1
- { "commit": "e064803955604198c6aac7b257efd0ad8503cb73", "version": "0.0.3" }
 
1
+ { "commit": "e74d6cafb53f6eb2bb80c32014b27ac0aa56e7fe" }
app/components/settings/debug/DebugTab.tsx CHANGED
@@ -2,6 +2,7 @@ import React, { useCallback, useEffect, useState } from 'react';
2
  import { useSettings } from '~/lib/hooks/useSettings';
3
  import commit from '~/commit.json';
4
  import { toast } from 'react-toastify';
 
5
 
6
  interface ProviderStatus {
7
  name: string;
@@ -236,7 +237,7 @@ const checkProviderStatus = async (url: string | null, providerName: string): Pr
236
  }
237
 
238
  // Try different endpoints based on provider
239
- const checkUrls = [`${url}/api/health`, `${url}/v1/models`];
240
  console.log(`[Debug] Checking additional endpoints:`, checkUrls);
241
 
242
  const results = await Promise.all(
@@ -321,14 +322,16 @@ export default function DebugTab() {
321
  .filter(([, provider]) => LOCAL_PROVIDERS.includes(provider.name))
322
  .map(async ([, provider]) => {
323
  const envVarName =
324
- provider.name.toLowerCase() === 'ollama'
325
- ? 'OLLAMA_API_BASE_URL'
326
- : provider.name.toLowerCase() === 'lmstudio'
327
- ? 'LMSTUDIO_API_BASE_URL'
328
- : `REACT_APP_${provider.name.toUpperCase()}_URL`;
329
 
330
  // Access environment variables through import.meta.env
331
- const url = import.meta.env[envVarName] || provider.settings.baseUrl || null; // Ensure baseUrl is used
 
 
 
 
 
 
332
  console.log(`[Debug] Using URL for ${provider.name}:`, url, `(from ${envVarName})`);
333
 
334
  const status = await checkProviderStatus(url, provider.name);
 
2
  import { useSettings } from '~/lib/hooks/useSettings';
3
  import commit from '~/commit.json';
4
  import { toast } from 'react-toastify';
5
+ import { providerBaseUrlEnvKeys } from '~/utils/constants';
6
 
7
  interface ProviderStatus {
8
  name: string;
 
237
  }
238
 
239
  // Try different endpoints based on provider
240
+ const checkUrls = [`${url}/api/health`, url.endsWith('v1') ? `${url}/models` : `${url}/v1/models`];
241
  console.log(`[Debug] Checking additional endpoints:`, checkUrls);
242
 
243
  const results = await Promise.all(
 
322
  .filter(([, provider]) => LOCAL_PROVIDERS.includes(provider.name))
323
  .map(async ([, provider]) => {
324
  const envVarName =
325
+ providerBaseUrlEnvKeys[provider.name].baseUrlKey || `REACT_APP_${provider.name.toUpperCase()}_URL`;
 
 
 
 
326
 
327
  // Access environment variables through import.meta.env
328
+ let settingsUrl = provider.settings.baseUrl;
329
+
330
+ if (settingsUrl && settingsUrl.trim().length === 0) {
331
+ settingsUrl = undefined;
332
+ }
333
+
334
+ const url = settingsUrl || import.meta.env[envVarName] || null; // Ensure baseUrl is used
335
  console.log(`[Debug] Using URL for ${provider.name}:`, url, `(from ${envVarName})`);
336
 
337
  const status = await checkProviderStatus(url, provider.name);
app/components/settings/providers/ProvidersTab.tsx CHANGED
@@ -7,6 +7,7 @@ import { logStore } from '~/lib/stores/logs';
7
 
8
  // Import a default fallback icon
9
  import DefaultIcon from '/icons/Default.svg'; // Adjust the path as necessary
 
10
 
11
  export default function ProvidersTab() {
12
  const { providers, updateProviderSettings, isLocalModel } = useSettings();
@@ -47,65 +48,77 @@ export default function ProvidersTab() {
47
  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"
48
  />
49
  </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 });
73
 
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
- />
81
- </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
- let newBaseUrl: string | undefined = e.target.value;
 
 
 
 
 
 
91
 
92
- if (newBaseUrl && newBaseUrl.trim().length === 0) {
93
- newBaseUrl = undefined;
 
 
94
  }
95
-
96
- updateProviderSettings(provider.name, { ...provider.settings, baseUrl: newBaseUrl });
97
- logStore.logProvider(`Base URL updated for ${provider.name}`, {
98
- provider: provider.name,
99
- baseUrl: newBaseUrl,
100
- });
101
  }}
102
- placeholder={`Enter ${provider.name} base URL`}
103
- 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"
104
  />
105
  </div>
106
- )}
107
- </div>
108
- ))}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
109
  </div>
110
  );
111
  }
 
7
 
8
  // Import a default fallback icon
9
  import DefaultIcon from '/icons/Default.svg'; // Adjust the path as necessary
10
+ import { providerBaseUrlEnvKeys } from '~/utils/constants';
11
 
12
  export default function ProvidersTab() {
13
  const { providers, updateProviderSettings, isLocalModel } = useSettings();
 
48
  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"
49
  />
50
  </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
+ )}
119
+ </div>
120
+ );
121
+ })}
122
  </div>
123
  );
124
  }
app/lib/.server/llm/api-key.ts CHANGED
@@ -4,6 +4,7 @@
4
  */
5
  import { env } from 'node:process';
6
  import type { IProviderSetting } from '~/types/model';
 
7
 
8
  export function getAPIKey(cloudflareEnv: Env, provider: string, userApiKeys?: Record<string, string>) {
9
  /**
@@ -16,7 +17,20 @@ export function getAPIKey(cloudflareEnv: Env, provider: string, userApiKeys?: Re
16
  return userApiKeys[provider];
17
  }
18
 
19
- // Fall back to environment variables
 
 
 
 
 
 
 
 
 
 
 
 
 
20
  switch (provider) {
21
  case 'Anthropic':
22
  return env.ANTHROPIC_API_KEY || cloudflareEnv.ANTHROPIC_API_KEY;
@@ -52,6 +66,19 @@ export function getAPIKey(cloudflareEnv: Env, provider: string, userApiKeys?: Re
52
  }
53
 
54
  export function getBaseURL(cloudflareEnv: Env, provider: string, providerSettings?: Record<string, IProviderSetting>) {
 
 
 
 
 
 
 
 
 
 
 
 
 
55
  let settingBaseUrl = providerSettings?.[provider].baseUrl;
56
 
57
  if (settingBaseUrl && settingBaseUrl.length == 0) {
 
4
  */
5
  import { env } from 'node:process';
6
  import type { IProviderSetting } from '~/types/model';
7
+ import { getProviderBaseUrlAndKey } from '~/utils/constants';
8
 
9
  export function getAPIKey(cloudflareEnv: Env, provider: string, userApiKeys?: Record<string, string>) {
10
  /**
 
17
  return userApiKeys[provider];
18
  }
19
 
20
+ const { apiKey } = getProviderBaseUrlAndKey({
21
+ provider,
22
+ apiKeys: userApiKeys,
23
+ providerSettings: undefined,
24
+ serverEnv: cloudflareEnv as any,
25
+ defaultBaseUrlKey: '',
26
+ defaultApiTokenKey: '',
27
+ });
28
+
29
+ if (apiKey) {
30
+ return apiKey;
31
+ }
32
+
33
+ // Fall back to hardcoded environment variables names
34
  switch (provider) {
35
  case 'Anthropic':
36
  return env.ANTHROPIC_API_KEY || cloudflareEnv.ANTHROPIC_API_KEY;
 
66
  }
67
 
68
  export function getBaseURL(cloudflareEnv: Env, provider: string, providerSettings?: Record<string, IProviderSetting>) {
69
+ const { baseUrl } = getProviderBaseUrlAndKey({
70
+ provider,
71
+ apiKeys: {},
72
+ providerSettings,
73
+ serverEnv: cloudflareEnv as any,
74
+ defaultBaseUrlKey: '',
75
+ defaultApiTokenKey: '',
76
+ });
77
+
78
+ if (baseUrl) {
79
+ return baseUrl;
80
+ }
81
+
82
  let settingBaseUrl = providerSettings?.[provider].baseUrl;
83
 
84
  if (settingBaseUrl && settingBaseUrl.length == 0) {
app/lib/hooks/useEditChatDescription.ts CHANGED
@@ -92,6 +92,7 @@ export function useEditChatDescription({
92
  }
93
 
94
  const lengthValid = trimmedDesc.length > 0 && trimmedDesc.length <= 100;
 
95
  // Allow letters, numbers, spaces, and common punctuation but exclude characters that could cause issues
96
  const characterValid = /^[a-zA-Z0-9\s\-_.,!?()[\]{}'"]+$/.test(trimmedDesc);
97
 
 
92
  }
93
 
94
  const lengthValid = trimmedDesc.length > 0 && trimmedDesc.length <= 100;
95
+
96
  // Allow letters, numbers, spaces, and common punctuation but exclude characters that could cause issues
97
  const characterValid = /^[a-zA-Z0-9\s\-_.,!?()[\]{}'"]+$/.test(trimmedDesc);
98
 
app/types/model.ts CHANGED
@@ -4,6 +4,7 @@ export type ProviderInfo = {
4
  staticModels: ModelInfo[];
5
  name: string;
6
  getDynamicModels?: (
 
7
  apiKeys?: Record<string, string>,
8
  providerSettings?: IProviderSetting,
9
  serverEnv?: Record<string, string>,
 
4
  staticModels: ModelInfo[];
5
  name: string;
6
  getDynamicModels?: (
7
+ providerName: string,
8
  apiKeys?: Record<string, string>,
9
  providerSettings?: IProviderSetting,
10
  serverEnv?: Record<string, string>,
app/utils/constants.ts CHANGED
@@ -318,6 +318,83 @@ const PROVIDER_LIST: ProviderInfo[] = [
318
  },
319
  ];
320
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
321
  export const DEFAULT_PROVIDER = PROVIDER_LIST[0];
322
 
323
  const staticModels: ModelInfo[] = PROVIDER_LIST.map((p) => p.staticModels).flat();
@@ -337,7 +414,7 @@ export async function getModelList(options: {
337
  await Promise.all(
338
  PROVIDER_LIST.filter(
339
  (p): p is ProviderInfo & { getDynamicModels: () => Promise<ModelInfo[]> } => !!p.getDynamicModels,
340
- ).map((p) => p.getDynamicModels(apiKeys, providerSettings?.[p.name], serverEnv)),
341
  )
342
  ).flat(),
343
  ...staticModels,
@@ -347,35 +424,26 @@ export async function getModelList(options: {
347
  }
348
 
349
  async function getTogetherModels(
 
350
  apiKeys?: Record<string, string>,
351
  settings?: IProviderSetting,
352
  serverEnv: Record<string, string> = {},
353
  ): Promise<ModelInfo[]> {
354
  try {
355
- let settingsBaseUrl = settings?.baseUrl;
356
-
357
- if (settingsBaseUrl && settingsBaseUrl.length == 0) {
358
- settingsBaseUrl = undefined;
359
- }
360
-
361
- const baseUrl =
362
- settingsBaseUrl ||
363
- serverEnv?.TOGETHER_API_BASE_URL ||
364
- process.env.TOGETHER_API_BASE_URL ||
365
- import.meta.env.TOGETHER_API_BASE_URL ||
366
- '';
367
- const provider = 'Together';
368
 
369
  if (!baseUrl) {
370
  return [];
371
  }
372
 
373
- let apiKey = import.meta.env.OPENAI_LIKE_API_KEY ?? '';
374
-
375
- if (apiKeys && apiKeys[provider]) {
376
- apiKey = apiKeys[provider];
377
- }
378
-
379
  if (!apiKey) {
380
  return [];
381
  }
@@ -393,7 +461,7 @@ async function getTogetherModels(
393
  label: `${m.display_name} - in:$${m.pricing.input.toFixed(
394
  2,
395
  )} out:$${m.pricing.output.toFixed(2)} - context ${Math.floor(m.context_length / 1000)}k`,
396
- provider,
397
  maxTokenAllowed: 8000,
398
  }));
399
  } catch (e) {
@@ -402,39 +470,40 @@ async function getTogetherModels(
402
  }
403
  }
404
 
405
- const getOllamaBaseUrl = (settings?: IProviderSetting, serverEnv: Record<string, string> = {}) => {
406
- let settingsBaseUrl = settings?.baseUrl;
407
-
408
- if (settingsBaseUrl && settingsBaseUrl.length == 0) {
409
- settingsBaseUrl = undefined;
410
- }
411
-
412
- const defaultBaseUrl =
413
- settings?.baseUrl ||
414
- serverEnv?.OLLAMA_API_BASE_URL ||
415
- process.env.OLLAMA_API_BASE_URL ||
416
- import.meta.env.OLLAMA_API_BASE_URL ||
417
- 'http://localhost:11434';
418
 
419
  // Check if we're in the browser
420
  if (typeof window !== 'undefined') {
421
  // Frontend always uses localhost
422
- return defaultBaseUrl;
423
  }
424
 
425
  // Backend: Check if we're running in Docker
426
  const isDocker = process.env.RUNNING_IN_DOCKER === 'true';
427
 
428
- return isDocker ? defaultBaseUrl.replace('localhost', 'host.docker.internal') : defaultBaseUrl;
429
  };
430
 
431
  async function getOllamaModels(
432
- apiKeys?: Record<string, string>,
 
433
  settings?: IProviderSetting,
434
  serverEnv: Record<string, string> = {},
435
  ): Promise<ModelInfo[]> {
436
  try {
437
- const baseUrl = getOllamaBaseUrl(settings, serverEnv);
 
 
 
 
 
438
  const response = await fetch(`${baseUrl}/api/tags`);
439
  const data = (await response.json()) as OllamaApiResponse;
440
 
@@ -453,34 +522,25 @@ async function getOllamaModels(
453
  }
454
 
455
  async function getOpenAILikeModels(
 
456
  apiKeys?: Record<string, string>,
457
  settings?: IProviderSetting,
458
  serverEnv: Record<string, string> = {},
459
  ): Promise<ModelInfo[]> {
460
  try {
461
- let settingsBaseUrl = settings?.baseUrl;
462
-
463
- if (settingsBaseUrl && settingsBaseUrl.length == 0) {
464
- settingsBaseUrl = undefined;
465
- }
466
-
467
- const baseUrl =
468
- settingsBaseUrl ||
469
- serverEnv.OPENAI_LIKE_API_BASE_URL ||
470
- process.env.OPENAI_LIKE_API_BASE_URL ||
471
- import.meta.env.OPENAI_LIKE_API_BASE_URL ||
472
- '';
473
 
474
  if (!baseUrl) {
475
  return [];
476
  }
477
 
478
- let apiKey = '';
479
-
480
- if (apiKeys && apiKeys.OpenAILike) {
481
- apiKey = apiKeys.OpenAILike;
482
- }
483
-
484
  const response = await fetch(`${baseUrl}/models`, {
485
  headers: {
486
  Authorization: `Bearer ${apiKey}`,
@@ -491,7 +551,7 @@ async function getOpenAILikeModels(
491
  return res.data.map((model: any) => ({
492
  name: model.id,
493
  label: model.id,
494
- provider: 'OpenAILike',
495
  }));
496
  } catch (e) {
497
  console.error('Error getting OpenAILike models:', e);
@@ -533,23 +593,25 @@ async function getOpenRouterModels(): Promise<ModelInfo[]> {
533
  }
534
 
535
  async function getLMStudioModels(
536
- _apiKeys?: Record<string, string>,
 
537
  settings?: IProviderSetting,
538
  serverEnv: Record<string, string> = {},
539
  ): Promise<ModelInfo[]> {
540
  try {
541
- let settingsBaseUrl = settings?.baseUrl;
 
 
 
 
 
 
 
542
 
543
- if (settingsBaseUrl && settingsBaseUrl.length == 0) {
544
- settingsBaseUrl = undefined;
545
  }
546
 
547
- const baseUrl =
548
- settingsBaseUrl ||
549
- serverEnv.LMSTUDIO_API_BASE_URL ||
550
- process.env.LMSTUDIO_API_BASE_URL ||
551
- import.meta.env.LMSTUDIO_API_BASE_URL ||
552
- 'http://localhost:1234';
553
  const response = await fetch(`${baseUrl}/v1/models`);
554
  const data = (await response.json()) as any;
555
 
@@ -594,7 +656,7 @@ async function initializeModelList(options: {
594
  await Promise.all(
595
  PROVIDER_LIST.filter(
596
  (p): p is ProviderInfo & { getDynamicModels: () => Promise<ModelInfo[]> } => !!p.getDynamicModels,
597
- ).map((p) => p.getDynamicModels(apiKeys, providerSettings?.[p.name], env)),
598
  )
599
  ).flat(),
600
  ...staticModels,
 
318
  },
319
  ];
320
 
321
+ export const providerBaseUrlEnvKeys: Record<string, { baseUrlKey?: string; apiTokenKey?: string }> = {
322
+ Anthropic: {
323
+ apiTokenKey: 'ANTHROPIC_API_KEY',
324
+ },
325
+ OpenAI: {
326
+ apiTokenKey: 'OPENAI_API_KEY',
327
+ },
328
+ Groq: {
329
+ apiTokenKey: 'GROQ_API_KEY',
330
+ },
331
+ HuggingFace: {
332
+ apiTokenKey: 'HuggingFace_API_KEY',
333
+ },
334
+ OpenRouter: {
335
+ apiTokenKey: 'OPEN_ROUTER_API_KEY',
336
+ },
337
+ Google: {
338
+ apiTokenKey: 'GOOGLE_GENERATIVE_AI_API_KEY',
339
+ },
340
+ OpenAILike: {
341
+ baseUrlKey: 'OPENAI_LIKE_API_BASE_URL',
342
+ apiTokenKey: 'OPENAI_LIKE_API_KEY',
343
+ },
344
+ Together: {
345
+ baseUrlKey: 'TOGETHER_API_BASE_URL',
346
+ apiTokenKey: 'TOGETHER_API_KEY',
347
+ },
348
+ Deepseek: {
349
+ apiTokenKey: 'DEEPSEEK_API_KEY',
350
+ },
351
+ Mistral: {
352
+ apiTokenKey: 'MISTRAL_API_KEY',
353
+ },
354
+ LMStudio: {
355
+ baseUrlKey: 'LMSTUDIO_API_BASE_URL',
356
+ },
357
+ xAI: {
358
+ apiTokenKey: 'XAI_API_KEY',
359
+ },
360
+ Cohere: {
361
+ apiTokenKey: 'COHERE_API_KEY',
362
+ },
363
+ Perplexity: {
364
+ apiTokenKey: 'PERPLEXITY_API_KEY',
365
+ },
366
+ Ollama: {
367
+ baseUrlKey: 'OLLAMA_API_BASE_URL',
368
+ },
369
+ };
370
+
371
+ export const getProviderBaseUrlAndKey = (options: {
372
+ provider: string;
373
+ apiKeys?: Record<string, string>;
374
+ providerSettings?: IProviderSetting;
375
+ serverEnv?: Record<string, string>;
376
+ defaultBaseUrlKey: string;
377
+ defaultApiTokenKey: string;
378
+ }) => {
379
+ const { provider, apiKeys, providerSettings, serverEnv, defaultBaseUrlKey, defaultApiTokenKey } = options;
380
+ let settingsBaseUrl = providerSettings?.baseUrl;
381
+
382
+ if (settingsBaseUrl && settingsBaseUrl.length == 0) {
383
+ settingsBaseUrl = undefined;
384
+ }
385
+
386
+ const baseUrlKey = providerBaseUrlEnvKeys[provider]?.baseUrlKey || defaultBaseUrlKey;
387
+ const baseUrl = settingsBaseUrl || serverEnv?.[baseUrlKey] || process.env[baseUrlKey] || import.meta.env[baseUrlKey];
388
+
389
+ const apiTokenKey = providerBaseUrlEnvKeys[provider]?.apiTokenKey || defaultApiTokenKey;
390
+ const apiKey =
391
+ apiKeys?.[provider] || serverEnv?.[apiTokenKey] || process.env[apiTokenKey] || import.meta.env[apiTokenKey];
392
+
393
+ return {
394
+ baseUrl,
395
+ apiKey,
396
+ };
397
+ };
398
  export const DEFAULT_PROVIDER = PROVIDER_LIST[0];
399
 
400
  const staticModels: ModelInfo[] = PROVIDER_LIST.map((p) => p.staticModels).flat();
 
414
  await Promise.all(
415
  PROVIDER_LIST.filter(
416
  (p): p is ProviderInfo & { getDynamicModels: () => Promise<ModelInfo[]> } => !!p.getDynamicModels,
417
+ ).map((p) => p.getDynamicModels(p.name, apiKeys, providerSettings?.[p.name], serverEnv)),
418
  )
419
  ).flat(),
420
  ...staticModels,
 
424
  }
425
 
426
  async function getTogetherModels(
427
+ name: string,
428
  apiKeys?: Record<string, string>,
429
  settings?: IProviderSetting,
430
  serverEnv: Record<string, string> = {},
431
  ): Promise<ModelInfo[]> {
432
  try {
433
+ const { baseUrl, apiKey } = getProviderBaseUrlAndKey({
434
+ provider: name,
435
+ apiKeys,
436
+ providerSettings: settings,
437
+ serverEnv,
438
+ defaultBaseUrlKey: 'TOGETHER_API_BASE_URL',
439
+ defaultApiTokenKey: 'TOGETHER_API_KEY',
440
+ });
441
+ console.log({ baseUrl, apiKey });
 
 
 
 
442
 
443
  if (!baseUrl) {
444
  return [];
445
  }
446
 
 
 
 
 
 
 
447
  if (!apiKey) {
448
  return [];
449
  }
 
461
  label: `${m.display_name} - in:$${m.pricing.input.toFixed(
462
  2,
463
  )} out:$${m.pricing.output.toFixed(2)} - context ${Math.floor(m.context_length / 1000)}k`,
464
+ provider: name,
465
  maxTokenAllowed: 8000,
466
  }));
467
  } catch (e) {
 
470
  }
471
  }
472
 
473
+ const getOllamaBaseUrl = (name: string, settings?: IProviderSetting, serverEnv: Record<string, string> = {}) => {
474
+ const { baseUrl } = getProviderBaseUrlAndKey({
475
+ provider: name,
476
+ providerSettings: settings,
477
+ serverEnv,
478
+ defaultBaseUrlKey: 'OLLAMA_API_BASE_URL',
479
+ defaultApiTokenKey: '',
480
+ });
 
 
 
 
 
481
 
482
  // Check if we're in the browser
483
  if (typeof window !== 'undefined') {
484
  // Frontend always uses localhost
485
+ return baseUrl;
486
  }
487
 
488
  // Backend: Check if we're running in Docker
489
  const isDocker = process.env.RUNNING_IN_DOCKER === 'true';
490
 
491
+ return isDocker ? baseUrl.replace('localhost', 'host.docker.internal') : baseUrl;
492
  };
493
 
494
  async function getOllamaModels(
495
+ name: string,
496
+ _apiKeys?: Record<string, string>,
497
  settings?: IProviderSetting,
498
  serverEnv: Record<string, string> = {},
499
  ): Promise<ModelInfo[]> {
500
  try {
501
+ const baseUrl = getOllamaBaseUrl(name, settings, serverEnv);
502
+
503
+ if (!baseUrl) {
504
+ return [];
505
+ }
506
+
507
  const response = await fetch(`${baseUrl}/api/tags`);
508
  const data = (await response.json()) as OllamaApiResponse;
509
 
 
522
  }
523
 
524
  async function getOpenAILikeModels(
525
+ name: string,
526
  apiKeys?: Record<string, string>,
527
  settings?: IProviderSetting,
528
  serverEnv: Record<string, string> = {},
529
  ): Promise<ModelInfo[]> {
530
  try {
531
+ const { baseUrl, apiKey } = getProviderBaseUrlAndKey({
532
+ provider: name,
533
+ apiKeys,
534
+ providerSettings: settings,
535
+ serverEnv,
536
+ defaultBaseUrlKey: 'OPENAI_LIKE_API_BASE_URL',
537
+ defaultApiTokenKey: 'OPENAI_LIKE_API_KEY',
538
+ });
 
 
 
 
539
 
540
  if (!baseUrl) {
541
  return [];
542
  }
543
 
 
 
 
 
 
 
544
  const response = await fetch(`${baseUrl}/models`, {
545
  headers: {
546
  Authorization: `Bearer ${apiKey}`,
 
551
  return res.data.map((model: any) => ({
552
  name: model.id,
553
  label: model.id,
554
+ provider: name,
555
  }));
556
  } catch (e) {
557
  console.error('Error getting OpenAILike models:', e);
 
593
  }
594
 
595
  async function getLMStudioModels(
596
+ name: string,
597
+ apiKeys?: Record<string, string>,
598
  settings?: IProviderSetting,
599
  serverEnv: Record<string, string> = {},
600
  ): Promise<ModelInfo[]> {
601
  try {
602
+ const { baseUrl } = getProviderBaseUrlAndKey({
603
+ provider: name,
604
+ apiKeys,
605
+ providerSettings: settings,
606
+ serverEnv,
607
+ defaultBaseUrlKey: 'LMSTUDIO_API_BASE_URL',
608
+ defaultApiTokenKey: '',
609
+ });
610
 
611
+ if (!baseUrl) {
612
+ return [];
613
  }
614
 
 
 
 
 
 
 
615
  const response = await fetch(`${baseUrl}/v1/models`);
616
  const data = (await response.json()) as any;
617
 
 
656
  await Promise.all(
657
  PROVIDER_LIST.filter(
658
  (p): p is ProviderInfo & { getDynamicModels: () => Promise<ModelInfo[]> } => !!p.getDynamicModels,
659
+ ).map((p) => p.getDynamicModels(p.name, apiKeys, providerSettings?.[p.name], env)),
660
  )
661
  ).flat(),
662
  ...staticModels,
vite.config.ts CHANGED
@@ -28,7 +28,7 @@ export default defineConfig((config) => {
28
  chrome129IssuePlugin(),
29
  config.mode === 'production' && optimizeCssModules({ apply: 'build' }),
30
  ],
31
- envPrefix: ["VITE_", "OPENAI_LIKE_API_", "OLLAMA_API_BASE_URL", "LMSTUDIO_API_BASE_URL","TOGETHER_API_BASE_URL"],
32
  css: {
33
  preprocessorOptions: {
34
  scss: {
 
28
  chrome129IssuePlugin(),
29
  config.mode === 'production' && optimizeCssModules({ apply: 'build' }),
30
  ],
31
+ envPrefix: ["VITE_", "OPENAI_LIKE_API_", "OLLAMA_API_BASE_URL", "LMSTUDIO_API_BASE_URL","TOGETHER_API_"],
32
  css: {
33
  preprocessorOptions: {
34
  scss: {