ALwrity + Wordpress + Wix + GSC integration

This commit is contained in:
ajaysi
2025-10-08 10:13:14 +05:30
parent 14dfb2e5c0
commit 3bab3450dc
147 changed files with 19815 additions and 17053 deletions

View File

@@ -1,482 +0,0 @@
"""
Bootstrap AI Competitive Suite
All-in-one AI-powered competitive tools for solo entrepreneurs.
Combines content performance prediction and competitive intelligence.
"""
import asyncio
import streamlit as st
from typing import Dict, Any, List, Optional
from datetime import datetime
from loguru import logger
# Import the AI-powered tools
from lib.content_performance_predictor.ai_performance_predictor import AIContentPerformancePredictor
from lib.competitive_intelligence.ai_competitive_intelligence import AICompetitiveIntelligence
class BootstrapAISuite:
"""
Unified AI suite for bootstrapped entrepreneurs.
Combines content performance prediction and competitive intelligence.
"""
def __init__(self):
"""Initialize the bootstrap AI suite."""
self.content_predictor = AIContentPerformancePredictor()
self.competitor_intel = AICompetitiveIntelligence()
logger.info("Bootstrap AI Suite initialized")
def get_suite_capabilities(self) -> Dict[str, Any]:
"""Get all suite capabilities."""
return {
'content_prediction': {
'name': 'AI Content Performance Predictor',
'description': 'Predict content performance using AI analysis',
'features': [
'Platform-specific optimization',
'Engagement prediction',
'Content recommendations',
'Hashtag optimization',
'Posting time suggestions'
]
},
'competitive_intelligence': {
'name': 'Bootstrap Competitive Intelligence',
'description': 'AI-powered competitor analysis for startups',
'features': [
'Competitor weakness identification',
'Content gap analysis',
'Strategic recommendations',
'Quick win opportunities',
'Market positioning advice'
]
},
'integrated_workflows': {
'name': 'Smart Workflows',
'description': 'Combined insights from both tools',
'features': [
'Competitor-informed content strategy',
'Performance-optimized competitive positioning',
'Data-driven differentiation',
'Strategic content calendar'
]
}
}
async def get_competitive_content_strategy(
self,
content: str,
target_platform: str,
competitor_urls: List[str],
industry: str,
your_strengths: List[str] = None
) -> Dict[str, Any]:
"""
Get a comprehensive content strategy combining performance prediction
and competitive analysis.
"""
logger.info("Generating competitive content strategy")
try:
# Step 1: Predict content performance
st.info("🎯 Analyzing content performance potential...")
performance_analysis = await self.content_predictor.predict_performance(
content, target_platform
)
# Step 2: Get competitive intelligence
st.info("🕵️ Analyzing competitive landscape...")
competitive_analysis = await self.competitor_intel.analyze_competitors(
competitor_urls, industry, your_strengths
)
# Step 3: Generate integrated strategy
st.info("🧠 Creating integrated strategy...")
integrated_strategy = await self._create_integrated_strategy(
performance_analysis, competitive_analysis, content, target_platform
)
return {
'content_performance': performance_analysis,
'competitive_intelligence': competitive_analysis,
'integrated_strategy': integrated_strategy,
'action_plan': self._create_action_plan(
performance_analysis, competitive_analysis, integrated_strategy
)
}
except Exception as e:
error_msg = f"Error generating competitive content strategy: {str(e)}"
logger.error(error_msg, exc_info=True)
return {'error': error_msg}
async def _create_integrated_strategy(
self,
performance_analysis: Dict[str, Any],
competitive_analysis: Dict[str, Any],
content: str,
platform: str
) -> Dict[str, Any]:
"""Create integrated strategy combining both analyses."""
# Extract key insights
predicted_performance = performance_analysis.get('predictions', {})
competitor_insights = competitive_analysis.get('competitor_insights', {})
content_gaps = competitive_analysis.get('content_gap_analysis', {})
from lib.gpt_providers.text_generation.main_text_generation import llm_text_gen
integration_prompt = f"""
Create an integrated content strategy that combines performance prediction and competitive analysis:
CONTENT TO ANALYZE:
"{content[:500]}"
TARGET PLATFORM: {platform}
PERFORMANCE PREDICTIONS:
{predicted_performance}
COMPETITIVE INSIGHTS:
Key Insights: {competitor_insights.get('key_insights', [])}
Content Gaps: {content_gaps.get('priority_gaps', [])}
Quick Wins: {competitive_analysis.get('quick_wins', [])}
Provide an integrated strategy that answers:
1. CONTENT OPTIMIZATION BASED ON COMPETITION:
- How can you modify your content to outperform competitors?
- What competitive advantages can you highlight?
- How can you fill content gaps while maintaining performance?
2. STRATEGIC POSITIONING:
- How does your predicted performance compare to competitive landscape?
- What unique angle can you take that competitors miss?
- How can you leverage competitor weaknesses in your content?
3. PERFORMANCE ENHANCEMENT:
- What competitive insights can improve your predicted performance?
- Which competitor strategies should you adopt or avoid?
- How can you differentiate while maintaining engagement?
4. TACTICAL EXECUTION:
- What specific changes will maximize both performance and competitive advantage?
- How should you sequence your content to beat competitors?
- What metrics should you track for competitive success?
Provide specific, actionable recommendations for a solo entrepreneur.
"""
try:
integrated_analysis = llm_text_gen(
integration_prompt,
system_prompt="You are a strategic content consultant specializing in competitive content strategy for solo entrepreneurs. Combine performance optimization with competitive intelligence."
)
return {
'full_strategy': integrated_analysis,
'optimization_recommendations': self._extract_optimization_recs(integrated_analysis),
'competitive_positioning': self._extract_positioning_strategy(integrated_analysis),
'performance_tactics': self._extract_performance_tactics(integrated_analysis)
}
except Exception as e:
logger.error(f"Error creating integrated strategy: {str(e)}")
return {
'full_strategy': f"Error generating integrated strategy: {str(e)}",
'optimization_recommendations': [],
'competitive_positioning': 'Focus on unique value proposition',
'performance_tactics': []
}
def _extract_optimization_recs(self, strategy: str) -> List[str]:
"""Extract optimization recommendations."""
recommendations = []
lines = strategy.split('\n')
for line in lines:
line = line.strip()
if ('optimize' in line.lower() or 'improve' in line.lower() or
'enhance' in line.lower()) and len(line) > 20:
recommendations.append(line.lstrip('•-* ').strip())
return recommendations[:5]
def _extract_positioning_strategy(self, strategy: str) -> str:
"""Extract positioning strategy."""
lines = strategy.split('\n')
for line in lines:
if ('position' in line.lower() or 'unique' in line.lower() or
'differentiate' in line.lower()) and len(line) > 30:
return line.strip()
return "Focus on unique value proposition that competitors can't match"
def _extract_performance_tactics(self, strategy: str) -> List[str]:
"""Extract performance tactics."""
tactics = []
lines = strategy.split('\n')
for line in lines:
line = line.strip()
if ('tactic' in line.lower() or 'execute' in line.lower() or
'implement' in line.lower()) and len(line) > 20:
tactics.append(line.lstrip('•-* ').strip())
return tactics[:4]
def _create_action_plan(
self,
performance_analysis: Dict[str, Any],
competitive_analysis: Dict[str, Any],
integrated_strategy: Dict[str, Any]
) -> List[Dict[str, Any]]:
"""Create actionable plan from all analyses."""
action_plan = []
# From performance analysis
recommendations = performance_analysis.get('recommendations', [])
for rec in recommendations[:2]:
action_plan.append({
'category': 'Content Performance',
'action': rec,
'priority': 'High',
'timeframe': 'Immediate',
'source': 'AI Performance Predictor'
})
# From competitive analysis
quick_wins = competitive_analysis.get('quick_wins', [])
for win in quick_wins[:2]:
action_plan.append({
'category': 'Competitive Strategy',
'action': win.get('action', win),
'priority': 'High',
'timeframe': win.get('timeframe', '1-2 weeks') if isinstance(win, dict) else '1-2 weeks',
'source': 'Competitive Intelligence'
})
# From integrated strategy
optimization_recs = integrated_strategy.get('optimization_recommendations', [])
for rec in optimization_recs[:2]:
action_plan.append({
'category': 'Integrated Strategy',
'action': rec,
'priority': 'Medium',
'timeframe': '1-4 weeks',
'source': 'Integrated Analysis'
})
return action_plan
def render_bootstrap_ai_suite():
"""Render the complete bootstrap AI suite interface."""
st.set_page_config(
page_title="Bootstrap AI Competitive Suite",
page_icon="🚀",
layout="wide"
)
st.title("🚀 Bootstrap AI Competitive Suite")
st.markdown("**The complete AI toolkit for solo entrepreneurs competing against big players**")
# Initialize suite
if 'ai_suite' not in st.session_state:
st.session_state.ai_suite = BootstrapAISuite()
suite = st.session_state.ai_suite
# Sidebar with capabilities
st.sidebar.header("🎯 AI Suite Capabilities")
capabilities = suite.get_suite_capabilities()
for key, capability in capabilities.items():
with st.sidebar.expander(capability['name']):
st.write(capability['description'])
for feature in capability['features']:
st.write(f"{feature}")
# Main tabs
tab1, tab2, tab3 = st.tabs([
"🎯 Content Performance Predictor",
"🕵️ Competitive Intelligence",
"🧠 Integrated Strategy"
])
# Tab 1: Content Performance Predictor
with tab1:
st.header("🎯 AI Content Performance Predictor")
st.markdown("Predict how well your content will perform before you publish")
# Import and render the AI predictor UI
from lib.content_performance_predictor.ai_performance_predictor import render_ai_predictor_ui
render_ai_predictor_ui()
# Tab 2: Competitive Intelligence
with tab2:
st.header("🕵️ Bootstrap Competitive Intelligence")
st.markdown("Analyze competitors and find opportunities to outmaneuver them")
# Import and render the competitive intelligence UI
from lib.competitive_intelligence.ai_competitive_intelligence import render_ai_competitive_intelligence_ui
render_ai_competitive_intelligence_ui()
# Tab 3: Integrated Strategy
with tab3:
st.header("🧠 Integrated AI Strategy")
st.markdown("Combine content performance prediction with competitive intelligence for maximum impact")
# Input section
st.subheader("Strategy Input")
col1, col2 = st.columns(2)
with col1:
content = st.text_area(
"Content to Analyze",
value="Discover how AI can revolutionize your content creation process with our innovative tools designed specifically for solo entrepreneurs and small businesses.",
height=100,
help="Enter the content you want to optimize"
)
platform = st.selectbox(
"Target Platform",
["Twitter", "LinkedIn", "Facebook", "Instagram"],
help="Which platform will you publish on?"
)
with col2:
industry = st.text_input(
"Your Industry",
value="AI Content Creation",
help="What industry are you in?"
)
competitor_urls = st.text_area(
"Competitor URLs (one per line)",
value="https://jasper.ai\nhttps://copy.ai\nhttps://writesonic.com",
height=80,
help="URLs of your main competitors"
)
your_strengths = st.text_input(
"Your Key Strengths (comma-separated)",
value="Personal touch, Agility, Niche expertise",
help="What advantages do you have?"
)
# Process inputs
urls = [url.strip() for url in competitor_urls.split('\n') if url.strip()]
strengths = [s.strip() for s in your_strengths.split(',') if s.strip()] if your_strengths else []
if st.button("🚀 Generate Integrated Strategy", type="primary"):
if content and platform and urls and industry:
with st.spinner("🧠 AI is creating your competitive content strategy..."):
strategy_result = asyncio.run(
suite.get_competitive_content_strategy(
content, platform, urls, industry, strengths
)
)
if 'error' not in strategy_result:
st.success("✅ Integrated strategy generated!")
# Action Plan First (most important)
action_plan = strategy_result.get('action_plan', [])
if action_plan:
st.header("📋 Your Action Plan")
st.markdown("**Do these actions in order for maximum impact:**")
for i, action in enumerate(action_plan):
with st.expander(f"Action #{i+1}: {action.get('category', 'Action')} - {action.get('priority', 'Medium')} Priority"):
st.write(f"**What to do:** {action.get('action', 'N/A')}")
st.write(f"**Timeframe:** {action.get('timeframe', 'N/A')}")
st.write(f"**Source:** {action.get('source', 'N/A')}")
# Integrated Strategy
integrated = strategy_result.get('integrated_strategy', {})
if integrated:
st.header("🧠 Integrated Strategy")
# Key recommendations
optimization_recs = integrated.get('optimization_recommendations', [])
if optimization_recs:
st.subheader("🎯 Content Optimization")
for rec in optimization_recs:
st.info(f"💡 {rec}")
# Positioning strategy
positioning = integrated.get('competitive_positioning', '')
if positioning:
st.subheader("🏆 Competitive Positioning")
st.success(f"📍 {positioning}")
# Performance tactics
tactics = integrated.get('performance_tactics', [])
if tactics:
st.subheader("⚡ Performance Tactics")
for tactic in tactics:
st.write(f"{tactic}")
# Detailed analyses in expandable sections
with st.expander("📊 Content Performance Analysis"):
performance = strategy_result.get('content_performance', {})
predictions = performance.get('predictions', {})
if predictions:
col1, col2, col3 = st.columns(3)
with col1:
st.metric("Engagement Score", f"{predictions.get('engagement_score', 0)}/10")
with col2:
st.metric("Virality Potential", f"{predictions.get('virality_potential', 0)}/10")
with col3:
st.metric("Platform Fit", f"{predictions.get('platform_optimization', 0)}/10")
recommendations = performance.get('recommendations', [])
if recommendations:
st.write("**Performance Recommendations:**")
for rec in recommendations:
st.write(f"{rec}")
with st.expander("🕵️ Competitive Intelligence"):
competitive = strategy_result.get('competitive_intelligence', {})
insights = competitive.get('competitor_insights', {})
key_insights = insights.get('key_insights', [])
if key_insights:
st.write("**Key Competitive Insights:**")
for insight in key_insights[:3]:
st.write(f"{insight}")
quick_wins = competitive.get('quick_wins', [])
if quick_wins:
st.write("**Quick Competitive Wins:**")
for win in quick_wins[:3]:
if isinstance(win, dict):
st.write(f"{win.get('action', win)}")
else:
st.write(f"{win}")
with st.expander("🤖 Complete Strategic Analysis"):
full_strategy = integrated.get('full_strategy', 'No detailed strategy available')
st.write(full_strategy)
else:
st.error(f"❌ Strategy generation failed: {strategy_result.get('error')}")
else:
st.warning("⚠️ Please fill in all required fields")
# Footer
st.markdown("---")
st.markdown("**💡 Pro Tip:** Use all three tools together for maximum competitive advantage. Start with the integrated strategy for best results!")
# Main execution
if __name__ == "__main__":
render_bootstrap_ai_suite()

View File

@@ -1,156 +0,0 @@
import os
import streamlit as st
import google.genai as genai
from google.genai import types
from google.genai.types import Tool, GenerateContentConfig, GoogleSearch
# Set page config
st.set_page_config(
page_title="Gemini Grounding Search",
page_icon="🔍",
layout="wide"
)
# Custom CSS for styling
st.markdown("""
<style>
.container {
align-items: center;
border-radius: 8px;
display: flex;
font-family: Google Sans, Roboto, sans-serif;
font-size: 14px;
line-height: 20px;
padding: 8px 12px;
background-color: #fafafa;
box-shadow: 0 0 0 1px #0000000f;
margin-top: 20px;
}
.chip {
display: inline-block;
border: solid 1px;
border-radius: 16px;
min-width: 14px;
padding: 5px 16px;
text-align: center;
user-select: none;
margin: 0 8px;
background-color: #ffffff;
border-color: #d2d2d2;
color: #5e5e5e;
text-decoration: none;
}
.chip:hover {
background-color: #f2f2f2;
}
.carousel {
overflow: auto;
scrollbar-width: none;
white-space: nowrap;
margin-right: -12px;
display: flex;
align-items: center;
}
.headline {
display: flex;
margin-right: 4px;
align-items: center;
}
.gradient-container {
position: relative;
}
.gradient {
position: absolute;
transform: translate(3px, -9px);
height: 36px;
width: 9px;
background: linear-gradient(90deg, #fafafa 15%, #fafafa00 100%);
}
.result-text {
font-size: 16px;
line-height: 1.6;
color: #202124;
margin: 20px 0;
white-space: pre-wrap;
}
@media (prefers-color-scheme: dark) {
.container {
background-color: #1f1f1f;
box-shadow: 0 0 0 1px #ffffff26;
}
.headline-label {
color: #fff;
}
.chip {
background-color: #2c2c2c;
border-color: #3c4043;
color: #fff;
}
.chip:hover {
background-color: #353536;
}
.gradient {
background: linear-gradient(90deg, #1f1f1f 15%, #1f1f1f00 100%);
}
.result-text {
color: #e8eaed;
}
}
</style>
""", unsafe_allow_html=True)
# Title
st.title("Gemini Grounding Search")
# Initialize Gemini client
if 'GEMINI_API_KEY' not in os.environ:
api_key = st.text_input("Enter your Gemini API Key:", type="password")
if api_key:
os.environ['GEMINI_API_KEY'] = api_key
# Search input
search_query = st.text_input("Enter your search query:", "When is the next total solar eclipse in the United States?")
if st.button("Search"):
if 'GEMINI_API_KEY' not in os.environ:
st.error("Please enter your Gemini API Key first!")
else:
try:
client = genai.Client(api_key=os.environ['GEMINI_API_KEY'])
model_id = "gemini-2.0-flash"
google_search_tool = Tool(
google_search = GoogleSearch()
)
with st.spinner("Searching..."):
response = client.models.generate_content(
model=model_id,
contents=search_query,
config=GenerateContentConfig(
tools=[google_search_tool],
response_modalities=["TEXT"],
)
)
# Display search results header
st.header("Search Results")
# Display the response text
if response.candidates[0].content.parts:
st.markdown('<div class="result-text">' +
response.candidates[0].content.parts[0].text.replace('\n', '<br>') +
'</div>',
unsafe_allow_html=True)
# Display the grounding metadata
if hasattr(response.candidates[0], 'grounding_metadata') and \
hasattr(response.candidates[0].grounding_metadata, 'search_entry_point') and \
hasattr(response.candidates[0].grounding_metadata.search_entry_point, 'rendered_content'):
st.header("Related Searches")
rendered_content = response.candidates[0].grounding_metadata.search_entry_point.rendered_content
st.markdown(rendered_content, unsafe_allow_html=True)
except Exception as e:
st.error(f"An error occurred: {str(e)}")

View File

@@ -1,108 +0,0 @@
import re #additional import for regex
import os
import json
import requests
from openai import OpenAI
client = OpenAI(
api_key=os.getenv('OPENAI-API-KEY')
)
# Target URL can be a website url or it can google search
query = "kedarkanta trek"
target_url = f"https://www.google.com/search?q={query}&gl=us"
response = requests.get(target_url)
print
html_text = response.text
# Remove unnecessary part to prevent HUGE TOKEN cost!
# Remove everything between <head> and </head>
html_text = re.sub(r'<head.*?>.*?</head>', '', html_text, flags=re.DOTALL)
# Remove all occurrences of content between <script> and </script>
html_text = re.sub(r'<script.*?>.*?</script>', '', html_text, flags=re.DOTALL)
# Remove all occurrences of content between <style> and </style>
html_text = re.sub(r'<style.*?>.*?</style>', '', html_text, flags=re.DOTALL)
completion = client.chat.completions.create(
model="gpt-4-1106-preview",
messages=[
{"role": "system", "content": "You are a master at scraping Google results data. Scrape two things: 1st. Scrape top 10 organic results data and 2nd. Scrape people_also_ask section from Google search result page."},
{"role": "user", "content": html_text}
],
tools=[
{
"type": "function",
"function": {
"name": "parse_organic_results",
"description": "Parse organic results from Google SERP raw HTML data nicely",
"parameters": {
'type': 'object',
'properties': {
'data': {
'type': 'array',
'items': {
'type': 'object',
'properties': {
'title': {'type': 'string'},
'original_url': {'type': 'string'},
'snippet': {'type': 'string'},
'position': {'type': 'integer'}
}
}
}
}
}
}
},
{
"type": "function",
"function": {
"name": "parse_people_also_ask_section",
"description": "Parse `people also ask` section from Google SERP raw HTML",
"parameters": {
'type': 'object',
'properties': {
'data': {
'type': 'array',
'items': {
'type': 'object',
'properties': {
'question': {'type': 'string'},
'original_url': {'type': 'string'},
'answer': {'type': 'string'},
}
}
}
}
}
}
}
],
tool_choice="auto"
)
# Organic_results
argument_str = completion.choices[0].message.tool_calls[0].function.arguments
argument_dict = json.loads(argument_str)
organic_results = argument_dict['data']
print('Organic results:')
for result in organic_results:
print(f"Blog Title: {result['title']}")
print(f"Blog URL: {result['original_url']}")
print(f"Blog Snippet: {result['snippet']}")
print(f"Blog Position: {result['position']}")
print('---')
# People also ask
argument_str = completion.choices[0].message.tool_calls[1].function.arguments
argument_dict = json.loads(argument_str)
people_also_ask = argument_dict['data']
print('People also ask:')
for result in people_also_ask:
print(f"People_Also_Ask: Question: {result['question']}")
print(f"People_Also_Ask: URL: {result['original_url']}")
print("People_Also_Ask: Answer: {result['answer']}")
print('---')

View File

@@ -1,30 +0,0 @@
import sys
from loguru import logger
logger.remove()
logger.add(sys.stdout,
colorize=True,
format="<level>{level}</level>|<green>{file}:{line}:{function}</green>| {message}"
)
from ..gpt_providers.text_generation.main_text_generation import llm_text_gen
def summarize_competitor_content(research_content):
"""Combine the given online research and gpt blog content"""
prompt = f"""You are a helpful assistant writing a research report about a company. I will provide you with company details.
Summarize the given company details into multiple paragraphs.
Be extremely concise, professional, and factual as possible.
The first paragraph should be an introduction and summary of the company.
The second paragraph should include pros and cons of the company.
The third paragraph should be on their pricing model.
Include a conclusion, summarizing your research about the given company details.
Company details: '{research_content}'"""
try:
response = llm_text_gen(prompt)
return response
except Exception as err:
logger.error(f"Failed to get response from LLM: {err}")
raise err

View File

@@ -1,23 +0,0 @@
import sys
from loguru import logger
logger.remove()
logger.add(sys.stdout,
colorize=True,
format="<level>{level}</level>|<green>{file}:{line}:{function}</green>| {message}"
)
from ..gpt_providers.text_generation.main_text_generation import llm_text_gen
def summarize_web_content(page_content, gpt_providers="openai"):
"""Combine the given online research and gpt blog content"""
prompt = f"""You are a helpful assistant that briefly summarizes the content of a webpage.
Summarize the given web page content below.
Web page content: '{page_content}'"""
try:
response = llm_text_gen(prompt)
return response
except Exception as err:
logger.error(f"summarize_web_content: Failed to get response from LLM: {err}")
raise err

View File

@@ -1,129 +0,0 @@
import os
import requests
from clint.textui import progress
from loguru import logger
from pathlib import Path
from dotenv import load_dotenv
load_dotenv(Path('../../.env'))
def search_ydc_index(search_query, num_web_results=10, country="IN"):
"""
Search YDC Index API and retrieve results.
Args:
search_query (str): The search query.
num_web_results (int): Number of web results to retrieve.
country (str): Country code.
api_key (str): YDC Index API key.
Returns:
dict: The response from the YDC Index API in JSON format.
"""
api_key = os.environ["YOU_API_KEY"]
try:
url = "https://api.ydc-index.io/search"
querystring = {
"query": search_query,
}
headers = {"X-API-Key": api_key}
response = requests.get(url, headers=headers, params=querystring, stream=True)
response.raise_for_status() # Raise an HTTPError for bad responses (4xx or 5xx)
result_json = response.json()
return result_json
except requests.exceptions.RequestException as req_exc:
logger.error(f"Request to YDC Index API failed: {req_exc}")
return {"error": str(req_exc)}
except Exception as e:
logger.error(f"An error occurred: {e}")
return {"error": str(e)}
def get_rag_results(search_query, num_web_results=10, country="IN"):
"""
Retrieve RAG (Relevance, Authority, and Goodness) results from YDC Index API.
Args:
search_query (str): The search query.
num_web_results (int): Number of web results to retrieve.
country (str): Country code
Returns:
dict: The response from the YDC Index API in JSON format.
"""
api_key = os.environ["YOU_API_KEY"]
try:
url = "https://api.ydc-index.io/rag"
querystring = {
"query": search_query,
"num_web_results": str(num_web_results),
"country": country
}
headers = {"X-API-Key": api_key}
with progress.Bar(expected_size=num_web_results, label="Fetching RAG Results") as bar:
response = requests.get(url, headers=headers, params=querystring, stream=True)
response.raise_for_status() # Raise an HTTPError for bad responses (4xx or 5xx)
result_json = response.json()
bar.show(result_json.get("web_results", [])) # Update progress bar with the number of web results
return result_json
except requests.exceptions.RequestException as req_exc:
logger.error(f"Request to YDC Index API failed: {req_exc}")
return {"error": str(req_exc)}
except Exception as e:
logger.error(f"An error occurred: {e}")
return {"error": str(e)}
def get_news_results(query, spellcheck=True):
"""
Retrieve news results from YDC Index API.
Args:
query (str): The search query.
spellcheck (bool): Whether to enable spellcheck.
api_key (str): YDC Index API key.
Returns:
dict: The response from the YDC Index API in JSON format.
"""
api_key = os.environ["YOU_API_KEY"]
try:
url = "https://api.ydc-index.io/news"
querystring = {
"q": query,
"spellcheck": str(spellcheck).lower()
}
headers = {"X-API-Key": api_key}
with progress.Bar(expected_size=1, label="Fetching News Results") as bar:
response = requests.get(url, headers=headers, params=querystring, stream=True)
response.raise_for_status() # Raise an HTTPError for bad responses (4xx or 5xx)
result_json = response.json()
bar.show() # Update progress bar
return result_json
except requests.exceptions.RequestException as req_exc:
logger.error(f"Request to YDC Index API failed: {req_exc}")
return {"error": str(req_exc)}
except Exception as e:
logger.error(f"An error occurred: {e}")
return {"error": str(e)}

View File

@@ -1,705 +0,0 @@
import streamlit as st
from loguru import logger
from typing import List, Dict, Any, Callable
# Import existing tools
from lib.ai_seo_tools.seo_structured_data import ai_structured_data
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.image_alt_text_generator import alt_text_gen
from lib.ai_seo_tools.opengraph_generator import og_tag_generator
from lib.ai_seo_tools.optimize_images_for_upload import main_img_optimizer
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_gap_analysis.enhanced_ui import render_enhanced_content_gap_analysis
from lib.ai_seo_tools.content_calendar.ui.dashboard import ContentCalendarDashboard
from lib.ai_seo_tools.technical_seo_crawler import render_technical_seo_crawler
# Import additional tools
from lib.ai_seo_tools.twitter_tags_generator import display_app as twitter_tags_app
from lib.ai_seo_tools.sitemap_analysis import main as sitemap_analyzer
from lib.ai_seo_tools.textstaty import analyze_text as readability_analyzer
from lib.ai_seo_tools.wordcloud import generate_wordcloud
# Import new enterprise tools
from ..ai_seo_tools.google_search_console_integration import render_gsc_integration
from ..ai_seo_tools.ai_content_strategy import render_ai_content_strategy
from ..ai_seo_tools.enterprise_seo_suite import render_enterprise_seo_suite
from lib.alwrity_ui.dashboard_styles import apply_dashboard_style, render_dashboard_header, render_category_header, render_card
# ============================================================================
# TOOL CONFIGURATION FUNCTIONS
# ============================================================================
def get_enterprise_tools_config() -> List[Dict[str, Any]]:
"""Get configuration for enterprise tools."""
return [
{
'name': '🎯 Enterprise SEO Suite',
'description': 'Unified command center for comprehensive SEO management with AI-powered workflows',
'function': render_enterprise_seo_suite,
'features': ['Complete SEO audit workflows', 'AI-powered recommendations', 'Strategic planning', 'Performance tracking']
},
{
'name': '📊 Google Search Console Intelligence',
'description': 'AI-powered insights from Google Search Console data with content recommendations',
'function': render_gsc_integration,
'features': ['GSC data analysis', 'Content opportunities', 'Performance insights', 'Strategic recommendations']
},
{
'name': '🧠 AI Content Strategy Generator',
'description': 'Generate comprehensive content strategies using AI market intelligence',
'function': render_ai_content_strategy,
'features': ['Content pillar development', 'Topic cluster strategy', 'Content calendar planning', 'Distribution strategy']
}
]
def get_analytics_tools_config() -> List[Dict[str, Any]]:
"""Get configuration for analytics tools."""
return [
{
'name': '📊 Google Search Console Intelligence',
'description': 'Deep analysis of GSC data with AI-powered content recommendations',
'function': render_gsc_integration,
'category': 'Search Analytics'
},
{
'name': '🔍 Enhanced Content Gap Analysis',
'description': 'Advanced competitor content analysis with AI insights',
'function': lambda: render_enhanced_content_gap_analysis(),
'category': 'Competitive Intelligence'
},
{
'name': '📈 SEO Performance Tracker',
'description': 'Track and analyze SEO performance with trend analysis',
'function': lambda: st.info("SEO Performance Tracker - Coming soon with advanced metrics"),
'category': 'Performance Analytics'
}
]
def get_technical_tools_config() -> List[Dict[str, Any]]:
"""Get configuration for technical SEO tools."""
return [
{
'name': '🔍 Technical SEO Crawler',
'description': 'Comprehensive site-wide technical SEO analysis',
'function': lambda: render_technical_seo_crawler(),
'priority': 'High'
},
{
'name': '📱 Mobile SEO Analyzer',
'description': 'Mobile-specific SEO analysis and optimization',
'function': lambda: st.info("Mobile SEO Analyzer - Advanced mobile optimization coming soon"),
'priority': 'Medium'
},
{
'name': '⚡ Core Web Vitals Optimizer',
'description': 'Analyze and optimize Core Web Vitals performance',
'function': lambda: st.info("Core Web Vitals Optimizer - Performance optimization coming soon"),
'priority': 'High'
},
{
'name': '🗺️ XML Sitemap Generator',
'description': 'Generate and optimize XML sitemaps',
'function': lambda: st.info("XML Sitemap Generator - Coming soon"),
'priority': 'Medium'
}
]
def get_content_tools_config() -> List[Dict[str, Any]]:
"""Get configuration for content and strategy tools."""
return [
{
'name': '🧠 AI Content Strategy Generator',
'description': 'Comprehensive content strategy with AI market intelligence',
'function': render_ai_content_strategy,
'type': 'Enterprise'
},
{
'name': '📅 Content Calendar Planner',
'description': 'AI-powered content calendar with SEO optimization',
'function': lambda: render_content_calendar(),
'type': 'Professional'
},
{
'name': '🎯 Topic Cluster Generator',
'description': 'Generate SEO topic clusters for content dominance',
'function': lambda: st.info("Topic Cluster Generator - Advanced clustering coming soon"),
'type': 'Professional'
},
{
'name': '📊 Content Performance Analyzer',
'description': 'Analyze content performance and optimization opportunities',
'function': lambda: st.info("Content Performance Analyzer - Coming soon"),
'type': 'Standard'
}
]
def get_basic_tools_config() -> List[Dict[str, Any]]:
"""Get configuration for basic SEO tools."""
return [
{
'name': '📝 Meta Description Generator',
'description': 'Generate SEO-optimized meta descriptions',
'function': lambda: metadesc_generator_main(),
'category': 'Metadata'
},
{
'name': '🎯 Content Title Generator',
'description': 'Create compelling, SEO-friendly titles',
'function': lambda: ai_title_generator(),
'category': 'Content'
},
{
'name': '🔗 OpenGraph Generator',
'description': 'Generate social media OpenGraph tags',
'function': lambda: og_tag_generator(),
'category': 'Social'
},
{
'name': '🖼️ Image Alt Text Generator',
'description': 'Generate SEO-friendly image alt text',
'function': lambda: alt_text_gen(),
'category': 'Images'
},
{
'name': '📋 Schema Markup Generator',
'description': 'Generate structured data markup',
'function': lambda: ai_structured_data(),
'category': 'Technical'
},
{
'name': '🔍 On-Page SEO Analyzer',
'description': 'Comprehensive on-page SEO analysis',
'function': lambda: analyze_onpage_seo(),
'category': 'Analysis'
},
{
'name': '🌐 URL SEO Checker',
'description': 'Quick SEO check for any URL',
'function': lambda: url_seo_checker(),
'category': 'Analysis'
}
]
def get_tool_functions_mapping() -> Dict[str, Callable]:
"""Get mapping of tool names to their functions for URL routing."""
return {
# Core content 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,
# Technical analysis tools
"technical_seo_crawler": render_technical_seo_crawler,
"pagespeed": google_pagespeed_insights,
"onpage_seo": analyze_onpage_seo,
"url_checker": url_seo_checker,
"sitemap_analysis": sitemap_analyzer,
# Social media tools
"twitter_tags": render_twitter_tags,
# Content analysis tools
"readability_analyzer": render_readability_analyzer,
"wordcloud_generator": render_wordcloud_generator,
# Advanced tools
"backlinking": backlinking_ui,
"content_gap_analysis": render_content_gap_analysis,
"enhanced_content_gap_analysis": render_enhanced_content_gap_analysis_ui,
"content_calendar": render_content_calendar,
# Tool combinations for workflow efficiency
"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,
render_twitter_tags
], "Social Media Optimization")
}
# ============================================================================
# INDIVIDUAL TOOL RENDERING FUNCTIONS
# ============================================================================
def render_content_gap_analysis():
"""Render the content gap analysis workflow interface."""
ui = ContentGapAnalysisUI()
ui.run()
def render_enhanced_content_gap_analysis_ui():
"""Render the enhanced content gap analysis with advertools integration."""
render_enhanced_content_gap_analysis()
def render_content_calendar():
"""Render the content calendar dashboard with proper error handling."""
import logging
import sys
# 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')
]
)
calendar_logger = logging.getLogger('content_calendar')
try:
calendar_logger.info("Initializing Content Calendar Dashboard")
dashboard = ContentCalendarDashboard()
calendar_logger.info("Rendering Content Calendar Dashboard")
dashboard.render()
calendar_logger.info("Content Calendar Dashboard rendered successfully")
except Exception as e:
calendar_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_twitter_tags():
"""Render the Twitter tags generator."""
twitter_tags_app()
def render_readability_analyzer():
"""Render the text readability analyzer."""
st.title("📖 Text Readability Analyzer")
st.write("Making Your Content Easy to Read")
text_input = st.text_area("Paste your text here:", height=200)
if st.button("Analyze Readability"):
if text_input.strip():
_display_readability_metrics(text_input)
_display_readability_recommendations()
else:
st.error("Please enter text to analyze.")
def _display_readability_metrics(text: str):
"""Display readability metrics for the given text."""
from textstat import textstat
metrics = {
"Flesch Reading Ease": textstat.flesch_reading_ease(text),
"Flesch-Kincaid Grade Level": textstat.flesch_kincaid_grade(text),
"Gunning Fog Index": textstat.gunning_fog(text),
"SMOG Index": textstat.smog_index(text),
"Automated Readability Index": textstat.automated_readability_index(text),
"Coleman-Liau Index": textstat.coleman_liau_index(text),
"Linsear Write Formula": textstat.linsear_write_formula(text),
"Dale-Chall Readability Score": textstat.dale_chall_readability_score(text),
"Readability Consensus": textstat.readability_consensus(text)
}
st.subheader("Text Analysis Results")
for metric, value in metrics.items():
st.metric(metric, f"{value:.2f}")
def _display_readability_recommendations():
"""Display readability recommendations."""
st.subheader("Key Takeaways:")
st.markdown("""
* **Don't Be Afraid to Simplify!** Often, simpler language makes content more impactful and easier to digest.
* **Aim for a Reading Level Appropriate for Your Audience:** Consider the education level, background, and familiarity of your readers.
* **Use Short Sentences:** This makes your content more scannable and easier to read.
* **Write for Everyone:** Accessibility should always be a priority. When in doubt, aim for clear, concise language!
""")
def render_wordcloud_generator():
"""Render the word cloud generator."""
st.title("☁️ Word Cloud Generator")
st.write("Visualize the most important words in your content")
text_input = st.text_area("Enter your text:", height=200)
if st.button("Generate Word Cloud"):
if text_input.strip():
_generate_and_display_wordcloud(text_input)
_display_text_statistics(text_input)
else:
st.error("Please enter text to generate a word cloud.")
def _generate_and_display_wordcloud(text: str):
"""Generate and display word cloud for the given text."""
from wordcloud import WordCloud
import matplotlib.pyplot as plt
# Create and generate a word cloud image
wordcloud = WordCloud(width=800, height=400, background_color='white').generate(text)
# Display the word cloud
st.subheader("Word Cloud Visualization")
fig, ax = plt.subplots(figsize=(10, 5))
ax.imshow(wordcloud, interpolation='bilinear')
ax.axis('off')
st.pyplot(fig)
def _display_text_statistics(text: str):
"""Display basic text statistics."""
st.subheader("Text Statistics")
words = text.split()
unique_words = set(words)
st.metric("Total Words", len(words))
st.metric("Unique Words", len(unique_words))
# ============================================================================
# TAB RENDERING FUNCTIONS
# ============================================================================
def render_enterprise_tab():
"""Render the Enterprise Suite tab."""
st.header("🏢 Enterprise SEO Command Center")
st.markdown("**Unified SEO management for enterprise-level optimization**")
enterprise_tools = get_enterprise_tools_config()
# Display enterprise tools
for tool in enterprise_tools:
_render_enterprise_tool_card(tool)
# Render selected enterprise tool
_render_selected_enterprise_tool(enterprise_tools)
def _render_enterprise_tool_card(tool: Dict[str, Any]):
"""Render an individual enterprise tool card."""
with st.expander(f"{tool['name']} - {tool['description']}", expanded=False):
col1, col2 = st.columns([2, 1])
with col1:
st.markdown("**Key Features:**")
for feature in tool['features']:
st.write(f"{feature}")
with col2:
if st.button(f"Launch {tool['name'].split()[1]}", key=f"enterprise_{tool['name']}", use_container_width=True):
st.session_state.selected_enterprise_tool = tool['name']
tool['function']()
def _render_selected_enterprise_tool(enterprise_tools: List[Dict[str, Any]]):
"""Render the selected enterprise tool if any."""
if 'selected_enterprise_tool' in st.session_state:
selected_tool = next((tool for tool in enterprise_tools if tool['name'] == st.session_state.selected_enterprise_tool), None)
if selected_tool:
st.markdown("---")
selected_tool['function']()
def render_analytics_tab():
"""Render the Analytics & Intelligence tab."""
st.header("📊 Analytics & Intelligence")
st.markdown("**Advanced analytics and competitive intelligence tools**")
analytics_tools = get_analytics_tools_config()
# Group tools by category
categories = _group_tools_by_category(analytics_tools)
for category, tools in categories.items():
st.subheader(f"📊 {category}")
for tool in tools:
_render_analytics_tool_row(tool)
def _group_tools_by_category(tools: List[Dict[str, Any]]) -> Dict[str, List[Dict[str, Any]]]:
"""Group tools by their category."""
categories = {}
for tool in tools:
category = tool['category']
if category not in categories:
categories[category] = []
categories[category].append(tool)
return categories
def _render_analytics_tool_row(tool: Dict[str, Any]):
"""Render an analytics tool row."""
col1, col2 = st.columns([3, 1])
with col1:
st.markdown(f"**{tool['name']}**")
st.write(tool['description'])
with col2:
if st.button("Launch", key=f"analytics_{tool['name']}", use_container_width=True):
tool['function']()
def render_technical_tab():
"""Render the Technical SEO tab."""
st.header("🔧 Technical SEO")
st.markdown("**Advanced technical SEO analysis and optimization tools**")
technical_tools = get_technical_tools_config()
# Display technical tools with priority indicators
for tool in technical_tools:
_render_technical_tool_row(tool)
def _render_technical_tool_row(tool: Dict[str, Any]):
"""Render a technical tool row with priority indicator."""
priority_color = "🔴" if tool['priority'] == 'High' else "🟡"
col1, col2, col3 = st.columns([2, 1, 1])
with col1:
st.markdown(f"**{tool['name']}** {priority_color}")
st.write(tool['description'])
with col2:
st.write(f"**Priority:** {tool['priority']}")
with col3:
if st.button("Launch", key=f"technical_{tool['name']}", use_container_width=True):
tool['function']()
def render_content_tab():
"""Render the Content & Strategy tab."""
st.header("📝 Content & Strategy")
st.markdown("**AI-powered content creation and strategy tools**")
content_tools = get_content_tools_config()
# Group by tool type
tool_types = _group_tools_by_type(content_tools)
for tool_type, tools in tool_types.items():
_render_content_tool_section(tool_type, tools)
def _group_tools_by_type(tools: List[Dict[str, Any]]) -> Dict[str, List[Dict[str, Any]]]:
"""Group tools by their type."""
tool_types = {}
for tool in tools:
tool_type = tool['type']
if tool_type not in tool_types:
tool_types[tool_type] = []
tool_types[tool_type].append(tool)
return tool_types
def _render_content_tool_section(tool_type: str, tools: List[Dict[str, Any]]):
"""Render a content tool section."""
type_color = {"Enterprise": "🏢", "Professional": "💼", "Standard": "📋"}
st.subheader(f"{type_color.get(tool_type, '📋')} {tool_type} Tools")
for tool in tools:
col1, col2 = st.columns([3, 1])
with col1:
st.markdown(f"**{tool['name']}**")
st.write(tool['description'])
with col2:
if st.button("Launch", key=f"content_{tool['name']}", use_container_width=True):
tool['function']()
def render_basic_tools_tab():
"""Render the Basic Tools tab."""
st.header("🎯 Basic SEO Tools")
st.markdown("**Essential SEO tools for quick optimization tasks**")
basic_tools = get_basic_tools_config()
# Group basic tools by category
basic_categories = _group_tools_by_category(basic_tools)
# Display in columns for better layout
_render_basic_tools_in_columns(basic_categories)
def _render_basic_tools_in_columns(basic_categories: Dict[str, List[Dict[str, Any]]]):
"""Render basic tools in two columns."""
col1, col2 = st.columns(2)
categories_list = list(basic_categories.items())
mid_point = len(categories_list) // 2
with col1:
for category, tools in categories_list[:mid_point]:
_render_basic_tool_category(category, tools)
with col2:
for category, tools in categories_list[mid_point:]:
_render_basic_tool_category(category, tools)
def _render_basic_tool_category(category: str, tools: List[Dict[str, Any]]):
"""Render a basic tool category."""
st.subheader(f"📂 {category}")
for tool in tools:
if st.button(f"{tool['name']}", key=f"basic_{tool['name']}", use_container_width=True):
tool['function']()
st.caption(tool['description'])
st.markdown("---")
def render_enterprise_features_footer():
"""Render the enterprise features footer."""
st.markdown("---")
st.markdown("### 🚀 Enterprise SEO Features")
col1, col2, col3 = st.columns(3)
with col1:
st.info("""
**🏢 Enterprise Suite**
- Unified SEO workflows
- AI-powered insights
- Strategic planning
- Performance tracking
""")
with col2:
st.info("""
**📊 Advanced Analytics**
- GSC integration
- Competitive intelligence
- Content gap analysis
- Performance insights
""")
with col3:
st.info("""
**🧠 AI Strategy**
- Content strategy generation
- Topic cluster planning
- Distribution optimization
- Market intelligence
""")
# ============================================================================
# MAIN DASHBOARD FUNCTIONS
# ============================================================================
def render_seo_tools_dashboard():
"""Render comprehensive SEO tools dashboard with enterprise features."""
st.title("🚀 Alwrity AI SEO Tools")
st.markdown("**Enterprise-level SEO tools powered by artificial intelligence**")
# Create tabs for different tool categories
tab1, tab2, tab3, tab4, tab5 = st.tabs([
"🏢 Enterprise Suite",
"📊 Analytics & Intelligence",
"🔧 Technical SEO",
"📝 Content & Strategy",
"🎯 Basic Tools"
])
with tab1:
render_enterprise_tab()
with tab2:
render_analytics_tab()
with tab3:
render_technical_tab()
with tab4:
render_content_tab()
with tab5:
render_basic_tools_tab()
# Add footer with enterprise features highlight
render_enterprise_features_footer()
def ai_seo_tools():
"""Main entry point for SEO tools dashboard with premium glassmorphic design."""
logger.info("Starting SEO Tools Dashboard")
# Apply common dashboard styling
apply_dashboard_style()
# Check if a specific tool is selected
selected_tool = st.query_params.get("tool")
if selected_tool:
_handle_selected_tool(selected_tool)
else:
# Show the dashboard if no tool is selected
render_seo_tools_dashboard()
def _handle_selected_tool(selected_tool: str):
"""Handle rendering of a specific selected tool."""
tool_functions = get_tool_functions_mapping()
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"Tool '{selected_tool}' is not available or under development.")
st.info("Please select a different tool from the dashboard.")
render_seo_tools_dashboard()
def run_tool_combination(tools: List[Callable], combination_name: str):
"""Run a combination of tools and provide cross-tool analysis."""
st.markdown(f"# {combination_name}")
st.markdown("Comprehensive SEO analysis workflow")
# Create tabs for each tool in the combination
tab_names = _generate_tab_names(tools)
tabs = st.tabs(tab_names)
# Run each tool in its own tab
_execute_tools_in_tabs(tabs, tools)
# Add cross-tool analysis section
_render_analysis_summary()
def _generate_tab_names(tools: List[Callable]) -> List[str]:
"""Generate tab names for tool combination."""
tab_names = []
for i, tool in enumerate(tools):
if hasattr(tool, '__name__'):
tab_names.append(tool.__name__.replace('_', ' ').title())
else:
tab_names.append(f"Step {i+1}")
return tab_names
def _execute_tools_in_tabs(tabs: List, tools: List[Callable]):
"""Execute tools in their respective tabs."""
for tab, tool in zip(tabs, tools):
with tab:
try:
tool()
except Exception as e:
st.error(f"Error running tool: {str(e)}")
logger.error(f"Error in tool combination: {str(e)}")
def _render_analysis_summary():
"""Render the analysis summary section."""
with st.expander("📊 Analysis Summary", expanded=True):
st.markdown("""
### Key Recommendations:
1. **Content Optimization**: Ensure your titles and meta descriptions are keyword-optimized
2. **Technical Performance**: Address any speed or technical issues identified
3. **Structured Data**: Implement schema markup for better search visibility
4. **Social Optimization**: Optimize social sharing tags for better engagement
### Next Steps:
- Implement the recommendations from each tool
- Monitor your rankings and traffic after changes
- Regularly audit your content using these tools
""")
# Add export functionality placeholder
if st.button("📥 Export Analysis Report", use_container_width=True):
st.info("Export functionality is being developed. Save your results manually for now.")

