Added new features to the project

This commit is contained in:
ajaysi
2025-06-30 07:49:48 +05:30
parent bbe56a364d
commit b21cbb68da
48 changed files with 19774 additions and 1889 deletions

View File

@@ -1,174 +1,634 @@
"""
Card components for Twitter UI.
Provides consistent card layouts for features and tweets.
Enhanced UI Cards with modern styling and improved functionality.
"""
import streamlit as st
from typing import Dict, Any, Optional, List
from ..styles.theme import Theme
from typing import Dict, List, Optional, Callable
import plotly.express as px
import plotly.graph_objects as go
from datetime import datetime
class BaseCard:
"""Base class for all card components."""
def apply_cards_styling():
"""Apply modern CSS styling for cards."""
st.markdown("""
<style>
/* Modern Card Styles */
.modern-card {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(20px);
border-radius: 16px;
padding: 1.5rem;
margin: 1rem 0;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
border: 1px solid rgba(255, 255, 255, 0.2);
transition: all 0.3s ease;
position: relative;
overflow: hidden;
}
.modern-card:hover {
transform: translateY(-4px);
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.15);
}
.modern-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 4px;
background: linear-gradient(135deg, #1DA1F2, #0C85D0);
}
.feature-card {
background: white;
border-radius: 12px;
padding: 1.5rem;
margin: 0.75rem 0;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
border: 1px solid #E1E8ED;
transition: all 0.3s ease;
cursor: pointer;
}
.feature-card:hover {
transform: translateY(-2px);
box-shadow: 0 8px 30px rgba(29, 161, 242, 0.15);
border-color: #1DA1F2;
}
.feature-card-header {
display: flex;
align-items: center;
gap: 1rem;
margin-bottom: 1rem;
}
.feature-icon {
font-size: 2rem;
width: 60px;
height: 60px;
display: flex;
align-items: center;
justify-content: center;
background: linear-gradient(135deg, #E6F7FF, #F0F9FF);
border-radius: 12px;
border: 2px solid #91D5FF;
}
.feature-title {
font-size: 1.25rem;
font-weight: 600;
color: #2D3748;
margin: 0;
}
.feature-description {
color: #657786;
font-size: 0.95rem;
line-height: 1.5;
margin-bottom: 1rem;
}
.feature-stats {
display: flex;
gap: 1rem;
margin-top: 1rem;
padding-top: 1rem;
border-top: 1px solid #E1E8ED;
}
.stat-item {
text-align: center;
flex: 1;
}
.stat-value {
font-size: 1.5rem;
font-weight: 700;
color: #1DA1F2;
display: block;
}
.stat-label {
font-size: 0.8rem;
color: #657786;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.tweet-card {
background: white;
border: 1px solid #E1E8ED;
border-radius: 16px;
padding: 1.5rem;
margin: 1rem 0;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.08);
position: relative;
}
.tweet-card::before {
content: "🐦";
position: absolute;
top: -10px;
left: 20px;
background: white;
padding: 0 10px;
font-size: 1.2rem;
}
.tweet-content {
font-size: 1.1rem;
line-height: 1.5;
color: #14171A;
margin-bottom: 1rem;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
}
.tweet-metadata {
display: flex;
justify-content: space-between;
align-items: center;
color: #657786;
font-size: 0.9rem;
border-top: 1px solid #E1E8ED;
padding-top: 1rem;
}
.engagement-badge {
background: linear-gradient(135deg, #52C41A, #73D13D);
color: white;
padding: 0.5rem 1rem;
border-radius: 20px;
font-weight: 600;
font-size: 0.9rem;
display: flex;
align-items: center;
gap: 0.5rem;
}
.character-badge {
padding: 0.25rem 0.75rem;
border-radius: 20px;
font-weight: 600;
font-size: 0.8rem;
}
.char-good { background: #E6F7FF; color: #1890FF; }
.char-warning { background: #FFF7E6; color: #FA8C16; }
.char-danger { background: #FFF1F0; color: #F5222D; }
.card-actions {
display: flex;
gap: 0.5rem;
margin-top: 1rem;
flex-wrap: wrap;
}
.action-button {
background: #F7F9FA;
border: 1px solid #E1E8ED;
border-radius: 8px;
padding: 0.5rem 1rem;
color: #657786;
font-size: 0.9rem;
cursor: pointer;
transition: all 0.3s ease;
text-decoration: none;
display: inline-flex;
align-items: center;
gap: 0.5rem;
}
.action-button:hover {
background: #1DA1F2;
color: white;
border-color: #1DA1F2;
transform: translateY(-1px);
}
.action-button.primary {
background: #1DA1F2;
color: white;
border-color: #1DA1F2;
}
.action-button.primary:hover {
background: #0C85D0;
border-color: #0C85D0;
}
.metrics-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
gap: 1rem;
margin: 1rem 0;
}
.metric-card {
background: white;
border-radius: 8px;
padding: 1rem;
text-align: center;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
border: 1px solid #E1E8ED;
}
.metric-value {
font-size: 1.5rem;
font-weight: 700;
color: #1DA1F2;
display: block;
margin-bottom: 0.25rem;
}
.metric-label {
font-size: 0.8rem;
color: #657786;
text-transform: uppercase;
letter-spacing: 0.5px;
}
/* Responsive Design */
@media (max-width: 768px) {
.modern-card, .feature-card, .tweet-card {
margin: 0.5rem;
padding: 1rem;
}
.feature-card-header {
flex-direction: column;
text-align: center;
}
.feature-stats {
flex-direction: column;
gap: 0.5rem;
}
.card-actions {
justify-content: center;
}
.metrics-grid {
grid-template-columns: repeat(2, 1fr);
}
}
</style>
""", unsafe_allow_html=True)
class FeatureCard:
"""Modern feature card component."""
def __init__(
self,
title: str,
description: str,
icon: Optional[str] = None,
status: Optional[str] = None,
actions: Optional[List[Dict[str, Any]]] = None
icon: str = "🔧",
stats: Optional[Dict[str, any]] = None,
actions: Optional[List[Dict]] = None,
on_click: Optional[Callable] = None
):
self.title = title
self.description = description
self.icon = icon
self.status = status
self.stats = stats or {}
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>
def render(self):
"""Render the feature card."""
apply_cards_styling()
# Create stats HTML
stats_html = ""
if self.stats:
stats_items = []
for label, value in self.stats.items():
stats_items.append(f"""
<div class="stat-item">
<span class="stat-value">{value}</span>
<span class="stat-label">{label}</span>
</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>
""")
stats_html = f"""
<div class="feature-stats">
{''.join(stats_items)}
</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()
"""
# Create actions HTML
actions_html = ""
if self.actions:
action_buttons = []
for action in self.actions:
button_class = "action-button"
if action.get("primary", False):
button_class += " primary"
action_buttons.append(f"""
<button class="{button_class}" onclick="{action.get('onclick', '')}">
{action.get('icon', '')} {action.get('label', 'Action')}
</button>
""")
actions_html = f"""
<div class="card-actions">
{''.join(action_buttons)}
</div>
"""
# Render the card
card_html = f"""
<div class="feature-card" onclick="{self.on_click or ''}">
<div class="feature-card-header">
<div class="feature-icon">{self.icon}</div>
<div>
<h3 class="feature-title">{self.title}</h3>
</div>
</div>
<p class="feature-description">{self.description}</p>
{stats_html}
{actions_html}
</div>
"""
st.markdown(card_html, unsafe_allow_html=True)
class TweetCard(BaseCard):
"""Card component for displaying tweets."""
class TweetCard:
"""Modern tweet card component."""
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
engagement_score: int = 0,
hashtags: List[str] = None,
emojis: List[str] = None,
metrics: Optional[Dict] = None,
timestamp: Optional[str] = None,
on_copy: Optional[Callable] = None,
on_save: Optional[Callable] = None,
on_edit: Optional[Callable] = None,
on_post: 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.content = content
self.engagement_score = engagement_score
self.hashtags = hashtags
self.emojis = emojis
self.hashtags = hashtags or []
self.emojis = emojis or []
self.metrics = metrics or {}
self.timestamp = timestamp or datetime.now().strftime("%Y-%m-%d %H:%M")
self.on_copy = on_copy
self.on_save = on_save
self.on_edit = on_edit
self.on_post = on_post
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>
def _get_character_info(self):
"""Get character count information."""
full_text = f"{self.content} {' '.join(self.hashtags)}"
count = len(full_text)
remaining = 280 - count
if count <= 240:
status_class = "char-good"
elif count <= 270:
status_class = "char-warning"
else:
status_class = "char-danger"
return {
"count": count,
"remaining": remaining,
"status_class": status_class
}
def render(self):
"""Render the tweet card."""
apply_cards_styling()
char_info = self._get_character_info()
full_content = f"{self.content} {' '.join(self.hashtags)}"
# Create metrics HTML
metrics_html = ""
if self.metrics:
metric_items = []
for label, value in self.metrics.items():
metric_items.append(f"""
<div class="metric-card">
<span class="metric-value">{value}</span>
<span class="metric-label">{label}</span>
</div>
""")
metrics_html = f"""
<div class="metrics-grid">
{''.join(metric_items)}
</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}%")
"""
# Create actions
actions = []
if self.on_copy:
actions.append('<button class="action-button" onclick="copyTweet()">📋 Copy</button>')
if self.on_save:
actions.append('<button class="action-button" onclick="saveTweet()">💾 Save</button>')
if self.on_edit:
actions.append('<button class="action-button" onclick="editTweet()">✏️ Edit</button>')
if self.on_post:
actions.append('<button class="action-button primary" onclick="postTweet()">🐦 Post</button>')
actions_html = f'<div class="card-actions">{"".join(actions)}</div>' if actions else ""
# Render the card
card_html = f"""
<div class="tweet-card">
<div class="tweet-content">{full_content}</div>
{metrics_html}
<div class="tweet-metadata">
<div class="engagement-badge">
📊 {self.engagement_score}% Engagement
</div>
<div class="character-badge {char_info['status_class']}">
{char_info['count']}/280
</div>
</div>
{actions_html}
</div>
"""
st.markdown(card_html, unsafe_allow_html=True)
class MetricsCard:
"""Modern metrics display card."""
def __init__(
self,
title: str,
metrics: Dict[str, any],
chart_data: Optional[Dict] = None,
trend: Optional[str] = None
):
self.title = title
self.metrics = metrics
self.chart_data = chart_data
self.trend = trend
def render(self):
"""Render the metrics card."""
apply_cards_styling()
# Create metrics grid
metric_items = []
for label, value in self.metrics.items():
metric_items.append(f"""
<div class="metric-card">
<span class="metric-value">{value}</span>
<span class="metric-label">{label}</span>
</div>
""")
metrics_grid = f"""
<div class="metrics-grid">
{''.join(metric_items)}
</div>
"""
# Add trend indicator
trend_html = ""
if self.trend:
trend_color = "#52C41A" if "up" in self.trend.lower() else "#F5222D"
trend_icon = "📈" if "up" in self.trend.lower() else "📉"
trend_html = f"""
<div style="text-align: center; margin-top: 1rem; color: {trend_color};">
{trend_icon} {self.trend}
</div>
"""
# Render the card
card_html = f"""
<div class="modern-card">
<h3 style="margin-bottom: 1rem; color: #2D3748;">{self.title}</h3>
{metrics_grid}
{trend_html}
</div>
"""
st.markdown(card_html, unsafe_allow_html=True)
# Add chart if provided
if self.chart_data:
self._render_chart()
def _render_chart(self):
"""Render chart for metrics."""
if self.chart_data.get("type") == "line":
fig = px.line(
x=self.chart_data.get("x", []),
y=self.chart_data.get("y", []),
title=self.chart_data.get("title", ""),
labels=self.chart_data.get("labels", {})
)
elif self.chart_data.get("type") == "bar":
fig = px.bar(
x=self.chart_data.get("x", []),
y=self.chart_data.get("y", []),
title=self.chart_data.get("title", ""),
labels=self.chart_data.get("labels", {})
)
else:
return
fig.update_layout(
plot_bgcolor='rgba(0,0,0,0)',
paper_bgcolor='rgba(0,0,0,0)',
showlegend=False,
height=300
)
st.plotly_chart(fig, use_container_width=True)
class StatusCard:
"""Status indicator card."""
def __init__(
self,
title: str,
status: str,
message: str,
icon: str = "",
actions: Optional[List[Dict]] = None
):
self.title = title
self.status = status # success, warning, error, info
self.message = message
self.icon = icon
self.actions = actions or []
def render(self):
"""Render the status card."""
apply_cards_styling()
# Status colors
status_colors = {
"success": "#52C41A",
"warning": "#FA8C16",
"error": "#F5222D",
"info": "#1890FF"
}
color = status_colors.get(self.status, "#1890FF")
# Create actions
actions_html = ""
if self.actions:
action_buttons = []
for action in self.actions:
action_buttons.append(f"""
<button class="action-button" onclick="{action.get('onclick', '')}">
{action.get('icon', '')} {action.get('label', 'Action')}
</button>
""")
actions_html = f"""
<div class="card-actions">
{''.join(action_buttons)}
</div>
"""
# Render the card
card_html = f"""
<div class="modern-card" style="border-left: 4px solid {color};">
<div style="display: flex; align-items: center; gap: 1rem; margin-bottom: 1rem;">
<span style="font-size: 2rem;">{self.icon}</span>
<div>
<h3 style="margin: 0; color: #2D3748;">{self.title}</h3>
<span style="color: {color}; font-weight: 600; text-transform: uppercase; font-size: 0.8rem;">
{self.status}
</span>
</div>
</div>
<p style="color: #657786; margin-bottom: 1rem;">{self.message}</p>
{actions_html}
</div>
"""
st.markdown(card_html, unsafe_allow_html=True)
# Utility functions for creating common cards
def create_feature_card(title: str, description: str, icon: str = "🔧", **kwargs):
"""Create and render a feature card."""
card = FeatureCard(title, description, icon, **kwargs)
card.render()
def create_tweet_card(content: str, **kwargs):
"""Create and render a tweet card."""
card = TweetCard(content, **kwargs)
card.render()
def create_metrics_card(title: str, metrics: Dict, **kwargs):
"""Create and render a metrics card."""
card = MetricsCard(title, metrics, **kwargs)
card.render()
def create_status_card(title: str, status: str, message: str, **kwargs):
"""Create and render a status card."""
card = StatusCard(title, status, message, **kwargs)
card.render()

