diff --git a/Getting Started/README_install_bat.md b/Getting Started/Option_1_Windows_Quick_Install/README.md
similarity index 100%
rename from Getting Started/README_install_bat.md
rename to Getting Started/Option_1_Windows_Quick_Install/README.md
diff --git a/Getting Started/Option_1_Windows_Quick_Install/install_alwrity_windows.bat b/Getting Started/Option_1_Windows_Quick_Install/install_alwrity_windows.bat
new file mode 100644
index 00000000..b0bde02e
--- /dev/null
+++ b/Getting Started/Option_1_Windows_Quick_Install/install_alwrity_windows.bat
@@ -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
\ No newline at end of file
diff --git a/Getting Started/Option_2_Python_Users/README.md b/Getting Started/Option_2_Python_Users/README.md
new file mode 100644
index 00000000..5a7b74ef
--- /dev/null
+++ b/Getting Started/Option_2_Python_Users/README.md
@@ -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
+
+---
diff --git a/Getting Started/setup.py b/Getting Started/Option_2_Python_Users/setup.py
similarity index 100%
rename from Getting Started/setup.py
rename to Getting Started/Option_2_Python_Users/setup.py
diff --git a/Getting Started/Dockerfile b/Getting Started/Option_3_Docker_Install/Dockerfile
similarity index 100%
rename from Getting Started/Dockerfile
rename to Getting Started/Option_3_Docker_Install/Dockerfile
diff --git a/Getting Started/README_dockerfile.md b/Getting Started/Option_3_Docker_Install/README.md
similarity index 100%
rename from Getting Started/README_dockerfile.md
rename to Getting Started/Option_3_Docker_Install/README.md
diff --git a/Getting Started/Option_4_Mac_Users/Dockerfile b/Getting Started/Option_4_Mac_Users/Dockerfile
new file mode 100644
index 00000000..e4f39152
--- /dev/null
+++ b/Getting Started/Option_4_Mac_Users/Dockerfile
@@ -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"]
\ No newline at end of file
diff --git a/Getting Started/Option_4_Mac_Users/README.md b/Getting Started/Option_4_Mac_Users/README.md
new file mode 100644
index 00000000..189c4798
--- /dev/null
+++ b/Getting Started/Option_4_Mac_Users/README.md
@@ -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
\ No newline at end of file
diff --git a/Getting Started/Option_4_Mac_Users/setup.py b/Getting Started/Option_4_Mac_Users/setup.py
new file mode 100644
index 00000000..8bc27105
--- /dev/null
+++ b/Getting Started/Option_4_Mac_Users/setup.py
@@ -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}")
\ No newline at end of file
diff --git a/Getting Started/README.md b/Getting Started/README.md
index 7cee0d5e..3b40d42d 100644
--- a/Getting Started/README.md
+++ b/Getting Started/README.md
@@ -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)
diff --git a/Getting Started/README_setup_py.md b/Getting Started/README_setup_py.md
deleted file mode 100644
index 17d1067c..00000000
--- a/Getting Started/README_setup_py.md
+++ /dev/null
@@ -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
-
----
diff --git a/Getting Started/install_alwrity.bat b/Getting Started/install_alwrity.bat
deleted file mode 100644
index 007e1d1d..00000000
--- a/Getting Started/install_alwrity.bat
+++ /dev/null
@@ -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
diff --git a/calendar_data.json b/calendar_data.json
new file mode 100644
index 00000000..a5a2e70c
--- /dev/null
+++ b/calendar_data.json
@@ -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
+ }
+ ]
+ }
+}
\ No newline at end of file
diff --git a/docs/architecture/database_schema.rst b/docs/architecture/database_schema.rst
index bbe346fb..1bb96afd 100644
--- a/docs/architecture/database_schema.rst
+++ b/docs/architecture/database_schema.rst
@@ -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
--------------------
diff --git a/lib/ai_seo_tools/content_calendar/README.md b/lib/ai_seo_tools/content_calendar/README.md
new file mode 100644
index 00000000..55602f8f
--- /dev/null
+++ b/lib/ai_seo_tools/content_calendar/README.md
@@ -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.
\ No newline at end of file
diff --git a/lib/ai_seo_tools/content_calendar/core/ai_generator.py b/lib/ai_seo_tools/content_calendar/core/ai_generator.py
new file mode 100644
index 00000000..3aeb8fb2
--- /dev/null
+++ b/lib/ai_seo_tools/content_calendar/core/ai_generator.py
@@ -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
\ No newline at end of file
diff --git a/lib/ai_seo_tools/content_calendar/core/calendar_manager.py b/lib/ai_seo_tools/content_calendar/core/calendar_manager.py
new file mode 100644
index 00000000..a977b4f5
--- /dev/null
+++ b/lib/ai_seo_tools/content_calendar/core/calendar_manager.py
@@ -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)
\ No newline at end of file
diff --git a/lib/ai_seo_tools/content_calendar/core/content_brief.py b/lib/ai_seo_tools/content_calendar/core/content_brief.py
new file mode 100644
index 00000000..72492687
--- /dev/null
+++ b/lib/ai_seo_tools/content_calendar/core/content_brief.py
@@ -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': {}
+ }
\ No newline at end of file
diff --git a/lib/ai_seo_tools/content_calendar/core/content_generator.py b/lib/ai_seo_tools/content_calendar/core/content_generator.py
new file mode 100644
index 00000000..3a0b6046
--- /dev/null
+++ b/lib/ai_seo_tools/content_calendar/core/content_generator.py
@@ -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 {}
\ No newline at end of file
diff --git a/lib/ai_seo_tools/content_calendar/examples/calendar_usage.py b/lib/ai_seo_tools/content_calendar/examples/calendar_usage.py
new file mode 100644
index 00000000..c3f77f33
--- /dev/null
+++ b/lib/ai_seo_tools/content_calendar/examples/calendar_usage.py
@@ -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()
\ No newline at end of file
diff --git a/lib/ai_seo_tools/content_calendar/examples/generate_content_brief.py b/lib/ai_seo_tools/content_calendar/examples/generate_content_brief.py
new file mode 100644
index 00000000..8d2851f1
--- /dev/null
+++ b/lib/ai_seo_tools/content_calendar/examples/generate_content_brief.py
@@ -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()
\ No newline at end of file
diff --git a/lib/ai_seo_tools/content_calendar/examples/integration_example.py b/lib/ai_seo_tools/content_calendar/examples/integration_example.py
new file mode 100644
index 00000000..e17f7ea7
--- /dev/null
+++ b/lib/ai_seo_tools/content_calendar/examples/integration_example.py
@@ -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()
\ No newline at end of file
diff --git a/lib/ai_seo_tools/content_calendar/examples/platform_adaptation_example.py b/lib/ai_seo_tools/content_calendar/examples/platform_adaptation_example.py
new file mode 100644
index 00000000..de654b2f
--- /dev/null
+++ b/lib/ai_seo_tools/content_calendar/examples/platform_adaptation_example.py
@@ -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()
\ No newline at end of file
diff --git a/lib/ai_seo_tools/content_calendar/integrations/gap_analyzer.py b/lib/ai_seo_tools/content_calendar/integrations/gap_analyzer.py
new file mode 100644
index 00000000..78dd0051
--- /dev/null
+++ b/lib/ai_seo_tools/content_calendar/integrations/gap_analyzer.py
@@ -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="{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {name} | {function} | {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
+ }
\ No newline at end of file
diff --git a/lib/ai_seo_tools/content_calendar/integrations/integration_manager.py b/lib/ai_seo_tools/content_calendar/integrations/integration_manager.py
new file mode 100644
index 00000000..f8a5bec5
--- /dev/null
+++ b/lib/ai_seo_tools/content_calendar/integrations/integration_manager.py
@@ -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 {}
\ No newline at end of file
diff --git a/lib/ai_seo_tools/content_calendar/integrations/platform_adapters.py b/lib/ai_seo_tools/content_calendar/integrations/platform_adapters.py
new file mode 100644
index 00000000..641f87bb
--- /dev/null
+++ b/lib/ai_seo_tools/content_calendar/integrations/platform_adapters.py
@@ -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="{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {message} "
+)
+
+# 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)
+ }
\ No newline at end of file
diff --git a/lib/ai_seo_tools/content_calendar/integrations/seo_optimizer.py b/lib/ai_seo_tools/content_calendar/integrations/seo_optimizer.py
new file mode 100644
index 00000000..877dba9b
--- /dev/null
+++ b/lib/ai_seo_tools/content_calendar/integrations/seo_optimizer.py
@@ -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)
+ }
\ No newline at end of file
diff --git a/lib/ai_seo_tools/content_calendar/integrations/seo_tools.py b/lib/ai_seo_tools/content_calendar/integrations/seo_tools.py
new file mode 100644
index 00000000..29298d16
--- /dev/null
+++ b/lib/ai_seo_tools/content_calendar/integrations/seo_tools.py
@@ -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="{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {message} "
+)
+
+# 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
+ }
\ No newline at end of file
diff --git a/lib/ai_seo_tools/content_calendar/models/calendar.py b/lib/ai_seo_tools/content_calendar/models/calendar.py
new file mode 100644
index 00000000..8e1713e3
--- /dev/null
+++ b/lib/ai_seo_tools/content_calendar/models/calendar.py
@@ -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')
+ )
\ No newline at end of file
diff --git a/lib/ai_seo_tools/content_calendar/tests/test_ai_generator.py b/lib/ai_seo_tools/content_calendar/tests/test_ai_generator.py
new file mode 100644
index 00000000..7773e1dc
--- /dev/null
+++ b/lib/ai_seo_tools/content_calendar/tests/test_ai_generator.py
@@ -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()
\ No newline at end of file
diff --git a/lib/ai_seo_tools/content_calendar/tests/test_content_brief.py b/lib/ai_seo_tools/content_calendar/tests/test_content_brief.py
new file mode 100644
index 00000000..f5105da5
--- /dev/null
+++ b/lib/ai_seo_tools/content_calendar/tests/test_content_brief.py
@@ -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()
\ No newline at end of file
diff --git a/lib/ai_seo_tools/content_calendar/tests/test_integration_manager.py b/lib/ai_seo_tools/content_calendar/tests/test_integration_manager.py
new file mode 100644
index 00000000..869b5ad0
--- /dev/null
+++ b/lib/ai_seo_tools/content_calendar/tests/test_integration_manager.py
@@ -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()
\ No newline at end of file
diff --git a/lib/ai_seo_tools/content_calendar/tests/test_platform_adapters.py b/lib/ai_seo_tools/content_calendar/tests/test_platform_adapters.py
new file mode 100644
index 00000000..b06220bf
--- /dev/null
+++ b/lib/ai_seo_tools/content_calendar/tests/test_platform_adapters.py
@@ -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()
\ No newline at end of file
diff --git a/lib/ai_seo_tools/content_calendar/tests/test_seo_optimizer.py b/lib/ai_seo_tools/content_calendar/tests/test_seo_optimizer.py
new file mode 100644
index 00000000..f36f1a26
--- /dev/null
+++ b/lib/ai_seo_tools/content_calendar/tests/test_seo_optimizer.py
@@ -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()
\ No newline at end of file
diff --git a/lib/ai_seo_tools/content_calendar/ui/add_content_modal.py b/lib/ai_seo_tools/content_calendar/ui/add_content_modal.py
new file mode 100644
index 00000000..c9b594b6
--- /dev/null
+++ b/lib/ai_seo_tools/content_calendar/ui/add_content_modal.py
@@ -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
\ No newline at end of file
diff --git a/lib/ai_seo_tools/content_calendar/ui/ai_suggestions_modal.py b/lib/ai_seo_tools/content_calendar/ui/ai_suggestions_modal.py
new file mode 100644
index 00000000..3d8cdd0b
--- /dev/null
+++ b/lib/ai_seo_tools/content_calendar/ui/ai_suggestions_modal.py
@@ -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}")
\ No newline at end of file
diff --git a/lib/ai_seo_tools/content_calendar/ui/calendar_view.py b/lib/ai_seo_tools/content_calendar/ui/calendar_view.py
new file mode 100644
index 00000000..a270664e
--- /dev/null
+++ b/lib/ai_seo_tools/content_calendar/ui/calendar_view.py
@@ -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.")
\ No newline at end of file
diff --git a/lib/ai_seo_tools/content_calendar/ui/components/ab_testing.py b/lib/ai_seo_tools/content_calendar/ui/components/ab_testing.py
new file mode 100644
index 00000000..d1d3a055
--- /dev/null
+++ b/lib/ai_seo_tools/content_calendar/ui/components/ab_testing.py
@@ -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}")
\ No newline at end of file
diff --git a/lib/ai_seo_tools/content_calendar/ui/components/badge.py b/lib/ai_seo_tools/content_calendar/ui/components/badge.py
new file mode 100644
index 00000000..645a0a4e
--- /dev/null
+++ b/lib/ai_seo_tools/content_calendar/ui/components/badge.py
@@ -0,0 +1,2 @@
+def render_badge(platform_disp, platform_icon, type_disp, status_disp):
+ return f"{platform_icon} {platform_disp} | {type_disp} | {status_disp} "
\ No newline at end of file
diff --git a/lib/ai_seo_tools/content_calendar/ui/components/content_card.py b/lib/ai_seo_tools/content_calendar/ui/components/content_card.py
new file mode 100644
index 00000000..214e93d9
--- /dev/null
+++ b/lib/ai_seo_tools/content_calendar/ui/components/content_card.py
@@ -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"
", unsafe_allow_html=True)
+ st.markdown(f"
", unsafe_allow_html=True)
+ st.markdown(f"
"
+ f"{type_icon}{row['title']}
", unsafe_allow_html=True)
+ st.markdown("
", 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("
", unsafe_allow_html=True)
+ st.markdown("
", unsafe_allow_html=True)
+ st.markdown(f"
{platform_icon} {platform_disp} | {type_disp} | {status_disp}
", unsafe_allow_html=True)
+ st.markdown("
", unsafe_allow_html=True)
\ No newline at end of file
diff --git a/lib/ai_seo_tools/content_calendar/ui/components/content_optimization.py b/lib/ai_seo_tools/content_calendar/ui/components/content_optimization.py
new file mode 100644
index 00000000..fea72e10
--- /dev/null
+++ b/lib/ai_seo_tools/content_calendar/ui/components/content_optimization.py
@@ -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.")
\ No newline at end of file
diff --git a/lib/ai_seo_tools/content_calendar/ui/components/content_series.py b/lib/ai_seo_tools/content_calendar/ui/components/content_series.py
new file mode 100644
index 00000000..6c414165
--- /dev/null
+++ b/lib/ai_seo_tools/content_calendar/ui/components/content_series.py
@@ -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()
\ No newline at end of file
diff --git a/lib/ai_seo_tools/content_calendar/ui/components/performance_insights.py b/lib/ai_seo_tools/content_calendar/ui/components/performance_insights.py
new file mode 100644
index 00000000..78448a86
--- /dev/null
+++ b/lib/ai_seo_tools/content_calendar/ui/components/performance_insights.py
@@ -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)}")
\ No newline at end of file
diff --git a/lib/ai_seo_tools/content_calendar/ui/dashboard.py b/lib/ai_seo_tools/content_calendar/ui/dashboard.py
new file mode 100644
index 00000000..963a77ae
--- /dev/null
+++ b/lib/ai_seo_tools/content_calendar/ui/dashboard.py
@@ -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("""
+
+ """, 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()
\ No newline at end of file
diff --git a/lib/ai_seo_tools/content_calendar/ui/filters.py b/lib/ai_seo_tools/content_calendar/ui/filters.py
new file mode 100644
index 00000000..39d9a27d
--- /dev/null
+++ b/lib/ai_seo_tools/content_calendar/ui/filters.py
@@ -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
\ No newline at end of file
diff --git a/lib/ai_seo_tools/content_calendar/utils/date_utils.py b/lib/ai_seo_tools/content_calendar/utils/date_utils.py
new file mode 100644
index 00000000..65da3712
--- /dev/null
+++ b/lib/ai_seo_tools/content_calendar/utils/date_utils.py
@@ -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
\ No newline at end of file
diff --git a/lib/ai_seo_tools/content_calendar/utils/error_handling.py b/lib/ai_seo_tools/content_calendar/utils/error_handling.py
new file mode 100644
index 00000000..af47b98d
--- /dev/null
+++ b/lib/ai_seo_tools/content_calendar/utils/error_handling.py
@@ -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)}")
\ No newline at end of file
diff --git a/lib/ai_seo_tools/content_gap_analysis/README.md b/lib/ai_seo_tools/content_gap_analysis/README.md
new file mode 100644
index 00000000..670a94ca
--- /dev/null
+++ b/lib/ai_seo_tools/content_gap_analysis/README.md
@@ -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
\ No newline at end of file
diff --git a/lib/ai_seo_tools/content_gap_analysis/__init__.py b/lib/ai_seo_tools/content_gap_analysis/__init__.py
new file mode 100644
index 00000000..a786dbf0
--- /dev/null
+++ b/lib/ai_seo_tools/content_gap_analysis/__init__.py
@@ -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()
\ No newline at end of file
diff --git a/lib/ai_seo_tools/content_gap_analysis/competitor_analyzer.py b/lib/ai_seo_tools/content_gap_analysis/competitor_analyzer.py
new file mode 100644
index 00000000..d5effd97
--- /dev/null
+++ b/lib/ai_seo_tools/content_gap_analysis/competitor_analyzer.py
@@ -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="{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {message} "
+)
+
+# 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'
+ ])
\ No newline at end of file
diff --git a/lib/ai_seo_tools/content_gap_analysis/keyword_researcher.py b/lib/ai_seo_tools/content_gap_analysis/keyword_researcher.py
new file mode 100644
index 00000000..8c8f8415
--- /dev/null
+++ b/lib/ai_seo_tools/content_gap_analysis/keyword_researcher.py
@@ -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="{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {message} "
+)
+
+# 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': {}
+ }
\ No newline at end of file
diff --git a/lib/ai_seo_tools/content_gap_analysis/main.py b/lib/ai_seo_tools/content_gap_analysis/main.py
new file mode 100644
index 00000000..08c4f6f8
--- /dev/null
+++ b/lib/ai_seo_tools/content_gap_analysis/main.py
@@ -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="{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {message} "
+)
+
+# 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 {}
\ No newline at end of file
diff --git a/lib/ai_seo_tools/content_gap_analysis/navigation.py b/lib/ai_seo_tools/content_gap_analysis/navigation.py
new file mode 100644
index 00000000..6e99bbf7
--- /dev/null
+++ b/lib/ai_seo_tools/content_gap_analysis/navigation.py
@@ -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
\ No newline at end of file
diff --git a/lib/ai_seo_tools/content_gap_analysis/recommendation_engine.py b/lib/ai_seo_tools/content_gap_analysis/recommendation_engine.py
new file mode 100644
index 00000000..9f7f9574
--- /dev/null
+++ b/lib/ai_seo_tools/content_gap_analysis/recommendation_engine.py
@@ -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="{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {message} "
+)
+
+# 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
\ No newline at end of file
diff --git a/lib/ai_seo_tools/content_gap_analysis/ui.py b/lib/ai_seo_tools/content_gap_analysis/ui.py
new file mode 100644
index 00000000..6832597f
--- /dev/null
+++ b/lib/ai_seo_tools/content_gap_analysis/ui.py
@@ -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()
\ No newline at end of file
diff --git a/lib/ai_seo_tools/content_gap_analysis/utils/README.md b/lib/ai_seo_tools/content_gap_analysis/utils/README.md
new file mode 100644
index 00000000..86bc5ffb
--- /dev/null
+++ b/lib/ai_seo_tools/content_gap_analysis/utils/README.md
@@ -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.
\ No newline at end of file
diff --git a/lib/ai_seo_tools/content_gap_analysis/utils/__init__.py b/lib/ai_seo_tools/content_gap_analysis/utils/__init__.py
new file mode 100644
index 00000000..04b0726f
--- /dev/null
+++ b/lib/ai_seo_tools/content_gap_analysis/utils/__init__.py
@@ -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'
+]
\ No newline at end of file
diff --git a/lib/ai_seo_tools/content_gap_analysis/utils/ai_processor.py b/lib/ai_seo_tools/content_gap_analysis/utils/ai_processor.py
new file mode 100644
index 00000000..eddbead0
--- /dev/null
+++ b/lib/ai_seo_tools/content_gap_analysis/utils/ai_processor.py
@@ -0,0 +1,1134 @@
+"""
+AI processor module 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.gpt_providers.text_generation.main_text_generation import llm_text_gen
+from lib.gpt_providers.text_to_image_generation.main_generate_image_from_prompt import generate_image
+import asyncio
+import sys
+import os
+import json
+import re
+from collections import Counter
+
+# Configure logger
+logger.remove() # Remove default handler
+logger.add(
+ "logs/ai_processor.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="{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {message} "
+)
+
+# Ensure logs directory exists
+os.makedirs("logs", exist_ok=True)
+
+class ProgressTracker:
+ """Tracks progress of AI processing tasks."""
+
+ def __init__(self):
+ """Initialize the progress tracker."""
+ self._progress = {
+ 'status': 'initializing',
+ 'current_step': 'Starting',
+ 'progress': 0,
+ 'details': 'Initializing...'
+ }
+
+ def update(self, progress_data: Dict[str, Any]) -> None:
+ """
+ Update progress information.
+
+ Args:
+ progress_data: Dictionary containing progress information
+ - status: Current status ('initializing', 'in_progress', 'completed', 'error')
+ - current_step: Description of current step
+ - progress: Progress percentage (0-100)
+ - details: Additional details about current step
+ """
+ if not isinstance(progress_data, dict):
+ raise ValueError("Progress data must be a dictionary")
+
+ required_fields = ['status', 'current_step', 'progress', 'details']
+ for field in required_fields:
+ if field not in progress_data:
+ raise ValueError(f"Missing required field: {field}")
+
+ # Validate progress value
+ progress = progress_data.get('progress', 0)
+ if not isinstance(progress, (int, float)) or progress < 0 or progress > 100:
+ raise ValueError("Progress must be a number between 0 and 100")
+
+ # Validate status
+ valid_statuses = ['initializing', 'in_progress', 'completed', 'error']
+ status = progress_data.get('status')
+ if status not in valid_statuses:
+ raise ValueError(f"Invalid status. Must be one of: {', '.join(valid_statuses)}")
+
+ # Update progress
+ self._progress.update(progress_data)
+
+ def get_progress(self) -> Dict[str, Any]:
+ """
+ Get current progress information.
+
+ Returns:
+ Dictionary containing current progress information
+ """
+ return self._progress.copy()
+
+ def reset(self) -> None:
+ """Reset progress to initial state."""
+ self._progress = {
+ 'status': 'initializing',
+ 'current_step': 'Starting',
+ 'progress': 0,
+ 'details': 'Initializing...'
+ }
+
+class AIProcessor:
+ """Processes and enhances content analysis using AI techniques."""
+
+ def __init__(self):
+ """Initialize the AI processor."""
+ self.cache = {}
+ self.progress = ProgressTracker()
+ self.system_prompts = {
+ 'content_analysis': """You are an expert SEO and content analyst. Your task is to analyze content and provide detailed insights.
+ Focus on:
+ 1. Content quality and structure
+ 2. Topic coverage and depth
+ 3. SEO optimization
+ 4. User engagement potential
+ 5. Content gaps and opportunities
+
+ Provide your analysis in a structured format with specific, actionable recommendations.
+ Use clear metrics and examples to support your insights.""",
+
+ 'competitor_analysis': """You are an expert competitive analyst. Your task is to analyze competitor data and provide strategic insights.
+ Focus on:
+ 1. Content strategy differences
+ 2. Competitive advantages
+ 3. Content gaps and opportunities
+ 4. Market positioning
+ 5. Strategic recommendations
+
+ Provide your analysis in a structured format with specific, actionable recommendations.
+ Include competitive benchmarks and improvement opportunities.""",
+
+ 'keyword_analysis': """You are an expert keyword analyst. Your task is to analyze keyword data and provide strategic insights.
+ Focus on:
+ 1. Keyword opportunities
+ 2. Search intent patterns
+ 3. Content format suggestions
+ 4. Topic clusters
+ 5. Strategic recommendations
+
+ Provide your analysis in a structured format with specific, actionable recommendations.
+ Include keyword metrics and content strategy suggestions.""",
+
+ 'recommendation_analysis': """You are an expert content strategist. Your task is to analyze recommendations and provide implementation insights.
+ Focus on:
+ 1. Implementation feasibility
+ 2. Expected impact
+ 3. Resource requirements
+ 4. Timeline suggestions
+ 5. Risk assessment
+
+ Provide your analysis in a structured format with specific, actionable guidance.
+ Include implementation steps and success metrics."""
+ }
+
+ def analyze_content(self, data: Dict[str, Any]) -> Dict[str, Any]:
+ """
+ Analyze website content using AI techniques.
+
+ Args:
+ data: Dictionary containing website content and metadata
+
+ Returns:
+ Dictionary containing AI-enhanced analysis
+ """
+ try:
+ self.progress.update({'status': 'in_progress', 'current_step': 'Analyzing content structure'})
+
+ url = data.get('url', '')
+ industry = data.get('industry', '')
+ content = data.get('content', {})
+
+ # Generate AI prompt for content analysis
+ prompt = f"""
+ Analyze the following website content and provide detailed insights:
+
+ URL: {url}
+ Industry: {industry}
+ Content: {content}
+
+ Please provide a comprehensive analysis covering:
+ 1. Content Quality Assessment
+ - Structure and organization
+ - Readability and engagement
+ - Technical elements
+ - SEO optimization
+
+ 2. Topic Analysis
+ - Coverage and depth
+ - Relevance to industry
+ - Content gaps
+ - Opportunities
+
+ 3. Performance Metrics
+ - User engagement potential
+ - SEO effectiveness
+ - Content value
+
+ 4. Strategic Recommendations
+ - Content improvements
+ - SEO enhancements
+ - Engagement strategies
+ - Implementation steps
+
+ Format your response in a clear, structured manner with specific examples and actionable recommendations.
+ """
+
+ self.progress.update({'current_step': 'Analyzing content structure'})
+
+ # Get AI analysis with custom system prompt
+ ai_analysis = llm_text_gen(prompt, system_prompt=self.system_prompts['content_analysis'])
+
+ self.progress.update({'current_step': 'Evaluating content quality'})
+
+ # Extract content metrics
+ content_metrics = self._analyze_content_metrics(content)
+
+ self.progress.update({'current_step': 'Assessing SEO elements'})
+
+ # Analyze content evolution
+ evolution_analysis = self._analyze_content_evolution(content)
+
+ self.progress.update({'current_step': 'Analyzing topic trends'})
+
+ # Analyze topic trends
+ topic_trends = self._analyze_topic_trends(content, industry)
+
+ self.progress.update({'current_step': 'Analyzing performance trends'})
+
+ # Analyze performance trends
+ performance_trends = self._analyze_performance_trends(content)
+
+ self.progress.update({'current_step': 'Generating content insights'})
+
+ # Generate AI insights
+ insights = self._generate_content_insights(content_metrics, evolution_analysis, topic_trends, performance_trends)
+
+ self.progress.update({'current_step': 'Creating visual representation'})
+
+ # Generate visual representation
+ visualization = self._generate_content_visualization(content, insights)
+
+ self.progress.update({'status': 'completed', 'current_step': 'Completed content analysis'})
+
+ return {
+ 'content_metrics': content_metrics,
+ 'content_evolution': evolution_analysis,
+ 'topic_trends': topic_trends,
+ 'performance_trends': performance_trends,
+ 'ai_insights': insights,
+ 'ai_analysis': ai_analysis,
+ 'visualization': visualization
+ }
+
+ except Exception as e:
+ if self.progress.get_progress().get('status') == 'in_progress':
+ self.progress.update({'status': 'error', 'current_step': 'Error in content analysis', 'details': str(e)})
+ st.error(f"Error in AI content analysis: {str(e)}")
+ return {
+ 'error': str(e),
+ 'content_metrics': {},
+ 'content_evolution': {},
+ 'topic_trends': {},
+ 'performance_trends': {},
+ 'ai_insights': {},
+ 'ai_analysis': {},
+ 'visualization': None
+ }
+
+ def analyze_competitors(self, data: Dict[str, Any]) -> Dict[str, Any]:
+ """
+ Analyze competitor content using AI techniques.
+
+ Args:
+ data: Dictionary containing competitor data and analysis
+
+ Returns:
+ Dictionary containing AI-enhanced competitor analysis
+ """
+ try:
+ self.progress.update({'status': 'in_progress', 'current_step': 'Analyzing competitor content'})
+
+ competitors = data.get('competitors', [])
+ analysis = data.get('analysis', {})
+
+ # Generate AI prompt for competitor analysis
+ prompt = f"""
+ Analyze the following competitor data and provide strategic insights:
+
+ Competitors: {competitors}
+ Analysis: {analysis}
+
+ Please provide a comprehensive analysis covering:
+ 1. Content Strategy Analysis
+ - Content types and formats
+ - Topic coverage
+ - Content quality
+ - Engagement strategies
+
+ 2. Competitive Position
+ - Market positioning
+ - Unique advantages
+ - Content gaps
+ - Opportunities
+
+ 3. Performance Metrics
+ - Content effectiveness
+ - User engagement
+ - SEO performance
+ - Market share
+
+ 4. Strategic Recommendations
+ - Content improvements
+ - Competitive advantages
+ - Market positioning
+ - Implementation steps
+
+ Format your response in a clear, structured manner with specific examples and actionable recommendations.
+ """
+
+ self.progress.update({'current_step': 'Evaluating market position'})
+
+ # Get AI analysis with custom system prompt
+ ai_analysis = llm_text_gen(prompt, system_prompt=self.system_prompts['competitor_analysis'])
+
+ self.progress.update({'current_step': 'Identifying content gaps'})
+
+ # Analyze competitor trends
+ trend_analysis = self._analyze_competitor_trends(competitors, analysis)
+
+ self.progress.update({'current_step': 'Generating competitive insights'})
+
+ # Generate competitive insights
+ insights = self._generate_competitive_insights(competitors, analysis, trend_analysis)
+
+ self.progress.update({'current_step': 'Creating visual representation'})
+
+ # Generate visual representation
+ visualization = self._generate_competitor_visualization(competitors, insights)
+
+ self.progress.update({'status': 'completed', 'current_step': 'Completed competitor analysis'})
+
+ return {
+ 'competitor_trends': trend_analysis,
+ 'competitive_insights': insights,
+ 'ai_analysis': ai_analysis,
+ 'visualization': visualization
+ }
+
+ except Exception as e:
+ if self.progress.get_progress().get('status') == 'in_progress':
+ self.progress.update({'status': 'error', 'current_step': 'Error in competitor analysis', 'details': str(e)})
+ st.error(f"Error in AI competitor analysis: {str(e)}")
+ return {
+ 'error': str(e),
+ 'competitor_trends': {},
+ 'competitive_insights': {},
+ 'ai_analysis': {},
+ 'visualization': None
+ }
+
+ def analyze_keywords(self, data: Dict[str, Any]) -> Dict[str, Any]:
+ """
+ Analyze keywords using AI techniques.
+
+ Args:
+ data: Dictionary containing keyword data
+
+ Returns:
+ Dictionary containing AI-enhanced keyword analysis
+ """
+ try:
+ self.progress.update({'status': 'in_progress', 'current_step': 'Analyzing keyword trends'})
+
+ industry = data.get('industry', '')
+ keywords = data.get('keywords', {})
+
+ # Generate AI prompt for keyword analysis
+ prompt = f"""
+ Analyze the following keyword data and provide strategic insights:
+
+ Industry: {industry}
+ Keywords: {keywords}
+
+ Please provide a comprehensive analysis covering:
+ 1. Keyword Opportunity Analysis
+ - Search volume trends
+ - Competition levels
+ - Difficulty scores
+ - Potential impact
+
+ 2. Search Intent Analysis
+ - User intent patterns
+ - Content type alignment
+ - Topic clusters
+ - Content gaps
+
+ 3. Content Strategy
+ - Format recommendations
+ - Topic suggestions
+ - Implementation approach
+ - Success metrics
+
+ 4. Strategic Recommendations
+ - Keyword targeting
+ - Content development
+ - Implementation steps
+ - Performance tracking
+
+ Format your response in a clear, structured manner with specific examples and actionable recommendations.
+ """
+
+ self.progress.update({'current_step': 'Evaluating search intent'})
+
+ # Get AI analysis with custom system prompt
+ ai_analysis = llm_text_gen(prompt, system_prompt=self.system_prompts['keyword_analysis'])
+
+ self.progress.update({'current_step': 'Identifying opportunities'})
+
+ # Analyze keyword trends
+ trend_analysis = self._analyze_keyword_trends(keywords)
+
+ self.progress.update({'current_step': 'Analyzing search intent'})
+
+ # Analyze search intent
+ intent_analysis = self._analyze_search_intent(keywords)
+
+ self.progress.update({'current_step': 'Generating keyword insights'})
+
+ # Generate keyword insights
+ insights = self._generate_keyword_insights(keywords, trend_analysis, intent_analysis)
+
+ self.progress.update({'current_step': 'Creating visual representation'})
+
+ # Generate visual representation
+ visualization = self._generate_keyword_visualization(keywords, insights)
+
+ self.progress.update({'status': 'completed', 'current_step': 'Completed keyword analysis'})
+
+ return {
+ 'keyword_trends': trend_analysis,
+ 'search_intent': intent_analysis,
+ 'keyword_insights': insights,
+ 'ai_analysis': ai_analysis,
+ 'visualization': visualization
+ }
+
+ except Exception as e:
+ if self.progress.get_progress().get('status') == 'in_progress':
+ self.progress.update({'status': 'error', 'current_step': 'Error in keyword analysis', 'details': str(e)})
+ st.error(f"Error in AI keyword analysis: {str(e)}")
+ return {
+ 'error': str(e),
+ 'keyword_trends': {},
+ 'search_intent': {},
+ 'keyword_insights': {},
+ 'ai_analysis': {},
+ 'visualization': None
+ }
+
+ def analyze_recommendations(self, data: Dict[str, Any]) -> Dict[str, Any]:
+ """
+ Analyze recommendations using AI techniques.
+
+ Args:
+ data: Dictionary containing recommendations and analysis
+
+ Returns:
+ Dictionary containing AI-enhanced recommendation analysis
+ """
+ try:
+ self.progress.update({'status': 'in_progress', 'current_step': 'Analyzing recommendation impact'})
+
+ recommendations = data.get('recommendations', {})
+ analysis = data.get('analysis', {})
+
+ # Generate AI prompt for recommendation analysis
+ prompt = f"""
+ Analyze the following recommendations and provide implementation insights:
+
+ Recommendations: {recommendations}
+ Analysis: {analysis}
+
+ Please provide a comprehensive analysis covering:
+ 1. Implementation Feasibility
+ - Resource requirements
+ - Technical complexity
+ - Timeline estimates
+ - Risk assessment
+
+ 2. Expected Impact
+ - SEO improvements
+ - User engagement
+ - Content quality
+ - Business value
+
+ 3. Implementation Strategy
+ - Priority order
+ - Dependencies
+ - Success metrics
+ - Monitoring plan
+
+ 4. Risk Management
+ - Potential challenges
+ - Mitigation strategies
+ - Contingency plans
+ - Success criteria
+
+ Format your response in a clear, structured manner with specific examples and actionable guidance.
+ """
+
+ self.progress.update({'current_step': 'Assessing expected impact'})
+
+ # Get AI analysis with custom system prompt
+ ai_analysis = llm_text_gen(prompt, system_prompt=self.system_prompts['recommendation_analysis'])
+
+ self.progress.update({'current_step': 'Analyzing resource requirements'})
+
+ # Analyze recommendation impact
+ impact_analysis = self._analyze_recommendation_impact(recommendations, analysis)
+
+ self.progress.update({'current_step': 'Generating strategic insights'})
+
+ # Generate strategic insights
+ insights = self._generate_strategic_insights(recommendations, analysis, impact_analysis)
+
+ self.progress.update({'current_step': 'Creating visual representation'})
+
+ # Generate visual representation
+ visualization = self._generate_recommendation_visualization(recommendations, insights)
+
+ self.progress.update({'status': 'completed', 'current_step': 'Completed recommendation analysis'})
+
+ return {
+ 'impact_analysis': impact_analysis,
+ 'strategic_insights': insights,
+ 'ai_analysis': ai_analysis,
+ 'visualization': visualization
+ }
+
+ except Exception as e:
+ if self.progress.get_progress().get('status') == 'in_progress':
+ self.progress.update({'status': 'error', 'current_step': 'Error in recommendation analysis', 'details': str(e)})
+ st.error(f"Error in AI recommendation analysis: {str(e)}")
+ return {
+ 'error': str(e),
+ 'impact_analysis': {},
+ 'strategic_insights': {},
+ 'ai_analysis': {},
+ 'visualization': None
+ }
+
+ def _analyze_content_metrics(self, content: Dict[str, Any]) -> Dict[str, Any]:
+ """Analyze content metrics using AI."""
+ prompt = f"""
+ Analyze the following content and provide detailed metrics:
+
+ Content: {content}
+
+ Please provide:
+ 1. Content Quality Metrics
+ - Structure score
+ - Readability score
+ - Engagement score
+ - SEO score
+
+ 2. Technical Metrics
+ - Content length
+ - Internal linking
+ - Meta tags
+ - Technical elements
+
+ 3. Performance Indicators
+ - User engagement potential
+ - SEO effectiveness
+ - Content value
+ - Improvement areas
+
+ Format your response in a clear, structured manner with specific metrics and scores.
+ """
+
+ try:
+ metrics = llm_text_gen(prompt, system_prompt=self.system_prompts['content_analysis'])
+ return metrics
+ except Exception as e:
+ st.error(f"Error analyzing content metrics: {str(e)}")
+ return {
+ 'quality_score': 0,
+ 'readability_score': 0,
+ 'engagement_score': 0,
+ 'seo_score': 0
+ }
+
+ def _analyze_content_evolution(self, content: Dict[str, Any]) -> Dict[str, Any]:
+ """Analyze content evolution using AI."""
+ prompt = f"""
+ Analyze the following content evolution:
+
+ Content: {content}
+
+ Please provide:
+ 1. Timeline Analysis
+ - Content development stages
+ - Key milestones
+ - Evolution patterns
+ - Growth indicators
+
+ 2. Performance Metrics
+ - Depth scores over time
+ - Coverage scores over time
+ - Engagement trends
+ - SEO performance
+
+ 3. Evolution Insights
+ - Content improvements
+ - Strategy changes
+ - Success factors
+ - Future opportunities
+
+ Format your response in a clear, structured manner with specific metrics and insights.
+ """
+
+ try:
+ evolution = llm_text_gen(prompt, system_prompt=self.system_prompts['content_analysis'])
+ return evolution
+ except Exception as e:
+ st.error(f"Error analyzing content evolution: {str(e)}")
+ return {
+ 'timeline': [],
+ 'depth_scores': [],
+ 'coverage_scores': []
+ }
+
+ def _analyze_topic_trends(self, content: Dict[str, Any], industry: str) -> Dict[str, Any]:
+ """Analyze topic trends using AI."""
+ prompt = f"""
+ Analyze topic trends for the following content and industry:
+
+ Content: {content}
+ Industry: {industry}
+
+ Please provide:
+ 1. Topic Analysis
+ - Current topic coverage
+ - Emerging topics
+ - Declining topics
+ - Industry alignment
+
+ 2. Trend Matrix
+ - Topic popularity
+ - Growth potential
+ - Competition level
+ - Implementation priority
+
+ 3. Strategic Insights
+ - Content opportunities
+ - Topic gaps
+ - Industry trends
+ - Action items
+
+ Format your response in a clear, structured manner with specific metrics and recommendations.
+ """
+
+ try:
+ trends = llm_text_gen(prompt, system_prompt=self.system_prompts['content_analysis'])
+ return trends
+ except Exception as e:
+ st.error(f"Error analyzing topic trends: {str(e)}")
+ return {
+ 'trend_matrix': []
+ }
+
+ def _analyze_performance_trends(self, content: Dict[str, Any]) -> Dict[str, Any]:
+ """Analyze performance trends using AI."""
+ prompt = f"""
+ Analyze performance trends for the following content:
+
+ Content: {content}
+
+ Please provide:
+ 1. Engagement Metrics
+ - User interaction
+ - Time on page
+ - Bounce rate
+ - Conversion rate
+
+ 2. Content Performance
+ - Traffic trends
+ - Engagement patterns
+ - Conversion metrics
+ - ROI indicators
+
+ 3. Improvement Areas
+ - Performance gaps
+ - Optimization opportunities
+ - Success factors
+ - Action items
+
+ Format your response in a clear, structured manner with specific metrics and recommendations.
+ """
+
+ try:
+ trends = llm_text_gen(prompt, system_prompt=self.system_prompts['content_analysis'])
+ return trends
+ except Exception as e:
+ st.error(f"Error analyzing performance trends: {str(e)}")
+ return {
+ 'engagement': {},
+ 'conversion': {}
+ }
+
+ def _analyze_competitor_trends(self, competitors: List[str], analysis: Dict[str, Any]) -> Dict[str, Any]:
+ """Analyze competitor trends using AI."""
+ prompt = f"""
+ Analyze competitor trends:
+
+ Competitors: {competitors}
+ Analysis: {analysis}
+
+ Please provide:
+ 1. Market Position
+ - Market share
+ - Growth trends
+ - Competitive advantages
+ - Market dynamics
+
+ 2. Strategy Analysis
+ - Content approach
+ - Marketing tactics
+ - User engagement
+ - Success factors
+
+ 3. Competitive Insights
+ - Market opportunities
+ - Threat assessment
+ - Strategic gaps
+ - Action items
+
+ Format your response in a clear, structured manner with specific metrics and recommendations.
+ """
+
+ try:
+ trends = llm_text_gen(prompt, system_prompt=self.system_prompts['competitor_analysis'])
+ return trends
+ except Exception as e:
+ st.error(f"Error analyzing competitor trends: {str(e)}")
+ return {}
+
+ def _analyze_keyword_trends(self, keywords: Dict[str, Any]) -> Dict[str, Any]:
+ """Analyze keyword trends using AI."""
+ prompt = f"""
+ Analyze keyword trends:
+
+ Keywords: {keywords}
+
+ Please provide:
+ 1. Performance Metrics
+ - Search volume
+ - Competition level
+ - Difficulty score
+ - Trend direction
+
+ 2. Opportunity Analysis
+ - Growth potential
+ - Competition gaps
+ - Content needs
+ - Implementation priority
+
+ 3. Strategic Insights
+ - Keyword opportunities
+ - Content strategy
+ - Implementation steps
+ - Success metrics
+
+ Format your response in a clear, structured manner with specific metrics and recommendations.
+ """
+
+ try:
+ trends = llm_text_gen(prompt, system_prompt=self.system_prompts['keyword_analysis'])
+ return trends
+ except Exception as e:
+ st.error(f"Error analyzing keyword trends: {str(e)}")
+ return {}
+
+ def _analyze_search_intent(self, keywords: Dict[str, Any]) -> Dict[str, Any]:
+ """Analyze search intent using AI."""
+ prompt = f"""
+ Analyze search intent:
+
+ Keywords: {keywords}
+
+ Please provide:
+ 1. Intent Analysis
+ - User intent types
+ - Content needs
+ - User journey
+ - Conversion potential
+
+ 2. Content Strategy
+ - Format recommendations
+ - Topic suggestions
+ - Implementation approach
+ - Success metrics
+
+ 3. Strategic Insights
+ - Content opportunities
+ - User engagement
+ - Conversion optimization
+ - Action items
+
+ Format your response in a clear, structured manner with specific metrics and recommendations.
+ """
+
+ try:
+ intent = llm_text_gen(prompt, system_prompt=self.system_prompts['keyword_analysis'])
+ return intent
+ except Exception as e:
+ st.error(f"Error analyzing search intent: {str(e)}")
+ return {
+ 'summary': []
+ }
+
+ def _analyze_recommendation_impact(self, recommendations: Dict[str, Any], analysis: Dict[str, Any]) -> Dict[str, Any]:
+ """Analyze recommendation impact using AI."""
+ prompt = f"""
+ Analyze recommendation impact:
+
+ Recommendations: {recommendations}
+ Analysis: {analysis}
+
+ Please provide:
+ 1. Impact Assessment
+ - SEO improvements
+ - User engagement
+ - Content quality
+ - Business value
+
+ 2. Implementation Analysis
+ - Resource requirements
+ - Technical complexity
+ - Timeline estimates
+ - Risk assessment
+
+ 3. Strategic Insights
+ - Priority order
+ - Dependencies
+ - Success metrics
+ - Monitoring plan
+
+ Format your response in a clear, structured manner with specific metrics and recommendations.
+ """
+
+ try:
+ impact = llm_text_gen(prompt, system_prompt=self.system_prompts['recommendation_analysis'])
+ return impact
+ except Exception as e:
+ st.error(f"Error analyzing recommendation impact: {str(e)}")
+ return {
+ 'seo': {},
+ 'engagement': {}
+ }
+
+ def _generate_content_insights(self, metrics: Dict[str, Any], evolution: Dict[str, Any], trends: Dict[str, Any], performance: Dict[str, Any]) -> Dict[str, Any]:
+ """Generate content insights using AI."""
+ prompt = f"""
+ Generate content insights:
+
+ Metrics: {metrics}
+ Evolution: {evolution}
+ Trends: {trends}
+ Performance: {performance}
+
+ Please provide:
+ 1. Quality Insights
+ - Content strengths
+ - Improvement areas
+ - Best practices
+ - Action items
+
+ 2. Engagement Insights
+ - User behavior
+ - Interaction patterns
+ - Conversion factors
+ - Optimization opportunities
+
+ 3. SEO Insights
+ - Technical optimization
+ - Content optimization
+ - Performance factors
+ - Implementation steps
+
+ Format your response in a clear, structured manner with specific insights and recommendations.
+ """
+
+ try:
+ insights = llm_text_gen(prompt, system_prompt=self.system_prompts['content_analysis'])
+ return insights
+ except Exception as e:
+ st.error(f"Error generating content insights: {str(e)}")
+ return {
+ 'quality': [],
+ 'engagement': [],
+ 'seo': []
+ }
+
+ def _generate_competitive_insights(self, competitors: List[str], analysis: Dict[str, Any], trends: Dict[str, Any]) -> Dict[str, Any]:
+ """Generate competitive insights using AI."""
+ prompt = f"""
+ Generate competitive insights:
+
+ Competitors: {competitors}
+ Analysis: {analysis}
+ Trends: {trends}
+
+ Please provide:
+ 1. Content Strategy
+ - Market position
+ - Competitive advantages
+ - Content gaps
+ - Opportunities
+
+ 2. Market Insights
+ - Industry trends
+ - User preferences
+ - Market dynamics
+ - Growth potential
+
+ 3. Strategic Recommendations
+ - Content improvements
+ - Market positioning
+ - Implementation steps
+ - Success metrics
+
+ Format your response in a clear, structured manner with specific insights and recommendations.
+ """
+
+ try:
+ insights = llm_text_gen(prompt, system_prompt=self.system_prompts['competitor_analysis'])
+ return insights
+ except Exception as e:
+ st.error(f"Error generating competitive insights: {str(e)}")
+ return {
+ 'content': [],
+ 'strategy': []
+ }
+
+ def _generate_keyword_insights(self, keywords: Dict[str, Any], trends: Dict[str, Any], intent: Dict[str, Any]) -> Dict[str, Any]:
+ """Generate keyword insights using AI."""
+ prompt = f"""
+ Generate keyword insights:
+
+ Keywords: {keywords}
+ Trends: {trends}
+ Intent: {intent}
+
+ Please provide:
+ 1. Opportunity Analysis
+ - Keyword potential
+ - Content needs
+ - Implementation priority
+ - Success metrics
+
+ 2. Content Strategy
+ - Topic clusters
+ - Format recommendations
+ - Implementation approach
+ - Performance tracking
+
+ 3. Strategic Recommendations
+ - Keyword targeting
+ - Content development
+ - Implementation steps
+ - Success metrics
+
+ Format your response in a clear, structured manner with specific insights and recommendations.
+ """
+
+ try:
+ insights = llm_text_gen(prompt, system_prompt=self.system_prompts['keyword_analysis'])
+ return insights
+ except Exception as e:
+ st.error(f"Error generating keyword insights: {str(e)}")
+ return {
+ 'opportunities': [],
+ 'strategy': []
+ }
+
+ def _generate_strategic_insights(self, recommendations: Dict[str, Any], analysis: Dict[str, Any], impact: Dict[str, Any]) -> Dict[str, Any]:
+ """Generate strategic insights using AI."""
+ prompt = f"""
+ Generate strategic insights:
+
+ Recommendations: {recommendations}
+ Analysis: {analysis}
+ Impact: {impact}
+
+ Please provide:
+ 1. Content Strategy
+ - Implementation approach
+ - Resource requirements
+ - Timeline estimates
+ - Success metrics
+
+ 2. Technical Strategy
+ - Technical requirements
+ - Implementation steps
+ - Risk assessment
+ - Monitoring plan
+
+ 3. Strategic Recommendations
+ - Priority order
+ - Dependencies
+ - Success criteria
+ - Action items
+
+ Format your response in a clear, structured manner with specific insights and recommendations.
+ """
+
+ try:
+ insights = llm_text_gen(prompt, system_prompt=self.system_prompts['recommendation_analysis'])
+ return insights
+ except Exception as e:
+ st.error(f"Error generating strategic insights: {str(e)}")
+ return {
+ 'content': [],
+ 'technical': []
+ }
+
+ def _generate_content_visualization(self, content: Dict[str, Any], insights: Dict[str, Any]) -> str:
+ """Generate visual representation of content analysis."""
+ try:
+ # Create a prompt for image generation
+ prompt = f"""
+ Create a visual representation of content analysis with the following insights:
+
+ Content Quality: {insights.get('quality', [])}
+ Engagement Metrics: {insights.get('engagement', [])}
+ SEO Performance: {insights.get('seo', [])}
+
+ The image should be professional, data-driven, and easy to understand.
+ """
+
+ # Generate image
+ image_path = generate_image(
+ user_prompt=prompt,
+ title="Content Analysis Visualization",
+ description="Visual representation of content analysis insights",
+ aspect_ratio="16:9"
+ )
+
+ return image_path
+
+ except Exception as e:
+ st.error(f"Error generating content visualization: {str(e)}")
+ return None
+
+ def _generate_competitor_visualization(self, competitors: List[str], insights: Dict[str, Any]) -> str:
+ """Generate visual representation of competitor analysis."""
+ try:
+ # Create a prompt for image generation
+ prompt = f"""
+ Create a visual representation of competitor analysis with the following insights:
+
+ Market Position: {insights.get('strategy', [])}
+ Competitive Advantages: {insights.get('content', [])}
+
+ The image should be professional, data-driven, and easy to understand.
+ """
+
+ # Generate image
+ image_path = generate_image(
+ user_prompt=prompt,
+ title="Competitor Analysis Visualization",
+ description="Visual representation of competitor analysis insights",
+ aspect_ratio="16:9"
+ )
+
+ return image_path
+
+ except Exception as e:
+ st.error(f"Error generating competitor visualization: {str(e)}")
+ return None
+
+ def _generate_keyword_visualization(self, keywords: Dict[str, Any], insights: Dict[str, Any]) -> str:
+ """Generate visual representation of keyword analysis."""
+ try:
+ # Create a prompt for image generation
+ prompt = f"""
+ Create a visual representation of keyword analysis with the following insights:
+
+ Keyword Opportunities: {insights.get('opportunities', [])}
+ Content Strategy: {insights.get('strategy', [])}
+
+ The image should be professional, data-driven, and easy to understand.
+ """
+
+ # Generate image
+ image_path = generate_image(
+ user_prompt=prompt,
+ title="Keyword Analysis Visualization",
+ description="Visual representation of keyword analysis insights",
+ aspect_ratio="16:9"
+ )
+
+ return image_path
+
+ except Exception as e:
+ st.error(f"Error generating keyword visualization: {str(e)}")
+ return None
+
+ def _generate_recommendation_visualization(self, recommendations: Dict[str, Any], insights: Dict[str, Any]) -> str:
+ """Generate visual representation of recommendation analysis."""
+ try:
+ # Create a prompt for image generation
+ prompt = f"""
+ Create a visual representation of recommendation analysis with the following insights:
+
+ Content Strategy: {insights.get('content', [])}
+ Technical Strategy: {insights.get('technical', [])}
+
+ The image should be professional, data-driven, and easy to understand.
+ """
+
+ # Generate image
+ image_path = generate_image(
+ user_prompt=prompt,
+ title="Recommendation Analysis Visualization",
+ description="Visual representation of recommendation analysis insights",
+ aspect_ratio="16:9"
+ )
+
+ return image_path
+
+ except Exception as e:
+ st.error(f"Error generating recommendation visualization: {str(e)}")
+ return None
\ No newline at end of file
diff --git a/lib/ai_seo_tools/content_gap_analysis/utils/content_parser.py b/lib/ai_seo_tools/content_gap_analysis/utils/content_parser.py
new file mode 100644
index 00000000..0a0af660
--- /dev/null
+++ b/lib/ai_seo_tools/content_gap_analysis/utils/content_parser.py
@@ -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
+ }
\ No newline at end of file
diff --git a/lib/ai_seo_tools/content_gap_analysis/utils/data_collector.py b/lib/ai_seo_tools/content_gap_analysis/utils/data_collector.py
new file mode 100644
index 00000000..5d302cdb
--- /dev/null
+++ b/lib/ai_seo_tools/content_gap_analysis/utils/data_collector.py
@@ -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
\ No newline at end of file
diff --git a/lib/ai_seo_tools/content_gap_analysis/utils/seo_analyzer.py b/lib/ai_seo_tools/content_gap_analysis/utils/seo_analyzer.py
new file mode 100644
index 00000000..df4e8e6b
--- /dev/null
+++ b/lib/ai_seo_tools/content_gap_analysis/utils/seo_analyzer.py
@@ -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
+ }
\ No newline at end of file
diff --git a/lib/ai_seo_tools/content_gap_analysis/utils/storage.py b/lib/ai_seo_tools/content_gap_analysis/utils/storage.py
new file mode 100644
index 00000000..bc05cc8c
--- /dev/null
+++ b/lib/ai_seo_tools/content_gap_analysis/utils/storage.py
@@ -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
\ No newline at end of file
diff --git a/lib/ai_seo_tools/content_gap_analysis/website_analyzer.py b/lib/ai_seo_tools/content_gap_analysis/website_analyzer.py
new file mode 100644
index 00000000..8b194bd9
--- /dev/null
+++ b/lib/ai_seo_tools/content_gap_analysis/website_analyzer.py
@@ -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="{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {message} "
+)
+
+# 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 []
\ No newline at end of file
diff --git a/lib/ai_seo_tools/content_title_generator.py b/lib/ai_seo_tools/content_title_generator.py
index 26dad8b9..ee1ef0a0 100644
--- a/lib/ai_seo_tools/content_title_generator.py
+++ b/lib/ai_seo_tools/content_title_generator.py
@@ -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="{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {message} "
+)
- # 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):
diff --git a/lib/ai_writers/ai_facebook_writer/facebook_ai_writer.py b/lib/ai_writers/ai_facebook_writer/facebook_ai_writer.py
index c0cd6d51..1982b7ff 100644
--- a/lib/ai_writers/ai_facebook_writer/facebook_ai_writer.py
+++ b/lib/ai_writers/ai_facebook_writer/facebook_ai_writer.py
@@ -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()
diff --git a/lib/ai_writers/ai_finance_report_generator/README.md b/lib/ai_writers/ai_finance_report_generator/README.md
new file mode 100644
index 00000000..554e9ba2
--- /dev/null
+++ b/lib/ai_writers/ai_finance_report_generator/README.md
@@ -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.
\ No newline at end of file
diff --git a/lib/ai_writers/ai_finance_report_generator/ai_financial_dashboard.py b/lib/ai_writers/ai_finance_report_generator/ai_financial_dashboard.py
new file mode 100644
index 00000000..b08b706f
--- /dev/null
+++ b/lib/ai_writers/ai_finance_report_generator/ai_financial_dashboard.py
@@ -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} |{file}:{line}:{function} | {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()
\ No newline at end of file
diff --git a/lib/ai_writers/ai_finance_report_generator/reports/README.md b/lib/ai_writers/ai_finance_report_generator/reports/README.md
new file mode 100644
index 00000000..6fc26864
--- /dev/null
+++ b/lib/ai_writers/ai_finance_report_generator/reports/README.md
@@ -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.
\ No newline at end of file
diff --git a/lib/ai_writers/ai_finance_report_generator/reports/fundamental_analysis/__init__.py b/lib/ai_writers/ai_finance_report_generator/reports/fundamental_analysis/__init__.py
new file mode 100644
index 00000000..af152a18
--- /dev/null
+++ b/lib/ai_writers/ai_finance_report_generator/reports/fundamental_analysis/__init__.py
@@ -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"
+ }
\ No newline at end of file
diff --git a/lib/ai_writers/ai_finance_report_generator/reports/market_research/__init__.py b/lib/ai_writers/ai_finance_report_generator/reports/market_research/__init__.py
new file mode 100644
index 00000000..4e276969
--- /dev/null
+++ b/lib/ai_writers/ai_finance_report_generator/reports/market_research/__init__.py
@@ -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"
+ }
\ No newline at end of file
diff --git a/lib/ai_writers/ai_finance_report_generator/reports/news_analysis/__init__.py b/lib/ai_writers/ai_finance_report_generator/reports/news_analysis/__init__.py
new file mode 100644
index 00000000..8006c8da
--- /dev/null
+++ b/lib/ai_writers/ai_finance_report_generator/reports/news_analysis/__init__.py
@@ -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"
+ }
\ No newline at end of file
diff --git a/lib/ai_writers/ai_finance_report_generator/reports/options_analysis/__init__.py b/lib/ai_writers/ai_finance_report_generator/reports/options_analysis/__init__.py
new file mode 100644
index 00000000..89eddb7a
--- /dev/null
+++ b/lib/ai_writers/ai_finance_report_generator/reports/options_analysis/__init__.py
@@ -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"
+ }
\ No newline at end of file
diff --git a/lib/ai_writers/ai_finance_report_generator/reports/portfolio_analysis/__init__.py b/lib/ai_writers/ai_finance_report_generator/reports/portfolio_analysis/__init__.py
new file mode 100644
index 00000000..1f7d5e88
--- /dev/null
+++ b/lib/ai_writers/ai_finance_report_generator/reports/portfolio_analysis/__init__.py
@@ -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"
+ }
\ No newline at end of file
diff --git a/lib/ai_writers/ai_finance_report_generator/reports/technical_analysis/__init__.py b/lib/ai_writers/ai_finance_report_generator/reports/technical_analysis/__init__.py
new file mode 100644
index 00000000..8cc609d9
--- /dev/null
+++ b/lib/ai_writers/ai_finance_report_generator/reports/technical_analysis/__init__.py
@@ -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
\ No newline at end of file
diff --git a/lib/ai_writers/ai_finance_report_generator/utils/__init__.py b/lib/ai_writers/ai_finance_report_generator/utils/__init__.py
new file mode 100644
index 00000000..285be4bb
--- /dev/null
+++ b/lib/ai_writers/ai_finance_report_generator/utils/__init__.py
@@ -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)
+ }
\ No newline at end of file
diff --git a/lib/ai_writers/ai_finance_report_generator/utils/storage.py b/lib/ai_writers/ai_finance_report_generator/utils/storage.py
new file mode 100644
index 00000000..52e81b1e
--- /dev/null
+++ b/lib/ai_writers/ai_finance_report_generator/utils/storage.py
@@ -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)
\ No newline at end of file
diff --git a/lib/ai_writers/ai_financial_writer.py b/lib/ai_writers/ai_financial_writer.py
deleted file mode 100644
index 70ae8011..00000000
--- a/lib/ai_writers/ai_financial_writer.py
+++ /dev/null
@@ -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} |{file}:{line}:{function} | {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)
diff --git a/lib/ai_writers/twitter_writers/tweet_generator/smart_tweet_generator.py b/lib/ai_writers/twitter_writers/tweet_generator/smart_tweet_generator.py
index 5627b349..7e36d792 100644
--- a/lib/ai_writers/twitter_writers/tweet_generator/smart_tweet_generator.py
+++ b/lib/ai_writers/twitter_writers/tweet_generator/smart_tweet_generator.py
@@ -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"""
-
-
Tweet Variation {index + 1}
-
{tweet['text']}
-
-
- Score: {tweet['engagement_score']}%
-
-
-
- """, 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()
\ No newline at end of file
diff --git a/lib/ai_writers/twitter_writers/twitter_dashboard.py b/lib/ai_writers/twitter_writers/twitter_dashboard.py
index 3471ac87..9bebb9c7 100644
--- a/lib/ai_writers/twitter_writers/twitter_dashboard.py
+++ b/lib/ai_writers/twitter_writers/twitter_dashboard.py
@@ -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'''
-
- '''
+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"""
-
-
{feature['icon']} {feature['name']}
-
{feature['description']}
-
- {feature['status'].title()}
-
-
- """, 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("""
-
-
🐦 Twitter AI Writer
-
Your all-in-one Twitter content creation and management platform.
- Harness the power of AI to enhance your Twitter marketing strategy.
-
- """, 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("🚀 Quick Actions ", 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("""
-
-
Need assistance? We're here to help!
-
-
- """, 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()
\ No newline at end of file
diff --git a/lib/ai_writers/twitter_writers/twitter_streamlit_ui/README.md b/lib/ai_writers/twitter_writers/twitter_streamlit_ui/README.md
new file mode 100644
index 00000000..1f3878d5
--- /dev/null
+++ b/lib/ai_writers/twitter_writers/twitter_streamlit_ui/README.md
@@ -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
\ No newline at end of file
diff --git a/lib/ai_writers/twitter_writers/twitter_streamlit_ui/__init__.py b/lib/ai_writers/twitter_writers/twitter_streamlit_ui/__init__.py
new file mode 100644
index 00000000..30496c43
--- /dev/null
+++ b/lib/ai_writers/twitter_writers/twitter_streamlit_ui/__init__.py
@@ -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"
+]
\ No newline at end of file
diff --git a/lib/ai_writers/twitter_writers/twitter_streamlit_ui/components/cards.py b/lib/ai_writers/twitter_writers/twitter_streamlit_ui/components/cards.py
new file mode 100644
index 00000000..4ffd3c54
--- /dev/null
+++ b/lib/ai_writers/twitter_writers/twitter_streamlit_ui/components/cards.py
@@ -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"""
+
+
+ {f'{self.icon} ' if self.icon else ''}
+
{self.title}
+
+
+ {self.description}
+
+ {f'
{self.status.title()} ' if self.status else ''}
+
+ """, 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"""
+
+
+ {self.icon}
+
{self.title}
+
+
+ {self.description}
+
+
{self.status.title()}
+
+ """, unsafe_allow_html=True)
+
+ if self.features:
+ for feature in self.features:
+ st.markdown(f"""
+
+
+ {feature["name"]} : {feature["description"]}
+
+
+ """, 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"""
+
+ """, 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}%")
\ No newline at end of file
diff --git a/lib/ai_writers/twitter_writers/twitter_streamlit_ui/components/forms.py b/lib/ai_writers/twitter_writers/twitter_streamlit_ui/components/forms.py
new file mode 100644
index 00000000..4d9429ac
--- /dev/null
+++ b/lib/ai_writers/twitter_writers/twitter_streamlit_ui/components/forms.py
@@ -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"""
+
+ """, 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"
+ )
\ No newline at end of file
diff --git a/lib/ai_writers/twitter_writers/twitter_streamlit_ui/components/navigation.py b/lib/ai_writers/twitter_writers/twitter_streamlit_ui/components/navigation.py
new file mode 100644
index 00000000..3f92350b
--- /dev/null
+++ b/lib/ai_writers/twitter_writers/twitter_streamlit_ui/components/navigation.py
@@ -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"""
+ {self.title}
+ """, unsafe_allow_html=True)
+
+ # Menu items
+ for item in self.menu_items:
+ st.markdown(f"""
+
+ """, 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"""
+
+ """)
+
+ st.markdown(f"""
+
+ """, 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'{item["icon"]} ' if item.get("icon") else ""
+ link_html = f'{item["label"]} ' if item.get("page") else f'{item["label"]} '
+ separator = f'/ ' if i < len(self.items) - 1 else ""
+
+ breadcrumb_items.append(f"""
+
+ {icon_html}
+ {link_html}
+
+ {separator}
+ """)
+
+ st.markdown(f"""
+
+ {''.join(breadcrumb_items)}
+
+ """, unsafe_allow_html=True)
\ No newline at end of file
diff --git a/lib/ai_writers/twitter_writers/twitter_streamlit_ui/dashboard.py b/lib/ai_writers/twitter_writers/twitter_streamlit_ui/dashboard.py
new file mode 100644
index 00000000..72a7fbbe
--- /dev/null
+++ b/lib/ai_writers/twitter_writers/twitter_streamlit_ui/dashboard.py
@@ -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()
\ No newline at end of file
diff --git a/lib/ai_writers/twitter_writers/twitter_streamlit_ui/styles/theme.py b/lib/ai_writers/twitter_writers/twitter_streamlit_ui/styles/theme.py
new file mode 100644
index 00000000..b96cfdf4
--- /dev/null
+++ b/lib/ai_writers/twitter_writers/twitter_streamlit_ui/styles/theme.py
@@ -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"", unsafe_allow_html=True)
\ No newline at end of file
diff --git a/lib/ai_writers/twitter_writers/twitter_streamlit_ui/utils/helpers.py b/lib/ai_writers/twitter_writers/twitter_streamlit_ui/utils/helpers.py
new file mode 100644
index 00000000..2c36ab09
--- /dev/null
+++ b/lib/ai_writers/twitter_writers/twitter_streamlit_ui/utils/helpers.py
@@ -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'', 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)}")
\ No newline at end of file
diff --git a/lib/ai_writers/youtube_writers/modules/channel_trailer_generator.py b/lib/ai_writers/youtube_writers/modules/channel_trailer_generator.py
new file mode 100644
index 00000000..15f53326
--- /dev/null
+++ b/lib/ai_writers/youtube_writers/modules/channel_trailer_generator.py
@@ -0,0 +1,1079 @@
+"""
+YouTube Channel Trailer Generator
+
+This module generates professional channel trailers for YouTube channels using AI.
+"""
+
+import streamlit as st
+import json
+from pathlib import Path
+from typing import Dict, List, Optional, Tuple, Any
+import sys
+import os
+from gtts import gTTS
+import tempfile
+import base64
+from io import BytesIO
+from datetime import datetime
+import logging
+
+# Add the project root to the Python path
+project_root = str(Path(__file__).parent.parent.parent.parent.parent)
+if project_root not in sys.path:
+ sys.path.append(project_root)
+
+from lib.gpt_providers.text_generation.main_text_generation import llm_text_gen
+from lib.gpt_providers.text_to_image_generation.main_generate_image_from_prompt import generate_image
+from lib.utils.save_to_file import save_to_file, save_audio, save_json, save_text
+
+# Configure logging
+logging.basicConfig(level=logging.INFO)
+logger = logging.getLogger(__name__)
+
+class TrailerGeneratorError(Exception):
+ """Custom exception for trailer generator errors."""
+ pass
+
+def validate_channel_info(channel_info: Dict) -> Tuple[bool, str]:
+ """Validate channel information before processing."""
+ required_fields = ['channel_name', 'channel_niche', 'target_audience', 'key_topics', 'unique_points']
+
+ # Check for missing required fields
+ for field in required_fields:
+ if not channel_info.get(field):
+ return False, f"Missing required field: {field}"
+
+ # Validate field lengths
+ if len(channel_info['channel_name']) < 3:
+ return False, "Channel name must be at least 3 characters long"
+
+ if len(channel_info['target_audience']) < 10:
+ return False, "Target audience description must be at least 10 characters long"
+
+ if len(channel_info['key_topics']) < 10:
+ return False, "Key topics must be at least 10 characters long"
+
+ if len(channel_info['unique_points']) < 10:
+ return False, "Unique selling points must be at least 10 characters long"
+
+ return True, ""
+
+def handle_voice_over_error(error: Exception) -> None:
+ """Handle voice-over generation errors gracefully."""
+ error_messages = {
+ "ConnectionError": "Unable to connect to the text-to-speech service. Please check your internet connection.",
+ "ValueError": "Invalid text input for voice-over generation.",
+ "Exception": f"An error occurred: {str(error)}"
+ }
+ error_type = type(error).__name__
+ error_message = error_messages.get(error_type, error_messages["Exception"])
+ logger.error(f"Voice-over generation error: {error_message}")
+ st.error(error_message)
+
+def validate_script(script: Dict) -> Tuple[bool, str]:
+ """Validate the generated script."""
+ required_sections = ['hook', 'introduction', 'showcase', 'value_proposition', 'call_to_action']
+
+ # Check for missing sections
+ for section in required_sections:
+ if section not in script:
+ return False, f"Missing required section: {section}"
+
+ # Validate section content
+ for section, content in script.items():
+ if not content.get('text'):
+ return False, f"Missing text in section: {section}"
+ if not content.get('duration'):
+ return False, f"Missing duration in section: {section}"
+
+ # Validate total duration
+ total_duration = sum(float(content['duration'].split()[0]) for content in script.values())
+ if total_duration > 90: # 90 seconds max
+ return False, f"Total duration ({total_duration}s) exceeds maximum allowed (90s)"
+
+ return True, ""
+
+def validate_narration(narration: Dict) -> Tuple[bool, str]:
+ """Validate the generated narration script."""
+ if not narration.get('narration'):
+ return False, "Missing narration content"
+
+ required_sections = ['hook', 'introduction', 'showcase', 'value_proposition', 'call_to_action']
+ for section in required_sections:
+ if section not in narration['narration']:
+ return False, f"Missing narration for section: {section}"
+
+ section_content = narration['narration'][section]
+ required_fields = ['text', 'voice_style', 'emotion', 'pauses', 'emphasis']
+ for field in required_fields:
+ if field not in section_content:
+ return False, f"Missing {field} in {section} narration"
+
+ return True, ""
+
+# Voice-over configuration
+VOICE_OPTIONS = {
+ "languages": {
+ "en": {
+ "name": "English",
+ "voices": ["en-US", "en-GB", "en-AU", "en-IN"],
+ "default_voice": "en-US"
+ },
+ "es": {
+ "name": "Spanish",
+ "voices": ["es-ES", "es-MX", "es-AR"],
+ "default_voice": "es-ES"
+ },
+ "fr": {
+ "name": "French",
+ "voices": ["fr-FR", "fr-CA"],
+ "default_voice": "fr-FR"
+ },
+ "de": {
+ "name": "German",
+ "voices": ["de-DE", "de-AT"],
+ "default_voice": "de-DE"
+ },
+ "ja": {
+ "name": "Japanese",
+ "voices": ["ja-JP"],
+ "default_voice": "ja-JP"
+ }
+ },
+ "voice_styles": {
+ "professional": {
+ "name": "Professional",
+ "description": "Clear, confident, and authoritative tone",
+ "pace": "moderate",
+ "pitch": "medium"
+ },
+ "casual": {
+ "name": "Casual",
+ "description": "Friendly and conversational tone",
+ "pace": "slightly_fast",
+ "pitch": "medium_high"
+ },
+ "enthusiastic": {
+ "name": "Enthusiastic",
+ "description": "Energetic and engaging tone",
+ "pace": "fast",
+ "pitch": "high"
+ },
+ "calm": {
+ "name": "Calm",
+ "description": "Relaxed and soothing tone",
+ "pace": "slow",
+ "pitch": "low"
+ },
+ "energetic": {
+ "name": "Energetic",
+ "description": "Dynamic and vibrant tone",
+ "pace": "fast",
+ "pitch": "medium_high"
+ }
+ },
+ "pacing_options": {
+ "very_slow": 0.5,
+ "slow": 0.75,
+ "moderate": 1.0,
+ "slightly_fast": 1.25,
+ "fast": 1.5
+ }
+}
+
+def get_voice_over_options() -> Dict:
+ """Get available voice-over options."""
+ return VOICE_OPTIONS
+
+def get_voice_style_settings(style: str) -> Dict:
+ """Get settings for a specific voice style."""
+ return VOICE_OPTIONS["voice_styles"].get(style, VOICE_OPTIONS["voice_styles"]["professional"])
+
+def adjust_text_for_voice_style(text: str, style: str) -> str:
+ """Adjust text to better match the selected voice style."""
+ style_settings = get_voice_style_settings(style)
+
+ # Add pauses and emphasis based on style
+ if style == "professional":
+ # Add strategic pauses for clarity
+ text = text.replace(".", ". [pause]")
+ text = text.replace("!", "! [pause]")
+ elif style == "casual":
+ # Add conversational markers
+ text = text.replace(".", "...")
+ elif style == "enthusiastic":
+ # Add emphasis markers
+ text = text.replace("!", "! [emphasis]")
+ elif style == "calm":
+ # Add longer pauses
+ text = text.replace(".", ". [long_pause]")
+ elif style == "energetic":
+ # Add dynamic emphasis
+ text = text.replace("!", "! [dynamic_emphasis]")
+
+ return text
+
+@st.cache_data(ttl=3600) # Cache for 1 hour
+def generate_voice_over(
+ text: str,
+ language: str = 'en',
+ voice_style: str = 'professional',
+ slow: bool = False
+) -> bytes:
+ """Generate voice-over audio using gTTS with enhanced options."""
+ try:
+ # Get language settings
+ lang_settings = VOICE_OPTIONS["languages"].get(language, VOICE_OPTIONS["languages"]["en"])
+ voice = lang_settings["default_voice"]
+
+ # Adjust text based on voice style
+ adjusted_text = adjust_text_for_voice_style(text, voice_style)
+
+ # Get style settings
+ style_settings = get_voice_style_settings(voice_style)
+
+ # Generate voice-over
+ tts = gTTS(
+ text=adjusted_text,
+ lang=voice,
+ slow=slow
+ )
+
+ audio_file = BytesIO()
+ tts.write_to_fp(audio_file)
+ audio_file.seek(0)
+ return audio_file.getvalue()
+ except Exception as e:
+ handle_voice_over_error(e)
+ return None
+
+def display_voice_over_options() -> Dict:
+ """Display voice-over options in the UI and return selected options."""
+ st.markdown("### 🎤 Voice-over Settings")
+
+ # Language selection
+ language = st.selectbox(
+ "Language",
+ options=list(VOICE_OPTIONS["languages"].keys()),
+ format_func=lambda x: VOICE_OPTIONS["languages"][x]["name"],
+ help="Select the language for your voice-over"
+ )
+
+ # Voice style selection
+ voice_style = st.selectbox(
+ "Voice Style",
+ options=list(VOICE_OPTIONS["voice_styles"].keys()),
+ format_func=lambda x: VOICE_OPTIONS["voice_styles"][x]["name"],
+ help="Select the style of voice-over"
+ )
+
+ # Display style description
+ style_info = VOICE_OPTIONS["voice_styles"][voice_style]
+ st.markdown(f"**Style Description:** {style_info['description']}")
+
+ # Pace selection
+ pace = st.select_slider(
+ "Speaking Pace",
+ options=list(VOICE_OPTIONS["pacing_options"].keys()),
+ value=style_info["pace"],
+ help="Adjust the speaking pace of the voice-over"
+ )
+
+ return {
+ "language": language,
+ "voice_style": voice_style,
+ "pace": pace
+ }
+
+@st.cache_data(ttl=3600) # Cache for 1 hour
+def generate_trailer_script(
+ channel_name: str,
+ channel_niche: str,
+ target_audience: str,
+ key_topics: str,
+ unique_points: str,
+ trailer_length: str
+) -> Dict:
+ """Generate the trailer script using GPT with caching."""
+ try:
+ prompt = f"""Create a professional and engaging YouTube channel trailer script for a {channel_niche} channel named '{channel_name}'.
+
+ Channel Details:
+ - Target Audience: {target_audience}
+ - Key Topics: {key_topics}
+ - Unique Selling Points: {unique_points}
+ - Desired Length: {trailer_length}
+
+ The script should follow this detailed structure:
+
+ 1. Hook (5-10 seconds):
+ - Start with a powerful question, statement, or visual hook
+ - Address the viewer's pain points or interests
+ - Create immediate curiosity
+ - Use dynamic language and emotional triggers
+ Visual Requirements:
+ - Dynamic opening shot or animation
+ - Text overlay with key hook phrase
+ - Background elements that match the channel theme
+ - Smooth camera movement or transition effects
+
+ 2. Channel Introduction (10-15 seconds):
+ - Clearly state the channel name
+ - Explain what makes this channel unique
+ - Establish credibility and expertise
+ - Use confident, engaging language
+ Visual Requirements:
+ - Channel logo reveal animation
+ - Brand colors and typography
+ - Professional backdrop or setting
+ - Subtle motion graphics
+
+ 3. Content Showcase (10-20 seconds):
+ - Highlight 2-3 key content types
+ - Show the value and benefits of watching
+ - Include specific examples of content
+ - Use dynamic transitions between topics
+ Visual Requirements:
+ - Split-screen or grid layout for content previews
+ - Thumbnail-style frames for each content type
+ - Dynamic transitions between content examples
+ - Overlay graphics showing key statistics or achievements
+
+ 4. Value Proposition (5-10 seconds):
+ - Clearly state what viewers will gain
+ - Emphasize unique benefits
+ - Address viewer's needs and desires
+ - Use compelling language
+ Visual Requirements:
+ - Benefit-focused graphics or icons
+ - Animated text highlights
+ - Background elements that reinforce the value
+ - Professional color scheme
+
+ 5. Call to Action (5-10 seconds):
+ - Clear subscription prompt
+ - Mention notification bell
+ - Create urgency or FOMO
+ - End with channel branding
+ Visual Requirements:
+ - Subscription button animation
+ - Notification bell icon
+ - Channel branding elements
+ - Final logo reveal
+
+ Additional Requirements:
+ - Keep language conversational and engaging
+ - Use active voice and present tense
+ - Include specific numbers and examples
+ - Maintain consistent tone throughout
+ - Ensure smooth transitions between sections
+ - Optimize for the selected duration ({trailer_length})
+
+ Format the response as a JSON with the following structure:
+ {{
+ "hook": {{
+ "text": "the hook text",
+ "duration": "estimated duration in seconds",
+ "visual_suggestions": {{
+ "main_visual": "primary visual element description",
+ "text_overlay": "text overlay style and content",
+ "background": "background elements and effects",
+ "transitions": ["transition effects"],
+ "color_scheme": "color palette suggestions"
+ }}
+ }},
+ "introduction": {{
+ "text": "the introduction text",
+ "duration": "estimated duration in seconds",
+ "visual_suggestions": {{
+ "logo_animation": "logo reveal animation style",
+ "typography": "text style and animation",
+ "background": "background elements",
+ "motion_graphics": ["motion graphic elements"],
+ "color_scheme": "color palette suggestions"
+ }}
+ }},
+ "showcase": {{
+ "text": "the showcase text",
+ "duration": "estimated duration in seconds",
+ "visual_suggestions": {{
+ "layout": "content showcase layout style",
+ "thumbnails": ["thumbnail style descriptions"],
+ "transitions": ["transition effects between content"],
+ "overlay_graphics": ["overlay elements"],
+ "color_scheme": "color palette suggestions"
+ }}
+ }},
+ "value_proposition": {{
+ "text": "the value proposition text",
+ "duration": "estimated duration in seconds",
+ "visual_suggestions": {{
+ "benefit_graphics": ["benefit-focused visual elements"],
+ "text_animation": "text animation style",
+ "background": "background elements",
+ "icons": ["icon suggestions"],
+ "color_scheme": "color palette suggestions"
+ }}
+ }},
+ "call_to_action": {{
+ "text": "the call to action text",
+ "duration": "estimated duration in seconds",
+ "visual_suggestions": {{
+ "cta_animation": "call-to-action animation style",
+ "button_design": "subscription button design",
+ "notification_icon": "notification bell design",
+ "branding": "final branding elements",
+ "color_scheme": "color palette suggestions"
+ }}
+ }},
+ "total_duration": "total estimated duration in seconds",
+ "notes": {{
+ "tone": "suggested tone and style",
+ "music_suggestions": ["suggested music types or moods"],
+ "transitions": ["suggested transition effects"],
+ "special_effects": ["suggested special effects"],
+ "production_tips": ["specific production recommendations"],
+ "visual_consistency": "guidelines for maintaining visual consistency",
+ "brand_guidelines": "specific brand implementation guidelines"
+ }}
+ }}
+
+ Ensure the script is optimized for the {trailer_length} format and maintains high engagement throughout.
+ """
+
+ response = get_gpt_response(prompt)
+ script = json.loads(response)
+
+ # Validate the generated script
+ is_valid, error_message = validate_script(script)
+ if not is_valid:
+ raise TrailerGeneratorError(f"Invalid script generated: {error_message}")
+
+ return script
+ except json.JSONDecodeError:
+ logger.error("Error parsing GPT response as JSON")
+ raise TrailerGeneratorError("Error generating script. Please try again.")
+ except Exception as e:
+ logger.error(f"Error generating script: {str(e)}")
+ raise TrailerGeneratorError(f"An error occurred: {str(e)}")
+
+@st.cache_data(ttl=3600) # Cache for 1 hour
+def generate_narration_script(script: Dict) -> Dict:
+ """Generate a natural-sounding narration script from the trailer script with caching."""
+ try:
+ prompt = f"""Convert this YouTube channel trailer script into a natural-sounding narration script.
+ The script should be engaging, conversational, and optimized for voice-over delivery.
+
+ Original Script:
+ {json.dumps(script, indent=2)}
+
+ Requirements:
+ 1. Maintain the same structure and timing
+ 2. Add natural pauses and emphasis
+ 3. Include voice modulation suggestions
+ 4. Add emotional cues
+ 5. Make it sound conversational
+ 6. Include pronunciation guides for any technical terms
+ 7. Add emphasis markers for important points
+
+ Format the response as a JSON with the following structure:
+ {{
+ "narration": {{
+ "hook": {{
+ "text": "narration text with emphasis and pauses",
+ "voice_style": "suggested voice style",
+ "emotion": "suggested emotion",
+ "pauses": ["pause points"],
+ "emphasis": ["words to emphasize"]
+ }},
+ "introduction": {{
+ "text": "narration text with emphasis and pauses",
+ "voice_style": "suggested voice style",
+ "emotion": "suggested emotion",
+ "pauses": ["pause points"],
+ "emphasis": ["words to emphasize"]
+ }},
+ "showcase": {{
+ "text": "narration text with emphasis and pauses",
+ "voice_style": "suggested voice style",
+ "emotion": "suggested emotion",
+ "pauses": ["pause points"],
+ "emphasis": ["words to emphasize"]
+ }},
+ "value_proposition": {{
+ "text": "narration text with emphasis and pauses",
+ "voice_style": "suggested voice style",
+ "emotion": "suggested emotion",
+ "pauses": ["pause points"],
+ "emphasis": ["words to emphasize"]
+ }},
+ "call_to_action": {{
+ "text": "narration text with emphasis and pauses",
+ "voice_style": "suggested voice style",
+ "emotion": "suggested emotion",
+ "pauses": ["pause points"],
+ "emphasis": ["words to emphasize"]
+ }}
+ }},
+ "voice_guidelines": {{
+ "overall_tone": "suggested overall tone",
+ "pace": "suggested speaking pace",
+ "energy_level": "suggested energy level",
+ "pronunciation_guide": {{
+ "term": "pronunciation"
+ }},
+ "special_instructions": ["special voice-over instructions"]
+ }}
+ }}
+ """
+
+ response = get_gpt_response(prompt)
+ narration = json.loads(response)
+
+ # Validate the generated narration
+ is_valid, error_message = validate_narration(narration)
+ if not is_valid:
+ raise TrailerGeneratorError(f"Invalid narration generated: {error_message}")
+
+ return narration
+ except json.JSONDecodeError:
+ logger.error("Error parsing GPT response as JSON")
+ raise TrailerGeneratorError("Error generating narration script. Please try again.")
+ except Exception as e:
+ logger.error(f"Error generating narration script: {str(e)}")
+ raise TrailerGeneratorError(f"An error occurred: {str(e)}")
+
+def update_session_state(key: str, value: Any) -> None:
+ """Update session state with feedback."""
+ st.session_state[key] = value
+ st.success(f"{key.replace('_', ' ').title()} updated successfully!")
+
+def write_yt_channel_trailer():
+ """Generate a YouTube channel trailer script and visual elements."""
+
+ st.title("🎥 YouTube Channel Trailer Generator")
+ st.markdown("Create an engaging channel trailer that converts visitors into subscribers.")
+
+ # Initialize session state for workflow
+ if 'current_step' not in st.session_state:
+ st.session_state.current_step = 1
+ if 'channel_info' not in st.session_state:
+ st.session_state.channel_info = {}
+ if 'script' not in st.session_state:
+ st.session_state.script = None
+ if 'visuals' not in st.session_state:
+ st.session_state.visuals = None
+ if 'editing_section' not in st.session_state:
+ st.session_state.editing_section = None
+ if 'narration' not in st.session_state:
+ st.session_state.narration = None
+ if 'voice_overs' not in st.session_state:
+ st.session_state.voice_overs = {}
+ if 'errors' not in st.session_state:
+ st.session_state.errors = []
+
+ # Progress bar
+ progress_text = {
+ 1: "Channel Information",
+ 2: "Script Generation",
+ 3: "Visual Elements",
+ 4: "Review & Edit",
+ 5: "Final Output"
+ }
+ st.progress((st.session_state.current_step - 1) / 4)
+ st.markdown(f"**Step {st.session_state.current_step}/5: {progress_text[st.session_state.current_step]}**")
+
+ # Display any errors
+ if st.session_state.errors:
+ for error in st.session_state.errors:
+ st.error(error)
+ st.session_state.errors = []
+
+ # Step 1: Channel Information
+ if st.session_state.current_step == 1:
+ with st.expander("Channel Information", expanded=True):
+ st.markdown("""
+ ### 📝 Basic Information
+ Let's start by gathering some basic information about your channel.
+ This will help us create a trailer that perfectly represents your brand.
+ """)
+
+ channel_name = st.text_input("Channel Name",
+ value=st.session_state.channel_info.get('channel_name', ''),
+ help="Enter your YouTube channel name")
+
+ channel_niche = st.text_input("Channel Niche/Category",
+ value=st.session_state.channel_info.get('channel_niche', ''),
+ help="e.g., Tech Reviews, Cooking, Gaming, etc.")
+
+ st.markdown("### 👥 Target Audience")
+ st.markdown("Describe who your content is for. Be specific about demographics, interests, and needs.")
+ target_audience = st.text_area("Target Audience",
+ value=st.session_state.channel_info.get('target_audience', ''),
+ help="Describe your target audience in detail")
+
+ st.markdown("### 📚 Content Types")
+ st.markdown("What kind of content do you create? List your main content types and topics.")
+ key_topics = st.text_area("Key Topics/Content Types",
+ value=st.session_state.channel_info.get('key_topics', ''),
+ help="List the main types of content you create")
+
+ st.markdown("### ✨ Unique Selling Points")
+ st.markdown("What makes your channel different? Highlight your unique features and value.")
+ unique_points = st.text_area("Unique Selling Points",
+ value=st.session_state.channel_info.get('unique_points', ''),
+ help="What makes your channel different?")
+
+ col1, col2 = st.columns(2)
+ with col1:
+ trailer_length = st.selectbox(
+ "Trailer Length",
+ ["30 seconds", "60 seconds", "90 seconds"],
+ index=["30 seconds", "60 seconds", "90 seconds"].index(
+ st.session_state.channel_info.get('trailer_length', "60 seconds")
+ ),
+ help="Choose the desired length of your channel trailer"
+ )
+ with col2:
+ brand_colors = st.color_picker(
+ "Brand Color",
+ value=st.session_state.channel_info.get('brand_colors', "#FF0000"),
+ help="Select your brand's primary color"
+ )
+
+ # Save channel info
+ st.session_state.channel_info = {
+ 'channel_name': channel_name,
+ 'channel_niche': channel_niche,
+ 'target_audience': target_audience,
+ 'key_topics': key_topics,
+ 'unique_points': unique_points,
+ 'trailer_length': trailer_length,
+ 'brand_colors': brand_colors
+ }
+
+ if st.button("Next: Generate Script"):
+ # Validate channel information
+ is_valid, error_message = validate_channel_info(st.session_state.channel_info)
+ if not is_valid:
+ st.session_state.errors.append(error_message)
+ st.rerun()
+ else:
+ st.session_state.current_step = 2
+ st.rerun()
+
+ # Step 2: Script Generation
+ elif st.session_state.current_step == 2:
+ st.markdown("### 📝 Script Generation")
+ st.markdown("""
+ We'll now generate a script for your channel trailer.
+ The script will be divided into key sections, each with specific timing and visual suggestions.
+ """)
+
+ if st.button("Generate Script"):
+ try:
+ with st.spinner("Generating your channel trailer script..."):
+ script = generate_trailer_script(
+ channel_name=st.session_state.channel_info['channel_name'],
+ channel_niche=st.session_state.channel_info['channel_niche'],
+ target_audience=st.session_state.channel_info['target_audience'],
+ key_topics=st.session_state.channel_info['key_topics'],
+ unique_points=st.session_state.channel_info['unique_points'],
+ trailer_length=st.session_state.channel_info['trailer_length']
+ )
+ update_session_state('script', script)
+
+ # Generate narration script
+ with st.spinner("Generating narration script..."):
+ narration = generate_narration_script(script)
+ update_session_state('narration', narration)
+ except TrailerGeneratorError as e:
+ st.session_state.errors.append(str(e))
+ st.rerun()
+
+ if st.session_state.script and st.session_state.narration:
+ # Display script sections with edit buttons and voice-over options
+ for section in ["hook", "introduction", "showcase", "value_proposition", "call_to_action"]:
+ with st.expander(f"{section.replace('_', ' ').title()}", expanded=True):
+ st.markdown(f"**Duration:** {st.session_state.script[section]['duration']}")
+ st.markdown(f"**Text:** {st.session_state.script[section]['text']}")
+
+ # Display narration details
+ st.markdown("### 🎤 Narration")
+ narration_section = st.session_state.narration['narration'][section]
+ st.markdown(f"**Voice Style:** {narration_section['voice_style']}")
+ st.markdown(f"**Emotion:** {narration_section['emotion']}")
+ st.markdown("**Emphasis Points:**")
+ for point in narration_section['emphasis']:
+ st.markdown(f"- {point}")
+
+ # Voice-over generation with enhanced options
+ st.markdown("### 🎙️ Generate Voice-over")
+ voice_options = display_voice_over_options()
+
+ if st.button(f"Generate Voice-over for {section.replace('_', ' ').title()}",
+ key=f"voice_{section}"):
+ with st.spinner(f"Generating voice-over for {section}..."):
+ audio_bytes = generate_voice_over(
+ text=narration_section['text'],
+ language=voice_options['language'],
+ voice_style=voice_options['voice_style'],
+ slow=voice_options['pace'] in ['very_slow', 'slow']
+ )
+ if audio_bytes:
+ st.session_state.voice_overs[section] = {
+ 'audio': audio_bytes,
+ 'options': voice_options
+ }
+ st.markdown(get_audio_player_html(audio_bytes), unsafe_allow_html=True)
+
+ # Display existing voice-over if available
+ if section in st.session_state.voice_overs:
+ st.markdown("### 🔊 Current Voice-over")
+ st.markdown(get_audio_player_html(st.session_state.voice_overs[section]['audio']),
+ unsafe_allow_html=True)
+
+ # Show current voice-over settings
+ current_options = st.session_state.voice_overs[section]['options']
+ st.markdown("**Current Settings:**")
+ st.markdown(f"- Language: {VOICE_OPTIONS['languages'][current_options['language']]['name']}")
+ st.markdown(f"- Voice Style: {VOICE_OPTIONS['voice_styles'][current_options['voice_style']]['name']}")
+ st.markdown(f"- Pace: {current_options['pace'].replace('_', ' ').title()}")
+
+ if st.button(f"Edit {section.replace('_', ' ').title()}", key=f"edit_{section}"):
+ st.session_state.editing_section = section
+ st.session_state.current_step = 4
+ st.rerun()
+
+ col1, col2 = st.columns(2)
+ with col1:
+ if st.button("Back to Channel Info"):
+ st.session_state.current_step = 1
+ st.rerun()
+ with col2:
+ if st.button("Next: Generate Visuals"):
+ st.session_state.current_step = 3
+ st.rerun()
+
+ # Step 3: Visual Elements
+ elif st.session_state.current_step == 3:
+ st.markdown("### 🎨 Visual Elements")
+ st.markdown("""
+ Let's create the visual elements for your channel trailer.
+ We'll generate a logo, background, and other visual assets that match your brand.
+ """)
+
+ if st.button("Generate Visuals"):
+ with st.spinner("Generating visual elements..."):
+ visuals = generate_trailer_visuals(
+ channel_name=st.session_state.channel_info['channel_name'],
+ channel_niche=st.session_state.channel_info['channel_niche'],
+ brand_color=st.session_state.channel_info['brand_colors']
+ )
+ st.session_state.visuals = visuals
+ st.success("Visual elements generated successfully!")
+
+ if st.session_state.visuals:
+ for visual in st.session_state.visuals:
+ with st.expander(f"{visual['type'].replace('_', ' ').title()}", expanded=True):
+ st.markdown(f"**Usage:** {visual['usage']}")
+ st.image(visual["image"], use_column_width=True)
+ if st.button(f"Regenerate {visual['type'].replace('_', ' ').title()}",
+ key=f"regen_{visual['type']}"):
+ with st.spinner(f"Regenerating {visual['type']}..."):
+ new_visual = generate_trailer_visuals(
+ channel_name=st.session_state.channel_info['channel_name'],
+ channel_niche=st.session_state.channel_info['channel_niche'],
+ brand_color=st.session_state.channel_info['brand_colors']
+ )
+ st.session_state.visuals = new_visual
+ st.rerun()
+
+ col1, col2 = st.columns(2)
+ with col1:
+ if st.button("Back to Script"):
+ st.session_state.current_step = 2
+ st.rerun()
+ with col2:
+ if st.button("Next: Review & Edit"):
+ st.session_state.current_step = 4
+ st.rerun()
+
+ # Step 4: Review & Edit
+ elif st.session_state.current_step == 4:
+ st.markdown("### ✍️ Review & Edit")
+
+ if st.session_state.editing_section:
+ st.markdown(f"### Editing {st.session_state.editing_section.replace('_', ' ').title()}")
+ section = st.session_state.script[st.session_state.editing_section]
+ narration_section = st.session_state.narration['narration'][st.session_state.editing_section]
+
+ edited_text = st.text_area("Edit Text", value=section['text'])
+ edited_duration = st.text_input("Edit Duration", value=section['duration'])
+
+ st.markdown("### 🎤 Narration Settings")
+ edited_narration = st.text_area("Edit Narration", value=narration_section['text'])
+ edited_voice_style = st.text_input("Voice Style", value=narration_section['voice_style'])
+ edited_emotion = st.text_input("Emotion", value=narration_section['emotion'])
+
+ if st.button("Save Changes"):
+ st.session_state.script[st.session_state.editing_section]['text'] = edited_text
+ st.session_state.script[st.session_state.editing_section]['duration'] = edited_duration
+ st.session_state.narration['narration'][st.session_state.editing_section]['text'] = edited_narration
+ st.session_state.narration['narration'][st.session_state.editing_section]['voice_style'] = edited_voice_style
+ st.session_state.narration['narration'][st.session_state.editing_section]['emotion'] = edited_emotion
+
+ # Regenerate voice-over for the edited section
+ if st.session_state.editing_section in st.session_state.voice_overs:
+ del st.session_state.voice_overs[st.session_state.editing_section]
+
+ st.session_state.editing_section = None
+ st.success("Changes saved successfully!")
+ st.rerun()
+
+ if st.button("Cancel Editing"):
+ st.session_state.editing_section = None
+ st.rerun()
+ else:
+ st.markdown("""
+ Review your channel trailer content. You can:
+ - Edit any section of the script
+ - Regenerate visual elements
+ - Download the final content
+ """)
+
+ # Display final preview
+ display_trailer_content(st.session_state.script, st.session_state.visuals)
+
+ col1, col2 = st.columns(2)
+ with col1:
+ if st.button("Back to Visuals"):
+ st.session_state.current_step = 3
+ st.rerun()
+ with col2:
+ if st.button("Finalize & Download"):
+ st.session_state.current_step = 5
+ st.rerun()
+
+ # Step 5: Final Output
+ elif st.session_state.current_step == 5:
+ st.markdown("### 🎉 Final Output")
+ st.success("Your channel trailer content is ready!")
+
+ # Display final content
+ display_trailer_content(st.session_state.script, st.session_state.visuals)
+
+ # Display voice-over guidelines
+ if st.session_state.narration:
+ st.markdown("### 🎤 Voice-over Guidelines")
+ guidelines = st.session_state.narration['voice_guidelines']
+ st.markdown(f"**Overall Tone:** {guidelines['overall_tone']}")
+ st.markdown(f"**Pace:** {guidelines['pace']}")
+ st.markdown(f"**Energy Level:** {guidelines['energy_level']}")
+
+ st.markdown("**Pronunciation Guide:**")
+ for term, pronunciation in guidelines['pronunciation_guide'].items():
+ st.markdown(f"- {term}: {pronunciation}")
+
+ st.markdown("**Special Instructions:**")
+ for instruction in guidelines['special_instructions']:
+ st.markdown(f"- {instruction}")
+
+ # Download options
+ st.markdown("### 💾 Download Options")
+ col1, col2, col3, col4 = st.columns(4)
+ with col1:
+ if st.button("Download Script"):
+ save_to_file(json.dumps(st.session_state.script, indent=2), "channel_trailer_script.json")
+ with col2:
+ if st.button("Download Narration"):
+ save_to_file(json.dumps(st.session_state.narration, indent=2), "channel_trailer_narration.json")
+ with col3:
+ if st.button("Download Voice-overs"):
+ # Save voice-overs logic here
+ pass
+ with col4:
+ if st.button("Start New Trailer"):
+ # Reset session state
+ for key in ['current_step', 'channel_info', 'script', 'visuals', 'editing_section',
+ 'narration', 'voice_overs']:
+ if key in st.session_state:
+ del st.session_state[key]
+ st.rerun()
+
+def generate_trailer_visuals(
+ channel_name: str,
+ channel_niche: str,
+ brand_color: str
+) -> List[Dict]:
+ """Generate visual elements for the trailer."""
+
+ # Generate channel logo concept
+ logo_prompt = f"""Create a professional logo for a {channel_niche} YouTube channel named '{channel_name}'.
+ Requirements:
+ - Simple and memorable design
+ - Works well in small sizes
+ - Incorporates brand color: {brand_color}
+ - Modern and clean style
+ - Suitable for animation
+ - Includes both icon and text elements
+ - Maintains readability at different sizes
+ """
+ logo_image = generate_image(logo_prompt)
+
+ # Generate background visuals
+ background_prompt = f"""Create a dynamic background for a {channel_niche} YouTube channel trailer.
+ Requirements:
+ - Matches the channel's theme and niche
+ - Incorporates brand color: {brand_color}
+ - Includes subtle motion elements
+ - Professional and modern design
+ - Suitable for text overlay
+ - Maintains visual hierarchy
+ - Works well with channel branding
+ """
+ background_image = generate_image(background_prompt)
+
+ # Generate thumbnail style previews
+ thumbnail_prompt = f"""Create a professional thumbnail style for a {channel_niche} YouTube channel.
+ Requirements:
+ - Eye-catching design
+ - Incorporates brand color: {brand_color}
+ - Includes space for text
+ - High contrast for visibility
+ - Modern and clean style
+ - Suitable for content preview
+ """
+ thumbnail_image = generate_image(thumbnail_prompt)
+
+ # Generate motion graphic elements
+ motion_prompt = f"""Create a set of motion graphic elements for a {channel_niche} YouTube channel.
+ Requirements:
+ - Modern and dynamic design
+ - Incorporates brand color: {brand_color}
+ - Suitable for transitions
+ - Clean and professional style
+ - Works well with channel branding
+ """
+ motion_image = generate_image(motion_prompt)
+
+ return [
+ {
+ "type": "logo",
+ "prompt": logo_prompt,
+ "image": logo_image,
+ "usage": "Channel branding and identification"
+ },
+ {
+ "type": "background",
+ "prompt": background_prompt,
+ "image": background_image,
+ "usage": "Main background and scene setting"
+ },
+ {
+ "type": "thumbnail",
+ "prompt": thumbnail_prompt,
+ "image": thumbnail_image,
+ "usage": "Content preview and showcase"
+ },
+ {
+ "type": "motion_graphics",
+ "prompt": motion_prompt,
+ "image": motion_image,
+ "usage": "Transitions and visual effects"
+ }
+ ]
+
+def display_trailer_content(script: Dict, visuals: List[Dict]):
+ """Display the generated trailer content."""
+
+ # Display script
+ st.markdown("### 📝 Trailer Script")
+
+ # Create tabs for different sections
+ script_tab, visuals_tab, production_tab = st.tabs(["Script", "Visuals", "Production Notes"])
+
+ with script_tab:
+ for section in ["hook", "introduction", "showcase", "value_proposition", "call_to_action"]:
+ if section in script:
+ st.markdown(f"#### {section.replace('_', ' ').title()}")
+ st.markdown(f"**Duration:** {script[section]['duration']}")
+ st.markdown(f"**Text:** {script[section]['text']}")
+
+ # Display visual suggestions in an organized way
+ st.markdown("**Visual Elements:**")
+ visual_suggestions = script[section]['visual_suggestions']
+ for key, value in visual_suggestions.items():
+ if isinstance(value, list):
+ st.markdown(f"**{key.replace('_', ' ').title()}:**")
+ for item in value:
+ st.markdown(f"- {item}")
+ else:
+ st.markdown(f"**{key.replace('_', ' ').title()}:** {value}")
+ st.markdown("---")
+
+ with visuals_tab:
+ st.markdown("### 🎨 Visual Elements")
+ for visual in visuals:
+ st.markdown(f"#### {visual['type'].replace('_', ' ').title()}")
+ st.markdown(f"**Usage:** {visual['usage']}")
+ st.image(visual["image"], use_column_width=True)
+ st.markdown("---")
+
+ with production_tab:
+ if "notes" in script:
+ st.markdown("### 📋 Production Notes")
+ notes = script["notes"]
+
+ st.markdown("#### 🎭 Tone and Style")
+ st.write(notes["tone"])
+
+ st.markdown("#### 🎵 Music Suggestions")
+ for music in notes["music_suggestions"]:
+ st.markdown(f"- {music}")
+
+ st.markdown("#### 🔄 Transitions")
+ for transition in notes["transitions"]:
+ st.markdown(f"- {transition}")
+
+ st.markdown("#### ✨ Special Effects")
+ for effect in notes["special_effects"]:
+ st.markdown(f"- {effect}")
+
+ st.markdown("#### 💡 Production Tips")
+ for tip in notes["production_tips"]:
+ st.markdown(f"- {tip}")
+
+ st.markdown("#### 🎨 Visual Consistency")
+ st.write(notes["visual_consistency"])
+
+ st.markdown("#### 📝 Brand Guidelines")
+ st.write(notes["brand_guidelines"])
+
+ # Add download options
+ st.markdown("### 💾 Download Options")
+ col1, col2 = st.columns(2)
+ with col1:
+ if st.button("Download Script"):
+ save_to_file(json.dumps(script, indent=2), "channel_trailer_script.json")
+ with col2:
+ if st.button("Download Visuals"):
+ # Save visuals logic here
+ pass
+
+def get_audio_player_html(audio_bytes: bytes) -> str:
+ """Generate HTML for audio player with the given audio bytes."""
+ b64 = base64.b64encode(audio_bytes).decode()
+ return f"""
+
+
+ Your browser does not support the audio element.
+
+ """
+
+if __name__ == "__main__":
+ try:
+ write_yt_channel_trailer()
+ except Exception as e:
+ logger.error(f"Unexpected error in trailer generator: {str(e)}")
+ st.error("An unexpected error occurred. Please try again or contact support.")
\ No newline at end of file
diff --git a/lib/ai_writers/youtube_writers/youtube_ai_writer.py b/lib/ai_writers/youtube_writers/youtube_ai_writer.py
index 73267fb0..05ccf281 100644
--- a/lib/ai_writers/youtube_writers/youtube_ai_writer.py
+++ b/lib/ai_writers/youtube_writers/youtube_ai_writer.py
@@ -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": "📅",
diff --git a/lib/utils/alwrity_utils.py b/lib/utils/alwrity_utils.py
index 50f35b55..d8375f85 100644
--- a/lib/utils/alwrity_utils.py
+++ b/lib/utils/alwrity_utils.py
@@ -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:
diff --git a/lib/utils/api_key_manager/components/website_setup.py b/lib/utils/api_key_manager/components/website_setup.py
index 278da58c..19915438 100644
--- a/lib/utils/api_key_manager/components/website_setup.py
+++ b/lib/utils/api_key_manager/components/website_setup.py
@@ -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)}")
diff --git a/lib/utils/save_to_file.py b/lib/utils/save_to_file.py
new file mode 100644
index 00000000..1ffccf83
--- /dev/null
+++ b/lib/utils/save_to_file.py
@@ -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)
\ No newline at end of file
diff --git a/lib/utils/seo_tools.py b/lib/utils/seo_tools.py
index 7ca33ab3..fc462b48 100644
--- a/lib/utils/seo_tools.py
+++ b/lib/utils/seo_tools.py
@@ -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(" ", 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"""
+
+
+
{details['icon']}
+
{combo_name}
+
{details['description']}
+
+ {''.join([f'{tool} ' for tool in details['tools']])}
+
+
+ """, 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"""
+
+
+
{feature['icon']}
+
{feature['name']}
+
{feature['description']}
+
+ {''.join([f'{badge} ' for badge in feature['badges']])}
+
+
+ """, 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"""
+
+
+
{details['icon']}
+
{tool_name}
+
{details['description']}
+
+ """, 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("""
+
+
About This Dashboard
+
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.
+
+ """, unsafe_allow_html=True)
+
+ st.markdown("""
+
+ """, 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 website’s 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!")
diff --git a/lib/utils/ui_setup.py b/lib/utils/ui_setup.py
index dae7f7d7..6ca409fe 100644
--- a/lib/utils/ui_setup.py
+++ b/lib/utils/ui_setup.py
@@ -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("""
+
+ """, 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"""
+
+
{details['icon']}
+
{platform}
+
{details['description']}
+
+ """, 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('', unsafe_allow_html=True)
@@ -402,10 +448,36 @@ def setup_alwrity_ui():
st.sidebar.image(icon_path, use_container_width=False)
st.sidebar.markdown('', 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("""
""", 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("""
-
- """, 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("""