2121from src .clients import list_providers
2222from src .core import ChatbotAgent , FinancialAdvisorAgent
2323from src .models import FinancialProfile , Portfolio
24+ from src .ui .settings_page import show_settings_page
2425
2526MONTECARLO_MIN_ASSET_VOLATILITY = float (
2627 os .getenv ("MONTECARLO_MIN_ASSET_VOLATILITY" , 0.1 )
3233MONTECARLO_DEFAULT_INITIAL_INVESTMENT = int (
3334 os .getenv ("MONTECARLO_DEFAULT_INITIAL_INVESTMENT" , 1000 )
3435)
36+ MONTECARLO_DEFAULT_MONTHLY_CONTRIBUTION = int (
37+ os .getenv ("MONTECARLO_DEFAULT_MONTHLY_CONTRIBUTION" , 100 )
38+ )
3539
3640# Configure logging
3741logging .basicConfig (
@@ -99,15 +103,70 @@ def is_ollama_available() -> bool:
99103 return False
100104
101105
106+ def _check_api_key_validity (provider : str ) -> bool :
107+ """
108+ Check if API key for a provider is valid and configured using actual connection tests.
109+
110+ Args:
111+ provider: The provider name (openai, google, ollama)
112+
113+ Returns:
114+ True if API key is valid, False otherwise
115+ """
116+ logger .debug ("Checking API key validity for provider: %s" , provider )
117+
118+ try :
119+ from src .ui .settings_page import (
120+ _test_google_connection ,
121+ _test_ollama_connection ,
122+ _test_openai_connection ,
123+ )
124+
125+ if provider == "openai" :
126+ api_key = os .getenv ("OPENAI_API_KEY" , "" ).strip ()
127+ if not api_key :
128+ logger .debug ("OpenAI API key not configured" )
129+ return False
130+ # Test the connection
131+ model = os .getenv ("OPENAI_MODEL" , "gpt-4o-mini" )
132+ success , _ = _test_openai_connection (api_key , model )
133+ logger .debug ("OpenAI API key validity: %s" , success )
134+ return success
135+
136+ elif provider == "google" :
137+ api_key = os .getenv ("GOOGLE_API_KEY" , "" ).strip ()
138+ if not api_key :
139+ logger .debug ("Google API key not configured" )
140+ return False
141+ # Test the connection
142+ model = os .getenv ("GOOGLE_MODEL" , "gemini-2.0-flash-exp" )
143+ success , _ = _test_google_connection (api_key , model )
144+ logger .debug ("Google API key validity: %s" , success )
145+ return success
146+
147+ elif provider == "ollama" :
148+ # Ollama doesn't need API key, just needs to be running
149+ logger .debug ("Ollama provider doesn't require API key validation" )
150+ return is_ollama_available ()
151+
152+ else :
153+ logger .warning ("Unknown provider: %s" , provider )
154+ return False
155+
156+ except Exception as e :
157+ logger .error ("Error checking API key validity for %s: %s" , provider , str (e ))
158+ return False
159+
160+
102161def get_available_providers () -> list :
103162 """
104- Get list of available providers.
105- Ollama is only available if it's running .
163+ Get list of available providers with valid API keys .
164+ Only returns providers that have valid credentials configured .
106165
107166 Returns:
108167 List of available provider names
109168 """
110- logger .debug ("Getting available providers" )
169+ logger .debug ("Getting available providers with valid API keys " )
111170
112171 all_providers = list_providers ()
113172 logger .debug ("All registered providers: %s" , all_providers )
@@ -122,9 +181,14 @@ def get_available_providers() -> list:
122181 else :
123182 logger .debug ("Ollama is not available" )
124183 else :
125- # Google and OpenAI are always available (if API keys are set, they'll work)
126- available .append (provider )
127- logger .debug ("Provider %s is available" , provider )
184+ # Check if API key is valid for Google and OpenAI
185+ if _check_api_key_validity (provider ):
186+ available .append (provider )
187+ logger .debug ("Provider %s is available with valid API key" , provider )
188+ else :
189+ logger .debug (
190+ "Provider %s is not available (no valid API key)" , provider
191+ )
128192
129193 logger .info ("Available providers: %s" , available )
130194 return available
@@ -495,6 +559,14 @@ def _initialize_session_state():
495559 None ,
496560 "Initialized cached_returns_data session state to None" ,
497561 ),
562+ "show_settings" : (
563+ False ,
564+ "Initialized show_settings session state to False" ,
565+ ),
566+ "skip_initial_settings" : (
567+ False ,
568+ "Initialized skip_initial_settings session state to False" ,
569+ ),
498570 }
499571
500572 for key , (default_value , debug_message ) in session_state_defaults .items ():
@@ -564,6 +636,11 @@ def _show_provider_selection():
564636 use_container_width = True ,
565637 )
566638
639+ if st .button ("🔑 Configure API Keys" , use_container_width = True ):
640+ logger .info ("Opening API configuration from provider selection" )
641+ st .session_state .show_settings = True
642+ st .rerun ()
643+
567644 st .divider ()
568645 st .info (
569646 "💡 **Tip:** Ollama is free and runs locally. "
@@ -712,6 +789,12 @@ def _setup_sidebar(chatbot_agent, financial_advisor_agent):
712789 st .session_state .profile_loaded_from_json = False
713790 st .success ("Conversation cleared!" )
714791
792+ # API Configuration button
793+ if st .button ("🔑 API Configuration" , use_container_width = True ):
794+ logger .info ("Opening API configuration settings" )
795+ st .session_state .show_settings = True
796+ st .rerun ()
797+
715798 # Upload custom profile from JSON
716799 st .divider ()
717800 st .subheader ("📤 Load Custom Profile" )
@@ -981,7 +1064,10 @@ def _extract_financial_metrics(
9811064 try :
9821065 if profile is None :
9831066 logger .warning ("Profile is None, returning defaults" )
984- return 5000 , 200
1067+ return (
1068+ MONTECARLO_DEFAULT_INITIAL_INVESTMENT ,
1069+ MONTECARLO_DEFAULT_MONTHLY_CONTRIBUTION ,
1070+ )
9851071
9861072 # Convert profile to dict for agent
9871073 profile_dict = (
@@ -1005,9 +1091,14 @@ def _extract_financial_metrics(
10051091 except Exception as e :
10061092 logger .error ("Error extracting PAC metrics: %s" , str (e ))
10071093 logger .warning (
1008- "PAC METRICS EXTRACTION FAILED - Returning defaults: Initial €5000, Monthly €200"
1094+ "PAC METRICS EXTRACTION FAILED - Returning defaults: Initial €%d, Monthly €%d" ,
1095+ MONTECARLO_DEFAULT_INITIAL_INVESTMENT ,
1096+ MONTECARLO_DEFAULT_MONTHLY_CONTRIBUTION ,
1097+ )
1098+ return (
1099+ MONTECARLO_DEFAULT_INITIAL_INVESTMENT ,
1100+ MONTECARLO_DEFAULT_MONTHLY_CONTRIBUTION ,
10091101 )
1010- return 5000 , 200 # Return defaults
10111102
10121103
10131104def _display_wealth_simulation (
@@ -1031,11 +1122,11 @@ def _display_wealth_simulation(
10311122
10321123 # Display explanation of Long-Term Wealth Projection
10331124 st .markdown (
1034- """
1125+ f """
10351126 **📊 Cos'è questa simulazione?**
10361127
1037- La simulazione utilizza il **Monte Carlo**, un metodo statistico che proietta 1.000 scenari di crescita del portafoglio
1038- su 20 anni, considerando volatilità storica e rendimenti medi. I tre scenari mostrati rappresentano:
1128+ La simulazione utilizza il **Monte Carlo**, un metodo statistico che proietta { MONTECARLO_SIMULATION_SCENARIOS } scenari di crescita del portafoglio
1129+ su { MONTECARLO_SIMULATION_YEARS } anni, considerando volatilità storica e rendimenti medi. I tre scenari mostrati rappresentano:
10391130 - **Pessimistico (10° percentile)**: Solo 1 scenario su 10 avrà risultati peggiori
10401131 - **Atteso (50° percentile)**: Il valore più probabile (mediana)
10411132 - **Ottimistico (75° percentile)**: 3 scenari su 4 rimangono sotto questo valore
@@ -1178,8 +1269,8 @@ def _display_wealth_simulation(
11781269 monthly_contribution ,
11791270 )
11801271 else :
1181- initial_investment = 5000
1182- monthly_contribution = 200
1272+ initial_investment = MONTECARLO_DEFAULT_INITIAL_INVESTMENT
1273+ monthly_contribution = MONTECARLO_DEFAULT_MONTHLY_CONTRIBUTION
11831274 logger .warning (
11841275 "PAC SECTION - Profile is NONE, using default PAC parameters: Initial €%d, Monthly €%d" ,
11851276 initial_investment ,
@@ -1257,7 +1348,7 @@ def _display_wealth_simulation(
12571348 )
12581349
12591350 fig_lump .update_layout (
1260- title = "Lump Sum Investment - 20 -Year Wealth Projection" ,
1351+ title = f "Lump Sum Investment - { MONTECARLO_SIMULATION_YEARS } -Year Wealth Projection" ,
12611352 xaxis_title = "Years" ,
12621353 yaxis_title = "Portfolio Value (€)" ,
12631354 hovermode = "x unified" ,
@@ -1624,6 +1715,39 @@ def main():
16241715 # Initialize session state
16251716 _initialize_session_state ()
16261717
1718+ # Check if any providers are available
1719+ available_providers = get_available_providers ()
1720+ logger .debug ("Available providers on startup: %s" , available_providers )
1721+
1722+ # Show API key configuration on first load only if NO providers are available
1723+ if not st .session_state .skip_initial_settings and not available_providers :
1724+ logger .debug (
1725+ "First load with no available providers - showing API key configuration page"
1726+ )
1727+ _show_header ()
1728+ st .subheader ("🔑 Configure Your API Keys" )
1729+ st .markdown (
1730+ "Welcome! No LLM providers are currently configured. Let's set up your API keys and LLM provider settings before we get started."
1731+ )
1732+ show_settings_page ()
1733+
1734+ # Add button to proceed to provider selection
1735+ if st .button ("✅ Continue to Provider Selection" , use_container_width = True ):
1736+ logger .info ("User proceeding to provider selection after configuration" )
1737+ st .session_state .skip_initial_settings = True
1738+ st .rerun ()
1739+ return
1740+
1741+ # Mark initial settings as skipped if we reach here (at least one provider is available)
1742+ if not st .session_state .skip_initial_settings and available_providers :
1743+ logger .debug ("Skipping initial settings - at least one provider is available" )
1744+ st .session_state .skip_initial_settings = True
1745+
1746+ # Show settings page if requested from sidebar
1747+ if st .session_state .get ("show_settings" , False ):
1748+ show_settings_page ()
1749+ return
1750+
16271751 # Provider selection modal on first load
16281752 if st .session_state .provider is None :
16291753 _show_provider_selection ()
0 commit comments