Made changes to Getting started with ALwrity and added lot of details on API keys

This commit is contained in:
ajaysi
2025-04-01 13:11:40 +05:30
parent 367f9bac2c
commit 6c833e2773
68 changed files with 8384 additions and 823 deletions

58
.gitignore vendored Normal file
View File

@@ -0,0 +1,58 @@
*cpython*
*.cpython*
.DS_Store
.vscode
*.pyc
.env
.env.local
.env.development.local
.env.test.local
pycache
__pycache__
*.pyc
*.pyo
*.pyd
*.pyw
*.pyz
*.pywz
*.pyzw
*.pyzp
*.pywp
*.pywpz
*.pywpzp
.swp
.swo
.swn
.swnw
.swnwp
.swnwpz
.swnwpzp
*.log
*.log.*
*.log.*.*
*.log.*.*.*
*.log.*.*.*.*
*.log.*.*.*.*.*
.venv
*.cpython*
*.cpython-312.pyc
*venv
*.venv
*.venv*
*.venv_*
*.venv_*_*
*.venv_*_*_*
*.venv_*_*_*_*
*venv
venv_new*
venv_*
AI-Writer_cursor_workspace.code-workspace
*.code-workspace
.cursorignore
lib/ai_writers/__pycache__/ai_agents_crew_writer.cpython-312.pyc

168
README.md
View File

