Onboarding changes

This commit is contained in:
ajay.calsoft
2025-04-15 17:03:04 +00:00
parent a2b92e27bc
commit 036fde9e81
4 changed files with 461 additions and 660 deletions

View File

@@ -1,245 +1,32 @@
"""AI providers setup component.""" """AI providers setup component - Wrapper for the actual setup UI."""
import streamlit as st import streamlit as st
from loguru import logger from loguru import logger
from typing import Dict, Any from typing import Dict, Any
from ..manager import APIKeyManager from ..manager import APIKeyManager
from .base import render_navigation_buttons, render_step_indicator, render_tab_style from .ai_providers_setup import render_ai_providers_setup # Import the refactored setup UI
from ..wizard_state import next_step, update_progress
from datetime import datetime
def validate_api_key(key: str) -> bool:
"""Validate if an API key is properly formatted."""
if not key:
return False
# Basic validation - check if key is not empty and has minimum length
return len(key.strip()) > 0
def render_ai_providers(api_key_manager: APIKeyManager) -> Dict[str, Any]: def render_ai_providers(api_key_manager: APIKeyManager) -> Dict[str, Any]:
"""Render the AI providers setup step.""" """Renders the AI providers setup step by calling the dedicated setup function."""
logger.info("[render_ai_providers] Starting AI providers setup") logger.debug("[render_ai_providers] Calling render_ai_providers_setup")
try: try:
# Initialize wizard state if not already initialized # The actual UI, saving, validation, and feedback are now handled within render_ai_providers_setup
if 'wizard_state' not in st.session_state: # This function acts primarily as a placeholder in the step sequence if needed,
st.session_state.wizard_state = { # or can be bypassed entirely if the main wizard calls render_ai_providers_setup directly.
'current_step': 1,
'total_steps': 6,
'progress': 0,
'completed_steps': set(),
'last_updated': datetime.now()
}
logger.info("[render_ai_providers] Initialized wizard state")
# Store API key manager in session state for update_progress # Store the manager instance if needed by other potential logic (unlikely now)
st.session_state['api_key_manager'] = api_key_manager if 'api_key_manager' not in st.session_state:
st.session_state['api_key_manager'] = api_key_manager
# Main content # Call the function that now contains all the rendering and logic for this step
st.markdown(""" component_state = render_ai_providers_setup(api_key_manager)
<div class='setup-header'>
<h2>🤖 AI Providers Setup</h2>
<p>Configure your AI service providers for content generation</p>
</div>
""", unsafe_allow_html=True)
# Create tabs for different AI providers # Return the state from the setup function, although it might not be used directly
tabs = st.tabs(["Primary Providers", "Additional Providers"]) return component_state
# Track if any changes were made
changes_made = False
has_valid_key = False
validation_message = ""
with tabs[0]:
st.markdown("### Primary AI Providers")
st.markdown("Configure the main AI providers for content creation")
# Create a grid layout for AI provider cards
col1, col2 = st.columns(2)
with col1:
# OpenAI Card
with st.container():
st.markdown("""
<div class="ai-provider-card">
<div class="ai-provider-header">
<div class="ai-provider-icon">🤖</div>
<div class="ai-provider-title">OpenAI</div>
</div>
<div class="ai-provider-content">
<p>Power your content with GPT-4 and GPT-3.5 models</p>
<div class="ai-provider-input">
""", unsafe_allow_html=True)
openai_key = st.text_input(
"OpenAI API Key",
type="password",
key="openai_key",
help="Enter your OpenAI API key"
)
if openai_key:
if validate_api_key(openai_key):
st.markdown("""
<div class="ai-provider-status status-valid">
✓ API key configured
</div>
""", unsafe_allow_html=True)
else:
st.markdown("""
<div class="ai-provider-status status-invalid">
⚠️ Invalid API key format
</div>
""", unsafe_allow_html=True)
with st.expander("📋 How to get your OpenAI API key", expanded=False):
st.markdown("""
**Step-by-step guide:**
1. Go to [OpenAI's website](https://platform.openai.com)
2. Sign up or log in to your account
3. Navigate to the API section
4. Click "Create new secret key"
5. Copy the generated key and paste it here
**Note:** Keep your API key secure and never share it publicly.
""")
st.markdown("</div></div></div>", unsafe_allow_html=True)
with col2:
# Google Card
with st.container():
st.markdown("""
<div class="ai-provider-card">
<div class="ai-provider-header">
<div class="ai-provider-icon">🔍</div>
<div class="ai-provider-title">Google Gemini</div>
</div>
<div class="ai-provider-content">
<p>Leverage Google's powerful Gemini models</p>
<div class="ai-provider-input">
""", unsafe_allow_html=True)
google_key = st.text_input(
"Google API Key",
type="password",
key="google_key",
help="Enter your Google API key"
)
if google_key:
if validate_api_key(google_key):
st.markdown("""
<div class="ai-provider-status status-valid">
✓ API key configured
</div>
""", unsafe_allow_html=True)
else:
st.markdown("""
<div class="ai-provider-status status-invalid">
⚠️ Invalid API key format
</div>
""", unsafe_allow_html=True)
with st.expander("📋 How to get your Google API key", expanded=False):
st.markdown("""
**Step-by-step guide:**
1. Visit [Google AI Studio](https://makersuite.google.com/app/apikey)
2. Sign in with your Google account
3. Click "Create API key"
4. Copy the generated key and paste it here
**Note:** Make sure to enable the Gemini API in your Google Cloud Console.
""")
st.markdown("</div></div></div>", unsafe_allow_html=True)
with tabs[1]:
st.markdown("### Additional AI Providers")
st.markdown("Configure additional AI providers for enhanced capabilities")
# Create a grid layout for additional provider cards
col1, col2 = st.columns(2)
with col1:
# Anthropic Card (Coming Soon)
with st.container():
st.markdown("""
<div class="ai-provider-card disabled">
<div class="ai-provider-header">
<div class="ai-provider-icon">🧠</div>
<div class="ai-provider-title">Anthropic <span class="coming-soon-badge">Coming Soon</span></div>
</div>
<div class="ai-provider-content">
<p>Access Claude for advanced content generation</p>
</div>
</div>
""", unsafe_allow_html=True)
st.info("Anthropic integration will be available in the next update")
with col2:
# Mistral Card (Coming Soon)
with st.container():
st.markdown("""
<div class="ai-provider-card disabled">
<div class="ai-provider-header">
<div class="ai-provider-icon">⚡</div>
<div class="ai-provider-title">Mistral <span class="coming-soon-badge">Coming Soon</span></div>
</div>
<div class="ai-provider-content">
<p>Use Mistral's efficient language models</p>
</div>
</div>
""", unsafe_allow_html=True)
st.info("Mistral integration will be available in the next update")
# Track changes and validate keys
if any([openai_key, google_key]):
changes_made = True
# Check if at least one valid API key is provided
if validate_api_key(openai_key) or validate_api_key(google_key):
has_valid_key = True
validation_message = "✅ At least one AI provider configured successfully"
else:
validation_message = "⚠️ Please provide at least one valid API key"
else:
validation_message = "⚠️ Please configure at least one AI provider to continue"
# Display validation message
if validation_message:
if "" in validation_message:
st.success(validation_message)
else:
st.warning(validation_message)
# Navigation buttons
if render_navigation_buttons(1, 6, changes_made):
if has_valid_key:
# Store the API keys in a separate session state key
st.session_state['api_keys'] = {
'openai': openai_key if validate_api_key(openai_key) else None,
'google': google_key if validate_api_key(google_key) else None
}
# Save API keys to .env file
if validate_api_key(openai_key):
api_key_manager.save_api_key("openai", openai_key)
logger.info("[render_ai_providers] OpenAI API key saved to .env file")
if validate_api_key(google_key):
api_key_manager.save_api_key("gemini", google_key)
logger.info("[render_ai_providers] Google Gemini API key saved to .env file")
# Update progress and move to next step
st.session_state['current_step'] = 2 # Set the next step explicitly
update_progress()
st.rerun() # Rerun to apply the changes
else:
st.error("Please configure at least one valid AI provider to continue")
return {"current_step": 1, "changes_made": changes_made}
except Exception as e: except Exception as e:
error_msg = f"Error in AI providers setup: {str(e)}" error_msg = f"Error calling AI providers setup: {str(e)}"
logger.error(f"[render_ai_providers] {error_msg}") logger.error(f"[render_ai_providers] {error_msg}", exc_info=True)
st.error(error_msg) st.error("An error occurred while setting up AI providers.")
return {"current_step": 1, "error": error_msg} # Ensure consistency in error return format if expected by the caller
return {"error": error_msg}

