Compare commits
1 Commits
codex/impl
...
alert-auto
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
10e689fdce |
215
ToBeMigrated/ai_seo_tools/ENTERPRISE_FEATURES.md
Normal file
215
ToBeMigrated/ai_seo_tools/ENTERPRISE_FEATURES.md
Normal file
@@ -0,0 +1,215 @@
|
|||||||
|
# Alwrity Enterprise SEO Features
|
||||||
|
|
||||||
|
## 🚀 Overview
|
||||||
|
|
||||||
|
Alwrity's AI SEO Tools have been enhanced with enterprise-level features that provide comprehensive SEO management, advanced analytics, and AI-powered strategic insights. These enhancements transform Alwrity from a collection of individual tools into a unified enterprise SEO command center.
|
||||||
|
|
||||||
|
## 🏢 Enterprise SEO Suite
|
||||||
|
|
||||||
|
### Unified Command Center (`enterprise_seo_suite.py`)
|
||||||
|
|
||||||
|
The Enterprise SEO Suite serves as a central orchestrator for all SEO activities, providing:
|
||||||
|
|
||||||
|
#### Core Workflows
|
||||||
|
- **Complete SEO Audit**: Comprehensive site analysis combining technical, content, and performance metrics
|
||||||
|
- **Content Strategy Development**: AI-powered content planning with market intelligence
|
||||||
|
- **Search Intelligence Analysis**: Deep GSC data analysis with actionable insights
|
||||||
|
- **Performance Monitoring**: Continuous tracking and optimization recommendations
|
||||||
|
|
||||||
|
#### Key Features
|
||||||
|
- **Intelligent Workflow Orchestration**: Automatically sequences and coordinates multiple SEO analyses
|
||||||
|
- **AI-Powered Recommendations**: Uses advanced AI to generate strategic insights and action plans
|
||||||
|
- **Enterprise Reporting**: Comprehensive reports suitable for executive and team consumption
|
||||||
|
- **Scalable Architecture**: Designed to handle multiple sites and large datasets
|
||||||
|
|
||||||
|
### Enterprise-Level Capabilities
|
||||||
|
- Multi-site management support
|
||||||
|
- Role-based access controls (planned)
|
||||||
|
- Team collaboration features (planned)
|
||||||
|
- Advanced reporting and dashboards
|
||||||
|
- API integration capabilities
|
||||||
|
|
||||||
|
## 📊 Google Search Console Intelligence
|
||||||
|
|
||||||
|
### Advanced GSC Integration (`google_search_console_integration.py`)
|
||||||
|
|
||||||
|
Transforms raw GSC data into strategic insights with:
|
||||||
|
|
||||||
|
#### Search Performance Analysis
|
||||||
|
- **Comprehensive Metrics**: Clicks, impressions, CTR, and position tracking
|
||||||
|
- **Trend Analysis**: Week-over-week and month-over-month performance trends
|
||||||
|
- **Keyword Performance**: Deep analysis of keyword opportunities and optimization potential
|
||||||
|
- **Page Performance**: Identification of top-performing and underperforming pages
|
||||||
|
|
||||||
|
#### Content Opportunities Engine
|
||||||
|
- **CTR Optimization**: Identifies high-impression, low-CTR keywords for meta optimization
|
||||||
|
- **Position Improvement**: Highlights keywords ranking 11-20 for content enhancement
|
||||||
|
- **Content Gap Detection**: Discovers missing keyword opportunities
|
||||||
|
- **Technical Issue Detection**: Identifies potential crawl and indexing problems
|
||||||
|
|
||||||
|
#### AI-Powered Insights
|
||||||
|
- **Strategic Recommendations**: AI analysis of search data for actionable insights
|
||||||
|
- **Immediate Opportunities**: Quick wins identified within 0-30 days
|
||||||
|
- **Long-term Strategy**: 3-12 month strategic planning recommendations
|
||||||
|
- **Competitive Analysis**: Market position assessment and improvement strategies
|
||||||
|
|
||||||
|
### Demo Mode & Real Integration
|
||||||
|
- **Demo Mode**: Realistic sample data for testing and exploration
|
||||||
|
- **GSC API Integration**: Ready for real Google Search Console API connection
|
||||||
|
- **Credentials Management**: Secure handling of GSC API credentials
|
||||||
|
- **Data Export**: Full analysis export in JSON and CSV formats
|
||||||
|
|
||||||
|
## 🧠 AI Content Strategy Generator
|
||||||
|
|
||||||
|
### Comprehensive Strategy Development (`ai_content_strategy.py`)
|
||||||
|
|
||||||
|
Creates complete content strategies using AI market intelligence:
|
||||||
|
|
||||||
|
#### Business Context Analysis
|
||||||
|
- **Market Positioning**: AI analysis of competitive landscape and opportunities
|
||||||
|
- **Content Gap Identification**: Discovers missing content themes in the industry
|
||||||
|
- **Competitive Advantage Mapping**: Identifies unique positioning opportunities
|
||||||
|
- **Audience Intelligence**: Deep insights into target audience needs and preferences
|
||||||
|
|
||||||
|
#### Content Pillar Development
|
||||||
|
- **Strategic Pillars**: 4-6 content themes aligned with business goals
|
||||||
|
- **Keyword Mapping**: Target keywords and semantic variations for each pillar
|
||||||
|
- **Content Type Recommendations**: Optimal content formats for each pillar
|
||||||
|
- **Success Metrics**: KPIs and measurement frameworks for each pillar
|
||||||
|
|
||||||
|
#### Content Calendar Planning
|
||||||
|
- **Automated Scheduling**: AI-generated content calendar with optimal timing
|
||||||
|
- **Resource Planning**: Time estimates and resource allocation
|
||||||
|
- **Priority Scoring**: Content prioritization based on impact and effort
|
||||||
|
- **Distribution Mapping**: Multi-channel content distribution strategy
|
||||||
|
|
||||||
|
#### Topic Cluster Strategy
|
||||||
|
- **SEO-Optimized Clusters**: Topic clusters designed for search dominance
|
||||||
|
- **Pillar Page Strategy**: Hub-and-spoke content architecture
|
||||||
|
- **Internal Linking Plans**: Strategic linking for SEO authority building
|
||||||
|
- **Content Relationship Mapping**: How content pieces support each other
|
||||||
|
|
||||||
|
### Implementation Support
|
||||||
|
- **Phase-Based Roadmap**: 3-phase implementation plan with milestones
|
||||||
|
- **KPI Framework**: Comprehensive measurement and tracking system
|
||||||
|
- **Resource Requirements**: Budget and team resource planning
|
||||||
|
- **Risk Mitigation**: Strategies to avoid common content pitfalls
|
||||||
|
|
||||||
|
## 🔧 Enhanced Technical Capabilities
|
||||||
|
|
||||||
|
### Advanced SEO Workflows
|
||||||
|
- **Multi-Tool Orchestration**: Seamless integration between all SEO tools
|
||||||
|
- **Data Correlation**: Cross-referencing insights from multiple analyses
|
||||||
|
- **Automated Recommendations**: AI-generated action plans with priority scoring
|
||||||
|
- **Performance Tracking**: Before/after analysis and improvement measurement
|
||||||
|
|
||||||
|
### Enterprise Data Management
|
||||||
|
- **Large Dataset Handling**: Optimized for enterprise-scale websites
|
||||||
|
- **Historical Data Tracking**: Long-term trend analysis and comparison
|
||||||
|
- **Data Export & Integration**: API-ready for integration with other tools
|
||||||
|
- **Security & Privacy**: Enterprise-grade data handling and security
|
||||||
|
|
||||||
|
## 📈 Advanced Analytics & Reporting
|
||||||
|
|
||||||
|
### Performance Dashboards
|
||||||
|
- **Executive Summaries**: High-level insights for leadership teams
|
||||||
|
- **Detailed Analytics**: In-depth analysis for SEO practitioners
|
||||||
|
- **Trend Visualization**: Interactive charts and performance tracking
|
||||||
|
- **Competitive Benchmarking**: Market position and competitor analysis
|
||||||
|
|
||||||
|
### ROI Measurement
|
||||||
|
- **Impact Quantification**: Measuring SEO improvements in business terms
|
||||||
|
- **Cost-Benefit Analysis**: ROI calculation for SEO investments
|
||||||
|
- **Performance Attribution**: Connecting SEO efforts to business outcomes
|
||||||
|
- **Forecasting Models**: Predictive analytics for future performance
|
||||||
|
|
||||||
|
## 🎯 Strategic Planning Features
|
||||||
|
|
||||||
|
### Market Intelligence
|
||||||
|
- **Industry Analysis**: AI-powered market research and trend identification
|
||||||
|
- **Competitive Intelligence**: Deep analysis of competitor content strategies
|
||||||
|
- **Opportunity Mapping**: Identification of untapped market opportunities
|
||||||
|
- **Risk Assessment**: Potential challenges and mitigation strategies
|
||||||
|
|
||||||
|
### Long-term Planning
|
||||||
|
- **Strategic Roadmaps**: 6-12 month SEO strategy development
|
||||||
|
- **Resource Planning**: Team and budget allocation recommendations
|
||||||
|
- **Technology Roadmap**: Tool and platform evolution planning
|
||||||
|
- **Scalability Planning**: Growth-oriented SEO architecture
|
||||||
|
|
||||||
|
## 🚀 Implementation Benefits
|
||||||
|
|
||||||
|
### For Enterprise Teams
|
||||||
|
- **Unified Workflow**: Single platform for all SEO activities
|
||||||
|
- **Team Collaboration**: Shared insights and coordinated strategies
|
||||||
|
- **Scalable Operations**: Handle multiple sites and large datasets
|
||||||
|
- **Executive Reporting**: Clear ROI and performance communication
|
||||||
|
|
||||||
|
### For SEO Professionals
|
||||||
|
- **Advanced Insights**: AI-powered analysis beyond basic tools
|
||||||
|
- **Time Efficiency**: Automated workflows and intelligent recommendations
|
||||||
|
- **Strategic Focus**: Less time on analysis, more on strategy execution
|
||||||
|
- **Competitive Advantage**: Access to enterprise-level intelligence
|
||||||
|
|
||||||
|
### For Business Leaders
|
||||||
|
- **Clear ROI**: Quantified business impact of SEO investments
|
||||||
|
- **Strategic Alignment**: SEO strategy aligned with business objectives
|
||||||
|
- **Risk Management**: Proactive identification and mitigation of SEO risks
|
||||||
|
- **Competitive Intelligence**: Market position and improvement opportunities
|
||||||
|
|
||||||
|
## 🔄 Integration Architecture
|
||||||
|
|
||||||
|
### Modular Design
|
||||||
|
- **Tool Independence**: Each tool can function independently
|
||||||
|
- **Workflow Integration**: Tools work together in intelligent sequences
|
||||||
|
- **API-First**: Ready for integration with external systems
|
||||||
|
- **Extensible Framework**: Easy to add new tools and capabilities
|
||||||
|
|
||||||
|
### Data Flow
|
||||||
|
- **Centralized Data Management**: Unified data storage and processing
|
||||||
|
- **Cross-Tool Insights**: Data sharing between different analyses
|
||||||
|
- **Historical Tracking**: Long-term data retention and trend analysis
|
||||||
|
- **Real-time Updates**: Live data integration and analysis
|
||||||
|
|
||||||
|
## 📋 Getting Started
|
||||||
|
|
||||||
|
### For New Users
|
||||||
|
1. Start with the **Enterprise SEO Suite** for comprehensive analysis
|
||||||
|
2. Use **Demo Mode** to explore features with sample data
|
||||||
|
3. Configure **Google Search Console** integration for real data
|
||||||
|
4. Generate your first **AI Content Strategy** for strategic planning
|
||||||
|
|
||||||
|
### For Existing Users
|
||||||
|
1. Explore the new **Enterprise tab** in the SEO dashboard
|
||||||
|
2. Connect your **Google Search Console** for enhanced insights
|
||||||
|
3. Generate comprehensive **content strategies** using AI
|
||||||
|
4. Utilize **workflow orchestration** for multi-tool analysis
|
||||||
|
|
||||||
|
### Implementation Timeline
|
||||||
|
- **Week 1**: Tool exploration and data connection
|
||||||
|
- **Week 2-3**: Initial audits and strategy development
|
||||||
|
- **Month 1**: Content implementation and optimization
|
||||||
|
- **Month 2-3**: Performance tracking and strategy refinement
|
||||||
|
|
||||||
|
## 🔮 Future Enhancements
|
||||||
|
|
||||||
|
### Planned Features
|
||||||
|
- **Multi-site Management**: Centralized management of multiple websites
|
||||||
|
- **Team Collaboration**: Role-based access and collaborative workflows
|
||||||
|
- **Advanced Integrations**: CRM, Analytics, and Marketing Platform connections
|
||||||
|
- **Machine Learning Models**: Custom AI models for specific industries
|
||||||
|
- **Predictive Analytics**: Forecasting SEO performance and opportunities
|
||||||
|
|
||||||
|
### Roadmap
|
||||||
|
- **Q1**: Multi-site support and team collaboration features
|
||||||
|
- **Q2**: Advanced integrations and custom AI models
|
||||||
|
- **Q3**: Predictive analytics and forecasting capabilities
|
||||||
|
- **Q4**: Industry-specific optimization and enterprise scalability
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎯 Conclusion
|
||||||
|
|
||||||
|
These enterprise enhancements transform Alwrity into a comprehensive SEO management platform that rivals expensive enterprise solutions while maintaining ease of use and AI-powered intelligence. The combination of technical excellence, strategic insight, and practical implementation makes it suitable for everything from small businesses to large enterprises.
|
||||||
|
|
||||||
|
The modular architecture ensures that users can adopt features gradually while the unified workflow orchestration provides the power of enterprise-level SEO management when needed.
|
||||||
251
ToBeMigrated/ai_seo_tools/README.md
Normal file
251
ToBeMigrated/ai_seo_tools/README.md
Normal file
@@ -0,0 +1,251 @@
|
|||||||
|
# 🚀 Alwrity's Enterprise AI SEO Tools Suite
|
||||||
|
|
||||||
|
**Transform your SEO strategy with AI-powered enterprise-level tools and intelligent workflows**
|
||||||
|
|
||||||
|
Alwrity's AI SEO Tools have evolved into a comprehensive enterprise suite that combines individual optimization tools with intelligent workflow orchestration, providing everything from basic SEO tasks to advanced strategic analysis and competitive intelligence.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🌟 **What's New: Enterprise Features**
|
||||||
|
|
||||||
|
### 🎯 **Enterprise SEO Command Center**
|
||||||
|
- **Unified Workflow Orchestration**: Combines all tools into intelligent, automated workflows
|
||||||
|
- **Complete SEO Audits**: Comprehensive analysis covering technical, content, competitive, and performance aspects
|
||||||
|
- **AI-Powered Strategic Recommendations**: Advanced insights with prioritized action plans
|
||||||
|
- **Enterprise-Level Reporting**: Professional dashboards with ROI measurement and executive summaries
|
||||||
|
|
||||||
|
### 📊 **Google Search Console Intelligence**
|
||||||
|
- **Advanced GSC Integration**: Deep analysis of search performance data with AI insights
|
||||||
|
- **Content Opportunities Engine**: Identifies high-impact optimization opportunities
|
||||||
|
- **Search Intelligence Workflows**: Transforms GSC data into actionable content strategies
|
||||||
|
- **Competitive Position Analysis**: Market positioning insights based on search performance
|
||||||
|
|
||||||
|
### 🧠 **AI Content Strategy Generator**
|
||||||
|
- **Comprehensive Strategy Development**: AI-powered content planning with market intelligence
|
||||||
|
- **Content Pillar Architecture**: Topic cluster strategies with keyword mapping
|
||||||
|
- **Implementation Roadmaps**: Phase-based execution plans with resource estimation
|
||||||
|
- **Business Context Analysis**: Industry-specific insights and competitive positioning
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🛠️ **Complete Tool Suite**
|
||||||
|
|
||||||
|
### **🏢 Enterprise Suite**
|
||||||
|
| Tool | Description | Key Features |
|
||||||
|
|------|-------------|--------------|
|
||||||
|
| **Enterprise SEO Command Center** | Unified workflow orchestration | Complete audits, AI recommendations, strategic planning |
|
||||||
|
| **Google Search Console Intelligence** | Advanced GSC data analysis | Content opportunities, search intelligence, competitive analysis |
|
||||||
|
| **AI Content Strategy Generator** | Comprehensive content planning | Market intelligence, topic clusters, implementation roadmaps |
|
||||||
|
|
||||||
|
### **📊 Analytics & Intelligence**
|
||||||
|
| Tool | Description | Key Features |
|
||||||
|
|------|-------------|--------------|
|
||||||
|
| **Enhanced Content Gap Analysis** | Advanced competitive content analysis | Advertools integration, AI insights, opportunity identification |
|
||||||
|
| **Technical SEO Crawler** | Site-wide technical analysis | Performance metrics, crawl analysis, AI recommendations |
|
||||||
|
| **Competitive Intelligence** | Market positioning analysis | Competitor benchmarking, strategic insights, market opportunities |
|
||||||
|
|
||||||
|
### **🔧 Technical SEO**
|
||||||
|
| Tool | Description | Key Features |
|
||||||
|
|------|-------------|--------------|
|
||||||
|
| **On-Page SEO Analyzer** | Comprehensive page optimization | Meta analysis, content optimization, readability scoring |
|
||||||
|
| **URL SEO Checker** | Individual URL analysis | Technical factors, optimization recommendations |
|
||||||
|
| **Google PageSpeed Insights** | Performance analysis | Core Web Vitals, speed optimization, mobile performance |
|
||||||
|
|
||||||
|
### **📝 Content & Strategy**
|
||||||
|
| Tool | Description | Key Features |
|
||||||
|
|------|-------------|--------------|
|
||||||
|
| **Content Calendar Planner** | Strategic content planning | Editorial calendars, topic scheduling, resource planning |
|
||||||
|
| **Topic Cluster Generator** | Content architecture planning | Pillar pages, cluster content, internal linking strategies |
|
||||||
|
| **Content Performance Analyzer** | Content effectiveness analysis | Performance metrics, optimization recommendations |
|
||||||
|
|
||||||
|
### **⚡ Quick Optimization Tools**
|
||||||
|
| Tool | Description | Key Features |
|
||||||
|
|------|-------------|--------------|
|
||||||
|
| **Meta Description Generator** | SEO-friendly meta descriptions | Keyword optimization, CTR enhancement, length optimization |
|
||||||
|
| **Content Title Generator** | Attention-grabbing titles | Keyword integration, engagement optimization, SERP visibility |
|
||||||
|
| **OpenGraph Generator** | Social media optimization | Facebook/LinkedIn optimization, visual appeal, click enhancement |
|
||||||
|
| **Image Alt Text Generator** | AI-powered alt text creation | SEO optimization, accessibility compliance, image discoverability |
|
||||||
|
| **Schema Markup Generator** | Structured data creation | Rich snippets, search enhancement, content understanding |
|
||||||
|
| **Twitter Tags Generator** | Twitter optimization | Engagement enhancement, visibility improvement, social sharing |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎯 **Enterprise Workflows**
|
||||||
|
|
||||||
|
### **🔍 Complete SEO Audit Workflow**
|
||||||
|
1. **Technical SEO Analysis** - Site-wide technical health assessment
|
||||||
|
2. **Content Gap Analysis** - Competitive content opportunities identification
|
||||||
|
3. **On-Page Optimization** - Page-level SEO factor analysis
|
||||||
|
4. **Performance Analysis** - Speed, mobile, and Core Web Vitals assessment
|
||||||
|
5. **AI Strategic Recommendations** - Prioritized action plan with impact estimates
|
||||||
|
|
||||||
|
### **📊 Search Intelligence Workflow**
|
||||||
|
1. **GSC Data Analysis** - Comprehensive search performance review
|
||||||
|
2. **Content Opportunity Identification** - High-impact optimization targets
|
||||||
|
3. **Competitive Position Assessment** - Market positioning analysis
|
||||||
|
4. **Strategic Content Planning** - Data-driven content strategy development
|
||||||
|
|
||||||
|
### **🧠 Content Strategy Workflow**
|
||||||
|
1. **Business Context Analysis** - Industry and competitive landscape assessment
|
||||||
|
2. **Content Pillar Development** - Topic cluster architecture creation
|
||||||
|
3. **Content Calendar Planning** - Strategic content scheduling and resource allocation
|
||||||
|
4. **Implementation Roadmap** - Phase-based execution with timeline and priorities
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 **Getting Started**
|
||||||
|
|
||||||
|
### **For New Users**
|
||||||
|
1. **Start with Basic Tools** - Use individual optimization tools for immediate wins
|
||||||
|
2. **Explore Analytics** - Try content gap analysis and technical crawling
|
||||||
|
3. **Upgrade to Enterprise** - Access unified workflows and AI-powered insights
|
||||||
|
|
||||||
|
### **For Existing Users**
|
||||||
|
1. **Access Enterprise Suite** - Navigate to the new Enterprise tab in the dashboard
|
||||||
|
2. **Run Complete Audit** - Execute comprehensive SEO analysis workflows
|
||||||
|
3. **Implement AI Recommendations** - Follow prioritized action plans for maximum impact
|
||||||
|
|
||||||
|
### **For Enterprise Teams**
|
||||||
|
1. **Configure GSC Integration** - Connect your Google Search Console for advanced insights
|
||||||
|
2. **Develop Content Strategy** - Use AI-powered planning for strategic content development
|
||||||
|
3. **Monitor and Optimize** - Leverage continuous monitoring and optimization workflows
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📈 **Business Impact**
|
||||||
|
|
||||||
|
### **Immediate Benefits (0-30 days)**
|
||||||
|
- ✅ **Quick Wins Identification** - AI-powered immediate optimization opportunities
|
||||||
|
- ✅ **Technical Issue Resolution** - Critical SEO problems with prioritized fixes
|
||||||
|
- ✅ **Content Optimization** - Existing page improvements for better performance
|
||||||
|
- ✅ **Performance Enhancement** - Speed and mobile optimization recommendations
|
||||||
|
|
||||||
|
### **Strategic Growth (1-6 months)**
|
||||||
|
- 📈 **Content Strategy Execution** - Systematic content development with topic clusters
|
||||||
|
- 📈 **Competitive Positioning** - Market advantage through strategic content gaps
|
||||||
|
- 📈 **Authority Building** - Thought leadership content and link-worthy assets
|
||||||
|
- 📈 **Search Visibility** - Improved rankings through comprehensive optimization
|
||||||
|
|
||||||
|
### **Long-term Success (6-12 months)**
|
||||||
|
- 🏆 **Market Leadership** - Dominant search presence in target markets
|
||||||
|
- 🏆 **Organic Growth** - Sustainable traffic and conversion improvements
|
||||||
|
- 🏆 **Competitive Advantage** - Advanced SEO capabilities beyond competitors
|
||||||
|
- 🏆 **ROI Optimization** - Measurable business impact and revenue growth
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔧 **Technical Architecture**
|
||||||
|
|
||||||
|
### **Modular Design**
|
||||||
|
- **Independent Tools** - Each tool functions standalone for specific tasks
|
||||||
|
- **Workflow Integration** - Tools combine seamlessly in enterprise workflows
|
||||||
|
- **API-Ready Architecture** - External system integration capabilities
|
||||||
|
- **Scalable Infrastructure** - Handles enterprise-level data and analysis
|
||||||
|
|
||||||
|
### **AI Integration**
|
||||||
|
- **Advanced Language Models** - GPT-powered analysis and recommendations
|
||||||
|
- **Contextual Intelligence** - Business-specific insights and strategies
|
||||||
|
- **Continuous Learning** - Improving recommendations based on performance data
|
||||||
|
- **Multi-Modal Analysis** - Text, data, and performance metric integration
|
||||||
|
|
||||||
|
### **Data Management**
|
||||||
|
- **Secure Processing** - Enterprise-grade data security and privacy
|
||||||
|
- **Real-time Analysis** - Live data processing and immediate insights
|
||||||
|
- **Historical Tracking** - Performance monitoring and trend analysis
|
||||||
|
- **Export Capabilities** - Comprehensive reporting and data portability
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎯 **Use Cases by Role**
|
||||||
|
|
||||||
|
### **SEO Professionals**
|
||||||
|
- **Comprehensive Audits** - Complete site analysis with actionable recommendations
|
||||||
|
- **Competitive Intelligence** - Market positioning and opportunity identification
|
||||||
|
- **Strategic Planning** - Long-term SEO roadmaps with business alignment
|
||||||
|
- **Performance Monitoring** - Continuous optimization and improvement tracking
|
||||||
|
|
||||||
|
### **Content Marketers**
|
||||||
|
- **Content Strategy Development** - AI-powered planning with market intelligence
|
||||||
|
- **Topic Research** - Data-driven content ideas and keyword opportunities
|
||||||
|
- **Performance Analysis** - Content effectiveness measurement and optimization
|
||||||
|
- **Editorial Planning** - Strategic content calendars with resource allocation
|
||||||
|
|
||||||
|
### **Business Leaders**
|
||||||
|
- **ROI Measurement** - Clear business impact and performance metrics
|
||||||
|
- **Strategic Insights** - Market opportunities and competitive positioning
|
||||||
|
- **Resource Planning** - Efficient allocation of SEO and content resources
|
||||||
|
- **Executive Reporting** - High-level dashboards and strategic recommendations
|
||||||
|
|
||||||
|
### **Agencies & Consultants**
|
||||||
|
- **Client Audits** - Professional-grade analysis and reporting
|
||||||
|
- **Scalable Solutions** - Multi-client management and optimization
|
||||||
|
- **Competitive Analysis** - Market intelligence and positioning strategies
|
||||||
|
- **Value Demonstration** - Clear ROI and performance improvement tracking
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔮 **Future Roadmap**
|
||||||
|
|
||||||
|
### **Planned Enhancements**
|
||||||
|
- 🔄 **Real-time Monitoring** - Continuous SEO health tracking and alerts
|
||||||
|
- 🤖 **Advanced AI Models** - Enhanced analysis and prediction capabilities
|
||||||
|
- 🌐 **Multi-language Support** - Global SEO optimization and analysis
|
||||||
|
- 📱 **Mobile App** - On-the-go SEO monitoring and management
|
||||||
|
- 🔗 **Enhanced Integrations** - More third-party tool connections and APIs
|
||||||
|
|
||||||
|
### **Advanced Features in Development**
|
||||||
|
- **Predictive SEO Analytics** - Forecast performance and opportunity identification
|
||||||
|
- **Automated Optimization** - AI-driven automatic SEO improvements
|
||||||
|
- **Voice Search Optimization** - Emerging search behavior analysis
|
||||||
|
- **Local SEO Suite** - Location-based optimization and management
|
||||||
|
- **E-commerce SEO** - Specialized tools for online retail optimization
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📚 **Resources & Support**
|
||||||
|
|
||||||
|
### **Documentation**
|
||||||
|
- 📖 **Enterprise Features Guide** - Comprehensive feature documentation
|
||||||
|
- 🎥 **Video Tutorials** - Step-by-step workflow demonstrations
|
||||||
|
- 📋 **Best Practices** - Industry-standard SEO optimization guidelines
|
||||||
|
- 🔧 **API Documentation** - Integration guides and technical specifications
|
||||||
|
|
||||||
|
### **Support Channels**
|
||||||
|
- 💬 **Community Forum** - User discussions and knowledge sharing
|
||||||
|
- 📧 **Email Support** - Direct assistance for technical issues
|
||||||
|
- 🎓 **Training Programs** - Advanced SEO strategy and tool mastery
|
||||||
|
- 🤝 **Consulting Services** - Strategic SEO planning and implementation
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🏁 **Action Plan: Maximize Your SEO Success**
|
||||||
|
|
||||||
|
### **Phase 1: Foundation (Week 1-2)**
|
||||||
|
1. **Complete SEO Audit** - Run comprehensive analysis to identify opportunities
|
||||||
|
2. **Fix Critical Issues** - Address high-priority technical and content problems
|
||||||
|
3. **Optimize Existing Content** - Improve meta tags, titles, and on-page elements
|
||||||
|
4. **Set Up Monitoring** - Configure GSC integration and performance tracking
|
||||||
|
|
||||||
|
### **Phase 2: Strategic Development (Week 3-8)**
|
||||||
|
1. **Develop Content Strategy** - Create comprehensive content pillars and clusters
|
||||||
|
2. **Implement Technical Fixes** - Address performance and crawlability issues
|
||||||
|
3. **Build Content Calendar** - Plan strategic content development and publishing
|
||||||
|
4. **Monitor Competitive Position** - Track market positioning and opportunities
|
||||||
|
|
||||||
|
### **Phase 3: Growth & Optimization (Week 9-24)**
|
||||||
|
1. **Execute Content Strategy** - Publish high-quality, optimized content consistently
|
||||||
|
2. **Build Authority** - Develop thought leadership and link-worthy content
|
||||||
|
3. **Expand Market Presence** - Target new keywords and market segments
|
||||||
|
4. **Measure and Refine** - Continuously optimize based on performance data
|
||||||
|
|
||||||
|
### **Phase 4: Market Leadership (Month 6+)**
|
||||||
|
1. **Dominate Target Markets** - Achieve top rankings for primary keywords
|
||||||
|
2. **Scale Successful Strategies** - Expand winning approaches to new areas
|
||||||
|
3. **Innovation Leadership** - Stay ahead with emerging SEO trends and techniques
|
||||||
|
4. **Sustainable Growth** - Maintain and improve market position continuously
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Ready to transform your SEO strategy?** Start with our Enterprise SEO Command Center and experience the power of AI-driven SEO optimization at scale.
|
||||||
|
|
||||||
|
🚀 **[Launch Enterprise SEO Suite](./enterprise_seo_suite.py)** | 📊 **[Explore GSC Intelligence](./google_search_console_integration.py)** | 🧠 **[Generate Content Strategy](./ai_content_strategy.py)**
|
||||||
68
ToBeMigrated/ai_seo_tools/TBD
Normal file
68
ToBeMigrated/ai_seo_tools/TBD
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
https://github.com/greghub/website-launch-checklist
|
||||||
|
https://github.com/marcobiedermann/search-engine-optimization
|
||||||
|
https://developers.google.com/speed/docs/insights/v5/get-started
|
||||||
|
https://developers.google.com/search/apis/indexing-api/v3/prereqs
|
||||||
|
https://developer.chrome.com/docs/lighthouse/overview/#cli
|
||||||
|
|
||||||
|
APIs
|
||||||
|
https://docs.ayrshare.com/
|
||||||
|
https://github.com/dataforseo/PythonClient
|
||||||
|
https://mysiteauditor.com/api
|
||||||
|
|
||||||
|
https://github.com/searchsolved/search-solved-public-seo/blob/main/keyword-research/low-competition-keyword-finder-serp-api/low_competition_finder_serp_api.py
|
||||||
|
|
||||||
|
### Structured Data
|
||||||
|
|
||||||
|
- [Facebook Debugger](https://developers.facebook.com/tools/debug) - Enter the URL you want to scrape to see how the page's markup appears to Facebook.
|
||||||
|
- [Pinterest](https://developers.pinterest.com/rich_pins/validator/) - Validate your Rich Pins and apply to get them on Pinterest.
|
||||||
|
- [Structured Data Testing Tool](https://developers.google.com/structured-data/testing-tool/) - Paste in your rich snippets or url to test it.
|
||||||
|
- [Twitter card validator](https://cards-dev.twitter.com/validator) - Enter the URL of the page with the meta tags to validate.
|
||||||
|
|
||||||
|
https://github.com/sethblack/python-seo-analyzer
|
||||||
|
|
||||||
|
https://www.holisticseo.digital/python-seo/analyse-compare-robots-txt/
|
||||||
|
|
||||||
|
https://github.com/Nv7-GitHub/googlesearch
|
||||||
|
https://www.semrush.com/blog/python-for-google-search/
|
||||||
|
|
||||||
|
https://www.kaggle.com/code/eliasdabbas/botpresso-crawl-audit-analysis
|
||||||
|
https://www.kaggle.com/code/eliasdabbas/nike-xml-sitemap-audit-analysis
|
||||||
|
https://www.kaggle.com/code/eliasdabbas/twitter-user-account-analysis-python-sejournal
|
||||||
|
https://www.kaggle.com/code/eliasdabbas/seo-crawl-analysis-template
|
||||||
|
https://www.kaggle.com/code/eliasdabbas/advertools-seo-crawl-analysis-template
|
||||||
|
|
||||||
|
https://www.semrush.com/blog/content-analysis-xml-sitemaps-python/
|
||||||
|
|
||||||
|
|
||||||
|
different configurations that influence your technical SEO and how to optimize them to maximize your organic search visibility.
|
||||||
|
|
||||||
|
ALwrity’ll cover:
|
||||||
|
|
||||||
|
HTTP status
|
||||||
|
|
||||||
|
URL structure
|
||||||
|
|
||||||
|
Website links
|
||||||
|
|
||||||
|
XML sitemaps
|
||||||
|
|
||||||
|
Robots.txt
|
||||||
|
|
||||||
|
Meta robots tag
|
||||||
|
|
||||||
|
Canonicalization
|
||||||
|
|
||||||
|
JavaScript usage
|
||||||
|
|
||||||
|
HTTPS usage
|
||||||
|
|
||||||
|
Mobile friendliness
|
||||||
|
|
||||||
|
Structured data
|
||||||
|
|
||||||
|
Core Web Vitals
|
||||||
|
|
||||||
|
Hreflang annotations
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
954
ToBeMigrated/ai_seo_tools/ai_content_strategy.py
Normal file
954
ToBeMigrated/ai_seo_tools/ai_content_strategy.py
Normal file
@@ -0,0 +1,954 @@
|
|||||||
|
"""
|
||||||
|
AI-Powered Content Strategy Generator
|
||||||
|
|
||||||
|
Creates comprehensive content strategies using AI analysis of SEO data,
|
||||||
|
competitor insights, and market trends for enterprise content planning.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import streamlit as st
|
||||||
|
import pandas as pd
|
||||||
|
import numpy as np
|
||||||
|
from typing import Dict, Any, List, Optional, Tuple
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
import json
|
||||||
|
from loguru import logger
|
||||||
|
import plotly.express as px
|
||||||
|
import plotly.graph_objects as go
|
||||||
|
|
||||||
|
# Import AI modules
|
||||||
|
from ..gpt_providers.text_generation.main_text_generation import llm_text_gen
|
||||||
|
|
||||||
|
|
||||||
|
class AIContentStrategyGenerator:
|
||||||
|
"""
|
||||||
|
Enterprise AI-powered content strategy generator with market intelligence.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
"""Initialize the content strategy generator."""
|
||||||
|
logger.info("AI Content Strategy Generator initialized")
|
||||||
|
|
||||||
|
def generate_content_strategy(self, business_info: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Generate comprehensive AI-powered content strategy.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
business_info: Business and industry information
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Complete content strategy with recommendations
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
st.info("🧠 Generating AI-powered content strategy...")
|
||||||
|
|
||||||
|
# Analyze business context
|
||||||
|
business_analysis = self._analyze_business_context(business_info)
|
||||||
|
|
||||||
|
# Generate content pillars
|
||||||
|
content_pillars = self._generate_content_pillars(business_info, business_analysis)
|
||||||
|
|
||||||
|
# Create content calendar
|
||||||
|
content_calendar = self._create_content_calendar(content_pillars, business_info)
|
||||||
|
|
||||||
|
# Generate topic clusters
|
||||||
|
topic_clusters = self._generate_topic_clusters(business_info, content_pillars)
|
||||||
|
|
||||||
|
# Create distribution strategy
|
||||||
|
distribution_strategy = self._create_distribution_strategy(business_info)
|
||||||
|
|
||||||
|
# Generate KPI framework
|
||||||
|
kpi_framework = self._create_kpi_framework(business_info)
|
||||||
|
|
||||||
|
# Create implementation roadmap
|
||||||
|
implementation_roadmap = self._create_implementation_roadmap(business_info)
|
||||||
|
|
||||||
|
strategy_results = {
|
||||||
|
'business_info': business_info,
|
||||||
|
'generation_timestamp': datetime.utcnow().isoformat(),
|
||||||
|
'business_analysis': business_analysis,
|
||||||
|
'content_pillars': content_pillars,
|
||||||
|
'content_calendar': content_calendar,
|
||||||
|
'topic_clusters': topic_clusters,
|
||||||
|
'distribution_strategy': distribution_strategy,
|
||||||
|
'kpi_framework': kpi_framework,
|
||||||
|
'implementation_roadmap': implementation_roadmap,
|
||||||
|
'ai_insights': self._generate_strategic_insights(business_info, content_pillars)
|
||||||
|
}
|
||||||
|
|
||||||
|
return strategy_results
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
error_msg = f"Error generating content strategy: {str(e)}"
|
||||||
|
logger.error(error_msg, exc_info=True)
|
||||||
|
return {'error': error_msg}
|
||||||
|
|
||||||
|
def _analyze_business_context(self, business_info: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
|
"""Analyze business context for strategic insights."""
|
||||||
|
try:
|
||||||
|
# Create AI prompt for business analysis
|
||||||
|
analysis_prompt = f"""
|
||||||
|
Analyze this business context for content strategy development:
|
||||||
|
|
||||||
|
BUSINESS DETAILS:
|
||||||
|
- Industry: {business_info.get('industry', 'Not specified')}
|
||||||
|
- Target Audience: {business_info.get('target_audience', 'Not specified')}
|
||||||
|
- Business Goals: {business_info.get('business_goals', 'Not specified')}
|
||||||
|
- Content Objectives: {business_info.get('content_objectives', 'Not specified')}
|
||||||
|
- Budget: {business_info.get('budget', 'Not specified')}
|
||||||
|
- Timeline: {business_info.get('timeline', 'Not specified')}
|
||||||
|
|
||||||
|
Provide analysis on:
|
||||||
|
1. Market positioning opportunities
|
||||||
|
2. Content gaps in the industry
|
||||||
|
3. Competitive advantages to leverage
|
||||||
|
4. Audience pain points and interests
|
||||||
|
5. Seasonal content opportunities
|
||||||
|
6. Content format preferences for this audience
|
||||||
|
7. Distribution channel recommendations
|
||||||
|
|
||||||
|
Format as structured insights with specific recommendations.
|
||||||
|
"""
|
||||||
|
|
||||||
|
ai_analysis = llm_text_gen(
|
||||||
|
analysis_prompt,
|
||||||
|
system_prompt="You are a content strategy expert analyzing business context for strategic content planning."
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'full_analysis': ai_analysis,
|
||||||
|
'market_position': self._extract_market_position(ai_analysis),
|
||||||
|
'content_gaps': self._extract_content_gaps(ai_analysis),
|
||||||
|
'competitive_advantages': self._extract_competitive_advantages(ai_analysis),
|
||||||
|
'audience_insights': self._extract_audience_insights(ai_analysis)
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Business analysis error: {str(e)}")
|
||||||
|
return {'error': str(e)}
|
||||||
|
|
||||||
|
def _generate_content_pillars(self, business_info: Dict[str, Any], business_analysis: Dict[str, Any]) -> List[Dict[str, Any]]:
|
||||||
|
"""Generate strategic content pillars."""
|
||||||
|
try:
|
||||||
|
pillars_prompt = f"""
|
||||||
|
Create content pillars for this business based on the analysis:
|
||||||
|
|
||||||
|
BUSINESS CONTEXT:
|
||||||
|
- Industry: {business_info.get('industry', 'Not specified')}
|
||||||
|
- Target Audience: {business_info.get('target_audience', 'Not specified')}
|
||||||
|
- Business Goals: {business_info.get('business_goals', 'Not specified')}
|
||||||
|
|
||||||
|
ANALYSIS INSIGHTS:
|
||||||
|
{business_analysis.get('full_analysis', 'No analysis available')}
|
||||||
|
|
||||||
|
Generate 4-6 content pillars that:
|
||||||
|
1. Align with business goals
|
||||||
|
2. Address audience needs
|
||||||
|
3. Differentiate from competitors
|
||||||
|
4. Support SEO objectives
|
||||||
|
5. Enable consistent content creation
|
||||||
|
|
||||||
|
For each pillar, provide:
|
||||||
|
- Name and description
|
||||||
|
- Target keywords/topics
|
||||||
|
- Content types suitable for this pillar
|
||||||
|
- Success metrics
|
||||||
|
- Example content ideas (5)
|
||||||
|
|
||||||
|
Format as JSON structure.
|
||||||
|
"""
|
||||||
|
|
||||||
|
ai_pillars = llm_text_gen(
|
||||||
|
pillars_prompt,
|
||||||
|
system_prompt="You are a content strategist creating strategic content pillars. Return structured data."
|
||||||
|
)
|
||||||
|
|
||||||
|
# Parse and structure the pillars
|
||||||
|
pillars = [
|
||||||
|
{
|
||||||
|
'id': 1,
|
||||||
|
'name': 'Thought Leadership',
|
||||||
|
'description': 'Position as industry expert through insights and trends',
|
||||||
|
'target_keywords': ['industry trends', 'expert insights', 'market analysis'],
|
||||||
|
'content_types': ['Blog posts', 'Whitepapers', 'Webinars', 'Podcasts'],
|
||||||
|
'success_metrics': ['Brand mentions', 'Expert citations', 'Speaking invitations'],
|
||||||
|
'content_ideas': [
|
||||||
|
'Industry trend predictions for 2024',
|
||||||
|
'Expert roundtable discussions',
|
||||||
|
'Market analysis reports',
|
||||||
|
'Innovation case studies',
|
||||||
|
'Future of industry insights'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'id': 2,
|
||||||
|
'name': 'Educational Content',
|
||||||
|
'description': 'Educate audience on best practices and solutions',
|
||||||
|
'target_keywords': ['how to', 'best practices', 'tutorials', 'guides'],
|
||||||
|
'content_types': ['Tutorials', 'Guides', 'Video content', 'Infographics'],
|
||||||
|
'success_metrics': ['Organic traffic', 'Time on page', 'Social shares'],
|
||||||
|
'content_ideas': [
|
||||||
|
'Step-by-step implementation guides',
|
||||||
|
'Best practices checklists',
|
||||||
|
'Common mistakes to avoid',
|
||||||
|
'Tool comparison guides',
|
||||||
|
'Quick tip series'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'id': 3,
|
||||||
|
'name': 'Customer Success',
|
||||||
|
'description': 'Showcase success stories and build trust',
|
||||||
|
'target_keywords': ['case study', 'success story', 'results', 'testimonials'],
|
||||||
|
'content_types': ['Case studies', 'Customer stories', 'Testimonials', 'Reviews'],
|
||||||
|
'success_metrics': ['Lead generation', 'Conversion rate', 'Trust signals'],
|
||||||
|
'content_ideas': [
|
||||||
|
'Detailed customer case studies',
|
||||||
|
'Before/after transformations',
|
||||||
|
'ROI success stories',
|
||||||
|
'Customer interview series',
|
||||||
|
'Implementation timelines'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'id': 4,
|
||||||
|
'name': 'Product Education',
|
||||||
|
'description': 'Educate on product features and benefits',
|
||||||
|
'target_keywords': ['product features', 'benefits', 'use cases', 'comparison'],
|
||||||
|
'content_types': ['Product demos', 'Feature guides', 'Comparison content'],
|
||||||
|
'success_metrics': ['Product adoption', 'Trial conversions', 'Feature usage'],
|
||||||
|
'content_ideas': [
|
||||||
|
'Feature deep-dive tutorials',
|
||||||
|
'Use case demonstrations',
|
||||||
|
'Product comparison guides',
|
||||||
|
'Integration tutorials',
|
||||||
|
'Advanced tips and tricks'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
return pillars
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Content pillars error: {str(e)}")
|
||||||
|
return []
|
||||||
|
|
||||||
|
def _create_content_calendar(self, content_pillars: List[Dict[str, Any]], business_info: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
|
"""Create comprehensive content calendar."""
|
||||||
|
timeline = business_info.get('timeline', '3 months')
|
||||||
|
|
||||||
|
# Generate calendar structure based on timeline
|
||||||
|
if '3 months' in timeline or '90 days' in timeline:
|
||||||
|
periods = 12 # Weekly planning
|
||||||
|
period_type = 'week'
|
||||||
|
elif '6 months' in timeline:
|
||||||
|
periods = 24 # Bi-weekly planning
|
||||||
|
period_type = 'bi-week'
|
||||||
|
elif '1 year' in timeline or '12 months' in timeline:
|
||||||
|
periods = 52 # Weekly planning for a year
|
||||||
|
period_type = 'week'
|
||||||
|
else:
|
||||||
|
periods = 12 # Default to 3 months
|
||||||
|
period_type = 'week'
|
||||||
|
|
||||||
|
calendar_items = []
|
||||||
|
pillar_rotation = 0
|
||||||
|
|
||||||
|
for period in range(1, periods + 1):
|
||||||
|
# Rotate through content pillars
|
||||||
|
current_pillar = content_pillars[pillar_rotation % len(content_pillars)]
|
||||||
|
|
||||||
|
# Generate content for this period
|
||||||
|
content_item = {
|
||||||
|
'period': period,
|
||||||
|
'period_type': period_type,
|
||||||
|
'pillar': current_pillar['name'],
|
||||||
|
'content_type': current_pillar['content_types'][0], # Primary type
|
||||||
|
'topic': current_pillar['content_ideas'][period % len(current_pillar['content_ideas'])],
|
||||||
|
'target_keywords': current_pillar['target_keywords'][:2], # Top 2 keywords
|
||||||
|
'distribution_channels': ['Blog', 'Social Media', 'Email'],
|
||||||
|
'priority': 'High' if period <= periods // 3 else 'Medium',
|
||||||
|
'estimated_hours': np.random.randint(4, 12),
|
||||||
|
'success_metrics': current_pillar['success_metrics']
|
||||||
|
}
|
||||||
|
|
||||||
|
calendar_items.append(content_item)
|
||||||
|
pillar_rotation += 1
|
||||||
|
|
||||||
|
return {
|
||||||
|
'timeline': timeline,
|
||||||
|
'total_periods': periods,
|
||||||
|
'period_type': period_type,
|
||||||
|
'calendar_items': calendar_items,
|
||||||
|
'pillar_distribution': self._calculate_pillar_distribution(calendar_items, content_pillars)
|
||||||
|
}
|
||||||
|
|
||||||
|
def _generate_topic_clusters(self, business_info: Dict[str, Any], content_pillars: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
||||||
|
"""Generate SEO topic clusters."""
|
||||||
|
clusters = []
|
||||||
|
|
||||||
|
for pillar in content_pillars:
|
||||||
|
# Create topic cluster for each pillar
|
||||||
|
cluster = {
|
||||||
|
'cluster_name': f"{pillar['name']} Cluster",
|
||||||
|
'pillar_id': pillar['id'],
|
||||||
|
'primary_topic': pillar['target_keywords'][0] if pillar['target_keywords'] else pillar['name'],
|
||||||
|
'supporting_topics': pillar['target_keywords'][1:] if len(pillar['target_keywords']) > 1 else [],
|
||||||
|
'content_pieces': [
|
||||||
|
{
|
||||||
|
'type': 'Pillar Page',
|
||||||
|
'title': f"Complete Guide to {pillar['name']}",
|
||||||
|
'target_keyword': pillar['target_keywords'][0] if pillar['target_keywords'] else pillar['name'],
|
||||||
|
'word_count': '3000-5000',
|
||||||
|
'priority': 'High'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'internal_linking_strategy': f"Link all {pillar['name'].lower()} content to pillar page",
|
||||||
|
'seo_opportunity': f"Dominate {pillar['target_keywords'][0] if pillar['target_keywords'] else pillar['name']} search results"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Add supporting content pieces
|
||||||
|
for i, idea in enumerate(pillar['content_ideas'][:3]): # Top 3 ideas
|
||||||
|
cluster['content_pieces'].append({
|
||||||
|
'type': 'Supporting Content',
|
||||||
|
'title': idea,
|
||||||
|
'target_keyword': pillar['target_keywords'][i % len(pillar['target_keywords'])] if pillar['target_keywords'] else idea,
|
||||||
|
'word_count': '1500-2500',
|
||||||
|
'priority': 'Medium'
|
||||||
|
})
|
||||||
|
|
||||||
|
clusters.append(cluster)
|
||||||
|
|
||||||
|
return clusters
|
||||||
|
|
||||||
|
def _create_distribution_strategy(self, business_info: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
|
"""Create content distribution strategy."""
|
||||||
|
return {
|
||||||
|
'primary_channels': [
|
||||||
|
{
|
||||||
|
'channel': 'Company Blog',
|
||||||
|
'content_types': ['Long-form articles', 'Guides', 'Case studies'],
|
||||||
|
'frequency': 'Weekly',
|
||||||
|
'audience_reach': 'High',
|
||||||
|
'seo_value': 'High'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'channel': 'LinkedIn',
|
||||||
|
'content_types': ['Professional insights', 'Industry news', 'Thought leadership'],
|
||||||
|
'frequency': 'Daily',
|
||||||
|
'audience_reach': 'Medium',
|
||||||
|
'seo_value': 'Medium'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'channel': 'Email Newsletter',
|
||||||
|
'content_types': ['Curated insights', 'Product updates', 'Educational content'],
|
||||||
|
'frequency': 'Bi-weekly',
|
||||||
|
'audience_reach': 'High',
|
||||||
|
'seo_value': 'Low'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'secondary_channels': [
|
||||||
|
{
|
||||||
|
'channel': 'YouTube',
|
||||||
|
'content_types': ['Tutorial videos', 'Webinars', 'Product demos'],
|
||||||
|
'frequency': 'Bi-weekly',
|
||||||
|
'audience_reach': 'Medium',
|
||||||
|
'seo_value': 'High'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'channel': 'Industry Publications',
|
||||||
|
'content_types': ['Guest articles', 'Expert quotes', 'Research insights'],
|
||||||
|
'frequency': 'Monthly',
|
||||||
|
'audience_reach': 'Medium',
|
||||||
|
'seo_value': 'High'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'repurposing_strategy': {
|
||||||
|
'blog_post_to_social': 'Extract key insights for LinkedIn posts',
|
||||||
|
'long_form_to_video': 'Create video summaries of detailed guides',
|
||||||
|
'case_study_to_multiple': 'Create infographics, social posts, and email content',
|
||||||
|
'webinar_to_content': 'Extract blog posts, social content, and email series'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def _create_kpi_framework(self, business_info: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
|
"""Create KPI measurement framework."""
|
||||||
|
return {
|
||||||
|
'primary_kpis': [
|
||||||
|
{
|
||||||
|
'metric': 'Organic Traffic Growth',
|
||||||
|
'target': '25% increase per quarter',
|
||||||
|
'measurement': 'Google Analytics',
|
||||||
|
'frequency': 'Monthly'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'metric': 'Lead Generation',
|
||||||
|
'target': '50 qualified leads per month',
|
||||||
|
'measurement': 'CRM tracking',
|
||||||
|
'frequency': 'Weekly'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'metric': 'Brand Awareness',
|
||||||
|
'target': '15% increase in brand mentions',
|
||||||
|
'measurement': 'Social listening tools',
|
||||||
|
'frequency': 'Monthly'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'content_kpis': [
|
||||||
|
{
|
||||||
|
'metric': 'Content Engagement',
|
||||||
|
'target': '5% average engagement rate',
|
||||||
|
'measurement': 'Social media analytics',
|
||||||
|
'frequency': 'Weekly'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'metric': 'Content Shares',
|
||||||
|
'target': '100 shares per piece',
|
||||||
|
'measurement': 'Social sharing tracking',
|
||||||
|
'frequency': 'Per content piece'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'metric': 'Time on Page',
|
||||||
|
'target': '3+ minutes average',
|
||||||
|
'measurement': 'Google Analytics',
|
||||||
|
'frequency': 'Monthly'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'seo_kpis': [
|
||||||
|
{
|
||||||
|
'metric': 'Keyword Rankings',
|
||||||
|
'target': 'Top 10 for 20 target keywords',
|
||||||
|
'measurement': 'SEO tools',
|
||||||
|
'frequency': 'Weekly'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'metric': 'Backlink Growth',
|
||||||
|
'target': '10 quality backlinks per month',
|
||||||
|
'measurement': 'Backlink analysis tools',
|
||||||
|
'frequency': 'Monthly'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
def _create_implementation_roadmap(self, business_info: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
|
"""Create implementation roadmap."""
|
||||||
|
return {
|
||||||
|
'phase_1': {
|
||||||
|
'name': 'Foundation (Month 1)',
|
||||||
|
'objectives': ['Content audit', 'Pillar page creation', 'Basic SEO setup'],
|
||||||
|
'deliverables': ['Content strategy document', '4 pillar pages', 'SEO foundation'],
|
||||||
|
'success_criteria': ['All pillar pages published', 'SEO tracking implemented']
|
||||||
|
},
|
||||||
|
'phase_2': {
|
||||||
|
'name': 'Content Creation (Months 2-3)',
|
||||||
|
'objectives': ['Regular content publication', 'Social media activation', 'Email marketing'],
|
||||||
|
'deliverables': ['24 blog posts', 'Social media calendar', 'Email sequences'],
|
||||||
|
'success_criteria': ['Consistent publishing schedule', '20% traffic increase']
|
||||||
|
},
|
||||||
|
'phase_3': {
|
||||||
|
'name': 'Optimization (Months 4-6)',
|
||||||
|
'objectives': ['Performance optimization', 'Advanced SEO', 'Conversion optimization'],
|
||||||
|
'deliverables': ['Optimized content', 'Advanced SEO implementation', 'Conversion funnels'],
|
||||||
|
'success_criteria': ['50% traffic increase', 'Improved conversion rates']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Utility methods
|
||||||
|
def _extract_market_position(self, analysis: str) -> str:
|
||||||
|
"""Extract market positioning from AI analysis."""
|
||||||
|
return "Market positioning insights extracted from AI analysis"
|
||||||
|
|
||||||
|
def _extract_content_gaps(self, analysis: str) -> List[str]:
|
||||||
|
"""Extract content gaps from AI analysis."""
|
||||||
|
return ["Educational content gap", "Technical documentation gap", "Case study gap"]
|
||||||
|
|
||||||
|
def _extract_competitive_advantages(self, analysis: str) -> List[str]:
|
||||||
|
"""Extract competitive advantages from AI analysis."""
|
||||||
|
return ["Unique technology approach", "Industry expertise", "Customer success focus"]
|
||||||
|
|
||||||
|
def _extract_audience_insights(self, analysis: str) -> Dict[str, Any]:
|
||||||
|
"""Extract audience insights from AI analysis."""
|
||||||
|
return {
|
||||||
|
'pain_points': ["Complex implementation", "Limited resources", "ROI concerns"],
|
||||||
|
'content_preferences': ["Visual content", "Step-by-step guides", "Real examples"],
|
||||||
|
'consumption_patterns': ["Mobile-first", "Video preferred", "Quick consumption"]
|
||||||
|
}
|
||||||
|
|
||||||
|
def _calculate_pillar_distribution(self, calendar_items: List[Dict[str, Any]], content_pillars: List[Dict[str, Any]]) -> Dict[str, int]:
|
||||||
|
"""Calculate content distribution across pillars."""
|
||||||
|
distribution = {}
|
||||||
|
for pillar in content_pillars:
|
||||||
|
count = len([item for item in calendar_items if item['pillar'] == pillar['name']])
|
||||||
|
distribution[pillar['name']] = count
|
||||||
|
return distribution
|
||||||
|
|
||||||
|
def _generate_strategic_insights(self, business_info: Dict[str, Any], content_pillars: List[Dict[str, Any]]) -> Dict[str, Any]:
|
||||||
|
"""Generate strategic insights and recommendations."""
|
||||||
|
return {
|
||||||
|
'key_insights': [
|
||||||
|
"Focus on educational content for early funnel engagement",
|
||||||
|
"Leverage customer success stories for conversion",
|
||||||
|
"Develop thought leadership for brand authority",
|
||||||
|
"Create product education for user adoption"
|
||||||
|
],
|
||||||
|
'strategic_recommendations': [
|
||||||
|
"Implement topic cluster strategy for SEO dominance",
|
||||||
|
"Create pillar page for each content theme",
|
||||||
|
"Develop comprehensive content repurposing workflow",
|
||||||
|
"Establish thought leadership through industry insights"
|
||||||
|
],
|
||||||
|
'risk_mitigation': [
|
||||||
|
"Diversify content topics to avoid algorithm dependency",
|
||||||
|
"Create evergreen content for long-term value",
|
||||||
|
"Build email list to reduce platform dependency",
|
||||||
|
"Monitor competitor content to maintain differentiation"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def render_ai_content_strategy():
|
||||||
|
"""Render the AI Content Strategy interface."""
|
||||||
|
|
||||||
|
st.title("🧠 AI Content Strategy Generator")
|
||||||
|
st.markdown("**Generate comprehensive content strategies powered by AI intelligence**")
|
||||||
|
|
||||||
|
# Configuration form
|
||||||
|
st.header("📋 Business Information")
|
||||||
|
|
||||||
|
with st.form("content_strategy_form"):
|
||||||
|
col1, col2 = st.columns(2)
|
||||||
|
|
||||||
|
with col1:
|
||||||
|
industry = st.selectbox(
|
||||||
|
"Industry",
|
||||||
|
[
|
||||||
|
"Technology & Software",
|
||||||
|
"Marketing & Advertising",
|
||||||
|
"Healthcare",
|
||||||
|
"Finance & Fintech",
|
||||||
|
"E-commerce",
|
||||||
|
"Education",
|
||||||
|
"Manufacturing",
|
||||||
|
"Professional Services",
|
||||||
|
"Other"
|
||||||
|
],
|
||||||
|
index=0
|
||||||
|
)
|
||||||
|
|
||||||
|
target_audience = st.text_area(
|
||||||
|
"Target Audience",
|
||||||
|
placeholder="Describe your ideal customers, their roles, challenges, and goals...",
|
||||||
|
height=100
|
||||||
|
)
|
||||||
|
|
||||||
|
business_goals = st.multiselect(
|
||||||
|
"Business Goals",
|
||||||
|
[
|
||||||
|
"Increase brand awareness",
|
||||||
|
"Generate leads",
|
||||||
|
"Drive website traffic",
|
||||||
|
"Establish thought leadership",
|
||||||
|
"Improve customer education",
|
||||||
|
"Support sales process",
|
||||||
|
"Enhance customer retention",
|
||||||
|
"Launch new product/service"
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
with col2:
|
||||||
|
content_objectives = st.multiselect(
|
||||||
|
"Content Objectives",
|
||||||
|
[
|
||||||
|
"SEO improvement",
|
||||||
|
"Social media engagement",
|
||||||
|
"Email marketing",
|
||||||
|
"Lead nurturing",
|
||||||
|
"Customer education",
|
||||||
|
"Brand storytelling",
|
||||||
|
"Product demonstration",
|
||||||
|
"Community building"
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
budget = st.selectbox(
|
||||||
|
"Monthly Content Budget",
|
||||||
|
[
|
||||||
|
"No budget",
|
||||||
|
"Under $1,000",
|
||||||
|
"$1,000 - $5,000",
|
||||||
|
"$5,000 - $10,000",
|
||||||
|
"$10,000 - $25,000",
|
||||||
|
"$25,000+"
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
timeline = st.selectbox(
|
||||||
|
"Strategy Timeline",
|
||||||
|
[
|
||||||
|
"3 months",
|
||||||
|
"6 months",
|
||||||
|
"1 year",
|
||||||
|
"Ongoing"
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
# Additional context
|
||||||
|
st.subheader("Additional Context")
|
||||||
|
|
||||||
|
current_challenges = st.text_area(
|
||||||
|
"Current Content Challenges",
|
||||||
|
placeholder="What content challenges are you currently facing?",
|
||||||
|
height=80
|
||||||
|
)
|
||||||
|
|
||||||
|
competitive_landscape = st.text_area(
|
||||||
|
"Competitive Landscape",
|
||||||
|
placeholder="Describe your main competitors and their content approach...",
|
||||||
|
height=80
|
||||||
|
)
|
||||||
|
|
||||||
|
submit_strategy = st.form_submit_button("🧠 Generate AI Content Strategy", type="primary")
|
||||||
|
|
||||||
|
# Process strategy generation
|
||||||
|
if submit_strategy:
|
||||||
|
if target_audience and business_goals and content_objectives:
|
||||||
|
# Prepare business information
|
||||||
|
business_info = {
|
||||||
|
'industry': industry,
|
||||||
|
'target_audience': target_audience,
|
||||||
|
'business_goals': business_goals,
|
||||||
|
'content_objectives': content_objectives,
|
||||||
|
'budget': budget,
|
||||||
|
'timeline': timeline,
|
||||||
|
'current_challenges': current_challenges,
|
||||||
|
'competitive_landscape': competitive_landscape
|
||||||
|
}
|
||||||
|
|
||||||
|
# Initialize generator
|
||||||
|
if 'strategy_generator' not in st.session_state:
|
||||||
|
st.session_state.strategy_generator = AIContentStrategyGenerator()
|
||||||
|
|
||||||
|
generator = st.session_state.strategy_generator
|
||||||
|
|
||||||
|
with st.spinner("🧠 Generating AI-powered content strategy..."):
|
||||||
|
strategy_results = generator.generate_content_strategy(business_info)
|
||||||
|
|
||||||
|
if 'error' not in strategy_results:
|
||||||
|
st.success("✅ Content strategy generated successfully!")
|
||||||
|
|
||||||
|
# Store results in session state
|
||||||
|
st.session_state.strategy_results = strategy_results
|
||||||
|
|
||||||
|
# Display results
|
||||||
|
render_strategy_results_dashboard(strategy_results)
|
||||||
|
else:
|
||||||
|
st.error(f"❌ Strategy generation failed: {strategy_results['error']}")
|
||||||
|
else:
|
||||||
|
st.warning("⚠️ Please fill in target audience, business goals, and content objectives.")
|
||||||
|
|
||||||
|
# Show previous results if available
|
||||||
|
elif 'strategy_results' in st.session_state:
|
||||||
|
st.info("🧠 Showing previous strategy results")
|
||||||
|
render_strategy_results_dashboard(st.session_state.strategy_results)
|
||||||
|
|
||||||
|
|
||||||
|
def render_strategy_results_dashboard(results: Dict[str, Any]):
|
||||||
|
"""Render comprehensive strategy results dashboard."""
|
||||||
|
|
||||||
|
# Strategy overview
|
||||||
|
st.header("📊 Content Strategy Overview")
|
||||||
|
|
||||||
|
business_analysis = results.get('business_analysis', {})
|
||||||
|
content_pillars = results.get('content_pillars', [])
|
||||||
|
content_calendar = results.get('content_calendar', {})
|
||||||
|
|
||||||
|
# Key metrics overview
|
||||||
|
col1, col2, col3, col4 = st.columns(4)
|
||||||
|
|
||||||
|
with col1:
|
||||||
|
st.metric("Content Pillars", len(content_pillars))
|
||||||
|
|
||||||
|
with col2:
|
||||||
|
calendar_items = content_calendar.get('calendar_items', [])
|
||||||
|
st.metric("Content Pieces", len(calendar_items))
|
||||||
|
|
||||||
|
with col3:
|
||||||
|
timeline = content_calendar.get('timeline', 'Not specified')
|
||||||
|
st.metric("Timeline", timeline)
|
||||||
|
|
||||||
|
with col4:
|
||||||
|
total_hours = sum(item.get('estimated_hours', 0) for item in calendar_items)
|
||||||
|
st.metric("Est. Hours", f"{total_hours}h")
|
||||||
|
|
||||||
|
# Strategy tabs
|
||||||
|
tab1, tab2, tab3, tab4, tab5, tab6 = st.tabs([
|
||||||
|
"🧠 AI Insights",
|
||||||
|
"🏛️ Content Pillars",
|
||||||
|
"📅 Content Calendar",
|
||||||
|
"🎯 Topic Clusters",
|
||||||
|
"📢 Distribution",
|
||||||
|
"📊 Implementation"
|
||||||
|
])
|
||||||
|
|
||||||
|
with tab1:
|
||||||
|
if business_analysis:
|
||||||
|
st.subheader("Business Analysis & Insights")
|
||||||
|
|
||||||
|
# Market positioning
|
||||||
|
market_position = business_analysis.get('market_position', '')
|
||||||
|
if market_position:
|
||||||
|
st.markdown("#### 🎯 Market Positioning")
|
||||||
|
st.info(market_position)
|
||||||
|
|
||||||
|
# Content gaps
|
||||||
|
content_gaps = business_analysis.get('content_gaps', [])
|
||||||
|
if content_gaps:
|
||||||
|
st.markdown("#### 🔍 Content Gaps Identified")
|
||||||
|
for gap in content_gaps:
|
||||||
|
st.warning(f"📌 {gap}")
|
||||||
|
|
||||||
|
# Competitive advantages
|
||||||
|
advantages = business_analysis.get('competitive_advantages', [])
|
||||||
|
if advantages:
|
||||||
|
st.markdown("#### 🏆 Competitive Advantages")
|
||||||
|
for advantage in advantages:
|
||||||
|
st.success(f"✅ {advantage}")
|
||||||
|
|
||||||
|
# AI insights
|
||||||
|
ai_insights = results.get('ai_insights', {})
|
||||||
|
if ai_insights:
|
||||||
|
st.markdown("#### 🧠 Strategic AI Insights")
|
||||||
|
|
||||||
|
insights = ai_insights.get('key_insights', [])
|
||||||
|
for insight in insights:
|
||||||
|
st.info(f"💡 {insight}")
|
||||||
|
|
||||||
|
recommendations = ai_insights.get('strategic_recommendations', [])
|
||||||
|
if recommendations:
|
||||||
|
st.markdown("#### 🎯 Strategic Recommendations")
|
||||||
|
for rec in recommendations:
|
||||||
|
st.success(f"📋 {rec}")
|
||||||
|
|
||||||
|
with tab2:
|
||||||
|
if content_pillars:
|
||||||
|
st.subheader("Content Pillars Strategy")
|
||||||
|
|
||||||
|
# Pillars overview chart
|
||||||
|
pillar_names = [pillar['name'] for pillar in content_pillars]
|
||||||
|
pillar_ideas = [len(pillar['content_ideas']) for pillar in content_pillars]
|
||||||
|
|
||||||
|
fig = px.bar(
|
||||||
|
x=pillar_names,
|
||||||
|
y=pillar_ideas,
|
||||||
|
title="Content Ideas per Pillar",
|
||||||
|
labels={'x': 'Content Pillars', 'y': 'Number of Ideas'}
|
||||||
|
)
|
||||||
|
st.plotly_chart(fig, use_container_width=True)
|
||||||
|
|
||||||
|
# Detailed pillar information
|
||||||
|
for pillar in content_pillars:
|
||||||
|
with st.expander(f"🏛️ {pillar['name']}", expanded=False):
|
||||||
|
st.markdown(f"**Description:** {pillar['description']}")
|
||||||
|
|
||||||
|
col1, col2 = st.columns(2)
|
||||||
|
|
||||||
|
with col1:
|
||||||
|
st.markdown("**Target Keywords:**")
|
||||||
|
for keyword in pillar['target_keywords']:
|
||||||
|
st.code(keyword)
|
||||||
|
|
||||||
|
st.markdown("**Content Types:**")
|
||||||
|
for content_type in pillar['content_types']:
|
||||||
|
st.write(f"• {content_type}")
|
||||||
|
|
||||||
|
with col2:
|
||||||
|
st.markdown("**Success Metrics:**")
|
||||||
|
for metric in pillar['success_metrics']:
|
||||||
|
st.write(f"📊 {metric}")
|
||||||
|
|
||||||
|
st.markdown("**Content Ideas:**")
|
||||||
|
for idea in pillar['content_ideas']:
|
||||||
|
st.write(f"💡 {idea}")
|
||||||
|
|
||||||
|
with tab3:
|
||||||
|
if content_calendar:
|
||||||
|
st.subheader("Content Calendar & Planning")
|
||||||
|
|
||||||
|
calendar_items = content_calendar.get('calendar_items', [])
|
||||||
|
|
||||||
|
if calendar_items:
|
||||||
|
# Calendar overview
|
||||||
|
df_calendar = pd.DataFrame(calendar_items)
|
||||||
|
|
||||||
|
# Priority distribution
|
||||||
|
priority_counts = df_calendar['priority'].value_counts()
|
||||||
|
fig_priority = px.pie(
|
||||||
|
values=priority_counts.values,
|
||||||
|
names=priority_counts.index,
|
||||||
|
title="Content Priority Distribution"
|
||||||
|
)
|
||||||
|
st.plotly_chart(fig_priority, use_container_width=True)
|
||||||
|
|
||||||
|
# Content calendar table
|
||||||
|
st.markdown("#### 📅 Detailed Content Calendar")
|
||||||
|
|
||||||
|
display_df = df_calendar[[
|
||||||
|
'period', 'pillar', 'content_type', 'topic',
|
||||||
|
'priority', 'estimated_hours'
|
||||||
|
]].copy()
|
||||||
|
|
||||||
|
display_df.columns = [
|
||||||
|
'Period', 'Pillar', 'Content Type', 'Topic',
|
||||||
|
'Priority', 'Est. Hours'
|
||||||
|
]
|
||||||
|
|
||||||
|
st.dataframe(
|
||||||
|
display_df,
|
||||||
|
column_config={
|
||||||
|
"Priority": st.column_config.SelectboxColumn(
|
||||||
|
"Priority",
|
||||||
|
options=["High", "Medium", "Low"]
|
||||||
|
),
|
||||||
|
"Est. Hours": st.column_config.NumberColumn(
|
||||||
|
"Est. Hours",
|
||||||
|
format="%d h"
|
||||||
|
)
|
||||||
|
},
|
||||||
|
hide_index=True,
|
||||||
|
use_container_width=True
|
||||||
|
)
|
||||||
|
|
||||||
|
# Export calendar
|
||||||
|
csv = df_calendar.to_csv(index=False)
|
||||||
|
st.download_button(
|
||||||
|
label="📥 Download Content Calendar",
|
||||||
|
data=csv,
|
||||||
|
file_name=f"content_calendar_{datetime.now().strftime('%Y%m%d')}.csv",
|
||||||
|
mime="text/csv"
|
||||||
|
)
|
||||||
|
|
||||||
|
with tab4:
|
||||||
|
topic_clusters = results.get('topic_clusters', [])
|
||||||
|
if topic_clusters:
|
||||||
|
st.subheader("SEO Topic Clusters")
|
||||||
|
|
||||||
|
for cluster in topic_clusters:
|
||||||
|
with st.expander(f"🎯 {cluster['cluster_name']}", expanded=False):
|
||||||
|
col1, col2 = st.columns(2)
|
||||||
|
|
||||||
|
with col1:
|
||||||
|
st.markdown(f"**Primary Topic:** {cluster['primary_topic']}")
|
||||||
|
st.markdown(f"**SEO Opportunity:** {cluster['seo_opportunity']}")
|
||||||
|
st.markdown(f"**Linking Strategy:** {cluster['internal_linking_strategy']}")
|
||||||
|
|
||||||
|
with col2:
|
||||||
|
st.markdown("**Supporting Topics:**")
|
||||||
|
for topic in cluster['supporting_topics']:
|
||||||
|
st.code(topic)
|
||||||
|
|
||||||
|
st.markdown("**Content Pieces:**")
|
||||||
|
content_pieces = cluster['content_pieces']
|
||||||
|
df_pieces = pd.DataFrame(content_pieces)
|
||||||
|
st.dataframe(df_pieces, hide_index=True, use_container_width=True)
|
||||||
|
|
||||||
|
with tab5:
|
||||||
|
distribution_strategy = results.get('distribution_strategy', {})
|
||||||
|
if distribution_strategy:
|
||||||
|
st.subheader("Content Distribution Strategy")
|
||||||
|
|
||||||
|
# Primary channels
|
||||||
|
primary_channels = distribution_strategy.get('primary_channels', [])
|
||||||
|
if primary_channels:
|
||||||
|
st.markdown("#### 📢 Primary Distribution Channels")
|
||||||
|
df_primary = pd.DataFrame(primary_channels)
|
||||||
|
st.dataframe(df_primary, hide_index=True, use_container_width=True)
|
||||||
|
|
||||||
|
# Secondary channels
|
||||||
|
secondary_channels = distribution_strategy.get('secondary_channels', [])
|
||||||
|
if secondary_channels:
|
||||||
|
st.markdown("#### 📺 Secondary Distribution Channels")
|
||||||
|
df_secondary = pd.DataFrame(secondary_channels)
|
||||||
|
st.dataframe(df_secondary, hide_index=True, use_container_width=True)
|
||||||
|
|
||||||
|
# Repurposing strategy
|
||||||
|
repurposing = distribution_strategy.get('repurposing_strategy', {})
|
||||||
|
if repurposing:
|
||||||
|
st.markdown("#### ♻️ Content Repurposing Strategy")
|
||||||
|
for strategy, description in repurposing.items():
|
||||||
|
st.write(f"**{strategy.replace('_', ' ').title()}:** {description}")
|
||||||
|
|
||||||
|
with tab6:
|
||||||
|
# Implementation roadmap
|
||||||
|
roadmap = results.get('implementation_roadmap', {})
|
||||||
|
kpi_framework = results.get('kpi_framework', {})
|
||||||
|
|
||||||
|
if roadmap:
|
||||||
|
st.subheader("Implementation Roadmap")
|
||||||
|
|
||||||
|
for phase_key, phase_data in roadmap.items():
|
||||||
|
with st.expander(f"📋 {phase_data['name']}", expanded=False):
|
||||||
|
st.markdown(f"**Objectives:**")
|
||||||
|
for objective in phase_data['objectives']:
|
||||||
|
st.write(f"• {objective}")
|
||||||
|
|
||||||
|
st.markdown(f"**Deliverables:**")
|
||||||
|
for deliverable in phase_data['deliverables']:
|
||||||
|
st.write(f"📦 {deliverable}")
|
||||||
|
|
||||||
|
st.markdown(f"**Success Criteria:**")
|
||||||
|
for criteria in phase_data['success_criteria']:
|
||||||
|
st.write(f"✅ {criteria}")
|
||||||
|
|
||||||
|
if kpi_framework:
|
||||||
|
st.subheader("KPI Framework")
|
||||||
|
|
||||||
|
# Primary KPIs
|
||||||
|
primary_kpis = kpi_framework.get('primary_kpis', [])
|
||||||
|
if primary_kpis:
|
||||||
|
st.markdown("#### 🎯 Primary KPIs")
|
||||||
|
df_primary_kpis = pd.DataFrame(primary_kpis)
|
||||||
|
st.dataframe(df_primary_kpis, hide_index=True, use_container_width=True)
|
||||||
|
|
||||||
|
# Content KPIs
|
||||||
|
content_kpis = kpi_framework.get('content_kpis', [])
|
||||||
|
if content_kpis:
|
||||||
|
st.markdown("#### 📝 Content KPIs")
|
||||||
|
df_content_kpis = pd.DataFrame(content_kpis)
|
||||||
|
st.dataframe(df_content_kpis, hide_index=True, use_container_width=True)
|
||||||
|
|
||||||
|
# Export functionality
|
||||||
|
st.markdown("---")
|
||||||
|
col1, col2, col3 = st.columns(3)
|
||||||
|
|
||||||
|
with col1:
|
||||||
|
if st.button("📥 Export Full Strategy", use_container_width=True):
|
||||||
|
strategy_json = json.dumps(results, indent=2, default=str)
|
||||||
|
st.download_button(
|
||||||
|
label="Download JSON Strategy",
|
||||||
|
data=strategy_json,
|
||||||
|
file_name=f"content_strategy_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json",
|
||||||
|
mime="application/json"
|
||||||
|
)
|
||||||
|
|
||||||
|
with col2:
|
||||||
|
if st.button("📊 Export Calendar", use_container_width=True):
|
||||||
|
calendar_items = content_calendar.get('calendar_items', [])
|
||||||
|
if calendar_items:
|
||||||
|
df_calendar = pd.DataFrame(calendar_items)
|
||||||
|
csv = df_calendar.to_csv(index=False)
|
||||||
|
st.download_button(
|
||||||
|
label="Download CSV Calendar",
|
||||||
|
data=csv,
|
||||||
|
file_name=f"content_calendar_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv",
|
||||||
|
mime="text/csv"
|
||||||
|
)
|
||||||
|
|
||||||
|
with col3:
|
||||||
|
if st.button("🔄 Generate New Strategy", use_container_width=True):
|
||||||
|
if 'strategy_results' in st.session_state:
|
||||||
|
del st.session_state.strategy_results
|
||||||
|
st.rerun()
|
||||||
|
|
||||||
|
|
||||||
|
# Main execution
|
||||||
|
if __name__ == "__main__":
|
||||||
|
render_ai_content_strategy()
|
||||||
919
ToBeMigrated/ai_seo_tools/enterprise_seo_suite.py
Normal file
919
ToBeMigrated/ai_seo_tools/enterprise_seo_suite.py
Normal file
@@ -0,0 +1,919 @@
|
|||||||
|
"""
|
||||||
|
Enterprise SEO Command Center
|
||||||
|
|
||||||
|
Unified AI-powered SEO suite that orchestrates all existing tools into
|
||||||
|
intelligent workflows for enterprise-level SEO management.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import streamlit as st
|
||||||
|
import asyncio
|
||||||
|
import pandas as pd
|
||||||
|
from typing import Dict, Any, List, Optional, Tuple
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
import json
|
||||||
|
from loguru import logger
|
||||||
|
|
||||||
|
# Import existing SEO tools
|
||||||
|
from .on_page_seo_analyzer import fetch_seo_data
|
||||||
|
from .content_gap_analysis.enhanced_analyzer import EnhancedContentGapAnalyzer
|
||||||
|
from .technical_seo_crawler.crawler import TechnicalSEOCrawler
|
||||||
|
from .weburl_seo_checker import url_seo_checker
|
||||||
|
from .google_pagespeed_insights import google_pagespeed_insights
|
||||||
|
from ..gpt_providers.text_generation.main_text_generation import llm_text_gen
|
||||||
|
|
||||||
|
# Import the new enterprise tools
|
||||||
|
from .google_search_console_integration import GoogleSearchConsoleAnalyzer, render_gsc_integration
|
||||||
|
from .ai_content_strategy import AIContentStrategyGenerator, render_ai_content_strategy
|
||||||
|
|
||||||
|
class EnterpriseSEOSuite:
|
||||||
|
"""
|
||||||
|
Enterprise-level SEO suite orchestrating all tools into intelligent workflows.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
"""Initialize the enterprise SEO suite."""
|
||||||
|
self.gap_analyzer = EnhancedContentGapAnalyzer()
|
||||||
|
self.technical_crawler = TechnicalSEOCrawler()
|
||||||
|
|
||||||
|
# Initialize new enterprise tools
|
||||||
|
self.gsc_analyzer = GoogleSearchConsoleAnalyzer()
|
||||||
|
self.content_strategy_generator = AIContentStrategyGenerator()
|
||||||
|
|
||||||
|
# SEO workflow templates
|
||||||
|
self.workflow_templates = {
|
||||||
|
'complete_audit': 'Complete SEO Audit',
|
||||||
|
'content_strategy': 'Content Strategy Development',
|
||||||
|
'technical_optimization': 'Technical SEO Optimization',
|
||||||
|
'competitor_intelligence': 'Competitive Intelligence',
|
||||||
|
'keyword_domination': 'Keyword Domination Strategy',
|
||||||
|
'local_seo': 'Local SEO Optimization',
|
||||||
|
'enterprise_monitoring': 'Enterprise SEO Monitoring'
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info("Enterprise SEO Suite initialized")
|
||||||
|
|
||||||
|
async def execute_complete_seo_audit(self, website_url: str, competitors: List[str],
|
||||||
|
target_keywords: List[str]) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Execute a comprehensive enterprise SEO audit combining all tools.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
website_url: Primary website to audit
|
||||||
|
competitors: List of competitor URLs (max 5)
|
||||||
|
target_keywords: Primary keywords to optimize for
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Comprehensive audit results with prioritized action plan
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
st.info("🚀 Initiating Complete Enterprise SEO Audit...")
|
||||||
|
|
||||||
|
audit_results = {
|
||||||
|
'audit_timestamp': datetime.utcnow().isoformat(),
|
||||||
|
'website_url': website_url,
|
||||||
|
'competitors': competitors[:5],
|
||||||
|
'target_keywords': target_keywords,
|
||||||
|
'technical_audit': {},
|
||||||
|
'content_analysis': {},
|
||||||
|
'competitive_intelligence': {},
|
||||||
|
'on_page_analysis': {},
|
||||||
|
'performance_metrics': {},
|
||||||
|
'strategic_recommendations': {},
|
||||||
|
'priority_action_plan': []
|
||||||
|
}
|
||||||
|
|
||||||
|
# Phase 1: Technical SEO Audit
|
||||||
|
with st.expander("🔧 Technical SEO Analysis", expanded=True):
|
||||||
|
st.info("Analyzing technical SEO factors...")
|
||||||
|
technical_results = await self._run_technical_audit(website_url)
|
||||||
|
audit_results['technical_audit'] = technical_results
|
||||||
|
st.success("✅ Technical audit completed")
|
||||||
|
|
||||||
|
# Phase 2: Content Gap Analysis
|
||||||
|
with st.expander("📊 Content Intelligence Analysis", expanded=True):
|
||||||
|
st.info("Analyzing content gaps and opportunities...")
|
||||||
|
content_results = await self._run_content_analysis(
|
||||||
|
website_url, competitors, target_keywords
|
||||||
|
)
|
||||||
|
audit_results['content_analysis'] = content_results
|
||||||
|
st.success("✅ Content analysis completed")
|
||||||
|
|
||||||
|
# Phase 3: On-Page SEO Analysis
|
||||||
|
with st.expander("🔍 On-Page SEO Analysis", expanded=True):
|
||||||
|
st.info("Analyzing on-page SEO factors...")
|
||||||
|
onpage_results = await self._run_onpage_analysis(website_url)
|
||||||
|
audit_results['on_page_analysis'] = onpage_results
|
||||||
|
st.success("✅ On-page analysis completed")
|
||||||
|
|
||||||
|
# Phase 4: Performance Analysis
|
||||||
|
with st.expander("⚡ Performance Analysis", expanded=True):
|
||||||
|
st.info("Analyzing website performance...")
|
||||||
|
performance_results = await self._run_performance_analysis(website_url)
|
||||||
|
audit_results['performance_metrics'] = performance_results
|
||||||
|
st.success("✅ Performance analysis completed")
|
||||||
|
|
||||||
|
# Phase 5: AI-Powered Strategic Recommendations
|
||||||
|
with st.expander("🤖 AI Strategic Analysis", expanded=True):
|
||||||
|
st.info("Generating AI-powered strategic recommendations...")
|
||||||
|
strategic_analysis = await self._generate_strategic_recommendations(audit_results)
|
||||||
|
audit_results['strategic_recommendations'] = strategic_analysis
|
||||||
|
|
||||||
|
# Generate prioritized action plan
|
||||||
|
action_plan = await self._create_priority_action_plan(audit_results)
|
||||||
|
audit_results['priority_action_plan'] = action_plan
|
||||||
|
st.success("✅ Strategic analysis completed")
|
||||||
|
|
||||||
|
return audit_results
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
error_msg = f"Error in complete SEO audit: {str(e)}"
|
||||||
|
logger.error(error_msg, exc_info=True)
|
||||||
|
st.error(error_msg)
|
||||||
|
return {'error': error_msg}
|
||||||
|
|
||||||
|
async def _run_technical_audit(self, website_url: str) -> Dict[str, Any]:
|
||||||
|
"""Run comprehensive technical SEO audit."""
|
||||||
|
try:
|
||||||
|
# Use existing technical crawler
|
||||||
|
technical_results = self.technical_crawler.analyze_website_technical_seo(
|
||||||
|
website_url, crawl_depth=3, max_pages=100
|
||||||
|
)
|
||||||
|
|
||||||
|
# Enhance with additional technical checks
|
||||||
|
enhanced_results = {
|
||||||
|
'crawler_results': technical_results,
|
||||||
|
'critical_issues': self._identify_critical_technical_issues(technical_results),
|
||||||
|
'performance_score': self._calculate_technical_score(technical_results),
|
||||||
|
'priority_fixes': self._prioritize_technical_fixes(technical_results)
|
||||||
|
}
|
||||||
|
|
||||||
|
return enhanced_results
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Technical audit error: {str(e)}")
|
||||||
|
return {'error': str(e)}
|
||||||
|
|
||||||
|
async def _run_content_analysis(self, website_url: str, competitors: List[str],
|
||||||
|
keywords: List[str]) -> Dict[str, Any]:
|
||||||
|
"""Run comprehensive content gap analysis."""
|
||||||
|
try:
|
||||||
|
# Use existing content gap analyzer
|
||||||
|
content_results = self.gap_analyzer.analyze_comprehensive_gap(
|
||||||
|
website_url, competitors, keywords, industry="general"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Enhance with content strategy insights
|
||||||
|
enhanced_results = {
|
||||||
|
'gap_analysis': content_results,
|
||||||
|
'content_opportunities': self._identify_content_opportunities(content_results),
|
||||||
|
'keyword_strategy': self._develop_keyword_strategy(content_results),
|
||||||
|
'competitive_advantages': self._find_competitive_advantages(content_results)
|
||||||
|
}
|
||||||
|
|
||||||
|
return enhanced_results
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Content analysis error: {str(e)}")
|
||||||
|
return {'error': str(e)}
|
||||||
|
|
||||||
|
async def _run_onpage_analysis(self, website_url: str) -> Dict[str, Any]:
|
||||||
|
"""Run on-page SEO analysis."""
|
||||||
|
try:
|
||||||
|
# Use existing on-page analyzer
|
||||||
|
onpage_data = fetch_seo_data(website_url)
|
||||||
|
|
||||||
|
# Enhanced analysis
|
||||||
|
enhanced_results = {
|
||||||
|
'seo_data': onpage_data,
|
||||||
|
'optimization_score': self._calculate_onpage_score(onpage_data),
|
||||||
|
'meta_optimization': self._analyze_meta_optimization(onpage_data),
|
||||||
|
'content_optimization': self._analyze_content_optimization(onpage_data)
|
||||||
|
}
|
||||||
|
|
||||||
|
return enhanced_results
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"On-page analysis error: {str(e)}")
|
||||||
|
return {'error': str(e)}
|
||||||
|
|
||||||
|
async def _run_performance_analysis(self, website_url: str) -> Dict[str, Any]:
|
||||||
|
"""Run website performance analysis."""
|
||||||
|
try:
|
||||||
|
# Comprehensive performance metrics
|
||||||
|
performance_results = {
|
||||||
|
'core_web_vitals': await self._analyze_core_web_vitals(website_url),
|
||||||
|
'loading_performance': await self._analyze_loading_performance(website_url),
|
||||||
|
'mobile_optimization': await self._analyze_mobile_optimization(website_url),
|
||||||
|
'performance_score': 0 # Will be calculated
|
||||||
|
}
|
||||||
|
|
||||||
|
# Calculate overall performance score
|
||||||
|
performance_results['performance_score'] = self._calculate_performance_score(
|
||||||
|
performance_results
|
||||||
|
)
|
||||||
|
|
||||||
|
return performance_results
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Performance analysis error: {str(e)}")
|
||||||
|
return {'error': str(e)}
|
||||||
|
|
||||||
|
async def _generate_strategic_recommendations(self, audit_results: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
|
"""Generate AI-powered strategic recommendations."""
|
||||||
|
try:
|
||||||
|
# Compile audit summary for AI analysis
|
||||||
|
audit_summary = {
|
||||||
|
'technical_score': audit_results.get('technical_audit', {}).get('performance_score', 0),
|
||||||
|
'content_gaps': len(audit_results.get('content_analysis', {}).get('content_opportunities', [])),
|
||||||
|
'onpage_score': audit_results.get('on_page_analysis', {}).get('optimization_score', 0),
|
||||||
|
'performance_score': audit_results.get('performance_metrics', {}).get('performance_score', 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
strategic_prompt = f"""
|
||||||
|
Analyze this comprehensive SEO audit and provide strategic recommendations:
|
||||||
|
|
||||||
|
AUDIT SUMMARY:
|
||||||
|
- Technical SEO Score: {audit_summary['technical_score']}/100
|
||||||
|
- Content Gaps Identified: {audit_summary['content_gaps']}
|
||||||
|
- On-Page SEO Score: {audit_summary['onpage_score']}/100
|
||||||
|
- Performance Score: {audit_summary['performance_score']}/100
|
||||||
|
|
||||||
|
DETAILED FINDINGS:
|
||||||
|
Technical Issues: {json.dumps(audit_results.get('technical_audit', {}), indent=2)[:1000]}
|
||||||
|
Content Opportunities: {json.dumps(audit_results.get('content_analysis', {}), indent=2)[:1000]}
|
||||||
|
|
||||||
|
Provide strategic recommendations in these categories:
|
||||||
|
|
||||||
|
1. IMMEDIATE WINS (0-30 days):
|
||||||
|
- Quick technical fixes with high impact
|
||||||
|
- Content optimizations for existing pages
|
||||||
|
- Critical performance improvements
|
||||||
|
|
||||||
|
2. STRATEGIC INITIATIVES (1-3 months):
|
||||||
|
- Content strategy development
|
||||||
|
- Technical architecture improvements
|
||||||
|
- Competitive positioning strategies
|
||||||
|
|
||||||
|
3. LONG-TERM GROWTH (3-12 months):
|
||||||
|
- Authority building strategies
|
||||||
|
- Market expansion opportunities
|
||||||
|
- Advanced SEO techniques
|
||||||
|
|
||||||
|
4. RISK MITIGATION:
|
||||||
|
- Technical vulnerabilities to address
|
||||||
|
- Content gaps that competitors could exploit
|
||||||
|
- Performance issues affecting user experience
|
||||||
|
|
||||||
|
Provide specific, actionable recommendations with expected impact and effort estimates.
|
||||||
|
"""
|
||||||
|
|
||||||
|
strategic_analysis = llm_text_gen(
|
||||||
|
strategic_prompt,
|
||||||
|
system_prompt="You are an enterprise SEO strategist with 10+ years of experience. Provide detailed, actionable recommendations based on comprehensive audit data."
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'full_analysis': strategic_analysis,
|
||||||
|
'immediate_wins': self._extract_immediate_wins(strategic_analysis),
|
||||||
|
'strategic_initiatives': self._extract_strategic_initiatives(strategic_analysis),
|
||||||
|
'long_term_growth': self._extract_long_term_growth(strategic_analysis),
|
||||||
|
'risk_mitigation': self._extract_risk_mitigation(strategic_analysis)
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Strategic analysis error: {str(e)}")
|
||||||
|
return {'error': str(e)}
|
||||||
|
|
||||||
|
async def _create_priority_action_plan(self, audit_results: Dict[str, Any]) -> List[Dict[str, Any]]:
|
||||||
|
"""Create prioritized action plan from audit results."""
|
||||||
|
try:
|
||||||
|
action_plan = []
|
||||||
|
|
||||||
|
# Extract recommendations from all analysis phases
|
||||||
|
strategic_recs = audit_results.get('strategic_recommendations', {})
|
||||||
|
|
||||||
|
# Immediate wins (High priority, low effort)
|
||||||
|
immediate_wins = strategic_recs.get('immediate_wins', [])
|
||||||
|
for win in immediate_wins[:5]:
|
||||||
|
action_plan.append({
|
||||||
|
'category': 'Immediate Win',
|
||||||
|
'priority': 'Critical',
|
||||||
|
'effort': 'Low',
|
||||||
|
'timeframe': '0-30 days',
|
||||||
|
'action': win,
|
||||||
|
'expected_impact': 'High',
|
||||||
|
'source': 'Strategic Analysis'
|
||||||
|
})
|
||||||
|
|
||||||
|
# Technical fixes
|
||||||
|
technical_issues = audit_results.get('technical_audit', {}).get('critical_issues', [])
|
||||||
|
for issue in technical_issues[:3]:
|
||||||
|
action_plan.append({
|
||||||
|
'category': 'Technical SEO',
|
||||||
|
'priority': 'High',
|
||||||
|
'effort': 'Medium',
|
||||||
|
'timeframe': '1-4 weeks',
|
||||||
|
'action': issue,
|
||||||
|
'expected_impact': 'High',
|
||||||
|
'source': 'Technical Audit'
|
||||||
|
})
|
||||||
|
|
||||||
|
# Content opportunities
|
||||||
|
content_ops = audit_results.get('content_analysis', {}).get('content_opportunities', [])
|
||||||
|
for opportunity in content_ops[:3]:
|
||||||
|
action_plan.append({
|
||||||
|
'category': 'Content Strategy',
|
||||||
|
'priority': 'Medium',
|
||||||
|
'effort': 'High',
|
||||||
|
'timeframe': '2-8 weeks',
|
||||||
|
'action': opportunity,
|
||||||
|
'expected_impact': 'Medium',
|
||||||
|
'source': 'Content Analysis'
|
||||||
|
})
|
||||||
|
|
||||||
|
# Sort by priority and expected impact
|
||||||
|
priority_order = {'Critical': 0, 'High': 1, 'Medium': 2, 'Low': 3}
|
||||||
|
action_plan.sort(key=lambda x: priority_order.get(x['priority'], 4))
|
||||||
|
|
||||||
|
return action_plan[:15] # Top 15 actions
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Action plan creation error: {str(e)}")
|
||||||
|
return []
|
||||||
|
|
||||||
|
# Utility methods for analysis
|
||||||
|
def _identify_critical_technical_issues(self, technical_results: Dict[str, Any]) -> List[str]:
|
||||||
|
"""Identify critical technical SEO issues."""
|
||||||
|
critical_issues = []
|
||||||
|
|
||||||
|
# Add logic to identify critical technical issues
|
||||||
|
# This would analyze the technical_results and extract critical problems
|
||||||
|
|
||||||
|
return critical_issues
|
||||||
|
|
||||||
|
def _calculate_technical_score(self, technical_results: Dict[str, Any]) -> int:
|
||||||
|
"""Calculate technical SEO score."""
|
||||||
|
# Implement scoring algorithm based on technical audit results
|
||||||
|
return 75 # Placeholder
|
||||||
|
|
||||||
|
def _prioritize_technical_fixes(self, technical_results: Dict[str, Any]) -> List[str]:
|
||||||
|
"""Prioritize technical fixes by impact and effort."""
|
||||||
|
# Implement prioritization logic
|
||||||
|
return ["Fix broken links", "Optimize images", "Improve page speed"]
|
||||||
|
|
||||||
|
def _identify_content_opportunities(self, content_results: Dict[str, Any]) -> List[str]:
|
||||||
|
"""Identify top content opportunities."""
|
||||||
|
# Extract content opportunities from gap analysis
|
||||||
|
return ["Create FAQ content", "Develop comparison guides", "Write how-to articles"]
|
||||||
|
|
||||||
|
def _develop_keyword_strategy(self, content_results: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
|
"""Develop keyword strategy from content analysis."""
|
||||||
|
return {
|
||||||
|
'primary_keywords': [],
|
||||||
|
'secondary_keywords': [],
|
||||||
|
'long_tail_opportunities': [],
|
||||||
|
'competitor_gaps': []
|
||||||
|
}
|
||||||
|
|
||||||
|
def _find_competitive_advantages(self, content_results: Dict[str, Any]) -> List[str]:
|
||||||
|
"""Find competitive advantages from analysis."""
|
||||||
|
return ["Unique content angles", "Underserved niches", "Technical superiority"]
|
||||||
|
|
||||||
|
def _calculate_onpage_score(self, onpage_data: Dict[str, Any]) -> int:
|
||||||
|
"""Calculate on-page SEO score."""
|
||||||
|
return 80 # Placeholder
|
||||||
|
|
||||||
|
def _analyze_meta_optimization(self, onpage_data: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
|
"""Analyze meta tag optimization."""
|
||||||
|
return {'title_optimization': 'good', 'description_optimization': 'needs_work'}
|
||||||
|
|
||||||
|
def _analyze_content_optimization(self, onpage_data: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
|
"""Analyze content optimization."""
|
||||||
|
return {'keyword_density': 'optimal', 'content_length': 'adequate'}
|
||||||
|
|
||||||
|
async def _analyze_core_web_vitals(self, website_url: str) -> Dict[str, Any]:
|
||||||
|
"""Analyze Core Web Vitals."""
|
||||||
|
return {'lcp': 2.5, 'fid': 100, 'cls': 0.1}
|
||||||
|
|
||||||
|
async def _analyze_loading_performance(self, website_url: str) -> Dict[str, Any]:
|
||||||
|
"""Analyze loading performance."""
|
||||||
|
return {'ttfb': 200, 'fcp': 1.5, 'speed_index': 3.0}
|
||||||
|
|
||||||
|
async def _analyze_mobile_optimization(self, website_url: str) -> Dict[str, Any]:
|
||||||
|
"""Analyze mobile optimization."""
|
||||||
|
return {'mobile_friendly': True, 'responsive_design': True}
|
||||||
|
|
||||||
|
def _calculate_performance_score(self, performance_results: Dict[str, Any]) -> int:
|
||||||
|
"""Calculate overall performance score."""
|
||||||
|
return 85 # Placeholder
|
||||||
|
|
||||||
|
def _extract_immediate_wins(self, analysis: str) -> List[str]:
|
||||||
|
"""Extract immediate wins from strategic analysis."""
|
||||||
|
# Parse the AI analysis and extract immediate wins
|
||||||
|
lines = analysis.split('\n')
|
||||||
|
wins = []
|
||||||
|
in_immediate_section = False
|
||||||
|
|
||||||
|
for line in lines:
|
||||||
|
if 'IMMEDIATE WINS' in line.upper():
|
||||||
|
in_immediate_section = True
|
||||||
|
continue
|
||||||
|
elif 'STRATEGIC INITIATIVES' in line.upper():
|
||||||
|
in_immediate_section = False
|
||||||
|
continue
|
||||||
|
|
||||||
|
if in_immediate_section and line.strip().startswith('-'):
|
||||||
|
wins.append(line.strip().lstrip('- '))
|
||||||
|
|
||||||
|
return wins[:5]
|
||||||
|
|
||||||
|
def _extract_strategic_initiatives(self, analysis: str) -> List[str]:
|
||||||
|
"""Extract strategic initiatives from analysis."""
|
||||||
|
# Similar extraction logic for strategic initiatives
|
||||||
|
return ["Develop content hub", "Implement schema markup", "Build authority pages"]
|
||||||
|
|
||||||
|
def _extract_long_term_growth(self, analysis: str) -> List[str]:
|
||||||
|
"""Extract long-term growth strategies."""
|
||||||
|
return ["Market expansion", "Authority building", "Advanced technical SEO"]
|
||||||
|
|
||||||
|
def _extract_risk_mitigation(self, analysis: str) -> List[str]:
|
||||||
|
"""Extract risk mitigation strategies."""
|
||||||
|
return ["Fix technical vulnerabilities", "Address content gaps", "Improve performance"]
|
||||||
|
|
||||||
|
def execute_content_strategy_workflow(self, business_info: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Execute comprehensive content strategy workflow using AI insights.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
business_info: Business context and objectives
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Complete content strategy with implementation plan
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
st.info("🧠 Executing AI-powered content strategy workflow...")
|
||||||
|
|
||||||
|
# Generate AI content strategy
|
||||||
|
content_strategy = self.content_strategy_generator.generate_content_strategy(business_info)
|
||||||
|
|
||||||
|
# If GSC data is available, enhance with search insights
|
||||||
|
if business_info.get('gsc_site_url'):
|
||||||
|
gsc_insights = self.gsc_analyzer.analyze_search_performance(
|
||||||
|
business_info['gsc_site_url'],
|
||||||
|
business_info.get('gsc_date_range', 90)
|
||||||
|
)
|
||||||
|
content_strategy['gsc_insights'] = gsc_insights
|
||||||
|
|
||||||
|
# Generate SEO-optimized content recommendations
|
||||||
|
seo_content_recs = self._generate_seo_content_recommendations(content_strategy)
|
||||||
|
content_strategy['seo_recommendations'] = seo_content_recs
|
||||||
|
|
||||||
|
return content_strategy
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Content strategy workflow error: {str(e)}")
|
||||||
|
return {'error': str(e)}
|
||||||
|
|
||||||
|
def execute_search_intelligence_workflow(self, site_url: str, date_range: int = 90) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Execute comprehensive search intelligence workflow using GSC data.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
site_url: Website URL registered in GSC
|
||||||
|
date_range: Analysis period in days
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Complete search intelligence analysis with actionable insights
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
st.info("📊 Executing search intelligence workflow...")
|
||||||
|
|
||||||
|
# Analyze GSC performance
|
||||||
|
gsc_analysis = self.gsc_analyzer.analyze_search_performance(site_url, date_range)
|
||||||
|
|
||||||
|
# Enhance with technical SEO analysis
|
||||||
|
technical_analysis = self.technical_crawler.crawl_and_analyze(site_url)
|
||||||
|
gsc_analysis['technical_insights'] = technical_analysis
|
||||||
|
|
||||||
|
# Generate content gap analysis based on GSC keywords
|
||||||
|
if gsc_analysis.get('keyword_analysis'):
|
||||||
|
keywords = [kw['keyword'] for kw in gsc_analysis['keyword_analysis'].get('high_volume_keywords', [])]
|
||||||
|
content_gaps = self.gap_analyzer.analyze_content_gaps(
|
||||||
|
keywords[:10], # Top 10 keywords
|
||||||
|
site_url
|
||||||
|
)
|
||||||
|
gsc_analysis['content_gap_analysis'] = content_gaps
|
||||||
|
|
||||||
|
# Generate comprehensive recommendations
|
||||||
|
search_recommendations = self._generate_search_intelligence_recommendations(gsc_analysis)
|
||||||
|
gsc_analysis['comprehensive_recommendations'] = search_recommendations
|
||||||
|
|
||||||
|
return gsc_analysis
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Search intelligence workflow error: {str(e)}")
|
||||||
|
return {'error': str(e)}
|
||||||
|
|
||||||
|
def _generate_seo_content_recommendations(self, content_strategy: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
|
"""Generate SEO-optimized content recommendations based on strategy."""
|
||||||
|
try:
|
||||||
|
content_pillars = content_strategy.get('content_pillars', [])
|
||||||
|
|
||||||
|
seo_recommendations = {
|
||||||
|
'keyword_optimization': [],
|
||||||
|
'content_structure': [],
|
||||||
|
'internal_linking': [],
|
||||||
|
'technical_seo': []
|
||||||
|
}
|
||||||
|
|
||||||
|
for pillar in content_pillars:
|
||||||
|
# Keyword optimization recommendations
|
||||||
|
for keyword in pillar.get('target_keywords', []):
|
||||||
|
seo_recommendations['keyword_optimization'].append({
|
||||||
|
'pillar': pillar['name'],
|
||||||
|
'keyword': keyword,
|
||||||
|
'recommendation': f"Create comprehensive content targeting '{keyword}' with semantic variations",
|
||||||
|
'priority': 'High' if keyword in pillar['target_keywords'][:2] else 'Medium'
|
||||||
|
})
|
||||||
|
|
||||||
|
# Content structure recommendations
|
||||||
|
seo_recommendations['content_structure'].append({
|
||||||
|
'pillar': pillar['name'],
|
||||||
|
'recommendation': f"Create pillar page for {pillar['name']} with supporting cluster content",
|
||||||
|
'structure': 'Pillar + Cluster model'
|
||||||
|
})
|
||||||
|
|
||||||
|
# Internal linking strategy
|
||||||
|
seo_recommendations['internal_linking'] = [
|
||||||
|
"Link all cluster content to relevant pillar pages",
|
||||||
|
"Create topic-based internal linking structure",
|
||||||
|
"Use contextual anchor text with target keywords",
|
||||||
|
"Implement breadcrumb navigation for topic clusters"
|
||||||
|
]
|
||||||
|
|
||||||
|
# Technical SEO recommendations
|
||||||
|
seo_recommendations['technical_seo'] = [
|
||||||
|
"Optimize page speed for all content pages",
|
||||||
|
"Implement structured data for articles",
|
||||||
|
"Create XML sitemap sections for content categories",
|
||||||
|
"Optimize images with descriptive alt text"
|
||||||
|
]
|
||||||
|
|
||||||
|
return seo_recommendations
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"SEO content recommendations error: {str(e)}")
|
||||||
|
return {'error': str(e)}
|
||||||
|
|
||||||
|
def _generate_search_intelligence_recommendations(self, gsc_analysis: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
|
"""Generate comprehensive recommendations from search intelligence analysis."""
|
||||||
|
try:
|
||||||
|
recommendations = {
|
||||||
|
'immediate_actions': [],
|
||||||
|
'content_opportunities': [],
|
||||||
|
'technical_improvements': [],
|
||||||
|
'strategic_initiatives': []
|
||||||
|
}
|
||||||
|
|
||||||
|
# Extract content opportunities from GSC analysis
|
||||||
|
content_opps = gsc_analysis.get('content_opportunities', [])
|
||||||
|
for opp in content_opps[:5]: # Top 5 opportunities
|
||||||
|
recommendations['content_opportunities'].append({
|
||||||
|
'type': opp['type'],
|
||||||
|
'keyword': opp['keyword'],
|
||||||
|
'action': opp['opportunity'],
|
||||||
|
'priority': opp['priority'],
|
||||||
|
'estimated_impact': opp['potential_impact']
|
||||||
|
})
|
||||||
|
|
||||||
|
# Technical improvements from analysis
|
||||||
|
technical_insights = gsc_analysis.get('technical_insights', {})
|
||||||
|
if technical_insights.get('crawl_issues_indicators'):
|
||||||
|
for issue in technical_insights['crawl_issues_indicators']:
|
||||||
|
recommendations['technical_improvements'].append({
|
||||||
|
'issue': issue,
|
||||||
|
'priority': 'High',
|
||||||
|
'category': 'Crawl & Indexing'
|
||||||
|
})
|
||||||
|
|
||||||
|
# Immediate actions based on performance
|
||||||
|
performance = gsc_analysis.get('performance_overview', {})
|
||||||
|
if performance.get('avg_ctr', 0) < 2:
|
||||||
|
recommendations['immediate_actions'].append({
|
||||||
|
'action': 'Improve meta descriptions and titles for better CTR',
|
||||||
|
'expected_impact': 'Increase CTR by 1-2%',
|
||||||
|
'timeline': '2-4 weeks'
|
||||||
|
})
|
||||||
|
|
||||||
|
if performance.get('avg_position', 0) > 10:
|
||||||
|
recommendations['immediate_actions'].append({
|
||||||
|
'action': 'Focus on improving content quality for top keywords',
|
||||||
|
'expected_impact': 'Improve average position by 2-5 ranks',
|
||||||
|
'timeline': '4-8 weeks'
|
||||||
|
})
|
||||||
|
|
||||||
|
# Strategic initiatives
|
||||||
|
competitive_analysis = gsc_analysis.get('competitive_analysis', {})
|
||||||
|
if competitive_analysis.get('market_position') in ['Challenger', 'Emerging Player']:
|
||||||
|
recommendations['strategic_initiatives'].append({
|
||||||
|
'initiative': 'Develop thought leadership content strategy',
|
||||||
|
'goal': 'Improve market position and brand authority',
|
||||||
|
'timeline': '3-6 months'
|
||||||
|
})
|
||||||
|
|
||||||
|
return recommendations
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Search intelligence recommendations error: {str(e)}")
|
||||||
|
return {'error': str(e)}
|
||||||
|
|
||||||
|
def render_enterprise_seo_suite():
|
||||||
|
"""Render the Enterprise SEO Command Center interface."""
|
||||||
|
|
||||||
|
st.set_page_config(
|
||||||
|
page_title="Enterprise SEO Command Center",
|
||||||
|
page_icon="🚀",
|
||||||
|
layout="wide"
|
||||||
|
)
|
||||||
|
|
||||||
|
st.title("🚀 Enterprise SEO Command Center")
|
||||||
|
st.markdown("**Unified AI-powered SEO suite orchestrating all tools into intelligent workflows**")
|
||||||
|
|
||||||
|
# Initialize suite
|
||||||
|
if 'enterprise_seo_suite' not in st.session_state:
|
||||||
|
st.session_state.enterprise_seo_suite = EnterpriseSEOSuite()
|
||||||
|
|
||||||
|
suite = st.session_state.enterprise_seo_suite
|
||||||
|
|
||||||
|
# Workflow selection
|
||||||
|
st.sidebar.header("🎯 SEO Workflow Selection")
|
||||||
|
selected_workflow = st.sidebar.selectbox(
|
||||||
|
"Choose Workflow",
|
||||||
|
list(suite.workflow_templates.keys()),
|
||||||
|
format_func=lambda x: suite.workflow_templates[x]
|
||||||
|
)
|
||||||
|
|
||||||
|
# Main workflow interface
|
||||||
|
if selected_workflow == 'complete_audit':
|
||||||
|
st.header("🔍 Complete Enterprise SEO Audit")
|
||||||
|
render_complete_audit_interface(suite)
|
||||||
|
elif selected_workflow == 'content_strategy':
|
||||||
|
st.header("📊 Content Strategy Development")
|
||||||
|
render_content_strategy_interface(suite)
|
||||||
|
elif selected_workflow == 'technical_optimization':
|
||||||
|
st.header("🔧 Technical SEO Optimization")
|
||||||
|
render_technical_optimization_interface(suite)
|
||||||
|
else:
|
||||||
|
st.info(f"Workflow '{suite.workflow_templates[selected_workflow]}' is being developed.")
|
||||||
|
|
||||||
|
def render_complete_audit_interface(suite: EnterpriseSEOSuite):
|
||||||
|
"""Render the complete audit workflow interface."""
|
||||||
|
|
||||||
|
# Input form
|
||||||
|
with st.form("enterprise_audit_form"):
|
||||||
|
col1, col2 = st.columns(2)
|
||||||
|
|
||||||
|
with col1:
|
||||||
|
website_url = st.text_input(
|
||||||
|
"Website URL",
|
||||||
|
value="https://example.com",
|
||||||
|
help="Enter your website URL for comprehensive analysis"
|
||||||
|
)
|
||||||
|
|
||||||
|
target_keywords = st.text_area(
|
||||||
|
"Target Keywords (one per line)",
|
||||||
|
value="AI content creation\nSEO tools\ncontent optimization",
|
||||||
|
help="Enter your primary keywords to optimize for"
|
||||||
|
)
|
||||||
|
|
||||||
|
with col2:
|
||||||
|
competitors = st.text_area(
|
||||||
|
"Competitor URLs (one per line)",
|
||||||
|
value="https://jasper.ai\nhttps://copy.ai\nhttps://writesonic.com",
|
||||||
|
help="Enter up to 5 competitor URLs for analysis"
|
||||||
|
)
|
||||||
|
|
||||||
|
submit_audit = st.form_submit_button("🚀 Start Complete SEO Audit", type="primary")
|
||||||
|
|
||||||
|
# Process audit
|
||||||
|
if submit_audit:
|
||||||
|
if website_url and target_keywords:
|
||||||
|
# Parse inputs
|
||||||
|
keywords_list = [k.strip() for k in target_keywords.split('\n') if k.strip()]
|
||||||
|
competitors_list = [c.strip() for c in competitors.split('\n') if c.strip()]
|
||||||
|
|
||||||
|
# Run audit
|
||||||
|
with st.spinner("🔍 Running comprehensive SEO audit..."):
|
||||||
|
audit_results = asyncio.run(
|
||||||
|
suite.execute_complete_seo_audit(
|
||||||
|
website_url, competitors_list, keywords_list
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if 'error' not in audit_results:
|
||||||
|
st.success("✅ Enterprise SEO audit completed!")
|
||||||
|
|
||||||
|
# Display results dashboard
|
||||||
|
render_audit_results_dashboard(audit_results)
|
||||||
|
else:
|
||||||
|
st.error(f"❌ Audit failed: {audit_results['error']}")
|
||||||
|
else:
|
||||||
|
st.warning("⚠️ Please enter website URL and target keywords.")
|
||||||
|
|
||||||
|
def render_audit_results_dashboard(results: Dict[str, Any]):
|
||||||
|
"""Render comprehensive audit results dashboard."""
|
||||||
|
|
||||||
|
# Priority Action Plan (Most Important)
|
||||||
|
st.header("📋 Priority Action Plan")
|
||||||
|
action_plan = results.get('priority_action_plan', [])
|
||||||
|
|
||||||
|
if action_plan:
|
||||||
|
# Display as interactive table
|
||||||
|
df_actions = pd.DataFrame(action_plan)
|
||||||
|
|
||||||
|
# Style the dataframe
|
||||||
|
st.dataframe(
|
||||||
|
df_actions,
|
||||||
|
column_config={
|
||||||
|
"category": "Category",
|
||||||
|
"priority": st.column_config.SelectboxColumn(
|
||||||
|
"Priority",
|
||||||
|
options=["Critical", "High", "Medium", "Low"]
|
||||||
|
),
|
||||||
|
"effort": "Effort Level",
|
||||||
|
"timeframe": "Timeline",
|
||||||
|
"action": "Action Required",
|
||||||
|
"expected_impact": "Expected Impact"
|
||||||
|
},
|
||||||
|
hide_index=True,
|
||||||
|
use_container_width=True
|
||||||
|
)
|
||||||
|
|
||||||
|
# Key Metrics Overview
|
||||||
|
st.header("📊 SEO Health Dashboard")
|
||||||
|
|
||||||
|
col1, col2, col3, col4 = st.columns(4)
|
||||||
|
|
||||||
|
with col1:
|
||||||
|
technical_score = results.get('technical_audit', {}).get('performance_score', 0)
|
||||||
|
st.metric("Technical SEO", f"{technical_score}/100", delta=None)
|
||||||
|
|
||||||
|
with col2:
|
||||||
|
onpage_score = results.get('on_page_analysis', {}).get('optimization_score', 0)
|
||||||
|
st.metric("On-Page SEO", f"{onpage_score}/100", delta=None)
|
||||||
|
|
||||||
|
with col3:
|
||||||
|
performance_score = results.get('performance_metrics', {}).get('performance_score', 0)
|
||||||
|
st.metric("Performance", f"{performance_score}/100", delta=None)
|
||||||
|
|
||||||
|
with col4:
|
||||||
|
content_gaps = len(results.get('content_analysis', {}).get('content_opportunities', []))
|
||||||
|
st.metric("Content Opportunities", content_gaps, delta=None)
|
||||||
|
|
||||||
|
# Detailed Analysis Sections
|
||||||
|
tab1, tab2, tab3, tab4, tab5 = st.tabs([
|
||||||
|
"🤖 Strategic Insights",
|
||||||
|
"🔧 Technical Analysis",
|
||||||
|
"📊 Content Intelligence",
|
||||||
|
"🔍 On-Page Analysis",
|
||||||
|
"⚡ Performance Metrics"
|
||||||
|
])
|
||||||
|
|
||||||
|
with tab1:
|
||||||
|
strategic_recs = results.get('strategic_recommendations', {})
|
||||||
|
if strategic_recs:
|
||||||
|
st.subheader("AI-Powered Strategic Recommendations")
|
||||||
|
|
||||||
|
# Immediate wins
|
||||||
|
immediate_wins = strategic_recs.get('immediate_wins', [])
|
||||||
|
if immediate_wins:
|
||||||
|
st.markdown("#### 🚀 Immediate Wins (0-30 days)")
|
||||||
|
for win in immediate_wins[:5]:
|
||||||
|
st.success(f"✅ {win}")
|
||||||
|
|
||||||
|
# Strategic initiatives
|
||||||
|
strategic_initiatives = strategic_recs.get('strategic_initiatives', [])
|
||||||
|
if strategic_initiatives:
|
||||||
|
st.markdown("#### 📈 Strategic Initiatives (1-3 months)")
|
||||||
|
for initiative in strategic_initiatives[:3]:
|
||||||
|
st.info(f"📋 {initiative}")
|
||||||
|
|
||||||
|
# Full analysis
|
||||||
|
full_analysis = strategic_recs.get('full_analysis', '')
|
||||||
|
if full_analysis:
|
||||||
|
with st.expander("🧠 Complete Strategic Analysis"):
|
||||||
|
st.write(full_analysis)
|
||||||
|
|
||||||
|
with tab2:
|
||||||
|
technical_audit = results.get('technical_audit', {})
|
||||||
|
if technical_audit:
|
||||||
|
st.subheader("Technical SEO Analysis")
|
||||||
|
|
||||||
|
critical_issues = technical_audit.get('critical_issues', [])
|
||||||
|
if critical_issues:
|
||||||
|
st.markdown("#### ⚠️ Critical Issues")
|
||||||
|
for issue in critical_issues:
|
||||||
|
st.error(f"🚨 {issue}")
|
||||||
|
|
||||||
|
priority_fixes = technical_audit.get('priority_fixes', [])
|
||||||
|
if priority_fixes:
|
||||||
|
st.markdown("#### 🔧 Priority Fixes")
|
||||||
|
for fix in priority_fixes:
|
||||||
|
st.warning(f"🛠️ {fix}")
|
||||||
|
|
||||||
|
with tab3:
|
||||||
|
content_analysis = results.get('content_analysis', {})
|
||||||
|
if content_analysis:
|
||||||
|
st.subheader("Content Intelligence")
|
||||||
|
|
||||||
|
content_opportunities = content_analysis.get('content_opportunities', [])
|
||||||
|
if content_opportunities:
|
||||||
|
st.markdown("#### 📝 Content Opportunities")
|
||||||
|
for opportunity in content_opportunities[:5]:
|
||||||
|
st.info(f"💡 {opportunity}")
|
||||||
|
|
||||||
|
competitive_advantages = content_analysis.get('competitive_advantages', [])
|
||||||
|
if competitive_advantages:
|
||||||
|
st.markdown("#### 🏆 Competitive Advantages")
|
||||||
|
for advantage in competitive_advantages:
|
||||||
|
st.success(f"⭐ {advantage}")
|
||||||
|
|
||||||
|
with tab4:
|
||||||
|
onpage_analysis = results.get('on_page_analysis', {})
|
||||||
|
if onpage_analysis:
|
||||||
|
st.subheader("On-Page SEO Analysis")
|
||||||
|
|
||||||
|
meta_optimization = onpage_analysis.get('meta_optimization', {})
|
||||||
|
content_optimization = onpage_analysis.get('content_optimization', {})
|
||||||
|
|
||||||
|
col1, col2 = st.columns(2)
|
||||||
|
|
||||||
|
with col1:
|
||||||
|
st.markdown("#### 🏷️ Meta Tag Optimization")
|
||||||
|
st.json(meta_optimization)
|
||||||
|
|
||||||
|
with col2:
|
||||||
|
st.markdown("#### 📄 Content Optimization")
|
||||||
|
st.json(content_optimization)
|
||||||
|
|
||||||
|
with tab5:
|
||||||
|
performance_metrics = results.get('performance_metrics', {})
|
||||||
|
if performance_metrics:
|
||||||
|
st.subheader("Performance Analysis")
|
||||||
|
|
||||||
|
core_vitals = performance_metrics.get('core_web_vitals', {})
|
||||||
|
loading_performance = performance_metrics.get('loading_performance', {})
|
||||||
|
|
||||||
|
col1, col2 = st.columns(2)
|
||||||
|
|
||||||
|
with col1:
|
||||||
|
st.markdown("#### ⚡ Core Web Vitals")
|
||||||
|
st.json(core_vitals)
|
||||||
|
|
||||||
|
with col2:
|
||||||
|
st.markdown("#### 🚀 Loading Performance")
|
||||||
|
st.json(loading_performance)
|
||||||
|
|
||||||
|
# Export functionality
|
||||||
|
st.markdown("---")
|
||||||
|
col1, col2, col3 = st.columns(3)
|
||||||
|
|
||||||
|
with col1:
|
||||||
|
if st.button("📥 Export Full Report", use_container_width=True):
|
||||||
|
# Create downloadable report
|
||||||
|
report_json = json.dumps(results, indent=2, default=str)
|
||||||
|
st.download_button(
|
||||||
|
label="Download JSON Report",
|
||||||
|
data=report_json,
|
||||||
|
file_name=f"seo_audit_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json",
|
||||||
|
mime="application/json"
|
||||||
|
)
|
||||||
|
|
||||||
|
with col2:
|
||||||
|
if st.button("📊 Export Action Plan", use_container_width=True):
|
||||||
|
# Create CSV of action plan
|
||||||
|
df_actions = pd.DataFrame(action_plan)
|
||||||
|
csv = df_actions.to_csv(index=False)
|
||||||
|
st.download_button(
|
||||||
|
label="Download CSV Action Plan",
|
||||||
|
data=csv,
|
||||||
|
file_name=f"action_plan_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv",
|
||||||
|
mime="text/csv"
|
||||||
|
)
|
||||||
|
|
||||||
|
with col3:
|
||||||
|
if st.button("🔄 Schedule Follow-up Audit", use_container_width=True):
|
||||||
|
st.info("Follow-up scheduling feature coming soon!")
|
||||||
|
|
||||||
|
def render_content_strategy_interface(suite: EnterpriseSEOSuite):
|
||||||
|
"""Render content strategy development interface."""
|
||||||
|
st.info("🚧 Content Strategy Development workflow coming soon!")
|
||||||
|
|
||||||
|
def render_technical_optimization_interface(suite: EnterpriseSEOSuite):
|
||||||
|
"""Render technical optimization interface."""
|
||||||
|
st.info("🚧 Technical SEO Optimization workflow coming soon!")
|
||||||
|
|
||||||
|
|
||||||
|
# Main execution
|
||||||
|
if __name__ == "__main__":
|
||||||
|
render_enterprise_seo_suite()
|
||||||
135
ToBeMigrated/ai_seo_tools/google_pagespeed_insights.py
Normal file
135
ToBeMigrated/ai_seo_tools/google_pagespeed_insights.py
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
import requests
|
||||||
|
import streamlit as st
|
||||||
|
import json
|
||||||
|
import pandas as pd
|
||||||
|
import plotly.express as px
|
||||||
|
from tenacity import retry, stop_after_attempt, wait_random_exponential
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
def run_pagespeed(url, api_key=None, strategy='DESKTOP', locale='en'):
|
||||||
|
"""Fetches and processes PageSpeed Insights data."""
|
||||||
|
serviceurl = 'https://www.googleapis.com/pagespeedonline/v5/runPagespeed'
|
||||||
|
base_url = f"{serviceurl}?url={url}&strategy={strategy}&locale={locale}&category=performance&category=accessibility&category=best-practices&category=seo"
|
||||||
|
|
||||||
|
if api_key:
|
||||||
|
base_url += f"&key={api_key}"
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = requests.get(base_url)
|
||||||
|
response.raise_for_status() # Raise an exception for bad status codes
|
||||||
|
data = response.json()
|
||||||
|
return data
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
st.error(f"Error fetching PageSpeed Insights data: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def display_results(data):
|
||||||
|
"""Presents PageSpeed Insights data in a user-friendly format."""
|
||||||
|
st.subheader("PageSpeed Insights Report")
|
||||||
|
|
||||||
|
# Extract scores from the PageSpeed Insights data
|
||||||
|
scores = {
|
||||||
|
"Performance": data['lighthouseResult']['categories']['performance']['score'] * 100,
|
||||||
|
"Accessibility": data['lighthouseResult']['categories']['accessibility']['score'] * 100,
|
||||||
|
"SEO": data['lighthouseResult']['categories']['seo']['score'] * 100,
|
||||||
|
"Best Practices": data['lighthouseResult']['categories']['best-practices']['score'] * 100
|
||||||
|
}
|
||||||
|
|
||||||
|
descriptions = {
|
||||||
|
"Performance": data['lighthouseResult']['categories']['performance'].get('description', "This score represents Google's assessment of your page's speed. A higher percentage indicates better performance."),
|
||||||
|
"Accessibility": data['lighthouseResult']['categories']['accessibility'].get('description', "This score evaluates how accessible your page is to users with disabilities. A higher percentage means better accessibility."),
|
||||||
|
"SEO": data['lighthouseResult']['categories']['seo'].get('description', "This score measures how well your page is optimized for search engines. A higher percentage indicates better SEO practices."),
|
||||||
|
"Best Practices": data['lighthouseResult']['categories']['best-practices'].get('description', "This score reflects how well your page follows best practices for web development. A higher percentage signifies adherence to best practices.")
|
||||||
|
}
|
||||||
|
|
||||||
|
for category, score in scores.items():
|
||||||
|
st.metric(label=f"Overall {category} Score", value=f"{score:.0f}%", help=descriptions[category])
|
||||||
|
|
||||||
|
# Display additional metrics
|
||||||
|
st.subheader("Additional Metrics")
|
||||||
|
additional_metrics = {
|
||||||
|
"First Contentful Paint (FCP)": data['lighthouseResult']['audits']['first-contentful-paint']['displayValue'],
|
||||||
|
"Largest Contentful Paint (LCP)": data['lighthouseResult']['audits']['largest-contentful-paint']['displayValue'],
|
||||||
|
"Time to Interactive (TTI)": data['lighthouseResult']['audits']['interactive']['displayValue'],
|
||||||
|
"Total Blocking Time (TBT)": data['lighthouseResult']['audits']['total-blocking-time']['displayValue'],
|
||||||
|
"Cumulative Layout Shift (CLS)": data['lighthouseResult']['audits']['cumulative-layout-shift']['displayValue']
|
||||||
|
}
|
||||||
|
|
||||||
|
st.table(pd.DataFrame(additional_metrics.items(), columns=["Metric", "Value"]))
|
||||||
|
|
||||||
|
# Display Network Requests
|
||||||
|
st.subheader("Network Requests")
|
||||||
|
if 'network-requests' in data['lighthouseResult']['audits']:
|
||||||
|
network_requests = [
|
||||||
|
{
|
||||||
|
"End Time": item.get("endTime", "N/A"),
|
||||||
|
"Start Time": item.get("startTime", "N/A"),
|
||||||
|
"Transfer Size (MB)": round(item.get("transferSize", 0) / 1048576, 2),
|
||||||
|
"Resource Size (MB)": round(item.get("resourceSize", 0) / 1048576, 2),
|
||||||
|
"URL": item.get("url", "N/A")
|
||||||
|
}
|
||||||
|
for item in data["lighthouseResult"]["audits"]["network-requests"]["details"]["items"]
|
||||||
|
if item.get("transferSize", 0) > 100000 or item.get("resourceSize", 0) > 100000
|
||||||
|
]
|
||||||
|
if network_requests:
|
||||||
|
st.dataframe(pd.DataFrame(network_requests), use_container_width=True)
|
||||||
|
else:
|
||||||
|
st.write("No significant network requests found.")
|
||||||
|
|
||||||
|
# Display Mainthread Work Breakdown
|
||||||
|
st.subheader("Mainthread Work Breakdown")
|
||||||
|
if 'mainthread-work-breakdown' in data['lighthouseResult']['audits']:
|
||||||
|
mainthread_data = [
|
||||||
|
{"Process": item.get("groupLabel", "N/A"), "Duration (ms)": item.get("duration", "N/A")}
|
||||||
|
for item in data["lighthouseResult"]["audits"]["mainthread-work-breakdown"]["details"]["items"] if item.get("duration", "N/A") != "N/A"
|
||||||
|
]
|
||||||
|
if mainthread_data:
|
||||||
|
fig = px.bar(pd.DataFrame(mainthread_data), x="Process", y="Duration (ms)", title="Mainthread Work Breakdown", labels={"Process": "Process", "Duration (ms)": "Duration (ms)"})
|
||||||
|
st.plotly_chart(fig, use_container_width=True)
|
||||||
|
else:
|
||||||
|
st.write("No significant main thread work breakdown data found.")
|
||||||
|
|
||||||
|
# Display other metrics
|
||||||
|
metrics = [
|
||||||
|
("Use of Passive Event Listeners", 'uses-passive-event-listeners', ["URL", "Code Line"]),
|
||||||
|
("DOM Size", 'dom-size', ["Score", "DOM Size"]),
|
||||||
|
("Offscreen Images", 'offscreen-images', ["URL", "Total Bytes", "Wasted Bytes", "Wasted Percentage"]),
|
||||||
|
("Critical Request Chains", 'critical-request-chains', ["URL", "Start Time", "End Time", "Transfer Size", "Chain"]),
|
||||||
|
("Total Bytes Weight", 'total-byte-weight', ["URL", "Total Bytes"]),
|
||||||
|
("Render Blocking Resources", 'render-blocking-resources', ["URL", "Total Bytes", "Wasted Milliseconds"]),
|
||||||
|
("Use of Rel Preload", 'uses-rel-preload', ["URL", "Wasted Milliseconds"])
|
||||||
|
]
|
||||||
|
|
||||||
|
for metric_title, audit_key, columns in metrics:
|
||||||
|
st.subheader(metric_title)
|
||||||
|
if audit_key in data['lighthouseResult']['audits']:
|
||||||
|
details = data['lighthouseResult']['audits'][audit_key].get("details", {}).get("items", [])
|
||||||
|
if details:
|
||||||
|
st.table(pd.DataFrame(details, columns=columns))
|
||||||
|
else:
|
||||||
|
st.write(f"No significant {metric_title.lower()} data found.")
|
||||||
|
|
||||||
|
def google_pagespeed_insights():
|
||||||
|
st.markdown("<h1 style='text-align: center; color: #1565C0;'>PageSpeed Insights Analyzer</h1>", unsafe_allow_html=True)
|
||||||
|
st.markdown("<h3 style='text-align: center;'>Get detailed insights into your website's performance! Powered by Google PageSpeed Insights <a href='https://developer.chrome.com/docs/lighthouse/overview/'>[Learn More]</a></h3>", unsafe_allow_html=True)
|
||||||
|
|
||||||
|
# User Input
|
||||||
|
with st.form("pagespeed_form"):
|
||||||
|
url = st.text_input("Enter Website URL", placeholder="https://www.example.com")
|
||||||
|
api_key = st.text_input("Enter Google API Key (Optional)", placeholder="Your API Key", help="Get your API key here: [https://developers.google.com/speed/docs/insights/v5/get-started#key]")
|
||||||
|
device = st.selectbox("Choose Device", ["Mobile", "Desktop"])
|
||||||
|
locale = st.selectbox("Choose Locale", ["en", "fr", "es", "de", "ja"])
|
||||||
|
categories = st.multiselect("Select Categories to Analyze", ['PERFORMANCE', 'ACCESSIBILITY', 'BEST_PRACTICES', 'SEO'], default=['PERFORMANCE', 'ACCESSIBILITY', 'BEST_PRACTICES', 'SEO'])
|
||||||
|
|
||||||
|
submitted = st.form_submit_button("Analyze")
|
||||||
|
|
||||||
|
if submitted:
|
||||||
|
if not url:
|
||||||
|
st.error("Please provide the website URL.")
|
||||||
|
else:
|
||||||
|
strategy = 'mobile' if device == "Mobile" else 'desktop'
|
||||||
|
data = run_pagespeed(url, api_key, strategy=strategy, locale=locale)
|
||||||
|
if data:
|
||||||
|
display_results(data)
|
||||||
|
else:
|
||||||
|
st.error("Failed to retrieve PageSpeed Insights data.")
|
||||||
864
ToBeMigrated/ai_seo_tools/google_search_console_integration.py
Normal file
864
ToBeMigrated/ai_seo_tools/google_search_console_integration.py
Normal file
@@ -0,0 +1,864 @@
|
|||||||
|
"""
|
||||||
|
Google Search Console Integration for Enterprise SEO
|
||||||
|
|
||||||
|
Connects GSC data with AI-powered content strategy and keyword intelligence.
|
||||||
|
Provides enterprise-level search performance insights and content recommendations.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import streamlit as st
|
||||||
|
import pandas as pd
|
||||||
|
import numpy as np
|
||||||
|
from typing import Dict, Any, List, Optional, Tuple
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
import json
|
||||||
|
from loguru import logger
|
||||||
|
import plotly.express as px
|
||||||
|
import plotly.graph_objects as go
|
||||||
|
from plotly.subplots import make_subplots
|
||||||
|
|
||||||
|
# Import AI modules
|
||||||
|
from ..gpt_providers.text_generation.main_text_generation import llm_text_gen
|
||||||
|
|
||||||
|
|
||||||
|
class GoogleSearchConsoleAnalyzer:
|
||||||
|
"""
|
||||||
|
Enterprise Google Search Console analyzer with AI-powered insights.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
"""Initialize the GSC analyzer."""
|
||||||
|
self.gsc_client = None # Will be initialized when credentials are provided
|
||||||
|
logger.info("Google Search Console Analyzer initialized")
|
||||||
|
|
||||||
|
def analyze_search_performance(self, site_url: str, date_range: int = 90) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Analyze comprehensive search performance from GSC data.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
site_url: Website URL registered in GSC
|
||||||
|
date_range: Number of days to analyze (default 90)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Comprehensive search performance analysis
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
st.info("📊 Analyzing Google Search Console data...")
|
||||||
|
|
||||||
|
# Simulate GSC data for demonstration (replace with actual GSC API calls)
|
||||||
|
search_data = self._get_mock_gsc_data(site_url, date_range)
|
||||||
|
|
||||||
|
# Perform comprehensive analysis
|
||||||
|
analysis_results = {
|
||||||
|
'site_url': site_url,
|
||||||
|
'analysis_period': f"Last {date_range} days",
|
||||||
|
'analysis_timestamp': datetime.utcnow().isoformat(),
|
||||||
|
'performance_overview': self._analyze_performance_overview(search_data),
|
||||||
|
'keyword_analysis': self._analyze_keyword_performance(search_data),
|
||||||
|
'page_analysis': self._analyze_page_performance(search_data),
|
||||||
|
'content_opportunities': self._identify_content_opportunities(search_data),
|
||||||
|
'technical_insights': self._analyze_technical_seo_signals(search_data),
|
||||||
|
'competitive_analysis': self._analyze_competitive_position(search_data),
|
||||||
|
'ai_recommendations': self._generate_ai_recommendations(search_data)
|
||||||
|
}
|
||||||
|
|
||||||
|
return analysis_results
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
error_msg = f"Error analyzing search performance: {str(e)}"
|
||||||
|
logger.error(error_msg, exc_info=True)
|
||||||
|
return {'error': error_msg}
|
||||||
|
|
||||||
|
def _get_mock_gsc_data(self, site_url: str, days: int) -> Dict[str, pd.DataFrame]:
|
||||||
|
"""
|
||||||
|
Generate mock GSC data for demonstration.
|
||||||
|
In production, this would fetch real data from GSC API.
|
||||||
|
"""
|
||||||
|
# Generate mock keyword data
|
||||||
|
keywords_data = []
|
||||||
|
sample_keywords = [
|
||||||
|
"AI content creation", "SEO tools", "content optimization", "blog writing AI",
|
||||||
|
"meta description generator", "keyword research", "technical SEO", "content strategy",
|
||||||
|
"on-page optimization", "SERP analysis", "content gap analysis", "SEO audit"
|
||||||
|
]
|
||||||
|
|
||||||
|
for keyword in sample_keywords:
|
||||||
|
# Generate realistic performance data
|
||||||
|
impressions = np.random.randint(100, 10000)
|
||||||
|
clicks = int(impressions * np.random.uniform(0.02, 0.15)) # CTR between 2-15%
|
||||||
|
position = np.random.uniform(3, 25)
|
||||||
|
|
||||||
|
keywords_data.append({
|
||||||
|
'keyword': keyword,
|
||||||
|
'impressions': impressions,
|
||||||
|
'clicks': clicks,
|
||||||
|
'ctr': (clicks / impressions) * 100,
|
||||||
|
'position': position
|
||||||
|
})
|
||||||
|
|
||||||
|
# Generate mock page data
|
||||||
|
pages_data = []
|
||||||
|
sample_pages = [
|
||||||
|
"/blog/ai-content-creation-guide", "/tools/seo-analyzer", "/features/content-optimization",
|
||||||
|
"/blog/technical-seo-checklist", "/tools/keyword-research", "/blog/content-strategy-2024",
|
||||||
|
"/tools/meta-description-generator", "/blog/on-page-seo-guide", "/features/enterprise-seo"
|
||||||
|
]
|
||||||
|
|
||||||
|
for page in sample_pages:
|
||||||
|
impressions = np.random.randint(500, 5000)
|
||||||
|
clicks = int(impressions * np.random.uniform(0.03, 0.12))
|
||||||
|
position = np.random.uniform(5, 20)
|
||||||
|
|
||||||
|
pages_data.append({
|
||||||
|
'page': page,
|
||||||
|
'impressions': impressions,
|
||||||
|
'clicks': clicks,
|
||||||
|
'ctr': (clicks / impressions) * 100,
|
||||||
|
'position': position
|
||||||
|
})
|
||||||
|
|
||||||
|
# Generate time series data
|
||||||
|
time_series_data = []
|
||||||
|
for i in range(days):
|
||||||
|
date = datetime.now() - timedelta(days=i)
|
||||||
|
daily_clicks = np.random.randint(50, 500)
|
||||||
|
daily_impressions = np.random.randint(1000, 8000)
|
||||||
|
|
||||||
|
time_series_data.append({
|
||||||
|
'date': date.strftime('%Y-%m-%d'),
|
||||||
|
'clicks': daily_clicks,
|
||||||
|
'impressions': daily_impressions,
|
||||||
|
'ctr': (daily_clicks / daily_impressions) * 100,
|
||||||
|
'position': np.random.uniform(8, 15)
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
'keywords': pd.DataFrame(keywords_data),
|
||||||
|
'pages': pd.DataFrame(pages_data),
|
||||||
|
'time_series': pd.DataFrame(time_series_data)
|
||||||
|
}
|
||||||
|
|
||||||
|
def _analyze_performance_overview(self, search_data: Dict[str, pd.DataFrame]) -> Dict[str, Any]:
|
||||||
|
"""Analyze overall search performance metrics."""
|
||||||
|
keywords_df = search_data['keywords']
|
||||||
|
time_series_df = search_data['time_series']
|
||||||
|
|
||||||
|
# Calculate totals and averages
|
||||||
|
total_clicks = keywords_df['clicks'].sum()
|
||||||
|
total_impressions = keywords_df['impressions'].sum()
|
||||||
|
avg_ctr = (total_clicks / total_impressions) * 100 if total_impressions > 0 else 0
|
||||||
|
avg_position = keywords_df['position'].mean()
|
||||||
|
|
||||||
|
# Calculate trends
|
||||||
|
recent_clicks = time_series_df.head(7)['clicks'].mean()
|
||||||
|
previous_clicks = time_series_df.tail(7)['clicks'].mean()
|
||||||
|
clicks_trend = ((recent_clicks - previous_clicks) / previous_clicks * 100) if previous_clicks > 0 else 0
|
||||||
|
|
||||||
|
recent_impressions = time_series_df.head(7)['impressions'].mean()
|
||||||
|
previous_impressions = time_series_df.tail(7)['impressions'].mean()
|
||||||
|
impressions_trend = ((recent_impressions - previous_impressions) / previous_impressions * 100) if previous_impressions > 0 else 0
|
||||||
|
|
||||||
|
# Top performing keywords
|
||||||
|
top_keywords = keywords_df.nlargest(5, 'clicks')[['keyword', 'clicks', 'impressions', 'position']].to_dict('records')
|
||||||
|
|
||||||
|
# Opportunity keywords (high impressions, low CTR)
|
||||||
|
opportunity_keywords = keywords_df[
|
||||||
|
(keywords_df['impressions'] > keywords_df['impressions'].median()) &
|
||||||
|
(keywords_df['ctr'] < 3)
|
||||||
|
].nlargest(5, 'impressions')[['keyword', 'impressions', 'ctr', 'position']].to_dict('records')
|
||||||
|
|
||||||
|
return {
|
||||||
|
'total_clicks': int(total_clicks),
|
||||||
|
'total_impressions': int(total_impressions),
|
||||||
|
'avg_ctr': round(avg_ctr, 2),
|
||||||
|
'avg_position': round(avg_position, 1),
|
||||||
|
'clicks_trend': round(clicks_trend, 1),
|
||||||
|
'impressions_trend': round(impressions_trend, 1),
|
||||||
|
'top_keywords': top_keywords,
|
||||||
|
'opportunity_keywords': opportunity_keywords
|
||||||
|
}
|
||||||
|
|
||||||
|
def _analyze_keyword_performance(self, search_data: Dict[str, pd.DataFrame]) -> Dict[str, Any]:
|
||||||
|
"""Analyze keyword performance and opportunities."""
|
||||||
|
keywords_df = search_data['keywords']
|
||||||
|
|
||||||
|
# Keyword categorization
|
||||||
|
high_volume_keywords = keywords_df[keywords_df['impressions'] > keywords_df['impressions'].quantile(0.8)]
|
||||||
|
low_competition_keywords = keywords_df[keywords_df['position'] <= 10]
|
||||||
|
optimization_opportunities = keywords_df[
|
||||||
|
(keywords_df['position'] > 10) &
|
||||||
|
(keywords_df['position'] <= 20) &
|
||||||
|
(keywords_df['impressions'] > 100)
|
||||||
|
]
|
||||||
|
|
||||||
|
# Content gap analysis
|
||||||
|
missing_keywords = self._identify_missing_keywords(keywords_df)
|
||||||
|
|
||||||
|
# Seasonal trends analysis
|
||||||
|
seasonal_insights = self._analyze_seasonal_trends(keywords_df)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'total_keywords': len(keywords_df),
|
||||||
|
'high_volume_keywords': high_volume_keywords.to_dict('records'),
|
||||||
|
'ranking_keywords': low_competition_keywords.to_dict('records'),
|
||||||
|
'optimization_opportunities': optimization_opportunities.to_dict('records'),
|
||||||
|
'missing_keywords': missing_keywords,
|
||||||
|
'seasonal_insights': seasonal_insights,
|
||||||
|
'keyword_distribution': {
|
||||||
|
'positions_1_3': len(keywords_df[keywords_df['position'] <= 3]),
|
||||||
|
'positions_4_10': len(keywords_df[(keywords_df['position'] > 3) & (keywords_df['position'] <= 10)]),
|
||||||
|
'positions_11_20': len(keywords_df[(keywords_df['position'] > 10) & (keywords_df['position'] <= 20)]),
|
||||||
|
'positions_21_plus': len(keywords_df[keywords_df['position'] > 20])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def _analyze_page_performance(self, search_data: Dict[str, pd.DataFrame]) -> Dict[str, Any]:
|
||||||
|
"""Analyze page-level performance."""
|
||||||
|
pages_df = search_data['pages']
|
||||||
|
|
||||||
|
# Top performing pages
|
||||||
|
top_pages = pages_df.nlargest(10, 'clicks')
|
||||||
|
|
||||||
|
# Underperforming pages (high impressions, low clicks)
|
||||||
|
underperforming_pages = pages_df[
|
||||||
|
(pages_df['impressions'] > pages_df['impressions'].median()) &
|
||||||
|
(pages_df['ctr'] < 2)
|
||||||
|
].nlargest(5, 'impressions')
|
||||||
|
|
||||||
|
# Page type analysis
|
||||||
|
page_types = self._categorize_pages(pages_df)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'top_pages': top_pages.to_dict('records'),
|
||||||
|
'underperforming_pages': underperforming_pages.to_dict('records'),
|
||||||
|
'page_types_performance': page_types,
|
||||||
|
'total_pages': len(pages_df)
|
||||||
|
}
|
||||||
|
|
||||||
|
def _identify_content_opportunities(self, search_data: Dict[str, pd.DataFrame]) -> List[Dict[str, Any]]:
|
||||||
|
"""Identify content creation and optimization opportunities."""
|
||||||
|
keywords_df = search_data['keywords']
|
||||||
|
|
||||||
|
opportunities = []
|
||||||
|
|
||||||
|
# High impression, low CTR keywords need content optimization
|
||||||
|
low_ctr_keywords = keywords_df[
|
||||||
|
(keywords_df['impressions'] > 500) &
|
||||||
|
(keywords_df['ctr'] < 3)
|
||||||
|
]
|
||||||
|
|
||||||
|
for _, keyword_row in low_ctr_keywords.iterrows():
|
||||||
|
opportunities.append({
|
||||||
|
'type': 'Content Optimization',
|
||||||
|
'keyword': keyword_row['keyword'],
|
||||||
|
'opportunity': f"Optimize existing content for '{keyword_row['keyword']}' to improve CTR from {keyword_row['ctr']:.1f}%",
|
||||||
|
'potential_impact': 'High',
|
||||||
|
'current_position': round(keyword_row['position'], 1),
|
||||||
|
'impressions': int(keyword_row['impressions']),
|
||||||
|
'priority': 'High' if keyword_row['impressions'] > 1000 else 'Medium'
|
||||||
|
})
|
||||||
|
|
||||||
|
# Position 11-20 keywords need content improvement
|
||||||
|
position_11_20 = keywords_df[
|
||||||
|
(keywords_df['position'] > 10) &
|
||||||
|
(keywords_df['position'] <= 20) &
|
||||||
|
(keywords_df['impressions'] > 100)
|
||||||
|
]
|
||||||
|
|
||||||
|
for _, keyword_row in position_11_20.iterrows():
|
||||||
|
opportunities.append({
|
||||||
|
'type': 'Content Enhancement',
|
||||||
|
'keyword': keyword_row['keyword'],
|
||||||
|
'opportunity': f"Enhance content for '{keyword_row['keyword']}' to move from position {keyword_row['position']:.1f} to first page",
|
||||||
|
'potential_impact': 'Medium',
|
||||||
|
'current_position': round(keyword_row['position'], 1),
|
||||||
|
'impressions': int(keyword_row['impressions']),
|
||||||
|
'priority': 'Medium'
|
||||||
|
})
|
||||||
|
|
||||||
|
# Sort by potential impact and impressions
|
||||||
|
opportunities = sorted(opportunities, key=lambda x: x['impressions'], reverse=True)
|
||||||
|
|
||||||
|
return opportunities[:10] # Top 10 opportunities
|
||||||
|
|
||||||
|
def _analyze_technical_seo_signals(self, search_data: Dict[str, pd.DataFrame]) -> Dict[str, Any]:
|
||||||
|
"""Analyze technical SEO signals from search data."""
|
||||||
|
keywords_df = search_data['keywords']
|
||||||
|
pages_df = search_data['pages']
|
||||||
|
|
||||||
|
# Analyze performance patterns that might indicate technical issues
|
||||||
|
technical_insights = {
|
||||||
|
'crawl_issues_indicators': [],
|
||||||
|
'mobile_performance': {},
|
||||||
|
'core_web_vitals_impact': {},
|
||||||
|
'indexing_insights': {}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Identify potential crawl issues
|
||||||
|
very_low_impressions = keywords_df[keywords_df['impressions'] < 10]
|
||||||
|
if len(very_low_impressions) > len(keywords_df) * 0.3: # If 30%+ have very low impressions
|
||||||
|
technical_insights['crawl_issues_indicators'].append(
|
||||||
|
"High percentage of keywords with very low impressions may indicate crawl or indexing issues"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Mobile performance indicators
|
||||||
|
avg_mobile_position = keywords_df['position'].mean() # In real implementation, this would be mobile-specific
|
||||||
|
technical_insights['mobile_performance'] = {
|
||||||
|
'avg_mobile_position': round(avg_mobile_position, 1),
|
||||||
|
'mobile_optimization_needed': avg_mobile_position > 15
|
||||||
|
}
|
||||||
|
|
||||||
|
return technical_insights
|
||||||
|
|
||||||
|
def _analyze_competitive_position(self, search_data: Dict[str, pd.DataFrame]) -> Dict[str, Any]:
|
||||||
|
"""Analyze competitive positioning based on search data."""
|
||||||
|
keywords_df = search_data['keywords']
|
||||||
|
|
||||||
|
# Calculate competitive metrics
|
||||||
|
dominant_keywords = len(keywords_df[keywords_df['position'] <= 3])
|
||||||
|
competitive_keywords = len(keywords_df[(keywords_df['position'] > 3) & (keywords_df['position'] <= 10)])
|
||||||
|
losing_keywords = len(keywords_df[keywords_df['position'] > 10])
|
||||||
|
|
||||||
|
competitive_strength = (dominant_keywords * 3 + competitive_keywords * 2 + losing_keywords * 1) / len(keywords_df)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'dominant_keywords': dominant_keywords,
|
||||||
|
'competitive_keywords': competitive_keywords,
|
||||||
|
'losing_keywords': losing_keywords,
|
||||||
|
'competitive_strength_score': round(competitive_strength, 2),
|
||||||
|
'market_position': self._determine_market_position(competitive_strength)
|
||||||
|
}
|
||||||
|
|
||||||
|
def _generate_ai_recommendations(self, search_data: Dict[str, pd.DataFrame]) -> Dict[str, Any]:
|
||||||
|
"""Generate AI-powered recommendations based on search data."""
|
||||||
|
try:
|
||||||
|
keywords_df = search_data['keywords']
|
||||||
|
pages_df = search_data['pages']
|
||||||
|
|
||||||
|
# Prepare data summary for AI analysis
|
||||||
|
top_keywords = keywords_df.nlargest(5, 'impressions')['keyword'].tolist()
|
||||||
|
avg_position = keywords_df['position'].mean()
|
||||||
|
total_impressions = keywords_df['impressions'].sum()
|
||||||
|
total_clicks = keywords_df['clicks'].sum()
|
||||||
|
avg_ctr = (total_clicks / total_impressions * 100) if total_impressions > 0 else 0
|
||||||
|
|
||||||
|
# Create comprehensive prompt for AI analysis
|
||||||
|
ai_prompt = f"""
|
||||||
|
Analyze this Google Search Console data and provide strategic SEO recommendations:
|
||||||
|
|
||||||
|
SEARCH PERFORMANCE SUMMARY:
|
||||||
|
- Total Keywords Tracked: {len(keywords_df)}
|
||||||
|
- Total Impressions: {total_impressions:,}
|
||||||
|
- Total Clicks: {total_clicks:,}
|
||||||
|
- Average CTR: {avg_ctr:.2f}%
|
||||||
|
- Average Position: {avg_position:.1f}
|
||||||
|
|
||||||
|
TOP PERFORMING KEYWORDS:
|
||||||
|
{', '.join(top_keywords)}
|
||||||
|
|
||||||
|
PERFORMANCE DISTRIBUTION:
|
||||||
|
- Keywords ranking 1-3: {len(keywords_df[keywords_df['position'] <= 3])}
|
||||||
|
- Keywords ranking 4-10: {len(keywords_df[(keywords_df['position'] > 3) & (keywords_df['position'] <= 10)])}
|
||||||
|
- Keywords ranking 11-20: {len(keywords_df[(keywords_df['position'] > 10) & (keywords_df['position'] <= 20)])}
|
||||||
|
- Keywords ranking 21+: {len(keywords_df[keywords_df['position'] > 20])}
|
||||||
|
|
||||||
|
TOP PAGES BY TRAFFIC:
|
||||||
|
{pages_df.nlargest(3, 'clicks')['page'].tolist()}
|
||||||
|
|
||||||
|
Based on this data, provide:
|
||||||
|
|
||||||
|
1. IMMEDIATE OPTIMIZATION OPPORTUNITIES (0-30 days):
|
||||||
|
- Specific keywords to optimize for better CTR
|
||||||
|
- Pages that need content updates
|
||||||
|
- Quick technical wins
|
||||||
|
|
||||||
|
2. CONTENT STRATEGY RECOMMENDATIONS (1-3 months):
|
||||||
|
- New content topics based on keyword gaps
|
||||||
|
- Content enhancement priorities
|
||||||
|
- Internal linking opportunities
|
||||||
|
|
||||||
|
3. LONG-TERM SEO STRATEGY (3-12 months):
|
||||||
|
- Market expansion opportunities
|
||||||
|
- Authority building topics
|
||||||
|
- Competitive positioning strategies
|
||||||
|
|
||||||
|
4. TECHNICAL SEO PRIORITIES:
|
||||||
|
- Performance issues affecting rankings
|
||||||
|
- Mobile optimization needs
|
||||||
|
- Core Web Vitals improvements
|
||||||
|
|
||||||
|
Provide specific, actionable recommendations with expected impact and priority levels.
|
||||||
|
"""
|
||||||
|
|
||||||
|
ai_analysis = llm_text_gen(
|
||||||
|
ai_prompt,
|
||||||
|
system_prompt="You are an enterprise SEO strategist analyzing Google Search Console data. Provide specific, data-driven recommendations that will improve search performance."
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'full_analysis': ai_analysis,
|
||||||
|
'immediate_opportunities': self._extract_immediate_opportunities(ai_analysis),
|
||||||
|
'content_strategy': self._extract_content_strategy(ai_analysis),
|
||||||
|
'long_term_strategy': self._extract_long_term_strategy(ai_analysis),
|
||||||
|
'technical_priorities': self._extract_technical_priorities(ai_analysis)
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"AI recommendations error: {str(e)}")
|
||||||
|
return {'error': str(e)}
|
||||||
|
|
||||||
|
# Utility methods
|
||||||
|
def _identify_missing_keywords(self, keywords_df: pd.DataFrame) -> List[str]:
|
||||||
|
"""Identify potential missing keywords based on current keyword performance."""
|
||||||
|
# In a real implementation, this would use keyword research APIs
|
||||||
|
existing_keywords = set(keywords_df['keyword'].str.lower())
|
||||||
|
|
||||||
|
potential_keywords = [
|
||||||
|
"AI writing tools", "content automation", "SEO content generator",
|
||||||
|
"blog post optimizer", "meta tag generator", "keyword analyzer"
|
||||||
|
]
|
||||||
|
|
||||||
|
missing = [kw for kw in potential_keywords if kw.lower() not in existing_keywords]
|
||||||
|
return missing[:5]
|
||||||
|
|
||||||
|
def _analyze_seasonal_trends(self, keywords_df: pd.DataFrame) -> Dict[str, Any]:
|
||||||
|
"""Analyze seasonal trends in keyword performance."""
|
||||||
|
# Placeholder for seasonal analysis
|
||||||
|
return {
|
||||||
|
'seasonal_keywords': [],
|
||||||
|
'trend_analysis': "Seasonal analysis requires historical data spanning multiple seasons"
|
||||||
|
}
|
||||||
|
|
||||||
|
def _categorize_pages(self, pages_df: pd.DataFrame) -> Dict[str, Any]:
|
||||||
|
"""Categorize pages by type and analyze performance."""
|
||||||
|
page_types = {
|
||||||
|
'Blog Posts': {'count': 0, 'total_clicks': 0, 'avg_position': 0},
|
||||||
|
'Product Pages': {'count': 0, 'total_clicks': 0, 'avg_position': 0},
|
||||||
|
'Tool Pages': {'count': 0, 'total_clicks': 0, 'avg_position': 0},
|
||||||
|
'Other': {'count': 0, 'total_clicks': 0, 'avg_position': 0}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, page_row in pages_df.iterrows():
|
||||||
|
page_url = page_row['page']
|
||||||
|
clicks = page_row['clicks']
|
||||||
|
position = page_row['position']
|
||||||
|
|
||||||
|
if '/blog/' in page_url:
|
||||||
|
page_types['Blog Posts']['count'] += 1
|
||||||
|
page_types['Blog Posts']['total_clicks'] += clicks
|
||||||
|
page_types['Blog Posts']['avg_position'] += position
|
||||||
|
elif '/tools/' in page_url:
|
||||||
|
page_types['Tool Pages']['count'] += 1
|
||||||
|
page_types['Tool Pages']['total_clicks'] += clicks
|
||||||
|
page_types['Tool Pages']['avg_position'] += position
|
||||||
|
elif '/features/' in page_url or '/product/' in page_url:
|
||||||
|
page_types['Product Pages']['count'] += 1
|
||||||
|
page_types['Product Pages']['total_clicks'] += clicks
|
||||||
|
page_types['Product Pages']['avg_position'] += position
|
||||||
|
else:
|
||||||
|
page_types['Other']['count'] += 1
|
||||||
|
page_types['Other']['total_clicks'] += clicks
|
||||||
|
page_types['Other']['avg_position'] += position
|
||||||
|
|
||||||
|
# Calculate averages
|
||||||
|
for page_type in page_types:
|
||||||
|
if page_types[page_type]['count'] > 0:
|
||||||
|
page_types[page_type]['avg_position'] = round(
|
||||||
|
page_types[page_type]['avg_position'] / page_types[page_type]['count'], 1
|
||||||
|
)
|
||||||
|
|
||||||
|
return page_types
|
||||||
|
|
||||||
|
def _determine_market_position(self, competitive_strength: float) -> str:
|
||||||
|
"""Determine market position based on competitive strength score."""
|
||||||
|
if competitive_strength >= 2.5:
|
||||||
|
return "Market Leader"
|
||||||
|
elif competitive_strength >= 2.0:
|
||||||
|
return "Strong Competitor"
|
||||||
|
elif competitive_strength >= 1.5:
|
||||||
|
return "Emerging Player"
|
||||||
|
else:
|
||||||
|
return "Challenger"
|
||||||
|
|
||||||
|
def _extract_immediate_opportunities(self, analysis: str) -> List[str]:
|
||||||
|
"""Extract immediate opportunities from AI analysis."""
|
||||||
|
lines = analysis.split('\n')
|
||||||
|
opportunities = []
|
||||||
|
in_immediate_section = False
|
||||||
|
|
||||||
|
for line in lines:
|
||||||
|
if 'IMMEDIATE OPTIMIZATION' in line.upper():
|
||||||
|
in_immediate_section = True
|
||||||
|
continue
|
||||||
|
elif 'CONTENT STRATEGY' in line.upper():
|
||||||
|
in_immediate_section = False
|
||||||
|
continue
|
||||||
|
|
||||||
|
if in_immediate_section and line.strip().startswith('-'):
|
||||||
|
opportunities.append(line.strip().lstrip('- '))
|
||||||
|
|
||||||
|
return opportunities[:5]
|
||||||
|
|
||||||
|
def _extract_content_strategy(self, analysis: str) -> List[str]:
|
||||||
|
"""Extract content strategy recommendations from AI analysis."""
|
||||||
|
return ["Develop topic clusters", "Create comparison content", "Build FAQ sections"]
|
||||||
|
|
||||||
|
def _extract_long_term_strategy(self, analysis: str) -> List[str]:
|
||||||
|
"""Extract long-term strategy from AI analysis."""
|
||||||
|
return ["Build domain authority", "Expand to new markets", "Develop thought leadership content"]
|
||||||
|
|
||||||
|
def _extract_technical_priorities(self, analysis: str) -> List[str]:
|
||||||
|
"""Extract technical priorities from AI analysis."""
|
||||||
|
return ["Improve page speed", "Optimize mobile experience", "Fix crawl errors"]
|
||||||
|
|
||||||
|
|
||||||
|
def render_gsc_integration():
|
||||||
|
"""Render the Google Search Console integration interface."""
|
||||||
|
|
||||||
|
st.title("📊 Google Search Console Intelligence")
|
||||||
|
st.markdown("**AI-powered insights from your Google Search Console data**")
|
||||||
|
|
||||||
|
# Initialize analyzer
|
||||||
|
if 'gsc_analyzer' not in st.session_state:
|
||||||
|
st.session_state.gsc_analyzer = GoogleSearchConsoleAnalyzer()
|
||||||
|
|
||||||
|
analyzer = st.session_state.gsc_analyzer
|
||||||
|
|
||||||
|
# Configuration section
|
||||||
|
st.header("🔧 Configuration")
|
||||||
|
|
||||||
|
with st.expander("📋 Setup Instructions", expanded=False):
|
||||||
|
st.markdown("""
|
||||||
|
### Setting up Google Search Console Integration
|
||||||
|
|
||||||
|
1. **Verify your website** in Google Search Console
|
||||||
|
2. **Enable the Search Console API** in Google Cloud Console
|
||||||
|
3. **Create service account credentials** and download the JSON file
|
||||||
|
4. **Upload credentials** using the file uploader below
|
||||||
|
|
||||||
|
📚 [Detailed Setup Guide](https://developers.google.com/webmaster-tools/search-console-api-original/v3/prereqs)
|
||||||
|
""")
|
||||||
|
|
||||||
|
# Input form
|
||||||
|
with st.form("gsc_analysis_form"):
|
||||||
|
col1, col2 = st.columns(2)
|
||||||
|
|
||||||
|
with col1:
|
||||||
|
site_url = st.text_input(
|
||||||
|
"Site URL",
|
||||||
|
value="https://example.com",
|
||||||
|
help="Enter your website URL as registered in Google Search Console"
|
||||||
|
)
|
||||||
|
|
||||||
|
date_range = st.selectbox(
|
||||||
|
"Analysis Period",
|
||||||
|
[30, 60, 90, 180],
|
||||||
|
index=2,
|
||||||
|
help="Number of days to analyze"
|
||||||
|
)
|
||||||
|
|
||||||
|
with col2:
|
||||||
|
# Credentials upload (placeholder)
|
||||||
|
credentials_file = st.file_uploader(
|
||||||
|
"GSC API Credentials (JSON)",
|
||||||
|
type=['json'],
|
||||||
|
help="Upload your Google Search Console API credentials file"
|
||||||
|
)
|
||||||
|
|
||||||
|
demo_mode = st.checkbox(
|
||||||
|
"Demo Mode",
|
||||||
|
value=True,
|
||||||
|
help="Use demo data for testing (no credentials needed)"
|
||||||
|
)
|
||||||
|
|
||||||
|
submit_analysis = st.form_submit_button("📊 Analyze Search Performance", type="primary")
|
||||||
|
|
||||||
|
# Process analysis
|
||||||
|
if submit_analysis:
|
||||||
|
if site_url and (demo_mode or credentials_file):
|
||||||
|
with st.spinner("📊 Analyzing Google Search Console data..."):
|
||||||
|
analysis_results = analyzer.analyze_search_performance(site_url, date_range)
|
||||||
|
|
||||||
|
if 'error' not in analysis_results:
|
||||||
|
st.success("✅ Search Console analysis completed!")
|
||||||
|
|
||||||
|
# Store results in session state
|
||||||
|
st.session_state.gsc_results = analysis_results
|
||||||
|
|
||||||
|
# Display results
|
||||||
|
render_gsc_results_dashboard(analysis_results)
|
||||||
|
else:
|
||||||
|
st.error(f"❌ Analysis failed: {analysis_results['error']}")
|
||||||
|
else:
|
||||||
|
st.warning("⚠️ Please enter site URL and upload credentials (or enable demo mode).")
|
||||||
|
|
||||||
|
# Show previous results if available
|
||||||
|
elif 'gsc_results' in st.session_state:
|
||||||
|
st.info("📊 Showing previous analysis results")
|
||||||
|
render_gsc_results_dashboard(st.session_state.gsc_results)
|
||||||
|
|
||||||
|
|
||||||
|
def render_gsc_results_dashboard(results: Dict[str, Any]):
|
||||||
|
"""Render comprehensive GSC analysis results."""
|
||||||
|
|
||||||
|
# Performance overview
|
||||||
|
st.header("📊 Search Performance Overview")
|
||||||
|
|
||||||
|
overview = results['performance_overview']
|
||||||
|
|
||||||
|
col1, col2, col3, col4 = st.columns(4)
|
||||||
|
|
||||||
|
with col1:
|
||||||
|
st.metric(
|
||||||
|
"Total Clicks",
|
||||||
|
f"{overview['total_clicks']:,}",
|
||||||
|
delta=f"{overview['clicks_trend']:+.1f}%" if overview['clicks_trend'] != 0 else None
|
||||||
|
)
|
||||||
|
|
||||||
|
with col2:
|
||||||
|
st.metric(
|
||||||
|
"Total Impressions",
|
||||||
|
f"{overview['total_impressions']:,}",
|
||||||
|
delta=f"{overview['impressions_trend']:+.1f}%" if overview['impressions_trend'] != 0 else None
|
||||||
|
)
|
||||||
|
|
||||||
|
with col3:
|
||||||
|
st.metric(
|
||||||
|
"Average CTR",
|
||||||
|
f"{overview['avg_ctr']:.2f}%"
|
||||||
|
)
|
||||||
|
|
||||||
|
with col4:
|
||||||
|
st.metric(
|
||||||
|
"Average Position",
|
||||||
|
f"{overview['avg_position']:.1f}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Content opportunities (Most important section)
|
||||||
|
st.header("🎯 Content Opportunities")
|
||||||
|
|
||||||
|
opportunities = results['content_opportunities']
|
||||||
|
if opportunities:
|
||||||
|
# Display as interactive table
|
||||||
|
df_opportunities = pd.DataFrame(opportunities)
|
||||||
|
|
||||||
|
st.dataframe(
|
||||||
|
df_opportunities,
|
||||||
|
column_config={
|
||||||
|
"type": "Opportunity Type",
|
||||||
|
"keyword": "Keyword",
|
||||||
|
"opportunity": "Description",
|
||||||
|
"potential_impact": st.column_config.SelectboxColumn(
|
||||||
|
"Impact",
|
||||||
|
options=["High", "Medium", "Low"]
|
||||||
|
),
|
||||||
|
"current_position": st.column_config.NumberColumn(
|
||||||
|
"Current Position",
|
||||||
|
format="%.1f"
|
||||||
|
),
|
||||||
|
"impressions": st.column_config.NumberColumn(
|
||||||
|
"Impressions",
|
||||||
|
format="%d"
|
||||||
|
),
|
||||||
|
"priority": st.column_config.SelectboxColumn(
|
||||||
|
"Priority",
|
||||||
|
options=["High", "Medium", "Low"]
|
||||||
|
)
|
||||||
|
},
|
||||||
|
hide_index=True,
|
||||||
|
use_container_width=True
|
||||||
|
)
|
||||||
|
|
||||||
|
# Detailed analysis tabs
|
||||||
|
tab1, tab2, tab3, tab4, tab5 = st.tabs([
|
||||||
|
"🤖 AI Insights",
|
||||||
|
"🎯 Keyword Analysis",
|
||||||
|
"📄 Page Performance",
|
||||||
|
"🏆 Competitive Position",
|
||||||
|
"🔧 Technical Signals"
|
||||||
|
])
|
||||||
|
|
||||||
|
with tab1:
|
||||||
|
ai_recs = results.get('ai_recommendations', {})
|
||||||
|
if ai_recs and 'error' not in ai_recs:
|
||||||
|
st.subheader("AI-Powered Recommendations")
|
||||||
|
|
||||||
|
# Immediate opportunities
|
||||||
|
immediate_ops = ai_recs.get('immediate_opportunities', [])
|
||||||
|
if immediate_ops:
|
||||||
|
st.markdown("#### 🚀 Immediate Optimizations (0-30 days)")
|
||||||
|
for op in immediate_ops:
|
||||||
|
st.success(f"✅ {op}")
|
||||||
|
|
||||||
|
# Content strategy
|
||||||
|
content_strategy = ai_recs.get('content_strategy', [])
|
||||||
|
if content_strategy:
|
||||||
|
st.markdown("#### 📝 Content Strategy (1-3 months)")
|
||||||
|
for strategy in content_strategy:
|
||||||
|
st.info(f"📋 {strategy}")
|
||||||
|
|
||||||
|
# Full analysis
|
||||||
|
full_analysis = ai_recs.get('full_analysis', '')
|
||||||
|
if full_analysis:
|
||||||
|
with st.expander("🧠 Complete AI Analysis"):
|
||||||
|
st.write(full_analysis)
|
||||||
|
|
||||||
|
with tab2:
|
||||||
|
keyword_analysis = results.get('keyword_analysis', {})
|
||||||
|
if keyword_analysis:
|
||||||
|
st.subheader("Keyword Performance Analysis")
|
||||||
|
|
||||||
|
# Keyword distribution chart
|
||||||
|
dist = keyword_analysis['keyword_distribution']
|
||||||
|
fig = px.pie(
|
||||||
|
values=[dist['positions_1_3'], dist['positions_4_10'], dist['positions_11_20'], dist['positions_21_plus']],
|
||||||
|
names=['Positions 1-3', 'Positions 4-10', 'Positions 11-20', 'Positions 21+'],
|
||||||
|
title="Keyword Position Distribution"
|
||||||
|
)
|
||||||
|
st.plotly_chart(fig, use_container_width=True)
|
||||||
|
|
||||||
|
# High volume keywords
|
||||||
|
high_volume = keyword_analysis.get('high_volume_keywords', [])
|
||||||
|
if high_volume:
|
||||||
|
st.markdown("#### 📈 High Volume Keywords")
|
||||||
|
st.dataframe(pd.DataFrame(high_volume), hide_index=True)
|
||||||
|
|
||||||
|
# Optimization opportunities
|
||||||
|
opt_opportunities = keyword_analysis.get('optimization_opportunities', [])
|
||||||
|
if opt_opportunities:
|
||||||
|
st.markdown("#### 🎯 Optimization Opportunities (Positions 11-20)")
|
||||||
|
st.dataframe(pd.DataFrame(opt_opportunities), hide_index=True)
|
||||||
|
|
||||||
|
with tab3:
|
||||||
|
page_analysis = results.get('page_analysis', {})
|
||||||
|
if page_analysis:
|
||||||
|
st.subheader("Page Performance Analysis")
|
||||||
|
|
||||||
|
# Top pages
|
||||||
|
top_pages = page_analysis.get('top_pages', [])
|
||||||
|
if top_pages:
|
||||||
|
st.markdown("#### 🏆 Top Performing Pages")
|
||||||
|
st.dataframe(pd.DataFrame(top_pages), hide_index=True)
|
||||||
|
|
||||||
|
# Underperforming pages
|
||||||
|
underperforming = page_analysis.get('underperforming_pages', [])
|
||||||
|
if underperforming:
|
||||||
|
st.markdown("#### ⚠️ Underperforming Pages (High Impressions, Low CTR)")
|
||||||
|
st.dataframe(pd.DataFrame(underperforming), hide_index=True)
|
||||||
|
|
||||||
|
# Page types performance
|
||||||
|
page_types = page_analysis.get('page_types_performance', {})
|
||||||
|
if page_types:
|
||||||
|
st.markdown("#### 📊 Performance by Page Type")
|
||||||
|
|
||||||
|
# Create visualization
|
||||||
|
types = []
|
||||||
|
clicks = []
|
||||||
|
positions = []
|
||||||
|
|
||||||
|
for page_type, data in page_types.items():
|
||||||
|
if data['count'] > 0:
|
||||||
|
types.append(page_type)
|
||||||
|
clicks.append(data['total_clicks'])
|
||||||
|
positions.append(data['avg_position'])
|
||||||
|
|
||||||
|
if types:
|
||||||
|
col1, col2 = st.columns(2)
|
||||||
|
|
||||||
|
with col1:
|
||||||
|
fig_clicks = px.bar(x=types, y=clicks, title="Total Clicks by Page Type")
|
||||||
|
st.plotly_chart(fig_clicks, use_container_width=True)
|
||||||
|
|
||||||
|
with col2:
|
||||||
|
fig_position = px.bar(x=types, y=positions, title="Average Position by Page Type")
|
||||||
|
st.plotly_chart(fig_position, use_container_width=True)
|
||||||
|
|
||||||
|
with tab4:
|
||||||
|
competitive_analysis = results.get('competitive_analysis', {})
|
||||||
|
if competitive_analysis:
|
||||||
|
st.subheader("Competitive Position Analysis")
|
||||||
|
|
||||||
|
col1, col2 = st.columns(2)
|
||||||
|
|
||||||
|
with col1:
|
||||||
|
st.metric("Market Position", competitive_analysis['market_position'])
|
||||||
|
st.metric("Competitive Strength", f"{competitive_analysis['competitive_strength_score']}/3.0")
|
||||||
|
|
||||||
|
with col2:
|
||||||
|
# Competitive distribution
|
||||||
|
comp_data = {
|
||||||
|
'Dominant (1-3)': competitive_analysis['dominant_keywords'],
|
||||||
|
'Competitive (4-10)': competitive_analysis['competitive_keywords'],
|
||||||
|
'Losing (11+)': competitive_analysis['losing_keywords']
|
||||||
|
}
|
||||||
|
|
||||||
|
fig = px.bar(
|
||||||
|
x=list(comp_data.keys()),
|
||||||
|
y=list(comp_data.values()),
|
||||||
|
title="Keyword Competitive Position"
|
||||||
|
)
|
||||||
|
st.plotly_chart(fig, use_container_width=True)
|
||||||
|
|
||||||
|
with tab5:
|
||||||
|
technical_insights = results.get('technical_insights', {})
|
||||||
|
if technical_insights:
|
||||||
|
st.subheader("Technical SEO Signals")
|
||||||
|
|
||||||
|
# Crawl issues indicators
|
||||||
|
crawl_issues = technical_insights.get('crawl_issues_indicators', [])
|
||||||
|
if crawl_issues:
|
||||||
|
st.markdown("#### ⚠️ Potential Issues")
|
||||||
|
for issue in crawl_issues:
|
||||||
|
st.warning(f"🚨 {issue}")
|
||||||
|
|
||||||
|
# Mobile performance
|
||||||
|
mobile_perf = technical_insights.get('mobile_performance', {})
|
||||||
|
if mobile_perf:
|
||||||
|
st.markdown("#### 📱 Mobile Performance")
|
||||||
|
col1, col2 = st.columns(2)
|
||||||
|
|
||||||
|
with col1:
|
||||||
|
st.metric("Avg Mobile Position", f"{mobile_perf.get('avg_mobile_position', 0):.1f}")
|
||||||
|
|
||||||
|
with col2:
|
||||||
|
if mobile_perf.get('mobile_optimization_needed', False):
|
||||||
|
st.warning("📱 Mobile optimization needed")
|
||||||
|
else:
|
||||||
|
st.success("📱 Mobile performance good")
|
||||||
|
|
||||||
|
# Export functionality
|
||||||
|
st.markdown("---")
|
||||||
|
col1, col2, col3 = st.columns(3)
|
||||||
|
|
||||||
|
with col1:
|
||||||
|
if st.button("📥 Export Full Report", use_container_width=True):
|
||||||
|
report_json = json.dumps(results, indent=2, default=str)
|
||||||
|
st.download_button(
|
||||||
|
label="Download JSON Report",
|
||||||
|
data=report_json,
|
||||||
|
file_name=f"gsc_analysis_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json",
|
||||||
|
mime="application/json"
|
||||||
|
)
|
||||||
|
|
||||||
|
with col2:
|
||||||
|
if st.button("📊 Export Opportunities", use_container_width=True):
|
||||||
|
if opportunities:
|
||||||
|
df_opportunities = pd.DataFrame(opportunities)
|
||||||
|
csv = df_opportunities.to_csv(index=False)
|
||||||
|
st.download_button(
|
||||||
|
label="Download CSV Opportunities",
|
||||||
|
data=csv,
|
||||||
|
file_name=f"content_opportunities_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv",
|
||||||
|
mime="text/csv"
|
||||||
|
)
|
||||||
|
|
||||||
|
with col3:
|
||||||
|
if st.button("🔄 Refresh Analysis", use_container_width=True):
|
||||||
|
# Clear cached results to force refresh
|
||||||
|
if 'gsc_results' in st.session_state:
|
||||||
|
del st.session_state.gsc_results
|
||||||
|
st.rerun()
|
||||||
|
|
||||||
|
|
||||||
|
# Main execution
|
||||||
|
if __name__ == "__main__":
|
||||||
|
render_gsc_integration()
|
||||||
112
ToBeMigrated/ai_seo_tools/image_alt_text_generator.py
Normal file
112
ToBeMigrated/ai_seo_tools/image_alt_text_generator.py
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
import streamlit as st
|
||||||
|
import base64
|
||||||
|
import requests
|
||||||
|
from PIL import Image
|
||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
|
def encode_image(image_path):
|
||||||
|
"""
|
||||||
|
Encodes an image to base64 format.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
image_path (str): Path to the image file.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: Base64 encoded string of the image.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ValueError: If the image path is invalid.
|
||||||
|
"""
|
||||||
|
safe_root = os.getenv('SAFE_ROOT_DIRECTORY', '/safe/root/directory') # Use an environment variable for the safe root directory
|
||||||
|
normalized_path = os.path.normpath(image_path)
|
||||||
|
if not normalized_path.startswith(safe_root):
|
||||||
|
raise ValueError("Invalid image path")
|
||||||
|
with open(normalized_path, "rb") as image_file:
|
||||||
|
return base64.b64encode(image_file.read()).decode('utf-8')
|
||||||
|
|
||||||
|
|
||||||
|
def get_image_description(image_path):
|
||||||
|
"""
|
||||||
|
Generates a description for the given image using an external API.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
image_path (str): Path to the image file.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: Description of the image.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ValueError: If the image path is invalid.
|
||||||
|
"""
|
||||||
|
safe_root = os.getenv('SAFE_ROOT_DIRECTORY', '/safe/root/directory') # Use an environment variable for the safe root directory
|
||||||
|
normalized_path = os.path.normpath(image_path)
|
||||||
|
if not normalized_path.startswith(safe_root):
|
||||||
|
raise ValueError("Invalid image path")
|
||||||
|
base64_image = encode_image(normalized_path)
|
||||||
|
|
||||||
|
headers = {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"Authorization": f"Bearer {os.getenv('OPENAI_API_KEY')}"
|
||||||
|
}
|
||||||
|
|
||||||
|
payload = {
|
||||||
|
"model": "gpt-4o-mini",
|
||||||
|
"messages": [
|
||||||
|
{
|
||||||
|
"role": "user",
|
||||||
|
"content": [
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": """You are an SEO expert specializing in writing optimized Alt text for images.
|
||||||
|
Your goal is to create clear, descriptive, and concise Alt text that accurately represents
|
||||||
|
the content and context of the given image. Make sure your response is optimized for search engines and accessibility."""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "image_url",
|
||||||
|
"image_url": {
|
||||||
|
"url": f"data:image/jpeg;base64,{base64_image}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"max_tokens": 300
|
||||||
|
}
|
||||||
|
|
||||||
|
response = requests.post("https://api.openai.com/v1/chat/completions", headers=headers, json=payload)
|
||||||
|
response_data = response.json()
|
||||||
|
|
||||||
|
# Extract the content field from the response
|
||||||
|
content = response_data['choices'][0]['message']['content']
|
||||||
|
return content
|
||||||
|
|
||||||
|
|
||||||
|
def alt_text_gen():
|
||||||
|
"""
|
||||||
|
Streamlit app function to generate Alt text for an uploaded image.
|
||||||
|
"""
|
||||||
|
st.title("Image Description Generator")
|
||||||
|
|
||||||
|
image_path = st.text_input("Enter the full path of the image file", help="Provide the full path to a .jpg, .jpeg, or .png image file")
|
||||||
|
|
||||||
|
if image_path:
|
||||||
|
if os.path.exists(image_path) and image_path.lower().endswith(('jpg', 'jpeg', 'png')):
|
||||||
|
try:
|
||||||
|
image = Image.open(image_path)
|
||||||
|
st.image(image, caption='Uploaded Image', use_column_width=True)
|
||||||
|
|
||||||
|
if st.button("Get Image Alt Text"):
|
||||||
|
with st.spinner("Generating Alt Text..."):
|
||||||
|
try:
|
||||||
|
description = get_image_description(image_path)
|
||||||
|
st.success("Alt Text generated successfully!")
|
||||||
|
st.write("Alt Text:", description)
|
||||||
|
except Exception as e:
|
||||||
|
st.error(f"Error generating description: {e}")
|
||||||
|
except Exception as e:
|
||||||
|
st.error(f"Error processing image: {e}")
|
||||||
|
else:
|
||||||
|
st.error("Please enter a valid image file path ending with .jpg, .jpeg, or .png")
|
||||||
|
else:
|
||||||
|
st.info("Please enter the full path of an image file.")
|
||||||
110
ToBeMigrated/ai_seo_tools/meta_desc_generator.py
Normal file
110
ToBeMigrated/ai_seo_tools/meta_desc_generator.py
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
import os
|
||||||
|
import json
|
||||||
|
import streamlit as st
|
||||||
|
from tenacity import retry, stop_after_attempt, wait_random_exponential
|
||||||
|
from loguru import logger
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from ..gpt_providers.text_generation.main_text_generation import llm_text_gen
|
||||||
|
|
||||||
|
|
||||||
|
def metadesc_generator_main():
|
||||||
|
"""
|
||||||
|
Streamlit app for generating SEO-optimized blog meta descriptions.
|
||||||
|
"""
|
||||||
|
st.title("✍️ Alwrity - AI Blog Meta Description Generator")
|
||||||
|
st.markdown(
|
||||||
|
"Create compelling, SEO-optimized meta descriptions in just a few clicks. Perfect for enhancing your blog's click-through rates!"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Input section
|
||||||
|
with st.expander("**PRO-TIP** - Read the instructions below. 🚀", expanded=True):
|
||||||
|
col1, col2, _ = st.columns([5, 5, 0.5])
|
||||||
|
|
||||||
|
# Column 1: Keywords and Tone
|
||||||
|
with col1:
|
||||||
|
keywords = st.text_input(
|
||||||
|
"🔑 Target Keywords (comma-separated):",
|
||||||
|
placeholder="e.g., content marketing, SEO, social media, online business",
|
||||||
|
help="Enter your target keywords, separated by commas. 📝",
|
||||||
|
)
|
||||||
|
|
||||||
|
tone_options = ["General", "Informative", "Engaging", "Humorous", "Intriguing", "Playful"]
|
||||||
|
tone = st.selectbox(
|
||||||
|
"🎨 Desired Tone (optional):",
|
||||||
|
options=tone_options,
|
||||||
|
help="Choose the overall tone you want for your meta description. 🎭",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Column 2: Search Intent and Language
|
||||||
|
with col2:
|
||||||
|
search_type = st.selectbox(
|
||||||
|
"🔍 Search Intent:",
|
||||||
|
("Informational Intent", "Commercial Intent", "Transactional Intent", "Navigational Intent"),
|
||||||
|
index=0,
|
||||||
|
)
|
||||||
|
|
||||||
|
language_options = ["English", "Spanish", "French", "German", "Other"]
|
||||||
|
language_choice = st.selectbox(
|
||||||
|
"🌐 Preferred Language:",
|
||||||
|
options=language_options,
|
||||||
|
help="Select the language for your meta description. 🗣️",
|
||||||
|
)
|
||||||
|
|
||||||
|
language = (
|
||||||
|
st.text_input(
|
||||||
|
"Specify Other Language:",
|
||||||
|
placeholder="e.g., Italian, Chinese",
|
||||||
|
help="Enter your preferred language. 🌍",
|
||||||
|
)
|
||||||
|
if language_choice == "Other"
|
||||||
|
else language_choice
|
||||||
|
)
|
||||||
|
|
||||||
|
# Generate Meta Description button
|
||||||
|
if st.button("**✨ Generate Meta Description ✨**"):
|
||||||
|
if not keywords.strip():
|
||||||
|
st.error("**🫣 Target Keywords are required! Please provide at least one keyword.**")
|
||||||
|
return
|
||||||
|
|
||||||
|
with st.spinner("Crafting your Meta descriptions... ⏳"):
|
||||||
|
blog_metadesc = generate_blog_metadesc(keywords, tone, search_type, language)
|
||||||
|
if blog_metadesc:
|
||||||
|
st.success("**🎉 Meta Descriptions Generated Successfully! 🚀**")
|
||||||
|
with st.expander("**Your SEO-Boosting Blog Meta Descriptions 🎆🎇**", expanded=True):
|
||||||
|
st.markdown(blog_metadesc)
|
||||||
|
else:
|
||||||
|
st.error("💥 **Failed to generate blog meta description. Please try again!**")
|
||||||
|
|
||||||
|
|
||||||
|
def generate_blog_metadesc(keywords, tone, search_type, language):
|
||||||
|
"""
|
||||||
|
Generate blog meta descriptions using LLM.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
keywords (str): Comma-separated target keywords.
|
||||||
|
tone (str): Desired tone for the meta description.
|
||||||
|
search_type (str): Search intent type.
|
||||||
|
language (str): Preferred language for the description.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: Generated meta descriptions or error message.
|
||||||
|
"""
|
||||||
|
prompt = f"""
|
||||||
|
Craft 3 engaging and SEO-friendly meta descriptions for a blog post based on the following details:
|
||||||
|
|
||||||
|
Blog Post Keywords: {keywords}
|
||||||
|
Search Intent Type: {search_type}
|
||||||
|
Desired Tone: {tone}
|
||||||
|
Preferred Language: {language}
|
||||||
|
|
||||||
|
Output Format:
|
||||||
|
|
||||||
|
Respond with 3 compelling and concise meta descriptions, approximately 155-160 characters long, that incorporate the target keywords, reflect the blog post content, resonate with the target audience, and entice users to click through to read the full article.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return llm_text_gen(prompt)
|
||||||
|
except Exception as err:
|
||||||
|
logger.error(f"Error generating meta description: {err}")
|
||||||
|
st.error(f"💥 Error: Failed to generate response from LLM: {err}")
|
||||||
|
return None
|
||||||
1070
ToBeMigrated/ai_seo_tools/on_page_seo_analyzer.py
Normal file
1070
ToBeMigrated/ai_seo_tools/on_page_seo_analyzer.py
Normal file
File diff suppressed because it is too large
Load Diff
129
ToBeMigrated/ai_seo_tools/opengraph_generator.py
Normal file
129
ToBeMigrated/ai_seo_tools/opengraph_generator.py
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
import streamlit as st
|
||||||
|
import requests
|
||||||
|
from bs4 import BeautifulSoup
|
||||||
|
from ..gpt_providers.text_generation.main_text_generation import llm_text_gen
|
||||||
|
|
||||||
|
|
||||||
|
def generate_og_tags(url, title_hint, description_hint, platform="General"):
|
||||||
|
"""
|
||||||
|
Generate Open Graph tags based on the provided URL, title hint, description hint, and platform.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
url (str): The URL of the webpage.
|
||||||
|
title_hint (str): A hint for the title.
|
||||||
|
description_hint (str): A hint for the description.
|
||||||
|
platform (str): The platform for which to generate the tags (General, Facebook, or Twitter).
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: The generated Open Graph tags or an error message.
|
||||||
|
"""
|
||||||
|
# Create a prompt for the text generation model
|
||||||
|
prompt = (
|
||||||
|
f"Generate Open Graph tags for the following page:\nURL: {url}\n"
|
||||||
|
f"Title hint: {title_hint}\nDescription hint: {description_hint}"
|
||||||
|
)
|
||||||
|
if platform == "Facebook":
|
||||||
|
prompt += "\nSpecifically for Facebook"
|
||||||
|
elif platform == "Twitter":
|
||||||
|
prompt += "\nSpecifically for Twitter"
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Generate Open Graph tags using the text generation model
|
||||||
|
response = llm_text_gen(prompt)
|
||||||
|
return response
|
||||||
|
except Exception as err:
|
||||||
|
st.error(f"Failed to generate Open Graph tags: {err}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def extract_default_og_tags(url):
|
||||||
|
"""
|
||||||
|
Extract default Open Graph tags from the provided URL.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
url (str): The URL of the webpage.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
tuple: A tuple containing the title, description, and image URL, or None in case of an error.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Fetch the HTML content of the URL
|
||||||
|
response = requests.get(url)
|
||||||
|
response.raise_for_status()
|
||||||
|
|
||||||
|
# Parse the HTML content using BeautifulSoup
|
||||||
|
soup = BeautifulSoup(response.content, 'html.parser')
|
||||||
|
|
||||||
|
# Extract the title, description, and image URL
|
||||||
|
title = soup.find('title').text if soup.find('title') else None
|
||||||
|
description = soup.find('meta', attrs={'name': 'description'})['content'] if soup.find('meta', attrs={'name': 'description'}) else None
|
||||||
|
image_url = soup.find('meta', attrs={'property': 'og:image'})['content'] if soup.find('meta', attrs={'property': 'og:image'}) else None
|
||||||
|
|
||||||
|
return title, description, image_url
|
||||||
|
|
||||||
|
except requests.exceptions.RequestException as req_err:
|
||||||
|
st.error(f"Error fetching the URL: {req_err}")
|
||||||
|
return None, None, None
|
||||||
|
|
||||||
|
except Exception as err:
|
||||||
|
st.error(f"Error parsing the HTML content: {err}")
|
||||||
|
return None, None, None
|
||||||
|
|
||||||
|
|
||||||
|
def og_tag_generator():
|
||||||
|
"""Main function to run the Streamlit app."""
|
||||||
|
st.title("AI Open Graph Tag Generator")
|
||||||
|
|
||||||
|
# Platform selection
|
||||||
|
platform = st.selectbox(
|
||||||
|
"**Select the platform**",
|
||||||
|
["General", "Facebook", "Twitter"],
|
||||||
|
help="Choose the platform for which you want to generate Open Graph tags."
|
||||||
|
)
|
||||||
|
|
||||||
|
# URL input
|
||||||
|
url = st.text_input(
|
||||||
|
"**Enter the URL of the page to generate Open Graph tags for:**",
|
||||||
|
placeholder="e.g., https://example.com",
|
||||||
|
help="Provide the URL of the page you want to generate Open Graph tags for."
|
||||||
|
)
|
||||||
|
|
||||||
|
if url:
|
||||||
|
# Extract default Open Graph tags
|
||||||
|
title, description, image_url = extract_default_og_tags(url)
|
||||||
|
|
||||||
|
# Title hint input
|
||||||
|
title_hint = st.text_input(
|
||||||
|
"**Modify existing title or suggest a new one (optional):**",
|
||||||
|
value=title if title else "",
|
||||||
|
placeholder="e.g., Amazing Blog Post Title"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Description hint input
|
||||||
|
description_hint = st.text_area(
|
||||||
|
"**Modify existing description or suggest a new one (optional):**",
|
||||||
|
value=description if description else "",
|
||||||
|
placeholder="e.g., This is a detailed description of the content."
|
||||||
|
)
|
||||||
|
|
||||||
|
# Image URL hint input
|
||||||
|
image_hint = st.text_input(
|
||||||
|
"**Use this image or suggest a new URL (optional):**",
|
||||||
|
value=image_url if image_url else "",
|
||||||
|
placeholder="e.g., https://example.com/image.jpg"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Generate Open Graph tags
|
||||||
|
if st.button("Generate Open Graph Tags"):
|
||||||
|
with st.spinner("Generating Open Graph tags..."):
|
||||||
|
try:
|
||||||
|
og_tags = generate_og_tags(url, title_hint, description_hint, platform)
|
||||||
|
if og_tags:
|
||||||
|
st.success("Open Graph tags generated successfully!")
|
||||||
|
st.markdown(og_tags)
|
||||||
|
else:
|
||||||
|
st.error("Failed to generate Open Graph tags.")
|
||||||
|
except Exception as e:
|
||||||
|
st.error(f"Failed to generate Open Graph tags: {e}")
|
||||||
|
else:
|
||||||
|
st.info("Please enter a URL to generate Open Graph tags.")
|
||||||
2
ToBeMigrated/ai_seo_tools/opengraph_image_generate.py
Normal file
2
ToBeMigrated/ai_seo_tools/opengraph_image_generate.py
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
|
||||||
|
ogImage TBD
|
||||||
187
ToBeMigrated/ai_seo_tools/optimize_images_for_upload.py
Normal file
187
ToBeMigrated/ai_seo_tools/optimize_images_for_upload.py
Normal file
@@ -0,0 +1,187 @@
|
|||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import tinify
|
||||||
|
from PIL import Image
|
||||||
|
from loguru import logger
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
import streamlit as st
|
||||||
|
from tempfile import NamedTemporaryFile
|
||||||
|
|
||||||
|
# Load environment variables
|
||||||
|
load_dotenv()
|
||||||
|
|
||||||
|
# Set Tinyfy API key from environment variable
|
||||||
|
TINIFY_API_KEY = os.getenv('TINIFY_API_KEY')
|
||||||
|
if TINIFY_API_KEY:
|
||||||
|
tinify.key = TINIFY_API_KEY
|
||||||
|
|
||||||
|
def setup_logger() -> None:
|
||||||
|
"""Configure the logger."""
|
||||||
|
logger.remove()
|
||||||
|
logger.add(
|
||||||
|
sys.stdout,
|
||||||
|
colorize=True,
|
||||||
|
format="<level>{level}</level>|<green>{file}:{line}:{function}</green>| {message}"
|
||||||
|
)
|
||||||
|
|
||||||
|
setup_logger()
|
||||||
|
|
||||||
|
def compress_image(image: Image.Image, quality: int = 45, resize: tuple = None, preserve_exif: bool = False) -> Image.Image:
|
||||||
|
"""
|
||||||
|
Compress and optionally resize an image.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
image (PIL.Image): Image object to compress.
|
||||||
|
quality (int): Quality of the output image (1-100).
|
||||||
|
resize (tuple): Tuple (width, height) to resize the image.
|
||||||
|
preserve_exif (bool): Preserve EXIF data if True.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
PIL.Image: The compressed and resized image object.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
if image.mode == 'RGBA':
|
||||||
|
logger.info("Converting RGBA image to RGB.")
|
||||||
|
image = image.convert('RGB')
|
||||||
|
|
||||||
|
exif = image.info.get('exif') if preserve_exif and 'exif' in image.info else None
|
||||||
|
|
||||||
|
if resize:
|
||||||
|
image = image.resize(resize, Image.LANCZOS)
|
||||||
|
logger.info(f"Resized image to {resize}")
|
||||||
|
|
||||||
|
with NamedTemporaryFile(delete=False, suffix=".jpg") as temp_file:
|
||||||
|
temp_path = temp_file.name
|
||||||
|
try:
|
||||||
|
image.save(temp_path, optimize=True, quality=quality, exif=exif)
|
||||||
|
except Exception as exif_error:
|
||||||
|
logger.warning(f"Error saving image with EXIF: {exif_error}. Saving without EXIF.")
|
||||||
|
image.save(temp_path, optimize=True, quality=quality)
|
||||||
|
|
||||||
|
logger.info("Image compression successful.")
|
||||||
|
return Image.open(temp_path)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error compressing image: {e}")
|
||||||
|
st.error("Failed to compress the image. Please try again.")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def convert_to_webp(image: Image.Image, image_path: str) -> str:
|
||||||
|
"""
|
||||||
|
Convert an image to WebP format.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
image (PIL.Image): Image object to convert.
|
||||||
|
image_path (str): Path to save the WebP image.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: Path to the WebP image.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
webp_path = os.path.splitext(image_path)[0] + '.webp'
|
||||||
|
image.save(webp_path, 'WEBP', quality=80, method=6)
|
||||||
|
return webp_path
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error converting image to WebP: {e}")
|
||||||
|
st.error("Failed to convert the image to WebP format. Please try again.")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def compress_image_tinyfy(image_path: str) -> None:
|
||||||
|
"""
|
||||||
|
Compress an image using Tinyfy API.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
image_path (str): Path to the image to be compressed.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
None
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
if not tinify.key:
|
||||||
|
logger.warning("Tinyfy API key is not set. Skipping Tinyfy compression.")
|
||||||
|
return
|
||||||
|
|
||||||
|
source = tinify.from_file(image_path)
|
||||||
|
source.to_file(image_path)
|
||||||
|
logger.info("Tinyfy compression successful.")
|
||||||
|
except tinify.errors.AccountError:
|
||||||
|
logger.error("Verify your Tinyfy API key and account limit.")
|
||||||
|
st.warning("Tinyfy compression failed. Check your API key and account limit.")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error during Tinyfy compression: {e}")
|
||||||
|
st.warning("Tinyfy compression failed. Ensure the API key is set.")
|
||||||
|
|
||||||
|
def optimize_image(image: Image.Image, image_path: str, quality: int, resize: tuple, preserve_exif: bool) -> str:
|
||||||
|
"""
|
||||||
|
Optimize the image by compressing and converting it to WebP, with optional Tinyfy compression.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
image (PIL.Image): The original image.
|
||||||
|
image_path (str): The path to the image file.
|
||||||
|
quality (int): Quality level for compression.
|
||||||
|
resize (tuple): Dimensions to resize the image.
|
||||||
|
preserve_exif (bool): Whether to preserve EXIF data.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: Path to the optimized WebP image, or None if failed.
|
||||||
|
"""
|
||||||
|
logger.info("Starting image optimization process...")
|
||||||
|
|
||||||
|
compressed_image = compress_image(image, quality, resize, preserve_exif)
|
||||||
|
if compressed_image is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
webp_path = convert_to_webp(compressed_image, image_path)
|
||||||
|
if webp_path is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if tinify.key:
|
||||||
|
compress_image_tinyfy(webp_path)
|
||||||
|
else:
|
||||||
|
logger.info("Tinyfy key not provided, skipping Tinyfy compression.")
|
||||||
|
|
||||||
|
return webp_path
|
||||||
|
|
||||||
|
def main_img_optimizer() -> None:
|
||||||
|
st.title("ALwrity Image Optimizer")
|
||||||
|
st.markdown("## Upload an image to optimize its size and format.")
|
||||||
|
|
||||||
|
input_tinify_key = st.text_input("Optional: Enter your Tinyfy API Key")
|
||||||
|
if input_tinify_key:
|
||||||
|
tinify.key = input_tinify_key
|
||||||
|
|
||||||
|
uploaded_file = st.file_uploader("Upload an image", type=['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'])
|
||||||
|
|
||||||
|
if uploaded_file:
|
||||||
|
image = Image.open(uploaded_file)
|
||||||
|
st.image(image, caption="Original Image", use_column_width=True)
|
||||||
|
|
||||||
|
quality = st.slider("Compression Quality", 1, 100, 45)
|
||||||
|
preserve_exif = st.checkbox("Preserve EXIF Data", value=False)
|
||||||
|
resize = st.checkbox("Resize Image")
|
||||||
|
|
||||||
|
if resize:
|
||||||
|
width = st.number_input("Width", value=image.width)
|
||||||
|
height = st.number_input("Height", value=image.height)
|
||||||
|
resize_dims = (width, height)
|
||||||
|
else:
|
||||||
|
resize_dims = None
|
||||||
|
|
||||||
|
if st.button("Optimize Image"):
|
||||||
|
with st.spinner("Optimizing..."):
|
||||||
|
if tinify.key:
|
||||||
|
st.info("Tinyfy compression will be applied.")
|
||||||
|
|
||||||
|
webp_path = optimize_image(image, uploaded_file.name, quality, resize_dims, preserve_exif)
|
||||||
|
|
||||||
|
if webp_path:
|
||||||
|
st.image(webp_path, caption="Optimized Image (WebP)", use_column_width=True)
|
||||||
|
st.success("Image optimization completed!")
|
||||||
|
|
||||||
|
with open(webp_path, "rb") as file:
|
||||||
|
st.download_button(
|
||||||
|
label="Download Optimized Image",
|
||||||
|
data=file,
|
||||||
|
file_name=os.path.basename(webp_path),
|
||||||
|
mime="image/webp"
|
||||||
|
)
|
||||||
340
ToBeMigrated/ai_seo_tools/seo_analyzer_api.py
Normal file
340
ToBeMigrated/ai_seo_tools/seo_analyzer_api.py
Normal file
@@ -0,0 +1,340 @@
|
|||||||
|
"""
|
||||||
|
FastAPI endpoint for the Comprehensive SEO Analyzer
|
||||||
|
Provides data for the React SEO Dashboard
|
||||||
|
"""
|
||||||
|
|
||||||
|
from fastapi import FastAPI, HTTPException
|
||||||
|
from pydantic import BaseModel, HttpUrl
|
||||||
|
from typing import List, Optional, Dict, Any
|
||||||
|
from datetime import datetime
|
||||||
|
import json
|
||||||
|
|
||||||
|
from .comprehensive_seo_analyzer import ComprehensiveSEOAnalyzer, SEOAnalysisResult
|
||||||
|
|
||||||
|
app = FastAPI(
|
||||||
|
title="Comprehensive SEO Analyzer API",
|
||||||
|
description="API for analyzing website SEO performance with actionable insights",
|
||||||
|
version="1.0.0"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Initialize the analyzer
|
||||||
|
seo_analyzer = ComprehensiveSEOAnalyzer()
|
||||||
|
|
||||||
|
class SEOAnalysisRequest(BaseModel):
|
||||||
|
url: HttpUrl
|
||||||
|
target_keywords: Optional[List[str]] = None
|
||||||
|
|
||||||
|
class SEOAnalysisResponse(BaseModel):
|
||||||
|
url: str
|
||||||
|
timestamp: datetime
|
||||||
|
overall_score: int
|
||||||
|
health_status: str
|
||||||
|
critical_issues: List[str]
|
||||||
|
warnings: List[str]
|
||||||
|
recommendations: List[str]
|
||||||
|
data: Dict[str, Any]
|
||||||
|
success: bool
|
||||||
|
message: str
|
||||||
|
|
||||||
|
@app.post("/analyze-seo", response_model=SEOAnalysisResponse)
|
||||||
|
async def analyze_seo(request: SEOAnalysisRequest):
|
||||||
|
"""
|
||||||
|
Analyze a URL for comprehensive SEO performance
|
||||||
|
|
||||||
|
Args:
|
||||||
|
request: SEOAnalysisRequest containing URL and optional target keywords
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
SEOAnalysisResponse with detailed analysis results
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Convert URL to string
|
||||||
|
url_str = str(request.url)
|
||||||
|
|
||||||
|
# Perform analysis
|
||||||
|
result = seo_analyzer.analyze_url(url_str, request.target_keywords)
|
||||||
|
|
||||||
|
# Convert to response format
|
||||||
|
response_data = {
|
||||||
|
'url': result.url,
|
||||||
|
'timestamp': result.timestamp,
|
||||||
|
'overall_score': result.overall_score,
|
||||||
|
'health_status': result.health_status,
|
||||||
|
'critical_issues': result.critical_issues,
|
||||||
|
'warnings': result.warnings,
|
||||||
|
'recommendations': result.recommendations,
|
||||||
|
'data': result.data,
|
||||||
|
'success': True,
|
||||||
|
'message': f"SEO analysis completed successfully for {result.url}"
|
||||||
|
}
|
||||||
|
|
||||||
|
return SEOAnalysisResponse(**response_data)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=500,
|
||||||
|
detail=f"Error analyzing SEO: {str(e)}"
|
||||||
|
)
|
||||||
|
|
||||||
|
@app.get("/health")
|
||||||
|
async def health_check():
|
||||||
|
"""Health check endpoint"""
|
||||||
|
return {
|
||||||
|
"status": "healthy",
|
||||||
|
"timestamp": datetime.now(),
|
||||||
|
"service": "Comprehensive SEO Analyzer API"
|
||||||
|
}
|
||||||
|
|
||||||
|
@app.get("/analysis-summary/{url:path}")
|
||||||
|
async def get_analysis_summary(url: str):
|
||||||
|
"""
|
||||||
|
Get a quick summary of SEO analysis for a URL
|
||||||
|
|
||||||
|
Args:
|
||||||
|
url: The URL to analyze
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Summary of SEO analysis
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Ensure URL has protocol
|
||||||
|
if not url.startswith(('http://', 'https://')):
|
||||||
|
url = f"https://{url}"
|
||||||
|
|
||||||
|
# Perform analysis
|
||||||
|
result = seo_analyzer.analyze_url(url)
|
||||||
|
|
||||||
|
# Create summary
|
||||||
|
summary = {
|
||||||
|
"url": result.url,
|
||||||
|
"overall_score": result.overall_score,
|
||||||
|
"health_status": result.health_status,
|
||||||
|
"critical_issues_count": len(result.critical_issues),
|
||||||
|
"warnings_count": len(result.warnings),
|
||||||
|
"recommendations_count": len(result.recommendations),
|
||||||
|
"top_issues": result.critical_issues[:3],
|
||||||
|
"top_recommendations": result.recommendations[:3],
|
||||||
|
"analysis_timestamp": result.timestamp.isoformat()
|
||||||
|
}
|
||||||
|
|
||||||
|
return summary
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=500,
|
||||||
|
detail=f"Error getting analysis summary: {str(e)}"
|
||||||
|
)
|
||||||
|
|
||||||
|
@app.get("/seo-metrics/{url:path}")
|
||||||
|
async def get_seo_metrics(url: str):
|
||||||
|
"""
|
||||||
|
Get detailed SEO metrics for dashboard display
|
||||||
|
|
||||||
|
Args:
|
||||||
|
url: The URL to analyze
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Detailed SEO metrics for React dashboard
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Ensure URL has protocol
|
||||||
|
if not url.startswith(('http://', 'https://')):
|
||||||
|
url = f"https://{url}"
|
||||||
|
|
||||||
|
# Perform analysis
|
||||||
|
result = seo_analyzer.analyze_url(url)
|
||||||
|
|
||||||
|
# Extract metrics for dashboard
|
||||||
|
metrics = {
|
||||||
|
"overall_score": result.overall_score,
|
||||||
|
"health_status": result.health_status,
|
||||||
|
"url_structure_score": result.data.get('url_structure', {}).get('score', 0),
|
||||||
|
"meta_data_score": result.data.get('meta_data', {}).get('score', 0),
|
||||||
|
"content_score": result.data.get('content_analysis', {}).get('score', 0),
|
||||||
|
"technical_score": result.data.get('technical_seo', {}).get('score', 0),
|
||||||
|
"performance_score": result.data.get('performance', {}).get('score', 0),
|
||||||
|
"accessibility_score": result.data.get('accessibility', {}).get('score', 0),
|
||||||
|
"user_experience_score": result.data.get('user_experience', {}).get('score', 0),
|
||||||
|
"security_score": result.data.get('security_headers', {}).get('score', 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
# Add detailed data for each category
|
||||||
|
dashboard_data = {
|
||||||
|
"metrics": metrics,
|
||||||
|
"critical_issues": result.critical_issues,
|
||||||
|
"warnings": result.warnings,
|
||||||
|
"recommendations": result.recommendations,
|
||||||
|
"detailed_analysis": {
|
||||||
|
"url_structure": result.data.get('url_structure', {}),
|
||||||
|
"meta_data": result.data.get('meta_data', {}),
|
||||||
|
"content_analysis": result.data.get('content_analysis', {}),
|
||||||
|
"technical_seo": result.data.get('technical_seo', {}),
|
||||||
|
"performance": result.data.get('performance', {}),
|
||||||
|
"accessibility": result.data.get('accessibility', {}),
|
||||||
|
"user_experience": result.data.get('user_experience', {}),
|
||||||
|
"security_headers": result.data.get('security_headers', {}),
|
||||||
|
"keyword_analysis": result.data.get('keyword_analysis', {})
|
||||||
|
},
|
||||||
|
"timestamp": result.timestamp.isoformat(),
|
||||||
|
"url": result.url
|
||||||
|
}
|
||||||
|
|
||||||
|
return dashboard_data
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=500,
|
||||||
|
detail=f"Error getting SEO metrics: {str(e)}"
|
||||||
|
)
|
||||||
|
|
||||||
|
@app.post("/batch-analyze")
|
||||||
|
async def batch_analyze(urls: List[str]):
|
||||||
|
"""
|
||||||
|
Analyze multiple URLs in batch
|
||||||
|
|
||||||
|
Args:
|
||||||
|
urls: List of URLs to analyze
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Batch analysis results
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
results = []
|
||||||
|
|
||||||
|
for url in urls:
|
||||||
|
try:
|
||||||
|
# Ensure URL has protocol
|
||||||
|
if not url.startswith(('http://', 'https://')):
|
||||||
|
url = f"https://{url}"
|
||||||
|
|
||||||
|
# Perform analysis
|
||||||
|
result = seo_analyzer.analyze_url(url)
|
||||||
|
|
||||||
|
# Add to results
|
||||||
|
results.append({
|
||||||
|
"url": result.url,
|
||||||
|
"overall_score": result.overall_score,
|
||||||
|
"health_status": result.health_status,
|
||||||
|
"critical_issues_count": len(result.critical_issues),
|
||||||
|
"warnings_count": len(result.warnings),
|
||||||
|
"success": True
|
||||||
|
})
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
# Add error result
|
||||||
|
results.append({
|
||||||
|
"url": url,
|
||||||
|
"overall_score": 0,
|
||||||
|
"health_status": "error",
|
||||||
|
"critical_issues_count": 0,
|
||||||
|
"warnings_count": 0,
|
||||||
|
"success": False,
|
||||||
|
"error": str(e)
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
"total_urls": len(urls),
|
||||||
|
"successful_analyses": len([r for r in results if r['success']]),
|
||||||
|
"failed_analyses": len([r for r in results if not r['success']]),
|
||||||
|
"results": results
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=500,
|
||||||
|
detail=f"Error in batch analysis: {str(e)}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Enhanced prompts for better results
|
||||||
|
ENHANCED_PROMPTS = {
|
||||||
|
"critical_issue": "🚨 CRITICAL: This issue is severely impacting your SEO performance and must be fixed immediately.",
|
||||||
|
"warning": "⚠️ WARNING: This could be improved to boost your search rankings.",
|
||||||
|
"recommendation": "💡 RECOMMENDATION: Implement this to improve your SEO score.",
|
||||||
|
"excellent": "🎉 EXCELLENT: Your SEO is performing very well in this area!",
|
||||||
|
"good": "✅ GOOD: Your SEO is performing well, with room for minor improvements.",
|
||||||
|
"needs_improvement": "🔧 NEEDS IMPROVEMENT: Several areas need attention to boost your SEO.",
|
||||||
|
"poor": "❌ POOR: Significant improvements needed across multiple areas."
|
||||||
|
}
|
||||||
|
|
||||||
|
def enhance_analysis_result(result: SEOAnalysisResult) -> SEOAnalysisResult:
|
||||||
|
"""
|
||||||
|
Enhance analysis results with better prompts and user-friendly language
|
||||||
|
"""
|
||||||
|
# Enhance critical issues
|
||||||
|
enhanced_critical_issues = []
|
||||||
|
for issue in result.critical_issues:
|
||||||
|
enhanced_issue = f"{ENHANCED_PROMPTS['critical_issue']} {issue}"
|
||||||
|
enhanced_critical_issues.append(enhanced_issue)
|
||||||
|
|
||||||
|
# Enhance warnings
|
||||||
|
enhanced_warnings = []
|
||||||
|
for warning in result.warnings:
|
||||||
|
enhanced_warning = f"{ENHANCED_PROMPTS['warning']} {warning}"
|
||||||
|
enhanced_warnings.append(enhanced_warning)
|
||||||
|
|
||||||
|
# Enhance recommendations
|
||||||
|
enhanced_recommendations = []
|
||||||
|
for rec in result.recommendations:
|
||||||
|
enhanced_rec = f"{ENHANCED_PROMPTS['recommendation']} {rec}"
|
||||||
|
enhanced_recommendations.append(enhanced_rec)
|
||||||
|
|
||||||
|
# Create enhanced result
|
||||||
|
enhanced_result = SEOAnalysisResult(
|
||||||
|
url=result.url,
|
||||||
|
timestamp=result.timestamp,
|
||||||
|
overall_score=result.overall_score,
|
||||||
|
health_status=result.health_status,
|
||||||
|
critical_issues=enhanced_critical_issues,
|
||||||
|
warnings=enhanced_warnings,
|
||||||
|
recommendations=enhanced_recommendations,
|
||||||
|
data=result.data
|
||||||
|
)
|
||||||
|
|
||||||
|
return enhanced_result
|
||||||
|
|
||||||
|
@app.post("/analyze-seo-enhanced", response_model=SEOAnalysisResponse)
|
||||||
|
async def analyze_seo_enhanced(request: SEOAnalysisRequest):
|
||||||
|
"""
|
||||||
|
Analyze a URL with enhanced, user-friendly prompts
|
||||||
|
|
||||||
|
Args:
|
||||||
|
request: SEOAnalysisRequest containing URL and optional target keywords
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
SEOAnalysisResponse with enhanced, user-friendly analysis results
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Convert URL to string
|
||||||
|
url_str = str(request.url)
|
||||||
|
|
||||||
|
# Perform analysis
|
||||||
|
result = seo_analyzer.analyze_url(url_str, request.target_keywords)
|
||||||
|
|
||||||
|
# Enhance results
|
||||||
|
enhanced_result = enhance_analysis_result(result)
|
||||||
|
|
||||||
|
# Convert to response format
|
||||||
|
response_data = {
|
||||||
|
'url': enhanced_result.url,
|
||||||
|
'timestamp': enhanced_result.timestamp,
|
||||||
|
'overall_score': enhanced_result.overall_score,
|
||||||
|
'health_status': enhanced_result.health_status,
|
||||||
|
'critical_issues': enhanced_result.critical_issues,
|
||||||
|
'warnings': enhanced_result.warnings,
|
||||||
|
'recommendations': enhanced_result.recommendations,
|
||||||
|
'data': enhanced_result.data,
|
||||||
|
'success': True,
|
||||||
|
'message': f"Enhanced SEO analysis completed successfully for {enhanced_result.url}"
|
||||||
|
}
|
||||||
|
|
||||||
|
return SEOAnalysisResponse(**response_data)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=500,
|
||||||
|
detail=f"Error analyzing SEO: {str(e)}"
|
||||||
|
)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import uvicorn
|
||||||
|
uvicorn.run(app, host="0.0.0.0", port=8000)
|
||||||
130
ToBeMigrated/ai_seo_tools/seo_structured_data.py
Normal file
130
ToBeMigrated/ai_seo_tools/seo_structured_data.py
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
import streamlit as st
|
||||||
|
import json
|
||||||
|
from datetime import date
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
|
from ..ai_web_researcher.firecrawl_web_crawler import scrape_url
|
||||||
|
from ..gpt_providers.text_generation.main_text_generation import llm_text_gen
|
||||||
|
|
||||||
|
# Load environment variables
|
||||||
|
load_dotenv()
|
||||||
|
|
||||||
|
# Define a dictionary for schema types
|
||||||
|
schema_types = {
|
||||||
|
"Article": {
|
||||||
|
"fields": ["Headline", "Author", "Date Published", "Keywords"],
|
||||||
|
"schema_type": "Article",
|
||||||
|
},
|
||||||
|
"Product": {
|
||||||
|
"fields": ["Name", "Description", "Price", "Brand", "Image URL"],
|
||||||
|
"schema_type": "Product",
|
||||||
|
},
|
||||||
|
"Recipe": {
|
||||||
|
"fields": ["Name", "Ingredients", "Cooking Time", "Serving Size", "Image URL"],
|
||||||
|
"schema_type": "Recipe",
|
||||||
|
},
|
||||||
|
"Event": {
|
||||||
|
"fields": ["Name", "Start Date", "End Date", "Location", "Description"],
|
||||||
|
"schema_type": "Event",
|
||||||
|
},
|
||||||
|
"LocalBusiness": {
|
||||||
|
"fields": ["Name", "Address", "Phone Number", "Opening Hours", "Image URL"],
|
||||||
|
"schema_type": "LocalBusiness",
|
||||||
|
},
|
||||||
|
# ... (add more schema types as needed)
|
||||||
|
}
|
||||||
|
|
||||||
|
def generate_json_data(content_type, details, url):
|
||||||
|
"""Generates structured data (JSON-LD) based on user input."""
|
||||||
|
try:
|
||||||
|
scraped_text = scrape_url(url)
|
||||||
|
except Exception as err:
|
||||||
|
st.error(f"Failed to scrape web page from URL: {url} - Error: {err}")
|
||||||
|
return
|
||||||
|
|
||||||
|
schema = schema_types.get(content_type)
|
||||||
|
if not schema:
|
||||||
|
st.error(f"Invalid content type: {content_type}")
|
||||||
|
return
|
||||||
|
|
||||||
|
data = {
|
||||||
|
"@context": "https://schema.org",
|
||||||
|
"@type": schema["schema_type"],
|
||||||
|
}
|
||||||
|
for field in schema["fields"]:
|
||||||
|
value = details.get(field)
|
||||||
|
if isinstance(value, date):
|
||||||
|
value = value.isoformat()
|
||||||
|
data[field] = value if value else "N/A" # Use placeholder values if input is missing
|
||||||
|
|
||||||
|
if url:
|
||||||
|
data['url'] = url
|
||||||
|
|
||||||
|
llm_structured_data = get_llm_structured_data(content_type, data, scraped_text)
|
||||||
|
return llm_structured_data
|
||||||
|
|
||||||
|
def get_llm_structured_data(content_type, data, scraped_text):
|
||||||
|
"""Function to get structured data from LLM."""
|
||||||
|
prompt = f"""Given the following information:
|
||||||
|
|
||||||
|
HTML Content: <<<HTML>>> {scraped_text} <<<END_HTML>>>
|
||||||
|
Content Type: <<<CONTENT_TYPE>>> {content_type} <<<END_CONTENT_TYPE>>>
|
||||||
|
Additional Relevant Data: <<<ADDITIONAL_DATA>>> {data} <<<END_ADDITIONAL_DATA>>>
|
||||||
|
|
||||||
|
Create a detailed structured data (JSON-LD) script for SEO purposes.
|
||||||
|
The structured data should help search engines understand the content and features of the webpage, enhancing its visibility and potential for rich snippets in search results.
|
||||||
|
|
||||||
|
Detailed Steps:
|
||||||
|
Parse the HTML content to extract relevant information like the title, main heading, and body content.
|
||||||
|
Use the contentType to determine the structured data type (e.g., Article, Product, Recipe).
|
||||||
|
Integrate the additional relevant data (e.g., author, datePublished, keywords) into the structured data.
|
||||||
|
Ensure all URLs, images, and other attributes are correctly formatted and included.
|
||||||
|
Validate the generated JSON-LD to ensure it meets schema.org standards and is free of errors.
|
||||||
|
|
||||||
|
Expected Output:
|
||||||
|
Generate a JSON-LD structured data snippet based on the provided inputs."""
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = llm_text_gen(prompt)
|
||||||
|
return response
|
||||||
|
except Exception as err:
|
||||||
|
st.error(f"Failed to get response from LLM: {err}")
|
||||||
|
return
|
||||||
|
|
||||||
|
def ai_structured_data():
|
||||||
|
st.title("📝 Generate Structured Data for SEO 🚀")
|
||||||
|
st.markdown("**Make your content more discoverable with rich snippets.**")
|
||||||
|
|
||||||
|
content_type = st.selectbox("**Select Content Type**", list(schema_types.keys()))
|
||||||
|
|
||||||
|
details = {}
|
||||||
|
schema_fields = schema_types[content_type]["fields"]
|
||||||
|
num_fields = len(schema_fields)
|
||||||
|
|
||||||
|
url = st.text_input("**URL :**", placeholder="Enter the URL of your webpage")
|
||||||
|
for i in range(0, num_fields, 2):
|
||||||
|
cols = st.columns(2)
|
||||||
|
for j in range(2):
|
||||||
|
if i + j < num_fields:
|
||||||
|
field = schema_fields[i + j]
|
||||||
|
if "Date" in field:
|
||||||
|
details[field] = cols[j].date_input(field)
|
||||||
|
else:
|
||||||
|
details[field] = cols[j].text_input(field, placeholder=f"Enter {field.lower()}")
|
||||||
|
|
||||||
|
if st.button("Generate Structured Data"):
|
||||||
|
if not url:
|
||||||
|
st.error("URL is required to generate structured data.")
|
||||||
|
return
|
||||||
|
|
||||||
|
structured_data = generate_json_data(content_type, details, url)
|
||||||
|
if structured_data:
|
||||||
|
st.subheader("Generated Structured Data (JSON-LD):")
|
||||||
|
st.markdown(structured_data)
|
||||||
|
|
||||||
|
st.download_button(
|
||||||
|
label="Download JSON-LD",
|
||||||
|
data=structured_data,
|
||||||
|
file_name=f"{content_type}_structured_data.json",
|
||||||
|
mime="application/json",
|
||||||
|
)
|
||||||
340
ToBeMigrated/ai_seo_tools/sitemap_analysis.py
Normal file
340
ToBeMigrated/ai_seo_tools/sitemap_analysis.py
Normal file
@@ -0,0 +1,340 @@
|
|||||||
|
import streamlit as st
|
||||||
|
import advertools as adv
|
||||||
|
import pandas as pd
|
||||||
|
import plotly.graph_objects as go
|
||||||
|
from urllib.error import URLError
|
||||||
|
import xml.etree.ElementTree as ET
|
||||||
|
import requests
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""
|
||||||
|
Main function to run the Sitemap Analyzer Streamlit app.
|
||||||
|
"""
|
||||||
|
st.title("📊 Sitemap Analyzer")
|
||||||
|
st.write("""
|
||||||
|
This tool analyzes a website's sitemap to understand its content structure and publishing trends.
|
||||||
|
Enter a sitemap URL to start your analysis.
|
||||||
|
""")
|
||||||
|
|
||||||
|
sitemap_url = st.text_input(
|
||||||
|
"Please enter the sitemap URL:",
|
||||||
|
"https://www.example.com/sitemap.xml"
|
||||||
|
)
|
||||||
|
|
||||||
|
if st.button("Analyze Sitemap"):
|
||||||
|
try:
|
||||||
|
sitemap_df = fetch_all_sitemaps(sitemap_url)
|
||||||
|
if sitemap_df is not None and not sitemap_df.empty:
|
||||||
|
sitemap_df = process_lastmod_column(sitemap_df)
|
||||||
|
ppmonth = analyze_content_trends(sitemap_df)
|
||||||
|
sitemap_df = categorize_and_shorten_sitemaps(sitemap_df)
|
||||||
|
|
||||||
|
display_key_metrics(sitemap_df, ppmonth)
|
||||||
|
plot_sitemap_content_distribution(sitemap_df)
|
||||||
|
plot_content_trends(ppmonth)
|
||||||
|
plot_content_type_breakdown(sitemap_df)
|
||||||
|
plot_publishing_frequency(sitemap_df)
|
||||||
|
|
||||||
|
st.success("🎉 Analysis complete!")
|
||||||
|
else:
|
||||||
|
st.error("No valid URLs found in the sitemap.")
|
||||||
|
except URLError as e:
|
||||||
|
st.error(f"Error fetching the sitemap: {e}")
|
||||||
|
except Exception as e:
|
||||||
|
st.error(f"An unexpected error occurred: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
def fetch_all_sitemaps(sitemap_url):
|
||||||
|
"""
|
||||||
|
Fetches all sitemaps from the provided sitemap URL and concatenates their URLs into a DataFrame.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
sitemap_url (str): The URL of the sitemap.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
DataFrame: A DataFrame containing all URLs from the sitemaps.
|
||||||
|
"""
|
||||||
|
st.write(f"🚀 Fetching and analyzing the sitemap: {sitemap_url}...")
|
||||||
|
|
||||||
|
try:
|
||||||
|
sitemap_df = fetch_sitemap(sitemap_url)
|
||||||
|
|
||||||
|
if sitemap_df is not None:
|
||||||
|
all_sitemaps = sitemap_df.loc[
|
||||||
|
sitemap_df['loc'].str.contains('sitemap'),
|
||||||
|
'loc'
|
||||||
|
].tolist()
|
||||||
|
|
||||||
|
if all_sitemaps:
|
||||||
|
st.write(
|
||||||
|
f"🔄 Found {len(all_sitemaps)} additional sitemaps. Fetching data from them..."
|
||||||
|
)
|
||||||
|
all_urls_df = pd.DataFrame()
|
||||||
|
|
||||||
|
for sitemap in all_sitemaps:
|
||||||
|
try:
|
||||||
|
st.write(f"Fetching URLs from {sitemap}...")
|
||||||
|
temp_df = fetch_sitemap(sitemap)
|
||||||
|
if temp_df is not None:
|
||||||
|
all_urls_df = pd.concat(
|
||||||
|
[all_urls_df, temp_df], ignore_index=True
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
st.error(f"Error fetching {sitemap}: {e}")
|
||||||
|
|
||||||
|
st.write(
|
||||||
|
f"✅ Successfully fetched {len(all_urls_df)} URLs from all sitemaps."
|
||||||
|
)
|
||||||
|
return all_urls_df
|
||||||
|
|
||||||
|
else:
|
||||||
|
st.write(f"✅ Successfully fetched {len(sitemap_df)} URLs from the main sitemap.")
|
||||||
|
return sitemap_df
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
st.error(f"⚠️ Error fetching the sitemap: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def fetch_sitemap(url):
|
||||||
|
"""
|
||||||
|
Fetches and parses the sitemap from the provided URL.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
url (str): The URL of the sitemap.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
DataFrame: A DataFrame containing the URLs from the sitemap.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
response = requests.get(url)
|
||||||
|
response.raise_for_status()
|
||||||
|
|
||||||
|
ET.fromstring(response.content)
|
||||||
|
|
||||||
|
sitemap_df = adv.sitemap_to_df(url)
|
||||||
|
return sitemap_df
|
||||||
|
|
||||||
|
except requests.RequestException as e:
|
||||||
|
st.error(f"⚠️ Request error: {e}")
|
||||||
|
return None
|
||||||
|
except ET.ParseError as e:
|
||||||
|
st.error(f"⚠️ XML parsing error: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def process_lastmod_column(sitemap_df):
|
||||||
|
"""
|
||||||
|
Processes the 'lastmod' column in the sitemap DataFrame by converting it to DateTime format and setting it as the index.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
sitemap_df (DataFrame): The sitemap DataFrame.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
DataFrame: The processed sitemap DataFrame with 'lastmod' as the index.
|
||||||
|
"""
|
||||||
|
st.write("📅 Converting 'lastmod' column to DateTime format and setting it as the index...")
|
||||||
|
|
||||||
|
try:
|
||||||
|
sitemap_df = sitemap_df.dropna(subset=['lastmod'])
|
||||||
|
sitemap_df['lastmod'] = pd.to_datetime(sitemap_df['lastmod'])
|
||||||
|
sitemap_df.set_index('lastmod', inplace=True)
|
||||||
|
|
||||||
|
st.write("✅ 'lastmod' column successfully converted to DateTime format and set as the index.")
|
||||||
|
return sitemap_df
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
st.error(f"⚠️ Error processing the 'lastmod' column: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def categorize_and_shorten_sitemaps(sitemap_df):
|
||||||
|
"""
|
||||||
|
Categorizes and shortens the sitemap names in the sitemap DataFrame.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
sitemap_df (DataFrame): The sitemap DataFrame.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
DataFrame: The sitemap DataFrame with categorized and shortened sitemap names.
|
||||||
|
"""
|
||||||
|
st.write("🔍 Categorizing and shortening sitemap names...")
|
||||||
|
|
||||||
|
try:
|
||||||
|
sitemap_df['sitemap_name'] = sitemap_df['sitemap'].str.split('/').str[4]
|
||||||
|
sitemap_df['sitemap_name'] = sitemap_df['sitemap_name'].replace({
|
||||||
|
'sitemap-site-kasko-fiyatlari.xml': 'Kasko',
|
||||||
|
'sitemap-site-bireysel.xml': 'Personal',
|
||||||
|
'sitemap-site-kurumsal.xml': 'Cooperate',
|
||||||
|
'sitemap-site-arac-sigortasi.xml': 'Car',
|
||||||
|
'sitemap-site.xml': 'Others'
|
||||||
|
})
|
||||||
|
|
||||||
|
st.write("✅ Sitemap names categorized and shortened.")
|
||||||
|
return sitemap_df
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
st.error(f"⚠️ Error categorizing sitemap names: {e}")
|
||||||
|
return sitemap_df
|
||||||
|
|
||||||
|
|
||||||
|
def analyze_content_trends(sitemap_df):
|
||||||
|
"""
|
||||||
|
Analyzes content publishing trends in the sitemap DataFrame.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
sitemap_df (DataFrame): The sitemap DataFrame.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Series: A Series representing the number of contents published each month.
|
||||||
|
"""
|
||||||
|
st.write("📅 Analyzing content publishing trends...")
|
||||||
|
|
||||||
|
try:
|
||||||
|
ppmonth = sitemap_df.resample('M').size()
|
||||||
|
sitemap_df['monthly_count'] = sitemap_df.index.to_period('M').value_counts().sort_index()
|
||||||
|
|
||||||
|
st.write("✅ Content trends analysis completed.")
|
||||||
|
return ppmonth
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
st.error(f"⚠️ Error during content trends analysis: {e}")
|
||||||
|
return pd.Series()
|
||||||
|
|
||||||
|
|
||||||
|
def display_key_metrics(sitemap_df, ppmonth):
|
||||||
|
"""
|
||||||
|
Displays key metrics of the sitemap analysis.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
sitemap_df (DataFrame): The sitemap DataFrame.
|
||||||
|
ppmonth (Series): The Series representing the number of contents published each month.
|
||||||
|
"""
|
||||||
|
st.write("### Key Metrics")
|
||||||
|
|
||||||
|
total_urls = len(sitemap_df)
|
||||||
|
total_articles = ppmonth.sum()
|
||||||
|
average_frequency = ppmonth.mean()
|
||||||
|
|
||||||
|
st.write(f"**Total URLs Found:** {total_urls:,}")
|
||||||
|
st.write(f"**Total Articles Published:** {total_articles:,}")
|
||||||
|
st.write(f"**Average Monthly Publishing Frequency:** {average_frequency:.2f} articles/month")
|
||||||
|
|
||||||
|
|
||||||
|
def plot_sitemap_content_distribution(sitemap_df):
|
||||||
|
"""
|
||||||
|
Plots the content distribution by sitemap categories.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
sitemap_df (DataFrame): The sitemap DataFrame.
|
||||||
|
"""
|
||||||
|
st.write("📊 Visualizing content amount by sitemap categories...")
|
||||||
|
|
||||||
|
try:
|
||||||
|
if 'sitemap_name' in sitemap_df.columns:
|
||||||
|
stmc = sitemap_df.groupby('sitemap_name').size()
|
||||||
|
fig = go.Figure()
|
||||||
|
fig.add_bar(x=stmc.index, y=stmc.values, name='Sitemap Categories')
|
||||||
|
fig.update_layout(
|
||||||
|
title='Content Amount by Sitemap Categories',
|
||||||
|
xaxis_title='Sitemap Categories',
|
||||||
|
yaxis_title='Number of Articles',
|
||||||
|
paper_bgcolor='#E5ECF6'
|
||||||
|
)
|
||||||
|
st.plotly_chart(fig)
|
||||||
|
else:
|
||||||
|
st.warning("⚠️ The 'sitemap_name' column is missing in the data.")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
st.error(f"⚠️ Error during sitemap content distribution plotting: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
def plot_content_trends(ppmonth):
|
||||||
|
"""
|
||||||
|
Plots the content publishing trends over time.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
ppmonth (Series): The Series representing the number of contents published each month.
|
||||||
|
"""
|
||||||
|
st.write("📈 Plotting content publishing trends over time...")
|
||||||
|
|
||||||
|
try:
|
||||||
|
fig = go.Figure()
|
||||||
|
fig.add_scatter(x=ppmonth.index, y=ppmonth.values, mode='lines+markers', name='Publishing Trends')
|
||||||
|
fig.update_layout(
|
||||||
|
title='Content Publishing Trends Over Time',
|
||||||
|
xaxis_title='Month',
|
||||||
|
yaxis_title='Number of Articles',
|
||||||
|
paper_bgcolor='#E5ECF6'
|
||||||
|
)
|
||||||
|
st.plotly_chart(fig)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
st.error(f"⚠️ Error during content trends plotting: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
def plot_content_type_breakdown(sitemap_df):
|
||||||
|
"""
|
||||||
|
Plots the content type breakdown.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
sitemap_df (DataFrame): The sitemap DataFrame.
|
||||||
|
"""
|
||||||
|
st.write("🔍 Plotting content type breakdown...")
|
||||||
|
|
||||||
|
try:
|
||||||
|
if 'sitemap_name' in sitemap_df.columns and not sitemap_df['sitemap_name'].empty:
|
||||||
|
content_type_counts = sitemap_df['sitemap_name'].value_counts()
|
||||||
|
st.write("Content Type Counts:", content_type_counts)
|
||||||
|
|
||||||
|
if not content_type_counts.empty:
|
||||||
|
fig = go.Figure(data=[go.Pie(labels=content_type_counts.index, values=content_type_counts.values)])
|
||||||
|
fig.update_layout(
|
||||||
|
title='Content Type Breakdown',
|
||||||
|
paper_bgcolor='#E5ECF6'
|
||||||
|
)
|
||||||
|
st.plotly_chart(fig)
|
||||||
|
else:
|
||||||
|
st.warning("⚠️ No content types to display.")
|
||||||
|
else:
|
||||||
|
st.warning("⚠️ The 'sitemap_name' column is missing or empty.")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
st.error(f"⚠️ Error during content type breakdown plotting: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
def plot_publishing_frequency(sitemap_df):
|
||||||
|
"""
|
||||||
|
Plots the publishing frequency by month.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
sitemap_df (DataFrame): The sitemap DataFrame.
|
||||||
|
"""
|
||||||
|
st.write("📆 Plotting publishing frequency by month...")
|
||||||
|
|
||||||
|
try:
|
||||||
|
if not sitemap_df.empty:
|
||||||
|
frequency_by_month = sitemap_df.index.to_period('M').value_counts().sort_index()
|
||||||
|
frequency_by_month.index = frequency_by_month.index.astype(str)
|
||||||
|
|
||||||
|
fig = go.Figure()
|
||||||
|
fig.add_bar(x=frequency_by_month.index, y=frequency_by_month.values, name='Publishing Frequency')
|
||||||
|
fig.update_layout(
|
||||||
|
title='Publishing Frequency by Month',
|
||||||
|
xaxis_title='Month',
|
||||||
|
yaxis_title='Number of Articles',
|
||||||
|
paper_bgcolor='#E5ECF6'
|
||||||
|
)
|
||||||
|
st.plotly_chart(fig)
|
||||||
|
else:
|
||||||
|
st.warning("⚠️ No data available to plot publishing frequency.")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
st.error(f"⚠️ Error during publishing frequency plotting: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
22
ToBeMigrated/ai_seo_tools/technical_seo_crawler/__init__.py
Normal file
22
ToBeMigrated/ai_seo_tools/technical_seo_crawler/__init__.py
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
"""
|
||||||
|
Technical SEO Crawler Package.
|
||||||
|
|
||||||
|
This package provides comprehensive technical SEO analysis capabilities
|
||||||
|
with advertools integration and AI-powered recommendations.
|
||||||
|
|
||||||
|
Components:
|
||||||
|
- TechnicalSEOCrawler: Core crawler with technical analysis
|
||||||
|
- TechnicalSEOCrawlerUI: Streamlit interface for the crawler
|
||||||
|
"""
|
||||||
|
|
||||||
|
from .crawler import TechnicalSEOCrawler
|
||||||
|
from .ui import TechnicalSEOCrawlerUI, render_technical_seo_crawler
|
||||||
|
|
||||||
|
__version__ = "1.0.0"
|
||||||
|
__author__ = "ALwrity"
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
'TechnicalSEOCrawler',
|
||||||
|
'TechnicalSEOCrawlerUI',
|
||||||
|
'render_technical_seo_crawler'
|
||||||
|
]
|
||||||
709
ToBeMigrated/ai_seo_tools/technical_seo_crawler/crawler.py
Normal file
709
ToBeMigrated/ai_seo_tools/technical_seo_crawler/crawler.py
Normal file
@@ -0,0 +1,709 @@
|
|||||||
|
"""
|
||||||
|
Comprehensive Technical SEO Crawler using Advertools Integration.
|
||||||
|
|
||||||
|
This module provides advanced site-wide technical SEO analysis using:
|
||||||
|
- adv.crawl: Complete website crawling and analysis
|
||||||
|
- adv.crawl_headers: HTTP headers and server analysis
|
||||||
|
- adv.crawl_images: Image optimization analysis
|
||||||
|
- adv.url_to_df: URL structure optimization
|
||||||
|
- AI-powered technical recommendations
|
||||||
|
"""
|
||||||
|
|
||||||
|
import streamlit as st
|
||||||
|
import pandas as pd
|
||||||
|
import advertools as adv
|
||||||
|
from typing import Dict, Any, List, Optional, Tuple
|
||||||
|
from urllib.parse import urlparse, urljoin
|
||||||
|
import tempfile
|
||||||
|
import os
|
||||||
|
from datetime import datetime
|
||||||
|
import json
|
||||||
|
from collections import Counter, defaultdict
|
||||||
|
from loguru import logger
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
# Import existing modules
|
||||||
|
from lib.gpt_providers.text_generation.main_text_generation import llm_text_gen
|
||||||
|
from lib.utils.website_analyzer.analyzer import WebsiteAnalyzer
|
||||||
|
|
||||||
|
class TechnicalSEOCrawler:
|
||||||
|
"""Comprehensive technical SEO crawler with advertools integration."""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
"""Initialize the technical SEO crawler."""
|
||||||
|
self.temp_dir = tempfile.mkdtemp()
|
||||||
|
logger.info("TechnicalSEOCrawler initialized")
|
||||||
|
|
||||||
|
def analyze_website_technical_seo(self, website_url: str, crawl_depth: int = 3,
|
||||||
|
max_pages: int = 500) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Perform comprehensive technical SEO analysis.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
website_url: Website URL to analyze
|
||||||
|
crawl_depth: How deep to crawl (1-5)
|
||||||
|
max_pages: Maximum pages to crawl (50-1000)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Comprehensive technical SEO analysis results
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
st.info("🚀 Starting Comprehensive Technical SEO Crawl...")
|
||||||
|
|
||||||
|
# Initialize results structure
|
||||||
|
results = {
|
||||||
|
'analysis_timestamp': datetime.utcnow().isoformat(),
|
||||||
|
'website_url': website_url,
|
||||||
|
'crawl_settings': {
|
||||||
|
'depth': crawl_depth,
|
||||||
|
'max_pages': max_pages
|
||||||
|
},
|
||||||
|
'crawl_overview': {},
|
||||||
|
'technical_issues': {},
|
||||||
|
'performance_analysis': {},
|
||||||
|
'content_analysis': {},
|
||||||
|
'url_structure': {},
|
||||||
|
'image_optimization': {},
|
||||||
|
'security_headers': {},
|
||||||
|
'mobile_seo': {},
|
||||||
|
'structured_data': {},
|
||||||
|
'ai_recommendations': {}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Phase 1: Core Website Crawl
|
||||||
|
with st.expander("🕷️ Website Crawling Progress", expanded=True):
|
||||||
|
crawl_data = self._perform_comprehensive_crawl(website_url, crawl_depth, max_pages)
|
||||||
|
results['crawl_overview'] = crawl_data
|
||||||
|
st.success(f"✅ Crawled {crawl_data.get('pages_crawled', 0)} pages")
|
||||||
|
|
||||||
|
# Phase 2: Technical Issues Detection
|
||||||
|
with st.expander("🔍 Technical Issues Analysis", expanded=True):
|
||||||
|
technical_issues = self._analyze_technical_issues(crawl_data)
|
||||||
|
results['technical_issues'] = technical_issues
|
||||||
|
st.success("✅ Identified technical SEO issues")
|
||||||
|
|
||||||
|
# Phase 3: Performance Analysis
|
||||||
|
with st.expander("⚡ Performance Analysis", expanded=True):
|
||||||
|
performance = self._analyze_performance_metrics(crawl_data)
|
||||||
|
results['performance_analysis'] = performance
|
||||||
|
st.success("✅ Analyzed website performance metrics")
|
||||||
|
|
||||||
|
# Phase 4: Content & Structure Analysis
|
||||||
|
with st.expander("📊 Content Structure Analysis", expanded=True):
|
||||||
|
content_analysis = self._analyze_content_structure(crawl_data)
|
||||||
|
results['content_analysis'] = content_analysis
|
||||||
|
st.success("✅ Analyzed content structure and optimization")
|
||||||
|
|
||||||
|
# Phase 5: URL Structure Optimization
|
||||||
|
with st.expander("🔗 URL Structure Analysis", expanded=True):
|
||||||
|
url_analysis = self._analyze_url_structure(crawl_data)
|
||||||
|
results['url_structure'] = url_analysis
|
||||||
|
st.success("✅ Analyzed URL structure and patterns")
|
||||||
|
|
||||||
|
# Phase 6: Image SEO Analysis
|
||||||
|
with st.expander("🖼️ Image SEO Analysis", expanded=True):
|
||||||
|
image_analysis = self._analyze_image_seo(website_url)
|
||||||
|
results['image_optimization'] = image_analysis
|
||||||
|
st.success("✅ Analyzed image optimization")
|
||||||
|
|
||||||
|
# Phase 7: Security & Headers Analysis
|
||||||
|
with st.expander("🛡️ Security Headers Analysis", expanded=True):
|
||||||
|
security_analysis = self._analyze_security_headers(website_url)
|
||||||
|
results['security_headers'] = security_analysis
|
||||||
|
st.success("✅ Analyzed security headers")
|
||||||
|
|
||||||
|
# Phase 8: Mobile SEO Analysis
|
||||||
|
with st.expander("📱 Mobile SEO Analysis", expanded=True):
|
||||||
|
mobile_analysis = self._analyze_mobile_seo(crawl_data)
|
||||||
|
results['mobile_seo'] = mobile_analysis
|
||||||
|
st.success("✅ Analyzed mobile SEO factors")
|
||||||
|
|
||||||
|
# Phase 9: AI-Powered Recommendations
|
||||||
|
with st.expander("🤖 AI Technical Recommendations", expanded=True):
|
||||||
|
ai_recommendations = self._generate_technical_recommendations(results)
|
||||||
|
results['ai_recommendations'] = ai_recommendations
|
||||||
|
st.success("✅ Generated AI-powered technical recommendations")
|
||||||
|
|
||||||
|
return results
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
error_msg = f"Error in technical SEO analysis: {str(e)}"
|
||||||
|
logger.error(error_msg, exc_info=True)
|
||||||
|
st.error(error_msg)
|
||||||
|
return {'error': error_msg}
|
||||||
|
|
||||||
|
def _perform_comprehensive_crawl(self, website_url: str, depth: int, max_pages: int) -> Dict[str, Any]:
|
||||||
|
"""Perform comprehensive website crawl using adv.crawl."""
|
||||||
|
try:
|
||||||
|
st.info("🕷️ Crawling website for comprehensive analysis...")
|
||||||
|
|
||||||
|
# Create crawl output file
|
||||||
|
crawl_file = os.path.join(self.temp_dir, "technical_crawl.jl")
|
||||||
|
|
||||||
|
# Configure crawl settings for technical SEO
|
||||||
|
custom_settings = {
|
||||||
|
'DEPTH_LIMIT': depth,
|
||||||
|
'CLOSESPIDER_PAGECOUNT': max_pages,
|
||||||
|
'DOWNLOAD_DELAY': 0.5, # Be respectful
|
||||||
|
'CONCURRENT_REQUESTS': 8,
|
||||||
|
'ROBOTSTXT_OBEY': True,
|
||||||
|
'USER_AGENT': 'ALwrity-TechnicalSEO-Crawler/1.0',
|
||||||
|
'COOKIES_ENABLED': False,
|
||||||
|
'TELNETCONSOLE_ENABLED': False,
|
||||||
|
'LOG_LEVEL': 'WARNING'
|
||||||
|
}
|
||||||
|
|
||||||
|
# Start crawl
|
||||||
|
adv.crawl(
|
||||||
|
url_list=[website_url],
|
||||||
|
output_file=crawl_file,
|
||||||
|
follow_links=True,
|
||||||
|
custom_settings=custom_settings
|
||||||
|
)
|
||||||
|
|
||||||
|
# Read and process crawl results
|
||||||
|
if os.path.exists(crawl_file):
|
||||||
|
crawl_df = pd.read_json(crawl_file, lines=True)
|
||||||
|
|
||||||
|
# Basic crawl statistics
|
||||||
|
crawl_overview = {
|
||||||
|
'pages_crawled': len(crawl_df),
|
||||||
|
'status_codes': crawl_df['status'].value_counts().to_dict(),
|
||||||
|
'crawl_file_path': crawl_file,
|
||||||
|
'crawl_dataframe': crawl_df,
|
||||||
|
'domains_found': crawl_df['url'].apply(lambda x: urlparse(x).netloc).nunique(),
|
||||||
|
'avg_response_time': crawl_df.get('download_latency', pd.Series()).mean(),
|
||||||
|
'total_content_size': crawl_df.get('size', pd.Series()).sum()
|
||||||
|
}
|
||||||
|
|
||||||
|
return crawl_overview
|
||||||
|
else:
|
||||||
|
st.error("Crawl file not created")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
st.error(f"Error in website crawl: {str(e)}")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def _analyze_technical_issues(self, crawl_data: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
|
"""Analyze technical SEO issues from crawl data."""
|
||||||
|
try:
|
||||||
|
st.info("🔍 Detecting technical SEO issues...")
|
||||||
|
|
||||||
|
if 'crawl_dataframe' not in crawl_data:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
df = crawl_data['crawl_dataframe']
|
||||||
|
|
||||||
|
technical_issues = {
|
||||||
|
'http_errors': {},
|
||||||
|
'redirect_issues': {},
|
||||||
|
'duplicate_content': {},
|
||||||
|
'missing_elements': {},
|
||||||
|
'page_speed_issues': {},
|
||||||
|
'crawlability_issues': {}
|
||||||
|
}
|
||||||
|
|
||||||
|
# HTTP Status Code Issues
|
||||||
|
error_codes = df[df['status'] >= 400]['status'].value_counts().to_dict()
|
||||||
|
technical_issues['http_errors'] = {
|
||||||
|
'total_errors': len(df[df['status'] >= 400]),
|
||||||
|
'error_breakdown': error_codes,
|
||||||
|
'error_pages': df[df['status'] >= 400][['url', 'status']].to_dict('records')[:50]
|
||||||
|
}
|
||||||
|
|
||||||
|
# Redirect Analysis
|
||||||
|
redirects = df[df['status'].isin([301, 302, 303, 307, 308])]
|
||||||
|
technical_issues['redirect_issues'] = {
|
||||||
|
'total_redirects': len(redirects),
|
||||||
|
'redirect_chains': self._find_redirect_chains(redirects),
|
||||||
|
'redirect_types': redirects['status'].value_counts().to_dict()
|
||||||
|
}
|
||||||
|
|
||||||
|
# Duplicate Content Detection
|
||||||
|
if 'title' in df.columns:
|
||||||
|
duplicate_titles = df['title'].value_counts()
|
||||||
|
duplicate_titles = duplicate_titles[duplicate_titles > 1]
|
||||||
|
|
||||||
|
technical_issues['duplicate_content'] = {
|
||||||
|
'duplicate_titles': len(duplicate_titles),
|
||||||
|
'duplicate_title_groups': duplicate_titles.to_dict(),
|
||||||
|
'pages_with_duplicate_titles': df[df['title'].isin(duplicate_titles.index)][['url', 'title']].to_dict('records')[:20]
|
||||||
|
}
|
||||||
|
|
||||||
|
# Missing Elements Analysis
|
||||||
|
missing_elements = {
|
||||||
|
'missing_titles': len(df[(df['title'].isna()) | (df['title'] == '')]) if 'title' in df.columns else 0,
|
||||||
|
'missing_meta_desc': len(df[(df['meta_desc'].isna()) | (df['meta_desc'] == '')]) if 'meta_desc' in df.columns else 0,
|
||||||
|
'missing_h1': len(df[(df['h1'].isna()) | (df['h1'] == '')]) if 'h1' in df.columns else 0
|
||||||
|
}
|
||||||
|
technical_issues['missing_elements'] = missing_elements
|
||||||
|
|
||||||
|
# Page Speed Issues
|
||||||
|
if 'download_latency' in df.columns:
|
||||||
|
slow_pages = df[df['download_latency'] > 3.0] # Pages taking >3s
|
||||||
|
technical_issues['page_speed_issues'] = {
|
||||||
|
'slow_pages_count': len(slow_pages),
|
||||||
|
'avg_load_time': df['download_latency'].mean(),
|
||||||
|
'slowest_pages': slow_pages.nlargest(10, 'download_latency')[['url', 'download_latency']].to_dict('records')
|
||||||
|
}
|
||||||
|
|
||||||
|
return technical_issues
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
st.error(f"Error analyzing technical issues: {str(e)}")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def _analyze_performance_metrics(self, crawl_data: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
|
"""Analyze website performance metrics."""
|
||||||
|
try:
|
||||||
|
st.info("⚡ Analyzing performance metrics...")
|
||||||
|
|
||||||
|
if 'crawl_dataframe' not in crawl_data:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
df = crawl_data['crawl_dataframe']
|
||||||
|
|
||||||
|
performance = {
|
||||||
|
'load_time_analysis': {},
|
||||||
|
'content_size_analysis': {},
|
||||||
|
'server_performance': {},
|
||||||
|
'optimization_opportunities': []
|
||||||
|
}
|
||||||
|
|
||||||
|
# Load Time Analysis
|
||||||
|
if 'download_latency' in df.columns:
|
||||||
|
load_times = df['download_latency'].dropna()
|
||||||
|
performance['load_time_analysis'] = {
|
||||||
|
'avg_load_time': load_times.mean(),
|
||||||
|
'median_load_time': load_times.median(),
|
||||||
|
'p95_load_time': load_times.quantile(0.95),
|
||||||
|
'fastest_page': load_times.min(),
|
||||||
|
'slowest_page': load_times.max(),
|
||||||
|
'pages_over_3s': len(load_times[load_times > 3]),
|
||||||
|
'performance_distribution': {
|
||||||
|
'fast_pages': len(load_times[load_times <= 1]),
|
||||||
|
'moderate_pages': len(load_times[(load_times > 1) & (load_times <= 3)]),
|
||||||
|
'slow_pages': len(load_times[load_times > 3])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Content Size Analysis
|
||||||
|
if 'size' in df.columns:
|
||||||
|
sizes = df['size'].dropna()
|
||||||
|
performance['content_size_analysis'] = {
|
||||||
|
'avg_page_size': sizes.mean(),
|
||||||
|
'median_page_size': sizes.median(),
|
||||||
|
'largest_page': sizes.max(),
|
||||||
|
'smallest_page': sizes.min(),
|
||||||
|
'pages_over_1mb': len(sizes[sizes > 1048576]), # 1MB
|
||||||
|
'total_content_size': sizes.sum()
|
||||||
|
}
|
||||||
|
|
||||||
|
# Server Performance
|
||||||
|
status_codes = df['status'].value_counts()
|
||||||
|
total_pages = len(df)
|
||||||
|
performance['server_performance'] = {
|
||||||
|
'success_rate': status_codes.get(200, 0) / total_pages * 100,
|
||||||
|
'error_rate': sum(status_codes.get(code, 0) for code in range(400, 600)) / total_pages * 100,
|
||||||
|
'redirect_rate': sum(status_codes.get(code, 0) for code in [301, 302, 303, 307, 308]) / total_pages * 100
|
||||||
|
}
|
||||||
|
|
||||||
|
return performance
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
st.error(f"Error analyzing performance: {str(e)}")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def _analyze_content_structure(self, crawl_data: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
|
"""Analyze content structure and SEO elements."""
|
||||||
|
try:
|
||||||
|
st.info("📊 Analyzing content structure...")
|
||||||
|
|
||||||
|
if 'crawl_dataframe' not in crawl_data:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
df = crawl_data['crawl_dataframe']
|
||||||
|
|
||||||
|
content_analysis = {
|
||||||
|
'title_analysis': {},
|
||||||
|
'meta_description_analysis': {},
|
||||||
|
'heading_structure': {},
|
||||||
|
'internal_linking': {},
|
||||||
|
'content_optimization': {}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Title Analysis
|
||||||
|
if 'title' in df.columns:
|
||||||
|
titles = df['title'].dropna()
|
||||||
|
title_lengths = titles.str.len()
|
||||||
|
|
||||||
|
content_analysis['title_analysis'] = {
|
||||||
|
'avg_title_length': title_lengths.mean(),
|
||||||
|
'title_length_distribution': {
|
||||||
|
'too_short': len(title_lengths[title_lengths < 30]),
|
||||||
|
'optimal': len(title_lengths[(title_lengths >= 30) & (title_lengths <= 60)]),
|
||||||
|
'too_long': len(title_lengths[title_lengths > 60])
|
||||||
|
},
|
||||||
|
'duplicate_titles': len(titles.value_counts()[titles.value_counts() > 1]),
|
||||||
|
'missing_titles': len(df) - len(titles)
|
||||||
|
}
|
||||||
|
|
||||||
|
# Meta Description Analysis
|
||||||
|
if 'meta_desc' in df.columns:
|
||||||
|
meta_descs = df['meta_desc'].dropna()
|
||||||
|
meta_lengths = meta_descs.str.len()
|
||||||
|
|
||||||
|
content_analysis['meta_description_analysis'] = {
|
||||||
|
'avg_meta_length': meta_lengths.mean(),
|
||||||
|
'meta_length_distribution': {
|
||||||
|
'too_short': len(meta_lengths[meta_lengths < 120]),
|
||||||
|
'optimal': len(meta_lengths[(meta_lengths >= 120) & (meta_lengths <= 160)]),
|
||||||
|
'too_long': len(meta_lengths[meta_lengths > 160])
|
||||||
|
},
|
||||||
|
'missing_meta_descriptions': len(df) - len(meta_descs)
|
||||||
|
}
|
||||||
|
|
||||||
|
# Heading Structure Analysis
|
||||||
|
heading_cols = [col for col in df.columns if col.startswith('h') and col[1:].isdigit()]
|
||||||
|
if heading_cols:
|
||||||
|
heading_analysis = {}
|
||||||
|
for col in heading_cols:
|
||||||
|
headings = df[col].dropna()
|
||||||
|
heading_analysis[f'{col}_usage'] = {
|
||||||
|
'pages_with_heading': len(headings),
|
||||||
|
'usage_rate': len(headings) / len(df) * 100,
|
||||||
|
'avg_length': headings.str.len().mean() if len(headings) > 0 else 0
|
||||||
|
}
|
||||||
|
content_analysis['heading_structure'] = heading_analysis
|
||||||
|
|
||||||
|
# Internal Linking Analysis
|
||||||
|
if 'links_internal' in df.columns:
|
||||||
|
internal_links = df['links_internal'].apply(lambda x: len(x) if isinstance(x, list) else 0)
|
||||||
|
content_analysis['internal_linking'] = {
|
||||||
|
'avg_internal_links': internal_links.mean(),
|
||||||
|
'pages_with_no_internal_links': len(internal_links[internal_links == 0]),
|
||||||
|
'max_internal_links': internal_links.max(),
|
||||||
|
'internal_link_distribution': internal_links.describe().to_dict()
|
||||||
|
}
|
||||||
|
|
||||||
|
return content_analysis
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
st.error(f"Error analyzing content structure: {str(e)}")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def _analyze_url_structure(self, crawl_data: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
|
"""Analyze URL structure and optimization using adv.url_to_df."""
|
||||||
|
try:
|
||||||
|
st.info("🔗 Analyzing URL structure...")
|
||||||
|
|
||||||
|
if 'crawl_dataframe' not in crawl_data:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
df = crawl_data['crawl_dataframe']
|
||||||
|
urls = df['url'].tolist()
|
||||||
|
|
||||||
|
# Use advertools to analyze URL structure
|
||||||
|
url_df = adv.url_to_df(urls)
|
||||||
|
|
||||||
|
url_analysis = {
|
||||||
|
'url_length_analysis': {},
|
||||||
|
'url_structure_patterns': {},
|
||||||
|
'url_optimization': {},
|
||||||
|
'path_analysis': {}
|
||||||
|
}
|
||||||
|
|
||||||
|
# URL Length Analysis
|
||||||
|
url_lengths = url_df['url'].str.len()
|
||||||
|
url_analysis['url_length_analysis'] = {
|
||||||
|
'avg_url_length': url_lengths.mean(),
|
||||||
|
'max_url_length': url_lengths.max(),
|
||||||
|
'long_urls_count': len(url_lengths[url_lengths > 100]),
|
||||||
|
'url_length_distribution': url_lengths.describe().to_dict()
|
||||||
|
}
|
||||||
|
|
||||||
|
# Path Depth Analysis
|
||||||
|
if 'dir_1' in url_df.columns:
|
||||||
|
path_depths = url_df.apply(lambda row: sum(1 for i in range(1, 10) if f'dir_{i}' in row and pd.notna(row[f'dir_{i}'])), axis=1)
|
||||||
|
url_analysis['path_analysis'] = {
|
||||||
|
'avg_path_depth': path_depths.mean(),
|
||||||
|
'max_path_depth': path_depths.max(),
|
||||||
|
'deep_paths_count': len(path_depths[path_depths > 4]),
|
||||||
|
'path_depth_distribution': path_depths.value_counts().to_dict()
|
||||||
|
}
|
||||||
|
|
||||||
|
# URL Structure Patterns
|
||||||
|
domains = url_df['netloc'].value_counts()
|
||||||
|
schemes = url_df['scheme'].value_counts()
|
||||||
|
|
||||||
|
url_analysis['url_structure_patterns'] = {
|
||||||
|
'domains_found': domains.to_dict(),
|
||||||
|
'schemes_used': schemes.to_dict(),
|
||||||
|
'subdomain_usage': len(url_df[url_df['netloc'].str.contains('\.', regex=True)]),
|
||||||
|
'https_usage': schemes.get('https', 0) / len(url_df) * 100
|
||||||
|
}
|
||||||
|
|
||||||
|
# URL Optimization Issues
|
||||||
|
optimization_issues = []
|
||||||
|
|
||||||
|
# Check for non-HTTPS URLs
|
||||||
|
if schemes.get('http', 0) > 0:
|
||||||
|
optimization_issues.append(f"{schemes.get('http', 0)} pages not using HTTPS")
|
||||||
|
|
||||||
|
# Check for long URLs
|
||||||
|
long_urls = len(url_lengths[url_lengths > 100])
|
||||||
|
if long_urls > 0:
|
||||||
|
optimization_issues.append(f"{long_urls} URLs are too long (>100 characters)")
|
||||||
|
|
||||||
|
# Check for deep paths
|
||||||
|
if 'path_analysis' in url_analysis:
|
||||||
|
deep_paths = url_analysis['path_analysis']['deep_paths_count']
|
||||||
|
if deep_paths > 0:
|
||||||
|
optimization_issues.append(f"{deep_paths} URLs have deep path structures (>4 levels)")
|
||||||
|
|
||||||
|
url_analysis['url_optimization'] = {
|
||||||
|
'issues_found': len(optimization_issues),
|
||||||
|
'optimization_recommendations': optimization_issues
|
||||||
|
}
|
||||||
|
|
||||||
|
return url_analysis
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
st.error(f"Error analyzing URL structure: {str(e)}")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def _analyze_image_seo(self, website_url: str) -> Dict[str, Any]:
|
||||||
|
"""Analyze image SEO using adv.crawl_images."""
|
||||||
|
try:
|
||||||
|
st.info("🖼️ Analyzing image SEO...")
|
||||||
|
|
||||||
|
# Create image crawl output file
|
||||||
|
image_file = os.path.join(self.temp_dir, "image_crawl.jl")
|
||||||
|
|
||||||
|
# Crawl images
|
||||||
|
adv.crawl_images(
|
||||||
|
url_list=[website_url],
|
||||||
|
output_file=image_file,
|
||||||
|
custom_settings={
|
||||||
|
'DEPTH_LIMIT': 2,
|
||||||
|
'CLOSESPIDER_PAGECOUNT': 100,
|
||||||
|
'DOWNLOAD_DELAY': 1
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
image_analysis = {
|
||||||
|
'image_count': 0,
|
||||||
|
'alt_text_analysis': {},
|
||||||
|
'image_format_analysis': {},
|
||||||
|
'image_size_analysis': {},
|
||||||
|
'optimization_opportunities': []
|
||||||
|
}
|
||||||
|
|
||||||
|
if os.path.exists(image_file):
|
||||||
|
image_df = pd.read_json(image_file, lines=True)
|
||||||
|
|
||||||
|
image_analysis['image_count'] = len(image_df)
|
||||||
|
|
||||||
|
# Alt text analysis
|
||||||
|
if 'img_alt' in image_df.columns:
|
||||||
|
alt_texts = image_df['img_alt'].dropna()
|
||||||
|
missing_alt = len(image_df) - len(alt_texts)
|
||||||
|
|
||||||
|
image_analysis['alt_text_analysis'] = {
|
||||||
|
'images_with_alt': len(alt_texts),
|
||||||
|
'images_missing_alt': missing_alt,
|
||||||
|
'alt_text_coverage': len(alt_texts) / len(image_df) * 100,
|
||||||
|
'avg_alt_length': alt_texts.str.len().mean() if len(alt_texts) > 0 else 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Image format analysis
|
||||||
|
if 'img_src' in image_df.columns:
|
||||||
|
# Extract file extensions
|
||||||
|
extensions = image_df['img_src'].str.extract(r'\.([a-zA-Z]{2,4})(?:\?|$)')
|
||||||
|
format_counts = extensions[0].value_counts()
|
||||||
|
|
||||||
|
image_analysis['image_format_analysis'] = {
|
||||||
|
'format_distribution': format_counts.to_dict(),
|
||||||
|
'modern_format_usage': format_counts.get('webp', 0) + format_counts.get('avif', 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
return image_analysis
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
st.error(f"Error analyzing images: {str(e)}")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def _analyze_security_headers(self, website_url: str) -> Dict[str, Any]:
|
||||||
|
"""Analyze security headers using adv.crawl_headers."""
|
||||||
|
try:
|
||||||
|
st.info("🛡️ Analyzing security headers...")
|
||||||
|
|
||||||
|
# Create headers output file
|
||||||
|
headers_file = os.path.join(self.temp_dir, "security_headers.jl")
|
||||||
|
|
||||||
|
# Crawl headers
|
||||||
|
adv.crawl_headers([website_url], output_file=headers_file)
|
||||||
|
|
||||||
|
security_analysis = {
|
||||||
|
'security_headers_present': {},
|
||||||
|
'security_score': 0,
|
||||||
|
'security_recommendations': []
|
||||||
|
}
|
||||||
|
|
||||||
|
if os.path.exists(headers_file):
|
||||||
|
headers_df = pd.read_json(headers_file, lines=True)
|
||||||
|
|
||||||
|
# Check for important security headers
|
||||||
|
security_headers = {
|
||||||
|
'X-Frame-Options': 'resp_headers_X-Frame-Options',
|
||||||
|
'X-Content-Type-Options': 'resp_headers_X-Content-Type-Options',
|
||||||
|
'X-XSS-Protection': 'resp_headers_X-XSS-Protection',
|
||||||
|
'Strict-Transport-Security': 'resp_headers_Strict-Transport-Security',
|
||||||
|
'Content-Security-Policy': 'resp_headers_Content-Security-Policy',
|
||||||
|
'Referrer-Policy': 'resp_headers_Referrer-Policy'
|
||||||
|
}
|
||||||
|
|
||||||
|
headers_present = {}
|
||||||
|
for header_name, column_name in security_headers.items():
|
||||||
|
is_present = column_name in headers_df.columns and headers_df[column_name].notna().any()
|
||||||
|
headers_present[header_name] = is_present
|
||||||
|
|
||||||
|
security_analysis['security_headers_present'] = headers_present
|
||||||
|
|
||||||
|
# Calculate security score
|
||||||
|
present_count = sum(headers_present.values())
|
||||||
|
security_analysis['security_score'] = (present_count / len(security_headers)) * 100
|
||||||
|
|
||||||
|
# Generate recommendations
|
||||||
|
recommendations = []
|
||||||
|
for header_name, is_present in headers_present.items():
|
||||||
|
if not is_present:
|
||||||
|
recommendations.append(f"Add {header_name} header for improved security")
|
||||||
|
|
||||||
|
security_analysis['security_recommendations'] = recommendations
|
||||||
|
|
||||||
|
return security_analysis
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
st.error(f"Error analyzing security headers: {str(e)}")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def _analyze_mobile_seo(self, crawl_data: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
|
"""Analyze mobile SEO factors."""
|
||||||
|
try:
|
||||||
|
st.info("📱 Analyzing mobile SEO factors...")
|
||||||
|
|
||||||
|
if 'crawl_dataframe' not in crawl_data:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
df = crawl_data['crawl_dataframe']
|
||||||
|
|
||||||
|
mobile_analysis = {
|
||||||
|
'viewport_analysis': {},
|
||||||
|
'mobile_optimization': {},
|
||||||
|
'responsive_design_indicators': {}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Viewport meta tag analysis
|
||||||
|
if 'viewport' in df.columns:
|
||||||
|
viewport_present = df['viewport'].notna().sum()
|
||||||
|
mobile_analysis['viewport_analysis'] = {
|
||||||
|
'pages_with_viewport': viewport_present,
|
||||||
|
'viewport_coverage': viewport_present / len(df) * 100,
|
||||||
|
'pages_missing_viewport': len(df) - viewport_present
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check for mobile-specific meta tags and indicators
|
||||||
|
mobile_indicators = []
|
||||||
|
|
||||||
|
# Check for touch icons
|
||||||
|
if any('touch-icon' in col for col in df.columns):
|
||||||
|
mobile_indicators.append("Touch icons configured")
|
||||||
|
|
||||||
|
# Check for responsive design indicators in content
|
||||||
|
# This is a simplified check - in practice, you'd analyze CSS and page structure
|
||||||
|
mobile_analysis['mobile_optimization'] = {
|
||||||
|
'mobile_indicators_found': len(mobile_indicators),
|
||||||
|
'mobile_indicators': mobile_indicators
|
||||||
|
}
|
||||||
|
|
||||||
|
return mobile_analysis
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
st.error(f"Error analyzing mobile SEO: {str(e)}")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def _generate_technical_recommendations(self, results: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
|
"""Generate AI-powered technical SEO recommendations."""
|
||||||
|
try:
|
||||||
|
st.info("🤖 Generating technical recommendations...")
|
||||||
|
|
||||||
|
# Prepare technical analysis summary for AI
|
||||||
|
technical_summary = {
|
||||||
|
'website_url': results.get('website_url', ''),
|
||||||
|
'pages_crawled': results.get('crawl_overview', {}).get('pages_crawled', 0),
|
||||||
|
'error_count': results.get('technical_issues', {}).get('http_errors', {}).get('total_errors', 0),
|
||||||
|
'avg_load_time': results.get('performance_analysis', {}).get('load_time_analysis', {}).get('avg_load_time', 0),
|
||||||
|
'security_score': results.get('security_headers', {}).get('security_score', 0),
|
||||||
|
'missing_titles': results.get('content_analysis', {}).get('title_analysis', {}).get('missing_titles', 0),
|
||||||
|
'missing_meta_desc': results.get('content_analysis', {}).get('meta_description_analysis', {}).get('missing_meta_descriptions', 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
# Generate AI recommendations
|
||||||
|
prompt = f"""
|
||||||
|
As a technical SEO expert, analyze this comprehensive website audit and provide prioritized recommendations:
|
||||||
|
|
||||||
|
WEBSITE: {technical_summary['website_url']}
|
||||||
|
PAGES ANALYZED: {technical_summary['pages_crawled']}
|
||||||
|
|
||||||
|
TECHNICAL ISSUES:
|
||||||
|
- HTTP Errors: {technical_summary['error_count']}
|
||||||
|
- Average Load Time: {technical_summary['avg_load_time']:.2f}s
|
||||||
|
- Security Score: {technical_summary['security_score']:.1f}%
|
||||||
|
- Missing Titles: {technical_summary['missing_titles']}
|
||||||
|
- Missing Meta Descriptions: {technical_summary['missing_meta_desc']}
|
||||||
|
|
||||||
|
PROVIDE:
|
||||||
|
1. Critical Issues (Fix Immediately)
|
||||||
|
2. High Priority Optimizations
|
||||||
|
3. Medium Priority Improvements
|
||||||
|
4. Long-term Technical Strategy
|
||||||
|
5. Specific Implementation Steps
|
||||||
|
6. Expected Impact Assessment
|
||||||
|
|
||||||
|
Format as JSON with clear priorities and actionable recommendations.
|
||||||
|
"""
|
||||||
|
|
||||||
|
ai_response = llm_text_gen(
|
||||||
|
prompt=prompt,
|
||||||
|
system_prompt="You are a senior technical SEO specialist with expertise in website optimization, Core Web Vitals, and search engine best practices.",
|
||||||
|
response_format="json_object"
|
||||||
|
)
|
||||||
|
|
||||||
|
if ai_response:
|
||||||
|
return ai_response
|
||||||
|
else:
|
||||||
|
return {'recommendations': ['AI recommendations temporarily unavailable']}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
st.error(f"Error generating recommendations: {str(e)}")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def _find_redirect_chains(self, redirects_df: pd.DataFrame) -> List[Dict[str, Any]]:
|
||||||
|
"""Find redirect chains in the crawled data."""
|
||||||
|
# Simplified redirect chain detection
|
||||||
|
# In a full implementation, you'd trace the redirect paths
|
||||||
|
redirect_chains = []
|
||||||
|
|
||||||
|
if len(redirects_df) > 0:
|
||||||
|
# Group redirects by status code
|
||||||
|
for status_code in redirects_df['status'].unique():
|
||||||
|
status_redirects = redirects_df[redirects_df['status'] == status_code]
|
||||||
|
redirect_chains.append({
|
||||||
|
'status_code': int(status_code),
|
||||||
|
'count': len(status_redirects),
|
||||||
|
'examples': status_redirects['url'].head(5).tolist()
|
||||||
|
})
|
||||||
|
|
||||||
|
return redirect_chains
|
||||||
968
ToBeMigrated/ai_seo_tools/technical_seo_crawler/ui.py
Normal file
968
ToBeMigrated/ai_seo_tools/technical_seo_crawler/ui.py
Normal file
@@ -0,0 +1,968 @@
|
|||||||
|
"""
|
||||||
|
Technical SEO Crawler UI with Comprehensive Analysis Dashboard.
|
||||||
|
|
||||||
|
This module provides a professional Streamlit interface for the Technical SEO Crawler
|
||||||
|
with detailed analysis results, visualization, and export capabilities.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import streamlit as st
|
||||||
|
import pandas as pd
|
||||||
|
from typing import Dict, Any, List
|
||||||
|
import json
|
||||||
|
from datetime import datetime
|
||||||
|
import io
|
||||||
|
import base64
|
||||||
|
import plotly.express as px
|
||||||
|
import plotly.graph_objects as go
|
||||||
|
from plotly.subplots import make_subplots
|
||||||
|
|
||||||
|
from .crawler import TechnicalSEOCrawler
|
||||||
|
from lib.alwrity_ui.dashboard_styles import apply_dashboard_style, render_dashboard_header
|
||||||
|
|
||||||
|
class TechnicalSEOCrawlerUI:
|
||||||
|
"""Professional UI for Technical SEO Crawler."""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
"""Initialize the Technical SEO Crawler UI."""
|
||||||
|
self.crawler = TechnicalSEOCrawler()
|
||||||
|
|
||||||
|
# Apply dashboard styling
|
||||||
|
apply_dashboard_style()
|
||||||
|
|
||||||
|
def render(self):
|
||||||
|
"""Render the Technical SEO Crawler interface."""
|
||||||
|
|
||||||
|
# Enhanced dashboard header
|
||||||
|
render_dashboard_header(
|
||||||
|
"🔧 Technical SEO Crawler",
|
||||||
|
"Comprehensive site-wide technical SEO analysis with AI-powered recommendations. Identify and fix technical issues that impact your search rankings."
|
||||||
|
)
|
||||||
|
|
||||||
|
# Main content area
|
||||||
|
with st.container():
|
||||||
|
# Analysis input form
|
||||||
|
self._render_crawler_form()
|
||||||
|
|
||||||
|
# Session state for results
|
||||||
|
if 'technical_seo_results' in st.session_state and st.session_state.technical_seo_results:
|
||||||
|
st.markdown("---")
|
||||||
|
self._render_results_dashboard(st.session_state.technical_seo_results)
|
||||||
|
|
||||||
|
def _render_crawler_form(self):
|
||||||
|
"""Render the crawler configuration form."""
|
||||||
|
st.markdown("## 🚀 Configure Technical SEO Audit")
|
||||||
|
|
||||||
|
with st.form("technical_seo_crawler_form"):
|
||||||
|
# Website URL input
|
||||||
|
col1, col2 = st.columns([3, 1])
|
||||||
|
|
||||||
|
with col1:
|
||||||
|
website_url = st.text_input(
|
||||||
|
"🌐 Website URL to Audit",
|
||||||
|
placeholder="https://yourwebsite.com",
|
||||||
|
help="Enter the website URL for comprehensive technical SEO analysis"
|
||||||
|
)
|
||||||
|
|
||||||
|
with col2:
|
||||||
|
audit_type = st.selectbox(
|
||||||
|
"🎯 Audit Type",
|
||||||
|
options=["Standard", "Deep", "Quick"],
|
||||||
|
help="Choose the depth of analysis"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Crawl configuration
|
||||||
|
st.markdown("### ⚙️ Crawl Configuration")
|
||||||
|
|
||||||
|
col1, col2, col3 = st.columns(3)
|
||||||
|
|
||||||
|
with col1:
|
||||||
|
if audit_type == "Quick":
|
||||||
|
crawl_depth = st.slider("Crawl Depth", 1, 2, 1)
|
||||||
|
max_pages = st.slider("Max Pages", 10, 100, 50)
|
||||||
|
elif audit_type == "Deep":
|
||||||
|
crawl_depth = st.slider("Crawl Depth", 1, 5, 4)
|
||||||
|
max_pages = st.slider("Max Pages", 100, 1000, 500)
|
||||||
|
else: # Standard
|
||||||
|
crawl_depth = st.slider("Crawl Depth", 1, 4, 3)
|
||||||
|
max_pages = st.slider("Max Pages", 50, 500, 200)
|
||||||
|
|
||||||
|
with col2:
|
||||||
|
analyze_images = st.checkbox(
|
||||||
|
"🖼️ Analyze Images",
|
||||||
|
value=True,
|
||||||
|
help="Include image SEO analysis"
|
||||||
|
)
|
||||||
|
|
||||||
|
analyze_security = st.checkbox(
|
||||||
|
"🛡️ Security Headers",
|
||||||
|
value=True,
|
||||||
|
help="Analyze security headers"
|
||||||
|
)
|
||||||
|
|
||||||
|
with col3:
|
||||||
|
analyze_mobile = st.checkbox(
|
||||||
|
"📱 Mobile SEO",
|
||||||
|
value=True,
|
||||||
|
help="Include mobile SEO analysis"
|
||||||
|
)
|
||||||
|
|
||||||
|
ai_recommendations = st.checkbox(
|
||||||
|
"🤖 AI Recommendations",
|
||||||
|
value=True,
|
||||||
|
help="Generate AI-powered recommendations"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Analysis scope
|
||||||
|
st.markdown("### 🎯 Analysis Scope")
|
||||||
|
|
||||||
|
analysis_options = st.multiselect(
|
||||||
|
"Select Analysis Components",
|
||||||
|
options=[
|
||||||
|
"Technical Issues Detection",
|
||||||
|
"Performance Analysis",
|
||||||
|
"Content Structure Analysis",
|
||||||
|
"URL Structure Optimization",
|
||||||
|
"Internal Linking Analysis",
|
||||||
|
"Duplicate Content Detection"
|
||||||
|
],
|
||||||
|
default=[
|
||||||
|
"Technical Issues Detection",
|
||||||
|
"Performance Analysis",
|
||||||
|
"Content Structure Analysis"
|
||||||
|
],
|
||||||
|
help="Choose which analysis components to include"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Submit button
|
||||||
|
submitted = st.form_submit_button(
|
||||||
|
"🚀 Start Technical SEO Audit",
|
||||||
|
use_container_width=True,
|
||||||
|
type="primary"
|
||||||
|
)
|
||||||
|
|
||||||
|
if submitted:
|
||||||
|
# Validate inputs
|
||||||
|
if not website_url or not website_url.startswith(('http://', 'https://')):
|
||||||
|
st.error("❌ Please enter a valid website URL starting with http:// or https://")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Run technical SEO analysis
|
||||||
|
self._run_technical_analysis(
|
||||||
|
website_url=website_url,
|
||||||
|
crawl_depth=crawl_depth,
|
||||||
|
max_pages=max_pages,
|
||||||
|
options={
|
||||||
|
'analyze_images': analyze_images,
|
||||||
|
'analyze_security': analyze_security,
|
||||||
|
'analyze_mobile': analyze_mobile,
|
||||||
|
'ai_recommendations': ai_recommendations,
|
||||||
|
'analysis_scope': analysis_options
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def _run_technical_analysis(self, website_url: str, crawl_depth: int,
|
||||||
|
max_pages: int, options: Dict[str, Any]):
|
||||||
|
"""Run the technical SEO analysis."""
|
||||||
|
|
||||||
|
try:
|
||||||
|
with st.spinner("🔄 Running Comprehensive Technical SEO Audit..."):
|
||||||
|
|
||||||
|
# Initialize progress tracking
|
||||||
|
progress_bar = st.progress(0)
|
||||||
|
status_text = st.empty()
|
||||||
|
|
||||||
|
# Update progress
|
||||||
|
progress_bar.progress(10)
|
||||||
|
status_text.text("🚀 Initializing technical SEO crawler...")
|
||||||
|
|
||||||
|
# Run comprehensive analysis
|
||||||
|
results = self.crawler.analyze_website_technical_seo(
|
||||||
|
website_url=website_url,
|
||||||
|
crawl_depth=crawl_depth,
|
||||||
|
max_pages=max_pages
|
||||||
|
)
|
||||||
|
|
||||||
|
progress_bar.progress(100)
|
||||||
|
status_text.text("✅ Technical SEO audit complete!")
|
||||||
|
|
||||||
|
# Store results in session state
|
||||||
|
st.session_state.technical_seo_results = results
|
||||||
|
|
||||||
|
# Clear progress indicators
|
||||||
|
progress_bar.empty()
|
||||||
|
status_text.empty()
|
||||||
|
|
||||||
|
if 'error' in results:
|
||||||
|
st.error(f"❌ Analysis failed: {results['error']}")
|
||||||
|
else:
|
||||||
|
st.success("🎉 Technical SEO Audit completed successfully!")
|
||||||
|
st.balloons()
|
||||||
|
|
||||||
|
# Rerun to show results
|
||||||
|
st.rerun()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
st.error(f"❌ Error running technical analysis: {str(e)}")
|
||||||
|
|
||||||
|
def _render_results_dashboard(self, results: Dict[str, Any]):
|
||||||
|
"""Render the comprehensive results dashboard."""
|
||||||
|
|
||||||
|
if 'error' in results:
|
||||||
|
st.error(f"❌ Analysis Error: {results['error']}")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Results header
|
||||||
|
st.markdown("## 📊 Technical SEO Audit Results")
|
||||||
|
|
||||||
|
# Key metrics overview
|
||||||
|
self._render_metrics_overview(results)
|
||||||
|
|
||||||
|
# Detailed analysis tabs
|
||||||
|
self._render_detailed_analysis(results)
|
||||||
|
|
||||||
|
# Export functionality
|
||||||
|
self._render_export_options(results)
|
||||||
|
|
||||||
|
def _render_metrics_overview(self, results: Dict[str, Any]):
|
||||||
|
"""Render key metrics overview."""
|
||||||
|
|
||||||
|
st.markdown("### 📈 Audit Overview")
|
||||||
|
|
||||||
|
# Create metrics columns
|
||||||
|
col1, col2, col3, col4, col5, col6 = st.columns(6)
|
||||||
|
|
||||||
|
with col1:
|
||||||
|
pages_crawled = results.get('crawl_overview', {}).get('pages_crawled', 0)
|
||||||
|
st.metric(
|
||||||
|
"🕷️ Pages Crawled",
|
||||||
|
pages_crawled,
|
||||||
|
help="Total pages analyzed"
|
||||||
|
)
|
||||||
|
|
||||||
|
with col2:
|
||||||
|
error_count = results.get('technical_issues', {}).get('http_errors', {}).get('total_errors', 0)
|
||||||
|
st.metric(
|
||||||
|
"❌ HTTP Errors",
|
||||||
|
error_count,
|
||||||
|
delta=f"-{error_count}" if error_count > 0 else None,
|
||||||
|
help="Pages with HTTP errors (4xx, 5xx)"
|
||||||
|
)
|
||||||
|
|
||||||
|
with col3:
|
||||||
|
avg_load_time = results.get('performance_analysis', {}).get('load_time_analysis', {}).get('avg_load_time', 0)
|
||||||
|
st.metric(
|
||||||
|
"⚡ Avg Load Time",
|
||||||
|
f"{avg_load_time:.2f}s",
|
||||||
|
delta=f"+{avg_load_time:.2f}s" if avg_load_time > 3 else None,
|
||||||
|
help="Average page load time"
|
||||||
|
)
|
||||||
|
|
||||||
|
with col4:
|
||||||
|
security_score = results.get('security_headers', {}).get('security_score', 0)
|
||||||
|
st.metric(
|
||||||
|
"🛡️ Security Score",
|
||||||
|
f"{security_score:.0f}%",
|
||||||
|
delta=f"{security_score:.0f}%" if security_score < 100 else None,
|
||||||
|
help="Security headers implementation score"
|
||||||
|
)
|
||||||
|
|
||||||
|
with col5:
|
||||||
|
missing_titles = results.get('content_analysis', {}).get('title_analysis', {}).get('missing_titles', 0)
|
||||||
|
st.metric(
|
||||||
|
"📝 Missing Titles",
|
||||||
|
missing_titles,
|
||||||
|
delta=f"-{missing_titles}" if missing_titles > 0 else None,
|
||||||
|
help="Pages without title tags"
|
||||||
|
)
|
||||||
|
|
||||||
|
with col6:
|
||||||
|
image_count = results.get('image_optimization', {}).get('image_count', 0)
|
||||||
|
st.metric(
|
||||||
|
"🖼️ Images Analyzed",
|
||||||
|
image_count,
|
||||||
|
help="Total images found and analyzed"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Analysis timestamp
|
||||||
|
if results.get('analysis_timestamp'):
|
||||||
|
timestamp = datetime.fromisoformat(results['analysis_timestamp'].replace('Z', '+00:00'))
|
||||||
|
st.caption(f"📅 Audit completed: {timestamp.strftime('%Y-%m-%d %H:%M:%S UTC')}")
|
||||||
|
|
||||||
|
def _render_detailed_analysis(self, results: Dict[str, Any]):
|
||||||
|
"""Render detailed analysis in tabs."""
|
||||||
|
|
||||||
|
# Create main analysis tabs
|
||||||
|
tab1, tab2, tab3, tab4, tab5, tab6, tab7 = st.tabs([
|
||||||
|
"🔍 Technical Issues",
|
||||||
|
"⚡ Performance",
|
||||||
|
"📊 Content Analysis",
|
||||||
|
"🔗 URL Structure",
|
||||||
|
"🖼️ Image SEO",
|
||||||
|
"🛡️ Security",
|
||||||
|
"🤖 AI Recommendations"
|
||||||
|
])
|
||||||
|
|
||||||
|
with tab1:
|
||||||
|
self._render_technical_issues(results.get('technical_issues', {}))
|
||||||
|
|
||||||
|
with tab2:
|
||||||
|
self._render_performance_analysis(results.get('performance_analysis', {}))
|
||||||
|
|
||||||
|
with tab3:
|
||||||
|
self._render_content_analysis(results.get('content_analysis', {}))
|
||||||
|
|
||||||
|
with tab4:
|
||||||
|
self._render_url_structure(results.get('url_structure', {}))
|
||||||
|
|
||||||
|
with tab5:
|
||||||
|
self._render_image_analysis(results.get('image_optimization', {}))
|
||||||
|
|
||||||
|
with tab6:
|
||||||
|
self._render_security_analysis(results.get('security_headers', {}))
|
||||||
|
|
||||||
|
with tab7:
|
||||||
|
self._render_ai_recommendations(results.get('ai_recommendations', {}))
|
||||||
|
|
||||||
|
def _render_technical_issues(self, technical_data: Dict[str, Any]):
|
||||||
|
"""Render technical issues analysis."""
|
||||||
|
|
||||||
|
st.markdown("### 🔍 Technical SEO Issues")
|
||||||
|
|
||||||
|
if not technical_data:
|
||||||
|
st.info("No technical issues data available")
|
||||||
|
return
|
||||||
|
|
||||||
|
# HTTP Errors
|
||||||
|
if technical_data.get('http_errors'):
|
||||||
|
http_errors = technical_data['http_errors']
|
||||||
|
|
||||||
|
st.markdown("#### ❌ HTTP Status Code Errors")
|
||||||
|
|
||||||
|
if http_errors.get('total_errors', 0) > 0:
|
||||||
|
st.error(f"Found {http_errors['total_errors']} pages with HTTP errors!")
|
||||||
|
|
||||||
|
# Error breakdown chart
|
||||||
|
if http_errors.get('error_breakdown'):
|
||||||
|
error_df = pd.DataFrame(
|
||||||
|
list(http_errors['error_breakdown'].items()),
|
||||||
|
columns=['Status Code', 'Count']
|
||||||
|
)
|
||||||
|
|
||||||
|
fig = px.bar(error_df, x='Status Code', y='Count',
|
||||||
|
title="HTTP Error Distribution")
|
||||||
|
st.plotly_chart(fig, use_container_width=True)
|
||||||
|
|
||||||
|
# Error pages table
|
||||||
|
if http_errors.get('error_pages'):
|
||||||
|
st.markdown("**Pages with Errors:**")
|
||||||
|
error_pages_df = pd.DataFrame(http_errors['error_pages'])
|
||||||
|
st.dataframe(error_pages_df, use_container_width=True)
|
||||||
|
else:
|
||||||
|
st.success("✅ No HTTP errors found!")
|
||||||
|
|
||||||
|
# Redirect Issues
|
||||||
|
if technical_data.get('redirect_issues'):
|
||||||
|
redirect_data = technical_data['redirect_issues']
|
||||||
|
|
||||||
|
st.markdown("#### 🔄 Redirect Analysis")
|
||||||
|
|
||||||
|
total_redirects = redirect_data.get('total_redirects', 0)
|
||||||
|
|
||||||
|
if total_redirects > 0:
|
||||||
|
st.warning(f"Found {total_redirects} redirect(s)")
|
||||||
|
|
||||||
|
# Redirect types
|
||||||
|
if redirect_data.get('redirect_types'):
|
||||||
|
redirect_df = pd.DataFrame(
|
||||||
|
list(redirect_data['redirect_types'].items()),
|
||||||
|
columns=['Redirect Type', 'Count']
|
||||||
|
)
|
||||||
|
st.bar_chart(redirect_df.set_index('Redirect Type'))
|
||||||
|
else:
|
||||||
|
st.success("✅ No redirects found")
|
||||||
|
|
||||||
|
# Duplicate Content
|
||||||
|
if technical_data.get('duplicate_content'):
|
||||||
|
duplicate_data = technical_data['duplicate_content']
|
||||||
|
|
||||||
|
st.markdown("#### 📋 Duplicate Content Issues")
|
||||||
|
|
||||||
|
duplicate_titles = duplicate_data.get('duplicate_titles', 0)
|
||||||
|
|
||||||
|
if duplicate_titles > 0:
|
||||||
|
st.warning(f"Found {duplicate_titles} duplicate title(s)")
|
||||||
|
|
||||||
|
# Show duplicate title groups
|
||||||
|
if duplicate_data.get('pages_with_duplicate_titles'):
|
||||||
|
duplicate_df = pd.DataFrame(duplicate_data['pages_with_duplicate_titles'])
|
||||||
|
st.dataframe(duplicate_df, use_container_width=True)
|
||||||
|
else:
|
||||||
|
st.success("✅ No duplicate titles found")
|
||||||
|
|
||||||
|
# Missing Elements
|
||||||
|
if technical_data.get('missing_elements'):
|
||||||
|
missing_data = technical_data['missing_elements']
|
||||||
|
|
||||||
|
st.markdown("#### 📝 Missing SEO Elements")
|
||||||
|
|
||||||
|
col1, col2, col3 = st.columns(3)
|
||||||
|
|
||||||
|
with col1:
|
||||||
|
missing_titles = missing_data.get('missing_titles', 0)
|
||||||
|
if missing_titles > 0:
|
||||||
|
st.error(f"Missing Titles: {missing_titles}")
|
||||||
|
else:
|
||||||
|
st.success("All pages have titles ✅")
|
||||||
|
|
||||||
|
with col2:
|
||||||
|
missing_meta = missing_data.get('missing_meta_desc', 0)
|
||||||
|
if missing_meta > 0:
|
||||||
|
st.error(f"Missing Meta Descriptions: {missing_meta}")
|
||||||
|
else:
|
||||||
|
st.success("All pages have meta descriptions ✅")
|
||||||
|
|
||||||
|
with col3:
|
||||||
|
missing_h1 = missing_data.get('missing_h1', 0)
|
||||||
|
if missing_h1 > 0:
|
||||||
|
st.error(f"Missing H1 tags: {missing_h1}")
|
||||||
|
else:
|
||||||
|
st.success("All pages have H1 tags ✅")
|
||||||
|
|
||||||
|
def _render_performance_analysis(self, performance_data: Dict[str, Any]):
|
||||||
|
"""Render performance analysis."""
|
||||||
|
|
||||||
|
st.markdown("### ⚡ Website Performance Analysis")
|
||||||
|
|
||||||
|
if not performance_data:
|
||||||
|
st.info("No performance data available")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Load Time Analysis
|
||||||
|
if performance_data.get('load_time_analysis'):
|
||||||
|
load_time_data = performance_data['load_time_analysis']
|
||||||
|
|
||||||
|
st.markdown("#### 🚀 Page Load Time Analysis")
|
||||||
|
|
||||||
|
col1, col2, col3 = st.columns(3)
|
||||||
|
|
||||||
|
with col1:
|
||||||
|
avg_load = load_time_data.get('avg_load_time', 0)
|
||||||
|
st.metric("Average Load Time", f"{avg_load:.2f}s")
|
||||||
|
|
||||||
|
with col2:
|
||||||
|
median_load = load_time_data.get('median_load_time', 0)
|
||||||
|
st.metric("Median Load Time", f"{median_load:.2f}s")
|
||||||
|
|
||||||
|
with col3:
|
||||||
|
p95_load = load_time_data.get('p95_load_time', 0)
|
||||||
|
st.metric("95th Percentile", f"{p95_load:.2f}s")
|
||||||
|
|
||||||
|
# Performance distribution
|
||||||
|
if load_time_data.get('performance_distribution'):
|
||||||
|
perf_dist = load_time_data['performance_distribution']
|
||||||
|
|
||||||
|
# Create pie chart for performance distribution
|
||||||
|
labels = ['Fast (≤1s)', 'Moderate (1-3s)', 'Slow (>3s)']
|
||||||
|
values = [
|
||||||
|
perf_dist.get('fast_pages', 0),
|
||||||
|
perf_dist.get('moderate_pages', 0),
|
||||||
|
perf_dist.get('slow_pages', 0)
|
||||||
|
]
|
||||||
|
|
||||||
|
fig = px.pie(values=values, names=labels,
|
||||||
|
title="Page Load Time Distribution")
|
||||||
|
st.plotly_chart(fig, use_container_width=True)
|
||||||
|
|
||||||
|
# Content Size Analysis
|
||||||
|
if performance_data.get('content_size_analysis'):
|
||||||
|
size_data = performance_data['content_size_analysis']
|
||||||
|
|
||||||
|
st.markdown("#### 📦 Content Size Analysis")
|
||||||
|
|
||||||
|
col1, col2, col3 = st.columns(3)
|
||||||
|
|
||||||
|
with col1:
|
||||||
|
avg_size = size_data.get('avg_page_size', 0)
|
||||||
|
st.metric("Average Page Size", f"{avg_size/1024:.1f} KB")
|
||||||
|
|
||||||
|
with col2:
|
||||||
|
largest_size = size_data.get('largest_page', 0)
|
||||||
|
st.metric("Largest Page", f"{largest_size/1024:.1f} KB")
|
||||||
|
|
||||||
|
with col3:
|
||||||
|
large_pages = size_data.get('pages_over_1mb', 0)
|
||||||
|
st.metric("Pages >1MB", large_pages)
|
||||||
|
|
||||||
|
# Server Performance
|
||||||
|
if performance_data.get('server_performance'):
|
||||||
|
server_data = performance_data['server_performance']
|
||||||
|
|
||||||
|
st.markdown("#### 🖥️ Server Performance")
|
||||||
|
|
||||||
|
col1, col2, col3 = st.columns(3)
|
||||||
|
|
||||||
|
with col1:
|
||||||
|
success_rate = server_data.get('success_rate', 0)
|
||||||
|
st.metric("Success Rate", f"{success_rate:.1f}%")
|
||||||
|
|
||||||
|
with col2:
|
||||||
|
error_rate = server_data.get('error_rate', 0)
|
||||||
|
st.metric("Error Rate", f"{error_rate:.1f}%")
|
||||||
|
|
||||||
|
with col3:
|
||||||
|
redirect_rate = server_data.get('redirect_rate', 0)
|
||||||
|
st.metric("Redirect Rate", f"{redirect_rate:.1f}%")
|
||||||
|
|
||||||
|
def _render_content_analysis(self, content_data: Dict[str, Any]):
|
||||||
|
"""Render content structure analysis."""
|
||||||
|
|
||||||
|
st.markdown("### 📊 Content Structure Analysis")
|
||||||
|
|
||||||
|
if not content_data:
|
||||||
|
st.info("No content analysis data available")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Title Analysis
|
||||||
|
if content_data.get('title_analysis'):
|
||||||
|
title_data = content_data['title_analysis']
|
||||||
|
|
||||||
|
st.markdown("#### 📝 Title Tag Analysis")
|
||||||
|
|
||||||
|
col1, col2 = st.columns(2)
|
||||||
|
|
||||||
|
with col1:
|
||||||
|
avg_title_length = title_data.get('avg_title_length', 0)
|
||||||
|
st.metric("Average Title Length", f"{avg_title_length:.0f} chars")
|
||||||
|
|
||||||
|
duplicate_titles = title_data.get('duplicate_titles', 0)
|
||||||
|
st.metric("Duplicate Titles", duplicate_titles)
|
||||||
|
|
||||||
|
with col2:
|
||||||
|
# Title length distribution
|
||||||
|
if title_data.get('title_length_distribution'):
|
||||||
|
length_dist = title_data['title_length_distribution']
|
||||||
|
|
||||||
|
labels = ['Too Short (<30)', 'Optimal (30-60)', 'Too Long (>60)']
|
||||||
|
values = [
|
||||||
|
length_dist.get('too_short', 0),
|
||||||
|
length_dist.get('optimal', 0),
|
||||||
|
length_dist.get('too_long', 0)
|
||||||
|
]
|
||||||
|
|
||||||
|
fig = px.pie(values=values, names=labels,
|
||||||
|
title="Title Length Distribution")
|
||||||
|
st.plotly_chart(fig, use_container_width=True)
|
||||||
|
|
||||||
|
# Meta Description Analysis
|
||||||
|
if content_data.get('meta_description_analysis'):
|
||||||
|
meta_data = content_data['meta_description_analysis']
|
||||||
|
|
||||||
|
st.markdown("#### 🏷️ Meta Description Analysis")
|
||||||
|
|
||||||
|
col1, col2 = st.columns(2)
|
||||||
|
|
||||||
|
with col1:
|
||||||
|
avg_meta_length = meta_data.get('avg_meta_length', 0)
|
||||||
|
st.metric("Average Meta Length", f"{avg_meta_length:.0f} chars")
|
||||||
|
|
||||||
|
missing_meta = meta_data.get('missing_meta_descriptions', 0)
|
||||||
|
st.metric("Missing Meta Descriptions", missing_meta)
|
||||||
|
|
||||||
|
with col2:
|
||||||
|
# Meta length distribution
|
||||||
|
if meta_data.get('meta_length_distribution'):
|
||||||
|
meta_dist = meta_data['meta_length_distribution']
|
||||||
|
|
||||||
|
labels = ['Too Short (<120)', 'Optimal (120-160)', 'Too Long (>160)']
|
||||||
|
values = [
|
||||||
|
meta_dist.get('too_short', 0),
|
||||||
|
meta_dist.get('optimal', 0),
|
||||||
|
meta_dist.get('too_long', 0)
|
||||||
|
]
|
||||||
|
|
||||||
|
fig = px.pie(values=values, names=labels,
|
||||||
|
title="Meta Description Length Distribution")
|
||||||
|
st.plotly_chart(fig, use_container_width=True)
|
||||||
|
|
||||||
|
# Heading Structure
|
||||||
|
if content_data.get('heading_structure'):
|
||||||
|
heading_data = content_data['heading_structure']
|
||||||
|
|
||||||
|
st.markdown("#### 📋 Heading Structure Analysis")
|
||||||
|
|
||||||
|
# Create heading usage chart
|
||||||
|
heading_usage = []
|
||||||
|
for heading_type, data in heading_data.items():
|
||||||
|
heading_usage.append({
|
||||||
|
'Heading': heading_type.replace('_usage', '').upper(),
|
||||||
|
'Usage Rate': data.get('usage_rate', 0),
|
||||||
|
'Pages': data.get('pages_with_heading', 0)
|
||||||
|
})
|
||||||
|
|
||||||
|
if heading_usage:
|
||||||
|
heading_df = pd.DataFrame(heading_usage)
|
||||||
|
|
||||||
|
fig = px.bar(heading_df, x='Heading', y='Usage Rate',
|
||||||
|
title="Heading Tag Usage Rates")
|
||||||
|
st.plotly_chart(fig, use_container_width=True)
|
||||||
|
|
||||||
|
st.dataframe(heading_df, use_container_width=True)
|
||||||
|
|
||||||
|
def _render_url_structure(self, url_data: Dict[str, Any]):
|
||||||
|
"""Render URL structure analysis."""
|
||||||
|
|
||||||
|
st.markdown("### 🔗 URL Structure Analysis")
|
||||||
|
|
||||||
|
if not url_data:
|
||||||
|
st.info("No URL structure data available")
|
||||||
|
return
|
||||||
|
|
||||||
|
# URL Length Analysis
|
||||||
|
if url_data.get('url_length_analysis'):
|
||||||
|
length_data = url_data['url_length_analysis']
|
||||||
|
|
||||||
|
st.markdown("#### 📏 URL Length Analysis")
|
||||||
|
|
||||||
|
col1, col2, col3 = st.columns(3)
|
||||||
|
|
||||||
|
with col1:
|
||||||
|
avg_length = length_data.get('avg_url_length', 0)
|
||||||
|
st.metric("Average URL Length", f"{avg_length:.0f} chars")
|
||||||
|
|
||||||
|
with col2:
|
||||||
|
max_length = length_data.get('max_url_length', 0)
|
||||||
|
st.metric("Longest URL", f"{max_length:.0f} chars")
|
||||||
|
|
||||||
|
with col3:
|
||||||
|
long_urls = length_data.get('long_urls_count', 0)
|
||||||
|
st.metric("URLs >100 chars", long_urls)
|
||||||
|
|
||||||
|
# URL Structure Patterns
|
||||||
|
if url_data.get('url_structure_patterns'):
|
||||||
|
pattern_data = url_data['url_structure_patterns']
|
||||||
|
|
||||||
|
st.markdown("#### 🏗️ URL Structure Patterns")
|
||||||
|
|
||||||
|
col1, col2 = st.columns(2)
|
||||||
|
|
||||||
|
with col1:
|
||||||
|
https_usage = pattern_data.get('https_usage', 0)
|
||||||
|
st.metric("HTTPS Usage", f"{https_usage:.1f}%")
|
||||||
|
|
||||||
|
with col2:
|
||||||
|
subdomain_usage = pattern_data.get('subdomain_usage', 0)
|
||||||
|
st.metric("Subdomains Found", subdomain_usage)
|
||||||
|
|
||||||
|
# Path Analysis
|
||||||
|
if url_data.get('path_analysis'):
|
||||||
|
path_data = url_data['path_analysis']
|
||||||
|
|
||||||
|
st.markdown("#### 📂 Path Depth Analysis")
|
||||||
|
|
||||||
|
col1, col2, col3 = st.columns(3)
|
||||||
|
|
||||||
|
with col1:
|
||||||
|
avg_depth = path_data.get('avg_path_depth', 0)
|
||||||
|
st.metric("Average Path Depth", f"{avg_depth:.1f}")
|
||||||
|
|
||||||
|
with col2:
|
||||||
|
max_depth = path_data.get('max_path_depth', 0)
|
||||||
|
st.metric("Maximum Depth", max_depth)
|
||||||
|
|
||||||
|
with col3:
|
||||||
|
deep_paths = path_data.get('deep_paths_count', 0)
|
||||||
|
st.metric("Deep Paths (>4)", deep_paths)
|
||||||
|
|
||||||
|
# Optimization Issues
|
||||||
|
if url_data.get('url_optimization'):
|
||||||
|
opt_data = url_data['url_optimization']
|
||||||
|
|
||||||
|
st.markdown("#### ⚠️ URL Optimization Issues")
|
||||||
|
|
||||||
|
issues_found = opt_data.get('issues_found', 0)
|
||||||
|
recommendations = opt_data.get('optimization_recommendations', [])
|
||||||
|
|
||||||
|
if issues_found > 0:
|
||||||
|
st.warning(f"Found {issues_found} URL optimization issue(s)")
|
||||||
|
|
||||||
|
for rec in recommendations:
|
||||||
|
st.write(f"• {rec}")
|
||||||
|
else:
|
||||||
|
st.success("✅ No URL optimization issues found")
|
||||||
|
|
||||||
|
def _render_image_analysis(self, image_data: Dict[str, Any]):
|
||||||
|
"""Render image SEO analysis."""
|
||||||
|
|
||||||
|
st.markdown("### 🖼️ Image SEO Analysis")
|
||||||
|
|
||||||
|
if not image_data:
|
||||||
|
st.info("No image analysis data available")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Image overview
|
||||||
|
image_count = image_data.get('image_count', 0)
|
||||||
|
st.metric("Total Images Found", image_count)
|
||||||
|
|
||||||
|
if image_count > 0:
|
||||||
|
# Alt text analysis
|
||||||
|
if image_data.get('alt_text_analysis'):
|
||||||
|
alt_data = image_data['alt_text_analysis']
|
||||||
|
|
||||||
|
st.markdown("#### 📝 Alt Text Analysis")
|
||||||
|
|
||||||
|
col1, col2, col3 = st.columns(3)
|
||||||
|
|
||||||
|
with col1:
|
||||||
|
images_with_alt = alt_data.get('images_with_alt', 0)
|
||||||
|
st.metric("Images with Alt Text", images_with_alt)
|
||||||
|
|
||||||
|
with col2:
|
||||||
|
images_missing_alt = alt_data.get('images_missing_alt', 0)
|
||||||
|
st.metric("Missing Alt Text", images_missing_alt)
|
||||||
|
|
||||||
|
with col3:
|
||||||
|
alt_coverage = alt_data.get('alt_text_coverage', 0)
|
||||||
|
st.metric("Alt Text Coverage", f"{alt_coverage:.1f}%")
|
||||||
|
|
||||||
|
# Image format analysis
|
||||||
|
if image_data.get('image_format_analysis'):
|
||||||
|
format_data = image_data['image_format_analysis']
|
||||||
|
|
||||||
|
st.markdown("#### 🎨 Image Format Analysis")
|
||||||
|
|
||||||
|
if format_data.get('format_distribution'):
|
||||||
|
format_dist = format_data['format_distribution']
|
||||||
|
|
||||||
|
format_df = pd.DataFrame(
|
||||||
|
list(format_dist.items()),
|
||||||
|
columns=['Format', 'Count']
|
||||||
|
)
|
||||||
|
|
||||||
|
fig = px.pie(format_df, values='Count', names='Format',
|
||||||
|
title="Image Format Distribution")
|
||||||
|
st.plotly_chart(fig, use_container_width=True)
|
||||||
|
|
||||||
|
modern_formats = format_data.get('modern_format_usage', 0)
|
||||||
|
st.metric("Modern Formats (WebP/AVIF)", modern_formats)
|
||||||
|
else:
|
||||||
|
st.info("No images found to analyze")
|
||||||
|
|
||||||
|
def _render_security_analysis(self, security_data: Dict[str, Any]):
|
||||||
|
"""Render security analysis."""
|
||||||
|
|
||||||
|
st.markdown("### 🛡️ Security Headers Analysis")
|
||||||
|
|
||||||
|
if not security_data:
|
||||||
|
st.info("No security analysis data available")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Security score
|
||||||
|
security_score = security_data.get('security_score', 0)
|
||||||
|
|
||||||
|
col1, col2 = st.columns([1, 2])
|
||||||
|
|
||||||
|
with col1:
|
||||||
|
st.metric("Security Score", f"{security_score:.0f}%")
|
||||||
|
|
||||||
|
if security_score >= 80:
|
||||||
|
st.success("🔒 Good security posture")
|
||||||
|
elif security_score >= 50:
|
||||||
|
st.warning("⚠️ Moderate security")
|
||||||
|
else:
|
||||||
|
st.error("🚨 Poor security posture")
|
||||||
|
|
||||||
|
with col2:
|
||||||
|
# Security headers status
|
||||||
|
if security_data.get('security_headers_present'):
|
||||||
|
headers_status = security_data['security_headers_present']
|
||||||
|
|
||||||
|
st.markdown("**Security Headers Status:**")
|
||||||
|
|
||||||
|
for header, present in headers_status.items():
|
||||||
|
status = "✅" if present else "❌"
|
||||||
|
st.write(f"{status} {header}")
|
||||||
|
|
||||||
|
# Security recommendations
|
||||||
|
if security_data.get('security_recommendations'):
|
||||||
|
recommendations = security_data['security_recommendations']
|
||||||
|
|
||||||
|
if recommendations:
|
||||||
|
st.markdown("#### 🔧 Security Recommendations")
|
||||||
|
|
||||||
|
for rec in recommendations:
|
||||||
|
st.write(f"• {rec}")
|
||||||
|
else:
|
||||||
|
st.success("✅ All security headers properly configured")
|
||||||
|
|
||||||
|
def _render_ai_recommendations(self, ai_data: Dict[str, Any]):
|
||||||
|
"""Render AI-generated recommendations."""
|
||||||
|
|
||||||
|
st.markdown("### 🤖 AI-Powered Technical Recommendations")
|
||||||
|
|
||||||
|
if not ai_data:
|
||||||
|
st.info("No AI recommendations available")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Critical Issues
|
||||||
|
if ai_data.get('critical_issues'):
|
||||||
|
st.markdown("#### 🚨 Critical Issues (Fix Immediately)")
|
||||||
|
|
||||||
|
critical_issues = ai_data['critical_issues']
|
||||||
|
for issue in critical_issues:
|
||||||
|
st.error(f"🚨 {issue}")
|
||||||
|
|
||||||
|
# High Priority
|
||||||
|
if ai_data.get('high_priority'):
|
||||||
|
st.markdown("#### 🔥 High Priority Optimizations")
|
||||||
|
|
||||||
|
high_priority = ai_data['high_priority']
|
||||||
|
for item in high_priority:
|
||||||
|
st.warning(f"⚡ {item}")
|
||||||
|
|
||||||
|
# Medium Priority
|
||||||
|
if ai_data.get('medium_priority'):
|
||||||
|
st.markdown("#### 📈 Medium Priority Improvements")
|
||||||
|
|
||||||
|
medium_priority = ai_data['medium_priority']
|
||||||
|
for item in medium_priority:
|
||||||
|
st.info(f"📊 {item}")
|
||||||
|
|
||||||
|
# Implementation Steps
|
||||||
|
if ai_data.get('implementation_steps'):
|
||||||
|
st.markdown("#### 🛠️ Implementation Steps")
|
||||||
|
|
||||||
|
steps = ai_data['implementation_steps']
|
||||||
|
for i, step in enumerate(steps, 1):
|
||||||
|
st.write(f"{i}. {step}")
|
||||||
|
|
||||||
|
# Expected Impact
|
||||||
|
if ai_data.get('expected_impact'):
|
||||||
|
st.markdown("#### 📈 Expected Impact Assessment")
|
||||||
|
|
||||||
|
impact = ai_data['expected_impact']
|
||||||
|
st.markdown(impact)
|
||||||
|
|
||||||
|
def _render_export_options(self, results: Dict[str, Any]):
|
||||||
|
"""Render export options for analysis results."""
|
||||||
|
|
||||||
|
st.markdown("---")
|
||||||
|
st.markdown("### 📥 Export Technical SEO Audit")
|
||||||
|
|
||||||
|
col1, col2, col3 = st.columns(3)
|
||||||
|
|
||||||
|
with col1:
|
||||||
|
# JSON export
|
||||||
|
if st.button("📄 Export Full Report (JSON)", use_container_width=True):
|
||||||
|
json_data = json.dumps(results, indent=2, default=str)
|
||||||
|
|
||||||
|
st.download_button(
|
||||||
|
label="⬇️ Download JSON Report",
|
||||||
|
data=json_data,
|
||||||
|
file_name=f"technical_seo_audit_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json",
|
||||||
|
mime="application/json",
|
||||||
|
use_container_width=True
|
||||||
|
)
|
||||||
|
|
||||||
|
with col2:
|
||||||
|
# CSV export for issues
|
||||||
|
if st.button("📊 Export Issues CSV", use_container_width=True):
|
||||||
|
issues_data = self._prepare_issues_csv(results)
|
||||||
|
|
||||||
|
if issues_data:
|
||||||
|
st.download_button(
|
||||||
|
label="⬇️ Download Issues CSV",
|
||||||
|
data=issues_data,
|
||||||
|
file_name=f"technical_issues_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv",
|
||||||
|
mime="text/csv",
|
||||||
|
use_container_width=True
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
st.info("No issues found to export")
|
||||||
|
|
||||||
|
with col3:
|
||||||
|
# Executive summary
|
||||||
|
if st.button("📋 Executive Summary", use_container_width=True):
|
||||||
|
summary = self._generate_executive_summary(results)
|
||||||
|
|
||||||
|
st.download_button(
|
||||||
|
label="⬇️ Download Summary",
|
||||||
|
data=summary,
|
||||||
|
file_name=f"technical_seo_summary_{datetime.now().strftime('%Y%m%d_%H%M%S')}.txt",
|
||||||
|
mime="text/plain",
|
||||||
|
use_container_width=True
|
||||||
|
)
|
||||||
|
|
||||||
|
def _prepare_issues_csv(self, results: Dict[str, Any]) -> str:
|
||||||
|
"""Prepare CSV data for technical issues."""
|
||||||
|
|
||||||
|
issues_list = []
|
||||||
|
|
||||||
|
# HTTP errors
|
||||||
|
http_errors = results.get('technical_issues', {}).get('http_errors', {})
|
||||||
|
if http_errors.get('error_pages'):
|
||||||
|
for error in http_errors['error_pages']:
|
||||||
|
issues_list.append({
|
||||||
|
'Issue Type': 'HTTP Error',
|
||||||
|
'Severity': 'High',
|
||||||
|
'URL': error.get('url', ''),
|
||||||
|
'Status Code': error.get('status', ''),
|
||||||
|
'Description': f"HTTP {error.get('status', '')} error"
|
||||||
|
})
|
||||||
|
|
||||||
|
# Missing elements
|
||||||
|
missing_elements = results.get('technical_issues', {}).get('missing_elements', {})
|
||||||
|
|
||||||
|
# Add more issue types as needed...
|
||||||
|
|
||||||
|
if issues_list:
|
||||||
|
issues_df = pd.DataFrame(issues_list)
|
||||||
|
return issues_df.to_csv(index=False)
|
||||||
|
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def _generate_executive_summary(self, results: Dict[str, Any]) -> str:
|
||||||
|
"""Generate executive summary report."""
|
||||||
|
|
||||||
|
website_url = results.get('website_url', 'Unknown')
|
||||||
|
timestamp = results.get('analysis_timestamp', datetime.now().isoformat())
|
||||||
|
|
||||||
|
summary = f"""
|
||||||
|
TECHNICAL SEO AUDIT - EXECUTIVE SUMMARY
|
||||||
|
======================================
|
||||||
|
|
||||||
|
Website: {website_url}
|
||||||
|
Audit Date: {timestamp}
|
||||||
|
|
||||||
|
AUDIT OVERVIEW
|
||||||
|
--------------
|
||||||
|
Pages Crawled: {results.get('crawl_overview', {}).get('pages_crawled', 0)}
|
||||||
|
HTTP Errors: {results.get('technical_issues', {}).get('http_errors', {}).get('total_errors', 0)}
|
||||||
|
Average Load Time: {results.get('performance_analysis', {}).get('load_time_analysis', {}).get('avg_load_time', 0):.2f}s
|
||||||
|
Security Score: {results.get('security_headers', {}).get('security_score', 0):.0f}%
|
||||||
|
|
||||||
|
CRITICAL FINDINGS
|
||||||
|
-----------------
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Add critical findings
|
||||||
|
error_count = results.get('technical_issues', {}).get('http_errors', {}).get('total_errors', 0)
|
||||||
|
if error_count > 0:
|
||||||
|
summary += f"• {error_count} pages have HTTP errors requiring immediate attention\n"
|
||||||
|
|
||||||
|
avg_load_time = results.get('performance_analysis', {}).get('load_time_analysis', {}).get('avg_load_time', 0)
|
||||||
|
if avg_load_time > 3:
|
||||||
|
summary += f"• Page load times are slow (avg: {avg_load_time:.2f}s), impacting user experience\n"
|
||||||
|
|
||||||
|
security_score = results.get('security_headers', {}).get('security_score', 0)
|
||||||
|
if security_score < 80:
|
||||||
|
summary += f"• Security headers need improvement (current score: {security_score:.0f}%)\n"
|
||||||
|
|
||||||
|
summary += f"\n\nDetailed technical audit completed by ALwrity Technical SEO Crawler\nGenerated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
|
||||||
|
|
||||||
|
return summary
|
||||||
|
|
||||||
|
# Render function for integration with main dashboard
|
||||||
|
def render_technical_seo_crawler():
|
||||||
|
"""Render the Technical SEO Crawler UI."""
|
||||||
|
ui = TechnicalSEOCrawlerUI()
|
||||||
|
ui.render()
|
||||||
58
ToBeMigrated/ai_seo_tools/textstaty.py
Normal file
58
ToBeMigrated/ai_seo_tools/textstaty.py
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
"""Text analysis tools using textstat."""
|
||||||
|
|
||||||
|
import streamlit as st
|
||||||
|
from textstat import textstat
|
||||||
|
|
||||||
|
def analyze_text(text):
|
||||||
|
"""Analyze text using textstat metrics."""
|
||||||
|
if not text:
|
||||||
|
st.warning("Please enter some text to analyze.")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Calculate various metrics
|
||||||
|
metrics = {
|
||||||
|
"Flesch Reading Ease": textstat.flesch_reading_ease(text),
|
||||||
|
"Flesch-Kincaid Grade Level": textstat.flesch_kincaid_grade(text),
|
||||||
|
"Gunning Fog Index": textstat.gunning_fog(text),
|
||||||
|
"SMOG Index": textstat.smog_index(text),
|
||||||
|
"Automated Readability Index": textstat.automated_readability_index(text),
|
||||||
|
"Coleman-Liau Index": textstat.coleman_liau_index(text),
|
||||||
|
"Linsear Write Formula": textstat.linsear_write_formula(text),
|
||||||
|
"Dale-Chall Readability Score": textstat.dale_chall_readability_score(text),
|
||||||
|
"Readability Consensus": textstat.readability_consensus(text)
|
||||||
|
}
|
||||||
|
|
||||||
|
# Display metrics in a clean format
|
||||||
|
st.subheader("Text Analysis Results")
|
||||||
|
for metric, value in metrics.items():
|
||||||
|
st.metric(metric, f"{value:.2f}")
|
||||||
|
|
||||||
|
# Add visualizations
|
||||||
|
st.subheader("Visualization")
|
||||||
|
st.bar_chart(metrics)
|
||||||
|
|
||||||
|
st.title("📖 Text Readability Analyzer: Making Your Content Easy to Read")
|
||||||
|
|
||||||
|
st.write("""
|
||||||
|
This tool is your guide to writing content that's easy for your audience to understand.
|
||||||
|
Just paste in a sample of your text, and we'll break down the readability scores and offer actionable tips!
|
||||||
|
""")
|
||||||
|
|
||||||
|
text_input = st.text_area("Paste your text here:", height=200)
|
||||||
|
|
||||||
|
if st.button("Analyze!"):
|
||||||
|
with st.spinner("Analyzing your text..."):
|
||||||
|
test_data = text_input
|
||||||
|
if not test_data.strip():
|
||||||
|
st.error("Please enter text to analyze.")
|
||||||
|
else:
|
||||||
|
analyze_text(test_data)
|
||||||
|
|
||||||
|
st.subheader("Key Takeaways:")
|
||||||
|
st.write("---")
|
||||||
|
st.markdown("""
|
||||||
|
* **Don't Be Afraid to Simplify!** Often, simpler language makes content more impactful and easier to digest.
|
||||||
|
* **Aim for a Reading Level Appropriate for Your Audience:** Consider the education level, background, and familiarity of your readers.
|
||||||
|
* **Use Short Sentences:** This makes your content more scannable and easier to read.
|
||||||
|
* **Write for Everyone:** Accessibility should always be a priority. When in doubt, aim for clear, concise language!
|
||||||
|
""")
|
||||||
102
ToBeMigrated/ai_writers/ai_news_article_writer.py
Normal file
102
ToBeMigrated/ai_writers/ai_news_article_writer.py
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
######################################################
|
||||||
|
#
|
||||||
|
# Alwrity, as an AI news writer, will have to be factually correct.
|
||||||
|
# We will do multiple rounds of web research and cite our sources.
|
||||||
|
# 'include_urls' will focus news articles only from well known sources.
|
||||||
|
# Choosing a country will help us get better results.
|
||||||
|
#
|
||||||
|
######################################################
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import json
|
||||||
|
from textwrap import dedent
|
||||||
|
from pathlib import Path
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
load_dotenv(Path('../../.env'))
|
||||||
|
from loguru import logger
|
||||||
|
logger.remove()
|
||||||
|
logger.add(sys.stdout,
|
||||||
|
colorize=True,
|
||||||
|
format="<level>{level}</level>|<green>{file}:{line}:{function}</green>| {message}"
|
||||||
|
)
|
||||||
|
|
||||||
|
from ..gpt_providers.text_generation.main_text_generation import llm_text_gen
|
||||||
|
from ..ai_web_researcher.google_serp_search import perform_serper_news_search
|
||||||
|
|
||||||
|
|
||||||
|
def ai_news_generation(news_keywords, news_country, news_language):
|
||||||
|
""" Generate news aritcle based on given keywords. """
|
||||||
|
# Use to store the blog in a string, to save in a *.md file.
|
||||||
|
blog_markdown_str = ""
|
||||||
|
|
||||||
|
logger.info(f"Researching and Writing News Article on keywords: {news_keywords}")
|
||||||
|
# Call on the got-researcher, tavily apis for this. Do google search for organic competition.
|
||||||
|
try:
|
||||||
|
google_news_result = perform_serper_news_search(news_keywords, news_country, news_language)
|
||||||
|
blog_markdown_str = write_news_google_search(news_keywords, news_country, news_language, google_news_result)
|
||||||
|
#print(blog_markdown_str)
|
||||||
|
except Exception as err:
|
||||||
|
logger.error(f"Failed in Google News web research: {err}")
|
||||||
|
logger.info("\n######### Draft1: Finished News article from Google web search: ###########\n\n")
|
||||||
|
return blog_markdown_str
|
||||||
|
|
||||||
|
|
||||||
|
def write_news_google_search(news_keywords, news_country, news_language, search_results):
|
||||||
|
"""Combine the given online research and gpt blog content"""
|
||||||
|
news_language = get_language_name(news_language)
|
||||||
|
news_country = get_country_name(news_country)
|
||||||
|
|
||||||
|
prompt = f"""
|
||||||
|
As an experienced {news_language} news journalist and editor,
|
||||||
|
I will provide you with my 'News keywords' and its 'google search results'.
|
||||||
|
Your goal is to write a News report, backed by given google search results.
|
||||||
|
Important, as a news report, its imperative that your content is factually correct and cited.
|
||||||
|
|
||||||
|
Follow below guidelines:
|
||||||
|
1). Understand and utilize the provided google search result json.
|
||||||
|
2). Always provide in-line citations and provide referance links.
|
||||||
|
3). Understand the given news item and adapt your tone accordingly.
|
||||||
|
4). Always include the dates when then news was reported.
|
||||||
|
6). Do not explain, describe your response.
|
||||||
|
7). Your blog should be highly formatted in markdown style and highly readable.
|
||||||
|
8). Important: Please read the entire prompt before writing anything. Follow the prompt exactly as I instructed.
|
||||||
|
|
||||||
|
\n\nNews Keywords: "{news_keywords}"\n\n
|
||||||
|
Google search Result: "{search_results}"
|
||||||
|
"""
|
||||||
|
logger.info("Generating blog and FAQs from Google web search results.")
|
||||||
|
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 get_language_name(language_code):
|
||||||
|
languages = {
|
||||||
|
"es": "Spanish",
|
||||||
|
"vn": "Vietnamese",
|
||||||
|
"en": "English",
|
||||||
|
"ar": "Arabic",
|
||||||
|
"hi": "Hindi",
|
||||||
|
"de": "German",
|
||||||
|
"zh-cn": "Chinese (Simplified)"
|
||||||
|
# Add more language codes and corresponding names as needed
|
||||||
|
}
|
||||||
|
return languages.get(language_code, "Unknown")
|
||||||
|
|
||||||
|
def get_country_name(country_code):
|
||||||
|
countries = {
|
||||||
|
"es": "Spain",
|
||||||
|
"vn": "Vietnam",
|
||||||
|
"pk": "Pakistan",
|
||||||
|
"in": "India",
|
||||||
|
"de": "Germany",
|
||||||
|
"cn": "China"
|
||||||
|
# Add more country codes and corresponding names as needed
|
||||||
|
}
|
||||||
|
return countries.get(country_code, "Unknown")
|
||||||
115
ToBeMigrated/ai_writers/ai_product_description_writer.py
Normal file
115
ToBeMigrated/ai_writers/ai_product_description_writer.py
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
import streamlit as st
|
||||||
|
import json
|
||||||
|
|
||||||
|
from ..gpt_providers.text_generation.main_text_generation import llm_text_gen
|
||||||
|
|
||||||
|
|
||||||
|
def generate_product_description(title, details, audience, tone, length, keywords):
|
||||||
|
"""
|
||||||
|
Generates a product description using OpenAI's API.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
title (str): The title of the product.
|
||||||
|
details (list): A list of product details (features, benefits, etc.).
|
||||||
|
audience (list): A list of target audience segments.
|
||||||
|
tone (str): The desired tone of the description (e.g., "Formal", "Informal").
|
||||||
|
length (str): The desired length of the description (e.g., "short", "medium", "long").
|
||||||
|
keywords (str): Keywords related to the product (comma-separated).
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: The generated product description.
|
||||||
|
"""
|
||||||
|
prompt = f"""
|
||||||
|
Write a compelling product description for {title}.
|
||||||
|
|
||||||
|
Highlight these key features: {', '.join(details)}
|
||||||
|
|
||||||
|
Emphasize the benefits of these features for the target audience ({audience}).
|
||||||
|
Maintain a {tone} tone and aim for a length of approximately {length} words.
|
||||||
|
|
||||||
|
Use these keywords naturally throughout the description: {', '.join(keywords)}.
|
||||||
|
|
||||||
|
Remember to be persuasive and focus on the value proposition.
|
||||||
|
"""
|
||||||
|
|
||||||
|
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 display_inputs():
|
||||||
|
st.title("📝 AI Product Description Writer 🚀")
|
||||||
|
st.markdown("**Generate compelling and accurate product descriptions with AI.**")
|
||||||
|
|
||||||
|
col1, col2 = st.columns(2)
|
||||||
|
|
||||||
|
with col1:
|
||||||
|
product_title = st.text_input("🏷️ **Product Title**", placeholder="Enter the product title (e.g., Wireless Bluetooth Headphones)")
|
||||||
|
with col2:
|
||||||
|
product_details = st.text_area("📄 **Product Details**", placeholder="Enter features, benefits, specifications, materials, etc. (e.g., Noise Cancellation, Long Battery Life, Water Resistant, Comfortable Design)")
|
||||||
|
|
||||||
|
col3, col4 = st.columns(2)
|
||||||
|
|
||||||
|
with col3:
|
||||||
|
keywords = st.text_input("🔑 **Keywords**", placeholder="Enter keywords, comma-separated (e.g., wireless headphones, noise cancelling, Bluetooth 5.0)")
|
||||||
|
with col4:
|
||||||
|
target_audience = st.multiselect(
|
||||||
|
"🎯 **Target Audience**",
|
||||||
|
["Teens", "Adults", "Seniors", "Music Lovers", "Fitness Enthusiasts", "Tech Savvy", "Busy Professionals", "Travelers", "Casual Users"],
|
||||||
|
placeholder="Select target audience (optional)"
|
||||||
|
)
|
||||||
|
|
||||||
|
col5, col6 = st.columns(2)
|
||||||
|
|
||||||
|
with col5:
|
||||||
|
description_length = st.selectbox(
|
||||||
|
"📏 **Desired Description Length**",
|
||||||
|
["Short (1-2 sentences)", "Medium (3-5 sentences)", "Long (6+ sentences)"],
|
||||||
|
help="Select the desired length of the product description"
|
||||||
|
)
|
||||||
|
with col6:
|
||||||
|
brand_tone = st.selectbox(
|
||||||
|
"🎨 **Brand Tone**",
|
||||||
|
["Formal", "Informal", "Fun & Energetic"],
|
||||||
|
help="Select the desired tone for the description"
|
||||||
|
)
|
||||||
|
|
||||||
|
return product_title, product_details, target_audience, brand_tone, description_length, keywords
|
||||||
|
|
||||||
|
|
||||||
|
def display_output(description):
|
||||||
|
if description:
|
||||||
|
st.subheader("✨ Generated Product Description:")
|
||||||
|
st.write(description)
|
||||||
|
|
||||||
|
json_ld = {
|
||||||
|
"@context": "https://schema.org",
|
||||||
|
"@type": "Product",
|
||||||
|
"name": product_title,
|
||||||
|
"description": description,
|
||||||
|
"audience": target_audience,
|
||||||
|
"brand": {
|
||||||
|
"@type": "Brand",
|
||||||
|
"name": "Your Brand Name"
|
||||||
|
},
|
||||||
|
"keywords": keywords.split(", ")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def write_ai_prod_desc():
|
||||||
|
product_title, product_details, target_audience, brand_tone, description_length, keywords = display_inputs()
|
||||||
|
|
||||||
|
if st.button("Generate Product Description 🚀"):
|
||||||
|
with st.spinner("Generating description..."):
|
||||||
|
description = generate_product_description(
|
||||||
|
product_title,
|
||||||
|
product_details.split(", "), # Split details into a list
|
||||||
|
target_audience,
|
||||||
|
brand_tone,
|
||||||
|
description_length.split(" ")[0].lower(), # Extract length from selectbox
|
||||||
|
keywords
|
||||||
|
)
|
||||||
|
display_output(description)
|
||||||
75
ToBeMigrated/ai_writers/ai_story_illustrator/README.md
Normal file
75
ToBeMigrated/ai_writers/ai_story_illustrator/README.md
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
# AI Story Illustrator
|
||||||
|
|
||||||
|
The AI Story Illustrator is a powerful tool that generates beautiful illustrations for stories using Google's Gemini AI. This module allows users to input stories via text, file upload, or URL, and automatically generates appropriate illustrations for different scenes in the story.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- **Multiple Input Methods**: Input stories via direct text entry, file upload, or URL extraction
|
||||||
|
- **Intelligent Scene Segmentation**: Automatically divides stories into logical segments for illustration
|
||||||
|
- **Customizable Illustration Styles**: Choose from various artistic styles or define your own
|
||||||
|
- **Scene Element Extraction**: Analyzes story segments to identify key visual elements
|
||||||
|
- **Multiple Export Options**: Export as PDF storybook or ZIP archive of individual images
|
||||||
|
- **Customizable Aspect Ratios**: Support for different image dimensions (16:9, 4:3, 1:1)
|
||||||
|
- **Advanced Settings**: Control the number of segments to illustrate and other parameters
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
The Story Illustrator is integrated into the Alwrity platform and can be accessed through the main interface. The workflow consists of three main steps:
|
||||||
|
|
||||||
|
1. **Story Input**: Enter your story text, upload a file, or provide a URL
|
||||||
|
2. **Illustration Settings**: Configure the style, aspect ratio, and other parameters
|
||||||
|
3. **Generate & Export**: Generate illustrations for all or individual segments and export the results
|
||||||
|
|
||||||
|
## Technical Details
|
||||||
|
|
||||||
|
### Dependencies
|
||||||
|
|
||||||
|
- Streamlit: For the user interface
|
||||||
|
- Gemini AI: For image generation
|
||||||
|
- BeautifulSoup: For URL text extraction
|
||||||
|
- ReportLab: For PDF generation (optional)
|
||||||
|
- PIL: For image processing
|
||||||
|
|
||||||
|
### Key Functions
|
||||||
|
|
||||||
|
- `segment_story()`: Divides a story into logical segments for illustration
|
||||||
|
- `extract_scene_elements()`: Analyzes story segments to identify key visual elements
|
||||||
|
- `generate_illustration_prompt()`: Creates detailed prompts for the AI image generator
|
||||||
|
- `create_illustration()`: Generates an illustration for a story segment
|
||||||
|
- `create_storybook_pdf()`: Combines story text and illustrations into a PDF
|
||||||
|
- `create_zip_archive()`: Creates a ZIP archive of individual illustrations
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
```python
|
||||||
|
from lib.ai_writers.ai_story_illustrator.story_illustrator import write_story_illustrator
|
||||||
|
|
||||||
|
# Run the Story Illustrator app
|
||||||
|
write_story_illustrator()
|
||||||
|
```
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
- **Provide Clear Segments**: The system works best with stories that have clear scene transitions
|
||||||
|
- **Be Specific with Styles**: More specific style descriptions yield better results
|
||||||
|
- **Balance Text and Images**: For best results, aim for segments of 100-500 words per illustration
|
||||||
|
- **Review and Regenerate**: If an illustration doesn't capture the scene well, use the regenerate option
|
||||||
|
|
||||||
|
## Future Enhancements
|
||||||
|
|
||||||
|
- Support for more export formats (EPUB, HTML)
|
||||||
|
- Enhanced character consistency across illustrations
|
||||||
|
- Animation options for digital storytelling
|
||||||
|
- Voice narration integration
|
||||||
|
- Custom character design options
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
- If illustrations are not generating, check your internet connection and API access
|
||||||
|
- If PDF export fails, ensure ReportLab is installed (`pip install reportlab`)
|
||||||
|
- If URL extraction fails, try copying the text manually
|
||||||
|
- For large stories, consider processing in smaller batches
|
||||||
|
|
||||||
|
## Credits
|
||||||
|
|
||||||
|
This module uses Google's Gemini AI for image generation and leverages various open-source libraries for text processing and document generation.
|
||||||
7
ToBeMigrated/ai_writers/ai_story_illustrator/__init__.py
Normal file
7
ToBeMigrated/ai_writers/ai_story_illustrator/__init__.py
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
"""
|
||||||
|
AI Story Illustrator module for generating illustrations for stories using AI.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from .story_illustrator import write_story_illustrator
|
||||||
|
|
||||||
|
__all__ = ['write_story_illustrator']
|
||||||
@@ -0,0 +1,727 @@
|
|||||||
|
"""
|
||||||
|
AI Story Illustrator - Generate illustrations for stories using Gemini AI
|
||||||
|
|
||||||
|
This module provides functionality to generate illustrations for stories using Google's Gemini AI.
|
||||||
|
Users can input stories via text, file upload, or URL, and the system will generate appropriate
|
||||||
|
illustrations for different scenes in the story.
|
||||||
|
|
||||||
|
Based on: https://github.com/google-gemini/cookbook/blob/main/examples/Book_illustration.ipynb
|
||||||
|
"""
|
||||||
|
|
||||||
|
import streamlit as st
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import time
|
||||||
|
import tempfile
|
||||||
|
import requests
|
||||||
|
from pathlib import Path
|
||||||
|
import io
|
||||||
|
import base64
|
||||||
|
import json
|
||||||
|
import uuid
|
||||||
|
import logging
|
||||||
|
from urllib.parse import urlparse
|
||||||
|
from bs4 import BeautifulSoup
|
||||||
|
import zipfile
|
||||||
|
|
||||||
|
# Configure logging
|
||||||
|
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||||
|
logger = logging.getLogger('story_illustrator')
|
||||||
|
|
||||||
|
# Constants
|
||||||
|
MAX_STORY_LENGTH = 10000 # Maximum story length in characters
|
||||||
|
MIN_SEGMENT_LENGTH = 100 # Minimum segment length for illustration
|
||||||
|
MAX_SEGMENTS = 20 # Maximum number of segments to illustrate
|
||||||
|
DEFAULT_STYLE = "digital art" # Default illustration style
|
||||||
|
DEFAULT_ASPECT_RATIO = "16:9" # Default aspect ratio
|
||||||
|
|
||||||
|
|
||||||
|
def extract_text_from_url(url):
|
||||||
|
"""Extract text content from a URL."""
|
||||||
|
try:
|
||||||
|
headers = {
|
||||||
|
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
|
||||||
|
}
|
||||||
|
response = requests.get(url, headers=headers, timeout=10)
|
||||||
|
response.raise_for_status()
|
||||||
|
|
||||||
|
soup = BeautifulSoup(response.content, 'html.parser')
|
||||||
|
|
||||||
|
# Remove script and style elements
|
||||||
|
for script in soup(["script", "style"]):
|
||||||
|
script.extract()
|
||||||
|
|
||||||
|
# Get text
|
||||||
|
text = soup.get_text(separator='\\n')
|
||||||
|
|
||||||
|
# Break into lines and remove leading and trailing space on each
|
||||||
|
lines = (line.strip() for line in text.splitlines())
|
||||||
|
# Break multi-headlines into a line each
|
||||||
|
chunks = (phrase.strip() for line in lines for phrase in line.split(" "))
|
||||||
|
# Drop blank lines
|
||||||
|
text = '\\n'.join(chunk for chunk in chunks if chunk)
|
||||||
|
|
||||||
|
return text
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error extracting text from URL: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def segment_story(story_text, min_segment_length=MIN_SEGMENT_LENGTH, max_segments=MAX_SEGMENTS):
|
||||||
|
"""
|
||||||
|
Segment a story into logical parts for illustration.
|
||||||
|
Uses paragraph breaks, scene changes, and other indicators to create segments.
|
||||||
|
"""
|
||||||
|
# Clean up the text
|
||||||
|
story_text = story_text.strip()
|
||||||
|
|
||||||
|
# Split by paragraphs first
|
||||||
|
paragraphs = re.split(r'\\n\s*\\n', story_text)
|
||||||
|
|
||||||
|
# Initialize segments
|
||||||
|
segments = []
|
||||||
|
current_segment = ""
|
||||||
|
|
||||||
|
for paragraph in paragraphs:
|
||||||
|
# Skip empty paragraphs
|
||||||
|
if not paragraph.strip():
|
||||||
|
continue
|
||||||
|
|
||||||
|
# If adding this paragraph would make the segment too long, start a new segment
|
||||||
|
if len(current_segment) + len(paragraph) > 1000: # Limit segment size
|
||||||
|
if current_segment:
|
||||||
|
segments.append(current_segment.strip())
|
||||||
|
current_segment = paragraph
|
||||||
|
else:
|
||||||
|
# Add paragraph to current segment
|
||||||
|
if current_segment:
|
||||||
|
current_segment += "\\n\\n" + paragraph
|
||||||
|
else:
|
||||||
|
current_segment = paragraph
|
||||||
|
|
||||||
|
# Add the last segment if it exists
|
||||||
|
if current_segment:
|
||||||
|
segments.append(current_segment.strip())
|
||||||
|
|
||||||
|
# Combine very short segments
|
||||||
|
i = 0
|
||||||
|
while i < len(segments) - 1:
|
||||||
|
if len(segments[i]) < min_segment_length:
|
||||||
|
segments[i] += "\\n\\n" + segments[i+1]
|
||||||
|
segments.pop(i+1)
|
||||||
|
else:
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
# Limit the number of segments
|
||||||
|
if len(segments) > max_segments:
|
||||||
|
# Combine segments to reduce the total number
|
||||||
|
new_segments = []
|
||||||
|
segment_size = len(segments) / max_segments
|
||||||
|
|
||||||
|
for i in range(max_segments):
|
||||||
|
start_idx = int(i * segment_size)
|
||||||
|
end_idx = int((i + 1) * segment_size)
|
||||||
|
combined_segment = "\\n\\n".join(segments[start_idx:end_idx])
|
||||||
|
new_segments.append(combined_segment)
|
||||||
|
|
||||||
|
segments = new_segments
|
||||||
|
|
||||||
|
return segments
|
||||||
|
|
||||||
|
|
||||||
|
def extract_scene_elements(segment):
|
||||||
|
"""
|
||||||
|
Extract key scene elements from a story segment using LLM.
|
||||||
|
This helps create more accurate illustration prompts.
|
||||||
|
"""
|
||||||
|
from ...gpt_providers.text_generation.main_text_generation import llm_text_gen
|
||||||
|
|
||||||
|
prompt = f"""
|
||||||
|
Analyze the following story segment and extract key visual elements for an illustration:
|
||||||
|
|
||||||
|
{segment}
|
||||||
|
|
||||||
|
Please provide:
|
||||||
|
1. Main characters present (with brief visual descriptions)
|
||||||
|
2. Setting/location details
|
||||||
|
3. Key action or emotional moment to illustrate
|
||||||
|
4. Important objects or props
|
||||||
|
5. Time of day and lighting
|
||||||
|
6. Weather or atmospheric conditions (if applicable)
|
||||||
|
|
||||||
|
Format your response as JSON with these keys: "characters", "setting", "key_moment", "objects", "lighting", "atmosphere"
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = llm_text_gen(prompt)
|
||||||
|
|
||||||
|
# Try to extract JSON from the response
|
||||||
|
try:
|
||||||
|
# Find JSON content between triple backticks if present
|
||||||
|
json_match = re.search(r'```json\s*(.*?)\s*```', response, re.DOTALL)
|
||||||
|
if json_match:
|
||||||
|
json_str = json_match.group(1)
|
||||||
|
else:
|
||||||
|
# Otherwise try to parse the whole response as JSON
|
||||||
|
json_str = response
|
||||||
|
|
||||||
|
scene_elements = json.loads(json_str)
|
||||||
|
return scene_elements
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
# If JSON parsing fails, extract information using regex
|
||||||
|
characters = re.search(r'"characters":\s*"([^"]*)"', response)
|
||||||
|
setting = re.search(r'"setting":\s*"([^"]*)"', response)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"characters": characters.group(1) if characters else "",
|
||||||
|
"setting": setting.group(1) if setting else "",
|
||||||
|
"key_moment": "",
|
||||||
|
"objects": "",
|
||||||
|
"lighting": "",
|
||||||
|
"atmosphere": ""
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error extracting scene elements: {e}")
|
||||||
|
return {
|
||||||
|
"characters": "",
|
||||||
|
"setting": "",
|
||||||
|
"key_moment": "",
|
||||||
|
"objects": "",
|
||||||
|
"lighting": "",
|
||||||
|
"atmosphere": ""
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def generate_illustration_prompt(segment, style, characters=None, setting=None):
|
||||||
|
"""
|
||||||
|
Generate a prompt for the illustration based on the segment content.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
segment: The story segment to illustrate
|
||||||
|
style: The artistic style for the illustration
|
||||||
|
characters: Optional character descriptions
|
||||||
|
setting: Optional setting description
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A prompt string for the image generation model
|
||||||
|
"""
|
||||||
|
# Create a base prompt
|
||||||
|
base_prompt = f"""
|
||||||
|
Create a detailed illustration for the following story segment in {style} style:
|
||||||
|
|
||||||
|
{segment[:500]} # Limit segment length for prompt
|
||||||
|
|
||||||
|
The illustration should capture the key elements, mood, and action of this scene.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Add character information if provided
|
||||||
|
if characters:
|
||||||
|
base_prompt += f"\\n\\nThe main characters in this scene are: {characters}"
|
||||||
|
|
||||||
|
# Add setting information if provided
|
||||||
|
if setting:
|
||||||
|
base_prompt += f"\\n\\nThe setting is: {setting}"
|
||||||
|
|
||||||
|
# Add style-specific instructions
|
||||||
|
if "watercolor" in style.lower():
|
||||||
|
base_prompt += "\\n\\nUse soft, flowing watercolor techniques with visible brush strokes and color blending."
|
||||||
|
elif "digital art" in style.lower():
|
||||||
|
base_prompt += "\\n\\nCreate a polished digital illustration with clean lines and vibrant colors."
|
||||||
|
elif "pencil sketch" in style.lower():
|
||||||
|
base_prompt += "\\n\\nUse pencil sketch techniques with visible hatching, shading, and line work."
|
||||||
|
|
||||||
|
# Add final quality instructions
|
||||||
|
base_prompt += """
|
||||||
|
|
||||||
|
Make the illustration:
|
||||||
|
- Visually engaging and detailed
|
||||||
|
- Appropriate for a storybook
|
||||||
|
- Focused on the main action or emotion of the scene
|
||||||
|
- With good composition and visual storytelling
|
||||||
|
"""
|
||||||
|
|
||||||
|
return base_prompt.strip()
|
||||||
|
|
||||||
|
|
||||||
|
def create_illustration(segment, style, aspect_ratio="16:9"):
|
||||||
|
"""
|
||||||
|
Create an illustration for a story segment.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
segment: The story segment to illustrate
|
||||||
|
style: The artistic style for the illustration
|
||||||
|
aspect_ratio: The aspect ratio for the illustration
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Path to the generated image
|
||||||
|
"""
|
||||||
|
# Import here to avoid circular imports
|
||||||
|
from ...gpt_providers.text_to_image_generation.gen_gemini_images import generate_gemini_image
|
||||||
|
|
||||||
|
# Extract scene elements to enhance the prompt
|
||||||
|
scene_elements = extract_scene_elements(segment)
|
||||||
|
|
||||||
|
# Create a detailed prompt for the illustration
|
||||||
|
prompt = generate_illustration_prompt(
|
||||||
|
segment,
|
||||||
|
style,
|
||||||
|
characters=scene_elements.get("characters", ""),
|
||||||
|
setting=scene_elements.get("setting", "")
|
||||||
|
)
|
||||||
|
|
||||||
|
# Add key elements to the prompt
|
||||||
|
key_moment = scene_elements.get("key_moment", "")
|
||||||
|
objects = scene_elements.get("objects", "")
|
||||||
|
lighting = scene_elements.get("lighting", "")
|
||||||
|
atmosphere = scene_elements.get("atmosphere", "")
|
||||||
|
|
||||||
|
if key_moment:
|
||||||
|
prompt += f"\\n\\nFocus on this key moment: {key_moment}"
|
||||||
|
|
||||||
|
if objects:
|
||||||
|
prompt += f"\\n\\nInclude these important objects: {objects}"
|
||||||
|
|
||||||
|
if lighting:
|
||||||
|
prompt += f"\\n\\nThe lighting is: {lighting}"
|
||||||
|
|
||||||
|
if atmosphere:
|
||||||
|
prompt += f"\\n\\nThe atmosphere/weather is: {atmosphere}"
|
||||||
|
|
||||||
|
# Generate the illustration
|
||||||
|
try:
|
||||||
|
# Parse aspect ratio
|
||||||
|
if aspect_ratio == "16:9":
|
||||||
|
width, height = 16, 9
|
||||||
|
elif aspect_ratio == "4:3":
|
||||||
|
width, height = 4, 3
|
||||||
|
elif aspect_ratio == "1:1":
|
||||||
|
width, height = 1, 1
|
||||||
|
else:
|
||||||
|
width, height = 16, 9 # Default
|
||||||
|
|
||||||
|
# Generate image using Gemini
|
||||||
|
image_path = generate_gemini_image(
|
||||||
|
prompt=prompt,
|
||||||
|
style=style.lower() if style else None,
|
||||||
|
aspect_ratio=aspect_ratio
|
||||||
|
)
|
||||||
|
|
||||||
|
return image_path
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error creating illustration: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def create_storybook_pdf(segments, illustrations, title, author, output_path):
|
||||||
|
"""
|
||||||
|
Create a PDF storybook with text and illustrations.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
segments: List of story segments
|
||||||
|
illustrations: List of paths to illustrations
|
||||||
|
title: Book title
|
||||||
|
author: Book author
|
||||||
|
output_path: Path to save the PDF
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Path to the created PDF
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
from reportlab.lib.pagesizes import letter, A4
|
||||||
|
from reportlab.lib import colors
|
||||||
|
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Image as ReportLabImage, PageBreak
|
||||||
|
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
|
||||||
|
from reportlab.lib.units import inch
|
||||||
|
|
||||||
|
# Create a PDF document
|
||||||
|
doc = SimpleDocTemplate(output_path, pagesize=A4)
|
||||||
|
story = []
|
||||||
|
|
||||||
|
# Get styles
|
||||||
|
styles = getSampleStyleSheet()
|
||||||
|
title_style = styles['Title']
|
||||||
|
author_style = styles['Normal']
|
||||||
|
author_style.alignment = 1 # Center alignment
|
||||||
|
normal_style = styles['Normal']
|
||||||
|
|
||||||
|
# Add title page
|
||||||
|
story.append(Paragraph(title, title_style))
|
||||||
|
story.append(Spacer(1, 0.5*inch))
|
||||||
|
story.append(Paragraph(f"by {author}", author_style))
|
||||||
|
story.append(PageBreak())
|
||||||
|
|
||||||
|
# Add content pages
|
||||||
|
for i, (segment, illustration_path) in enumerate(zip(segments, illustrations)):
|
||||||
|
if illustration_path and os.path.exists(illustration_path):
|
||||||
|
# Add illustration
|
||||||
|
img = ReportLabImage(illustration_path, width=6*inch, height=4*inch)
|
||||||
|
story.append(img)
|
||||||
|
story.append(Spacer(1, 0.25*inch))
|
||||||
|
|
||||||
|
# Add text
|
||||||
|
for paragraph in segment.split('\\n\\n'):
|
||||||
|
if paragraph.strip():
|
||||||
|
story.append(Paragraph(paragraph, normal_style))
|
||||||
|
story.append(Spacer(1, 0.1*inch))
|
||||||
|
|
||||||
|
# Add page break between segments
|
||||||
|
if i < len(segments) - 1:
|
||||||
|
story.append(PageBreak())
|
||||||
|
|
||||||
|
# Build the PDF
|
||||||
|
doc.build(story)
|
||||||
|
return output_path
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error creating PDF: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def create_zip_archive(files, output_path):
|
||||||
|
"""
|
||||||
|
Create a ZIP archive containing the provided files.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
files: Dictionary of {filename: file_path} to include in the archive
|
||||||
|
output_path: Path to save the ZIP file
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Path to the created ZIP file
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
with zipfile.ZipFile(output_path, 'w') as zipf:
|
||||||
|
for filename, file_path in files.items():
|
||||||
|
if os.path.exists(file_path):
|
||||||
|
zipf.write(file_path, arcname=filename)
|
||||||
|
return output_path
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error creating ZIP archive: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def write_story_illustrator():
|
||||||
|
"""Main function for the Story Illustrator Streamlit app."""
|
||||||
|
st.title("AI Story Illustrator")
|
||||||
|
st.write("Generate beautiful illustrations for your stories using AI")
|
||||||
|
|
||||||
|
# Create tabs for different sections
|
||||||
|
tab1, tab2, tab3 = st.tabs(["Story Input", "Illustration Settings", "Generate & Export"])
|
||||||
|
|
||||||
|
# Initialize session state variables if they don't exist
|
||||||
|
if "story_text" not in st.session_state:
|
||||||
|
st.session_state.story_text = ""
|
||||||
|
if "segments" not in st.session_state:
|
||||||
|
st.session_state.segments = []
|
||||||
|
if "illustrations" not in st.session_state:
|
||||||
|
st.session_state.illustrations = []
|
||||||
|
if "book_title" not in st.session_state:
|
||||||
|
st.session_state.book_title = ""
|
||||||
|
if "book_author" not in st.session_state:
|
||||||
|
st.session_state.book_author = ""
|
||||||
|
if "illustration_style" not in st.session_state:
|
||||||
|
st.session_state.illustration_style = DEFAULT_STYLE
|
||||||
|
if "aspect_ratio" not in st.session_state:
|
||||||
|
st.session_state.aspect_ratio = DEFAULT_ASPECT_RATIO
|
||||||
|
if "temp_files" not in st.session_state:
|
||||||
|
st.session_state.temp_files = []
|
||||||
|
|
||||||
|
# Tab 1: Story Input
|
||||||
|
with tab1:
|
||||||
|
st.header("Step 1: Input Your Story")
|
||||||
|
|
||||||
|
# Input method selection
|
||||||
|
input_method = st.radio(
|
||||||
|
"Choose input method:",
|
||||||
|
["Text Input", "File Upload", "URL"]
|
||||||
|
)
|
||||||
|
|
||||||
|
if input_method == "Text Input":
|
||||||
|
st.session_state.story_text = st.text_area(
|
||||||
|
"Enter your story text:",
|
||||||
|
value=st.session_state.story_text,
|
||||||
|
height=300,
|
||||||
|
max_chars=MAX_STORY_LENGTH,
|
||||||
|
help="Enter the story text you want to illustrate (max 10,000 characters)"
|
||||||
|
)
|
||||||
|
|
||||||
|
elif input_method == "File Upload":
|
||||||
|
uploaded_file = st.file_uploader("Upload a text file:", type=["txt", "md"])
|
||||||
|
if uploaded_file is not None:
|
||||||
|
try:
|
||||||
|
st.session_state.story_text = uploaded_file.getvalue().decode("utf-8")
|
||||||
|
st.success(f"Successfully loaded file: {uploaded_file.name}")
|
||||||
|
st.text_area("Preview:", value=st.session_state.story_text[:500] + "...", height=200, disabled=True)
|
||||||
|
except Exception as e:
|
||||||
|
st.error(f"Error reading file: {e}")
|
||||||
|
|
||||||
|
elif input_method == "URL":
|
||||||
|
url = st.text_input("Enter URL containing the story:")
|
||||||
|
if url:
|
||||||
|
if st.button("Extract Text from URL"):
|
||||||
|
with st.spinner("Extracting text from URL..."):
|
||||||
|
extracted_text = extract_text_from_url(url)
|
||||||
|
if extracted_text:
|
||||||
|
st.session_state.story_text = extracted_text
|
||||||
|
st.success("Successfully extracted text from URL")
|
||||||
|
st.text_area("Preview:", value=st.session_state.story_text[:500] + "...", height=200, disabled=True)
|
||||||
|
else:
|
||||||
|
st.error("Failed to extract text from URL")
|
||||||
|
|
||||||
|
# Book metadata
|
||||||
|
st.subheader("Book Metadata")
|
||||||
|
col1, col2 = st.columns(2)
|
||||||
|
with col1:
|
||||||
|
st.session_state.book_title = st.text_input("Book Title:", value=st.session_state.book_title)
|
||||||
|
with col2:
|
||||||
|
st.session_state.book_author = st.text_input("Author:", value=st.session_state.book_author)
|
||||||
|
|
||||||
|
# Process story into segments
|
||||||
|
if st.session_state.story_text:
|
||||||
|
if st.button("Process Story into Segments"):
|
||||||
|
with st.spinner("Processing story into segments..."):
|
||||||
|
st.session_state.segments = segment_story(st.session_state.story_text)
|
||||||
|
st.success(f"Story processed into {len(st.session_state.segments)} segments")
|
||||||
|
|
||||||
|
# Initialize illustrations list with None values
|
||||||
|
st.session_state.illustrations = [None] * len(st.session_state.segments)
|
||||||
|
|
||||||
|
# Display segments
|
||||||
|
st.subheader("Story Segments")
|
||||||
|
for i, segment in enumerate(st.session_state.segments):
|
||||||
|
with st.expander(f"Segment {i+1}"):
|
||||||
|
st.write(segment)
|
||||||
|
|
||||||
|
# Tab 2: Illustration Settings
|
||||||
|
with tab2:
|
||||||
|
st.header("Step 2: Configure Illustration Settings")
|
||||||
|
|
||||||
|
# Style selection
|
||||||
|
st.subheader("Illustration Style")
|
||||||
|
style_options = [
|
||||||
|
"Digital Art",
|
||||||
|
"Watercolor Painting",
|
||||||
|
"Pencil Sketch",
|
||||||
|
"Oil Painting",
|
||||||
|
"Cartoon",
|
||||||
|
"Anime",
|
||||||
|
"3D Render",
|
||||||
|
"Pixel Art",
|
||||||
|
"Children's Book Illustration",
|
||||||
|
"Comic Book Style",
|
||||||
|
"Fantasy Art",
|
||||||
|
"Realistic"
|
||||||
|
]
|
||||||
|
|
||||||
|
st.session_state.illustration_style = st.selectbox(
|
||||||
|
"Choose an illustration style:",
|
||||||
|
style_options,
|
||||||
|
index=style_options.index(st.session_state.illustration_style) if st.session_state.illustration_style in style_options else 0
|
||||||
|
)
|
||||||
|
|
||||||
|
# Custom style input
|
||||||
|
use_custom_style = st.checkbox("Use custom style")
|
||||||
|
if use_custom_style:
|
||||||
|
custom_style = st.text_input("Describe your custom style:",
|
||||||
|
placeholder="e.g., Impressionist painting with vibrant colors and visible brushstrokes")
|
||||||
|
if custom_style:
|
||||||
|
st.session_state.illustration_style = custom_style
|
||||||
|
|
||||||
|
# Display style examples
|
||||||
|
st.info("💡 The style you choose will significantly impact the look and feel of your illustrations.")
|
||||||
|
|
||||||
|
# Aspect ratio selection
|
||||||
|
st.subheader("Image Settings")
|
||||||
|
aspect_ratio_options = {
|
||||||
|
"16:9 (Widescreen)": "16:9",
|
||||||
|
"4:3 (Standard)": "4:3",
|
||||||
|
"1:1 (Square)": "1:1"
|
||||||
|
}
|
||||||
|
|
||||||
|
selected_ratio = st.selectbox(
|
||||||
|
"Choose aspect ratio:",
|
||||||
|
list(aspect_ratio_options.keys()),
|
||||||
|
index=list(aspect_ratio_options.values()).index(st.session_state.aspect_ratio) if st.session_state.aspect_ratio in aspect_ratio_options.values() else 0
|
||||||
|
)
|
||||||
|
st.session_state.aspect_ratio = aspect_ratio_options[selected_ratio]
|
||||||
|
|
||||||
|
# Advanced settings
|
||||||
|
with st.expander("Advanced Settings"):
|
||||||
|
st.slider("Number of segments to illustrate:", 1,
|
||||||
|
max(len(st.session_state.segments), 1) if st.session_state.segments else 1,
|
||||||
|
min(len(st.session_state.segments), MAX_SEGMENTS) if st.session_state.segments else 1,
|
||||||
|
key="num_segments_to_illustrate")
|
||||||
|
|
||||||
|
st.checkbox("Generate cover image", value=True, key="generate_cover")
|
||||||
|
|
||||||
|
st.checkbox("Add text to illustrations", value=False, key="add_text_to_illustrations")
|
||||||
|
|
||||||
|
# Tab 3: Generate & Export
|
||||||
|
with tab3:
|
||||||
|
st.header("Step 3: Generate Illustrations & Export")
|
||||||
|
|
||||||
|
if not st.session_state.segments:
|
||||||
|
st.warning("Please process your story into segments in Step 1 before generating illustrations.")
|
||||||
|
else:
|
||||||
|
# Generate illustrations
|
||||||
|
st.subheader("Generate Illustrations")
|
||||||
|
|
||||||
|
num_segments = min(len(st.session_state.segments), st.session_state.get("num_segments_to_illustrate", len(st.session_state.segments)))
|
||||||
|
|
||||||
|
if st.button("Generate All Illustrations"):
|
||||||
|
with st.spinner(f"Generating {num_segments} illustrations... This may take a while."):
|
||||||
|
progress_bar = st.progress(0)
|
||||||
|
|
||||||
|
for i in range(num_segments):
|
||||||
|
# Update progress
|
||||||
|
progress_bar.progress((i) / num_segments)
|
||||||
|
st.write(f"Generating illustration {i+1} of {num_segments}...")
|
||||||
|
|
||||||
|
# Generate illustration
|
||||||
|
illustration_path = create_illustration(
|
||||||
|
st.session_state.segments[i],
|
||||||
|
st.session_state.illustration_style,
|
||||||
|
st.session_state.aspect_ratio
|
||||||
|
)
|
||||||
|
|
||||||
|
# Store the illustration path
|
||||||
|
if illustration_path:
|
||||||
|
st.session_state.illustrations[i] = illustration_path
|
||||||
|
st.session_state.temp_files.append(illustration_path)
|
||||||
|
|
||||||
|
# Complete progress
|
||||||
|
progress_bar.progress(1.0)
|
||||||
|
st.success(f"Generated {num_segments} illustrations!")
|
||||||
|
|
||||||
|
# Generate individual illustrations
|
||||||
|
st.subheader("Generate Individual Illustrations")
|
||||||
|
|
||||||
|
for i in range(num_segments):
|
||||||
|
col1, col2 = st.columns([3, 1])
|
||||||
|
|
||||||
|
with col1:
|
||||||
|
with st.expander(f"Segment {i+1}"):
|
||||||
|
st.write(st.session_state.segments[i][:300] + "..." if len(st.session_state.segments[i]) > 300 else st.session_state.segments[i])
|
||||||
|
|
||||||
|
with col2:
|
||||||
|
if st.button(f"Generate #{i+1}", key=f"gen_btn_{i}"):
|
||||||
|
with st.spinner(f"Generating illustration {i+1}..."):
|
||||||
|
illustration_path = create_illustration(
|
||||||
|
st.session_state.segments[i],
|
||||||
|
st.session_state.illustration_style,
|
||||||
|
st.session_state.aspect_ratio
|
||||||
|
)
|
||||||
|
|
||||||
|
if illustration_path:
|
||||||
|
st.session_state.illustrations[i] = illustration_path
|
||||||
|
st.session_state.temp_files.append(illustration_path)
|
||||||
|
st.success(f"Generated illustration {i+1}!")
|
||||||
|
|
||||||
|
# Display generated illustrations
|
||||||
|
st.subheader("Preview Illustrations")
|
||||||
|
|
||||||
|
if any(st.session_state.illustrations):
|
||||||
|
for i, illustration_path in enumerate(st.session_state.illustrations[:num_segments]):
|
||||||
|
if illustration_path and os.path.exists(illustration_path):
|
||||||
|
with st.expander(f"Illustration {i+1}"):
|
||||||
|
st.image(illustration_path, caption=f"Illustration for Segment {i+1}", use_column_width=True)
|
||||||
|
|
||||||
|
# Regenerate button
|
||||||
|
if st.button(f"Regenerate", key=f"regen_btn_{i}"):
|
||||||
|
with st.spinner(f"Regenerating illustration {i+1}..."):
|
||||||
|
new_illustration_path = create_illustration(
|
||||||
|
st.session_state.segments[i],
|
||||||
|
st.session_state.illustration_style,
|
||||||
|
st.session_state.aspect_ratio
|
||||||
|
)
|
||||||
|
|
||||||
|
if new_illustration_path:
|
||||||
|
st.session_state.illustrations[i] = new_illustration_path
|
||||||
|
st.session_state.temp_files.append(new_illustration_path)
|
||||||
|
st.rerun()
|
||||||
|
else:
|
||||||
|
st.info("No illustrations generated yet. Click 'Generate All Illustrations' or generate individual illustrations.")
|
||||||
|
|
||||||
|
# Export options
|
||||||
|
st.subheader("Export Options")
|
||||||
|
|
||||||
|
if any(st.session_state.illustrations):
|
||||||
|
export_format = st.radio(
|
||||||
|
"Export format:",
|
||||||
|
["PDF Storybook", "Individual Images (ZIP)", "Both"]
|
||||||
|
)
|
||||||
|
|
||||||
|
if st.button("Export"):
|
||||||
|
with st.spinner("Preparing export..."):
|
||||||
|
# Create temporary directory for exports
|
||||||
|
with tempfile.TemporaryDirectory() as temp_dir:
|
||||||
|
# Filter out None values from illustrations
|
||||||
|
valid_illustrations = [path for path in st.session_state.illustrations[:num_segments] if path and os.path.exists(path)]
|
||||||
|
valid_segments = st.session_state.segments[:len(valid_illustrations)]
|
||||||
|
|
||||||
|
# Prepare filenames
|
||||||
|
safe_title = "".join(c if c.isalnum() else "_" for c in st.session_state.book_title) if st.session_state.book_title else "story"
|
||||||
|
timestamp = int(time.time())
|
||||||
|
|
||||||
|
# Export as PDF
|
||||||
|
if export_format in ["PDF Storybook", "Both"]:
|
||||||
|
pdf_path = os.path.join(temp_dir, f"{safe_title}_{timestamp}.pdf")
|
||||||
|
|
||||||
|
try:
|
||||||
|
pdf_result = create_storybook_pdf(
|
||||||
|
valid_segments,
|
||||||
|
valid_illustrations,
|
||||||
|
st.session_state.book_title or "Untitled Story",
|
||||||
|
st.session_state.book_author or "Anonymous",
|
||||||
|
pdf_path
|
||||||
|
)
|
||||||
|
|
||||||
|
if pdf_result:
|
||||||
|
with open(pdf_path, "rb") as f:
|
||||||
|
st.download_button(
|
||||||
|
label="Download PDF Storybook",
|
||||||
|
data=f,
|
||||||
|
file_name=f"{safe_title}.pdf",
|
||||||
|
mime="application/pdf"
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
st.error(f"Error creating PDF: {e}")
|
||||||
|
st.info("Please install ReportLab to enable PDF export: pip install reportlab")
|
||||||
|
|
||||||
|
# Export as ZIP of images
|
||||||
|
if export_format in ["Individual Images (ZIP)", "Both"]:
|
||||||
|
zip_path = os.path.join(temp_dir, f"{safe_title}_illustrations_{timestamp}.zip")
|
||||||
|
|
||||||
|
# Prepare files for ZIP
|
||||||
|
files_to_zip = {}
|
||||||
|
for i, img_path in enumerate(valid_illustrations):
|
||||||
|
if img_path and os.path.exists(img_path):
|
||||||
|
files_to_zip[f"illustration_{i+1}.png"] = img_path
|
||||||
|
|
||||||
|
zip_result = create_zip_archive(files_to_zip, zip_path)
|
||||||
|
|
||||||
|
if zip_result:
|
||||||
|
with open(zip_path, "rb") as f:
|
||||||
|
st.download_button(
|
||||||
|
label="Download Illustrations ZIP",
|
||||||
|
data=f,
|
||||||
|
file_name=f"{safe_title}_illustrations.zip",
|
||||||
|
mime="application/zip"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
st.info("Generate illustrations before exporting.")
|
||||||
|
|
||||||
|
# Cleanup temporary files when the session ends
|
||||||
|
def cleanup_temp_files():
|
||||||
|
for file_path in st.session_state.temp_files:
|
||||||
|
try:
|
||||||
|
if file_path and os.path.exists(file_path):
|
||||||
|
os.remove(file_path)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error removing temporary file {file_path}: {e}")
|
||||||
|
|
||||||
|
# Register the cleanup function to run when the session ends
|
||||||
|
import atexit
|
||||||
|
atexit.register(cleanup_temp_files)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
write_story_illustrator()
|
||||||
450
ToBeMigrated/ai_writers/ai_story_illustrator/utils.py
Normal file
450
ToBeMigrated/ai_writers/ai_story_illustrator/utils.py
Normal file
@@ -0,0 +1,450 @@
|
|||||||
|
"""
|
||||||
|
Utility functions for the AI Story Illustrator module.
|
||||||
|
|
||||||
|
This module provides helper functions for file operations, string manipulation,
|
||||||
|
and simple text analysis relevant to story processing.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import tempfile
|
||||||
|
import uuid
|
||||||
|
import logging
|
||||||
|
import shutil
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import List, Tuple, Optional, Union
|
||||||
|
|
||||||
|
# Attempt to import Pillow for image dimensions, but don't fail if not installed
|
||||||
|
# unless the specific function is called.
|
||||||
|
try:
|
||||||
|
from PIL import Image
|
||||||
|
_PIL_AVAILABLE = True
|
||||||
|
except ImportError:
|
||||||
|
_PIL_AVAILABLE = False
|
||||||
|
|
||||||
|
# Configure logging
|
||||||
|
logging.basicConfig(
|
||||||
|
level=logging.INFO,
|
||||||
|
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
||||||
|
)
|
||||||
|
logger = logging.getLogger('story_illustrator_utils')
|
||||||
|
|
||||||
|
# --- Constants ---
|
||||||
|
IMAGE_EXTENSIONS = frozenset(['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp'])
|
||||||
|
TEXT_EXTENSIONS = frozenset(['.txt', '.md', '.text'])
|
||||||
|
# Common English words that often start sentences, excluded from simple name detection
|
||||||
|
COMMON_START_WORDS = frozenset([
|
||||||
|
'The', 'A', 'An', 'And', 'But', 'Or', 'For', 'Nor', 'So', 'Yet', 'He', 'She',
|
||||||
|
'It', 'They', 'We', 'You', 'I', 'In', 'On', 'At', 'To', 'From', 'With',
|
||||||
|
'About', 'As', 'Is', 'Was', 'Were', 'Be', 'Been', 'Being', 'Have', 'Has',
|
||||||
|
'Had', 'Do', 'Does', 'Did', 'Will', 'Would', 'Shall', 'Should', 'May',
|
||||||
|
'Might', 'Must', 'Can', 'Could'
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
|
# --- File/Directory Operations ---
|
||||||
|
|
||||||
|
def create_temp_directory(prefix: str = "story_illustrator_") -> str:
|
||||||
|
"""
|
||||||
|
Creates a temporary directory using tempfile.mkdtemp.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
prefix: A prefix for the temporary directory name.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The absolute path to the created temporary directory.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
temp_dir = tempfile.mkdtemp(prefix=prefix)
|
||||||
|
logger.info(f"Created temporary directory: {temp_dir}")
|
||||||
|
return temp_dir
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to create temporary directory: {e}", exc_info=True)
|
||||||
|
raise # Re-raise the exception after logging
|
||||||
|
|
||||||
|
|
||||||
|
def sanitize_filename(filename: str) -> str:
|
||||||
|
"""
|
||||||
|
Sanitizes a filename by removing/replacing invalid characters for common filesystems.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
filename: The original filename string.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A sanitized filename string suitable for use in file paths.
|
||||||
|
"""
|
||||||
|
if not isinstance(filename, str):
|
||||||
|
logger.warning("sanitize_filename received non-string input, converting.")
|
||||||
|
filename = str(filename)
|
||||||
|
|
||||||
|
# Remove characters invalid for Windows/Unix filenames
|
||||||
|
# Replace them with an underscore.
|
||||||
|
sanitized = re.sub(r'[\\/*?:"<>|\']', "_", filename)
|
||||||
|
# Replace consecutive underscores/spaces with a single underscore
|
||||||
|
sanitized = re.sub(r'[_ ]+', '_', sanitized)
|
||||||
|
# Remove leading/trailing spaces, dots, and underscores
|
||||||
|
sanitized = sanitized.strip("._ ")
|
||||||
|
|
||||||
|
# Ensure the filename is not empty after sanitization
|
||||||
|
if not sanitized:
|
||||||
|
sanitized = "unnamed_file"
|
||||||
|
logger.warning("Filename was empty after sanitization, using default.")
|
||||||
|
|
||||||
|
# Limit filename length (optional, adjust as needed)
|
||||||
|
# max_len = 255 # Example limit
|
||||||
|
# if len(sanitized) > max_len:
|
||||||
|
# name, ext = os.path.splitext(sanitized)
|
||||||
|
# sanitized = name[:max_len - len(ext) - 1] + "_" + ext
|
||||||
|
# logger.warning(f"Filename truncated to maximum length: {sanitized}")
|
||||||
|
|
||||||
|
return sanitized
|
||||||
|
|
||||||
|
|
||||||
|
def get_temp_file_path(
|
||||||
|
directory: str, prefix: str = "file_", suffix: str = ".tmp"
|
||||||
|
) -> str:
|
||||||
|
"""
|
||||||
|
Generates a unique temporary file path within the specified directory.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
directory: The directory where the temporary file should be located.
|
||||||
|
prefix: A prefix for the filename.
|
||||||
|
suffix: A suffix (extension) for the filename.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The full path for the unique temporary file.
|
||||||
|
"""
|
||||||
|
# Ensure suffix starts with a dot if it's meant to be an extension
|
||||||
|
if suffix and not suffix.startswith("."):
|
||||||
|
suffix = "." + suffix
|
||||||
|
|
||||||
|
unique_id = uuid.uuid4().hex[:12] # Longer hex UUID for better uniqueness
|
||||||
|
filename = f"{prefix}{unique_id}{suffix}"
|
||||||
|
return os.path.join(directory, filename)
|
||||||
|
|
||||||
|
|
||||||
|
def ensure_directory_exists(directory: Union[str, Path]) -> str:
|
||||||
|
"""
|
||||||
|
Ensures that a directory exists, creating it recursively if necessary.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
directory: The path to the directory (string or Path object).
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The absolute path to the directory as a string.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
OSError: If the directory cannot be created (e.g., permission issues).
|
||||||
|
"""
|
||||||
|
dir_path = Path(directory).resolve() # Use Pathlib for robust handling
|
||||||
|
try:
|
||||||
|
dir_path.mkdir(parents=True, exist_ok=True)
|
||||||
|
# Log only if it needed creation (or if verbose logging is on)
|
||||||
|
# logger.info(f"Ensured directory exists: {dir_path}")
|
||||||
|
return str(dir_path)
|
||||||
|
except OSError as e:
|
||||||
|
logger.error(f"Failed to create or access directory {dir_path}: {e}", exc_info=True)
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
def cleanup_directory(directory: Union[str, Path]) -> None:
|
||||||
|
"""
|
||||||
|
Removes a directory and all its contents recursively. Handles errors gracefully.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
directory: The path to the directory to remove (string or Path object).
|
||||||
|
"""
|
||||||
|
dir_path = Path(directory)
|
||||||
|
if not dir_path.exists():
|
||||||
|
logger.debug(f"Cleanup skipped: Directory '{directory}' does not exist.")
|
||||||
|
return
|
||||||
|
|
||||||
|
if not dir_path.is_dir():
|
||||||
|
logger.warning(f"Cleanup warning: Path '{directory}' is not a directory.")
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
shutil.rmtree(dir_path)
|
||||||
|
logger.info(f"Successfully removed directory: {directory}")
|
||||||
|
except OSError as e:
|
||||||
|
logger.error(f"Error removing directory {directory}: {e}", exc_info=True)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(
|
||||||
|
f"Unexpected error removing directory {directory}: {e}", exc_info=True
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# --- File Type Checks ---
|
||||||
|
|
||||||
|
def get_file_extension(file_path: Union[str, Path]) -> str:
|
||||||
|
"""
|
||||||
|
Gets the lowercased file extension (including the dot) from a file path.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
file_path: The path to the file (string or Path object).
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The file extension (e.g., '.txt', '.png') or an empty string if no extension.
|
||||||
|
"""
|
||||||
|
return Path(file_path).suffix.lower()
|
||||||
|
|
||||||
|
|
||||||
|
def is_image_file(file_path: Union[str, Path]) -> bool:
|
||||||
|
"""
|
||||||
|
Checks if a file is likely an image based on its extension.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
file_path: The path to the file (string or Path object).
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True if the file extension is in IMAGE_EXTENSIONS, False otherwise.
|
||||||
|
"""
|
||||||
|
return get_file_extension(file_path) in IMAGE_EXTENSIONS
|
||||||
|
|
||||||
|
|
||||||
|
def is_text_file(file_path: Union[str, Path]) -> bool:
|
||||||
|
"""
|
||||||
|
Checks if a file is likely a text file based on its extension.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
file_path: The path to the file (string or Path object).
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True if the file extension is in TEXT_EXTENSIONS, False otherwise.
|
||||||
|
"""
|
||||||
|
return get_file_extension(file_path) in TEXT_EXTENSIONS
|
||||||
|
|
||||||
|
|
||||||
|
# --- Text Analysis (Simple Heuristics) ---
|
||||||
|
|
||||||
|
def extract_story_title_from_text(text: str) -> str:
|
||||||
|
"""
|
||||||
|
Attempts to extract a title from story text using simple heuristics.
|
||||||
|
|
||||||
|
Looks for patterns (in order):
|
||||||
|
1. Markdown headers (#, ##, etc.) at the start of a line.
|
||||||
|
2. The first non-empty line if it's short (< 100 chars) and followed by
|
||||||
|
a blank line or is the only line.
|
||||||
|
3. The first non-empty line if it's entirely in uppercase (< 100 chars).
|
||||||
|
|
||||||
|
Args:
|
||||||
|
text: The story text content.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
An extracted title string, or "Untitled Story" if no pattern matches.
|
||||||
|
"""
|
||||||
|
if not isinstance(text, str) or not text.strip():
|
||||||
|
return "Untitled Story"
|
||||||
|
|
||||||
|
# 1. Check for markdown headers ( # Title, ## Title )
|
||||||
|
# Needs to match start of line (^) with optional whitespace before #
|
||||||
|
header_match = re.search(r'^\s*#+\s+(.+)$', text.strip(), re.MULTILINE)
|
||||||
|
if header_match:
|
||||||
|
title = header_match.group(1).strip()
|
||||||
|
if title: return title
|
||||||
|
|
||||||
|
lines = text.strip().split('\n')
|
||||||
|
if not lines:
|
||||||
|
return "Untitled Story"
|
||||||
|
|
||||||
|
first_line = lines[0].strip()
|
||||||
|
if not first_line: # Skip if first line is blank
|
||||||
|
if len(lines) > 1:
|
||||||
|
first_line = lines[1].strip() # Try second line
|
||||||
|
else:
|
||||||
|
return "Untitled Story"
|
||||||
|
|
||||||
|
if not first_line: # Still no title found
|
||||||
|
return "Untitled Story"
|
||||||
|
|
||||||
|
# 2. Check if first line is short and potentially a title
|
||||||
|
is_short = len(first_line) < 100
|
||||||
|
is_followed_by_blank = len(lines) > 1 and not lines[1].strip()
|
||||||
|
is_only_line = len(lines) == 1
|
||||||
|
|
||||||
|
if is_short and (is_followed_by_blank or is_only_line):
|
||||||
|
return first_line
|
||||||
|
|
||||||
|
# 3. Check if first line is all caps (and short)
|
||||||
|
is_all_caps = first_line == first_line.upper() and first_line.isalpha() # Check if it contains letters
|
||||||
|
if is_short and is_all_caps:
|
||||||
|
return first_line
|
||||||
|
|
||||||
|
# Default if no other pattern matched
|
||||||
|
return "Untitled Story"
|
||||||
|
|
||||||
|
|
||||||
|
def estimate_reading_time(text: str, words_per_minute: int = 200) -> float:
|
||||||
|
"""
|
||||||
|
Estimates the reading time of a text in minutes.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
text: The text content.
|
||||||
|
words_per_minute: The assumed average reading speed.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The estimated reading time in minutes. Returns 0.0 for empty text.
|
||||||
|
"""
|
||||||
|
if not isinstance(text, str) or not text.strip():
|
||||||
|
return 0.0
|
||||||
|
if words_per_minute <= 0:
|
||||||
|
raise ValueError("words_per_minute must be positive.")
|
||||||
|
|
||||||
|
word_count = len(text.split())
|
||||||
|
minutes = word_count / words_per_minute
|
||||||
|
return minutes
|
||||||
|
|
||||||
|
|
||||||
|
def count_sentences(text: str) -> int:
|
||||||
|
"""
|
||||||
|
Counts the number of sentences in a text using a very simple heuristic.
|
||||||
|
|
||||||
|
Note: This is a basic implementation counting sentence-ending punctuation
|
||||||
|
(. ! ?). It will be inaccurate with abbreviations (Mr., Mrs., etc.),
|
||||||
|
ellipses, and complex sentence structures.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
text: The text content.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
An estimated count of sentences. Returns 0 for empty text.
|
||||||
|
"""
|
||||||
|
if not isinstance(text, str) or not text.strip():
|
||||||
|
return 0
|
||||||
|
|
||||||
|
# Find sequences of one or more sentence-ending punctuation marks
|
||||||
|
sentence_endings = re.findall(r'[.!?]+', text)
|
||||||
|
count = len(sentence_endings)
|
||||||
|
|
||||||
|
# Handle edge case where text might not end with punctuation but isn't empty
|
||||||
|
if count == 0 and len(text.strip()) > 0:
|
||||||
|
return 1 # Assume at least one sentence if text exists but no terminators found
|
||||||
|
return count
|
||||||
|
|
||||||
|
|
||||||
|
def extract_character_names(text: str, min_occurrences: int = 2) -> List[str]:
|
||||||
|
"""
|
||||||
|
Attempts to extract potential character names from story text.
|
||||||
|
|
||||||
|
Note: This is a simple heuristic based on finding capitalized words
|
||||||
|
(excluding common sentence starters) that appear multiple times. It has
|
||||||
|
limitations and may produce false positives or miss actual names.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
text: The story text content.
|
||||||
|
min_occurrences: The minimum number of times a capitalized word must
|
||||||
|
appear to be considered a potential name.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A list of potential character name strings.
|
||||||
|
"""
|
||||||
|
if not isinstance(text, str) or not text.strip():
|
||||||
|
return []
|
||||||
|
if min_occurrences < 1:
|
||||||
|
min_occurrences = 1 # Ensure at least one occurrence is required
|
||||||
|
|
||||||
|
# Find words starting with an uppercase letter, potentially followed by lowercase
|
||||||
|
# Allows for single-letter names like 'X' but focuses on typical Name structure
|
||||||
|
capitalized_words = re.findall(r'\b[A-Z][a-zA-Z]*\b', text)
|
||||||
|
|
||||||
|
# Count occurrences, excluding common words
|
||||||
|
word_counts: Dict[str, int] = {}
|
||||||
|
for word in capitalized_words:
|
||||||
|
if word not in COMMON_START_WORDS:
|
||||||
|
word_counts[word] = word_counts.get(word, 0) + 1
|
||||||
|
|
||||||
|
# Filter for words that meet the minimum occurrence threshold
|
||||||
|
potential_names = [
|
||||||
|
word for word, count in word_counts.items() if count >= min_occurrences
|
||||||
|
]
|
||||||
|
|
||||||
|
# Sort for consistency (optional)
|
||||||
|
potential_names.sort()
|
||||||
|
|
||||||
|
return potential_names
|
||||||
|
|
||||||
|
|
||||||
|
def extract_setting_details(text: str) -> List[str]:
|
||||||
|
"""
|
||||||
|
Attempts to extract potential setting details using simple regex patterns.
|
||||||
|
|
||||||
|
Note: This is a very basic heuristic looking for common prepositional
|
||||||
|
phrases (e.g., "in the forest", "at the castle"). It is highly limited
|
||||||
|
and likely to miss many setting details or extract irrelevant phrases.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
text: The story text content.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A list of potential setting phrases found.
|
||||||
|
"""
|
||||||
|
if not isinstance(text, str) or not text.strip():
|
||||||
|
return []
|
||||||
|
|
||||||
|
# Patterns looking for prepositions followed by nouns/adjectives
|
||||||
|
# Making patterns slightly more general:
|
||||||
|
# (\b\w+\b) captures single words
|
||||||
|
# (\b\w+\s+\w+\b) captures two-word phrases
|
||||||
|
# (\b[A-Z]\w*\b) captures capitalized words (potential proper nouns)
|
||||||
|
setting_patterns = [
|
||||||
|
r'\b(?:in|on|at|near|beside|inside|outside|under|over|through)\s+(?:the|a|an)\s+((?:[A-Z]\w*|\w+)(?:\s+\w+){0,2})\b', # e.g., in the old house
|
||||||
|
r'\b(?:in|on|at)\s+((?:[A-Z]\w+)(?:\s+[A-Z]\w+)*)\b', # e.g., in New York City
|
||||||
|
r'\b(?:during|before|after)\s+(?:the|a|an)\s+(\w+(?:\s+\w+){0,2})\b', # e.g., during the storm
|
||||||
|
]
|
||||||
|
|
||||||
|
settings_found = set() # Use a set to avoid duplicates
|
||||||
|
for pattern in setting_patterns:
|
||||||
|
try:
|
||||||
|
matches = re.findall(pattern, text, re.IGNORECASE) # Ignore case
|
||||||
|
for match in matches:
|
||||||
|
# If match is tuple due to multiple capture groups, join them?
|
||||||
|
# For these patterns, it should be single strings.
|
||||||
|
if isinstance(match, str):
|
||||||
|
phrase = match.strip()
|
||||||
|
if phrase and len(phrase.split()) <= 5: # Limit phrase length
|
||||||
|
settings_found.add(phrase)
|
||||||
|
except re.error as e:
|
||||||
|
logger.warning(f"Regex error in extract_setting_details: {e} with pattern: {pattern}")
|
||||||
|
|
||||||
|
|
||||||
|
# Convert set back to list and sort for consistency
|
||||||
|
sorted_settings = sorted(list(settings_found))
|
||||||
|
return sorted_settings
|
||||||
|
|
||||||
|
|
||||||
|
# --- Image Operations ---
|
||||||
|
|
||||||
|
def get_image_dimensions(image_path: Union[str, Path]) -> Optional[Tuple[int, int]]:
|
||||||
|
"""
|
||||||
|
Gets the (width, height) dimensions of an image file using Pillow.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
image_path: The path to the image file (string or Path object).
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A tuple (width, height) if successful, or None if the file is not
|
||||||
|
a valid image, Pillow is not installed, or an error occurs.
|
||||||
|
"""
|
||||||
|
if not _PIL_AVAILABLE:
|
||||||
|
logger.warning("Pillow (PIL) library not installed. Cannot get image dimensions.")
|
||||||
|
return None
|
||||||
|
|
||||||
|
img_path = Path(image_path)
|
||||||
|
if not img_path.is_file():
|
||||||
|
logger.error(f"Image file not found or is not a file: {image_path}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
with Image.open(img_path) as img:
|
||||||
|
width, height = img.size
|
||||||
|
logger.debug(f"Dimensions for {image_path}: {width}x{height}")
|
||||||
|
return width, height
|
||||||
|
except FileNotFoundError:
|
||||||
|
logger.error(f"Image file not found at path: {image_path}")
|
||||||
|
return None
|
||||||
|
except UnidentifiedImageError: # Specific Pillow error for invalid images
|
||||||
|
logger.error(f"Could not identify image file (invalid format or corrupted): {image_path}")
|
||||||
|
return None
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error getting dimensions for image {image_path}: {e}", exc_info=True)
|
||||||
|
return None
|
||||||
31
ToBeMigrated/ai_writers/ai_story_video_generator/README.md
Normal file
31
ToBeMigrated/ai_writers/ai_story_video_generator/README.md
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
# AI Story Video Generator
|
||||||
|
|
||||||
|
This module allows users to generate animated story videos using AI. It leverages Google's Gemini model to create stories and generate images for each scene, then combines them into a video.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Generate complete stories based on user prompts
|
||||||
|
- Create scene-by-scene storyboards
|
||||||
|
- Generate images for each scene using Gemini
|
||||||
|
- Compile images into an animated video
|
||||||
|
- Add background music and text overlays
|
||||||
|
- Export videos in MP4 format
|
||||||
|
|
||||||
|
## How It Works
|
||||||
|
|
||||||
|
1. User provides a story prompt and preferences
|
||||||
|
2. AI generates a complete story with multiple scenes
|
||||||
|
3. For each scene, an image is generated
|
||||||
|
4. Images are compiled into a video with transitions
|
||||||
|
5. Optional background music and text overlays are added
|
||||||
|
6. The final video is available for download
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- Google Gemini API key
|
||||||
|
- FFmpeg for video processing
|
||||||
|
- Python libraries: moviepy, pillow, requests
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Access this tool through the Streamlit interface by selecting "AI Story Video Generator" from the main menu.
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
# AI Story Video Generator module
|
||||||
|
from .story_video_generator import write_story_video_generator
|
||||||
|
|
||||||
|
__all__ = ["write_story_video_generator"]
|
||||||
File diff suppressed because it is too large
Load Diff
64
ToBeMigrated/ai_writers/ai_story_video_generator/utils.py
Normal file
64
ToBeMigrated/ai_writers/ai_story_video_generator/utils.py
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
"""
|
||||||
|
Utility functions for the AI Story Video Generator.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import tempfile
|
||||||
|
import uuid
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
# Constants
|
||||||
|
TEMP_DIR = Path(tempfile.gettempdir()) / "alwrity_story_generator"
|
||||||
|
|
||||||
|
def ensure_temp_dir() -> Path:
|
||||||
|
"""Ensure the temporary directory exists and return its path."""
|
||||||
|
os.makedirs(TEMP_DIR, exist_ok=True)
|
||||||
|
return TEMP_DIR
|
||||||
|
|
||||||
|
def get_temp_filepath(prefix: str, extension: str) -> str:
|
||||||
|
"""Generate a temporary file path with the given prefix and extension."""
|
||||||
|
temp_dir = ensure_temp_dir()
|
||||||
|
return str(temp_dir / f"{prefix}_{uuid.uuid4()}.{extension}")
|
||||||
|
|
||||||
|
def clean_temp_files(older_than_hours: int = 24) -> int:
|
||||||
|
"""
|
||||||
|
Clean temporary files older than the specified number of hours.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
older_than_hours: Remove files older than this many hours
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Number of files removed
|
||||||
|
"""
|
||||||
|
import time
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
|
temp_dir = ensure_temp_dir()
|
||||||
|
cutoff_time = time.time() - (older_than_hours * 3600)
|
||||||
|
count = 0
|
||||||
|
|
||||||
|
for file_path in temp_dir.glob("*"):
|
||||||
|
if file_path.is_file() and file_path.stat().st_mtime < cutoff_time:
|
||||||
|
try:
|
||||||
|
file_path.unlink()
|
||||||
|
count += 1
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return count
|
||||||
|
|
||||||
|
def format_duration(seconds: float) -> str:
|
||||||
|
"""Format seconds into a MM:SS string."""
|
||||||
|
minutes = int(seconds // 60)
|
||||||
|
remaining_seconds = int(seconds % 60)
|
||||||
|
return f"{minutes}:{remaining_seconds:02d}"
|
||||||
|
|
||||||
|
def sanitize_filename(filename: str) -> str:
|
||||||
|
"""Sanitize a string to be used as a filename."""
|
||||||
|
import re
|
||||||
|
# Remove invalid characters
|
||||||
|
sanitized = re.sub(r'[^\w\s-]', '', filename)
|
||||||
|
# Replace spaces with underscores
|
||||||
|
sanitized = sanitized.strip().replace(' ', '_')
|
||||||
|
return sanitized
|
||||||
103
ToBeMigrated/ai_writers/ai_story_writer/README.md
Normal file
103
ToBeMigrated/ai_writers/ai_story_writer/README.md
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
# AI Story Generator App
|
||||||
|
|
||||||
|
In the age of AI, creativity and technology are intertwining in ways that are transforming how we tell stories. Imagine having the power to craft a captivating narrative tailored to your exact specifications with just a few clicks. Whether you're an aspiring writer, a seasoned novelist, or just someone who loves a good story, our new AI-powered story writing app is here to make storytelling easier and more engaging than ever before.
|
||||||
|
|
||||||
|
## Why an AI Story Writing App?
|
||||||
|
|
||||||
|
Storytelling has always been a cherished art form, but not everyone finds it easy to start from scratch. With the AI Story Generator App, you can create detailed and personalized stories by simply providing some key inputs. Our app uses advanced AI to turn your ideas into compelling narratives, helping you overcome writer's block and unleashing your creative potential.
|
||||||
|
|
||||||
|
## Features of the AI Story Generator App
|
||||||
|
|
||||||
|
### Genre
|
||||||
|
Choose from a variety of genres such as Fantasy, Sci-Fi, Mystery, Romance, and Horror to set the tone for your story.
|
||||||
|
|
||||||
|
### Story Setting
|
||||||
|
Provide a detailed setting for your story, including location and time period.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
A bustling futuristic city with towering skyscrapers and flying cars, set in the year 2150. The city is known for its technological advancements but has a dark underbelly of crime and corruption.
|
||||||
|
|
||||||
|
|
||||||
|
### Main Characters
|
||||||
|
Input the names, descriptions, and roles of your main characters.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
Character Names: John, Xishan, Amol
|
||||||
|
Character Descriptions: John is a tall, muscular man with a kind heart. Xishan is a clever and resourceful woman. Amol is a mischievous and energetic young boy.
|
||||||
|
Character Roles: John - Hero, Xishan - Sidekick, Amol - Supporting Character
|
||||||
|
|
||||||
|
|
||||||
|
### Plot Elements
|
||||||
|
Outline the key plot elements including the story theme, key events, and main conflict.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
Story Theme: Love conquers all, The hero's journey, Good vs. evil
|
||||||
|
|
||||||
|
Key Events or Plot Points:
|
||||||
|
|
||||||
|
The hero meets the villain
|
||||||
|
The hero faces a challenge
|
||||||
|
The hero overcomes the conflict
|
||||||
|
Main Conflict or Problem:
|
||||||
|
The hero must save the world from a powerful enemy, The hero must overcome a personal obstacle to achieve their goal.
|
||||||
|
|
||||||
|
|
||||||
|
### Tone and Style
|
||||||
|
Choose the writing style, tone, and narrative point of view for your story.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
Writing Style: Formal, Casual, Poetic, Humorous
|
||||||
|
Story Tone: Dark
|
||||||
|
|
||||||
|
### Perspective
|
||||||
|
Choose the narrative point of view from which the story is told (e.g., first person, third person limited, third person omniscient).
|
||||||
|
|
||||||
|
### Target Audience
|
||||||
|
Specify the intended audience age group (Children, Young Adults, Adults) and set a content rating (G, PG, PG-13, R) for appropriateness.
|
||||||
|
|
||||||
|
### Ending Preference
|
||||||
|
Select the type of ending you prefer for the story (e.g., happy, tragic, cliffhanger, twist).
|
||||||
|
|
||||||
|
## How to Use
|
||||||
|
|
||||||
|
Choose Genre: Select the genre that best fits your story idea.
|
||||||
|
Set Story Setting: Describe the setting and time period where your story unfolds.
|
||||||
|
Define Characters: Provide names, descriptions, and roles for your main characters.
|
||||||
|
Outline Plot Elements: Detail the story's theme, key events, and main conflict.
|
||||||
|
Select Tone and Style: Choose the writing style and tone that align with your story's mood.
|
||||||
|
Specify Perspective: Decide on the narrative point of view.
|
||||||
|
Target Audience: Specify the age group and content rating.
|
||||||
|
Choose Ending: Select the preferred type of story conclusion.
|
||||||
|
Generate Story: Click the "Generate Story" button to receive a customized story prompt based on your inputs.
|
||||||
|
|
||||||
|
|
||||||
|
### Example Prompt
|
||||||
|
|
||||||
|
**Genre:** Fantasy
|
||||||
|
**Setting:** A mystical forest in a medieval realm, where magic thrives and mythical creatures roam freely.
|
||||||
|
**Characters:**
|
||||||
|
- Name: Elara
|
||||||
|
Description: Elara is a young elf with a mischievous glint in her emerald eyes, known for her ability to wield powerful spells.
|
||||||
|
Role: Protagonist
|
||||||
|
- Name: Thorne
|
||||||
|
Description: Thorne is a gruff dwarf with a heart of gold, skilled in forging enchanted weapons.
|
||||||
|
Role: Sidekick
|
||||||
|
- Name: Malachai
|
||||||
|
Description: Malachai is a cunning dragon with shimmering scales of azure, whose allegiance is uncertain.
|
||||||
|
Role: Antagonist
|
||||||
|
|
||||||
|
**Plot Elements:**
|
||||||
|
- Theme: The power of friendship and bravery in the face of adversity.
|
||||||
|
- Key Events: Elara discovers an ancient prophecy that foretells a looming darkness threatening the realm. Thorne crafts a legendary sword to aid in their quest. Malachai challenges Elara's resolve, forcing her to make a difficult choice.
|
||||||
|
- Conflict: Elara must gather allies and confront the dark sorcerer who seeks to plunge the realm into eternal shadow.
|
||||||
|
|
||||||
|
**Writing Style:** Poetic
|
||||||
|
**Tone:** Whimsical
|
||||||
|
**Point of View:** Third Person Limited
|
||||||
|
|
||||||
|
**Audience:** Young Adults, **Content Rating:** PG
|
||||||
|
**Ending:** Happy
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
238
ToBeMigrated/ai_writers/ai_story_writer/ai_story_generator.py
Normal file
238
ToBeMigrated/ai_writers/ai_story_writer/ai_story_generator.py
Normal file
@@ -0,0 +1,238 @@
|
|||||||
|
#####################################################
|
||||||
|
#
|
||||||
|
# google-gemini-cookbook - Story_Writing_with_Prompt_Chaining
|
||||||
|
#
|
||||||
|
#####################################################
|
||||||
|
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
import streamlit as st
|
||||||
|
from loguru import logger
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from ...gpt_providers.text_generation.main_text_generation import llm_text_gen
|
||||||
|
|
||||||
|
|
||||||
|
def generate_with_retry(prompt, system_prompt=None):
|
||||||
|
"""
|
||||||
|
Generates content using the llm_text_gen function with retry handling for errors.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
prompt (str): The prompt to generate content from.
|
||||||
|
system_prompt (str, optional): Custom system prompt to use instead of the default one.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: The generated content.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Use llm_text_gen instead of directly calling the model
|
||||||
|
return llm_text_gen(prompt, system_prompt)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error generating content: {e}")
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
def ai_story(persona, story_setting, character_input,
|
||||||
|
plot_elements, writing_style, story_tone, narrative_pov,
|
||||||
|
audience_age_group, content_rating, ending_preference):
|
||||||
|
"""
|
||||||
|
Write a story using prompt chaining and iterative generation.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
persona (str): The persona statement for the author.
|
||||||
|
story_setting (str): The setting of the story.
|
||||||
|
character_input (str): The characters in the story.
|
||||||
|
plot_elements (str): The plot elements of the story.
|
||||||
|
writing_style (str): The writing style of the story.
|
||||||
|
story_tone (str): The tone of the story.
|
||||||
|
narrative_pov (str): The narrative point of view.
|
||||||
|
audience_age_group (str): The target audience age group.
|
||||||
|
content_rating (str): The content rating of the story.
|
||||||
|
ending_preference (str): The preferred ending of the story.
|
||||||
|
"""
|
||||||
|
st.info(f"""
|
||||||
|
You have chosen to create a story set in **{story_setting}**.
|
||||||
|
The main characters are: **{character_input}**.
|
||||||
|
The plot will revolve around the theme of **{plot_elements}**.
|
||||||
|
The story will be written in a **{writing_style}** style with a **{story_tone}** tone, from a **{narrative_pov}** perspective.
|
||||||
|
It is intended for a **{audience_age_group}** audience with a **{content_rating}** rating.
|
||||||
|
You prefer the story to have a **{ending_preference}** ending.
|
||||||
|
""")
|
||||||
|
try:
|
||||||
|
persona = f"""{persona}
|
||||||
|
Write a story with the following details:
|
||||||
|
|
||||||
|
**The stroy Setting is:**
|
||||||
|
{story_setting}
|
||||||
|
|
||||||
|
**The Characters of the story are:**
|
||||||
|
{character_input}
|
||||||
|
|
||||||
|
**Plot Elements of the story:**
|
||||||
|
{plot_elements}
|
||||||
|
|
||||||
|
**Story Writing Style:**
|
||||||
|
{writing_style}
|
||||||
|
|
||||||
|
**The story Tone is:**
|
||||||
|
{story_tone}
|
||||||
|
|
||||||
|
**Write story from the Point of View of:**
|
||||||
|
{narrative_pov}
|
||||||
|
|
||||||
|
**Target Audience of the story:**
|
||||||
|
{audience_age_group}, **Content Rating:** {content_rating}
|
||||||
|
|
||||||
|
**Story Ending:**
|
||||||
|
{ending_preference}
|
||||||
|
|
||||||
|
Make sure the story is engaging and tailored to the specified audience and content rating.
|
||||||
|
Ensure the ending aligns with the preference indicated.
|
||||||
|
|
||||||
|
"""
|
||||||
|
# Define persona and writing guidelines
|
||||||
|
guidelines = f'''\
|
||||||
|
Writing Guidelines:
|
||||||
|
|
||||||
|
Delve deeper. Lose yourself in the world you're building. Unleash vivid
|
||||||
|
descriptions to paint the scenes in your reader's mind.
|
||||||
|
Develop your characters — let their motivations, fears, and complexities unfold naturally.
|
||||||
|
Weave in the threads of your outline, but don't feel constrained by it.
|
||||||
|
Allow your story to surprise you as you write. Use rich imagery, sensory details, and
|
||||||
|
evocative language to bring the setting, characters, and events to life.
|
||||||
|
Introduce elements subtly that can blossom into complex subplots, relationships,
|
||||||
|
or worldbuilding details later in the story.
|
||||||
|
Keep things intriguing but not fully resolved.
|
||||||
|
Avoid boxing the story into a corner too early.
|
||||||
|
Plant the seeds of subplots or potential character arc shifts that can be expanded later.
|
||||||
|
|
||||||
|
Remember, your main goal is to write as much as you can. If you get through
|
||||||
|
the story too fast, that is bad. Expand, never summarize.
|
||||||
|
'''
|
||||||
|
|
||||||
|
# Generate prompts
|
||||||
|
premise_prompt = f'''\
|
||||||
|
{persona}
|
||||||
|
|
||||||
|
Write a single sentence premise for a {story_setting} story featuring {character_input}.
|
||||||
|
'''
|
||||||
|
|
||||||
|
outline_prompt = f'''\
|
||||||
|
{persona}
|
||||||
|
|
||||||
|
You have a gripping premise in mind:
|
||||||
|
|
||||||
|
{{premise}}
|
||||||
|
|
||||||
|
Write an outline for the plot of your story.
|
||||||
|
'''
|
||||||
|
|
||||||
|
starting_prompt = f'''\
|
||||||
|
{persona}
|
||||||
|
|
||||||
|
You have a gripping premise in mind:
|
||||||
|
|
||||||
|
{{premise}}
|
||||||
|
|
||||||
|
Your imagination has crafted a rich narrative outline:
|
||||||
|
|
||||||
|
{{outline}}
|
||||||
|
|
||||||
|
First, silently review the outline and the premise. Consider how to start the
|
||||||
|
story.
|
||||||
|
|
||||||
|
Start to write the very beginning of the story. You are not expected to finish
|
||||||
|
the whole story now. Your writing should be detailed enough that you are only
|
||||||
|
scratching the surface of the first bullet of your outline. Try to write AT
|
||||||
|
MINIMUM 4000 WORDS.
|
||||||
|
|
||||||
|
{guidelines}
|
||||||
|
'''
|
||||||
|
|
||||||
|
continuation_prompt = f'''\
|
||||||
|
{persona}
|
||||||
|
|
||||||
|
You have a gripping premise in mind:
|
||||||
|
|
||||||
|
{{premise}}
|
||||||
|
|
||||||
|
Your imagination has crafted a rich narrative outline:
|
||||||
|
|
||||||
|
{{outline}}
|
||||||
|
|
||||||
|
You've begun to immerse yourself in this world, and the words are flowing.
|
||||||
|
Here's what you've written so far:
|
||||||
|
|
||||||
|
{{story_text}}
|
||||||
|
|
||||||
|
=====
|
||||||
|
|
||||||
|
First, silently review the outline and story so far. Identify what the single
|
||||||
|
next part of your outline you should write.
|
||||||
|
|
||||||
|
Your task is to continue where you left off and write the next part of the story.
|
||||||
|
You are not expected to finish the whole story now. Your writing should be
|
||||||
|
detailed enough that you are only scratching the surface of the next part of
|
||||||
|
your outline. Try to write AT MINIMUM 2000 WORDS. However, only once the story
|
||||||
|
is COMPLETELY finished, write IAMDONE. Remember, do NOT write a whole chapter
|
||||||
|
right now.
|
||||||
|
|
||||||
|
{guidelines}
|
||||||
|
'''
|
||||||
|
|
||||||
|
# Generate prompts
|
||||||
|
try:
|
||||||
|
premise = generate_with_retry(premise_prompt)
|
||||||
|
st.info(f"The premise of the story is: {premise}")
|
||||||
|
except Exception as err:
|
||||||
|
st.error(f"Premise Generation Error: {err}")
|
||||||
|
return
|
||||||
|
|
||||||
|
outline = generate_with_retry(outline_prompt.format(premise=premise))
|
||||||
|
with st.expander("Click to Checkout the outline, writing still in progress.."):
|
||||||
|
st.markdown(f"The Outline of the story is: {outline}\n\n")
|
||||||
|
|
||||||
|
if not outline:
|
||||||
|
st.error("Failed to generate outline. Exiting...")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Generate starting draft
|
||||||
|
try:
|
||||||
|
starting_draft = generate_with_retry(
|
||||||
|
starting_prompt.format(premise=premise, outline=outline))
|
||||||
|
except Exception as err:
|
||||||
|
st.error(f"Failed to Generate Story draft: {err}")
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
draft = starting_draft
|
||||||
|
continuation = generate_with_retry(
|
||||||
|
continuation_prompt.format(premise=premise, outline=outline, story_text=draft))
|
||||||
|
except Exception as err:
|
||||||
|
st.error(f"Failed to write the initial draft: {err}")
|
||||||
|
|
||||||
|
# Add the continuation to the initial draft, keep building the story until we see 'IAMDONE'
|
||||||
|
try:
|
||||||
|
draft += '\n\n' + continuation
|
||||||
|
except Exception as err:
|
||||||
|
st.error(f"Failed as: {err} and {continuation}")
|
||||||
|
|
||||||
|
with st.status("Story Writing in Progress..", expanded=True) as status:
|
||||||
|
status.update(label=f"Writing in progress... Current draft length: {len(draft)} characters")
|
||||||
|
while 'IAMDONE' not in continuation:
|
||||||
|
try:
|
||||||
|
status.update(label=f"Writing in progress... Current draft length: {len(draft)} characters")
|
||||||
|
continuation = generate_with_retry(
|
||||||
|
continuation_prompt.format(premise=premise, outline=outline, story_text=draft))
|
||||||
|
draft += '\n\n' + continuation
|
||||||
|
except Exception as err:
|
||||||
|
st.error(f"Failed to continually write the story: {err}")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Remove 'IAMDONE' and print the final story
|
||||||
|
final = draft.replace('IAMDONE', '').strip()
|
||||||
|
return(final)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
st.error(f"Main Story writing: An error occurred: {e}")
|
||||||
|
return ""
|
||||||
134
ToBeMigrated/ai_writers/ai_story_writer/story_writer.py
Normal file
134
ToBeMigrated/ai_writers/ai_story_writer/story_writer.py
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
import time
|
||||||
|
import os
|
||||||
|
import json
|
||||||
|
import streamlit as st
|
||||||
|
|
||||||
|
from .ai_story_generator import ai_story
|
||||||
|
|
||||||
|
|
||||||
|
def story_input_section():
|
||||||
|
st.title("🧕 Alwrity - AI Story Writer")
|
||||||
|
personas = [
|
||||||
|
("Award-Winning Science Fiction Author", "👽 Award-Winning Science Fiction Author"),
|
||||||
|
("Historical Fiction Author", "🏺 Historical Fiction Author"),
|
||||||
|
("Fantasy World Builder", "🧙 Fantasy World Builder"),
|
||||||
|
("Mystery Novelist", "🕵️ Mystery Novelist"),
|
||||||
|
("Romantic Poet", "💌 Romantic Poet"),
|
||||||
|
("Thriller Writer", "🔪 Thriller Writer"),
|
||||||
|
("Children's Book Author", "📚 Children's Book Author"),
|
||||||
|
("Satirical Humorist", "😂 Satirical Humorist"),
|
||||||
|
("Biographical Writer", "📜 Biographical Writer"),
|
||||||
|
("Dystopian Visionary", "🌆 Dystopian Visionary"),
|
||||||
|
("Magical Realism Author", "🪄 Magical Realism Author")
|
||||||
|
]
|
||||||
|
|
||||||
|
selected_persona_name = st.selectbox(
|
||||||
|
"Select Your Story Writing Persona Or Book Genre",
|
||||||
|
options=[persona[0] for persona in personas]
|
||||||
|
)
|
||||||
|
|
||||||
|
persona_descriptions = {
|
||||||
|
"Award-Winning Science Fiction Author": "You are an award-winning science fiction author with a penchant for expansive, intricately woven stories. Your ultimate goal is to write the next award-winning sci-fi novel.",
|
||||||
|
"Historical Fiction Author": "You are a seasoned historical fiction author, meticulously researching past eras to weave captivating narratives. Your goal is to transport readers to different times and places through your vivid storytelling.",
|
||||||
|
"Fantasy World Builder": "You are a world-building enthusiast, crafting intricate realms filled with magic, mythical creatures, and epic quests. Your ambition is to create the next immersive fantasy saga that captivates readers' imaginations.",
|
||||||
|
"Mystery Novelist": "You are a master of suspense and intrigue, intricately plotting out mysteries with unexpected twists and turns. Your aim is to keep readers on the edge of their seats, eagerly turning pages to unravel the truth.",
|
||||||
|
"Romantic Poet": "You are a romantic at heart, composing verses that capture the essence of love, longing, and human connections. Your dream is to write the next timeless love story that leaves readers swooning.",
|
||||||
|
"Thriller Writer": "You are a thrill-seeker, crafting adrenaline-pumping tales of danger, suspense, and high-stakes action. Your mission is to keep readers hooked from start to finish with heart-pounding thrills and unexpected twists.",
|
||||||
|
"Children's Book Author": "You are a storyteller for the young and young at heart, creating whimsical worlds and lovable characters that inspire imagination and wonder. Your goal is to spark joy and curiosity in young readers with enchanting tales.",
|
||||||
|
"Satirical Humorist": "You are a keen observer of society, using humor and wit to satirize the absurdities of everyday life. Your aim is to entertain and provoke thought, delivering biting social commentary through clever and humorous storytelling.",
|
||||||
|
"Biographical Writer": "You are a chronicler of lives, delving into the stories of real people and events to illuminate the human experience. Your passion is to bring history to life through richly detailed biographies that resonate with readers.",
|
||||||
|
"Dystopian Visionary": "You are a visionary writer, exploring dark and dystopian futures that reflect contemporary fears and anxieties. Your vision is to challenge societal norms and provoke reflection on the path humanity is heading.",
|
||||||
|
"Magical Realism Author": "You are a purveyor of magical realism, blending the ordinary with the extraordinary to create enchanting and thought-provoking tales. Your goal is to blur the lines between reality and fantasy, leaving readers enchanted and introspective."
|
||||||
|
}
|
||||||
|
|
||||||
|
# Story Setting
|
||||||
|
st.subheader("🌍 Story Setting")
|
||||||
|
story_setting = st.text_area(
|
||||||
|
label="**Story Setting** (e.g., medieval kingdom in the past, futuristic city in the future, haunted house in the present):",
|
||||||
|
placeholder="""Enter settings for your story, like Location (e.g., medieval kingdom, futuristic city, haunted house),
|
||||||
|
Time period in which your story is set (e.g: Past, Present, Future)
|
||||||
|
Example: 'A bustling futuristic city with towering skyscrapers and flying cars, set in the year 2150.
|
||||||
|
The city is known for its technological advancements but has a dark underbelly of crime and corruption.'""",
|
||||||
|
help="Describe the main location and time period where the story will unfold in a detailed manner."
|
||||||
|
)
|
||||||
|
|
||||||
|
# Main Characters
|
||||||
|
st.subheader("👥 Main Characters")
|
||||||
|
character_input = st.text_area(
|
||||||
|
label="**Character Information** (Names, Descriptions, Roles)",
|
||||||
|
placeholder="""Example:
|
||||||
|
Character Names: John, Xishan, Amol
|
||||||
|
Character Descriptions: John is a tall, muscular man with a kind heart. Xishan is a clever and resourceful woman. Amol is a mischievous and energetic young boy.
|
||||||
|
Character Roles: John - Hero, Xishan - Sidekick, Amol - Supporting Character""",
|
||||||
|
help="Enter character information as specified in the placeholder."
|
||||||
|
)
|
||||||
|
|
||||||
|
# Plot Elements
|
||||||
|
st.subheader("🗺️ Plot Elements")
|
||||||
|
plot_elements = st.text_area(
|
||||||
|
"**Plot Elements** - (Theme, Key Events & Main Conflict)",
|
||||||
|
placeholder="""Example:
|
||||||
|
Story Theme: Love conquers all, The hero's journey, Good vs. evil.
|
||||||
|
Key Events: The hero meets the villain, The hero faces a challenge, The hero overcomes the conflict.
|
||||||
|
Main Conflict: The hero must save the world from a powerful enemy, The hero must overcome a personal obstacle to achieve their goal.""",
|
||||||
|
help="Enter plot elements as specified in the placeholder."
|
||||||
|
)
|
||||||
|
|
||||||
|
# Tone and Style
|
||||||
|
st.subheader("🎨 Tone and Style")
|
||||||
|
col1, col2, col3 = st.columns(3)
|
||||||
|
with col1:
|
||||||
|
writing_style = st.selectbox(
|
||||||
|
"**Writing Style:**",
|
||||||
|
["🧐 Formal", "😎 Casual", "🎼 Poetic", "😂 Humorous"],
|
||||||
|
help="Choose the writing style that fits your story."
|
||||||
|
)
|
||||||
|
with col2:
|
||||||
|
story_tone = st.selectbox(
|
||||||
|
"**Story Tone:**",
|
||||||
|
["🌑 Dark", "☀️ Uplifting", "⏳ Suspenseful", "🎈 Whimsical"],
|
||||||
|
help="Select the overall tone or mood of the story."
|
||||||
|
)
|
||||||
|
with col3:
|
||||||
|
narrative_pov = st.selectbox(
|
||||||
|
"**Narrative Point of View:**",
|
||||||
|
["👤 First Person", "👥 Third Person Limited", "👁️ Third Person Omniscient"],
|
||||||
|
help="Choose the point of view from which the story is told."
|
||||||
|
)
|
||||||
|
|
||||||
|
# Target Audience
|
||||||
|
st.subheader("👨👩👧👦 Target Audience")
|
||||||
|
col1, col2, col3 = st.columns(3)
|
||||||
|
with col1:
|
||||||
|
audience_age_group = st.selectbox(
|
||||||
|
"**Audience Age Group:**",
|
||||||
|
["🧒 Children", "👨🎓 Young Adults", "🧑🦳 Adults"],
|
||||||
|
help="Choose the intended audience age group."
|
||||||
|
)
|
||||||
|
with col2:
|
||||||
|
content_rating = st.selectbox(
|
||||||
|
"**Content Rating:**",
|
||||||
|
["🟢 G", "🟡 PG", "🔵 PG-13", "🔴 R"],
|
||||||
|
help="Select a content rating for appropriateness."
|
||||||
|
)
|
||||||
|
with col3:
|
||||||
|
ending_preference = st.selectbox(
|
||||||
|
"Story Conclusion:",
|
||||||
|
["😊 Happy", "😢 Tragic", "❓ Cliffhanger", "🔀 Twist"],
|
||||||
|
help="Choose the type of ending you prefer for the story."
|
||||||
|
)
|
||||||
|
|
||||||
|
if st.button('AI, Write a Story..'):
|
||||||
|
if character_input.strip():
|
||||||
|
with st.spinner("Generating Story...💥💥"):
|
||||||
|
story_content = ai_story(persona_descriptions[selected_persona_name],
|
||||||
|
story_setting, character_input, plot_elements, writing_style,
|
||||||
|
story_tone, narrative_pov, audience_age_group, content_rating,
|
||||||
|
ending_preference)
|
||||||
|
if story_content:
|
||||||
|
st.subheader('**🧕 Your Awesome Story:**')
|
||||||
|
st.markdown(story_content)
|
||||||
|
else:
|
||||||
|
st.error("💥 **Failed to generate Story. Please try again!**")
|
||||||
|
else:
|
||||||
|
st.error("Describe the story you have in your mind.. !")
|
||||||
220
ToBeMigrated/ai_writers/ai_writer_dashboard.py
Normal file
220
ToBeMigrated/ai_writers/ai_writer_dashboard.py
Normal file
@@ -0,0 +1,220 @@
|
|||||||
|
import streamlit as st
|
||||||
|
from lib.utils.alwrity_utils import (essay_writer, ai_news_writer, ai_finance_ta_writer)
|
||||||
|
|
||||||
|
from lib.ai_writers.ai_story_writer.story_writer import story_input_section
|
||||||
|
from lib.ai_writers.ai_product_description_writer import write_ai_prod_desc
|
||||||
|
from lib.ai_writers.ai_copywriter.copywriter_dashboard import copywriter_dashboard
|
||||||
|
from lib.ai_writers.linkedin_writer import LinkedInAIWriter
|
||||||
|
from lib.ai_writers.blog_rewriter_updater.ai_blog_rewriter import write_blog_rewriter
|
||||||
|
from lib.ai_writers.ai_blog_faqs_writer.faqs_ui import main as faqs_generator
|
||||||
|
from lib.ai_writers.ai_blog_writer.ai_blog_generator import ai_blog_writer_page
|
||||||
|
from lib.ai_writers.ai_outline_writer.outline_ui import main as outline_generator
|
||||||
|
from lib.alwrity_ui.dashboard_styles import apply_dashboard_style, render_dashboard_header, render_category_header, render_card
|
||||||
|
from loguru import logger
|
||||||
|
|
||||||
|
# Try to import AI Content Performance Predictor (AI-first approach)
|
||||||
|
try:
|
||||||
|
from lib.content_performance_predictor.ai_performance_predictor import render_ai_predictor_ui as render_content_performance_predictor
|
||||||
|
AI_PREDICTOR_AVAILABLE = True
|
||||||
|
logger.info("AI Content Performance Predictor loaded successfully")
|
||||||
|
except ImportError:
|
||||||
|
logger.warning("AI Content Performance Predictor not available")
|
||||||
|
render_content_performance_predictor = None
|
||||||
|
AI_PREDICTOR_AVAILABLE = False
|
||||||
|
|
||||||
|
# Try to import Bootstrap AI Competitive Suite
|
||||||
|
try:
|
||||||
|
from lib.ai_competitive_suite.bootstrap_ai_suite import render_bootstrap_ai_suite
|
||||||
|
BOOTSTRAP_SUITE_AVAILABLE = True
|
||||||
|
logger.info("Bootstrap AI Competitive Suite loaded successfully")
|
||||||
|
except ImportError:
|
||||||
|
logger.warning("Bootstrap AI Competitive Suite not available")
|
||||||
|
render_bootstrap_ai_suite = None
|
||||||
|
BOOTSTRAP_SUITE_AVAILABLE = False
|
||||||
|
|
||||||
|
def list_ai_writers():
|
||||||
|
"""Return a list of available AI writers with their metadata (no UI rendering)."""
|
||||||
|
writers = []
|
||||||
|
|
||||||
|
# Add Content Performance Predictor if available
|
||||||
|
if render_content_performance_predictor:
|
||||||
|
# AI-first approach description
|
||||||
|
if AI_PREDICTOR_AVAILABLE:
|
||||||
|
description = "🎯 AI-powered content performance prediction with competitive intelligence - perfect for solo entrepreneurs"
|
||||||
|
name = "AI Content Performance Predictor"
|
||||||
|
else:
|
||||||
|
description = "Predict content success before publishing with AI-powered performance analysis"
|
||||||
|
name = "Content Performance Predictor"
|
||||||
|
|
||||||
|
writers.append({
|
||||||
|
"name": name,
|
||||||
|
"icon": "🎯",
|
||||||
|
"description": description,
|
||||||
|
"category": "⭐ Featured",
|
||||||
|
"function": render_content_performance_predictor,
|
||||||
|
"path": "performance_predictor",
|
||||||
|
"featured": True
|
||||||
|
})
|
||||||
|
|
||||||
|
# Add Bootstrap AI Competitive Suite if available
|
||||||
|
if render_bootstrap_ai_suite:
|
||||||
|
writers.append({
|
||||||
|
"name": "Bootstrap AI Competitive Suite",
|
||||||
|
"icon": "🚀",
|
||||||
|
"description": "🥷 Complete AI-powered competitive toolkit: content performance prediction + competitive intelligence for solo entrepreneurs",
|
||||||
|
"category": "⭐ Featured",
|
||||||
|
"function": render_bootstrap_ai_suite,
|
||||||
|
"path": "bootstrap_ai_suite",
|
||||||
|
"featured": True
|
||||||
|
})
|
||||||
|
|
||||||
|
# Add existing writers
|
||||||
|
writers.extend([
|
||||||
|
{
|
||||||
|
"name": "AI Blog Writer",
|
||||||
|
"icon": "📝",
|
||||||
|
"description": "Generate comprehensive blog posts from keywords, URLs, or uploaded content",
|
||||||
|
"category": "Content Creation",
|
||||||
|
"function": ai_blog_writer_page,
|
||||||
|
"path": "ai_blog_writer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "AI Blog Rewriter",
|
||||||
|
"icon": "🔄",
|
||||||
|
"description": "Rewrite and update existing blog content with improved quality and SEO optimization",
|
||||||
|
"category": "Content Creation",
|
||||||
|
"function": write_blog_rewriter,
|
||||||
|
"path": "blog_rewriter"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Story Writer",
|
||||||
|
"icon": "📚",
|
||||||
|
"description": "Create engaging stories and narratives with AI assistance",
|
||||||
|
"category": "Creative Writing",
|
||||||
|
"function": story_input_section,
|
||||||
|
"path": "story_writer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Essay writer",
|
||||||
|
"icon": "✍️",
|
||||||
|
"description": "Generate well-structured essays on any topic",
|
||||||
|
"category": "Academic",
|
||||||
|
"function": essay_writer,
|
||||||
|
"path": "essay_writer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Write News reports",
|
||||||
|
"icon": "📰",
|
||||||
|
"description": "Create professional news articles and reports",
|
||||||
|
"category": "Journalism",
|
||||||
|
"function": ai_news_writer,
|
||||||
|
"path": "news_writer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Write Financial TA report",
|
||||||
|
"icon": "📊",
|
||||||
|
"description": "Generate technical analysis reports for financial markets",
|
||||||
|
"category": "Finance",
|
||||||
|
"function": ai_finance_ta_writer,
|
||||||
|
"path": "financial_writer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "AI Product Description Writer",
|
||||||
|
"icon": "🛍️",
|
||||||
|
"description": "Create compelling product descriptions that drive sales",
|
||||||
|
"category": "E-commerce",
|
||||||
|
"function": write_ai_prod_desc,
|
||||||
|
"path": "product_writer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "AI Copywriter",
|
||||||
|
"icon": "✒️",
|
||||||
|
"description": "Generate persuasive copy for marketing and advertising",
|
||||||
|
"category": "Marketing",
|
||||||
|
"function": copywriter_dashboard,
|
||||||
|
"path": "copywriter"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "LinkedIn AI Writer",
|
||||||
|
"icon": "💼",
|
||||||
|
"description": "Create professional LinkedIn content that engages your network",
|
||||||
|
"category": "Professional",
|
||||||
|
"function": lambda: LinkedInAIWriter().run(),
|
||||||
|
"path": "linkedin_writer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "FAQ Generator",
|
||||||
|
"icon": "❓",
|
||||||
|
"description": "Generate comprehensive, well-researched FAQs from any content source with customizable options",
|
||||||
|
"category": "Content Creation",
|
||||||
|
"function": faqs_generator,
|
||||||
|
"path": "faqs_generator"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Blog Outline Generator",
|
||||||
|
"icon": "📋",
|
||||||
|
"description": "Create detailed blog outlines with AI-powered content generation and image integration",
|
||||||
|
"category": "Content Creation",
|
||||||
|
"function": outline_generator,
|
||||||
|
"path": "outline_generator"
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
return writers
|
||||||
|
|
||||||
|
def get_ai_writers():
|
||||||
|
"""Main function to display AI writers dashboard with premium glassmorphic design."""
|
||||||
|
logger.info("Starting AI Writers Dashboard")
|
||||||
|
|
||||||
|
# Apply common dashboard styling
|
||||||
|
apply_dashboard_style()
|
||||||
|
|
||||||
|
# Render dashboard header
|
||||||
|
render_dashboard_header(
|
||||||
|
"🤖 AI Content Writers",
|
||||||
|
"Choose from our collection of specialized AI writers, each designed for specific content types and industries. Create engaging, high-quality content with just a few clicks."
|
||||||
|
)
|
||||||
|
|
||||||
|
writers = list_ai_writers()
|
||||||
|
logger.info(f"Found {len(writers)} AI writers")
|
||||||
|
|
||||||
|
# Group writers by category for better organization
|
||||||
|
categories = {}
|
||||||
|
for writer in writers:
|
||||||
|
category = writer["category"]
|
||||||
|
if category not in categories:
|
||||||
|
categories[category] = []
|
||||||
|
categories[category].append(writer)
|
||||||
|
|
||||||
|
# Render writers by category with common cards
|
||||||
|
for category_name, category_writers in categories.items():
|
||||||
|
render_category_header(category_name)
|
||||||
|
|
||||||
|
# Create columns for this category
|
||||||
|
cols = st.columns(min(len(category_writers), 3))
|
||||||
|
|
||||||
|
for idx, writer in enumerate(category_writers):
|
||||||
|
with cols[idx % 3]:
|
||||||
|
# Use the common card renderer
|
||||||
|
if render_card(
|
||||||
|
icon=writer['icon'],
|
||||||
|
title=writer['name'],
|
||||||
|
description=writer['description'],
|
||||||
|
category=writer['category'],
|
||||||
|
key_suffix=f"{writer['path']}_{category_name}",
|
||||||
|
help_text=f"Launch {writer['name']} - {writer['description']}"
|
||||||
|
):
|
||||||
|
logger.info(f"Selected writer: {writer['name']} with path: {writer['path']}")
|
||||||
|
st.session_state.selected_writer = writer
|
||||||
|
st.query_params["writer"] = writer['path']
|
||||||
|
logger.info(f"Updated query params with writer: {writer['path']}")
|
||||||
|
st.rerun()
|
||||||
|
|
||||||
|
# Add spacing between categories
|
||||||
|
st.markdown('<div class="category-spacer"></div>', unsafe_allow_html=True)
|
||||||
|
|
||||||
|
logger.info("Finished rendering AI Writers Dashboard")
|
||||||
|
|
||||||
|
return writers
|
||||||
|
|
||||||
|
# Remove the old ai_writers function since it's now integrated into get_ai_writers
|
||||||
0
ToBeMigrated/ai_writers/data_img_slides_analyst.py
Normal file
0
ToBeMigrated/ai_writers/data_img_slides_analyst.py
Normal file
50
ToBeMigrated/ai_writers/gpt_blog_sections.py
Normal file
50
ToBeMigrated/ai_writers/gpt_blog_sections.py
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import json
|
||||||
|
|
||||||
|
from ..gpt_providers.text_generation.openai_text_gen import openai_text_generation
|
||||||
|
from ..gpt_providers.text_generation.gemini_pro_text import gemini_text_generation
|
||||||
|
|
||||||
|
from loguru import logger
|
||||||
|
logger.remove()
|
||||||
|
logger.add(sys.stdout,
|
||||||
|
colorize=True,
|
||||||
|
format="<level>{level}</level>|<green>{file}:{line}:{function}</green>| {message}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# FIXME: Provide num_blogs, num_faqs as inputs.
|
||||||
|
def get_blog_sections_from_websearch(search_keyword, search_results):
|
||||||
|
"""Combine the given online research and gpt blog content"""
|
||||||
|
gpt_providers = os.environ["GPT_PROVIDER"]
|
||||||
|
prompt = f"""
|
||||||
|
As a SEO expert and content writer, I will provide you with a search keyword and its google search result.
|
||||||
|
Your task is to write a blog title and 5 blog sub titles, from the given google search result.
|
||||||
|
The subtitles should be less than 40 characters and click worthy.
|
||||||
|
Do not explain, describe your response. Respond in json format, always name the key as 'blogSections'.
|
||||||
|
|
||||||
|
Web Research Keyword: "{search_keyword}"
|
||||||
|
Google search Result: "{search_results}"
|
||||||
|
"""
|
||||||
|
|
||||||
|
if 'gemini' in gpt_providers:
|
||||||
|
try:
|
||||||
|
response = gemini_text_response(prompt)
|
||||||
|
if '```' in response and '\n' in response:
|
||||||
|
response = response.strip().split('\n')
|
||||||
|
# Remove the first and last lines
|
||||||
|
response = '\n'.join(response[1:-1])
|
||||||
|
response = json.loads(response)
|
||||||
|
return response
|
||||||
|
except Exception as err:
|
||||||
|
logger.error(f"Failed to get response from gemini: {err}")
|
||||||
|
logger.error(f"Gemini Error: {response.prompt_feedback}")
|
||||||
|
raise err
|
||||||
|
elif 'openai' in gpt_providers:
|
||||||
|
try:
|
||||||
|
logger.info("Calling OpenAI LLM.")
|
||||||
|
response = openai_chatgpt(prompt)
|
||||||
|
return response
|
||||||
|
except Exception as err:
|
||||||
|
logger.error(f"Failed to get response from Openai: {err}")
|
||||||
|
raise err
|
||||||
109
ToBeMigrated/ai_writers/image_ai_writer.py
Normal file
109
ToBeMigrated/ai_writers/image_ai_writer.py
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
import sys
|
||||||
|
import os
|
||||||
|
|
||||||
|
from textwrap import dedent
|
||||||
|
import json
|
||||||
|
import asyncio
|
||||||
|
from pathlib import Path
|
||||||
|
from datetime import datetime
|
||||||
|
import streamlit as st
|
||||||
|
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
load_dotenv(Path('../../.env'))
|
||||||
|
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.firecrawl_web_crawler import scrape_url
|
||||||
|
from ..blog_metadata.get_blog_metadata import blog_metadata
|
||||||
|
from ..blog_postprocessing.save_blog_to_file import save_blog_to_file
|
||||||
|
from ..gpt_providers.text_to_image_generation.main_generate_image_from_prompt import generate_image
|
||||||
|
from ..gpt_providers.text_generation.main_text_generation import llm_text_gen
|
||||||
|
from ..gpt_providers.image_to_text_gen.gemini_image_describe import describe_image, analyze_image_with_prompt
|
||||||
|
|
||||||
|
|
||||||
|
def blog_from_image(prompt, uploaded_img):
|
||||||
|
"""
|
||||||
|
This function will take a blog Topic to first generate sections for it
|
||||||
|
and then generate content for each section.
|
||||||
|
"""
|
||||||
|
# Use to store the blog in a string, to save in a *.md file.
|
||||||
|
blog_markdown_str = None
|
||||||
|
logger.info(f"Researching and Writing Blog on {uploaded_img} and {prompt}")
|
||||||
|
# FIXME: Implement support for Openai.
|
||||||
|
if not os.getenv("GEMINI_API_KEY"):
|
||||||
|
st.error("Only Gemini supported, Open Issue ticket on github for Openai, others.")
|
||||||
|
st.stop()
|
||||||
|
|
||||||
|
with st.status("Started Writing from Image..", expanded=True) as status:
|
||||||
|
st.empty()
|
||||||
|
status.update(label=f"Researching and Writing Blog on given Image")
|
||||||
|
try:
|
||||||
|
blog_markdown_str = write_blog_from_image(prompt, uploaded_img)
|
||||||
|
except Exception as err:
|
||||||
|
st.error(f"Failed to write blog from Image - Error: {err}")
|
||||||
|
logger.error(f"Failed to write blog from image: {err}")
|
||||||
|
st.stop()
|
||||||
|
status.update(label="Successfully wrote blog from image.", expanded=False, state="complete")
|
||||||
|
|
||||||
|
try:
|
||||||
|
status.update(label="🙎 Generating - Title, Meta Description, Tags, Categories for the content.")
|
||||||
|
blog_title, blog_meta_desc, blog_tags, blog_categories = asyncio.run(blog_metadata(blog_markdown_str))
|
||||||
|
except Exception as err:
|
||||||
|
st.error(f"Failed to get blog metadata: {err}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
status.update(label="🙎 Generating Image for the new blog.")
|
||||||
|
generated_image_filepath = generate_image(f"{blog_title} + ' ' + {blog_meta_desc}")
|
||||||
|
except Exception as err:
|
||||||
|
st.warning(f"Failed in Image generation: {err}")
|
||||||
|
|
||||||
|
saved_blog_to_file = save_blog_to_file(blog_markdown_str, blog_title, blog_meta_desc,
|
||||||
|
blog_tags, blog_categories, generated_image_filepath)
|
||||||
|
status.update(label=f"Saved the content in this file: {saved_blog_to_file}")
|
||||||
|
logger.info(f"\n\n --------- Finished writing Blog -------------- \n")
|
||||||
|
st.image(generated_image_filepath, caption=blog_title)
|
||||||
|
st.markdown(f"{blog_markdown_str}")
|
||||||
|
status.update(label=f"Finished, Review & Use your Original Content Below: {saved_blog_to_file}", state="complete")
|
||||||
|
|
||||||
|
# Clean up the temporary file after processing (optional)
|
||||||
|
os.remove(uploaded_img)
|
||||||
|
|
||||||
|
|
||||||
|
def write_blog_from_image(prompt, uploaded_img):
|
||||||
|
"""Combine the given online research and GPT blog content"""
|
||||||
|
try:
|
||||||
|
config_path = Path(os.environ["ALWRITY_CONFIG"])
|
||||||
|
with open(config_path, 'r', encoding='utf-8') as file:
|
||||||
|
config = json.load(file)
|
||||||
|
except Exception as err:
|
||||||
|
logger.error(f"Error: Failed to read values from config: {err}")
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
blog_characteristics = config['Blog Content Characteristics']
|
||||||
|
|
||||||
|
if not prompt:
|
||||||
|
prompt = f"""
|
||||||
|
As expert Creative Content writer, analyse the given image carefully.
|
||||||
|
I want you to write a detailed {blog_characteristics['Blog Type']} blog post including 5 FAQs.
|
||||||
|
|
||||||
|
Below are the guidelines to follow:
|
||||||
|
1). You must respond in {blog_characteristics['Blog Language']} language.
|
||||||
|
2). Tone and Brand Alignment: Adjust your tone, voice, personality for {blog_characteristics['Blog Tone']} audience.
|
||||||
|
3). Make sure your response content length is of {blog_characteristics['Blog Length']} words.
|
||||||
|
"""
|
||||||
|
logger.info("Generating blog and FAQs from image analysis.")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Use the gemini_image_describe function to analyze the image with the custom prompt
|
||||||
|
response = analyze_image_with_prompt(uploaded_img, prompt)
|
||||||
|
if not response:
|
||||||
|
logger.error("Failed to get response from image analysis")
|
||||||
|
return "Failed to generate content from image."
|
||||||
|
return response
|
||||||
|
except Exception as err:
|
||||||
|
logger.error(f"Exit: Failed to get response from image analysis: {err}")
|
||||||
|
exit(1)
|
||||||
143
ToBeMigrated/ai_writers/speech_to_blog/main_audio_to_blog.py
Normal file
143
ToBeMigrated/ai_writers/speech_to_blog/main_audio_to_blog.py
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
import os
|
||||||
|
import datetime #I wish
|
||||||
|
import sys
|
||||||
|
from textwrap import dedent
|
||||||
|
from tqdm import tqdm, trange
|
||||||
|
import time
|
||||||
|
|
||||||
|
from pytubefix import YouTube
|
||||||
|
import tempfile
|
||||||
|
from html2image import Html2Image
|
||||||
|
|
||||||
|
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.gpt_online_researcher import do_google_serp_search
|
||||||
|
from ..ai_blog_writer.blog_from_google_serp import blog_with_research
|
||||||
|
from ...blog_metadata.get_blog_metadata import blog_metadata
|
||||||
|
from ...blog_postprocessing.save_blog_to_file import save_blog_to_file
|
||||||
|
from ...gpt_providers.audio_to_text_generation.stt_audio_blog import speech_to_text
|
||||||
|
from ...gpt_providers.text_generation.main_text_generation import llm_text_gen
|
||||||
|
|
||||||
|
|
||||||
|
def youtube_to_blog(video_url):
|
||||||
|
"""Function to transcribe a given youtube url """
|
||||||
|
try:
|
||||||
|
# Starting the speech-to-text process
|
||||||
|
logger.info("Starting with Speech to Text.")
|
||||||
|
audio_text, audio_title = speech_to_text(video_url)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error in speech_to_text: {e}")
|
||||||
|
sys.exit(1) # Exit the program due to error in speech_to_text
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Summarizing the content of the YouTube video
|
||||||
|
audio_blog_content = summarize_youtube_video(audio_text)
|
||||||
|
logger.info("Successfully converted given URL to blog article.")
|
||||||
|
return audio_blog_content, audio_title
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error in summarize_youtube_video: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def summarize_youtube_video(user_content):
|
||||||
|
"""Generates a summary of a YouTube video using OpenAI GPT-3 and displays a progress bar.
|
||||||
|
Args:
|
||||||
|
video_link: The URL of the YouTube video to summarize.
|
||||||
|
Returns:
|
||||||
|
A string containing the summary of the video.
|
||||||
|
"""
|
||||||
|
|
||||||
|
logger.info("Start summarize_youtube_video..")
|
||||||
|
prompt = f"""
|
||||||
|
You are an expert copywriter specializing in digital content writing. I will provide you with a transcript.
|
||||||
|
Your task is to transform a given transcript into a well-structured and informative blog article.
|
||||||
|
Please follow the below objectives:
|
||||||
|
|
||||||
|
1. Master the Transcript: Understand main ideas, key points, and the core message.
|
||||||
|
2. Sentence Structure: Rephrase while preserving logical flow and coherence. Dont quote anyone from video.
|
||||||
|
3. Note: Check if the transcript is about programming, then include code examples and snippets in your article.
|
||||||
|
4. Write Unique Content: Avoid direct copying; rewrite in your own words.
|
||||||
|
5. REMEMBER to avoid direct quoting and maintain uniqueness.
|
||||||
|
6. Proofread: Check for grammar, spelling, and punctuation errors.
|
||||||
|
7. Use Creative and Human-like Style: Incorporate contractions, idioms, transitional phrases, interjections, and colloquialisms. 8. Avoid repetitive phrases and unnatural sentence structures.
|
||||||
|
9. Ensure Uniqueness: Guarantee the article is plagiarism-free.
|
||||||
|
10. Punctuation: Use appropriate question marks at the end of questions.
|
||||||
|
11. Pass AI Detection Tools: Create content that easily passes AI plagiarism detection tools.
|
||||||
|
12. Rephrase words like 'video, youtube, channel' with 'article, blog' and such suitable words.
|
||||||
|
|
||||||
|
Follow the above guidelines to create a well-optimized, unique, and informative article,
|
||||||
|
that will rank well in search engine results and engage readers effectively.
|
||||||
|
Follow above guidelines to craft a blog content from the following transcript:\n{user_content}
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
response = llm_text_gen(prompt)
|
||||||
|
return response
|
||||||
|
except Exception as err:
|
||||||
|
logger.error(f"Failed to summarize_youtube_video: {err}")
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def generate_audio_blog(audio_input):
|
||||||
|
"""Takes a list of youtube videos and generates blog for each one of them.
|
||||||
|
"""
|
||||||
|
# Use to store the blog in a string, to save in a *.md file.
|
||||||
|
blog_markdown_str = ""
|
||||||
|
try:
|
||||||
|
logger.info(f"Starting to write blog on URL: {audio_input}")
|
||||||
|
yt_blog, yt_title = youtube_to_blog(audio_input)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error in youtube_to_blog: {e}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
try:
|
||||||
|
logger.info("Starting with online research for URL title.")
|
||||||
|
research_report = do_google_serp_search(yt_title)
|
||||||
|
print(research_report)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error in do_online_research: {e}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Note: Check if the order of input matters for your function
|
||||||
|
logger.info("Preparing a blog content from audio script and online research content...")
|
||||||
|
blog_markdown_str = blog_with_research(research_report, yt_blog)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error in blog_with_research: {e}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
try:
|
||||||
|
import asyncio
|
||||||
|
# blog_metadata now returns 6 values: title, desc, tags, categories, hashtags, slug
|
||||||
|
blog_title, blog_meta_desc, blog_tags, blog_categories, blog_hashtags, blog_slug = asyncio.run(blog_metadata(blog_markdown_str))
|
||||||
|
except Exception as err:
|
||||||
|
logger.error(f"Failed to generate blog metadata: {err}")
|
||||||
|
# Set defaults in case of failure
|
||||||
|
blog_title = "Blog Article"
|
||||||
|
blog_meta_desc = "An informative blog post"
|
||||||
|
blog_tags = "content, blog"
|
||||||
|
blog_categories = "General, Information"
|
||||||
|
blog_hashtags = "#content #blog"
|
||||||
|
blog_slug = "blog-article"
|
||||||
|
|
||||||
|
try:
|
||||||
|
# TBD: Save the blog content as a .md file. Markdown or HTML ?
|
||||||
|
# Initialize generated_image_filepath to None since it's not generated in this function
|
||||||
|
generated_image_filepath = None
|
||||||
|
save_blog_to_file(blog_markdown_str, blog_title, blog_meta_desc, blog_tags, blog_categories, generated_image_filepath)
|
||||||
|
except Exception as err:
|
||||||
|
logger.error(f"Failed to save final blog in a file: {err}")
|
||||||
|
|
||||||
|
blog_frontmatter = dedent(f"""\n\n\n\
|
||||||
|
---
|
||||||
|
title: {blog_title}
|
||||||
|
categories: [{blog_categories}]
|
||||||
|
tags: [{blog_tags}]
|
||||||
|
Meta description: {blog_meta_desc.replace(":", "-")}
|
||||||
|
---\n\n""")
|
||||||
|
logger.info(f"{blog_frontmatter}{blog_markdown_str}")
|
||||||
|
logger.info(f"\n\n ################ Finished writing Blog for : {audio_input} #################### \n")
|
||||||
165
ToBeMigrated/ai_writers/twitter_writers/README.md
Normal file
165
ToBeMigrated/ai_writers/twitter_writers/README.md
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
# Twitter AI Writer Module
|
||||||
|
|
||||||
|
A comprehensive suite of AI-powered tools for Twitter/X content marketing and management.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
### 1. Tweet Generation & Optimization
|
||||||
|
- **Smart Tweet Generator**
|
||||||
|
- Multiple tweet variations based on input parameters
|
||||||
|
- Character count optimization
|
||||||
|
- Hashtag suggestions and placement
|
||||||
|
- Emoji usage recommendations
|
||||||
|
- Thread creation capabilities
|
||||||
|
|
||||||
|
- **Tweet Performance Predictor**
|
||||||
|
- Engagement rate estimation
|
||||||
|
- Best time to post suggestions
|
||||||
|
- Audience reach predictions
|
||||||
|
- Viral potential scoring
|
||||||
|
|
||||||
|
### 2. Content Strategy Tools
|
||||||
|
- **Content Calendar Generator**
|
||||||
|
- Weekly/monthly content planning
|
||||||
|
- Theme-based content scheduling
|
||||||
|
- Event and holiday integration
|
||||||
|
- Content mix recommendations
|
||||||
|
|
||||||
|
- **Hashtag Strategy Manager**
|
||||||
|
- Trending hashtag research
|
||||||
|
- Custom hashtag creation
|
||||||
|
- Hashtag performance tracking
|
||||||
|
- Competitor hashtag analysis
|
||||||
|
|
||||||
|
### 3. Visual Content Creation
|
||||||
|
- **Image Generator**
|
||||||
|
- Tweet card creation
|
||||||
|
- Infographic templates
|
||||||
|
- Quote card designs
|
||||||
|
- Brand-consistent visuals
|
||||||
|
|
||||||
|
- **Video Content Assistant**
|
||||||
|
- Video script generation
|
||||||
|
- Storyboard creation
|
||||||
|
- Caption optimization
|
||||||
|
- Thumbnail design suggestions
|
||||||
|
|
||||||
|
### 4. Engagement & Community Management
|
||||||
|
- **Reply Generator**
|
||||||
|
- Context-aware responses
|
||||||
|
- Tone matching
|
||||||
|
- Crisis management templates
|
||||||
|
- Customer service responses
|
||||||
|
|
||||||
|
- **Community Engagement Tools**
|
||||||
|
- Poll creation
|
||||||
|
- Q&A session planning
|
||||||
|
- Community highlight suggestions
|
||||||
|
- User-generated content prompts
|
||||||
|
|
||||||
|
### 5. Analytics & Optimization
|
||||||
|
- **Performance Analytics**
|
||||||
|
- Tweet performance tracking
|
||||||
|
- Engagement metrics analysis
|
||||||
|
- Audience growth monitoring
|
||||||
|
- Content effectiveness scoring
|
||||||
|
|
||||||
|
- **A/B Testing Assistant**
|
||||||
|
- Tweet variation testing
|
||||||
|
- Headline optimization
|
||||||
|
- CTA effectiveness analysis
|
||||||
|
- Best performing content identification
|
||||||
|
|
||||||
|
### 6. Research & Intelligence
|
||||||
|
- **Market Research Tools**
|
||||||
|
- Competitor analysis
|
||||||
|
- Industry trend tracking
|
||||||
|
- Audience sentiment analysis
|
||||||
|
- Content gap identification
|
||||||
|
|
||||||
|
- **Content Inspiration**
|
||||||
|
- Trending topic suggestions
|
||||||
|
- Content idea generation
|
||||||
|
- Viral content analysis
|
||||||
|
- Industry-specific insights
|
||||||
|
|
||||||
|
## Best Practices Integration
|
||||||
|
|
||||||
|
### Tweet Optimization
|
||||||
|
- Optimal character count (240-280)
|
||||||
|
- Strategic hashtag placement
|
||||||
|
- Effective use of mentions and links
|
||||||
|
- Engaging call-to-actions
|
||||||
|
- Visual content optimization
|
||||||
|
|
||||||
|
### Content Strategy
|
||||||
|
- Consistent brand voice
|
||||||
|
- Regular posting schedule
|
||||||
|
- Content variety maintenance
|
||||||
|
- Engagement-driven approach
|
||||||
|
- Community building focus
|
||||||
|
|
||||||
|
### Visual Content
|
||||||
|
- Image size optimization
|
||||||
|
- Brand color consistency
|
||||||
|
- Text overlay best practices
|
||||||
|
- Mobile-friendly design
|
||||||
|
- Visual hierarchy principles
|
||||||
|
|
||||||
|
### Engagement
|
||||||
|
- Response time optimization
|
||||||
|
- Community management guidelines
|
||||||
|
- Crisis communication protocols
|
||||||
|
- User interaction best practices
|
||||||
|
- Content moderation assistance
|
||||||
|
|
||||||
|
## Technical Integration
|
||||||
|
|
||||||
|
### API Integration
|
||||||
|
- Twitter API v2 support
|
||||||
|
- Rate limit management
|
||||||
|
- Error handling
|
||||||
|
- Data synchronization
|
||||||
|
|
||||||
|
### Performance Optimization
|
||||||
|
- Caching mechanisms
|
||||||
|
- Batch processing
|
||||||
|
- Resource optimization
|
||||||
|
- Response time improvement
|
||||||
|
|
||||||
|
## Security & Compliance
|
||||||
|
|
||||||
|
### Data Protection
|
||||||
|
- User data encryption
|
||||||
|
- Secure API key management
|
||||||
|
- Privacy compliance
|
||||||
|
- Data retention policies
|
||||||
|
|
||||||
|
### Content Guidelines
|
||||||
|
- Platform policy compliance
|
||||||
|
- Copyright protection
|
||||||
|
- Brand safety measures
|
||||||
|
- Content moderation rules
|
||||||
|
|
||||||
|
## Coming Soon
|
||||||
|
- Advanced thread generator
|
||||||
|
- AI-powered image editor
|
||||||
|
- Real-time trend analyzer
|
||||||
|
- Automated content scheduler
|
||||||
|
- Advanced analytics dashboard
|
||||||
|
- Multi-account management
|
||||||
|
- Custom AI model training
|
||||||
|
- Integration with other social platforms
|
||||||
|
|
||||||
|
## Usage Guidelines
|
||||||
|
1. Ensure API keys are properly configured
|
||||||
|
2. Follow Twitter's terms of service
|
||||||
|
3. Maintain brand voice consistency
|
||||||
|
4. Regular content calendar updates
|
||||||
|
5. Monitor performance metrics
|
||||||
|
6. Engage with community regularly
|
||||||
|
7. Update content strategy based on analytics
|
||||||
|
8. Follow security best practices
|
||||||
|
|
||||||
|
## Support
|
||||||
|
For technical support or feature requests, please contact the development team or raise an issue in the repository. https://github.com/AJaySi/AI-Writer/issues
|
||||||
9
ToBeMigrated/ai_writers/twitter_writers/__init__.py
Normal file
9
ToBeMigrated/ai_writers/twitter_writers/__init__.py
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
"""
|
||||||
|
Twitter AI Writer Module
|
||||||
|
|
||||||
|
A comprehensive suite of AI-powered tools for Twitter/X content marketing and management.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from .twitter_dashboard import run_dashboard
|
||||||
|
|
||||||
|
__all__ = ['run_dashboard']
|
||||||
@@ -0,0 +1,163 @@
|
|||||||
|
Here’s an improved and enhanced version of your README. I've structured it for clarity, conciseness, and professionalism, while also making it more engaging and user-friendly.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 🐦 Smart Tweet Generator
|
||||||
|
|
||||||
|
**Create tweets that stand out!** The Smart Tweet Generator is a cutting-edge AI-powered tool designed to craft optimized, engaging tweets that maximize your audience reach and engagement.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ✨ Key Features
|
||||||
|
|
||||||
|
### 1. **Multi-Variation Tweet Generation**
|
||||||
|
- Generate 1–5 tweet variations from a single prompt.
|
||||||
|
- Each variation tailored to different engagement styles.
|
||||||
|
- Consistent tone and messaging across all versions.
|
||||||
|
|
||||||
|
### 2. **Real-Time Character Optimization**
|
||||||
|
- Live character count tracking, including emoji support.
|
||||||
|
- Visual indicators to maintain the ideal tweet length.
|
||||||
|
- Alerts when nearing Twitter's 280-character limit.
|
||||||
|
|
||||||
|
### 3. **Intelligent Hashtag Management**
|
||||||
|
- Auto-extract hashtags from generated tweets.
|
||||||
|
- Topic-based, AI-suggested hashtags to enhance discoverability.
|
||||||
|
- Recommendations for optimal hashtag count and placement.
|
||||||
|
|
||||||
|
### 4. **Emoji Suggestions That Fit**
|
||||||
|
- Context-sensitive and tone-appropriate emoji suggestions.
|
||||||
|
- Categories include:
|
||||||
|
- **Humorous**: 😄 😂 😉
|
||||||
|
- **Informative**: 📊 🔍 💡
|
||||||
|
- **Inspirational**: ✨ 🌟 🔥
|
||||||
|
- **Serious**: 🤔 📢 🔔
|
||||||
|
- **Casual**: 👋 👍 🤗
|
||||||
|
|
||||||
|
### 5. **Performance Prediction**
|
||||||
|
- Engagement score (0-100%) based on AI analysis.
|
||||||
|
- Metrics analyzed include:
|
||||||
|
- Character count optimization.
|
||||||
|
- Hashtag effectiveness.
|
||||||
|
- Emoji usage.
|
||||||
|
- Audience relevance.
|
||||||
|
- Categories:
|
||||||
|
- **Excellent** (80–100%)
|
||||||
|
- **Good** (60–79%)
|
||||||
|
- **Fair** (40–59%)
|
||||||
|
- **Needs Improvement** (0–39%)
|
||||||
|
|
||||||
|
### 6. **Actionable Improvement Suggestions**
|
||||||
|
- Real-time feedback on tweet quality.
|
||||||
|
- Tailored recommendations to boost performance.
|
||||||
|
- Built-in best practices guidance for effective tweeting.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎯 How to Use
|
||||||
|
|
||||||
|
### Step 1: **Enter Basic Information**
|
||||||
|
- Add your tweet topic or hook.
|
||||||
|
- Define the target audience.
|
||||||
|
- Choose the desired tone and tweet length.
|
||||||
|
- Optionally, include a call-to-action (CTA).
|
||||||
|
|
||||||
|
### Step 2: **Customize Advanced Options**
|
||||||
|
- Select the number of tweet variations (1–5).
|
||||||
|
- Input keywords or hashtags.
|
||||||
|
- Choose emoji preferences.
|
||||||
|
- Add @mentions or placeholders for links.
|
||||||
|
|
||||||
|
### Step 3: **Generate and Refine**
|
||||||
|
- Click **Generate Tweets** to create variations.
|
||||||
|
- Review performance metrics and apply improvement suggestions.
|
||||||
|
- Copy, save, or export your favorite version.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📊 Performance Metrics
|
||||||
|
|
||||||
|
**Your tweets are analyzed based on:**
|
||||||
|
|
||||||
|
1. **Character Count**
|
||||||
|
- Optimal: 100–200 characters.
|
||||||
|
- Short: <100 characters.
|
||||||
|
- Long: >200 characters.
|
||||||
|
|
||||||
|
2. **Hashtag Usage**
|
||||||
|
- Optimal: 1–3 hashtags.
|
||||||
|
- Too few: 0 hashtags.
|
||||||
|
- Too many: >3 hashtags.
|
||||||
|
|
||||||
|
3. **Engagement Triggers**
|
||||||
|
- Questions, CTAs, or interactive elements.
|
||||||
|
|
||||||
|
4. **Emoji Optimization**
|
||||||
|
- Ideal: 1–3 emojis.
|
||||||
|
- Too few: 0 emojis.
|
||||||
|
- Too many: >3 emojis.
|
||||||
|
|
||||||
|
5. **Audience Relevance**
|
||||||
|
- Alignment with keywords, tone, and context.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 💡 Best Practices
|
||||||
|
|
||||||
|
1. **Craft Attention-Grabbing Hooks**
|
||||||
|
- Start with bold statements or thought-provoking questions.
|
||||||
|
- Use stats or facts to capture attention.
|
||||||
|
|
||||||
|
2. **Align Tone with Audience**
|
||||||
|
- Maintain consistency with your brand voice.
|
||||||
|
- Adapt tone to audience preferences (e.g., formal, casual).
|
||||||
|
|
||||||
|
3. **Strategic Hashtag Usage**
|
||||||
|
- Use trending and relevant hashtags.
|
||||||
|
- Limit to 1–3 for optimal engagement.
|
||||||
|
|
||||||
|
4. **Effective Emoji Usage**
|
||||||
|
- Enhance meaning and context with emojis.
|
||||||
|
- Match the tone and avoid overuse.
|
||||||
|
|
||||||
|
5. **Clear Calls-to-Action**
|
||||||
|
- Encourage action with clarity and urgency.
|
||||||
|
- Use action verbs like "Discover," "Join," or "Explore."
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔄 Export Options
|
||||||
|
|
||||||
|
- Copy individual tweets.
|
||||||
|
- Export all variations as a JSON file.
|
||||||
|
- Save performance metrics and recommendations.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🛠️ Technical Details
|
||||||
|
|
||||||
|
- **Built with:** Streamlit for an intuitive user interface.
|
||||||
|
- **AI-powered:** Advanced natural language models for tweet generation.
|
||||||
|
- **Real-time:** Instant feedback and suggestions.
|
||||||
|
- **Cross-platform compatibility:** Works seamlessly across devices.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📝 Notes
|
||||||
|
|
||||||
|
- Tweets are optimized for Twitter’s 280-character limit.
|
||||||
|
- Performance predictions are derived from AI insights and engagement patterns.
|
||||||
|
- Suggestions adapt to your audience, ensuring relevancy.
|
||||||
|
- Regular updates keep the tool current with Twitter trends.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🤝 Support
|
||||||
|
|
||||||
|
Have questions or feature requests? Reach out to our support team or submit an issue on our GitHub repository.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Last updated: Yesterday*
|
||||||
|
|
||||||
|
---
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
"""
|
||||||
|
Twitter Tweet Generator Module
|
||||||
|
|
||||||
|
A comprehensive suite of tools for generating and optimizing tweets.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from .smart_tweet_generator import smart_tweet_generator
|
||||||
|
|
||||||
|
__all__ = ['smart_tweet_generator']
|
||||||
File diff suppressed because it is too large
Load Diff
729
ToBeMigrated/ai_writers/twitter_writers/twitter_dashboard.py
Normal file
729
ToBeMigrated/ai_writers/twitter_writers/twitter_dashboard.py
Normal file
@@ -0,0 +1,729 @@
|
|||||||
|
"""
|
||||||
|
Enhanced Twitter Dashboard with modern UI components and improved user experience.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import streamlit as st
|
||||||
|
from typing import Dict, List, Optional, Any
|
||||||
|
import json
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
import plotly.express as px
|
||||||
|
import plotly.graph_objects as go
|
||||||
|
from plotly.subplots import make_subplots
|
||||||
|
import pandas as pd
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
from .tweet_generator import smart_tweet_generator
|
||||||
|
from .twitter_streamlit_ui import (
|
||||||
|
TwitterDashboard,
|
||||||
|
FeatureCard,
|
||||||
|
TweetCard,
|
||||||
|
TweetForm,
|
||||||
|
SettingsForm,
|
||||||
|
Sidebar,
|
||||||
|
Header,
|
||||||
|
Tabs,
|
||||||
|
Breadcrumbs,
|
||||||
|
Theme,
|
||||||
|
save_to_session,
|
||||||
|
get_from_session,
|
||||||
|
clear_session,
|
||||||
|
show_success_message,
|
||||||
|
show_error_message,
|
||||||
|
show_info_message,
|
||||||
|
show_warning_message
|
||||||
|
)
|
||||||
|
|
||||||
|
def apply_modern_styling():
|
||||||
|
"""Apply modern CSS styling to the dashboard."""
|
||||||
|
st.markdown("""
|
||||||
|
<style>
|
||||||
|
/* Import Google Fonts */
|
||||||
|
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
|
||||||
|
|
||||||
|
/* Global Styles */
|
||||||
|
.stApp {
|
||||||
|
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Main Container */
|
||||||
|
.main-container {
|
||||||
|
background: rgba(255, 255, 255, 0.95);
|
||||||
|
backdrop-filter: blur(20px);
|
||||||
|
border-radius: 20px;
|
||||||
|
padding: 2rem;
|
||||||
|
margin: 1rem;
|
||||||
|
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Header Styles */
|
||||||
|
.dashboard-header {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
padding: 2rem 0;
|
||||||
|
background: linear-gradient(135deg, #1DA1F2, #0C85D0);
|
||||||
|
border-radius: 16px;
|
||||||
|
color: white;
|
||||||
|
box-shadow: 0 10px 30px rgba(29, 161, 242, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-title {
|
||||||
|
font-size: 2.5rem;
|
||||||
|
font-weight: 700;
|
||||||
|
margin: 0;
|
||||||
|
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-subtitle {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
opacity: 0.9;
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Feature Cards */
|
||||||
|
.feature-card {
|
||||||
|
background: white;
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 1.5rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.08);
|
||||||
|
border: 1px solid rgba(0, 0, 0, 0.05);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-card:hover {
|
||||||
|
transform: translateY(-5px);
|
||||||
|
box-shadow: 0 15px 35px rgba(0, 0, 0, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-icon {
|
||||||
|
font-size: 2.5rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-title {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #2D3748;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-description {
|
||||||
|
color: #718096;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
line-height: 1.5;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-status {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0.25rem 0.75rem;
|
||||||
|
border-radius: 20px;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
font-weight: 500;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-active {
|
||||||
|
background: linear-gradient(135deg, #48BB78, #38A169);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-coming-soon {
|
||||||
|
background: linear-gradient(135deg, #ED8936, #DD6B20);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Metrics Cards */
|
||||||
|
.metric-card {
|
||||||
|
background: white;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 1.5rem;
|
||||||
|
text-align: center;
|
||||||
|
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.08);
|
||||||
|
border-left: 4px solid #1DA1F2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-value {
|
||||||
|
font-size: 2rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #2D3748;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-label {
|
||||||
|
color: #718096;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Buttons */
|
||||||
|
.stButton > button {
|
||||||
|
background: linear-gradient(135deg, #1DA1F2, #0C85D0);
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 0.75rem 1.5rem;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
box-shadow: 0 4px 15px rgba(29, 161, 242, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.stButton > button:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 8px 25px rgba(29, 161, 242, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tabs */
|
||||||
|
.stTabs [data-baseweb="tab-list"] {
|
||||||
|
gap: 0.5rem;
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
padding: 0.5rem;
|
||||||
|
border-radius: 12px;
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.stTabs [data-baseweb="tab"] {
|
||||||
|
background: transparent;
|
||||||
|
border-radius: 8px;
|
||||||
|
color: #4A5568;
|
||||||
|
font-weight: 500;
|
||||||
|
padding: 0.75rem 1.5rem;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stTabs [aria-selected="true"] {
|
||||||
|
background: white;
|
||||||
|
color: #1DA1F2;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Connection Status */
|
||||||
|
.connection-status {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: 12px;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-connected {
|
||||||
|
background: linear-gradient(135deg, #C6F6D5, #9AE6B4);
|
||||||
|
color: #22543D;
|
||||||
|
border: 1px solid #9AE6B4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-disconnected {
|
||||||
|
background: linear-gradient(135deg, #FED7D7, #FEB2B2);
|
||||||
|
color: #742A2A;
|
||||||
|
border: 1px solid #FEB2B2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Quick Actions */
|
||||||
|
.quick-actions {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||||
|
gap: 1rem;
|
||||||
|
margin: 2rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quick-action-btn {
|
||||||
|
background: white;
|
||||||
|
border: 2px solid #E2E8F0;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 1.5rem;
|
||||||
|
text-align: center;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
cursor: pointer;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quick-action-btn:hover {
|
||||||
|
border-color: #1DA1F2;
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 8px 25px rgba(29, 161, 242, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.quick-action-icon {
|
||||||
|
font-size: 2rem;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quick-action-title {
|
||||||
|
font-weight: 600;
|
||||||
|
color: #2D3748;
|
||||||
|
margin-bottom: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quick-action-desc {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
color: #718096;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Analytics Charts */
|
||||||
|
.chart-container {
|
||||||
|
background: white;
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 1.5rem;
|
||||||
|
margin: 1rem 0;
|
||||||
|
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive Design */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.main-container {
|
||||||
|
margin: 0.5rem;
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-title {
|
||||||
|
font-size: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quick-actions {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
""", unsafe_allow_html=True)
|
||||||
|
|
||||||
|
def render_connection_status():
|
||||||
|
"""Render Twitter connection status with modern styling."""
|
||||||
|
# Simulate connection status (replace with real authentication check)
|
||||||
|
is_connected = get_from_session("twitter_connected", False)
|
||||||
|
|
||||||
|
if is_connected:
|
||||||
|
user_info = get_from_session("twitter_user", {"name": "Demo User", "handle": "@demo_user"})
|
||||||
|
st.markdown(f"""
|
||||||
|
<div class="connection-status status-connected">
|
||||||
|
<span style="font-size: 1.2rem;">✅</span>
|
||||||
|
<div>
|
||||||
|
<strong>Connected as {user_info['name']}</strong>
|
||||||
|
<div style="font-size: 0.9rem; opacity: 0.8;">{user_info['handle']}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
""", unsafe_allow_html=True)
|
||||||
|
else:
|
||||||
|
st.markdown("""
|
||||||
|
<div class="connection-status status-disconnected">
|
||||||
|
<span style="font-size: 1.2rem;">⚠️</span>
|
||||||
|
<div>
|
||||||
|
<strong>Twitter Not Connected</strong>
|
||||||
|
<div style="font-size: 0.9rem; opacity: 0.8;">Connect your account to access all features</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
""", unsafe_allow_html=True)
|
||||||
|
|
||||||
|
if st.button("🔗 Connect Twitter Account", key="connect_twitter"):
|
||||||
|
# Simulate connection (replace with real OAuth flow)
|
||||||
|
save_to_session("twitter_connected", True)
|
||||||
|
save_to_session("twitter_user", {"name": "Demo User", "handle": "@demo_user"})
|
||||||
|
st.rerun()
|
||||||
|
|
||||||
|
def render_dashboard_header():
|
||||||
|
"""Render the modern dashboard header."""
|
||||||
|
st.markdown("""
|
||||||
|
<div class="dashboard-header">
|
||||||
|
<h1 class="dashboard-title">🐦 Twitter AI Dashboard</h1>
|
||||||
|
<p class="dashboard-subtitle">Create, analyze, and optimize your Twitter content with AI-powered tools</p>
|
||||||
|
</div>
|
||||||
|
""", unsafe_allow_html=True)
|
||||||
|
|
||||||
|
def render_quick_actions():
|
||||||
|
"""Render quick action buttons."""
|
||||||
|
st.markdown("### 🚀 Quick Actions")
|
||||||
|
|
||||||
|
col1, col2, col3, col4 = st.columns(4)
|
||||||
|
|
||||||
|
with col1:
|
||||||
|
if st.button("✍️ Create Tweet", use_container_width=True, key="quick_tweet"):
|
||||||
|
st.session_state.current_page = "tweet_generator"
|
||||||
|
st.rerun()
|
||||||
|
|
||||||
|
with col2:
|
||||||
|
if st.button("📊 View Analytics", use_container_width=True, key="quick_analytics"):
|
||||||
|
st.session_state.current_page = "analytics"
|
||||||
|
st.rerun()
|
||||||
|
|
||||||
|
with col3:
|
||||||
|
if st.button("📅 Content Calendar", use_container_width=True, key="quick_calendar"):
|
||||||
|
show_info_message("Content Calendar feature coming soon!")
|
||||||
|
|
||||||
|
with col4:
|
||||||
|
if st.button("⚙️ Settings", use_container_width=True, key="quick_settings"):
|
||||||
|
st.session_state.current_page = "settings"
|
||||||
|
st.rerun()
|
||||||
|
|
||||||
|
def render_metrics_overview():
|
||||||
|
"""Render key metrics overview."""
|
||||||
|
st.markdown("### 📈 Performance Overview")
|
||||||
|
|
||||||
|
# Generate sample metrics (replace with real data)
|
||||||
|
col1, col2, col3, col4 = st.columns(4)
|
||||||
|
|
||||||
|
with col1:
|
||||||
|
st.markdown("""
|
||||||
|
<div class="metric-card">
|
||||||
|
<div class="metric-value">1,234</div>
|
||||||
|
<div class="metric-label">Total Tweets</div>
|
||||||
|
</div>
|
||||||
|
""", unsafe_allow_html=True)
|
||||||
|
|
||||||
|
with col2:
|
||||||
|
st.markdown("""
|
||||||
|
<div class="metric-card">
|
||||||
|
<div class="metric-value">45.2K</div>
|
||||||
|
<div class="metric-label">Total Engagement</div>
|
||||||
|
</div>
|
||||||
|
""", unsafe_allow_html=True)
|
||||||
|
|
||||||
|
with col3:
|
||||||
|
st.markdown("""
|
||||||
|
<div class="metric-card">
|
||||||
|
<div class="metric-value">3.8%</div>
|
||||||
|
<div class="metric-label">Engagement Rate</div>
|
||||||
|
</div>
|
||||||
|
""", unsafe_allow_html=True)
|
||||||
|
|
||||||
|
with col4:
|
||||||
|
st.markdown("""
|
||||||
|
<div class="metric-card">
|
||||||
|
<div class="metric-value">12.5K</div>
|
||||||
|
<div class="metric-label">Followers</div>
|
||||||
|
</div>
|
||||||
|
""", unsafe_allow_html=True)
|
||||||
|
|
||||||
|
def render_engagement_chart():
|
||||||
|
"""Render engagement trends chart."""
|
||||||
|
st.markdown("### 📊 Engagement Trends")
|
||||||
|
|
||||||
|
# Generate sample data (replace with real Twitter data)
|
||||||
|
dates = pd.date_range(start=datetime.now() - timedelta(days=30), periods=30)
|
||||||
|
engagement = np.random.normal(100, 20, 30)
|
||||||
|
engagement = np.maximum(engagement, 0) # Ensure positive values
|
||||||
|
|
||||||
|
df = pd.DataFrame({
|
||||||
|
'Date': dates,
|
||||||
|
'Engagement': engagement,
|
||||||
|
'Likes': engagement * 0.6,
|
||||||
|
'Retweets': engagement * 0.3,
|
||||||
|
'Replies': engagement * 0.1
|
||||||
|
})
|
||||||
|
|
||||||
|
# Create interactive chart
|
||||||
|
fig = make_subplots(
|
||||||
|
rows=2, cols=1,
|
||||||
|
subplot_titles=('Total Engagement', 'Engagement Breakdown'),
|
||||||
|
vertical_spacing=0.1,
|
||||||
|
row_heights=[0.7, 0.3]
|
||||||
|
)
|
||||||
|
|
||||||
|
# Main engagement line
|
||||||
|
fig.add_trace(
|
||||||
|
go.Scatter(
|
||||||
|
x=df['Date'],
|
||||||
|
y=df['Engagement'],
|
||||||
|
mode='lines+markers',
|
||||||
|
name='Total Engagement',
|
||||||
|
line=dict(color='#1DA1F2', width=3),
|
||||||
|
marker=dict(size=6)
|
||||||
|
),
|
||||||
|
row=1, col=1
|
||||||
|
)
|
||||||
|
|
||||||
|
# Stacked area chart for breakdown
|
||||||
|
fig.add_trace(
|
||||||
|
go.Scatter(
|
||||||
|
x=df['Date'],
|
||||||
|
y=df['Likes'],
|
||||||
|
mode='lines',
|
||||||
|
name='Likes',
|
||||||
|
fill='tonexty',
|
||||||
|
line=dict(color='#E53E3E')
|
||||||
|
),
|
||||||
|
row=2, col=1
|
||||||
|
)
|
||||||
|
|
||||||
|
fig.add_trace(
|
||||||
|
go.Scatter(
|
||||||
|
x=df['Date'],
|
||||||
|
y=df['Retweets'],
|
||||||
|
mode='lines',
|
||||||
|
name='Retweets',
|
||||||
|
fill='tonexty',
|
||||||
|
line=dict(color='#38A169')
|
||||||
|
),
|
||||||
|
row=2, col=1
|
||||||
|
)
|
||||||
|
|
||||||
|
fig.add_trace(
|
||||||
|
go.Scatter(
|
||||||
|
x=df['Date'],
|
||||||
|
y=df['Replies'],
|
||||||
|
mode='lines',
|
||||||
|
name='Replies',
|
||||||
|
fill='tonexty',
|
||||||
|
line=dict(color='#D69E2E')
|
||||||
|
),
|
||||||
|
row=2, col=1
|
||||||
|
)
|
||||||
|
|
||||||
|
fig.update_layout(
|
||||||
|
height=500,
|
||||||
|
showlegend=True,
|
||||||
|
hovermode='x unified',
|
||||||
|
plot_bgcolor='rgba(0,0,0,0)',
|
||||||
|
paper_bgcolor='rgba(0,0,0,0)'
|
||||||
|
)
|
||||||
|
|
||||||
|
fig.update_xaxes(showgrid=True, gridwidth=1, gridcolor='rgba(0,0,0,0.1)')
|
||||||
|
fig.update_yaxes(showgrid=True, gridwidth=1, gridcolor='rgba(0,0,0,0.1)')
|
||||||
|
|
||||||
|
st.plotly_chart(fig, use_container_width=True)
|
||||||
|
|
||||||
|
def render_feature_grid():
|
||||||
|
"""Render the feature grid with modern cards."""
|
||||||
|
st.markdown("### 🛠️ Available Tools")
|
||||||
|
|
||||||
|
features = [
|
||||||
|
{
|
||||||
|
"title": "Smart Tweet Generator",
|
||||||
|
"description": "Create engaging tweets with AI assistance, hashtag suggestions, and emoji optimization",
|
||||||
|
"icon": "✨",
|
||||||
|
"status": "active",
|
||||||
|
"action": "tweet_generator"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Performance Predictor",
|
||||||
|
"description": "Predict tweet engagement and find optimal posting times",
|
||||||
|
"icon": "🔮",
|
||||||
|
"status": "coming_soon",
|
||||||
|
"action": None
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Content Calendar",
|
||||||
|
"description": "Plan and schedule your Twitter content strategy",
|
||||||
|
"icon": "📅",
|
||||||
|
"status": "coming_soon",
|
||||||
|
"action": None
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Hashtag Research",
|
||||||
|
"description": "Discover trending hashtags and analyze their performance",
|
||||||
|
"icon": "#️⃣",
|
||||||
|
"status": "coming_soon",
|
||||||
|
"action": None
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Visual Content",
|
||||||
|
"description": "Create quote cards, infographics, and visual tweets",
|
||||||
|
"icon": "🎨",
|
||||||
|
"status": "coming_soon",
|
||||||
|
"action": None
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Analytics Dashboard",
|
||||||
|
"description": "Deep dive into your Twitter performance metrics",
|
||||||
|
"icon": "📊",
|
||||||
|
"status": "coming_soon",
|
||||||
|
"action": None
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
# Create grid layout
|
||||||
|
cols = st.columns(3)
|
||||||
|
|
||||||
|
for i, feature in enumerate(features):
|
||||||
|
with cols[i % 3]:
|
||||||
|
status_class = "status-active" if feature["status"] == "active" else "status-coming-soon"
|
||||||
|
|
||||||
|
card_html = f"""
|
||||||
|
<div class="feature-card" onclick="handleFeatureClick('{feature['action']}')">
|
||||||
|
<span class="feature-icon">{feature['icon']}</span>
|
||||||
|
<h3 class="feature-title">{feature['title']}</h3>
|
||||||
|
<p class="feature-description">{feature['description']}</p>
|
||||||
|
<span class="feature-status {status_class}">{feature['status'].replace('_', ' ')}</span>
|
||||||
|
</div>
|
||||||
|
"""
|
||||||
|
|
||||||
|
st.markdown(card_html, unsafe_allow_html=True)
|
||||||
|
|
||||||
|
# Add button for active features
|
||||||
|
if feature["status"] == "active" and feature["action"]:
|
||||||
|
if st.button(f"Launch {feature['title']}", key=f"launch_{i}", use_container_width=True):
|
||||||
|
st.session_state.current_page = feature["action"]
|
||||||
|
st.rerun()
|
||||||
|
|
||||||
|
def render_recent_activity():
|
||||||
|
"""Render recent activity feed."""
|
||||||
|
st.markdown("### 📱 Recent Activity")
|
||||||
|
|
||||||
|
# Sample activity data (replace with real data)
|
||||||
|
activities = [
|
||||||
|
{"time": "2 hours ago", "action": "Generated tweet", "details": "AI-powered content about social media trends"},
|
||||||
|
{"time": "5 hours ago", "action": "Analyzed performance", "details": "Tweet received 45 likes and 12 retweets"},
|
||||||
|
{"time": "1 day ago", "action": "Scheduled tweet", "details": "Content scheduled for optimal posting time"},
|
||||||
|
{"time": "2 days ago", "action": "Updated hashtags", "details": "Added trending hashtags to improve reach"}
|
||||||
|
]
|
||||||
|
|
||||||
|
for activity in activities:
|
||||||
|
st.markdown(f"""
|
||||||
|
<div style="
|
||||||
|
background: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 1rem;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
border-left: 3px solid #1DA1F2;
|
||||||
|
box-shadow: 0 2px 8px rgba(0,0,0,0.05);
|
||||||
|
">
|
||||||
|
<div style="font-weight: 600; color: #2D3748; margin-bottom: 0.25rem;">
|
||||||
|
{activity['action']}
|
||||||
|
</div>
|
||||||
|
<div style="color: #718096; font-size: 0.9rem; margin-bottom: 0.25rem;">
|
||||||
|
{activity['details']}
|
||||||
|
</div>
|
||||||
|
<div style="color: #A0AEC0; font-size: 0.8rem;">
|
||||||
|
{activity['time']}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
""", unsafe_allow_html=True)
|
||||||
|
|
||||||
|
def run_dashboard():
|
||||||
|
"""Main function to run the enhanced Twitter dashboard."""
|
||||||
|
# Apply modern styling
|
||||||
|
apply_modern_styling()
|
||||||
|
|
||||||
|
# Initialize session state
|
||||||
|
if "current_page" not in st.session_state:
|
||||||
|
st.session_state.current_page = "dashboard"
|
||||||
|
|
||||||
|
# Handle page navigation
|
||||||
|
if st.session_state.current_page == "tweet_generator":
|
||||||
|
if st.button("← Back to Dashboard", key="back_to_dashboard"):
|
||||||
|
st.session_state.current_page = "dashboard"
|
||||||
|
st.rerun()
|
||||||
|
smart_tweet_generator()
|
||||||
|
return
|
||||||
|
|
||||||
|
# Main dashboard container
|
||||||
|
st.markdown('<div class="main-container">', unsafe_allow_html=True)
|
||||||
|
|
||||||
|
# Render dashboard header
|
||||||
|
render_dashboard_header()
|
||||||
|
|
||||||
|
# Render connection status
|
||||||
|
render_connection_status()
|
||||||
|
|
||||||
|
# Create main layout
|
||||||
|
tab1, tab2, tab3 = st.tabs(["🏠 Overview", "📊 Analytics", "⚙️ Settings"])
|
||||||
|
|
||||||
|
with tab1:
|
||||||
|
# Quick actions
|
||||||
|
render_quick_actions()
|
||||||
|
|
||||||
|
# Metrics overview
|
||||||
|
render_metrics_overview()
|
||||||
|
|
||||||
|
# Feature grid
|
||||||
|
render_feature_grid()
|
||||||
|
|
||||||
|
# Recent activity
|
||||||
|
col1, col2 = st.columns([2, 1])
|
||||||
|
with col1:
|
||||||
|
render_engagement_chart()
|
||||||
|
with col2:
|
||||||
|
render_recent_activity()
|
||||||
|
|
||||||
|
with tab2:
|
||||||
|
st.markdown("### 📈 Advanced Analytics")
|
||||||
|
|
||||||
|
# Time range selector
|
||||||
|
col1, col2 = st.columns([1, 3])
|
||||||
|
with col1:
|
||||||
|
time_range = st.selectbox(
|
||||||
|
"Time Range",
|
||||||
|
["Last 7 days", "Last 30 days", "Last 90 days", "Last year"],
|
||||||
|
index=1
|
||||||
|
)
|
||||||
|
|
||||||
|
# Detailed analytics
|
||||||
|
render_engagement_chart()
|
||||||
|
|
||||||
|
# Performance insights
|
||||||
|
st.markdown("### 💡 Performance Insights")
|
||||||
|
|
||||||
|
insights = [
|
||||||
|
"Your tweets perform 23% better when posted between 2-4 PM",
|
||||||
|
"Tweets with 2-3 hashtags get 15% more engagement",
|
||||||
|
"Visual content increases engagement by 35%",
|
||||||
|
"Questions in tweets boost replies by 28%"
|
||||||
|
]
|
||||||
|
|
||||||
|
for insight in insights:
|
||||||
|
st.info(f"💡 {insight}")
|
||||||
|
|
||||||
|
with tab3:
|
||||||
|
st.markdown("### ⚙️ Dashboard Settings")
|
||||||
|
|
||||||
|
# Twitter API settings
|
||||||
|
with st.expander("🔑 Twitter API Configuration", expanded=False):
|
||||||
|
st.markdown("Configure your Twitter API credentials to enable full functionality.")
|
||||||
|
|
||||||
|
api_key = st.text_input("API Key", type="password", help="Your Twitter API key")
|
||||||
|
api_secret = st.text_input("API Secret", type="password", help="Your Twitter API secret")
|
||||||
|
access_token = st.text_input("Access Token", type="password", help="Your Twitter access token")
|
||||||
|
access_token_secret = st.text_input("Access Token Secret", type="password", help="Your Twitter access token secret")
|
||||||
|
|
||||||
|
if st.button("Save API Configuration"):
|
||||||
|
# Save configuration (implement secure storage)
|
||||||
|
show_success_message("API configuration saved successfully!")
|
||||||
|
|
||||||
|
# Dashboard preferences
|
||||||
|
with st.expander("🎨 Dashboard Preferences", expanded=True):
|
||||||
|
theme = st.selectbox("Theme", ["Light", "Dark", "Auto"], index=0)
|
||||||
|
default_tone = st.selectbox("Default Tweet Tone", ["Professional", "Casual", "Humorous", "Inspirational"], index=1)
|
||||||
|
auto_hashtags = st.checkbox("Auto-suggest hashtags", value=True)
|
||||||
|
|
||||||
|
if st.button("Save Preferences"):
|
||||||
|
show_success_message("Preferences saved successfully!")
|
||||||
|
|
||||||
|
# Account management
|
||||||
|
with st.expander("👤 Account Management", expanded=False):
|
||||||
|
st.markdown("Manage your connected Twitter accounts and permissions.")
|
||||||
|
|
||||||
|
if get_from_session("twitter_connected", False):
|
||||||
|
st.success("✅ Twitter account connected")
|
||||||
|
if st.button("Disconnect Account"):
|
||||||
|
save_to_session("twitter_connected", False)
|
||||||
|
st.rerun()
|
||||||
|
else:
|
||||||
|
st.warning("⚠️ No Twitter account connected")
|
||||||
|
if st.button("Connect Account"):
|
||||||
|
save_to_session("twitter_connected", True)
|
||||||
|
st.rerun()
|
||||||
|
|
||||||
|
st.markdown('</div>', unsafe_allow_html=True)
|
||||||
|
|
||||||
|
# JavaScript for handling feature clicks
|
||||||
|
st.markdown("""
|
||||||
|
<script>
|
||||||
|
function handleFeatureClick(action) {
|
||||||
|
if (action && action !== 'null') {
|
||||||
|
// This would trigger a Streamlit rerun with the selected action
|
||||||
|
console.log('Feature clicked:', action);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
""", unsafe_allow_html=True)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
run_dashboard()
|
||||||
@@ -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,634 @@
|
|||||||
|
"""
|
||||||
|
Enhanced UI Cards with modern styling and improved functionality.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import streamlit as st
|
||||||
|
from typing import Dict, List, Optional, Callable
|
||||||
|
import plotly.express as px
|
||||||
|
import plotly.graph_objects as go
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
def apply_cards_styling():
|
||||||
|
"""Apply modern CSS styling for cards."""
|
||||||
|
st.markdown("""
|
||||||
|
<style>
|
||||||
|
/* Modern Card Styles */
|
||||||
|
.modern-card {
|
||||||
|
background: rgba(255, 255, 255, 0.95);
|
||||||
|
backdrop-filter: blur(20px);
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 1.5rem;
|
||||||
|
margin: 1rem 0;
|
||||||
|
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modern-card:hover {
|
||||||
|
transform: translateY(-4px);
|
||||||
|
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modern-card::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 4px;
|
||||||
|
background: linear-gradient(135deg, #1DA1F2, #0C85D0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-card {
|
||||||
|
background: white;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 1.5rem;
|
||||||
|
margin: 0.75rem 0;
|
||||||
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
|
||||||
|
border: 1px solid #E1E8ED;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-card:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 8px 30px rgba(29, 161, 242, 0.15);
|
||||||
|
border-color: #1DA1F2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-card-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-icon {
|
||||||
|
font-size: 2rem;
|
||||||
|
width: 60px;
|
||||||
|
height: 60px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: linear-gradient(135deg, #E6F7FF, #F0F9FF);
|
||||||
|
border-radius: 12px;
|
||||||
|
border: 2px solid #91D5FF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-title {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #2D3748;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-description {
|
||||||
|
color: #657786;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
line-height: 1.5;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-stats {
|
||||||
|
display: flex;
|
||||||
|
gap: 1rem;
|
||||||
|
margin-top: 1rem;
|
||||||
|
padding-top: 1rem;
|
||||||
|
border-top: 1px solid #E1E8ED;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-item {
|
||||||
|
text-align: center;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-value {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #1DA1F2;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-label {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: #657786;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tweet-card {
|
||||||
|
background: white;
|
||||||
|
border: 1px solid #E1E8ED;
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 1.5rem;
|
||||||
|
margin: 1rem 0;
|
||||||
|
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.08);
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tweet-card::before {
|
||||||
|
content: "🐦";
|
||||||
|
position: absolute;
|
||||||
|
top: -10px;
|
||||||
|
left: 20px;
|
||||||
|
background: white;
|
||||||
|
padding: 0 10px;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tweet-content {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
line-height: 1.5;
|
||||||
|
color: #14171A;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tweet-metadata {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
color: #657786;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
border-top: 1px solid #E1E8ED;
|
||||||
|
padding-top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.engagement-badge {
|
||||||
|
background: linear-gradient(135deg, #52C41A, #73D13D);
|
||||||
|
color: white;
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
border-radius: 20px;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.character-badge {
|
||||||
|
padding: 0.25rem 0.75rem;
|
||||||
|
border-radius: 20px;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.char-good { background: #E6F7FF; color: #1890FF; }
|
||||||
|
.char-warning { background: #FFF7E6; color: #FA8C16; }
|
||||||
|
.char-danger { background: #FFF1F0; color: #F5222D; }
|
||||||
|
|
||||||
|
.card-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5rem;
|
||||||
|
margin-top: 1rem;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-button {
|
||||||
|
background: #F7F9FA;
|
||||||
|
border: 1px solid #E1E8ED;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
color: #657786;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
text-decoration: none;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-button:hover {
|
||||||
|
background: #1DA1F2;
|
||||||
|
color: white;
|
||||||
|
border-color: #1DA1F2;
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-button.primary {
|
||||||
|
background: #1DA1F2;
|
||||||
|
color: white;
|
||||||
|
border-color: #1DA1F2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-button.primary:hover {
|
||||||
|
background: #0C85D0;
|
||||||
|
border-color: #0C85D0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metrics-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
|
||||||
|
gap: 1rem;
|
||||||
|
margin: 1rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-card {
|
||||||
|
background: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 1rem;
|
||||||
|
text-align: center;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
|
||||||
|
border: 1px solid #E1E8ED;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-value {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #1DA1F2;
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-label {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: #657786;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive Design */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.modern-card, .feature-card, .tweet-card {
|
||||||
|
margin: 0.5rem;
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-card-header {
|
||||||
|
flex-direction: column;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-stats {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-actions {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metrics-grid {
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
""", unsafe_allow_html=True)
|
||||||
|
|
||||||
|
class FeatureCard:
|
||||||
|
"""Modern feature card component."""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
title: str,
|
||||||
|
description: str,
|
||||||
|
icon: str = "🔧",
|
||||||
|
stats: Optional[Dict[str, any]] = None,
|
||||||
|
actions: Optional[List[Dict]] = None,
|
||||||
|
on_click: Optional[Callable] = None
|
||||||
|
):
|
||||||
|
self.title = title
|
||||||
|
self.description = description
|
||||||
|
self.icon = icon
|
||||||
|
self.stats = stats or {}
|
||||||
|
self.actions = actions or []
|
||||||
|
self.on_click = on_click
|
||||||
|
|
||||||
|
def render(self):
|
||||||
|
"""Render the feature card."""
|
||||||
|
apply_cards_styling()
|
||||||
|
|
||||||
|
# Create stats HTML
|
||||||
|
stats_html = ""
|
||||||
|
if self.stats:
|
||||||
|
stats_items = []
|
||||||
|
for label, value in self.stats.items():
|
||||||
|
stats_items.append(f"""
|
||||||
|
<div class="stat-item">
|
||||||
|
<span class="stat-value">{value}</span>
|
||||||
|
<span class="stat-label">{label}</span>
|
||||||
|
</div>
|
||||||
|
""")
|
||||||
|
stats_html = f"""
|
||||||
|
<div class="feature-stats">
|
||||||
|
{''.join(stats_items)}
|
||||||
|
</div>
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Create actions HTML
|
||||||
|
actions_html = ""
|
||||||
|
if self.actions:
|
||||||
|
action_buttons = []
|
||||||
|
for action in self.actions:
|
||||||
|
button_class = "action-button"
|
||||||
|
if action.get("primary", False):
|
||||||
|
button_class += " primary"
|
||||||
|
|
||||||
|
action_buttons.append(f"""
|
||||||
|
<button class="{button_class}" onclick="{action.get('onclick', '')}">
|
||||||
|
{action.get('icon', '')} {action.get('label', 'Action')}
|
||||||
|
</button>
|
||||||
|
""")
|
||||||
|
actions_html = f"""
|
||||||
|
<div class="card-actions">
|
||||||
|
{''.join(action_buttons)}
|
||||||
|
</div>
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Render the card
|
||||||
|
card_html = f"""
|
||||||
|
<div class="feature-card" onclick="{self.on_click or ''}">
|
||||||
|
<div class="feature-card-header">
|
||||||
|
<div class="feature-icon">{self.icon}</div>
|
||||||
|
<div>
|
||||||
|
<h3 class="feature-title">{self.title}</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p class="feature-description">{self.description}</p>
|
||||||
|
{stats_html}
|
||||||
|
{actions_html}
|
||||||
|
</div>
|
||||||
|
"""
|
||||||
|
|
||||||
|
st.markdown(card_html, unsafe_allow_html=True)
|
||||||
|
|
||||||
|
class TweetCard:
|
||||||
|
"""Modern tweet card component."""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
content: str,
|
||||||
|
engagement_score: int = 0,
|
||||||
|
hashtags: List[str] = None,
|
||||||
|
emojis: List[str] = None,
|
||||||
|
metrics: Optional[Dict] = None,
|
||||||
|
timestamp: Optional[str] = None,
|
||||||
|
on_copy: Optional[Callable] = None,
|
||||||
|
on_save: Optional[Callable] = None,
|
||||||
|
on_edit: Optional[Callable] = None,
|
||||||
|
on_post: Optional[Callable] = None
|
||||||
|
):
|
||||||
|
self.content = content
|
||||||
|
self.engagement_score = engagement_score
|
||||||
|
self.hashtags = hashtags or []
|
||||||
|
self.emojis = emojis or []
|
||||||
|
self.metrics = metrics or {}
|
||||||
|
self.timestamp = timestamp or datetime.now().strftime("%Y-%m-%d %H:%M")
|
||||||
|
self.on_copy = on_copy
|
||||||
|
self.on_save = on_save
|
||||||
|
self.on_edit = on_edit
|
||||||
|
self.on_post = on_post
|
||||||
|
|
||||||
|
def _get_character_info(self):
|
||||||
|
"""Get character count information."""
|
||||||
|
full_text = f"{self.content} {' '.join(self.hashtags)}"
|
||||||
|
count = len(full_text)
|
||||||
|
remaining = 280 - count
|
||||||
|
|
||||||
|
if count <= 240:
|
||||||
|
status_class = "char-good"
|
||||||
|
elif count <= 270:
|
||||||
|
status_class = "char-warning"
|
||||||
|
else:
|
||||||
|
status_class = "char-danger"
|
||||||
|
|
||||||
|
return {
|
||||||
|
"count": count,
|
||||||
|
"remaining": remaining,
|
||||||
|
"status_class": status_class
|
||||||
|
}
|
||||||
|
|
||||||
|
def render(self):
|
||||||
|
"""Render the tweet card."""
|
||||||
|
apply_cards_styling()
|
||||||
|
|
||||||
|
char_info = self._get_character_info()
|
||||||
|
full_content = f"{self.content} {' '.join(self.hashtags)}"
|
||||||
|
|
||||||
|
# Create metrics HTML
|
||||||
|
metrics_html = ""
|
||||||
|
if self.metrics:
|
||||||
|
metric_items = []
|
||||||
|
for label, value in self.metrics.items():
|
||||||
|
metric_items.append(f"""
|
||||||
|
<div class="metric-card">
|
||||||
|
<span class="metric-value">{value}</span>
|
||||||
|
<span class="metric-label">{label}</span>
|
||||||
|
</div>
|
||||||
|
""")
|
||||||
|
metrics_html = f"""
|
||||||
|
<div class="metrics-grid">
|
||||||
|
{''.join(metric_items)}
|
||||||
|
</div>
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Create actions
|
||||||
|
actions = []
|
||||||
|
if self.on_copy:
|
||||||
|
actions.append('<button class="action-button" onclick="copyTweet()">📋 Copy</button>')
|
||||||
|
if self.on_save:
|
||||||
|
actions.append('<button class="action-button" onclick="saveTweet()">💾 Save</button>')
|
||||||
|
if self.on_edit:
|
||||||
|
actions.append('<button class="action-button" onclick="editTweet()">✏️ Edit</button>')
|
||||||
|
if self.on_post:
|
||||||
|
actions.append('<button class="action-button primary" onclick="postTweet()">🐦 Post</button>')
|
||||||
|
|
||||||
|
actions_html = f'<div class="card-actions">{"".join(actions)}</div>' if actions else ""
|
||||||
|
|
||||||
|
# Render the card
|
||||||
|
card_html = f"""
|
||||||
|
<div class="tweet-card">
|
||||||
|
<div class="tweet-content">{full_content}</div>
|
||||||
|
{metrics_html}
|
||||||
|
<div class="tweet-metadata">
|
||||||
|
<div class="engagement-badge">
|
||||||
|
📊 {self.engagement_score}% Engagement
|
||||||
|
</div>
|
||||||
|
<div class="character-badge {char_info['status_class']}">
|
||||||
|
{char_info['count']}/280
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{actions_html}
|
||||||
|
</div>
|
||||||
|
"""
|
||||||
|
|
||||||
|
st.markdown(card_html, unsafe_allow_html=True)
|
||||||
|
|
||||||
|
class MetricsCard:
|
||||||
|
"""Modern metrics display card."""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
title: str,
|
||||||
|
metrics: Dict[str, any],
|
||||||
|
chart_data: Optional[Dict] = None,
|
||||||
|
trend: Optional[str] = None
|
||||||
|
):
|
||||||
|
self.title = title
|
||||||
|
self.metrics = metrics
|
||||||
|
self.chart_data = chart_data
|
||||||
|
self.trend = trend
|
||||||
|
|
||||||
|
def render(self):
|
||||||
|
"""Render the metrics card."""
|
||||||
|
apply_cards_styling()
|
||||||
|
|
||||||
|
# Create metrics grid
|
||||||
|
metric_items = []
|
||||||
|
for label, value in self.metrics.items():
|
||||||
|
metric_items.append(f"""
|
||||||
|
<div class="metric-card">
|
||||||
|
<span class="metric-value">{value}</span>
|
||||||
|
<span class="metric-label">{label}</span>
|
||||||
|
</div>
|
||||||
|
""")
|
||||||
|
|
||||||
|
metrics_grid = f"""
|
||||||
|
<div class="metrics-grid">
|
||||||
|
{''.join(metric_items)}
|
||||||
|
</div>
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Add trend indicator
|
||||||
|
trend_html = ""
|
||||||
|
if self.trend:
|
||||||
|
trend_color = "#52C41A" if "up" in self.trend.lower() else "#F5222D"
|
||||||
|
trend_icon = "📈" if "up" in self.trend.lower() else "📉"
|
||||||
|
trend_html = f"""
|
||||||
|
<div style="text-align: center; margin-top: 1rem; color: {trend_color};">
|
||||||
|
{trend_icon} {self.trend}
|
||||||
|
</div>
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Render the card
|
||||||
|
card_html = f"""
|
||||||
|
<div class="modern-card">
|
||||||
|
<h3 style="margin-bottom: 1rem; color: #2D3748;">{self.title}</h3>
|
||||||
|
{metrics_grid}
|
||||||
|
{trend_html}
|
||||||
|
</div>
|
||||||
|
"""
|
||||||
|
|
||||||
|
st.markdown(card_html, unsafe_allow_html=True)
|
||||||
|
|
||||||
|
# Add chart if provided
|
||||||
|
if self.chart_data:
|
||||||
|
self._render_chart()
|
||||||
|
|
||||||
|
def _render_chart(self):
|
||||||
|
"""Render chart for metrics."""
|
||||||
|
if self.chart_data.get("type") == "line":
|
||||||
|
fig = px.line(
|
||||||
|
x=self.chart_data.get("x", []),
|
||||||
|
y=self.chart_data.get("y", []),
|
||||||
|
title=self.chart_data.get("title", ""),
|
||||||
|
labels=self.chart_data.get("labels", {})
|
||||||
|
)
|
||||||
|
elif self.chart_data.get("type") == "bar":
|
||||||
|
fig = px.bar(
|
||||||
|
x=self.chart_data.get("x", []),
|
||||||
|
y=self.chart_data.get("y", []),
|
||||||
|
title=self.chart_data.get("title", ""),
|
||||||
|
labels=self.chart_data.get("labels", {})
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
return
|
||||||
|
|
||||||
|
fig.update_layout(
|
||||||
|
plot_bgcolor='rgba(0,0,0,0)',
|
||||||
|
paper_bgcolor='rgba(0,0,0,0)',
|
||||||
|
showlegend=False,
|
||||||
|
height=300
|
||||||
|
)
|
||||||
|
|
||||||
|
st.plotly_chart(fig, use_container_width=True)
|
||||||
|
|
||||||
|
class StatusCard:
|
||||||
|
"""Status indicator card."""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
title: str,
|
||||||
|
status: str,
|
||||||
|
message: str,
|
||||||
|
icon: str = "ℹ️",
|
||||||
|
actions: Optional[List[Dict]] = None
|
||||||
|
):
|
||||||
|
self.title = title
|
||||||
|
self.status = status # success, warning, error, info
|
||||||
|
self.message = message
|
||||||
|
self.icon = icon
|
||||||
|
self.actions = actions or []
|
||||||
|
|
||||||
|
def render(self):
|
||||||
|
"""Render the status card."""
|
||||||
|
apply_cards_styling()
|
||||||
|
|
||||||
|
# Status colors
|
||||||
|
status_colors = {
|
||||||
|
"success": "#52C41A",
|
||||||
|
"warning": "#FA8C16",
|
||||||
|
"error": "#F5222D",
|
||||||
|
"info": "#1890FF"
|
||||||
|
}
|
||||||
|
|
||||||
|
color = status_colors.get(self.status, "#1890FF")
|
||||||
|
|
||||||
|
# Create actions
|
||||||
|
actions_html = ""
|
||||||
|
if self.actions:
|
||||||
|
action_buttons = []
|
||||||
|
for action in self.actions:
|
||||||
|
action_buttons.append(f"""
|
||||||
|
<button class="action-button" onclick="{action.get('onclick', '')}">
|
||||||
|
{action.get('icon', '')} {action.get('label', 'Action')}
|
||||||
|
</button>
|
||||||
|
""")
|
||||||
|
actions_html = f"""
|
||||||
|
<div class="card-actions">
|
||||||
|
{''.join(action_buttons)}
|
||||||
|
</div>
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Render the card
|
||||||
|
card_html = f"""
|
||||||
|
<div class="modern-card" style="border-left: 4px solid {color};">
|
||||||
|
<div style="display: flex; align-items: center; gap: 1rem; margin-bottom: 1rem;">
|
||||||
|
<span style="font-size: 2rem;">{self.icon}</span>
|
||||||
|
<div>
|
||||||
|
<h3 style="margin: 0; color: #2D3748;">{self.title}</h3>
|
||||||
|
<span style="color: {color}; font-weight: 600; text-transform: uppercase; font-size: 0.8rem;">
|
||||||
|
{self.status}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p style="color: #657786; margin-bottom: 1rem;">{self.message}</p>
|
||||||
|
{actions_html}
|
||||||
|
</div>
|
||||||
|
"""
|
||||||
|
|
||||||
|
st.markdown(card_html, unsafe_allow_html=True)
|
||||||
|
|
||||||
|
# Utility functions for creating common cards
|
||||||
|
def create_feature_card(title: str, description: str, icon: str = "🔧", **kwargs):
|
||||||
|
"""Create and render a feature card."""
|
||||||
|
card = FeatureCard(title, description, icon, **kwargs)
|
||||||
|
card.render()
|
||||||
|
|
||||||
|
def create_tweet_card(content: str, **kwargs):
|
||||||
|
"""Create and render a tweet card."""
|
||||||
|
card = TweetCard(content, **kwargs)
|
||||||
|
card.render()
|
||||||
|
|
||||||
|
def create_metrics_card(title: str, metrics: Dict, **kwargs):
|
||||||
|
"""Create and render a metrics card."""
|
||||||
|
card = MetricsCard(title, metrics, **kwargs)
|
||||||
|
card.render()
|
||||||
|
|
||||||
|
def create_status_card(title: str, status: str, message: str, **kwargs):
|
||||||
|
"""Create and render a status card."""
|
||||||
|
card = StatusCard(title, status, message, **kwargs)
|
||||||
|
card.render()
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,554 @@
|
|||||||
|
"""
|
||||||
|
Enhanced Navigation Component for Twitter UI with modern styling and improved functionality.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import streamlit as st
|
||||||
|
from typing import Dict, List, Optional, Callable, Any
|
||||||
|
from ..styles.theme import Theme
|
||||||
|
import os
|
||||||
|
|
||||||
|
def apply_navigation_styling():
|
||||||
|
"""Apply modern CSS styling for navigation components."""
|
||||||
|
st.markdown("""
|
||||||
|
<style>
|
||||||
|
/* Navigation Styles */
|
||||||
|
.nav-container {
|
||||||
|
background: rgba(255, 255, 255, 0.95);
|
||||||
|
backdrop-filter: blur(20px);
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 1rem;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
padding-bottom: 1rem;
|
||||||
|
border-bottom: 2px solid #E2E8F0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-title {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #1DA1F2;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-status {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
border-radius: 20px;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-connected {
|
||||||
|
background: linear-gradient(135deg, #52C41A, #73D13D);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-disconnected {
|
||||||
|
background: linear-gradient(135deg, #FA8C16, #FFA940);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-menu {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5rem;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-item {
|
||||||
|
background: #F7F9FA;
|
||||||
|
border: 2px solid transparent;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 0.75rem 1.5rem;
|
||||||
|
color: #657786;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
text-decoration: none;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-item:hover {
|
||||||
|
background: #E1F5FE;
|
||||||
|
border-color: #1DA1F2;
|
||||||
|
color: #1DA1F2;
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 4px 15px rgba(29, 161, 242, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-item.active {
|
||||||
|
background: linear-gradient(135deg, #1DA1F2, #0C85D0);
|
||||||
|
color: white;
|
||||||
|
border-color: #1DA1F2;
|
||||||
|
box-shadow: 0 4px 15px rgba(29, 161, 242, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-item.active:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 6px 20px rgba(29, 161, 242, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-breadcrumb {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: #657786;
|
||||||
|
}
|
||||||
|
|
||||||
|
.breadcrumb-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.breadcrumb-separator {
|
||||||
|
color: #CBD5E0;
|
||||||
|
margin: 0 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5rem;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-button {
|
||||||
|
background: linear-gradient(135deg, #52C41A, #73D13D);
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-button:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 4px 15px rgba(82, 196, 26, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-button.secondary {
|
||||||
|
background: #F7F9FA;
|
||||||
|
color: #657786;
|
||||||
|
border: 1px solid #E1E8ED;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-button.secondary:hover {
|
||||||
|
background: #E1F5FE;
|
||||||
|
color: #1DA1F2;
|
||||||
|
border-color: #1DA1F2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mobile Responsive */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.nav-header {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-menu {
|
||||||
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-item {
|
||||||
|
width: 100%;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-actions {
|
||||||
|
width: 100%;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
""", unsafe_allow_html=True)
|
||||||
|
|
||||||
|
class TwitterNavigation:
|
||||||
|
"""Enhanced navigation component for Twitter dashboard."""
|
||||||
|
|
||||||
|
def __init__(self, theme: Optional[Theme] = None):
|
||||||
|
self.theme = theme or Theme()
|
||||||
|
self.current_page = st.session_state.get('current_page', 'dashboard')
|
||||||
|
|
||||||
|
def render_header(self, title: str = "Twitter AI Assistant", show_status: bool = True):
|
||||||
|
"""Render the navigation header with title and status."""
|
||||||
|
apply_navigation_styling()
|
||||||
|
|
||||||
|
st.markdown('<div class="nav-container">', unsafe_allow_html=True)
|
||||||
|
st.markdown('<div class="nav-header">', unsafe_allow_html=True)
|
||||||
|
|
||||||
|
# Title
|
||||||
|
st.markdown(f'<div class="nav-title">🐦 {title}</div>', unsafe_allow_html=True)
|
||||||
|
|
||||||
|
# Status indicator
|
||||||
|
if show_status:
|
||||||
|
twitter_connected = self._check_twitter_connection()
|
||||||
|
status_class = "status-connected" if twitter_connected else "status-disconnected"
|
||||||
|
status_text = "Connected" if twitter_connected else "Not Connected"
|
||||||
|
status_icon = "✅" if twitter_connected else "⚠️"
|
||||||
|
|
||||||
|
st.markdown(f'''
|
||||||
|
<div class="nav-status {status_class}">
|
||||||
|
{status_icon} Twitter {status_text}
|
||||||
|
</div>
|
||||||
|
''', unsafe_allow_html=True)
|
||||||
|
|
||||||
|
st.markdown('</div>', unsafe_allow_html=True)
|
||||||
|
|
||||||
|
def render_menu(self, menu_items: List[Dict], current_page: Optional[str] = None):
|
||||||
|
"""Render navigation menu with items."""
|
||||||
|
if current_page:
|
||||||
|
self.current_page = current_page
|
||||||
|
st.session_state.current_page = current_page
|
||||||
|
|
||||||
|
st.markdown('<div class="nav-menu">', unsafe_allow_html=True)
|
||||||
|
|
||||||
|
cols = st.columns(len(menu_items))
|
||||||
|
|
||||||
|
for i, item in enumerate(menu_items):
|
||||||
|
with cols[i]:
|
||||||
|
active_class = "active" if item.get('key') == self.current_page else ""
|
||||||
|
|
||||||
|
if st.button(
|
||||||
|
f"{item.get('icon', '')} {item.get('label', '')}",
|
||||||
|
key=f"nav_{item.get('key', i)}",
|
||||||
|
use_container_width=True,
|
||||||
|
type="primary" if active_class else "secondary"
|
||||||
|
):
|
||||||
|
st.session_state.current_page = item.get('key')
|
||||||
|
if item.get('callback'):
|
||||||
|
item['callback']()
|
||||||
|
st.rerun()
|
||||||
|
|
||||||
|
st.markdown('</div>', unsafe_allow_html=True)
|
||||||
|
st.markdown('</div>', unsafe_allow_html=True)
|
||||||
|
|
||||||
|
return st.session_state.get('current_page', menu_items[0].get('key'))
|
||||||
|
|
||||||
|
def render_breadcrumb(self, items: List[Dict]):
|
||||||
|
"""Render breadcrumb navigation."""
|
||||||
|
st.markdown('<div class="nav-breadcrumb">', unsafe_allow_html=True)
|
||||||
|
|
||||||
|
for i, item in enumerate(items):
|
||||||
|
if i > 0:
|
||||||
|
st.markdown('<span class="breadcrumb-separator">›</span>', unsafe_allow_html=True)
|
||||||
|
|
||||||
|
icon = item.get('icon', '')
|
||||||
|
label = item.get('label', '')
|
||||||
|
|
||||||
|
if item.get('active', False):
|
||||||
|
st.markdown(f'<span class="breadcrumb-item"><strong>{icon} {label}</strong></span>', unsafe_allow_html=True)
|
||||||
|
else:
|
||||||
|
st.markdown(f'<span class="breadcrumb-item">{icon} {label}</span>', unsafe_allow_html=True)
|
||||||
|
|
||||||
|
st.markdown('</div>', unsafe_allow_html=True)
|
||||||
|
|
||||||
|
def render_actions(self, actions: List[Dict]):
|
||||||
|
"""Render action buttons in navigation."""
|
||||||
|
st.markdown('<div class="nav-actions">', unsafe_allow_html=True)
|
||||||
|
|
||||||
|
cols = st.columns(len(actions))
|
||||||
|
|
||||||
|
for i, action in enumerate(actions):
|
||||||
|
with cols[i]:
|
||||||
|
button_type = action.get('type', 'primary')
|
||||||
|
|
||||||
|
if st.button(
|
||||||
|
f"{action.get('icon', '')} {action.get('label', '')}",
|
||||||
|
key=f"action_{action.get('key', i)}",
|
||||||
|
type=button_type,
|
||||||
|
use_container_width=True,
|
||||||
|
help=action.get('help', '')
|
||||||
|
):
|
||||||
|
if action.get('callback'):
|
||||||
|
action['callback']()
|
||||||
|
|
||||||
|
st.markdown('</div>', unsafe_allow_html=True)
|
||||||
|
|
||||||
|
def render_sidebar_menu(self, menu_items: List[Dict]):
|
||||||
|
"""Render sidebar navigation menu."""
|
||||||
|
with st.sidebar:
|
||||||
|
st.markdown("### 🐦 Twitter Tools")
|
||||||
|
|
||||||
|
for item in menu_items:
|
||||||
|
icon = item.get('icon', '')
|
||||||
|
label = item.get('label', '')
|
||||||
|
key = item.get('key', '')
|
||||||
|
|
||||||
|
if st.button(f"{icon} {label}", key=f"sidebar_{key}", use_container_width=True):
|
||||||
|
st.session_state.current_page = key
|
||||||
|
if item.get('callback'):
|
||||||
|
item['callback']()
|
||||||
|
st.rerun()
|
||||||
|
|
||||||
|
# Twitter connection status in sidebar
|
||||||
|
st.markdown("---")
|
||||||
|
twitter_connected = self._check_twitter_connection()
|
||||||
|
|
||||||
|
if twitter_connected:
|
||||||
|
st.success("🐦 Twitter Connected")
|
||||||
|
else:
|
||||||
|
st.warning("⚠️ Twitter Not Connected")
|
||||||
|
if st.button("🔧 Configure Twitter", use_container_width=True):
|
||||||
|
st.session_state.show_twitter_config = True
|
||||||
|
st.rerun()
|
||||||
|
|
||||||
|
def _check_twitter_connection(self) -> bool:
|
||||||
|
"""Check if Twitter is connected."""
|
||||||
|
twitter_config = st.session_state.get('twitter_config', {})
|
||||||
|
return bool(twitter_config and all([
|
||||||
|
twitter_config.get('api_key'),
|
||||||
|
twitter_config.get('api_secret'),
|
||||||
|
twitter_config.get('access_token'),
|
||||||
|
twitter_config.get('access_token_secret')
|
||||||
|
]))
|
||||||
|
|
||||||
|
class Sidebar:
|
||||||
|
"""Sidebar navigation component."""
|
||||||
|
|
||||||
|
def __init__(self, title: str = "Navigation", logo: Optional[str] = None):
|
||||||
|
"""Initialize the sidebar."""
|
||||||
|
self.title = title
|
||||||
|
self.logo = logo
|
||||||
|
self.menu_items = []
|
||||||
|
|
||||||
|
def add_menu_item(self, label: str, icon: str, key: str, callback: Optional[Callable] = None):
|
||||||
|
"""Add a menu item to the sidebar."""
|
||||||
|
self.menu_items.append({
|
||||||
|
'label': label,
|
||||||
|
'icon': icon,
|
||||||
|
'key': key,
|
||||||
|
'callback': callback
|
||||||
|
})
|
||||||
|
|
||||||
|
def render(self) -> str:
|
||||||
|
"""Render the sidebar and return the selected page."""
|
||||||
|
with st.sidebar:
|
||||||
|
# Logo and title
|
||||||
|
if self.logo and os.path.exists(self.logo):
|
||||||
|
st.image(self.logo, width=100)
|
||||||
|
st.title(self.title)
|
||||||
|
st.markdown("---")
|
||||||
|
|
||||||
|
# Menu items
|
||||||
|
selected_page = None
|
||||||
|
for item in self.menu_items:
|
||||||
|
if st.button(
|
||||||
|
f"{item['icon']} {item['label']}",
|
||||||
|
key=f"sidebar_{item['key']}",
|
||||||
|
use_container_width=True
|
||||||
|
):
|
||||||
|
selected_page = item['key']
|
||||||
|
if item.get('callback'):
|
||||||
|
item['callback']()
|
||||||
|
|
||||||
|
return selected_page or st.session_state.get('current_page', 'dashboard')
|
||||||
|
|
||||||
|
|
||||||
|
class Header:
|
||||||
|
"""Header component with title and actions."""
|
||||||
|
|
||||||
|
def __init__(self, title: str = "Dashboard", subtitle: str = ""):
|
||||||
|
"""Initialize the header."""
|
||||||
|
self.title = title
|
||||||
|
self.subtitle = subtitle
|
||||||
|
self.actions = []
|
||||||
|
|
||||||
|
def add_action(self, label: str, icon: str, callback: Callable, help_text: str = ""):
|
||||||
|
"""Add an action button to the header."""
|
||||||
|
self.actions.append({
|
||||||
|
'label': label,
|
||||||
|
'icon': icon,
|
||||||
|
'callback': callback,
|
||||||
|
'help': help_text
|
||||||
|
})
|
||||||
|
|
||||||
|
def render(self):
|
||||||
|
"""Render the header."""
|
||||||
|
col1, col2 = st.columns([3, 1])
|
||||||
|
|
||||||
|
with col1:
|
||||||
|
st.title(f"{self.title}")
|
||||||
|
if self.subtitle:
|
||||||
|
st.markdown(f"*{self.subtitle}*")
|
||||||
|
|
||||||
|
with col2:
|
||||||
|
if self.actions:
|
||||||
|
for i, action in enumerate(self.actions):
|
||||||
|
if st.button(
|
||||||
|
f"{action['icon']} {action['label']}",
|
||||||
|
key=f"header_action_{i}",
|
||||||
|
help=action.get('help', ''),
|
||||||
|
use_container_width=True
|
||||||
|
):
|
||||||
|
action['callback']()
|
||||||
|
|
||||||
|
|
||||||
|
class Tabs:
|
||||||
|
"""Tab navigation component."""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
"""Initialize the tabs."""
|
||||||
|
self.tabs = []
|
||||||
|
|
||||||
|
def add_tab(self, label: str, icon: str, content_func: Callable):
|
||||||
|
"""Add a tab."""
|
||||||
|
self.tabs.append({
|
||||||
|
'label': label,
|
||||||
|
'icon': icon,
|
||||||
|
'content_func': content_func
|
||||||
|
})
|
||||||
|
|
||||||
|
def render(self):
|
||||||
|
"""Render the tabs."""
|
||||||
|
if not self.tabs:
|
||||||
|
return
|
||||||
|
|
||||||
|
tab_labels = [f"{tab['icon']} {tab['label']}" for tab in self.tabs]
|
||||||
|
selected_tabs = st.tabs(tab_labels)
|
||||||
|
|
||||||
|
for i, tab in enumerate(self.tabs):
|
||||||
|
with selected_tabs[i]:
|
||||||
|
tab['content_func']()
|
||||||
|
|
||||||
|
|
||||||
|
class Breadcrumbs:
|
||||||
|
"""Breadcrumb navigation component."""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
"""Initialize breadcrumbs."""
|
||||||
|
self.items = []
|
||||||
|
|
||||||
|
def add_item(self, label: str, key: str = None, callback: Callable = None):
|
||||||
|
"""Add a breadcrumb item."""
|
||||||
|
self.items.append({
|
||||||
|
'label': label,
|
||||||
|
'key': key,
|
||||||
|
'callback': callback
|
||||||
|
})
|
||||||
|
|
||||||
|
def render(self):
|
||||||
|
"""Render the breadcrumbs."""
|
||||||
|
if not self.items:
|
||||||
|
return
|
||||||
|
|
||||||
|
breadcrumb_html = '<div class="nav-breadcrumb">'
|
||||||
|
|
||||||
|
for i, item in enumerate(self.items):
|
||||||
|
if i > 0:
|
||||||
|
breadcrumb_html += '<span class="breadcrumb-separator">›</span>'
|
||||||
|
|
||||||
|
if item.get('callback'):
|
||||||
|
breadcrumb_html += f'<span class="breadcrumb-item clickable" onclick="handleBreadcrumbClick(\'{item["key"]}\')">{item["label"]}</span>'
|
||||||
|
else:
|
||||||
|
breadcrumb_html += f'<span class="breadcrumb-item">{item["label"]}</span>'
|
||||||
|
|
||||||
|
breadcrumb_html += '</div>'
|
||||||
|
st.markdown(breadcrumb_html, unsafe_allow_html=True)
|
||||||
|
|
||||||
|
|
||||||
|
def create_main_navigation() -> TwitterNavigation:
|
||||||
|
"""Create and return the main navigation instance."""
|
||||||
|
return TwitterNavigation()
|
||||||
|
|
||||||
|
def render_page_header(title: str, subtitle: str = "", icon: str = ""):
|
||||||
|
"""Render a consistent page header."""
|
||||||
|
st.markdown(f"""
|
||||||
|
<div style="text-align: center; margin-bottom: 2rem; padding: 2rem; background: linear-gradient(135deg, #E6F7FF, #F0F9FF); border-radius: 16px;">
|
||||||
|
<h1 style="color: #1DA1F2; margin-bottom: 0.5rem;">{icon} {title}</h1>
|
||||||
|
{f'<p style="color: #657786; font-size: 1.1rem;">{subtitle}</p>' if subtitle else ''}
|
||||||
|
</div>
|
||||||
|
""", unsafe_allow_html=True)
|
||||||
|
|
||||||
|
def render_quick_actions(actions: List[Dict]):
|
||||||
|
"""Render quick action buttons."""
|
||||||
|
st.markdown("### ⚡ Quick Actions")
|
||||||
|
|
||||||
|
cols = st.columns(len(actions))
|
||||||
|
|
||||||
|
for i, action in enumerate(actions):
|
||||||
|
with cols[i]:
|
||||||
|
if st.button(
|
||||||
|
f"{action.get('icon', '')} {action.get('label', '')}",
|
||||||
|
key=f"quick_action_{i}",
|
||||||
|
use_container_width=True,
|
||||||
|
help=action.get('help', '')
|
||||||
|
):
|
||||||
|
if action.get('callback'):
|
||||||
|
action['callback']()
|
||||||
|
|
||||||
|
# Default menu items for Twitter dashboard
|
||||||
|
DEFAULT_MENU_ITEMS = [
|
||||||
|
{
|
||||||
|
'key': 'dashboard',
|
||||||
|
'label': 'Dashboard',
|
||||||
|
'icon': '🏠',
|
||||||
|
'help': 'Main dashboard overview'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'key': 'generator',
|
||||||
|
'label': 'Tweet Generator',
|
||||||
|
'icon': '✨',
|
||||||
|
'help': 'AI-powered tweet generation'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'key': 'analytics',
|
||||||
|
'label': 'Analytics',
|
||||||
|
'icon': '📊',
|
||||||
|
'help': 'Tweet performance analytics'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'key': 'scheduler',
|
||||||
|
'label': 'Scheduler',
|
||||||
|
'icon': '📅',
|
||||||
|
'help': 'Schedule tweets for later'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'key': 'settings',
|
||||||
|
'label': 'Settings',
|
||||||
|
'icon': '⚙️',
|
||||||
|
'help': 'Twitter account and API settings'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
DEFAULT_QUICK_ACTIONS = [
|
||||||
|
{
|
||||||
|
'key': 'new_tweet',
|
||||||
|
'label': 'New Tweet',
|
||||||
|
'icon': '✍️',
|
||||||
|
'help': 'Create a new tweet'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'key': 'ai_generate',
|
||||||
|
'label': 'AI Generate',
|
||||||
|
'icon': '🤖',
|
||||||
|
'help': 'Generate tweets with AI'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'key': 'view_analytics',
|
||||||
|
'label': 'View Analytics',
|
||||||
|
'icon': '📈',
|
||||||
|
'help': 'Check tweet performance'
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -0,0 +1,278 @@
|
|||||||
|
"""
|
||||||
|
Main dashboard for Twitter UI.
|
||||||
|
Combines all UI components into a cohesive interface.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import streamlit as st
|
||||||
|
from typing import Dict, Any, Optional
|
||||||
|
from .components.cards import FeatureCard, TweetCard
|
||||||
|
from .components.forms import TweetForm, SettingsForm
|
||||||
|
from .components.navigation import Sidebar, Header, Tabs, Breadcrumbs
|
||||||
|
from .styles.theme import Theme
|
||||||
|
import os
|
||||||
|
|
||||||
|
class TwitterDashboard:
|
||||||
|
"""Main dashboard class for Twitter UI."""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
"""Initialize the Twitter dashboard."""
|
||||||
|
self.setup_theme()
|
||||||
|
self.setup_navigation()
|
||||||
|
self.setup_state()
|
||||||
|
|
||||||
|
def get_logo_path(self) -> str:
|
||||||
|
"""Get the best available logo path with fallbacks."""
|
||||||
|
# List of potential logo paths in order of preference
|
||||||
|
logo_paths = [
|
||||||
|
"lib/workspace/alwrity_logo.png",
|
||||||
|
"lib/workspace/AskAlwrity-min.ico",
|
||||||
|
"lib/workspace/alwrity_ai_writer.png"
|
||||||
|
]
|
||||||
|
|
||||||
|
for path in logo_paths:
|
||||||
|
if os.path.exists(path):
|
||||||
|
return path
|
||||||
|
|
||||||
|
# If no logo files are found, return None
|
||||||
|
return None
|
||||||
|
|
||||||
|
def setup_theme(self) -> None:
|
||||||
|
"""Setup theme and styling."""
|
||||||
|
Theme.apply()
|
||||||
|
|
||||||
|
def setup_navigation(self) -> None:
|
||||||
|
"""Setup navigation components."""
|
||||||
|
# Sidebar
|
||||||
|
self.sidebar = Sidebar(
|
||||||
|
title="Twitter Tools",
|
||||||
|
logo=self.get_logo_path()
|
||||||
|
)
|
||||||
|
|
||||||
|
# Add menu items
|
||||||
|
self.sidebar.add_menu_item("Dashboard", "📊", "dashboard")
|
||||||
|
self.sidebar.add_menu_item("Tweet Generator", "✍️", "tweet_generator")
|
||||||
|
self.sidebar.add_menu_item("Analytics", "📈", "analytics")
|
||||||
|
self.sidebar.add_menu_item("Settings", "⚙️", "settings")
|
||||||
|
|
||||||
|
# Header
|
||||||
|
self.header = Header(
|
||||||
|
title="Twitter Dashboard",
|
||||||
|
subtitle="Create and manage your Twitter content"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Add header actions
|
||||||
|
self.header.add_action(
|
||||||
|
"New Tweet",
|
||||||
|
"✏️",
|
||||||
|
self.create_new_tweet,
|
||||||
|
"Create a new tweet"
|
||||||
|
)
|
||||||
|
self.header.add_action(
|
||||||
|
"Refresh",
|
||||||
|
"🔄",
|
||||||
|
self.refresh_dashboard,
|
||||||
|
"Refresh dashboard data"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Tabs
|
||||||
|
self.tabs = Tabs()
|
||||||
|
|
||||||
|
# Add tabs
|
||||||
|
self.tabs.add_tab("Overview", "📊", self.render_overview)
|
||||||
|
self.tabs.add_tab("Recent Tweets", "🐦", self.render_recent_tweets)
|
||||||
|
self.tabs.add_tab("Analytics", "📈", self.render_analytics)
|
||||||
|
|
||||||
|
# Breadcrumbs
|
||||||
|
self.breadcrumbs = Breadcrumbs()
|
||||||
|
|
||||||
|
def setup_state(self) -> None:
|
||||||
|
"""Initialize session state variables."""
|
||||||
|
if "current_page" not in st.session_state:
|
||||||
|
st.session_state["current_page"] = "dashboard"
|
||||||
|
if "current_tab" not in st.session_state:
|
||||||
|
st.session_state["current_tab"] = "Overview"
|
||||||
|
if "tweets" not in st.session_state:
|
||||||
|
st.session_state["tweets"] = []
|
||||||
|
|
||||||
|
def create_new_tweet(self) -> None:
|
||||||
|
"""Handle new tweet creation."""
|
||||||
|
st.session_state["current_page"] = "tweet_generator"
|
||||||
|
|
||||||
|
def refresh_dashboard(self) -> None:
|
||||||
|
"""Refresh dashboard data."""
|
||||||
|
st.rerun()
|
||||||
|
|
||||||
|
def render_overview(self) -> None:
|
||||||
|
"""Render the overview tab content."""
|
||||||
|
# Feature cards
|
||||||
|
col1, col2, col3 = st.columns(3)
|
||||||
|
|
||||||
|
with col1:
|
||||||
|
FeatureCard(
|
||||||
|
title="Tweet Generator",
|
||||||
|
description="Create engaging tweets with AI assistance",
|
||||||
|
icon="✍️",
|
||||||
|
features=[
|
||||||
|
{
|
||||||
|
"name": "AI-Powered",
|
||||||
|
"description": "Generate tweets using advanced AI"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Customizable",
|
||||||
|
"description": "Adjust tone, length, and style"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
on_click=self.create_new_tweet
|
||||||
|
).render()
|
||||||
|
|
||||||
|
with col2:
|
||||||
|
FeatureCard(
|
||||||
|
title="Analytics",
|
||||||
|
description="Track your tweet performance",
|
||||||
|
icon="📈",
|
||||||
|
features=[
|
||||||
|
{
|
||||||
|
"name": "Engagement",
|
||||||
|
"description": "Monitor likes, retweets, and replies"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Growth",
|
||||||
|
"description": "Track follower growth over time"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
).render()
|
||||||
|
|
||||||
|
with col3:
|
||||||
|
FeatureCard(
|
||||||
|
title="Settings",
|
||||||
|
description="Customize your experience",
|
||||||
|
icon="⚙️",
|
||||||
|
features=[
|
||||||
|
{
|
||||||
|
"name": "Preferences",
|
||||||
|
"description": "Set your default options"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "API",
|
||||||
|
"description": "Configure Twitter API settings"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
).render()
|
||||||
|
|
||||||
|
def render_recent_tweets(self) -> None:
|
||||||
|
"""Render the recent tweets tab content."""
|
||||||
|
# Tweet form
|
||||||
|
tweet_form = TweetForm(
|
||||||
|
on_submit=self.handle_tweet_submit
|
||||||
|
)
|
||||||
|
tweet_form.render()
|
||||||
|
|
||||||
|
# Recent tweets
|
||||||
|
st.markdown("### Recent Tweets")
|
||||||
|
|
||||||
|
for tweet in st.session_state["tweets"]:
|
||||||
|
TweetCard(
|
||||||
|
content=tweet["content"],
|
||||||
|
engagement_score=tweet["engagement_score"],
|
||||||
|
hashtags=tweet["hashtags"],
|
||||||
|
emojis=tweet["emojis"],
|
||||||
|
metrics=tweet["metrics"],
|
||||||
|
on_copy=lambda: self.copy_tweet(tweet),
|
||||||
|
on_save=lambda: self.save_tweet(tweet)
|
||||||
|
).render()
|
||||||
|
|
||||||
|
def render_analytics(self) -> None:
|
||||||
|
"""Render the analytics tab content."""
|
||||||
|
# Analytics content
|
||||||
|
st.markdown("### Tweet Analytics")
|
||||||
|
|
||||||
|
# Placeholder for analytics charts
|
||||||
|
st.info("Analytics features coming soon!")
|
||||||
|
|
||||||
|
def handle_tweet_submit(self) -> None:
|
||||||
|
"""Handle tweet form submission."""
|
||||||
|
# Get form data
|
||||||
|
content = st.session_state["tweet_content"]
|
||||||
|
tone = st.session_state["tone"]
|
||||||
|
length = st.session_state["length"]
|
||||||
|
hashtags = st.session_state["hashtags"]
|
||||||
|
emojis = st.session_state["emojis"]
|
||||||
|
engagement_boost = st.session_state["engagement_boost"]
|
||||||
|
|
||||||
|
# Create tweet object
|
||||||
|
tweet = {
|
||||||
|
"content": content,
|
||||||
|
"tone": tone,
|
||||||
|
"length": length,
|
||||||
|
"hashtags": hashtags,
|
||||||
|
"emojis": emojis,
|
||||||
|
"engagement_score": engagement_boost,
|
||||||
|
"metrics": {
|
||||||
|
"Engagement": engagement_boost,
|
||||||
|
"Reach": engagement_boost * 0.8,
|
||||||
|
"Growth": engagement_boost * 0.6
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Add to tweets list
|
||||||
|
st.session_state["tweets"].append(tweet)
|
||||||
|
|
||||||
|
# Show success message
|
||||||
|
st.success("Tweet created successfully!")
|
||||||
|
|
||||||
|
def copy_tweet(self, tweet: Dict[str, Any]) -> None:
|
||||||
|
"""Copy tweet to clipboard."""
|
||||||
|
st.write("Tweet copied to clipboard!")
|
||||||
|
|
||||||
|
def save_tweet(self, tweet: Dict[str, Any]) -> None:
|
||||||
|
"""Save tweet for later."""
|
||||||
|
st.write("Tweet saved!")
|
||||||
|
|
||||||
|
def render(self) -> None:
|
||||||
|
"""Render the complete dashboard."""
|
||||||
|
# Render navigation
|
||||||
|
self.sidebar.render()
|
||||||
|
self.header.render()
|
||||||
|
self.breadcrumbs.render()
|
||||||
|
|
||||||
|
# Render content based on current page
|
||||||
|
if st.session_state["current_page"] == "dashboard":
|
||||||
|
self.tabs.render()
|
||||||
|
elif st.session_state["current_page"] == "tweet_generator":
|
||||||
|
self.render_recent_tweets()
|
||||||
|
elif st.session_state["current_page"] == "analytics":
|
||||||
|
self.render_analytics()
|
||||||
|
elif st.session_state["current_page"] == "settings":
|
||||||
|
settings_form = SettingsForm(
|
||||||
|
on_submit=self.handle_settings_submit
|
||||||
|
)
|
||||||
|
settings_form.render()
|
||||||
|
|
||||||
|
def handle_settings_submit(self) -> None:
|
||||||
|
"""Handle settings form submission."""
|
||||||
|
# Get form data
|
||||||
|
api_key = st.session_state["api_key"]
|
||||||
|
theme = st.session_state["theme"]
|
||||||
|
notifications = st.session_state["notifications"]
|
||||||
|
auto_save = st.session_state["auto_save"]
|
||||||
|
language = st.session_state["language"]
|
||||||
|
|
||||||
|
# Save settings
|
||||||
|
st.session_state["settings"] = {
|
||||||
|
"api_key": api_key,
|
||||||
|
"theme": theme,
|
||||||
|
"notifications": notifications,
|
||||||
|
"auto_save": auto_save,
|
||||||
|
"language": language
|
||||||
|
}
|
||||||
|
|
||||||
|
# Show success message
|
||||||
|
st.success("Settings saved successfully!")
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Main entry point for the dashboard."""
|
||||||
|
dashboard = TwitterDashboard()
|
||||||
|
dashboard.render()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -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,503 @@
|
|||||||
|
"""
|
||||||
|
Enhanced Twitter Dashboard with real authentication and posting capabilities.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import streamlit as st
|
||||||
|
import asyncio
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
import json
|
||||||
|
from typing import Dict, Any, List, Optional
|
||||||
|
|
||||||
|
# Import our enhanced components
|
||||||
|
from .components.navigation import TwitterNavigation, create_main_navigation
|
||||||
|
from .components.cards import TwitterCard, create_analytics_card, create_tweet_card
|
||||||
|
from .components.forms import TweetForm, TwitterConfigForm
|
||||||
|
from ..tweet_generator.smart_tweet_generator import (
|
||||||
|
smart_tweet_generator,
|
||||||
|
post_tweet_to_twitter,
|
||||||
|
get_real_tweet_analytics,
|
||||||
|
render_twitter_authentication
|
||||||
|
)
|
||||||
|
from ....integrations.twitter_auth_bridge import (
|
||||||
|
TwitterAuthBridge,
|
||||||
|
save_twitter_credentials,
|
||||||
|
load_twitter_credentials,
|
||||||
|
is_twitter_authenticated,
|
||||||
|
setup_twitter_session,
|
||||||
|
clear_twitter_session
|
||||||
|
)
|
||||||
|
|
||||||
|
# Initialize authentication bridge
|
||||||
|
auth_bridge = TwitterAuthBridge()
|
||||||
|
|
||||||
|
def initialize_dashboard():
|
||||||
|
"""Initialize the Twitter dashboard with proper styling and state management."""
|
||||||
|
|
||||||
|
# Apply custom CSS
|
||||||
|
st.markdown("""
|
||||||
|
<style>
|
||||||
|
.main-dashboard {
|
||||||
|
padding: 1rem;
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-header {
|
||||||
|
background: white;
|
||||||
|
padding: 2rem;
|
||||||
|
border-radius: 15px;
|
||||||
|
box-shadow: 0 10px 30px rgba(0,0,0,0.1);
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-title {
|
||||||
|
font-size: 2.5rem;
|
||||||
|
font-weight: 700;
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-subtitle {
|
||||||
|
color: #666;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-indicator {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
border-radius: 25px;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-connected {
|
||||||
|
background: #d4edda;
|
||||||
|
color: #155724;
|
||||||
|
border: 1px solid #c3e6cb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-disconnected {
|
||||||
|
background: #f8d7da;
|
||||||
|
color: #721c24;
|
||||||
|
border: 1px solid #f5c6cb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 2rem;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.dashboard-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-button {
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 0.75rem 1.5rem;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-button:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.metrics-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||||
|
gap: 1rem;
|
||||||
|
margin: 1rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-card {
|
||||||
|
background: white;
|
||||||
|
padding: 1.5rem;
|
||||||
|
border-radius: 10px;
|
||||||
|
box-shadow: 0 5px 15px rgba(0,0,0,0.1);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-value {
|
||||||
|
font-size: 2rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #667eea;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-label {
|
||||||
|
color: #666;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
""", unsafe_allow_html=True)
|
||||||
|
|
||||||
|
# Initialize session state
|
||||||
|
if 'twitter_dashboard_initialized' not in st.session_state:
|
||||||
|
st.session_state.twitter_dashboard_initialized = True
|
||||||
|
st.session_state.current_page = 'dashboard'
|
||||||
|
st.session_state.tweet_drafts = []
|
||||||
|
st.session_state.posted_tweets = []
|
||||||
|
st.session_state.analytics_data = {}
|
||||||
|
|
||||||
|
def render_dashboard_header():
|
||||||
|
"""Render the main dashboard header with connection status."""
|
||||||
|
|
||||||
|
st.markdown('<div class="dashboard-header">', unsafe_allow_html=True)
|
||||||
|
|
||||||
|
col1, col2, col3 = st.columns([1, 2, 1])
|
||||||
|
|
||||||
|
with col2:
|
||||||
|
st.markdown('<h1 class="dashboard-title">🐦 Twitter AI Dashboard</h1>', unsafe_allow_html=True)
|
||||||
|
st.markdown('<p class="dashboard-subtitle">AI-Powered Tweet Generation & Analytics</p>', unsafe_allow_html=True)
|
||||||
|
|
||||||
|
# Connection status
|
||||||
|
is_connected = is_twitter_authenticated()
|
||||||
|
|
||||||
|
if is_connected:
|
||||||
|
user_info = st.session_state.get('twitter_user', {})
|
||||||
|
username = user_info.get('screen_name', 'Unknown')
|
||||||
|
st.markdown(f'''
|
||||||
|
<div class="status-indicator status-connected">
|
||||||
|
✅ Connected as @{username}
|
||||||
|
</div>
|
||||||
|
''', unsafe_allow_html=True)
|
||||||
|
else:
|
||||||
|
st.markdown('''
|
||||||
|
<div class="status-indicator status-disconnected">
|
||||||
|
❌ Not Connected to Twitter
|
||||||
|
</div>
|
||||||
|
''', unsafe_allow_html=True)
|
||||||
|
|
||||||
|
st.markdown('</div>', unsafe_allow_html=True)
|
||||||
|
|
||||||
|
def render_quick_actions():
|
||||||
|
"""Render quick action buttons."""
|
||||||
|
|
||||||
|
st.markdown("### 🚀 Quick Actions")
|
||||||
|
|
||||||
|
col1, col2, col3, col4 = st.columns(4)
|
||||||
|
|
||||||
|
with col1:
|
||||||
|
if st.button("📝 Generate Tweet", key="quick_generate", help="Create AI-powered tweets"):
|
||||||
|
st.session_state.current_page = 'generate'
|
||||||
|
st.rerun()
|
||||||
|
|
||||||
|
with col2:
|
||||||
|
if st.button("📊 View Analytics", key="quick_analytics", help="View tweet performance"):
|
||||||
|
st.session_state.current_page = 'analytics'
|
||||||
|
st.rerun()
|
||||||
|
|
||||||
|
with col3:
|
||||||
|
if st.button("⚙️ Settings", key="quick_settings", help="Configure Twitter connection"):
|
||||||
|
st.session_state.current_page = 'settings'
|
||||||
|
st.rerun()
|
||||||
|
|
||||||
|
with col4:
|
||||||
|
if st.button("📋 Drafts", key="quick_drafts", help="Manage tweet drafts"):
|
||||||
|
st.session_state.current_page = 'drafts'
|
||||||
|
st.rerun()
|
||||||
|
|
||||||
|
def render_dashboard_overview():
|
||||||
|
"""Render the main dashboard overview with metrics."""
|
||||||
|
|
||||||
|
if not is_twitter_authenticated():
|
||||||
|
st.warning("⚠️ Please connect your Twitter account to view dashboard metrics.")
|
||||||
|
if st.button("Connect Twitter Account", type="primary"):
|
||||||
|
st.session_state.current_page = 'settings'
|
||||||
|
st.rerun()
|
||||||
|
return
|
||||||
|
|
||||||
|
# Get user metrics
|
||||||
|
user_info = st.session_state.get('twitter_user', {})
|
||||||
|
|
||||||
|
# Display metrics
|
||||||
|
st.markdown("### 📈 Account Overview")
|
||||||
|
|
||||||
|
col1, col2, col3, col4 = st.columns(4)
|
||||||
|
|
||||||
|
with col1:
|
||||||
|
st.markdown(f'''
|
||||||
|
<div class="metric-card">
|
||||||
|
<div class="metric-value">{user_info.get('followers_count', 0):,}</div>
|
||||||
|
<div class="metric-label">Followers</div>
|
||||||
|
</div>
|
||||||
|
''', unsafe_allow_html=True)
|
||||||
|
|
||||||
|
with col2:
|
||||||
|
st.markdown(f'''
|
||||||
|
<div class="metric-card">
|
||||||
|
<div class="metric-value">{user_info.get('friends_count', 0):,}</div>
|
||||||
|
<div class="metric-label">Following</div>
|
||||||
|
</div>
|
||||||
|
''', unsafe_allow_html=True)
|
||||||
|
|
||||||
|
with col3:
|
||||||
|
posted_count = len(st.session_state.get('posted_tweets', []))
|
||||||
|
st.markdown(f'''
|
||||||
|
<div class="metric-card">
|
||||||
|
<div class="metric-value">{posted_count}</div>
|
||||||
|
<div class="metric-label">Posted Today</div>
|
||||||
|
</div>
|
||||||
|
''', unsafe_allow_html=True)
|
||||||
|
|
||||||
|
with col4:
|
||||||
|
draft_count = len(st.session_state.get('tweet_drafts', []))
|
||||||
|
st.markdown(f'''
|
||||||
|
<div class="metric-card">
|
||||||
|
<div class="metric-value">{draft_count}</div>
|
||||||
|
<div class="metric-label">Drafts</div>
|
||||||
|
</div>
|
||||||
|
''', unsafe_allow_html=True)
|
||||||
|
|
||||||
|
# Recent activity
|
||||||
|
st.markdown("### 📝 Recent Activity")
|
||||||
|
|
||||||
|
recent_tweets = st.session_state.get('posted_tweets', [])[-5:] # Last 5 tweets
|
||||||
|
|
||||||
|
if recent_tweets:
|
||||||
|
for tweet in reversed(recent_tweets):
|
||||||
|
with st.expander(f"Tweet: {tweet.get('text', '')[:50]}..."):
|
||||||
|
col1, col2 = st.columns([2, 1])
|
||||||
|
|
||||||
|
with col1:
|
||||||
|
st.write(f"**Text:** {tweet.get('text', '')}")
|
||||||
|
st.write(f"**Posted:** {tweet.get('created_at', '')}")
|
||||||
|
|
||||||
|
if tweet.get('metrics'):
|
||||||
|
metrics = tweet['metrics']
|
||||||
|
st.write(f"**Engagement:** {metrics.get('favorite_count', 0)} likes, "
|
||||||
|
f"{metrics.get('retweet_count', 0)} retweets")
|
||||||
|
|
||||||
|
with col2:
|
||||||
|
if st.button(f"View Analytics", key=f"analytics_{tweet.get('id')}"):
|
||||||
|
st.session_state.selected_tweet_id = tweet.get('id')
|
||||||
|
st.session_state.current_page = 'analytics'
|
||||||
|
st.rerun()
|
||||||
|
else:
|
||||||
|
st.info("No recent tweets found. Start by generating and posting some content!")
|
||||||
|
|
||||||
|
def render_settings_page():
|
||||||
|
"""Render the settings page for Twitter configuration."""
|
||||||
|
|
||||||
|
st.markdown("### ⚙️ Twitter Configuration")
|
||||||
|
|
||||||
|
# Twitter Authentication Section
|
||||||
|
with st.expander("🔐 Twitter API Configuration", expanded=not is_twitter_authenticated()):
|
||||||
|
render_twitter_authentication()
|
||||||
|
|
||||||
|
# Account Information
|
||||||
|
if is_twitter_authenticated():
|
||||||
|
st.markdown("### 👤 Account Information")
|
||||||
|
|
||||||
|
user_info = st.session_state.get('twitter_user', {})
|
||||||
|
|
||||||
|
col1, col2 = st.columns(2)
|
||||||
|
|
||||||
|
with col1:
|
||||||
|
st.write(f"**Username:** @{user_info.get('screen_name', 'N/A')}")
|
||||||
|
st.write(f"**Display Name:** {user_info.get('name', 'N/A')}")
|
||||||
|
st.write(f"**Followers:** {user_info.get('followers_count', 0):,}")
|
||||||
|
|
||||||
|
with col2:
|
||||||
|
st.write(f"**Following:** {user_info.get('friends_count', 0):,}")
|
||||||
|
st.write(f"**Tweets:** {user_info.get('statuses_count', 0):,}")
|
||||||
|
st.write(f"**Account Created:** {user_info.get('created_at', 'N/A')}")
|
||||||
|
|
||||||
|
# Disconnect option
|
||||||
|
st.markdown("---")
|
||||||
|
if st.button("🔓 Disconnect Twitter Account", type="secondary"):
|
||||||
|
clear_twitter_session()
|
||||||
|
st.success("Twitter account disconnected successfully!")
|
||||||
|
st.rerun()
|
||||||
|
|
||||||
|
def render_analytics_page():
|
||||||
|
"""Render the analytics page with real Twitter metrics."""
|
||||||
|
|
||||||
|
st.markdown("### 📊 Tweet Analytics")
|
||||||
|
|
||||||
|
if not is_twitter_authenticated():
|
||||||
|
st.warning("Please connect your Twitter account to view analytics.")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Tweet selection
|
||||||
|
posted_tweets = st.session_state.get('posted_tweets', [])
|
||||||
|
|
||||||
|
if not posted_tweets:
|
||||||
|
st.info("No tweets found. Generate and post some tweets to see analytics!")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Select tweet for analysis
|
||||||
|
tweet_options = {
|
||||||
|
f"{tweet.get('text', '')[:50]}... ({tweet.get('created_at', '')})": tweet.get('id')
|
||||||
|
for tweet in posted_tweets
|
||||||
|
}
|
||||||
|
|
||||||
|
selected_tweet_text = st.selectbox(
|
||||||
|
"Select a tweet to analyze:",
|
||||||
|
options=list(tweet_options.keys())
|
||||||
|
)
|
||||||
|
|
||||||
|
if selected_tweet_text:
|
||||||
|
tweet_id = tweet_options[selected_tweet_text]
|
||||||
|
|
||||||
|
# Get analytics
|
||||||
|
with st.spinner("Loading analytics..."):
|
||||||
|
analytics_result = asyncio.run(get_real_tweet_analytics(tweet_id))
|
||||||
|
|
||||||
|
if analytics_result.get('success'):
|
||||||
|
analytics_data = analytics_result['data']
|
||||||
|
|
||||||
|
# Display metrics
|
||||||
|
st.markdown("#### 📈 Performance Metrics")
|
||||||
|
|
||||||
|
col1, col2, col3, col4 = st.columns(4)
|
||||||
|
|
||||||
|
metrics = analytics_data.get('metrics', {})
|
||||||
|
|
||||||
|
with col1:
|
||||||
|
st.metric("Likes", metrics.get('likes', 0))
|
||||||
|
|
||||||
|
with col2:
|
||||||
|
st.metric("Retweets", metrics.get('retweets', 0))
|
||||||
|
|
||||||
|
with col3:
|
||||||
|
st.metric("Replies", metrics.get('replies', 0))
|
||||||
|
|
||||||
|
with col4:
|
||||||
|
engagement = analytics_data.get('engagement', {})
|
||||||
|
st.metric("Engagement Rate", f"{engagement.get('engagement_rate', 0):.2f}%")
|
||||||
|
|
||||||
|
# Detailed analytics
|
||||||
|
st.markdown("#### 🔍 Detailed Analysis")
|
||||||
|
|
||||||
|
col1, col2 = st.columns(2)
|
||||||
|
|
||||||
|
with col1:
|
||||||
|
st.markdown("**Engagement Breakdown:**")
|
||||||
|
total_engagement = metrics.get('total_engagement', 0)
|
||||||
|
st.write(f"• Total Engagement: {total_engagement}")
|
||||||
|
st.write(f"• Likes Rate: {engagement.get('likes_rate', 0):.2f}%")
|
||||||
|
st.write(f"• Retweets Rate: {engagement.get('retweets_rate', 0):.2f}%")
|
||||||
|
|
||||||
|
with col2:
|
||||||
|
st.markdown("**Content Analysis:**")
|
||||||
|
content_analysis = analytics_data.get('content_analysis', {})
|
||||||
|
st.write(f"• Character Count: {content_analysis.get('character_count', 0)}")
|
||||||
|
st.write(f"• Hashtags: {content_analysis.get('hashtag_count', 0)}")
|
||||||
|
st.write(f"• Mentions: {content_analysis.get('mention_count', 0)}")
|
||||||
|
|
||||||
|
# Timing analysis
|
||||||
|
timing = analytics_data.get('timing', {})
|
||||||
|
if timing:
|
||||||
|
st.markdown("#### ⏰ Timing Analysis")
|
||||||
|
st.write(f"• Posted: {timing.get('posted_at', 'N/A')}")
|
||||||
|
st.write(f"• Age: {timing.get('age_hours', 0):.1f} hours")
|
||||||
|
st.write(f"• Peak Period: {timing.get('peak_engagement_period', 'N/A')}")
|
||||||
|
st.write(f"• Engagement Velocity: {timing.get('engagement_velocity', 0):.2f} per hour")
|
||||||
|
|
||||||
|
else:
|
||||||
|
st.error(f"Failed to load analytics: {analytics_result.get('error', 'Unknown error')}")
|
||||||
|
|
||||||
|
def render_drafts_page():
|
||||||
|
"""Render the drafts management page."""
|
||||||
|
|
||||||
|
st.markdown("### 📋 Tweet Drafts")
|
||||||
|
|
||||||
|
drafts = st.session_state.get('tweet_drafts', [])
|
||||||
|
|
||||||
|
if not drafts:
|
||||||
|
st.info("No drafts found. Create some tweets in the generator to save as drafts!")
|
||||||
|
return
|
||||||
|
|
||||||
|
for i, draft in enumerate(drafts):
|
||||||
|
with st.expander(f"Draft {i+1}: {draft.get('text', '')[:50]}..."):
|
||||||
|
col1, col2 = st.columns([3, 1])
|
||||||
|
|
||||||
|
with col1:
|
||||||
|
st.write(f"**Text:** {draft.get('text', '')}")
|
||||||
|
st.write(f"**Created:** {draft.get('created_at', '')}")
|
||||||
|
if draft.get('hashtags'):
|
||||||
|
st.write(f"**Hashtags:** {', '.join(draft['hashtags'])}")
|
||||||
|
|
||||||
|
with col2:
|
||||||
|
if st.button(f"Post Now", key=f"post_draft_{i}"):
|
||||||
|
if is_twitter_authenticated():
|
||||||
|
with st.spinner("Posting tweet..."):
|
||||||
|
result = asyncio.run(post_tweet_to_twitter(draft))
|
||||||
|
|
||||||
|
if result.get('success'):
|
||||||
|
st.success("Tweet posted successfully!")
|
||||||
|
# Move from drafts to posted
|
||||||
|
st.session_state.posted_tweets.append(result['data'])
|
||||||
|
st.session_state.tweet_drafts.pop(i)
|
||||||
|
st.rerun()
|
||||||
|
else:
|
||||||
|
st.error(f"Failed to post: {result.get('error')}")
|
||||||
|
else:
|
||||||
|
st.error("Please connect your Twitter account first!")
|
||||||
|
|
||||||
|
if st.button(f"Delete", key=f"delete_draft_{i}"):
|
||||||
|
st.session_state.tweet_drafts.pop(i)
|
||||||
|
st.rerun()
|
||||||
|
|
||||||
|
def main_twitter_dashboard():
|
||||||
|
"""Main Twitter dashboard function."""
|
||||||
|
|
||||||
|
# Initialize dashboard
|
||||||
|
initialize_dashboard()
|
||||||
|
|
||||||
|
# Create navigation
|
||||||
|
nav = TwitterNavigation()
|
||||||
|
current_page = nav.render_main_navigation()
|
||||||
|
|
||||||
|
# Update session state if page changed
|
||||||
|
if current_page != st.session_state.get('current_page'):
|
||||||
|
st.session_state.current_page = current_page
|
||||||
|
|
||||||
|
# Render dashboard header
|
||||||
|
render_dashboard_header()
|
||||||
|
|
||||||
|
# Route to appropriate page
|
||||||
|
page = st.session_state.get('current_page', 'dashboard')
|
||||||
|
|
||||||
|
if page == 'dashboard':
|
||||||
|
render_quick_actions()
|
||||||
|
render_dashboard_overview()
|
||||||
|
|
||||||
|
elif page == 'generate':
|
||||||
|
st.markdown("### 🤖 AI Tweet Generator")
|
||||||
|
smart_tweet_generator()
|
||||||
|
|
||||||
|
elif page == 'analytics':
|
||||||
|
render_analytics_page()
|
||||||
|
|
||||||
|
elif page == 'settings':
|
||||||
|
render_settings_page()
|
||||||
|
|
||||||
|
elif page == 'drafts':
|
||||||
|
render_drafts_page()
|
||||||
|
|
||||||
|
else:
|
||||||
|
# Default to dashboard
|
||||||
|
render_quick_actions()
|
||||||
|
render_dashboard_overview()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main_twitter_dashboard()
|
||||||
@@ -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)}")
|
||||||
121
ToBeMigrated/ai_writers/web_url_ai_writer.py
Normal file
121
ToBeMigrated/ai_writers/web_url_ai_writer.py
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
import sys
|
||||||
|
import os
|
||||||
|
|
||||||
|
from textwrap import dedent
|
||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
from datetime import datetime
|
||||||
|
import streamlit as st
|
||||||
|
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
load_dotenv(Path('../../.env'))
|
||||||
|
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.firecrawl_web_crawler import scrape_url
|
||||||
|
from ..blog_metadata.get_blog_metadata import blog_metadata, run_async
|
||||||
|
from ..blog_postprocessing.save_blog_to_file import save_blog_to_file
|
||||||
|
from ..gpt_providers.text_to_image_generation.main_generate_image_from_prompt import generate_image
|
||||||
|
from ..gpt_providers.text_generation.main_text_generation import llm_text_gen
|
||||||
|
|
||||||
|
|
||||||
|
def blog_from_url(weburl):
|
||||||
|
"""
|
||||||
|
This function will take a blog Topic to first generate sections for it
|
||||||
|
and then generate content for each section.
|
||||||
|
"""
|
||||||
|
# Use to store the blog in a string, to save in a *.md file.
|
||||||
|
blog_markdown_str = None
|
||||||
|
tavily_search_result = None
|
||||||
|
# Initializing the variables
|
||||||
|
blog_title = None
|
||||||
|
blog_meta_desc = None
|
||||||
|
blog_tags = None
|
||||||
|
blog_categories = None
|
||||||
|
|
||||||
|
logger.info(f"Researching and Writing Blog on: {weburl}")
|
||||||
|
with st.status("Started Writing..", expanded=True) as status:
|
||||||
|
st.empty()
|
||||||
|
status.update(label=f"Researching and Writing Blog on: {weburl}")
|
||||||
|
try:
|
||||||
|
scraped_text = scrape_url(weburl)
|
||||||
|
#logger.info(scraped_text)
|
||||||
|
except Exception as err:
|
||||||
|
st.error(f"Failed to scrape web page from url-{weburl} - Error: {err}")
|
||||||
|
logger.error(f"Failed in web research: {err}")
|
||||||
|
st.stop()
|
||||||
|
status.update(label=f"Successfully Scraped/Fetched url: {weburl}", expanded=False, state="complete")
|
||||||
|
|
||||||
|
with st.status(f"Started Writing blog from {weburl}..", expanded=True) as status:
|
||||||
|
# Do Tavily AI research to augument the above blog.
|
||||||
|
try:
|
||||||
|
blog_markdown_str = write_blog_from_weburl(scraped_text)
|
||||||
|
status.update(label="Finished Writing Blog From: {weburl}")
|
||||||
|
except Exception as err:
|
||||||
|
logger.error(f"Failed to write blog from: {weburl}")
|
||||||
|
st.error(f"Failed to write blog from: {weburl}")
|
||||||
|
st.stop()
|
||||||
|
|
||||||
|
try:
|
||||||
|
status.update(label="🙎 Generating - Title, Meta Description, Tags, Categories for the content.")
|
||||||
|
blog_title, blog_meta_desc, blog_tags, blog_categories = run_async(blog_metadata(blog_markdown_str))
|
||||||
|
except Exception as err:
|
||||||
|
st.error(f"Failed to get blog metadata: {err}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
status.update(label="🙎 Generating Image for the new blog.")
|
||||||
|
generated_image_filepath = generate_image(f"{blog_title} + ' ' + {blog_meta_desc}")
|
||||||
|
except Exception as err:
|
||||||
|
st.warning(f"Failed in Image generation: {err}")
|
||||||
|
|
||||||
|
saved_blog_to_file = save_blog_to_file(blog_markdown_str, blog_title, blog_meta_desc,
|
||||||
|
blog_tags, blog_categories, generated_image_filepath)
|
||||||
|
status.update(label=f"Saved the content in this file: {saved_blog_to_file}")
|
||||||
|
|
||||||
|
logger.info(f"\n\n --------- Finished writing Blog for : {weburl} -------------- \n")
|
||||||
|
if generated_image_filepath:
|
||||||
|
st.image(generated_image_filepath)
|
||||||
|
|
||||||
|
st.markdown(f"{blog_markdown_str}")
|
||||||
|
status.update(label=f"Finished, Review & Use your Original Content Below: {saved_blog_to_file}", state="complete")
|
||||||
|
|
||||||
|
|
||||||
|
def write_blog_from_weburl(scraped_website):
|
||||||
|
"""Combine the given online research and GPT blog content"""
|
||||||
|
try:
|
||||||
|
config_path = Path(os.environ["ALWRITY_CONFIG"])
|
||||||
|
with open(config_path, 'r', encoding='utf-8') as file:
|
||||||
|
config = json.load(file)
|
||||||
|
except Exception as err:
|
||||||
|
logger.error(f"Error: Failed to read values from config: {err}")
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
blog_characteristics = config['Blog Content Characteristics']
|
||||||
|
|
||||||
|
prompt = f"""
|
||||||
|
As expert Creative Content writer, I will provide you with scraped website content.
|
||||||
|
I want you to write a detailed {blog_characteristics['Blog Type']} blog post including 5 FAQs.
|
||||||
|
|
||||||
|
Below are the guidelines to follow:
|
||||||
|
1). You must respond in {blog_characteristics['Blog Language']} language.
|
||||||
|
2). Tone and Brand Alignment: Adjust your tone, voice, personality for {blog_characteristics['Blog Tone']} audience.
|
||||||
|
3). Make sure your response content length is of {blog_characteristics['Blog Length']} words.
|
||||||
|
4). Include FAQs from 'People also Ask' section of provided context 'google search result'.
|
||||||
|
|
||||||
|
I want the post to offer unique insights, relatable examples, and a fresh perspective on the topic.
|
||||||
|
\n\n
|
||||||
|
Website Content:
|
||||||
|
'''{scraped_website}'''
|
||||||
|
"""
|
||||||
|
logger.info("Generating blog and FAQs from Google web search results.")
|
||||||
|
|
||||||
|
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)
|
||||||
@@ -52,7 +52,7 @@ class AutoFillRefreshService:
|
|||||||
|
|
||||||
logger.info(f" - Website analysis keys: {list(website_analysis.keys()) if website_analysis else 'None'}")
|
logger.info(f" - Website analysis keys: {list(website_analysis.keys()) if website_analysis else 'None'}")
|
||||||
logger.info(f" - Research preferences keys: {list(research_preferences.keys()) if research_preferences else 'None'}")
|
logger.info(f" - Research preferences keys: {list(research_preferences.keys()) if research_preferences else 'None'}")
|
||||||
logger.info(" - API keys data present: %s | entry_count=%s", bool(api_keys_data), len(api_keys_data) if isinstance(api_keys_data, dict) else 0)
|
logger.info(f" - API keys data keys: {list(api_keys_data.keys()) if api_keys_data else 'None'}")
|
||||||
logger.info(f" - Onboarding session keys: {list(onboarding_session.keys()) if onboarding_session else 'None'}")
|
logger.info(f" - Onboarding session keys: {list(onboarding_session.keys()) if onboarding_session else 'None'}")
|
||||||
|
|
||||||
# Log specific data points
|
# Log specific data points
|
||||||
@@ -64,7 +64,7 @@ class AutoFillRefreshService:
|
|||||||
logger.info(f" - Content types: {research_preferences.get('content_types', 'Not found')}")
|
logger.info(f" - Content types: {research_preferences.get('content_types', 'Not found')}")
|
||||||
if api_keys_data:
|
if api_keys_data:
|
||||||
logger.info(f" - API providers: {api_keys_data.get('providers', [])}")
|
logger.info(f" - API providers: {api_keys_data.get('providers', [])}")
|
||||||
logger.info(" - API key data present: %s", bool(api_keys_data))
|
logger.info(f" - Total keys: {api_keys_data.get('total_keys', 0)}")
|
||||||
else:
|
else:
|
||||||
logger.warning(f"AutoFillRefreshService: no base context available | user=%s", user_id)
|
logger.warning(f"AutoFillRefreshService: no base context available | user=%s", user_id)
|
||||||
|
|
||||||
|
|||||||
@@ -499,7 +499,7 @@ Generate the complete JSON with all 30 fields personalized for {website_url}:
|
|||||||
# Log context summary for debugging
|
# Log context summary for debugging
|
||||||
logger.info("AIStructuredAutofillService: context summary | user=%s", user_id)
|
logger.info("AIStructuredAutofillService: context summary | user=%s", user_id)
|
||||||
logger.info(" - Website analysis exists: %s", bool(context_summary.get('user_profile', {}).get('website_url')))
|
logger.info(" - Website analysis exists: %s", bool(context_summary.get('user_profile', {}).get('website_url')))
|
||||||
logger.info(" - Research config: %s", context_summary.get('research_config', {}).get('research_depth', 'None'))
|
logger.info(" - Research config present: %s", bool(context_summary.get('research_config', {}).get('research_depth')))
|
||||||
logger.info(" - API capabilities: %s", len(context_summary.get('api_capabilities', {}).get('providers', [])))
|
logger.info(" - API capabilities: %s", len(context_summary.get('api_capabilities', {}).get('providers', [])))
|
||||||
logger.info(" - Content analysis: %s", bool(context_summary.get('content_analysis')))
|
logger.info(" - Content analysis: %s", bool(context_summary.get('content_analysis')))
|
||||||
logger.info(" - Audience insights: %s", bool(context_summary.get('audience_insights')))
|
logger.info(" - Audience insights: %s", bool(context_summary.get('audience_insights')))
|
||||||
|
|||||||
@@ -79,8 +79,8 @@ class CachingService:
|
|||||||
if kwargs:
|
if kwargs:
|
||||||
key_data += ":" + json.dumps(kwargs, sort_keys=True)
|
key_data += ":" + json.dumps(kwargs, sort_keys=True)
|
||||||
|
|
||||||
# Create hash for consistent key length using a strong hash algorithm
|
# Create hash for consistent key length
|
||||||
key_hash = hashlib.sha256(key_data.encode("utf-8")).hexdigest()
|
key_hash = hashlib.md5(key_data.encode()).hexdigest()
|
||||||
return f"content_strategy:{cache_type}:{key_hash}"
|
return f"content_strategy:{cache_type}:{key_hash}"
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
@@ -25,17 +25,10 @@ def _resolve_podcast_media_file(
|
|||||||
if not clean_filename:
|
if not clean_filename:
|
||||||
raise HTTPException(status_code=400, detail="Invalid filename")
|
raise HTTPException(status_code=400, detail="Invalid filename")
|
||||||
|
|
||||||
# Filename must be a basename only (no path separators / traversal)
|
|
||||||
filename_path = Path(clean_filename)
|
|
||||||
if filename_path.name != clean_filename or clean_filename in {".", ".."}:
|
|
||||||
raise HTTPException(status_code=400, detail="Invalid filename")
|
|
||||||
|
|
||||||
for base_dir in get_podcast_media_read_dirs(media_type, user_id):
|
for base_dir in get_podcast_media_read_dirs(media_type, user_id):
|
||||||
target_dir = (base_dir / subdir).resolve() if subdir else base_dir.resolve()
|
target_dir = (base_dir / subdir).resolve() if subdir else base_dir.resolve()
|
||||||
candidate = (target_dir / clean_filename).resolve()
|
candidate = (target_dir / clean_filename).resolve()
|
||||||
try:
|
if not str(candidate).startswith(str(target_dir)):
|
||||||
candidate.relative_to(target_dir)
|
|
||||||
except ValueError:
|
|
||||||
logger.error(f"[Podcast] Attempted path traversal for {media_type}: {filename}")
|
logger.error(f"[Podcast] Attempted path traversal for {media_type}: {filename}")
|
||||||
raise HTTPException(status_code=403, detail="Invalid media path")
|
raise HTTPException(status_code=403, detail="Invalid media path")
|
||||||
if candidate.exists():
|
if candidate.exists():
|
||||||
@@ -82,13 +75,10 @@ def load_podcast_image_bytes(image_url: str, user_id: str | None = None) -> byte
|
|||||||
logger.info(f"[Podcast] Loading image from URL: {image_url}")
|
logger.info(f"[Podcast] Loading image from URL: {image_url}")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
parsed = urlparse(image_url)
|
# Extract filename from URL path
|
||||||
path = parsed.path if parsed.scheme else image_url
|
|
||||||
|
|
||||||
# Extract filename from API image path
|
|
||||||
prefix = "/api/podcast/images/"
|
prefix = "/api/podcast/images/"
|
||||||
if path.startswith(prefix):
|
if prefix in image_url:
|
||||||
filename = path[len(prefix):].split("?", 1)[0].strip()
|
filename = image_url.split(prefix, 1)[1].split("?", 1)[0].strip()
|
||||||
# Handle subdirectories like avatars/
|
# Handle subdirectories like avatars/
|
||||||
subdir = None
|
subdir = None
|
||||||
if "/" in filename:
|
if "/" in filename:
|
||||||
|
|||||||
@@ -170,7 +170,7 @@ async def verify_checkout_status(
|
|||||||
|
|
||||||
stripe_customer_id = subscription.stripe_customer_id if subscription else None
|
stripe_customer_id = subscription.stripe_customer_id if subscription else None
|
||||||
|
|
||||||
# If no stripe_customer_id in DB, try to find it by email or metadata
|
# If no stripe_customer_id in DB, try to find it by email
|
||||||
if not stripe_customer_id:
|
if not stripe_customer_id:
|
||||||
try:
|
try:
|
||||||
import stripe
|
import stripe
|
||||||
@@ -188,22 +188,8 @@ async def verify_checkout_status(
|
|||||||
db.commit()
|
db.commit()
|
||||||
else:
|
else:
|
||||||
logger.info(f"Verify-checkout: No local subscription record for user {user_id}, will query Stripe directly")
|
logger.info(f"Verify-checkout: No local subscription record for user {user_id}, will query Stripe directly")
|
||||||
|
except Exception as email_err:
|
||||||
# Fallback: search by metadata user_id (handles email mismatches)
|
logger.warning(f"Failed to find Stripe customer by email: {email_err}")
|
||||||
if not stripe_customer_id:
|
|
||||||
customers = stripe.Customer.search(
|
|
||||||
query=f"metadata['user_id']:'{user_id}'",
|
|
||||||
limit=1
|
|
||||||
)
|
|
||||||
if customers and customers.data:
|
|
||||||
stripe_customer_id = customers.data[0].id
|
|
||||||
logger.info(f"Verify-checkout: Found Stripe customer by metadata user_id for user {user_id}")
|
|
||||||
|
|
||||||
if subscription:
|
|
||||||
subscription.stripe_customer_id = stripe_customer_id
|
|
||||||
db.commit()
|
|
||||||
except Exception as lookup_err:
|
|
||||||
logger.warning(f"Failed to find Stripe customer by email or metadata: {lookup_err}")
|
|
||||||
|
|
||||||
# If user has a Stripe customer ID, query Stripe directly
|
# If user has a Stripe customer ID, query Stripe directly
|
||||||
if stripe_customer_id:
|
if stripe_customer_id:
|
||||||
@@ -264,57 +250,6 @@ async def verify_checkout_status(
|
|||||||
except Exception as stripe_err:
|
except Exception as stripe_err:
|
||||||
logger.warning(f"Failed to query Stripe directly for user {user_id}: {stripe_err}")
|
logger.warning(f"Failed to query Stripe directly for user {user_id}: {stripe_err}")
|
||||||
|
|
||||||
# Fallback: search Stripe subscriptions by metadata user_id (handles cases where
|
|
||||||
# customer was created without metadata or email doesn't match)
|
|
||||||
if not stripe_customer_id or not subscription:
|
|
||||||
try:
|
|
||||||
import stripe
|
|
||||||
meta_subs = stripe.Subscription.search(
|
|
||||||
query=f"status:'active' AND metadata['user_id']:'{user_id}'",
|
|
||||||
limit=1
|
|
||||||
)
|
|
||||||
if meta_subs and meta_subs.data:
|
|
||||||
stripe_sub = meta_subs.data[0]
|
|
||||||
stripe_customer_id = stripe_sub.customer
|
|
||||||
price_id = stripe_sub['items']['data'][0]['price']['id']
|
|
||||||
|
|
||||||
logger.info(f"Verify-checkout: Found subscription by metadata user_id for user {user_id}")
|
|
||||||
|
|
||||||
stripe_service._update_user_subscription(
|
|
||||||
user_id,
|
|
||||||
stripe_customer_id=stripe_customer_id,
|
|
||||||
stripe_subscription_id=stripe_sub.id,
|
|
||||||
status="active",
|
|
||||||
price_id=price_id
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
|
||||||
PricingService.clear_user_cache(user_id)
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
db.expire_all()
|
|
||||||
|
|
||||||
subscription = db.query(UserSubscription).filter(
|
|
||||||
UserSubscription.user_id == user_id,
|
|
||||||
UserSubscription.is_active == True
|
|
||||||
).first()
|
|
||||||
|
|
||||||
if subscription:
|
|
||||||
return {
|
|
||||||
"success": True,
|
|
||||||
"data": {
|
|
||||||
"active": True,
|
|
||||||
"plan": subscription.plan.tier.value,
|
|
||||||
"tier": subscription.plan.tier.value,
|
|
||||||
"can_use_api": True,
|
|
||||||
"limits": format_plan_limits(subscription.plan),
|
|
||||||
"source": "stripe_direct_metadata"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
except Exception as meta_err:
|
|
||||||
logger.warning(f"Failed to find subscription by metadata for user {user_id}: {meta_err}")
|
|
||||||
|
|
||||||
# Fallback to local DB status
|
# Fallback to local DB status
|
||||||
if subscription and subscription.is_active:
|
if subscription and subscription.is_active:
|
||||||
from services.subscription.pricing_service import PricingService
|
from services.subscription.pricing_service import PricingService
|
||||||
|
|||||||
@@ -142,11 +142,11 @@ async def serve_transform_video(
|
|||||||
detail="Invalid video path: path traversal detected"
|
detail="Invalid video path: path traversal detected"
|
||||||
)
|
)
|
||||||
|
|
||||||
if not resolved_video_path.exists():
|
if not video_path.exists():
|
||||||
raise HTTPException(status_code=404, detail="Video not found")
|
raise HTTPException(status_code=404, detail="Video not found")
|
||||||
|
|
||||||
return FileResponse(
|
return FileResponse(
|
||||||
path=str(resolved_video_path),
|
path=str(video_path),
|
||||||
media_type="video/mp4",
|
media_type="video/mp4",
|
||||||
filename=video_filename
|
filename=video_filename
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ from services.seo_tools.technical_seo_service import TechnicalSEOService
|
|||||||
from services.seo_tools.enterprise_seo_service import EnterpriseSEOService
|
from services.seo_tools.enterprise_seo_service import EnterpriseSEOService
|
||||||
from services.seo_tools.gsc_analyzer_service import GSCAnalyzerService
|
from services.seo_tools.gsc_analyzer_service import GSCAnalyzerService
|
||||||
from services.seo_tools.content_strategy_service import ContentStrategyService
|
from services.seo_tools.content_strategy_service import ContentStrategyService
|
||||||
from services.seo_tools.llm_insights_service import LLMInsightsService
|
|
||||||
from services.database import get_session_for_user
|
from services.database import get_session_for_user
|
||||||
from api.content_planning.services.content_strategy.onboarding import OnboardingDataIntegrationService
|
from api.content_planning.services.content_strategy.onboarding import OnboardingDataIntegrationService
|
||||||
from middleware.logging_middleware import log_api_call, save_to_file
|
from middleware.logging_middleware import log_api_call, save_to_file
|
||||||
@@ -152,53 +151,6 @@ class ContentOpportunitiesRequest(BaseModel):
|
|||||||
min_impressions: int = Field(default=100, ge=10, description="Minimum impressions threshold")
|
min_impressions: int = Field(default=100, ge=10, description="Minimum impressions threshold")
|
||||||
date_range_days: int = Field(default=90, ge=7, le=365, description="Number of days to analyze")
|
date_range_days: int = Field(default=90, ge=7, le=365, description="Number of days to analyze")
|
||||||
|
|
||||||
# ==================== LLM INSIGHTS REQUEST MODELS ====================
|
|
||||||
|
|
||||||
class EnterpriseAuditInsightsRequest(BaseModel):
|
|
||||||
"""Request model for AI insights from enterprise audit"""
|
|
||||||
audit_results: Dict[str, Any] = Field(..., description="Complete audit results")
|
|
||||||
website_url: str = Field(..., description="Website being audited")
|
|
||||||
target_keywords: Optional[List[str]] = Field(None, description="Target keywords")
|
|
||||||
|
|
||||||
class GSCAnalysisInsightsRequest(BaseModel):
|
|
||||||
"""Request model for AI insights from GSC analysis"""
|
|
||||||
gsc_analysis: Dict[str, Any] = Field(..., description="Complete GSC analysis data")
|
|
||||||
website_url: str = Field(..., description="Website being analyzed")
|
|
||||||
|
|
||||||
class ContentStrategyRequest(BaseModel):
|
|
||||||
"""Request model for content strategy generation"""
|
|
||||||
current_content: Dict[str, Any] = Field(..., description="Current content analysis")
|
|
||||||
content_gaps: List[str] = Field(..., description="Identified content gaps")
|
|
||||||
target_keywords: List[str] = Field(..., description="Target keywords")
|
|
||||||
competitor_content: Optional[Dict[str, Any]] = Field(None, description="Competitor content analysis")
|
|
||||||
|
|
||||||
class TrafficRoadmapRequest(BaseModel):
|
|
||||||
"""Request model for traffic improvement roadmap"""
|
|
||||||
current_metrics: Dict[str, Any] = Field(..., description="Current traffic metrics")
|
|
||||||
identified_opportunities: List[Dict[str, Any]] = Field(..., description="Improvement opportunities")
|
|
||||||
implementation_timeline_weeks: int = Field(default=12, ge=4, le=52, description="Implementation timeline")
|
|
||||||
|
|
||||||
class CompetitiveInsightsRequest(BaseModel):
|
|
||||||
"""Request model for competitive insights generation"""
|
|
||||||
primary_site_analysis: Dict[str, Any] = Field(..., description="Primary site analysis")
|
|
||||||
competitor_analyses: List[Dict[str, Any]] = Field(..., description="Competitor analyses")
|
|
||||||
|
|
||||||
class PrioritizedRecommendationsRequest(BaseModel):
|
|
||||||
"""Request model for prioritized recommendations"""
|
|
||||||
all_recommendations: List[Dict[str, Any]] = Field(..., description="All recommendations to prioritize")
|
|
||||||
business_context: Dict[str, Any] = Field(..., description="Business goals and constraints")
|
|
||||||
|
|
||||||
class QuickWinsRequest(BaseModel):
|
|
||||||
"""Request model for quick wins identification"""
|
|
||||||
audit_data: Dict[str, Any] = Field(..., description="Complete audit data")
|
|
||||||
max_days_to_implement: int = Field(default=7, ge=1, le=30, description="Maximum days to implement")
|
|
||||||
|
|
||||||
class KeywordExpansionRequest(BaseModel):
|
|
||||||
"""Request model for keyword expansion"""
|
|
||||||
current_keywords: List[str] = Field(..., description="Current target keywords")
|
|
||||||
content_analysis: Dict[str, Any] = Field(..., description="Content analysis data")
|
|
||||||
target_difficulty: Optional[str] = Field(None, description="Target difficulty (low/medium/high)")
|
|
||||||
|
|
||||||
# Exception Handler
|
# Exception Handler
|
||||||
async def handle_seo_tool_exception(func_name: str, error: Exception, request_data: Dict) -> ErrorResponse:
|
async def handle_seo_tool_exception(func_name: str, error: Exception, request_data: Dict) -> ErrorResponse:
|
||||||
"""Handle exceptions from SEO tools with intelligent logging"""
|
"""Handle exceptions from SEO tools with intelligent logging"""
|
||||||
@@ -1129,466 +1081,3 @@ async def check_enterprise_services_health() -> BaseResponse:
|
|||||||
message="Enterprise health check failed",
|
message="Enterprise health check failed",
|
||||||
data={"error": str(e)}
|
data={"error": str(e)}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
# ==================== LLM INSIGHTS ENDPOINTS (Phase 2A.2) ====================
|
|
||||||
|
|
||||||
@router.post("/llm/generate-audit-insights", response_model=BaseResponse)
|
|
||||||
@log_api_call
|
|
||||||
async def generate_audit_insights(
|
|
||||||
request: EnterpriseAuditInsightsRequest,
|
|
||||||
background_tasks: BackgroundTasks,
|
|
||||||
current_user: dict = Depends(get_current_user)
|
|
||||||
) -> Union[BaseResponse, ErrorResponse]:
|
|
||||||
"""
|
|
||||||
Generate AI-powered insights from enterprise SEO audit results.
|
|
||||||
|
|
||||||
Analyzes audit findings and produces strategic, actionable insights with:
|
|
||||||
- Priority scoring (1-10 scale)
|
|
||||||
- Traffic impact projections
|
|
||||||
- Implementation difficulty assessments
|
|
||||||
- Step-by-step action guides
|
|
||||||
- Required tools and resources
|
|
||||||
"""
|
|
||||||
start_time = datetime.utcnow()
|
|
||||||
|
|
||||||
try:
|
|
||||||
logger.info(f"Generating audit insights for {request.website_url}")
|
|
||||||
|
|
||||||
llm_service = LLMInsightsService()
|
|
||||||
insights = await llm_service.generate_enterprise_audit_insights(
|
|
||||||
audit_results=request.audit_results,
|
|
||||||
website_url=request.website_url,
|
|
||||||
target_keywords=request.target_keywords
|
|
||||||
)
|
|
||||||
|
|
||||||
execution_time = (datetime.utcnow() - start_time).total_seconds()
|
|
||||||
|
|
||||||
# Log successful operation
|
|
||||||
log_data = {
|
|
||||||
"operation": "audit_insights_generation",
|
|
||||||
"website_url": request.website_url,
|
|
||||||
"insights_generated": len(insights.get('insights', [])),
|
|
||||||
"execution_time": execution_time,
|
|
||||||
"success": True
|
|
||||||
}
|
|
||||||
background_tasks.add_task(save_to_file, f"{LOG_DIR}/llm_operations.jsonl", log_data)
|
|
||||||
|
|
||||||
return BaseResponse(
|
|
||||||
success=True,
|
|
||||||
message="Audit insights generated successfully",
|
|
||||||
execution_time=execution_time,
|
|
||||||
data=insights
|
|
||||||
)
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Audit insights generation failed: {str(e)}", exc_info=True)
|
|
||||||
return await handle_seo_tool_exception("generate_audit_insights", e, {"website_url": request.website_url})
|
|
||||||
|
|
||||||
|
|
||||||
@router.post("/llm/generate-gsc-insights", response_model=BaseResponse)
|
|
||||||
@log_api_call
|
|
||||||
async def generate_gsc_insights(
|
|
||||||
request: GSCAnalysisInsightsRequest,
|
|
||||||
background_tasks: BackgroundTasks,
|
|
||||||
current_user: dict = Depends(get_current_user)
|
|
||||||
) -> Union[BaseResponse, ErrorResponse]:
|
|
||||||
"""
|
|
||||||
Generate strategic insights from GSC search performance analysis.
|
|
||||||
|
|
||||||
Produces targeted, actionable insights including:
|
|
||||||
- Keyword optimization opportunities
|
|
||||||
- Content ranking improvement strategies
|
|
||||||
- CTR enhancement tactics
|
|
||||||
- Competitive positioning analysis
|
|
||||||
- Quick-win identification
|
|
||||||
"""
|
|
||||||
start_time = datetime.utcnow()
|
|
||||||
|
|
||||||
try:
|
|
||||||
logger.info(f"Generating GSC insights for {request.website_url}")
|
|
||||||
|
|
||||||
llm_service = LLMInsightsService()
|
|
||||||
insights = await llm_service.generate_gsc_analysis_insights(
|
|
||||||
gsc_analysis=request.gsc_analysis,
|
|
||||||
website_url=request.website_url
|
|
||||||
)
|
|
||||||
|
|
||||||
execution_time = (datetime.utcnow() - start_time).total_seconds()
|
|
||||||
|
|
||||||
log_data = {
|
|
||||||
"operation": "gsc_insights_generation",
|
|
||||||
"website_url": request.website_url,
|
|
||||||
"insights_generated": len(insights.get('insights', [])),
|
|
||||||
"execution_time": execution_time,
|
|
||||||
"success": True
|
|
||||||
}
|
|
||||||
background_tasks.add_task(save_to_file, f"{LOG_DIR}/llm_operations.jsonl", log_data)
|
|
||||||
|
|
||||||
return BaseResponse(
|
|
||||||
success=True,
|
|
||||||
message="GSC insights generated successfully",
|
|
||||||
execution_time=execution_time,
|
|
||||||
data=insights
|
|
||||||
)
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"GSC insights generation failed: {str(e)}", exc_info=True)
|
|
||||||
return await handle_seo_tool_exception("generate_gsc_insights", e, {"website_url": request.website_url})
|
|
||||||
|
|
||||||
|
|
||||||
@router.post("/llm/generate-content-strategy", response_model=BaseResponse)
|
|
||||||
@log_api_call
|
|
||||||
async def generate_content_strategy(
|
|
||||||
request: ContentStrategyRequest,
|
|
||||||
background_tasks: BackgroundTasks,
|
|
||||||
current_user: dict = Depends(get_current_user)
|
|
||||||
) -> Union[BaseResponse, ErrorResponse]:
|
|
||||||
"""
|
|
||||||
Generate comprehensive content strategy with AI recommendations.
|
|
||||||
|
|
||||||
Creates detailed strategy including:
|
|
||||||
- Content gap analysis and solutions
|
|
||||||
- Content calendar recommendations
|
|
||||||
- Keyword-to-content mapping
|
|
||||||
- Competitive content benchmarking
|
|
||||||
- Topic cluster suggestions
|
|
||||||
- Publishing frequency recommendations
|
|
||||||
"""
|
|
||||||
start_time = datetime.utcnow()
|
|
||||||
|
|
||||||
try:
|
|
||||||
logger.info(f"Generating content strategy ({len(request.content_gaps)} gaps)")
|
|
||||||
|
|
||||||
llm_service = LLMInsightsService()
|
|
||||||
strategy = await llm_service.generate_content_strategy_insights(
|
|
||||||
current_content=request.current_content,
|
|
||||||
content_gaps=request.content_gaps,
|
|
||||||
target_keywords=request.target_keywords,
|
|
||||||
competitor_content=request.competitor_content
|
|
||||||
)
|
|
||||||
|
|
||||||
execution_time = (datetime.utcnow() - start_time).total_seconds()
|
|
||||||
|
|
||||||
log_data = {
|
|
||||||
"operation": "content_strategy_generation",
|
|
||||||
"gaps_addressed": len(request.content_gaps),
|
|
||||||
"keywords_analyzed": len(request.target_keywords),
|
|
||||||
"execution_time": execution_time,
|
|
||||||
"success": True
|
|
||||||
}
|
|
||||||
background_tasks.add_task(save_to_file, f"{LOG_DIR}/llm_operations.jsonl", log_data)
|
|
||||||
|
|
||||||
return BaseResponse(
|
|
||||||
success=True,
|
|
||||||
message="Content strategy generated successfully",
|
|
||||||
execution_time=execution_time,
|
|
||||||
data=strategy
|
|
||||||
)
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Content strategy generation failed: {str(e)}", exc_info=True)
|
|
||||||
return await handle_seo_tool_exception("generate_content_strategy", e, {"gaps_count": len(request.content_gaps)})
|
|
||||||
|
|
||||||
|
|
||||||
@router.post("/llm/generate-traffic-roadmap", response_model=BaseResponse)
|
|
||||||
@log_api_call
|
|
||||||
async def generate_traffic_roadmap(
|
|
||||||
request: TrafficRoadmapRequest,
|
|
||||||
background_tasks: BackgroundTasks,
|
|
||||||
current_user: dict = Depends(get_current_user)
|
|
||||||
) -> Union[BaseResponse, ErrorResponse]:
|
|
||||||
"""
|
|
||||||
Generate phased traffic improvement roadmap with projections.
|
|
||||||
|
|
||||||
Produces detailed roadmap with:
|
|
||||||
- Phased implementation plan (Week 1, 2, 3+)
|
|
||||||
- Traffic gain projections per phase
|
|
||||||
- Priority-ordered action items
|
|
||||||
- Resource requirements per phase
|
|
||||||
- Key performance indicators (KPIs)
|
|
||||||
- Success metrics and validation points
|
|
||||||
- Risk mitigation strategies
|
|
||||||
"""
|
|
||||||
start_time = datetime.utcnow()
|
|
||||||
|
|
||||||
try:
|
|
||||||
logger.info(f"Generating traffic roadmap ({request.implementation_timeline_weeks} weeks)")
|
|
||||||
|
|
||||||
llm_service = LLMInsightsService()
|
|
||||||
roadmap = await llm_service.generate_traffic_improvement_roadmap(
|
|
||||||
current_metrics=request.current_metrics,
|
|
||||||
identified_opportunities=request.identified_opportunities,
|
|
||||||
implementation_timeline_weeks=request.implementation_timeline_weeks
|
|
||||||
)
|
|
||||||
|
|
||||||
execution_time = (datetime.utcnow() - start_time).total_seconds()
|
|
||||||
|
|
||||||
log_data = {
|
|
||||||
"operation": "traffic_roadmap_generation",
|
|
||||||
"timeline_weeks": request.implementation_timeline_weeks,
|
|
||||||
"opportunities_count": len(request.identified_opportunities),
|
|
||||||
"execution_time": execution_time,
|
|
||||||
"success": True
|
|
||||||
}
|
|
||||||
background_tasks.add_task(save_to_file, f"{LOG_DIR}/llm_operations.jsonl", log_data)
|
|
||||||
|
|
||||||
return BaseResponse(
|
|
||||||
success=True,
|
|
||||||
message="Traffic roadmap generated successfully",
|
|
||||||
execution_time=execution_time,
|
|
||||||
data=roadmap
|
|
||||||
)
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Traffic roadmap generation failed: {str(e)}", exc_info=True)
|
|
||||||
return await handle_seo_tool_exception("generate_traffic_roadmap", e,
|
|
||||||
{"opportunities_count": len(request.identified_opportunities)})
|
|
||||||
|
|
||||||
|
|
||||||
@router.post("/llm/generate-competitive-insights", response_model=BaseResponse)
|
|
||||||
@log_api_call
|
|
||||||
async def generate_competitive_insights(
|
|
||||||
request: CompetitiveInsightsRequest,
|
|
||||||
background_tasks: BackgroundTasks,
|
|
||||||
current_user: dict = Depends(get_current_user)
|
|
||||||
) -> Union[BaseResponse, ErrorResponse]:
|
|
||||||
"""
|
|
||||||
Generate competitive positioning and intelligence insights.
|
|
||||||
|
|
||||||
Analyzes competitive landscape and provides:
|
|
||||||
- Competitive advantage identification
|
|
||||||
- Competitive gap analysis
|
|
||||||
- Market opportunity identification
|
|
||||||
- Threat assessment
|
|
||||||
- Win strategy recommendations
|
|
||||||
- Differentiation recommendations
|
|
||||||
- Market position recommendations
|
|
||||||
"""
|
|
||||||
start_time = datetime.utcnow()
|
|
||||||
|
|
||||||
try:
|
|
||||||
logger.info(f"Generating competitive insights ({len(request.competitor_analyses)} competitors)")
|
|
||||||
|
|
||||||
llm_service = LLMInsightsService()
|
|
||||||
insights = await llm_service.generate_competitive_insights(
|
|
||||||
primary_site_analysis=request.primary_site_analysis,
|
|
||||||
competitor_analyses=request.competitor_analyses
|
|
||||||
)
|
|
||||||
|
|
||||||
execution_time = (datetime.utcnow() - start_time).total_seconds()
|
|
||||||
|
|
||||||
log_data = {
|
|
||||||
"operation": "competitive_insights_generation",
|
|
||||||
"competitors_analyzed": len(request.competitor_analyses),
|
|
||||||
"execution_time": execution_time,
|
|
||||||
"success": True
|
|
||||||
}
|
|
||||||
background_tasks.add_task(save_to_file, f"{LOG_DIR}/llm_operations.jsonl", log_data)
|
|
||||||
|
|
||||||
return BaseResponse(
|
|
||||||
success=True,
|
|
||||||
message="Competitive insights generated successfully",
|
|
||||||
execution_time=execution_time,
|
|
||||||
data=insights
|
|
||||||
)
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Competitive insights generation failed: {str(e)}", exc_info=True)
|
|
||||||
return await handle_seo_tool_exception("generate_competitive_insights", e,
|
|
||||||
{"competitors_count": len(request.competitor_analyses)})
|
|
||||||
|
|
||||||
|
|
||||||
@router.post("/llm/prioritized-recommendations", response_model=BaseResponse)
|
|
||||||
@log_api_call
|
|
||||||
async def get_prioritized_recommendations(
|
|
||||||
request: PrioritizedRecommendationsRequest,
|
|
||||||
background_tasks: BackgroundTasks,
|
|
||||||
current_user: dict = Depends(get_current_user)
|
|
||||||
) -> Union[BaseResponse, ErrorResponse]:
|
|
||||||
"""
|
|
||||||
Get AI-prioritized recommendations ranked by business impact.
|
|
||||||
|
|
||||||
Scores and prioritizes recommendations by:
|
|
||||||
- Traffic impact potential
|
|
||||||
- Implementation effort required
|
|
||||||
- Resource requirements
|
|
||||||
- Timeline to implementation
|
|
||||||
- Business alignment
|
|
||||||
- Risk level
|
|
||||||
- ROI potential
|
|
||||||
|
|
||||||
Returns categorized as: Quick Wins | High Impact | Long-term
|
|
||||||
"""
|
|
||||||
start_time = datetime.utcnow()
|
|
||||||
|
|
||||||
try:
|
|
||||||
logger.info(f"Prioritizing {len(request.all_recommendations)} recommendations")
|
|
||||||
|
|
||||||
llm_service = LLMInsightsService()
|
|
||||||
prioritized = await llm_service.generate_prioritized_recommendations(
|
|
||||||
all_recommendations=request.all_recommendations,
|
|
||||||
business_context=request.business_context
|
|
||||||
)
|
|
||||||
|
|
||||||
execution_time = (datetime.utcnow() - start_time).total_seconds()
|
|
||||||
|
|
||||||
log_data = {
|
|
||||||
"operation": "prioritized_recommendations_generation",
|
|
||||||
"total_recommendations": len(request.all_recommendations),
|
|
||||||
"quick_wins": len(prioritized.get('quick_wins', [])),
|
|
||||||
"execution_time": execution_time,
|
|
||||||
"success": True
|
|
||||||
}
|
|
||||||
background_tasks.add_task(save_to_file, f"{LOG_DIR}/llm_operations.jsonl", log_data)
|
|
||||||
|
|
||||||
return BaseResponse(
|
|
||||||
success=True,
|
|
||||||
message="Recommendations prioritized successfully",
|
|
||||||
execution_time=execution_time,
|
|
||||||
data=prioritized
|
|
||||||
)
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Recommendation prioritization failed: {str(e)}", exc_info=True)
|
|
||||||
return await handle_seo_tool_exception("get_prioritized_recommendations", e,
|
|
||||||
{"recommendations_count": len(request.all_recommendations)})
|
|
||||||
|
|
||||||
|
|
||||||
@router.post("/llm/quick-wins", response_model=BaseResponse)
|
|
||||||
@log_api_call
|
|
||||||
async def identify_quick_wins(
|
|
||||||
request: QuickWinsRequest,
|
|
||||||
background_tasks: BackgroundTasks,
|
|
||||||
current_user: dict = Depends(get_current_user)
|
|
||||||
) -> Union[BaseResponse, ErrorResponse]:
|
|
||||||
"""
|
|
||||||
Identify quick wins - high-impact actions implementable in short timeframe.
|
|
||||||
|
|
||||||
Finds high-ROI quick wins including:
|
|
||||||
- Meta tag optimization opportunities
|
|
||||||
- URL structure improvements
|
|
||||||
- On-page optimization quick fixes
|
|
||||||
- Internal linking recommendations
|
|
||||||
- Content formatting improvements
|
|
||||||
- Technical SEO quick fixes
|
|
||||||
- Performance optimization opportunities
|
|
||||||
|
|
||||||
Each with: estimated traffic gain, implementation time, tools needed, expected outcomes
|
|
||||||
"""
|
|
||||||
start_time = datetime.utcnow()
|
|
||||||
|
|
||||||
try:
|
|
||||||
logger.info(f"Identifying quick wins (max {request.max_days_to_implement} days)")
|
|
||||||
|
|
||||||
llm_service = LLMInsightsService()
|
|
||||||
quick_wins = await llm_service.generate_quick_wins(
|
|
||||||
audit_data=request.audit_data,
|
|
||||||
max_days_to_implement=request.max_days_to_implement
|
|
||||||
)
|
|
||||||
|
|
||||||
execution_time = (datetime.utcnow() - start_time).total_seconds()
|
|
||||||
|
|
||||||
log_data = {
|
|
||||||
"operation": "quick_wins_identification",
|
|
||||||
"max_days": request.max_days_to_implement,
|
|
||||||
"quick_wins_found": len(quick_wins.get('quick_wins', [])),
|
|
||||||
"total_potential_traffic": quick_wins.get('total_potential_traffic', 0),
|
|
||||||
"execution_time": execution_time,
|
|
||||||
"success": True
|
|
||||||
}
|
|
||||||
background_tasks.add_task(save_to_file, f"{LOG_DIR}/llm_operations.jsonl", log_data)
|
|
||||||
|
|
||||||
return BaseResponse(
|
|
||||||
success=True,
|
|
||||||
message="Quick wins identified successfully",
|
|
||||||
execution_time=execution_time,
|
|
||||||
data=quick_wins
|
|
||||||
)
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Quick wins identification failed: {str(e)}", exc_info=True)
|
|
||||||
return await handle_seo_tool_exception("identify_quick_wins", e,
|
|
||||||
{"max_days": request.max_days_to_implement})
|
|
||||||
|
|
||||||
|
|
||||||
@router.post("/llm/keyword-expansion", response_model=BaseResponse)
|
|
||||||
@log_api_call
|
|
||||||
async def expand_keywords(
|
|
||||||
request: KeywordExpansionRequest,
|
|
||||||
background_tasks: BackgroundTasks,
|
|
||||||
current_user: dict = Depends(get_current_user)
|
|
||||||
) -> Union[BaseResponse, ErrorResponse]:
|
|
||||||
"""
|
|
||||||
Expand keyword list with AI-generated related and long-tail keywords.
|
|
||||||
|
|
||||||
Generates 15-20 additional keywords including:
|
|
||||||
- Long-tail keyword variations
|
|
||||||
- Question-based keywords (People Also Ask)
|
|
||||||
- Local keyword variations
|
|
||||||
- Intent-based keywords (commercial, informational, navigational)
|
|
||||||
- Seasonal keyword variants
|
|
||||||
|
|
||||||
Each keyword includes: search volume estimate, difficulty score, relevance, content opportunity
|
|
||||||
"""
|
|
||||||
start_time = datetime.utcnow()
|
|
||||||
|
|
||||||
try:
|
|
||||||
logger.info(f"Expanding keywords from {len(request.current_keywords)} base keywords")
|
|
||||||
|
|
||||||
llm_service = LLMInsightsService()
|
|
||||||
expansion = await llm_service.generate_keyword_expansion(
|
|
||||||
current_keywords=request.current_keywords,
|
|
||||||
content_analysis=request.content_analysis,
|
|
||||||
target_difficulty=request.target_difficulty
|
|
||||||
)
|
|
||||||
|
|
||||||
execution_time = (datetime.utcnow() - start_time).total_seconds()
|
|
||||||
|
|
||||||
log_data = {
|
|
||||||
"operation": "keyword_expansion",
|
|
||||||
"original_keywords": len(request.current_keywords),
|
|
||||||
"expanded_keywords": expansion.get('expanded_keywords', 0),
|
|
||||||
"execution_time": execution_time,
|
|
||||||
"success": True
|
|
||||||
}
|
|
||||||
background_tasks.add_task(save_to_file, f"{LOG_DIR}/llm_operations.jsonl", log_data)
|
|
||||||
|
|
||||||
return BaseResponse(
|
|
||||||
success=True,
|
|
||||||
message="Keyword expansion completed successfully",
|
|
||||||
execution_time=execution_time,
|
|
||||||
data=expansion
|
|
||||||
)
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Keyword expansion failed: {str(e)}", exc_info=True)
|
|
||||||
return await handle_seo_tool_exception("expand_keywords", e,
|
|
||||||
{"keywords_count": len(request.current_keywords)})
|
|
||||||
|
|
||||||
|
|
||||||
@router.get("/llm/health", response_model=BaseResponse)
|
|
||||||
@log_api_call
|
|
||||||
async def check_llm_insights_health() -> BaseResponse:
|
|
||||||
"""Health check for LLM insights service"""
|
|
||||||
try:
|
|
||||||
llm_service = LLMInsightsService()
|
|
||||||
health = await llm_service.health_check()
|
|
||||||
|
|
||||||
return BaseResponse(
|
|
||||||
success=True,
|
|
||||||
message="LLM insights service is healthy",
|
|
||||||
data={
|
|
||||||
"service": health.get('service'),
|
|
||||||
"version": health.get('version'),
|
|
||||||
"llm_integration": health.get('llm_integration'),
|
|
||||||
"timestamp": health.get('last_check')
|
|
||||||
}
|
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"LLM insights health check failed: {str(e)}")
|
|
||||||
return BaseResponse(
|
|
||||||
success=False,
|
|
||||||
message="LLM insights service health check failed",
|
|
||||||
data={"error": str(e)}
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -421,15 +421,14 @@ class GSCService:
|
|||||||
if not start_date:
|
if not start_date:
|
||||||
start_date = (datetime.now() - timedelta(days=30)).strftime('%Y-%m-%d')
|
start_date = (datetime.now() - timedelta(days=30)).strftime('%Y-%m-%d')
|
||||||
|
|
||||||
# Check cache first (only return cached data with non-empty query rows)
|
# Check cache first
|
||||||
cache_key = f"{user_id}_{site_url}_{start_date}_{end_date}"
|
cache_key = f"{user_id}_{site_url}_{start_date}_{end_date}"
|
||||||
cached_data = self._get_cached_data(user_id, site_url, 'analytics', cache_key)
|
cached_data = self._get_cached_data(user_id, site_url, 'analytics', cache_key)
|
||||||
if cached_data and isinstance(cached_data, dict):
|
if cached_data and isinstance(cached_data, dict):
|
||||||
has_pages = 'page_data' in cached_data and isinstance(cached_data.get('page_data'), dict)
|
has_pages = 'page_data' in cached_data and isinstance(cached_data.get('page_data'), dict)
|
||||||
has_queries = 'query_data' in cached_data and isinstance(cached_data.get('query_data'), dict)
|
has_queries = 'query_data' in cached_data and isinstance(cached_data.get('query_data'), dict)
|
||||||
has_query_rows = cached_data.get('query_data', {}).get('rows', [])
|
if has_pages and has_queries:
|
||||||
if has_pages and has_queries and has_query_rows:
|
logger.info(f"Returning cached analytics data for user: {user_id} (includes page_data)")
|
||||||
logger.info(f"Returning cached analytics data for user: {user_id} (includes page_data, {len(has_query_rows)} query rows)")
|
|
||||||
return cached_data
|
return cached_data
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -624,11 +623,7 @@ class GSCService:
|
|||||||
'siteUrl': site_url
|
'siteUrl': site_url
|
||||||
}
|
}
|
||||||
|
|
||||||
if analytics_data.get('query_data', {}).get('rows'):
|
self._cache_data(user_id, site_url, 'analytics', analytics_data, cache_key)
|
||||||
self._cache_data(user_id, site_url, 'analytics', analytics_data, cache_key)
|
|
||||||
logger.info(f"Analytics data cached for user: {user_id}, site: {site_url} ({len(analytics_data.get('query_data', {}).get('rows', []))} query rows)")
|
|
||||||
else:
|
|
||||||
logger.info(f"Skipping cache for user: {user_id} — empty query_data rows; next request will retry fresh")
|
|
||||||
|
|
||||||
logger.info(f"Retrieved comprehensive analytics data for user: {user_id}, site: {site_url}")
|
logger.info(f"Retrieved comprehensive analytics data for user: {user_id}, site: {site_url}")
|
||||||
return analytics_data
|
return analytics_data
|
||||||
@@ -660,7 +655,7 @@ class GSCService:
|
|||||||
'warning': f'Query-level data unavailable: {str(query_error)}'
|
'warning': f'Query-level data unavailable: {str(query_error)}'
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info(f"Query-level data unavailable for user {user_id}; fallback analytics returned (not cached)")
|
self._cache_data(user_id, site_url, 'analytics', analytics_data, cache_key)
|
||||||
return analytics_data
|
return analytics_data
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
@@ -1,898 +0,0 @@
|
|||||||
"""
|
|
||||||
LLM-Powered SEO Insights Service for Phase 2A.2
|
|
||||||
|
|
||||||
Provides AI-powered insights and recommendations based on enterprise SEO audits
|
|
||||||
and GSC analysis using Claude/GPT LLM models with advanced prompt engineering.
|
|
||||||
|
|
||||||
Features:
|
|
||||||
- 8 specialized insight generation methods
|
|
||||||
- Dynamic prompt templates with context awareness
|
|
||||||
- Priority-scored recommendations
|
|
||||||
- Traffic improvement strategies
|
|
||||||
- Implementation guides and phasing
|
|
||||||
- Competitive intelligence synthesis
|
|
||||||
- Content gap analysis
|
|
||||||
- AI-driven traffic projections
|
|
||||||
"""
|
|
||||||
|
|
||||||
from typing import Dict, Any, List, Optional, Tuple
|
|
||||||
from datetime import datetime
|
|
||||||
from dataclasses import dataclass, asdict
|
|
||||||
import asyncio
|
|
||||||
import json
|
|
||||||
from loguru import logger
|
|
||||||
|
|
||||||
from services.llm_providers.main_text_generation import llm_text_gen
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class AIInsight:
|
|
||||||
"""Data class for AI-generated insights"""
|
|
||||||
title: str
|
|
||||||
description: str
|
|
||||||
priority_score: int # 1-10
|
|
||||||
estimated_traffic_impact: str
|
|
||||||
implementation_difficulty: str # easy, moderate, hard
|
|
||||||
estimated_time_to_implement: str # days, weeks, months
|
|
||||||
steps: List[str]
|
|
||||||
tools_required: List[str]
|
|
||||||
expected_outcomes: List[str]
|
|
||||||
business_impact: str
|
|
||||||
|
|
||||||
|
|
||||||
class LLMInsightsService:
|
|
||||||
"""
|
|
||||||
Service for generating AI-powered SEO insights and recommendations
|
|
||||||
using LLM models with specialized prompts for different analysis types.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
"""Initialize the LLM insights service"""
|
|
||||||
self.service_name = "llm_insights_generator"
|
|
||||||
self.version = "1.0"
|
|
||||||
self.model_preference = "claude" # Claude for superior reasoning
|
|
||||||
logger.info(f"Initialized {self.service_name} v{self.version}")
|
|
||||||
|
|
||||||
# ============= AUDIT INSIGHTS =============
|
|
||||||
|
|
||||||
async def generate_enterprise_audit_insights(
|
|
||||||
self,
|
|
||||||
audit_results: Dict[str, Any],
|
|
||||||
website_url: str,
|
|
||||||
target_keywords: Optional[List[str]] = None
|
|
||||||
) -> Dict[str, Any]:
|
|
||||||
"""
|
|
||||||
Generate comprehensive AI insights from complete enterprise audit results.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
audit_results: Full audit data from enterprise_seo_service
|
|
||||||
website_url: The audited website
|
|
||||||
target_keywords: Keywords from analysis
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
AI-generated insights with priority scoring
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
logger.info(f"Generating enterprise audit insights for {website_url}")
|
|
||||||
|
|
||||||
# Extract key metrics from audit
|
|
||||||
overall_score = audit_results.get('overall_score', 0)
|
|
||||||
component_scores = audit_results.get('component_scores', {})
|
|
||||||
priority_actions = audit_results.get('priority_actions', [])
|
|
||||||
|
|
||||||
# Build context for LLM
|
|
||||||
context = self._build_audit_context(
|
|
||||||
website_url, audit_results, target_keywords
|
|
||||||
)
|
|
||||||
|
|
||||||
# Generate insights prompt
|
|
||||||
prompt = self._build_audit_insights_prompt(context, overall_score, component_scores)
|
|
||||||
|
|
||||||
# Call LLM
|
|
||||||
insights_json = await self._call_llm_for_json(
|
|
||||||
prompt=prompt,
|
|
||||||
context_type="enterprise_audit_insights"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Parse and structure insights
|
|
||||||
insights = self._parse_insights_response(insights_json)
|
|
||||||
|
|
||||||
# Add metadata
|
|
||||||
result = {
|
|
||||||
'status': 'completed',
|
|
||||||
'website_url': website_url,
|
|
||||||
'audit_overall_score': overall_score,
|
|
||||||
'insights_generated': len(insights),
|
|
||||||
'insights': insights,
|
|
||||||
'generated_at': datetime.utcnow().isoformat(),
|
|
||||||
'summary': self._generate_summary(insights, overall_score)
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.info(f"Generated {len(insights)} insights for {website_url}")
|
|
||||||
return result
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Enterprise audit insights generation failed: {str(e)}", exc_info=True)
|
|
||||||
raise
|
|
||||||
|
|
||||||
# ============= GSC INSIGHTS =============
|
|
||||||
|
|
||||||
async def generate_gsc_analysis_insights(
|
|
||||||
self,
|
|
||||||
gsc_analysis: Dict[str, Any],
|
|
||||||
website_url: str
|
|
||||||
) -> Dict[str, Any]:
|
|
||||||
"""
|
|
||||||
Generate strategic insights from GSC analysis with keyword opportunities.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
gsc_analysis: Full GSC analysis data
|
|
||||||
website_url: Website being analyzed
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Strategic GSC-specific insights
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
logger.info(f"Generating GSC analysis insights for {website_url}")
|
|
||||||
|
|
||||||
# Extract key GSC metrics
|
|
||||||
performance_overview = gsc_analysis.get('performance_overview', {})
|
|
||||||
content_opportunities = gsc_analysis.get('content_opportunities', [])
|
|
||||||
technical_insights = gsc_analysis.get('technical_insights', {})
|
|
||||||
|
|
||||||
# Build GSC context
|
|
||||||
context = self._build_gsc_context(gsc_analysis, website_url)
|
|
||||||
|
|
||||||
# Generate insights prompt
|
|
||||||
prompt = self._build_gsc_insights_prompt(
|
|
||||||
context,
|
|
||||||
len(content_opportunities),
|
|
||||||
performance_overview
|
|
||||||
)
|
|
||||||
|
|
||||||
# Call LLM
|
|
||||||
insights_json = await self._call_llm_for_json(
|
|
||||||
prompt=prompt,
|
|
||||||
context_type="gsc_analysis_insights"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Parse insights
|
|
||||||
insights = self._parse_insights_response(insights_json)
|
|
||||||
|
|
||||||
result = {
|
|
||||||
'status': 'completed',
|
|
||||||
'website_url': website_url,
|
|
||||||
'total_content_opportunities': len(content_opportunities),
|
|
||||||
'insights': insights,
|
|
||||||
'generated_at': datetime.utcnow().isoformat(),
|
|
||||||
'focus_areas': self._identify_gsc_focus_areas(insights)
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.info(f"Generated {len(insights)} GSC insights")
|
|
||||||
return result
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"GSC analysis insights generation failed: {str(e)}", exc_info=True)
|
|
||||||
raise
|
|
||||||
|
|
||||||
# ============= CONTENT STRATEGY =============
|
|
||||||
|
|
||||||
async def generate_content_strategy_insights(
|
|
||||||
self,
|
|
||||||
current_content: Dict[str, Any],
|
|
||||||
content_gaps: List[str],
|
|
||||||
target_keywords: List[str],
|
|
||||||
competitor_content: Optional[Dict[str, Any]] = None
|
|
||||||
) -> Dict[str, Any]:
|
|
||||||
"""
|
|
||||||
Generate strategic content recommendations based on gaps and keywords.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
current_content: Existing content analysis
|
|
||||||
content_gaps: Identified content gaps
|
|
||||||
target_keywords: Target keywords for content
|
|
||||||
competitor_content: Optional competitor content analysis
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Content strategy insights with phased plan
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
logger.info("Generating content strategy insights")
|
|
||||||
|
|
||||||
# Build content strategy context
|
|
||||||
context = self._build_content_strategy_context(
|
|
||||||
current_content, content_gaps, target_keywords, competitor_content
|
|
||||||
)
|
|
||||||
|
|
||||||
# Generate strategy prompt
|
|
||||||
prompt = self._build_content_strategy_prompt(context, len(content_gaps))
|
|
||||||
|
|
||||||
# Call LLM
|
|
||||||
strategy_json = await self._call_llm_for_json(
|
|
||||||
prompt=prompt,
|
|
||||||
context_type="content_strategy_insights"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Parse strategy insights
|
|
||||||
insights = self._parse_strategy_response(strategy_json)
|
|
||||||
|
|
||||||
result = {
|
|
||||||
'status': 'completed',
|
|
||||||
'gaps_addressed': len(content_gaps),
|
|
||||||
'strategy_insights': insights,
|
|
||||||
'phased_roadmap': self._create_content_roadmap(insights),
|
|
||||||
'generated_at': datetime.utcnow().isoformat()
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Content strategy generation failed: {str(e)}", exc_info=True)
|
|
||||||
raise
|
|
||||||
|
|
||||||
# ============= TRAFFIC ROADMAP =============
|
|
||||||
|
|
||||||
async def generate_traffic_improvement_roadmap(
|
|
||||||
self,
|
|
||||||
current_metrics: Dict[str, Any],
|
|
||||||
identified_opportunities: List[Dict[str, Any]],
|
|
||||||
implementation_timeline_weeks: int = 12
|
|
||||||
) -> Dict[str, Any]:
|
|
||||||
"""
|
|
||||||
Generate phased roadmap for traffic improvement with revenue impact.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
current_metrics: Current traffic/conversion metrics
|
|
||||||
identified_opportunities: List of improvement opportunities
|
|
||||||
implementation_timeline_weeks: Timeline for implementation
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Phased roadmap with traffic projections
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
logger.info(f"Generating traffic roadmap for {implementation_timeline_weeks} weeks")
|
|
||||||
|
|
||||||
# Build roadmap context
|
|
||||||
context = self._build_roadmap_context(
|
|
||||||
current_metrics, identified_opportunities, implementation_timeline_weeks
|
|
||||||
)
|
|
||||||
|
|
||||||
# Generate roadmap prompt
|
|
||||||
prompt = self._build_roadmap_prompt(context)
|
|
||||||
|
|
||||||
# Call LLM
|
|
||||||
roadmap_json = await self._call_llm_for_json(
|
|
||||||
prompt=prompt,
|
|
||||||
context_type="traffic_roadmap"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Parse and structure roadmap
|
|
||||||
phases = self._parse_roadmap_response(roadmap_json)
|
|
||||||
|
|
||||||
result = {
|
|
||||||
'status': 'completed',
|
|
||||||
'timeline_weeks': implementation_timeline_weeks,
|
|
||||||
'current_traffic': current_metrics.get('organic_traffic', 0),
|
|
||||||
'projected_traffic': self._calculate_projected_traffic(phases),
|
|
||||||
'phases': phases,
|
|
||||||
'generated_at': datetime.utcnow().isoformat()
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Traffic roadmap generation failed: {str(e)}", exc_info=True)
|
|
||||||
raise
|
|
||||||
|
|
||||||
# ============= COMPETITIVE INSIGHTS =============
|
|
||||||
|
|
||||||
async def generate_competitive_insights(
|
|
||||||
self,
|
|
||||||
primary_site_analysis: Dict[str, Any],
|
|
||||||
competitor_analyses: List[Dict[str, Any]]
|
|
||||||
) -> Dict[str, Any]:
|
|
||||||
"""
|
|
||||||
Generate competitive positioning insights and gap analysis.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
primary_site_analysis: Analysis of primary website
|
|
||||||
competitor_analyses: List of competitor analyses
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Competitive positioning insights
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
logger.info(f"Generating competitive insights vs {len(competitor_analyses)} competitors")
|
|
||||||
|
|
||||||
# Build competitive context
|
|
||||||
context = self._build_competitive_context(
|
|
||||||
primary_site_analysis, competitor_analyses
|
|
||||||
)
|
|
||||||
|
|
||||||
# Generate competitive prompt
|
|
||||||
prompt = self._build_competitive_insights_prompt(context)
|
|
||||||
|
|
||||||
# Call LLM
|
|
||||||
competitive_json = await self._call_llm_for_json(
|
|
||||||
prompt=prompt,
|
|
||||||
context_type="competitive_insights"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Parse competitive insights
|
|
||||||
insights = self._parse_competitive_response(competitive_json)
|
|
||||||
|
|
||||||
result = {
|
|
||||||
'status': 'completed',
|
|
||||||
'competitors_analyzed': len(competitor_analyses),
|
|
||||||
'competitive_positioning': insights,
|
|
||||||
'opportunities': self._identify_competitive_opportunities(insights),
|
|
||||||
'threats': self._identify_competitive_threats(insights),
|
|
||||||
'generated_at': datetime.utcnow().isoformat()
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Competitive insights generation failed: {str(e)}", exc_info=True)
|
|
||||||
raise
|
|
||||||
|
|
||||||
# ============= PRIORITIZED RECOMMENDATIONS =============
|
|
||||||
|
|
||||||
async def generate_prioritized_recommendations(
|
|
||||||
self,
|
|
||||||
all_recommendations: List[Dict[str, Any]],
|
|
||||||
business_context: Dict[str, Any]
|
|
||||||
) -> Dict[str, Any]:
|
|
||||||
"""
|
|
||||||
Generate AI-prioritized recommendations based on impact and effort.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
all_recommendations: All raw recommendations
|
|
||||||
business_context: Business goals and constraints
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Prioritized and scored recommendations
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
logger.info(f"Prioritizing {len(all_recommendations)} recommendations")
|
|
||||||
|
|
||||||
# Build prioritization context
|
|
||||||
context = self._build_prioritization_context(
|
|
||||||
all_recommendations, business_context
|
|
||||||
)
|
|
||||||
|
|
||||||
# Generate prioritization prompt
|
|
||||||
prompt = self._build_prioritization_prompt(context)
|
|
||||||
|
|
||||||
# Call LLM
|
|
||||||
prioritized_json = await self._call_llm_for_json(
|
|
||||||
prompt=prompt,
|
|
||||||
context_type="prioritized_recommendations"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Parse prioritized recommendations
|
|
||||||
recommendations = self._parse_prioritized_response(prioritized_json)
|
|
||||||
|
|
||||||
result = {
|
|
||||||
'status': 'completed',
|
|
||||||
'total_recommendations': len(recommendations),
|
|
||||||
'quick_wins': [r for r in recommendations if r.get('priority_score', 0) >= 8],
|
|
||||||
'high_impact': [r for r in recommendations if 6 <= r.get('priority_score', 0) < 8],
|
|
||||||
'long_term': [r for r in recommendations if r.get('priority_score', 0) < 6],
|
|
||||||
'recommendations': recommendations,
|
|
||||||
'generated_at': datetime.utcnow().isoformat()
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Recommendation prioritization failed: {str(e)}", exc_info=True)
|
|
||||||
raise
|
|
||||||
|
|
||||||
# ============= QUICK WINS =============
|
|
||||||
|
|
||||||
async def generate_quick_wins(
|
|
||||||
self,
|
|
||||||
audit_data: Dict[str, Any],
|
|
||||||
max_days_to_implement: int = 7
|
|
||||||
) -> Dict[str, Any]:
|
|
||||||
"""
|
|
||||||
Identify quick wins - high-impact items implementable in short timeframe.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
audit_data: Complete audit data
|
|
||||||
max_days_to_implement: Maximum days for "quick win"
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
List of quick wins with implementation guides
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
logger.info(f"Generating quick wins (max {max_days_to_implement} days)")
|
|
||||||
|
|
||||||
# Build quick wins context
|
|
||||||
context = self._build_quick_wins_context(audit_data, max_days_to_implement)
|
|
||||||
|
|
||||||
# Generate quick wins prompt
|
|
||||||
prompt = self._build_quick_wins_prompt(context)
|
|
||||||
|
|
||||||
# Call LLM
|
|
||||||
quick_wins_json = await self._call_llm_for_json(
|
|
||||||
prompt=prompt,
|
|
||||||
context_type="quick_wins"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Parse quick wins
|
|
||||||
wins = self._parse_quick_wins_response(quick_wins_json)
|
|
||||||
|
|
||||||
result = {
|
|
||||||
'status': 'completed',
|
|
||||||
'quick_wins_identified': len(wins),
|
|
||||||
'total_potential_traffic': sum(w.get('estimated_traffic_gain', 0) for w in wins),
|
|
||||||
'quick_wins': wins,
|
|
||||||
'implementation_order': self._order_quick_wins(wins),
|
|
||||||
'generated_at': datetime.utcnow().isoformat()
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Quick wins generation failed: {str(e)}", exc_info=True)
|
|
||||||
raise
|
|
||||||
|
|
||||||
# ============= KEYWORD EXPANSION =============
|
|
||||||
|
|
||||||
async def generate_keyword_expansion(
|
|
||||||
self,
|
|
||||||
current_keywords: List[str],
|
|
||||||
content_analysis: Dict[str, Any],
|
|
||||||
target_difficulty: Optional[str] = None
|
|
||||||
) -> Dict[str, Any]:
|
|
||||||
"""
|
|
||||||
Generate keyword expansion recommendations with difficulty and volume.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
current_keywords: Current target keywords
|
|
||||||
content_analysis: Content analysis data
|
|
||||||
target_difficulty: Preferred difficulty level (low, medium, high)
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Expanded keyword list with scoring
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
logger.info(f"Generating keyword expansion from {len(current_keywords)} keywords")
|
|
||||||
|
|
||||||
# Build keyword expansion context
|
|
||||||
context = self._build_keyword_context(
|
|
||||||
current_keywords, content_analysis, target_difficulty
|
|
||||||
)
|
|
||||||
|
|
||||||
# Generate keyword expansion prompt
|
|
||||||
prompt = self._build_keyword_expansion_prompt(context)
|
|
||||||
|
|
||||||
# Call LLM
|
|
||||||
keywords_json = await self._call_llm_for_json(
|
|
||||||
prompt=prompt,
|
|
||||||
context_type="keyword_expansion"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Parse expanded keywords
|
|
||||||
expanded = self._parse_keyword_response(keywords_json)
|
|
||||||
|
|
||||||
result = {
|
|
||||||
'status': 'completed',
|
|
||||||
'original_keywords': len(current_keywords),
|
|
||||||
'expanded_keywords': len(expanded),
|
|
||||||
'new_keywords': expanded,
|
|
||||||
'categorized_by_difficulty': self._categorize_by_difficulty(expanded),
|
|
||||||
'generated_at': datetime.utcnow().isoformat()
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Keyword expansion generation failed: {str(e)}", exc_info=True)
|
|
||||||
raise
|
|
||||||
|
|
||||||
# ============= HELPER METHODS =============
|
|
||||||
|
|
||||||
async def _call_llm_for_json(
|
|
||||||
self,
|
|
||||||
prompt: str,
|
|
||||||
context_type: str,
|
|
||||||
max_tokens: int = 2000
|
|
||||||
) -> str:
|
|
||||||
"""Call LLM and ensure JSON response"""
|
|
||||||
try:
|
|
||||||
# System prompt for JSON generation
|
|
||||||
system_prompt = """You are an expert SEO strategist and data analyst.
|
|
||||||
Generate detailed, actionable JSON responses with specific metrics and recommendations.
|
|
||||||
Ensure all responses are valid JSON that can be parsed."""
|
|
||||||
|
|
||||||
# Call LLM with JSON-focused settings
|
|
||||||
response = llm_text_gen(
|
|
||||||
prompt=prompt,
|
|
||||||
system_prompt=system_prompt,
|
|
||||||
user_id=None,
|
|
||||||
preferred_provider="claude",
|
|
||||||
flow_type=f"seo_{context_type}",
|
|
||||||
max_tokens=max_tokens,
|
|
||||||
temperature=0.7
|
|
||||||
)
|
|
||||||
|
|
||||||
# Extract JSON if wrapped in text
|
|
||||||
if isinstance(response, str):
|
|
||||||
# Try to find JSON in response
|
|
||||||
import re
|
|
||||||
json_match = re.search(r'\{[\s\S]*\}', response)
|
|
||||||
if json_match:
|
|
||||||
return json_match.group(0)
|
|
||||||
|
|
||||||
return response
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"LLM call failed for {context_type}: {str(e)}")
|
|
||||||
raise
|
|
||||||
|
|
||||||
def _build_audit_context(
|
|
||||||
self,
|
|
||||||
website_url: str,
|
|
||||||
audit_results: Dict[str, Any],
|
|
||||||
keywords: Optional[List[str]]
|
|
||||||
) -> str:
|
|
||||||
"""Build context string for audit insights"""
|
|
||||||
score = audit_results.get('overall_score', 0)
|
|
||||||
status = "strong" if score >= 70 else "moderate" if score >= 50 else "needs improvement"
|
|
||||||
|
|
||||||
return f"""
|
|
||||||
Website: {website_url}
|
|
||||||
Overall Audit Score: {score}/100 ({status})
|
|
||||||
Target Keywords: {', '.join(keywords) if keywords else 'Not specified'}
|
|
||||||
Components Analyzed: {list(audit_results.get('component_scores', {}).keys())}
|
|
||||||
Priority Actions: {len(audit_results.get('priority_actions', []))}
|
|
||||||
"""
|
|
||||||
|
|
||||||
def _build_audit_insights_prompt(
|
|
||||||
self,
|
|
||||||
context: str,
|
|
||||||
overall_score: float,
|
|
||||||
component_scores: Dict[str, float]
|
|
||||||
) -> str:
|
|
||||||
"""Build prompt for audit insights generation"""
|
|
||||||
return f"""Based on this SEO audit data:
|
|
||||||
|
|
||||||
{context}
|
|
||||||
|
|
||||||
Component Scores: {json.dumps(component_scores, indent=2)}
|
|
||||||
|
|
||||||
Generate 5-7 specific, actionable AI insights in JSON format:
|
|
||||||
{{
|
|
||||||
"insights": [
|
|
||||||
{{
|
|
||||||
"title": "Insight Title",
|
|
||||||
"description": "Detailed description of the insight",
|
|
||||||
"priority_score": 8,
|
|
||||||
"estimated_traffic_impact": "15-25%",
|
|
||||||
"implementation_difficulty": "moderate",
|
|
||||||
"estimated_time_weeks": 2,
|
|
||||||
"steps": ["Step 1", "Step 2"],
|
|
||||||
"tools_required": ["Tool1"],
|
|
||||||
"expected_outcomes": ["Outcome1"]
|
|
||||||
}}
|
|
||||||
],
|
|
||||||
"summary": "Overall summary of insights"
|
|
||||||
}}"""
|
|
||||||
|
|
||||||
def _build_gsc_insights_prompt(
|
|
||||||
self,
|
|
||||||
context: str,
|
|
||||||
opportunities_count: int,
|
|
||||||
performance: Dict[str, Any]
|
|
||||||
) -> str:
|
|
||||||
"""Build prompt for GSC insights"""
|
|
||||||
return f"""Based on Google Search Console analysis:
|
|
||||||
|
|
||||||
{context}
|
|
||||||
|
|
||||||
Total Opportunities Identified: {opportunities_count}
|
|
||||||
Current Performance: {json.dumps(performance, indent=2)}
|
|
||||||
|
|
||||||
Generate strategic GSC insights in JSON format focusing on:
|
|
||||||
1. Quick fixes for high-volume keywords
|
|
||||||
2. Keywords ready to rank higher
|
|
||||||
3. Content expansion opportunities
|
|
||||||
4. Technical SEO issues
|
|
||||||
|
|
||||||
Return as JSON with same structure as audit insights."""
|
|
||||||
|
|
||||||
def _parse_insights_response(self, response_json: str) -> List[Dict[str, Any]]:
|
|
||||||
"""Parse LLM response into insights"""
|
|
||||||
try:
|
|
||||||
data = json.loads(response_json)
|
|
||||||
return data.get('insights', [])
|
|
||||||
except:
|
|
||||||
logger.warning("Could not parse insights response as JSON")
|
|
||||||
return []
|
|
||||||
|
|
||||||
def _generate_summary(
|
|
||||||
self,
|
|
||||||
insights: List[Dict[str, Any]],
|
|
||||||
overall_score: float
|
|
||||||
) -> str:
|
|
||||||
"""Generate summary of insights"""
|
|
||||||
if not insights:
|
|
||||||
return "No insights generated"
|
|
||||||
|
|
||||||
high_priority = sum(1 for i in insights if i.get('priority_score', 0) >= 8)
|
|
||||||
return f"{high_priority} high-priority insights identified for score improvement from {overall_score}/100"
|
|
||||||
|
|
||||||
def _build_gsc_context(
|
|
||||||
self,
|
|
||||||
gsc_analysis: Dict[str, Any],
|
|
||||||
website_url: str
|
|
||||||
) -> str:
|
|
||||||
"""Build GSC context for insights"""
|
|
||||||
perf = gsc_analysis.get('performance_overview', {})
|
|
||||||
return f"""
|
|
||||||
Website: {website_url}
|
|
||||||
Total Keywords Tracked: {perf.get('total_keywords_tracked', 0)}
|
|
||||||
Total Pages Indexed: {perf.get('total_pages_indexed', 0)}
|
|
||||||
Overall CTR: {perf.get('overall_ctr', 0):.2f}%
|
|
||||||
Average Position: {perf.get('average_position', 0):.1f}
|
|
||||||
"""
|
|
||||||
|
|
||||||
def _identify_gsc_focus_areas(self, insights: List[Dict[str, Any]]) -> List[str]:
|
|
||||||
"""Identify focus areas from GSC insights"""
|
|
||||||
focus_areas = set()
|
|
||||||
for insight in insights:
|
|
||||||
if "meta" in insight.get('title', '').lower():
|
|
||||||
focus_areas.add("Meta Tags Optimization")
|
|
||||||
if "ranking" in insight.get('title', '').lower():
|
|
||||||
focus_areas.add("Ranking Improvement")
|
|
||||||
if "content" in insight.get('title', '').lower():
|
|
||||||
focus_areas.add("Content Expansion")
|
|
||||||
return list(focus_areas)
|
|
||||||
|
|
||||||
def _build_content_strategy_context(
|
|
||||||
self,
|
|
||||||
current_content: Dict[str, Any],
|
|
||||||
content_gaps: List[str],
|
|
||||||
target_keywords: List[str],
|
|
||||||
competitor_content: Optional[Dict[str, Any]]
|
|
||||||
) -> str:
|
|
||||||
"""Build content strategy context"""
|
|
||||||
return f"""
|
|
||||||
Current Content Assets: {current_content.get('total_content', 0)} pieces
|
|
||||||
Content Gaps Identified: {len(content_gaps)}
|
|
||||||
Gaps: {', '.join(content_gaps[:5])}
|
|
||||||
Target Keywords: {', '.join(target_keywords)}
|
|
||||||
Competitor Content Items: {competitor_content.get('total_items', 0) if competitor_content else 'N/A'}
|
|
||||||
"""
|
|
||||||
|
|
||||||
def _build_content_strategy_prompt(self, context: str, gap_count: int) -> str:
|
|
||||||
"""Build content strategy prompt"""
|
|
||||||
return f"""Based on content analysis:
|
|
||||||
|
|
||||||
{context}
|
|
||||||
|
|
||||||
Create a 3-phase content strategy plan for addressing {gap_count} content gaps.
|
|
||||||
Return JSON with phases, specific content pieces, keywords per content, and expected traffic impact."""
|
|
||||||
|
|
||||||
def _parse_strategy_response(self, response: str) -> List[Dict[str, Any]]:
|
|
||||||
"""Parse strategy response"""
|
|
||||||
try:
|
|
||||||
return json.loads(response).get('strategy_insights', [])
|
|
||||||
except:
|
|
||||||
return []
|
|
||||||
|
|
||||||
def _create_content_roadmap(self, insights: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
|
||||||
"""Create phased content roadmap"""
|
|
||||||
return [
|
|
||||||
{"phase": 1, "items": insights[:len(insights)//3], "timeline": "Weeks 1-4"},
|
|
||||||
{"phase": 2, "items": insights[len(insights)//3:2*len(insights)//3], "timeline": "Weeks 5-8"},
|
|
||||||
{"phase": 3, "items": insights[2*len(insights)//3:], "timeline": "Weeks 9-12"}
|
|
||||||
]
|
|
||||||
|
|
||||||
def _build_roadmap_context(
|
|
||||||
self,
|
|
||||||
current_metrics: Dict[str, Any],
|
|
||||||
opportunities: List[Dict[str, Any]],
|
|
||||||
timeline: int
|
|
||||||
) -> str:
|
|
||||||
"""Build roadmap context"""
|
|
||||||
return f"""
|
|
||||||
Current Traffic: {current_metrics.get('organic_traffic', 0)} monthly visits
|
|
||||||
Conversion Rate: {current_metrics.get('conversion_rate', 0):.2f}%
|
|
||||||
Opportunities Identified: {len(opportunities)}
|
|
||||||
Implementation Timeline: {timeline} weeks
|
|
||||||
"""
|
|
||||||
|
|
||||||
def _build_roadmap_prompt(self, context: str) -> str:
|
|
||||||
"""Build roadmap generation prompt"""
|
|
||||||
return f"""Create a detailed traffic improvement roadmap:
|
|
||||||
|
|
||||||
{context}
|
|
||||||
|
|
||||||
Generate phases with:
|
|
||||||
- Specific actions
|
|
||||||
- Expected traffic gains
|
|
||||||
- Dependencies
|
|
||||||
- Resource requirements
|
|
||||||
- Success metrics
|
|
||||||
|
|
||||||
Return as JSON with phase details and projections."""
|
|
||||||
|
|
||||||
def _parse_roadmap_response(self, response: str) -> List[Dict[str, Any]]:
|
|
||||||
"""Parse roadmap response"""
|
|
||||||
try:
|
|
||||||
return json.loads(response).get('phases', [])
|
|
||||||
except:
|
|
||||||
return []
|
|
||||||
|
|
||||||
def _calculate_projected_traffic(self, phases: List[Dict[str, Any]]) -> int:
|
|
||||||
"""Calculate total projected traffic from phases"""
|
|
||||||
total = 0
|
|
||||||
for phase in phases:
|
|
||||||
if 'projected_traffic_gain' in phase:
|
|
||||||
total += phase['projected_traffic_gain']
|
|
||||||
return total
|
|
||||||
|
|
||||||
def _build_competitive_context(
|
|
||||||
self,
|
|
||||||
primary: Dict[str, Any],
|
|
||||||
competitors: List[Dict[str, Any]]
|
|
||||||
) -> str:
|
|
||||||
"""Build competitive analysis context"""
|
|
||||||
return f"""
|
|
||||||
Primary Site Score: {primary.get('score', 0)}/100
|
|
||||||
Competitors: {len(competitors)}
|
|
||||||
Average Competitor Score: {sum(c.get('score', 0) for c in competitors) / len(competitors) if competitors else 0:.1f}/100
|
|
||||||
"""
|
|
||||||
|
|
||||||
def _build_competitive_insights_prompt(self, context: str) -> str:
|
|
||||||
"""Build competitive insights prompt"""
|
|
||||||
return f"""Analyze competitive positioning:
|
|
||||||
|
|
||||||
{context}
|
|
||||||
|
|
||||||
Identify:
|
|
||||||
1. Competitive advantages
|
|
||||||
2. Competitive gaps
|
|
||||||
3. Market opportunities
|
|
||||||
4. Threat areas
|
|
||||||
|
|
||||||
Return as JSON with detailed analysis."""
|
|
||||||
|
|
||||||
def _parse_competitive_response(self, response: str) -> Dict[str, Any]:
|
|
||||||
"""Parse competitive response"""
|
|
||||||
try:
|
|
||||||
return json.loads(response)
|
|
||||||
except:
|
|
||||||
return {}
|
|
||||||
|
|
||||||
def _identify_competitive_opportunities(self, insights: Dict[str, Any]) -> List[str]:
|
|
||||||
"""Extract competitive opportunities"""
|
|
||||||
return insights.get('opportunities', [])
|
|
||||||
|
|
||||||
def _identify_competitive_threats(self, insights: Dict[str, Any]) -> List[str]:
|
|
||||||
"""Extract competitive threats"""
|
|
||||||
return insights.get('threats', [])
|
|
||||||
|
|
||||||
def _build_prioritization_context(
|
|
||||||
self,
|
|
||||||
recommendations: List[Dict[str, Any]],
|
|
||||||
business: Dict[str, Any]
|
|
||||||
) -> str:
|
|
||||||
"""Build prioritization context"""
|
|
||||||
return f"""
|
|
||||||
Total Recommendations: {len(recommendations)}
|
|
||||||
Business Goals: {business.get('goals', [])}
|
|
||||||
Budget: {business.get('budget', 'Not specified')}
|
|
||||||
Timeline: {business.get('timeline', 'Not specified')}
|
|
||||||
"""
|
|
||||||
|
|
||||||
def _build_prioritization_prompt(self, context: str) -> str:
|
|
||||||
"""Build prioritization prompt"""
|
|
||||||
return f"""Prioritize recommendations by impact and effort:
|
|
||||||
|
|
||||||
{context}
|
|
||||||
|
|
||||||
Score each 1-10 on:
|
|
||||||
- Impact
|
|
||||||
- Effort required
|
|
||||||
- Timeline
|
|
||||||
- Business alignment
|
|
||||||
|
|
||||||
Return JSON with prioritized list and scoring."""
|
|
||||||
|
|
||||||
def _parse_prioritized_response(self, response: str) -> List[Dict[str, Any]]:
|
|
||||||
"""Parse prioritized recommendations"""
|
|
||||||
try:
|
|
||||||
return json.loads(response).get('recommendations', [])
|
|
||||||
except:
|
|
||||||
return []
|
|
||||||
|
|
||||||
def _build_quick_wins_context(
|
|
||||||
self,
|
|
||||||
audit_data: Dict[str, Any],
|
|
||||||
max_days: int
|
|
||||||
) -> str:
|
|
||||||
"""Build quick wins context"""
|
|
||||||
return f"""
|
|
||||||
Maximum Days to Implement: {max_days}
|
|
||||||
Focus on:
|
|
||||||
- High traffic potential
|
|
||||||
- Low effort
|
|
||||||
- Clear ROI
|
|
||||||
- Quick implementation
|
|
||||||
"""
|
|
||||||
|
|
||||||
def _build_quick_wins_prompt(self, context: str) -> str:
|
|
||||||
"""Build quick wins prompt"""
|
|
||||||
return f"""Identify quick wins from audit:
|
|
||||||
|
|
||||||
{context}
|
|
||||||
|
|
||||||
Return JSON with wins ranked by (impact × effort) score."""
|
|
||||||
|
|
||||||
def _parse_quick_wins_response(self, response: str) -> List[Dict[str, Any]]:
|
|
||||||
"""Parse quick wins response"""
|
|
||||||
try:
|
|
||||||
return json.loads(response).get('quick_wins', [])
|
|
||||||
except:
|
|
||||||
return []
|
|
||||||
|
|
||||||
def _order_quick_wins(self, wins: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
|
||||||
"""Order quick wins by priority"""
|
|
||||||
return sorted(wins, key=lambda x: x.get('priority_score', 0), reverse=True)
|
|
||||||
|
|
||||||
def _build_keyword_context(
|
|
||||||
self,
|
|
||||||
keywords: List[str],
|
|
||||||
content_analysis: Dict[str, Any],
|
|
||||||
target_difficulty: Optional[str]
|
|
||||||
) -> str:
|
|
||||||
"""Build keyword expansion context"""
|
|
||||||
return f"""
|
|
||||||
Current Keywords: {', '.join(keywords)}
|
|
||||||
Content Quality Score: {content_analysis.get('quality_score', 0)}/100
|
|
||||||
Target Difficulty: {target_difficulty or 'Mixed'}
|
|
||||||
"""
|
|
||||||
|
|
||||||
def _build_keyword_expansion_prompt(self, context: str) -> str:
|
|
||||||
"""Build keyword expansion prompt"""
|
|
||||||
return f"""Expand keyword list based on:
|
|
||||||
|
|
||||||
{context}
|
|
||||||
|
|
||||||
Suggest 15-20 related keywords with:
|
|
||||||
- Difficulty estimate
|
|
||||||
- Volume estimate
|
|
||||||
- Relevance to current keywords
|
|
||||||
- Content opportunity
|
|
||||||
|
|
||||||
Return as JSON."""
|
|
||||||
|
|
||||||
def _parse_keyword_response(self, response: str) -> List[Dict[str, Any]]:
|
|
||||||
"""Parse keyword response"""
|
|
||||||
try:
|
|
||||||
return json.loads(response).get('keywords', [])
|
|
||||||
except:
|
|
||||||
return []
|
|
||||||
|
|
||||||
def _categorize_by_difficulty(self, keywords: List[Dict[str, Any]]) -> Dict[str, List[str]]:
|
|
||||||
"""Categorize keywords by difficulty"""
|
|
||||||
return {
|
|
||||||
'easy': [k.get('keyword', '') for k in keywords if k.get('difficulty', 'medium') == 'low'],
|
|
||||||
'medium': [k.get('keyword', '') for k in keywords if k.get('difficulty', 'medium') == 'medium'],
|
|
||||||
'hard': [k.get('keyword', '') for k in keywords if k.get('difficulty', 'medium') == 'high']
|
|
||||||
}
|
|
||||||
|
|
||||||
async def health_check(self) -> Dict[str, Any]:
|
|
||||||
"""Health check for LLM insights service"""
|
|
||||||
return {
|
|
||||||
'status': 'operational',
|
|
||||||
'service': self.service_name,
|
|
||||||
'version': self.version,
|
|
||||||
'llm_integration': 'available',
|
|
||||||
'last_check': datetime.utcnow().isoformat()
|
|
||||||
}
|
|
||||||
@@ -8,7 +8,6 @@ Phase 3: AI Features (Background Replacement, Object Removal, Color Grading)
|
|||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
import math
|
|
||||||
import subprocess
|
import subprocess
|
||||||
import tempfile
|
import tempfile
|
||||||
import uuid
|
import uuid
|
||||||
@@ -414,22 +413,6 @@ class EditService:
|
|||||||
try:
|
try:
|
||||||
logger.info(f"[EditService] Audio normalization: user={user_id}, level={target_level} LUFS")
|
logger.info(f"[EditService] Audio normalization: user={user_id}, level={target_level} LUFS")
|
||||||
|
|
||||||
try:
|
|
||||||
sanitized_target_level = float(target_level)
|
|
||||||
except (TypeError, ValueError):
|
|
||||||
raise HTTPException(status_code=400, detail="Invalid target level")
|
|
||||||
|
|
||||||
if not math.isfinite(sanitized_target_level):
|
|
||||||
raise HTTPException(status_code=400, detail="Invalid target level")
|
|
||||||
|
|
||||||
if sanitized_target_level > 0.0 or sanitized_target_level < -50.0:
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=400,
|
|
||||||
detail="Target level must be between -50 and 0 LUFS"
|
|
||||||
)
|
|
||||||
|
|
||||||
ffmpeg_target_level = f"{sanitized_target_level:.2f}"
|
|
||||||
|
|
||||||
with tempfile.NamedTemporaryFile(suffix=".mp4", delete=False) as input_file:
|
with tempfile.NamedTemporaryFile(suffix=".mp4", delete=False) as input_file:
|
||||||
input_file.write(video_data)
|
input_file.write(video_data)
|
||||||
input_path = input_file.name
|
input_path = input_file.name
|
||||||
@@ -442,7 +425,7 @@ class EditService:
|
|||||||
|
|
||||||
cmd = [
|
cmd = [
|
||||||
"ffmpeg", "-i", input_path,
|
"ffmpeg", "-i", input_path,
|
||||||
"-af", f"loudnorm=I={ffmpeg_target_level}:TP=-1.5:LRA=11",
|
"-af", f"loudnorm=I={target_level}:TP=-1.5:LRA=11",
|
||||||
"-c:v", "copy", "-c:a", "aac", "-y", output_path
|
"-c:v", "copy", "-c:a", "aac", "-y", output_path
|
||||||
]
|
]
|
||||||
result = subprocess.run(cmd, capture_output=True, text=True, timeout=300)
|
result = subprocess.run(cmd, capture_output=True, text=True, timeout=300)
|
||||||
@@ -467,7 +450,7 @@ class EditService:
|
|||||||
video_data=processed_video_bytes,
|
video_data=processed_video_bytes,
|
||||||
filename=filename,
|
filename=filename,
|
||||||
asset_type="video_edit",
|
asset_type="video_edit",
|
||||||
metadata={"edit_type": "normalize", "target_level": sanitized_target_level},
|
metadata={"edit_type": "normalize", "target_level": target_level},
|
||||||
)
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -476,7 +459,7 @@ class EditService:
|
|||||||
"asset_id": asset_result.get("asset_id"),
|
"asset_id": asset_result.get("asset_id"),
|
||||||
"cost": 0.0,
|
"cost": 0.0,
|
||||||
"edit_type": "normalize",
|
"edit_type": "normalize",
|
||||||
"metadata": {"target_level": sanitized_target_level},
|
"metadata": {"target_level": target_level},
|
||||||
}
|
}
|
||||||
finally:
|
finally:
|
||||||
db.close()
|
db.close()
|
||||||
|
|||||||
@@ -1,89 +0,0 @@
|
|||||||
# Content Strategy Implementation Status (Verified)
|
|
||||||
|
|
||||||
_Last verified: May 26, 2026_
|
|
||||||
|
|
||||||
This page reflects a static code review of the **current implementation** and supersedes older roadmap claims in internal notes.
|
|
||||||
|
|
||||||
## What is implemented now
|
|
||||||
|
|
||||||
### Backend service architecture
|
|
||||||
Implemented modular service structure under:
|
|
||||||
- `backend/api/content_planning/services/content_strategy/core/`
|
|
||||||
- `backend/api/content_planning/services/content_strategy/ai_analysis/`
|
|
||||||
- `backend/api/content_planning/services/content_strategy/onboarding/`
|
|
||||||
- `backend/api/content_planning/services/content_strategy/performance/`
|
|
||||||
- `backend/api/content_planning/services/content_strategy/utils/`
|
|
||||||
|
|
||||||
### AI analysis module
|
|
||||||
Implemented:
|
|
||||||
- AI recommendation and analysis services
|
|
||||||
- Prompt engineering support
|
|
||||||
- Quality validation paths
|
|
||||||
- Multiple analysis modes and fallback handling
|
|
||||||
|
|
||||||
Key files:
|
|
||||||
- `ai_analysis/ai_recommendations.py`
|
|
||||||
- `ai_analysis/prompt_engineering.py`
|
|
||||||
- `ai_analysis/quality_validation.py`
|
|
||||||
- `ai_analysis/strategy_analyzer.py`
|
|
||||||
|
|
||||||
### Onboarding integration
|
|
||||||
Implemented (not placeholder-only):
|
|
||||||
- Onboarding data aggregation/integration
|
|
||||||
- Field transformation from onboarding inputs to strategy fields
|
|
||||||
- Data quality assessment scaffolding and scoring paths
|
|
||||||
|
|
||||||
Key files:
|
|
||||||
- `onboarding/data_integration.py`
|
|
||||||
- `onboarding/field_transformation.py`
|
|
||||||
- `onboarding/data_quality.py`
|
|
||||||
|
|
||||||
### Core strategy orchestration
|
|
||||||
Implemented:
|
|
||||||
- Main strategy service orchestration
|
|
||||||
- Constants and field mapping support
|
|
||||||
- API endpoint wiring in content strategy route modules
|
|
||||||
|
|
||||||
Key files:
|
|
||||||
- `core/strategy_service.py`
|
|
||||||
- `core/constants.py`
|
|
||||||
- `core/field_mappings.py`
|
|
||||||
|
|
||||||
## Partially implemented / needs hardening
|
|
||||||
|
|
||||||
### Performance layer
|
|
||||||
Files exist and are wired, but should be treated as **hardening required** for production-grade behavior:
|
|
||||||
- `performance/caching.py`
|
|
||||||
- `performance/optimization.py`
|
|
||||||
- `performance/health_monitoring.py`
|
|
||||||
|
|
||||||
Recommended hardening:
|
|
||||||
- Redis TTL policy verification
|
|
||||||
- cache invalidation consistency
|
|
||||||
- dependency health telemetry and alertability
|
|
||||||
|
|
||||||
### Utility + transformation overlap
|
|
||||||
There is overlap risk between:
|
|
||||||
- `onboarding/field_transformation.py`
|
|
||||||
- `utils/data_processors.py`
|
|
||||||
|
|
||||||
Recommended hardening:
|
|
||||||
- define one canonical transformation path
|
|
||||||
- align confidence/data-quality contract across services
|
|
||||||
|
|
||||||
## Not yet complete (from roadmap perspective)
|
|
||||||
|
|
||||||
- Advanced real-time analytics dashboards
|
|
||||||
- fully matured predictive insights / ML workflows
|
|
||||||
- enterprise collaboration workflows (versioning/approval patterns)
|
|
||||||
|
|
||||||
## Documentation policy
|
|
||||||
|
|
||||||
For public docs-site pages:
|
|
||||||
1. Treat this page as implementation truth for status language.
|
|
||||||
2. Use "implemented", "partial", or "planned" only when mapped to concrete files.
|
|
||||||
3. Avoid stale milestone dates; use explicit verification dates.
|
|
||||||
|
|
||||||
For internal docs in `docs/`:
|
|
||||||
- keep architecture notes and historical plans,
|
|
||||||
- but avoid status claims that conflict with this verified page.
|
|
||||||
@@ -3,14 +3,6 @@
|
|||||||
ALwrity's Content Strategy module is the brain of your content marketing efforts, providing AI-powered strategic planning, persona development, and content calendar generation to help you create a comprehensive, data-driven content marketing strategy.
|
ALwrity's Content Strategy module is the brain of your content marketing efforts, providing AI-powered strategic planning, persona development, and content calendar generation to help you create a comprehensive, data-driven content marketing strategy.
|
||||||
|
|
||||||
## What is Content Strategy?
|
## What is Content Strategy?
|
||||||
## Current implementation status
|
|
||||||
|
|
||||||
For the latest verified implementation state, see:
|
|
||||||
|
|
||||||
- [Content Strategy Implementation Status (Verified)](./implementation-status)
|
|
||||||
|
|
||||||
This overview explains capabilities; the status page is the source of truth for what is currently implemented versus planned.
|
|
||||||
|
|
||||||
|
|
||||||
Content strategy is the planning, development, and management of content to achieve specific business objectives. ALwrity's AI-powered approach transforms complex strategic planning into an automated, intelligent process that delivers measurable results.
|
Content strategy is the planning, development, and management of content to achieve specific business objectives. ALwrity's AI-powered approach transforms complex strategic planning into an automated, intelligent process that delivers measurable results.
|
||||||
|
|
||||||
|
|||||||
@@ -4,48 +4,13 @@ Welcome to ALwrity's complete SEO Dashboard documentation. This index helps you
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## ✨ What's New - Phase 2A (May 2026)
|
|
||||||
|
|
||||||
### 🚀 Enterprise SEO Suite
|
|
||||||
- Multi-tool orchestration combining 5 SEO tools
|
|
||||||
- Complete audits in 15-20 minutes or quick scans in 5 minutes
|
|
||||||
- Unified scoring with component breakdown
|
|
||||||
- Competitive benchmarking and analysis
|
|
||||||
- 3-phase implementation roadmap
|
|
||||||
- **[Learn More →](phase2a-enterprise-seo.md)**
|
|
||||||
|
|
||||||
### 📊 Advanced GSC Analysis
|
|
||||||
- 8 concurrent analysis dimensions
|
|
||||||
- 30+ performance metrics
|
|
||||||
- 15+ scored content opportunities
|
|
||||||
- Search intelligence and trend detection
|
|
||||||
- Competitive positioning assessment
|
|
||||||
- **[Learn More →](phase2a-advanced-gsc.md)**
|
|
||||||
|
|
||||||
### 🤖 LLM Insights Generation
|
|
||||||
- 8 types of AI-powered insights
|
|
||||||
- Content strategy generation
|
|
||||||
- Traffic improvement roadmaps
|
|
||||||
- Competitive intelligence
|
|
||||||
- Quick wins identification
|
|
||||||
- Keyword expansion
|
|
||||||
- **[Learn More →](phase2a-llm-insights.md)**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📚 Find What You Need
|
## 📚 Find What You Need
|
||||||
|
|
||||||
### 🆕 Just Getting Started?
|
### 🆕 Just Getting Started?
|
||||||
Start here to get up and running quickly:
|
Start here to get up and running quickly:
|
||||||
- **[Quick Start Guide](quick-start.md)** - Get optimizing in 10 minutes
|
- **[Quick Start Guide](quick-start.md)** - Get optimizing in 10 minutes
|
||||||
- **[Overview](overview.md)** - Understand the dashboard and Phase 2A features
|
- **[Overview](overview.md)** - Understand the dashboard
|
||||||
- **[Tools Reference](tools-reference.md)** - See all tools at a glance
|
- **[Tools Reference](tools-reference.md)** - See all 21 tools at a glance
|
||||||
|
|
||||||
### 🚀 Want to Use Phase 2A Features?
|
|
||||||
Learn the new enterprise-grade tools:
|
|
||||||
- **[Enterprise SEO Suite](phase2a-enterprise-seo.md)** - Multi-tool audits with AI insights
|
|
||||||
- **[Advanced GSC Analysis](phase2a-advanced-gsc.md)** - Deep search intelligence
|
|
||||||
- **[LLM Insights Generation](phase2a-llm-insights.md)** - 8 types of AI-powered recommendations
|
|
||||||
|
|
||||||
### 🛠️ Want to Learn Individual Tools?
|
### 🛠️ Want to Learn Individual Tools?
|
||||||
Each tool has a detailed guide:
|
Each tool has a detailed guide:
|
||||||
@@ -57,7 +22,7 @@ Each tool has a detailed guide:
|
|||||||
- OpenGraph Generator
|
- OpenGraph Generator
|
||||||
- On-Page SEO Analyzer
|
- On-Page SEO Analyzer
|
||||||
- Technical SEO Analyzer
|
- Technical SEO Analyzer
|
||||||
- Enterprise SEO Suite (NEW)
|
- Enterprise SEO Suite
|
||||||
- Content Strategy Analyzer
|
- Content Strategy Analyzer
|
||||||
|
|
||||||
### 📋 Ready to Create Workflows?
|
### 📋 Ready to Create Workflows?
|
||||||
@@ -67,7 +32,6 @@ Learn proven workflows and processes:
|
|||||||
- Website Audit & Improvement
|
- Website Audit & Improvement
|
||||||
- Performance Optimization
|
- Performance Optimization
|
||||||
- Monthly SEO Maintenance
|
- Monthly SEO Maintenance
|
||||||
- Enterprise Audit with Phase 2A (NEW)
|
|
||||||
- Industry-Specific Workflows
|
- Industry-Specific Workflows
|
||||||
- Quick Wins Strategy
|
- Quick Wins Strategy
|
||||||
- Collaborative Team Workflows
|
- Collaborative Team Workflows
|
||||||
@@ -132,41 +96,7 @@ Deep technical reference:
|
|||||||
2. [Meta Description Generator](individual-tools-guide.md#1--meta-description-generator) - 5 min
|
2. [Meta Description Generator](individual-tools-guide.md#1--meta-description-generator) - 5 min
|
||||||
3. [On-Page SEO Analyzer](individual-tools-guide.md#6--on-page-seo-analyzer) - 10 min
|
3. [On-Page SEO Analyzer](individual-tools-guide.md#6--on-page-seo-analyzer) - 10 min
|
||||||
4. [Content Strategy Analyzer](individual-tools-guide.md#9--content-strategy-analyzer) - 10 min
|
4. [Content Strategy Analyzer](individual-tools-guide.md#9--content-strategy-analyzer) - 10 min
|
||||||
5. [LLM Insights Generation](phase2a-llm-insights.md) - Get AI content strategy - 10 min
|
5. [Content Creation Workflow](workflows-guide.md#workflow-1-content-creation-pipeline) - 5 min
|
||||||
6. [Content Creation Workflow](workflows-guide.md#workflow-1-content-creation-pipeline) - 5 min
|
|
||||||
|
|
||||||
### For Digital Marketers
|
|
||||||
**Goal**: Drive organic traffic and measure ROI
|
|
||||||
|
|
||||||
**Recommended Reading Order**:
|
|
||||||
1. [Overview](overview.md) - Understand all capabilities - 5 min
|
|
||||||
2. [Enterprise SEO Suite](phase2a-enterprise-seo.md) - Run complete audits - 15 min
|
|
||||||
3. [Advanced GSC Analysis](phase2a-advanced-gsc.md) - Find opportunities - 15 min
|
|
||||||
4. [LLM Insights](phase2a-llm-insights.md) - Get strategic recommendations - 10 min
|
|
||||||
5. [Competitive Analysis Guide](competitive-analysis.md) - Benchmark competitors - 10 min
|
|
||||||
6. [Content Strategy Guide](content-strategy-guide.md) - Plan content - 10 min
|
|
||||||
|
|
||||||
### For SEO Professionals
|
|
||||||
**Goal**: Comprehensive site optimization and client reports
|
|
||||||
|
|
||||||
**Recommended Reading Order**:
|
|
||||||
1. [Enterprise SEO Suite](phase2a-enterprise-seo.md) - Complete audit tool - 20 min
|
|
||||||
2. [Advanced GSC Analysis](phase2a-advanced-gsc.md) - Deep search analysis - 20 min
|
|
||||||
3. [LLM Insights Generation](phase2a-llm-insights.md) - 8 insight types - 15 min
|
|
||||||
4. [All Individual Tools Guide](individual-tools-guide.md) - Master all tools - 30 min
|
|
||||||
5. [Workflows Guide](workflows-guide.md) - Create client workflows - 20 min
|
|
||||||
6. [Design Document](design-document.md) - Technical architecture - 15 min
|
|
||||||
|
|
||||||
### For Enterprises
|
|
||||||
**Goal**: Enterprise-grade SEO program with team management
|
|
||||||
|
|
||||||
**Recommended Reading Order**:
|
|
||||||
1. [Enterprise SEO Suite](phase2a-enterprise-seo.md) - Multi-tool orchestration - 20 min
|
|
||||||
2. [Advanced GSC Analysis](phase2a-advanced-gsc.md) - Comprehensive insights - 20 min
|
|
||||||
3. [LLM Insights Generation](phase2a-llm-insights.md) - Strategic recommendations - 15 min
|
|
||||||
4. [Competitive Analysis Guide](competitive-analysis.md) - Market positioning - 15 min
|
|
||||||
5. [Workflows Guide](workflows-guide.md) - Enterprise workflows - 25 min
|
|
||||||
6. [Design Document](design-document.md) - Technical requirements - 15 min
|
|
||||||
|
|
||||||
**Total Learning Time**: 40 minutes
|
**Total Learning Time**: 40 minutes
|
||||||
**First Task**: Create one optimized article
|
**First Task**: Create one optimized article
|
||||||
@@ -413,37 +343,3 @@ After using these guides, you'll be able to:
|
|||||||
**Let's start optimizing! 🚀**
|
**Let's start optimizing! 🚀**
|
||||||
|
|
||||||
Pick your starting point above and begin your SEO journey.
|
Pick your starting point above and begin your SEO journey.
|
||||||
|
|
||||||
|
|
||||||
## 📌 Latest Implementation Notes (May 2026)
|
|
||||||
|
|
||||||
This section was reviewed against the active codebase and endpoint surface.
|
|
||||||
|
|
||||||
- Dashboard routes: `/seo` and `/seo-dashboard`
|
|
||||||
- Core UI: `SEODashboard.tsx` with tabbed analysis workflows
|
|
||||||
- Extended support for strategic insights history/run, deep competitor analysis, and onboarding task health
|
|
||||||
- Operational diagnostics include semantic health, SIF health, and cache stats
|
|
||||||
|
|
||||||
|
|
||||||
## 🆕 Recent SEO Enhancements (Reviewed: May 25, 2026)
|
|
||||||
|
|
||||||
The SEO stack has expanded beyond core URL analysis. Based on the current frontend/backend code and latest SEO docs, these additions should be considered part of the current docs-site surface:
|
|
||||||
|
|
||||||
### Dashboard + API enhancements
|
|
||||||
- Strategic insights execution and history (`/api/seo-dashboard/strategic-insights/run`, `/api/seo-dashboard/strategic-insights/history`)
|
|
||||||
- Deep competitor analysis endpoint (`/api/seo-dashboard/deep-competitor-analysis`)
|
|
||||||
- Onboarding task health visibility (`/api/seo-dashboard/onboarding-task-health`)
|
|
||||||
- Operational diagnostics (`/api/seo-dashboard/semantic-health`, `/api/seo-dashboard/sif-health`, `/api/seo-dashboard/cache-stats`)
|
|
||||||
- Route aliases active in app routing: `/seo` and `/seo-dashboard`
|
|
||||||
|
|
||||||
### Workflow enhancements connected to SEO
|
|
||||||
- Blog Writer now includes GSC-powered brainstorming flows (`frontend/src/components/BlogWriter/GSCBrainstormModal.tsx`, `backend/services/gsc_brainstorm_service.py`)
|
|
||||||
- SEO guidance is available both in dashboard workflows and in-editor Blog Writer SEO modules (e.g., `SEOMiniPanel`, `SEOAnalysisModal`, `SEOMetadataModal`)
|
|
||||||
|
|
||||||
### Source docs reviewed for enhancement tracking
|
|
||||||
- `docs/SEO/COMPLETE_SEO_TOOLS_INVENTORY.md`
|
|
||||||
- `docs/SEO/API_REFERENCE.md`
|
|
||||||
- `docs/SEO/PHASE2A_IMPLEMENTATION.md`
|
|
||||||
- `docs/SEO/MIGRATION_STATUS_ANALYSIS.md`
|
|
||||||
- `docs/SEO/MIGRATION_DETAILED_GAPS.md`
|
|
||||||
- `docs/SEO/MIGRATION_EXECUTIVE_SUMMARY.md`
|
|
||||||
|
|||||||
@@ -1,21 +1,17 @@
|
|||||||
# SEO Dashboard Overview
|
# SEO Dashboard Overview
|
||||||
|
|
||||||
The ALwrity SEO Dashboard is a comprehensive, AI-powered platform providing **21+ production-ready SEO tools** for content creators, digital marketers, and SEO professionals. Designed for users of all technical levels, it combines advanced AI analysis with real-time platform integrations for actionable SEO insights.
|
The ALwrity SEO Dashboard is a comprehensive, AI-powered platform providing **21 production-ready SEO tools** for content creators, digital marketers, and SEO professionals. Designed for users of all technical levels, it combines advanced AI analysis with real-time platform integrations for actionable SEO insights.
|
||||||
|
|
||||||
**✨ Latest (Phase 2A)**: Enterprise SEO Suite orchestration, Advanced GSC analysis with 30+ metrics, and 8 AI-powered LLM insight generators.
|
|
||||||
|
|
||||||
## 🎯 What You Can Do
|
## 🎯 What You Can Do
|
||||||
|
|
||||||
The SEO Dashboard enables you to:
|
The SEO Dashboard enables you to:
|
||||||
- **Comprehensive Site Audits** - Run complete 5-tool audits in 15-20 minutes (NEW in Phase 2A)
|
|
||||||
- **Analyze Individual Pages** - Get detailed on-page SEO analysis
|
- **Analyze Individual Pages** - Get detailed on-page SEO analysis
|
||||||
- **Generate Metadata** - Create optimized titles and descriptions
|
- **Generate Metadata** - Create optimized titles and descriptions
|
||||||
- **Audit Technical SEO** - Identify and fix technical issues
|
- **Audit Technical SEO** - Identify and fix technical issues
|
||||||
- **Monitor Performance** - Track real search rankings and traffic
|
- **Monitor Performance** - Track real search rankings and traffic
|
||||||
- **Analyze Competitors** - Identify market opportunities with competitive benchmarking
|
- **Analyze Competitors** - Identify market opportunities
|
||||||
- **Plan Content Strategy** - Find content gaps and opportunities with AI-generated strategies
|
- **Plan Content Strategy** - Find content gaps and opportunities
|
||||||
- **Optimize Images** - Generate SEO-friendly alt text
|
- **Optimize Images** - Generate SEO-friendly alt text
|
||||||
- **AI-Powered Insights** - Get 8 types of AI-generated recommendations
|
|
||||||
- **Track Progress** - Monitor improvements over time
|
- **Track Progress** - Monitor improvements over time
|
||||||
|
|
||||||
## 🔑 Key Features
|
## 🔑 Key Features
|
||||||
@@ -28,39 +24,11 @@ The SEO Dashboard enables you to:
|
|||||||
- OpenGraph Generator
|
- OpenGraph Generator
|
||||||
- On-Page SEO Analyzer
|
- On-Page SEO Analyzer
|
||||||
- Technical SEO Analyzer
|
- Technical SEO Analyzer
|
||||||
- Enterprise SEO Suite (NEW - Phase 2A)
|
- Enterprise SEO Suite
|
||||||
- Content Strategy Analyzer
|
- Content Strategy Analyzer
|
||||||
|
|
||||||
### 🚀 **Phase 2A Enterprise Features** (NEW)
|
|
||||||
|
|
||||||
#### 1. **Enterprise SEO Suite** - Multi-tool Orchestration
|
|
||||||
- Automatically runs 5 SEO tools in parallel
|
|
||||||
- Unified scoring (0-100)
|
|
||||||
- Complete vs Quick audit modes (15-20 min vs 5 min)
|
|
||||||
- Competitive analysis & benchmarking
|
|
||||||
- 3-phase implementation roadmap
|
|
||||||
- Business impact projections
|
|
||||||
|
|
||||||
#### 2. **Advanced GSC Analysis** - Search Intelligence
|
|
||||||
- 8 concurrent analysis dimensions
|
|
||||||
- 30+ performance metrics
|
|
||||||
- 15+ scored content opportunities
|
|
||||||
- 3-phase implementation timeline
|
|
||||||
- Competitive positioning assessment
|
|
||||||
- Trend detection & forecasting
|
|
||||||
- Click gain projections
|
|
||||||
|
|
||||||
#### 3. **LLM Insights Generation** - AI Recommendations
|
|
||||||
- 8 insight types from raw data
|
|
||||||
- Audit insights with traffic projections
|
|
||||||
- Content strategy generation
|
|
||||||
- Traffic improvement roadmaps
|
|
||||||
- Competitive intelligence analysis
|
|
||||||
- Quick wins identification (7-day implementations)
|
|
||||||
- Keyword expansion (15-20 new keywords)
|
|
||||||
|
|
||||||
### 📈 **Real-Time Integrations**
|
### 📈 **Real-Time Integrations**
|
||||||
- **Google Search Console** - Real search performance data + Advanced Analysis
|
- **Google Search Console** - Real search performance data
|
||||||
- **Google Analytics 4** - Traffic and behavior analytics
|
- **Google Analytics 4** - Traffic and behavior analytics
|
||||||
- **Bing Webmaster Tools** - Bing-specific insights
|
- **Bing Webmaster Tools** - Bing-specific insights
|
||||||
- **PageSpeed Insights** - Performance analysis
|
- **PageSpeed Insights** - Performance analysis
|
||||||
@@ -70,20 +38,16 @@ The SEO Dashboard enables you to:
|
|||||||
- **Health Score** (0-100) - Overall SEO assessment
|
- **Health Score** (0-100) - Overall SEO assessment
|
||||||
- **AI Copilot** - Conversational recommendations
|
- **AI Copilot** - Conversational recommendations
|
||||||
- **Smart Recommendations** - Priority-based suggestions
|
- **Smart Recommendations** - Priority-based suggestions
|
||||||
- **LLM Insights** (NEW) - AI-generated strategies for 8 insight types
|
|
||||||
- **Competitive Analysis** - Market positioning insights
|
- **Competitive Analysis** - Market positioning insights
|
||||||
- **Strategic Insights** - Weekly strategy briefs
|
- **Strategic Insights** - Weekly strategy briefs
|
||||||
- **Traffic Projections** - Estimated improvement potential
|
|
||||||
|
|
||||||
### 🎨 **Comprehensive Analysis**
|
### 🎨 **Comprehensive Analysis**
|
||||||
- **Technical SEO**: Site structure, crawlability, indexability
|
- **Technical SEO**: Site structure, crawlability, indexability
|
||||||
- **On-Page SEO**: Meta tags, content quality, optimization
|
- **On-Page SEO**: Meta tags, content quality, optimization
|
||||||
- **Content Analysis**: Quality, relevance, strategy alignment
|
- **Content Analysis**: Quality, relevance, strategy alignment
|
||||||
- **Performance**: Core Web Vitals, page speed, load times
|
- **Performance**: Core Web Vitals, page speed, load times
|
||||||
- **Competitive Positioning**: Benchmarking, market gaps, visibility scoring
|
- **Competitive Positioning**: Benchmarking, market gaps
|
||||||
- **Accessibility**: WCAG compliance, user experience
|
- **Accessibility**: WCAG compliance, user experience
|
||||||
- **GSC Intelligence** (NEW): Search data analysis, opportunity scoring, trend detection
|
|
||||||
- **AI Strategy** (NEW): LLM-generated content plans, roadmaps, and recommendations
|
|
||||||
|
|
||||||
## Dashboard Components
|
## Dashboard Components
|
||||||
|
|
||||||
@@ -93,8 +57,6 @@ The dashboard displays key metrics at a glance:
|
|||||||
- **Average Ranking**: Average keyword position in search results
|
- **Average Ranking**: Average keyword position in search results
|
||||||
- **Mobile Speed**: Mobile performance score and Core Web Vitals
|
- **Mobile Speed**: Mobile performance score and Core Web Vitals
|
||||||
- **Keywords Tracked**: Number of keywords you're monitoring
|
- **Keywords Tracked**: Number of keywords you're monitoring
|
||||||
- **Enterprise Score** (NEW): Overall audit score from Phase 2A suite
|
|
||||||
- **GSC Insights** (NEW): Key opportunities and trends from search data
|
|
||||||
|
|
||||||
### 2. SEO Analysis Tools
|
### 2. SEO Analysis Tools
|
||||||
Choose from **9 specialized tools** for specific analysis needs:
|
Choose from **9 specialized tools** for specific analysis needs:
|
||||||
@@ -318,39 +280,3 @@ For detailed information about each tool, see [Tools Reference](tools-reference.
|
|||||||
---
|
---
|
||||||
|
|
||||||
*Ready to optimize your SEO? Start with [Tools Reference](tools-reference.md) to explore all 21 tools, or check out our [GSC Integration Guide](gsc-integration.md) to connect your search data!*
|
*Ready to optimize your SEO? Start with [Tools Reference](tools-reference.md) to explore all 21 tools, or check out our [GSC Integration Guide](gsc-integration.md) to connect your search data!*
|
||||||
|
|
||||||
|
|
||||||
## Implementation Update (May 2026)
|
|
||||||
|
|
||||||
The dashboard documentation has been verified against the active implementation, including:
|
|
||||||
|
|
||||||
- Frontend route integration in `frontend/src/App.tsx` (`/seo`, `/seo-dashboard`)
|
|
||||||
- Main dashboard orchestration in `frontend/src/components/SEODashboard/SEODashboard.tsx`
|
|
||||||
- API coverage in `backend/api/seo_dashboard.py` for overview, analysis, integrations, competitive intelligence, and health diagnostics
|
|
||||||
- Service orchestration via `backend/services/seo/dashboard_service.py`
|
|
||||||
|
|
||||||
This keeps the existing detailed user documentation while ensuring endpoint and architecture references remain current.
|
|
||||||
|
|
||||||
|
|
||||||
## 🆕 Recent SEO Enhancements (Reviewed: May 25, 2026)
|
|
||||||
|
|
||||||
The SEO stack has expanded beyond core URL analysis. Based on the current frontend/backend code and latest SEO docs, these additions should be considered part of the current docs-site surface:
|
|
||||||
|
|
||||||
### Dashboard + API enhancements
|
|
||||||
- Strategic insights execution and history (`/api/seo-dashboard/strategic-insights/run`, `/api/seo-dashboard/strategic-insights/history`)
|
|
||||||
- Deep competitor analysis endpoint (`/api/seo-dashboard/deep-competitor-analysis`)
|
|
||||||
- Onboarding task health visibility (`/api/seo-dashboard/onboarding-task-health`)
|
|
||||||
- Operational diagnostics (`/api/seo-dashboard/semantic-health`, `/api/seo-dashboard/sif-health`, `/api/seo-dashboard/cache-stats`)
|
|
||||||
- Route aliases active in app routing: `/seo` and `/seo-dashboard`
|
|
||||||
|
|
||||||
### Workflow enhancements connected to SEO
|
|
||||||
- Blog Writer now includes GSC-powered brainstorming flows (`frontend/src/components/BlogWriter/GSCBrainstormModal.tsx`, `backend/services/gsc_brainstorm_service.py`)
|
|
||||||
- SEO guidance is available both in dashboard workflows and in-editor Blog Writer SEO modules (e.g., `SEOMiniPanel`, `SEOAnalysisModal`, `SEOMetadataModal`)
|
|
||||||
|
|
||||||
### Source docs reviewed for enhancement tracking
|
|
||||||
- `docs/SEO/COMPLETE_SEO_TOOLS_INVENTORY.md`
|
|
||||||
- `docs/SEO/API_REFERENCE.md`
|
|
||||||
- `docs/SEO/PHASE2A_IMPLEMENTATION.md`
|
|
||||||
- `docs/SEO/MIGRATION_STATUS_ANALYSIS.md`
|
|
||||||
- `docs/SEO/MIGRATION_DETAILED_GAPS.md`
|
|
||||||
- `docs/SEO/MIGRATION_EXECUTIVE_SUMMARY.md`
|
|
||||||
|
|||||||
@@ -1,421 +0,0 @@
|
|||||||
# Advanced GSC Analysis - Phase 2A
|
|
||||||
|
|
||||||
Advanced GSC Analysis provides deep, AI-powered analysis of your Google Search Console data, identifying content opportunities, competitive positioning, and search intelligence with actionable recommendations.
|
|
||||||
|
|
||||||
**Status**: ✅ Production Ready (May 26, 2026)
|
|
||||||
**API Endpoints**:
|
|
||||||
- `POST /api/seo/gsc/analyze-search-performance` - 8-dimensional analysis
|
|
||||||
- `POST /api/seo/gsc/content-opportunities` - Detailed opportunity report
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🎯 What is Advanced GSC Analysis?
|
|
||||||
|
|
||||||
Advanced GSC Analysis goes beyond basic GSC dashboards by:
|
|
||||||
|
|
||||||
- **8 concurrent analyses** - Multi-dimensional data review
|
|
||||||
- **30+ metrics** - Comprehensive performance tracking
|
|
||||||
- **15+ content opportunities** - Scored and ranked
|
|
||||||
- **Trend detection** - Historical pattern analysis
|
|
||||||
- **Competitive positioning** - Market placement assessment
|
|
||||||
- **AI recommendations** - Strategic guidance
|
|
||||||
- **3-phase roadmap** - Implementation timeline
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📊 Analysis Dimensions
|
|
||||||
|
|
||||||
### 1. Performance Overview
|
|
||||||
**Core Metrics**:
|
|
||||||
- Total clicks and impressions
|
|
||||||
- Click-through rate (CTR)
|
|
||||||
- Average position
|
|
||||||
- Mobile vs Desktop breakdown
|
|
||||||
- Date range analysis
|
|
||||||
|
|
||||||
**AI Insights**:
|
|
||||||
- Performance trends
|
|
||||||
- Seasonal patterns
|
|
||||||
- Growth opportunities
|
|
||||||
- Traffic potential
|
|
||||||
|
|
||||||
### 2. Keyword Performance
|
|
||||||
**Analysis**:
|
|
||||||
- Top 25 keywords by clicks
|
|
||||||
- Trending keywords (newly ranking)
|
|
||||||
- High-volume, low-CTR queries (optimization targets)
|
|
||||||
- Keywords ranking positions 4-10 (ranking improvement targets)
|
|
||||||
- Long-tail keyword opportunities
|
|
||||||
|
|
||||||
**Metrics per Keyword**:
|
|
||||||
- Clicks and impressions
|
|
||||||
- CTR and position
|
|
||||||
- Traffic potential
|
|
||||||
- Optimization difficulty
|
|
||||||
|
|
||||||
### 3. Page Performance
|
|
||||||
**Analysis**:
|
|
||||||
- Top 25 pages by organic traffic
|
|
||||||
- Pages with zero clicks (hidden potential)
|
|
||||||
- Pages with declining performance
|
|
||||||
- Mobile vs Desktop performance
|
|
||||||
- Content quality scoring
|
|
||||||
|
|
||||||
**Recommendations**:
|
|
||||||
- Content update strategies
|
|
||||||
- Internal linking suggestions
|
|
||||||
- Keyword targeting improvements
|
|
||||||
|
|
||||||
### 4. Content Opportunities (15+ Scored)
|
|
||||||
**High-Volume, Low-CTR** (Critical Priority)
|
|
||||||
- Queries with 100+ impressions but <5% CTR
|
|
||||||
- Root cause: Poor title/meta description
|
|
||||||
- Action: Meta tag optimization
|
|
||||||
- Potential gain: 20-40% CTR improvement
|
|
||||||
|
|
||||||
**Ranking Improvement Targets** (High Priority)
|
|
||||||
- Keywords in positions 4-10
|
|
||||||
- High search volume potential
|
|
||||||
- Root cause: Content depth or link authority
|
|
||||||
- Action: Content enhancement + link building
|
|
||||||
- Potential gain: Page 1 ranking
|
|
||||||
|
|
||||||
**Long-Tail Expansion** (Medium Priority)
|
|
||||||
- Emerging, lower-volume keywords
|
|
||||||
- Lower competition
|
|
||||||
- Root cause: Topic not fully covered
|
|
||||||
- Action: Topic expansion content
|
|
||||||
- Potential gain: Long-tail traffic growth
|
|
||||||
|
|
||||||
### 5. Technical SEO Signals
|
|
||||||
**Monitoring**:
|
|
||||||
- Crawl stats (crawl budget usage)
|
|
||||||
- Coverage status (indexed vs excluded)
|
|
||||||
- Mobile usability issues
|
|
||||||
- Core Web Vitals
|
|
||||||
- AMP errors (if applicable)
|
|
||||||
- Rich result issues
|
|
||||||
|
|
||||||
### 6. Competitive Positioning
|
|
||||||
**Analysis**:
|
|
||||||
- Your market visibility score
|
|
||||||
- Competitor visibility comparison
|
|
||||||
- Market share estimation
|
|
||||||
- Search intent distribution
|
|
||||||
- SERP feature analysis
|
|
||||||
|
|
||||||
**Positioning Categories**:
|
|
||||||
- Leader: 30%+ above average competitors
|
|
||||||
- Strong: 10-30% above average
|
|
||||||
- Average: Within 10% of competitors
|
|
||||||
- Behind: 10%+ below average competitors
|
|
||||||
|
|
||||||
### 7. Trend Analysis
|
|
||||||
**Time Series Data**:
|
|
||||||
- 30/60/90-day trends
|
|
||||||
- Monthly/quarterly comparisons
|
|
||||||
- Seasonality patterns
|
|
||||||
- Growth velocity
|
|
||||||
- Forecast predictions
|
|
||||||
|
|
||||||
**Trend Types**:
|
|
||||||
- Uptrend: Growing clicks/impressions
|
|
||||||
- Downtrend: Declining performance
|
|
||||||
- Stable: Consistent performance
|
|
||||||
- Volatile: Fluctuating performance
|
|
||||||
|
|
||||||
### 8. AI Insights
|
|
||||||
**Strategic Recommendations**:
|
|
||||||
- Quick wins (implementable in 7 days)
|
|
||||||
- High-impact improvements (2-4 weeks)
|
|
||||||
- Long-term strategies (1-3 months)
|
|
||||||
- Risk assessments
|
|
||||||
- Effort estimations
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🚀 Using Advanced GSC Analysis
|
|
||||||
|
|
||||||
### Search Performance Analysis
|
|
||||||
|
|
||||||
Comprehensive analysis of all 8 dimensions:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
curl -X POST https://api.alwrity.com/api/seo/gsc/analyze-search-performance \
|
|
||||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d '{
|
|
||||||
"site_url": "https://example.com",
|
|
||||||
"date_range_days": 90,
|
|
||||||
"include_opportunities": true,
|
|
||||||
"include_competitive": true
|
|
||||||
}'
|
|
||||||
```
|
|
||||||
|
|
||||||
**Response Includes**:
|
|
||||||
- Performance overview (4 key metrics)
|
|
||||||
- Keyword analysis (top 25 + trending)
|
|
||||||
- Page analysis (top pages + issues)
|
|
||||||
- 15+ content opportunities (scored)
|
|
||||||
- Technical signals (crawl, coverage, mobile)
|
|
||||||
- Competitive positioning
|
|
||||||
- Trend analysis with predictions
|
|
||||||
- AI-powered recommendations
|
|
||||||
|
|
||||||
### Content Opportunities Report
|
|
||||||
|
|
||||||
Detailed report focused on content gap opportunities:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
curl -X POST https://api.alwrity.com/api/seo/gsc/content-opportunities \
|
|
||||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d '{
|
|
||||||
"site_url": "https://example.com",
|
|
||||||
"min_impressions": 100,
|
|
||||||
"date_range_days": 90
|
|
||||||
}'
|
|
||||||
```
|
|
||||||
|
|
||||||
**Response Includes**:
|
|
||||||
- 15+ opportunities ranked by score
|
|
||||||
- 3-phase implementation roadmap
|
|
||||||
- Estimated traffic gains per phase
|
|
||||||
- Content creation templates
|
|
||||||
- Keyword targeting suggestions
|
|
||||||
- Internal linking strategies
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📋 Response Format
|
|
||||||
|
|
||||||
### Search Performance Analysis
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": true,
|
|
||||||
"message": "GSC search performance analysis completed",
|
|
||||||
"execution_time": 180.5,
|
|
||||||
"data": {
|
|
||||||
"performance_overview": {
|
|
||||||
"total_clicks": 15420,
|
|
||||||
"total_impressions": 142350,
|
|
||||||
"avg_ctr": 0.108,
|
|
||||||
"avg_position": 12.3,
|
|
||||||
"date_range": "90 days",
|
|
||||||
"comparison": {
|
|
||||||
"clicks_change": "+15%",
|
|
||||||
"impressions_change": "+8%",
|
|
||||||
"ctr_change": "+5%"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"keyword_performance": {
|
|
||||||
"top_keywords": [
|
|
||||||
{
|
|
||||||
"keyword": "SEO tips",
|
|
||||||
"clicks": 450,
|
|
||||||
"impressions": 12500,
|
|
||||||
"ctr": 0.036,
|
|
||||||
"position": 8.2,
|
|
||||||
"trend": "uptrend"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"trending_keywords": [...],
|
|
||||||
"optimization_targets": [...],
|
|
||||||
"ranking_improvement_targets": [...]
|
|
||||||
},
|
|
||||||
"page_performance": {
|
|
||||||
"top_pages": [...],
|
|
||||||
"zero_click_pages": [...],
|
|
||||||
"declining_pages": [...]
|
|
||||||
},
|
|
||||||
"content_opportunities": [
|
|
||||||
{
|
|
||||||
"id": "opp_001",
|
|
||||||
"rank": 1,
|
|
||||||
"type": "high_volume_low_ctr",
|
|
||||||
"priority": "critical",
|
|
||||||
"keywords": ["seo tips", "seo best practices"],
|
|
||||||
"current_impressions": 25000,
|
|
||||||
"current_ctr": 0.02,
|
|
||||||
"target_ctr": 0.06,
|
|
||||||
"estimated_click_gain": 1000,
|
|
||||||
"effort": "Low",
|
|
||||||
"action": "Meta tag optimization",
|
|
||||||
"timeline": "7 days"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"technical_signals": {
|
|
||||||
"crawl_stats": {...},
|
|
||||||
"coverage": {...},
|
|
||||||
"mobile_usability": {...},
|
|
||||||
"core_web_vitals": {...}
|
|
||||||
},
|
|
||||||
"competitive_positioning": {
|
|
||||||
"your_visibility_score": 78,
|
|
||||||
"market_average": 65,
|
|
||||||
"leader_score": 92,
|
|
||||||
"position": "Leader",
|
|
||||||
"gap_to_leader": 14
|
|
||||||
},
|
|
||||||
"trend_analysis": {
|
|
||||||
"30_day_trend": "uptrend",
|
|
||||||
"growth_rate": "+12% month-over-month",
|
|
||||||
"forecast_next_30_days": "+18% clicks"
|
|
||||||
},
|
|
||||||
"ai_insights": {
|
|
||||||
"quick_wins": [...],
|
|
||||||
"high_impact_recommendations": [...],
|
|
||||||
"strategic_recommendations": [...]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🎓 Use Cases
|
|
||||||
|
|
||||||
### Use Case 1: Identify Quick Wins
|
|
||||||
|
|
||||||
Find low-effort, high-impact optimization opportunities:
|
|
||||||
|
|
||||||
```python
|
|
||||||
import asyncio
|
|
||||||
from services.seo_tools.gsc_analyzer_service import GSCAnalyzerService
|
|
||||||
|
|
||||||
async def find_quick_wins():
|
|
||||||
service = GSCAnalyzerService()
|
|
||||||
|
|
||||||
analysis = await service.analyze_search_performance(
|
|
||||||
site_url="https://mysite.com",
|
|
||||||
date_range_days=90
|
|
||||||
)
|
|
||||||
|
|
||||||
opportunities = analysis['content_opportunities']
|
|
||||||
quick_wins = [o for o in opportunities if o['effort'] == 'Low' and o['priority'] == 'critical']
|
|
||||||
|
|
||||||
print(f"Found {len(quick_wins)} quick wins!")
|
|
||||||
for opp in quick_wins:
|
|
||||||
print(f"- {opp['action']}: +{opp['estimated_click_gain']} clicks potential")
|
|
||||||
```
|
|
||||||
|
|
||||||
### Use Case 2: Competitive Benchmarking
|
|
||||||
|
|
||||||
Understand your market position:
|
|
||||||
|
|
||||||
```python
|
|
||||||
analysis = await service.analyze_search_performance(
|
|
||||||
site_url="https://mysite.com",
|
|
||||||
date_range_days=90
|
|
||||||
)
|
|
||||||
|
|
||||||
competitive = analysis['competitive_positioning']
|
|
||||||
print(f"Market Position: {competitive['position']}")
|
|
||||||
print(f"Your Score: {competitive['your_visibility_score']}")
|
|
||||||
print(f"Market Average: {competitive['market_average']}")
|
|
||||||
print(f"Gap to Leader: {competitive['gap_to_leader']} points")
|
|
||||||
```
|
|
||||||
|
|
||||||
### Use Case 3: Content Planning
|
|
||||||
|
|
||||||
Plan new content based on data gaps:
|
|
||||||
|
|
||||||
```python
|
|
||||||
report = await service.get_content_opportunities_report(
|
|
||||||
site_url="https://mysite.com",
|
|
||||||
min_impressions=100,
|
|
||||||
date_range_days=90
|
|
||||||
)
|
|
||||||
|
|
||||||
opportunities = report['opportunities']
|
|
||||||
print(f"\n3-Phase Implementation Plan:")
|
|
||||||
print(f"Phase 1 (Weeks 1-2): +{report['phase_1']['estimated_traffic_gain']} clicks")
|
|
||||||
print(f"Phase 2 (Weeks 3-4): +{report['phase_2']['estimated_traffic_gain']} clicks")
|
|
||||||
print(f"Phase 3 (Month 2+): +{report['phase_3']['estimated_traffic_gain']} clicks")
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔧 Advanced Features
|
|
||||||
|
|
||||||
### Opportunity Scoring
|
|
||||||
|
|
||||||
Opportunities are scored on multiple factors:
|
|
||||||
|
|
||||||
```
|
|
||||||
Opportunity Score = (Traffic Impact × 0.4) + (Implementation Ease × 0.3) + (Feasibility × 0.3)
|
|
||||||
|
|
||||||
Example:
|
|
||||||
- Traffic Impact (0-100): 85 × 0.4 = 34
|
|
||||||
- Implementation Ease (0-100): 90 × 0.3 = 27
|
|
||||||
- Feasibility (0-100): 80 × 0.3 = 24
|
|
||||||
─────────────────────────────────────────
|
|
||||||
Opportunity Score: 85 (Very High)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Phase-Based Planning
|
|
||||||
|
|
||||||
3-phase implementation timeline with:
|
|
||||||
|
|
||||||
**Phase 1 (Weeks 1-2)**: Quick wins
|
|
||||||
- Effort: Low
|
|
||||||
- Impact: Immediate
|
|
||||||
- Estimated gain: 5-15% traffic
|
|
||||||
|
|
||||||
**Phase 2 (Weeks 3-4)**: Ranking improvements
|
|
||||||
- Effort: Medium
|
|
||||||
- Impact: 2-4 weeks
|
|
||||||
- Estimated gain: 10-20% traffic
|
|
||||||
|
|
||||||
**Phase 3 (Month 2+)**: Long-term strategy
|
|
||||||
- Effort: High
|
|
||||||
- Impact: Long-term
|
|
||||||
- Estimated gain: 20-40% traffic
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📊 Performance Metrics
|
|
||||||
|
|
||||||
**Search Performance Analysis**:
|
|
||||||
- Duration: 2-3 minutes
|
|
||||||
- Metrics calculated: 30+
|
|
||||||
- Opportunities identified: 15+
|
|
||||||
- Analysis dimensions: 8
|
|
||||||
|
|
||||||
**Content Opportunities Report**:
|
|
||||||
- Duration: 1-2 minutes
|
|
||||||
- Opportunities scored: 15+
|
|
||||||
- Phased roadmaps: 3 (Phase 1, 2, 3)
|
|
||||||
- Estimated total traffic gain: 35-75%
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🎯 Next Steps
|
|
||||||
|
|
||||||
1. **[Setup GSC Connection](gsc-integration.md)** - Connect your GSC account
|
|
||||||
2. **[Run First Analysis](quick-start.md)** - Get your baseline metrics
|
|
||||||
3. **[Create Content Plan](content-strategy-guide.md)** - Plan improvements
|
|
||||||
4. **[Track Progress](workflows-guide.md)** - Monitor performance over time
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ❓ FAQ
|
|
||||||
|
|
||||||
**Q: How often is GSC data updated?**
|
|
||||||
A: Data is updated in real-time, though GSC data itself has a 2-3 day delay.
|
|
||||||
|
|
||||||
**Q: What's the minimum data needed?**
|
|
||||||
A: At least 30 days of data for meaningful analysis. 90 days is recommended.
|
|
||||||
|
|
||||||
**Q: How are opportunities prioritized?**
|
|
||||||
A: By a combination of traffic impact, implementation ease, and feasibility.
|
|
||||||
|
|
||||||
**Q: Can I customize the analysis dimensions?**
|
|
||||||
A: Yes, in Phase 2B we'll add customization options.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
*Last Updated: May 26, 2026*
|
|
||||||
*Phase: 2A (Production)*
|
|
||||||
*Status: ✅ Complete*
|
|
||||||
@@ -1,376 +0,0 @@
|
|||||||
# Phase 2A: Enterprise SEO Transformation - Complete Guide
|
|
||||||
|
|
||||||
**Status**: ✅ Production Ready (May 26, 2026)
|
|
||||||
**Version**: 2.0.0
|
|
||||||
**Scope**: Enterprise-grade SEO suite with AI-powered insights
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🎯 Phase 2A Overview
|
|
||||||
|
|
||||||
Phase 2A represents a major evolution in ALwrity's SEO capabilities, transforming individual tool analysis into a cohesive, enterprise-grade SEO platform with AI-powered strategy generation.
|
|
||||||
|
|
||||||
### What's New in Phase 2A
|
|
||||||
|
|
||||||
**3 Major Components**:
|
|
||||||
1. **Enterprise SEO Suite** - Multi-tool orchestration
|
|
||||||
2. **Advanced GSC Analysis** - Deep search intelligence
|
|
||||||
3. **LLM Insights Generation** - AI-powered strategy
|
|
||||||
|
|
||||||
**Combined Capabilities**:
|
|
||||||
- 5-tool orchestration runs in parallel (75% faster)
|
|
||||||
- 30+ metrics across 8 analysis dimensions
|
|
||||||
- 15+ content opportunities identified per analysis
|
|
||||||
- 8 types of AI insights from raw data
|
|
||||||
- 3-phase implementation roadmaps with traffic projections
|
|
||||||
- 95%+ actionability of recommendations
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📊 Component Breakdown
|
|
||||||
|
|
||||||
### Component 1: Enterprise SEO Suite
|
|
||||||
|
|
||||||
**What It Does**: Automatically runs 5 SEO tools in parallel and synthesizes results into unified assessment.
|
|
||||||
|
|
||||||
**Key Metrics**:
|
|
||||||
- Overall Score: 0-100 weighted composite
|
|
||||||
- Component scores: 5 individual assessments
|
|
||||||
- Competitive benchmarking: Compare to 2-5 competitors
|
|
||||||
- Recommendations: 15+ prioritized by impact
|
|
||||||
- Timeline: Phase 1 (weeks 1-2), Phase 2 (weeks 3-4), Phase 3 (month 2+)
|
|
||||||
|
|
||||||
**Tools Orchestrated**:
|
|
||||||
- Technical SEO Analyzer (25%)
|
|
||||||
- On-Page SEO Analyzer (25%)
|
|
||||||
- PageSpeed Analyzer (20%)
|
|
||||||
- Sitemap Analyzer (10%)
|
|
||||||
- Content Strategy Analyzer (20%)
|
|
||||||
|
|
||||||
**Response Time**: 15-20 minutes (complete) or 5 minutes (quick)
|
|
||||||
|
|
||||||
[**Full Documentation →**](phase2a-enterprise-seo.md)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Component 2: Advanced GSC Analysis
|
|
||||||
|
|
||||||
**What It Does**: Analyzes Google Search Console data across 8 dimensions to find optimization opportunities.
|
|
||||||
|
|
||||||
**Analysis Dimensions**:
|
|
||||||
1. Performance Overview (clicks, impressions, CTR, position)
|
|
||||||
2. Keyword Performance (top 25, trending, optimization targets)
|
|
||||||
3. Page Performance (top pages, zero-click pages)
|
|
||||||
4. Content Opportunities (15+ scored opportunities)
|
|
||||||
5. Technical Signals (crawl, coverage, mobile, Core Web Vitals)
|
|
||||||
6. Competitive Positioning (market share, visibility)
|
|
||||||
7. Trend Analysis (30/60/90-day trends, forecasts)
|
|
||||||
8. AI Insights (strategic recommendations)
|
|
||||||
|
|
||||||
**Opportunity Types**:
|
|
||||||
- High-volume, low-CTR (meta tag optimization)
|
|
||||||
- Ranking improvement targets (positions 4-10)
|
|
||||||
- Long-tail expansion (emerging keywords)
|
|
||||||
|
|
||||||
**Response Time**: 2-3 minutes (analysis) or 1-2 minutes (opportunities report)
|
|
||||||
|
|
||||||
[**Full Documentation →**](phase2a-advanced-gsc.md)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Component 3: LLM Insights Generation
|
|
||||||
|
|
||||||
**What It Does**: Transforms raw SEO data into strategic, AI-powered recommendations using advanced language models.
|
|
||||||
|
|
||||||
**8 Insight Types**:
|
|
||||||
1. **Audit Insights** - From enterprise audit data (priority 1-10 scoring)
|
|
||||||
2. **GSC Insights** - From search console data (search intelligence)
|
|
||||||
3. **Content Strategy** - Complete content plan (gap-filling, calendar)
|
|
||||||
4. **Traffic Roadmap** - Phased improvement plan (week-by-week projections)
|
|
||||||
5. **Competitive Insights** - Market positioning analysis
|
|
||||||
6. **Prioritized Recommendations** - AI-ranked by business impact
|
|
||||||
7. **Quick Wins** - 7-day implementations (5-10 quick fixes)
|
|
||||||
8. **Keyword Expansion** - 15-20 new keywords with difficulty/volume
|
|
||||||
|
|
||||||
**Scoring & Ranking**:
|
|
||||||
- Priority: High/Medium/Low
|
|
||||||
- Impact: Traffic % improvement estimate
|
|
||||||
- Effort: Days/weeks required
|
|
||||||
- ROI: Estimated business value
|
|
||||||
|
|
||||||
**Response Time**: 1-3 minutes per insight type
|
|
||||||
|
|
||||||
[**Full Documentation →**](phase2a-llm-insights.md)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🚀 Quick Start
|
|
||||||
|
|
||||||
### Get Your Enterprise Audit
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Complete audit (15-20 min)
|
|
||||||
curl -X POST https://api.alwrity.com/api/seo/enterprise/complete-audit \
|
|
||||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d '{
|
|
||||||
"website_url": "https://example.com",
|
|
||||||
"competitors": ["https://competitor1.com"],
|
|
||||||
"target_keywords": ["SEO", "digital marketing"]
|
|
||||||
}'
|
|
||||||
|
|
||||||
# Quick audit (5 min)
|
|
||||||
curl -X POST https://api.alwrity.com/api/seo/enterprise/quick-audit \
|
|
||||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d '{"website_url": "https://example.com"}'
|
|
||||||
```
|
|
||||||
|
|
||||||
### Run GSC Analysis
|
|
||||||
|
|
||||||
```bash
|
|
||||||
curl -X POST https://api.alwrity.com/api/seo/gsc/analyze-search-performance \
|
|
||||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d '{
|
|
||||||
"site_url": "https://example.com",
|
|
||||||
"date_range_days": 90,
|
|
||||||
"include_opportunities": true
|
|
||||||
}'
|
|
||||||
```
|
|
||||||
|
|
||||||
### Generate AI Insights
|
|
||||||
|
|
||||||
```bash
|
|
||||||
curl -X POST https://api.alwrity.com/api/seo/llm/generate-audit-insights \
|
|
||||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d '{
|
|
||||||
"audit_results": {...},
|
|
||||||
"website_url": "https://example.com",
|
|
||||||
"target_keywords": ["SEO"]
|
|
||||||
}'
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📈 Performance Impact
|
|
||||||
|
|
||||||
### Typical Results (90 Days)
|
|
||||||
|
|
||||||
Based on Phase 2A recommendations:
|
|
||||||
|
|
||||||
| Timeframe | Traffic Gain | Ranking Improvement | New Keywords |
|
|
||||||
|-----------|--------------|-------------------|--------------|
|
|
||||||
| Week 1-2 (Quick Wins) | +5-15% | Top 20 positions | 3-5 new |
|
|
||||||
| Week 3-4 (High Impact) | +10-20% | Top 10 positions | 5-10 new |
|
|
||||||
| Month 2+ (Long-term) | +20-40% | Page 1 ranking | 10-20 new |
|
|
||||||
| **Total (90 days)** | **+35-75%** | **Page 1 focus** | **20-35 new** |
|
|
||||||
|
|
||||||
*Results vary by site maturity, competition, and implementation diligence*
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🎓 Use Case Examples
|
|
||||||
|
|
||||||
### Use Case 1: SEO Agency
|
|
||||||
**Goal**: Deliver enterprise-grade audits to clients
|
|
||||||
|
|
||||||
**Workflow**:
|
|
||||||
1. Run Enterprise Audit → Get score and breakdown
|
|
||||||
2. Run Advanced GSC Analysis → Find opportunities
|
|
||||||
3. Generate AI Insights → Create client roadmap
|
|
||||||
4. Export reports → Send to client
|
|
||||||
5. Track progress monthly → Benchmark improvements
|
|
||||||
|
|
||||||
**Timeline**: 1-2 hours total work, client has 90-day action plan
|
|
||||||
|
|
||||||
### Use Case 2: In-House SEO Team
|
|
||||||
**Goal**: Strategic planning and execution
|
|
||||||
|
|
||||||
**Workflow**:
|
|
||||||
1. Monthly Enterprise Audit → Track SEO health
|
|
||||||
2. Bi-weekly GSC Analysis → Monitor search trends
|
|
||||||
3. Weekly LLM Insights → Generate action items
|
|
||||||
4. Daily quick wins → Implement 7-day fixes
|
|
||||||
5. Quarterly reviews → Measure ROI
|
|
||||||
|
|
||||||
**Timeline**: 5-10 hours/month, continuous improvement
|
|
||||||
|
|
||||||
### Use Case 3: Content Creator
|
|
||||||
**Goal**: Optimize content for search
|
|
||||||
|
|
||||||
**Workflow**:
|
|
||||||
1. Pre-publish: Quick Audit → Check SEO readiness
|
|
||||||
2. Post-publish: LLM Insights → Find optimization angles
|
|
||||||
3. Monthly: GSC Analysis → Find repurposing opportunities
|
|
||||||
4. Quarterly: Full Audit → Track overall progress
|
|
||||||
|
|
||||||
**Timeline**: 30 min/week, improved search rankings
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔧 Technical Architecture
|
|
||||||
|
|
||||||
### Service Orchestration
|
|
||||||
|
|
||||||
```
|
|
||||||
┌─────────────────────────────────────┐
|
|
||||||
│ Enterprise SEO Service │
|
|
||||||
├─────────────────────────────────────┤
|
|
||||||
│ • Coordinates 5 services in parallel│
|
|
||||||
│ • Calculates weighted composite │
|
|
||||||
│ • Generates 3-phase roadmap │
|
|
||||||
│ • Handles graceful failure │
|
|
||||||
└─────────────────────────────────────┘
|
|
||||||
│ │ │
|
|
||||||
↓ ↓ ↓
|
|
||||||
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
|
|
||||||
│ Technical │ │ On-Page SEO │ │ PageSpeed │
|
|
||||||
│ SEO Service │ │ Service │ │ Service │
|
|
||||||
│ (25% weight) │ │ (25% weight) │ │ (20% weight) │
|
|
||||||
└──────────────┘ └──────────────┘ └──────────────┘
|
|
||||||
│ │
|
|
||||||
↓ ↓
|
|
||||||
┌──────────────┐ ┌──────────────┐
|
|
||||||
│ Sitemap │ │ Content │
|
|
||||||
│ Service │ │ Strategy │
|
|
||||||
│ (10% weight) │ │ (20% weight) │
|
|
||||||
└──────────────┘ └──────────────┘
|
|
||||||
```
|
|
||||||
|
|
||||||
### Data Flow
|
|
||||||
|
|
||||||
```
|
|
||||||
Raw SEO Data
|
|
||||||
↓
|
|
||||||
Enterprise Audit Service (orchestration)
|
|
||||||
↓
|
|
||||||
GSC Analyzer Service (analysis)
|
|
||||||
↓
|
|
||||||
LLM Insights Service (AI processing)
|
|
||||||
↓
|
|
||||||
Structured JSON Output (ready for frontend)
|
|
||||||
↓
|
|
||||||
User: Insights, Roadmap, Recommendations
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📚 Documentation Structure
|
|
||||||
|
|
||||||
### For New Users
|
|
||||||
1. [Phase 2A Overview](#) ← You are here
|
|
||||||
2. [Enterprise SEO Suite](phase2a-enterprise-seo.md)
|
|
||||||
3. [Advanced GSC Analysis](phase2a-advanced-gsc.md)
|
|
||||||
4. [LLM Insights Generation](phase2a-llm-insights.md)
|
|
||||||
|
|
||||||
### For Integrators
|
|
||||||
1. [API Reference](../api.md)
|
|
||||||
2. [Integration Guide](../guides/integration-guide.md)
|
|
||||||
3. [Code Examples](#)
|
|
||||||
|
|
||||||
### For Operators
|
|
||||||
1. [Deployment Guide](../guides/deployment.md)
|
|
||||||
2. [Health Monitoring](../guides/monitoring.md)
|
|
||||||
3. [Troubleshooting](../guides/troubleshooting.md)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ✨ Key Improvements vs Phase 1
|
|
||||||
|
|
||||||
| Feature | Phase 1 | Phase 2A |
|
|
||||||
|---------|---------|----------|
|
|
||||||
| Tools | 9 individual | 5 orchestrated |
|
|
||||||
| Analysis Speed | ~60 min | 15-20 min (75% faster) |
|
|
||||||
| Scoring | Individual scores | Unified composite (0-100) |
|
|
||||||
| Insights | Manual review | AI-generated (8 types) |
|
|
||||||
| Opportunities | Listed | Scored and ranked (15+) |
|
|
||||||
| Roadmaps | None | 3-phase with projections |
|
|
||||||
| Competitive | Basic | Advanced positioning |
|
|
||||||
| GSC Analysis | Dashboard only | 8-dimension deep analysis |
|
|
||||||
| Recommendations | Basic | Priority ranked by AI |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🎯 Success Metrics
|
|
||||||
|
|
||||||
**Backend Delivery** (May 26, 2026):
|
|
||||||
- ✅ 12 services fully implemented
|
|
||||||
- ✅ 29 API endpoints live
|
|
||||||
- ✅ 5,200+ lines of code
|
|
||||||
- ✅ 27+ test methods
|
|
||||||
- ✅ 5,200+ lines of documentation
|
|
||||||
|
|
||||||
**Frontend Delivery**:
|
|
||||||
- ✅ 6 React components (4,850 lines)
|
|
||||||
- ✅ API client with 15+ methods
|
|
||||||
- ✅ LLM service with 10+ methods
|
|
||||||
- ✅ 12,000+ lines of documentation
|
|
||||||
- ✅ Fully integrated with SEODashboard
|
|
||||||
|
|
||||||
**Production Ready**:
|
|
||||||
- ✅ All endpoints tested
|
|
||||||
- ✅ Error handling complete
|
|
||||||
- ✅ Authentication integrated
|
|
||||||
- ✅ Logging configured
|
|
||||||
- ✅ Rate limiting ready
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🚀 Next Steps
|
|
||||||
|
|
||||||
### Immediate (This Week)
|
|
||||||
1. Try the Enterprise Audit → See your comprehensive SEO score
|
|
||||||
2. Run GSC Analysis → Find your top opportunities
|
|
||||||
3. Generate AI Insights → Get a strategic roadmap
|
|
||||||
4. Review recommendations → Identify quick wins
|
|
||||||
|
|
||||||
### Short-term (Next 1-2 Weeks)
|
|
||||||
1. Implement quick wins (7-day fixes)
|
|
||||||
2. Plan Phase 1 content improvements
|
|
||||||
3. Set up monthly tracking
|
|
||||||
4. Begin GSC optimization
|
|
||||||
|
|
||||||
### Medium-term (1-3 Months)
|
|
||||||
1. Execute Phase 2 improvements (ranking optimization)
|
|
||||||
2. Create Phase 3 strategy (long-term growth)
|
|
||||||
3. Monitor progress with monthly audits
|
|
||||||
4. Adjust roadmap based on results
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ❓ FAQ
|
|
||||||
|
|
||||||
**Q: How accurate are the AI recommendations?**
|
|
||||||
A: 92%+ alignment with industry best practices. AI learns from thousands of successful implementations.
|
|
||||||
|
|
||||||
**Q: Can I trust the traffic projections?**
|
|
||||||
A: Projections are based on historical data and best practices. Actual results vary by industry, competition, and execution quality. Conservative estimates tend to be 70-90% accurate.
|
|
||||||
|
|
||||||
**Q: What if I disagree with a recommendation?**
|
|
||||||
A: All recommendations are prioritized suggestions, not requirements. You can adjust based on your business context.
|
|
||||||
|
|
||||||
**Q: How often should I run analyses?**
|
|
||||||
A: Complete audits: monthly. Quick audits: weekly. GSC analysis: bi-weekly. LLM insights: on-demand.
|
|
||||||
|
|
||||||
**Q: Can I export the data?**
|
|
||||||
A: Yes, all responses are in JSON format suitable for export to Excel, Sheets, or BI tools.
|
|
||||||
|
|
||||||
**Q: What's included in Enterprise?**
|
|
||||||
A: All Phase 2A features are available to Premium and Enterprise subscribers.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📞 Support
|
|
||||||
|
|
||||||
- **Documentation**: [Full docs](./index.md)
|
|
||||||
- **API Reference**: [Complete reference](../api.md)
|
|
||||||
- **Examples**: [Code samples](../examples.md)
|
|
||||||
- **Help**: Contact support@alwrity.com
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
*Last Updated: May 26, 2026*
|
|
||||||
*Phase: 2A (Production)*
|
|
||||||
*Status: ✅ Complete & Ready*
|
|
||||||
@@ -1,370 +0,0 @@
|
|||||||
# Enterprise SEO Suite - Phase 2A
|
|
||||||
|
|
||||||
The Enterprise SEO Suite is a comprehensive workflow orchestrator that combines all 5 core SEO analysis tools into a unified, powerful audit system. Designed for enterprises, agencies, and serious SEO professionals.
|
|
||||||
|
|
||||||
**Status**: ✅ Production Ready (May 26, 2026)
|
|
||||||
**API Endpoint**: `POST /api/seo/enterprise/complete-audit` & `POST /api/seo/enterprise/quick-audit`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🎯 What is Enterprise SEO Suite?
|
|
||||||
|
|
||||||
The Enterprise SEO Suite automatically coordinates 5 different SEO analysis tools and combines their results into a unified assessment with:
|
|
||||||
|
|
||||||
- **Multi-tool orchestration** - All 5 tools run in parallel
|
|
||||||
- **Unified scoring** - Weighted composite score (0-100)
|
|
||||||
- **Competitive analysis** - Compare against competitors
|
|
||||||
- **Implementation roadmap** - 3-phase timeline with milestones
|
|
||||||
- **Business impact** - Traffic improvement projections
|
|
||||||
- **AI-powered insights** - Strategic recommendations
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ⚙️ How It Works
|
|
||||||
|
|
||||||
### Architecture
|
|
||||||
|
|
||||||
```
|
|
||||||
Enterprise SEO Service
|
|
||||||
├── Technical SEO Analyzer (25% weight)
|
|
||||||
├── On-Page SEO Analyzer (25% weight)
|
|
||||||
├── PageSpeed Analyzer (20% weight)
|
|
||||||
├── Sitemap Analyzer (10% weight)
|
|
||||||
└── Content Strategy Analyzer (20% weight)
|
|
||||||
|
|
||||||
All run in PARALLEL using asyncio.gather()
|
|
||||||
Result: 75% faster than sequential execution
|
|
||||||
```
|
|
||||||
|
|
||||||
### Execution Timeline
|
|
||||||
|
|
||||||
| Mode | Duration | Scope |
|
|
||||||
|------|----------|-------|
|
|
||||||
| **Complete Audit** | 15-20 minutes | Full comprehensive analysis |
|
|
||||||
| **Quick Audit** | 5 minutes | Critical issues only |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📊 Complete Audit Features
|
|
||||||
|
|
||||||
### 1. Technical SEO Analysis (25%)
|
|
||||||
Analyzes site structure and crawlability:
|
|
||||||
- ✅ Site crawling (1-5 depth levels)
|
|
||||||
- ✅ Robots.txt validation
|
|
||||||
- ✅ Sitemap verification
|
|
||||||
- ✅ Canonicalization audit
|
|
||||||
- ✅ Redirect chain detection
|
|
||||||
- ✅ Broken link identification
|
|
||||||
- ✅ Mobile usability analysis
|
|
||||||
- ✅ Issue severity classification
|
|
||||||
|
|
||||||
### 2. On-Page SEO Analysis (25%)
|
|
||||||
Evaluates page-level optimization:
|
|
||||||
- ✅ Meta tag analysis (title, description, headers)
|
|
||||||
- ✅ Content quality scoring
|
|
||||||
- ✅ Keyword optimization (0-100 score)
|
|
||||||
- ✅ Internal linking structure
|
|
||||||
- ✅ Image SEO optimization
|
|
||||||
- ✅ Mobile friendliness check
|
|
||||||
- ✅ Accessibility compliance (WCAG)
|
|
||||||
- ✅ Overall page score (0-100)
|
|
||||||
|
|
||||||
### 3. PageSpeed Analysis (20%)
|
|
||||||
Measures performance metrics:
|
|
||||||
- ✅ Core Web Vitals (LCP, FID, CLS)
|
|
||||||
- ✅ Performance score (0-100)
|
|
||||||
- ✅ Accessibility score (0-100)
|
|
||||||
- ✅ Best practices score (0-100)
|
|
||||||
- ✅ SEO score (0-100)
|
|
||||||
- ✅ Business impact analysis
|
|
||||||
- ✅ Optimization opportunities ranked
|
|
||||||
|
|
||||||
### 4. Sitemap Analysis (10%)
|
|
||||||
Reviews content structure:
|
|
||||||
- ✅ URL structure analysis
|
|
||||||
- ✅ Content distribution
|
|
||||||
- ✅ Publishing frequency
|
|
||||||
- ✅ Content trends and patterns
|
|
||||||
- ✅ SEO opportunities
|
|
||||||
- ✅ Growth recommendations
|
|
||||||
|
|
||||||
### 5. Content Strategy Analysis (20%)
|
|
||||||
Plans content direction:
|
|
||||||
- ✅ Content gap identification
|
|
||||||
- ✅ Opportunity scoring (15+ opportunities)
|
|
||||||
- ✅ Competitive content benchmarking
|
|
||||||
- ✅ Topic cluster recommendations
|
|
||||||
- ✅ Pillar page strategy
|
|
||||||
- ✅ Content calendar suggestions
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📈 Scoring System
|
|
||||||
|
|
||||||
### Overall Score Calculation
|
|
||||||
|
|
||||||
The Enterprise SEO Suite uses a **weighted composite scoring** system:
|
|
||||||
|
|
||||||
```
|
|
||||||
Overall Score = (Technical×0.25) + (OnPage×0.25) + (PageSpeed×0.20) + (Sitemap×0.10) + (Content×0.20)
|
|
||||||
|
|
||||||
Example:
|
|
||||||
- Technical: 75 × 0.25 = 18.75
|
|
||||||
- OnPage: 85 × 0.25 = 21.25
|
|
||||||
- PageSpeed: 70 × 0.20 = 14.00
|
|
||||||
- Sitemap: 90 × 0.10 = 9.00
|
|
||||||
- Content: 80 × 0.20 = 16.00
|
|
||||||
─────────────────────────────────
|
|
||||||
Overall: 79.00 (GOOD)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Score Categories
|
|
||||||
|
|
||||||
| Score | Status | Interpretation |
|
|
||||||
|-------|--------|-----------------|
|
|
||||||
| 90-100 | 🟢 Excellent | Industry-leading SEO |
|
|
||||||
| 80-89 | 🔵 Good | Strong SEO foundation |
|
|
||||||
| 70-79 | 🟡 Needs Improvement | Address key issues |
|
|
||||||
| <70 | 🔴 Poor | Urgent action needed |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🚀 Using the Enterprise Audit
|
|
||||||
|
|
||||||
### Quick Audit (5 minutes)
|
|
||||||
|
|
||||||
Best for: Quick assessment, monitoring, CI/CD pipelines
|
|
||||||
|
|
||||||
```bash
|
|
||||||
curl -X POST https://api.alwrity.com/api/seo/enterprise/quick-audit \
|
|
||||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d '{
|
|
||||||
"website_url": "https://example.com"
|
|
||||||
}'
|
|
||||||
```
|
|
||||||
|
|
||||||
**Returns**:
|
|
||||||
- Overall score (0-100)
|
|
||||||
- Critical issues only
|
|
||||||
- Top 3 quick wins
|
|
||||||
- Estimated traffic improvement
|
|
||||||
- No competitor analysis
|
|
||||||
|
|
||||||
### Complete Audit (15-20 minutes)
|
|
||||||
|
|
||||||
Best for: Comprehensive assessment, strategy planning, benchmarking
|
|
||||||
|
|
||||||
```bash
|
|
||||||
curl -X POST https://api.alwrity.com/api/seo/enterprise/complete-audit \
|
|
||||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d '{
|
|
||||||
"website_url": "https://example.com",
|
|
||||||
"competitors": [
|
|
||||||
"https://competitor1.com",
|
|
||||||
"https://competitor2.com"
|
|
||||||
],
|
|
||||||
"target_keywords": ["SEO", "content marketing"]
|
|
||||||
}'
|
|
||||||
```
|
|
||||||
|
|
||||||
**Returns**:
|
|
||||||
- Complete analysis across all 5 tools
|
|
||||||
- Component scores and breakdown
|
|
||||||
- 15+ prioritized recommendations
|
|
||||||
- Competitive gap analysis
|
|
||||||
- 3-phase implementation timeline
|
|
||||||
- Business impact projections
|
|
||||||
- AI-powered strategic insights
|
|
||||||
- Executive summary report
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📋 Response Format
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": true,
|
|
||||||
"message": "Complete enterprise audit executed successfully",
|
|
||||||
"execution_time": 1240.5,
|
|
||||||
"data": {
|
|
||||||
"overall_score": 79,
|
|
||||||
"overall_status": "good",
|
|
||||||
"audit_type": "complete",
|
|
||||||
"component_scores": {
|
|
||||||
"technical_seo": 75,
|
|
||||||
"on_page_seo": 85,
|
|
||||||
"pagespeed": 70,
|
|
||||||
"sitemap": 90,
|
|
||||||
"content_strategy": 80
|
|
||||||
},
|
|
||||||
"competitive_analysis": {
|
|
||||||
"your_score": 79,
|
|
||||||
"competitor1_score": 72,
|
|
||||||
"competitor2_score": 68,
|
|
||||||
"market_gap": 11,
|
|
||||||
"position": "Leader"
|
|
||||||
},
|
|
||||||
"recommendations": [
|
|
||||||
{
|
|
||||||
"priority": 1,
|
|
||||||
"category": "Technical SEO",
|
|
||||||
"title": "Fix Mobile Usability Issues",
|
|
||||||
"impact": "High",
|
|
||||||
"effort": "Medium",
|
|
||||||
"estimated_traffic_gain": "15-20%",
|
|
||||||
"steps": ["Step 1", "Step 2", "Step 3"]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"implementation_roadmap": {
|
|
||||||
"phase_1": {
|
|
||||||
"duration": "Weeks 1-2",
|
|
||||||
"focus": "Quick wins & critical fixes",
|
|
||||||
"estimated_traffic_gain": "5-10%",
|
|
||||||
"tasks": ["Task 1", "Task 2"]
|
|
||||||
},
|
|
||||||
"phase_2": {
|
|
||||||
"duration": "Weeks 3-4",
|
|
||||||
"focus": "Ranking improvements",
|
|
||||||
"estimated_traffic_gain": "10-15%",
|
|
||||||
"tasks": ["Task 1", "Task 2"]
|
|
||||||
},
|
|
||||||
"phase_3": {
|
|
||||||
"duration": "Month 2+",
|
|
||||||
"focus": "Long-term strategy",
|
|
||||||
"estimated_traffic_gain": "15-30%",
|
|
||||||
"tasks": ["Task 1", "Task 2"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🎓 Use Cases
|
|
||||||
|
|
||||||
### Use Case 1: Monthly SEO Audits
|
|
||||||
|
|
||||||
Run a complete audit monthly to track SEO health:
|
|
||||||
|
|
||||||
```python
|
|
||||||
import asyncio
|
|
||||||
from services.seo_tools.enterprise_seo_service import EnterpriseSEOService
|
|
||||||
|
|
||||||
async def monthly_audit():
|
|
||||||
service = EnterpriseSEOService()
|
|
||||||
|
|
||||||
result = await service.execute_complete_audit(
|
|
||||||
website_url="https://mysite.com",
|
|
||||||
competitors=[
|
|
||||||
"https://competitor1.com",
|
|
||||||
"https://competitor2.com"
|
|
||||||
],
|
|
||||||
target_keywords=["SEO", "digital marketing"]
|
|
||||||
)
|
|
||||||
|
|
||||||
print(f"SEO Score: {result['overall_score']}")
|
|
||||||
print(f"Recommendations: {len(result['recommendations'])}")
|
|
||||||
print(f"Traffic Potential: {result['implementation_roadmap']['phase_1']['estimated_traffic_gain']}")
|
|
||||||
|
|
||||||
asyncio.run(monthly_audit())
|
|
||||||
```
|
|
||||||
|
|
||||||
### Use Case 2: Pre-Launch Audit
|
|
||||||
|
|
||||||
Run a quick audit before publishing to check SEO readiness:
|
|
||||||
|
|
||||||
```python
|
|
||||||
# Quick validation before launch
|
|
||||||
result = await service.execute_quick_audit("https://staging.mysite.com")
|
|
||||||
|
|
||||||
if result['overall_score'] < 70:
|
|
||||||
print("⚠️ SEO score below 70 - address issues before launch")
|
|
||||||
for rec in result['recommendations'][:3]:
|
|
||||||
print(f"- {rec['title']}")
|
|
||||||
else:
|
|
||||||
print("✅ Site is SEO-ready for launch")
|
|
||||||
```
|
|
||||||
|
|
||||||
### Use Case 3: Competitive Benchmarking
|
|
||||||
|
|
||||||
Compare your site against competitors:
|
|
||||||
|
|
||||||
```python
|
|
||||||
result = await service.execute_complete_audit(
|
|
||||||
website_url="https://mysite.com",
|
|
||||||
competitors=["https://comp1.com", "https://comp2.com"]
|
|
||||||
)
|
|
||||||
|
|
||||||
competitive = result['competitive_analysis']
|
|
||||||
print(f"Your Score: {competitive['your_score']}")
|
|
||||||
print(f"Market Position: {competitive['position']}")
|
|
||||||
print(f"Gap to Leader: {competitive['market_gap']} points")
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔧 Advanced Features
|
|
||||||
|
|
||||||
### Health Check Endpoint
|
|
||||||
|
|
||||||
Monitor the Enterprise SEO service health:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
GET /api/seo/enterprise/health
|
|
||||||
```
|
|
||||||
|
|
||||||
Returns status of all sub-services:
|
|
||||||
- Technical SEO Service
|
|
||||||
- On-Page SEO Service
|
|
||||||
- PageSpeed Service
|
|
||||||
- Sitemap Service
|
|
||||||
- Content Strategy Service
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📊 Performance Metrics
|
|
||||||
|
|
||||||
**Complete Audit**:
|
|
||||||
- Duration: 15-20 minutes
|
|
||||||
- Parallel components: 5
|
|
||||||
- Sequential equivalent: ~60 minutes
|
|
||||||
- Speed improvement: **75% faster** ⚡
|
|
||||||
|
|
||||||
**Quick Audit**:
|
|
||||||
- Duration: 5 minutes
|
|
||||||
- Scope: Critical issues only
|
|
||||||
- Rate limit: Unlimited
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🎯 Next Steps
|
|
||||||
|
|
||||||
1. **[Install & Configure](quick-start.md)** - Get started in 10 minutes
|
|
||||||
2. **[View API Reference](gsc-integration.md)** - Complete endpoint documentation
|
|
||||||
3. **[Explore Tools](individual-tools-guide.md)** - Learn about each component
|
|
||||||
4. **[Try Workflows](workflows-guide.md)** - See real-world examples
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ❓ FAQ
|
|
||||||
|
|
||||||
**Q: How often should I run a complete audit?**
|
|
||||||
A: Monthly is recommended for most sites. Quick audits can be run more frequently.
|
|
||||||
|
|
||||||
**Q: What if a component fails?**
|
|
||||||
A: The service gracefully handles failures. If one component fails, others continue, and you receive partial results.
|
|
||||||
|
|
||||||
**Q: Can I customize the weights?**
|
|
||||||
A: Currently using 25/25/20/10/20 distribution. Custom weighting coming in Phase 2B.
|
|
||||||
|
|
||||||
**Q: How is competitive analysis done?**
|
|
||||||
A: We analyze your competitor's publicly available content and technical SEO.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
*Last Updated: May 26, 2026*
|
|
||||||
*Phase: 2A (Production)*
|
|
||||||
*Status: ✅ Complete*
|
|
||||||
@@ -1,530 +0,0 @@
|
|||||||
# LLM Insights Generation - Phase 2A
|
|
||||||
|
|
||||||
LLM Insights Generation transforms raw SEO data into strategic, actionable intelligence using advanced AI. Generate audit insights, content strategies, traffic roadmaps, and competitive intelligence automatically.
|
|
||||||
|
|
||||||
**Status**: ✅ Production Ready (May 26, 2026)
|
|
||||||
**API Endpoints**: 9 comprehensive endpoints for AI-powered insights
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🎯 What is LLM Insights?
|
|
||||||
|
|
||||||
LLM Insights uses advanced AI to automatically generate strategic recommendations from SEO data:
|
|
||||||
|
|
||||||
- **8 insight types** - Different AI-powered analyses
|
|
||||||
- **Priority scoring** - Rank by business impact
|
|
||||||
- **Traffic projections** - Estimate improvement potential
|
|
||||||
- **Phased roadmaps** - Implementation timelines
|
|
||||||
- **Competitive intelligence** - Market positioning
|
|
||||||
- **Quick wins** - 7-day implementations
|
|
||||||
- **Keyword expansion** - 15-20 new keyword suggestions
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🚀 LLM Endpoints Overview
|
|
||||||
|
|
||||||
### 1. Generate Audit Insights
|
|
||||||
|
|
||||||
Transform enterprise audit data into strategic insights:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
POST /api/seo/llm/generate-audit-insights
|
|
||||||
```
|
|
||||||
|
|
||||||
**Input**: Complete enterprise audit results
|
|
||||||
**Output**: Priority-scored insights with traffic projections
|
|
||||||
|
|
||||||
**Response Includes**:
|
|
||||||
- 10+ insights ranked by priority (1-10)
|
|
||||||
- Traffic impact estimations (low/medium/high)
|
|
||||||
- Implementation difficulty assessments
|
|
||||||
- Step-by-step action guides
|
|
||||||
- Required tools and resources
|
|
||||||
- Timeline estimates (days/weeks)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 2. Generate GSC Insights
|
|
||||||
|
|
||||||
Analyze search performance data strategically:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
POST /api/seo/llm/generate-gsc-insights
|
|
||||||
```
|
|
||||||
|
|
||||||
**Input**: Complete GSC analysis data (8 dimensions)
|
|
||||||
**Output**: Strategic search intelligence
|
|
||||||
|
|
||||||
**Response Includes**:
|
|
||||||
- Keyword optimization opportunities
|
|
||||||
- CTR improvement strategies
|
|
||||||
- Content ranking improvement plans
|
|
||||||
- Competitive positioning analysis
|
|
||||||
- Quick-win identification
|
|
||||||
- Search intent analysis
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 3. Generate Content Strategy
|
|
||||||
|
|
||||||
Create comprehensive content plans:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
POST /api/seo/llm/generate-content-strategy
|
|
||||||
```
|
|
||||||
|
|
||||||
**Input**:
|
|
||||||
- Current content analysis
|
|
||||||
- Content gaps (15-25 identified)
|
|
||||||
- Target keywords (50-100)
|
|
||||||
- Competitor content (optional)
|
|
||||||
|
|
||||||
**Output**: Complete content strategy
|
|
||||||
|
|
||||||
**Response Includes**:
|
|
||||||
- Gap-filling content plan
|
|
||||||
- Content calendar (3-month)
|
|
||||||
- Keyword-to-content mapping
|
|
||||||
- Topic cluster recommendations
|
|
||||||
- Pillar page strategy
|
|
||||||
- Content format recommendations
|
|
||||||
- Publishing frequency plan
|
|
||||||
- Content ROI estimates
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 4. Generate Traffic Roadmap
|
|
||||||
|
|
||||||
Plan phased traffic improvement:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
POST /api/seo/llm/generate-traffic-roadmap
|
|
||||||
```
|
|
||||||
|
|
||||||
**Input**:
|
|
||||||
- Current traffic metrics
|
|
||||||
- Identified opportunities (15+)
|
|
||||||
- Implementation timeline (weeks)
|
|
||||||
|
|
||||||
**Output**: Phase-based improvement plan
|
|
||||||
|
|
||||||
**Response Includes**:
|
|
||||||
- Week-by-week action plan
|
|
||||||
- Traffic gain projections per week
|
|
||||||
- Key performance indicators (KPIs)
|
|
||||||
- Success metrics
|
|
||||||
- Dependency mapping
|
|
||||||
- Resource requirements
|
|
||||||
- Risk mitigation strategies
|
|
||||||
- Validation checkpoints
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 5. Generate Competitive Insights
|
|
||||||
|
|
||||||
Analyze competitive landscape:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
POST /api/seo/llm/generate-competitive-insights
|
|
||||||
```
|
|
||||||
|
|
||||||
**Input**:
|
|
||||||
- Your site analysis
|
|
||||||
- 2-5 competitor analyses
|
|
||||||
|
|
||||||
**Output**: Competitive intelligence
|
|
||||||
|
|
||||||
**Response Includes**:
|
|
||||||
- Competitive advantage identification
|
|
||||||
- Competitive gap analysis
|
|
||||||
- Market opportunity identification
|
|
||||||
- Threat assessment
|
|
||||||
- Win strategy recommendations
|
|
||||||
- Differentiation recommendations
|
|
||||||
- Positioning strategies
|
|
||||||
- Blue ocean opportunities
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 6. Prioritized Recommendations
|
|
||||||
|
|
||||||
Get AI-ranked recommendations:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
POST /api/seo/llm/prioritized-recommendations
|
|
||||||
```
|
|
||||||
|
|
||||||
**Input**:
|
|
||||||
- All recommendations (50-100)
|
|
||||||
- Business context (goals, constraints)
|
|
||||||
|
|
||||||
**Output**: Prioritized action list
|
|
||||||
|
|
||||||
**Response Includes**:
|
|
||||||
- Ranked by business impact (High/Medium/Low)
|
|
||||||
- Traffic improvement potential
|
|
||||||
- Implementation effort
|
|
||||||
- Timeline to implement
|
|
||||||
- Resource requirements
|
|
||||||
- ROI potential
|
|
||||||
- Risk level
|
|
||||||
- Categorized as:
|
|
||||||
- Quick Wins (0-7 days)
|
|
||||||
- High Impact (1-4 weeks)
|
|
||||||
- Long-term (1-3 months)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 7. Quick Wins Identification
|
|
||||||
|
|
||||||
Find 7-day implementations:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
POST /api/seo/llm/quick-wins
|
|
||||||
```
|
|
||||||
|
|
||||||
**Input**:
|
|
||||||
- Complete audit data
|
|
||||||
- Max implementation days (1-30)
|
|
||||||
|
|
||||||
**Output**: Immediately actionable items
|
|
||||||
|
|
||||||
**Response Includes**:
|
|
||||||
- 5-10 quick wins
|
|
||||||
- Estimated traffic gain per win
|
|
||||||
- Implementation steps (3-5 steps)
|
|
||||||
- Tools needed
|
|
||||||
- Expected outcomes
|
|
||||||
- Success metrics
|
|
||||||
- Timeline breakdown
|
|
||||||
|
|
||||||
**Quick Win Categories**:
|
|
||||||
- Meta tag optimization
|
|
||||||
- URL structure improvements
|
|
||||||
- Internal linking fixes
|
|
||||||
- Content formatting
|
|
||||||
- Technical SEO fixes
|
|
||||||
- Performance quick fixes
|
|
||||||
- H-tag restructuring
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 8. Keyword Expansion
|
|
||||||
|
|
||||||
Generate 15-20 new keywords:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
POST /api/seo/llm/keyword-expansion
|
|
||||||
```
|
|
||||||
|
|
||||||
**Input**:
|
|
||||||
- Current target keywords (10-20)
|
|
||||||
- Content analysis
|
|
||||||
- Target difficulty (optional)
|
|
||||||
|
|
||||||
**Output**: Expanded keyword list
|
|
||||||
|
|
||||||
**Response Includes**:
|
|
||||||
- 15-20 new keywords
|
|
||||||
- Long-tail variations
|
|
||||||
- Question-based keywords
|
|
||||||
- Local variations (if applicable)
|
|
||||||
- Intent-based keywords (commercial, informational, navigational)
|
|
||||||
- Seasonal variants
|
|
||||||
- Search volume estimates
|
|
||||||
- Difficulty scores
|
|
||||||
- Relevance to your content
|
|
||||||
- Content opportunity analysis
|
|
||||||
|
|
||||||
**Keyword Categories**:
|
|
||||||
- Long-tail (3-5+ words)
|
|
||||||
- Question-based (People Also Ask)
|
|
||||||
- Local variations (geo-targeted)
|
|
||||||
- Intent-based (transactional, commercial, informational)
|
|
||||||
- Seasonal variants
|
|
||||||
- Related keywords
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 9. LLM Service Health
|
|
||||||
|
|
||||||
Monitor the insights service:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
GET /api/seo/llm/health
|
|
||||||
```
|
|
||||||
|
|
||||||
**Returns**:
|
|
||||||
- Service status
|
|
||||||
- LLM integration status
|
|
||||||
- Response time
|
|
||||||
- Last check timestamp
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📊 Usage Examples
|
|
||||||
|
|
||||||
### Example 1: Complete Insight Generation
|
|
||||||
|
|
||||||
Generate all insights from audit data:
|
|
||||||
|
|
||||||
```python
|
|
||||||
import asyncio
|
|
||||||
from services.seo_tools.llm_insights_service import LLMInsightsService
|
|
||||||
|
|
||||||
async def generate_all_insights():
|
|
||||||
service = LLMInsightsService()
|
|
||||||
|
|
||||||
# 1. Audit Insights
|
|
||||||
audit_insights = await service.generate_enterprise_audit_insights(
|
|
||||||
audit_results=audit_data,
|
|
||||||
website_url="https://example.com",
|
|
||||||
target_keywords=["SEO", "content"]
|
|
||||||
)
|
|
||||||
|
|
||||||
# 2. GSC Insights
|
|
||||||
gsc_insights = await service.generate_gsc_analysis_insights(
|
|
||||||
gsc_analysis=gsc_data,
|
|
||||||
website_url="https://example.com"
|
|
||||||
)
|
|
||||||
|
|
||||||
# 3. Content Strategy
|
|
||||||
strategy = await service.generate_content_strategy_insights(
|
|
||||||
current_content=content_analysis,
|
|
||||||
content_gaps=identified_gaps,
|
|
||||||
target_keywords=target_keywords,
|
|
||||||
competitor_content=competitor_analysis
|
|
||||||
)
|
|
||||||
|
|
||||||
# 4. Traffic Roadmap
|
|
||||||
roadmap = await service.generate_traffic_improvement_roadmap(
|
|
||||||
current_metrics=traffic_metrics,
|
|
||||||
identified_opportunities=opportunities,
|
|
||||||
implementation_timeline_weeks=12
|
|
||||||
)
|
|
||||||
|
|
||||||
# 5. Competitive Insights
|
|
||||||
competitive = await service.generate_competitive_insights(
|
|
||||||
primary_site_analysis=your_analysis,
|
|
||||||
competitor_analyses=competitors
|
|
||||||
)
|
|
||||||
|
|
||||||
# 6. Prioritized Recommendations
|
|
||||||
prioritized = await service.generate_prioritized_recommendations(
|
|
||||||
all_recommendations=all_recs,
|
|
||||||
business_context=business_goals
|
|
||||||
)
|
|
||||||
|
|
||||||
# 7. Quick Wins
|
|
||||||
quick_wins = await service.generate_quick_wins(
|
|
||||||
audit_data=audit_data,
|
|
||||||
max_days_to_implement=7
|
|
||||||
)
|
|
||||||
|
|
||||||
# 8. Keyword Expansion
|
|
||||||
keywords = await service.generate_keyword_expansion(
|
|
||||||
current_keywords=current_keywords,
|
|
||||||
content_analysis=content_analysis,
|
|
||||||
target_difficulty="medium"
|
|
||||||
)
|
|
||||||
|
|
||||||
return {
|
|
||||||
"audit_insights": audit_insights,
|
|
||||||
"gsc_insights": gsc_insights,
|
|
||||||
"content_strategy": strategy,
|
|
||||||
"traffic_roadmap": roadmap,
|
|
||||||
"competitive_insights": competitive,
|
|
||||||
"prioritized_recommendations": prioritized,
|
|
||||||
"quick_wins": quick_wins,
|
|
||||||
"keyword_expansion": keywords
|
|
||||||
}
|
|
||||||
|
|
||||||
insights = asyncio.run(generate_all_insights())
|
|
||||||
```
|
|
||||||
|
|
||||||
### Example 2: Priority-Based Action Planning
|
|
||||||
|
|
||||||
Focus on highest-impact items first:
|
|
||||||
|
|
||||||
```python
|
|
||||||
# Get prioritized recommendations
|
|
||||||
recommendations = await service.generate_prioritized_recommendations(
|
|
||||||
all_recommendations=all_recommendations,
|
|
||||||
business_context={
|
|
||||||
"goal": "Increase organic traffic 50%",
|
|
||||||
"timeline": "3 months",
|
|
||||||
"budget": "Medium",
|
|
||||||
"team_size": 2
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
# Focus on quick wins first
|
|
||||||
quick_wins = [r for r in recommendations['quick_wins'] if r['effort'] == 'Low']
|
|
||||||
print(f"Quick Wins to do today: {len(quick_wins)}")
|
|
||||||
|
|
||||||
# Then high impact
|
|
||||||
high_impact = [r for r in recommendations['high_impact'] if r['effort'] == 'Medium']
|
|
||||||
print(f"High Impact items: {len(high_impact)}")
|
|
||||||
|
|
||||||
# Finally long-term strategy
|
|
||||||
long_term = recommendations['long_term']
|
|
||||||
print(f"Long-term improvements: {len(long_term)}")
|
|
||||||
```
|
|
||||||
|
|
||||||
### Example 3: Traffic Improvement Planning
|
|
||||||
|
|
||||||
Plan 90-day traffic growth:
|
|
||||||
|
|
||||||
```python
|
|
||||||
# Generate phased roadmap
|
|
||||||
roadmap = await service.generate_traffic_improvement_roadmap(
|
|
||||||
current_metrics={
|
|
||||||
"monthly_organic_traffic": 10000,
|
|
||||||
"keywords_ranked_top_10": 45,
|
|
||||||
"avg_position": 12.5
|
|
||||||
},
|
|
||||||
identified_opportunities=opportunities_list,
|
|
||||||
implementation_timeline_weeks=12
|
|
||||||
)
|
|
||||||
|
|
||||||
print("90-Day Traffic Improvement Plan:")
|
|
||||||
print(f"\nWeek 1-2 (Phase 1 - Quick Wins):")
|
|
||||||
for task in roadmap['phase_1']['tasks']:
|
|
||||||
print(f" - {task}")
|
|
||||||
print(f" Expected gain: +{roadmap['phase_1']['traffic_gain']}% traffic")
|
|
||||||
|
|
||||||
print(f"\nWeek 3-4 (Phase 2 - Ranking Improvements):")
|
|
||||||
for task in roadmap['phase_2']['tasks']:
|
|
||||||
print(f" - {task}")
|
|
||||||
print(f" Expected gain: +{roadmap['phase_2']['traffic_gain']}% traffic")
|
|
||||||
|
|
||||||
print(f"\nMonth 2+ (Phase 3 - Long-term Strategy):")
|
|
||||||
for task in roadmap['phase_3']['tasks']:
|
|
||||||
print(f" - {task}")
|
|
||||||
print(f" Expected gain: +{roadmap['phase_3']['traffic_gain']}% traffic")
|
|
||||||
|
|
||||||
print(f"\nTotal Expected Improvement: +{roadmap['total_improvement']}% traffic")
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🎯 Response Format Example
|
|
||||||
|
|
||||||
### Audit Insights Response
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": true,
|
|
||||||
"message": "Audit insights generated successfully",
|
|
||||||
"execution_time": 12.5,
|
|
||||||
"data": {
|
|
||||||
"insights": [
|
|
||||||
{
|
|
||||||
"id": "insight_001",
|
|
||||||
"priority": 1,
|
|
||||||
"category": "Technical SEO",
|
|
||||||
"title": "Fix Mobile Usability Issues",
|
|
||||||
"description": "Your site has detected mobile usability problems affecting ~15% of pages",
|
|
||||||
"traffic_impact": "High",
|
|
||||||
"estimated_traffic_gain": "15-20%",
|
|
||||||
"implementation_effort": "Medium",
|
|
||||||
"implementation_timeline": "7-10 days",
|
|
||||||
"steps": [
|
|
||||||
"Step 1: Identify affected pages using Google Console",
|
|
||||||
"Step 2: Fix responsive design issues",
|
|
||||||
"Step 3: Test with mobile emulator",
|
|
||||||
"Step 4: Submit URL inspection in GSC"
|
|
||||||
],
|
|
||||||
"required_tools": ["Google Mobile-Friendly Test", "Chrome DevTools"],
|
|
||||||
"success_metrics": ["All pages pass mobile test", "Mobile usability score increase"],
|
|
||||||
"related_keywords": ["mobile SEO", "responsive design"]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"summary": {
|
|
||||||
"total_insights": 12,
|
|
||||||
"high_priority": 3,
|
|
||||||
"medium_priority": 5,
|
|
||||||
"low_priority": 4,
|
|
||||||
"total_potential_traffic_gain": "45-65%",
|
|
||||||
"estimated_implementation_time": "3-4 weeks"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔧 Advanced Features
|
|
||||||
|
|
||||||
### AI Prompt Engineering
|
|
||||||
|
|
||||||
Each insight type uses specialized AI prompts optimized for:
|
|
||||||
- **Audit Insights**: Action-oriented recommendations
|
|
||||||
- **GSC Insights**: Search data interpretation
|
|
||||||
- **Content Strategy**: Topic and keyword mapping
|
|
||||||
- **Traffic Roadmap**: Timeline and milestone planning
|
|
||||||
- **Competitive Analysis**: Market positioning
|
|
||||||
- **Keyword Expansion**: Long-tail and intent-based keywords
|
|
||||||
|
|
||||||
### Scoring Algorithms
|
|
||||||
|
|
||||||
Insights are scored on multiple dimensions:
|
|
||||||
|
|
||||||
```
|
|
||||||
Priority Score = (Traffic Impact × 0.4) + (Ease × 0.3) + (Timeline × 0.2) + (Resource Cost × 0.1)
|
|
||||||
|
|
||||||
Range: 0-100 (Higher = More actionable)
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📊 Performance Metrics
|
|
||||||
|
|
||||||
**Generation Time by Insight Type**:
|
|
||||||
- Audit Insights: 30-60 seconds
|
|
||||||
- GSC Insights: 20-40 seconds
|
|
||||||
- Content Strategy: 45-90 seconds
|
|
||||||
- Traffic Roadmap: 60-120 seconds
|
|
||||||
- Competitive Insights: 45-90 seconds
|
|
||||||
- Prioritized Recommendations: 30-60 seconds
|
|
||||||
- Quick Wins: 20-40 seconds
|
|
||||||
- Keyword Expansion: 15-30 seconds
|
|
||||||
|
|
||||||
**Insight Quality Metrics**:
|
|
||||||
- Accuracy: 92%+ alignment with industry best practices
|
|
||||||
- Actionability: 95%+ of recommendations are implementable
|
|
||||||
- ROI: Average 15-40% traffic improvement within 90 days
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🎯 Next Steps
|
|
||||||
|
|
||||||
1. **[View Enterprise Audit](phase2a-enterprise-seo.md)** - Understand audit data
|
|
||||||
2. **[Explore GSC Analysis](phase2a-advanced-gsc.md)** - Learn GSC insights
|
|
||||||
3. **[Run Insights](quick-start.md)** - Generate your first insights
|
|
||||||
4. **[Track Results](workflows-guide.md)** - Monitor improvements
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ❓ FAQ
|
|
||||||
|
|
||||||
**Q: How accurate are the AI recommendations?**
|
|
||||||
A: 92%+ alignment with industry best practices. AI learns from thousands of successful SEO implementations.
|
|
||||||
|
|
||||||
**Q: Can I customize the insights?**
|
|
||||||
A: Yes, in Phase 2B we'll add customization for business context, industry, and goals.
|
|
||||||
|
|
||||||
**Q: How often should I regenerate insights?**
|
|
||||||
A: Monthly is recommended to track changes and identify new opportunities.
|
|
||||||
|
|
||||||
**Q: What if insights contradict each other?**
|
|
||||||
A: The prioritization algorithm handles this by considering business impact and feasibility.
|
|
||||||
|
|
||||||
**Q: Can I export the insights?**
|
|
||||||
A: Yes, all insights are available in JSON format and can be exported for reporting.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
*Last Updated: May 26, 2026*
|
|
||||||
*Phase: 2A (Production)*
|
|
||||||
*Status: ✅ Complete*
|
|
||||||
@@ -377,27 +377,3 @@ Pick your next step:
|
|||||||
**Happy optimizing! 🚀**
|
**Happy optimizing! 🚀**
|
||||||
|
|
||||||
Need more help? See [Tools Reference](tools-reference.md) or ask the AI Copilot in your dashboard.
|
Need more help? See [Tools Reference](tools-reference.md) or ask the AI Copilot in your dashboard.
|
||||||
|
|
||||||
|
|
||||||
## 🆕 Recent SEO Enhancements (Reviewed: May 25, 2026)
|
|
||||||
|
|
||||||
The SEO stack has expanded beyond core URL analysis. Based on the current frontend/backend code and latest SEO docs, these additions should be considered part of the current docs-site surface:
|
|
||||||
|
|
||||||
### Dashboard + API enhancements
|
|
||||||
- Strategic insights execution and history (`/api/seo-dashboard/strategic-insights/run`, `/api/seo-dashboard/strategic-insights/history`)
|
|
||||||
- Deep competitor analysis endpoint (`/api/seo-dashboard/deep-competitor-analysis`)
|
|
||||||
- Onboarding task health visibility (`/api/seo-dashboard/onboarding-task-health`)
|
|
||||||
- Operational diagnostics (`/api/seo-dashboard/semantic-health`, `/api/seo-dashboard/sif-health`, `/api/seo-dashboard/cache-stats`)
|
|
||||||
- Route aliases active in app routing: `/seo` and `/seo-dashboard`
|
|
||||||
|
|
||||||
### Workflow enhancements connected to SEO
|
|
||||||
- Blog Writer now includes GSC-powered brainstorming flows (`frontend/src/components/BlogWriter/GSCBrainstormModal.tsx`, `backend/services/gsc_brainstorm_service.py`)
|
|
||||||
- SEO guidance is available both in dashboard workflows and in-editor Blog Writer SEO modules (e.g., `SEOMiniPanel`, `SEOAnalysisModal`, `SEOMetadataModal`)
|
|
||||||
|
|
||||||
### Source docs reviewed for enhancement tracking
|
|
||||||
- `docs/SEO/COMPLETE_SEO_TOOLS_INVENTORY.md`
|
|
||||||
- `docs/SEO/API_REFERENCE.md`
|
|
||||||
- `docs/SEO/PHASE2A_IMPLEMENTATION.md`
|
|
||||||
- `docs/SEO/MIGRATION_STATUS_ANALYSIS.md`
|
|
||||||
- `docs/SEO/MIGRATION_DETAILED_GAPS.md`
|
|
||||||
- `docs/SEO/MIGRATION_EXECUTIVE_SUMMARY.md`
|
|
||||||
|
|||||||
@@ -355,72 +355,3 @@ See next section...
|
|||||||
**Status**: Production Ready ✅
|
**Status**: Production Ready ✅
|
||||||
|
|
||||||
For support, visit our documentation or contact support@alwrity.com
|
For support, visit our documentation or contact support@alwrity.com
|
||||||
|
|
||||||
|
|
||||||
## ✅ Implementation Alignment (May 2026)
|
|
||||||
|
|
||||||
The following endpoints are currently mounted and used by the active SEO Dashboard implementation:
|
|
||||||
|
|
||||||
### Analysis Execution
|
|
||||||
- `POST /api/seo-dashboard/analyze-comprehensive`
|
|
||||||
- `POST /api/seo-dashboard/analyze-full`
|
|
||||||
- `POST /api/seo-dashboard/batch-analyze`
|
|
||||||
- `POST /api/seo-dashboard/analyze-urls-ai`
|
|
||||||
|
|
||||||
### Dashboard Data & Reporting
|
|
||||||
- `GET /api/seo-dashboard/data`
|
|
||||||
- `GET /api/seo-dashboard/overview`
|
|
||||||
- `GET /api/seo-dashboard/metrics`
|
|
||||||
- `GET /api/seo-dashboard/metrics-detailed`
|
|
||||||
- `GET /api/seo-dashboard/analysis-summary`
|
|
||||||
- `GET /api/seo-dashboard/health-score`
|
|
||||||
- `GET /api/seo-dashboard/insights`
|
|
||||||
|
|
||||||
### Integrations
|
|
||||||
- `GET /api/seo-dashboard/platforms`
|
|
||||||
- `GET /api/seo-dashboard/gsc/raw`
|
|
||||||
- `GET /api/seo-dashboard/bing/raw`
|
|
||||||
- `POST /api/seo-dashboard/refresh`
|
|
||||||
|
|
||||||
### Competitive & Strategic Intelligence
|
|
||||||
- `GET /api/seo-dashboard/competitive-insights`
|
|
||||||
- `GET /api/seo-dashboard/deep-competitor-analysis`
|
|
||||||
- `POST /api/seo-dashboard/strategic-insights/run`
|
|
||||||
- `GET /api/seo-dashboard/strategic-insights/history`
|
|
||||||
|
|
||||||
### Operations & Health
|
|
||||||
- `GET /api/seo-dashboard/health`
|
|
||||||
- `GET /api/seo-dashboard/semantic-health`
|
|
||||||
- `GET /api/seo-dashboard/sif-health`
|
|
||||||
- `GET /api/seo-dashboard/cache-stats`
|
|
||||||
- `GET /api/seo-dashboard/onboarding-task-health`
|
|
||||||
|
|
||||||
### Source-of-Truth Code Paths
|
|
||||||
- Frontend dashboard: `frontend/src/components/SEODashboard/`
|
|
||||||
- Frontend API clients: `frontend/src/api/seoDashboard.ts`, `frontend/src/api/seoAnalysis.ts`
|
|
||||||
- Backend API router: `backend/api/seo_dashboard.py`
|
|
||||||
- Backend SEO service: `backend/services/seo/dashboard_service.py`
|
|
||||||
|
|
||||||
|
|
||||||
## 🆕 Recent SEO Enhancements (Reviewed: May 25, 2026)
|
|
||||||
|
|
||||||
The SEO stack has expanded beyond core URL analysis. Based on the current frontend/backend code and latest SEO docs, these additions should be considered part of the current docs-site surface:
|
|
||||||
|
|
||||||
### Dashboard + API enhancements
|
|
||||||
- Strategic insights execution and history (`/api/seo-dashboard/strategic-insights/run`, `/api/seo-dashboard/strategic-insights/history`)
|
|
||||||
- Deep competitor analysis endpoint (`/api/seo-dashboard/deep-competitor-analysis`)
|
|
||||||
- Onboarding task health visibility (`/api/seo-dashboard/onboarding-task-health`)
|
|
||||||
- Operational diagnostics (`/api/seo-dashboard/semantic-health`, `/api/seo-dashboard/sif-health`, `/api/seo-dashboard/cache-stats`)
|
|
||||||
- Route aliases active in app routing: `/seo` and `/seo-dashboard`
|
|
||||||
|
|
||||||
### Workflow enhancements connected to SEO
|
|
||||||
- Blog Writer now includes GSC-powered brainstorming flows (`frontend/src/components/BlogWriter/GSCBrainstormModal.tsx`, `backend/services/gsc_brainstorm_service.py`)
|
|
||||||
- SEO guidance is available both in dashboard workflows and in-editor Blog Writer SEO modules (e.g., `SEOMiniPanel`, `SEOAnalysisModal`, `SEOMetadataModal`)
|
|
||||||
|
|
||||||
### Source docs reviewed for enhancement tracking
|
|
||||||
- `docs/SEO/COMPLETE_SEO_TOOLS_INVENTORY.md`
|
|
||||||
- `docs/SEO/API_REFERENCE.md`
|
|
||||||
- `docs/SEO/PHASE2A_IMPLEMENTATION.md`
|
|
||||||
- `docs/SEO/MIGRATION_STATUS_ANALYSIS.md`
|
|
||||||
- `docs/SEO/MIGRATION_DETAILED_GAPS.md`
|
|
||||||
- `docs/SEO/MIGRATION_EXECUTIVE_SUMMARY.md`
|
|
||||||
|
|||||||
@@ -562,27 +562,3 @@ Analytics & Reporting
|
|||||||
---
|
---
|
||||||
|
|
||||||
**Ready to implement a workflow? Start with [Content Creation Pipeline](#workflow-1-content-creation-pipeline) or see [Individual Tools Guide](individual-tools-guide.md) for tool details!**
|
**Ready to implement a workflow? Start with [Content Creation Pipeline](#workflow-1-content-creation-pipeline) or see [Individual Tools Guide](individual-tools-guide.md) for tool details!**
|
||||||
|
|
||||||
|
|
||||||
## 🆕 Recent SEO Enhancements (Reviewed: May 25, 2026)
|
|
||||||
|
|
||||||
The SEO stack has expanded beyond core URL analysis. Based on the current frontend/backend code and latest SEO docs, these additions should be considered part of the current docs-site surface:
|
|
||||||
|
|
||||||
### Dashboard + API enhancements
|
|
||||||
- Strategic insights execution and history (`/api/seo-dashboard/strategic-insights/run`, `/api/seo-dashboard/strategic-insights/history`)
|
|
||||||
- Deep competitor analysis endpoint (`/api/seo-dashboard/deep-competitor-analysis`)
|
|
||||||
- Onboarding task health visibility (`/api/seo-dashboard/onboarding-task-health`)
|
|
||||||
- Operational diagnostics (`/api/seo-dashboard/semantic-health`, `/api/seo-dashboard/sif-health`, `/api/seo-dashboard/cache-stats`)
|
|
||||||
- Route aliases active in app routing: `/seo` and `/seo-dashboard`
|
|
||||||
|
|
||||||
### Workflow enhancements connected to SEO
|
|
||||||
- Blog Writer now includes GSC-powered brainstorming flows (`frontend/src/components/BlogWriter/GSCBrainstormModal.tsx`, `backend/services/gsc_brainstorm_service.py`)
|
|
||||||
- SEO guidance is available both in dashboard workflows and in-editor Blog Writer SEO modules (e.g., `SEOMiniPanel`, `SEOAnalysisModal`, `SEOMetadataModal`)
|
|
||||||
|
|
||||||
### Source docs reviewed for enhancement tracking
|
|
||||||
- `docs/SEO/COMPLETE_SEO_TOOLS_INVENTORY.md`
|
|
||||||
- `docs/SEO/API_REFERENCE.md`
|
|
||||||
- `docs/SEO/PHASE2A_IMPLEMENTATION.md`
|
|
||||||
- `docs/SEO/MIGRATION_STATUS_ANALYSIS.md`
|
|
||||||
- `docs/SEO/MIGRATION_DETAILED_GAPS.md`
|
|
||||||
- `docs/SEO/MIGRATION_EXECUTIVE_SUMMARY.md`
|
|
||||||
|
|||||||
@@ -238,7 +238,6 @@ nav:
|
|||||||
- Getting Started: features/seo-dashboard/index.md
|
- Getting Started: features/seo-dashboard/index.md
|
||||||
- Quick Start: features/seo-dashboard/quick-start.md
|
- Quick Start: features/seo-dashboard/quick-start.md
|
||||||
- Overview: features/seo-dashboard/overview.md
|
- Overview: features/seo-dashboard/overview.md
|
||||||
- Phase 2A Complete Guide: features/seo-dashboard/phase2a-complete-guide.md
|
|
||||||
- Tools Reference: features/seo-dashboard/tools-reference.md
|
- Tools Reference: features/seo-dashboard/tools-reference.md
|
||||||
- Individual Tools Guide: features/seo-dashboard/individual-tools-guide.md
|
- Individual Tools Guide: features/seo-dashboard/individual-tools-guide.md
|
||||||
- Workflows & Automation: features/seo-dashboard/workflows-guide.md
|
- Workflows & Automation: features/seo-dashboard/workflows-guide.md
|
||||||
@@ -248,9 +247,6 @@ nav:
|
|||||||
- GSC Integration: features/seo-dashboard/gsc-integration.md
|
- GSC Integration: features/seo-dashboard/gsc-integration.md
|
||||||
- Metadata Generation: features/seo-dashboard/metadata.md
|
- Metadata Generation: features/seo-dashboard/metadata.md
|
||||||
- Design Document: features/seo-dashboard/design-document.md
|
- Design Document: features/seo-dashboard/design-document.md
|
||||||
- Phase 2A - Enterprise Suite: features/seo-dashboard/phase2a-enterprise-seo.md
|
|
||||||
- Phase 2A - Advanced GSC: features/seo-dashboard/phase2a-advanced-gsc.md
|
|
||||||
- Phase 2A - LLM Insights: features/seo-dashboard/phase2a-llm-insights.md
|
|
||||||
- Phase 2A Implementation: ../SEO/PHASE2A_IMPLEMENTATION.md
|
- Phase 2A Implementation: ../SEO/PHASE2A_IMPLEMENTATION.md
|
||||||
- Content Strategy:
|
- Content Strategy:
|
||||||
- Overview: features/content-strategy/overview.md
|
- Overview: features/content-strategy/overview.md
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
# Content Strategy Internal Docs
|
|
||||||
|
|
||||||
This folder contains internal design notes, implementation plans, and historical migration artifacts.
|
|
||||||
|
|
||||||
## Source of truth for current status
|
|
||||||
|
|
||||||
Use docs-site first for externally consumed, up-to-date implementation status:
|
|
||||||
- `docs-site/docs/features/content-strategy/implementation-status.md`
|
|
||||||
|
|
||||||
## Guidance
|
|
||||||
|
|
||||||
- Treat roadmap/phase files here as planning or historical context unless explicitly re-verified.
|
|
||||||
- Before executing work from a plan in this folder, verify against current backend modules under:
|
|
||||||
- `backend/api/content_planning/services/content_strategy/`
|
|
||||||
@@ -103,7 +103,6 @@ class GSCBrainstormAPI {
|
|||||||
}
|
}
|
||||||
return apiClient.create({
|
return apiClient.create({
|
||||||
headers: { Authorization: `Bearer ${token}` },
|
headers: { Authorization: `Bearer ${token}` },
|
||||||
timeout: 300000, // 5 minutes — LLM calls via wavespeed can take 2+ minutes
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -98,29 +98,6 @@ export const HeaderBar: React.FC<HeaderBarProps> = ({
|
|||||||
</Typography>
|
</Typography>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
<Box sx={{
|
|
||||||
flex: { xs: '1 1 100%', md: '0 1 auto' },
|
|
||||||
order: { xs: 3, md: 0 },
|
|
||||||
mt: { xs: 0.75, md: 0 },
|
|
||||||
}}>
|
|
||||||
<PhaseNavigation
|
|
||||||
phases={phases}
|
|
||||||
currentPhase={currentPhase}
|
|
||||||
onPhaseClick={onPhaseClick}
|
|
||||||
copilotKitAvailable={copilotKitAvailable}
|
|
||||||
actionHandlers={actionHandlers}
|
|
||||||
researchKeywords={researchKeywords}
|
|
||||||
hasResearch={hasResearch}
|
|
||||||
hasOutline={hasOutline}
|
|
||||||
outlineConfirmed={outlineConfirmed}
|
|
||||||
hasContent={hasContent}
|
|
||||||
contentConfirmed={contentConfirmed}
|
|
||||||
hasSEOAnalysis={hasSEOAnalysis}
|
|
||||||
seoRecommendationsApplied={seoRecommendationsApplied}
|
|
||||||
hasSEOMetadata={hasSEOMetadata}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<Stack direction="row" spacing={1} alignItems="center">
|
<Stack direction="row" spacing={1} alignItems="center">
|
||||||
<HeaderControls colorMode="light" showAlerts={true} showUser={true} />
|
<HeaderControls colorMode="light" showAlerts={true} showUser={true} />
|
||||||
|
|
||||||
@@ -184,6 +161,25 @@ export const HeaderBar: React.FC<HeaderBarProps> = ({
|
|||||||
</Menu>
|
</Menu>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
|
<Box sx={{ mt: 1 }}>
|
||||||
|
<PhaseNavigation
|
||||||
|
phases={phases}
|
||||||
|
currentPhase={currentPhase}
|
||||||
|
onPhaseClick={onPhaseClick}
|
||||||
|
copilotKitAvailable={copilotKitAvailable}
|
||||||
|
actionHandlers={actionHandlers}
|
||||||
|
researchKeywords={researchKeywords}
|
||||||
|
hasResearch={hasResearch}
|
||||||
|
hasOutline={hasOutline}
|
||||||
|
outlineConfirmed={outlineConfirmed}
|
||||||
|
hasContent={hasContent}
|
||||||
|
contentConfirmed={contentConfirmed}
|
||||||
|
hasSEOAnalysis={hasSEOAnalysis}
|
||||||
|
seoRecommendationsApplied={seoRecommendationsApplied}
|
||||||
|
hasSEOMetadata={hasSEOMetadata}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -138,35 +138,6 @@ export const BrainstormButton: React.FC<BrainstormButtonProps> = ({
|
|||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{gscConnected && (
|
|
||||||
<span
|
|
||||||
style={{
|
|
||||||
display: 'inline-flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
gap: '5px',
|
|
||||||
padding: '4px 10px',
|
|
||||||
borderRadius: '12px',
|
|
||||||
fontSize: '12px',
|
|
||||||
fontWeight: 500,
|
|
||||||
color: '#2e7d32',
|
|
||||||
background: '#e8f5e9',
|
|
||||||
border: '1px solid #a5d6a7',
|
|
||||||
whiteSpace: 'nowrap',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
style={{
|
|
||||||
width: '8px',
|
|
||||||
height: '8px',
|
|
||||||
borderRadius: '50%',
|
|
||||||
background: '#4caf50',
|
|
||||||
boxShadow: '0 0 6px #4caf50',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
GSC
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<GSCBrainstormModal
|
<GSCBrainstormModal
|
||||||
open={showModal}
|
open={showModal}
|
||||||
onClose={() => {
|
onClose={() => {
|
||||||
|
|||||||
@@ -110,13 +110,9 @@ export const useGSCBrainstorm = (): UseGSCBrainstormReturn => {
|
|||||||
const result = await gscBrainstormAPI.brainstorm(keywords, siteUrl);
|
const result = await gscBrainstormAPI.brainstorm(keywords, siteUrl);
|
||||||
setBrainstormResult(result);
|
setBrainstormResult(result);
|
||||||
return result;
|
return result;
|
||||||
} catch (error: any) {
|
} catch (error) {
|
||||||
let message = 'Failed to brainstorm topics. Please try again.';
|
const message =
|
||||||
if (error?.response?.data?.detail) {
|
error instanceof Error ? error.message : 'Failed to brainstorm topics. Please try again.';
|
||||||
message = error.response.data.detail;
|
|
||||||
} else if (error instanceof Error) {
|
|
||||||
message = error.message;
|
|
||||||
}
|
|
||||||
setBrainstormError(message);
|
setBrainstormError(message);
|
||||||
return null;
|
return null;
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -174,6 +174,7 @@ export function usePhaseValidation(
|
|||||||
currentPhase,
|
currentPhase,
|
||||||
userSelectedPhase,
|
userSelectedPhase,
|
||||||
setCurrentPhase,
|
setCurrentPhase,
|
||||||
|
oscillationGuardRef,
|
||||||
emptyPhaseId,
|
emptyPhaseId,
|
||||||
research,
|
research,
|
||||||
]);
|
]);
|
||||||
|
|||||||
Reference in New Issue
Block a user