View File

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

View File

@@ -1,754 +0,0 @@
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.database.models 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.
"""
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.get('Creativity Level', 'medium')}
- Formality Level: {model_settings.get('Formality Level', 'professional')}
- Style Elements: {', '.join(style_preferences)}
SEO Preferences:
- Keyword Density: {seo_preferences.get('Keyword Density', 2)}%
- Internal Linking: {'Enabled' if seo_preferences.get('Internal Linking', True) else 'Disabled'}
- External Linking: {'Enabled' if seo_preferences.get('External Linking', True) 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."""
# Generate content using llm_text_gen
generated_content = llm_text_gen(
prompt=prompt,
max_tokens=1000,
temperature=0.7,
top_p=0.9,
frequency_penalty=0.5,
presence_penalty=0.5
)
if not generated_content:
self.logger.error("No content generated from AI model")
return []
# 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)
if not content_data or 'suggestions' not in content_data:
self.logger.error("Invalid content structure in AI response")
return []
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)
if not content_data or 'suggestions' not in content_data:
self.logger.error("Invalid content structure in extracted JSON")
return []
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)}")
return []
except Exception as e:
self.logger.error(f"Error generating AI suggestions: {str(e)}", exc_info=True)
return []
def _format_suggestions(
self,
content_data: Dict[str, Any],
content_type: str,
audience: str,
goals: List[str],
tone: str,
length: str,
model_settings: Dict[str, Any],
seo_preferences: Dict[str, Any],
platform: str
) -> List[Dict[str, Any]]:
"""Format and process suggestions from content data."""
suggestions = []
for suggestion in content_data.get('suggestions', []):
formatted_suggestion = {
'title': suggestion.get('title', ''),
'type': content_type,
'platform': platform,
'audience': audience,
'impact': f"High impact for {', '.join(goals)}",
'preview': suggestion.get('introduction', ''),
'style_elements': [
f"Tone: {tone}",
f"Length: {length}",
f"Creativity: {model_settings['Creativity Level']}",
f"Formality: {model_settings['Formality Level']}"
],
'seo_elements': [
f"Keyword Density: {seo_preferences['Keyword Density']}%",
"Internal Linking: Enabled" if seo_preferences['Internal Linking'] else "Internal Linking: Disabled",
"External Linking: Enabled" if seo_preferences['External Linking'] else "External Linking: Disabled"
],
'engagement_score': f"{85 + len(suggestions)*5}%",
'reach': 'High',
'conversion': f"{3.5 + len(suggestions)*0.5}%",
'seo_impact': 'Strong',
'platform_optimizations': suggestion.get('platform_optimizations', []),
'variations': [
"Alternative headline",
"Different content angle",
"Alternative format"
],
'seo_recommendations': suggestion.get('seo_elements', []),
'media_suggestions': [
"Featured image",
"Supporting graphics",
"Social media visuals"
]
}
suggestions.append(formatted_suggestion)
return suggestions

View File

@@ -1,163 +0,0 @@
from datetime import datetime, timedelta
from typing import Dict, List, Any, Optional
import logging
import sys
import json
import os
from lib.database.models import ContentItem, ContentType, Platform, get_engine, get_session, init_db
from ..integrations.seo_tools import SEOToolsIntegration
from ..integrations.gap_analyzer import GapAnalyzerIntegration
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__)
engine = get_engine()
init_db(engine)
session = get_session(engine)
class CalendarManager:
"""
Main calendar management system that coordinates content planning,
scheduling, and optimization.
"""
def __init__(self):
self.logger = logging.getLogger('content_calendar.manager')
self.logger.info("Initializing CalendarManager")
self.seo_tools = SEOToolsIntegration()
self.gap_analyzer = GapAnalyzerIntegration()
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
) -> List[ContentItem]:
self.logger.info(f"Creating new calendar for {website_url}")
self.logger.debug(f"Parameters: start_date={start_date}, duration={duration}, platforms={platforms}")
try:
gap_analysis = self.gap_analyzer.analyze_gaps(website_url)
topics = self._generate_topics(gap_analysis, platforms)
schedule = calculate_publish_dates(
topics=topics,
start_date=start_date,
duration=duration
)
# Add to DB
for topic in schedule:
session.add(topic)
session.commit()
self.logger.info("Calendar created and content scheduled in DB successfully")
return schedule
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]:
topics = []
for gap in gap_analysis['gaps']:
topic = self._generate_topic_from_gap(gap, platforms)
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:
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),
'publish_date': datetime.now(),
'status': 'Draft',
'author': None,
'tags': [],
'notes': None,
'seo_data': {}
}
return ContentItem(**topic_data)
def _optimize_topic(self, topic: ContentItem) -> ContentItem:
topic.title = self.seo_tools.optimize_title(topic.title)
topic.seo_data['meta_description'] = self.seo_tools.generate_meta_description(topic.description)
topic.seo_data['structured_data'] = self.seo_tools.generate_structured_data(topic.content_type)
return topic
def get_all_content(self) -> List[ContentItem]:
return session.query(ContentItem).all()
def remove_content(self, content_id):
content = session.query(ContentItem).get(content_id)
if content:
session.delete(content)
session.commit()
def update_content(self, content_id, **kwargs):
content = session.query(ContentItem).get(content_id)
if content:
for key, value in kwargs.items():
setattr(content, key, value)
session.commit()
def get_calendar(self) -> Optional[List[ContentItem]]:
"""
Get the current calendar.
"""
self.logger.debug("Getting current calendar")
return self.get_all_content()
def update_calendar(self, calendar: List[ContentItem]) -> None:
"""
Update the current calendar.
"""
self.get_all_content()
for content in calendar:
session.add(content)
session.commit()
def export_calendar(self) -> Optional[Dict[str, Any]]:
"""Export the current calendar."""
self.logger.info("Exporting calendar")
calendar = self.get_calendar()
if not calendar:
self.logger.warning("No calendar to export")
return None
try:
calendar_data = [content.to_dict() for content in calendar]
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_data.json", "w") as f:
json.dump(calendar, f, indent=2, default=str)
def load_calendar_from_json(self):
if os.path.exists("calendar_data.json"):
with open("calendar_data.json", "r") as f:
data = json.load(f)
self.update_calendar(data)

View File

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

View File

@@ -1,626 +0,0 @@
from typing import Dict, List, Any, Optional
import logging
from pathlib import Path
import sys
from datetime import datetime, timedelta
# 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.database.models import ContentItem, ContentType, Platform
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
from lib.ai_seo_tools.content_calendar.core.content_repurposer import SmartContentRepurposingEngine
logger = logging.getLogger(__name__)
class ContentGenerator:
"""
Enhanced content generator with smart repurposing capabilities.
"""
def __init__(self):
self.logger = logging.getLogger('content_calendar.content_generator')
self.logger.info("Initializing ContentGenerator")
self._setup_logging()
self._load_ai_tools()
# Initialize the Smart Content Repurposing Engine
self.repurposing_engine = SmartContentRepurposingEngine()
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 {}
@handle_calendar_error
def repurpose_content_for_platforms(
self,
content_item: ContentItem,
target_platforms: List[Platform],
strategy: str = 'adaptive'
) -> List[ContentItem]:
"""
Repurpose existing content for multiple platforms using the Smart Content Repurposing Engine.
Args:
content_item: Original content to repurpose
target_platforms: List of platforms to create content for
strategy: Repurposing strategy ('adaptive', 'atomic', 'series')
Returns:
List of repurposed content items optimized for each platform
"""
try:
self.logger.info(f"Repurposing content '{content_item.title}' for {len(target_platforms)} platforms")
# Use the repurposing engine to create platform-specific content
repurposed_content = self.repurposing_engine.repurpose_single_content(
content=content_item,
target_platforms=target_platforms,
strategy=strategy
)
self.logger.info(f"Successfully created {len(repurposed_content)} repurposed content pieces")
return repurposed_content
except Exception as e:
self.logger.error(f"Error repurposing content: {str(e)}")
return []
@handle_calendar_error
def create_content_series_across_platforms(
self,
source_content: ContentItem,
platforms: List[Platform],
series_type: str = 'progressive_disclosure'
) -> Dict[str, List[ContentItem]]:
"""
Create a cross-platform content series with progressive disclosure strategy.
Args:
source_content: Original comprehensive content
platforms: Target platforms for the series
series_type: Type of series ('progressive_disclosure', 'platform_native')
Returns:
Dictionary mapping platforms to their content pieces
"""
try:
self.logger.info(f"Creating cross-platform series for '{source_content.title}'")
# Use the repurposing engine to create a content series
series_content = self.repurposing_engine.create_content_series(
content=source_content,
platforms=platforms,
series_type=series_type
)
total_pieces = sum(len(pieces) for pieces in series_content.values())
self.logger.info(f"Successfully created series with {total_pieces} pieces across {len(series_content)} platforms")
return series_content
except Exception as e:
self.logger.error(f"Error creating content series: {str(e)}")
return {}
@handle_calendar_error
def analyze_content_for_repurposing(
self,
content_item: ContentItem,
available_platforms: List[Platform]
) -> Dict[str, Any]:
"""
Analyze content and get AI-powered repurposing suggestions.
Args:
content_item: Content to analyze
available_platforms: Available platforms for repurposing
Returns:
Dictionary containing repurposing suggestions and analysis
"""
try:
self.logger.info(f"Analyzing content '{content_item.title}' for repurposing opportunities")
# Get repurposing suggestions from the engine
suggestions = self.repurposing_engine.get_repurposing_suggestions(
content=content_item,
available_platforms=available_platforms
)
# Add content analysis
content_text = content_item.description or content_item.notes or ""
content_atoms = self.repurposing_engine.analyze_content_atoms(
content=content_text,
title=content_item.title
)
analysis = {
'content_analysis': {
'word_count': len(content_text.split()) if content_text else 0,
'content_richness': self._assess_content_richness(content_atoms),
'repurposing_potential': self._assess_repurposing_potential(content_atoms),
'content_atoms': content_atoms
},
'platform_suggestions': suggestions['recommended_platforms'],
'strategy_suggestions': suggestions['repurposing_strategies'],
'estimated_output': {
'total_pieces': suggestions['estimated_pieces'],
'time_savings': f"{suggestions['estimated_pieces'] * 2} hours",
'content_multiplication': f"{suggestions['estimated_pieces']}x"
}
}
return analysis
except Exception as e:
self.logger.error(f"Error analyzing content for repurposing: {str(e)}")
return {}
def _assess_content_richness(self, content_atoms: Dict[str, List[str]]) -> str:
"""Assess the richness of content based on extracted atoms."""
total_atoms = sum(len(atoms) for atoms in content_atoms.values())
if total_atoms >= 15:
return "High"
elif total_atoms >= 8:
return "Medium"
else:
return "Low"
def _assess_repurposing_potential(self, content_atoms: Dict[str, List[str]]) -> str:
"""Assess the repurposing potential based on content atoms."""
# Check for diverse content types
atom_types_with_content = sum(1 for atoms in content_atoms.values() if atoms)
if atom_types_with_content >= 4:
return "Excellent"
elif atom_types_with_content >= 3:
return "Good"
elif atom_types_with_content >= 2:
return "Fair"
else:
return "Limited"
@handle_calendar_error
def generate_content_with_repurposing_plan(
self,
content_item: ContentItem,
context: Dict[str, Any],
target_platforms: List[Platform] = None
) -> Dict[str, Any]:
"""
Generate content along with a comprehensive repurposing plan.
Args:
content_item: Content item to generate
context: Content context from gap analysis
target_platforms: Platforms to include in repurposing plan
Returns:
Dictionary containing generated content and repurposing plan
"""
try:
self.logger.info(f"Generating content with repurposing plan for '{content_item.title}'")
# Generate the main content structure
headings = self.generate_headings(content_item, context)
subheadings = self.generate_subheadings(content_item, headings, context)
key_points = self.generate_key_points(content_item, context)
outline = {
'headings': headings,
'subheadings': subheadings,
'key_points': key_points
}
content_flow = self.generate_content_flow(content_item, outline)
# Create repurposing plan if platforms are specified
repurposing_plan = {}
if target_platforms:
# Analyze repurposing potential
analysis = self.analyze_content_for_repurposing(content_item, target_platforms)
# Generate repurposing suggestions
repurposing_plan = {
'analysis': analysis,
'recommended_strategy': self._recommend_repurposing_strategy(analysis),
'platform_roadmap': self._create_platform_roadmap(content_item, target_platforms),
'content_calendar_integration': self._suggest_calendar_integration(content_item, target_platforms)
}
return {
'content': {
'outline': outline,
'content_flow': content_flow,
'metadata': {
'generated_at': str(datetime.now()),
'content_type': content_item.content_type.name,
'platforms': [p.name for p in content_item.platforms] if content_item.platforms else []
}
},
'repurposing_plan': repurposing_plan
}
except Exception as e:
self.logger.error(f"Error generating content with repurposing plan: {str(e)}")
return {}
def _recommend_repurposing_strategy(self, analysis: Dict[str, Any]) -> str:
"""Recommend the best repurposing strategy based on content analysis."""
content_richness = analysis.get('content_analysis', {}).get('content_richness', 'Low')
repurposing_potential = analysis.get('content_analysis', {}).get('repurposing_potential', 'Limited')
if content_richness == 'High' and repurposing_potential in ['Excellent', 'Good']:
return 'progressive_disclosure'
elif content_richness in ['Medium', 'High']:
return 'adaptive'
else:
return 'atomic'
def _create_platform_roadmap(
self,
content_item: ContentItem,
target_platforms: List[Platform]
) -> Dict[str, Any]:
"""Create a roadmap for content distribution across platforms."""
roadmap = {
'timeline': {},
'platform_sequence': [],
'cross_promotion_opportunities': []
}
# Create a timeline for content release
base_date = content_item.publish_date or datetime.now()
for i, platform in enumerate(target_platforms):
release_date = base_date + timedelta(days=i)
roadmap['timeline'][platform.name] = {
'release_date': release_date.strftime('%Y-%m-%d'),
'content_type': self._get_optimal_content_type_for_platform(platform),
'engagement_strategy': self._get_engagement_strategy_for_platform(platform)
}
roadmap['platform_sequence'].append(platform.name)
return roadmap
def _suggest_calendar_integration(
self,
content_item: ContentItem,
target_platforms: List[Platform]
) -> Dict[str, Any]:
"""Suggest how to integrate repurposed content into the content calendar."""
return {
'scheduling_recommendations': {
'primary_content': 'Schedule as main content piece',
'repurposed_content': 'Schedule 1-2 days after primary content',
'series_content': 'Schedule weekly releases for maximum impact'
},
'calendar_tags': [
'repurposed_content',
f'source_{content_item.id}',
'multi_platform_series'
],
'performance_tracking': {
'metrics_to_track': ['engagement_rate', 'cross_platform_traffic', 'conversion_rate'],
'comparison_baseline': 'Compare against single-platform content performance'
}
}
def _get_optimal_content_type_for_platform(self, platform: Platform) -> str:
"""Get the optimal content type for a specific platform."""
platform_content_types = {
Platform.TWITTER: 'Thread or single tweet',
Platform.LINKEDIN: 'Professional post or article',
Platform.INSTAGRAM: 'Visual post with caption',
Platform.FACEBOOK: 'Engaging post with discussion starter',
Platform.WEBSITE: 'Full blog post or article'
}
return platform_content_types.get(platform, 'Standard post')
def _get_engagement_strategy_for_platform(self, platform: Platform) -> str:
"""Get the engagement strategy for a specific platform."""
engagement_strategies = {
Platform.TWITTER: 'Use hashtags, engage in conversations, create polls',
Platform.LINKEDIN: 'Professional networking, thought leadership, industry discussions',
Platform.INSTAGRAM: 'Visual storytelling, user-generated content, stories',
Platform.FACEBOOK: 'Community building, discussions, live interactions',
Platform.WEBSITE: 'SEO optimization, internal linking, lead magnets'
}
return engagement_strategies.get(platform, 'Standard engagement tactics')

View File

@@ -1,599 +0,0 @@
from typing import Dict, List, Any, Optional, Tuple
import logging
import re
from datetime import datetime, timedelta
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.database.models import ContentItem, ContentType, Platform, SEOData
from lib.gpt_providers.text_generation.main_text_generation import llm_text_gen
from ..utils.error_handling import handle_calendar_error
logger = logging.getLogger(__name__)
class ContentAtomizer:
"""
Break down content into atomic pieces that can be recombined
for different platforms and purposes.
"""
def __init__(self):
self.logger = logging.getLogger('content_calendar.atomizer')
def atomize_content(self, content: str, title: str = "") -> Dict[str, List[str]]:
"""
Extract key quotes, statistics, tips, and examples from content.
Args:
content: The content text to atomize
title: The content title for context
Returns:
Dictionary containing different types of content atoms
"""
try:
self.logger.info(f"Atomizing content: {title[:50]}...")
# Use AI to extract content atoms
prompt = f"""
Analyze the following content and extract key elements that can be repurposed:
Title: {title}
Content: {content[:3000]}...
Extract and categorize the following elements:
1. Key Statistics (numbers, percentages, data points)
2. Quotable Insights (memorable quotes or key insights)
3. Actionable Tips (practical advice or steps)
4. Examples/Case Studies (real examples or stories)
5. Key Questions (thought-provoking questions)
6. Main Arguments (core points or arguments)
Format your response as JSON:
{{
"statistics": ["stat1", "stat2", ...],
"quotes": ["quote1", "quote2", ...],
"tips": ["tip1", "tip2", ...],
"examples": ["example1", "example2", ...],
"questions": ["question1", "question2", ...],
"arguments": ["argument1", "argument2", ...]
}}
"""
response = llm_text_gen(
prompt=prompt,
system_prompt="You are an expert content analyst. Extract key elements that can be repurposed across different platforms.",
json_struct={
"type": "object",
"properties": {
"statistics": {"type": "array", "items": {"type": "string"}},
"quotes": {"type": "array", "items": {"type": "string"}},
"tips": {"type": "array", "items": {"type": "string"}},
"examples": {"type": "array", "items": {"type": "string"}},
"questions": {"type": "array", "items": {"type": "string"}},
"arguments": {"type": "array", "items": {"type": "string"}}
}
}
)
if response:
return response
else:
# Fallback to basic extraction
return self._basic_content_extraction(content)
except Exception as e:
self.logger.error(f"Error atomizing content: {str(e)}")
return self._basic_content_extraction(content)
def _basic_content_extraction(self, content: str) -> Dict[str, List[str]]:
"""Fallback method for basic content extraction."""
atoms = {
"statistics": [],
"quotes": [],
"tips": [],
"examples": [],
"questions": [],
"arguments": []
}
# Extract statistics (numbers with %)
stats = re.findall(r'\d+%|\d+\.\d+%|\d+,\d+|\d+ percent', content)
atoms["statistics"] = stats[:5] # Limit to 5
# Extract questions
questions = re.findall(r'[A-Z][^.!?]*\?', content)
atoms["questions"] = questions[:3] # Limit to 3
# Extract sentences that might be tips (containing words like "should", "must", "need to")
tip_patterns = r'[^.!?]*(?:should|must|need to|important to|remember to)[^.!?]*[.!?]'
tips = re.findall(tip_patterns, content, re.IGNORECASE)
atoms["tips"] = tips[:5] # Limit to 5
return atoms
class ContentRepurposer:
"""
Main content repurposing engine that transforms content for different platforms.
"""
def __init__(self):
self.logger = logging.getLogger('content_calendar.repurposer')
self.atomizer = ContentAtomizer()
# Platform-specific content specifications
self.platform_specs = {
Platform.TWITTER: {
'max_length': 280,
'optimal_length': 240,
'format': 'concise',
'tone': 'engaging',
'hashtags': True,
'mentions': True
},
Platform.LINKEDIN: {
'max_length': 3000,
'optimal_length': 1500,
'format': 'professional',
'tone': 'authoritative',
'hashtags': True,
'mentions': False
},
Platform.INSTAGRAM: {
'max_length': 2200,
'optimal_length': 1000,
'format': 'visual-focused',
'tone': 'casual',
'hashtags': True,
'mentions': True
},
Platform.FACEBOOK: {
'max_length': 63206,
'optimal_length': 500,
'format': 'engaging',
'tone': 'conversational',
'hashtags': False,
'mentions': True
},
Platform.WEBSITE: {
'max_length': None,
'optimal_length': 2000,
'format': 'comprehensive',
'tone': 'informative',
'hashtags': False,
'mentions': False
}
}
@handle_calendar_error
def repurpose_content(
self,
source_content: ContentItem,
target_platforms: List[Platform],
repurpose_strategy: str = 'adaptive'
) -> List[ContentItem]:
"""
Repurpose content for multiple platforms.
Args:
source_content: Original content to repurpose
target_platforms: List of platforms to create content for
repurpose_strategy: Strategy for repurposing ('adaptive', 'atomic', 'series')
Returns:
List of repurposed content items
"""
try:
self.logger.info(f"Repurposing content '{source_content.title}' for {len(target_platforms)} platforms")
repurposed_content = []
# Get content text (assuming it's in description or notes)
content_text = source_content.description or source_content.notes or ""
if not content_text:
self.logger.warning("No content text found for repurposing")
return []
# Atomize the content
atoms = self.atomizer.atomize_content(content_text, source_content.title)
# Generate repurposed content for each platform
for platform in target_platforms:
if platform == source_content.platforms[0] if source_content.platforms else None:
continue # Skip the original platform
repurposed_item = self._create_platform_specific_content(
source_content=source_content,
target_platform=platform,
atoms=atoms,
strategy=repurpose_strategy
)
if repurposed_item:
repurposed_content.append(repurposed_item)
self.logger.info(f"Successfully repurposed content into {len(repurposed_content)} variations")
return repurposed_content
except Exception as e:
self.logger.error(f"Error repurposing content: {str(e)}")
return []
def _create_platform_specific_content(
self,
source_content: ContentItem,
target_platform: Platform,
atoms: Dict[str, List[str]],
strategy: str
) -> Optional[ContentItem]:
"""Create platform-specific content variation."""
try:
platform_spec = self.platform_specs.get(target_platform, {})
# Generate platform-specific content using AI
repurposed_text = self._generate_platform_content(
source_content=source_content,
target_platform=target_platform,
atoms=atoms,
platform_spec=platform_spec,
strategy=strategy
)
if not repurposed_text:
return None
# Create new content item
repurposed_item = ContentItem(
title=self._adapt_title_for_platform(source_content.title, target_platform),
description=repurposed_text,
content_type=self._determine_content_type_for_platform(target_platform),
platforms=[target_platform],
publish_date=source_content.publish_date + timedelta(days=1), # Schedule for next day
status="draft",
author=source_content.author,
tags=source_content.tags + [f"repurposed_from_{source_content.id}"],
notes=f"Repurposed from: {source_content.title}",
seo_data=self._adapt_seo_data_for_platform(source_content.seo_data, target_platform)
)
return repurposed_item
except Exception as e:
self.logger.error(f"Error creating platform-specific content: {str(e)}")
return None
def _generate_platform_content(
self,
source_content: ContentItem,
target_platform: Platform,
atoms: Dict[str, List[str]],
platform_spec: Dict[str, Any],
strategy: str
) -> str:
"""Generate content optimized for specific platform."""
try:
# Prepare content elements
title = source_content.title
original_content = source_content.description or ""
# Create platform-specific prompt
prompt = self._create_repurposing_prompt(
title=title,
original_content=original_content,
target_platform=target_platform,
atoms=atoms,
platform_spec=platform_spec,
strategy=strategy
)
# Generate content using AI
repurposed_content = llm_text_gen(prompt)
return repurposed_content or ""
except Exception as e:
self.logger.error(f"Error generating platform content: {str(e)}")
return ""
def _create_repurposing_prompt(
self,
title: str,
original_content: str,
target_platform: Platform,
atoms: Dict[str, List[str]],
platform_spec: Dict[str, Any],
strategy: str
) -> str:
"""Create AI prompt for content repurposing."""
platform_guidelines = {
Platform.TWITTER: "Create engaging tweets that drive conversation. Use threads for complex topics. Include relevant hashtags.",
Platform.LINKEDIN: "Write professional content that provides value to business professionals. Focus on insights and actionable advice.",
Platform.INSTAGRAM: "Create visually-oriented content with engaging captions. Use storytelling and include relevant hashtags.",
Platform.FACEBOOK: "Write conversational content that encourages engagement. Ask questions and create community discussion.",
Platform.WEBSITE: "Create comprehensive, SEO-optimized content with clear structure and valuable information."
}
atoms_text = ""
for atom_type, atom_list in atoms.items():
if atom_list:
atoms_text += f"\n{atom_type.title()}: {', '.join(atom_list[:3])}"
prompt = f"""
Repurpose the following content for {target_platform.name}:
Original Title: {title}
Original Content: {original_content[:1500]}...
Key Content Elements:{atoms_text}
Platform Guidelines: {platform_guidelines.get(target_platform, '')}
Platform Specifications:
- Optimal Length: {platform_spec.get('optimal_length', 'flexible')} characters
- Format: {platform_spec.get('format', 'standard')}
- Tone: {platform_spec.get('tone', 'professional')}
- Include Hashtags: {platform_spec.get('hashtags', False)}
Requirements:
1. Adapt the content to fit {target_platform.name}'s format and audience
2. Maintain the core message and value
3. Optimize for {target_platform.name} engagement
4. Include platform-appropriate calls to action
5. Use the extracted content elements effectively
Create compelling, platform-optimized content that will perform well on {target_platform.name}.
"""
return prompt
def _adapt_title_for_platform(self, original_title: str, platform: Platform) -> str:
"""Adapt title for specific platform."""
platform_prefixes = {
Platform.TWITTER: "🧵 ",
Platform.LINKEDIN: "💼 ",
Platform.INSTAGRAM: "📸 ",
Platform.FACEBOOK: "💬 ",
Platform.WEBSITE: ""
}
prefix = platform_prefixes.get(platform, "")
return f"{prefix}{original_title}"
def _determine_content_type_for_platform(self, platform: Platform) -> ContentType:
"""Determine appropriate content type for platform."""
platform_content_types = {
Platform.TWITTER: ContentType.SOCIAL_MEDIA,
Platform.LINKEDIN: ContentType.SOCIAL_MEDIA,
Platform.INSTAGRAM: ContentType.SOCIAL_MEDIA,
Platform.FACEBOOK: ContentType.SOCIAL_MEDIA,
Platform.WEBSITE: ContentType.BLOG_POST
}
return platform_content_types.get(platform, ContentType.SOCIAL_MEDIA)
def _adapt_seo_data_for_platform(self, original_seo: SEOData, platform: Platform) -> SEOData:
"""Adapt SEO data for specific platform."""
if platform == Platform.WEBSITE:
return original_seo
# For social media platforms, create simplified SEO data
return SEOData(
title=original_seo.title,
meta_description=original_seo.meta_description[:160] if original_seo.meta_description else "",
keywords=original_seo.keywords[:5] if original_seo.keywords else [],
structured_data={}
)
class ContentSeriesRepurposer:
"""
Create cross-platform content series with progressive disclosure strategy.
"""
def __init__(self):
self.logger = logging.getLogger('content_calendar.series_repurposer')
self.repurposer = ContentRepurposer()
def create_cross_platform_series(
self,
source_content: ContentItem,
platforms: List[Platform],
series_strategy: str = 'progressive_disclosure'
) -> Dict[str, List[ContentItem]]:
"""
Create a content series that progressively reveals information
across different platforms, driving traffic between them.
Args:
source_content: Original comprehensive content
platforms: Target platforms for the series
series_strategy: Strategy for content distribution
Returns:
Dictionary mapping platforms to their content pieces
"""
try:
self.logger.info(f"Creating cross-platform series for: {source_content.title}")
series_content = {}
if series_strategy == 'progressive_disclosure':
series_content = self._create_progressive_disclosure_series(
source_content, platforms
)
elif series_strategy == 'platform_native':
series_content = self._create_platform_native_series(
source_content, platforms
)
else:
# Default to simple repurposing
repurposed = self.repurposer.repurpose_content(
source_content, platforms
)
for item in repurposed:
platform = item.platforms[0]
if platform not in series_content:
series_content[platform] = []
series_content[platform].append(item)
return series_content
except Exception as e:
self.logger.error(f"Error creating cross-platform series: {str(e)}")
return {}
def _create_progressive_disclosure_series(
self,
source_content: ContentItem,
platforms: List[Platform]
) -> Dict[str, List[ContentItem]]:
"""Create series with progressive information disclosure."""
series_content = {}
# Define disclosure strategy
disclosure_strategy = {
Platform.TWITTER: "teaser", # Hook with key stat/question
Platform.INSTAGRAM: "visual", # Visual summary with key points
Platform.LINKEDIN: "insight", # Professional insight/analysis
Platform.FACEBOOK: "discussion", # Community discussion starter
Platform.WEBSITE: "complete" # Full detailed content
}
for platform in platforms:
strategy = disclosure_strategy.get(platform, "summary")
content_piece = self._create_disclosure_content(
source_content, platform, strategy
)
if content_piece:
series_content[platform] = [content_piece]
return series_content
def _create_disclosure_content(
self,
source_content: ContentItem,
platform: Platform,
disclosure_type: str
) -> Optional[ContentItem]:
"""Create content piece for specific disclosure strategy."""
try:
# This would use the repurposer with specific instructions
# for the disclosure type
repurposed = self.repurposer._create_platform_specific_content(
source_content=source_content,
target_platform=platform,
atoms=self.repurposer.atomizer.atomize_content(
source_content.description or "",
source_content.title
),
strategy=disclosure_type
)
return repurposed
except Exception as e:
self.logger.error(f"Error creating disclosure content: {str(e)}")
return None
def _create_platform_native_series(
self,
source_content: ContentItem,
platforms: List[Platform]
) -> Dict[str, List[ContentItem]]:
"""Create series optimized for each platform's native format."""
# Implementation for platform-native series
# This would create multiple pieces per platform
# optimized for that platform's specific characteristics
return {}
# Main repurposing interface
class SmartContentRepurposingEngine:
"""
Main interface for the Smart Content Repurposing Engine.
"""
def __init__(self):
self.logger = logging.getLogger('content_calendar.repurposing_engine')
self.repurposer = ContentRepurposer()
self.series_repurposer = ContentSeriesRepurposer()
self.atomizer = ContentAtomizer()
def repurpose_single_content(
self,
content: ContentItem,
target_platforms: List[Platform],
strategy: str = 'adaptive'
) -> List[ContentItem]:
"""Repurpose a single piece of content."""
return self.repurposer.repurpose_content(content, target_platforms, strategy)
def create_content_series(
self,
content: ContentItem,
platforms: List[Platform],
series_type: str = 'progressive_disclosure'
) -> Dict[str, List[ContentItem]]:
"""Create a cross-platform content series."""
return self.series_repurposer.create_cross_platform_series(
content, platforms, series_type
)
def analyze_content_atoms(self, content: str, title: str = "") -> Dict[str, List[str]]:
"""Analyze content and extract reusable atoms."""
return self.atomizer.atomize_content(content, title)
def get_repurposing_suggestions(
self,
content: ContentItem,
available_platforms: List[Platform]
) -> Dict[str, Any]:
"""Get AI-powered suggestions for content repurposing."""
try:
# Analyze content to suggest best repurposing strategies
content_text = content.description or content.notes or ""
atoms = self.atomizer.atomize_content(content_text, content.title)
suggestions = {
'recommended_platforms': [],
'repurposing_strategies': [],
'content_atoms': atoms,
'estimated_pieces': 0
}
# Analyze content type and suggest platforms
if content.content_type == ContentType.BLOG_POST:
suggestions['recommended_platforms'] = [
Platform.TWITTER, Platform.LINKEDIN, Platform.INSTAGRAM
]
suggestions['estimated_pieces'] = len(available_platforms) * 2
elif content.content_type == ContentType.VIDEO:
suggestions['recommended_platforms'] = [
Platform.TWITTER, Platform.INSTAGRAM, Platform.FACEBOOK
]
suggestions['estimated_pieces'] = len(available_platforms) * 3
# Suggest strategies based on content richness
if len(atoms.get('statistics', [])) > 3:
suggestions['repurposing_strategies'].append('data_driven')
if len(atoms.get('tips', [])) > 5:
suggestions['repurposing_strategies'].append('tip_series')
if len(atoms.get('examples', [])) > 2:
suggestions['repurposing_strategies'].append('case_study_series')
return suggestions
except Exception as e:
self.logger.error(f"Error getting repurposing suggestions: {str(e)}")
return {
'recommended_platforms': [],
'repurposing_strategies': [],
'content_atoms': {},
'estimated_pieces': 0
}