@@ -284,3 +284,171 @@ Still stuck, [Open issue here](https://github.com/AJaySi/AI-Writer/issues) & Som
> SOFTWARE.
> </em></p>
## Easy Installation Guide for Content Creators
### Step 1: Install Python 3.11
1. Download Python 3.11 installer:
- Visit [Python 3.11.6 Download Page](https://www.python.org/downloads/release/python-3116/)
- Scroll down and click on "Windows installer (64-bit)"
- Save the file to your computer
2. Run the installer:
- Double click the downloaded file
- ✅ IMPORTANT: Check "Add Python 3.11 to PATH"
- Click "Install Now"
- Wait for installation to complete
- Click "Close"
### Step 2: Install ALwrity
1. Download this project:
- Click the green "Code" button above
- Select "Download ZIP"
- Extract the ZIP file to your desired location
2. Open Command Prompt:
- Press Windows + R
- Type "cmd" and press Enter
- Navigate to the extracted folder:
```
cd path\to\ALwrity
```
3. Run the automatic installer:
```
python setup.py install
```
### Troubleshooting
If you encounter any issues:
1. Make sure Python 3.11 is installed correctly:
- Open Command Prompt
- Type: `python --version`
- Should show: `Python 3.11.x`
2. Common Issues:
- If you see "Python is not recognized": Restart your computer
- If you get package errors: Run `pip install --upgrade pip` first
Need help? [Open an issue](../../issues) and we'll assist you!
## For Developers
If you're a developer or want to contribute:
```bash
# Clone the repository
git clone https://github.com/yourusername/ALwrity.git
# Create virtual environment
python -m venv venv
# Activate virtual environment
# On Windows:
.\venv\Scripts\activate
# On Mac/Linux:
source venv/bin/activate
# Install dependencies
pip install -r requirements.txt
```
# ALwrity - AI Content Writing Assistant
## Quick Start Guide for Non-Technical Users
### Option 1: One-Click Installation (Recommended)
1. Download this project:
- Click the green "Code" button above
- Select "Download ZIP"
- Extract the ZIP file to your desired location (e.g., Desktop)
2. Run the installer:
- Double-click `install.bat` in the extracted folder
- If Windows asks for permission, click "Yes"
- Follow the on-screen instructions
- Wait for the installation to complete
3. Start ALwrity:
- Open Command Prompt (Windows + R, type "cmd", press Enter)
- Navigate to the ALwrity folder:
```
cd path\to\ALwrity
```
- Type `alwrity` and press Enter
### Option 2: Manual Installation
If the one-click installer doesn't work, follow these steps:
1. Install Python 3.11:
- Visit [Python 3.11.6 Download Page](https://www.python.org/downloads/release/python-3116/)
- Click "Windows installer (64-bit)"
- Run the installer
- ✅ IMPORTANT: Check "Add Python 3.11 to PATH"
- Click "Install Now"
- Wait for installation to complete
2. Install ALwrity:
- Open Command Prompt (Windows + R, type "cmd", press Enter)
- Navigate to the ALwrity folder:
```
cd path\to\ALwrity
```
- Run the installation:
```
python setup.py install
```
- Follow any on-screen instructions
3. Start ALwrity:
- In the same Command Prompt window, type:
```
alwrity
```
- Press Enter
### Troubleshooting Guide
#### Common Issues and Solutions:
1. "Python is not recognized"
- Solution: Restart your computer after installing Python
- Make sure you checked "Add Python 3.11 to PATH" during installation
2. "Visual C++ Build Tools not found"
- Solution: Run this command in an administrative PowerShell:
```
winget install Microsoft.VisualStudio.2022.BuildTools --silent --override "--wait --quiet --add Microsoft.VisualStudio.Workload.VCTools --includeRecommended"
```
3. "Rust compiler not found"
- Solution: Run these commands in PowerShell:
```
Invoke-WebRequest -Uri https://static.rust-lang.org/rustup/dist/x86_64-pc-windows-msvc/rustup-init.exe -OutFile rustup-init.exe
./rustup-init.exe -y
```
4. Installation Errors
- Check the `install_errors.log` file in the ALwrity folder
- Share the error message with our support team
#### Need Help?
- Open an issue on GitHub
- Join our support community
- Contact our support team
### System Requirements
- Windows 10 or later
- Python 3.11.x
- At least 4GB RAM
- 2GB free disk space
### First-Time Setup
After installation:
1. The first time you run ALwrity, it will ask for your API keys
2. Follow the on-screen instructions to enter your keys
3. Your keys will be saved securely for future use
### Updating ALwrity
To update to the latest version:
1. Download the latest release
2. Run `install.bat` again
3. Follow the on-screen instructions

View File

@@ -1,24 +1,66 @@
import streamlit as st
# Set page config - must be the first Streamlit command
st.set_page_config(
page_title="AI Writer - Content Generation Platform",
page_icon="✍️",
layout="wide",
initial_sidebar_state="collapsed", # Start with collapsed sidebar
menu_items={
'Get Help': None,
'Report a bug': None,
'About': None
}
)
# Add CSS to hide sidebar during setup
st.markdown("""
<style>
#MainMenu {visibility: hidden;}
footer {visibility: hidden;}
.stDeployButton {display:none;}
/* Hide sidebar during setup */
[data-testid="stSidebar"] {
visibility: hidden !important;
width: 0px !important;
position: fixed !important;
}
</style>
""", unsafe_allow_html=True)
import os
import json
import base64
import logging
from datetime import datetime
# Configure logging
logging.basicConfig(
level=logging.DEBUG,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.StreamHandler(), # Output to console
#logging.FileHandler('alwrity.log') # Output to file
]
)
logger = logging.getLogger(__name__)
from lib.utils.config_manager import save_config
from lib.utils.ui_setup import setup_ui
from lib.utils.api_key_manager import check_all_api_keys
from lib.utils.alwrity_sidebar import sidebar_configuration
from lib.utils.api_key_manager.api_key_manager import APIKeyManager, render
from lib.utils.api_key_manager.validation import check_all_api_keys
from dotenv import load_dotenv
from lib.utils.content_generators import ai_writers, content_planning_tools, blog_from_keyword, story_input_section, essay_writer, ai_news_writer, ai_finance_ta_writer, write_ai_prod_desc, do_web_research, competitor_analysis, ai_agents_content_planner
from lib.utils.content_generators import ai_writers, content_planning_tools, blog_from_keyword, story_input_section, essay_writer, ai_news_writer, ai_finance_ta_writer, write_ai_prod_desc, do_web_research, competitor_analysis
from lib.utils.seo_tools import ai_seo_tools
from lib.utils.ui_setup import setup_ui, setup_tabs
from lib.utils.alwrity_utils import ai_agents_team, ai_social_writer
from lib.utils.file_processor import load_image, read_prompts, write_prompts
from lib.utils.voice_processing import record_voice
def process_folder_for_rag(folder_path):
"""Placeholder for the process_folder_for_rag function."""
logger.info(f"Processing folder for RAG: {folder_path}")
st.write(f"This is a placeholder for processing the folder: {folder_path}")
@@ -27,225 +69,77 @@ def save_config(config):
Saves the provided configuration dictionary to a JSON file specified by the environment variable.
"""
try:
logger.debug(f"Saving configuration to {os.getenv('ALWRITY_CONFIG')}")
with open(os.getenv("ALWRITY_CONFIG"), "w") as config_file:
json.dump(config, config_file, indent=4)
logger.info("Configuration saved successfully")
except Exception as e:
logger.error(f"Error saving configuration: {str(e)}", exc_info=True)
st.error(f"An error occurred while saving the configuration: {e}")
# Sidebar configuration
def sidebar_configuration():
st.sidebar.title("🛠️ Personalization & Settings 🏗️")
with st.sidebar.expander("**👷 Content Personalization**"):
blog_length = st.text_input("**Content Length (words)**", value="2000",
help="Approximate word count for blogs. Note: Actual length may vary based on GPT provider and max token count.")
blog_tone_options = ["Casual", "Professional", "How-to", "Beginner", "Research", "Programming", "Social Media", "Customize"]
blog_tone = st.selectbox("**Content Tone**",
options=blog_tone_options,
help="Select the desired tone for the blog content.")
if blog_tone == "Customize":
custom_tone = st.text_input("Enter the tone of your content", help="Specify the tone of your content.")
if custom_tone:
blog_tone = custom_tone
else:
st.warning("Please specify the tone of your content.")
blog_demographic_options = ["Professional", "Gen-Z", "Tech-savvy", "Student", "Digital Marketing", "Customize"]
blog_demographic = st.selectbox("**Target Audience**",
options=blog_demographic_options,
help="Select the primary audience for the blog content.")
if blog_demographic == "Customize":
custom_demographic = st.text_input("Enter your target audience",
help="Specify your target audience.",
placeholder="Eg. Domain expert, Content creator, Financial expert etc..")
if custom_demographic:
blog_demographic = custom_demographic
else:
st.warning("Please specify your target audience.")
blog_type = st.selectbox("**Content Type**",
options=["Informational", "Commercial", "Company", "News", "Finance", "Competitor", "Programming", "Scholar"],
help="Select the category that best describes the blog content.")
blog_language = st.selectbox("**Content Language**",
options=["English", "Spanish", "German", "Chinese", "Arabic", "Nepali", "Hindi", "Hindustani", "Customize"],
help="Select the language in which the blog will be written.")
if blog_language == "Customize":
custom_lang = st.text_input("Enter the language of your choice", help="Specify the content language.")
if custom_lang:
blog_language = custom_lang
else:
st.warning("Please specify the language of your content.")
blog_output_format = st.selectbox("**Content Output Format**",
options=["markdown", "HTML", "plaintext"],
help="Select the format for the blog output.")
with st.sidebar.expander("**🩻 Images Personalization**"):
image_generation_model = st.selectbox("**Image Generation Model**",
options=["stable-diffusion", "dalle2", "dalle3"],
help="Select the model to generate images for the blog.")
number_of_blog_images = st.number_input("**Number of Blog Images**", value=1, help="Specify the number of images to include in the blog.")
with st.sidebar.expander("**🤖 LLM Personalization**"):
gpt_provider = st.selectbox("**GPT Provider**",
options=["google", "openai", "minstral"],
help="Select the provider for the GPT model.")
model = st.text_input("**Model**", value="gemini-1.5-flash-latest", help="Specify the model version to use from the selected provider.")
temperature = st.slider(
"Temperature",
min_value=0.1,
max_value=1.0,
value=0.7,
step=0.1,
format="%.1f",
help="""Temperature controls the 'creativity' or randomness of the text generated by GPT.
Greater determinism with higher values indicating more randomness."""
)
top_p = st.slider(
"Top-p",
min_value=0.0,
max_value=1.0,
value=0.9,
step=0.1,
format="%.1f",
help="Top-p sampling controls the level of diversity in the generated text."
)
# Selectbox for max tokens
max_tokens_options = [500, 1000, 2000, 4000, 16000, 32000, 64000]
max_tokens = st.selectbox(
"Max Tokens",
options=max_tokens_options,
index=max_tokens_options.index(4000),
help="Max tokens determine the maximum length of the output sequence generated by a model."
)
n = st.number_input("N",
value=1,
min_value=1,
max_value=10,
help="Defines the number of words or characters grouped together in a sequence when analyzing text.")
frequency_penalty = st.slider(
"Frequency Penalty",
min_value=0.0,
max_value=2.0,
value=1.0,
step=0.1,
format="%.1f",
help="Influences word selection during text generation, promoting diversity with higher values."
)
presence_penalty = st.slider(
"Presence Penalty",
min_value=0.0,
max_value=2.0,
value=1.0,
step=0.1,
format="%.1f",
help="Encourages the use of diverse words by discouraging repetition."
)
with st.sidebar.expander("**🕵️ Search Engine Personalization**"):
geographic_location = st.selectbox("**Geographic Location**",
options=["us", "in", "fr", "cn"],
help="Select the geographic location for tailoring search results.")
search_language = st.selectbox("**Search Language**",
options=["en", "zn-cn", "de", "hi"],
help="Select the language for the search results.")
number_of_results = st.number_input("**Number of Results**",
value=10,
max_value=20,
min_value=1,
help="Specify the number of search results to retrieve.")
time_range = st.selectbox("**Time Range**",
options=["anytime", "past day", "past week", "past month", "past year"],
help="Select the time range for filtering search results.")
include_domains = st.text_input("**Include Domains**", value="",
help="List specific domains to include in search results. Leave blank to include all domains.")
similar_url = st.text_input("**Similar URL**", value="", help="Provide a URL to find similar results. Leave blank if not needed.")
# Storing collected inputs in a dictionary
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_output_format
},
"Blog Images Details": {
"Image Generation Model": image_generation_model,
"Number of Blog Images": number_of_blog_images
},
"LLM Options": {
"GPT Provider": gpt_provider,
"Model": model,
"Temperature": temperature,
"Top-p": top_p,
"Max Tokens": max_tokens,
"N": n,
"Frequency Penalty": frequency_penalty,
"Presence Penalty": presence_penalty
},
"Search Engine Parameters": {
"Geographic Location": geographic_location,
"Search Language": search_language,
"Number of Results": number_of_results,
"Time Range": time_range,
"Include Domains": include_domains,
"Similar URL": similar_url
}
}
# Writing the configuration to a file whenever a change is made
save_config(config)
def main():
"""Main application entry point."""
# Initialize API key manager
api_key_manager = APIKeyManager()
# Setup UI
setup_ui()
#load_environment
load_dotenv()
setup_ui()
if check_all_api_keys():
logger.debug("Environment variables loaded")
setup_environment_paths()
logger.debug("Environment paths configured")
# Check API keys and show setup if needed
if not check_all_api_keys(api_key_manager):
logger.info("API keys not verified")
render(api_key_manager)
return
else:
logger.info("All API keys verified")
# Remove the CSS that hides the sidebar
st.markdown("""
<style>
#MainMenu {visibility: visible;}
footer {visibility: visible;}
.stDeployButton {display:block;}
[data-testid="stSidebar"] {
visibility: visible !important;
width: 250px !important;
position: relative !important;
}
[data-testid="stSidebar"][aria-expanded="true"] {
width: 250px !important;
}
[data-testid="stSidebar"][aria-expanded="false"] {
width: 250px !important;
}
.main .block-container {
padding-left: 2rem;
}
</style>
""", unsafe_allow_html=True)
setup_environment_paths()
sidebar_configuration()
setup_tabs()
modify_prompts_sidebar()
def setup_environment_paths():
"""Sets up environment paths for saving files and configurations."""
os.environ["SEARCH_SAVE_FILE"] = os.path.join(os.getcwd(), "lib", "workspace", "alwrity_web_research",
logger.debug("Setting up environment paths")
try:
os.environ["SEARCH_SAVE_FILE"] = os.path.join(os.getcwd(), "lib", "workspace", "alwrity_web_research",
f"web_research_report_{datetime.now().strftime('%Y-%m-%d_%H-%M-%S')}")
os.environ["IMG_SAVE_DIR"] = os.path.join(os.getcwd(), "lib", "workspace", "alwrity_content")
os.environ["CONTENT_SAVE_DIR"] = os.path.join(os.getcwd(), "lib", "workspace", "alwrity_content")
os.environ["PROMPTS_DIR"] = os.path.join(os.getcwd(), "lib", "workspace", "alwrity_prompts")
os.environ["ALWRITY_CONFIG"] = os.path.join(os.getcwd(), "lib", "workspace", "alwrity_config", "main_config.json")
def modify_prompts_sidebar():
"""Provides a sidebar for modifying prompts."""
st.sidebar.title("📝 Modify Prompts")
prompts = read_prompts()
if prompts:
edited_prompts = []
for i, prompt in enumerate(prompts):
edited_prompt = st.sidebar.text_area(f"Prompt {i+1}", prompt)
edited_prompts.append(edited_prompt)
if st.sidebar.button("Save Prompts"):
write_prompts(edited_prompts)
st.sidebar.success("Prompts saved successfully!")
else:
st.sidebar.warning("No prompts found in the file.")
os.environ["IMG_SAVE_DIR"] = os.path.join(os.getcwd(), "lib", "workspace", "alwrity_content")
os.environ["CONTENT_SAVE_DIR"] = os.path.join(os.getcwd(), "lib", "workspace", "alwrity_content")
os.environ["PROMPTS_DIR"] = os.path.join(os.getcwd(), "lib", "workspace", "alwrity_prompts")
os.environ["ALWRITY_CONFIG"] = os.path.join(os.getcwd(), "lib", "workspace", "alwrity_config", "main_config.json")
logger.info("Environment paths configured successfully")
except Exception as e:
logger.error(f"Error setting up environment paths: {str(e)}", exc_info=True)
raise
# Functions for the main options
@@ -278,34 +172,6 @@ def ai_writers():
st.subheader("Exiting, Getting Lost. But.... I have nowhere to go 🥹🥹")
def content_planning_tools():
st.markdown("""**Alwrity content Ideation & Planning** : Provide few keywords to do comprehensive web research.
Provide few keywords to get Google, Neural, pytrends analysis. Know keywords, blog titles to target.
Generate months long content calendar around given keywords.""")
options = [
"Keywords Researcher",
"Competitor Analysis",
"Content Calender Ideator"
]
choice = st.radio("Select a content planning tool:", options, index=0, format_func=lambda x: f"🔍 {x}")
if choice == "Keywords Researcher":
do_web_research()
elif choice == "Competitor Analysis":
competitor_analysis()
elif choice == "Content Calender Ideator":
plan_keywords = st.text_input(
"**Enter Your main Keywords to get 2 months content calendar:**",
placeholder="Enter 2-3 main keywords to generate AI content calendar with keyword researched blog titles",
help="The keywords are the ones where you would want to generate 50-60 blogs/articles on."
)
if st.button("**Ideate Content Calender**"):
if plan_keywords:
ai_agents_content_planner(plan_keywords)
else:
st.error("Come on, really, Enter some keywords to plan on..")
def alwrity_brain():
st.title("🧠 Alwrity Brain, Better than yours!")

39
install.bat Normal file
View File

@@ -0,0 +1,39 @@
@echo off
echo Welcome to ALwrity Installer
echo ============================
echo.
:: Check if Python 3.11 is installed
python --version 2>nul | findstr /i "3.11" >nul
if errorlevel 1 (
echo Python 3.11 is not installed or not in PATH
echo Please install Python 3.11 from https://www.python.org/downloads/release/python-3116/
echo Make sure to check "Add Python 3.11 to PATH" during installation
echo.
echo Press any key to open the download page...
pause >nul
start https://www.python.org/downloads/release/python-3116/
exit /b 1
)
:: Create virtual environment if it doesn't exist
if not exist "venv" (
echo Creating virtual environment...
python -m venv venv
)
:: Activate virtual environment and install requirements
echo Activating virtual environment...
call venv\Scripts\activate.bat
echo Upgrading pip...
python -m pip install --upgrade pip
echo Installing ALwrity...
python setup.py install
echo.
echo Installation complete!
echo To start ALwrity, open a new command prompt and type: alwrity
echo.
pause

View File

@@ -1,168 +0,0 @@
#!/usr/bin/env python3
"""
Installation helper script for AI-Writer
This script checks for required system dependencies and guides the user through installation
"""
import os
import sys
import platform
import subprocess
import shutil
import datetime
import socket
import traceback
def log_error(error_type, details):
"""
Logs installation errors to a file with timestamp and system information.
Args:
error_type: Type of error (e.g., 'Python Version Check', 'Rust Installation')
details: Detailed error message
"""
log_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'install_errors.log')
timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
# Collect system information
system_info = {
"OS": platform.system(),
"OS Version": platform.version(),
"Architecture": platform.machine(),
"Python Version": f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}",
"Hostname": socket.gethostname()
}
# Format the log entry
log_entry = f"[{timestamp}] ERROR: {error_type}\n"
log_entry += f"Details: {details}\n"
log_entry += "System Information:\n"
for key, value in system_info.items():
log_entry += f" {key}: {value}\n"
log_entry += "-" * 80 + "\n"
# Write to log file
with open(log_file, 'a') as f:
f.write(log_entry)
print(f"Error logged to {log_file}")
def check_python_version():
print("Checking Python version...")
version = sys.version_info
if version.major < 3 or (version.major == 3 and version.minor < 10):
error_msg = f"Python 3.10+ is required. Found Python {version.major}.{version.minor}"
print(f"Error: {error_msg}")
log_error("Python Version Check", error_msg)
return False
print(f"✓ Python {version.major}.{version.minor}.{version.micro} found")
return True
def check_visual_cpp_build_tools():
if platform.system() != "Windows":
return True
print("Checking for Visual C++ Build Tools...")
# Check if cl.exe exists in PATH
if shutil.which("cl"):
print("✓ Visual C++ Build Tools found")
return True
error_msg = "Visual C++ Build Tools not found. Required for building certain Python packages."
print("❌ Visual C++ Build Tools not found")
print("\nVisual C++ Build Tools are required to build certain Python packages.")
print("To install Visual C++ Build Tools:")
print("Option 1: Run this command in an administrative PowerShell:")
print(" winget install Microsoft.VisualStudio.2022.BuildTools --silent --override \"--wait --quiet --add Microsoft.VisualStudio.Workload.VCTools --includeRecommended\"")
print("\nOption 2: Download and install from the official Microsoft website:")
print(" https://visualstudio.microsoft.com/visual-cpp-build-tools/")
log_error("Visual C++ Build Tools Check", error_msg)
return False
def check_rust_compiler():
print("Checking for Rust compiler...")
# Check if rustc exists in PATH
if shutil.which("rustc"):
print("✓ Rust compiler found")
return True
error_msg = "Rust compiler not found. Required for building certain Python packages."
print("❌ Rust compiler not found")
if platform.system() == "Windows":
print("\nTo install Rust on Windows, run:")
print(" Invoke-WebRequest -Uri https://static.rust-lang.org/rustup/dist/x86_64-pc-windows-msvc/rustup-init.exe -OutFile rustup-init.exe")
print(" ./rustup-init.exe -y")
else:
print("\nTo install Rust on Linux/macOS, run:")
print(" curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y")
print(" source $HOME/.cargo/env")
log_error("Rust Compiler Check", error_msg)
return False
def main():
print("AI-Writer Dependency Checker\n")
all_checks_passed = True
# Run dependency checks
if not check_python_version():
all_checks_passed = False
if not check_visual_cpp_build_tools():
all_checks_passed = False
if not check_rust_compiler():
all_checks_passed = False
# If all checks pass, create virtual environment and install requirements
if all_checks_passed:
print("\nAll system dependencies found!")
# Ask user if they want to proceed with installation
response = input("\nWould you like to create a virtual environment and install Python dependencies? (y/n): ")
if response.lower() == 'y':
print("\nCreating virtual environment...")
try:
if platform.system() == "Windows":
venv_result = os.system("python -m venv venv")
if venv_result != 0:
raise Exception(f"Failed to create virtual environment, exit code: {venv_result}")
install_result = os.system("venv\\Scripts\\activate && pip install -r requirements.txt")
if install_result != 0:
raise Exception(f"Failed to install dependencies, exit code: {install_result}")
else:
venv_result = os.system("python3 -m venv venv")
if venv_result != 0:
raise Exception(f"Failed to create virtual environment, exit code: {venv_result}")
install_result = os.system("source venv/bin/activate && pip install -r requirements.txt")
if install_result != 0:
raise Exception(f"Failed to install dependencies, exit code: {install_result}")
except Exception as e:
error_msg = str(e)
print(f"\nError during installation: {error_msg}")
log_error("Dependency Installation", f"{error_msg}\n{traceback.format_exc()}")
print("Please check the install_errors.log file for details.")
print("\nInstallation complete! To run the application:")
print("1. Activate the virtual environment:")
if platform.system() == "Windows":
print(" venv\\Scripts\\activate")
else:
print(" source venv/bin/activate")
print("2. Run the application:")
print(" streamlit run alwrity.py")
else:
print("\nSkipping dependency installation. You can install them manually with:")
print("1. Create a virtual environment: python -m venv venv")
print("2. Activate the virtual environment")
print("3. Install dependencies: pip install -r requirements.txt")
else:
print("\nPlease install the missing dependencies and try again.")
print("Check the install_errors.log file for detailed error information.")
if __name__ == "__main__":
main()

View File

@@ -24,14 +24,14 @@
# Scrape the website for contact details (email addresses, contact forms, etc.).
# Use natural language processing (NLP) to understand the type of content on the website and who the contact person might be (webmaster, editor, or guest post manager).
# Website Content Understanding:
# Scrape a summary of each websites content (e.g., their blog topics, categories, and tone) to personalize the email based on the site's focus.
# Scrape a summary of each website's content (e.g., their blog topics, categories, and tone) to personalize the email based on the site's focus.
#
# Personalized Outreach:
# AI Email Composition:
# Compose personalized outreach emails based on:
# The scraped data (website content, topic focus, etc.).
# The user's input (what kind of guest post or content they want to contribute).
# Example: Hi [Webmaster Name], I noticed that your site [Site Name] features high-quality content about [Topic]. I would love to contribute a guest post on [Proposed Topic] in exchange for a backlink.
# Example: "Hi [Webmaster Name], I noticed that your site [Site Name] features high-quality content about [Topic]. I would love to contribute a guest post on [Proposed Topic] in exchange for a backlink."
#
# Automated Email Sending:
# Review Emails (Optional HITL):
@@ -50,7 +50,7 @@
# Automated Responses:
# If a website replies positively, AI can respond with predefined follow-up emails (e.g., proposing topics, confirming submission deadlines).
# Follow-up Reminders:
# If theres no reply, the system can send polite follow-up reminders at pre-set intervals.
# If there's no reply, the system can send polite follow-up reminders at pre-set intervals.
#
#Key Features:
#
@@ -71,7 +71,7 @@
#
# Lead Tracking and Management:
# Track all emails sent, monitor replies, and keep track of successful backlinks.
# Log each leads status (e.g., emailed, responded, no reply) to manage future interactions.
# Log each lead's status (e.g., emailed, responded, no reply) to manage future interactions.
#
# Multiple Keywords/Queries:
# Allow users to run the same process for a batch of keywords, automatically generating relevant search queries for each.
@@ -89,13 +89,13 @@
# Prioritize high-authority websites to maximize the impact of backlinks.
#
# Spam Detection:
# Use AI to detect and avoid spammy or low-quality websites that might harm the users SEO.
# Use AI to detect and avoid spammy or low-quality websites that might harm the user's SEO.
#
# Contact Form Auto-Fill:
# If the site only offers a contact form (without email), automatically fill and submit the form with AI-generated content.
#
# Dynamic Content Suggestions:
# Suggest guest post topics based on the websites focus, using NLP to analyze the site's existing content.
# Suggest guest post topics based on the website's focus, using NLP to analyze the site's existing content.
#
# Bulk Email Support:
# Allow users to bulk-send outreach emails while still personalizing each message for scalability.
@@ -130,7 +130,7 @@
import sys
from googlesearch import search
# from googlesearch import search # Temporarily disabled for future enhancement
from loguru import logger
from lib.ai_web_researcher.firecrawl_web_crawler import scrape_website
from lib.gpt_providers.text_generation.main_text_generation import llm_text_gen
@@ -180,48 +180,32 @@ def find_backlink_opportunities(keyword):
search_queries = generate_search_queries(keyword)
results = []
for query in search_queries:
urls = search_for_urls(query)
for url in urls:
website_data = scrape_website(url)
logger.info(f"Scraped Website content for {url}: {website_data}")
if website_data:
contact_info = extract_contact_info(website_data)
logger.info(f"Contact details found for {url}: {contact_info}")
# Temporarily disabled Google search functionality
# for query in search_queries:
# urls = search_for_urls(query)
# for url in urls:
# website_data = scrape_website(url)
# logger.info(f"Scraped Website content for {url}: {website_data}")
# if website_data:
# contact_info = extract_contact_info(website_data)
# logger.info(f"Contact details found for {url}: {contact_info}")
# Placeholder return for now
return []
# AI-driven insights using website data
insights_prompt = f"""
You are an expert in analyzing website content. Below is the content of a website. Please analyze it and provide actionable insights for a personalized guest post outreach:
Website Content:
{website_data.get("content_summary", "")}
1. **Website Focus**: What is the primary topic, audience, and tone?
2. **Guest Posting Guidelines**: Are there any guest post preferences (content type, length, etc.)?
3. **Suggested Topics**: Based on the sites content, what topics might align well?
4. **Personalization Tips**: How can we make the outreach more tailored to this site?
"""
insights = llm_text_gen(insights_prompt)
detailed_result = {
"url": url,
"metadata": {
"title": website_data.get("metadata", {}).get("title", ""),
"description": website_data.get("metadata", {}).get("description", ""),
"keywords": website_data.get("metadata", {}).get("keywords", []),
},
"content_summary": website_data.get("content_summary", ""),
"contact_info": contact_info,
"insights": insights,
"backlink_opportunity": {
"query": query,
"context": "Guest post opportunity"
}
}
results.append(detailed_result)
return results
def search_for_urls(query):
"""
Search for URLs using Google search.
Args:
query (str): The search query.
Returns:
list: List of URLs found.
"""
# Temporarily disabled Google search functionality
# return list(search(query, num_results=10))
return []
def compose_personalized_email(website_data, insights, user_proposal):
"""
@@ -300,24 +284,6 @@ def send_email(smtp_server, smtp_port, smtp_user, smtp_password, to_email, subje
logger.error(f"Failed to send email to {to_email}: {e}")
return False
def search_for_urls(query):
"""
Search for URLs based on a query using Firecrawl.
Args:
query (str): The search query.
Returns:
list: A list of URLs.
"""
# We can use Firecrawl, which also provides AI extraction.
try:
google_search_result = search(query, max_results=5)
print(google_search_result)
return google_search_result
except Exception as err:
logger.error(f"Failed to do GoogleSearch: {err}")
def extract_contact_info(website_data):
"""
Extract contact information from website data.

View File

@@ -2,7 +2,7 @@ import os
import json
import streamlit as st
from tenacity import retry, stop_after_attempt, wait_random_exponential
import cloudscraper
import crawl4ai
from bs4 import BeautifulSoup
import requests
import csv
@@ -18,7 +18,7 @@ from ..gpt_providers.text_generation.main_text_generation import llm_text_gen
def fetch_and_parse_html(url):
"""
Fetches HTML content from the given URL using CloudScraper and parses it with BeautifulSoup.
Fetches HTML content from the given URL using crawl4ai and parses it with BeautifulSoup.
Args:
url (str): The URL of the webpage to fetch.
@@ -27,9 +27,8 @@ def fetch_and_parse_html(url):
BeautifulSoup: Parsed HTML content.
"""
try:
scraper = cloudscraper.create_scraper()
html = scraper.get(url)
soup = BeautifulSoup(html.text, 'html.parser')
html = crawl4ai.get(url)
soup = BeautifulSoup(html, 'html.parser')
return soup
except Exception as e:
st.error(f"⚠️ Error fetching or parsing HTML: {e}")

View File

@@ -1,7 +1,35 @@
import textstat
import streamlit as st
"""Text analysis tools using textstat."""
st.set_page_config(layout="wide", page_title="Text Readability Analyzer", page_icon=":book:")
import streamlit as st
from textstat import textstat
def analyze_text(text):
"""Analyze text using textstat metrics."""
if not text:
st.warning("Please enter some text to analyze.")
return
# Calculate various metrics
metrics = {
"Flesch Reading Ease": textstat.flesch_reading_ease(text),
"Flesch-Kincaid Grade Level": textstat.flesch_kincaid_grade(text),
"Gunning Fog Index": textstat.gunning_fog(text),
"SMOG Index": textstat.smog_index(text),
"Automated Readability Index": textstat.automated_readability_index(text),
"Coleman-Liau Index": textstat.coleman_liau_index(text),
"Linsear Write Formula": textstat.linsear_write_formula(text),
"Dale-Chall Readability Score": textstat.dale_chall_readability_score(text),
"Readability Consensus": textstat.readability_consensus(text)
}
# Display metrics in a clean format
st.subheader("Text Analysis Results")
for metric, value in metrics.items():
st.metric(metric, f"{value:.2f}")
# Add visualizations
st.subheader("Visualization")
st.bar_chart(metrics)
st.title("📖 Text Readability Analyzer: Making Your Content Easy to Read")
@@ -10,122 +38,6 @@ st.write("""
Just paste in a sample of your text, and we'll break down the readability scores and offer actionable tips!
""")
def analyze_text(test_data):
"""
Analyzes the readability of the provided text and returns a dictionary with the results.
Parameters:
test_data (str): The text to be analyzed.
Returns:
dict: A dictionary containing readability scores and additional metrics.
"""
return {
"Flesch Reading Ease": {
"score": textstat.flesch_reading_ease(test_data),
"description": "This score rates your text on a scale of 0-100, with higher scores being easier to read.",
"tips": [
"Score below 30? Simplify your text by breaking down complex sentences, using shorter words, and avoiding jargon.",
"Score around 60-70? You're in the 'standard' range.",
"Score over 90? Your text is very easy to read. Add some complexity or sophistication if needed."
]
},
"Flesch-Kincaid Grade Level": {
"score": textstat.flesch_kincaid_grade(test_data),
"description": "This formula estimates the US school grade level needed to understand your text.",
"tips": [
"High Score? Your writing might be too complex for your target audience.",
"Low Score? Your audience might find the text too simple.",
"Match Your Audience: Tailor the complexity to your readers."
]
},
"SMOG Index": {
"score": textstat.smog_index(test_data),
"description": "This formula measures text complexity by looking at the number of long words and sentences.",
"tips": [
"Best for texts with at least 30 sentences.",
"Adjust complexity to match your target audience."
]
},
"Coleman-Liau Index": {
"score": textstat.coleman_liau_index(test_data),
"description": "This formula uses sentence length and the number of syllables per word to estimate the reading level."
},
"Automated Readability Index (ARI)": {
"score": textstat.automated_readability_index(test_data),
"description": "Estimates the grade level required to comprehend your text."
},
"Dale-Chall Readability Score": {
"score": textstat.dale_chall_readability_score(test_data),
"description": "Focuses on the number of uncommon words (not on a list of 3000 common words) and sentence length.",
"tips": [
"Easy to Understand: Aim for a score around the reading level of your audience.",
"High School Level? Scores between 9 and 12 indicate a high school reading level.",
"Beyond High School? Scores above 12 are usually for a college-level audience."
]
},
"Gunning Fog": {
"score": textstat.gunning_fog(test_data),
"description": "Calculates the grade level required to understand the text."
},
"Linsear Write Formula": {
"score": textstat.linsear_write_formula(test_data),
"description": "Estimates the US grade level needed to understand the text."
},
"Text Standard (Consensus)": {
"score": textstat.text_standard(test_data),
"description": "A consensus estimate of the US grade level needed to understand your text, based on multiple readability scores."
},
"Spache Readability": {
"score": textstat.spache_readability(test_data),
"description": "Best for analyzing text for children, typically up to grade 4.",
"tips": [
"Considers the number of unfamiliar words and the length of sentences."
]
},
"McAlpine EFLAW": {
"score": textstat.mcalpine_eflaw(test_data),
"description": "Evaluates text for foreign language learners, focusing on 'miniwords' and sentence length.",
"tips": [
"Target Score: Aim for a score of 25 or less."
]
},
"Reading Time": {
"score": textstat.reading_time(test_data),
"description": "Estimated reading time in minutes."
},
"Syllable Count": {
"score": textstat.syllable_count(test_data),
"description": "The number of syllables in the text."
},
"Word Count": {
"score": textstat.lexicon_count(test_data),
"description": "The number of words in the text."
},
"Sentence Count": {
"score": textstat.sentence_count(test_data),
"description": "The number of sentences in the text."
},
"Character Count": {
"score": textstat.char_count(test_data),
"description": "The number of characters in the text."
},
"Letter Count (without punctuation)": {
"score": textstat.letter_count(test_data),
"description": "The number of letters without punctuation."
},
"Polysyllable Count": {
"score": textstat.polysyllabcount(test_data),
"description": "The number of polysyllabic words in the text."
},
"Monosyllable Count": {
"score": textstat.monosyllabcount(test_data),
"description": "The number of monosyllabic words in the text."
}
}
text_input = st.text_area("Paste your text here:", height=200)
if st.button("Analyze!"):
@@ -134,18 +46,7 @@ if st.button("Analyze!"):
if not test_data.strip():
st.error("Please enter text to analyze.")
else:
results = analyze_text(test_data)
st.subheader("Readability Scores:")
st.write("---")
for metric, data in results.items():
st.markdown(f"**{metric}:** {data['score']}")
st.markdown(f"* **What It Means:** {data['description']}")
if 'tips' in data:
st.markdown("* **Actionable Tips:**")
for tip in data['tips']:
st.markdown(f" * {tip}")
st.write(" ")
analyze_text(test_data)
st.subheader("Key Takeaways:")
st.write("---")

View File

@@ -1,3 +1,5 @@
"""Webpage content analysis tool."""
import streamlit as st
import requests
from bs4 import BeautifulSoup
@@ -7,8 +9,7 @@ from nltk.tokenize import word_tokenize
from nltk.util import ngrams
from langchain.llms import OpenAI
from langchain.chains import ConversationChain
st.set_page_config(layout="wide", page_title="Web Content Analyzer - Dive Deep with AI!", page_icon=":mag_right:")
from urllib.parse import urlparse
st.title("🧠 Web Content Analyzer: Uncover Hidden Insights with AI! 🧠")
st.write("""
@@ -39,19 +40,36 @@ if st.button("Analyze with AI!"):
st.stop()
try:
# Validate URL
parsed_url = urlparse(url)
if not parsed_url.scheme:
url = "https://" + url
# Fetch webpage content
response = requests.get(url)
response.raise_for_status()
soup = BeautifulSoup(response.content, 'html.parser')
body_txt = soup.find('body').text
words = [w.lower() for w in word_tokenize(body_txt)]
stopw = nltk.corpus.stopwords.words(language)
final_words = [w for w in words if w not in stopw and w.isalpha()]
response.raise_for_status()
# Parse HTML
soup = BeautifulSoup(response.text, 'html.parser')
# Extract content
title = soup.title.string if soup.title else "No title found"
meta_description = soup.find('meta', {'name': 'description'})
description = meta_description['content'] if meta_description else "No description found"
# Display results
st.subheader("Page Analysis")
st.metric("Title", title)
st.metric("Description", description)
# Content statistics
text_content = soup.get_text()
words = text_content.split()
st.metric("Word Count", len(words))
st.metric("Unique Words", len(set(words)))
# Frequency analysis (same as before)
freq = nltk.FreqDist(final_words)
freq = nltk.FreqDist(words)
keywords = freq.most_common(10)
df_keywords = pd.DataFrame(keywords, columns=("Keyword", "Frequency"))
@@ -60,19 +78,19 @@ if st.button("Analyze with AI!"):
st.write(" ")
st.markdown("**Main Theme:**")
ai_theme = conversation_chain.run(f"What is the main theme or topic of this content? \n {body_txt}")
ai_theme = conversation_chain.run(f"What is the main theme or topic of this content? \n {text_content}")
st.markdown(f" {ai_theme}")
st.write(" ")
st.markdown("**Suggested Keywords:**")
ai_keywords = conversation_chain.run(f"What other relevant keywords might be helpful to target for this content? \n {body_txt}")
ai_keywords = conversation_chain.run(f"What other relevant keywords might be helpful to target for this content? \n {text_content}")
st.markdown(f" {ai_keywords}")
st.write(" ")
st.markdown("**Content Improvement:**")
ai_improvement = conversation_chain.run(f"What could be done to improve this content for clarity, engagement, or SEO? \n {body_txt}")
ai_improvement = conversation_chain.run(f"What could be done to improve this content for clarity, engagement, or SEO? \n {text_content}")
st.markdown(f" {ai_improvement}")
# --- Display Frequency Results ---
@@ -94,3 +112,5 @@ if st.button("Analyze with AI!"):
""")
except requests.exceptions.RequestException as e:
st.error(f"Oops! Something went wrong fetching the URL. Error: {e}")
except Exception as e:
st.error(f"An error occurred: {e}")

View File

@@ -1,3 +1,5 @@
"""Word cloud generation tool."""
import streamlit as st
import requests
from bs4 import BeautifulSoup
@@ -5,8 +7,8 @@ import pandas as pd
import nltk
from nltk.tokenize import word_tokenize
from nltk.util import ngrams
st.set_page_config(layout="wide", page_title="Web Content Analyzer - Dive Into Your Words!", page_icon=":mag:")
from wordcloud import WordCloud
import matplotlib.pyplot as plt
st.title("🔎 Web Content Analyzer: Uncover Your Words' Power! 🔎")
st.write("""
@@ -86,3 +88,26 @@ if st.button("Analyze Your Content!"):
except requests.exceptions.RequestException as e:
st.error(f"Oops! Something went wrong fetching the URL. Error: {e}")
def generate_wordcloud(text):
"""Generate a word cloud from the given text."""
if not text:
st.warning("Please enter some text to generate a word cloud.")
return
# Create and generate a word cloud image
wordcloud = WordCloud(width=800, height=400, background_color='white').generate(text)
# Display the word cloud
st.subheader("Word Cloud Visualization")
fig, ax = plt.subplots(figsize=(10, 5))
ax.imshow(wordcloud, interpolation='bilinear')
ax.axis('off')
st.pyplot(fig)
# Add some statistics
st.subheader("Text Statistics")
words = text.split()
unique_words = set(words)
st.metric("Total Words", len(words))
st.metric("Unique Words", len(unique_words))

View File

@@ -1,3 +1,5 @@
"""AI-powered blog rewriter tool."""
import streamlit as st
from bs4 import BeautifulSoup
import requests
@@ -9,12 +11,6 @@ from exa_py import Exa
generator = pipeline('text-generation', model='gpt-3') # Example, adjust based on your model
def main():
st.set_page_config(
page_title="AI Blog Content Refresher",
page_icon=":pencil2:",
layout="wide"
)
st.markdown("<h1 style='text-align: center; color: #1565C0;'>AI Blog Content Refresher</h1>", unsafe_allow_html=True)
st.markdown("<h3 style='text-align: center;'>Keep your blog fresh and engaging with AI!</h3>", unsafe_allow_html=True)

105
lib/gpt_providers/config.py Normal file
View File

@@ -0,0 +1,105 @@
"""Configuration management for GPT providers."""
import os
import json
from loguru import logger
import sys
# Configure logger to output to both file and stdout
logger.remove() # Remove default handler
logger.add(
"logs/config.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_config() -> Optional[Dict]:
"""
Load configuration from environment or config file.
Returns:
Optional[Dict]: Configuration dictionary or None if loading fails
"""
try:
logger.info("[load_config] Starting configuration load")
# First try to load from environment variable
config_str = os.getenv('ALWRITY_CONFIG')
if config_str:
logger.debug("[load_config] Found configuration in environment variable")
try:
config = json.loads(config_str)
logger.info("[load_config] Successfully loaded configuration from environment")
return config
except json.JSONDecodeError as e:
logger.error(f"[load_config] Failed to parse environment config: {str(e)}")
# If no environment variable, try to load from file
config_path = os.getenv('ALWRITY_CONFIG', 'config.json')
logger.debug(f"[load_config] Attempting to load config from file: {config_path}")
if os.path.exists(config_path):
try:
with open(config_path, 'r') as f:
config = json.load(f)
logger.info("[load_config] Successfully loaded configuration from file")
return config
except json.JSONDecodeError as e:
logger.error(f"[load_config] Failed to parse config file: {str(e)}")
except Exception as e:
logger.error(f"[load_config] Error reading config file: {str(e)}")
else:
logger.error(f"[load_config] Config file not found: {config_path}")
return None
except Exception as e:
logger.error(f"[load_config] Unexpected error loading configuration: {str(e)}")
return None
def read_return_config_section(section: str) -> tuple:
"""
Read a specific section from the configuration.
Args:
section (str): The section to read
Returns:
tuple: Configuration values
"""
try:
logger.info(f"[read_return_config_section] Reading section: {section}")
config = load_config()
if not config:
logger.error("[read_return_config_section] No configuration available")
return None, None, None, None, None, None, None
section_config = config.get(section, {})
logger.debug(f"[read_return_config_section] Section config: {section_config}")
# Extract values with defaults
gpt_provider = section_config.get('gpt_provider', 'openai')
model = section_config.get('model', 'gpt-3.5-turbo')
temperature = float(section_config.get('temperature', 0.7))
max_tokens = int(section_config.get('max_tokens', 2000))
top_p = float(section_config.get('top_p', 1.0))
n = int(section_config.get('n', 1))
fp = section_config.get('fp', 'json')
logger.info(f"[read_return_config_section] Successfully read configuration for {section}")
logger.debug(f"[read_return_config_section] Values: provider={gpt_provider}, model={model}, "
f"temperature={temperature}, max_tokens={max_tokens}")
return gpt_provider, model, temperature, max_tokens, top_p, n, fp
except Exception as e:
logger.error(f"[read_return_config_section] Error reading configuration section: {str(e)}")
return None, None, None, None, None, None, None

View File

@@ -0,0 +1,157 @@
# AI Text Generation Guide for Content Creators
## What is AI Text Generation?
AI Text Generation is a powerful tool that helps content creators generate high-quality, engaging content using advanced artificial intelligence models. This tool supports multiple AI providers, each offering unique strengths for different types of content creation.
## Available AI Models
### 1. OpenAI's GPT Models
**Best for:** General content creation, creative writing, and detailed analysis
**Key Features:**
- **Advanced Understanding**: Deep comprehension of context and nuance
- **Creative Flexibility**: Adapts to various writing styles and tones
- **Consistent Quality**: Reliable output for long-form content
- **Streaming Responses**: Real-time content generation
**Use Cases:**
- Blog posts and articles
- Creative storytelling
- Technical writing
- Content analysis and summaries
### 2. Google's Gemini Pro
**Best for:** Balanced content creation and factual accuracy
**Key Features:**
- **Factual Accuracy**: Strong focus on reliable information
- **Balanced Output**: Good mix of creativity and precision
- **Multilingual Support**: Works well across different languages
- **Contextual Understanding**: Strong grasp of context
**Use Cases:**
- Educational content
- Fact-based articles
- Multilingual content
- Research-based writing
### 3. Anthropic's Claude
**Best for:** Professional and academic content
**Key Features:**
- **Professional Tone**: Excellent for formal writing
- **Detailed Analysis**: Strong analytical capabilities
- **Ethical Considerations**: Built-in ethical guidelines
- **Long-form Excellence**: Great for extended content
**Use Cases:**
- Academic writing
- Professional documentation
- Research papers
- Policy documents
### 4. DeepSeek
**Best for:** Technical and specialized content
**Key Features:**
- **Technical Precision**: Excellent for technical writing
- **Specialized Knowledge**: Strong in specific domains
- **Efficient Processing**: Fast response times
- **Customizable Output**: Flexible formatting options
**Use Cases:**
- Technical documentation
- Industry-specific content
- Scientific writing
- Specialized reports
## How to Use the Text Generation Tool
### 1. Setting Up Your Content Parameters
Before generating content, you can specify:
- **Language**: Choose your preferred writing language
- **Tone**: Select the appropriate tone (formal, casual, technical, etc.)
- **Content Length**: Set your desired word count
- **Content Type**: Specify the type of content (blog, article, etc.)
- **Target Audience**: Define your reader demographic
- **Output Format**: Choose your preferred format (Markdown, HTML, etc.)
### 2. Content Generation Process
1. **Input Your Requirements**: Provide your content specifications
2. **Select Your Model**: Choose the AI model best suited for your needs
3. **Generate Content**: Let the AI create your content
4. **Review and Edit**: Polish the generated content as needed
### 3. Customization Options
You can adjust various parameters to fine-tune your content:
- **Temperature**: Control creativity (lower = more focused, higher = more creative)
- **Maximum Length**: Set content length limits
- **Output Format**: Choose how you want the content structured
- **Language Style**: Adjust the writing style and complexity
## Best Practices for Content Creation
### 1. Before Generation
- Clearly define your content goals
- Identify your target audience
- Choose the appropriate model for your needs
- Set clear parameters for tone and style
### 2. During Generation
- Monitor the content quality
- Ensure it aligns with your brand voice
- Check for factual accuracy
- Maintain consistency with your style guide
### 3. After Generation
- Review and edit the content
- Fact-check important information
- Optimize for SEO if needed
- Add your personal touch
## Tips for Optimal Results
1. **Be Specific**: Provide clear instructions for the AI
2. **Use Examples**: Share examples of your desired style
3. **Iterate**: Don't hesitate to regenerate if needed
4. **Review**: Always review and edit generated content
5. **Optimize**: Fine-tune parameters for better results
## Common Use Cases
### Blog Writing
- Generate engaging blog posts
- Create consistent content series
- Develop topic outlines
- Write product reviews
### Article Creation
- Research-based articles
- Opinion pieces
- How-to guides
- Industry analysis
### Technical Writing
- Documentation
- User guides
- Technical specifications
- Process descriptions
### Creative Writing
- Story development
- Character creation
- Plot outlines
- Scene descriptions
## Need Help?
If you encounter any issues or need assistance:
1. Check the model-specific documentation
2. Review your input parameters
3. Try adjusting the generation settings
4. Contact support for technical issues
---
*Note: This tool is designed to assist content creators in generating high-quality content. While AI can help with content creation, it's important to review and edit the generated content to ensure it meets your standards and brand guidelines.*

View File

@@ -1,33 +1,121 @@
import os
import anthropic
from anthropic import Anthropic
import streamlit as st
import asyncio
from tenacity import (
retry,
stop_after_attempt,
wait_random_exponential,
) # for exponential backoff
# Configure standard logging
import logging
logging.basicConfig(level=logging.INFO, format='[%(asctime)s-%(levelname)s-%(module)s-%(lineno)d]- %(message)s')
logger = logging.getLogger(__name__)
def anthropic_text_response(prompt):
""" """
client = Anthropic(api_key=os.environ.get("ANTHROPIC_API_KEY"),)
async def test_anthropic_api_key(api_key: str) -> tuple[bool, str]:
"""
Test if the provided Anthropic API key is valid.
Args:
api_key (str): The Anthropic API key to test
Returns:
tuple[bool, str]: A tuple containing (is_valid, message)
"""
try:
# Create Anthropic client with the provided key
client = anthropic.Anthropic(api_key=api_key)
# Try a simple completion as a test
response = client.messages.create(
max_tokens=1024,
messages=[
{
"role": "user",
"content": prompt,
}
],
# This will come from config file.
model="claude-3-opus-20240229",
model="claude-3-haiku-20240307",
max_tokens=10,
messages=[{
"role": "user",
"content": "Say hello"
}]
)
return(message.content)
except anthropic.APIConnectionError as e:
st.error("The server could not be reached")
st.error(e.__cause__) # an underlying Exception, likely raised within httpx.
except anthropic.RateLimitError as e:
st.error("A 429 status code was received; we should back off a bit.")
except anthropic.APIStatusError as e:
st.error("Another non-200-range status code was received")
st.error(e.status_code)
st.error(e.response)
# If we get here, the key is valid
return True, "Anthropic API key is valid"
except anthropic.AuthenticationError:
return False, "Invalid Anthropic API key"
except anthropic.RateLimitError:
return False, "Rate limit exceeded. Please try again later."
except Exception as e:
return False, f"Error testing Anthropic API key: {str(e)}"
@retry(wait=wait_random_exponential(min=1, max=60), stop=stop_after_attempt(6))
def anthropic_text_response(prompt, model="claude-3-haiku-20240307", temperature=0.7, max_tokens=2048, top_p=0.9, n=1, system_prompt="You are a helpful AI assistant."):
"""
Generate text using Anthropic's Claude model with retry logic.
Args:
prompt (str): The input text to generate completion for
model (str, optional): Model to use. Defaults to "claude-3-haiku-20240307"
temperature (float, optional): Controls randomness. Defaults to 0.7
max_tokens (int, optional): Maximum number of tokens to generate. Defaults to 2048
top_p (float, optional): Controls diversity. Defaults to 0.9
n (int, optional): Number of completions to generate. Defaults to 1
system_prompt (str, optional): System prompt to guide the model. Defaults to "You are a helpful AI assistant."
Returns:
str: The generated text completion
"""
try:
# Create Anthropic client
client = anthropic.Anthropic(api_key=os.getenv('ANTHROPIC_API_KEY'))
# Generate completion
response = client.messages.create(
model=model,
max_tokens=max_tokens,
temperature=temperature,
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": prompt}
]
)
# Return the generated text
return response.content[0].text
except Exception as e:
logger.error(f"Error in Anthropic text generation: {e}")
raise SystemExit from e
def anthropic_text_gen(prompt, model="claude-3-haiku-20240307", temperature=0.7, max_tokens=2048):
"""
Generate text using Anthropic's Claude model.
Args:
prompt (str): The input text to generate completion for
model (str, optional): Model to use. Defaults to "claude-3-haiku-20240307"
temperature (float, optional): Controls randomness. Defaults to 0.7
max_tokens (int, optional): Maximum number of tokens to generate. Defaults to 2048
Returns:
str: The generated text completion
"""
try:
# Create Anthropic client
client = anthropic.Anthropic(api_key=os.getenv('ANTHROPIC_API_KEY'))
# Generate completion
response = client.messages.create(
model=model,
max_tokens=max_tokens,
temperature=temperature,
messages=[{
"role": "user",
"content": prompt
}]
)
# Return the generated text
return response.content[0].text
except Exception as e:
logger.error(f"Error in Anthropic text generation: {e}")
return str(e)

View File

@@ -6,6 +6,8 @@ from tenacity import (
stop_after_attempt,
wait_random_exponential,
)
import openai
import asyncio
# Configure standard logging
logging.basicConfig(level=logging.INFO, format='[%(asctime)s-%(levelname)s-%(module)s-%(lineno)d]- %(message)s')
@@ -67,3 +69,71 @@ def deepseek_text_response(prompt, model, temperature, max_tokens, top_p, n, sys
except Exception as err:
logger.error(f"DeepSeek error: {err}")
raise SystemExit from err
async def test_deepseek_api_key(api_key: str) -> tuple[bool, str]:
"""
Test if the provided DeepSeek API key is valid.
Args:
api_key (str): The DeepSeek API key to test
Returns:
tuple[bool, str]: A tuple containing (is_valid, message)
"""
try:
# Create OpenAI client with DeepSeek base URL
client = openai.OpenAI(
api_key=api_key,
base_url="https://api.deepseek.com/v1"
)
# Try to list models as a simple API test
models = client.models.list()
# If we get here, the key is valid
return True, "DeepSeek API key is valid"
except openai.AuthenticationError:
return False, "Invalid DeepSeek API key"
except openai.RateLimitError:
return False, "Rate limit exceeded. Please try again later."
except Exception as e:
return False, f"Error testing DeepSeek API key: {str(e)}"
def deepseek_text_gen(prompt, model="deepseek-chat", temperature=0.7, max_tokens=2048):
"""
Generate text using DeepSeek's API.
Args:
prompt (str): The input text to generate completion for
model (str, optional): Model to use. Defaults to "deepseek-chat"
temperature (float, optional): Controls randomness. Defaults to 0.7
max_tokens (int, optional): Maximum number of tokens to generate. Defaults to 2048
Returns:
str: The generated text completion
"""
try:
# Create OpenAI client with DeepSeek base URL
client = openai.OpenAI(
api_key=os.getenv('DEEPSEEK_API_KEY'),
base_url="https://api.deepseek.com/v1"
)
# Generate chat completion
response = client.chat.completions.create(
model=model,
messages=[{
"role": "user",
"content": prompt
}],
temperature=temperature,
max_tokens=max_tokens
)
# Return the generated text
return response.choices[0].message.content
except Exception as e:
logger.error(f"Error in DeepSeek text generation: {e}")
return str(e)

View File

@@ -18,6 +18,12 @@ from tenacity import (
wait_random_exponential,
)
import asyncio
# Configure standard logging
import logging
logging.basicConfig(level=logging.INFO, format='[%(asctime)s-%(levelname)s-%(module)s-%(lineno)d]- %(message)s')
logger = logging.getLogger(__name__)
@retry(wait=wait_random_exponential(min=1, max=60), stop=stop_after_attempt(6))
def gemini_text_response(prompt, temperature, top_p, n, max_tokens, system_prompt):
@@ -96,3 +102,65 @@ def gemini_text_response(prompt, temperature, top_p, n, max_tokens, system_promp
# return response.text
# except Exception as err:
# logger.error(f"Failed to get SEO METADATA from Gemini: {err}. Retrying.")
async def test_gemini_api_key(api_key: str) -> tuple[bool, str]:
"""
Test if the provided Gemini API key is valid.
Args:
api_key (str): The Gemini API key to test
Returns:
tuple[bool, str]: A tuple containing (is_valid, message)
"""
try:
# Configure Gemini with the provided key
genai.configure(api_key=api_key)
# Try to list models as a simple API test
models = genai.list_models()
# Check if Gemini Pro is available
if any(model.name == "gemini-pro" for model in models):
return True, "Gemini API key is valid"
else:
return False, "Gemini Pro model not available with this API key"
except Exception as e:
return False, f"Error testing Gemini API key: {str(e)}"
def gemini_pro_text_gen(prompt, temperature=0.7, top_p=0.9, top_k=40, max_tokens=2048):
"""
Generate text using Google's Gemini Pro model.
Args:
prompt (str): The input text to generate completion for
temperature (float, optional): Controls randomness. Defaults to 0.7
top_p (float, optional): Controls diversity. Defaults to 0.9
top_k (int, optional): Controls vocabulary size. Defaults to 40
max_tokens (int, optional): Maximum number of tokens to generate. Defaults to 2048
Returns:
str: The generated text completion
"""
try:
# Configure the model
model = genai.GenerativeModel('gemini-pro')
# Generate content
response = model.generate_content(
prompt,
generation_config=genai.types.GenerationConfig(
temperature=temperature,
top_p=top_p,
top_k=top_k,
max_output_tokens=max_tokens,
)
)
# Return the generated text
return response.text
except Exception as e:
logger.error(f"Error in Gemini Pro text generation: {e}")
return str(e)

View File

@@ -1,6 +1,6 @@
import os
import sys
import configparser
import json
from pathlib import Path
from dotenv import load_dotenv
load_dotenv(Path('../.env'))
@@ -28,10 +28,20 @@ def llm_text_gen(prompt):
str: Generated text based on the prompt.
"""
try:
# Read the config param to create system instruction for the LLM.
gpt_provider, model, temperature, max_tokens, top_p, n, fp = read_return_config_section('llm_config')
blog_tone, blog_demographic, blog_type, blog_language, \
logger.info("[llm_text_gen] Starting text generation")
logger.debug(f"[llm_text_gen] Prompt length: {len(prompt)} characters")
try:
# Read the config param to create system instruction for the LLM.
gpt_provider, model, temperature, max_tokens, top_p, n, fp = read_return_config_section('llm_config')
blog_tone, blog_demographic, blog_type, blog_language, \
blog_output_format, blog_length = read_return_config_section('blog_characteristics')
logger.debug(f"[llm_text_gen] Config loaded successfully - Provider: {gpt_provider}, Model: {model}")
except Exception as err:
logger.error(f"[llm_text_gen] Error reading config params: {err}")
raise err
# Construct the system prompt with the sidebar config params.
system_instructions = f"""You are a highly skilled content writer with a knack for creating engaging and informative content.
@@ -117,7 +127,6 @@ def check_gpt_provider(gpt_provider):
return gpt_provider
def get_api_key(gpt_provider):
"""
Get the API key for the specified GPT provider.

View File

@@ -1,13 +1,9 @@
import os
import logging
from pathlib import Path
from mistralai.client import MistralClient
from mistralai.models.chat_completion import ChatMessage
logging.basicConfig(level=logging.INFO, format='%(asctime)s-%(levelname)s-%(module)s-%(lineno)d-%(message)s')
from dotenv import load_dotenv
load_dotenv(Path('../../.env'))
from mistralai import Mistral
import asyncio
from loguru import logger
from tenacity import (
retry,
@@ -15,26 +11,134 @@ from tenacity import (
wait_random_exponential,
) # for exponential backoff
from dotenv import load_dotenv
load_dotenv(Path('../../.env'))
# Configure standard logging
logging.basicConfig(level=logging.INFO, format='[%(asctime)s-%(levelname)s-%(module)s-%(lineno)d]- %(message)s')
logger = logging.getLogger(__name__)
async def test_mistral_api_key(api_key: str) -> tuple[bool, str]:
"""
Test if the provided Mistral API key is valid.
Args:
api_key (str): The Mistral API key to test
Returns:
tuple[bool, str]: A tuple containing (is_valid, message)
"""
try:
async with Mistral(api_key=api_key) as client:
# Try a simple completion as a test
response = await client.chat.complete_async(
model="mistral-small-latest",
messages=[{
"role": "user",
"content": "Hello"
}],
max_tokens=10
)
if response and response.choices:
return True, "Mistral API key is valid"
else:
return False, "Invalid response from Mistral API"
except Exception as e:
return False, f"Error testing Mistral API key: {str(e)}"
@retry(wait=wait_random_exponential(min=1, max=60), stop=stop_after_attempt(6))
def mistral_text_response(prompt):
""" Common function to get text response from minstral. """
api_key = os.environ["MISTRAL_API_KEY"]
model = "mistral-medium"
async def mistral_chat_completion_async(
prompt: str,
model: str = "mistral-small-latest",
temperature: float = 0.7,
max_tokens: int = 2048,
top_p: float = 0.9,
system_prompt: str = "You are a helpful AI assistant."
) -> str:
"""
Generate text using Mistral's chat completion API asynchronously.
Args:
prompt (str): The input text to generate completion for
model (str, optional): Model to use. Defaults to "mistral-small-latest"
temperature (float, optional): Controls randomness. Defaults to 0.7
max_tokens (int, optional): Maximum number of tokens to generate. Defaults to 2048
top_p (float, optional): Controls diversity. Defaults to 0.9
system_prompt (str, optional): System prompt to guide the model. Defaults to "You are a helpful AI assistant."
Returns:
str: The generated text completion
"""
try:
async with Mistral(api_key=os.getenv('MISTRAL_API_KEY')) as client:
messages = []
# Add system message if provided
if system_prompt:
messages.append({
"role": "system",
"content": system_prompt
})
# Add user message
messages.append({
"role": "user",
"content": prompt
})
# Generate chat completion
response = await client.chat.complete_async(
model=model,
messages=messages,
temperature=temperature,
max_tokens=max_tokens,
top_p=top_p
)
if response and response.choices:
return response.choices[0].message.content
else:
raise Exception("No response generated")
except Exception as e:
logger.error(f"Error in Mistral chat completion: {e}")
raise SystemExit from e
client = MistralClient(api_key=api_key)
# Synchronous wrapper for compatibility
def mistral_chat_completion(
prompt: str,
model: str = "mistral-small-latest",
temperature: float = 0.7,
max_tokens: int = 2048,
top_p: float = 0.9,
system_prompt: str = "You are a helpful AI assistant."
) -> str:
"""
Synchronous wrapper for mistral_chat_completion_async.
"""
try:
return asyncio.run(mistral_chat_completion_async(
prompt=prompt,
model=model,
temperature=temperature,
max_tokens=max_tokens,
top_p=top_p,
system_prompt=system_prompt
))
except Exception as e:
logger.error(f"Error in Mistral chat completion: {e}")
return str(e)
messages = [
ChatMessage(role="user", content=prompt)
]
# No streaming
chat_response = client.chat(
# For backward compatibility
def mistral_text_response(prompt, model="mistral-small-latest", temperature=0.7, max_tokens=2048):
"""
Legacy function for backward compatibility.
"""
return mistral_chat_completion(
prompt=prompt,
model=model,
messages=messages,
temperature=temperature,
max_tokens=max_tokens
)
print(chat_response)
# With streaming
for chunk in client.chat_stream(model=model, messages=messages):
print(chunk)

View File

@@ -1,6 +1,7 @@
import os
import time #IWish
import openai
import asyncio
# Configure standard logging
import logging
@@ -13,6 +14,33 @@ from tenacity import (
) # for exponential backoff
async def test_openai_api_key(api_key: str) -> tuple[bool, str]:
"""
Test if the provided OpenAI API key is valid.
Args:
api_key (str): The OpenAI API key to test
Returns:
tuple[bool, str]: A tuple containing (is_valid, message)
"""
try:
# Create OpenAI client with the provided key
client = openai.OpenAI(api_key=api_key)
# Try to list models as a simple API test
models = client.models.list()
# If we get here, the key is valid
return True, "OpenAI API key is valid"
except openai.AuthenticationError:
return False, "Invalid OpenAI API key"
except openai.RateLimitError:
return False, "Rate limit exceeded. Please try again later."
except Exception as e:
return False, f"Error testing OpenAI API key: {str(e)}"
@retry(wait=wait_random_exponential(min=1, max=60), stop=stop_after_attempt(6))
def openai_chatgpt(prompt, model, temperature, max_tokens, top_p, n, fp, system_prompt):
"""

View File

@@ -0,0 +1,88 @@
# Content Style Analyzer Guide
## What is the Content Style Analyzer?
The Content Style Analyzer is an AI-powered tool that helps you understand and improve your writing style. It analyzes your content to provide detailed insights about your writing approach, helping you create more consistent and engaging content.
## What Can It Do?
### 1. Writing Style Analysis
The analyzer examines your content to identify:
- **Tone**: Whether your writing is formal, casual, technical, or conversational
- **Voice**: If you're using active or passive voice
- **Complexity**: How complex your writing is (simple, moderate, or complex)
- **Engagement Level**: How engaging your content is (low, medium, or high)
### 2. Content Characteristics
It provides insights about:
- **Sentence Structure**: How your sentences are organized
- **Vocabulary Level**: Whether you're using basic, intermediate, or advanced vocabulary
- **Paragraph Organization**: How your paragraphs flow together
- **Content Flow**: How well your ideas progress throughout the content
### 3. Target Audience Analysis
The tool helps you understand:
- **Demographics**: Who your content appeals to
- **Expertise Level**: Whether it's suitable for beginners, intermediate, or advanced readers
- **Industry Focus**: Which industry your content is targeting
- **Geographic Focus**: Which regions your content is most relevant for
### 4. Content Type Assessment
It identifies:
- **Primary Type**: Whether it's a blog post, article, product description, etc.
- **Secondary Types**: Other content categories it might fit into
- **Purpose**: Whether it's meant to inform, entertain, persuade, etc.
- **Call to Action**: How effectively you're guiding readers to take action
### 5. Style Pattern Analysis
The analyzer also looks for specific patterns in your writing:
- **Sentence Patterns**: How your sentences are structured
- **Word Patterns**: Your vocabulary choices and frequency
- **Rhetorical Devices**: Literary techniques you're using
## How to Use It
1. **Input Your Content**: Provide your content, including:
- Main content text
- Title
- Description
2. **Get Analysis**: The tool will analyze your content and provide detailed insights
3. **Review Recommendations**: Receive suggestions for:
- Writing tone
- Target audience
- Content type
- Creativity level
- Geographic focus
## Benefits for Content Creators
1. **Consistency**: Maintain a consistent writing style across your content
2. **Audience Alignment**: Ensure your content matches your target audience's expectations
3. **Quality Improvement**: Identify areas where your writing can be enhanced
4. **Style Optimization**: Get recommendations for better engagement
5. **Content Strategy**: Make data-driven decisions about your content approach
## Tips for Best Results
1. **Provide Complete Content**: Include all relevant sections (title, description, main content)
2. **Keep Content Length Reasonable**: The analyzer works best with content up to 4000 characters
3. **Review All Sections**: Pay attention to all aspects of the analysis for comprehensive insights
4. **Use Recommendations**: Apply the suggested improvements to enhance your content
## Understanding the Results
The analysis results are presented in a clear, structured format that helps you:
- Identify your current writing style
- Understand your content's strengths
- Spot areas for improvement
- Make informed decisions about future content
## Need Help?
If you encounter any issues or have questions about the analysis results, please refer to your content team or technical support for assistance.
---
*Note: This tool is designed to help content creators improve their writing style and content quality. It uses advanced AI technology to provide detailed insights and recommendations.*

View File

@@ -0,0 +1,203 @@
"""Style analyzer module for analyzing content style using LLM."""
from typing import Dict, List, Optional
from loguru import logger
from ..gpt_providers.text_generation.main_text_generation import llm_text_gen
import json
import re
class StyleAnalyzer:
"""Analyzer for content style using LLM."""
def __init__(self):
"""Initialize the style analyzer."""
logger.info("[StyleAnalyzer.__init__] Initializing style analyzer")
def _clean_json_response(self, text: str) -> str:
"""
Clean the LLM response to extract valid JSON.
Args:
text (str): Raw response from LLM
Returns:
str: Cleaned JSON string
"""
try:
# Remove markdown code block markers
cleaned_string = text.replace("```json", "").replace("```", "").strip()
# Log the cleaned JSON for debugging
logger.debug(f"[StyleAnalyzer._clean_json_response] Cleaned JSON: {cleaned_string}")
return cleaned_string
except Exception as e:
logger.error(f"[StyleAnalyzer._clean_json_response] Error cleaning response: {str(e)}")
return ""
def analyze_content_style(self, content: Dict) -> Dict:
"""
Analyze the style of the provided content.
Args:
content (Dict): Content to analyze, containing main_content, title, etc.
Returns:
Dict: Analysis results
"""
try:
logger.info("[StyleAnalyzer.analyze_content_style] Starting content style analysis")
# Prepare content for analysis
main_content = content.get("main_content", "")
title = content.get("title", "")
description = content.get("description", "")
# Construct the analysis prompt
prompt = f"""Analyze the following content and provide a comprehensive writing style analysis.
Focus on identifying the writing style, tone, and characteristics that make this content unique.
Title: {title}
Description: {description}
Content: {main_content[:4000]} # Limit content length for API
IMPORTANT: Respond ONLY with a JSON object in the following format. Do not include any additional text, explanations, or markdown formatting:
{{
"writing_style": {{
"tone": "formal/casual/technical/etc",
"voice": "active/passive",
"complexity": "simple/moderate/complex",
"engagement_level": "low/medium/high"
}},
"content_characteristics": {{
"sentence_structure": "description",
"vocabulary_level": "basic/intermediate/advanced",
"paragraph_organization": "description",
"content_flow": "description"
}},
"target_audience": {{
"demographics": ["list"],
"expertise_level": "beginner/intermediate/advanced",
"industry_focus": "primary industry",
"geographic_focus": "primary region"
}},
"content_type": {{
"primary_type": "blog/article/product/etc",
"secondary_types": ["list"],
"purpose": "inform/entertain/persuade/etc",
"call_to_action": "type and frequency"
}},
"recommended_settings": {{
"writing_tone": "recommended tone",
"target_audience": "recommended audience",
"content_type": "recommended type",
"creativity_level": "low/medium/high",
"geographic_location": "recommended location"
}}
}}"""
# Get analysis from LLM
logger.debug("[StyleAnalyzer.analyze_content_style] Sending prompt to LLM")
analysis_text = llm_text_gen(prompt)
try:
# Clean and parse the JSON response
cleaned_json = self._clean_json_response(analysis_text)
if not cleaned_json:
raise ValueError("No valid JSON found in response")
# Log the cleaned JSON for debugging
logger.debug(f"[StyleAnalyzer.analyze_content_style] Cleaned JSON: {cleaned_json}")
# Try to parse the cleaned JSON
try:
analysis = json.loads(cleaned_json)
except json.JSONDecodeError as e:
# If parsing fails, try to fix common JSON issues
logger.warning(f"[StyleAnalyzer.analyze_content_style] Initial JSON parsing failed: {e}")
# Fix any remaining issues
cleaned_json = re.sub(r'([^"\\])\n', r'\1 ', cleaned_json)
cleaned_json = re.sub(r'\\n', ' ', cleaned_json)
# Try parsing again
analysis = json.loads(cleaned_json)
logger.info("[StyleAnalyzer.analyze_content_style] Successfully parsed analysis results")
return analysis
except json.JSONDecodeError as e:
logger.error(f"[StyleAnalyzer.analyze_content_style] Failed to parse JSON response: {e}")
logger.debug(f"[StyleAnalyzer.analyze_content_style] Raw response: {analysis_text}")
return {
"error": "Failed to parse analysis results",
"raw_response": analysis_text
}
except Exception as e:
logger.error(f"[StyleAnalyzer.analyze_content_style] Error during analysis: {str(e)}")
return {
"error": str(e),
"success": False
}
def analyze_style_patterns(self, content: Dict) -> Dict:
"""
Analyze specific writing style patterns in the content.
Args:
content (Dict): Content to analyze
Returns:
Dict: Pattern analysis results
"""
try:
main_content = content.get("main_content", "")
prompt = f"""Analyze the following content for specific writing style patterns.
Focus on identifying recurring patterns in sentence structure, word choice, and rhetorical devices.
Content: {main_content[:4000]}
IMPORTANT: Respond ONLY with a JSON object in the following format. Do not include any additional text, explanations, or markdown formatting:
{{
"sentence_patterns": {{
"structure": ["list of patterns"],
"length": "short/medium/long",
"complexity": "simple/moderate/complex"
}},
"word_patterns": {{
"vocabulary": ["list of patterns"],
"frequency": "low/medium/high",
"diversity": "low/medium/high"
}},
"rhetorical_devices": {{
"types": ["list of devices"],
"frequency": "low/medium/high",
"effectiveness": "low/medium/high"
}}
}}"""
analysis_text = llm_text_gen(prompt)
try:
cleaned_json = self._clean_json_response(analysis_text)
if not cleaned_json:
raise ValueError("No valid JSON found in response")
analysis = json.loads(cleaned_json)
return analysis
except json.JSONDecodeError as e:
logger.error(f"[StyleAnalyzer.analyze_style_patterns] Failed to parse JSON response: {e}")
return {
"error": "Failed to parse pattern analysis results",
"raw_response": analysis_text
}
except Exception as e:
logger.error(f"[StyleAnalyzer.analyze_style_patterns] Error during analysis: {str(e)}")
return {
"error": str(e),
"success": False
}

99
lib/utils/ai_research.py Normal file
View File

@@ -0,0 +1,99 @@
"""AI research module for topic analysis and research."""
import asyncio
from typing import Dict, Any
from loguru import logger
import sys
from ..web_crawlers.async_web_crawler import AsyncWebCrawlerService
from ..gpt_providers.text_generation.main_text_generation import llm_text_gen
# Configure logger
logger.remove()
logger.add(
"logs/ai_research.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 research_topic(topic: str) -> Dict[str, Any]:
"""
Research a topic using web crawling and AI analysis.
Args:
topic (str): The topic to research
Returns:
Dict[str, Any]: Research results including overview, findings, and recommendations
"""
try:
logger.info(f"[research_topic] Starting research for topic: {topic}")
# Initialize web crawler
async def analyze_topic():
async with AsyncWebCrawlerService() as crawler:
# Perform web research
search_results = await crawler.crawl_website(topic)
if not search_results.get('success'):
return {
'success': False,
'error': search_results.get('error', 'Research failed')
}
# Analyze content with LLM
analysis = await crawler.analyze_content_with_llm(
search_results['content'],
api_key=None, # Should be passed from config
gpt_provider="google" # Should be configurable
)
# Structure the response
return {
'success': True,
'data': {
'research': {
'overview': {
'topic': topic,
'scope': analysis.get('topics', []),
'methodology': 'Web crawling and AI analysis'
},
'data_quality': {
'is_reliable': bool(analysis.get('seo_score', 0) > 0.7)
},
'analysis_quality': {
'is_thorough': bool(len(analysis.get('key_insights', [])) > 5)
},
'recommendations': analysis.get('recommendations', []),
'next_steps': analysis.get('priority_areas', [])
}
}
}
# Run the async analysis
results = asyncio.run(analyze_topic())
if not results.get('success'):
error_msg = results.get('error', 'Research failed')
logger.error(f"[research_topic] Research failed: {error_msg}")
return {
'success': False,
'error': error_msg
}
logger.info("[research_topic] Research completed successfully")
return results
except Exception as e:
error_msg = f"Research failed: {str(e)}"
logger.error(f"[research_topic] {error_msg}")
return {
'success': False,
'error': str(e)
}

View File

@@ -0,0 +1,244 @@
import streamlit as st
import logging
from .config_manager import save_config
# Configure logging
logging.basicConfig(
level=logging.DEBUG,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.StreamHandler(), # Output to console
#logging.FileHandler('alwrity.log') # Output to file
]
)
logger = logging.getLogger(__name__)
# Sidebar configuration
def sidebar_configuration():
"""Configure the sidebar with all necessary options."""
try:
# Configure sidebar styling
st.sidebar.markdown("""
<style>
[data-testid="stSidebar"] {
min-width: 250px !important;
max-width: 250px !important;
visibility: visible !important;
position: relative !important;
transform: translateX(0) !important;
}
[data-testid="stSidebar"][aria-expanded="true"] {
min-width: 250px !important;
max-width: 250px !important;
transform: translateX(0) !important;
}
[data-testid="stSidebar"][aria-expanded="false"] {
min-width: 250px !important;
max-width: 250px !important;
transform: translateX(0) !important;
}
.stSidebar .element-container {
padding: 0.5rem;
}
.stSidebar .stMarkdown {
padding: 0.5rem;
}
.stSidebar .stSelectbox {
padding: 0.5rem;
}
.stSidebar .stTextInput {
padding: 0.5rem;
}
.stSidebar .stNumberInput {
padding: 0.5rem;
}
.stSidebar .stSlider {
padding: 0.5rem;
}
/* Ensure sidebar is visible */
section[data-testid="stSidebar"] {
visibility: visible !important;
transform: translateX(0) !important;
}
</style>
""", unsafe_allow_html=True)
logger.info("Initializing sidebar configuration")
st.sidebar.title("🛠️ Personalization & Settings 🏗️")
with st.sidebar.expander("**👷 Content Personalization**"):
logger.debug("Setting up content personalization options")
blog_length = st.text_input("**Content Length (words)**", value="2000",
help="Approximate word count for blogs. Note: Actual length may vary based on GPT provider and max token count.")
blog_tone_options = ["Casual", "Professional", "How-to", "Beginner", "Research", "Programming", "Social Media", "Customize"]
blog_tone = st.selectbox("**Content Tone**",
options=blog_tone_options,
help="Select the desired tone for the blog content.")
logger.debug(f"Selected blog tone: {blog_tone}")
if blog_tone == "Customize":
custom_tone = st.text_input("Enter the tone of your content", help="Specify the tone of your content.")
if custom_tone:
blog_tone = custom_tone
logger.debug(f"Custom tone set to: {custom_tone}")
else:
logger.warning("Custom tone not specified")
st.warning("Please specify the tone of your content.")
blog_demographic_options = ["Professional", "Gen-Z", "Tech-savvy", "Student", "Digital Marketing", "Customize"]
blog_demographic = st.selectbox("**Target Audience**",
options=blog_demographic_options,
help="Select the primary audience for the blog content.")
if blog_demographic == "Customize":
custom_demographic = st.text_input("Enter your target audience",
help="Specify your target audience.",
placeholder="Eg. Domain expert, Content creator, Financial expert etc..")
if custom_demographic:
blog_demographic = custom_demographic
else:
st.warning("Please specify your target audience.")
blog_type = st.selectbox("**Content Type**",
options=["Informational", "Commercial", "Company", "News", "Finance", "Competitor", "Programming", "Scholar"],
help="Select the category that best describes the blog content.")
blog_language = st.selectbox("**Content Language**",
options=["English", "Spanish", "German", "Chinese", "Arabic", "Nepali", "Hindi", "Hindustani", "Customize"],
help="Select the language in which the blog will be written.")
if blog_language == "Customize":
custom_lang = st.text_input("Enter the language of your choice", help="Specify the content language.")
if custom_lang:
blog_language = custom_lang
else:
st.warning("Please specify the language of your content.")
blog_output_format = st.selectbox("**Content Output Format**",
options=["markdown", "HTML", "plaintext"],
help="Select the format for the blog output.")
with st.sidebar.expander("**🩻 Images Personalization**"):
image_generation_model = st.selectbox("**Image Generation Model**",
options=["stable-diffusion", "dalle2", "dalle3"],
help="Select the model to generate images for the blog.")
number_of_blog_images = st.number_input("**Number of Blog Images**", value=1, help="Specify the number of images to include in the blog.")
with st.sidebar.expander("**🤖 LLM Personalization**"):
gpt_provider = st.selectbox("**GPT Provider**",
options=["google", "openai", "minstral"],
help="Select the provider for the GPT model.")
model = st.text_input("**Model**", value="gemini-1.5-flash-latest", help="Specify the model version to use from the selected provider.")
temperature = st.slider(
"Temperature",
min_value=0.1,
max_value=1.0,
value=0.7,
step=0.1,
format="%.1f",
help="""Temperature controls the 'creativity' or randomness of the text generated by GPT.
Greater determinism with higher values indicating more randomness."""
)
top_p = st.slider(
"Top-p",
min_value=0.0,
max_value=1.0,
value=0.9,
step=0.1,
format="%.1f",
help="Top-p sampling controls the level of diversity in the generated text."
)
# Selectbox for max tokens
max_tokens_options = [500, 1000, 2000, 4000, 16000, 32000, 64000]
max_tokens = st.selectbox(
"Max Tokens",
options=max_tokens_options,
index=max_tokens_options.index(4000),
help="Max tokens determine the maximum length of the output sequence generated by a model."
)
n = st.number_input("N",
value=1,
min_value=1,
max_value=10,
help="Defines the number of words or characters grouped together in a sequence when analyzing text.")
frequency_penalty = st.slider(
"Frequency Penalty",
min_value=0.0,
max_value=2.0,
value=1.0,
step=0.1,
format="%.1f",
help="Influences word selection during text generation, promoting diversity with higher values."
)
presence_penalty = st.slider(
"Presence Penalty",
min_value=0.0,
max_value=2.0,
value=1.0,
step=0.1,
format="%.1f",
help="Encourages the use of diverse words by discouraging repetition."
)
with st.sidebar.expander("**🕵️ Search Engine Personalization**"):
geographic_location = st.selectbox("**Geographic Location**",
options=["us", "in", "fr", "cn"],
help="Select the geographic location for tailoring search results.")
search_language = st.selectbox("**Search Language**",
options=["en", "zn-cn", "de", "hi"],
help="Select the language for the search results.")
number_of_results = st.number_input("**Number of Results**",
value=10,
max_value=20,
min_value=1,
help="Specify the number of search results to retrieve.")
time_range = st.selectbox("**Time Range**",
options=["anytime", "past day", "past week", "past month", "past year"],
help="Select the time range for filtering search results.")
include_domains = st.text_input("**Include Domains**", value="",
help="List specific domains to include in search results. Leave blank to include all domains.")
similar_url = st.text_input("**Similar URL**", value="", help="Provide a URL to find similar results. Leave blank if not needed.")
# Storing collected inputs in a dictionary
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_output_format
},
"Blog Images Details": {
"Image Generation Model": image_generation_model,
"Number of Blog Images": number_of_blog_images
},
"LLM Options": {
"GPT Provider": gpt_provider,
"Model": model,
"Temperature": temperature,
"Top-p": top_p,
"Max Tokens": max_tokens,
"N": n,
"Frequency Penalty": frequency_penalty,
"Presence Penalty": presence_penalty
},
"Search Engine Parameters": {
"Geographic Location": geographic_location,
"Search Language": search_language,
"Number of Results": number_of_results,
"Time Range": time_range,
"Include Domains": include_domains,
"Similar URL": similar_url
}
}
# Writing the configuration to a file whenever a change is made
save_config(config)
except Exception as e:
logger.error(f"Error configuring sidebar: {str(e)}")
st.error(f"Error configuring sidebar: {str(e)}")

View File

@@ -8,7 +8,7 @@ from lib.ai_writers.keywords_to_blog_streamlit import write_blog_from_keywords
from lib.ai_writers.speech_to_blog.main_audio_to_blog import generate_audio_blog
from lib.ai_writers.long_form_ai_writer import long_form_generator
from lib.ai_writers.ai_news_article_writer import ai_news_generation
from lib.ai_writers.ai_agents_crew_writer import ai_agents_writers
#from lib.ai_writers.ai_agents_crew_writer import ai_agents_writers
from lib.ai_writers.ai_financial_writer import write_basic_ta_report
from lib.ai_writers.facebook_ai_writer import facebook_post_writer
from lib.ai_writers.linkedin_ai_writer import linked_post_writer
@@ -24,8 +24,8 @@ import tiktoken
import openai
from lib.gpt_providers.text_to_image_generation.main_generate_image_from_prompt import generate_image
from lib.utils.voice_processing import record_voice
from lib.content_planning_calender.content_planning_agents_alwrity_crew import ai_agents_content_planner
from ..gpt_providers.text_generation.main_text_generation import llm_text_gen
#from lib.content_planning_calender.content_planning_agents_alwrity_crew import ai_agents_content_planner
from lib.gpt_providers.text_generation.main_text_generation import llm_text_gen
def is_youtube_link(text):
@@ -292,9 +292,9 @@ def ai_agents_team():
if plan_keywords and len(plan_keywords.split()) >= 2:
with st.spinner("Get Content Plan..."):
try:
plan_content = ai_agents_content_planner(plan_keywords)
st.success(f"Successfully generated content plan for: {plan_keywords}")
st.markdown(plan_content)
#plan_content = ai_agents_content_planner(plan_keywords)
st.success(f"Coming soon: Content plan for: {plan_keywords}")
#st.markdown(plan_content)
except Exception as err:
st.error(f"Failed to generate content plan: {err}")
else:

View File

@@ -1,69 +0,0 @@
import os
import streamlit as st
from dotenv import load_dotenv
def check_all_api_keys():
"""
Checks if all required API keys are present in the environment variables.
Prompts the user to enter missing keys and saves them in the .env file.
This includes general API keys and the LLM provider key.
"""
# Load environment variables from .env (MUST COME FIRST)
load_dotenv()
api_keys = {
"METAPHOR_API_KEY": "https://dashboard.exa.ai/login",
"TAVILY_API_KEY": "https://tavily.com/#api",
"SERPER_API_KEY": "https://serper.dev/signup",
"STABILITY_API_KEY": "https://platform.stability.ai/",
"FIRECRAWL_API_KEY": "https://www.firecrawl.dev/account"
}
# Check for missing keys AFTER loading environment variables
missing_keys = {
key: url for key, url in api_keys.items() if os.getenv(key) is None
}
gpt_provider = os.getenv("GPT_PROVIDER")
supported_providers = {
'google': "GEMINI_API_KEY",
'openai': "OPENAI_API_KEY",
'mistral': "MISTRAL_API_KEY"
}
if not gpt_provider or gpt_provider.lower() not in supported_providers:
gpt_provider = st.selectbox(
"Select your LLM Provider", options=list(supported_providers.keys())
)
os.environ["GPT_PROVIDER"] = gpt_provider
try:
with open(".env", "a") as env_file:
env_file.write(f"GPT_PROVIDER={gpt_provider}\n")
except IOError as e:
st.error(f"Failed to write GPT_PROVIDER to .env file: {e}")
st.success(f"GPT Provider set to {gpt_provider}")
api_key_var = supported_providers[gpt_provider.lower()]
if not os.getenv(api_key_var):
missing_keys[api_key_var] = ''
# If there are missing keys, prompt the user to enter them
if missing_keys:
st.warning(f"API keys not found: {', '.join(missing_keys)}. Please provide them below. Restart the app after saving the keys.")
with st.form(key='api_keys_form'):
# Gather all missing keys in one go
for key, url in missing_keys.items():
if url:
st.text_input(f"{key}: 👉[Get it here]({url})👈", type="password", key=key)
else:
st.text_input(f"{key}:", type="password", key=key)
# Save all keys at once when the button is clicked
if st.form_submit_button("Save Keys"):
with open(".env", "a") as env_file:
for key in missing_keys:
key_value = st.session_state[key]
env_file.write(f"{key}={key_value}\n")
st.success("API keys saved successfully! Please restart the application.")
st.stop()
return False
return True

View File

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

View File

@@ -0,0 +1,37 @@
"""API key manager package."""
from .manager import APIKeyManager
from .api_key_manager import (
initialize_wizard_state,
update_progress,
check_all_api_keys,
render,
render_navigation
)
from .components import (
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
)
__all__ = [
'APIKeyManager',
'initialize_wizard_state',
'update_progress',
'check_all_api_keys',
'render',
'render_navigation',
'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'
]

View File

@@ -0,0 +1,42 @@
"""AI research functionality for API key manager."""
from loguru import logger
import asyncio
from typing import Dict, Any, Optional
async def research_topic(topic: str, api_keys: Dict[str, str]) -> Dict[str, Any]:
"""
Research a topic using available AI services.
Args:
topic (str): The topic to research
api_keys (Dict[str, str]): Dictionary of API keys for different services
Returns:
Dict[str, Any]: Research results and metadata
"""
try:
logger.info(f"Starting research on topic: {topic}")
# TODO: Implement actual research functionality using available API keys
# This is a placeholder implementation
results = {
"topic": topic,
"status": "success",
"data": {
"summary": f"Research summary for {topic}",
"key_points": ["Point 1", "Point 2", "Point 3"],
"sources": ["Source 1", "Source 2"]
}
}
logger.info("Research completed successfully")
return results
except Exception as e:
logger.error(f"Error during research: {str(e)}")
return {
"topic": topic,
"status": "error",
"error": str(e)
}

View File

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

View File

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

View File

@@ -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.*

View 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'
]

View 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}

View 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}

