# Twitter Integration Implementation Plan
This document outlines a comprehensive plan to implement and enhance the Twitter features in AI-Writer, transforming the "coming soon" features into fully functional components using Tweepy and user-provided API keys.
## Current State Analysis
The current Twitter functionality in AI-Writer includes:
- A Twitter dashboard with multiple planned features
- Only the "Smart Tweet Generator" is currently active
- The tweet generator creates content but lacks real Twitter integration
- Performance metrics are simulated rather than based on real data
- No actual posting, scheduling, or analytics capabilities
## Implementation Strategy
We'll implement the Twitter features in multiple phases, focusing on delivering value incrementally while building toward a comprehensive Twitter management solution.
### Phase 1: Twitter Authentication & Basic Integration
#### 1.1 Twitter API Authentication System
Create a secure system for users to connect their Twitter accounts:
```python
# lib/integrations/twitter/auth.py
import tweepy
import streamlit as st
from typing import Dict, Optional, Any
import os
import json
from pathlib import Path
import time
from loguru import logger
# Configuration paths
CONFIG_DIR = Path(__file__).parent.parent.parent.parent / 'config' / 'twitter'
CONFIG_DIR.mkdir(exist_ok=True, parents=True)
def get_twitter_auth_url() -> str:
"""Generate Twitter OAuth URL for user authentication."""
try:
# Get API keys from session state or environment
consumer_key = st.session_state.get('twitter_consumer_key', os.getenv('TWITTER_CONSUMER_KEY', ''))
consumer_secret = st.session_state.get('twitter_consumer_secret', os.getenv('TWITTER_CONSUMER_SECRET', ''))
if not consumer_key or not consumer_secret:
logger.error("Twitter API keys not found")
return ""
# Initialize OAuth handler
oauth1_user_handler = tweepy.OAuth1UserHandler(
consumer_key, consumer_secret,
callback="http://localhost:8501/twitter/callback"
)
# Get authorization URL
auth_url = oauth1_user_handler.get_authorization_url()
# Store OAuth handler in session state
st.session_state.twitter_oauth_handler = oauth1_user_handler
return auth_url
except Exception as e:
logger.error(f"Error generating Twitter auth URL: {str(e)}")
return ""
def handle_twitter_callback(oauth_verifier: str) -> bool:
"""Handle Twitter OAuth callback."""
try:
# Get OAuth handler from session state
oauth_handler = st.session_state.get('twitter_oauth_handler')
if not oauth_handler:
logger.error("OAuth handler not found in session state")
return False
# Get access tokens
access_token, access_token_secret = oauth_handler.get_access_token(oauth_verifier)
# Store tokens in session state
st.session_state.twitter_access_token = access_token
st.session_state.twitter_access_token_secret = access_token_secret
# Create API client
api = create_twitter_api()
# Get user info
user = api.verify_credentials()
# Store user info
st.session_state.twitter_user = {
'id': user.id,
'screen_name': user.screen_name,
'name': user.name,
'profile_image_url': user.profile_image_url,
'followers_count': user.followers_count,
'friends_count': user.friends_count
}
# Save credentials to file
save_twitter_credentials(
user.id,
access_token,
access_token_secret
)
return True
except Exception as e:
logger.error(f"Error handling Twitter callback: {str(e)}")
return False
def create_twitter_api() -> tweepy.API:
"""Create Twitter API client."""
try:
# Get API keys and tokens
consumer_key = st.session_state.get('twitter_consumer_key', os.getenv('TWITTER_CONSUMER_KEY', ''))
consumer_secret = st.session_state.get('twitter_consumer_secret', os.getenv('TWITTER_CONSUMER_SECRET', ''))
access_token = st.session_state.get('twitter_access_token', '')
access_token_secret = st.session_state.get('twitter_access_token_secret', '')
# Create auth handler
auth = tweepy.OAuth1UserHandler(
consumer_key, consumer_secret,
access_token, access_token_secret
)
# Create API client
api = tweepy.API(auth)
return api
except Exception as e:
logger.error(f"Error creating Twitter API client: {str(e)}")
raise
def create_twitter_client() -> tweepy.Client:
"""Create Twitter API v2 client."""
try:
# Get API keys and tokens
consumer_key = st.session_state.get('twitter_consumer_key', os.getenv('TWITTER_CONSUMER_KEY', ''))
consumer_secret = st.session_state.get('twitter_consumer_secret', os.getenv('TWITTER_CONSUMER_SECRET', ''))
access_token = st.session_state.get('twitter_access_token', '')
access_token_secret = st.session_state.get('twitter_access_token_secret', '')
bearer_token = st.session_state.get('twitter_bearer_token', os.getenv('TWITTER_BEARER_TOKEN', ''))
# Create client
client = tweepy.Client(
bearer_token=bearer_token,
consumer_key=consumer_key,
consumer_secret=consumer_secret,
access_token=access_token,
access_token_secret=access_token_secret
)
return client
except Exception as e:
logger.error(f"Error creating Twitter client: {str(e)}")
raise
def save_twitter_credentials(user_id: int, access_token: str, access_token_secret: str) -> bool:
"""Save Twitter credentials to file."""
try:
# Create user-specific credentials file
creds_file = CONFIG_DIR / f"{user_id}.json"
# Save credentials
with open(creds_file, 'w') as f:
json.dump({
'access_token': access_token,
'access_token_secret': access_token_secret,
'timestamp': time.time()
}, f)
return True
except Exception as e:
logger.error(f"Error saving Twitter credentials: {str(e)}")
return False
def load_twitter_credentials(user_id: int) -> Dict[str, str]:
"""Load Twitter credentials from file."""
try:
# Get user-specific credentials file
creds_file = CONFIG_DIR / f"{user_id}.json"
# Check if file exists
if not creds_file.exists():
logger.warning(f"No credentials found for user {user_id}")
return {}
# Load credentials
with open(creds_file, 'r') as f:
creds = json.load(f)
# Update session state
st.session_state.twitter_access_token = creds['access_token']
st.session_state.twitter_access_token_secret = creds['access_token_secret']
return creds
except Exception as e:
logger.error(f"Error loading Twitter credentials: {str(e)}")
return {}
def is_authenticated() -> bool:
"""Check if user is authenticated with Twitter."""
return (
'twitter_access_token' in st.session_state and
'twitter_access_token_secret' in st.session_state and
'twitter_user' in st.session_state
)
def logout() -> None:
"""Log out user from Twitter."""
if 'twitter_access_token' in st.session_state:
del st.session_state.twitter_access_token
if 'twitter_access_token_secret' in st.session_state:
del st.session_state.twitter_access_token_secret
if 'twitter_user' in st.session_state:
del st.session_state.twitter_user
if 'twitter_oauth_handler' in st.session_state:
del st.session_state.twitter_oauth_handler
```
#### 1.2 Twitter API Key Management UI
Create a UI for users to enter and manage their Twitter API keys:
```python
# lib/integrations/twitter/api_key_manager.py
import streamlit as st
from typing import Dict, Optional
import os
from loguru import logger
def render_twitter_api_key_manager() -> None:
"""Render Twitter API key management UI."""
st.markdown("## Twitter API Configuration")
st.markdown("""
To use Twitter integration features, you need to provide your Twitter API keys.
You can get these by creating a Twitter Developer account and setting up a project.
""")
# Create tabs for different authentication methods
tab1, tab2 = st.tabs(["Basic Authentication", "Advanced Settings"])
with tab1:
# Get existing keys from session state or environment
consumer_key = st.session_state.get('twitter_consumer_key', os.getenv('TWITTER_CONSUMER_KEY', ''))
consumer_secret = st.session_state.get('twitter_consumer_secret', os.getenv('TWITTER_CONSUMER_SECRET', ''))
bearer_token = st.session_state.get('twitter_bearer_token', os.getenv('TWITTER_BEARER_TOKEN', ''))
# Input fields for API keys
new_consumer_key = st.text_input(
"Consumer Key (API Key)",
value=consumer_key,
type="password",
help="Your Twitter API consumer key"
)
new_consumer_secret = st.text_input(
"Consumer Secret (API Secret)",
value=consumer_secret,
type="password",
help="Your Twitter API consumer secret"
)
new_bearer_token = st.text_input(
"Bearer Token",
value=bearer_token,
type="password",
help="Your Twitter API bearer token"
)
# Save button
if st.button("Save API Keys", use_container_width=True):
# Update session state
st.session_state.twitter_consumer_key = new_consumer_key
st.session_state.twitter_consumer_secret = new_consumer_secret
st.session_state.twitter_bearer_token = new_bearer_token
# Show success message
st.success("Twitter API keys saved successfully!")
with tab2:
st.markdown("### Advanced API Settings")
# Callback URL
st.text_input(
"Callback URL",
value="http://localhost:8501/twitter/callback",
disabled=True,
help="Use this URL in your Twitter Developer Portal"
)
# API usage limits
st.info("""
**Twitter API Rate Limits:**
- Standard API: 500,000 tweets/month
- Essential API: 10,000 tweets/month
Make sure your Twitter Developer account has the appropriate access level for your needs.
""")
# Help resources
st.markdown("""
**Need help?**
- [Twitter Developer Portal](https://developer.twitter.com)
- [API Documentation](https://developer.twitter.com/en/docs)
- [Rate Limits](https://developer.twitter.com/en/docs/twitter-api/rate-limits)
""")
```
#### 1.3 Twitter Account Connection UI
Create a UI for users to connect their Twitter accounts:
```python
# lib/integrations/twitter/account_manager.py
import streamlit as st
from typing import Dict, Optional
from .auth import get_twitter_auth_url, is_authenticated, logout, create_twitter_api
import tweepy
from loguru import logger
def render_twitter_account_manager() -> None:
"""Render Twitter account management UI."""
st.markdown("## Twitter Account")
# Check if API keys are configured
if not st.session_state.get('twitter_consumer_key') or not st.session_state.get('twitter_consumer_secret'):
st.warning("Please configure your Twitter API keys first.")
return
# Check if user is authenticated
if is_authenticated():
# Get user info
user = st.session_state.twitter_user
# Display user info
col1, col2, col3 = st.columns([1, 3, 1])
with col1:
st.image(user['profile_image_url'], width=80)
with col2:
st.markdown(f"**{user['name']}** (@{user['screen_name']})")
st.markdown(f"Followers: {user['followers_count']} | Following: {user['friends_count']}")
with col3:
if st.button("Disconnect", key="disconnect_twitter"):
logout()
st.experimental_rerun()
# Account status
st.success("â
Your Twitter account is connected and ready to use.")
# Account actions
st.markdown("### Quick Actions")
col1, col2 = st.columns(2)
with col1:
if st.button("View Profile", use_container_width=True):
try:
# Get API client
api = create_twitter_api()
# Get user timeline
tweets = api.user_timeline(count=5)
# Display tweets
st.markdown("#### Recent Tweets")
for tweet in tweets:
st.markdown(f"""
{tweet.text}
Posted on {tweet.created_at.strftime('%Y-%m-%d %H:%M')}
""", unsafe_allow_html=True)
except Exception as e:
logger.error(f"Error fetching tweets: {str(e)}")
st.error(f"Error fetching tweets: {str(e)}")
with col2:
if st.button("Check API Limits", use_container_width=True):
try:
# Get API client
api = create_twitter_api()
# Get rate limit status
limits = api.rate_limit_status()
# Display limits
st.markdown("#### API Rate Limits")
# Timeline limits
timeline_limit = limits['resources']['statuses']['/statuses/user_timeline']
st.markdown(f"**Timeline:** {timeline_limit['remaining']}/{timeline_limit['limit']} requests remaining")
# Search limits
search_limit = limits['resources']['search']['/search/tweets']
st.markdown(f"**Search:** {search_limit['remaining']}/{search_limit['limit']} requests remaining")
# Tweet posting limits
st.markdown("**Tweet Posting:** Limited by your Twitter API access level")
except Exception as e:
logger.error(f"Error checking API limits: {str(e)}")
st.error(f"Error checking API limits: {str(e)}")
else:
# Get auth URL
auth_url = get_twitter_auth_url()
if auth_url:
# Display connect button
st.markdown("""
""".format(auth_url=auth_url), unsafe_allow_html=True)
st.info("Click the button above to connect your Twitter account.")
else:
st.error("Failed to generate authentication URL. Please check your API keys.")
```
#### 1.4 Twitter Callback Handler
Create a handler for the Twitter OAuth callback:
```python
# lib/integrations/twitter/callback_handler.py
import streamlit as st
from urllib.parse import parse_qs, urlparse
from .auth import handle_twitter_callback
from loguru import logger
def handle_oauth_callback():
"""Handle Twitter OAuth callback in Streamlit."""
# Get current URL
query_params = st.experimental_get_query_params()
# Check if this is a callback
if 'oauth_token' in query_params and 'oauth_verifier' in query_params:
oauth_verifier = query_params['oauth_verifier'][0]
# Handle callback
success = handle_twitter_callback(oauth_verifier)
if success:
# Clear query parameters to avoid reprocessing
st.experimental_set_query_params()
# Show success message
st.success("Successfully connected Twitter account!")
# Redirect to main page
st.experimental_rerun()
else:
st.error("Failed to authenticate with Twitter. Please try again.")
# Check for error
if 'denied' in query_params:
st.error("Twitter authentication was denied.")
st.experimental_set_query_params()
```
### Phase 2: Enhanced Tweet Generator with Real Twitter Integration
#### 2.1 Improved Tweet Generator with Real Data
Enhance the tweet generator to use real Twitter data:
```python
# lib/ai_writers/twitter_writers/tweet_generator/enhanced_tweet_generator.py
import streamlit as st
import re
import json
import time
from typing import Dict, List, Tuple, Optional
import random
import emoji
from datetime import datetime
import tweepy
from .....gpt_providers.text_generation.main_text_generation import llm_text_gen
from ....integrations.twitter.auth import create_twitter_api, create_twitter_client, is_authenticated
# Constants
MAX_TWEET_LENGTH = 280
EMOJI_CATEGORIES = {
"Humorous": ["ð", "ð", "ðĪĢ", "ð", "ð", "ð", "ðĪŠ", "ð", "ðĪ", "ð"],
"Informative": ["ð", "ð", "ð", "ð", "ðĄ", "ð", "ð", "ð", "ð", "ð"],
"Inspirational": ["âĻ", "ð", "ðŦ", "â", "ðĨ", "ðŠ", "ð", "ð", "ðŊ", "ðŊ"],
"Serious": ["ðĪ", "ð", "ð§", "ðĒ", "ð", "âïļ", "ð", "ð", "ðŽ", "ð°"],
"Casual": ["ð", "ð", "ð", "ð", "ðĪ", "ð", "âïļ", "ðĪ", "ð", "ð"]
}
def count_characters(text: str) -> int:
"""Count characters in tweet, accounting for emojis."""
return len(text)
def extract_hashtags(text: str) -> List[str]:
"""Extract hashtags from tweet text."""
return re.findall(r'#\w+', text)
def get_trending_hashtags(location_id: int = 1) -> List[str]:
"""Get trending hashtags from Twitter API."""
try:
if not is_authenticated():
return []
# Get API client
api = create_twitter_api()
# Get trending topics
trends = api.get_place_trends(location_id)
# Extract hashtags
hashtags = [trend['name'] for trend in trends[0]['trends'] if trend['name'].startswith('#')]
return hashtags[:10] # Return top 10 hashtags
except Exception as e:
st.error(f"Error fetching trending hashtags: {str(e)}")
return []
def suggest_hashtags(topic: str, tone: str, use_trending: bool = False) -> List[str]:
"""Suggest relevant hashtags based on topic and tone."""
# Enhanced hashtag suggestions based on topic and tone
base_hashtags = {
"professional": ["#Business", "#Leadership", "#Innovation"],
"casual": ["#Life", "#Fun", "#Trending"],
"informative": ["#Learn", "#Tips", "#HowTo"],
"humorous": ["#Funny", "#LOL", "#Humor"],
"inspirational": ["#Motivation", "#Success", "#Growth"]
}
topic_hashtags = {
"tech": ["#Technology", "#TechNews", "#Innovation"],
"business": ["#Business", "#Entrepreneurship", "#Startup"],
"marketing": ["#Marketing", "#DigitalMarketing", "#SocialMedia"],
"education": ["#Education", "#Learning", "#Knowledge"],
"health": ["#Health", "#Wellness", "#Fitness"]
}
# Combine base and topic hashtags
suggested = base_hashtags.get(tone.lower(), []) + topic_hashtags.get(topic.lower(), [])
# Add trending hashtags if requested
if use_trending and is_authenticated():
trending = get_trending_hashtags()
suggested = list(set(suggested + trending))
return list(set(suggested))[:5] # Return unique hashtags, max 5
def get_optimal_posting_time() -> Dict[str, Any]:
"""Get optimal posting time based on follower activity."""
try:
if not is_authenticated():
return {
"time": datetime.now().strftime("%H:%M"),
"day": datetime.now().strftime("%A"),
"confidence": "low"
}
# Get API client
api = create_twitter_api()
# Get user timeline
tweets = api.user_timeline(count=100)
# Analyze engagement by hour and day
hour_engagement = {}
day_engagement = {}
for tweet in tweets:
hour = tweet.created_at.hour
day = tweet.created_at.strftime("%A")
engagement = tweet.favorite_count + tweet.retweet_count
if hour not in hour_engagement:
hour_engagement[hour] = []
hour_engagement[hour].append(engagement)
if day not in day_engagement:
day_engagement[day] = []
day_engagement[day].append(engagement)
# Calculate average engagement by hour and day
hour_avg_engagement = {h: sum(e)/len(e) for h, e in hour_engagement.items() if e}
day_avg_engagement = {d: sum(e)/len(e) for d, e in day_engagement.items() if e}
# Find optimal hour and day
optimal_hour = max(hour_avg_engagement.items(), key=lambda x: x[1])[0] if hour_avg_engagement else datetime.now().hour
optimal_day = max(day_avg_engagement.items(), key=lambda x: x[1])[0] if day_avg_engagement else datetime.now().strftime("%A")
# Calculate confidence
confidence = "high" if len(tweets) >= 50 else "medium" if len(tweets) >= 20 else "low"
return {
"time": f"{optimal_hour:02d}:00",
"day": optimal_day,
"confidence": confidence
}
except Exception as e:
st.error(f"Error calculating optimal posting time: {str(e)}")
return {
"time": datetime.now().strftime("%H:%M"),
"day": datetime.now().strftime("%A"),
"confidence": "low"
}
def analyze_audience() -> Dict[str, Any]:
"""Analyze Twitter audience for content targeting."""
try:
if not is_authenticated():
return {
"follower_count": 0,
"engagement_rate": 0,
"top_interests": [],
"active_hours": []
}
# Get API client
api = create_twitter_api()
# Get user info
user = api.verify_credentials()
# Get user timeline
tweets = api.user_timeline(count=100)
# Calculate engagement rate
total_engagement = sum(tweet.favorite_count + tweet.retweet_count for tweet in tweets)
engagement_rate = total_engagement / (len(tweets) * user.followers_count) if tweets and user.followers_count else 0
# Analyze active hours
hour_activity = {}
for tweet in tweets:
hour = tweet.created_at.hour
if hour not in hour_activity:
hour_activity[hour] = 0
hour_activity[hour] += 1
active_hours = sorted(hour_activity.items(), key=lambda x: x[1], reverse=True)[:3]
active_hours = [f"{hour:02d}:00" for hour, _ in active_hours]
# Get follower interests (simplified)
top_interests = ["Technology", "Business", "Marketing"] # Would require more complex analysis
return {
"follower_count": user.followers_count,
"engagement_rate": engagement_rate * 100, # Convert to percentage
"top_interests": top_interests,
"active_hours": active_hours
}
except Exception as e:
st.error(f"Error analyzing audience: {str(e)}")
return {
"follower_count": 0,
"engagement_rate": 0,
"top_interests": [],
"active_hours": []
}
def generate_tweet_variations(
hook: str,
target_audience: str,
tone: str,
call_to_action: str = "",
keywords: str = "",
length: str = "medium",
num_variations: int = 3,
use_ai: bool = True
) -> List[Dict]:
"""Generate multiple tweet variations with AI and Twitter data."""
# Enhanced prompt template with Twitter-specific guidance
prompt_template = f"""
Create {num_variations} engaging tweet variations with the following parameters:
- Hook/Topic: {hook}
- Target Audience: {target_audience}
- Tone: {tone}
- Call to Action: {call_to_action}
- Keywords: {keywords}
- Length: {length}
Each tweet should:
1. Start with an attention-grabbing hook
2. Include relevant hashtags (max 2-3)
3. Use appropriate emojis (1-2 max)
4. End with a clear call-to-action
5. Stay within Twitter's 280 character limit
6. Match the specified tone and audience
Format each tweet as a JSON object with:
- text: The tweet content
- hashtags: List of suggested hashtags
- emojis: List of suggested emojis
"""
if use_ai:
# Use AI to generate tweets
try:
response = llm_text_gen(prompt_template)
# Parse JSON response
try:
tweets = json.loads(response)
if not isinstance(tweets, list):
tweets = [tweets]
except:
# Handle non-JSON response by extracting tweet text
tweet_texts = re.findall(r'"text"\s*:\s*"([^"]+)"', response)
tweets = [{"text": text, "hashtags": extract_hashtags(text), "emojis": []} for text in tweet_texts]
# Ensure we have the requested number of variations
while len(tweets) < num_variations:
tweets.append({
"text": f"{hook} {call_to_action}",
"hashtags": suggest_hashtags(keywords, tone),
"emojis": []
})
# Add engagement score
for tweet in tweets:
tweet["engagement_score"] = random.randint(60, 95)
return tweets[:num_variations]
except Exception as e:
st.error(f"Error generating tweets with AI: {str(e)}")
# Fall back to template-based generation
# Template-based generation (fallback)
templates = [
"{emoji} {hook} {hashtags} {cta}",
"{hook} {emoji} {hashtags} {cta}",
"{hashtags} {hook} {emoji} {cta}"
]
tweets = []
for i in range(num_variations):
template = templates[i % len(templates)]
emoji_list = EMOJI_CATEGORIES.get(tone.capitalize(), EMOJI_CATEGORIES["Casual"])
emoji_str = random.choice(emoji_list)
hashtag_list = suggest_hashtags(keywords, tone)
hashtag_str = " ".join(hashtag_list[:2])
tweet_text = template.format(
emoji=emoji_str,
hook=hook,
hashtags=hashtag_str,
cta=call_to_action
)
tweets.append({
"text": tweet_text,
"hashtags": hashtag_list,
"emojis": [emoji_str],
"engagement_score": random.randint(60, 95)
})
return tweets
def post_tweet(tweet_text: str) -> Dict[str, Any]:
"""Post a tweet to Twitter."""
try:
if not is_authenticated():
return {"success": False, "error": "Not authenticated with Twitter"}
# Get API client
api = create_twitter_api()
# Post tweet
status = api.update_status(tweet_text)
return {
"success": True,
"tweet_id": status.id,
"created_at": status.created_at.isoformat()
}
except Exception as e:
return {"success": False, "error": str(e)}
def schedule_tweet(tweet_text: str, scheduled_time: datetime) -> Dict[str, Any]:
"""Schedule a tweet for later posting."""
try:
# Store scheduled tweet in session state
if "scheduled_tweets" not in st.session_state:
st.session_state.scheduled_tweets = []
tweet_id = f"scheduled_{int(time.time())}"
st.session_state.scheduled_tweets.append({
"id": tweet_id,
"text": tweet_text,
"scheduled_time": scheduled_time.isoformat(),
"status": "scheduled"
})
return {
"success": True,
"id": tweet_id,
"scheduled_time": scheduled_time.isoformat()
}
except Exception as e:
return {"success": False, "error": str(e)}
def enhanced_tweet_generator():
"""Enhanced Smart Tweet Generator with Twitter integration."""
st.title("âĻ Enhanced Tweet Generator")
st.markdown("Create and post engaging tweets with AI and Twitter data")
# Check if connected to Twitter
twitter_connected = is_authenticated()
# Twitter connection status
if twitter_connected:
user = st.session_state.twitter_user
st.success(f"Connected as @{user['screen_name']}")
# Show audience insights
with st.expander("Audience Insights", expanded=False):
audience = analyze_audience()
col1, col2, col3 = st.columns(3)
with col1:
st.metric("Followers", f"{audience['follower_count']:,}")
with col2:
st.metric("Engagement Rate", f"{audience['engagement_rate']:.2f}%")
with col3:
st.metric("Active Hours", ", ".join(audience['active_hours']) if audience['active_hours'] else "Unknown")
if audience['top_interests']:
st.markdown(f"**Top Interests:** {', '.join(audience['top_interests'])}")
else:
st.warning("Connect your Twitter account to access advanced features")
# Input section with improved UI
with st.expander("Tweet Parameters", expanded=True):
col1, col2 = st.columns(2)
with col1:
hook = st.text_area("Tweet Hook/Topic",
placeholder="Enter your main message or topic...",
help="The main message or topic of your tweet")
target_audience = st.selectbox(
"Target Audience",
["Professionals", "Students", "General"],
help="Select your target audience"
)
tone = st.radio(
"Tweet Tone",
["Professional", "Casual", "Informative", "Humorous", "Inspirational"],
horizontal=True,
help="Choose the tone for your tweet"
)
with col2:
call_to_action = st.text_input(
"Call to Action",
placeholder="e.g., Learn more, Follow us...",
help="What action do you want your audience to take?"
)
keywords = st.text_input(
"Keywords/Hashtags",
placeholder="Enter keywords separated by commas",
help="Keywords to include in your tweet"
)
length = st.select_slider(
"Tweet Length",
options=["short", "medium", "long"],
value="medium",
help="Choose your desired tweet length"
)
num_variations = st.slider(
"Number of Variations",
min_value=1,
max_value=5,
value=3,
help="How many tweet variations would you like to generate?"
)
# Advanced options
with st.expander("Advanced Options", expanded=False):
use_trending = st.checkbox("Include trending hashtags", value=False)
if twitter_connected:
# Get optimal posting time
optimal_time = get_optimal_posting_time()
st.markdown(f"""
**Optimal Posting Time:** {optimal_time['day']} at {optimal_time['time']}
(Confidence: {optimal_time['confidence']})
""")
# Generate button with loading state
if st.button("Generate Tweets", use_container_width=True):
with st.spinner("Generating tweet variations..."):
tweets = generate_tweet_variations(
hook, target_audience, tone,
call_to_action, keywords, length,
num_variations, use_ai=True
)
# Store tweets in session state
st.session_state.generated_tweets = tweets
# Display tweets
st.markdown("### Generated Tweets")
for i, tweet in enumerate(tweets):
with st.container():
st.markdown(f"""
Tweet Variation {i + 1}
{tweet['text']}
Score: {tweet['engagement_score']}%
{count_characters(tweet['text'])}/280 chars
""", unsafe_allow_html=True)
# Action buttons
col1, col2, col3 = st.columns(3)
with col1:
if st.button(f"Copy Tweet {i + 1}", key=f"copy_{i}"):
st.code(tweet['text'])
st.success("Tweet copied to clipboard!")
with col2:
if twitter_connected and st.button(f"Post Now {i + 1}", key=f"post_{i}"):
result = post_tweet(tweet['text'])
if result['success']:
st.success("Tweet posted successfully!")
else:
st.error(f"Error posting tweet: {result['error']}")
with col3:
if twitter_connected and st.button(f"Schedule {i + 1}", key=f"schedule_{i}"):
# Show scheduling options
st.session_state.tweet_to_schedule = tweet['text']
st.session_state.scheduling_tweet_index = i
st.experimental_rerun()
# Export options
st.markdown("### ðĨ Export Options")
col1, col2 = st.columns(2)
with col1:
if st.button("Export as JSON"):
st.download_button(
"Download JSON",
data=json.dumps(tweets, indent=2),
file_name=f"tweets_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json",
mime="application/json"
)
with col2:
if st.button("Copy All Tweets"):
tweet_texts = "\n\n".join(tweet["text"] for tweet in tweets)
st.code(tweet_texts)
# Handle tweet scheduling
if "tweet_to_schedule" in st.session_state:
st.markdown("### Schedule Tweet")
st.markdown(f"**Tweet to schedule:** {st.session_state.tweet_to_schedule}")
col1, col2 = st.columns(2)
with col1:
schedule_date = st.date_input("Date", value=datetime.now().date())
with col2:
schedule_time = st.time_input("Time", value=datetime.now().time())
scheduled_datetime = datetime.combine(schedule_date, schedule_time)
if st.button("Confirm Schedule"):
result = schedule_tweet(st.session_state.tweet_to_schedule, scheduled_datetime)
if result['success']:
st.success(f"Tweet scheduled for {scheduled_datetime.strftime('%Y-%m-%d %H:%M')}")
del st.session_state.tweet_to_schedule
del st.session_state.scheduling_tweet_index
else:
st.error(f"Error scheduling tweet: {result['error']}")
```
#### 2.2 Tweet Performance Predictor
Implement the Tweet Performance Predictor feature:
```python
# lib/ai_writers/twitter_writers/tweet_performance/performance_predictor.py
import streamlit as st
import pandas as pd
import numpy as np
from typing import Dict, List, Any
import json
from datetime import datetime, timedelta
import plotly.express as px
import plotly.graph_objects as go
from sklearn.ensemble import RandomForestRegressor
from sklearn.preprocessing import OneHotEncoder
import pickle
import os
from pathlib import Path
from ....integrations.twitter.auth import create_twitter_api, is_authenticated
# Constants
MODEL_DIR = Path(__file__).parent.parent.parent.parent.parent / 'models' / 'twitter'
MODEL_DIR.mkdir(exist_ok=True, parents=True)
MODEL_PATH = MODEL_DIR / 'performance_predictor.pkl'
def get_tweet_history() -> pd.DataFrame:
"""Get tweet history from Twitter API."""
try:
if not is_authenticated():
return pd.DataFrame()
# Get API client
api = create_twitter_api()
# Get user timeline
tweets = api.user_timeline(count=200, tweet_mode='extended')
# Create DataFrame
data = []
for tweet in tweets:
# Skip retweets
if hasattr(tweet, 'retweeted_status'):
continue
# Extract tweet data
tweet_data = {
'id': tweet.id,
'text': tweet.full_text,
'created_at': tweet.created_at,
'likes': tweet.favorite_count,
'retweets': tweet.retweet_count,
'replies': 0, # Not available in standard API
'impressions': 0, # Not available in standard API
'hashtags': [h['text'] for h in tweet.entities.get('hashtags', [])],
'mentions': [m['screen_name'] for m in tweet.entities.get('user_mentions', [])],
'urls': [u['expanded_url'] for u in tweet.entities.get('urls', [])],
'media': 1 if 'media' in tweet.entities else 0,
'hour': tweet.created_at.hour,
'day': tweet.created_at.weekday(),
'length': len(tweet.full_text)
}
# Calculate engagement
tweet_data['engagement'] = tweet_data['likes'] + tweet_data['retweets']
data.append(tweet_data)
# Create DataFrame
df = pd.DataFrame(data)
return df
except Exception as e:
st.error(f"Error fetching tweet history: {str(e)}")
return pd.DataFrame()
def train_performance_model(df: pd.DataFrame) -> Any:
"""Train a model to predict tweet performance."""
try:
if df.empty:
return None
# Feature engineering
X = pd.DataFrame({
'hour': df['hour'],
'day': df['day'],
'length': df['length'],
'hashtag_count': df['hashtags'].apply(len),
'mention_count': df['mentions'].apply(len),
'url_count': df['urls'].apply(len),
'has_media': df['media']
})
# Target variable
y = df['engagement']
# Train model
model = RandomForestRegressor(n_estimators=100, random_state=42)
model.fit(X, y)
# Save model
with open(MODEL_PATH, 'wb') as f:
pickle.dump(model, f)
return model
except Exception as e:
st.error(f"Error training model: {str(e)}")
return None
def load_performance_model() -> Any:
"""Load the performance prediction model."""
try:
if MODEL_PATH.exists():
with open(MODEL_PATH, 'rb') as f:
model = pickle.load(f)
return model
else:
return None
except Exception as e:
st.error(f"Error loading model: {str(e)}")
return None
def predict_performance(
tweet_text: str,
hour: int,
day: int,
has_media: bool = False
) -> Dict[str, Any]:
"""Predict tweet performance."""
try:
# Load model
model = load_performance_model()
if model is None:
# No model available, use heuristics
return predict_performance_heuristic(tweet_text, hour, day, has_media)
# Feature extraction
hashtags = len([w for w in tweet_text.split() if w.startswith('#')])
mentions = len([w for w in tweet_text.split() if w.startswith('@')])
urls = len([w for w in tweet_text.split() if w.startswith('http')])
length = len(tweet_text)
# Create feature vector
X = pd.DataFrame({
'hour': [hour],
'day': [day],
'length': [length],
'hashtag_count': [hashtags],
'mention_count': [mentions],
'url_count': [urls],
'has_media': [1 if has_media else 0]
})
# Make prediction
engagement = model.predict(X)[0]
# Calculate confidence
confidence = "high" if model.n_estimators >= 100 else "medium"
return {
"predicted_engagement": engagement,
"confidence": confidence,
"factors": {
"time_factor": get_time_factor(hour, day),
"content_factor": get_content_factor(tweet_text),
"media_factor": 1.5 if has_media else 1.0
}
}
except Exception as e:
st.error(f"Error predicting performance: {str(e)}")
return predict_performance_heuristic(tweet_text, hour, day, has_media)
def predict_performance_heuristic(
tweet_text: str,
hour: int,
day: int,
has_media: bool = False
) -> Dict[str, Any]:
"""Predict tweet performance using heuristics."""
# Time factor (higher for business hours on weekdays)
time_factor = get_time_factor(hour, day)
# Content factor
content_factor = get_content_factor(tweet_text)
# Media factor
media_factor = 1.5 if has_media else 1.0
# Base engagement (arbitrary value)
base_engagement = 10
# Calculate predicted engagement
predicted_engagement = base_engagement * time_factor * content_factor * media_factor
return {
"predicted_engagement": predicted_engagement,
"confidence": "low",
"factors": {
"time_factor": time_factor,
"content_factor": content_factor,
"media_factor": media_factor
}
}
def get_time_factor(hour: int, day: int) -> float:
"""Calculate time factor for engagement prediction."""
# Weekday factor (higher for weekdays)
weekday_factor = 1.0 if day < 5 else 0.8
# Hour factor (higher for business hours)
if 9 <= hour <= 17:
hour_factor = 1.0
elif 7 <= hour <= 8 or 18 <= hour <= 21:
hour_factor = 0.8
else:
hour_factor = 0.6
return weekday_factor * hour_factor
def get_content_factor(tweet_text: str) -> float:
"""Calculate content factor for engagement prediction."""
# Length factor (higher for medium-length tweets)
length = len(tweet_text)
if 70 <= length <= 140:
length_factor = 1.0
elif length < 70:
length_factor = 0.8
else:
length_factor = 0.9
# Hashtag factor (higher for 1-2 hashtags)
hashtags = len([w for w in tweet_text.split() if w.startswith('#')])
if 1 <= hashtags <= 2:
hashtag_factor = 1.0
elif hashtags == 0:
hashtag_factor = 0.8
else:
hashtag_factor = 0.7 # Too many hashtags
# Question factor (higher for tweets with questions)
question_factor = 1.2 if '?' in tweet_text else 1.0
# Call to action factor
cta_words = ['follow', 'retweet', 'like', 'share', 'comment', 'check', 'click', 'read']
cta_factor = 1.1 if any(word in tweet_text.lower() for word in cta_words) else 1.0
return length_factor * hashtag_factor * question_factor * cta_factor
def get_best_posting_times() -> List[Dict[str, Any]]:
"""Get best posting times based on historical data."""
try:
# Get tweet history
df = get_tweet_history()
if df.empty:
# Return default recommendations
return [
{"day": "Monday", "hour": 12, "confidence": "low"},
{"day": "Wednesday", "hour": 15, "confidence": "low"},
{"day": "Friday", "hour": 10, "confidence": "low"}
]
# Group by day and hour
df['day_name'] = df['created_at'].dt.day_name()
engagement_by_time = df.groupby(['day_name', 'hour'])['engagement'].mean().reset_index()
# Sort by engagement
engagement_by_time = engagement_by_time.sort_values('engagement', ascending=False)
# Get top 3 times
top_times = engagement_by_time.head(3)
# Format results
results = []
for _, row in top_times.iterrows():
results.append({
"day": row['day_name'],
"hour": row['hour'],
"confidence": "high" if len(df) >= 100 else "medium" if len(df) >= 50 else "low"
})
return results
except Exception as e:
st.error(f"Error getting best posting times: {str(e)}")
return [
{"day": "Monday", "hour": 12, "confidence": "low"},
{"day": "Wednesday", "hour": 15, "confidence": "low"},
{"day": "Friday", "hour": 10, "confidence": "low"}
]
def render_performance_predictor():
"""Render the Tweet Performance Predictor UI."""
st.title("ð Tweet Performance Predictor")
st.markdown("Predict engagement and find the best time to post your tweets")
# Check if connected to Twitter
twitter_connected = is_authenticated()
if twitter_connected:
user = st.session_state.twitter_user
st.success(f"Connected as @{user['screen_name']}")
# Get tweet history
with st.spinner("Analyzing your tweet history..."):
df = get_tweet_history()
if not df.empty:
# Train model if needed
if not MODEL_PATH.exists():
with st.spinner("Training prediction model..."):
train_performance_model(df)
# Show historical performance
with st.expander("Your Tweet Performance", expanded=True):
# Engagement over time
st.markdown("#### Engagement Over Time")
fig = px.line(
df.sort_values('created_at'),
x='created_at',
y='engagement',
title="Tweet Engagement History"
)
st.plotly_chart(fig, use_container_width=True)
# Engagement by day and hour
st.markdown("#### Engagement by Day and Hour")
df['day_name'] = df['created_at'].dt.day_name()
pivot = df.pivot_table(
index='day_name',
columns='hour',
values='engagement',
aggfunc='mean'
)
fig = px.imshow(
pivot,
labels=dict(x="Hour of Day", y="Day of Week", color="Avg. Engagement"),
x=pivot.columns,
y=pivot.index,
color_continuous_scale="Viridis"
)
st.plotly_chart(fig, use_container_width=True)
# Content analysis
st.markdown("#### Content Analysis")
col1, col2 = st.columns(2)
with col1:
# Engagement by tweet length
df['length_bin'] = pd.cut(df['length'], bins=[0, 70, 140, 210, 280], labels=['0-70', '71-140', '141-210', '211-280'])
length_engagement = df.groupby('length_bin')['engagement'].mean().reset_index()
fig = px.bar(
length_engagement,
x='length_bin',
y='engagement',
title="Engagement by Tweet Length"
)
st.plotly_chart(fig, use_container_width=True)
with col2:
# Engagement by hashtag count
df['hashtag_count'] = df['hashtags'].apply(len)
df['hashtag_bin'] = pd.cut(df['hashtag_count'], bins=[-1, 0, 1, 2, 10], labels=['0', '1', '2', '3+'])
hashtag_engagement = df.groupby('hashtag_bin')['engagement'].mean().reset_index()
fig = px.bar(
hashtag_engagement,
x='hashtag_bin',
y='engagement',
title="Engagement by Hashtag Count"
)
st.plotly_chart(fig, use_container_width=True)
# Best posting times
best_times = get_best_posting_times()
st.markdown("### ð Best Times to Post")
for i, time in enumerate(best_times):
st.markdown(f"**{i+1}.** {time['day']} at {time['hour']}:00 (Confidence: {time['confidence']})")
else:
st.warning("Connect your Twitter account to access performance prediction features")
# Show demo data
st.markdown("### Demo Data")
st.markdown("Here's how the performance predictor works with sample data:")
# Sample engagement chart
dates = pd.date_range(start='2023-01-01', periods=30)
engagements = np.random.normal(loc=20, scale=10, size=30)
engagements = np.abs(engagements) # Make all values positive
df = pd.DataFrame({
'date': dates,
'engagement': engagements
})
fig = px.line(
df,
x='date',
y='engagement',
title="Sample Tweet Engagement History"
)
st.plotly_chart(fig, use_container_width=True)
# Tweet input for prediction
st.markdown("### Predict Tweet Performance")
tweet_text = st.text_area(
"Enter your tweet",
placeholder="Type your tweet here...",
max_chars=280
)
col1, col2, col3 = st.columns(3)
with col1:
day = st.selectbox(
"Day of week",
["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
)
day_num = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"].index(day)
with col2:
hour = st.slider("Hour of day", 0, 23, 12)
with col3:
has_media = st.checkbox("Include media", value=False)
if st.button("Predict Performance", use_container_width=True):
if tweet_text:
with st.spinner("Predicting performance..."):
prediction = predict_performance(tweet_text, hour, day_num, has_media)
# Display prediction
st.markdown("### Prediction Results")
col1, col2 = st.columns(2)
with col1:
st.metric(
"Predicted Engagement",
f"{prediction['predicted_engagement']:.1f}",
delta=None
)
with col2:
st.markdown(f"**Confidence:** {prediction['confidence'].title()}")
# Factors affecting prediction
st.markdown("#### Factors Affecting Prediction")
factors = prediction['factors']
factor_df = pd.DataFrame({
'Factor': ['Time of posting', 'Content quality', 'Media inclusion'],
'Impact': [
factors['time_factor'],
factors['content_factor'],
factors['media_factor']
]
})
fig = px.bar(
factor_df,
x='Factor',
y='Impact',
title="Impact Factors"
)
st.plotly_chart(fig, use_container_width=True)
# Recommendations
st.markdown("#### Recommendations to Improve Performance")
recommendations = []
if factors['time_factor'] < 0.9:
recommendations.append("Consider posting during weekdays between 9 AM and 5 PM for better engagement.")
if factors['content_factor'] < 0.9:
if len(tweet_text) > 140:
recommendations.append("Try shortening your tweet to 70-140 characters for optimal engagement.")
hashtags = len([w for w in tweet_text.split() if w.startswith('#')])
if hashtags == 0:
recommendations.append("Add 1-2 relevant hashtags to increase visibility.")
elif hashtags > 2:
recommendations.append("Reduce the number of hashtags to 1-2 for better engagement.")
if '?' not in tweet_text:
recommendations.append("Consider adding a question to encourage responses.")
cta_words = ['follow', 'retweet', 'like', 'share', 'comment', 'check', 'click', 'read']
if not any(word in tweet_text.lower() for word in cta_words):
recommendations.append("Add a clear call-to-action to encourage engagement.")
if not has_media:
recommendations.append("Including an image or video could increase engagement by up to 50%.")
if recommendations:
for rec in recommendations:
st.info(rec)
else:
st.success("Your tweet is well-optimized for engagement!")
else:
st.error("Please enter a tweet to predict performance.")
```
### Phase 3: Content Strategy Tools
#### 3.1 Content Calendar Generator
Implement the Content Calendar Generator feature:
```python
# lib/ai_writers/twitter_writers/content_strategy/calendar_generator.py
import streamlit as st
import pandas as pd
import numpy as np
from typing import Dict, List, Any
import json
from datetime import datetime, timedelta
import plotly.express as px
import plotly.graph_objects as go
import calendar
import random
from io import BytesIO
from ....integrations.twitter.auth import create_twitter_api, is_authenticated
from ....gpt_providers.text_generation.main_text_generation import llm_text_gen
def generate_content_themes(industry: str, num_themes: int = 5) -> List[str]:
"""Generate content themes based on industry."""
# Predefined themes by industry
industry_themes = {
"Technology": [
"Product Updates", "Tech Tips", "Industry News", "Customer Success Stories",
"Behind the Scenes", "Tech Trends", "How-To Guides", "Tech Humor",
"Industry Events", "Q&A Sessions"
],
"Marketing": [
"Marketing Tips", "Case Studies", "Industry Trends", "Tool Recommendations",
"Marketing Metrics", "Campaign Spotlights", "Marketing Humor", "Expert Interviews",
"Marketing Events", "Strategy Insights"
],
"Education": [
"Learning Resources", "Student Spotlights", "Education News", "Teaching Tips",
"Research Highlights", "Campus Events", "Educational Humor", "Alumni Stories",
"Faculty Spotlights", "Educational Trends"
],
"Health": [
"Health Tips", "Wellness Advice", "Medical News", "Patient Stories",
"Research Updates", "Health Myths Debunked", "Nutrition Tips", "Exercise Guides",
"Mental Health Awareness", "Healthcare Innovations"
],
"Finance": [
"Financial Tips", "Market Updates", "Investment Strategies", "Financial Education",
"Economic News", "Savings Guides", "Financial Planning", "Success Stories",
"Industry Trends", "Q&A Sessions"
]
}
# Get themes for the selected industry or use AI to generate them
if industry in industry_themes:
themes = industry_themes[industry]
return random.sample(themes, min(num_themes, len(themes)))
else:
# Use AI to generate themes
prompt = f"""
Generate {num_themes} content themes for Twitter posts in the {industry} industry.
Each theme should be a short phrase (2-4 words) that describes a category of content.
Return the themes as a JSON array of strings.
"""
try:
response = llm_text_gen(prompt)
# Try to parse as JSON
try:
themes = json.loads(response)
if isinstance(themes, list):
return themes[:num_themes]
except:
# Extract themes using regex
import re
themes = re.findall(r'"([^"]+)"', response)
return themes[:num_themes]
except Exception as e:
st.error(f"Error generating themes: {str(e)}")
# Return generic themes
generic_themes = [
"Industry News", "Tips & Tricks", "Behind the Scenes",
"Customer Stories", "Product Highlights"
]
return generic_themes[:num_themes]
def generate_content_ideas(theme: str, industry: str, num_ideas: int = 3) -> List[str]:
"""Generate content ideas for a theme."""
# Use AI to generate content ideas
prompt = f"""
Generate {num_ideas} specific Twitter post ideas for the theme "{theme}" in the {industry} industry.
Each idea should be a brief description (not the actual tweet) of what the post will be about.
Keep each idea under 100 characters.
Return the ideas as a JSON array of strings.
"""
try:
response = llm_text_gen(prompt)
# Try to parse as JSON
try:
ideas = json.loads(response)
if isinstance(ideas, list):
return ideas[:num_ideas]
except:
# Extract ideas using regex
import re
ideas = re.findall(r'"([^"]+)"', response)
return ideas[:num_ideas] if ideas else [f"{theme} idea {i+1}" for i in range(num_ideas)]
except Exception as e:
st.error(f"Error generating ideas: {str(e)}")
# Return generic ideas
return [f"{theme} idea {i+1}" for i in range(num_ideas)]
def create_content_calendar(
start_date: datetime,
end_date: datetime,
posts_per_week: int,
themes: List[str],
industry: str
) -> pd.DataFrame:
"""Create a content calendar with themes and ideas."""
# Calculate date range
date_range = pd.date_range(start=start_date, end=end_date)
# Filter for weekdays if needed
if posts_per_week <= 5:
date_range = date_range[date_range.dayofweek < 5] # Monday to Friday
# Calculate posting frequency
if posts_per_week < 5:
# Select specific days
days_to_post = random.sample(range(5), posts_per_week) # Random weekdays
date_range = date_range[date_range.dayofweek.isin(days_to_post)]
# Limit to the requested frequency
posting_dates = []
current_week = -1
posts_this_week = 0
for date in date_range:
week_num = date.isocalendar()[1]
if week_num != current_week:
current_week = week_num
posts_this_week = 0
if posts_this_week < posts_per_week:
posting_dates.append(date)
posts_this_week += 1
# Create calendar dataframe
calendar_data = []
for date in posting_dates:
# Assign theme (rotate through themes)
theme_index = len(calendar_data) % len(themes)
theme = themes[theme_index]
# Generate content ideas
ideas = generate_content_ideas(theme, industry, num_ideas=1)
calendar_data.append({
'date': date,
'day': date.day_name(),
'week': date.isocalendar()[1],
'theme': theme,
'content_idea': ideas[0] if ideas else "",
'status': 'Planned'
})
# Create DataFrame
df = pd.DataFrame(calendar_data)
return df
def export_calendar_to_excel(df: pd.DataFrame) -> BytesIO:
"""Export content calendar to Excel."""
# Create Excel writer
output = BytesIO()
writer = pd.ExcelWriter(output, engine='xlsxwriter')
# Write DataFrame to Excel
df.to_excel(writer, sheet_name='Content Calendar', index=False)
# Get workbook and worksheet
workbook = writer.book
worksheet = writer.sheets['Content Calendar']
# Add formats
header_format = workbook.add_format({
'bold': True,
'text_wrap': True,
'valign': 'top',
'fg_color': '#D7E4BC',
'border': 1
})
date_format = workbook.add_format({
'num_format': 'yyyy-mm-dd',
'border': 1
})
cell_format = workbook.add_format({
'text_wrap': True,
'border': 1
})
# Write headers with format
for col_num, value in enumerate(df.columns.values):
worksheet.write(0, col_num, value, header_format)
# Format columns
worksheet.set_column('A:A', 12, date_format) # Date column
worksheet.set_column('B:B', 10, cell_format) # Day column
worksheet.set_column('C:C', 8, cell_format) # Week column
worksheet.set_column('D:D', 15, cell_format) # Theme column
worksheet.set_column('E:E', 40, cell_format) # Content idea column
worksheet.set_column('F:F', 10, cell_format) # Status column
# Close the writer
writer.close()
# Return the Excel file
output.seek(0)
return output
def render_calendar_generator():
"""Render the Content Calendar Generator UI."""
st.title("ðïļ Content Calendar Generator")
st.markdown("Create a strategic Twitter content calendar with themes and ideas")
# Check if connected to Twitter
twitter_connected = is_authenticated()
if twitter_connected:
user = st.session_state.twitter_user
st.success(f"Connected as @{user['screen_name']}")
# Calendar settings
st.markdown("### Calendar Settings")
col1, col2 = st.columns(2)
with col1:
start_date = st.date_input("Start Date", value=datetime.now().date())
industry = st.selectbox(
"Industry",
["Technology", "Marketing", "Education", "Health", "Finance", "Other"],
index=0
)
if industry == "Other":
industry = st.text_input("Specify Industry")
with col2:
duration = st.selectbox(
"Duration",
["1 week", "2 weeks", "1 month", "3 months"],
index=2
)
# Calculate end date based on duration
if duration == "1 week":
end_date = start_date + timedelta(days=6)
elif duration == "2 weeks":
end_date = start_date + timedelta(days=13)
elif duration == "1 month":
end_date = start_date + timedelta(days=29)
else: # 3 months
end_date = start_date + timedelta(days=89)
posts_per_week = st.slider(
"Posts per Week",
min_value=1,
max_value=7,
value=3
)
# Theme settings
st.markdown("### Content Themes")
num_themes = st.slider(
"Number of Themes",
min_value=1,
max_value=10,
value=5
)
# Generate themes button
if st.button("Generate Themes", key="generate_themes"):
with st.spinner("Generating content themes..."):
themes = generate_content_themes(industry, num_themes)
st.session_state.content_themes = themes
# Display and edit themes
if "content_themes" in st.session_state:
themes = st.session_state.content_themes
# Create columns for themes
cols = st.columns(min(5, len(themes)))
# Display each theme in a column
for i, theme in enumerate(themes):
col_index = i % len(cols)
with cols[col_index]:
new_theme = st.text_input(f"Theme {i+1}", value=theme, key=f"theme_{i}")
themes[i] = new_theme
st.session_state.content_themes = themes
else:
st.info("Click 'Generate Themes' to create content themes for your calendar")
# Generate calendar button
if st.button("Generate Calendar", use_container_width=True):
if "content_themes" in st.session_state and st.session_state.content_themes:
with st.spinner("Creating your content calendar..."):
# Create calendar
calendar_df = create_content_calendar(
start_date=start_date,
end_date=end_date,
posts_per_week=posts_per_week,
themes=st.session_state.content_themes,
industry=industry
)
# Store in session state
st.session_state.content_calendar = calendar_df
# Display calendar
st.markdown("### Your Content Calendar")
# Calendar view
calendar_view = st.radio(
"View",
["Table", "Calendar View"],
horizontal=True
)
if calendar_view == "Table":
# Display as table
st.dataframe(
calendar_df,
column_config={
"date": st.column_config.DateColumn("Date", format="YYYY-MM-DD"),
"day": "Day",
"week": "Week",
"theme": "Theme",
"content_idea": st.column_config.TextColumn("Content Idea", width="large"),
"status": "Status"
},
hide_index=True
)
else:
# Display as calendar
# Group by week
weeks = calendar_df.groupby('week')
for week_num, week_data in weeks:
st.markdown(f"#### Week {week_num}")
# Create a row for each day of the week
days = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
cols = st.columns(7)
# Add day headers
for i, day in enumerate(days):
with cols[i]:
st.markdown(f"**{day[:3]}**")
# Add content for each day
for i, day in enumerate(days):
with cols[i]:
# Find posts for this day
day_posts = week_data[week_data['day'] == day]
if not day_posts.empty:
for _, post in day_posts.iterrows():
st.markdown(f"""
{post['date'].strftime('%Y-%m-%d')}
{post['theme']}
{post['content_idea']}
""", unsafe_allow_html=True)
else:
st.markdown("â")
st.markdown("---")
# Export options
st.markdown("### Export Options")
col1, col2 = st.columns(2)
with col1:
# Export as Excel
excel_file = export_calendar_to_excel(calendar_df)
st.download_button(
"Download Excel Calendar",
data=excel_file,
file_name=f"twitter_content_calendar_{datetime.now().strftime('%Y%m%d')}.xlsx",
mime="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
)
with col2:
# Export as CSV
csv = calendar_df.to_csv(index=False)
st.download_button(
"Download CSV Calendar",
data=csv,
file_name=f"twitter_content_calendar_{datetime.now().strftime('%Y%m%d')}.csv",
mime="text/csv"
)
else:
st.error("Please generate themes first")
```
#### 3.2 Hashtag Strategy Manager
Implement the Hashtag Strategy Manager feature:
```python
# lib/ai_writers/twitter_writers/content_strategy/hashtag_manager.py
import streamlit as st
import pandas as pd
import numpy as np
from typing import Dict, List, Any
import json
from datetime import datetime, timedelta
import plotly.express as px
import plotly.graph_objects as go
import re
import tweepy
from collections import Counter
from ....integrations.twitter.auth import create_twitter_api, create_twitter_client, is_authenticated
from ....gpt_providers.text_generation.main_text_generation import llm_text_gen
def get_trending_hashtags(location_id: int = 1) -> List[Dict[str, Any]]:
"""Get trending hashtags from Twitter API."""
try:
if not is_authenticated():
return []
# Get API client
api = create_twitter_api()
# Get trending topics
trends = api.get_place_trends(location_id)
# Extract hashtags
hashtags = [
{
"name": trend["name"],
"volume": trend.get("tweet_volume", 0),
"is_hashtag": trend["name"].startswith("#")
}
for trend in trends[0]["trends"]
]
# Sort by volume
hashtags.sort(key=lambda x: x["volume"] if x["volume"] else 0, reverse=True)
return hashtags
except Exception as e:
st.error(f"Error fetching trending hashtags: {str(e)}")
return []
def get_trending_locations() -> List[Dict[str, Any]]:
"""Get available trending locations."""
try:
if not is_authenticated():
return []
# Get API client
api = create_twitter_api()
# Get available trend locations
locations = api.available_trends()
# Format locations
formatted_locations = [
{
"woeid": location["woeid"],
"name": location["name"],
"country": location.get("countryCode", "")
}
for location in locations
]
# Sort by name
formatted_locations.sort(key=lambda x: x["name"])
return formatted_locations
except Exception as e:
st.error(f"Error fetching trending locations: {str(e)}")
return []
def analyze_hashtag(hashtag: str) -> Dict[str, Any]:
"""Analyze a specific hashtag."""
try:
if not is_authenticated():
return {
"volume": 0,
"sentiment": "neutral",
"related_hashtags": [],
"sample_tweets": []
}
# Clean hashtag
if not hashtag.startswith("#"):
hashtag = f"#{hashtag}"
# Get API client
api = create_twitter_api()
client = create_twitter_client()
# Search for tweets with the hashtag
tweets = api.search_tweets(q=hashtag, count=100, tweet_mode="extended")
# Extract data
sample_tweets = []
all_hashtags = []
sentiment_scores = []
for tweet in tweets:
# Skip retweets
if hasattr(tweet, "retweeted_status"):
continue
# Add to sample tweets
sample_tweets.append({
"text": tweet.full_text,
"created_at": tweet.created_at,
"likes": tweet.favorite_count,
"retweets": tweet.retweet_count
})
# Extract hashtags
tweet_hashtags = [h["text"] for h in tweet.entities.get("hashtags", [])]
all_hashtags.extend(tweet_hashtags)
# Simple sentiment analysis
text = tweet.full_text.lower()
positive_words = ["good", "great", "awesome", "excellent", "love", "happy", "best", "amazing"]
negative_words = ["bad", "terrible", "awful", "hate", "worst", "poor", "disappointing"]
positive_count = sum(1 for word in positive_words if word in text)
negative_count = sum(1 for word in negative_words if word in text)
if positive_count > negative_count:
sentiment_scores.append(1) # Positive
elif negative_count > positive_count:
sentiment_scores.append(-1) # Negative
else:
sentiment_scores.append(0) # Neutral
# Calculate related hashtags
hashtag_counts = Counter(all_hashtags)
related_hashtags = [
{"name": f"#{h}", "count": count}
for h, count in hashtag_counts.most_common(10)
if h.lower() != hashtag[1:].lower() # Exclude the analyzed hashtag
]
# Calculate sentiment
avg_sentiment = sum(sentiment_scores) / len(sentiment_scores) if sentiment_scores else 0
sentiment = "positive" if avg_sentiment > 0.2 else "negative" if avg_sentiment < -0.2 else "neutral"
# Get volume (approximate)
volume = len(tweets)
return {
"volume": volume,
"sentiment": sentiment,
"related_hashtags": related_hashtags,
"sample_tweets": sample_tweets[:5] # Return top 5 tweets
}
except Exception as e:
st.error(f"Error analyzing hashtag: {str(e)}")
return {
"volume": 0,
"sentiment": "neutral",
"related_hashtags": [],
"sample_tweets": []
}
def get_account_hashtags() -> Dict[str, int]:
"""Get hashtags used in the user's account."""
try:
if not is_authenticated():
return {}
# Get API client
api = create_twitter_api()
# Get user timeline
tweets = api.user_timeline(count=200, tweet_mode="extended")
# Extract hashtags
all_hashtags = []
for tweet in tweets:
# Skip retweets
if hasattr(tweet, "retweeted_status"):
continue
# Extract hashtags
tweet_hashtags = [h["text"] for h in tweet.entities.get("hashtags", [])]
all_hashtags.extend(tweet_hashtags)
# Count hashtags
hashtag_counts = Counter(all_hashtags)
return dict(hashtag_counts.most_common(20))
except Exception as e:
st.error(f"Error getting account hashtags: {str(e)}")
return {}
def generate_hashtag_recommendations(industry: str, topic: str) -> List[Dict[str, Any]]:
"""Generate hashtag recommendations based on industry and topic."""
# Use AI to generate recommendations
prompt = f"""
Generate 10 effective Twitter hashtags for the {industry} industry, specifically for content about {topic}.
For each hashtag, provide:
1. The hashtag itself (including the # symbol)
2. A brief description of why it's effective
3. An estimate of how popular it is (high, medium, or low)
Return the results as a JSON array of objects with the following structure:
[
{{
"hashtag": "#example",
"description": "Brief explanation of why this hashtag is effective",
"popularity": "high/medium/low"
}}
]
"""
try:
response = llm_text_gen(prompt)
# Try to parse as JSON
try:
recommendations = json.loads(response)
if isinstance(recommendations, list):
return recommendations
except:
# Extract using regex
hashtags = re.findall(r'#\w+', response)
return [
{
"hashtag": hashtag,
"description": f"Recommended hashtag for {topic} in {industry}",
"popularity": "medium"
}
for hashtag in hashtags[:10]
]
except Exception as e:
st.error(f"Error generating hashtag recommendations: {str(e)}")
# Return generic recommendations
return [
{
"hashtag": f"#{industry.lower()}",
"description": f"Main industry hashtag",
"popularity": "high"
},
{
"hashtag": f"#{topic.lower().replace(' ', '')}",
"description": f"Topic-specific hashtag",
"popularity": "medium"
}
]
def render_hashtag_manager():
"""Render the Hashtag Strategy Manager UI."""
st.title("#ïļâĢ Hashtag Strategy Manager")
st.markdown("Research and manage trending hashtags for better reach")
# Check if connected to Twitter
twitter_connected = is_authenticated()
if twitter_connected:
user = st.session_state.twitter_user
st.success(f"Connected as @{user['screen_name']}")
# Create tabs
tab1, tab2, tab3 = st.tabs(["Trending Hashtags", "Hashtag Analysis", "Hashtag Strategy"])
with tab1:
st.markdown("### Trending Hashtags")
# Get trending locations
if twitter_connected:
locations = get_trending_locations()
if locations:
# Create location selector
location_options = {f"{loc['name']}, {loc['country']}": loc["woeid"] for loc in locations}
selected_location = st.selectbox(
"Select Location",
options=list(location_options.keys()),
index=0
)
location_id = location_options[selected_location]
# Get trending hashtags
if st.button("Get Trending Hashtags", use_container_width=True):
with st.spinner("Fetching trending hashtags..."):
trends = get_trending_hashtags(location_id)
if trends:
# Filter hashtags
show_all = st.checkbox("Show all trending topics (not just hashtags)")
if not show_all:
trends = [t for t in trends if t["is_hashtag"]]
# Create DataFrame
trends_df = pd.DataFrame(trends)
# Display trends
st.dataframe(
trends_df,
column_config={
"name": "Trend",
"volume": st.column_config.NumberColumn("Volume", format="%d"),
"is_hashtag": "Is Hashtag"
},
hide_index=True
)
# Visualize top trends
st.markdown("#### Top Trends by Volume")
# Filter out trends with no volume data
volume_trends = [t for t in trends if t["volume"]]
if volume_trends:
# Sort by volume
volume_trends.sort(key=lambda x: x["volume"], reverse=True)
# Take top 10
top_trends = volume_trends[:10]
# Create DataFrame
top_df = pd.DataFrame(top_trends)
# Create bar chart
fig = px.bar(
top_df,
x="name",
y="volume",
title="Top Trending Topics by Volume",
labels={"name": "Trend", "volume": "Tweet Volume"}
)
st.plotly_chart(fig, use_container_width=True)
else:
st.info("No volume data available for trending topics")
else:
st.warning("No trending topics found")
else:
st.warning("Could not fetch trending locations")
else:
st.warning("Connect your Twitter account to access trending hashtags")
# Show sample data
st.markdown("### Sample Trending Hashtags")
sample_trends = [
{"name": "#AI", "volume": 125000, "is_hashtag": True},
{"name": "#MachineLearning", "volume": 78000, "is_hashtag": True},
{"name": "#DataScience", "volume": 65000, "is_hashtag": True},
{"name": "#Python", "volume": 52000, "is_hashtag": True},
{"name": "#BigData", "volume": 48000, "is_hashtag": True}
]
# Create DataFrame
sample_df = pd.DataFrame(sample_trends)
# Display trends
st.dataframe(
sample_df,
column_config={
"name": "Trend",
"volume": st.column_config.NumberColumn("Volume", format="%d"),
"is_hashtag": "Is Hashtag"
},
hide_index=True
)
with tab2:
st.markdown("### Hashtag Analysis")
# Hashtag input
hashtag = st.text_input(
"Enter a hashtag to analyze",
placeholder="e.g., #AI"
)
if hashtag:
# Clean hashtag
if not hashtag.startswith("#"):
hashtag = f"#{hashtag}"
# Analyze button
if st.button("Analyze Hashtag", use_container_width=True):
with st.spinner(f"Analyzing {hashtag}..."):
if twitter_connected:
# Get hashtag analysis
analysis = analyze_hashtag(hashtag)
# Display results
st.markdown(f"#### Analysis Results for {hashtag}")
# Metrics
col1, col2, col3 = st.columns(3)
with col1:
st.metric("Volume", f"{analysis['volume']:,}")
with col2:
sentiment = analysis["sentiment"].title()
sentiment_color = {
"Positive": "green",
"Neutral": "blue",
"Negative": "red"
}.get(sentiment, "blue")
st.markdown(f"""
""", unsafe_allow_html=True)
with col3:
related_count = len(analysis["related_hashtags"])
st.metric("Related Hashtags", related_count)
# Related hashtags
if analysis["related_hashtags"]:
st.markdown("#### Related Hashtags")
# Create DataFrame
related_df = pd.DataFrame(analysis["related_hashtags"])
# Display related hashtags
st.dataframe(
related_df,
column_config={
"name": "Hashtag",
"count": st.column_config.NumberColumn("Count", format="%d")
},
hide_index=True
)
# Sample tweets
if analysis["sample_tweets"]:
st.markdown("#### Sample Tweets")
for tweet in analysis["sample_tweets"]:
st.markdown(f"""
{tweet['text']}
{tweet['created_at'].strftime('%Y-%m-%d %H:%M')}
âĪïļ {tweet['likes']} | ð {tweet['retweets']}
""", unsafe_allow_html=True)
else:
st.warning("Connect your Twitter account to analyze hashtags")
# Show sample analysis
st.markdown(f"#### Sample Analysis for {hashtag}")
# Metrics
col1, col2, col3 = st.columns(3)
with col1:
st.metric("Volume", "5,280")
with col2:
st.markdown("""
""", unsafe_allow_html=True)
with col3:
st.metric("Related Hashtags", 8)
# Related hashtags
st.markdown("#### Related Hashtags")
sample_related = [
{"name": "#MachineLearning", "count": 45},
{"name": "#DataScience", "count": 32},
{"name": "#Python", "count": 28},
{"name": "#DeepLearning", "count": 21},
{"name": "#BigData", "count": 18}
]
# Create DataFrame
related_df = pd.DataFrame(sample_related)
# Display related hashtags
st.dataframe(
related_df,
column_config={
"name": "Hashtag",
"count": st.column_config.NumberColumn("Count", format="%d")
},
hide_index=True
)
# Account hashtag analysis
st.markdown("### Your Hashtag Usage")
if twitter_connected:
if st.button("Analyze My Hashtags", use_container_width=True):
with st.spinner("Analyzing your hashtag usage..."):
# Get account hashtags
account_hashtags = get_account_hashtags()
if account_hashtags:
# Create DataFrame
hashtags_df = pd.DataFrame([
{"hashtag": f"#{h}", "count": count}
for h, count in account_hashtags.items()
])
# Display hashtags
st.dataframe(
hashtags_df,
column_config={
"hashtag": "Hashtag",
"count": st.column_config.NumberColumn("Count", format="%d")
},
hide_index=True
)
# Visualize top hashtags
st.markdown("#### Your Top Hashtags")
# Create bar chart
fig = px.bar(
hashtags_df.head(10),
x="hashtag",
y="count",
title="Your Most Used Hashtags",
labels={"hashtag": "Hashtag", "count": "Usage Count"}
)
st.plotly_chart(fig, use_container_width=True)
else:
st.info("No hashtags found in your recent tweets")
else:
st.warning("Connect your Twitter account to analyze your hashtag usage")
with tab3:
st.markdown("### Hashtag Strategy")
# Industry and topic inputs
col1, col2 = st.columns(2)
with col1:
industry = st.selectbox(
"Industry",
["Technology", "Marketing", "Education", "Health", "Finance", "Other"],
index=0
)
if industry == "Other":
industry = st.text_input("Specify Industry")
with col2:
topic = st.text_input(
"Content Topic",
placeholder="e.g., Artificial Intelligence"
)
# Generate recommendations
if st.button("Generate Hashtag Recommendations", use_container_width=True):
if topic:
with st.spinner("Generating hashtag recommendations..."):
# Get recommendations
recommendations = generate_hashtag_recommendations(industry, topic)
if recommendations:
# Display recommendations
st.markdown("#### Recommended Hashtags")
for rec in recommendations:
# Determine color based on popularity
color = {
"high": "#28a745",
"medium": "#ffc107",
"low": "#6c757d"
}.get(rec.get("popularity", "medium").lower(), "#6c757d")
st.markdown(f"""
{rec['hashtag']}
{rec.get('description', '')}
{rec.get('popularity', 'medium').title()} Popularity
""", unsafe_allow_html=True)
# Hashtag strategy tips
st.markdown("#### Hashtag Strategy Tips")
st.markdown("""
- **Use a mix of popular and niche hashtags** to balance reach and competition
- **Limit to 2-3 hashtags per tweet** for optimal engagement
- **Research before using trending hashtags** to ensure relevance
- **Create branded hashtags** for campaigns and tracking
- **Place hashtags within your tweet text** when possible, rather than at the end
- **Monitor performance** to identify which hashtags drive the most engagement
""")
# Save hashtags
if st.button("Save to Hashtag Library"):
# Store in session state
if "hashtag_library" not in st.session_state:
st.session_state.hashtag_library = []
# Add new hashtags
for rec in recommendations:
if rec["hashtag"] not in [h["hashtag"] for h in st.session_state.hashtag_library]:
st.session_state.hashtag_library.append(rec)
st.success("Hashtags saved to your library!")
else:
st.warning("Could not generate recommendations")
else:
st.error("Please enter a content topic")
# Hashtag library
if "hashtag_library" in st.session_state and st.session_state.hashtag_library:
st.markdown("### Your Hashtag Library")
# Create DataFrame
library_df = pd.DataFrame(st.session_state.hashtag_library)
# Display library
st.dataframe(
library_df,
column_config={
"hashtag": "Hashtag",
"description": st.column_config.TextColumn("Description", width="large"),
"popularity": "Popularity"
},
hide_index=True
)
# Export button
if st.button("Export Hashtag Library"):
# Convert to CSV
csv = library_df.to_csv(index=False)
# Download button
st.download_button(
"Download CSV",
data=csv,
file_name=f"hashtag_library_{datetime.now().strftime('%Y%m%d')}.csv",
mime="text/csv"
)
```
### Phase 4: Visual Content Creation
#### 4.1 Image Generator
Implement the Image Generator feature:
```python
# lib/ai_writers/twitter_writers/visual_content/image_generator.py
import streamlit as st
import pandas as pd
import numpy as np
from typing import Dict, List, Any
import json
from datetime import datetime
import io
import base64
from PIL import Image, ImageDraw, ImageFont
import requests
import os
from pathlib import Path
# Constants
TEMPLATE_DIR = Path(__file__).parent.parent.parent.parent.parent / 'assets' / 'templates'
TEMPLATE_DIR.mkdir(exist_ok=True, parents=True)
FONT_DIR = Path(__file__).parent.parent.parent.parent.parent / 'assets' / 'fonts'
FONT_DIR.mkdir(exist_ok=True, parents=True)
def generate_quote_image(
quote: str,
author: str = "",
template: str = "gradient",
color_scheme: str = "blue"
) -> Image.Image:
"""Generate a quote image."""
# Define templates
templates = {
"gradient": {
"width": 1200,
"height": 675,
"background": {
"blue": [(25, 84, 123), (142, 197, 252)],
"green": [(25, 123, 48), (142, 252, 169)],
"purple": [(84, 25, 123), (197, 142, 252)],
"red": [(123, 25, 25), (252, 142, 142)]
},
"text_color": (255, 255, 255),
"author_color": (255, 255, 255, 200)
},
"minimal": {
"width": 1200,
"height": 675,
"background": {
"blue": (240, 248, 255),
"green": (240, 255, 240),
"purple": (248, 240, 255),
"red": (255, 240, 240)
},
"text_color": (50, 50, 50),
"author_color": (100, 100, 100)
},
"bold": {
"width": 1200,
"height": 675,
"background": {
"blue": (25, 84, 123),
"green": (25, 123, 48),
"purple": (84, 25, 123),
"red": (123, 25, 25)
},
"text_color": (255, 255, 255),
"author_color": (255, 255, 255, 200)
}
}
# Get template settings
template_settings = templates.get(template, templates["gradient"])
width = template_settings["width"]
height = template_settings["height"]
# Create image
img = Image.new("RGB", (width, height), color=(255, 255, 255))
draw = ImageDraw.Draw(img)
# Draw background
if template == "gradient":
# Create gradient background
bg_colors = template_settings["background"][color_scheme]
for y in range(height):
r = int(bg_colors[0][0] + (bg_colors[1][0] - bg_colors[0][0]) * y / height)
g = int(bg_colors[0][1] + (bg_colors[1][1] - bg_colors[0][1]) * y / height)
b = int(bg_colors[0][2] + (bg_colors[1][2] - bg_colors[0][2]) * y / height)
draw.line([(0, y), (width, y)], fill=(r, g, b))
else:
# Solid background
draw.rectangle([(0, 0), (width, height)], fill=template_settings["background"][color_scheme])
# Add quote
# Try to load font, fall back to default if not available
try:
quote_font = ImageFont.truetype(str(FONT_DIR / "Roboto-Bold.ttf"), 48)
author_font = ImageFont.truetype(str(FONT_DIR / "Roboto-Regular.ttf"), 32)
except:
quote_font = ImageFont.load_default()
author_font = ImageFont.load_default()
# Wrap text
max_width = width - 200
words = quote.split()
lines = []
current_line = []
for word in words:
test_line = " ".join(current_line + [word])
bbox = draw.textbbox((0, 0), test_line, font=quote_font)
text_width = bbox[2] - bbox[0]
if text_width <= max_width:
current_line.append(word)
else:
lines.append(" ".join(current_line))
current_line = [word]
if current_line:
lines.append(" ".join(current_line))
# Draw quote
quote_text = "\n".join(lines)
quote_bbox = draw.textbbox((0, 0), quote_text, font=quote_font)
quote_width = quote_bbox[2] - quote_bbox[0]
quote_height = quote_bbox[3] - quote_bbox[1]
quote_x = (width - quote_width) // 2
quote_y = (height - quote_height) // 2 - 50 if author else (height - quote_height) // 2
# Add quote marks
draw.text((quote_x - 60, quote_y - 80), """, fill=template_settings["text_color"], font=quote_font)
# Draw quote text
draw.text((quote_x, quote_y), quote_text, fill=template_settings["text_color"], font=quote_font, align="center")
# Draw author
if author:
author_text = f"â {author}"
author_bbox = draw.textbbox((0, 0), author_text, font=author_font)
author_width = author_bbox[2] - author_bbox[0]
author_x = (width - author_width) // 2
author_y = quote_y + quote_height + 40
draw.text((author_x, author_y), author_text, fill=template_settings["author_color"], font=author_font)
return img
def generate_tweet_card(
text: str,
username: str = "",
profile_image: str = None,
theme: str = "light"
) -> Image.Image:
"""Generate a tweet card image."""
# Define themes
themes = {
"light": {
"background": (255, 255, 255),
"text": (20, 23, 26),
"username": (83, 100, 113),
"border": (235, 238, 240)
},
"dark": {
"background": (21, 32, 43),
"text": (255, 255, 255),
"username": (136, 153, 166),
"border": (56, 68, 77)
},
"black": {
"background": (0, 0, 0),
"text": (217, 217, 217),
"username": (110, 118, 125),
"border": (47, 51, 54)
}
}
# Get theme settings
theme_settings = themes.get(theme, themes["light"])
# Create image
width = 1200
height = 675
img = Image.new("RGB", (width, height), color=theme_settings["background"])
draw = ImageDraw.Draw(img)
# Draw card background
card_width = 800
card_height = 400
card_x = (width - card_width) // 2
card_y = (height - card_height) // 2
# Draw card border
draw.rectangle(
[(card_x, card_y), (card_x + card_width, card_y + card_height)],
outline=theme_settings["border"],
width=2
)
# Try to load font, fall back to default if not available
try:
text_font = ImageFont.truetype(str(FONT_DIR / "Roboto-Regular.ttf"), 32)
username_font = ImageFont.truetype(str(FONT_DIR / "Roboto-Bold.ttf"), 28)
except:
text_font = ImageFont.load_default()
username_font = ImageFont.load_default()
# Add profile image
profile_size = 80
profile_x = card_x + 40
profile_y = card_y + 40
if profile_image:
try:
# Try to load profile image
response = requests.get(profile_image)
profile_img = Image.open(io.BytesIO(response.content))
profile_img = profile_img.resize((profile_size, profile_size))
# Create circular mask
mask = Image.new("L", (profile_size, profile_size), 0)
mask_draw = ImageDraw.Draw(mask)
mask_draw.ellipse((0, 0, profile_size, profile_size), fill=255)
# Apply mask
profile_img.putalpha(mask)
# Paste profile image
img.paste(profile_img, (profile_x, profile_y), profile_img)
except:
# Draw placeholder circle
draw.ellipse(
[(profile_x, profile_y), (profile_x + profile_size, profile_y + profile_size)],
fill=theme_settings["username"]
)
else:
# Draw placeholder circle
draw.ellipse(
[(profile_x, profile_y), (profile_x + profile_size, profile_y + profile_size)],
fill=theme_settings["username"]
)
# Add username
if username:
username_x = profile_x + profile_size + 20
username_y = profile_y + 10
draw.text((username_x, username_y), username, fill=theme_settings["text"], font=username_font)
draw.text((username_x, username_y + 40), f"@{username}", fill=theme_settings["username"], font=text_font)
# Wrap text
max_width = card_width - 80
words = text.split()
lines = []
current_line = []
for word in words:
test_line = " ".join(current_line + [word])
bbox = draw.textbbox((0, 0), test_line, font=text_font)
text_width = bbox[2] - bbox[0]
if text_width <= max_width:
current_line.append(word)
else:
lines.append(" ".join(current_line))
current_line = [word]
if current_line:
lines.append(" ".join(current_line))
# Draw tweet text
tweet_text = "\n".join(lines)
text_x = card_x + 40
text_y = profile_y + profile_size + 40
draw.text((text_x, text_y), tweet_text, fill=theme_settings["text"], font=text_font)
return img
def generate_infographic(
title: str,
items: List[str],
theme: str = "blue"
) -> Image.Image:
"""Generate a simple infographic."""
# Define themes
themes = {
"blue": {
"background": (240, 248, 255),
"title_bg": (25, 84, 123),
"title_text": (255, 255, 255),
"item_bg": [(142, 197, 252), (173, 216, 230)],
"item_text": (25, 25, 25)
},
"green": {
"background": (240, 255, 240),
"title_bg": (25, 123, 48),
"title_text": (255, 255, 255),
"item_bg": [(142, 252, 169), (173, 230, 188)],
"item_text": (25, 25, 25)
},
"purple": {
"background": (248, 240, 255),
"title_bg": (84, 25, 123),
"title_text": (255, 255, 255),
"item_bg": [(197, 142, 252), (216, 173, 230)],
"item_text": (25, 25, 25)
},
"red": {
"background": (255, 240, 240),
"title_bg": (123, 25, 25),
"title_text": (255, 255, 255),
"item_bg": [(252, 142, 142), (230, 173, 173)],
"item_text": (25, 25, 25)
}
}
# Get theme settings
theme_settings = themes.get(theme, themes["blue"])
# Create image
width = 1200
height = 675
img = Image.new("RGB", (width, height), color=theme_settings["background"])
draw = ImageDraw.Draw(img)
# Try to load font, fall back to default if not available
try:
title_font = ImageFont.truetype(str(FONT_DIR / "Roboto-Bold.ttf"), 48)
item_font = ImageFont.truetype(str(FONT_DIR / "Roboto-Regular.ttf"), 32)
except:
title_font = ImageFont.load_default()
item_font = ImageFont.load_default()
# Draw title
title_height = 100
draw.rectangle([(0, 0), (width, title_height)], fill=theme_settings["title_bg"])
title_bbox = draw.textbbox((0, 0), title, font=title_font)
title_width = title_bbox[2] - title_bbox[0]
title_x = (width - title_width) // 2
title_y = (title_height - title_font.size) // 2
draw.text((title_x, title_y), title, fill=theme_settings["title_text"], font=title_font)
# Draw items
item_height = 80
item_padding = 20
item_y = title_height + 50
for i, item in enumerate(items):
# Alternate background colors
bg_color = theme_settings["item_bg"][i % 2]
# Draw item background
draw.rectangle(
[(50, item_y), (width - 50, item_y + item_height)],
fill=bg_color,
outline=theme_settings["title_bg"],
width=2
)
# Draw item number
number_size = 60
draw.ellipse(
[(70, item_y + (item_height - number_size) // 2),
(70 + number_size, item_y + (item_height - number_size) // 2 + number_size)],
fill=theme_settings["title_bg"]
)
number_text = str(i + 1)
number_bbox = draw.textbbox((0, 0), number_text, font=item_font)
number_width = number_bbox[2] - number_bbox[0]
number_height = number_bbox[3] - number_bbox[1]
number_x = 70 + (number_size - number_width) // 2
number_y = item_y + (item_height - number_height) // 2
draw.text((number_x, number_y), number_text, fill=theme_settings["title_text"], font=item_font)
# Draw item text
item_text_x = 70 + number_size + 20
item_text_y = item_y + (item_height - item_font.size) // 2
draw.text((item_text_x, item_text_y), item, fill=theme_settings["item_text"], font=item_font)
# Update y position for next item
item_y += item_height + item_padding
return img
def render_image_generator():
"""Render the Image Generator UI."""
st.title("ðžïļ Twitter Image Generator")
st.markdown("Create engaging visual content for your tweets")
# Create tabs for different image types
tab1, tab2, tab3 = st.tabs(["Quote Cards", "Tweet Cards", "Infographics"])
with tab1:
st.markdown("### Quote Cards")
st.markdown("Create shareable quote images for Twitter")
# Quote input
quote = st.text_area(
"Quote Text",
placeholder="Enter your quote here...",
height=100
)
author = st.text_input(
"Author/Source",
placeholder="e.g., Albert Einstein"
)
# Design options
col1, col2 = st.columns(2)
with col1:
template = st.selectbox(
"Template",
["gradient", "minimal", "bold"],
index=0
)
with col2:
color_scheme = st.selectbox(
"Color Scheme",
["blue", "green", "purple", "red"],
index=0
)
# Generate button
if st.button("Generate Quote Card", use_container_width=True, key="generate_quote"):
if quote:
with st.spinner("Generating quote card..."):
# Generate image
img = generate_quote_image(quote, author, template, color_scheme)
# Convert to bytes
buf = io.BytesIO()
img.save(buf, format="PNG")
byte_im = buf.getvalue()
# Display image
st.image(byte_im, caption="Generated Quote Card", use_column_width=True)
# Download button
st.download_button(
"Download Image",
data=byte_im,
file_name=f"quote_card_{datetime.now().strftime('%Y%m%d_%H%M%S')}.png",
mime="image/png"
)
else:
st.error("Please enter a quote")
with tab2:
st.markdown("### Tweet Cards")
st.markdown("Create images that showcase your tweets")
# Tweet input
tweet_text = st.text_area(
"Tweet Text",
placeholder="Enter your tweet here...",
height=100
)
username = st.text_input(
"Twitter Username",
placeholder="e.g., elonmusk"
)
# Design options
theme = st.selectbox(
"Theme",
["light", "dark", "black"],
index=0
)
# Profile image
profile_image = None
if username:
try:
# Try to fetch profile image
profile_image = f"https://unavatar.io/twitter/{username}"
except:
pass
# Generate button
if st.button("Generate Tweet Card", use_container_width=True, key="generate_tweet"):
if tweet_text:
with st.spinner("Generating tweet card..."):
# Generate image
img = generate_tweet_card(tweet_text, username, profile_image, theme)
# Convert to bytes
buf = io.BytesIO()
img.save(buf, format="PNG")
byte_im = buf.getvalue()
# Display image
st.image(byte_im, caption="Generated Tweet Card", use_column_width=True)
# Download button
st.download_button(
"Download Image",
data=byte_im,
file_name=f"tweet_card_{datetime.now().strftime('%Y%m%d_%H%M%S')}.png",
mime="image/png"
)
else:
st.error("Please enter tweet text")
with tab3:
st.markdown("### Infographics")
st.markdown("Create simple infographics for Twitter")
# Infographic input
title = st.text_input(
"Infographic Title",
placeholder="e.g., 5 Tips for Better Tweets"
)
# Items input
items = []
for i in range(5):
item = st.text_input(
f"Item {i+1}",
placeholder=f"Enter item {i+1}...",
key=f"item_{i}"
)
if item:
items.append(item)
# Design options
theme = st.selectbox(
"Color Theme",
["blue", "green", "purple", "red"],
index=0,
key="infographic_theme"
)
# Generate button
if st.button("Generate Infographic", use_container_width=True, key="generate_infographic"):
if title and items:
with st.spinner("Generating infographic..."):
# Generate image
img = generate_infographic(title, items, theme)
# Convert to bytes
buf = io.BytesIO()
img.save(buf, format="PNG")
byte_im = buf.getvalue()
# Display image
st.image(byte_im, caption="Generated Infographic", use_column_width=True)
# Download button
st.download_button(
"Download Image",
data=byte_im,
file_name=f"infographic_{datetime.now().strftime('%Y%m%d_%H%M%S')}.png",
mime="image/png"
)
else:
st.error("Please enter a title and at least one item")
```
### Phase 5: Analytics & Optimization
#### 5.1 Performance Analytics
Implement the Performance Analytics feature:
```python
# lib/ai_writers/twitter_writers/analytics/performance_analytics.py
import streamlit as st
import pandas as pd
import numpy as np
from typing import Dict, List, Any
import json
from datetime import datetime, timedelta
import plotly.express as px
import plotly.graph_objects as go
from plotly.subplots import make_subplots
from ....integrations.twitter.auth import create_twitter_api, create_twitter_client, is_authenticated
def get_tweet_performance(days: int = 30) -> pd.DataFrame:
"""Get tweet performance data from Twitter API."""
try:
if not is_authenticated():
return pd.DataFrame()
# Get API client
api = create_twitter_api()
# Get user timeline
tweets = api.user_timeline(count=200, tweet_mode="extended")
# Filter by date
cutoff_date = datetime.now() - timedelta(days=days)
tweets = [t for t in tweets if t.created_at >= cutoff_date]
# Create DataFrame
data = []
for tweet in tweets:
# Skip retweets
if hasattr(tweet, "retweeted_status"):
continue
# Extract tweet data
tweet_data = {
"id": tweet.id,
"text": tweet.full_text,
"created_at": tweet.created_at,
"likes": tweet.favorite_count,
"retweets": tweet.retweet_count,
"replies": 0, # Not available in standard API
"impressions": 0, # Not available in standard API
"engagement": tweet.favorite_count + tweet.retweet_count,
"hashtags": len(tweet.entities.get("hashtags", [])),
"mentions": len(tweet.entities.get("user_mentions", [])),
"urls": len(tweet.entities.get("urls", [])),
"media": 1 if "media" in tweet.entities else 0,
"hour": tweet.created_at.hour,
"day": tweet.created_at.strftime("%A"),
"length": len(tweet.full_text)
}
data.append(tweet_data)
# Create DataFrame
df = pd.DataFrame(data)
return df
except Exception as e:
st.error(f"Error fetching tweet performance: {str(e)}")
return pd.DataFrame()
def analyze_performance(df: pd.DataFrame) -> Dict[str, Any]:
"""Analyze tweet performance data."""
if df.empty:
return {
"total_engagement": 0,
"avg_engagement": 0,
"top_tweet": None,
"worst_tweet": None,
"best_time": None,
"best_day": None,
"media_impact": 0,
"hashtag_impact": 0,
"length_impact": 0
}
# Calculate metrics
total_engagement = df["engagement"].sum()
avg_engagement = df["engagement"].mean()
# Find top and worst tweets
top_tweet = df.loc[df["engagement"].idxmax()] if not df.empty else None
worst_tweet = df.loc[df["engagement"].idxmin()] if not df.empty else None
# Find best time and day
hour_engagement = df.groupby("hour")["engagement"].mean()
best_hour = hour_engagement.idxmax() if not hour_engagement.empty else None
day_engagement = df.groupby("day")["engagement"].mean()
best_day = day_engagement.idxmax() if not day_engagement.empty else None
# Calculate impact factors
media_impact = df[df["media"] == 1]["engagement"].mean() / avg_engagement if avg_engagement > 0 else 1
hashtag_impact = 0
if not df.empty:
hashtag_groups = df.groupby("hashtags")["engagement"].mean()
optimal_hashtags = hashtag_groups.idxmax() if not hashtag_groups.empty else 0
hashtag_impact = hashtag_groups.max() / avg_engagement if avg_engagement > 0 else 1
length_impact = 0
if not df.empty:
df["length_bin"] = pd.cut(df["length"], bins=[0, 70, 140, 210, 280], labels=["0-70", "71-140", "141-210", "211-280"])
length_groups = df.groupby("length_bin")["engagement"].mean()
optimal_length = length_groups.idxmax() if not length_groups.empty else "71-140"
length_impact = length_groups.max() / avg_engagement if avg_engagement > 0 else 1
return {
"total_engagement": total_engagement,
"avg_engagement": avg_engagement,
"top_tweet": top_tweet,
"worst_tweet": worst_tweet,
"best_time": best_hour,
"best_day": best_day,
"media_impact": media_impact,
"hashtag_impact": hashtag_impact,
"length_impact": length_impact
}
def render_performance_analytics():
"""Render the Performance Analytics UI."""
st.title("ð Twitter Performance Analytics")
st.markdown("Track tweet performance and engagement metrics")
# Check if connected to Twitter
twitter_connected = is_authenticated()
if twitter_connected:
user = st.session_state.twitter_user
st.success(f"Connected as @{user['screen_name']}")
# Date range selector
col1, col2 = st.columns(2)
with col1:
days = st.slider(
"Time Period",
min_value=7,
max_value=90,
value=30,
step=1
)
with col2:
refresh = st.button("Refresh Data", use_container_width=True)
# Get performance data
with st.spinner("Fetching tweet performance data..."):
df = get_tweet_performance(days)
if not df.empty:
# Store in session state
st.session_state.tweet_performance = df
# Analyze performance
analysis = analyze_performance(df)
# Display overview metrics
st.markdown("### Performance Overview")
col1, col2, col3, col4 = st.columns(4)
with col1:
st.metric("Total Tweets", len(df))
with col2:
st.metric("Total Engagement", f"{analysis['total_engagement']:,}")
with col3:
st.metric("Avg. Engagement", f"{analysis['avg_engagement']:.1f}")
with col4:
st.metric("Media Impact", f"{analysis['media_impact']:.2f}x")
# Engagement over time
st.markdown("### Engagement Over Time")
# Sort by date
df_sorted = df.sort_values("created_at")
# Create time series chart
fig = px.line(
df_sorted,
x="created_at",
y="engagement",
title="Tweet Engagement History",
labels={"created_at": "Date", "engagement": "Engagement"}
)
# Add 7-day moving average
df_sorted["ma7"] = df_sorted["engagement"].rolling(7).mean()
fig.add_trace(
go.Scatter(
x=df_sorted["created_at"],
y=df_sorted["ma7"],
mode="lines",
name="7-day Moving Average",
line=dict(color="red", width=2)
)
)
st.plotly_chart(fig, use_container_width=True)
# Engagement by day and hour
st.markdown("### Engagement by Day and Hour")
# Create heatmap
pivot = df.pivot_table(
index="day",
columns="hour",
values="engagement",
aggfunc="mean"
)
# Reorder days
days_order = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
pivot = pivot.reindex(days_order)
fig = px.imshow(
pivot,
labels=dict(x="Hour of Day", y="Day of Week", color="Avg. Engagement"),
x=list(range(24)),
y=days_order,
color_continuous_scale="Viridis",
title="Engagement Heatmap by Day and Hour"
)
st.plotly_chart(fig, use_container_width=True)
# Content analysis
st.markdown("### Content Analysis")
col1, col2 = st.columns(2)
with col1:
# Engagement by tweet length
df["length_bin"] = pd.cut(df["length"], bins=[0, 70, 140, 210, 280], labels=["0-70", "71-140", "141-210", "211-280"])
length_engagement = df.groupby("length_bin")["engagement"].mean().reset_index()
fig = px.bar(
length_engagement,
x="length_bin",
y="engagement",
title="Engagement by Tweet Length",
labels={"length_bin": "Character Count", "engagement": "Avg. Engagement"}
)
st.plotly_chart(fig, use_container_width=True)
with col2:
# Engagement by hashtag count
hashtag_engagement = df.groupby("hashtags")["engagement"].mean().reset_index()
fig = px.bar(
hashtag_engagement,
x="hashtags",
y="engagement",
title="Engagement by Hashtag Count",
labels={"hashtags": "Number of Hashtags", "engagement": "Avg. Engagement"}
)
st.plotly_chart(fig, use_container_width=True)
# Media impact
st.markdown("### Media Impact")
media_engagement = df.groupby("media")["engagement"].mean().reset_index()
media_engagement["media"] = media_engagement["media"].map({0: "No Media", 1: "With Media"})
fig = px.bar(
media_engagement,
x="media",
y="engagement",
title="Engagement by Media Inclusion",
labels={"media": "Media", "engagement": "Avg. Engagement"},
color="media",
color_discrete_map={"No Media": "#1DA1F2", "With Media": "#17BF63"}
)
st.plotly_chart(fig, use_container_width=True)
# Top tweets
st.markdown("### Top Performing Tweets")
# Sort by engagement
top_tweets = df.sort_values("engagement", ascending=False).head(5)
for i, (_, tweet) in enumerate(top_tweets.iterrows()):
st.markdown(f"""
{tweet['text']}
{tweet['created_at'].strftime('%Y-%m-%d %H:%M')}
âĪïļ {tweet['likes']} | ð {tweet['retweets']} | ðŽ {tweet['replies']}
""", unsafe_allow_html=True)
# Recommendations
st.markdown("### Performance Recommendations")
recommendations = []
# Best time to post
if analysis["best_time"] is not None and analysis["best_day"] is not None:
recommendations.append(f"Post on {analysis['best_day']} around {analysis['best_time']}:00 for optimal engagement")
# Media recommendation
if analysis["media_impact"] > 1.2:
recommendations.append(f"Including media increases engagement by {(analysis['media_impact']-1)*100:.1f}%")
# Hashtag recommendation
hashtag_groups = df.groupby("hashtags")["engagement"].mean()
if not hashtag_groups.empty:
optimal_hashtags = hashtag_groups.idxmax()
recommendations.append(f"Using {optimal_hashtags} hashtags yields the best engagement")
# Length recommendation
length_groups = df.groupby("length_bin")["engagement"].mean()
if not length_groups.empty:
optimal_length = length_groups.idxmax()
recommendations.append(f"Tweets with {optimal_length} characters perform best")
# Display recommendations
for rec in recommendations:
st.info(rec)
# Export options
st.markdown("### Export Data")
col1, col2 = st.columns(2)
with col1:
# Export as CSV
csv = df.to_csv(index=False)
st.download_button(
"Download CSV",
data=csv,
file_name=f"tweet_performance_{datetime.now().strftime('%Y%m%d')}.csv",
mime="text/csv"
)
with col2:
# Export as JSON
json_str = df.to_json(orient="records", date_format="iso")
st.download_button(
"Download JSON",
data=json_str,
file_name=f"tweet_performance_{datetime.now().strftime('%Y%m%d')}.json",
mime="application/json"
)
else:
st.warning("No tweets found in the selected time period")
else:
st.warning("Connect your Twitter account to access performance analytics")
# Show sample data
st.markdown("### Sample Performance Data")
# Create sample data
dates = pd.date_range(start=datetime.now() - timedelta(days=30), periods=30)
engagements = np.random.normal(loc=20, scale=10, size=30)
engagements = np.abs(engagements) # Make all values positive
df = pd.DataFrame({
"date": dates,
"engagement": engagements
})
# Sample metrics
col1, col2, col3, col4 = st.columns(4)
with col1:
st.metric("Total Tweets", "42")
with col2:
st.metric("Total Engagement", "1,254")
with col3:
st.metric("Avg. Engagement", "29.8")
with col4:
st.metric("Media Impact", "1.45x")
# Sample chart
fig = px.line(
df,
x="date",
y="engagement",
title="Sample Tweet Engagement History"
)
st.plotly_chart(fig, use_container_width=True)
```
### Phase 6: Integration and Dashboard Updates
#### 6.1 Update Twitter Dashboard
Update the Twitter dashboard to include the new features:
```python
# lib/ai_writers/twitter_writers/twitter_dashboard.py
import streamlit as st
import streamlit.components.v1 as components
from typing import Dict, List
import json
from datetime import datetime
from .tweet_generator.enhanced_tweet_generator import enhanced_tweet_generator
from .tweet_performance.performance_predictor import render_performance_predictor
from .content_strategy.calendar_generator import render_calendar_generator
from .content_strategy.hashtag_manager import render_hashtag_manager
from .visual_content.image_generator import render_image_generator
from .analytics.performance_analytics import render_performance_analytics
from ...integrations.twitter.auth import is_authenticated
from ...integrations.twitter.account_manager import render_twitter_account_manager
from ...integrations.twitter.api_key_manager import render_twitter_api_key_manager
from ...integrations.twitter.callback_handler import handle_oauth_callback
def load_feature_data() -> Dict:
"""Load feature data from a structured format."""
return {
"tweet_generation": {
"title": "Tweet Generation & Optimization",
"icon": "ðĶ",
"description": "Create and optimize engaging tweets with AI assistance",
"features": [
{
"name": "Smart Tweet Generator",
"description": "Generate multiple tweet variations with optimal character count, hashtags, and emojis",
"status": "active",
"icon": "âĻ",
"function": enhanced_tweet_generator
},
{
"name": "Tweet Performance Predictor",
"description": "Predict engagement rates and best posting times for maximum impact",
"status": "active",
"icon": "ð",
"function": render_performance_predictor
}
]
},
"content_strategy": {
"title": "Content Strategy Tools",
"icon": "ð
",
"description": "Plan and manage your Twitter content strategy effectively",
"features": [
{
"name": "Content Calendar Generator",
"description": "Create weekly/monthly content plans with theme-based scheduling",
"status": "active",
"icon": "ðïļ",
"function": render_calendar_generator
},
{
"name": "Hashtag Strategy Manager",
"description": "Research and manage trending hashtags for better reach",
"status": "active",
"icon": "#ïļâĢ",
"function": render_hashtag_manager
}
]
},
"visual_content": {
"title": "Visual Content Creation",
"icon": "ðĻ",
"description": "Create engaging visual content for your tweets",
"features": [
{
"name": "Image Generator",
"description": "Create tweet cards, infographics, and quote designs",
"status": "active",
"icon": "ðžïļ",
"function": render_image_generator
},
{
"name": "Video Content Assistant",
"description": "Generate video scripts and optimize captions",
"status": "coming_soon",
"icon": "ðĨ"
}
]
},
"engagement": {
"title": "Engagement & Community",
"icon": "ðĪ",
"description": "Manage and enhance community engagement",
"features": [
{
"name": "Reply Generator",
"description": "Generate context-aware responses with appropriate tone",
"status": "coming_soon",
"icon": "ðŽ"
},
{
"name": "Community Tools",
"description": "Create polls and plan Q&A sessions",
"status": "coming_soon",
"icon": "ðĨ"
}
]
},
"analytics": {
"title": "Analytics & Optimization",
"icon": "ð",
"description": "Track performance and optimize your Twitter strategy",
"features": [
{
"name": "Performance Analytics",
"description": "Track tweet performance and engagement metrics",
"status": "active",
"icon": "ð",
"function": render_performance_analytics
},
{
"name": "A/B Testing Assistant",
"description": "Test and optimize tweet variations for better results",
"status": "coming_soon",
"icon": "ð"
}
]
},
"research": {
"title": "Research & Intelligence",
"icon": "ð",
"description": "Gain insights and stay ahead of trends",
"features": [
{
"name": "Market Research",
"description": "Analyze competitors and track industry trends",
"status": "coming_soon",
"icon": "ð"
},
{
"name": "Content Inspiration",
"description": "Get trending topic suggestions and content ideas",
"status": "coming_soon",
"icon": "ðĄ"
}
]
}
}
def render_feature_card(feature: Dict) -> None:
"""Render a single feature card with its details."""
with st.container():
st.markdown(f"""
{feature['icon']} {feature['name']}
{feature['description']}
{feature['status'].title()}
""", unsafe_allow_html=True)
def render_category_section(category: Dict) -> None:
"""Render a category section with all its features."""
st.markdown(f"### {category['icon']} {category['title']}")
st.markdown(f"*{category['description']}*")
col1, col2 = st.columns(2)
with col1:
render_feature_card(category['features'][0])
if category['features'][0]['status'] == 'active':
if st.button(f"Launch {category['features'][0]['name']}", key=f"launch_{category['features'][0]['name']}"):
st.session_state.current_feature = category['features'][0]['function']
st.experimental_rerun()
with col2:
render_feature_card(category['features'][1])
if category['features'][1]['status'] == 'active':
if st.button(f"Launch {category['features'][1]['name']}", key=f"launch_{category['features'][1]['name']}"):
st.session_state.current_feature = category['features'][1]['function']
st.experimental_rerun()
def run_dashboard():
"""Main function to run the Twitter dashboard."""
# Handle OAuth callback if present
handle_oauth_callback()
# Check if a feature is currently active
if "current_feature" in st.session_state and callable(st.session_state.current_feature):
# Add back button
if st.button("â Back to Dashboard"):
del st.session_state.current_feature
st.experimental_rerun()
# Run the current feature
st.session_state.current_feature()
return
# Header
st.title("ðĶ Twitter AI Writer Dashboard")
st.markdown("""
Welcome to your all-in-one Twitter content creation and management platform.
Explore our AI-powered tools to enhance your Twitter marketing strategy.
""")
# Twitter account status
twitter_connected = is_authenticated()
if twitter_connected:
user = st.session_state.twitter_user
st.success(f"Connected as @{user['screen_name']}")
else:
st.warning("Connect your Twitter account to access all features")
# Show account connection UI
with st.expander("Twitter Account Setup", expanded=True):
# First show API key manager
render_twitter_api_key_manager()
# Then show account manager
render_twitter_account_manager()
# Create tabs for different sections
tab1, tab2, tab3 = st.tabs(["ðŊ Quick Actions", "ð Analytics", "âïļ Settings"])
with tab1:
st.markdown("### ð Quick Actions")
col1, col2, col3 = st.columns(3)
with col1:
if st.button("ð Create New Tweet", use_container_width=True):
# Set the current feature to the enhanced tweet generator
st.session_state.current_feature = enhanced_tweet_generator
st.experimental_rerun()
with col2:
if st.button("ð
Plan Content", use_container_width=True):
# Set the current feature to the calendar generator
st.session_state.current_feature = render_calendar_generator
st.experimental_rerun()
with col3:
if st.button("ð View Analytics", use_container_width=True):
# Set the current feature to performance analytics
st.session_state.current_feature = render_performance_analytics
st.experimental_rerun()
with tab2:
st.markdown("### ð Analytics Dashboard")
if twitter_connected:
# Show mini analytics dashboard
try:
from .analytics.performance_analytics import get_tweet_performance, analyze_performance
# Get performance data
with st.spinner("Loading analytics..."):
df = get_tweet_performance(days=30)
if not df.empty:
# Analyze performance
analysis = analyze_performance(df)
# Display overview metrics
col1, col2, col3, col4 = st.columns(4)
with col1:
st.metric("Total Tweets", len(df))
with col2:
st.metric("Total Engagement", f"{analysis['total_engagement']:,}")
with col3:
st.metric("Avg. Engagement", f"{analysis['avg_engagement']:.1f}")
with col4:
st.metric("Media Impact", f"{analysis['media_impact']:.2f}x")
# Show engagement chart
import plotly.express as px
# Sort by date
df_sorted = df.sort_values("created_at")
# Create time series chart
fig = px.line(
df_sorted,
x="created_at",
y="engagement",
title="Recent Tweet Engagement",
labels={"created_at": "Date", "engagement": "Engagement"}
)
st.plotly_chart(fig, use_container_width=True)
# View full analytics button
if st.button("View Full Analytics", use_container_width=True):
st.session_state.current_feature = render_performance_analytics
st.experimental_rerun()
else:
st.info("No recent tweets found. Start tweeting to see analytics!")
except Exception as e:
st.error(f"Error loading analytics: {str(e)}")
st.info("View full analytics for detailed insights")
else:
st.info("Connect your Twitter account to view analytics")
with tab3:
st.markdown("### âïļ Settings")
# Twitter account settings
with st.expander("Twitter Account", expanded=True):
render_twitter_account_manager()
# API key settings
with st.expander("API Configuration", expanded=False):
render_twitter_api_key_manager()
# Preferences
with st.expander("Preferences", expanded=False):
st.markdown("#### Dashboard Preferences")
# Theme preference
theme = st.selectbox(
"Theme",
["Light", "Dark", "System"],
index=2
)
# Default content type
content_type = st.selectbox(
"Default Content Type",
["Informative", "Promotional", "Engaging", "Educational"],
index=0
)
# Save preferences
if st.button("Save Preferences"):
st.session_state.twitter_preferences = {
"theme": theme,
"content_type": content_type
}
st.success("Preferences saved!")
# Load feature data
features = load_feature_data()
# Main content area
st.markdown("## ð ïļ Available Tools")
# Render each category
for category in features.values():
render_category_section(category)
# Footer
st.markdown("---")
st.markdown("""
""", unsafe_allow_html=True)
if __name__ == "__main__":
run_dashboard()
```
## Testing Plan
### Unit Testing
1. **Authentication Tests**
- Test Twitter API key validation
- Test OAuth flow
- Test token storage and retrieval
2. **API Integration Tests**
- Test tweet posting
- Test timeline retrieval
- Test hashtag search
- Test media upload
3. **Feature Tests**
- Test tweet generation
- Test performance prediction
- Test content calendar creation
- Test image generation
### Integration Testing
1. **End-to-End Flow Tests**
- Test complete user journey from authentication to posting
- Test data flow between components
- Test error handling and recovery
2. **Cross-Feature Tests**
- Test integration between tweet generator and performance predictor
- Test integration between content calendar and tweet scheduler
- Test integration between analytics and content recommendations
### User Acceptance Testing
1. **Usability Tests**
- Test with real users to gather feedback
- Evaluate UI/UX design
- Measure time to complete common tasks
2. **Performance Tests**
- Test with large datasets
- Measure response times
- Identify bottlenecks
## Implementation Timeline
### Phase 1: Twitter Authentication & Basic Integration (2 weeks)
- Week 1: Set up Twitter API authentication system
- Week 2: Create account connection UI and API key management
### Phase 2: Enhanced Tweet Generator (2 weeks)
- Week 3: Implement real Twitter data integration
- Week 4: Develop tweet performance predictor
### Phase 3: Content Strategy Tools (3 weeks)
- Week 5: Implement content calendar generator
- Week 6-7: Develop hashtag strategy manager
### Phase 4: Visual Content Creation (2 weeks)
- Week 8-9: Implement image generator for quotes, tweets, and infographics
### Phase 5: Analytics & Optimization (2 weeks)
- Week 10-11: Implement performance analytics dashboard
### Phase 6: Integration and Dashboard Updates (1 week)
- Week 12: Update Twitter dashboard and integrate all features
## Conclusion
This implementation plan provides a comprehensive approach to enhancing the Twitter features in AI-Writer. By leveraging the Tweepy library and user-provided API keys, we can transform the "coming soon" features into fully functional components that provide real value to users.
The phased approach allows for incremental delivery of features, with each phase building on the previous one. This ensures that users can start benefiting from the enhancements early, while more advanced features are developed.
Key benefits of this implementation:
1. **Real Twitter Integration**: Users can connect their Twitter accounts and interact directly with the platform.
2. **Data-Driven Insights**: Performance analytics and predictions based on real Twitter data.
3. **Comprehensive Content Strategy**: Tools for planning, creating, and optimizing Twitter content.
4. **Visual Content Creation**: Easy-to-use tools for creating engaging visual content.
5. **Streamlined Workflow**: Integrated dashboard for managing all Twitter activities.
By following this plan, AI-Writer will stand out against competitors by offering a complete Twitter content creation and management solution that leverages real data and AI to optimize performance.