View File

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

View File

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

View File

@@ -1,307 +0,0 @@
"""
Unified platform adapter for content adaptation across different platforms.
"""
import logging
from typing import Dict, Any, List, Optional, TypedDict
from datetime import datetime
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
class ContentItem(TypedDict):
"""Type definition for content items."""
id: str
title: str
content: str
platforms: List[str]
status: str
created_at: datetime
updated_at: datetime
published_at: Optional[datetime]
metadata: Dict[str, Any]
analytics: Optional[Dict[str, Any]]
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 get_content_performance(self, content_item: ContentItem) -> Dict[str, Any]:
"""Get performance metrics for content across platforms."""
try:
logger.info(f"Getting performance metrics for content: {getattr(content_item, 'title', 'Untitled')}")
# Get platform from content item
platforms = getattr(content_item, 'platforms', None)
if platforms and len(platforms) > 0:
platform = platforms[0].name if hasattr(platforms[0], 'name') else str(platforms[0])
else:
platform = 'Unknown'
# Initialize performance metrics
performance = {
'engagement_metrics': {
'likes': 0,
'comments': 0,
'shares': 0,
'reach': 0
},
'seo_metrics': {
'impressions': 0,
'clicks': 0,
'ctr': 0,
'position': 0
},
'conversion_metrics': {
'conversions': 0,
'conversion_rate': 0,
'revenue': 0
},
'platform_specific': {},
'performance_trends': [],
'recommendations': []
}
# Add platform-specific metrics
if platform.upper() == 'WEBSITE':
performance['platform_specific'] = {
'bounce_rate': 0,
'time_on_page': 0,
'page_views': 0
}
return performance
except Exception as e:
error_msg = f"Error getting content performance: {str(e)}"
logger.error(error_msg, exc_info=True)
return {
'error': error_msg,
'metrics': {},
'trends': {},
'recommendations': []
}
def _handle_instagram(self, data: Dict[str, Any]) -> Dict[str, Any]:
"""Handle Instagram content generation."""
try:
# Generate Instagram-specific content
caption = metadesc_generator_main(data)
hashtags = self._generate_hashtags(data)
return {
'platform': 'instagram',
'content': {
'caption': caption,
'hashtags': hashtags,
'media_suggestions': self._get_media_suggestions(data)
}
}
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:
# Generate LinkedIn-specific content
post = metadesc_generator_main(data)
return {
'platform': 'linkedin',
'content': {
'post': post,
'engagement_optimization': self._get_engagement_suggestions(data),
'media_suggestions': self._get_media_suggestions(data)
}
}
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:
# Generate Twitter-specific content
tweet = metadesc_generator_main(data)
hashtags = self._generate_hashtags(data)
return {
'platform': 'twitter',
'content': {
'tweet': tweet,
'hashtags': hashtags,
'thread_structure': self._get_thread_structure(data),
'media_suggestions': self._get_media_suggestions(data)
}
}
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:
# Generate Facebook-specific content
post = metadesc_generator_main(data)
return {
'platform': 'facebook',
'content': {
'post': post,
'engagement_optimization': self._get_engagement_suggestions(data),
'media_suggestions': self._get_media_suggestions(data)
}
}
except Exception as e:
logger.error(f"Error generating Facebook content: {str(e)}")
return {
'platform': 'facebook',
'error': str(e)
}
def _generate_hashtags(self, data: Dict[str, Any]) -> List[str]:
"""Generate relevant hashtags for content."""
try:
# Extract keywords from content
keywords = data.get('keywords', [])
# Add platform-specific hashtags
platform = data.get('platform', '').lower()
platform_hashtags = {
'instagram': ['#instagood', '#photooftheday'],
'twitter': ['#trending', '#followme'],
'linkedin': ['#business', '#professional'],
'facebook': ['#social', '#community']
}.get(platform, [])
return keywords + platform_hashtags
except Exception as e:
logger.error(f"Error generating hashtags: {str(e)}")
return []
def _get_media_suggestions(self, data: Dict[str, Any]) -> List[Dict[str, Any]]:
"""Get media suggestions for content."""
try:
# Generate media suggestions based on content type
content_type = data.get('type', 'post')
suggestions = []
if content_type == 'blog':
suggestions.append({
'type': 'featured_image',
'description': 'Main blog post image',
'dimensions': '1200x630'
})
elif content_type == 'social':
suggestions.append({
'type': 'post_image',
'description': 'Social media post image',
'dimensions': '1080x1080'
})
return suggestions
except Exception as e:
logger.error(f"Error getting media suggestions: {str(e)}")
return []
def _get_engagement_suggestions(self, data: Dict[str, Any]) -> Dict[str, Any]:
"""Get engagement optimization suggestions."""
try:
return {
'best_posting_times': ['9:00 AM', '5:00 PM'],
'engagement_tips': [
'Ask questions to encourage comments',
'Use relevant hashtags',
'Include a clear call-to-action'
],
'content_length': {
'optimal': '150-200 characters',
'maximum': '300 characters'
}
}
except Exception as e:
logger.error(f"Error getting engagement suggestions: {str(e)}")
return {}
def _get_thread_structure(self, data: Dict[str, Any]) -> List[Dict[str, Any]]:
"""Get thread structure for Twitter threads."""
try:
content = data.get('content', '')
sentences = content.split('.')
thread = []
current_tweet = ''
for sentence in sentences:
if len(current_tweet + sentence) <= 280:
current_tweet += sentence + '.'
else:
if current_tweet:
thread.append({
'content': current_tweet.strip(),
'type': 'tweet'
})
current_tweet = sentence + '.'
if current_tweet:
thread.append({
'content': current_tweet.strip(),
'type': 'tweet'
})
return thread
except Exception as e:
logger.error(f"Error generating thread structure: {str(e)}")
return []

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,294 +0,0 @@
import streamlit as st
from typing import Dict, Any, List
from lib.database.models import ContentItem
import logging
from lib.ai_seo_tools.content_calendar.core.content_generator import ContentGenerator
from lib.ai_seo_tools.content_calendar.core.calendar_manager import CalendarManager
logger = logging.getLogger(__name__)
def render_ab_testing(content_generator: ContentGenerator, calendar_manager: CalendarManager):
"""Render the A/B testing interface."""
st.header("A/B Testing")
# 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 = 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("""
## Welcome to A/B Testing! 🧪
Test different versions of your content to find what works best. Here's what you can do:
### Features:
- 🔄 **Variant Generation**: Create multiple versions of your content
- 📊 **Performance Tracking**: Compare metrics across variants
- 📈 **Statistical Analysis**: Get data-driven insights
- 🎯 **Winner Selection**: Identify the best performing content
### Getting Started:
1. First, add some content to your calendar
2. Select the content you want to test
3. Generate variants with different parameters
4. Track performance and analyze results
Ready to get started? Add some content to your calendar first!
""")
return
# Content Selection
selected_content = st.selectbox(
"Select content to test",
options=content_options,
key="ab_test_content_select"
)
if selected_content:
try:
content_item = next(
item for item in available_content
if item.title == selected_content
)
# Show onboarding info if no test history
if not st.session_state.get('ab_test_results', {}).get(content_item.title):
st.info("""
### A/B Testing Guide
Create and compare different versions of your content:
- **Headline Variations**: Test different titles and hooks
- **Content Structure**: Try different content flows
- **Call-to-Action**: Test various CTAs
- **Visual Elements**: Compare different media placements
Click 'Generate Test Variants' to get started!
""")
# Test Configuration
st.markdown("### Create A/B Test")
col1, col2 = st.columns([2, 1])
with col1:
test_content = st.selectbox(
"Select content to A/B test",
options=content_options,
key="ab_test_content_select_unique"
)
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'],
index=0
)
with col2:
audience_size = st.select_slider(
"Audience Size",
options=['Small', 'Medium', 'Large'],
value='Medium'
)
confidence_level = st.slider(
"Confidence Level",
min_value=90,
max_value=99,
value=95,
help="Statistical confidence level for test results"
)
# Generate Variants
if st.button("Generate Variants"):
with st.spinner("Generating variants..."):
variants = _generate_ab_test_variants(content_generator, content_item, num_variants)
if variants:
st.success(f"Generated {len(variants)} variants!")
# Display variants in tabs
variant_tabs = st.tabs([f"Variant {i+1}" for i in range(len(variants))])
for i, tab in enumerate(variant_tabs):
with tab:
st.markdown(f"### Variant {i+1}")
st.json(variants[i]['content'])
# Variant metrics
col1, col2, col3 = st.columns(3)
with col1:
st.metric(
"Engagement Score",
f"{variants[i]['metrics']['engagement_score']:.1f}%"
)
with col2:
st.metric(
"Conversion Rate",
f"{variants[i]['metrics']['conversion_rate']:.1f}%"
)
with col3:
st.metric(
"Reach",
f"{variants[i]['metrics']['reach']:,}"
)
# Results Analysis
st.markdown("### Analyze Results")
if test_content in st.session_state.ab_test_results:
test_data = st.session_state.ab_test_results[test_content]
# Test Status
st.info(f"Test Status: {test_data['status']}")
st.write(f"Started: {test_data['start_time']}")
if test_data['status'] == 'running':
if st.button("End Test and Analyze"):
with st.spinner("Analyzing results..."):
results = _analyze_ab_test_results(content_item)
if results:
st.success("Analysis complete!")
_display_test_results(results)
except Exception as e:
logger.error(f"Error in A/B testing interface: {str(e)}", exc_info=True)
st.error(f"Error in A/B testing: {str(e)}")
def _generate_ab_test_variants(
content_generator,
content: ContentItem,
num_variants: int
) -> List[Dict[str, Any]]:
"""Generate A/B test variants for content."""
try:
logger.info(f"Generating {num_variants} variants for content: {content.title}")
# Convert content to dictionary format
content_dict = {
'title': content.title,
'content': content.description,
'metadata': {
'platform': content.platforms[0].name if content.platforms else 'Unknown',
'content_type': content.content_type.name
}
}
variants = []
for i in range(num_variants):
# Generate different variations
variant = content_generator.generate_variation(
content=content_dict,
variation_type=f"variant_{i+1}"
)
if variant:
variants.append(variant)
return variants
except Exception as e:
logger.error(f"Error generating variants: {str(e)}")
return []
def _analyze_ab_test_results(content_item: ContentItem) -> Dict[str, Any]:
"""Analyze results of A/B testing for content optimization."""
try:
logger.info(f"Analyzing A/B test results for: {content_item.title}")
if content_item.title not in st.session_state.ab_test_results:
raise ValueError("No A/B test results found for this content")
test_data = st.session_state.ab_test_results[content_item.title]
variants = test_data['variants']
# Calculate performance metrics
results = {
'total_engagement': sum(v['metrics']['engagement_score'] for v in variants),
'total_conversions': sum(v['metrics']['conversion_rate'] for v in variants),
'total_reach': sum(v['metrics']['reach'] for v in variants),
'best_performing_variant': max(variants, key=lambda x: x['metrics']['engagement_score']),
'recommendations': []
}
# Generate recommendations
for variant in variants:
if variant['metrics']['engagement_score'] > 0.7: # High engagement threshold
results['recommendations'].append({
'variant_id': variant['variant_id'],
'reason': 'High engagement score',
'suggested_actions': ['Scale this variant', 'Apply learnings to other content']
})
# Update test status
test_data['status'] = 'completed'
test_data['results'] = results
logger.info("A/B test results analyzed successfully")
return results
except Exception as e:
logger.error(f"Error analyzing A/B test results: {str(e)}", exc_info=True)
st.error(f"Error analyzing A/B test results: {str(e)}")
return {}
def _display_test_results(results: Dict[str, Any]) -> None:
"""Display A/B test results in the UI."""
with st.expander("Overall Performance", expanded=True):
col1, col2, col3 = st.columns(3)
with col1:
st.metric(
"Total Engagement",
f"{results['total_engagement']:.1f}%"
)
with col2:
st.metric(
"Total Conversions",
f"{results['total_conversions']:.1f}%"
)
with col3:
st.metric(
"Total Reach",
f"{results['total_reach']:,}"
)
with st.expander("Best Performing Variant", expanded=True):
best_variant = results['best_performing_variant']
st.markdown(f"### {best_variant['variant_id']}")
st.json(best_variant['content'])
with st.expander("Recommendations", expanded=True):
for rec in results['recommendations']:
st.markdown(f"#### {rec['variant_id']}")
st.write(f"Reason: {rec['reason']}")
st.write("Suggested Actions:")
for action in rec['suggested_actions']:
st.write(f"- {action}")

View File

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

View File

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

View File

@@ -1,498 +0,0 @@
import streamlit as st
from typing import Dict, Any, List
from datetime import datetime
import pandas as pd
from lib.ai_seo_tools.content_calendar.core.content_generator import ContentGenerator
from lib.ai_seo_tools.content_calendar.core.ai_generator import AIGenerator
from lib.ai_seo_tools.content_calendar.integrations.seo_optimizer import SEOOptimizer
from lib.database.models import ContentItem, ContentType, Platform, SEOData
import logging
from lib.database.models import get_engine, get_session, init_db
logger = logging.getLogger('content_calendar.optimization')
engine = get_engine()
init_db(engine)
session = get_session(engine)
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.title("Content Calendar")
# 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
# Create main tabs
main_tabs = st.tabs(["Content Planning", "Content Optimization"])
with main_tabs[0]:
# Create two columns for the layout
col1, col2 = st.columns([1, 1])
with col1:
st.header("Quick Calendar Generation")
st.markdown("""
Generate a content calendar in three simple steps:
1. Enter your keywords
2. Select target platforms
3. Choose time period
""")
# Step 1: Keywords Input
st.subheader("Step 1: Enter Keywords")
keywords = st.text_area(
"Enter keywords or topics (one per line)",
help="Enter the main topics or keywords you want to create content about"
)
# Step 2: Platform Selection
st.subheader("Step 2: Select Target Platforms")
platform_categories = {
"Website": ["WEBSITE"],
"Social Media": ["INSTAGRAM", "FACEBOOK", "TWITTER", "LINKEDIN"],
"Video": ["YOUTUBE"],
"Newsletter": ["NEWSLETTER"]
}
selected_platforms = []
for category, platforms in platform_categories.items():
st.markdown(f"**{category}**")
for platform in platforms:
if st.checkbox(platform.replace("_", " ").title(), key=f"platform_{platform}"):
selected_platforms.append(platform)
# Step 3: Time Period
st.subheader("Step 3: Choose Time Period")
time_period = st.selectbox(
"Select time period",
["1 Week", "2 Weeks", "1 Month", "3 Months", "6 Months"],
help="Choose how far ahead you want to plan your content"
)
# Generate Calendar Button
if st.button("Generate with AI", type="primary"):
if not keywords or not selected_platforms:
st.error("Please enter keywords and select at least one platform.")
else:
with st.spinner("Generating content calendar..."):
try:
# Generate content ideas based on keywords
content_ideas = []
for keyword in keywords.split('\n'):
if keyword.strip():
# Generate content ideas for each platform
for platform in selected_platforms:
try:
# Create a content item for the AI generator
content_item = ContentItem(
title=keyword.strip(),
description=f"Content about {keyword.strip()}",
content_type=ContentType.BLOG_POST if platform == "WEBSITE" else ContentType.SOCIAL_MEDIA,
platforms=[Platform[platform]],
publish_date=datetime.now(),
seo_data=SEOData(
title=keyword.strip(),
meta_description=f"Content about {keyword.strip()}",
keywords=[keyword.strip()],
structured_data={}
)
)
# Generate content using AI generator
content_idea = ai_generator.enhance_content(
content=content_item,
enhancement_type='content_generation',
target_audience={
'content_settings': {
'tone': 'professional',
'length': 'medium',
'engagement_goal': 'awareness',
'creativity_level': 5
}
}
)
if content_idea:
content_ideas.append({
'title': content_idea.get('title', keyword.strip()),
'introduction': content_idea.get('content', f"Content about {keyword.strip()}"),
'platform': platform,
'meta_description': content_idea.get('meta_description', ''),
'keywords': [keyword.strip()]
})
except Exception as e:
logger.error(f"Error generating content for {keyword} on {platform}: {str(e)}")
continue
if content_ideas:
# Create calendar entries
calendar = st.session_state.calendar_manager.get_calendar()
for idea in content_ideas:
try:
# Create content item
content_item = ContentItem(
title=idea['title'],
description=idea['introduction'],
content_type=ContentType.BLOG_POST if idea['platform'] == "WEBSITE" else ContentType.SOCIAL_MEDIA,
platforms=[Platform[idea['platform']]],
publish_date=datetime.now(),
seo_data=SEOData(
title=idea['title'],
meta_description=idea.get('meta_description', ''),
keywords=idea.get('keywords', []),
structured_data={}
)
)
calendar.add_content(content_item)
except Exception as e:
logger.error(f"Error adding content to calendar: {str(e)}")
continue
st.success("Content calendar generated successfully!")
st.rerun() # Refresh to show new content
else:
st.error("Failed to generate any content ideas. Please try different keywords or settings.")
except Exception as e:
logger.error(f"Error generating content calendar: {str(e)}")
st.error("An error occurred while generating the content calendar. Please try again.")
with col2:
st.header("Scheduled Content")
# Get all content from calendar
calendar = st.session_state.calendar_manager.get_calendar()
if not calendar:
st.info("No content scheduled yet. Generate content using the form on the left.")
else:
# Group content by platform
platform_content = {}
for item in calendar.get_all_content():
platform = item.platforms[0].name if item.platforms else "Unknown"
if platform not in platform_content:
platform_content[platform] = []
platform_content[platform].append(item)
# Create tabs for each platform
platform_tabs = st.tabs(list(platform_content.keys()))
for i, (platform, content) in enumerate(platform_content.items()):
with platform_tabs[i]:
st.write(f"### {platform} Content")
# Convert content to DataFrame for better display
content_data = []
for item in content:
content_data.append({
'Date': item.publish_date.strftime('%Y-%m-%d'),
'Title': item.title,
'Type': item.content_type.name,
'Status': item.status
})
if content_data:
df = pd.DataFrame(content_data)
st.dataframe(df, use_container_width=True)
# Add action buttons for each content item
for item in content:
with st.expander(f"Actions for: {item.title}"):
col1, col2, col3 = st.columns(3)
with col1:
if st.button("Edit", key=f"edit_{item.title}"):
st.session_state.selected_content = item.title
with col2:
if st.button("Optimize", key=f"optimize_{item.title}"):
st.session_state.selected_content = item.title
st.session_state.active_tab = "Content Optimization"
with col3:
if st.button("Delete", key=f"delete_{item.title}"):
calendar.remove_content(item)
st.success(f"Removed {item.title}")
st.rerun()
with main_tabs[1]:
st.header("Content Optimization")
# Get available content
calendar = st.session_state.calendar_manager.get_calendar()
if not calendar:
st.info("No content available for optimization. Use the Content Planning tab to generate content.")
return
available_content = calendar.get_all_content()
content_options = [item.title for item in available_content]
# 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")
# Show onboarding info if no optimization history
if not optimization_manager.get_optimization_history(content_item.title):
st.info("""
### Content Optimization Guide
Use these tools to enhance your content:
- **Content Tone**: Adjust the writing style to match your brand voice
- **Content Length**: Optimize for your target platform's requirements
- **Engagement Goal**: Focus on specific audience actions
- **Creativity Level**: Balance between creative and professional content
Click 'Generate Optimization' to get started!
""")
# Advanced Optimization Settings
col1, col2 = st.columns(2)
with col1:
tone = st.select_slider(
"Content Tone",
options=["Professional", "Casual", "Educational", "Entertaining", "Persuasive"],
value="Professional"
)
length = st.radio(
"Content Length",
["Short", "Medium", "Long"],
horizontal=True
)
with col2:
engagement_goal = st.selectbox(
"Engagement Goal",
["Awareness", "Consideration", "Conversion", "Retention"]
)
creativity_level = st.slider(
"Creativity Level",
min_value=1,
max_value=10,
value=5
)
if st.button("Generate Optimization", type="primary"):
with st.spinner("Optimizing content..."):
try:
# Generate optimization
optimization = content_generator.optimize_content(
content=content_item,
tone=tone,
length=length,
engagement_goal=engagement_goal,
creativity_level=creativity_level
)
if optimization:
st.success("Content optimized successfully!")
# Show optimization results
st.subheader("Optimization Results")
st.write(optimization.get('content', ''))
# Save optimization history
optimization_manager.track_optimization(
content_item.title,
{
'tone': tone,
'length': length,
'engagement_goal': engagement_goal,
'creativity_level': creativity_level,
'content': optimization.get('content', ''),
'timestamp': datetime.now()
}
)
else:
st.error("Failed to optimize content. Please try again.")
except Exception as e:
logger.error(f"Error optimizing content: {str(e)}")
st.error("An error occurred while optimizing content. Please try again.")
with opt_tabs[1]:
st.subheader("SEO Optimization")
# SEO optimization content here
with opt_tabs[2]:
st.subheader("Content Preview")
# Content preview here
with opt_tabs[3]:
st.subheader("Optimization History")
# Optimization history here
with opt_tabs[4]:
st.subheader("Performance Analytics")
# Analytics content here
except Exception as e:
logger.error(f"Error processing selected content: {str(e)}")
st.error("Error processing selected content. Please try again.")
# Remove everything after this point

View File

@@ -1,517 +0,0 @@
import streamlit as st
import pandas as pd
from typing import Dict, List, Any, Optional
from datetime import datetime, timedelta
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.parent)
if parent_dir not in sys.path:
sys.path.append(parent_dir)
from lib.database.models import ContentItem, ContentType, Platform, SEOData
from lib.ai_seo_tools.content_calendar.core.content_repurposer import SmartContentRepurposingEngine
from lib.ai_seo_tools.content_calendar.core.content_generator import ContentGenerator
logger = logging.getLogger(__name__)
class ContentRepurposingUI:
"""
Streamlit UI component for the Smart Content Repurposing Engine.
"""
def __init__(self):
self.repurposing_engine = SmartContentRepurposingEngine()
self.content_generator = ContentGenerator()
self.logger = logging.getLogger('content_calendar.repurposing_ui')
def render_repurposing_interface(self):
"""Render the main repurposing interface."""
st.header("🔄 Smart Content Repurposing Engine")
st.markdown("Transform your content into multiple platform-optimized pieces with AI-powered repurposing.")
# Create tabs for different repurposing functions
tab1, tab2, tab3, tab4 = st.tabs([
"📝 Single Content Repurposing",
"📚 Content Series Creation",
"🔍 Content Analysis",
"📊 Repurposing Dashboard"
])
with tab1:
self._render_single_content_repurposing()
with tab2:
self._render_content_series_creation()
with tab3:
self._render_content_analysis()
with tab4:
self._render_repurposing_dashboard()
def _render_single_content_repurposing(self):
"""Render the single content repurposing interface."""
st.subheader("Repurpose Single Content")
st.markdown("Transform one piece of content into multiple platform-optimized variations.")
# Content input section
col1, col2 = st.columns([2, 1])
with col1:
st.markdown("### 📄 Source Content")
# Content input options
input_method = st.radio(
"How would you like to provide content?",
["Manual Input", "Upload File", "Select from Calendar"],
horizontal=True
)
source_content = None
if input_method == "Manual Input":
source_content = self._render_manual_content_input()
elif input_method == "Upload File":
source_content = self._render_file_upload_input()
else: # Select from Calendar
source_content = self._render_calendar_selection()
with col2:
st.markdown("### 🎯 Target Platforms")
# Platform selection
available_platforms = [
Platform.TWITTER,
Platform.LINKEDIN,
Platform.INSTAGRAM,
Platform.FACEBOOK,
Platform.WEBSITE
]
selected_platforms = st.multiselect(
"Select target platforms:",
options=available_platforms,
default=[Platform.TWITTER, Platform.LINKEDIN],
format_func=lambda x: x.name.title()
)
# Repurposing strategy
strategy = st.selectbox(
"Repurposing Strategy:",
["adaptive", "atomic", "series"],
help="Adaptive: AI chooses best approach, Atomic: Break into small pieces, Series: Create connected content"
)
# Generate repurposed content
if st.button("🚀 Generate Repurposed Content", type="primary"):
if source_content and selected_platforms:
with st.spinner("Repurposing content..."):
try:
repurposed_content = self.content_generator.repurpose_content_for_platforms(
content_item=source_content,
target_platforms=selected_platforms,
strategy=strategy
)
if repurposed_content:
self._display_repurposed_content(repurposed_content)
else:
st.error("Failed to generate repurposed content. Please try again.")
except Exception as e:
st.error(f"Error during repurposing: {str(e)}")
else:
st.warning("Please provide source content and select at least one target platform.")
def _render_content_series_creation(self):
"""Render the content series creation interface."""
st.subheader("Create Cross-Platform Content Series")
st.markdown("Generate a strategic content series that progressively reveals information across platforms.")
# Source content input
source_content = self._render_manual_content_input(key_suffix="_series")
if source_content:
col1, col2 = st.columns(2)
with col1:
st.markdown("### 🌐 Platform Strategy")
# Platform selection with strategy
platforms = st.multiselect(
"Select platforms for series:",
options=[Platform.TWITTER, Platform.LINKEDIN, Platform.INSTAGRAM, Platform.FACEBOOK, Platform.WEBSITE],
default=[Platform.TWITTER, Platform.LINKEDIN, Platform.WEBSITE],
format_func=lambda x: x.name.title(),
key="series_platforms"
)
series_type = st.selectbox(
"Series Strategy:",
["progressive_disclosure", "platform_native"],
help="Progressive: Gradually reveal info across platforms, Native: Optimize for each platform's strengths"
)
with col2:
st.markdown("### 📅 Timeline Preview")
if platforms:
# Show timeline preview
timeline_df = self._create_series_timeline_preview(source_content, platforms)
st.dataframe(timeline_df, use_container_width=True)
# Generate series
if st.button("📚 Create Content Series", type="primary", key="create_series"):
if platforms:
with st.spinner("Creating content series..."):
try:
series_content = self.content_generator.create_content_series_across_platforms(
source_content=source_content,
platforms=platforms,
series_type=series_type
)
if series_content:
self._display_content_series(series_content)
else:
st.error("Failed to create content series. Please try again.")
except Exception as e:
st.error(f"Error creating series: {str(e)}")
else:
st.warning("Please select at least one platform for the series.")
def _render_content_analysis(self):
"""Render the content analysis interface."""
st.subheader("Content Repurposing Analysis")
st.markdown("Analyze your content's repurposing potential and get AI-powered recommendations.")
# Content input
content_to_analyze = self._render_manual_content_input(key_suffix="_analysis")
if content_to_analyze:
col1, col2 = st.columns([1, 1])
with col1:
available_platforms = st.multiselect(
"Available platforms for analysis:",
options=[Platform.TWITTER, Platform.LINKEDIN, Platform.INSTAGRAM, Platform.FACEBOOK, Platform.WEBSITE],
default=[Platform.TWITTER, Platform.LINKEDIN, Platform.INSTAGRAM, Platform.FACEBOOK, Platform.WEBSITE],
format_func=lambda x: x.name.title(),
key="analysis_platforms"
)
with col2:
if st.button("🔍 Analyze Content", type="primary"):
if available_platforms:
with st.spinner("Analyzing content..."):
try:
analysis = self.content_generator.analyze_content_for_repurposing(
content_item=content_to_analyze,
available_platforms=available_platforms
)
if analysis:
self._display_content_analysis(analysis)
else:
st.error("Failed to analyze content. Please try again.")
except Exception as e:
st.error(f"Error during analysis: {str(e)}")
else:
st.warning("Please select at least one platform for analysis.")
def _render_repurposing_dashboard(self):
"""Render the repurposing dashboard with metrics and insights."""
st.subheader("Repurposing Dashboard")
st.markdown("Track your content repurposing performance and insights.")
# Mock data for demonstration
col1, col2, col3, col4 = st.columns(4)
with col1:
st.metric("Content Pieces Created", "156", "+23")
with col2:
st.metric("Time Saved", "312 hours", "+45 hours")
with col3:
st.metric("Platform Coverage", "85%", "+12%")
with col4:
st.metric("Engagement Boost", "34%", "+8%")
# Recent repurposing activity
st.markdown("### 📈 Recent Repurposing Activity")
# Mock data for recent activity
recent_activity = pd.DataFrame({
'Date': ['2024-01-15', '2024-01-14', '2024-01-13', '2024-01-12'],
'Source Content': ['AI Writing Tips', 'SEO Best Practices', 'Content Strategy Guide', 'Social Media Trends'],
'Platforms': ['Twitter, LinkedIn', 'LinkedIn, Instagram', 'All Platforms', 'Twitter, Facebook'],
'Pieces Created': [3, 2, 5, 2],
'Status': ['Published', 'Scheduled', 'Draft', 'Published']
})
st.dataframe(recent_activity, use_container_width=True)
# Performance insights
st.markdown("### 💡 Performance Insights")
insights_col1, insights_col2 = st.columns(2)
with insights_col1:
st.info("🎯 **Best Performing Platform**: LinkedIn posts show 45% higher engagement when repurposed from blog content.")
with insights_col2:
st.success("📊 **Optimization Tip**: Twitter threads perform 60% better when created from long-form content with statistics.")
def _render_manual_content_input(self, key_suffix: str = "") -> Optional[ContentItem]:
"""Render manual content input form."""
with st.form(f"content_input_form{key_suffix}"):
title = st.text_input("Content Title:", key=f"title{key_suffix}")
content_type = st.selectbox(
"Content Type:",
options=[ContentType.BLOG_POST, ContentType.SOCIAL_MEDIA, ContentType.VIDEO, ContentType.NEWSLETTER],
format_func=lambda x: x.name.replace('_', ' ').title(),
key=f"content_type{key_suffix}"
)
description = st.text_area(
"Content Description/Body:",
height=200,
help="Paste your content here. This will be analyzed and repurposed.",
key=f"description{key_suffix}"
)
col1, col2 = st.columns(2)
with col1:
author = st.text_input("Author:", value="Content Creator", key=f"author{key_suffix}")
with col2:
tags = st.text_input("Tags (comma-separated):", key=f"tags{key_suffix}")
submitted = st.form_submit_button("📝 Use This Content")
if submitted and title and description:
# Create ContentItem
content_item = ContentItem(
title=title,
description=description,
content_type=content_type,
platforms=[],
publish_date=datetime.now(),
status="draft",
author=author,
tags=tags.split(',') if tags else [],
notes="",
seo_data=SEOData(title=title, meta_description="", keywords=[], structured_data={})
)
return content_item
return None
def _render_file_upload_input(self) -> Optional[ContentItem]:
"""Render file upload input."""
uploaded_file = st.file_uploader(
"Upload content file:",
type=['txt', 'md', 'docx'],
help="Upload a text file, markdown file, or Word document"
)
if uploaded_file:
try:
# Read file content
if uploaded_file.type == "text/plain":
content = str(uploaded_file.read(), "utf-8")
else:
content = str(uploaded_file.read(), "utf-8") # Simplified for demo
# Extract title from filename
title = uploaded_file.name.split('.')[0].replace('_', ' ').title()
# Create ContentItem
content_item = ContentItem(
title=title,
description=content,
content_type=ContentType.BLOG_POST,
platforms=[],
publish_date=datetime.now(),
status="draft",
author="Uploaded Content",
tags=[],
notes=f"Uploaded from file: {uploaded_file.name}",
seo_data=SEOData(title=title, meta_description="", keywords=[], structured_data={})
)
st.success(f"✅ File uploaded: {uploaded_file.name}")
return content_item
except Exception as e:
st.error(f"Error reading file: {str(e)}")
return None
def _render_calendar_selection(self) -> Optional[ContentItem]:
"""Render calendar content selection."""
st.info("📅 Calendar integration coming soon! For now, please use manual input or file upload.")
return None
def _display_repurposed_content(self, repurposed_content: List[ContentItem]):
"""Display the repurposed content results."""
st.success(f"✅ Successfully created {len(repurposed_content)} repurposed content pieces!")
for i, content in enumerate(repurposed_content):
with st.expander(f"📱 {content.platforms[0].name.title()} - {content.title}"):
st.markdown(f"**Platform:** {content.platforms[0].name.title()}")
st.markdown(f"**Content Type:** {content.content_type.name.replace('_', ' ').title()}")
st.markdown(f"**Scheduled for:** {content.publish_date.strftime('%Y-%m-%d')}")
st.markdown("**Content:**")
st.write(content.description)
if content.tags:
st.markdown(f"**Tags:** {', '.join(content.tags)}")
# Action buttons
col1, col2, col3 = st.columns(3)
with col1:
if st.button(f"📝 Edit", key=f"edit_{i}"):
st.info("Edit functionality coming soon!")
with col2:
if st.button(f"📅 Schedule", key=f"schedule_{i}"):
st.info("Scheduling functionality coming soon!")
with col3:
if st.button(f"📋 Copy", key=f"copy_{i}"):
st.code(content.description)
def _display_content_series(self, series_content: Dict[str, List[ContentItem]]):
"""Display the content series results."""
total_pieces = sum(len(pieces) for pieces in series_content.values())
st.success(f"✅ Successfully created content series with {total_pieces} pieces across {len(series_content)} platforms!")
for platform, content_pieces in series_content.items():
st.markdown(f"### 📱 {platform.title()} Series ({len(content_pieces)} pieces)")
for i, content in enumerate(content_pieces):
with st.expander(f"Part {i+1}: {content.title}"):
st.markdown(f"**Scheduled for:** {content.publish_date.strftime('%Y-%m-%d')}")
st.markdown("**Content:**")
st.write(content.description)
if content.tags:
st.markdown(f"**Tags:** {', '.join(content.tags)}")
def _display_content_analysis(self, analysis: Dict[str, Any]):
"""Display content analysis results."""
st.markdown("### 📊 Content Analysis Results")
# Content metrics
col1, col2, col3 = st.columns(3)
content_analysis = analysis.get('content_analysis', {})
with col1:
st.metric("Word Count", content_analysis.get('word_count', 0))
with col2:
richness = content_analysis.get('content_richness', 'Unknown')
st.metric("Content Richness", richness)
with col3:
potential = content_analysis.get('repurposing_potential', 'Unknown')
st.metric("Repurposing Potential", potential)
# Recommendations
st.markdown("### 💡 Recommendations")
col1, col2 = st.columns(2)
with col1:
st.markdown("**Recommended Platforms:**")
platforms = analysis.get('platform_suggestions', [])
for platform in platforms:
st.write(f"{platform.name.title()}")
with col2:
st.markdown("**Suggested Strategies:**")
strategies = analysis.get('strategy_suggestions', [])
for strategy in strategies:
st.write(f"{strategy.replace('_', ' ').title()}")
# Content atoms
st.markdown("### 🔬 Content Atoms Analysis")
atoms = content_analysis.get('content_atoms', {})
for atom_type, atom_list in atoms.items():
if atom_list:
with st.expander(f"{atom_type.title()} ({len(atom_list)} found)"):
for atom in atom_list:
st.write(f"{atom}")
# Estimated output
estimated = analysis.get('estimated_output', {})
if estimated:
st.markdown("### 📈 Estimated Output")
col1, col2, col3 = st.columns(3)
with col1:
st.metric("Total Pieces", estimated.get('total_pieces', 0))
with col2:
st.metric("Time Savings", estimated.get('time_savings', '0 hours'))
with col3:
st.metric("Content Multiplication", estimated.get('content_multiplication', '1x'))
def _create_series_timeline_preview(self, content: ContentItem, platforms: List[Platform]) -> pd.DataFrame:
"""Create a preview timeline for content series."""
timeline_data = []
base_date = datetime.now()
for i, platform in enumerate(platforms):
release_date = base_date + timedelta(days=i)
timeline_data.append({
'Platform': platform.name.title(),
'Release Date': release_date.strftime('%Y-%m-%d'),
'Content Type': self._get_platform_content_type(platform),
'Strategy': self._get_platform_strategy(platform)
})
return pd.DataFrame(timeline_data)
def _get_platform_content_type(self, platform: Platform) -> str:
"""Get content type for platform."""
types = {
Platform.TWITTER: "Thread/Tweet",
Platform.LINKEDIN: "Professional Post",
Platform.INSTAGRAM: "Visual Post",
Platform.FACEBOOK: "Engaging Post",
Platform.WEBSITE: "Blog Article"
}
return types.get(platform, "Standard Post")
def _get_platform_strategy(self, platform: Platform) -> str:
"""Get strategy for platform."""
strategies = {
Platform.TWITTER: "Hook & Engage",
Platform.LINKEDIN: "Authority Building",
Platform.INSTAGRAM: "Visual Storytelling",
Platform.FACEBOOK: "Community Discussion",
Platform.WEBSITE: "Complete Information"
}
return strategies.get(platform, "Standard Approach")
# Main function to render the UI
def render_content_repurposing_ui():
"""Main function to render the content repurposing UI."""
ui = ContentRepurposingUI()
ui.render_repurposing_interface()
# For testing
if __name__ == "__main__":
render_content_repurposing_ui()

View File

@@ -1,457 +0,0 @@
import streamlit as st
from typing import Dict, Any, List
from datetime import datetime, timedelta
import pandas as pd
from lib.ai_seo_tools.content_calendar.core.content_generator import ContentGenerator
from lib.ai_seo_tools.content_calendar.core.ai_generator import AIGenerator
from lib.ai_seo_tools.content_calendar.integrations.seo_optimizer import SEOOptimizer
from lib.database.models 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', series_type: str = '', series_flow: str = '', metadata: Dict[str, Any] = {}) -> 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,
'series_type': series_type,
'series_flow': series_flow,
'pieces': [],
'performance': {},
'created_at': datetime.now(),
'status': 'draft',
'relationships': {},
'platform_distribution': {p.name: [] for p in platforms},
'metadata': metadata
}
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'])}"
# Create a structured piece object
structured_piece = {
'id': piece_id,
'title': piece.get('title', f"Part {len(series['pieces']) + 1}"),
'content': piece.get('content', ''),
'platform': piece.get('platform', series['platforms'][0]),
'scheduled_date': None,
'status': 'draft',
'relationships': {
'previous': None,
'next': None
},
'performance': {
'engagement': 0,
'reach': 0,
'conversion_rate': 0
}
}
# Track relationships
if series['pieces']:
previous_piece = series['pieces'][-1]
structured_piece['relationships']['previous'] = previous_piece['id']
structured_piece['relationships']['next'] = piece_id
# Add to platform distribution
platform_name = structured_piece['platform'].name
if platform_name in series['platform_distribution']:
series['platform_distribution'][platform_name].append(piece_id)
series['pieces'].append(structured_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."""
st.header("Content Series Generator")
# 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("""
## Welcome to Content Series Generator! 📚
Create and manage content series across multiple platforms. Here's what you can do:
### Features:
- 📝 **Series Creation**: Generate connected content pieces
- 🔄 **Cross-Platform Distribution**: Optimize for different platforms
- 📊 **Series Analytics**: Track performance across the series
- 📅 **Smart Scheduling**: Plan content distribution
### Getting Started:
1. First, add some content to your calendar
2. Select a topic for your content series
3. Configure series parameters and platforms
4. Generate and schedule your series
Ready to get started? Add some content to your calendar first!
""")
return
# Series Configuration
st.subheader("Create New Content Series")
# Show onboarding info if no series exist
if not st.session_state.get('content_series', {}):
st.info("""
### Content Series Guide
Create engaging content series with these features:
- **Series Planning**: Define your series structure and goals
- **Content Generation**: Create connected content pieces
- **Platform Optimization**: Adapt content for each platform
- **Performance Tracking**: Monitor series success
Fill out the form below to create your first series!
""")
# 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')}"
# Prepare metadata with default values
metadata = {
'tone': series_tone,
'length': 'medium', # Default length
'engagement_goal': series_goals[0] if series_goals else 'Awareness',
'creativity_level': 'balanced' # Default creativity level
}
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,
series_type=series_goals[0] if series_goals else 'Awareness',
series_flow='sequential', # Default flow
metadata=metadata
)
if series:
# Generate series content
series_content = content_generator.generate_content(
content_type=ContentType[content_type],
topic=series_topic,
platforms=[Platform[p] for p in platforms],
num_pieces=num_pieces,
requirements={
'tone': series_tone,
'length': metadata['length'],
'engagement_goal': metadata['engagement_goal'],
'creativity_level': metadata['creativity_level'],
'series_type': metadata['engagement_goal'],
'series_flow': 'sequential',
'target_audience': target_audience
}
)
if series_content:
# Add content pieces to series
for piece in series_content:
series_manager.add_piece(
series_id=series['id'],
piece=piece
)
# Schedule series
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)
series_manager.schedule_series(
series_id=series['id'],
start_date=start_date,
interval_days=interval
)
elif schedule_strategy == 'burst':
start_date = st.date_input("Start Date", datetime.now())
burst_size = st.number_input("Burst Size", min_value=1, value=1)
series_manager.schedule_series(
series_id=series['id'],
start_date=start_date,
interval_days=1,
burst_size=burst_size
)
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!")
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 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.rerun()
def on_series_complete():
"""Handle series completion."""
try:
st.session_state.series_complete = True
st.rerun()
except Exception as e:
logger.error(f"Error handling series completion: {str(e)}")
st.error("An error occurred while completing the series. Please try again.")

View File

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

View File

@@ -1,638 +0,0 @@
import streamlit as st
import pandas as pd
from datetime import datetime, timedelta
import logging
import sys
import hashlib
from pathlib import Path
from typing import Dict, Any
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.content_optimization import render_content_optimization
from .components.ab_testing import render_ab_testing
from .components.content_series import render_content_series_generator
from .components.performance_insights import render_performance_insights
import json
from lib.content_scheduler.ui.dashboard import run_dashboard as run_scheduler_dashboard
# 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.database.models import ContentItem, ContentType, Platform, get_engine, get_session, init_db
from ..core.calendar_manager import CalendarManager
from ..core.content_generator import ContentGenerator
from ..core.ai_generator import AIGenerator
from ..core.content_brief import ContentBriefGenerator
from ..integrations.seo_optimizer import SEOOptimizer
from lib.integrations.platform_adapters import PlatformAdapter, UnifiedPlatformAdapter
# Initialize logger
logger = logging.getLogger(__name__)
# Initialize DB/session (do this once at app startup)
engine = get_engine()
init_db(engine)
session = get_session(engine)
# Import content repurposing UI with error handling
def render_smart_repurposing_tab():
"""Render the Smart Content Repurposing tab with error handling."""
try:
from lib.ai_seo_tools.content_calendar.ui.components.content_repurposing_ui import render_content_repurposing_ui
render_content_repurposing_ui()
except ImportError as e:
st.error(f"Smart Content Repurposing feature is not available: {str(e)}")
st.info("Please ensure all dependencies are installed correctly.")
except Exception as e:
st.error(f"Error loading Smart Content Repurposing: {str(e)}")
st.info("Please check the logs for more details.")
class ContentCalendarDashboard:
"""Interactive dashboard for content calendar management."""
def __init__(self):
self.logger = logging.getLogger('content_calendar.dashboard')
self.logger.info("Initializing ContentCalendarDashboard")
self.content_brief_generator = ContentBriefGenerator()
self.content_generator = ContentGenerator()
self.ai_generator = AIGenerator()
self.platform_adapter = UnifiedPlatformAdapter()
self.seo_optimizer = SEOOptimizer()
# Initialize session state variables
if 'ab_test_results' not in st.session_state:
st.session_state.ab_test_results = {}
if 'optimization_history' not in st.session_state:
st.session_state.optimization_history = {}
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",
"🔄 Smart Repurposing",
"A/B Testing",
"Content Series",
"Analytics",
"Content Scheduling"
])
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):
try:
st.session_state.editing_content = row
st.rerun()
except Exception as e:
logger.error(f"Error handling edit action: {str(e)}")
st.error("An error occurred while editing content. Please try again.")
def on_delete(row):
try:
self._delete_content(row)
st.success(f"Successfully deleted content: {row['title']}")
st.rerun()
except Exception as e:
logger.error(f"Error handling delete action: {str(e)}")
st.error("An error occurred while deleting content. Please try again.")
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.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.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_smart_repurposing_tab()
with tabs[3]:
render_ab_testing(self.content_generator, None)
with tabs[4]:
render_content_series_generator(
self.ai_generator,
self.content_generator,
self.seo_optimizer
)
with tabs[5]:
st.header("Analytics")
st.markdown("### Performance Insights")
all_content = session.query(ContentItem).all()
selected_content = st.selectbox(
"Select content to analyze",
options=[item.title for item in all_content],
key="analytics_content_select"
)
if selected_content:
content_item = next(
item for item in 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])
with tabs[6]:
run_scheduler_dashboard()
self.logger.info("Dashboard render completed successfully (tabbed UI)")
except Exception as e:
self.logger.error(f"Error rendering dashboard: {str(e)}", exc_info=True)
st.error(f"An error occurred: {str(e)}")
def _inject_custom_css(self):
st.markdown("""
<style>
/* Add your custom CSS here if needed */
</style>
""", unsafe_allow_html=True)
def _get_calendar_data(self):
self.logger.info("_get_calendar_data called")
try:
all_content = session.query(ContentItem).all()
data = []
for item in all_content:
data.append({
'date': item.publish_date,
'title': item.title,
'platform': item.platforms[0] if item.platforms else 'Unknown',
'type': item.content_type.value if hasattr(item.content_type, 'value') else str(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):
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)
new_item = ContentItem(
title=content['title'],
description="",
content_type=content_type_enum,
platforms=[platform_enum.value],
publish_date=pd.to_datetime(content['publish_date']),
status=content.get('status', 'Draft'),
author=None,
tags=[],
notes=None,
seo_data={}
)
session.add(new_item)
session.commit()
def _delete_content(self, row):
# Find by title and publish_date (could be improved with unique IDs)
all_content = session.query(ContentItem).all()
for item in all_content:
if (item.title == row['title'] and
str(item.publish_date.date()) == str(row['date'].date()) and
(item.platforms[0] if item.platforms else 'Unknown') == str(row['platform']) and
(item.content_type.value if hasattr(item.content_type, 'value') else str(item.content_type)) == str(row['type'])):
session.delete(item)
session.commit()
break
def _edit_content(self, row, new_title, new_platform, new_type, new_status):
self._delete_content(row)
self._add_content({
'title': new_title,
'platform': new_platform,
'type': new_type,
'publish_date': row['date'],
'status': new_status
})
def _get_item_key(self, row):
key_str = f"{row['title']}_{row['date']}_{row['platform']}_{row['type']}"
return hashlib.md5(key_str.encode()).hexdigest()
def _generate_ai_suggestions(self, content_type, topic, audience, goals, tone, length, model_settings, style_preferences, seo_preferences, platform_settings):
"""Generate AI content suggestions based on input parameters."""
try:
self.logger.info(f"Generating AI suggestions for topic: {topic}")
# Map content type string to ContentType enum
content_type_map = {
'Blog Post': ContentType.BLOG_POST,
'Social Media Post': ContentType.SOCIAL_MEDIA,
'Video': ContentType.VIDEO,
'Newsletter': ContentType.NEWSLETTER,
'Article': ContentType.BLOG_POST,
'Social Post': ContentType.SOCIAL_MEDIA
}
content_type_enum = content_type_map.get(content_type, ContentType.BLOG_POST)
# Map platform string to Platform enum
platform_map = {
'Blog': Platform.WEBSITE,
'Instagram': Platform.INSTAGRAM,
'Twitter': Platform.TWITTER,
'LinkedIn': Platform.LINKEDIN,
'Facebook': Platform.FACEBOOK,
'Website': Platform.WEBSITE
}
platform = st.session_state.get('ai_modal_platform', 'Blog')
platform_enum = platform_map.get(platform, Platform.WEBSITE)
# Create a content item for the suggestion
content_item = ContentItem(
title=topic,
description="",
content_type=content_type_enum,
platforms=[platform_enum],
publish_date=datetime.now(),
seo_data=SEOData(
title=topic,
meta_description="",
keywords=[],
structured_data={}
),
status='Draft'
)
# Use AIGenerator to generate suggestions
suggestions = self.ai_generator.generate_ai_suggestions(
content_type=content_type_enum,
topic=topic,
audience=audience,
goals=goals,
tone=tone,
length=length,
model_settings=model_settings,
style_preferences=style_preferences,
seo_preferences=seo_preferences,
platform_settings=platform_settings,
platform=platform_enum
)
if not suggestions:
self.logger.warning("No suggestions generated")
return []
# Format suggestions
formatted_suggestions = []
for suggestion in suggestions:
formatted_suggestion = {
'title': suggestion.get('title', topic),
'type': content_type,
'platform': platform,
'audience': audience,
'impact': f"High impact for {', '.join(goals)}",
'preview': suggestion.get('preview', ''),
'style_elements': [
f"Tone: {tone}",
f"Length: {length}",
f"Creativity: {model_settings.get('Creativity Level', 'balanced')}",
f"Formality: {model_settings.get('Formality Level', 'professional')}"
],
'seo_elements': [
f"Keyword Density: {seo_preferences.get('Keyword Density', '2')}%",
"Internal Linking: Enabled" if seo_preferences.get('Internal Linking', True) else "Internal Linking: Disabled",
"External Linking: Enabled" if seo_preferences.get('External Linking', True) else "External Linking: Disabled"
],
'engagement_score': f"{85 + len(formatted_suggestions)*5}%",
'reach': 'High',
'conversion': f"{3.5 + len(formatted_suggestions)*0.5}%",
'seo_impact': 'Strong',
'platform_optimizations': suggestion.get('platform_optimizations', []),
'variations': suggestion.get('variations', [
"Alternative headline",
"Different content angle",
"Alternative format"
]),
'seo_recommendations': suggestion.get('seo_elements', []),
'media_suggestions': suggestion.get('media_suggestions', [
"Featured image",
"Supporting graphics",
"Social media visuals"
])
}
formatted_suggestions.append(formatted_suggestion)
self.logger.info(f"Generated {len(formatted_suggestions)} suggestions successfully")
return formatted_suggestions
except Exception as e:
self.logger.error(f"Error generating AI suggestions: {str(e)}", exc_info=True)
st.error(f"Error generating suggestions: {str(e)}")
return []
def _create_content_brief(self, content_item: ContentItem) -> Dict[str, Any]:
"""Create a detailed content brief for the given content item."""
try:
self.logger.info(f"Creating content brief for: {content_item.title}")
# Generate content brief using the content brief generator
brief = self.content_brief_generator.generate_brief(
content_item=content_item,
target_audience={
'audience': content_item.description,
'goals': ['engage', 'inform', 'convert']
}
)
# Enhance brief with SEO data
if brief and 'content_flow' in brief:
brief['seo_optimization'] = {
'meta_description': self.seo_optimizer.generate_meta_description(
brief['content_flow'].get('introduction', {}).get('summary', '')
),
'keywords': self.seo_optimizer.extract_keywords(
brief['content_flow'].get('introduction', {}).get('summary', '')
),
'structured_data': self.seo_optimizer.generate_structured_data(
content_item.content_type
)
}
self.logger.info(f"Content brief created successfully for: {content_item.title}")
return brief
except Exception as e:
self.logger.error(f"Error creating content brief: {str(e)}", exc_info=True)
st.error(f"Error creating content brief: {str(e)}")
return {}
def _schedule_content(self, content_item: ContentItem, publish_date: datetime) -> bool:
"""Schedule content for publishing on the specified date."""
try:
self.logger.info(f"Scheduling content: {content_item.title} for {publish_date}")
# Get the calendar
calendar = self.calendar_manager.get_calendar()
if not calendar:
raise ValueError("No calendar found")
# Update the publish date
content_item.publish_date = publish_date
# Add to calendar
calendar.add_content(content_item)
# Save changes
self.calendar_manager.save_calendar_to_json()
self.logger.info(f"Content scheduled successfully: {content_item.title}")
return True
except Exception as e:
self.logger.error(f"Error scheduling content: {str(e)}", exc_info=True)
st.error(f"Error scheduling content: {str(e)}")
return False
def _refine_suggestion(self, suggestion: Dict[str, Any], feedback: Dict[str, Any]) -> Dict[str, Any]:
"""Refine an AI-generated suggestion based on user feedback."""
try:
self.logger.info("Refining AI suggestion based on feedback")
# Update suggestion based on feedback
if 'tone' in feedback:
suggestion['style_elements'] = [
f"Tone: {feedback['tone']}",
*[elem for elem in suggestion['style_elements'] if not elem.startswith('Tone:')]
]
if 'length' in feedback:
suggestion['style_elements'] = [
f"Length: {feedback['length']}",
*[elem for elem in suggestion['style_elements'] if not elem.startswith('Length:')]
]
if 'keywords' in feedback:
suggestion['seo_elements'] = [
f"Keywords: {', '.join(feedback['keywords'])}",
*[elem for elem in suggestion['seo_elements'] if not elem.startswith('Keywords:')]
]
# Regenerate content with refined parameters
refined_content = self.content_brief_generator.generate_brief(
content_item=ContentItem(
title=suggestion['title'],
description="",
content_type=ContentType[suggestion['type'].upper().replace(' ', '_')],
platforms=[Platform[suggestion['platform'].upper()]],
publish_date=datetime.now(),
seo_data=SEOData(
title=suggestion['title'],
meta_description="",
keywords=feedback.get('keywords', []),
structured_data={}
),
status='Draft'
),
target_audience={
'audience': suggestion['audience'],
'goals': feedback.get('goals', ['engage', 'inform']),
'preferences': {
'tone': feedback.get('tone', 'professional'),
'length': feedback.get('length', 'medium')
}
}
)
if refined_content:
suggestion['preview'] = refined_content.get('content_flow', {}).get('introduction', {}).get('summary', '')
self.logger.info("Suggestion refined successfully")
return suggestion
except Exception as e:
self.logger.error(f"Error refining suggestion: {str(e)}", exc_info=True)
st.error(f"Error refining suggestion: {str(e)}")
return suggestion
def _customize_suggestion(self, suggestion: Dict[str, Any], customizations: Dict[str, Any]) -> Dict[str, Any]:
"""Customize an AI-generated suggestion with specific requirements."""
try:
self.logger.info("Customizing AI suggestion")
# Apply customizations
if 'title' in customizations:
suggestion['title'] = customizations['title']
if 'platform' in customizations:
suggestion['platform'] = customizations['platform']
if 'style' in customizations:
suggestion['style_elements'] = [
f"Tone: {customizations['style'].get('tone', 'professional')}",
f"Length: {customizations['style'].get('length', 'medium')}",
f"Creativity: {customizations['style'].get('creativity', 'balanced')}",
f"Formality: {customizations['style'].get('formality', 'professional')}"
]
if 'seo' in customizations:
suggestion['seo_elements'] = [
f"Keyword Density: {customizations['seo'].get('keyword_density', '2')}%",
"Internal Linking: Enabled" if customizations['seo'].get('internal_linking', True) else "Internal Linking: Disabled",
"External Linking: Enabled" if customizations['seo'].get('external_linking', True) else "External Linking: Disabled"
]
# Regenerate content with customizations
customized_content = self.content_brief_generator.generate_brief(
content_item=ContentItem(
title=suggestion['title'],
description="",
content_type=ContentType[suggestion['type'].upper().replace(' ', '_')],
platforms=[Platform[suggestion['platform'].upper()]],
publish_date=datetime.now(),
seo_data=SEOData(
title=suggestion['title'],
meta_description="",
keywords=customizations.get('seo', {}).get('keywords', []),
structured_data={}
),
status='Draft'
),
target_audience={
'audience': suggestion['audience'],
'goals': customizations.get('goals', ['engage', 'inform']),
'preferences': customizations.get('style', {})
}
)
if customized_content:
suggestion['preview'] = customized_content.get('content_flow', {}).get('introduction', {}).get('summary', '')
self.logger.info("Suggestion customized successfully")
return suggestion
except Exception as e:
self.logger.error(f"Error customizing suggestion: {str(e)}", exc_info=True)
st.error(f"Error customizing suggestion: {str(e)}")
return suggestion
def _optimize_content_for_platform(self, content_item: ContentItem, platform: Platform) -> Dict[str, Any]:
"""Optimize content specifically for a target platform."""
try:
self.logger.info(f"Optimizing content for {platform.name}: {content_item.title}")
# Get platform-specific requirements
platform_requirements = self.platform_adapter.get_platform_requirements(platform)
# Generate platform-optimized content
optimized_content = self.content_generator.optimize_for_platform(
content=content_item,
platform=platform,
requirements=platform_requirements
)
if not optimized_content:
raise ValueError(f"Failed to optimize content for {platform.name}")
# Enhance with AI
ai_enhanced = self.ai_generator.enhance_for_platform(
content=optimized_content,
platform=platform,
enhancement_type='platform_specific'
)
if ai_enhanced:
optimized_content.update(ai_enhanced)
# Track optimization history
if content_item.title not in st.session_state.optimization_history:
st.session_state.optimization_history[content_item.title] = []
st.session_state.optimization_history[content_item.title].append({
'platform': platform.name,
'timestamp': datetime.now(),
'changes': optimized_content.get('changes', [])
})
self.logger.info(f"Content optimized successfully for {platform.name}")
return optimized_content
except Exception as e:
self.logger.error(f"Error optimizing content: {str(e)}", exc_info=True)
st.error(f"Error optimizing content: {str(e)}")
return {}
if __name__ == "__main__":
dashboard = ContentCalendarDashboard()
dashboard.render()

View File

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

View File

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

View File

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

View File

@@ -1,344 +0,0 @@
# 🎯 AI Content Performance Predictor
**LLM-Powered Content Success Prediction for Solo Developers**
The AI Content Performance Predictor is an intelligent feature that leverages Large Language Models (LLMs) to analyze your content and predict its potential success before you publish. Perfect for solo developers and entrepreneurs who need smart content insights without complex ML infrastructure.
## 📋 Table of Contents
- [Overview](#overview)
- [Features](#features)
- [Installation](#installation)
- [Usage](#usage)
- [Architecture](#architecture)
- [AI Analysis Engine](#ai-analysis-engine)
- [API Reference](#api-reference)
- [File Structure](#file-structure)
- [Configuration](#configuration)
- [Development](#development)
- [Performance Metrics](#performance-metrics)
- [Troubleshooting](#troubleshooting)
- [Contributing](#contributing)
## 🔍 Overview
The AI Content Performance Predictor uses advanced LLM capabilities to provide intelligent content analysis and predictions:
- **LLM-Powered Analysis**: Uses your existing `llm_text_gen` integration for smart predictions
- **Platform-Specific Insights**: Tailored analysis for Twitter, LinkedIn, Facebook, Instagram, and more
- **Zero Training Required**: No ML model training needed - works immediately
- **Solo Developer Friendly**: Designed for resource-constrained environments
- **Real-time Predictions**: Instant analysis and recommendations
### Key Benefits
- **🧠 AI-Powered Intelligence**: Leverages LLM understanding for content analysis
- **⚡ Instant Predictions**: No waiting for model training or data collection
- **📊 Smart Insights**: Platform-specific recommendations and optimization tips
- **🎯 Success Scoring**: Comprehensive performance scoring system
- **🔄 Adaptive Learning**: Improves recommendations based on platform best practices
- **🎨 Multi-platform**: Optimized for different social media platforms
## ✨ Features
### Core Features
#### 1. **AI Prediction Engine**
- Overall performance score (0-100)
- Success probability percentage
- Platform-specific optimization
- Content quality assessment
#### 2. **LLM Integration**
- Uses existing Alwrity LLM infrastructure
- No additional API costs or setup
- Intelligent content understanding
- Context-aware analysis
#### 3. **Platform Optimization**
- Twitter: Character limits, hashtag optimization, engagement factors
- LinkedIn: Professional tone, optimal length, business focus
- Facebook: Community engagement, storytelling elements
- Instagram: Visual content readiness, hashtag strategy
#### 4. **Smart Recommendations**
- Content improvement suggestions
- Optimal posting strategies
- Engagement enhancement tips
- SEO optimization advice
#### 5. **Interactive UI**
- Clean Streamlit interface
- Real-time analysis
- Visual performance indicators
- Actionable insights display
### Analysis Categories
1. **📈 Engagement Potential**: Predicted likes, comments, shares
2. **🎯 Content Quality**: Overall content effectiveness score
3. **⏰ Timing Insights**: Optimal posting time recommendations
4. **🔍 SEO Score**: Search engine optimization assessment
5. **🏷️ Hashtag Strategy**: Hashtag effectiveness analysis
6. **👥 Audience Alignment**: Content-audience fit assessment
## 🚀 Installation
### Prerequisites
```bash
# Already included in Alwrity - no additional installation required!
# Uses existing dependencies: streamlit, llm_text_gen
```
### Setup
1. **Auto-Integration** (already included):
```python
# Available in AI Writer Dashboard
# Access via: "AI Content Performance Predictor"
```
2. **Direct Usage**:
```python
from lib.content_performance_predictor.ai_performance_predictor import AIContentPerformancePredictor
```
3. **UI Component**:
```python
from lib.content_performance_predictor.ai_performance_predictor import render_ai_predictor_ui
```
## 📖 Usage
### Through AI Writer Dashboard
1. Open Alwrity
2. Navigate to "AI Writer Dashboard"
3. Select "🎯 AI Content Performance Predictor"
4. Enter your content and select platform
5. Get instant AI-powered predictions!
### Direct API Usage
```python
from lib.content_performance_predictor.ai_performance_predictor import AIContentPerformancePredictor
# Initialize predictor
predictor = AIContentPerformancePredictor()
# Analyze content
result = await predictor.predict_performance(
content="Your amazing content here!",
platform="twitter",
target_audience="tech entrepreneurs"
)
print(f"Overall Score: {result['overall_score']}")
print(f"Recommendations: {result['recommendations']}")
```
### Programmatic Usage
```python
import streamlit as st
from lib.content_performance_predictor.ai_performance_predictor import render_ai_predictor_ui
# Add to your Streamlit app
st.title("Content Analysis")
render_ai_predictor_ui()
```
### Batch Content Analysis
```python
# Analyze multiple pieces of content
contents = [
{"content": "Post 1", "platform": "twitter"},
{"content": "Post 2", "platform": "linkedin"},
{"content": "Post 3", "platform": "facebook"}
]
for content_data in contents:
result = await predictor.predict_performance(**content_data)
print(f"Content: {content_data['content'][:50]}...")
print(f"Score: {result['overall_score']}")
print("---")
```
## 🏗️ Architecture
### System Architecture
```
┌─────────────────────────────────────────────────────────────┐
│ STREAMLIT UI │
│ (render_ai_predictor_ui) │
└─────────────────┬───────────────────────────────────────────┘
┌─────────────────┴───────────────────────────────────────────┐
│ AI PREDICTION ENGINE │
│ (AIContentPerformancePredictor) │
│ ┌─────────────────┐ ┌─────────────────────────────────┐ │
│ │ AI Analysis │ │ Platform Configs │ │
│ │ (LLM-powered) │ │ (Twitter, LinkedIn, etc.) │ │
│ └─────────────────┘ └─────────────────────────────────┘ │
└─────────────────┬───────────────────────────────────────────┘
┌─────────────────┴───────────────────────────────────────────┐
│ ALWRITY LLM ENGINE │
│ (llm_text_gen) │
└─────────────────────────────────────────────────────────────┘
```
### Component Details
1. **AIContentPerformancePredictor**: Main prediction class
2. **Platform Configurations**: Optimized settings for each platform
3. **LLM Integration**: Seamless integration with existing AI infrastructure
4. **UI Components**: Interactive Streamlit interface
## 🧠 AI Analysis Engine
### LLM-Powered Predictions
The predictor uses sophisticated prompts to analyze:
- **Content Quality**: Grammar, readability, engagement potential
- **Platform Fit**: Alignment with platform best practices
- **Audience Appeal**: Target audience relevance
- **Optimization Opportunities**: Specific improvement suggestions
### Platform-Specific Analysis
#### Twitter Configuration
- Optimal Length: 100-280 characters
- Hashtags: 1-3 relevant hashtags
- Engagement Factors: Questions, calls-to-action, trending topics
#### LinkedIn Configuration
- Optimal Length: 150-300 words
- Professional Tone: Business-focused language
- Engagement: Industry insights, professional experiences
#### Facebook Configuration
- Optimal Length: 40-80 characters for high engagement
- Community Focus: Shareable, relatable content
- Visual Ready: Content that complements images/videos
#### Instagram Configuration
- Visual Emphasis: Content supporting visual storytelling
- Hashtags: 5-10 strategic hashtags
- Story Potential: Content suitable for Instagram Stories
## 📊 Performance Metrics
### Success Indicators
- **Overall Score**: 0-100 performance prediction
- **Platform Alignment**: How well content fits the platform
- **Engagement Prediction**: Expected interaction levels
- **Optimization Score**: Room for improvement rating
### Recommendation Categories
1. **Content Improvements**: Direct text enhancements
2. **Platform Optimization**: Platform-specific adjustments
3. **Timing Suggestions**: Optimal posting strategies
4. **Engagement Boosters**: Tactics to increase interaction
## 🔧 Configuration
### Platform Settings
Located in `ai_performance_predictor.py`:
```python
PLATFORM_CONFIGS = {
"twitter": {
"optimal_length": {"min": 100, "max": 280},
"hashtag_range": {"min": 1, "max": 3},
"engagement_factors": ["questions", "cta", "trending"]
},
# ... other platforms
}
```
### Customization
You can modify:
- Platform-specific parameters
- Analysis prompts
- Scoring algorithms
- UI components
## 🚀 Development
### Adding New Platforms
1. Add platform config to `PLATFORM_CONFIGS`
2. Update analysis prompts
3. Test with platform-specific content
### Enhancing AI Analysis
1. Modify prompts in `_create_analysis_prompt()`
2. Add new scoring criteria
3. Implement additional recommendation types
## 🔍 Troubleshooting
### Common Issues
**No Predictions Generated**:
- Check LLM service availability
- Verify content input format
- Ensure platform is supported
**Low Accuracy Scores**:
- Content may be too short/long for platform
- Platform mismatch with content style
- Generic content without specific appeal
**UI Not Loading**:
- Check Streamlit dependencies
- Verify import paths
- Ensure LLM service is configured
### Debug Mode
Enable detailed logging:
```python
import logging
logging.basicConfig(level=logging.DEBUG)
```
## 📈 Performance Tips
1. **Content Length**: Follow platform-specific optimal lengths
2. **Platform Selection**: Choose the right platform for your content type
3. **Target Audience**: Specify your audience for better predictions
4. **Iterate**: Use recommendations to improve content before posting
## 🤝 Contributing
1. Fork the repository
2. Create a feature branch
3. Make your changes
4. Test with different content types
5. Submit a pull request
### Development Setup
```bash
# No additional setup required!
# Uses existing Alwrity infrastructure
```
## 📝 License
Part of the Alwrity AI Content Creation Suite.
---
**Ready to predict your content's success? Access the AI Content Performance Predictor through the AI Writer Dashboard now!**

View File

@@ -1,662 +0,0 @@
"""
AI-Powered Content Performance Predictor
This module uses AI (LLM) to predict content performance instead of traditional ML models.
Perfect for solo developers who want competitive intelligence without expensive ML infrastructure.
"""
import asyncio
import json
from datetime import datetime, timedelta
from typing import Dict, Any, List, Optional
from loguru import logger
import streamlit as st
# Import existing Alwrity modules
from lib.database.twitter_service import TwitterDatabaseService
from lib.ai_web_researcher.google_trends_researcher import do_google_trends_analysis
from lib.gpt_providers.text_generation.main_text_generation import llm_text_gen
class AIContentPerformancePredictor:
"""
AI-powered content performance predictor using LLM intelligence.
No ML training required - uses AI's existing knowledge of content patterns.
"""
def __init__(self):
"""Initialize the AI predictor."""
self.twitter_service = TwitterDatabaseService()
self.platform_configs = {
'twitter': {
'optimal_length': 120,
'hashtag_range': (1, 3),
'best_times': [9, 12, 15, 18, 21],
'engagement_factors': ['questions', 'hashtags', 'mentions', 'visuals']
},
'linkedin': {
'optimal_length': 1500,
'hashtag_range': (3, 7),
'best_times': [8, 12, 17],
'engagement_factors': ['professional_insights', 'industry_expertise', 'networking']
},
'facebook': {
'optimal_length': 200,
'hashtag_range': (1, 5),
'best_times': [12, 15, 18],
'engagement_factors': ['visual_content', 'community_building', 'emotional_connection']
},
'instagram': {
'optimal_length': 150,
'hashtag_range': (5, 15),
'best_times': [11, 13, 17, 19],
'engagement_factors': ['visual_appeal', 'storytelling', 'trending_hashtags']
}
}
logger.info("AI Content Performance Predictor initialized")
async def predict_content_performance(self, content_data: Dict[str, Any]) -> Dict[str, Any]:
"""
Predict content performance using AI analysis.
Args:
content_data: Dictionary containing content and metadata
Returns:
AI-powered performance prediction with insights
"""
try:
st.info("🧠 AI is analyzing your content...")
# Extract content details
content = content_data.get('content', '')
platform = content_data.get('platform', 'twitter')
hashtags = content_data.get('hashtags', [])
posting_time = content_data.get('posting_time', datetime.now())
# Get current trends for context
trending_context = await self._get_trending_context(platform)
# Create comprehensive AI prompt for prediction
prediction_prompt = self._create_prediction_prompt(
content, platform, hashtags, posting_time, trending_context
)
# Get AI prediction
ai_response = llm_text_gen(
prediction_prompt,
system_prompt="You are an expert social media analyst with deep knowledge of content performance patterns across all platforms. Provide specific, actionable predictions."
)
# Parse AI response into structured prediction
structured_prediction = self._parse_ai_prediction(ai_response, content_data)
# Add platform-specific insights
platform_insights = self._get_platform_insights(content_data, platform)
# Generate actionable recommendations
recommendations = await self._generate_ai_recommendations(content_data, structured_prediction)
return {
'success': True,
'content_analyzed': content[:100] + "..." if len(content) > 100 else content,
'platform': platform,
'ai_prediction': structured_prediction,
'platform_insights': platform_insights,
'recommendations': recommendations,
'trending_context': trending_context,
'analysis_timestamp': datetime.now().isoformat(),
'confidence_level': self._calculate_confidence_level(content_data)
}
except Exception as e:
error_msg = f"Error in AI prediction: {str(e)}"
logger.error(error_msg, exc_info=True)
return {'error': error_msg}
def _create_prediction_prompt(
self,
content: str,
platform: str,
hashtags: List[str],
posting_time: datetime,
trending_context: Dict[str, Any]
) -> str:
"""Create a comprehensive prompt for AI prediction."""
config = self.platform_configs.get(platform, {})
prompt = f"""
Analyze this {platform} content and predict its performance:
CONTENT TO ANALYZE:
"{content}"
METADATA:
- Platform: {platform}
- Hashtags: {hashtags}
- Posting Time: {posting_time.strftime('%A %I:%M %p')}
- Content Length: {len(content)} characters
- Word Count: {len(content.split())} words
PLATFORM CONTEXT:
- Optimal Length: {config.get('optimal_length', 'N/A')} characters
- Recommended Hashtags: {config.get('hashtag_range', 'N/A')}
- Best Posting Times: {config.get('best_times', 'N/A')}
CURRENT TRENDS:
{json.dumps(trending_context, indent=2)}
PREDICTION REQUIREMENTS:
Please provide a detailed analysis with these specific predictions:
1. ENGAGEMENT PREDICTION:
- Estimated engagement rate (0-10%)
- Estimated likes (number)
- Estimated shares/retweets (number)
- Estimated comments (number)
2. PERFORMANCE ANALYSIS:
- Strengths of this content
- Weaknesses to address
- Viral potential (Low/Medium/High)
- Audience appeal rating (1-10)
3. OPTIMIZATION OPPORTUNITIES:
- How to improve engagement potential
- Better hashtag suggestions
- Content format improvements
- Timing optimization
4. COMPETITIVE ASSESSMENT:
- How this compares to typical content in this niche
- Unique elements that stand out
- Missing elements competitors usually include
Format your response as a detailed analysis with specific numbers and actionable insights.
Be realistic but optimistic in your predictions.
"""
return prompt
async def _get_trending_context(self, platform: str) -> Dict[str, Any]:
"""Get current trending context for better predictions."""
try:
# Use existing Twitter integration if available
if platform == 'twitter' and hasattr(self.twitter_service, 'get_trending_topics'):
trending_topics = self.twitter_service.get_trending_topics()
else:
# Fallback to general trends
trending_topics = [
'AI and technology',
'Content creation',
'Social media marketing',
'Digital transformation',
'Remote work'
]
return {
'trending_topics': trending_topics[:5],
'platform': platform,
'analysis_date': datetime.now().isoformat()
}
except Exception as e:
logger.error(f"Error getting trending context: {str(e)}")
return {
'trending_topics': ['General content', 'Engagement tips'],
'platform': platform,
'analysis_date': datetime.now().isoformat()
}
def _parse_ai_prediction(self, ai_response: str, content_data: Dict[str, Any]) -> Dict[str, Any]:
"""Parse AI response into structured prediction data."""
try:
# Extract numerical predictions using simple parsing
# This is a simplified version - in production, you might want more sophisticated parsing
prediction = {
'engagement_rate': self._extract_percentage(ai_response, 'engagement rate'),
'estimated_likes': self._extract_number(ai_response, 'likes'),
'estimated_shares': self._extract_number(ai_response, ['shares', 'retweets']),
'estimated_comments': self._extract_number(ai_response, 'comments'),
'viral_potential': self._extract_rating(ai_response, 'viral potential'),
'audience_appeal': self._extract_rating(ai_response, 'audience appeal'),
'strengths': self._extract_list_items(ai_response, 'strengths'),
'weaknesses': self._extract_list_items(ai_response, 'weaknesses'),
'full_analysis': ai_response
}
return prediction
except Exception as e:
logger.error(f"Error parsing AI prediction: {str(e)}")
return {
'engagement_rate': 2.5, # Default reasonable prediction
'estimated_likes': 50,
'estimated_shares': 10,
'estimated_comments': 5,
'viral_potential': 'Medium',
'audience_appeal': 7,
'full_analysis': ai_response
}
def _get_platform_insights(self, content_data: Dict[str, Any], platform: str) -> Dict[str, Any]:
"""Get platform-specific insights."""
config = self.platform_configs.get(platform, {})
content = content_data.get('content', '')
hashtags = content_data.get('hashtags', [])
insights = {
'platform_optimization': [],
'timing_analysis': {},
'format_analysis': {},
'hashtag_analysis': {}
}
# Length analysis
optimal_length = config.get('optimal_length', 200)
current_length = len(content)
if abs(current_length - optimal_length) > 50:
insights['platform_optimization'].append(
f"Content length ({current_length}) differs from optimal ({optimal_length}) for {platform}"
)
else:
insights['platform_optimization'].append(
f"Content length is well-optimized for {platform}"
)
# Hashtag analysis
hashtag_range = config.get('hashtag_range', (1, 5))
hashtag_count = len(hashtags)
if hashtag_count < hashtag_range[0]:
insights['hashtag_analysis']['recommendation'] = f"Add more hashtags (optimal: {hashtag_range[0]}-{hashtag_range[1]})"
elif hashtag_count > hashtag_range[1]:
insights['hashtag_analysis']['recommendation'] = f"Consider reducing hashtags (optimal: {hashtag_range[0]}-{hashtag_range[1]})"
else:
insights['hashtag_analysis']['recommendation'] = "Hashtag count is optimal"
# Timing analysis
best_times = config.get('best_times', [])
current_hour = datetime.now().hour
insights['timing_analysis'] = {
'best_times': best_times,
'current_timing': 'Optimal' if current_hour in best_times else 'Suboptimal',
'suggestion': f"Consider posting at {best_times} for better engagement" if current_hour not in best_times else "Current timing is optimal"
}
return insights
async def _generate_ai_recommendations(
self,
content_data: Dict[str, Any],
prediction: Dict[str, Any]
) -> List[Dict[str, str]]:
"""Generate AI-powered recommendations for improvement."""
recommendations_prompt = f"""
Based on this content analysis, provide specific improvement recommendations:
CONTENT: "{content_data.get('content', '')[:200]}..."
PLATFORM: {content_data.get('platform', 'twitter')}
PREDICTED ENGAGEMENT: {prediction.get('engagement_rate', 'N/A')}%
VIRAL POTENTIAL: {prediction.get('viral_potential', 'N/A')}
Provide 5-7 specific, actionable recommendations to improve this content's performance:
1. Content optimization suggestions
2. Hashtag improvements
3. Timing recommendations
4. Format enhancements
5. Engagement boosters
6. Audience targeting tips
7. Platform-specific optimizations
Format each recommendation as:
- Category: [category]
- Action: [specific action to take]
- Expected Impact: [what improvement to expect]
- Priority: [High/Medium/Low]
Focus on quick wins and high-impact changes.
"""
try:
ai_recommendations = llm_text_gen(
recommendations_prompt,
system_prompt="You are a content optimization expert. Provide specific, actionable recommendations that can be implemented immediately."
)
# Parse recommendations into structured format
return self._parse_recommendations(ai_recommendations)
except Exception as e:
logger.error(f"Error generating AI recommendations: {str(e)}")
return [
{
'category': 'Content Enhancement',
'action': 'Add more engaging elements like questions or calls-to-action',
'expected_impact': 'Increase engagement by 20-30%',
'priority': 'High'
},
{
'category': 'Hashtag Optimization',
'action': 'Research and add 2-3 trending relevant hashtags',
'expected_impact': 'Improve discoverability',
'priority': 'Medium'
}
]
def _extract_percentage(self, text: str, keyword: str) -> float:
"""Extract percentage value from AI response."""
import re
patterns = [
rf'{keyword}.*?(\d+\.?\d*)%',
rf'(\d+\.?\d*)%.*?{keyword}',
rf'{keyword}.*?(\d+\.?\d*) percent'
]
for pattern in patterns:
match = re.search(pattern, text, re.IGNORECASE)
if match:
return float(match.group(1))
return 2.5 # Default reasonable engagement rate
def _extract_number(self, text: str, keywords: List[str]) -> int:
"""Extract number from AI response."""
import re
if isinstance(keywords, str):
keywords = [keywords]
for keyword in keywords:
patterns = [
rf'{keyword}.*?(\d+)',
rf'(\d+).*?{keyword}'
]
for pattern in patterns:
match = re.search(pattern, text, re.IGNORECASE)
if match:
return int(match.group(1))
return 25 # Default reasonable number
def _extract_rating(self, text: str, keyword: str) -> str:
"""Extract rating (High/Medium/Low) from AI response."""
import re
pattern = rf'{keyword}.*?(High|Medium|Low)'
match = re.search(pattern, text, re.IGNORECASE)
if match:
return match.group(1).capitalize()
return 'Medium' # Default
def _extract_list_items(self, text: str, section: str) -> List[str]:
"""Extract list items from a section of AI response."""
import re
# Find the section
section_pattern = rf'{section}:?\s*(.*?)(?=\n\n|\d\.|[A-Z]+:|$)'
match = re.search(section_pattern, text, re.IGNORECASE | re.DOTALL)
if match:
section_text = match.group(1)
# Extract bullet points or numbered items
items = re.findall(r'[-•]\s*(.+)', section_text)
if not items:
items = re.findall(r'\d+\.\s*(.+)', section_text)
return [item.strip() for item in items[:3]] # Return first 3 items
return []
def _parse_recommendations(self, ai_recommendations: str) -> List[Dict[str, str]]:
"""Parse AI recommendations into structured format."""
recommendations = []
try:
# Simple parsing - split by numbers or bullet points
import re
sections = re.split(r'\d+\.|[-•]', ai_recommendations)
for section in sections[1:6]: # Take first 5 recommendations
if len(section.strip()) > 10: # Only substantial recommendations
recommendations.append({
'category': 'AI Recommendation',
'action': section.strip()[:200], # Limit length
'expected_impact': 'Improved engagement',
'priority': 'Medium'
})
except Exception as e:
logger.error(f"Error parsing recommendations: {str(e)}")
# Ensure we have at least a few recommendations
if len(recommendations) < 3:
recommendations.extend([
{
'category': 'Engagement',
'action': 'Add questions to encourage audience interaction',
'expected_impact': '20-30% more engagement',
'priority': 'High'
},
{
'category': 'Visibility',
'action': 'Use trending hashtags relevant to your niche',
'expected_impact': 'Better discoverability',
'priority': 'Medium'
},
{
'category': 'Timing',
'action': 'Post during peak engagement hours for your audience',
'expected_impact': '15-25% more reach',
'priority': 'Medium'
}
])
return recommendations[:7] # Return max 7 recommendations
def _calculate_confidence_level(self, content_data: Dict[str, Any]) -> str:
"""Calculate confidence level of prediction."""
confidence_factors = 0
# More complete data = higher confidence
if content_data.get('content'):
confidence_factors += 1
if content_data.get('hashtags'):
confidence_factors += 1
if content_data.get('platform'):
confidence_factors += 1
if content_data.get('posting_time'):
confidence_factors += 1
if confidence_factors >= 4:
return 'High'
elif confidence_factors >= 2:
return 'Medium'
else:
return 'Low'
async def analyze_content_batch(self, content_list: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
"""Analyze multiple pieces of content."""
results = []
for i, content_data in enumerate(content_list):
st.write(f"🔍 Analyzing content {i+1}/{len(content_list)}")
result = await self.predict_content_performance(content_data)
results.append(result)
return results
def get_platform_best_practices(self, platform: str) -> Dict[str, Any]:
"""Get best practices for a specific platform."""
config = self.platform_configs.get(platform, {})
return {
'platform': platform,
'optimal_length': config.get('optimal_length'),
'hashtag_range': config.get('hashtag_range'),
'best_posting_times': config.get('best_times'),
'engagement_factors': config.get('engagement_factors', []),
'tips': [
f"Keep content around {config.get('optimal_length', 200)} characters",
f"Use {config.get('hashtag_range', (1, 5))[0]}-{config.get('hashtag_range', (1, 5))[1]} relevant hashtags",
f"Post during peak hours: {config.get('best_times', [])}",
"Include engaging elements like questions or calls-to-action",
"Use visuals when possible to increase engagement"
]
}
# Usage example and Streamlit interface
def render_ai_predictor_ui():
"""Render the AI content performance predictor interface."""
st.title("🎯 AI Content Performance Predictor")
st.markdown("Get AI-powered predictions for your content performance - no ML training required!")
# Initialize predictor
if 'ai_predictor' not in st.session_state:
st.session_state.ai_predictor = AIContentPerformancePredictor()
predictor = st.session_state.ai_predictor
# Input section
st.header("📝 Content Analysis")
col1, col2 = st.columns(2)
with col1:
platform = st.selectbox(
"Platform",
["twitter", "linkedin", "facebook", "instagram"],
help="Choose your target platform"
)
posting_time = st.time_input("Posting Time", value=datetime.now().time())
with col2:
hashtags_input = st.text_input(
"Hashtags (comma-separated)",
value="AI, ContentCreation, Marketing",
help="Enter hashtags without # symbol"
)
content = st.text_area(
"Content to Analyze",
value="Discover how AI is revolutionizing content creation! What's your experience with AI tools? Share your thoughts below! 🚀",
height=150,
help="Enter the content you want to analyze"
)
# Process hashtags
hashtags = [tag.strip() for tag in hashtags_input.split(',') if tag.strip()]
if st.button("🧠 Analyze Content Performance", type="primary"):
if content:
# Prepare content data
content_data = {
'content': content,
'platform': platform,
'hashtags': hashtags,
'posting_time': datetime.combine(datetime.now().date(), posting_time)
}
# Run AI analysis
with st.spinner("🤖 AI is analyzing your content..."):
results = asyncio.run(predictor.predict_content_performance(content_data))
if results.get('success'):
st.success("✅ Analysis Complete!")
# Display predictions
st.header("📊 AI Performance Prediction")
prediction = results.get('ai_prediction', {})
# Key metrics
col1, col2, col3, col4 = st.columns(4)
with col1:
st.metric(
"Engagement Rate",
f"{prediction.get('engagement_rate', 0):.1f}%"
)
with col2:
st.metric(
"Est. Likes",
f"{prediction.get('estimated_likes', 0):,}"
)
with col3:
st.metric(
"Est. Shares",
f"{prediction.get('estimated_shares', 0):,}"
)
with col4:
st.metric(
"Viral Potential",
prediction.get('viral_potential', 'Medium')
)
# Platform insights
platform_insights = results.get('platform_insights', {})
if platform_insights:
st.subheader("🎯 Platform Optimization")
for insight in platform_insights.get('platform_optimization', []):
st.info(f"💡 {insight}")
# Timing analysis
timing = platform_insights.get('timing_analysis', {})
if timing:
st.write(f"**Timing Analysis:** {timing.get('suggestion', 'N/A')}")
# Hashtag analysis
hashtag_analysis = platform_insights.get('hashtag_analysis', {})
if hashtag_analysis:
st.write(f"**Hashtag Recommendation:** {hashtag_analysis.get('recommendation', 'N/A')}")
# AI Recommendations
recommendations = results.get('recommendations', [])
if recommendations:
st.subheader("🚀 AI Recommendations")
for i, rec in enumerate(recommendations):
with st.expander(f"💡 {rec.get('category', 'Recommendation')} - {rec.get('priority', 'Medium')} Priority"):
st.write(f"**Action:** {rec.get('action', 'N/A')}")
st.write(f"**Expected Impact:** {rec.get('expected_impact', 'N/A')}")
# Full AI Analysis
if prediction.get('full_analysis'):
with st.expander("🤖 Complete AI Analysis"):
st.write(prediction['full_analysis'])
else:
st.error(f"❌ Analysis failed: {results.get('error')}")
else:
st.warning("⚠️ Please enter content to analyze")
# Platform best practices
st.sidebar.header("📚 Platform Best Practices")
selected_platform = st.sidebar.selectbox("Get tips for:", ["twitter", "linkedin", "facebook", "instagram"])
best_practices = predictor.get_platform_best_practices(selected_platform)
st.sidebar.write(f"**{selected_platform.title()} Best Practices:**")
for tip in best_practices.get('tips', []):
st.sidebar.write(f"{tip}")
# Main execution
if __name__ == "__main__":
render_ai_predictor_ui()

View File

@@ -1,7 +0,0 @@
"""
UI module for the Content Scheduler dashboard.
"""
from .dashboard import run_dashboard
__all__ = ['run_dashboard']

View File

@@ -1,386 +0,0 @@
"""
Main dashboard implementation for the Content Scheduler.
"""
import streamlit as st
import pandas as pd
from datetime import datetime, timedelta
from typing import List, Dict, Any
import plotly.express as px
import plotly.graph_objects as go
from lib.database.models import ContentItem, Schedule, ScheduleStatus, ContentType, Platform, get_engine, get_session, init_db
engine = get_engine()
init_db(engine)
session = get_session(engine)
def run_dashboard():
"""Run the Streamlit dashboard."""
st.title("📅 Alwrity Content Scheduler Dashboard")
# Sidebar navigation
st.sidebar.title("Navigation")
page = st.sidebar.radio(
"Go to",
["Overview", "Schedule Management", "Create Schedule", "Job Monitor", "Analytics"]
)
if page == "Overview":
show_overview()
elif page == "Schedule Management":
show_schedule_management()
elif page == "Create Schedule":
show_create_schedule()
elif page == "Job Monitor":
show_job_monitor()
else:
show_analytics()
def show_overview():
"""Display the overview dashboard."""
st.header("📊 Overview")
# Get data from unified database
all_content = session.query(ContentItem).all()
all_schedules = session.query(Schedule).all()
# Display metrics
col1, col2, col3, col4 = st.columns(4)
with col1:
st.metric("Total Content Items", len(all_content))
with col2:
scheduled_count = len([s for s in all_schedules if s.status == ScheduleStatus.SCHEDULED])
st.metric("Scheduled Items", scheduled_count)
with col3:
completed_count = len([s for s in all_schedules if s.status == ScheduleStatus.COMPLETED])
st.metric("Completed", completed_count)
with col4:
failed_count = len([s for s in all_schedules if s.status == ScheduleStatus.FAILED])
st.metric("Failed", failed_count)
# Recent content
st.subheader("📝 Recent Content Items")
if all_content:
recent_content = sorted(all_content, key=lambda x: x.created_at, reverse=True)[:5]
for item in recent_content:
with st.expander(f"{item.title} ({item.content_type.value})"):
st.write(f"**Description:** {item.description or 'No description'}")
st.write(f"**Platforms:** {', '.join(item.platforms) if isinstance(item.platforms, list) else item.platforms}")
st.write(f"**Status:** {item.status}")
st.write(f"**Created:** {item.created_at}")
# Show associated schedules
item_schedules = [s for s in all_schedules if s.content_item_id == item.id]
if item_schedules:
st.write("**Schedules:**")
for schedule in item_schedules:
st.write(f" - {schedule.scheduled_time} ({schedule.status.value})")
else:
st.info("No content items found. Create some content in the Content Calendar first!")
def show_schedule_management():
"""Display the schedule management interface."""
st.header("📅 Schedule Management")
# Get all schedules
all_schedules = session.query(Schedule).all()
if not all_schedules:
st.info("No schedules found. Create schedules from the 'Create Schedule' tab.")
return
# Filter options
col1, col2 = st.columns(2)
with col1:
status_filter = st.selectbox(
"Filter by Status",
options=["All"] + [status.value for status in ScheduleStatus],
key="schedule_status_filter"
)
with col2:
date_filter = st.date_input(
"Filter by Date (from)",
value=datetime.now().date() - timedelta(days=30),
key="schedule_date_filter"
)
# Apply filters
filtered_schedules = all_schedules
if status_filter != "All":
filtered_schedules = [s for s in filtered_schedules if s.status.value == status_filter]
filtered_schedules = [s for s in filtered_schedules if s.scheduled_time.date() >= date_filter]
# Display schedules
st.subheader(f"📋 Schedules ({len(filtered_schedules)} items)")
for schedule in sorted(filtered_schedules, key=lambda x: x.scheduled_time, reverse=True):
content_item = session.query(ContentItem).get(schedule.content_item_id)
if content_item:
with st.expander(f"{content_item.title} - {schedule.scheduled_time.strftime('%Y-%m-%d %H:%M')} ({schedule.status.value})"):
col1, col2 = st.columns(2)
with col1:
st.write(f"**Content:** {content_item.title}")
st.write(f"**Type:** {content_item.content_type.value}")
st.write(f"**Platforms:** {', '.join(content_item.platforms) if isinstance(content_item.platforms, list) else content_item.platforms}")
st.write(f"**Scheduled Time:** {schedule.scheduled_time}")
st.write(f"**Status:** {schedule.status.value}")
with col2:
st.write(f"**Recurrence:** {schedule.recurrence or 'One-time'}")
st.write(f"**Priority:** {schedule.priority}")
st.write(f"**Created:** {schedule.created_at}")
if schedule.result:
st.write(f"**Result:** {schedule.result}")
# Action buttons
col1, col2, col3 = st.columns(3)
with col1:
if st.button(f"Edit Schedule", key=f"edit_{schedule.id}"):
st.session_state.edit_schedule_id = schedule.id
st.rerun()
with col2:
if schedule.status == ScheduleStatus.SCHEDULED:
if st.button(f"Cancel", key=f"cancel_{schedule.id}"):
schedule.status = ScheduleStatus.CANCELLED
session.commit()
st.success("Schedule cancelled!")
st.rerun()
with col3:
if st.button(f"Delete", key=f"delete_{schedule.id}"):
session.delete(schedule)
session.commit()
st.success("Schedule deleted!")
st.rerun()
def show_create_schedule():
"""Display the schedule creation interface."""
st.header(" Create New Schedule")
# Get available content items
content_items = session.query(ContentItem).all()
if not content_items:
st.warning("No content items available. Please create content in the Content Calendar first.")
return
# Create schedule form
with st.form("create_schedule_form"):
st.subheader("Schedule Configuration")
# Select content item
content_options = {f"{item.title} ({item.content_type.value})": item.id for item in content_items}
selected_content = st.selectbox(
"Select Content Item",
options=list(content_options.keys()),
key="schedule_content_select"
)
# Schedule timing
col1, col2 = st.columns(2)
with col1:
schedule_date = st.date_input(
"Schedule Date",
value=datetime.now().date() + timedelta(days=1),
key="schedule_date"
)
with col2:
schedule_time = st.time_input(
"Schedule Time",
value=datetime.now().time(),
key="schedule_time"
)
# Combine date and time
schedule_datetime = datetime.combine(schedule_date, schedule_time)
# Recurrence options
recurrence = st.selectbox(
"Recurrence",
options=["none", "daily", "weekly", "monthly"],
key="schedule_recurrence"
)
# Priority
priority = st.slider(
"Priority",
min_value=1,
max_value=10,
value=5,
key="schedule_priority"
)
# Platform selection (override content item platforms if needed)
content_item_id = content_options[selected_content]
content_item = session.query(ContentItem).get(content_item_id)
if content_item:
current_platforms = content_item.platforms if isinstance(content_item.platforms, list) else [content_item.platforms]
st.write(f"**Current Platforms:** {', '.join(current_platforms)}")
override_platforms = st.checkbox("Override Platforms", key="override_platforms")
if override_platforms:
available_platforms = [p.value for p in Platform]
selected_platforms = st.multiselect(
"Select Platforms",
options=available_platforms,
default=current_platforms,
key="schedule_platforms"
)
else:
selected_platforms = current_platforms
# Submit button
submitted = st.form_submit_button("Create Schedule")
if submitted:
try:
# Create new schedule
new_schedule = Schedule(
content_item_id=content_item_id,
scheduled_time=schedule_datetime,
status=ScheduleStatus.SCHEDULED,
recurrence=recurrence if recurrence != "none" else None,
priority=priority
)
session.add(new_schedule)
session.commit()
st.success(f"✅ Schedule created successfully! Content will be published on {schedule_datetime}")
# Show schedule details
with st.expander("Schedule Details", expanded=True):
st.write(f"**Content:** {content_item.title}")
st.write(f"**Scheduled Time:** {schedule_datetime}")
st.write(f"**Platforms:** {', '.join(selected_platforms)}")
st.write(f"**Recurrence:** {recurrence}")
st.write(f"**Priority:** {priority}")
except Exception as e:
st.error(f"❌ Error creating schedule: {str(e)}")
def show_job_monitor():
"""Display the job monitoring interface."""
st.header("🔍 Job Monitor")
# Get all schedules with their status
all_schedules = session.query(Schedule).all()
if not all_schedules:
st.info("No jobs to monitor.")
return
# Status distribution
status_counts = {}
for schedule in all_schedules:
status = schedule.status.value
status_counts[status] = status_counts.get(status, 0) + 1
# Display status chart
if status_counts:
fig = px.pie(
values=list(status_counts.values()),
names=list(status_counts.keys()),
title="Job Status Distribution"
)
st.plotly_chart(fig, use_container_width=True)
# Recent job activity
st.subheader("📊 Recent Job Activity")
recent_schedules = sorted(all_schedules, key=lambda x: x.updated_at, reverse=True)[:10]
for schedule in recent_schedules:
content_item = session.query(ContentItem).get(schedule.content_item_id)
if content_item:
status_color = {
ScheduleStatus.SCHEDULED: "🟡",
ScheduleStatus.RUNNING: "🔵",
ScheduleStatus.COMPLETED: "🟢",
ScheduleStatus.FAILED: "🔴",
ScheduleStatus.CANCELLED: ""
}.get(schedule.status, "")
st.write(f"{status_color} **{content_item.title}** - {schedule.status.value} - {schedule.updated_at.strftime('%Y-%m-%d %H:%M')}")
if schedule.result:
st.write(f" └─ {schedule.result}")
def show_analytics():
"""Display the analytics dashboard."""
st.header("📈 Analytics")
# Get data
all_content = session.query(ContentItem).all()
all_schedules = session.query(Schedule).all()
if not all_schedules:
st.info("No data available for analytics.")
return
# Time-based analytics
st.subheader("📅 Schedule Timeline")
# Create timeline data
timeline_data = []
for schedule in all_schedules:
content_item = session.query(ContentItem).get(schedule.content_item_id)
if content_item:
timeline_data.append({
'Date': schedule.scheduled_time.date(),
'Content': content_item.title,
'Status': schedule.status.value,
'Type': content_item.content_type.value
})
if timeline_data:
df = pd.DataFrame(timeline_data)
# Schedule frequency by date
date_counts = df.groupby('Date').size().reset_index(name='Count')
fig = px.line(date_counts, x='Date', y='Count', title='Scheduled Content Over Time')
st.plotly_chart(fig, use_container_width=True)
# Content type distribution
type_counts = df['Type'].value_counts()
fig = px.bar(x=type_counts.index, y=type_counts.values, title='Content Type Distribution')
st.plotly_chart(fig, use_container_width=True)
# Status breakdown
status_counts = df['Status'].value_counts()
fig = px.pie(values=status_counts.values, names=status_counts.index, title='Status Distribution')
st.plotly_chart(fig, use_container_width=True)
# Performance metrics
st.subheader("📊 Performance Metrics")
col1, col2, col3 = st.columns(3)
with col1:
total_schedules = len(all_schedules)
st.metric("Total Schedules", total_schedules)
with col2:
completed_schedules = len([s for s in all_schedules if s.status == ScheduleStatus.COMPLETED])
success_rate = (completed_schedules / total_schedules * 100) if total_schedules > 0 else 0
st.metric("Success Rate", f"{success_rate:.1f}%")
with col3:
failed_schedules = len([s for s in all_schedules if s.status == ScheduleStatus.FAILED])
failure_rate = (failed_schedules / total_schedules * 100) if total_schedules > 0 else 0
st.metric("Failure Rate", f"{failure_rate:.1f}%")

View File

@@ -1,392 +0,0 @@
"""
Timeline view implementation for the Content Scheduler.
Provides interactive Gantt charts and progress tracking visualization.
"""
import streamlit as st
import plotly.figure_factory as ff
import plotly.graph_objects as go
from datetime import datetime, timedelta
from typing import List, Dict, Any, Optional
import pandas as pd
import json
# Use unified database models
from lib.database.models import ContentItem, Schedule, ScheduleStatus, get_session
class TimelineView:
"""Interactive timeline view with Gantt charts and progress tracking."""
def __init__(self):
"""Initialize the timeline view."""
self.session = get_session()
def render(self):
"""Render the timeline view."""
st.header("Schedule Timeline")
# Timeline controls
self._render_timeline_controls()
# Main timeline view
self._render_timeline()
# Progress tracking
self._render_progress_tracking()
def _render_timeline_controls(self):
"""Render timeline control options."""
col1, col2, col3 = st.columns([2, 2, 1])
with col1:
view_type = st.selectbox(
"View Type",
["Gantt Chart", "Timeline", "List View"],
help="Select the type of timeline visualization"
)
with col2:
date_range = st.date_input(
"Date Range",
value=(
datetime.now().date(),
datetime.now().date() + timedelta(days=7)
),
help="Select the date range to display"
)
with col3:
if st.button("Export", help="Export timeline data"):
self._export_timeline_data()
def _render_timeline(self):
"""Render the main timeline visualization."""
# Get schedules for the selected date range
schedules = self._get_schedules_for_timeline()
if not schedules:
st.info("No schedules found for the selected date range.")
return
# Create Gantt chart data
gantt_data = self._create_gantt_data(schedules)
# Create and display Gantt chart
fig = self._create_gantt_chart(gantt_data)
st.plotly_chart(fig, use_container_width=True)
# Display schedule details
self._render_schedule_details(schedules)
def _render_progress_tracking(self):
"""Render progress tracking visualization."""
st.subheader("Progress Tracking")
# Progress metrics
col1, col2, col3 = st.columns(3)
with col1:
self._render_progress_metric(
"Completed",
self._get_completed_count(),
"green"
)
with col2:
self._render_progress_metric(
"In Progress",
self._get_in_progress_count(),
"orange"
)
with col3:
self._render_progress_metric(
"Pending",
self._get_pending_count(),
"blue"
)
# Progress chart
self._render_progress_chart()
def _get_schedules_for_timeline(self) -> List[Schedule]:
"""Get schedules for the timeline view."""
try:
# Get date range from session state or use default
if hasattr(st.session_state, 'date_range') and st.session_state.date_range:
start_date, end_date = st.session_state.date_range
else:
start_date = datetime.now().date()
end_date = start_date + timedelta(days=7)
# Convert to datetime
start_datetime = datetime.combine(start_date, datetime.min.time())
end_datetime = datetime.combine(end_date, datetime.max.time())
# Query schedules from unified database
schedules = self.session.query(Schedule).filter(
Schedule.scheduled_time >= start_datetime,
Schedule.scheduled_time <= end_datetime
).all()
return schedules
except Exception as e:
st.error(f"Failed to get schedules: {str(e)}")
return []
def _create_gantt_data(self, schedules: List[Schedule]) -> List[Dict[str, Any]]:
"""Create data for Gantt chart."""
gantt_data = []
for schedule in schedules:
# Get content item details
content_item = self.session.query(ContentItem).filter(
ContentItem.id == schedule.content_item_id
).first()
if content_item:
# Calculate task duration
duration = timedelta(hours=1) # Default duration
# Create task data
task = {
'Task': content_item.title[:50] + "..." if len(content_item.title) > 50 else content_item.title,
'Start': schedule.scheduled_time,
'Finish': schedule.scheduled_time + duration,
'Resource': schedule.status.value,
'Status': schedule.status.value,
'Progress': self._calculate_progress(schedule)
}
gantt_data.append(task)
return gantt_data
def _create_gantt_chart(self, gantt_data: List[Dict[str, Any]]) -> go.Figure:
"""Create Gantt chart visualization."""
if not gantt_data:
# Return empty figure
fig = go.Figure()
fig.update_layout(
title='Content Schedule Timeline',
xaxis_title='Timeline',
yaxis_title='Status',
height=400
)
return fig
# Convert data to DataFrame
df = pd.DataFrame(gantt_data)
# Create Gantt chart
fig = ff.create_gantt(
df,
index_col='Resource',
show_colorbar=True,
group_tasks=True,
showgrid_x=True,
showgrid_y=True
)
# Update layout
fig.update_layout(
title='Content Schedule Timeline',
xaxis_title='Timeline',
yaxis_title='Status',
height=400,
showlegend=True
)
return fig
def _render_schedule_details(self, schedules: List[Schedule]):
"""Render detailed schedule information."""
st.subheader("Schedule Details")
for schedule in schedules:
# Get content item details
content_item = self.session.query(ContentItem).filter(
ContentItem.id == schedule.content_item_id
).first()
if content_item:
with st.expander(f"{content_item.title} - {schedule.status.value}"):
col1, col2 = st.columns(2)
with col1:
st.write("**Schedule Information**")
st.write(f"Content Type: {content_item.content_type.value if content_item.content_type else 'Unknown'}")
st.write(f"Status: {schedule.status.value}")
st.write(f"Scheduled Time: {schedule.scheduled_time}")
st.write(f"Priority: {schedule.priority}")
if schedule.recurrence:
st.write(f"Recurrence: {schedule.recurrence}")
with col2:
st.write("**Progress**")
progress = self._calculate_progress(schedule)
st.progress(progress / 100)
st.write(f"Progress: {progress:.1f}%")
# Action buttons
col2a, col2b = st.columns(2)
with col2a:
if st.button(f"Edit {schedule.id}", key=f"edit_{schedule.id}"):
st.session_state.edit_schedule_id = schedule.id
with col2b:
if st.button(f"Cancel {schedule.id}", key=f"cancel_{schedule.id}"):
self._cancel_schedule(schedule.id)
def _render_progress_metric(self, label: str, value: int, color: str):
"""Render a progress metric."""
st.metric(label, value)
def _render_progress_chart(self):
"""Render progress chart visualization."""
try:
# Get progress data
progress_data = self._get_progress_data()
if progress_data:
# Create pie chart
labels = list(progress_data.keys())
values = list(progress_data.values())
fig = go.Figure(data=[go.Pie(labels=labels, values=values)])
fig.update_layout(
title="Schedule Status Distribution",
height=300
)
st.plotly_chart(fig, use_container_width=True)
else:
st.info("No progress data available.")
except Exception as e:
st.error(f"Error rendering progress chart: {str(e)}")
def _calculate_progress(self, schedule: Schedule) -> float:
"""Calculate progress percentage for a schedule."""
try:
if schedule.status == ScheduleStatus.COMPLETED:
return 100.0
elif schedule.status == ScheduleStatus.RUNNING:
return 50.0
elif schedule.status == ScheduleStatus.FAILED:
return 0.0
else: # PENDING
return 0.0
except Exception as e:
st.error(f"Error calculating progress: {str(e)}")
return 0.0
def _get_completed_count(self) -> int:
"""Get count of completed schedules."""
try:
return self.session.query(Schedule).filter(
Schedule.status == ScheduleStatus.COMPLETED
).count()
except Exception as e:
st.error(f"Error getting completed count: {str(e)}")
return 0
def _get_in_progress_count(self) -> int:
"""Get count of in-progress schedules."""
try:
return self.session.query(Schedule).filter(
Schedule.status == ScheduleStatus.RUNNING
).count()
except Exception as e:
st.error(f"Error getting in-progress count: {str(e)}")
return 0
def _get_pending_count(self) -> int:
"""Get count of pending schedules."""
try:
return self.session.query(Schedule).filter(
Schedule.status == ScheduleStatus.PENDING
).count()
except Exception as e:
st.error(f"Error getting pending count: {str(e)}")
return 0
def _get_progress_data(self) -> Dict[str, int]:
"""Get progress data for visualization."""
try:
progress_data = {}
# Count schedules by status
for status in ScheduleStatus:
count = self.session.query(Schedule).filter(
Schedule.status == status
).count()
progress_data[status.value] = count
return progress_data
except Exception as e:
st.error(f"Error getting progress data: {str(e)}")
return {}
def _cancel_schedule(self, schedule_id: int):
"""Cancel a schedule."""
try:
schedule = self.session.query(Schedule).filter(
Schedule.id == schedule_id
).first()
if schedule:
schedule.status = ScheduleStatus.CANCELLED
self.session.commit()
st.success(f"Schedule {schedule_id} cancelled successfully!")
st.experimental_rerun()
else:
st.error("Schedule not found.")
except Exception as e:
st.error(f"Error cancelling schedule: {str(e)}")
self.session.rollback()
def _export_timeline_data(self):
"""Export timeline data."""
try:
schedules = self._get_schedules_for_timeline()
if schedules:
# Prepare export data
export_data = []
for schedule in schedules:
content_item = self.session.query(ContentItem).filter(
ContentItem.id == schedule.content_item_id
).first()
if content_item:
export_data.append({
'Schedule ID': schedule.id,
'Title': content_item.title,
'Content Type': content_item.content_type.value if content_item.content_type else 'Unknown',
'Scheduled Time': schedule.scheduled_time.isoformat(),
'Status': schedule.status.value,
'Priority': schedule.priority,
'Recurrence': schedule.recurrence or 'None'
})
# Convert to CSV
df = pd.DataFrame(export_data)
csv = df.to_csv(index=False)
# Provide download
st.download_button(
label="Download Timeline Data",
data=csv,
file_name=f"timeline_data_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv",
mime="text/csv"
)
else:
st.warning("No data to export.")
except Exception as e:
st.error(f"Error exporting data: {str(e)}")

View File

@@ -1,283 +0,0 @@
# Platform Adapters
A flexible and extensible system for managing content across different social media platforms and content management systems.
## Overview
The platform adapters system provides a unified interface for publishing, managing, and analyzing content across multiple platforms. It follows a modular architecture where each platform has its own adapter implementation while maintaining a consistent interface.
## Architecture
### Core Components
1. **Base Platform Adapter (`base.py`)**
- Abstract base class defining the interface for all platform adapters
- Common functionality and error handling
- Standardized response formatting
2. **Platform Manager (`manager.py`)**
- Central manager for handling multiple platform adapters
- Platform initialization and configuration
- Unified content publishing and management
3. **Unified Platform Adapter (`unified.py`)**
- Content adaptation across different platforms
- Platform-specific content generation
- Performance analytics and recommendations
### Current Implementations
#### Twitter Adapter (`twitter.py`)
- Full implementation of Twitter API integration
- Features:
- Tweet publishing with media support
- Content validation
- Analytics and engagement metrics
- Media upload handling
- Rate limit management
#### WordPress Adapter (TBD)
- Planned implementation of WordPress REST API integration
- Features:
- ⏳ Post creation and management
- ⏳ Page management
- ⏳ Media library integration
- ⏳ Category and tag management
- ⏳ Custom post type support
- ⏳ SEO metadata management
- ⏳ Comment moderation
- ⏳ User management
#### Wix Adapter (TBD)
- Planned implementation of Wix API integration
- Features:
- ⏳ Blog post management
- ⏳ Page content management
- ⏳ Media upload and management
- ⏳ SEO settings
- ⏳ Collection management
- ⏳ Form submissions handling
- ⏳ Site settings management
- ⏳ Analytics integration
## Features
### Core Features
- ✅ Multi-platform content publishing
- ✅ Content validation and optimization
- ✅ Analytics and performance tracking
- ✅ Media handling
- ✅ Error handling and logging
- ✅ Platform-specific content adaptation
### Platform-Specific Features
#### Twitter
- ✅ Tweet publishing
- ✅ Media attachments
- ✅ Analytics tracking
- ✅ Content validation
- ✅ Rate limit handling
#### Instagram (TBD)
- ⏳ Post creation
- ⏳ Story publishing
- ⏳ Hashtag optimization
- ⏳ Media handling
#### LinkedIn (TBD)
- ⏳ Post creation
- ⏳ Article publishing
- ⏳ Professional content optimization
- ⏳ Company page integration
#### Facebook (TBD)
- ⏳ Post creation
- ⏳ Page management
- ⏳ Audience targeting
- ⏳ Analytics integration
#### WordPress (TBD)
- ⏳ REST API integration
- ⏳ Content synchronization
- ⏳ Media management
- ⏳ SEO optimization
- ⏳ Custom post types
- ⏳ Plugin integration
#### Wix (TBD)
- ⏳ API integration
- ⏳ Content management
- ⏳ Media handling
- ⏳ SEO settings
- ⏳ Collection management
- ⏳ Analytics integration
## Configuration
Each platform adapter requires specific configuration parameters:
### Twitter Configuration
```python
{
'api_key': 'your_api_key',
'api_secret': 'your_api_secret',
'access_token': 'your_access_token',
'access_token_secret': 'your_access_token_secret'
}
```
### WordPress Configuration
```python
{
'site_url': 'https://your-wordpress-site.com',
'username': 'your_username',
'application_password': 'your_application_password',
'api_version': 'v2'
}
```
### Wix Configuration
```python
{
'site_id': 'your_site_id',
'api_key': 'your_api_key',
'access_token': 'your_access_token'
}
```
## Usage
### Basic Usage
```python
from lib.integrations.platform_adapters.manager import PlatformManager
# Initialize platform manager
config = {
'platforms': {
'twitter': {
'api_key': 'your_api_key',
'api_secret': 'your_api_secret',
'access_token': 'your_access_token',
'access_token_secret': 'your_access_token_secret'
},
'wordpress': {
'site_url': 'https://your-wordpress-site.com',
'username': 'your_username',
'application_password': 'your_application_password'
},
'wix': {
'site_id': 'your_site_id',
'api_key': 'your_api_key',
'access_token': 'your_access_token'
}
}
}
manager = PlatformManager(config)
# Publish content
content = {
'text': 'Hello, World!',
'media': [
{
'url': 'https://example.com/image.jpg',
'type': 'image'
}
]
}
result = await manager.publish_content(content, platforms=['twitter', 'wordpress', 'wix'])
```
## TBD Features
### Platform Support
- [ ] Instagram adapter implementation
- [ ] LinkedIn adapter implementation
- [ ] Facebook adapter implementation
- [ ] YouTube adapter implementation
- [ ] TikTok adapter implementation
- [ ] WordPress adapter implementation
- [ ] Wix adapter implementation
### Content Management
- [ ] Bulk content publishing
- [ ] Content scheduling
- [ ] Content templates
- [ ] A/B testing support
- [ ] Content versioning
- [ ] Cross-platform content synchronization
- [ ] CMS-specific content optimization
### Analytics
- [ ] Cross-platform analytics
- [ ] Custom metric tracking
- [ ] Automated reporting
- [ ] Performance optimization suggestions
- [ ] ROI tracking
- [ ] CMS-specific analytics integration
### Media Handling
- [ ] Advanced media optimization
- [ ] Media library management
- [ ] Automatic media resizing
- [ ] Media format conversion
- [ ] Media metadata management
- [ ] Cross-platform media synchronization
### Security
- [ ] OAuth2 implementation
- [ ] API key rotation
- [ ] Rate limit handling
- [ ] Error recovery
- [ ] Audit logging
- [ ] CMS-specific security features
## Contributing
1. Fork the repository
2. Create a feature branch
3. Implement your changes
4. Add tests
5. Submit a pull request
## Testing
Each platform adapter should include:
- Unit tests
- Integration tests
- Mock API responses
- Error handling tests
- Rate limit tests
- CMS-specific test cases
## Error Handling
The system implements standardized error handling:
- Platform-specific error mapping
- Retry mechanisms
- Error logging
- User-friendly error messages
- CMS-specific error handling
## Logging
Comprehensive logging system:
- Platform operations
- API calls
- Error tracking
- Performance metrics
- Debug information
- CMS-specific logging
## Dependencies
- Python 3.11+
- tweepy (for Twitter integration)
- requests
- loguru
- typing
- datetime
- wordpress-xmlrpc (for WordPress integration)
- wix-api-client (for Wix integration)

View File

@@ -1,15 +0,0 @@
"""
Platform adapters for content publishing and management.
"""
from .base import PlatformAdapter
from .manager import PlatformManager
from .twitter import TwitterAdapter
from .unified import UnifiedPlatformAdapter
__all__ = [
'PlatformAdapter',
'PlatformManager',
'TwitterAdapter',
'UnifiedPlatformAdapter'
]

View File

@@ -1,157 +0,0 @@
"""
Base platform adapter class.
"""
from abc import ABC, abstractmethod
from typing import Dict, Any, Optional, List
from datetime import datetime
class PlatformAdapter(ABC):
"""Base class for platform-specific adapters."""
def __init__(self, config: Dict[str, Any]):
"""Initialize platform adapter with configuration."""
self.config = config
self.platform_name = self.__class__.__name__.replace('Adapter', '').upper()
@abstractmethod
async def publish_content(
self,
content: Dict[str, Any],
schedule_time: Optional[datetime] = None
) -> Dict[str, Any]:
"""Publish content to the platform."""
pass
@abstractmethod
async def get_content_status(
self,
content_id: str
) -> Dict[str, Any]:
"""Get the status of published content."""
pass
@abstractmethod
async def delete_content(
self,
content_id: str
) -> Dict[str, Any]:
"""Delete published content."""
pass
@abstractmethod
async def update_content(
self,
content_id: str,
updates: Dict[str, Any]
) -> Dict[str, Any]:
"""Update published content."""
pass
@abstractmethod
async def get_analytics(
self,
content_id: str,
start_date: Optional[datetime] = None,
end_date: Optional[datetime] = None
) -> Dict[str, Any]:
"""Get analytics for published content."""
pass
@abstractmethod
async def validate_content(
self,
content: Dict[str, Any]
) -> Dict[str, Any]:
"""Validate content before publishing."""
pass
@abstractmethod
async def get_optimal_publish_time(
self,
content_type: str,
target_audience: Optional[Dict[str, Any]] = None
) -> datetime:
"""Get optimal publish time for content."""
pass
@abstractmethod
async def get_platform_limits(
self
) -> Dict[str, Any]:
"""Get platform-specific limits and constraints."""
pass
@abstractmethod
async def get_supported_content_types(
self
) -> List[str]:
"""Get list of supported content types."""
pass
@abstractmethod
async def get_platform_metrics(
self
) -> Dict[str, Any]:
"""Get platform-specific metrics and statistics."""
pass
def _format_error_response(
self,
error: Exception,
context: Optional[Dict[str, Any]] = None
) -> Dict[str, Any]:
"""Format error response."""
return {
'success': False,
'platform': self.platform_name,
'error': str(error),
'error_type': error.__class__.__name__,
'context': context or {}
}
def _format_success_response(
self,
data: Dict[str, Any],
context: Optional[Dict[str, Any]] = None
) -> Dict[str, Any]:
"""Format success response."""
return {
'success': True,
'platform': self.platform_name,
'data': data,
'context': context or {}
}
def _validate_config(self) -> None:
"""Validate platform configuration."""
required_fields = self.get_required_config_fields()
missing_fields = [
field for field in required_fields
if field not in self.config
]
if missing_fields:
raise ValueError(
f"Missing required configuration fields: {', '.join(missing_fields)}"
)
@classmethod
def get_required_config_fields(cls) -> List[str]:
"""Get list of required configuration fields."""
return []
@classmethod
def get_platform_name(cls) -> str:
"""Get platform name."""
return cls.__name__.replace('Adapter', '').upper()
@classmethod
def get_platform_description(cls) -> str:
"""Get platform description."""
return "Base platform adapter"
@classmethod
def get_platform_version(cls) -> str:
"""Get platform adapter version."""
return "1.0.0"

View File

@@ -1,284 +0,0 @@
"""
Platform manager for handling multiple platform adapters.
"""
import logging
from typing import Dict, Any, List, Optional, Type
from datetime import datetime
from .base import PlatformAdapter
from .twitter import TwitterAdapter
from .wix import WixAdapter
logger = logging.getLogger(__name__)
class PlatformManager:
"""Manages multiple platform adapters."""
def __init__(self, config: Dict[str, Any]):
"""Initialize platform manager with configuration."""
self.config = config
self.adapters: Dict[str, PlatformAdapter] = {}
self._initialize_adapters()
def _initialize_adapters(self) -> None:
"""Initialize platform adapters based on configuration."""
platform_configs = self.config.get('platforms', {})
for platform, config in platform_configs.items():
try:
adapter = self._create_adapter(platform, config)
if adapter:
self.adapters[platform] = adapter
logger.info(f"Initialized {platform} adapter")
except Exception as e:
logger.error(f"Failed to initialize {platform} adapter: {str(e)}")
def _create_adapter(
self,
platform: str,
config: Dict[str, Any]
) -> Optional[PlatformAdapter]:
"""Create platform adapter instance."""
adapter_map: Dict[str, Type[PlatformAdapter]] = {
'TWITTER': TwitterAdapter,
'WIX': WixAdapter,
# Add other platform adapters here
}
adapter_class = adapter_map.get(platform.upper())
if not adapter_class:
logger.warning(f"Unsupported platform: {platform}")
return None
try:
return adapter_class(config)
except Exception as e:
raise Exception(
f"Failed to create {platform} adapter: {str(e)}"
)
async def publish_content(
self,
content: Dict[str, Any],
platforms: List[str],
schedule_time: Optional[datetime] = None
) -> Dict[str, Dict[str, Any]]:
"""Publish content to multiple platforms."""
results = {}
for platform in platforms:
if platform not in self.adapters:
results[platform] = {
'success': False,
'error': f"Platform adapter not found: {platform}"
}
continue
try:
result = await self.adapters[platform].publish_content(
content,
schedule_time
)
results[platform] = result
except Exception as e:
results[platform] = {
'success': False,
'error': str(e)
}
return results
async def get_content_status(
self,
content_id: str,
platform: str
) -> Dict[str, Any]:
"""Get content status from a specific platform."""
if platform not in self.adapters:
return {
'success': False,
'error': f"Platform adapter not found: {platform}"
}
try:
return await self.adapters[platform].get_content_status(content_id)
except Exception as e:
return {
'success': False,
'error': str(e)
}
async def delete_content(
self,
content_id: str,
platform: str
) -> Dict[str, Any]:
"""Delete content from a specific platform."""
if platform not in self.adapters:
return {
'success': False,
'error': f"Platform adapter not found: {platform}"
}
try:
return await self.adapters[platform].delete_content(content_id)
except Exception as e:
return {
'success': False,
'error': str(e)
}
async def update_content(
self,
content_id: str,
updates: Dict[str, Any],
platform: str
) -> Dict[str, Any]:
"""Update content on a specific platform."""
if platform not in self.adapters:
return {
'success': False,
'error': f"Platform adapter not found: {platform}"
}
try:
return await self.adapters[platform].update_content(
content_id,
updates
)
except Exception as e:
return {
'success': False,
'error': str(e)
}
async def get_analytics(
self,
content_id: str,
platform: str,
start_date: Optional[datetime] = None,
end_date: Optional[datetime] = None
) -> Dict[str, Any]:
"""Get analytics from a specific platform."""
if platform not in self.adapters:
return {
'success': False,
'error': f"Platform adapter not found: {platform}"
}
try:
return await self.adapters[platform].get_analytics(
content_id,
start_date,
end_date
)
except Exception as e:
return {
'success': False,
'error': str(e)
}
async def validate_content(
self,
content: Dict[str, Any],
platform: str
) -> Dict[str, Any]:
"""Validate content for a specific platform."""
if platform not in self.adapters:
return {
'success': False,
'error': f"Platform adapter not found: {platform}"
}
try:
return await self.adapters[platform].validate_content(content)
except Exception as e:
return {
'success': False,
'error': str(e)
}
async def get_optimal_publish_time(
self,
content_type: str,
platform: str,
target_audience: Optional[Dict[str, Any]] = None
) -> datetime:
"""Get optimal publish time for a specific platform."""
if platform not in self.adapters:
raise Exception(f"Platform adapter not found: {platform}")
return await self.adapters[platform].get_optimal_publish_time(
content_type,
target_audience
)
async def get_platform_limits(
self,
platform: str
) -> Dict[str, Any]:
"""Get platform limits for a specific platform."""
if platform not in self.adapters:
return {
'success': False,
'error': f"Platform adapter not found: {platform}"
}
try:
return await self.adapters[platform].get_platform_limits()
except Exception as e:
return {
'success': False,
'error': str(e)
}
async def get_supported_content_types(
self,
platform: str
) -> List[str]:
"""Get supported content types for a specific platform."""
if platform not in self.adapters:
raise Exception(f"Platform adapter not found: {platform}")
return await self.adapters[platform].get_supported_content_types()
async def get_platform_metrics(
self,
platform: str
) -> Dict[str, Any]:
"""Get platform metrics for a specific platform."""
if platform not in self.adapters:
return {
'success': False,
'error': f"Platform adapter not found: {platform}"
}
try:
return await self.adapters[platform].get_platform_metrics()
except Exception as e:
return {
'success': False,
'error': str(e)
}
def get_available_platforms(self) -> List[str]:
"""Get list of available platform adapters."""
return list(self.adapters.keys())
def get_platform_info(self, platform: str) -> Dict[str, Any]:
"""Get information about a specific platform."""
if platform not in self.adapters:
return {
'success': False,
'error': f"Platform adapter not found: {platform}"
}
adapter = self.adapters[platform]
return {
'success': True,
'name': adapter.get_platform_name(),
'description': adapter.get_platform_description(),
'version': adapter.get_platform_version(),
'required_config': adapter.get_required_config_fields()
}

View File

@@ -1,568 +0,0 @@
"""
Twitter platform adapter implementation with enhanced error handling and real metrics.
"""
from typing import Dict, Any, Optional, List
from datetime import datetime
import tweepy
from tweepy.models import Status
import logging
import time
from .base import PlatformAdapter
logger = logging.getLogger(__name__)
class TwitterAdapter(PlatformAdapter):
"""Enhanced Twitter platform adapter with real metrics and error handling."""
def __init__(self, config: Dict[str, Any]):
"""Initialize Twitter adapter with configuration."""
super().__init__(config)
self._validate_config()
self._initialize_client()
self.rate_limit_tracker = {}
def _initialize_client(self) -> None:
"""Initialize Twitter API client with enhanced error handling."""
try:
# Initialize OAuth handler
auth = tweepy.OAuthHandler(
self.config['api_key'],
self.config['api_secret']
)
auth.set_access_token(
self.config['access_token'],
self.config['access_token_secret']
)
# Create API client with wait_on_rate_limit
self.client = tweepy.API(
auth,
wait_on_rate_limit=True,
retry_count=3,
retry_delay=5
)
# Verify credentials
user = self.client.verify_credentials()
if not user:
raise Exception("Failed to verify Twitter credentials")
logger.info(f"Twitter client initialized for @{user.screen_name}")
except tweepy.Unauthorized:
raise Exception("Invalid Twitter API credentials")
except tweepy.Forbidden:
raise Exception("Access forbidden - check API permissions")
except Exception as e:
raise Exception(f"Failed to initialize Twitter client: {str(e)}")
async def publish_content(
self,
content: Dict[str, Any],
schedule_time: Optional[datetime] = None
) -> Dict[str, Any]:
"""Publish content to Twitter with enhanced error handling."""
try:
# Validate content first
validation = await self.validate_content(content)
if not validation.get('success'):
return validation
# Check rate limits
if not self._check_rate_limit('tweets'):
return self._format_error_response(
Exception("Rate limit exceeded for tweets"),
{'content': content}
)
# Prepare tweet content
tweet_text = content.get('text', '')
media_ids = []
# Handle media attachments if present
if 'media' in content and content['media']:
for media in content['media']:
media_id = self._upload_media(media)
if media_id:
media_ids.append(media_id)
# Create tweet
tweet = self.client.update_status(
status=tweet_text,
media_ids=media_ids if media_ids else None
)
# Update rate limit tracker
self._update_rate_limit_tracker('tweets')
# Format response with comprehensive data
tweet_data = {
'id': tweet.id_str,
'text': tweet.text,
'created_at': tweet.created_at.isoformat(),
'user': {
'screen_name': tweet.user.screen_name,
'name': tweet.user.name,
'followers_count': tweet.user.followers_count
},
'metrics': {
'retweet_count': tweet.retweet_count,
'favorite_count': tweet.favorite_count,
'reply_count': getattr(tweet, 'reply_count', 0)
},
'urls': {
'tweet_url': f"https://twitter.com/{tweet.user.screen_name}/status/{tweet.id_str}"
}
}
return self._format_success_response(tweet_data)
except tweepy.Unauthorized:
return self._format_error_response(
Exception("Authentication failed - please reconnect your account"),
{'content': content}
)
except tweepy.Forbidden as e:
error_msg = "Access forbidden"
if "duplicate" in str(e).lower():
error_msg = "Duplicate tweet detected - please modify your content"
elif "automated" in str(e).lower():
error_msg = "Tweet appears automated - please make it more personal"
return self._format_error_response(
Exception(error_msg),
{'content': content}
)
except tweepy.TooManyRequests:
return self._format_error_response(
Exception("Rate limit exceeded - please wait before posting again"),
{'content': content}
)
except Exception as e:
return self._format_error_response(e, {'content': content})
async def get_content_status(self, content_id: str) -> Dict[str, Any]:
"""Get status of a tweet with real metrics."""
try:
tweet = self.client.get_status(
content_id,
include_entities=True,
tweet_mode='extended'
)
tweet_data = {
'id': tweet.id_str,
'text': tweet.full_text,
'created_at': tweet.created_at.isoformat(),
'metrics': {
'retweet_count': tweet.retweet_count,
'favorite_count': tweet.favorite_count,
'reply_count': getattr(tweet, 'reply_count', 0),
'quote_count': getattr(tweet, 'quote_count', 0)
},
'engagement': {
'engagement_rate': self._calculate_engagement_rate(tweet),
'total_engagement': tweet.retweet_count + tweet.favorite_count + getattr(tweet, 'reply_count', 0)
},
'user': {
'screen_name': tweet.user.screen_name,
'followers_count': tweet.user.followers_count
}
}
return self._format_success_response(tweet_data)
except tweepy.NotFound:
return self._format_error_response(
Exception("Tweet not found - it may have been deleted"),
{'content_id': content_id}
)
except Exception as e:
return self._format_error_response(e, {'content_id': content_id})
async def get_analytics(
self,
content_id: str,
start_date: Optional[datetime] = None,
end_date: Optional[datetime] = None
) -> Dict[str, Any]:
"""Get comprehensive analytics for a tweet."""
try:
# Get tweet details
tweet = self.client.get_status(
content_id,
include_entities=True,
tweet_mode='extended'
)
# Calculate engagement metrics
total_engagement = (
tweet.retweet_count +
tweet.favorite_count +
getattr(tweet, 'reply_count', 0) +
getattr(tweet, 'quote_count', 0)
)
engagement_rate = self._calculate_engagement_rate(tweet)
# Get time-based metrics (if tweet is recent)
time_metrics = self._calculate_time_metrics(tweet)
analytics_data = {
'tweet_id': tweet.id_str,
'metrics': {
'likes': tweet.favorite_count,
'retweets': tweet.retweet_count,
'replies': getattr(tweet, 'reply_count', 0),
'quotes': getattr(tweet, 'quote_count', 0),
'total_engagement': total_engagement,
'impressions': getattr(tweet, 'impression_count', 0) # May not be available
},
'engagement': {
'engagement_rate': engagement_rate,
'likes_rate': (tweet.favorite_count / tweet.user.followers_count * 100) if tweet.user.followers_count > 0 else 0,
'retweets_rate': (tweet.retweet_count / tweet.user.followers_count * 100) if tweet.user.followers_count > 0 else 0
},
'timing': time_metrics,
'audience': {
'followers_at_post': tweet.user.followers_count,
'reach_percentage': (total_engagement / tweet.user.followers_count * 100) if tweet.user.followers_count > 0 else 0
},
'content_analysis': {
'character_count': len(tweet.full_text),
'hashtag_count': len([entity for entity in tweet.entities.get('hashtags', [])]),
'mention_count': len([entity for entity in tweet.entities.get('user_mentions', [])]),
'url_count': len([entity for entity in tweet.entities.get('urls', [])])
}
}
return self._format_success_response(analytics_data)
except Exception as e:
return self._format_error_response(e, {
'content_id': content_id,
'start_date': start_date,
'end_date': end_date
})
async def validate_content(self, content: Dict[str, Any]) -> Dict[str, Any]:
"""Enhanced content validation."""
try:
errors = []
warnings = []
# Check text
text = content.get('text', '')
if not text.strip():
errors.append("Tweet text cannot be empty")
# Check length
if len(text) > 280:
errors.append(f"Tweet text exceeds 280 characters ({len(text)}/280)")
elif len(text) > 270:
warnings.append("Tweet is close to character limit")
# Check for very short tweets
if len(text) < 10:
warnings.append("Very short tweets may get less engagement")
# Check media
media = content.get('media', [])
if len(media) > 4:
errors.append("Maximum 4 media attachments allowed")
# Check for spam indicators
if text.count('#') > 3:
warnings.append("Too many hashtags may reduce engagement")
if text.count('@') > 5:
warnings.append("Too many mentions may appear spammy")
# Check for duplicate content (basic check)
if self._is_potential_duplicate(text):
warnings.append("Content may be similar to recent tweets")
if errors:
return self._format_error_response(
ValueError(f"Validation failed: {'; '.join(errors)}"),
{'content': content, 'warnings': warnings}
)
validation_data = {
'valid': True,
'content': content,
'warnings': warnings,
'suggestions': self._get_content_suggestions(text)
}
return self._format_success_response(validation_data)
except Exception as e:
return self._format_error_response(e, {'content': content})
def _calculate_engagement_rate(self, tweet: Status) -> float:
"""Calculate engagement rate for a tweet."""
try:
total_engagement = (
tweet.favorite_count +
tweet.retweet_count +
getattr(tweet, 'reply_count', 0) +
getattr(tweet, 'quote_count', 0)
)
followers = tweet.user.followers_count
return (total_engagement / followers * 100) if followers > 0 else 0.0
except Exception:
return 0.0
def _calculate_time_metrics(self, tweet: Status) -> Dict[str, Any]:
"""Calculate time-based metrics for a tweet."""
try:
now = datetime.now()
tweet_time = tweet.created_at.replace(tzinfo=None)
age_hours = (now - tweet_time).total_seconds() / 3600
# Calculate engagement velocity (engagement per hour)
total_engagement = (
tweet.favorite_count +
tweet.retweet_count +
getattr(tweet, 'reply_count', 0)
)
engagement_velocity = total_engagement / max(age_hours, 1)
return {
'age_hours': round(age_hours, 2),
'engagement_velocity': round(engagement_velocity, 2),
'peak_engagement_period': self._estimate_peak_period(tweet_time),
'posted_at': tweet_time.isoformat()
}
except Exception:
return {}
def _estimate_peak_period(self, tweet_time: datetime) -> str:
"""Estimate if tweet was posted during peak engagement period."""
hour = tweet_time.hour
if 9 <= hour <= 10:
return "Morning Peak (9-10 AM)"
elif 12 <= hour <= 13:
return "Lunch Peak (12-1 PM)"
elif 19 <= hour <= 21:
return "Evening Peak (7-9 PM)"
else:
return "Off-Peak Hours"
def _check_rate_limit(self, endpoint: str) -> bool:
"""Check if we're within rate limits for an endpoint."""
try:
rate_limits = self.client.get_rate_limit_status()
endpoint_map = {
'tweets': '/statuses/update',
'user_timeline': '/statuses/user_timeline',
'verify_credentials': '/account/verify_credentials'
}
if endpoint in endpoint_map:
limit_info = rate_limits['resources']['statuses'].get(endpoint_map[endpoint])
if limit_info:
return limit_info['remaining'] > 0
return True # Default to allowing if we can't check
except Exception:
return True # Default to allowing if check fails
def _update_rate_limit_tracker(self, endpoint: str) -> None:
"""Update internal rate limit tracker."""
now = time.time()
if endpoint not in self.rate_limit_tracker:
self.rate_limit_tracker[endpoint] = []
# Add current request
self.rate_limit_tracker[endpoint].append(now)
# Clean old requests (older than 15 minutes)
self.rate_limit_tracker[endpoint] = [
timestamp for timestamp in self.rate_limit_tracker[endpoint]
if now - timestamp < 900 # 15 minutes
]
def _is_potential_duplicate(self, text: str) -> bool:
"""Basic check for potential duplicate content."""
# This is a simplified check - in production, you'd want more sophisticated detection
try:
# Get recent tweets from user
recent_tweets = self.client.user_timeline(count=20, tweet_mode='extended')
for tweet in recent_tweets:
# Simple similarity check
if self._calculate_text_similarity(text, tweet.full_text) > 0.8:
return True
return False
except Exception:
return False # If we can't check, assume it's not a duplicate
def _calculate_text_similarity(self, text1: str, text2: str) -> float:
"""Calculate simple text similarity."""
# Simple word-based similarity
words1 = set(text1.lower().split())
words2 = set(text2.lower().split())
if not words1 or not words2:
return 0.0
intersection = words1.intersection(words2)
union = words1.union(words2)
return len(intersection) / len(union) if union else 0.0
def _get_content_suggestions(self, text: str) -> List[str]:
"""Get suggestions for improving tweet content."""
suggestions = []
if len(text) < 50:
suggestions.append("Consider adding more context to increase engagement")
if not any(char in text for char in '!?'):
suggestions.append("Adding punctuation can make tweets more engaging")
if '#' not in text:
suggestions.append("Consider adding 1-2 relevant hashtags")
if not any(emoji_char in text for emoji_char in '😀😃😄😁😆😅😂🤣'):
suggestions.append("Emojis can increase engagement and visual appeal")
return suggestions
async def delete_content(
self,
content_id: str
) -> Dict[str, Any]:
"""Delete a tweet."""
try:
self.client.destroy_status(content_id)
return self._format_success_response({
'id': content_id,
'deleted': True
})
except Exception as e:
return self._format_error_response(
e,
{'content_id': content_id}
)
async def update_content(
self,
content_id: str,
updates: Dict[str, Any]
) -> Dict[str, Any]:
"""Update a tweet."""
try:
# Twitter doesn't support updating tweets
# We'll delete the old one and create a new one
await self.delete_content(content_id)
return await self.publish_content(updates)
except Exception as e:
return self._format_error_response(
e,
{
'content_id': content_id,
'updates': updates
}
)
async def get_optimal_publish_time(
self,
content_type: str,
target_audience: Optional[Dict[str, Any]] = None
) -> datetime:
"""Get optimal publish time for content."""
# Implement optimal time calculation based on:
# - Content type
# - Target audience timezone
# - Historical engagement data
# For now, return current time
return datetime.now()
async def get_platform_limits(
self
) -> Dict[str, Any]:
"""Get Twitter platform limits."""
return self._format_success_response({
'tweet_length': 280,
'media_attachments': 4,
'poll_options': 4,
'poll_duration': 10080, # 7 days in minutes
'rate_limits': {
'tweets_per_day': 2000,
'tweets_per_hour': 100
}
})
async def get_supported_content_types(
self
) -> List[str]:
"""Get list of supported content types."""
return ['TWEET', 'THREAD', 'POLL']
async def get_platform_metrics(
self
) -> Dict[str, Any]:
"""Get Twitter platform metrics."""
try:
account = self.client.verify_credentials()
return self._format_success_response({
'followers_count': account.followers_count,
'following_count': account.friends_count,
'tweets_count': account.statuses_count,
'account_created_at': account.created_at.isoformat()
})
except Exception as e:
return self._format_error_response(e)
def _upload_media(self, media: Dict[str, Any]) -> Optional[str]:
"""Upload media to Twitter."""
try:
if 'url' in media:
# Download media from URL
response = requests.get(media['url'])
media_file = BytesIO(response.content)
elif 'file' in media:
# Use local file
media_file = open(media['file'], 'rb')
else:
return None
# Upload media
media_upload = self.client.media_upload(
filename=media.get('filename', 'media'),
file=media_file
)
return media_upload.media_id_string
except Exception as e:
logger.error(f"Failed to upload media: {str(e)}")
return None
@classmethod
def get_required_config_fields(cls) -> List[str]:
"""Get list of required configuration fields."""
return [
'api_key',
'api_secret',
'access_token',
'access_token_secret'
]
@classmethod
def get_platform_description(cls) -> str:
"""Get platform description."""
return "Twitter platform adapter for posting and managing tweets"
@classmethod
def get_platform_version(cls) -> str:
"""Get platform adapter version."""
return "1.0.0"

