From 036fde9e819e19d3cb1a744881ed7f2bb2847181 Mon Sep 17 00:00:00 2001 From: "ajay.calsoft" Date: Tue, 15 Apr 2025 17:03:04 +0000 Subject: [PATCH 1/3] Onboarding changes --- .../components/ai_providers.py | 255 +-------- .../components/ai_providers_setup.py | 260 ++++++--- .../components/ai_research_setup.py | 498 +++++++----------- lib/utils/api_key_manager/components/base.py | 108 ++-- 4 files changed, 461 insertions(+), 660 deletions(-) diff --git a/lib/utils/api_key_manager/components/ai_providers.py b/lib/utils/api_key_manager/components/ai_providers.py index 441ef2f5..1bc3d92e 100644 --- a/lib/utils/api_key_manager/components/ai_providers.py +++ b/lib/utils/api_key_manager/components/ai_providers.py @@ -1,245 +1,32 @@ -"""AI providers setup component.""" +"""AI providers setup component - Wrapper for the actual setup UI.""" import streamlit as st from loguru import logger from typing import Dict, Any from ..manager import APIKeyManager -from .base import render_navigation_buttons, render_step_indicator, render_tab_style -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 +from .ai_providers_setup import render_ai_providers_setup # Import the refactored setup UI def render_ai_providers(api_key_manager: APIKeyManager) -> Dict[str, Any]: - """Render the AI providers setup step.""" - logger.info("[render_ai_providers] Starting AI providers setup") + """Renders the AI providers setup step by calling the dedicated setup function.""" + logger.debug("[render_ai_providers] Calling render_ai_providers_setup") try: - # Initialize wizard state if not already initialized - if 'wizard_state' not in st.session_state: - st.session_state.wizard_state = { - 'current_step': 1, - 'total_steps': 6, - 'progress': 0, - 'completed_steps': set(), - 'last_updated': datetime.now() - } - logger.info("[render_ai_providers] Initialized wizard state") + # The actual UI, saving, validation, and feedback are now handled within render_ai_providers_setup + # This function acts primarily as a placeholder in the step sequence if needed, + # or can be bypassed entirely if the main wizard calls render_ai_providers_setup directly. - # Store API key manager in session state for update_progress - st.session_state['api_key_manager'] = api_key_manager - - # Main content - st.markdown(""" -
-

πŸ€– AI Providers Setup

-

Configure your AI service providers for content generation

-
- """, unsafe_allow_html=True) - - # Create tabs for different AI providers - tabs = st.tabs(["Primary Providers", "Additional Providers"]) - - # 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(""" -
-
-
πŸ€–
-
OpenAI
-
-
-

Power your content with GPT-4 and GPT-3.5 models

-
- """, 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(""" -
- βœ“ API key configured -
- """, unsafe_allow_html=True) - else: - st.markdown(""" -
- ⚠️ Invalid API key format -
- """, 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("
", unsafe_allow_html=True) - - with col2: - # Google Card - with st.container(): - st.markdown(""" -
-
-
πŸ”
-
Google Gemini
-
-
-

Leverage Google's powerful Gemini models

-
- """, 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(""" -
- βœ“ API key configured -
- """, unsafe_allow_html=True) - else: - st.markdown(""" -
- ⚠️ Invalid API key format -
- """, 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("
", 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(""" -
-
-
🧠
-
Anthropic Coming Soon
-
-
-

Access Claude for advanced content generation

-
-
- """, 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(""" -
-
-
⚑
-
Mistral Coming Soon
-
-
-

Use Mistral's efficient language models

