diff --git a/lib/utils/api_key_manager/components/__init__.py b/lib/utils/api_key_manager/components/__init__.py index 858f76b6..15a369fa 100644 --- a/lib/utils/api_key_manager/components/__init__.py +++ b/lib/utils/api_key_manager/components/__init__.py @@ -1,6 +1,7 @@ """API key manager components package.""" from .ai_research_setup import render_ai_research_setup +from .ai_research import render_ai_research from .ai_providers import render_ai_providers from .final_setup import render_final_setup from .personalization_setup import render_personalization_setup @@ -10,6 +11,7 @@ from .website_setup import render_website_setup __all__ = [ 'render_ai_research_setup', + 'render_ai_research', 'render_ai_providers', 'render_final_setup', 'render_personalization_setup', diff --git a/lib/utils/api_key_manager/components/ai_providers.py b/lib/utils/api_key_manager/components/ai_providers.py index 441ef2f5..8fbae979 100644 --- a/lib/utils/api_key_manager/components/ai_providers.py +++ b/lib/utils/api_key_manager/components/ai_providers.py @@ -7,6 +7,8 @@ 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 +import os +from dotenv import load_dotenv def validate_api_key(key: str) -> bool: """Validate if an API key is properly formatted.""" @@ -15,10 +17,53 @@ def validate_api_key(key: str) -> bool: # Basic validation - check if key is not empty and has minimum length return len(key.strip()) > 0 +def save_to_env_file(key_name: str, key_value: str) -> bool: + """Save API key to .env file.""" + try: + env_path = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__))))), '.env') + + # Read existing .env file + env_contents = [] + if os.path.exists(env_path): + with open(env_path, 'r') as f: + env_contents = f.readlines() + + # Check if key already exists + key_exists = False + for i, line in enumerate(env_contents): + if line.startswith(f"{key_name}="): + env_contents[i] = f"{key_name}={key_value}\n" + key_exists = True + break + + # Add new key if it doesn't exist + if not key_exists: + env_contents.append(f"{key_name}={key_value}\n") + + # Write back to .env file + with open(env_path, 'w') as f: + f.writelines(env_contents) + + # Reload environment variables to ensure consistency + load_dotenv(override=True) + + logger.info(f"[save_to_env_file] Successfully saved {key_name} to .env file") + return True + except Exception as e: + logger.error(f"[save_to_env_file] Error saving to .env file: {str(e)}") + return False + 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") try: + # Load environment variables + load_dotenv(override=True) + + # Get existing API keys from .env + openai_key = os.getenv('OPENAI_API_KEY', '') + gemini_key = os.getenv('GEMINI_API_KEY', '') + # Initialize wizard state if not already initialized if 'wizard_state' not in st.session_state: st.session_state.wizard_state = { @@ -35,10 +80,7 @@ def render_ai_providers(api_key_manager: APIKeyManager) -> Dict[str, Any]: # Main content st.markdown(""" -
-

🤖 AI Providers Setup

-

Configure your AI service providers for content generation

-
+

🤖 AI LLM Providers Setup

""", unsafe_allow_html=True) # Create tabs for different AI providers @@ -51,7 +93,6 @@ def render_ai_providers(api_key_manager: APIKeyManager) -> Dict[str, Any]: 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) @@ -59,26 +100,19 @@ def render_ai_providers(api_key_manager: APIKeyManager) -> Dict[str, Any]: 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_input = st.text_input( "OpenAI API Key", + value=openai_key, type="password", key="openai_key", - help="Enter your OpenAI API key" + help="Enter your OpenAI API key", + placeholder="Power your content generation with GPT-4 AI models" ) if openai_key: - if validate_api_key(openai_key): + st.success("✅ OpenAI API key found in environment") + elif openai_input: + if validate_api_key(openai_input): st.markdown("""
✓ API key configured @@ -102,32 +136,23 @@ def render_ai_providers(api_key_manager: APIKeyManager) -> Dict[str, Any]: **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", + gemini_input = st.text_input( + "Google Gemini API Key", + value=gemini_key, type="password", key="google_key", - help="Enter your Google API key" + help="Enter your Google API key", + placeholder="Power your content generation with Gemini AI models" ) - if google_key: - if validate_api_key(google_key): + if gemini_key: + st.success("✅ Gemini API key found in environment") + elif gemini_input: + if validate_api_key(gemini_input): st.markdown("""
✓ API key configured @@ -150,8 +175,6 @@ def render_ai_providers(api_key_manager: APIKeyManager) -> Dict[str, Any]: **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") @@ -193,10 +216,10 @@ def render_ai_providers(api_key_manager: APIKeyManager) -> Dict[str, Any]: st.info("Mistral integration will be available in the next update") # Track changes and validate keys - if any([openai_key, google_key]): + if any([openai_input, gemini_input]): 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): + if validate_api_key(openai_input) or validate_api_key(gemini_input): has_valid_key = True validation_message = "✅ At least one AI provider configured successfully" else: @@ -214,20 +237,33 @@ def render_ai_providers(api_key_manager: APIKeyManager) -> Dict[str, Any]: # 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(openai_input): + if save_to_env_file("OPENAI_API_KEY", openai_input): + logger.info("[render_ai_providers] OpenAI API key saved to .env file") + else: + st.error("Failed to save OpenAI API key to .env file") + return {"current_step": 1, "error": "Failed to save OpenAI API key"} - 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") + if validate_api_key(gemini_input): + if save_to_env_file("GEMINI_API_KEY", gemini_input): + logger.info("[render_ai_providers] Google Gemini API key saved to .env file") + else: + st.error("Failed to save Gemini API key to .env file") + return {"current_step": 1, "error": "Failed to save Gemini API key"} + + # Reload environment variables to ensure consistency + load_dotenv(override=True) + + # Get updated API keys from environment + updated_openai_key = os.getenv('OPENAI_API_KEY', '') + updated_gemini_key = os.getenv('GEMINI_API_KEY', '') + + # Store the API keys in session state + st.session_state['api_keys'] = { + 'openai': updated_openai_key, + 'google': updated_gemini_key + } # Update progress and move to next step st.session_state['current_step'] = 2 # Set the next step explicitly diff --git a/lib/utils/api_key_manager/components/ai_providers_setup.py b/lib/utils/api_key_manager/components/ai_providers_setup.py deleted file mode 100644 index 1ab045f6..00000000 --- a/lib/utils/api_key_manager/components/ai_providers_setup.py +++ /dev/null @@ -1,114 +0,0 @@ -"""AI providers setup component for API key manager.""" - -from typing import Dict, Any -from loguru import logger -import streamlit as st -import os -import sys - -def render_ai_providers_setup(api_key_manager) -> Dict[str, Any]: - """ - Render the AI providers setup component. - - Args: - api_key_manager: API key manager instance - - Returns: - Dict[str, Any]: Component state - """ - try: - logger.info("[render_ai_providers_setup] Rendering AI providers setup") - - # Display section header - st.header("Step 1: Select 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. - """) - - # Create columns for different providers - col1, col2 = st.columns(2) - - 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" - ) - - 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)} - - except Exception as e: - error_msg = f"Error in AI providers setup: {str(e)}" - logger.error(f"[render_ai_providers_setup] {error_msg}") - st.error(error_msg) - return {"current_step": 1, "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 37567968..1c81b394 100644 --- a/lib/utils/api_key_manager/components/ai_research_setup.py +++ b/lib/utils/api_key_manager/components/ai_research_setup.py @@ -29,10 +29,7 @@ def render_ai_research_setup(api_key_manager: APIKeyManager) -> Dict[str, Any]: logger.info("[render_ai_research_setup] Rendering AI research setup component") st.markdown(""" -
-