View File

@@ -1,290 +0,0 @@
"""
Unified platform adapter for content adaptation across different platforms.
"""
import logging
from typing import Dict, Any, List, Optional
from datetime import datetime
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
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 get_content_performance(self, content_item: Dict[str, Any]) -> Dict[str, Any]:
"""Get performance metrics for content across platforms."""
try:
logger.info(f"Getting performance metrics for content: {content_item.get('title', 'Untitled')}")
# Get platform from content item
platform = content_item.get('platforms', ['Unknown'])[0]
# Initialize performance metrics
performance = {
'engagement_metrics': {
'likes': 0,
'comments': 0,
'shares': 0,
'reach': 0
},
'seo_metrics': {
'impressions': 0,
'clicks': 0,
'ctr': 0,
'position': 0
},
'conversion_metrics': {
'conversions': 0,
'conversion_rate': 0,
'revenue': 0
},
'platform_specific': {},
'performance_trends': [],
'recommendations': []
}
# Add platform-specific metrics
if platform == 'WEBSITE':
performance['platform_specific'] = {
'bounce_rate': 0,
'time_on_page': 0,
'page_views': 0
}
return performance
except Exception as e:
error_msg = f"Error getting content performance: {str(e)}"
logger.error(error_msg, exc_info=True)
return {
'error': error_msg,
'metrics': {},
'trends': {},
'recommendations': []
}
def _handle_instagram(self, data: Dict[str, Any]) -> Dict[str, Any]:
"""Handle Instagram content generation."""
try:
# Generate Instagram-specific content
caption = metadesc_generator_main(data)
hashtags = self._generate_hashtags(data)
return {
'platform': 'instagram',
'content': {
'caption': caption,
'hashtags': hashtags,
'media_suggestions': self._get_media_suggestions(data)
}
}
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:
# Generate LinkedIn-specific content
post = metadesc_generator_main(data)
return {
'platform': 'linkedin',
'content': {
'post': post,
'engagement_optimization': self._get_engagement_suggestions(data),
'media_suggestions': self._get_media_suggestions(data)
}
}
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:
# Generate Twitter-specific content
tweet = metadesc_generator_main(data)
hashtags = self._generate_hashtags(data)
return {
'platform': 'twitter',
'content': {
'tweet': tweet,
'hashtags': hashtags,
'thread_structure': self._get_thread_structure(data),
'media_suggestions': self._get_media_suggestions(data)
}
}
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:
# Generate Facebook-specific content
post = metadesc_generator_main(data)
return {
'platform': 'facebook',
'content': {
'post': post,
'engagement_optimization': self._get_engagement_suggestions(data),
'media_suggestions': self._get_media_suggestions(data)
}
}
except Exception as e:
logger.error(f"Error generating Facebook content: {str(e)}")
return {
'platform': 'facebook',
'error': str(e)
}
def _generate_hashtags(self, data: Dict[str, Any]) -> List[str]:
"""Generate relevant hashtags for content."""
try:
# Extract keywords from content
keywords = data.get('keywords', [])
# Add platform-specific hashtags
platform = data.get('platform', '').lower()
platform_hashtags = {
'instagram': ['#instagood', '#photooftheday'],
'twitter': ['#trending', '#followme'],
'linkedin': ['#business', '#professional'],
'facebook': ['#social', '#community']
}.get(platform, [])
return keywords + platform_hashtags
except Exception as e:
logger.error(f"Error generating hashtags: {str(e)}")
return []
def _get_media_suggestions(self, data: Dict[str, Any]) -> List[Dict[str, Any]]:
"""Get media suggestions for content."""
try:
# Generate media suggestions based on content type
content_type = data.get('type', 'post')
suggestions = []
if content_type == 'blog':
suggestions.append({
'type': 'featured_image',
'description': 'Main blog post image',
'dimensions': '1200x630'
})
elif content_type == 'social':
suggestions.append({
'type': 'post_image',
'description': 'Social media post image',
'dimensions': '1080x1080'
})
return suggestions
except Exception as e:
logger.error(f"Error getting media suggestions: {str(e)}")
return []
def _get_engagement_suggestions(self, data: Dict[str, Any]) -> Dict[str, Any]:
"""Get engagement optimization suggestions."""
try:
return {
'best_posting_times': ['9:00 AM', '5:00 PM'],
'engagement_tips': [
'Ask questions to encourage comments',
'Use relevant hashtags',
'Include a clear call-to-action'
],
'content_length': {
'optimal': '150-200 characters',
'maximum': '300 characters'
}
}
except Exception as e:
logger.error(f"Error getting engagement suggestions: {str(e)}")
return {}
def _get_thread_structure(self, data: Dict[str, Any]) -> List[Dict[str, Any]]:
"""Get thread structure for Twitter threads."""
try:
content = data.get('content', '')
sentences = content.split('.')
thread = []
current_tweet = ''
for sentence in sentences:
if len(current_tweet + sentence) <= 280:
current_tweet += sentence + '.'
else:
if current_tweet:
thread.append({
'content': current_tweet.strip(),
'type': 'tweet'
})
current_tweet = sentence + '.'
if current_tweet:
thread.append({
'content': current_tweet.strip(),
'type': 'tweet'
})
return thread
except Exception as e:
logger.error(f"Error generating thread structure: {str(e)}")
return []

