AI Image and Audio Generation Improvements.
AI Video Generation Pre-Flight Checklist. Cost Estimate Improvements.
This commit is contained in:
@@ -1,634 +0,0 @@
|
||||
"""
|
||||
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
@@ -1,554 +0,0 @@
|
||||
"""
|
||||
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'
|
||||
}
|
||||
]
|
||||
Reference in New Issue
Block a user