Onboarding changes
This commit is contained in:
@@ -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}
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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}
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user