diff --git a/lib/ai_writers/linkedin_writer/README.md b/lib/ai_writers/linkedin_writer/README.md index 7ce65837..191c8247 100644 --- a/lib/ai_writers/linkedin_writer/README.md +++ b/lib/ai_writers/linkedin_writer/README.md @@ -333,7 +333,7 @@ To use the LinkedIn AI Writer: ## Requirements -- Python 3.11 +- Python 3.8+ - Streamlit - OpenAI API key (for GPT models) - Optional: LinkedIn API credentials (for future integration) diff --git a/lib/ai_writers/linkedin_writer/linkedin_ai_writer.py b/lib/ai_writers/linkedin_writer/linkedin_ai_writer.py index 2749de5b..f48be704 100644 --- a/lib/ai_writers/linkedin_writer/linkedin_ai_writer.py +++ b/lib/ai_writers/linkedin_writer/linkedin_ai_writer.py @@ -29,10 +29,24 @@ from .modules.article_generator.linkedin_article_generator import linkedin_artic from .modules.carousel_generator.linkedin_carousel_generator import linkedin_carousel_generator_ui from .modules.video_script_generator.linkedin_video_script_generator import linkedin_video_script_generator_ui from .modules.comment_response_generator.linkedin_comment_response_generator_ui import linkedin_comment_response_generator_ui +from .modules.profile_optimizer.linkedin_profile_optimizer_ui import linkedin_profile_optimizer_ui +from .modules.poll_generator import linkedin_poll_generator_ui +from .modules.company_page_generator import linkedin_company_page_generator_ui # Import image generation from ...gpt_providers.text_to_image_generation.main_generate_image_from_prompt import generate_image +# Create a wrapper for the async profile optimizer UI +def linkedin_profile_optimizer_ui_wrapper(): + """Wrapper function to call the async LinkedIn Profile Optimizer UI.""" + import asyncio + asyncio.run(linkedin_profile_optimizer_ui()) + +# Create a wrapper for the async company page generator UI +def linkedin_company_page_generator_ui_wrapper(): + """Wrapper function to call the async LinkedIn Company Page Generator UI.""" + import asyncio + asyncio.run(linkedin_company_page_generator_ui()) def linkedin_main_menu(): """Main function for the LinkedIn AI Writer.""" @@ -161,8 +175,8 @@ def linkedin_main_menu(): "description": "Enhance LinkedIn profiles to improve visibility and professional appeal.", "color": "#0A66C2", "category": "Profile & Personal Branding", - "function": None, - "status": "coming_soon", + "function": linkedin_profile_optimizer_ui_wrapper, + "status": "active", "features": [ "Headline optimization", "About section generation", @@ -182,8 +196,8 @@ def linkedin_main_menu(): "description": "Create engaging polls that drive interaction and gather insights.", "color": "#0A66C2", "category": "Profile & Personal Branding", - "function": None, - "status": "coming_soon", + "function": linkedin_poll_generator_ui, + "status": "active", "features": [ "Question formulation optimization", "Option generation based on topic", @@ -205,8 +219,8 @@ def linkedin_main_menu(): "description": "Create content for company pages that builds brand awareness and engagement.", "color": "#0A66C2", "category": "Business & Marketing", - "function": None, - "status": "coming_soon", + "function": linkedin_company_page_generator_ui_wrapper, + "status": "active", "features": [ "Company culture post generation", "Product/service announcement templates", @@ -516,7 +530,7 @@ def linkedin_main_menu(): """, unsafe_allow_html=True) # Add a button to access the tool - if st.button(f"Use {tool['name']}", key=f"btn_{tool['name']}"): + if st.button(f"Use {tool['name']}", key=f"btn_{tool['category']}_{tool['name']}"): # Store the selected tool in session state st.session_state.selected_tool = tool st.rerun() diff --git a/lib/ai_writers/linkedin_writer/modules/profile_optimizer/linkedin_profile_optimizer_ui.py b/lib/ai_writers/linkedin_writer/modules/profile_optimizer/linkedin_profile_optimizer_ui.py index 551ae076..439c5d86 100644 --- a/lib/ai_writers/linkedin_writer/modules/profile_optimizer/linkedin_profile_optimizer_ui.py +++ b/lib/ai_writers/linkedin_writer/modules/profile_optimizer/linkedin_profile_optimizer_ui.py @@ -31,31 +31,32 @@ async def linkedin_profile_optimizer_ui(): st.info("Upload your profile information for a comprehensive analysis") # Profile Data Input - with st.expander("Enter Profile Information", expanded=True): - profile_data = { - "headline": st.text_input("Current Headline"), - "about": st.text_area("About Section"), - "industry": st.text_input("Industry"), - "current_role": st.text_input("Current Role"), - "experience": [], - "skills": st.text_area("Current Skills (one per line)").split("\n"), - "education": st.text_area("Education (one per line)").split("\n") - } - - # Experience Input - st.subheader("Work Experience") - num_experiences = st.number_input("Number of experiences to add", min_value=0, max_value=10, value=1) - - for i in range(num_experiences): - with st.expander(f"Experience {i+1}"): - exp = { - "role": st.text_input(f"Role {i+1}"), - "company": st.text_input(f"Company {i+1}"), - "description": st.text_area(f"Description {i+1}") - } - profile_data["experience"].append(exp) + st.subheader("Enter Profile Information") + profile_data = { + "headline": st.text_input("Current Headline", key="profile_headline"), + "about": st.text_area("About Section", key="profile_about"), + "industry": st.text_input("Industry", key="profile_industry"), + "current_role": st.text_input("Current Role", key="profile_role"), + "experience": [], + "skills": st.text_area("Current Skills (one per line)", key="profile_skills").split("\n"), + "education": st.text_area("Education (one per line)", key="profile_education").split("\n") + } - if st.button("Analyze Profile"): + # Experience Input + st.subheader("Work Experience") + num_experiences = st.number_input("Number of experiences to add", min_value=0, max_value=10, value=1, key="profile_num_exp") + + for i in range(num_experiences): + st.markdown(f"**Experience {i+1}**") + exp = { + "role": st.text_input(f"Role {i+1}", key=f"profile_role_{i}"), + "company": st.text_input(f"Company {i+1}", key=f"profile_company_{i}"), + "description": st.text_area(f"Description {i+1}", key=f"profile_desc_{i}") + } + profile_data["experience"].append(exp) + st.divider() + + if st.button("Analyze Profile", key="profile_analyze_btn"): with st.spinner("Analyzing your profile..."): analysis = await optimizer.analyze_profile_strength(profile_data) @@ -83,11 +84,11 @@ async def linkedin_profile_optimizer_ui(): st.header("Headline Optimizer") st.info("Optimize your headline for better visibility and impact") - current_headline = st.text_input("Current Headline") - industry = st.text_input("Industry") - role = st.text_input("Current/Target Role") + current_headline = st.text_input("Current Headline", key="headline_current") + industry = st.text_input("Industry", key="headline_industry") + role = st.text_input("Current/Target Role", key="headline_role") - if st.button("Optimize Headline"): + if st.button("Optimize Headline", key="headline_optimize_btn"): with st.spinner("Generating optimized headline..."): headline_optimization = await optimizer.optimize_headline( current_headline, @@ -110,11 +111,11 @@ async def linkedin_profile_optimizer_ui(): st.header("About Section Generator") st.info("Create an engaging and professional About section") - current_about = st.text_area("Current About Section") - achievements = st.text_area("Key Achievements (one per line)").split("\n") - target_audience = st.text_input("Target Audience") + current_about = st.text_area("Current About Section", key="about_current") + achievements = st.text_area("Key Achievements (one per line)", key="about_achievements").split("\n") + target_audience = st.text_input("Target Audience", key="about_audience") - if st.button("Generate About Section"): + if st.button("Generate About Section", key="about_generate_btn"): with st.spinner("Generating optimized About section..."): about_optimization = await optimizer.generate_about_section( current_about, @@ -128,8 +129,9 @@ async def linkedin_profile_optimizer_ui(): st.subheader("Section Structure") for section, explanation in about_optimization['structure_explanation'].items(): - with st.expander(section): - st.write(explanation) + st.markdown(f"**{section}**") + st.write(explanation) + st.divider() st.subheader("Impact Factors") for factor in about_optimization['impact_factors']: @@ -141,44 +143,47 @@ async def linkedin_profile_optimizer_ui(): st.info("Enhance your work experience descriptions for maximum impact") experiences = [] - num_exp = st.number_input("Number of experiences to enhance", min_value=1, max_value=10, value=1) + num_exp = st.number_input("Number of experiences to enhance", min_value=1, max_value=10, value=1, key="exp_num") for i in range(num_exp): - with st.expander(f"Experience {i+1}"): - exp = { - "role": st.text_input(f"Role {i+1}"), - "company": st.text_input(f"Company {i+1}"), - "description": st.text_area(f"Current Description {i+1}") - } - experiences.append(exp) + st.markdown(f"**Experience {i+1}**") + exp = { + "role": st.text_input(f"Role {i+1}", key=f"exp_role_{i}"), + "company": st.text_input(f"Company {i+1}", key=f"exp_company_{i}"), + "description": st.text_area(f"Current Description {i+1}", key=f"exp_desc_{i}") + } + experiences.append(exp) + st.divider() - if st.button("Enhance Experiences"): + if st.button("Enhance Experiences", key="exp_enhance_btn"): with st.spinner("Enhancing experience descriptions..."): enhanced_experiences = await optimizer.enhance_experience_descriptions(experiences) for i, exp in enumerate(enhanced_experiences): - with st.expander(f"Enhanced Experience {i+1}"): - st.subheader(f"{exp['role']} at {exp['company']}") - st.markdown(exp['enhanced_description']) - - st.subheader("Key Achievements") - for achievement in exp['achievements']: - st.success(achievement) - - st.subheader("Keywords Used") - for keyword in exp['keywords']: - st.info(keyword) + st.markdown(f"**Enhanced Experience {i+1}**") + st.subheader(f"{exp['role']} at {exp['company']}") + st.markdown(exp['enhanced_description']) + + st.subheader("Key Achievements") + for achievement in exp['achievements']: + st.success(achievement) + + st.subheader("Keywords Used") + for keyword in exp['keywords']: + st.info(keyword) + + st.divider() # Skills Recommender Tab with tabs[4]: st.header("Skills Recommender") st.info("Get personalized skill recommendations for your profile") - current_skills = st.text_area("Current Skills (one per line)").split("\n") - industry = st.text_input("Industry (for skills)") - role = st.text_input("Role (for skills)") + current_skills = st.text_area("Current Skills (one per line)", key="skills_current").split("\n") + industry = st.text_input("Industry (for skills)", key="skills_industry") + role = st.text_input("Role (for skills)", key="skills_role") - if st.button("Get Skill Recommendations"): + if st.button("Get Skill Recommendations", key="skills_recommend_btn"): with st.spinner("Analyzing and recommending skills..."): skill_recommendations = await optimizer.recommend_skills( current_skills, @@ -204,6 +209,7 @@ async def linkedin_profile_optimizer_ui(): st.subheader("Skill Categories") for category, skills in skill_recommendations['skill_categories'].items(): - with st.expander(category): - for skill in skills: - st.write(f"- {skill}") \ No newline at end of file + st.markdown(f"**{category}**") + for skill in skills: + st.write(f"- {skill}") + st.divider() \ No newline at end of file diff --git a/lib/ai_writers/twitter_ai_writer.py b/lib/ai_writers/twitter_ai_writer.py deleted file mode 100644 index 719fd0e6..00000000 --- a/lib/ai_writers/twitter_ai_writer.py +++ /dev/null @@ -1,100 +0,0 @@ -import time #Iwish -import os -import json -import requests -import streamlit as st - -from ..gpt_providers.text_generation.main_text_generation import llm_text_gen - - -def tweet_writer(): - """ AI Tweet Generator """ - with st.expander("**PRO-TIP** - Read the instructions below.", expanded=True): - col1, col2 = st.columns([5, 5]) - with col1: - hook = st.text_input( - label="**What's the tweet about? (Hook)**", - placeholder="e.g., Discover the future of tech today!", - help="Provide a compelling opening statement or question to grab attention." - ) - - with col2: - target_audience = st.text_input( - label="**Target Audience**", - placeholder="e.g., technology enthusiasts, travel lovers", - help="Describe the audience you want to target with this tweet." - ) - - col3, col4 = st.columns([5, 5]) - with col3: - tweet_tone = st.selectbox( - label="**Tweet Tone**", - options=["Humorous", "Informative", "Inspirational", "Serious", "Casual"], - help="Choose the tone you'd like the tweet to have." - ) - - with col4: - cta = st.text_input( - label="**Call to Action (Optional)**", - placeholder="e.g., Retweet this if you agree! (Leave blank if not applicable)", - help="Provide a call to action if you'd like to include one." - ) - - col5, col6 = st.columns([5, 5]) - with col5: - keywords_hashtags = st.text_input( - label="**Keywords/Hashtags**", - placeholder="e.g., #AI #Innovation", - help="Provide 2-3 relevant keywords or hashtags." - ) - - with col6: - tweet_length = st.selectbox( - "Tweet Length (Optional)", - options=["Short (under 100 characters)", "Medium (100-200 characters)", "Long (200+ characters)"], - help="Choose the desired tweet length.", - ) - - if st.button('**Write Tweets**'): - if not target_audience or not hook: - st.error("đŸšĢ Please provide all required inputs.") - else: - with st.status("Assigning AI professional to write your tweets...", expanded=True) as status: - response = tweet_generator(target_audience, hook, tweet_tone, cta, keywords_hashtags, tweet_length) - if response: - st.subheader(f'**🧕👩: Your Tweets!**') - st.markdown(response) - else: - st.error("đŸ’Ĩ**Failed to generate tweets. Please try again!**") - - -def tweet_generator(target_audience, hook, tone_style, cta, keywords_hashtags, tweet_length): - """ Tweet Generator """ - - prompt = f""" - You are a social media expert creating tweets for an audience interested in {target_audience}. - Write 5 engaging, concise, and visually appealing tweets that each: - - 1. Start with a compelling hook based on the following input: "{hook}" - 2. Include the following call to action: "{cta}" - 3. Use 2-3 relevant keywords/hashtags, including: "{keywords_hashtags}" - 4. Adopt the following tone/style: "{tone_style}" - 5. Adhere to the following length requirement: {tweet_length} - - Make sure to keep the tone consistent with the selected style and platform context. - - Here are some examples of call-to-actions to include (if no specific CTA was provided): - - Retweet this if you agree! - - Share your thoughts in the comments! - - Learn more at [link] - - Follow for more {target_audience} content. - - Output each tweet separated by a newline. - """ - - try: - response = llm_text_gen(prompt) - return response - except Exception as err: - st.error(f"Exit: Failed to get response from LLM: {err}") - exit(1) diff --git a/lib/ai_writers/twitter_writers/tweet_generator/__init__.py b/lib/ai_writers/twitter_writers/tweet_generator/__init__.py new file mode 100644 index 00000000..5fa6c3b7 --- /dev/null +++ b/lib/ai_writers/twitter_writers/tweet_generator/__init__.py @@ -0,0 +1,9 @@ +""" +Twitter Tweet Generator Module + +A comprehensive suite of tools for generating and optimizing tweets. +""" + +from .smart_tweet_generator import smart_tweet_generator + +__all__ = ['smart_tweet_generator'] \ No newline at end of file diff --git a/lib/ai_writers/twitter_writers/twitter_dashboard.py b/lib/ai_writers/twitter_writers/twitter_dashboard.py index 5627b349..d69032ee 100644 --- a/lib/ai_writers/twitter_writers/twitter_dashboard.py +++ b/lib/ai_writers/twitter_writers/twitter_dashboard.py @@ -339,4 +339,4 @@ def smart_tweet_generator(): st.code(tweet_texts) if __name__ == "__main__": - smart_tweet_generator() \ No newline at end of file + smart_tweet_generator() diff --git a/lib/gpt_providers/text_generation/main_text_generation.py b/lib/gpt_providers/text_generation/main_text_generation.py index d3cd3e1b..68642f77 100644 --- a/lib/gpt_providers/text_generation/main_text_generation.py +++ b/lib/gpt_providers/text_generation/main_text_generation.py @@ -13,7 +13,7 @@ logger.add(sys.stdout, ) from .openai_text_gen import openai_chatgpt -from .gemini_pro_text import gemini_text_response +from .gemini_pro_text import gemini_text_response, gemini_structured_json_response from .anthropic_text_gen import anthropic_text_response from .deepseek_text_gen import deepseek_text_response from ...utils.read_main_config_params import read_return_config_section diff --git a/lib/utils/alwrity_utils.py b/lib/utils/alwrity_utils.py index 51cc726c..b4fad895 100644 --- a/lib/utils/alwrity_utils.py +++ b/lib/utils/alwrity_utils.py @@ -16,7 +16,7 @@ from lib.ai_writers.ai_news_article_writer import ai_news_generation from lib.ai_writers.ai_financial_writer import write_basic_ta_report from lib.ai_writers.ai_facebook_writer.facebook_ai_writer import facebook_main_menu from lib.ai_writers.linkedin_writer.linkedin_ai_writer import linkedin_main_menu -from lib.ai_writers.twitter_ai_writer import tweet_writer +from lib.ai_writers.twitter_writers.twitter_dashboard import run_dashboard from lib.ai_writers.insta_ai_writer import insta_writer from lib.ai_writers.youtube_writers.youtube_ai_writer import youtube_main_menu from lib.ai_writers.web_url_ai_writer import blog_from_url @@ -468,9 +468,9 @@ def ai_social_writer(): if "facebook" in selected_platform: facebook_main_menu() elif "linkedin" in selected_platform: - linked_post_writer() + linkedin_main_menu() elif "twitter" in selected_platform: - tweet_writer() + run_dashboard() elif "instagram" in selected_platform: insta_writer() elif "youtube" in selected_platform: diff --git a/lib/utils/api_key_manager/components/ai_providers.py b/lib/utils/api_key_manager/components/ai_providers.py index 441ef2f5..1bc3d92e 100644 --- a/lib/utils/api_key_manager/components/ai_providers.py +++ b/lib/utils/api_key_manager/components/ai_providers.py @@ -1,245 +1,32 @@ -"""AI providers setup component.""" +"""AI providers setup component - Wrapper for the actual setup UI.""" import streamlit as st from loguru import logger from typing import Dict, Any from ..manager import APIKeyManager -from .base import render_navigation_buttons, render_step_indicator, render_tab_style -from ..wizard_state import next_step, update_progress -from datetime import datetime - -def validate_api_key(key: str) -> bool: - """Validate if an API key is properly formatted.""" - if not key: - return False - # Basic validation - check if key is not empty and has minimum length - return len(key.strip()) > 0 +from .ai_providers_setup import render_ai_providers_setup # Import the refactored setup UI def render_ai_providers(api_key_manager: APIKeyManager) -> Dict[str, Any]: - """Render the AI providers setup step.""" - logger.info("[render_ai_providers] Starting AI providers setup") + """Renders the AI providers setup step by calling the dedicated setup function.""" + logger.debug("[render_ai_providers] Calling render_ai_providers_setup") try: - # Initialize wizard state if not already initialized - if 'wizard_state' not in st.session_state: - st.session_state.wizard_state = { - 'current_step': 1, - 'total_steps': 6, - 'progress': 0, - 'completed_steps': set(), - 'last_updated': datetime.now() - } - logger.info("[render_ai_providers] Initialized wizard state") + # The actual UI, saving, validation, and feedback are now handled within render_ai_providers_setup + # This function acts primarily as a placeholder in the step sequence if needed, + # or can be bypassed entirely if the main wizard calls render_ai_providers_setup directly. - # Store API key manager in session state for update_progress - st.session_state['api_key_manager'] = api_key_manager - - # Main content - st.markdown(""" -
-

