ALwrity Version 0.5.0 (Fastapi + React )

This commit is contained in:
ajaysi
2025-08-06 12:48:02 +05:30
parent f28a919caa
commit 32f97fa6b3
476 changed files with 115544 additions and 28747 deletions

View File

@@ -1,159 +0,0 @@
# ALwrity Setup Guide: API Key Manager
## What is the API Key Manager?
The API Key Manager is a crucial component of ALwrity that helps you set up and configure all the necessary API keys and settings for your content creation workflow. It provides a user-friendly wizard interface to guide you through the setup process step by step.
## Setup Wizard Steps
### 1. Website Setup
- **Purpose**: Configure your website's basic information
- **Features**:
- Website URL configuration
- Site structure setup
- Basic SEO settings
- Content organization preferences
### 2. AI Research Setup
- **Purpose**: Set up AI-powered research capabilities
- **Features**:
- Research parameters configuration
- Data collection preferences
- Analysis settings
- Research depth options
### 3. AI Providers Configuration
- **Purpose**: Configure AI service providers
- **Supported Providers**:
- OpenAI (GPT models)
- Google (Gemini Pro)
- Anthropic (Claude)
- DeepSeek
- **Features**:
- API key management
- Model selection
- Usage preferences
- Cost optimization settings
### 4. Personalization Setup
- **Purpose**: Customize your content creation experience
- **Features**:
- Writing style preferences
- Tone settings
- Content structure templates
- Brand voice configuration
### 5. ALwrity Integrations
- **Purpose**: Set up additional tools and services
- **Features**:
- Third-party service connections
- Plugin configurations
- API integrations
- Workflow automation settings
### 6. Final Setup
- **Purpose**: Complete and verify your configuration
- **Features**:
- Configuration review
- Settings verification
- Test connections
- Setup completion
## How to Use the Setup Wizard
### 1. Starting the Setup
1. Launch ALwrity
2. Navigate to the Setup section
3. Begin the wizard process
### 2. Navigation
- Use the step indicator to track progress
- Navigate between steps using buttons
- Save progress automatically
- Return to previous steps if needed
### 3. Configuration Process
1. **Enter Information**: Fill in required details
2. **Verify Settings**: Review your inputs
3. **Test Connections**: Ensure everything works
4. **Complete Setup**: Finalize your configuration
## Managing API Keys
### 1. Key Storage
- Secure storage of API keys
- Environment variable management
- Key rotation support
- Access control
### 2. Key Validation
- Automatic key verification
- Usage monitoring
- Error handling
- Expiration tracking
### 3. Security Features
- Encrypted storage
- Access logging
- Permission management
- Secure transmission
## Progress Tracking
### 1. Setup Progress
- Visual progress indicator
- Step completion tracking
- Overall setup status
- Remaining tasks
### 2. Status Monitoring
- API key status
- Connection status
- Configuration status
- Error reporting
## Best Practices
### 1. Before Setup
- Gather all necessary API keys
- Review provider documentation
- Plan your configuration
- Backup existing settings
### 2. During Setup
- Follow the wizard steps
- Verify each configuration
- Test connections
- Save progress regularly
### 3. After Setup
- Review all settings
- Test functionality
- Document configurations
- Monitor usage
## Troubleshooting
### 1. Common Issues
- Invalid API keys
- Connection problems
- Configuration errors
- Setup interruptions
### 2. Solutions
- Key verification
- Connection testing
- Error logging
- Support resources
## Need Help?
If you encounter any issues during setup:
1. Check the error messages
2. Review the documentation
3. Verify your API keys
4. Contact ALwrity support
---
*Note: Keep your API keys secure and never share them. The API Key Manager helps you manage these keys safely while setting up ALwrity for optimal content creation.*

View File

@@ -1,37 +1,54 @@
"""API key manager package."""
"""API Key Manager package for ALwrity."""
from .manager import APIKeyManager
from .api_key_manager import (
initialize_wizard_state,
update_progress,
check_all_api_keys,
render,
render_navigation
from .api_key_manager import render, check_onboarding_completion, get_onboarding_status, reset_onboarding
from .onboarding_progress import (
OnboardingProgress,
get_onboarding_progress,
render_progress_indicator,
render_resume_message,
StepStatus,
StepData
)
from .components import (
render_website_setup,
render_ai_research_setup,
render_ai_providers,
render_final_setup,
render_personalization_setup,
render_alwrity_integrations,
from .validation import check_all_api_keys
from .components.base import (
render_step_indicator,
render_navigation_buttons,
render_step_indicator
render_step_validation,
render_resume_options
)
# Export all public components
__all__ = [
# Main classes
'APIKeyManager',
'initialize_wizard_state',
'update_progress',
'check_all_api_keys',
'OnboardingProgress',
'StepStatus',
'StepData',
# Main functions
'render',
'render_navigation',
'render_website_setup',
'render_ai_research_setup',
'render_ai_providers',
'render_final_setup',
'render_personalization_setup',
'render_alwrity_integrations',
'check_onboarding_completion',
'get_onboarding_status',
'reset_onboarding',
'get_onboarding_progress',
# UI components
'render_progress_indicator',
'render_resume_message',
'render_step_indicator',
'render_navigation_buttons',
'render_step_indicator'
]
'render_step_validation',
'render_resume_options',
# Validation
'check_all_api_keys'
]
# Version information
__version__ = "2.0.0"
__author__ = "ALwrity Team"
__description__ = "Comprehensive API key management and onboarding system for ALwrity"
# Note: FastAPI endpoints have been moved to the backend/ directory
# for better separation of concerns and enterprise architecture.

View File

@@ -1,165 +0,0 @@
"""API key manager for handling various API keys."""
from typing import Dict, Any, Optional
from loguru import logger
import streamlit as st
import os
import json
import sys
from datetime import datetime
from dotenv import load_dotenv
from .components.website_setup import render_website_setup
from .components.ai_research_setup import render_ai_research_setup
from .components.ai_providers import render_ai_providers
from .components.final_setup import render_final_setup
from .components.personalization_setup import render_personalization_setup
from .components.alwrity_integrations import render_alwrity_integrations
from .components.base import render_navigation_buttons, render_step_indicator
from .wizard_state import initialize_wizard_state, get_current_step, next_step, previous_step
from .manager import APIKeyManager
from .validation import check_all_api_keys
# Configure logger to output to both file and stdout
logger.remove() # Remove default handler
logger.add("logs/api_key_manager.log",
format="<green>{time:YYYY-MM-DD HH:mm:ss}</green> | <level>{level: <8}</level> | <cyan>{name}</cyan>:<cyan>{function}</cyan>:<cyan>{line}</cyan> - <level>{message}</level>",
level="DEBUG")
logger.add(sys.stdout,
format="<green>{time:YYYY-MM-DD HH:mm:ss}</green> | <level>{level: <8}</level> | <cyan>{name}</cyan>:<cyan>{function}</cyan>:<cyan>{line}</cyan> - <level>{message}</level>",
level="INFO")
def initialize_wizard_state():
"""Initialize or get the wizard state from session"""
logger.debug("Initializing wizard state")
if 'wizard_state' not in st.session_state:
st.session_state.wizard_state = {
'current_step': 0,
'total_steps': 0,
'completed_steps': set(),
'api_keys_status': {},
'setup_progress': 0
}
logger.info("Created new wizard state")
def update_progress():
"""Update the overall setup progress"""
logger.debug("Updating setup progress")
try:
# Get the API key manager instance from session state
api_key_manager = st.session_state.get('api_key_manager')
if not api_key_manager:
logger.warning("API key manager not found in session state")
return
total_keys = sum(len(keys) for keys in api_key_manager.api_key_groups.values())
configured_keys = sum(1 for status in st.session_state.wizard_state['api_keys_status'].values()
if status.get('configured', False))
progress = (configured_keys / total_keys) * 100
st.session_state.wizard_state['setup_progress'] = progress
logger.info(f"Updated progress to {progress:.1f}%")
except Exception as e:
logger.error(f"Error updating progress: {str(e)}", exc_info=True)
def render(api_key_manager: APIKeyManager) -> Dict[str, Any]:
"""
Render the API key manager interface.
Returns:
Dict[str, Any]: Current state
"""
try:
logger.info("[render] Rendering API key manager interface")
# Initialize session state for current step if not exists
if "current_step" not in st.session_state:
st.session_state.current_step = 1
logger.info("[render] Initialized current_step to 1")
# Display step indicator
render_step_indicator(st.session_state.current_step, 6)
# Render appropriate step based on current_step
if st.session_state.current_step == 1:
logger.info("[render] Rendering AI providers setup")
return render_ai_providers(api_key_manager)
elif st.session_state.current_step == 2:
logger.info("[render] Rendering website setup")
return render_website_setup(api_key_manager)
elif st.session_state.current_step == 3:
logger.info("[render] Rendering AI Research setup")
return render_ai_research_setup(api_key_manager)
elif st.session_state.current_step == 4:
logger.info("[render] Rendering personalization setup")
return render_personalization_setup(api_key_manager)
elif st.session_state.current_step == 5:
logger.info("[render] Rendering ALwrity integrations setup")
return render_alwrity_integrations(api_key_manager)
elif st.session_state.current_step == 6:
logger.info("[render] Rendering final setup")
return render_final_setup(api_key_manager)
except Exception as e:
error_msg = f"Error in API key manager: {str(e)}"
logger.error(f"[render] {error_msg}")
st.error(error_msg)
return {"current_step": st.session_state.current_step, "error": error_msg}
def render_navigation(self):
"""Render navigation buttons with proper state handling"""
st.markdown("""
<div class="nav-buttons">
""", unsafe_allow_html=True)
# Back button
if self.current_step > 1:
if st.button("← Back", key="back_button"):
self.current_step -= 1
st.rerun()
# Next/Continue button
if self.current_step < 3:
if st.button("Continue →", key="next_button"):
if self.current_step == 1:
# Validate at least one provider is configured
if not self.validate_providers():
st.error("Please configure at least one AI provider to continue.")
return
# Store all API keys in session state
st.session_state['api_keys'] = {
'openai': self.openai_key,
'google': self.google_key,
'anthropic': self.anthropic_key,
'mistral': self.mistral_key,
'serpapi': self.serpapi_key,
'google_search': self.google_search_key,
'google_search_cx': self.google_search_cx,
'bing_search': self.bing_search_key,
'tavily': self.tavily_key,
'metaphor': self.metaphor_key,
'wordpress': {
'url': self.wordpress_url,
'username': self.wordpress_username,
'password': self.wordpress_password,
'app_password': self.wordpress_app_password
}
}
self.current_step = 2
st.rerun()
elif self.current_step == 2:
# Validate WordPress credentials
if not self.validate_wordpress_credentials():
st.error("Please configure valid WordPress credentials to continue.")
return
# Store WordPress credentials in session state
st.session_state['wordpress_credentials'] = {
'url': self.wordpress_url,
'username': self.wordpress_username,
'password': self.wordpress_password,
'app_password': self.wordpress_app_password
}
self.current_step = 3
st.rerun()
st.markdown("</div>", unsafe_allow_html=True)

View File

@@ -1,76 +0,0 @@
"""API key manager components."""
import asyncio
import streamlit as st
import os
from loguru import logger
from .styles import API_KEY_MANAGER_STYLES
from .config import FEATURE_PREVIEWS, API_KEY_CONFIGS
from .wizard_state import (
get_current_step,
next_step,
previous_step,
set_selected_providers,
get_selected_providers,
set_website_url,
get_website_url,
set_api_key,
get_api_key,
can_proceed_to_next_step,
get_api_keys
)
from .health_monitor import APIKeyHealthMonitor
from .key_rotation import KeyRotationManager
from ...utils.website_analyzer import analyze_website
from .api_key_tests import (
test_openai_api_key,
test_gemini_api_key,
test_anthropic_api_key,
test_deepseek_api_key,
test_mistral_api_key
)
from .components.base import render_step_indicator, render_navigation_buttons, render_success_message
from .components import (
render_ai_providers,
render_website_setup,
render_health_monitoring,
render_ai_research_setup,
render_final_setup
)
def render_wizard():
"""Render the main wizard interface."""
st.title("API Key Setup Wizard")
# Get current step
current_step = get_current_step()
# Render step indicator
render_step_indicator()
# Render current step content
if current_step == 1:
render_ai_providers()
elif current_step == 2:
render_website_setup()
elif current_step == 3:
render_ai_research_setup()
elif current_step == 4:
render_final_setup()
elif current_step == 5:
render_health_monitoring()
# Render navigation buttons
render_navigation_buttons()
__all__ = [
'render_wizard',
'render_step_indicator',
'render_navigation_buttons',
'render_success_message',
'render_ai_providers',
'render_website_setup',
'render_ai_research_setup',
'render_health_monitoring',
'render_final_setup'
]

View File

@@ -1,281 +0,0 @@
"""AI providers setup component."""
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
import os
from dotenv import load_dotenv
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 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 = {
'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
st.session_state['api_key_manager'] = api_key_manager
# Main content
st.markdown("""
<div class='setup-header'><h2>🤖 AI LLM Providers Setup</h2></div>
""", 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")
# Create a grid layout for AI provider cards
col1, col2 = st.columns(2)
with col1:
# OpenAI Card
with st.container():
openai_input = st.text_input(
"OpenAI API Key",
value=openai_key,
type="password",
key="openai_key",
help="Enter your OpenAI API key",
placeholder="Power your content generation with GPT-4 AI models"
)
if openai_key:
st.success("✅ OpenAI API key found in environment")
elif openai_input:
if validate_api_key(openai_input):
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.
""")
with col2:
# Google Card
with st.container():
gemini_input = st.text_input(
"Google Gemini API Key",
value=gemini_key,
type="password",
key="google_key",
help="Enter your Google API key",
placeholder="Power your content generation with Gemini AI models"
)
if gemini_key:
st.success("✅ Gemini API key found in environment")
elif gemini_input:
if validate_api_key(gemini_input):
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.
""")
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_input, gemini_input]):
changes_made = True
# Check if at least one valid API key is provided
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:
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:
# Save API keys 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(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
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:
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}