-
-
- """, 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} + # Store the manager instance if needed by other potential logic (unlikely now) + if 'api_key_manager' not in st.session_state: + st.session_state['api_key_manager'] = api_key_manager + + # Call the function that now contains all the rendering and logic for this step + component_state = render_ai_providers_setup(api_key_manager) + # Return the state from the setup function, although it might not be used directly + return component_state + except Exception as e: - error_msg = f"Error in AI providers setup: {str(e)}" - logger.error(f"[render_ai_providers] {error_msg}") - st.error(error_msg) - return {"current_step": 1, "error": error_msg} \ No newline at end of file + error_msg = f"Error calling AI providers setup: {str(e)}" + logger.error(f"[render_ai_providers] {error_msg}", exc_info=True) + st.error("An error occurred while setting up AI providers.") + # Ensure consistency in error return format if expected by the caller + return {"error": error_msg} diff --git a/lib/utils/api_key_manager/components/ai_providers_setup.py b/lib/utils/api_key_manager/components/ai_providers_setup.py index 1ab045f6..1774e18c 100644 --- a/lib/utils/api_key_manager/components/ai_providers_setup.py +++ b/lib/utils/api_key_manager/components/ai_providers_setup.py @@ -5,110 +5,200 @@ from loguru import logger import streamlit as st import os 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]: """ - Render the AI providers setup component. + Render the AI providers setup component with immediate feedback. Args: api_key_manager: API key manager instance Returns: - Dict[str, Any]: Component state + Dict[str, Any]: Component state (not directly used here, handled by state manager) """ try: 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 - st.header("Step 1: Select AI Providers") + st.header("Step 1: Configure AI Providers") st.markdown(""" 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 - col1, col2 = st.columns(2) + # --- OpenAI --- + 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: - st.subheader("OpenAI") - st.markdown(""" - OpenAI's GPT models provide powerful natural language processing capabilities. - - Get your API key from: [OpenAI Dashboard](https://platform.openai.com/account/api-keys) - """) - - openai_key = api_key_manager.get_api_key("openai") - openai_input = st.text_input( - "OpenAI API Key", - value=openai_key if openai_key else "", - type="password", - key="openai_key_input" - ) + # --- Google Gemini --- + st.subheader("Google Gemini (Required)") + st.markdown("Get your API key from: [Google AI Studio](https://makersuite.google.com/app/apikey)") + 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="⚠️") + + # --- 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="⚠️") + + # --- Final Notes --- + st.info("Note: At least one AI provider (OpenAI or Google Gemini) must have a valid API key to proceed.") - with col2: - 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) - """) - - 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)} + # REMOVED the old saving logic block here + # Return value is not strictly needed if navigation relies on session state status + return {} + except Exception as e: - error_msg = f"Error in AI providers setup: {str(e)}" - logger.error(f"[render_ai_providers_setup] {error_msg}") + error_msg = f"Error rendering AI providers setup: {str(e)}" + logger.error(f"[render_ai_providers_setup] {error_msg}", exc_info=True) st.error(error_msg) - return {"current_step": 1, "error": error_msg} + return {"error": error_msg} diff --git a/lib/utils/api_key_manager/components/ai_research_setup.py b/lib/utils/api_key_manager/components/ai_research_setup.py index 6eeaf853..83707f6c 100644 --- a/lib/utils/api_key_manager/components/ai_research_setup.py +++ b/lib/utils/api_key_manager/components/ai_research_setup.py @@ -4,346 +4,222 @@ import streamlit as st from loguru import logger from typing import Dict, Any from ..manager import APIKeyManager -from .base import render_navigation_buttons +# Removed import of render_navigation_buttons as it's handled in base import os -from dotenv import load_dotenv +from dotenv import load_dotenv # Keep if api_key_manager uses it 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 -logger.remove() # Remove default handler -logger.add( - "logs/ai_research_setup.log", - rotation="500 MB", - retention="10 days", - level="DEBUG", - format="{time:YYYY-MM-DD HH:mm:ss} | {level} | {message}" -) -logger.add( - sys.stdout, - level="INFO", - format="{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {message}" -) +# Configure logger (assuming configured elsewhere or keep minimal here) +logger.add(sys.stderr, level="INFO") # Keep simple example if needed + +# Helper function to validate a specific research provider's key +def _validate_research_key(provider_name: str, key_value: str) -> bool: + """Validate the API key for a given research 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 == "serpapi": + 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]: - """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") + 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("""
-