View 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}

View 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}

View 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}

View 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)

View 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}

View 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")

View 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}

View 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}

View 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}

View File

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

View File

@@ -0,0 +1,149 @@
"""API key manager class."""
from typing import Dict, Any, Optional
from loguru import logger
import streamlit as st
import os
import json
import sys
from datetime import datetime
from dotenv import load_dotenv
# Configure logger to output to both file and stdout
logger.remove() # Remove default handler
logger.add("logs/api_key_manager.log",
format="<green>{time:YYYY-MM-DD HH:mm:ss}</green> | <level>{level: <8}</level> | <cyan>{name}</cyan>:<cyan>{function}</cyan>:<cyan>{line}</cyan> - <level>{message}</level>",
level="DEBUG")
logger.add(sys.stdout,
format="<green>{time:YYYY-MM-DD HH:mm:ss}</green> | <level>{level: <8}</level> | <cyan>{name}</cyan>:<cyan>{function}</cyan>:<cyan>{line}</cyan> - <level>{message}</level>",
level="INFO")
class APIKeyManager:
"""Manager for handling API keys."""
def __init__(self):
"""Initialize the API key manager."""
logger.info("[APIKeyManager.__init__] Initializing API key manager")
self.api_keys = {}
self.load_api_keys()
self.api_key_groups = {
"Create": {
"GEMINI_API_KEY": {
"url": "https://makersuite.google.com/app/apikey",
"description": "Google's Gemini AI for content generation",
"setup_steps": [
"Visit Google AI Studio",
"Create a Google Cloud account",
"Enable Gemini API",
"Generate API key"
]
},
"OPENAI_API_KEY": {
"url": "https://platform.openai.com/api-keys",
"description": "OpenAI's GPT models for content creation",
"setup_steps": [
"Go to OpenAI platform",
"Create an account",
"Navigate to API keys",
"Create new API key"
]
},
"MISTRAL_API_KEY": {
"url": "https://console.mistral.ai/api-keys/",
"description": "Mistral AI for efficient content generation",
"setup_steps": [
"Visit Mistral AI website",
"Sign up for an account",
"Access API section",
"Generate API key"
]
}
},
"Research": {
"TAVILY_API_KEY": {
"url": "https://tavily.com/#api",
"description": "Powers intelligent web research features",
"setup_steps": [
"Go to Tavily's website",
"Create an account",
"Access your API dashboard",
"Generate a new API key"
]
},
"SERPER_API_KEY": {
"url": "https://serper.dev/signup",
"description": "Enables Google search functionality",
"setup_steps": [
"Visit Serper.dev",
"Sign up for an account",
"Go to API section",
"Create your API key"
]
}
},
"Deep Search": {
"METAPHOR_API_KEY": {
"url": "https://dashboard.exa.ai/login",
"description": "Enables advanced web search capabilities",
"setup_steps": [
"Visit the Exa AI dashboard",
"Sign up for a free account",
"Navigate to API Keys section",
"Create a new API key"
]
},
"FIRECRAWL_API_KEY": {
"url": "https://www.firecrawl.dev/account",
"description": "Enables web content extraction",
"setup_steps": [
"Visit Firecrawl website",
"Sign up for an account",
"Access API dashboard",
"Create your API key"
]
}
},
"Integrations": {
"STABILITY_API_KEY": {
"url": "https://platform.stability.ai/",
"description": "Enables AI image generation",
"setup_steps": [
"Access Stability AI platform",
"Create an account",
"Navigate to API settings",
"Generate your API key"
]
}
}
}
def load_api_keys(self):
"""Load API keys from environment variables."""
logger.info("[APIKeyManager.load_api_keys] Loading API keys from environment")
try:
# Load from environment variables
self.api_keys = {
"openai": os.getenv("OPENAI_API_KEY", ""),
"google": os.getenv("GOOGLE_API_KEY", ""),
"tavily": os.getenv("TAVILY_API_KEY", ""),
"metaphor": os.getenv("METAPHOR_API_KEY", ""),
"mistral": os.getenv("MISTRAL_API_KEY", "")
}
logger.info("[APIKeyManager.load_api_keys] Successfully loaded API keys")
except Exception as e:
logger.error(f"[APIKeyManager.load_api_keys] Error loading API keys: {str(e)}")
def save_api_key(self, provider: str, key: str):
"""Save an API key."""
logger.info(f"[APIKeyManager.save_api_key] Saving API key for provider: {provider}")
try:
self.api_keys[provider] = key
# Save to environment variable
os.environ[f"{provider.upper()}_API_KEY"] = key
logger.info(f"[APIKeyManager.save_api_key] Successfully saved API key for {provider}")
except Exception as e:
logger.error(f"[APIKeyManager.save_api_key] Error saving API key: {str(e)}")
def get_api_key(self, provider: str) -> Optional[str]:
"""Get an API key."""
return self.api_keys.get(provider)