View File

@@ -5,110 +5,200 @@ from loguru import logger
import streamlit as st import streamlit as st
import os import os
import sys import sys
# Assuming api_key_tests are in the parent directory relative to this component
from ..api_key_tests import (
test_openai_api_key,
test_gemini_api_key,
test_anthropic_api_key, # Keep if needed elsewhere, not used in this step currently
test_deepseek_api_key, # Keep if needed elsewhere
test_mistral_api_key
)
# Helper function to validate a specific provider's key
def _validate_provider_key(provider_name: str, key_value: str) -> bool:
"""Validate the API key for a given provider."""
if not key_value:
logger.debug(f"Validation: Key for {provider_name} is empty.")
return False
try:
logger.debug(f"Validating key for {provider_name}...")
if provider_name == "openai":
is_valid = test_openai_api_key(key_value)
elif provider_name == "gemini":
is_valid = test_gemini_api_key(key_value)
elif provider_name == "mistral":
is_valid = test_mistral_api_key(key_value)
else:
logger.warning(f"Validation not implemented for provider: {provider_name}")
return False # Or True if unknown providers are allowed without validation
logger.info(f"Validation result for {provider_name}: {'Valid' if is_valid else 'Invalid'}")
return is_valid
except Exception as e:
logger.error(f"Error validating key for {provider_name}: {e}", exc_info=True)
return False
# Callback function for handling API key input changes
def _handle_api_key_change(provider_name: str, api_key_manager):
"""Save and validate API key when input changes."""
key_input_widget_key = f"{provider_name}_key_input"
status_widget_key = f"{provider_name}_status"
# Check if the input widget key exists in session state
if key_input_widget_key not in st.session_state:
logger.warning(f"Input widget key '{key_input_widget_key}' not found in session state.")
return
key_value = st.session_state[key_input_widget_key]
current_status = st.session_state.get(status_widget_key)
logger.debug(f"Handling change for {provider_name}. Key: {'***' if key_value else 'Empty'}. Current status: {current_status}")
# If key is empty, reset status
if not key_value:
api_key_manager.save_api_key(provider_name, "") # Ensure empty key is saved
st.session_state[status_widget_key] = "unsaved"
logger.info(f"Cleared API key for {provider_name}.")
return
# Set status to saving/validating
st.session_state[status_widget_key] = "saving"
st.rerun() # Rerun to show the spinner immediately
try:
# Save the key using the manager
logger.debug(f"Saving key for {provider_name}...")
api_key_manager.save_api_key(provider_name, key_value)
logger.info(f"Saved API key for {provider_name}.")
# Validate the key
is_valid = _validate_provider_key(provider_name, key_value)
# Update status based on validation result
if is_valid:
st.session_state[status_widget_key] = "valid"
else:
st.session_state[status_widget_key] = "invalid"
except Exception as e:
logger.error(f"Error during saving/validation for {provider_name}: {e}", exc_info=True)
st.session_state[status_widget_key] = "error"
def render_ai_providers_setup(api_key_manager) -> Dict[str, Any]: def render_ai_providers_setup(api_key_manager) -> Dict[str, Any]:
""" """
Render the AI providers setup component. Render the AI providers setup component with immediate feedback.
Args: Args:
api_key_manager: API key manager instance api_key_manager: API key manager instance
Returns: Returns:
Dict[str, Any]: Component state Dict[str, Any]: Component state (not directly used here, handled by state manager)
""" """
try: try:
logger.info("[render_ai_providers_setup] Rendering AI providers setup") logger.info("[render_ai_providers_setup] Rendering AI providers setup")
# Initialize status in session state if not present
for provider in ["openai", "gemini", "mistral"]:
status_key = f"{provider}_status"
if status_key not in st.session_state:
# Check if a key exists and try to validate it on first load
existing_key = api_key_manager.get_api_key(provider)
if existing_key:
if _validate_provider_key(provider, existing_key):
st.session_state[status_key] = "valid"
else:
# Keep it unsaved/invalid on load if pre-existing key is bad
# Or maybe set to invalid? Let's choose unsaved for now.
st.session_state[status_key] = "invalid"
else:
st.session_state[status_key] = "unsaved"
# Display section header # Display section header
st.header("Step 1: Select AI Providers") st.header("Step 1: Configure AI Providers")
st.markdown(""" st.markdown("""
Configure your AI providers to enable advanced content generation capabilities. Configure your AI providers to enable advanced content generation capabilities.
Choose and set up the AI services you want to use. Enter your API keys below. They will be validated automatically.
""") """)
# Create columns for different providers # --- OpenAI ---
col1, col2 = st.columns(2) st.subheader("OpenAI (Required)")
st.markdown("Get your API key from: [OpenAI Dashboard](https://platform.openai.com/account/api-keys)")
openai_key = api_key_manager.get_api_key("openai")
st.text_input(
"OpenAI API Key",
value=openai_key if openai_key else "",
type="password",
key="openai_key_input",
on_change=_handle_api_key_change,
args=("openai", api_key_manager)
)
# Feedback Area for OpenAI
openai_status = st.session_state.get("openai_status", "unsaved")
if openai_status == "saving":
st.spinner("Validating OpenAI key...")
elif openai_status == "valid":
st.success("OpenAI key saved and valid!", icon="")
elif openai_status == "invalid":
st.error("Invalid OpenAI key. Please check and try again.", icon="")
elif openai_status == "error":
st.error("Error saving/validating OpenAI key.", icon="⚠️")
with col1: # --- Google Gemini ---
st.subheader("OpenAI") st.subheader("Google Gemini (Required)")
st.markdown(""" st.markdown("Get your API key from: [Google AI Studio](https://makersuite.google.com/app/apikey)")
OpenAI's GPT models provide powerful natural language processing capabilities. gemini_key = api_key_manager.get_api_key("gemini")
st.text_input(
"Gemini API Key",
value=gemini_key if gemini_key else "",
type="password",
key="gemini_key_input",
on_change=_handle_api_key_change,
args=("gemini", api_key_manager)
)
# Feedback Area for Gemini
gemini_status = st.session_state.get("gemini_status", "unsaved")
if gemini_status == "saving":
st.spinner("Validating Gemini key...")
elif gemini_status == "valid":
st.success("Gemini key saved and valid!", icon="")
elif gemini_status == "invalid":
st.error("Invalid Gemini key. Please check and try again.", icon="")
elif gemini_status == "error":
st.error("Error saving/validating Gemini key.", icon="⚠️")
Get your API key from: [OpenAI Dashboard](https://platform.openai.com/account/api-keys) # --- Mistral AI (Optional) ---
""") st.subheader("Mistral AI (Optional)")
st.markdown("Get your API key from: [Mistral Platform](https://console.mistral.ai/api-keys/)")
mistral_key = api_key_manager.get_api_key("mistral")
st.text_input(
"Mistral API Key",
value=mistral_key if mistral_key else "",
type="password",
key="mistral_key_input",
on_change=_handle_api_key_change,
args=("mistral", api_key_manager)
)
# Feedback Area for Mistral
mistral_status = st.session_state.get("mistral_status", "unsaved")
if mistral_status == "saving":
st.spinner("Validating Mistral key...")
elif mistral_status == "valid":
st.success("Mistral key saved and valid!", icon="")
elif mistral_status == "invalid":
st.error("Invalid Mistral key. Please check and try again.", icon="")
elif mistral_status == "error":
st.error("Error saving/validating Mistral key.", icon="⚠️")
openai_key = api_key_manager.get_api_key("openai") # --- Final Notes ---
openai_input = st.text_input( st.info("Note: At least one AI provider (OpenAI or Google Gemini) must have a valid API key to proceed.")
"OpenAI API Key",
value=openai_key if openai_key else "",
type="password",
key="openai_key_input"
)
with col2: # REMOVED the old saving logic block here
st.subheader("Google Gemini")
st.markdown("""
Google's Gemini models offer advanced AI capabilities.
Get your API key from: [Google AI Studio](https://makersuite.google.com/app/apikey) # Return value is not strictly needed if navigation relies on session state status
""") return {}
gemini_key = api_key_manager.get_api_key("gemini")
gemini_input = st.text_input(
"Gemini API Key",
value=gemini_key if gemini_key else "",
type="password",
key="gemini_key_input"
)
# Optional AI Provider
st.subheader("Additional AI Provider (Optional)")
col1, col2 = st.columns(2)
with col1:
st.markdown("""
Mistral AI provides an alternative model for content generation.
Get your API key from: [Mistral Platform](https://console.mistral.ai/api-keys/)
""")
mistral_key = api_key_manager.get_api_key("mistral")
mistral_input = st.text_input(
"Mistral API Key (Optional)",
value=mistral_key if mistral_key else "",
type="password",
key="mistral_key_input"
)
# Add a note about saving
st.info("""
Note: At least one AI provider (OpenAI or Google Gemini) is required.
Click Continue to save your keys and proceed.
""")
# Save keys if they've changed when proceeding to next step
if st.session_state.get('wizard_current_step', 1) > 1:
if openai_input != openai_key:
api_key_manager.save_api_key("openai", openai_input)
logger.info("[render_ai_providers_setup] OpenAI API key saved")
if gemini_input != gemini_key:
api_key_manager.save_api_key("gemini", gemini_input)
logger.info("[render_ai_providers_setup] Gemini API key saved")
if mistral_input != mistral_key:
api_key_manager.save_api_key("mistral", mistral_input)
logger.info("[render_ai_providers_setup] Mistral API key saved")
# Validate that at least one required provider is configured
if not (openai_input or gemini_input):
st.error("Please configure at least one AI provider (OpenAI or Google Gemini) to proceed.")
return {"current_step": 1, "can_proceed": False}
return {"current_step": 1, "can_proceed": bool(openai_input or gemini_input)}
except Exception as e: except Exception as e:
error_msg = f"Error in AI providers setup: {str(e)}" error_msg = f"Error rendering AI providers setup: {str(e)}"
logger.error(f"[render_ai_providers_setup] {error_msg}") logger.error(f"[render_ai_providers_setup] {error_msg}", exc_info=True)
st.error(error_msg) st.error(error_msg)
return {"current_step": 1, "error": error_msg} return {"error": error_msg}