View File

@@ -1,327 +0,0 @@
"""
Wix platform adapter implementation.
"""
from io import BytesIO
from typing import Dict, Any, Optional, List
from datetime import datetime
import logging
from pathlib import Path
import requests
from .base import PlatformAdapter
from lib.integrations.wix.wix_api_client import WixAPIClient
logger = logging.getLogger(__name__)
class WixAdapter(PlatformAdapter):
"""Wix platform adapter."""
def __init__(self, config: Dict[str, Any]):
"""Initialize Wix adapter with configuration."""
super().__init__(config)
self._validate_config()
self._initialize_client()
def _initialize_client(self) -> None:
"""Initialize Wix API client."""
try:
self.client = WixAPIClient(
api_key=self.config.get('api_key'),
refresh_token=self.config.get('refresh_token'),
site_id=self.config.get('site_id')
)
logger.info("Successfully initialized Wix API client")
except Exception as e:
raise Exception(f"Failed to initialize Wix client: {str(e)}")
async def publish_content(
self,
content: Dict[str, Any],
schedule_time: Optional[datetime] = None
) -> Dict[str, Any]:
"""Publish content to Wix blog."""
try:
# Validate content
validation = await self.validate_content(content)
if not validation.get('success'):
return validation
# Prepare blog post data
post_data = {
'title': content.get('title', ''),
'content': content.get('content', ''),
'excerpt': content.get('excerpt', ''),
'slug': content.get('slug', ''),
'tags': content.get('tags', []),
'categories': content.get('categories', []),
'seo': content.get('seo', {}),
'publish_date': schedule_time.isoformat() if schedule_time else None
}
# Handle media attachments
media_ids = []
if 'media' in content:
for media in content['media']:
media_id = await self._upload_media(media)
if media_id:
media_ids.append(media_id)
# Create blog post
post = self.client.create_post(post_data)
# Add media to post if any
if media_ids:
self.client.add_media_to_post(post['id'], media_ids)
return self._format_success_response({
'id': post['id'],
'title': post['title'],
'url': post['url'],
'created_at': post['created_at']
})
except Exception as e:
return self._format_error_response(
e,
{'content': content, 'schedule_time': schedule_time}
)
async def get_content_status(
self,
content_id: str
) -> Dict[str, Any]:
"""Get status of a blog post."""
try:
post = self.client.get_post(content_id)
return self._format_success_response({
'id': post['id'],
'title': post['title'],
'status': post['status'],
'url': post['url'],
'created_at': post['created_at'],
'updated_at': post['updated_at'],
'published_at': post.get('published_at')
})
except Exception as e:
return self._format_error_response(
e,
{'content_id': content_id}
)
async def delete_content(
self,
content_id: str
) -> Dict[str, Any]:
"""Delete a blog post."""
try:
self.client.delete_post(content_id)
return self._format_success_response({
'id': content_id,
'deleted': True
})
except Exception as e:
return self._format_error_response(
e,
{'content_id': content_id}
)
async def update_content(
self,
content_id: str,
updates: Dict[str, Any]
) -> Dict[str, Any]:
"""Update a blog post."""
try:
post = self.client.update_post(content_id, updates)
return self._format_success_response({
'id': post['id'],
'title': post['title'],
'url': post['url'],
'updated_at': post['updated_at']
})
except Exception as e:
return self._format_error_response(
e,
{
'content_id': content_id,
'updates': updates
}
)
async def get_analytics(
self,
content_id: str,
start_date: Optional[datetime] = None,
end_date: Optional[datetime] = None
) -> Dict[str, Any]:
"""Get analytics for a blog post."""
try:
analytics = self.client.get_post_analytics(
content_id,
start_date,
end_date
)
return self._format_success_response({
'id': content_id,
'metrics': {
'views': analytics.get('views', 0),
'unique_visitors': analytics.get('unique_visitors', 0),
'average_time_on_page': analytics.get('average_time_on_page', 0),
'bounce_rate': analytics.get('bounce_rate', 0)
}
})
except Exception as e:
return self._format_error_response(
e,
{
'content_id': content_id,
'start_date': start_date,
'end_date': end_date
}
)
async def validate_content(
self,
content: Dict[str, Any]
) -> Dict[str, Any]:
"""Validate content before publishing."""
try:
# Check required fields
required_fields = ['title', 'content']
missing_fields = [
field for field in required_fields
if field not in content
]
if missing_fields:
return self._format_error_response(
ValueError(f"Missing required fields: {', '.join(missing_fields)}"),
{'content': content}
)
# Check content length
if len(content['content']) > 100000: # Wix limit
return self._format_error_response(
ValueError("Content exceeds maximum length of 100,000 characters"),
{'content': content}
)
# Check media attachments
media = content.get('media', [])
if len(media) > 20: # Wix limit
return self._format_error_response(
ValueError("Maximum 20 media attachments allowed"),
{'content': content}
)
return self._format_success_response({
'valid': True,
'content': content
})
except Exception as e:
return self._format_error_response(
e,
{'content': content}
)
async def get_optimal_publish_time(
self,
content_type: str,
target_audience: Optional[Dict[str, Any]] = None
) -> datetime:
"""Get optimal publish time for content."""
# Implement optimal time calculation based on:
# - Content type
# - Target audience timezone
# - Historical engagement data
# For now, return current time
return datetime.now()
async def get_platform_limits(
self
) -> Dict[str, Any]:
"""Get Wix platform limits."""
return self._format_success_response({
'content_length': 100000,
'media_attachments': 20,
'tags_per_post': 50,
'categories_per_post': 10,
'rate_limits': {
'posts_per_day': 100,
'media_uploads_per_day': 1000
}
})
async def get_supported_content_types(
self
) -> List[str]:
"""Get list of supported content types."""
return ['BLOG_POST', 'PAGE', 'COLLECTION_ITEM']
async def get_platform_metrics(
self
) -> Dict[str, Any]:
"""Get Wix platform metrics."""
try:
site_stats = self.client.get_site_statistics()
return self._format_success_response({
'total_posts': site_stats.get('total_posts', 0),
'total_views': site_stats.get('total_views', 0),
'total_comments': site_stats.get('total_comments', 0),
'average_engagement': site_stats.get('average_engagement', 0)
})
except Exception as e:
return self._format_error_response(e)
async def _upload_media(
self,
media: Dict[str, Any]
) -> Optional[str]:
"""Upload media to Wix."""
try:
if 'url' in media:
# Download media from URL
response = requests.get(media['url'])
media_file = BytesIO(response.content)
filename = media.get('filename', 'media')
elif 'file' in media:
# Use local file
file_path = Path(media['file'])
media_file = open(file_path, 'rb')
filename = file_path.name
else:
return None
# Upload media
media_id = self.client.upload_media(
file=media_file,
filename=filename,
mime_type=media.get('mime_type')
)
return media_id
except Exception as e:
logger.error(f"Failed to upload media: {str(e)}")
return None
@classmethod
def get_required_config_fields(cls) -> List[str]:
"""Get list of required configuration fields."""
return [
'api_key',
'refresh_token',
'site_id'
]
@classmethod
def get_platform_description(cls) -> str:
"""Get platform description."""
return "Wix platform adapter for managing blog posts and content"
@classmethod
def get_platform_version(cls) -> str:
"""Get platform adapter version."""
return "1.0.0"