🤖 AI Providers Setup

-

Configure your AI service providers for content generation

-
- """, unsafe_allow_html=True) - - # Create tabs for different AI providers - tabs = st.tabs(["Primary Providers", "Additional Providers"]) - - # Track if any changes were made - changes_made = False - has_valid_key = False - validation_message = "" - - with tabs[0]: - st.markdown("### Primary AI Providers") - st.markdown("Configure the main AI providers for content creation") - - # Create a grid layout for AI provider cards - col1, col2 = st.columns(2) - - with col1: - # OpenAI Card - with st.container(): - st.markdown(""" -
-
-
🤖
-
OpenAI
-
-
-

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

-
- """, unsafe_allow_html=True) - - openai_key = st.text_input( - "OpenAI API Key", - type="password", - key="openai_key", - help="Enter your OpenAI API key" - ) - - if openai_key: - if validate_api_key(openai_key): - st.markdown(""" -
- ✓ API key configured -
- """, unsafe_allow_html=True) - else: - st.markdown(""" -
- âš ī¸ Invalid API key format -
- """, unsafe_allow_html=True) - - with st.expander("📋 How to get your OpenAI API key", expanded=False): - st.markdown(""" - **Step-by-step guide:** - 1. Go to [OpenAI's website](https://platform.openai.com) - 2. Sign up or log in to your account - 3. Navigate to the API section - 4. Click "Create new secret key" - 5. Copy the generated key and paste it here - - **Note:** Keep your API key secure and never share it publicly. - """) - - st.markdown("
", unsafe_allow_html=True) - - with col2: - # Google Card - with st.container(): - st.markdown(""" -
-
-
🔍
-
Google Gemini
-
-
-

Leverage Google's powerful Gemini models

-
- """, unsafe_allow_html=True) - - google_key = st.text_input( - "Google API Key", - type="password", - key="google_key", - help="Enter your Google API key" - ) - - if google_key: - if validate_api_key(google_key): - st.markdown(""" -
- ✓ API key configured -
- """, unsafe_allow_html=True) - else: - st.markdown(""" -
- âš ī¸ Invalid API key format -
- """, unsafe_allow_html=True) - - with st.expander("📋 How to get your Google API key", expanded=False): - st.markdown(""" - **Step-by-step guide:** - 1. Visit [Google AI Studio](https://makersuite.google.com/app/apikey) - 2. Sign in with your Google account - 3. Click "Create API key" - 4. Copy the generated key and paste it here - - **Note:** Make sure to enable the Gemini API in your Google Cloud Console. - """) - - st.markdown("
", unsafe_allow_html=True) - - with tabs[1]: - st.markdown("### Additional AI Providers") - st.markdown("Configure additional AI providers for enhanced capabilities") - - # Create a grid layout for additional provider cards - col1, col2 = st.columns(2) - - with col1: - # Anthropic Card (Coming Soon) - with st.container(): - st.markdown(""" -
-
-
🧠
-
Anthropic Coming Soon
-
-
-

Access Claude for advanced content generation

-
-
- """, unsafe_allow_html=True) - st.info("Anthropic integration will be available in the next update") - - with col2: - # Mistral Card (Coming Soon) - with st.container(): - st.markdown(""" -
-
-
⚡
-
Mistral Coming Soon
-
-
-

Use Mistral's efficient language models

-
-
- """, unsafe_allow_html=True) - st.info("Mistral integration will be available in the next update") - - # Track changes and validate keys - if any([openai_key, google_key]): - changes_made = True - # Check if at least one valid API key is provided - if validate_api_key(openai_key) or validate_api_key(google_key): - has_valid_key = True - validation_message = "✅ At least one AI provider configured successfully" - else: - validation_message = "âš ī¸ Please provide at least one valid API key" - else: - validation_message = "âš ī¸ Please configure at least one AI provider to continue" - - # Display validation message - if validation_message: - if "✅" in validation_message: - st.success(validation_message) - else: - st.warning(validation_message) - - # Navigation buttons - if render_navigation_buttons(1, 6, changes_made): - if has_valid_key: - # Store the API keys in a separate session state key - st.session_state['api_keys'] = { - 'openai': openai_key if validate_api_key(openai_key) else None, - 'google': google_key if validate_api_key(google_key) else None - } - - # Save API keys to .env file - if validate_api_key(openai_key): - api_key_manager.save_api_key("openai", openai_key) - logger.info("[render_ai_providers] OpenAI API key saved to .env file") - - if validate_api_key(google_key): - api_key_manager.save_api_key("gemini", google_key) - logger.info("[render_ai_providers] Google Gemini API key saved to .env file") - - # Update progress and move to next step - st.session_state['current_step'] = 2 # Set the next step explicitly - update_progress() - st.rerun() # Rerun to apply the changes - else: - st.error("Please configure at least one valid AI provider to continue") - - return {"current_step": 1, "changes_made": changes_made} + # Store the manager instance if needed by other potential logic (unlikely now) + if 'api_key_manager' not in st.session_state: + st.session_state['api_key_manager'] = api_key_manager + + # Call the function that now contains all the rendering and logic for this step + component_state = render_ai_providers_setup(api_key_manager) + # Return the state from the setup function, although it might not be used directly + return component_state + except Exception as e: - error_msg = f"Error in AI providers setup: {str(e)}" - logger.error(f"[render_ai_providers] {error_msg}") - st.error(error_msg) - return {"current_step": 1, "error": error_msg} \ No newline at end of file + error_msg = f"Error calling AI providers setup: {str(e)}" + logger.error(f"[render_ai_providers] {error_msg}", exc_info=True) + st.error("An error occurred while setting up AI providers.") + # Ensure consistency in error return format if expected by the caller + return {"error": error_msg} diff --git a/lib/utils/api_key_manager/components/ai_providers_setup.py b/lib/utils/api_key_manager/components/ai_providers_setup.py index 1ab045f6..198d7011 100644 --- a/lib/utils/api_key_manager/components/ai_providers_setup.py +++ b/lib/utils/api_key_manager/components/ai_providers_setup.py @@ -5,110 +5,214 @@ from loguru import logger import streamlit as st import os import sys +# Corrected import: Assuming validation functions are in validation.py in the parent directory +from ..validation import ( + test_openai_api_key, + test_gemini_api_key, + # test_anthropic_api_key, # Keep commented if not used or add if needed + # test_deepseek_api_key, # Keep commented if not used or add if needed + 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": + # Ensure the function exists in validation.py + if callable(getattr(sys.modules[__name__], 'test_openai_api_key', None)): + is_valid = test_openai_api_key(key_value) + else: + logger.error("test_openai_api_key not found in validation module") + is_valid = False # Assume invalid if test func missing + elif provider_name == "gemini": + if callable(getattr(sys.modules[__name__], 'test_gemini_api_key', None)): + is_valid = test_gemini_api_key(key_value) + else: + logger.error("test_gemini_api_key not found in validation module") + is_valid = False + elif provider_name == "mistral": + if callable(getattr(sys.modules[__name__], 'test_mistral_api_key', None)): + is_valid = test_mistral_api_key(key_value) + else: + logger.error("test_mistral_api_key not found in validation module") + is_valid = False + else: + logger.warning(f"Validation not implemented for provider: {provider_name}") + return False # Or True if unknown providers are allowed without validation + + logger.info(f"Validation result for {provider_name}: {'Valid' if is_valid else 'Invalid'}") + return is_valid + except Exception as e: + logger.error(f"Error validating key for {provider_name}: {e}", exc_info=True) + return False + +# Callback function for handling API key input changes +def _handle_api_key_change(provider_name: str, api_key_manager): + """Save and validate API key when input changes.""" + key_input_widget_key = f"{provider_name}_key_input" + status_widget_key = f"{provider_name}_status" + + # Check if the input widget key exists in session state + if key_input_widget_key not in st.session_state: + logger.warning(f"Input widget key '{key_input_widget_key}' not found in session state.") + return + + key_value = st.session_state[key_input_widget_key] + current_status = st.session_state.get(status_widget_key) + + logger.debug(f"Handling change for {provider_name}. Key: {'***' if key_value else 'Empty'}. Current status: {current_status}") + + # If key is empty, reset status + if not key_value: + api_key_manager.save_api_key(provider_name, "") # Ensure empty key is saved + st.session_state[status_widget_key] = "unsaved" + logger.info(f"Cleared API key for {provider_name}.") + return + + # Set status to saving/validating + st.session_state[status_widget_key] = "saving" + st.rerun() # Rerun to show the spinner immediately + + try: + # Save the key using the manager + logger.debug(f"Saving key for {provider_name}...") + api_key_manager.save_api_key(provider_name, key_value) + logger.info(f"Saved API key for {provider_name}.") + + # Validate the key + is_valid = _validate_provider_key(provider_name, key_value) + + # Update status based on validation result + if is_valid: + st.session_state[status_widget_key] = "valid" + else: + st.session_state[status_widget_key] = "invalid" + + except Exception as e: + logger.error(f"Error during saving/validation for {provider_name}: {e}", exc_info=True) + st.session_state[status_widget_key] = "error" def render_ai_providers_setup(api_key_manager) -> Dict[str, Any]: """ - Render the AI providers setup component. + Render the AI providers setup component with immediate feedback. Args: api_key_manager: API key manager instance Returns: - Dict[str, Any]: Component state + Dict[str, Any]: Component state (not directly used here, handled by state manager) """ try: logger.info("[render_ai_providers_setup] Rendering AI providers setup") + # Initialize status in session state if not present + for provider in ["openai", "gemini", "mistral"]: + status_key = f"{provider}_status" + if status_key not in st.session_state: + # Check if a key exists and try to validate it on first load + existing_key = api_key_manager.get_api_key(provider) + if existing_key: + if _validate_provider_key(provider, existing_key): + st.session_state[status_key] = "valid" + else: + # Keep it unsaved/invalid on load if pre-existing key is bad + # Or maybe set to invalid? Let's choose unsaved for now. + st.session_state[status_key] = "invalid" + else: + st.session_state[status_key] = "unsaved" + # Display section header - st.header("Step 1: Select AI Providers") + st.header("Step 1: Configure AI Providers") st.markdown(""" Configure your AI providers to enable advanced content generation capabilities. - Choose and set up the AI services you want to use. + Enter your API keys below. They will be validated automatically. """) - # Create columns for different providers - col1, col2 = st.columns(2) + # --- OpenAI --- + st.subheader("OpenAI (Required)") + st.markdown("Get your API key from: [OpenAI Dashboard](https://platform.openai.com/account/api-keys)") + openai_key = api_key_manager.get_api_key("openai") + st.text_input( + "OpenAI API Key", + value=openai_key if openai_key else "", + type="password", + key="openai_key_input", + on_change=_handle_api_key_change, + args=("openai", api_key_manager) + ) + # Feedback Area for OpenAI + openai_status = st.session_state.get("openai_status", "unsaved") + feedback_placeholder_openai = st.empty() + if openai_status == "saving": + feedback_placeholder_openai.info("Validating OpenAI key...", icon="âŗ") + elif openai_status == "valid": + feedback_placeholder_openai.success("OpenAI key saved and valid!", icon="✅") + elif openai_status == "invalid": + feedback_placeholder_openai.error("Invalid OpenAI key. Please check and try again.", icon="❌") + elif openai_status == "error": + feedback_placeholder_openai.error("Error saving/validating OpenAI key.", icon="âš ī¸") - with col1: - st.subheader("OpenAI") - st.markdown(""" - OpenAI's GPT models provide powerful natural language processing capabilities. - - Get your API key from: [OpenAI Dashboard](https://platform.openai.com/account/api-keys) - """) - - openai_key = api_key_manager.get_api_key("openai") - openai_input = st.text_input( - "OpenAI API Key", - value=openai_key if openai_key else "", - type="password", - key="openai_key_input" - ) - - 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)} + # --- Google Gemini --- + st.subheader("Google Gemini (Required)") + st.markdown("Get your API key from: [Google AI Studio](https://makersuite.google.com/app/apikey)") + gemini_key = api_key_manager.get_api_key("gemini") + st.text_input( + "Gemini API Key", + value=gemini_key if gemini_key else "", + type="password", + key="gemini_key_input", + on_change=_handle_api_key_change, + args=("gemini", api_key_manager) + ) + # Feedback Area for Gemini + gemini_status = st.session_state.get("gemini_status", "unsaved") + feedback_placeholder_gemini = st.empty() + if gemini_status == "saving": + feedback_placeholder_gemini.info("Validating Gemini key...", icon="âŗ") + elif gemini_status == "valid": + feedback_placeholder_gemini.success("Gemini key saved and valid!", icon="✅") + elif gemini_status == "invalid": + feedback_placeholder_gemini.error("Invalid Gemini key. Please check and try again.", icon="❌") + elif gemini_status == "error": + feedback_placeholder_gemini.error("Error saving/validating Gemini key.", icon="âš ī¸") + + # --- Mistral AI (Optional) --- + st.subheader("Mistral AI (Optional)") + st.markdown("Get your API key from: [Mistral Platform](https://console.mistral.ai/api-keys/)") + mistral_key = api_key_manager.get_api_key("mistral") + st.text_input( + "Mistral API Key", + value=mistral_key if mistral_key else "", + type="password", + key="mistral_key_input", + on_change=_handle_api_key_change, + args=("mistral", api_key_manager) + ) + # Feedback Area for Mistral + mistral_status = st.session_state.get("mistral_status", "unsaved") + feedback_placeholder_mistral = st.empty() + if mistral_status == "saving": + feedback_placeholder_mistral.info("Validating Mistral key...", icon="âŗ") + elif mistral_status == "valid": + feedback_placeholder_mistral.success("Mistral key saved and valid!", icon="✅") + elif mistral_status == "invalid": + feedback_placeholder_mistral.error("Invalid Mistral key. Please check and try again.", icon="❌") + elif mistral_status == "error": + feedback_placeholder_mistral.error("Error saving/validating Mistral key.", icon="âš ī¸") + + # --- Final Notes --- + st.info("Note: At least one AI provider (OpenAI or Google Gemini) must have a valid API key to proceed.") + # Return value is not strictly needed if navigation relies on session state status + return {} + except Exception as e: - error_msg = f"Error in AI providers setup: {str(e)}" - logger.error(f"[render_ai_providers_setup] {error_msg}") + error_msg = f"Error rendering AI providers setup: {str(e)}" + logger.error(f"[render_ai_providers_setup] {error_msg}", exc_info=True) st.error(error_msg) - return {"current_step": 1, "error": error_msg} + return {"error": error_msg} diff --git a/lib/utils/api_key_manager/components/ai_research_setup.py b/lib/utils/api_key_manager/components/ai_research_setup.py index 6eeaf853..3df488b7 100644 --- a/lib/utils/api_key_manager/components/ai_research_setup.py +++ b/lib/utils/api_key_manager/components/ai_research_setup.py @@ -4,346 +4,236 @@ 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 +from dotenv import load_dotenv # Keep if api_key_manager uses it import sys +# Corrected import: Assuming validation functions are in validation.py in the parent directory +from ..validation import ( + test_serpapi_key, + test_tavily_key, + test_metaphor_key, + test_firecrawl_key + # Add others if needed later, e.g., test_bing_key, test_google_search_key +) -# Configure logger -logger.remove() # Remove default handler -logger.add( - "logs/ai_research_setup.log", - rotation="500 MB", - retention="10 days", - level="DEBUG", - format="{time:YYYY-MM-DD HH:mm:ss} | {level} | {message}" -) -logger.add( - sys.stdout, - level="INFO", - format="{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {message}" -) +# Configure logger (assuming configured elsewhere or keep minimal here) +logger.add(sys.stderr, level="INFO") # Keep simple example if needed + +# Helper function to validate a specific research provider's key +def _validate_research_key(provider_name: str, key_value: str) -> bool: + """Validate the API key for a given research provider.""" + if not key_value: + logger.debug(f"Validation: Key for {provider_name} is empty.") + return False + try: + logger.debug(f"Validating key for {provider_name}...") + # 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" def render_ai_research_setup(api_key_manager: APIKeyManager) -> Dict[str, Any]: - """Render the AI research setup step.""" + """Render the AI research setup step with immediate feedback.""" logger.info("[render_ai_research_setup] Rendering AI research setup component") + research_providers = ["serpapi", "tavily", "metaphor", "firecrawl"] + + # Initialize statuses + for provider in research_providers: + status_key = f"{provider}_status" + if status_key not in st.session_state: + existing_key = api_key_manager.get_api_key(provider) + if existing_key: + if _validate_research_key(provider, existing_key): + st.session_state[status_key] = "valid" + else: + st.session_state[status_key] = "invalid" + else: + st.session_state[status_key] = "unsaved" + st.markdown("""
-

🔍 AI Research Setup

-

Configure your AI research providers for content analysis and research

+

Step 3: Configure AI Research Tools (Optional)

+

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

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

Step-by-step guide:

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

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

-
-
-
- """, unsafe_allow_html=True) - - st.markdown("
", unsafe_allow_html=True) + # Feedback Area + serpapi_status = st.session_state.get("serpapi_status", "unsaved") + feedback_placeholder_serpapi = st.empty() + if serpapi_status == "saving": + feedback_placeholder_serpapi.info("Validating SerpAPI key...", icon="âŗ") + elif serpapi_status == "valid": + feedback_placeholder_serpapi.success("SerpAPI key saved and valid!", icon="✅") + elif serpapi_status == "invalid": + feedback_placeholder_serpapi.error("Invalid SerpAPI key.", icon="❌") + elif serpapi_status == "error": + feedback_placeholder_serpapi.error("Error saving/validating SerpAPI key.", icon="âš ī¸") - # Firecrawl Card - st.markdown(""" -
-
-
đŸ•ˇī¸
-
Firecrawl
-
-
- Web content extraction and analysis -
-
- """, unsafe_allow_html=True) - - firecrawl_key = st.text_input( + # --- Firecrawl --- + with col1: + st.subheader("Firecrawl") + st.markdown("Web content extraction and crawling. Get key: [Firecrawl](https://www.firecrawl.dev/account)") + firecrawl_key_val = api_key_manager.get_api_key("firecrawl") + st.text_input( "Firecrawl API Key", + value=firecrawl_key_val if firecrawl_key_val else "", type="password", - key="firecrawl_key", - help="Enter your Firecrawl API key" + key="firecrawl_key_input", + on_change=_handle_research_key_change, + args=("firecrawl", api_key_manager) ) - - if firecrawl_key: - st.markdown(""" -
- ✓ API key configured -
- """, unsafe_allow_html=True) - - st.markdown(""" -
-
- 📋 How to get your Firecrawl API key -
-

Step-by-step guide:

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

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

-
-
-
- """, unsafe_allow_html=True) - - st.markdown("
", unsafe_allow_html=True) - + # Feedback Area + firecrawl_status = st.session_state.get("firecrawl_status", "unsaved") + feedback_placeholder_firecrawl = st.empty() + if firecrawl_status == "saving": + feedback_placeholder_firecrawl.info("Validating Firecrawl key...", icon="âŗ") + elif firecrawl_status == "valid": + feedback_placeholder_firecrawl.success("Firecrawl key saved and valid!", icon="✅") + elif firecrawl_status == "invalid": + feedback_placeholder_firecrawl.error("Invalid Firecrawl key.", icon="❌") + elif firecrawl_status == "error": + feedback_placeholder_firecrawl.error("Error saving/validating Firecrawl key.", icon="âš ī¸") + + # --- Tavily --- with col2: - st.markdown("### AI Deep Research") - - # Tavily Card - st.markdown(""" -
-
-
🤖
-
Tavily AI
-
-
- AI-powered search with semantic understanding -
-
- """, unsafe_allow_html=True) - - tavily_key = st.text_input( + st.subheader("Tavily AI") + st.markdown("AI-powered search & summarization. Get key: [Tavily](https://tavily.com)") + tavily_key_val = api_key_manager.get_api_key("tavily") + st.text_input( "Tavily API Key", + value=tavily_key_val if tavily_key_val else "", type="password", - key="tavily_key", - help="Enter your Tavily API key" + key="tavily_key_input", + on_change=_handle_research_key_change, + args=("tavily", api_key_manager) ) - - if tavily_key: - st.markdown(""" -
- ✓ API key configured -
- """, unsafe_allow_html=True) - - st.markdown(""" -
-
- 📋 How to get your Tavily API key -
-

Step-by-step guide:

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

Note: Tavily provides AI-powered semantic search capabilities.

-
-
-
- """, unsafe_allow_html=True) - - st.markdown("
", unsafe_allow_html=True) - - # Metaphor/Exa Card - st.markdown(""" -
-
-
🧠
-
Metaphor/Exa
-
-
- Neural search engine for deep research -
-
- """, unsafe_allow_html=True) - - metaphor_key = st.text_input( + # Feedback Area + tavily_status = st.session_state.get("tavily_status", "unsaved") + feedback_placeholder_tavily = st.empty() + if tavily_status == "saving": + feedback_placeholder_tavily.info("Validating Tavily key...", icon="âŗ") + elif tavily_status == "valid": + feedback_placeholder_tavily.success("Tavily key saved and valid!", icon="✅") + elif tavily_status == "invalid": + feedback_placeholder_tavily.error("Invalid Tavily key.", icon="❌") + elif tavily_status == "error": + feedback_placeholder_tavily.error("Error saving/validating Tavily key.", icon="âš ī¸") + + # --- Metaphor/Exa --- + with col2: + st.subheader("Metaphor/Exa") + st.markdown("Neural search for deep research. Get key: [Metaphor/Exa](https://metaphor.systems)") + metaphor_key_val = api_key_manager.get_api_key("metaphor") + st.text_input( "Metaphor/Exa API Key", + value=metaphor_key_val if metaphor_key_val else "", type="password", - key="metaphor_key", - help="Enter your Metaphor/Exa API key" + key="metaphor_key_input", + on_change=_handle_research_key_change, + args=("metaphor", api_key_manager) ) - - if metaphor_key: - st.markdown(""" -
- ✓ API key configured -
- """, unsafe_allow_html=True) - - st.markdown(""" -
-
- 📋 How to get your Metaphor/Exa API key -
-

Step-by-step guide:

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

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

-
-
-
- """, unsafe_allow_html=True) - - st.markdown("
", unsafe_allow_html=True) - - # Disabled Options Expander + # Feedback Area + metaphor_status = st.session_state.get("metaphor_status", "unsaved") + feedback_placeholder_metaphor = st.empty() + if metaphor_status == "saving": + feedback_placeholder_metaphor.info("Validating Metaphor/Exa key...", icon="âŗ") + elif metaphor_status == "valid": + feedback_placeholder_metaphor.success("Metaphor/Exa key saved and valid!", icon="✅") + elif metaphor_status == "invalid": + feedback_placeholder_metaphor.error("Invalid Metaphor/Exa key.", icon="❌") + elif metaphor_status == "error": + feedback_placeholder_metaphor.error("Error saving/validating Metaphor/Exa key.", icon="âš ī¸") + + # --- Coming Soon --- with st.expander("🔜 Coming Soon - More Search Options", expanded=False): - st.markdown(""" -
-

Bing Search API

-

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

- -

Google Search API

-

Google's programmable search engine with customizable search parameters.

- -

These integrations are under development and will be available soon!

-
- """, unsafe_allow_html=True) - - # 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} + 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.") + + return {} diff --git a/lib/utils/api_key_manager/components/base.py b/lib/utils/api_key_manager/components/base.py index 953df736..66ed8c41 100644 --- a/lib/utils/api_key_manager/components/base.py +++ b/lib/utils/api_key_manager/components/base.py @@ -3,16 +3,17 @@ import streamlit as st from typing import Dict, Any from loguru import logger -from ..styles import API_KEY_MANAGER_STYLES +from ..styles import API_KEY_MANAGER_STYLES # Assuming styles are correctly imported from ..wizard_state import ( - get_current_step, - next_step, - previous_step, - can_proceed_to_next_step + get_current_step, # Keep if used elsewhere + next_step, # Keep if used elsewhere + previous_step, # Keep if used elsewhere + can_proceed_to_next_step # Keep if used elsewhere ) def render_step_indicator(current_step: int, total_steps: int) -> None: """Render the step indicator.""" + # Existing step indicator code... (Keep as is) try: st.markdown("""