View File

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

View File

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

View File

@@ -0,0 +1,95 @@
"""API key validation module."""
from typing import Dict, Any, List, Tuple
from loguru import logger
import os
from dotenv import load_dotenv
from .manager import APIKeyManager
def check_all_api_keys(api_key_manager: APIKeyManager) -> bool:
"""Check if minimum required API keys are present.
Args:
api_key_manager (APIKeyManager): The API key manager instance
Returns:
bool: True if minimum required keys are present (at least one AI provider and one research provider)
"""
try:
# Load environment variables
logger.info("Starting API key validation process...")
# Get the current working directory and .env file path
current_dir = os.getcwd()
env_path = os.path.join(current_dir, '.env')
logger.info(f"Looking for .env file at: {env_path}")
# Check if .env file exists
if not os.path.exists(env_path):
logger.error(f".env file not found at {env_path}")
return False
# Load environment variables
load_dotenv(env_path)
logger.debug("Environment variables loaded")
# Log all environment variables (without their values)
logger.debug("Available environment variables:")
for key in os.environ.keys():
if any(provider in key for provider in ['API_KEY', 'SERPAPI', 'TAVILY', 'METAPHOR', 'FIRECRAWL']):
logger.debug(f"Found environment variable: {key}")
# Step 1: Check for at least one AI provider
logger.info("Checking AI provider API keys...")
ai_providers = [
'OPENAI_API_KEY',
'GEMINI_API_KEY',
'ANTHROPIC_API_KEY',
'MISTRAL_API_KEY'
]
# Log which AI providers are found
for provider in ai_providers:
value = os.getenv(provider)
if value:
logger.info(f"Found {provider} (length: {len(value)})")
else:
logger.debug(f"Missing {provider}")
has_ai_provider = any(os.getenv(key) for key in ai_providers)
if not has_ai_provider:
logger.warning("No AI provider API key found")
return False
else:
logger.success("✓ At least one AI provider key found")
# Step 2: Check for at least one research provider
logger.info("Checking research provider API keys...")
research_providers = [
'SERPAPI_KEY',
'TAVILY_API_KEY',
'METAPHOR_API_KEY',
'FIRECRAWL_API_KEY'
]
# Log which research providers are found
for provider in research_providers:
value = os.getenv(provider)
if value:
logger.info(f"Found {provider} (length: {len(value)})")
else:
logger.debug(f"Missing {provider}")
has_research_provider = any(os.getenv(key) for key in research_providers)
if not has_research_provider:
logger.warning("No research provider API key found")
return False
else:
logger.success("✓ At least one research provider key found")
logger.success("All required API keys validated successfully!")
return True
except Exception as e:
logger.error(f"Error checking API keys: {str(e)}", exc_info=True)
return False

