Content Calendar, Content Gap Analysis, and Content Optimization

This commit is contained in:
ajaysi
2025-05-27 09:15:08 +05:30
parent 4049d19787
commit 889021c078
100 changed files with 18504 additions and 1251 deletions

View File

@@ -0,0 +1,122 @@
@echo off
setlocal enabledelayedexpansion
s :: Set colors for better visibility
color 0A
:: Set the Python version requirement
set MIN_PYTHON_VERSION=3.9
echo ===============================================
echo ALwrity Installation Setup
echo ===============================================
echo.
echo [1/5] Checking Python installation...
python --version > nul 2>&1
if errorlevel 1 (
color 0C
echo [ERROR] Python is not installed!
echo Please install Python %MIN_PYTHON_VERSION% or higher from python.org
echo Press any key to exit...
pause > nul
exit /b 1
)
:: Get Python version
for /f "tokens=2" %%V in ('python --version 2^>^&1') do set PYTHON_VERSION=%%V
for /f "tokens=1,2 delims=." %%a in ("%PYTHON_VERSION%") do (
set PYTHON_MAJOR=%%a
set PYTHON_MINOR=%%b
)
:: Check Python version
set /a PYTHON_VER=%PYTHON_MAJOR%*100 + %PYTHON_MINOR%
set /a MIN_VER=309
if %PYTHON_VER% LSS %MIN_VER% (
color 0C
echo [ERROR] Python version %MIN_PYTHON_VERSION% or higher is required!
echo Current version: %PYTHON_VERSION%
echo Please upgrade Python from python.org
echo Press any key to exit...
pause > nul
exit /b 1
)
echo [✓] Python %PYTHON_VERSION% detected
echo.
echo [2/5] Creating virtual environment...
python -m venv "%~dp0..\..\venv"
if errorlevel 1 (
color 0C
echo [ERROR] Failed to create virtual environment!
echo Press any key to exit...
pause > nul
exit /b 1
)
echo [✓] Virtual environment created
echo.
echo [3/5] Activating virtual environment...
call "%~dp0..\..\venv\Scripts\activate.bat"
if errorlevel 1 (
color 0C
echo [ERROR] Failed to activate virtual environment!
echo Press any key to exit...
pause > nul
exit /b 1
)
echo [✓] Virtual environment activated
echo.
echo [4/5] Upgrading pip...
python -m pip install --upgrade pip
if errorlevel 1 (
color 0C
echo [ERROR] Failed to upgrade pip!
echo Press any key to exit...
pause > nul
exit /b 1
)
echo [✓] Pip upgraded
echo.
echo [5/5] Installing requirements...
pip install -r "%~dp0..\..\requirements.txt"
if errorlevel 1 (
color 0C
echo [ERROR] Failed to install requirements!
echo Press any key to exit...
pause > nul
exit /b 1
)
echo [✓] Requirements installed
echo.
color 0A
echo ===============================================
echo Installation Completed Successfully!
echo ===============================================
echo.
echo Next steps to run ALwrity:
echo.
echo 1. Open a new Command Prompt window
echo 2. Navigate to the ALwrity root directory by copying and pasting this command:
echo cd /d "%~dp0..\.."
echo.
echo 3. Activate the virtual environment by copying and pasting this command:
echo "%~dp0..\..\venv\Scripts\activate.bat"
echo.
echo 4. Run ALwrity with Streamlit by copying and pasting this command:
echo streamlit run "%~dp0..\..\alwrity.py"
echo.
echo Note: You'll need to activate the virtual environment (step 3)
echo each time you want to run ALwrity.
echo.
echo Troubleshooting:
echo - If you see any errors, make sure Python is in your PATH
echo - For help, visit: https://github.com/yourusername/ALwrity
echo.
echo Press any key to exit...
pause > nul

View File

@@ -0,0 +1,27 @@
# ALwrity Installation Guide
## Quick Start
1. **Install Python**
- Download and install Python from [python.org](https://www.python.org/downloads/)
- During installation, check "Add Python to PATH"
2. **Install ALwrity**
- Download this project
- Open the 'Getting Started' folder
- Double-click `setup.py`
- Follow the on-screen instructions
## Running ALwrity
1. Open Command Prompt/Terminal in the 'Getting Started' folder
2. Run: `venv\Scripts\activate` (Windows) or `source venv/bin/activate` (Mac/Linux)
3. Run: `streamlit run alwrity.py`
## Need Help?
- If you see "pip not found": Re-install Python and check "Add Python to PATH"
- For other issues: [Open a support ticket](https://github.com/AJaySi/AI-Writer/issues)
- Join our support community
---

View File

@@ -0,0 +1,26 @@
# Use Python 3.8 slim image optimized for M1/M2 Macs
FROM --platform=linux/arm64 python:3.8-slim
# Set working directory
WORKDIR /app
# Install system dependencies
RUN apt-get update && apt-get install -y \
build-essential \
curl \
software-properties-common \
git \
&& rm -rf /var/lib/apt/lists/*
# Clone the repository
RUN git clone https://github.com/AJaySi/AI-Writer.git .
# Install Python dependencies
RUN python -m pip install --upgrade pip
RUN pip install -r requirements.txt
# Expose Streamlit port
EXPOSE 8501
# Run the application
CMD ["streamlit", "run", "alwrity.py"]

View File

@@ -0,0 +1,23 @@
# ALwrity Installation for Mac Users
## Prerequisites
- macOS 10.15 or later
- Terminal access
- Internet connection
## Installation Methods
### Method 1: Easy Setup (Recommended)
1. Open Terminal
2. Navigate to this directory
3. Run: `python setup.py`
4. Follow the on-screen instructions
### Method 2: Docker Installation
1. Install Docker Desktop for Mac
- Visit [Docker Desktop](https://www.docker.com/products/docker-desktop)
- Download and install the Apple Silicon (M1/M2) or Intel version
2. Build and run:
```bash
docker build -t alwrity .
docker run -p 8501:8501 alwrity

View File

@@ -0,0 +1,78 @@
import sys
import os
import subprocess
import shutil
from pathlib import Path
def print_step(text):
print(f"\n{text}")
def print_error(text):
print(f"\nError: {text}", file=sys.stderr)
def check_homebrew():
try:
subprocess.run(['brew', '--version'], capture_output=True, check=True)
return True
except:
return False
def setup_homebrew():
print_step("Homebrew is required for some dependencies")
print("Please install Homebrew by running this command in Terminal:")
print('/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"')
print("\nAfter installing Homebrew, run this setup script again.")
sys.exit(1)
def create_virtual_environment(venv_path):
try:
if venv_path.exists():
shutil.rmtree(venv_path)
subprocess.run([sys.executable, '-m', 'venv', str(venv_path)], check=True)
return True
except Exception as e:
print_error(f"Failed to create virtual environment: {e}")
return False
def install_requirements(venv_python, requirements_path):
try:
subprocess.run([str(venv_python), '-m', 'pip', 'install', '--upgrade', 'pip'], check=True)
subprocess.run([str(venv_python), '-m', 'pip', 'install', '-r', str(requirements_path)], check=True)
return True
except Exception as e:
print_error(f"Failed to install requirements: {e}")
return False
def main():
print("\n=== ALwrity Mac Installation ===\n")
if not check_homebrew():
setup_homebrew()
current_dir = Path(__file__).parent
project_root = current_dir.parent.parent
requirements_path = project_root / 'requirements.txt'
venv_path = current_dir / 'venv'
print_step("Creating virtual environment")
if not create_virtual_environment(venv_path):
return
print_step("Installing dependencies")
venv_python = venv_path / 'bin' / 'python'
if not install_requirements(venv_python, requirements_path):
return
print("\n✓ Installation completed successfully!")
print("\nTo start ALwrity:")
print("1. Open Terminal in this directory")
print("2. Run: source venv/bin/activate")
print("3. Run: streamlit run alwrity.py")
if __name__ == '__main__':
try:
main()
except KeyboardInterrupt:
print("\nInstallation cancelled")
except Exception as e:
print_error(f"Unexpected error: {e}")

View File

@@ -1,52 +1,25 @@
# ALwrity Installation Guide: Start Here!
# Getting Started with ALwrity
Welcome to ALwrity! This guide will help you choose the best installation method for your needs, whether you're a non-technical user or a developer. Please read the options below and follow the recommended path for your system.
Welcome to ALwrity! Choose the installation method that best suits you:
---
## Option 1: Quick Install for Windows Users (Recommended for Content Creators)
- No technical knowledge required
- Automatic Python installation
- One-click setup
→ [Go to Windows Quick Install](./Option_1_Windows_Quick_Install)
## Which Installation Method Should I Use?
## Option 2: Setup for Python Users
- For users who already have Python installed
- More customization options
- Manual virtual environment setup
→ [Go to Python Setup](./Option_2_Python_Users)
### 1. **Docker (Recommended for Most Users, All Platforms)**
- **Best for:** Anyone who wants a hassle-free, one-command setup on Windows, Mac, or Linux.
- **Why choose Docker?**
- No need to install Python, Rust, or system libraries manually.
- Everything runs in a safe, isolated environment.
- Consistent experience across all operating systems.
- **How to use:**
- See [README_dockerfile.md](./README_dockerfile.md) for step-by-step instructions.
## Option 3: Docker Installation
- For advanced users and developers
- Containerized environment
- Platform-independent setup
→ [Go to Docker Setup](./Option_3_Docker_Install)
### 2. **Windows One-Click Installer (`install_alwrity.bat`)**
- **Best for:** Windows users who prefer a simple double-click installer.
- **Why choose this?**
- Checks and installs all prerequisites for you (Python, Rust, Visual C++ Build Tools).
- Minimal technical knowledge required.
- **How to use:**
- See [README_install_bat.md](./README_install_bat.md) for detailed instructions.
### 3. **Manual Setup for Linux/macOS (`setup.py`)**
- **Best for:** Linux/macOS users who are comfortable with the terminal.
- **Why choose this?**
- Gives you more control over the environment.
- Useful if you want to customize or develop ALwrity.
- **How to use:**
- See [README_setup_py.md](./README_setup_py.md) for a full walkthrough.
---
## Quick Decision Table
| Your System | Easiest Method | File/Guide to Use |
|---------------------|-----------------------|--------------------------|
| Windows (any) | Docker or install_alwrity.bat | README_dockerfile.md or README_install_bat.md |
| Mac | Docker | README_dockerfile.md |
| Linux | Docker | README_dockerfile.md |
| Linux/macOS (dev) | setup.py (manual) | README_setup_py.md |
---
## Still Unsure?
- If you are not sure, **Docker is the safest and easiest choice** for most users.
- If you run into any issues, check the troubleshooting sections in each guide or [open an issue on GitHub](https://github.com/AJaySi/AI-Writer/issues).
---
Happy writing!
## Need Help?
- Visit our [GitHub Issues](https://github.com/AJaySi/AI-Writer/issues) page
- Check our [Documentation](../docs)

View File

@@ -1,92 +0,0 @@
# ALwrity Linux/macOS Installer Guide (`setup.py`)
---
## What is `setup.py`?
`setup.py` is an automated installer for ALwrity on Linux and macOS. It checks your system, sets up a virtual environment, installs all dependencies, and configures ALwrity for you.
---
## What Does It Do?
- Checks your Python version (must be 3.11.x)
- Checks for Rust compiler (required for some Python packages)
- Creates a Python virtual environment (`venv`) if it doesn't exist
- Activates the virtual environment (auto-activation for Linux/macOS)
- Installs all required Python packages from `requirements.txt`
- Installs ALwrity as a command-line tool
- Prints clear next steps for running ALwrity
- Logs any errors to `install_errors.log` for easy troubleshooting
---
## Prerequisites
- **Linux or macOS**
- **Python 3.11.x** (install from https://www.python.org/downloads/ if needed)
- **Rust compiler** (install with `curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y`)
- **At least 4GB RAM**
- **2GB free disk space**
---
## Step-by-Step Instructions
### 1. Open a Terminal
- Navigate to the ALwrity project folder:
```
cd /path/to/AI-Writer/Getting\ Started
```
### 2. Run the Installer
- Run:
```
python3 setup.py install
```
- The script will check your system and install everything needed.
- If you see errors about Python or Rust, follow the on-screen instructions to install them, then re-run the script.
### 3. Start ALwrity
- Activate the virtual environment:
```
source venv/bin/activate
```
- Start the app:
```
streamlit run alwrity.py
```
- Or use the command:
```
alwrity
```
---
## Troubleshooting
- **Python version error:**
- Make sure you have Python 3.11.x installed and are using `python3`.
- **Rust not found:**
- Install Rust with:
```
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
source $HOME/.cargo/env
```
- **Other errors:**
- Check the `install_errors.log` file in the folder for details.
- Copy the error and [open an issue on GitHub](https://github.com/AJaySi/AI-Writer/issues).
---
## FAQ
- **Do I need to install anything else?**
- No, `setup.py` will handle everything for you if prerequisites are met.
- **Can I run this on Windows?**
- Use the Windows installer (`install_alwrity.bat`) instead.
- **Is it safe?**
- Yes, the script only installs ALwrity and its dependencies.
---
## Need More Help?
- [Open an issue on GitHub](https://github.com/AJaySi/AI-Writer/issues)
- Join our support community
---

View File

@@ -1,91 +0,0 @@
@echo off
:: ALwrity Automated Windows Installer
:: This script will set up ALwrity with minimal user input.
:: Last updated: April 23, 2025
chcp 65001 >nul
setlocal enabledelayedexpansion
:: Welcome message
cls
echo ======================================
echo ALwrity - One Click Windows Installer
echo ======================================
echo.
:: Check for admin rights
openfiles >nul 2>&1
if %errorlevel% NEQ 0 (
echo This installer needs to be run as administrator.
echo Please right-click and select "Run as administrator".
pause
exit /b 1
)
:: 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 Downloading Python 3.11 installer...
powershell -Command "Invoke-WebRequest -Uri https://www.python.org/ftp/python/3.11.6/python-3.11.6-amd64.exe -OutFile python-3.11.6-amd64.exe"
echo Launching Python installer. Please check 'Add Python to PATH' and complete installation.
start python-3.11.6-amd64.exe
echo After installation, please re-run this installer.
pause
exit /b 1
)
:: Check for Visual C++ Build Tools
where cl >nul 2>&1
if errorlevel 1 (
echo Visual C++ Build Tools not found. Installing...
powershell -Command "Start-Process 'powershell' -Verb runAs -ArgumentList 'winget install Microsoft.VisualStudio.2022.BuildTools --silent --override \"--wait --quiet --add Microsoft.VisualStudio.Workload.VCTools --includeRecommended\"'"
echo Please wait for the installation to finish, then re-run this installer.
pause
exit /b 1
)
:: Check for Rust compiler
where rustc >nul 2>&1
if errorlevel 1 (
echo Rust compiler not found. Installing...
powershell -Command "Invoke-WebRequest -Uri https://static.rust-lang.org/rustup/dist/x86_64-pc-windows-msvc/rustup-init.exe -OutFile rustup-init.exe"
start rustup-init.exe -y
echo Please wait for the installation to finish, then re-run this installer.
pause
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
:: Upgrade pip
echo Upgrading pip...
python -m pip install --upgrade pip
:: Install requirements if requirements.txt exists
if exist requirements.txt (
echo Installing Python dependencies...
pip install -r requirements.txt
)
:: Install ALwrity
if exist setup.py (
echo Installing ALwrity...
python setup.py install
) else (
echo setup.py not found. Skipping ALwrity install step.
)
echo.
echo Installation complete!
echo To start ALwrity, open a new command prompt and type: alwrity
echo.
pause

85
calendar_data.json Normal file
View File

@@ -0,0 +1,85 @@
{
"name": null,
"description": null,
"start_date": "2025-05-22T08:00:48.925689",
"duration": "monthly",
"platforms": [
"website",
"instagram",
"twitter",
"linkedin",
"facebook"
],
"schedule": {
"2025-05-22": [
{
"title": "AI writer",
"description": "",
"content_type": "blog_post",
"platforms": [
"website"
],
"publish_date": "2025-05-22T00:00:00",
"seo_data": {
"title": "AI writer",
"meta_description": "",
"keywords": [],
"structured_data": {},
"canonical_url": null,
"og_tags": null,
"twitter_cards": null
},
"status": "Draft",
"author": null,
"tags": [],
"notes": null
},
{
"title": "alwrity.",
"description": "",
"content_type": "blog_post",
"platforms": [
"linkedin"
],
"publish_date": "2025-05-22T00:00:00",
"seo_data": {
"title": "alwrity.",
"meta_description": "",
"keywords": [],
"structured_data": {},
"canonical_url": null,
"og_tags": null,
"twitter_cards": null
},
"status": "Draft",
"author": null,
"tags": [],
"notes": null
}
],
"2025-05-31": [
{
"title": "ai content generation",
"description": "",
"content_type": "blog_post",
"platforms": [
"website"
],
"publish_date": "2025-05-31T00:00:00",
"seo_data": {
"title": "ai content generation",
"meta_description": "",
"keywords": [],
"structured_data": {},
"canonical_url": null,
"og_tags": null,
"twitter_cards": null
},
"status": "Draft",
"author": null,
"tags": [],
"notes": null
}
]
}
}

View File

@@ -160,6 +160,142 @@ Stores reusable content templates.
# Relationships
user = relationship("User")
ContentGapAnalysis
~~~~~~~~~~~~~~~~~
Stores content gap analysis results.
.. code-block:: python
class ContentGapAnalysis(Base):
__tablename__ = "content_gap_analyses"
id = Column(Integer, primary_key=True)
user_id = Column(Integer, ForeignKey("users.id"))
website_url = Column(String, nullable=False)
industry = Column(String, nullable=False)
analysis_date = Column(DateTime, default=datetime.utcnow)
status = Column(String, nullable=False) # completed, in_progress, failed
metadata = Column(JSON)
# Relationships
user = relationship("User", back_populates="content_gap_analyses")
website_analysis = relationship("WebsiteAnalysis", back_populates="content_gap_analysis")
competitor_analysis = relationship("CompetitorAnalysis", back_populates="content_gap_analysis")
keyword_analysis = relationship("KeywordAnalysis", back_populates="content_gap_analysis")
recommendations = relationship("ContentRecommendation", back_populates="content_gap_analysis")
WebsiteAnalysis
~~~~~~~~~~~~~~
Stores website analysis results.
.. code-block:: python
class WebsiteAnalysis(Base):
__tablename__ = "website_analyses"
id = Column(Integer, primary_key=True)
content_gap_analysis_id = Column(Integer, ForeignKey("content_gap_analyses.id"))
content_score = Column(Float)
seo_score = Column(Float)
structure_score = Column(Float)
content_metrics = Column(JSON)
seo_metrics = Column(JSON)
technical_metrics = Column(JSON)
ai_insights = Column(JSON)
created_at = Column(DateTime, default=datetime.utcnow)
# Relationships
content_gap_analysis = relationship("ContentGapAnalysis", back_populates="website_analysis")
CompetitorAnalysis
~~~~~~~~~~~~~~~~
Stores competitor analysis results.
.. code-block:: python
class CompetitorAnalysis(Base):
__tablename__ = "competitor_analyses"
id = Column(Integer, primary_key=True)
content_gap_analysis_id = Column(Integer, ForeignKey("content_gap_analyses.id"))
competitor_url = Column(String, nullable=False)
market_position = Column(JSON)
content_gaps = Column(JSON)
competitive_advantages = Column(JSON)
trend_analysis = Column(JSON)
created_at = Column(DateTime, default=datetime.utcnow)
# Relationships
content_gap_analysis = relationship("ContentGapAnalysis", back_populates="competitor_analysis")
KeywordAnalysis
~~~~~~~~~~~~~
Stores keyword analysis results.
.. code-block:: python
class KeywordAnalysis(Base):
__tablename__ = "keyword_analyses"
id = Column(Integer, primary_key=True)
content_gap_analysis_id = Column(Integer, ForeignKey("content_gap_analyses.id"))
top_keywords = Column(JSON)
search_intent = Column(JSON)
opportunities = Column(JSON)
trend_analysis = Column(JSON)
created_at = Column(DateTime, default=datetime.utcnow)
# Relationships
content_gap_analysis = relationship("ContentGapAnalysis", back_populates="keyword_analysis")
ContentRecommendation
~~~~~~~~~~~~~~~~~~~
Stores content recommendations.
.. code-block:: python
class ContentRecommendation(Base):
__tablename__ = "content_recommendations"
id = Column(Integer, primary_key=True)
content_gap_analysis_id = Column(Integer, ForeignKey("content_gap_analyses.id"))
recommendation_type = Column(String, nullable=False) # content, seo, technical, etc.
priority_score = Column(Float)
recommendation = Column(Text, nullable=False)
implementation_steps = Column(JSON)
expected_impact = Column(JSON)
status = Column(String, nullable=False) # pending, in_progress, completed, rejected
created_at = Column(DateTime, default=datetime.utcnow)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
# Relationships
content_gap_analysis = relationship("ContentGapAnalysis", back_populates="recommendations")
AnalysisHistory
~~~~~~~~~~~~~
Tracks the history of analysis runs.
.. code-block:: python
class AnalysisHistory(Base):
__tablename__ = "analysis_histories"
id = Column(Integer, primary_key=True)
content_gap_analysis_id = Column(Integer, ForeignKey("content_gap_analyses.id"))
run_date = Column(DateTime, default=datetime.utcnow)
status = Column(String, nullable=False) # completed, in_progress, failed
metrics = Column(JSON) # Performance metrics for the analysis run
error_log = Column(Text) # Any errors encountered during analysis
# Relationships
content_gap_analysis = relationship("ContentGapAnalysis")
Vector Database Schema
--------------------

View File

@@ -0,0 +1,167 @@
# Content Calendar & Topic Planning System
A comprehensive content planning and scheduling system that leverages existing SEO tools and AI capabilities to create optimized content calendars based on content gap analysis.
## Folder Structure
```
content_calendar/
├── README.md
├── core/
│ ├── __init__.py
│ ├── calendar_manager.py # Main calendar management system
│ ├── topic_generator.py # AI-powered topic generation
│ └── content_predictor.py # Content performance prediction
├── integrations/
│ ├── __init__.py
│ ├── seo_tools.py # Integration with existing SEO tools
│ ├── gap_analyzer.py # Content gap analysis integration
│ └── platform_adapters.py # Platform-specific content adaptation
├── models/
│ ├── __init__.py
│ ├── calendar.py # Calendar data models
│ ├── content.py # Content data models
│ └── analytics.py # Analytics data models
├── utils/
│ ├── __init__.py
│ ├── date_utils.py # Date and scheduling utilities
│ ├── validation.py # Input validation
│ └── error_handling.py # Error handling utilities
└── tests/
├── __init__.py
├── test_calendar.py
├── test_topic_generator.py
└── test_integrations.py
```
## Implementation Plan
### Phase 1: Core Infrastructure
1. **Basic Calendar Management**
- Implement calendar data structures
- Create scheduling algorithms
- Build date management utilities
2. **Topic Generation System**
- Integrate with existing AI tools
- Implement topic generation logic
- Add SEO optimization features
3. **Integration Framework**
- Connect with existing SEO tools
- Implement content gap analysis integration
- Create platform-specific adapters
### Phase 2: AI & SEO Enhancement
1. **AI-Powered Features**
- Implement topic ideation
- Add content structure generation
- Create performance prediction models
2. **SEO Optimization**
- Integrate title optimization
- Add meta description generation
- Implement structured data creation
3. **Content Performance**
- Add performance tracking
- Implement analytics collection
- Create reporting system
### Phase 3: UI Development
1. **Calendar Interface**
- Create interactive calendar view
- Implement drag-and-drop functionality
- Add platform-specific views
2. **Content Planning Panel**
- Build topic suggestion interface
- Create SEO metrics display
- Implement content gap visualization
3. **Analytics Dashboard**
- Design performance metrics view
- Create engagement tracking
- Implement progress monitoring
### Phase 4: Testing & Refinement
1. **Testing**
- Unit testing
- Integration testing
- User acceptance testing
2. **Optimization**
- Performance optimization
- Code refactoring
- Bug fixes
3. **Documentation**
- API documentation
- User guides
- Integration guides
## Integration with Existing Tools
### SEO Tools Integration
- `content_title_generator.py` - For optimized titles
- `meta_desc_generator.py` - For meta descriptions
- `seo_structured_data.py` - For structured data
- `content_gap_analysis/` - For gap analysis
- `webpage_content_analysis.py` - For content analysis
### AI Capabilities
- Leverage existing `llm_text_gen` for:
- Topic generation
- Content structure
- Performance prediction
## Key Features
1. **Content Planning**
- AI-powered topic generation
- SEO-optimized content scheduling
- Platform-specific planning
2. **SEO Integration**
- Automated SEO optimization
- Performance tracking
- Gap analysis integration
3. **Analytics & Reporting**
- Content performance metrics
- SEO impact tracking
- Platform engagement stats
## Getting Started
1. **Prerequisites**
- Python 3.8+
- Access to existing SEO tools
- Required API keys
2. **Installation**
```bash
# Add installation steps here
```
3. **Configuration**
```python
# Add configuration example here
```
4. **Basic Usage**
```python
# Add usage example here
```
## Contributing
Guidelines for contributing to the project.
## License
Project license information.

View File

@@ -0,0 +1,798 @@
from typing import Dict, List, Any, Optional
import logging
from pathlib import Path
import sys
import json
# Add parent directory to path to import existing tools
parent_dir = str(Path(__file__).parent.parent.parent.parent)
if parent_dir not in sys.path:
sys.path.append(parent_dir)
from lib.ai_seo_tools.content_calendar.models.calendar import ContentType, ContentItem, Platform
from lib.ai_seo_tools.content_calendar.utils.error_handling import handle_calendar_error
from lib.gpt_providers.text_generation.main_text_generation import llm_text_gen
from lib.ai_seo_tools.content_gap_analysis.main import ContentGapAnalysis
from lib.ai_seo_tools.content_title_generator import ai_title_generator
from lib.ai_seo_tools.meta_desc_generator import metadesc_generator_main
logger = logging.getLogger(__name__)
class AIGenerator:
"""AI-powered content generation and enhancement."""
def __init__(self):
self.logger = logging.getLogger('content_calendar.ai_generator')
self.logger.info("Initializing AIGenerator")
self._setup_logging()
self._load_ai_tools()
def _setup_logging(self):
"""Configure logging for AI generator."""
logger.setLevel(logging.INFO)
handler = logging.StreamHandler()
formatter = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
handler.setFormatter(formatter)
logger.addHandler(handler)
def _load_ai_tools(self):
"""Load and initialize AI tools."""
try:
# Initialize AI tools
self.gap_analyzer = ContentGapAnalysis()
self.title_generator = ai_title_generator
self.meta_generator = metadesc_generator_main
except Exception as e:
logger.error(f"Error loading AI tools: {str(e)}")
raise
def generate_content(self, content_item: ContentItem, target_audience: Dict[str, Any]) -> Dict[str, Any]:
"""Generate base content using AI."""
try:
self.logger.info(f"Generating content for: {content_item.title}")
# Generate content based on type and platform
content = {
'title': content_item.title,
'content_flow': {
'introduction': {
'summary': f"An engaging introduction about {content_item.title}",
'key_points': [
f"Key point 1 about {content_item.title}",
f"Key point 2 about {content_item.title}",
f"Key point 3 about {content_item.title}"
]
},
'main_content': {
'sections': [
{
'title': f"Section 1: Understanding {content_item.title}",
'content': f"Detailed content about {content_item.title}",
'subsections': []
},
{
'title': f"Section 2: Best Practices for {content_item.title}",
'content': "Best practices and recommendations",
'subsections': []
}
]
},
'conclusion': {
'summary': f"Concluding thoughts about {content_item.title}",
'call_to_action': "Next steps and actions"
}
},
'metadata': {
'tone': target_audience.get('content_settings', {}).get('tone', 'professional'),
'length': target_audience.get('content_settings', {}).get('length', 'medium'),
'platform': content_item.platforms[0].name if content_item.platforms else 'Unknown',
'content_type': content_item.content_type.name
}
}
return content
except Exception as e:
self.logger.error(f"Error generating content: {str(e)}", exc_info=True)
return {}
def enhance_content(self, content: ContentItem, enhancement_type: str, target_audience: Dict[str, Any]) -> Dict[str, Any]:
"""Enhance existing content using AI."""
try:
self.logger.info(f"Enhancing content: {content.title}")
# Enhance content based on type
enhanced = {
'content': f"Enhanced version of {content.description}",
'changes': [
"Improved readability",
"Enhanced engagement elements",
"Optimized for target audience"
],
'metadata': {
'enhancement_type': enhancement_type,
'target_audience': target_audience
}
}
return enhanced
except Exception as e:
self.logger.error(f"Error enhancing content: {str(e)}", exc_info=True)
return {}
def enhance_for_platform(self, content: Dict[str, Any], platform: Platform, enhancement_type: str) -> Dict[str, Any]:
"""Enhance content specifically for a platform."""
try:
self.logger.info(f"Enhancing content for platform: {platform.name}")
# Platform-specific enhancements
enhanced = {
'content': content.get('content', ''),
'changes': [
f"Optimized for {platform.name}",
"Platform-specific formatting",
"Enhanced engagement elements"
],
'metadata': {
'platform': platform.name,
'enhancement_type': enhancement_type
}
}
return enhanced
except Exception as e:
self.logger.error(f"Error enhancing for platform: {str(e)}", exc_info=True)
return {}
def enhance_variant(self, content: Dict[str, Any], variant_type: str, optimization_goals: List[str]) -> Dict[str, Any]:
"""Enhance a content variant for A/B testing."""
try:
self.logger.info(f"Enhancing variant: {variant_type}")
# Variant-specific enhancements
enhanced = {
'content': content.get('content', ''),
'changes': [
f"Optimized for {', '.join(optimization_goals)}",
"Enhanced variant-specific elements",
"Improved engagement metrics"
],
'metadata': {
'variant_type': variant_type,
'optimization_goals': optimization_goals
}
}
return enhanced
except Exception as e:
self.logger.error(f"Error enhancing variant: {str(e)}", exc_info=True)
return {}
def enhance_for_seo(self, content: Dict[str, Any], seo_goals: List[str]) -> Dict[str, Any]:
"""Enhance content for SEO optimization."""
try:
self.logger.info("Enhancing content for SEO")
# SEO-specific enhancements
enhanced = {
'content': content.get('content', ''),
'changes': [
f"Optimized for {', '.join(seo_goals)}",
"Enhanced keyword placement",
"Improved meta information"
],
'metadata': {
'seo_goals': seo_goals
}
}
return enhanced
except Exception as e:
self.logger.error(f"Error enhancing for SEO: {str(e)}", exc_info=True)
return {}
def generate_series_content(self, content_item: ContentItem, series_info: Dict[str, Any]) -> Dict[str, Any]:
"""Generate content for a series."""
try:
self.logger.info(f"Generating series content: {content_item.title}")
# Generate series-specific content
content = {
'title': content_item.title,
'content_flow': {
'introduction': {
'summary': f"Part {series_info['part_number']} of {series_info['total_parts']} about {series_info['topic']}",
'key_points': [
f"Key point 1 for part {series_info['part_number']}",
f"Key point 2 for part {series_info['part_number']}",
f"Key point 3 for part {series_info['part_number']}"
]
},
'main_content': {
'sections': [
{
'title': f"Section 1: Part {series_info['part_number']} Overview",
'content': f"Detailed content for part {series_info['part_number']}",
'subsections': []
},
{
'title': f"Section 2: Part {series_info['part_number']} Details",
'content': "Specific details and information",
'subsections': []
}
]
},
'conclusion': {
'summary': f"Concluding thoughts for part {series_info['part_number']}",
'next_part': f"Preview of part {series_info['part_number'] + 1}" if series_info['part_number'] < series_info['total_parts'] else "Series conclusion"
}
},
'metadata': {
'series_info': series_info,
'platform': content_item.platforms[0].name if content_item.platforms else 'Unknown',
'content_type': content_item.content_type.name
}
}
return content
except Exception as e:
self.logger.error(f"Error generating series content: {str(e)}", exc_info=True)
return {}
@handle_calendar_error
def generate_headings(
self,
title: str,
content_type: ContentType,
context: Dict[str, Any]
) -> List[Dict[str, Any]]:
"""
Generate content headings using AI.
Args:
title: Content title
content_type: Type of content
context: Content context from gap analysis
Returns:
List of generated headings with metadata
"""
try:
# Get content gaps and opportunities
gaps = self.gap_analyzer.analyze_gaps(context.get('website_url', ''))
# Generate headings based on content type and gaps
prompt = self._create_heading_prompt(title, content_type, gaps)
headings = self._call_ai_model(prompt)
return self._format_headings(headings)
except Exception as e:
logger.error(f"Error generating headings: {str(e)}")
return []
@handle_calendar_error
def generate_subheadings(
self,
main_heading: Dict[str, Any],
content_type: ContentType,
context: Dict[str, Any]
) -> List[Dict[str, Any]]:
"""
Generate subheadings for a main heading.
Args:
main_heading: Main heading to generate subheadings for
content_type: Type of content
context: Content context
Returns:
List of generated subheadings
"""
try:
# Create prompt for subheading generation
prompt = self._create_subheading_prompt(
main_heading,
content_type,
context
)
# Generate subheadings
subheadings = self._call_ai_model(prompt)
return self._format_subheadings(subheadings)
except Exception as e:
logger.error(f"Error generating subheadings: {str(e)}")
return []
@handle_calendar_error
def generate_key_points(
self,
title: str,
content_type: ContentType,
context: Dict[str, Any]
) -> List[Dict[str, Any]]:
"""
Generate key points for content.
Args:
title: Content title
content_type: Type of content
context: Content context
Returns:
List of key points with supporting information
"""
try:
# Generate title and meta description for SEO context
seo_title = self.title_generator(title)
meta_desc = self.meta_generator(title)
# Create prompt for key points
prompt = self._create_key_points_prompt(
title,
content_type,
{'title': seo_title, 'meta_description': meta_desc},
context
)
# Generate key points
points = self._call_ai_model(prompt)
return self._format_key_points(points)
except Exception as e:
logger.error(f"Error generating key points: {str(e)}")
return []
@handle_calendar_error
def generate_content_flow(
self,
title: str,
content_type: ContentType,
outline: Dict[str, Any]
) -> Dict[str, Any]:
"""
Generate content flow and structure.
Args:
title: Content title
content_type: Type of content
outline: Content outline with headings and key points
Returns:
Dictionary containing content flow and structure
"""
try:
# Create prompt for content flow
prompt = self._create_flow_prompt(title, content_type, outline)
# Generate content flow
flow = self._call_ai_model(prompt)
return self._format_content_flow(flow)
except Exception as e:
logger.error(f"Error generating content flow: {str(e)}")
return {}
def _create_heading_prompt(
self,
title: str,
content_type: ContentType,
gaps: Dict[str, Any]
) -> str:
"""Create prompt for heading generation."""
return f"""
Generate main headings for a {content_type.value} titled "{title}".
Consider the following content gaps and opportunities:
{json.dumps(gaps, indent=2)}
For each heading, provide:
1. Title
2. Level (1 for main headings)
3. Key keywords to include
4. Brief summary of what this section should cover
Format the response as a JSON array of heading objects.
"""
def _create_subheading_prompt(
self,
main_heading: Dict[str, Any],
content_type: ContentType,
context: Dict[str, Any]
) -> str:
"""Create prompt for subheading generation."""
return f"""
Generate subheadings for the main heading "{main_heading['title']}"
in a {content_type.value}.
Main heading details:
{json.dumps(main_heading, indent=2)}
For each subheading, provide:
1. Title
2. Level (2 for subheadings)
3. Key keywords to include
4. Brief summary of what this subsection should cover
Format the response as a JSON array of subheading objects.
"""
def _create_key_points_prompt(
self,
title: str,
content_type: ContentType,
seo_data: Dict[str, Any],
context: Dict[str, Any]
) -> str:
"""Create prompt for key points generation."""
return f"""
Generate key points for a {content_type.value} titled "{title}".
SEO Requirements:
{json.dumps(seo_data, indent=2)}
For each key point, provide:
1. Main point
2. Importance level (high/medium/low)
3. Supporting evidence or examples
4. Related keywords to include
Format the response as a JSON array of key point objects.
"""
def _create_flow_prompt(
self,
title: str,
content_type: ContentType,
outline: Dict[str, Any]
) -> str:
"""Create prompt for content flow generation."""
return f"""
Generate content flow and structure for a {content_type.value} titled "{title}".
Content Outline:
{json.dumps(outline, indent=2)}
Provide:
1. Introduction structure
2. Main sections flow
3. Conclusion approach
4. Transition points between sections
5. Content pacing recommendations
Format the response as a JSON object with these sections.
"""
def _call_ai_model(self, prompt: str) -> Any:
"""
Call the AI model with the given prompt.
Args:
prompt: The prompt to send to the AI model
Returns:
The AI model's response, parsed as JSON
"""
try:
# Call the AI model
response = llm_text_gen(
prompt=prompt,
max_tokens=1000,
temperature=0.7,
top_p=0.9,
frequency_penalty=0.5,
presence_penalty=0.5
)
# Parse the response as JSON
try:
return json.loads(response)
except json.JSONDecodeError as e:
logger.error(f"Error parsing AI response as JSON: {str(e)}")
logger.error(f"Raw response: {response}")
return {}
except Exception as e:
logger.error(f"Error calling AI model: {str(e)}")
return {}
def _format_headings(self, headings: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
"""Format and validate generated headings."""
formatted = []
for heading in headings:
formatted.append({
'title': heading.get('title', ''),
'level': heading.get('level', 1),
'keywords': heading.get('keywords', []),
'summary': heading.get('summary', '')
})
return formatted
def _format_subheadings(self, subheadings: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
"""Format and validate generated subheadings."""
formatted = []
for subheading in subheadings:
formatted.append({
'title': subheading.get('title', ''),
'level': subheading.get('level', 2),
'keywords': subheading.get('keywords', []),
'summary': subheading.get('summary', '')
})
return formatted
def _format_key_points(self, points: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
"""Format and validate generated key points."""
formatted = []
for point in points:
formatted.append({
'point': point.get('point', ''),
'importance': point.get('importance', 'medium'),
'supporting_evidence': point.get('evidence', []),
'related_keywords': point.get('keywords', [])
})
return formatted
def _format_content_flow(self, flow: Dict[str, Any]) -> Dict[str, Any]:
"""Format and validate generated content flow."""
return {
'introduction': flow.get('introduction', {}),
'main_sections': flow.get('main_sections', []),
'conclusion': flow.get('conclusion', {}),
'transitions': flow.get('transitions', []),
'content_pacing': flow.get('pacing', {})
}
def generate_ai_suggestions(
self,
content_type: str,
topic: str,
audience: str,
goals: List[str],
tone: str,
length: str,
model_settings: Dict[str, Any],
style_preferences: List[str],
seo_preferences: Dict[str, Any],
platform_settings: Dict[str, Any],
platform: str
) -> List[Dict[str, Any]]:
"""
Generate AI content suggestions based on input parameters.
Args:
content_type: Type of content to generate
topic: Main topic or subject
audience: Target audience
goals: List of content goals
tone: Desired tone
length: Content length
model_settings: AI model settings
style_preferences: Style preferences
seo_preferences: SEO preferences
platform_settings: Platform-specific settings
platform: Target platform
Returns:
List of generated content suggestions
"""
try:
self.logger.info(f"Generating AI suggestions for topic: {topic}")
# Create a comprehensive prompt for content generation
prompt = f"""Generate content suggestions for the following parameters:
Content Type: {content_type}
Topic: {topic}
Target Audience: {audience}
Goals: {', '.join(goals)}
Tone: {tone}
Length: {length}
Style Preferences:
- Creativity Level: {model_settings['Creativity Level']}
- Formality Level: {model_settings['Formality Level']}
- Style Elements: {', '.join(style_preferences)}
SEO Preferences:
- Keyword Density: {seo_preferences['Keyword Density']}%
- Internal Linking: {'Enabled' if seo_preferences['Internal Linking'] else 'Disabled'}
- External Linking: {'Enabled' if seo_preferences['External Linking'] else 'Disabled'}
Platform Settings:
- Platform: {platform}
- Platform-specific requirements: {', '.join(platform_settings)}
Please generate 3 different content suggestions. Format your response as a valid JSON object with the following structure:
{{
"suggestions": [
{{
"title": "string",
"introduction": "string",
"key_points": ["string"],
"main_sections": [
{{
"title": "string",
"content": "string",
"engagement_elements": ["string"],
"seo_elements": ["string"]
}}
],
"conclusion": "string",
"seo_elements": ["string"],
"platform_optimizations": ["string"],
"engagement_strategies": ["string"],
"content_metrics": {{
"estimated_read_time": "string",
"word_count": "number",
"keyword_density": "number",
"engagement_score": "number"
}}
}}
]
}}
IMPORTANT: Your response must be a valid JSON object. Do not include any text before or after the JSON object."""
# Define JSON structure for validation
json_struct = {
"type": "object",
"properties": {
"suggestions": {
"type": "array",
"items": {
"type": "object",
"properties": {
"title": {"type": "string"},
"introduction": {"type": "string"},
"key_points": {"type": "array", "items": {"type": "string"}},
"main_sections": {
"type": "array",
"items": {
"type": "object",
"properties": {
"title": {"type": "string"},
"content": {"type": "string"},
"engagement_elements": {"type": "array", "items": {"type": "string"}},
"seo_elements": {"type": "array", "items": {"type": "string"}}
}
}
},
"conclusion": {"type": "string"},
"seo_elements": {"type": "array", "items": {"type": "string"}},
"platform_optimizations": {"type": "array", "items": {"type": "string"}},
"engagement_strategies": {"type": "array", "items": {"type": "string"}},
"content_metrics": {
"type": "object",
"properties": {
"estimated_read_time": {"type": "string"},
"word_count": {"type": "number"},
"keyword_density": {"type": "number"},
"engagement_score": {"type": "number"}
}
}
}
}
}
}
}
# Generate content using llm_text_gen with JSON structure
generated_content = llm_text_gen(prompt, json_struct=json_struct)
if not generated_content:
raise ValueError("Failed to generate content suggestions")
# Parse the generated content
try:
# If generated_content is already a dict, use it directly
if isinstance(generated_content, dict):
content_data = generated_content
else:
# Try to parse as JSON string
content_data = json.loads(generated_content)
return self._format_suggestions(
content_data,
content_type,
audience,
goals,
tone,
length,
model_settings,
seo_preferences,
platform
)
except json.JSONDecodeError as e:
self.logger.error(f"Error parsing generated content: {str(e)}")
# Try to extract JSON from the response if it's wrapped in other text
try:
# Find the first '{' and last '}'
start = generated_content.find('{')
end = generated_content.rfind('}') + 1
if start >= 0 and end > start:
json_str = generated_content[start:end]
content_data = json.loads(json_str)
return self._format_suggestions(
content_data,
content_type,
audience,
goals,
tone,
length,
model_settings,
seo_preferences,
platform
)
except Exception as e2:
self.logger.error(f"Error extracting JSON from response: {str(e2)}")
raise ValueError("Failed to parse generated content")
except Exception as e:
self.logger.error(f"Error generating AI suggestions: {str(e)}", exc_info=True)
raise
def _format_suggestions(
self,
content_data: Dict[str, Any],
content_type: str,
audience: str,
goals: List[str],
tone: str,
length: str,
model_settings: Dict[str, Any],
seo_preferences: Dict[str, Any],
platform: str
) -> List[Dict[str, Any]]:
"""Format and process suggestions from content data."""
suggestions = []
for suggestion in content_data.get('suggestions', []):
formatted_suggestion = {
'title': suggestion.get('title', ''),
'type': content_type,
'platform': platform,
'audience': audience,
'impact': f"High impact for {', '.join(goals)}",
'preview': suggestion.get('introduction', ''),
'style_elements': [
f"Tone: {tone}",
f"Length: {length}",
f"Creativity: {model_settings['Creativity Level']}",
f"Formality: {model_settings['Formality Level']}"
],
'seo_elements': [
f"Keyword Density: {seo_preferences['Keyword Density']}%",
"Internal Linking: Enabled" if seo_preferences['Internal Linking'] else "Internal Linking: Disabled",
"External Linking: Enabled" if seo_preferences['External Linking'] else "External Linking: Disabled"
],
'engagement_score': f"{85 + len(suggestions)*5}%",
'reach': 'High',
'conversion': f"{3.5 + len(suggestions)*0.5}%",
'seo_impact': 'Strong',
'platform_optimizations': suggestion.get('platform_optimizations', []),
'variations': [
"Alternative headline",
"Different content angle",
"Alternative format"
],
'seo_recommendations': suggestion.get('seo_elements', []),
'media_suggestions': [
"Featured image",
"Supporting graphics",
"Social media visuals"
]
}
suggestions.append(formatted_suggestion)
return suggestions

View File

@@ -0,0 +1,196 @@
from datetime import datetime, timedelta
from typing import Dict, List, Any, Optional
import logging
import sys
import json
import os
from ..integrations.seo_tools import SEOToolsIntegration
from ..integrations.gap_analyzer import GapAnalyzerIntegration
from ..models.calendar import Calendar, ContentItem
from ..utils.date_utils import calculate_publish_dates
from ..utils.error_handling import handle_calendar_error
logging.basicConfig(
level=logging.DEBUG,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.StreamHandler(sys.stdout),
logging.FileHandler('content_calendar_debug.log', mode='a')
]
)
logger = logging.getLogger(__name__)
CALENDAR_JSON_PATH = "calendar_data.json"
class CalendarManager:
"""
Main calendar management system that coordinates content planning,
scheduling, and optimization.
"""
def __init__(self):
"""Initialize calendar manager."""
self.logger = logging.getLogger('content_calendar.manager')
self.logger.info("Initializing CalendarManager")
self.seo_tools = SEOToolsIntegration()
self.gap_analyzer = GapAnalyzerIntegration()
self._calendar: Optional[Calendar] = None
self.logger.info("CalendarManager initialized successfully")
@handle_calendar_error
def create_calendar(
self,
start_date: datetime,
duration: str, # 'weekly', 'monthly', 'quarterly'
platforms: List[str],
website_url: str
) -> Calendar:
"""
Create a new content calendar based on content gap analysis and SEO requirements.
Args:
start_date: When the calendar should begin
duration: How long the calendar should span
platforms: List of platforms to create content for
website_url: URL of the website to analyze
Returns:
Calendar object containing the content schedule
"""
self.logger.info(f"Creating new calendar for {website_url}")
self.logger.debug(f"Parameters: start_date={start_date}, duration={duration}, platforms={platforms}")
try:
# 1. Analyze content gaps
self.logger.info("Analyzing content gaps")
gap_analysis = self.gap_analyzer.analyze_gaps(website_url)
# 2. Generate topics based on gaps
self.logger.info("Generating topics from gap analysis")
topics = self._generate_topics(gap_analysis, platforms)
# 3. Calculate publish dates
self.logger.info("Calculating publish dates")
schedule = calculate_publish_dates(
topics=topics,
start_date=start_date,
duration=duration
)
# 4. Create calendar
self.logger.info("Creating calendar object")
self._calendar = Calendar(
start_date=start_date,
duration=duration,
platforms=platforms,
schedule=schedule
)
self.logger.info("Calendar created successfully")
return self._calendar
except Exception as e:
self.logger.error(f"Error creating calendar: {str(e)}", exc_info=True)
raise
def _generate_topics(
self,
gap_analysis: Dict[str, Any],
platforms: List[str]
) -> List[ContentItem]:
"""
Generate content topics based on gap analysis and platform requirements.
"""
topics = []
for gap in gap_analysis['gaps']:
# Generate topic using AI
topic = self._generate_topic_from_gap(gap, platforms)
# Optimize for SEO
optimized_topic = self._optimize_topic(topic)
topics.append(optimized_topic)
return topics
def _generate_topic_from_gap(
self,
gap: Dict[str, Any],
platforms: List[str]
) -> ContentItem:
"""
Generate a specific topic based on a content gap.
"""
# Use existing AI tools to generate topic
topic_data = {
'title': self._generate_title(gap),
'description': self._generate_description(gap),
'keywords': gap.get('keywords', []),
'platforms': platforms,
'content_type': self._determine_content_type(gap, platforms)
}
return ContentItem(**topic_data)
def _optimize_topic(self, topic: ContentItem) -> ContentItem:
"""
Optimize a topic for SEO using existing tools.
"""
# Optimize title
topic.title = self.seo_tools.optimize_title(topic.title)
# Generate meta description
topic.meta_description = self.seo_tools.generate_meta_description(
topic.description
)
# Add structured data
topic.structured_data = self.seo_tools.generate_structured_data(
topic.content_type
)
return topic
def get_calendar(self) -> Optional[Calendar]:
"""
Get the current calendar.
"""
self.logger.debug("Getting current calendar")
return self._calendar
def update_calendar(self, calendar: Calendar) -> None:
"""
Update the current calendar.
"""
self._calendar = calendar
def export_calendar(self) -> Optional[Dict[str, Any]]:
"""Export the current calendar."""
self.logger.info("Exporting calendar")
if not self._calendar:
self.logger.warning("No calendar to export")
return None
try:
calendar_data = self._calendar.export()
self.logger.info("Calendar exported successfully")
return calendar_data
except Exception as e:
self.logger.error(f"Error exporting calendar: {str(e)}", exc_info=True)
return None
def save_calendar_to_json(self):
calendar = self.get_calendar()
if calendar:
with open(CALENDAR_JSON_PATH, "w") as f:
json.dump(calendar.to_dict(), f, indent=2, default=str)
def load_calendar_from_json(self):
from lib.ai_seo_tools.content_calendar.models.calendar import Calendar
if os.path.exists(CALENDAR_JSON_PATH):
with open(CALENDAR_JSON_PATH, "r") as f:
data = json.load(f)
self._calendar = Calendar.from_dict(data)

View File

@@ -0,0 +1,151 @@
from typing import Dict, List, Any, Optional
import logging
from pathlib import Path
import sys
# Add parent directory to path to import existing tools
parent_dir = str(Path(__file__).parent.parent.parent.parent)
if parent_dir not in sys.path:
sys.path.append(parent_dir)
from lib.ai_seo_tools.content_calendar.models.calendar import ContentType, ContentItem, Platform
from lib.ai_seo_tools.content_calendar.utils.error_handling import handle_calendar_error
from lib.gpt_providers.text_generation.main_text_generation import llm_text_gen
from lib.ai_seo_tools.content_gap_analysis.main import ContentGapAnalysis
from lib.ai_seo_tools.content_title_generator import ai_title_generator
from lib.ai_seo_tools.meta_desc_generator import metadesc_generator_main
from .ai_generator import AIGenerator
logger = logging.getLogger(__name__)
class ContentBriefGenerator:
"""
Generates comprehensive content briefs using AI-powered analysis.
"""
def __init__(self):
self.logger = logging.getLogger('content_calendar.content_brief')
self.logger.info("Initializing ContentBriefGenerator")
self._setup_logging()
self._load_ai_tools()
def _setup_logging(self):
"""Configure logging for content brief generator."""
logger.setLevel(logging.INFO)
handler = logging.StreamHandler()
formatter = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
handler.setFormatter(formatter)
logger.addHandler(handler)
def _load_ai_tools(self):
"""Load and initialize AI tools."""
try:
# Initialize AI tools
self.gap_analyzer = ContentGapAnalysis()
self.title_generator = ai_title_generator
self.meta_generator = metadesc_generator_main
self.ai_generator = AIGenerator()
except Exception as e:
logger.error(f"Error loading AI tools: {str(e)}")
raise
@handle_calendar_error
def generate_brief(
self,
content_item: ContentItem,
target_audience: Optional[Dict[str, Any]] = None
) -> Dict[str, Any]:
"""
Generate a comprehensive content brief.
Args:
content_item: Content item to generate brief for
target_audience: Optional target audience data
Returns:
Dictionary containing the content brief
"""
try:
logger.info(f"Generating content brief for: {content_item.title}")
# Generate content outline
outline = self._generate_outline(content_item)
# Generate key points
key_points = self.ai_generator.generate_key_points(
title=content_item.title,
content_type=content_item.content_type,
context=content_item.context
)
# Generate content flow
content_flow = self.ai_generator.generate_content_flow(
title=content_item.title,
content_type=content_item.content_type,
outline=outline
)
# Compile the brief
brief = {
'title': content_item.title,
'content_type': content_item.content_type.value,
'outline': outline,
'key_points': key_points,
'content_flow': content_flow,
'target_audience': target_audience or {},
'seo_data': content_item.seo_data,
'platform_specs': content_item.platform_specs
}
logger.info("Content brief generated successfully")
return brief
except Exception as e:
logger.error(f"Error generating content brief: {str(e)}")
raise
def _generate_outline(
self,
content_item: ContentItem
) -> Dict[str, Any]:
"""
Generate content outline with headings and subheadings.
Args:
content_item: Content item to generate outline for
Returns:
Dictionary containing the content outline
"""
try:
# Generate main headings
main_headings = self.ai_generator.generate_headings(
title=content_item.title,
content_type=content_item.content_type,
context=content_item.context
)
# Generate subheadings for each main heading
subheadings = {}
for heading in main_headings:
heading_subheadings = self.ai_generator.generate_subheadings(
main_heading=heading,
content_type=content_item.content_type,
context=content_item.context
)
subheadings[heading['title']] = heading_subheadings
return {
'main_headings': main_headings,
'subheadings': subheadings
}
except Exception as e:
logger.error(f"Error generating outline: {str(e)}")
return {
'main_headings': [],
'subheadings': {}
}

View File

@@ -0,0 +1,323 @@
from typing import Dict, List, Any, Optional
import logging
from pathlib import Path
import sys
# Add parent directory to path to import existing tools
parent_dir = str(Path(__file__).parent.parent.parent.parent)
if parent_dir not in sys.path:
sys.path.append(parent_dir)
from ..models.calendar import ContentItem, ContentType
from ..utils.error_handling import handle_calendar_error
from lib.ai_seo_tools.content_gap_analysis.main import ContentGapAnalysis
from lib.ai_seo_tools.content_title_generator import ai_title_generator
from lib.ai_seo_tools.meta_desc_generator import metadesc_generator_main
logger = logging.getLogger(__name__)
class ContentGenerator:
"""
AI-powered content generation for content briefs.
"""
def __init__(self):
self.logger = logging.getLogger('content_calendar.content_generator')
self.logger.info("Initializing ContentGenerator")
self._setup_logging()
self._load_ai_tools()
def _setup_logging(self):
"""Configure logging for content generator."""
logger.setLevel(logging.INFO)
handler = logging.StreamHandler()
formatter = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
handler.setFormatter(formatter)
logger.addHandler(handler)
def _load_ai_tools(self):
"""Load and initialize AI tools."""
try:
# Initialize AI tools
self.gap_analyzer = ContentGapAnalysis()
self.title_generator = ai_title_generator
self.meta_generator = metadesc_generator_main
except Exception as e:
logger.error(f"Error loading AI tools: {str(e)}")
raise
@handle_calendar_error
def generate_headings(
self,
content_item: ContentItem,
context: Dict[str, Any]
) -> List[Dict[str, Any]]:
"""
Generate main headings for content.
Args:
content_item: Content item to generate headings for
context: Content context from gap analysis
Returns:
List of main headings with metadata
"""
try:
# Use AI to generate headings based on content type and context
headings = self._generate_ai_headings(
title=content_item.title,
content_type=content_item.content_type,
context=context
)
# Format and validate headings
formatted_headings = []
for heading in headings:
formatted_heading = {
'title': heading['title'],
'level': heading.get('level', 1),
'keywords': heading.get('keywords', []),
'summary': heading.get('summary', '')
}
formatted_headings.append(formatted_heading)
return formatted_headings
except Exception as e:
logger.error(f"Error generating headings: {str(e)}")
return []
@handle_calendar_error
def generate_subheadings(
self,
content_item: ContentItem,
main_headings: List[Dict[str, Any]],
context: Dict[str, Any]
) -> Dict[str, List[Dict[str, Any]]]:
"""
Generate subheadings for each main heading.
Args:
content_item: Content item to generate subheadings for
main_headings: List of main headings
context: Content context from gap analysis
Returns:
Dictionary mapping main headings to their subheadings
"""
try:
subheadings = {}
for heading in main_headings:
# Generate subheadings for each main heading
heading_subheadings = self._generate_ai_subheadings(
main_heading=heading,
content_type=content_item.content_type,
context=context
)
# Format and validate subheadings
formatted_subheadings = []
for subheading in heading_subheadings:
formatted_subheading = {
'title': subheading['title'],
'level': subheading.get('level', 2),
'keywords': subheading.get('keywords', []),
'summary': subheading.get('summary', '')
}
formatted_subheadings.append(formatted_subheading)
subheadings[heading['title']] = formatted_subheadings
return subheadings
except Exception as e:
logger.error(f"Error generating subheadings: {str(e)}")
return {}
@handle_calendar_error
def generate_key_points(
self,
content_item: ContentItem,
context: Dict[str, Any]
) -> List[Dict[str, Any]]:
"""
Generate key points for the content.
Args:
content_item: Content item to generate key points for
context: Content context from gap analysis
Returns:
List of key points with supporting information
"""
try:
# Generate key points using AI
key_points = self._generate_ai_key_points(
title=content_item.title,
content_type=content_item.content_type,
context=context
)
# Format and validate key points
formatted_points = []
for point in key_points:
formatted_point = {
'point': point['point'],
'importance': point.get('importance', 'medium'),
'supporting_evidence': point.get('evidence', []),
'related_keywords': point.get('keywords', [])
}
formatted_points.append(formatted_point)
return formatted_points
except Exception as e:
logger.error(f"Error generating key points: {str(e)}")
return []
@handle_calendar_error
def generate_content_flow(
self,
content_item: ContentItem,
outline: Dict[str, Any]
) -> Dict[str, Any]:
"""
Generate content flow and structure.
Args:
content_item: Content item to generate flow for
outline: Content outline with headings and key points
Returns:
Dictionary containing content flow and structure
"""
try:
# Generate content flow using AI
flow = self._generate_ai_content_flow(
title=content_item.title,
content_type=content_item.content_type,
outline=outline
)
return {
'introduction': flow.get('introduction', {}),
'main_sections': flow.get('main_sections', []),
'conclusion': flow.get('conclusion', {}),
'transitions': flow.get('transitions', []),
'content_pacing': flow.get('pacing', {})
}
except Exception as e:
logger.error(f"Error generating content flow: {str(e)}")
return {}
def _generate_ai_headings(
self,
title: str,
content_type: ContentType,
context: Dict[str, Any]
) -> List[Dict[str, Any]]:
"""
Use AI to generate content headings.
"""
# TODO: Implement AI heading generation
# This would use the existing AI tools to generate headings
return []
def _generate_ai_subheadings(
self,
main_heading: Dict[str, Any],
content_type: ContentType,
context: Dict[str, Any]
) -> List[Dict[str, Any]]:
"""
Use AI to generate subheadings.
"""
# TODO: Implement AI subheading generation
return []
def _generate_ai_key_points(
self,
title: str,
content_type: ContentType,
context: Dict[str, Any]
) -> List[Dict[str, Any]]:
"""
Use AI to generate key points.
"""
# TODO: Implement AI key point generation
return []
def _generate_ai_content_flow(
self,
title: str,
content_type: ContentType,
outline: Dict[str, Any]
) -> Dict[str, Any]:
"""
Use AI to generate content flow.
"""
# TODO: Implement AI content flow generation
return {}
def generate_variation(self, content: Dict[str, Any], variation_type: str) -> Dict[str, Any]:
"""Generate a variation of the given content.
Args:
content: Original content to vary
variation_type: Type of variation to generate ('tone', 'length', 'style', etc.)
Returns:
Dictionary containing the varied content
"""
try:
self.logger.info(f"Generating {variation_type} variation for content")
# Generate variation based on type
variation = {
'title': f"{content.get('title', '')} - {variation_type.title()} Variation",
'content_flow': {
'introduction': {
'summary': f"Varied introduction for {content.get('title', '')}",
'key_points': [
f"Varied key point 1 for {variation_type}",
f"Varied key point 2 for {variation_type}",
f"Varied key point 3 for {variation_type}"
]
},
'main_content': {
'sections': [
{
'title': f"Varied Section 1: {variation_type.title()} Approach",
'content': f"Varied content for {variation_type}",
'subsections': []
},
{
'title': f"Varied Section 2: {variation_type.title()} Details",
'content': "Varied details and information",
'subsections': []
}
]
},
'conclusion': {
'summary': f"Varied conclusion for {variation_type}",
'call_to_action': "Varied call to action"
}
},
'metadata': {
'variation_type': variation_type,
'original_content': content.get('title', ''),
'platform': content.get('metadata', {}).get('platform', 'Unknown'),
'content_type': content.get('metadata', {}).get('content_type', 'Unknown')
}
}
return variation
except Exception as e:
self.logger.error(f"Error generating variation: {str(e)}")
return {}

View File

@@ -0,0 +1,80 @@
from datetime import datetime
from typing import List, Dict, Any
from ..core.calendar_manager import CalendarManager
from ..models.calendar import ContentType, Platform
def create_content_calendar(
website_url: str,
start_date: datetime,
duration: str,
platforms: List[str]
) -> Dict[str, Any]:
"""
Example of creating a content calendar.
Args:
website_url: URL of the website to analyze
start_date: When to start the calendar
duration: How long the calendar should span
platforms: List of platforms to create content for
Returns:
Dictionary containing the calendar data
"""
# Initialize calendar manager
calendar_manager = CalendarManager()
# Create calendar
calendar = calendar_manager.create_calendar(
start_date=start_date,
duration=duration,
platforms=platforms,
website_url=website_url
)
# Export calendar
calendar_data = calendar_manager.export_calendar()
return calendar_data
def main():
"""Example usage of the content calendar system."""
# Example parameters
website_url = "https://example.com"
start_date = datetime.now()
duration = "monthly"
platforms = [
Platform.WEBSITE.value,
Platform.FACEBOOK.value,
Platform.TWITTER.value,
Platform.LINKEDIN.value
]
try:
# Create calendar
calendar_data = create_content_calendar(
website_url=website_url,
start_date=start_date,
duration=duration,
platforms=platforms
)
# Print calendar summary
print("\nContent Calendar Summary:")
print(f"Duration: {calendar_data['duration']}")
print(f"Platforms: {', '.join(calendar_data['platforms'])}")
print("\nScheduled Content:")
for date, items in calendar_data['schedule'].items():
print(f"\n{date}:")
for item in items:
print(f"- {item['title']} ({item['content_type']})")
print(f" Platforms: {', '.join(item['platforms'])}")
print(f" Status: {item['status']}")
except Exception as e:
print(f"Error creating calendar: {str(e)}")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,138 @@
from datetime import datetime
from typing import Dict, Any
from ..models.calendar import ContentItem, ContentType, Platform, SEOData
from ..core.content_brief import ContentBriefGenerator
def create_content_brief(
title: str,
content_type: ContentType,
platforms: list[Platform],
website_url: str,
target_audience: Dict[str, Any]
) -> Dict[str, Any]:
"""
Create a content brief for the given content.
Args:
title: Content title
content_type: Type of content
platforms: List of platforms to publish on
website_url: Website URL for context
target_audience: Target audience information
Returns:
Dictionary containing the content brief
"""
# Create content item
content_item = ContentItem(
id=f"content-{datetime.now().strftime('%Y%m%d%H%M%S')}",
title=title,
description=f"Content brief for {title}",
content_type=content_type,
platforms=platforms,
publish_date=datetime.now(),
seo_data=SEOData(
keywords=[], # Will be generated by SEO tools
meta_description="", # Will be generated by SEO tools
structured_data={}
),
platform_specs={}, # Will be generated based on platforms
context={
"website_url": website_url,
"target_audience": target_audience.get("demographics", {}).get("profession", ""),
"content_goals": ["educate", "generate leads"]
}
)
# Initialize content brief generator
generator = ContentBriefGenerator()
# Generate brief
brief = generator.generate_brief(
content_item=content_item,
target_audience=target_audience
)
return brief
def main():
"""Example usage of content brief generation."""
# Example content details
title = "10 Ways to Improve Your SEO Strategy"
content_type = ContentType.BLOG_POST
platforms = [Platform.WEBSITE, Platform.LINKEDIN]
website_url = "https://example.com"
# Example target audience
target_audience = {
"demographics": {
"age_range": "25-45",
"profession": "digital marketers",
"experience_level": "intermediate"
},
"interests": [
"SEO",
"content marketing",
"digital strategy",
"search engine optimization"
],
"pain_points": [
"low search rankings",
"poor content performance",
"lack of organic traffic",
"difficulty in keyword research"
],
"goals": [
"improve search rankings",
"increase organic traffic",
"generate more leads",
"build brand authority"
]
}
try:
# Generate content brief
brief = create_content_brief(
title=title,
content_type=content_type,
platforms=platforms,
website_url=website_url,
target_audience=target_audience
)
# Print brief summary
print("\nContent Brief Summary:")
print(f"Title: {brief['title']}")
print(f"Content Type: {brief['content_type']}")
print("\nOutline:")
for heading in brief['outline']['main_headings']:
print(f"\n- {heading['title']}")
print(f" Keywords: {', '.join(heading['keywords'])}")
print(f" Summary: {heading['summary']}")
# Print subheadings
subheadings = brief['outline']['subheadings'].get(heading['title'], [])
for subheading in subheadings:
print(f" - {subheading['title']}")
print(f" Keywords: {', '.join(subheading['keywords'])}")
print("\nKey Points:")
for point in brief['key_points']:
print(f"\n- {point['point']}")
print(f" Importance: {point['importance']}")
print(f" Evidence: {', '.join(point['supporting_evidence'])}")
print("\nContent Flow:")
flow = brief['content_flow']
print(f"Introduction: {flow['introduction'].get('summary', '')}")
print(f"Main Sections: {len(flow['main_sections'])} sections")
print(f"Conclusion: {flow['conclusion'].get('summary', '')}")
print(f"Transitions: {len(flow['transitions'])} transition points")
except Exception as e:
print(f"Error generating content brief: {str(e)}")
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,196 @@
import logging
from datetime import datetime, timedelta
from typing import Dict, Any, List
from ..integrations.integration_manager import IntegrationManager
# Set up logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
def create_cross_platform_content(
title: str,
content: str,
platforms: List[str],
content_type: str,
target_audience: Dict[str, Any],
industry: str,
keywords: List[str]
) -> Dict[str, Any]:
"""Create and optimize content for multiple platforms."""
try:
# Initialize integration manager
integration_manager = IntegrationManager()
# Prepare content item
content_item = {
'title': title,
'content': content,
'content_type': content_type,
'keywords': keywords,
'target_audience': target_audience,
'industry': industry
}
# Get platform suggestions
suggestions = integration_manager.get_platform_suggestions(
content=content_item,
platforms=platforms
)
# Validate content for each platform
validation_results = {}
for platform in platforms:
validation = integration_manager.validate_platform_content(
content=content_item,
platform=platform
)
validation_results[platform] = validation
# Optimize content for each platform
optimized_content = integration_manager.optimize_cross_platform_content(
content=content_item,
platforms=platforms
)
return {
'original_content': content_item,
'platform_suggestions': suggestions,
'validation_results': validation_results,
'optimized_content': optimized_content
}
except Exception as e:
logger.error(f"Error creating cross-platform content: {str(e)}")
return {
'error': str(e)
}
def create_content_calendar(
start_date: datetime,
end_date: datetime,
platforms: List[str],
content_types: List[str],
target_audience: Dict[str, Any],
industry: str,
keywords: List[str]
) -> Dict[str, Any]:
"""Create a cross-platform content calendar."""
try:
# Initialize integration manager
integration_manager = IntegrationManager()
# Create calendar
calendar = integration_manager.create_cross_platform_calendar(
start_date=start_date,
end_date=end_date,
platforms=platforms,
content_types=content_types,
target_audience=target_audience,
industry=industry,
keywords=keywords
)
return calendar
except Exception as e:
logger.error(f"Error creating content calendar: {str(e)}")
return {
'error': str(e)
}
def main():
"""Main function to demonstrate integration manager usage."""
# Example content details
title = "The Future of AI in Content Marketing"
content = """
Artificial Intelligence is revolutionizing the way we approach content marketing.
From automated content generation to personalized recommendations, AI tools are
helping marketers create more engaging and effective content strategies.
Key points:
1. AI-powered content generation
2. Personalized content recommendations
3. Automated content optimization
4. Data-driven content strategy
5. Future trends in AI marketing
"""
# Platform and content settings
platforms = ['instagram', 'twitter', 'linkedin', 'blog', 'facebook']
content_type = 'article'
target_audience = {
'age_range': '25-34',
'interests': ['technology', 'marketing', 'AI'],
'location': 'global',
'profession': 'marketing professionals'
}
industry = 'technology'
keywords = ['AI', 'content marketing', 'automation', 'personalization']
# Create cross-platform content
logger.info("Creating cross-platform content...")
content_result = create_cross_platform_content(
title=title,
content=content,
platforms=platforms,
content_type=content_type,
target_audience=target_audience,
industry=industry,
keywords=keywords
)
# Print content results
logger.info("\nCross-Platform Content Results:")
logger.info("===============================")
# Print platform suggestions
logger.info("\nPlatform Suggestions:")
for platform, suggestions in content_result['platform_suggestions'].items():
logger.info(f"\n{platform.upper()}:")
for key, value in suggestions.items():
logger.info(f" {key}: {value}")
# Print validation results
logger.info("\nValidation Results:")
for platform, validation in content_result['validation_results'].items():
logger.info(f"\n{platform.upper()}:")
logger.info(f" Valid: {validation['is_valid']}")
if not validation['is_valid']:
logger.info(f" Error: {validation.get('error', 'N/A')}")
# Print optimized content
logger.info("\nOptimized Content:")
for platform, optimized in content_result['optimized_content'].items():
logger.info(f"\n{platform.upper()}:")
for key, value in optimized.items():
logger.info(f" {key}: {value}")
# Create content calendar
logger.info("\nCreating content calendar...")
start_date = datetime.now()
end_date = start_date + timedelta(days=30)
calendar_result = create_content_calendar(
start_date=start_date,
end_date=end_date,
platforms=platforms,
content_types=[content_type],
target_audience=target_audience,
industry=industry,
keywords=keywords
)
# Print calendar results
logger.info("\nContent Calendar Results:")
logger.info("========================")
# Print platform calendars
logger.info("\nPlatform Calendars:")
for platform, calendar in calendar_result['platform_calendars'].items():
logger.info(f"\n{platform.upper()}:")
logger.info(f" Content Items: {len(calendar['content_items'])}")
for item in calendar['content_items']:
logger.info(f" - {item['original_item']['title']}")
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,142 @@
from typing import Dict, Any
from datetime import datetime
from ..integrations.platform_adapters import UnifiedPlatformAdapter
def create_platform_content(
title: str,
content: str,
platforms: list,
context: Dict[str, Any] = None
) -> Dict[str, Any]:
"""
Create platform-specific content using the UnifiedPlatformAdapter.
Args:
title: The title of the content
content: The main content to be adapted
platforms: List of platforms to adapt content for
context: Additional context for content adaptation
Returns:
Dict containing adapted content for each platform
"""
# Initialize the platform adapter
adapter = UnifiedPlatformAdapter()
# Prepare base content
base_content = {
'title': title,
'content': content,
'keywords': ['content', 'marketing', 'social media'],
'tone': 'professional',
'cta': 'Learn More',
'audience': 'For All',
'language': 'English',
'industry': 'technology',
'word_count': 1000
}
# Adapt content for each platform
adapted_content = {}
for platform in platforms:
try:
platform_content = adapter.adapt_content(
content=base_content,
platform=platform,
context=context
)
adapted_content[platform] = platform_content
except Exception as e:
print(f"Error adapting content for {platform}: {str(e)}")
adapted_content[platform] = {'error': str(e)}
return adapted_content
def main():
"""Example usage of platform content adaptation."""
# Example content
title = "The Future of AI in Content Marketing"
content = """
Artificial Intelligence is revolutionizing content marketing in unprecedented ways.
From automated content generation to personalized user experiences, AI is becoming
an indispensable tool for marketers. This article explores the latest trends and
innovations in AI-powered content marketing.
"""
# Example context
context = {
'target_audience': 'marketing professionals',
'campaign_goals': ['awareness', 'engagement', 'lead generation'],
'brand_voice': 'authoritative yet approachable',
'content_theme': 'technology and innovation'
}
# Platforms to adapt content for
platforms = ['instagram', 'twitter', 'linkedin', 'blog', 'facebook']
# Generate platform-specific content
adapted_content = create_platform_content(
title=title,
content=content,
platforms=platforms,
context=context
)
# Print results
print("\nPlatform-Specific Content Adaptation Results:")
print("=" * 50)
for platform, content in adapted_content.items():
print(f"\n{platform.upper()} Content:")
print("-" * 30)
if 'error' in content:
print(f"Error: {content['error']}")
continue
# Print platform-specific content
if platform == 'instagram':
print("\nCaptions:")
for caption in content['captions']:
print(f"- {caption}")
print("\nHashtags:")
print(content['hashtags'])
elif platform == 'twitter':
print("\nTweets:")
for tweet in content['tweets']:
print(f"- {tweet}")
print("\nThread Structure:")
print(content['thread_structure'])
elif platform == 'linkedin':
print("\nPost:")
print(content['post'])
print("\nEngagement Optimization:")
print(content['engagement_optimization'])
elif platform == 'blog':
print("\nPost:")
print(content['post'])
print("\nSEO Optimization:")
print(content['seo_optimization'])
elif platform == 'facebook':
print("\nPost:")
print(content['post'])
print("\nEngagement Optimization:")
print(content['engagement_optimization'])
# Print media suggestions
print("\nMedia Suggestions:")
for media in content['media_suggestions']:
print(f"- {media['type']}: {media['description']}")
# Print platform-specific recommendations
print("\nPlatform-Specific Recommendations:")
for key, value in content['platform_specific'].items():
print(f"- {key}: {value}")
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,127 @@
"""
Gap analyzer integration for content calendar.
"""
import streamlit as st
from typing import Dict, Any, List, Optional
from loguru import logger
from lib.utils.website_analyzer.analyzer import WebsiteAnalyzer
from lib.ai_seo_tools.content_gap_analysis.main import ContentGapAnalysis
import asyncio
import sys
import os
import json
from datetime import datetime
# Configure logger for content calendar debugging
logger.remove() # Remove default handler
logger.add(
sys.stdout,
level="DEBUG",
format="<green>{time:YYYY-MM-DD HH:mm:ss}</green> | <level>{level: <8}</level> | <cyan>{name}</cyan> | <yellow>{function}</yellow> | {message}",
filter=lambda record: "content_calendar" in record["name"].lower()
)
class GapAnalyzerIntegration:
"""Integrates content gap analysis with content calendar."""
def __init__(self):
"""Initialize the gap analyzer integration."""
self.gap_analyzer = ContentGapAnalysis()
logger.debug("GapAnalyzerIntegration initialized for content calendar")
def analyze_gaps(self, data: Dict[str, Any]) -> Dict[str, Any]:
"""
Analyze content gaps.
Args:
data: Dictionary containing content data
Returns:
Dictionary containing gap analysis results
"""
try:
logger.debug(f"Starting gap analysis with data: {json.dumps(data, indent=2)}")
# Run gap analysis
results = self.gap_analyzer.analyze(data)
logger.debug(f"Gap analysis completed with results: {json.dumps(results, indent=2)}")
return results
except Exception as e:
error_msg = f"Error analyzing content gaps: {str(e)}"
logger.error(error_msg, exc_info=True)
return {
'error': error_msg,
'gaps': [],
'recommendations': []
}
def get_topic_suggestions(
self,
gap_analysis: Dict[str, Any],
platform: str,
count: int = 5
) -> List[Dict[str, Any]]:
"""
Get topic suggestions for a specific platform based on gap analysis.
Args:
gap_analysis: Results from gap analysis
platform: Target platform for content
count: Number of suggestions to generate
Returns:
List of topic suggestions
"""
try:
logger.debug(f"Generating topic suggestions for platform: {platform}, count: {count}")
suggestions = []
for gap in gap_analysis.get('processed_gaps', []):
# Generate platform-specific topics
platform_topics = self.ai_processor.generate_platform_topics(
gap=gap,
platform=platform,
count=count
)
logger.debug(f"Generated topics for gap: {json.dumps(platform_topics, indent=2)}")
suggestions.extend(platform_topics)
logger.debug(f"Total suggestions generated: {len(suggestions)}")
return suggestions
except Exception as e:
logger.error(f"Error generating topic suggestions: {str(e)}")
return []
def analyze_topic_relevance(
self,
topic: Dict[str, Any],
gap_analysis: Dict[str, Any]
) -> Dict[str, Any]:
"""
Analyze how well a topic addresses content gaps.
Args:
topic: Topic to analyze
gap_analysis: Results from gap analysis
Returns:
Dictionary containing relevance analysis
"""
try:
logger.debug(f"Analyzing topic relevance: {json.dumps(topic, indent=2)}")
relevance = self.ai_processor.analyze_topic_relevance(
topic=topic,
gaps=gap_analysis.get('gaps', [])
)
logger.debug(f"Topic relevance analysis completed: {json.dumps(relevance, indent=2)}")
return relevance
except Exception as e:
logger.error(f"Error analyzing topic relevance: {str(e)}")
return {
'error': str(e),
'score': 0
}

View File

@@ -0,0 +1,196 @@
import logging
from typing import Dict, List, Any, Optional
from datetime import datetime, timedelta
from ..core.calendar_manager import CalendarManager
from ..core.content_brief import ContentBriefGenerator
from .platform_adapters import UnifiedPlatformAdapter
logger = logging.getLogger(__name__)
class IntegrationManager:
"""Manages integration between content calendar and platform adapters."""
def __init__(self):
"""Initialize the integration manager."""
self.calendar_manager = CalendarManager()
self.content_brief_generator = ContentBriefGenerator()
self.platform_adapter = UnifiedPlatformAdapter()
def create_cross_platform_calendar(
self,
start_date: datetime,
end_date: datetime,
platforms: List[str],
content_types: List[str],
target_audience: Optional[Dict[str, Any]] = None,
industry: Optional[str] = None,
keywords: Optional[List[str]] = None
) -> Dict[str, Any]:
"""Create a cross-platform content calendar."""
try:
# Generate base calendar
calendar = self.calendar_manager.create_calendar(
start_date=start_date,
end_date=end_date,
content_types=content_types,
target_audience=target_audience,
industry=industry,
keywords=keywords
)
# Adapt content for each platform
platform_calendars = {}
for platform in platforms:
platform_calendars[platform] = self._adapt_calendar_for_platform(
calendar=calendar,
platform=platform
)
return {
'base_calendar': calendar,
'platform_calendars': platform_calendars,
'metadata': {
'start_date': start_date,
'end_date': end_date,
'platforms': platforms,
'content_types': content_types,
'industry': industry,
'keywords': keywords
}
}
except Exception as e:
logger.error(f"Error creating cross-platform calendar: {str(e)}")
raise
def _adapt_calendar_for_platform(
self,
calendar: Dict[str, Any],
platform: str
) -> Dict[str, Any]:
"""Adapt calendar content for a specific platform."""
try:
adapted_calendar = {
'platform': platform,
'content_items': [],
'metadata': calendar.get('metadata', {})
}
# Adapt each content item
for item in calendar.get('content_items', []):
adapted_item = self._adapt_content_item(item, platform)
if adapted_item:
adapted_calendar['content_items'].append(adapted_item)
return adapted_calendar
except Exception as e:
logger.error(f"Error adapting calendar for platform {platform}: {str(e)}")
return {
'platform': platform,
'content_items': [],
'error': str(e)
}
def _adapt_content_item(
self,
item: Dict[str, Any],
platform: str
) -> Optional[Dict[str, Any]]:
"""Adapt a content item for a specific platform."""
try:
# Generate content brief if not exists
if 'brief' not in item:
item['brief'] = self.content_brief_generator.generate_brief(item)
# Adapt content for platform
adapted_content = self.platform_adapter.adapt_content(
content=item,
platform=platform
)
if adapted_content:
return {
'original_item': item,
'adapted_content': adapted_content,
'platform_specifics': self.platform_adapter.get_platform_specs(platform)
}
return None
except Exception as e:
logger.error(f"Error adapting content item for platform {platform}: {str(e)}")
return None
def get_platform_suggestions(
self,
content: Dict[str, Any],
platforms: List[str]
) -> Dict[str, Any]:
"""Get platform-specific suggestions for content."""
try:
suggestions = {}
for platform in platforms:
platform_suggestions = self.platform_adapter.get_platform_suggestions(
content=content,
platform=platform
)
if platform_suggestions:
suggestions[platform] = platform_suggestions
return suggestions
except Exception as e:
logger.error(f"Error getting platform suggestions: {str(e)}")
return {}
def validate_platform_content(
self,
content: Dict[str, Any],
platform: str
) -> Dict[str, Any]:
"""Validate content for a specific platform."""
try:
validation_result = self.platform_adapter.validate_content(
content=content,
platform=platform
)
return {
'platform': platform,
'is_valid': validation_result,
'specifications': self.platform_adapter.get_platform_specs(platform)
}
except Exception as e:
logger.error(f"Error validating platform content: {str(e)}")
return {
'platform': platform,
'is_valid': False,
'error': str(e)
}
def optimize_cross_platform_content(
self,
content: Dict[str, Any],
platforms: List[str]
) -> Dict[str, Any]:
"""Optimize content for multiple platforms."""
try:
optimized_content = {}
for platform in platforms:
platform_optimized = self.platform_adapter.optimize_content(
content=content,
platform=platform
)
if platform_optimized:
optimized_content[platform] = platform_optimized
return optimized_content
except Exception as e:
logger.error(f"Error optimizing cross-platform content: {str(e)}")
return {}

View File

@@ -0,0 +1,137 @@
"""
Platform adapters for content calendar.
"""
import streamlit as st
from typing import Dict, Any, List, Optional
from loguru import logger
from lib.utils.website_analyzer.analyzer import WebsiteAnalyzer
from lib.ai_seo_tools.content_gap_analysis.main import ContentGapAnalysis
from lib.ai_seo_tools.content_title_generator import ai_title_generator
from lib.ai_seo_tools.meta_desc_generator import metadesc_generator_main
from lib.ai_seo_tools.seo_structured_data import ai_structured_data
import asyncio
import sys
import os
import json
# Configure logger
logger.remove() # Remove default handler
logger.add(
"logs/platform_adapters.log",
rotation="50 MB",
retention="10 days",
level="DEBUG",
format="{time:YYYY-MM-DD HH:mm:ss} | {level} | {message}"
)
logger.add(
sys.stdout,
level="INFO",
format="<green>{time:YYYY-MM-DD HH:mm:ss}</green> | <level>{level: <8}</level> | <cyan>{message}</cyan>"
)
# Ensure logs directory exists
os.makedirs("logs", exist_ok=True)
class UnifiedPlatformAdapter:
"""Unified adapter for different social media platforms."""
def __init__(self):
"""Initialize the platform adapter."""
self.platform_handlers = {
'instagram': self._handle_instagram,
'linkedin': self._handle_linkedin,
'twitter': self._handle_twitter,
'facebook': self._handle_facebook
}
logger.info("UnifiedPlatformAdapter initialized")
def generate_content(self, platform: str, data: Dict[str, Any]) -> Dict[str, Any]:
"""
Generate content for a specific platform.
Args:
platform: Target platform
data: Content data
Returns:
Dictionary containing generated content
"""
try:
handler = self.platform_handlers.get(platform.lower())
if not handler:
raise ValueError(f"Unsupported platform: {platform}")
return handler(data)
except Exception as e:
error_msg = f"Error generating content for {platform}: {str(e)}"
logger.error(error_msg, exc_info=True)
return {
'error': error_msg,
'content': None
}
def _handle_instagram(self, data: Dict[str, Any]) -> Dict[str, Any]:
"""Handle Instagram content generation."""
try:
# Use content title generator for Instagram captions
caption = ai_title_generator(data)
return {
'platform': 'instagram',
'content': caption
}
except Exception as e:
logger.error(f"Error generating Instagram content: {str(e)}")
return {
'platform': 'instagram',
'error': str(e)
}
def _handle_linkedin(self, data: Dict[str, Any]) -> Dict[str, Any]:
"""Handle LinkedIn content generation."""
try:
# Use meta description generator for LinkedIn posts
post = metadesc_generator_main(data)
return {
'platform': 'linkedin',
'content': post
}
except Exception as e:
logger.error(f"Error generating LinkedIn content: {str(e)}")
return {
'platform': 'linkedin',
'error': str(e)
}
def _handle_twitter(self, data: Dict[str, Any]) -> Dict[str, Any]:
"""Handle Twitter content generation."""
try:
# Use content title generator for tweets
tweet = ai_title_generator(data)
return {
'platform': 'twitter',
'content': tweet
}
except Exception as e:
logger.error(f"Error generating Twitter content: {str(e)}")
return {
'platform': 'twitter',
'error': str(e)
}
def _handle_facebook(self, data: Dict[str, Any]) -> Dict[str, Any]:
"""Handle Facebook content generation."""
try:
# Use meta description generator for Facebook posts
post = metadesc_generator_main(data)
return {
'platform': 'facebook',
'content': post
}
except Exception as e:
logger.error(f"Error generating Facebook content: {str(e)}")
return {
'platform': 'facebook',
'error': str(e)
}

View File

@@ -0,0 +1,219 @@
import logging
from typing import Dict, Any, List, Optional
from datetime import datetime
from ...meta_desc_generator import generate_blog_metadesc
from ...content_title_generator import generate_blog_titles
from ...seo_structured_data import generate_json_data
logger = logging.getLogger(__name__)
class SEOOptimizer:
"""Integrates SEO tools with content calendar system."""
def __init__(self):
"""Initialize the SEO optimizer."""
self._setup_logging()
def _setup_logging(self):
"""Configure logging for SEO optimizer."""
logger.setLevel(logging.INFO)
handler = logging.StreamHandler()
formatter = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
handler.setFormatter(formatter)
logger.addHandler(handler)
def optimize_content(
self,
content: Dict[str, Any],
content_type: str = 'article',
language: str = 'English',
search_intent: str = 'Informational Intent'
) -> Dict[str, Any]:
"""
Optimize content for SEO using existing tools.
Args:
content: Content to optimize
content_type: Type of content (article, product, etc.)
language: Content language
search_intent: Search intent type
Returns:
Optimized content with SEO elements
"""
try:
# Extract content details
title = content.get('title', '')
keywords = content.get('keywords', [])
content_text = content.get('content', '')
# Generate SEO elements
optimized_title = self._optimize_title(
title=title,
keywords=keywords,
content_type=content_type,
language=language,
search_intent=search_intent
)
meta_description = self._generate_meta_description(
keywords=keywords,
content_type=content_type,
language=language,
search_intent=search_intent
)
structured_data = self._generate_structured_data(
content=content,
content_type=content_type
)
return {
'original_content': content,
'seo_optimized': {
'title': optimized_title,
'meta_description': meta_description,
'structured_data': structured_data,
'keywords': keywords,
'content_type': content_type,
'language': language,
'search_intent': search_intent
}
}
except Exception as e:
logger.error(f"Error optimizing content: {str(e)}")
return {
'error': str(e)
}
def _optimize_title(
self,
title: str,
keywords: List[str],
content_type: str,
language: str,
search_intent: str
) -> List[str]:
"""Generate SEO-optimized titles."""
try:
# Convert keywords list to comma-separated string
keywords_str = ', '.join(keywords)
# Generate titles using existing tool
titles = generate_blog_titles(
input_blog_keywords=keywords_str,
input_blog_content=title,
input_title_type=content_type,
input_title_intent=search_intent,
input_language=language
)
return titles.split('\n') if titles else []
except Exception as e:
logger.error(f"Error optimizing title: {str(e)}")
return []
def _generate_meta_description(
self,
keywords: List[str],
content_type: str,
language: str,
search_intent: str
) -> List[str]:
"""Generate SEO-optimized meta descriptions."""
try:
# Convert keywords list to comma-separated string
keywords_str = ', '.join(keywords)
# Generate meta descriptions using existing tool
descriptions = generate_blog_metadesc(
keywords=keywords_str,
tone='Informative',
search_type=search_intent,
language=language
)
return descriptions.split('\n') if descriptions else []
except Exception as e:
logger.error(f"Error generating meta description: {str(e)}")
return []
def _generate_structured_data(
self,
content: Dict[str, Any],
content_type: str
) -> Optional[Dict[str, Any]]:
"""Generate structured data for content."""
try:
# Prepare content details for structured data
details = {
'Headline': content.get('title', ''),
'Author': content.get('author', ''),
'Date Published': content.get('publish_date', datetime.now().isoformat()),
'Keywords': ', '.join(content.get('keywords', [])),
'Description': content.get('description', ''),
'Image URL': content.get('image_url', '')
}
# Generate structured data using existing tool
structured_data = generate_json_data(
content_type=content_type,
details=details,
url=content.get('url', '')
)
return structured_data
except Exception as e:
logger.error(f"Error generating structured data: {str(e)}")
return None
def optimize_calendar_content(
self,
calendar: Dict[str, Any],
content_type: str = 'article',
language: str = 'English',
search_intent: str = 'Informational Intent'
) -> Dict[str, Any]:
"""
Optimize all content in calendar for SEO.
Args:
calendar: Content calendar to optimize
content_type: Type of content
language: Content language
search_intent: Search intent type
Returns:
Calendar with SEO-optimized content
"""
try:
optimized_calendar = {
'metadata': calendar.get('metadata', {}),
'content_items': []
}
# Optimize each content item
for item in calendar.get('content_items', []):
optimized_item = self.optimize_content(
content=item,
content_type=content_type,
language=language,
search_intent=search_intent
)
if optimized_item:
optimized_calendar['content_items'].append(optimized_item)
return optimized_calendar
except Exception as e:
logger.error(f"Error optimizing calendar content: {str(e)}")
return {
'error': str(e)
}

View File

@@ -0,0 +1,143 @@
"""SEO tools integration for content calendar."""
import streamlit as st
from loguru import logger
from typing import Dict, Any, List, Optional
import asyncio
import sys
import os
from lib.ai_seo_tools.content_title_generator import ai_title_generator
from lib.utils.website_analyzer.analyzer import WebsiteAnalyzer
from lib.gpt_providers.text_generation.main_text_generation import llm_text_gen
# Configure logger
logger.remove() # Remove default handler
logger.add(
"logs/seo_tools_integration.log",
rotation="50 MB",
retention="10 days",
level="DEBUG",
format="{time:YYYY-MM-DD HH:mm:ss} | {level} | {message}"
)
logger.add(
sys.stdout,
level="INFO",
format="<green>{time:YYYY-MM-DD HH:mm:ss}</green> | <level>{level: <8}</level> | <cyan>{message}</cyan>"
)
# Ensure logs directory exists
os.makedirs("logs", exist_ok=True)
class SEOToolsIntegration:
"""Integration with SEO tools for content calendar."""
def __init__(self):
"""Initialize the SEO tools integration."""
self.website_analyzer = WebsiteAnalyzer()
logger.info("SEOToolsIntegration initialized")
def analyze_content(self, url: str) -> Dict[str, Any]:
"""
Analyze content for SEO optimization.
Args:
url: The URL to analyze
Returns:
Dictionary containing SEO analysis results
"""
try:
# Analyze website
analysis = self.website_analyzer.analyze_website(url)
if not analysis.get('success', False):
return {
'error': analysis.get('error', 'Unknown error in analysis'),
'seo_score': 0,
'recommendations': []
}
# Extract SEO information
seo_info = analysis['data']['analysis']['seo_info']
return {
'seo_score': seo_info.get('overall_score', 0),
'meta_tags': seo_info.get('meta_tags', {}),
'content': seo_info.get('content', {}),
'recommendations': seo_info.get('recommendations', [])
}
except Exception as e:
error_msg = f"Error analyzing content: {str(e)}"
logger.error(error_msg, exc_info=True)
return {
'error': error_msg,
'seo_score': 0,
'recommendations': []
}
def generate_title(self, url: str) -> Dict[str, Any]:
"""
Generate SEO-optimized title.
Args:
url: The URL to analyze
Returns:
Dictionary containing title suggestions
"""
return ai_title_generator(url)
def optimize_content(self, content: str, keywords: List[str]) -> Dict[str, Any]:
"""
Optimize content for SEO.
Args:
content: The content to optimize
keywords: List of target keywords
Returns:
Dictionary containing optimization suggestions
"""
try:
# Prepare prompt for content optimization
prompt = f"""Optimize the following content for SEO:
Content: {content}
Target Keywords: {', '.join(keywords)}
Provide optimization suggestions for:
1. Keyword usage and placement
2. Content structure and readability
3. Meta information
4. Internal linking opportunities
5. Content length and depth
Format the response as JSON with 'suggestions' and 'score' keys."""
# Get AI optimization suggestions
suggestions = llm_text_gen(
prompt=prompt,
system_prompt="You are an SEO expert specializing in content optimization.",
response_format="json_object"
)
if not suggestions:
return {
'error': 'Failed to generate optimization suggestions',
'suggestions': [],
'score': 0
}
return {
'suggestions': suggestions.get('suggestions', []),
'score': suggestions.get('score', 0)
}
except Exception as e:
error_msg = f"Error optimizing content: {str(e)}"
logger.error(error_msg, exc_info=True)
return {
'error': error_msg,
'suggestions': [],
'score': 0
}

View File

@@ -0,0 +1,237 @@
import logging
import sys
logging.basicConfig(
level=logging.DEBUG,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.StreamHandler(sys.stdout),
logging.FileHandler('content_calendar_debug.log', mode='a')
]
)
logger = logging.getLogger(__name__)
from datetime import datetime
from typing import Dict, List, Any, Optional
from dataclasses import dataclass, field
from enum import Enum
import pandas as pd
class ContentType(Enum):
"""Types of content that can be scheduled."""
BLOG_POST = "blog_post"
SOCIAL_MEDIA = "social_media"
VIDEO = "video"
PODCAST = "podcast"
NEWSLETTER = "newsletter"
LANDING_PAGE = "landing_page"
class Platform(Enum):
"""Supported content platforms."""
WEBSITE = "website"
FACEBOOK = "facebook"
TWITTER = "twitter"
LINKEDIN = "linkedin"
INSTAGRAM = "instagram"
YOUTUBE = "youtube"
MEDIUM = "medium"
@dataclass
class SEOData:
"""SEO-related data for content."""
title: str
meta_description: str
keywords: List[str]
structured_data: Dict[str, Any]
canonical_url: Optional[str] = None
og_tags: Optional[Dict[str, str]] = None
twitter_cards: Optional[Dict[str, str]] = None
@staticmethod
def from_dict(data):
return SEOData(
title=data.get('title', ''),
meta_description=data.get('meta_description', ''),
keywords=data.get('keywords', []),
structured_data=data.get('structured_data', {}),
canonical_url=data.get('canonical_url'),
og_tags=data.get('og_tags'),
twitter_cards=data.get('twitter_cards')
)
@dataclass
class ContentItem:
"""Represents a single content item in the calendar."""
title: str
description: str
content_type: ContentType
platforms: List[Platform]
publish_date: datetime
seo_data: SEOData
status: str = "draft"
author: Optional[str] = None
tags: List[str] = field(default_factory=list)
notes: Optional[str] = None
def to_dict(self) -> Dict[str, Any]:
"""Convert content item to dictionary."""
return {
'title': self.title,
'description': self.description,
'content_type': self.content_type.value,
'platforms': [p.value for p in self.platforms],
'publish_date': self.publish_date.isoformat(),
'seo_data': {
'title': self.seo_data.title,
'meta_description': self.seo_data.meta_description,
'keywords': self.seo_data.keywords,
'structured_data': self.seo_data.structured_data,
'canonical_url': self.seo_data.canonical_url,
'og_tags': self.seo_data.og_tags,
'twitter_cards': self.seo_data.twitter_cards
},
'status': self.status,
'author': self.author,
'tags': self.tags,
'notes': self.notes
}
@staticmethod
def from_dict(data):
from .calendar import ContentType, Platform, SEOData
return ContentItem(
title=data['title'],
description=data.get('description', ''),
content_type=ContentType(data['content_type']),
platforms=[Platform(p) for p in data['platforms']],
publish_date=pd.to_datetime(data['publish_date']),
seo_data=SEOData.from_dict(data.get('seo_data', {})),
status=data.get('status', 'draft'),
author=data.get('author'),
tags=data.get('tags', []),
notes=data.get('notes')
)
@dataclass
class Calendar:
"""Represents a content calendar."""
start_date: datetime
duration: str # 'weekly', 'monthly', 'quarterly'
platforms: List[Platform]
schedule: Dict[str, List[ContentItem]]
name: Optional[str] = None
description: Optional[str] = None
def __init__(self, start_date: datetime, duration: str, platforms: List[Platform],
schedule: Dict[str, List[ContentItem]], name: Optional[str] = None,
description: Optional[str] = None):
"""Initialize a new calendar.
Args:
start_date: Start date of the calendar
duration: Duration of the calendar ('weekly', 'monthly', 'quarterly')
platforms: List of platforms to schedule content for
schedule: Dictionary mapping dates to content items
name: Optional name for the calendar
description: Optional description of the calendar
"""
self.start_date = start_date
self.duration = duration
self.platforms = platforms
self.schedule = schedule
self.name = name
self.description = description
self.content_items: List[ContentItem] = []
self.logger = logging.getLogger('content_calendar.calendar')
# Initialize content_items from schedule
for items in self.schedule.values():
self.content_items.extend(items)
def get_all_content(self) -> List[ContentItem]:
"""Get all content items in the calendar.
Returns:
List of all ContentItem objects in the calendar
"""
try:
self.logger.debug(f"Getting all content items. Count: {len(self.content_items)}")
return self.content_items
except Exception as e:
self.logger.error(f"Error getting all content: {str(e)}")
return []
def to_dict(self) -> Dict[str, Any]:
"""Convert calendar to dictionary."""
return {
'name': self.name,
'description': self.description,
'start_date': self.start_date.isoformat(),
'duration': self.duration,
'platforms': [p.value for p in self.platforms],
'schedule': {
date: [item.to_dict() for item in items]
for date, items in self.schedule.items()
}
}
def export(self, format: str = 'json') -> Dict[str, Any]:
"""
Export calendar in specified format.
Currently only supports JSON format.
"""
if format.lower() != 'json':
raise ValueError(f"Unsupported export format: {format}")
return self.to_dict()
def get_content_for_date(self, date: datetime) -> List[ContentItem]:
"""Get all content items scheduled for a specific date."""
date_str = date.strftime('%Y-%m-%d')
return self.schedule.get(date_str, [])
def get_content_for_platform(
self,
platform: Platform
) -> List[ContentItem]:
"""Get all content items for a specific platform."""
all_content = []
for items in self.schedule.values():
platform_content = [
item for item in items
if platform in item.platforms
]
all_content.extend(platform_content)
return all_content
def add_content(self, content: ContentItem) -> None:
"""Add a new content item to the calendar."""
date_str = content.publish_date.strftime('%Y-%m-%d')
if date_str not in self.schedule:
self.schedule[date_str] = []
self.schedule[date_str].append(content)
def remove_content(self, content: ContentItem) -> None:
"""Remove a content item from the calendar."""
date_str = content.publish_date.strftime('%Y-%m-%d')
if date_str in self.schedule:
self.schedule[date_str] = [
item for item in self.schedule[date_str]
if item != content
]
@staticmethod
def from_dict(data):
from .calendar import ContentItem, Platform
schedule = {
date: [ContentItem.from_dict(item) for item in items]
for date, items in data.get('schedule', {}).items()
}
return Calendar(
start_date=pd.to_datetime(data['start_date']),
duration=data['duration'],
platforms=[Platform(p) for p in data['platforms']],
schedule=schedule,
name=data.get('name'),
description=data.get('description')
)

View File

@@ -0,0 +1,185 @@
import unittest
from typing import Dict, Any
from ..models.calendar import ContentType
from ..core.ai_generator import AIContentGenerator
class TestAIContentGenerator(unittest.TestCase):
"""Test cases for AIContentGenerator."""
def setUp(self):
"""Set up test cases."""
self.generator = AIContentGenerator()
self.test_title = "10 Ways to Improve Your SEO Strategy"
self.test_content_type = ContentType.BLOG_POST
self.test_context = {
"website_url": "https://example.com",
"target_audience": "digital marketers",
"content_goals": ["educate", "generate leads"]
}
def test_generate_headings(self):
"""Test heading generation."""
headings = self.generator.generate_headings(
title=self.test_title,
content_type=self.test_content_type,
context=self.test_context
)
self.assertIsInstance(headings, list)
for heading in headings:
self.assertIn('title', heading)
self.assertIn('level', heading)
self.assertIn('keywords', heading)
self.assertIn('summary', heading)
# Verify heading level
self.assertEqual(heading['level'], 1)
# Verify heading content
self.assertIsInstance(heading['title'], str)
self.assertIsInstance(heading['keywords'], list)
self.assertIsInstance(heading['summary'], str)
def test_generate_subheadings(self):
"""Test subheading generation."""
main_heading = {
'title': 'Understanding SEO Basics',
'level': 1,
'keywords': ['SEO', 'basics', 'fundamentals'],
'summary': 'Introduction to core SEO concepts'
}
subheadings = self.generator.generate_subheadings(
main_heading=main_heading,
content_type=self.test_content_type,
context=self.test_context
)
self.assertIsInstance(subheadings, list)
for subheading in subheadings:
self.assertIn('title', subheading)
self.assertIn('level', subheading)
self.assertIn('keywords', subheading)
self.assertIn('summary', subheading)
# Verify subheading level
self.assertEqual(subheading['level'], 2)
# Verify subheading content
self.assertIsInstance(subheading['title'], str)
self.assertIsInstance(subheading['keywords'], list)
self.assertIsInstance(subheading['summary'], str)
def test_generate_key_points(self):
"""Test key points generation."""
key_points = self.generator.generate_key_points(
title=self.test_title,
content_type=self.test_content_type,
context=self.test_context
)
self.assertIsInstance(key_points, list)
for point in key_points:
self.assertIn('point', point)
self.assertIn('importance', point)
self.assertIn('supporting_evidence', point)
self.assertIn('related_keywords', point)
# Verify point content
self.assertIsInstance(point['point'], str)
self.assertIn(point['importance'], ['high', 'medium', 'low'])
self.assertIsInstance(point['supporting_evidence'], list)
self.assertIsInstance(point['related_keywords'], list)
def test_generate_content_flow(self):
"""Test content flow generation."""
outline = {
'main_headings': [
{
'title': 'Introduction',
'level': 1,
'keywords': ['SEO', 'introduction'],
'summary': 'Overview of SEO importance'
}
],
'subheadings': {
'Introduction': [
{
'title': 'What is SEO?',
'level': 2,
'keywords': ['definition', 'basics'],
'summary': 'Basic definition of SEO'
}
]
}
}
flow = self.generator.generate_content_flow(
title=self.test_title,
content_type=self.test_content_type,
outline=outline
)
self.assertIsInstance(flow, dict)
self.assertIn('introduction', flow)
self.assertIn('main_sections', flow)
self.assertIn('conclusion', flow)
self.assertIn('transitions', flow)
self.assertIn('content_pacing', flow)
# Verify flow content
self.assertIsInstance(flow['introduction'], dict)
self.assertIsInstance(flow['main_sections'], list)
self.assertIsInstance(flow['conclusion'], dict)
self.assertIsInstance(flow['transitions'], list)
self.assertIsInstance(flow['content_pacing'], dict)
def test_prompt_creation(self):
"""Test prompt creation methods."""
# Test heading prompt
heading_prompt = self.generator._create_heading_prompt(
title=self.test_title,
content_type=self.test_content_type,
gaps={'opportunities': ['keyword research', 'content optimization']}
)
self.assertIsInstance(heading_prompt, str)
self.assertIn(self.test_title, heading_prompt)
self.assertIn(self.test_content_type.value, heading_prompt)
# Test subheading prompt
main_heading = {
'title': 'Understanding SEO Basics',
'level': 1,
'keywords': ['SEO', 'basics'],
'summary': 'Introduction to SEO'
}
subheading_prompt = self.generator._create_subheading_prompt(
main_heading=main_heading,
content_type=self.test_content_type,
context=self.test_context
)
self.assertIsInstance(subheading_prompt, str)
self.assertIn(main_heading['title'], subheading_prompt)
# Test key points prompt
key_points_prompt = self.generator._create_key_points_prompt(
title=self.test_title,
content_type=self.test_content_type,
seo_data={'keywords': ['SEO', 'strategy']},
context=self.test_context
)
self.assertIsInstance(key_points_prompt, str)
self.assertIn(self.test_title, key_points_prompt)
# Test flow prompt
flow_prompt = self.generator._create_flow_prompt(
title=self.test_title,
content_type=self.test_content_type,
outline={'main_headings': []}
)
self.assertIsInstance(flow_prompt, str)
self.assertIn(self.test_title, flow_prompt)
if __name__ == '__main__':
unittest.main()

View File

@@ -0,0 +1,132 @@
import unittest
from datetime import datetime
from typing import Dict, Any
from ..models.calendar import ContentItem, ContentType, Platform, SEOData
from ..core.content_brief import ContentBriefGenerator
class TestContentBriefGenerator(unittest.TestCase):
"""Test cases for ContentBriefGenerator."""
def setUp(self):
"""Set up test cases."""
self.generator = ContentBriefGenerator()
self.test_content_item = self._create_test_content_item()
def _create_test_content_item(self) -> ContentItem:
"""Create a test content item."""
return ContentItem(
id="test-001",
title="10 Ways to Improve Your SEO Strategy",
description="A comprehensive guide to enhancing your website's SEO performance",
content_type=ContentType.BLOG_POST,
platforms=[Platform.WEBSITE, Platform.LINKEDIN],
publish_date=datetime.now(),
seo_data=SEOData(
keywords=["SEO", "search engine optimization", "digital marketing"],
meta_description="Learn effective SEO strategies to boost your website's visibility",
structured_data={}
),
platform_specs={
"website": {
"format": "blog post",
"min_length": 1500
},
"linkedin": {
"format": "article",
"min_length": 800
}
},
context={
"website_url": "https://example.com",
"target_audience": "digital marketers",
"content_goals": ["educate", "generate leads"]
}
)
def test_generate_brief(self):
"""Test content brief generation."""
# Generate brief
brief = self.generator.generate_brief(
content_item=self.test_content_item,
target_audience={
"demographics": {
"age_range": "25-45",
"profession": "digital marketers"
},
"interests": ["SEO", "content marketing", "digital strategy"],
"pain_points": [
"low search rankings",
"poor content performance",
"lack of organic traffic"
]
}
)
# Verify brief structure
self.assertIsInstance(brief, dict)
self.assertIn('title', brief)
self.assertIn('content_type', brief)
self.assertIn('outline', brief)
self.assertIn('key_points', brief)
self.assertIn('content_flow', brief)
self.assertIn('target_audience', brief)
self.assertIn('seo_data', brief)
self.assertIn('platform_specs', brief)
# Verify outline structure
outline = brief['outline']
self.assertIn('main_headings', outline)
self.assertIn('subheadings', outline)
# Verify key points
self.assertIsInstance(brief['key_points'], list)
# Verify content flow
flow = brief['content_flow']
self.assertIn('introduction', flow)
self.assertIn('main_sections', flow)
self.assertIn('conclusion', flow)
self.assertIn('transitions', flow)
self.assertIn('content_pacing', flow)
def test_generate_brief_without_audience(self):
"""Test content brief generation without target audience data."""
brief = self.generator.generate_brief(
content_item=self.test_content_item
)
self.assertIsInstance(brief, dict)
self.assertIn('target_audience', brief)
self.assertEqual(brief['target_audience'], {})
def test_generate_outline(self):
"""Test outline generation."""
outline = self.generator._generate_outline(self.test_content_item)
self.assertIsInstance(outline, dict)
self.assertIn('main_headings', outline)
self.assertIn('subheadings', outline)
# Verify main headings
main_headings = outline['main_headings']
self.assertIsInstance(main_headings, list)
for heading in main_headings:
self.assertIn('title', heading)
self.assertIn('level', heading)
self.assertIn('keywords', heading)
self.assertIn('summary', heading)
# Verify subheadings
subheadings = outline['subheadings']
self.assertIsInstance(subheadings, dict)
for heading_title, heading_subheadings in subheadings.items():
self.assertIsInstance(heading_subheadings, list)
for subheading in heading_subheadings:
self.assertIn('title', subheading)
self.assertIn('level', subheading)
self.assertIn('keywords', subheading)
self.assertIn('summary', subheading)
if __name__ == '__main__':
unittest.main()

View File

@@ -0,0 +1,171 @@
import unittest
from datetime import datetime, timedelta
from typing import Dict, Any
from ..integrations.integration_manager import IntegrationManager
class TestIntegrationManager(unittest.TestCase):
"""Test cases for the IntegrationManager class."""
def setUp(self):
"""Set up test fixtures."""
self.integration_manager = IntegrationManager()
self.start_date = datetime.now()
self.end_date = self.start_date + timedelta(days=30)
self.platforms = ['instagram', 'twitter', 'linkedin', 'blog', 'facebook']
self.content_types = ['article', 'social', 'video']
self.target_audience = {
'age_range': '25-34',
'interests': ['technology', 'marketing'],
'location': 'global'
}
self.industry = 'technology'
self.keywords = ['AI', 'content marketing', 'social media']
# Sample content item
self.sample_content = {
'title': 'The Future of AI in Content Marketing',
'content': 'AI is revolutionizing content marketing...',
'content_type': 'article',
'keywords': ['AI', 'content marketing', 'automation'],
'target_audience': self.target_audience,
'industry': self.industry
}
def test_create_cross_platform_calendar(self):
"""Test creating a cross-platform content calendar."""
calendar = self.integration_manager.create_cross_platform_calendar(
start_date=self.start_date,
end_date=self.end_date,
platforms=self.platforms,
content_types=self.content_types,
target_audience=self.target_audience,
industry=self.industry,
keywords=self.keywords
)
# Check basic structure
self.assertIn('base_calendar', calendar)
self.assertIn('platform_calendars', calendar)
self.assertIn('metadata', calendar)
# Check platform calendars
platform_calendars = calendar['platform_calendars']
self.assertEqual(len(platform_calendars), len(self.platforms))
for platform in self.platforms:
self.assertIn(platform, platform_calendars)
platform_calendar = platform_calendars[platform]
self.assertIn('content_items', platform_calendar)
self.assertIn('metadata', platform_calendar)
def test_adapt_calendar_for_platform(self):
"""Test adapting calendar for a specific platform."""
# Create base calendar
calendar = self.integration_manager.create_cross_platform_calendar(
start_date=self.start_date,
end_date=self.end_date,
platforms=[self.platforms[0]], # Test with just Instagram
content_types=self.content_types,
target_audience=self.target_audience,
industry=self.industry,
keywords=self.keywords
)
# Get platform calendar
platform_calendar = calendar['platform_calendars'][self.platforms[0]]
# Check structure
self.assertIn('content_items', platform_calendar)
self.assertIn('metadata', platform_calendar)
# Check content items
for item in platform_calendar['content_items']:
self.assertIn('original_item', item)
self.assertIn('adapted_content', item)
self.assertIn('platform_specifics', item)
def test_adapt_content_item(self):
"""Test adapting a content item for a platform."""
adapted_item = self.integration_manager._adapt_content_item(
item=self.sample_content,
platform='instagram'
)
# Check structure
self.assertIsNotNone(adapted_item)
self.assertIn('original_item', adapted_item)
self.assertIn('adapted_content', adapted_item)
self.assertIn('platform_specifics', adapted_item)
# Check content adaptation
adapted_content = adapted_item['adapted_content']
self.assertIn('captions', adapted_content)
self.assertIn('hashtags', adapted_content)
self.assertIn('media_suggestions', adapted_content)
def test_get_platform_suggestions(self):
"""Test getting platform-specific suggestions."""
suggestions = self.integration_manager.get_platform_suggestions(
content=self.sample_content,
platforms=self.platforms
)
# Check structure
self.assertEqual(len(suggestions), len(self.platforms))
for platform in self.platforms:
self.assertIn(platform, suggestions)
platform_suggestions = suggestions[platform]
self.assertIsInstance(platform_suggestions, dict)
def test_validate_platform_content(self):
"""Test validating content for a platform."""
validation = self.integration_manager.validate_platform_content(
content=self.sample_content,
platform='instagram'
)
# Check structure
self.assertIn('platform', validation)
self.assertIn('is_valid', validation)
self.assertIn('specifications', validation)
# Check validation result
self.assertIsInstance(validation['is_valid'], bool)
def test_optimize_cross_platform_content(self):
"""Test optimizing content for multiple platforms."""
optimized = self.integration_manager.optimize_cross_platform_content(
content=self.sample_content,
platforms=self.platforms
)
# Check structure
self.assertEqual(len(optimized), len(self.platforms))
for platform in self.platforms:
self.assertIn(platform, optimized)
platform_optimized = optimized[platform]
self.assertIsInstance(platform_optimized, dict)
def test_error_handling(self):
"""Test error handling with invalid inputs."""
# Test with invalid platform
with self.assertRaises(Exception):
self.integration_manager.validate_platform_content(
content=self.sample_content,
platform='invalid_platform'
)
# Test with invalid content
invalid_content = {'title': 'Invalid Content'}
validation = self.integration_manager.validate_platform_content(
content=invalid_content,
platform='instagram'
)
self.assertFalse(validation['is_valid'])
self.assertIn('error', validation)
if __name__ == '__main__':
unittest.main()

View File

@@ -0,0 +1,186 @@
import unittest
from typing import Dict, Any
from datetime import datetime
from ..integrations.platform_adapters import UnifiedPlatformAdapter
class TestUnifiedPlatformAdapter(unittest.TestCase):
"""Test cases for the UnifiedPlatformAdapter."""
def setUp(self):
"""Set up test cases."""
self.adapter = UnifiedPlatformAdapter()
self.test_content = {
'title': 'Test Content',
'content': 'This is a test content for platform adaptation.',
'keywords': ['test', 'content', 'platform'],
'tone': 'professional',
'cta': 'Learn More',
'audience': 'For All',
'language': 'English',
'industry': 'technology',
'word_count': 1000
}
def test_adapt_instagram_content(self):
"""Test Instagram content adaptation."""
adapted_content = self.adapter.adapt_content(
content=self.test_content,
platform='instagram'
)
self.assertIsInstance(adapted_content, dict)
self.assertIn('captions', adapted_content)
self.assertIn('hashtags', adapted_content)
self.assertIn('media_suggestions', adapted_content)
self.assertIn('platform_specific', adapted_content)
def test_adapt_twitter_content(self):
"""Test Twitter content adaptation."""
adapted_content = self.adapter.adapt_content(
content=self.test_content,
platform='twitter'
)
self.assertIsInstance(adapted_content, dict)
self.assertIn('tweets', adapted_content)
self.assertIn('thread_structure', adapted_content)
self.assertIn('media_suggestions', adapted_content)
self.assertIn('platform_specific', adapted_content)
def test_adapt_linkedin_content(self):
"""Test LinkedIn content adaptation."""
adapted_content = self.adapter.adapt_content(
content=self.test_content,
platform='linkedin'
)
self.assertIsInstance(adapted_content, dict)
self.assertIn('post', adapted_content)
self.assertIn('engagement_optimization', adapted_content)
self.assertIn('media_suggestions', adapted_content)
self.assertIn('platform_specific', adapted_content)
def test_adapt_blog_content(self):
"""Test blog content adaptation."""
adapted_content = self.adapter.adapt_content(
content=self.test_content,
platform='blog'
)
self.assertIsInstance(adapted_content, dict)
self.assertIn('post', adapted_content)
self.assertIn('seo_optimization', adapted_content)
self.assertIn('media_suggestions', adapted_content)
self.assertIn('platform_specific', adapted_content)
def test_adapt_facebook_content(self):
"""Test Facebook content adaptation."""
adapted_content = self.adapter.adapt_content(
content=self.test_content,
platform='facebook'
)
self.assertIsInstance(adapted_content, dict)
self.assertIn('post', adapted_content)
self.assertIn('engagement_optimization', adapted_content)
self.assertIn('media_suggestions', adapted_content)
self.assertIn('platform_specific', adapted_content)
def test_validate_content(self):
"""Test content validation."""
# Test valid content
self.assertTrue(
self.adapter.validate_content(
self.test_content,
'instagram'
)
)
# Test invalid content (missing required fields)
invalid_content = {
'title': 'Test Content',
'content': 'This is a test content.'
}
self.assertFalse(
self.adapter.validate_content(
invalid_content,
'instagram'
)
)
def test_unsupported_platform(self):
"""Test handling of unsupported platform."""
with self.assertRaises(ValueError):
self.adapter.adapt_content(
content=self.test_content,
platform='unsupported_platform'
)
def test_content_adaptation_with_context(self):
"""Test content adaptation with additional context."""
context = {
'target_audience': 'professionals',
'campaign_goals': ['awareness', 'engagement'],
'brand_voice': 'authoritative'
}
adapted_content = self.adapter.adapt_content(
content=self.test_content,
platform='linkedin',
context=context
)
self.assertIsInstance(adapted_content, dict)
self.assertIn('post', adapted_content)
self.assertIn('engagement_optimization', adapted_content)
def test_error_handling(self):
"""Test error handling in content adaptation."""
# Test with invalid content structure
invalid_content = {
'title': 123, # Invalid type
'content': None # Missing required field
}
adapted_content = self.adapter.adapt_content(
content=invalid_content,
platform='blog'
)
self.assertIn('error', adapted_content)
def test_platform_specs(self):
"""Test platform specifications."""
specs = self.adapter.platform_specs
# Check Instagram specs
self.assertIn('instagram', specs)
self.assertIn('max_caption_length', specs['instagram'])
self.assertIn('max_hashtags', specs['instagram'])
self.assertIn('required_fields', specs['instagram'])
# Check Twitter specs
self.assertIn('twitter', specs)
self.assertIn('max_tweet_length', specs['twitter'])
self.assertIn('max_thread_length', specs['twitter'])
self.assertIn('required_fields', specs['twitter'])
# Check LinkedIn specs
self.assertIn('linkedin', specs)
self.assertIn('max_post_length', specs['linkedin'])
self.assertIn('required_fields', specs['linkedin'])
# Check blog specs
self.assertIn('blog', specs)
self.assertIn('min_word_count', specs['blog'])
self.assertIn('max_word_count', specs['blog'])
self.assertIn('required_fields', specs['blog'])
# Check Facebook specs
self.assertIn('facebook', specs)
self.assertIn('max_post_length', specs['facebook'])
self.assertIn('required_fields', specs['facebook'])
if __name__ == '__main__':
unittest.main()

View File

@@ -0,0 +1,132 @@
import unittest
from datetime import datetime
from typing import Dict, Any
from ..integrations.seo_optimizer import SEOOptimizer
class TestSEOOptimizer(unittest.TestCase):
"""Test cases for the SEOOptimizer class."""
def setUp(self):
"""Set up test fixtures."""
self.seo_optimizer = SEOOptimizer()
# Sample content for testing
self.sample_content = {
'title': 'The Future of AI in Content Marketing',
'content': 'AI is revolutionizing content marketing...',
'keywords': ['AI', 'content marketing', 'automation'],
'author': 'John Doe',
'publish_date': datetime.now().isoformat(),
'description': 'An in-depth look at AI in content marketing',
'image_url': 'https://example.com/image.jpg',
'url': 'https://example.com/article'
}
# Sample calendar for testing
self.sample_calendar = {
'metadata': {
'start_date': datetime.now().isoformat(),
'end_date': datetime.now().isoformat(),
'platforms': ['blog', 'social'],
'content_types': ['article']
},
'content_items': [self.sample_content]
}
def test_optimize_content(self):
"""Test content optimization."""
optimized = self.seo_optimizer.optimize_content(
content=self.sample_content,
content_type='article',
language='English',
search_intent='Informational Intent'
)
# Check structure
self.assertIn('original_content', optimized)
self.assertIn('seo_optimized', optimized)
# Check SEO elements
seo_elements = optimized['seo_optimized']
self.assertIn('title', seo_elements)
self.assertIn('meta_description', seo_elements)
self.assertIn('structured_data', seo_elements)
self.assertIn('keywords', seo_elements)
def test_optimize_title(self):
"""Test title optimization."""
titles = self.seo_optimizer._optimize_title(
title=self.sample_content['title'],
keywords=self.sample_content['keywords'],
content_type='article',
language='English',
search_intent='Informational Intent'
)
# Check titles
self.assertIsInstance(titles, list)
self.assertTrue(len(titles) > 0)
def test_generate_meta_description(self):
"""Test meta description generation."""
descriptions = self.seo_optimizer._generate_meta_description(
keywords=self.sample_content['keywords'],
content_type='article',
language='English',
search_intent='Informational Intent'
)
# Check descriptions
self.assertIsInstance(descriptions, list)
self.assertTrue(len(descriptions) > 0)
def test_generate_structured_data(self):
"""Test structured data generation."""
structured_data = self.seo_optimizer._generate_structured_data(
content=self.sample_content,
content_type='article'
)
# Check structured data
self.assertIsNotNone(structured_data)
def test_optimize_calendar_content(self):
"""Test calendar content optimization."""
optimized_calendar = self.seo_optimizer.optimize_calendar_content(
calendar=self.sample_calendar,
content_type='article',
language='English',
search_intent='Informational Intent'
)
# Check structure
self.assertIn('metadata', optimized_calendar)
self.assertIn('content_items', optimized_calendar)
# Check content items
self.assertTrue(len(optimized_calendar['content_items']) > 0)
for item in optimized_calendar['content_items']:
self.assertIn('original_content', item)
self.assertIn('seo_optimized', item)
def test_error_handling(self):
"""Test error handling with invalid inputs."""
# Test with invalid content
invalid_content = {'title': 'Invalid Content'}
optimized = self.seo_optimizer.optimize_content(
content=invalid_content,
content_type='article'
)
self.assertIn('error', optimized)
# Test with invalid calendar
invalid_calendar = {'metadata': {}}
optimized_calendar = self.seo_optimizer.optimize_calendar_content(
calendar=invalid_calendar,
content_type='article'
)
self.assertIn('error', optimized_calendar)
if __name__ == '__main__':
unittest.main()

View File

@@ -0,0 +1,21 @@
import streamlit as st
def render_add_content_modal(selected_date, on_add_content, on_generate_with_ai):
if st.button("+ Add Content", key="open_add_content_dialog_bottom"):
st.session_state['show_add_content_dialog'] = True
if st.session_state.get('show_add_content_dialog', False):
st.markdown("### Add Content")
with st.form("quick_add_form_dialog_bottom"):
title = st.text_input("Title")
platform = st.selectbox("Platform", ["Blog", "Instagram", "Twitter", "LinkedIn", "Facebook"])
content_type = st.selectbox("Content Type", ["Article", "Social Post", "Video", "Newsletter"])
publish_date = st.date_input("Publish Date", selected_date)
col_add, col_ai = st.columns([0.6, 0.4])
with col_add:
if st.form_submit_button("Add Content"):
on_add_content(title, platform, content_type, publish_date)
with col_ai:
if st.form_submit_button("Generate with AI"):
on_generate_with_ai(title, platform, content_type)
if st.button("Close", key="close_add_content_dialog_bottom"):
st.session_state['show_add_content_dialog'] = False

View File

@@ -0,0 +1,137 @@
import streamlit as st
def render_ai_suggestions_modal(generate_ai_suggestions, on_create_brief, on_schedule, on_refine, on_customize):
st.subheader("AI Content Suggestions")
default_type = st.session_state.get('ai_modal_type', "Blog Post")
default_topic = st.session_state.get('ai_modal_topic', "")
default_platform = st.session_state.get('ai_modal_platform', "Blog")
content_types = {
"Blog Post": "Long-form content for in-depth topics",
"Social Media Post": "Short, engaging content for social platforms",
"Video": "Visual content with script and storyboard",
"Newsletter": "Email content for subscriber engagement"
}
content_type = st.selectbox(
"Content Type",
list(content_types.keys()),
format_func=lambda x: f"{x} - {content_types[x]}",
key="modal_suggestion_type",
index=list(content_types.keys()).index(default_type) if default_type in content_types else 0
)
topic = st.text_input("Enter topic or keyword", value=default_topic, key="modal_suggestion_topic")
with st.expander("Advanced Options"):
audience = st.multiselect(
"Target Audience",
["Professionals", "Students", "Entrepreneurs", "General Public", "Industry Experts"],
default=["Professionals"]
)
goals = st.multiselect(
"Content Goals",
["Increase Engagement", "Generate Leads", "Build Authority", "Drive Traffic", "Educate"],
default=["Increase Engagement"]
)
tone = st.select_slider(
"Content Tone",
options=["Professional", "Casual", "Educational", "Entertaining", "Persuasive"],
value="Professional"
)
length = st.radio(
"Content Length",
["Short", "Medium", "Long"],
horizontal=True
)
st.subheader("AI Model Settings")
model_settings = {
"Creativity Level": st.slider("Creativity Level", 0.0, 1.0, 0.7, 0.1),
"Formality Level": st.slider("Formality Level", 0.0, 1.0, 0.5, 0.1),
"Technical Depth": st.slider("Technical Depth", 0.0, 1.0, 0.5, 0.1)
}
st.subheader("Content Style Preferences")
style_preferences = {
"Use Examples": st.checkbox("Include Real-world Examples", True),
"Use Statistics": st.checkbox("Include Statistics and Data", True),
"Use Quotes": st.checkbox("Include Expert Quotes", False),
"Use Case Studies": st.checkbox("Include Case Studies", False)
}
st.subheader("SEO Preferences")
seo_preferences = {
"Keyword Density": st.slider("Keyword Density (%)", 1, 5, 2),
"Internal Linking": st.checkbox("Suggest Internal Links", True),
"External Linking": st.checkbox("Suggest External Links", True),
"Meta Description": st.checkbox("Generate Meta Description", True)
}
st.subheader("Platform-specific Settings")
platform_settings = {
"Hashtag Usage": st.checkbox("Suggest Hashtags", True),
"Image Suggestions": st.checkbox("Suggest Images", True),
"Video Suggestions": st.checkbox("Suggest Videos", False),
"Interactive Elements": st.checkbox("Suggest Interactive Elements", False)
}
if st.button("Generate Suggestions", type="primary", key="modal_generate_btn"):
with st.spinner("Generating suggestions..."):
suggestions = generate_ai_suggestions(
content_type,
topic,
audience,
goals,
tone,
length,
model_settings,
style_preferences,
seo_preferences,
platform_settings
)
if suggestions:
suggestion_tabs = st.tabs([f"Suggestion {i+1}" for i in range(len(suggestions))])
for i, (tab, suggestion) in enumerate(zip(suggestion_tabs, suggestions)):
with tab:
col1, col2 = st.columns([2, 1])
with col1:
st.subheader(suggestion['title'])
st.write(f"**Type:** {suggestion['type']}")
st.write(f"**Platform:** {suggestion['platform']}")
st.write(f"**Target Audience:** {', '.join(suggestion['audience'])}")
st.write(f"**Estimated Impact:** {suggestion['impact']}")
with st.expander("Content Preview"):
st.write(suggestion.get('preview', 'Preview not available'))
if suggestion.get('style_elements'):
st.write("**Style Elements:**")
for element in suggestion['style_elements']:
st.write(f"- {element}")
if suggestion.get('seo_elements'):
st.write("**SEO Elements:**")
for element in suggestion['seo_elements']:
st.write(f"- {element}")
with col2:
st.subheader("Performance Metrics")
metrics = {
"Engagement Score": suggestion.get('engagement_score', '85%'),
"Reach Potential": suggestion.get('reach', 'High'),
"Conversion Rate": suggestion.get('conversion', '3.5%'),
"SEO Impact": suggestion.get('seo_impact', 'Strong')
}
for metric, value in metrics.items():
st.metric(metric, value)
st.subheader("Actions")
if st.button("Create Brief", key=f"modal_brief_{i}"):
on_create_brief(suggestion)
if st.button("Schedule", key=f"modal_schedule_{i}"):
on_schedule(suggestion)
if st.button("Refine", key=f"modal_refine_{i}"):
on_refine(suggestion)
if st.button("Customize", key=f"modal_customize_{i}"):
on_customize(suggestion)
with st.expander("Additional Options"):
st.write("**Platform Optimizations**")
for platform in suggestion.get('platform_optimizations', []):
st.write(f"- {platform}")
st.write("**Content Variations**")
for variation in suggestion.get('variations', []):
st.write(f"- {variation}")
st.write("**SEO Recommendations**")
for seo in suggestion.get('seo_recommendations', []):
st.write(f"- {seo}")
if suggestion.get('media_suggestions'):
st.write("**Media Suggestions**")
for media in suggestion['media_suggestions']:
st.write(f"- {media}")

View File

@@ -0,0 +1,51 @@
import streamlit as st
from .components.content_card import render_content_card
from .components.badge import render_badge
def render_calendar_view(calendar_data, icon_map, status_color, on_edit, on_delete, on_generate, get_item_key):
if calendar_data is not None and not calendar_data.empty:
st.markdown("### All Scheduled Content")
calendar_data = calendar_data.sort_values(by="date")
grouped = list(calendar_data.groupby(calendar_data['date'].dt.date))
for i, (date, group) in enumerate(grouped):
exp_open = (i == 0)
with st.expander(f"{date.strftime('%B %d, %Y')}", expanded=exp_open):
for idx, row in group.iterrows():
item_key = get_item_key(row)
is_editing = st.session_state.get("editing_item_key") == item_key
platform = str(row['platform'])
if hasattr(platform, 'value'):
platform = platform.value
platform_map = {
'blog': 'Blog',
'website': 'Blog',
'instagram': 'Instagram',
'twitter': 'Twitter',
'linkedin': 'LinkedIn',
'facebook': 'Facebook',
}
platform_disp = platform_map.get(platform.lower(), 'Blog')
type_disp = str(row['type'])
if hasattr(type_disp, 'value'):
type_disp = type_disp.value
type_disp = type_disp.replace('_', ' ').title()
status_disp = row['status'].capitalize()
platform_icon = icon_map.get(platform_disp, '🌐')
type_icon = icon_map.get(type_disp, '📄')
render_content_card(
row=row,
is_editing=is_editing,
on_edit=lambda r=row: on_edit(r),
on_delete=lambda r=row: on_delete(r),
on_generate=lambda r=row: on_generate(r),
icon_map=icon_map,
status_color=status_color,
platform_disp=platform_disp,
type_disp=type_disp,
status_disp=status_disp,
platform_icon=platform_icon,
type_icon=type_icon,
item_key=item_key
)
else:
st.info("No content scheduled yet. Add content to see it here.")

View File

@@ -0,0 +1,231 @@
import streamlit as st
from typing import Dict, Any, List
from lib.ai_seo_tools.content_calendar.models.calendar import ContentItem
import logging
logger = logging.getLogger(__name__)
def render_ab_testing(
content_generator,
calendar_manager
) -> None:
"""Render the A/B testing interface."""
try:
st.header("A/B Testing")
# Test Configuration
st.markdown("### Create A/B Test")
col1, col2 = st.columns([2, 1])
with col1:
test_content = st.selectbox(
"Select content for A/B testing",
options=[item.title for item in calendar_manager.get_calendar().get_all_content()],
key="ab_test_content_select"
)
with col2:
num_variants = st.slider(
"Number of variants",
min_value=2,
max_value=5,
value=2,
help="Number of different versions to test"
)
if test_content:
content_item = next(
item for item in calendar_manager.get_calendar().get_all_content()
if item.title == test_content
)
# Test Settings
with st.expander("Test Settings"):
col1, col2 = st.columns(2)
with col1:
test_duration = st.number_input(
"Test Duration (days)",
min_value=1,
max_value=30,
value=7
)
target_metric = st.selectbox(
"Primary Metric",
options=['Engagement', 'Conversion', 'Reach', 'Click-through'],
value='Engagement'
)
with col2:
audience_size = st.select_slider(
"Audience Size",
options=['Small', 'Medium', 'Large'],
value='Medium'
)
confidence_level = st.slider(
"Confidence Level",
min_value=90,
max_value=99,
value=95,
help="Statistical confidence level for test results"
)
# Generate Variants
if st.button("Generate Variants"):
with st.spinner("Generating variants..."):
variants = _generate_ab_test_variants(content_generator, content_item, num_variants)
if variants:
st.success(f"Generated {len(variants)} variants!")
# Display variants in tabs
variant_tabs = st.tabs([f"Variant {i+1}" for i in range(len(variants))])
for i, tab in enumerate(variant_tabs):
with tab:
st.markdown(f"### Variant {i+1}")
st.json(variants[i]['content'])
# Variant metrics
col1, col2, col3 = st.columns(3)
with col1:
st.metric(
"Engagement Score",
f"{variants[i]['metrics']['engagement_score']:.1f}%"
)
with col2:
st.metric(
"Conversion Rate",
f"{variants[i]['metrics']['conversion_rate']:.1f}%"
)
with col3:
st.metric(
"Reach",
f"{variants[i]['metrics']['reach']:,}"
)
# Results Analysis
st.markdown("### Analyze Results")
if test_content in st.session_state.ab_test_results:
test_data = st.session_state.ab_test_results[test_content]
# Test Status
st.info(f"Test Status: {test_data['status']}")
st.write(f"Started: {test_data['start_time']}")
if test_data['status'] == 'running':
if st.button("End Test and Analyze"):
with st.spinner("Analyzing results..."):
results = _analyze_ab_test_results(content_item)
if results:
st.success("Analysis complete!")
_display_test_results(results)
except Exception as e:
logger.error(f"Error in A/B testing interface: {str(e)}", exc_info=True)
st.error(f"Error in A/B testing: {str(e)}")
def _generate_ab_test_variants(
content_generator,
content: ContentItem,
num_variants: int
) -> List[Dict[str, Any]]:
"""Generate A/B test variants for content."""
try:
logger.info(f"Generating {num_variants} variants for content: {content.title}")
# Convert content to dictionary format
content_dict = {
'title': content.title,
'content': content.description,
'metadata': {
'platform': content.platforms[0].name if content.platforms else 'Unknown',
'content_type': content.content_type.name
}
}
variants = []
for i in range(num_variants):
# Generate different variations
variant = content_generator.generate_variation(
content=content_dict,
variation_type=f"variant_{i+1}"
)
if variant:
variants.append(variant)
return variants
except Exception as e:
logger.error(f"Error generating variants: {str(e)}")
return []
def _analyze_ab_test_results(content_item: ContentItem) -> Dict[str, Any]:
"""Analyze results of A/B testing for content optimization."""
try:
logger.info(f"Analyzing A/B test results for: {content_item.title}")
if content_item.title not in st.session_state.ab_test_results:
raise ValueError("No A/B test results found for this content")
test_data = st.session_state.ab_test_results[content_item.title]
variants = test_data['variants']
# Calculate performance metrics
results = {
'total_engagement': sum(v['metrics']['engagement_score'] for v in variants),
'total_conversions': sum(v['metrics']['conversion_rate'] for v in variants),
'total_reach': sum(v['metrics']['reach'] for v in variants),
'best_performing_variant': max(variants, key=lambda x: x['metrics']['engagement_score']),
'recommendations': []
}
# Generate recommendations
for variant in variants:
if variant['metrics']['engagement_score'] > 0.7: # High engagement threshold
results['recommendations'].append({
'variant_id': variant['variant_id'],
'reason': 'High engagement score',
'suggested_actions': ['Scale this variant', 'Apply learnings to other content']
})
# Update test status
test_data['status'] = 'completed'
test_data['results'] = results
logger.info("A/B test results analyzed successfully")
return results
except Exception as e:
logger.error(f"Error analyzing A/B test results: {str(e)}", exc_info=True)
st.error(f"Error analyzing A/B test results: {str(e)}")
return {}
def _display_test_results(results: Dict[str, Any]) -> None:
"""Display A/B test results in the UI."""
with st.expander("Overall Performance", expanded=True):
col1, col2, col3 = st.columns(3)
with col1:
st.metric(
"Total Engagement",
f"{results['total_engagement']:.1f}%"
)
with col2:
st.metric(
"Total Conversions",
f"{results['total_conversions']:.1f}%"
)
with col3:
st.metric(
"Total Reach",
f"{results['total_reach']:,}"
)
with st.expander("Best Performing Variant", expanded=True):
best_variant = results['best_performing_variant']
st.markdown(f"### {best_variant['variant_id']}")
st.json(best_variant['content'])
with st.expander("Recommendations", expanded=True):
for rec in results['recommendations']:
st.markdown(f"#### {rec['variant_id']}")
st.write(f"Reason: {rec['reason']}")
st.write("Suggested Actions:")
for action in rec['suggested_actions']:
st.write(f"- {action}")

View File

@@ -0,0 +1,2 @@
def render_badge(platform_disp, platform_icon, type_disp, status_disp):
return f"<span class='badge-content-calendar badge-platform-{platform_disp.lower()}'>{platform_icon} {platform_disp} &nbsp;|&nbsp; {type_disp} &nbsp;|&nbsp; <span class='chip-status chip-status-{status_disp.lower()}'>{status_disp}</span></span>"

View File

@@ -0,0 +1,22 @@
import streamlit as st
def render_content_card(row, is_editing, on_edit, on_delete, on_generate, icon_map, status_color, platform_disp, type_disp, status_disp, platform_icon, type_icon, item_key):
st.markdown(f"<div class='card-content-calendar'>", unsafe_allow_html=True)
st.markdown(f"<div style='display:flex;align-items:center;justify-content:space-between;gap:8px;'>", unsafe_allow_html=True)
st.markdown(f"<div style='display:flex;align-items:center;gap:8px;min-width:0;flex:1;'>"
f"{type_icon}<span class='content-title'>{row['title']}</span></div>", unsafe_allow_html=True)
st.markdown("<div style='display:flex;align-items:center;gap:4px;'>", unsafe_allow_html=True)
col1, col2, col3 = st.columns([1, 1, 1])
with col1:
if st.button("", key=f"generate_{item_key}", help="Generate with AI Blog Writer", use_container_width=True):
on_generate()
with col2:
if st.button("✏️", key=f"edit_{item_key}", help="Edit Content", use_container_width=True):
on_edit()
with col3:
if st.button("🗑️", key=f"delete_{item_key}", help="Delete Content", use_container_width=True):
on_delete()
st.markdown("</div>", unsafe_allow_html=True)
st.markdown("</div>", unsafe_allow_html=True)
st.markdown(f"<div class='content-meta'><span class='badge-content-calendar badge-platform-{platform_disp.lower()}'>{platform_icon} {platform_disp} &nbsp;|&nbsp; {type_disp} &nbsp;|&nbsp; <span class='chip-status chip-status-{status_disp.lower()}'>{status_disp}</span></span></div>", unsafe_allow_html=True)
st.markdown("</div>", unsafe_allow_html=True)

View File

@@ -0,0 +1,467 @@
import streamlit as st
from typing import Dict, Any, List
from datetime import datetime
import pandas as pd
from ...core.content_generator import ContentGenerator
from ...core.ai_generator import AIGenerator
from ...integrations.seo_optimizer import SEOOptimizer
from ...models.calendar import ContentItem, ContentType, Platform, SEOData
import logging
logger = logging.getLogger('content_calendar.optimization')
class OptimizationManager:
def __init__(self):
if 'optimization_history' not in st.session_state:
st.session_state.optimization_history = {}
if 'optimization_previews' not in st.session_state:
st.session_state.optimization_previews = {}
if 'optimization_metrics' not in st.session_state:
st.session_state.optimization_metrics = {}
def track_optimization(self, content_id: str, optimization_data: Dict[str, Any]) -> bool:
"""Track optimization changes for content with detailed metrics."""
try:
if content_id not in st.session_state.optimization_history:
st.session_state.optimization_history[content_id] = []
optimization_data['timestamp'] = datetime.now()
optimization_data['metrics'] = self._calculate_optimization_metrics(optimization_data)
st.session_state.optimization_history[content_id].append(optimization_data)
# Update metrics
if content_id not in st.session_state.optimization_metrics:
st.session_state.optimization_metrics[content_id] = []
st.session_state.optimization_metrics[content_id].append(optimization_data['metrics'])
return True
except Exception as e:
logger.error(f"Error tracking optimization: {str(e)}")
return False
def _calculate_optimization_metrics(self, optimization_data: Dict[str, Any]) -> Dict[str, Any]:
"""Calculate detailed optimization metrics."""
try:
metrics = {
'readability_score': 0,
'seo_score': 0,
'engagement_potential': 0,
'keyword_density': 0,
'content_quality': 0
}
# Calculate readability score
if 'content' in optimization_data:
content = optimization_data['content']
metrics['readability_score'] = self._calculate_readability(content)
# Calculate SEO score
if 'seo_data' in optimization_data:
seo_data = optimization_data['seo_data']
metrics['seo_score'] = self._calculate_seo_score(seo_data)
metrics['keyword_density'] = self._calculate_keyword_density(seo_data)
# Calculate engagement potential
if 'engagement_metrics' in optimization_data:
engagement = optimization_data['engagement_metrics']
metrics['engagement_potential'] = self._calculate_engagement_potential(engagement)
# Calculate overall content quality
metrics['content_quality'] = (
metrics['readability_score'] * 0.3 +
metrics['seo_score'] * 0.3 +
metrics['engagement_potential'] * 0.4
)
return metrics
except Exception as e:
logger.error(f"Error calculating optimization metrics: {str(e)}")
return {}
def _calculate_readability(self, content: str) -> float:
"""Calculate content readability score."""
try:
# Implement readability calculation logic
# This is a placeholder implementation
return 0.8
except Exception as e:
logger.error(f"Error calculating readability: {str(e)}")
return 0.0
def _calculate_seo_score(self, seo_data: SEOData) -> float:
"""Calculate SEO optimization score."""
try:
# Implement SEO score calculation logic
# This is a placeholder implementation
return 0.85
except Exception as e:
logger.error(f"Error calculating SEO score: {str(e)}")
return 0.0
def _calculate_keyword_density(self, seo_data: SEOData) -> float:
"""Calculate keyword density."""
try:
# Implement keyword density calculation logic
# This is a placeholder implementation
return 2.5
except Exception as e:
logger.error(f"Error calculating keyword density: {str(e)}")
return 0.0
def _calculate_engagement_potential(self, engagement: Dict[str, Any]) -> float:
"""Calculate content engagement potential."""
try:
# Implement engagement potential calculation logic
# This is a placeholder implementation
return 0.75
except Exception as e:
logger.error(f"Error calculating engagement potential: {str(e)}")
return 0.0
def get_optimization_history(self, content_id: str) -> List[Dict[str, Any]]:
"""Get detailed optimization history for content."""
return st.session_state.optimization_history.get(content_id, [])
def get_optimization_metrics(self, content_id: str) -> List[Dict[str, Any]]:
"""Get optimization metrics history."""
return st.session_state.optimization_metrics.get(content_id, [])
def save_preview(self, content_id: str, preview_data: Dict[str, Any]) -> bool:
"""Save optimization preview with versioning."""
try:
if content_id not in st.session_state.optimization_previews:
st.session_state.optimization_previews[content_id] = []
preview_data['version'] = len(st.session_state.optimization_previews[content_id]) + 1
preview_data['timestamp'] = datetime.now()
st.session_state.optimization_previews[content_id].append(preview_data)
return True
except Exception as e:
logger.error(f"Error saving preview: {str(e)}")
return False
def get_preview(self, content_id: str, version: int = None) -> Dict[str, Any]:
"""Get optimization preview with optional versioning."""
try:
previews = st.session_state.optimization_previews.get(content_id, [])
if not previews:
return {}
if version is None:
return previews[-1]
for preview in previews:
if preview['version'] == version:
return preview
return {}
except Exception as e:
logger.error(f"Error getting preview: {str(e)}")
return {}
def render_content_optimization(
content_generator: ContentGenerator,
ai_generator: AIGenerator,
seo_optimizer: SEOOptimizer
):
"""Render the content optimization interface with advanced features."""
st.header("Content Optimization")
# Initialize optimization manager
optimization_manager = OptimizationManager()
# Check if calendar manager is available
if 'calendar_manager' not in st.session_state:
st.error("Calendar manager not initialized. Please refresh the page.")
return
# Get available content
try:
available_content = st.session_state.calendar_manager.get_calendar().get_all_content()
content_options = [item.title for item in available_content]
except Exception as e:
logger.error(f"Error getting content options: {str(e)}")
st.error("Error loading content. Please try again.")
return
if not content_options:
st.info("No content available for optimization. Please add some content first.")
return
# Content Selection
selected_content = st.selectbox(
"Select content to optimize",
options=content_options,
key="optimize_content_select"
)
if selected_content:
try:
content_item = next(
item for item in available_content
if item.title == selected_content
)
# Create tabs for different optimization aspects
opt_tabs = st.tabs(["Content Optimization", "SEO Optimization", "Preview", "History", "Analytics"])
with opt_tabs[0]:
st.subheader("Content Optimization")
# Advanced Optimization Settings
with st.expander("Advanced Settings", expanded=True):
col1, col2 = st.columns(2)
with col1:
tone = st.select_slider(
"Content Tone",
options=['Professional', 'Casual', 'Friendly', 'Authoritative', 'Conversational'],
value='Professional'
)
length = st.select_slider(
"Content Length",
options=['Short', 'Medium', 'Long', 'Comprehensive'],
value='Medium'
)
with col2:
engagement_goal = st.select_slider(
"Engagement Goal",
options=['Awareness', 'Consideration', 'Conversion', 'Retention'],
value='Consideration'
)
creativity_level = st.slider(
"Creativity Level",
min_value=1,
max_value=10,
value=5
)
# Platform-Specific Optimization
st.subheader("Platform-Specific Optimization")
platforms = st.multiselect(
"Target Platforms",
options=[p.name for p in content_item.platforms],
default=[p.name for p in content_item.platforms]
)
# Generate Optimization
if st.button("Generate Optimization"):
with st.spinner("Generating optimization..."):
try:
# Generate optimized content
optimized_content = content_generator.optimize_for_platform(
content=content_item,
platform=Platform[platforms[0]] if platforms else content_item.platforms[0],
requirements={
'tone': tone,
'length': length,
'engagement_goal': engagement_goal,
'creativity_level': creativity_level
}
)
if optimized_content:
# Track optimization
optimization_manager.track_optimization(
content_item.title,
{
'type': 'content',
'changes': optimized_content.get('changes', []),
'metrics': optimized_content.get('metrics', {}),
'content': optimized_content.get('content', ''),
'engagement_metrics': optimized_content.get('engagement_metrics', {})
}
)
# Save preview
optimization_manager.save_preview(
content_item.title,
{
'original': content_item.description,
'optimized': optimized_content.get('content', ''),
'changes': optimized_content.get('changes', []),
'metrics': optimized_content.get('metrics', {})
}
)
st.success("Content optimized successfully!")
except Exception as e:
logger.error(f"Error optimizing content: {str(e)}")
st.error(f"Error optimizing content: {str(e)}")
with opt_tabs[1]:
st.subheader("SEO Optimization")
# SEO Settings
with st.expander("SEO Settings", expanded=True):
col1, col2 = st.columns(2)
with col1:
keyword_density = st.slider(
"Target Keyword Density",
min_value=1,
max_value=5,
value=2,
help="Target percentage of keywords in content"
)
internal_linking = st.checkbox(
"Enable Internal Linking",
value=True,
help="Automatically add internal links to related content"
)
with col2:
external_linking = st.checkbox(
"Enable External Linking",
value=True,
help="Add relevant external links for credibility"
)
structured_data = st.checkbox(
"Add Structured Data",
value=True,
help="Include schema.org structured data"
)
# Generate SEO Optimization
if st.button("Generate SEO Optimization"):
with st.spinner("Generating SEO optimization..."):
try:
# Generate SEO-optimized content
seo_optimized = seo_optimizer.optimize_content(
content=content_item,
content_type=content_item.content_type.name,
language='English',
search_intent='Informational Intent',
settings={
'keyword_density': keyword_density,
'internal_linking': internal_linking,
'external_linking': external_linking,
'structured_data': structured_data
}
)
if seo_optimized:
# Track optimization
optimization_manager.track_optimization(
content_item.title,
{
'type': 'seo',
'changes': seo_optimized.get('changes', []),
'metrics': seo_optimized.get('metrics', {}),
'seo_data': seo_optimized
}
)
# Save preview
optimization_manager.save_preview(
content_item.title,
{
'meta_description': seo_optimized.get('meta_description', ''),
'keywords': seo_optimized.get('keywords', []),
'structured_data': seo_optimized.get('structured_data', {}),
'changes': seo_optimized.get('changes', [])
}
)
st.success("SEO optimization completed!")
except Exception as e:
logger.error(f"Error optimizing SEO: {str(e)}")
st.error(f"Error optimizing SEO: {str(e)}")
with opt_tabs[2]:
st.subheader("Optimization Preview")
preview_data = optimization_manager.get_preview(content_item.title)
if preview_data:
# Content Preview
if 'original' in preview_data:
st.markdown("### Content Changes")
col1, col2 = st.columns(2)
with col1:
st.markdown("#### Original Content")
st.write(preview_data['original'])
with col2:
st.markdown("#### Optimized Content")
st.write(preview_data['optimized'])
st.markdown("#### Key Changes")
for change in preview_data.get('changes', []):
st.write(f"- {change}")
# SEO Preview
if 'meta_description' in preview_data:
st.markdown("### SEO Changes")
st.markdown("#### Meta Description")
st.write(preview_data['meta_description'])
st.markdown("#### Keywords")
st.write(", ".join(preview_data['keywords']))
st.markdown("#### Structured Data")
st.json(preview_data['structured_data'])
# Metrics Preview
if 'metrics' in preview_data:
st.markdown("### Optimization Metrics")
metrics = preview_data['metrics']
col1, col2, col3 = st.columns(3)
with col1:
st.metric("Readability Score", f"{metrics.get('readability_score', 0):.1%}")
with col2:
st.metric("SEO Score", f"{metrics.get('seo_score', 0):.1%}")
with col3:
st.metric("Engagement Potential", f"{metrics.get('engagement_potential', 0):.1%}")
else:
st.info("No optimization preview available. Generate optimization first.")
with opt_tabs[3]:
st.subheader("Optimization History")
history = optimization_manager.get_optimization_history(content_item.title)
if history:
for entry in history:
with st.expander(f"Optimization at {entry['timestamp']}"):
st.write(f"Type: {entry['type']}")
st.write("Changes:")
for change in entry.get('changes', []):
st.write(f"- {change}")
if 'metrics' in entry:
st.write("Metrics:")
st.json(entry['metrics'])
else:
st.info("No optimization history available.")
with opt_tabs[4]:
st.subheader("Optimization Analytics")
metrics_history = optimization_manager.get_optimization_metrics(content_item.title)
if metrics_history:
# Convert metrics history to DataFrame
df = pd.DataFrame(metrics_history)
# Plot metrics over time
st.line_chart(df[['readability_score', 'seo_score', 'engagement_potential', 'content_quality']])
# Display current metrics
current_metrics = metrics_history[-1]
col1, col2, col3, col4 = st.columns(4)
with col1:
st.metric("Readability", f"{current_metrics.get('readability_score', 0):.1%}")
with col2:
st.metric("SEO Score", f"{current_metrics.get('seo_score', 0):.1%}")
with col3:
st.metric("Engagement", f"{current_metrics.get('engagement_potential', 0):.1%}")
with col4:
st.metric("Overall Quality", f"{current_metrics.get('content_quality', 0):.1%}")
# Display keyword density trend
st.subheader("Keyword Density Trend")
st.line_chart(df['keyword_density'])
else:
st.info("No optimization metrics available. Generate optimization first.")

View File

@@ -0,0 +1,392 @@
import streamlit as st
from typing import Dict, Any, List
from datetime import datetime, timedelta
import pandas as pd
from ...core.content_generator import ContentGenerator
from ...core.ai_generator import AIGenerator
from ...integrations.seo_optimizer import SEOOptimizer
from ...models.calendar import ContentItem, ContentType, Platform, SEOData
import logging
logger = logging.getLogger('content_calendar.series')
class SeriesManager:
def __init__(self):
self.series_data = {}
if 'content_series' not in st.session_state:
st.session_state.content_series = {}
if 'series_relationships' not in st.session_state:
st.session_state.series_relationships = {}
if 'series_performance' not in st.session_state:
st.session_state.series_performance = {}
def create_series(self, series_id: str, topic: str, num_pieces: int, content_type: ContentType,
platforms: List[Platform], schedule_strategy: str = 'linear') -> Dict[str, Any]:
"""Create a new content series with tracking and scheduling."""
try:
series = {
'id': series_id,
'topic': topic,
'num_pieces': num_pieces,
'content_type': content_type,
'platforms': platforms,
'schedule_strategy': schedule_strategy,
'pieces': [],
'performance': {},
'created_at': datetime.now(),
'status': 'draft',
'relationships': {},
'platform_distribution': {p.name: [] for p in platforms}
}
st.session_state.content_series[series_id] = series
return series
except Exception as e:
logger.error(f"Error creating series: {str(e)}")
return None
def add_piece(self, series_id: str, piece: Dict[str, Any]) -> bool:
"""Add a content piece to the series with relationship tracking."""
try:
if series_id in st.session_state.content_series:
series = st.session_state.content_series[series_id]
piece_id = f"piece_{len(series['pieces'])}"
piece['id'] = piece_id
# Track relationships
if series['pieces']:
previous_piece = series['pieces'][-1]
piece['relationships'] = {
'previous': previous_piece['id'],
'next': None
}
previous_piece['relationships']['next'] = piece_id
# Add to platform distribution
for platform in piece.get('platforms', []):
if platform.name in series['platform_distribution']:
series['platform_distribution'][platform.name].append(piece_id)
series['pieces'].append(piece)
return True
return False
except Exception as e:
logger.error(f"Error adding piece to series: {str(e)}")
return False
def get_series_performance(self, series_id: str) -> Dict[str, Any]:
"""Get comprehensive performance analytics for a series."""
try:
if series_id in st.session_state.content_series:
series = st.session_state.content_series[series_id]
performance = {
'overall': {
'total_engagement': 0,
'total_reach': 0,
'conversion_rate': 0,
'average_engagement': 0
},
'platforms': {},
'pieces': {},
'trends': {
'engagement': [],
'reach': [],
'conversions': []
}
}
# Calculate overall metrics
for piece in series['pieces']:
piece_performance = piece.get('performance', {})
performance['overall']['total_engagement'] += piece_performance.get('engagement', 0)
performance['overall']['total_reach'] += piece_performance.get('reach', 0)
performance['overall']['conversion_rate'] += piece_performance.get('conversion_rate', 0)
# Track piece-specific performance
performance['pieces'][piece['id']] = piece_performance
# Track trends
performance['trends']['engagement'].append(piece_performance.get('engagement', 0))
performance['trends']['reach'].append(piece_performance.get('reach', 0))
performance['trends']['conversions'].append(piece_performance.get('conversion_rate', 0))
# Calculate averages
num_pieces = len(series['pieces'])
if num_pieces > 0:
performance['overall']['average_engagement'] = performance['overall']['total_engagement'] / num_pieces
performance['overall']['conversion_rate'] = performance['overall']['conversion_rate'] / num_pieces
# Calculate platform-specific performance
for platform in series['platforms']:
platform_pieces = series['platform_distribution'].get(platform.name, [])
platform_performance = {
'engagement': 0,
'reach': 0,
'conversion_rate': 0
}
for piece_id in platform_pieces:
piece_performance = performance['pieces'].get(piece_id, {})
platform_performance['engagement'] += piece_performance.get('engagement', 0)
platform_performance['reach'] += piece_performance.get('reach', 0)
platform_performance['conversion_rate'] += piece_performance.get('conversion_rate', 0)
if platform_pieces:
platform_performance['engagement'] /= len(platform_pieces)
platform_performance['conversion_rate'] /= len(platform_pieces)
performance['platforms'][platform.name] = platform_performance
return performance
return {}
except Exception as e:
logger.error(f"Error getting series performance: {str(e)}")
return {}
def update_series_status(self, series_id: str, status: str) -> bool:
"""Update the status of a series."""
try:
if series_id in st.session_state.content_series:
st.session_state.content_series[series_id]['status'] = status
return True
return False
except Exception as e:
logger.error(f"Error updating series status: {str(e)}")
return False
def schedule_series(self, series_id: str, start_date: datetime, interval: int = 7) -> bool:
"""Schedule the series content with flexible scheduling strategies."""
try:
if series_id in st.session_state.content_series:
series = st.session_state.content_series[series_id]
current_date = start_date
for piece in series['pieces']:
piece['scheduled_date'] = current_date
if series['schedule_strategy'] == 'linear':
current_date += timedelta(days=interval)
elif series['schedule_strategy'] == 'burst':
current_date += timedelta(days=1)
elif series['schedule_strategy'] == 'custom':
# Custom scheduling is handled by the UI
pass
return True
return False
except Exception as e:
logger.error(f"Error scheduling series: {str(e)}")
return False
def render_content_series_generator(ai_generator: AIGenerator, content_generator: ContentGenerator,
seo_optimizer: SEOOptimizer):
"""Render the content series generator interface with enhanced features."""
st.header("Content Series Generator")
# Initialize series manager
series_manager = SeriesManager()
# Series Creation Form
with st.form("series_creation_form"):
st.subheader("Create New Series")
series_topic = st.text_input("Series Topic")
num_pieces = st.slider("Number of pieces", 2, 10, 3)
content_type = st.selectbox(
"Content Type",
options=[ct.name for ct in ContentType],
key="series_content_type"
)
# Multi-platform selection
platforms = st.multiselect(
"Target Platforms",
options=[p.name for p in Platform],
default=['WEBSITE'],
key="series_platforms"
)
# Schedule strategy
schedule_strategy = st.selectbox(
"Schedule Strategy",
options=['linear', 'burst', 'custom'],
help="Linear: Evenly spaced, Burst: Grouped together, Custom: Manual scheduling"
)
# Series metadata
with st.expander("Series Metadata"):
target_audience = st.text_area("Target Audience")
series_goals = st.multiselect(
"Series Goals",
options=['Awareness', 'Engagement', 'Conversion', 'Education'],
default=['Awareness']
)
series_tone = st.select_slider(
"Series Tone",
options=['Professional', 'Casual', 'Friendly', 'Authoritative', 'Conversational'],
value='Professional'
)
submitted = st.form_submit_button("Generate Series")
if submitted and series_topic:
with st.spinner("Generating content series..."):
try:
# Create series
series_id = f"series_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
series = series_manager.create_series(
series_id=series_id,
topic=series_topic,
num_pieces=num_pieces,
content_type=ContentType[content_type],
platforms=[Platform[p] for p in platforms],
schedule_strategy=schedule_strategy
)
if series:
# Generate series content
for i in range(num_pieces):
content_item = ContentItem(
title=f"{series_topic} - Part {i+1}",
description="",
content_type=ContentType[content_type],
platforms=[Platform[p] for p in platforms],
publish_date=datetime.now() + timedelta(days=i*7),
seo_data=SEOData(
title=f"{series_topic} - Part {i+1}",
meta_description="",
keywords=[],
structured_data={}
),
status='Draft'
)
# Generate content using AI
base_content = ai_generator.generate_series_content(
content_item=content_item,
series_info={
'topic': series_topic,
'part_number': i+1,
'total_parts': num_pieces,
'content_type': content_type,
'platforms': platforms,
'audience': target_audience,
'goals': series_goals,
'tone': series_tone
}
)
if base_content:
# Enhance with Content Generator
enhanced_content = content_generator.enhance_series_content(
content=base_content,
series_info={
'topic': series_topic,
'part_number': i+1,
'total_parts': num_pieces
}
)
if enhanced_content:
base_content.update(enhanced_content)
# Add to series
series_manager.add_piece(series_id, {
'part_number': i+1,
'content': base_content,
'seo_data': seo_optimizer.optimize_content(
content=base_content,
content_type=content_type,
language='English',
search_intent='Informational Intent'
)
})
st.success(f"Generated {num_pieces} content pieces for series!")
# Display series preview
with st.expander("Series Preview", expanded=True):
for piece in series_manager.series_data[series_id]['pieces']:
st.markdown(f"### Part {piece['part_number']}")
st.json(piece['content'])
# Platform-specific previews
st.markdown("#### Platform Previews")
for platform in platforms:
with st.expander(f"{platform} Preview"):
st.write(piece['content'].get('platform_previews', {}).get(platform, 'No preview available'))
# Series scheduling
st.subheader("Series Scheduling")
if schedule_strategy == 'linear':
start_date = st.date_input("Start Date", datetime.now())
interval = st.number_input("Days between pieces", min_value=1, value=7)
if st.button("Schedule Series"):
series_manager.schedule_series(series_id, start_date, interval)
st.success("Series scheduled successfully!")
elif schedule_strategy == 'burst':
start_date = st.date_input("Start Date", datetime.now())
if st.button("Schedule Series"):
series_manager.schedule_series(series_id, start_date, interval=1)
st.success("Series scheduled successfully!")
else: # custom
for i, piece in enumerate(series_manager.series_data[series_id]['pieces']):
piece['scheduled_date'] = st.date_input(
f"Publish Date for Part {i+1}",
datetime.now() + timedelta(days=i*7)
)
if st.button("Save Schedule"):
st.success("Series schedule saved!")
# Series performance tracking
st.subheader("Series Performance")
performance_data = series_manager.get_series_performance(series_id)
if performance_data:
st.write("### Overall Performance")
col1, col2, col3 = st.columns(3)
with col1:
st.metric("Total Engagement", f"{performance_data['overall']['total_engagement']:.1f}%")
with col2:
st.metric("Total Reach", f"{performance_data['overall']['total_reach']:,}")
with col3:
st.metric("Conversion Rate", f"{performance_data['overall']['conversion_rate']:.1f}%")
# Platform-specific performance
st.write("### Platform Performance")
for platform in platforms:
with st.expander(f"{platform} Performance"):
platform_data = performance_data['platforms'].get(platform, {})
st.write(f"Engagement: {platform_data.get('engagement', 0):.1f}%")
st.write(f"Reach: {platform_data.get('reach', 0):,}")
st.write(f"Conversions: {platform_data.get('conversion_rate', 0):.1f}%")
# Performance trends
st.write("### Performance Trends")
trend_data = performance_data['trends']
st.line_chart(pd.DataFrame({
'Engagement': trend_data['engagement'],
'Reach': trend_data['reach'],
'Conversions': trend_data['conversions']
}))
except Exception as e:
logger.error(f"Error generating series: {str(e)}", exc_info=True)
st.error(f"Error generating series: {str(e)}")
# Display existing series
if st.session_state.content_series:
st.subheader("Existing Series")
for series_id, series in st.session_state.content_series.items():
with st.expander(f"Series: {series['topic']}"):
st.write(f"Status: {series['status']}")
st.write(f"Pieces: {len(series['pieces'])}")
st.write(f"Created: {series['created_at']}")
# Series actions
if st.button(f"View Details", key=f"view_{series_id}"):
st.session_state.selected_series = series_id
if st.button(f"Delete Series", key=f"delete_{series_id}"):
del st.session_state.content_series[series_id]
st.experimental_rerun()

View File

@@ -0,0 +1,81 @@
import streamlit as st
from typing import Dict, Any
from lib.ai_seo_tools.content_calendar.models.calendar import ContentItem
import logging
logger = logging.getLogger(__name__)
def render_performance_insights(content_item: ContentItem, platform_adapter) -> None:
"""Render performance insights for a content item."""
try:
logger.info(f"Rendering performance insights for: {content_item.title}")
# Get performance data from platform adapter
performance_data = platform_adapter.get_content_performance(content_item)
if not performance_data:
st.warning("No performance data available for this content")
return
# Create metrics section
st.subheader("Performance Metrics")
col1, col2, col3 = st.columns(3)
with col1:
st.metric(
"Engagement Rate",
f"{performance_data.get('engagement_rate', 0):.1f}%",
f"{performance_data.get('engagement_rate_change', 0):+.1f}%"
)
with col2:
st.metric(
"Reach",
f"{performance_data.get('reach', 0):,}",
f"{performance_data.get('reach_change', 0):+,}"
)
with col3:
st.metric(
"Conversion Rate",
f"{performance_data.get('conversion_rate', 0):.1f}%",
f"{performance_data.get('conversion_rate_change', 0):+.1f}%"
)
# Create audience insights section
st.subheader("Audience Insights")
audience_data = performance_data.get('audience_insights', {})
if audience_data:
col1, col2 = st.columns(2)
with col1:
st.write("Demographics")
st.write(f"- Age: {audience_data.get('age_range', 'N/A')}")
st.write(f"- Gender: {audience_data.get('gender', 'N/A')}")
st.write(f"- Location: {audience_data.get('location', 'N/A')}")
with col2:
st.write("Behavior")
st.write(f"- Peak Time: {audience_data.get('peak_time', 'N/A')}")
st.write(f"- Device: {audience_data.get('device', 'N/A')}")
st.write(f"- Platform: {audience_data.get('platform', 'N/A')}")
# Create content insights section
st.subheader("Content Insights")
content_insights = performance_data.get('content_insights', {})
if content_insights:
st.write("Top Performing Elements")
for element, score in content_insights.get('top_elements', {}).items():
st.write(f"- {element}: {score}")
st.write("Improvement Suggestions")
for suggestion in content_insights.get('suggestions', []):
st.write(f"- {suggestion}")
logger.info(f"Performance insights rendered successfully for: {content_item.title}")
except Exception as e:
logger.error(f"Error rendering performance insights: {str(e)}", exc_info=True)
st.error(f"Error rendering performance insights: {str(e)}")

View File

@@ -0,0 +1,634 @@
import streamlit as st
import pandas as pd
from datetime import datetime
import logging
import sys
import hashlib
from .calendar_view import render_calendar_view
from .filters import render_filters
from .add_content_modal import render_add_content_modal
from .ai_suggestions_modal import render_ai_suggestions_modal
from .components.performance_insights import render_performance_insights
from .components.content_series import render_content_series_generator
from .components.ab_testing import render_ab_testing
from .components.content_optimization import render_content_optimization
from ..core.calendar_manager import CalendarManager
from ..core.content_brief import ContentBriefGenerator
from ..core.content_generator import ContentGenerator
from ..core.ai_generator import AIGenerator
from ..integrations.platform_adapters import UnifiedPlatformAdapter
from ..integrations.seo_optimizer import SEOOptimizer
from lib.ai_seo_tools.content_calendar.models.calendar import ContentItem, Platform, ContentType, SEOData, Calendar
from lib.gpt_providers.text_generation.main_text_generation import llm_text_gen
from typing import Dict, Any, List, Tuple
import json
class ContentCalendarDashboard:
"""Interactive dashboard for content calendar management."""
def __init__(self):
self.logger = logging.getLogger('content_calendar.dashboard')
self.logger.info("Initializing ContentCalendarDashboard")
# Initialize calendar manager and store in session state
if 'calendar_manager' not in st.session_state:
st.session_state.calendar_manager = CalendarManager()
st.session_state.calendar_manager.load_calendar_from_json()
self.calendar_manager = st.session_state.calendar_manager
self.content_brief_generator = ContentBriefGenerator()
self.content_generator = ContentGenerator()
self.ai_generator = AIGenerator()
self.platform_adapter = UnifiedPlatformAdapter()
self.seo_optimizer = SEOOptimizer()
# Initialize A/B testing state
if 'ab_test_results' not in st.session_state:
st.session_state.ab_test_results = {}
# Initialize content optimization state
if 'optimization_history' not in st.session_state:
st.session_state.optimization_history = {}
# Ensure a calendar exists
if not self.calendar_manager.get_calendar():
self.calendar_manager._calendar = Calendar(
start_date=datetime.now(),
duration='monthly',
platforms=[Platform.WEBSITE, Platform.INSTAGRAM, Platform.TWITTER, Platform.LINKEDIN, Platform.FACEBOOK],
schedule={}
)
# Initialize session state
if 'calendar_data' not in st.session_state:
st.session_state.calendar_data = None
if 'selected_content' not in st.session_state:
st.session_state.selected_content = None
if 'view_mode' not in st.session_state:
st.session_state.view_mode = 'day'
if 'selected_date' not in st.session_state:
st.session_state.selected_date = datetime.now()
self.logger.info("ContentCalendarDashboard initialized successfully")
def render(self):
self.logger.info("Starting dashboard render (tabbed UI)")
try:
self._inject_custom_css()
st.title("AI Content Planning")
st.markdown("""
Plan, schedule, and manage your content strategy with AI-powered insights. Use the calendar to organize your content and leverage AI tools for optimization.
""")
tabs = st.tabs(["Content Planning", "Content Optimization", "A/B Testing", "Content Series", "Analytics"])
with tabs[0]:
icon_map = {
'Blog': '📝', 'Website': '🌐', 'Instagram': '📸', 'Twitter': '🐦', 'LinkedIn': '💼', 'Facebook': '📘',
'Article': '📄', 'Social Post': '💬', 'Video': '🎬', 'Newsletter': '✉️'
}
status_color = {
'Draft': '#bdbdbd', 'Scheduled': '#1976d2', 'Published': '#43a047', 'Archived': '#757575'
}
calendar_data = self._get_calendar_data()
def on_edit(row):
st.session_state["editing_item_key"] = self._get_item_key(row)
st.experimental_rerun()
def on_delete(row):
self._delete_content(row)
st.experimental_rerun()
def on_generate(row):
st.session_state['show_ai_modal'] = True
st.session_state['ai_modal_topic'] = row['title']
st.session_state['ai_modal_type'] = str(row['type'])
st.session_state['ai_modal_platform'] = str(row['platform'])
st.experimental_rerun()
render_calendar_view(
calendar_data=calendar_data,
icon_map=icon_map,
status_color=status_color,
on_edit=on_edit,
on_delete=on_delete,
on_generate=on_generate,
get_item_key=self._get_item_key
)
st.markdown("---")
render_filters()
def handle_add_content(title, platform, content_type, publish_date):
self._add_content({
'title': title,
'platform': platform,
'type': content_type,
'publish_date': publish_date
})
st.session_state['show_add_content_dialog'] = False
st.success("Content added!")
st.experimental_rerun()
def handle_generate_with_ai(title, platform, content_type):
st.session_state['show_add_content_dialog'] = False
st.session_state['show_ai_modal'] = True
st.session_state['ai_modal_topic'] = title
st.session_state['ai_modal_type'] = content_type
st.session_state['ai_modal_platform'] = platform
render_add_content_modal(
selected_date=st.session_state.selected_date,
on_add_content=handle_add_content,
on_generate_with_ai=handle_generate_with_ai
)
if st.session_state.get('show_ai_modal', False):
st.markdown("### AI Content Suggestions")
with st.container():
render_ai_suggestions_modal(
generate_ai_suggestions=self._generate_ai_suggestions,
on_create_brief=self._create_content_brief,
on_schedule=self._schedule_content,
on_refine=self._refine_suggestion,
on_customize=self._customize_suggestion
)
if st.button("Close"):
st.session_state['show_ai_modal'] = False
with tabs[1]:
render_content_optimization(
content_generator=self.content_generator,
ai_generator=self.ai_generator,
seo_optimizer=self.seo_optimizer
)
with tabs[2]:
render_ab_testing(self.content_generator, self.calendar_manager)
with tabs[3]:
render_content_series_generator(
self.ai_generator,
self.content_generator,
self.seo_optimizer
)
with tabs[4]:
st.header("Analytics")
st.markdown("### Performance Insights")
selected_content = st.selectbox(
"Select content to analyze",
options=[item.title for item in self.calendar_manager.get_calendar().get_all_content()],
key="analytics_content_select"
)
if selected_content:
content_item = next(
item for item in self.calendar_manager.get_calendar().get_all_content()
if item.title == selected_content
)
render_performance_insights(content_item, self.platform_adapter)
st.markdown("### Optimization History")
if selected_content in st.session_state.optimization_history:
st.json(st.session_state.optimization_history[selected_content])
self.logger.info("Dashboard render completed successfully (tabbed UI)")
except Exception as e:
self.logger.error(f"Error rendering dashboard: {str(e)}", exc_info=True)
st.error(f"An error occurred: {str(e)}")
def _inject_custom_css(self):
st.markdown("""
<style>
/* Add your custom CSS here if needed */
</style>
""", unsafe_allow_html=True)
def _get_calendar_data(self):
self.logger.info("_get_calendar_data called")
try:
calendar_obj = self.calendar_manager.get_calendar()
if not calendar_obj:
self.logger.info("No calendar found in manager")
return None
data = []
for date_str, items in calendar_obj.schedule.items():
for item in items:
data.append({
'date': pd.to_datetime(date_str),
'title': item.title,
'platform': item.platforms[0] if item.platforms else 'Unknown',
'type': item.content_type,
'status': item.status
})
df = pd.DataFrame(data) if data else None
return df
except Exception as e:
self.logger.error(f"Error loading calendar data: {str(e)}", exc_info=True)
st.error(f"Error loading calendar data: {str(e)}")
return None
def _add_content(self, content):
calendar = self.calendar_manager.get_calendar()
if not calendar:
st.error("No calendar found. Please create a calendar first.")
return
platform_map = {
'Blog': Platform.WEBSITE,
'Instagram': Platform.INSTAGRAM,
'Twitter': Platform.TWITTER,
'LinkedIn': Platform.LINKEDIN,
'Facebook': Platform.FACEBOOK,
}
platform_enum = platform_map.get(content['platform'], Platform.WEBSITE)
content_type_map = {
'Article': ContentType.BLOG_POST,
'Social Post': ContentType.SOCIAL_MEDIA,
'Video': ContentType.VIDEO,
'Newsletter': ContentType.NEWSLETTER,
}
content_type_enum = content_type_map.get(content['type'], ContentType.BLOG_POST)
seo_data = SEOData(
title=content['title'],
meta_description="",
keywords=[],
structured_data={},
)
new_item = ContentItem(
title=content['title'],
description="",
content_type=content_type_enum,
platforms=[platform_enum],
publish_date=pd.to_datetime(content['publish_date']),
seo_data=seo_data,
status=content.get('status', 'Draft')
)
calendar.add_content(new_item)
self.calendar_manager.save_calendar_to_json()
def _delete_content(self, row):
calendar = self.calendar_manager.get_calendar()
if not calendar:
return
for date_str, items in list(calendar.schedule.items()):
calendar.schedule[date_str] = [
item for item in items
if not (
item.title == row['title'] and
str(item.publish_date.date()) == str(row['date'].date()) and
item.platforms[0].name == str(row['platform']) and
item.content_type.name == str(row['type'])
)
]
if not calendar.schedule[date_str]:
del calendar.schedule[date_str]
self.calendar_manager.save_calendar_to_json()
def _edit_content(self, row, new_title, new_platform, new_type, new_status):
self._delete_content(row)
self._add_content({
'title': new_title,
'platform': new_platform,
'type': new_type,
'publish_date': row['date'],
'status': new_status
})
def _get_item_key(self, row):
key_str = f"{row['title']}_{row['date']}_{row['platform']}_{row['type']}"
return hashlib.md5(key_str.encode()).hexdigest()
def _generate_ai_suggestions(self, content_type, topic, audience, goals, tone, length, model_settings, style_preferences, seo_preferences, platform_settings):
"""Generate AI content suggestions based on input parameters."""
try:
self.logger.info(f"Generating AI suggestions for topic: {topic}")
# Map content type string to ContentType enum
content_type_map = {
'Blog Post': ContentType.BLOG_POST,
'Social Media Post': ContentType.SOCIAL_MEDIA,
'Video': ContentType.VIDEO,
'Newsletter': ContentType.NEWSLETTER,
'Article': ContentType.BLOG_POST,
'Social Post': ContentType.SOCIAL_MEDIA
}
content_type_enum = content_type_map.get(content_type, ContentType.BLOG_POST)
# Map platform string to Platform enum
platform_map = {
'Blog': Platform.WEBSITE,
'Instagram': Platform.INSTAGRAM,
'Twitter': Platform.TWITTER,
'LinkedIn': Platform.LINKEDIN,
'Facebook': Platform.FACEBOOK,
'Website': Platform.WEBSITE
}
platform = st.session_state.get('ai_modal_platform', 'Blog')
platform_enum = platform_map.get(platform, Platform.WEBSITE)
# Create a content item for the suggestion
content_item = ContentItem(
title=topic,
description="",
content_type=content_type_enum,
platforms=[platform_enum],
publish_date=datetime.now(),
seo_data=SEOData(
title=topic,
meta_description="",
keywords=[],
structured_data={}
),
status='Draft'
)
# Use AIGenerator to generate suggestions
suggestions = self.ai_generator.generate_ai_suggestions(
content_type=content_type_enum,
topic=topic,
audience=audience,
goals=goals,
tone=tone,
length=length,
model_settings=model_settings,
style_preferences=style_preferences,
seo_preferences=seo_preferences,
platform_settings=platform_settings,
platform=platform_enum
)
if not suggestions:
self.logger.warning("No suggestions generated")
return []
# Format suggestions
formatted_suggestions = []
for suggestion in suggestions:
formatted_suggestion = {
'title': suggestion.get('title', topic),
'type': content_type,
'platform': platform,
'audience': audience,
'impact': f"High impact for {', '.join(goals)}",
'preview': suggestion.get('preview', ''),
'style_elements': [
f"Tone: {tone}",
f"Length: {length}",
f"Creativity: {model_settings.get('Creativity Level', 'balanced')}",
f"Formality: {model_settings.get('Formality Level', 'professional')}"
],
'seo_elements': [
f"Keyword Density: {seo_preferences.get('Keyword Density', '2')}%",
"Internal Linking: Enabled" if seo_preferences.get('Internal Linking', True) else "Internal Linking: Disabled",
"External Linking: Enabled" if seo_preferences.get('External Linking', True) else "External Linking: Disabled"
],
'engagement_score': f"{85 + len(formatted_suggestions)*5}%",
'reach': 'High',
'conversion': f"{3.5 + len(formatted_suggestions)*0.5}%",
'seo_impact': 'Strong',
'platform_optimizations': suggestion.get('platform_optimizations', []),
'variations': suggestion.get('variations', [
"Alternative headline",
"Different content angle",
"Alternative format"
]),
'seo_recommendations': suggestion.get('seo_elements', []),
'media_suggestions': suggestion.get('media_suggestions', [
"Featured image",
"Supporting graphics",
"Social media visuals"
])
}
formatted_suggestions.append(formatted_suggestion)
self.logger.info(f"Generated {len(formatted_suggestions)} suggestions successfully")
return formatted_suggestions
except Exception as e:
self.logger.error(f"Error generating AI suggestions: {str(e)}", exc_info=True)
st.error(f"Error generating suggestions: {str(e)}")
return []
def _create_content_brief(self, content_item: ContentItem) -> Dict[str, Any]:
"""Create a detailed content brief for the given content item."""
try:
self.logger.info(f"Creating content brief for: {content_item.title}")
# Generate content brief using the content brief generator
brief = self.content_brief_generator.generate_brief(
content_item=content_item,
target_audience={
'audience': content_item.description,
'goals': ['engage', 'inform', 'convert']
}
)
# Enhance brief with SEO data
if brief and 'content_flow' in brief:
brief['seo_optimization'] = {
'meta_description': self.seo_optimizer.generate_meta_description(
brief['content_flow'].get('introduction', {}).get('summary', '')
),
'keywords': self.seo_optimizer.extract_keywords(
brief['content_flow'].get('introduction', {}).get('summary', '')
),
'structured_data': self.seo_optimizer.generate_structured_data(
content_item.content_type
)
}
self.logger.info(f"Content brief created successfully for: {content_item.title}")
return brief
except Exception as e:
self.logger.error(f"Error creating content brief: {str(e)}", exc_info=True)
st.error(f"Error creating content brief: {str(e)}")
return {}
def _schedule_content(self, content_item: ContentItem, publish_date: datetime) -> bool:
"""Schedule content for publishing on the specified date."""
try:
self.logger.info(f"Scheduling content: {content_item.title} for {publish_date}")
# Get the calendar
calendar = self.calendar_manager.get_calendar()
if not calendar:
raise ValueError("No calendar found")
# Update the publish date
content_item.publish_date = publish_date
# Add to calendar
calendar.add_content(content_item)
# Save changes
self.calendar_manager.save_calendar_to_json()
self.logger.info(f"Content scheduled successfully: {content_item.title}")
return True
except Exception as e:
self.logger.error(f"Error scheduling content: {str(e)}", exc_info=True)
st.error(f"Error scheduling content: {str(e)}")
return False
def _refine_suggestion(self, suggestion: Dict[str, Any], feedback: Dict[str, Any]) -> Dict[str, Any]:
"""Refine an AI-generated suggestion based on user feedback."""
try:
self.logger.info("Refining AI suggestion based on feedback")
# Update suggestion based on feedback
if 'tone' in feedback:
suggestion['style_elements'] = [
f"Tone: {feedback['tone']}",
*[elem for elem in suggestion['style_elements'] if not elem.startswith('Tone:')]
]
if 'length' in feedback:
suggestion['style_elements'] = [
f"Length: {feedback['length']}",
*[elem for elem in suggestion['style_elements'] if not elem.startswith('Length:')]
]
if 'keywords' in feedback:
suggestion['seo_elements'] = [
f"Keywords: {', '.join(feedback['keywords'])}",
*[elem for elem in suggestion['seo_elements'] if not elem.startswith('Keywords:')]
]
# Regenerate content with refined parameters
refined_content = self.content_brief_generator.generate_brief(
content_item=ContentItem(
title=suggestion['title'],
description="",
content_type=ContentType[suggestion['type'].upper().replace(' ', '_')],
platforms=[Platform[suggestion['platform'].upper()]],
publish_date=datetime.now(),
seo_data=SEOData(
title=suggestion['title'],
meta_description="",
keywords=feedback.get('keywords', []),
structured_data={}
),
status='Draft'
),
target_audience={
'audience': suggestion['audience'],
'goals': feedback.get('goals', ['engage', 'inform']),
'preferences': {
'tone': feedback.get('tone', 'professional'),
'length': feedback.get('length', 'medium')
}
}
)
if refined_content:
suggestion['preview'] = refined_content.get('content_flow', {}).get('introduction', {}).get('summary', '')
self.logger.info("Suggestion refined successfully")
return suggestion
except Exception as e:
self.logger.error(f"Error refining suggestion: {str(e)}", exc_info=True)
st.error(f"Error refining suggestion: {str(e)}")
return suggestion
def _customize_suggestion(self, suggestion: Dict[str, Any], customizations: Dict[str, Any]) -> Dict[str, Any]:
"""Customize an AI-generated suggestion with specific requirements."""
try:
self.logger.info("Customizing AI suggestion")
# Apply customizations
if 'title' in customizations:
suggestion['title'] = customizations['title']
if 'platform' in customizations:
suggestion['platform'] = customizations['platform']
if 'style' in customizations:
suggestion['style_elements'] = [
f"Tone: {customizations['style'].get('tone', 'professional')}",
f"Length: {customizations['style'].get('length', 'medium')}",
f"Creativity: {customizations['style'].get('creativity', 'balanced')}",
f"Formality: {customizations['style'].get('formality', 'professional')}"
]
if 'seo' in customizations:
suggestion['seo_elements'] = [
f"Keyword Density: {customizations['seo'].get('keyword_density', '2')}%",
"Internal Linking: Enabled" if customizations['seo'].get('internal_linking', True) else "Internal Linking: Disabled",
"External Linking: Enabled" if customizations['seo'].get('external_linking', True) else "External Linking: Disabled"
]
# Regenerate content with customizations
customized_content = self.content_brief_generator.generate_brief(
content_item=ContentItem(
title=suggestion['title'],
description="",
content_type=ContentType[suggestion['type'].upper().replace(' ', '_')],
platforms=[Platform[suggestion['platform'].upper()]],
publish_date=datetime.now(),
seo_data=SEOData(
title=suggestion['title'],
meta_description="",
keywords=customizations.get('seo', {}).get('keywords', []),
structured_data={}
),
status='Draft'
),
target_audience={
'audience': suggestion['audience'],
'goals': customizations.get('goals', ['engage', 'inform']),
'preferences': customizations.get('style', {})
}
)
if customized_content:
suggestion['preview'] = customized_content.get('content_flow', {}).get('introduction', {}).get('summary', '')
self.logger.info("Suggestion customized successfully")
return suggestion
except Exception as e:
self.logger.error(f"Error customizing suggestion: {str(e)}", exc_info=True)
st.error(f"Error customizing suggestion: {str(e)}")
return suggestion
def _optimize_content_for_platform(self, content_item: ContentItem, platform: Platform) -> Dict[str, Any]:
"""Optimize content specifically for a target platform."""
try:
self.logger.info(f"Optimizing content for {platform.name}: {content_item.title}")
# Get platform-specific requirements
platform_requirements = self.platform_adapter.get_platform_requirements(platform)
# Generate platform-optimized content
optimized_content = self.content_generator.optimize_for_platform(
content=content_item,
platform=platform,
requirements=platform_requirements
)
if not optimized_content:
raise ValueError(f"Failed to optimize content for {platform.name}")
# Enhance with AI
ai_enhanced = self.ai_generator.enhance_for_platform(
content=optimized_content,
platform=platform,
enhancement_type='platform_specific'
)
if ai_enhanced:
optimized_content.update(ai_enhanced)
# Track optimization history
if content_item.title not in st.session_state.optimization_history:
st.session_state.optimization_history[content_item.title] = []
st.session_state.optimization_history[content_item.title].append({
'platform': platform.name,
'timestamp': datetime.now(),
'changes': optimized_content.get('changes', [])
})
self.logger.info(f"Content optimized successfully for {platform.name}")
return optimized_content
except Exception as e:
self.logger.error(f"Error optimizing content: {str(e)}", exc_info=True)
st.error(f"Error optimizing content: {str(e)}")
return {}
if __name__ == "__main__":
dashboard = ContentCalendarDashboard()
dashboard.render()

View File

@@ -0,0 +1,30 @@
import streamlit as st
from datetime import datetime, timedelta
def render_filters():
with st.expander("Filters", expanded=False):
col1, col2 = st.columns(2)
with col1:
start_date = st.date_input("Start Date", st.session_state.get('filter_start_date', datetime.now()))
end_date = st.date_input("End Date", st.session_state.get('filter_end_date', datetime.now() + timedelta(days=30)))
st.session_state['filter_start_date'] = start_date
st.session_state['filter_end_date'] = end_date
with col2:
platforms = st.multiselect(
"Platforms",
["Blog", "Instagram", "Twitter", "LinkedIn", "Facebook"],
default=st.session_state.get('filter_platforms', ["Blog"])
)
st.session_state['filter_platforms'] = platforms
content_types = st.multiselect(
"Content Types",
["Article", "Social Post", "Video", "Newsletter"],
default=st.session_state.get('filter_content_types', ["Article"])
)
st.session_state['filter_content_types'] = content_types
statuses = st.multiselect(
"Status",
["Draft", "Scheduled", "Published", "Archived"],
default=st.session_state.get('filter_statuses', ["Draft", "Scheduled"])
)
st.session_state['filter_statuses'] = statuses

View File

@@ -0,0 +1,198 @@
from datetime import datetime, timedelta
from typing import Dict, List, Any
import calendar
import random
def calculate_publish_dates(
topics: List[Dict[str, Any]],
start_date: datetime,
duration: str
) -> Dict[str, List[Dict[str, Any]]]:
"""
Calculate optimal publish dates for content topics.
Args:
topics: List of content topics to schedule
start_date: When to start publishing
duration: How long the calendar should span ('weekly', 'monthly', 'quarterly')
Returns:
Dictionary mapping dates to scheduled content
"""
# Calculate end date based on duration
end_date = _calculate_end_date(start_date, duration)
# Get all dates in range
dates = _get_dates_in_range(start_date, end_date)
# Calculate optimal posting frequency
frequency = _calculate_posting_frequency(len(topics), len(dates))
# Schedule content
schedule = _schedule_content(topics, dates, frequency)
return schedule
def _calculate_end_date(start_date: datetime, duration: str) -> datetime:
"""Calculate end date based on duration."""
if duration == 'weekly':
return start_date + timedelta(days=7)
elif duration == 'monthly':
# Add one month
if start_date.month == 12:
return datetime(start_date.year + 1, 1, start_date.day)
return datetime(start_date.year, start_date.month + 1, start_date.day)
elif duration == 'quarterly':
# Add three months
new_month = start_date.month + 3
new_year = start_date.year
if new_month > 12:
new_month -= 12
new_year += 1
return datetime(new_year, new_month, start_date.day)
else:
raise ValueError(f"Invalid duration: {duration}")
def _get_dates_in_range(
start_date: datetime,
end_date: datetime
) -> List[datetime]:
"""Get all dates in the given range."""
dates = []
current_date = start_date
while current_date <= end_date:
# Skip weekends
if current_date.weekday() < 5: # 0-4 are weekdays
dates.append(current_date)
current_date += timedelta(days=1)
return dates
def _calculate_posting_frequency(
num_topics: int,
num_dates: int
) -> Dict[str, int]:
"""
Calculate optimal posting frequency based on number of topics and dates.
Returns:
Dictionary with posting frequency for each content type
"""
# Calculate base frequency
base_frequency = num_dates / num_topics
# Adjust for content types
return {
'blog_post': max(1, int(base_frequency * 0.4)), # 40% of content
'social_media': max(1, int(base_frequency * 0.3)), # 30% of content
'video': max(1, int(base_frequency * 0.2)), # 20% of content
'newsletter': max(1, int(base_frequency * 0.1)) # 10% of content
}
def _schedule_content(
topics: List[Dict[str, Any]],
dates: List[datetime],
frequency: Dict[str, int]
) -> Dict[str, List[Dict[str, Any]]]:
"""
Schedule content topics across available dates.
Args:
topics: List of content topics to schedule
dates: Available dates for scheduling
frequency: Posting frequency for each content type
Returns:
Dictionary mapping dates to scheduled content
"""
schedule = {}
current_date_index = 0
# Group topics by content type
topics_by_type = _group_topics_by_type(topics)
# Schedule each content type
for content_type, type_topics in topics_by_type.items():
type_frequency = frequency.get(content_type, 1)
for topic in type_topics:
# Find next available date
while current_date_index < len(dates):
date = dates[current_date_index]
date_str = date.strftime('%Y-%m-%d')
# Check if date is available
if date_str not in schedule:
schedule[date_str] = []
# Add topic to schedule
schedule[date_str].append(topic)
# Move to next date based on frequency
current_date_index += type_frequency
break
# If we've used all dates, wrap around
if current_date_index >= len(dates):
current_date_index = 0
return schedule
def _group_topics_by_type(
topics: List[Dict[str, Any]]
) -> Dict[str, List[Dict[str, Any]]]:
"""Group topics by their content type."""
grouped = {}
for topic in topics:
content_type = topic.get('content_type', 'blog_post')
if content_type not in grouped:
grouped[content_type] = []
grouped[content_type].append(topic)
return grouped
def get_optimal_posting_time(
content_type: str,
platform: str
) -> datetime.time:
"""
Get optimal posting time for content type and platform.
Args:
content_type: Type of content
platform: Target platform
Returns:
Optimal time to post
"""
# Default optimal times (can be customized based on platform analytics)
optimal_times = {
'blog_post': {
'website': datetime.time(9, 0), # 9 AM
'medium': datetime.time(10, 0) # 10 AM
},
'social_media': {
'facebook': datetime.time(15, 0), # 3 PM
'twitter': datetime.time(12, 0), # 12 PM
'linkedin': datetime.time(8, 0), # 8 AM
'instagram': datetime.time(19, 0) # 7 PM
},
'video': {
'youtube': datetime.time(14, 0) # 2 PM
},
'newsletter': {
'email': datetime.time(6, 0) # 6 AM
}
}
# Get optimal time for content type and platform
content_times = optimal_times.get(content_type, {})
optimal_time = content_times.get(platform)
if optimal_time is None:
# Default to 9 AM if no specific time is set
optimal_time = datetime.time(9, 0)
return optimal_time

View File

@@ -0,0 +1,154 @@
import functools
import logging
from typing import Any, Callable, TypeVar, cast
from datetime import datetime
logger = logging.getLogger(__name__)
T = TypeVar('T')
def handle_calendar_error(func: Callable[..., T]) -> Callable[..., T]:
"""
Decorator to handle errors in calendar operations.
Args:
func: Function to decorate
Returns:
Decorated function with error handling
"""
@functools.wraps(func)
def wrapper(*args: Any, **kwargs: Any) -> T:
try:
return func(*args, **kwargs)
except ValueError as e:
logger.error(f"Invalid input in {func.__name__}: {str(e)}")
raise
except Exception as e:
logger.error(f"Error in {func.__name__}: {str(e)}")
raise CalendarError(f"Calendar operation failed: {str(e)}")
return cast(Callable[..., T], wrapper)
class CalendarError(Exception):
"""Base exception for calendar-related errors."""
pass
class ContentError(CalendarError):
"""Exception for content-related errors."""
pass
class SchedulingError(CalendarError):
"""Exception for scheduling-related errors."""
pass
class ValidationError(CalendarError):
"""Exception for validation-related errors."""
pass
def validate_date_range(
start_date: datetime,
end_date: datetime
) -> None:
"""
Validate date range for calendar operations.
Args:
start_date: Start date
end_date: End date
Raises:
ValidationError: If date range is invalid
"""
if not isinstance(start_date, datetime):
raise ValidationError("Start date must be a datetime object")
if not isinstance(end_date, datetime):
raise ValidationError("End date must be a datetime object")
if start_date > end_date:
raise ValidationError("Start date must be before end date")
if (end_date - start_date).days > 365:
raise ValidationError("Calendar duration cannot exceed one year")
def validate_content_item(content: dict) -> None:
"""
Validate content item structure.
Args:
content: Content item to validate
Raises:
ValidationError: If content item is invalid
"""
required_fields = ['title', 'description', 'content_type', 'platforms']
for field in required_fields:
if field not in content:
raise ValidationError(f"Missing required field: {field}")
if not isinstance(content['platforms'], list):
raise ValidationError("Platforms must be a list")
if not content['platforms']:
raise ValidationError("At least one platform must be specified")
def validate_calendar_duration(duration: str) -> None:
"""
Validate calendar duration.
Args:
duration: Duration to validate ('weekly', 'monthly', 'quarterly')
Raises:
ValidationError: If duration is invalid
"""
valid_durations = ['weekly', 'monthly', 'quarterly']
if duration not in valid_durations:
raise ValidationError(
f"Invalid duration: {duration}. "
f"Must be one of: {', '.join(valid_durations)}"
)
def log_calendar_operation(
operation: str,
details: dict
) -> None:
"""
Log calendar operation details.
Args:
operation: Name of the operation
details: Operation details to log
"""
logger.info(f"Calendar operation: {operation}")
logger.debug(f"Operation details: {details}")
def handle_api_error(
error: Exception,
operation: str
) -> None:
"""
Handle API-related errors.
Args:
error: The error that occurred
operation: The operation that failed
"""
logger.error(f"API error in {operation}: {str(error)}")
raise CalendarError(f"API operation failed: {str(error)}")
def handle_integration_error(
error: Exception,
integration: str
) -> None:
"""
Handle integration-related errors.
Args:
error: The error that occurred
integration: The integration that failed
"""
logger.error(f"Integration error with {integration}: {str(error)}")
raise CalendarError(f"Integration failed: {str(error)}")

View File

@@ -0,0 +1,182 @@
# Content Gap Analysis Tool
A comprehensive AI-powered tool for analyzing content gaps and generating strategic content recommendations.
## Overview
The Content Gap Analysis tool combines multiple SEO tools to provide a complete analysis of your content strategy, identify opportunities, and generate actionable recommendations. It leverages existing AI SEO tools and adds new capabilities for comprehensive content analysis.
## Workflow Design
### 1. Website Analysis
**Input:** Website URL
**Tools Integration:**
- `analyze_onpage_seo()`: Analyze content quality and structure
- `url_seo_checker()`: Check technical SEO aspects
- `google_pagespeed_insights()`: Assess page performance
**Analysis Components:**
- Content structure mapping
- Topic categorization
- Content depth assessment
- Performance metrics
### 2. Competitor Analysis
**Input:** Competitor URLs
**Tools Integration:**
- `url_seo_checker()`: Analyze competitor URLs
- `analyze_onpage_seo()`: Compare content quality
- `ai_title_generator()`: Analyze title patterns
**Analysis Components:**
- Content strategy comparison
- Topic coverage gaps
- Content format analysis
- Title pattern analysis
### 3. Keyword Research
**Input:** Industry/Niche
**Tools Integration:**
- `ai_title_generator()`: Generate keyword-based titles
- `metadesc_generator_main()`: Analyze meta descriptions for keyword usage
- `ai_structured_data()`: Check structured data implementation
**Analysis Components:**
- Keyword opportunity identification
- Search intent analysis
- Content format suggestions
- Topic clustering
### 4. AI-Powered Recommendations
**Tools Integration:**
- `ai_title_generator()`: Generate content titles
- `metadesc_generator_main()`: Create content summaries
- `ai_structured_data()`: Suggest structured data implementation
**Output Components:**
- Content topic suggestions
- Format recommendations
- Priority scoring
- Implementation timeline
## Implementation Plan
### Phase 1: Core Infrastructure
1. Create base classes and interfaces
2. Implement data collection modules
3. Set up AI model integration
4. Develop data storage system
### Phase 2: Tool Integration
1. Integrate existing SEO tools
2. Create unified API for tool interaction
3. Implement data sharing between tools
4. Develop result aggregation system
### Phase 3: Analysis Engine
1. Implement content structure analysis
2. Develop competitor analysis algorithms
3. Create keyword research system
4. Build recommendation engine
### Phase 4: UI/UX Development
1. Create step-by-step workflow interface
2. Implement progress tracking
3. Develop visualization components
4. Add export functionality
## Technical Requirements
### Dependencies
- Existing SEO tools from `lib/ai_seo_tools/`
- AI models for content analysis
- Web scraping capabilities
- Data storage system
### File Structure
```
content_gap_analysis/
├── __init__.py
├── main.py
├── website_analyzer.py
├── competitor_analyzer.py
├── keyword_researcher.py
├── recommendation_engine.py
├── utils/
│ ├── __init__.py
│ ├── data_collector.py
│ ├── content_parser.py
│ └── ai_processor.py
└── tests/
├── __init__.py
├── test_website_analyzer.py
├── test_competitor_analyzer.py
└── test_keyword_researcher.py
```
## Integration Points
### Existing Tools
1. **On-Page SEO Analyzer**
- Function: `analyze_onpage_seo()`
- Purpose: Content quality assessment
- Integration: Content structure analysis
2. **URL SEO Checker**
- Function: `url_seo_checker()`
- Purpose: Technical optimization
- Integration: URL structure analysis
3. **Blog Title Generator**
- Function: `ai_title_generator()`
- Purpose: Content ideas
- Integration: Keyword analysis
4. **Meta Description Generator**
- Function: `metadesc_generator_main()`
- Purpose: Content summaries
- Integration: Content optimization
5. **Structured Data Generator**
- Function: `ai_structured_data()`
- Purpose: Rich snippets
- Integration: Content enhancement
### New Components
1. **Content Structure Analyzer**
- Purpose: Map website content structure
- Output: Content hierarchy and relationships
2. **Competitor Content Analyzer**
- Purpose: Analyze competitor content strategy
- Output: Content gaps and opportunities
3. **Keyword Opportunity Finder**
- Purpose: Identify keyword gaps
- Output: Keyword recommendations
4. **AI Recommendation Engine**
- Purpose: Generate content recommendations
- Output: Actionable content strategy
## Future Enhancements
1. **Advanced Analytics**
- Content performance tracking
- ROI analysis
- Trend prediction
2. **Automation Features**
- Automated content planning
- Schedule generation
- Priority scoring
3. **Integration Expansion**
- CMS integration
- Analytics platform connection
- Social media analysis
4. **AI Improvements**
- Advanced topic modeling
- Sentiment analysis
- Content quality scoring

View File

@@ -0,0 +1,36 @@
"""
Content Gap Analysis Tool for Alwrity.
"""
from .ui import ContentGapAnalysisUI
from .main import ContentGapAnalysis
from .keyword_researcher import KeywordResearcher
from .competitor_analyzer import CompetitorAnalyzer
from .website_analyzer import WebsiteAnalyzer
from .recommendation_engine import RecommendationEngine
from .utils.ai_processor import AIProcessor
__all__ = [
'ContentGapAnalysisUI',
'ContentGapAnalysis',
'KeywordResearcher',
'CompetitorAnalyzer',
'WebsiteAnalyzer',
'RecommendationEngine',
'AIProcessor'
]
def run_content_gap_analysis():
"""Run the Content Gap Analysis tool."""
# Initialize the UI with proper configuration
ui = ContentGapAnalysisUI()
# Set up the page configuration
st.set_page_config(
page_title="Content Gap Analysis",
page_icon="📊",
layout="wide"
)
# Run the UI
ui.run()

View File

@@ -0,0 +1,711 @@
"""
Competitor analyzer for content gap analysis.
"""
from typing import Dict, Any, List, Optional
import streamlit as st
from collections import Counter, defaultdict
from loguru import logger
from lib.utils.website_analyzer.analyzer import WebsiteAnalyzer
from lib.ai_seo_tools.content_gap_analysis.utils.data_collector import DataCollector
from lib.ai_seo_tools.content_gap_analysis.utils.content_parser import ContentParser
from lib.ai_seo_tools.content_gap_analysis.utils.ai_processor import AIProcessor, ProgressTracker
import asyncio
import sys
import os
import json
from lib.gpt_providers.text_generation.main_text_generation import llm_text_gen
# Configure logger
logger.remove() # Remove default handler
logger.add(
"logs/competitor_analyzer.log",
rotation="50 MB",
retention="10 days",
level="DEBUG",
format="{time:YYYY-MM-DD HH:mm:ss} | {level} | {message}"
)
logger.add(
sys.stdout,
level="INFO",
format="<green>{time:YYYY-MM-DD HH:mm:ss}</green> | <level>{level: <8}</level> | <cyan>{message}</cyan>"
)
# Ensure logs directory exists
os.makedirs("logs", exist_ok=True)
class CompetitorAnalyzer:
"""Analyzes competitor content and market position."""
def __init__(self):
"""Initialize the competitor analyzer."""
self.website_analyzer = WebsiteAnalyzer()
self.ai_processor = AIProcessor()
self.progress = ProgressTracker()
# Define analysis stages
self.stages = {
'competitor_analysis': {
'name': 'Competitor Analysis',
'steps': [
'Initializing competitor analysis',
'Analyzing competitor content',
'Evaluating market position',
'Identifying content gaps',
'Generating competitive insights'
]
}
}
logger.info("CompetitorAnalyzer initialized")
def analyze(self, competitor_urls: List[str], industry: str) -> Dict[str, Any]:
"""
Analyze competitor websites.
Args:
competitor_urls: List of competitor URLs to analyze
industry: Industry category
Returns:
Dictionary containing competitor analysis results
"""
try:
results = {
'competitors': [],
'market_position': {},
'content_gaps': [],
'advantages': []
}
# Analyze each competitor
for url in competitor_urls:
competitor_analysis = self.website_analyzer.analyze_website(url)
if competitor_analysis.get('success', False):
results['competitors'].append({
'url': url,
'analysis': competitor_analysis['data']
})
# Generate market position analysis using AI
prompt = f"""Analyze the market position of competitors in the {industry} industry:
Competitor Analyses:
{json.dumps(results['competitors'], indent=2)}
Provide:
1. Market position analysis
2. Content gaps
3. Competitive advantages
Format the response as JSON with 'market_position', 'content_gaps', and 'advantages' keys."""
# Get AI analysis
analysis = llm_text_gen(
prompt=prompt,
system_prompt="You are an SEO expert specializing in competitive analysis.",
response_format="json_object"
)
if analysis:
results['market_position'] = analysis.get('market_position', {})
results['content_gaps'] = analysis.get('content_gaps', [])
results['advantages'] = analysis.get('advantages', [])
return results
except Exception as e:
error_msg = f"Error analyzing competitors: {str(e)}"
logger.error(error_msg, exc_info=True)
return {
'error': error_msg,
'competitors': [],
'market_position': {},
'content_gaps': [],
'advantages': []
}
def _analyze_competitor_content(self, competitor_urls: List[str]) -> Dict[str, Any]:
"""Analyze competitor content."""
try:
content_analysis = {}
for url in competitor_urls:
# Get AI analysis for each competitor
analysis = self.ai_processor.analyze_content({
'url': url,
'content': {} # Content will be fetched by AI processor
})
content_analysis[url] = {
'content_metrics': analysis.get('content_metrics', {}),
'content_evolution': analysis.get('content_evolution', {}),
'topic_trends': analysis.get('topic_trends', {}),
'performance_trends': analysis.get('performance_trends', {})
}
return content_analysis
except Exception as e:
st.error(f"Error analyzing competitor content: {str(e)}")
return {}
def _evaluate_market_position(self, content_analysis: Dict[str, Any], industry: str) -> Dict[str, Any]:
"""Evaluate market position."""
try:
market_position = {
'industry_rank': 0,
'content_quality_rank': 0,
'market_share': 0,
'competitive_advantages': [],
'competitive_disadvantages': []
}
# Calculate industry rank based on content quality
content_quality_scores = [
analysis.get('content_metrics', {}).get('quality_score', 0)
for analysis in content_analysis.values()
]
if content_quality_scores:
market_position['content_quality_rank'] = sum(content_quality_scores) / len(content_quality_scores)
# Identify competitive advantages and disadvantages
for url, analysis in content_analysis.items():
quality_score = analysis.get('content_metrics', {}).get('quality_score', 0)
if quality_score > market_position['content_quality_rank']:
market_position['competitive_advantages'].append({
'url': url,
'advantage': 'Higher content quality',
'score': quality_score
})
elif quality_score < market_position['content_quality_rank']:
market_position['competitive_disadvantages'].append({
'url': url,
'disadvantage': 'Lower content quality',
'score': quality_score
})
return market_position
except Exception as e:
st.error(f"Error evaluating market position: {str(e)}")
return {}
def _identify_content_gaps(self, content_analysis: Dict[str, Any]) -> List[Dict[str, Any]]:
"""Identify content gaps."""
try:
content_gaps = []
# Analyze content coverage
all_topics = set()
for analysis in content_analysis.values():
topics = analysis.get('topic_trends', {}).get('topics', [])
all_topics.update(topics)
# Identify missing topics for each competitor
for url, analysis in content_analysis.items():
covered_topics = set(analysis.get('topic_trends', {}).get('topics', []))
missing_topics = all_topics - covered_topics
if missing_topics:
content_gaps.append({
'url': url,
'missing_topics': list(missing_topics),
'gap_type': 'topic_coverage'
})
return content_gaps
except Exception as e:
st.error(f"Error identifying content gaps: {str(e)}")
return []
def _generate_competitive_insights(self, content_analysis: Dict[str, Any], market_position: Dict[str, Any], content_gaps: List[Dict[str, Any]]) -> List[str]:
"""Generate competitive insights."""
try:
insights = []
# Market position insights
if market_position.get('content_quality_rank', 0) > 80:
insights.append("Strong market position with high content quality")
elif market_position.get('content_quality_rank', 0) > 60:
insights.append("Moderate market position with room for improvement")
else:
insights.append("Weak market position requiring significant improvement")
# Content gap insights
if content_gaps:
insights.append(f"Identified {len(content_gaps)} content gaps across competitors")
# Competitive advantage insights
if market_position.get('competitive_advantages'):
insights.append(f"Found {len(market_position['competitive_advantages'])} competitive advantages")
return insights
except Exception as e:
st.error(f"Error generating competitive insights: {str(e)}")
return []
def _run_seo_analysis(self, url: str) -> dict:
"""
Run SEO analysis on competitor website.
Args:
url (str): The URL to analyze
Returns:
dict: SEO analysis results
"""
# Run website analysis using the new analyzer
analysis = self.website_analyzer.analyze_website(url)
if not analysis.get('success', False):
return {
'error': analysis.get('error', 'Unknown error in SEO analysis'),
'onpage_seo': {},
'url_seo': {}
}
# Extract SEO information from the analysis
seo_info = analysis['data']['analysis']['seo_info']
basic_info = analysis['data']['analysis']['basic_info']
return {
'onpage_seo': {
'meta_tags': seo_info.get('meta_tags', {}),
'content': seo_info.get('content', {}),
'recommendations': seo_info.get('recommendations', [])
},
'url_seo': {
'title': basic_info.get('title', ''),
'meta_description': basic_info.get('meta_description', ''),
'has_robots_txt': bool(basic_info.get('robots_txt')),
'has_sitemap': bool(basic_info.get('sitemap'))
}
}
def _analyze_title_patterns(self, url: str) -> dict:
"""
Analyze title patterns using the title generator.
Args:
url (str): The URL to analyze
Returns:
dict: Title pattern analysis results
"""
# Use title generator to analyze patterns
title_analysis = ai_title_generator(url)
return {
'patterns': title_analysis.get('patterns', {}),
'suggestions': title_analysis.get('suggestions', [])
}
def _compare_competitors(self, results: dict) -> dict:
"""
Compare results across all competitors.
Args:
results (dict): Analysis results for all competitors
Returns:
dict: Comparative analysis results
"""
comparison = {
'content_comparison': self._compare_content(results),
'seo_comparison': self._compare_seo(results),
'title_comparison': self._compare_titles(results),
'performance_metrics': self._compare_performance(results),
'content_gaps': self._identify_content_gaps(results)
}
# Add AI-enhanced insights
comparison['ai_insights'] = self.ai_processor.analyze_competitor_comparison(comparison)
return comparison
def _compare_content(self, results: dict) -> dict:
"""Compare content structure across competitors."""
content_comparison = {
'topic_distribution': self._analyze_topic_distribution(results),
'content_depth': self._analyze_content_depth(results),
'content_formats': self._analyze_content_formats(results),
'content_quality': self._analyze_content_quality(results)
}
return content_comparison
def _analyze_topic_distribution(self, results: dict) -> dict:
"""Analyze topic distribution across competitors."""
all_topics = []
topic_frequency = Counter()
for url, data in results.items():
topics = data['content_structure'].get('topics', [])
all_topics.extend([t['topic'] for t in topics])
topic_frequency.update([t['topic'] for t in topics])
return {
'common_topics': [topic for topic, count in topic_frequency.most_common(10)],
'unique_topics': list(set(all_topics)),
'topic_frequency': dict(topic_frequency.most_common()),
'topic_coverage': len(set(all_topics)) / len(all_topics) if all_topics else 0
}
def _analyze_content_depth(self, results: dict) -> dict:
"""Analyze content depth across competitors."""
depth_metrics = {
'word_counts': {},
'section_counts': {},
'heading_distribution': defaultdict(list),
'content_hierarchy': {}
}
for url, data in results.items():
content_structure = data['content_structure']
# Word count analysis
depth_metrics['word_counts'][url] = content_structure.get('text_statistics', {}).get('word_count', 0)
# Section analysis
depth_metrics['section_counts'][url] = len(content_structure.get('sections', []))
# Heading distribution
for level, count in content_structure.get('hierarchy', {}).get('heading_distribution', {}).items():
depth_metrics['heading_distribution'][level].append(count)
# Content hierarchy
depth_metrics['content_hierarchy'][url] = content_structure.get('hierarchy', {})
return depth_metrics
def _analyze_content_formats(self, results: dict) -> dict:
"""Analyze content formats across competitors."""
format_analysis = {
'format_types': defaultdict(int),
'format_distribution': defaultdict(list),
'format_effectiveness': {}
}
for url, data in results.items():
sections = data['content_structure'].get('sections', [])
for section in sections:
format_type = section.get('type', 'unknown')
format_analysis['format_types'][format_type] += 1
format_analysis['format_distribution'][format_type].append({
'url': url,
'heading': section.get('heading', ''),
'word_count': section.get('word_count', 0)
})
return format_analysis
def _analyze_content_quality(self, results: dict) -> dict:
"""Analyze content quality across competitors."""
quality_metrics = {
'readability_scores': {},
'content_structure_scores': {},
'engagement_metrics': {},
'overall_quality': {}
}
for url, data in results.items():
content_structure = data['content_structure']
# Readability analysis
readability = content_structure.get('readability', {})
quality_metrics['readability_scores'][url] = {
'flesch_score': readability.get('flesch_score', 0),
'avg_sentence_length': readability.get('avg_sentence_length', 0),
'avg_word_length': readability.get('avg_word_length', 0)
}
# Structure analysis
hierarchy = content_structure.get('hierarchy', {})
quality_metrics['content_structure_scores'][url] = {
'has_proper_hierarchy': hierarchy.get('has_proper_hierarchy', False),
'heading_distribution': hierarchy.get('heading_distribution', {}),
'max_depth': hierarchy.get('max_depth', 0)
}
return quality_metrics
def _compare_seo(self, results: dict) -> dict:
"""Compare SEO metrics across competitors."""
seo_comparison = {
'onpage_metrics': defaultdict(list),
'technical_metrics': defaultdict(list),
'content_metrics': defaultdict(list),
'overall_seo_score': {}
}
for url, data in results.items():
seo_info = data.get('website_analysis', {}).get('analysis', {}).get('seo_info', {})
# On-page SEO metrics
meta_tags = seo_info.get('meta_tags', {})
seo_comparison['onpage_metrics']['title_score'].append(
100 if meta_tags.get('title', {}).get('status') == 'good' else 50
)
seo_comparison['onpage_metrics']['description_score'].append(
100 if meta_tags.get('description', {}).get('status') == 'good' else 50
)
seo_comparison['onpage_metrics']['keywords_score'].append(
100 if meta_tags.get('keywords', {}).get('status') == 'good' else 50
)
# Technical SEO metrics
technical = data.get('website_analysis', {}).get('analysis', {}).get('basic_info', {})
seo_comparison['technical_metrics']['has_robots_txt'].append(
100 if technical.get('robots_txt') else 0
)
seo_comparison['technical_metrics']['has_sitemap'].append(
100 if technical.get('sitemap') else 0
)
# Content SEO metrics
content = seo_info.get('content', {})
seo_comparison['content_metrics']['readability_score'].append(
content.get('readability_score', 0)
)
seo_comparison['content_metrics']['content_quality_score'].append(
content.get('content_quality_score', 0)
)
# Overall SEO score
seo_comparison['overall_seo_score'][url] = seo_info.get('overall_score', 0)
return seo_comparison
def _compare_titles(self, results: dict) -> dict:
"""Compare title patterns across competitors."""
title_comparison = {
'pattern_distribution': defaultdict(int),
'length_distribution': defaultdict(list),
'keyword_usage': defaultdict(int),
'format_preferences': defaultdict(int)
}
for url, data in results.items():
title_patterns = data['title_patterns']
# Pattern analysis
for pattern in title_patterns.get('patterns', {}):
title_comparison['pattern_distribution'][pattern] += 1
# Length analysis
for suggestion in title_patterns.get('suggestions', []):
title_comparison['length_distribution'][len(suggestion)].append(suggestion)
# Keyword analysis
for suggestion in title_patterns.get('suggestions', []):
words = suggestion.lower().split()
for word in words:
if len(word) > 3: # Filter out short words
title_comparison['keyword_usage'][word] += 1
return title_comparison
def _compare_performance(self, results: dict) -> dict:
"""Compare performance metrics across competitors."""
performance_metrics = {
'content_effectiveness': {},
'engagement_metrics': {},
'technical_performance': {},
'overall_performance': {}
}
for url, data in results.items():
# Content effectiveness
content_structure = data['content_structure']
performance_metrics['content_effectiveness'][url] = {
'content_depth': content_structure.get('text_statistics', {}).get('word_count', 0),
'content_quality': content_structure.get('readability', {}).get('flesch_score', 0),
'content_structure': content_structure.get('hierarchy', {}).get('has_proper_hierarchy', False)
}
# Technical performance
seo_analysis = data['seo_analysis']
performance_metrics['technical_performance'][url] = {
'onpage_score': sum(1 for v in seo_analysis.get('onpage_seo', {}).values() if v),
'technical_score': sum(1 for v in seo_analysis.get('url_seo', {}).values() if v)
}
return performance_metrics
def _find_missing_topics(self, results: dict) -> List[Dict[str, Any]]:
"""Find topics that are missing or underrepresented."""
all_topics = set()
topic_coverage = defaultdict(int)
# Collect all topics and their coverage
for url, data in results.items():
topics = data['content_structure'].get('topics', [])
for topic in topics:
all_topics.add(topic['topic'])
topic_coverage[topic['topic']] += 1
# Identify missing or underrepresented topics
missing_topics = []
total_competitors = len(results)
for topic in all_topics:
coverage = topic_coverage[topic] / total_competitors
if coverage < 0.5: # Topic covered by less than 50% of competitors
missing_topics.append({
'topic': topic,
'coverage': coverage,
'opportunity_score': 1 - coverage
})
return sorted(missing_topics, key=lambda x: x['opportunity_score'], reverse=True)
def _identify_opportunities(self, results: dict) -> List[Dict[str, Any]]:
"""Identify content opportunities based on analysis."""
opportunities = []
# Analyze content depth opportunities
depth_metrics = self._analyze_content_depth(results)
avg_word_count = sum(depth_metrics['word_counts'].values()) / len(depth_metrics['word_counts'])
for url, word_count in depth_metrics['word_counts'].items():
if word_count < avg_word_count * 0.7: # Content depth significantly below average
opportunities.append({
'type': 'content_depth',
'url': url,
'current_value': word_count,
'target_value': avg_word_count,
'opportunity_score': (avg_word_count - word_count) / avg_word_count
})
# Analyze format opportunities
format_analysis = self._analyze_content_formats(results)
for format_type, distribution in format_analysis['format_distribution'].items():
if len(distribution) < len(results) * 0.3: # Format used by less than 30% of competitors
opportunities.append({
'type': 'content_format',
'format': format_type,
'current_coverage': len(distribution) / len(results),
'opportunity_score': 1 - (len(distribution) / len(results))
})
return sorted(opportunities, key=lambda x: x['opportunity_score'], reverse=True)
def _analyze_format_gaps(self, results: dict) -> List[Dict[str, Any]]:
"""Analyze gaps in content formats."""
format_gaps = []
format_analysis = self._analyze_content_formats(results)
# Identify underutilized formats
for format_type, count in format_analysis['format_types'].items():
if count < len(results) * 0.3: # Format used by less than 30% of competitors
format_gaps.append({
'format': format_type,
'current_usage': count,
'potential_impact': 'high' if count < len(results) * 0.2 else 'medium',
'suggested_implementation': self._generate_format_suggestions(format_type)
})
return format_gaps
def _analyze_quality_gaps(self, results: dict) -> List[Dict[str, Any]]:
"""Analyze gaps in content quality."""
quality_gaps = []
quality_metrics = self._analyze_content_quality(results)
# Analyze readability gaps
readability_scores = quality_metrics['readability_scores']
avg_flesch = sum(score['flesch_score'] for score in readability_scores.values()) / len(readability_scores)
for url, scores in readability_scores.items():
if scores['flesch_score'] < avg_flesch * 0.8: # Readability significantly below average
quality_gaps.append({
'type': 'readability',
'url': url,
'current_score': scores['flesch_score'],
'target_score': avg_flesch,
'improvement_needed': avg_flesch - scores['flesch_score']
})
return quality_gaps
def _analyze_seo_gaps(self, results: dict) -> List[Dict[str, Any]]:
"""Analyze gaps in SEO implementation."""
seo_gaps = []
seo_comparison = self._compare_seo(results)
# Analyze on-page SEO gaps
for metric, values in seo_comparison['onpage_metrics'].items():
avg_value = sum(values) / len(values)
for url, value in zip(results.keys(), values):
if value < avg_value * 0.7: # Significantly below average
seo_gaps.append({
'type': 'onpage_seo',
'metric': metric,
'url': url,
'current_value': value,
'target_value': avg_value,
'improvement_needed': avg_value - value
})
# Analyze technical SEO gaps
for metric, values in seo_comparison['technical_metrics'].items():
avg_value = sum(values) / len(values)
for url, value in zip(results.keys(), values):
if value < avg_value * 0.7: # Significantly below average
seo_gaps.append({
'type': 'technical_seo',
'metric': metric,
'url': url,
'current_value': value,
'target_value': avg_value,
'improvement_needed': avg_value - value
})
# Analyze content SEO gaps
for metric, values in seo_comparison['content_metrics'].items():
avg_value = sum(values) / len(values)
for url, value in zip(results.keys(), values):
if value < avg_value * 0.7: # Significantly below average
seo_gaps.append({
'type': 'content_seo',
'metric': metric,
'url': url,
'current_value': value,
'target_value': avg_value,
'improvement_needed': avg_value - value
})
return seo_gaps
def _generate_format_suggestions(self, format_type: str) -> List[str]:
"""Generate suggestions for implementing specific content formats."""
format_suggestions = {
'article': [
'Create in-depth articles with comprehensive coverage',
'Include expert quotes and statistics',
'Add visual elements and infographics'
],
'blog_post': [
'Write engaging blog posts with personal insights',
'Include call-to-actions',
'Add social sharing buttons'
],
'how-to': [
'Create step-by-step guides',
'Include screenshots or videos',
'Add troubleshooting sections'
],
'case_study': [
'Present real-world examples',
'Include metrics and results',
'Add client testimonials'
]
}
return format_suggestions.get(format_type, [
'Research successful examples',
'Analyze competitor implementation',
'Create unique value proposition'
])

View File

@@ -0,0 +1,649 @@
"""
Keyword researcher for content gap analysis.
"""
from typing import Dict, Any, List, Optional
import streamlit as st
from loguru import logger
from lib.utils.website_analyzer.analyzer import WebsiteAnalyzer
from lib.ai_seo_tools.content_gap_analysis.utils.data_collector import DataCollector
from lib.ai_seo_tools.content_gap_analysis.utils.content_parser import ContentParser
from lib.ai_seo_tools.content_gap_analysis.utils.ai_processor import AIProcessor, ProgressTracker
import asyncio
import sys
import os
import json
from lib.gpt_providers.text_generation.main_text_generation import llm_text_gen
from lib.ai_seo_tools.content_title_generator import ai_title_generator
from lib.ai_seo_tools.meta_desc_generator import metadesc_generator_main
from lib.ai_seo_tools.seo_structured_data import ai_structured_data
# Configure logger
logger.remove() # Remove default handler
logger.add(
"logs/keyword_researcher.log",
rotation="50 MB",
retention="10 days",
level="DEBUG",
format="{time:YYYY-MM-DD HH:mm:ss} | {level} | {message}"
)
logger.add(
sys.stdout,
level="INFO",
format="<green>{time:YYYY-MM-DD HH:mm:ss}</green> | <level>{level: <8}</level> | <cyan>{message}</cyan>"
)
# Ensure logs directory exists
os.makedirs("logs", exist_ok=True)
class KeywordResearcher:
"""Researches and analyzes keywords for content strategy."""
def __init__(self):
"""Initialize the keyword researcher."""
self.ai_processor = AIProcessor()
self.progress = ProgressTracker()
# Define analysis stages
self.stages = {
'keyword_analysis': {
'name': 'Keyword Analysis',
'steps': [
'Initializing keyword research',
'Analyzing keyword trends',
'Evaluating search intent',
'Identifying opportunities',
'Generating keyword insights'
]
}
}
def analyze(self, industry: str, url: str) -> Dict[str, Any]:
"""
Analyze keywords for content strategy.
Args:
industry: Industry category
url: Target website URL
Returns:
Dictionary containing analysis results
"""
try:
self.progress.start_stage('keyword_analysis')
self.progress.next_step()
# Analyze keyword trends
trend_analysis = self._analyze_keyword_trends(industry)
self.progress.next_step()
# Evaluate search intent
intent_analysis = self._evaluate_search_intent(trend_analysis)
self.progress.next_step()
# Identify opportunities
opportunities = self._identify_opportunities(trend_analysis, intent_analysis)
self.progress.next_step()
# Generate insights
insights = self._generate_keyword_insights(trend_analysis, intent_analysis, opportunities)
self.progress.next_step()
self.progress.complete_stage()
return {
'trend_analysis': trend_analysis,
'intent_analysis': intent_analysis,
'opportunities': opportunities,
'insights': insights
}
except Exception as e:
if self.progress.current_stage:
self.progress.update_progress(0, f"Error in {self.progress.stages[self.progress.current_stage]['name']}: {str(e)}")
st.error(f"Error analyzing keywords: {str(e)}")
return {
'error': str(e),
'trend_analysis': {},
'intent_analysis': {},
'opportunities': [],
'insights': []
}
def _analyze_keyword_trends(self, industry: str) -> Dict[str, Any]:
"""Analyze keyword trends."""
try:
# Get AI analysis for keyword trends
analysis = self.ai_processor.analyze_keywords({
'industry': industry,
'keywords': {} # Keywords will be fetched by AI processor
})
return {
'trends': analysis.get('keyword_trends', {}),
'search_intent': analysis.get('search_intent', {}),
'keyword_insights': analysis.get('keyword_insights', {})
}
except Exception as e:
st.error(f"Error analyzing keyword trends: {str(e)}")
return {}
def _evaluate_search_intent(self, trend_analysis: Dict[str, Any]) -> Dict[str, Any]:
"""Evaluate search intent."""
try:
intent_analysis = {
'informational': [],
'transactional': [],
'navigational': [],
'commercial': []
}
# Categorize keywords by intent
for keyword, data in trend_analysis.get('trends', {}).items():
intent = data.get('intent', 'informational')
if intent in intent_analysis:
intent_analysis[intent].append({
'keyword': keyword,
'volume': data.get('volume', 0),
'difficulty': data.get('difficulty', 0)
})
return intent_analysis
except Exception as e:
st.error(f"Error evaluating search intent: {str(e)}")
return {}
def _identify_opportunities(self, trend_analysis: Dict[str, Any], intent_analysis: Dict[str, Any]) -> List[Dict[str, Any]]:
"""Identify keyword opportunities."""
try:
opportunities = []
# Analyze each intent category
for intent, keywords in intent_analysis.items():
for keyword_data in keywords:
# Calculate opportunity score
volume = keyword_data.get('volume', 0)
difficulty = keyword_data.get('difficulty', 0)
opportunity_score = volume * (1 - difficulty/100)
if opportunity_score > 50: # Threshold for good opportunities
opportunities.append({
'keyword': keyword_data['keyword'],
'intent': intent,
'volume': volume,
'difficulty': difficulty,
'opportunity_score': opportunity_score
})
# Sort by opportunity score
opportunities.sort(key=lambda x: x['opportunity_score'], reverse=True)
return opportunities
except Exception as e:
st.error(f"Error identifying opportunities: {str(e)}")
return []
def _generate_keyword_insights(self, trend_analysis: Dict[str, Any], intent_analysis: Dict[str, Any], opportunities: List[Dict[str, Any]]) -> List[str]:
"""Generate keyword insights."""
try:
insights = []
# Trend insights
if trend_analysis.get('trends'):
insights.append(f"Analyzed {len(trend_analysis['trends'])} keywords for trends")
# Intent insights
for intent, keywords in intent_analysis.items():
if keywords:
insights.append(f"Found {len(keywords)} {intent} keywords")
# Opportunity insights
if opportunities:
insights.append(f"Identified {len(opportunities)} high-potential keyword opportunities")
return insights
except Exception as e:
st.error(f"Error generating keyword insights: {str(e)}")
return []
def _generate_titles(self, industry: str) -> dict:
"""
Generate keyword-based titles using the title generator.
Args:
industry (str): The industry to generate titles for
Returns:
dict: Generated titles and patterns
"""
return ai_title_generator(industry)
def _analyze_meta_descriptions(self, industry: str) -> dict:
"""
Analyze meta descriptions for keyword usage.
Args:
industry (str): The industry to analyze
Returns:
dict: Meta description analysis results
"""
return metadesc_generator_main(industry)
def _analyze_structured_data(self, industry: str) -> dict:
"""
Analyze structured data implementation.
Args:
industry (str): The industry to analyze
Returns:
dict: Structured data analysis results
"""
return ai_structured_data(industry)
def _extract_keywords(self, titles: dict, meta_analysis: dict) -> list:
"""
Extract keywords from titles and meta descriptions.
Args:
titles (dict): Generated titles
meta_analysis (dict): Meta description analysis
Returns:
list: Extracted keywords with metrics
"""
prompt = f"""
As an SEO expert, analyze the following content and extract relevant keywords with their metrics:
Titles: {titles}
Meta Descriptions: {meta_analysis}
Please provide a JSON response with the following structure:
{{
"keywords": [
{{
"keyword": "string",
"search_volume": "number",
"difficulty": "number",
"relevance_score": "number",
"content_type": "string"
}}
],
"summary": {{
"total_keywords": "number",
"high_opportunity_keywords": "number",
"recommended_focus_areas": ["string"]
}}
}}
Focus on:
1. Primary keywords and their variations
2. Long-tail keywords
3. Industry-specific terminology
4. Search volume and difficulty metrics
5. Content type recommendations
"""
try:
response = llm_text_gen(prompt, json_struct={
"type": "object",
"properties": {
"keywords": {
"type": "array",
"items": {
"type": "object",
"properties": {
"keyword": {"type": "string"},
"search_volume": {"type": "number"},
"difficulty": {"type": "number"},
"relevance_score": {"type": "number"},
"content_type": {"type": "string"}
}
}
},
"summary": {
"type": "object",
"properties": {
"total_keywords": {"type": "number"},
"high_opportunity_keywords": {"type": "number"},
"recommended_focus_areas": {
"type": "array",
"items": {"type": "string"}
}
}
}
}
})
return response
except Exception as e:
st.error(f"Error extracting keywords: {e}")
return []
def _analyze_search_intent(self, ai_insights: dict) -> dict:
"""
Analyze search intent from AI insights.
Args:
ai_insights (dict): AI-processed insights
Returns:
dict: Search intent analysis
"""
prompt = f"""
As an SEO expert, analyze the following content insights and determine the search intent:
Content Insights: {ai_insights}
Please provide a JSON response with the following structure:
{{
"informational": [
{{
"keyword": "string",
"intent_type": "string",
"content_suggestions": ["string"]
}}
],
"transactional": [
{{
"keyword": "string",
"intent_type": "string",
"content_suggestions": ["string"]
}}
],
"navigational": [
{{
"keyword": "string",
"intent_type": "string",
"content_suggestions": ["string"]
}}
],
"summary": {{
"dominant_intent": "string",
"content_strategy_recommendations": ["string"]
}}
}}
Focus on:
1. Identifying primary search intent for each keyword
2. Suggesting appropriate content types
3. Providing content strategy recommendations
4. Analyzing user behavior patterns
"""
try:
response = llm_text_gen(prompt, json_struct={
"type": "object",
"properties": {
"informational": {
"type": "array",
"items": {
"type": "object",
"properties": {
"keyword": {"type": "string"},
"intent_type": {"type": "string"},
"content_suggestions": {
"type": "array",
"items": {"type": "string"}
}
}
}
},
"transactional": {
"type": "array",
"items": {
"type": "object",
"properties": {
"keyword": {"type": "string"},
"intent_type": {"type": "string"},
"content_suggestions": {
"type": "array",
"items": {"type": "string"}
}
}
}
},
"navigational": {
"type": "array",
"items": {
"type": "object",
"properties": {
"keyword": {"type": "string"},
"intent_type": {"type": "string"},
"content_suggestions": {
"type": "array",
"items": {"type": "string"}
}
}
}
},
"summary": {
"type": "object",
"properties": {
"dominant_intent": {"type": "string"},
"content_strategy_recommendations": {
"type": "array",
"items": {"type": "string"}
}
}
}
}
})
return response
except Exception as e:
st.error(f"Error analyzing search intent: {e}")
return {
'informational': [],
'transactional': [],
'navigational': []
}
def _suggest_content_formats(self, ai_insights: dict) -> list:
"""
Suggest content formats based on AI insights.
Args:
ai_insights (dict): AI-processed insights
Returns:
list: Suggested content formats
"""
prompt = f"""
As a content strategy expert, analyze the following insights and suggest appropriate content formats:
AI Insights: {ai_insights}
Please provide a JSON response with the following structure:
{{
"content_formats": [
{{
"format": "string",
"description": "string",
"use_cases": ["string"],
"recommended_topics": ["string"],
"estimated_impact": "string"
}}
],
"format_strategy": {{
"primary_formats": ["string"],
"secondary_formats": ["string"],
"implementation_priority": ["string"]
}}
}}
Focus on:
1. Identifying the most effective content formats
2. Matching formats to user intent
3. Suggesting specific use cases
4. Providing implementation guidance
"""
try:
response = llm_text_gen(prompt, json_struct={
"type": "object",
"properties": {
"content_formats": {
"type": "array",
"items": {
"type": "object",
"properties": {
"format": {"type": "string"},
"description": {"type": "string"},
"use_cases": {
"type": "array",
"items": {"type": "string"}
},
"recommended_topics": {
"type": "array",
"items": {"type": "string"}
},
"estimated_impact": {"type": "string"}
}
}
},
"format_strategy": {
"type": "object",
"properties": {
"primary_formats": {
"type": "array",
"items": {"type": "string"}
},
"secondary_formats": {
"type": "array",
"items": {"type": "string"}
},
"implementation_priority": {
"type": "array",
"items": {"type": "string"}
}
}
}
}
})
return response
except Exception as e:
st.error(f"Error suggesting content formats: {e}")
return []
def _create_topic_clusters(self, ai_insights: dict) -> dict:
"""
Create topic clusters from AI insights.
Args:
ai_insights (dict): AI-processed insights
Returns:
dict: Topic clusters and relationships
"""
prompt = f"""
As a content organization expert, analyze the following insights and create topic clusters:
AI Insights: {ai_insights}
Please provide a JSON response with the following structure:
{{
"clusters": [
{{
"cluster_name": "string",
"main_topics": ["string"],
"subtopics": ["string"],
"related_keywords": ["string"],
"content_opportunities": ["string"]
}}
],
"relationships": {{
"cluster_connections": [
{{
"source": "string",
"target": "string",
"relationship_type": "string",
"strength": "number"
}}
],
"content_hierarchy": {{
"primary_topics": ["string"],
"secondary_topics": ["string"],
"tertiary_topics": ["string"]
}}
}}
}}
Focus on:
1. Identifying main topic clusters
2. Organizing subtopics and related keywords
3. Mapping relationships between clusters
4. Suggesting content opportunities
"""
try:
response = llm_text_gen(prompt, json_struct={
"type": "object",
"properties": {
"clusters": {
"type": "array",
"items": {
"type": "object",
"properties": {
"cluster_name": {"type": "string"},
"main_topics": {
"type": "array",
"items": {"type": "string"}
},
"subtopics": {
"type": "array",
"items": {"type": "string"}
},
"related_keywords": {
"type": "array",
"items": {"type": "string"}
},
"content_opportunities": {
"type": "array",
"items": {"type": "string"}
}
}
}
},
"relationships": {
"type": "object",
"properties": {
"cluster_connections": {
"type": "array",
"items": {
"type": "object",
"properties": {
"source": {"type": "string"},
"target": {"type": "string"},
"relationship_type": {"type": "string"},
"strength": {"type": "number"}
}
}
},
"content_hierarchy": {
"type": "object",
"properties": {
"primary_topics": {
"type": "array",
"items": {"type": "string"}
},
"secondary_topics": {
"type": "array",
"items": {"type": "string"}
},
"tertiary_topics": {
"type": "array",
"items": {"type": "string"}
}
}
}
}
}
}
})
return response
except Exception as e:
st.error(f"Error creating topic clusters: {e}")
return {
'clusters': [],
'relationships': {}
}

View File

@@ -0,0 +1,361 @@
"""
Main module for content gap analysis.
"""
from typing import Dict, Any, List, Optional
import streamlit as st
from loguru import logger
from lib.utils.website_analyzer.analyzer import WebsiteAnalyzer
from .competitor_analyzer import CompetitorAnalyzer
from .keyword_researcher import KeywordResearcher
from .recommendation_engine import RecommendationEngine
from .utils.ai_processor import AIProcessor, ProgressTracker
from .utils.storage import ContentGapAnalysisStorage
from datetime import datetime
import asyncio
import sys
import os
from lib.gpt_providers.text_generation.main_text_generation import llm_text_gen
from .utils.content_parser import ContentParser
# Configure logger
logger.remove() # Remove default handler
logger.add(
"logs/content_gap_analysis.log",
rotation="50 MB",
retention="10 days",
level="DEBUG",
format="{time:YYYY-MM-DD HH:mm:ss} | {level} | {message}"
)
logger.add(
sys.stdout,
level="INFO",
format="<green>{time:YYYY-MM-DD HH:mm:ss}</green> | <level>{level: <8}</level> | <cyan>{message}</cyan>"
)
# Ensure logs directory exists
os.makedirs("logs", exist_ok=True)
class ContentGapAnalysis:
"""Main class for content gap analysis."""
def __init__(self, db_session=None):
"""Initialize the content gap analysis components."""
self.website_analyzer = WebsiteAnalyzer()
self.competitor_analyzer = CompetitorAnalyzer()
self.keyword_researcher = KeywordResearcher()
self.recommendation_engine = RecommendationEngine()
self.ai_processor = AIProcessor()
self.progress = ProgressTracker()
self.storage = ContentGapAnalysisStorage(db_session) if db_session else None
# Define analysis phases
self.phases = {
'website_analysis': {
'name': 'Website Analysis',
'steps': [
'Initializing website analysis',
'Analyzing website content',
'Evaluating SEO elements',
'Generating website insights'
]
},
'competitor_analysis': {
'name': 'Competitor Analysis',
'steps': [
'Initializing competitor analysis',
'Analyzing competitor content',
'Comparing market position',
'Generating competitive insights'
]
},
'keyword_analysis': {
'name': 'Keyword Analysis',
'steps': [
'Initializing keyword research',
'Analyzing keyword trends',
'Evaluating search intent',
'Generating keyword insights'
]
},
'recommendation_generation': {
'name': 'Recommendation Generation',
'steps': [
'Initializing recommendation engine',
'Analyzing content gaps',
'Generating recommendations',
'Creating implementation plan'
]
}
}
logger.info("ContentGapAnalysis initialized")
def analyze(self, url: str, industry: str, competitor_urls: Optional[List[str]] = None, user_id: Optional[int] = None) -> Dict[str, Any]:
"""
Run the complete content gap analysis workflow.
Args:
url: Target website URL
industry: Industry category
competitor_urls: Optional list of competitor URLs
user_id: Optional user ID for storing results
Returns:
Dictionary containing analysis results
"""
try:
results = {}
start_time = datetime.utcnow()
# Phase 1: Website Analysis
self.progress.start_stage('website_analysis')
self.progress.next_step()
website_analysis = self.website_analyzer.analyze(url)
results['website'] = website_analysis
self.progress.next_step()
self.progress.complete_stage()
# Phase 2: Competitor Analysis
if competitor_urls:
self.progress.start_stage('competitor_analysis')
self.progress.next_step()
competitor_analysis = self.competitor_analyzer.analyze(competitor_urls, industry)
results['competitors'] = competitor_analysis
self.progress.next_step()
self.progress.complete_stage()
# Phase 3: Keyword Analysis
self.progress.start_stage('keyword_analysis')
self.progress.next_step()
keyword_analysis = self.keyword_researcher.analyze(industry, url)
results['keywords'] = keyword_analysis
self.progress.next_step()
self.progress.complete_stage()
# Phase 4: Recommendation Generation
self.progress.start_stage('recommendation_generation')
self.progress.next_step()
recommendations = self.recommendation_engine.generate_recommendations(
website_analysis,
competitor_analysis if competitor_urls else None,
keyword_analysis
)
results['recommendations'] = recommendations
self.progress.next_step()
self.progress.complete_stage()
# Calculate analysis duration
end_time = datetime.utcnow()
results['duration'] = (end_time - start_time).total_seconds()
# Store results if user_id is provided and storage is available
if user_id and self.storage:
analysis_id = self.storage.save_analysis(user_id, url, industry, results)
if analysis_id:
results['analysis_id'] = analysis_id
return results
except Exception as e:
if self.progress.current_stage:
self.progress.update_progress(0, f"Error in {self.progress.stages[self.progress.current_stage]['name']}: {str(e)}")
st.error(f"Error in content gap analysis: {str(e)}")
return {
'error': str(e),
'website': {},
'competitors': [],
'keywords': {},
'recommendations': []
}
def get_analysis(self, analysis_id: int) -> Optional[Dict[str, Any]]:
"""
Retrieve stored analysis results.
Args:
analysis_id: Analysis ID
Returns:
Dictionary containing analysis results if found, None otherwise
"""
if not self.storage:
st.error("Storage not initialized")
return None
return self.storage.get_analysis(analysis_id)
def get_user_analyses(self, user_id: int) -> List[Dict[str, Any]]:
"""
Get all analyses for a user.
Args:
user_id: User ID
Returns:
List of analysis summaries
"""
if not self.storage:
st.error("Storage not initialized")
return []
return self.storage.get_user_analyses(user_id)
def update_recommendation_status(self, recommendation_id: int, status: str) -> bool:
"""
Update the status of a recommendation.
Args:
recommendation_id: Recommendation ID
status: New status
Returns:
True if successful, False otherwise
"""
if not self.storage:
st.error("Storage not initialized")
return False
return self.storage.update_recommendation_status(recommendation_id, status)
def delete_analysis(self, analysis_id: int) -> bool:
"""
Delete an analysis and all related data.
Args:
analysis_id: Analysis ID
Returns:
True if successful, False otherwise
"""
if not self.storage:
st.error("Storage not initialized")
return False
return self.storage.delete_analysis(analysis_id)
def get_analysis_summary(self, results: Dict[str, Any]) -> Dict[str, Any]:
"""
Generate a summary of the analysis results.
Args:
results: Dictionary containing analysis results
Returns:
Dictionary containing summary metrics and insights
"""
try:
self.progress.start_stage('summary_generation')
self.progress.next_step()
summary = {
'website_metrics': self._summarize_website_metrics(results.get('website', {})),
'competitor_insights': self._summarize_competitor_insights(results.get('competitors', {})),
'keyword_opportunities': self._summarize_keyword_opportunities(results.get('keywords', {})),
'recommendation_highlights': self._summarize_recommendations(results.get('recommendations', {})),
'ai_insights': results.get('ai_insights', {})
}
self.progress.complete_stage()
return summary
except Exception as e:
if self.progress.current_stage:
self.progress.update_progress(0, f"Error generating summary: {str(e)}")
st.error(f"Error generating analysis summary: {str(e)}")
return {
'error': str(e),
'website_metrics': {},
'competitor_insights': {},
'keyword_opportunities': {},
'recommendation_highlights': {},
'ai_insights': {}
}
def export_results(self, results: Dict[str, Any], format: str = 'json') -> str:
"""
Export analysis results in the specified format.
Args:
results: Dictionary containing analysis results
format: Export format ('json' or 'csv')
Returns:
String containing exported results
"""
try:
self.progress.start_stage('export')
self.progress.next_step()
if format.lower() == 'json':
import json
exported = json.dumps(results, indent=2)
elif format.lower() == 'csv':
import pandas as pd
# Convert results to DataFrame and then to CSV
df = pd.DataFrame(results)
exported = df.to_csv(index=False)
else:
raise ValueError(f"Unsupported export format: {format}")
self.progress.complete_stage()
return exported
except Exception as e:
if self.progress.current_stage:
self.progress.update_progress(0, f"Error exporting results: {str(e)}")
st.error(f"Error exporting results: {str(e)}")
return str(e)
def _summarize_website_metrics(self, website_data: Dict[str, Any]) -> Dict[str, Any]:
"""Generate summary of website metrics."""
try:
return {
'content_score': website_data.get('content_score', 0),
'seo_score': website_data.get('seo_score', 0),
'structure_score': website_data.get('structure_score', 0),
'key_insights': website_data.get('insights', [])[:5] # Top 5 insights
}
except Exception as e:
st.error(f"Error summarizing website metrics: {str(e)}")
return {}
def _summarize_competitor_insights(self, competitor_data: Dict[str, Any]) -> Dict[str, Any]:
"""Generate summary of competitor insights."""
try:
return {
'market_position': competitor_data.get('market_position', {}),
'content_gaps': competitor_data.get('content_gaps', [])[:5], # Top 5 gaps
'competitive_advantages': competitor_data.get('advantages', [])[:5] # Top 5 advantages
}
except Exception as e:
st.error(f"Error summarizing competitor insights: {str(e)}")
return {}
def _summarize_keyword_opportunities(self, keyword_data: Dict[str, Any]) -> Dict[str, Any]:
"""Generate summary of keyword opportunities."""
try:
return {
'top_keywords': keyword_data.get('top_keywords', [])[:10], # Top 10 keywords
'search_intent': keyword_data.get('search_intent', {}),
'opportunities': keyword_data.get('opportunities', [])[:5] # Top 5 opportunities
}
except Exception as e:
st.error(f"Error summarizing keyword opportunities: {str(e)}")
return {}
def _summarize_recommendations(self, recommendation_data: Dict[str, Any]) -> Dict[str, Any]:
"""Generate summary of recommendations."""
try:
return {
'priority_recommendations': recommendation_data.get('priority_recommendations', [])[:5], # Top 5 recommendations
'implementation_timeline': recommendation_data.get('timeline', {}),
'expected_impact': recommendation_data.get('impact', {})
}
except Exception as e:
st.error(f"Error summarizing recommendations: {str(e)}")
return {}

View File

@@ -0,0 +1,41 @@
"""
Navigation component for Content Gap Analysis tool.
"""
import streamlit as st
def show_content_gap_analysis_nav():
"""Show navigation for Content Gap Analysis tool."""
st.sidebar.title("Content Gap Analysis")
st.sidebar.markdown("""
Analyze your content strategy, identify gaps, and get AI-powered recommendations.
""")
# Navigation options
nav_option = st.sidebar.radio(
"Select Analysis Type",
["Website Analysis", "Competitor Analysis", "Keyword Research", "Recommendations"]
)
# Tool description
st.sidebar.markdown("""
### Features
- Website content analysis
- Competitor content comparison
- Keyword research and trends
- AI-powered recommendations
- Content gap identification
- Implementation timeline
""")
# Help section
with st.sidebar.expander("How to Use"):
st.markdown("""
1. Start with Website Analysis
2. Add competitor URLs
3. Research keywords
4. Get recommendations
5. Export results
""")
return nav_option

View File

@@ -0,0 +1,440 @@
"""
Recommendation engine for content gap analysis.
"""
import streamlit as st
from typing import Dict, Any, List, Optional
from loguru import logger
from lib.utils.website_analyzer.analyzer import WebsiteAnalyzer
from lib.ai_seo_tools.content_gap_analysis.utils.data_collector import DataCollector
from lib.ai_seo_tools.content_gap_analysis.utils.content_parser import ContentParser
from lib.ai_seo_tools.content_gap_analysis.utils.ai_processor import AIProcessor, ProgressTracker
from lib.ai_seo_tools.content_title_generator import ai_title_generator
import asyncio
import sys
import os
import json
from lib.gpt_providers.text_generation.main_text_generation import llm_text_gen
# Configure logger
logger.remove() # Remove default handler
logger.add(
"logs/recommendation_engine.log",
rotation="50 MB",
retention="10 days",
level="DEBUG",
format="{time:YYYY-MM-DD HH:mm:ss} | {level} | {message}"
)
logger.add(
sys.stdout,
level="INFO",
format="<green>{time:YYYY-MM-DD HH:mm:ss}</green> | <level>{level: <8}</level> | <cyan>{message}</cyan>"
)
# Ensure logs directory exists
os.makedirs("logs", exist_ok=True)
class RecommendationEngine:
"""
Generates content recommendations based on analysis results.
"""
def __init__(self):
"""Initialize the recommendation engine with required components."""
self.ai_processor = AIProcessor()
self.progress = ProgressTracker()
# Define analysis stages
self.stages = {
'recommendation_generation': {
'name': 'Recommendation Generation',
'steps': [
'Initializing recommendation engine',
'Analyzing content gaps',
'Evaluating opportunities',
'Generating recommendations',
'Creating implementation plan'
]
}
}
def generate_recommendations(self, website_analysis: Dict[str, Any], competitor_analysis: Optional[Dict[str, Any]], keyword_analysis: Dict[str, Any]) -> Dict[str, Any]:
"""
Generate content recommendations.
Args:
website_analysis: Website analysis results
competitor_analysis: Optional competitor analysis results
keyword_analysis: Keyword analysis results
Returns:
Dictionary containing recommendations
"""
try:
self.progress.start_stage('recommendation_generation')
self.progress.next_step()
# Analyze content gaps
content_gaps = self._analyze_content_gaps(website_analysis, competitor_analysis, keyword_analysis)
self.progress.next_step()
# Evaluate opportunities
opportunities = self._evaluate_opportunities(content_gaps, keyword_analysis)
self.progress.next_step()
# Generate recommendations
recommendations = self._generate_recommendations(content_gaps, opportunities)
self.progress.next_step()
# Create implementation plan
implementation_plan = self._create_implementation_plan(recommendations)
self.progress.next_step()
self.progress.complete_stage()
return {
'content_gaps': content_gaps,
'opportunities': opportunities,
'recommendations': recommendations,
'implementation_plan': implementation_plan
}
except Exception as e:
if self.progress.current_stage:
self.progress.update_progress(0, f"Error in {self.progress.stages[self.progress.current_stage]['name']}: {str(e)}")
st.error(f"Error generating recommendations: {str(e)}")
return {
'error': str(e),
'content_gaps': [],
'opportunities': [],
'recommendations': [],
'implementation_plan': {}
}
def _analyze_content_gaps(self, website_analysis: Dict[str, Any], competitor_analysis: Optional[Dict[str, Any]], keyword_analysis: Dict[str, Any]) -> List[Dict[str, Any]]:
"""Analyze content gaps."""
try:
content_gaps = []
# Analyze website content gaps
website_gaps = self._analyze_website_gaps(website_analysis)
content_gaps.extend(website_gaps)
# Analyze competitor gaps if available
if competitor_analysis:
competitor_gaps = self._analyze_competitor_gaps(competitor_analysis)
content_gaps.extend(competitor_gaps)
# Analyze keyword gaps
keyword_gaps = self._analyze_keyword_gaps(keyword_analysis)
content_gaps.extend(keyword_gaps)
return content_gaps
except Exception as e:
st.error(f"Error analyzing content gaps: {str(e)}")
return []
def _analyze_website_gaps(self, website_analysis: Dict[str, Any]) -> List[Dict[str, Any]]:
"""Analyze website content gaps."""
try:
gaps = []
# Check content quality
quality_metrics = website_analysis.get('quality_metrics', {})
if quality_metrics.get('readability_score', 0) < 70:
gaps.append({
'type': 'content_quality',
'issue': 'Low readability score',
'score': quality_metrics.get('readability_score', 0),
'recommendation': 'Improve content readability'
})
# Check SEO elements
seo_metrics = website_analysis.get('seo_metrics', {})
if seo_metrics.get('seo_score', 0) < 70:
gaps.append({
'type': 'seo',
'issue': 'Low SEO score',
'score': seo_metrics.get('seo_score', 0),
'recommendation': 'Enhance SEO optimization'
})
return gaps
except Exception as e:
st.error(f"Error analyzing website gaps: {str(e)}")
return []
def _analyze_competitor_gaps(self, competitor_analysis: Dict[str, Any]) -> List[Dict[str, Any]]:
"""Analyze competitor content gaps."""
try:
gaps = []
# Check content gaps
content_gaps = competitor_analysis.get('content_gaps', [])
for gap in content_gaps:
gaps.append({
'type': 'competitor',
'issue': f"Missing topic: {', '.join(gap.get('missing_topics', []))}",
'recommendation': 'Create content for missing topics'
})
return gaps
except Exception as e:
st.error(f"Error analyzing competitor gaps: {str(e)}")
return []
def _analyze_keyword_gaps(self, keyword_analysis: Dict[str, Any]) -> List[Dict[str, Any]]:
"""Analyze keyword gaps."""
try:
gaps = []
# Check keyword opportunities
opportunities = keyword_analysis.get('opportunities', [])
for opportunity in opportunities:
gaps.append({
'type': 'keyword',
'issue': f"Keyword opportunity: {opportunity.get('keyword')}",
'volume': opportunity.get('volume', 0),
'difficulty': opportunity.get('difficulty', 0),
'recommendation': f"Target keyword: {opportunity.get('keyword')}"
})
return gaps
except Exception as e:
st.error(f"Error analyzing keyword gaps: {str(e)}")
return []
def _evaluate_opportunities(self, content_gaps: List[Dict[str, Any]], keyword_analysis: Dict[str, Any]) -> List[Dict[str, Any]]:
"""Evaluate content opportunities."""
try:
opportunities = []
# Evaluate each gap
for gap in content_gaps:
# Calculate priority score
priority_score = self._calculate_priority_score(gap, keyword_analysis)
if priority_score > 50: # Threshold for good opportunities
opportunities.append({
'type': gap.get('type'),
'issue': gap.get('issue'),
'recommendation': gap.get('recommendation'),
'priority_score': priority_score
})
# Sort by priority score
opportunities.sort(key=lambda x: x['priority_score'], reverse=True)
return opportunities
except Exception as e:
st.error(f"Error evaluating opportunities: {str(e)}")
return []
def _calculate_priority_score(self, gap: Dict[str, Any], keyword_analysis: Dict[str, Any]) -> float:
"""Calculate priority score for a gap."""
try:
base_score = 0
# Base score based on gap type
if gap.get('type') == 'content_quality':
base_score = 70
elif gap.get('type') == 'seo':
base_score = 80
elif gap.get('type') == 'competitor':
base_score = 60
elif gap.get('type') == 'keyword':
base_score = 50
# Adjust score based on keyword data
if gap.get('type') == 'keyword':
keyword = gap.get('issue', '').split(': ')[-1]
keyword_data = keyword_analysis.get('trend_analysis', {}).get('trends', {}).get(keyword, {})
if keyword_data:
base_score += keyword_data.get('volume', 0) * 0.1
base_score -= keyword_data.get('difficulty', 0) * 0.2
return min(100, max(0, base_score))
except Exception as e:
st.error(f"Error calculating priority score: {str(e)}")
return 0
def _generate_recommendations(self, content_gaps: List[Dict[str, Any]], opportunities: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
"""Generate content recommendations."""
try:
recommendations = []
# Generate recommendations for each opportunity
for opportunity in opportunities:
recommendations.append({
'type': opportunity.get('type'),
'issue': opportunity.get('issue'),
'recommendation': opportunity.get('recommendation'),
'priority': opportunity.get('priority_score', 0),
'implementation_steps': self._generate_implementation_steps(opportunity)
})
return recommendations
except Exception as e:
st.error(f"Error generating recommendations: {str(e)}")
return []
def _generate_implementation_steps(self, opportunity: Dict[str, Any]) -> List[str]:
"""Generate implementation steps for a recommendation."""
try:
steps = []
if opportunity.get('type') == 'content_quality':
steps = [
'Review current content structure',
'Improve readability and formatting',
'Enhance content organization',
'Update content based on best practices'
]
elif opportunity.get('type') == 'seo':
steps = [
'Audit current SEO implementation',
'Optimize meta tags and descriptions',
'Improve content structure for SEO',
'Implement technical SEO improvements'
]
elif opportunity.get('type') == 'competitor':
steps = [
'Research competitor content',
'Identify unique value proposition',
'Create content for missing topics',
'Optimize content for target keywords'
]
elif opportunity.get('type') == 'keyword':
steps = [
'Research keyword intent',
'Create content strategy',
'Develop content for target keyword',
'Optimize content for search'
]
return steps
except Exception as e:
st.error(f"Error generating implementation steps: {str(e)}")
return []
def _create_implementation_plan(self, recommendations: List[Dict[str, Any]]) -> Dict[str, Any]:
"""Create implementation plan."""
try:
plan = {
'phases': [],
'timeline': {},
'resources': {},
'success_metrics': {}
}
# Create phases based on recommendation types
phases = {
'content_quality': 'Content Enhancement',
'seo': 'SEO Optimization',
'competitor': 'Competitive Content',
'keyword': 'Keyword Targeting'
}
# Group recommendations by phase
for phase_name in phases.values():
phase_recommendations = [
rec for rec in recommendations
if phases.get(rec.get('type')) == phase_name
]
if phase_recommendations:
plan['phases'].append({
'name': phase_name,
'recommendations': phase_recommendations,
'duration': '2-4 weeks',
'resources': ['Content team', 'SEO team'],
'success_metrics': [
'Content quality score',
'SEO performance',
'User engagement'
]
})
return plan
except Exception as e:
st.error(f"Error creating implementation plan: {str(e)}")
return {}
def _generate_content_topics(self, ai_insights: dict) -> list:
"""
Generate content topic suggestions.
Args:
ai_insights (dict): AI-processed insights
Returns:
list: Content topic suggestions
"""
# TODO: Implement content topic generation
return []
def _suggest_content_formats(self, ai_insights: dict) -> list:
"""
Suggest content formats based on analysis.
Args:
ai_insights (dict): AI-processed insights
Returns:
list: Content format suggestions
"""
# TODO: Implement content format suggestions
return []
def _calculate_priority_scores(self, ai_insights: dict) -> dict:
"""
Calculate priority scores for recommendations.
Args:
ai_insights (dict): AI-processed insights
Returns:
dict: Priority scores for each recommendation
"""
# TODO: Implement priority scoring
return {}
def _create_timeline(self, ai_insights: dict) -> dict:
"""
Create implementation timeline for recommendations.
Args:
ai_insights (dict): AI-processed insights
Returns:
dict: Implementation timeline
"""
# TODO: Implement timeline creation
return {
'short_term': [],
'medium_term': [],
'long_term': []
}
def _generate_specific_suggestions(self, recommendations: dict, analysis_results: dict) -> dict:
"""
Generate specific content suggestions using existing tools.
Args:
recommendations (dict): General recommendations
analysis_results (dict): Analysis results
Returns:
dict: Specific content suggestions
"""
suggestions = {}
# Generate titles for suggested topics
for topic in recommendations['content_topics']:
suggestions[topic] = {
'titles': ai_title_generator(topic),
'meta_descriptions': metadesc_generator_main(topic),
'structured_data': ai_structured_data(topic)
}
return suggestions

View File

@@ -0,0 +1,769 @@
"""
Streamlit UI for Content Gap Analysis workflow.
"""
import streamlit as st
import pandas as pd
import plotly.express as px
import plotly.graph_objects as go
import json
from datetime import datetime
from .main import ContentGapAnalysis
from .keyword_researcher import KeywordResearcher
from .competitor_analyzer import CompetitorAnalyzer
from .website_analyzer import WebsiteAnalyzer
from .recommendation_engine import RecommendationEngine
from .utils.ai_processor import AIProcessor
from .navigation import show_content_gap_analysis_nav
from typing import Dict, Any
import logging
# Configure logging
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)
class ContentGapAnalysisUI:
"""Streamlit UI for Content Gap Analysis workflow."""
def __init__(self):
"""Initialize the UI components."""
# Initialize session state for progress tracking
if 'current_step' not in st.session_state:
st.session_state.current_step = 1
if 'analysis_results' not in st.session_state:
st.session_state.analysis_results = {}
# Initialize analysis components
self.analyzer = ContentGapAnalysis()
self.keyword_researcher = KeywordResearcher()
self.competitor_analyzer = CompetitorAnalyzer()
self.website_analyzer = WebsiteAnalyzer()
self.recommendation_engine = RecommendationEngine()
self.ai_processor = AIProcessor()
def run(self):
"""Run the Streamlit interface."""
try:
# Show navigation
nav_option = show_content_gap_analysis_nav()
# Main content area
st.title("Content Gap Analysis")
st.markdown("""
This tool helps you identify content gaps and opportunities by analyzing your website,
competitors, and market trends. Follow the steps below to get started.
""")
# Progress tracking
self._show_progress()
# Main workflow steps
if nav_option == "Website Analysis" or st.session_state.current_step == 1:
self._website_analysis_step()
elif nav_option == "Competitor Analysis" or st.session_state.current_step == 2:
self._competitor_analysis_step()
elif nav_option == "Keyword Research" or st.session_state.current_step == 3:
self._keyword_research_step()
elif nav_option == "Recommendations" or st.session_state.current_step == 4:
self._recommendations_step()
else:
self._export_results()
except Exception as e:
logger.error(f"Error in run method: {str(e)}", exc_info=True)
st.error(f"An error occurred: {str(e)}")
def _show_progress(self):
"""Display progress tracking."""
steps = [
"Website Analysis",
"Competitor Analysis",
"Keyword Research",
"Recommendations",
"Export Results"
]
progress = st.session_state.current_step / len(steps)
st.progress(progress)
cols = st.columns(len(steps))
for i, col in enumerate(cols):
with col:
if i + 1 < st.session_state.current_step:
st.success(f"{steps[i]}")
elif i + 1 == st.session_state.current_step:
st.info(f"{steps[i]}")
else:
st.text(f"{steps[i]}")
def _website_analysis_step(self):
"""Website analysis step UI."""
try:
st.header("Step 1: Website Analysis")
# Display previous results if they exist
if 'website' in st.session_state.analysis_results:
st.info("Previous analysis results found. You can analyze a new website or proceed to the next step.")
self._display_website_analysis(st.session_state.analysis_results['website'])
col1, col2 = st.columns(2)
with col1:
if st.button("Analyze New Website"):
st.session_state.analysis_results.pop('website', None)
st.rerun()
with col2:
if st.button("Proceed to Competitor Analysis"):
st.session_state.current_step = 2
st.rerun()
return
# Create form for new analysis
with st.form("website_analysis_form"):
website_url = st.text_input("Enter your website URL")
industry = st.text_input("Enter your industry/niche")
submitted = st.form_submit_button("Analyze Website")
# Handle form submission outside the form
if submitted and website_url and industry:
# Initialize progress tracking
if 'analysis_progress' not in st.session_state:
st.session_state.analysis_progress = {
'status': 'initializing',
'current_step': 'Starting Analysis',
'progress': 0,
'details': 'Initializing analysis...'
}
# Create progress container
progress_container = st.empty()
status_container = st.empty()
details_container = st.empty()
# Update progress display
def update_progress_display():
progress = st.session_state.analysis_progress
# Update progress bar
with progress_container:
st.progress(progress['progress'] / 100)
# Update status
with status_container:
if progress['status'] == 'error':
st.error(f"Error: {progress['current_step']}")
elif progress['status'] == 'completed':
st.success(f"{progress['current_step']}")
else:
st.info(f"{progress['current_step']}")
# Update details
with details_container:
st.write(progress['details'])
# Initial progress display
update_progress_display()
try:
# Get basic analysis
results = self.website_analyzer.analyze(website_url)
# Update progress from analyzer
st.session_state.analysis_progress = self.website_analyzer.progress.get_progress()
update_progress_display()
if isinstance(results, dict) and 'error' in results:
st.error(f"Error in website analysis: {results['error']}")
return
# Get AI-enhanced analysis
st.session_state.analysis_progress.update({
'current_step': 'AI Analysis',
'progress': 95,
'details': 'Performing AI-enhanced analysis...'
})
update_progress_display()
ai_analysis = self.ai_processor.analyze_content({
'url': website_url,
'industry': industry,
'content': results
})
# Combine results
if isinstance(results, dict):
results.update(ai_analysis)
else:
results = {'error': 'Invalid analysis results format'}
# Store results in session state
st.session_state.analysis_results['website'] = results
# Update final progress
st.session_state.analysis_progress.update({
'status': 'completed',
'current_step': 'Analysis Complete',
'progress': 100,
'details': 'Analysis completed successfully!'
})
update_progress_display()
# Display results
self._display_website_analysis(results)
except Exception as e:
logger.error(f"Error during website analysis: {str(e)}", exc_info=True)
st.session_state.analysis_progress.update({
'status': 'error',
'current_step': 'Analysis Failed',
'details': f"Error during website analysis: {str(e)}"
})
update_progress_display()
st.error(f"Error during website analysis: {str(e)}")
return
except Exception as e:
logger.error(f"Error in website analysis step: {str(e)}", exc_info=True)
st.error(f"Error in website analysis: {str(e)}")
def _display_website_analysis(self, results: Dict[str, Any]):
"""Display website analysis results."""
try:
if not isinstance(results, dict):
st.error("Invalid analysis results format")
return
if 'error' in results:
st.error(f"Error in analysis: {results['error']}")
return
# Content Metrics
st.subheader("Content Metrics")
content_metrics = results.get('content_metrics', {})
if content_metrics:
# Basic metrics in columns
col1, col2, col3, col4 = st.columns(4)
with col1:
st.metric("Word Count", f"{content_metrics.get('word_count', 0):,}")
with col2:
st.metric("Headings", f"{content_metrics.get('heading_count', 0):,}")
with col3:
st.metric("Images", f"{content_metrics.get('image_count', 0):,}")
with col4:
st.metric("Links", f"{content_metrics.get('link_count', 0):,}")
# Content Structure Visualization
st.write("Content Structure")
heading_data = {
'Type': ['H1', 'H2', 'H3', 'Paragraphs'],
'Count': [
content_metrics.get('h1_count', 0),
content_metrics.get('h2_count', 0),
content_metrics.get('h3_count', 0),
content_metrics.get('paragraph_count', 0)
]
}
fig = px.bar(
heading_data,
x='Type',
y='Count',
title="Content Structure Distribution",
color='Type',
color_discrete_sequence=px.colors.qualitative.Set3
)
st.plotly_chart(fig, use_container_width=True)
# Content Features
st.write("Content Features")
features = {
'Feature': ['Meta Description', 'Robots.txt', 'Sitemap'],
'Status': [
content_metrics.get('has_meta_description', False),
content_metrics.get('has_robots_txt', False),
content_metrics.get('has_sitemap', False)
]
}
fig = px.bar(
features,
x='Feature',
y='Status',
title="Content Features Status",
color='Status',
color_discrete_sequence=['red', 'green']
)
st.plotly_chart(fig, use_container_width=True)
# SEO Metrics
st.subheader("SEO Metrics")
seo_metrics = results.get('seo_metrics', {})
if seo_metrics:
# Basic metrics in columns
col1, col2, col3, col4 = st.columns(4)
with col1:
st.metric("Overall Score", f"{seo_metrics.get('overall_score', 0):.1f}%")
with col2:
content_quality = seo_metrics.get('content', {}).get('content_quality_score', 0)
st.metric("Content Quality", f"{content_quality:.1f}%")
with col3:
readability = seo_metrics.get('content', {}).get('readability_score', 0)
st.metric("Readability", f"{readability:.1f}%")
with col4:
keyword_density = seo_metrics.get('content', {}).get('keyword_density', 0)
st.metric("Keyword Density", f"{keyword_density:.1f}%")
# SEO Scores Radar Chart
seo_scores = {
'Metric': ['Overall', 'Content Quality', 'Readability', 'Keyword Density'],
'Score': [
seo_metrics.get('overall_score', 0),
content_quality,
readability,
keyword_density
]
}
fig = px.line_polar(
seo_scores,
r='Score',
theta='Metric',
line_close=True,
title="SEO Performance Overview"
)
fig.update_traces(fill='toself')
st.plotly_chart(fig, use_container_width=True)
# Meta Tags Analysis
st.write("Meta Tags Analysis")
meta_tags = seo_metrics.get('meta_tags', {})
if meta_tags:
# Title Analysis
title = meta_tags.get('title', {})
st.write("Title Tag")
st.write(f"Status: {'' if title.get('status') == 'good' else ''}")
st.write(f"Value: {title.get('value', 'N/A')}")
st.write(f"Length: {title.get('length', 0)} characters")
st.write(f"Score: {title.get('score', 0)}%")
if title.get('recommendation'):
st.warning(title.get('recommendation'))
# Description Analysis
desc = meta_tags.get('description', {})
st.write("Meta Description")
st.write(f"Status: {'' if desc.get('status') == 'good' else ''}")
st.write(f"Value: {desc.get('value', 'N/A')}")
st.write(f"Length: {desc.get('length', 0)} characters")
st.write(f"Score: {desc.get('score', 0)}%")
if desc.get('recommendation'):
st.warning(desc.get('recommendation'))
# Keywords Analysis
keywords = meta_tags.get('keywords', {})
st.write("Meta Keywords")
st.write(f"Status: {'' if keywords.get('status') == 'good' else ''}")
st.write(f"Value: {keywords.get('value', 'N/A')}")
if keywords.get('recommendation'):
st.warning(keywords.get('recommendation'))
# Technical Metrics
st.subheader("Technical Metrics")
technical_info = results.get('technical_info', {})
if technical_info:
col1, col2 = st.columns(2)
with col1:
st.write("Basic Information")
st.metric("Status Code", technical_info.get('status_code', 'N/A'))
st.metric("Server", technical_info.get('server_info', {}).get('server', 'N/A'))
st.metric("Content Type", technical_info.get('server_info', {}).get('content_type', 'N/A'))
with col2:
st.write("Security Information")
security_info = technical_info.get('security_info', {})
security_data = {
'Feature': ['SSL', 'HSTS', 'XSS Protection'],
'Status': [
security_info.get('ssl', False),
security_info.get('hsts', False),
security_info.get('xss_protection', False)
]
}
fig = px.bar(
security_data,
x='Feature',
y='Status',
title="Security Features Status",
color='Status',
color_discrete_sequence=['red', 'green']
)
st.plotly_chart(fig, use_container_width=True)
# Performance Metrics
st.subheader("Performance Metrics")
performance = results.get('performance', {})
if performance:
# Basic metrics in columns
col1, col2, col3, col4 = st.columns(4)
with col1:
st.metric("Load Time", f"{performance.get('load_time', 0):.2f}s")
with col2:
st.metric("Page Size", f"{performance.get('page_size', 0):.1f} KB")
with col3:
st.metric("Status Code", performance.get('status_code', 'N/A'))
with col4:
st.metric("Response Time", f"{performance.get('response_time', 0):.2f}s")
# Insights and Recommendations
st.subheader("Insights and Recommendations")
insights = results.get('insights', [])
if insights:
for insight in insights:
st.info(f"{insight}")
else:
st.info("No specific insights available")
except Exception as e:
logger.error(f"Error displaying website analysis: {str(e)}", exc_info=True)
st.error(f"Error displaying website analysis: {str(e)}")
def _competitor_analysis_step(self):
"""Competitor analysis step UI."""
try:
st.header("Step 2: Competitor Analysis")
with st.form("competitor_analysis_form"):
competitors = st.text_area(
"Enter competitor URLs (one per line)",
help="Enter the URLs of your main competitors"
)
submitted = st.form_submit_button("Analyze Competitors")
if submitted and competitors:
with st.spinner("Analyzing competitors..."):
competitor_urls = [url.strip() for url in competitors.split('\n') if url.strip()]
results = self.competitor_analyzer.analyze(competitor_urls)
# Get AI-enhanced competitor analysis
ai_analysis = self.ai_processor.analyze_competitors({
'competitors': competitor_urls,
'analysis': results
})
# Combine results
results.update(ai_analysis)
st.session_state.analysis_results['competitors'] = results
# Display results
self._display_competitor_analysis(results)
# Move to next step
st.session_state.current_step = 3
st.rerun()
except Exception as e:
logger.error(f"Error in competitor analysis step: {str(e)}", exc_info=True)
st.error(f"Error in competitor analysis: {str(e)}")
def _display_competitor_analysis(self, results: dict):
"""Display competitor analysis results."""
st.subheader("Competitor Analysis Results")
# Competitor comparison
st.subheader("Competitor Comparison")
comp_data = pd.DataFrame(results.get('comparison', []))
if not comp_data.empty:
fig = px.bar(
comp_data,
x='competitor',
y='score',
color='metric',
title="Competitor Comparison"
)
st.plotly_chart(fig)
# AI-Enhanced Competitor Analysis
st.subheader("AI-Enhanced Competitor Analysis")
# Competitor Trend Analysis
trend_data = results.get('competitor_trends', {})
if trend_data:
fig = go.Figure()
for competitor, trends in trend_data.items():
fig.add_trace(go.Scatter(
x=trends.get('timeline', []),
y=trends.get('scores', []),
name=competitor,
mode='lines+markers'
))
fig.update_layout(
title="Competitor Performance Trends",
xaxis_title="Timeline",
yaxis_title="Score"
)
st.plotly_chart(fig)
# Content gaps
st.subheader("Content Gaps")
gaps = results.get('content_gaps', [])
for gap in gaps:
st.info(f"{gap}")
# AI-Generated Competitive Insights
st.subheader("Competitive Insights")
insights = results.get('competitive_insights', {})
if insights:
for category, points in insights.items():
with st.expander(f"{category.title()} Analysis"):
for point in points:
st.success(f"{point}")
def _keyword_research_step(self):
"""Keyword research step UI."""
try:
st.header("Step 3: Keyword Research")
with st.form("keyword_research_form"):
industry = st.text_input(
"Enter your industry/niche",
value=st.session_state.analysis_results.get('website', {}).get('industry', '')
)
submitted = st.form_submit_button("Research Keywords")
if submitted and industry:
with st.spinner("Researching keywords..."):
results = self.keyword_researcher.research(industry)
# Get AI-enhanced keyword analysis
ai_analysis = self.ai_processor.analyze_keywords({
'industry': industry,
'keywords': results
})
# Combine results
results.update(ai_analysis)
st.session_state.analysis_results['keywords'] = results
# Display results
self._display_keyword_research(results)
# Move to next step
st.session_state.current_step = 4
st.rerun()
except Exception as e:
logger.error(f"Error in keyword research step: {str(e)}", exc_info=True)
st.error(f"Error in keyword research: {str(e)}")
def _display_keyword_research(self, results: dict):
"""Display keyword research results."""
st.subheader("Keyword Research Results")
# Keyword metrics
st.subheader("Keyword Metrics")
keyword_data = pd.DataFrame(results.get('keywords', []))
if not keyword_data.empty:
fig = px.scatter(
keyword_data,
x='search_volume',
y='difficulty',
size='relevance_score',
hover_data=['keyword'],
title="Keyword Opportunities"
)
st.plotly_chart(fig)
# AI-Enhanced Keyword Analysis
st.subheader("AI-Enhanced Keyword Analysis")
# Keyword Trend Analysis
trend_data = results.get('keyword_trends', {})
if trend_data:
fig = go.Figure()
for keyword, trends in trend_data.items():
fig.add_trace(go.Scatter(
x=trends.get('timeline', []),
y=trends.get('scores', []),
name=keyword,
mode='lines+markers'
))
fig.update_layout(
title="Keyword Trend Analysis",
xaxis_title="Timeline",
yaxis_title="Trend Score"
)
st.plotly_chart(fig)
# Search intent distribution
st.subheader("Search Intent Distribution")
intent_data = pd.DataFrame(results.get('search_intent', {}).get('summary', {}))
if not intent_data.empty:
fig = px.pie(
intent_data,
values='count',
names='intent',
title="Search Intent Distribution"
)
st.plotly_chart(fig)
# Content format suggestions
st.subheader("Content Format Suggestions")
formats = results.get('content_formats', [])
for format in formats:
st.info(f"{format}")
# AI-Generated Keyword Insights
st.subheader("Keyword Insights")
insights = results.get('keyword_insights', {})
if insights:
for category, points in insights.items():
with st.expander(f"{category.title()} Insights"):
for point in points:
st.success(f"{point}")
def _recommendations_step(self):
"""Recommendations step UI."""
try:
st.header("Step 4: Content Recommendations")
with st.spinner("Generating recommendations..."):
results = self.recommendation_engine.generate_recommendations(
st.session_state.analysis_results
)
# Get AI-enhanced recommendations
ai_recommendations = self.ai_processor.analyze_recommendations({
'recommendations': results,
'analysis': st.session_state.analysis_results
})
# Combine results
results.update(ai_recommendations)
st.session_state.analysis_results['recommendations'] = results
# Display results
self._display_recommendations(results)
# Move to next step
st.session_state.current_step = 5
st.rerun()
except Exception as e:
logger.error(f"Error in recommendations step: {str(e)}", exc_info=True)
st.error(f"Error in recommendations: {str(e)}")
def _display_recommendations(self, results: dict):
"""Display content recommendations."""
st.subheader("Content Recommendations")
# Priority recommendations
st.subheader("Priority Recommendations")
priorities = results.get('priorities', [])
for priority in priorities:
st.success(f"{priority}")
# AI-Enhanced Recommendations
st.subheader("AI-Enhanced Recommendations")
# Recommendation Impact Analysis
impact_data = results.get('impact_analysis', {})
if impact_data:
fig = go.Figure()
for metric, values in impact_data.items():
fig.add_trace(go.Bar(
name=metric,
x=values.get('categories', []),
y=values.get('scores', [])
))
fig.update_layout(
title="Recommendation Impact Analysis",
xaxis_title="Categories",
yaxis_title="Impact Score",
barmode='group'
)
st.plotly_chart(fig)
# Implementation timeline
st.subheader("Implementation Timeline")
timeline = results.get('timeline', [])
for item in timeline:
st.info(f"{item}")
# Expected impact
st.subheader("Expected Impact")
impact = results.get('impact', {})
for metric, value in impact.items():
st.metric(metric, value)
# AI-Generated Strategic Insights
st.subheader("Strategic Insights")
insights = results.get('strategic_insights', {})
if insights:
for category, points in insights.items():
with st.expander(f"{category.title()} Strategy"):
for point in points:
st.success(f"{point}")
def _export_results(self):
"""Export results step UI."""
st.header("Step 5: Export Results")
# Export options
export_format = st.radio(
"Choose export format",
["JSON", "CSV", "PDF"]
)
if st.button("Export Results"):
if export_format == "JSON":
self._export_json()
elif export_format == "CSV":
self._export_csv()
else:
st.info("PDF export coming soon!")
def _export_json(self):
"""Export results as JSON."""
results = st.session_state.analysis_results
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
filename = f"content_gap_analysis_{timestamp}.json"
st.download_button(
"Download JSON",
data=json.dumps(results, indent=2),
file_name=filename,
mime="application/json"
)
def _export_csv(self):
"""Export results as CSV."""
results = st.session_state.analysis_results
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
# Convert results to CSV format
csv_data = []
for section, data in results.items():
if isinstance(data, list):
for item in data:
if isinstance(item, dict):
item['section'] = section
csv_data.append(item)
elif isinstance(data, dict):
data['section'] = section
csv_data.append(data)
if csv_data:
df = pd.DataFrame(csv_data)
filename = f"content_gap_analysis_{timestamp}.csv"
st.download_button(
"Download CSV",
data=df.to_csv(index=False),
file_name=filename,
mime="text/csv"
)
def main():
"""Main entry point for the Streamlit app."""
ui = ContentGapAnalysisUI()
ui.run()
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,249 @@
# Content Gap Analysis Utils
This directory contains utility modules that power the Content Gap Analysis tool. These modules provide core functionality for data collection, processing, analysis, and storage.
## Directory Structure
```
utils/
├── README.md
├── ai_processor.py # AI-powered content analysis and processing
├── content_parser.py # Content structure parsing and analysis
├── data_collector.py # Website data collection and processing
└── storage.py # Analysis results storage and retrieval
```
## Module Descriptions
### 1. AI Processor (`ai_processor.py`)
The AI Processor module enhances content analysis using AI techniques. It provides intelligent analysis of website content, competitor data, and keyword research.
#### Key Features:
- Content quality assessment
- Topic analysis and clustering
- Performance metrics analysis
- Strategic recommendations generation
- Progress tracking for analysis tasks
#### Main Components:
- `AIProcessor`: Main class for AI-powered analysis
- `ProgressTracker`: Tracks analysis progress and status
#### Usage Example:
```python
from utils.ai_processor import AIProcessor
processor = AIProcessor()
analysis = processor.analyze_content({
'url': 'https://example.com',
'industry': 'technology',
'content': content_data
})
```
### 2. Content Parser (`content_parser.py`)
The Content Parser module handles the parsing and analysis of website content structure. It provides detailed insights into content organization and quality.
#### Key Features:
- Content structure analysis
- Text statistics calculation
- Topic extraction
- Readability analysis
- Content hierarchy analysis
#### Main Components:
- `ContentParser`: Main class for content parsing and analysis
#### Usage Example:
```python
from utils.content_parser import ContentParser
parser = ContentParser()
structure = parser.parse_structure({
'main_content': content,
'html': html_content,
'headings': headings_data
})
```
### 3. Data Collector (`data_collector.py`)
The Data Collector module is responsible for gathering website data for analysis. It handles web scraping and data extraction.
#### Key Features:
- Website content collection
- Meta data extraction
- Heading structure analysis
- Link and image extraction
- Error handling and retry logic
#### Main Components:
- `DataCollector`: Main class for data collection
#### Usage Example:
```python
from utils.data_collector import DataCollector
collector = DataCollector()
data = collector.collect('https://example.com')
```
### 4. Storage (`storage.py`)
The Storage module manages the persistence and retrieval of analysis results. It provides a robust database interface for storing and accessing analysis data.
#### Key Features:
- Analysis results storage
- Historical data management
- Recommendation tracking
- User-specific analysis storage
- Error handling and rollback support
#### Main Components:
- `ContentGapAnalysisStorage`: Main class for storage operations
#### Usage Example:
```python
from utils.storage import ContentGapAnalysisStorage
storage = ContentGapAnalysisStorage(db_session)
analysis_id = storage.save_analysis(
user_id=1,
website_url='https://example.com',
industry='technology',
results=analysis_results
)
```
## Integration Points
### 1. Website Analysis Integration
```python
from utils.data_collector import DataCollector
from utils.content_parser import ContentParser
from utils.ai_processor import AIProcessor
# Collect data
collector = DataCollector()
data = collector.collect(url)
# Parse content
parser = ContentParser()
structure = parser.parse_structure(data)
# Process with AI
processor = AIProcessor()
analysis = processor.analyze_content({
'url': url,
'content': structure
})
```
### 2. Storage Integration
```python
from utils.storage import ContentGapAnalysisStorage
# Store analysis results
storage = ContentGapAnalysisStorage(db_session)
analysis_id = storage.save_analysis(
user_id=user_id,
website_url=url,
industry=industry,
results=analysis_results
)
# Retrieve analysis
results = storage.get_analysis(analysis_id)
```
## Error Handling
All modules implement comprehensive error handling:
1. **Data Collection Errors**
- Network timeouts
- Invalid URLs
- Access restrictions
- Parsing errors
2. **Processing Errors**
- Invalid data formats
- AI processing failures
- Resource limitations
- Analysis timeouts
3. **Storage Errors**
- Database connection issues
- Transaction failures
- Data validation errors
- Concurrent access conflicts
## Best Practices
1. **Data Collection**
- Implement rate limiting
- Use proper user agents
- Handle redirects
- Validate input data
2. **Content Processing**
- Clean and normalize data
- Handle encoding issues
- Implement fallback strategies
- Cache processed results
3. **Storage Management**
- Use transactions
- Implement data validation
- Handle concurrent access
- Maintain data integrity
## Future Enhancements
1. **Performance Optimizations**
- Implement parallel processing
- Add caching layer
- Optimize database queries
- Enhance error recovery
2. **Feature Additions**
- Content performance tracking
- Automated content planning
- Enhanced competitive intelligence
- Advanced topic clustering
3. **Integration Improvements**
- API endpoints
- Export capabilities
- Data visualization
- Progress tracking
4. **UI/UX Enhancements**
- Interactive visualizations
- Real-time progress updates
- Export interfaces
- Customization options
## Contributing
When contributing to these utility modules:
1. Follow the existing code structure
2. Add comprehensive error handling
3. Include unit tests
4. Update documentation
5. Follow PEP 8 style guide
## Dependencies
- BeautifulSoup4: HTML parsing
- NLTK: Natural language processing
- SQLAlchemy: Database operations
- Streamlit: UI components
- Requests: HTTP requests
## License
This project is licensed under the MIT License - see the LICENSE file for details.

View File

@@ -0,0 +1,13 @@
"""
Utility modules for content gap analysis.
"""
from .data_collector import DataCollector
from .content_parser import ContentParser
from .ai_processor import AIProcessor
__all__ = [
'DataCollector',
'ContentParser',
'AIProcessor'
]

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,236 @@
"""
Content parser utility for analyzing website content structure.
"""
from typing import Dict, Any, List
import re
from bs4 import BeautifulSoup
import nltk
from nltk.tokenize import sent_tokenize, word_tokenize
from nltk.corpus import stopwords
from collections import Counter
class ContentParser:
"""Parser for analyzing website content structure."""
def __init__(self):
"""Initialize the content parser."""
try:
nltk.data.find('tokenizers/punkt')
except LookupError:
nltk.download('punkt')
try:
nltk.data.find('corpora/stopwords')
except LookupError:
nltk.download('stopwords')
self.stop_words = set(stopwords.words('english'))
def parse_structure(self, content: Dict[str, Any]) -> Dict[str, Any]:
"""
Parse and analyze the structure of website content.
Args:
content: Dictionary containing website content
Returns:
Dictionary containing parsed content structure
"""
try:
# Parse main content
main_content = content.get('main_content', '')
soup = BeautifulSoup(content.get('html', ''), 'html.parser')
# Extract text statistics
text_stats = self._analyze_text(main_content)
# Extract content sections
sections = self._extract_sections(soup)
# Extract topics
topics = self._extract_topics(main_content)
# Analyze readability
readability = self._analyze_readability(main_content)
# Analyze content hierarchy
hierarchy = self._analyze_hierarchy(content.get('headings', []))
return {
'text_statistics': text_stats,
'sections': sections,
'topics': topics,
'readability': readability,
'hierarchy': hierarchy,
'metadata': content.get('metadata', {})
}
except Exception as e:
return {
'error': str(e),
'text_statistics': {},
'sections': [],
'topics': [],
'readability': {},
'hierarchy': {},
'metadata': {}
}
def _analyze_text(self, text: str) -> Dict[str, Any]:
"""Analyze text statistics."""
sentences = sent_tokenize(text)
words = word_tokenize(text.lower())
words = [w for w in words if w.isalnum() and w not in self.stop_words]
return {
'word_count': len(words),
'sentence_count': len(sentences),
'average_sentence_length': len(words) / max(len(sentences), 1),
'unique_words': len(set(words)),
'stop_words': len([w for w in word_tokenize(text.lower()) if w in self.stop_words]),
'characters': len(text),
'paragraphs': len(text.split('\n\n')),
'sentences': sentences
}
def _extract_sections(self, soup: BeautifulSoup) -> List[Dict[str, Any]]:
"""Extract content sections."""
sections = []
# Find main content containers
containers = soup.find_all(['article', 'section', 'div'], class_=re.compile(r'content|main|article|section'))
for container in containers:
# Get section heading
heading = container.find(['h1', 'h2', 'h3'])
heading_text = heading.get_text().strip() if heading else 'Untitled Section'
# Get section content
content = container.get_text().strip()
# Get section type
section_type = container.name
if container.get('class'):
section_type = ' '.join(container.get('class'))
sections.append({
'heading': heading_text,
'content': content,
'type': section_type,
'word_count': len(word_tokenize(content)),
'position': self._get_element_position(container)
})
return sections
def _extract_topics(self, text: str) -> List[Dict[str, Any]]:
"""Extract main topics from content."""
# Tokenize and clean text
words = word_tokenize(text.lower())
words = [w for w in words if w.isalnum() and w not in self.stop_words]
# Get word frequencies
word_freq = Counter(words)
# Get top topics
topics = []
for word, freq in word_freq.most_common(10):
topics.append({
'topic': word,
'frequency': freq,
'percentage': freq / len(words) * 100
})
return topics
def _analyze_readability(self, text: str) -> Dict[str, float]:
"""Analyze text readability."""
sentences = sent_tokenize(text)
words = word_tokenize(text.lower())
words = [w for w in words if w.isalnum()]
# Calculate average sentence length
avg_sentence_length = len(words) / max(len(sentences), 1)
# Calculate average word length
avg_word_length = sum(len(w) for w in words) / max(len(words), 1)
# Calculate Flesch Reading Ease score
# Formula: 206.835 - 1.015(total words/total sentences) - 84.6(total syllables/total words)
syllables = sum(self._count_syllables(w) for w in words)
flesch_score = 206.835 - 1.015 * avg_sentence_length - 84.6 * (syllables / max(len(words), 1))
return {
'flesch_score': max(0, min(100, flesch_score)),
'avg_sentence_length': avg_sentence_length,
'avg_word_length': avg_word_length,
'syllables_per_word': syllables / max(len(words), 1)
}
def _analyze_hierarchy(self, headings: List[Dict[str, Any]]) -> Dict[str, Any]:
"""Analyze content hierarchy."""
# Group headings by level
heading_levels = {}
for heading in headings:
level = heading['level']
if level not in heading_levels:
heading_levels[level] = []
heading_levels[level].append(heading)
# Calculate hierarchy metrics
total_headings = len(headings)
max_depth = max(int(level[1]) for level in heading_levels.keys()) if heading_levels else 0
return {
'total_headings': total_headings,
'max_depth': max_depth,
'heading_distribution': {level: len(headings) for level, headings in heading_levels.items()},
'has_proper_hierarchy': self._check_proper_hierarchy(heading_levels)
}
def _check_proper_hierarchy(self, heading_levels: Dict[str, List[Dict[str, Any]]]) -> bool:
"""Check if headings follow proper hierarchy."""
if not heading_levels:
return False
# Check if h1 exists
if 'h1' not in heading_levels:
return False
# Check if h1 is unique
if len(heading_levels['h1']) > 1:
return False
# Check if levels are sequential
levels = sorted(int(level[1]) for level in heading_levels.keys())
return all(levels[i] - levels[i-1] <= 1 for i in range(1, len(levels)))
def _count_syllables(self, word: str) -> int:
"""Count syllables in a word."""
word = word.lower()
count = 0
vowels = 'aeiouy'
word = word.lower()
if word[0] in vowels:
count += 1
for index in range(1, len(word)):
if word[index] in vowels and word[index - 1] not in vowels:
count += 1
if word.endswith('e'):
count -= 1
if count == 0:
count += 1
return count
def _get_element_position(self, element) -> Dict[str, int]:
"""Get element position in the document."""
try:
return {
'top': element.sourceline,
'left': element.sourcepos
}
except:
return {
'top': 0,
'left': 0
}

View File

@@ -0,0 +1,112 @@
"""
Data collector utility for content gap analysis.
"""
import requests
from bs4 import BeautifulSoup
from typing import Dict, Any
class DataCollector:
"""
Collects and processes website data for analysis.
"""
def __init__(self):
"""Initialize the data collector."""
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'
}
def collect(self, url: str) -> Dict[str, Any]:
"""
Collect website data for analysis.
Args:
url (str): The URL to collect data from
Returns:
dict: Collected website data
"""
try:
# Fetch webpage content
response = requests.get(url, headers=self.headers)
response.raise_for_status()
# Parse HTML content
soup = BeautifulSoup(response.text, 'html.parser')
# Extract relevant data
data = {
'url': url,
'title': self._extract_title(soup),
'meta_description': self._extract_meta_description(soup),
'headings': self._extract_headings(soup),
'content': self._extract_content(soup),
'links': self._extract_links(soup),
'images': self._extract_images(soup)
}
return data
except Exception as e:
return {
'error': str(e),
'url': url
}
def _extract_title(self, soup: BeautifulSoup) -> str:
"""Extract page title."""
title = soup.find('title')
return title.text if title else ''
def _extract_meta_description(self, soup: BeautifulSoup) -> str:
"""Extract meta description."""
meta = soup.find('meta', attrs={'name': 'description'})
return meta.get('content', '') if meta else ''
def _extract_headings(self, soup: BeautifulSoup) -> Dict[str, list]:
"""Extract all headings."""
headings = {}
for i in range(1, 7):
tags = soup.find_all(f'h{i}')
headings[f'h{i}'] = [tag.text.strip() for tag in tags]
return headings
def _extract_content(self, soup: BeautifulSoup) -> str:
"""Extract main content."""
# Remove script and style elements
for script in soup(['script', 'style']):
script.decompose()
# Get text content
text = soup.get_text()
# Clean up text
lines = (line.strip() for line in text.splitlines())
chunks = (phrase.strip() for line in lines for phrase in line.split(" "))
text = ' '.join(chunk for chunk in chunks if chunk)
return text
def _extract_links(self, soup: BeautifulSoup) -> list:
"""Extract all links."""
links = []
for link in soup.find_all('a'):
href = link.get('href')
if href:
links.append({
'url': href,
'text': link.text.strip()
})
return links
def _extract_images(self, soup: BeautifulSoup) -> list:
"""Extract all images."""
images = []
for img in soup.find_all('img'):
images.append({
'src': img.get('src', ''),
'alt': img.get('alt', ''),
'title': img.get('title', '')
})
return images

View File

@@ -0,0 +1,237 @@
"""
SEO analyzer utility for content gap analysis.
"""
import requests
from bs4 import BeautifulSoup
from urllib.parse import urlparse, urljoin
import re
from typing import Dict, Any, List, Optional
from ....utils.website_analyzer.analyzer import WebsiteAnalyzer
def analyze_onpage_seo(url: str) -> Dict[str, Any]:
"""
Analyze on-page SEO elements of a website.
Args:
url: The URL to analyze
Returns:
Dictionary containing SEO analysis results
"""
try:
# Use the combined website analyzer
analyzer = WebsiteAnalyzer()
analysis = analyzer.analyze_website(url)
if not analysis.get('success', False):
return {
'error': analysis.get('error', 'Unknown error in SEO analysis'),
'meta_title': '',
'meta_description': '',
'has_robots_txt': False,
'has_sitemap': False,
'mobile_friendly': False,
'load_time': 0
}
# Extract relevant information from the analysis
seo_info = analysis['data']['analysis']['seo_info']
basic_info = analysis['data']['analysis']['basic_info']
performance = analysis['data']['analysis']['performance']
return {
'meta_tags': seo_info.get('meta_tags', {}),
'content': seo_info.get('content', {}),
'meta_title': basic_info.get('title', ''),
'meta_description': basic_info.get('meta_description', ''),
'has_robots_txt': bool(basic_info.get('robots_txt')),
'has_sitemap': bool(basic_info.get('sitemap')),
'mobile_friendly': True, # This would need to be implemented separately
'load_time': performance.get('load_time', 0)
}
except Exception as e:
return {
'error': str(e),
'meta_title': '',
'meta_description': '',
'has_robots_txt': False,
'has_sitemap': False,
'mobile_friendly': False,
'load_time': 0
}
def _analyze_meta_tags(soup: BeautifulSoup) -> Dict[str, Any]:
"""Analyze meta tags of the webpage."""
meta_tags = {}
# Title tag
title_tag = soup.find('title')
if title_tag:
meta_tags['title'] = title_tag.string.strip()
# Meta description
meta_desc = soup.find('meta', {'name': 'description'})
if meta_desc:
meta_tags['description'] = meta_desc.get('content', '').strip()
# Meta keywords
meta_keywords = soup.find('meta', {'name': 'keywords'})
if meta_keywords:
meta_tags['keywords'] = meta_keywords.get('content', '').strip()
# Open Graph tags
og_tags = {}
for tag in soup.find_all('meta', property=re.compile(r'^og:')):
og_tags[tag['property']] = tag.get('content', '')
meta_tags['og_tags'] = og_tags
# Twitter Card tags
twitter_tags = {}
for tag in soup.find_all('meta', name=re.compile(r'^twitter:')):
twitter_tags[tag['name']] = tag.get('content', '')
meta_tags['twitter_tags'] = twitter_tags
return meta_tags
def _analyze_headings(soup: BeautifulSoup) -> Dict[str, Any]:
"""Analyze heading structure of the webpage."""
headings = {
'h1': [],
'h2': [],
'h3': [],
'h4': [],
'h5': [],
'h6': []
}
for tag in ['h1', 'h2', 'h3', 'h4', 'h5', 'h6']:
for heading in soup.find_all(tag):
headings[tag].append(heading.get_text().strip())
return headings
def _analyze_content(soup: BeautifulSoup) -> Dict[str, Any]:
"""Analyze main content of the webpage."""
# Find main content
main_content = soup.find('main') or soup.find('article') or soup.find('div', class_=re.compile(r'content|main|article'))
if not main_content:
return {
'word_count': 0,
'paragraph_count': 0,
'content': ''
}
# Get text content
content = main_content.get_text()
# Count words and paragraphs
words = content.split()
paragraphs = main_content.find_all('p')
return {
'word_count': len(words),
'paragraph_count': len(paragraphs),
'content': content
}
def _analyze_links(soup: BeautifulSoup, base_url: str) -> Dict[str, Any]:
"""Analyze links on the webpage."""
links = {
'internal': [],
'external': [],
'broken': []
}
base_domain = urlparse(base_url).netloc
for link in soup.find_all('a', href=True):
href = link['href']
# Handle relative URLs
if not href.startswith(('http://', 'https://')):
href = urljoin(base_url, href)
# Categorize link
if urlparse(href).netloc == base_domain:
links['internal'].append({
'url': href,
'text': link.get_text().strip(),
'title': link.get('title', '')
})
else:
links['external'].append({
'url': href,
'text': link.get_text().strip(),
'title': link.get('title', '')
})
return links
def _analyze_images(soup: BeautifulSoup) -> Dict[str, Any]:
"""Analyze images on the webpage."""
images = []
for img in soup.find_all('img'):
image_data = {
'src': img.get('src', ''),
'alt': img.get('alt', ''),
'title': img.get('title', ''),
'width': img.get('width', ''),
'height': img.get('height', ''),
'has_alt': bool(img.get('alt')),
'has_title': bool(img.get('title')),
'has_dimensions': bool(img.get('width') and img.get('height'))
}
images.append(image_data)
return {
'total': len(images),
'with_alt': sum(1 for img in images if img['has_alt']),
'with_title': sum(1 for img in images if img['has_title']),
'with_dimensions': sum(1 for img in images if img['has_dimensions']),
'images': images
}
def _check_technical_elements(soup: BeautifulSoup, url: str) -> Dict[str, Any]:
"""Check technical SEO elements."""
base_url = urlparse(url)
domain = base_url.netloc
# Check robots.txt
robots_url = f"{base_url.scheme}://{domain}/robots.txt"
try:
robots_response = requests.get(robots_url, timeout=5)
has_robots_txt = robots_response.status_code == 200
except:
has_robots_txt = False
# Check sitemap
sitemap_url = f"{base_url.scheme}://{domain}/sitemap.xml"
try:
sitemap_response = requests.get(sitemap_url, timeout=5)
has_sitemap = sitemap_response.status_code == 200
except:
has_sitemap = False
# Check mobile friendliness
viewport = soup.find('meta', {'name': 'viewport'})
has_viewport = bool(viewport)
# Check canonical URL
canonical = soup.find('link', {'rel': 'canonical'})
has_canonical = bool(canonical)
# Check language
html_lang = soup.find('html').get('lang', '')
has_language = bool(html_lang)
return {
'has_robots_txt': has_robots_txt,
'has_sitemap': has_sitemap,
'mobile_friendly': has_viewport,
'has_canonical': has_canonical,
'has_language': has_language,
'language': html_lang
}

View File

@@ -0,0 +1,270 @@
"""
Storage module for content gap analysis results.
"""
from typing import Dict, Any, List, Optional
from datetime import datetime
from sqlalchemy.orm import Session
from sqlalchemy.exc import SQLAlchemyError
import streamlit as st
class ContentGapAnalysisStorage:
"""Handles storage and retrieval of content gap analysis results."""
def __init__(self, db_session: Session):
"""Initialize the storage handler."""
self.db = db_session
def save_analysis(self, user_id: int, website_url: str, industry: str, results: Dict[str, Any]) -> Optional[int]:
"""
Save content gap analysis results.
Args:
user_id: User ID
website_url: Target website URL
industry: Industry category
results: Analysis results dictionary
Returns:
Analysis ID if successful, None otherwise
"""
try:
# Create main analysis record
analysis = ContentGapAnalysis(
user_id=user_id,
website_url=website_url,
industry=industry,
status='completed',
metadata={'version': '1.0'}
)
self.db.add(analysis)
self.db.flush() # Get the ID without committing
# Save website analysis
website_analysis = WebsiteAnalysis(
content_gap_analysis_id=analysis.id,
content_score=results.get('website', {}).get('content_score', 0),
seo_score=results.get('website', {}).get('seo_score', 0),
structure_score=results.get('website', {}).get('structure_score', 0),
content_metrics=results.get('website', {}).get('content_metrics', {}),
seo_metrics=results.get('website', {}).get('seo_metrics', {}),
technical_metrics=results.get('website', {}).get('technical_metrics', {}),
ai_insights=results.get('website', {}).get('ai_insights', {})
)
self.db.add(website_analysis)
# Save competitor analysis if available
if 'competitors' in results:
for competitor in results['competitors']:
competitor_analysis = CompetitorAnalysis(
content_gap_analysis_id=analysis.id,
competitor_url=competitor.get('url'),
market_position=competitor.get('market_position', {}),
content_gaps=competitor.get('content_gaps', []),
competitive_advantages=competitor.get('competitive_advantages', []),
trend_analysis=competitor.get('trend_analysis', {})
)
self.db.add(competitor_analysis)
# Save keyword analysis
keyword_analysis = KeywordAnalysis(
content_gap_analysis_id=analysis.id,
top_keywords=results.get('keywords', {}).get('top_keywords', []),
search_intent=results.get('keywords', {}).get('search_intent', {}),
opportunities=results.get('keywords', {}).get('opportunities', []),
trend_analysis=results.get('keywords', {}).get('trend_analysis', {})
)
self.db.add(keyword_analysis)
# Save recommendations
for recommendation in results.get('recommendations', []):
content_recommendation = ContentRecommendation(
content_gap_analysis_id=analysis.id,
recommendation_type=recommendation.get('type'),
priority_score=recommendation.get('priority_score', 0),
recommendation=recommendation.get('recommendation', ''),
implementation_steps=recommendation.get('implementation_steps', []),
expected_impact=recommendation.get('expected_impact', {}),
status='pending'
)
self.db.add(content_recommendation)
# Save analysis history
history = AnalysisHistory(
content_gap_analysis_id=analysis.id,
status='completed',
metrics={'duration': results.get('duration', 0)}
)
self.db.add(history)
# Commit all changes
self.db.commit()
return analysis.id
except SQLAlchemyError as e:
self.db.rollback()
st.error(f"Error saving analysis results: {str(e)}")
return None
def get_analysis(self, analysis_id: int) -> Optional[Dict[str, Any]]:
"""
Retrieve content gap analysis results.
Args:
analysis_id: Analysis ID
Returns:
Dictionary containing analysis results if found, None otherwise
"""
try:
analysis = self.db.query(ContentGapAnalysis).get(analysis_id)
if not analysis:
return None
# Get website analysis
website_analysis = self.db.query(WebsiteAnalysis).filter_by(
content_gap_analysis_id=analysis_id
).first()
# Get competitor analysis
competitor_analyses = self.db.query(CompetitorAnalysis).filter_by(
content_gap_analysis_id=analysis_id
).all()
# Get keyword analysis
keyword_analysis = self.db.query(KeywordAnalysis).filter_by(
content_gap_analysis_id=analysis_id
).first()
# Get recommendations
recommendations = self.db.query(ContentRecommendation).filter_by(
content_gap_analysis_id=analysis_id
).all()
# Get analysis history
history = self.db.query(AnalysisHistory).filter_by(
content_gap_analysis_id=analysis_id
).order_by(AnalysisHistory.run_date.desc()).all()
return {
'id': analysis.id,
'website_url': analysis.website_url,
'industry': analysis.industry,
'analysis_date': analysis.analysis_date,
'status': analysis.status,
'website': {
'content_score': website_analysis.content_score,
'seo_score': website_analysis.seo_score,
'structure_score': website_analysis.structure_score,
'content_metrics': website_analysis.content_metrics,
'seo_metrics': website_analysis.seo_metrics,
'technical_metrics': website_analysis.technical_metrics,
'ai_insights': website_analysis.ai_insights
} if website_analysis else {},
'competitors': [{
'url': ca.competitor_url,
'market_position': ca.market_position,
'content_gaps': ca.content_gaps,
'competitive_advantages': ca.competitive_advantages,
'trend_analysis': ca.trend_analysis
} for ca in competitor_analyses],
'keywords': {
'top_keywords': keyword_analysis.top_keywords,
'search_intent': keyword_analysis.search_intent,
'opportunities': keyword_analysis.opportunities,
'trend_analysis': keyword_analysis.trend_analysis
} if keyword_analysis else {},
'recommendations': [{
'type': r.recommendation_type,
'priority_score': r.priority_score,
'recommendation': r.recommendation,
'implementation_steps': r.implementation_steps,
'expected_impact': r.expected_impact,
'status': r.status
} for r in recommendations],
'history': [{
'run_date': h.run_date,
'status': h.status,
'metrics': h.metrics,
'error_log': h.error_log
} for h in history]
}
except SQLAlchemyError as e:
st.error(f"Error retrieving analysis results: {str(e)}")
return None
def get_user_analyses(self, user_id: int) -> List[Dict[str, Any]]:
"""
Get all analyses for a user.
Args:
user_id: User ID
Returns:
List of analysis summaries
"""
try:
analyses = self.db.query(ContentGapAnalysis).filter_by(
user_id=user_id
).order_by(ContentGapAnalysis.analysis_date.desc()).all()
return [{
'id': analysis.id,
'website_url': analysis.website_url,
'industry': analysis.industry,
'analysis_date': analysis.analysis_date,
'status': analysis.status
} for analysis in analyses]
except SQLAlchemyError as e:
st.error(f"Error retrieving user analyses: {str(e)}")
return []
def update_recommendation_status(self, recommendation_id: int, status: str) -> bool:
"""
Update the status of a recommendation.
Args:
recommendation_id: Recommendation ID
status: New status
Returns:
True if successful, False otherwise
"""
try:
recommendation = self.db.query(ContentRecommendation).get(recommendation_id)
if recommendation:
recommendation.status = status
recommendation.updated_at = datetime.utcnow()
self.db.commit()
return True
return False
except SQLAlchemyError as e:
self.db.rollback()
st.error(f"Error updating recommendation status: {str(e)}")
return False
def delete_analysis(self, analysis_id: int) -> bool:
"""
Delete an analysis and all related data.
Args:
analysis_id: Analysis ID
Returns:
True if successful, False otherwise
"""
try:
analysis = self.db.query(ContentGapAnalysis).get(analysis_id)
if analysis:
self.db.delete(analysis)
self.db.commit()
return True
return False
except SQLAlchemyError as e:
self.db.rollback()
st.error(f"Error deleting analysis: {str(e)}")
return False

View File

@@ -0,0 +1,291 @@
"""Website analyzer module for content gap analysis."""
import streamlit as st
from loguru import logger
from typing import Dict, Any, List, Optional
import asyncio
import sys
import os
import json
from lib.utils.website_analyzer.analyzer import WebsiteAnalyzer as BaseWebsiteAnalyzer
from lib.gpt_providers.text_generation.main_text_generation import llm_text_gen
# Configure logger
logger.remove() # Remove default handler
logger.add(
"logs/content_gap_website_analyzer.log",
rotation="50 MB",
retention="10 days",
level="DEBUG",
format="{time:YYYY-MM-DD HH:mm:ss} | {level} | {message}"
)
logger.add(
sys.stdout,
level="INFO",
format="<green>{time:YYYY-MM-DD HH:mm:ss}</green> | <level>{level: <8}</level> | <cyan>{message}</cyan>"
)
# Ensure logs directory exists
os.makedirs("logs", exist_ok=True)
class WebsiteAnalyzer(BaseWebsiteAnalyzer):
"""Extended website analyzer for content gap analysis."""
def __init__(self):
"""Initialize the website analyzer."""
super().__init__()
logger.info("ContentGapWebsiteAnalyzer initialized")
def analyze_content_gaps(self, url: str, competitor_urls: List[str]) -> Dict[str, Any]:
"""
Analyze content gaps between the target website and competitors.
Args:
url: The target URL to analyze
competitor_urls: List of competitor URLs to compare against
Returns:
Dictionary containing content gap analysis results
"""
try:
# Analyze target website
target_analysis = self.analyze_website(url)
if not target_analysis.get('success', False):
return {
'error': target_analysis.get('error', 'Unknown error in target analysis'),
'gaps': [],
'recommendations': []
}
# Analyze competitor websites
competitor_analyses = []
for competitor_url in competitor_urls:
analysis = self.analyze_website(competitor_url)
if analysis.get('success', False):
competitor_analyses.append(analysis['data'])
# Generate content gap analysis using AI
prompt = f"""Analyze content gaps between the target website and competitors:
Target Website:
{json.dumps(target_analysis['data'], indent=2)}
Competitor Websites:
{json.dumps(competitor_analyses, indent=2)}
Identify:
1. Missing content topics
2. Content depth differences
3. Keyword gaps
4. Content structure improvements
5. Content quality recommendations
Format the response as JSON with 'gaps' and 'recommendations' keys."""
# Get AI analysis
analysis = llm_text_gen(
prompt=prompt,
system_prompt="You are an SEO expert specializing in content gap analysis.",
response_format="json_object"
)
if not analysis:
return {
'error': 'Failed to generate content gap analysis',
'gaps': [],
'recommendations': []
}
return {
'gaps': analysis.get('gaps', []),
'recommendations': analysis.get('recommendations', [])
}
except Exception as e:
error_msg = f"Error analyzing content gaps: {str(e)}"
logger.error(error_msg, exc_info=True)
return {
'error': error_msg,
'gaps': [],
'recommendations': []
}
def analyze(self, url: str) -> Dict[str, Any]:
"""
Analyze a website for content gaps and SEO opportunities.
Args:
url: The URL to analyze
Returns:
Dictionary containing analysis results
"""
try:
# Initialize progress tracking
progress = {
'status': 'in_progress',
'current_stage': 'content_analysis',
'current_step': 'Initializing analysis',
'progress': 0,
'details': 'Starting website analysis...'
}
self.progress.update(progress)
# Get base website analysis
logger.info("Starting base website analysis")
website_analysis = self.analyze_website(url)
if not website_analysis.get('success', False):
error_msg = website_analysis.get('error', 'Unknown error in website analysis')
logger.error(f"Error in website analysis: {error_msg}")
progress['status'] = 'error'
progress['details'] = error_msg
self.progress.update(progress)
return {
'error': error_msg,
'error_details': website_analysis.get('error_details', {}),
'progress': progress
}
# Extract SEO metrics from the analysis
seo_metrics = self._extract_seo_metrics(website_analysis['data'])
# Extract performance metrics
performance_metrics = self._extract_performance_metrics(website_analysis['data'])
# Update progress
progress['status'] = 'completed'
progress['progress'] = 100
progress['details'] = 'Analysis completed successfully'
self.progress.update(progress)
return {
'success': True,
'data': {
'seo_metrics': seo_metrics,
'performance_metrics': performance_metrics,
'website_analysis': website_analysis['data']
},
'progress': progress
}
except Exception as e:
error_msg = f"Error in content gap analysis: {str(e)}"
logger.error(error_msg, exc_info=True)
progress['status'] = 'error'
progress['details'] = error_msg
self.progress.update(progress)
return {
'error': error_msg,
'error_details': {
'type': type(e).__name__,
'traceback': str(e.__traceback__)
},
'progress': progress
}
def _extract_seo_metrics(self, website_analysis: Dict[str, Any]) -> Dict[str, Any]:
"""Extract SEO-related metrics from website analysis."""
try:
seo_info = website_analysis.get('analysis', {}).get('seo_info', {})
return {
'overall_score': seo_info.get('overall_score', 0),
'meta_tags': {
'title': seo_info.get('meta_tags', {}).get('title', {}),
'description': seo_info.get('meta_tags', {}).get('description', {}),
'keywords': seo_info.get('meta_tags', {}).get('keywords', {})
},
'content': {
'word_count': seo_info.get('content', {}).get('word_count', 0),
'readability_score': seo_info.get('content', {}).get('readability_score', 0),
'content_quality_score': seo_info.get('content', {}).get('content_quality_score', 0)
}
}
except Exception as e:
logger.error(f"Error extracting SEO metrics: {str(e)}", exc_info=True)
return {}
def _extract_performance_metrics(self, website_analysis: Dict[str, Any]) -> Dict[str, Any]:
"""Extract performance metrics from website analysis."""
try:
performance_info = website_analysis.get('analysis', {}).get('performance', {})
return {
'load_time': performance_info.get('load_time', 0),
'page_size': performance_info.get('page_size', 0),
'resource_count': performance_info.get('resource_count', 0),
'performance_score': performance_info.get('performance_score', 0)
}
except Exception as e:
logger.error(f"Error extracting performance metrics: {str(e)}", exc_info=True)
return {}
def _extract_content_metrics(self, website_analysis: Dict[str, Any]) -> Dict[str, Any]:
"""Extract content-related metrics from website analysis."""
try:
content_info = website_analysis['analysis']['content_info']
return {
'word_count': content_info.get('word_count', 0),
'heading_count': content_info.get('heading_count', 0),
'image_count': content_info.get('image_count', 0),
'link_count': content_info.get('link_count', 0),
'has_meta_description': content_info.get('has_meta_description', False),
'has_robots_txt': content_info.get('has_robots_txt', False),
'has_sitemap': content_info.get('has_sitemap', False)
}
except Exception as e:
logger.error(f"Error extracting content metrics: {str(e)}", exc_info=True)
return {}
def _extract_technical_info(self, website_analysis: Dict[str, Any]) -> Dict[str, Any]:
"""Extract technical information from website analysis."""
try:
basic_info = website_analysis.get('analysis', {}).get('basic_info', {})
return {
'title': basic_info.get('title', ''),
'meta_description': basic_info.get('meta_description', ''),
'headers': basic_info.get('headers', {}),
'robots_txt': basic_info.get('robots_txt', ''),
'sitemap': basic_info.get('sitemap', ''),
'server_info': basic_info.get('server_info', {}),
'security_info': basic_info.get('security_info', {})
}
except Exception as e:
logger.error(f"Error extracting technical info: {str(e)}", exc_info=True)
return {}
def _generate_insights(self, content_metrics: Dict[str, Any], seo_metrics: Dict[str, Any]) -> List[str]:
"""Generate content insights based on analysis results."""
try:
insights = []
# Content insights
if content_metrics['word_count'] < 300:
insights.append("Content length is below recommended minimum (300 words)")
elif content_metrics['word_count'] > 2000:
insights.append("Content length is above recommended maximum (2000 words)")
if content_metrics['heading_count'] < 2:
insights.append("Content structure could be improved with more headings")
if content_metrics['image_count'] == 0:
insights.append("Consider adding images to improve content engagement")
# SEO insights
if seo_metrics.get('overall_score', 0) < 60:
insights.append("SEO optimization needs significant improvement")
elif seo_metrics.get('overall_score', 0) < 80:
insights.append("SEO optimization has room for improvement")
if not content_metrics['has_meta_description']:
insights.append("Missing meta description - important for SEO")
if not content_metrics['has_robots_txt']:
insights.append("Missing robots.txt - important for search engine crawling")
if not content_metrics['has_sitemap']:
insights.append("Missing sitemap.xml - important for search engine indexing")
return insights
except Exception as e:
logger.error(f"Error generating insights: {str(e)}", exc_info=True)
return []

View File

@@ -1,3 +1,5 @@
"""Content title generator module."""
import os
import json
import streamlit as st
@@ -6,70 +8,106 @@ from tenacity import (
stop_after_attempt,
wait_random_exponential,
)
from ..gpt_providers.text_generation.main_text_generation import llm_text_gen
from loguru import logger
from typing import Dict, Any, List, Optional
import asyncio
import sys
from lib.gpt_providers.text_generation.main_text_generation import llm_text_gen
from lib.utils.website_analyzer.analyzer import WebsiteAnalyzer
def ai_title_generator():
""" UI for the AI Blog Title Generator """
st.title("✍️ Alwrity - AI Blog Title Generator")
# Configure logger
logger.remove() # Remove default handler
logger.add(
"logs/content_title_generator.log",
rotation="50 MB",
retention="10 days",
level="DEBUG",
format="{time:YYYY-MM-DD HH:mm:ss} | {level} | {message}"
)
logger.add(
sys.stdout,
level="INFO",
format="<green>{time:YYYY-MM-DD HH:mm:ss}</green> | <level>{level: <8}</level> | <cyan>{message}</cyan>"
)
# Input section
with st.expander("**PRO-TIP** - Follow the steps below for best results.", expanded=True):
col1, col2 = st.columns([5, 5])
with col1:
input_blog_keywords = st.text_input(
'**🔑 Enter main keywords of your blog!**',
placeholder="e.g., AI tools, digital marketing, SEO",
help="Use 2-3 words that best describe the main topic of your blog."
)
input_blog_content = st.text_area(
'**📄 Copy/Paste your entire blog content.** (Optional)',
placeholder="e.g., Content about the importance of AI in digital marketing...",
help="Paste your full blog content here for more accurate title suggestions. This is optional."
)
with col2:
input_title_type = st.selectbox(
'📝 Blog Type',
('General', 'How-to Guides', 'Tutorials', 'Listicles', 'Newsworthy Posts', 'FAQs', 'Checklists/Cheat Sheets'),
index=0
)
input_title_intent = st.selectbox(
'🔍 Search Intent',
('Informational Intent', 'Commercial Intent', 'Transactional Intent', 'Navigational Intent'),
index=0
)
language_options = ["English", "Spanish", "French", "German", "Chinese", "Japanese", "Other"]
input_language = st.selectbox(
'🌐 Select Language',
options=language_options,
index=0,
help="Choose the language for your blog title."
)
if input_language == "Other":
input_language = st.text_input(
'Specify Language',
placeholder="e.g., Italian, Dutch",
help="Specify your preferred language."
)
# Generate Blog Title button
if st.button('**Generate Blog Titles**'):
with st.spinner("Generating blog titles..."):
if input_blog_content == 'Optional':
input_blog_content = None
if not input_blog_keywords and not input_blog_content:
st.error('**🫣 Provide Inputs to generate Blog Titles. Either Blog Keywords OR content is required!**')
else:
blog_titles = generate_blog_titles(input_blog_keywords, input_blog_content, input_title_type, input_title_intent, input_language)
if blog_titles:
st.subheader('**👩🧕🔬 Go Rule search ranking with these Blog Titles!**')
with st.expander("**Final - Blog Titles Output 🎆🎇🎇**", expanded=True):
st.markdown(blog_titles)
else:
st.error("💥 **Failed to generate blog titles. Please try again!**")
# Ensure logs directory exists
os.makedirs("logs", exist_ok=True)
def ai_title_generator(url: str) -> Dict[str, Any]:
"""
Generate SEO-optimized titles using AI.
Args:
url: The URL to analyze
Returns:
Dictionary containing title suggestions and analysis
"""
try:
# Initialize analyzer
analyzer = WebsiteAnalyzer()
# Analyze website
analysis = analyzer.analyze_website(url)
if not analysis.get('success', False):
return {
'error': analysis.get('error', 'Unknown error in analysis'),
'patterns': {},
'suggestions': []
}
# Extract content and meta information
content_info = analysis['data']['analysis']['content_info']
seo_info = analysis['data']['analysis']['seo_info']
# Generate title suggestions using AI
prompt = f"""Based on the following website content and SEO analysis, generate 5 SEO-optimized title suggestions:
Content Analysis:
- Word Count: {content_info.get('word_count', 0)}
- Heading Structure: {content_info.get('heading_structure', {})}
SEO Analysis:
- Meta Title: {seo_info.get('meta_tags', {}).get('title', {}).get('value', '')}
- Meta Description: {seo_info.get('meta_tags', {}).get('description', {}).get('value', '')}
- Keywords: {seo_info.get('meta_tags', {}).get('keywords', {}).get('value', '')}
Generate 5 title suggestions that are:
1. SEO-optimized
2. Engaging and click-worthy
3. Between 50-60 characters
4. Include relevant keywords
5. Follow best practices for title optimization
Format the response as JSON with 'suggestions' and 'patterns' keys."""
# Get AI suggestions
suggestions = llm_text_gen(
prompt=prompt,
system_prompt="You are an SEO expert specializing in title optimization.",
response_format="json_object"
)
if not suggestions:
return {
'error': 'Failed to generate title suggestions',
'patterns': {},
'suggestions': []
}
return {
'patterns': suggestions.get('patterns', {}),
'suggestions': suggestions.get('suggestions', [])
}
except Exception as e:
error_msg = f"Error generating title suggestions: {str(e)}"
logger.error(error_msg, exc_info=True)
return {
'error': error_msg,
'patterns': {},
'suggestions': []
}
@retry(stop=stop_after_attempt(3), wait=wait_random_exponential(min=1, max=4))
def generate_blog_titles(input_blog_keywords, input_blog_content, input_title_type, input_title_intent, input_language):

View File

@@ -194,7 +194,7 @@ def facebook_main_menu():
if st.session_state.selected_tool is not None:
with tool_container:
# Add a back button at the top
if st.button("← Back to Dashboard", key="back_to_dashboard"):
if st.button("← Back to Dashboard", key="back_to_facebook_dashboard"):
st.session_state.selected_tool = None
st.rerun()

View File

@@ -0,0 +1,190 @@
# AI Finance Report Generator
An advanced AI-powered financial analysis and report generation system that combines data collection, technical analysis, visualization, and automated report generation.
## Project Structure
```
ai_finance_report_generator/
├── ai_financial_dashboard.py # Main dashboard interface
├── utils/ # Utility functions
│ ├── __init__.py
│ └── storage.py # Data persistence
├── reports/ # Report generation modules
│ ├── technical_analysis/ # Technical analysis reports
│ ├── fundamental_analysis/ # Fundamental analysis reports
│ ├── options_analysis/ # Options analysis reports
│ ├── portfolio_analysis/ # Portfolio analysis reports
│ ├── market_research/ # Market research reports
│ └── news_analysis/ # News analysis reports
└── README.md # This file
```
## Features
### Current Features
- Unified dashboard interface for all financial analysis tools
- Technical Analysis report generation
- Options analysis report generation
- User preferences management
- Recent reports tracking
- Data persistence with JSON storage
- Financial data collection from various sources
- Integration with LLM for report generation
### Planned Features
#### 1. Data Collection Module
- Web scraping for financial news and data
- API integrations (Yahoo Finance, Alpha Vantage, Financial Modeling Prep)
- Real-time market data collection
- Historical data retrieval
- Company financial statements
- Market sentiment data
- Economic indicators
- Sector analysis data
#### 2. Technical Analysis Module
- Moving averages (SMA, EMA, WMA)
- RSI, MACD, Bollinger Bands
- Volume analysis
- Support/Resistance levels
- Trend analysis
- Pattern recognition
- Fibonacci retracements
- Momentum indicators
#### 3. Fundamental Analysis Module
- Financial ratios calculation
- Company valuation metrics
- Growth analysis
- Profitability analysis
- Debt analysis
- Cash flow analysis
- Industry comparison
- Peer analysis
#### 4. Data Visualization Module
- Candlestick charts
- Technical indicator overlays
- Volume charts
- Price action patterns
- Correlation matrices
- Heat maps
- Interactive charts
- Custom chart templates
#### 5. Report Generation Module
- Technical analysis reports
- Fundamental analysis reports
- Market research reports
- Investment recommendations
- Risk assessment reports
- Sector analysis reports
- News impact analysis
- Custom report templates
#### 6. News and Sentiment Analysis Module
- News aggregation
- Sentiment scoring
- Social media analysis
- Market sentiment indicators
- News impact analysis
- Event correlation
- Trend detection
- Sentiment visualization
#### 7. Portfolio Analysis Module
- Portfolio performance analysis
- Risk assessment
- Asset allocation
- Correlation analysis
- Diversification metrics
- Performance attribution
- Portfolio optimization
- Rebalancing suggestions
## Usage
### Basic Usage
```python
from lib.ai_writers.ai_finance_report_generator.ai_financial_dashboard import get_dashboard
# Get dashboard instance
dashboard = get_dashboard()
# Generate technical analysis report
ta_report = dashboard.generate_technical_analysis("AAPL")
# Generate options analysis report
options_report = dashboard.generate_options_analysis("AAPL")
# Get recent reports
recent_reports = dashboard.get_recent_reports()
```
### User Preferences
```python
# Update user preferences
dashboard.update_preferences({
"report_format": "markdown",
"include_charts": True,
"chart_style": "dark",
"language": "en"
})
# Get current preferences
preferences = dashboard.get_preferences()
```
### Portfolio Analysis
```python
# Create portfolio
portfolio = [
{"symbol": "AAPL", "shares": 100},
{"symbol": "GOOGL", "shares": 50}
]
# Generate portfolio report
portfolio_report = dashboard.generate_portfolio_analysis(portfolio)
```
## Installation
```bash
pip install -r requirements.txt
```
## Dependencies
1. **Data Collection**
- `finance_data_researcher`
- `web_scraping_tools`
2. **Analysis Tools**
- `pandas_ta`
- `numpy`
- `scipy`
3. **Visualization**
- `matplotlib`
- `plotly`
4. **Text Generation**
- `llm_text_gen`
- `gpt_providers`
## Contributing
1. Fork the repository
2. Create your feature branch (`git checkout -b feature/AmazingFeature`)
3. Commit your changes (`git commit -m 'Add some AmazingFeature'`)
4. Push to the branch (`git push origin feature/AmazingFeature`)
5. Open a Pull Request
## License
This project is licensed under the MIT License - see the LICENSE file for details.

View File

@@ -0,0 +1,358 @@
"""
AI Financial Dashboard Module
This module combines the financial dashboard interface with financial report generation capabilities.
It provides a unified interface for managing financial analysis tools and generating reports.
"""
import sys
import os
from textwrap import dedent
from pathlib import Path
from datetime import datetime
from typing import Dict, List, Any, Optional, Union
from loguru import logger
logger.remove()
logger.add(sys.stdout,
colorize=True,
format="<level>{level}</level>|<green>{file}:{line}:{function}</green>| {message}"
)
from ...ai_web_researcher.finance_data_researcher import get_finance_data, get_fin_options_data
from ...gpt_providers.text_generation.main_text_generation import llm_text_gen
from .utils import get_feature_status
from .utils.storage import get_storage_manager
class UserPreferences:
"""Class to manage user preferences and settings."""
def __init__(self):
self.default_settings = {
"theme": "light",
"currency": "USD",
"timezone": "UTC",
"date_format": "%Y-%m-%d",
"default_symbols": [],
"notifications": True,
"auto_refresh": False,
"refresh_interval": 300, # 5 minutes
"report_format": "markdown",
"include_charts": True,
"chart_style": "default",
"language": "en"
}
self.settings = self.default_settings.copy()
self.storage = get_storage_manager()
self.load_settings()
def update_setting(self, key: str, value: Any) -> None:
"""Update a specific setting."""
if key in self.default_settings:
self.settings[key] = value
self.save_settings()
def get_setting(self, key: str) -> Any:
"""Get a specific setting value."""
return self.settings.get(key, self.default_settings.get(key))
def reset_settings(self) -> None:
"""Reset all settings to default values."""
self.settings = self.default_settings.copy()
self.save_settings()
def save_settings(self) -> None:
"""Save current settings to storage."""
self.storage.save_user_preferences(self.settings)
def load_settings(self) -> None:
"""Load settings from storage."""
stored_settings = self.storage.load_user_preferences()
if stored_settings:
self.settings.update(stored_settings)
class RecentReport:
"""Class to represent a recently generated report."""
def __init__(self, report_type: str, symbol: Optional[str], timestamp: datetime, content: Optional[str] = None):
self.report_type = report_type
self.symbol = symbol
self.timestamp = timestamp
self.content = content
self.id = f"{report_type}_{symbol}_{timestamp.strftime('%Y%m%d%H%M%S')}"
def to_dict(self) -> Dict[str, Any]:
"""Convert report to dictionary format."""
return {
"id": self.id,
"type": self.report_type,
"symbol": self.symbol,
"timestamp": self.timestamp.isoformat(),
"content": self.content
}
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> 'RecentReport':
"""Create report from dictionary format."""
return cls(
report_type=data["type"],
symbol=data["symbol"],
timestamp=datetime.fromisoformat(data["timestamp"]),
content=data.get("content")
)
class FinancialDashboard:
"""Main dashboard class for managing financial analysis tools and generating reports."""
def __init__(self):
self.features = {
"technical_analysis": {
"name": "Technical Analysis",
"description": "Generate technical analysis reports with indicators and patterns",
"icon": "📊",
"route": "/technical-analysis",
"category": "analysis",
"dependencies": ["data_collection"],
"version": "1.0.0"
},
"fundamental_analysis": {
"name": "Fundamental Analysis",
"description": "Analyze company financials and valuation metrics",
"icon": "📈",
"route": "/fundamental-analysis",
"category": "analysis",
"dependencies": ["data_collection"],
"version": "0.1.0"
},
"options_analysis": {
"name": "Options Analysis",
"description": "Analyze options chains and generate trading strategies",
"icon": "",
"route": "/options-analysis",
"category": "analysis",
"dependencies": ["data_collection", "options_data"],
"version": "1.0.0"
},
"portfolio_analysis": {
"name": "Portfolio Analysis",
"description": "Analyze portfolio performance and risk metrics",
"icon": "📑",
"route": "/portfolio-analysis",
"category": "portfolio",
"dependencies": ["data_collection", "portfolio_data"],
"version": "0.1.0"
},
"market_research": {
"name": "Market Research",
"description": "Generate market research reports and sector analysis",
"icon": "🔍",
"route": "/market-research",
"category": "research",
"dependencies": ["data_collection", "news_data"],
"version": "0.1.0"
},
"news_analysis": {
"name": "News Analysis",
"description": "Analyze news impact and market sentiment",
"icon": "📰",
"route": "/news-analysis",
"category": "research",
"dependencies": ["data_collection", "news_data"],
"version": "0.1.0"
}
}
self.user_preferences = UserPreferences()
self.storage = get_storage_manager()
self.recent_reports: List[RecentReport] = []
self.max_recent_reports = 10
self.load_recent_reports()
def get_all_features(self) -> List[Dict[str, Any]]:
"""Get all available features with their status."""
features_list = []
for feature_id, feature_info in self.features.items():
status = get_feature_status(feature_id)
feature_info.update(status)
features_list.append(feature_info)
return features_list
def get_feature(self, feature_id: str) -> Dict[str, Any]:
"""Get information about a specific feature."""
if feature_id not in self.features:
raise ValueError(f"Feature {feature_id} not found")
feature_info = self.features[feature_id].copy()
status = get_feature_status(feature_id)
feature_info.update(status)
return feature_info
def get_implemented_features(self) -> List[Dict[str, Any]]:
"""Get only the implemented features."""
return [f for f in self.get_all_features() if f["implemented"]]
def get_coming_soon_features(self) -> List[Dict[str, Any]]:
"""Get features that are coming soon."""
return [f for f in self.get_all_features() if f["coming_soon"]]
def get_features_by_category(self, category: str) -> List[Dict[str, Any]]:
"""Get features filtered by category."""
return [f for f in self.get_all_features() if f["category"] == category]
def add_recent_report(self, report_type: str, symbol: Optional[str] = None, content: Optional[str] = None) -> None:
"""Add a report to the recent reports list."""
report = RecentReport(report_type, symbol, datetime.now(), content)
self.recent_reports.insert(0, report)
if len(self.recent_reports) > self.max_recent_reports:
self.recent_reports.pop()
self.save_recent_reports()
def get_recent_reports(self, limit: Optional[int] = None) -> List[Dict[str, Any]]:
"""Get recent reports."""
reports = self.recent_reports[:limit] if limit else self.recent_reports
return [{
**r.to_dict(),
"feature_info": self.get_feature(r.report_type)
} for r in reports]
def save_recent_reports(self) -> None:
"""Save recent reports to storage."""
reports_data = [r.to_dict() for r in self.recent_reports]
self.storage.save_recent_reports(reports_data)
def load_recent_reports(self) -> None:
"""Load recent reports from storage."""
reports_data = self.storage.load_recent_reports()
self.recent_reports = [RecentReport.from_dict(r) for r in reports_data]
def get_dashboard_summary(self) -> Dict[str, Any]:
"""Get a summary of the dashboard state."""
return {
"total_features": len(self.features),
"implemented_features": len(self.get_implemented_features()),
"coming_soon_features": len(self.get_coming_soon_features()),
"recent_reports": len(self.recent_reports),
"categories": list(set(f["category"] for f in self.features.values())),
"user_preferences": self.user_preferences.settings
}
def check_feature_dependencies(self, feature_id: str) -> Dict[str, bool]:
"""Check if all dependencies for a feature are met."""
if feature_id not in self.features:
raise ValueError(f"Feature {feature_id} not found")
feature = self.features[feature_id]
dependencies = feature.get("dependencies", [])
return {
dep: get_feature_status(dep)["implemented"]
for dep in dependencies
}
def backup_data(self, backup_dir: Optional[str] = None) -> None:
"""Create a backup of all dashboard data."""
self.storage.backup_storage(backup_dir)
def restore_from_backup(self, backup_file: str) -> None:
"""Restore dashboard data from a backup file."""
self.storage.restore_from_backup(backup_file)
self.user_preferences.load_settings()
self.load_recent_reports()
def generate_technical_analysis(self, symbol: str) -> str:
"""Generate a technical analysis report for the given symbol."""
try:
# Get financial data
symbol_fin_data = get_finance_data(symbol)
# Generate report
report_content = self._generate_ta_report(symbol_fin_data, symbol)
# Add to recent reports
self.add_recent_report("technical_analysis", symbol, report_content)
logger.info(f"Done: Final Technical Analysis for {symbol}")
return report_content
except Exception as err:
logger.error(f"Error: Failed to generate Technical Analysis report: {err}")
raise
def generate_options_analysis(self, symbol: str) -> str:
"""Generate an options analysis report for the given symbol."""
try:
# Get options data
options_data = get_fin_options_data(symbol)
# Generate report
report_content = self._generate_options_report(options_data, symbol)
# Add to recent reports
self.add_recent_report("options_analysis", symbol, report_content)
logger.info(f"Done: Options Analysis for {symbol}")
return report_content
except Exception as err:
logger.error(f"Error: Failed to generate Options Analysis report: {err}")
raise
def _generate_ta_report(self, last_day_summary: str, symbol: str) -> str:
"""Generate technical analysis report using LLM."""
prompt = f"""
You are a seasoned Technical Analysis (TA) expert, rivaling legends like Charles Dow, John Bollinger, and Alan Andrews.
Your deep understanding of market dynamics, coupled with mastery of technical indicators,
allows you to decipher complex patterns and offer precise predictions.
Your expertise extends to practical tools like the pandas_ta module, enabling you to extract valuable insights from raw data.
**Objective:**
Analyze the provided technical indicators for {symbol} on its last trading day and predict its price movement over the next few trading sessions.
**Instructions:**
1. **Identify Potential Trading Signals:** Highlight specific indicators suggesting bullish, bearish, or neutral signals. Explain the rationale behind each signal, referencing historical patterns or comparable market scenarios.
2. **Detect Patterns and Divergences:** Analyze the interplay between different indicators. Detect patterns like moving average crossovers, candlestick formations, or divergences between price action and indicators. Explain the significance of each pattern.
3. **Price Movement Prediction:** Based on your analysis, provide a clear prediction for {symbol}'s price movement in the next few days. State the expected direction (up, down, sideways) and potential price targets if identifiable.
4. **Risk Assessment:** Briefly discuss any potential risks or factors that could invalidate your predictions, promoting a balanced and informed perspective.
**Technical Indicators for {symbol} on the Last Trading Day:**
{last_day_summary}
Remember, your analysis should be detailed, insightful, and actionable for traders seeking to capitalize on market movements.
"""
try:
return llm_text_gen(prompt)
except Exception as err:
logger.error(f"Failed to generate TA report: {err}")
raise
def _generate_options_report(self, results_sentences: List[str], ticker: str) -> str:
"""Generate options analysis report using LLM."""
prompt = f"""
You are a financial expert specializing in options trading and market sentiment analysis.
You have been provided with the following technical analysis of options data for the ticker symbol {ticker} with the nearest expiry date:
{chr(10).join(results_sentences)}
Based on this data, provide a comprehensive analysis of the options market for {ticker}.
Your analysis should include:
1. **Implied Volatility Interpretation:** Discuss the significance of the average implied volatility for both call and put options. What does it suggest about market expectations of future price movements?
2. **Volume and Open Interest Insights:** Analyze the volume and open interest for call and put options. What does this data reveal about current market positioning and potential future trading activity?
3. **Sentiment Analysis:** Evaluate the put-call ratio, implied volatility skew, and overall market sentiment. What do these indicators suggest about trader sentiment and potential future price direction?
4. **Potential Trading Strategies:** Based on your analysis, suggest potential options trading strategies that could be employed for {ticker}, considering the current market conditions and sentiment.
Please provide your analysis in a clear and concise manner, suitable for someone with a good understanding of options trading.
"""
try:
return llm_text_gen(prompt)
except Exception as err:
logger.error(f"Failed to generate options report: {err}")
raise
def get_dashboard() -> FinancialDashboard:
"""Get the financial dashboard instance."""
return FinancialDashboard()

View File

@@ -0,0 +1,265 @@
# Financial Reports Module
This directory contains the core report generation modules for different types of financial analysis. Each module is designed to handle a specific type of financial report and can be accessed through the main dashboard interface.
## Directory Structure
```
reports/
├── technical_analysis/ # Technical analysis reports
├── fundamental_analysis/ # Fundamental analysis reports
├── options_analysis/ # Options analysis reports
├── portfolio_analysis/ # Portfolio analysis reports
├── market_research/ # Market research reports
└── news_analysis/ # News analysis reports
```
## Report Types
### 1. Technical Analysis Reports
Location: `technical_analysis/`
Generates technical analysis reports including:
- Moving averages (SMA, EMA, WMA)
- RSI, MACD, Bollinger Bands
- Volume analysis
- Support/Resistance levels
- Trend analysis
- Pattern recognition
Usage:
```python
from lib.ai_writers.ai_finance_report_generator.reports.technical_analysis import generate_ta_report
report = generate_ta_report("AAPL")
```
### 2. Fundamental Analysis Reports
Location: `fundamental_analysis/`
Generates fundamental analysis reports including:
- Financial ratios
- Company valuation metrics
- Growth analysis
- Profitability analysis
- Debt analysis
- Cash flow analysis
Usage:
```python
from lib.ai_writers.ai_finance_report_generator.reports.fundamental_analysis import generate_fa_report
report = generate_fa_report("AAPL")
```
### 3. Options Analysis Reports
Location: `options_analysis/`
Generates options analysis reports including:
- Options chain analysis
- Implied volatility analysis
- Options strategies
- Risk metrics
- Greeks analysis
Usage:
```python
from lib.ai_writers.ai_finance_report_generator.reports.options_analysis import generate_options_report
report = generate_options_report("AAPL")
```
### 4. Portfolio Analysis Reports
Location: `portfolio_analysis/`
Generates portfolio analysis reports including:
- Portfolio performance analysis
- Risk assessment
- Asset allocation
- Correlation analysis
- Diversification metrics
- Performance attribution
Usage:
```python
from lib.ai_writers.ai_finance_report_generator.reports.portfolio_analysis import generate_portfolio_report
portfolio = [
{"symbol": "AAPL", "shares": 100},
{"symbol": "GOOGL", "shares": 50}
]
report = generate_portfolio_report(portfolio)
```
### 5. Market Research Reports
Location: `market_research/`
Generates market research reports including:
- Sector analysis
- Industry trends
- Market overview
- Competitive analysis
- Market opportunities
- Risk factors
Usage:
```python
from lib.ai_writers.ai_finance_report_generator.reports.market_research import generate_market_research_report
report = generate_market_research_report(sectors=["Technology", "Healthcare"])
```
### 6. News Analysis Reports
Location: `news_analysis/`
Generates news analysis reports including:
- News sentiment analysis
- Market impact analysis
- Event correlation
- Trend detection
- Social media analysis
- News aggregation
Usage:
```python
from lib.ai_writers.ai_finance_report_generator.reports.news_analysis import generate_news_analysis_report
report = generate_news_analysis_report("AAPL")
```
## Common Features
All report modules share the following features:
1. **Data Validation**
- Input validation for symbols and parameters
- Error handling for invalid inputs
- Data type checking
2. **Report Formatting**
- Markdown formatting
- Chart generation (when applicable)
- Customizable templates
3. **Storage Integration**
- Automatic report storage
- Recent reports tracking
- Report versioning
4. **User Preferences**
- Customizable report formats
- Language selection
- Chart style preferences
## Integration with Dashboard
All report modules are integrated with the main dashboard and can be accessed through the `FinancialDashboard` class:
```python
from lib.ai_writers.ai_finance_report_generator.ai_financial_dashboard import get_dashboard
dashboard = get_dashboard()
# Generate reports through dashboard
ta_report = dashboard.generate_technical_analysis("AAPL")
options_report = dashboard.generate_options_analysis("AAPL")
# Get recent reports
recent_reports = dashboard.get_recent_reports()
```
## Adding New Report Types
To add a new report type:
1. Create a new directory in the `reports/` folder
2. Create an `__init__.py` file with the report generation function
3. Add the report type to the dashboard features
4. Implement the report generation logic
5. Add appropriate error handling and validation
Example:
```python
# reports/new_analysis/__init__.py
from typing import Dict, Any
from ...utils import validate_symbol
def generate_new_analysis_report(symbol: str) -> Dict[str, Any]:
"""
Generate a new type of analysis report.
Args:
symbol (str): Stock symbol to analyze
Returns:
Dict[str, Any]: Analysis report
"""
if not validate_symbol(symbol):
raise ValueError("Invalid symbol provided")
# Implement report generation logic
return {
"symbol": symbol,
"analysis": "Report content"
}
```
## Error Handling
All report modules implement consistent error handling:
1. **Input Validation**
- Symbol validation
- Parameter validation
- Data type checking
2. **Data Collection Errors**
- API errors
- Network errors
- Data format errors
3. **Report Generation Errors**
- LLM errors
- Template errors
- Formatting errors
4. **Storage Errors**
- File system errors
- Database errors
- Backup errors
## Contributing
When contributing to the reports module:
1. Follow the existing code structure
2. Add appropriate type hints
3. Include comprehensive docstrings
4. Add error handling
5. Update the dashboard integration
6. Add tests for new functionality
## Dependencies
The reports module depends on:
1. **Data Collection**
- `finance_data_researcher`
- `web_scraping_tools`
2. **Analysis Tools**
- `pandas_ta`
- `numpy`
- `scipy`
3. **Visualization**
- `matplotlib`
- `plotly`
4. **Text Generation**
- `llm_text_gen`
- `gpt_providers`
## License
This module is part of the AI Finance Report Generator project and is licensed under the MIT License.

View File

@@ -0,0 +1,34 @@
"""
Fundamental Analysis Reports Module
This module handles the generation of fundamental analysis reports including:
- Financial ratios
- Company valuation metrics
- Growth analysis
- Profitability analysis
- Debt analysis
- Cash flow analysis
"""
from typing import Dict, Any
from ...utils import validate_symbol
def generate_fa_report(symbol: str) -> Dict[str, Any]:
"""
Generate a fundamental analysis report for the given symbol.
Args:
symbol (str): Stock symbol to analyze
Returns:
Dict[str, Any]: Fundamental analysis report
"""
if not validate_symbol(symbol):
raise ValueError("Invalid symbol provided")
# TODO: Implement fundamental analysis report generation
return {
"symbol": symbol,
"status": "coming_soon",
"message": "Fundamental analysis report generation is coming soon"
}

View File

@@ -0,0 +1,29 @@
"""
Market Research Reports Module
This module handles the generation of market research reports including:
- Sector analysis
- Industry trends
- Market overview
- Competitive analysis
- Market opportunities
- Risk factors
"""
from typing import Dict, Any, List
def generate_market_research_report(sectors: List[str] = None) -> Dict[str, Any]:
"""
Generate a market research report.
Args:
sectors (List[str], optional): List of sectors to analyze
Returns:
Dict[str, Any]: Market research report
"""
# TODO: Implement market research report generation
return {
"status": "coming_soon",
"message": "Market research report generation is coming soon"
}

View File

@@ -0,0 +1,33 @@
"""
News Analysis Reports Module
This module handles the generation of news analysis reports including:
- News sentiment analysis
- Market impact analysis
- Event correlation
- Trend detection
- Social media analysis
- News aggregation
"""
from typing import Dict, Any, List
from ...utils import validate_symbol
def generate_news_analysis_report(symbol: str = None) -> Dict[str, Any]:
"""
Generate a news analysis report.
Args:
symbol (str, optional): Stock symbol to analyze news for
Returns:
Dict[str, Any]: News analysis report
"""
if symbol and not validate_symbol(symbol):
raise ValueError("Invalid symbol provided")
# TODO: Implement news analysis report generation
return {
"status": "coming_soon",
"message": "News analysis report generation is coming soon"
}

View File

@@ -0,0 +1,33 @@
"""
Options Analysis Reports Module
This module handles the generation of options analysis reports including:
- Options chain analysis
- Implied volatility analysis
- Options strategies
- Risk metrics
- Greeks analysis
"""
from typing import Dict, Any
from ...utils import validate_symbol
def generate_options_report(symbol: str) -> Dict[str, Any]:
"""
Generate an options analysis report for the given symbol.
Args:
symbol (str): Stock symbol to analyze
Returns:
Dict[str, Any]: Options analysis report
"""
if not validate_symbol(symbol):
raise ValueError("Invalid symbol provided")
# TODO: Implement options analysis report generation
return {
"symbol": symbol,
"status": "coming_soon",
"message": "Options analysis report generation is coming soon"
}

View File

@@ -0,0 +1,32 @@
"""
Portfolio Analysis Reports Module
This module handles the generation of portfolio analysis reports including:
- Portfolio performance analysis
- Risk assessment
- Asset allocation
- Correlation analysis
- Diversification metrics
- Performance attribution
"""
from typing import Dict, Any, List
def generate_portfolio_report(portfolio: List[Dict[str, Any]]) -> Dict[str, Any]:
"""
Generate a portfolio analysis report.
Args:
portfolio (List[Dict[str, Any]]): List of portfolio positions
Returns:
Dict[str, Any]: Portfolio analysis report
"""
if not portfolio:
raise ValueError("Portfolio cannot be empty")
# TODO: Implement portfolio analysis report generation
return {
"status": "coming_soon",
"message": "Portfolio analysis report generation is coming soon"
}

View File

@@ -0,0 +1,314 @@
"""
Technical Analysis Reports Module
This module handles the generation of technical analysis reports using yfinance data and pandas_ta for indicators.
"""
from typing import Dict, Any, List, Optional
import yfinance as yf
import pandas as pd
import pandas_ta as ta
import plotly.graph_objects as go
from datetime import datetime, timedelta
from loguru import logger
from ...utils import validate_symbol
from ...ai_financial_dashboard import get_dashboard
class TechnicalAnalysis:
def __init__(self, symbol: str, timeframe: str = "1d", period: str = "1y"):
"""
Initialize Technical Analysis.
Args:
symbol (str): Stock symbol to analyze
timeframe (str): Data timeframe (1m, 5m, 15m, 30m, 1h, 1d, 1wk, 1mo)
period (str): Data period (1d, 5d, 1mo, 3mo, 6mo, 1y, 2y, 5y, 10y, ytd, max)
"""
logger.info(f"Initializing Technical Analysis for {symbol} with timeframe {timeframe} and period {period}")
self.symbol = symbol
self.timeframe = timeframe
self.period = period
self.data = None
self.indicators = {}
self.stock = yf.Ticker(symbol)
def fetch_data(self) -> None:
"""Fetch historical price data using yfinance"""
try:
logger.info(f"Fetching historical data for {self.symbol}")
# Get historical data
self.data = self.stock.history(period=self.period, interval=self.timeframe)
logger.debug(f"Retrieved {len(self.data)} data points")
# Get additional info
logger.info("Fetching company information")
self.info = self.stock.info
# Calculate basic metrics
logger.debug("Calculating basic metrics")
self.data['Returns'] = self.data['Close'].pct_change()
self.data['Volatility'] = self.data['Returns'].rolling(window=20).std()
logger.success(f"Successfully fetched data for {self.symbol}")
except Exception as e:
logger.error(f"Error fetching data for {self.symbol}: {str(e)}")
raise ValueError(f"Error fetching data for {self.symbol}: {str(e)}")
def calculate_indicators(self) -> None:
"""Calculate technical indicators using pandas_ta"""
if self.data is None:
logger.error("Data not fetched. Call fetch_data() first.")
raise ValueError("Data not fetched. Call fetch_data() first.")
logger.info("Calculating technical indicators")
# Moving Averages
logger.debug("Calculating Moving Averages")
self.indicators['sma_20'] = self.data.ta.sma(length=20)
self.indicators['sma_50'] = self.data.ta.sma(length=50)
self.indicators['sma_200'] = self.data.ta.sma(length=200)
self.indicators['ema_20'] = self.data.ta.ema(length=20)
# RSI
logger.debug("Calculating RSI")
self.indicators['rsi'] = self.data.ta.rsi()
# MACD
logger.debug("Calculating MACD")
macd = self.data.ta.macd()
self.indicators['macd'] = macd['MACD_12_26_9']
self.indicators['macd_signal'] = macd['MACDs_12_26_9']
self.indicators['macd_hist'] = macd['MACDh_12_26_9']
# Bollinger Bands
logger.debug("Calculating Bollinger Bands")
bbands = self.data.ta.bbands()
self.indicators['bb_upper'] = bbands['BBU_20_2.0']
self.indicators['bb_middle'] = bbands['BBM_20_2.0']
self.indicators['bb_lower'] = bbands['BBL_20_2.0']
# Volume Analysis
logger.debug("Calculating Volume indicators")
self.indicators['volume_sma'] = self.data['Volume'].rolling(window=20).mean()
self.indicators['obv'] = self.data.ta.obv()
# Additional Indicators
logger.debug("Calculating additional indicators")
self.indicators['stoch'] = self.data.ta.stoch()
self.indicators['adx'] = self.data.ta.adx()
self.indicators['atr'] = self.data.ta.atr()
logger.success("Successfully calculated all technical indicators")
def identify_patterns(self) -> List[Dict[str, Any]]:
"""Identify chart patterns"""
logger.info("Identifying chart patterns")
patterns = []
# Candlestick Patterns
if len(self.data) >= 3:
logger.debug("Analyzing candlestick patterns")
# Doji
doji = self.data.ta.cdl_doji()
if doji['CDL_DOJI'].iloc[-1] != 0:
logger.debug("Doji pattern detected")
patterns.append({
'type': 'doji',
'date': self.data.index[-1],
'significance': 'neutral'
})
# Engulfing
engulfing = self.data.ta.cdl_engulfing()
if engulfing['CDL_ENGULFING'].iloc[-1] != 0:
logger.debug("Engulfing pattern detected")
patterns.append({
'type': 'engulfing',
'date': self.data.index[-1],
'significance': 'bullish' if engulfing['CDL_ENGULFING'].iloc[-1] > 0 else 'bearish'
})
logger.info(f"Identified {len(patterns)} patterns")
return patterns
def find_support_resistance(self) -> Dict[str, List[float]]:
"""Find support and resistance levels using price action"""
logger.info("Finding support and resistance levels")
levels = {
'support': [],
'resistance': []
}
# Use recent price action to identify levels
recent_data = self.data.tail(100)
logger.debug(f"Analyzing {len(recent_data)} recent data points for S/R levels")
# Find local minima and maxima
for i in range(2, len(recent_data) - 2):
# Support level
if (recent_data['Low'].iloc[i] < recent_data['Low'].iloc[i-1] and
recent_data['Low'].iloc[i] < recent_data['Low'].iloc[i-2] and
recent_data['Low'].iloc[i] < recent_data['Low'].iloc[i+1] and
recent_data['Low'].iloc[i] < recent_data['Low'].iloc[i+2]):
levels['support'].append(recent_data['Low'].iloc[i])
# Resistance level
if (recent_data['High'].iloc[i] > recent_data['High'].iloc[i-1] and
recent_data['High'].iloc[i] > recent_data['High'].iloc[i-2] and
recent_data['High'].iloc[i] > recent_data['High'].iloc[i+1] and
recent_data['High'].iloc[i] > recent_data['High'].iloc[i+2]):
levels['resistance'].append(recent_data['High'].iloc[i])
# Remove duplicates and sort
levels['support'] = sorted(list(set(levels['support'])))
levels['resistance'] = sorted(list(set(levels['resistance'])))
logger.info(f"Found {len(levels['support'])} support and {len(levels['resistance'])} resistance levels")
return levels
def generate_chart(self) -> go.Figure:
"""Generate interactive chart using plotly"""
logger.info("Generating interactive chart")
fig = go.Figure()
# Candlestick chart
logger.debug("Adding candlestick chart")
fig.add_trace(go.Candlestick(
x=self.data.index,
open=self.data['Open'],
high=self.data['High'],
low=self.data['Low'],
close=self.data['Close'],
name='Price'
))
# Moving Averages
logger.debug("Adding moving averages")
fig.add_trace(go.Scatter(
x=self.data.index,
y=self.indicators['sma_20'],
name='SMA 20',
line=dict(color='blue')
))
fig.add_trace(go.Scatter(
x=self.data.index,
y=self.indicators['sma_50'],
name='SMA 50',
line=dict(color='orange')
))
# Bollinger Bands
logger.debug("Adding Bollinger Bands")
fig.add_trace(go.Scatter(
x=self.data.index,
y=self.indicators['bb_upper'],
name='BB Upper',
line=dict(color='gray', dash='dash')
))
fig.add_trace(go.Scatter(
x=self.data.index,
y=self.indicators['bb_lower'],
name='BB Lower',
line=dict(color='gray', dash='dash'),
fill='tonexty'
))
# Volume
logger.debug("Adding volume bars")
fig.add_trace(go.Bar(
x=self.data.index,
y=self.data['Volume'],
name='Volume',
marker_color='rgba(0,0,255,0.3)'
))
# Layout
logger.debug("Setting chart layout")
fig.update_layout(
title=f'{self.symbol} Technical Analysis',
yaxis_title='Price',
xaxis_title='Date',
template='plotly_dark'
)
logger.success("Successfully generated chart")
return fig
def _generate_summary(self) -> Dict[str, Any]:
"""Generate summary of technical analysis"""
logger.info("Generating analysis summary")
current_price = self.data['Close'].iloc[-1]
sma_20 = self.indicators['sma_20'].iloc[-1]
sma_50 = self.indicators['sma_50'].iloc[-1]
rsi = self.indicators['rsi'].iloc[-1]
summary = {
'current_price': current_price,
'price_change': self.data['Returns'].iloc[-1] * 100,
'trend': 'bullish' if current_price > sma_20 > sma_50 else 'bearish',
'rsi_signal': 'overbought' if rsi > 70 else 'oversold' if rsi < 30 else 'neutral',
'volatility': self.data['Volatility'].iloc[-1],
'volume_trend': 'increasing' if self.data['Volume'].iloc[-1] > self.indicators['volume_sma'].iloc[-1] else 'decreasing'
}
logger.debug(f"Analysis summary: {summary}")
return summary
def generate_report(self) -> Dict[str, Any]:
"""Generate comprehensive technical analysis report"""
logger.info(f"Generating comprehensive report for {self.symbol}")
self.fetch_data()
self.calculate_indicators()
patterns = self.identify_patterns()
levels = self.find_support_resistance()
chart = self.generate_chart()
summary = self._generate_summary()
report = {
'symbol': self.symbol,
'timestamp': datetime.now(),
'company_info': self.info,
'indicators': self.indicators,
'patterns': patterns,
'levels': levels,
'chart': chart,
'summary': summary
}
logger.success(f"Successfully generated report for {self.symbol}")
return report
def generate_ta_report(symbol: str) -> Dict[str, Any]:
"""
Generate a technical analysis report for the given symbol.
Args:
symbol (str): Stock symbol to analyze
Returns:
Dict[str, Any]: Technical analysis report
"""
logger.info(f"Generating technical analysis report for {symbol}")
if not validate_symbol(symbol):
logger.error(f"Invalid symbol provided: {symbol}")
raise ValueError("Invalid symbol provided")
try:
analysis = TechnicalAnalysis(symbol)
report = analysis.generate_report()
# Add to dashboard's recent reports
dashboard = get_dashboard()
dashboard.add_recent_report("technical_analysis", symbol, report)
logger.success(f"Successfully completed technical analysis for {symbol}")
return report
except Exception as e:
logger.error(f"Error generating technical analysis report for {symbol}: {str(e)}")
raise

View File

@@ -0,0 +1,62 @@
"""
Utility functions and helpers for the AI Finance Report Generator.
"""
from typing import Dict, List, Any
import logging
# Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
def validate_symbol(symbol: str) -> bool:
"""
Validate if the given symbol is in correct format.
Args:
symbol (str): Stock symbol to validate
Returns:
bool: True if valid, False otherwise
"""
if not isinstance(symbol, str):
return False
return len(symbol.strip()) > 0
def format_currency(value: float) -> str:
"""
Format number as currency.
Args:
value (float): Number to format
Returns:
str: Formatted currency string
"""
return f"${value:,.2f}"
def get_feature_status(feature_name: str) -> Dict[str, Any]:
"""
Get the status of a feature.
Args:
feature_name (str): Name of the feature
Returns:
Dict[str, Any]: Feature status information
"""
# This will be expanded as we implement more features
implemented_features = {
"technical_analysis": True,
"options_analysis": True,
}
return {
"name": feature_name,
"implemented": implemented_features.get(feature_name, False),
"coming_soon": not implemented_features.get(feature_name, False)
}

View File

@@ -0,0 +1,208 @@
"""
Storage Module for AI Finance Report Generator
This module handles the persistence of user preferences and recent reports using JSON files.
"""
import json
import os
from typing import Dict, List, Any, Optional
from datetime import datetime
from pathlib import Path
class StorageManager:
"""Manages storage operations for user preferences and recent reports."""
def __init__(self, base_dir: Optional[str] = None):
"""
Initialize the storage manager.
Args:
base_dir (Optional[str]): Base directory for storage files
"""
if base_dir is None:
# Use user's home directory by default
self.base_dir = Path.home() / ".ai_finance"
else:
self.base_dir = Path(base_dir)
# Create storage directory if it doesn't exist
self.base_dir.mkdir(parents=True, exist_ok=True)
# Define file paths
self.prefs_file = self.base_dir / "preferences.json"
self.reports_file = self.base_dir / "recent_reports.json"
# Initialize files if they don't exist
self._initialize_storage()
def _initialize_storage(self) -> None:
"""Initialize storage files if they don't exist."""
if not self.prefs_file.exists():
self._save_preferences({})
if not self.reports_file.exists():
self._save_reports([])
def _save_preferences(self, preferences: Dict[str, Any]) -> None:
"""
Save user preferences to file.
Args:
preferences (Dict[str, Any]): User preferences to save
"""
with open(self.prefs_file, 'w') as f:
json.dump(preferences, f, indent=4)
def _load_preferences(self) -> Dict[str, Any]:
"""
Load user preferences from file.
Returns:
Dict[str, Any]: User preferences
"""
try:
with open(self.prefs_file, 'r') as f:
return json.load(f)
except (json.JSONDecodeError, FileNotFoundError):
return {}
def _save_reports(self, reports: List[Dict[str, Any]]) -> None:
"""
Save recent reports to file.
Args:
reports (List[Dict[str, Any]]): Recent reports to save
"""
with open(self.reports_file, 'w') as f:
json.dump(reports, f, indent=4)
def _load_reports(self) -> List[Dict[str, Any]]:
"""
Load recent reports from file.
Returns:
List[Dict[str, Any]]: Recent reports
"""
try:
with open(self.reports_file, 'r') as f:
return json.load(f)
except (json.JSONDecodeError, FileNotFoundError):
return []
def save_user_preferences(self, preferences: Dict[str, Any]) -> None:
"""
Save user preferences.
Args:
preferences (Dict[str, Any]): User preferences to save
"""
self._save_preferences(preferences)
def load_user_preferences(self) -> Dict[str, Any]:
"""
Load user preferences.
Returns:
Dict[str, Any]: User preferences
"""
return self._load_preferences()
def save_recent_reports(self, reports: List[Dict[str, Any]]) -> None:
"""
Save recent reports.
Args:
reports (List[Dict[str, Any]]): Recent reports to save
"""
# Convert datetime objects to ISO format strings
serialized_reports = []
for report in reports:
serialized_report = report.copy()
if isinstance(report.get('timestamp'), datetime):
serialized_report['timestamp'] = report['timestamp'].isoformat()
serialized_reports.append(serialized_report)
self._save_reports(serialized_reports)
def load_recent_reports(self) -> List[Dict[str, Any]]:
"""
Load recent reports.
Returns:
List[Dict[str, Any]]: Recent reports with datetime objects
"""
reports = self._load_reports()
# Convert ISO format strings back to datetime objects
for report in reports:
if isinstance(report.get('timestamp'), str):
report['timestamp'] = datetime.fromisoformat(report['timestamp'])
return reports
def clear_storage(self) -> None:
"""Clear all stored data."""
self._save_preferences({})
self._save_reports([])
def backup_storage(self, backup_dir: Optional[str] = None) -> None:
"""
Create a backup of the storage files.
Args:
backup_dir (Optional[str]): Directory to store backup files
"""
if backup_dir is None:
backup_dir = self.base_dir / "backups"
else:
backup_dir = Path(backup_dir)
backup_dir.mkdir(parents=True, exist_ok=True)
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
# Backup preferences
if self.prefs_file.exists():
backup_prefs = backup_dir / f"preferences_{timestamp}.json"
with open(self.prefs_file, 'r') as src, open(backup_prefs, 'w') as dst:
dst.write(src.read())
# Backup reports
if self.reports_file.exists():
backup_reports = backup_dir / f"recent_reports_{timestamp}.json"
with open(self.reports_file, 'r') as src, open(backup_reports, 'w') as dst:
dst.write(src.read())
def restore_from_backup(self, backup_file: str) -> None:
"""
Restore storage from a backup file.
Args:
backup_file (str): Path to the backup file
"""
backup_path = Path(backup_file)
if not backup_path.exists():
raise FileNotFoundError(f"Backup file not found: {backup_file}")
# Determine which type of backup file it is
if "preferences" in backup_path.name:
with open(backup_path, 'r') as src, open(self.prefs_file, 'w') as dst:
dst.write(src.read())
elif "recent_reports" in backup_path.name:
with open(backup_path, 'r') as src, open(self.reports_file, 'w') as dst:
dst.write(src.read())
else:
raise ValueError(f"Unknown backup file type: {backup_file}")
def get_storage_manager(base_dir: Optional[str] = None) -> StorageManager:
"""
Get a storage manager instance.
Args:
base_dir (Optional[str]): Base directory for storage files
Returns:
StorageManager: Storage manager instance
"""
return StorageManager(base_dir)

View File

@@ -1,91 +0,0 @@
import sys
import os
from textwrap import dedent
from pathlib import Path
from datetime import datetime
from loguru import logger
logger.remove()
logger.add(sys.stdout,
colorize=True,
format="<level>{level}</level>|<green>{file}:{line}:{function}</green>| {message}"
)
from ..ai_web_researcher.finance_data_researcher import get_finance_data, get_fin_options_data
from ..gpt_providers.text_generation.main_text_generation import llm_text_gen
def write_basic_ta_report(symbol):
""" Write financial TA for given ticker symbol """
try:
symbol_fin_data = get_finance_data(symbol)
#get_visual_reports
fin_report = gen_finta_report(symbol_fin_data, symbol)
logger.info(f"Done: Final Technical Analysis for {symbol}:\n\n")
except Exception as err:
logger.error(f"Error: Failed to generate Financial report: {err}")
#fin_options_data = get_fin_options_data(symbol)
#options_report = gen_options_report(fin_options_data, symbol)
def gen_options_report(results_sentences, ticker):
""" Call LLM to generate options report """
prompt = f"""
You are a financial expert specializing in options trading and market sentiment analysis.
You have been provided with the following technical analysis of options data for the ticker symbol {ticker} with the nearest expiry date:
{chr(10).join(results_sentences)}
Based on this data, provide a comprehensive analysis of the options market for {ticker}.
Your analysis should include:
1. **Implied Volatility Interpretation:** Discuss the significance of the average implied volatility for both call and put options. What does it suggest about market expectations of future price movements?
2. **Volume and Open Interest Insights:** Analyze the volume and open interest for call and put options. What does this data reveal about current market positioning and potential future trading activity?
3. **Sentiment Analysis:** Evaluate the put-call ratio, implied volatility skew, and overall market sentiment. What do these indicators suggest about trader sentiment and potential future price direction?
4. **Potential Trading Strategies:** Based on your analysis, suggest potential options trading strategies that could be employed for {ticker}, considering the current market conditions and sentiment.
Please provide your analysis in a clear and concise manner, suitable for someone with a good understanding of options trading.
"""
logger.info("Generating Financial Technical report..")
try:
response = llm_text_gen(prompt)
return response
except Exception as err:
logger.error(f"Exit: Failed to get response from LLM: {err}")
exit(1)
def gen_finta_report(last_day_summary, symbol):
""" Get AI to write TA report from given data """
prompt = f"""
You are a seasoned Technical Analysis (TA) expert, rivaling legends like Charles Dow, John Bollinger, and Alan Andrews.
Your deep understanding of market dynamics, coupled with mastery of technical indicators,
allows you to decipher complex patterns and offer precise predictions.
Your expertise extends to practical tools like the pandas_ta module, enabling you to extract valuable insights from raw data.
**Objective:**
Analyze the provided technical indicators for {symbol} on its last trading day and predict its price movement over the next few trading sessions.
**Instructions:**
1. **Identify Potential Trading Signals:** Highlight specific indicators suggesting bullish, bearish, or neutral signals. Explain the rationale behind each signal, referencing historical patterns or comparable market scenarios.
2. **Detect Patterns and Divergences:** Analyze the interplay between different indicators. Detect patterns like moving average crossovers, candlestick formations, or divergences between price action and indicators. Explain the significance of each pattern.
3. **Price Movement Prediction:** Based on your analysis, provide a clear prediction for {symbol}'s price movement in the next few days. State the expected direction (up, down, sideways) and potential price targets if identifiable.
4. **Risk Assessment:** Briefly discuss any potential risks or factors that could invalidate your predictions, promoting a balanced and informed perspective.
**Technical Indicators for {symbol} on the Last Trading Day:**
{last_day_summary}
Remember, your analysis should be detailed, insightful, and actionable for traders seeking to capitalize on market movements.
"""
logger.info("Generating Financial Technical report..")
try:
response = llm_text_gen(prompt)
return response
except Exception as err:
logger.error(f"Exit: Failed to get response from LLM: {err}")
exit(1)

View File

@@ -1,13 +1,33 @@
"""
Smart Tweet Generator with modern UI components.
"""
import streamlit as st
import re
import json
import time
from typing import Dict, List, Tuple, Optional
import random
import emoji
from datetime import datetime
from ....gpt_providers.text_generation.main_text_generation import llm_text_gen
from ..twitter_streamlit_ui import (
TweetForm,
TweetCard,
Theme,
save_to_session,
get_from_session,
show_success_message,
show_error_message,
show_info_message,
validate_tweet_content,
validate_hashtags,
validate_emojis,
calculate_engagement_score,
generate_tweet_metrics,
copy_to_clipboard,
create_download_button
)
# Constants
MAX_TWEET_LENGTH = 280
@@ -19,14 +39,6 @@ EMOJI_CATEGORIES = {
"Casual": ["👋", "👍", "🙋", "💁", "🤗", "👌", "✌️", "🤝", "👊", "🙏"]
}
def count_characters(text: str) -> int:
"""Count characters in tweet, accounting for emojis."""
return len(text)
def extract_hashtags(text: str) -> List[str]:
"""Extract hashtags from tweet text."""
return re.findall(r'#\w+', text)
def suggest_hashtags(topic: str, tone: str) -> List[str]:
"""Suggest relevant hashtags based on topic and tone."""
# Enhanced hashtag suggestions based on topic and tone
@@ -61,63 +73,6 @@ def suggest_emojis(tone: str, count: int = 3) -> List[str]:
}
return emoji_map.get(tone.lower(), [""])[:count]
def predict_tweet_performance(tweet: str, target_audience: str, tone: str) -> Dict:
"""Predict tweet performance with enhanced metrics."""
char_count = count_characters(tweet)
hashtags = extract_hashtags(tweet)
# Enhanced performance metrics
metrics = {
"character_count": {
"score": min(100, (char_count / 280) * 100),
"status": "optimal" if 100 <= char_count <= 200 else "suboptimal",
"suggestion": "Consider adjusting length for optimal engagement" if char_count < 100 or char_count > 200 else "Length is optimal"
},
"hashtag_usage": {
"score": min(100, (len(hashtags) / 3) * 100),
"status": "optimal" if 1 <= len(hashtags) <= 3 else "suboptimal",
"suggestion": "Add more hashtags" if len(hashtags) < 1 else "Reduce hashtag count" if len(hashtags) > 3 else "Hashtag count is optimal"
},
"engagement_potential": {
"score": 0,
"status": "needs_improvement",
"suggestion": ""
},
"audience_alignment": {
"score": 0,
"status": "needs_improvement",
"suggestion": ""
}
}
# Calculate engagement potential
engagement_triggers = ["?", "!", "RT", "like", "follow", "check", "learn", "discover"]
trigger_count = sum(1 for trigger in engagement_triggers if trigger.lower() in tweet.lower())
metrics["engagement_potential"]["score"] = min(100, (trigger_count / 3) * 100)
metrics["engagement_potential"]["status"] = "optimal" if trigger_count >= 1 else "needs_improvement"
metrics["engagement_potential"]["suggestion"] = "Add engagement triggers" if trigger_count < 1 else "Good engagement potential"
# Calculate audience alignment
audience_keywords = {
"professionals": ["business", "industry", "professional", "career"],
"students": ["learn", "study", "education", "student"],
"general": ["everyone", "people", "community", "world"]
}
keyword_count = sum(1 for keyword in audience_keywords.get(target_audience.lower(), [])
if keyword.lower() in tweet.lower())
metrics["audience_alignment"]["score"] = min(100, (keyword_count / 2) * 100)
metrics["audience_alignment"]["status"] = "optimal" if keyword_count >= 1 else "needs_improvement"
metrics["audience_alignment"]["suggestion"] = "Add audience-specific keywords" if keyword_count < 1 else "Good audience alignment"
# Calculate overall score
overall_score = sum(metric["score"] for metric in metrics.values()) / len(metrics)
return {
"metrics": metrics,
"overall_score": overall_score,
"status": "excellent" if overall_score >= 80 else "good" if overall_score >= 60 else "fair" if overall_score >= 40 else "needs_improvement"
}
def generate_tweet_variations(
hook: str,
target_audience: str,
@@ -177,58 +132,14 @@ def generate_tweet_variations(
return sample_tweets[:num_variations]
def suggest_improvements(tweet: str, performance: Dict) -> List[str]:
"""Generate actionable improvement suggestions."""
suggestions = []
metrics = performance["metrics"]
# Character count suggestions
if metrics["character_count"]["status"] == "suboptimal":
suggestions.append(f"📝 {metrics['character_count']['suggestion']}")
# Hashtag suggestions
if metrics["hashtag_usage"]["status"] == "suboptimal":
suggestions.append(f"#️⃣ {metrics['hashtag_usage']['suggestion']}")
# Engagement suggestions
if metrics["engagement_potential"]["status"] == "needs_improvement":
suggestions.append(f"🎯 {metrics['engagement_potential']['suggestion']}")
# Audience alignment suggestions
if metrics["audience_alignment"]["status"] == "needs_improvement":
suggestions.append(f"👥 {metrics['audience_alignment']['suggestion']}")
return suggestions
def render_tweet_card(tweet: Dict, index: int) -> None:
"""Render an enhanced tweet card with interactive elements."""
with st.container():
st.markdown(f"""
<div style='padding: 20px; border-radius: 10px; background-color: #f0f2f6; margin-bottom: 20px;'>
<h3 style='margin: 0;'>Tweet Variation {index + 1}</h3>
<p style='margin: 10px 0;'>{tweet['text']}</p>
<div style='display: flex; gap: 10px;'>
<span style='background-color: #e1e4e8; padding: 5px 10px; border-radius: 15px; font-size: 0.8em;'>
Score: {tweet['engagement_score']}%
</span>
</div>
</div>
""", unsafe_allow_html=True)
# Interactive elements
col1, col2 = st.columns(2)
with col1:
if st.button(f"Copy Tweet {index + 1}", key=f"copy_{index}"):
st.write("Tweet copied to clipboard!")
with col2:
if st.button(f"Save Tweet {index + 1}", key=f"save_{index}"):
st.write("Tweet saved!")
def smart_tweet_generator():
"""Enhanced Smart Tweet Generator with improved UI and AI integration."""
st.title("✨ Smart Tweet Generator")
st.markdown("Create engaging tweets with AI-powered optimization")
# Apply theme
Theme().apply()
# Input section with improved UI
with st.expander("Tweet Parameters", expanded=True):
col1, col2 = st.columns(2)
@@ -291,34 +202,27 @@ def smart_tweet_generator():
# Display performance metrics
st.markdown("### 📊 Performance Metrics")
for tweet in tweets:
performance = predict_tweet_performance(tweet["text"], target_audience, tone)
# Calculate engagement score
engagement_score = calculate_engagement_score(
tweet["text"],
tweet["hashtags"],
tweet["emojis"],
tone
)
# Overall score with progress bar
st.progress(performance["overall_score"] / 100)
st.metric("Overall Score", f"{performance['overall_score']:.1f}%")
# Generate metrics
metrics = generate_tweet_metrics(engagement_score)
# Detailed metrics in columns
cols = st.columns(4)
metrics = performance["metrics"]
with cols[0]:
st.metric("Character Count", f"{metrics['character_count']['score']:.1f}%")
with cols[1]:
st.metric("Hashtag Usage", f"{metrics['hashtag_usage']['score']:.1f}%")
with cols[2]:
st.metric("Engagement", f"{metrics['engagement_potential']['score']:.1f}%")
with cols[3]:
st.metric("Audience Fit", f"{metrics['audience_alignment']['score']:.1f}%")
# Improvement suggestions
suggestions = suggest_improvements(tweet["text"], performance)
if suggestions:
st.markdown("### 💡 Improvement Suggestions")
for suggestion in suggestions:
st.info(suggestion)
# Tweet card
render_tweet_card(tweet, tweets.index(tweet))
# Display tweet card
TweetCard(
content=tweet["text"],
engagement_score=engagement_score,
hashtags=tweet["hashtags"],
emojis=tweet["emojis"],
metrics=metrics,
on_copy=lambda: copy_to_clipboard(tweet["text"]),
on_save=lambda: save_tweet(tweet)
).render()
st.markdown("---")
@@ -326,17 +230,23 @@ def smart_tweet_generator():
st.markdown("### 📥 Export Options")
col1, col2 = st.columns(2)
with col1:
if st.button("Export as JSON"):
st.download_button(
"Download JSON",
data=json.dumps(tweets, indent=2),
file_name=f"tweets_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json",
mime="application/json"
)
create_download_button(
data=tweets,
filename=f"tweets_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json",
button_text="Export as JSON"
)
with col2:
if st.button("Copy All Tweets"):
tweet_texts = "\n\n".join(tweet["text"] for tweet in tweets)
st.code(tweet_texts)
copy_to_clipboard(tweet_texts)
show_success_message("All tweets copied to clipboard!")
def save_tweet(tweet: Dict):
"""Save tweet for later."""
tweets = get_from_session("tweets", [])
tweets.append(tweet)
save_to_session("tweets", tweets)
show_success_message("Tweet saved successfully!")
if __name__ == "__main__":
smart_tweet_generator()

View File

@@ -1,116 +1,29 @@
"""
Twitter Dashboard with modern UI components.
"""
import streamlit as st
import streamlit.components.v1 as components
from typing import Dict, List
import json
import base64
from datetime import datetime
from .tweet_generator import smart_tweet_generator
def add_bg_from_base64(base64_string):
"""Add background image using base64 string."""
return f'''
<style>
.stApp {{
background-image: url("data:image/png;base64,{base64_string}");
background-size: cover;
background-position: center;
background-repeat: no-repeat;
background-attachment: fixed;
}}
/* Enhanced styling for containers */
.element-container, .stMarkdown, .stButton {{
background-color: rgba(0, 0, 0, 0.7);
border-radius: 10px;
padding: 20px;
margin: 10px 0;
backdrop-filter: blur(10px);
}}
/* Typography enhancements */
h1, h2, h3 {{
color: #ffffff !important;
font-family: 'Helvetica Neue', sans-serif;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
}}
p, li {{
color: #e0e0e0 !important;
font-family: 'Helvetica Neue', sans-serif;
}}
/* Button styling */
.stButton > button {{
background: linear-gradient(45deg, #1DA1F2, #0C85D0);
color: white;
border: none;
padding: 10px 20px;
border-radius: 25px;
font-weight: bold;
transition: all 0.3s ease;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}}
.stButton > button:hover {{
transform: translateY(-2px);
box-shadow: 0 6px 8px rgba(0, 0, 0, 0.2);
}}
/* Tab styling */
.stTabs [data-baseweb="tab-list"] {{
gap: 8px;
background-color: rgba(0, 0, 0, 0.6);
padding: 10px;
border-radius: 10px;
}}
.stTabs [data-baseweb="tab"] {{
background-color: transparent;
color: #ffffff;
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 5px;
}}
.stTabs [data-baseweb="tab"]:hover {{
background-color: rgba(29, 161, 242, 0.2);
}}
/* Feature card styling */
.feature-card {{
background: linear-gradient(135deg, rgba(29, 161, 242, 0.1), rgba(0, 0, 0, 0.3));
border: 1px solid rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
padding: 20px;
border-radius: 15px;
margin-bottom: 20px;
transition: transform 0.3s ease;
}}
.feature-card:hover {{
transform: translateY(-5px);
}}
/* Status badge styling */
.status-badge {{
display: inline-block;
padding: 5px 15px;
border-radius: 20px;
font-size: 0.8em;
font-weight: bold;
text-transform: uppercase;
}}
.status-active {{
background: linear-gradient(45deg, #00C853, #69F0AE);
color: #000000;
}}
.status-coming-soon {{
background: linear-gradient(45deg, #FFD700, #FFA000);
color: #000000;
}}
</style>
'''
from .twitter_streamlit_ui import (
TwitterDashboard,
FeatureCard,
TweetForm,
SettingsForm,
Sidebar,
Header,
Tabs,
Breadcrumbs,
Theme,
save_to_session,
get_from_session,
clear_session,
show_success_message,
show_error_message
)
def load_feature_data() -> Dict:
"""Load feature data from a structured format."""
@@ -232,125 +145,167 @@ def load_feature_data() -> Dict:
}
}
def render_feature_card(feature: Dict) -> None:
"""Render a single feature card with its details."""
status_class = "status-active" if feature['status'] == 'active' else "status-coming-soon"
with st.container():
st.markdown(f"""
<div class='feature-card'>
<h3 style='color: #ffffff; margin: 0;'>{feature['icon']} {feature['name']}</h3>
<p style='color: #e0e0e0; margin: 10px 0;'>{feature['description']}</p>
<span class='status-badge {status_class}'>
{feature['status'].title()}
</span>
</div>
""", unsafe_allow_html=True)
def render_category_section(category: Dict) -> None:
"""Render a category section with all its features."""
st.markdown(f"### {category['icon']} {category['title']}")
st.markdown(f"*{category['description']}*")
col1, col2 = st.columns(2)
with col1:
render_feature_card(category['features'][0])
with col2:
render_feature_card(category['features'][1])
def get_space_background() -> str:
"""Return base64 encoded space-themed background."""
return """iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mN8/+F9PQAJYgN4hWvGzQAAAABJRU5ErkJggg==""" # This is a placeholder. You'll need to replace with actual base64 image
def run_dashboard():
"""Main function to run the Twitter dashboard."""
# Add space-themed background
st.markdown(add_bg_from_base64(get_space_background()), unsafe_allow_html=True)
# Initialize dashboard
dashboard = TwitterDashboard()
# Enhanced Header with gradient text
st.markdown("""
<div style='text-align: center; padding: 40px 0;'>
<h1 style='
font-size: 3em;
background: linear-gradient(45deg, #1DA1F2, #ffffff);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
margin-bottom: 10px;
'>🐦 Twitter AI Writer</h1>
<p style='
font-size: 1.2em;
color: #e0e0e0;
max-width: 600px;
margin: 0 auto;
'>Your all-in-one Twitter content creation and management platform.
Harness the power of AI to enhance your Twitter marketing strategy.</p>
</div>
""", unsafe_allow_html=True)
# Load feature data
features = load_feature_data()
# Create tabs with enhanced styling
tab1, tab2, tab3 = st.tabs(["🎯 Quick Actions", "📊 Analytics", "⚙️ Settings"])
with tab1:
st.markdown("<h2 style='color: #ffffff;'>🚀 Quick Actions</h2>", unsafe_allow_html=True)
col1, col2, col3 = st.columns(3)
with col1:
if st.button("📝 Create New Tweet", use_container_width=True):
# Call the Smart Tweet Generator
smart_tweet_generator()
with col2:
st.button("📅 Schedule Content", use_container_width=True)
with col3:
st.button("📊 View Analytics", use_container_width=True)
with tab2:
st.markdown("### 📈 Analytics Dashboard")
st.info("Analytics features coming soon! Stay tuned for detailed insights and performance metrics.")
with tab3:
st.markdown("### ⚙️ Settings")
st.info("Settings and configuration options coming soon!")
# Main content area
st.markdown("## 🛠️ Available Tools")
# Render each category
for category in features.values():
render_category_section(category)
# If this is the tweet generation category and the Smart Tweet Generator is active,
# add a button to launch it
if category["title"] == "Tweet Generation & Optimization" and category["features"][0]["status"] == "active":
if st.button(f"🚀 Launch {category['features'][0]['name']}", use_container_width=True):
category["features"][0]["function"]()
# Setup navigation
sidebar = Sidebar(title="Twitter Tools")
sidebar.add_menu_item("Dashboard", "📊", "dashboard")
sidebar.add_menu_item("Tweet Generator", "✍️", "tweet_generator")
sidebar.add_menu_item("Analytics", "📈", "analytics")
sidebar.add_menu_item("Settings", "⚙️", "settings")
# Setup header
header = Header(
title="Twitter AI Writer",
subtitle="Your all-in-one Twitter content creation and management platform"
)
header.add_action("New Tweet", "✏️", lambda: save_to_session("current_page", "tweet_generator"))
header.add_action("Refresh", "🔄", lambda: st.experimental_rerun())
# Setup tabs
tabs = Tabs()
tabs.add_tab("Overview", "📊", lambda: render_overview(features))
tabs.add_tab("Recent Tweets", "🐦", lambda: render_recent_tweets())
tabs.add_tab("Analytics", "📈", lambda: render_analytics())
# Setup breadcrumbs
breadcrumbs = Breadcrumbs()
breadcrumbs.add_item("Home", "dashboard", "🏠")
breadcrumbs.add_item(get_from_session("current_page", "Dashboard").title())
# Render dashboard
dashboard.render()
# Enhanced Footer
st.markdown("---")
st.markdown("""
<div style='text-align: center; padding: 20px; background: rgba(0, 0, 0, 0.5); border-radius: 10px;'>
<p style='color: #ffffff; margin-bottom: 10px;'>Need assistance? We're here to help!</p>
<div style='display: flex; justify-content: center; gap: 20px;'>
<a href='#' style='
text-decoration: none;
color: #1DA1F2;
background: rgba(255, 255, 255, 0.1);
padding: 8px 20px;
border-radius: 20px;
transition: all 0.3s ease;
'>📚 Documentation</a>
<a href='#' style='
text-decoration: none;
color: #1DA1F2;
background: rgba(255, 255, 255, 0.1);
padding: 8px 20px;
border-radius: 20px;
transition: all 0.3s ease;
'>💬 Contact Support</a>
</div>
</div>
""", unsafe_allow_html=True)
def render_overview(features: Dict):
"""Render the overview tab content."""
# Feature cards
col1, col2, col3 = st.columns(3)
with col1:
FeatureCard(
title="Tweet Generator",
description="Create engaging tweets with AI assistance",
icon="✍️",
features=[
{
"name": "AI-Powered",
"description": "Generate tweets using advanced AI"
},
{
"name": "Customizable",
"description": "Adjust tone, length, and style"
}
],
on_click=lambda: save_to_session("current_page", "tweet_generator")
).render()
with col2:
FeatureCard(
title="Analytics",
description="Track your tweet performance",
icon="📈",
features=[
{
"name": "Engagement",
"description": "Monitor likes, retweets, and replies"
},
{
"name": "Growth",
"description": "Track follower growth over time"
}
]
).render()
with col3:
FeatureCard(
title="Settings",
description="Customize your experience",
icon="⚙️",
features=[
{
"name": "Preferences",
"description": "Set your default options"
},
{
"name": "API",
"description": "Configure Twitter API settings"
}
]
).render()
def render_recent_tweets():
"""Render the recent tweets tab content."""
# Tweet form
tweet_form = TweetForm(
on_submit=lambda: handle_tweet_submit()
)
tweet_form.render()
# Recent tweets
st.markdown("### Recent Tweets")
tweets = get_from_session("tweets", [])
for tweet in tweets:
TweetCard(
content=tweet["content"],
engagement_score=tweet["engagement_score"],
hashtags=tweet["hashtags"],
emojis=tweet["emojis"],
metrics=tweet["metrics"],
on_copy=lambda: copy_tweet(tweet),
on_save=lambda: save_tweet(tweet)
).render()
def render_analytics():
"""Render the analytics tab content."""
st.markdown("### Tweet Analytics")
st.info("Analytics features coming soon!")
def handle_tweet_submit():
"""Handle tweet form submission."""
# Get form data
content = get_from_session("tweet_content")
tone = get_from_session("tone")
length = get_from_session("length")
hashtags = get_from_session("hashtags")
emojis = get_from_session("emojis")
engagement_boost = get_from_session("engagement_boost")
# Create tweet object
tweet = {
"content": content,
"tone": tone,
"length": length,
"hashtags": hashtags,
"emojis": emojis,
"engagement_score": engagement_boost,
"metrics": {
"Engagement": engagement_boost,
"Reach": engagement_boost * 0.8,
"Growth": engagement_boost * 0.6
}
}
# Add to tweets list
tweets = get_from_session("tweets", [])
tweets.append(tweet)
save_to_session("tweets", tweets)
# Show success message
show_success_message("Tweet created successfully!")
def copy_tweet(tweet: Dict):
"""Copy tweet to clipboard."""
show_success_message("Tweet copied to clipboard!")
def save_tweet(tweet: Dict):
"""Save tweet for later."""
show_success_message("Tweet saved!")
if __name__ == "__main__":
run_dashboard()

View File

@@ -0,0 +1,203 @@
# Twitter Streamlit UI Components
This module provides a unified, reusable UI component library for all Twitter-related features in the AI Writer suite. It implements best practices for Streamlit UI development and ensures consistency across all Twitter tools.
## Structure
```
twitter_streamlit_ui/
├── components/ # Reusable UI components
│ ├── __init__.py
│ ├── cards.py # Card components (feature cards, tweet cards)
│ ├── forms.py # Form components (input forms, settings forms)
│ ├── navigation.py # Navigation components (tabs, sidebar)
│ ├── feedback.py # Feedback components (loading, errors, success)
│ └── layout.py # Layout components (containers, columns)
├── styles/ # CSS and styling
│ ├── __init__.py
│ ├── theme.py # Theme configuration
│ ├── components.py # Component-specific styles
│ └── animations.py # Animation styles
├── utils/ # UI utilities
│ ├── __init__.py
│ ├── state.py # State management
│ ├── validation.py # Input validation
│ └── performance.py # Performance optimizations
└── README.md # This file
```
## Key Improvements
### 1. Consistent UI Components
- **Card Components**
- Feature cards with consistent styling
- Tweet cards with standardized layout
- Status badges with unified design
- **Form Components**
- Standardized input forms
- Consistent validation feedback
- Unified error handling
- **Navigation Components**
- Consistent tab styling
- Standardized sidebar navigation
- Breadcrumb navigation
### 2. Enhanced User Experience
- **Loading States**
- Progress indicators for long operations
- Skeleton loading for content
- Smooth transitions between states
- **Feedback Mechanisms**
- Toast notifications for actions
- Error messages with recovery options
- Success confirmations
- **Responsive Design**
- Mobile-friendly layouts
- Adaptive column systems
- Flexible containers
### 3. Performance Optimizations
- **State Management**
- Centralized state handling
- Efficient data persistence
- Optimized re-rendering
- **Resource Loading**
- Lazy loading of components
- Optimized image loading
- Cached computations
### 4. Accessibility Features
- **Keyboard Navigation**
- Focus management
- Keyboard shortcuts
- ARIA labels
- **Visual Accessibility**
- High contrast themes
- Screen reader support
- Color blind friendly
### 5. Error Handling
- **Graceful Degradation**
- Fallback UI components
- Error boundaries
- Recovery options
- **User Feedback**
- Clear error messages
- Actionable suggestions
- Help documentation
## Usage
### Basic Component Usage
```python
from twitter_streamlit_ui.components.cards import FeatureCard
from twitter_streamlit_ui.components.forms import TweetForm
from twitter_streamlit_ui.styles.theme import apply_theme
# Apply theme
apply_theme()
# Use components
feature_card = FeatureCard(
title="Tweet Generator",
description="Create engaging tweets with AI",
icon="🐦"
)
feature_card.render()
tweet_form = TweetForm()
tweet_form.render()
```
### State Management
```python
from twitter_streamlit_ui.utils.state import StateManager
# Initialize state
state = StateManager()
state.initialize()
# Update state
state.update("current_tweet", tweet_data)
```
### Error Handling
```python
from twitter_streamlit_ui.components.feedback import ErrorBoundary
with ErrorBoundary():
# Your code here
pass
```
## Best Practices
1. **Component Reusability**
- Use existing components when possible
- Create new components only when necessary
- Follow the established patterns
2. **State Management**
- Use the StateManager for all state
- Avoid direct session state manipulation
- Keep state updates atomic
3. **Performance**
- Use lazy loading for heavy components
- Implement caching where appropriate
- Monitor render performance
4. **Accessibility**
- Include ARIA labels
- Ensure keyboard navigation
- Test with screen readers
5. **Error Handling**
- Use ErrorBoundary components
- Provide clear error messages
- Include recovery options
## Future Improvements
1. **Component Library**
- Add more specialized components
- Enhance existing components
- Create component documentation
2. **Theme System**
- Add more theme options
- Implement theme switching
- Create custom theme builder
3. **Performance**
- Implement virtual scrolling
- Add performance monitoring
- Optimize resource loading
4. **Testing**
- Add component tests
- Implement E2E tests
- Create test documentation
## Contributing
1. Follow the established patterns
2. Add tests for new components
3. Update documentation
4. Ensure accessibility
5. Optimize performance

View File

@@ -0,0 +1,66 @@
"""
Twitter Streamlit UI package.
Provides a modern and user-friendly interface for Twitter tools.
"""
from .dashboard import TwitterDashboard
from .components.cards import FeatureCard, TweetCard
from .components.forms import TweetForm, SettingsForm
from .components.navigation import Sidebar, Header, Tabs, Breadcrumbs
from .styles.theme import Theme
from .utils.helpers import (
save_to_session,
get_from_session,
clear_session,
save_to_file,
load_from_file,
format_datetime,
parse_datetime,
validate_tweet_content,
validate_hashtags,
validate_emojis,
calculate_engagement_score,
generate_tweet_metrics,
copy_to_clipboard,
show_success_message,
show_error_message,
show_info_message,
show_warning_message,
create_download_button,
create_upload_button
)
__version__ = "1.0.0"
__author__ = "AI Writer Team"
__all__ = [
"TwitterDashboard",
"FeatureCard",
"TweetCard",
"TweetForm",
"SettingsForm",
"Sidebar",
"Header",
"Tabs",
"Breadcrumbs",
"Theme",
"save_to_session",
"get_from_session",
"clear_session",
"save_to_file",
"load_from_file",
"format_datetime",
"parse_datetime",
"validate_tweet_content",
"validate_hashtags",
"validate_emojis",
"calculate_engagement_score",
"generate_tweet_metrics",
"copy_to_clipboard",
"show_success_message",
"show_error_message",
"show_info_message",
"show_warning_message",
"create_download_button",
"create_upload_button"
]

View File

@@ -0,0 +1,174 @@
"""
Card components for Twitter UI.
Provides consistent card layouts for features and tweets.
"""
import streamlit as st
from typing import Dict, Any, Optional, List
from ..styles.theme import Theme
class BaseCard:
"""Base class for all card components."""
def __init__(
self,
title: str,
description: str,
icon: Optional[str] = None,
status: Optional[str] = None,
actions: Optional[List[Dict[str, Any]]] = None
):
self.title = title
self.description = description
self.icon = icon
self.status = status
self.actions = actions or []
def render(self) -> None:
"""Render the card with consistent styling."""
with st.container():
st.markdown(f"""
<div class="card">
<div style="display: flex; align-items: center; margin-bottom: {Theme.SPACING["sm"]};">
{f'<span style="font-size: 1.5em; margin-right: {Theme.SPACING["sm"]};">{self.icon}</span>' if self.icon else ''}
<h3 style="margin: 0;">{self.title}</h3>
</div>
<p style="color: {Theme.COLORS["text_secondary"]}; margin: {Theme.SPACING["sm"]} 0;">
{self.description}
</p>
{f'<span class="status-badge status-{self.status}">{self.status.title()}</span>' if self.status else ''}
</div>
""", unsafe_allow_html=True)
if self.actions:
cols = st.columns(len(self.actions))
for i, action in enumerate(self.actions):
with cols[i]:
if st.button(
action["label"],
key=f"action_{i}",
help=action.get("help"),
use_container_width=True
):
action["callback"]()
class FeatureCard(BaseCard):
"""Card component for displaying features."""
def __init__(
self,
title: str,
description: str,
icon: str,
status: str = "active",
features: Optional[List[Dict[str, Any]]] = None,
on_click: Optional[callable] = None
):
super().__init__(title, description, icon, status)
self.features = features or []
self.on_click = on_click
def render(self) -> None:
"""Render the feature card with enhanced styling."""
with st.container():
st.markdown(f"""
<div class="card feature-card">
<div style="display: flex; align-items: center; margin-bottom: {Theme.SPACING["sm"]};">
<span style="font-size: 1.5em; margin-right: {Theme.SPACING["sm"]};">{self.icon}</span>
<h3 style="margin: 0;">{self.title}</h3>
</div>
<p style="color: {Theme.COLORS["text_secondary"]}; margin: {Theme.SPACING["sm"]} 0;">
{self.description}
</p>
<span class="status-badge status-{self.status}">{self.status.title()}</span>
</div>
""", unsafe_allow_html=True)
if self.features:
for feature in self.features:
st.markdown(f"""
<div style="margin-left: {Theme.SPACING["lg"]}; margin-top: {Theme.SPACING["sm"]};">
<p style="margin: 0;">
<strong>{feature["name"]}</strong>: {feature["description"]}
</p>
</div>
""", unsafe_allow_html=True)
if self.on_click:
if st.button(
f"Launch {self.title}",
key=f"launch_{self.title.lower().replace(' ', '_')}",
use_container_width=True
):
self.on_click()
class TweetCard(BaseCard):
"""Card component for displaying tweets."""
def __init__(
self,
content: str,
engagement_score: float,
hashtags: List[str],
emojis: List[str],
metrics: Optional[Dict[str, Any]] = None,
on_copy: Optional[callable] = None,
on_save: Optional[callable] = None
):
super().__init__(
title="Tweet",
description=content,
icon="🐦",
actions=[
{
"label": "Copy",
"callback": on_copy or (lambda: None),
"help": "Copy tweet to clipboard"
},
{
"label": "Save",
"callback": on_save or (lambda: None),
"help": "Save tweet for later"
}
]
)
self.engagement_score = engagement_score
self.hashtags = hashtags
self.emojis = emojis
self.metrics = metrics or {}
def render(self) -> None:
"""Render the tweet card with metrics and actions."""
with st.container():
st.markdown(f"""
<div class="card tweet-card">
<div style="display: flex; align-items: center; margin-bottom: {Theme.SPACING["sm"]};">
<span style="font-size: 1.5em; margin-right: {Theme.SPACING["sm"]};">{self.icon}</span>
<h3 style="margin: 0;">Tweet</h3>
</div>
<p style="color: {Theme.COLORS["text"]}; margin: {Theme.SPACING["sm"]} 0;">
{self.description}
</p>
<div style="display: flex; gap: {Theme.SPACING["sm"]}; margin: {Theme.SPACING["sm"]} 0;">
{''.join(f'<span style="color: {Theme.COLORS["primary"]};">{tag}</span>' for tag in self.hashtags)}
</div>
<div style="display: flex; gap: {Theme.SPACING["sm"]}; margin: {Theme.SPACING["sm"]} 0;">
{''.join(f'<span>{emoji}</span>' for emoji in self.emojis)}
</div>
<div style="margin-top: {Theme.SPACING["md"]};">
<div style="display: flex; justify-content: space-between; align-items: center;">
<span>Engagement Score: {self.engagement_score}%</span>
<div style="display: flex; gap: {Theme.SPACING["sm"]};">
<button class="stButton" onclick="copyTweet()">Copy</button>
<button class="stButton" onclick="saveTweet()">Save</button>
</div>
</div>
</div>
</div>
""", unsafe_allow_html=True)
if self.metrics:
cols = st.columns(len(self.metrics))
for i, (metric, value) in enumerate(self.metrics.items()):
with cols[i]:
st.metric(metric, f"{value}%")

View File

@@ -0,0 +1,255 @@
"""
Form components for Twitter UI.
Provides consistent form layouts and input validation.
"""
import streamlit as st
from typing import Dict, Any, Optional, List, Callable
from ..styles.theme import Theme
class BaseForm:
"""Base class for all form components."""
def __init__(
self,
title: str,
description: Optional[str] = None,
on_submit: Optional[Callable] = None
):
self.title = title
self.description = description
self.on_submit = on_submit
self.fields: Dict[str, Any] = {}
def add_field(
self,
key: str,
label: str,
field_type: str = "text",
required: bool = False,
help_text: Optional[str] = None,
options: Optional[List[str]] = None,
default: Any = None,
validation: Optional[Callable] = None
) -> None:
"""Add a field to the form."""
self.fields[key] = {
"label": label,
"type": field_type,
"required": required,
"help_text": help_text,
"options": options,
"default": default,
"validation": validation
}
def validate(self) -> bool:
"""Validate all form fields."""
for key, field in self.fields.items():
if field["required"] and not st.session_state.get(key):
st.error(f"{field['label']} is required")
return False
if field["validation"] and not field["validation"](st.session_state.get(key)):
return False
return True
def render(self) -> None:
"""Render the form with consistent styling."""
with st.container():
st.markdown(f"""
<div class="form-container">
<h3 style="margin-bottom: {Theme.SPACING['sm']};">{self.title}</h3>
{f'<p style="color: {Theme.COLORS["text_secondary"]}; margin-bottom: {Theme.SPACING["md"]};">{self.description}</p>' if self.description else ''}
</div>
""", unsafe_allow_html=True)
for key, field in self.fields.items():
if field["type"] == "text":
st.text_input(
field["label"],
key=key,
help=field["help_text"],
value=field["default"]
)
elif field["type"] == "textarea":
st.text_area(
field["label"],
key=key,
help=field["help_text"],
value=field["default"]
)
elif field["type"] == "select":
st.selectbox(
field["label"],
options=field["options"],
key=key,
help=field["help_text"],
index=field["options"].index(field["default"]) if field["default"] in field["options"] else 0
)
elif field["type"] == "multiselect":
st.multiselect(
field["label"],
options=field["options"],
key=key,
help=field["help_text"],
default=field["default"]
)
elif field["type"] == "number":
st.number_input(
field["label"],
key=key,
help=field["help_text"],
value=field["default"]
)
elif field["type"] == "slider":
st.slider(
field["label"],
key=key,
help=field["help_text"],
value=field["default"]
)
elif field["type"] == "checkbox":
st.checkbox(
field["label"],
key=key,
help=field["help_text"],
value=field["default"]
)
if st.button("Submit", use_container_width=True):
if self.validate() and self.on_submit:
self.on_submit()
class TweetForm(BaseForm):
"""Form component for tweet generation."""
def __init__(
self,
on_submit: Optional[Callable] = None,
default_tone: str = "professional",
default_length: str = "medium"
):
super().__init__(
title="Generate Tweet",
description="Create engaging tweets with AI assistance",
on_submit=on_submit
)
# Add tweet content field
self.add_field(
"tweet_content",
"Tweet Content",
field_type="textarea",
required=True,
help_text="Enter your tweet content or topic"
)
# Add tone selection
self.add_field(
"tone",
"Tweet Tone",
field_type="select",
options=["professional", "casual", "humorous", "informative", "inspirational"],
default=default_tone,
help_text="Select the tone for your tweet"
)
# Add length selection
self.add_field(
"length",
"Tweet Length",
field_type="select",
options=["short", "medium", "long"],
default=default_length,
help_text="Select the desired length of your tweet"
)
# Add hashtag options
self.add_field(
"hashtags",
"Hashtags",
field_type="multiselect",
options=["#AI", "#Tech", "#Innovation", "#Business", "#Marketing"],
help_text="Select relevant hashtags"
)
# Add emoji options
self.add_field(
"emojis",
"Emojis",
field_type="multiselect",
options=["🚀", "💡", "🎯", "🔥", ""],
help_text="Select emojis to include"
)
# Add engagement settings
self.add_field(
"engagement_boost",
"Engagement Boost",
field_type="slider",
default=50,
help_text="Adjust the engagement optimization level"
)
class SettingsForm(BaseForm):
"""Form component for user settings."""
def __init__(
self,
on_submit: Optional[Callable] = None,
default_settings: Optional[Dict[str, Any]] = None
):
super().__init__(
title="User Settings",
description="Customize your Twitter experience",
on_submit=on_submit
)
settings = default_settings or {}
# Add API settings
self.add_field(
"api_key",
"Twitter API Key",
field_type="text",
help_text="Enter your Twitter API key",
default=settings.get("api_key", "")
)
# Add theme settings
self.add_field(
"theme",
"Theme",
field_type="select",
options=["light", "dark", "system"],
default=settings.get("theme", "system"),
help_text="Select your preferred theme"
)
# Add notification settings
self.add_field(
"notifications",
"Enable Notifications",
field_type="checkbox",
default=settings.get("notifications", True),
help_text="Receive notifications for important updates"
)
# Add auto-save settings
self.add_field(
"auto_save",
"Auto-save Drafts",
field_type="checkbox",
default=settings.get("auto_save", True),
help_text="Automatically save tweet drafts"
)
# Add language settings
self.add_field(
"language",
"Language",
field_type="select",
options=["English", "Spanish", "French", "German", "Japanese"],
default=settings.get("language", "English"),
help_text="Select your preferred language"
)

View File

@@ -0,0 +1,222 @@
"""
Navigation components for Twitter UI.
Provides consistent navigation and layout structure.
"""
import streamlit as st
from typing import Dict, Any, Optional, List
from ..styles.theme import Theme
class Sidebar:
"""Sidebar navigation component."""
def __init__(
self,
title: str = "Twitter Tools",
logo: Optional[str] = None,
menu_items: Optional[List[Dict[str, Any]]] = None
):
self.title = title
self.logo = logo
self.menu_items = menu_items or []
def add_menu_item(
self,
label: str,
icon: str,
page: str,
badge: Optional[str] = None
) -> None:
"""Add a menu item to the sidebar."""
self.menu_items.append({
"label": label,
"icon": icon,
"page": page,
"badge": badge
})
def render(self) -> None:
"""Render the sidebar with consistent styling."""
with st.sidebar:
# Logo and title
if self.logo:
st.image(self.logo, width=50)
st.markdown(f"""
<h2 style="margin: {Theme.SPACING["sm"]} 0;">{self.title}</h2>
""", unsafe_allow_html=True)
# Menu items
for item in self.menu_items:
st.markdown(f"""
<div class="menu-item">
<span style="font-size: 1.2em; margin-right: {Theme.SPACING["sm"]};">{item["icon"]}</span>
<span>{item["label"]}</span>
{f'<span class="badge">{item["badge"]}</span>' if item.get("badge") else ""}
</div>
""", unsafe_allow_html=True)
if st.button(
item["label"],
key=f"nav_{item['page']}",
use_container_width=True
):
st.session_state["current_page"] = item["page"]
class Header:
"""Header navigation component."""
def __init__(
self,
title: str,
subtitle: Optional[str] = None,
actions: Optional[List[Dict[str, Any]]] = None
):
self.title = title
self.subtitle = subtitle
self.actions = actions or []
def add_action(
self,
label: str,
icon: str,
callback: callable,
help_text: Optional[str] = None
) -> None:
"""Add an action button to the header."""
self.actions.append({
"label": label,
"icon": icon,
"callback": callback,
"help_text": help_text
})
def render(self) -> None:
"""Render the header with consistent styling."""
# Build action buttons HTML
action_buttons = []
for action in self.actions:
help_text = action.get("help_text", "")
action_buttons.append(f"""
<button class="header-action" title="{help_text}">
<span style="font-size: 1.2em; margin-right: {Theme.SPACING["xs"]};">{action["icon"]}</span>
{action["label"]}
</button>
""")
st.markdown(f"""
<div class="header">
<div>
<h1 style="margin: 0;">{self.title}</h1>
{f'<p style="color: {Theme.COLORS["text_secondary"]}; margin: {Theme.SPACING["xs"]} 0;">{self.subtitle}</p>' if self.subtitle else ""}
</div>
<div style="display: flex; gap: {Theme.SPACING["sm"]};">
{''.join(action_buttons)}
</div>
</div>
""", unsafe_allow_html=True)
# Add action button callbacks
for i, action in enumerate(self.actions):
if st.button(
action["label"],
key=f"header_action_{i}",
help=action.get("help_text")
):
action["callback"]()
class Tabs:
"""Tab navigation component."""
def __init__(
self,
tabs: Optional[List[Dict[str, Any]]] = None,
default_tab: Optional[str] = None
):
self.tabs = tabs or []
self.default_tab = default_tab
def add_tab(
self,
label: str,
icon: Optional[str] = None,
content: Optional[callable] = None
) -> None:
"""Add a tab to the navigation."""
self.tabs.append({
"label": label,
"icon": icon,
"content": content
})
def render(self) -> None:
"""Render the tabs with consistent styling."""
if not self.tabs:
return
# Create tab labels with icons
tab_labels = [
f"{tab['icon']} {tab['label']}" if tab.get('icon') else tab['label']
for tab in self.tabs
]
# Get current tab from session state or use default
current_tab = st.session_state.get("current_tab", self.default_tab or self.tabs[0]["label"])
# Render tabs
selected_tab = st.tabs(tab_labels)[tab_labels.index(current_tab)]
# Update session state
st.session_state["current_tab"] = current_tab
# Render tab content
with selected_tab:
for tab in self.tabs:
if tab["label"] == current_tab and tab.get("content"):
tab["content"]()
class Breadcrumbs:
"""Breadcrumb navigation component."""
def __init__(
self,
items: Optional[List[Dict[str, Any]]] = None
):
self.items = items or []
def add_item(
self,
label: str,
page: Optional[str] = None,
icon: Optional[str] = None
) -> None:
"""Add a breadcrumb item."""
self.items.append({
"label": label,
"page": page,
"icon": icon
})
def render(self) -> None:
"""Render the breadcrumbs with consistent styling."""
if not self.items:
return
breadcrumb_items = []
for i, item in enumerate(self.items):
icon_html = f'<span style="font-size: 1.2em; margin-right: {Theme.SPACING["xs"]};">{item["icon"]}</span>' if item.get("icon") else ""
link_html = f'<a href="#" onclick="setPage(\'{item["page"]}\')">{item["label"]}</a>' if item.get("page") else f'<span>{item["label"]}</span>'
separator = f'<span style="margin: 0 {Theme.SPACING["xs"]};">/</span>' if i < len(self.items) - 1 else ""
breadcrumb_items.append(f"""
<span class="breadcrumb-item">
{icon_html}
{link_html}
</span>
{separator}
""")
st.markdown(f"""
<div class="breadcrumbs">
{''.join(breadcrumb_items)}
</div>
""", unsafe_allow_html=True)

View File

@@ -0,0 +1,270 @@
"""
Main dashboard for Twitter UI.
Combines all UI components into a cohesive interface.
"""
import streamlit as st
from typing import Dict, Any, Optional
from .components.cards import FeatureCard, TweetCard
from .components.forms import TweetForm, SettingsForm
from .components.navigation import Sidebar, Header, Tabs, Breadcrumbs
from .styles.theme import Theme
class TwitterDashboard:
"""Main dashboard class for Twitter UI."""
def __init__(self):
self.setup_page()
self.setup_theme()
self.setup_navigation()
self.setup_state()
def setup_page(self) -> None:
"""Configure the Streamlit page settings."""
st.set_page_config(
page_title="Twitter Tools",
page_icon="🐦",
layout="wide",
initial_sidebar_state="expanded"
)
def setup_theme(self) -> None:
"""Apply the theme to the dashboard."""
Theme().apply()
def setup_navigation(self) -> None:
"""Setup navigation components."""
# Sidebar
self.sidebar = Sidebar(
title="Twitter Tools",
logo="assets/logo.png"
)
# Add menu items
self.sidebar.add_menu_item("Dashboard", "📊", "dashboard")
self.sidebar.add_menu_item("Tweet Generator", "✍️", "tweet_generator")
self.sidebar.add_menu_item("Analytics", "📈", "analytics")
self.sidebar.add_menu_item("Settings", "⚙️", "settings")
# Header
self.header = Header(
title="Twitter Dashboard",
subtitle="Create and manage your Twitter content"
)
# Add header actions
self.header.add_action(
"New Tweet",
"✏️",
self.create_new_tweet,
"Create a new tweet"
)
self.header.add_action(
"Refresh",
"🔄",
self.refresh_dashboard,
"Refresh dashboard data"
)
# Tabs
self.tabs = Tabs()
# Add tabs
self.tabs.add_tab("Overview", "📊", self.render_overview)
self.tabs.add_tab("Recent Tweets", "🐦", self.render_recent_tweets)
self.tabs.add_tab("Analytics", "📈", self.render_analytics)
# Breadcrumbs
self.breadcrumbs = Breadcrumbs()
def setup_state(self) -> None:
"""Initialize session state variables."""
if "current_page" not in st.session_state:
st.session_state["current_page"] = "dashboard"
if "current_tab" not in st.session_state:
st.session_state["current_tab"] = "Overview"
if "tweets" not in st.session_state:
st.session_state["tweets"] = []
def create_new_tweet(self) -> None:
"""Handle new tweet creation."""
st.session_state["current_page"] = "tweet_generator"
def refresh_dashboard(self) -> None:
"""Refresh dashboard data."""
st.experimental_rerun()
def render_overview(self) -> None:
"""Render the overview tab content."""
# Feature cards
col1, col2, col3 = st.columns(3)
with col1:
FeatureCard(
title="Tweet Generator",
description="Create engaging tweets with AI assistance",
icon="✍️",
features=[
{
"name": "AI-Powered",
"description": "Generate tweets using advanced AI"
},
{
"name": "Customizable",
"description": "Adjust tone, length, and style"
}
],
on_click=self.create_new_tweet
).render()
with col2:
FeatureCard(
title="Analytics",
description="Track your tweet performance",
icon="📈",
features=[
{
"name": "Engagement",
"description": "Monitor likes, retweets, and replies"
},
{
"name": "Growth",
"description": "Track follower growth over time"
}
]
).render()
with col3:
FeatureCard(
title="Settings",
description="Customize your experience",
icon="⚙️",
features=[
{
"name": "Preferences",
"description": "Set your default options"
},
{
"name": "API",
"description": "Configure Twitter API settings"
}
]
).render()
def render_recent_tweets(self) -> None:
"""Render the recent tweets tab content."""
# Tweet form
tweet_form = TweetForm(
on_submit=self.handle_tweet_submit
)
tweet_form.render()
# Recent tweets
st.markdown("### Recent Tweets")
for tweet in st.session_state["tweets"]:
TweetCard(
content=tweet["content"],
engagement_score=tweet["engagement_score"],
hashtags=tweet["hashtags"],
emojis=tweet["emojis"],
metrics=tweet["metrics"],
on_copy=lambda: self.copy_tweet(tweet),
on_save=lambda: self.save_tweet(tweet)
).render()
def render_analytics(self) -> None:
"""Render the analytics tab content."""
# Analytics content
st.markdown("### Tweet Analytics")
# Placeholder for analytics charts
st.info("Analytics features coming soon!")
def handle_tweet_submit(self) -> None:
"""Handle tweet form submission."""
# Get form data
content = st.session_state["tweet_content"]
tone = st.session_state["tone"]
length = st.session_state["length"]
hashtags = st.session_state["hashtags"]
emojis = st.session_state["emojis"]
engagement_boost = st.session_state["engagement_boost"]
# Create tweet object
tweet = {
"content": content,
"tone": tone,
"length": length,
"hashtags": hashtags,
"emojis": emojis,
"engagement_score": engagement_boost,
"metrics": {
"Engagement": engagement_boost,
"Reach": engagement_boost * 0.8,
"Growth": engagement_boost * 0.6
}
}
# Add to tweets list
st.session_state["tweets"].append(tweet)
# Show success message
st.success("Tweet created successfully!")
def copy_tweet(self, tweet: Dict[str, Any]) -> None:
"""Copy tweet to clipboard."""
st.write("Tweet copied to clipboard!")
def save_tweet(self, tweet: Dict[str, Any]) -> None:
"""Save tweet for later."""
st.write("Tweet saved!")
def render(self) -> None:
"""Render the complete dashboard."""
# Render navigation
self.sidebar.render()
self.header.render()
self.breadcrumbs.render()
# Render content based on current page
if st.session_state["current_page"] == "dashboard":
self.tabs.render()
elif st.session_state["current_page"] == "tweet_generator":
self.render_recent_tweets()
elif st.session_state["current_page"] == "analytics":
self.render_analytics()
elif st.session_state["current_page"] == "settings":
settings_form = SettingsForm(
on_submit=self.handle_settings_submit
)
settings_form.render()
def handle_settings_submit(self) -> None:
"""Handle settings form submission."""
# Get form data
api_key = st.session_state["api_key"]
theme = st.session_state["theme"]
notifications = st.session_state["notifications"]
auto_save = st.session_state["auto_save"]
language = st.session_state["language"]
# Save settings
st.session_state["settings"] = {
"api_key": api_key,
"theme": theme,
"notifications": notifications,
"auto_save": auto_save,
"language": language
}
# Show success message
st.success("Settings saved successfully!")
def main():
"""Main entry point for the dashboard."""
dashboard = TwitterDashboard()
dashboard.render()
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,173 @@
"""
Theme configuration for Twitter UI components.
Provides consistent styling across all Twitter-related features.
"""
import streamlit as st
from typing import Dict, Any
class Theme:
"""Theme configuration for Twitter UI components."""
# Color palette
COLORS = {
"primary": "#1DA1F2", # Twitter blue
"secondary": "#14171A", # Dark blue
"background": "#15202B", # Dark background
"text": "#FFFFFF", # White text
"text_secondary": "#8899A6", # Gray text
"success": "#17BF63", # Green
"warning": "#FFAD1F", # Yellow
"error": "#E0245E", # Red
"border": "rgba(255, 255, 255, 0.1)", # Subtle border
}
# Typography
TYPOGRAPHY = {
"font_family": "'Helvetica Neue', sans-serif",
"font_sizes": {
"h1": "2.5rem",
"h2": "2rem",
"h3": "1.5rem",
"body": "1rem",
"small": "0.875rem",
},
"font_weights": {
"regular": 400,
"medium": 500,
"bold": 700,
},
}
# Spacing
SPACING = {
"xs": "0.25rem",
"sm": "0.5rem",
"md": "1rem",
"lg": "1.5rem",
"xl": "2rem",
}
# Border radius
BORDER_RADIUS = {
"sm": "4px",
"md": "8px",
"lg": "12px",
"xl": "16px",
"full": "9999px",
}
# Shadows
SHADOWS = {
"sm": "0 1px 2px rgba(0, 0, 0, 0.05)",
"md": "0 4px 6px rgba(0, 0, 0, 0.1)",
"lg": "0 10px 15px rgba(0, 0, 0, 0.1)",
"xl": "0 20px 25px rgba(0, 0, 0, 0.15)",
}
# Transitions
TRANSITIONS = {
"fast": "0.15s ease",
"normal": "0.3s ease",
"slow": "0.5s ease",
}
@classmethod
def get_css(cls) -> str:
"""Get the complete CSS for the theme."""
return f"""
/* Base styles */
.stApp {{
background-color: {cls.COLORS['background']};
color: {cls.COLORS['text']};
font-family: {cls.TYPOGRAPHY['font_family']};
}}
/* Typography */
h1, h2, h3, h4, h5, h6 {{
color: {cls.COLORS['text']};
font-family: {cls.TYPOGRAPHY['font_family']};
font-weight: {cls.TYPOGRAPHY['font_weights']['bold']};
}}
/* Buttons */
.stButton > button {{
background: linear-gradient(45deg, {cls.COLORS['primary']}, #0C85D0);
color: {cls.COLORS['text']};
border: none;
padding: {cls.SPACING['md']} {cls.SPACING['lg']};
border-radius: {cls.BORDER_RADIUS['full']};
font-weight: {cls.TYPOGRAPHY['font_weights']['medium']};
transition: all {cls.TRANSITIONS['normal']};
box-shadow: {cls.SHADOWS['md']};
}}
.stButton > button:hover {{
transform: translateY(-2px);
box-shadow: {cls.SHADOWS['lg']};
}}
/* Cards */
.card {{
background: rgba(255, 255, 255, 0.05);
border: 1px solid {cls.COLORS['border']};
border-radius: {cls.BORDER_RADIUS['lg']};
padding: {cls.SPACING['lg']};
margin-bottom: {cls.SPACING['md']};
backdrop-filter: blur(10px);
transition: transform {cls.TRANSITIONS['normal']};
}}
.card:hover {{
transform: translateY(-4px);
}}
/* Forms */
.stTextInput > div > div > input {{
background-color: rgba(255, 255, 255, 0.05);
border: 1px solid {cls.COLORS['border']};
border-radius: {cls.BORDER_RADIUS['md']};
color: {cls.COLORS['text']};
padding: {cls.SPACING['md']};
}}
/* Tabs */
.stTabs [data-baseweb="tab-list"] {{
gap: {cls.SPACING['sm']};
background-color: rgba(0, 0, 0, 0.2);
padding: {cls.SPACING['md']};
border-radius: {cls.BORDER_RADIUS['lg']};
}}
.stTabs [data-baseweb="tab"] {{
background-color: transparent;
color: {cls.COLORS['text']};
border: 1px solid {cls.COLORS['border']};
border-radius: {cls.BORDER_RADIUS['md']};
padding: {cls.SPACING['sm']} {cls.SPACING['md']};
}}
/* Status badges */
.status-badge {{
display: inline-block;
padding: {cls.SPACING['xs']} {cls.SPACING['md']};
border-radius: {cls.BORDER_RADIUS['full']};
font-size: {cls.TYPOGRAPHY['font_sizes']['small']};
font-weight: {cls.TYPOGRAPHY['font_weights']['medium']};
}}
.status-active {{
background: linear-gradient(45deg, {cls.COLORS['success']}, #69F0AE);
color: {cls.COLORS['secondary']};
}}
.status-coming-soon {{
background: linear-gradient(45deg, {cls.COLORS['warning']}, #FFA000);
color: {cls.COLORS['secondary']};
}}
"""
@classmethod
def apply(cls) -> None:
"""Apply the theme to the Streamlit app."""
st.markdown(f"<style>{cls.get_css()}</style>", unsafe_allow_html=True)

View File

@@ -0,0 +1,194 @@
"""
Utility functions for Twitter UI.
Provides helper functions for common operations.
"""
import streamlit as st
from typing import Dict, Any, List, Optional
import json
import os
from datetime import datetime
def save_to_session(key: str, value: Any) -> None:
"""Save a value to the session state."""
st.session_state[key] = value
def get_from_session(key: str, default: Any = None) -> Any:
"""Get a value from the session state."""
return st.session_state.get(key, default)
def clear_session() -> None:
"""Clear all session state variables."""
for key in list(st.session_state.keys()):
del st.session_state[key]
def save_to_file(data: Dict[str, Any], filename: str) -> None:
"""Save data to a JSON file."""
try:
with open(filename, 'w') as f:
json.dump(data, f, indent=4)
except Exception as e:
st.error(f"Error saving data: {str(e)}")
def load_from_file(filename: str) -> Optional[Dict[str, Any]]:
"""Load data from a JSON file."""
try:
if os.path.exists(filename):
with open(filename, 'r') as f:
return json.load(f)
except Exception as e:
st.error(f"Error loading data: {str(e)}")
return None
def format_datetime(dt: datetime) -> str:
"""Format a datetime object for display."""
return dt.strftime("%Y-%m-%d %H:%M:%S")
def parse_datetime(dt_str: str) -> Optional[datetime]:
"""Parse a datetime string."""
try:
return datetime.strptime(dt_str, "%Y-%m-%d %H:%M:%S")
except ValueError:
return None
def validate_tweet_content(content: str) -> bool:
"""Validate tweet content."""
if not content:
st.error("Tweet content cannot be empty")
return False
if len(content) > 280:
st.error("Tweet content cannot exceed 280 characters")
return False
return True
def validate_hashtags(hashtags: List[str]) -> bool:
"""Validate hashtags."""
for tag in hashtags:
if not tag.startswith('#'):
st.error(f"Hashtag {tag} must start with #")
return False
if len(tag) > 30:
st.error(f"Hashtag {tag} cannot exceed 30 characters")
return False
return True
def validate_emojis(emojis: List[str]) -> bool:
"""Validate emojis."""
for emoji in emojis:
if len(emoji) != 1:
st.error(f"Invalid emoji: {emoji}")
return False
return True
def calculate_engagement_score(
content: str,
hashtags: List[str],
emojis: List[str],
tone: str
) -> float:
"""Calculate engagement score for a tweet."""
score = 0.0
# Content length score (optimal length is 100-150 characters)
content_length = len(content)
if 100 <= content_length <= 150:
score += 30
elif 50 <= content_length <= 200:
score += 20
else:
score += 10
# Hashtag score (optimal number is 2-3 hashtags)
hashtag_count = len(hashtags)
if 2 <= hashtag_count <= 3:
score += 20
elif 1 <= hashtag_count <= 4:
score += 15
else:
score += 5
# Emoji score (optimal number is 1-2 emojis)
emoji_count = len(emojis)
if 1 <= emoji_count <= 2:
score += 20
elif 0 <= emoji_count <= 3:
score += 15
else:
score += 5
# Tone score
tone_scores = {
"professional": 15,
"casual": 20,
"humorous": 25,
"informative": 15,
"inspirational": 20
}
score += tone_scores.get(tone, 10)
return min(score, 100)
def generate_tweet_metrics(engagement_score: float) -> Dict[str, float]:
"""Generate metrics for a tweet based on engagement score."""
return {
"Engagement": engagement_score,
"Reach": engagement_score * 0.8,
"Growth": engagement_score * 0.6
}
def copy_to_clipboard(text: str) -> None:
"""Copy text to clipboard."""
try:
st.write(f'<script>navigator.clipboard.writeText("{text}")</script>', unsafe_allow_html=True)
except Exception as e:
st.error(f"Error copying to clipboard: {str(e)}")
def show_success_message(message: str) -> None:
"""Show a success message."""
st.success(message)
def show_error_message(message: str) -> None:
"""Show an error message."""
st.error(message)
def show_info_message(message: str) -> None:
"""Show an info message."""
st.info(message)
def show_warning_message(message: str) -> None:
"""Show a warning message."""
st.warning(message)
def create_download_button(
data: Dict[str, Any],
filename: str,
button_text: str = "Download"
) -> None:
"""Create a download button for data."""
try:
json_str = json.dumps(data, indent=4)
st.download_button(
label=button_text,
data=json_str,
file_name=filename,
mime="application/json"
)
except Exception as e:
st.error(f"Error creating download button: {str(e)}")
def create_upload_button(
on_upload: callable,
button_text: str = "Upload",
file_types: List[str] = ["json"]
) -> None:
"""Create an upload button for data."""
try:
uploaded_file = st.file_uploader(
button_text,
type=file_types
)
if uploaded_file is not None:
data = json.load(uploaded_file)
on_upload(data)
except Exception as e:
st.error(f"Error handling upload: {str(e)}")

File diff suppressed because it is too large Load Diff

View File

@@ -18,6 +18,7 @@ from .modules.tags_generator import write_yt_tags
from .modules.shorts_script_generator import write_yt_shorts
from .modules.community_post_generator import write_yt_community_post
from .modules.shorts_video_generator import write_yt_shorts_video
from .modules.channel_trailer_generator import write_yt_channel_trailer
def youtube_main_menu():
@@ -75,6 +76,15 @@ def youtube_main_menu():
"function": write_yt_shorts_video,
"status": "active"
},
{
"name": "Channel Trailer Generator",
"icon": "🎥",
"description": "Create compelling channel trailers that convert visitors into subscribers.",
"color": "#FF0000", # YouTube red
"category": "Content Creation",
"function": write_yt_channel_trailer,
"status": "active"
},
# Optimization Tools
{
@@ -135,15 +145,6 @@ def youtube_main_menu():
"function": None,
"status": "future"
},
{
"name": "Channel Trailer Generator",
"icon": "🎥",
"description": "Create compelling channel trailers that convert visitors into subscribers.",
"color": "#990000", # Even darker red for future
"category": "Future Tools",
"function": None,
"status": "future"
},
{
"name": "Video Series Planner",
"icon": "📅",

View File

@@ -8,7 +8,7 @@ from loguru import logger
from lib.ai_writers.ai_news_article_writer import ai_news_generation
from lib.ai_writers.ai_financial_writer import write_basic_ta_report
from lib.ai_writers.ai_finance_report_generator.ai_financial_dashboard import get_dashboard
from lib.ai_writers.ai_facebook_writer.facebook_ai_writer import facebook_main_menu
from lib.ai_writers.linkedin_writer.linkedin_ai_writer import linkedin_main_menu
from lib.ai_writers.twitter_writers.twitter_dashboard import run_dashboard
@@ -198,7 +198,9 @@ def ai_finance_ta_writer():
if ticker_symbol:
with st.spinner("Generating TA Report..."):
try:
ta_report = write_basic_ta_report(ticker_symbol)
# Get dashboard instance and generate technical analysis
dashboard = get_dashboard()
ta_report = dashboard.generate_technical_analysis(ticker_symbol)
st.success(f"Successfully generated TA report for: {ticker_symbol}")
st.markdown(ta_report)
except Exception as err:

View File

@@ -3,7 +3,7 @@
import streamlit as st
from loguru import logger
from ...website_analyzer import analyze_website
from ...website_analyzer.seo_analyzer import analyze_seo
from ...website_analyzer.analyzer import WebsiteAnalyzer
import asyncio
import sys
from typing import Dict, Any
@@ -127,37 +127,19 @@ def render_website_setup(api_key_manager: APIKeyManager) -> Dict[str, Any]:
# 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
]
}
# Replace the old SEO analysis code with the new analyzer
analyzer = WebsiteAnalyzer()
seo_results = analyzer.analyze_website(url)
if seo_results.get('success', False):
results['data']['seo_analysis'] = seo_results['data']['analysis']['seo_info']
else:
results['data']['seo_analysis'] = {
'error': seo_results.get('error', 'Unknown error in SEO analysis'),
'overall_score': 0,
'meta_tags': {},
'content': {},
'recommendations': []
}
logger.debug(f"[render_website_setup] Analysis results received: {results.get('success', False)}")

83
lib/utils/save_to_file.py Normal file
View File

@@ -0,0 +1,83 @@
"""
Utility module for saving generated content to files.
Handles saving various types of content to the workspace directory.
"""
import os
import json
from pathlib import Path
from datetime import datetime
from typing import Union, Dict, List, Any
# Define the workspace directory
WORKSPACE_DIR = Path(__file__).parent.parent.parent / "workspace" / "alwrity_content"
def ensure_directory_exists(directory: Union[str, Path]) -> None:
"""Ensure the specified directory exists."""
os.makedirs(directory, exist_ok=True)
def save_to_file(
content: Union[str, Dict, List, Any],
filename: str,
content_type: str = "text",
subdirectory: str = None
) -> str:
"""
Save content to a file in the workspace directory.
Args:
content: The content to save (string, dict, list, or any serializable object)
filename: Name of the file to save
content_type: Type of content ('text', 'json', 'audio', 'image')
subdirectory: Optional subdirectory within the workspace
Returns:
str: Path to the saved file
"""
# Create timestamp for unique filenames
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
base_filename = f"{timestamp}_{filename}"
# Determine the target directory
target_dir = WORKSPACE_DIR
if subdirectory:
target_dir = target_dir / subdirectory
# Ensure directory exists
ensure_directory_exists(target_dir)
# Determine file extension and format content
if content_type == "json":
file_path = target_dir / f"{base_filename}.json"
with open(file_path, "w", encoding="utf-8") as f:
json.dump(content, f, indent=2, ensure_ascii=False)
elif content_type == "audio":
file_path = target_dir / f"{base_filename}.mp3"
with open(file_path, "wb") as f:
f.write(content)
elif content_type == "image":
file_path = target_dir / f"{base_filename}.png"
with open(file_path, "wb") as f:
f.write(content)
else: # text
file_path = target_dir / f"{base_filename}.txt"
with open(file_path, "w", encoding="utf-8") as f:
f.write(str(content))
return str(file_path)
def save_audio(audio_bytes: bytes, filename: str, subdirectory: str = "audio") -> str:
"""Save audio content to a file."""
return save_to_file(audio_bytes, filename, "audio", subdirectory)
def save_image(image_bytes: bytes, filename: str, subdirectory: str = "images") -> str:
"""Save image content to a file."""
return save_to_file(image_bytes, filename, "image", subdirectory)
def save_json(data: Union[Dict, List], filename: str, subdirectory: str = "json") -> str:
"""Save JSON content to a file."""
return save_to_file(data, filename, "json", subdirectory)
def save_text(text: str, filename: str, subdirectory: str = "text") -> str:
"""Save text content to a file."""
return save_to_file(text, filename, "text", subdirectory)

View File

@@ -9,7 +9,347 @@ from lib.ai_seo_tools.google_pagespeed_insights import google_pagespeed_insights
from lib.ai_seo_tools.on_page_seo_analyzer import analyze_onpage_seo
from lib.ai_seo_tools.weburl_seo_checker import url_seo_checker
from lib.ai_marketing_tools.ai_backlinker.backlinking_ui_streamlit import backlinking_ui
from lib.ai_seo_tools.content_gap_analysis.ui import ContentGapAnalysisUI
from lib.ai_seo_tools.content_calendar.ui.dashboard import ContentCalendarDashboard
def render_content_gap_analysis():
"""Render the content gap analysis workflow interface."""
from lib.ai_seo_tools.content_gap_analysis.ui import ContentGapAnalysisUI
# Initialize and run the Content Gap Analysis UI
ui = ContentGapAnalysisUI()
ui.run()
def render_content_calendar():
"""Render the content calendar dashboard."""
import logging
import sys
from datetime import datetime
# Configure logging
logging.basicConfig(
level=logging.DEBUG,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.StreamHandler(sys.stdout),
logging.FileHandler('content_calendar.log', mode='a')
]
)
logger = logging.getLogger('content_calendar')
try:
logger.info("Initializing Content Calendar Dashboard")
dashboard = ContentCalendarDashboard()
logger.info("Rendering Content Calendar Dashboard")
dashboard.render()
logger.info("Content Calendar Dashboard rendered successfully")
except Exception as e:
logger.error(f"Error rendering content calendar: {str(e)}", exc_info=True)
st.error(f"An error occurred while loading the content calendar: {str(e)}")
def render_seo_tools_dashboard():
"""Render a modern dashboard for SEO tools with improved UI and navigation."""
selected_section = st.session_state.get('seo_dashboard_section', 'combinations')
# Define card gradients at the top so it's available in all sections
card_gradients = {
"Content Optimization Suite": "linear-gradient(135deg, #43e97b 0%, #38f9d7 100%)",
"Technical SEO Audit": "linear-gradient(135deg, #667eea 0%, #764ba2 100%)",
"Image Optimization Suite": "linear-gradient(135deg, #f7971e 0%, #ffd200 100%)",
"Social Media Optimization": "linear-gradient(135deg, #f953c6 0%, #b91d73 100%)",
"Content Gap Analysis": "linear-gradient(135deg, #667eea 0%, #764ba2 100%)",
"Content Calendar": "linear-gradient(135deg, #4CAF50 0%, #2196F3 100%)",
"Structured Data Generator": "linear-gradient(135deg, #43e97b 0%, #38f9d7 100%)",
"Blog Title Generator": "linear-gradient(135deg, #2193b0 0%, #6dd5ed 100%)",
"Meta Description Generator": "linear-gradient(135deg, #f7971e 0%, #ffd200 100%)",
"Image Alt Text Generator": "linear-gradient(135deg, #f953c6 0%, #b91d73 100%)",
"OpenGraph Tags Generator": "linear-gradient(135deg, #f857a6 0%, #ff5858 100%)",
"Image Optimizer": "linear-gradient(135deg, #43cea2 0%, #185a9d 100%)",
"PageSpeed Insights": "linear-gradient(135deg, #ff9966 0%, #ff5e62 100%)",
"On-Page SEO Analyzer": "linear-gradient(135deg, #56ab2f 0%, #a8e063 100%)",
"URL SEO Checker": "linear-gradient(135deg, #3a7bd5 0%, #00d2ff 100%)",
"AI Backlinking Tool": "linear-gradient(135deg, #e96443 0%, #904e95 100%)"
}
# Navigation bar only (no dashboard title/description)
nav_cols = st.columns([1,1,1,1])
nav_labels = ["Tool Combos", "Advanced", "Individual", "About"]
nav_keys = ["combinations", "advanced", "individual", "about"]
for i, label in enumerate(nav_labels):
if nav_cols[i].button(label, key=f"nav_{label}"):
st.session_state['seo_dashboard_section'] = nav_keys[i]
selected_section = nav_keys[i]
st.markdown("<hr style='margin:1.5rem 0;'>", unsafe_allow_html=True)
# Define tool combinations for cross-tool analysis
tool_combinations = {
"Content Optimization Suite": {
"icon": "📊",
"description": "Comprehensive content optimization combining title generation, meta descriptions, and structured data.",
"tools": ["Blog Title Generator", "Meta Description Generator", "Structured Data Generator"],
"path": "content_optimization",
"color": "#4CAF50"
},
"Technical SEO Audit": {
"icon": "🔧",
"description": "Complete technical SEO analysis including page speed, on-page SEO, and URL structure.",
"tools": ["PageSpeed Insights", "On-Page SEO Analyzer", "URL SEO Checker"],
"path": "technical_audit",
"color": "#2196F3"
},
"Image Optimization Suite": {
"icon": "🖼️",
"description": "End-to-end image optimization with alt text generation and performance optimization.",
"tools": ["Image Alt Text Generator", "Image Optimizer"],
"path": "image_optimization",
"color": "#FF9800"
},
"Social Media Optimization": {
"icon": "📱",
"description": "Enhance social media presence with OpenGraph tags and backlink analysis.",
"tools": ["OpenGraph Tags Generator", "AI Backlinking Tool"],
"path": "social_optimization",
"color": "#9C27B0"
}
}
# Define individual SEO tools
seo_tools = {
"Structured Data Generator": {
"icon": "📋",
"description": "Generate structured data (Rich Snippets) to enhance your search results with additional information.",
"color": "#4CAF50",
"path": "structured_data",
"status": "active"
},
"Blog Title Generator": {
"icon": "✏️",
"description": "Create SEO-optimized blog titles that attract clicks and improve search rankings.",
"color": "#2196F3",
"path": "blog_title",
"status": "active"
},
"Meta Description Generator": {
"icon": "📝",
"description": "Generate compelling meta descriptions that improve click-through rates from search results.",
"color": "#FF9800",
"path": "meta_description",
"status": "active"
},
"Image Alt Text Generator": {
"icon": "🖼️",
"description": "Create descriptive alt text for images to improve accessibility and image SEO.",
"color": "#9C27B0",
"path": "alt_text",
"status": "active"
},
"OpenGraph Tags Generator": {
"icon": "📱",
"description": "Generate OpenGraph tags for better social media sharing and visibility.",
"color": "#F44336",
"path": "opengraph",
"status": "active"
},
"Image Optimizer": {
"icon": "📉",
"description": "Optimize and resize images for better website performance and SEO.",
"color": "#607D8B",
"path": "image_optimizer",
"status": "active"
},
"PageSpeed Insights": {
"icon": "",
"description": "Analyze your website's performance using Google PageSpeed Insights.",
"color": "#795548",
"path": "pagespeed",
"status": "active"
},
"On-Page SEO Analyzer": {
"icon": "🔍",
"description": "Analyze and optimize your webpage's SEO elements and content.",
"color": "#009688",
"path": "onpage_seo",
"status": "active"
},
"URL SEO Checker": {
"icon": "🌐",
"description": "Check the SEO health of specific URLs and get improvement suggestions.",
"color": "#3F51B5",
"path": "url_checker",
"status": "active"
},
"AI Backlinking Tool": {
"icon": "🔗",
"description": "Discover and analyze backlink opportunities using AI-powered insights.",
"color": "#E91E63",
"path": "backlinking",
"status": "active"
}
}
# --- Tool Combinations Section ---
if selected_section == 'combinations':
combo_cols = st.columns(2)
for idx, (combo_name, details) in enumerate(tool_combinations.items()):
gradient = card_gradients.get(combo_name, "linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%)")
with combo_cols[idx % 2]:
st.markdown(f"""
<div class="seo-card" style="background: {gradient}; position: relative; overflow: hidden;">
<div class="seo-card-overlay"></div>
<div class="seo-icon">{details['icon']}</div>
<div class="seo-title">{combo_name}</div>
<div class="seo-description">{details['description']}</div>
<div>
{''.join([f'<span class="tool-badge">{tool}</span>' for tool in details['tools']])}
</div>
</div>
""", unsafe_allow_html=True)
if st.button(f"Launch {combo_name}", key=f"combo_{combo_name}", use_container_width=True):
st.query_params["tool"] = details["path"]
st.rerun()
# --- Advanced Features Section ---
elif selected_section == 'advanced':
adv_cols = st.columns(2)
adv_features = [
{
"name": "Content Gap Analysis",
"icon": "🎯",
"description": "Identify content opportunities and optimize your content strategy with AI-powered insights.",
"badges": ["Website Analysis", "Competitor Research", "Keyword Opportunities", "AI Recommendations"],
"gradient": card_gradients["Content Gap Analysis"],
"button": "Start Content Gap Analysis",
"key": "content_gap_analysis",
"path": "content_gap_analysis"
},
{
"name": "Content Calendar",
"icon": "📅",
"description": "Plan, schedule, and manage your content strategy with our AI-powered content calendar.",
"badges": ["Content Planning", "Scheduling", "Performance Tracking", "AI Insights"],
"gradient": card_gradients["Content Calendar"],
"button": "Open Content Calendar",
"key": "content_calendar",
"path": "content_calendar"
}
]
for idx, feature in enumerate(adv_features):
with adv_cols[idx % 2]:
st.markdown(f"""
<div class="seo-card" style="background: {feature['gradient']}; position: relative; overflow: hidden;">
<div class="seo-card-overlay"></div>
<div class="seo-icon">{feature['icon']}</div>
<div class="seo-title">{feature['name']}</div>
<div class="seo-description">{feature['description']}</div>
<div>
{''.join([f'<span class="tool-badge">{badge}</span>' for badge in feature['badges']])}
</div>
</div>
""", unsafe_allow_html=True)
if st.button(feature['button'], key=feature['key'], use_container_width=True):
st.query_params["tool"] = feature["path"]
st.rerun()
# --- Individual Tools Section ---
elif selected_section == 'individual':
cols = st.columns(3)
for idx, (tool_name, details) in enumerate(seo_tools.items()):
gradient = card_gradients.get(tool_name, "linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%)")
with cols[idx % 3]:
st.markdown(f"""
<div class="seo-card" style="background: {gradient}; position: relative; overflow: hidden;">
<div class="seo-card-overlay"></div>
<div class="seo-icon">{details['icon']}</div>
<div class="seo-title">{tool_name}</div>
<div class="seo-description">{details['description']}</div>
</div>
""", unsafe_allow_html=True)
if st.button(f"Use {tool_name}", key=f"btn_{tool_name}", use_container_width=True):
st.query_params["tool"] = details["path"]
st.rerun()
# --- About Section ---
elif selected_section == 'about':
st.markdown("""
<div style='text-align: center; margin: 2rem 0;'>
<h2>About This Dashboard</h2>
<p style='color: #666;'>This dashboard brings together powerful AI-driven SEO tools and workflows to help you optimize your website and content strategy. Use the navigation above to explore combinations, advanced features, or individual tools.</p>
</div>
""", unsafe_allow_html=True)
st.markdown("""
<style>
.seo-card {
border-radius: 14px;
padding: 24px;
margin-bottom: 24px;
box-shadow: 0 4px 16px rgba(44, 62, 80, 0.10), 0 1.5px 4px rgba(44,62,80,0.06);
transition: transform 0.2s cubic-bezier(.4,2,.6,1), box-shadow 0.2s;
height: 100%;
border: 1.5px solid #e3e8ee;
position: relative;
overflow: hidden;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.seo-card-overlay {
position: absolute;
top: 0; left: 0; right: 0; bottom: 0;
background: rgba(255,255,255,0.72);
z-index: 1;
pointer-events: none;
border-radius: 14px;
box-shadow: 0 2px 8px rgba(44,62,80,0.08);
}
.seo-card:hover {
transform: translateY(-6px) scale(1.025);
box-shadow: 0 8px 32px rgba(44, 62, 80, 0.18), 0 2px 8px rgba(44,62,80,0.10);
border-color: #4CAF50;
}
.seo-icon {
font-size: 2.7rem;
margin-bottom: 18px;
z-index: 2;
position: relative;
text-shadow: 0 2px 8px rgba(44,62,80,0.10);
}
.seo-title {
font-size: 1.25rem;
font-weight: 800;
margin-bottom: 12px;
color: #222b45;
z-index: 2;
position: relative;
text-shadow: 0 2px 8px rgba(44,62,80,0.10);
letter-spacing: 0.01em;
}
.seo-description {
color: #34495e;
font-size: 1.08rem;
margin-bottom: 15px;
z-index: 2;
position: relative;
text-align: center;
line-height: 1.5;
text-shadow: 0 1px 4px rgba(44,62,80,0.08);
}
.tool-badge {
display: inline-block;
padding: 4px 12px;
border-radius: 12px;
font-size: 0.9rem;
margin-right: 8px;
margin-bottom: 8px;
background: rgba(255, 255, 255, 0.95);
color: #2196F3;
font-weight: 600;
border: 1px solid #e3e8ee;
}
</style>
""", unsafe_allow_html=True)
def ai_seo_tools():
"""
@@ -17,71 +357,83 @@ def ai_seo_tools():
such as generating structured data, optimizing images, checking page speed,
and analyzing on-page SEO.
"""
st.markdown(
"""
Welcome to your one-stop solution for AI-driven SEO optimization. Select a tool from the options below
to improve your websites SEO with cutting-edge AI technology.
"""
)
# List of SEO tools with unique emojis for each option
options = [
"📝 Generate Structured Data - Rich Snippet",
"✏️ Generate SEO Optimized Blog Titles",
"📝 Generate Meta Description for SEO",
"🖼️ Generate Image Alt Text",
"📄 Generate OpenGraph Tags",
"📉 Optimize/Resize Image",
"⚡ Run Google PageSpeed Insights",
"🔍 Analyze On-Page SEO",
"🌐 URL SEO Checker",
"🔗 AI Backlinking Tool"
]
# Check if a specific tool is selected
selected_tool = st.query_params.get("tool")
# User selection of SEO tools using radio buttons
choice = st.radio(
"**👇 Select an AI SEO Tool:**",
options,
index=0,
format_func=lambda x: x
)
if selected_tool:
# Map tool paths to their respective functions
tool_functions = {
# Individual tools
"structured_data": ai_structured_data,
"blog_title": ai_title_generator,
"meta_description": metadesc_generator_main,
"alt_text": alt_text_gen,
"opengraph": og_tag_generator,
"image_optimizer": main_img_optimizer,
"pagespeed": google_pagespeed_insights,
"onpage_seo": analyze_onpage_seo,
"url_checker": url_seo_checker,
"backlinking": backlinking_ui,
# Tool combinations
"content_optimization": lambda: run_tool_combination([
ai_title_generator,
metadesc_generator_main,
ai_structured_data
], "Content Optimization Suite"),
"technical_audit": lambda: run_tool_combination([
google_pagespeed_insights,
analyze_onpage_seo,
url_seo_checker
], "Technical SEO Audit"),
"image_optimization": lambda: run_tool_combination([
alt_text_gen,
main_img_optimizer
], "Image Optimization Suite"),
"social_optimization": lambda: run_tool_combination([
og_tag_generator,
backlinking_ui
], "Social Media Optimization"),
# Add Content Gap Analysis and Content Calendar
"content_gap_analysis": render_content_gap_analysis,
"content_calendar": render_content_calendar
}
if selected_tool in tool_functions:
# Clear any existing content
st.empty()
# Execute the selected tool's function
tool_functions[selected_tool]()
else:
st.error(f"Invalid tool selected: {selected_tool}")
render_seo_tools_dashboard()
else:
# Show the dashboard if no tool is selected
render_seo_tools_dashboard()
def run_tool_combination(tools, combination_name):
"""Run a combination of tools and provide cross-tool analysis."""
st.markdown(f"# {combination_name}")
st.markdown("Running comprehensive analysis...")
# Call the respective functions based on the user selection
if choice == "📝 Generate Structured Data - Rich Snippet":
# Generate Structured Data for Rich Snippets
ai_structured_data()
elif choice == "📝 Generate Meta Description for SEO":
# Generate SEO-optimized meta descriptions
metadesc_generator_main()
elif choice == "✏️ Generate SEO Optimized Blog Titles":
# Generate SEO-friendly blog titles
ai_title_generator()
elif choice == "🖼️ Generate Image Alt Text":
# Generate alternative text for images
alt_text_gen()
elif choice == "📄 Generate OpenGraph Tags":
# Generate OpenGraph tags for social media sharing
og_tag_generator()
elif choice == "📉 Optimize/Resize Image":
# Optimize images by resizing or compressing them
main_img_optimizer()
elif choice == "⚡ Run Google PageSpeed Insights":
# Run Google PageSpeed Insights for performance analysis
google_pagespeed_insights()
elif choice == "🔍 Analyze On-Page SEO":
# Analyze on-page SEO elements
analyze_onpage_seo()
elif choice == "🌐 URL SEO Checker":
# Check SEO health of a specific URL
url_seo_checker()
elif choice == "🔗 AI Backlinking Tool":
# Run AI Backlinking tool for link-building opportunities
backlinking_ui()
# Create tabs for each tool in the combination
tabs = st.tabs([f"Step {i+1}" for i in range(len(tools))])
# Run each tool in its own tab
for i, (tab, tool) in enumerate(zip(tabs, tools)):
with tab:
st.markdown(f"### Step {i+1}")
tool()
# Add cross-tool analysis section
st.markdown("## 📊 Cross-Tool Analysis")
st.markdown("Analyzing results across all tools...")
# Add recommendations based on combined results
st.markdown("## 💡 Recommendations")
st.markdown("Based on the combined analysis, here are the key recommendations:")
# Add a button to export the complete analysis
if st.button("📥 Export Complete Analysis", use_container_width=True):
st.info("Analysis export functionality coming soon!")

View File

@@ -1,3 +1,8 @@
"""
UI setup module for ALwrity application.
Provides consistent navigation and layout structure.
"""
import os
import streamlit as st
from lib.utils.file_processor import load_image
@@ -15,6 +20,99 @@ from lib.ai_writers.insta_ai_writer import insta_writer
from lib.ai_writers.youtube_writers.youtube_ai_writer import youtube_main_menu
from lib.ai_writers.ai_writer_dashboard import get_ai_writers, list_ai_writers
def render_social_tools_dashboard():
"""Render a modern dashboard for social media tools."""
st.markdown("""
<style>
.social-card {
background: white;
border-radius: 10px;
padding: 20px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
transition: transform 0.3s ease;
height: 100%;
}
.social-card:hover {
transform: translateY(-5px);
}
.social-icon {
font-size: 2.5rem;
margin-bottom: 15px;
}
.social-title {
font-size: 1.2rem;
font-weight: 600;
margin-bottom: 10px;
}
.social-description {
color: #666;
font-size: 0.9rem;
margin-bottom: 15px;
}
.social-button {
width: 100%;
padding: 8px 16px;
border-radius: 5px;
border: none;
font-weight: 500;
cursor: pointer;
transition: all 0.3s ease;
}
</style>
""", unsafe_allow_html=True)
# Define social tools with their details and paths
social_tools = {
"Facebook": {
"icon": "📘",
"description": "Create engaging Facebook posts and manage your content strategy",
"color": "#4267B2",
"path": "facebook"
},
"LinkedIn": {
"icon": "💼",
"description": "Generate professional LinkedIn content and optimize your profile",
"color": "#0077B5",
"path": "linkedin"
},
"Twitter": {
"icon": "🐦",
"description": "Craft viral tweets and manage your Twitter presence",
"color": "#1DA1F2",
"path": "twitter"
},
"Instagram": {
"icon": "📸",
"description": "Create Instagram captions and plan your visual content",
"color": "#E1306C",
"path": "instagram"
},
"YouTube": {
"icon": "🎥",
"description": "Generate video scripts and optimize your YouTube content",
"color": "#FF0000",
"path": "youtube"
}
}
# Create a grid of cards
cols = st.columns(3)
for idx, (platform, details) in enumerate(social_tools.items()):
with cols[idx % 3]:
st.markdown(f"""
<div class="social-card">
<div class="social-icon">{details['icon']}</div>
<div class="social-title">{platform}</div>
<div class="social-description">{details['description']}</div>
</div>
""", unsafe_allow_html=True)
if st.button(f"Open {platform}", key=f"btn_{platform}",
help=f"Launch {platform} tools",
use_container_width=True):
# Set query parameters to redirect to the specific tool
st.query_params["tool"] = details["path"]
st.rerun()
def setup_ui():
"""Set up the UI with custom styling."""
@@ -314,29 +412,18 @@ def setup_alwrity_ui():
"AI Writers": ("📝", get_ai_writers),
"Content Planning": ("📅", content_planning_tools),
"AI SEO Tools": ("🔍", ai_seo_tools),
"AI Social Tools": ("📱", None), # Set to None as we'll handle this separately
"AI Social Tools": ("📱", render_social_tools_dashboard),
"ALwrity Settings": ("⚙️", render_settings_page),
"Agents Teams(TBD)": ("🤝", lambda: st.subheader("Agents Teams - Coming Soon!")),
"Ask Alwrity(TBD)": ("💬", lambda: (
st.subheader("Chat with your Data, Chat with any Data.. COMING SOON !"),
st.markdown("Create a collection by uploading files (PDF, MD, CSV, etc), or crawl a data source (Websites, more sources coming soon."),
st.markdown("One can ask/chat, summarize and do semantic search over the uploaded data")
)),
"ALwrity Settings": ("⚙️", render_settings_page)
))
}
logger.info(f"Defined {len(nav_items)} navigation items")
# Define sub-menu items for AI Social Tools
social_tools_submenu = {
"Facebook": ("📘", lambda: facebook_main_menu()),
"LinkedIn": ("💼", lambda: linkedin_main_menu()),
"Twitter": ("🐦", lambda: run_dashboard()),
"Instagram": ("📸", lambda: insta_writer()),
"YouTube": ("🎥", lambda: youtube_main_menu())
}
logger.info(f"Defined {len(social_tools_submenu)} social tools submenu items")
# Create sidebar navigation
st.sidebar.markdown("### ALwrity Options")
st.sidebar.markdown('<div class="sidebar-nav">', unsafe_allow_html=True)
@@ -345,53 +432,12 @@ def setup_alwrity_ui():
for name, (icon, func) in nav_items.items():
button_class = "nav-button active" if st.session_state.active_tab == name else "nav-button"
if name == "AI Social Tools":
# For AI Social Tools, we'll create a button that toggles the sub-menu
if st.sidebar.button(f"{icon} {name}", key=f"nav_{name}",
help=f"Navigate to {name}", use_container_width=True):
st.session_state.active_tab = name
# Reset sub-tab when main tab changes
st.session_state.active_sub_tab = None
logger.info(f"Selected main tab: {name}")
# If AI Social Tools is active, show the sub-menu
if st.session_state.active_tab == "AI Social Tools":
st.sidebar.markdown('<div class="sub-menu">', unsafe_allow_html=True)
# Create sub-menu buttons
for sub_name, (sub_icon, sub_func) in social_tools_submenu.items():
# Create the button with a custom key that includes the platform name
button_key = f"sub_{sub_name}"
# Determine if this button is active
is_active = st.session_state.active_sub_tab == sub_name
# Create a container with the platform-specific class
platform_class = f"{sub_name.lower()}-button"
if is_active:
platform_class += " active"
# Add the platform-specific class to the button container
st.sidebar.markdown(f'<div class="{platform_class}">', unsafe_allow_html=True)
# Create the button
if st.sidebar.button(f"{sub_icon} {sub_name}", key=button_key,
help=f"Navigate to {sub_name}", use_container_width=True):
st.session_state.active_sub_tab = sub_name
logger.info(f"Selected social tool: {sub_name}")
# Close the div
st.sidebar.markdown('</div>', unsafe_allow_html=True)
st.sidebar.markdown('</div>', unsafe_allow_html=True)
else:
# For other navigation items, create regular buttons
if st.sidebar.button(f"{icon} {name}", key=f"nav_{name}",
help=f"Navigate to {name}", use_container_width=True):
st.session_state.active_tab = name
# Reset sub-tab when main tab changes
st.session_state.active_sub_tab = None
logger.info(f"Selected main tab: {name}")
if st.sidebar.button(f"{icon} {name}", key=f"nav_{name}",
help=f"Navigate to {name}", use_container_width=True):
st.session_state.active_tab = name
# Reset sub-tab when main tab changes
st.session_state.active_sub_tab = None
logger.info(f"Selected main tab: {name}")
st.sidebar.markdown('</div>', unsafe_allow_html=True)
@@ -402,10 +448,36 @@ def setup_alwrity_ui():
st.sidebar.image(icon_path, use_container_width=False)
st.sidebar.markdown('</div>', unsafe_allow_html=True)
# Display content based on active tab
# Display content based on active tab and tool selection
if st.session_state.active_tab == "AI Social Tools":
if not st.session_state.active_sub_tab:
# Only show title and info when no sub-tab is selected
# Check if a specific tool is selected
selected_tool = st.query_params.get("tool")
if selected_tool:
# Add a back button at the top
if st.button("← Back to Social Tools Dashboard", key=f"back_to_dashboard_{selected_tool}"):
# Clear the tool query parameter
st.query_params.clear()
st.rerun()
# Map tool paths to their respective functions
tool_functions = {
"facebook": facebook_main_menu,
"linkedin": linkedin_main_menu,
"twitter": run_dashboard,
"instagram": insta_writer,
"youtube": youtube_main_menu
}
if selected_tool in tool_functions:
# Clear any existing content
st.empty()
# Execute the selected tool's function
tool_functions[selected_tool]()
else:
st.error(f"Invalid tool selected: {selected_tool}")
render_social_tools_dashboard()
else:
# Show the dashboard if no tool is selected
st.markdown("""
<style>
.main .block-container {
@@ -414,40 +486,14 @@ def setup_alwrity_ui():
</style>
""", unsafe_allow_html=True)
st.title(f"{nav_items[st.session_state.active_tab][0]} {st.session_state.active_tab}")
st.info("Please select a social media platform from the sidebar.")
else:
# When a platform is selected, show no title and minimize spacing
st.markdown("""
<style>
.main .block-container {
padding-top: 0 !important;
padding-bottom: 0;
}
/* Remove all margins and padding from content area */
.element-container {
margin: 0 !important;
padding: 0 !important;
}
/* Hide any automatic headers */
.main .block-container > div:first-child {
margin-top: 0 !important;
padding-top: 0 !important;
}
</style>
""", unsafe_allow_html=True)
# Call the function directly without any title
social_tools_submenu[st.session_state.active_sub_tab][1]()
render_social_tools_dashboard()
else:
# Check if we're in the AI Writers section and handle writer selection
# Handle other tabs as before
if st.session_state.active_tab == "AI Writers":
# Get the writer parameter from the URL using st.query_params
writer = st.query_params.get("writer")
logger.info(f"Current writer from query params: {writer}")
if writer:
# Get the list of writers without rendering the dashboard
writers = list_ai_writers()
logger.info(f"Found {len(writers)} writers")
@@ -457,9 +503,7 @@ def setup_alwrity_ui():
if w["path"] == writer:
writer_found = True
logger.info(f"Found matching writer: {w['name']}, executing function")
# Clear any existing content
st.empty()
# Execute the writer function
w["function"]()
break
@@ -467,11 +511,9 @@ def setup_alwrity_ui():
logger.error(f"No writer found with path: {writer}")
st.error(f"No writer found with path: {writer}")
else:
# If no writer selected, show the dashboard
logger.info("No writer selected, showing dashboard")
get_ai_writers()
else:
# For all other tabs, show the title
st.markdown("""
<style>
.main .block-container {

View File

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

View File

@@ -1,7 +1,7 @@
"""Website scraping and AI analysis module."""
"""Website and SEO analysis module."""
import asyncio
from typing import Dict, List, Optional
from typing import Dict, List, Optional, Tuple
from bs4 import BeautifulSoup
from urllib.parse import urljoin, urlparse
import streamlit as st
@@ -21,51 +21,29 @@ import whois
import dns.resolver
from requests.exceptions import RequestException
from concurrent.futures import ThreadPoolExecutor
from .models import (
SEOAnalysisResult,
MetaTagAnalysis,
ContentAnalysis,
SEORecommendation
)
# Configure logging
logging.basicConfig(
level=logging.DEBUG,
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.StreamHandler(),
logging.FileHandler('logs/website_analyzer.log')
]
)
# Create a logger for the website analyzer
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)
}
# Create a separate logger for scraping operations
scraping_logger = logging.getLogger('website_analyzer.scraping')
scraping_logger.setLevel(logging.WARNING)
class WebsiteAnalyzer:
def __init__(self):
@@ -89,13 +67,17 @@ class WebsiteAnalyzer:
try:
# Validate URL
if not self._validate_url(url):
logger.error(f"Invalid URL format: {url}")
return {"error": "Invalid URL format"}
error_msg = f"Invalid URL format: {url}"
logger.error(error_msg)
return {
"success": False,
"error": error_msg,
"error_details": {"stage": "url_validation"}
}
# Basic URL parsing
parsed_url = urlparse(url)
domain = parsed_url.netloc
logger.debug(f"Parsed domain: {domain}")
# Initialize results dictionary
results = {
@@ -107,36 +89,105 @@ class WebsiteAnalyzer:
# Perform various analyses
with ThreadPoolExecutor(max_workers=4) as executor:
logger.info("Starting parallel analysis tasks")
# Basic website info
logger.info("Starting basic info analysis")
basic_info = executor.submit(self._get_basic_info, url).result()
if "error" in basic_info:
error_msg = f"Basic info analysis failed: {basic_info['error']}"
logger.error(error_msg)
return {
"success": False,
"error": error_msg,
"error_details": {
"stage": "basic_info",
"details": basic_info.get("error_details", {})
}
}
results["analysis"]["basic_info"] = basic_info
# SSL/TLS info
logger.info("Starting SSL analysis")
ssl_info = executor.submit(self._check_ssl, domain).result()
results["analysis"]["ssl_info"] = ssl_info
# DNS info
logger.info("Starting DNS analysis")
dns_info = executor.submit(self._check_dns, domain).result()
results["analysis"]["dns_info"] = dns_info
# WHOIS info
logger.info("Starting WHOIS analysis")
whois_info = executor.submit(self._get_whois_info, domain).result()
results["analysis"]["whois_info"] = whois_info
# Content analysis
logger.info("Starting content analysis")
content_info = executor.submit(self._analyze_content, url).result()
if "error" in content_info:
error_msg = f"Content analysis failed: {content_info['error']}"
logger.error(error_msg)
return {
"success": False,
"error": error_msg,
"error_details": {
"stage": "content_analysis",
"details": content_info.get("error_details", {})
}
}
results["analysis"]["content_info"] = content_info
# Performance metrics
logger.info("Starting performance analysis")
performance = executor.submit(self._check_performance, url).result()
if "error" in performance:
error_msg = f"Performance analysis failed: {performance['error']}"
logger.error(error_msg)
return {
"success": False,
"error": error_msg,
"error_details": {
"stage": "performance_analysis",
"details": performance.get("error_details", {})
}
}
results["analysis"]["performance"] = performance
# SEO analysis
logger.info("Starting SEO analysis")
seo_analysis = executor.submit(self._analyze_seo, url).result()
if "error" in seo_analysis:
error_msg = f"SEO analysis failed: {seo_analysis['error']}"
logger.error(error_msg)
return {
"success": False,
"error": error_msg,
"error_details": {
"stage": "seo_analysis",
"details": seo_analysis.get("error_details", {})
}
}
results["analysis"]["seo_info"] = seo_analysis
logger.info(f"Analysis completed successfully for {url}")
return results
logger.debug(f"Final results: {json.dumps(results, indent=2)}")
return {
"success": True,
"data": results
}
except Exception as e:
logger.error(f"Error during website analysis: {str(e)}", exc_info=True)
return {"error": str(e)}
error_msg = f"Error during website analysis: {str(e)}"
logger.error(error_msg, exc_info=True)
return {
"success": False,
"error": error_msg,
"error_details": {
"type": type(e).__name__,
"traceback": str(e.__traceback__)
}
}
def _validate_url(self, url: str) -> bool:
"""Validate URL format."""
@@ -149,7 +200,7 @@ class WebsiteAnalyzer:
def _get_basic_info(self, url: str) -> Dict:
"""Get basic website information."""
logger.debug(f"Getting basic info for {url}")
scraping_logger.debug(f"Getting basic info for {url}")
try:
response = self.session.get(url, timeout=10)
response.raise_for_status()
@@ -165,13 +216,31 @@ class WebsiteAnalyzer:
"robots_txt": self._get_robots_txt(url),
"sitemap": self._get_sitemap(url)
}
except requests.exceptions.RequestException as e:
error_msg = f"Request error in basic info: {str(e)}"
logger.error(error_msg, exc_info=True)
return {
"error": error_msg,
"error_details": {
"type": "RequestException",
"status_code": getattr(e.response, 'status_code', None) if hasattr(e, 'response') else None,
"url": url
}
}
except Exception as e:
logger.error(f"Error getting basic info: {str(e)}", exc_info=True)
return {"error": str(e)}
error_msg = f"Error getting basic info: {str(e)}"
logger.error(error_msg, exc_info=True)
return {
"error": error_msg,
"error_details": {
"type": type(e).__name__,
"traceback": str(e.__traceback__)
}
}
def _check_ssl(self, domain: str) -> Dict:
"""Check SSL/TLS certificate information."""
logger.debug(f"Checking SSL for {domain}")
scraping_logger.debug(f"Checking SSL for {domain}")
try:
context = ssl.create_default_context()
with socket.create_connection((domain, 443)) as sock:
@@ -190,7 +259,7 @@ class WebsiteAnalyzer:
def _check_dns(self, domain: str) -> Dict:
"""Check DNS records."""
logger.debug(f"Checking DNS for {domain}")
scraping_logger.debug(f"Checking DNS for {domain}")
try:
records = {}
for record_type in ['A', 'AAAA', 'MX', 'NS', 'TXT']:
@@ -200,7 +269,7 @@ class WebsiteAnalyzer:
except dns.resolver.NoAnswer:
records[record_type] = []
except Exception as e:
logger.warning(f"Error resolving {record_type} record: {str(e)}")
scraping_logger.warning(f"Error resolving {record_type} record: {str(e)}")
records[record_type] = []
return records
except Exception as e:
@@ -209,6 +278,7 @@ class WebsiteAnalyzer:
def _get_whois_info(self, domain: str) -> Dict:
"""Get WHOIS information for a domain."""
scraping_logger.debug(f"Getting WHOIS info for {domain}")
try:
w = whois.whois(domain)
@@ -240,7 +310,7 @@ class WebsiteAnalyzer:
def _analyze_content(self, url: str) -> Dict:
"""Analyze website content."""
logger.debug(f"Analyzing content for {url}")
scraping_logger.debug(f"Analyzing content for {url}")
try:
response = self.session.get(url, timeout=10)
response.raise_for_status()
@@ -255,6 +325,14 @@ class WebsiteAnalyzer:
# Count headings
headings = soup.find_all(['h1', 'h2', 'h3', 'h4', 'h5', 'h6'])
heading_counts = {
'h1': len(soup.find_all('h1')),
'h2': len(soup.find_all('h2')),
'h3': len(soup.find_all('h3')),
'h4': len(soup.find_all('h4')),
'h5': len(soup.find_all('h5')),
'h6': len(soup.find_all('h6'))
}
# Count images
images = soup.find_all('img')
@@ -262,22 +340,52 @@ class WebsiteAnalyzer:
# Count links
links = soup.find_all('a')
# Count paragraphs
paragraphs = soup.find_all('p')
return {
"word_count": word_count,
"heading_count": len(headings),
"heading_structure": heading_counts,
"image_count": len(images),
"link_count": len(links),
"paragraph_count": len(paragraphs),
"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 requests.exceptions.RequestException as e:
logger.error(f"Request error in content analysis: {str(e)}", exc_info=True)
return {
"word_count": 0,
"heading_count": 0,
"heading_structure": {'h1': 0, 'h2': 0, 'h3': 0, 'h4': 0, 'h5': 0, 'h6': 0},
"image_count": 0,
"link_count": 0,
"paragraph_count": 0,
"has_meta_description": False,
"has_robots_txt": False,
"has_sitemap": False,
"error": str(e)
}
except Exception as e:
logger.error(f"Content analysis error: {str(e)}", exc_info=True)
return {"error": str(e)}
return {
"word_count": 0,
"heading_count": 0,
"heading_structure": {'h1': 0, 'h2': 0, 'h3': 0, 'h4': 0, 'h5': 0, 'h6': 0},
"image_count": 0,
"link_count": 0,
"paragraph_count": 0,
"has_meta_description": False,
"has_robots_txt": False,
"has_sitemap": False,
"error": str(e)
}
def _check_performance(self, url: str) -> Dict:
"""Check website performance metrics."""
logger.debug(f"Checking performance for {url}")
scraping_logger.debug(f"Checking performance for {url}")
try:
start_time = datetime.now()
response = self.session.get(url, timeout=10)
@@ -289,11 +397,29 @@ class WebsiteAnalyzer:
"load_time": load_time,
"status_code": response.status_code,
"content_length": len(response.content),
"headers": dict(response.headers)
"headers": dict(response.headers),
"response_time": response.elapsed.total_seconds()
}
except requests.exceptions.RequestException as e:
logger.error(f"Request error in performance check: {str(e)}", exc_info=True)
return {
"load_time": 0,
"status_code": 0,
"content_length": 0,
"headers": {},
"response_time": 0,
"error": str(e)
}
except Exception as e:
logger.error(f"Performance check error: {str(e)}", exc_info=True)
return {"error": str(e)}
return {
"load_time": 0,
"status_code": 0,
"content_length": 0,
"headers": {},
"response_time": 0,
"error": str(e)
}
def _get_meta_description(self, soup: BeautifulSoup) -> Optional[str]:
"""Extract meta description from HTML."""
@@ -308,7 +434,7 @@ class WebsiteAnalyzer:
if response.status_code == 200:
return response.text
except Exception as e:
logger.warning(f"Error fetching robots.txt: {str(e)}")
scraping_logger.warning(f"Error fetching robots.txt: {str(e)}")
return None
def _get_sitemap(self, url: str) -> Optional[str]:
@@ -319,5 +445,253 @@ class WebsiteAnalyzer:
if response.status_code == 200:
return response.text
except Exception as e:
logger.warning(f"Error fetching sitemap.xml: {str(e)}")
return None
scraping_logger.warning(f"Error fetching sitemap.xml: {str(e)}")
return None
def _analyze_seo(self, url: str) -> Dict:
"""Analyze website SEO."""
try:
# Extract content
content, soup, extract_errors = self._extract_content(url)
if not content or not soup:
return {
"error": "Failed to extract content",
"error_details": {"errors": extract_errors}
}
# Analyze meta tags
meta_analysis = self._analyze_meta_tags(soup)
# Analyze content with AI
content_analysis, recommendations = self._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 {
"overall_score": overall_score,
"meta_tags": meta_analysis.__dict__,
"content": content_analysis.__dict__,
"recommendations": [rec.__dict__ for rec in recommendations]
}
except Exception as e:
error_msg = f"Error in SEO analysis: {str(e)}"
logger.error(error_msg, exc_info=True)
return {
"error": error_msg,
"error_details": {
"type": type(e).__name__,
"traceback": str(e.__traceback__)
}
}
def _extract_content(self, url: str) -> Tuple[Optional[str], Optional[BeautifulSoup], List[str]]:
"""Extract content from URL."""
errors = []
try:
response = self.session.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(self, 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(self, content: str) -> Tuple[ContentAnalysis, List[SEORecommendation]]:
"""Analyze content using AI."""
try:
# 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."""
try:
# Get AI analysis using llm_text_gen
analysis = llm_text_gen(
prompt=prompt,
system_prompt="You are an SEO expert analyzing website content.",
response_format="json_object"
)
if not analysis:
logger.error("Empty response from AI analysis")
return self._get_fallback_analysis(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 self._get_fallback_analysis(content)
except Exception as e:
logger.error(f"Error in AI analysis setup: {str(e)}")
return self._get_fallback_analysis(content)
def _get_fallback_analysis(self, content: str) -> Tuple[ContentAnalysis, List[SEORecommendation]]:
"""Provide fallback analysis when AI analysis is not available."""
try:
# Basic content analysis
words = content.split()
word_count = len(words)
# Simple readability score based on word count
readability_score = min(100, max(0, word_count / 10))
# Basic content quality score
content_quality_score = min(100, max(0, word_count / 20))
# Create basic recommendations
recommendations = [
SEORecommendation(
priority="high",
category="content",
issue="AI analysis unavailable",
recommendation="Consider running the analysis again with a valid API key for more detailed insights",
impact="Limited analysis capabilities"
)
]
return ContentAnalysis(
word_count=word_count,
headings_structure={},
keyword_density={},
readability_score=readability_score,
content_quality_score=content_quality_score
), recommendations
except Exception as e:
logger.error(f"Error in fallback analysis: {str(e)}")
return ContentAnalysis(
word_count=0,
headings_structure={},
keyword_density={},
readability_score=0,
content_quality_score=0
), []
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:
error_msg = f"Error in base analysis: {results['error']}"
logger.error(error_msg)
logger.error(f"Error details: {json.dumps(results.get('error_details', {}), indent=2)}")
return {
"success": False,
"error": error_msg,
"error_details": results.get("error_details", {})
}
# Add success status and wrap results
logger.info("Analysis completed successfully")
logger.debug(f"Analysis results: {json.dumps(results, indent=2)}")
return {
"success": True,
"data": results
}
except Exception as e:
error_msg = f"Error in analyze_website: {str(e)}"
logger.error(error_msg, exc_info=True)
return {
"success": False,
"error": error_msg,
"error_details": {
"type": type(e).__name__,
"traceback": str(e.__traceback__)
}
}

View File

@@ -0,0 +1,134 @@
from typing import Dict
import json
class ContentGapAnalyzer:
def __init__(self, analyzer):
self.analyzer = analyzer
def analyze(self, url: str) -> Dict:
"""
Analyze content gaps for a given URL.
Args:
url (str): The URL to analyze
Returns:
Dict: Analysis results including content gaps and recommendations
"""
try:
# Get base analysis
logger.info(f"Starting content gap analysis for URL: {url}")
base_analysis = self.analyzer.analyze_website(url)
# Check for errors in base analysis
if not base_analysis.get("success", False):
error_msg = base_analysis.get("error", "Unknown error in website analysis")
error_details = base_analysis.get("error_details", {})
logger.error(f"Base analysis failed: {error_msg}")
logger.error(f"Error details: {json.dumps(error_details, indent=2)}")
return {
"success": False,
"error": error_msg,
"error_details": error_details,
"stage": "base_analysis"
}
# Extract required sections
analysis_data = base_analysis.get("data", {}).get("analysis", {})
required_sections = ["content_info", "basic_info", "performance"]
missing_sections = [section for section in required_sections if section not in analysis_data]
if missing_sections:
error_msg = f"Missing required analysis sections: {', '.join(missing_sections)}"
logger.error(error_msg)
logger.error(f"Available sections: {list(analysis_data.keys())}")
return {
"success": False,
"error": error_msg,
"error_details": {
"missing_sections": missing_sections,
"available_sections": list(analysis_data.keys())
},
"stage": "section_validation"
}
# Extract content metrics
try:
content_info = analysis_data["content_info"]
basic_info = analysis_data["basic_info"]
performance = analysis_data["performance"]
except KeyError as e:
error_msg = f"Error extracting analysis section: {str(e)}"
logger.error(error_msg)
return {
"success": False,
"error": error_msg,
"error_details": {
"type": "KeyError",
"missing_key": str(e),
"available_keys": list(analysis_data.keys())
},
"stage": "data_extraction"
}
# Analyze content gaps
try:
gaps = self._analyze_content_gaps(content_info, basic_info, performance)
except Exception as e:
error_msg = f"Error analyzing content gaps: {str(e)}"
logger.error(error_msg, exc_info=True)
return {
"success": False,
"error": error_msg,
"error_details": {
"type": type(e).__name__,
"traceback": str(e.__traceback__)
},
"stage": "gap_analysis"
}
# Generate recommendations
try:
recommendations = self._generate_recommendations(gaps)
except Exception as e:
error_msg = f"Error generating recommendations: {str(e)}"
logger.error(error_msg, exc_info=True)
return {
"success": False,
"error": error_msg,
"error_details": {
"type": type(e).__name__,
"traceback": str(e.__traceback__)
},
"stage": "recommendation_generation"
}
return {
"success": True,
"data": {
"content_gaps": gaps,
"recommendations": recommendations,
"metrics": {
"word_count": content_info.get("word_count", 0),
"heading_count": content_info.get("heading_count", 0),
"image_count": content_info.get("image_count", 0),
"link_count": content_info.get("link_count", 0),
"paragraph_count": content_info.get("paragraph_count", 0),
"load_time": performance.get("load_time", 0),
"response_time": performance.get("response_time", 0)
}
}
}
except Exception as e:
error_msg = f"Error in content gap analysis: {str(e)}"
logger.error(error_msg, exc_info=True)
return {
"success": False,
"error": error_msg,
"error_details": {
"type": type(e).__name__,
"traceback": str(e.__traceback__)
},
"stage": "general"
}

View File

@@ -1,233 +0,0 @@
"""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
)

View File

@@ -673,4 +673,19 @@ select option {
.search-option.disabled h4,
.search-option.disabled p {
color: #64748b;
}
/* Move main content upwards and reduce free space at the top */
.main .block-container {
padding-top: 0 !important;
margin-top: 0 !important;
}
h1 {
margin-top: 0 !important;
padding-top: 0 !important;
}
/* Optionally, reduce margin for the main title container if used */
.seo-main-title, .main-title, .dashboard-title {
margin-top: 0 !important;
padding-top: 0 !important;
}

View File

@@ -21,8 +21,8 @@ imageio-ffmpeg==0.4.5
decorator==4.4.2
reportlab==4.4.0
textblob==0.19.0
numpy>=1.22.4,<2.0.0
pandas>=2.0.3
numpy>=1.24.0
pandas>=2.0.0
scikit-learn>=1.3.2
matplotlib>=3.8.2
plotly>=5.18.0
@@ -40,7 +40,7 @@ lxml_html_clean>=0.4.1
streamlit>=1.44.0
Authlib>=1.3.2
yfinance>=0.2.36
pandas_ta>=0.3.14b0
pandas-ta>=0.3.14b0
firecrawl-py>=1.14.1
gTTS>=2.5.1
streamlit-mic-recorder>=0.0.8
@@ -52,4 +52,6 @@ tavily-python>=0.2.8
tinify>=1.6.0
validators>=0.20.0
python-whois==0.9.5
dnspython
dnspython
sqlalchemy==2.0.41
scipy>=1.10.0