View File

@@ -1,337 +0,0 @@
"""
Twitter Authentication Bridge
Connects the platform adapter with the UI authentication system for secure Twitter integration.
"""
import streamlit as st
import tweepy
import json
import os
from typing import Dict, Any, Optional, Tuple
from datetime import datetime, timedelta
from pathlib import Path
import hashlib
import base64
from cryptography.fernet import Fernet
import logging
from .platform_adapters.twitter import TwitterAdapter
logger = logging.getLogger(__name__)
class TwitterAuthBridge:
"""Bridge between Twitter authentication and platform adapter."""
def __init__(self):
self.config_dir = Path("config/twitter")
self.config_dir.mkdir(parents=True, exist_ok=True)
self.encryption_key = self._get_or_create_encryption_key()
def _get_or_create_encryption_key(self) -> bytes:
"""Get or create encryption key for secure credential storage."""
key_file = self.config_dir / "encryption.key"
if key_file.exists():
with open(key_file, 'rb') as f:
return f.read()
else:
key = Fernet.generate_key()
with open(key_file, 'wb') as f:
f.write(key)
return key
def encrypt_credentials(self, credentials: Dict[str, str]) -> str:
"""Encrypt Twitter credentials for secure storage."""
try:
fernet = Fernet(self.encryption_key)
credentials_json = json.dumps(credentials)
encrypted_data = fernet.encrypt(credentials_json.encode())
return base64.b64encode(encrypted_data).decode()
except Exception as e:
logger.error(f"Failed to encrypt credentials: {str(e)}")
raise
def decrypt_credentials(self, encrypted_data: str) -> Dict[str, str]:
"""Decrypt Twitter credentials from secure storage."""
try:
fernet = Fernet(self.encryption_key)
encrypted_bytes = base64.b64decode(encrypted_data.encode())
decrypted_data = fernet.decrypt(encrypted_bytes)
return json.loads(decrypted_data.decode())
except Exception as e:
logger.error(f"Failed to decrypt credentials: {str(e)}")
raise
def save_credentials(self, user_id: str, credentials: Dict[str, str]) -> bool:
"""Save encrypted Twitter credentials to file."""
try:
# Create user-specific credentials file
user_hash = hashlib.sha256(user_id.encode()).hexdigest()[:16]
creds_file = self.config_dir / f"user_{user_hash}.enc"
# Add timestamp and validation
credentials_with_meta = {
**credentials,
'created_at': datetime.now().isoformat(),
'user_id_hash': user_hash
}
# Encrypt and save
encrypted_data = self.encrypt_credentials(credentials_with_meta)
with open(creds_file, 'w') as f:
f.write(encrypted_data)
logger.info(f"Credentials saved for user {user_hash}")
return True
except Exception as e:
logger.error(f"Failed to save credentials: {str(e)}")
return False
def load_credentials(self, user_id: str) -> Optional[Dict[str, str]]:
"""Load and decrypt Twitter credentials from file."""
try:
user_hash = hashlib.sha256(user_id.encode()).hexdigest()[:16]
creds_file = self.config_dir / f"user_{user_hash}.enc"
if not creds_file.exists():
logger.warning(f"No credentials found for user {user_hash}")
return None
# Load and decrypt
with open(creds_file, 'r') as f:
encrypted_data = f.read()
credentials = self.decrypt_credentials(encrypted_data)
# Validate credentials are not expired (optional)
created_at = datetime.fromisoformat(credentials.get('created_at', ''))
if datetime.now() - created_at > timedelta(days=365): # 1 year expiry
logger.warning(f"Credentials expired for user {user_hash}")
return None
# Remove metadata before returning
clean_credentials = {k: v for k, v in credentials.items()
if k not in ['created_at', 'user_id_hash']}
return clean_credentials
except Exception as e:
logger.error(f"Failed to load credentials: {str(e)}")
return None
def delete_credentials(self, user_id: str) -> bool:
"""Delete stored Twitter credentials."""
try:
user_hash = hashlib.sha256(user_id.encode()).hexdigest()[:16]
creds_file = self.config_dir / f"user_{user_hash}.enc"
if creds_file.exists():
creds_file.unlink()
logger.info(f"Credentials deleted for user {user_hash}")
return True
except Exception as e:
logger.error(f"Failed to delete credentials: {str(e)}")
return False
def validate_credentials(self, credentials: Dict[str, str]) -> Tuple[bool, str]:
"""Validate Twitter API credentials."""
try:
# Check required fields
required_fields = ['api_key', 'api_secret', 'access_token', 'access_token_secret']
missing_fields = [field for field in required_fields if not credentials.get(field)]
if missing_fields:
return False, f"Missing required fields: {', '.join(missing_fields)}"
# Test connection
auth = tweepy.OAuthHandler(
credentials['api_key'],
credentials['api_secret']
)
auth.set_access_token(
credentials['access_token'],
credentials['access_token_secret']
)
api = tweepy.API(auth)
user = api.verify_credentials()
if user:
return True, f"Valid credentials for @{user.screen_name}"
else:
return False, "Failed to verify credentials"
except tweepy.Unauthorized:
return False, "Invalid API credentials"
except tweepy.Forbidden:
return False, "Access forbidden - check API permissions"
except tweepy.TooManyRequests:
return False, "Rate limit exceeded - try again later"
except Exception as e:
return False, f"Connection error: {str(e)}"
def get_twitter_adapter(self, user_id: str) -> Optional[TwitterAdapter]:
"""Get configured Twitter adapter for user."""
try:
# First check session state
if 'twitter_adapter' in st.session_state:
return st.session_state.twitter_adapter
# Load credentials
credentials = self.load_credentials(user_id)
if not credentials:
return None
# Validate credentials
is_valid, message = self.validate_credentials(credentials)
if not is_valid:
logger.error(f"Invalid credentials: {message}")
return None
# Create adapter
adapter = TwitterAdapter(credentials)
# Cache in session state
st.session_state.twitter_adapter = adapter
return adapter
except Exception as e:
logger.error(f"Failed to get Twitter adapter: {str(e)}")
return None
def get_user_info(self, user_id: str) -> Optional[Dict[str, Any]]:
"""Get Twitter user information."""
try:
adapter = self.get_twitter_adapter(user_id)
if not adapter:
return None
# Get user info from Twitter
user = adapter.client.verify_credentials()
user_info = {
'id': user.id_str,
'screen_name': user.screen_name,
'name': user.name,
'description': user.description,
'followers_count': user.followers_count,
'friends_count': user.friends_count,
'statuses_count': user.statuses_count,
'profile_image_url': user.profile_image_url_https,
'profile_banner_url': getattr(user, 'profile_banner_url', ''),
'verified': user.verified,
'created_at': user.created_at.isoformat(),
'location': user.location or '',
'url': user.url or ''
}
return user_info
except Exception as e:
logger.error(f"Failed to get user info: {str(e)}")
return None
def setup_session_state(self, user_id: str) -> bool:
"""Setup session state with Twitter authentication."""
try:
# Load credentials
credentials = self.load_credentials(user_id)
if not credentials:
return False
# Get user info
user_info = self.get_user_info(user_id)
if not user_info:
return False
# Setup session state
st.session_state.twitter_authenticated = True
st.session_state.twitter_user_id = user_id
st.session_state.twitter_user_info = user_info
st.session_state.twitter_config = credentials
return True
except Exception as e:
logger.error(f"Failed to setup session state: {str(e)}")
return False
def clear_session_state(self) -> None:
"""Clear Twitter authentication from session state."""
keys_to_clear = [
'twitter_authenticated',
'twitter_user_id',
'twitter_user_info',
'twitter_config',
'twitter_adapter'
]
for key in keys_to_clear:
if key in st.session_state:
del st.session_state[key]
def is_authenticated(self) -> bool:
"""Check if user is authenticated with Twitter."""
return (
st.session_state.get('twitter_authenticated', False) and
st.session_state.get('twitter_user_info') is not None and
st.session_state.get('twitter_config') is not None
)
def get_rate_limit_status(self, user_id: str) -> Optional[Dict[str, Any]]:
"""Get current rate limit status."""
try:
adapter = self.get_twitter_adapter(user_id)
if not adapter:
return None
rate_limits = adapter.client.get_rate_limit_status()
# Extract relevant rate limits
relevant_limits = {
'tweets': rate_limits['resources']['statuses']['/statuses/update'],
'user_timeline': rate_limits['resources']['statuses']['/statuses/user_timeline'],
'verify_credentials': rate_limits['resources']['account']['/account/verify_credentials']
}
return relevant_limits
except Exception as e:
logger.error(f"Failed to get rate limit status: {str(e)}")
return None
# Global instance
twitter_auth = TwitterAuthBridge()
# Convenience functions for UI
def save_twitter_credentials(user_id: str, credentials: Dict[str, str]) -> bool:
"""Save Twitter credentials (convenience function)."""
return twitter_auth.save_credentials(user_id, credentials)
def load_twitter_credentials(user_id: str) -> Optional[Dict[str, str]]:
"""Load Twitter credentials (convenience function)."""
return twitter_auth.load_credentials(user_id)
def get_twitter_adapter(user_id: str) -> Optional[TwitterAdapter]:
"""Get Twitter adapter (convenience function)."""
return twitter_auth.get_twitter_adapter(user_id)
def is_twitter_authenticated() -> bool:
"""Check if Twitter is authenticated (convenience function)."""
return twitter_auth.is_authenticated()
def setup_twitter_session(user_id: str) -> bool:
"""Setup Twitter session (convenience function)."""
return twitter_auth.setup_session_state(user_id)
def clear_twitter_session() -> None:
"""Clear Twitter session (convenience function)."""
twitter_auth.clear_session_state()
def validate_twitter_credentials(credentials: Dict[str, str]) -> Tuple[bool, str]:
"""Validate Twitter credentials (convenience function)."""
return twitter_auth.validate_credentials(credentials)