View File

@@ -4,346 +4,222 @@ import streamlit as st
from loguru import logger from loguru import logger
from typing import Dict, Any from typing import Dict, Any
from ..manager import APIKeyManager from ..manager import APIKeyManager
from .base import render_navigation_buttons # Removed import of render_navigation_buttons as it's handled in base
import os import os
from dotenv import load_dotenv from dotenv import load_dotenv # Keep if api_key_manager uses it
import sys import sys
# Import test functions (adjust path if needed)
from ..api_key_tests import (
test_serpapi_key,
test_tavily_key,
test_metaphor_key,
test_firecrawl_key
# Add others if needed later, e.g., test_bing_key, test_google_search_key
)
# Configure logger # Configure logger (assuming configured elsewhere or keep minimal here)
logger.remove() # Remove default handler logger.add(sys.stderr, level="INFO") # Keep simple example if needed
logger.add(
"logs/ai_research_setup.log", # Helper function to validate a specific research provider's key
rotation="500 MB", def _validate_research_key(provider_name: str, key_value: str) -> bool:
retention="10 days", """Validate the API key for a given research provider."""
level="DEBUG", if not key_value:
format="{time:YYYY-MM-DD HH:mm:ss} | {level} | {message}" logger.debug(f"Validation: Key for {provider_name} is empty.")
) return False
logger.add( try:
sys.stdout, logger.debug(f"Validating key for {provider_name}...")
level="INFO", if provider_name == "serpapi":
format="<green>{time:YYYY-MM-DD HH:mm:ss}</green> | <level>{level: <8}</level> | <cyan>{message}</cyan>" is_valid = test_serpapi_key(key_value)
) elif provider_name == "tavily":
is_valid = test_tavily_key(key_value)
elif provider_name == "metaphor":
is_valid = test_metaphor_key(key_value)
elif provider_name == "firecrawl":
is_valid = test_firecrawl_key(key_value)
else:
logger.warning(f"Validation not implemented for research provider: {provider_name}")
return False # Default to False for unknown providers
logger.info(f"Validation result for {provider_name}: {'Valid' if is_valid else 'Invalid'}")
return is_valid
except Exception as e:
logger.error(f"Error validating key for {provider_name}: {e}", exc_info=True)
return False
# Callback function for handling API key input changes
def _handle_research_key_change(provider_name: str, api_key_manager):
"""Save and validate research API key when input changes."""
key_input_widget_key = f"{provider_name}_key_input"
status_widget_key = f"{provider_name}_status"
if key_input_widget_key not in st.session_state:
logger.warning(f"Input widget key '{key_input_widget_key}' not found in session state.")
return
key_value = st.session_state[key_input_widget_key]
current_status = st.session_state.get(status_widget_key)
logger.debug(f"Handling research key change for {provider_name}. Key: {'***' if key_value else 'Empty'}. Current status: {current_status}")
if not key_value:
api_key_manager.save_api_key(provider_name, "")
st.session_state[status_widget_key] = "unsaved"
logger.info(f"Cleared API key for {provider_name}.")
return
st.session_state[status_widget_key] = "saving"
st.rerun()
try:
logger.debug(f"Saving key for {provider_name}...")
api_key_manager.save_api_key(provider_name, key_value)
logger.info(f"Saved API key for {provider_name}.")
is_valid = _validate_research_key(provider_name, key_value)
if is_valid:
st.session_state[status_widget_key] = "valid"
else:
st.session_state[status_widget_key] = "invalid"
except Exception as e:
logger.error(f"Error during saving/validation for {provider_name}: {e}", exc_info=True)
st.session_state[status_widget_key] = "error"
def render_ai_research_setup(api_key_manager: APIKeyManager) -> Dict[str, Any]: def render_ai_research_setup(api_key_manager: APIKeyManager) -> Dict[str, Any]:
"""Render the AI research setup step.""" """Render the AI research setup step with immediate feedback."""
logger.info("[render_ai_research_setup] Rendering AI research setup component") logger.info("[render_ai_research_setup] Rendering AI research setup component")
research_providers = ["serpapi", "tavily", "metaphor", "firecrawl"]
# Initialize statuses
for provider in research_providers:
status_key = f"{provider}_status"
if status_key not in st.session_state:
existing_key = api_key_manager.get_api_key(provider)
if existing_key:
if _validate_research_key(provider, existing_key):
st.session_state[status_key] = "valid"
else:
st.session_state[status_key] = "invalid"
else:
st.session_state[status_key] = "unsaved"
st.markdown(""" st.markdown("""
<div class='setup-header'> <div class='setup-header'>
<h2>🔍 AI Research Setup</h2> <h2>Step 3: Configure AI Research Tools (Optional)</h2>
<p>Configure your AI research providers for content analysis and research</p> <p>Set up API keys for enhanced web research, crawling, and analysis. These are optional but recommended.</p>
</div> </div>
""", unsafe_allow_html=True) """, unsafe_allow_html=True)
# Create two columns for different search types
col1, col2 = st.columns(2) col1, col2 = st.columns(2)
# --- SerpAPI ---
with col1: with col1:
st.markdown("### The Usual") st.subheader("SerpAPI")
st.markdown("Access real-time search engine results. Get key: [SerpAPI](https://serpapi.com)")
# SerpAPI Card serpapi_key_val = api_key_manager.get_api_key("serpapi")
st.markdown(""" st.text_input(
<div class="ai-provider-card">
<div class="ai-provider-header">
<div class="ai-provider-icon">🔎</div>
<div class="ai-provider-title">SerpAPI</div>
</div>
<div class="ai-provider-description">
Access search engine results for research
</div>
<div class="ai-provider-input">
""", unsafe_allow_html=True)
serpapi_key = st.text_input(
"SerpAPI Key", "SerpAPI Key",
value=serpapi_key_val if serpapi_key_val else "",
type="password", type="password",
key="serpapi_key", key="serpapi_key_input",
help="Enter your SerpAPI key" on_change=_handle_research_key_change,
args=("serpapi", api_key_manager)
) )
# Feedback Area
serpapi_status = st.session_state.get("serpapi_status", "unsaved")
feedback_placeholder_serpapi = st.empty()
if serpapi_status == "saving":
feedback_placeholder_serpapi.info("Validating SerpAPI key...", icon="")
elif serpapi_status == "valid":
feedback_placeholder_serpapi.success("SerpAPI key saved and valid!", icon="")
elif serpapi_status == "invalid":
feedback_placeholder_serpapi.error("Invalid SerpAPI key.", icon="")
elif serpapi_status == "error":
feedback_placeholder_serpapi.error("Error saving/validating SerpAPI key.", icon="⚠️")
if serpapi_key: # --- Firecrawl ---
st.markdown(""" with col1:
<div class="ai-provider-status status-valid"> st.subheader("Firecrawl")
✓ API key configured st.markdown("Web content extraction and crawling. Get key: [Firecrawl](https://www.firecrawl.dev/account)")
</div> firecrawl_key_val = api_key_manager.get_api_key("firecrawl")
""", unsafe_allow_html=True) st.text_input(
st.markdown("""
<div class="api-info-section">
<details>
<summary>📋 How to get your SerpAPI key</summary>
<div class="api-info-content">
<p><strong>Step-by-step guide:</strong></p>
<ol>
<li>Visit <a href="https://serpapi.com" target="_blank">SerpAPI</a></li>
<li>Create an account</li>
<li>Go to your dashboard</li>
<li>Copy your API key</li>
<li>Paste it here</li>
</ol>
<p><strong>Note:</strong> SerpAPI provides real-time search results from multiple engines.</p>
</div>
</details>
</div>
""", unsafe_allow_html=True)
st.markdown("</div></div>", unsafe_allow_html=True)
# Firecrawl Card
st.markdown("""
<div class="ai-provider-card">
<div class="ai-provider-header">
<div class="ai-provider-icon">🕷️</div>
<div class="ai-provider-title">Firecrawl</div>
</div>
<div class="ai-provider-description">
Web content extraction and analysis
</div>
<div class="ai-provider-input">
""", unsafe_allow_html=True)
firecrawl_key = st.text_input(
"Firecrawl API Key", "Firecrawl API Key",
value=firecrawl_key_val if firecrawl_key_val else "",
type="password", type="password",
key="firecrawl_key", key="firecrawl_key_input",
help="Enter your Firecrawl API key" on_change=_handle_research_key_change,
args=("firecrawl", api_key_manager)
) )
# Feedback Area
firecrawl_status = st.session_state.get("firecrawl_status", "unsaved")
feedback_placeholder_firecrawl = st.empty()
if firecrawl_status == "saving":
feedback_placeholder_firecrawl.info("Validating Firecrawl key...", icon="")
elif firecrawl_status == "valid":
feedback_placeholder_firecrawl.success("Firecrawl key saved and valid!", icon="")
elif firecrawl_status == "invalid":
feedback_placeholder_firecrawl.error("Invalid Firecrawl key.", icon="")
elif firecrawl_status == "error":
feedback_placeholder_firecrawl.error("Error saving/validating Firecrawl key.", icon="⚠️")
if firecrawl_key: # --- Tavily ---
st.markdown("""
<div class="ai-provider-status status-valid">
✓ API key configured
</div>
""", unsafe_allow_html=True)
st.markdown("""
<div class="api-info-section">
<details>
<summary>📋 How to get your Firecrawl API key</summary>
<div class="api-info-content">
<p><strong>Step-by-step guide:</strong></p>
<ol>
<li>Visit <a href="https://www.firecrawl.dev/account" target="_blank">Firecrawl</a></li>
<li>Create an account</li>
<li>Go to your dashboard</li>
<li>Generate your API key</li>
<li>Copy and paste it here</li>
</ol>
<p><strong>Note:</strong> Firecrawl provides powerful web content extraction and analysis capabilities.</p>
</div>
</details>
</div>
""", unsafe_allow_html=True)
st.markdown("</div></div>", unsafe_allow_html=True)
with col2: with col2:
st.markdown("### AI Deep Research") st.subheader("Tavily AI")
st.markdown("AI-powered search & summarization. Get key: [Tavily](https://tavily.com)")
# Tavily Card tavily_key_val = api_key_manager.get_api_key("tavily")
st.markdown(""" st.text_input(
<div class="ai-provider-card">
<div class="ai-provider-header">
<div class="ai-provider-icon">🤖</div>
<div class="ai-provider-title">Tavily AI</div>
</div>
<div class="ai-provider-description">
AI-powered search with semantic understanding
</div>
<div class="ai-provider-input">
""", unsafe_allow_html=True)
tavily_key = st.text_input(
"Tavily API Key", "Tavily API Key",
value=tavily_key_val if tavily_key_val else "",
type="password", type="password",
key="tavily_key", key="tavily_key_input",
help="Enter your Tavily API key" on_change=_handle_research_key_change,
args=("tavily", api_key_manager)
) )
# Feedback Area
tavily_status = st.session_state.get("tavily_status", "unsaved")
feedback_placeholder_tavily = st.empty()
if tavily_status == "saving":
feedback_placeholder_tavily.info("Validating Tavily key...", icon="")
elif tavily_status == "valid":
feedback_placeholder_tavily.success("Tavily key saved and valid!", icon="")
elif tavily_status == "invalid":
feedback_placeholder_tavily.error("Invalid Tavily key.", icon="")
elif tavily_status == "error":
feedback_placeholder_tavily.error("Error saving/validating Tavily key.", icon="⚠️")
if tavily_key: # --- Metaphor/Exa ---
st.markdown(""" with col2:
<div class="ai-provider-status status-valid"> st.subheader("Metaphor/Exa")
✓ API key configured st.markdown("Neural search for deep research. Get key: [Metaphor/Exa](https://metaphor.systems)")
</div> metaphor_key_val = api_key_manager.get_api_key("metaphor")
""", unsafe_allow_html=True) st.text_input(
st.markdown("""
<div class="api-info-section">
<details>
<summary>📋 How to get your Tavily API key</summary>
<div class="api-info-content">
<p><strong>Step-by-step guide:</strong></p>
<ol>
<li>Visit <a href="https://tavily.com" target="_blank">Tavily</a></li>
<li>Create an account</li>
<li>Go to API settings</li>
<li>Generate a new API key</li>
<li>Copy and paste it here</li>
</ol>
<p><strong>Note:</strong> Tavily provides AI-powered semantic search capabilities.</p>
</div>
</details>
</div>
""", unsafe_allow_html=True)
st.markdown("</div></div>", unsafe_allow_html=True)
# Metaphor/Exa Card
st.markdown("""
<div class="ai-provider-card">
<div class="ai-provider-header">
<div class="ai-provider-icon">🧠</div>
<div class="ai-provider-title">Metaphor/Exa</div>
</div>
<div class="ai-provider-description">
Neural search engine for deep research
</div>
<div class="ai-provider-input">
""", unsafe_allow_html=True)
metaphor_key = st.text_input(
"Metaphor/Exa API Key", "Metaphor/Exa API Key",
value=metaphor_key_val if metaphor_key_val else "",
type="password", type="password",
key="metaphor_key", key="metaphor_key_input",
help="Enter your Metaphor/Exa API key" on_change=_handle_research_key_change,
args=("metaphor", api_key_manager)
) )
# Feedback Area
metaphor_status = st.session_state.get("metaphor_status", "unsaved")
feedback_placeholder_metaphor = st.empty()
if metaphor_status == "saving":
feedback_placeholder_metaphor.info("Validating Metaphor/Exa key...", icon="")
elif metaphor_status == "valid":
feedback_placeholder_metaphor.success("Metaphor/Exa key saved and valid!", icon="")
elif metaphor_status == "invalid":
feedback_placeholder_metaphor.error("Invalid Metaphor/Exa key.", icon="")
elif metaphor_status == "error":
feedback_placeholder_metaphor.error("Error saving/validating Metaphor/Exa key.", icon="⚠️")
if metaphor_key: # --- Coming Soon ---
st.markdown("""
<div class="ai-provider-status status-valid">
✓ API key configured
</div>
""", unsafe_allow_html=True)
st.markdown("""
<div class="api-info-section">
<details>
<summary>📋 How to get your Metaphor/Exa API key</summary>
<div class="api-info-content">
<p><strong>Step-by-step guide:</strong></p>
<ol>
<li>Visit <a href="https://metaphor.systems" target="_blank">Metaphor/Exa</a></li>
<li>Create an account</li>
<li>Navigate to API settings</li>
<li>Generate your API key</li>
<li>Copy and paste it here</li>
</ol>
<p><strong>Note:</strong> Metaphor/Exa provides neural search capabilities for deep research.</p>
</div>
</details>
</div>
""", unsafe_allow_html=True)
st.markdown("</div></div>", unsafe_allow_html=True)
# Disabled Options Expander
with st.expander("🔜 Coming Soon - More Search Options", expanded=False): with st.expander("🔜 Coming Soon - More Search Options", expanded=False):
st.markdown(""" st.info("Integrations for Bing Search and Google Search APIs are planned.")
<div style='opacity: 0.7;'>
<h4>Bing Search API</h4>
<p>Microsoft's powerful search API with web, news, and image search capabilities.</p>
<h4>Google Search API</h4> st.info("You can skip this step if you don't need these research tools. Click Continue to proceed.")
<p>Google's programmable search engine with customizable search parameters.</p>
<p><em>These integrations are under development and will be available soon!</em></p> # Removed the old saving logic block here
</div>
""", unsafe_allow_html=True)
# Track changes return {}
changes_made = bool(serpapi_key or tavily_key or metaphor_key or firecrawl_key)
# Navigation buttons with correct arguments
if render_navigation_buttons(3, 5, changes_made):
if changes_made:
try:
# Load existing .env file if it exists
load_dotenv()
# Create or update .env file with new API keys
with open('.env', 'a') as f:
if serpapi_key:
f.write(f"\nSERPAPI_KEY={serpapi_key}")
logger.info("[render_ai_research_setup] Saved SerpAPI key")
if tavily_key:
f.write(f"\nTAVILY_API_KEY={tavily_key}")
logger.info("[render_ai_research_setup] Saved Tavily API key")
if metaphor_key:
f.write(f"\nMETAPHOR_API_KEY={metaphor_key}")
logger.info("[render_ai_research_setup] Saved Metaphor API key")
if firecrawl_key:
f.write(f"\nFIRECRAWL_API_KEY={firecrawl_key}")
logger.info("[render_ai_research_setup] Saved Firecrawl API key")
# Store the API keys in session state
st.session_state['api_keys'] = {
'serpapi': serpapi_key,
'tavily': tavily_key,
'metaphor': metaphor_key,
'firecrawl': firecrawl_key
}
# Update progress and move to next step
st.session_state['current_step'] = 4
st.rerun()
except Exception as e:
error_msg = f"Error saving API keys: {str(e)}"
logger.error(f"[render_ai_research_setup] {error_msg}")
st.error(error_msg)
else:
st.error("Please configure at least one research provider to continue")
# Detailed Information Section
st.markdown("""
---
### Understanding Your Research Options
#### The Usual: Traditional Search
**SerpAPI**
- Real-time search results from multiple search engines
- Access to structured data from search results
- Great for gathering general information and market research
- Includes features like:
- Web search results
- News articles
- Knowledge graphs
- Related questions
#### AI Deep Research: Advanced Search Capabilities
**Tavily AI**
- AI-powered search with semantic understanding
- Automatically summarizes and analyzes search results
- Perfect for:
- Deep research tasks
- Academic research
- Fact-checking
- Real-time information gathering
**Metaphor/Exa**
- Neural search engine that understands context and meaning
- Specialized in finding highly relevant content
- Ideal for:
- Technical research
- Finding similar content
- Discovering patterns in research
- Understanding topic landscapes
#### Choosing the Right Tool
1. **For General Research:**
- Start with SerpAPI for broad coverage and structured data
2. **For Deep Analysis:**
- Use Tavily AI when you need AI-powered insights
- Choose Metaphor/Exa for neural search and pattern discovery
3. **For Comprehensive Research:**
- Combine multiple tools to get the most complete picture
- Use SerpAPI for initial research
- Follow up with AI tools for deeper insights
> **Pro Tip:** Configure multiple providers to ensure you have backup options and can cross-reference results for better accuracy.
""")
return {"current_step": 3, "changes_made": changes_made}

