ALwrity Version 0.5.1 (Fastapi + React)
This commit is contained in:
754
ToBeMigrated/content_calendar/core/ai_generator.py
Normal file
754
ToBeMigrated/content_calendar/core/ai_generator.py
Normal file
@@ -0,0 +1,754 @@
|
||||
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
|
||||
163
ToBeMigrated/content_calendar/core/calendar_manager.py
Normal file
163
ToBeMigrated/content_calendar/core/calendar_manager.py
Normal file
@@ -0,0 +1,163 @@
|
||||
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)
|
||||
151
ToBeMigrated/content_calendar/core/content_brief.py
Normal file
151
ToBeMigrated/content_calendar/core/content_brief.py
Normal file
@@ -0,0 +1,151 @@
|
||||
from typing import Dict, List, Any, Optional
|
||||
import logging
|
||||
from pathlib import Path
|
||||
import sys
|
||||
|
||||
# Add parent directory to path to import existing tools
|
||||
parent_dir = str(Path(__file__).parent.parent.parent.parent)
|
||||
if parent_dir not in sys.path:
|
||||
sys.path.append(parent_dir)
|
||||
|
||||
from lib.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': {}
|
||||
}
|
||||
626
ToBeMigrated/content_calendar/core/content_generator.py
Normal file
626
ToBeMigrated/content_calendar/core/content_generator.py
Normal file
@@ -0,0 +1,626 @@
|
||||
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')
|
||||
599
ToBeMigrated/content_calendar/core/content_repurposer.py
Normal file
599
ToBeMigrated/content_calendar/core/content_repurposer.py
Normal file
@@ -0,0 +1,599 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user