View File

@@ -1,400 +0,0 @@
"""AI research setup component for the API key manager."""
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
import sys
# Configure logger
logger.remove() # Remove default handler
logger.add(
"logs/ai_research_setup.log",
rotation="500 MB",
retention="10 days",
level="DEBUG",
format="{time:YYYY-MM-DD HH:mm:ss} | {level} | {message}"
)
logger.add(
sys.stdout,
level="INFO",
format="<green>{time:YYYY-MM-DD HH:mm:ss}</green> | <level>{level: <8}</level> | <cyan>{message}</cyan>"
)
def get_existing_api_key(key_name: str) -> str:
"""Get existing API key from environment or .env file.
Args:
key_name (str): Name of the API key to retrieve
Returns:
str: The API key value if found, empty string otherwise
"""
# First try to get from environment
api_key = os.getenv(key_name)
# If not in environment, try to get from .env file
if not api_key and os.path.exists('.env'):
try:
with open('.env', 'r') as f:
for line in f:
if line.strip().startswith(f"{key_name}="):
api_key = line.strip().split('=')[1]
break
except Exception as e:
logger.error(f"[get_existing_api_key] Failed to read {key_name} from .env: {str(e)}")
return api_key if api_key else ""
def update_env_file(api_keys: Dict[str, str]) -> None:
"""Update the .env file with new API keys, avoiding duplicates.
Args:
api_keys (Dict[str, str]): Dictionary of API keys to update
"""
try:
# Read existing .env file content
env_content = []
if os.path.exists('.env'):
with open('.env', 'r') as f:
env_content = f.readlines()
# Remove trailing newlines and empty lines
env_content = [line.strip() for line in env_content if line.strip()]
# Create a dictionary of existing variables
env_dict = {}
for line in env_content:
if '=' in line:
key, value = line.split('=', 1)
env_dict[key.strip()] = value.strip()
# Update with new values
env_dict.update(api_keys)
# Write back to .env file
with open('.env', 'w') as f:
for key, value in env_dict.items():
f.write(f"{key}={value}\n")
logger.info("[update_env_file] Successfully updated .env file with API keys")
except Exception as e:
logger.error(f"[update_env_file] Error updating .env file: {str(e)}")
raise
def render_ai_research_setup(api_key_manager: APIKeyManager) -> Dict[str, Any]:
"""Render the AI research setup step."""
logger.info("[render_ai_research_setup] Rendering AI research setup component")
st.markdown("""
<div class='setup-header'><h2>🔍 AI Web Research API Setup</h2></div>
""", unsafe_allow_html=True)
# Create two columns for different search types
col1, col2 = st.columns(2)
with col1:
st.markdown("### The Usual")
# Get existing API keys
existing_serpapi_key = get_existing_api_key("SERPAPI_KEY")
existing_firecrawl_key = get_existing_api_key("FIRECRAWL_API_KEY")
serpapi_key = st.text_input(
"## Enter 🔎 SerpAPI",
value=existing_serpapi_key,
type="password",
key="serpapi_key",
help="Enter your SerpAPI key",
placeholder="Access search engine results for research"
)
if serpapi_key or existing_serpapi_key:
st.markdown("""
<div class="ai-provider-status status-valid">
✓ API key configured
</div>
""", unsafe_allow_html=True)
st.markdown("""
<div class="api-info-section">
<details>
<summary>📋 How to get your SerpAPI key</summary>
<div class="api-info-content">
<p><strong>Step-by-step guide:</strong></p>
<ol>
<li>Visit <a href="https://serpapi.com" target="_blank">SerpAPI</a></li>
<li>Create an account</li>
<li>Go to your dashboard</li>
<li>Copy your API key</li>
<li>Paste it here</li>
</ol>
<p><strong>Note:</strong> SerpAPI provides real-time search results from multiple engines.</p>
</div>
</details>
</div>
""", unsafe_allow_html=True)
st.markdown("</div></div>", unsafe_allow_html=True)
firecrawl_key = st.text_input(
"Enter 🕷️ Firecrawl API Key",
value=existing_firecrawl_key,
type="password",
key="firecrawl_key",
help="Enter your Firecrawl API key",
placeholder="Web content extraction and analysis"
)
if firecrawl_key or existing_firecrawl_key:
st.markdown("""
<div class="ai-provider-status status-valid">
✓ Firecrawl API key configured
</div>
""", unsafe_allow_html=True)
st.markdown("""
<div class="api-info-section">
<details>
<summary>📋 How to get your Firecrawl API key</summary>
<div class="api-info-content">
<p><strong>Step-by-step guide:</strong></p>
<ol>
<li>Visit <a href="https://www.firecrawl.dev/account" target="_blank">Firecrawl</a></li>
<li>Create an account</li>
<li>Go to your dashboard</li>
<li>Generate your API key</li>
<li>Copy and paste it here</li>
</ol>
<p><strong>Note:</strong> Firecrawl provides powerful web content extraction and analysis capabilities.</p>
</div>
</details>
</div>
""", unsafe_allow_html=True)
st.markdown("</div></div>", unsafe_allow_html=True)
with col2:
st.markdown("### AI Deep Research")
# Get existing API keys
existing_tavily_key = get_existing_api_key("TAVILY_API_KEY")
existing_metaphor_key = get_existing_api_key("METAPHOR_API_KEY")
tavily_key = st.text_input(
"Enter 🤖 Tavily API Key",
value=existing_tavily_key,
type="password",
key="tavily_key",
help="Enter your Tavily API key",
placeholder="AI-powered search with semantic understanding"
)
if tavily_key or existing_tavily_key:
st.markdown("""
<div class="ai-provider-status status-valid">
✓ Tavily API key configured
</div>
""", unsafe_allow_html=True)
st.markdown("""
<div class="api-info-section">
<details>
<summary>📋 How to get your Tavily API key</summary>
<div class="api-info-content">
<p><strong>Step-by-step guide:</strong></p>
<ol>
<li>Visit <a href="https://tavily.com" target="_blank">Tavily</a></li>
<li>Create an account</li>
<li>Go to API settings</li>
<li>Generate a new API key</li>
<li>Copy and paste it here</li>
</ol>
<p><strong>Note:</strong> Tavily provides AI-powered semantic search capabilities.</p>
</div>
</details>
</div>
""", unsafe_allow_html=True)
st.markdown("</div></div>", unsafe_allow_html=True)
metaphor_key = st.text_input(
"Enter 🧠 Metaphor/Exa API Key",
value=existing_metaphor_key,
type="password",
key="metaphor_key",
help="Enter your Metaphor/Exa API key",
placeholder="Neural search engine for deep research"
)
if metaphor_key or existing_metaphor_key:
st.markdown("""
<div class="ai-provider-status status-valid">
✓ API key configured
</div>
""", unsafe_allow_html=True)
st.markdown("""
<div class="api-info-section">
<details>
<summary>📋 How to get your Metaphor/Exa API key</summary>
<div class="api-info-content">
<p><strong>Step-by-step guide:</strong></p>
<ol>
<li>Visit <a href="https://metaphor.systems" target="_blank">Metaphor/Exa</a></li>
<li>Create an account</li>
<li>Navigate to API settings</li>
<li>Generate your API key</li>
<li>Copy and paste it here</li>
</ol>
<p><strong>Note:</strong> Metaphor/Exa provides neural search capabilities for deep research.</p>
</div>
</details>
</div>
""", unsafe_allow_html=True)
st.markdown("</div></div>", unsafe_allow_html=True)
# 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:
# Prepare API keys dictionary with only non-empty values
api_keys = {}
if serpapi_key:
api_keys['SERPAPI_KEY'] = serpapi_key
if tavily_key:
api_keys['TAVILY_API_KEY'] = tavily_key
if metaphor_key:
api_keys['METAPHOR_API_KEY'] = metaphor_key
if firecrawl_key:
api_keys['FIRECRAWL_API_KEY'] = firecrawl_key
# Update .env file with new API keys
update_env_file(api_keys)
# Update environment variables
for key, value in api_keys.items():
os.environ[key] = value
# 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("---")
st.markdown("### Understanding Your Research Options")
# Create four columns for the information popovers
info_col1, info_col2, info_col3, info_col4 = st.columns(4)
# 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
""")
# 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
""")
# 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.
""")
# 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}

View File

@@ -1,226 +0,0 @@
"""ALwrity integrations setup component."""
import streamlit as st
from loguru import logger
import os
from typing import Dict, Any
from ..manager import APIKeyManager
from .base import render_navigation_buttons, render_step_indicator, render_tab_style
def update_env_file(env_vars: Dict[str, str]) -> None:
"""Update the .env file with new environment variables, avoiding duplicates.
Args:
env_vars (Dict[str, str]): Dictionary of environment variables to update
"""
try:
# Read existing .env file content
env_content = []
if os.path.exists('.env'):
with open('.env', 'r') as f:
env_content = f.readlines()
# Remove trailing newlines and empty lines
env_content = [line.strip() for line in env_content if line.strip()]
# Create a dictionary of existing variables
env_dict = {}
for line in env_content:
if '=' in line:
key, value = line.split('=', 1)
env_dict[key.strip()] = value.strip()
# Update with new values
env_dict.update(env_vars)
# Write back to .env file
with open('.env', 'w') as f:
for key, value in env_dict.items():
f.write(f"{key}={value}\n")
logger.info("[update_env_file] Successfully updated .env file")
except Exception as e:
logger.error(f"[update_env_file] Error updating .env file: {str(e)}")
raise
def render_alwrity_integrations(api_key_manager: APIKeyManager) -> Dict[str, Any]:
"""Render the ALwrity integrations setup step."""
try:
# Apply enhanced tab styling
render_tab_style()
st.markdown("""
<div class='setup-header'>
<h2>🔄 ALwrity Integrations</h2>
<p>Connect your content platforms and tools</p>
</div>
""", unsafe_allow_html=True)
# Create tabs for different integration types
tabs = st.tabs(["Website Platforms", "Social Media", "Analytics Tools"])
changes_made = False
has_valid_integrations = False
validation_message = ""
with tabs[0]:
st.markdown("""
<div class="tab-content">
<h3>Website Platforms</h3>
<p>Connect your website platforms for seamless content publishing</p>
</div>
""", unsafe_allow_html=True)
# Website Platforms Grid
col1, col2 = st.columns(2)
with col1:
# WordPress Card (Coming Soon)
with st.container():
st.markdown("""
<div class="integration-card disabled">
<div class="integration-header">
<div class="integration-icon">🌐</div>
<div class="integration-title">WordPress <span class="coming-soon-badge">Coming Soon</span></div>
</div>
<div class="integration-content">
<p>Connect your WordPress site for direct content publishing.</p>
</div>
</div>
""", unsafe_allow_html=True)
st.info("WordPress integration will be available in the next update")
with col2:
# Wix Card (Coming Soon)
with st.container():
st.markdown("""
<div class="integration-card disabled">
<div class="integration-header">
<div class="integration-icon">🎨</div>
<div class="integration-title">Wix <span class="coming-soon-badge">Coming Soon</span></div>
</div>
<div class="integration-content">
<p>Connect your Wix site for direct content publishing.</p>
</div>
</div>
""", unsafe_allow_html=True)
st.info("Wix integration will be available in the next update")
with tabs[1]:
st.markdown("""
<div class="tab-content">
<h3>Social Media</h3>
<p>Connect your social media accounts for content distribution</p>
</div>
""", unsafe_allow_html=True)
# Social Media Grid
col1, col2 = st.columns(2)
with col1:
# Facebook Card (Coming Soon)
with st.container():
st.markdown("""
<div class="integration-card disabled">
<div class="integration-header">
<div class="integration-icon">📘</div>
<div class="integration-title">Facebook <span class="coming-soon-badge">Coming Soon</span></div>
</div>
<div class="integration-content">
<p>Connect your Facebook account for content sharing.</p>
</div>
</div>
""", unsafe_allow_html=True)
st.info("Facebook integration will be available in the next update")
with col2:
# Instagram Card (Coming Soon)
with st.container():
st.markdown("""
<div class="integration-card disabled">
<div class="integration-header">
<div class="integration-icon">📸</div>
<div class="integration-title">Instagram <span class="coming-soon-badge">Coming Soon</span></div>
</div>
<div class="integration-content">
<p>Connect your Instagram account for content sharing.</p>
</div>
</div>
""", unsafe_allow_html=True)
st.info("Instagram integration will be available in the next update")
with tabs[2]:
st.markdown("""
<div class="tab-content">
<h3>Analytics Tools</h3>
<p>Connect your analytics tools for content performance tracking</p>
</div>
""", unsafe_allow_html=True)
# Google Search Console Card (Coming Soon)
with st.container():
st.markdown("""
<div class="integration-card disabled">
<div class="integration-header">
<div class="integration-icon">📊</div>
<div class="integration-title">Google Search Console <span class="coming-soon-badge">Coming Soon</span></div>
</div>
<div class="integration-content">
<p>Connect your Google Search Console for SEO insights.</p>
</div>
</div>
""", unsafe_allow_html=True)
st.info("Google Search Console integration will be available in the next update")
# Validate integrations
changes_made = True # Always allow proceeding since integrations are coming soon
has_valid_integrations = True
validation_message = "✅ Website platform integrations will be available in the next update"
# 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(5, 6, changes_made):
if has_valid_integrations:
try:
# Store integration settings in session state
st.session_state['integrations'] = {
'coming_soon': {
'wordpress': True,
'wix': True,
'facebook': True,
'instagram': True,
'google_search_console': True
}
}
# Update INTEGRATION_DONE in .env file and environment
env_vars = {'INTEGRATION_DONE': 'True'}
update_env_file(env_vars)
# Update environment variable
os.environ['INTEGRATION_DONE'] = 'True'
logger.info("Updated INTEGRATION_DONE status")
# Update progress and move to next step
st.session_state['current_step'] = 6
st.rerun()
except Exception as e:
error_msg = f"Failed to update integration status: {str(e)}"
logger.error(error_msg)
st.error(error_msg)
else:
st.error("Please configure at least one integration to continue")
return {"current_step": 5, "changes_made": changes_made}
except Exception as e:
error_msg = f"Error in ALwrity integrations setup: {str(e)}"
logger.error(f"[render_alwrity_integrations] {error_msg}")
st.error(error_msg)
return {"current_step": 5, "error": error_msg}

View File

@@ -1,181 +0,0 @@
"""Base components for the API key manager."""
import streamlit as st
from typing import Dict, Any
from loguru import logger
from ..styles import API_KEY_MANAGER_STYLES
def render_step_indicator(current_step: int, total_steps: int) -> None:
"""Render the step indicator."""
try:
st.markdown("""
<style>
.step-indicator {
display: flex;
justify-content: space-between;
margin-bottom: 2rem;
padding: 1rem;
background: #f0f2f6;
border-radius: 10px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.step {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 1rem;
border-radius: 20px;
background: #ffffff;
transition: all 0.3s ease;
}
.step.active {
background: #1f77b4;
color: white;
}
.step.completed {
background: #2ecc71;
color: white;
}
.step-icon {
font-size: 1.2rem;
}
.step-number {
font-weight: bold;
}
.step-title {
font-size: 0.9rem;
}
.step-line {
flex: 1;
height: 2px;
background: #e0e0e0;
margin: 0 1rem;
}
.step-line.active {
background: #1f77b4;
}
.step-line.completed {
background: #2ecc71;
}
</style>
""", unsafe_allow_html=True)
steps = [
("🔑", "AI LLM", 1),
("🔍", "Website Analysis", 2),
("👤", "AI Research", 3),
("🎨", "Personalization", 4),
("🔄", "Integrations", 5),
("", "Complete", 6)
]
html = '<div class="step-indicator">'
for i, (icon, title, step) in enumerate(steps):
step_class = "active" if step == current_step else "completed" if step < current_step else ""
line_class = "active" if step == current_step else "completed" if step < current_step else ""
html += f'''
<div class="step {step_class}">
<span class="step-icon">{icon}</span>
<span class="step-number">{step}</span>
<span class="step-title">{title}</span>
</div>
'''
if i < len(steps) - 1:
html += f'<div class="step-line {line_class}"></div>'
html += '</div>'
st.markdown(html, unsafe_allow_html=True)
except Exception as e:
logger.error(f"Error rendering step indicator: {str(e)}")
st.error("Error displaying step indicator")
def render_navigation_buttons(current_step: int, total_steps: int, changes_made: bool = True) -> bool:
"""Render the navigation buttons with modern glassmorphic styling.
Args:
current_step (int): Current step number
total_steps (int): Total number of steps
changes_made (bool): Whether changes were made in the current step
Returns:
bool: True if next/complete button was clicked, False otherwise
"""
col1, col2, col3 = st.columns([1, 2, 1])
with col1:
if current_step > 1:
if st.button("**← Back**", use_container_width=True, key="back_button"):
from ..wizard_state import previous_step
previous_step()
st.rerun()
with col3:
if current_step < total_steps:
next_text = "**Continue →**"
if st.button(next_text, use_container_width=True, disabled=not changes_made, key="next_button"):
# Don't call next_step() here, let the component handle it
return True
else:
if st.button("**Complete Setup ✓**", use_container_width=True, type="primary", key="complete_button"):
# Save the configuration
st.success("✅ Setup completed successfully!")
return True
return False
def render_tab_style() -> None:
"""Render enhanced tab styling."""
st.markdown("""
<style>
.stTabs [data-baseweb="tab-list"] {
gap: 2rem;
background: #f8f9fa;
padding: 0.5rem;
border-radius: 10px;
margin-bottom: 1rem;
}
.stTabs [data-baseweb="tab"] {
padding: 0.75rem 1.5rem;
border-radius: 25px;
transition: all 0.3s ease;
background: transparent;
color: #495057;
font-weight: 500;
}
.stTabs [data-baseweb="tab"]:hover {
background: #e9ecef;
color: #1f77b4;
}
.stTabs [aria-selected="true"] {
background: #1f77b4 !important;
color: white !important;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.stTabs [data-baseweb="tab-list"] button:nth-child(1) {
margin-left: 0.5rem;
}
.stTabs [data-baseweb="tab-list"] button:nth-child(3) {
margin-right: 0.5rem;
}
.tab-content {
background: white;
padding: 1.5rem;
border-radius: 10px;
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
margin-top: 1rem;
}
</style>
""", unsafe_allow_html=True)
def render_success_message():
"""Render the success message with glassmorphic design."""
st.markdown("""
<div class="success-message">
<h3 style='color: white; margin-bottom: 12px; font-size: 1.4em;'>✅ API keys saved successfully!</h3>
<p style='color: rgba(255,255,255,0.95); font-size: 1.1em;'>
Please restart the application for the changes to take effect.
</p>
</div>
""", unsafe_allow_html=True)

View File

@@ -1,272 +0,0 @@
"""Final setup component for the API key manager."""
import streamlit as st
from loguru import logger
import sys
import json
import os
from typing import Dict, Any
from ..manager import APIKeyManager
from ..validation import check_all_api_keys
# Configure logger to output to both file and stdout
logger.remove() # Remove default handler
logger.add(
"logs/final_setup.log",
rotation="500 MB",
retention="10 days",
level="DEBUG",
format="{time:YYYY-MM-DD HH:mm:ss} | {level} | {message}"
)
logger.add(
sys.stdout,
level="INFO",
format="<green>{time:YYYY-MM-DD HH:mm:ss}</green> | <level>{level: <8}</level> | <cyan>{message}</cyan>"
)
def load_main_config() -> Dict[str, Any]:
"""Load the main configuration file."""
config_path = os.path.join("lib", "workspace", "alwrity_config", "main_config.json")
try:
with open(config_path, 'r') as f:
return json.load(f)
except Exception as e:
logger.error(f"Error loading main_config.json: {str(e)}")
return {}
def render_final_setup(api_key_manager: APIKeyManager) -> Dict[str, Any]:
"""Render the final setup step.
Args:
api_key_manager (APIKeyManager): The API key manager instance
Returns:
Dict[str, Any]: Current state
"""
logger.info("[render_final_setup] Rendering final setup component")
st.markdown("### Step 6: Final Setup & Validation")
# Load main config
main_config = load_main_config()
# Create tabs for each step
tabs = st.tabs([
"Step 1: AI LLM Setup",
"Step 2: Website Analysis",
"Step 3: AI Research",
"Step 4: Personalization",
"Step 5: Integrations"
])
# Step 1: AI LLM Setup
with tabs[0]:
st.markdown("#### AI LLM Configuration")
# Get API keys from environment
openai_key = os.getenv('OPENAI_API_KEY', 'Not configured')
gemini_key = os.getenv('GEMINI_API_KEY', 'Not configured')
anthropic_key = os.getenv('ANTHROPIC_API_KEY', 'Not configured')
mistral_key = os.getenv('MISTRAL_API_KEY', 'Not configured')
# Display API keys (masked)
st.markdown("##### API Keys")
col1, col2 = st.columns(2)
with col1:
st.markdown(f"**OpenAI API Key:** {'*' * 8}{openai_key[-4:] if openai_key != 'Not configured' else ''}")
st.markdown(f"**Google Gemini API Key:** {'*' * 8}{gemini_key[-4:] if gemini_key != 'Not configured' else ''}")
with col2:
st.markdown(f"**Anthropic API Key:** {'*' * 8}{anthropic_key[-4:] if anthropic_key != 'Not configured' else ''}")
st.markdown(f"**Mistral API Key:** {'*' * 8}{mistral_key[-4:] if mistral_key != 'Not configured' else ''}")
# Step 2: Website Analysis
with tabs[1]:
st.markdown("#### Website Analysis Configuration")
# Get website URL from environment
website_url = os.getenv('WEBSITE_URL', 'Not configured')
# Display website URL
st.markdown("##### Website URL")
st.markdown(f"**Website URL:** {website_url}")
# Display website analysis settings
st.markdown("##### Analysis Settings")
st.markdown("Website analysis settings will be used to understand your content style and preferences.")
# Step 3: AI Research
with tabs[2]:
st.markdown("#### AI Research Configuration")
# Get research API keys from environment
serpapi_key = os.getenv('SERPAPI_KEY', 'Not configured')
tavily_key = os.getenv('TAVILY_API_KEY', 'Not configured')
metaphor_key = os.getenv('METAPHOR_API_KEY', 'Not configured')
firecrawl_key = os.getenv('FIRECRAWL_API_KEY', 'Not configured')
# Display API keys (masked)
st.markdown("##### Research API Keys")
col1, col2 = st.columns(2)
with col1:
st.markdown(f"**SerpAPI Key:** {'*' * 8}{serpapi_key[-4:] if serpapi_key != 'Not configured' else ''}")
st.markdown(f"**Tavily API Key:** {'*' * 8}{tavily_key[-4:] if tavily_key != 'Not configured' else ''}")
with col2:
st.markdown(f"**Metaphor API Key:** {'*' * 8}{metaphor_key[-4:] if metaphor_key != 'Not configured' else ''}")
st.markdown(f"**Firecrawl API Key:** {'*' * 8}{firecrawl_key[-4:] if firecrawl_key != 'Not configured' else ''}")
# Step 4: Personalization
with tabs[3]:
st.markdown("#### Personalization Configuration")
# Display personalization settings from main config
with st.popover("Blog Content Characteristics", help="Click to see details about blog content settings"):
st.markdown("##### Blog Content Characteristics")
blog_settings = main_config.get("Blog Content Characteristics", {})
st.write(f"- Blog Length: {blog_settings.get('Blog Length', '2000')}")
st.write(f"- Blog Tone: {blog_settings.get('Blog Tone', 'Professional')}")
st.write(f"- Blog Demographic: {blog_settings.get('Blog Demographic', 'Professional')}")
st.write(f"- Blog Type: {blog_settings.get('Blog Type', 'Informational')}")
st.write(f"- Blog Language: {blog_settings.get('Blog Language', 'English')}")
st.write(f"- Blog Output Format: {blog_settings.get('Blog Output Format', 'markdown')}")
st.markdown("These settings define the overall structure and style of your blog content.")
with st.popover("Blog Images Details", help="Click to see details about image generation settings"):
st.markdown("##### Blog Images Details")
image_settings = main_config.get("Blog Images Details", {})
st.write(f"- Image Generation Model: {image_settings.get('Image Generation Model', 'stable-diffusion')}")
st.write(f"- Number of Blog Images: {image_settings.get('Number of Blog Images', 1)}")
st.markdown("These settings control how images are generated for your blog posts.")
with st.popover("LLM Options", help="Click to see details about language model settings"):
st.markdown("##### LLM Options")
llm_settings = main_config.get("LLM Options", {})
st.write(f"- GPT Provider: {llm_settings.get('GPT Provider', 'google')}")
st.write(f"- Model: {llm_settings.get('Model', 'gemini-1.5-flash-latest')}")
st.write(f"- Temperature: {llm_settings.get('Temperature', 0.7)}")
st.write(f"- Top-p: {llm_settings.get('Top-p', 0.9)}")
st.write(f"- Max Tokens: {llm_settings.get('Max Tokens', 4000)}")
st.write(f"- Frequency Penalty: {llm_settings.get('Frequency Penalty', 1.0)}")
st.write(f"- Presence Penalty: {llm_settings.get('Presence Penalty', 1.0)}")
st.markdown("These settings control the behavior of the language model used for content generation.")
with st.popover("Search Engine Parameters", help="Click to see details about search engine settings"):
st.markdown("##### Search Engine Parameters")
search_settings = main_config.get("Search Engine Parameters", {})
st.write(f"- Geographic Location: {search_settings.get('Geographic Location', 'us')}")
st.write(f"- Search Language: {search_settings.get('Search Language', 'en')}")
st.write(f"- Number of Results: {search_settings.get('Number of Results', 10)}")
st.write(f"- Time Range: {search_settings.get('Time Range', 'anytime')}")
st.markdown("These settings control how search engines are used for research and content creation.")
# Step 5: Integrations
with tabs[4]:
st.markdown("#### ALwrity Integrations Configuration")
# Display integrations settings
st.markdown("##### Website Platforms")
st.info("WordPress integration will be available in the next update")
st.info("Wix integration will be available in the next update")
st.markdown("##### Social Media")
st.info("Facebook integration will be available in the next update")
st.info("Instagram integration will be available in the next update")
st.markdown("##### Analytics Tools")
st.info("Google Search Console integration will be available in the next update")
# Navigation buttons
col1, col2 = st.columns(2)
with col1:
if st.button("← Back to Personalization"):
logger.info("[render_final_setup] User clicked back to personalization")
st.session_state.current_step = 4
st.session_state.next_step = "personalization_setup"
st.rerun()
with col2:
if st.button("Complete Setup →"):
logger.info("[render_final_setup] User clicked complete setup")
try:
# First set FINAL_SETUP_COMPLETE to True
try:
# Read existing .env content
env_lines = []
if os.path.exists('.env'):
with open('.env', 'r') as f:
env_lines = f.readlines()
# Remove any existing FINAL_SETUP_COMPLETE entries
env_lines = [line for line in env_lines if not line.startswith('FINAL_SETUP_COMPLETE=')]
# Add the new FINAL_SETUP_COMPLETE entry
env_lines.append("FINAL_SETUP_COMPLETE=True\n")
# Write back to .env file
with open('.env', 'w') as f:
f.writelines(env_lines)
# Set environment variable
os.environ['FINAL_SETUP_COMPLETE'] = "True"
logger.info("[render_final_setup] Set FINAL_SETUP_COMPLETE=True")
except Exception as e:
logger.error(f"[render_final_setup] Error setting FINAL_SETUP_COMPLETE: {str(e)}")
st.error("Error updating setup status. Please try again.")
return {"current_step": 6, "changes_made": False}
# Now validate all steps
validation_result = check_all_api_keys(api_key_manager)
if not validation_result:
# If validation fails, revert FINAL_SETUP_COMPLETE
try:
env_lines = [line for line in env_lines if not line.startswith('FINAL_SETUP_COMPLETE=')]
env_lines.append("FINAL_SETUP_COMPLETE=False\n")
with open('.env', 'w') as f:
f.writelines(env_lines)
os.environ['FINAL_SETUP_COMPLETE'] = "False"
except Exception:
pass # Ignore reversion errors
st.error("Setup validation failed. Please ensure all required steps are completed.")
logger.error("[render_final_setup] Validation failed")
return {"current_step": 6, "changes_made": False}
# Log the current API keys in the manager
logger.info("[render_final_setup] Current API keys in manager:")
for key, value in api_key_manager.api_keys.items():
if value:
logger.info(f" - {key}: {'*' * 8}{value[-4:]}")
else:
logger.info(f" - {key}: Not set")
# Save main configuration
config_path = os.path.join("lib", "workspace", "alwrity_config", "main_config.json")
with open(config_path, 'w') as f:
json.dump(main_config, f, indent=4)
logger.info("[render_final_setup] Saved main configuration")
# Show success message
st.success("✅ Setup completed successfully! Redirecting to main application...")
# Set setup completion flag in session state
st.session_state['setup_completed'] = True
st.session_state['redirect_to_main'] = True
# Clear the current step to ensure proper redirection
if 'current_step' in st.session_state:
del st.session_state['current_step']
# Rerun to trigger redirection
st.rerun()
except Exception as e:
error_msg = f"Error completing setup: {str(e)}"
logger.error(f"[render_final_setup] {error_msg}")
st.error(error_msg)
return {"current_step": 6, "changes_made": False}
return {"current_step": 6, "changes_made": True}

View File

@@ -1,39 +0,0 @@
"""Health monitoring component for the API key manager."""
import streamlit as st
from loguru import logger
from ..health_monitor import APIKeyHealthMonitor
from ..key_rotation import KeyRotationManager
from ..wizard_state import get_api_keys
def render_health_monitoring():
"""Render the API key health monitoring dashboard."""
st.header("API Key Health & Rotation")
# Initialize managers
health_monitor = APIKeyHealthMonitor()
rotation_manager = KeyRotationManager()
# Create tabs for different views
health_tab, rotation_tab = st.tabs(["Health Monitor", "Key Rotation"])
with health_tab:
health_monitor.get_health_dashboard()
with rotation_tab:
rotation_manager.display_rotation_dashboard()
# Manual rotation controls
st.subheader("Manual Controls")
key_type = st.selectbox(
"Select Key Type",
options=[k.split('_')[0] for k in get_api_keys()]
)
if key_type:
if st.button("Force Rotation"):
new_key = rotation_manager.rotate_if_needed(key_type)
if new_key:
st.success(f"Rotated to new key: {new_key}")
else:
st.warning("No suitable key available for rotation")

View File

@@ -1,487 +0,0 @@
"""Personalization setup component for the API key manager."""
import streamlit as st
from loguru import logger
import sys
import json
import os
from typing import Dict, Any
from ..manager import APIKeyManager
from ....web_crawlers.async_web_crawler import AsyncWebCrawlerService
from ....personalization.style_analyzer import StyleAnalyzer
from lib.utils.style_utils import (
get_test_config_styles,
get_glass_container,
get_info_section,
get_example_box,
get_analysis_section,
get_style_guide_html
)
from .base import render_navigation_buttons
from .alwrity_integrations import render_alwrity_integrations
import asyncio
import os
from pathlib import Path
import yaml
# Configure logger to output to both file and stdout
logger.remove() # Remove default handler
logger.add(
"logs/personalization_setup.log",
rotation="500 MB",
retention="10 days",
level="DEBUG",
format="{time:YYYY-MM-DD HH:mm:ss} | {level} | {message}"
)
logger.add(
sys.stdout,
level="INFO",
format="<green>{time:YYYY-MM-DD HH:mm:ss}</green> | <level>{level: <8}</level> | <cyan>{message}</cyan>"
)
def load_main_config() -> Dict[str, Any]:
"""Load the main configuration file."""
config_path = os.path.join("lib", "workspace", "alwrity_config", "main_config.json")
try:
with open(config_path, 'r') as f:
return json.load(f)
except Exception as e:
logger.error(f"Error loading main_config.json: {str(e)}")
return {}
def save_main_config(config: Dict[str, Any]) -> bool:
"""Save the main configuration file."""
try:
config_path = os.path.join("lib", "workspace", "alwrity_config", "main_config.json")
os.makedirs(os.path.dirname(config_path), exist_ok=True)
with open(config_path, 'w') as f:
json.dump(config, f, indent=4)
return True
except Exception as e:
logger.error(f"Error saving main_config.json: {str(e)}")
return False
def display_style_analysis(analysis_results: dict):
"""Display the style analysis results in a structured format."""
try:
# Writing Style Section
writing_style = analysis_results.get("writing_style", {})
writing_style_content = f"""
<ul>
<li><strong>Tone:</strong> {writing_style.get("tone", "N/A")}</li>
<li><strong>Voice:</strong> {writing_style.get("voice", "N/A")}</li>
<li><strong>Complexity:</strong> {writing_style.get("complexity", "N/A")}</li>
<li><strong>Formality:</strong> {writing_style.get("formality", "N/A")}</li>
</ul>
"""
st.markdown(get_analysis_section("Writing Style", writing_style_content), unsafe_allow_html=True)
# Target Audience Section
target_audience = analysis_results.get("target_audience", {})
target_audience_content = f"""
<ul>
<li><strong>Demographics:</strong> {', '.join(target_audience.get("demographics", ["N/A"]))}</li>
<li><strong>Expertise Level:</strong> {target_audience.get("expertise_level", "N/A")}</li>
<li><strong>Industry Focus:</strong> {target_audience.get("industry_focus", "N/A")}</li>
<li><strong>Geographic Focus:</strong> {target_audience.get("geographic_focus", "N/A")}</li>
</ul>
"""
st.markdown(get_analysis_section("Target Audience", target_audience_content), unsafe_allow_html=True)
# Content Type Section
content_type = analysis_results.get("content_type", {})
content_type_content = f"""
<ul>
<li><strong>Primary Type:</strong> {content_type.get("primary_type", "N/A")}</li>
<li><strong>Secondary Types:</strong> {', '.join(content_type.get("secondary_types", ["N/A"]))}</li>
<li><strong>Purpose:</strong> {content_type.get("purpose", "N/A")}</li>
<li><strong>Call to Action:</strong> {content_type.get("call_to_action", "N/A")}</li>
</ul>
"""
st.markdown(get_analysis_section("Content Type", content_type_content), unsafe_allow_html=True)
# Recommended Settings Section
recommended = analysis_results.get("recommended_settings", {})
recommended_content = f"""
<ul>
<li><strong>Writing Tone:</strong> {recommended.get("writing_tone", "N/A")}</li>
<li><strong>Target Audience:</strong> {recommended.get("target_audience", "N/A")}</li>
<li><strong>Content Type:</strong> {recommended.get("content_type", "N/A")}</li>
<li><strong>Creativity Level:</strong> {recommended.get("creativity_level", "N/A")}</li>
<li><strong>Geographic Location:</strong> {recommended.get("geographic_location", "N/A")}</li>
</ul>
"""
st.markdown(get_analysis_section("Recommended Settings", recommended_content), unsafe_allow_html=True)
except Exception as e:
logger.error(f"Error displaying style analysis: {str(e)}")
st.error(f"Error displaying analysis results: {str(e)}")
def render_personalization_setup(api_key_manager: APIKeyManager) -> Dict[str, Any]:
"""Render the personalization setup step."""
logger.info("[render_personalization_setup] Rendering personalization setup component")
st.markdown("""
# ✨ Personalization Setup
Configure your content generation preferences and writing style
""")
# Main section selection using radio buttons
setup_mode = st.radio(
"Choose Setup Mode",
["Manual Settings", "ALwrity Personalization"],
horizontal=True,
label_visibility="collapsed"
)
if setup_mode == "Manual Settings":
# Create tabs for different settings categories
tabs = st.tabs([
"Blog Content Characteristics",
"Blog Images",
"AI Generation Settings",
"Search Settings"
])
# Blog Content Characteristics Tab
with tabs[0]:
col1, col2 = st.columns([1, 1])
with col1:
st.markdown("#### Blog Content Characteristics")
blog_length = st.text_input(
"Blog Length",
value="2000",
placeholder="e.g., 2000",
help="Target word count for your blog posts"
)
blog_tone = st.selectbox(
"Blog Tone",
["Professional", "Casual", "Technical", "Conversational"],
help="The overall tone of your content"
)
blog_demographic = st.selectbox(
"Target Demographic",
["Professional", "General", "Technical", "Academic"],
help="Your primary audience demographic"
)
blog_type = st.selectbox(
"Content Type",
["Informational", "Educational", "Entertainment", "Technical"],
help="The primary type of content you create"
)
blog_language = st.selectbox(
"Content Language",
["English", "Spanish", "French", "German", "Other"],
help="Primary language for your content"
)
blog_format = st.selectbox(
"Output Format",
["markdown", "html", "plain text"],
help="Format of the generated content"
)
with col2:
st.markdown("### Blog Content Settings Guide")
st.markdown("""
#### Blog Length
- Determines word count target
- Affects content depth
- Impacts SEO performance
#### Blog Tone
- Professional: Business-oriented
- Casual: Friendly, approachable
- Technical: Detailed, precise
#### Best Practices
- Match tone to audience
- Consider SEO requirements
- Maintain consistency
""")
# Blog Images Tab
with tabs[1]:
col1, col2 = st.columns([1, 1])
with col1:
st.markdown("#### Blog Images Settings")
image_model = st.selectbox(
"Image Generation Model",
["stable-diffusion", "dall-e", "midjourney"],
help="AI model for generating images"
)
num_images = st.number_input(
"Number of Images",
min_value=1,
max_value=5,
value=1,
help="Number of images per blog post"
)
image_style = st.selectbox(
"Image Style",
["Realistic", "Artistic", "Professional", "Creative"],
help="Style of generated images"
)
with col2:
st.markdown("### Image Generation Guide")
st.markdown("""
#### Model Selection
- Stable Diffusion: Versatile, fast
- DALL-E: High quality, creative
- Midjourney: Artistic, detailed
#### Best Practices
- Consider content type
- Balance quality vs. speed
- Optimize for platforms
""")
# AI Generation Settings Tab
with tabs[2]:
col1, col2 = st.columns([1, 1])
with col1:
st.markdown("#### AI Generation Settings")
gpt_provider = st.selectbox(
"AI Provider",
["google", "openai", "anthropic"],
help="Choose your preferred AI provider"
)
model = st.text_input(
"Model",
value="gemini-1.5-flash-latest",
help="The specific AI model to use"
)
temperature = st.slider(
"Creativity Level",
min_value=0.0,
max_value=1.0,
value=0.7,
help="Higher = more creative, lower = more focused"
)
max_tokens = st.number_input(
"Maximum Length",
min_value=100,
max_value=8000,
value=4000,
help="Maximum length of generated content"
)
with col2:
st.markdown("### AI Settings Guide")
st.markdown("""
#### Provider Selection
- Google: Balanced, reliable
- OpenAI: Creative, versatile
- Anthropic: Precise, ethical
#### Temperature Guide
- 0.0-0.3: Focused, consistent
- 0.4-0.7: Balanced creativity
- 0.8-1.0: Highly creative
""")
# Search Settings Tab
with tabs[3]:
col1, col2 = st.columns([1, 1])
with col1:
st.markdown("#### Search Settings")
geo_location = st.text_input(
"Geographic Location",
value="us",
help="Target geographic location for search"
)
search_language = st.selectbox(
"Search Language",
["en", "es", "fr", "de", "other"],
help="Language for search results"
)
num_results = st.number_input(
"Number of Results",
min_value=1,
max_value=50,
value=10,
help="Number of search results to analyze"
)
time_range = st.selectbox(
"Time Range",
["anytime", "day", "week", "month", "year"],
help="Time range for search results"
)
with col2:
st.markdown("### Search Settings Guide")
st.markdown("""
#### Location & Language
- Affects result relevance
- Impacts local SEO
- Consider target market
#### Search Optimization
- Balance quantity vs. quality
- Consider time sensitivity
- Optimize for accuracy
""")
# Save button for manual settings
if st.button("Save Manual Settings", type="primary", use_container_width=True):
try:
# Save to main_config.json
config = {
"Blog Content Characteristics": {
"Blog Length": blog_length,
"Blog Tone": blog_tone,
"Blog Demographic": blog_demographic,
"Blog Type": blog_type,
"Blog Language": blog_language,
"Blog Output Format": blog_format
},
"Blog Images Details": {
"Image Generation Model": image_model,
"Number of Blog Images": num_images,
"Image Style": image_style
},
"LLM Options": {
"GPT Provider": gpt_provider,
"Model": model,
"Temperature": temperature,
"Max Tokens": max_tokens
},
"Search Engine Parameters": {
"Geographic Location": geo_location,
"Search Language": search_language,
"Number of Results": num_results,
"Time Range": time_range
}
}
if save_main_config(config):
try:
# Read existing .env file content
env_lines = []
if os.path.exists('.env'):
with open('.env', 'r') as f:
env_lines = f.readlines()
# Remove any existing PERSONALIZATION_DONE entries
env_lines = [line for line in env_lines if not line.startswith('PERSONALIZATION_DONE=')]
# Add new PERSONALIZATION_DONE entry
env_lines.append("PERSONALIZATION_DONE=True\n")
# Write back to .env file
with open('.env', 'w') as f:
f.writelines(env_lines)
# Update environment variable and session state
os.environ['PERSONALIZATION_DONE'] = "True"
st.session_state['personalization_saved'] = True
logger.info("Successfully set PERSONALIZATION_DONE=True in .env and environment")
st.success("✅ Your personalization settings have been saved successfully!")
except Exception as e:
logger.error(f"Error updating PERSONALIZATION_DONE: {str(e)}")
st.error("Settings saved but failed to update environment. Please try again.")
else:
st.error("Unable to save settings. Please try again.")
except Exception as e:
logger.error(f"Error saving settings: {str(e)}")
st.error(f"Failed to save settings: {str(e)}")
else: # ALwrity Personalization
col1, col2 = st.columns([2, 1])
with col1:
st.markdown("### Website URL")
url = st.text_input(
"Enter your website URL",
placeholder="https://example.com",
help="Provide your website URL to analyze your content style"
)
if not url:
st.markdown("### Written Samples")
st.info("No website URL? No problem! Provide written samples instead.")
samples = st.text_area(
"Paste your content samples here",
help="Paste 2-3 samples of your best content"
)
if st.button("🎨 Analyze Style", use_container_width=True):
# Existing style analysis code...
pass
with col2:
st.markdown("### How ALwrity Discovers Your Style")
st.markdown("""
#### AI-Powered Analysis
ALwrity analyzes your content to understand:
- Writing tone and voice
- Content structure
- Target audience
- Engagement style
#### Personalized Recommendations
We provide:
- Writing guidelines
- Content templates
- Style recommendations
- Audience insights
""")
# Navigation buttons
if render_navigation_buttons(4, 6, changes_made=True):
try:
# If user hasn't saved settings manually, mark as skipped
if 'personalization_saved' not in st.session_state or not st.session_state.get('personalization_saved'):
# Read existing .env file content
env_lines = []
if os.path.exists('.env'):
with open('.env', 'r') as f:
env_lines = f.readlines()
# Remove any existing PERSONALIZATION_DONE entries
env_lines = [line for line in env_lines if not line.startswith('PERSONALIZATION_DONE=')]
# Add PERSONALIZATION_DONE=False since user skipped
env_lines.append("PERSONALIZATION_DONE=False\n")
# Write back to .env file
with open('.env', 'w') as f:
f.writelines(env_lines)
# Update environment variable
os.environ['PERSONALIZATION_DONE'] = "False"
logger.info("User skipped personalization. Set PERSONALIZATION_DONE=False")
except Exception as e:
logger.error(f"Error updating PERSONALIZATION_DONE on skip: {str(e)}")
st.error("Error updating environment. You may need to configure personalization later.")
st.session_state.current_step = 5
st.rerun()
return {"current_step": 4, "changes_made": True}

View File

@@ -1,312 +0,0 @@
"""Website setup component for the API key manager."""
import streamlit as st
from loguru import logger
from ...website_analyzer import analyze_website
from ...website_analyzer.analyzer import WebsiteAnalyzer
import asyncio
import sys
from typing import Dict, Any
from ..manager import APIKeyManager
from .base import render_navigation_buttons
import os
# Configure logger to output to both file and stdout
logger.remove() # Remove default handler
logger.add(
"logs/website_setup.log",
rotation="50 MB",
retention="10 days",
level="DEBUG",
format="{time:YYYY-MM-DD HH:mm:ss} | {level} | {message}"
)
logger.add(
sys.stdout,
level="INFO",
format="<green>{time:YYYY-MM-DD HH:mm:ss}</green> | <level>{level: <8}</level> | <cyan>{message}</cyan>"
)
# Ensure logs directory exists
os.makedirs("logs", exist_ok=True)
def render_website_setup(api_key_manager: APIKeyManager) -> Dict[str, Any]:
"""Render the website setup step.
Args:
api_key_manager (APIKeyManager): The API key manager instance
Returns:
Dict[str, Any]: Current state
"""
logger.info("[render_website_setup] Rendering website setup component")
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:
# Get existing website URL from environment or .env file
existing_url = os.getenv('WEBSITE_URL', None)
if not existing_url and os.path.exists('.env'):
try:
with open('.env', 'r') as f:
for line in f:
if line.strip().startswith('WEBSITE_URL='):
existing_url = line.strip().split('=')[1]
break
except Exception as e:
logger.error(f"[render_website_setup] Failed to read existing URL from .env: {str(e)}")
# If existing_url is 'no_website_provided', set it to empty for better UX
if existing_url == 'no_website_provided':
existing_url = ''
url = st.text_input(
"Enter your website URL, if you own one",
value=existing_url if existing_url else "",
placeholder="https://example.com"
)
logger.info(f"[render_website_setup] URL input value: {url}")
# Save URL to .env file
try:
# Check if WEBSITE_URL already exists in .env file
website_url_exists = False
env_lines = []
if os.path.exists('.env'):
with open('.env', 'r') as f:
for line in f:
if line.strip().startswith('WEBSITE_URL='):
website_url_exists = True
# Replace the existing WEBSITE_URL line with the new value
if url:
env_lines.append(f"WEBSITE_URL={url}\n")
else:
env_lines.append("WEBSITE_URL=no_website_provided\n")
else:
env_lines.append(line)
# If WEBSITE_URL doesn't exist, add it
if not website_url_exists:
if url:
env_lines.append(f"WEBSITE_URL={url}\n")
else:
env_lines.append("WEBSITE_URL=no_website_provided\n")
# Write all lines back to the .env file
with open('.env', 'w') as f:
f.writelines(env_lines)
# Set environment variable
if url:
os.environ['WEBSITE_URL'] = url
logger.info(f"[render_website_setup] Saved website URL to .env: {url}")
else:
os.environ['WEBSITE_URL'] = "no_website_provided"
logger.info("[render_website_setup] Set default website URL: no_website_provided")
except Exception as e:
logger.error(f"[render_website_setup] Failed to save website URL: {str(e)}")
analyze_type = st.radio(
"Analysis Type",
["Basic Website Analysis", "Full Website Analysis with SEO"],
horizontal=True,
label_visibility="hidden",
help="Choose between basic website analysis or comprehensive SEO analysis"
)
if st.button("Analyze Website"):
if url:
with st.spinner("Analyzing website..."):
try:
logger.info(f"[render_website_setup] Starting website analysis for URL: {url}")
# Call the analyze_website function
results = analyze_website(url)
# Replace the old SEO analysis code with the new analyzer
analyzer = WebsiteAnalyzer()
seo_results = analyzer.analyze_website(url)
if seo_results.get('success', False):
results['data']['seo_analysis'] = seo_results['data']['analysis']['seo_info']
else:
results['data']['seo_analysis'] = {
'error': seo_results.get('error', 'Unknown error in SEO analysis'),
'overall_score': 0,
'meta_tags': {},
'content': {},
'recommendations': []
}
logger.debug(f"[render_website_setup] Analysis results received: {results.get('success', False)}")
# Store results in session state
st.session_state.website_analysis = results
logger.info("[render_website_setup] Results stored in session state")
if not results.get('success', False):
error_msg = results.get('error', 'Analysis failed')
logger.error(f"[render_website_setup] Analysis failed: {error_msg}")
st.error(error_msg)
else:
logger.info("[render_website_setup] Analysis completed successfully")
st.success("✅ Website analysis completed successfully!")
except Exception as e:
error_msg = f"Analysis failed: {str(e)}"
logger.error(f"[render_website_setup] {error_msg}")
st.error(error_msg)
else:
logger.warning("[render_website_setup] No URL provided")
st.warning("Please enter a valid URL")
with col2:
# Check if we have analysis results
if 'website_analysis' in st.session_state:
results = st.session_state.website_analysis
if results.get('success', False):
data = results.get('data', {})
analysis = data.get('analysis', {})
# Create tabs for different sections
if analyze_type == "Full Website Analysis with SEO":
tab1, tab2, tab3, tab4, tab5 = st.tabs([
"Basic Metrics",
"Content Analysis",
"SEO Analysis",
"Technical SEO",
"Strategy"
])
else:
tab1, tab2, tab3, tab4 = st.tabs([
"Basic Metrics",
"Content Analysis",
"Technical Info",
"Strategy"
])
with tab1:
st.markdown("##### Basic Metrics")
basic_info = analysis.get('basic_info', {})
st.write(f"Status Code: {basic_info.get('status_code')}")
st.write(f"Content Type: {basic_info.get('content_type')}")
st.write(f"Title: {basic_info.get('title')}")
st.write(f"Meta Description: {basic_info.get('meta_description')}")
# SSL Info
ssl_info = analysis.get('ssl_info', {})
if ssl_info.get('has_ssl'):
st.success("SSL Certificate is valid")
st.write(f"Expiry: {ssl_info.get('expiry')}")
else:
st.error("No valid SSL certificate found")
with tab2:
st.markdown("##### Content Analysis")
content_info = analysis.get('content_info', {})
# Content Overview
st.markdown("###### 📊 Content Overview")
col1, col2, col3, col4 = st.columns(4)
with col1:
st.metric("Word Count", content_info.get('word_count', 0))
with col2:
st.metric("Headings", content_info.get('heading_count', 0))
with col3:
st.metric("Images", content_info.get('image_count', 0))
with col4:
st.metric("Links", content_info.get('link_count', 0))
if analyze_type == "Full Analysis with SEO":
with tab3:
st.markdown("##### SEO Analysis")
seo_data = data.get('seo_analysis', {})
# Display SEO Score
seo_score = seo_data.get('overall_score', 0)
st.markdown(f"### SEO Score: {seo_score}/100")
st.progress(seo_score / 100)
# Meta Tags Analysis
st.markdown("#### Meta Tags Analysis")
meta_analysis = seo_data.get('meta_tags', {})
for key, value in meta_analysis.items():
if isinstance(value, bool):
st.write(f"{'' if value else ''} {key.replace('_', ' ').title()}")
elif isinstance(value, dict):
st.write(f"**{key.replace('_', ' ').title()}:**")
st.write(f"Status: {value.get('status', 'N/A')}")
st.write(f"Value: {value.get('value', 'N/A')}")
if value.get('recommendation'):
st.write(f"Recommendation: {value['recommendation']}")
else:
st.write(f"**{key.replace('_', ' ').title()}:** {value}")
# Content Analysis
st.markdown("#### AI Content Analysis")
content_analysis = seo_data.get('content', {})
st.write(f"**Word Count:** {content_analysis.get('word_count', 0)}")
st.write(f"**Readability Score:** {content_analysis.get('readability_score', 0)}/100")
st.write(f"**Content Quality Score:** {content_analysis.get('content_quality_score', 0)}/100")
# Recommendations
st.markdown("#### SEO Recommendations")
recommendations = seo_data.get('recommendations', [])
for rec in recommendations:
st.write(f"**{rec.get('priority', '').upper()} Priority - {rec.get('category', '')}**")
st.write(f"Issue: {rec.get('issue', '')}")
st.write(f"Recommendation: {rec.get('recommendation', '')}")
st.write(f"Impact: {rec.get('impact', '')}")
st.write("---")
with tab4:
st.markdown("##### Technical SEO")
technical_seo = seo_data.get('technical_analysis', {})
# Mobile Friendliness
st.markdown("#### Mobile Friendliness")
mobile_friendly = technical_seo.get('mobile_friendly', False)
st.write(f"{'' if mobile_friendly else ''} Mobile Friendly")
# Page Speed
st.markdown("#### Page Speed")
speed_metrics = technical_seo.get('speed_metrics', {})
for metric, value in speed_metrics.items():
st.write(f"**{metric.replace('_', ' ').title()}:** {value}")
# Technical Issues
st.markdown("#### Technical Issues")
issues = technical_seo.get('issues', [])
for issue in issues:
st.write(f"{issue}")
with tab4 if analyze_type == "Basic Website Analysis" else tab5:
st.markdown("##### Strategy Recommendations")
strategy_info = analysis.get('strategy', {})
if strategy_info:
for category, recommendations in strategy_info.items():
st.markdown(f"###### {category.replace('_', ' ').title()}")
for rec in recommendations:
st.write(f"{rec}")
else:
st.info("No strategy recommendations available")
else:
error_msg = results.get('error', 'Analysis failed')
logger.error(f"[render_website_setup] Displaying error: {error_msg}")
st.error(error_msg)
else:
logger.debug("[render_website_setup] No analysis results in session state")
st.info("Enter a URL and click 'Analyze Website' to see results")
# Navigation buttons
if render_navigation_buttons(2, 5, True):
# Move to next step (AI Research Setup)
st.session_state.current_step = 3
st.session_state.next_step = "ai_research_setup"
st.rerun()
return {"current_step": 2, "changes_made": True}

View File

@@ -1,121 +0,0 @@
"""API Key Rotation Manager."""
from datetime import datetime
from typing import Dict, Optional, List
import streamlit as st
from .health_monitor import APIKeyHealthMonitor
from .wizard_state import get_api_keys, set_api_key
class KeyRotationManager:
"""Manages automatic rotation of API keys based on health metrics."""
def __init__(self):
"""Initialize the key rotation manager."""
self.health_monitor = APIKeyHealthMonitor()
if 'active_keys' not in st.session_state:
st.session_state.active_keys = {}
def get_active_key(self, key_type: str) -> str:
"""Get the currently active key for a given type."""
return st.session_state.active_keys.get(key_type)
def set_active_key(self, key_type: str, key_name: str) -> None:
"""Set the active key for a given type."""
st.session_state.active_keys[key_type] = key_name
def rotate_if_needed(self, key_type: str) -> Optional[str]:
"""Check and rotate key if needed based on health metrics."""
current_key = self.get_active_key(key_type)
# If no current key or current key needs rotation
if not current_key or self.health_monitor.should_rotate_key(current_key):
new_key = self.health_monitor.get_best_available_key(key_type)
if new_key and new_key != current_key:
# Set cooldown on the old key if it exists
if current_key:
self.health_monitor.set_cooldown(current_key, duration_minutes=30)
# Update the active key
self.set_active_key(key_type, new_key)
return new_key
return current_key
def get_rotation_status(self) -> Dict[str, Dict]:
"""Get rotation status for all key types."""
status = {}
api_keys = get_api_keys()
for key_name in api_keys:
key_type = key_name.split('_')[0] # e.g., OPENAI from OPENAI_API_KEY
active_key = self.get_active_key(key_type)
health = self.health_monitor.get_key_health(key_name)
if key_type not in status:
status[key_type] = {
'active_key': active_key,
'available_keys': [],
'cooldown_keys': []
}
if health and health['in_cooldown']:
status[key_type]['cooldown_keys'].append(key_name)
else:
status[key_type]['available_keys'].append(key_name)
return status
def display_rotation_dashboard(self) -> None:
"""Display the key rotation dashboard."""
st.subheader("🔄 API Key Rotation Status")
rotation_status = self.get_rotation_status()
if not rotation_status:
st.info("No API keys configured for rotation.")
return
for key_type, status in rotation_status.items():
with st.expander(f"{key_type} Rotation Status"):
# Active Key
st.write("**Active Key:**")
if status['active_key']:
st.success(status['active_key'])
else:
st.warning("No active key")
# Available Keys
st.write("**Available Keys:**")
if status['available_keys']:
for key in status['available_keys']:
st.write(f"- {key}")
else:
st.warning("No available keys")
# Cooldown Keys
if status['cooldown_keys']:
st.write("**Keys in Cooldown:**")
for key in status['cooldown_keys']:
health = self.health_monitor.get_key_health(key)
if health and health['cooldown_until']:
time_left = (health['cooldown_until'] - datetime.now())
minutes_left = int(time_left.total_seconds() / 60)
st.info(f"- {key} (Cooldown: {minutes_left} minutes remaining)")
def initialize_rotation(self) -> None:
"""Initialize key rotation for all API key types."""
api_keys = get_api_keys()
key_types = set()
# Get unique key types
for key_name in api_keys:
key_type = key_name.split('_')[0]
key_types.add(key_type)
# Initialize rotation for each key type
for key_type in key_types:
if not self.get_active_key(key_type):
best_key = self.health_monitor.get_best_available_key(key_type)
if best_key:
self.set_active_key(key_type, best_key)

View File

@@ -1,238 +0,0 @@
"""API key manager class."""
from typing import Dict, Any, Optional
from loguru import logger
import streamlit as st
import os
import json
import sys
from datetime import datetime
from dotenv import load_dotenv
# Configure logger to output to both file and stdout
logger.remove() # Remove default handler
logger.add("logs/api_key_manager.log",
format="<green>{time:YYYY-MM-DD HH:mm:ss}</green> | <level>{level: <8}</level> | <cyan>{name}</cyan>:<cyan>{function}</cyan>:<cyan>{line}</cyan> - <level>{message}</level>",
level="DEBUG")
logger.add(sys.stdout,
format="<green>{time:YYYY-MM-DD HH:mm:ss}</green> | <level>{level: <8}</level> | <cyan>{name}</cyan>:<cyan>{function}</cyan>:<cyan>{line}</cyan> - <level>{message}</level>",
level="INFO")
class APIKeyManager:
"""Manager for handling API keys."""
def __init__(self):
"""Initialize the API key manager."""
logger.info("[APIKeyManager.__init__] Initializing API key manager")
self.api_keys = {}
self.load_api_keys()
self.api_key_groups = {
"Create": {
"GEMINI_API_KEY": {
"url": "https://makersuite.google.com/app/apikey",
"description": "Google's Gemini AI for content generation",
"setup_steps": [
"Visit Google AI Studio",
"Create a Google Cloud account",
"Enable Gemini API",
"Generate API key"
]
},
"OPENAI_API_KEY": {
"url": "https://platform.openai.com/api-keys",
"description": "OpenAI's GPT models for content creation",
"setup_steps": [
"Go to OpenAI platform",
"Create an account",
"Navigate to API keys",
"Create new API key"
]
},
"MISTRAL_API_KEY": {
"url": "https://console.mistral.ai/api-keys/",
"description": "Mistral AI for efficient content generation",
"setup_steps": [
"Visit Mistral AI website",
"Sign up for an account",
"Access API section",
"Generate API key"
]
}
},
"Research": {
"TAVILY_API_KEY": {
"url": "https://tavily.com/#api",
"description": "Powers intelligent web research features",
"setup_steps": [
"Go to Tavily's website",
"Create an account",
"Access your API dashboard",
"Generate a new API key"
]
},
"SERPER_API_KEY": {
"url": "https://serper.dev/signup",
"description": "Enables Google search functionality",
"setup_steps": [
"Visit Serper.dev",
"Sign up for an account",
"Go to API section",
"Create your API key"
]
}
},
"Deep Search": {
"METAPHOR_API_KEY": {
"url": "https://dashboard.exa.ai/login",
"description": "Enables advanced web search capabilities",
"setup_steps": [
"Visit the Exa AI dashboard",
"Sign up for a free account",
"Navigate to API Keys section",
"Create a new API key"
]
},
"FIRECRAWL_API_KEY": {
"url": "https://www.firecrawl.dev/account",
"description": "Enables web content extraction",
"setup_steps": [
"Visit Firecrawl website",
"Sign up for an account",
"Access API dashboard",
"Create your API key"
]
}
},
"Integrations": {
"STABILITY_API_KEY": {
"url": "https://platform.stability.ai/",
"description": "Enables AI image generation",
"setup_steps": [
"Access Stability AI platform",
"Create an account",
"Navigate to API settings",
"Generate your API key"
]
}
}
}
def load_api_keys(self):
"""Load API keys from environment variables."""
try:
logger.info("[APIKeyManager.load_api_keys] Loading API keys from environment")
# Get the current working directory and .env file path
current_dir = os.getcwd()
env_path = os.path.join(current_dir, '.env')
logger.info(f"[APIKeyManager.load_api_keys] Looking for .env file at: {env_path}")
# Check if .env file exists
if not os.path.exists(env_path):
logger.warning(f"[APIKeyManager.load_api_keys] .env file not found at {env_path}")
return
# Load environment variables
load_dotenv(env_path, override=True)
logger.debug("[APIKeyManager.load_api_keys] Environment variables loaded")
# Define all possible API key providers
all_providers = [
# AI Providers
'OPENAI_API_KEY',
'GEMINI_API_KEY',
'ANTHROPIC_API_KEY',
'MISTRAL_API_KEY',
# Research Providers
'SERPER_API_KEY',
'TAVILY_API_KEY',
'METAPHOR_API_KEY',
'FIRECRAWL_API_KEY'
]
# Load API keys from environment variables
for provider in all_providers:
value = os.getenv(provider)
if value:
self.api_keys[provider] = value
logger.info(f"[APIKeyManager.load_api_keys] Loaded {provider} from environment")
else:
logger.debug(f"[APIKeyManager.load_api_keys] {provider} not found in environment")
logger.info(f"[APIKeyManager.load_api_keys] Loaded {len(self.api_keys)} API keys")
except Exception as e:
logger.error(f"[APIKeyManager.load_api_keys] Error loading API keys: {str(e)}")
def save_api_key(self, provider: str, api_key: str) -> bool:
"""
Save an API key for a provider.
Args:
provider: The provider name (e.g., 'openai', 'gemini')
api_key: The API key value
Returns:
bool: True if successful, False otherwise
"""
try:
logger.info(f"[APIKeyManager] Saving API key for {provider}")
# Map provider to environment variable name
env_var_map = {
'openai': 'OPENAI_API_KEY',
'gemini': 'GEMINI_API_KEY',
'mistral': 'MISTRAL_API_KEY',
'anthropic': 'ANTHROPIC_API_KEY',
'serpapi': 'SERPAPI_API_KEY',
'tavily': 'TAVILY_API_KEY',
'metaphor': 'METAPHOR_API_KEY',
'firecrawl': 'FIRECRAWL_API_KEY'
}
env_var = env_var_map.get(provider)
if not env_var:
logger.error(f"[APIKeyManager] Unknown provider: {provider}")
return False
# Update the in-memory dictionary
self.api_keys[provider] = api_key
# Update environment variable
os.environ[env_var] = api_key
# Read existing .env file content
env_path = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__)))), '.env')
try:
with open(env_path, 'r', encoding='utf-8') as f:
lines = f.readlines()
except FileNotFoundError:
lines = []
# Update or add the API key
key_found = False
updated_lines = []
for line in lines:
if line.startswith(f"{env_var}="):
updated_lines.append(f"{env_var}={api_key}\n")
key_found = True
else:
updated_lines.append(line)
if not key_found:
updated_lines.append(f"{env_var}={api_key}\n")
# Write back to .env file
with open(env_path, 'w', encoding='utf-8') as f:
f.writelines(updated_lines)
logger.info(f"[APIKeyManager] Successfully saved API key for {provider}")
return True
except Exception as e:
logger.error(f"[APIKeyManager] Error saving API key for {provider}: {str(e)}")
return False
def get_api_key(self, provider: str) -> Optional[str]:
"""Get an API key."""
return self.api_keys.get(provider)

View File

@@ -1,37 +0,0 @@
"""State management for the API key manager."""
import streamlit as st
from datetime import datetime
def initialize_wizard_state():
"""Initialize or get the wizard state from session."""
if 'wizard_state' not in st.session_state:
st.session_state.wizard_state = {
'current_step': 0,
'total_steps': 0,
'completed_steps': set(),
'api_keys_status': {},
'setup_progress': 0
}
def update_progress(api_keys_config):
"""Update the overall setup progress."""
total_keys = sum(len(keys) for keys in api_keys_config.values())
configured_keys = sum(1 for status in st.session_state.wizard_state['api_keys_status'].values()
if status.get('configured', False))
st.session_state.wizard_state['setup_progress'] = (configured_keys / total_keys) * 100
def update_key_status(key):
"""Update the status of an API key in the wizard state."""
st.session_state.wizard_state['api_keys_status'][key] = {
'configured': True,
'timestamp': datetime.now().isoformat()
}
def get_key_status(key):
"""Get the current status of an API key."""
return st.session_state.wizard_state['api_keys_status'].get(key, {})
def get_progress():
"""Get the current setup progress."""
return st.session_state.wizard_state['setup_progress']

View File

@@ -1,482 +0,0 @@
API_KEY_MANAGER_STYLES = """
<style>
/* Main container */
.main .block-container {
padding-top: 0.5rem;
}
/* Step indicator */
.step-indicator {
display: flex;
flex-direction: row;
gap: 0.25rem;
margin: 0.5rem 0;
padding: 0.5rem;
background: linear-gradient(135deg, #1a365d, #2c5282);
border-radius: 2px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
width: 100%;
justify-content: center;
}
.step {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.25rem 0.75rem;
border-radius: 3px;
transition: all 0.3s ease;
position: relative;
font-size: 0.85rem;
white-space: nowrap;
}
.step:not(:last-child)::after {
content: '';
position: absolute;
right: -0.25rem;
top: 50%;
transform: translateY(-50%);
width: 0.5rem;
height: 2px;
background: rgba(255, 255, 255, 0.3);
}
.step.completed {
background: rgba(255, 255, 255, 0.1);
}
.step.current {
background: rgba(255, 255, 255, 0.2);
font-weight: 600;
}
.step.upcoming {
opacity: 0.7;
}
.step-number {
display: flex;
align-items: center;
justify-content: center;
width: 5px;
height: 5px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.2);
font-weight: 600;
font-size: 0.5rem;
flex-shrink: 0;
}
.step.completed .step-number {
background: #4CAF50;
}
.step.current .step-number {
background: #2196F3;
}
.step.upcoming .step-number {
background: rgba(255, 255, 255, 0.3);
}
.step-content {
display: flex;
flex-direction: row;
gap: 0.5rem;
align-items: center;
}
.step-title {
font-weight: 500;
color: white;
font-size: 0.85rem;
}
.step-description {
font-size: 0.7rem;
color: rgba(255, 255, 255, 0.8);
}
/* Navigation buttons */
.nav-buttons {
display: flex;
justify-content: space-between;
margin-top: 1rem;
gap: 1rem;
}
.nav-button {
background: linear-gradient(135deg, #1a365d, #2c5282);
color: white;
border: none;
padding: 0.5rem 1rem;
border-radius: 6px;
cursor: pointer;
transition: all 0.3s ease;
font-weight: 500;
font-size: 0.9rem;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.nav-button:hover {
background: linear-gradient(135deg, #2c5282, #1a365d);
transform: translateY(-1px);
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.1);
}
.nav-button:disabled {
background: #94a3b8;
cursor: not-allowed;
transform: none;
box-shadow: none;
}
/* Form elements */
.stTextInput input {
border: 1px solid #e2e8f0;
border-radius: 6px;
padding: 0.5rem;
transition: all 0.3s ease;
font-size: 0.9rem;
}
.stTextInput input:focus {
border-color: #2c5282;
box-shadow: 0 0 0 2px rgba(44, 82, 130, 0.1);
}
.stSelectbox select {
border: 1px solid #e2e8f0;
border-radius: 6px;
padding: 0.5rem;
transition: all 0.3s ease;
font-size: 0.9rem;
}
.stSelectbox select:focus {
border-color: #2c5282;
box-shadow: 0 0 0 2px rgba(44, 82, 130, 0.1);
}
/* Success message */
.success-message {
background: linear-gradient(135deg, #059669, #10b981);
color: white;
padding: 1rem;
border-radius: 6px;
margin: 0.75rem 0;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
font-size: 0.9rem;
}
/* Error message */
.error-message {
background: linear-gradient(135deg, #dc2626, #ef4444);
color: white;
padding: 1rem;
border-radius: 6px;
margin: 0.75rem 0;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
font-size: 0.9rem;
}
/* Loading spinner */
.loading-spinner {
display: flex;
justify-content: center;
align-items: center;
padding: 1.5rem;
}
/* Card styling */
.card {
background: white;
border-radius: 2px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
padding: 0.5rem;
margin-bottom: 0.5rem;
transition: all 0.3s ease;
}
.card:hover {
transform: translateY(-1px);
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.1);
}
/* Glassmorphic effect */
.glass {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 2px;
padding: 1rem;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
/* Gradient text */
.gradient-text {
background: linear-gradient(135deg, #1a365d, #2c5282);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
font-weight: 600;
}
/* Hide sidebar */
section[data-testid="stSidebar"] {
display: none;
}
/* AI Provider Cards */
.ai-provider-card {
background: white;
border-radius: 2px;
padding: 0.5rem;
margin-bottom: 0.75rem;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
transition: all 0.3s ease;
border: 1px solid #e2e8f0;
}
.ai-provider-card:hover {
transform: translateY(-1px);
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.1);
}
.ai-provider-header {
display: flex;
align-items: center;
gap: 0.75rem;
margin-bottom: 0.75rem;
}
.ai-provider-icon {
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 6px;
background: linear-gradient(135deg, #1a365d, #2c5282);
color: white;
font-size: 16px;
}
.ai-provider-title {
font-size: 1rem;
font-weight: 600;
color: #1a365d;
}
.ai-provider-description {
color: #4a5568;
font-size: 0.85rem;
margin-bottom: 0.75rem;
}
.ai-provider-input {
margin-top: 0.75rem;
}
.ai-provider-status {
display: flex;
align-items: center;
gap: 0.5rem;
margin-top: 0.5rem;
font-size: 0.85rem;
}
.status-valid {
color: #059669;
}
.status-invalid {
color: #dc2626;
}
/* Coming Soon Badge */
.coming-soon-badge {
display: inline-block;
padding: 0.25rem 0.5rem;
background: linear-gradient(135deg, #4a5568, #2d3748);
color: white;
border-radius: 9999px;
font-size: 0.7rem;
font-weight: 500;
margin-left: 0.5rem;
}
/* Main container styles */
.setup-header {
background: linear-gradient(135deg, #1f77b4 0%, #2ecc71 100%);
padding: 2rem;
border-radius: 15px;
margin-bottom: 2rem;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
.setup-header h2 {
color: white;
margin: 0;
font-size: 2rem;
}
.setup-header p {
color: rgba(255, 255, 255, 0.9);
margin: 0.5rem 0 0;
font-size: 1.1rem;
}
/* AI Provider Card styles */
.ai-provider-card {
background: white;
border-radius: 15px;
padding: 1.5rem;
margin-bottom: 1.5rem;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
transition: all 0.3s ease;
border: 1px solid rgba(0, 0, 0, 0.05);
}
.ai-provider-card:hover {
transform: translateY(-2px);
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.1);
}
.ai-provider-card.disabled {
opacity: 0.7;
background: #f8f9fa;
cursor: not-allowed;
}
.ai-provider-header {
display: flex;
align-items: center;
gap: 1rem;
margin-bottom: 1rem;
}
.ai-provider-icon {
font-size: 2rem;
background: #f8f9fa;
width: 3rem;
height: 3rem;
display: flex;
align-items: center;
justify-content: center;
border-radius: 10px;
color: #1f77b4;
}
.ai-provider-title {
font-size: 1.2rem;
font-weight: 600;
color: #2c3e50;
}
.ai-provider-content {
color: #6c757d;
font-size: 0.95rem;
line-height: 1.5;
}
.ai-provider-content p {
margin: 0 0 1rem 0;
}
.ai-provider-input {
margin-top: 1rem;
}
.ai-provider-status {
margin-top: 0.5rem;
padding: 0.5rem 1rem;
border-radius: 5px;
font-size: 0.9rem;
display: inline-flex;
align-items: center;
gap: 0.5rem;
}
.status-valid {
background: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
}
.status-invalid {
background: #fff3cd;
color: #856404;
border: 1px solid #ffeeba;
}
.coming-soon-badge {
background: #e9ecef;
color: #6c757d;
padding: 0.25rem 0.75rem;
border-radius: 15px;
font-size: 0.8rem;
margin-left: 0.5rem;
}
/* Tab styling */
.stTabs [data-baseweb="tab-list"] {
gap: 2rem;
background: #f8f9fa;
padding: 0.5rem;
border-radius: 10px;
margin-bottom: 1rem;
}
.stTabs [data-baseweb="tab"] {
padding: 0.75rem 1.5rem;
border-radius: 25px;
transition: all 0.3s ease;
background: transparent;
color: #495057;
font-weight: 500;
}
.stTabs [data-baseweb="tab"]:hover {
background: #e9ecef;
color: #1f77b4;
}
.stTabs [aria-selected="true"] {
background: #1f77b4 !important;
color: white !important;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
/* Navigation buttons */
.stButton button {
background: linear-gradient(135deg, #1f77b4 0%, #2ecc71 100%);
color: white;
border: none;
padding: 0.75rem 1.5rem;
border-radius: 25px;
font-weight: 500;
transition: all 0.3s ease;
}
.stButton button:hover {
transform: translateY(-2px);
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
.stButton button:disabled {
background: #e9ecef;
color: #adb5bd;
cursor: not-allowed;
}
/* Success message */
.success-message {
background: linear-gradient(135deg, #2ecc71 0%, #27ae60 100%);
padding: 1.5rem;
border-radius: 10px;
margin: 1rem 0;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
</style>
"""

View File

@@ -1,153 +0,0 @@
"""API key validation module."""
from typing import Dict, Any, List, Tuple
from loguru import logger
import os
from dotenv import load_dotenv
from .manager import APIKeyManager
def check_all_api_keys(api_key_manager: APIKeyManager) -> bool:
"""Check if minimum required API keys are present.
Args:
api_key_manager (APIKeyManager): The API key manager instance
Returns:
bool: True if minimum required keys are present (at least one AI provider and one research provider)
"""
try:
# Load environment variables
logger.info("Starting API key validation process...")
# Get the current working directory and .env file path
current_dir = os.getcwd()
env_path = os.path.join(current_dir, '.env')
logger.info(f"Looking for .env file at: {env_path}")
# Check if .env file exists
if not os.path.exists(env_path):
logger.error(f".env file not found at {env_path}")
return False
# Load environment variables
load_dotenv(env_path, override=True) # Add override=True to ensure variables are reloaded
logger.debug("Environment variables loaded")
# Log all environment variables (without their values)
logger.debug("Available environment variables:")
for key in os.environ.keys():
if any(provider in key for provider in ['API_KEY', 'SERPAPI', 'TAVILY', 'METAPHOR', 'FIRECRAWL']):
logger.debug(f"Found environment variable: {key}")
# Step 1: Check for at least one AI provider
logger.info("Checking AI provider API keys...")
ai_providers = [
'OPENAI_API_KEY',
'GEMINI_API_KEY',
'ANTHROPIC_API_KEY',
'MISTRAL_API_KEY'
]
# Log which AI providers are found
for provider in ai_providers:
value = os.getenv(provider)
if value:
logger.info(f"Found {provider} (length: {len(value)})")
else:
logger.debug(f"Missing {provider}")
has_ai_provider = any(os.getenv(key) for key in ai_providers)
if not has_ai_provider:
logger.warning("No AI provider API key found")
# Check if keys are in the API key manager
if hasattr(api_key_manager, 'get_api_key'):
for provider in ai_providers:
key = api_key_manager.get_api_key(provider)
if key:
logger.info(f"Found {provider} in API key manager")
has_ai_provider = True
break
if not has_ai_provider:
return False
else:
logger.success("✓ At least one AI provider key found")
# Step 2: Check for at least one research provider
logger.info("Checking research provider API keys...")
research_providers = [
'SERPAPI_KEY',
'TAVILY_API_KEY',
'METAPHOR_API_KEY',
'FIRECRAWL_API_KEY'
]
# Log which research providers are found
for provider in research_providers:
value = os.getenv(provider)
if value:
logger.info(f"Found {provider} (length: {len(value)})")
else:
logger.debug(f"Missing {provider}")
has_research_provider = any(os.getenv(key) for key in research_providers)
if not has_research_provider:
logger.warning("No research provider API key found")
# Check if keys are in the API key manager
if hasattr(api_key_manager, 'get_api_key'):
for provider in research_providers:
key = api_key_manager.get_api_key(provider)
if key:
logger.info(f"Found {provider} in API key manager")
has_research_provider = True
break
if not has_research_provider:
return False
else:
logger.success("✓ At least one research provider key found")
# Step 3: Check for website URL
logger.info("Checking website URL...")
website_url = os.getenv('WEBSITE_URL')
if not website_url:
logger.warning("No website URL found in environment variables")
return False
else:
logger.success(f"✓ Website URL found: {website_url}")
# Step 4: Check for personalization status
logger.info("Checking personalization status...")
if 'PERSONALIZATION_DONE' not in os.environ:
logger.warning("PERSONALIZATION_DONE environment variable is not defined")
return False
else:
logger.success(f"✓ Personalization status: {os.environ['PERSONALIZATION_DONE']}")
# Step 5: Check for integration status
logger.info("Checking integration status...")
if 'INTEGRATION_DONE' not in os.environ:
logger.warning("INTEGRATION_DONE environment variable is not defined")
return False
else:
logger.success(f"✓ Integration status: {os.environ['INTEGRATION_DONE']}")
# Step 6: Check for final setup status
logger.info("Checking final setup status...")
if 'FINAL_SETUP_COMPLETE' not in os.environ:
logger.warning("FINAL_SETUP_COMPLETE environment variable is not defined")
return False
else:
final_setup_status = os.environ['FINAL_SETUP_COMPLETE']
if final_setup_status.lower() == 'true':
logger.success("✓ Final setup completed successfully")
else:
logger.warning("Final setup validation failed")
return False
logger.success("All required API keys and setup steps validated successfully!")
return True
except Exception as e:
logger.error(f"Error checking API keys: {str(e)}", exc_info=True)
return False

View File

@@ -1,92 +0,0 @@
"""Wizard state management for the API key manager."""
import streamlit as st
from loguru import logger
def initialize_wizard_state():
"""Initialize or get the wizard state from session."""
if 'wizard_state' not in st.session_state:
st.session_state.wizard_state = {
'current_step': 0,
'total_steps': 0,
'completed_steps': set(),
'api_keys_status': {},
'setup_progress': 0
}
logger.info("Initialized wizard state")
def get_current_step():
"""Get the current step from the wizard state."""
return st.session_state.wizard_state.get('current_step', 0)
def next_step():
"""Move to the next step in the wizard."""
current_step = get_current_step()
st.session_state.wizard_state['current_step'] = current_step + 1
st.session_state.wizard_state['completed_steps'].add(current_step)
logger.info(f"Moving to next step: {current_step + 1}")
def previous_step():
"""Move to the previous step in the wizard."""
current_step = get_current_step()
if current_step > 0:
st.session_state.wizard_state['current_step'] = current_step - 1
st.session_state.wizard_state['completed_steps'].discard(current_step - 1)
logger.info(f"Moving to previous step: {current_step - 1}")
def update_progress():
"""Update the overall setup progress."""
total_steps = st.session_state.wizard_state.get('total_steps', 0)
completed_steps = len(st.session_state.wizard_state.get('completed_steps', set()))
if total_steps > 0:
progress = (completed_steps / total_steps) * 100
st.session_state.wizard_state['setup_progress'] = progress
logger.info(f"Updated progress: {progress:.1f}%")
def is_step_completed(step):
"""Check if a specific step is completed."""
return step in st.session_state.wizard_state.get('completed_steps', set())
def get_step_status(step):
"""Get the status of a specific step."""
current_step = get_current_step()
if step < current_step:
return "completed"
elif step == current_step:
return "current"
else:
return "pending"
def can_proceed_to_next_step():
"""Check if the user can proceed to the next step."""
current_step = get_current_step()
if current_step == 1:
# Get selected providers
selected_providers = get_selected_providers()
# If no providers are selected, cannot proceed
if not selected_providers:
return False
# Check if at least one selected provider has a valid API key
for provider in selected_providers:
validation_status = get_validation_status(provider)
if validation_status and validation_status.get('is_valid', False):
return True
return False
elif current_step == 2:
# Website URL is now optional
return True
elif current_step == 3:
# AI Research setup - both Tavily and Metaphor are optional
return True
elif current_step == 4:
# Final setup - always allow proceeding
return True
return False