Skip to content

Commit d34852c

Browse files
authored
Merge branch 'stackblitz-labs:main' into main
2 parents b32c408 + cb3c536 commit d34852c

File tree

2 files changed

+244
-1
lines changed

2 files changed

+244
-1
lines changed

app/lib/stores/settings.ts

Lines changed: 134 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,37 @@ export const shortcutsStore = map<Shortcuts>({
5252

5353
// Create a single key for provider settings
5454
const PROVIDER_SETTINGS_KEY = 'provider_settings';
55+
const AUTO_ENABLED_KEY = 'auto_enabled_providers';
5556

5657
// Add this helper function at the top of the file
5758
const isBrowser = typeof window !== 'undefined';
5859

59-
// Initialize provider settings from both localStorage and defaults
60+
// Interface for configured provider info from server
61+
interface ConfiguredProvider {
62+
name: string;
63+
isConfigured: boolean;
64+
configMethod: 'environment' | 'none';
65+
}
66+
67+
// Fetch configured providers from server
68+
const fetchConfiguredProviders = async (): Promise<ConfiguredProvider[]> => {
69+
try {
70+
const response = await fetch('/api/configured-providers');
71+
72+
if (!response.ok) {
73+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
74+
}
75+
76+
const data = (await response.json()) as { providers?: ConfiguredProvider[] };
77+
78+
return data.providers || [];
79+
} catch (error) {
80+
console.error('Error fetching configured providers:', error);
81+
return [];
82+
}
83+
};
84+
85+
// Initialize provider settings from both localStorage and server-detected configuration
6086
const getInitialProviderSettings = (): ProviderSetting => {
6187
const initialSettings: ProviderSetting = {};
6288

@@ -92,8 +118,84 @@ const getInitialProviderSettings = (): ProviderSetting => {
92118
return initialSettings;
93119
};
94120

121+
// Auto-enable providers that are configured on the server
122+
const autoEnableConfiguredProviders = async () => {
123+
if (!isBrowser) {
124+
return;
125+
}
126+
127+
try {
128+
const configuredProviders = await fetchConfiguredProviders();
129+
const currentSettings = providersStore.get();
130+
const savedSettings = localStorage.getItem(PROVIDER_SETTINGS_KEY);
131+
const autoEnabledProviders = localStorage.getItem(AUTO_ENABLED_KEY);
132+
133+
// Track which providers were auto-enabled to avoid overriding user preferences
134+
const previouslyAutoEnabled = autoEnabledProviders ? JSON.parse(autoEnabledProviders) : [];
135+
const newlyAutoEnabled: string[] = [];
136+
137+
let hasChanges = false;
138+
139+
configuredProviders.forEach(({ name, isConfigured, configMethod }) => {
140+
if (isConfigured && configMethod === 'environment' && LOCAL_PROVIDERS.includes(name)) {
141+
const currentProvider = currentSettings[name];
142+
143+
if (currentProvider) {
144+
/*
145+
* Only auto-enable if:
146+
* 1. Provider is not already enabled, AND
147+
* 2. Either we haven't saved settings yet (first time) OR provider was previously auto-enabled
148+
*/
149+
const hasUserSettings = savedSettings !== null;
150+
const wasAutoEnabled = previouslyAutoEnabled.includes(name);
151+
const shouldAutoEnable = !currentProvider.settings.enabled && (!hasUserSettings || wasAutoEnabled);
152+
153+
if (shouldAutoEnable) {
154+
currentSettings[name] = {
155+
...currentProvider,
156+
settings: {
157+
...currentProvider.settings,
158+
enabled: true,
159+
},
160+
};
161+
newlyAutoEnabled.push(name);
162+
hasChanges = true;
163+
}
164+
}
165+
}
166+
});
167+
168+
if (hasChanges) {
169+
// Update the store
170+
providersStore.set(currentSettings);
171+
172+
// Save to localStorage
173+
localStorage.setItem(PROVIDER_SETTINGS_KEY, JSON.stringify(currentSettings));
174+
175+
// Update the auto-enabled providers list
176+
const allAutoEnabled = [...new Set([...previouslyAutoEnabled, ...newlyAutoEnabled])];
177+
localStorage.setItem(AUTO_ENABLED_KEY, JSON.stringify(allAutoEnabled));
178+
179+
console.log(`Auto-enabled providers: ${newlyAutoEnabled.join(', ')}`);
180+
}
181+
} catch (error) {
182+
console.error('Error auto-enabling configured providers:', error);
183+
}
184+
};
185+
95186
export const providersStore = map<ProviderSetting>(getInitialProviderSettings());
96187

188+
// Export the auto-enable function for use in components
189+
export const initializeProviders = autoEnableConfiguredProviders;
190+
191+
// Initialize providers when the module loads (in browser only)
192+
if (isBrowser) {
193+
// Use a small delay to ensure DOM and other resources are ready
194+
setTimeout(() => {
195+
autoEnableConfiguredProviders();
196+
}, 100);
197+
}
198+
97199
// Create a function to update provider settings that handles both store and persistence
98200
export const updateProviderSettings = (provider: string, settings: ProviderSetting) => {
99201
const currentSettings = providersStore.get();
@@ -113,6 +215,37 @@ export const updateProviderSettings = (provider: string, settings: ProviderSetti
113215
// Save to localStorage
114216
const allSettings = providersStore.get();
115217
localStorage.setItem(PROVIDER_SETTINGS_KEY, JSON.stringify(allSettings));
218+
219+
// If this is a local provider, update the auto-enabled tracking
220+
if (LOCAL_PROVIDERS.includes(provider) && updatedProvider.settings.enabled !== undefined) {
221+
updateAutoEnabledTracking(provider, updatedProvider.settings.enabled);
222+
}
223+
};
224+
225+
// Update auto-enabled tracking when user manually changes provider settings
226+
const updateAutoEnabledTracking = (providerName: string, isEnabled: boolean) => {
227+
if (!isBrowser) {
228+
return;
229+
}
230+
231+
try {
232+
const autoEnabledProviders = localStorage.getItem(AUTO_ENABLED_KEY);
233+
const currentAutoEnabled = autoEnabledProviders ? JSON.parse(autoEnabledProviders) : [];
234+
235+
if (isEnabled) {
236+
// If user enables provider, add to auto-enabled list (for future detection)
237+
if (!currentAutoEnabled.includes(providerName)) {
238+
currentAutoEnabled.push(providerName);
239+
localStorage.setItem(AUTO_ENABLED_KEY, JSON.stringify(currentAutoEnabled));
240+
}
241+
} else {
242+
// If user disables provider, remove from auto-enabled list (respect user choice)
243+
const updatedAutoEnabled = currentAutoEnabled.filter((name: string) => name !== providerName);
244+
localStorage.setItem(AUTO_ENABLED_KEY, JSON.stringify(updatedAutoEnabled));
245+
}
246+
} catch (error) {
247+
console.error('Error updating auto-enabled tracking:', error);
248+
}
116249
};
117250

118251
export const isDebugMode = atom(false);
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import type { LoaderFunction } from '@remix-run/cloudflare';
2+
import { json } from '@remix-run/cloudflare';
3+
import { LLMManager } from '~/lib/modules/llm/manager';
4+
import { LOCAL_PROVIDERS } from '~/lib/stores/settings';
5+
6+
interface ConfiguredProvider {
7+
name: string;
8+
isConfigured: boolean;
9+
configMethod: 'environment' | 'none';
10+
}
11+
12+
interface ConfiguredProvidersResponse {
13+
providers: ConfiguredProvider[];
14+
}
15+
16+
/**
17+
* API endpoint that detects which providers are configured via environment variables
18+
* This helps auto-enable providers that have been set up by the user
19+
*/
20+
export const loader: LoaderFunction = async ({ context }) => {
21+
try {
22+
const llmManager = LLMManager.getInstance(context?.cloudflare?.env as any);
23+
const configuredProviders: ConfiguredProvider[] = [];
24+
25+
// Check each local provider for environment configuration
26+
for (const providerName of LOCAL_PROVIDERS) {
27+
const providerInstance = llmManager.getProvider(providerName);
28+
let isConfigured = false;
29+
let configMethod: 'environment' | 'none' = 'none';
30+
31+
if (providerInstance) {
32+
const config = providerInstance.config;
33+
34+
/*
35+
* Check if required environment variables are set
36+
* For providers with baseUrlKey (Ollama, LMStudio, OpenAILike)
37+
*/
38+
if (config.baseUrlKey) {
39+
const baseUrlEnvVar = config.baseUrlKey;
40+
const cloudflareEnv = (context?.cloudflare?.env as Record<string, any>)?.[baseUrlEnvVar];
41+
const processEnv = process.env[baseUrlEnvVar];
42+
const managerEnv = llmManager.env[baseUrlEnvVar];
43+
44+
const envBaseUrl = cloudflareEnv || processEnv || managerEnv;
45+
46+
/*
47+
* Only consider configured if environment variable is explicitly set
48+
* Don't count default config.baseUrl values or placeholder values
49+
*/
50+
const isValidEnvValue =
51+
envBaseUrl &&
52+
typeof envBaseUrl === 'string' &&
53+
envBaseUrl.trim().length > 0 &&
54+
!envBaseUrl.includes('your_') && // Filter out placeholder values like "your_openai_like_base_url_here"
55+
!envBaseUrl.includes('_here') &&
56+
envBaseUrl.startsWith('http'); // Must be a valid URL
57+
58+
if (isValidEnvValue) {
59+
isConfigured = true;
60+
configMethod = 'environment';
61+
}
62+
}
63+
64+
// For providers that might need API keys as well (check this separately, not as fallback)
65+
if (config.apiTokenKey && !isConfigured) {
66+
const apiTokenEnvVar = config.apiTokenKey;
67+
const envApiToken =
68+
(context?.cloudflare?.env as Record<string, any>)?.[apiTokenEnvVar] ||
69+
process.env[apiTokenEnvVar] ||
70+
llmManager.env[apiTokenEnvVar];
71+
72+
// Only consider configured if API key is set and not a placeholder
73+
const isValidApiToken =
74+
envApiToken &&
75+
typeof envApiToken === 'string' &&
76+
envApiToken.trim().length > 0 &&
77+
!envApiToken.includes('your_') && // Filter out placeholder values
78+
!envApiToken.includes('_here') &&
79+
envApiToken.length > 10; // API keys are typically longer than 10 chars
80+
81+
if (isValidApiToken) {
82+
isConfigured = true;
83+
configMethod = 'environment';
84+
}
85+
}
86+
}
87+
88+
configuredProviders.push({
89+
name: providerName,
90+
isConfigured,
91+
configMethod,
92+
});
93+
}
94+
95+
return json<ConfiguredProvidersResponse>({
96+
providers: configuredProviders,
97+
});
98+
} catch (error) {
99+
console.error('Error detecting configured providers:', error);
100+
101+
// Return default state on error
102+
return json<ConfiguredProvidersResponse>({
103+
providers: LOCAL_PROVIDERS.map((name) => ({
104+
name,
105+
isConfigured: false,
106+
configMethod: 'none' as const,
107+
})),
108+
});
109+
}
110+
};

0 commit comments

Comments
 (0)