Content Calendar, Content Gap Analysis, and Content Optimization

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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