View File

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

View File

@@ -5,7 +5,7 @@ from lib.utils.alwrity_utils import (
)
from lib.ai_writers.ai_story_writer.story_writer import story_input_section
from lib.ai_writers.ai_product_description_writer import write_ai_prod_desc
from lib.content_planning_calender.content_planning_agents_alwrity_crew import ai_agents_content_planner
#from lib.content_planning_calender.content_planning_agents_alwrity_crew import ai_agents_content_planner
from lib.utils.seo_tools import ai_seo_tools
@@ -62,6 +62,7 @@ def content_planning_tools():
)
if st.button("**Ideate Content Calender**"):
if plan_keywords:
ai_agents_content_planner(plan_keywords)
#ai_agents_content_planner(plan_keywords)
st.header("COming Soon.")
else:
st.error("Come on, really, Enter some keywords to plan on..")

View File

@@ -3,6 +3,8 @@ import sys
import json
from pathlib import Path
from loguru import logger
import yaml
logger.remove()
logger.add(sys.stdout,
colorize=True,
@@ -30,7 +32,6 @@ def read_return_config_section(config_section):
with open(config_path, 'r', encoding="utf-8") as file:
config = json.load(file)
if config_section == 'system_prompt':
prompt_file_path = os.path.join(os.getcwd(), 'lib', 'workspace', 'alwrity_prompts', 'alwrity_system_instruction.prompts')
with open(prompt_file_path, 'r') as file:
@@ -81,3 +82,30 @@ def read_return_config_section(config_section):
except Exception as err:
logger.error(f"An unexpected error occurred: {err}")
raise
def get_personalization_settings():
"""Get personalization settings from ALWRITY_CONFIG."""
try:
config_path = Path(os.environ["ALWRITY_CONFIG"])
config = yaml.safe_load(config_path.read_text())
return config.get('personalization', {})
except Exception as e:
logger.error(f"Error reading personalization settings: {str(e)}")
return {}
def save_personalization_settings(settings):
"""Save personalization settings to ALWRITY_CONFIG."""
try:
config_path = Path(os.environ["ALWRITY_CONFIG"])
config = yaml.safe_load(config_path.read_text())
# Update personalization section
config['personalization'] = settings
# Save back to file
config_path.write_text(yaml.dump(config, default_flow_style=False))
logger.info("Personalization settings saved successfully")
except Exception as e:
logger.error(f"Error saving personalization settings: {str(e)}")
raise

View File

@@ -1,21 +1,74 @@
import os
import streamlit as st
from .file_processor import load_image
from .content_generators import content_planning_tools, ai_writers
from .alwrity_utils import ai_agents_team, ai_social_writer
from .seo_tools import ai_seo_tools
from lib.utils.file_processor import load_image
from lib.utils.content_generators import content_planning_tools, ai_writers
from lib.utils.alwrity_utils import ai_social_writer
from lib.utils.seo_tools import ai_seo_tools
def setup_ui():
"""Sets up the Streamlit UI with custom CSS and logo."""
try:
css_file_path = os.path.join('lib', 'workspace', 'alwrity_ui_styling.css')
with open(css_file_path) as f:
custom_css = f.read()
st.set_page_config(page_title="Alwrity", layout="wide")
st.markdown(f'<style>{custom_css}</style>', unsafe_allow_html=True)
except Exception as err:
st.error(f"Failed in setting up Alwrity Streamlit UI: {err}")
"""Set up the UI with custom styling."""
# Add custom CSS
st.markdown("""
<style>
/* Main app styling */
.stApp {
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
}
/* Header styling */
h1, h2, h3 {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
font-weight: 600;
}
/* Button styling */
.stButton > button {
border-radius: 8px;
font-weight: 500;
transition: all 0.3s ease;
}
.stButton > button:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}
/* Input field styling */
.stTextInput > div > div > input {
border-radius: 8px;
border: 1px solid rgba(0,0,0,0.1);
padding: 0.5rem 1rem;
}
/* Checkbox styling */
.stCheckbox > label {
font-weight: 500;
}
/* Expander styling */
.streamlit-expanderHeader {
font-weight: 500;
color: #2c3e50;
}
/* Success message styling */
.stSuccess {
background: linear-gradient(135deg, #43c6ac 0%, #191654 100%);
padding: 1rem;
border-radius: 8px;
color: white;
}
/* Error message styling */
.stError {
background: linear-gradient(135deg, #ff6b6b 0%, #ff8e8e 100%);
padding: 1rem;
border-radius: 8px;
color: white;
}
</style>
""", unsafe_allow_html=True)
image_base64 = load_image("lib/workspace/alwrity_logo.png")
st.markdown(f"""
@@ -37,8 +90,9 @@ def setup_tabs():
ai_writers()
with tab3:
ai_agents_team()
#ai_agents_team()
st.subheader("Agents Teams")
with tab4:
ai_seo_tools()

View File

@@ -0,0 +1,181 @@
# Website Analyzer Module
A comprehensive website analysis toolkit that provides detailed insights into website performance, SEO metrics, and content quality. This module combines traditional web analysis techniques with AI-powered content evaluation to deliver actionable recommendations.
## Features
### 1. Comprehensive Website Analysis
- Basic website information extraction
- SSL/TLS certificate validation
- DNS record analysis
- WHOIS information retrieval
- Content analysis and structure evaluation
- Performance metrics assessment
### 2. Advanced SEO Analysis
- Meta tag optimization analysis
- Content quality evaluation
- Keyword density analysis
- Readability scoring
- Heading structure analysis
- AI-powered content recommendations
### 3. Technical Infrastructure
- Asynchronous web crawling
- Multi-threaded analysis
- Robust error handling
- Comprehensive logging
- Type-safe data models
## Module Structure
### 1. `analyzer.py`
The main analysis engine that provides comprehensive website analysis.
#### Key Components:
- `WebsiteAnalyzer` class
- URL validation
- Basic website information extraction
- SSL/TLS certificate checking
- DNS record analysis
- WHOIS information retrieval
- Content analysis
- Performance metrics assessment
#### Features:
- Concurrent analysis using ThreadPoolExecutor
- Robust error handling and logging
- User-agent simulation for reliable scraping
- Timeout handling for requests
- Comprehensive result formatting
### 2. `seo_analyzer.py`
Specialized SEO analysis module with AI integration.
#### Key Components:
- `extract_content()`: Fetches and parses webpage content
- `analyze_meta_tags()`: Evaluates meta tags and SEO elements
- `analyze_content_with_ai()`: AI-powered content analysis
- `analyze_seo()`: Main SEO analysis function
#### Features:
- Meta tag optimization analysis
- Content quality scoring
- Keyword density analysis
- Readability evaluation
- AI-powered recommendations
- Weighted scoring system
### 3. `models.py`
Data models for structured analysis results.
#### Key Components:
- `SEORecommendation`: Individual SEO recommendations
- `MetaTagAnalysis`: Meta tag analysis results
- `ContentAnalysis`: Content analysis metrics
- `SEOAnalysisResult`: Complete analysis results
#### Features:
- Type-safe data structures
- Clear data organization
- Easy serialization/deserialization
- Comprehensive documentation
## Usage Examples
### Basic Website Analysis
```python
from website_analyzer import analyze_website
# Analyze a website
results = analyze_website("https://example.com")
# Access analysis results
if results["success"]:
data = results["data"]
print(f"Domain: {data['domain']}")
print(f"SSL Info: {data['analysis']['ssl_info']}")
print(f"Content Info: {data['analysis']['content_info']}")
```
### SEO Analysis
```python
from website_analyzer.seo_analyzer import analyze_seo
# Perform SEO analysis
seo_results = analyze_seo("https://example.com", "your-openai-api-key")
# Access SEO results
if seo_results.success:
print(f"Overall Score: {seo_results.overall_score}")
print(f"Meta Tags: {seo_results.meta_tags}")
print(f"Content Analysis: {seo_results.content}")
print(f"Recommendations: {seo_results.recommendations}")
```
## Dependencies
- `requests`: HTTP requests
- `beautifulsoup4`: HTML parsing
- `python-whois`: WHOIS information
- `dnspython`: DNS record analysis
- `openai`: AI-powered analysis
- `loguru`: Logging
- `typing`: Type hints
- `dataclasses`: Data models
## Error Handling
The module implements comprehensive error handling:
- URL validation
- Request timeouts
- Connection errors
- Parsing errors
- API errors
- DNS resolution errors
- SSL/TLS errors
All errors are logged and returned in a structured format for easy handling.
## Logging
The module uses `loguru` for logging with the following features:
- File rotation (500 MB)
- 10-day retention
- Debug level logging
- Structured log format
- Both file and stdout output
## Best Practices
1. **API Key Management**
- Store API keys securely
- Use environment variables
- Implement rate limiting
2. **Error Handling**
- Always check success status
- Handle errors gracefully
- Log errors appropriately
3. **Performance**
- Use concurrent analysis
- Implement timeouts
- Cache results when possible
4. **Rate Limiting**
- Respect website robots.txt
- Implement delays between requests
- Use appropriate user agents
## Contributing
1. Fork the repository
2. Create a feature branch
3. Commit your changes
4. Push to the branch
5. Create a Pull Request
## License
This module is part of the ALwrity project and is licensed under the MIT License.

View File

@@ -0,0 +1,7 @@
"""Website analyzer module for AI-powered website analysis."""
from .analyzer import analyze_website
from .seo_analyzer import analyze_seo
from .models import SEOAnalysisResult
__all__ = ['analyze_seo', 'SEOAnalysisResult', 'analyze_website']

View File

@@ -0,0 +1,323 @@
"""Website scraping and AI analysis module."""
import asyncio
from typing import Dict, List, Optional
from bs4 import BeautifulSoup
from urllib.parse import urljoin, urlparse
import streamlit as st
import re
from loguru import logger
from ...web_crawlers.async_web_crawler import AsyncWebCrawlerService
from ...gpt_providers.text_generation.main_text_generation import llm_text_gen
import os
import sys
import logging
import json
from datetime import datetime
import requests
import ssl
import socket
import whois
import dns.resolver
from requests.exceptions import RequestException
from concurrent.futures import ThreadPoolExecutor
# Configure logging
logging.basicConfig(
level=logging.DEBUG,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.StreamHandler(),
logging.FileHandler('website_analyzer.log')
]
)
logger = logging.getLogger(__name__)
def analyze_website(url: str) -> Dict:
"""
Analyze a website and return comprehensive results.
Args:
url (str): The URL to analyze
Returns:
Dict: Analysis results including various metrics and checks
"""
logger.info(f"Starting website analysis for URL: {url}")
try:
analyzer = WebsiteAnalyzer()
results = analyzer.analyze_website(url)
# Add success status to results
if "error" in results:
return {
"success": False,
"error": results["error"]
}
# Add success status and wrap results
return {
"success": True,
"data": results
}
except Exception as e:
logger.error(f"Error in analyze_website: {str(e)}", exc_info=True)
return {
"success": False,
"error": str(e)
}
class WebsiteAnalyzer:
def __init__(self):
self.session = requests.Session()
self.session.headers.update({
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
})
logger.info("WebsiteAnalyzer initialized")
def analyze_website(self, url: str) -> Dict:
"""
Perform comprehensive analysis of a website.
Args:
url (str): The URL to analyze
Returns:
Dict: Analysis results including various metrics and checks
"""
logger.info(f"Starting analysis for URL: {url}")
try:
# Validate URL
if not self._validate_url(url):
logger.error(f"Invalid URL format: {url}")
return {"error": "Invalid URL format"}
# Basic URL parsing
parsed_url = urlparse(url)
domain = parsed_url.netloc
logger.debug(f"Parsed domain: {domain}")
# Initialize results dictionary
results = {
"url": url,
"domain": domain,
"timestamp": datetime.now().isoformat(),
"analysis": {}
}
# Perform various analyses
with ThreadPoolExecutor(max_workers=4) as executor:
# Basic website info
basic_info = executor.submit(self._get_basic_info, url).result()
results["analysis"]["basic_info"] = basic_info
# SSL/TLS info
ssl_info = executor.submit(self._check_ssl, domain).result()
results["analysis"]["ssl_info"] = ssl_info
# DNS info
dns_info = executor.submit(self._check_dns, domain).result()
results["analysis"]["dns_info"] = dns_info
# WHOIS info
whois_info = executor.submit(self._get_whois_info, domain).result()
results["analysis"]["whois_info"] = whois_info
# Content analysis
content_info = executor.submit(self._analyze_content, url).result()
results["analysis"]["content_info"] = content_info
# Performance metrics
performance = executor.submit(self._check_performance, url).result()
results["analysis"]["performance"] = performance
logger.info(f"Analysis completed successfully for {url}")
return results
except Exception as e:
logger.error(f"Error during website analysis: {str(e)}", exc_info=True)
return {"error": str(e)}
def _validate_url(self, url: str) -> bool:
"""Validate URL format."""
try:
result = urlparse(url)
return all([result.scheme, result.netloc])
except Exception as e:
logger.error(f"URL validation error: {str(e)}")
return False
def _get_basic_info(self, url: str) -> Dict:
"""Get basic website information."""
logger.debug(f"Getting basic info for {url}")
try:
response = self.session.get(url, timeout=10)
response.raise_for_status()
soup = BeautifulSoup(response.text, 'html.parser')
return {
"status_code": response.status_code,
"content_type": response.headers.get('content-type', ''),
"title": soup.title.string if soup.title else '',
"meta_description": self._get_meta_description(soup),
"headers": dict(response.headers),
"robots_txt": self._get_robots_txt(url),
"sitemap": self._get_sitemap(url)
}
except Exception as e:
logger.error(f"Error getting basic info: {str(e)}", exc_info=True)
return {"error": str(e)}
def _check_ssl(self, domain: str) -> Dict:
"""Check SSL/TLS certificate information."""
logger.debug(f"Checking SSL for {domain}")
try:
context = ssl.create_default_context()
with socket.create_connection((domain, 443)) as sock:
with context.wrap_socket(sock, server_hostname=domain) as ssock:
cert = ssock.getpeercert()
return {
"has_ssl": True,
"issuer": dict(x[0] for x in cert['issuer']),
"expiry": datetime.strptime(cert['notAfter'], '%b %d %H:%M:%S %Y %Z').isoformat(),
"version": cert['version'],
"subject": dict(x[0] for x in cert['subject'])
}
except Exception as e:
logger.error(f"SSL check error: {str(e)}", exc_info=True)
return {"has_ssl": False, "error": str(e)}
def _check_dns(self, domain: str) -> Dict:
"""Check DNS records."""
logger.debug(f"Checking DNS for {domain}")
try:
records = {}
for record_type in ['A', 'AAAA', 'MX', 'NS', 'TXT']:
try:
answers = dns.resolver.resolve(domain, record_type)
records[record_type] = [str(rdata) for rdata in answers]
except dns.resolver.NoAnswer:
records[record_type] = []
except Exception as e:
logger.warning(f"Error resolving {record_type} record: {str(e)}")
records[record_type] = []
return records
except Exception as e:
logger.error(f"DNS check error: {str(e)}", exc_info=True)
return {"error": str(e)}
def _get_whois_info(self, domain: str) -> Dict:
"""Get WHOIS information for a domain."""
try:
w = whois.whois(domain)
def format_date(date_value):
if isinstance(date_value, list):
return date_value[0].isoformat() if date_value else 'Unknown'
return date_value.isoformat() if date_value else 'Unknown'
return {
'registrar': w.registrar if hasattr(w, 'registrar') else 'Unknown',
'creation_date': format_date(w.creation_date),
'expiration_date': format_date(w.expiration_date),
'updated_date': format_date(w.updated_date) if hasattr(w, 'updated_date') else 'Unknown',
'name_servers': w.name_servers if hasattr(w, 'name_servers') else [],
'domain_name': w.domain_name if hasattr(w, 'domain_name') else domain,
'text': w.text if hasattr(w, 'text') else ''
}
except Exception as e:
logger.error(f"WHOIS check error: {str(e)}")
return {
'registrar': 'Unknown',
'creation_date': 'Unknown',
'expiration_date': 'Unknown',
'updated_date': 'Unknown',
'name_servers': [],
'domain_name': domain,
'text': ''
}
def _analyze_content(self, url: str) -> Dict:
"""Analyze website content."""
logger.debug(f"Analyzing content for {url}")
try:
response = self.session.get(url, timeout=10)
response.raise_for_status()
soup = BeautifulSoup(response.text, 'html.parser')
# Get all text content
text_content = soup.get_text()
# Count words
words = re.findall(r'\w+', text_content.lower())
word_count = len(words)
# Count headings
headings = soup.find_all(['h1', 'h2', 'h3', 'h4', 'h5', 'h6'])
# Count images
images = soup.find_all('img')
# Count links
links = soup.find_all('a')
return {
"word_count": word_count,
"heading_count": len(headings),
"image_count": len(images),
"link_count": len(links),
"has_meta_description": bool(self._get_meta_description(soup)),
"has_robots_txt": bool(self._get_robots_txt(url)),
"has_sitemap": bool(self._get_sitemap(url))
}
except Exception as e:
logger.error(f"Content analysis error: {str(e)}", exc_info=True)
return {"error": str(e)}
def _check_performance(self, url: str) -> Dict:
"""Check website performance metrics."""
logger.debug(f"Checking performance for {url}")
try:
start_time = datetime.now()
response = self.session.get(url, timeout=10)
end_time = datetime.now()
load_time = (end_time - start_time).total_seconds()
return {
"load_time": load_time,
"status_code": response.status_code,
"content_length": len(response.content),
"headers": dict(response.headers)
}
except Exception as e:
logger.error(f"Performance check error: {str(e)}", exc_info=True)
return {"error": str(e)}
def _get_meta_description(self, soup: BeautifulSoup) -> Optional[str]:
"""Extract meta description from HTML."""
meta_desc = soup.find('meta', attrs={'name': 'description'})
return meta_desc.get('content') if meta_desc else None
def _get_robots_txt(self, url: str) -> Optional[str]:
"""Get robots.txt content."""
try:
robots_url = f"{url.rstrip('/')}/robots.txt"
response = self.session.get(robots_url, timeout=5)
if response.status_code == 200:
return response.text
except Exception as e:
logger.warning(f"Error fetching robots.txt: {str(e)}")
return None
def _get_sitemap(self, url: str) -> Optional[str]:
"""Get sitemap.xml content."""
try:
sitemap_url = f"{url.rstrip('/')}/sitemap.xml"
response = self.session.get(sitemap_url, timeout=5)
if response.status_code == 200:
return response.text
except Exception as e:
logger.warning(f"Error fetching sitemap.xml: {str(e)}")
return None

View File

@@ -0,0 +1,45 @@
"""Data models for website analysis results."""
from dataclasses import dataclass
from typing import List, Dict, Optional
from datetime import datetime
@dataclass
class SEORecommendation:
"""A single SEO recommendation."""
priority: str # 'high', 'medium', 'low'
category: str # 'content', 'technical', 'meta', etc.
issue: str
recommendation: str
impact: str
@dataclass
class MetaTagAnalysis:
"""Analysis of meta tags."""
title: Dict[str, str] # {'status': 'good', 'value': 'actual title', 'recommendation': 'suggestion'}
description: Dict[str, str]
keywords: Dict[str, str]
has_robots: bool
has_sitemap: bool
@dataclass
class ContentAnalysis:
"""Analysis of page content."""
word_count: int
headings_structure: Dict[str, int] # {'h1': 1, 'h2': 3, etc}
keyword_density: Dict[str, float]
readability_score: float
content_quality_score: float
@dataclass
class SEOAnalysisResult:
"""Complete SEO analysis result."""
url: str
analyzed_at: datetime
overall_score: float # 0-100
meta_tags: MetaTagAnalysis
content: ContentAnalysis
recommendations: List[SEORecommendation]
errors: List[str]
warnings: List[str]
success: bool

View File

@@ -0,0 +1,233 @@
"""SEO analyzer module with AI integration."""
import requests
from bs4 import BeautifulSoup
from datetime import datetime
from typing import Dict, List, Tuple, Optional
from urllib.parse import urlparse
import openai
from loguru import logger
import os
from dotenv import load_dotenv
from .models import (
SEOAnalysisResult,
MetaTagAnalysis,
ContentAnalysis,
SEORecommendation
)
def extract_content(url: str) -> Tuple[Optional[str], Optional[BeautifulSoup], List[str]]:
"""Extract content from URL."""
errors = []
try:
response = requests.get(url, timeout=10)
response.raise_for_status()
soup = BeautifulSoup(response.text, 'html.parser')
return response.text, soup, errors
except requests.RequestException as e:
error_msg = f"Error fetching URL: {str(e)}"
logger.error(error_msg)
errors.append(error_msg)
return None, None, errors
def analyze_meta_tags(soup: BeautifulSoup) -> MetaTagAnalysis:
"""Analyze meta tags using BeautifulSoup."""
# Title analysis
title = soup.title.string if soup.title else ""
title_analysis = {
'status': 'good' if title and 30 <= len(title) <= 60 else 'needs_improvement',
'value': title,
'recommendation': '' if title and 30 <= len(title) <= 60 else 'Title should be between 30-60 characters'
}
# Meta description analysis
meta_desc = soup.find('meta', attrs={'name': 'description'})
desc = meta_desc.get('content', '') if meta_desc else ""
desc_analysis = {
'status': 'good' if desc and 120 <= len(desc) <= 160 else 'needs_improvement',
'value': desc,
'recommendation': '' if desc and 120 <= len(desc) <= 160 else 'Description should be between 120-160 characters'
}
# Keywords analysis
meta_keywords = soup.find('meta', attrs={'name': 'keywords'})
keywords = meta_keywords.get('content', '') if meta_keywords else ""
keywords_analysis = {
'status': 'good' if keywords else 'needs_improvement',
'value': keywords,
'recommendation': '' if keywords else 'Add relevant keywords meta tag'
}
return MetaTagAnalysis(
title=title_analysis,
description=desc_analysis,
keywords=keywords_analysis,
has_robots=bool(soup.find('meta', attrs={'name': 'robots'})),
has_sitemap=bool(soup.find('link', attrs={'rel': 'sitemap'}))
)
def analyze_content_with_ai(content: str) -> Tuple[ContentAnalysis, List[SEORecommendation]]:
"""Analyze content using AI."""
try:
# Load environment variables
load_dotenv()
# Get API key from environment
api_key = os.getenv('OPENAI_API_KEY')
if not api_key:
raise ValueError("OpenAI API key not found in environment variables")
# Initialize OpenAI client
client = openai.OpenAI(api_key=api_key)
# Prepare prompt for content analysis
prompt = f"""Analyze the following webpage content for SEO and provide a structured analysis:
Content: {content[:4000]}... # Truncate to avoid token limits
Provide analysis in the following format:
1. Word count
2. Heading structure analysis
3. Keyword density for main topics
4. Readability score (0-100)
5. Content quality score (0-100)
6. List of SEO recommendations with priority (high/medium/low), category, issue, recommendation, and impact
Format the response as JSON."""
# Get AI analysis
response = client.chat.completions.create(
model="gpt-4",
messages=[
{"role": "system", "content": "You are an SEO expert analyzing website content."},
{"role": "user", "content": prompt}
],
response_format={"type": "json_object"}
)
# Parse AI response
analysis = response.choices[0].message.content
# Create ContentAnalysis object
content_analysis = ContentAnalysis(
word_count=len(content.split()),
headings_structure=analysis.get('heading_structure', {}),
keyword_density=analysis.get('keyword_density', {}),
readability_score=analysis.get('readability_score', 0),
content_quality_score=analysis.get('content_quality_score', 0)
)
# Create recommendations
recommendations = [
SEORecommendation(
priority=rec['priority'],
category=rec['category'],
issue=rec['issue'],
recommendation=rec['recommendation'],
impact=rec['impact']
)
for rec in analysis.get('recommendations', [])
]
return content_analysis, recommendations
except Exception as e:
logger.error(f"Error in AI analysis: {str(e)}")
return ContentAnalysis(
word_count=len(content.split()),
headings_structure={},
keyword_density={},
readability_score=0,
content_quality_score=0
), []
def analyze_seo(url: str) -> SEOAnalysisResult:
"""Main function to analyze website SEO."""
errors = []
warnings = []
# Validate URL
try:
parsed_url = urlparse(url)
if not all([parsed_url.scheme, parsed_url.netloc]):
errors.append("Invalid URL format")
raise ValueError("Invalid URL format")
except Exception as e:
errors.append(f"URL parsing error: {str(e)}")
return SEOAnalysisResult(
url=url,
analyzed_at=datetime.now(),
overall_score=0,
meta_tags=None,
content=None,
recommendations=[],
errors=errors,
warnings=warnings,
success=False
)
# Extract content
content, soup, extract_errors = extract_content(url)
errors.extend(extract_errors)
if not content or not soup:
return SEOAnalysisResult(
url=url,
analyzed_at=datetime.now(),
overall_score=0,
meta_tags=None,
content=None,
recommendations=[],
errors=errors,
warnings=warnings,
success=False
)
try:
# Analyze meta tags
meta_analysis = analyze_meta_tags(soup)
# Analyze content with AI
content_analysis, recommendations = analyze_content_with_ai(content)
# Calculate overall score
meta_score = sum([
1 if meta_analysis.title['status'] == 'good' else 0,
1 if meta_analysis.description['status'] == 'good' else 0,
1 if meta_analysis.keywords['status'] == 'good' else 0,
1 if meta_analysis.has_robots else 0,
1 if meta_analysis.has_sitemap else 0
]) * 20 # Scale to 100
overall_score = (
meta_score * 0.3 + # 30% weight for meta tags
content_analysis.readability_score * 0.3 + # 30% weight for readability
content_analysis.content_quality_score * 0.4 # 40% weight for content quality
)
return SEOAnalysisResult(
url=url,
analyzed_at=datetime.now(),
overall_score=overall_score,
meta_tags=meta_analysis,
content=content_analysis,
recommendations=recommendations,
errors=errors,
warnings=warnings,
success=True
)
except Exception as e:
error_msg = f"Error in SEO analysis: {str(e)}"
logger.error(error_msg)
errors.append(error_msg)
return SEOAnalysisResult(
url=url,
analyzed_at=datetime.now(),
overall_score=0,
meta_tags=None,
content=None,
recommendations=[],
errors=errors,
warnings=warnings,
success=False
)

151
lib/web_crawlers/README.md Normal file
View File

@@ -0,0 +1,151 @@
# Web Crawler Guide for Content Creators
## What is a Web Crawler?
A web crawler is a powerful tool that helps content creators gather, analyze, and understand content from websites. It's like having a digital assistant that can quickly scan websites and extract valuable information to help you create better content.
## Key Features
### 1. Content Extraction
- **Main Content**: Extracts the primary content from web pages
- **Meta Information**: Captures titles, descriptions, and meta tags
- **Structure Analysis**: Identifies headings and content hierarchy
- **Media Elements**: Collects links and images with their descriptions
### 2. AI-Powered Analysis
- **Topic Identification**: Automatically identifies main topics
- **Content Quality Assessment**: Evaluates readability and engagement
- **SEO Analysis**: Provides SEO scores and recommendations
- **Content Gap Analysis**: Identifies missing information
- **Opportunity Detection**: Suggests areas for improvement
### 3. Smart Processing
- **Fast Performance**: Uses advanced async technology for quick results
- **Error Handling**: Gracefully handles website access issues
- **Content Cleaning**: Removes unnecessary elements for clean analysis
- **Multiple Page Support**: Can analyze multiple pages efficiently
## Use Cases for Content Creators
### 1. Content Research
- **Competitor Analysis**: Study competitor content and strategies
- **Topic Research**: Gather information for new content ideas
- **Industry Trends**: Track industry developments and updates
- **Content Inspiration**: Find inspiration from successful content
### 2. Content Optimization
- **SEO Improvement**: Identify SEO opportunities
- **Content Structure**: Analyze and improve content organization
- **Readability Enhancement**: Get suggestions for better readability
- **Engagement Optimization**: Improve content engagement
### 3. Content Strategy
- **Gap Analysis**: Identify content gaps in your niche
- **Topic Planning**: Plan content topics and themes
- **Audience Understanding**: Better understand target audience needs
- **Performance Tracking**: Monitor content performance
## How to Use the Web Crawler
### 1. Basic Usage
1. **Enter URL**: Provide the website URL you want to analyze
2. **Start Crawling**: The crawler will automatically extract content
3. **Review Results**: Get comprehensive analysis of the content
### 2. Advanced Features
- **Custom Analysis**: Set specific parameters for content analysis
- **Batch Processing**: Analyze multiple pages at once
- **Detailed Reports**: Get in-depth content analysis reports
- **Export Options**: Export results in various formats
### 3. Analysis Options
- **Content Quality**: Evaluate writing style and structure
- **SEO Metrics**: Check SEO performance
- **Engagement Factors**: Analyze reader engagement potential
- **Improvement Suggestions**: Get actionable recommendations
## Benefits for Content Creators
### 1. Time Savings
- Quick content research
- Automated analysis
- Efficient data gathering
- Streamlined workflow
### 2. Quality Improvement
- Better content structure
- Enhanced readability
- Improved SEO performance
- Higher engagement potential
### 3. Strategic Advantage
- Data-driven decisions
- Competitive insights
- Content optimization
- Performance tracking
## Best Practices
### 1. Before Crawling
- Identify clear objectives
- Select relevant websites
- Set analysis parameters
- Prepare for data collection
### 2. During Analysis
- Review extracted content
- Validate information
- Check for accuracy
- Note important insights
### 3. After Analysis
- Apply findings to content
- Track improvements
- Update content strategy
- Monitor results
## Common Applications
### 1. Blog Content
- Topic research
- Content structure analysis
- SEO optimization
- Engagement improvement
### 2. Article Writing
- Research gathering
- Fact verification
- Source analysis
- Content enhancement
### 3. Website Content
- Page optimization
- Content audit
- Structure improvement
- SEO enhancement
### 4. Social Media Content
- Trend analysis
- Content ideas
- Engagement optimization
- Performance tracking
## Tips for Optimal Results
1. **Be Specific**: Clearly define your analysis goals
2. **Choose Quality Sources**: Select reliable websites for analysis
3. **Review Results**: Always verify extracted information
4. **Apply Insights**: Use findings to improve your content
5. **Track Progress**: Monitor improvements over time
## ALwrity, Need Help?
If you encounter any issues or need assistance:
1. Check the documentation
2. Review error messages
3. Verify website accessibility
4. Contact support if needed
---
*Note: This tool is designed to help content creators gather and analyze web content efficiently. Always respect website terms of service and robots.txt files when crawling websites.*

View File

@@ -0,0 +1,246 @@
"""Web crawler module using requests and BeautifulSoup."""
from typing import Dict, List, Optional
import json
from loguru import logger
import requests
import aiohttp
import asyncio
from bs4 import BeautifulSoup
from urllib.parse import urljoin, urlparse
from pydantic import BaseModel, Field
import os
from ..gpt_providers.text_generation.main_text_generation import llm_text_gen
class WebsiteContent(BaseModel):
"""Model for website content analysis."""
title: str = Field("", description="Title of the webpage")
description: str = Field("", description="Meta description of the webpage")
main_content: str = Field("", description="Main content of the webpage")
headings: List[str] = Field([], description="All headings on the page")
links: List[Dict[str, str]] = Field([], description="All links on the page")
images: List[Dict[str, str]] = Field([], description="All images on the page")
meta_tags: Dict[str, str] = Field({}, description="Meta tags from the page")
class AsyncWebCrawlerService:
"""Service for crawling websites."""
def __init__(self):
"""Initialize the crawler service."""
logger.info("[AsyncWebCrawlerService.__init__] Initializing crawler service")
self.visited_urls = set()
self.base_url = None
self.domain = None
self.session = None
self.max_pages = 10 # Limit the number of pages to crawl
self.timeout = 30 # Timeout in seconds for requests
self.headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
}
async def __aenter__(self):
"""Create aiohttp session when entering context."""
logger.debug("[AsyncWebCrawlerService.__aenter__] Creating aiohttp session")
self.session = aiohttp.ClientSession(headers=self.headers)
return self
async def __aexit__(self, exc_type, exc_val, exc_tb):
"""Close aiohttp session when exiting context."""
logger.debug("[AsyncWebCrawlerService.__aexit__] Closing aiohttp session")
if self.session:
await self.session.close()
async def fetch_url(self, url: str) -> str:
"""
Fetch URL content asynchronously.
Args:
url (str): URL to fetch
Returns:
str: HTML content
"""
logger.debug(f"[AsyncWebCrawlerService.fetch_url] Fetching URL: {url}")
if not self.session:
logger.debug("[AsyncWebCrawlerService.fetch_url] Creating new session")
self.session = aiohttp.ClientSession(headers=self.headers)
async with self.session.get(url) as response:
if response.status == 200:
logger.debug(f"[AsyncWebCrawlerService.fetch_url] Successfully fetched URL: {url}")
return await response.text()
else:
error_msg = f"Failed to fetch URL: Status code {response.status}"
logger.error(f"[AsyncWebCrawlerService.fetch_url] {error_msg}")
raise Exception(error_msg)
async def crawl_website(self, url: str) -> Dict:
"""
Crawl a website and extract its content.
Args:
url (str): The URL to crawl
Returns:
Dict: Extracted website content and metadata
"""
try:
logger.info(f"[AsyncWebCrawlerService.crawl_website] Starting crawl for URL: {url}")
# Fetch the page content
try:
html_content = await self.fetch_url(url)
logger.debug("[AsyncWebCrawlerService.crawl_website] Successfully fetched HTML content")
except Exception as e:
error_msg = f"Failed to fetch content from {url}: {str(e)}"
logger.error(f"[AsyncWebCrawlerService.crawl_website] {error_msg}")
return {
'success': False,
'error': error_msg
}
# Parse HTML with BeautifulSoup
logger.debug("[AsyncWebCrawlerService.crawl_website] Parsing HTML content")
soup = BeautifulSoup(html_content, 'html.parser')
# Extract main content (focusing on article-like content)
main_content_elements = soup.find_all(['article', 'main', 'div'], class_=['content', 'main-content', 'article', 'post'])
if not main_content_elements:
main_content_elements = soup.find_all(['p', 'article', 'section'])
main_content = ' '.join([elem.get_text(strip=True) for elem in main_content_elements])
# If still no content, get all paragraph text
if not main_content:
main_content = ' '.join([p.get_text(strip=True) for p in soup.find_all('p')])
logger.debug(f"[AsyncWebCrawlerService.crawl_website] Extracted {len(main_content)} characters of main content")
# Extract content
content = {
'title': soup.title.string.strip() if soup.title else '',
'description': soup.find('meta', {'name': 'description'}).get('content', '').strip() if soup.find('meta', {'name': 'description'}) else '',
'main_content': main_content,
'headings': [h.get_text(strip=True) for h in soup.find_all(['h1', 'h2', 'h3', 'h4', 'h5', 'h6'])],
'links': [{'text': a.get_text(strip=True), 'href': urljoin(url, a.get('href', ''))} for a in soup.find_all('a', href=True)],
'images': [{'alt': img.get('alt', '').strip(), 'src': urljoin(url, img.get('src', ''))} for img in soup.find_all('img', src=True)],
'meta_tags': {
meta.get('name', meta.get('property', '')): meta.get('content', '').strip()
for meta in soup.find_all('meta')
if (meta.get('name') or meta.get('property')) and meta.get('content')
}
}
logger.debug(f"[AsyncWebCrawlerService.crawl_website] Extracted {len(content['links'])} links and {len(content['images'])} images")
# Close the session if it exists
if self.session:
logger.debug("[AsyncWebCrawlerService.crawl_website] Closing session")
await self.session.close()
self.session = None
logger.info("[AsyncWebCrawlerService.crawl_website] Successfully completed website crawl")
return {
'success': True,
'content': content,
'url': url
}
except Exception as e:
error_msg = f"Error crawling {url}: {str(e)}"
logger.error(f"[AsyncWebCrawlerService.crawl_website] {error_msg}")
# Ensure session is closed even if there's an error
if self.session:
logger.debug("[AsyncWebCrawlerService.crawl_website] Closing session after error")
await self.session.close()
self.session = None
return {
'success': False,
'error': str(e)
}
async def analyze_content_with_llm(self, content: Dict, api_key: str, gpt_provider: str) -> Dict:
"""
Analyze content using LLM.
Args:
content (Dict): Content to analyze
api_key (str): API key for the LLM service
gpt_provider (str): Provider to use (openai/google)
Returns:
Dict: Analysis results
"""
try:
logger.info(f"[AsyncWebCrawlerService.analyze_content_with_llm] Starting content analysis with {gpt_provider}")
# Prepare the content for analysis
main_content = content.get("main_content", "")
if isinstance(main_content, dict):
main_content = main_content.get("text", "")
logger.debug(f"[AsyncWebCrawlerService.analyze_content_with_llm] Prepared {len(main_content)} characters for analysis")
# Construct the prompt for analysis
prompt = f"""Analyze the following website content and provide a comprehensive analysis:
Content:
{main_content[:4000]} # Limit content length for API
Please provide analysis in the following JSON format:
{{
"topics": ["topic1", "topic2", ...],
"key_insights": ["insight1", "insight2", ...],
"content_quality": {{
"readability": "score",
"engagement": "score",
"completeness": "score"
}},
"recommendations": ["rec1", "rec2", ...],
"seo_score": "score",
"content_gaps": ["gap1", "gap2", ...],
"opportunities": ["opp1", "opp2", ...],
"priority_areas": ["area1", "area2", ...]
}}
Ensure the response is valid JSON."""
# Call the LLM function
logger.debug("[AsyncWebCrawlerService.analyze_content_with_llm] Calling llm_text_gen with prompt")
response = llm_text_gen(prompt)
if not response:
logger.error("[AsyncWebCrawlerService.analyze_content_with_llm] No response from LLM")
return {}
# Clean up the response before parsing
logger.debug("[AsyncWebCrawlerService.analyze_content_with_llm] Cleaning response for JSON parsing")
try:
# Remove any leading/trailing whitespace
cleaned_response = response.strip()
# If response starts with a newline or other characters before {, clean it
start_idx = cleaned_response.find('{')
end_idx = cleaned_response.rfind('}')
if start_idx != -1 and end_idx != -1:
cleaned_response = cleaned_response[start_idx:end_idx + 1]
# Fix any line breaks within strings
cleaned_response = cleaned_response.replace('\n', ' ')
logger.debug(f"[AsyncWebCrawlerService.analyze_content_with_llm] Attempting to parse cleaned response: {cleaned_response[:100]}...")
# Parse the cleaned response
analysis_result = json.loads(cleaned_response)
logger.info("[AsyncWebCrawlerService.analyze_content_with_llm] Successfully parsed LLM response")
logger.debug(f"[AsyncWebCrawlerService.analyze_content_with_llm] Analysis result keys: {analysis_result.keys()}")
return analysis_result
except json.JSONDecodeError as e:
logger.error(f"[AsyncWebCrawlerService.analyze_content_with_llm] Failed to parse LLM response as JSON: {str(e)}")
logger.debug(f"[AsyncWebCrawlerService.analyze_content_with_llm] Raw response: {response[:100]}...")
return {}
except Exception as e:
logger.error(f"[AsyncWebCrawlerService.analyze_content_with_llm] Error analyzing content with LLM: {str(e)}")
return {}

View File

@@ -0,0 +1,94 @@
"""Web crawler for ALwrity style analysis."""
import asyncio
from crawl4ai import AsyncWebCrawler
from crawl4ai.async_configs import BrowserConfig, CrawlerRunConfig, CacheMode
from loguru import logger
async def analyze_website_style(url: str, sample_text: str = None) -> dict:
"""
Analyze website content or sample text for style analysis.
Args:
url: Website URL to analyze
sample_text: Optional sample text to analyze instead of website
Returns:
dict: Analysis results including content style metrics
"""
try:
if sample_text:
# Analyze sample text directly
return {
"success": True,
"content": sample_text,
"metrics": {
"word_count": len(sample_text.split()),
"sentence_count": len(sample_text.split('.')),
"avg_sentence_length": len(sample_text.split()) / max(len(sample_text.split('.')), 1)
}
}
browser_config = BrowserConfig() # Default browser configuration
run_config = CrawlerRunConfig() # Default crawl run configuration
async with AsyncWebCrawler(config=browser_config) as crawler:
result = await crawler.arun(
url=url,
config=run_config
)
print(result.markdown) # Print clean markdown content
logger.debug(f"Crawl result: {result}")
if result.success:
# Process content for style analysis
content = result.markdown
sentences = [s.strip() for s in content.split('.') if s.strip()]
return {
"success": True,
"content": content,
"metrics": {
"word_count": len(content.split()),
"sentence_count": len(sentences),
"avg_sentence_length": len(content.split()) / max(len(sentences), 1),
"internal_links": len(result.links["internal"]),
"images": len(result.media["images"])
}
}
else:
return {
"success": False,
"error": result.error_message
}
except Exception as e:
logger.error(f"Error in style analysis: {str(e)}")
return {
"success": False,
"error": str(e)
}
def analyze_style(url: str = None, sample_text: str = None) -> dict:
"""
Synchronous wrapper for style analysis.
Args:
url: Website URL to analyze
sample_text: Optional sample text to analyze
Returns:
dict: Analysis results
"""
return asyncio.run(analyze_website_style(url, sample_text))
# Deep Crawling
# One of Crawl4AI's most powerful features is its ability to perform
# configurable deep crawling that can explore websites beyond a single page.
# With fine-tuned control over crawl depth, domain boundaries,
# and content filtering, Crawl4AI gives you the tools to extract precisely the content you need.
#
#
#
#
#

View File

@@ -0,0 +1,50 @@
"""Page for AI Research Setup redirection."""
import streamlit as st
from loguru import logger
import sys
import os
# Configure logger
logger.remove() # Remove default handler
logger.add(
"logs/ai_research_setup_page.log",
rotation="500 MB",
retention="10 days",
level="DEBUG",
format="{time:YYYY-MM-DD HH:mm:ss} | {level} | {message}",
backtrace=True,
diagnose=True
)
logger.add(
sys.stdout,
level="INFO",
format="<green>{time:YYYY-MM-DD HH:mm:ss}</green> | <level>{level: <8}</level> | <cyan>{message}</cyan>"
)
# Set page config
st.set_page_config(
layout="wide",
initial_sidebar_state="collapsed",
menu_items={
'Get Help': None,
'Report a bug': None,
'About': None
}
)
def render_ai_research_setup_page():
"""Render the AI Research Setup page."""
try:
logger.info("Starting AI Research Setup page")
# Import and render the AI Research Setup component
from lib.utils.api_key_manager.components.ai_research_setup import render_ai_research_setup
render_ai_research_setup()
except Exception as e:
logger.error(f"Error in render_ai_research_setup_page: {str(e)}")
st.error(f"An error occurred: {str(e)}")
if __name__ == "__main__":
render_ai_research_setup_page()

View File

@@ -0,0 +1,84 @@
import streamlit as st
import os
import json
from pathlib import Path
st.set_page_config(
page_title="Personalization Setup",
page_icon="⚙️",
layout="wide"
)
st.title("Personalization Setup")
# Initialize session state for active tab if not exists
if 'active_tab' not in st.session_state:
st.session_state.active_tab = "Writing Preferences"
# Create tabs for different sections
tab1, tab2 = st.tabs(["Writing Preferences", "AI Configuration"])
with tab1:
st.write("""
This section allows you to customize your AI writing experience.
Configure your preferences and settings here.
""")
# Add your personalization options here
st.subheader("Writing Style Preferences")
tone = st.selectbox(
"Select your preferred writing tone",
["Professional", "Casual", "Academic", "Creative"]
)
st.subheader("Content Preferences")
content_type = st.multiselect(
"Select your preferred content types",
["Blog Posts", "Articles", "Social Media", "Technical Writing", "Creative Writing"]
)
if st.button("Save Preferences"):
st.success("Your preferences have been saved!")
with tab2:
st.subheader("AI Configuration Settings")
# Create a form for AI 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!")

352
pages/style_utils.py Normal file
View File

@@ -0,0 +1,352 @@
"""CSS styles and utilities for ALwrity pages."""
def get_base_styles() -> str:
"""
Get the base CSS styles for ALwrity.
Returns:
str: CSS styles as a string
"""
return """
<style>
/* Hide main menu */
#MainMenu {
visibility: hidden !important;
}
/* Hide footer */
footer {
visibility: hidden !important;
}
/* Hide deploy button */
.stDeployButton {
display: none !important;
}
/* Hide sidebar in both states */
[data-testid="stSidebar"][aria-expanded="true"],
[data-testid="stSidebar"][aria-expanded="false"] {
visibility: hidden !important;
width: 0px !important;
position: fixed !important;
}
/* Hide hamburger menu */
.st-emotion-cache-1rs6os {
visibility: hidden !important;
}
/* Ensure main content takes full width */
.main .block-container {
max-width: 100% !important;
padding-top: 1rem !important;
}
</style>
"""
def get_glassmorphic_styles() -> str:
"""
Get the glassmorphic CSS styles for ALwrity.
Returns:
str: CSS styles as a string
"""
return """
<style>
.glass-container {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 10px;
padding: 20px;
margin: 10px 0;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
.glass-container:hover {
border: 1px solid rgba(255, 255, 255, 0.3);
box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.47);
}
.info-section {
background: linear-gradient(135deg, rgba(31,119,180,0.1), rgba(31,119,180,0.05));
border-radius: 12px;
padding: 16px;
margin: 8px 0;
}
.info-section h4 {
color: #1f77b4;
margin-bottom: 8px;
}
.info-section p {
margin: 4px 0;
line-height: 1.5;
}
.metric-card {
background: rgba(255, 255, 255, 0.1);
border-radius: 8px;
padding: 15px;
margin: 10px 0;
border: 1px solid rgba(255, 255, 255, 0.2);
}
.metric-value {
font-size: 24px;
font-weight: bold;
color: #00ff00;
}
.metric-label {
font-size: 14px;
color: #888;
}
.progress-bar {
height: 8px;
background: rgba(255, 255, 255, 0.1);
border-radius: 4px;
overflow: hidden;
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, #00ff00, #00ccff);
transition: width 0.3s ease;
}
.stTabs [data-baseweb="tab-list"] {
gap: 2px;
}
.stTabs [data-baseweb="tab"] {
background-color: rgba(255, 255, 255, 0.1);
border-radius: 4px;
padding: 10px 20px;
margin: 0 2px;
}
.stTabs [data-baseweb="tab"]:hover {
background-color: rgba(255, 255, 255, 0.2);
}
.stTabs [aria-selected="true"] {
background-color: rgba(255, 255, 255, 0.3) !important;
border: 1px solid rgba(255, 255, 255, 0.4);
}
.stExpander {
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 8px;
margin: 10px 0;
}
.stExpander:hover {
border-color: rgba(255, 255, 255, 0.2);
}
.stExpander .streamlit-expanderHeader {
background: rgba(255, 255, 255, 0.1);
border-radius: 8px 8px 0 0;
padding: 10px 15px;
}
.stExpander .streamlit-expanderContent {
background: rgba(255, 255, 255, 0.05);
border-radius: 0 0 8px 8px;
padding: 15px;
}
.example-box {
background: rgba(31,119,180,0.05);
border-left: 3px solid #1f77b4;
padding: 12px;
margin: 8px 0;
border-radius: 0 8px 8px 0;
box-shadow: 0 2px 4px rgba(31,119,180,0.1);
}
.example-box p {
margin: 4px 0;
font-style: italic;
}
.example-box code {
color: #00ff00;
font-family: monospace;
}
.analysis-section {
background: rgba(31,119,180,0.05);
border-radius: 12px;
padding: 16px;
margin: 8px 0;
}
.analysis-section h3 {
color: #1f77b4;
margin-bottom: 12px;
}
.analysis-section ul {
margin: 8px 0;
padding-left: 20px;
}
.analysis-section li {
margin: 4px 0;
}
.insight-card {
background: rgba(255, 255, 255, 0.1);
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 8px;
padding: 15px;
margin: 10px 0;
}
.insight-card h4 {
color: #00ff00;
margin-bottom: 10px;
}
.insight-card ul {
margin: 0;
padding-left: 20px;
}
.insight-card li {
margin: 5px 0;
}
.recommendation-box {
background: rgba(0, 255, 0, 0.1);
border: 1px solid rgba(0, 255, 0, 0.2);
border-radius: 6px;
padding: 10px;
margin: 5px 0;
}
.recommendation-box h5 {
color: #00ff00;
margin-bottom: 5px;
}
.recommendation-box p {
margin: 0;
font-size: 14px;
}
.stButton>button {
background: linear-gradient(90deg, #00ff00, #00ccff);
border: none;
color: white;
padding: 10px 20px;
border-radius: 5px;
font-weight: bold;
transition: all 0.3s ease;
}
.stButton>button:hover {
background: linear-gradient(90deg, #00ccff, #00ff00);
transform: translateY(-2px);
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
.stProgress .st-bo {
background-color: rgba(255, 255, 255, 0.1);
}
.stProgress .st-bo > div {
background: linear-gradient(90deg, #00ff00, #00ccff);
}
</style>
"""
def get_glass_container(content: str) -> str:
"""Wrap content in a glass container."""
return f"""
<div class="glass-container">
{content}
</div>
"""
def get_info_section(content: str) -> str:
"""Wrap content in an info section."""
return f"""
<div class="info-section">
{content}
</div>
"""
def get_example_box(content: str) -> str:
"""Wrap content in an example box."""
return f"""
<div class="example-box">
{content}
</div>
"""
def get_analysis_section(title: str, content: str) -> str:
"""Create an analysis section with title and content."""
return f"""
<div class="analysis-section">
<h3>{title}</h3>
{content}
</div>
"""
def get_style_guide_html() -> str:
"""
Get the style guide HTML content.
Returns:
str: HTML content for the style guide section
"""
return """
### 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
"""
def get_test_config_styles() -> str:
"""
Get all CSS styles for test configuration settings page.
Returns:
str: Combined CSS styles as a string
"""
return f"{get_base_styles()}{get_glassmorphic_styles()}"

View File

@@ -0,0 +1,310 @@
"""Test configuration settings page for ALwrity."""
import streamlit as st
from loguru import logger
import asyncio
from lib.web_crawlers.async_web_crawler import AsyncWebCrawlerService
from pages.style_utils import (
get_test_config_styles,
get_glass_container,
get_info_section,
get_example_box,
get_analysis_section,
get_style_guide_html
)
import sys
from lib.personalization.style_analyzer import StyleAnalyzer
# Set page config - must be the first Streamlit command
st.set_page_config(
layout="wide",
initial_sidebar_state="collapsed",
menu_items={
'Get Help': None,
'Report a bug': None,
'About': None
}
)
import yaml
from pathlib import Path
import os
from loguru import logger
from lib.utils.read_main_config_params import get_personalization_settings
from lib.web_crawlers.crawl4ai_web_crawler import analyze_style
# Configure logger
logger.remove() # Remove default handler
logger.add(
"logs/test_config_settings.log",
rotation="500 MB",
retention="10 days",
level="DEBUG",
format="{time:YYYY-MM-DD HH:mm:ss} | {level} | {message}",
backtrace=True,
diagnose=True
)
logger.add(
sys.stdout,
level="INFO",
format="<green>{time:YYYY-MM-DD HH:mm:ss}</green> | <level>{level: <8}</level> | <cyan>{message}</cyan>"
)
# Apply CSS styles
st.markdown(get_test_config_styles(), unsafe_allow_html=True)
def load_website_url():
"""Load website URL from config file."""
try:
logger.debug("Loading website URL from config file")
config_path = Path(os.environ["ALWRITY_CONFIG"])
config = yaml.safe_load(config_path.read_text())
url = config.get('website', {}).get('url', '')
logger.info(f"Loaded website URL: {url}")
return url
except Exception as e:
logger.error(f"Error loading website URL: {str(e)}", exc_info=True)
return ''
def display_style_analysis(analysis_results: dict):
"""Display the style analysis results in a structured format."""
try:
# Writing Style Section
st.markdown("### 🎨 Writing Style Analysis")
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>Engagement Level:</strong> {writing_style.get("engagement_level", "N/A")}</li>
</ul>
"""
st.markdown(get_analysis_section("Writing Style", writing_style_content), unsafe_allow_html=True)
# Content Characteristics Section
content_chars = analysis_results.get("content_characteristics", {})
content_chars_content = f"""
<ul>
<li><strong>Sentence Structure:</strong> {content_chars.get("sentence_structure", "N/A")}</li>
<li><strong>Vocabulary Level:</strong> {content_chars.get("vocabulary_level", "N/A")}</li>
<li><strong>Paragraph Organization:</strong> {content_chars.get("paragraph_organization", "N/A")}</li>
<li><strong>Content Flow:</strong> {content_chars.get("content_flow", "N/A")}</li>
</ul>
"""
st.markdown(get_analysis_section("Content Characteristics", content_chars_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_test_config_settings():
"""Render the test configuration settings page."""
try:
logger.info("Starting to render test configuration settings")
# Add back button at the top
col1, col2 = st.columns([1, 3])
with col1:
if st.button("← Back to Personalization Setup"):
logger.info("User clicked back to personalization setup")
# Set session state for navigation
st.session_state.current_step = 4
st.session_state.next_step = "personalization_setup"
# Navigate back to personalization setup
st.switch_page("pages/personalization_setup.py")
# Title and description
st.title("🎨 Find Your Style with ALwrity")
st.markdown(get_glass_container(
"<p>Enter a website URL or provide content samples to analyze your writing style and get personalized recommendations.</p>"
), unsafe_allow_html=True)
# 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(get_info_section("""
<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>
"""), 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}")
st.markdown('</div>', unsafe_allow_html=True)
# 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:
# TODO: Implement sample text analysis
st.info("Sample text analysis coming soon!")
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
""")
except Exception as e:
logger.error(f"Error in render_test_config_settings: {str(e)}")
st.error(f"An error occurred: {str(e)}")
if __name__ == "__main__":
logger.info("Starting test config settings page")
render_test_config_settings()
logger.info("Test config settings page rendered successfully")

View File

@@ -1,47 +1,48 @@
requests
typer[all]
rich
python-dotenv
loguru
openai
crewai[tools]
crewai_tools
python-docx
PyPDF2
google.generativeai
anthropic
tenacity
tavily-python
tabulate
metaphor_python
exa_py
GoogleNews
langchain-google-genai
clint
scikit-learn
matplotlib
plotly
textstat
requests_html
pytrends
pytube
pytubefix
readability
wordcloud
prompt_toolkit
ipython
html2image
lxml_html_clean
streamlit
yfinance
pandas_ta
firecrawl-py
gTTS
validators
streamlit-mic-recorder
tinify
cloudscraper
xmlschema
moviepy
googlesearch-python
streamlit-aggrid
requests>=2.31.0
typer>=0.9.0
rich>=13.7.0
python-dotenv>=1.0.0
beautifulsoup4==4.12.2
aiohttp>=3.11.11
openai>=1.3.7
PyPDF2>=3.0.1
google-generativeai<0.9.0,>=0.8.0
anthropic>=0.18.1
tenacity>=8.2.3
tabulate>=0.9.0
metaphor-python==0.1.16
exa_py>=1.9.1
GoogleNews>=1.6.15
langchain-google-genai>=2.0.10
clint>=0.5.1
numpy>=1.22.4,<2.0.0
pandas>=2.0.3
scikit-learn>=1.3.2
matplotlib>=3.8.2
plotly>=5.18.0
textstat>=0.7.3
requests_html>=0.10.0
pytrends>=4.9.0
pytube>=15.0.0
pytubefix>=8.12.2
readability>=0.3.2
wordcloud>=1.9.3
prompt_toolkit>=3.0.43
html2image>=2.0.5
lxml[html_clean]>=5.3.0
lxml_html_clean>=0.4.1
streamlit>=1.29.0
yfinance>=0.2.36
pandas_ta>=0.3.14b0
firecrawl-py>=1.14.1
gTTS>=2.5.1
streamlit-mic-recorder>=0.0.8
streamlit-aggrid>=1.1.2
crawl4ai>=0.5.0
playwright>=1.51.0
loguru==0.7.2
tavily-python>=0.2.8
tinify>=1.6.0
validators>=0.20.0
python-whois==0.9.5
dnspython

157
setup.py Normal file
View File

@@ -0,0 +1,157 @@
import sys
import os
import platform
import subprocess
import shutil
import datetime
import socket
import traceback
import pkg_resources
from setuptools import setup, find_packages
def log_error(error_type, details):
"""
Logs installation errors to a file with timestamp and system information.
"""
log_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'install_errors.log')
timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
system_info = {
"OS": platform.system(),
"OS Version": platform.version(),
"Architecture": platform.machine(),
"Python Version": f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}",
"Hostname": socket.gethostname()
}
log_entry = f"[{timestamp}] ERROR: {error_type}\n"
log_entry += f"Details: {details}\n"
log_entry += "System Information:\n"
for key, value in system_info.items():
log_entry += f" {key}: {value}\n"
log_entry += "-" * 80 + "\n"
with open(log_file, 'a') as f:
f.write(log_entry)
print(f"Error logged to {log_file}")
def check_system_dependencies():
"""Check for required system dependencies."""
print("Checking system dependencies...")
all_checks_passed = True
# Check Python version
print("Checking Python version...")
if sys.version_info < (3, 11) or sys.version_info >= (3, 12):
error_msg = "ALwrity requires Python 3.11.x"
print(f"Error: {error_msg}")
log_error("Python Version Check", error_msg)
all_checks_passed = False
else:
print(f"✓ Python {sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro} found")
# Check Visual C++ Build Tools on Windows
if platform.system() == "Windows":
print("Checking for Visual C++ Build Tools...")
if not shutil.which("cl"):
error_msg = "Visual C++ Build Tools not found"
print("❌ Visual C++ Build Tools not found")
print("\nTo install Visual C++ Build Tools, run in an administrative PowerShell:")
print("winget install Microsoft.VisualStudio.2022.BuildTools --silent --override \"--wait --quiet --add Microsoft.VisualStudio.Workload.VCTools --includeRecommended\"")
log_error("Visual C++ Build Tools Check", error_msg)
all_checks_passed = False
else:
print("✓ Visual C++ Build Tools found")
# Check Rust compiler
print("Checking for Rust compiler...")
if not shutil.which("rustc"):
error_msg = "Rust compiler not found"
print("❌ Rust compiler not found")
if platform.system() == "Windows":
print("\nTo install Rust on Windows, run:")
print("Invoke-WebRequest -Uri https://static.rust-lang.org/rustup/dist/x86_64-pc-windows-msvc/rustup-init.exe -OutFile rustup-init.exe")
print("./rustup-init.exe -y")
else:
print("\nTo install Rust on Linux/macOS, run:")
print("curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y")
print("source $HOME/.cargo/env")
log_error("Rust Compiler Check", error_msg)
all_checks_passed = False
else:
print("✓ Rust compiler found")
return all_checks_passed
def get_requirements():
"""Read requirements from requirements.txt."""
with open('requirements.txt') as f:
requirements = [line.strip() for line in f if line.strip() and not line.startswith('#')]
return requirements
def install_requirements(requirements):
"""Install each requirement, showing progress."""
print("Installing required packages...")
for requirement in requirements:
try:
print(f"Installing {requirement}...")
subprocess.check_call([sys.executable, "-m", "pip", "install", requirement])
except subprocess.CalledProcessError as e:
error_msg = f"Error installing {requirement}: {e}"
print(error_msg)
log_error("Package Installation", error_msg)
sys.exit(1)
def main():
"""Main installation function."""
print("ALwrity Installation\n")
# Check system dependencies
if not check_system_dependencies():
print("\nPlease install the missing dependencies and try again.")
print("Check the install_errors.log file for detailed error information.")
sys.exit(1)
# Create virtual environment if it doesn't exist
if not os.path.exists("venv"):
print("\nCreating virtual environment...")
try:
subprocess.check_call([sys.executable, "-m", "venv", "venv"])
except subprocess.CalledProcessError as e:
error_msg = f"Failed to create virtual environment: {e}"
print(error_msg)
log_error("Virtual Environment Creation", error_msg)
sys.exit(1)
# Install requirements
requirements = get_requirements()
install_requirements(requirements)
# Run setup
setup(
name="alwrity",
version="1.0.0",
description="AI-powered content writing assistant",
author="Your Name",
packages=find_packages(),
python_requires=">=3.11, <3.12",
install_requires=requirements,
entry_points={
'console_scripts': [
'alwrity=alwrity:main',
],
},
)
print("\nInstallation complete! To start ALwrity:")
print("1. Activate the virtual environment:")
if platform.system() == "Windows":
print(" .\\venv\\Scripts\\activate")
else:
print(" source venv/bin/activate")
print("2. Run the application:")
print(" streamlit run alwrity.py")
if __name__ == '__main__':
main()