View File

@@ -3,16 +3,17 @@
import streamlit as st import streamlit as st
from typing import Dict, Any from typing import Dict, Any
from loguru import logger from loguru import logger
from ..styles import API_KEY_MANAGER_STYLES from ..styles import API_KEY_MANAGER_STYLES # Assuming styles are correctly imported
from ..wizard_state import ( from ..wizard_state import (
get_current_step, get_current_step, # Keep if used elsewhere
next_step, next_step, # Keep if used elsewhere
previous_step, previous_step, # Keep if used elsewhere
can_proceed_to_next_step can_proceed_to_next_step # Keep if used elsewhere
) )
def render_step_indicator(current_step: int, total_steps: int) -> None: def render_step_indicator(current_step: int, total_steps: int) -> None:
"""Render the step indicator.""" """Render the step indicator."""
# Existing step indicator code... (Keep as is)
try: try:
st.markdown(""" st.markdown("""
<style> <style>
@@ -67,11 +68,11 @@ def render_step_indicator(current_step: int, total_steps: int) -> None:
""", unsafe_allow_html=True) """, unsafe_allow_html=True)
steps = [ steps = [
("🔑", "AI LLM", 1), ("🔑", "AI Providers", 1),
("🤖", "Website Setup", 2), ("🌐", "Website Setup", 2),
("👤", "AI Research", 3), ("🔍", "AI Research", 3),
("🎨", "Personalization", 4), ("🎨", "Personalization", 4),
("🔄", "Integrations", 5), ("🔗", "Integrations", 5),
("", "Complete", 6) ("", "Complete", 6)
] ]
@@ -97,18 +98,10 @@ def render_step_indicator(current_step: int, total_steps: int) -> None:
logger.error(f"Error rendering step indicator: {str(e)}") logger.error(f"Error rendering step indicator: {str(e)}")
st.error("Error displaying step indicator") st.error("Error displaying step indicator")
def render_navigation_buttons(current_step: int, total_steps: int, changes_made: bool = False) -> bool: def render_navigation_buttons(current_step: int, total_steps: int) -> None:
"""Render the navigation buttons with modern glassmorphic styling. """Render the navigation buttons with validation logic for steps 1 and 3."""
Args:
current_step (int): Current step number
total_steps (int): Total number of steps
changes_made (bool): Whether changes were made in the current step
Returns:
bool: True if next/complete button was clicked, False otherwise
"""
col1, col2, col3 = st.columns([1, 2, 1]) col1, col2, col3 = st.columns([1, 2, 1])
proceed_error_placeholder = col2.empty() # Placeholder for error message
with col1: with col1:
if current_step > 1: if current_step > 1:
@@ -119,18 +112,72 @@ def render_navigation_buttons(current_step: int, total_steps: int, changes_made:
with col3: with col3:
if current_step < total_steps: if current_step < total_steps:
next_text = "**Continue →**" next_text = "**Continue →**"
if st.button(next_text, use_container_width=True, disabled=not changes_made, key="next_button"): button_disabled = False
return True error_message = ""
else:
if st.button("**Complete Setup ✓**", use_container_width=True, type="primary", key="complete_button"):
# Save the configuration
st.success("✅ Setup completed successfully!")
return True
return False if current_step == 1:
# --- Step 1 Specific Validation ---
openai_valid = st.session_state.get("openai_status") == "valid"
gemini_valid = st.session_state.get("gemini_status") == "valid"
if not (openai_valid or gemini_valid):
button_disabled = True
error_message = "Please ensure at least one required AI provider (OpenAI or Gemini) has a valid API key to continue."
logger.debug(f"Step 1 validation: OpenAI Valid={openai_valid}, Gemini Valid={gemini_valid}, Proceed={not button_disabled}")
elif current_step == 3:
# --- Step 3 Specific Validation ---
research_providers = ["serpapi", "tavily", "metaphor", "firecrawl"]
invalid_key_found = False
for provider in research_providers:
status = st.session_state.get(f"{provider}_status")
# Disable if any *entered* key is invalid or in error state
if status in ["invalid", "error"]:
invalid_key_found = True
break
if invalid_key_found:
button_disabled = True
error_message = f"Please ensure any entered research API keys are valid before continuing. Check {provider.capitalize()} key."
logger.debug(f"Step 3 validation: Invalid Key Found={invalid_key_found}, Proceed={not button_disabled}")
# --- Default Logic for Other Steps ---
# else: # No specific validation for other steps currently
# button_disabled = False
# --- Render Button ---
if st.button(next_text, use_container_width=True, disabled=button_disabled, key="next_button"):
if button_disabled:
# Should not happen if disabled, but safeguard
proceed_error_placeholder.error(error_message if error_message else "Cannot proceed.", icon="⚠️")
logger.warning(f"Continue button clicked on Step {current_step} while disabled.")
else:
# Proceed to next step
logger.info(f"Proceeding from step {current_step} to {current_step + 1}")
st.session_state['current_step'] = current_step + 1
st.rerun()
# Show error persistently if button is disabled
elif button_disabled:
proceed_error_placeholder.error(error_message, icon="⚠️")
elif current_step == total_steps:
# --- Final Step Logic ---
final_step_can_complete = True # Replace with actual final validation logic
if st.button("**Complete Setup ✓**", use_container_width=True, type="primary", disabled=not final_step_can_complete, key="complete_button"):
if final_step_can_complete:
logger.info("Setup completed successfully!")
st.session_state['setup_complete'] = True
st.success("✅ Setup completed successfully!")
st.balloons()
st.rerun()
else:
proceed_error_placeholder.error("Please complete all required steps before finishing.", icon="⚠️")
logger.warning("Complete Setup clicked but final validation failed.")
elif not final_step_can_complete:
proceed_error_placeholder.error("Please complete all required steps before finishing.", icon="⚠️")
def render_tab_style() -> None: def render_tab_style() -> None:
"""Render enhanced tab styling.""" """Render enhanced tab styling."""
# Existing tab style code... (Keep as is)
st.markdown(""" st.markdown("""
<style> <style>
.stTabs [data-baseweb="tab-list"] { .stTabs [data-baseweb="tab-list"] {
@@ -175,6 +222,7 @@ def render_tab_style() -> None:
def render_success_message(): def render_success_message():
"""Render the success message with glassmorphic design.""" """Render the success message with glassmorphic design."""
# Existing success message code... (Keep as is)
st.markdown(""" st.markdown("""
<div class="success-message"> <div class="success-message">
<h3 style='color: white; margin-bottom: 12px; font-size: 1.4em;'>✅ API keys saved successfully!</h3> <h3 style='color: white; margin-bottom: 12px; font-size: 1.4em;'>✅ API keys saved successfully!</h3>