View File

@@ -1,232 +1,554 @@
"""
Navigation components for Twitter UI.
Provides consistent navigation and layout structure.
Enhanced Navigation Component for Twitter UI with modern styling and improved functionality.
"""
import streamlit as st
from typing import Dict, Any, Optional, List
from typing import Dict, List, Optional, Callable, Any
from ..styles.theme import Theme
import os
def apply_navigation_styling():
"""Apply modern CSS styling for navigation components."""
st.markdown("""
<style>
/* Navigation Styles */
.nav-container {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(20px);
border-radius: 16px;
padding: 1rem;
margin-bottom: 2rem;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
border: 1px solid rgba(255, 255, 255, 0.2);
}
.nav-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 1rem;
padding-bottom: 1rem;
border-bottom: 2px solid #E2E8F0;
}
.nav-title {
font-size: 1.5rem;
font-weight: 700;
color: #1DA1F2;
display: flex;
align-items: center;
gap: 0.5rem;
}
.nav-status {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 1rem;
border-radius: 20px;
font-size: 0.9rem;
font-weight: 600;
}
.status-connected {
background: linear-gradient(135deg, #52C41A, #73D13D);
color: white;
}
.status-disconnected {
background: linear-gradient(135deg, #FA8C16, #FFA940);
color: white;
}
.nav-menu {
display: flex;
gap: 0.5rem;
flex-wrap: wrap;
}
.nav-item {
background: #F7F9FA;
border: 2px solid transparent;
border-radius: 12px;
padding: 0.75rem 1.5rem;
color: #657786;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
text-decoration: none;
display: flex;
align-items: center;
gap: 0.5rem;
}
.nav-item:hover {
background: #E1F5FE;
border-color: #1DA1F2;
color: #1DA1F2;
transform: translateY(-2px);
box-shadow: 0 4px 15px rgba(29, 161, 242, 0.2);
}
.nav-item.active {
background: linear-gradient(135deg, #1DA1F2, #0C85D0);
color: white;
border-color: #1DA1F2;
box-shadow: 0 4px 15px rgba(29, 161, 242, 0.3);
}
.nav-item.active:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(29, 161, 242, 0.4);
}
.nav-breadcrumb {
display: flex;
align-items: center;
gap: 0.5rem;
margin-bottom: 1rem;
font-size: 0.9rem;
color: #657786;
}
.breadcrumb-item {
display: flex;
align-items: center;
gap: 0.25rem;
}
.breadcrumb-separator {
color: #CBD5E0;
margin: 0 0.5rem;
}
.nav-actions {
display: flex;
gap: 0.5rem;
align-items: center;
}
.action-button {
background: linear-gradient(135deg, #52C41A, #73D13D);
color: white;
border: none;
border-radius: 8px;
padding: 0.5rem 1rem;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
gap: 0.5rem;
}
.action-button:hover {
transform: translateY(-2px);
box-shadow: 0 4px 15px rgba(82, 196, 26, 0.3);
}
.action-button.secondary {
background: #F7F9FA;
color: #657786;
border: 1px solid #E1E8ED;
}
.action-button.secondary:hover {
background: #E1F5FE;
color: #1DA1F2;
border-color: #1DA1F2;
}
/* Mobile Responsive */
@media (max-width: 768px) {
.nav-header {
flex-direction: column;
gap: 1rem;
align-items: flex-start;
}
.nav-menu {
flex-direction: column;
width: 100%;
}
.nav-item {
width: 100%;
justify-content: center;
}
.nav-actions {
width: 100%;
justify-content: center;
}
}
</style>
""", unsafe_allow_html=True)
class TwitterNavigation:
"""Enhanced navigation component for Twitter dashboard."""
def __init__(self, theme: Optional[Theme] = None):
self.theme = theme or Theme()
self.current_page = st.session_state.get('current_page', 'dashboard')
def render_header(self, title: str = "Twitter AI Assistant", show_status: bool = True):
"""Render the navigation header with title and status."""
apply_navigation_styling()
st.markdown('<div class="nav-container">', unsafe_allow_html=True)
st.markdown('<div class="nav-header">', unsafe_allow_html=True)
# Title
st.markdown(f'<div class="nav-title">🐦 {title}</div>', unsafe_allow_html=True)
# Status indicator
if show_status:
twitter_connected = self._check_twitter_connection()
status_class = "status-connected" if twitter_connected else "status-disconnected"
status_text = "Connected" if twitter_connected else "Not Connected"
status_icon = "" if twitter_connected else "⚠️"
st.markdown(f'''
<div class="nav-status {status_class}">
{status_icon} Twitter {status_text}
</div>
''', unsafe_allow_html=True)
st.markdown('</div>', unsafe_allow_html=True)
def render_menu(self, menu_items: List[Dict], current_page: Optional[str] = None):
"""Render navigation menu with items."""
if current_page:
self.current_page = current_page
st.session_state.current_page = current_page
st.markdown('<div class="nav-menu">', unsafe_allow_html=True)
cols = st.columns(len(menu_items))
for i, item in enumerate(menu_items):
with cols[i]:
active_class = "active" if item.get('key') == self.current_page else ""
if st.button(
f"{item.get('icon', '')} {item.get('label', '')}",
key=f"nav_{item.get('key', i)}",
use_container_width=True,
type="primary" if active_class else "secondary"
):
st.session_state.current_page = item.get('key')
if item.get('callback'):
item['callback']()
st.rerun()
st.markdown('</div>', unsafe_allow_html=True)
st.markdown('</div>', unsafe_allow_html=True)
return st.session_state.get('current_page', menu_items[0].get('key'))
def render_breadcrumb(self, items: List[Dict]):
"""Render breadcrumb navigation."""
st.markdown('<div class="nav-breadcrumb">', unsafe_allow_html=True)
for i, item in enumerate(items):
if i > 0:
st.markdown('<span class="breadcrumb-separator"></span>', unsafe_allow_html=True)
icon = item.get('icon', '')
label = item.get('label', '')
if item.get('active', False):
st.markdown(f'<span class="breadcrumb-item"><strong>{icon} {label}</strong></span>', unsafe_allow_html=True)
else:
st.markdown(f'<span class="breadcrumb-item">{icon} {label}</span>', unsafe_allow_html=True)
st.markdown('</div>', unsafe_allow_html=True)
def render_actions(self, actions: List[Dict]):
"""Render action buttons in navigation."""
st.markdown('<div class="nav-actions">', unsafe_allow_html=True)
cols = st.columns(len(actions))
for i, action in enumerate(actions):
with cols[i]:
button_type = action.get('type', 'primary')
if st.button(
f"{action.get('icon', '')} {action.get('label', '')}",
key=f"action_{action.get('key', i)}",
type=button_type,
use_container_width=True,
help=action.get('help', '')
):
if action.get('callback'):
action['callback']()
st.markdown('</div>', unsafe_allow_html=True)
def render_sidebar_menu(self, menu_items: List[Dict]):
"""Render sidebar navigation menu."""
with st.sidebar:
st.markdown("### 🐦 Twitter Tools")
for item in menu_items:
icon = item.get('icon', '')
label = item.get('label', '')
key = item.get('key', '')
if st.button(f"{icon} {label}", key=f"sidebar_{key}", use_container_width=True):
st.session_state.current_page = key
if item.get('callback'):
item['callback']()
st.rerun()
# Twitter connection status in sidebar
st.markdown("---")
twitter_connected = self._check_twitter_connection()
if twitter_connected:
st.success("🐦 Twitter Connected")
else:
st.warning("⚠️ Twitter Not Connected")
if st.button("🔧 Configure Twitter", use_container_width=True):
st.session_state.show_twitter_config = True
st.rerun()
def _check_twitter_connection(self) -> bool:
"""Check if Twitter is connected."""
twitter_config = st.session_state.get('twitter_config', {})
return bool(twitter_config and all([
twitter_config.get('api_key'),
twitter_config.get('api_secret'),
twitter_config.get('access_token'),
twitter_config.get('access_token_secret')
]))
class Sidebar:
"""Sidebar navigation component."""
def __init__(
self,
title: str = "Twitter Tools",
logo: Optional[str] = None,
menu_items: Optional[List[Dict[str, Any]]] = None
):
def __init__(self, title: str = "Navigation", logo: Optional[str] = None):
"""Initialize the sidebar."""
self.title = title
self.logo = logo
self.menu_items = menu_items or []
self.menu_items = []
def add_menu_item(
self,
label: str,
icon: str,
page: str,
badge: Optional[str] = None
) -> None:
def add_menu_item(self, label: str, icon: str, key: str, callback: Optional[Callable] = None):
"""Add a menu item to the sidebar."""
self.menu_items.append({
"label": label,
"icon": icon,
"page": page,
"badge": badge
'label': label,
'icon': icon,
'key': key,
'callback': callback
})
def render(self) -> None:
"""Render the sidebar with consistent styling."""
def render(self) -> str:
"""Render the sidebar and return the selected page."""
with st.sidebar:
# Logo and title
if self.logo:
try:
import os
if os.path.exists(self.logo):
st.image(self.logo, width=50)
else:
# Show a placeholder or just skip the logo
st.markdown("🐦", help="Twitter Tools Logo")
except Exception as e:
# If there's any error loading the image, show an emoji instead
st.markdown("🐦", help="Twitter Tools Logo")
st.markdown(f"""
<h2 style="margin: {Theme.SPACING["sm"]} 0;">{self.title}</h2>
""", unsafe_allow_html=True)
if self.logo and os.path.exists(self.logo):
st.image(self.logo, width=100)
st.title(self.title)
st.markdown("---")
# Menu items
selected_page = None
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']}",
f"{item['icon']} {item['label']}",
key=f"sidebar_{item['key']}",
use_container_width=True
):
st.session_state["current_page"] = item["page"]
selected_page = item['key']
if item.get('callback'):
item['callback']()
return selected_page or st.session_state.get('current_page', 'dashboard')
class Header:
"""Header navigation component."""
"""Header component with title and actions."""
def __init__(
self,
title: str,
subtitle: Optional[str] = None,
actions: Optional[List[Dict[str, Any]]] = None
):
def __init__(self, title: str = "Dashboard", subtitle: str = ""):
"""Initialize the header."""
self.title = title
self.subtitle = subtitle
self.actions = actions or []
self.actions = []
def add_action(
self,
label: str,
icon: str,
callback: callable,
help_text: Optional[str] = None
) -> None:
def add_action(self, label: str, icon: str, callback: Callable, help_text: str = ""):
"""Add an action button to the header."""
self.actions.append({
"label": label,
"icon": icon,
"callback": callback,
"help_text": help_text
'label': label,
'icon': icon,
'callback': callback,
'help': 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>
""")
def render(self):
"""Render the header."""
col1, col2 = st.columns([3, 1])
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)
with col1:
st.title(f"{self.title}")
if self.subtitle:
st.markdown(f"*{self.subtitle}*")
# 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"]()
with col2:
if self.actions:
for i, action in enumerate(self.actions):
if st.button(
f"{action['icon']} {action['label']}",
key=f"header_action_{i}",
help=action.get('help', ''),
use_container_width=True
):
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 __init__(self):
"""Initialize the tabs."""
self.tabs = []
def add_tab(
self,
label: str,
icon: Optional[str] = None,
content: Optional[callable] = None
) -> None:
"""Add a tab to the navigation."""
def add_tab(self, label: str, icon: str, content_func: Callable):
"""Add a tab."""
self.tabs.append({
"label": label,
"icon": icon,
"content": content
'label': label,
'icon': icon,
'content_func': content_func
})
def render(self) -> None:
"""Render the tabs with consistent styling."""
def render(self):
"""Render the tabs."""
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
]
tab_labels = [f"{tab['icon']} {tab['label']}" for tab in self.tabs]
selected_tabs = st.tabs(tab_labels)
# 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"]()
for i, tab in enumerate(self.tabs):
with selected_tabs[i]:
tab['content_func']()
class Breadcrumbs:
"""Breadcrumb navigation component."""
def __init__(
self,
items: Optional[List[Dict[str, Any]]] = None
):
self.items = items or []
def __init__(self):
"""Initialize breadcrumbs."""
self.items = []
def add_item(
self,
label: str,
page: Optional[str] = None,
icon: Optional[str] = None
) -> None:
def add_item(self, label: str, key: str = None, callback: Callable = None):
"""Add a breadcrumb item."""
self.items.append({
"label": label,
"page": page,
"icon": icon
'label': label,
'key': key,
'callback': callback
})
def render(self) -> None:
"""Render the breadcrumbs with consistent styling."""
def render(self):
"""Render the breadcrumbs."""
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}
""")
breadcrumb_html = '<div class="nav-breadcrumb">'
st.markdown(f"""
<div class="breadcrumbs">
{''.join(breadcrumb_items)}
</div>
""", unsafe_allow_html=True)
for i, item in enumerate(self.items):
if i > 0:
breadcrumb_html += '<span class="breadcrumb-separator"></span>'
if item.get('callback'):
breadcrumb_html += f'<span class="breadcrumb-item clickable" onclick="handleBreadcrumbClick(\'{item["key"]}\')">{item["label"]}</span>'
else:
breadcrumb_html += f'<span class="breadcrumb-item">{item["label"]}</span>'
breadcrumb_html += '</div>'
st.markdown(breadcrumb_html, unsafe_allow_html=True)
def create_main_navigation() -> TwitterNavigation:
"""Create and return the main navigation instance."""
return TwitterNavigation()
def render_page_header(title: str, subtitle: str = "", icon: str = ""):
"""Render a consistent page header."""
st.markdown(f"""
<div style="text-align: center; margin-bottom: 2rem; padding: 2rem; background: linear-gradient(135deg, #E6F7FF, #F0F9FF); border-radius: 16px;">
<h1 style="color: #1DA1F2; margin-bottom: 0.5rem;">{icon} {title}</h1>
{f'<p style="color: #657786; font-size: 1.1rem;">{subtitle}</p>' if subtitle else ''}
</div>
""", unsafe_allow_html=True)
def render_quick_actions(actions: List[Dict]):
"""Render quick action buttons."""
st.markdown("### ⚡ Quick Actions")
cols = st.columns(len(actions))
for i, action in enumerate(actions):
with cols[i]:
if st.button(
f"{action.get('icon', '')} {action.get('label', '')}",
key=f"quick_action_{i}",
use_container_width=True,
help=action.get('help', '')
):
if action.get('callback'):
action['callback']()
# Default menu items for Twitter dashboard
DEFAULT_MENU_ITEMS = [
{
'key': 'dashboard',
'label': 'Dashboard',
'icon': '🏠',
'help': 'Main dashboard overview'
},
{
'key': 'generator',
'label': 'Tweet Generator',
'icon': '',
'help': 'AI-powered tweet generation'
},
{
'key': 'analytics',
'label': 'Analytics',
'icon': '📊',
'help': 'Tweet performance analytics'
},
{
'key': 'scheduler',
'label': 'Scheduler',
'icon': '📅',
'help': 'Schedule tweets for later'
},
{
'key': 'settings',
'label': 'Settings',
'icon': '⚙️',
'help': 'Twitter account and API settings'
}
]
DEFAULT_QUICK_ACTIONS = [
{
'key': 'new_tweet',
'label': 'New Tweet',
'icon': '✍️',
'help': 'Create a new tweet'
},
{
'key': 'ai_generate',
'label': 'AI Generate',
'icon': '🤖',
'help': 'Generate tweets with AI'
},
{
'key': 'view_analytics',
'label': 'View Analytics',
'icon': '📈',
'help': 'Check tweet performance'
}
]

View File

@@ -0,0 +1,503 @@
"""
Enhanced Twitter Dashboard with real authentication and posting capabilities.
"""
import streamlit as st
import asyncio
from datetime import datetime, timedelta
import json
from typing import Dict, Any, List, Optional
# Import our enhanced components
from .components.navigation import TwitterNavigation, create_main_navigation
from .components.cards import TwitterCard, create_analytics_card, create_tweet_card
from .components.forms import TweetForm, TwitterConfigForm
from ..tweet_generator.smart_tweet_generator import (
smart_tweet_generator,
post_tweet_to_twitter,
get_real_tweet_analytics,
render_twitter_authentication
)
from ....integrations.twitter_auth_bridge import (
TwitterAuthBridge,
save_twitter_credentials,
load_twitter_credentials,
is_twitter_authenticated,
setup_twitter_session,
clear_twitter_session
)
# Initialize authentication bridge
auth_bridge = TwitterAuthBridge()
def initialize_dashboard():
"""Initialize the Twitter dashboard with proper styling and state management."""
# Apply custom CSS
st.markdown("""
<style>
.main-dashboard {
padding: 1rem;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
}
.dashboard-header {
background: white;
padding: 2rem;
border-radius: 15px;
box-shadow: 0 10px 30px rgba(0,0,0,0.1);
margin-bottom: 2rem;
text-align: center;
}
.dashboard-title {
font-size: 2.5rem;
font-weight: 700;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
margin-bottom: 0.5rem;
}
.dashboard-subtitle {
color: #666;
font-size: 1.1rem;
margin-bottom: 1rem;
}
.status-indicator {
display: inline-flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 1rem;
border-radius: 25px;
font-weight: 500;
font-size: 0.9rem;
}
.status-connected {
background: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
}
.status-disconnected {
background: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
}
.dashboard-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 2rem;
margin-bottom: 2rem;
}
@media (max-width: 768px) {
.dashboard-grid {
grid-template-columns: 1fr;
}
}
.action-button {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
padding: 0.75rem 1.5rem;
border-radius: 8px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
}
.action-button:hover {
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
}
.metrics-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1rem;
margin: 1rem 0;
}
.metric-card {
background: white;
padding: 1.5rem;
border-radius: 10px;
box-shadow: 0 5px 15px rgba(0,0,0,0.1);
text-align: center;
}
.metric-value {
font-size: 2rem;
font-weight: 700;
color: #667eea;
margin-bottom: 0.5rem;
}
.metric-label {
color: #666;
font-size: 0.9rem;
text-transform: uppercase;
letter-spacing: 0.5px;
}
</style>
""", unsafe_allow_html=True)
# Initialize session state
if 'twitter_dashboard_initialized' not in st.session_state:
st.session_state.twitter_dashboard_initialized = True
st.session_state.current_page = 'dashboard'
st.session_state.tweet_drafts = []
st.session_state.posted_tweets = []
st.session_state.analytics_data = {}
def render_dashboard_header():
"""Render the main dashboard header with connection status."""
st.markdown('<div class="dashboard-header">', unsafe_allow_html=True)
col1, col2, col3 = st.columns([1, 2, 1])
with col2:
st.markdown('<h1 class="dashboard-title">🐦 Twitter AI Dashboard</h1>', unsafe_allow_html=True)
st.markdown('<p class="dashboard-subtitle">AI-Powered Tweet Generation & Analytics</p>', unsafe_allow_html=True)
# Connection status
is_connected = is_twitter_authenticated()
if is_connected:
user_info = st.session_state.get('twitter_user', {})
username = user_info.get('screen_name', 'Unknown')
st.markdown(f'''
<div class="status-indicator status-connected">
✅ Connected as @{username}
</div>
''', unsafe_allow_html=True)
else:
st.markdown('''
<div class="status-indicator status-disconnected">
❌ Not Connected to Twitter
</div>
''', unsafe_allow_html=True)
st.markdown('</div>', unsafe_allow_html=True)
def render_quick_actions():
"""Render quick action buttons."""
st.markdown("### 🚀 Quick Actions")
col1, col2, col3, col4 = st.columns(4)
with col1:
if st.button("📝 Generate Tweet", key="quick_generate", help="Create AI-powered tweets"):
st.session_state.current_page = 'generate'
st.rerun()
with col2:
if st.button("📊 View Analytics", key="quick_analytics", help="View tweet performance"):
st.session_state.current_page = 'analytics'
st.rerun()
with col3:
if st.button("⚙️ Settings", key="quick_settings", help="Configure Twitter connection"):
st.session_state.current_page = 'settings'
st.rerun()
with col4:
if st.button("📋 Drafts", key="quick_drafts", help="Manage tweet drafts"):
st.session_state.current_page = 'drafts'
st.rerun()
def render_dashboard_overview():
"""Render the main dashboard overview with metrics."""
if not is_twitter_authenticated():
st.warning("⚠️ Please connect your Twitter account to view dashboard metrics.")
if st.button("Connect Twitter Account", type="primary"):
st.session_state.current_page = 'settings'
st.rerun()
return
# Get user metrics
user_info = st.session_state.get('twitter_user', {})
# Display metrics
st.markdown("### 📈 Account Overview")
col1, col2, col3, col4 = st.columns(4)
with col1:
st.markdown(f'''
<div class="metric-card">
<div class="metric-value">{user_info.get('followers_count', 0):,}</div>
<div class="metric-label">Followers</div>
</div>
''', unsafe_allow_html=True)
with col2:
st.markdown(f'''
<div class="metric-card">
<div class="metric-value">{user_info.get('friends_count', 0):,}</div>
<div class="metric-label">Following</div>
</div>
''', unsafe_allow_html=True)
with col3:
posted_count = len(st.session_state.get('posted_tweets', []))
st.markdown(f'''
<div class="metric-card">
<div class="metric-value">{posted_count}</div>
<div class="metric-label">Posted Today</div>
</div>
''', unsafe_allow_html=True)
with col4:
draft_count = len(st.session_state.get('tweet_drafts', []))
st.markdown(f'''
<div class="metric-card">
<div class="metric-value">{draft_count}</div>
<div class="metric-label">Drafts</div>
</div>
''', unsafe_allow_html=True)
# Recent activity
st.markdown("### 📝 Recent Activity")
recent_tweets = st.session_state.get('posted_tweets', [])[-5:] # Last 5 tweets
if recent_tweets:
for tweet in reversed(recent_tweets):
with st.expander(f"Tweet: {tweet.get('text', '')[:50]}..."):
col1, col2 = st.columns([2, 1])
with col1:
st.write(f"**Text:** {tweet.get('text', '')}")
st.write(f"**Posted:** {tweet.get('created_at', '')}")
if tweet.get('metrics'):
metrics = tweet['metrics']
st.write(f"**Engagement:** {metrics.get('favorite_count', 0)} likes, "
f"{metrics.get('retweet_count', 0)} retweets")
with col2:
if st.button(f"View Analytics", key=f"analytics_{tweet.get('id')}"):
st.session_state.selected_tweet_id = tweet.get('id')
st.session_state.current_page = 'analytics'
st.rerun()
else:
st.info("No recent tweets found. Start by generating and posting some content!")
def render_settings_page():
"""Render the settings page for Twitter configuration."""
st.markdown("### ⚙️ Twitter Configuration")
# Twitter Authentication Section
with st.expander("🔐 Twitter API Configuration", expanded=not is_twitter_authenticated()):
render_twitter_authentication()
# Account Information
if is_twitter_authenticated():
st.markdown("### 👤 Account Information")
user_info = st.session_state.get('twitter_user', {})
col1, col2 = st.columns(2)
with col1:
st.write(f"**Username:** @{user_info.get('screen_name', 'N/A')}")
st.write(f"**Display Name:** {user_info.get('name', 'N/A')}")
st.write(f"**Followers:** {user_info.get('followers_count', 0):,}")
with col2:
st.write(f"**Following:** {user_info.get('friends_count', 0):,}")
st.write(f"**Tweets:** {user_info.get('statuses_count', 0):,}")
st.write(f"**Account Created:** {user_info.get('created_at', 'N/A')}")
# Disconnect option
st.markdown("---")
if st.button("🔓 Disconnect Twitter Account", type="secondary"):
clear_twitter_session()
st.success("Twitter account disconnected successfully!")
st.rerun()
def render_analytics_page():
"""Render the analytics page with real Twitter metrics."""
st.markdown("### 📊 Tweet Analytics")
if not is_twitter_authenticated():
st.warning("Please connect your Twitter account to view analytics.")
return
# Tweet selection
posted_tweets = st.session_state.get('posted_tweets', [])
if not posted_tweets:
st.info("No tweets found. Generate and post some tweets to see analytics!")
return
# Select tweet for analysis
tweet_options = {
f"{tweet.get('text', '')[:50]}... ({tweet.get('created_at', '')})": tweet.get('id')
for tweet in posted_tweets
}
selected_tweet_text = st.selectbox(
"Select a tweet to analyze:",
options=list(tweet_options.keys())
)
if selected_tweet_text:
tweet_id = tweet_options[selected_tweet_text]
# Get analytics
with st.spinner("Loading analytics..."):
analytics_result = asyncio.run(get_real_tweet_analytics(tweet_id))
if analytics_result.get('success'):
analytics_data = analytics_result['data']
# Display metrics
st.markdown("#### 📈 Performance Metrics")
col1, col2, col3, col4 = st.columns(4)
metrics = analytics_data.get('metrics', {})
with col1:
st.metric("Likes", metrics.get('likes', 0))
with col2:
st.metric("Retweets", metrics.get('retweets', 0))
with col3:
st.metric("Replies", metrics.get('replies', 0))
with col4:
engagement = analytics_data.get('engagement', {})
st.metric("Engagement Rate", f"{engagement.get('engagement_rate', 0):.2f}%")
# Detailed analytics
st.markdown("#### 🔍 Detailed Analysis")
col1, col2 = st.columns(2)
with col1:
st.markdown("**Engagement Breakdown:**")
total_engagement = metrics.get('total_engagement', 0)
st.write(f"• Total Engagement: {total_engagement}")
st.write(f"• Likes Rate: {engagement.get('likes_rate', 0):.2f}%")
st.write(f"• Retweets Rate: {engagement.get('retweets_rate', 0):.2f}%")
with col2:
st.markdown("**Content Analysis:**")
content_analysis = analytics_data.get('content_analysis', {})
st.write(f"• Character Count: {content_analysis.get('character_count', 0)}")
st.write(f"• Hashtags: {content_analysis.get('hashtag_count', 0)}")
st.write(f"• Mentions: {content_analysis.get('mention_count', 0)}")
# Timing analysis
timing = analytics_data.get('timing', {})
if timing:
st.markdown("#### ⏰ Timing Analysis")
st.write(f"• Posted: {timing.get('posted_at', 'N/A')}")
st.write(f"• Age: {timing.get('age_hours', 0):.1f} hours")
st.write(f"• Peak Period: {timing.get('peak_engagement_period', 'N/A')}")
st.write(f"• Engagement Velocity: {timing.get('engagement_velocity', 0):.2f} per hour")
else:
st.error(f"Failed to load analytics: {analytics_result.get('error', 'Unknown error')}")
def render_drafts_page():
"""Render the drafts management page."""
st.markdown("### 📋 Tweet Drafts")
drafts = st.session_state.get('tweet_drafts', [])
if not drafts:
st.info("No drafts found. Create some tweets in the generator to save as drafts!")
return
for i, draft in enumerate(drafts):
with st.expander(f"Draft {i+1}: {draft.get('text', '')[:50]}..."):
col1, col2 = st.columns([3, 1])
with col1:
st.write(f"**Text:** {draft.get('text', '')}")
st.write(f"**Created:** {draft.get('created_at', '')}")
if draft.get('hashtags'):
st.write(f"**Hashtags:** {', '.join(draft['hashtags'])}")
with col2:
if st.button(f"Post Now", key=f"post_draft_{i}"):
if is_twitter_authenticated():
with st.spinner("Posting tweet..."):
result = asyncio.run(post_tweet_to_twitter(draft))
if result.get('success'):
st.success("Tweet posted successfully!")
# Move from drafts to posted
st.session_state.posted_tweets.append(result['data'])
st.session_state.tweet_drafts.pop(i)
st.rerun()
else:
st.error(f"Failed to post: {result.get('error')}")
else:
st.error("Please connect your Twitter account first!")
if st.button(f"Delete", key=f"delete_draft_{i}"):
st.session_state.tweet_drafts.pop(i)
st.rerun()
def main_twitter_dashboard():
"""Main Twitter dashboard function."""
# Initialize dashboard
initialize_dashboard()
# Create navigation
nav = TwitterNavigation()
current_page = nav.render_main_navigation()
# Update session state if page changed
if current_page != st.session_state.get('current_page'):
st.session_state.current_page = current_page
# Render dashboard header
render_dashboard_header()
# Route to appropriate page
page = st.session_state.get('current_page', 'dashboard')
if page == 'dashboard':
render_quick_actions()
render_dashboard_overview()
elif page == 'generate':
st.markdown("### 🤖 AI Tweet Generator")
smart_tweet_generator()
elif page == 'analytics':
render_analytics_page()
elif page == 'settings':
render_settings_page()
elif page == 'drafts':
render_drafts_page()
else:
# Default to dashboard
render_quick_actions()
render_dashboard_overview()
if __name__ == "__main__":
main_twitter_dashboard()