Content Calendar, Content Gap Analysis, and Content Optimization
This commit is contained in:
@@ -1,13 +1,33 @@
|
||||
"""
|
||||
Smart Tweet Generator with modern UI components.
|
||||
"""
|
||||
|
||||
import streamlit as st
|
||||
import re
|
||||
import json
|
||||
import time
|
||||
from typing import Dict, List, Tuple, Optional
|
||||
import random
|
||||
import emoji
|
||||
from datetime import datetime
|
||||
|
||||
from ....gpt_providers.text_generation.main_text_generation import llm_text_gen
|
||||
from ..twitter_streamlit_ui import (
|
||||
TweetForm,
|
||||
TweetCard,
|
||||
Theme,
|
||||
save_to_session,
|
||||
get_from_session,
|
||||
show_success_message,
|
||||
show_error_message,
|
||||
show_info_message,
|
||||
validate_tweet_content,
|
||||
validate_hashtags,
|
||||
validate_emojis,
|
||||
calculate_engagement_score,
|
||||
generate_tweet_metrics,
|
||||
copy_to_clipboard,
|
||||
create_download_button
|
||||
)
|
||||
|
||||
# Constants
|
||||
MAX_TWEET_LENGTH = 280
|
||||
@@ -19,14 +39,6 @@ EMOJI_CATEGORIES = {
|
||||
"Casual": ["👋", "👍", "🙋", "💁", "🤗", "👌", "✌️", "🤝", "👊", "🙏"]
|
||||
}
|
||||
|
||||
def count_characters(text: str) -> int:
|
||||
"""Count characters in tweet, accounting for emojis."""
|
||||
return len(text)
|
||||
|
||||
def extract_hashtags(text: str) -> List[str]:
|
||||
"""Extract hashtags from tweet text."""
|
||||
return re.findall(r'#\w+', text)
|
||||
|
||||
def suggest_hashtags(topic: str, tone: str) -> List[str]:
|
||||
"""Suggest relevant hashtags based on topic and tone."""
|
||||
# Enhanced hashtag suggestions based on topic and tone
|
||||
@@ -61,63 +73,6 @@ def suggest_emojis(tone: str, count: int = 3) -> List[str]:
|
||||
}
|
||||
return emoji_map.get(tone.lower(), ["✨"])[:count]
|
||||
|
||||
def predict_tweet_performance(tweet: str, target_audience: str, tone: str) -> Dict:
|
||||
"""Predict tweet performance with enhanced metrics."""
|
||||
char_count = count_characters(tweet)
|
||||
hashtags = extract_hashtags(tweet)
|
||||
|
||||
# Enhanced performance metrics
|
||||
metrics = {
|
||||
"character_count": {
|
||||
"score": min(100, (char_count / 280) * 100),
|
||||
"status": "optimal" if 100 <= char_count <= 200 else "suboptimal",
|
||||
"suggestion": "Consider adjusting length for optimal engagement" if char_count < 100 or char_count > 200 else "Length is optimal"
|
||||
},
|
||||
"hashtag_usage": {
|
||||
"score": min(100, (len(hashtags) / 3) * 100),
|
||||
"status": "optimal" if 1 <= len(hashtags) <= 3 else "suboptimal",
|
||||
"suggestion": "Add more hashtags" if len(hashtags) < 1 else "Reduce hashtag count" if len(hashtags) > 3 else "Hashtag count is optimal"
|
||||
},
|
||||
"engagement_potential": {
|
||||
"score": 0,
|
||||
"status": "needs_improvement",
|
||||
"suggestion": ""
|
||||
},
|
||||
"audience_alignment": {
|
||||
"score": 0,
|
||||
"status": "needs_improvement",
|
||||
"suggestion": ""
|
||||
}
|
||||
}
|
||||
|
||||
# Calculate engagement potential
|
||||
engagement_triggers = ["?", "!", "RT", "like", "follow", "check", "learn", "discover"]
|
||||
trigger_count = sum(1 for trigger in engagement_triggers if trigger.lower() in tweet.lower())
|
||||
metrics["engagement_potential"]["score"] = min(100, (trigger_count / 3) * 100)
|
||||
metrics["engagement_potential"]["status"] = "optimal" if trigger_count >= 1 else "needs_improvement"
|
||||
metrics["engagement_potential"]["suggestion"] = "Add engagement triggers" if trigger_count < 1 else "Good engagement potential"
|
||||
|
||||
# Calculate audience alignment
|
||||
audience_keywords = {
|
||||
"professionals": ["business", "industry", "professional", "career"],
|
||||
"students": ["learn", "study", "education", "student"],
|
||||
"general": ["everyone", "people", "community", "world"]
|
||||
}
|
||||
keyword_count = sum(1 for keyword in audience_keywords.get(target_audience.lower(), [])
|
||||
if keyword.lower() in tweet.lower())
|
||||
metrics["audience_alignment"]["score"] = min(100, (keyword_count / 2) * 100)
|
||||
metrics["audience_alignment"]["status"] = "optimal" if keyword_count >= 1 else "needs_improvement"
|
||||
metrics["audience_alignment"]["suggestion"] = "Add audience-specific keywords" if keyword_count < 1 else "Good audience alignment"
|
||||
|
||||
# Calculate overall score
|
||||
overall_score = sum(metric["score"] for metric in metrics.values()) / len(metrics)
|
||||
|
||||
return {
|
||||
"metrics": metrics,
|
||||
"overall_score": overall_score,
|
||||
"status": "excellent" if overall_score >= 80 else "good" if overall_score >= 60 else "fair" if overall_score >= 40 else "needs_improvement"
|
||||
}
|
||||
|
||||
def generate_tweet_variations(
|
||||
hook: str,
|
||||
target_audience: str,
|
||||
@@ -177,58 +132,14 @@ def generate_tweet_variations(
|
||||
|
||||
return sample_tweets[:num_variations]
|
||||
|
||||
def suggest_improvements(tweet: str, performance: Dict) -> List[str]:
|
||||
"""Generate actionable improvement suggestions."""
|
||||
suggestions = []
|
||||
metrics = performance["metrics"]
|
||||
|
||||
# Character count suggestions
|
||||
if metrics["character_count"]["status"] == "suboptimal":
|
||||
suggestions.append(f"📝 {metrics['character_count']['suggestion']}")
|
||||
|
||||
# Hashtag suggestions
|
||||
if metrics["hashtag_usage"]["status"] == "suboptimal":
|
||||
suggestions.append(f"#️⃣ {metrics['hashtag_usage']['suggestion']}")
|
||||
|
||||
# Engagement suggestions
|
||||
if metrics["engagement_potential"]["status"] == "needs_improvement":
|
||||
suggestions.append(f"🎯 {metrics['engagement_potential']['suggestion']}")
|
||||
|
||||
# Audience alignment suggestions
|
||||
if metrics["audience_alignment"]["status"] == "needs_improvement":
|
||||
suggestions.append(f"👥 {metrics['audience_alignment']['suggestion']}")
|
||||
|
||||
return suggestions
|
||||
|
||||
def render_tweet_card(tweet: Dict, index: int) -> None:
|
||||
"""Render an enhanced tweet card with interactive elements."""
|
||||
with st.container():
|
||||
st.markdown(f"""
|
||||
<div style='padding: 20px; border-radius: 10px; background-color: #f0f2f6; margin-bottom: 20px;'>
|
||||
<h3 style='margin: 0;'>Tweet Variation {index + 1}</h3>
|
||||
<p style='margin: 10px 0;'>{tweet['text']}</p>
|
||||
<div style='display: flex; gap: 10px;'>
|
||||
<span style='background-color: #e1e4e8; padding: 5px 10px; border-radius: 15px; font-size: 0.8em;'>
|
||||
Score: {tweet['engagement_score']}%
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
# Interactive elements
|
||||
col1, col2 = st.columns(2)
|
||||
with col1:
|
||||
if st.button(f"Copy Tweet {index + 1}", key=f"copy_{index}"):
|
||||
st.write("Tweet copied to clipboard!")
|
||||
with col2:
|
||||
if st.button(f"Save Tweet {index + 1}", key=f"save_{index}"):
|
||||
st.write("Tweet saved!")
|
||||
|
||||
def smart_tweet_generator():
|
||||
"""Enhanced Smart Tweet Generator with improved UI and AI integration."""
|
||||
st.title("✨ Smart Tweet Generator")
|
||||
st.markdown("Create engaging tweets with AI-powered optimization")
|
||||
|
||||
# Apply theme
|
||||
Theme().apply()
|
||||
|
||||
# Input section with improved UI
|
||||
with st.expander("Tweet Parameters", expanded=True):
|
||||
col1, col2 = st.columns(2)
|
||||
@@ -291,34 +202,27 @@ def smart_tweet_generator():
|
||||
# Display performance metrics
|
||||
st.markdown("### 📊 Performance Metrics")
|
||||
for tweet in tweets:
|
||||
performance = predict_tweet_performance(tweet["text"], target_audience, tone)
|
||||
# Calculate engagement score
|
||||
engagement_score = calculate_engagement_score(
|
||||
tweet["text"],
|
||||
tweet["hashtags"],
|
||||
tweet["emojis"],
|
||||
tone
|
||||
)
|
||||
|
||||
# Overall score with progress bar
|
||||
st.progress(performance["overall_score"] / 100)
|
||||
st.metric("Overall Score", f"{performance['overall_score']:.1f}%")
|
||||
# Generate metrics
|
||||
metrics = generate_tweet_metrics(engagement_score)
|
||||
|
||||
# Detailed metrics in columns
|
||||
cols = st.columns(4)
|
||||
metrics = performance["metrics"]
|
||||
|
||||
with cols[0]:
|
||||
st.metric("Character Count", f"{metrics['character_count']['score']:.1f}%")
|
||||
with cols[1]:
|
||||
st.metric("Hashtag Usage", f"{metrics['hashtag_usage']['score']:.1f}%")
|
||||
with cols[2]:
|
||||
st.metric("Engagement", f"{metrics['engagement_potential']['score']:.1f}%")
|
||||
with cols[3]:
|
||||
st.metric("Audience Fit", f"{metrics['audience_alignment']['score']:.1f}%")
|
||||
|
||||
# Improvement suggestions
|
||||
suggestions = suggest_improvements(tweet["text"], performance)
|
||||
if suggestions:
|
||||
st.markdown("### 💡 Improvement Suggestions")
|
||||
for suggestion in suggestions:
|
||||
st.info(suggestion)
|
||||
|
||||
# Tweet card
|
||||
render_tweet_card(tweet, tweets.index(tweet))
|
||||
# Display tweet card
|
||||
TweetCard(
|
||||
content=tweet["text"],
|
||||
engagement_score=engagement_score,
|
||||
hashtags=tweet["hashtags"],
|
||||
emojis=tweet["emojis"],
|
||||
metrics=metrics,
|
||||
on_copy=lambda: copy_to_clipboard(tweet["text"]),
|
||||
on_save=lambda: save_tweet(tweet)
|
||||
).render()
|
||||
|
||||
st.markdown("---")
|
||||
|
||||
@@ -326,17 +230,23 @@ def smart_tweet_generator():
|
||||
st.markdown("### 📥 Export Options")
|
||||
col1, col2 = st.columns(2)
|
||||
with col1:
|
||||
if st.button("Export as JSON"):
|
||||
st.download_button(
|
||||
"Download JSON",
|
||||
data=json.dumps(tweets, indent=2),
|
||||
file_name=f"tweets_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json",
|
||||
mime="application/json"
|
||||
)
|
||||
create_download_button(
|
||||
data=tweets,
|
||||
filename=f"tweets_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json",
|
||||
button_text="Export as JSON"
|
||||
)
|
||||
with col2:
|
||||
if st.button("Copy All Tweets"):
|
||||
tweet_texts = "\n\n".join(tweet["text"] for tweet in tweets)
|
||||
st.code(tweet_texts)
|
||||
copy_to_clipboard(tweet_texts)
|
||||
show_success_message("All tweets copied to clipboard!")
|
||||
|
||||
def save_tweet(tweet: Dict):
|
||||
"""Save tweet for later."""
|
||||
tweets = get_from_session("tweets", [])
|
||||
tweets.append(tweet)
|
||||
save_to_session("tweets", tweets)
|
||||
show_success_message("Tweet saved successfully!")
|
||||
|
||||
if __name__ == "__main__":
|
||||
smart_tweet_generator()
|
||||
@@ -1,116 +1,29 @@
|
||||
"""
|
||||
Twitter Dashboard with modern UI components.
|
||||
"""
|
||||
|
||||
import streamlit as st
|
||||
import streamlit.components.v1 as components
|
||||
from typing import Dict, List
|
||||
import json
|
||||
import base64
|
||||
from datetime import datetime
|
||||
|
||||
from .tweet_generator import smart_tweet_generator
|
||||
|
||||
def add_bg_from_base64(base64_string):
|
||||
"""Add background image using base64 string."""
|
||||
return f'''
|
||||
<style>
|
||||
.stApp {{
|
||||
background-image: url("data:image/png;base64,{base64_string}");
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
background-attachment: fixed;
|
||||
}}
|
||||
|
||||
/* Enhanced styling for containers */
|
||||
.element-container, .stMarkdown, .stButton {{
|
||||
background-color: rgba(0, 0, 0, 0.7);
|
||||
border-radius: 10px;
|
||||
padding: 20px;
|
||||
margin: 10px 0;
|
||||
backdrop-filter: blur(10px);
|
||||
}}
|
||||
|
||||
/* Typography enhancements */
|
||||
h1, h2, h3 {{
|
||||
color: #ffffff !important;
|
||||
font-family: 'Helvetica Neue', sans-serif;
|
||||
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
|
||||
}}
|
||||
|
||||
p, li {{
|
||||
color: #e0e0e0 !important;
|
||||
font-family: 'Helvetica Neue', sans-serif;
|
||||
}}
|
||||
|
||||
/* Button styling */
|
||||
.stButton > button {{
|
||||
background: linear-gradient(45deg, #1DA1F2, #0C85D0);
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 10px 20px;
|
||||
border-radius: 25px;
|
||||
font-weight: bold;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||
}}
|
||||
|
||||
.stButton > button:hover {{
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 8px rgba(0, 0, 0, 0.2);
|
||||
}}
|
||||
|
||||
/* Tab styling */
|
||||
.stTabs [data-baseweb="tab-list"] {{
|
||||
gap: 8px;
|
||||
background-color: rgba(0, 0, 0, 0.6);
|
||||
padding: 10px;
|
||||
border-radius: 10px;
|
||||
}}
|
||||
|
||||
.stTabs [data-baseweb="tab"] {{
|
||||
background-color: transparent;
|
||||
color: #ffffff;
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
border-radius: 5px;
|
||||
}}
|
||||
|
||||
.stTabs [data-baseweb="tab"]:hover {{
|
||||
background-color: rgba(29, 161, 242, 0.2);
|
||||
}}
|
||||
|
||||
/* Feature card styling */
|
||||
.feature-card {{
|
||||
background: linear-gradient(135deg, rgba(29, 161, 242, 0.1), rgba(0, 0, 0, 0.3));
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
backdrop-filter: blur(10px);
|
||||
padding: 20px;
|
||||
border-radius: 15px;
|
||||
margin-bottom: 20px;
|
||||
transition: transform 0.3s ease;
|
||||
}}
|
||||
|
||||
.feature-card:hover {{
|
||||
transform: translateY(-5px);
|
||||
}}
|
||||
|
||||
/* Status badge styling */
|
||||
.status-badge {{
|
||||
display: inline-block;
|
||||
padding: 5px 15px;
|
||||
border-radius: 20px;
|
||||
font-size: 0.8em;
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
}}
|
||||
|
||||
.status-active {{
|
||||
background: linear-gradient(45deg, #00C853, #69F0AE);
|
||||
color: #000000;
|
||||
}}
|
||||
|
||||
.status-coming-soon {{
|
||||
background: linear-gradient(45deg, #FFD700, #FFA000);
|
||||
color: #000000;
|
||||
}}
|
||||
</style>
|
||||
'''
|
||||
from .twitter_streamlit_ui import (
|
||||
TwitterDashboard,
|
||||
FeatureCard,
|
||||
TweetForm,
|
||||
SettingsForm,
|
||||
Sidebar,
|
||||
Header,
|
||||
Tabs,
|
||||
Breadcrumbs,
|
||||
Theme,
|
||||
save_to_session,
|
||||
get_from_session,
|
||||
clear_session,
|
||||
show_success_message,
|
||||
show_error_message
|
||||
)
|
||||
|
||||
def load_feature_data() -> Dict:
|
||||
"""Load feature data from a structured format."""
|
||||
@@ -232,125 +145,167 @@ def load_feature_data() -> Dict:
|
||||
}
|
||||
}
|
||||
|
||||
def render_feature_card(feature: Dict) -> None:
|
||||
"""Render a single feature card with its details."""
|
||||
status_class = "status-active" if feature['status'] == 'active' else "status-coming-soon"
|
||||
with st.container():
|
||||
st.markdown(f"""
|
||||
<div class='feature-card'>
|
||||
<h3 style='color: #ffffff; margin: 0;'>{feature['icon']} {feature['name']}</h3>
|
||||
<p style='color: #e0e0e0; margin: 10px 0;'>{feature['description']}</p>
|
||||
<span class='status-badge {status_class}'>
|
||||
{feature['status'].title()}
|
||||
</span>
|
||||
</div>
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
def render_category_section(category: Dict) -> None:
|
||||
"""Render a category section with all its features."""
|
||||
st.markdown(f"### {category['icon']} {category['title']}")
|
||||
st.markdown(f"*{category['description']}*")
|
||||
|
||||
col1, col2 = st.columns(2)
|
||||
with col1:
|
||||
render_feature_card(category['features'][0])
|
||||
with col2:
|
||||
render_feature_card(category['features'][1])
|
||||
|
||||
def get_space_background() -> str:
|
||||
"""Return base64 encoded space-themed background."""
|
||||
return """iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mN8/+F9PQAJYgN4hWvGzQAAAABJRU5ErkJggg==""" # This is a placeholder. You'll need to replace with actual base64 image
|
||||
|
||||
def run_dashboard():
|
||||
"""Main function to run the Twitter dashboard."""
|
||||
# Add space-themed background
|
||||
st.markdown(add_bg_from_base64(get_space_background()), unsafe_allow_html=True)
|
||||
# Initialize dashboard
|
||||
dashboard = TwitterDashboard()
|
||||
|
||||
# Enhanced Header with gradient text
|
||||
st.markdown("""
|
||||
<div style='text-align: center; padding: 40px 0;'>
|
||||
<h1 style='
|
||||
font-size: 3em;
|
||||
background: linear-gradient(45deg, #1DA1F2, #ffffff);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
margin-bottom: 10px;
|
||||
'>🐦 Twitter AI Writer</h1>
|
||||
<p style='
|
||||
font-size: 1.2em;
|
||||
color: #e0e0e0;
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
'>Your all-in-one Twitter content creation and management platform.
|
||||
Harness the power of AI to enhance your Twitter marketing strategy.</p>
|
||||
</div>
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
# Load feature data
|
||||
features = load_feature_data()
|
||||
|
||||
# Create tabs with enhanced styling
|
||||
tab1, tab2, tab3 = st.tabs(["🎯 Quick Actions", "📊 Analytics", "⚙️ Settings"])
|
||||
|
||||
with tab1:
|
||||
st.markdown("<h2 style='color: #ffffff;'>🚀 Quick Actions</h2>", unsafe_allow_html=True)
|
||||
col1, col2, col3 = st.columns(3)
|
||||
|
||||
with col1:
|
||||
if st.button("📝 Create New Tweet", use_container_width=True):
|
||||
# Call the Smart Tweet Generator
|
||||
smart_tweet_generator()
|
||||
with col2:
|
||||
st.button("📅 Schedule Content", use_container_width=True)
|
||||
with col3:
|
||||
st.button("📊 View Analytics", use_container_width=True)
|
||||
|
||||
with tab2:
|
||||
st.markdown("### 📈 Analytics Dashboard")
|
||||
st.info("Analytics features coming soon! Stay tuned for detailed insights and performance metrics.")
|
||||
|
||||
with tab3:
|
||||
st.markdown("### ⚙️ Settings")
|
||||
st.info("Settings and configuration options coming soon!")
|
||||
|
||||
# Main content area
|
||||
st.markdown("## 🛠️ Available Tools")
|
||||
|
||||
# Render each category
|
||||
for category in features.values():
|
||||
render_category_section(category)
|
||||
|
||||
# If this is the tweet generation category and the Smart Tweet Generator is active,
|
||||
# add a button to launch it
|
||||
if category["title"] == "Tweet Generation & Optimization" and category["features"][0]["status"] == "active":
|
||||
if st.button(f"🚀 Launch {category['features'][0]['name']}", use_container_width=True):
|
||||
category["features"][0]["function"]()
|
||||
# Setup navigation
|
||||
sidebar = Sidebar(title="Twitter Tools")
|
||||
sidebar.add_menu_item("Dashboard", "📊", "dashboard")
|
||||
sidebar.add_menu_item("Tweet Generator", "✍️", "tweet_generator")
|
||||
sidebar.add_menu_item("Analytics", "📈", "analytics")
|
||||
sidebar.add_menu_item("Settings", "⚙️", "settings")
|
||||
|
||||
# Setup header
|
||||
header = Header(
|
||||
title="Twitter AI Writer",
|
||||
subtitle="Your all-in-one Twitter content creation and management platform"
|
||||
)
|
||||
header.add_action("New Tweet", "✏️", lambda: save_to_session("current_page", "tweet_generator"))
|
||||
header.add_action("Refresh", "🔄", lambda: st.experimental_rerun())
|
||||
|
||||
# Setup tabs
|
||||
tabs = Tabs()
|
||||
tabs.add_tab("Overview", "📊", lambda: render_overview(features))
|
||||
tabs.add_tab("Recent Tweets", "🐦", lambda: render_recent_tweets())
|
||||
tabs.add_tab("Analytics", "📈", lambda: render_analytics())
|
||||
|
||||
# Setup breadcrumbs
|
||||
breadcrumbs = Breadcrumbs()
|
||||
breadcrumbs.add_item("Home", "dashboard", "🏠")
|
||||
breadcrumbs.add_item(get_from_session("current_page", "Dashboard").title())
|
||||
|
||||
# Render dashboard
|
||||
dashboard.render()
|
||||
|
||||
# Enhanced Footer
|
||||
st.markdown("---")
|
||||
st.markdown("""
|
||||
<div style='text-align: center; padding: 20px; background: rgba(0, 0, 0, 0.5); border-radius: 10px;'>
|
||||
<p style='color: #ffffff; margin-bottom: 10px;'>Need assistance? We're here to help!</p>
|
||||
<div style='display: flex; justify-content: center; gap: 20px;'>
|
||||
<a href='#' style='
|
||||
text-decoration: none;
|
||||
color: #1DA1F2;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
padding: 8px 20px;
|
||||
border-radius: 20px;
|
||||
transition: all 0.3s ease;
|
||||
'>📚 Documentation</a>
|
||||
<a href='#' style='
|
||||
text-decoration: none;
|
||||
color: #1DA1F2;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
padding: 8px 20px;
|
||||
border-radius: 20px;
|
||||
transition: all 0.3s ease;
|
||||
'>💬 Contact Support</a>
|
||||
</div>
|
||||
</div>
|
||||
""", unsafe_allow_html=True)
|
||||
def render_overview(features: Dict):
|
||||
"""Render the overview tab content."""
|
||||
# Feature cards
|
||||
col1, col2, col3 = st.columns(3)
|
||||
|
||||
with col1:
|
||||
FeatureCard(
|
||||
title="Tweet Generator",
|
||||
description="Create engaging tweets with AI assistance",
|
||||
icon="✍️",
|
||||
features=[
|
||||
{
|
||||
"name": "AI-Powered",
|
||||
"description": "Generate tweets using advanced AI"
|
||||
},
|
||||
{
|
||||
"name": "Customizable",
|
||||
"description": "Adjust tone, length, and style"
|
||||
}
|
||||
],
|
||||
on_click=lambda: save_to_session("current_page", "tweet_generator")
|
||||
).render()
|
||||
|
||||
with col2:
|
||||
FeatureCard(
|
||||
title="Analytics",
|
||||
description="Track your tweet performance",
|
||||
icon="📈",
|
||||
features=[
|
||||
{
|
||||
"name": "Engagement",
|
||||
"description": "Monitor likes, retweets, and replies"
|
||||
},
|
||||
{
|
||||
"name": "Growth",
|
||||
"description": "Track follower growth over time"
|
||||
}
|
||||
]
|
||||
).render()
|
||||
|
||||
with col3:
|
||||
FeatureCard(
|
||||
title="Settings",
|
||||
description="Customize your experience",
|
||||
icon="⚙️",
|
||||
features=[
|
||||
{
|
||||
"name": "Preferences",
|
||||
"description": "Set your default options"
|
||||
},
|
||||
{
|
||||
"name": "API",
|
||||
"description": "Configure Twitter API settings"
|
||||
}
|
||||
]
|
||||
).render()
|
||||
|
||||
def render_recent_tweets():
|
||||
"""Render the recent tweets tab content."""
|
||||
# Tweet form
|
||||
tweet_form = TweetForm(
|
||||
on_submit=lambda: handle_tweet_submit()
|
||||
)
|
||||
tweet_form.render()
|
||||
|
||||
# Recent tweets
|
||||
st.markdown("### Recent Tweets")
|
||||
tweets = get_from_session("tweets", [])
|
||||
for tweet in tweets:
|
||||
TweetCard(
|
||||
content=tweet["content"],
|
||||
engagement_score=tweet["engagement_score"],
|
||||
hashtags=tweet["hashtags"],
|
||||
emojis=tweet["emojis"],
|
||||
metrics=tweet["metrics"],
|
||||
on_copy=lambda: copy_tweet(tweet),
|
||||
on_save=lambda: save_tweet(tweet)
|
||||
).render()
|
||||
|
||||
def render_analytics():
|
||||
"""Render the analytics tab content."""
|
||||
st.markdown("### Tweet Analytics")
|
||||
st.info("Analytics features coming soon!")
|
||||
|
||||
def handle_tweet_submit():
|
||||
"""Handle tweet form submission."""
|
||||
# Get form data
|
||||
content = get_from_session("tweet_content")
|
||||
tone = get_from_session("tone")
|
||||
length = get_from_session("length")
|
||||
hashtags = get_from_session("hashtags")
|
||||
emojis = get_from_session("emojis")
|
||||
engagement_boost = get_from_session("engagement_boost")
|
||||
|
||||
# Create tweet object
|
||||
tweet = {
|
||||
"content": content,
|
||||
"tone": tone,
|
||||
"length": length,
|
||||
"hashtags": hashtags,
|
||||
"emojis": emojis,
|
||||
"engagement_score": engagement_boost,
|
||||
"metrics": {
|
||||
"Engagement": engagement_boost,
|
||||
"Reach": engagement_boost * 0.8,
|
||||
"Growth": engagement_boost * 0.6
|
||||
}
|
||||
}
|
||||
|
||||
# Add to tweets list
|
||||
tweets = get_from_session("tweets", [])
|
||||
tweets.append(tweet)
|
||||
save_to_session("tweets", tweets)
|
||||
|
||||
# Show success message
|
||||
show_success_message("Tweet created successfully!")
|
||||
|
||||
def copy_tweet(tweet: Dict):
|
||||
"""Copy tweet to clipboard."""
|
||||
show_success_message("Tweet copied to clipboard!")
|
||||
|
||||
def save_tweet(tweet: Dict):
|
||||
"""Save tweet for later."""
|
||||
show_success_message("Tweet saved!")
|
||||
|
||||
if __name__ == "__main__":
|
||||
run_dashboard()
|
||||
203
lib/ai_writers/twitter_writers/twitter_streamlit_ui/README.md
Normal file
203
lib/ai_writers/twitter_writers/twitter_streamlit_ui/README.md
Normal file
@@ -0,0 +1,203 @@
|
||||
# Twitter Streamlit UI Components
|
||||
|
||||
This module provides a unified, reusable UI component library for all Twitter-related features in the AI Writer suite. It implements best practices for Streamlit UI development and ensures consistency across all Twitter tools.
|
||||
|
||||
## Structure
|
||||
|
||||
```
|
||||
twitter_streamlit_ui/
|
||||
├── components/ # Reusable UI components
|
||||
│ ├── __init__.py
|
||||
│ ├── cards.py # Card components (feature cards, tweet cards)
|
||||
│ ├── forms.py # Form components (input forms, settings forms)
|
||||
│ ├── navigation.py # Navigation components (tabs, sidebar)
|
||||
│ ├── feedback.py # Feedback components (loading, errors, success)
|
||||
│ └── layout.py # Layout components (containers, columns)
|
||||
├── styles/ # CSS and styling
|
||||
│ ├── __init__.py
|
||||
│ ├── theme.py # Theme configuration
|
||||
│ ├── components.py # Component-specific styles
|
||||
│ └── animations.py # Animation styles
|
||||
├── utils/ # UI utilities
|
||||
│ ├── __init__.py
|
||||
│ ├── state.py # State management
|
||||
│ ├── validation.py # Input validation
|
||||
│ └── performance.py # Performance optimizations
|
||||
└── README.md # This file
|
||||
```
|
||||
|
||||
## Key Improvements
|
||||
|
||||
### 1. Consistent UI Components
|
||||
|
||||
- **Card Components**
|
||||
- Feature cards with consistent styling
|
||||
- Tweet cards with standardized layout
|
||||
- Status badges with unified design
|
||||
|
||||
- **Form Components**
|
||||
- Standardized input forms
|
||||
- Consistent validation feedback
|
||||
- Unified error handling
|
||||
|
||||
- **Navigation Components**
|
||||
- Consistent tab styling
|
||||
- Standardized sidebar navigation
|
||||
- Breadcrumb navigation
|
||||
|
||||
### 2. Enhanced User Experience
|
||||
|
||||
- **Loading States**
|
||||
- Progress indicators for long operations
|
||||
- Skeleton loading for content
|
||||
- Smooth transitions between states
|
||||
|
||||
- **Feedback Mechanisms**
|
||||
- Toast notifications for actions
|
||||
- Error messages with recovery options
|
||||
- Success confirmations
|
||||
|
||||
- **Responsive Design**
|
||||
- Mobile-friendly layouts
|
||||
- Adaptive column systems
|
||||
- Flexible containers
|
||||
|
||||
### 3. Performance Optimizations
|
||||
|
||||
- **State Management**
|
||||
- Centralized state handling
|
||||
- Efficient data persistence
|
||||
- Optimized re-rendering
|
||||
|
||||
- **Resource Loading**
|
||||
- Lazy loading of components
|
||||
- Optimized image loading
|
||||
- Cached computations
|
||||
|
||||
### 4. Accessibility Features
|
||||
|
||||
- **Keyboard Navigation**
|
||||
- Focus management
|
||||
- Keyboard shortcuts
|
||||
- ARIA labels
|
||||
|
||||
- **Visual Accessibility**
|
||||
- High contrast themes
|
||||
- Screen reader support
|
||||
- Color blind friendly
|
||||
|
||||
### 5. Error Handling
|
||||
|
||||
- **Graceful Degradation**
|
||||
- Fallback UI components
|
||||
- Error boundaries
|
||||
- Recovery options
|
||||
|
||||
- **User Feedback**
|
||||
- Clear error messages
|
||||
- Actionable suggestions
|
||||
- Help documentation
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Component Usage
|
||||
|
||||
```python
|
||||
from twitter_streamlit_ui.components.cards import FeatureCard
|
||||
from twitter_streamlit_ui.components.forms import TweetForm
|
||||
from twitter_streamlit_ui.styles.theme import apply_theme
|
||||
|
||||
# Apply theme
|
||||
apply_theme()
|
||||
|
||||
# Use components
|
||||
feature_card = FeatureCard(
|
||||
title="Tweet Generator",
|
||||
description="Create engaging tweets with AI",
|
||||
icon="🐦"
|
||||
)
|
||||
feature_card.render()
|
||||
|
||||
tweet_form = TweetForm()
|
||||
tweet_form.render()
|
||||
```
|
||||
|
||||
### State Management
|
||||
|
||||
```python
|
||||
from twitter_streamlit_ui.utils.state import StateManager
|
||||
|
||||
# Initialize state
|
||||
state = StateManager()
|
||||
state.initialize()
|
||||
|
||||
# Update state
|
||||
state.update("current_tweet", tweet_data)
|
||||
```
|
||||
|
||||
### Error Handling
|
||||
|
||||
```python
|
||||
from twitter_streamlit_ui.components.feedback import ErrorBoundary
|
||||
|
||||
with ErrorBoundary():
|
||||
# Your code here
|
||||
pass
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Component Reusability**
|
||||
- Use existing components when possible
|
||||
- Create new components only when necessary
|
||||
- Follow the established patterns
|
||||
|
||||
2. **State Management**
|
||||
- Use the StateManager for all state
|
||||
- Avoid direct session state manipulation
|
||||
- Keep state updates atomic
|
||||
|
||||
3. **Performance**
|
||||
- Use lazy loading for heavy components
|
||||
- Implement caching where appropriate
|
||||
- Monitor render performance
|
||||
|
||||
4. **Accessibility**
|
||||
- Include ARIA labels
|
||||
- Ensure keyboard navigation
|
||||
- Test with screen readers
|
||||
|
||||
5. **Error Handling**
|
||||
- Use ErrorBoundary components
|
||||
- Provide clear error messages
|
||||
- Include recovery options
|
||||
|
||||
## Future Improvements
|
||||
|
||||
1. **Component Library**
|
||||
- Add more specialized components
|
||||
- Enhance existing components
|
||||
- Create component documentation
|
||||
|
||||
2. **Theme System**
|
||||
- Add more theme options
|
||||
- Implement theme switching
|
||||
- Create custom theme builder
|
||||
|
||||
3. **Performance**
|
||||
- Implement virtual scrolling
|
||||
- Add performance monitoring
|
||||
- Optimize resource loading
|
||||
|
||||
4. **Testing**
|
||||
- Add component tests
|
||||
- Implement E2E tests
|
||||
- Create test documentation
|
||||
|
||||
## Contributing
|
||||
|
||||
1. Follow the established patterns
|
||||
2. Add tests for new components
|
||||
3. Update documentation
|
||||
4. Ensure accessibility
|
||||
5. Optimize performance
|
||||
@@ -0,0 +1,66 @@
|
||||
"""
|
||||
Twitter Streamlit UI package.
|
||||
Provides a modern and user-friendly interface for Twitter tools.
|
||||
"""
|
||||
|
||||
from .dashboard import TwitterDashboard
|
||||
from .components.cards import FeatureCard, TweetCard
|
||||
from .components.forms import TweetForm, SettingsForm
|
||||
from .components.navigation import Sidebar, Header, Tabs, Breadcrumbs
|
||||
from .styles.theme import Theme
|
||||
from .utils.helpers import (
|
||||
save_to_session,
|
||||
get_from_session,
|
||||
clear_session,
|
||||
save_to_file,
|
||||
load_from_file,
|
||||
format_datetime,
|
||||
parse_datetime,
|
||||
validate_tweet_content,
|
||||
validate_hashtags,
|
||||
validate_emojis,
|
||||
calculate_engagement_score,
|
||||
generate_tweet_metrics,
|
||||
copy_to_clipboard,
|
||||
show_success_message,
|
||||
show_error_message,
|
||||
show_info_message,
|
||||
show_warning_message,
|
||||
create_download_button,
|
||||
create_upload_button
|
||||
)
|
||||
|
||||
__version__ = "1.0.0"
|
||||
__author__ = "AI Writer Team"
|
||||
|
||||
__all__ = [
|
||||
"TwitterDashboard",
|
||||
"FeatureCard",
|
||||
"TweetCard",
|
||||
"TweetForm",
|
||||
"SettingsForm",
|
||||
"Sidebar",
|
||||
"Header",
|
||||
"Tabs",
|
||||
"Breadcrumbs",
|
||||
"Theme",
|
||||
"save_to_session",
|
||||
"get_from_session",
|
||||
"clear_session",
|
||||
"save_to_file",
|
||||
"load_from_file",
|
||||
"format_datetime",
|
||||
"parse_datetime",
|
||||
"validate_tweet_content",
|
||||
"validate_hashtags",
|
||||
"validate_emojis",
|
||||
"calculate_engagement_score",
|
||||
"generate_tweet_metrics",
|
||||
"copy_to_clipboard",
|
||||
"show_success_message",
|
||||
"show_error_message",
|
||||
"show_info_message",
|
||||
"show_warning_message",
|
||||
"create_download_button",
|
||||
"create_upload_button"
|
||||
]
|
||||
@@ -0,0 +1,174 @@
|
||||
"""
|
||||
Card components for Twitter UI.
|
||||
Provides consistent card layouts for features and tweets.
|
||||
"""
|
||||
|
||||
import streamlit as st
|
||||
from typing import Dict, Any, Optional, List
|
||||
from ..styles.theme import Theme
|
||||
|
||||
class BaseCard:
|
||||
"""Base class for all card components."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
title: str,
|
||||
description: str,
|
||||
icon: Optional[str] = None,
|
||||
status: Optional[str] = None,
|
||||
actions: Optional[List[Dict[str, Any]]] = None
|
||||
):
|
||||
self.title = title
|
||||
self.description = description
|
||||
self.icon = icon
|
||||
self.status = status
|
||||
self.actions = actions or []
|
||||
|
||||
def render(self) -> None:
|
||||
"""Render the card with consistent styling."""
|
||||
with st.container():
|
||||
st.markdown(f"""
|
||||
<div class="card">
|
||||
<div style="display: flex; align-items: center; margin-bottom: {Theme.SPACING["sm"]};">
|
||||
{f'<span style="font-size: 1.5em; margin-right: {Theme.SPACING["sm"]};">{self.icon}</span>' if self.icon else ''}
|
||||
<h3 style="margin: 0;">{self.title}</h3>
|
||||
</div>
|
||||
<p style="color: {Theme.COLORS["text_secondary"]}; margin: {Theme.SPACING["sm"]} 0;">
|
||||
{self.description}
|
||||
</p>
|
||||
{f'<span class="status-badge status-{self.status}">{self.status.title()}</span>' if self.status else ''}
|
||||
</div>
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
if self.actions:
|
||||
cols = st.columns(len(self.actions))
|
||||
for i, action in enumerate(self.actions):
|
||||
with cols[i]:
|
||||
if st.button(
|
||||
action["label"],
|
||||
key=f"action_{i}",
|
||||
help=action.get("help"),
|
||||
use_container_width=True
|
||||
):
|
||||
action["callback"]()
|
||||
|
||||
class FeatureCard(BaseCard):
|
||||
"""Card component for displaying features."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
title: str,
|
||||
description: str,
|
||||
icon: str,
|
||||
status: str = "active",
|
||||
features: Optional[List[Dict[str, Any]]] = None,
|
||||
on_click: Optional[callable] = None
|
||||
):
|
||||
super().__init__(title, description, icon, status)
|
||||
self.features = features or []
|
||||
self.on_click = on_click
|
||||
|
||||
def render(self) -> None:
|
||||
"""Render the feature card with enhanced styling."""
|
||||
with st.container():
|
||||
st.markdown(f"""
|
||||
<div class="card feature-card">
|
||||
<div style="display: flex; align-items: center; margin-bottom: {Theme.SPACING["sm"]};">
|
||||
<span style="font-size: 1.5em; margin-right: {Theme.SPACING["sm"]};">{self.icon}</span>
|
||||
<h3 style="margin: 0;">{self.title}</h3>
|
||||
</div>
|
||||
<p style="color: {Theme.COLORS["text_secondary"]}; margin: {Theme.SPACING["sm"]} 0;">
|
||||
{self.description}
|
||||
</p>
|
||||
<span class="status-badge status-{self.status}">{self.status.title()}</span>
|
||||
</div>
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
if self.features:
|
||||
for feature in self.features:
|
||||
st.markdown(f"""
|
||||
<div style="margin-left: {Theme.SPACING["lg"]}; margin-top: {Theme.SPACING["sm"]};">
|
||||
<p style="margin: 0;">
|
||||
<strong>{feature["name"]}</strong>: {feature["description"]}
|
||||
</p>
|
||||
</div>
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
if self.on_click:
|
||||
if st.button(
|
||||
f"Launch {self.title}",
|
||||
key=f"launch_{self.title.lower().replace(' ', '_')}",
|
||||
use_container_width=True
|
||||
):
|
||||
self.on_click()
|
||||
|
||||
class TweetCard(BaseCard):
|
||||
"""Card component for displaying tweets."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
content: str,
|
||||
engagement_score: float,
|
||||
hashtags: List[str],
|
||||
emojis: List[str],
|
||||
metrics: Optional[Dict[str, Any]] = None,
|
||||
on_copy: Optional[callable] = None,
|
||||
on_save: Optional[callable] = None
|
||||
):
|
||||
super().__init__(
|
||||
title="Tweet",
|
||||
description=content,
|
||||
icon="🐦",
|
||||
actions=[
|
||||
{
|
||||
"label": "Copy",
|
||||
"callback": on_copy or (lambda: None),
|
||||
"help": "Copy tweet to clipboard"
|
||||
},
|
||||
{
|
||||
"label": "Save",
|
||||
"callback": on_save or (lambda: None),
|
||||
"help": "Save tweet for later"
|
||||
}
|
||||
]
|
||||
)
|
||||
self.engagement_score = engagement_score
|
||||
self.hashtags = hashtags
|
||||
self.emojis = emojis
|
||||
self.metrics = metrics or {}
|
||||
|
||||
def render(self) -> None:
|
||||
"""Render the tweet card with metrics and actions."""
|
||||
with st.container():
|
||||
st.markdown(f"""
|
||||
<div class="card tweet-card">
|
||||
<div style="display: flex; align-items: center; margin-bottom: {Theme.SPACING["sm"]};">
|
||||
<span style="font-size: 1.5em; margin-right: {Theme.SPACING["sm"]};">{self.icon}</span>
|
||||
<h3 style="margin: 0;">Tweet</h3>
|
||||
</div>
|
||||
<p style="color: {Theme.COLORS["text"]}; margin: {Theme.SPACING["sm"]} 0;">
|
||||
{self.description}
|
||||
</p>
|
||||
<div style="display: flex; gap: {Theme.SPACING["sm"]}; margin: {Theme.SPACING["sm"]} 0;">
|
||||
{''.join(f'<span style="color: {Theme.COLORS["primary"]};">{tag}</span>' for tag in self.hashtags)}
|
||||
</div>
|
||||
<div style="display: flex; gap: {Theme.SPACING["sm"]}; margin: {Theme.SPACING["sm"]} 0;">
|
||||
{''.join(f'<span>{emoji}</span>' for emoji in self.emojis)}
|
||||
</div>
|
||||
<div style="margin-top: {Theme.SPACING["md"]};">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center;">
|
||||
<span>Engagement Score: {self.engagement_score}%</span>
|
||||
<div style="display: flex; gap: {Theme.SPACING["sm"]};">
|
||||
<button class="stButton" onclick="copyTweet()">Copy</button>
|
||||
<button class="stButton" onclick="saveTweet()">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
if self.metrics:
|
||||
cols = st.columns(len(self.metrics))
|
||||
for i, (metric, value) in enumerate(self.metrics.items()):
|
||||
with cols[i]:
|
||||
st.metric(metric, f"{value}%")
|
||||
@@ -0,0 +1,255 @@
|
||||
"""
|
||||
Form components for Twitter UI.
|
||||
Provides consistent form layouts and input validation.
|
||||
"""
|
||||
|
||||
import streamlit as st
|
||||
from typing import Dict, Any, Optional, List, Callable
|
||||
from ..styles.theme import Theme
|
||||
|
||||
class BaseForm:
|
||||
"""Base class for all form components."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
title: str,
|
||||
description: Optional[str] = None,
|
||||
on_submit: Optional[Callable] = None
|
||||
):
|
||||
self.title = title
|
||||
self.description = description
|
||||
self.on_submit = on_submit
|
||||
self.fields: Dict[str, Any] = {}
|
||||
|
||||
def add_field(
|
||||
self,
|
||||
key: str,
|
||||
label: str,
|
||||
field_type: str = "text",
|
||||
required: bool = False,
|
||||
help_text: Optional[str] = None,
|
||||
options: Optional[List[str]] = None,
|
||||
default: Any = None,
|
||||
validation: Optional[Callable] = None
|
||||
) -> None:
|
||||
"""Add a field to the form."""
|
||||
self.fields[key] = {
|
||||
"label": label,
|
||||
"type": field_type,
|
||||
"required": required,
|
||||
"help_text": help_text,
|
||||
"options": options,
|
||||
"default": default,
|
||||
"validation": validation
|
||||
}
|
||||
|
||||
def validate(self) -> bool:
|
||||
"""Validate all form fields."""
|
||||
for key, field in self.fields.items():
|
||||
if field["required"] and not st.session_state.get(key):
|
||||
st.error(f"{field['label']} is required")
|
||||
return False
|
||||
if field["validation"] and not field["validation"](st.session_state.get(key)):
|
||||
return False
|
||||
return True
|
||||
|
||||
def render(self) -> None:
|
||||
"""Render the form with consistent styling."""
|
||||
with st.container():
|
||||
st.markdown(f"""
|
||||
<div class="form-container">
|
||||
<h3 style="margin-bottom: {Theme.SPACING['sm']};">{self.title}</h3>
|
||||
{f'<p style="color: {Theme.COLORS["text_secondary"]}; margin-bottom: {Theme.SPACING["md"]};">{self.description}</p>' if self.description else ''}
|
||||
</div>
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
for key, field in self.fields.items():
|
||||
if field["type"] == "text":
|
||||
st.text_input(
|
||||
field["label"],
|
||||
key=key,
|
||||
help=field["help_text"],
|
||||
value=field["default"]
|
||||
)
|
||||
elif field["type"] == "textarea":
|
||||
st.text_area(
|
||||
field["label"],
|
||||
key=key,
|
||||
help=field["help_text"],
|
||||
value=field["default"]
|
||||
)
|
||||
elif field["type"] == "select":
|
||||
st.selectbox(
|
||||
field["label"],
|
||||
options=field["options"],
|
||||
key=key,
|
||||
help=field["help_text"],
|
||||
index=field["options"].index(field["default"]) if field["default"] in field["options"] else 0
|
||||
)
|
||||
elif field["type"] == "multiselect":
|
||||
st.multiselect(
|
||||
field["label"],
|
||||
options=field["options"],
|
||||
key=key,
|
||||
help=field["help_text"],
|
||||
default=field["default"]
|
||||
)
|
||||
elif field["type"] == "number":
|
||||
st.number_input(
|
||||
field["label"],
|
||||
key=key,
|
||||
help=field["help_text"],
|
||||
value=field["default"]
|
||||
)
|
||||
elif field["type"] == "slider":
|
||||
st.slider(
|
||||
field["label"],
|
||||
key=key,
|
||||
help=field["help_text"],
|
||||
value=field["default"]
|
||||
)
|
||||
elif field["type"] == "checkbox":
|
||||
st.checkbox(
|
||||
field["label"],
|
||||
key=key,
|
||||
help=field["help_text"],
|
||||
value=field["default"]
|
||||
)
|
||||
|
||||
if st.button("Submit", use_container_width=True):
|
||||
if self.validate() and self.on_submit:
|
||||
self.on_submit()
|
||||
|
||||
class TweetForm(BaseForm):
|
||||
"""Form component for tweet generation."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
on_submit: Optional[Callable] = None,
|
||||
default_tone: str = "professional",
|
||||
default_length: str = "medium"
|
||||
):
|
||||
super().__init__(
|
||||
title="Generate Tweet",
|
||||
description="Create engaging tweets with AI assistance",
|
||||
on_submit=on_submit
|
||||
)
|
||||
|
||||
# Add tweet content field
|
||||
self.add_field(
|
||||
"tweet_content",
|
||||
"Tweet Content",
|
||||
field_type="textarea",
|
||||
required=True,
|
||||
help_text="Enter your tweet content or topic"
|
||||
)
|
||||
|
||||
# Add tone selection
|
||||
self.add_field(
|
||||
"tone",
|
||||
"Tweet Tone",
|
||||
field_type="select",
|
||||
options=["professional", "casual", "humorous", "informative", "inspirational"],
|
||||
default=default_tone,
|
||||
help_text="Select the tone for your tweet"
|
||||
)
|
||||
|
||||
# Add length selection
|
||||
self.add_field(
|
||||
"length",
|
||||
"Tweet Length",
|
||||
field_type="select",
|
||||
options=["short", "medium", "long"],
|
||||
default=default_length,
|
||||
help_text="Select the desired length of your tweet"
|
||||
)
|
||||
|
||||
# Add hashtag options
|
||||
self.add_field(
|
||||
"hashtags",
|
||||
"Hashtags",
|
||||
field_type="multiselect",
|
||||
options=["#AI", "#Tech", "#Innovation", "#Business", "#Marketing"],
|
||||
help_text="Select relevant hashtags"
|
||||
)
|
||||
|
||||
# Add emoji options
|
||||
self.add_field(
|
||||
"emojis",
|
||||
"Emojis",
|
||||
field_type="multiselect",
|
||||
options=["🚀", "💡", "🎯", "🔥", "✨"],
|
||||
help_text="Select emojis to include"
|
||||
)
|
||||
|
||||
# Add engagement settings
|
||||
self.add_field(
|
||||
"engagement_boost",
|
||||
"Engagement Boost",
|
||||
field_type="slider",
|
||||
default=50,
|
||||
help_text="Adjust the engagement optimization level"
|
||||
)
|
||||
|
||||
class SettingsForm(BaseForm):
|
||||
"""Form component for user settings."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
on_submit: Optional[Callable] = None,
|
||||
default_settings: Optional[Dict[str, Any]] = None
|
||||
):
|
||||
super().__init__(
|
||||
title="User Settings",
|
||||
description="Customize your Twitter experience",
|
||||
on_submit=on_submit
|
||||
)
|
||||
|
||||
settings = default_settings or {}
|
||||
|
||||
# Add API settings
|
||||
self.add_field(
|
||||
"api_key",
|
||||
"Twitter API Key",
|
||||
field_type="text",
|
||||
help_text="Enter your Twitter API key",
|
||||
default=settings.get("api_key", "")
|
||||
)
|
||||
|
||||
# Add theme settings
|
||||
self.add_field(
|
||||
"theme",
|
||||
"Theme",
|
||||
field_type="select",
|
||||
options=["light", "dark", "system"],
|
||||
default=settings.get("theme", "system"),
|
||||
help_text="Select your preferred theme"
|
||||
)
|
||||
|
||||
# Add notification settings
|
||||
self.add_field(
|
||||
"notifications",
|
||||
"Enable Notifications",
|
||||
field_type="checkbox",
|
||||
default=settings.get("notifications", True),
|
||||
help_text="Receive notifications for important updates"
|
||||
)
|
||||
|
||||
# Add auto-save settings
|
||||
self.add_field(
|
||||
"auto_save",
|
||||
"Auto-save Drafts",
|
||||
field_type="checkbox",
|
||||
default=settings.get("auto_save", True),
|
||||
help_text="Automatically save tweet drafts"
|
||||
)
|
||||
|
||||
# Add language settings
|
||||
self.add_field(
|
||||
"language",
|
||||
"Language",
|
||||
field_type="select",
|
||||
options=["English", "Spanish", "French", "German", "Japanese"],
|
||||
default=settings.get("language", "English"),
|
||||
help_text="Select your preferred language"
|
||||
)
|
||||
@@ -0,0 +1,222 @@
|
||||
"""
|
||||
Navigation components for Twitter UI.
|
||||
Provides consistent navigation and layout structure.
|
||||
"""
|
||||
|
||||
import streamlit as st
|
||||
from typing import Dict, Any, Optional, List
|
||||
from ..styles.theme import Theme
|
||||
|
||||
class Sidebar:
|
||||
"""Sidebar navigation component."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
title: str = "Twitter Tools",
|
||||
logo: Optional[str] = None,
|
||||
menu_items: Optional[List[Dict[str, Any]]] = None
|
||||
):
|
||||
self.title = title
|
||||
self.logo = logo
|
||||
self.menu_items = menu_items or []
|
||||
|
||||
def add_menu_item(
|
||||
self,
|
||||
label: str,
|
||||
icon: str,
|
||||
page: str,
|
||||
badge: Optional[str] = None
|
||||
) -> None:
|
||||
"""Add a menu item to the sidebar."""
|
||||
self.menu_items.append({
|
||||
"label": label,
|
||||
"icon": icon,
|
||||
"page": page,
|
||||
"badge": badge
|
||||
})
|
||||
|
||||
def render(self) -> None:
|
||||
"""Render the sidebar with consistent styling."""
|
||||
with st.sidebar:
|
||||
# Logo and title
|
||||
if self.logo:
|
||||
st.image(self.logo, width=50)
|
||||
st.markdown(f"""
|
||||
<h2 style="margin: {Theme.SPACING["sm"]} 0;">{self.title}</h2>
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
# Menu items
|
||||
for item in self.menu_items:
|
||||
st.markdown(f"""
|
||||
<div class="menu-item">
|
||||
<span style="font-size: 1.2em; margin-right: {Theme.SPACING["sm"]};">{item["icon"]}</span>
|
||||
<span>{item["label"]}</span>
|
||||
{f'<span class="badge">{item["badge"]}</span>' if item.get("badge") else ""}
|
||||
</div>
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
if st.button(
|
||||
item["label"],
|
||||
key=f"nav_{item['page']}",
|
||||
use_container_width=True
|
||||
):
|
||||
st.session_state["current_page"] = item["page"]
|
||||
|
||||
class Header:
|
||||
"""Header navigation component."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
title: str,
|
||||
subtitle: Optional[str] = None,
|
||||
actions: Optional[List[Dict[str, Any]]] = None
|
||||
):
|
||||
self.title = title
|
||||
self.subtitle = subtitle
|
||||
self.actions = actions or []
|
||||
|
||||
def add_action(
|
||||
self,
|
||||
label: str,
|
||||
icon: str,
|
||||
callback: callable,
|
||||
help_text: Optional[str] = None
|
||||
) -> None:
|
||||
"""Add an action button to the header."""
|
||||
self.actions.append({
|
||||
"label": label,
|
||||
"icon": icon,
|
||||
"callback": callback,
|
||||
"help_text": help_text
|
||||
})
|
||||
|
||||
def render(self) -> None:
|
||||
"""Render the header with consistent styling."""
|
||||
# Build action buttons HTML
|
||||
action_buttons = []
|
||||
for action in self.actions:
|
||||
help_text = action.get("help_text", "")
|
||||
action_buttons.append(f"""
|
||||
<button class="header-action" title="{help_text}">
|
||||
<span style="font-size: 1.2em; margin-right: {Theme.SPACING["xs"]};">{action["icon"]}</span>
|
||||
{action["label"]}
|
||||
</button>
|
||||
""")
|
||||
|
||||
st.markdown(f"""
|
||||
<div class="header">
|
||||
<div>
|
||||
<h1 style="margin: 0;">{self.title}</h1>
|
||||
{f'<p style="color: {Theme.COLORS["text_secondary"]}; margin: {Theme.SPACING["xs"]} 0;">{self.subtitle}</p>' if self.subtitle else ""}
|
||||
</div>
|
||||
<div style="display: flex; gap: {Theme.SPACING["sm"]};">
|
||||
{''.join(action_buttons)}
|
||||
</div>
|
||||
</div>
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
# Add action button callbacks
|
||||
for i, action in enumerate(self.actions):
|
||||
if st.button(
|
||||
action["label"],
|
||||
key=f"header_action_{i}",
|
||||
help=action.get("help_text")
|
||||
):
|
||||
action["callback"]()
|
||||
|
||||
class Tabs:
|
||||
"""Tab navigation component."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
tabs: Optional[List[Dict[str, Any]]] = None,
|
||||
default_tab: Optional[str] = None
|
||||
):
|
||||
self.tabs = tabs or []
|
||||
self.default_tab = default_tab
|
||||
|
||||
def add_tab(
|
||||
self,
|
||||
label: str,
|
||||
icon: Optional[str] = None,
|
||||
content: Optional[callable] = None
|
||||
) -> None:
|
||||
"""Add a tab to the navigation."""
|
||||
self.tabs.append({
|
||||
"label": label,
|
||||
"icon": icon,
|
||||
"content": content
|
||||
})
|
||||
|
||||
def render(self) -> None:
|
||||
"""Render the tabs with consistent styling."""
|
||||
if not self.tabs:
|
||||
return
|
||||
|
||||
# Create tab labels with icons
|
||||
tab_labels = [
|
||||
f"{tab['icon']} {tab['label']}" if tab.get('icon') else tab['label']
|
||||
for tab in self.tabs
|
||||
]
|
||||
|
||||
# Get current tab from session state or use default
|
||||
current_tab = st.session_state.get("current_tab", self.default_tab or self.tabs[0]["label"])
|
||||
|
||||
# Render tabs
|
||||
selected_tab = st.tabs(tab_labels)[tab_labels.index(current_tab)]
|
||||
|
||||
# Update session state
|
||||
st.session_state["current_tab"] = current_tab
|
||||
|
||||
# Render tab content
|
||||
with selected_tab:
|
||||
for tab in self.tabs:
|
||||
if tab["label"] == current_tab and tab.get("content"):
|
||||
tab["content"]()
|
||||
|
||||
class Breadcrumbs:
|
||||
"""Breadcrumb navigation component."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
items: Optional[List[Dict[str, Any]]] = None
|
||||
):
|
||||
self.items = items or []
|
||||
|
||||
def add_item(
|
||||
self,
|
||||
label: str,
|
||||
page: Optional[str] = None,
|
||||
icon: Optional[str] = None
|
||||
) -> None:
|
||||
"""Add a breadcrumb item."""
|
||||
self.items.append({
|
||||
"label": label,
|
||||
"page": page,
|
||||
"icon": icon
|
||||
})
|
||||
|
||||
def render(self) -> None:
|
||||
"""Render the breadcrumbs with consistent styling."""
|
||||
if not self.items:
|
||||
return
|
||||
|
||||
breadcrumb_items = []
|
||||
for i, item in enumerate(self.items):
|
||||
icon_html = f'<span style="font-size: 1.2em; margin-right: {Theme.SPACING["xs"]};">{item["icon"]}</span>' if item.get("icon") else ""
|
||||
link_html = f'<a href="#" onclick="setPage(\'{item["page"]}\')">{item["label"]}</a>' if item.get("page") else f'<span>{item["label"]}</span>'
|
||||
separator = f'<span style="margin: 0 {Theme.SPACING["xs"]};">/</span>' if i < len(self.items) - 1 else ""
|
||||
|
||||
breadcrumb_items.append(f"""
|
||||
<span class="breadcrumb-item">
|
||||
{icon_html}
|
||||
{link_html}
|
||||
</span>
|
||||
{separator}
|
||||
""")
|
||||
|
||||
st.markdown(f"""
|
||||
<div class="breadcrumbs">
|
||||
{''.join(breadcrumb_items)}
|
||||
</div>
|
||||
""", unsafe_allow_html=True)
|
||||
270
lib/ai_writers/twitter_writers/twitter_streamlit_ui/dashboard.py
Normal file
270
lib/ai_writers/twitter_writers/twitter_streamlit_ui/dashboard.py
Normal file
@@ -0,0 +1,270 @@
|
||||
"""
|
||||
Main dashboard for Twitter UI.
|
||||
Combines all UI components into a cohesive interface.
|
||||
"""
|
||||
|
||||
import streamlit as st
|
||||
from typing import Dict, Any, Optional
|
||||
from .components.cards import FeatureCard, TweetCard
|
||||
from .components.forms import TweetForm, SettingsForm
|
||||
from .components.navigation import Sidebar, Header, Tabs, Breadcrumbs
|
||||
from .styles.theme import Theme
|
||||
|
||||
class TwitterDashboard:
|
||||
"""Main dashboard class for Twitter UI."""
|
||||
|
||||
def __init__(self):
|
||||
self.setup_page()
|
||||
self.setup_theme()
|
||||
self.setup_navigation()
|
||||
self.setup_state()
|
||||
|
||||
def setup_page(self) -> None:
|
||||
"""Configure the Streamlit page settings."""
|
||||
st.set_page_config(
|
||||
page_title="Twitter Tools",
|
||||
page_icon="🐦",
|
||||
layout="wide",
|
||||
initial_sidebar_state="expanded"
|
||||
)
|
||||
|
||||
def setup_theme(self) -> None:
|
||||
"""Apply the theme to the dashboard."""
|
||||
Theme().apply()
|
||||
|
||||
def setup_navigation(self) -> None:
|
||||
"""Setup navigation components."""
|
||||
# Sidebar
|
||||
self.sidebar = Sidebar(
|
||||
title="Twitter Tools",
|
||||
logo="assets/logo.png"
|
||||
)
|
||||
|
||||
# Add menu items
|
||||
self.sidebar.add_menu_item("Dashboard", "📊", "dashboard")
|
||||
self.sidebar.add_menu_item("Tweet Generator", "✍️", "tweet_generator")
|
||||
self.sidebar.add_menu_item("Analytics", "📈", "analytics")
|
||||
self.sidebar.add_menu_item("Settings", "⚙️", "settings")
|
||||
|
||||
# Header
|
||||
self.header = Header(
|
||||
title="Twitter Dashboard",
|
||||
subtitle="Create and manage your Twitter content"
|
||||
)
|
||||
|
||||
# Add header actions
|
||||
self.header.add_action(
|
||||
"New Tweet",
|
||||
"✏️",
|
||||
self.create_new_tweet,
|
||||
"Create a new tweet"
|
||||
)
|
||||
self.header.add_action(
|
||||
"Refresh",
|
||||
"🔄",
|
||||
self.refresh_dashboard,
|
||||
"Refresh dashboard data"
|
||||
)
|
||||
|
||||
# Tabs
|
||||
self.tabs = Tabs()
|
||||
|
||||
# Add tabs
|
||||
self.tabs.add_tab("Overview", "📊", self.render_overview)
|
||||
self.tabs.add_tab("Recent Tweets", "🐦", self.render_recent_tweets)
|
||||
self.tabs.add_tab("Analytics", "📈", self.render_analytics)
|
||||
|
||||
# Breadcrumbs
|
||||
self.breadcrumbs = Breadcrumbs()
|
||||
|
||||
def setup_state(self) -> None:
|
||||
"""Initialize session state variables."""
|
||||
if "current_page" not in st.session_state:
|
||||
st.session_state["current_page"] = "dashboard"
|
||||
if "current_tab" not in st.session_state:
|
||||
st.session_state["current_tab"] = "Overview"
|
||||
if "tweets" not in st.session_state:
|
||||
st.session_state["tweets"] = []
|
||||
|
||||
def create_new_tweet(self) -> None:
|
||||
"""Handle new tweet creation."""
|
||||
st.session_state["current_page"] = "tweet_generator"
|
||||
|
||||
def refresh_dashboard(self) -> None:
|
||||
"""Refresh dashboard data."""
|
||||
st.experimental_rerun()
|
||||
|
||||
def render_overview(self) -> None:
|
||||
"""Render the overview tab content."""
|
||||
# Feature cards
|
||||
col1, col2, col3 = st.columns(3)
|
||||
|
||||
with col1:
|
||||
FeatureCard(
|
||||
title="Tweet Generator",
|
||||
description="Create engaging tweets with AI assistance",
|
||||
icon="✍️",
|
||||
features=[
|
||||
{
|
||||
"name": "AI-Powered",
|
||||
"description": "Generate tweets using advanced AI"
|
||||
},
|
||||
{
|
||||
"name": "Customizable",
|
||||
"description": "Adjust tone, length, and style"
|
||||
}
|
||||
],
|
||||
on_click=self.create_new_tweet
|
||||
).render()
|
||||
|
||||
with col2:
|
||||
FeatureCard(
|
||||
title="Analytics",
|
||||
description="Track your tweet performance",
|
||||
icon="📈",
|
||||
features=[
|
||||
{
|
||||
"name": "Engagement",
|
||||
"description": "Monitor likes, retweets, and replies"
|
||||
},
|
||||
{
|
||||
"name": "Growth",
|
||||
"description": "Track follower growth over time"
|
||||
}
|
||||
]
|
||||
).render()
|
||||
|
||||
with col3:
|
||||
FeatureCard(
|
||||
title="Settings",
|
||||
description="Customize your experience",
|
||||
icon="⚙️",
|
||||
features=[
|
||||
{
|
||||
"name": "Preferences",
|
||||
"description": "Set your default options"
|
||||
},
|
||||
{
|
||||
"name": "API",
|
||||
"description": "Configure Twitter API settings"
|
||||
}
|
||||
]
|
||||
).render()
|
||||
|
||||
def render_recent_tweets(self) -> None:
|
||||
"""Render the recent tweets tab content."""
|
||||
# Tweet form
|
||||
tweet_form = TweetForm(
|
||||
on_submit=self.handle_tweet_submit
|
||||
)
|
||||
tweet_form.render()
|
||||
|
||||
# Recent tweets
|
||||
st.markdown("### Recent Tweets")
|
||||
|
||||
for tweet in st.session_state["tweets"]:
|
||||
TweetCard(
|
||||
content=tweet["content"],
|
||||
engagement_score=tweet["engagement_score"],
|
||||
hashtags=tweet["hashtags"],
|
||||
emojis=tweet["emojis"],
|
||||
metrics=tweet["metrics"],
|
||||
on_copy=lambda: self.copy_tweet(tweet),
|
||||
on_save=lambda: self.save_tweet(tweet)
|
||||
).render()
|
||||
|
||||
def render_analytics(self) -> None:
|
||||
"""Render the analytics tab content."""
|
||||
# Analytics content
|
||||
st.markdown("### Tweet Analytics")
|
||||
|
||||
# Placeholder for analytics charts
|
||||
st.info("Analytics features coming soon!")
|
||||
|
||||
def handle_tweet_submit(self) -> None:
|
||||
"""Handle tweet form submission."""
|
||||
# Get form data
|
||||
content = st.session_state["tweet_content"]
|
||||
tone = st.session_state["tone"]
|
||||
length = st.session_state["length"]
|
||||
hashtags = st.session_state["hashtags"]
|
||||
emojis = st.session_state["emojis"]
|
||||
engagement_boost = st.session_state["engagement_boost"]
|
||||
|
||||
# Create tweet object
|
||||
tweet = {
|
||||
"content": content,
|
||||
"tone": tone,
|
||||
"length": length,
|
||||
"hashtags": hashtags,
|
||||
"emojis": emojis,
|
||||
"engagement_score": engagement_boost,
|
||||
"metrics": {
|
||||
"Engagement": engagement_boost,
|
||||
"Reach": engagement_boost * 0.8,
|
||||
"Growth": engagement_boost * 0.6
|
||||
}
|
||||
}
|
||||
|
||||
# Add to tweets list
|
||||
st.session_state["tweets"].append(tweet)
|
||||
|
||||
# Show success message
|
||||
st.success("Tweet created successfully!")
|
||||
|
||||
def copy_tweet(self, tweet: Dict[str, Any]) -> None:
|
||||
"""Copy tweet to clipboard."""
|
||||
st.write("Tweet copied to clipboard!")
|
||||
|
||||
def save_tweet(self, tweet: Dict[str, Any]) -> None:
|
||||
"""Save tweet for later."""
|
||||
st.write("Tweet saved!")
|
||||
|
||||
def render(self) -> None:
|
||||
"""Render the complete dashboard."""
|
||||
# Render navigation
|
||||
self.sidebar.render()
|
||||
self.header.render()
|
||||
self.breadcrumbs.render()
|
||||
|
||||
# Render content based on current page
|
||||
if st.session_state["current_page"] == "dashboard":
|
||||
self.tabs.render()
|
||||
elif st.session_state["current_page"] == "tweet_generator":
|
||||
self.render_recent_tweets()
|
||||
elif st.session_state["current_page"] == "analytics":
|
||||
self.render_analytics()
|
||||
elif st.session_state["current_page"] == "settings":
|
||||
settings_form = SettingsForm(
|
||||
on_submit=self.handle_settings_submit
|
||||
)
|
||||
settings_form.render()
|
||||
|
||||
def handle_settings_submit(self) -> None:
|
||||
"""Handle settings form submission."""
|
||||
# Get form data
|
||||
api_key = st.session_state["api_key"]
|
||||
theme = st.session_state["theme"]
|
||||
notifications = st.session_state["notifications"]
|
||||
auto_save = st.session_state["auto_save"]
|
||||
language = st.session_state["language"]
|
||||
|
||||
# Save settings
|
||||
st.session_state["settings"] = {
|
||||
"api_key": api_key,
|
||||
"theme": theme,
|
||||
"notifications": notifications,
|
||||
"auto_save": auto_save,
|
||||
"language": language
|
||||
}
|
||||
|
||||
# Show success message
|
||||
st.success("Settings saved successfully!")
|
||||
|
||||
def main():
|
||||
"""Main entry point for the dashboard."""
|
||||
dashboard = TwitterDashboard()
|
||||
dashboard.render()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,173 @@
|
||||
"""
|
||||
Theme configuration for Twitter UI components.
|
||||
Provides consistent styling across all Twitter-related features.
|
||||
"""
|
||||
|
||||
import streamlit as st
|
||||
from typing import Dict, Any
|
||||
|
||||
class Theme:
|
||||
"""Theme configuration for Twitter UI components."""
|
||||
|
||||
# Color palette
|
||||
COLORS = {
|
||||
"primary": "#1DA1F2", # Twitter blue
|
||||
"secondary": "#14171A", # Dark blue
|
||||
"background": "#15202B", # Dark background
|
||||
"text": "#FFFFFF", # White text
|
||||
"text_secondary": "#8899A6", # Gray text
|
||||
"success": "#17BF63", # Green
|
||||
"warning": "#FFAD1F", # Yellow
|
||||
"error": "#E0245E", # Red
|
||||
"border": "rgba(255, 255, 255, 0.1)", # Subtle border
|
||||
}
|
||||
|
||||
# Typography
|
||||
TYPOGRAPHY = {
|
||||
"font_family": "'Helvetica Neue', sans-serif",
|
||||
"font_sizes": {
|
||||
"h1": "2.5rem",
|
||||
"h2": "2rem",
|
||||
"h3": "1.5rem",
|
||||
"body": "1rem",
|
||||
"small": "0.875rem",
|
||||
},
|
||||
"font_weights": {
|
||||
"regular": 400,
|
||||
"medium": 500,
|
||||
"bold": 700,
|
||||
},
|
||||
}
|
||||
|
||||
# Spacing
|
||||
SPACING = {
|
||||
"xs": "0.25rem",
|
||||
"sm": "0.5rem",
|
||||
"md": "1rem",
|
||||
"lg": "1.5rem",
|
||||
"xl": "2rem",
|
||||
}
|
||||
|
||||
# Border radius
|
||||
BORDER_RADIUS = {
|
||||
"sm": "4px",
|
||||
"md": "8px",
|
||||
"lg": "12px",
|
||||
"xl": "16px",
|
||||
"full": "9999px",
|
||||
}
|
||||
|
||||
# Shadows
|
||||
SHADOWS = {
|
||||
"sm": "0 1px 2px rgba(0, 0, 0, 0.05)",
|
||||
"md": "0 4px 6px rgba(0, 0, 0, 0.1)",
|
||||
"lg": "0 10px 15px rgba(0, 0, 0, 0.1)",
|
||||
"xl": "0 20px 25px rgba(0, 0, 0, 0.15)",
|
||||
}
|
||||
|
||||
# Transitions
|
||||
TRANSITIONS = {
|
||||
"fast": "0.15s ease",
|
||||
"normal": "0.3s ease",
|
||||
"slow": "0.5s ease",
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def get_css(cls) -> str:
|
||||
"""Get the complete CSS for the theme."""
|
||||
return f"""
|
||||
/* Base styles */
|
||||
.stApp {{
|
||||
background-color: {cls.COLORS['background']};
|
||||
color: {cls.COLORS['text']};
|
||||
font-family: {cls.TYPOGRAPHY['font_family']};
|
||||
}}
|
||||
|
||||
/* Typography */
|
||||
h1, h2, h3, h4, h5, h6 {{
|
||||
color: {cls.COLORS['text']};
|
||||
font-family: {cls.TYPOGRAPHY['font_family']};
|
||||
font-weight: {cls.TYPOGRAPHY['font_weights']['bold']};
|
||||
}}
|
||||
|
||||
/* Buttons */
|
||||
.stButton > button {{
|
||||
background: linear-gradient(45deg, {cls.COLORS['primary']}, #0C85D0);
|
||||
color: {cls.COLORS['text']};
|
||||
border: none;
|
||||
padding: {cls.SPACING['md']} {cls.SPACING['lg']};
|
||||
border-radius: {cls.BORDER_RADIUS['full']};
|
||||
font-weight: {cls.TYPOGRAPHY['font_weights']['medium']};
|
||||
transition: all {cls.TRANSITIONS['normal']};
|
||||
box-shadow: {cls.SHADOWS['md']};
|
||||
}}
|
||||
|
||||
.stButton > button:hover {{
|
||||
transform: translateY(-2px);
|
||||
box-shadow: {cls.SHADOWS['lg']};
|
||||
}}
|
||||
|
||||
/* Cards */
|
||||
.card {{
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border: 1px solid {cls.COLORS['border']};
|
||||
border-radius: {cls.BORDER_RADIUS['lg']};
|
||||
padding: {cls.SPACING['lg']};
|
||||
margin-bottom: {cls.SPACING['md']};
|
||||
backdrop-filter: blur(10px);
|
||||
transition: transform {cls.TRANSITIONS['normal']};
|
||||
}}
|
||||
|
||||
.card:hover {{
|
||||
transform: translateY(-4px);
|
||||
}}
|
||||
|
||||
/* Forms */
|
||||
.stTextInput > div > div > input {{
|
||||
background-color: rgba(255, 255, 255, 0.05);
|
||||
border: 1px solid {cls.COLORS['border']};
|
||||
border-radius: {cls.BORDER_RADIUS['md']};
|
||||
color: {cls.COLORS['text']};
|
||||
padding: {cls.SPACING['md']};
|
||||
}}
|
||||
|
||||
/* Tabs */
|
||||
.stTabs [data-baseweb="tab-list"] {{
|
||||
gap: {cls.SPACING['sm']};
|
||||
background-color: rgba(0, 0, 0, 0.2);
|
||||
padding: {cls.SPACING['md']};
|
||||
border-radius: {cls.BORDER_RADIUS['lg']};
|
||||
}}
|
||||
|
||||
.stTabs [data-baseweb="tab"] {{
|
||||
background-color: transparent;
|
||||
color: {cls.COLORS['text']};
|
||||
border: 1px solid {cls.COLORS['border']};
|
||||
border-radius: {cls.BORDER_RADIUS['md']};
|
||||
padding: {cls.SPACING['sm']} {cls.SPACING['md']};
|
||||
}}
|
||||
|
||||
/* Status badges */
|
||||
.status-badge {{
|
||||
display: inline-block;
|
||||
padding: {cls.SPACING['xs']} {cls.SPACING['md']};
|
||||
border-radius: {cls.BORDER_RADIUS['full']};
|
||||
font-size: {cls.TYPOGRAPHY['font_sizes']['small']};
|
||||
font-weight: {cls.TYPOGRAPHY['font_weights']['medium']};
|
||||
}}
|
||||
|
||||
.status-active {{
|
||||
background: linear-gradient(45deg, {cls.COLORS['success']}, #69F0AE);
|
||||
color: {cls.COLORS['secondary']};
|
||||
}}
|
||||
|
||||
.status-coming-soon {{
|
||||
background: linear-gradient(45deg, {cls.COLORS['warning']}, #FFA000);
|
||||
color: {cls.COLORS['secondary']};
|
||||
}}
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def apply(cls) -> None:
|
||||
"""Apply the theme to the Streamlit app."""
|
||||
st.markdown(f"<style>{cls.get_css()}</style>", unsafe_allow_html=True)
|
||||
@@ -0,0 +1,194 @@
|
||||
"""
|
||||
Utility functions for Twitter UI.
|
||||
Provides helper functions for common operations.
|
||||
"""
|
||||
|
||||
import streamlit as st
|
||||
from typing import Dict, Any, List, Optional
|
||||
import json
|
||||
import os
|
||||
from datetime import datetime
|
||||
|
||||
def save_to_session(key: str, value: Any) -> None:
|
||||
"""Save a value to the session state."""
|
||||
st.session_state[key] = value
|
||||
|
||||
def get_from_session(key: str, default: Any = None) -> Any:
|
||||
"""Get a value from the session state."""
|
||||
return st.session_state.get(key, default)
|
||||
|
||||
def clear_session() -> None:
|
||||
"""Clear all session state variables."""
|
||||
for key in list(st.session_state.keys()):
|
||||
del st.session_state[key]
|
||||
|
||||
def save_to_file(data: Dict[str, Any], filename: str) -> None:
|
||||
"""Save data to a JSON file."""
|
||||
try:
|
||||
with open(filename, 'w') as f:
|
||||
json.dump(data, f, indent=4)
|
||||
except Exception as e:
|
||||
st.error(f"Error saving data: {str(e)}")
|
||||
|
||||
def load_from_file(filename: str) -> Optional[Dict[str, Any]]:
|
||||
"""Load data from a JSON file."""
|
||||
try:
|
||||
if os.path.exists(filename):
|
||||
with open(filename, 'r') as f:
|
||||
return json.load(f)
|
||||
except Exception as e:
|
||||
st.error(f"Error loading data: {str(e)}")
|
||||
return None
|
||||
|
||||
def format_datetime(dt: datetime) -> str:
|
||||
"""Format a datetime object for display."""
|
||||
return dt.strftime("%Y-%m-%d %H:%M:%S")
|
||||
|
||||
def parse_datetime(dt_str: str) -> Optional[datetime]:
|
||||
"""Parse a datetime string."""
|
||||
try:
|
||||
return datetime.strptime(dt_str, "%Y-%m-%d %H:%M:%S")
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
def validate_tweet_content(content: str) -> bool:
|
||||
"""Validate tweet content."""
|
||||
if not content:
|
||||
st.error("Tweet content cannot be empty")
|
||||
return False
|
||||
if len(content) > 280:
|
||||
st.error("Tweet content cannot exceed 280 characters")
|
||||
return False
|
||||
return True
|
||||
|
||||
def validate_hashtags(hashtags: List[str]) -> bool:
|
||||
"""Validate hashtags."""
|
||||
for tag in hashtags:
|
||||
if not tag.startswith('#'):
|
||||
st.error(f"Hashtag {tag} must start with #")
|
||||
return False
|
||||
if len(tag) > 30:
|
||||
st.error(f"Hashtag {tag} cannot exceed 30 characters")
|
||||
return False
|
||||
return True
|
||||
|
||||
def validate_emojis(emojis: List[str]) -> bool:
|
||||
"""Validate emojis."""
|
||||
for emoji in emojis:
|
||||
if len(emoji) != 1:
|
||||
st.error(f"Invalid emoji: {emoji}")
|
||||
return False
|
||||
return True
|
||||
|
||||
def calculate_engagement_score(
|
||||
content: str,
|
||||
hashtags: List[str],
|
||||
emojis: List[str],
|
||||
tone: str
|
||||
) -> float:
|
||||
"""Calculate engagement score for a tweet."""
|
||||
score = 0.0
|
||||
|
||||
# Content length score (optimal length is 100-150 characters)
|
||||
content_length = len(content)
|
||||
if 100 <= content_length <= 150:
|
||||
score += 30
|
||||
elif 50 <= content_length <= 200:
|
||||
score += 20
|
||||
else:
|
||||
score += 10
|
||||
|
||||
# Hashtag score (optimal number is 2-3 hashtags)
|
||||
hashtag_count = len(hashtags)
|
||||
if 2 <= hashtag_count <= 3:
|
||||
score += 20
|
||||
elif 1 <= hashtag_count <= 4:
|
||||
score += 15
|
||||
else:
|
||||
score += 5
|
||||
|
||||
# Emoji score (optimal number is 1-2 emojis)
|
||||
emoji_count = len(emojis)
|
||||
if 1 <= emoji_count <= 2:
|
||||
score += 20
|
||||
elif 0 <= emoji_count <= 3:
|
||||
score += 15
|
||||
else:
|
||||
score += 5
|
||||
|
||||
# Tone score
|
||||
tone_scores = {
|
||||
"professional": 15,
|
||||
"casual": 20,
|
||||
"humorous": 25,
|
||||
"informative": 15,
|
||||
"inspirational": 20
|
||||
}
|
||||
score += tone_scores.get(tone, 10)
|
||||
|
||||
return min(score, 100)
|
||||
|
||||
def generate_tweet_metrics(engagement_score: float) -> Dict[str, float]:
|
||||
"""Generate metrics for a tweet based on engagement score."""
|
||||
return {
|
||||
"Engagement": engagement_score,
|
||||
"Reach": engagement_score * 0.8,
|
||||
"Growth": engagement_score * 0.6
|
||||
}
|
||||
|
||||
def copy_to_clipboard(text: str) -> None:
|
||||
"""Copy text to clipboard."""
|
||||
try:
|
||||
st.write(f'<script>navigator.clipboard.writeText("{text}")</script>', unsafe_allow_html=True)
|
||||
except Exception as e:
|
||||
st.error(f"Error copying to clipboard: {str(e)}")
|
||||
|
||||
def show_success_message(message: str) -> None:
|
||||
"""Show a success message."""
|
||||
st.success(message)
|
||||
|
||||
def show_error_message(message: str) -> None:
|
||||
"""Show an error message."""
|
||||
st.error(message)
|
||||
|
||||
def show_info_message(message: str) -> None:
|
||||
"""Show an info message."""
|
||||
st.info(message)
|
||||
|
||||
def show_warning_message(message: str) -> None:
|
||||
"""Show a warning message."""
|
||||
st.warning(message)
|
||||
|
||||
def create_download_button(
|
||||
data: Dict[str, Any],
|
||||
filename: str,
|
||||
button_text: str = "Download"
|
||||
) -> None:
|
||||
"""Create a download button for data."""
|
||||
try:
|
||||
json_str = json.dumps(data, indent=4)
|
||||
st.download_button(
|
||||
label=button_text,
|
||||
data=json_str,
|
||||
file_name=filename,
|
||||
mime="application/json"
|
||||
)
|
||||
except Exception as e:
|
||||
st.error(f"Error creating download button: {str(e)}")
|
||||
|
||||
def create_upload_button(
|
||||
on_upload: callable,
|
||||
button_text: str = "Upload",
|
||||
file_types: List[str] = ["json"]
|
||||
) -> None:
|
||||
"""Create an upload button for data."""
|
||||
try:
|
||||
uploaded_file = st.file_uploader(
|
||||
button_text,
|
||||
type=file_types
|
||||
)
|
||||
if uploaded_file is not None:
|
||||
data = json.load(uploaded_file)
|
||||
on_upload(data)
|
||||
except Exception as e:
|
||||
st.error(f"Error handling upload: {str(e)}")
|
||||
Reference in New Issue
Block a user