diff --git a/app/lib/stores/settings.ts b/app/lib/stores/settings.ts index ef11072..fb123e6 100644 --- a/app/lib/stores/settings.ts +++ b/app/lib/stores/settings.ts @@ -52,11 +52,37 @@ export const shortcutsStore = map({ // Create a single key for provider settings const PROVIDER_SETTINGS_KEY = 'provider_settings'; +const AUTO_ENABLED_KEY = 'auto_enabled_providers'; // Add this helper function at the top of the file const isBrowser = typeof window !== 'undefined'; -// Initialize provider settings from both localStorage and defaults +// Interface for configured provider info from server +interface ConfiguredProvider { + name: string; + isConfigured: boolean; + configMethod: 'environment' | 'none'; +} + +// Fetch configured providers from server +const fetchConfiguredProviders = async (): Promise => { + try { + const response = await fetch('/api/configured-providers'); + + if (!response.ok) { + throw new Error(`HTTP ${response.status}: ${response.statusText}`); + } + + const data = (await response.json()) as { providers?: ConfiguredProvider[] }; + + return data.providers || []; + } catch (error) { + console.error('Error fetching configured providers:', error); + return []; + } +}; + +// Initialize provider settings from both localStorage and server-detected configuration const getInitialProviderSettings = (): ProviderSetting => { const initialSettings: ProviderSetting = {}; @@ -92,8 +118,84 @@ const getInitialProviderSettings = (): ProviderSetting => { return initialSettings; }; +// Auto-enable providers that are configured on the server +const autoEnableConfiguredProviders = async () => { + if (!isBrowser) { + return; + } + + try { + const configuredProviders = await fetchConfiguredProviders(); + const currentSettings = providersStore.get(); + const savedSettings = localStorage.getItem(PROVIDER_SETTINGS_KEY); + const autoEnabledProviders = localStorage.getItem(AUTO_ENABLED_KEY); + + // Track which providers were auto-enabled to avoid overriding user preferences + const previouslyAutoEnabled = autoEnabledProviders ? JSON.parse(autoEnabledProviders) : []; + const newlyAutoEnabled: string[] = []; + + let hasChanges = false; + + configuredProviders.forEach(({ name, isConfigured, configMethod }) => { + if (isConfigured && configMethod === 'environment' && LOCAL_PROVIDERS.includes(name)) { + const currentProvider = currentSettings[name]; + + if (currentProvider) { + /* + * Only auto-enable if: + * 1. Provider is not already enabled, AND + * 2. Either we haven't saved settings yet (first time) OR provider was previously auto-enabled + */ + const hasUserSettings = savedSettings !== null; + const wasAutoEnabled = previouslyAutoEnabled.includes(name); + const shouldAutoEnable = !currentProvider.settings.enabled && (!hasUserSettings || wasAutoEnabled); + + if (shouldAutoEnable) { + currentSettings[name] = { + ...currentProvider, + settings: { + ...currentProvider.settings, + enabled: true, + }, + }; + newlyAutoEnabled.push(name); + hasChanges = true; + } + } + } + }); + + if (hasChanges) { + // Update the store + providersStore.set(currentSettings); + + // Save to localStorage + localStorage.setItem(PROVIDER_SETTINGS_KEY, JSON.stringify(currentSettings)); + + // Update the auto-enabled providers list + const allAutoEnabled = [...new Set([...previouslyAutoEnabled, ...newlyAutoEnabled])]; + localStorage.setItem(AUTO_ENABLED_KEY, JSON.stringify(allAutoEnabled)); + + console.log(`Auto-enabled providers: ${newlyAutoEnabled.join(', ')}`); + } + } catch (error) { + console.error('Error auto-enabling configured providers:', error); + } +}; + export const providersStore = map(getInitialProviderSettings()); +// Export the auto-enable function for use in components +export const initializeProviders = autoEnableConfiguredProviders; + +// Initialize providers when the module loads (in browser only) +if (isBrowser) { + // Use a small delay to ensure DOM and other resources are ready + setTimeout(() => { + autoEnableConfiguredProviders(); + }, 100); +} + // Create a function to update provider settings that handles both store and persistence export const updateProviderSettings = (provider: string, settings: ProviderSetting) => { const currentSettings = providersStore.get(); @@ -113,6 +215,37 @@ export const updateProviderSettings = (provider: string, settings: ProviderSetti // Save to localStorage const allSettings = providersStore.get(); localStorage.setItem(PROVIDER_SETTINGS_KEY, JSON.stringify(allSettings)); + + // If this is a local provider, update the auto-enabled tracking + if (LOCAL_PROVIDERS.includes(provider) && updatedProvider.settings.enabled !== undefined) { + updateAutoEnabledTracking(provider, updatedProvider.settings.enabled); + } +}; + +// Update auto-enabled tracking when user manually changes provider settings +const updateAutoEnabledTracking = (providerName: string, isEnabled: boolean) => { + if (!isBrowser) { + return; + } + + try { + const autoEnabledProviders = localStorage.getItem(AUTO_ENABLED_KEY); + const currentAutoEnabled = autoEnabledProviders ? JSON.parse(autoEnabledProviders) : []; + + if (isEnabled) { + // If user enables provider, add to auto-enabled list (for future detection) + if (!currentAutoEnabled.includes(providerName)) { + currentAutoEnabled.push(providerName); + localStorage.setItem(AUTO_ENABLED_KEY, JSON.stringify(currentAutoEnabled)); + } + } else { + // If user disables provider, remove from auto-enabled list (respect user choice) + const updatedAutoEnabled = currentAutoEnabled.filter((name: string) => name !== providerName); + localStorage.setItem(AUTO_ENABLED_KEY, JSON.stringify(updatedAutoEnabled)); + } + } catch (error) { + console.error('Error updating auto-enabled tracking:', error); + } }; export const isDebugMode = atom(false); diff --git a/app/routes/api.configured-providers.ts b/app/routes/api.configured-providers.ts new file mode 100644 index 0000000..3236cf0 --- /dev/null +++ b/app/routes/api.configured-providers.ts @@ -0,0 +1,110 @@ +import type { LoaderFunction } from '@remix-run/cloudflare'; +import { json } from '@remix-run/cloudflare'; +import { LLMManager } from '~/lib/modules/llm/manager'; +import { LOCAL_PROVIDERS } from '~/lib/stores/settings'; + +interface ConfiguredProvider { + name: string; + isConfigured: boolean; + configMethod: 'environment' | 'none'; +} + +interface ConfiguredProvidersResponse { + providers: ConfiguredProvider[]; +} + +/** + * API endpoint that detects which providers are configured via environment variables + * This helps auto-enable providers that have been set up by the user + */ +export const loader: LoaderFunction = async ({ context }) => { + try { + const llmManager = LLMManager.getInstance(context?.cloudflare?.env as any); + const configuredProviders: ConfiguredProvider[] = []; + + // Check each local provider for environment configuration + for (const providerName of LOCAL_PROVIDERS) { + const providerInstance = llmManager.getProvider(providerName); + let isConfigured = false; + let configMethod: 'environment' | 'none' = 'none'; + + if (providerInstance) { + const config = providerInstance.config; + + /* + * Check if required environment variables are set + * For providers with baseUrlKey (Ollama, LMStudio, OpenAILike) + */ + if (config.baseUrlKey) { + const baseUrlEnvVar = config.baseUrlKey; + const cloudflareEnv = (context?.cloudflare?.env as Record)?.[baseUrlEnvVar]; + const processEnv = process.env[baseUrlEnvVar]; + const managerEnv = llmManager.env[baseUrlEnvVar]; + + const envBaseUrl = cloudflareEnv || processEnv || managerEnv; + + /* + * Only consider configured if environment variable is explicitly set + * Don't count default config.baseUrl values or placeholder values + */ + const isValidEnvValue = + envBaseUrl && + typeof envBaseUrl === 'string' && + envBaseUrl.trim().length > 0 && + !envBaseUrl.includes('your_') && // Filter out placeholder values like "your_openai_like_base_url_here" + !envBaseUrl.includes('_here') && + envBaseUrl.startsWith('http'); // Must be a valid URL + + if (isValidEnvValue) { + isConfigured = true; + configMethod = 'environment'; + } + } + + // For providers that might need API keys as well (check this separately, not as fallback) + if (config.apiTokenKey && !isConfigured) { + const apiTokenEnvVar = config.apiTokenKey; + const envApiToken = + (context?.cloudflare?.env as Record)?.[apiTokenEnvVar] || + process.env[apiTokenEnvVar] || + llmManager.env[apiTokenEnvVar]; + + // Only consider configured if API key is set and not a placeholder + const isValidApiToken = + envApiToken && + typeof envApiToken === 'string' && + envApiToken.trim().length > 0 && + !envApiToken.includes('your_') && // Filter out placeholder values + !envApiToken.includes('_here') && + envApiToken.length > 10; // API keys are typically longer than 10 chars + + if (isValidApiToken) { + isConfigured = true; + configMethod = 'environment'; + } + } + } + + configuredProviders.push({ + name: providerName, + isConfigured, + configMethod, + }); + } + + return json({ + providers: configuredProviders, + }); + } catch (error) { + console.error('Error detecting configured providers:', error); + + // Return default state on error + return json({ + providers: LOCAL_PROVIDERS.map((name) => ({ + name, + isConfigured: false, + configMethod: 'none' as const, + })), + }); + } +};