Skip to content

Commit 8ea5290

Browse files
Merge pull request #29 from merendamattia/copilot/add-api-key-configuration-ui
Add API Key Configuration UI for Streamlit interface
2 parents 97842b2 + 72ae5df commit 8ea5290

File tree

4 files changed

+587
-15
lines changed

4 files changed

+587
-15
lines changed

.env.example

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,4 @@ MONTECARLO_SIMULATION_SCENARIOS=1000
3333
MONTECARLO_SIMULATION_YEARS=20
3434
MONTECARLO_MIN_ASSET_VOLATILITY=0.1
3535
MONTECARLO_DEFAULT_INITIAL_INVESTMENT=1000
36+
MONTECARLO_DEFAULT_MONTHLY_CONTRIBUTION=100

app.py

Lines changed: 139 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
from src.clients import list_providers
2222
from src.core import ChatbotAgent, FinancialAdvisorAgent
2323
from src.models import FinancialProfile, Portfolio
24+
from src.ui.settings_page import show_settings_page
2425

2526
MONTECARLO_MIN_ASSET_VOLATILITY = float(
2627
os.getenv("MONTECARLO_MIN_ASSET_VOLATILITY", 0.1)
@@ -32,6 +33,9 @@
3233
MONTECARLO_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
3741
logging.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+
102161
def 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

10131104
def _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()

src/ui/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)