ALwrity Version 0.5.0 (Fastapi + React )
This commit is contained in:
@@ -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.*
|
||||
@@ -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.
|
||||
@@ -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)
|
||||
@@ -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'
|
||||
]
|
||||
@@ -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}
|
||||
@@ -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}
|
||||
@@ -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}
|
||||
@@ -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)
|
||||
@@ -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}
|
||||
@@ -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")
|
||||
@@ -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}
|
||||
@@ -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}
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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']
|
||||
@@ -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>
|
||||
"""
|
||||
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user