🔍 AI Research Setup

-

Configure your AI research providers for content analysis and research

-
+

🔍 AI Web Research API Setup

""", unsafe_allow_html=True) # Create two columns for different search types @@ -188,19 +185,7 @@ def render_ai_research_setup(api_key_manager: APIKeyManager) -> Dict[str, Any]: st.markdown("
", unsafe_allow_html=True) - # Disabled Options Expander - 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) + # Track changes changes_made = bool(serpapi_key or tavily_key or metaphor_key or firecrawl_key) @@ -246,56 +231,97 @@ def render_ai_research_setup(api_key_manager: APIKeyManager) -> Dict[str, Any]: st.error("Please configure at least one research provider to continue") # Detailed Information Section - st.markdown(""" - --- - ### Understanding Your Research Options + st.markdown("---") + 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 + # Create four columns for the information popovers + info_col1, info_col2, info_col3, info_col4 = st.columns(4) - #### AI Deep Research: Advanced Search Capabilities + # The Usual: Traditional Search Popover + with info_col1: + with st.popover("#### The Usual: Traditional Search"): + st.markdown(""" + **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 + """) - **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 + # AI Deep Research Popover + with info_col2: + with st.popover("#### AI Deep Research: Advanced Search Capabilities"): + st.markdown(""" + **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 + """) - **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 Popover + with info_col3: + with st.popover("#### Choosing the Right Tool"): + st.markdown(""" + 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. + """) - #### 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. - """) + # Coming Soon Popover + with info_col4: + with st.popover("#### 🔜 Coming Soon - More Search Options"): + st.markdown(""" + **Bing Search API** + - Microsoft's powerful search API with comprehensive capabilities + - Features include: + - Web search with advanced filtering + - News articles with sentiment analysis + - Image search with visual recognition + - Video search with content understanding + - Custom search parameters for targeted results + + **Google Search API** + - Google's programmable search engine with extensive features + - Capabilities include: + - Custom search engine creation + - Site-specific search + - Image and video search + - News search with time-based filtering + - Knowledge graph integration + + **Additional Planned Integrations:** + - **DuckDuckGo API**: Privacy-focused search with no tracking + - **Brave Search API**: Independent search engine with unique features + - **Perplexity API**: AI-powered research assistant with real-time data + + > **Note:** These integrations are under active development and will be available in future updates. + """) return {"current_step": 3, "changes_made": changes_made} diff --git a/lib/utils/api_key_manager/components/base.py b/lib/utils/api_key_manager/components/base.py index 3ea77723..feff7169 100644 --- a/lib/utils/api_key_manager/components/base.py +++ b/lib/utils/api_key_manager/components/base.py @@ -4,12 +4,6 @@ import streamlit as st from typing import Dict, Any from loguru import logger from ..styles import API_KEY_MANAGER_STYLES -from ..wizard_state import ( - 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.""" diff --git a/lib/utils/api_key_manager/components/website_setup.py b/lib/utils/api_key_manager/components/website_setup.py index 6ae840ca..f3548ff9 100644 --- a/lib/utils/api_key_manager/components/website_setup.py +++ b/lib/utils/api_key_manager/components/website_setup.py @@ -36,19 +36,20 @@ def render_website_setup(api_key_manager: APIKeyManager) -> Dict[str, Any]: """ logger.info("[render_website_setup] Rendering website setup component") - st.markdown("### Step 2: Website Setup") + st.markdown("### Step 2: Enter Your Website URL for Analysis (Optional)") # Create two columns for input and results col1, col2 = st.columns([1, 1]) with col1: - st.markdown("#### Enter Website URL") - url = st.text_input("Website URL", placeholder="https://example.com") - logger.debug(f"[render_website_setup] URL input value: {url}") + url = st.text_input("Enter your website URL, if you own one", placeholder="https://example.com") + logger.info(f"[render_website_setup] URL input value: {url}") analyze_type = st.radio( "Analysis Type", - ["Basic Analysis", "Full Analysis with SEO"], + ["Basic Website Analysis", "Full Website Analysis with SEO"], + horizontal=True, + label_visibility="hidden", help="Choose between basic website analysis or comprehensive SEO analysis" ) @@ -115,8 +116,6 @@ def render_website_setup(api_key_manager: APIKeyManager) -> Dict[str, Any]: st.warning("Please enter a valid URL") with col2: - st.markdown("#### Analysis Results") - # Check if we have analysis results if 'website_analysis' in st.session_state: results = st.session_state.website_analysis @@ -126,7 +125,7 @@ def render_website_setup(api_key_manager: APIKeyManager) -> Dict[str, Any]: analysis = data.get('analysis', {}) # Create tabs for different sections - if analyze_type == "Full Analysis with SEO": + if analyze_type == "Full Website Analysis with SEO": tab1, tab2, tab3, tab4, tab5 = st.tabs([ "Basic Metrics", "Content Analysis", @@ -237,7 +236,7 @@ def render_website_setup(api_key_manager: APIKeyManager) -> Dict[str, Any]: for issue in issues: st.write(f"• {issue}") - with tab4 if analyze_type == "Basic Analysis" else tab5: + with tab4 if analyze_type == "Basic Website Analysis" else tab5: st.markdown("##### Strategy Recommendations") strategy_info = analysis.get('strategy', {})