πŸ” AI Research Setup

-

Configure your AI research providers for content analysis and research

+

Step 3: Configure AI Research Tools (Optional)

+

Set up API keys for enhanced web research, crawling, and analysis. These are optional but recommended.

""", unsafe_allow_html=True) - # Create two columns for different search types col1, col2 = st.columns(2) + # --- SerpAPI --- with col1: - st.markdown("### The Usual") - - # SerpAPI Card - st.markdown(""" -
-
-
πŸ”Ž
-
SerpAPI
-
-
- Access search engine results for research -
-
- """, unsafe_allow_html=True) - - serpapi_key = st.text_input( + st.subheader("SerpAPI") + st.markdown("Access real-time search engine results. Get key: [SerpAPI](https://serpapi.com)") + serpapi_key_val = api_key_manager.get_api_key("serpapi") + st.text_input( "SerpAPI Key", + value=serpapi_key_val if serpapi_key_val else "", type="password", - key="serpapi_key", - help="Enter your SerpAPI key" + key="serpapi_key_input", + on_change=_handle_research_key_change, + args=("serpapi", api_key_manager) ) - - if serpapi_key: - st.markdown(""" -
- βœ“ API key configured -
- """, unsafe_allow_html=True) - - st.markdown(""" -
-
- πŸ“‹ How to get your SerpAPI key -
-

Step-by-step guide:

-
    -
  1. Visit SerpAPI
  2. -
  3. Create an account
  4. -
  5. Go to your dashboard
  6. -
  7. Copy your API key
  8. -
  9. Paste it here
  10. -
-

Note: SerpAPI provides real-time search results from multiple engines.

-
-
-
- """, unsafe_allow_html=True) - - st.markdown("
", unsafe_allow_html=True) + # 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="⚠️") - # Firecrawl Card - st.markdown(""" -
-
-
πŸ•·οΈ
-
Firecrawl
-
-
- Web content extraction and analysis -
-
- """, unsafe_allow_html=True) - - firecrawl_key = st.text_input( + # --- Firecrawl --- + with col1: + st.subheader("Firecrawl") + st.markdown("Web content extraction and crawling. Get key: [Firecrawl](https://www.firecrawl.dev/account)") + firecrawl_key_val = api_key_manager.get_api_key("firecrawl") + st.text_input( "Firecrawl API Key", + value=firecrawl_key_val if firecrawl_key_val else "", type="password", - key="firecrawl_key", - help="Enter your Firecrawl API key" + key="firecrawl_key_input", + on_change=_handle_research_key_change, + args=("firecrawl", api_key_manager) ) - - if firecrawl_key: - st.markdown(""" -
- βœ“ API key configured -
- """, unsafe_allow_html=True) - - st.markdown(""" -
-
- πŸ“‹ How to get your Firecrawl API key -
-

Step-by-step guide:

-
    -
  1. Visit Firecrawl
  2. -
  3. Create an account
  4. -
  5. Go to your dashboard
  6. -
  7. Generate your API key
  8. -
  9. Copy and paste it here
  10. -
-

Note: Firecrawl provides powerful web content extraction and analysis capabilities.

-
-
-
- """, unsafe_allow_html=True) - - st.markdown("
", unsafe_allow_html=True) - + # 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="⚠️") + + # --- Tavily --- with col2: - st.markdown("### AI Deep Research") - - # Tavily Card - st.markdown(""" -
-
-
πŸ€–
-
Tavily AI
-
-
- AI-powered search with semantic understanding -
-
- """, unsafe_allow_html=True) - - tavily_key = st.text_input( + st.subheader("Tavily AI") + st.markdown("AI-powered search & summarization. Get key: [Tavily](https://tavily.com)") + tavily_key_val = api_key_manager.get_api_key("tavily") + st.text_input( "Tavily API Key", + value=tavily_key_val if tavily_key_val else "", type="password", - key="tavily_key", - help="Enter your Tavily API key" + key="tavily_key_input", + on_change=_handle_research_key_change, + args=("tavily", api_key_manager) ) - - if tavily_key: - st.markdown(""" -
- βœ“ API key configured -
- """, unsafe_allow_html=True) - - st.markdown(""" -
-
- πŸ“‹ How to get your Tavily API key -
-

