ALwrity Version 0.5.1 (Fastapi + React)

This commit is contained in:
ajaysi
2025-08-06 16:29:49 +05:30
parent dbf761c31f
commit 2579c12ba4
331 changed files with 0 additions and 22 deletions

View 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

View File

@@ -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"
]

View File

@@ -0,0 +1,634 @@
"""
Enhanced UI Cards with modern styling and improved functionality.
"""
import streamlit as st
from typing import Dict, List, Optional, Callable
import plotly.express as px
import plotly.graph_objects as go
from datetime import datetime
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: 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.stats = stats or {}
self.actions = actions or []
self.on_click = on_click
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>
""")
stats_html = f"""
<div class="feature-stats">
{''.join(stats_items)}
</div>
"""
# 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:
"""Modern tweet card component."""
def __init__(
self,
content: str,
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
):
self.content = content
self.engagement_score = engagement_score
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 _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>
"""
# 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()

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,554 @@
"""
Enhanced Navigation Component for Twitter UI with modern styling and improved functionality.
"""
import streamlit as st
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 = "Navigation", logo: Optional[str] = None):
"""Initialize the sidebar."""
self.title = title
self.logo = logo
self.menu_items = []
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,
'key': key,
'callback': callback
})
def render(self) -> str:
"""Render the sidebar and return the selected page."""
with st.sidebar:
# Logo and title
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:
if st.button(
f"{item['icon']} {item['label']}",
key=f"sidebar_{item['key']}",
use_container_width=True
):
selected_page = item['key']
if item.get('callback'):
item['callback']()
return selected_page or st.session_state.get('current_page', 'dashboard')
class Header:
"""Header component with title and actions."""
def __init__(self, title: str = "Dashboard", subtitle: str = ""):
"""Initialize the header."""
self.title = title
self.subtitle = subtitle
self.actions = []
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': help_text
})
def render(self):
"""Render the header."""
col1, col2 = st.columns([3, 1])
with col1:
st.title(f"{self.title}")
if self.subtitle:
st.markdown(f"*{self.subtitle}*")
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):
"""Initialize the tabs."""
self.tabs = []
def add_tab(self, label: str, icon: str, content_func: Callable):
"""Add a tab."""
self.tabs.append({
'label': label,
'icon': icon,
'content_func': content_func
})
def render(self):
"""Render the tabs."""
if not self.tabs:
return
tab_labels = [f"{tab['icon']} {tab['label']}" for tab in self.tabs]
selected_tabs = st.tabs(tab_labels)
for i, tab in enumerate(self.tabs):
with selected_tabs[i]:
tab['content_func']()
class Breadcrumbs:
"""Breadcrumb navigation component."""
def __init__(self):
"""Initialize breadcrumbs."""
self.items = []
def add_item(self, label: str, key: str = None, callback: Callable = None):
"""Add a breadcrumb item."""
self.items.append({
'label': label,
'key': key,
'callback': callback
})
def render(self):
"""Render the breadcrumbs."""
if not self.items:
return
breadcrumb_html = '<div class="nav-breadcrumb">'
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,278 @@
"""
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
import os
class TwitterDashboard:
"""Main dashboard class for Twitter UI."""
def __init__(self):
"""Initialize the Twitter dashboard."""
self.setup_theme()
self.setup_navigation()
self.setup_state()
def get_logo_path(self) -> str:
"""Get the best available logo path with fallbacks."""
# List of potential logo paths in order of preference
logo_paths = [
"lib/workspace/alwrity_logo.png",
"lib/workspace/AskAlwrity-min.ico",
"lib/workspace/alwrity_ai_writer.png"
]
for path in logo_paths:
if os.path.exists(path):
return path
# If no logo files are found, return None
return None
def setup_theme(self) -> None:
"""Setup theme and styling."""
Theme.apply()
def setup_navigation(self) -> None:
"""Setup navigation components."""
# Sidebar
self.sidebar = Sidebar(
title="Twitter Tools",
logo=self.get_logo_path()
)
# 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.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()

View File

@@ -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)

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()

View File

@@ -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)}")