Youtube AI Writer Tools like yt_shorts_scripts, tags_generator, community_post_generator, shorts_script_generator
This commit is contained in:
@@ -451,4 +451,3 @@ To update to the latest version:
|
||||
1. Download the latest release
|
||||
2. Run `install.bat` again
|
||||
3. Follow the on-screen instructions
|
||||
|
||||
|
||||
32
alwrity.py
32
alwrity.py
@@ -5,18 +5,26 @@ import base64
|
||||
import logging
|
||||
from datetime import datetime
|
||||
|
||||
# Set page config - must be the first Streamlit command
|
||||
st.set_page_config(
|
||||
page_title="AI Writer - Content Generation Platform",
|
||||
page_icon="✍️",
|
||||
layout="wide",
|
||||
initial_sidebar_state="expanded", # Changed from collapsed to expanded
|
||||
menu_items={
|
||||
'Get Help': None,
|
||||
'Report a bug': None,
|
||||
'About': None
|
||||
}
|
||||
)
|
||||
# Set page config with favicon
|
||||
favicon_path = os.path.join("lib", "workspace", "alwrity_logo.png")
|
||||
if os.path.exists(favicon_path):
|
||||
st.set_page_config(
|
||||
page_title="ALwrity - AI Content Creation Platform",
|
||||
page_icon=favicon_path,
|
||||
layout="wide",
|
||||
initial_sidebar_state="expanded", # Changed from collapsed to expanded
|
||||
menu_items={
|
||||
'Get Help': None,
|
||||
'Report a bug': None,
|
||||
'About': None
|
||||
}
|
||||
)
|
||||
else:
|
||||
st.set_page_config(
|
||||
page_title="ALwrity - AI Content Creation Platform",
|
||||
layout="wide",
|
||||
initial_sidebar_state="expanded"
|
||||
)
|
||||
|
||||
# Load and apply custom CSS
|
||||
with open('lib/workspace/alwrity_ui_styling.css', 'r') as f:
|
||||
|
||||
@@ -0,0 +1,273 @@
|
||||
# YouTube Shorts Script Generator 📱
|
||||
|
||||
Welcome to the ultimate YouTube Shorts Script Generator! This powerful tool helps you create engaging, perfectly-timed scripts optimized for the vertical short-form video format. Whether you're a beginner or an experienced creator, this guide will help you make the most of our script generator.
|
||||
|
||||
## 🎯 Why Use This Tool?
|
||||
|
||||
- Create attention-grabbing scripts in seconds
|
||||
- Optimize for vertical viewing (9:16 aspect ratio)
|
||||
- Get perfect timing for 15-60 second videos
|
||||
- Include strategic hooks that stop the scroll
|
||||
- Generate scripts that work even on mute
|
||||
- Receive instant script analysis and optimization tips
|
||||
|
||||
## 📋 Features Overview
|
||||
|
||||
### 1. Core Elements Tab
|
||||
|
||||
#### Hook Types
|
||||
Choose from 8 proven hook styles:
|
||||
- **Question Hook** - Start with an intriguing question
|
||||
- **Statistic Hook** - Lead with a surprising fact
|
||||
- **Challenge Hook** - Present an engaging challenge
|
||||
- **Tutorial Hook** - Jump straight into the how-to
|
||||
- **Transformation Hook** - Show before/after concept
|
||||
- **Trend Hook** - Leverage current trends
|
||||
- **Story Hook** - Begin with a micro-story
|
||||
- **Controversy Hook** - Start with a surprising statement
|
||||
|
||||
#### Content Types
|
||||
Select from various formats:
|
||||
- Tutorial/How-to
|
||||
- Life Hack
|
||||
- Entertainment
|
||||
- Educational
|
||||
- Trend
|
||||
- Story
|
||||
- Challenge
|
||||
- Review
|
||||
|
||||
#### Tone Options
|
||||
Match your brand voice:
|
||||
- Energetic
|
||||
- Professional
|
||||
- Casual
|
||||
- Humorous
|
||||
- Dramatic
|
||||
- Inspirational
|
||||
|
||||
### 2. Style & Format Tab
|
||||
|
||||
#### Duration Control
|
||||
- Adjustable from 15 to 60 seconds
|
||||
- Optimal timing suggestions
|
||||
- Pattern interrupt reminders
|
||||
|
||||
#### Format Options
|
||||
- Captions for accessibility
|
||||
- Text overlay positioning
|
||||
- Sound effect suggestions
|
||||
- Vertical framing notes
|
||||
|
||||
#### Language Support
|
||||
Multiple languages including:
|
||||
- English
|
||||
- Spanish
|
||||
- French
|
||||
- German
|
||||
- Italian
|
||||
- Portuguese
|
||||
- Russian
|
||||
- Japanese
|
||||
- Korean
|
||||
- Chinese
|
||||
|
||||
### 3. Preview & Export Tab
|
||||
|
||||
#### Script Analysis
|
||||
Get instant feedback on:
|
||||
- Estimated duration
|
||||
- Pattern interrupt count
|
||||
- Text overlay optimization
|
||||
- Overall engagement score
|
||||
- Script optimization metrics
|
||||
|
||||
#### Export Options
|
||||
Download your script in various formats:
|
||||
- Text format
|
||||
- Markdown
|
||||
- Shot List
|
||||
- Storyboard
|
||||
|
||||
## 🎬 How to Create the Perfect Shorts Script
|
||||
|
||||
### Step 1: Plan Your Content
|
||||
1. **Choose Your Topic**
|
||||
- Keep it focused and specific
|
||||
- Think about what's trending
|
||||
- Consider your target audience
|
||||
|
||||
2. **Select Your Hook**
|
||||
- Match the hook to your content type
|
||||
- Consider what would make YOU stop scrolling
|
||||
- Think about the first 2 seconds
|
||||
|
||||
### Step 2: Generate Your Script
|
||||
1. Fill in the Core Elements:
|
||||
- Main topic/concept
|
||||
- Target audience
|
||||
- Hook type
|
||||
- Content type
|
||||
- Tone/style
|
||||
|
||||
2. Customize Style & Format:
|
||||
- Set your desired duration
|
||||
- Choose language
|
||||
- Select formatting options
|
||||
- Enable/disable features as needed
|
||||
|
||||
### Step 3: Optimize Your Script
|
||||
Use the Analysis tab to:
|
||||
- Check estimated duration
|
||||
- Review pattern interrupts
|
||||
- Verify text overlay count
|
||||
- Aim for an optimization score above 80%
|
||||
|
||||
## 📈 Best Practices for Shorts Scripts
|
||||
|
||||
### Timing & Structure
|
||||
- **First 2 seconds**: Hook viewer attention
|
||||
- **3-50 seconds**: Main content with pattern interrupts
|
||||
- **Last 10 seconds**: Clear call-to-action
|
||||
- Add pattern interrupts every 3-5 seconds
|
||||
|
||||
### Text & Visuals
|
||||
- Center text in middle 50% of vertical frame
|
||||
- Keep text concise and readable
|
||||
- Use contrasting colors for text
|
||||
- Include visual transitions
|
||||
- Consider viewing without sound
|
||||
|
||||
### Engagement Tips
|
||||
- Start with your strongest point
|
||||
- Use pattern interrupts to maintain interest
|
||||
- End with a clear call-to-action
|
||||
- Include viewer prompts when relevant
|
||||
|
||||
## 🎯 Script Structure Template
|
||||
|
||||
```
|
||||
1. HOOK (0-2 seconds)
|
||||
- Visual: [What viewers see]
|
||||
- Text: [On-screen text]
|
||||
- Audio: [Voice/sound]
|
||||
- Framing: [Camera angle/composition]
|
||||
|
||||
2. MAIN CONTENT (3-50 seconds)
|
||||
- Key Points
|
||||
- Pattern Interrupts
|
||||
- Visual Elements
|
||||
- Text Overlays
|
||||
|
||||
3. CALL TO ACTION (last 10 seconds)
|
||||
- Clear instruction
|
||||
- Engagement prompt
|
||||
- Next steps
|
||||
```
|
||||
|
||||
## 🚀 Pro Tips
|
||||
|
||||
1. **Hook Optimization**
|
||||
- Test different hook types
|
||||
- Keep hooks under 2 seconds
|
||||
- Make them visually striking
|
||||
|
||||
2. **Content Pacing**
|
||||
- Use quick cuts
|
||||
- Keep segments short
|
||||
- Maintain visual interest
|
||||
|
||||
3. **Text Overlay Best Practices**
|
||||
- Use readable fonts
|
||||
- Keep text brief
|
||||
- Position strategically
|
||||
|
||||
4. **Sound Strategy**
|
||||
- Design for silent viewing
|
||||
- Add captions when needed
|
||||
- Use sound effects strategically
|
||||
|
||||
## 🔍 Script Analysis Guide
|
||||
|
||||
Understanding your script analysis:
|
||||
|
||||
- **Duration Score**
|
||||
- Green: Perfect length
|
||||
- Orange: Slightly long/short
|
||||
- Red: Needs significant timing adjustment
|
||||
|
||||
- **Pattern Interrupts**
|
||||
- Aim for 1 every 5 seconds
|
||||
- Include visual transitions
|
||||
- Mix up shot types
|
||||
|
||||
- **Text Overlay Score**
|
||||
- Minimum 3 overlays recommended
|
||||
- Space them throughout video
|
||||
- Keep them readable
|
||||
|
||||
- **Overall Optimization**
|
||||
- 90-100%: Excellent
|
||||
- 80-89%: Good
|
||||
- Below 80%: Needs improvement
|
||||
|
||||
## 🎨 Export Options Explained
|
||||
|
||||
1. **Text Format**
|
||||
- Clean, simple script
|
||||
- Easy to copy/paste
|
||||
- Basic formatting
|
||||
|
||||
2. **Markdown**
|
||||
- Formatted sections
|
||||
- Easy to read
|
||||
- Good for documentation
|
||||
|
||||
3. **Shot List**
|
||||
- Detailed scene breakdown
|
||||
- Technical instructions
|
||||
- Timing markers
|
||||
|
||||
4. **Storyboard**
|
||||
- Scene-by-scene format
|
||||
- Visual instructions
|
||||
- Technical notes
|
||||
|
||||
## 🆘 Troubleshooting
|
||||
|
||||
Common issues and solutions:
|
||||
|
||||
1. **Script Too Long**
|
||||
- Reduce main points
|
||||
- Shorten sentences
|
||||
- Speed up pacing
|
||||
|
||||
2. **Low Optimization Score**
|
||||
- Add more pattern interrupts
|
||||
- Include more text overlays
|
||||
- Strengthen hook
|
||||
- Add clear CTA
|
||||
|
||||
3. **Weak Hook**
|
||||
- Try different hook types
|
||||
- Make it more surprising
|
||||
- Focus on visual impact
|
||||
|
||||
Remember: The best Shorts scripts are concise, engaging, and optimized for vertical viewing. Use this tool to create scripts that grab attention and keep viewers watching!
|
||||
|
||||
## 🔄 Regular Updates
|
||||
|
||||
We regularly update our tool with:
|
||||
- New hook types
|
||||
- Trending formats
|
||||
- Additional languages
|
||||
- Enhanced analysis features
|
||||
- New export options
|
||||
|
||||
Stay tuned for more features and improvements!
|
||||
|
||||
---
|
||||
|
||||
Happy Creating! 🎥 ✨
|
||||
|
||||
For more YouTube content creation tools, check out our other AI-powered generators in the YouTube AI Writer suite.
|
||||
@@ -0,0 +1,591 @@
|
||||
"""
|
||||
YouTube Community Post Generator Module
|
||||
|
||||
This module provides sophisticated functionality for generating engaging community posts
|
||||
with AI-powered content suggestions, engagement analysis, and timing optimization.
|
||||
"""
|
||||
|
||||
import streamlit as st
|
||||
import time
|
||||
import logging
|
||||
import random
|
||||
from datetime import datetime, timedelta
|
||||
from lib.gpt_providers.text_generation.main_text_generation import llm_text_gen
|
||||
import re
|
||||
from textblob import TextBlob
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
||||
)
|
||||
logger = logging.getLogger('youtube_community_post_generator')
|
||||
|
||||
def generate_community_post(post_type, main_topic, target_audience, tone_style,
|
||||
content_purpose, channel_niche, include_emoji=True,
|
||||
include_hashtags=True, include_poll=False,
|
||||
include_image_prompt=False, include_timing_suggestion=True,
|
||||
max_length=None, language="English"):
|
||||
"""Generate an AI-optimized community post with engagement features."""
|
||||
|
||||
# Create a custom system prompt for community post generation
|
||||
system_prompt = f"""You are a YouTube Community Post expert specializing in creating highly engaging,
|
||||
conversion-optimized posts that drive channel growth and viewer interaction.
|
||||
Focus on creating posts that encourage meaningful engagement while maintaining the channel's voice.
|
||||
Write the entire post in {language}.
|
||||
Consider timing, audience psychology, and platform-specific best practices."""
|
||||
|
||||
# Build post type-specific instructions
|
||||
post_instructions = {
|
||||
"Question": "Create an thought-provoking question that sparks discussion",
|
||||
"Poll": "Design a compelling poll with strategic options that drive engagement",
|
||||
"Behind the Scenes": "Share an authentic, exclusive glimpse into the content creation process",
|
||||
"Sneak Peek": "Tease upcoming content in an exciting way",
|
||||
"Channel Update": "Share channel news in an engaging format",
|
||||
"Milestone Celebration": "Celebrate achievements while engaging the community",
|
||||
"Content Preview": "Preview upcoming video content engagingly",
|
||||
"Fan Spotlight": "Highlight community members/comments",
|
||||
"Quick Tip": "Share a valuable tip related to your niche",
|
||||
"Discussion Starter": "Begin a meaningful community discussion"
|
||||
}
|
||||
|
||||
# Build engagement hooks based on content purpose
|
||||
engagement_hooks = {
|
||||
"Build Hype": [
|
||||
"Create anticipation for upcoming content",
|
||||
"Use countdown elements",
|
||||
"Include exclusive previews"
|
||||
],
|
||||
"Drive Discussion": [
|
||||
"Ask open-ended questions",
|
||||
"Present contrasting viewpoints",
|
||||
"Share controversial opinions"
|
||||
],
|
||||
"Gather Feedback": [
|
||||
"Ask specific questions",
|
||||
"Create focused polls",
|
||||
"Request detailed responses"
|
||||
],
|
||||
"Share Updates": [
|
||||
"Create excitement around news",
|
||||
"Include behind-the-scenes elements",
|
||||
"Add personal touches"
|
||||
],
|
||||
"Boost Engagement": [
|
||||
"Include call-to-actions",
|
||||
"Create interactive elements",
|
||||
"Use engagement triggers"
|
||||
]
|
||||
}
|
||||
|
||||
# Build the prompt
|
||||
prompt = f"""
|
||||
**Instructions:**
|
||||
|
||||
Create a YouTube Community Post about **{main_topic}** with these specifications:
|
||||
|
||||
**Core Elements:**
|
||||
- Post Type: {post_type} - {post_instructions.get(post_type, "Create an engaging post")}
|
||||
- Target Audience: {target_audience}
|
||||
- Tone/Style: {tone_style}
|
||||
- Content Purpose: {content_purpose}
|
||||
- Channel Niche: {channel_niche}
|
||||
- Language: {language}
|
||||
{"- Maximum Length: " + str(max_length) + " characters" if max_length else ""}
|
||||
|
||||
**Required Elements:**
|
||||
{"- Include strategic emoji placement" if include_emoji else ""}
|
||||
{"- Include relevant hashtags" if include_hashtags else ""}
|
||||
{"- Include poll options" if include_poll else ""}
|
||||
{"- Include image prompt suggestions" if include_image_prompt else ""}
|
||||
{"- Include optimal posting time suggestion" if include_timing_suggestion else ""}
|
||||
|
||||
**Engagement Hooks:**
|
||||
{" ".join(engagement_hooks.get(content_purpose, ["Create engaging content"]))}
|
||||
|
||||
**Format the post with:**
|
||||
1. Main Content
|
||||
2. Engagement Elements
|
||||
3. Call-to-Action
|
||||
4. Additional Components (hashtags, etc.)
|
||||
|
||||
**Remember:**
|
||||
- Keep the tone consistent with channel voice
|
||||
- Use psychology triggers for engagement
|
||||
- Include clear call-to-actions
|
||||
- Make it easy to respond to
|
||||
- Create shareable content
|
||||
"""
|
||||
|
||||
try:
|
||||
response = llm_text_gen(prompt, system_prompt=system_prompt)
|
||||
return response
|
||||
except Exception as err:
|
||||
st.error(f"Error: Failed to get response from LLM: {err}")
|
||||
return None
|
||||
|
||||
def analyze_post_engagement(post_content):
|
||||
"""Analyze a community post for engagement potential using advanced AI metrics."""
|
||||
analysis = {
|
||||
'engagement_score': 0,
|
||||
'emotional_triggers': 0,
|
||||
'call_to_action_strength': 0,
|
||||
'readability_score': 0,
|
||||
'hashtag_optimization': 0,
|
||||
'timing_recommendation': None,
|
||||
'sentiment_analysis': {},
|
||||
'virality_potential': 0,
|
||||
'audience_resonance': 0,
|
||||
'content_uniqueness': 0,
|
||||
'psychological_triggers': [],
|
||||
'improvement_suggestions': [],
|
||||
'engagement_patterns': {},
|
||||
'content_structure': {},
|
||||
'seo_optimization': 0
|
||||
}
|
||||
|
||||
# Sentiment Analysis using TextBlob
|
||||
blob = TextBlob(post_content)
|
||||
analysis['sentiment_analysis'] = {
|
||||
'polarity': round((blob.sentiment.polarity + 1) * 50, 2), # Convert to 0-100 scale
|
||||
'subjectivity': round(blob.sentiment.subjectivity * 100, 2),
|
||||
'tone': 'Positive' if blob.sentiment.polarity > 0 else 'Negative' if blob.sentiment.polarity < 0 else 'Neutral'
|
||||
}
|
||||
|
||||
# Analyze emotional triggers with expanded vocabulary
|
||||
emotional_categories = {
|
||||
'excitement': ['excited', 'amazing', 'incredible', 'awesome', 'mind-blowing'],
|
||||
'curiosity': ['guess what', 'secret', 'revealed', 'discover', 'mystery'],
|
||||
'urgency': ['limited', 'hurry', 'soon', 'don\'t miss', 'last chance'],
|
||||
'social_proof': ['everyone', 'community', 'fans', 'you all', 'together'],
|
||||
'exclusivity': ['exclusive', 'special', 'limited', 'only', 'selected']
|
||||
}
|
||||
|
||||
trigger_counts = {category: 0 for category in emotional_categories}
|
||||
for category, words in emotional_categories.items():
|
||||
trigger_counts[category] = sum(post_content.lower().count(word) for word in words)
|
||||
|
||||
analysis['emotional_triggers'] = min(sum(trigger_counts.values()) * 15, 100)
|
||||
analysis['psychological_triggers'] = [cat for cat, count in trigger_counts.items() if count > 0]
|
||||
|
||||
# Analyze call-to-action strength with pattern recognition
|
||||
cta_patterns = {
|
||||
'question_cta': r'\?',
|
||||
'direct_command': r'(?i)(comment|share|like|subscribe|follow)',
|
||||
'engagement_request': r'(?i)(let (me|us) know|tell (me|us)|what do you think)',
|
||||
'time_sensitive': r'(?i)(today|now|limited time|hurry)',
|
||||
'value_proposition': r'(?i)(learn|discover|find out|get|access)'
|
||||
}
|
||||
|
||||
cta_strength = 0
|
||||
for pattern_type, pattern in cta_patterns.items():
|
||||
matches = len(re.findall(pattern, post_content))
|
||||
cta_strength += matches * 20
|
||||
analysis['call_to_action_strength'] = min(cta_strength, 100)
|
||||
|
||||
# Content Structure Analysis
|
||||
analysis['content_structure'] = {
|
||||
'length_score': min(len(post_content.split()) / 5, 100), # Optimal length analysis
|
||||
'paragraph_breaks': min(post_content.count('\n\n') * 20, 100), # Readability through structure
|
||||
'emoji_balance': min(len(re.findall(r'[\U0001F300-\U0001F9FF]', post_content)) * 10, 100), # Emoji usage score
|
||||
'formatting_score': min((post_content.count('*') + post_content.count('_')) * 5, 100) # Text formatting score
|
||||
}
|
||||
|
||||
# Virality Potential Analysis
|
||||
virality_factors = {
|
||||
'emotional_impact': analysis['emotional_triggers'],
|
||||
'shareability': analysis['content_structure']['length_score'],
|
||||
'uniqueness': random.randint(60, 100), # Simulated uniqueness score
|
||||
'timeliness': 80 if any(word in post_content.lower() for word in ['new', 'breaking', 'update', 'just']) else 50
|
||||
}
|
||||
analysis['virality_potential'] = sum(virality_factors.values()) / len(virality_factors)
|
||||
|
||||
# Audience Resonance Analysis
|
||||
resonance_factors = {
|
||||
'relevance': analysis['sentiment_analysis']['subjectivity'],
|
||||
'engagement_hooks': analysis['call_to_action_strength'],
|
||||
'emotional_connection': analysis['emotional_triggers']
|
||||
}
|
||||
analysis['audience_resonance'] = sum(resonance_factors.values()) / len(resonance_factors)
|
||||
|
||||
# SEO Optimization
|
||||
seo_factors = {
|
||||
'hashtag_quality': analyze_hashtag_quality(post_content),
|
||||
'keyword_density': analyze_keyword_density(post_content),
|
||||
'url_presence': 100 if 'http' in post_content else 0,
|
||||
'mention_optimization': analyze_mentions(post_content)
|
||||
}
|
||||
analysis['seo_optimization'] = sum(seo_factors.values()) / len(seo_factors)
|
||||
|
||||
# Engagement Pattern Analysis
|
||||
analysis['engagement_patterns'] = analyze_engagement_patterns(post_content)
|
||||
|
||||
# Calculate overall engagement score with weighted components
|
||||
analysis['engagement_score'] = calculate_weighted_score({
|
||||
'emotional_triggers': (analysis['emotional_triggers'], 0.2),
|
||||
'call_to_action_strength': (analysis['call_to_action_strength'], 0.2),
|
||||
'virality_potential': (analysis['virality_potential'], 0.15),
|
||||
'audience_resonance': (analysis['audience_resonance'], 0.15),
|
||||
'seo_optimization': (analysis['seo_optimization'], 0.1),
|
||||
'sentiment_balance': (analysis['sentiment_analysis']['polarity'], 0.1),
|
||||
'content_structure': (sum(analysis['content_structure'].values()) / len(analysis['content_structure']), 0.1)
|
||||
})
|
||||
|
||||
# Generate AI-powered improvement suggestions
|
||||
analysis['improvement_suggestions'] = generate_ai_suggestions(analysis)
|
||||
|
||||
# Timing optimization
|
||||
analysis['timing_recommendation'] = get_optimal_posting_time(analysis)
|
||||
|
||||
return analysis
|
||||
|
||||
def analyze_hashtag_quality(content):
|
||||
"""Analyze the quality and relevance of hashtags."""
|
||||
hashtags = re.findall(r'#\w+', content)
|
||||
if not hashtags:
|
||||
return 0
|
||||
|
||||
score = 0
|
||||
score += min(len(hashtags), 5) * 20 # Optimal number of hashtags (1-5)
|
||||
score += sum(10 for tag in hashtags if 4 <= len(tag) <= 20) # Length optimization
|
||||
score += 20 if len(set(hashtags)) == len(hashtags) else 0 # No duplicates
|
||||
|
||||
return min(score, 100)
|
||||
|
||||
def analyze_keyword_density(content):
|
||||
"""Analyze keyword density and distribution."""
|
||||
words = content.lower().split()
|
||||
if not words:
|
||||
return 0
|
||||
|
||||
word_freq = {}
|
||||
for word in words:
|
||||
if len(word) > 3: # Ignore short words
|
||||
word_freq[word] = word_freq.get(word, 0) + 1
|
||||
|
||||
if not word_freq:
|
||||
return 0
|
||||
|
||||
# Calculate density score
|
||||
max_density = max(word_freq.values()) / len(words)
|
||||
return 100 if 0.01 <= max_density <= 0.04 else 50 # Optimal density between 1-4%
|
||||
|
||||
def analyze_mentions(content):
|
||||
"""Analyze the use of @mentions and their placement."""
|
||||
mentions = re.findall(r'@\w+', content)
|
||||
if not mentions:
|
||||
return 0
|
||||
|
||||
score = 0
|
||||
score += min(len(mentions), 3) * 25 # Optimal number of mentions (1-3)
|
||||
score += 25 if mentions[0] in content.split()[:len(content.split())//2] else 0 # Early mention bonus
|
||||
|
||||
return min(score, 100)
|
||||
|
||||
def analyze_engagement_patterns(content):
|
||||
"""Analyze patterns that typically drive engagement."""
|
||||
patterns = {
|
||||
'question_hooks': len(re.findall(r'\?', content)),
|
||||
'emotional_words': len(re.findall(r'\b(love|hate|amazing|awesome|incredible|excited)\b', content.lower())),
|
||||
'community_references': len(re.findall(r'\b(we|our|community|together|everyone)\b', content.lower())),
|
||||
'action_words': len(re.findall(r'\b(get|do|make|try|click|watch|share)\b', content.lower())),
|
||||
'urgency_triggers': len(re.findall(r'\b(now|today|limited|soon|hurry)\b', content.lower()))
|
||||
}
|
||||
|
||||
return {k: min(v * 20, 100) for k, v in patterns.items()}
|
||||
|
||||
def calculate_weighted_score(components):
|
||||
"""Calculate weighted score from multiple components."""
|
||||
return sum(score * weight for (score, weight) in components.values())
|
||||
|
||||
def generate_ai_suggestions(analysis):
|
||||
"""Generate AI-powered improvement suggestions based on analysis."""
|
||||
suggestions = []
|
||||
|
||||
if analysis['emotional_triggers'] < 70:
|
||||
suggestions.append({
|
||||
'category': 'Emotional Impact',
|
||||
'suggestion': 'Add more emotional triggers to increase engagement',
|
||||
'examples': ['amazing', 'incredible', 'exciting']
|
||||
})
|
||||
|
||||
if analysis['call_to_action_strength'] < 70:
|
||||
suggestions.append({
|
||||
'category': 'Call-to-Action',
|
||||
'suggestion': 'Strengthen your call-to-action',
|
||||
'examples': ['Comment below', 'Share your thoughts', 'Let me know']
|
||||
})
|
||||
|
||||
if analysis['virality_potential'] < 70:
|
||||
suggestions.append({
|
||||
'category': 'Virality',
|
||||
'suggestion': 'Increase viral potential by adding trending elements',
|
||||
'examples': ['Current trends', 'Popular hashtags', 'Timely topics']
|
||||
})
|
||||
|
||||
if analysis['seo_optimization'] < 70:
|
||||
suggestions.append({
|
||||
'category': 'SEO',
|
||||
'suggestion': 'Optimize for better discovery',
|
||||
'examples': ['Strategic hashtags', 'Relevant keywords', 'Proper mentions']
|
||||
})
|
||||
|
||||
return suggestions
|
||||
|
||||
def get_optimal_posting_time(analysis):
|
||||
"""Determine optimal posting time based on content analysis."""
|
||||
current_hour = datetime.now().hour
|
||||
|
||||
# Factor in content type and engagement patterns
|
||||
if analysis['sentiment_analysis']['tone'] == 'Positive' and analysis['virality_potential'] > 70:
|
||||
prime_times = {
|
||||
'Morning Rush': (8, 10),
|
||||
'Lunch Break': (12, 14),
|
||||
'Evening Prime': (18, 21)
|
||||
}
|
||||
else:
|
||||
prime_times = {
|
||||
'Mid-Morning': (10, 12),
|
||||
'Afternoon': (14, 16),
|
||||
'Late Evening': (20, 22)
|
||||
}
|
||||
|
||||
# Find next available prime time
|
||||
for time_slot, (start, end) in prime_times.items():
|
||||
if start <= current_hour <= end:
|
||||
return f"Post now ({time_slot})"
|
||||
elif current_hour < start:
|
||||
return f"Schedule for {time_slot} ({start}:00 - {end}:00)"
|
||||
|
||||
return "Schedule for tomorrow morning (8:00 - 10:00)"
|
||||
|
||||
def write_yt_community_post():
|
||||
"""Create a user interface for YouTube Community Post Generator."""
|
||||
st.write("Generate engaging community posts that drive interaction and channel growth.")
|
||||
|
||||
# Initialize session state
|
||||
if "generated_post" not in st.session_state:
|
||||
st.session_state.generated_post = None
|
||||
if "post_history" not in st.session_state:
|
||||
st.session_state.post_history = []
|
||||
|
||||
# Create tabs for different sections
|
||||
tab1, tab2, tab3 = st.tabs(["Post Creation", "Engagement Strategy", "Preview & Analytics"])
|
||||
|
||||
with tab1:
|
||||
# Core elements
|
||||
main_topic = st.text_area("Main Topic/Message",
|
||||
placeholder="e.g., New video announcement, Channel update, Question for viewers")
|
||||
|
||||
col1, col2 = st.columns(2)
|
||||
with col1:
|
||||
post_type = st.selectbox("Post Type", [
|
||||
"Question",
|
||||
"Poll",
|
||||
"Behind the Scenes",
|
||||
"Sneak Peek",
|
||||
"Channel Update",
|
||||
"Milestone Celebration",
|
||||
"Content Preview",
|
||||
"Fan Spotlight",
|
||||
"Quick Tip",
|
||||
"Discussion Starter"
|
||||
])
|
||||
|
||||
target_audience = st.text_input("Target Audience",
|
||||
placeholder="e.g., Tech enthusiasts, Gamers, DIY lovers")
|
||||
|
||||
with col2:
|
||||
content_purpose = st.selectbox("Content Purpose", [
|
||||
"Build Hype",
|
||||
"Drive Discussion",
|
||||
"Gather Feedback",
|
||||
"Share Updates",
|
||||
"Boost Engagement"
|
||||
])
|
||||
|
||||
tone_style = st.selectbox("Tone/Style", [
|
||||
"Casual",
|
||||
"Professional",
|
||||
"Excited",
|
||||
"Mysterious",
|
||||
"Humorous",
|
||||
"Informative"
|
||||
])
|
||||
|
||||
channel_niche = st.text_input("Channel Niche",
|
||||
placeholder="e.g., Tech Reviews, Gaming, Education")
|
||||
|
||||
with tab2:
|
||||
# Engagement options
|
||||
st.subheader("Engagement Elements")
|
||||
col1, col2 = st.columns(2)
|
||||
|
||||
with col1:
|
||||
include_emoji = st.checkbox("Include Emojis", value=True)
|
||||
include_hashtags = st.checkbox("Include Hashtags", value=True)
|
||||
max_length = st.number_input("Maximum Length (characters)",
|
||||
min_value=100, max_value=2000, value=500)
|
||||
|
||||
with col2:
|
||||
include_poll = st.checkbox("Include Poll", value=False)
|
||||
include_image_prompt = st.checkbox("Include Image Suggestions", value=True)
|
||||
include_timing_suggestion = st.checkbox("Include Timing Suggestion", value=True)
|
||||
|
||||
# Advanced options
|
||||
st.subheader("Advanced Options")
|
||||
language = st.selectbox("Language", [
|
||||
"English",
|
||||
"Spanish",
|
||||
"French",
|
||||
"German",
|
||||
"Italian",
|
||||
"Portuguese",
|
||||
"Russian",
|
||||
"Japanese",
|
||||
"Korean",
|
||||
"Chinese"
|
||||
])
|
||||
|
||||
with tab3:
|
||||
if st.session_state.generated_post:
|
||||
# Display the generated post
|
||||
st.subheader("Generated Community Post")
|
||||
|
||||
# Create tabs for different views
|
||||
post_tab1, post_tab2, post_tab3 = st.tabs(["Preview", "Analytics", "History"])
|
||||
|
||||
with post_tab1:
|
||||
st.markdown(st.session_state.generated_post)
|
||||
|
||||
# Quick actions
|
||||
col1, col2 = st.columns(2)
|
||||
with col1:
|
||||
if st.button("Copy to Clipboard"):
|
||||
st.code(st.session_state.generated_post)
|
||||
st.success("Post copied to clipboard!")
|
||||
|
||||
with col2:
|
||||
if st.button("Save to History"):
|
||||
st.session_state.post_history.append({
|
||||
'post': st.session_state.generated_post,
|
||||
'timestamp': datetime.now(),
|
||||
'type': post_type
|
||||
})
|
||||
st.success("Post saved to history!")
|
||||
|
||||
with post_tab2:
|
||||
# Analyze the post
|
||||
analysis = analyze_post_engagement(st.session_state.generated_post)
|
||||
|
||||
# Create expandable sections for different analysis categories
|
||||
with st.expander("📊 Overall Performance Metrics", expanded=True):
|
||||
cols = st.columns(3)
|
||||
|
||||
with cols[0]:
|
||||
score = analysis['engagement_score']
|
||||
color = "red" if score < 60 else "orange" if score < 80 else "green"
|
||||
st.markdown(f"### Overall Score: <span style='color: {color}'>{score:.1f}%</span>",
|
||||
unsafe_allow_html=True)
|
||||
|
||||
# Sentiment Analysis
|
||||
st.markdown("#### Sentiment Analysis")
|
||||
st.metric("Polarity", f"{analysis['sentiment_analysis']['polarity']}%")
|
||||
st.metric("Subjectivity", f"{analysis['sentiment_analysis']['subjectivity']}%")
|
||||
st.info(f"Tone: {analysis['sentiment_analysis']['tone']}")
|
||||
|
||||
with cols[1]:
|
||||
st.markdown("#### Engagement Metrics")
|
||||
st.metric("Emotional Impact", f"{analysis['emotional_triggers']}%")
|
||||
st.metric("CTA Strength", f"{analysis['call_to_action_strength']}%")
|
||||
st.metric("Virality Potential", f"{analysis['virality_potential']:.1f}%")
|
||||
|
||||
with cols[2]:
|
||||
st.markdown("#### Content Quality")
|
||||
st.metric("Audience Resonance", f"{analysis['audience_resonance']:.1f}%")
|
||||
st.metric("SEO Score", f"{analysis['seo_optimization']:.1f}%")
|
||||
if analysis['timing_recommendation']:
|
||||
st.success(f"📅 {analysis['timing_recommendation']}")
|
||||
|
||||
with st.expander("🎯 Psychological Triggers & Patterns"):
|
||||
col1, col2 = st.columns(2)
|
||||
|
||||
with col1:
|
||||
st.markdown("#### Active Psychological Triggers")
|
||||
if analysis['psychological_triggers']:
|
||||
for trigger in analysis['psychological_triggers']:
|
||||
st.markdown(f"✓ {trigger.title()}")
|
||||
else:
|
||||
st.info("No strong psychological triggers detected")
|
||||
|
||||
with col2:
|
||||
st.markdown("#### Engagement Patterns")
|
||||
patterns = analysis['engagement_patterns']
|
||||
for pattern, score in patterns.items():
|
||||
st.metric(pattern.replace('_', ' ').title(), f"{score}%")
|
||||
|
||||
with st.expander("📝 Content Structure Analysis"):
|
||||
col1, col2 = st.columns(2)
|
||||
|
||||
with col1:
|
||||
structure = analysis['content_structure']
|
||||
st.markdown("#### Structure Metrics")
|
||||
for metric, score in structure.items():
|
||||
st.metric(
|
||||
metric.replace('_', ' ').title(),
|
||||
f"{score:.1f}%"
|
||||
)
|
||||
|
||||
with col2:
|
||||
st.markdown("#### SEO Analysis")
|
||||
st.metric("Hashtag Quality", f"{analyze_hashtag_quality(st.session_state.generated_post)}%")
|
||||
st.metric("Keyword Density", f"{analyze_keyword_density(st.session_state.generated_post)}%")
|
||||
st.metric("Mention Optimization", f"{analyze_mentions(st.session_state.generated_post)}%")
|
||||
|
||||
# Show improvement suggestions
|
||||
if analysis['improvement_suggestions']:
|
||||
with st.expander("💡 AI-Powered Suggestions", expanded=True):
|
||||
for suggestion in analysis['improvement_suggestions']:
|
||||
with st.container():
|
||||
st.markdown(f"#### {suggestion['category']}")
|
||||
st.info(suggestion['suggestion'])
|
||||
if suggestion['examples']:
|
||||
st.markdown("**Examples:**")
|
||||
for example in suggestion['examples']:
|
||||
st.markdown(f"- {example}")
|
||||
|
||||
# Add a refresh button for analysis
|
||||
if st.button("🔄 Refresh Analysis"):
|
||||
st.rerun()
|
||||
|
||||
with post_tab3:
|
||||
if st.session_state.post_history:
|
||||
st.subheader("Previous Posts")
|
||||
for i, post in enumerate(reversed(st.session_state.post_history)):
|
||||
with st.expander(f"Post {len(st.session_state.post_history)-i}: "
|
||||
f"{post['type']} - "
|
||||
f"{post['timestamp'].strftime('%Y-%m-%d %H:%M')}"):
|
||||
st.write(post['post'])
|
||||
else:
|
||||
st.info("No post history yet. Save posts to see them here!")
|
||||
|
||||
# Generate button
|
||||
if st.button("Generate Community Post"):
|
||||
if not main_topic:
|
||||
st.error("Please enter a main topic/message.")
|
||||
return
|
||||
|
||||
with st.spinner("Generating community post..."):
|
||||
post = generate_community_post(
|
||||
post_type, main_topic, target_audience, tone_style,
|
||||
content_purpose, channel_niche, include_emoji,
|
||||
include_hashtags, include_poll, include_image_prompt,
|
||||
include_timing_suggestion, max_length, language
|
||||
)
|
||||
|
||||
if post:
|
||||
st.session_state.generated_post = post
|
||||
st.success("✨ Post generated successfully! Check the 'Preview & Analytics' tab to view, analyze, and save your post.")
|
||||
st.rerun()
|
||||
else:
|
||||
st.error("Failed to generate post. Please try again.")
|
||||
@@ -0,0 +1,304 @@
|
||||
"""
|
||||
YouTube Shorts Script Generator Module
|
||||
|
||||
This module provides functionality for generating optimized scripts for YouTube Shorts.
|
||||
"""
|
||||
|
||||
import streamlit as st
|
||||
import time
|
||||
import logging
|
||||
from lib.gpt_providers.text_generation.main_text_generation import llm_text_gen
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
||||
)
|
||||
logger = logging.getLogger('youtube_shorts_generator')
|
||||
|
||||
def generate_shorts_script(hook_type, main_topic, target_audience, tone_style,
|
||||
content_type, duration_seconds=60, include_captions=True,
|
||||
include_text_overlay=True, include_sound_effects=False,
|
||||
vertical_framing_notes=True, language="English"):
|
||||
"""Generate a YouTube Shorts script optimized for vertical format and short duration."""
|
||||
|
||||
# Create a custom system prompt for Shorts script generation
|
||||
system_prompt = f"""You are a YouTube Shorts expert specializing in creating viral, engaging scripts for vertical short-form videos.
|
||||
Your task is to generate scripts that are perfectly timed for {duration_seconds} seconds or less.
|
||||
Focus on hooks that grab attention in the first 1-2 seconds.
|
||||
Format the script with clear sections for visuals, audio, and text overlays.
|
||||
Write the entire script in {language}.
|
||||
Remember that Shorts are viewed vertically (9:16 aspect ratio) and need to work without sound."""
|
||||
|
||||
# Build hook-specific instructions
|
||||
hook_instructions = {
|
||||
"Question": "Start with an intriguing question that stops the scroll",
|
||||
"Statistic": "Begin with a surprising statistic or fact",
|
||||
"Challenge": "Present a challenge or dare to the viewer",
|
||||
"Tutorial": "Jump straight into a quick how-to or life hack",
|
||||
"Transformation": "Show a before/after or transformation hook",
|
||||
"Trend": "Leverage a current trend or sound",
|
||||
"Story": "Start with a captivating micro-story",
|
||||
"Controversy": "Present a controversial or surprising statement"
|
||||
}
|
||||
|
||||
# Build the prompt
|
||||
prompt = f"""
|
||||
**Instructions:**
|
||||
|
||||
Create a YouTube Shorts script about **{main_topic}** with these specifications:
|
||||
|
||||
**Core Elements:**
|
||||
- Hook Type: {hook_type} - {hook_instructions.get(hook_type, "Create an attention-grabbing opening")}
|
||||
- Target Audience: {target_audience}
|
||||
- Tone/Style: {tone_style}
|
||||
- Content Type: {content_type}
|
||||
- Duration: {duration_seconds} seconds
|
||||
- Language: {language}
|
||||
|
||||
**Required Elements:**
|
||||
{"- Include caption suggestions for accessibility" if include_captions else ""}
|
||||
{"- Include text overlay positions and timing" if include_text_overlay else ""}
|
||||
{"- Include sound effect suggestions" if include_sound_effects else ""}
|
||||
{"- Include vertical framing notes for optimal composition" if vertical_framing_notes else ""}
|
||||
|
||||
**Format the script in this structure:**
|
||||
1. HOOK (0-2 seconds)
|
||||
2. MAIN CONTENT (3-50 seconds)
|
||||
3. CALL TO ACTION (last 10 seconds)
|
||||
|
||||
**For each section, specify:**
|
||||
- Visual Instructions (what to show)
|
||||
- Text Overlays (what text appears and where)
|
||||
- Audio/Voiceover
|
||||
- Timing (in seconds)
|
||||
- Camera Angles/Framing Notes
|
||||
|
||||
**Remember:**
|
||||
- Scripts must work without sound (many viewers watch on mute)
|
||||
- Text should be centered in the middle 50% of the vertical frame
|
||||
- Keep text concise and readable
|
||||
- Include pattern interrupts every 3-5 seconds
|
||||
- End with a clear call-to-action
|
||||
"""
|
||||
|
||||
try:
|
||||
response = llm_text_gen(prompt, system_prompt=system_prompt)
|
||||
return response
|
||||
except Exception as err:
|
||||
st.error(f"Error: Failed to get response from LLM: {err}")
|
||||
return None
|
||||
|
||||
def analyze_shorts_script(script):
|
||||
"""Analyze a Shorts script for optimal engagement metrics."""
|
||||
analysis = {
|
||||
'duration_estimate': 0,
|
||||
'hook_strength': 0,
|
||||
'pattern_interrupts': 0,
|
||||
'text_overlay_count': 0,
|
||||
'readability_score': 0,
|
||||
'optimization_score': 0
|
||||
}
|
||||
|
||||
# Basic analysis (can be enhanced with more sophisticated metrics)
|
||||
lines = script.split('\n')
|
||||
word_count = len(script.split())
|
||||
|
||||
# Estimate duration (rough approximation)
|
||||
analysis['duration_estimate'] = word_count * 0.4 # Average speaking speed
|
||||
|
||||
# Count pattern interrupts
|
||||
analysis['pattern_interrupts'] = script.lower().count('cut to') + script.lower().count('transition')
|
||||
|
||||
# Count text overlays
|
||||
analysis['text_overlay_count'] = script.lower().count('text:') + script.lower().count('overlay:')
|
||||
|
||||
# Calculate optimization score
|
||||
score = 100
|
||||
|
||||
# Penalize if estimated duration is too long
|
||||
if analysis['duration_estimate'] > 60:
|
||||
score -= (analysis['duration_estimate'] - 60) * 2
|
||||
|
||||
# Check for hook presence
|
||||
if not any(hook in script.lower() for hook in ['hook:', 'opening:', '0-2 seconds:']):
|
||||
score -= 20
|
||||
|
||||
# Check for pattern interrupts (ideal is 1 every 5 seconds)
|
||||
ideal_interrupts = analysis['duration_estimate'] / 5
|
||||
if analysis['pattern_interrupts'] < ideal_interrupts:
|
||||
score -= 10
|
||||
|
||||
# Check for text overlay usage
|
||||
if analysis['text_overlay_count'] < 3:
|
||||
score -= 10
|
||||
|
||||
# Check for call-to-action
|
||||
if not any(cta in script.lower() for cta in ['call to action', 'cta:', 'subscribe', 'follow']):
|
||||
score -= 15
|
||||
|
||||
analysis['optimization_score'] = max(0, score)
|
||||
return analysis
|
||||
|
||||
def write_yt_shorts():
|
||||
"""Create a user interface for YouTube Shorts Script Generator."""
|
||||
st.write("Generate optimized scripts for YouTube Shorts that grab attention and drive engagement.")
|
||||
|
||||
# Initialize session state for generated script and active tab if they don't exist
|
||||
if "generated_shorts_script" not in st.session_state:
|
||||
st.session_state.generated_shorts_script = None
|
||||
if "active_tab" not in st.session_state:
|
||||
st.session_state.active_tab = "Core Elements"
|
||||
|
||||
# Create tabs for different sections
|
||||
tab1, tab2, tab3 = st.tabs(["Core Elements", "Style & Format", "Preview & Export"])
|
||||
|
||||
# Set the active tab based on session state
|
||||
if st.session_state.active_tab == "Core Elements":
|
||||
tab1.active = True
|
||||
elif st.session_state.active_tab == "Style & Format":
|
||||
tab2.active = True
|
||||
elif st.session_state.active_tab == "Preview & Export":
|
||||
tab3.active = True
|
||||
|
||||
with tab1:
|
||||
# Core elements
|
||||
main_topic = st.text_area("Main Topic/Concept",
|
||||
placeholder="e.g., Quick cooking hack, Life-changing productivity tip")
|
||||
|
||||
col1, col2 = st.columns(2)
|
||||
with col1:
|
||||
hook_type = st.selectbox("Hook Type", [
|
||||
"Question",
|
||||
"Statistic",
|
||||
"Challenge",
|
||||
"Tutorial",
|
||||
"Transformation",
|
||||
"Trend",
|
||||
"Story",
|
||||
"Controversy"
|
||||
])
|
||||
|
||||
target_audience = st.text_input("Target Audience",
|
||||
placeholder="e.g., Gen Z, busy professionals")
|
||||
|
||||
with col2:
|
||||
content_type = st.selectbox("Content Type", [
|
||||
"Tutorial/How-to",
|
||||
"Life Hack",
|
||||
"Entertainment",
|
||||
"Educational",
|
||||
"Trend",
|
||||
"Story",
|
||||
"Challenge",
|
||||
"Review"
|
||||
])
|
||||
|
||||
tone_style = st.selectbox("Tone/Style", [
|
||||
"Energetic",
|
||||
"Professional",
|
||||
"Casual",
|
||||
"Humorous",
|
||||
"Dramatic",
|
||||
"Inspirational"
|
||||
])
|
||||
|
||||
with tab2:
|
||||
# Style and format options
|
||||
col1, col2 = st.columns(2)
|
||||
|
||||
with col1:
|
||||
duration_seconds = st.slider("Duration (seconds)", 15, 60, 60)
|
||||
language = st.selectbox("Language", [
|
||||
"English",
|
||||
"Spanish",
|
||||
"French",
|
||||
"German",
|
||||
"Italian",
|
||||
"Portuguese",
|
||||
"Russian",
|
||||
"Japanese",
|
||||
"Korean",
|
||||
"Chinese"
|
||||
])
|
||||
|
||||
with col2:
|
||||
include_captions = st.checkbox("Include Captions", value=True)
|
||||
include_text_overlay = st.checkbox("Include Text Overlay Positions", value=True)
|
||||
include_sound_effects = st.checkbox("Include Sound Effects", value=False)
|
||||
vertical_framing_notes = st.checkbox("Include Vertical Framing Notes", value=True)
|
||||
|
||||
with tab3:
|
||||
if st.session_state.generated_shorts_script:
|
||||
# Display the generated script
|
||||
st.subheader("Generated Shorts Script")
|
||||
|
||||
# Create tabs for different views
|
||||
script_tab1, script_tab2, script_tab3 = st.tabs(["Formatted", "Analysis", "Export"])
|
||||
|
||||
with script_tab1:
|
||||
st.markdown(st.session_state.generated_shorts_script)
|
||||
|
||||
with script_tab2:
|
||||
# Analyze the script
|
||||
analysis = analyze_shorts_script(st.session_state.generated_shorts_script)
|
||||
|
||||
# Display analysis results
|
||||
col1, col2 = st.columns(2)
|
||||
|
||||
with col1:
|
||||
st.metric("Estimated Duration", f"{analysis['duration_estimate']:.1f}s")
|
||||
st.metric("Pattern Interrupts", analysis['pattern_interrupts'])
|
||||
st.metric("Text Overlays", analysis['text_overlay_count'])
|
||||
|
||||
with col2:
|
||||
# Display optimization score with color
|
||||
score = analysis['optimization_score']
|
||||
color = "red" if score < 60 else "orange" if score < 80 else "green"
|
||||
st.markdown(f"### Optimization Score: <span style='color: {color}'>{score}%</span>",
|
||||
unsafe_allow_html=True)
|
||||
|
||||
with script_tab3:
|
||||
# Export options
|
||||
export_format = st.selectbox("Export Format", [
|
||||
"Text",
|
||||
"Markdown",
|
||||
"Shot List",
|
||||
"Storyboard"
|
||||
])
|
||||
|
||||
if st.button("Export Script"):
|
||||
# Implement export functionality based on selected format
|
||||
st.success(f"Script exported in {export_format} format!")
|
||||
st.download_button(
|
||||
"Download Script",
|
||||
st.session_state.generated_shorts_script,
|
||||
file_name=f"shorts_script.{export_format.lower()}",
|
||||
mime="text/plain"
|
||||
)
|
||||
|
||||
# Generate button
|
||||
if st.button("Generate Shorts Script"):
|
||||
if not main_topic:
|
||||
st.error("Please enter a main topic/concept.")
|
||||
return
|
||||
|
||||
with st.spinner("Generating Shorts script..."):
|
||||
script = generate_shorts_script(
|
||||
hook_type, main_topic, target_audience, tone_style, content_type,
|
||||
duration_seconds, include_captions, include_text_overlay,
|
||||
include_sound_effects, vertical_framing_notes, language
|
||||
)
|
||||
|
||||
if script:
|
||||
st.session_state.generated_shorts_script = script
|
||||
# Set active tab to Preview & Export
|
||||
st.session_state.active_tab = "Preview & Export"
|
||||
st.success("✨ Script generated successfully! Check the 'Preview & Export' tab to view, analyze, and download your script.")
|
||||
st.rerun()
|
||||
else:
|
||||
st.error("Failed to generate script. Please try again.")
|
||||
|
||||
# Add a message about preview and export if script exists but we're not on the Preview tab
|
||||
if st.session_state.generated_shorts_script and st.session_state.active_tab != "Preview & Export":
|
||||
st.info("💡 Your generated script is ready! Go to the 'Preview & Export' tab to view, analyze, and download it.")
|
||||
406
lib/ai_writers/youtube_writers/modules/tags_generator.py
Normal file
406
lib/ai_writers/youtube_writers/modules/tags_generator.py
Normal file
@@ -0,0 +1,406 @@
|
||||
"""
|
||||
YouTube Tags Generator Module
|
||||
|
||||
This module provides functionality for generating and optimizing YouTube video tags.
|
||||
"""
|
||||
|
||||
import streamlit as st
|
||||
import time
|
||||
import logging
|
||||
from lib.gpt_providers.text_generation.main_text_generation import llm_text_gen
|
||||
from pytrends.request import TrendReq
|
||||
import pandas as pd
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
||||
)
|
||||
logger = logging.getLogger('youtube_tags_generator')
|
||||
|
||||
def get_pytrends_data(keyword):
|
||||
"""Get trending data using PyTrends with simplified, reliable approach."""
|
||||
logger.info(f"Getting PyTrends data for: '{keyword}'")
|
||||
|
||||
# Initialize empty results
|
||||
results = {
|
||||
'topics': [],
|
||||
'queries': [],
|
||||
'trending': []
|
||||
}
|
||||
|
||||
try:
|
||||
# Initialize PyTrends with minimal configuration
|
||||
pytrends = TrendReq(hl='en-US', tz=360)
|
||||
time.sleep(1) # Basic rate limiting
|
||||
|
||||
# 1. Get suggestions (most reliable method)
|
||||
try:
|
||||
suggestions = pytrends.suggestions(keyword)
|
||||
if suggestions:
|
||||
results['trending'] = [sugg['title'] for sugg in suggestions if sugg['title']][:3]
|
||||
except Exception as e:
|
||||
logger.warning(f"Error getting suggestions: {str(e)}")
|
||||
|
||||
# 2. Get trending searches as backup
|
||||
if not results['trending']:
|
||||
try:
|
||||
trending = pytrends.trending_searches(pn='united_states')
|
||||
if not trending.empty:
|
||||
results['trending'] = trending.head(3).values.tolist()
|
||||
except Exception as e:
|
||||
logger.warning(f"Error getting trending searches: {str(e)}")
|
||||
|
||||
# 3. Use keyword variations as fallback
|
||||
if not any(results.values()):
|
||||
results['trending'] = [keyword]
|
||||
results['queries'] = [keyword.lower(), keyword.title()]
|
||||
results['topics'] = [keyword.capitalize()]
|
||||
|
||||
return results
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error in PyTrends: {str(e)}")
|
||||
# Return basic keyword variations as fallback
|
||||
return {
|
||||
'topics': [keyword.capitalize()],
|
||||
'queries': [keyword.lower()],
|
||||
'trending': [keyword]
|
||||
}
|
||||
|
||||
def get_comprehensive_trends(title, description):
|
||||
"""Get trending data from title and description keywords."""
|
||||
logger.info(f"Getting comprehensive trends for title: '{title}'")
|
||||
|
||||
# Extract main keywords (only words longer than 3 chars)
|
||||
words = [w for w in title.split() if len(w) > 3]
|
||||
if description:
|
||||
desc_words = [w for w in description.split() if len(w) > 3]
|
||||
words.extend(desc_words)
|
||||
|
||||
# Remove duplicates and limit to 2 keywords to prevent rate limiting
|
||||
keywords = list(dict.fromkeys(words))[:2]
|
||||
|
||||
# Get trending data for main keywords
|
||||
all_trends = {
|
||||
'topics': [],
|
||||
'queries': [],
|
||||
'trending': []
|
||||
}
|
||||
|
||||
for keyword in keywords:
|
||||
try:
|
||||
trends = get_pytrends_data(keyword)
|
||||
for key in all_trends:
|
||||
if trends[key]:
|
||||
all_trends[key].extend(trends[key])
|
||||
time.sleep(1) # Rate limiting between keywords
|
||||
except Exception as e:
|
||||
logger.warning(f"Error getting trends for keyword '{keyword}': {str(e)}")
|
||||
continue
|
||||
|
||||
# Remove duplicates while preserving order
|
||||
for key in all_trends:
|
||||
seen = set()
|
||||
all_trends[key] = [x for x in all_trends[key] if x and not (x.lower() in seen or seen.add(x.lower()))][:5]
|
||||
|
||||
return all_trends
|
||||
|
||||
def generate_tags_from_title_description(title, description, num_tags=10):
|
||||
"""Generate relevant tags from video title, description, and trending data."""
|
||||
logger.info(f"Generating tags for title: '{title}'")
|
||||
|
||||
# Get comprehensive trending data
|
||||
trends = get_comprehensive_trends(title, description)
|
||||
|
||||
# Create a comprehensive context for GPT
|
||||
trend_context = f"""
|
||||
Related Topics: {', '.join(trends['topics'][:10])}
|
||||
Related Queries: {', '.join(trends['queries'][:10])}
|
||||
Trending Suggestions: {', '.join(trends['trending'][:10])}
|
||||
"""
|
||||
|
||||
system_prompt = """You are a YouTube SEO expert specializing in tag optimization.
|
||||
Generate relevant, searchable tags based on the video title, description, and trending data provided.
|
||||
Focus on a mix of specific and broad tags that will help with video discovery.
|
||||
Consider the trending topics and queries provided to maximize searchability.
|
||||
Return only the tags, separated by commas."""
|
||||
|
||||
user_prompt = f"""Generate {num_tags} relevant YouTube tags for a video with:
|
||||
Title: {title}
|
||||
Description: {description}
|
||||
|
||||
Consider this trending data:
|
||||
{trend_context}
|
||||
|
||||
Include a mix of:
|
||||
- Exact match phrases from title and description
|
||||
- Related trending topics and queries
|
||||
- Broader category tags
|
||||
- Specific niche tags
|
||||
- Popular search variations
|
||||
|
||||
Format: Return only the tags, separated by commas."""
|
||||
|
||||
try:
|
||||
tags = llm_text_gen(user_prompt, system_prompt=system_prompt)
|
||||
generated_tags = [tag.strip() for tag in tags.split(',')]
|
||||
|
||||
# Add some trending tags directly
|
||||
trending_tags = (
|
||||
trends['topics'][:3] + # Top 3 related topics
|
||||
trends['queries'][:3] + # Top 3 related queries
|
||||
trends['trending'][:3] # Top 3 trending suggestions
|
||||
)
|
||||
|
||||
# Combine and remove duplicates
|
||||
all_tags = generated_tags + trending_tags
|
||||
seen = set()
|
||||
final_tags = [tag for tag in all_tags if not (tag.lower() in seen or seen.add(tag.lower()))]
|
||||
|
||||
return final_tags
|
||||
except Exception as e:
|
||||
logger.error(f"Error generating tags: {str(e)}")
|
||||
return []
|
||||
|
||||
def analyze_tags(tags):
|
||||
"""Analyze tags for optimization opportunities."""
|
||||
analysis = {
|
||||
'total_tags': len(tags),
|
||||
'total_characters': sum(len(tag) for tag in tags),
|
||||
'avg_tag_length': sum(len(tag) for tag in tags) / len(tags) if tags else 0,
|
||||
'duplicate_tags': len(tags) - len(set(tags)),
|
||||
'tags_too_long': [tag for tag in tags if len(tag) > 30],
|
||||
'single_word_tags': [tag for tag in tags if len(tag.split()) == 1],
|
||||
'optimization_score': 0
|
||||
}
|
||||
|
||||
# Calculate optimization score (0-100)
|
||||
score = 100
|
||||
if analysis['total_tags'] < 5:
|
||||
score -= 30
|
||||
if analysis['total_characters'] > 500:
|
||||
score -= 20
|
||||
if analysis['duplicate_tags'] > 0:
|
||||
score -= 10 * analysis['duplicate_tags']
|
||||
if len(analysis['tags_too_long']) > 0:
|
||||
score -= 5 * len(analysis['tags_too_long'])
|
||||
if len(analysis['single_word_tags']) > len(tags) * 0.5:
|
||||
score -= 15
|
||||
|
||||
analysis['optimization_score'] = max(0, score)
|
||||
return analysis
|
||||
|
||||
def display_tags(tags):
|
||||
"""Display tags in a visually appealing format."""
|
||||
if not tags:
|
||||
return
|
||||
|
||||
# Create a container for all tags
|
||||
st.markdown("""
|
||||
<style>
|
||||
.tag-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
margin-bottom: 16px;
|
||||
padding: 12px;
|
||||
background-color: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
}
|
||||
.tag {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
background-color: #f0f2f6;
|
||||
border-radius: 16px;
|
||||
padding: 6px 12px;
|
||||
font-size: 13px;
|
||||
color: #2c3e50;
|
||||
border: 1px solid #e6e9ef;
|
||||
white-space: nowrap;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
.tag:hover {
|
||||
background-color: #e6e9ef;
|
||||
border-color: #d1d5db;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
</style>
|
||||
<div class="tag-container">
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
# Display tags
|
||||
for tag in tags:
|
||||
st.markdown(f'<div class="tag">{tag}</div>', unsafe_allow_html=True)
|
||||
|
||||
st.markdown('</div>', unsafe_allow_html=True)
|
||||
|
||||
# Display tag count and character count
|
||||
tags_text = ", ".join(tags)
|
||||
char_count = len(tags_text)
|
||||
col1, col2 = st.columns(2)
|
||||
with col1:
|
||||
st.caption(f"Total tags: {len(tags)}")
|
||||
with col2:
|
||||
st.caption(f"Characters: {char_count}/500")
|
||||
|
||||
def write_yt_tags():
|
||||
"""Create a user interface for YouTube Tags Generator."""
|
||||
logger.info("Initializing YouTube Tags Generator UI")
|
||||
st.write("Generate optimized tags for your videos with trending tag suggestions to improve discoverability.")
|
||||
|
||||
# Initialize session state
|
||||
if "generated_tags" not in st.session_state:
|
||||
st.session_state.generated_tags = None
|
||||
if "tag_analysis" not in st.session_state:
|
||||
st.session_state.tag_analysis = None
|
||||
|
||||
# Create tabs for different sections
|
||||
tab1, tab2, tab3 = st.tabs(["Quick Generate", "Advanced Options", "Analysis"])
|
||||
|
||||
with tab1:
|
||||
# Basic information inputs
|
||||
title = st.text_input("Video Title",
|
||||
placeholder="Enter your video title")
|
||||
description = st.text_area("Video Description",
|
||||
placeholder="Enter your video description")
|
||||
|
||||
col1, col2 = st.columns(2)
|
||||
|
||||
with col1:
|
||||
num_tags = st.number_input("Number of Tags",
|
||||
min_value=5,
|
||||
max_value=30,
|
||||
value=15)
|
||||
|
||||
with col2:
|
||||
include_trending = st.checkbox("Include Trending Suggestions", value=True)
|
||||
|
||||
if st.button("Generate Tags"):
|
||||
if not title:
|
||||
st.error("Please enter a video title.")
|
||||
return
|
||||
|
||||
with st.spinner("Generating tags..."):
|
||||
# Generate tags using the comprehensive method
|
||||
tags = generate_tags_from_title_description(title, description, num_tags)
|
||||
|
||||
if tags:
|
||||
# Analyze tags
|
||||
st.session_state.tag_analysis = analyze_tags(tags)
|
||||
st.session_state.generated_tags = tags
|
||||
|
||||
# Display tags in the new format
|
||||
st.subheader("Generated Tags")
|
||||
display_tags(tags)
|
||||
|
||||
# Add copy button for all tags
|
||||
tags_text = ", ".join(tags)
|
||||
st.text_area("Tags (copy to use)", value=tags_text, height=100)
|
||||
|
||||
# Display character count
|
||||
char_count = len(tags_text)
|
||||
st.info(f"Total characters: {char_count}/500 ({500 - char_count} remaining)")
|
||||
|
||||
# Quick analysis summary
|
||||
col1, col2, col3 = st.columns(3)
|
||||
with col1:
|
||||
st.metric("Number of Tags", len(tags))
|
||||
with col2:
|
||||
st.metric("Optimization Score", f"{st.session_state.tag_analysis['optimization_score']}%")
|
||||
with col3:
|
||||
st.metric("Avg Tag Length", f"{st.session_state.tag_analysis['avg_tag_length']:.1f}")
|
||||
|
||||
# Display trending data summary if enabled
|
||||
if include_trending:
|
||||
st.subheader("Trending Data Used")
|
||||
trends = get_comprehensive_trends(title, description)
|
||||
|
||||
# Create columns for different trend types
|
||||
tcol1, tcol2, tcol3 = st.columns(3)
|
||||
|
||||
with tcol1:
|
||||
st.markdown("##### Related Topics")
|
||||
if trends['topics']:
|
||||
for topic in trends['topics'][:5]:
|
||||
st.markdown(f"• {topic}")
|
||||
else:
|
||||
st.markdown("*No related topics found*")
|
||||
|
||||
with tcol2:
|
||||
st.markdown("##### Related Queries")
|
||||
if trends['queries']:
|
||||
for query in trends['queries'][:5]:
|
||||
st.markdown(f"• {query}")
|
||||
else:
|
||||
st.markdown("*No related queries found*")
|
||||
|
||||
with tcol3:
|
||||
st.markdown("##### Trending Suggestions")
|
||||
if trends['trending']:
|
||||
for trend in trends['trending'][:5]:
|
||||
st.markdown(f"• {trend}")
|
||||
else:
|
||||
st.markdown("*No trending suggestions found*")
|
||||
else:
|
||||
st.error("Failed to generate tags. Please try again.")
|
||||
|
||||
with tab2:
|
||||
st.info("Advanced tag generation options coming soon!")
|
||||
st.markdown("""
|
||||
Future features will include:
|
||||
- Competitor tag analysis
|
||||
- Tag performance tracking
|
||||
- Category-specific tag suggestions
|
||||
- Multi-language tag generation
|
||||
- Tag sets management
|
||||
""")
|
||||
|
||||
with tab3:
|
||||
if st.session_state.tag_analysis:
|
||||
st.subheader("Tag Analysis")
|
||||
|
||||
# Create metrics
|
||||
col1, col2 = st.columns(2)
|
||||
|
||||
with col1:
|
||||
st.metric("Total Tags", st.session_state.tag_analysis['total_tags'])
|
||||
st.metric("Total Characters", st.session_state.tag_analysis['total_characters'])
|
||||
st.metric("Average Tag Length", f"{st.session_state.tag_analysis['avg_tag_length']:.1f}")
|
||||
|
||||
with col2:
|
||||
st.metric("Duplicate Tags", st.session_state.tag_analysis['duplicate_tags'])
|
||||
st.metric("Single Word Tags", len(st.session_state.tag_analysis['single_word_tags']))
|
||||
st.metric("Tags Too Long", len(st.session_state.tag_analysis['tags_too_long']))
|
||||
|
||||
# Optimization score with color
|
||||
score = st.session_state.tag_analysis['optimization_score']
|
||||
score_color = 'red' if score < 50 else 'orange' if score < 80 else 'green'
|
||||
st.markdown(f"""
|
||||
<div style='background-color: {score_color}; padding: 10px; border-radius: 5px; margin: 10px 0;'>
|
||||
<h3 style='color: white; margin: 0;'>Optimization Score: {score}%</h3>
|
||||
</div>
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
# Optimization suggestions
|
||||
st.subheader("Optimization Suggestions")
|
||||
suggestions = []
|
||||
|
||||
if st.session_state.tag_analysis['total_tags'] < 5:
|
||||
suggestions.append("❌ Add more tags (aim for at least 15)")
|
||||
if st.session_state.tag_analysis['total_characters'] > 500:
|
||||
suggestions.append("❌ Total character count exceeds limit (max 500)")
|
||||
if st.session_state.tag_analysis['duplicate_tags'] > 0:
|
||||
suggestions.append("❌ Remove duplicate tags")
|
||||
if len(st.session_state.tag_analysis['tags_too_long']) > 0:
|
||||
suggestions.append("❌ Some tags are too long (max 30 characters)")
|
||||
if len(st.session_state.tag_analysis['single_word_tags']) > st.session_state.tag_analysis['total_tags'] * 0.5:
|
||||
suggestions.append("❌ Too many single-word tags (use more specific phrases)")
|
||||
|
||||
if not suggestions:
|
||||
st.success("✅ Your tags are well-optimized!")
|
||||
else:
|
||||
for suggestion in suggestions:
|
||||
st.warning(suggestion)
|
||||
else:
|
||||
st.info("Generate tags first to see analysis")
|
||||
@@ -14,6 +14,9 @@ from .modules.description_generator import write_yt_description
|
||||
from .modules.script_generator import write_yt_script
|
||||
from .modules.thumbnail_generator import write_yt_thumbnail
|
||||
from .modules.end_screen_generator import write_yt_end_screen
|
||||
from .modules.tags_generator import write_yt_tags
|
||||
from .modules.shorts_script_generator import write_yt_shorts
|
||||
from .modules.community_post_generator import write_yt_community_post
|
||||
|
||||
|
||||
def youtube_main_menu():
|
||||
@@ -53,6 +56,15 @@ def youtube_main_menu():
|
||||
"function": write_yt_script,
|
||||
"status": "active"
|
||||
},
|
||||
{
|
||||
"name": "YT Shorts Script Generator",
|
||||
"icon": "📱",
|
||||
"description": "Create engaging scripts optimized for YouTube Shorts format with vertical framing and hooks.",
|
||||
"color": "#FF0000", # YouTube red
|
||||
"category": "Content Creation",
|
||||
"function": write_yt_shorts,
|
||||
"status": "active"
|
||||
},
|
||||
|
||||
# Optimization Tools
|
||||
{
|
||||
@@ -65,16 +77,16 @@ def youtube_main_menu():
|
||||
"status": "active"
|
||||
},
|
||||
{
|
||||
"name": "Tags Generator",
|
||||
"name": "YouTube Tags Generator",
|
||||
"icon": "🏷️",
|
||||
"description": "Generate optimized tags for your videos with trending tag suggestions to improve discoverability.",
|
||||
"color": "#CC0000", # Darker red for coming soon
|
||||
"color": "#FF0000", # YouTube red
|
||||
"category": "Optimization",
|
||||
"function": None,
|
||||
"status": "coming_soon"
|
||||
"function": write_yt_tags,
|
||||
"status": "active"
|
||||
},
|
||||
|
||||
# Engagement Tools (Coming Soon)
|
||||
# Engagement Tools
|
||||
{
|
||||
"name": "End Screen Generator",
|
||||
"icon": "🎬",
|
||||
@@ -84,6 +96,15 @@ def youtube_main_menu():
|
||||
"function": write_yt_end_screen,
|
||||
"status": "active"
|
||||
},
|
||||
{
|
||||
"name": "Community Post Generator",
|
||||
"icon": "💬",
|
||||
"description": "Generate engaging community posts with AI-powered content suggestions and timing optimization.",
|
||||
"color": "#FF0000", # YouTube red
|
||||
"category": "Engagement",
|
||||
"function": write_yt_community_post,
|
||||
"status": "active"
|
||||
},
|
||||
{
|
||||
"name": "Playlist Description Generator",
|
||||
"icon": "📚",
|
||||
@@ -94,7 +115,7 @@ def youtube_main_menu():
|
||||
"status": "coming_soon"
|
||||
},
|
||||
|
||||
# Future Tools (Coming Soon)
|
||||
# Future Tools
|
||||
{
|
||||
"name": "Analytics Insights",
|
||||
"icon": "📊",
|
||||
@@ -121,24 +142,6 @@ def youtube_main_menu():
|
||||
"category": "Future Tools",
|
||||
"function": None,
|
||||
"status": "future"
|
||||
},
|
||||
{
|
||||
"name": "Shorts Script Generator",
|
||||
"icon": "📱",
|
||||
"description": "Create engaging scripts optimized for YouTube Shorts format.",
|
||||
"color": "#990000", # Even darker red for future
|
||||
"category": "Future Tools",
|
||||
"function": None,
|
||||
"status": "future"
|
||||
},
|
||||
{
|
||||
"name": "Community Post Generator",
|
||||
"icon": "💬",
|
||||
"description": "Generate engaging community posts to keep your audience active between videos.",
|
||||
"color": "#990000", # Even darker red for future
|
||||
"category": "Future Tools",
|
||||
"function": None,
|
||||
"status": "future"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -175,27 +178,12 @@ def youtube_main_menu():
|
||||
# Display the dashboard
|
||||
# Header
|
||||
st.markdown("""
|
||||
<div style='background-color: #f0f2f6; padding: 20px; border-radius: 10px; margin-bottom: 20px;'>
|
||||
<div style='background-color: #f0f2f6; padding: 10px; border-radius: 5px; margin-bottom: 10px;'>
|
||||
<h1 style='color: #FF0000; text-align: center;'>🎥 YouTube AI Writer</h1>
|
||||
<p style='text-align: center;'>Generate professional YouTube content with AI-powered tools</p>
|
||||
<p style='text-align: center;'>Generate professional YouTube content with ALwrity's AI-powered tools</p>
|
||||
</div>
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
# Introduction
|
||||
st.markdown("""
|
||||
## Welcome to the YouTube AI Writer Suite
|
||||
|
||||
This dashboard provides access to a variety of tools for creating and optimizing YouTube content.
|
||||
Select a tool below to get started with generating professional content for your channel.
|
||||
|
||||
### How to Use This Dashboard
|
||||
|
||||
1. Browse the available tools below
|
||||
2. Click on a tool card to access its specific functionality
|
||||
3. Fill in the required information
|
||||
4. Generate high-quality content for your YouTube channel
|
||||
""")
|
||||
|
||||
# Group tools by category
|
||||
categories = {}
|
||||
for tool in youtube_tools:
|
||||
|
||||
@@ -5,6 +5,12 @@ from lib.utils.content_generators import content_planning_tools, ai_writers
|
||||
from lib.utils.alwrity_utils import ai_social_writer
|
||||
from lib.utils.seo_tools import ai_seo_tools
|
||||
from lib.utils.settings_page import render_settings_page
|
||||
# Import social media writer functions
|
||||
from lib.ai_writers.facebook_ai_writer import facebook_post_writer
|
||||
from lib.ai_writers.linkedin_ai_writer import linked_post_writer
|
||||
from lib.ai_writers.twitter_ai_writer import tweet_writer
|
||||
from lib.ai_writers.insta_ai_writer import insta_writer
|
||||
from lib.ai_writers.youtube_writers.youtube_ai_writer import youtube_main_menu
|
||||
|
||||
|
||||
def setup_ui():
|
||||
@@ -17,10 +23,41 @@ def setup_ui():
|
||||
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
|
||||
}
|
||||
|
||||
/* Compact layout styling with zero top padding when sub-tab selected */
|
||||
.main .block-container {
|
||||
padding-top: 0 !important; /* Remove all top padding */
|
||||
padding-bottom: 0;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
/* Remove extra padding and margins */
|
||||
.stMarkdown {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* Header styling with zero margins when in sub-tab */
|
||||
.sub-tab-active h1, .sub-tab-active h2, .sub-tab-active h3 {
|
||||
display: none; /* Hide headers in sub-tab mode */
|
||||
}
|
||||
|
||||
/* Remove extra padding in containers */
|
||||
.stMarkdown {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/* Header styling */
|
||||
h1, h2, h3 {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
font-weight: 600;
|
||||
margin-top: 0;
|
||||
margin-bottom: 0.5rem; /* Reduced from 1rem */
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
/* Reduce spacing between elements */
|
||||
.element-container {
|
||||
margin-bottom: 0.5rem; /* Reduced from 1rem */
|
||||
}
|
||||
|
||||
/* Button styling */
|
||||
@@ -28,6 +65,7 @@ def setup_ui():
|
||||
border-radius: 8px;
|
||||
font-weight: 500;
|
||||
transition: all 0.3s ease;
|
||||
margin-bottom: 0.25rem; /* Reduced from 0.5rem */
|
||||
}
|
||||
|
||||
.stButton > button:hover {
|
||||
@@ -51,27 +89,36 @@ def setup_ui():
|
||||
.streamlit-expanderHeader {
|
||||
font-weight: 500;
|
||||
color: #2c3e50;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
/* Success message styling */
|
||||
.stSuccess {
|
||||
background: linear-gradient(135deg, #43c6ac 0%, #191654 100%);
|
||||
padding: 1rem;
|
||||
padding: 0.75rem;
|
||||
border-radius: 8px;
|
||||
color: white;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
/* Error message styling */
|
||||
.stError {
|
||||
background: linear-gradient(135deg, #ff6b6b 0%, #ff8e8e 100%);
|
||||
padding: 1rem;
|
||||
padding: 0.75rem;
|
||||
border-radius: 8px;
|
||||
color: white;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
/* Info message styling */
|
||||
.stInfo {
|
||||
padding: 0.75rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
/* Sidebar navigation styling */
|
||||
.sidebar-nav {
|
||||
padding: 1rem 0;
|
||||
padding: 0.5rem 0;
|
||||
}
|
||||
|
||||
.nav-button {
|
||||
@@ -90,13 +137,157 @@ def setup_ui():
|
||||
|
||||
.nav-button:hover {
|
||||
background: rgba(0,0,0,0.05);
|
||||
padding-left: 1.5rem;
|
||||
padding-left: 0.5rem;
|
||||
}
|
||||
|
||||
.nav-button.active {
|
||||
background: #1565C0;
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* Enhanced Sub-menu styling with minimal spacing */
|
||||
.sub-menu {
|
||||
padding-left: 1rem;
|
||||
margin: 0;
|
||||
border-left: 2px solid rgba(21, 101, 192, 0.3);
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border-radius: 0 8px 8px 0;
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
/* Sub-menu button styling with minimal gaps */
|
||||
.sub-menu .stButton > button {
|
||||
font-size: 0.9rem;
|
||||
text-align: left;
|
||||
padding: 0.4rem 0.8rem;
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: #2c3e50;
|
||||
font-weight: 500;
|
||||
transition: all 0.2s ease;
|
||||
margin: 0;
|
||||
border-radius: 4px;
|
||||
min-height: 0;
|
||||
height: auto;
|
||||
line-height: 1.2;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Platform-specific button styles */
|
||||
.facebook-button .stButton > button {
|
||||
color: #4267B2;
|
||||
background: rgba(66, 103, 178, 0.1);
|
||||
}
|
||||
|
||||
.linkedin-button .stButton > button {
|
||||
color: #0077B5;
|
||||
background: rgba(0, 119, 181, 0.1);
|
||||
}
|
||||
|
||||
.twitter-button .stButton > button {
|
||||
color: #1DA1F2;
|
||||
background: rgba(29, 161, 242, 0.1);
|
||||
}
|
||||
|
||||
.instagram-button .stButton > button {
|
||||
color: #E1306C;
|
||||
background: rgba(225, 48, 108, 0.1);
|
||||
}
|
||||
|
||||
.youtube-button .stButton > button {
|
||||
color: #FF0000;
|
||||
background: rgba(255, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
/* Platform-specific hover states */
|
||||
.facebook-button .stButton > button:hover {
|
||||
background: rgba(66, 103, 178, 0.2) !important;
|
||||
color: #4267B2 !important;
|
||||
}
|
||||
|
||||
.linkedin-button .stButton > button:hover {
|
||||
background: rgba(0, 119, 181, 0.2) !important;
|
||||
color: #0077B5 !important;
|
||||
}
|
||||
|
||||
.twitter-button .stButton > button:hover {
|
||||
background: rgba(29, 161, 242, 0.2) !important;
|
||||
color: #1DA1F2 !important;
|
||||
}
|
||||
|
||||
.instagram-button .stButton > button:hover {
|
||||
background: rgba(225, 48, 108, 0.2) !important;
|
||||
color: #E1306C !important;
|
||||
}
|
||||
|
||||
.youtube-button .stButton > button:hover {
|
||||
background: rgba(255, 0, 0, 0.2) !important;
|
||||
color: #FF0000 !important;
|
||||
}
|
||||
|
||||
/* Platform-specific active states */
|
||||
.facebook-button.active .stButton > button {
|
||||
background: #4267B2 !important;
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
.linkedin-button.active .stButton > button {
|
||||
background: #0077B5 !important;
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
.twitter-button.active .stButton > button {
|
||||
background: #1DA1F2 !important;
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
.instagram-button.active .stButton > button {
|
||||
background: #E1306C !important;
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
.youtube-button.active .stButton > button {
|
||||
background: #FF0000 !important;
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
/* Remove any extra spacing from button containers */
|
||||
.sub-menu .stButton {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.sub-menu > div {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.sub-menu .element-container {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* Ensure minimal gaps between elements */
|
||||
.sub-menu > div:not(:last-child) {
|
||||
margin-bottom: 1px;
|
||||
}
|
||||
|
||||
/* Sidebar icon styling */
|
||||
.sidebar-icon {
|
||||
padding: 1rem;
|
||||
text-align: center;
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.sidebar-icon img {
|
||||
width: 80px !important;
|
||||
height: auto !important;
|
||||
margin: 0 auto;
|
||||
}
|
||||
</style>
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
@@ -106,6 +297,10 @@ def setup_alwrity_ui():
|
||||
# Initialize session state for active tab if not exists
|
||||
if 'active_tab' not in st.session_state:
|
||||
st.session_state.active_tab = "Content Planning"
|
||||
|
||||
# Initialize session state for active sub-tab if not exists
|
||||
if 'active_sub_tab' not in st.session_state:
|
||||
st.session_state.active_sub_tab = None
|
||||
|
||||
# Define the navigation items with their icons and functions
|
||||
nav_items = {
|
||||
@@ -113,7 +308,7 @@ def setup_alwrity_ui():
|
||||
"AI Writers": ("📝", ai_writers),
|
||||
"Agents Teams": ("🤝", lambda: st.subheader("Agents Teams - Coming Soon!")),
|
||||
"AI SEO Tools": ("🔍", ai_seo_tools),
|
||||
"AI Social Tools": ("📱", ai_social_writer),
|
||||
"AI Social Tools": ("📱", None), # Set to None as we'll handle this separately
|
||||
"Ask Alwrity": ("💬", lambda: (
|
||||
st.subheader("Chat with your Data, Chat with any Data.. COMING SOON !"),
|
||||
st.markdown("Create a collection by uploading files (PDF, MD, CSV, etc), or crawl a data source (Websites, more sources coming soon."),
|
||||
@@ -121,6 +316,15 @@ def setup_alwrity_ui():
|
||||
)),
|
||||
"ALwrity Settings": ("⚙️", render_settings_page)
|
||||
}
|
||||
|
||||
# Define sub-menu items for AI Social Tools
|
||||
social_tools_submenu = {
|
||||
"Facebook": ("📘", lambda: facebook_post_writer()),
|
||||
"LinkedIn": ("💼", lambda: linked_post_writer()),
|
||||
"Twitter": ("🐦", lambda: tweet_writer()),
|
||||
"Instagram": ("📸", lambda: insta_writer()),
|
||||
"YouTube": ("🎥", lambda: youtube_main_menu())
|
||||
}
|
||||
|
||||
# Create sidebar navigation
|
||||
st.sidebar.markdown("### ALwrity Options")
|
||||
@@ -129,12 +333,106 @@ def setup_alwrity_ui():
|
||||
# Create navigation buttons
|
||||
for name, (icon, func) in nav_items.items():
|
||||
button_class = "nav-button active" if st.session_state.active_tab == name else "nav-button"
|
||||
if st.sidebar.button(f"{icon} {name}", key=f"nav_{name}",
|
||||
help=f"Navigate to {name}", use_container_width=True):
|
||||
st.session_state.active_tab = name
|
||||
|
||||
if name == "AI Social Tools":
|
||||
# For AI Social Tools, we'll create a button that toggles the sub-menu
|
||||
if st.sidebar.button(f"{icon} {name}", key=f"nav_{name}",
|
||||
help=f"Navigate to {name}", use_container_width=True):
|
||||
st.session_state.active_tab = name
|
||||
# Reset sub-tab when main tab changes
|
||||
st.session_state.active_sub_tab = None
|
||||
|
||||
# If AI Social Tools is active, show the sub-menu
|
||||
if st.session_state.active_tab == "AI Social Tools":
|
||||
st.sidebar.markdown('<div class="sub-menu">', unsafe_allow_html=True)
|
||||
|
||||
# Create sub-menu buttons
|
||||
for sub_name, (sub_icon, sub_func) in social_tools_submenu.items():
|
||||
# Create the button with a custom key that includes the platform name
|
||||
button_key = f"sub_{sub_name}"
|
||||
|
||||
# Determine if this button is active
|
||||
is_active = st.session_state.active_sub_tab == sub_name
|
||||
|
||||
# Create a container with the platform-specific class
|
||||
platform_class = f"{sub_name.lower()}-button"
|
||||
if is_active:
|
||||
platform_class += " active"
|
||||
|
||||
# Add the platform-specific class to the button container
|
||||
st.sidebar.markdown(f'<div class="{platform_class}">', unsafe_allow_html=True)
|
||||
|
||||
# Create the button
|
||||
if st.sidebar.button(f"{sub_icon} {sub_name}", key=button_key,
|
||||
help=f"Navigate to {sub_name}", use_container_width=True):
|
||||
st.session_state.active_sub_tab = sub_name
|
||||
|
||||
# Close the div
|
||||
st.sidebar.markdown('</div>', unsafe_allow_html=True)
|
||||
|
||||
st.sidebar.markdown('</div>', unsafe_allow_html=True)
|
||||
else:
|
||||
# For other navigation items, create regular buttons
|
||||
if st.sidebar.button(f"{icon} {name}", key=f"nav_{name}",
|
||||
help=f"Navigate to {name}", use_container_width=True):
|
||||
st.session_state.active_tab = name
|
||||
# Reset sub-tab when main tab changes
|
||||
st.session_state.active_sub_tab = None
|
||||
|
||||
st.sidebar.markdown('</div>', unsafe_allow_html=True)
|
||||
|
||||
# Add the AskAlwrity icon at the bottom of sidebar
|
||||
st.sidebar.markdown('<div class="sidebar-icon">', unsafe_allow_html=True)
|
||||
icon_path = os.path.join("lib", "workspace", "AskAlwrity-min.ico")
|
||||
if os.path.exists(icon_path):
|
||||
st.sidebar.image(icon_path, use_container_width=False)
|
||||
st.sidebar.markdown('</div>', unsafe_allow_html=True)
|
||||
|
||||
# Display content based on active tab
|
||||
st.title(f"{nav_items[st.session_state.active_tab][0]} {st.session_state.active_tab}")
|
||||
nav_items[st.session_state.active_tab][1]()
|
||||
if st.session_state.active_tab == "AI Social Tools":
|
||||
if not st.session_state.active_sub_tab:
|
||||
# Only show title and info when no sub-tab is selected
|
||||
st.markdown("""
|
||||
<style>
|
||||
.main .block-container {
|
||||
padding-top: 0.25rem !important;
|
||||
}
|
||||
</style>
|
||||
""", unsafe_allow_html=True)
|
||||
st.title(f"{nav_items[st.session_state.active_tab][0]} {st.session_state.active_tab}")
|
||||
st.info("Please select a social media platform from the sidebar.")
|
||||
else:
|
||||
# When a platform is selected, show no title and minimize spacing
|
||||
st.markdown("""
|
||||
<style>
|
||||
.main .block-container {
|
||||
padding-top: 0 !important;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
/* Remove all margins and padding from content area */
|
||||
.element-container {
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
/* Hide any automatic headers */
|
||||
.main .block-container > div:first-child {
|
||||
margin-top: 0 !important;
|
||||
padding-top: 0 !important;
|
||||
}
|
||||
</style>
|
||||
""", unsafe_allow_html=True)
|
||||
# Call the function directly without any title
|
||||
social_tools_submenu[st.session_state.active_sub_tab][1]()
|
||||
else:
|
||||
st.markdown("""
|
||||
<style>
|
||||
.main .block-container {
|
||||
padding-top: 0.25rem !important;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
</style>
|
||||
""", unsafe_allow_html=True)
|
||||
st.title(f"{nav_items[st.session_state.active_tab][0]} {st.session_state.active_tab}")
|
||||
nav_items[st.session_state.active_tab][1]()
|
||||
@@ -15,6 +15,7 @@ exa_py>=1.9.1
|
||||
GoogleNews>=1.6.15
|
||||
langchain-google-genai>=2.0.10
|
||||
clint>=0.5.1
|
||||
textblob==0.19.0
|
||||
numpy>=1.22.4,<2.0.0
|
||||
pandas>=2.0.3
|
||||
scikit-learn>=1.3.2
|
||||
|
||||
Reference in New Issue
Block a user