Step-by-step guide:

-
    -
  1. Visit Tavily
  2. -
  3. Create an account
  4. -
  5. Go to API settings
  6. -
  7. Generate a new API key
  8. -
  9. Copy and paste it here
  10. -
-

Note: Tavily provides AI-powered semantic search capabilities.

-
-
-
- """, unsafe_allow_html=True) - - st.markdown("
", unsafe_allow_html=True) - - # Metaphor/Exa Card - st.markdown(""" -
-
-
🧠
-
Metaphor/Exa
-
-
- Neural search engine for deep research -
-
- """, unsafe_allow_html=True) - - metaphor_key = st.text_input( + # 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="⚠️") + + # --- Metaphor/Exa --- + with col2: + st.subheader("Metaphor/Exa") + st.markdown("Neural search for deep research. Get key: [Metaphor/Exa](https://metaphor.systems)") + metaphor_key_val = api_key_manager.get_api_key("metaphor") + st.text_input( "Metaphor/Exa API Key", + value=metaphor_key_val if metaphor_key_val else "", type="password", - key="metaphor_key", - help="Enter your Metaphor/Exa API key" + key="metaphor_key_input", + on_change=_handle_research_key_change, + args=("metaphor", api_key_manager) ) - - if metaphor_key: - st.markdown(""" -
- βœ“ API key configured -
- """, unsafe_allow_html=True) - - st.markdown(""" -
-
- πŸ“‹ How to get your Metaphor/Exa API key -
-

Step-by-step guide:

-
    -
  1. Visit Metaphor/Exa
  2. -
  3. Create an account
  4. -
  5. Navigate to API settings
  6. -
  7. Generate your API key
  8. -
  9. Copy and paste it here
  10. -
-

Note: Metaphor/Exa provides neural search capabilities for deep research.

-
-
-
- """, unsafe_allow_html=True) - - st.markdown("
", unsafe_allow_html=True) - - # Disabled Options Expander + # 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="⚠️") + + # --- Coming Soon --- with st.expander("πŸ”œ Coming Soon - More Search Options", expanded=False): - st.markdown(""" -
-

Bing Search API

-

Microsoft's powerful search API with web, news, and image search capabilities.

- -

Google Search API

-

Google's programmable search engine with customizable search parameters.

- -

These integrations are under development and will be available soon!

-
- """, unsafe_allow_html=True) + st.info("Integrations for Bing Search and Google Search APIs are planned.") + + st.info("You can skip this step if you don't need these research tools. Click Continue to proceed.") - # Track changes - changes_made = bool(serpapi_key or tavily_key or metaphor_key or firecrawl_key) + # Removed the old saving logic block here - # 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} + return {} diff --git a/lib/utils/api_key_manager/components/base.py b/lib/utils/api_key_manager/components/base.py index 953df736..66ed8c41 100644 --- a/lib/utils/api_key_manager/components/base.py +++ b/lib/utils/api_key_manager/components/base.py @@ -3,16 +3,17 @@ import streamlit as st from typing import Dict, Any 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 ( - get_current_step, - next_step, - previous_step, - can_proceed_to_next_step + get_current_step, # Keep if used elsewhere + next_step, # Keep if used elsewhere + previous_step, # Keep if used elsewhere + can_proceed_to_next_step # Keep if used elsewhere ) def render_step_indicator(current_step: int, total_steps: int) -> None: """Render the step indicator.""" + # Existing step indicator code... (Keep as is) try: st.markdown("""