Made changes to Getting started with ALwrity and added lot of details on API keys
This commit is contained in:
178
lib/utils/api_key_manager/components/README.md
Normal file
178
lib/utils/api_key_manager/components/README.md
Normal file
@@ -0,0 +1,178 @@
|
||||
# ALwrity Setup Components Guide
|
||||
|
||||
## Overview
|
||||
|
||||
The ALwrity Setup Components are the building blocks that guide you through setting up your content creation environment. Each component is designed to help you configure specific aspects of ALwrity for optimal content creation.
|
||||
|
||||
## Core Components
|
||||
|
||||
### 1. Website Setup (`website_setup.py`)
|
||||
**Purpose**: Configure your website's basic information and analyze its current state
|
||||
|
||||
**Features**:
|
||||
- **URL Configuration**: Set up your website's URL
|
||||
- **Analysis Options**:
|
||||
- Basic Analysis: Quick overview of your website
|
||||
- Full Analysis with SEO: Comprehensive website and SEO analysis
|
||||
- **Analysis Results**:
|
||||
- Basic Metrics: Status, content type, title, meta description
|
||||
- Content Analysis: Word count, headings, images, links
|
||||
- SEO Analysis: SEO score, meta tags, content quality
|
||||
- Technical SEO: Mobile friendliness, page speed, technical issues
|
||||
- Strategy Recommendations: Actionable improvements
|
||||
|
||||
### 2. AI Research Setup (`ai_research_setup.py`)
|
||||
**Purpose**: Configure AI-powered research tools for content creation
|
||||
|
||||
**Features**:
|
||||
- **Traditional Search**:
|
||||
- SerpAPI integration for real-time search results
|
||||
- Access to structured data and knowledge graphs
|
||||
- News articles and related questions
|
||||
|
||||
- **AI Deep Research**:
|
||||
- Tavily AI for semantic understanding
|
||||
- Metaphor/Exa for neural search capabilities
|
||||
- Advanced research features
|
||||
|
||||
### 3. AI Providers (`ai_providers.py`)
|
||||
**Purpose**: Set up your preferred AI content generation services
|
||||
|
||||
**Supported Providers**:
|
||||
- **OpenAI (GPT models)**
|
||||
- Advanced language models
|
||||
- Creative content generation
|
||||
- Context-aware responses
|
||||
|
||||
- **Google (Gemini Pro)**
|
||||
- Balanced content creation
|
||||
- Factual accuracy
|
||||
- Multilingual support
|
||||
|
||||
- **Anthropic (Claude)**
|
||||
- Professional writing
|
||||
- Detailed analysis
|
||||
- Ethical considerations
|
||||
|
||||
- **DeepSeek**
|
||||
- Technical content
|
||||
- Specialized knowledge
|
||||
- Efficient processing
|
||||
|
||||
### 4. Personalization Setup (`personalization_setup.py`)
|
||||
**Purpose**: Customize your content creation experience
|
||||
|
||||
**Features**:
|
||||
- **Writing Style**:
|
||||
- Tone preferences
|
||||
- Voice settings
|
||||
- Content structure
|
||||
|
||||
- **Brand Configuration**:
|
||||
- Brand voice
|
||||
- Style guidelines
|
||||
- Content templates
|
||||
|
||||
### 5. ALwrity Integrations (`alwrity_integrations.py`)
|
||||
**Purpose**: Connect additional tools and services
|
||||
|
||||
**Features**:
|
||||
- **Third-party Services**:
|
||||
- Analytics integration
|
||||
- Social media tools
|
||||
- Content management systems
|
||||
|
||||
- **Workflow Automation**:
|
||||
- Publishing tools
|
||||
- Content scheduling
|
||||
- Distribution channels
|
||||
|
||||
### 6. Final Setup (`final_setup.py`)
|
||||
**Purpose**: Complete and verify your configuration
|
||||
|
||||
**Features**:
|
||||
- **Configuration Review**:
|
||||
- Settings verification
|
||||
- Connection testing
|
||||
- Setup completion
|
||||
|
||||
- **Validation**:
|
||||
- API key verification
|
||||
- Service connectivity
|
||||
- System readiness
|
||||
|
||||
## Base Components
|
||||
|
||||
### 1. Navigation (`base.py`)
|
||||
**Purpose**: Provide consistent navigation throughout the setup process
|
||||
|
||||
**Features**:
|
||||
- Step indicators
|
||||
- Navigation buttons
|
||||
- Progress tracking
|
||||
- Back/forward controls
|
||||
|
||||
## How to Use the Components
|
||||
|
||||
### 1. Starting the Setup
|
||||
1. Launch ALwrity
|
||||
2. Navigate to the Setup section
|
||||
3. Follow the guided wizard process
|
||||
|
||||
### 2. Component Navigation
|
||||
- Use the step indicator to track progress
|
||||
- Navigate between components 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
|
||||
|
||||
## 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: Each component is designed to help you set up a specific aspect of ALwrity. Follow the setup wizard in order to ensure all components are properly configured for optimal content creation.*
|
||||
20
lib/utils/api_key_manager/components/__init__.py
Normal file
20
lib/utils/api_key_manager/components/__init__.py
Normal file
@@ -0,0 +1,20 @@
|
||||
"""API key manager components package."""
|
||||
|
||||
from .website_setup import render_website_setup
|
||||
from .ai_research_setup import render_ai_research_setup
|
||||
from .ai_providers import render_ai_providers
|
||||
from .final_setup import render_final_setup
|
||||
from .personalization_setup import render_personalization_setup
|
||||
from .alwrity_integrations import render_alwrity_integrations
|
||||
from .base import render_navigation_buttons, render_step_indicator
|
||||
|
||||
__all__ = [
|
||||
'render_website_setup',
|
||||
'render_ai_research_setup',
|
||||
'render_ai_providers',
|
||||
'render_final_setup',
|
||||
'render_personalization_setup',
|
||||
'render_alwrity_integrations',
|
||||
'render_navigation_buttons',
|
||||
'render_step_indicator'
|
||||
]
|
||||
225
lib/utils/api_key_manager/components/ai_providers.py
Normal file
225
lib/utils/api_key_manager/components/ai_providers.py
Normal file
@@ -0,0 +1,225 @@
|
||||
"""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
|
||||
|
||||
def validate_api_key(key: str) -> bool:
|
||||
"""Validate if an API key is properly formatted."""
|
||||
if not key:
|
||||
return False
|
||||
# Basic validation - check if key is not empty and has minimum length
|
||||
return len(key.strip()) > 0
|
||||
|
||||
def render_ai_providers(api_key_manager: APIKeyManager) -> Dict[str, Any]:
|
||||
"""Render the AI providers setup step."""
|
||||
logger.info("[render_ai_providers] Starting AI providers setup")
|
||||
try:
|
||||
# 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 Providers Setup</h2>
|
||||
<p>Configure your AI service providers for content generation</p>
|
||||
</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")
|
||||
st.markdown("Configure the main AI providers for content creation")
|
||||
|
||||
# Create a grid layout for AI provider cards
|
||||
col1, col2 = st.columns(2)
|
||||
|
||||
with col1:
|
||||
# OpenAI Card
|
||||
with st.container():
|
||||
st.markdown("""
|
||||
<div class="ai-provider-card">
|
||||
<div class="ai-provider-header">
|
||||
<div class="ai-provider-icon">🤖</div>
|
||||
<div class="ai-provider-title">OpenAI</div>
|
||||
</div>
|
||||
<div class="ai-provider-content">
|
||||
<p>Power your content with GPT-4 and GPT-3.5 models</p>
|
||||
<div class="ai-provider-input">
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
openai_key = st.text_input(
|
||||
"OpenAI API Key",
|
||||
type="password",
|
||||
key="openai_key",
|
||||
help="Enter your OpenAI API key"
|
||||
)
|
||||
|
||||
if openai_key:
|
||||
if validate_api_key(openai_key):
|
||||
st.markdown("""
|
||||
<div class="ai-provider-status status-valid">
|
||||
✓ API key configured
|
||||
</div>
|
||||
""", unsafe_allow_html=True)
|
||||
else:
|
||||
st.markdown("""
|
||||
<div class="ai-provider-status status-invalid">
|
||||
⚠️ Invalid API key format
|
||||
</div>
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
with st.expander("📋 How to get your OpenAI API key", expanded=False):
|
||||
st.markdown("""
|
||||
**Step-by-step guide:**
|
||||
1. Go to [OpenAI's website](https://platform.openai.com)
|
||||
2. Sign up or log in to your account
|
||||
3. Navigate to the API section
|
||||
4. Click "Create new secret key"
|
||||
5. Copy the generated key and paste it here
|
||||
|
||||
**Note:** Keep your API key secure and never share it publicly.
|
||||
""")
|
||||
|
||||
st.markdown("</div></div></div>", unsafe_allow_html=True)
|
||||
|
||||
with col2:
|
||||
# Google Card
|
||||
with st.container():
|
||||
st.markdown("""
|
||||
<div class="ai-provider-card">
|
||||
<div class="ai-provider-header">
|
||||
<div class="ai-provider-icon">🔍</div>
|
||||
<div class="ai-provider-title">Google Gemini</div>
|
||||
</div>
|
||||
<div class="ai-provider-content">
|
||||
<p>Leverage Google's powerful Gemini models</p>
|
||||
<div class="ai-provider-input">
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
google_key = st.text_input(
|
||||
"Google API Key",
|
||||
type="password",
|
||||
key="google_key",
|
||||
help="Enter your Google API key"
|
||||
)
|
||||
|
||||
if google_key:
|
||||
if validate_api_key(google_key):
|
||||
st.markdown("""
|
||||
<div class="ai-provider-status status-valid">
|
||||
✓ API key configured
|
||||
</div>
|
||||
""", unsafe_allow_html=True)
|
||||
else:
|
||||
st.markdown("""
|
||||
<div class="ai-provider-status status-invalid">
|
||||
⚠️ Invalid API key format
|
||||
</div>
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
with st.expander("📋 How to get your Google API key", expanded=False):
|
||||
st.markdown("""
|
||||
**Step-by-step guide:**
|
||||
1. Visit [Google AI Studio](https://makersuite.google.com/app/apikey)
|
||||
2. Sign in with your Google account
|
||||
3. Click "Create API key"
|
||||
4. Copy the generated key and paste it here
|
||||
|
||||
**Note:** Make sure to enable the Gemini API in your Google Cloud Console.
|
||||
""")
|
||||
|
||||
st.markdown("</div></div></div>", unsafe_allow_html=True)
|
||||
|
||||
with tabs[1]:
|
||||
st.markdown("### Additional AI Providers")
|
||||
st.markdown("Configure additional AI providers for enhanced capabilities")
|
||||
|
||||
# Create a grid layout for additional provider cards
|
||||
col1, col2 = st.columns(2)
|
||||
|
||||
with col1:
|
||||
# Anthropic Card (Coming Soon)
|
||||
with st.container():
|
||||
st.markdown("""
|
||||
<div class="ai-provider-card disabled">
|
||||
<div class="ai-provider-header">
|
||||
<div class="ai-provider-icon">🧠</div>
|
||||
<div class="ai-provider-title">Anthropic <span class="coming-soon-badge">Coming Soon</span></div>
|
||||
</div>
|
||||
<div class="ai-provider-content">
|
||||
<p>Access Claude for advanced content generation</p>
|
||||
</div>
|
||||
</div>
|
||||
""", unsafe_allow_html=True)
|
||||
st.info("Anthropic integration will be available in the next update")
|
||||
|
||||
with col2:
|
||||
# Mistral Card (Coming Soon)
|
||||
with st.container():
|
||||
st.markdown("""
|
||||
<div class="ai-provider-card disabled">
|
||||
<div class="ai-provider-header">
|
||||
<div class="ai-provider-icon">⚡</div>
|
||||
<div class="ai-provider-title">Mistral <span class="coming-soon-badge">Coming Soon</span></div>
|
||||
</div>
|
||||
<div class="ai-provider-content">
|
||||
<p>Use Mistral's efficient language models</p>
|
||||
</div>
|
||||
</div>
|
||||
""", unsafe_allow_html=True)
|
||||
st.info("Mistral integration will be available in the next update")
|
||||
|
||||
# Track changes and validate keys
|
||||
if any([openai_key, google_key]):
|
||||
changes_made = True
|
||||
# Check if at least one valid API key is provided
|
||||
if validate_api_key(openai_key) or validate_api_key(google_key):
|
||||
has_valid_key = True
|
||||
validation_message = "✅ At least one AI provider configured successfully"
|
||||
else:
|
||||
validation_message = "⚠️ Please provide at least one valid API key"
|
||||
else:
|
||||
validation_message = "⚠️ Please configure at least one AI provider to continue"
|
||||
|
||||
# Display validation message
|
||||
if validation_message:
|
||||
if "✅" in validation_message:
|
||||
st.success(validation_message)
|
||||
else:
|
||||
st.warning(validation_message)
|
||||
|
||||
# Navigation buttons
|
||||
if render_navigation_buttons(1, 6, changes_made):
|
||||
if has_valid_key:
|
||||
# Store the API keys in a separate session state key
|
||||
st.session_state['api_keys'] = {
|
||||
'openai': openai_key if validate_api_key(openai_key) else None,
|
||||
'google': google_key if validate_api_key(google_key) else None
|
||||
}
|
||||
|
||||
# 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}
|
||||
114
lib/utils/api_key_manager/components/ai_providers_setup.py
Normal file
114
lib/utils/api_key_manager/components/ai_providers_setup.py
Normal file
@@ -0,0 +1,114 @@
|
||||
"""AI providers setup component for API key manager."""
|
||||
|
||||
from typing import Dict, Any
|
||||
from loguru import logger
|
||||
import streamlit as st
|
||||
import os
|
||||
import sys
|
||||
|
||||
def render_ai_providers_setup(api_key_manager) -> Dict[str, Any]:
|
||||
"""
|
||||
Render the AI providers setup component.
|
||||
|
||||
Args:
|
||||
api_key_manager: API key manager instance
|
||||
|
||||
Returns:
|
||||
Dict[str, Any]: Component state
|
||||
"""
|
||||
try:
|
||||
logger.info("[render_ai_providers_setup] Rendering AI providers setup")
|
||||
|
||||
# Display section header
|
||||
st.header("Step 1: Select AI Providers")
|
||||
st.markdown("""
|
||||
Configure your AI providers to enable advanced content generation capabilities.
|
||||
Choose and set up the AI services you want to use.
|
||||
""")
|
||||
|
||||
# Create columns for different providers
|
||||
col1, col2 = st.columns(2)
|
||||
|
||||
with col1:
|
||||
st.subheader("OpenAI")
|
||||
st.markdown("""
|
||||
OpenAI's GPT models provide powerful natural language processing capabilities.
|
||||
|
||||
Get your API key from: [OpenAI Dashboard](https://platform.openai.com/account/api-keys)
|
||||
""")
|
||||
|
||||
openai_key = api_key_manager.get_api_key("openai")
|
||||
openai_input = st.text_input(
|
||||
"OpenAI API Key",
|
||||
value=openai_key if openai_key else "",
|
||||
type="password",
|
||||
key="openai_key_input"
|
||||
)
|
||||
|
||||
with col2:
|
||||
st.subheader("Google Gemini")
|
||||
st.markdown("""
|
||||
Google's Gemini models offer advanced AI capabilities.
|
||||
|
||||
Get your API key from: [Google AI Studio](https://makersuite.google.com/app/apikey)
|
||||
""")
|
||||
|
||||
gemini_key = api_key_manager.get_api_key("gemini")
|
||||
gemini_input = st.text_input(
|
||||
"Gemini API Key",
|
||||
value=gemini_key if gemini_key else "",
|
||||
type="password",
|
||||
key="gemini_key_input"
|
||||
)
|
||||
|
||||
# Optional AI Provider
|
||||
st.subheader("Additional AI Provider (Optional)")
|
||||
col1, col2 = st.columns(2)
|
||||
|
||||
with col1:
|
||||
st.markdown("""
|
||||
Mistral AI provides an alternative model for content generation.
|
||||
|
||||
Get your API key from: [Mistral Platform](https://console.mistral.ai/api-keys/)
|
||||
""")
|
||||
|
||||
mistral_key = api_key_manager.get_api_key("mistral")
|
||||
mistral_input = st.text_input(
|
||||
"Mistral API Key (Optional)",
|
||||
value=mistral_key if mistral_key else "",
|
||||
type="password",
|
||||
key="mistral_key_input"
|
||||
)
|
||||
|
||||
# Add a note about saving
|
||||
st.info("""
|
||||
Note: At least one AI provider (OpenAI or Google Gemini) is required.
|
||||
Click Continue to save your keys and proceed.
|
||||
""")
|
||||
|
||||
# Save keys if they've changed when proceeding to next step
|
||||
if st.session_state.get('wizard_current_step', 1) > 1:
|
||||
if openai_input != openai_key:
|
||||
api_key_manager.save_api_key("openai", openai_input)
|
||||
logger.info("[render_ai_providers_setup] OpenAI API key saved")
|
||||
|
||||
if gemini_input != gemini_key:
|
||||
api_key_manager.save_api_key("gemini", gemini_input)
|
||||
logger.info("[render_ai_providers_setup] Gemini API key saved")
|
||||
|
||||
if mistral_input != mistral_key:
|
||||
api_key_manager.save_api_key("mistral", mistral_input)
|
||||
logger.info("[render_ai_providers_setup] Mistral API key saved")
|
||||
|
||||
# Validate that at least one required provider is configured
|
||||
if not (openai_input or gemini_input):
|
||||
st.error("Please configure at least one AI provider (OpenAI or Google Gemini) to proceed.")
|
||||
return {"current_step": 1, "can_proceed": False}
|
||||
|
||||
return {"current_step": 1, "can_proceed": bool(openai_input or gemini_input)}
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"Error in AI providers setup: {str(e)}"
|
||||
logger.error(f"[render_ai_providers_setup] {error_msg}")
|
||||
st.error(error_msg)
|
||||
return {"current_step": 1, "error": error_msg}
|
||||
137
lib/utils/api_key_manager/components/ai_research.py
Normal file
137
lib/utils/api_key_manager/components/ai_research.py
Normal file
@@ -0,0 +1,137 @@
|
||||
"""AI Research setup component."""
|
||||
|
||||
import streamlit as st
|
||||
from typing import Dict, Any
|
||||
from loguru import logger
|
||||
from ..manager import APIKeyManager
|
||||
from .base import render_navigation_buttons, render_step_indicator
|
||||
|
||||
def render_ai_research(api_key_manager: APIKeyManager) -> Dict[str, Any]:
|
||||
"""Render the AI Research setup step."""
|
||||
try:
|
||||
st.markdown("""
|
||||
<div class='setup-header'>
|
||||
<h2>🔍 AI Research Configuration</h2>
|
||||
<p>Configure your research preferences and provide user information</p>
|
||||
</div>
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
# Create tabs for different sections
|
||||
tabs = st.tabs(["User Information", "Research Preferences"])
|
||||
|
||||
changes_made = False
|
||||
has_valid_info = False
|
||||
validation_message = ""
|
||||
|
||||
with tabs[0]:
|
||||
st.markdown("### User Information")
|
||||
st.markdown("Please provide your details for personalized research experience")
|
||||
|
||||
# User Information Card
|
||||
with st.container():
|
||||
st.markdown("""
|
||||
<div class="user-info-card">
|
||||
<div class="user-info-header">
|
||||
<div class="user-info-icon">👤</div>
|
||||
<div class="user-info-title">Personal Details</div>
|
||||
</div>
|
||||
<div class="user-info-content">
|
||||
<p>Your information helps us customize the research experience.</p>
|
||||
</div>
|
||||
</div>
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
# User Input Fields with Streamlit Components
|
||||
full_name = st.text_input("Full Name", key="full_name",
|
||||
help="Enter your full name as you'd like it to appear")
|
||||
|
||||
email = st.text_input("Email Address", key="email",
|
||||
help="Enter your business email address")
|
||||
|
||||
company = st.text_input("Company/Organization", key="company",
|
||||
help="Enter your company or organization name")
|
||||
|
||||
role = st.selectbox("Role",
|
||||
["Content Creator", "Marketing Manager", "Business Owner", "Other"],
|
||||
help="Select your primary role")
|
||||
|
||||
with tabs[1]:
|
||||
st.markdown("### Research Preferences")
|
||||
st.markdown("Configure how AI assists with your research")
|
||||
|
||||
# Research Preferences Card
|
||||
with st.container():
|
||||
st.markdown("""
|
||||
<div class="research-prefs-card">
|
||||
<div class="research-prefs-header">
|
||||
<div class="research-prefs-icon">🎯</div>
|
||||
<div class="research-prefs-title">Research Settings</div>
|
||||
</div>
|
||||
</div>
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
# Research Preferences Settings
|
||||
research_depth = st.select_slider(
|
||||
"Research Depth",
|
||||
options=["Basic", "Standard", "Deep", "Comprehensive"],
|
||||
value="Standard",
|
||||
help="Choose how detailed you want the AI research to be"
|
||||
)
|
||||
|
||||
st.markdown("#### Content Types")
|
||||
content_types = st.multiselect(
|
||||
"Select content types to focus on",
|
||||
["Blog Posts", "Social Media", "Technical Articles", "News", "Academic Papers"],
|
||||
default=["Blog Posts", "Social Media"],
|
||||
help="Choose what types of content you want to research"
|
||||
)
|
||||
|
||||
auto_research = st.toggle(
|
||||
"Enable Automated Research",
|
||||
help="Automatically start research when content topics are added"
|
||||
)
|
||||
|
||||
# Validate inputs
|
||||
if all([full_name, email, company]):
|
||||
changes_made = True
|
||||
has_valid_info = True
|
||||
validation_message = "✅ User information completed successfully"
|
||||
else:
|
||||
validation_message = "⚠️ Please fill in all required fields 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(3, 6, changes_made):
|
||||
if has_valid_info:
|
||||
# Store user information in session state
|
||||
st.session_state['user_info'] = {
|
||||
'full_name': full_name,
|
||||
'email': email,
|
||||
'company': company,
|
||||
'role': role,
|
||||
'research_preferences': {
|
||||
'depth': research_depth,
|
||||
'content_types': content_types,
|
||||
'auto_research': auto_research
|
||||
}
|
||||
}
|
||||
|
||||
# Update progress and move to next step
|
||||
st.session_state['current_step'] = 4
|
||||
st.rerun()
|
||||
else:
|
||||
st.error("Please complete all required fields to continue")
|
||||
|
||||
return {"current_step": 3, "changes_made": changes_made}
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"Error in AI research setup: {str(e)}"
|
||||
logger.error(f"[render_ai_research] {error_msg}")
|
||||
st.error(error_msg)
|
||||
return {"current_step": 3, "error": error_msg}
|
||||
349
lib/utils/api_key_manager/components/ai_research_setup.py
Normal file
349
lib/utils/api_key_manager/components/ai_research_setup.py
Normal file
@@ -0,0 +1,349 @@
|
||||
"""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 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 Research Setup</h2>
|
||||
<p>Configure your AI research providers for content analysis and research</p>
|
||||
</div>
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
# Create two columns for different search types
|
||||
col1, col2 = st.columns(2)
|
||||
|
||||
with col1:
|
||||
st.markdown("### The Usual")
|
||||
|
||||
# SerpAPI Card
|
||||
st.markdown("""
|
||||
<div class="ai-provider-card">
|
||||
<div class="ai-provider-header">
|
||||
<div class="ai-provider-icon">🔎</div>
|
||||
<div class="ai-provider-title">SerpAPI</div>
|
||||
</div>
|
||||
<div class="ai-provider-description">
|
||||
Access search engine results for research
|
||||
</div>
|
||||
<div class="ai-provider-input">
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
serpapi_key = st.text_input(
|
||||
"SerpAPI Key",
|
||||
type="password",
|
||||
key="serpapi_key",
|
||||
help="Enter your SerpAPI key"
|
||||
)
|
||||
|
||||
if 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 Card
|
||||
st.markdown("""
|
||||
<div class="ai-provider-card">
|
||||
<div class="ai-provider-header">
|
||||
<div class="ai-provider-icon">🕷️</div>
|
||||
<div class="ai-provider-title">Firecrawl</div>
|
||||
</div>
|
||||
<div class="ai-provider-description">
|
||||
Web content extraction and analysis
|
||||
</div>
|
||||
<div class="ai-provider-input">
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
firecrawl_key = st.text_input(
|
||||
"Firecrawl API Key",
|
||||
type="password",
|
||||
key="firecrawl_key",
|
||||
help="Enter your Firecrawl API key"
|
||||
)
|
||||
|
||||
if firecrawl_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 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")
|
||||
|
||||
# Tavily Card
|
||||
st.markdown("""
|
||||
<div class="ai-provider-card">
|
||||
<div class="ai-provider-header">
|
||||
<div class="ai-provider-icon">🤖</div>
|
||||
<div class="ai-provider-title">Tavily AI</div>
|
||||
</div>
|
||||
<div class="ai-provider-description">
|
||||
AI-powered search with semantic understanding
|
||||
</div>
|
||||
<div class="ai-provider-input">
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
tavily_key = st.text_input(
|
||||
"Tavily API Key",
|
||||
type="password",
|
||||
key="tavily_key",
|
||||
help="Enter your Tavily API key"
|
||||
)
|
||||
|
||||
if tavily_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 Tavily API key</summary>
|
||||
<div class="api-info-content">
|
||||
<p><strong>Step-by-step guide:</strong></p>
|
||||
<ol>
|
||||
<li>Visit <a href="https://tavily.com" target="_blank">Tavily</a></li>
|
||||
<li>Create an account</li>
|
||||
<li>Go to API settings</li>
|
||||
<li>Generate a new API key</li>
|
||||
<li>Copy and paste it here</li>
|
||||
</ol>
|
||||
<p><strong>Note:</strong> Tavily provides AI-powered semantic search capabilities.</p>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
st.markdown("</div></div>", unsafe_allow_html=True)
|
||||
|
||||
# Metaphor/Exa Card
|
||||
st.markdown("""
|
||||
<div class="ai-provider-card">
|
||||
<div class="ai-provider-header">
|
||||
<div class="ai-provider-icon">🧠</div>
|
||||
<div class="ai-provider-title">Metaphor/Exa</div>
|
||||
</div>
|
||||
<div class="ai-provider-description">
|
||||
Neural search engine for deep research
|
||||
</div>
|
||||
<div class="ai-provider-input">
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
metaphor_key = st.text_input(
|
||||
"Metaphor/Exa API Key",
|
||||
type="password",
|
||||
key="metaphor_key",
|
||||
help="Enter your Metaphor/Exa API key"
|
||||
)
|
||||
|
||||
if 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)
|
||||
|
||||
# Disabled Options Expander
|
||||
with st.expander("🔜 Coming Soon - More Search Options", expanded=False):
|
||||
st.markdown("""
|
||||
<div style='opacity: 0.7;'>
|
||||
<h4>Bing Search API</h4>
|
||||
<p>Microsoft's powerful search API with web, news, and image search capabilities.</p>
|
||||
|
||||
<h4>Google Search API</h4>
|
||||
<p>Google's programmable search engine with customizable search parameters.</p>
|
||||
|
||||
<p><em>These integrations are under development and will be available soon!</em></p>
|
||||
</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:
|
||||
# Load existing .env file if it exists
|
||||
load_dotenv()
|
||||
|
||||
# Create or update .env file with new API keys
|
||||
with open('.env', 'a') as f:
|
||||
if serpapi_key:
|
||||
f.write(f"\nSERPAPI_KEY={serpapi_key}")
|
||||
logger.info("[render_ai_research_setup] Saved SerpAPI key")
|
||||
if tavily_key:
|
||||
f.write(f"\nTAVILY_API_KEY={tavily_key}")
|
||||
logger.info("[render_ai_research_setup] Saved Tavily API key")
|
||||
if metaphor_key:
|
||||
f.write(f"\nMETAPHOR_API_KEY={metaphor_key}")
|
||||
logger.info("[render_ai_research_setup] Saved Metaphor API key")
|
||||
if firecrawl_key:
|
||||
f.write(f"\nFIRECRAWL_API_KEY={firecrawl_key}")
|
||||
logger.info("[render_ai_research_setup] Saved Firecrawl API key")
|
||||
|
||||
# Store the API keys in session state
|
||||
st.session_state['api_keys'] = {
|
||||
'serpapi': serpapi_key,
|
||||
'tavily': tavily_key,
|
||||
'metaphor': metaphor_key,
|
||||
'firecrawl': firecrawl_key
|
||||
}
|
||||
|
||||
# Update progress and move to next step
|
||||
st.session_state['current_step'] = 4
|
||||
st.rerun()
|
||||
except Exception as e:
|
||||
error_msg = f"Error saving API keys: {str(e)}"
|
||||
logger.error(f"[render_ai_research_setup] {error_msg}")
|
||||
st.error(error_msg)
|
||||
else:
|
||||
st.error("Please configure at least one research provider to continue")
|
||||
|
||||
# Detailed Information Section
|
||||
st.markdown("""
|
||||
---
|
||||
### Understanding Your Research Options
|
||||
|
||||
#### The Usual: Traditional Search
|
||||
**SerpAPI**
|
||||
- Real-time search results from multiple search engines
|
||||
- Access to structured data from search results
|
||||
- Great for gathering general information and market research
|
||||
- Includes features like:
|
||||
- Web search results
|
||||
- News articles
|
||||
- Knowledge graphs
|
||||
- Related questions
|
||||
|
||||
#### AI Deep Research: Advanced Search Capabilities
|
||||
|
||||
**Tavily AI**
|
||||
- AI-powered search with semantic understanding
|
||||
- Automatically summarizes and analyzes search results
|
||||
- Perfect for:
|
||||
- Deep research tasks
|
||||
- Academic research
|
||||
- Fact-checking
|
||||
- Real-time information gathering
|
||||
|
||||
**Metaphor/Exa**
|
||||
- Neural search engine that understands context and meaning
|
||||
- Specialized in finding highly relevant content
|
||||
- Ideal for:
|
||||
- Technical research
|
||||
- Finding similar content
|
||||
- Discovering patterns in research
|
||||
- Understanding topic landscapes
|
||||
|
||||
#### Choosing the Right Tool
|
||||
|
||||
1. **For General Research:**
|
||||
- Start with SerpAPI for broad coverage and structured data
|
||||
|
||||
2. **For Deep Analysis:**
|
||||
- Use Tavily AI when you need AI-powered insights
|
||||
- Choose Metaphor/Exa for neural search and pattern discovery
|
||||
|
||||
3. **For Comprehensive Research:**
|
||||
- Combine multiple tools to get the most complete picture
|
||||
- Use SerpAPI for initial research
|
||||
- Follow up with AI tools for deeper insights
|
||||
|
||||
> **Pro Tip:** Configure multiple providers to ensure you have backup options and can cross-reference results for better accuracy.
|
||||
""")
|
||||
|
||||
return {"current_step": 3, "changes_made": changes_made}
|
||||
176
lib/utils/api_key_manager/components/alwrity_integrations.py
Normal file
176
lib/utils/api_key_manager/components/alwrity_integrations.py
Normal file
@@ -0,0 +1,176 @@
|
||||
"""ALwrity integrations 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
|
||||
|
||||
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:
|
||||
# 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 progress and move to next step
|
||||
st.session_state['current_step'] = 6
|
||||
st.rerun()
|
||||
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}
|
||||
185
lib/utils/api_key_manager/components/base.py
Normal file
185
lib/utils/api_key_manager/components/base.py
Normal file
@@ -0,0 +1,185 @@
|
||||
"""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
|
||||
from ..wizard_state import (
|
||||
get_current_step,
|
||||
next_step,
|
||||
previous_step,
|
||||
can_proceed_to_next_step
|
||||
)
|
||||
|
||||
def render_step_indicator(current_step: int, total_steps: int) -> None:
|
||||
"""Render the step indicator."""
|
||||
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 Setup", 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 = False) -> 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"):
|
||||
st.session_state['current_step'] = current_step - 1
|
||||
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"):
|
||||
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)
|
||||
146
lib/utils/api_key_manager/components/final_setup.py
Normal file
146
lib/utils/api_key_manager/components/final_setup.py
Normal file
@@ -0,0 +1,146 @@
|
||||
"""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 5: Final Setup")
|
||||
|
||||
# Load main config
|
||||
main_config = load_main_config()
|
||||
|
||||
# Display configuration summary
|
||||
st.markdown("#### Configuration Summary")
|
||||
|
||||
# Blog Content Characteristics
|
||||
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')}")
|
||||
|
||||
# LLM Options
|
||||
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"- Max Tokens: {llm_settings.get('Max Tokens', 4000)}")
|
||||
|
||||
# Personalization Settings
|
||||
st.markdown("##### Personalization Settings")
|
||||
personalization = main_config.get("personalization", {})
|
||||
st.write(f"- Writing Tone: {personalization.get('writing_tone', 'Professional')}")
|
||||
st.write(f"- Target Audience: {personalization.get('target_audience', 'General')}")
|
||||
st.write(f"- Content Type: {personalization.get('content_type', 'Blog Posts')}")
|
||||
|
||||
# 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:
|
||||
# Verify all required API keys are present and valid
|
||||
is_valid, missing_keys, impact_messages = check_all_api_keys(api_key_manager)
|
||||
|
||||
if not is_valid:
|
||||
st.error("⚠️ Some required API keys are missing")
|
||||
st.markdown("### Missing API Keys and Impact")
|
||||
|
||||
# Display impact messages in a structured way
|
||||
for message in impact_messages:
|
||||
if message.startswith("⚠️"):
|
||||
st.error(message)
|
||||
else:
|
||||
st.warning(message)
|
||||
|
||||
st.markdown("""
|
||||
<div style='background-color: #fff3cd; color: #856404; padding: 1rem; border-radius: 0.25rem; margin-top: 1rem;'>
|
||||
<h4 style='margin: 0;'>Required Keys:</h4>
|
||||
<ul style='margin: 0.5rem 0 0;'>
|
||||
<li>At least one AI provider (OpenAI, Google Gemini, Anthropic Claude, or Mistral)</li>
|
||||
<li>At least one research provider (SerpAPI, Tavily, Metaphor, or Firecrawl)</li>
|
||||
</ul>
|
||||
<p style='margin: 0.5rem 0 0;'>Please configure the required keys before proceeding.</p>
|
||||
</div>
|
||||
""", unsafe_allow_html=True)
|
||||
return {"current_step": 6, "changes_made": True}
|
||||
|
||||
# Save final configuration
|
||||
if not os.path.exists("lib/workspace/alwrity_config"):
|
||||
os.makedirs("lib/workspace/alwrity_config")
|
||||
|
||||
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)
|
||||
|
||||
# Show success message with HTML formatting
|
||||
st.markdown("""
|
||||
<div style='background-color: #d4edda; color: #155724; padding: 1rem; border-radius: 0.25rem;'>
|
||||
<h4 style='margin: 0;'>✅ Setup Completed Successfully!</h4>
|
||||
<p style='margin: 0.5rem 0 0;'>Your configuration has been saved and you're ready to use ALwrity.</p>
|
||||
</div>
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
# Set setup completion flag in session state
|
||||
st.session_state['setup_completed'] = True
|
||||
|
||||
# Redirect to main application
|
||||
st.switch_page("alwrity.py")
|
||||
|
||||
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": 5, "changes_made": True}
|
||||
39
lib/utils/api_key_manager/components/health_monitor.py
Normal file
39
lib/utils/api_key_manager/components/health_monitor.py
Normal file
@@ -0,0 +1,39 @@
|
||||
"""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")
|
||||
188
lib/utils/api_key_manager/components/personalization.py
Normal file
188
lib/utils/api_key_manager/components/personalization.py
Normal file
@@ -0,0 +1,188 @@
|
||||
"""Personalization setup component."""
|
||||
|
||||
import streamlit as st
|
||||
from typing import Dict, Any
|
||||
from loguru import logger
|
||||
from ..manager import APIKeyManager
|
||||
from .base import render_navigation_buttons, render_step_indicator
|
||||
|
||||
def render_personalization(api_key_manager: APIKeyManager) -> Dict[str, Any]:
|
||||
"""Render the personalization setup step."""
|
||||
try:
|
||||
st.markdown("""
|
||||
<div class='setup-header'>
|
||||
<h2>🎨 Personalization Settings</h2>
|
||||
<p>Customize your content generation experience</p>
|
||||
</div>
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
# Create tabs for different sections
|
||||
tabs = st.tabs(["Content Style", "Brand Voice", "Advanced Settings"])
|
||||
|
||||
changes_made = False
|
||||
has_valid_settings = False
|
||||
validation_message = ""
|
||||
|
||||
with tabs[0]:
|
||||
st.markdown("### Content Style")
|
||||
st.markdown("Define your preferred content style and tone")
|
||||
|
||||
# Content Style Card
|
||||
with st.container():
|
||||
st.markdown("""
|
||||
<div class="style-card">
|
||||
<div class="style-header">
|
||||
<div class="style-icon">✨</div>
|
||||
<div class="style-title">Writing Style</div>
|
||||
</div>
|
||||
<div class="style-content">
|
||||
<p>Choose how you want your content to be written.</p>
|
||||
</div>
|
||||
</div>
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
# Style Settings
|
||||
writing_style = st.selectbox(
|
||||
"Writing Style",
|
||||
["Professional", "Casual", "Technical", "Conversational", "Academic"],
|
||||
help="Select your preferred writing style"
|
||||
)
|
||||
|
||||
tone = st.select_slider(
|
||||
"Content Tone",
|
||||
options=["Formal", "Semi-Formal", "Neutral", "Friendly", "Humorous"],
|
||||
value="Neutral",
|
||||
help="Choose the tone for your content"
|
||||
)
|
||||
|
||||
content_length = st.select_slider(
|
||||
"Content Length",
|
||||
options=["Concise", "Standard", "Detailed", "Comprehensive"],
|
||||
value="Standard",
|
||||
help="Select your preferred content length"
|
||||
)
|
||||
|
||||
with tabs[1]:
|
||||
st.markdown("### Brand Voice")
|
||||
st.markdown("Configure your brand's unique voice and personality")
|
||||
|
||||
# Brand Voice Card
|
||||
with st.container():
|
||||
st.markdown("""
|
||||
<div class="brand-card">
|
||||
<div class="brand-header">
|
||||
<div class="brand-icon">🎯</div>
|
||||
<div class="brand-title">Brand Identity</div>
|
||||
</div>
|
||||
<div class="brand-content">
|
||||
<p>Define your brand's personality and voice.</p>
|
||||
</div>
|
||||
</div>
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
# Brand Settings
|
||||
brand_personality = st.multiselect(
|
||||
"Brand Personality Traits",
|
||||
["Professional", "Innovative", "Friendly", "Trustworthy", "Creative", "Expert"],
|
||||
default=["Professional", "Trustworthy"],
|
||||
help="Select traits that best describe your brand"
|
||||
)
|
||||
|
||||
brand_voice = st.text_area(
|
||||
"Brand Voice Description",
|
||||
help="Describe how your brand should sound in content"
|
||||
)
|
||||
|
||||
keywords = st.text_input(
|
||||
"Brand Keywords",
|
||||
help="Enter key terms that should be used in your content"
|
||||
)
|
||||
|
||||
with tabs[2]:
|
||||
st.markdown("### Advanced Settings")
|
||||
st.markdown("Fine-tune your content generation preferences")
|
||||
|
||||
# Advanced Settings Card
|
||||
with st.container():
|
||||
st.markdown("""
|
||||
<div class="advanced-card">
|
||||
<div class="advanced-header">
|
||||
<div class="advanced-icon">⚙️</div>
|
||||
<div class="advanced-title">Advanced Options</div>
|
||||
</div>
|
||||
<div class="advanced-content">
|
||||
<p>Configure advanced content generation settings.</p>
|
||||
</div>
|
||||
</div>
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
# Advanced Settings
|
||||
seo_optimization = st.toggle(
|
||||
"Enable SEO Optimization",
|
||||
help="Automatically optimize content for search engines"
|
||||
)
|
||||
|
||||
readability_level = st.select_slider(
|
||||
"Readability Level",
|
||||
options=["Simple", "Standard", "Advanced", "Expert"],
|
||||
value="Standard",
|
||||
help="Choose the complexity level of your content"
|
||||
)
|
||||
|
||||
content_structure = st.multiselect(
|
||||
"Content Structure",
|
||||
["Introduction", "Key Points", "Examples", "Conclusion", "Call-to-Action"],
|
||||
default=["Introduction", "Key Points", "Conclusion"],
|
||||
help="Select required content sections"
|
||||
)
|
||||
|
||||
# Validate settings
|
||||
if all([writing_style, tone, content_length, brand_personality]):
|
||||
changes_made = True
|
||||
has_valid_settings = True
|
||||
validation_message = "✅ Personalization settings completed successfully"
|
||||
else:
|
||||
validation_message = "⚠️ Please complete all required settings 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(4, 6, changes_made):
|
||||
if has_valid_settings:
|
||||
# Store personalization settings in session state
|
||||
st.session_state['personalization'] = {
|
||||
'content_style': {
|
||||
'writing_style': writing_style,
|
||||
'tone': tone,
|
||||
'content_length': content_length
|
||||
},
|
||||
'brand_voice': {
|
||||
'personality': brand_personality,
|
||||
'voice_description': brand_voice,
|
||||
'keywords': keywords
|
||||
},
|
||||
'advanced_settings': {
|
||||
'seo_optimization': seo_optimization,
|
||||
'readability_level': readability_level,
|
||||
'content_structure': content_structure
|
||||
}
|
||||
}
|
||||
|
||||
# Update progress and move to next step
|
||||
st.session_state['current_step'] = 5
|
||||
st.rerun()
|
||||
else:
|
||||
st.error("Please complete all required settings to continue")
|
||||
|
||||
return {"current_step": 4, "changes_made": changes_made}
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"Error in personalization setup: {str(e)}"
|
||||
logger.error(f"[render_personalization] {error_msg}")
|
||||
st.error(error_msg)
|
||||
return {"current_step": 4, "error": error_msg}
|
||||
702
lib/utils/api_key_manager/components/personalization_setup.py
Normal file
702
lib/utils/api_key_manager/components/personalization_setup.py
Normal file
@@ -0,0 +1,702 @@
|
||||
"""Personalization setup component for the API key manager."""
|
||||
|
||||
import streamlit as st
|
||||
from loguru import logger
|
||||
import sys
|
||||
import json
|
||||
from typing import Dict, Any
|
||||
from ..manager import APIKeyManager
|
||||
from ....web_crawlers.async_web_crawler import AsyncWebCrawlerService
|
||||
from ....personalization.style_analyzer import StyleAnalyzer
|
||||
from pages.style_utils import (
|
||||
get_analysis_section,
|
||||
get_glass_container,
|
||||
get_info_section,
|
||||
get_example_box
|
||||
)
|
||||
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("""
|
||||
<div class='setup-header'>
|
||||
<h2>✨ Personalization Setup</h2>
|
||||
<p>Configure your content generation preferences and writing style</p>
|
||||
</div>
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
# Load main config
|
||||
main_config = load_main_config()
|
||||
|
||||
# Create tabs for different personalization methods
|
||||
tab1, tab2 = st.tabs([
|
||||
"Manual Settings",
|
||||
"ALwrity Personalization"
|
||||
])
|
||||
|
||||
with tab1:
|
||||
st.markdown("### Manual Settings Configuration")
|
||||
|
||||
# Add container for better width control
|
||||
st.markdown("""
|
||||
<div style='width: 100%; max-width: 100%; margin: 0; padding: 0;'>
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
# Create two columns for settings and explanations (1:2 ratio)
|
||||
settings_col, info_col = st.columns([1, 2])
|
||||
|
||||
with settings_col:
|
||||
st.markdown("""
|
||||
<div style='padding-right: 2rem;'>
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
# Blog Content Characteristics
|
||||
st.markdown("#### Blog Content Characteristics")
|
||||
blog_settings = main_config.get("Blog Content Characteristics", {})
|
||||
|
||||
blog_length = st.text_input(
|
||||
"Blog Length",
|
||||
value=blog_settings.get("Blog Length", "2000"),
|
||||
placeholder="e.g., 2000",
|
||||
help="Target word count for your blog posts"
|
||||
)
|
||||
|
||||
blog_tone = st.selectbox(
|
||||
"Blog Tone",
|
||||
options=["Professional", "Casual", "Technical", "Conversational"],
|
||||
index=["Professional", "Casual", "Technical", "Conversational"].index(blog_settings.get("Blog Tone", "Professional")),
|
||||
help="The overall tone of your content"
|
||||
)
|
||||
|
||||
blog_demographic = st.selectbox(
|
||||
"Target Demographic",
|
||||
options=["Professional", "General", "Technical", "Academic"],
|
||||
index=["Professional", "General", "Technical", "Academic"].index(blog_settings.get("Blog Demographic", "Professional")),
|
||||
help="Your primary audience demographic"
|
||||
)
|
||||
|
||||
blog_type = st.selectbox(
|
||||
"Content Type",
|
||||
options=["Informational", "Educational", "Entertainment", "Technical"],
|
||||
index=["Informational", "Educational", "Entertainment", "Technical"].index(blog_settings.get("Blog Type", "Informational")),
|
||||
help="The primary type of content you create"
|
||||
)
|
||||
|
||||
blog_language = st.selectbox(
|
||||
"Content Language",
|
||||
options=["English", "Spanish", "French", "German", "Other"],
|
||||
index=["English", "Spanish", "French", "German", "Other"].index(blog_settings.get("Blog Language", "English")),
|
||||
help="Primary language for your content"
|
||||
)
|
||||
|
||||
blog_format = st.selectbox(
|
||||
"Output Format",
|
||||
options=["markdown", "html", "plain text"],
|
||||
index=["markdown", "html", "plain text"].index(blog_settings.get("Blog Output Format", "markdown")),
|
||||
help="Format of the generated content"
|
||||
)
|
||||
|
||||
# Blog Images Details
|
||||
st.markdown("#### Blog Images")
|
||||
image_settings = main_config.get("Blog Images Details", {})
|
||||
|
||||
image_model = st.selectbox(
|
||||
"Image Generation Model",
|
||||
options=["stable-diffusion", "dall-e", "midjourney"],
|
||||
index=["stable-diffusion", "dall-e", "midjourney"].index(image_settings.get("Image Generation Model", "stable-diffusion")),
|
||||
help="AI model for generating images"
|
||||
)
|
||||
|
||||
num_images = st.number_input(
|
||||
"Number of Images",
|
||||
min_value=1,
|
||||
max_value=5,
|
||||
value=image_settings.get("Number of Blog Images", 1),
|
||||
help="Number of images to generate per blog post"
|
||||
)
|
||||
|
||||
# LLM Options
|
||||
st.markdown("#### AI Generation Settings")
|
||||
llm_settings = main_config.get("LLM Options", {})
|
||||
|
||||
gpt_provider = st.selectbox(
|
||||
"AI Provider",
|
||||
options=["google", "openai", "anthropic"],
|
||||
index=["google", "openai", "anthropic"].index(llm_settings.get("GPT Provider", "google")),
|
||||
help="Choose your preferred AI provider"
|
||||
)
|
||||
|
||||
model = st.text_input(
|
||||
"Model",
|
||||
value=llm_settings.get("Model", "gemini-1.5-flash-latest"),
|
||||
placeholder="e.g., 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=float(llm_settings.get("Temperature", 0.7)),
|
||||
help="Higher values = more creative, lower values = more focused"
|
||||
)
|
||||
|
||||
top_p = st.slider(
|
||||
"Output Diversity",
|
||||
min_value=0.0,
|
||||
max_value=1.0,
|
||||
value=float(llm_settings.get("Top-p", 0.9)),
|
||||
help="Controls diversity of generated content"
|
||||
)
|
||||
|
||||
max_tokens = st.number_input(
|
||||
"Maximum Length",
|
||||
min_value=100,
|
||||
max_value=8000,
|
||||
value=int(llm_settings.get("Max Tokens", 4000)),
|
||||
help="Maximum length of generated content"
|
||||
)
|
||||
|
||||
frequency_penalty = st.slider(
|
||||
"Frequency Penalty",
|
||||
min_value=-2.0,
|
||||
max_value=2.0,
|
||||
value=float(llm_settings.get("Frequency Penalty", 1.0)),
|
||||
help="Reduces repetition of the same words"
|
||||
)
|
||||
|
||||
presence_penalty = st.slider(
|
||||
"Presence Penalty",
|
||||
min_value=-2.0,
|
||||
max_value=2.0,
|
||||
value=float(llm_settings.get("Presence Penalty", 1.0)),
|
||||
help="Encourages discussion of new topics"
|
||||
)
|
||||
|
||||
# Search Engine Parameters
|
||||
st.markdown("#### Search Settings")
|
||||
search_settings = main_config.get("Search Engine Parameters", {})
|
||||
|
||||
geo_location = st.text_input(
|
||||
"Geographic Location",
|
||||
value=search_settings.get("Geographic Location", "us"),
|
||||
placeholder="e.g., us, uk, ca",
|
||||
help="Target geographic location for search results"
|
||||
)
|
||||
|
||||
search_language = st.selectbox(
|
||||
"Search Language",
|
||||
options=["en", "es", "fr", "de", "other"],
|
||||
index=["en", "es", "fr", "de", "other"].index(search_settings.get("Search Language", "en")),
|
||||
help="Language for search results"
|
||||
)
|
||||
|
||||
num_results = st.number_input(
|
||||
"Number of Results",
|
||||
min_value=1,
|
||||
max_value=50,
|
||||
value=search_settings.get("Number of Results", 10),
|
||||
help="Number of search results to analyze"
|
||||
)
|
||||
|
||||
time_range = st.selectbox(
|
||||
"Time Range",
|
||||
options=["anytime", "day", "week", "month", "year"],
|
||||
index=["anytime", "day", "week", "month", "year"].index(search_settings.get("Time Range", "anytime")),
|
||||
help="Time range for search results"
|
||||
)
|
||||
|
||||
st.markdown("</div>", unsafe_allow_html=True)
|
||||
|
||||
with info_col:
|
||||
st.markdown("""
|
||||
<div style='
|
||||
padding-left: 2rem;
|
||||
border-left: 2px solid #e0e0e0;
|
||||
background-color: #f8f9fa;
|
||||
border-radius: 0 8px 8px 0;
|
||||
margin: -1rem 0;
|
||||
padding-top: 1rem;
|
||||
padding-bottom: 1rem;
|
||||
'>
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
st.markdown("""
|
||||
<div style='padding: 0 1rem;'>
|
||||
### Understanding Your Settings
|
||||
|
||||
#### Blog Content Settings
|
||||
|
||||
**Blog Length**
|
||||
- Determines the target word count for your posts
|
||||
- Affects content depth and detail level
|
||||
- Impacts reader engagement and SEO performance
|
||||
- Recommended: 1500-2500 words for comprehensive coverage
|
||||
|
||||
**Blog Tone**
|
||||
- Professional: Formal, business-oriented, authoritative
|
||||
- Casual: Friendly, conversational, approachable
|
||||
- Technical: Detailed, precise, industry-specific
|
||||
- Conversational: Engaging, relatable, personal
|
||||
|
||||
**Target Demographic**
|
||||
- Professional: Business audience, decision-makers
|
||||
- General: Broad readership, general public
|
||||
- Technical: Specialized audience, industry experts
|
||||
- Academic: Research-focused, scholarly readers
|
||||
|
||||
**Content Type**
|
||||
- Informational: Facts, insights, and analysis
|
||||
- Educational: Teaching, tutorials, how-to guides
|
||||
- Entertainment: Engaging, fun, light content
|
||||
- Technical: Detailed analysis, specifications
|
||||
|
||||
**Content Language**
|
||||
- Select your primary content language
|
||||
- Affects grammar, idioms, and cultural context
|
||||
- Impacts SEO and audience reach
|
||||
|
||||
**Output Format**
|
||||
- Markdown: Best for most platforms
|
||||
- HTML: For web publishing
|
||||
- Plain Text: For simple content
|
||||
|
||||
#### Image Generation Settings
|
||||
|
||||
**Image Generation Model**
|
||||
- Stable Diffusion: Best for general content
|
||||
- DALL-E: Great for creative concepts
|
||||
- Midjourney: Excellent for artistic content
|
||||
|
||||
**Number of Images**
|
||||
- Consider your content type and platform
|
||||
- More images = better engagement but higher cost
|
||||
- Recommended: 1-2 images per post
|
||||
|
||||
#### AI Generation Settings
|
||||
|
||||
**AI Provider**
|
||||
- Google: Balanced, reliable, cost-effective
|
||||
- OpenAI: Creative, nuanced, versatile
|
||||
- Anthropic: Precise, ethical, focused
|
||||
|
||||
**Model Selection**
|
||||
- Latest models offer best performance
|
||||
- Specialized models for specific needs
|
||||
- Consider cost vs. quality trade-offs
|
||||
|
||||
**Creativity Level (Temperature)**
|
||||
- 0.0: Focused, consistent, predictable
|
||||
- 0.5: Balanced creativity and coherence
|
||||
- 1.0: Maximum creativity, more varied
|
||||
|
||||
**Output Diversity (Top-p)**
|
||||
- Controls variety in word choices
|
||||
- Higher values = more diverse vocabulary
|
||||
- Lower values = more focused terminology
|
||||
|
||||
**Maximum Length**
|
||||
- Affects content completeness
|
||||
- Consider platform limits
|
||||
- Balance detail vs. readability
|
||||
|
||||
**Frequency & Presence Penalties**
|
||||
- Reduce repetition of words
|
||||
- Encourage topic diversity
|
||||
- Fine-tune content variety
|
||||
|
||||
#### Search Settings
|
||||
|
||||
**Geographic Location**
|
||||
- Target specific regions
|
||||
- Affects local SEO
|
||||
- Influences content relevance
|
||||
|
||||
**Search Language**
|
||||
- Match your content language
|
||||
- Affects result relevance
|
||||
- Impacts SEO performance
|
||||
|
||||
**Number of Results**
|
||||
- More results = better analysis
|
||||
- Consider processing time
|
||||
- Balance quality vs. speed
|
||||
|
||||
**Time Range**
|
||||
- Anytime: All available content
|
||||
- Recent: Latest information
|
||||
- Historical: Past content
|
||||
|
||||
### Best Practices
|
||||
|
||||
1. **Start Conservative**
|
||||
- Begin with moderate settings
|
||||
- Adjust based on results
|
||||
- Monitor performance
|
||||
|
||||
2. **Consider Your Audience**
|
||||
- Match tone to reader expectations
|
||||
- Adjust complexity appropriately
|
||||
- Focus on value delivery
|
||||
|
||||
3. **Optimize for Platform**
|
||||
- Consider platform limitations
|
||||
- Match format requirements
|
||||
- Optimize for engagement
|
||||
|
||||
4. **Regular Review**
|
||||
- Monitor content performance
|
||||
- Adjust settings as needed
|
||||
- Stay updated with trends
|
||||
</div>
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
st.markdown("</div>", unsafe_allow_html=True)
|
||||
|
||||
# Close the container
|
||||
st.markdown("</div>", unsafe_allow_html=True)
|
||||
|
||||
# Add some spacing before the save button
|
||||
st.markdown("<div style='height: 1.5rem;'></div>", unsafe_allow_html=True)
|
||||
|
||||
if st.button("Save Manual Settings", type="primary", use_container_width=True):
|
||||
# Update main config with new values
|
||||
main_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
|
||||
}
|
||||
|
||||
main_config["Blog Images Details"] = {
|
||||
"Image Generation Model": image_model,
|
||||
"Number of Blog Images": num_images
|
||||
}
|
||||
|
||||
main_config["LLM Options"] = {
|
||||
"GPT Provider": gpt_provider,
|
||||
"Model": model,
|
||||
"Temperature": temperature,
|
||||
"Top-p": top_p,
|
||||
"Max Tokens": max_tokens,
|
||||
"Frequency Penalty": frequency_penalty,
|
||||
"Presence Penalty": presence_penalty
|
||||
}
|
||||
|
||||
main_config["Search Engine Parameters"] = {
|
||||
"Geographic Location": geo_location,
|
||||
"Search Language": search_language,
|
||||
"Number of Results": num_results,
|
||||
"Time Range": time_range
|
||||
}
|
||||
|
||||
if save_main_config(main_config):
|
||||
st.success("✅ Your personalization settings have been saved successfully!")
|
||||
else:
|
||||
st.error("Unable to save settings. Please try again.")
|
||||
|
||||
with tab2:
|
||||
st.markdown("#### ALwrity Personalization")
|
||||
|
||||
# Create two columns for the layout
|
||||
col1, col2 = st.columns([2, 1])
|
||||
|
||||
with col1:
|
||||
# Website URL input
|
||||
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. Leave empty if you want to provide written samples instead."
|
||||
)
|
||||
logger.debug(f"Website URL input value: {url}")
|
||||
|
||||
# Alternative: Written samples
|
||||
if not url:
|
||||
st.markdown("### Written Samples")
|
||||
st.markdown("""
|
||||
<div style='background-color: #f8f9fa; padding: 1rem; border-radius: 0.5rem; margin: 1rem 0;'>
|
||||
<p>No website URL? No problem! You can provide written samples of your content instead.</p>
|
||||
<p>Share your best articles, blog posts, or any content that represents your writing style.</p>
|
||||
</div>
|
||||
""", unsafe_allow_html=True)
|
||||
samples = st.text_area(
|
||||
"Paste your content samples here",
|
||||
help="Paste 2-3 samples of your best content. This helps ALwrity understand your writing style."
|
||||
)
|
||||
logger.debug(f"Sample text length: {len(samples) if samples else 0}")
|
||||
|
||||
# ALwrity Style button
|
||||
st.markdown("<div style='height: 20px'></div>", unsafe_allow_html=True)
|
||||
if st.button("🎨 ALwrity Style", use_container_width=True):
|
||||
if url:
|
||||
with st.status("Starting style analysis...", expanded=True) as status:
|
||||
try:
|
||||
logger.info(f"Starting style analysis for URL: {url}")
|
||||
|
||||
# Step 1: Initialize crawler
|
||||
status.update(label="Step 1/4: Initializing web crawler...", state="running")
|
||||
crawler_service = AsyncWebCrawlerService()
|
||||
|
||||
# Step 2: Crawl website
|
||||
status.update(label="Step 2/4: Crawling website content...", state="running")
|
||||
loop = asyncio.new_event_loop()
|
||||
asyncio.set_event_loop(loop)
|
||||
result = loop.run_until_complete(crawler_service.crawl_website(url))
|
||||
loop.close()
|
||||
|
||||
if result.get('success', False):
|
||||
content = result.get('content', {})
|
||||
|
||||
# Step 3: Initialize style analyzer
|
||||
status.update(label="Step 3/4: Analyzing content style...", state="running")
|
||||
style_analyzer = StyleAnalyzer()
|
||||
|
||||
# Step 4: Perform style analysis
|
||||
status.update(label="Step 4/4: Generating style recommendations...", state="running")
|
||||
style_analysis = style_analyzer.analyze_content_style(content)
|
||||
|
||||
if style_analysis.get('error'):
|
||||
status.update(label="Analysis failed", state="error")
|
||||
st.error(f"Style analysis failed: {style_analysis['error']}")
|
||||
else:
|
||||
status.update(label="Analysis complete!", state="complete")
|
||||
# Display style analysis results
|
||||
display_style_analysis(style_analysis)
|
||||
|
||||
# Display original content in tabs
|
||||
tab1, tab2, tab3 = st.tabs(["Content", "Metadata", "Links"])
|
||||
|
||||
with tab1:
|
||||
st.markdown("### Main Content")
|
||||
st.markdown(content.get('main_content', 'No content found'))
|
||||
|
||||
with tab2:
|
||||
st.markdown("### Metadata")
|
||||
st.markdown(f"""
|
||||
**Title:** {content.get('title', 'No title found')}
|
||||
|
||||
**Description:** {content.get('description', 'No description found')}
|
||||
|
||||
**Meta Tags:**
|
||||
{content.get('meta_tags', {})}
|
||||
""")
|
||||
|
||||
with tab3:
|
||||
st.markdown("### Links")
|
||||
for link in content.get('links', []):
|
||||
st.markdown(f"- [{link.get('text', '')}]({link.get('href', '')})")
|
||||
|
||||
else:
|
||||
status.update(label="Crawling failed", state="error")
|
||||
st.error(f"Failed to analyze website: {result.get('error', 'Unknown error')}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error during style analysis: {str(e)}")
|
||||
st.error(f"Analysis failed: {str(e)}")
|
||||
elif samples:
|
||||
with st.spinner("Analyzing content samples..."):
|
||||
try:
|
||||
# Initialize style analyzer
|
||||
style_analyzer = StyleAnalyzer()
|
||||
|
||||
# Analyze content samples
|
||||
style_analysis = style_analyzer.analyze_content_style({"main_content": samples})
|
||||
|
||||
if style_analysis.get('error'):
|
||||
st.error(f"Style analysis failed: {style_analysis['error']}")
|
||||
else:
|
||||
# Display style analysis results
|
||||
display_style_analysis(style_analysis)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error analyzing samples: {str(e)}")
|
||||
st.error(f"Analysis failed: {str(e)}")
|
||||
else:
|
||||
st.warning("Please provide either a website URL or content samples")
|
||||
|
||||
with col2:
|
||||
st.markdown("""
|
||||
### How ALwrity Discovers Your Style
|
||||
|
||||
**AI-Powered Style Analysis**
|
||||
|
||||
ALwrity AI analyzes your existing content to understand your unique writing style and preferences. This helps us generate content that matches your voice perfectly.
|
||||
|
||||
**Step 1: Content Analysis**
|
||||
|
||||
We'll analyze your website content or written samples to understand:
|
||||
|
||||
- Writing tone and voice
|
||||
- Vocabulary and language style
|
||||
- Content structure and formatting
|
||||
- Target audience and engagement style
|
||||
|
||||
**Step 2: Style Recommendations**
|
||||
|
||||
Based on the analysis, we'll provide:
|
||||
|
||||
- Personalized writing guidelines
|
||||
- Content structure templates
|
||||
- Tone and voice recommendations
|
||||
- Audience engagement strategies
|
||||
|
||||
**Step 3: Content Generation**
|
||||
|
||||
Finally, we'll use these insights to:
|
||||
|
||||
- Generate content that matches your style
|
||||
- Maintain consistency across all content
|
||||
- Optimize for your target audience
|
||||
- Ensure brand voice alignment
|
||||
""")
|
||||
|
||||
# API Configuration Form
|
||||
st.markdown("### API Configuration")
|
||||
with st.form("ai_config_form"):
|
||||
# API Keys
|
||||
st.text_input("OpenAI API Key", type="password", key="openai_key")
|
||||
st.text_input("Google API Key", type="password", key="google_key")
|
||||
st.text_input("SerpAPI Key", type="password", key="serpapi_key")
|
||||
|
||||
# Model Selection
|
||||
st.selectbox("Select Model", ["gpt-3.5-turbo", "gpt-4"], key="model")
|
||||
|
||||
# Temperature
|
||||
st.slider("Temperature", 0.0, 2.0, 0.7, 0.1, key="temperature")
|
||||
|
||||
# Max Tokens
|
||||
st.number_input("Max Tokens", 100, 4000, 2000, 100, key="max_tokens")
|
||||
|
||||
# Submit button
|
||||
submitted = st.form_submit_button("Save Configuration")
|
||||
|
||||
if submitted:
|
||||
# Create config directory if it doesn't exist
|
||||
config_dir = Path("config")
|
||||
config_dir.mkdir(exist_ok=True)
|
||||
|
||||
# Save configuration
|
||||
config = {
|
||||
"openai_key": st.session_state.openai_key,
|
||||
"google_key": st.session_state.google_key,
|
||||
"serpapi_key": st.session_state.serpapi_key,
|
||||
"model": st.session_state.model,
|
||||
"temperature": st.session_state.temperature,
|
||||
"max_tokens": st.session_state.max_tokens
|
||||
}
|
||||
|
||||
config_file = config_dir / "test_config.json"
|
||||
with open(config_file, "w") as f:
|
||||
json.dump(config, f, indent=4)
|
||||
|
||||
st.success("Configuration saved successfully!")
|
||||
|
||||
# Navigation buttons with correct arguments
|
||||
if render_navigation_buttons(4, 5, changes_made=True):
|
||||
st.session_state.current_step = 5
|
||||
st.rerun()
|
||||
|
||||
return {"current_step": 4, "changes_made": True}
|
||||
266
lib/utils/api_key_manager/components/website_setup.py
Normal file
266
lib/utils/api_key_manager/components/website_setup.py
Normal file
@@ -0,0 +1,266 @@
|
||||
"""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.seo_analyzer import analyze_seo
|
||||
import asyncio
|
||||
import sys
|
||||
from typing import Dict, Any
|
||||
from ..manager import APIKeyManager
|
||||
from .base import render_navigation_buttons
|
||||
|
||||
# Configure logger to output to both file and stdout
|
||||
logger.remove() # Remove default handler
|
||||
logger.add(
|
||||
"logs/website_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 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: Website Setup")
|
||||
|
||||
# Create two columns for input and results
|
||||
col1, col2 = st.columns([1, 1])
|
||||
|
||||
with col1:
|
||||
st.markdown("#### Enter Website URL")
|
||||
url = st.text_input("Website URL", placeholder="https://example.com")
|
||||
logger.debug(f"[render_website_setup] URL input value: {url}")
|
||||
|
||||
analyze_type = st.radio(
|
||||
"Analysis Type",
|
||||
["Basic Analysis", "Full Analysis with SEO"],
|
||||
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)
|
||||
|
||||
# If full analysis is selected, add SEO analysis
|
||||
if analyze_type == "Full Analysis with SEO":
|
||||
seo_results = analyze_seo(url)
|
||||
if seo_results.success:
|
||||
results['data']['seo_analysis'] = {
|
||||
'overall_score': seo_results.overall_score,
|
||||
'meta_tags': {
|
||||
'title': seo_results.meta_tags.title,
|
||||
'description': seo_results.meta_tags.description,
|
||||
'keywords': seo_results.meta_tags.keywords,
|
||||
'has_robots': seo_results.meta_tags.has_robots,
|
||||
'has_sitemap': seo_results.meta_tags.has_sitemap
|
||||
},
|
||||
'content': {
|
||||
'word_count': seo_results.content.word_count,
|
||||
'readability_score': seo_results.content.readability_score,
|
||||
'content_quality_score': seo_results.content.content_quality_score,
|
||||
'headings_structure': seo_results.content.headings_structure,
|
||||
'keyword_density': seo_results.content.keyword_density
|
||||
},
|
||||
'recommendations': [
|
||||
{
|
||||
'priority': rec.priority,
|
||||
'category': rec.category,
|
||||
'issue': rec.issue,
|
||||
'recommendation': rec.recommendation,
|
||||
'impact': rec.impact
|
||||
}
|
||||
for rec in seo_results.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:
|
||||
st.markdown("#### Analysis Results")
|
||||
|
||||
# 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 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 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}
|
||||
Reference in New Issue
Block a user