Content Calendar, Content Gap Analysis, and Content Optimization
This commit is contained in:
@@ -194,7 +194,7 @@ def facebook_main_menu():
|
||||
if st.session_state.selected_tool is not None:
|
||||
with tool_container:
|
||||
# Add a back button at the top
|
||||
if st.button("← Back to Dashboard", key="back_to_dashboard"):
|
||||
if st.button("← Back to Dashboard", key="back_to_facebook_dashboard"):
|
||||
st.session_state.selected_tool = None
|
||||
st.rerun()
|
||||
|
||||
|
||||
190
lib/ai_writers/ai_finance_report_generator/README.md
Normal file
190
lib/ai_writers/ai_finance_report_generator/README.md
Normal file
@@ -0,0 +1,190 @@
|
||||
# AI Finance Report Generator
|
||||
|
||||
An advanced AI-powered financial analysis and report generation system that combines data collection, technical analysis, visualization, and automated report generation.
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
ai_finance_report_generator/
|
||||
├── ai_financial_dashboard.py # Main dashboard interface
|
||||
├── utils/ # Utility functions
|
||||
│ ├── __init__.py
|
||||
│ └── storage.py # Data persistence
|
||||
├── reports/ # Report generation modules
|
||||
│ ├── technical_analysis/ # Technical analysis reports
|
||||
│ ├── fundamental_analysis/ # Fundamental analysis reports
|
||||
│ ├── options_analysis/ # Options analysis reports
|
||||
│ ├── portfolio_analysis/ # Portfolio analysis reports
|
||||
│ ├── market_research/ # Market research reports
|
||||
│ └── news_analysis/ # News analysis reports
|
||||
└── README.md # This file
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
### Current Features
|
||||
- Unified dashboard interface for all financial analysis tools
|
||||
- Technical Analysis report generation
|
||||
- Options analysis report generation
|
||||
- User preferences management
|
||||
- Recent reports tracking
|
||||
- Data persistence with JSON storage
|
||||
- Financial data collection from various sources
|
||||
- Integration with LLM for report generation
|
||||
|
||||
### Planned Features
|
||||
|
||||
#### 1. Data Collection Module
|
||||
- Web scraping for financial news and data
|
||||
- API integrations (Yahoo Finance, Alpha Vantage, Financial Modeling Prep)
|
||||
- Real-time market data collection
|
||||
- Historical data retrieval
|
||||
- Company financial statements
|
||||
- Market sentiment data
|
||||
- Economic indicators
|
||||
- Sector analysis data
|
||||
|
||||
#### 2. Technical Analysis Module
|
||||
- Moving averages (SMA, EMA, WMA)
|
||||
- RSI, MACD, Bollinger Bands
|
||||
- Volume analysis
|
||||
- Support/Resistance levels
|
||||
- Trend analysis
|
||||
- Pattern recognition
|
||||
- Fibonacci retracements
|
||||
- Momentum indicators
|
||||
|
||||
#### 3. Fundamental Analysis Module
|
||||
- Financial ratios calculation
|
||||
- Company valuation metrics
|
||||
- Growth analysis
|
||||
- Profitability analysis
|
||||
- Debt analysis
|
||||
- Cash flow analysis
|
||||
- Industry comparison
|
||||
- Peer analysis
|
||||
|
||||
#### 4. Data Visualization Module
|
||||
- Candlestick charts
|
||||
- Technical indicator overlays
|
||||
- Volume charts
|
||||
- Price action patterns
|
||||
- Correlation matrices
|
||||
- Heat maps
|
||||
- Interactive charts
|
||||
- Custom chart templates
|
||||
|
||||
#### 5. Report Generation Module
|
||||
- Technical analysis reports
|
||||
- Fundamental analysis reports
|
||||
- Market research reports
|
||||
- Investment recommendations
|
||||
- Risk assessment reports
|
||||
- Sector analysis reports
|
||||
- News impact analysis
|
||||
- Custom report templates
|
||||
|
||||
#### 6. News and Sentiment Analysis Module
|
||||
- News aggregation
|
||||
- Sentiment scoring
|
||||
- Social media analysis
|
||||
- Market sentiment indicators
|
||||
- News impact analysis
|
||||
- Event correlation
|
||||
- Trend detection
|
||||
- Sentiment visualization
|
||||
|
||||
#### 7. Portfolio Analysis Module
|
||||
- Portfolio performance analysis
|
||||
- Risk assessment
|
||||
- Asset allocation
|
||||
- Correlation analysis
|
||||
- Diversification metrics
|
||||
- Performance attribution
|
||||
- Portfolio optimization
|
||||
- Rebalancing suggestions
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```python
|
||||
from lib.ai_writers.ai_finance_report_generator.ai_financial_dashboard import get_dashboard
|
||||
|
||||
# Get dashboard instance
|
||||
dashboard = get_dashboard()
|
||||
|
||||
# Generate technical analysis report
|
||||
ta_report = dashboard.generate_technical_analysis("AAPL")
|
||||
|
||||
# Generate options analysis report
|
||||
options_report = dashboard.generate_options_analysis("AAPL")
|
||||
|
||||
# Get recent reports
|
||||
recent_reports = dashboard.get_recent_reports()
|
||||
```
|
||||
|
||||
### User Preferences
|
||||
|
||||
```python
|
||||
# Update user preferences
|
||||
dashboard.update_preferences({
|
||||
"report_format": "markdown",
|
||||
"include_charts": True,
|
||||
"chart_style": "dark",
|
||||
"language": "en"
|
||||
})
|
||||
|
||||
# Get current preferences
|
||||
preferences = dashboard.get_preferences()
|
||||
```
|
||||
|
||||
### Portfolio Analysis
|
||||
|
||||
```python
|
||||
# Create portfolio
|
||||
portfolio = [
|
||||
{"symbol": "AAPL", "shares": 100},
|
||||
{"symbol": "GOOGL", "shares": 50}
|
||||
]
|
||||
|
||||
# Generate portfolio report
|
||||
portfolio_report = dashboard.generate_portfolio_analysis(portfolio)
|
||||
```
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
## Dependencies
|
||||
|
||||
1. **Data Collection**
|
||||
- `finance_data_researcher`
|
||||
- `web_scraping_tools`
|
||||
|
||||
2. **Analysis Tools**
|
||||
- `pandas_ta`
|
||||
- `numpy`
|
||||
- `scipy`
|
||||
|
||||
3. **Visualization**
|
||||
- `matplotlib`
|
||||
- `plotly`
|
||||
|
||||
4. **Text Generation**
|
||||
- `llm_text_gen`
|
||||
- `gpt_providers`
|
||||
|
||||
## Contributing
|
||||
|
||||
1. Fork the repository
|
||||
2. Create your feature branch (`git checkout -b feature/AmazingFeature`)
|
||||
3. Commit your changes (`git commit -m 'Add some AmazingFeature'`)
|
||||
4. Push to the branch (`git push origin feature/AmazingFeature`)
|
||||
5. Open a Pull Request
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the MIT License - see the LICENSE file for details.
|
||||
@@ -0,0 +1,358 @@
|
||||
"""
|
||||
AI Financial Dashboard Module
|
||||
|
||||
This module combines the financial dashboard interface with financial report generation capabilities.
|
||||
It provides a unified interface for managing financial analysis tools and generating reports.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
from textwrap import dedent
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
from typing import Dict, List, Any, Optional, Union
|
||||
|
||||
from loguru import logger
|
||||
logger.remove()
|
||||
logger.add(sys.stdout,
|
||||
colorize=True,
|
||||
format="<level>{level}</level>|<green>{file}:{line}:{function}</green>| {message}"
|
||||
)
|
||||
|
||||
from ...ai_web_researcher.finance_data_researcher import get_finance_data, get_fin_options_data
|
||||
from ...gpt_providers.text_generation.main_text_generation import llm_text_gen
|
||||
from .utils import get_feature_status
|
||||
from .utils.storage import get_storage_manager
|
||||
|
||||
class UserPreferences:
|
||||
"""Class to manage user preferences and settings."""
|
||||
|
||||
def __init__(self):
|
||||
self.default_settings = {
|
||||
"theme": "light",
|
||||
"currency": "USD",
|
||||
"timezone": "UTC",
|
||||
"date_format": "%Y-%m-%d",
|
||||
"default_symbols": [],
|
||||
"notifications": True,
|
||||
"auto_refresh": False,
|
||||
"refresh_interval": 300, # 5 minutes
|
||||
"report_format": "markdown",
|
||||
"include_charts": True,
|
||||
"chart_style": "default",
|
||||
"language": "en"
|
||||
}
|
||||
self.settings = self.default_settings.copy()
|
||||
self.storage = get_storage_manager()
|
||||
self.load_settings()
|
||||
|
||||
def update_setting(self, key: str, value: Any) -> None:
|
||||
"""Update a specific setting."""
|
||||
if key in self.default_settings:
|
||||
self.settings[key] = value
|
||||
self.save_settings()
|
||||
|
||||
def get_setting(self, key: str) -> Any:
|
||||
"""Get a specific setting value."""
|
||||
return self.settings.get(key, self.default_settings.get(key))
|
||||
|
||||
def reset_settings(self) -> None:
|
||||
"""Reset all settings to default values."""
|
||||
self.settings = self.default_settings.copy()
|
||||
self.save_settings()
|
||||
|
||||
def save_settings(self) -> None:
|
||||
"""Save current settings to storage."""
|
||||
self.storage.save_user_preferences(self.settings)
|
||||
|
||||
def load_settings(self) -> None:
|
||||
"""Load settings from storage."""
|
||||
stored_settings = self.storage.load_user_preferences()
|
||||
if stored_settings:
|
||||
self.settings.update(stored_settings)
|
||||
|
||||
class RecentReport:
|
||||
"""Class to represent a recently generated report."""
|
||||
|
||||
def __init__(self, report_type: str, symbol: Optional[str], timestamp: datetime, content: Optional[str] = None):
|
||||
self.report_type = report_type
|
||||
self.symbol = symbol
|
||||
self.timestamp = timestamp
|
||||
self.content = content
|
||||
self.id = f"{report_type}_{symbol}_{timestamp.strftime('%Y%m%d%H%M%S')}"
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
"""Convert report to dictionary format."""
|
||||
return {
|
||||
"id": self.id,
|
||||
"type": self.report_type,
|
||||
"symbol": self.symbol,
|
||||
"timestamp": self.timestamp.isoformat(),
|
||||
"content": self.content
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: Dict[str, Any]) -> 'RecentReport':
|
||||
"""Create report from dictionary format."""
|
||||
return cls(
|
||||
report_type=data["type"],
|
||||
symbol=data["symbol"],
|
||||
timestamp=datetime.fromisoformat(data["timestamp"]),
|
||||
content=data.get("content")
|
||||
)
|
||||
|
||||
class FinancialDashboard:
|
||||
"""Main dashboard class for managing financial analysis tools and generating reports."""
|
||||
|
||||
def __init__(self):
|
||||
self.features = {
|
||||
"technical_analysis": {
|
||||
"name": "Technical Analysis",
|
||||
"description": "Generate technical analysis reports with indicators and patterns",
|
||||
"icon": "📊",
|
||||
"route": "/technical-analysis",
|
||||
"category": "analysis",
|
||||
"dependencies": ["data_collection"],
|
||||
"version": "1.0.0"
|
||||
},
|
||||
"fundamental_analysis": {
|
||||
"name": "Fundamental Analysis",
|
||||
"description": "Analyze company financials and valuation metrics",
|
||||
"icon": "📈",
|
||||
"route": "/fundamental-analysis",
|
||||
"category": "analysis",
|
||||
"dependencies": ["data_collection"],
|
||||
"version": "0.1.0"
|
||||
},
|
||||
"options_analysis": {
|
||||
"name": "Options Analysis",
|
||||
"description": "Analyze options chains and generate trading strategies",
|
||||
"icon": "⚡",
|
||||
"route": "/options-analysis",
|
||||
"category": "analysis",
|
||||
"dependencies": ["data_collection", "options_data"],
|
||||
"version": "1.0.0"
|
||||
},
|
||||
"portfolio_analysis": {
|
||||
"name": "Portfolio Analysis",
|
||||
"description": "Analyze portfolio performance and risk metrics",
|
||||
"icon": "📑",
|
||||
"route": "/portfolio-analysis",
|
||||
"category": "portfolio",
|
||||
"dependencies": ["data_collection", "portfolio_data"],
|
||||
"version": "0.1.0"
|
||||
},
|
||||
"market_research": {
|
||||
"name": "Market Research",
|
||||
"description": "Generate market research reports and sector analysis",
|
||||
"icon": "🔍",
|
||||
"route": "/market-research",
|
||||
"category": "research",
|
||||
"dependencies": ["data_collection", "news_data"],
|
||||
"version": "0.1.0"
|
||||
},
|
||||
"news_analysis": {
|
||||
"name": "News Analysis",
|
||||
"description": "Analyze news impact and market sentiment",
|
||||
"icon": "📰",
|
||||
"route": "/news-analysis",
|
||||
"category": "research",
|
||||
"dependencies": ["data_collection", "news_data"],
|
||||
"version": "0.1.0"
|
||||
}
|
||||
}
|
||||
|
||||
self.user_preferences = UserPreferences()
|
||||
self.storage = get_storage_manager()
|
||||
self.recent_reports: List[RecentReport] = []
|
||||
self.max_recent_reports = 10
|
||||
self.load_recent_reports()
|
||||
|
||||
def get_all_features(self) -> List[Dict[str, Any]]:
|
||||
"""Get all available features with their status."""
|
||||
features_list = []
|
||||
for feature_id, feature_info in self.features.items():
|
||||
status = get_feature_status(feature_id)
|
||||
feature_info.update(status)
|
||||
features_list.append(feature_info)
|
||||
return features_list
|
||||
|
||||
def get_feature(self, feature_id: str) -> Dict[str, Any]:
|
||||
"""Get information about a specific feature."""
|
||||
if feature_id not in self.features:
|
||||
raise ValueError(f"Feature {feature_id} not found")
|
||||
|
||||
feature_info = self.features[feature_id].copy()
|
||||
status = get_feature_status(feature_id)
|
||||
feature_info.update(status)
|
||||
return feature_info
|
||||
|
||||
def get_implemented_features(self) -> List[Dict[str, Any]]:
|
||||
"""Get only the implemented features."""
|
||||
return [f for f in self.get_all_features() if f["implemented"]]
|
||||
|
||||
def get_coming_soon_features(self) -> List[Dict[str, Any]]:
|
||||
"""Get features that are coming soon."""
|
||||
return [f for f in self.get_all_features() if f["coming_soon"]]
|
||||
|
||||
def get_features_by_category(self, category: str) -> List[Dict[str, Any]]:
|
||||
"""Get features filtered by category."""
|
||||
return [f for f in self.get_all_features() if f["category"] == category]
|
||||
|
||||
def add_recent_report(self, report_type: str, symbol: Optional[str] = None, content: Optional[str] = None) -> None:
|
||||
"""Add a report to the recent reports list."""
|
||||
report = RecentReport(report_type, symbol, datetime.now(), content)
|
||||
self.recent_reports.insert(0, report)
|
||||
if len(self.recent_reports) > self.max_recent_reports:
|
||||
self.recent_reports.pop()
|
||||
self.save_recent_reports()
|
||||
|
||||
def get_recent_reports(self, limit: Optional[int] = None) -> List[Dict[str, Any]]:
|
||||
"""Get recent reports."""
|
||||
reports = self.recent_reports[:limit] if limit else self.recent_reports
|
||||
return [{
|
||||
**r.to_dict(),
|
||||
"feature_info": self.get_feature(r.report_type)
|
||||
} for r in reports]
|
||||
|
||||
def save_recent_reports(self) -> None:
|
||||
"""Save recent reports to storage."""
|
||||
reports_data = [r.to_dict() for r in self.recent_reports]
|
||||
self.storage.save_recent_reports(reports_data)
|
||||
|
||||
def load_recent_reports(self) -> None:
|
||||
"""Load recent reports from storage."""
|
||||
reports_data = self.storage.load_recent_reports()
|
||||
self.recent_reports = [RecentReport.from_dict(r) for r in reports_data]
|
||||
|
||||
def get_dashboard_summary(self) -> Dict[str, Any]:
|
||||
"""Get a summary of the dashboard state."""
|
||||
return {
|
||||
"total_features": len(self.features),
|
||||
"implemented_features": len(self.get_implemented_features()),
|
||||
"coming_soon_features": len(self.get_coming_soon_features()),
|
||||
"recent_reports": len(self.recent_reports),
|
||||
"categories": list(set(f["category"] for f in self.features.values())),
|
||||
"user_preferences": self.user_preferences.settings
|
||||
}
|
||||
|
||||
def check_feature_dependencies(self, feature_id: str) -> Dict[str, bool]:
|
||||
"""Check if all dependencies for a feature are met."""
|
||||
if feature_id not in self.features:
|
||||
raise ValueError(f"Feature {feature_id} not found")
|
||||
|
||||
feature = self.features[feature_id]
|
||||
dependencies = feature.get("dependencies", [])
|
||||
|
||||
return {
|
||||
dep: get_feature_status(dep)["implemented"]
|
||||
for dep in dependencies
|
||||
}
|
||||
|
||||
def backup_data(self, backup_dir: Optional[str] = None) -> None:
|
||||
"""Create a backup of all dashboard data."""
|
||||
self.storage.backup_storage(backup_dir)
|
||||
|
||||
def restore_from_backup(self, backup_file: str) -> None:
|
||||
"""Restore dashboard data from a backup file."""
|
||||
self.storage.restore_from_backup(backup_file)
|
||||
self.user_preferences.load_settings()
|
||||
self.load_recent_reports()
|
||||
|
||||
def generate_technical_analysis(self, symbol: str) -> str:
|
||||
"""Generate a technical analysis report for the given symbol."""
|
||||
try:
|
||||
# Get financial data
|
||||
symbol_fin_data = get_finance_data(symbol)
|
||||
|
||||
# Generate report
|
||||
report_content = self._generate_ta_report(symbol_fin_data, symbol)
|
||||
|
||||
# Add to recent reports
|
||||
self.add_recent_report("technical_analysis", symbol, report_content)
|
||||
|
||||
logger.info(f"Done: Final Technical Analysis for {symbol}")
|
||||
return report_content
|
||||
|
||||
except Exception as err:
|
||||
logger.error(f"Error: Failed to generate Technical Analysis report: {err}")
|
||||
raise
|
||||
|
||||
def generate_options_analysis(self, symbol: str) -> str:
|
||||
"""Generate an options analysis report for the given symbol."""
|
||||
try:
|
||||
# Get options data
|
||||
options_data = get_fin_options_data(symbol)
|
||||
|
||||
# Generate report
|
||||
report_content = self._generate_options_report(options_data, symbol)
|
||||
|
||||
# Add to recent reports
|
||||
self.add_recent_report("options_analysis", symbol, report_content)
|
||||
|
||||
logger.info(f"Done: Options Analysis for {symbol}")
|
||||
return report_content
|
||||
|
||||
except Exception as err:
|
||||
logger.error(f"Error: Failed to generate Options Analysis report: {err}")
|
||||
raise
|
||||
|
||||
def _generate_ta_report(self, last_day_summary: str, symbol: str) -> str:
|
||||
"""Generate technical analysis report using LLM."""
|
||||
prompt = f"""
|
||||
You are a seasoned Technical Analysis (TA) expert, rivaling legends like Charles Dow, John Bollinger, and Alan Andrews.
|
||||
Your deep understanding of market dynamics, coupled with mastery of technical indicators,
|
||||
allows you to decipher complex patterns and offer precise predictions.
|
||||
|
||||
Your expertise extends to practical tools like the pandas_ta module, enabling you to extract valuable insights from raw data.
|
||||
|
||||
**Objective:**
|
||||
Analyze the provided technical indicators for {symbol} on its last trading day and predict its price movement over the next few trading sessions.
|
||||
|
||||
**Instructions:**
|
||||
1. **Identify Potential Trading Signals:** Highlight specific indicators suggesting bullish, bearish, or neutral signals. Explain the rationale behind each signal, referencing historical patterns or comparable market scenarios.
|
||||
2. **Detect Patterns and Divergences:** Analyze the interplay between different indicators. Detect patterns like moving average crossovers, candlestick formations, or divergences between price action and indicators. Explain the significance of each pattern.
|
||||
3. **Price Movement Prediction:** Based on your analysis, provide a clear prediction for {symbol}'s price movement in the next few days. State the expected direction (up, down, sideways) and potential price targets if identifiable.
|
||||
4. **Risk Assessment:** Briefly discuss any potential risks or factors that could invalidate your predictions, promoting a balanced and informed perspective.
|
||||
|
||||
**Technical Indicators for {symbol} on the Last Trading Day:**
|
||||
{last_day_summary}
|
||||
|
||||
Remember, your analysis should be detailed, insightful, and actionable for traders seeking to capitalize on market movements.
|
||||
"""
|
||||
|
||||
try:
|
||||
return llm_text_gen(prompt)
|
||||
except Exception as err:
|
||||
logger.error(f"Failed to generate TA report: {err}")
|
||||
raise
|
||||
|
||||
def _generate_options_report(self, results_sentences: List[str], ticker: str) -> str:
|
||||
"""Generate options analysis report using LLM."""
|
||||
prompt = f"""
|
||||
You are a financial expert specializing in options trading and market sentiment analysis.
|
||||
You have been provided with the following technical analysis of options data for the ticker symbol {ticker} with the nearest expiry date:
|
||||
|
||||
{chr(10).join(results_sentences)}
|
||||
|
||||
Based on this data, provide a comprehensive analysis of the options market for {ticker}.
|
||||
|
||||
Your analysis should include:
|
||||
|
||||
1. **Implied Volatility Interpretation:** Discuss the significance of the average implied volatility for both call and put options. What does it suggest about market expectations of future price movements?
|
||||
2. **Volume and Open Interest Insights:** Analyze the volume and open interest for call and put options. What does this data reveal about current market positioning and potential future trading activity?
|
||||
3. **Sentiment Analysis:** Evaluate the put-call ratio, implied volatility skew, and overall market sentiment. What do these indicators suggest about trader sentiment and potential future price direction?
|
||||
4. **Potential Trading Strategies:** Based on your analysis, suggest potential options trading strategies that could be employed for {ticker}, considering the current market conditions and sentiment.
|
||||
|
||||
Please provide your analysis in a clear and concise manner, suitable for someone with a good understanding of options trading.
|
||||
"""
|
||||
|
||||
try:
|
||||
return llm_text_gen(prompt)
|
||||
except Exception as err:
|
||||
logger.error(f"Failed to generate options report: {err}")
|
||||
raise
|
||||
|
||||
def get_dashboard() -> FinancialDashboard:
|
||||
"""Get the financial dashboard instance."""
|
||||
return FinancialDashboard()
|
||||
265
lib/ai_writers/ai_finance_report_generator/reports/README.md
Normal file
265
lib/ai_writers/ai_finance_report_generator/reports/README.md
Normal file
@@ -0,0 +1,265 @@
|
||||
# Financial Reports Module
|
||||
|
||||
This directory contains the core report generation modules for different types of financial analysis. Each module is designed to handle a specific type of financial report and can be accessed through the main dashboard interface.
|
||||
|
||||
## Directory Structure
|
||||
|
||||
```
|
||||
reports/
|
||||
├── technical_analysis/ # Technical analysis reports
|
||||
├── fundamental_analysis/ # Fundamental analysis reports
|
||||
├── options_analysis/ # Options analysis reports
|
||||
├── portfolio_analysis/ # Portfolio analysis reports
|
||||
├── market_research/ # Market research reports
|
||||
└── news_analysis/ # News analysis reports
|
||||
```
|
||||
|
||||
## Report Types
|
||||
|
||||
### 1. Technical Analysis Reports
|
||||
Location: `technical_analysis/`
|
||||
|
||||
Generates technical analysis reports including:
|
||||
- Moving averages (SMA, EMA, WMA)
|
||||
- RSI, MACD, Bollinger Bands
|
||||
- Volume analysis
|
||||
- Support/Resistance levels
|
||||
- Trend analysis
|
||||
- Pattern recognition
|
||||
|
||||
Usage:
|
||||
```python
|
||||
from lib.ai_writers.ai_finance_report_generator.reports.technical_analysis import generate_ta_report
|
||||
|
||||
report = generate_ta_report("AAPL")
|
||||
```
|
||||
|
||||
### 2. Fundamental Analysis Reports
|
||||
Location: `fundamental_analysis/`
|
||||
|
||||
Generates fundamental analysis reports including:
|
||||
- Financial ratios
|
||||
- Company valuation metrics
|
||||
- Growth analysis
|
||||
- Profitability analysis
|
||||
- Debt analysis
|
||||
- Cash flow analysis
|
||||
|
||||
Usage:
|
||||
```python
|
||||
from lib.ai_writers.ai_finance_report_generator.reports.fundamental_analysis import generate_fa_report
|
||||
|
||||
report = generate_fa_report("AAPL")
|
||||
```
|
||||
|
||||
### 3. Options Analysis Reports
|
||||
Location: `options_analysis/`
|
||||
|
||||
Generates options analysis reports including:
|
||||
- Options chain analysis
|
||||
- Implied volatility analysis
|
||||
- Options strategies
|
||||
- Risk metrics
|
||||
- Greeks analysis
|
||||
|
||||
Usage:
|
||||
```python
|
||||
from lib.ai_writers.ai_finance_report_generator.reports.options_analysis import generate_options_report
|
||||
|
||||
report = generate_options_report("AAPL")
|
||||
```
|
||||
|
||||
### 4. Portfolio Analysis Reports
|
||||
Location: `portfolio_analysis/`
|
||||
|
||||
Generates portfolio analysis reports including:
|
||||
- Portfolio performance analysis
|
||||
- Risk assessment
|
||||
- Asset allocation
|
||||
- Correlation analysis
|
||||
- Diversification metrics
|
||||
- Performance attribution
|
||||
|
||||
Usage:
|
||||
```python
|
||||
from lib.ai_writers.ai_finance_report_generator.reports.portfolio_analysis import generate_portfolio_report
|
||||
|
||||
portfolio = [
|
||||
{"symbol": "AAPL", "shares": 100},
|
||||
{"symbol": "GOOGL", "shares": 50}
|
||||
]
|
||||
report = generate_portfolio_report(portfolio)
|
||||
```
|
||||
|
||||
### 5. Market Research Reports
|
||||
Location: `market_research/`
|
||||
|
||||
Generates market research reports including:
|
||||
- Sector analysis
|
||||
- Industry trends
|
||||
- Market overview
|
||||
- Competitive analysis
|
||||
- Market opportunities
|
||||
- Risk factors
|
||||
|
||||
Usage:
|
||||
```python
|
||||
from lib.ai_writers.ai_finance_report_generator.reports.market_research import generate_market_research_report
|
||||
|
||||
report = generate_market_research_report(sectors=["Technology", "Healthcare"])
|
||||
```
|
||||
|
||||
### 6. News Analysis Reports
|
||||
Location: `news_analysis/`
|
||||
|
||||
Generates news analysis reports including:
|
||||
- News sentiment analysis
|
||||
- Market impact analysis
|
||||
- Event correlation
|
||||
- Trend detection
|
||||
- Social media analysis
|
||||
- News aggregation
|
||||
|
||||
Usage:
|
||||
```python
|
||||
from lib.ai_writers.ai_finance_report_generator.reports.news_analysis import generate_news_analysis_report
|
||||
|
||||
report = generate_news_analysis_report("AAPL")
|
||||
```
|
||||
|
||||
## Common Features
|
||||
|
||||
All report modules share the following features:
|
||||
|
||||
1. **Data Validation**
|
||||
- Input validation for symbols and parameters
|
||||
- Error handling for invalid inputs
|
||||
- Data type checking
|
||||
|
||||
2. **Report Formatting**
|
||||
- Markdown formatting
|
||||
- Chart generation (when applicable)
|
||||
- Customizable templates
|
||||
|
||||
3. **Storage Integration**
|
||||
- Automatic report storage
|
||||
- Recent reports tracking
|
||||
- Report versioning
|
||||
|
||||
4. **User Preferences**
|
||||
- Customizable report formats
|
||||
- Language selection
|
||||
- Chart style preferences
|
||||
|
||||
## Integration with Dashboard
|
||||
|
||||
All report modules are integrated with the main dashboard and can be accessed through the `FinancialDashboard` class:
|
||||
|
||||
```python
|
||||
from lib.ai_writers.ai_finance_report_generator.ai_financial_dashboard import get_dashboard
|
||||
|
||||
dashboard = get_dashboard()
|
||||
|
||||
# Generate reports through dashboard
|
||||
ta_report = dashboard.generate_technical_analysis("AAPL")
|
||||
options_report = dashboard.generate_options_analysis("AAPL")
|
||||
|
||||
# Get recent reports
|
||||
recent_reports = dashboard.get_recent_reports()
|
||||
```
|
||||
|
||||
## Adding New Report Types
|
||||
|
||||
To add a new report type:
|
||||
|
||||
1. Create a new directory in the `reports/` folder
|
||||
2. Create an `__init__.py` file with the report generation function
|
||||
3. Add the report type to the dashboard features
|
||||
4. Implement the report generation logic
|
||||
5. Add appropriate error handling and validation
|
||||
|
||||
Example:
|
||||
```python
|
||||
# reports/new_analysis/__init__.py
|
||||
from typing import Dict, Any
|
||||
from ...utils import validate_symbol
|
||||
|
||||
def generate_new_analysis_report(symbol: str) -> Dict[str, Any]:
|
||||
"""
|
||||
Generate a new type of analysis report.
|
||||
|
||||
Args:
|
||||
symbol (str): Stock symbol to analyze
|
||||
|
||||
Returns:
|
||||
Dict[str, Any]: Analysis report
|
||||
"""
|
||||
if not validate_symbol(symbol):
|
||||
raise ValueError("Invalid symbol provided")
|
||||
|
||||
# Implement report generation logic
|
||||
return {
|
||||
"symbol": symbol,
|
||||
"analysis": "Report content"
|
||||
}
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
All report modules implement consistent error handling:
|
||||
|
||||
1. **Input Validation**
|
||||
- Symbol validation
|
||||
- Parameter validation
|
||||
- Data type checking
|
||||
|
||||
2. **Data Collection Errors**
|
||||
- API errors
|
||||
- Network errors
|
||||
- Data format errors
|
||||
|
||||
3. **Report Generation Errors**
|
||||
- LLM errors
|
||||
- Template errors
|
||||
- Formatting errors
|
||||
|
||||
4. **Storage Errors**
|
||||
- File system errors
|
||||
- Database errors
|
||||
- Backup errors
|
||||
|
||||
## Contributing
|
||||
|
||||
When contributing to the reports module:
|
||||
|
||||
1. Follow the existing code structure
|
||||
2. Add appropriate type hints
|
||||
3. Include comprehensive docstrings
|
||||
4. Add error handling
|
||||
5. Update the dashboard integration
|
||||
6. Add tests for new functionality
|
||||
|
||||
## Dependencies
|
||||
|
||||
The reports module depends on:
|
||||
|
||||
1. **Data Collection**
|
||||
- `finance_data_researcher`
|
||||
- `web_scraping_tools`
|
||||
|
||||
2. **Analysis Tools**
|
||||
- `pandas_ta`
|
||||
- `numpy`
|
||||
- `scipy`
|
||||
|
||||
3. **Visualization**
|
||||
- `matplotlib`
|
||||
- `plotly`
|
||||
|
||||
4. **Text Generation**
|
||||
- `llm_text_gen`
|
||||
- `gpt_providers`
|
||||
|
||||
## License
|
||||
|
||||
This module is part of the AI Finance Report Generator project and is licensed under the MIT License.
|
||||
@@ -0,0 +1,34 @@
|
||||
"""
|
||||
Fundamental Analysis Reports Module
|
||||
|
||||
This module handles the generation of fundamental analysis reports including:
|
||||
- Financial ratios
|
||||
- Company valuation metrics
|
||||
- Growth analysis
|
||||
- Profitability analysis
|
||||
- Debt analysis
|
||||
- Cash flow analysis
|
||||
"""
|
||||
|
||||
from typing import Dict, Any
|
||||
from ...utils import validate_symbol
|
||||
|
||||
def generate_fa_report(symbol: str) -> Dict[str, Any]:
|
||||
"""
|
||||
Generate a fundamental analysis report for the given symbol.
|
||||
|
||||
Args:
|
||||
symbol (str): Stock symbol to analyze
|
||||
|
||||
Returns:
|
||||
Dict[str, Any]: Fundamental analysis report
|
||||
"""
|
||||
if not validate_symbol(symbol):
|
||||
raise ValueError("Invalid symbol provided")
|
||||
|
||||
# TODO: Implement fundamental analysis report generation
|
||||
return {
|
||||
"symbol": symbol,
|
||||
"status": "coming_soon",
|
||||
"message": "Fundamental analysis report generation is coming soon"
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
"""
|
||||
Market Research Reports Module
|
||||
|
||||
This module handles the generation of market research reports including:
|
||||
- Sector analysis
|
||||
- Industry trends
|
||||
- Market overview
|
||||
- Competitive analysis
|
||||
- Market opportunities
|
||||
- Risk factors
|
||||
"""
|
||||
|
||||
from typing import Dict, Any, List
|
||||
|
||||
def generate_market_research_report(sectors: List[str] = None) -> Dict[str, Any]:
|
||||
"""
|
||||
Generate a market research report.
|
||||
|
||||
Args:
|
||||
sectors (List[str], optional): List of sectors to analyze
|
||||
|
||||
Returns:
|
||||
Dict[str, Any]: Market research report
|
||||
"""
|
||||
# TODO: Implement market research report generation
|
||||
return {
|
||||
"status": "coming_soon",
|
||||
"message": "Market research report generation is coming soon"
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
"""
|
||||
News Analysis Reports Module
|
||||
|
||||
This module handles the generation of news analysis reports including:
|
||||
- News sentiment analysis
|
||||
- Market impact analysis
|
||||
- Event correlation
|
||||
- Trend detection
|
||||
- Social media analysis
|
||||
- News aggregation
|
||||
"""
|
||||
|
||||
from typing import Dict, Any, List
|
||||
from ...utils import validate_symbol
|
||||
|
||||
def generate_news_analysis_report(symbol: str = None) -> Dict[str, Any]:
|
||||
"""
|
||||
Generate a news analysis report.
|
||||
|
||||
Args:
|
||||
symbol (str, optional): Stock symbol to analyze news for
|
||||
|
||||
Returns:
|
||||
Dict[str, Any]: News analysis report
|
||||
"""
|
||||
if symbol and not validate_symbol(symbol):
|
||||
raise ValueError("Invalid symbol provided")
|
||||
|
||||
# TODO: Implement news analysis report generation
|
||||
return {
|
||||
"status": "coming_soon",
|
||||
"message": "News analysis report generation is coming soon"
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
"""
|
||||
Options Analysis Reports Module
|
||||
|
||||
This module handles the generation of options analysis reports including:
|
||||
- Options chain analysis
|
||||
- Implied volatility analysis
|
||||
- Options strategies
|
||||
- Risk metrics
|
||||
- Greeks analysis
|
||||
"""
|
||||
|
||||
from typing import Dict, Any
|
||||
from ...utils import validate_symbol
|
||||
|
||||
def generate_options_report(symbol: str) -> Dict[str, Any]:
|
||||
"""
|
||||
Generate an options analysis report for the given symbol.
|
||||
|
||||
Args:
|
||||
symbol (str): Stock symbol to analyze
|
||||
|
||||
Returns:
|
||||
Dict[str, Any]: Options analysis report
|
||||
"""
|
||||
if not validate_symbol(symbol):
|
||||
raise ValueError("Invalid symbol provided")
|
||||
|
||||
# TODO: Implement options analysis report generation
|
||||
return {
|
||||
"symbol": symbol,
|
||||
"status": "coming_soon",
|
||||
"message": "Options analysis report generation is coming soon"
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
"""
|
||||
Portfolio Analysis Reports Module
|
||||
|
||||
This module handles the generation of portfolio analysis reports including:
|
||||
- Portfolio performance analysis
|
||||
- Risk assessment
|
||||
- Asset allocation
|
||||
- Correlation analysis
|
||||
- Diversification metrics
|
||||
- Performance attribution
|
||||
"""
|
||||
|
||||
from typing import Dict, Any, List
|
||||
|
||||
def generate_portfolio_report(portfolio: List[Dict[str, Any]]) -> Dict[str, Any]:
|
||||
"""
|
||||
Generate a portfolio analysis report.
|
||||
|
||||
Args:
|
||||
portfolio (List[Dict[str, Any]]): List of portfolio positions
|
||||
|
||||
Returns:
|
||||
Dict[str, Any]: Portfolio analysis report
|
||||
"""
|
||||
if not portfolio:
|
||||
raise ValueError("Portfolio cannot be empty")
|
||||
|
||||
# TODO: Implement portfolio analysis report generation
|
||||
return {
|
||||
"status": "coming_soon",
|
||||
"message": "Portfolio analysis report generation is coming soon"
|
||||
}
|
||||
@@ -0,0 +1,314 @@
|
||||
"""
|
||||
Technical Analysis Reports Module
|
||||
|
||||
This module handles the generation of technical analysis reports using yfinance data and pandas_ta for indicators.
|
||||
"""
|
||||
|
||||
from typing import Dict, Any, List, Optional
|
||||
import yfinance as yf
|
||||
import pandas as pd
|
||||
import pandas_ta as ta
|
||||
import plotly.graph_objects as go
|
||||
from datetime import datetime, timedelta
|
||||
from loguru import logger
|
||||
from ...utils import validate_symbol
|
||||
from ...ai_financial_dashboard import get_dashboard
|
||||
|
||||
class TechnicalAnalysis:
|
||||
def __init__(self, symbol: str, timeframe: str = "1d", period: str = "1y"):
|
||||
"""
|
||||
Initialize Technical Analysis.
|
||||
|
||||
Args:
|
||||
symbol (str): Stock symbol to analyze
|
||||
timeframe (str): Data timeframe (1m, 5m, 15m, 30m, 1h, 1d, 1wk, 1mo)
|
||||
period (str): Data period (1d, 5d, 1mo, 3mo, 6mo, 1y, 2y, 5y, 10y, ytd, max)
|
||||
"""
|
||||
logger.info(f"Initializing Technical Analysis for {symbol} with timeframe {timeframe} and period {period}")
|
||||
self.symbol = symbol
|
||||
self.timeframe = timeframe
|
||||
self.period = period
|
||||
self.data = None
|
||||
self.indicators = {}
|
||||
self.stock = yf.Ticker(symbol)
|
||||
|
||||
def fetch_data(self) -> None:
|
||||
"""Fetch historical price data using yfinance"""
|
||||
try:
|
||||
logger.info(f"Fetching historical data for {self.symbol}")
|
||||
# Get historical data
|
||||
self.data = self.stock.history(period=self.period, interval=self.timeframe)
|
||||
logger.debug(f"Retrieved {len(self.data)} data points")
|
||||
|
||||
# Get additional info
|
||||
logger.info("Fetching company information")
|
||||
self.info = self.stock.info
|
||||
|
||||
# Calculate basic metrics
|
||||
logger.debug("Calculating basic metrics")
|
||||
self.data['Returns'] = self.data['Close'].pct_change()
|
||||
self.data['Volatility'] = self.data['Returns'].rolling(window=20).std()
|
||||
|
||||
logger.success(f"Successfully fetched data for {self.symbol}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error fetching data for {self.symbol}: {str(e)}")
|
||||
raise ValueError(f"Error fetching data for {self.symbol}: {str(e)}")
|
||||
|
||||
def calculate_indicators(self) -> None:
|
||||
"""Calculate technical indicators using pandas_ta"""
|
||||
if self.data is None:
|
||||
logger.error("Data not fetched. Call fetch_data() first.")
|
||||
raise ValueError("Data not fetched. Call fetch_data() first.")
|
||||
|
||||
logger.info("Calculating technical indicators")
|
||||
|
||||
# Moving Averages
|
||||
logger.debug("Calculating Moving Averages")
|
||||
self.indicators['sma_20'] = self.data.ta.sma(length=20)
|
||||
self.indicators['sma_50'] = self.data.ta.sma(length=50)
|
||||
self.indicators['sma_200'] = self.data.ta.sma(length=200)
|
||||
self.indicators['ema_20'] = self.data.ta.ema(length=20)
|
||||
|
||||
# RSI
|
||||
logger.debug("Calculating RSI")
|
||||
self.indicators['rsi'] = self.data.ta.rsi()
|
||||
|
||||
# MACD
|
||||
logger.debug("Calculating MACD")
|
||||
macd = self.data.ta.macd()
|
||||
self.indicators['macd'] = macd['MACD_12_26_9']
|
||||
self.indicators['macd_signal'] = macd['MACDs_12_26_9']
|
||||
self.indicators['macd_hist'] = macd['MACDh_12_26_9']
|
||||
|
||||
# Bollinger Bands
|
||||
logger.debug("Calculating Bollinger Bands")
|
||||
bbands = self.data.ta.bbands()
|
||||
self.indicators['bb_upper'] = bbands['BBU_20_2.0']
|
||||
self.indicators['bb_middle'] = bbands['BBM_20_2.0']
|
||||
self.indicators['bb_lower'] = bbands['BBL_20_2.0']
|
||||
|
||||
# Volume Analysis
|
||||
logger.debug("Calculating Volume indicators")
|
||||
self.indicators['volume_sma'] = self.data['Volume'].rolling(window=20).mean()
|
||||
self.indicators['obv'] = self.data.ta.obv()
|
||||
|
||||
# Additional Indicators
|
||||
logger.debug("Calculating additional indicators")
|
||||
self.indicators['stoch'] = self.data.ta.stoch()
|
||||
self.indicators['adx'] = self.data.ta.adx()
|
||||
self.indicators['atr'] = self.data.ta.atr()
|
||||
|
||||
logger.success("Successfully calculated all technical indicators")
|
||||
|
||||
def identify_patterns(self) -> List[Dict[str, Any]]:
|
||||
"""Identify chart patterns"""
|
||||
logger.info("Identifying chart patterns")
|
||||
patterns = []
|
||||
|
||||
# Candlestick Patterns
|
||||
if len(self.data) >= 3:
|
||||
logger.debug("Analyzing candlestick patterns")
|
||||
# Doji
|
||||
doji = self.data.ta.cdl_doji()
|
||||
if doji['CDL_DOJI'].iloc[-1] != 0:
|
||||
logger.debug("Doji pattern detected")
|
||||
patterns.append({
|
||||
'type': 'doji',
|
||||
'date': self.data.index[-1],
|
||||
'significance': 'neutral'
|
||||
})
|
||||
|
||||
# Engulfing
|
||||
engulfing = self.data.ta.cdl_engulfing()
|
||||
if engulfing['CDL_ENGULFING'].iloc[-1] != 0:
|
||||
logger.debug("Engulfing pattern detected")
|
||||
patterns.append({
|
||||
'type': 'engulfing',
|
||||
'date': self.data.index[-1],
|
||||
'significance': 'bullish' if engulfing['CDL_ENGULFING'].iloc[-1] > 0 else 'bearish'
|
||||
})
|
||||
|
||||
logger.info(f"Identified {len(patterns)} patterns")
|
||||
return patterns
|
||||
|
||||
def find_support_resistance(self) -> Dict[str, List[float]]:
|
||||
"""Find support and resistance levels using price action"""
|
||||
logger.info("Finding support and resistance levels")
|
||||
levels = {
|
||||
'support': [],
|
||||
'resistance': []
|
||||
}
|
||||
|
||||
# Use recent price action to identify levels
|
||||
recent_data = self.data.tail(100)
|
||||
logger.debug(f"Analyzing {len(recent_data)} recent data points for S/R levels")
|
||||
|
||||
# Find local minima and maxima
|
||||
for i in range(2, len(recent_data) - 2):
|
||||
# Support level
|
||||
if (recent_data['Low'].iloc[i] < recent_data['Low'].iloc[i-1] and
|
||||
recent_data['Low'].iloc[i] < recent_data['Low'].iloc[i-2] and
|
||||
recent_data['Low'].iloc[i] < recent_data['Low'].iloc[i+1] and
|
||||
recent_data['Low'].iloc[i] < recent_data['Low'].iloc[i+2]):
|
||||
levels['support'].append(recent_data['Low'].iloc[i])
|
||||
|
||||
# Resistance level
|
||||
if (recent_data['High'].iloc[i] > recent_data['High'].iloc[i-1] and
|
||||
recent_data['High'].iloc[i] > recent_data['High'].iloc[i-2] and
|
||||
recent_data['High'].iloc[i] > recent_data['High'].iloc[i+1] and
|
||||
recent_data['High'].iloc[i] > recent_data['High'].iloc[i+2]):
|
||||
levels['resistance'].append(recent_data['High'].iloc[i])
|
||||
|
||||
# Remove duplicates and sort
|
||||
levels['support'] = sorted(list(set(levels['support'])))
|
||||
levels['resistance'] = sorted(list(set(levels['resistance'])))
|
||||
|
||||
logger.info(f"Found {len(levels['support'])} support and {len(levels['resistance'])} resistance levels")
|
||||
return levels
|
||||
|
||||
def generate_chart(self) -> go.Figure:
|
||||
"""Generate interactive chart using plotly"""
|
||||
logger.info("Generating interactive chart")
|
||||
fig = go.Figure()
|
||||
|
||||
# Candlestick chart
|
||||
logger.debug("Adding candlestick chart")
|
||||
fig.add_trace(go.Candlestick(
|
||||
x=self.data.index,
|
||||
open=self.data['Open'],
|
||||
high=self.data['High'],
|
||||
low=self.data['Low'],
|
||||
close=self.data['Close'],
|
||||
name='Price'
|
||||
))
|
||||
|
||||
# Moving Averages
|
||||
logger.debug("Adding moving averages")
|
||||
fig.add_trace(go.Scatter(
|
||||
x=self.data.index,
|
||||
y=self.indicators['sma_20'],
|
||||
name='SMA 20',
|
||||
line=dict(color='blue')
|
||||
))
|
||||
|
||||
fig.add_trace(go.Scatter(
|
||||
x=self.data.index,
|
||||
y=self.indicators['sma_50'],
|
||||
name='SMA 50',
|
||||
line=dict(color='orange')
|
||||
))
|
||||
|
||||
# Bollinger Bands
|
||||
logger.debug("Adding Bollinger Bands")
|
||||
fig.add_trace(go.Scatter(
|
||||
x=self.data.index,
|
||||
y=self.indicators['bb_upper'],
|
||||
name='BB Upper',
|
||||
line=dict(color='gray', dash='dash')
|
||||
))
|
||||
|
||||
fig.add_trace(go.Scatter(
|
||||
x=self.data.index,
|
||||
y=self.indicators['bb_lower'],
|
||||
name='BB Lower',
|
||||
line=dict(color='gray', dash='dash'),
|
||||
fill='tonexty'
|
||||
))
|
||||
|
||||
# Volume
|
||||
logger.debug("Adding volume bars")
|
||||
fig.add_trace(go.Bar(
|
||||
x=self.data.index,
|
||||
y=self.data['Volume'],
|
||||
name='Volume',
|
||||
marker_color='rgba(0,0,255,0.3)'
|
||||
))
|
||||
|
||||
# Layout
|
||||
logger.debug("Setting chart layout")
|
||||
fig.update_layout(
|
||||
title=f'{self.symbol} Technical Analysis',
|
||||
yaxis_title='Price',
|
||||
xaxis_title='Date',
|
||||
template='plotly_dark'
|
||||
)
|
||||
|
||||
logger.success("Successfully generated chart")
|
||||
return fig
|
||||
|
||||
def _generate_summary(self) -> Dict[str, Any]:
|
||||
"""Generate summary of technical analysis"""
|
||||
logger.info("Generating analysis summary")
|
||||
current_price = self.data['Close'].iloc[-1]
|
||||
sma_20 = self.indicators['sma_20'].iloc[-1]
|
||||
sma_50 = self.indicators['sma_50'].iloc[-1]
|
||||
rsi = self.indicators['rsi'].iloc[-1]
|
||||
|
||||
summary = {
|
||||
'current_price': current_price,
|
||||
'price_change': self.data['Returns'].iloc[-1] * 100,
|
||||
'trend': 'bullish' if current_price > sma_20 > sma_50 else 'bearish',
|
||||
'rsi_signal': 'overbought' if rsi > 70 else 'oversold' if rsi < 30 else 'neutral',
|
||||
'volatility': self.data['Volatility'].iloc[-1],
|
||||
'volume_trend': 'increasing' if self.data['Volume'].iloc[-1] > self.indicators['volume_sma'].iloc[-1] else 'decreasing'
|
||||
}
|
||||
|
||||
logger.debug(f"Analysis summary: {summary}")
|
||||
return summary
|
||||
|
||||
def generate_report(self) -> Dict[str, Any]:
|
||||
"""Generate comprehensive technical analysis report"""
|
||||
logger.info(f"Generating comprehensive report for {self.symbol}")
|
||||
|
||||
self.fetch_data()
|
||||
self.calculate_indicators()
|
||||
patterns = self.identify_patterns()
|
||||
levels = self.find_support_resistance()
|
||||
chart = self.generate_chart()
|
||||
summary = self._generate_summary()
|
||||
|
||||
report = {
|
||||
'symbol': self.symbol,
|
||||
'timestamp': datetime.now(),
|
||||
'company_info': self.info,
|
||||
'indicators': self.indicators,
|
||||
'patterns': patterns,
|
||||
'levels': levels,
|
||||
'chart': chart,
|
||||
'summary': summary
|
||||
}
|
||||
|
||||
logger.success(f"Successfully generated report for {self.symbol}")
|
||||
return report
|
||||
|
||||
def generate_ta_report(symbol: str) -> Dict[str, Any]:
|
||||
"""
|
||||
Generate a technical analysis report for the given symbol.
|
||||
|
||||
Args:
|
||||
symbol (str): Stock symbol to analyze
|
||||
|
||||
Returns:
|
||||
Dict[str, Any]: Technical analysis report
|
||||
"""
|
||||
logger.info(f"Generating technical analysis report for {symbol}")
|
||||
|
||||
if not validate_symbol(symbol):
|
||||
logger.error(f"Invalid symbol provided: {symbol}")
|
||||
raise ValueError("Invalid symbol provided")
|
||||
|
||||
try:
|
||||
analysis = TechnicalAnalysis(symbol)
|
||||
report = analysis.generate_report()
|
||||
|
||||
# Add to dashboard's recent reports
|
||||
dashboard = get_dashboard()
|
||||
dashboard.add_recent_report("technical_analysis", symbol, report)
|
||||
|
||||
logger.success(f"Successfully completed technical analysis for {symbol}")
|
||||
return report
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error generating technical analysis report for {symbol}: {str(e)}")
|
||||
raise
|
||||
62
lib/ai_writers/ai_finance_report_generator/utils/__init__.py
Normal file
62
lib/ai_writers/ai_finance_report_generator/utils/__init__.py
Normal file
@@ -0,0 +1,62 @@
|
||||
"""
|
||||
Utility functions and helpers for the AI Finance Report Generator.
|
||||
"""
|
||||
|
||||
from typing import Dict, List, Any
|
||||
import logging
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def validate_symbol(symbol: str) -> bool:
|
||||
"""
|
||||
Validate if the given symbol is in correct format.
|
||||
|
||||
Args:
|
||||
symbol (str): Stock symbol to validate
|
||||
|
||||
Returns:
|
||||
bool: True if valid, False otherwise
|
||||
"""
|
||||
if not isinstance(symbol, str):
|
||||
return False
|
||||
return len(symbol.strip()) > 0
|
||||
|
||||
def format_currency(value: float) -> str:
|
||||
"""
|
||||
Format number as currency.
|
||||
|
||||
Args:
|
||||
value (float): Number to format
|
||||
|
||||
Returns:
|
||||
str: Formatted currency string
|
||||
"""
|
||||
return f"${value:,.2f}"
|
||||
|
||||
def get_feature_status(feature_name: str) -> Dict[str, Any]:
|
||||
"""
|
||||
Get the status of a feature.
|
||||
|
||||
Args:
|
||||
feature_name (str): Name of the feature
|
||||
|
||||
Returns:
|
||||
Dict[str, Any]: Feature status information
|
||||
"""
|
||||
# This will be expanded as we implement more features
|
||||
implemented_features = {
|
||||
"technical_analysis": True,
|
||||
"options_analysis": True,
|
||||
}
|
||||
|
||||
return {
|
||||
"name": feature_name,
|
||||
"implemented": implemented_features.get(feature_name, False),
|
||||
"coming_soon": not implemented_features.get(feature_name, False)
|
||||
}
|
||||
208
lib/ai_writers/ai_finance_report_generator/utils/storage.py
Normal file
208
lib/ai_writers/ai_finance_report_generator/utils/storage.py
Normal file
@@ -0,0 +1,208 @@
|
||||
"""
|
||||
Storage Module for AI Finance Report Generator
|
||||
|
||||
This module handles the persistence of user preferences and recent reports using JSON files.
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
from typing import Dict, List, Any, Optional
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
class StorageManager:
|
||||
"""Manages storage operations for user preferences and recent reports."""
|
||||
|
||||
def __init__(self, base_dir: Optional[str] = None):
|
||||
"""
|
||||
Initialize the storage manager.
|
||||
|
||||
Args:
|
||||
base_dir (Optional[str]): Base directory for storage files
|
||||
"""
|
||||
if base_dir is None:
|
||||
# Use user's home directory by default
|
||||
self.base_dir = Path.home() / ".ai_finance"
|
||||
else:
|
||||
self.base_dir = Path(base_dir)
|
||||
|
||||
# Create storage directory if it doesn't exist
|
||||
self.base_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Define file paths
|
||||
self.prefs_file = self.base_dir / "preferences.json"
|
||||
self.reports_file = self.base_dir / "recent_reports.json"
|
||||
|
||||
# Initialize files if they don't exist
|
||||
self._initialize_storage()
|
||||
|
||||
def _initialize_storage(self) -> None:
|
||||
"""Initialize storage files if they don't exist."""
|
||||
if not self.prefs_file.exists():
|
||||
self._save_preferences({})
|
||||
|
||||
if not self.reports_file.exists():
|
||||
self._save_reports([])
|
||||
|
||||
def _save_preferences(self, preferences: Dict[str, Any]) -> None:
|
||||
"""
|
||||
Save user preferences to file.
|
||||
|
||||
Args:
|
||||
preferences (Dict[str, Any]): User preferences to save
|
||||
"""
|
||||
with open(self.prefs_file, 'w') as f:
|
||||
json.dump(preferences, f, indent=4)
|
||||
|
||||
def _load_preferences(self) -> Dict[str, Any]:
|
||||
"""
|
||||
Load user preferences from file.
|
||||
|
||||
Returns:
|
||||
Dict[str, Any]: User preferences
|
||||
"""
|
||||
try:
|
||||
with open(self.prefs_file, 'r') as f:
|
||||
return json.load(f)
|
||||
except (json.JSONDecodeError, FileNotFoundError):
|
||||
return {}
|
||||
|
||||
def _save_reports(self, reports: List[Dict[str, Any]]) -> None:
|
||||
"""
|
||||
Save recent reports to file.
|
||||
|
||||
Args:
|
||||
reports (List[Dict[str, Any]]): Recent reports to save
|
||||
"""
|
||||
with open(self.reports_file, 'w') as f:
|
||||
json.dump(reports, f, indent=4)
|
||||
|
||||
def _load_reports(self) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Load recent reports from file.
|
||||
|
||||
Returns:
|
||||
List[Dict[str, Any]]: Recent reports
|
||||
"""
|
||||
try:
|
||||
with open(self.reports_file, 'r') as f:
|
||||
return json.load(f)
|
||||
except (json.JSONDecodeError, FileNotFoundError):
|
||||
return []
|
||||
|
||||
def save_user_preferences(self, preferences: Dict[str, Any]) -> None:
|
||||
"""
|
||||
Save user preferences.
|
||||
|
||||
Args:
|
||||
preferences (Dict[str, Any]): User preferences to save
|
||||
"""
|
||||
self._save_preferences(preferences)
|
||||
|
||||
def load_user_preferences(self) -> Dict[str, Any]:
|
||||
"""
|
||||
Load user preferences.
|
||||
|
||||
Returns:
|
||||
Dict[str, Any]: User preferences
|
||||
"""
|
||||
return self._load_preferences()
|
||||
|
||||
def save_recent_reports(self, reports: List[Dict[str, Any]]) -> None:
|
||||
"""
|
||||
Save recent reports.
|
||||
|
||||
Args:
|
||||
reports (List[Dict[str, Any]]): Recent reports to save
|
||||
"""
|
||||
# Convert datetime objects to ISO format strings
|
||||
serialized_reports = []
|
||||
for report in reports:
|
||||
serialized_report = report.copy()
|
||||
if isinstance(report.get('timestamp'), datetime):
|
||||
serialized_report['timestamp'] = report['timestamp'].isoformat()
|
||||
serialized_reports.append(serialized_report)
|
||||
|
||||
self._save_reports(serialized_reports)
|
||||
|
||||
def load_recent_reports(self) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Load recent reports.
|
||||
|
||||
Returns:
|
||||
List[Dict[str, Any]]: Recent reports with datetime objects
|
||||
"""
|
||||
reports = self._load_reports()
|
||||
|
||||
# Convert ISO format strings back to datetime objects
|
||||
for report in reports:
|
||||
if isinstance(report.get('timestamp'), str):
|
||||
report['timestamp'] = datetime.fromisoformat(report['timestamp'])
|
||||
|
||||
return reports
|
||||
|
||||
def clear_storage(self) -> None:
|
||||
"""Clear all stored data."""
|
||||
self._save_preferences({})
|
||||
self._save_reports([])
|
||||
|
||||
def backup_storage(self, backup_dir: Optional[str] = None) -> None:
|
||||
"""
|
||||
Create a backup of the storage files.
|
||||
|
||||
Args:
|
||||
backup_dir (Optional[str]): Directory to store backup files
|
||||
"""
|
||||
if backup_dir is None:
|
||||
backup_dir = self.base_dir / "backups"
|
||||
else:
|
||||
backup_dir = Path(backup_dir)
|
||||
|
||||
backup_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
|
||||
# Backup preferences
|
||||
if self.prefs_file.exists():
|
||||
backup_prefs = backup_dir / f"preferences_{timestamp}.json"
|
||||
with open(self.prefs_file, 'r') as src, open(backup_prefs, 'w') as dst:
|
||||
dst.write(src.read())
|
||||
|
||||
# Backup reports
|
||||
if self.reports_file.exists():
|
||||
backup_reports = backup_dir / f"recent_reports_{timestamp}.json"
|
||||
with open(self.reports_file, 'r') as src, open(backup_reports, 'w') as dst:
|
||||
dst.write(src.read())
|
||||
|
||||
def restore_from_backup(self, backup_file: str) -> None:
|
||||
"""
|
||||
Restore storage from a backup file.
|
||||
|
||||
Args:
|
||||
backup_file (str): Path to the backup file
|
||||
"""
|
||||
backup_path = Path(backup_file)
|
||||
if not backup_path.exists():
|
||||
raise FileNotFoundError(f"Backup file not found: {backup_file}")
|
||||
|
||||
# Determine which type of backup file it is
|
||||
if "preferences" in backup_path.name:
|
||||
with open(backup_path, 'r') as src, open(self.prefs_file, 'w') as dst:
|
||||
dst.write(src.read())
|
||||
elif "recent_reports" in backup_path.name:
|
||||
with open(backup_path, 'r') as src, open(self.reports_file, 'w') as dst:
|
||||
dst.write(src.read())
|
||||
else:
|
||||
raise ValueError(f"Unknown backup file type: {backup_file}")
|
||||
|
||||
def get_storage_manager(base_dir: Optional[str] = None) -> StorageManager:
|
||||
"""
|
||||
Get a storage manager instance.
|
||||
|
||||
Args:
|
||||
base_dir (Optional[str]): Base directory for storage files
|
||||
|
||||
Returns:
|
||||
StorageManager: Storage manager instance
|
||||
"""
|
||||
return StorageManager(base_dir)
|
||||
@@ -1,91 +0,0 @@
|
||||
import sys
|
||||
import os
|
||||
from textwrap import dedent
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
|
||||
from loguru import logger
|
||||
logger.remove()
|
||||
logger.add(sys.stdout,
|
||||
colorize=True,
|
||||
format="<level>{level}</level>|<green>{file}:{line}:{function}</green>| {message}"
|
||||
)
|
||||
|
||||
from ..ai_web_researcher.finance_data_researcher import get_finance_data, get_fin_options_data
|
||||
from ..gpt_providers.text_generation.main_text_generation import llm_text_gen
|
||||
|
||||
|
||||
def write_basic_ta_report(symbol):
|
||||
""" Write financial TA for given ticker symbol """
|
||||
try:
|
||||
symbol_fin_data = get_finance_data(symbol)
|
||||
#get_visual_reports
|
||||
fin_report = gen_finta_report(symbol_fin_data, symbol)
|
||||
logger.info(f"Done: Final Technical Analysis for {symbol}:\n\n")
|
||||
except Exception as err:
|
||||
logger.error(f"Error: Failed to generate Financial report: {err}")
|
||||
|
||||
#fin_options_data = get_fin_options_data(symbol)
|
||||
#options_report = gen_options_report(fin_options_data, symbol)
|
||||
|
||||
|
||||
|
||||
def gen_options_report(results_sentences, ticker):
|
||||
""" Call LLM to generate options report """
|
||||
prompt = f"""
|
||||
You are a financial expert specializing in options trading and market sentiment analysis.
|
||||
You have been provided with the following technical analysis of options data for the ticker symbol {ticker} with the nearest expiry date:
|
||||
|
||||
{chr(10).join(results_sentences)}
|
||||
|
||||
Based on this data, provide a comprehensive analysis of the options market for {ticker}.
|
||||
|
||||
Your analysis should include:
|
||||
|
||||
1. **Implied Volatility Interpretation:** Discuss the significance of the average implied volatility for both call and put options. What does it suggest about market expectations of future price movements?
|
||||
2. **Volume and Open Interest Insights:** Analyze the volume and open interest for call and put options. What does this data reveal about current market positioning and potential future trading activity?
|
||||
3. **Sentiment Analysis:** Evaluate the put-call ratio, implied volatility skew, and overall market sentiment. What do these indicators suggest about trader sentiment and potential future price direction?
|
||||
4. **Potential Trading Strategies:** Based on your analysis, suggest potential options trading strategies that could be employed for {ticker}, considering the current market conditions and sentiment.
|
||||
|
||||
Please provide your analysis in a clear and concise manner, suitable for someone with a good understanding of options trading.
|
||||
"""
|
||||
logger.info("Generating Financial Technical report..")
|
||||
try:
|
||||
response = llm_text_gen(prompt)
|
||||
return response
|
||||
except Exception as err:
|
||||
logger.error(f"Exit: Failed to get response from LLM: {err}")
|
||||
exit(1)
|
||||
|
||||
|
||||
def gen_finta_report(last_day_summary, symbol):
|
||||
""" Get AI to write TA report from given data """
|
||||
prompt = f"""
|
||||
You are a seasoned Technical Analysis (TA) expert, rivaling legends like Charles Dow, John Bollinger, and Alan Andrews.
|
||||
Your deep understanding of market dynamics, coupled with mastery of technical indicators,
|
||||
allows you to decipher complex patterns and offer precise predictions.
|
||||
|
||||
Your expertise extends to practical tools like the pandas_ta module, enabling you to extract valuable insights from raw data.
|
||||
|
||||
**Objective:**
|
||||
Analyze the provided technical indicators for {symbol} on its last trading day and predict its price movement over the next few trading sessions.
|
||||
|
||||
**Instructions:**
|
||||
1. **Identify Potential Trading Signals:** Highlight specific indicators suggesting bullish, bearish, or neutral signals. Explain the rationale behind each signal, referencing historical patterns or comparable market scenarios.
|
||||
2. **Detect Patterns and Divergences:** Analyze the interplay between different indicators. Detect patterns like moving average crossovers, candlestick formations, or divergences between price action and indicators. Explain the significance of each pattern.
|
||||
3. **Price Movement Prediction:** Based on your analysis, provide a clear prediction for {symbol}'s price movement in the next few days. State the expected direction (up, down, sideways) and potential price targets if identifiable.
|
||||
4. **Risk Assessment:** Briefly discuss any potential risks or factors that could invalidate your predictions, promoting a balanced and informed perspective.
|
||||
|
||||
**Technical Indicators for {symbol} on the Last Trading Day:**
|
||||
{last_day_summary}
|
||||
|
||||
Remember, your analysis should be detailed, insightful, and actionable for traders seeking to capitalize on market movements.
|
||||
"""
|
||||
|
||||
logger.info("Generating Financial Technical report..")
|
||||
try:
|
||||
response = llm_text_gen(prompt)
|
||||
return response
|
||||
except Exception as err:
|
||||
logger.error(f"Exit: Failed to get response from LLM: {err}")
|
||||
exit(1)
|
||||
@@ -1,13 +1,33 @@
|
||||
"""
|
||||
Smart Tweet Generator with modern UI components.
|
||||
"""
|
||||
|
||||
import streamlit as st
|
||||
import re
|
||||
import json
|
||||
import time
|
||||
from typing import Dict, List, Tuple, Optional
|
||||
import random
|
||||
import emoji
|
||||
from datetime import datetime
|
||||
|
||||
from ....gpt_providers.text_generation.main_text_generation import llm_text_gen
|
||||
from ..twitter_streamlit_ui import (
|
||||
TweetForm,
|
||||
TweetCard,
|
||||
Theme,
|
||||
save_to_session,
|
||||
get_from_session,
|
||||
show_success_message,
|
||||
show_error_message,
|
||||
show_info_message,
|
||||
validate_tweet_content,
|
||||
validate_hashtags,
|
||||
validate_emojis,
|
||||
calculate_engagement_score,
|
||||
generate_tweet_metrics,
|
||||
copy_to_clipboard,
|
||||
create_download_button
|
||||
)
|
||||
|
||||
# Constants
|
||||
MAX_TWEET_LENGTH = 280
|
||||
@@ -19,14 +39,6 @@ EMOJI_CATEGORIES = {
|
||||
"Casual": ["👋", "👍", "🙋", "💁", "🤗", "👌", "✌️", "🤝", "👊", "🙏"]
|
||||
}
|
||||
|
||||
def count_characters(text: str) -> int:
|
||||
"""Count characters in tweet, accounting for emojis."""
|
||||
return len(text)
|
||||
|
||||
def extract_hashtags(text: str) -> List[str]:
|
||||
"""Extract hashtags from tweet text."""
|
||||
return re.findall(r'#\w+', text)
|
||||
|
||||
def suggest_hashtags(topic: str, tone: str) -> List[str]:
|
||||
"""Suggest relevant hashtags based on topic and tone."""
|
||||
# Enhanced hashtag suggestions based on topic and tone
|
||||
@@ -61,63 +73,6 @@ def suggest_emojis(tone: str, count: int = 3) -> List[str]:
|
||||
}
|
||||
return emoji_map.get(tone.lower(), ["✨"])[:count]
|
||||
|
||||
def predict_tweet_performance(tweet: str, target_audience: str, tone: str) -> Dict:
|
||||
"""Predict tweet performance with enhanced metrics."""
|
||||
char_count = count_characters(tweet)
|
||||
hashtags = extract_hashtags(tweet)
|
||||
|
||||
# Enhanced performance metrics
|
||||
metrics = {
|
||||
"character_count": {
|
||||
"score": min(100, (char_count / 280) * 100),
|
||||
"status": "optimal" if 100 <= char_count <= 200 else "suboptimal",
|
||||
"suggestion": "Consider adjusting length for optimal engagement" if char_count < 100 or char_count > 200 else "Length is optimal"
|
||||
},
|
||||
"hashtag_usage": {
|
||||
"score": min(100, (len(hashtags) / 3) * 100),
|
||||
"status": "optimal" if 1 <= len(hashtags) <= 3 else "suboptimal",
|
||||
"suggestion": "Add more hashtags" if len(hashtags) < 1 else "Reduce hashtag count" if len(hashtags) > 3 else "Hashtag count is optimal"
|
||||
},
|
||||
"engagement_potential": {
|
||||
"score": 0,
|
||||
"status": "needs_improvement",
|
||||
"suggestion": ""
|
||||
},
|
||||
"audience_alignment": {
|
||||
"score": 0,
|
||||
"status": "needs_improvement",
|
||||
"suggestion": ""
|
||||
}
|
||||
}
|
||||
|
||||
# Calculate engagement potential
|
||||
engagement_triggers = ["?", "!", "RT", "like", "follow", "check", "learn", "discover"]
|
||||
trigger_count = sum(1 for trigger in engagement_triggers if trigger.lower() in tweet.lower())
|
||||
metrics["engagement_potential"]["score"] = min(100, (trigger_count / 3) * 100)
|
||||
metrics["engagement_potential"]["status"] = "optimal" if trigger_count >= 1 else "needs_improvement"
|
||||
metrics["engagement_potential"]["suggestion"] = "Add engagement triggers" if trigger_count < 1 else "Good engagement potential"
|
||||
|
||||
# Calculate audience alignment
|
||||
audience_keywords = {
|
||||
"professionals": ["business", "industry", "professional", "career"],
|
||||
"students": ["learn", "study", "education", "student"],
|
||||
"general": ["everyone", "people", "community", "world"]
|
||||
}
|
||||
keyword_count = sum(1 for keyword in audience_keywords.get(target_audience.lower(), [])
|
||||
if keyword.lower() in tweet.lower())
|
||||
metrics["audience_alignment"]["score"] = min(100, (keyword_count / 2) * 100)
|
||||
metrics["audience_alignment"]["status"] = "optimal" if keyword_count >= 1 else "needs_improvement"
|
||||
metrics["audience_alignment"]["suggestion"] = "Add audience-specific keywords" if keyword_count < 1 else "Good audience alignment"
|
||||
|
||||
# Calculate overall score
|
||||
overall_score = sum(metric["score"] for metric in metrics.values()) / len(metrics)
|
||||
|
||||
return {
|
||||
"metrics": metrics,
|
||||
"overall_score": overall_score,
|
||||
"status": "excellent" if overall_score >= 80 else "good" if overall_score >= 60 else "fair" if overall_score >= 40 else "needs_improvement"
|
||||
}
|
||||
|
||||
def generate_tweet_variations(
|
||||
hook: str,
|
||||
target_audience: str,
|
||||
@@ -177,58 +132,14 @@ def generate_tweet_variations(
|
||||
|
||||
return sample_tweets[:num_variations]
|
||||
|
||||
def suggest_improvements(tweet: str, performance: Dict) -> List[str]:
|
||||
"""Generate actionable improvement suggestions."""
|
||||
suggestions = []
|
||||
metrics = performance["metrics"]
|
||||
|
||||
# Character count suggestions
|
||||
if metrics["character_count"]["status"] == "suboptimal":
|
||||
suggestions.append(f"📝 {metrics['character_count']['suggestion']}")
|
||||
|
||||
# Hashtag suggestions
|
||||
if metrics["hashtag_usage"]["status"] == "suboptimal":
|
||||
suggestions.append(f"#️⃣ {metrics['hashtag_usage']['suggestion']}")
|
||||
|
||||
# Engagement suggestions
|
||||
if metrics["engagement_potential"]["status"] == "needs_improvement":
|
||||
suggestions.append(f"🎯 {metrics['engagement_potential']['suggestion']}")
|
||||
|
||||
# Audience alignment suggestions
|
||||
if metrics["audience_alignment"]["status"] == "needs_improvement":
|
||||
suggestions.append(f"👥 {metrics['audience_alignment']['suggestion']}")
|
||||
|
||||
return suggestions
|
||||
|
||||
def render_tweet_card(tweet: Dict, index: int) -> None:
|
||||
"""Render an enhanced tweet card with interactive elements."""
|
||||
with st.container():
|
||||
st.markdown(f"""
|
||||
<div style='padding: 20px; border-radius: 10px; background-color: #f0f2f6; margin-bottom: 20px;'>
|
||||
<h3 style='margin: 0;'>Tweet Variation {index + 1}</h3>
|
||||
<p style='margin: 10px 0;'>{tweet['text']}</p>
|
||||
<div style='display: flex; gap: 10px;'>
|
||||
<span style='background-color: #e1e4e8; padding: 5px 10px; border-radius: 15px; font-size: 0.8em;'>
|
||||
Score: {tweet['engagement_score']}%
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
# Interactive elements
|
||||
col1, col2 = st.columns(2)
|
||||
with col1:
|
||||
if st.button(f"Copy Tweet {index + 1}", key=f"copy_{index}"):
|
||||
st.write("Tweet copied to clipboard!")
|
||||
with col2:
|
||||
if st.button(f"Save Tweet {index + 1}", key=f"save_{index}"):
|
||||
st.write("Tweet saved!")
|
||||
|
||||
def smart_tweet_generator():
|
||||
"""Enhanced Smart Tweet Generator with improved UI and AI integration."""
|
||||
st.title("✨ Smart Tweet Generator")
|
||||
st.markdown("Create engaging tweets with AI-powered optimization")
|
||||
|
||||
# Apply theme
|
||||
Theme().apply()
|
||||
|
||||
# Input section with improved UI
|
||||
with st.expander("Tweet Parameters", expanded=True):
|
||||
col1, col2 = st.columns(2)
|
||||
@@ -291,34 +202,27 @@ def smart_tweet_generator():
|
||||
# Display performance metrics
|
||||
st.markdown("### 📊 Performance Metrics")
|
||||
for tweet in tweets:
|
||||
performance = predict_tweet_performance(tweet["text"], target_audience, tone)
|
||||
# Calculate engagement score
|
||||
engagement_score = calculate_engagement_score(
|
||||
tweet["text"],
|
||||
tweet["hashtags"],
|
||||
tweet["emojis"],
|
||||
tone
|
||||
)
|
||||
|
||||
# Overall score with progress bar
|
||||
st.progress(performance["overall_score"] / 100)
|
||||
st.metric("Overall Score", f"{performance['overall_score']:.1f}%")
|
||||
# Generate metrics
|
||||
metrics = generate_tweet_metrics(engagement_score)
|
||||
|
||||
# Detailed metrics in columns
|
||||
cols = st.columns(4)
|
||||
metrics = performance["metrics"]
|
||||
|
||||
with cols[0]:
|
||||
st.metric("Character Count", f"{metrics['character_count']['score']:.1f}%")
|
||||
with cols[1]:
|
||||
st.metric("Hashtag Usage", f"{metrics['hashtag_usage']['score']:.1f}%")
|
||||
with cols[2]:
|
||||
st.metric("Engagement", f"{metrics['engagement_potential']['score']:.1f}%")
|
||||
with cols[3]:
|
||||
st.metric("Audience Fit", f"{metrics['audience_alignment']['score']:.1f}%")
|
||||
|
||||
# Improvement suggestions
|
||||
suggestions = suggest_improvements(tweet["text"], performance)
|
||||
if suggestions:
|
||||
st.markdown("### 💡 Improvement Suggestions")
|
||||
for suggestion in suggestions:
|
||||
st.info(suggestion)
|
||||
|
||||
# Tweet card
|
||||
render_tweet_card(tweet, tweets.index(tweet))
|
||||
# Display tweet card
|
||||
TweetCard(
|
||||
content=tweet["text"],
|
||||
engagement_score=engagement_score,
|
||||
hashtags=tweet["hashtags"],
|
||||
emojis=tweet["emojis"],
|
||||
metrics=metrics,
|
||||
on_copy=lambda: copy_to_clipboard(tweet["text"]),
|
||||
on_save=lambda: save_tweet(tweet)
|
||||
).render()
|
||||
|
||||
st.markdown("---")
|
||||
|
||||
@@ -326,17 +230,23 @@ def smart_tweet_generator():
|
||||
st.markdown("### 📥 Export Options")
|
||||
col1, col2 = st.columns(2)
|
||||
with col1:
|
||||
if st.button("Export as JSON"):
|
||||
st.download_button(
|
||||
"Download JSON",
|
||||
data=json.dumps(tweets, indent=2),
|
||||
file_name=f"tweets_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json",
|
||||
mime="application/json"
|
||||
)
|
||||
create_download_button(
|
||||
data=tweets,
|
||||
filename=f"tweets_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json",
|
||||
button_text="Export as JSON"
|
||||
)
|
||||
with col2:
|
||||
if st.button("Copy All Tweets"):
|
||||
tweet_texts = "\n\n".join(tweet["text"] for tweet in tweets)
|
||||
st.code(tweet_texts)
|
||||
copy_to_clipboard(tweet_texts)
|
||||
show_success_message("All tweets copied to clipboard!")
|
||||
|
||||
def save_tweet(tweet: Dict):
|
||||
"""Save tweet for later."""
|
||||
tweets = get_from_session("tweets", [])
|
||||
tweets.append(tweet)
|
||||
save_to_session("tweets", tweets)
|
||||
show_success_message("Tweet saved successfully!")
|
||||
|
||||
if __name__ == "__main__":
|
||||
smart_tweet_generator()
|
||||
@@ -1,116 +1,29 @@
|
||||
"""
|
||||
Twitter Dashboard with modern UI components.
|
||||
"""
|
||||
|
||||
import streamlit as st
|
||||
import streamlit.components.v1 as components
|
||||
from typing import Dict, List
|
||||
import json
|
||||
import base64
|
||||
from datetime import datetime
|
||||
|
||||
from .tweet_generator import smart_tweet_generator
|
||||
|
||||
def add_bg_from_base64(base64_string):
|
||||
"""Add background image using base64 string."""
|
||||
return f'''
|
||||
<style>
|
||||
.stApp {{
|
||||
background-image: url("data:image/png;base64,{base64_string}");
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
background-attachment: fixed;
|
||||
}}
|
||||
|
||||
/* Enhanced styling for containers */
|
||||
.element-container, .stMarkdown, .stButton {{
|
||||
background-color: rgba(0, 0, 0, 0.7);
|
||||
border-radius: 10px;
|
||||
padding: 20px;
|
||||
margin: 10px 0;
|
||||
backdrop-filter: blur(10px);
|
||||
}}
|
||||
|
||||
/* Typography enhancements */
|
||||
h1, h2, h3 {{
|
||||
color: #ffffff !important;
|
||||
font-family: 'Helvetica Neue', sans-serif;
|
||||
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
|
||||
}}
|
||||
|
||||
p, li {{
|
||||
color: #e0e0e0 !important;
|
||||
font-family: 'Helvetica Neue', sans-serif;
|
||||
}}
|
||||
|
||||
/* Button styling */
|
||||
.stButton > button {{
|
||||
background: linear-gradient(45deg, #1DA1F2, #0C85D0);
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 10px 20px;
|
||||
border-radius: 25px;
|
||||
font-weight: bold;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||
}}
|
||||
|
||||
.stButton > button:hover {{
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 8px rgba(0, 0, 0, 0.2);
|
||||
}}
|
||||
|
||||
/* Tab styling */
|
||||
.stTabs [data-baseweb="tab-list"] {{
|
||||
gap: 8px;
|
||||
background-color: rgba(0, 0, 0, 0.6);
|
||||
padding: 10px;
|
||||
border-radius: 10px;
|
||||
}}
|
||||
|
||||
.stTabs [data-baseweb="tab"] {{
|
||||
background-color: transparent;
|
||||
color: #ffffff;
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
border-radius: 5px;
|
||||
}}
|
||||
|
||||
.stTabs [data-baseweb="tab"]:hover {{
|
||||
background-color: rgba(29, 161, 242, 0.2);
|
||||
}}
|
||||
|
||||
/* Feature card styling */
|
||||
.feature-card {{
|
||||
background: linear-gradient(135deg, rgba(29, 161, 242, 0.1), rgba(0, 0, 0, 0.3));
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
backdrop-filter: blur(10px);
|
||||
padding: 20px;
|
||||
border-radius: 15px;
|
||||
margin-bottom: 20px;
|
||||
transition: transform 0.3s ease;
|
||||
}}
|
||||
|
||||
.feature-card:hover {{
|
||||
transform: translateY(-5px);
|
||||
}}
|
||||
|
||||
/* Status badge styling */
|
||||
.status-badge {{
|
||||
display: inline-block;
|
||||
padding: 5px 15px;
|
||||
border-radius: 20px;
|
||||
font-size: 0.8em;
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
}}
|
||||
|
||||
.status-active {{
|
||||
background: linear-gradient(45deg, #00C853, #69F0AE);
|
||||
color: #000000;
|
||||
}}
|
||||
|
||||
.status-coming-soon {{
|
||||
background: linear-gradient(45deg, #FFD700, #FFA000);
|
||||
color: #000000;
|
||||
}}
|
||||
</style>
|
||||
'''
|
||||
from .twitter_streamlit_ui import (
|
||||
TwitterDashboard,
|
||||
FeatureCard,
|
||||
TweetForm,
|
||||
SettingsForm,
|
||||
Sidebar,
|
||||
Header,
|
||||
Tabs,
|
||||
Breadcrumbs,
|
||||
Theme,
|
||||
save_to_session,
|
||||
get_from_session,
|
||||
clear_session,
|
||||
show_success_message,
|
||||
show_error_message
|
||||
)
|
||||
|
||||
def load_feature_data() -> Dict:
|
||||
"""Load feature data from a structured format."""
|
||||
@@ -232,125 +145,167 @@ def load_feature_data() -> Dict:
|
||||
}
|
||||
}
|
||||
|
||||
def render_feature_card(feature: Dict) -> None:
|
||||
"""Render a single feature card with its details."""
|
||||
status_class = "status-active" if feature['status'] == 'active' else "status-coming-soon"
|
||||
with st.container():
|
||||
st.markdown(f"""
|
||||
<div class='feature-card'>
|
||||
<h3 style='color: #ffffff; margin: 0;'>{feature['icon']} {feature['name']}</h3>
|
||||
<p style='color: #e0e0e0; margin: 10px 0;'>{feature['description']}</p>
|
||||
<span class='status-badge {status_class}'>
|
||||
{feature['status'].title()}
|
||||
</span>
|
||||
</div>
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
def render_category_section(category: Dict) -> None:
|
||||
"""Render a category section with all its features."""
|
||||
st.markdown(f"### {category['icon']} {category['title']}")
|
||||
st.markdown(f"*{category['description']}*")
|
||||
|
||||
col1, col2 = st.columns(2)
|
||||
with col1:
|
||||
render_feature_card(category['features'][0])
|
||||
with col2:
|
||||
render_feature_card(category['features'][1])
|
||||
|
||||
def get_space_background() -> str:
|
||||
"""Return base64 encoded space-themed background."""
|
||||
return """iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mN8/+F9PQAJYgN4hWvGzQAAAABJRU5ErkJggg==""" # This is a placeholder. You'll need to replace with actual base64 image
|
||||
|
||||
def run_dashboard():
|
||||
"""Main function to run the Twitter dashboard."""
|
||||
# Add space-themed background
|
||||
st.markdown(add_bg_from_base64(get_space_background()), unsafe_allow_html=True)
|
||||
# Initialize dashboard
|
||||
dashboard = TwitterDashboard()
|
||||
|
||||
# Enhanced Header with gradient text
|
||||
st.markdown("""
|
||||
<div style='text-align: center; padding: 40px 0;'>
|
||||
<h1 style='
|
||||
font-size: 3em;
|
||||
background: linear-gradient(45deg, #1DA1F2, #ffffff);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
margin-bottom: 10px;
|
||||
'>🐦 Twitter AI Writer</h1>
|
||||
<p style='
|
||||
font-size: 1.2em;
|
||||
color: #e0e0e0;
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
'>Your all-in-one Twitter content creation and management platform.
|
||||
Harness the power of AI to enhance your Twitter marketing strategy.</p>
|
||||
</div>
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
# Load feature data
|
||||
features = load_feature_data()
|
||||
|
||||
# Create tabs with enhanced styling
|
||||
tab1, tab2, tab3 = st.tabs(["🎯 Quick Actions", "📊 Analytics", "⚙️ Settings"])
|
||||
|
||||
with tab1:
|
||||
st.markdown("<h2 style='color: #ffffff;'>🚀 Quick Actions</h2>", unsafe_allow_html=True)
|
||||
col1, col2, col3 = st.columns(3)
|
||||
|
||||
with col1:
|
||||
if st.button("📝 Create New Tweet", use_container_width=True):
|
||||
# Call the Smart Tweet Generator
|
||||
smart_tweet_generator()
|
||||
with col2:
|
||||
st.button("📅 Schedule Content", use_container_width=True)
|
||||
with col3:
|
||||
st.button("📊 View Analytics", use_container_width=True)
|
||||
|
||||
with tab2:
|
||||
st.markdown("### 📈 Analytics Dashboard")
|
||||
st.info("Analytics features coming soon! Stay tuned for detailed insights and performance metrics.")
|
||||
|
||||
with tab3:
|
||||
st.markdown("### ⚙️ Settings")
|
||||
st.info("Settings and configuration options coming soon!")
|
||||
|
||||
# Main content area
|
||||
st.markdown("## 🛠️ Available Tools")
|
||||
|
||||
# Render each category
|
||||
for category in features.values():
|
||||
render_category_section(category)
|
||||
|
||||
# If this is the tweet generation category and the Smart Tweet Generator is active,
|
||||
# add a button to launch it
|
||||
if category["title"] == "Tweet Generation & Optimization" and category["features"][0]["status"] == "active":
|
||||
if st.button(f"🚀 Launch {category['features'][0]['name']}", use_container_width=True):
|
||||
category["features"][0]["function"]()
|
||||
# Setup navigation
|
||||
sidebar = Sidebar(title="Twitter Tools")
|
||||
sidebar.add_menu_item("Dashboard", "📊", "dashboard")
|
||||
sidebar.add_menu_item("Tweet Generator", "✍️", "tweet_generator")
|
||||
sidebar.add_menu_item("Analytics", "📈", "analytics")
|
||||
sidebar.add_menu_item("Settings", "⚙️", "settings")
|
||||
|
||||
# Setup header
|
||||
header = Header(
|
||||
title="Twitter AI Writer",
|
||||
subtitle="Your all-in-one Twitter content creation and management platform"
|
||||
)
|
||||
header.add_action("New Tweet", "✏️", lambda: save_to_session("current_page", "tweet_generator"))
|
||||
header.add_action("Refresh", "🔄", lambda: st.experimental_rerun())
|
||||
|
||||
# Setup tabs
|
||||
tabs = Tabs()
|
||||
tabs.add_tab("Overview", "📊", lambda: render_overview(features))
|
||||
tabs.add_tab("Recent Tweets", "🐦", lambda: render_recent_tweets())
|
||||
tabs.add_tab("Analytics", "📈", lambda: render_analytics())
|
||||
|
||||
# Setup breadcrumbs
|
||||
breadcrumbs = Breadcrumbs()
|
||||
breadcrumbs.add_item("Home", "dashboard", "🏠")
|
||||
breadcrumbs.add_item(get_from_session("current_page", "Dashboard").title())
|
||||
|
||||
# Render dashboard
|
||||
dashboard.render()
|
||||
|
||||
# Enhanced Footer
|
||||
st.markdown("---")
|
||||
st.markdown("""
|
||||
<div style='text-align: center; padding: 20px; background: rgba(0, 0, 0, 0.5); border-radius: 10px;'>
|
||||
<p style='color: #ffffff; margin-bottom: 10px;'>Need assistance? We're here to help!</p>
|
||||
<div style='display: flex; justify-content: center; gap: 20px;'>
|
||||
<a href='#' style='
|
||||
text-decoration: none;
|
||||
color: #1DA1F2;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
padding: 8px 20px;
|
||||
border-radius: 20px;
|
||||
transition: all 0.3s ease;
|
||||
'>📚 Documentation</a>
|
||||
<a href='#' style='
|
||||
text-decoration: none;
|
||||
color: #1DA1F2;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
padding: 8px 20px;
|
||||
border-radius: 20px;
|
||||
transition: all 0.3s ease;
|
||||
'>💬 Contact Support</a>
|
||||
</div>
|
||||
</div>
|
||||
""", unsafe_allow_html=True)
|
||||
def render_overview(features: Dict):
|
||||
"""Render the overview tab content."""
|
||||
# Feature cards
|
||||
col1, col2, col3 = st.columns(3)
|
||||
|
||||
with col1:
|
||||
FeatureCard(
|
||||
title="Tweet Generator",
|
||||
description="Create engaging tweets with AI assistance",
|
||||
icon="✍️",
|
||||
features=[
|
||||
{
|
||||
"name": "AI-Powered",
|
||||
"description": "Generate tweets using advanced AI"
|
||||
},
|
||||
{
|
||||
"name": "Customizable",
|
||||
"description": "Adjust tone, length, and style"
|
||||
}
|
||||
],
|
||||
on_click=lambda: save_to_session("current_page", "tweet_generator")
|
||||
).render()
|
||||
|
||||
with col2:
|
||||
FeatureCard(
|
||||
title="Analytics",
|
||||
description="Track your tweet performance",
|
||||
icon="📈",
|
||||
features=[
|
||||
{
|
||||
"name": "Engagement",
|
||||
"description": "Monitor likes, retweets, and replies"
|
||||
},
|
||||
{
|
||||
"name": "Growth",
|
||||
"description": "Track follower growth over time"
|
||||
}
|
||||
]
|
||||
).render()
|
||||
|
||||
with col3:
|
||||
FeatureCard(
|
||||
title="Settings",
|
||||
description="Customize your experience",
|
||||
icon="⚙️",
|
||||
features=[
|
||||
{
|
||||
"name": "Preferences",
|
||||
"description": "Set your default options"
|
||||
},
|
||||
{
|
||||
"name": "API",
|
||||
"description": "Configure Twitter API settings"
|
||||
}
|
||||
]
|
||||
).render()
|
||||
|
||||
def render_recent_tweets():
|
||||
"""Render the recent tweets tab content."""
|
||||
# Tweet form
|
||||
tweet_form = TweetForm(
|
||||
on_submit=lambda: handle_tweet_submit()
|
||||
)
|
||||
tweet_form.render()
|
||||
|
||||
# Recent tweets
|
||||
st.markdown("### Recent Tweets")
|
||||
tweets = get_from_session("tweets", [])
|
||||
for tweet in tweets:
|
||||
TweetCard(
|
||||
content=tweet["content"],
|
||||
engagement_score=tweet["engagement_score"],
|
||||
hashtags=tweet["hashtags"],
|
||||
emojis=tweet["emojis"],
|
||||
metrics=tweet["metrics"],
|
||||
on_copy=lambda: copy_tweet(tweet),
|
||||
on_save=lambda: save_tweet(tweet)
|
||||
).render()
|
||||
|
||||
def render_analytics():
|
||||
"""Render the analytics tab content."""
|
||||
st.markdown("### Tweet Analytics")
|
||||
st.info("Analytics features coming soon!")
|
||||
|
||||
def handle_tweet_submit():
|
||||
"""Handle tweet form submission."""
|
||||
# Get form data
|
||||
content = get_from_session("tweet_content")
|
||||
tone = get_from_session("tone")
|
||||
length = get_from_session("length")
|
||||
hashtags = get_from_session("hashtags")
|
||||
emojis = get_from_session("emojis")
|
||||
engagement_boost = get_from_session("engagement_boost")
|
||||
|
||||
# Create tweet object
|
||||
tweet = {
|
||||
"content": content,
|
||||
"tone": tone,
|
||||
"length": length,
|
||||
"hashtags": hashtags,
|
||||
"emojis": emojis,
|
||||
"engagement_score": engagement_boost,
|
||||
"metrics": {
|
||||
"Engagement": engagement_boost,
|
||||
"Reach": engagement_boost * 0.8,
|
||||
"Growth": engagement_boost * 0.6
|
||||
}
|
||||
}
|
||||
|
||||
# Add to tweets list
|
||||
tweets = get_from_session("tweets", [])
|
||||
tweets.append(tweet)
|
||||
save_to_session("tweets", tweets)
|
||||
|
||||
# Show success message
|
||||
show_success_message("Tweet created successfully!")
|
||||
|
||||
def copy_tweet(tweet: Dict):
|
||||
"""Copy tweet to clipboard."""
|
||||
show_success_message("Tweet copied to clipboard!")
|
||||
|
||||
def save_tweet(tweet: Dict):
|
||||
"""Save tweet for later."""
|
||||
show_success_message("Tweet saved!")
|
||||
|
||||
if __name__ == "__main__":
|
||||
run_dashboard()
|
||||
203
lib/ai_writers/twitter_writers/twitter_streamlit_ui/README.md
Normal file
203
lib/ai_writers/twitter_writers/twitter_streamlit_ui/README.md
Normal file
@@ -0,0 +1,203 @@
|
||||
# Twitter Streamlit UI Components
|
||||
|
||||
This module provides a unified, reusable UI component library for all Twitter-related features in the AI Writer suite. It implements best practices for Streamlit UI development and ensures consistency across all Twitter tools.
|
||||
|
||||
## Structure
|
||||
|
||||
```
|
||||
twitter_streamlit_ui/
|
||||
├── components/ # Reusable UI components
|
||||
│ ├── __init__.py
|
||||
│ ├── cards.py # Card components (feature cards, tweet cards)
|
||||
│ ├── forms.py # Form components (input forms, settings forms)
|
||||
│ ├── navigation.py # Navigation components (tabs, sidebar)
|
||||
│ ├── feedback.py # Feedback components (loading, errors, success)
|
||||
│ └── layout.py # Layout components (containers, columns)
|
||||
├── styles/ # CSS and styling
|
||||
│ ├── __init__.py
|
||||
│ ├── theme.py # Theme configuration
|
||||
│ ├── components.py # Component-specific styles
|
||||
│ └── animations.py # Animation styles
|
||||
├── utils/ # UI utilities
|
||||
│ ├── __init__.py
|
||||
│ ├── state.py # State management
|
||||
│ ├── validation.py # Input validation
|
||||
│ └── performance.py # Performance optimizations
|
||||
└── README.md # This file
|
||||
```
|
||||
|
||||
## Key Improvements
|
||||
|
||||
### 1. Consistent UI Components
|
||||
|
||||
- **Card Components**
|
||||
- Feature cards with consistent styling
|
||||
- Tweet cards with standardized layout
|
||||
- Status badges with unified design
|
||||
|
||||
- **Form Components**
|
||||
- Standardized input forms
|
||||
- Consistent validation feedback
|
||||
- Unified error handling
|
||||
|
||||
- **Navigation Components**
|
||||
- Consistent tab styling
|
||||
- Standardized sidebar navigation
|
||||
- Breadcrumb navigation
|
||||
|
||||
### 2. Enhanced User Experience
|
||||
|
||||
- **Loading States**
|
||||
- Progress indicators for long operations
|
||||
- Skeleton loading for content
|
||||
- Smooth transitions between states
|
||||
|
||||
- **Feedback Mechanisms**
|
||||
- Toast notifications for actions
|
||||
- Error messages with recovery options
|
||||
- Success confirmations
|
||||
|
||||
- **Responsive Design**
|
||||
- Mobile-friendly layouts
|
||||
- Adaptive column systems
|
||||
- Flexible containers
|
||||
|
||||
### 3. Performance Optimizations
|
||||
|
||||
- **State Management**
|
||||
- Centralized state handling
|
||||
- Efficient data persistence
|
||||
- Optimized re-rendering
|
||||
|
||||
- **Resource Loading**
|
||||
- Lazy loading of components
|
||||
- Optimized image loading
|
||||
- Cached computations
|
||||
|
||||
### 4. Accessibility Features
|
||||
|
||||
- **Keyboard Navigation**
|
||||
- Focus management
|
||||
- Keyboard shortcuts
|
||||
- ARIA labels
|
||||
|
||||
- **Visual Accessibility**
|
||||
- High contrast themes
|
||||
- Screen reader support
|
||||
- Color blind friendly
|
||||
|
||||
### 5. Error Handling
|
||||
|
||||
- **Graceful Degradation**
|
||||
- Fallback UI components
|
||||
- Error boundaries
|
||||
- Recovery options
|
||||
|
||||
- **User Feedback**
|
||||
- Clear error messages
|
||||
- Actionable suggestions
|
||||
- Help documentation
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Component Usage
|
||||
|
||||
```python
|
||||
from twitter_streamlit_ui.components.cards import FeatureCard
|
||||
from twitter_streamlit_ui.components.forms import TweetForm
|
||||
from twitter_streamlit_ui.styles.theme import apply_theme
|
||||
|
||||
# Apply theme
|
||||
apply_theme()
|
||||
|
||||
# Use components
|
||||
feature_card = FeatureCard(
|
||||
title="Tweet Generator",
|
||||
description="Create engaging tweets with AI",
|
||||
icon="🐦"
|
||||
)
|
||||
feature_card.render()
|
||||
|
||||
tweet_form = TweetForm()
|
||||
tweet_form.render()
|
||||
```
|
||||
|
||||
### State Management
|
||||
|
||||
```python
|
||||
from twitter_streamlit_ui.utils.state import StateManager
|
||||
|
||||
# Initialize state
|
||||
state = StateManager()
|
||||
state.initialize()
|
||||
|
||||
# Update state
|
||||
state.update("current_tweet", tweet_data)
|
||||
```
|
||||
|
||||
### Error Handling
|
||||
|
||||
```python
|
||||
from twitter_streamlit_ui.components.feedback import ErrorBoundary
|
||||
|
||||
with ErrorBoundary():
|
||||
# Your code here
|
||||
pass
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Component Reusability**
|
||||
- Use existing components when possible
|
||||
- Create new components only when necessary
|
||||
- Follow the established patterns
|
||||
|
||||
2. **State Management**
|
||||
- Use the StateManager for all state
|
||||
- Avoid direct session state manipulation
|
||||
- Keep state updates atomic
|
||||
|
||||
3. **Performance**
|
||||
- Use lazy loading for heavy components
|
||||
- Implement caching where appropriate
|
||||
- Monitor render performance
|
||||
|
||||
4. **Accessibility**
|
||||
- Include ARIA labels
|
||||
- Ensure keyboard navigation
|
||||
- Test with screen readers
|
||||
|
||||
5. **Error Handling**
|
||||
- Use ErrorBoundary components
|
||||
- Provide clear error messages
|
||||
- Include recovery options
|
||||
|
||||
## Future Improvements
|
||||
|
||||
1. **Component Library**
|
||||
- Add more specialized components
|
||||
- Enhance existing components
|
||||
- Create component documentation
|
||||
|
||||
2. **Theme System**
|
||||
- Add more theme options
|
||||
- Implement theme switching
|
||||
- Create custom theme builder
|
||||
|
||||
3. **Performance**
|
||||
- Implement virtual scrolling
|
||||
- Add performance monitoring
|
||||
- Optimize resource loading
|
||||
|
||||
4. **Testing**
|
||||
- Add component tests
|
||||
- Implement E2E tests
|
||||
- Create test documentation
|
||||
|
||||
## Contributing
|
||||
|
||||
1. Follow the established patterns
|
||||
2. Add tests for new components
|
||||
3. Update documentation
|
||||
4. Ensure accessibility
|
||||
5. Optimize performance
|
||||
@@ -0,0 +1,66 @@
|
||||
"""
|
||||
Twitter Streamlit UI package.
|
||||
Provides a modern and user-friendly interface for Twitter tools.
|
||||
"""
|
||||
|
||||
from .dashboard import TwitterDashboard
|
||||
from .components.cards import FeatureCard, TweetCard
|
||||
from .components.forms import TweetForm, SettingsForm
|
||||
from .components.navigation import Sidebar, Header, Tabs, Breadcrumbs
|
||||
from .styles.theme import Theme
|
||||
from .utils.helpers import (
|
||||
save_to_session,
|
||||
get_from_session,
|
||||
clear_session,
|
||||
save_to_file,
|
||||
load_from_file,
|
||||
format_datetime,
|
||||
parse_datetime,
|
||||
validate_tweet_content,
|
||||
validate_hashtags,
|
||||
validate_emojis,
|
||||
calculate_engagement_score,
|
||||
generate_tweet_metrics,
|
||||
copy_to_clipboard,
|
||||
show_success_message,
|
||||
show_error_message,
|
||||
show_info_message,
|
||||
show_warning_message,
|
||||
create_download_button,
|
||||
create_upload_button
|
||||
)
|
||||
|
||||
__version__ = "1.0.0"
|
||||
__author__ = "AI Writer Team"
|
||||
|
||||
__all__ = [
|
||||
"TwitterDashboard",
|
||||
"FeatureCard",
|
||||
"TweetCard",
|
||||
"TweetForm",
|
||||
"SettingsForm",
|
||||
"Sidebar",
|
||||
"Header",
|
||||
"Tabs",
|
||||
"Breadcrumbs",
|
||||
"Theme",
|
||||
"save_to_session",
|
||||
"get_from_session",
|
||||
"clear_session",
|
||||
"save_to_file",
|
||||
"load_from_file",
|
||||
"format_datetime",
|
||||
"parse_datetime",
|
||||
"validate_tweet_content",
|
||||
"validate_hashtags",
|
||||
"validate_emojis",
|
||||
"calculate_engagement_score",
|
||||
"generate_tweet_metrics",
|
||||
"copy_to_clipboard",
|
||||
"show_success_message",
|
||||
"show_error_message",
|
||||
"show_info_message",
|
||||
"show_warning_message",
|
||||
"create_download_button",
|
||||
"create_upload_button"
|
||||
]
|
||||
@@ -0,0 +1,174 @@
|
||||
"""
|
||||
Card components for Twitter UI.
|
||||
Provides consistent card layouts for features and tweets.
|
||||
"""
|
||||
|
||||
import streamlit as st
|
||||
from typing import Dict, Any, Optional, List
|
||||
from ..styles.theme import Theme
|
||||
|
||||
class BaseCard:
|
||||
"""Base class for all card components."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
title: str,
|
||||
description: str,
|
||||
icon: Optional[str] = None,
|
||||
status: Optional[str] = None,
|
||||
actions: Optional[List[Dict[str, Any]]] = None
|
||||
):
|
||||
self.title = title
|
||||
self.description = description
|
||||
self.icon = icon
|
||||
self.status = status
|
||||
self.actions = actions or []
|
||||
|
||||
def render(self) -> None:
|
||||
"""Render the card with consistent styling."""
|
||||
with st.container():
|
||||
st.markdown(f"""
|
||||
<div class="card">
|
||||
<div style="display: flex; align-items: center; margin-bottom: {Theme.SPACING["sm"]};">
|
||||
{f'<span style="font-size: 1.5em; margin-right: {Theme.SPACING["sm"]};">{self.icon}</span>' if self.icon else ''}
|
||||
<h3 style="margin: 0;">{self.title}</h3>
|
||||
</div>
|
||||
<p style="color: {Theme.COLORS["text_secondary"]}; margin: {Theme.SPACING["sm"]} 0;">
|
||||
{self.description}
|
||||
</p>
|
||||
{f'<span class="status-badge status-{self.status}">{self.status.title()}</span>' if self.status else ''}
|
||||
</div>
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
if self.actions:
|
||||
cols = st.columns(len(self.actions))
|
||||
for i, action in enumerate(self.actions):
|
||||
with cols[i]:
|
||||
if st.button(
|
||||
action["label"],
|
||||
key=f"action_{i}",
|
||||
help=action.get("help"),
|
||||
use_container_width=True
|
||||
):
|
||||
action["callback"]()
|
||||
|
||||
class FeatureCard(BaseCard):
|
||||
"""Card component for displaying features."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
title: str,
|
||||
description: str,
|
||||
icon: str,
|
||||
status: str = "active",
|
||||
features: Optional[List[Dict[str, Any]]] = None,
|
||||
on_click: Optional[callable] = None
|
||||
):
|
||||
super().__init__(title, description, icon, status)
|
||||
self.features = features or []
|
||||
self.on_click = on_click
|
||||
|
||||
def render(self) -> None:
|
||||
"""Render the feature card with enhanced styling."""
|
||||
with st.container():
|
||||
st.markdown(f"""
|
||||
<div class="card feature-card">
|
||||
<div style="display: flex; align-items: center; margin-bottom: {Theme.SPACING["sm"]};">
|
||||
<span style="font-size: 1.5em; margin-right: {Theme.SPACING["sm"]};">{self.icon}</span>
|
||||
<h3 style="margin: 0;">{self.title}</h3>
|
||||
</div>
|
||||
<p style="color: {Theme.COLORS["text_secondary"]}; margin: {Theme.SPACING["sm"]} 0;">
|
||||
{self.description}
|
||||
</p>
|
||||
<span class="status-badge status-{self.status}">{self.status.title()}</span>
|
||||
</div>
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
if self.features:
|
||||
for feature in self.features:
|
||||
st.markdown(f"""
|
||||
<div style="margin-left: {Theme.SPACING["lg"]}; margin-top: {Theme.SPACING["sm"]};">
|
||||
<p style="margin: 0;">
|
||||
<strong>{feature["name"]}</strong>: {feature["description"]}
|
||||
</p>
|
||||
</div>
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
if self.on_click:
|
||||
if st.button(
|
||||
f"Launch {self.title}",
|
||||
key=f"launch_{self.title.lower().replace(' ', '_')}",
|
||||
use_container_width=True
|
||||
):
|
||||
self.on_click()
|
||||
|
||||
class TweetCard(BaseCard):
|
||||
"""Card component for displaying tweets."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
content: str,
|
||||
engagement_score: float,
|
||||
hashtags: List[str],
|
||||
emojis: List[str],
|
||||
metrics: Optional[Dict[str, Any]] = None,
|
||||
on_copy: Optional[callable] = None,
|
||||
on_save: Optional[callable] = None
|
||||
):
|
||||
super().__init__(
|
||||
title="Tweet",
|
||||
description=content,
|
||||
icon="🐦",
|
||||
actions=[
|
||||
{
|
||||
"label": "Copy",
|
||||
"callback": on_copy or (lambda: None),
|
||||
"help": "Copy tweet to clipboard"
|
||||
},
|
||||
{
|
||||
"label": "Save",
|
||||
"callback": on_save or (lambda: None),
|
||||
"help": "Save tweet for later"
|
||||
}
|
||||
]
|
||||
)
|
||||
self.engagement_score = engagement_score
|
||||
self.hashtags = hashtags
|
||||
self.emojis = emojis
|
||||
self.metrics = metrics or {}
|
||||
|
||||
def render(self) -> None:
|
||||
"""Render the tweet card with metrics and actions."""
|
||||
with st.container():
|
||||
st.markdown(f"""
|
||||
<div class="card tweet-card">
|
||||
<div style="display: flex; align-items: center; margin-bottom: {Theme.SPACING["sm"]};">
|
||||
<span style="font-size: 1.5em; margin-right: {Theme.SPACING["sm"]};">{self.icon}</span>
|
||||
<h3 style="margin: 0;">Tweet</h3>
|
||||
</div>
|
||||
<p style="color: {Theme.COLORS["text"]}; margin: {Theme.SPACING["sm"]} 0;">
|
||||
{self.description}
|
||||
</p>
|
||||
<div style="display: flex; gap: {Theme.SPACING["sm"]}; margin: {Theme.SPACING["sm"]} 0;">
|
||||
{''.join(f'<span style="color: {Theme.COLORS["primary"]};">{tag}</span>' for tag in self.hashtags)}
|
||||
</div>
|
||||
<div style="display: flex; gap: {Theme.SPACING["sm"]}; margin: {Theme.SPACING["sm"]} 0;">
|
||||
{''.join(f'<span>{emoji}</span>' for emoji in self.emojis)}
|
||||
</div>
|
||||
<div style="margin-top: {Theme.SPACING["md"]};">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center;">
|
||||
<span>Engagement Score: {self.engagement_score}%</span>
|
||||
<div style="display: flex; gap: {Theme.SPACING["sm"]};">
|
||||
<button class="stButton" onclick="copyTweet()">Copy</button>
|
||||
<button class="stButton" onclick="saveTweet()">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
if self.metrics:
|
||||
cols = st.columns(len(self.metrics))
|
||||
for i, (metric, value) in enumerate(self.metrics.items()):
|
||||
with cols[i]:
|
||||
st.metric(metric, f"{value}%")
|
||||
@@ -0,0 +1,255 @@
|
||||
"""
|
||||
Form components for Twitter UI.
|
||||
Provides consistent form layouts and input validation.
|
||||
"""
|
||||
|
||||
import streamlit as st
|
||||
from typing import Dict, Any, Optional, List, Callable
|
||||
from ..styles.theme import Theme
|
||||
|
||||
class BaseForm:
|
||||
"""Base class for all form components."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
title: str,
|
||||
description: Optional[str] = None,
|
||||
on_submit: Optional[Callable] = None
|
||||
):
|
||||
self.title = title
|
||||
self.description = description
|
||||
self.on_submit = on_submit
|
||||
self.fields: Dict[str, Any] = {}
|
||||
|
||||
def add_field(
|
||||
self,
|
||||
key: str,
|
||||
label: str,
|
||||
field_type: str = "text",
|
||||
required: bool = False,
|
||||
help_text: Optional[str] = None,
|
||||
options: Optional[List[str]] = None,
|
||||
default: Any = None,
|
||||
validation: Optional[Callable] = None
|
||||
) -> None:
|
||||
"""Add a field to the form."""
|
||||
self.fields[key] = {
|
||||
"label": label,
|
||||
"type": field_type,
|
||||
"required": required,
|
||||
"help_text": help_text,
|
||||
"options": options,
|
||||
"default": default,
|
||||
"validation": validation
|
||||
}
|
||||
|
||||
def validate(self) -> bool:
|
||||
"""Validate all form fields."""
|
||||
for key, field in self.fields.items():
|
||||
if field["required"] and not st.session_state.get(key):
|
||||
st.error(f"{field['label']} is required")
|
||||
return False
|
||||
if field["validation"] and not field["validation"](st.session_state.get(key)):
|
||||
return False
|
||||
return True
|
||||
|
||||
def render(self) -> None:
|
||||
"""Render the form with consistent styling."""
|
||||
with st.container():
|
||||
st.markdown(f"""
|
||||
<div class="form-container">
|
||||
<h3 style="margin-bottom: {Theme.SPACING['sm']};">{self.title}</h3>
|
||||
{f'<p style="color: {Theme.COLORS["text_secondary"]}; margin-bottom: {Theme.SPACING["md"]};">{self.description}</p>' if self.description else ''}
|
||||
</div>
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
for key, field in self.fields.items():
|
||||
if field["type"] == "text":
|
||||
st.text_input(
|
||||
field["label"],
|
||||
key=key,
|
||||
help=field["help_text"],
|
||||
value=field["default"]
|
||||
)
|
||||
elif field["type"] == "textarea":
|
||||
st.text_area(
|
||||
field["label"],
|
||||
key=key,
|
||||
help=field["help_text"],
|
||||
value=field["default"]
|
||||
)
|
||||
elif field["type"] == "select":
|
||||
st.selectbox(
|
||||
field["label"],
|
||||
options=field["options"],
|
||||
key=key,
|
||||
help=field["help_text"],
|
||||
index=field["options"].index(field["default"]) if field["default"] in field["options"] else 0
|
||||
)
|
||||
elif field["type"] == "multiselect":
|
||||
st.multiselect(
|
||||
field["label"],
|
||||
options=field["options"],
|
||||
key=key,
|
||||
help=field["help_text"],
|
||||
default=field["default"]
|
||||
)
|
||||
elif field["type"] == "number":
|
||||
st.number_input(
|
||||
field["label"],
|
||||
key=key,
|
||||
help=field["help_text"],
|
||||
value=field["default"]
|
||||
)
|
||||
elif field["type"] == "slider":
|
||||
st.slider(
|
||||
field["label"],
|
||||
key=key,
|
||||
help=field["help_text"],
|
||||
value=field["default"]
|
||||
)
|
||||
elif field["type"] == "checkbox":
|
||||
st.checkbox(
|
||||
field["label"],
|
||||
key=key,
|
||||
help=field["help_text"],
|
||||
value=field["default"]
|
||||
)
|
||||
|
||||
if st.button("Submit", use_container_width=True):
|
||||
if self.validate() and self.on_submit:
|
||||
self.on_submit()
|
||||
|
||||
class TweetForm(BaseForm):
|
||||
"""Form component for tweet generation."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
on_submit: Optional[Callable] = None,
|
||||
default_tone: str = "professional",
|
||||
default_length: str = "medium"
|
||||
):
|
||||
super().__init__(
|
||||
title="Generate Tweet",
|
||||
description="Create engaging tweets with AI assistance",
|
||||
on_submit=on_submit
|
||||
)
|
||||
|
||||
# Add tweet content field
|
||||
self.add_field(
|
||||
"tweet_content",
|
||||
"Tweet Content",
|
||||
field_type="textarea",
|
||||
required=True,
|
||||
help_text="Enter your tweet content or topic"
|
||||
)
|
||||
|
||||
# Add tone selection
|
||||
self.add_field(
|
||||
"tone",
|
||||
"Tweet Tone",
|
||||
field_type="select",
|
||||
options=["professional", "casual", "humorous", "informative", "inspirational"],
|
||||
default=default_tone,
|
||||
help_text="Select the tone for your tweet"
|
||||
)
|
||||
|
||||
# Add length selection
|
||||
self.add_field(
|
||||
"length",
|
||||
"Tweet Length",
|
||||
field_type="select",
|
||||
options=["short", "medium", "long"],
|
||||
default=default_length,
|
||||
help_text="Select the desired length of your tweet"
|
||||
)
|
||||
|
||||
# Add hashtag options
|
||||
self.add_field(
|
||||
"hashtags",
|
||||
"Hashtags",
|
||||
field_type="multiselect",
|
||||
options=["#AI", "#Tech", "#Innovation", "#Business", "#Marketing"],
|
||||
help_text="Select relevant hashtags"
|
||||
)
|
||||
|
||||
# Add emoji options
|
||||
self.add_field(
|
||||
"emojis",
|
||||
"Emojis",
|
||||
field_type="multiselect",
|
||||
options=["🚀", "💡", "🎯", "🔥", "✨"],
|
||||
help_text="Select emojis to include"
|
||||
)
|
||||
|
||||
# Add engagement settings
|
||||
self.add_field(
|
||||
"engagement_boost",
|
||||
"Engagement Boost",
|
||||
field_type="slider",
|
||||
default=50,
|
||||
help_text="Adjust the engagement optimization level"
|
||||
)
|
||||
|
||||
class SettingsForm(BaseForm):
|
||||
"""Form component for user settings."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
on_submit: Optional[Callable] = None,
|
||||
default_settings: Optional[Dict[str, Any]] = None
|
||||
):
|
||||
super().__init__(
|
||||
title="User Settings",
|
||||
description="Customize your Twitter experience",
|
||||
on_submit=on_submit
|
||||
)
|
||||
|
||||
settings = default_settings or {}
|
||||
|
||||
# Add API settings
|
||||
self.add_field(
|
||||
"api_key",
|
||||
"Twitter API Key",
|
||||
field_type="text",
|
||||
help_text="Enter your Twitter API key",
|
||||
default=settings.get("api_key", "")
|
||||
)
|
||||
|
||||
# Add theme settings
|
||||
self.add_field(
|
||||
"theme",
|
||||
"Theme",
|
||||
field_type="select",
|
||||
options=["light", "dark", "system"],
|
||||
default=settings.get("theme", "system"),
|
||||
help_text="Select your preferred theme"
|
||||
)
|
||||
|
||||
# Add notification settings
|
||||
self.add_field(
|
||||
"notifications",
|
||||
"Enable Notifications",
|
||||
field_type="checkbox",
|
||||
default=settings.get("notifications", True),
|
||||
help_text="Receive notifications for important updates"
|
||||
)
|
||||
|
||||
# Add auto-save settings
|
||||
self.add_field(
|
||||
"auto_save",
|
||||
"Auto-save Drafts",
|
||||
field_type="checkbox",
|
||||
default=settings.get("auto_save", True),
|
||||
help_text="Automatically save tweet drafts"
|
||||
)
|
||||
|
||||
# Add language settings
|
||||
self.add_field(
|
||||
"language",
|
||||
"Language",
|
||||
field_type="select",
|
||||
options=["English", "Spanish", "French", "German", "Japanese"],
|
||||
default=settings.get("language", "English"),
|
||||
help_text="Select your preferred language"
|
||||
)
|
||||
@@ -0,0 +1,222 @@
|
||||
"""
|
||||
Navigation components for Twitter UI.
|
||||
Provides consistent navigation and layout structure.
|
||||
"""
|
||||
|
||||
import streamlit as st
|
||||
from typing import Dict, Any, Optional, List
|
||||
from ..styles.theme import Theme
|
||||
|
||||
class Sidebar:
|
||||
"""Sidebar navigation component."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
title: str = "Twitter Tools",
|
||||
logo: Optional[str] = None,
|
||||
menu_items: Optional[List[Dict[str, Any]]] = None
|
||||
):
|
||||
self.title = title
|
||||
self.logo = logo
|
||||
self.menu_items = menu_items or []
|
||||
|
||||
def add_menu_item(
|
||||
self,
|
||||
label: str,
|
||||
icon: str,
|
||||
page: str,
|
||||
badge: Optional[str] = None
|
||||
) -> None:
|
||||
"""Add a menu item to the sidebar."""
|
||||
self.menu_items.append({
|
||||
"label": label,
|
||||
"icon": icon,
|
||||
"page": page,
|
||||
"badge": badge
|
||||
})
|
||||
|
||||
def render(self) -> None:
|
||||
"""Render the sidebar with consistent styling."""
|
||||
with st.sidebar:
|
||||
# Logo and title
|
||||
if self.logo:
|
||||
st.image(self.logo, width=50)
|
||||
st.markdown(f"""
|
||||
<h2 style="margin: {Theme.SPACING["sm"]} 0;">{self.title}</h2>
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
# Menu items
|
||||
for item in self.menu_items:
|
||||
st.markdown(f"""
|
||||
<div class="menu-item">
|
||||
<span style="font-size: 1.2em; margin-right: {Theme.SPACING["sm"]};">{item["icon"]}</span>
|
||||
<span>{item["label"]}</span>
|
||||
{f'<span class="badge">{item["badge"]}</span>' if item.get("badge") else ""}
|
||||
</div>
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
if st.button(
|
||||
item["label"],
|
||||
key=f"nav_{item['page']}",
|
||||
use_container_width=True
|
||||
):
|
||||
st.session_state["current_page"] = item["page"]
|
||||
|
||||
class Header:
|
||||
"""Header navigation component."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
title: str,
|
||||
subtitle: Optional[str] = None,
|
||||
actions: Optional[List[Dict[str, Any]]] = None
|
||||
):
|
||||
self.title = title
|
||||
self.subtitle = subtitle
|
||||
self.actions = actions or []
|
||||
|
||||
def add_action(
|
||||
self,
|
||||
label: str,
|
||||
icon: str,
|
||||
callback: callable,
|
||||
help_text: Optional[str] = None
|
||||
) -> None:
|
||||
"""Add an action button to the header."""
|
||||
self.actions.append({
|
||||
"label": label,
|
||||
"icon": icon,
|
||||
"callback": callback,
|
||||
"help_text": help_text
|
||||
})
|
||||
|
||||
def render(self) -> None:
|
||||
"""Render the header with consistent styling."""
|
||||
# Build action buttons HTML
|
||||
action_buttons = []
|
||||
for action in self.actions:
|
||||
help_text = action.get("help_text", "")
|
||||
action_buttons.append(f"""
|
||||
<button class="header-action" title="{help_text}">
|
||||
<span style="font-size: 1.2em; margin-right: {Theme.SPACING["xs"]};">{action["icon"]}</span>
|
||||
{action["label"]}
|
||||
</button>
|
||||
""")
|
||||
|
||||
st.markdown(f"""
|
||||
<div class="header">
|
||||
<div>
|
||||
<h1 style="margin: 0;">{self.title}</h1>
|
||||
{f'<p style="color: {Theme.COLORS["text_secondary"]}; margin: {Theme.SPACING["xs"]} 0;">{self.subtitle}</p>' if self.subtitle else ""}
|
||||
</div>
|
||||
<div style="display: flex; gap: {Theme.SPACING["sm"]};">
|
||||
{''.join(action_buttons)}
|
||||
</div>
|
||||
</div>
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
# Add action button callbacks
|
||||
for i, action in enumerate(self.actions):
|
||||
if st.button(
|
||||
action["label"],
|
||||
key=f"header_action_{i}",
|
||||
help=action.get("help_text")
|
||||
):
|
||||
action["callback"]()
|
||||
|
||||
class Tabs:
|
||||
"""Tab navigation component."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
tabs: Optional[List[Dict[str, Any]]] = None,
|
||||
default_tab: Optional[str] = None
|
||||
):
|
||||
self.tabs = tabs or []
|
||||
self.default_tab = default_tab
|
||||
|
||||
def add_tab(
|
||||
self,
|
||||
label: str,
|
||||
icon: Optional[str] = None,
|
||||
content: Optional[callable] = None
|
||||
) -> None:
|
||||
"""Add a tab to the navigation."""
|
||||
self.tabs.append({
|
||||
"label": label,
|
||||
"icon": icon,
|
||||
"content": content
|
||||
})
|
||||
|
||||
def render(self) -> None:
|
||||
"""Render the tabs with consistent styling."""
|
||||
if not self.tabs:
|
||||
return
|
||||
|
||||
# Create tab labels with icons
|
||||
tab_labels = [
|
||||
f"{tab['icon']} {tab['label']}" if tab.get('icon') else tab['label']
|
||||
for tab in self.tabs
|
||||
]
|
||||
|
||||
# Get current tab from session state or use default
|
||||
current_tab = st.session_state.get("current_tab", self.default_tab or self.tabs[0]["label"])
|
||||
|
||||
# Render tabs
|
||||
selected_tab = st.tabs(tab_labels)[tab_labels.index(current_tab)]
|
||||
|
||||
# Update session state
|
||||
st.session_state["current_tab"] = current_tab
|
||||
|
||||
# Render tab content
|
||||
with selected_tab:
|
||||
for tab in self.tabs:
|
||||
if tab["label"] == current_tab and tab.get("content"):
|
||||
tab["content"]()
|
||||
|
||||
class Breadcrumbs:
|
||||
"""Breadcrumb navigation component."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
items: Optional[List[Dict[str, Any]]] = None
|
||||
):
|
||||
self.items = items or []
|
||||
|
||||
def add_item(
|
||||
self,
|
||||
label: str,
|
||||
page: Optional[str] = None,
|
||||
icon: Optional[str] = None
|
||||
) -> None:
|
||||
"""Add a breadcrumb item."""
|
||||
self.items.append({
|
||||
"label": label,
|
||||
"page": page,
|
||||
"icon": icon
|
||||
})
|
||||
|
||||
def render(self) -> None:
|
||||
"""Render the breadcrumbs with consistent styling."""
|
||||
if not self.items:
|
||||
return
|
||||
|
||||
breadcrumb_items = []
|
||||
for i, item in enumerate(self.items):
|
||||
icon_html = f'<span style="font-size: 1.2em; margin-right: {Theme.SPACING["xs"]};">{item["icon"]}</span>' if item.get("icon") else ""
|
||||
link_html = f'<a href="#" onclick="setPage(\'{item["page"]}\')">{item["label"]}</a>' if item.get("page") else f'<span>{item["label"]}</span>'
|
||||
separator = f'<span style="margin: 0 {Theme.SPACING["xs"]};">/</span>' if i < len(self.items) - 1 else ""
|
||||
|
||||
breadcrumb_items.append(f"""
|
||||
<span class="breadcrumb-item">
|
||||
{icon_html}
|
||||
{link_html}
|
||||
</span>
|
||||
{separator}
|
||||
""")
|
||||
|
||||
st.markdown(f"""
|
||||
<div class="breadcrumbs">
|
||||
{''.join(breadcrumb_items)}
|
||||
</div>
|
||||
""", unsafe_allow_html=True)
|
||||
270
lib/ai_writers/twitter_writers/twitter_streamlit_ui/dashboard.py
Normal file
270
lib/ai_writers/twitter_writers/twitter_streamlit_ui/dashboard.py
Normal file
@@ -0,0 +1,270 @@
|
||||
"""
|
||||
Main dashboard for Twitter UI.
|
||||
Combines all UI components into a cohesive interface.
|
||||
"""
|
||||
|
||||
import streamlit as st
|
||||
from typing import Dict, Any, Optional
|
||||
from .components.cards import FeatureCard, TweetCard
|
||||
from .components.forms import TweetForm, SettingsForm
|
||||
from .components.navigation import Sidebar, Header, Tabs, Breadcrumbs
|
||||
from .styles.theme import Theme
|
||||
|
||||
class TwitterDashboard:
|
||||
"""Main dashboard class for Twitter UI."""
|
||||
|
||||
def __init__(self):
|
||||
self.setup_page()
|
||||
self.setup_theme()
|
||||
self.setup_navigation()
|
||||
self.setup_state()
|
||||
|
||||
def setup_page(self) -> None:
|
||||
"""Configure the Streamlit page settings."""
|
||||
st.set_page_config(
|
||||
page_title="Twitter Tools",
|
||||
page_icon="🐦",
|
||||
layout="wide",
|
||||
initial_sidebar_state="expanded"
|
||||
)
|
||||
|
||||
def setup_theme(self) -> None:
|
||||
"""Apply the theme to the dashboard."""
|
||||
Theme().apply()
|
||||
|
||||
def setup_navigation(self) -> None:
|
||||
"""Setup navigation components."""
|
||||
# Sidebar
|
||||
self.sidebar = Sidebar(
|
||||
title="Twitter Tools",
|
||||
logo="assets/logo.png"
|
||||
)
|
||||
|
||||
# Add menu items
|
||||
self.sidebar.add_menu_item("Dashboard", "📊", "dashboard")
|
||||
self.sidebar.add_menu_item("Tweet Generator", "✍️", "tweet_generator")
|
||||
self.sidebar.add_menu_item("Analytics", "📈", "analytics")
|
||||
self.sidebar.add_menu_item("Settings", "⚙️", "settings")
|
||||
|
||||
# Header
|
||||
self.header = Header(
|
||||
title="Twitter Dashboard",
|
||||
subtitle="Create and manage your Twitter content"
|
||||
)
|
||||
|
||||
# Add header actions
|
||||
self.header.add_action(
|
||||
"New Tweet",
|
||||
"✏️",
|
||||
self.create_new_tweet,
|
||||
"Create a new tweet"
|
||||
)
|
||||
self.header.add_action(
|
||||
"Refresh",
|
||||
"🔄",
|
||||
self.refresh_dashboard,
|
||||
"Refresh dashboard data"
|
||||
)
|
||||
|
||||
# Tabs
|
||||
self.tabs = Tabs()
|
||||
|
||||
# Add tabs
|
||||
self.tabs.add_tab("Overview", "📊", self.render_overview)
|
||||
self.tabs.add_tab("Recent Tweets", "🐦", self.render_recent_tweets)
|
||||
self.tabs.add_tab("Analytics", "📈", self.render_analytics)
|
||||
|
||||
# Breadcrumbs
|
||||
self.breadcrumbs = Breadcrumbs()
|
||||
|
||||
def setup_state(self) -> None:
|
||||
"""Initialize session state variables."""
|
||||
if "current_page" not in st.session_state:
|
||||
st.session_state["current_page"] = "dashboard"
|
||||
if "current_tab" not in st.session_state:
|
||||
st.session_state["current_tab"] = "Overview"
|
||||
if "tweets" not in st.session_state:
|
||||
st.session_state["tweets"] = []
|
||||
|
||||
def create_new_tweet(self) -> None:
|
||||
"""Handle new tweet creation."""
|
||||
st.session_state["current_page"] = "tweet_generator"
|
||||
|
||||
def refresh_dashboard(self) -> None:
|
||||
"""Refresh dashboard data."""
|
||||
st.experimental_rerun()
|
||||
|
||||
def render_overview(self) -> None:
|
||||
"""Render the overview tab content."""
|
||||
# Feature cards
|
||||
col1, col2, col3 = st.columns(3)
|
||||
|
||||
with col1:
|
||||
FeatureCard(
|
||||
title="Tweet Generator",
|
||||
description="Create engaging tweets with AI assistance",
|
||||
icon="✍️",
|
||||
features=[
|
||||
{
|
||||
"name": "AI-Powered",
|
||||
"description": "Generate tweets using advanced AI"
|
||||
},
|
||||
{
|
||||
"name": "Customizable",
|
||||
"description": "Adjust tone, length, and style"
|
||||
}
|
||||
],
|
||||
on_click=self.create_new_tweet
|
||||
).render()
|
||||
|
||||
with col2:
|
||||
FeatureCard(
|
||||
title="Analytics",
|
||||
description="Track your tweet performance",
|
||||
icon="📈",
|
||||
features=[
|
||||
{
|
||||
"name": "Engagement",
|
||||
"description": "Monitor likes, retweets, and replies"
|
||||
},
|
||||
{
|
||||
"name": "Growth",
|
||||
"description": "Track follower growth over time"
|
||||
}
|
||||
]
|
||||
).render()
|
||||
|
||||
with col3:
|
||||
FeatureCard(
|
||||
title="Settings",
|
||||
description="Customize your experience",
|
||||
icon="⚙️",
|
||||
features=[
|
||||
{
|
||||
"name": "Preferences",
|
||||
"description": "Set your default options"
|
||||
},
|
||||
{
|
||||
"name": "API",
|
||||
"description": "Configure Twitter API settings"
|
||||
}
|
||||
]
|
||||
).render()
|
||||
|
||||
def render_recent_tweets(self) -> None:
|
||||
"""Render the recent tweets tab content."""
|
||||
# Tweet form
|
||||
tweet_form = TweetForm(
|
||||
on_submit=self.handle_tweet_submit
|
||||
)
|
||||
tweet_form.render()
|
||||
|
||||
# Recent tweets
|
||||
st.markdown("### Recent Tweets")
|
||||
|
||||
for tweet in st.session_state["tweets"]:
|
||||
TweetCard(
|
||||
content=tweet["content"],
|
||||
engagement_score=tweet["engagement_score"],
|
||||
hashtags=tweet["hashtags"],
|
||||
emojis=tweet["emojis"],
|
||||
metrics=tweet["metrics"],
|
||||
on_copy=lambda: self.copy_tweet(tweet),
|
||||
on_save=lambda: self.save_tweet(tweet)
|
||||
).render()
|
||||
|
||||
def render_analytics(self) -> None:
|
||||
"""Render the analytics tab content."""
|
||||
# Analytics content
|
||||
st.markdown("### Tweet Analytics")
|
||||
|
||||
# Placeholder for analytics charts
|
||||
st.info("Analytics features coming soon!")
|
||||
|
||||
def handle_tweet_submit(self) -> None:
|
||||
"""Handle tweet form submission."""
|
||||
# Get form data
|
||||
content = st.session_state["tweet_content"]
|
||||
tone = st.session_state["tone"]
|
||||
length = st.session_state["length"]
|
||||
hashtags = st.session_state["hashtags"]
|
||||
emojis = st.session_state["emojis"]
|
||||
engagement_boost = st.session_state["engagement_boost"]
|
||||
|
||||
# Create tweet object
|
||||
tweet = {
|
||||
"content": content,
|
||||
"tone": tone,
|
||||
"length": length,
|
||||
"hashtags": hashtags,
|
||||
"emojis": emojis,
|
||||
"engagement_score": engagement_boost,
|
||||
"metrics": {
|
||||
"Engagement": engagement_boost,
|
||||
"Reach": engagement_boost * 0.8,
|
||||
"Growth": engagement_boost * 0.6
|
||||
}
|
||||
}
|
||||
|
||||
# Add to tweets list
|
||||
st.session_state["tweets"].append(tweet)
|
||||
|
||||
# Show success message
|
||||
st.success("Tweet created successfully!")
|
||||
|
||||
def copy_tweet(self, tweet: Dict[str, Any]) -> None:
|
||||
"""Copy tweet to clipboard."""
|
||||
st.write("Tweet copied to clipboard!")
|
||||
|
||||
def save_tweet(self, tweet: Dict[str, Any]) -> None:
|
||||
"""Save tweet for later."""
|
||||
st.write("Tweet saved!")
|
||||
|
||||
def render(self) -> None:
|
||||
"""Render the complete dashboard."""
|
||||
# Render navigation
|
||||
self.sidebar.render()
|
||||
self.header.render()
|
||||
self.breadcrumbs.render()
|
||||
|
||||
# Render content based on current page
|
||||
if st.session_state["current_page"] == "dashboard":
|
||||
self.tabs.render()
|
||||
elif st.session_state["current_page"] == "tweet_generator":
|
||||
self.render_recent_tweets()
|
||||
elif st.session_state["current_page"] == "analytics":
|
||||
self.render_analytics()
|
||||
elif st.session_state["current_page"] == "settings":
|
||||
settings_form = SettingsForm(
|
||||
on_submit=self.handle_settings_submit
|
||||
)
|
||||
settings_form.render()
|
||||
|
||||
def handle_settings_submit(self) -> None:
|
||||
"""Handle settings form submission."""
|
||||
# Get form data
|
||||
api_key = st.session_state["api_key"]
|
||||
theme = st.session_state["theme"]
|
||||
notifications = st.session_state["notifications"]
|
||||
auto_save = st.session_state["auto_save"]
|
||||
language = st.session_state["language"]
|
||||
|
||||
# Save settings
|
||||
st.session_state["settings"] = {
|
||||
"api_key": api_key,
|
||||
"theme": theme,
|
||||
"notifications": notifications,
|
||||
"auto_save": auto_save,
|
||||
"language": language
|
||||
}
|
||||
|
||||
# Show success message
|
||||
st.success("Settings saved successfully!")
|
||||
|
||||
def main():
|
||||
"""Main entry point for the dashboard."""
|
||||
dashboard = TwitterDashboard()
|
||||
dashboard.render()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,173 @@
|
||||
"""
|
||||
Theme configuration for Twitter UI components.
|
||||
Provides consistent styling across all Twitter-related features.
|
||||
"""
|
||||
|
||||
import streamlit as st
|
||||
from typing import Dict, Any
|
||||
|
||||
class Theme:
|
||||
"""Theme configuration for Twitter UI components."""
|
||||
|
||||
# Color palette
|
||||
COLORS = {
|
||||
"primary": "#1DA1F2", # Twitter blue
|
||||
"secondary": "#14171A", # Dark blue
|
||||
"background": "#15202B", # Dark background
|
||||
"text": "#FFFFFF", # White text
|
||||
"text_secondary": "#8899A6", # Gray text
|
||||
"success": "#17BF63", # Green
|
||||
"warning": "#FFAD1F", # Yellow
|
||||
"error": "#E0245E", # Red
|
||||
"border": "rgba(255, 255, 255, 0.1)", # Subtle border
|
||||
}
|
||||
|
||||
# Typography
|
||||
TYPOGRAPHY = {
|
||||
"font_family": "'Helvetica Neue', sans-serif",
|
||||
"font_sizes": {
|
||||
"h1": "2.5rem",
|
||||
"h2": "2rem",
|
||||
"h3": "1.5rem",
|
||||
"body": "1rem",
|
||||
"small": "0.875rem",
|
||||
},
|
||||
"font_weights": {
|
||||
"regular": 400,
|
||||
"medium": 500,
|
||||
"bold": 700,
|
||||
},
|
||||
}
|
||||
|
||||
# Spacing
|
||||
SPACING = {
|
||||
"xs": "0.25rem",
|
||||
"sm": "0.5rem",
|
||||
"md": "1rem",
|
||||
"lg": "1.5rem",
|
||||
"xl": "2rem",
|
||||
}
|
||||
|
||||
# Border radius
|
||||
BORDER_RADIUS = {
|
||||
"sm": "4px",
|
||||
"md": "8px",
|
||||
"lg": "12px",
|
||||
"xl": "16px",
|
||||
"full": "9999px",
|
||||
}
|
||||
|
||||
# Shadows
|
||||
SHADOWS = {
|
||||
"sm": "0 1px 2px rgba(0, 0, 0, 0.05)",
|
||||
"md": "0 4px 6px rgba(0, 0, 0, 0.1)",
|
||||
"lg": "0 10px 15px rgba(0, 0, 0, 0.1)",
|
||||
"xl": "0 20px 25px rgba(0, 0, 0, 0.15)",
|
||||
}
|
||||
|
||||
# Transitions
|
||||
TRANSITIONS = {
|
||||
"fast": "0.15s ease",
|
||||
"normal": "0.3s ease",
|
||||
"slow": "0.5s ease",
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def get_css(cls) -> str:
|
||||
"""Get the complete CSS for the theme."""
|
||||
return f"""
|
||||
/* Base styles */
|
||||
.stApp {{
|
||||
background-color: {cls.COLORS['background']};
|
||||
color: {cls.COLORS['text']};
|
||||
font-family: {cls.TYPOGRAPHY['font_family']};
|
||||
}}
|
||||
|
||||
/* Typography */
|
||||
h1, h2, h3, h4, h5, h6 {{
|
||||
color: {cls.COLORS['text']};
|
||||
font-family: {cls.TYPOGRAPHY['font_family']};
|
||||
font-weight: {cls.TYPOGRAPHY['font_weights']['bold']};
|
||||
}}
|
||||
|
||||
/* Buttons */
|
||||
.stButton > button {{
|
||||
background: linear-gradient(45deg, {cls.COLORS['primary']}, #0C85D0);
|
||||
color: {cls.COLORS['text']};
|
||||
border: none;
|
||||
padding: {cls.SPACING['md']} {cls.SPACING['lg']};
|
||||
border-radius: {cls.BORDER_RADIUS['full']};
|
||||
font-weight: {cls.TYPOGRAPHY['font_weights']['medium']};
|
||||
transition: all {cls.TRANSITIONS['normal']};
|
||||
box-shadow: {cls.SHADOWS['md']};
|
||||
}}
|
||||
|
||||
.stButton > button:hover {{
|
||||
transform: translateY(-2px);
|
||||
box-shadow: {cls.SHADOWS['lg']};
|
||||
}}
|
||||
|
||||
/* Cards */
|
||||
.card {{
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border: 1px solid {cls.COLORS['border']};
|
||||
border-radius: {cls.BORDER_RADIUS['lg']};
|
||||
padding: {cls.SPACING['lg']};
|
||||
margin-bottom: {cls.SPACING['md']};
|
||||
backdrop-filter: blur(10px);
|
||||
transition: transform {cls.TRANSITIONS['normal']};
|
||||
}}
|
||||
|
||||
.card:hover {{
|
||||
transform: translateY(-4px);
|
||||
}}
|
||||
|
||||
/* Forms */
|
||||
.stTextInput > div > div > input {{
|
||||
background-color: rgba(255, 255, 255, 0.05);
|
||||
border: 1px solid {cls.COLORS['border']};
|
||||
border-radius: {cls.BORDER_RADIUS['md']};
|
||||
color: {cls.COLORS['text']};
|
||||
padding: {cls.SPACING['md']};
|
||||
}}
|
||||
|
||||
/* Tabs */
|
||||
.stTabs [data-baseweb="tab-list"] {{
|
||||
gap: {cls.SPACING['sm']};
|
||||
background-color: rgba(0, 0, 0, 0.2);
|
||||
padding: {cls.SPACING['md']};
|
||||
border-radius: {cls.BORDER_RADIUS['lg']};
|
||||
}}
|
||||
|
||||
.stTabs [data-baseweb="tab"] {{
|
||||
background-color: transparent;
|
||||
color: {cls.COLORS['text']};
|
||||
border: 1px solid {cls.COLORS['border']};
|
||||
border-radius: {cls.BORDER_RADIUS['md']};
|
||||
padding: {cls.SPACING['sm']} {cls.SPACING['md']};
|
||||
}}
|
||||
|
||||
/* Status badges */
|
||||
.status-badge {{
|
||||
display: inline-block;
|
||||
padding: {cls.SPACING['xs']} {cls.SPACING['md']};
|
||||
border-radius: {cls.BORDER_RADIUS['full']};
|
||||
font-size: {cls.TYPOGRAPHY['font_sizes']['small']};
|
||||
font-weight: {cls.TYPOGRAPHY['font_weights']['medium']};
|
||||
}}
|
||||
|
||||
.status-active {{
|
||||
background: linear-gradient(45deg, {cls.COLORS['success']}, #69F0AE);
|
||||
color: {cls.COLORS['secondary']};
|
||||
}}
|
||||
|
||||
.status-coming-soon {{
|
||||
background: linear-gradient(45deg, {cls.COLORS['warning']}, #FFA000);
|
||||
color: {cls.COLORS['secondary']};
|
||||
}}
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def apply(cls) -> None:
|
||||
"""Apply the theme to the Streamlit app."""
|
||||
st.markdown(f"<style>{cls.get_css()}</style>", unsafe_allow_html=True)
|
||||
@@ -0,0 +1,194 @@
|
||||
"""
|
||||
Utility functions for Twitter UI.
|
||||
Provides helper functions for common operations.
|
||||
"""
|
||||
|
||||
import streamlit as st
|
||||
from typing import Dict, Any, List, Optional
|
||||
import json
|
||||
import os
|
||||
from datetime import datetime
|
||||
|
||||
def save_to_session(key: str, value: Any) -> None:
|
||||
"""Save a value to the session state."""
|
||||
st.session_state[key] = value
|
||||
|
||||
def get_from_session(key: str, default: Any = None) -> Any:
|
||||
"""Get a value from the session state."""
|
||||
return st.session_state.get(key, default)
|
||||
|
||||
def clear_session() -> None:
|
||||
"""Clear all session state variables."""
|
||||
for key in list(st.session_state.keys()):
|
||||
del st.session_state[key]
|
||||
|
||||
def save_to_file(data: Dict[str, Any], filename: str) -> None:
|
||||
"""Save data to a JSON file."""
|
||||
try:
|
||||
with open(filename, 'w') as f:
|
||||
json.dump(data, f, indent=4)
|
||||
except Exception as e:
|
||||
st.error(f"Error saving data: {str(e)}")
|
||||
|
||||
def load_from_file(filename: str) -> Optional[Dict[str, Any]]:
|
||||
"""Load data from a JSON file."""
|
||||
try:
|
||||
if os.path.exists(filename):
|
||||
with open(filename, 'r') as f:
|
||||
return json.load(f)
|
||||
except Exception as e:
|
||||
st.error(f"Error loading data: {str(e)}")
|
||||
return None
|
||||
|
||||
def format_datetime(dt: datetime) -> str:
|
||||
"""Format a datetime object for display."""
|
||||
return dt.strftime("%Y-%m-%d %H:%M:%S")
|
||||
|
||||
def parse_datetime(dt_str: str) -> Optional[datetime]:
|
||||
"""Parse a datetime string."""
|
||||
try:
|
||||
return datetime.strptime(dt_str, "%Y-%m-%d %H:%M:%S")
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
def validate_tweet_content(content: str) -> bool:
|
||||
"""Validate tweet content."""
|
||||
if not content:
|
||||
st.error("Tweet content cannot be empty")
|
||||
return False
|
||||
if len(content) > 280:
|
||||
st.error("Tweet content cannot exceed 280 characters")
|
||||
return False
|
||||
return True
|
||||
|
||||
def validate_hashtags(hashtags: List[str]) -> bool:
|
||||
"""Validate hashtags."""
|
||||
for tag in hashtags:
|
||||
if not tag.startswith('#'):
|
||||
st.error(f"Hashtag {tag} must start with #")
|
||||
return False
|
||||
if len(tag) > 30:
|
||||
st.error(f"Hashtag {tag} cannot exceed 30 characters")
|
||||
return False
|
||||
return True
|
||||
|
||||
def validate_emojis(emojis: List[str]) -> bool:
|
||||
"""Validate emojis."""
|
||||
for emoji in emojis:
|
||||
if len(emoji) != 1:
|
||||
st.error(f"Invalid emoji: {emoji}")
|
||||
return False
|
||||
return True
|
||||
|
||||
def calculate_engagement_score(
|
||||
content: str,
|
||||
hashtags: List[str],
|
||||
emojis: List[str],
|
||||
tone: str
|
||||
) -> float:
|
||||
"""Calculate engagement score for a tweet."""
|
||||
score = 0.0
|
||||
|
||||
# Content length score (optimal length is 100-150 characters)
|
||||
content_length = len(content)
|
||||
if 100 <= content_length <= 150:
|
||||
score += 30
|
||||
elif 50 <= content_length <= 200:
|
||||
score += 20
|
||||
else:
|
||||
score += 10
|
||||
|
||||
# Hashtag score (optimal number is 2-3 hashtags)
|
||||
hashtag_count = len(hashtags)
|
||||
if 2 <= hashtag_count <= 3:
|
||||
score += 20
|
||||
elif 1 <= hashtag_count <= 4:
|
||||
score += 15
|
||||
else:
|
||||
score += 5
|
||||
|
||||
# Emoji score (optimal number is 1-2 emojis)
|
||||
emoji_count = len(emojis)
|
||||
if 1 <= emoji_count <= 2:
|
||||
score += 20
|
||||
elif 0 <= emoji_count <= 3:
|
||||
score += 15
|
||||
else:
|
||||
score += 5
|
||||
|
||||
# Tone score
|
||||
tone_scores = {
|
||||
"professional": 15,
|
||||
"casual": 20,
|
||||
"humorous": 25,
|
||||
"informative": 15,
|
||||
"inspirational": 20
|
||||
}
|
||||
score += tone_scores.get(tone, 10)
|
||||
|
||||
return min(score, 100)
|
||||
|
||||
def generate_tweet_metrics(engagement_score: float) -> Dict[str, float]:
|
||||
"""Generate metrics for a tweet based on engagement score."""
|
||||
return {
|
||||
"Engagement": engagement_score,
|
||||
"Reach": engagement_score * 0.8,
|
||||
"Growth": engagement_score * 0.6
|
||||
}
|
||||
|
||||
def copy_to_clipboard(text: str) -> None:
|
||||
"""Copy text to clipboard."""
|
||||
try:
|
||||
st.write(f'<script>navigator.clipboard.writeText("{text}")</script>', unsafe_allow_html=True)
|
||||
except Exception as e:
|
||||
st.error(f"Error copying to clipboard: {str(e)}")
|
||||
|
||||
def show_success_message(message: str) -> None:
|
||||
"""Show a success message."""
|
||||
st.success(message)
|
||||
|
||||
def show_error_message(message: str) -> None:
|
||||
"""Show an error message."""
|
||||
st.error(message)
|
||||
|
||||
def show_info_message(message: str) -> None:
|
||||
"""Show an info message."""
|
||||
st.info(message)
|
||||
|
||||
def show_warning_message(message: str) -> None:
|
||||
"""Show a warning message."""
|
||||
st.warning(message)
|
||||
|
||||
def create_download_button(
|
||||
data: Dict[str, Any],
|
||||
filename: str,
|
||||
button_text: str = "Download"
|
||||
) -> None:
|
||||
"""Create a download button for data."""
|
||||
try:
|
||||
json_str = json.dumps(data, indent=4)
|
||||
st.download_button(
|
||||
label=button_text,
|
||||
data=json_str,
|
||||
file_name=filename,
|
||||
mime="application/json"
|
||||
)
|
||||
except Exception as e:
|
||||
st.error(f"Error creating download button: {str(e)}")
|
||||
|
||||
def create_upload_button(
|
||||
on_upload: callable,
|
||||
button_text: str = "Upload",
|
||||
file_types: List[str] = ["json"]
|
||||
) -> None:
|
||||
"""Create an upload button for data."""
|
||||
try:
|
||||
uploaded_file = st.file_uploader(
|
||||
button_text,
|
||||
type=file_types
|
||||
)
|
||||
if uploaded_file is not None:
|
||||
data = json.load(uploaded_file)
|
||||
on_upload(data)
|
||||
except Exception as e:
|
||||
st.error(f"Error handling upload: {str(e)}")
|
||||
1079
lib/ai_writers/youtube_writers/modules/channel_trailer_generator.py
Normal file
1079
lib/ai_writers/youtube_writers/modules/channel_trailer_generator.py
Normal file
File diff suppressed because it is too large
Load Diff
@@ -18,6 +18,7 @@ from .modules.tags_generator import write_yt_tags
|
||||
from .modules.shorts_script_generator import write_yt_shorts
|
||||
from .modules.community_post_generator import write_yt_community_post
|
||||
from .modules.shorts_video_generator import write_yt_shorts_video
|
||||
from .modules.channel_trailer_generator import write_yt_channel_trailer
|
||||
|
||||
|
||||
def youtube_main_menu():
|
||||
@@ -75,6 +76,15 @@ def youtube_main_menu():
|
||||
"function": write_yt_shorts_video,
|
||||
"status": "active"
|
||||
},
|
||||
{
|
||||
"name": "Channel Trailer Generator",
|
||||
"icon": "🎥",
|
||||
"description": "Create compelling channel trailers that convert visitors into subscribers.",
|
||||
"color": "#FF0000", # YouTube red
|
||||
"category": "Content Creation",
|
||||
"function": write_yt_channel_trailer,
|
||||
"status": "active"
|
||||
},
|
||||
|
||||
# Optimization Tools
|
||||
{
|
||||
@@ -135,15 +145,6 @@ def youtube_main_menu():
|
||||
"function": None,
|
||||
"status": "future"
|
||||
},
|
||||
{
|
||||
"name": "Channel Trailer Generator",
|
||||
"icon": "🎥",
|
||||
"description": "Create compelling channel trailers that convert visitors into subscribers.",
|
||||
"color": "#990000", # Even darker red for future
|
||||
"category": "Future Tools",
|
||||
"function": None,
|
||||
"status": "future"
|
||||
},
|
||||
{
|
||||
"name": "Video Series Planner",
|
||||
"icon": "📅",
|
||||
|
||||
Reference in New Issue
Block a user