View File

@@ -1,208 +0,0 @@
# Wix Blog Integration for Alwrity
This integration allows you to publish blog content from Alwrity directly to your Wix site using the Wix REST API.
## Features
- **Blog Post Management**: Create, update, and delete blog posts
- **Media Management**: Upload images and other media files
- **SEO Optimization**: Comprehensive SEO settings and analysis
- **Category Management**: Create and manage blog categories
- **Markdown Support**: Write in markdown and publish as HTML
- **Streamlit UI**: User-friendly interface for publishing
## Prerequisites
Before using this integration, you'll need:
1. A Wix site with the Blog feature enabled
2. Wix API credentials (refresh token and site ID)
3. Python 3.7+ with required dependencies
## Getting Wix API Credentials
To use this integration, you need to obtain a refresh token and site ID from Wix:
1. **Create a Wix Developer Account**:
- Go to [Wix Developers](https://dev.wix.com/) and sign up or log in
- Create a new OAuth app
2. **Configure OAuth App**:
- Set a name and description for your app
- Add redirect URLs (e.g., `https://localhost:3000/oauth/callback`)
- Save the app and note the App ID and App Secret
3. **Get a Refresh Token**:
- Follow the OAuth flow to get an authorization code
- Exchange the code for an access token and refresh token
- Detailed instructions: [Wix OAuth Documentation](https://dev.wix.com/api/rest/getting-started/authentication)
4. **Get Your Site ID**:
- Log in to your Wix account
- Go to your site's dashboard
- The site ID is in the URL: `https://manage.wix.com/dashboard/{SITE_ID}/home`
## Installation
The Wix integration is included with Alwrity. No additional installation is required.
## Usage
### Using the Streamlit UI
1. Navigate to the Wix integration in the Alwrity UI
2. Enter your Wix refresh token and site ID
3. Fill in the blog details and content
4. Click "Publish to Wix"
### Using the Python API
```python
from lib.integrations.wix_integration import WixIntegration
# Initialize the integration
wix = WixIntegration(
refresh_token="YOUR_REFRESH_TOKEN",
site_id="YOUR_SITE_ID"
)
# Publish a blog post
result = wix.publish_blog_post(
title="My Blog Post",
content="# Hello World\n\nThis is my blog post.",
is_markdown=True,
tags=["example", "blog"],
categories=["Technology"],
publish=True
)
# Get the published post URL
post_url = result.get("post", {}).get("url")
print(f"Published at: {post_url}")
```
### Using the Command-Line Interface
```bash
# Set environment variables
export WIX_REFRESH_TOKEN="YOUR_REFRESH_TOKEN"
export WIX_SITE_ID="YOUR_SITE_ID"
# List blog posts
python -m lib.integrations.wix_cli list-posts
# Publish a blog post
python -m lib.integrations.wix_cli publish-post \
--title "My Blog Post" \
--content-file blog.md \
--is-markdown \
--tags "example,blog" \
--categories "Technology"
# Generate an SEO report
python -m lib.integrations.wix_cli seo-report \
--title "My Blog Post" \
--keywords "example,blog,technology"
```
## API Reference
### WixIntegration
The main integration class that provides high-level methods for working with Wix blogs.
#### Methods
- `publish_blog_post(title, content, ...)`: Publish a blog post
- `upload_media(file_path, ...)`: Upload a media file
- `get_seo_report(post_id, target_keywords)`: Generate an SEO report
- `list_blog_posts(limit, offset, ...)`: List blog posts
- `list_categories()`: List blog categories
- `create_category(name, description)`: Create a blog category
- `get_post_by_id(post_id)`: Get a blog post by ID
- `get_post_by_title(title)`: Get a blog post by title
- `delete_post(post_id)`: Delete a blog post
### WixAPIClient
Low-level client for interacting with the Wix API.
### WixBlogManager
Handles blog content management, including markdown processing and image handling.
### WixSEOOptimizer
Provides SEO analysis and optimization for blog posts.
## Error Handling
The integration includes comprehensive error handling:
- API errors are logged with detailed information
- Authentication errors provide clear guidance
- File handling errors include path information
- Network errors include retry logic
## Best Practices
1. **Store credentials securely**:
- Use environment variables or a secure credential store
- Don't hardcode credentials in your code
2. **Optimize images before upload**:
- Compress images to reduce file size
- Use appropriate image formats (JPEG for photos, PNG for graphics)
3. **SEO optimization**:
- Use the SEO report to improve your content
- Include relevant keywords in titles and headings
- Add alt text to all images
4. **Content management**:
- Use categories and tags consistently
- Include featured images for better visual appeal
- Write clear, concise meta descriptions
## Troubleshooting
### Common Issues
1. **Authentication Errors**:
- Ensure your refresh token is valid
- Check that your site ID is correct
- Verify that your app has the necessary permissions
2. **API Rate Limits**:
- The Wix API has rate limits that may affect bulk operations
- Add delays between requests if you're publishing many posts
3. **Image Upload Issues**:
- Check that the image file exists and is readable
- Verify that the image format is supported (JPEG, PNG, GIF)
- Ensure the image file size is within Wix limits
4. **Content Formatting Issues**:
- If using markdown, ensure it's valid
- Check for special characters that might cause issues
- Verify that HTML content is properly formatted
### Getting Help
If you encounter issues not covered here:
1. Check the logs for detailed error messages
2. Consult the [Wix API Documentation](https://dev.wix.com/api/rest/getting-started)
3. Contact Alwrity support for assistance
## License
This integration is part of the Alwrity platform and is subject to the same license terms.
## Acknowledgements
- [Wix REST API](https://dev.wix.com/api/rest) for providing the API endpoints
- [Requests](https://docs.python-requests.org/) for HTTP functionality
- [Markdown](https://python-markdown.github.io/) for markdown processing
- [BeautifulSoup](https://www.crummy.com/software/BeautifulSoup/) for HTML parsing
- [Streamlit](https://streamlit.io/) for the user interface

View File

@@ -1,841 +0,0 @@
"""
Wix API Client for Blog Management
This module provides a comprehensive client for interacting with the Wix API
to manage blog posts, SEO settings, and media uploads.
Documentation: https://dev.wix.com/api/rest/getting-started
"""
import os
import json
import time
import logging
import requests
from typing import Dict, List, Optional, Union, Any, Tuple
from datetime import datetime
import mimetypes
from pathlib import Path
from io import BytesIO
# Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger('wix_api_client')
class WixAPIClient:
"""
Client for interacting with the Wix API for blog management.
This client handles authentication, blog post creation/updating,
media uploads, and SEO settings.
"""
# Base URLs for different Wix API endpoints
BASE_URL = "https://www.wixapis.com"
OAUTH_URL = "https://www.wix.com/oauth"
# API Endpoints
BLOG_API = "/blog/v3"
MEDIA_API = "/site-media/v1"
SEO_API = "/site-properties/v4/seo"
def __init__(
self,
api_key: Optional[str] = None,
refresh_token: Optional[str] = None,
site_id: Optional[str] = None
):
"""
Initialize the Wix API Client.
Args:
api_key: Wix API key (optional if using refresh token)
refresh_token: Wix refresh token for OAuth authentication
site_id: Wix site ID
"""
self.api_key = api_key or os.environ.get('WIX_API_KEY')
self.refresh_token = refresh_token or os.environ.get('WIX_REFRESH_TOKEN')
self.site_id = site_id or os.environ.get('WIX_SITE_ID')
self.access_token = None
self.token_expiry = 0
if not self.refresh_token:
logger.warning("No refresh token provided. Authentication will fail.")
if not self.site_id:
logger.warning("No site ID provided. API calls will fail.")
def _get_headers(self) -> Dict[str, str]:
"""
Get the headers required for API requests.
Returns:
Dict containing the necessary headers for Wix API requests
"""
# Ensure we have a valid access token
self._ensure_valid_token()
headers = {
"Authorization": f"Bearer {self.access_token}",
"wix-site-id": self.site_id,
"Content-Type": "application/json"
}
return headers
def _ensure_valid_token(self) -> None:
"""
Ensure we have a valid access token, refreshing if necessary.
"""
current_time = time.time()
# If token is expired or doesn't exist, refresh it
if not self.access_token or current_time >= self.token_expiry:
self._refresh_access_token()
def _refresh_access_token(self) -> None:
"""
Refresh the access token using the refresh token.
"""
if not self.refresh_token:
raise ValueError("Refresh token is required for authentication")
url = f"{self.OAUTH_URL}/access"
payload = {
"grant_type": "refresh_token",
"refresh_token": self.refresh_token,
"client_id": self.api_key if self.api_key else ""
}
try:
response = requests.post(url, json=payload)
response.raise_for_status()
data = response.json()
self.access_token = data.get("access_token")
# Set token expiry (subtract 5 minutes for safety margin)
expires_in = data.get("expires_in", 3600) # Default to 1 hour if not specified
self.token_expiry = time.time() + expires_in - 300
logger.info("Successfully refreshed access token")
except requests.exceptions.RequestException as e:
logger.error(f"Failed to refresh access token: {str(e)}")
if response.text:
logger.error(f"Response: {response.text}")
raise
def _make_request(
self,
method: str,
endpoint: str,
data: Optional[Dict] = None,
params: Optional[Dict] = None,
files: Optional[Dict] = None
) -> Dict:
"""
Make a request to the Wix API.
Args:
method: HTTP method (GET, POST, PUT, DELETE)
endpoint: API endpoint
data: Request payload
params: Query parameters
files: Files to upload
Returns:
Response data as dictionary
"""
url = f"{self.BASE_URL}{endpoint}"
headers = self._get_headers()
# If we're uploading files, remove the Content-Type header
if files:
headers.pop("Content-Type", None)
try:
response = requests.request(
method=method,
url=url,
headers=headers,
json=data,
params=params,
files=files
)
# Log request details for debugging
logger.debug(f"Request: {method} {url}")
logger.debug(f"Headers: {headers}")
if data:
logger.debug(f"Data: {json.dumps(data)}")
if params:
logger.debug(f"Params: {params}")
# Handle response
response.raise_for_status()
if response.content:
return response.json()
return {}
except requests.exceptions.HTTPError as e:
logger.error(f"HTTP error: {str(e)}")
if response.text:
logger.error(f"Response: {response.text}")
raise
except requests.exceptions.RequestException as e:
logger.error(f"Request error: {str(e)}")
raise
def list_posts(
self,
limit: int = 50,
offset: int = 0,
sort_field: str = "lastPublishedDate",
sort_order: str = "desc",
filter_by: Optional[Dict] = None
) -> Dict:
"""
List blog posts with pagination and sorting.
Args:
limit: Maximum number of posts to return (default: 50)
offset: Pagination offset (default: 0)
sort_field: Field to sort by (default: lastPublishedDate)
sort_order: Sort order, 'asc' or 'desc' (default: desc)
filter_by: Optional filter criteria
Returns:
Dictionary containing blog posts and pagination info
"""
endpoint = f"{self.BLOG_API}/posts/query"
payload = {
"limit": limit,
"offset": offset,
"sort": [
{
"fieldName": sort_field,
"order": sort_order
}
]
}
if filter_by:
payload["filter"] = filter_by
return self._make_request("POST", endpoint, data=payload)
def get_post(self, post_id: str) -> Dict:
"""
Get a specific blog post by ID.
Args:
post_id: ID of the blog post
Returns:
Blog post data
"""
endpoint = f"{self.BLOG_API}/posts/{post_id}"
return self._make_request("GET", endpoint)
def create_post(
self,
title: str,
content: str,
excerpt: Optional[str] = None,
featured_image_id: Optional[str] = None,
tags: Optional[List[str]] = None,
categories: Optional[List[str]] = None,
seo_data: Optional[Dict] = None,
publish: bool = False
) -> Dict:
"""
Create a new blog post.
Args:
title: Post title
content: Post content (HTML)
excerpt: Post excerpt/summary
featured_image_id: ID of the featured image (from media manager)
tags: List of tags
categories: List of category IDs
seo_data: SEO settings for the post
publish: Whether to publish the post immediately
Returns:
Created blog post data
"""
endpoint = f"{self.BLOG_API}/posts"
# Prepare the post data
post_data = {
"post": {
"title": title,
"content": content,
"excerpt": excerpt or "",
"featured_image_id": featured_image_id,
"tags": tags or [],
"categoryIds": categories or []
}
}
# Add SEO data if provided
if seo_data:
post_data["post"]["seoData"] = seo_data
# Create the post
response = self._make_request("POST", endpoint, data=post_data)
# Publish the post if requested
if publish and response.get("post", {}).get("id"):
post_id = response["post"]["id"]
self.publish_post(post_id)
# Refresh the post data to get the published version
response = self.get_post(post_id)
return response
def update_post(
self,
post_id: str,
title: Optional[str] = None,
content: Optional[str] = None,
excerpt: Optional[str] = None,
featured_image_id: Optional[str] = None,
tags: Optional[List[str]] = None,
categories: Optional[List[str]] = None,
seo_data: Optional[Dict] = None,
publish: bool = False
) -> Dict:
"""
Update an existing blog post.
Args:
post_id: ID of the post to update
title: New post title (optional)
content: New post content (HTML) (optional)
excerpt: New post excerpt/summary (optional)
featured_image_id: New featured image ID (optional)
tags: New list of tags (optional)
categories: New list of category IDs (optional)
seo_data: New SEO settings (optional)
publish: Whether to publish the post after updating
Returns:
Updated blog post data
"""
# First, get the current post data
current_post = self.get_post(post_id)
if "post" not in current_post:
raise ValueError(f"Post with ID {post_id} not found")
current_post_data = current_post["post"]
# Update only the fields that were provided
update_data = {
"post": {
"id": post_id,
"title": title if title is not None else current_post_data.get("title", ""),
"content": content if content is not None else current_post_data.get("content", ""),
"excerpt": excerpt if excerpt is not None else current_post_data.get("excerpt", ""),
"featured_image_id": featured_image_id if featured_image_id is not None else current_post_data.get("featuredImageId"),
"tags": tags if tags is not None else current_post_data.get("tags", []),
"categoryIds": categories if categories is not None else current_post_data.get("categoryIds", [])
}
}
# Add SEO data if provided
if seo_data:
update_data["post"]["seoData"] = seo_data
elif "seoData" in current_post_data:
update_data["post"]["seoData"] = current_post_data["seoData"]
# Update the post
endpoint = f"{self.BLOG_API}/posts/{post_id}"
response = self._make_request("PATCH", endpoint, data=update_data)
# Publish the post if requested
if publish:
self.publish_post(post_id)
# Refresh the post data to get the published version
response = self.get_post(post_id)
return response
def delete_post(self, post_id: str) -> Dict:
"""
Delete a blog post.
Args:
post_id: ID of the post to delete
Returns:
Response data
"""
endpoint = f"{self.BLOG_API}/posts/{post_id}"
return self._make_request("DELETE", endpoint)
def publish_post(self, post_id: str) -> Dict:
"""
Publish a draft blog post.
Args:
post_id: ID of the post to publish
Returns:
Published post data
"""
endpoint = f"{self.BLOG_API}/posts/{post_id}/publish"
return self._make_request("POST", endpoint)
def unpublish_post(self, post_id: str) -> Dict:
"""
Unpublish a published blog post (revert to draft).
Args:
post_id: ID of the post to unpublish
Returns:
Unpublished post data
"""
endpoint = f"{self.BLOG_API}/posts/{post_id}/unpublish"
return self._make_request("POST", endpoint)
def list_categories(self) -> Dict:
"""
List all blog categories.
Returns:
Dictionary containing blog categories
"""
endpoint = f"{self.BLOG_API}/categories"
return self._make_request("GET", endpoint)
def create_category(self, label: str, description: Optional[str] = None) -> Dict:
"""
Create a new blog category.
Args:
label: Category name
description: Category description (optional)
Returns:
Created category data
"""
endpoint = f"{self.BLOG_API}/categories"
payload = {
"category": {
"label": label,
"description": description or ""
}
}
return self._make_request("POST", endpoint, data=payload)
def update_category(
self,
category_id: str,
label: Optional[str] = None,
description: Optional[str] = None
) -> Dict:
"""
Update an existing blog category.
Args:
category_id: ID of the category to update
label: New category name (optional)
description: New category description (optional)
Returns:
Updated category data
"""
# First, get the current category data
current_categories = self.list_categories()
current_category = None
for category in current_categories.get("categories", []):
if category.get("id") == category_id:
current_category = category
break
if not current_category:
raise ValueError(f"Category with ID {category_id} not found")
# Update only the fields that were provided
update_data = {
"category": {
"id": category_id,
"label": label if label is not None else current_category.get("label", ""),
"description": description if description is not None else current_category.get("description", "")
}
}
endpoint = f"{self.BLOG_API}/categories/{category_id}"
return self._make_request("PATCH", endpoint, data=update_data)
def delete_category(self, category_id: str) -> Dict:
"""
Delete a blog category.
Args:
category_id: ID of the category to delete
Returns:
Response data
"""
endpoint = f"{self.BLOG_API}/categories/{category_id}"
return self._make_request("DELETE", endpoint)
def upload_image(
self,
file_path: str,
title: Optional[str] = None,
alt_text: Optional[str] = None,
description: Optional[str] = None
) -> Dict:
"""
Upload an image to the Wix media manager.
Args:
file_path: Path to the image file
title: Image title (optional)
alt_text: Image alt text for accessibility (optional)
description: Image description (optional)
Returns:
Uploaded image data
"""
# Check if file exists
if not os.path.isfile(file_path):
raise FileNotFoundError(f"File not found: {file_path}")
# Get file name and mime type
file_name = os.path.basename(file_path)
mime_type, _ = mimetypes.guess_type(file_path)
if not mime_type or not mime_type.startswith('image/'):
raise ValueError(f"File does not appear to be an image: {file_path}")
# Prepare metadata
metadata = {
"title": title or file_name,
"altText": alt_text or "",
"description": description or ""
}
# First, get an upload URL
endpoint = f"{self.MEDIA_API}/files/upload/url"
upload_url_response = self._make_request("POST", endpoint, data={
"mimeType": mime_type,
"fileName": file_name
})
if "uploadUrl" not in upload_url_response:
raise ValueError("Failed to get upload URL")
upload_url = upload_url_response["uploadUrl"]
# Upload the file to the provided URL
with open(file_path, 'rb') as file:
upload_response = requests.post(
upload_url,
files={'file': (file_name, file, mime_type)},
headers={"Content-Type": mime_type}
)
upload_response.raise_for_status()
# Complete the upload with metadata
endpoint = f"{self.MEDIA_API}/files"
complete_data = {
"uploadToken": upload_url_response.get("uploadToken"),
"mediaOptions": {
"mimeType": mime_type,
"fileName": file_name,
"mediaType": "IMAGE",
"title": metadata["title"],
"description": metadata["description"],
"alt": metadata["altText"]
}
}
return self._make_request("POST", endpoint, data=complete_data)
def get_media_item(self, media_id: str) -> Dict:
"""
Get details of a specific media item.
Args:
media_id: ID of the media item
Returns:
Media item data
"""
endpoint = f"{self.MEDIA_API}/files/{media_id}"
return self._make_request("GET", endpoint)
def list_media_items(
self,
media_type: str = "IMAGE",
limit: int = 50,
offset: int = 0
) -> Dict:
"""
List media items with pagination.
Args:
media_type: Type of media to list (IMAGE, VIDEO, AUDIO, DOCUMENT)
limit: Maximum number of items to return
offset: Pagination offset
Returns:
Dictionary containing media items and pagination info
"""
endpoint = f"{self.MEDIA_API}/files/query"
payload = {
"query": {
"paging": {
"limit": limit,
"offset": offset
},
"filter": {
"mediaType": media_type
}
}
}
return self._make_request("POST", endpoint, data=payload)
def delete_media_item(self, media_id: str) -> Dict:
"""
Delete a media item.
Args:
media_id: ID of the media item to delete
Returns:
Response data
"""
endpoint = f"{self.MEDIA_API}/files/{media_id}"
return self._make_request("DELETE", endpoint)
def get_seo_settings(self, page_url: str) -> Dict:
"""
Get SEO settings for a specific page.
Args:
page_url: URL path of the page (e.g., "/blog/my-post")
Returns:
SEO settings data
"""
endpoint = f"{self.SEO_API}/sites/{self.site_id}/url/{page_url}"
return self._make_request("GET", endpoint)
def update_seo_settings(
self,
page_url: str,
title: Optional[str] = None,
description: Optional[str] = None,
keywords: Optional[List[str]] = None,
og_image_url: Optional[str] = None,
structured_data: Optional[Dict] = None,
no_index: Optional[bool] = None
) -> Dict:
"""
Update SEO settings for a specific page.
Args:
page_url: URL path of the page (e.g., "/blog/my-post")
title: SEO title
description: SEO description
keywords: SEO keywords
og_image_url: Open Graph image URL
structured_data: Structured data (JSON-LD)
no_index: Whether to prevent indexing by search engines
Returns:
Updated SEO settings data
"""
# First, get current SEO settings
try:
current_settings = self.get_seo_settings(page_url)
except:
# If the page doesn't exist yet, start with empty settings
current_settings = {"tags": {}}
# Prepare the update data
seo_data = {
"tags": {}
}
# Update only the fields that were provided
if title is not None:
seo_data["tags"]["title"] = title
elif "title" in current_settings.get("tags", {}):
seo_data["tags"]["title"] = current_settings["tags"]["title"]
if description is not None:
seo_data["tags"]["description"] = description
elif "description" in current_settings.get("tags", {}):
seo_data["tags"]["description"] = current_settings["tags"]["description"]
if keywords is not None:
seo_data["tags"]["keywords"] = ", ".join(keywords)
elif "keywords" in current_settings.get("tags", {}):
seo_data["tags"]["keywords"] = current_settings["tags"]["keywords"]
if og_image_url is not None:
seo_data["tags"]["og:image"] = og_image_url
elif "og:image" in current_settings.get("tags", {}):
seo_data["tags"]["og:image"] = current_settings["tags"]["og:image"]
if structured_data is not None:
seo_data["tags"]["jsonld"] = json.dumps(structured_data)
elif "jsonld" in current_settings.get("tags", {}):
seo_data["tags"]["jsonld"] = current_settings["tags"]["jsonld"]
if no_index is not None:
seo_data["tags"]["robots"] = "noindex" if no_index else "index"
elif "robots" in current_settings.get("tags", {}):
seo_data["tags"]["robots"] = current_settings["tags"]["robots"]
endpoint = f"{self.SEO_API}/sites/{self.site_id}/url/{page_url}"
return self._make_request("PUT", endpoint, data=seo_data)
def create_blog_post_with_image(
self,
title: str,
content: str,
image_path: Optional[str] = None,
excerpt: Optional[str] = None,
tags: Optional[List[str]] = None,
categories: Optional[List[str]] = None,
seo_title: Optional[str] = None,
seo_description: Optional[str] = None,
seo_keywords: Optional[List[str]] = None,
publish: bool = False
) -> Dict:
"""
Create a blog post with an optional featured image in one operation.
Args:
title: Post title
content: Post content (HTML)
image_path: Path to featured image (optional)
excerpt: Post excerpt/summary (optional)
tags: List of tags (optional)
categories: List of category IDs (optional)
seo_title: SEO title (optional)
seo_description: SEO description (optional)
seo_keywords: SEO keywords (optional)
publish: Whether to publish the post immediately (optional)
Returns:
Created blog post data
"""
# Upload image if provided
featured_image_id = None
if image_path and os.path.isfile(image_path):
try:
image_response = self.upload_image(
file_path=image_path,
title=title,
alt_text=title
)
featured_image_id = image_response.get("file", {}).get("id")
logger.info(f"Uploaded image with ID: {featured_image_id}")
except Exception as e:
logger.error(f"Failed to upload image: {str(e)}")
# Prepare SEO data
seo_data = None
if seo_title or seo_description or seo_keywords:
seo_data = {
"title": seo_title or title,
"description": seo_description or excerpt or "",
"keywords": seo_keywords or tags or []
}
# Create the blog post
return self.create_post(
title=title,
content=content,
excerpt=excerpt,
featured_image_id=featured_image_id,
tags=tags,
categories=categories,
seo_data=seo_data,
publish=publish
)
def get_or_create_category(self, category_name: str) -> str:
"""
Get a category ID by name, creating it if it doesn't exist.
Args:
category_name: Name of the category
Returns:
Category ID
"""
# List all categories
categories_response = self.list_categories()
categories = categories_response.get("categories", [])
# Check if category exists
for category in categories:
if category.get("label", "").lower() == category_name.lower():
return category.get("id")
# Create category if it doesn't exist
create_response = self.create_category(label=category_name)
return create_response.get("category", {}).get("id")
def get_post_by_slug(self, slug: str) -> Optional[Dict]:
"""
Find a post by its slug.
Args:
slug: Post slug
Returns:
Post data or None if not found
"""
# List posts with a filter for the slug
filter_by = {
"slug": {
"$eq": slug
}
}
response = self.list_posts(limit=1, filter_by=filter_by)
posts = response.get("posts", [])
if posts:
return posts[0]
return None
def get_post_url(self, post_id: str) -> str:
"""
Get the full URL for a blog post.
Args:
post_id: ID of the blog post
Returns:
Full URL to the blog post
"""
post_data = self.get_post(post_id)
slug = post_data.get("post", {}).get("slug", "")
# Get the blog URL prefix
# This is a simplification - in reality, you might need to get this from site settings
return f"/blog/{slug}"

View File

@@ -1,720 +0,0 @@
"""
Wix Blog Manager
This module provides high-level functions for managing blog content on Wix,
including content creation, SEO optimization, and media management.
"""
import os
import re
import logging
import tempfile
import requests
from typing import Dict, List, Optional, Union, Any, Tuple
from datetime import datetime
from pathlib import Path
import markdown
import html2text
from bs4 import BeautifulSoup
from .wix_api_client import WixAPIClient
# Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger('wix_blog_manager')
class WixBlogManager:
"""
High-level manager for Wix blog content.
This class provides convenient methods for common blog management tasks,
building on the lower-level WixAPIClient.
"""
def __init__(
self,
api_key: Optional[str] = None,
refresh_token: Optional[str] = None,
site_id: Optional[str] = None
):
"""
Initialize the Wix Blog Manager.
Args:
api_key: Wix API key (optional if using refresh token)
refresh_token: Wix refresh token for OAuth authentication
site_id: Wix site ID
"""
self.client = WixAPIClient(api_key, refresh_token, site_id)
def publish_markdown_post(
self,
title: str,
markdown_content: str,
featured_image_path: Optional[str] = None,
featured_image_url: Optional[str] = None,
excerpt: Optional[str] = None,
tags: Optional[List[str]] = None,
categories: Optional[List[str]] = None,
seo_title: Optional[str] = None,
seo_description: Optional[str] = None,
seo_keywords: Optional[List[str]] = None,
publish: bool = False
) -> Dict:
"""
Publish a blog post from markdown content.
Args:
title: Post title
markdown_content: Post content in markdown format
featured_image_path: Local path to featured image (optional)
featured_image_url: URL of featured image to download (optional)
excerpt: Post excerpt/summary (optional)
tags: List of tags (optional)
categories: List of category names (optional)
seo_title: SEO title (optional)
seo_description: SEO description (optional)
seo_keywords: SEO keywords (optional)
publish: Whether to publish the post immediately (optional)
Returns:
Published blog post data
"""
# Convert markdown to HTML
html_content = self._markdown_to_html(markdown_content)
# Process images in the content
html_content, embedded_images = self._process_content_images(html_content)
# Handle featured image
featured_image_id = None
temp_image_path = None
if featured_image_url and not featured_image_path:
# Download the image from URL
try:
temp_image_path = self._download_image(featured_image_url)
featured_image_path = temp_image_path
except Exception as e:
logger.error(f"Failed to download featured image: {str(e)}")
if featured_image_path:
try:
image_response = self.client.upload_image(
file_path=featured_image_path,
title=title,
alt_text=title
)
featured_image_id = image_response.get("file", {}).get("id")
logger.info(f"Uploaded featured image with ID: {featured_image_id}")
except Exception as e:
logger.error(f"Failed to upload featured image: {str(e)}")
# Clean up temporary file if created
if temp_image_path and os.path.exists(temp_image_path):
try:
os.remove(temp_image_path)
except:
pass
# Process categories - convert names to IDs
category_ids = []
if categories:
for category_name in categories:
try:
category_id = self.client.get_or_create_category(category_name)
if category_id:
category_ids.append(category_id)
except Exception as e:
logger.error(f"Failed to process category '{category_name}': {str(e)}")
# Generate excerpt if not provided
if not excerpt:
excerpt = self._generate_excerpt(markdown_content)
# Prepare SEO data
seo_data = None
if seo_title or seo_description or seo_keywords:
seo_data = {
"title": seo_title or title,
"description": seo_description or excerpt or "",
"keywords": seo_keywords or tags or []
}
# Create the blog post
response = self.client.create_post(
title=title,
content=html_content,
excerpt=excerpt,
featured_image_id=featured_image_id,
tags=tags,
categories=category_ids,
seo_data=seo_data,
publish=publish
)
# Update SEO settings if the post was published
if publish and response.get("post", {}).get("id"):
post_id = response["post"]["id"]
post_url = self.client.get_post_url(post_id)
try:
self.client.update_seo_settings(
page_url=post_url,
title=seo_title or title,
description=seo_description or excerpt or "",
keywords=seo_keywords or tags,
og_image_url=featured_image_url
)
except Exception as e:
logger.error(f"Failed to update SEO settings: {str(e)}")
return response
def update_markdown_post(
self,
post_id: str,
title: Optional[str] = None,
markdown_content: Optional[str] = None,
featured_image_path: Optional[str] = None,
featured_image_url: Optional[str] = None,
excerpt: Optional[str] = None,
tags: Optional[List[str]] = None,
categories: Optional[List[str]] = None,
seo_title: Optional[str] = None,
seo_description: Optional[str] = None,
seo_keywords: Optional[List[str]] = None,
publish: bool = False
) -> Dict:
"""
Update an existing blog post with markdown content.
Args:
post_id: ID of the post to update
title: New post title (optional)
markdown_content: New post content in markdown format (optional)
featured_image_path: Local path to new featured image (optional)
featured_image_url: URL of new featured image to download (optional)
excerpt: New post excerpt/summary (optional)
tags: New list of tags (optional)
categories: New list of category names (optional)
seo_title: New SEO title (optional)
seo_description: New SEO description (optional)
seo_keywords: New SEO keywords (optional)
publish: Whether to publish the post after updating (optional)
Returns:
Updated blog post data
"""
# Get current post data
current_post = self.client.get_post(post_id)
if "post" not in current_post:
raise ValueError(f"Post with ID {post_id} not found")
# Convert markdown to HTML if provided
html_content = None
if markdown_content:
html_content = self._markdown_to_html(markdown_content)
# Process images in the content
html_content, embedded_images = self._process_content_images(html_content)
# Handle featured image
featured_image_id = None
temp_image_path = None
if featured_image_url and not featured_image_path:
# Download the image from URL
try:
temp_image_path = self._download_image(featured_image_url)
featured_image_path = temp_image_path
except Exception as e:
logger.error(f"Failed to download featured image: {str(e)}")
if featured_image_path:
try:
image_response = self.client.upload_image(
file_path=featured_image_path,
title=title or current_post["post"].get("title", ""),
alt_text=title or current_post["post"].get("title", "")
)
featured_image_id = image_response.get("file", {}).get("id")
logger.info(f"Uploaded featured image with ID: {featured_image_id}")
except Exception as e:
logger.error(f"Failed to upload featured image: {str(e)}")
# Clean up temporary file if created
if temp_image_path and os.path.exists(temp_image_path):
try:
os.remove(temp_image_path)
except:
pass
# Process categories - convert names to IDs
category_ids = None
if categories:
category_ids = []
for category_name in categories:
try:
category_id = self.client.get_or_create_category(category_name)
if category_id:
category_ids.append(category_id)
except Exception as e:
logger.error(f"Failed to process category '{category_name}': {str(e)}")
# Generate excerpt if not provided but markdown is
if not excerpt and markdown_content:
excerpt = self._generate_excerpt(markdown_content)
# Prepare SEO data
seo_data = None
if seo_title or seo_description or seo_keywords:
seo_data = {
"title": seo_title or title or current_post["post"].get("title", ""),
"description": seo_description or excerpt or current_post["post"].get("excerpt", ""),
"keywords": seo_keywords or tags or current_post["post"].get("tags", [])
}
# Update the blog post
response = self.client.update_post(
post_id=post_id,
title=title,
content=html_content,
excerpt=excerpt,
featured_image_id=featured_image_id,
tags=tags,
categories=category_ids,
seo_data=seo_data,
publish=publish
)
# Update SEO settings if needed
if (seo_title or seo_description or seo_keywords or featured_image_url):
post_url = self.client.get_post_url(post_id)
try:
self.client.update_seo_settings(
page_url=post_url,
title=seo_title or title,
description=seo_description or excerpt,
keywords=seo_keywords or tags,
og_image_url=featured_image_url
)
except Exception as e:
logger.error(f"Failed to update SEO settings: {str(e)}")
return response
def find_post_by_title(self, title: str) -> Optional[Dict]:
"""
Find a post by its title (exact match).
Args:
title: Post title to search for
Returns:
Post data or None if not found
"""
# List all posts (this is inefficient but Wix API doesn't support filtering by title)
# In a production environment, you might want to implement pagination
response = self.client.list_posts(limit=100)
posts = response.get("posts", [])
for post in posts:
if post.get("title") == title:
return post
return None
def publish_or_update_markdown_post(
self,
title: str,
markdown_content: str,
featured_image_path: Optional[str] = None,
featured_image_url: Optional[str] = None,
excerpt: Optional[str] = None,
tags: Optional[List[str]] = None,
categories: Optional[List[str]] = None,
seo_title: Optional[str] = None,
seo_description: Optional[str] = None,
seo_keywords: Optional[List[str]] = None,
publish: bool = False,
update_if_exists: bool = True
) -> Dict:
"""
Publish a new post or update an existing one with the same title.
Args:
title: Post title
markdown_content: Post content in markdown format
featured_image_path: Local path to featured image (optional)
featured_image_url: URL of featured image to download (optional)
excerpt: Post excerpt/summary (optional)
tags: List of tags (optional)
categories: List of category names (optional)
seo_title: SEO title (optional)
seo_description: SEO description (optional)
seo_keywords: SEO keywords (optional)
publish: Whether to publish the post immediately (optional)
update_if_exists: Whether to update an existing post with the same title (optional)
Returns:
Published or updated blog post data
"""
# Check if a post with this title already exists
existing_post = self.find_post_by_title(title)
if existing_post and update_if_exists:
# Update existing post
logger.info(f"Updating existing post with title: {title}")
return self.update_markdown_post(
post_id=existing_post["id"],
title=title,
markdown_content=markdown_content,
featured_image_path=featured_image_path,
featured_image_url=featured_image_url,
excerpt=excerpt,
tags=tags,
categories=categories,
seo_title=seo_title,
seo_description=seo_description,
seo_keywords=seo_keywords,
publish=publish
)
else:
# Create new post
logger.info(f"Creating new post with title: {title}")
return self.publish_markdown_post(
title=title,
markdown_content=markdown_content,
featured_image_path=featured_image_path,
featured_image_url=featured_image_url,
excerpt=excerpt,
tags=tags,
categories=categories,
seo_title=seo_title,
seo_description=seo_description,
seo_keywords=seo_keywords,
publish=publish
)
def optimize_seo_for_post(
self,
post_id: str,
seo_title: Optional[str] = None,
seo_description: Optional[str] = None,
seo_keywords: Optional[List[str]] = None,
og_image_url: Optional[str] = None,
structured_data: Optional[Dict] = None
) -> Dict:
"""
Optimize SEO settings for an existing blog post.
Args:
post_id: ID of the blog post
seo_title: SEO title (optional)
seo_description: SEO description (optional)
seo_keywords: SEO keywords (optional)
og_image_url: Open Graph image URL (optional)
structured_data: Structured data (JSON-LD) (optional)
Returns:
Updated SEO settings data
"""
# Get the post URL
post_url = self.client.get_post_url(post_id)
# Update SEO settings
return self.client.update_seo_settings(
page_url=post_url,
title=seo_title,
description=seo_description,
keywords=seo_keywords,
og_image_url=og_image_url,
structured_data=structured_data
)
def generate_structured_data(
self,
post_id: str,
author_name: str,
publisher_name: str,
publisher_logo_url: str
) -> Dict:
"""
Generate structured data (JSON-LD) for a blog post.
Args:
post_id: ID of the blog post
author_name: Name of the author
publisher_name: Name of the publisher
publisher_logo_url: URL of the publisher's logo
Returns:
Structured data as a dictionary
"""
# Get post data
post_data = self.client.get_post(post_id)
post = post_data.get("post", {})
# Get post URL
post_url = self.client.get_post_url(post_id)
# Create structured data
structured_data = {
"@context": "https://schema.org",
"@type": "BlogPosting",
"headline": post.get("title", ""),
"description": post.get("excerpt", ""),
"author": {
"@type": "Person",
"name": author_name
},
"publisher": {
"@type": "Organization",
"name": publisher_name,
"logo": {
"@type": "ImageObject",
"url": publisher_logo_url
}
},
"datePublished": post.get("publishedDate", ""),
"dateModified": post.get("lastPublishedDate", "")
}
# Add featured image if available
if post.get("featuredImageId"):
try:
media_item = self.client.get_media_item(post["featuredImageId"])
image_url = media_item.get("file", {}).get("url", "")
if image_url:
structured_data["image"] = image_url
except:
pass
return structured_data
def apply_structured_data_to_post(
self,
post_id: str,
author_name: str,
publisher_name: str,
publisher_logo_url: str
) -> Dict:
"""
Generate and apply structured data to a blog post.
Args:
post_id: ID of the blog post
author_name: Name of the author
publisher_name: Name of the publisher
publisher_logo_url: URL of the publisher's logo
Returns:
Updated SEO settings data
"""
# Generate structured data
structured_data = self.generate_structured_data(
post_id=post_id,
author_name=author_name,
publisher_name=publisher_name,
publisher_logo_url=publisher_logo_url
)
# Get the post URL
post_url = self.client.get_post_url(post_id)
# Update SEO settings with structured data
return self.client.update_seo_settings(
page_url=post_url,
structured_data=structured_data
)
# Helper methods
def _markdown_to_html(self, markdown_content: str) -> str:
"""
Convert markdown content to HTML.
Args:
markdown_content: Content in markdown format
Returns:
HTML content
"""
# Use the markdown library to convert to HTML
html = markdown.markdown(
markdown_content,
extensions=['extra', 'codehilite', 'tables', 'toc']
)
return html
def _html_to_markdown(self, html_content: str) -> str:
"""
Convert HTML content to markdown.
Args:
html_content: Content in HTML format
Returns:
Markdown content
"""
# Use html2text to convert HTML to markdown
h = html2text.HTML2Text()
h.ignore_links = False
h.ignore_images = False
h.ignore_tables = False
h.ignore_emphasis = False
return h.handle(html_content)
def _process_content_images(self, html_content: str) -> Tuple[str, List[Dict]]:
"""
Process images in HTML content, uploading them to Wix and replacing URLs.
Args:
html_content: HTML content with image tags
Returns:
Tuple of (updated HTML content, list of uploaded image data)
"""
soup = BeautifulSoup(html_content, 'html.parser')
img_tags = soup.find_all('img')
uploaded_images = []
for img in img_tags:
src = img.get('src', '')
alt = img.get('alt', '')
# Skip images that are already hosted on Wix
if 'wixstatic.com' in src:
continue
# Handle images with data URLs
if src.startswith('data:image'):
logger.info("Skipping data URL image - not supported in this implementation")
continue
# Handle remote images
if src.startswith('http://') or src.startswith('https://'):
try:
# Download the image
temp_path = self._download_image(src)
# Upload to Wix
image_response = self.client.upload_image(
file_path=temp_path,
title=alt or "Blog image",
alt_text=alt or "Blog image"
)
# Get the new URL
new_url = image_response.get("file", {}).get("url", "")
if new_url:
# Replace the src attribute
img['src'] = new_url
uploaded_images.append({
'original_url': src,
'wix_url': new_url,
'wix_id': image_response.get("file", {}).get("id", "")
})
# Clean up temp file
if os.path.exists(temp_path):
os.remove(temp_path)
except Exception as e:
logger.error(f"Failed to process image {src}: {str(e)}")
# Handle local images (not implemented in this version)
else:
logger.info(f"Skipping local image {src} - not supported in this implementation")
# Return the updated HTML
return str(soup), uploaded_images
def _download_image(self, url: str) -> str:
"""
Download an image from a URL to a temporary file.
Args:
url: URL of the image
Returns:
Path to the downloaded temporary file
"""
response = requests.get(url, stream=True)
response.raise_for_status()
# Determine file extension
content_type = response.headers.get('content-type', '')
extension = '.jpg' # Default
if 'image/jpeg' in content_type:
extension = '.jpg'
elif 'image/png' in content_type:
extension = '.png'
elif 'image/gif' in content_type:
extension = '.gif'
elif 'image/webp' in content_type:
extension = '.webp'
# Create a temporary file
fd, temp_path = tempfile.mkstemp(suffix=extension)
os.close(fd)
# Write the image data to the file
with open(temp_path, 'wb') as f:
for chunk in response.iter_content(chunk_size=8192):
f.write(chunk)
return temp_path
def _generate_excerpt(self, markdown_content: str, max_length: int = 160) -> str:
"""
Generate an excerpt from markdown content.
Args:
markdown_content: Content in markdown format
max_length: Maximum length of the excerpt
Returns:
Generated excerpt
"""
# Convert markdown to plain text
h = html2text.HTML2Text()
h.ignore_links = True
h.ignore_images = True
h.ignore_tables = True
h.ignore_emphasis = True
# First convert markdown to HTML, then HTML to plain text
html = markdown.markdown(markdown_content)
plain_text = h.handle(html)
# Clean up the text
plain_text = re.sub(r'\s+', ' ', plain_text).strip()
# Truncate to max_length
if len(plain_text) <= max_length:
return plain_text
# Try to truncate at a sentence boundary
sentences = re.split(r'(?<=[.!?])\s+', plain_text)
excerpt = ""
for sentence in sentences:
if len(excerpt + sentence) <= max_length:
excerpt += sentence + " "
else:
break
# If we couldn't get a full sentence, just truncate
if not excerpt:
excerpt = plain_text[:max_length-3] + "..."
return excerpt.strip()

View File

@@ -1,350 +0,0 @@
"""
Wix Blog Publisher for Alwrity
This module integrates the Wix API with the Alwrity AI Writer platform,
allowing users to publish generated blog content directly to their Wix site.
"""
import os
import logging
import tempfile
import streamlit as st
from typing import Dict, List, Optional, Union, Any, Tuple
from pathlib import Path
from .wix_integration import WixIntegration
# Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger('wix_blog_publisher')
def publish_to_wix(
title: str,
content: str,
is_markdown: bool = True,
featured_image_path: Optional[str] = None,
featured_image_url: Optional[str] = None,
excerpt: Optional[str] = None,
tags: Optional[List[str]] = None,
categories: Optional[List[str]] = None,
seo_title: Optional[str] = None,
seo_description: Optional[str] = None,
seo_keywords: Optional[List[str]] = None,
author_name: Optional[str] = None,
publisher_name: Optional[str] = None,
publisher_logo_url: Optional[str] = None,
publish: bool = True,
update_if_exists: bool = True,
api_key: Optional[str] = None,
refresh_token: Optional[str] = None,
site_id: Optional[str] = None
) -> Dict:
"""
Publish a blog post to Wix.
Args:
title: Post title
content: Post content (markdown or HTML)
is_markdown: Whether the content is in markdown format
featured_image_path: Local path to featured image (optional)
featured_image_url: URL of featured image to download (optional)
excerpt: Post excerpt/summary (optional)
tags: List of tags (optional)
categories: List of category names (optional)
seo_title: SEO title (optional)
seo_description: SEO description (optional)
seo_keywords: SEO keywords (optional)
author_name: Name of the author (optional)
publisher_name: Name of the publisher (optional)
publisher_logo_url: URL of the publisher's logo (optional)
publish: Whether to publish the post immediately (optional)
update_if_exists: Whether to update an existing post with the same title (optional)
api_key: Wix API key (optional if using refresh token)
refresh_token: Wix refresh token for OAuth authentication
site_id: Wix site ID
Returns:
Published blog post data
"""
# Initialize Wix integration
wix = WixIntegration(api_key, refresh_token, site_id)
# Publish the blog post
return wix.publish_blog_post(
title=title,
content=content,
is_markdown=is_markdown,
featured_image_path=featured_image_path,
featured_image_url=featured_image_url,
excerpt=excerpt,
tags=tags,
categories=categories,
seo_title=seo_title,
seo_description=seo_description,
seo_keywords=seo_keywords,
author_name=author_name,
publisher_name=publisher_name,
publisher_logo_url=publisher_logo_url,
publish=publish,
update_if_exists=update_if_exists
)
def wix_blog_publisher_ui():
"""
Streamlit UI for publishing blog posts to Wix.
"""
st.title("Publish to Wix")
st.write("Publish your blog content directly to your Wix site.")
# Authentication settings
st.header("Wix Authentication")
# Check for saved credentials
if "wix_refresh_token" in st.session_state and "wix_site_id" in st.session_state:
st.success("✅ Wix credentials are saved in this session.")
show_saved = st.checkbox("Show saved credentials")
if show_saved:
st.text_input("Refresh Token", value=st.session_state.wix_refresh_token, type="password", disabled=True)
st.text_input("Site ID", value=st.session_state.wix_site_id, disabled=True)
clear_creds = st.button("Clear saved credentials")
if clear_creds:
if "wix_refresh_token" in st.session_state:
del st.session_state.wix_refresh_token
if "wix_site_id" in st.session_state:
del st.session_state.wix_site_id
st.rerun()
else:
col1, col2 = st.columns(2)
with col1:
refresh_token = st.text_input("Wix Refresh Token", type="password", help="Your Wix refresh token for API authentication")
with col2:
site_id = st.text_input("Wix Site ID", help="Your Wix site ID")
save_creds = st.checkbox("Save credentials for this session", value=True)
if st.button("Validate Credentials"):
if not refresh_token:
st.error("Refresh token is required.")
return
if not site_id:
st.error("Site ID is required.")
return
# Try to initialize Wix integration to validate credentials
try:
wix = WixIntegration(refresh_token=refresh_token, site_id=site_id)
# Test API call
site_info = wix.get_site_info()
if site_info.get("status") == "connected":
st.success(f"✅ Credentials validated successfully! Found {site_info.get('post_count', 0)} posts and {site_info.get('category_count', 0)} categories.")
# Save credentials if requested
if save_creds:
st.session_state.wix_refresh_token = refresh_token
st.session_state.wix_site_id = site_id
st.rerun()
else:
st.error(f"❌ Failed to validate credentials: {site_info.get('error', 'Unknown error')}")
except Exception as e:
st.error(f"❌ Failed to validate credentials: {str(e)}")
return
# Blog content section
st.header("Blog Content")
# Check if we have content in session state (from other parts of the app)
blog_title = st.text_input(
"Blog Title",
value=st.session_state.get("blog_title", ""),
help="The title of your blog post"
)
content_type = st.radio(
"Content Format",
["Markdown", "HTML"],
horizontal=True,
help="The format of your blog content"
)
is_markdown = content_type == "Markdown"
blog_content = st.text_area(
"Blog Content",
value=st.session_state.get("blog_content", ""),
height=300,
help="The content of your blog post"
)
# Featured image
st.subheader("Featured Image")
image_source = st.radio(
"Image Source",
["None", "Upload", "URL"],
horizontal=True,
help="How to provide the featured image"
)
featured_image_path = None
featured_image_url = None
if image_source == "Upload":
uploaded_file = st.file_uploader("Upload Featured Image", type=["jpg", "jpeg", "png", "gif"])
if uploaded_file:
# Save the uploaded file to a temporary location
with tempfile.NamedTemporaryFile(delete=False, suffix=f".{uploaded_file.name.split('.')[-1]}") as tmp:
tmp.write(uploaded_file.getvalue())
featured_image_path = tmp.name
elif image_source == "URL":
featured_image_url = st.text_input("Featured Image URL", help="URL of the featured image")
# Blog metadata
st.header("Blog Metadata")
col1, col2 = st.columns(2)
with col1:
excerpt = st.text_area(
"Excerpt",
value=st.session_state.get("blog_excerpt", ""),
help="A short summary of your blog post"
)
tags_input = st.text_input(
"Tags (comma-separated)",
value=", ".join(st.session_state.get("blog_tags", [])) if isinstance(st.session_state.get("blog_tags", []), list) else st.session_state.get("blog_tags", ""),
help="Tags for your blog post, separated by commas"
)
tags = [tag.strip() for tag in tags_input.split(",")] if tags_input else None
categories_input = st.text_input(
"Categories (comma-separated)",
value=", ".join(st.session_state.get("blog_categories", [])) if isinstance(st.session_state.get("blog_categories", []), list) else st.session_state.get("blog_categories", ""),
help="Categories for your blog post, separated by commas"
)
categories = [cat.strip() for cat in categories_input.split(",")] if categories_input else None
with col2:
author_name = st.text_input("Author Name", help="Name of the blog post author")
publisher_name = st.text_input("Publisher Name", help="Name of the blog publisher (usually your site name)")
publisher_logo_url = st.text_input("Publisher Logo URL", help="URL of the publisher's logo")
# SEO settings
with st.expander("SEO Settings"):
seo_title = st.text_input("SEO Title", value=blog_title, help="Title for search engines (defaults to blog title)")
seo_description = st.text_area("SEO Description", value=excerpt, help="Description for search engines (defaults to excerpt)")
seo_keywords_input = st.text_input("SEO Keywords (comma-separated)", value=tags_input, help="Keywords for search engines (defaults to tags)")
seo_keywords = [kw.strip() for kw in seo_keywords_input.split(",")] if seo_keywords_input else None
# Publishing options
st.header("Publishing Options")
col1, col2 = st.columns(2)
with col1:
publish = not st.checkbox("Save as draft", help="If checked, the post will be saved as a draft instead of being published")
with col2:
update_if_exists = st.checkbox("Update if exists", value=True, help="If checked, an existing post with the same title will be updated")
# Publish button
if st.button("Publish to Wix", type="primary"):
if not blog_title:
st.error("Blog title is required.")
return
if not blog_content:
st.error("Blog content is required.")
return
# Get credentials
refresh_token = st.session_state.get("wix_refresh_token")
site_id = st.session_state.get("wix_site_id")
if not refresh_token or not site_id:
st.error("Wix credentials are required. Please enter them in the authentication section.")
return
# Show progress
with st.spinner("Publishing to Wix..."):
try:
# Publish to Wix
result = publish_to_wix(
title=blog_title,
content=blog_content,
is_markdown=is_markdown,
featured_image_path=featured_image_path,
featured_image_url=featured_image_url,
excerpt=excerpt,
tags=tags,
categories=categories,
seo_title=seo_title,
seo_description=seo_description,
seo_keywords=seo_keywords,
author_name=author_name,
publisher_name=publisher_name,
publisher_logo_url=publisher_logo_url,
publish=publish,
update_if_exists=update_if_exists,
refresh_token=refresh_token,
site_id=site_id
)
# Clean up temporary file if created
if featured_image_path and os.path.exists(featured_image_path) and featured_image_path.startswith(tempfile.gettempdir()):
try:
os.remove(featured_image_path)
except:
pass
# Show success message
st.success("✅ Blog post published successfully!")
# Show post details
post = result.get("post", {})
st.subheader("Published Post Details")
col1, col2 = st.columns(2)
with col1:
st.write(f"**Title:** {post.get('title', 'N/A')}")
st.write(f"**Status:** {post.get('status', 'N/A')}")
st.write(f"**ID:** {post.get('id', 'N/A')}")
with col2:
st.write(f"**Published Date:** {post.get('publishedDate', 'N/A')}")
st.write(f"**URL:** {post.get('url', 'N/A')}")
st.write(f"**Tags:** {', '.join(post.get('tags', []))}")
# Add a view button if URL is available
if post.get("url"):
st.markdown(f"[View Post]({post.get('url')})")
# Add SEO report button
if st.button("Generate SEO Report"):
with st.spinner("Generating SEO report..."):
try:
wix = WixIntegration(refresh_token=refresh_token, site_id=site_id)
seo_report = wix.get_seo_report(post.get("id"), seo_keywords or tags or [])
st.subheader("SEO Report")
st.write(f"**SEO Score:** {seo_report.get('seo_score', 0):.1f}/100")
st.write("**Recommendations:**")
for i, rec in enumerate(seo_report.get("recommendations", [])):
st.write(f"{i+1}. {rec}")
except Exception as e:
st.error(f"Failed to generate SEO report: {str(e)}")
except Exception as e:
st.error(f"❌ Failed to publish blog post: {str(e)}")
logger.error(f"Failed to publish blog post: {str(e)}")
# For testing the UI directly
if __name__ == "__main__":
wix_blog_publisher_ui()

View File

@@ -1,388 +0,0 @@
"""
Wix Integration for Alwrity
This module provides a high-level interface for integrating Wix blog functionality
with the Alwrity AI Writer platform.
"""
import os
import logging
import json
from typing import Dict, List, Optional, Union, Any, Tuple
from pathlib import Path
from .wix_api_client import WixAPIClient
from .wix_blog_manager import WixBlogManager
from .wix_seo_optimizer import WixSEOOptimizer
# Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger('wix_integration')
class WixIntegration:
"""
Main integration class for Wix blog functionality.
This class provides a simplified interface for common operations,
combining the functionality of the API client, blog manager, and SEO optimizer.
"""
def __init__(
self,
api_key: Optional[str] = None,
refresh_token: Optional[str] = None,
site_id: Optional[str] = None
):
"""
Initialize the Wix Integration.
Args:
api_key: Wix API key (optional if using refresh token)
refresh_token: Wix refresh token for OAuth authentication
site_id: Wix site ID
"""
self.api_client = WixAPIClient(api_key, refresh_token, site_id)
self.blog_manager = WixBlogManager(api_key, refresh_token, site_id)
self.seo_optimizer = WixSEOOptimizer(api_key, refresh_token, site_id)
def publish_blog_post(
self,
title: str,
content: str,
is_markdown: bool = True,
featured_image_path: Optional[str] = None,
featured_image_url: Optional[str] = None,
excerpt: Optional[str] = None,
tags: Optional[List[str]] = None,
categories: Optional[List[str]] = None,
seo_title: Optional[str] = None,
seo_description: Optional[str] = None,
seo_keywords: Optional[List[str]] = None,
author_name: Optional[str] = None,
publisher_name: Optional[str] = None,
publisher_logo_url: Optional[str] = None,
publish: bool = True,
update_if_exists: bool = True
) -> Dict:
"""
Publish a blog post with comprehensive SEO optimization.
Args:
title: Post title
content: Post content (markdown or HTML)
is_markdown: Whether the content is in markdown format
featured_image_path: Local path to featured image (optional)
featured_image_url: URL of featured image to download (optional)
excerpt: Post excerpt/summary (optional)
tags: List of tags (optional)
categories: List of category names (optional)
seo_title: SEO title (optional)
seo_description: SEO description (optional)
seo_keywords: SEO keywords (optional)
author_name: Name of the author (optional)
publisher_name: Name of the publisher (optional)
publisher_logo_url: URL of the publisher's logo (optional)
publish: Whether to publish the post immediately (optional)
update_if_exists: Whether to update an existing post with the same title (optional)
Returns:
Published blog post data
"""
# Generate SEO data if not provided
if not seo_keywords and tags:
seo_keywords = tags
if not seo_title:
seo_title = title
if not seo_description and not excerpt:
if is_markdown:
# Generate description from markdown content
seo_description = self.blog_manager._generate_excerpt(content)
else:
# Generate description from HTML content
seo_description = self.seo_optimizer.generate_meta_description(content)
elif not seo_description:
seo_description = excerpt
# Publish or update the post
if is_markdown:
response = self.blog_manager.publish_or_update_markdown_post(
title=title,
markdown_content=content,
featured_image_path=featured_image_path,
featured_image_url=featured_image_url,
excerpt=excerpt,
tags=tags,
categories=categories,
seo_title=seo_title,
seo_description=seo_description,
seo_keywords=seo_keywords,
publish=publish,
update_if_exists=update_if_exists
)
else:
# Find existing post or create new one
existing_post = self.blog_manager.find_post_by_title(title)
if existing_post and update_if_exists:
# Update existing post
response = self.api_client.update_post(
post_id=existing_post["id"],
title=title,
content=content,
excerpt=excerpt,
tags=tags,
categories=[self.api_client.get_or_create_category(cat) for cat in categories] if categories else None,
seo_data={
"title": seo_title,
"description": seo_description,
"keywords": seo_keywords or []
},
publish=publish
)
else:
# Create new post
response = self.api_client.create_post(
title=title,
content=content,
excerpt=excerpt,
tags=tags,
categories=[self.api_client.get_or_create_category(cat) for cat in categories] if categories else None,
seo_data={
"title": seo_title,
"description": seo_description,
"keywords": seo_keywords or []
},
publish=publish
)
# Apply additional SEO optimization if the post was published
if publish and response.get("post", {}).get("id"):
post_id = response["post"]["id"]
# Apply structured data if author and publisher info is provided
if author_name and publisher_name and publisher_logo_url:
try:
self.seo_optimizer.apply_structured_data_to_post(
post_id=post_id,
author_name=author_name,
publisher_name=publisher_name,
publisher_logo_url=publisher_logo_url
)
except Exception as e:
logger.error(f"Failed to apply structured data: {str(e)}")
# Apply comprehensive SEO optimization
try:
self.seo_optimizer.apply_seo_optimization(
post_id=post_id,
title=seo_title,
description=seo_description,
keywords=seo_keywords,
author_name=author_name,
publisher_name=publisher_name,
publisher_logo_url=publisher_logo_url,
og_image_url=featured_image_url
)
except Exception as e:
logger.error(f"Failed to apply SEO optimization: {str(e)}")
return response
def upload_media(
self,
file_path: str,
title: Optional[str] = None,
alt_text: Optional[str] = None,
description: Optional[str] = None
) -> Dict:
"""
Upload a media file to Wix.
Args:
file_path: Path to the media file
title: Media title (optional)
alt_text: Media alt text (optional)
description: Media description (optional)
Returns:
Uploaded media data
"""
return self.api_client.upload_image(
file_path=file_path,
title=title,
alt_text=alt_text,
description=description
)
def get_seo_report(self, post_id: str, target_keywords: List[str]) -> Dict:
"""
Generate a comprehensive SEO report for a blog post.
Args:
post_id: ID of the blog post
target_keywords: List of target keywords
Returns:
Dictionary with SEO report data
"""
return self.seo_optimizer.generate_seo_report(post_id, target_keywords)
def list_blog_posts(
self,
limit: int = 50,
offset: int = 0,
sort_field: str = "lastPublishedDate",
sort_order: str = "desc"
) -> Dict:
"""
List blog posts with pagination and sorting.
Args:
limit: Maximum number of posts to return (default: 50)
offset: Pagination offset (default: 0)
sort_field: Field to sort by (default: lastPublishedDate)
sort_order: Sort order, 'asc' or 'desc' (default: desc)
Returns:
Dictionary containing blog posts and pagination info
"""
return self.api_client.list_posts(
limit=limit,
offset=offset,
sort_field=sort_field,
sort_order=sort_order
)
def list_categories(self) -> Dict:
"""
List all blog categories.
Returns:
Dictionary containing blog categories
"""
return self.api_client.list_categories()
def create_category(self, name: str, description: Optional[str] = None) -> str:
"""
Create a new blog category.
Args:
name: Category name
description: Category description (optional)
Returns:
ID of the created category
"""
response = self.api_client.create_category(
label=name,
description=description
)
return response.get("category", {}).get("id", "")
def get_post_by_id(self, post_id: str) -> Dict:
"""
Get a blog post by ID.
Args:
post_id: ID of the blog post
Returns:
Blog post data
"""
return self.api_client.get_post(post_id)
def get_post_by_title(self, title: str) -> Optional[Dict]:
"""
Get a blog post by title.
Args:
title: Title of the blog post
Returns:
Blog post data or None if not found
"""
return self.blog_manager.find_post_by_title(title)
def delete_post(self, post_id: str) -> Dict:
"""
Delete a blog post.
Args:
post_id: ID of the blog post
Returns:
Response data
"""
return self.api_client.delete_post(post_id)
def update_post_status(self, post_id: str, publish: bool = True) -> Dict:
"""
Update the publication status of a blog post.
Args:
post_id: ID of the blog post
publish: Whether to publish (True) or unpublish (False) the post
Returns:
Updated blog post data
"""
if publish:
return self.api_client.publish_post(post_id)
else:
return self.api_client.unpublish_post(post_id)
def search_posts(self, query: str, limit: int = 10) -> List[Dict]:
"""
Search for blog posts by content or title.
Args:
query: Search query
limit: Maximum number of results to return
Returns:
List of matching blog posts
"""
# First try to find by title
title_matches = []
try:
all_posts = self.list_blog_posts(limit=100)["posts"]
for post in all_posts:
if query.lower() in post.get("title", "").lower():
title_matches.append(post)
if len(title_matches) >= limit:
break
except Exception as e:
logger.error(f"Error searching posts by title: {str(e)}")
return title_matches[:limit]
def get_site_info(self) -> Dict:
"""
Get information about the Wix site.
Returns:
Dictionary with site information
"""
try:
# Make a simple API call to verify credentials and get site info
posts = self.list_blog_posts(limit=1)
categories = self.list_categories()
return {
"site_id": self.api_client.site_id,
"post_count": posts.get("totalCount", 0),
"category_count": len(categories.get("categories", [])),
"status": "connected"
}
except Exception as e:
logger.error(f"Error getting site info: {str(e)}")
return {
"site_id": self.api_client.site_id,
"status": "error",
"error": str(e)
}

View File

@@ -1,335 +0,0 @@
import os
import sys
import mimetypes
import requests
from requests.auth import HTTPBasicAuth
import base64
import json
from clint.textui import progress
from PIL import Image
import tempfile
import os
from loguru import logger
logger.remove()
logger.add(sys.stdout,
colorize=True,
format="<level>{level}</level>|<green>{file}:{line}:{function}</green>| {message}"
)
## Check if blog needs to be posted on wordpress.
#if wordpress:
## Fixme: Fetch all tags and categories to check, if present ones are present and
## use them else create new ones. Its better to use chatgpt than string comparison.
## Similar tags and categories will be missed.
## blog_categories =
## blog_tags =
#logger.info("Uploading the blog to wordpress.\n")
#main_img_path = compress_image(main_img_path, quality=85)
#try:
# img_details = analyze_and_extract_details_from_image(main_img_path)
# alt_text = img_details.get('alt_text')
# img_description = img_details.get('description')
# img_title = img_details.get('title')
# caption = img_details.get('caption')
# try:
# media = upload_media(wordpress_url, wordpress_username, wordpress_password,
# main_img_path, alt_text, img_description, img_title, caption)
# except Exception as err:
# sys.exit(f"Error occurred in upload_media: {err}")
#except Exception as e:
# sys.exit(f"Error occurred in analyze_and_extract_details_from_image: {e}")
#
## Then create the post with the uploaded media as the featured image
#media_id = media['id']
#blog_markdown_str = convert_markdown_to_html(blog_markdown_str)
#try:
# upload_blog_post(wordpress_url, wordpress_username, wordpress_password, a_blog_topic,
# blog_markdown_str, media_id, blog_meta_desc, blog_categories, blog_tags, status='publish')
#except Exception as err:
# sys.exit(f"Failed to upload blog to wordpress.Error: {err}")
def compress_image(image_path, quality=85):
"""
Compress the image by reducing its quality and logger.info size information.
:param image_path: Path to the original image
:param quality: Quality of the output image (1-100), lower means more compression
:return: Path to the compressed image
"""
if not os.path.exists(image_path):
raise ValueError(f"Provided image path does not exist: {image_path}")
# Get the size of the original image
original_size = os.path.getsize(image_path)
# Open the image
with Image.open(image_path) as img:
# Define the format based on the original image format
img_format = img.format
# Create a temporary file to save the compressed image
temp_file = tempfile.NamedTemporaryFile(delete=False, suffix='.' + img_format.lower())
# Save the image with reduced quality
img.save(temp_file, format=img_format, quality=quality, optimize=True)
# Get the size of the compressed image
compressed_size = os.path.getsize(temp_file.name)
# Calculate the percentage reduction
reduction = (1 - (compressed_size / original_size)) * 100
logger.info("########### Image Compression ###############")
logger.info(f"Compressing the image, Original size: {original_size / 1024:.2f} KB")
logger.info(f"Compressed size: {compressed_size / 1024:.2f} KB")
logger.info(f"Reduction in image size: {reduction:.2f}%")
# TBD: https://tinypng.com/developers/reference/python
logger.info(f"Note: Consider converting images to JPEG/WebP format.\n\n")
return temp_file.name
def create_wordpress_tag(url, username, app_password, tag_name):
"""
Create a new tag in WordPress using the REST API and return its ID.
:param url: URL of the WordPress site (e.g., 'https://example.com')
:param username: WordPress username
:param app_password: WordPress application password
:param tag_name: Name of the tag to be created
:return: ID of the created tag or error message
"""
api_endpoint = f"{url}/wp-json/wp/v2/tags"
headers = {
'Content-Type': 'application/json',
}
data = {
'name': tag_name,
}
response = requests.post(api_endpoint, json=data, auth=HTTPBasicAuth(username, app_password), headers=headers)
if response.status_code == 201:
return response.json().get('id') # Return the ID of the created tag
else:
return response.text
def create_wordpress_category(url, username, app_password, category_name):
"""
Create a new category in WordPress using the REST API and return its ID.
:param url: URL of the WordPress site (e.g., 'https://example.com')
:param username: WordPress username
:param app_password: WordPress application password
:param category_name: Name of the category to be created
:return: ID of the created category or error message
"""
api_endpoint = f"{url}/wp-json/wp/v2/categories"
headers = {
'Content-Type': 'application/json',
}
data = {
'name': category_name,
}
response = requests.post(api_endpoint, json=data, auth=HTTPBasicAuth(username, app_password), headers=headers)
if response.status_code == 201:
return response.json().get('id') # Return the ID of the created category
else:
return response.text
def get_all_wordpress_categories(url, username, password):
"""
Get all categories from WordPress.
:param url: URL of the WordPress site
:param username: WordPress username
:param password: WordPress application password
:return: Dictionary of category names and their IDs
"""
logger.info("Fetching all wordpress categories to create Or use exsiting.")
categories = {}
api_endpoint = f"{url}/wp-json/wp/v2/categories"
response = requests.get(api_endpoint, auth=HTTPBasicAuth(username, password))
if response.status_code == 200:
for category in response.json():
categories[category['name']] = category['id']
return categories
else:
return "Error: " + response.text
def get_all_wordpress_tags(url, username, password):
"""
Get all tags from WordPress.
:param url: URL of the WordPress site
:param username: WordPress username
:param password: WordPress application password
:return: Dictionary of tag names and their IDs
"""
logger.info("Fetching all tags from wordpress to create or use existing tag.")
tags = {}
api_endpoint = f"{url}/wp-json/wp/v2/tags"
response = requests.get(api_endpoint, auth=HTTPBasicAuth(username, password))
if response.status_code == 200:
for tag in response.json():
tags[tag['name']] = tag['id']
return tags
else:
return "Error: " + response.text
def create_or_get_wordpress_category(url, username, password, category_name):
"""
Create a new category or get existing one from WordPress.
:param url: URL of the WordPress site
:param username: WordPress username
:param password: WordPress application password
:param category_name: Name of the category
:return: ID of the category
"""
existing_categories = get_all_wordpress_categories(url, username, password)
if category_name in existing_categories:
return existing_categories[category_name]
else:
return create_wordpress_category(url, username, password, category_name)
def create_or_get_wordpress_tag(url, username, password, tag_name):
"""
Create a new tag or get existing one from WordPress.
:param url: URL of the WordPress site
:param username: WordPress username
:param password: WordPress application password
:param tag_name: Name of the tag
:return: ID of the tag
"""
existing_tags = get_all_wordpress_tags(url, username, password)
if tag_name in existing_tags:
return existing_tags[tag_name]
else:
return create_wordpress_tag(url, username, password, tag_name)
def upload_media(url, username, password, media_path, alt_text, description, title, caption):
"""
Upload media to WordPress site with alt text, description, title, and caption.
:param url: URL of your WordPress site
:param username: Your WordPress username
:param password: Your WordPress password
:param media_path: Path to the media file
:param alt_text: Alternative text for the image
:param description: Description of the media
:param title: Title of the media
:param caption: Caption for the media
"""
if not os.path.exists(media_path):
logger.info(f"File not found: {media_path}")
return None
mime_type, _ = mimetypes.guess_type(media_path)
if mime_type is None:
logger.info(f"Unable to determine MIME type for the file: {media_path}")
return None
credentials = username + ':' + password
token = base64.b64encode(credentials.encode())
header = {
'Authorization': 'Basic ' + token.decode('utf-8'),
'Content-Disposition': 'attachment; filename={}'.format(os.path.basename(media_path))
}
with open(media_path, 'rb', encoding="utf-8") as media:
media_name = os.path.basename(media_path)
files = {'file': (media_name, media, mime_type)}
# Upload the media file
response = requests.post(url + '/wp-json/wp/v2/media', headers=header, files=files)
if response.status_code == 201:
logger.info("Media uploaded successfully.")
media_id = response.json()['id']
# Update media with alt text, description, title, and caption
media_data = {
'alt_text': alt_text,
'description': description,
'title': title,
'caption': caption
}
media_update_response = requests.post(f"{url}/wp-json/wp/v2/media/{media_id}", headers=header, json=media_data)
if media_update_response.status_code == 200:
logger.info("Media updated with alt text, description, title, and caption successfully.")
return media_update_response.json()
else:
logger.error("Failed to update media.")
logger.error(f"Response:{media_update_response.content}")
return None
else:
logger.error("Failed to upload media.")
logger.error("Response:{response.content}")
return None
def upload_blog_post(url, username, password, title, content, media_id, meta_desc, categories=None, tags=None, status='draft'):
"""
Upload a blog post to a WordPress site.
https://developer.wordpress.org/rest-api/reference/posts/#create-a-post
:param url: URL of your WordPress site
:param username: Your WordPress username
:param password: Your WordPress password
:param title: Title of the blog post
:param content: Content of the blog post
:param media_id: ID of the uploaded media to be set as the featured image
:param categories: List of category IDs
:param tags: List of tag IDs
:param status: Status of the post ('draft', 'publish', etc.)
"""
credentials = username + ':' + password
token = base64.b64encode(credentials.encode())
header = {'Authorization': 'Basic ' + token.decode('utf-8')}
# Prepare the data for the post
# https://developer.wordpress.org/rest-api/reference/posts/#schema-meta
post = {
'title': title,
'content': content,
# One of: publish, future, draft, pending, private
'status': status,
'excerpt': meta_desc,
'featured_media': media_id,
#'categories': categories,
#'tags': tags,
'meta': {
'description': meta_desc # This depends on your WordPress setup
}
}
#if categories:
# post['categories'] = categories
# Make the request
response = requests.post(url + '/wp-json/wp/v2/posts', headers=header, json=post)
# Check response
if response.status_code == 201:
logger.info("Blog to wordpress, uploaded successfully.")
return json.loads(response.content)
else:
logger.error("Blog upload to wordpress Failed.")
logger.error(f"Response: {response.content}") # Print response content for debugging
return None