Revert ai_research_setup.py and base.py to the state before commit 036fde9.

This commit is contained in:
ajaysi
2025-04-16 19:36:15 +05:30
parent 3a871d4de0
commit 5da0562aa5
2 changed files with 357 additions and 287 deletions

View File

@@ -4,228 +4,346 @@ import streamlit as st
from loguru import logger
from typing import Dict, Any
from ..manager import APIKeyManager
from .base import render_navigation_buttons
import os
from dotenv import load_dotenv # Keep if api_key_manager uses it
from dotenv import load_dotenv
import sys
# 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}...")
# Ensure the function exists in validation.py before calling
if provider_name == "serpapi":
if callable(getattr(sys.modules[__name__], 'test_serpapi_key', None)):
is_valid = test_serpapi_key(key_value)
else:
logger.error("test_serpapi_key not found in validation module")
is_valid = False
elif provider_name == "tavily":
if callable(getattr(sys.modules[__name__], 'test_tavily_key', None)):
is_valid = test_tavily_key(key_value)
else:
logger.error("test_tavily_key not found in validation module")
is_valid = False
elif provider_name == "metaphor":
if callable(getattr(sys.modules[__name__], 'test_metaphor_key', None)):
is_valid = test_metaphor_key(key_value)
else:
logger.error("test_metaphor_key not found in validation module")
is_valid = False
elif provider_name == "firecrawl":
if callable(getattr(sys.modules[__name__], 'test_firecrawl_key', None)):
is_valid = test_firecrawl_key(key_value)
else:
logger.error("test_firecrawl_key not found in validation module")
is_valid = False
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"
# 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="<green>{time:YYYY-MM-DD HH:mm:ss}</green> | <level>{level: <8}</level> | <cyan>{message}</cyan>"
)
def render_ai_research_setup(api_key_manager: APIKeyManager) -> Dict[str, Any]:
"""Render the AI research setup step with immediate feedback."""
"""Render the AI research setup step."""
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("""
<div class='setup-header'>
<h2>Step 3: Configure AI Research Tools (Optional)</h2>
<p>Set up API keys for enhanced web research, crawling, and analysis. These are optional but recommended.</p>
<h2>🔍 AI Research Setup</h2>
<p>Configure your AI research providers for content analysis and research</p>
</div>
""", unsafe_allow_html=True)
# Create two columns for different search types
col1, col2 = st.columns(2)
# --- SerpAPI ---
with col1:
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_input",
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="⚠️")
# --- 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_input",
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="⚠️")
# --- Tavily ---
with col2:
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_input",
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="⚠️")
# --- 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_input",
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="⚠️")
# --- Coming Soon ---
with st.expander("🔜 Coming Soon - More Search Options", expanded=False):
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.")
st.markdown("### The Usual")
return {}
# SerpAPI Card
st.markdown("""
<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",
type="password",
key="serpapi_key",
help="Enter your SerpAPI key"
)
if serpapi_key:
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 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",
type="password",
key="firecrawl_key",
help="Enter your Firecrawl API key"
)
if firecrawl_key:
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:
st.markdown("### AI Deep Research")
# Tavily Card
st.markdown("""
<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",
type="password",
key="tavily_key",
help="Enter your Tavily API key"
)
if tavily_key:
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 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",
type="password",
key="metaphor_key",
help="Enter your Metaphor/Exa API key"
)
if metaphor_key:
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):
st.markdown("""
<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>
<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>
</div>
""", unsafe_allow_html=True)
# Track changes
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,17 +3,16 @@
import streamlit as st
from typing import Dict, Any
from loguru import logger
from ..styles import API_KEY_MANAGER_STYLES # Assuming styles are correctly imported
from ..styles import API_KEY_MANAGER_STYLES
from ..wizard_state import (
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
get_current_step,
next_step,
previous_step,
can_proceed_to_next_step
)
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("""
<style>
@@ -68,11 +67,11 @@ def render_step_indicator(current_step: int, total_steps: int) -> None:
""", unsafe_allow_html=True)
steps = [
("🔑", "AI Providers", 1),
("🌐", "Website Setup", 2),
("🔍", "AI Research", 3),
("🔑", "AI LLM", 1),
("🤖", "Website Setup", 2),
("👤", "AI Research", 3),
("🎨", "Personalization", 4),
("🔗", "Integrations", 5),
("🔄", "Integrations", 5),
("", "Complete", 6)
]
@@ -98,11 +97,19 @@ def render_step_indicator(current_step: int, total_steps: int) -> None:
logger.error(f"Error rendering step indicator: {str(e)}")
st.error("Error displaying step indicator")
def render_navigation_buttons(current_step: int, total_steps: int) -> None:
"""Render the navigation buttons with validation logic for steps 1 and 3."""
def render_navigation_buttons(current_step: int, total_steps: int, changes_made: bool = False) -> bool:
"""Render the navigation buttons with modern glassmorphic styling.
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])
proceed_error_placeholder = col2.empty() # Placeholder for error message
with col1:
if current_step > 1:
if st.button("**← Back**", use_container_width=True, key="back_button"):
@@ -112,72 +119,18 @@ def render_navigation_buttons(current_step: int, total_steps: int) -> None:
with col3:
if current_step < total_steps:
next_text = "**Continue →**"
button_disabled = False
error_message = ""
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="⚠️")
if st.button(next_text, use_container_width=True, disabled=not changes_made, key="next_button"):
return True
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
def render_tab_style() -> None:
"""Render enhanced tab styling."""
# Existing tab style code... (Keep as is)
st.markdown("""
<style>
.stTabs [data-baseweb="tab-list"] {
@@ -222,7 +175,6 @@ def render_tab_style() -> None:
def render_success_message():
"""Render the success message with glassmorphic design."""
# Existing success message code... (Keep as is)
st.markdown("""
<div class="success-message">
<h3 style='color: white; margin-bottom: 12px; font-size: 1.4em;'>✅ API keys saved successfully!</h3>