Content Calendar, Content Gap Analysis, and Content Optimization
This commit is contained in:
203
lib/ai_writers/twitter_writers/twitter_streamlit_ui/README.md
Normal file
203
lib/ai_writers/twitter_writers/twitter_streamlit_ui/README.md
Normal file
@@ -0,0 +1,203 @@
|
||||
# Twitter Streamlit UI Components
|
||||
|
||||
This module provides a unified, reusable UI component library for all Twitter-related features in the AI Writer suite. It implements best practices for Streamlit UI development and ensures consistency across all Twitter tools.
|
||||
|
||||
## Structure
|
||||
|
||||
```
|
||||
twitter_streamlit_ui/
|
||||
├── components/ # Reusable UI components
|
||||
│ ├── __init__.py
|
||||
│ ├── cards.py # Card components (feature cards, tweet cards)
|
||||
│ ├── forms.py # Form components (input forms, settings forms)
|
||||
│ ├── navigation.py # Navigation components (tabs, sidebar)
|
||||
│ ├── feedback.py # Feedback components (loading, errors, success)
|
||||
│ └── layout.py # Layout components (containers, columns)
|
||||
├── styles/ # CSS and styling
|
||||
│ ├── __init__.py
|
||||
│ ├── theme.py # Theme configuration
|
||||
│ ├── components.py # Component-specific styles
|
||||
│ └── animations.py # Animation styles
|
||||
├── utils/ # UI utilities
|
||||
│ ├── __init__.py
|
||||
│ ├── state.py # State management
|
||||
│ ├── validation.py # Input validation
|
||||
│ └── performance.py # Performance optimizations
|
||||
└── README.md # This file
|
||||
```
|
||||
|
||||
## Key Improvements
|
||||
|
||||
### 1. Consistent UI Components
|
||||
|
||||
- **Card Components**
|
||||
- Feature cards with consistent styling
|
||||
- Tweet cards with standardized layout
|
||||
- Status badges with unified design
|
||||
|
||||
- **Form Components**
|
||||
- Standardized input forms
|
||||
- Consistent validation feedback
|
||||
- Unified error handling
|
||||
|
||||
- **Navigation Components**
|
||||
- Consistent tab styling
|
||||
- Standardized sidebar navigation
|
||||
- Breadcrumb navigation
|
||||
|
||||
### 2. Enhanced User Experience
|
||||
|
||||
- **Loading States**
|
||||
- Progress indicators for long operations
|
||||
- Skeleton loading for content
|
||||
- Smooth transitions between states
|
||||
|
||||
- **Feedback Mechanisms**
|
||||
- Toast notifications for actions
|
||||
- Error messages with recovery options
|
||||
- Success confirmations
|
||||
|
||||
- **Responsive Design**
|
||||
- Mobile-friendly layouts
|
||||
- Adaptive column systems
|
||||
- Flexible containers
|
||||
|
||||
### 3. Performance Optimizations
|
||||
|
||||
- **State Management**
|
||||
- Centralized state handling
|
||||
- Efficient data persistence
|
||||
- Optimized re-rendering
|
||||
|
||||
- **Resource Loading**
|
||||
- Lazy loading of components
|
||||
- Optimized image loading
|
||||
- Cached computations
|
||||
|
||||
### 4. Accessibility Features
|
||||
|
||||
- **Keyboard Navigation**
|
||||
- Focus management
|
||||
- Keyboard shortcuts
|
||||
- ARIA labels
|
||||
|
||||
- **Visual Accessibility**
|
||||
- High contrast themes
|
||||
- Screen reader support
|
||||
- Color blind friendly
|
||||
|
||||
### 5. Error Handling
|
||||
|
||||
- **Graceful Degradation**
|
||||
- Fallback UI components
|
||||
- Error boundaries
|
||||
- Recovery options
|
||||
|
||||
- **User Feedback**
|
||||
- Clear error messages
|
||||
- Actionable suggestions
|
||||
- Help documentation
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Component Usage
|
||||
|
||||
```python
|
||||
from twitter_streamlit_ui.components.cards import FeatureCard
|
||||
from twitter_streamlit_ui.components.forms import TweetForm
|
||||
from twitter_streamlit_ui.styles.theme import apply_theme
|
||||
|
||||
# Apply theme
|
||||
apply_theme()
|
||||
|
||||
# Use components
|
||||
feature_card = FeatureCard(
|
||||
title="Tweet Generator",
|
||||
description="Create engaging tweets with AI",
|
||||
icon="🐦"
|
||||
)
|
||||
feature_card.render()
|
||||
|
||||
tweet_form = TweetForm()
|
||||
tweet_form.render()
|
||||
```
|
||||
|
||||
### State Management
|
||||
|
||||
```python
|
||||
from twitter_streamlit_ui.utils.state import StateManager
|
||||
|
||||
# Initialize state
|
||||
state = StateManager()
|
||||
state.initialize()
|
||||
|
||||
# Update state
|
||||
state.update("current_tweet", tweet_data)
|
||||
```
|
||||
|
||||
### Error Handling
|
||||
|
||||
```python
|
||||
from twitter_streamlit_ui.components.feedback import ErrorBoundary
|
||||
|
||||
with ErrorBoundary():
|
||||
# Your code here
|
||||
pass
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Component Reusability**
|
||||
- Use existing components when possible
|
||||
- Create new components only when necessary
|
||||
- Follow the established patterns
|
||||
|
||||
2. **State Management**
|
||||
- Use the StateManager for all state
|
||||
- Avoid direct session state manipulation
|
||||
- Keep state updates atomic
|
||||
|
||||
3. **Performance**
|
||||
- Use lazy loading for heavy components
|
||||
- Implement caching where appropriate
|
||||
- Monitor render performance
|
||||
|
||||
4. **Accessibility**
|
||||
- Include ARIA labels
|
||||
- Ensure keyboard navigation
|
||||
- Test with screen readers
|
||||
|
||||
5. **Error Handling**
|
||||
- Use ErrorBoundary components
|
||||
- Provide clear error messages
|
||||
- Include recovery options
|
||||
|
||||
## Future Improvements
|
||||
|
||||
1. **Component Library**
|
||||
- Add more specialized components
|
||||
- Enhance existing components
|
||||
- Create component documentation
|
||||
|
||||
2. **Theme System**
|
||||
- Add more theme options
|
||||
- Implement theme switching
|
||||
- Create custom theme builder
|
||||
|
||||
3. **Performance**
|
||||
- Implement virtual scrolling
|
||||
- Add performance monitoring
|
||||
- Optimize resource loading
|
||||
|
||||
4. **Testing**
|
||||
- Add component tests
|
||||
- Implement E2E tests
|
||||
- Create test documentation
|
||||
|
||||
## Contributing
|
||||
|
||||
1. Follow the established patterns
|
||||
2. Add tests for new components
|
||||
3. Update documentation
|
||||
4. Ensure accessibility
|
||||
5. Optimize performance
|
||||
@@ -0,0 +1,66 @@
|
||||
"""
|
||||
Twitter Streamlit UI package.
|
||||
Provides a modern and user-friendly interface for Twitter tools.
|
||||
"""
|
||||
|
||||
from .dashboard import TwitterDashboard
|
||||
from .components.cards import FeatureCard, TweetCard
|
||||
from .components.forms import TweetForm, SettingsForm
|
||||
from .components.navigation import Sidebar, Header, Tabs, Breadcrumbs
|
||||
from .styles.theme import Theme
|
||||
from .utils.helpers import (
|
||||
save_to_session,
|
||||
get_from_session,
|
||||
clear_session,
|
||||
save_to_file,
|
||||
load_from_file,
|
||||
format_datetime,
|
||||
parse_datetime,
|
||||
validate_tweet_content,
|
||||
validate_hashtags,
|
||||
validate_emojis,
|
||||
calculate_engagement_score,
|
||||
generate_tweet_metrics,
|
||||
copy_to_clipboard,
|
||||
show_success_message,
|
||||
show_error_message,
|
||||
show_info_message,
|
||||
show_warning_message,
|
||||
create_download_button,
|
||||
create_upload_button
|
||||
)
|
||||
|
||||
__version__ = "1.0.0"
|
||||
__author__ = "AI Writer Team"
|
||||
|
||||
__all__ = [
|
||||
"TwitterDashboard",
|
||||
"FeatureCard",
|
||||
"TweetCard",
|
||||
"TweetForm",
|
||||
"SettingsForm",
|
||||
"Sidebar",
|
||||
"Header",
|
||||
"Tabs",
|
||||
"Breadcrumbs",
|
||||
"Theme",
|
||||
"save_to_session",
|
||||
"get_from_session",
|
||||
"clear_session",
|
||||
"save_to_file",
|
||||
"load_from_file",
|
||||
"format_datetime",
|
||||
"parse_datetime",
|
||||
"validate_tweet_content",
|
||||
"validate_hashtags",
|
||||
"validate_emojis",
|
||||
"calculate_engagement_score",
|
||||
"generate_tweet_metrics",
|
||||
"copy_to_clipboard",
|
||||
"show_success_message",
|
||||
"show_error_message",
|
||||
"show_info_message",
|
||||
"show_warning_message",
|
||||
"create_download_button",
|
||||
"create_upload_button"
|
||||
]
|
||||
@@ -0,0 +1,174 @@
|
||||
"""
|
||||
Card components for Twitter UI.
|
||||
Provides consistent card layouts for features and tweets.
|
||||
"""
|
||||
|
||||
import streamlit as st
|
||||
from typing import Dict, Any, Optional, List
|
||||
from ..styles.theme import Theme
|
||||
|
||||
class BaseCard:
|
||||
"""Base class for all card components."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
title: str,
|
||||
description: str,
|
||||
icon: Optional[str] = None,
|
||||
status: Optional[str] = None,
|
||||
actions: Optional[List[Dict[str, Any]]] = None
|
||||
):
|
||||
self.title = title
|
||||
self.description = description
|
||||
self.icon = icon
|
||||
self.status = status
|
||||
self.actions = actions or []
|
||||
|
||||
def render(self) -> None:
|
||||
"""Render the card with consistent styling."""
|
||||
with st.container():
|
||||
st.markdown(f"""
|
||||
<div class="card">
|
||||
<div style="display: flex; align-items: center; margin-bottom: {Theme.SPACING["sm"]};">
|
||||
{f'<span style="font-size: 1.5em; margin-right: {Theme.SPACING["sm"]};">{self.icon}</span>' if self.icon else ''}
|
||||
<h3 style="margin: 0;">{self.title}</h3>
|
||||
</div>
|
||||
<p style="color: {Theme.COLORS["text_secondary"]}; margin: {Theme.SPACING["sm"]} 0;">
|
||||
{self.description}
|
||||
</p>
|
||||
{f'<span class="status-badge status-{self.status}">{self.status.title()}</span>' if self.status else ''}
|
||||
</div>
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
if self.actions:
|
||||
cols = st.columns(len(self.actions))
|
||||
for i, action in enumerate(self.actions):
|
||||
with cols[i]:
|
||||
if st.button(
|
||||
action["label"],
|
||||
key=f"action_{i}",
|
||||
help=action.get("help"),
|
||||
use_container_width=True
|
||||
):
|
||||
action["callback"]()
|
||||
|
||||
class FeatureCard(BaseCard):
|
||||
"""Card component for displaying features."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
title: str,
|
||||
description: str,
|
||||
icon: str,
|
||||
status: str = "active",
|
||||
features: Optional[List[Dict[str, Any]]] = None,
|
||||
on_click: Optional[callable] = None
|
||||
):
|
||||
super().__init__(title, description, icon, status)
|
||||
self.features = features or []
|
||||
self.on_click = on_click
|
||||
|
||||
def render(self) -> None:
|
||||
"""Render the feature card with enhanced styling."""
|
||||
with st.container():
|
||||
st.markdown(f"""
|
||||
<div class="card feature-card">
|
||||
<div style="display: flex; align-items: center; margin-bottom: {Theme.SPACING["sm"]};">
|
||||
<span style="font-size: 1.5em; margin-right: {Theme.SPACING["sm"]};">{self.icon}</span>
|
||||
<h3 style="margin: 0;">{self.title}</h3>
|
||||
</div>
|
||||
<p style="color: {Theme.COLORS["text_secondary"]}; margin: {Theme.SPACING["sm"]} 0;">
|
||||
{self.description}
|
||||
</p>
|
||||
<span class="status-badge status-{self.status}">{self.status.title()}</span>
|
||||
</div>
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
if self.features:
|
||||
for feature in self.features:
|
||||
st.markdown(f"""
|
||||
<div style="margin-left: {Theme.SPACING["lg"]}; margin-top: {Theme.SPACING["sm"]};">
|
||||
<p style="margin: 0;">
|
||||
<strong>{feature["name"]}</strong>: {feature["description"]}
|
||||
</p>
|
||||
</div>
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
if self.on_click:
|
||||
if st.button(
|
||||
f"Launch {self.title}",
|
||||
key=f"launch_{self.title.lower().replace(' ', '_')}",
|
||||
use_container_width=True
|
||||
):
|
||||
self.on_click()
|
||||
|
||||
class TweetCard(BaseCard):
|
||||
"""Card component for displaying tweets."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
content: str,
|
||||
engagement_score: float,
|
||||
hashtags: List[str],
|
||||
emojis: List[str],
|
||||
metrics: Optional[Dict[str, Any]] = None,
|
||||
on_copy: Optional[callable] = None,
|
||||
on_save: Optional[callable] = None
|
||||
):
|
||||
super().__init__(
|
||||
title="Tweet",
|
||||
description=content,
|
||||
icon="🐦",
|
||||
actions=[
|
||||
{
|
||||
"label": "Copy",
|
||||
"callback": on_copy or (lambda: None),
|
||||
"help": "Copy tweet to clipboard"
|
||||
},
|
||||
{
|
||||
"label": "Save",
|
||||
"callback": on_save or (lambda: None),
|
||||
"help": "Save tweet for later"
|
||||
}
|
||||
]
|
||||
)
|
||||
self.engagement_score = engagement_score
|
||||
self.hashtags = hashtags
|
||||
self.emojis = emojis
|
||||
self.metrics = metrics or {}
|
||||
|
||||
def render(self) -> None:
|
||||
"""Render the tweet card with metrics and actions."""
|
||||
with st.container():
|
||||
st.markdown(f"""
|
||||
<div class="card tweet-card">
|
||||
<div style="display: flex; align-items: center; margin-bottom: {Theme.SPACING["sm"]};">
|
||||
<span style="font-size: 1.5em; margin-right: {Theme.SPACING["sm"]};">{self.icon}</span>
|
||||
<h3 style="margin: 0;">Tweet</h3>
|
||||
</div>
|
||||
<p style="color: {Theme.COLORS["text"]}; margin: {Theme.SPACING["sm"]} 0;">
|
||||
{self.description}
|
||||
</p>
|
||||
<div style="display: flex; gap: {Theme.SPACING["sm"]}; margin: {Theme.SPACING["sm"]} 0;">
|
||||
{''.join(f'<span style="color: {Theme.COLORS["primary"]};">{tag}</span>' for tag in self.hashtags)}
|
||||
</div>
|
||||
<div style="display: flex; gap: {Theme.SPACING["sm"]}; margin: {Theme.SPACING["sm"]} 0;">
|
||||
{''.join(f'<span>{emoji}</span>' for emoji in self.emojis)}
|
||||
</div>
|
||||
<div style="margin-top: {Theme.SPACING["md"]};">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center;">
|
||||
<span>Engagement Score: {self.engagement_score}%</span>
|
||||
<div style="display: flex; gap: {Theme.SPACING["sm"]};">
|
||||
<button class="stButton" onclick="copyTweet()">Copy</button>
|
||||
<button class="stButton" onclick="saveTweet()">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
if self.metrics:
|
||||
cols = st.columns(len(self.metrics))
|
||||
for i, (metric, value) in enumerate(self.metrics.items()):
|
||||
with cols[i]:
|
||||
st.metric(metric, f"{value}%")
|
||||
@@ -0,0 +1,255 @@
|
||||
"""
|
||||
Form components for Twitter UI.
|
||||
Provides consistent form layouts and input validation.
|
||||
"""
|
||||
|
||||
import streamlit as st
|
||||
from typing import Dict, Any, Optional, List, Callable
|
||||
from ..styles.theme import Theme
|
||||
|
||||
class BaseForm:
|
||||
"""Base class for all form components."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
title: str,
|
||||
description: Optional[str] = None,
|
||||
on_submit: Optional[Callable] = None
|
||||
):
|
||||
self.title = title
|
||||
self.description = description
|
||||
self.on_submit = on_submit
|
||||
self.fields: Dict[str, Any] = {}
|
||||
|
||||
def add_field(
|
||||
self,
|
||||
key: str,
|
||||
label: str,
|
||||
field_type: str = "text",
|
||||
required: bool = False,
|
||||
help_text: Optional[str] = None,
|
||||
options: Optional[List[str]] = None,
|
||||
default: Any = None,
|
||||
validation: Optional[Callable] = None
|
||||
) -> None:
|
||||
"""Add a field to the form."""
|
||||
self.fields[key] = {
|
||||
"label": label,
|
||||
"type": field_type,
|
||||
"required": required,
|
||||
"help_text": help_text,
|
||||
"options": options,
|
||||
"default": default,
|
||||
"validation": validation
|
||||
}
|
||||
|
||||
def validate(self) -> bool:
|
||||
"""Validate all form fields."""
|
||||
for key, field in self.fields.items():
|
||||
if field["required"] and not st.session_state.get(key):
|
||||
st.error(f"{field['label']} is required")
|
||||
return False
|
||||
if field["validation"] and not field["validation"](st.session_state.get(key)):
|
||||
return False
|
||||
return True
|
||||
|
||||
def render(self) -> None:
|
||||
"""Render the form with consistent styling."""
|
||||
with st.container():
|
||||
st.markdown(f"""
|
||||
<div class="form-container">
|
||||
<h3 style="margin-bottom: {Theme.SPACING['sm']};">{self.title}</h3>
|
||||
{f'<p style="color: {Theme.COLORS["text_secondary"]}; margin-bottom: {Theme.SPACING["md"]};">{self.description}</p>' if self.description else ''}
|
||||
</div>
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
for key, field in self.fields.items():
|
||||
if field["type"] == "text":
|
||||
st.text_input(
|
||||
field["label"],
|
||||
key=key,
|
||||
help=field["help_text"],
|
||||
value=field["default"]
|
||||
)
|
||||
elif field["type"] == "textarea":
|
||||
st.text_area(
|
||||
field["label"],
|
||||
key=key,
|
||||
help=field["help_text"],
|
||||
value=field["default"]
|
||||
)
|
||||
elif field["type"] == "select":
|
||||
st.selectbox(
|
||||
field["label"],
|
||||
options=field["options"],
|
||||
key=key,
|
||||
help=field["help_text"],
|
||||
index=field["options"].index(field["default"]) if field["default"] in field["options"] else 0
|
||||
)
|
||||
elif field["type"] == "multiselect":
|
||||
st.multiselect(
|
||||
field["label"],
|
||||
options=field["options"],
|
||||
key=key,
|
||||
help=field["help_text"],
|
||||
default=field["default"]
|
||||
)
|
||||
elif field["type"] == "number":
|
||||
st.number_input(
|
||||
field["label"],
|
||||
key=key,
|
||||
help=field["help_text"],
|
||||
value=field["default"]
|
||||
)
|
||||
elif field["type"] == "slider":
|
||||
st.slider(
|
||||
field["label"],
|
||||
key=key,
|
||||
help=field["help_text"],
|
||||
value=field["default"]
|
||||
)
|
||||
elif field["type"] == "checkbox":
|
||||
st.checkbox(
|
||||
field["label"],
|
||||
key=key,
|
||||
help=field["help_text"],
|
||||
value=field["default"]
|
||||
)
|
||||
|
||||
if st.button("Submit", use_container_width=True):
|
||||
if self.validate() and self.on_submit:
|
||||
self.on_submit()
|
||||
|
||||
class TweetForm(BaseForm):
|
||||
"""Form component for tweet generation."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
on_submit: Optional[Callable] = None,
|
||||
default_tone: str = "professional",
|
||||
default_length: str = "medium"
|
||||
):
|
||||
super().__init__(
|
||||
title="Generate Tweet",
|
||||
description="Create engaging tweets with AI assistance",
|
||||
on_submit=on_submit
|
||||
)
|
||||
|
||||
# Add tweet content field
|
||||
self.add_field(
|
||||
"tweet_content",
|
||||
"Tweet Content",
|
||||
field_type="textarea",
|
||||
required=True,
|
||||
help_text="Enter your tweet content or topic"
|
||||
)
|
||||
|
||||
# Add tone selection
|
||||
self.add_field(
|
||||
"tone",
|
||||
"Tweet Tone",
|
||||
field_type="select",
|
||||
options=["professional", "casual", "humorous", "informative", "inspirational"],
|
||||
default=default_tone,
|
||||
help_text="Select the tone for your tweet"
|
||||
)
|
||||
|
||||
# Add length selection
|
||||
self.add_field(
|
||||
"length",
|
||||
"Tweet Length",
|
||||
field_type="select",
|
||||
options=["short", "medium", "long"],
|
||||
default=default_length,
|
||||
help_text="Select the desired length of your tweet"
|
||||
)
|
||||
|
||||
# Add hashtag options
|
||||
self.add_field(
|
||||
"hashtags",
|
||||
"Hashtags",
|
||||
field_type="multiselect",
|
||||
options=["#AI", "#Tech", "#Innovation", "#Business", "#Marketing"],
|
||||
help_text="Select relevant hashtags"
|
||||
)
|
||||
|
||||
# Add emoji options
|
||||
self.add_field(
|
||||
"emojis",
|
||||
"Emojis",
|
||||
field_type="multiselect",
|
||||
options=["🚀", "💡", "🎯", "🔥", "✨"],
|
||||
help_text="Select emojis to include"
|
||||
)
|
||||
|
||||
# Add engagement settings
|
||||
self.add_field(
|
||||
"engagement_boost",
|
||||
"Engagement Boost",
|
||||
field_type="slider",
|
||||
default=50,
|
||||
help_text="Adjust the engagement optimization level"
|
||||
)
|
||||
|
||||
class SettingsForm(BaseForm):
|
||||
"""Form component for user settings."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
on_submit: Optional[Callable] = None,
|
||||
default_settings: Optional[Dict[str, Any]] = None
|
||||
):
|
||||
super().__init__(
|
||||
title="User Settings",
|
||||
description="Customize your Twitter experience",
|
||||
on_submit=on_submit
|
||||
)
|
||||
|
||||
settings = default_settings or {}
|
||||
|
||||
# Add API settings
|
||||
self.add_field(
|
||||
"api_key",
|
||||
"Twitter API Key",
|
||||
field_type="text",
|
||||
help_text="Enter your Twitter API key",
|
||||
default=settings.get("api_key", "")
|
||||
)
|
||||
|
||||
# Add theme settings
|
||||
self.add_field(
|
||||
"theme",
|
||||
"Theme",
|
||||
field_type="select",
|
||||
options=["light", "dark", "system"],
|
||||
default=settings.get("theme", "system"),
|
||||
help_text="Select your preferred theme"
|
||||
)
|
||||
|
||||
# Add notification settings
|
||||
self.add_field(
|
||||
"notifications",
|
||||
"Enable Notifications",
|
||||
field_type="checkbox",
|
||||
default=settings.get("notifications", True),
|
||||
help_text="Receive notifications for important updates"
|
||||
)
|
||||
|
||||
# Add auto-save settings
|
||||
self.add_field(
|
||||
"auto_save",
|
||||
"Auto-save Drafts",
|
||||
field_type="checkbox",
|
||||
default=settings.get("auto_save", True),
|
||||
help_text="Automatically save tweet drafts"
|
||||
)
|
||||
|
||||
# Add language settings
|
||||
self.add_field(
|
||||
"language",
|
||||
"Language",
|
||||
field_type="select",
|
||||
options=["English", "Spanish", "French", "German", "Japanese"],
|
||||
default=settings.get("language", "English"),
|
||||
help_text="Select your preferred language"
|
||||
)
|
||||
@@ -0,0 +1,222 @@
|
||||
"""
|
||||
Navigation components for Twitter UI.
|
||||
Provides consistent navigation and layout structure.
|
||||
"""
|
||||
|
||||
import streamlit as st
|
||||
from typing import Dict, Any, Optional, List
|
||||
from ..styles.theme import Theme
|
||||
|
||||
class Sidebar:
|
||||
"""Sidebar navigation component."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
title: str = "Twitter Tools",
|
||||
logo: Optional[str] = None,
|
||||
menu_items: Optional[List[Dict[str, Any]]] = None
|
||||
):
|
||||
self.title = title
|
||||
self.logo = logo
|
||||
self.menu_items = menu_items or []
|
||||
|
||||
def add_menu_item(
|
||||
self,
|
||||
label: str,
|
||||
icon: str,
|
||||
page: str,
|
||||
badge: Optional[str] = None
|
||||
) -> None:
|
||||
"""Add a menu item to the sidebar."""
|
||||
self.menu_items.append({
|
||||
"label": label,
|
||||
"icon": icon,
|
||||
"page": page,
|
||||
"badge": badge
|
||||
})
|
||||
|
||||
def render(self) -> None:
|
||||
"""Render the sidebar with consistent styling."""
|
||||
with st.sidebar:
|
||||
# Logo and title
|
||||
if self.logo:
|
||||
st.image(self.logo, width=50)
|
||||
st.markdown(f"""
|
||||
<h2 style="margin: {Theme.SPACING["sm"]} 0;">{self.title}</h2>
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
# Menu items
|
||||
for item in self.menu_items:
|
||||
st.markdown(f"""
|
||||
<div class="menu-item">
|
||||
<span style="font-size: 1.2em; margin-right: {Theme.SPACING["sm"]};">{item["icon"]}</span>
|
||||
<span>{item["label"]}</span>
|
||||
{f'<span class="badge">{item["badge"]}</span>' if item.get("badge") else ""}
|
||||
</div>
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
if st.button(
|
||||
item["label"],
|
||||
key=f"nav_{item['page']}",
|
||||
use_container_width=True
|
||||
):
|
||||
st.session_state["current_page"] = item["page"]
|
||||
|
||||
class Header:
|
||||
"""Header navigation component."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
title: str,
|
||||
subtitle: Optional[str] = None,
|
||||
actions: Optional[List[Dict[str, Any]]] = None
|
||||
):
|
||||
self.title = title
|
||||
self.subtitle = subtitle
|
||||
self.actions = actions or []
|
||||
|
||||
def add_action(
|
||||
self,
|
||||
label: str,
|
||||
icon: str,
|
||||
callback: callable,
|
||||
help_text: Optional[str] = None
|
||||
) -> None:
|
||||
"""Add an action button to the header."""
|
||||
self.actions.append({
|
||||
"label": label,
|
||||
"icon": icon,
|
||||
"callback": callback,
|
||||
"help_text": help_text
|
||||
})
|
||||
|
||||
def render(self) -> None:
|
||||
"""Render the header with consistent styling."""
|
||||
# Build action buttons HTML
|
||||
action_buttons = []
|
||||
for action in self.actions:
|
||||
help_text = action.get("help_text", "")
|
||||
action_buttons.append(f"""
|
||||
<button class="header-action" title="{help_text}">
|
||||
<span style="font-size: 1.2em; margin-right: {Theme.SPACING["xs"]};">{action["icon"]}</span>
|
||||
{action["label"]}
|
||||
</button>
|
||||
""")
|
||||
|
||||
st.markdown(f"""
|
||||
<div class="header">
|
||||
<div>
|
||||
<h1 style="margin: 0;">{self.title}</h1>
|
||||
{f'<p style="color: {Theme.COLORS["text_secondary"]}; margin: {Theme.SPACING["xs"]} 0;">{self.subtitle}</p>' if self.subtitle else ""}
|
||||
</div>
|
||||
<div style="display: flex; gap: {Theme.SPACING["sm"]};">
|
||||
{''.join(action_buttons)}
|
||||
</div>
|
||||
</div>
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
# Add action button callbacks
|
||||
for i, action in enumerate(self.actions):
|
||||
if st.button(
|
||||
action["label"],
|
||||
key=f"header_action_{i}",
|
||||
help=action.get("help_text")
|
||||
):
|
||||
action["callback"]()
|
||||
|
||||
class Tabs:
|
||||
"""Tab navigation component."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
tabs: Optional[List[Dict[str, Any]]] = None,
|
||||
default_tab: Optional[str] = None
|
||||
):
|
||||
self.tabs = tabs or []
|
||||
self.default_tab = default_tab
|
||||
|
||||
def add_tab(
|
||||
self,
|
||||
label: str,
|
||||
icon: Optional[str] = None,
|
||||
content: Optional[callable] = None
|
||||
) -> None:
|
||||
"""Add a tab to the navigation."""
|
||||
self.tabs.append({
|
||||
"label": label,
|
||||
"icon": icon,
|
||||
"content": content
|
||||
})
|
||||
|
||||
def render(self) -> None:
|
||||
"""Render the tabs with consistent styling."""
|
||||
if not self.tabs:
|
||||
return
|
||||
|
||||
# Create tab labels with icons
|
||||
tab_labels = [
|
||||
f"{tab['icon']} {tab['label']}" if tab.get('icon') else tab['label']
|
||||
for tab in self.tabs
|
||||
]
|
||||
|
||||
# Get current tab from session state or use default
|
||||
current_tab = st.session_state.get("current_tab", self.default_tab or self.tabs[0]["label"])
|
||||
|
||||
# Render tabs
|
||||
selected_tab = st.tabs(tab_labels)[tab_labels.index(current_tab)]
|
||||
|
||||
# Update session state
|
||||
st.session_state["current_tab"] = current_tab
|
||||
|
||||
# Render tab content
|
||||
with selected_tab:
|
||||
for tab in self.tabs:
|
||||
if tab["label"] == current_tab and tab.get("content"):
|
||||
tab["content"]()
|
||||
|
||||
class Breadcrumbs:
|
||||
"""Breadcrumb navigation component."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
items: Optional[List[Dict[str, Any]]] = None
|
||||
):
|
||||
self.items = items or []
|
||||
|
||||
def add_item(
|
||||
self,
|
||||
label: str,
|
||||
page: Optional[str] = None,
|
||||
icon: Optional[str] = None
|
||||
) -> None:
|
||||
"""Add a breadcrumb item."""
|
||||
self.items.append({
|
||||
"label": label,
|
||||
"page": page,
|
||||
"icon": icon
|
||||
})
|
||||
|
||||
def render(self) -> None:
|
||||
"""Render the breadcrumbs with consistent styling."""
|
||||
if not self.items:
|
||||
return
|
||||
|
||||
breadcrumb_items = []
|
||||
for i, item in enumerate(self.items):
|
||||
icon_html = f'<span style="font-size: 1.2em; margin-right: {Theme.SPACING["xs"]};">{item["icon"]}</span>' if item.get("icon") else ""
|
||||
link_html = f'<a href="#" onclick="setPage(\'{item["page"]}\')">{item["label"]}</a>' if item.get("page") else f'<span>{item["label"]}</span>'
|
||||
separator = f'<span style="margin: 0 {Theme.SPACING["xs"]};">/</span>' if i < len(self.items) - 1 else ""
|
||||
|
||||
breadcrumb_items.append(f"""
|
||||
<span class="breadcrumb-item">
|
||||
{icon_html}
|
||||
{link_html}
|
||||
</span>
|
||||
{separator}
|
||||
""")
|
||||
|
||||
st.markdown(f"""
|
||||
<div class="breadcrumbs">
|
||||
{''.join(breadcrumb_items)}
|
||||
</div>
|
||||
""", unsafe_allow_html=True)
|
||||
270
lib/ai_writers/twitter_writers/twitter_streamlit_ui/dashboard.py
Normal file
270
lib/ai_writers/twitter_writers/twitter_streamlit_ui/dashboard.py
Normal file
@@ -0,0 +1,270 @@
|
||||
"""
|
||||
Main dashboard for Twitter UI.
|
||||
Combines all UI components into a cohesive interface.
|
||||
"""
|
||||
|
||||
import streamlit as st
|
||||
from typing import Dict, Any, Optional
|
||||
from .components.cards import FeatureCard, TweetCard
|
||||
from .components.forms import TweetForm, SettingsForm
|
||||
from .components.navigation import Sidebar, Header, Tabs, Breadcrumbs
|
||||
from .styles.theme import Theme
|
||||
|
||||
class TwitterDashboard:
|
||||
"""Main dashboard class for Twitter UI."""
|
||||
|
||||
def __init__(self):
|
||||
self.setup_page()
|
||||
self.setup_theme()
|
||||
self.setup_navigation()
|
||||
self.setup_state()
|
||||
|
||||
def setup_page(self) -> None:
|
||||
"""Configure the Streamlit page settings."""
|
||||
st.set_page_config(
|
||||
page_title="Twitter Tools",
|
||||
page_icon="🐦",
|
||||
layout="wide",
|
||||
initial_sidebar_state="expanded"
|
||||
)
|
||||
|
||||
def setup_theme(self) -> None:
|
||||
"""Apply the theme to the dashboard."""
|
||||
Theme().apply()
|
||||
|
||||
def setup_navigation(self) -> None:
|
||||
"""Setup navigation components."""
|
||||
# Sidebar
|
||||
self.sidebar = Sidebar(
|
||||
title="Twitter Tools",
|
||||
logo="assets/logo.png"
|
||||
)
|
||||
|
||||
# Add menu items
|
||||
self.sidebar.add_menu_item("Dashboard", "📊", "dashboard")
|
||||
self.sidebar.add_menu_item("Tweet Generator", "✍️", "tweet_generator")
|
||||
self.sidebar.add_menu_item("Analytics", "📈", "analytics")
|
||||
self.sidebar.add_menu_item("Settings", "⚙️", "settings")
|
||||
|
||||
# Header
|
||||
self.header = Header(
|
||||
title="Twitter Dashboard",
|
||||
subtitle="Create and manage your Twitter content"
|
||||
)
|
||||
|
||||
# Add header actions
|
||||
self.header.add_action(
|
||||
"New Tweet",
|
||||
"✏️",
|
||||
self.create_new_tweet,
|
||||
"Create a new tweet"
|
||||
)
|
||||
self.header.add_action(
|
||||
"Refresh",
|
||||
"🔄",
|
||||
self.refresh_dashboard,
|
||||
"Refresh dashboard data"
|
||||
)
|
||||
|
||||
# Tabs
|
||||
self.tabs = Tabs()
|
||||
|
||||
# Add tabs
|
||||
self.tabs.add_tab("Overview", "📊", self.render_overview)
|
||||
self.tabs.add_tab("Recent Tweets", "🐦", self.render_recent_tweets)
|
||||
self.tabs.add_tab("Analytics", "📈", self.render_analytics)
|
||||
|
||||
# Breadcrumbs
|
||||
self.breadcrumbs = Breadcrumbs()
|
||||
|
||||
def setup_state(self) -> None:
|
||||
"""Initialize session state variables."""
|
||||
if "current_page" not in st.session_state:
|
||||
st.session_state["current_page"] = "dashboard"
|
||||
if "current_tab" not in st.session_state:
|
||||
st.session_state["current_tab"] = "Overview"
|
||||
if "tweets" not in st.session_state:
|
||||
st.session_state["tweets"] = []
|
||||
|
||||
def create_new_tweet(self) -> None:
|
||||
"""Handle new tweet creation."""
|
||||
st.session_state["current_page"] = "tweet_generator"
|
||||
|
||||
def refresh_dashboard(self) -> None:
|
||||
"""Refresh dashboard data."""
|
||||
st.experimental_rerun()
|
||||
|
||||
def render_overview(self) -> None:
|
||||
"""Render the overview tab content."""
|
||||
# Feature cards
|
||||
col1, col2, col3 = st.columns(3)
|
||||
|
||||
with col1:
|
||||
FeatureCard(
|
||||
title="Tweet Generator",
|
||||
description="Create engaging tweets with AI assistance",
|
||||
icon="✍️",
|
||||
features=[
|
||||
{
|
||||
"name": "AI-Powered",
|
||||
"description": "Generate tweets using advanced AI"
|
||||
},
|
||||
{
|
||||
"name": "Customizable",
|
||||
"description": "Adjust tone, length, and style"
|
||||
}
|
||||
],
|
||||
on_click=self.create_new_tweet
|
||||
).render()
|
||||
|
||||
with col2:
|
||||
FeatureCard(
|
||||
title="Analytics",
|
||||
description="Track your tweet performance",
|
||||
icon="📈",
|
||||
features=[
|
||||
{
|
||||
"name": "Engagement",
|
||||
"description": "Monitor likes, retweets, and replies"
|
||||
},
|
||||
{
|
||||
"name": "Growth",
|
||||
"description": "Track follower growth over time"
|
||||
}
|
||||
]
|
||||
).render()
|
||||
|
||||
with col3:
|
||||
FeatureCard(
|
||||
title="Settings",
|
||||
description="Customize your experience",
|
||||
icon="⚙️",
|
||||
features=[
|
||||
{
|
||||
"name": "Preferences",
|
||||
"description": "Set your default options"
|
||||
},
|
||||
{
|
||||
"name": "API",
|
||||
"description": "Configure Twitter API settings"
|
||||
}
|
||||
]
|
||||
).render()
|
||||
|
||||
def render_recent_tweets(self) -> None:
|
||||
"""Render the recent tweets tab content."""
|
||||
# Tweet form
|
||||
tweet_form = TweetForm(
|
||||
on_submit=self.handle_tweet_submit
|
||||
)
|
||||
tweet_form.render()
|
||||
|
||||
# Recent tweets
|
||||
st.markdown("### Recent Tweets")
|
||||
|
||||
for tweet in st.session_state["tweets"]:
|
||||
TweetCard(
|
||||
content=tweet["content"],
|
||||
engagement_score=tweet["engagement_score"],
|
||||
hashtags=tweet["hashtags"],
|
||||
emojis=tweet["emojis"],
|
||||
metrics=tweet["metrics"],
|
||||
on_copy=lambda: self.copy_tweet(tweet),
|
||||
on_save=lambda: self.save_tweet(tweet)
|
||||
).render()
|
||||
|
||||
def render_analytics(self) -> None:
|
||||
"""Render the analytics tab content."""
|
||||
# Analytics content
|
||||
st.markdown("### Tweet Analytics")
|
||||
|
||||
# Placeholder for analytics charts
|
||||
st.info("Analytics features coming soon!")
|
||||
|
||||
def handle_tweet_submit(self) -> None:
|
||||
"""Handle tweet form submission."""
|
||||
# Get form data
|
||||
content = st.session_state["tweet_content"]
|
||||
tone = st.session_state["tone"]
|
||||
length = st.session_state["length"]
|
||||
hashtags = st.session_state["hashtags"]
|
||||
emojis = st.session_state["emojis"]
|
||||
engagement_boost = st.session_state["engagement_boost"]
|
||||
|
||||
# Create tweet object
|
||||
tweet = {
|
||||
"content": content,
|
||||
"tone": tone,
|
||||
"length": length,
|
||||
"hashtags": hashtags,
|
||||
"emojis": emojis,
|
||||
"engagement_score": engagement_boost,
|
||||
"metrics": {
|
||||
"Engagement": engagement_boost,
|
||||
"Reach": engagement_boost * 0.8,
|
||||
"Growth": engagement_boost * 0.6
|
||||
}
|
||||
}
|
||||
|
||||
# Add to tweets list
|
||||
st.session_state["tweets"].append(tweet)
|
||||
|
||||
# Show success message
|
||||
st.success("Tweet created successfully!")
|
||||
|
||||
def copy_tweet(self, tweet: Dict[str, Any]) -> None:
|
||||
"""Copy tweet to clipboard."""
|
||||
st.write("Tweet copied to clipboard!")
|
||||
|
||||
def save_tweet(self, tweet: Dict[str, Any]) -> None:
|
||||
"""Save tweet for later."""
|
||||
st.write("Tweet saved!")
|
||||
|
||||
def render(self) -> None:
|
||||
"""Render the complete dashboard."""
|
||||
# Render navigation
|
||||
self.sidebar.render()
|
||||
self.header.render()
|
||||
self.breadcrumbs.render()
|
||||
|
||||
# Render content based on current page
|
||||
if st.session_state["current_page"] == "dashboard":
|
||||
self.tabs.render()
|
||||
elif st.session_state["current_page"] == "tweet_generator":
|
||||
self.render_recent_tweets()
|
||||
elif st.session_state["current_page"] == "analytics":
|
||||
self.render_analytics()
|
||||
elif st.session_state["current_page"] == "settings":
|
||||
settings_form = SettingsForm(
|
||||
on_submit=self.handle_settings_submit
|
||||
)
|
||||
settings_form.render()
|
||||
|
||||
def handle_settings_submit(self) -> None:
|
||||
"""Handle settings form submission."""
|
||||
# Get form data
|
||||
api_key = st.session_state["api_key"]
|
||||
theme = st.session_state["theme"]
|
||||
notifications = st.session_state["notifications"]
|
||||
auto_save = st.session_state["auto_save"]
|
||||
language = st.session_state["language"]
|
||||
|
||||
# Save settings
|
||||
st.session_state["settings"] = {
|
||||
"api_key": api_key,
|
||||
"theme": theme,
|
||||
"notifications": notifications,
|
||||
"auto_save": auto_save,
|
||||
"language": language
|
||||
}
|
||||
|
||||
# Show success message
|
||||
st.success("Settings saved successfully!")
|
||||
|
||||
def main():
|
||||
"""Main entry point for the dashboard."""
|
||||
dashboard = TwitterDashboard()
|
||||
dashboard.render()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,173 @@
|
||||
"""
|
||||
Theme configuration for Twitter UI components.
|
||||
Provides consistent styling across all Twitter-related features.
|
||||
"""
|
||||
|
||||
import streamlit as st
|
||||
from typing import Dict, Any
|
||||
|
||||
class Theme:
|
||||
"""Theme configuration for Twitter UI components."""
|
||||
|
||||
# Color palette
|
||||
COLORS = {
|
||||
"primary": "#1DA1F2", # Twitter blue
|
||||
"secondary": "#14171A", # Dark blue
|
||||
"background": "#15202B", # Dark background
|
||||
"text": "#FFFFFF", # White text
|
||||
"text_secondary": "#8899A6", # Gray text
|
||||
"success": "#17BF63", # Green
|
||||
"warning": "#FFAD1F", # Yellow
|
||||
"error": "#E0245E", # Red
|
||||
"border": "rgba(255, 255, 255, 0.1)", # Subtle border
|
||||
}
|
||||
|
||||
# Typography
|
||||
TYPOGRAPHY = {
|
||||
"font_family": "'Helvetica Neue', sans-serif",
|
||||
"font_sizes": {
|
||||
"h1": "2.5rem",
|
||||
"h2": "2rem",
|
||||
"h3": "1.5rem",
|
||||
"body": "1rem",
|
||||
"small": "0.875rem",
|
||||
},
|
||||
"font_weights": {
|
||||
"regular": 400,
|
||||
"medium": 500,
|
||||
"bold": 700,
|
||||
},
|
||||
}
|
||||
|
||||
# Spacing
|
||||
SPACING = {
|
||||
"xs": "0.25rem",
|
||||
"sm": "0.5rem",
|
||||
"md": "1rem",
|
||||
"lg": "1.5rem",
|
||||
"xl": "2rem",
|
||||
}
|
||||
|
||||
# Border radius
|
||||
BORDER_RADIUS = {
|
||||
"sm": "4px",
|
||||
"md": "8px",
|
||||
"lg": "12px",
|
||||
"xl": "16px",
|
||||
"full": "9999px",
|
||||
}
|
||||
|
||||
# Shadows
|
||||
SHADOWS = {
|
||||
"sm": "0 1px 2px rgba(0, 0, 0, 0.05)",
|
||||
"md": "0 4px 6px rgba(0, 0, 0, 0.1)",
|
||||
"lg": "0 10px 15px rgba(0, 0, 0, 0.1)",
|
||||
"xl": "0 20px 25px rgba(0, 0, 0, 0.15)",
|
||||
}
|
||||
|
||||
# Transitions
|
||||
TRANSITIONS = {
|
||||
"fast": "0.15s ease",
|
||||
"normal": "0.3s ease",
|
||||
"slow": "0.5s ease",
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def get_css(cls) -> str:
|
||||
"""Get the complete CSS for the theme."""
|
||||
return f"""
|
||||
/* Base styles */
|
||||
.stApp {{
|
||||
background-color: {cls.COLORS['background']};
|
||||
color: {cls.COLORS['text']};
|
||||
font-family: {cls.TYPOGRAPHY['font_family']};
|
||||
}}
|
||||
|
||||
/* Typography */
|
||||
h1, h2, h3, h4, h5, h6 {{
|
||||
color: {cls.COLORS['text']};
|
||||
font-family: {cls.TYPOGRAPHY['font_family']};
|
||||
font-weight: {cls.TYPOGRAPHY['font_weights']['bold']};
|
||||
}}
|
||||
|
||||
/* Buttons */
|
||||
.stButton > button {{
|
||||
background: linear-gradient(45deg, {cls.COLORS['primary']}, #0C85D0);
|
||||
color: {cls.COLORS['text']};
|
||||
border: none;
|
||||
padding: {cls.SPACING['md']} {cls.SPACING['lg']};
|
||||
border-radius: {cls.BORDER_RADIUS['full']};
|
||||
font-weight: {cls.TYPOGRAPHY['font_weights']['medium']};
|
||||
transition: all {cls.TRANSITIONS['normal']};
|
||||
box-shadow: {cls.SHADOWS['md']};
|
||||
}}
|
||||
|
||||
.stButton > button:hover {{
|
||||
transform: translateY(-2px);
|
||||
box-shadow: {cls.SHADOWS['lg']};
|
||||
}}
|
||||
|
||||
/* Cards */
|
||||
.card {{
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border: 1px solid {cls.COLORS['border']};
|
||||
border-radius: {cls.BORDER_RADIUS['lg']};
|
||||
padding: {cls.SPACING['lg']};
|
||||
margin-bottom: {cls.SPACING['md']};
|
||||
backdrop-filter: blur(10px);
|
||||
transition: transform {cls.TRANSITIONS['normal']};
|
||||
}}
|
||||
|
||||
.card:hover {{
|
||||
transform: translateY(-4px);
|
||||
}}
|
||||
|
||||
/* Forms */
|
||||
.stTextInput > div > div > input {{
|
||||
background-color: rgba(255, 255, 255, 0.05);
|
||||
border: 1px solid {cls.COLORS['border']};
|
||||
border-radius: {cls.BORDER_RADIUS['md']};
|
||||
color: {cls.COLORS['text']};
|
||||
padding: {cls.SPACING['md']};
|
||||
}}
|
||||
|
||||
/* Tabs */
|
||||
.stTabs [data-baseweb="tab-list"] {{
|
||||
gap: {cls.SPACING['sm']};
|
||||
background-color: rgba(0, 0, 0, 0.2);
|
||||
padding: {cls.SPACING['md']};
|
||||
border-radius: {cls.BORDER_RADIUS['lg']};
|
||||
}}
|
||||
|
||||
.stTabs [data-baseweb="tab"] {{
|
||||
background-color: transparent;
|
||||
color: {cls.COLORS['text']};
|
||||
border: 1px solid {cls.COLORS['border']};
|
||||
border-radius: {cls.BORDER_RADIUS['md']};
|
||||
padding: {cls.SPACING['sm']} {cls.SPACING['md']};
|
||||
}}
|
||||
|
||||
/* Status badges */
|
||||
.status-badge {{
|
||||
display: inline-block;
|
||||
padding: {cls.SPACING['xs']} {cls.SPACING['md']};
|
||||
border-radius: {cls.BORDER_RADIUS['full']};
|
||||
font-size: {cls.TYPOGRAPHY['font_sizes']['small']};
|
||||
font-weight: {cls.TYPOGRAPHY['font_weights']['medium']};
|
||||
}}
|
||||
|
||||
.status-active {{
|
||||
background: linear-gradient(45deg, {cls.COLORS['success']}, #69F0AE);
|
||||
color: {cls.COLORS['secondary']};
|
||||
}}
|
||||
|
||||
.status-coming-soon {{
|
||||
background: linear-gradient(45deg, {cls.COLORS['warning']}, #FFA000);
|
||||
color: {cls.COLORS['secondary']};
|
||||
}}
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def apply(cls) -> None:
|
||||
"""Apply the theme to the Streamlit app."""
|
||||
st.markdown(f"<style>{cls.get_css()}</style>", unsafe_allow_html=True)
|
||||
@@ -0,0 +1,194 @@
|
||||
"""
|
||||
Utility functions for Twitter UI.
|
||||
Provides helper functions for common operations.
|
||||
"""
|
||||
|
||||
import streamlit as st
|
||||
from typing import Dict, Any, List, Optional
|
||||
import json
|
||||
import os
|
||||
from datetime import datetime
|
||||
|
||||
def save_to_session(key: str, value: Any) -> None:
|
||||
"""Save a value to the session state."""
|
||||
st.session_state[key] = value
|
||||
|
||||
def get_from_session(key: str, default: Any = None) -> Any:
|
||||
"""Get a value from the session state."""
|
||||
return st.session_state.get(key, default)
|
||||
|
||||
def clear_session() -> None:
|
||||
"""Clear all session state variables."""
|
||||
for key in list(st.session_state.keys()):
|
||||
del st.session_state[key]
|
||||
|
||||
def save_to_file(data: Dict[str, Any], filename: str) -> None:
|
||||
"""Save data to a JSON file."""
|
||||
try:
|
||||
with open(filename, 'w') as f:
|
||||
json.dump(data, f, indent=4)
|
||||
except Exception as e:
|
||||
st.error(f"Error saving data: {str(e)}")
|
||||
|
||||
def load_from_file(filename: str) -> Optional[Dict[str, Any]]:
|
||||
"""Load data from a JSON file."""
|
||||
try:
|
||||
if os.path.exists(filename):
|
||||
with open(filename, 'r') as f:
|
||||
return json.load(f)
|
||||
except Exception as e:
|
||||
st.error(f"Error loading data: {str(e)}")
|
||||
return None
|
||||
|
||||
def format_datetime(dt: datetime) -> str:
|
||||
"""Format a datetime object for display."""
|
||||
return dt.strftime("%Y-%m-%d %H:%M:%S")
|
||||
|
||||
def parse_datetime(dt_str: str) -> Optional[datetime]:
|
||||
"""Parse a datetime string."""
|
||||
try:
|
||||
return datetime.strptime(dt_str, "%Y-%m-%d %H:%M:%S")
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
def validate_tweet_content(content: str) -> bool:
|
||||
"""Validate tweet content."""
|
||||
if not content:
|
||||
st.error("Tweet content cannot be empty")
|
||||
return False
|
||||
if len(content) > 280:
|
||||
st.error("Tweet content cannot exceed 280 characters")
|
||||
return False
|
||||
return True
|
||||
|
||||
def validate_hashtags(hashtags: List[str]) -> bool:
|
||||
"""Validate hashtags."""
|
||||
for tag in hashtags:
|
||||
if not tag.startswith('#'):
|
||||
st.error(f"Hashtag {tag} must start with #")
|
||||
return False
|
||||
if len(tag) > 30:
|
||||
st.error(f"Hashtag {tag} cannot exceed 30 characters")
|
||||
return False
|
||||
return True
|
||||
|
||||
def validate_emojis(emojis: List[str]) -> bool:
|
||||
"""Validate emojis."""
|
||||
for emoji in emojis:
|
||||
if len(emoji) != 1:
|
||||
st.error(f"Invalid emoji: {emoji}")
|
||||
return False
|
||||
return True
|
||||
|
||||
def calculate_engagement_score(
|
||||
content: str,
|
||||
hashtags: List[str],
|
||||
emojis: List[str],
|
||||
tone: str
|
||||
) -> float:
|
||||
"""Calculate engagement score for a tweet."""
|
||||
score = 0.0
|
||||
|
||||
# Content length score (optimal length is 100-150 characters)
|
||||
content_length = len(content)
|
||||
if 100 <= content_length <= 150:
|
||||
score += 30
|
||||
elif 50 <= content_length <= 200:
|
||||
score += 20
|
||||
else:
|
||||
score += 10
|
||||
|
||||
# Hashtag score (optimal number is 2-3 hashtags)
|
||||
hashtag_count = len(hashtags)
|
||||
if 2 <= hashtag_count <= 3:
|
||||
score += 20
|
||||
elif 1 <= hashtag_count <= 4:
|
||||
score += 15
|
||||
else:
|
||||
score += 5
|
||||
|
||||
# Emoji score (optimal number is 1-2 emojis)
|
||||
emoji_count = len(emojis)
|
||||
if 1 <= emoji_count <= 2:
|
||||
score += 20
|
||||
elif 0 <= emoji_count <= 3:
|
||||
score += 15
|
||||
else:
|
||||
score += 5
|
||||
|
||||
# Tone score
|
||||
tone_scores = {
|
||||
"professional": 15,
|
||||
"casual": 20,
|
||||
"humorous": 25,
|
||||
"informative": 15,
|
||||
"inspirational": 20
|
||||
}
|
||||
score += tone_scores.get(tone, 10)
|
||||
|
||||
return min(score, 100)
|
||||
|
||||
def generate_tweet_metrics(engagement_score: float) -> Dict[str, float]:
|
||||
"""Generate metrics for a tweet based on engagement score."""
|
||||
return {
|
||||
"Engagement": engagement_score,
|
||||
"Reach": engagement_score * 0.8,
|
||||
"Growth": engagement_score * 0.6
|
||||
}
|
||||
|
||||
def copy_to_clipboard(text: str) -> None:
|
||||
"""Copy text to clipboard."""
|
||||
try:
|
||||
st.write(f'<script>navigator.clipboard.writeText("{text}")</script>', unsafe_allow_html=True)
|
||||
except Exception as e:
|
||||
st.error(f"Error copying to clipboard: {str(e)}")
|
||||
|
||||
def show_success_message(message: str) -> None:
|
||||
"""Show a success message."""
|
||||
st.success(message)
|
||||
|
||||
def show_error_message(message: str) -> None:
|
||||
"""Show an error message."""
|
||||
st.error(message)
|
||||
|
||||
def show_info_message(message: str) -> None:
|
||||
"""Show an info message."""
|
||||
st.info(message)
|
||||
|
||||
def show_warning_message(message: str) -> None:
|
||||
"""Show a warning message."""
|
||||
st.warning(message)
|
||||
|
||||
def create_download_button(
|
||||
data: Dict[str, Any],
|
||||
filename: str,
|
||||
button_text: str = "Download"
|
||||
) -> None:
|
||||
"""Create a download button for data."""
|
||||
try:
|
||||
json_str = json.dumps(data, indent=4)
|
||||
st.download_button(
|
||||
label=button_text,
|
||||
data=json_str,
|
||||
file_name=filename,
|
||||
mime="application/json"
|
||||
)
|
||||
except Exception as e:
|
||||
st.error(f"Error creating download button: {str(e)}")
|
||||
|
||||
def create_upload_button(
|
||||
on_upload: callable,
|
||||
button_text: str = "Upload",
|
||||
file_types: List[str] = ["json"]
|
||||
) -> None:
|
||||
"""Create an upload button for data."""
|
||||
try:
|
||||
uploaded_file = st.file_uploader(
|
||||
button_text,
|
||||
type=file_types
|
||||
)
|
||||
if uploaded_file is not None:
|
||||
data = json.load(uploaded_file)
|
||||
on_upload(data)
|
||||
except Exception as e:
|
||||
st.error(f"Error handling upload: {str(e)}")
|
||||
Reference in New Issue
Block a user