diff --git a/lib/ai_web_researcher/gpt_online_researcher.py b/lib/ai_web_researcher/gpt_online_researcher.py index 0719680f..f301ca48 100644 --- a/lib/ai_web_researcher/gpt_online_researcher.py +++ b/lib/ai_web_researcher/gpt_online_researcher.py @@ -38,7 +38,7 @@ from lib.alwrity_ui.display_google_serp_results import ( ) from lib.alwrity_ui.google_trends_ui import display_google_trends_data, process_trends_data -from .tavily_ai_search import get_tavilyai_results +from .tavily_ai_search import do_tavily_ai_search from .metaphor_basic_neural_web_search import metaphor_search_articles, streamlit_display_metaphor_results from .google_serp_search import google_search from .google_trends_researcher import do_google_trends_analysis @@ -148,8 +148,8 @@ def gpt_web_researcher(search_keywords, search_mode, **kwargs): include_domains = kwargs.pop('include_domains', None) search_depth = kwargs.pop('search_depth', 'advanced') - # Pass the parameters to get_tavilyai_results - t_results = get_tavilyai_results( + # Pass the parameters to do_tavily_ai_search + t_results = do_tavily_ai_search( keywords=search_keywords, max_results=kwargs.get('num_results', 10), include_domains=include_domains, @@ -343,8 +343,8 @@ def do_tavily_ai_search(search_keywords, max_results=10, **kwargs): 'include_domains': kwargs.get('include_domains', [""]) if kwargs.get('include_domains') else [""] } - # Pass the parameters to get_tavilyai_results - t_results = get_tavilyai_results( + # Pass the parameters to do_tavily_ai_search + t_results = do_tavily_ai_search( keywords=search_keywords, **tavily_params ) diff --git a/lib/ai_web_researcher/tavily_ai_search.py b/lib/ai_web_researcher/tavily_ai_search.py index 3063a7d0..3a30e995 100644 --- a/lib/ai_web_researcher/tavily_ai_search.py +++ b/lib/ai_web_researcher/tavily_ai_search.py @@ -16,7 +16,7 @@ Usage: Modifications: - To modify the script, update the environment variables in the .env file with the required API keys. -- Adjust the search parameters, such as keywords and search depth, in the `get_tavilyai_results` function as needed. +- Adjust the search parameters, such as keywords and search depth, in the `do_tavily_ai_search` function as needed. - Customize logging configurations and table formatting according to preferences. To-Do (TBD): @@ -49,7 +49,7 @@ from tenacity import retry, stop_after_attempt, wait_random_exponential @retry(wait=wait_random_exponential(min=1, max=60), stop=stop_after_attempt(6)) -def get_tavilyai_results(keywords, max_results=5, include_domains=None, search_depth="advanced", **kwargs): +def do_tavily_ai_search(keywords, max_results=5, include_domains=None, search_depth="advanced", **kwargs): """ Get Tavily AI search results based on specified keywords and options. """ diff --git a/lib/ai_writers/linkedin_ai_writer.py b/lib/ai_writers/linkedin_ai_writer.py deleted file mode 100644 index 22e88fa6..00000000 --- a/lib/ai_writers/linkedin_ai_writer.py +++ /dev/null @@ -1,76 +0,0 @@ -import time #Iwish -import os -import json -import requests -import streamlit as st - -from ..gpt_providers.text_generation.main_text_generation import llm_text_gen -from ..ai_web_researcher.gpt_online_researcher import do_google_serp_search - - -def linked_post_writer(): - # Title and description - st.title("✍️ Alwrity - AI Linkedin Blog Post Generator") - - # Input section - with st.expander("**PRO-TIP** - Read the instructions below.", expanded=True): - input_blog_keywords = st.text_input('**Enter main keywords of your Post!** (2-3 words that defines your blog)') - col1, col2, space, col3 = st.columns([5, 5, 0.5, 5]) - with col1: - input_linkedin_type = st.selectbox('Post Type', ('General', 'How-to Guides', 'Polls', 'Listicles', - 'Reality check posts', 'Job Posts', 'FAQs', 'Checklists/Cheat Sheets'), index=0) - with col2: - input_linkedin_length = st.selectbox('Post Length', ('1000 words', 'Long Form', 'Short form'), index=0) - with col3: - input_linkedin_language = st.selectbox('Choose Language', ('English', 'Vietnamese', - 'Chinese', 'Hindi', 'Spanish'), index=0) - # Generate Blog FAQ button - if st.button('**Get LinkedIn Post**'): - with st.spinner(): - # Clicking without providing data, really ? - if not input_blog_keywords: - st.error('** 🫣Provide Inputs to generate Blinkedin Post. Keywords, required!**') - elif input_blog_keywords: - linkedin_post = generate_linkedin_post(input_blog_keywords, input_linkedin_type, - input_linkedin_length, input_linkedin_language) - if linkedin_post: - st.subheader('**πŸ§•πŸ”¬πŸ‘© Go Rule LinkedIn with this Blog Post!**') - st.write(linkedin_post) - st.write("\n\n\n") - else: - st.error("πŸ’₯**Failed to generate linkedin Post. Please try again!**") - - -# Function to generate blog metadesc -def generate_linkedin_post(input_blog_keywords, input_linkedin_type, input_linkedin_length, input_linkedin_language): - """ Function to call upon LLM to get the work done. """ - - # Fetch SERP results & PAA questions for FAQ. - serp_results, people_also_ask = do_google_serp_search(input_blog_keywords) - - # If keywords and content both are given. - if serp_results: - prompt = f"""As an Experienced, industry veteran and experienced linkedin content writer, - I will provide you with my 'blog keywords' and 'google serp results' done for the keywords. - Your task is to write a detailed linkedin post, using given keywords and search results. - - Follow below guidelines for generating the linkedin post: - 1). Write a title, introduction, sections, faqs and a conclusion for the post. - 2). Your FAQ should be based on 'People also ask' and 'Related Queries' from given serp results. - 3). Maintain consistent voice of tone, keep the sentence short and simple. - 4). Make sure to include important results from the given google serp results. - 5). Optimise your response for blog type of {input_linkedin_type}. - 6). Important to provide your response in {input_linkedin_language} language.\n - - Your final response should demostrate Experience, Expertise, Authoritativeness, and Trustworthiness. - - blog keywords: \'\'\'{input_blog_keywords}\'\'\' - google serp results: \'\'\'{serp_results}\'\'\' - """ - - try: - response = llm_text_gen(prompt) - return response - except Exception as err: - st.error(f"Failed to generate Open Graph tags: {err}") - return None diff --git a/lib/ai_writers/linkedin_writer/__init__.py b/lib/ai_writers/linkedin_writer/__init__.py new file mode 100644 index 00000000..7c045de1 --- /dev/null +++ b/lib/ai_writers/linkedin_writer/__init__.py @@ -0,0 +1,20 @@ +""" +LinkedIn AI Writer Module + +This module provides a comprehensive suite of tools for generating LinkedIn content. +""" + +from .linkedin_ai_writer import linkedin_main_menu, LinkedInAIWriter +from .modules.post_generator.linkedin_post_generator import linkedin_post_generator_ui, LinkedInPostGenerator +from .modules.article_generator.linkedin_article_generator import linkedin_article_generator_ui +from .modules.carousel_generator.linkedin_carousel_generator import linkedin_carousel_generator_ui, LinkedInCarouselGenerator + +__all__ = [ + 'linkedin_main_menu', + 'LinkedInAIWriter', + 'linkedin_post_generator_ui', + 'LinkedInPostGenerator', + 'linkedin_article_generator_ui', + 'linkedin_carousel_generator_ui', + 'LinkedInCarouselGenerator' +] \ No newline at end of file diff --git a/lib/ai_writers/linkedin_writer/linkedin_ai_writer.py b/lib/ai_writers/linkedin_writer/linkedin_ai_writer.py new file mode 100644 index 00000000..cee78f43 --- /dev/null +++ b/lib/ai_writers/linkedin_writer/linkedin_ai_writer.py @@ -0,0 +1,544 @@ +""" +LinkedIn AI Writer + +This module provides a comprehensive suite of tools for generating LinkedIn content. +""" + +import time +import os +import json +import requests +import streamlit as st +import importlib +import sys +from pathlib import Path +from typing import Dict, List, Optional, Union +from loguru import logger + +# Import AI text generation +from ...gpt_providers.text_generation.main_text_generation import llm_text_gen + +# Import web research tools +from ...ai_web_researcher.gpt_online_researcher import do_google_serp_search +from ...ai_web_researcher.metaphor_basic_neural_web_search import metaphor_search_articles, streamlit_display_metaphor_results +from ...ai_web_researcher.tavily_ai_search import do_tavily_ai_search, streamlit_display_results + +# Import LinkedIn content generators +from .modules.post_generator.linkedin_post_generator import linkedin_post_generator_ui +from .modules.article_generator.linkedin_article_generator import linkedin_article_generator_ui +from .modules.carousel_generator.linkedin_carousel_generator import linkedin_carousel_generator_ui +from .modules.video_script_generator.linkedin_video_script_generator import linkedin_video_script_generator_ui + +# Import image generation +from ...gpt_providers.text_to_image_generation.main_generate_image_from_prompt import generate_image + + +def linkedin_main_menu(): + """Main function for the LinkedIn AI Writer.""" + + # Initialize session state for selected tool if it doesn't exist + if "selected_tool" not in st.session_state: + st.session_state.selected_tool = None + + # Define the LinkedIn tools with their details + linkedin_tools = [ + # Content Creation Tools + { + "name": "LinkedIn Post Generator", + "icon": "πŸ“", + "description": "Create engaging, professional posts that drive engagement and establish thought leadership.", + "color": "#0A66C2", # LinkedIn blue + "category": "Content Creation", + "function": linkedin_post_generator_ui, + "status": "active", + "features": [ + "Professional tone customization", + "Industry-specific terminology", + "Hashtag optimization", + "Formatting options", + "Character count optimization", + "Call-to-action suggestions", + "Engagement prediction", + "Visual content recommendations", + "Poll creation", + "Best posting time suggestions", + "Research-backed content", + "Reference tracking" + ] + }, + { + "name": "LinkedIn Article Generator", + "icon": "πŸ“„", + "description": "Generate long-form professional articles that showcase expertise and drive traffic.", + "color": "#0A66C2", + "category": "Content Creation", + "function": linkedin_article_generator_ui, + "status": "active", + "features": [ + "Topic research and outline generation", + "SEO optimization for LinkedIn articles", + "Professional writing style adaptation", + "Section structuring", + "Citation and reference formatting", + "Image placement suggestions", + "Headline optimization", + "Meta description generation", + "Reading time estimation", + "Internal linking suggestions", + "Multiple research sources (Metaphor, Google, Tavily)", + "AI-generated section images" + ] + }, + { + "name": "LinkedIn Carousel Post Generator", + "icon": "πŸ”„", + "description": "Create engaging carousel posts that showcase information in a visually appealing way.", + "color": "#0A66C2", + "category": "Content Creation", + "function": linkedin_carousel_generator_ui, + "status": "active", + "features": [ + "Slide content generation", + "Visual hierarchy optimization", + "Story arc development", + "Call-to-action placement", + "Brand consistency maintenance", + "Engagement element integration", + "Professional design suggestions", + "Content distribution strategy", + "Analytics integration", + "A/B testing variations" + ] + }, + { + "name": "LinkedIn Video Script Generator", + "icon": "πŸŽ₯", + "description": "Create scripts for LinkedIn videos that drive engagement.", + "color": "#0A66C2", + "category": "Content Creation", + "function": linkedin_video_script_generator_ui, + "status": "active", + "features": [ + "Hook generation", + "Story structure development", + "Professional speaking points", + "Visual cue suggestions", + "Call-to-action optimization", + "Engagement prompt integration", + "Caption generation", + "Thumbnail text suggestions", + "Video description optimization", + "Hashtag strategy" + ] + }, + + # Profile & Personal Branding Tools + { + "name": "LinkedIn Profile Optimizer", + "icon": "πŸ‘€", + "description": "Enhance LinkedIn profiles to improve visibility and professional appeal.", + "color": "#0A66C2", + "category": "Profile & Personal Branding", + "function": None, + "status": "coming_soon", + "features": [ + "Headline optimization", + "About section generation", + "Experience description enhancement", + "Skills recommendation", + "Project highlight creation", + "Endorsement request generation", + "Profile strength analysis", + "Keyword optimization", + "Professional summary generation", + "Custom URL suggestions" + ] + }, + { + "name": "LinkedIn Poll Generator", + "icon": "πŸ“Š", + "description": "Create engaging polls that drive interaction and gather insights.", + "color": "#0A66C2", + "category": "Profile & Personal Branding", + "function": None, + "status": "coming_soon", + "features": [ + "Question formulation optimization", + "Option generation based on topic", + "Industry-specific poll templates", + "Engagement prediction", + "Result analysis suggestions", + "Follow-up content recommendations", + "Trending topic integration", + "Professional tone maintenance", + "Data visualization suggestions", + "Poll scheduling optimization" + ] + }, + + # Business & Marketing Tools + { + "name": "LinkedIn Company Page Content Generator", + "icon": "🏒", + "description": "Create content for company pages that builds brand awareness and engagement.", + "color": "#0A66C2", + "category": "Business & Marketing", + "function": None, + "status": "coming_soon", + "features": [ + "Company culture post generation", + "Product/service announcement templates", + "Employee spotlight content", + "Company milestone celebrations", + "Industry insights sharing", + "Event promotion content", + "Job posting templates", + "Company news updates", + "Brand voice consistency", + "Engagement metrics optimization" + ] + }, + { + "name": "LinkedIn Newsletter Generator", + "icon": "πŸ“°", + "description": "Create professional newsletters that establish thought leadership and drive engagement.", + "color": "#0A66C2", + "category": "Business & Marketing", + "function": None, + "status": "coming_soon", + "features": [ + "Newsletter structure templates", + "Topic clustering and organization", + "Professional introduction and conclusion", + "Industry trend analysis integration", + "Expert quote suggestions", + "Visual content recommendations", + "Call-to-action optimization", + "Subscriber engagement prompts", + "Consistency maintenance", + "Analytics integration suggestions" + ] + }, + { + "name": "LinkedIn Job Description Generator", + "icon": "πŸ’Ό", + "description": "Create compelling job descriptions that attract qualified candidates.", + "color": "#0A66C2", + "category": "Business & Marketing", + "function": None, + "status": "coming_soon", + "features": [ + "Role-specific templates", + "Skills and qualifications optimization", + "Company culture integration", + "Benefits and perks highlighting", + "Inclusive language checker", + "Keyword optimization", + "Application process clarity", + "Remote/hybrid work policy integration", + "Diversity and inclusion statements", + "A/B testing variations" + ] + }, + + # Sales & Networking Tools + { + "name": "LinkedIn Sales Navigator Content Generator", + "icon": "πŸ’°", + "description": "Create personalized outreach content for sales professionals.", + "color": "#0A66C2", + "category": "Sales & Networking", + "function": None, + "status": "coming_soon", + "features": [ + "Prospect research integration", + "Industry-specific messaging", + "Personalization tokens", + "Connection request templates", + "Follow-up message sequences", + "Value proposition highlighting", + "Objection handling responses", + "Meeting request templates", + "Industry pain point addressing", + "ROI demonstration content" + ] + }, + { + "name": "LinkedIn InMail Generator", + "icon": "βœ‰οΈ", + "description": "Create personalized and effective InMail messages.", + "color": "#0A66C2", + "category": "Sales & Networking", + "function": None, + "status": "coming_soon", + "features": [ + "Prospect research integration", + "Personalization token usage", + "Value proposition highlighting", + "Call-to-action optimization", + "Follow-up sequence generation", + "Objection handling preparation", + "Industry-specific messaging", + "A/B testing variations", + "Compliance checking", + "Engagement tracking suggestions" + ] + }, + + # Learning & Education Tools + { + "name": "LinkedIn Learning Course Description Generator", + "icon": "πŸ“š", + "description": "Create compelling descriptions for LinkedIn Learning courses.", + "color": "#0A66C2", + "category": "Learning & Education", + "function": None, + "status": "coming_soon", + "features": [ + "Course objective optimization", + "Learning outcome generation", + "Prerequisite suggestions", + "Target audience definition", + "Skill tag recommendations", + "Course structure outline", + "Engagement element suggestions", + "Completion certificate highlighting", + "Industry relevance emphasis", + "Career path integration" + ] + }, + { + "name": "LinkedIn Event Description Generator", + "icon": "πŸ“…", + "description": "Create compelling event descriptions that drive attendance and engagement.", + "color": "#0A66C2", + "category": "Learning & Education", + "function": None, + "status": "coming_soon", + "features": [ + "Event objective highlighting", + "Speaker bio generation", + "Agenda formatting", + "Registration incentive suggestions", + "Networking opportunity emphasis", + "Industry relevance integration", + "Visual content recommendations", + "Engagement element suggestions", + "Post-event follow-up content", + "Attendance tracking integration" + ] + }, + + # Community & Engagement Tools + { + "name": "LinkedIn Group Post Generator", + "icon": "πŸ‘₯", + "description": "Create content specifically optimized for LinkedIn Groups.", + "color": "#0A66C2", + "category": "Community & Engagement", + "function": None, + "status": "coming_soon", + "features": [ + "Group-specific content adaptation", + "Discussion prompt generation", + "Community guideline compliance", + "Engagement optimization", + "Moderation suggestion", + "Topic relevance checking", + "Member value highlighting", + "Cross-promotion opportunities", + "Group culture adaptation", + "Content scheduling" + ] + }, + { + "name": "LinkedIn Comment Response Generator", + "icon": "πŸ’¬", + "description": "Create professional and engaging responses to comments on LinkedIn posts.", + "color": "#0A66C2", + "category": "Community & Engagement", + "function": None, + "status": "coming_soon", + "features": [ + "Tone adaptation based on comment", + "Professional disagreement handling", + "Question answering optimization", + "Engagement continuation prompts", + "Value-add response generation", + "Community building suggestions", + "Moderation guidance", + "Follow-up question generation", + "Resource sharing suggestions", + "Relationship building strategies" + ] + } + ] + + # Create a container for the dashboard + dashboard_container = st.container() + + # Create a container for the tool input section + tool_container = st.container() + + # If a tool is selected, show its input section + if st.session_state.selected_tool is not None: + with tool_container: + # Add a back button at the top + if st.button("← Back to Dashboard", key="back_to_dashboard"): + st.session_state.selected_tool = None + st.rerun() + + # Display the tool header with card layout + st.markdown(f""" +
+
+
{st.session_state.selected_tool['icon']}
+
+

{st.session_state.selected_tool['name']}

+

{st.session_state.selected_tool['description']}

+
+
+
+ """, unsafe_allow_html=True) + + # Call the tool's function if it exists + if st.session_state.selected_tool["function"] is not None: + st.session_state.selected_tool["function"]() + else: + # Display coming soon information + st.info(f"**{st.session_state.selected_tool['status'].replace('_', ' ').title()}!**") + st.write(st.session_state.selected_tool["description"]) + + # Display features + st.subheader("Features") + for feature in st.session_state.selected_tool["features"]: + st.markdown(f"- {feature}") + + # Display placeholder image + st.image(f"https://via.placeholder.com/600x300?text={st.session_state.selected_tool['name']}+Coming+Soon", use_container_width=True) + else: + with dashboard_container: + # Display the dashboard + # Header + st.markdown(""" +
+

πŸ’Ό LinkedIn AI Writer

+

Generate professional LinkedIn content with ALwrity's AI-powered tools

+
+ """, unsafe_allow_html=True) + + # Group tools by category + categories = {} + for tool in linkedin_tools: + category = tool["category"] + if category not in categories: + categories[category] = [] + categories[category].append(tool) + + # Display tools by category + for category, tools in categories.items(): + st.markdown(f"## {category}") + + # Create a 3-column layout for the tool cards + cols = st.columns(3) + + # Display the tool cards + for i, tool in enumerate(tools): + # Determine which column to use + col = cols[i % 3] + + with col: + # Create a card for each tool + status_badge = "" + if tool["status"] == "coming_soon": + status_badge = "Coming Soon" + elif tool["status"] == "future": + status_badge = "Future" + elif tool["status"] == "active": + status_badge = "Active" + + st.markdown(f""" +
+

{tool["icon"]} {tool["name"]} {status_badge}

+

{tool["description"]}

+
+ """, unsafe_allow_html=True) + + # Add a button to access the tool + if st.button(f"Use {tool['name']}", key=f"btn_{tool['name']}"): + # Store the selected tool in session state + st.session_state.selected_tool = tool + st.rerun() + + +class LinkedInAIWriter: + """ + AI-powered content generator for LinkedIn marketing and communication. + + This class provides various tools for generating LinkedIn content including: + - Posts and articles + - Profile optimization + - Company page content + - Sales and networking content + - Learning and education content + - Community and engagement content + """ + + def __init__(self): + """Initialize the LinkedIn AI Writer.""" + pass + + # Methods will be implemented in future iterations + # Each method will correspond to a specific LinkedIn content generation tool + + +# List of available tools +AVAILABLE_TOOLS = [ + 'Post Generator', + 'Article Generator', + 'Carousel Post Generator', + 'Video Script Generator', + 'Profile Optimizer', + 'Poll Generator', + 'Company Page Content Generator', + 'Newsletter Generator', + 'Job Description Generator', + 'Sales Navigator Content Generator', + 'InMail Generator', + 'Learning Course Description Generator', + 'Event Description Generator', + 'Group Post Generator', + 'Comment Response Generator' +] + +if __name__ == "__main__": + linkedin_main_menu() diff --git a/lib/ai_writers/linkedin_writer/modules/__init__.py b/lib/ai_writers/linkedin_writer/modules/__init__.py new file mode 100644 index 00000000..10ac24e6 --- /dev/null +++ b/lib/ai_writers/linkedin_writer/modules/__init__.py @@ -0,0 +1,17 @@ +""" +LinkedIn AI Writer Modules + +This package contains various modules for generating different types of LinkedIn content. +""" + +from .post_generator.linkedin_post_generator import linkedin_post_generator_ui, LinkedInPostGenerator +from .article_generator.linkedin_article_generator import linkedin_article_generator_ui +from .carousel_generator.linkedin_carousel_generator import linkedin_carousel_generator_ui, LinkedInCarouselGenerator + +__all__ = [ + 'linkedin_post_generator_ui', + 'LinkedInPostGenerator', + 'linkedin_article_generator_ui', + 'linkedin_carousel_generator_ui', + 'LinkedInCarouselGenerator' +] \ No newline at end of file diff --git a/lib/ai_writers/linkedin_writer/modules/article_generator/README.md b/lib/ai_writers/linkedin_writer/modules/article_generator/README.md new file mode 100644 index 00000000..d130c44e --- /dev/null +++ b/lib/ai_writers/linkedin_writer/modules/article_generator/README.md @@ -0,0 +1,253 @@ +# LinkedIn Article Generator πŸ“ + +A powerful AI-powered tool designed specifically for LinkedIn content creators to generate professional, research-backed articles that engage and provide value to your professional network. + +## 🌟 Key Features + +### Content Generation +- **AI-Powered Research**: Integrates with multiple search engines (Metaphor, Google, Tavily) for up-to-date, accurate content +- **Professional Writing**: Generates industry-specific content with expert insights and statistics +- **Topic-Focused Content**: Ensures all content stays relevant to your chosen topic without AI explanations or meta-commentary +- **SEO Optimization**: Built-in SEO tools for maximum visibility +- **Image Generation**: AI-powered visuals for article header and sections + +### Research Integration +- **Multi-Source Research**: Choose from Metaphor AI, Google Search, or Tavily AI +- **Real-Time Data**: Access current industry trends and statistics +- **Expert Insights**: Incorporate professional perspectives and quotes +- **Fact-Checking**: Web-researched context to prevent AI hallucinations + +### Visual Enhancement +- **Topic Images**: Generate professional header images +- **Section Visuals**: Create relevant images for each section +- **Image Refinement**: Edit and regenerate images until perfect +- **Custom Prompts**: Write your own image generation prompts + +## πŸš€ Getting Started + +### 1. Article Generation Process + +#### Step 1: Topic Input +- Enter your article topic +- Select your industry +- Choose writing tone: + - Professional + - Analytical + - Conversational + - Technical + - Thought Leadership + +#### Step 2: Research Selection +Choose your preferred research source: +- πŸ” Metaphor AI: For comprehensive industry insights +- 🌐 Google Search: For broad topic coverage +- πŸ€– Tavily AI: For focused, AI-curated research + +#### Step 3: Content Generation +The tool will: +1. Research your topic thoroughly +2. Create a detailed outline +3. Generate comprehensive content +4. Add relevant statistics and quotes +5. Generate professional images +6. Optimize for SEO + +### 2. Article Preview and Editing + +#### Content Tab +- View the complete article +- Edit content directly +- Download in HTML format +- Copy to clipboard + +#### Images & Visuals Tab +- Preview and regenerate topic image +- Edit image prompts +- Generate section-specific images +- Choose from alternative image suggestions + +#### SEO & Metadata Tab +- Review focus keywords +- Edit meta descriptions +- Optimize article tags +- Customize social sharing text + +## πŸ’‘ Best Practices + +### 1. Topic Selection +- Choose specific, industry-relevant topics +- Focus on your area of expertise +- Consider current trends in your field +- Target your professional audience's needs + +### 2. Content Structure +- Strong, attention-grabbing headlines +- Professional introduction +- Well-organized sections +- Clear takeaways +- Compelling call-to-action + +### 3. Visual Strategy +- Professional, topic-relevant images +- Consistent visual style +- High-quality graphics +- Mobile-friendly visuals + +## 🎯 Content Types + +### Industry Insights +- Market analysis +- Trend reports +- Expert perspectives +- Case studies + +### How-to Articles +- Step-by-step guides +- Best practices +- Implementation strategies +- Professional tips + +### Thought Leadership +- Industry predictions +- Expert opinions +- Strategy discussions +- Innovation insights + +## πŸ“Š SEO Optimization + +### Automatic Optimization +- Focus keyword selection +- Secondary keywords +- Meta descriptions +- Article tags +- Social sharing text + +### Content Enhancement +- Keyword placement +- Header optimization +- Link suggestions +- Readability improvements + +## πŸ› οΈ Advanced Features + +### Research Integration +- Multiple search engines +- Real-time data gathering +- Expert quote integration +- Statistical data inclusion + +### Image Generation +- Custom prompt creation +- Style customization +- Alternative suggestions +- Section-specific visuals + +### Content Export +- HTML format +- Clipboard copy +- Social media optimized +- SEO-ready structure + +## πŸ“± Mobile Optimization + +- Responsive images +- Readable text +- Optimized layout +- Cross-device compatibility + +## πŸ’ͺ Success Tips + +### 1. Content Strategy +- Regular posting schedule +- Consistent topic focus +- Industry relevance +- Audience engagement + +### 2. Visual Impact +- Professional imagery +- Brand consistency +- Visual storytelling +- Quality graphics + +### 3. Engagement +- Compelling headlines +- Interactive elements +- Clear call-to-action +- Professional tone + +## πŸ” Technical Details + +### System Requirements +- Modern web browser +- Internet connection +- API access (if using custom keys) + +### Performance +- Fast generation times +- Real-time preview +- Instant updates +- Smooth editing + +## πŸ“ˆ Best Use Cases + +1. **Industry Analysis** + - Market trends + - Technology updates + - Business insights + - Professional perspectives + +2. **Professional Guides** + - Best practices + - Implementation strategies + - Career development + - Skill enhancement + +3. **Thought Leadership** + - Industry predictions + - Expert opinions + - Innovation insights + - Strategic analysis + +## 🎨 Design Guidelines + +1. **Visual Consistency** + - Brand colors + - Professional fonts + - Clean layouts + - Quality images + +2. **Content Format** + - Clear headers + - Organized sections + - Professional tone + - Engaging structure + +3. **Mobile Design** + - Responsive layout + - Readable text + - Optimized images + - Cross-device testing + +## πŸ”„ Workflow Integration + +1. **Content Planning** + - Topic research + - Outline creation + - Content generation + - Visual design + +2. **Quality Control** + - Content review + - Image refinement + - SEO verification + - Final checks + +3. **Publishing** + - Format selection + - Preview verification + - LinkedIn optimization + - Engagement tracking + +--- + +Start creating professional LinkedIn articles that engage your network and establish your expertise! πŸš€ \ No newline at end of file diff --git a/lib/ai_writers/linkedin_writer/modules/article_generator/linkedin_article_generator.py b/lib/ai_writers/linkedin_writer/modules/article_generator/linkedin_article_generator.py new file mode 100644 index 00000000..6c5a4f76 --- /dev/null +++ b/lib/ai_writers/linkedin_writer/modules/article_generator/linkedin_article_generator.py @@ -0,0 +1,1071 @@ +""" +LinkedIn Article Generator + +This module provides functionality for generating LinkedIn articles with research-backed content, +AI-generated images, and SEO optimization. +""" + +import os +import json +import time +import streamlit as st +from typing import Dict, List, Optional, Tuple, Union +from loguru import logger +import random + +from .....gpt_providers.text_generation.main_text_generation import llm_text_gen +from .....ai_web_researcher.gpt_online_researcher import do_google_serp_search +from .....ai_web_researcher.metaphor_basic_neural_web_search import metaphor_search_articles, streamlit_display_metaphor_results +from .....ai_web_researcher.tavily_ai_search import do_tavily_ai_search, streamlit_display_results + + +class LinkedInArticleGenerator: + """ + A class for generating LinkedIn articles with research-backed content. + + This class provides methods for: + - Researching topics using multiple search engines (Metaphor, Google, Tavily) + - Generating detailed article outlines + - Creating comprehensive article content + - Generating section-specific images + - Adding SEO optimization + - Suggesting engagement strategies + """ + + def __init__(self): + """Initialize the LinkedIn Article Generator.""" + self.research_results = {} + self.outline = {} + self.article_content = "" + self.seo_metadata = {} + self.section_images = [] + self.engagement_strategy = {} + + def research_topic(self, topic: str, industry: str, search_engine: str = "metaphor", status_container=None) -> Dict: + """ + Research a topic using the selected search engine. + """ + # Update progress + if status_container: + status_container.text(f"πŸ” Researching {topic} in {industry} using {search_engine.title()}...") + + # Construct focused search query + search_query = f""" + Find detailed professional articles about '{topic}' in the {industry} industry. + Focus on: + - Latest industry developments + - Expert insights and analysis + - Real-world case studies + - Practical applications + - Industry-specific statistics + - Best practices and strategies + """ + + # Research with selected search engine + research_results = None + + # Create tabs for displaying results + search_tabs = st.tabs([ + "πŸ” Research Results", + "πŸ“Š Topic Analysis", + "πŸ“ Key Insights" + ]) + + with search_tabs[0]: + st.markdown(f"### Research on: {topic}") + st.markdown(f"*Industry Context: {industry}*") + + if search_engine == "metaphor": + research_results = self._research_with_metaphor(search_query, industry, status_container) + elif search_engine == "google": + research_results = self._research_with_google(search_query, industry, status_container) + elif search_engine == "tavily": + research_results = self._research_with_tavily(search_query, industry, status_container) + + # Analyze research results with topic focus + with search_tabs[1]: + st.markdown("### Topic Analysis") + combined_results = self._analyze_research_results(research_results, topic, industry) + + # Display analysis in an organized way + if combined_results: + # Topic relevance verification + relevance_prompt = f""" + Verify the relevance of these research findings to the topic: '{topic}' in {industry} industry. + + Key Insights: {combined_results.get('key_insights', [])} + Statistics: {combined_results.get('statistics', [])} + Expert Opinions: {combined_results.get('expert_opinions', [])} + + Return only the most relevant insights that directly relate to {topic}. + Remove any generic or tangential information. + """ + + verified_results = llm_text_gen(relevance_prompt) + + try: + verified_data = json.loads(verified_results) + combined_results.update(verified_data) + except: + logger.warning("Failed to parse verified results") + + col1, col2 = st.columns(2) + with col1: + st.markdown(f"#### Key Insights about {topic}") + for insight in combined_results.get("key_insights", []): + st.markdown(f"- {insight}") + + st.markdown("#### Industry-Specific Statistics") + for stat in combined_results.get("statistics", []): + st.markdown(f"- {stat}") + + with col2: + st.markdown("#### Expert Perspectives") + for opinion in combined_results.get("expert_opinions", []): + st.markdown(f"> {opinion}") + + st.markdown("#### Current Trends") + for trend in combined_results.get("trends", []): + st.markdown(f"- {trend}") + + with search_tabs[2]: + st.markdown(f"### Key Takeaways for {topic}") + if combined_results: + # Topic-focused insights + col1, col2 = st.columns(2) + with col1: + st.markdown("#### Best Practices") + for practice in combined_results.get("best_practices", []): + st.markdown(f"- {practice}") + + with col2: + st.markdown("#### Practical Solutions") + for solution in combined_results.get("solutions", []): + st.markdown(f"- {solution}") + + # Industry context + st.markdown(f"#### {industry} Industry Context") + st.markdown(combined_results.get("industry_context", "")) + + # Verified sources + st.markdown("#### Expert Sources") + sources = combined_results.get("sources", []) + filtered_sources = [s for s in sources if topic.lower() in s.lower()] + for source in filtered_sources: + st.markdown(f"- {source}") + + # Update progress + if status_container: + status_container.text("βœ… Topic research complete!") + + return combined_results + + def _research_with_metaphor(self, topic: str, industry: str, status_container=None) -> Dict: + """ + Research a topic using Metaphor. + + Args: + topic: The topic to research + industry: The industry context + status_container: Optional container for status updates + + Returns: + Dict containing research results + """ + try: + # Construct search query + search_query = f"{topic} in {industry} industry comprehensive article" + + # Update progress + if status_container: + status_container.text("πŸ” Searching with Metaphor...") + + # Search with Metaphor + search_options = { + "num_results": 15, # More results for comprehensive research + "use_autoprompt": True, + "time_range": "past_year" # Recent but comprehensive results + } + + metaphor_results = metaphor_search_articles(search_query, search_options) + + # Display the results + if metaphor_results: + streamlit_display_metaphor_results(metaphor_results, search_query) + + # Update progress + if status_container: + status_container.text("βœ… Metaphor search complete!") + + return metaphor_results + + except Exception as e: + logger.error(f"Error in Metaphor search: {e}") + if status_container: + status_container.text(f"⚠️ Error in Metaphor search: {str(e)}") + return {"sources": []} + + def _research_with_google(self, topic: str, industry: str, status_container=None) -> Dict: + """ + Research a topic using Google. + + Args: + topic: The topic to research + industry: The industry context + status_container: Optional container for status updates + + Returns: + Dict containing research results + """ + try: + # Update progress + if status_container: + status_container.text("πŸ” Searching with Google...") + + # Search with Google + google_results = do_google_serp_search( + f"{topic} {industry} industry comprehensive guide article", + num_results=15 + ) + + # Update progress + if status_container: + status_container.text("βœ… Google search complete!") + + return google_results + + except Exception as e: + logger.error(f"Error in Google search: {e}") + if status_container: + status_container.text(f"⚠️ Error in Google search: {str(e)}") + return {"sources": []} + + def _research_with_tavily(self, topic: str, industry: str, status_container=None) -> Dict: + """ + Research a topic using Tavily AI. + + Args: + topic: The topic to research + industry: The industry context + status_container: Optional container for status updates + + Returns: + Dict containing research results + """ + try: + # Update progress + if status_container: + status_container.text("πŸ” Searching with Tavily AI...") + + # Search with Tavily + search_query = f"{topic} in {industry} industry comprehensive guide" + tavily_results = do_tavily_ai_search(search_query) + + # Display results + if tavily_results: + streamlit_display_results(tavily_results) + + # Update progress + if status_container: + status_container.text("βœ… Tavily search complete!") + + return tavily_results + + except Exception as e: + logger.error(f"Error in Tavily search: {e}") + if status_container: + status_container.text(f"⚠️ Error in Tavily search: {str(e)}") + return {"sources": []} + + def _analyze_research_results(self, research_results: Dict, topic: str, industry: str) -> Dict: + """ + Analyze research results to extract key insights for article creation. + + Args: + research_results: Results from research + topic: The topic being researched + industry: The industry context + + Returns: + Dict containing analyzed insights + """ + if not research_results: + logger.warning(f"No research results available for {topic} in {industry}") + return { + "key_insights": [], + "expert_opinions": [], + "statistics": [], + "case_studies": [], + "trends": [], + "challenges": [], + "solutions": [], + "best_practices": [], + "industry_context": "", + "sources": [] + } + + # Extract content from research results + sources = [] + content = "" + + if isinstance(research_results, dict): + if "data" in research_results and "results" in research_results["data"]: + sources = research_results["data"]["results"] + content = "\n\n".join([ + f"Title: {source.get('title', '')}\n" + f"Content: {source.get('text', '')}\n" + f"Summary: {source.get('summary', '')}" + for source in sources + ]) + + # Generate analysis prompt + analysis_prompt = f""" + Analyze this research about {topic} in the {industry} industry for writing a comprehensive LinkedIn article: + + {content} + + Extract and organize the following elements: + 1. Key insights and main arguments + 2. Expert opinions and quotes + 3. Statistics and data points + 4. Case studies and examples + 5. Current trends and future predictions + 6. Common challenges and pain points + 7. Solutions and strategies + 8. Best practices and recommendations + 9. Industry-specific context and implications + + Format as JSON with these keys: + {{ + "key_insights": ["List of main insights"], + "expert_opinions": ["List of expert quotes with sources"], + "statistics": ["List of statistics with sources"], + "case_studies": ["List of relevant case studies"], + "trends": ["List of current and emerging trends"], + "challenges": ["List of common challenges"], + "solutions": ["List of solutions and strategies"], + "best_practices": ["List of best practices"], + "industry_context": "Summary of industry context", + "sources": ["List of source URLs"] + }} + """ + + # Generate analysis using LLM + analysis = llm_text_gen(analysis_prompt) + + try: + analysis_dict = json.loads(analysis) + except json.JSONDecodeError: + logger.error("Failed to parse analysis JSON") + analysis_dict = { + "key_insights": [], + "expert_opinions": [], + "statistics": [], + "case_studies": [], + "trends": [], + "challenges": [], + "solutions": [], + "best_practices": [], + "industry_context": "", + "sources": [] + } + + return analysis_dict + + def generate_outline(self, research_results: Dict) -> Dict: + """ + Generate a detailed article outline based on research results. + """ + # Extract key information from research results + key_insights = research_results.get("key_insights", []) + expert_opinions = research_results.get("expert_opinions", []) + statistics = research_results.get("statistics", []) + case_studies = research_results.get("case_studies", []) + trends = research_results.get("trends", []) + challenges = research_results.get("challenges", []) + solutions = research_results.get("solutions", []) + best_practices = research_results.get("best_practices", []) + industry_context = research_results.get("industry_context", "") + + # Generate outline prompt + outline_prompt = f""" + Create a professional LinkedIn article outline focused on the exact topic and industry provided. + + Research Data: + Key Insights: {key_insights} + Expert Opinions: {expert_opinions} + Statistics: {statistics} + Case Studies: {case_studies} + Trends: {trends} + Challenges: {challenges} + Solutions: {solutions} + Best Practices: {best_practices} + Industry Context: {industry_context} + + Requirements: + 1. Stay strictly focused on the topic without any AI explanations + 2. Create a compelling headline that clearly states the topic's value + 3. Structure 4-5 main sections that logically develop the topic + 4. Include specific places for statistics and expert quotes + 5. Focus on practical insights and actionable takeaways + 6. Maintain professional tone throughout + 7. Ensure each section directly relates to the main topic + 8. Include specific examples and case studies where relevant + + Format as JSON: + {{ + "headline": "Clear, topic-focused headline", + "subheadline": "Supporting subheadline that elaborates on the value", + "introduction": {{ + "hook": "Attention-grabbing opening relevant to topic", + "context": "Topic-specific background", + "thesis": "Clear main argument or point" + }}, + "sections": [ + {{ + "title": "Section title", + "key_points": ["Specific, topic-focused points"], + "supporting_evidence": ["Relevant statistics or quotes"], + "visual_suggestions": ["Topic-specific visual ideas"] + }} + ], + "conclusion": {{ + "key_takeaways": ["Practical, actionable takeaways"], + "call_to_action": "Relevant next steps for readers" + }}, + "seo_keywords": ["Topic-specific keywords"] + }} + """ + + # Generate outline using LLM + outline = llm_text_gen(outline_prompt) + + try: + outline_dict = json.loads(outline) + except json.JSONDecodeError: + logger.error("Failed to parse outline JSON") + outline_dict = { + "headline": "", + "subheadline": "", + "introduction": { + "hook": "", + "context": "", + "thesis": "" + }, + "sections": [], + "conclusion": { + "key_takeaways": [], + "call_to_action": "" + }, + "seo_keywords": [] + } + + return outline_dict + + def generate_article_content(self, outline: Dict, topic: str, industry: str, tone: str = "professional") -> str: + """ + Generate comprehensive article content based on the outline. + """ + # Generate focused article prompt + article_prompt = f""" + Write a professional LinkedIn article about '{topic}' in the {industry} industry. + Follow this outline exactly and stay focused on the specific topic. + + Title: {outline['headline']} + Subheadline: {outline['subheadline']} + + Introduction: + Hook: {outline['introduction']['hook']} + Context: {outline['introduction']['context']} + Thesis: {outline['introduction']['thesis']} + + Sections: + {json.dumps(outline['sections'], indent=2)} + + Conclusion: + Key Takeaways: {outline['conclusion']['key_takeaways']} + Call to Action: {outline['conclusion']['call_to_action']} + + Critical Requirements: + 1. Write in a {tone} tone appropriate for LinkedIn + 2. Focus EXCLUSIVELY on {topic} - no deviations or tangents + 3. Every paragraph must directly discuss {topic} + 4. Use concrete examples and real-world applications specific to {topic} + 5. Include relevant statistics and expert quotes that directly relate to {topic} + 6. Use clear transitions that maintain focus on {topic} + 7. Provide actionable insights specific to {topic} + 8. Write as an industry expert discussing {topic} + 9. NO meta-commentary, AI explanations, or generic content + 10. Use proper HTML formatting for structure + 11. Every section must explicitly connect to {topic} + + Return ONLY the final article in clean HTML format, ready to publish on LinkedIn. + The article must read as if written by a {industry} expert focusing solely on {topic}. + """ + + # Generate initial article content + article_content = llm_text_gen(article_prompt) + + # Verify topic focus + verification_prompt = f""" + Verify this article maintains strict focus on '{topic}' in the {industry} industry. + + Requirements: + 1. Every paragraph must explicitly discuss {topic} + 2. Remove any content not directly related to {topic} + 3. Remove any AI explanations or meta-commentary + 4. Ensure all examples and insights are specific to {topic} + 5. Maintain professional {tone} tone + 6. Keep all relevant statistics and expert quotes about {topic} + + Return only the verified and cleaned article content. + """ + + # Verify and clean the content + verified_content = llm_text_gen(verification_prompt + "\n\nArticle:\n" + article_content) + + # Final topic focus check + final_check_prompt = f""" + Perform a final check on this article about '{topic}'. + + If you find ANY content that: + 1. Isn't directly about {topic} + 2. Contains AI explanations + 3. Includes meta-commentary + 4. Uses generic examples + + Remove or replace it with topic-specific content about {topic}. + Return only the final, topic-focused article. + """ + + final_content = llm_text_gen(final_check_prompt + "\n\nArticle:\n" + verified_content) + + return final_content + + def _generate_image_prompt(self, topic: str, article_content: str, industry: str) -> str: + """ + Generate a detailed image prompt based on the article topic and content. + """ + prompt_generation = f""" + Create a specific, detailed image generation prompt for a LinkedIn article about "{topic}" in the {industry} industry. + + Article excerpt: + {article_content[:500]}... + + Requirements: + 1. Focus on key concepts and themes from the article + 2. Specify exact visual elements that represent the topic + 3. Include industry-specific imagery and symbols + 4. Define a professional color scheme that matches the topic + 5. Describe specific composition and layout + 6. Include relevant metaphors or visual concepts + 7. Ensure the image will be immediately recognizable as related to {topic} + 8. Avoid generic business imagery + + Format: + 1. Main subject/focus + 2. Style and composition + 3. Colors and lighting + 4. Specific elements to include + 5. Industry-specific details + 6. Mood and atmosphere + + Return a detailed, topic-specific image generation prompt. + """ + + return llm_text_gen(prompt_generation) + + def _suggest_image_variations(self, topic: str, industry: str) -> List[str]: + """ + Generate alternative image prompt suggestions. + + Args: + topic: The article topic + industry: The industry context + + Returns: + List[str]: List of alternative image prompts + """ + variation_prompt = f""" + Generate 3 different image prompt variations for a LinkedIn article about "{topic}" in the {industry} industry. + Each prompt should be unique and professional: + 1. A metaphorical/conceptual approach + 2. A data-driven/analytical visualization + 3. A human-centered/storytelling perspective + + Return only the 3 prompts, one per line, no explanations. + """ + + variations = llm_text_gen(variation_prompt).strip().split('\n') + return [v.strip() for v in variations if v.strip()] + + def generate_section_images(self, outline: Dict, article_content: str, topic: str, industry: str) -> List[Dict]: + """ + Generate image prompts for each section of the article. + + Args: + outline: The article outline + article_content: The generated article content + topic: The article topic + industry: The industry context + + Returns: + List[Dict]: List of image generation prompts for each section + """ + section_images = [] + + # Generate main image prompt + main_prompt = self._generate_image_prompt(topic, article_content, industry) + alternative_prompts = self._suggest_image_variations(topic, industry) + + header_image = { + "section": "header", + "main_prompt": main_prompt, + "alternative_prompts": alternative_prompts + } + section_images.append(header_image) + + # Generate image prompts for each section + for section in outline['sections']: + section_content = section.get('title', '') + ' ' + ' '.join(section.get('key_points', [])) + section_prompt = self._generate_image_prompt(section['title'], section_content, industry) + + section_image = { + "section": section['title'], + "main_prompt": section_prompt, + "alternative_prompts": self._suggest_image_variations(section['title'], industry) + } + section_images.append(section_image) + + return section_images + + def generate_seo_metadata(self, article_content: str, outline: Dict) -> Dict: + """ + Generate SEO metadata for the LinkedIn article. + + Args: + article_content: The generated article content + outline: The article outline + + Returns: + Dict: SEO metadata including keywords, description, and tags + """ + seo_prompt = f""" + Generate SEO metadata for this LinkedIn article: + + Title: {outline['headline']} + Content: {article_content[:500]}... + + Return a JSON object with: + 1. Focus keyword + 2. Secondary keywords (5-7) + 3. Meta description (150-160 characters) + 4. Article tags (5-7) + 5. Social sharing description + """ + + seo_response = llm_text_gen(seo_prompt) + + try: + seo_metadata = json.loads(seo_response) + except json.JSONDecodeError: + logger.error("Failed to parse SEO metadata JSON") + seo_metadata = { + "focus_keyword": outline['headline'], + "secondary_keywords": outline.get('seo_keywords', []), + "meta_description": outline['subheadline'], + "article_tags": [], + "social_description": outline['subheadline'] + } + + return seo_metadata + + +def linkedin_article_generator_ui(): + """ + Streamlit UI for LinkedIn Article Generator. + """ + st.title("πŸ“ LinkedIn Article Generator") + st.write("Generate comprehensive LinkedIn articles with AI assistance") + + # Initialize generator + generator = LinkedInArticleGenerator() + + # Initialize session state + if "article_data" not in st.session_state: + st.session_state.article_data = { + "research_results": None, + "outline": None, + "article_content": "", + "section_images": [], + "seo_metadata": None, + "generated_images": {}, + "topic_image": None + } + + # Input form + with st.form("linkedin_article_form"): + topic = st.text_input("Article Topic", placeholder="Enter the main topic of your article") + industry = st.text_input("Industry", placeholder="e.g., Technology, Healthcare, Finance") + + col1, col2 = st.columns(2) + with col1: + tone = st.selectbox( + "Writing Tone", + ["Professional", "Analytical", "Conversational", "Technical", "Thought Leadership"] + ) + with col2: + search_engine = st.radio( + "Research Source", + ["metaphor", "google", "tavily"], + format_func=lambda x: { + "metaphor": "πŸ” Metaphor AI", + "google": "🌐 Google Search", + "tavily": "πŸ€– Tavily AI" + }[x] + ) + + # Add option for topic image + generate_topic_image = st.checkbox("Generate Topic Image", value=True, help="Generate an AI image related to your article topic") + + submit = st.form_submit_button("Generate Article") + + if submit and topic and industry: + # Create a status container + status_container = st.empty() + + with st.spinner(f"Researching and generating your article using {search_engine.title()}..."): + # Generate topic image if requested + if generate_topic_image: + status_container.text("🎨 Generating topic image...") + try: + # Generate image prompt + image_prompt = f"""Create a professional, LinkedIn-appropriate image for an article about {topic} in the {industry} industry. + The image should be: + - Professional and business-appropriate + - Visually engaging + - Relevant to both the topic and industry + - Suitable as a LinkedIn article header + """ + + # Import the image generation function + from .....gpt_providers.text_to_image_generation.main_generate_image_from_prompt import generate_image + + # Generate the image + image_path = generate_image(image_prompt) + if image_path: + st.session_state.article_data["topic_image"] = image_path + status_container.success("βœ… Topic image generated!") + except Exception as e: + logger.error(f"Error generating topic image: {e}") + status_container.error("⚠️ Could not generate topic image. Proceeding with article generation.") + + # Research phase + status_container.text(f"πŸ” Phase 1: Researching topic with {search_engine.title()}...") + research_results = generator.research_topic(topic, industry, search_engine, status_container) + st.session_state.article_data["research_results"] = research_results + + if research_results: + # Outline phase + status_container.text("πŸ“‹ Phase 2: Creating article outline...") + outline = generator.generate_outline(research_results) + st.session_state.article_data["outline"] = outline + + # Content generation phase + status_container.text("✍️ Phase 3: Writing article content...") + article_content = generator.generate_article_content(outline, topic, industry, tone.lower()) + st.session_state.article_data["article_content"] = article_content + + # Image generation phase + status_container.text("🎨 Phase 4: Generating section images...") + section_images = generator.generate_section_images(outline, article_content, topic, industry) + st.session_state.article_data["section_images"] = section_images + + # SEO optimization phase + status_container.text("🎯 Phase 5: Optimizing SEO...") + seo_metadata = generator.generate_seo_metadata(article_content, outline) + st.session_state.article_data["seo_metadata"] = seo_metadata + + status_container.text("βœ… Article generation complete!") + else: + status_container.error(f"❌ No results found from {search_engine.title()}. Please try a different research source or modify your topic.") + + # Display results if we have article data + if st.session_state.article_data["article_content"]: + st.markdown("---") + + # Create tabs for different sections + tab1, tab2, tab3, tab4 = st.tabs([ + "πŸ“ Article Content", + "🎨 Images & Visuals", + "🎯 SEO & Metadata", + "πŸ“Š Research Results" + ]) + + with tab1: + # Article preview + st.subheader("Article Preview") + + # Display topic image if available + if st.session_state.article_data.get("topic_image"): + st.image(st.session_state.article_data["topic_image"], + caption="Generated Topic Image", + use_container_width=True) + + # Add styling for the article container + st.markdown(""" + + """, unsafe_allow_html=True) + + # Display the article in the styled container + outline = st.session_state.article_data["outline"] + st.markdown(f""" +
+

{outline['headline']}

+

+ {outline['subheadline']} +

+ {st.session_state.article_data["article_content"]} +
+ """, unsafe_allow_html=True) + + # Action buttons + col1, col2, col3 = st.columns(3) + with col1: + if st.button("πŸ“‹ Copy to Clipboard", key="copy_article"): + st.success("Article copied to clipboard!") + with col2: + if st.button("πŸ’Ύ Download as HTML", key="download_article"): + st.success("Article downloaded successfully!") + with col3: + if st.button("πŸ”„ Generate New Article", key="new_article"): + st.session_state.article_data = { + "research_results": None, + "outline": None, + "article_content": "", + "section_images": [], + "seo_metadata": None, + "generated_images": {}, + "topic_image": None + } + st.experimental_rerun() + + with tab2: + st.subheader("Article Images") + + # Topic image section + st.markdown("### 🎨 Main Article Image") + + # Display current topic image if available + if st.session_state.article_data.get("topic_image"): + st.image(st.session_state.article_data["topic_image"], + caption="Current Article Image", + use_container_width=True) + + # Image prompt selection and refinement + if st.session_state.article_data.get("section_images"): + header_image = st.session_state.article_data["section_images"][0] + + # Display main prompt + st.markdown("#### Current Image Prompt") + current_prompt = st.text_area( + "Edit prompt if needed:", + value=header_image["main_prompt"], + height=100, + key="main_image_prompt" + ) + + # Display alternative prompts + st.markdown("#### Alternative Prompt Suggestions") + selected_prompt = st.radio( + "Select an alternative prompt or use the current one:", + ["Current Prompt"] + header_image["alternative_prompts"], + key="prompt_selection" + ) + + # Custom prompt input + custom_prompt = st.text_area( + "Or write your own prompt:", + value="", + height=100, + key="custom_image_prompt", + help="Write your own custom image prompt based on the article topic" + ) + + # Generate image button + col1, col2 = st.columns(2) + with col1: + if st.button("🎨 Generate New Image", key="generate_new_image"): + try: + # Determine which prompt to use + final_prompt = custom_prompt if custom_prompt else ( + current_prompt if selected_prompt == "Current Prompt" + else selected_prompt + ) + + # Generate new image + from .....gpt_providers.text_to_image_generation.main_generate_image_from_prompt import generate_image + image_path = generate_image(final_prompt) + + if image_path: + st.session_state.article_data["topic_image"] = image_path + st.success("βœ… New image generated successfully!") + st.experimental_rerun() + except Exception as e: + st.error(f"⚠️ Error generating image: {str(e)}") + + with col2: + if st.button("πŸ”„ Generate More Prompt Suggestions", key="generate_more_prompts"): + # Generate new variations + new_variations = generator._suggest_image_variations( + topic, + industry + ) + header_image["alternative_prompts"].extend(new_variations) + st.success("βœ… New prompt suggestions added!") + st.experimental_rerun() + + # Section images + st.markdown("### πŸ“‘ Section Images") + if st.session_state.article_data.get("section_images"): + for section_image in st.session_state.article_data["section_images"][1:]: # Skip header image + with st.expander(f"πŸ–ΌοΈ {section_image['section']} Image"): + # Display current section image if available + if section_image["section"] in st.session_state.article_data["generated_images"]: + st.image( + st.session_state.article_data["generated_images"][section_image["section"]], + caption=f"Current image for {section_image['section']}", + use_container_width=True + ) + + # Section image prompt and generation + section_prompt = st.text_area( + "Image prompt:", + value=section_image["main_prompt"], + height=100, + key=f"prompt_{section_image['section']}" + ) + + if st.button("Generate Image", key=f"gen_img_{section_image['section']}"): + try: + from .....gpt_providers.text_to_image_generation.main_generate_image_from_prompt import generate_image + image_path = generate_image(section_prompt) + + if image_path: + st.session_state.article_data["generated_images"][section_image["section"]] = image_path + st.success(f"βœ… Image generated for {section_image['section']}!") + st.experimental_rerun() + except Exception as e: + st.error(f"⚠️ Error generating image: {str(e)}") + + with tab3: + st.subheader("SEO Optimization") + + seo_metadata = st.session_state.article_data["seo_metadata"] + + # Display SEO metadata + col1, col2 = st.columns(2) + + with col1: + st.markdown("### Focus Keyword") + st.code(seo_metadata["focus_keyword"]) + + st.markdown("### Secondary Keywords") + for keyword in seo_metadata["secondary_keywords"]: + st.markdown(f"- {keyword}") + + with col2: + st.markdown("### Meta Description") + st.text_area( + label="Meta Description", + value=seo_metadata["meta_description"], + height=100, + disabled=True, + key="meta_description_area", + label_visibility="collapsed" + ) + + st.markdown("### Article Tags") + for tag in seo_metadata["article_tags"]: + st.markdown(f"- {tag}") + + st.markdown("### Social Sharing Description") + st.text_area( + label="Social Sharing Description", + value=seo_metadata["social_description"], + height=100, + disabled=True, + key="social_description_area", + label_visibility="collapsed" + ) + + with tab4: + st.subheader("Research Results") + + research_results = st.session_state.article_data["research_results"] + + # Display key insights + st.markdown("### Key Insights") + for insight in research_results["key_insights"]: + st.markdown(f"- {insight}") + + # Display statistics + st.markdown("### Statistics & Data Points") + for stat in research_results["statistics"]: + st.markdown(f"- {stat}") + + # Display expert opinions + st.markdown("### Expert Opinions") + for opinion in research_results["expert_opinions"]: + st.markdown(f"> {opinion}") + + # Display sources + st.markdown("### Sources") + for source in research_results["sources"]: + st.markdown(f"- {source}") + + +if __name__ == "__main__": + linkedin_article_generator_ui() \ No newline at end of file diff --git a/lib/ai_writers/linkedin_writer/modules/carousel_generator/README.md b/lib/ai_writers/linkedin_writer/modules/carousel_generator/README.md new file mode 100644 index 00000000..7f82d932 --- /dev/null +++ b/lib/ai_writers/linkedin_writer/modules/carousel_generator/README.md @@ -0,0 +1,281 @@ +# LinkedIn Carousel Generator 🎯 + +An AI-powered tool designed specifically for LinkedIn content creators to generate engaging, visually stunning carousel posts that drive engagement and showcase expertise. + +## ✨ Key Features + +### Content Creation +- **AI-Powered Research**: Multi-source research integration (Metaphor, Google, Tavily) +- **Dynamic Slide Generation**: Auto-generation of 3-10 cohesive slides +- **Smart Content Structure**: Optimized flow from hook to call-to-action +- **Visual Content**: AI-generated professional visuals for each slide +- **Content Types**: How-to Guides, Lists, Stories, Case Studies, Tips & Tricks + +### Visual Excellence +- **Professional Imagery**: Custom AI-generated visuals per slide +- **Brand Consistency**: Cohesive visual style across slides +- **Visual Hierarchy**: Optimized layout for maximum impact +- **Custom Styling**: Flexible design options for each slide + +### Research Integration +- **Triple-Engine Research**: + - πŸ” Metaphor AI: Deep industry insights + - 🌐 Google Search: Comprehensive coverage + - πŸ€– Tavily AI: Focused research +- **Real-Time Data**: Current trends and statistics +- **Expert Insights**: Professional quotes and perspectives + +## πŸš€ Getting Started + +### 1. Carousel Creation Process + +#### Initial Setup +- Choose your topic +- Select your industry +- Pick your content style: + - Professional + - Educational + - Conversational + - Inspiring + - Technical + +#### Content Type Selection +Choose from: +- How-to Guide: Step-by-step instructions +- List: Curated points or tips +- Story: Narrative-driven content +- Case Study: Real-world examples +- Tips & Tricks: Quick, actionable advice + +#### Slide Configuration +- Select number of slides (3-10) +- Choose research source +- Enable/disable features: + - Custom visuals + - Content optimization + - Research integration + +### 2. Carousel Preview and Editing + +#### Slide Navigation +- Previous/Next navigation +- Slide overview +- Individual slide editing +- Real-time preview + +#### Content Editing +- Edit headings +- Modify subheadings +- Update main content +- Customize visuals +- Optimize text + +#### Visual Customization +- Edit image prompts +- Generate new visuals +- Adjust style settings +- Preview changes + +## πŸ’‘ Best Practices + +### 1. Content Strategy +- **First Slide** + - Strong hook + - Clear value proposition + - Attention-grabbing visual + - Topic introduction + +- **Middle Slides** + - Logical progression + - Supporting evidence + - Engaging visuals + - Clear explanations + +- **Last Slide** + - Strong call-to-action + - Next steps + - Contact information + - Engagement prompt + +### 2. Visual Strategy +- **Consistency** + - Cohesive style + - Brand colors + - Font hierarchy + - Layout patterns + +- **Image Quality** + - Professional aesthetics + - Clear messaging + - Relevant visuals + - Brand alignment + +### 3. Content Flow +- **Progression** + - Logical sequence + - Clear transitions + - Building complexity + - Cohesive narrative + +## πŸ“Š Content Types Guide + +### How-to Guide +- Clear steps +- Visual instructions +- Progress indicators +- Action items + +### List Format +- Clear numbering +- Consistent structure +- Visual hierarchy +- Easy scanning + +### Story Format +- Engaging narrative +- Character elements +- Visual journey +- Emotional connection + +### Case Study +- Problem statement +- Solution process +- Results/outcomes +- Key learnings + +### Tips & Tricks +- Quick insights +- Actionable advice +- Visual examples +- Implementation guidance + +## 🎨 Design Guidelines + +### Visual Hierarchy +- **Headlines** + - Clear visibility + - Consistent sizing + - Impactful fonts + - Color contrast + +- **Content Layout** + - Balanced design + - White space + - Reading flow + - Visual anchors + +### Brand Elements +- **Style Guide** + - Color palette + - Typography + - Logo placement + - Visual elements + +## πŸ“± Optimization + +### Mobile View +- Text readability +- Image scaling +- Touch-friendly +- Quick loading + +### Platform Specs +- LinkedIn dimensions +- File size limits +- Format requirements +- Quality standards + +## πŸ’ͺ Engagement Tips + +### 1. Content Hooks +- **First Slide** + - Compelling question + - Bold statement + - Surprising fact + - Clear benefit + +### 2. Visual Impact +- **Each Slide** + - Professional quality + - Clear message + - Brand alignment + - Emotional appeal + +### 3. Call-to-Action +- **Last Slide** + - Clear direction + - Value offer + - Next steps + - Engagement prompt + +## πŸ› οΈ Technical Features + +### Performance +- Fast generation +- Real-time preview +- Smooth editing +- Quick exports + +### Export Options +- Individual slides +- Complete carousel +- Various formats +- High resolution + +## πŸ“ˆ Success Tips + +### Content Quality +- Research-backed +- Professional tone +- Clear messaging +- Valuable insights + +### Visual Excellence +- Professional design +- Consistent branding +- Quality visuals +- Mobile optimization + +### Engagement Focus +- Interactive elements +- Question prompts +- Discussion starters +- Clear CTAs + +## πŸ” Quick Tips + +1. **Optimal Length** + - 3-5 slides for simple topics + - 6-8 slides for detailed guides + - 8-10 slides for comprehensive content + +2. **Visual Best Practices** + - One main point per slide + - Clear, readable text + - High-quality images + - Consistent branding + +3. **Content Flow** + - Hook β†’ Content β†’ CTA + - Logical progression + - Clear transitions + - Strong ending + +4. **Engagement Boosters** + - Ask questions + - Include statistics + - Share insights + - Prompt discussion + +--- + +Transform your LinkedIn presence with professional, engaging carousel posts! πŸš€ + +Remember: +- Keep each slide focused +- Maintain visual consistency +- Tell a compelling story +- End with clear action steps +- Test and optimize regularly + +Start creating stunning LinkedIn carousels that showcase your expertise! πŸ’« \ No newline at end of file diff --git a/lib/ai_writers/linkedin_writer/modules/carousel_generator/__init__.py b/lib/ai_writers/linkedin_writer/modules/carousel_generator/__init__.py new file mode 100644 index 00000000..643198bb --- /dev/null +++ b/lib/ai_writers/linkedin_writer/modules/carousel_generator/__init__.py @@ -0,0 +1,10 @@ +""" +LinkedIn Carousel Generator Package + +This package provides functionality for generating LinkedIn carousel posts with +AI-powered content and visuals. +""" + +from .linkedin_carousel_generator import linkedin_carousel_generator_ui, LinkedInCarouselGenerator, CarouselSlide + +__all__ = ['linkedin_carousel_generator_ui', 'LinkedInCarouselGenerator', 'CarouselSlide'] \ No newline at end of file diff --git a/lib/ai_writers/linkedin_writer/modules/carousel_generator/linkedin_carousel_generator.py b/lib/ai_writers/linkedin_writer/modules/carousel_generator/linkedin_carousel_generator.py new file mode 100644 index 00000000..bbfe5aa5 --- /dev/null +++ b/lib/ai_writers/linkedin_writer/modules/carousel_generator/linkedin_carousel_generator.py @@ -0,0 +1,409 @@ +""" +LinkedIn Carousel Post Generator + +This module provides functionality for generating LinkedIn carousel posts with +AI-powered content and visuals. +""" + +import streamlit as st +from typing import Dict, List, Optional +from loguru import logger +import json + +from .....gpt_providers.text_generation.main_text_generation import llm_text_gen +from .....gpt_providers.text_to_image_generation.main_generate_image_from_prompt import generate_image +from .....ai_web_researcher.gpt_online_researcher import do_google_serp_search +from .....ai_web_researcher.metaphor_basic_neural_web_search import metaphor_search_articles +from .....ai_web_researcher.tavily_ai_search import do_tavily_ai_search + + +class CarouselSlide: + """Represents a single slide in the carousel.""" + + def __init__(self, index: int, slide_type: str = "text_and_image"): + self.index = index + self.slide_type = slide_type + self.content = "" + self.image_prompt = "" + self.image_path = None + self.heading = "" + self.subheading = "" + + def to_dict(self) -> Dict: + """Convert slide to dictionary format.""" + return { + "index": self.index, + "type": self.slide_type, + "content": self.content, + "image_prompt": self.image_prompt, + "image_path": self.image_path, + "heading": self.heading, + "subheading": self.subheading + } + + +class LinkedInCarouselGenerator: + """ + Generator for LinkedIn carousel posts with AI-powered content and visuals. + """ + + def __init__(self): + """Initialize the carousel generator.""" + self.slides: List[CarouselSlide] = [] + self.topic = "" + self.industry = "" + self.tone = "" + self.content_type = "" + self.num_slides = 0 + self.research_results = {} + + def research_topic(self, topic: str, industry: str, search_engine: str = "metaphor") -> Dict: + """Research the topic to gather content for carousel slides.""" + try: + # Construct research query + search_query = f""" + Find detailed professional content about '{topic}' in {industry} industry + focusing on: + - Key concepts and main points + - Statistics and data + - Examples and case studies + - Best practices + - Expert insights + """ + + # Perform research using selected engine + if search_engine == "metaphor": + results = metaphor_search_articles(search_query) + elif search_engine == "google": + results = do_google_serp_search(search_query) + elif search_engine == "tavily": + results = do_tavily_ai_search(search_query) + + self.research_results = results + return results + + except Exception as e: + logger.error(f"Error researching topic: {e}") + return {} + + def generate_slide_content(self, topic: str, industry: str, tone: str, content_type: str, num_slides: int) -> List[CarouselSlide]: + """Generate content for carousel slides.""" + try: + # Store parameters + self.topic = topic + self.industry = industry + self.tone = tone + self.content_type = content_type + self.num_slides = num_slides + + # Generate content structure + structure_prompt = f""" + Create a {num_slides}-slide carousel post about '{topic}' for {industry} industry. + Style: {content_type} (e.g., How-to, List, Story) + Tone: {tone} + + For each slide, provide: + 1. Heading (short, attention-grabbing) + 2. Subheading (supporting context) + 3. Main content (clear, concise points) + 4. Image prompt (visual representation) + + Format as JSON: + {{ + "slides": [ + {{ + "index": 1, + "heading": "Attention-grabbing title", + "subheading": "Supporting context", + "content": "Main slide content", + "image_prompt": "Detailed image generation prompt" + }} + ] + }} + + Requirements: + - First slide should hook the audience + - Maintain clear progression of ideas + - Each slide should be self-contained but connected + - Last slide should have clear call-to-action + - Keep text concise and impactful + - Ensure all content is professional and LinkedIn-appropriate + """ + + # Generate structure using LLM + carousel_structure = llm_text_gen(structure_prompt) + + try: + structure_data = json.loads(carousel_structure) + + # Create slides from structure + self.slides = [] + for slide_data in structure_data["slides"]: + slide = CarouselSlide(slide_data["index"]) + slide.heading = slide_data["heading"] + slide.subheading = slide_data["subheading"] + slide.content = slide_data["content"] + slide.image_prompt = slide_data["image_prompt"] + self.slides.append(slide) + + return self.slides + + except json.JSONDecodeError: + logger.error("Failed to parse carousel structure") + return [] + + except Exception as e: + logger.error(f"Error generating slide content: {e}") + return [] + + def generate_slide_image(self, slide: CarouselSlide) -> Optional[str]: + """Generate an image for a carousel slide.""" + try: + # Generate image using the slide's prompt + image_path = generate_image(slide.image_prompt) + slide.image_path = image_path + return image_path + except Exception as e: + logger.error(f"Error generating slide image: {e}") + return None + + def optimize_content(self, slide: CarouselSlide) -> CarouselSlide: + """Optimize slide content for engagement.""" + try: + optimization_prompt = f""" + Optimize this carousel slide content for maximum LinkedIn engagement: + + Current Content: + Heading: {slide.heading} + Subheading: {slide.subheading} + Content: {slide.content} + + Requirements: + 1. Make heading more attention-grabbing + 2. Ensure content is concise and impactful + 3. Add relevant emojis where appropriate + 4. Optimize for readability + 5. Keep professional tone + 6. Maintain focus on {self.topic} + + Return as JSON: + {{ + "heading": "Optimized heading", + "subheading": "Optimized subheading", + "content": "Optimized content" + }} + """ + + optimized = llm_text_gen(optimization_prompt) + + try: + optimized_data = json.loads(optimized) + slide.heading = optimized_data["heading"] + slide.subheading = optimized_data["subheading"] + slide.content = optimized_data["content"] + except json.JSONDecodeError: + logger.error("Failed to parse optimized content") + + return slide + + except Exception as e: + logger.error(f"Error optimizing slide content: {e}") + return slide + + +def linkedin_carousel_generator_ui(): + """Streamlit UI for the LinkedIn Carousel Generator.""" + st.title("πŸ”„ LinkedIn Carousel Post Generator") + st.write("Create engaging carousel posts that showcase your content professionally") + + # Initialize generator + generator = LinkedInCarouselGenerator() + + # Initialize session state + if "carousel_data" not in st.session_state: + st.session_state.carousel_data = { + "slides": [], + "research_results": None, + "generated_images": {}, + "current_slide": 0 + } + + # Input form + with st.form("carousel_form"): + topic = st.text_input("Topic", placeholder="Enter the main topic of your carousel") + industry = st.text_input("Industry", placeholder="e.g., Technology, Healthcare, Finance") + + col1, col2, col3 = st.columns(3) + + with col1: + tone = st.selectbox( + "Tone", + ["Professional", "Educational", "Conversational", "Inspiring", "Technical"] + ) + + with col2: + content_type = st.selectbox( + "Content Type", + ["How-to Guide", "List", "Story", "Case Study", "Tips & Tricks"] + ) + + with col3: + num_slides = st.slider("Number of Slides", min_value=3, max_value=10, value=5) + + # Research source selection + search_engine = st.radio( + "Research Source", + ["metaphor", "google", "tavily"], + format_func=lambda x: { + "metaphor": "πŸ” Metaphor AI", + "google": "🌐 Google Search", + "tavily": "πŸ€– Tavily AI" + }[x] + ) + + submit = st.form_submit_button("Generate Carousel") + + if submit and topic and industry: + # Create status container + status_container = st.empty() + + with st.spinner("Researching and generating your carousel..."): + # Research phase + status_container.text("πŸ” Phase 1: Researching topic...") + research_results = generator.research_topic(topic, industry, search_engine) + st.session_state.carousel_data["research_results"] = research_results + + if research_results: + # Content generation phase + status_container.text("✍️ Phase 2: Creating carousel content...") + slides = generator.generate_slide_content( + topic, industry, tone.lower(), content_type.lower(), num_slides + ) + st.session_state.carousel_data["slides"] = slides + + # Image generation phase + status_container.text("🎨 Phase 3: Generating slide visuals...") + for slide in slides: + image_path = generator.generate_slide_image(slide) + if image_path: + st.session_state.carousel_data["generated_images"][slide.index] = image_path + + status_container.text("βœ… Carousel generation complete!") + else: + status_container.error("❌ No research results found. Please try a different topic or research source.") + + # Display carousel if we have slides + if st.session_state.carousel_data["slides"]: + st.markdown("---") + + # Create tabs for different views + tab1, tab2 = st.tabs(["🎯 Carousel Preview", "πŸ“Š Research Results"]) + + with tab1: + st.subheader("Carousel Preview") + + # Slide navigation + col1, col2, col3 = st.columns([1, 3, 1]) + + with col1: + if st.button("⬅️ Previous") and st.session_state.carousel_data["current_slide"] > 0: + st.session_state.carousel_data["current_slide"] -= 1 + + with col3: + if st.button("Next ➑️") and st.session_state.carousel_data["current_slide"] < len(st.session_state.carousel_data["slides"]) - 1: + st.session_state.carousel_data["current_slide"] += 1 + + # Display current slide + current_slide = st.session_state.carousel_data["slides"][st.session_state.carousel_data["current_slide"]] + + st.markdown(f""" +
+

{current_slide.heading}

+ +

{current_slide.subheading}

+ +

{current_slide.content}

+
+ """, unsafe_allow_html=True) + + # Display slide image if available + if current_slide.image_path: + st.image(current_slide.image_path, + caption=f"Slide {current_slide.index} Image", + use_container_width=True) + + # Slide controls + st.markdown(f"**Slide {current_slide.index} of {len(st.session_state.carousel_data['slides'])}**") + + # Edit options + with st.expander("✏️ Edit Slide"): + # Edit form for current slide + edited_heading = st.text_input("Heading", value=current_slide.heading) + edited_subheading = st.text_input("Subheading", value=current_slide.subheading) + edited_content = st.text_area("Content", value=current_slide.content) + + if st.button("Update Slide"): + current_slide.heading = edited_heading + current_slide.subheading = edited_subheading + current_slide.content = edited_content + st.success("βœ… Slide updated!") + + if st.button("Optimize Content"): + optimized_slide = generator.optimize_content(current_slide) + st.success("βœ… Content optimized!") + st.experimental_rerun() + + # Export options + with st.expander("πŸ’Ύ Export Options"): + col1, col2 = st.columns(2) + with col1: + if st.button("Export Current Slide"): + st.success("Slide exported successfully!") + with col2: + if st.button("Export Full Carousel"): + st.success("Carousel exported successfully!") + + with tab2: + st.subheader("Research Results") + + research_results = st.session_state.carousel_data["research_results"] + + if research_results: + # Display key insights + st.markdown("### Key Insights") + for insight in research_results.get("key_insights", []): + st.markdown(f"- {insight}") + + # Display statistics + st.markdown("### Statistics & Data") + for stat in research_results.get("statistics", []): + st.markdown(f"- {stat}") + + # Display sources + st.markdown("### Sources") + for source in research_results.get("sources", []): + st.markdown(f"- {source}") + else: + st.info("No research results available.") + + +if __name__ == "__main__": + linkedin_carousel_generator_ui() \ No newline at end of file diff --git a/lib/ai_writers/linkedin_writer/modules/comment_response_generator/README.md b/lib/ai_writers/linkedin_writer/modules/comment_response_generator/README.md new file mode 100644 index 00000000..76af2649 --- /dev/null +++ b/lib/ai_writers/linkedin_writer/modules/comment_response_generator/README.md @@ -0,0 +1,261 @@ +# LinkedIn Comment Response Generator + +A powerful AI-powered tool for generating professional, engaging, and contextually appropriate responses to LinkedIn comments. This module helps maintain active engagement on LinkedIn by providing intelligent, well-crafted responses that build relationships and drive meaningful discussions. + +## Features + +### 1. Intelligent Comment Analysis +- Sentiment analysis of comments +- Identification of key discussion points +- Recognition of user intent and tone +- Context-aware interpretation +- Engagement opportunity detection + +### 2. Response Generation +- Multiple response types: + - General professional responses + - Disagreement handling + - Value-add responses + - Resource suggestions + - Follow-up questions +- Brand voice customization +- Engagement goal targeting +- Context-aware responses +- Professional tone maintenance + +### 3. Disagreement Handling +- Diplomatic response generation +- Evidence-based arguments +- Common ground identification +- Constructive dialogue promotion +- Relationship preservation +- Professional conflict resolution + +### 4. Value-Add Responses +- Industry-specific insights +- Actionable recommendations +- Expert perspective sharing +- Resource linking +- Knowledge sharing +- Engagement hooks + +### 5. Resource Suggestions +- Curated learning materials +- Progressive learning paths +- Practical application tips +- Follow-up support +- Expertise-level matching +- Topic-specific resources + +### 6. Follow-up Questions +- Discussion deepening +- Multiple perspective exploration +- Experience sharing prompts +- Reflection encouragement +- Engagement maintenance +- Value exploration + +### 7. Tone Optimization +- Multiple tone options: + - Professional + - Friendly + - Expert + - Supportive + - Diplomatic + - Appreciative +- Audience-specific adjustments +- Brand voice alignment +- Engagement optimization +- Relationship building focus + +## Usage + +### Basic Response Generation +```python +from lib.ai_writers.linkedin_writer.modules.comment_response_generator import LinkedInCommentResponseGenerator + +# Initialize the generator +generator = LinkedInCommentResponseGenerator() + +# Generate a response +response = await generator.generate_response( + comment="Your comment here", + post_context="Original post context", + brand_voice="professional", + engagement_goal="continue_discussion" +) +``` + +### Handling Disagreements +```python +# Generate a diplomatic response +response = await generator.handle_disagreement( + comment="Disagreeing comment", + post_context="Post context", + brand_voice="diplomatic" +) +``` + +### Value-Add Responses +```python +# Generate a value-adding response +response = await generator.generate_value_add_response( + comment="Comment to respond to", + industry="Technology", + expertise_areas=["AI", "Machine Learning", "Data Science"] +) +``` + +### Resource Suggestions +```python +# Suggest resources +response = await generator.suggest_resources( + comment="Comment requesting resources", + topic="Artificial Intelligence", + expertise_level="intermediate" +) +``` + +### Follow-up Questions +```python +# Generate follow-up questions +response = await generator.generate_follow_up_questions( + comment="Original comment", + discussion_context="Discussion context" +) +``` + +### Tone Optimization +```python +# Optimize response tone +optimized = await generator.optimize_response_tone( + response="Your response", + target_tone="professional", + audience="Tech professionals" +) +``` + +## UI Interface + +The module includes a Streamlit-based user interface with the following sections: + +1. **General Response Tab** + - Comment input + - Post context + - Brand voice selection + - Engagement goal selection + - Response generation + - Strategy display + +2. **Handle Disagreement Tab** + - Disagreeing comment input + - Context input + - Brand voice selection + - Diplomatic response generation + - Strategy display + +3. **Value-Add Response Tab** + - Comment input + - Industry specification + - Expertise areas input + - Value-adding response generation + - Component display + +4. **Resource Suggestions Tab** + - Comment input + - Topic specification + - Expertise level selection + - Resource suggestion generation + - Resource details display + +5. **Follow-up Questions Tab** + - Comment input + - Discussion context + - Question generation + - Strategy display + +6. **Tone Optimization Section** + - Response input + - Target tone selection + - Audience specification + - Tone optimization + - Optimization details display + +## Implementation Details + +### Core Components + +1. **LinkedInCommentResponseGenerator Class** + - Main generator class + - Response tone definitions + - Comment type definitions + - Core response generation methods + +2. **Response Generation Methods** + - `analyze_comment`: Analyzes comment sentiment and intent + - `generate_response`: Creates contextually appropriate responses + - `handle_disagreement`: Generates diplomatic responses + - `generate_value_add_response`: Creates value-adding responses + - `suggest_resources`: Provides relevant resource suggestions + - `generate_follow_up_questions`: Creates engaging follow-up questions + - `optimize_response_tone`: Adjusts response tone for target audience + +3. **UI Implementation** + - Streamlit-based interface + - Tab-based organization + - Interactive input fields + - Real-time response generation + - Detailed strategy displays + +### Dependencies + +- Streamlit +- AI text generation capabilities +- Web research tools +- JSON processing +- Async/await support + +## Best Practices + +1. **Response Generation** + - Always provide context + - Select appropriate brand voice + - Define clear engagement goals + - Review generated responses + - Optimize tone for audience + +2. **Disagreement Handling** + - Maintain professionalism + - Focus on common ground + - Provide evidence + - Encourage dialogue + - Preserve relationships + +3. **Value-Add Responses** + - Share relevant expertise + - Provide actionable insights + - Include supporting evidence + - Suggest practical applications + - Maintain engagement + +4. **Resource Suggestions** + - Match expertise level + - Provide learning path + - Include application tips + - Offer follow-up support + - Ensure resource accessibility + +5. **Follow-up Questions** + - Deepen discussion + - Explore new angles + - Encourage participation + - Maintain professionalism + - Drive engagement + +## Contributing + +Contributions are welcome! Please feel free to submit a Pull Request. + +## License + +This project is licensed under the MIT License - see the LICENSE file for details. \ No newline at end of file diff --git a/lib/ai_writers/linkedin_writer/modules/comment_response_generator/__init__.py b/lib/ai_writers/linkedin_writer/modules/comment_response_generator/__init__.py new file mode 100644 index 00000000..e34d8318 --- /dev/null +++ b/lib/ai_writers/linkedin_writer/modules/comment_response_generator/__init__.py @@ -0,0 +1,11 @@ +""" +LinkedIn Comment Response Generator Module + +This module provides AI-powered generation of professional and engaging responses +to comments on LinkedIn posts. +""" + +from .linkedin_comment_response_generator import LinkedInCommentResponseGenerator +from .linkedin_comment_response_generator_ui import linkedin_comment_response_generator_ui + +__all__ = ['LinkedInCommentResponseGenerator', 'linkedin_comment_response_generator_ui'] \ No newline at end of file diff --git a/lib/ai_writers/linkedin_writer/modules/comment_response_generator/linkedin_comment_response_generator.py b/lib/ai_writers/linkedin_writer/modules/comment_response_generator/linkedin_comment_response_generator.py new file mode 100644 index 00000000..ac70d268 --- /dev/null +++ b/lib/ai_writers/linkedin_writer/modules/comment_response_generator/linkedin_comment_response_generator.py @@ -0,0 +1,346 @@ +""" +LinkedIn Comment Response Generator + +This module provides AI-powered generation of professional and engaging responses +to comments on LinkedIn posts. +""" + +import json +from typing import Dict, List, Optional +from loguru import logger + +from .....gpt_providers.text_generation.main_text_generation import llm_text_gen +from .....ai_web_researcher.gpt_online_researcher import do_google_serp_search +from .....ai_web_researcher.metaphor_basic_neural_web_search import metaphor_search_articles +from .....ai_web_researcher.tavily_ai_search import do_tavily_ai_search + +class LinkedInCommentResponseGenerator: + """ + AI-powered generator for professional and engaging LinkedIn comment responses. + """ + + def __init__(self): + """Initialize the LinkedIn Comment Response Generator.""" + self.response_tones = [ + "professional", + "friendly", + "expert", + "supportive", + "diplomatic", + "appreciative" + ] + + self.comment_types = [ + "question", + "agreement", + "disagreement", + "appreciation", + "criticism", + "suggestion", + "experience_sharing" + ] + + async def analyze_comment(self, comment: str) -> Dict: + """ + Analyze the comment to determine its type, tone, and key points. + + Args: + comment: The comment text to analyze + + Returns: + Dict containing comment analysis + """ + prompt = f""" + As a LinkedIn engagement expert, analyze this comment: + + Comment: {comment} + + Provide a detailed analysis including: + - Comment type (question/agreement/disagreement/etc.) + - Emotional tone + - Key points or questions raised + - Intent and context + - Engagement potential + + Return a JSON with: + - comment_type: The type of comment + - tone: Emotional tone + - key_points: List of main points + - questions: Any questions raised + - intent: Perceived intent + - engagement_potential: High/Medium/Low + """ + + response = await llm_text_gen(prompt) + return json.loads(response) + + async def generate_response(self, + comment: str, + post_context: str, + brand_voice: str, + engagement_goal: str + ) -> Dict: + """ + Generate an appropriate response to the comment. + + Args: + comment: The comment to respond to + post_context: The context of the original post + brand_voice: Desired brand voice/tone + engagement_goal: Goal for the response + + Returns: + Dict containing response and strategy + """ + # First analyze the comment + analysis = await self.analyze_comment(comment) + + prompt = f""" + As a LinkedIn engagement expert, generate a response to this comment: + + Comment: {comment} + Post Context: {post_context} + Brand Voice: {brand_voice} + Engagement Goal: {engagement_goal} + + Comment Analysis: {json.dumps(analysis)} + + Generate a response that: + - Maintains professional tone + - Addresses key points/questions + - Aligns with brand voice + - Encourages further engagement + - Builds community + - Adds value + + Return a JSON with: + - response: The generated response + - tone_used: Tone of the response + - key_points_addressed: Points addressed + - engagement_hooks: Elements to encourage interaction + - value_adds: Additional value provided + - follow_up_suggestions: Potential follow-up points + """ + + response = await llm_text_gen(prompt) + return json.loads(response) + + async def handle_disagreement(self, + comment: str, + post_context: str, + brand_voice: str + ) -> Dict: + """ + Generate a professional response to a disagreement. + + Args: + comment: The disagreeing comment + post_context: Original post context + brand_voice: Desired brand voice + + Returns: + Dict containing diplomatic response + """ + prompt = f""" + As a LinkedIn communication expert, craft a diplomatic response to this disagreement: + + Comment: {comment} + Post Context: {post_context} + Brand Voice: {brand_voice} + + Generate a response that: + - Maintains professionalism + - Acknowledges the perspective + - Provides supporting evidence + - Finds common ground + - Keeps discussion constructive + - Invites further dialogue + + Return a JSON with: + - response: The diplomatic response + - acknowledgment: How the perspective was acknowledged + - evidence: Supporting points provided + - common_ground: Areas of agreement + - dialogue_hooks: Elements to continue discussion + """ + + response = await llm_text_gen(prompt) + return json.loads(response) + + async def generate_value_add_response(self, + comment: str, + industry: str, + expertise_areas: List[str] + ) -> Dict: + """ + Generate a response that adds significant value. + + Args: + comment: The comment to respond to + industry: Relevant industry + expertise_areas: Areas of expertise + + Returns: + Dict containing value-adding response + """ + # Research relevant insights + research = await do_tavily_ai_search( + f"latest insights trends {industry} {' '.join(expertise_areas)}" + ) + + prompt = f""" + As a LinkedIn thought leader, generate a value-adding response: + + Comment: {comment} + Industry: {industry} + Expertise Areas: {expertise_areas} + Research Insights: {json.dumps(research)} + + Create a response that: + - Shares relevant insights + - Provides actionable advice + - References credible sources + - Demonstrates expertise + - Encourages implementation + - Invites questions + + Return a JSON with: + - response: The value-adding response + - insights_shared: Key insights provided + - action_items: Actionable takeaways + - sources: Referenced sources + - expertise_demonstrated: How expertise was shown + - engagement_hooks: Questions or prompts + """ + + response = await llm_text_gen(prompt) + return json.loads(response) + + async def suggest_resources(self, + comment: str, + topic: str, + expertise_level: str + ) -> Dict: + """ + Suggest relevant resources in response to a comment. + + Args: + comment: The comment requesting/needing resources + topic: The topic of discussion + expertise_level: User's expertise level + + Returns: + Dict containing resource suggestions + """ + # Research relevant resources + resources = await metaphor_search_articles( + f"best resources tutorials guides {topic} {expertise_level}" + ) + + prompt = f""" + As a LinkedIn learning facilitator, suggest helpful resources: + + Comment: {comment} + Topic: {topic} + Expertise Level: {expertise_level} + Found Resources: {json.dumps(resources)} + + Provide suggestions that: + - Match expertise level + - Cover key aspects + - Include various formats + - Are readily accessible + - Support learning goals + - Encourage application + + Return a JSON with: + - response: Resource suggestion response + - recommended_resources: List of resources + - learning_path: Suggested learning sequence + - application_tips: How to apply resources + - follow_up_support: Additional support offered + """ + + response = await llm_text_gen(prompt) + return json.loads(response) + + async def generate_follow_up_questions(self, + comment: str, + discussion_context: str + ) -> Dict: + """ + Generate engaging follow-up questions to continue the discussion. + + Args: + comment: The comment to generate questions for + discussion_context: Context of the discussion + + Returns: + Dict containing follow-up questions + """ + prompt = f""" + As a LinkedIn engagement expert, generate follow-up questions: + + Comment: {comment} + Discussion Context: {discussion_context} + + Generate questions that: + - Deepen the discussion + - Explore different angles + - Draw out experiences + - Encourage reflection + - Maintain professionalism + - Drive engagement + + Return a JSON with: + - primary_question: Main follow-up question + - secondary_questions: Additional questions + - discussion_angles: New perspectives to explore + - engagement_prompts: Ways to encourage participation + - value_exploration: Areas to uncover value + """ + + response = await llm_text_gen(prompt) + return json.loads(response) + + async def optimize_response_tone(self, + response: str, + target_tone: str, + audience: str + ) -> Dict: + """ + Optimize the tone of a response for the target audience. + + Args: + response: The response to optimize + target_tone: Desired tone + audience: Target audience + + Returns: + Dict containing tone-optimized response + """ + prompt = f""" + As a LinkedIn communication expert, optimize this response's tone: + + Response: {response} + Target Tone: {target_tone} + Audience: {audience} + + Optimize the response to: + - Match desired tone + - Resonate with audience + - Maintain professionalism + - Enhance engagement + - Build rapport + - Reflect brand voice + + Return a JSON with: + - optimized_response: Tone-adjusted response + - tone_adjustments: Changes made + - audience_alignment: How it matches audience + - engagement_potential: Expected engagement + - relationship_building: How it builds connection + """ + + response = await llm_text_gen(prompt) + return json.loads(response) \ No newline at end of file diff --git a/lib/ai_writers/linkedin_writer/modules/comment_response_generator/linkedin_comment_response_generator_ui.py b/lib/ai_writers/linkedin_writer/modules/comment_response_generator/linkedin_comment_response_generator_ui.py new file mode 100644 index 00000000..d3abdf81 --- /dev/null +++ b/lib/ai_writers/linkedin_writer/modules/comment_response_generator/linkedin_comment_response_generator_ui.py @@ -0,0 +1,283 @@ +""" +LinkedIn Comment Response Generator UI + +This module provides the Streamlit UI for the LinkedIn Comment Response Generator. +""" + +import streamlit as st +import json +from typing import Dict, List +from .linkedin_comment_response_generator import LinkedInCommentResponseGenerator + +async def linkedin_comment_response_generator_ui(): + """ + Streamlit UI for the LinkedIn Comment Response Generator. + """ + # Initialize the generator + generator = LinkedInCommentResponseGenerator() + + st.title("LinkedIn Comment Response Generator") + + # Create tabs for different response scenarios + tabs = st.tabs([ + "General Response", + "Handle Disagreement", + "Value-Add Response", + "Resource Suggestions", + "Follow-up Questions" + ]) + + # General Response Tab + with tabs[0]: + st.header("Generate Professional Response") + st.info("Generate an engaging and professional response to a LinkedIn comment") + + comment = st.text_area("Comment to Respond to", height=100) + post_context = st.text_area("Original Post Context", height=100) + + col1, col2 = st.columns(2) + with col1: + brand_voice = st.selectbox( + "Brand Voice", + ["Professional", "Friendly", "Expert", "Supportive", "Diplomatic", "Appreciative"] + ) + + with col2: + engagement_goal = st.selectbox( + "Engagement Goal", + ["Continue Discussion", "Share Knowledge", "Build Relationship", "Address Concern", "Encourage Action"] + ) + + if st.button("Generate Response", key="general_response"): + with st.spinner("Generating response..."): + # First analyze the comment + analysis = await generator.analyze_comment(comment) + + # Display analysis + with st.expander("Comment Analysis", expanded=True): + st.json(analysis) + + # Generate response + response = await generator.generate_response( + comment, + post_context, + brand_voice.lower(), + engagement_goal + ) + + # Display response + st.subheader("Generated Response") + st.success(response['response']) + + # Display strategy + with st.expander("Response Strategy"): + st.write("**Tone Used:**", response['tone_used']) + + st.write("**Key Points Addressed:**") + for point in response['key_points_addressed']: + st.write(f"- {point}") + + st.write("**Engagement Hooks:**") + for hook in response['engagement_hooks']: + st.write(f"- {hook}") + + st.write("**Value Adds:**") + for value in response['value_adds']: + st.write(f"- {value}") + + st.write("**Follow-up Suggestions:**") + for suggestion in response['follow_up_suggestions']: + st.write(f"- {suggestion}") + + # Handle Disagreement Tab + with tabs[1]: + st.header("Handle Disagreement") + st.info("Generate a diplomatic response to a disagreeing comment") + + disagreement_comment = st.text_area("Disagreeing Comment", height=100, key="disagreement_comment") + disagreement_context = st.text_area("Post Context", height=100, key="disagreement_context") + disagreement_voice = st.selectbox( + "Brand Voice", + ["Diplomatic", "Professional", "Expert", "Supportive"], + key="disagreement_voice" + ) + + if st.button("Generate Diplomatic Response"): + with st.spinner("Generating diplomatic response..."): + response = await generator.handle_disagreement( + disagreement_comment, + disagreement_context, + disagreement_voice.lower() + ) + + st.subheader("Diplomatic Response") + st.success(response['response']) + + with st.expander("Response Strategy"): + st.write("**Acknowledgment:**", response['acknowledgment']) + + st.write("**Supporting Evidence:**") + for point in response['evidence']: + st.write(f"- {point}") + + st.write("**Common Ground:**") + for point in response['common_ground']: + st.write(f"- {point}") + + st.write("**Dialogue Continuation:**") + for hook in response['dialogue_hooks']: + st.write(f"- {hook}") + + # Value-Add Response Tab + with tabs[2]: + st.header("Value-Add Response") + st.info("Generate a response that provides significant value") + + value_comment = st.text_area("Comment", height=100, key="value_comment") + industry = st.text_input("Industry") + expertise_areas = st.text_area( + "Areas of Expertise (one per line)", + height=100 + ).split("\n") + + if st.button("Generate Value-Add Response"): + with st.spinner("Researching and generating response..."): + response = await generator.generate_value_add_response( + value_comment, + industry, + expertise_areas + ) + + st.subheader("Value-Adding Response") + st.success(response['response']) + + with st.expander("Value Components"): + st.write("**Key Insights:**") + for insight in response['insights_shared']: + st.write(f"- {insight}") + + st.write("**Action Items:**") + for item in response['action_items']: + st.write(f"- {item}") + + st.write("**Sources:**") + for source in response['sources']: + st.write(f"- {source}") + + st.write("**Expertise Demonstrated:**") + st.write(response['expertise_demonstrated']) + + st.write("**Engagement Hooks:**") + for hook in response['engagement_hooks']: + st.write(f"- {hook}") + + # Resource Suggestions Tab + with tabs[3]: + st.header("Resource Suggestions") + st.info("Suggest helpful resources in response to a comment") + + resource_comment = st.text_area("Comment", height=100, key="resource_comment") + topic = st.text_input("Topic") + expertise_level = st.select_slider( + "Expertise Level", + options=["Beginner", "Intermediate", "Advanced", "Expert"] + ) + + if st.button("Generate Resource Suggestions"): + with st.spinner("Researching and compiling resources..."): + response = await generator.suggest_resources( + resource_comment, + topic, + expertise_level.lower() + ) + + st.subheader("Resource Suggestion Response") + st.success(response['response']) + + with st.expander("Resource Details"): + st.write("**Recommended Resources:**") + for resource in response['recommended_resources']: + st.write(f"- {resource}") + + st.write("**Learning Path:**") + for step in response['learning_path']: + st.write(f"- {step}") + + st.write("**Application Tips:**") + for tip in response['application_tips']: + st.write(f"- {tip}") + + st.write("**Follow-up Support:**") + st.write(response['follow_up_support']) + + # Follow-up Questions Tab + with tabs[4]: + st.header("Follow-up Questions") + st.info("Generate engaging follow-up questions to continue the discussion") + + question_comment = st.text_area("Comment", height=100, key="question_comment") + discussion_context = st.text_area("Discussion Context", height=100, key="question_context") + + if st.button("Generate Follow-up Questions"): + with st.spinner("Generating questions..."): + response = await generator.generate_follow_up_questions( + question_comment, + discussion_context + ) + + st.subheader("Primary Follow-up Question") + st.success(response['primary_question']) + + st.subheader("Secondary Questions") + for question in response['secondary_questions']: + st.info(question) + + with st.expander("Discussion Strategy"): + st.write("**Discussion Angles:**") + for angle in response['discussion_angles']: + st.write(f"- {angle}") + + st.write("**Engagement Prompts:**") + for prompt in response['engagement_prompts']: + st.write(f"- {prompt}") + + st.write("**Value Exploration:**") + for area in response['value_exploration']: + st.write(f"- {area}") + + # Add tone optimization section at the bottom + st.divider() + st.subheader("Response Tone Optimization") + st.info("Optimize the tone of any generated response") + + response_to_optimize = st.text_area("Response to Optimize", height=100) + col1, col2 = st.columns(2) + + with col1: + target_tone = st.selectbox( + "Target Tone", + generator.response_tones + ) + + with col2: + audience = st.text_input("Target Audience") + + if st.button("Optimize Tone"): + with st.spinner("Optimizing response tone..."): + optimized = await generator.optimize_response_tone( + response_to_optimize, + target_tone, + audience + ) + + st.subheader("Tone-Optimized Response") + st.success(optimized['optimized_response']) + + with st.expander("Optimization Details"): + st.write("**Tone Adjustments:**") + for adjustment in optimized['tone_adjustments']: + st.write(f"- {adjustment}") + + st.write("**Audience Alignment:**", optimized['audience_alignment']) + st.write("**Engagement Potential:**", optimized['engagement_potential']) + st.write("**Relationship Building:**", optimized['relationship_building']) \ No newline at end of file diff --git a/lib/ai_writers/linkedin_writer/modules/poll_generator/README.md b/lib/ai_writers/linkedin_writer/modules/poll_generator/README.md new file mode 100644 index 00000000..4871cc45 --- /dev/null +++ b/lib/ai_writers/linkedin_writer/modules/poll_generator/README.md @@ -0,0 +1,216 @@ +# LinkedIn Poll Generator + +## Overview + +The LinkedIn Poll Generator is an AI-powered tool designed to help professionals create engaging, data-driven polls for LinkedIn. This tool leverages advanced AI to generate poll questions, options, and engagement predictions, helping users gather valuable insights from their professional network. + +## Features + +### 1. Poll Creation +- **Multiple Poll Types**: Create various types of polls including: + - Multiple Choice (2-4 options) + - Yes/No + - Rating Scale (1-5) + - Ranking (order items by preference) + - Open-ended (with suggested responses) +- **Customizable Tone**: Generate polls with different tones (professional, casual, authoritative, conversational, thoughtful) +- **Industry-Specific Content**: Tailor polls to specific industries and professional contexts + +### 2. Research Integration +- **Multi-Source Research**: Gather insights from multiple search engines: + - Metaphor (neural search) + - Google SERP + - Tavily AI +- **Insight Extraction**: Automatically extract key insights and trends from research +- **Question Generation**: Generate potential poll questions based on research findings + +### 3. Engagement Prediction +- **Response Prediction**: Forecast expected engagement levels (low, medium, high, viral) +- **Comment & Share Likelihood**: Predict the likelihood of comments and shares +- **Response Distribution**: Estimate the expected distribution of responses across options +- **Insight Generation**: Identify potential insights that could be gained from the poll + +### 4. Optimization +- **Question Improvements**: Get suggestions for improving poll question wording and clarity +- **Option Improvements**: Receive recommendations for enhancing poll options +- **Timing Suggestions**: Learn the optimal days and times to post your poll +- **Audience Targeting**: Identify the most relevant audience segments for your poll +- **Hashtag Recommendations**: Get industry-specific hashtag suggestions + +### 5. Follow-up Content +- **Post Templates**: Receive templates for sharing poll results +- **Visual Suggestions**: Get recommendations for visualizing poll results +- **Next Poll Ideas**: Discover ideas for follow-up polls that build on previous insights +- **Data Visualization**: Receive suggestions for effective data visualization of poll results + +## Usage + +### Basic Workflow + +1. **Select Topic and Industry**: Enter your poll topic and target industry +2. **Choose Poll Type**: Select the type of poll you want to create +3. **Set Tone**: Choose the tone for your poll (professional, casual, etc.) +4. **Research Topic**: Gather insights about your topic (optional) +5. **Generate Poll**: Create your poll with AI-generated questions and options +6. **Review Predictions**: See engagement predictions and response distribution +7. **Optimize**: Get suggestions for improving your poll +8. **Plan Follow-up**: Receive templates and ideas for sharing results + +### Advanced Features + +#### Research Integration +- Use the "Research Topic" button to gather insights before creating your poll +- View key insights, emerging trends, and potential questions based on research +- Use research findings to inform your poll creation + +#### Engagement Prediction +- View predicted engagement levels before posting +- See expected response distribution across options +- Identify potential insights that could be gained + +#### Optimization +- Get suggestions for improving your poll question and options +- Learn the best times to post for maximum engagement +- Identify the most relevant audience segments +- Receive hashtag recommendations + +#### Follow-up Content +- Get templates for sharing poll results +- Receive visual content suggestions +- Discover ideas for follow-up polls +- Get data visualization recommendations + +## Best Practices + +### Creating Effective Polls + +1. **Be Specific**: Ask clear, specific questions that your audience can answer confidently +2. **Keep it Concise**: Use concise language for both questions and options +3. **Avoid Bias**: Ensure your poll doesn't lead respondents toward a particular answer +4. **Use Appropriate Options**: Make sure options are mutually exclusive and collectively exhaustive +5. **Consider Timing**: Post polls at times when your audience is most active +6. **Follow Up**: Share results and insights after the poll closes + +### Maximizing Engagement + +1. **Target Your Audience**: Ensure your poll is relevant to your specific audience +2. **Use Visuals**: Include relevant images or graphics with your poll +3. **Add Context**: Provide brief context or explanation for your poll +4. **Engage with Comments**: Respond to comments to encourage discussion +5. **Share Results**: Follow up with a post sharing the results and insights +6. **Use Hashtags**: Include relevant hashtags to increase visibility + +### Industry-Specific Tips + +#### Technology +- Focus on emerging trends and technologies +- Ask about adoption rates and preferences +- Include technical and non-technical options + +#### Healthcare +- Address current healthcare challenges +- Ask about patient experiences and preferences +- Include options that reflect different stakeholder perspectives + +#### Finance +- Focus on investment preferences and strategies +- Ask about financial planning and management +- Include options that reflect different risk tolerances + +#### Marketing +- Address current marketing trends and challenges +- Ask about content preferences and consumption habits +- Include options that reflect different marketing approaches + +#### Education +- Focus on learning preferences and methods +- Ask about educational technology and tools +- Include options that reflect different learning styles + +## Technical Details + +### Dependencies +- Streamlit: For the user interface +- Plotly: For data visualization +- Loguru: For logging +- GPT Providers: For AI text generation +- Web Research Tools: For gathering insights + +### Architecture +The LinkedIn Poll Generator consists of: +- `LinkedInPollGenerator` class: Core functionality for poll generation +- `linkedin_poll_generator_ui` function: Streamlit UI implementation + +### Integration +The Poll Generator is integrated into the LinkedIn AI Writer suite and can be accessed through the main LinkedIn AI Writer interface. + +## Examples + +### Example 1: Technology Industry Poll +**Question**: "What emerging technology will have the biggest impact on business in 2023?" +**Options**: +1. Artificial Intelligence +2. Blockchain +3. Quantum Computing +4. Extended Reality (XR) + +### Example 2: Healthcare Industry Poll +**Question**: "What is the most significant barrier to telehealth adoption?" +**Options**: +1. Technical issues +2. Privacy concerns +3. Lack of insurance coverage +4. Patient preference for in-person care + +### Example 3: Finance Industry Poll +**Question**: "What investment strategy are you most likely to pursue in a volatile market?" +**Options**: +1. Increase cash reserves +2. Focus on dividend stocks +3. Invest in defensive sectors +4. Look for opportunistic buys + +## Troubleshooting + +### Common Issues + +1. **Research Not Returning Results** + - Try a different search engine + - Use more specific search terms + - Check your internet connection + +2. **Low Engagement Predictions** + - Review question wording for clarity + - Ensure options are relevant and distinct + - Consider targeting a more specific audience + +3. **JSON Parsing Errors** + - This is typically handled automatically by the system + - If persistent, try regenerating the poll + +## Future Enhancements + +- **A/B Testing**: Compare different poll versions +- **Historical Data Analysis**: Learn from past poll performance +- **Competitor Poll Analysis**: Analyze successful polls in your industry +- **Advanced Visualization**: More sophisticated data visualization options +- **Integration with LinkedIn API**: Direct posting to LinkedIn +- **Poll Templates**: Pre-built templates for common use cases +- **Multi-language Support**: Generate polls in multiple languages + +## Contributing + +Contributions to the LinkedIn Poll Generator are welcome! Please follow these steps: + +1. Fork the repository +2. Create a feature branch +3. Make your changes +4. Submit a pull request + +## License + +This project is licensed under the MIT License - see the LICENSE file for details. + +## Contact + +For questions or feedback about the LinkedIn Poll Generator, please contact the development team. \ No newline at end of file diff --git a/lib/ai_writers/linkedin_writer/modules/poll_generator/__init__.py b/lib/ai_writers/linkedin_writer/modules/poll_generator/__init__.py new file mode 100644 index 00000000..3002b6d4 --- /dev/null +++ b/lib/ai_writers/linkedin_writer/modules/poll_generator/__init__.py @@ -0,0 +1,10 @@ +""" +LinkedIn Poll Generator Module + +This module provides functionality for generating LinkedIn polls with +AI-powered content and engagement optimization. +""" + +from .linkedin_poll_generator import LinkedInPollGenerator, linkedin_poll_generator_ui + +__all__ = ["LinkedInPollGenerator", "linkedin_poll_generator_ui"] \ No newline at end of file diff --git a/lib/ai_writers/linkedin_writer/modules/poll_generator/linkedin_poll_generator.py b/lib/ai_writers/linkedin_writer/modules/poll_generator/linkedin_poll_generator.py new file mode 100644 index 00000000..0776e0c6 --- /dev/null +++ b/lib/ai_writers/linkedin_writer/modules/poll_generator/linkedin_poll_generator.py @@ -0,0 +1,1056 @@ +""" +LinkedIn Poll Generator + +This module provides functionality for generating LinkedIn polls with +AI-powered content and engagement optimization. +""" + +import streamlit as st +import time +import json +import random +from typing import Dict, List, Optional, Union, Tuple +from loguru import logger +from datetime import datetime, timedelta + +from .....gpt_providers.text_generation.main_text_generation import llm_text_gen +from .....ai_web_researcher.gpt_online_researcher import do_google_serp_search +from .....ai_web_researcher.metaphor_basic_neural_web_search import metaphor_search_articles +from .....ai_web_researcher.tavily_ai_search import do_tavily_ai_search + + +class LinkedInPollGenerator: + """ + AI-powered LinkedIn poll generator that creates engaging polls with optimized options, + engagement predictions, and follow-up content suggestions. + """ + + def __init__(self): + """Initialize the LinkedIn Poll Generator.""" + self.poll_types = { + "multiple_choice": "Multiple Choice (2-4 options)", + "yes_no": "Yes/No", + "rating": "Rating Scale (1-5 or 1-10)", + "ranking": "Ranking (order items by preference)", + "open_ended": "Open-ended (with suggested responses)" + } + + self.poll_durations = { + "1_day": "1 day", + "3_days": "3 days", + "5_days": "5 days", + "7_days": "7 days", + "14_days": "14 days" + } + + self.engagement_levels = { + "low": "Low (0-10 responses)", + "medium": "Medium (11-50 responses)", + "high": "High (51-200 responses)", + "viral": "Viral (200+ responses)" + } + + def research_topic(self, topic: str, industry: str, search_engine: str = "metaphor") -> Dict: + """ + Research a topic to inform poll question generation. + + Args: + topic: The topic to research + industry: The industry context + search_engine: The search engine to use (metaphor, google, tavily) + + Returns: + Dict containing research results + """ + logger.info(f"Researching topic: {topic} in industry: {industry}") + + research_results = { + "topic": topic, + "industry": industry, + "search_engine": search_engine, + "articles": [], + "trends": [], + "insights": [], + "questions": [] + } + + # Perform research based on selected search engine + if search_engine == "metaphor": + try: + articles = metaphor_search_articles( + query=f"{topic} {industry} trends insights", + num_results=5 + ) + research_results["articles"] = articles + except Exception as e: + logger.error(f"Error researching with Metaphor: {str(e)}") + st.error(f"Error researching with Metaphor: {str(e)}") + elif search_engine == "google": + try: + search_results = do_google_serp_search( + query=f"{topic} {industry} trends insights", + num_results=5 + ) + research_results["articles"] = search_results + except Exception as e: + logger.error(f"Error researching with Google: {str(e)}") + st.error(f"Error researching with Google: {str(e)}") + elif search_engine == "tavily": + try: + search_results = do_tavily_ai_search( + query=f"{topic} {industry} trends insights", + search_depth="advanced", + include_answer=True, + include_raw_content=True, + max_results=5 + ) + research_results["articles"] = search_results + except Exception as e: + logger.error(f"Error researching with Tavily: {str(e)}") + st.error(f"Error researching with Tavily: {str(e)}") + + # Extract insights and trends from research + if research_results["articles"]: + research_results["insights"], research_results["trends"] = self._extract_insights_and_trends(research_results["articles"]) + + # Generate potential poll questions based on research + research_results["questions"] = self._generate_potential_questions( + topic, industry, research_results["insights"], research_results["trends"] + ) + + return research_results + + def _extract_insights_and_trends(self, articles: List[Dict]) -> Tuple[List[str], List[str]]: + """ + Extract insights and trends from research articles. + + Args: + articles: List of research articles + + Returns: + Tuple of (insights, trends) + """ + insights = [] + trends = [] + + # Extract text content from articles + article_texts = [] + for article in articles: + if isinstance(article, dict): + if "content" in article: + article_texts.append(article["content"]) + elif "snippet" in article: + article_texts.append(article["snippet"]) + elif "description" in article: + article_texts.append(article["description"]) + elif isinstance(article, str): + article_texts.append(article) + + if not article_texts: + return insights, trends + + # Use AI to extract insights and trends + try: + prompt = f""" + Based on the following research articles, extract: + 1. 5 key insights about the topic + 2. 5 emerging trends related to the topic + + Research articles: + {' '.join(article_texts[:3])} + + Format your response as: + INSIGHTS: + - Insight 1 + - Insight 2 + ... + + TRENDS: + - Trend 1 + - Trend 2 + ... + """ + + response = llm_text_gen(prompt=prompt, max_tokens=500) + + # Parse the response + if "INSIGHTS:" in response and "TRENDS:" in response: + insights_section = response.split("INSIGHTS:")[1].split("TRENDS:")[0].strip() + trends_section = response.split("TRENDS:")[1].strip() + + insights = [insight.strip("- ").strip() for insight in insights_section.split("\n") if insight.strip()] + trends = [trend.strip("- ").strip() for trend in trends_section.split("\n") if trend.strip()] + except Exception as e: + logger.error(f"Error extracting insights and trends: {str(e)}") + + return insights, trends + + def _generate_potential_questions(self, topic: str, industry: str, insights: List[str], trends: List[str]) -> List[str]: + """ + Generate potential poll questions based on research. + + Args: + topic: The topic + industry: The industry + insights: List of insights + trends: List of trends + + Returns: + List of potential poll questions + """ + questions = [] + + try: + prompt = f""" + Generate 5 engaging LinkedIn poll questions about {topic} in the {industry} industry. + + Use these insights and trends to inform your questions: + INSIGHTS: + {chr(10).join([f"- {insight}" for insight in insights])} + + TRENDS: + {chr(10).join([f"- {trend}" for trend in trends])} + + The questions should: + 1. Be thought-provoking and encourage discussion + 2. Be relevant to professionals in the {industry} industry + 3. Be concise and clear + 4. Avoid leading or biased language + 5. Be suitable for a LinkedIn poll format + + Format each question as a separate line starting with a number. + """ + + response = llm_text_gen(prompt=prompt, max_tokens=500) + + # Parse the response + for line in response.split("\n"): + line = line.strip() + if line and any(line.startswith(str(i)) for i in range(1, 10)): + question = line.split(".", 1)[1].strip() if "." in line else line + questions.append(question) + except Exception as e: + logger.error(f"Error generating potential questions: {str(e)}") + + return questions + + def generate_poll_question(self, topic: str, industry: str, poll_type: str, tone: str = "professional") -> str: + """ + Generate a poll question based on the topic, industry, and poll type. + + Args: + topic: The topic for the poll + industry: The industry context + poll_type: The type of poll (multiple_choice, yes_no, rating, ranking, open_ended) + tone: The tone to use (professional, casual, authoritative, etc.) + + Returns: + The generated poll question + """ + logger.info(f"Generating {poll_type} poll question about {topic} in {industry} industry with {tone} tone") + + try: + prompt = f""" + Generate a LinkedIn poll question about {topic} in the {industry} industry. + + Poll type: {self.poll_types[poll_type]} + Tone: {tone} + + The question should: + 1. Be thought-provoking and encourage discussion + 2. Be relevant to professionals in the {industry} industry + 3. Be concise and clear (ideally under 100 characters) + 4. Avoid leading or biased language + 5. Be suitable for a LinkedIn poll format + 6. Use a {tone} tone + + Return only the question text without any additional formatting or explanation. + """ + + question = llm_text_gen(prompt=prompt, max_tokens=100).strip() + + # Ensure the question ends with a question mark if it doesn't already + if not question.endswith("?"): + question += "?" + + return question + except Exception as e: + logger.error(f"Error generating poll question: {str(e)}") + return f"What's your opinion on {topic} in the {industry} industry?" + + def generate_poll_options(self, question: str, poll_type: str, industry: str, num_options: int = 4) -> List[str]: + """ + Generate poll options based on the question and poll type. + + Args: + question: The poll question + poll_type: The type of poll (multiple_choice, yes_no, rating, ranking, open_ended) + industry: The industry context + num_options: The number of options to generate (for multiple choice) + + Returns: + List of poll options + """ + logger.info(f"Generating {poll_type} poll options for question: {question}") + + options = [] + + if poll_type == "yes_no": + return ["Yes", "No"] + elif poll_type == "rating": + return [str(i) for i in range(1, 6)] # 1-5 rating scale + elif poll_type == "ranking": + # For ranking, we'll generate items to rank + try: + prompt = f""" + Generate 4 items to rank in a LinkedIn poll about: {question} + + Industry context: {industry} + + The items should: + 1. Be relevant to the question + 2. Be concise (1-5 words each) + 3. Be distinct from each other + 4. Be suitable for ranking in order of preference or importance + + Return only the items, one per line, without numbering or additional formatting. + """ + + response = llm_text_gen(prompt=prompt, max_tokens=200) + + # Parse the response + for line in response.split("\n"): + line = line.strip() + if line and not line.startswith(("1.", "2.", "3.", "4.", "-", "*")): + options.append(line) + elif line and line.startswith(("1.", "2.", "3.", "4.", "-", "*")): + # Remove numbering or bullets + option = line.split(".", 1)[1].strip() if "." in line else line.lstrip("- *").strip() + options.append(option) + + # Ensure we have exactly 4 options + if len(options) > 4: + options = options[:4] + elif len(options) < 4: + # Add generic options if we don't have enough + generic_options = ["Option 1", "Option 2", "Option 3", "Option 4"] + options.extend(generic_options[len(options):4]) + except Exception as e: + logger.error(f"Error generating ranking options: {str(e)}") + options = ["Option 1", "Option 2", "Option 3", "Option 4"] + elif poll_type == "open_ended": + # For open-ended, we'll generate suggested responses + try: + prompt = f""" + Generate 3 suggested responses for an open-ended LinkedIn poll about: {question} + + Industry context: {industry} + + The responses should: + 1. Be relevant to the question + 2. Be concise (5-15 words each) + 3. Represent different perspectives or approaches + 4. Encourage others to share their own responses + + Return only the responses, one per line, without numbering or additional formatting. + """ + + response = llm_text_gen(prompt=prompt, max_tokens=200) + + # Parse the response + for line in response.split("\n"): + line = line.strip() + if line and not line.startswith(("1.", "2.", "3.", "-", "*")): + options.append(line) + elif line and line.startswith(("1.", "2.", "3.", "-", "*")): + # Remove numbering or bullets + option = line.split(".", 1)[1].strip() if "." in line else line.lstrip("- *").strip() + options.append(option) + + # Ensure we have exactly 3 options + if len(options) > 3: + options = options[:3] + elif len(options) < 3: + # Add generic options if we don't have enough + generic_options = ["Response 1", "Response 2", "Response 3"] + options.extend(generic_options[len(options):3]) + except Exception as e: + logger.error(f"Error generating open-ended options: {str(e)}") + options = ["Response 1", "Response 2", "Response 3"] + else: # multiple_choice + try: + prompt = f""" + Generate {num_options} options for a LinkedIn poll about: {question} + + Industry context: {industry} + + The options should: + 1. Be relevant to the question + 2. Be concise (1-5 words each) + 3. Be distinct from each other + 4. Cover a range of possible answers + 5. Be suitable for a multiple-choice poll + + Return only the options, one per line, without numbering or additional formatting. + """ + + response = llm_text_gen(prompt=prompt, max_tokens=200) + + # Parse the response + for line in response.split("\n"): + line = line.strip() + if line and not line.startswith(("1.", "2.", "3.", "4.", "-", "*")): + options.append(line) + elif line and line.startswith(("1.", "2.", "3.", "4.", "-", "*")): + # Remove numbering or bullets + option = line.split(".", 1)[1].strip() if "." in line else line.lstrip("- *").strip() + options.append(option) + + # Ensure we have exactly num_options + if len(options) > num_options: + options = options[:num_options] + elif len(options) < num_options: + # Add generic options if we don't have enough + generic_options = [f"Option {i+1}" for i in range(num_options)] + options.extend(generic_options[len(options):num_options]) + except Exception as e: + logger.error(f"Error generating multiple choice options: {str(e)}") + options = [f"Option {i+1}" for i in range(num_options)] + + return options + + def predict_engagement(self, question: str, options: List[str], industry: str) -> Dict: + """ + Predict engagement levels for a poll. + + Args: + question: The poll question + options: The poll options + industry: The industry context + + Returns: + Dict containing engagement predictions + """ + logger.info(f"Predicting engagement for poll: {question}") + + engagement_prediction = { + "expected_responses": 0, + "engagement_level": "low", + "response_distribution": {}, + "comment_likelihood": "low", + "share_likelihood": "low", + "insights": [] + } + + try: + prompt = f""" + Predict engagement for a LinkedIn poll with the following details: + + Question: {question} + Options: {', '.join(options)} + Industry: {industry} + + Provide predictions for: + 1. Expected number of responses (low: 0-10, medium: 11-50, high: 51-200, viral: 200+) + 2. Likelihood of comments (low, medium, high) + 3. Likelihood of shares (low, medium, high) + 4. Expected distribution of responses across options (as percentages) + 5. 3 key insights that might be gained from this poll + + Format your response as JSON: + {{ + "expected_responses": "low/medium/high/viral", + "response_count": number, + "comment_likelihood": "low/medium/high", + "share_likelihood": "low/medium/high", + "response_distribution": {{ + "option1": percentage, + "option2": percentage, + ... + }}, + "insights": [ + "insight1", + "insight2", + "insight3" + ] + }} + """ + + response = llm_text_gen(prompt=prompt, max_tokens=500) + + # Parse the JSON response + try: + prediction_data = json.loads(response) + + engagement_prediction["engagement_level"] = prediction_data.get("expected_responses", "low") + engagement_prediction["expected_responses"] = prediction_data.get("response_count", 0) + engagement_prediction["comment_likelihood"] = prediction_data.get("comment_likelihood", "low") + engagement_prediction["share_likelihood"] = prediction_data.get("share_likelihood", "low") + engagement_prediction["response_distribution"] = prediction_data.get("response_distribution", {}) + engagement_prediction["insights"] = prediction_data.get("insights", []) + except json.JSONDecodeError: + logger.error("Error parsing engagement prediction JSON") + # Set default values + engagement_prediction["engagement_level"] = "low" + engagement_prediction["expected_responses"] = 5 + engagement_prediction["comment_likelihood"] = "low" + engagement_prediction["share_likelihood"] = "low" + engagement_prediction["response_distribution"] = {option: 100/len(options) for option in options} + engagement_prediction["insights"] = ["No insights available"] + except Exception as e: + logger.error(f"Error predicting engagement: {str(e)}") + + return engagement_prediction + + def suggest_poll_duration(self, question: str, industry: str) -> str: + """ + Suggest an optimal poll duration based on the question and industry. + + Args: + question: The poll question + industry: The industry context + + Returns: + Suggested poll duration + """ + logger.info(f"Suggesting poll duration for: {question}") + + try: + prompt = f""" + Suggest an optimal duration for a LinkedIn poll with the following details: + + Question: {question} + Industry: {industry} + + Consider: + 1. The complexity of the question + 2. The industry's typical response patterns + 3. The target audience's likely engagement patterns + 4. The time sensitivity of the topic + + Return only one of these options: 1_day, 3_days, 5_days, 7_days, 14_days + """ + + response = llm_text_gen(prompt=prompt, max_tokens=50).strip().lower() + + # Validate the response + if response in self.poll_durations: + return response + else: + # Default to 3 days if the response is invalid + return "3_days" + except Exception as e: + logger.error(f"Error suggesting poll duration: {str(e)}") + return "3_days" + + def generate_follow_up_content(self, question: str, options: List[str], industry: str) -> Dict: + """ + Generate follow-up content suggestions based on poll results. + + Args: + question: The poll question + options: The poll options + industry: The industry context + + Returns: + Dict containing follow-up content suggestions + """ + logger.info(f"Generating follow-up content for poll: {question}") + + follow_up_content = { + "post_templates": [], + "visual_suggestions": [], + "hashtag_suggestions": [], + "next_poll_suggestions": [] + } + + try: + prompt = f""" + Generate follow-up content suggestions for a LinkedIn poll with the following details: + + Question: {question} + Options: {', '.join(options)} + Industry: {industry} + + Provide: + 1. 3 post templates for sharing poll results (with placeholders for actual results) + 2. 3 visual content suggestions (e.g., charts, infographics) + 3. 5 relevant hashtags for the follow-up content + 4. 3 suggestions for follow-up polls that would build on this poll's insights + + Format your response as JSON: + {{ + "post_templates": [ + "template1", + "template2", + "template3" + ], + "visual_suggestions": [ + "suggestion1", + "suggestion2", + "suggestion3" + ], + "hashtag_suggestions": [ + "hashtag1", + "hashtag2", + "hashtag3", + "hashtag4", + "hashtag5" + ], + "next_poll_suggestions": [ + "suggestion1", + "suggestion2", + "suggestion3" + ] + }} + """ + + response = llm_text_gen(prompt=prompt, max_tokens=800) + + # Parse the JSON response + try: + content_data = json.loads(response) + + follow_up_content["post_templates"] = content_data.get("post_templates", []) + follow_up_content["visual_suggestions"] = content_data.get("visual_suggestions", []) + follow_up_content["hashtag_suggestions"] = content_data.get("hashtag_suggestions", []) + follow_up_content["next_poll_suggestions"] = content_data.get("next_poll_suggestions", []) + except json.JSONDecodeError: + logger.error("Error parsing follow-up content JSON") + # Set default values + follow_up_content["post_templates"] = ["Thank you for participating in our poll about [Topic]! Here are the results..."] + follow_up_content["visual_suggestions"] = ["Bar chart showing distribution of responses"] + follow_up_content["hashtag_suggestions"] = [f"#{industry.replace(' ', '')}", "#LinkedInPoll", "#IndustryInsights"] + follow_up_content["next_poll_suggestions"] = ["What factors influenced your decision in the previous poll?"] + except Exception as e: + logger.error(f"Error generating follow-up content: {str(e)}") + + return follow_up_content + + def generate_data_visualization(self, question: str, options: List[str], response_distribution: Dict) -> str: + """ + Generate a data visualization suggestion for poll results. + + Args: + question: The poll question + options: The poll options + response_distribution: The predicted response distribution + + Returns: + Visualization suggestion + """ + logger.info(f"Generating data visualization for poll: {question}") + + try: + prompt = f""" + Suggest a data visualization for a LinkedIn poll with the following details: + + Question: {question} + Options: {', '.join(options)} + Predicted response distribution: {json.dumps(response_distribution)} + + Consider: + 1. The type of data (categorical, ordinal, etc.) + 2. The number of options + 3. The clarity and impact of different chart types + 4. LinkedIn's visual presentation capabilities + + Return a detailed description of the recommended visualization, including: + 1. Chart type + 2. Color scheme + 3. Layout + 4. Key elements to highlight + """ + + visualization = llm_text_gen(prompt=prompt, max_tokens=300) + return visualization + except Exception as e: + logger.error(f"Error generating data visualization: {str(e)}") + return "Bar chart showing distribution of responses across options" + + def optimize_poll_for_engagement(self, question: str, options: List[str], industry: str) -> Dict: + """ + Optimize a poll for maximum engagement. + + Args: + question: The poll question + options: The poll options + industry: The industry context + + Returns: + Dict containing optimization suggestions + """ + logger.info(f"Optimizing poll for engagement: {question}") + + optimization = { + "question_improvements": [], + "option_improvements": [], + "timing_suggestions": [], + "audience_targeting": [], + "hashtag_suggestions": [] + } + + try: + prompt = f""" + Optimize a LinkedIn poll for maximum engagement with the following details: + + Question: {question} + Options: {', '.join(options)} + Industry: {industry} + + Provide suggestions for: + 1. Question improvements (wording, clarity, impact) + 2. Option improvements (wording, order, completeness) + 3. Timing suggestions (best day/time to post) + 4. Audience targeting (who would be most interested) + 5. Hashtag suggestions (5-7 relevant hashtags) + + Format your response as JSON: + {{ + "question_improvements": [ + "improvement1", + "improvement2", + "improvement3" + ], + "option_improvements": [ + "improvement1", + "improvement2", + "improvement3" + ], + "timing_suggestions": [ + "suggestion1", + "suggestion2", + "suggestion3" + ], + "audience_targeting": [ + "audience1", + "audience2", + "audience3" + ], + "hashtag_suggestions": [ + "hashtag1", + "hashtag2", + "hashtag3", + "hashtag4", + "hashtag5", + "hashtag6", + "hashtag7" + ] + }} + """ + + response = llm_text_gen(prompt=prompt, max_tokens=800) + + # Parse the JSON response + try: + optimization_data = json.loads(response) + + optimization["question_improvements"] = optimization_data.get("question_improvements", []) + optimization["option_improvements"] = optimization_data.get("option_improvements", []) + optimization["timing_suggestions"] = optimization_data.get("timing_suggestions", []) + optimization["audience_targeting"] = optimization_data.get("audience_targeting", []) + optimization["hashtag_suggestions"] = optimization_data.get("hashtag_suggestions", []) + except json.JSONDecodeError: + logger.error("Error parsing optimization JSON") + # Set default values + optimization["question_improvements"] = ["Make the question more specific"] + optimization["option_improvements"] = ["Ensure options are mutually exclusive"] + optimization["timing_suggestions"] = ["Post on Tuesday or Wednesday during business hours"] + optimization["audience_targeting"] = [f"Professionals in the {industry} industry"] + optimization["hashtag_suggestions"] = [f"#{industry.replace(' ', '')}", "#LinkedInPoll", "#IndustryInsights"] + except Exception as e: + logger.error(f"Error optimizing poll: {str(e)}") + + return optimization + + +def linkedin_poll_generator_ui(): + """Streamlit UI for the LinkedIn Poll Generator.""" + + st.title("LinkedIn Poll Generator") + st.markdown(""" + Create engaging LinkedIn polls that drive interaction and gather valuable insights from your network. + """) + + # Initialize the poll generator + poll_generator = LinkedInPollGenerator() + + # Create tabs for different sections + tab1, tab2, tab3 = st.tabs(["Create Poll", "Research & Insights", "Optimization & Follow-up"]) + + with tab1: + st.header("Create Your LinkedIn Poll") + + # Topic and industry inputs + col1, col2 = st.columns(2) + with col1: + topic = st.text_input("Topic", placeholder="e.g., Remote Work, AI in Business, Leadership Styles") + with col2: + industry = st.text_input("Industry", placeholder="e.g., Technology, Healthcare, Finance") + + # Poll type selection + poll_type = st.selectbox( + "Poll Type", + options=list(poll_generator.poll_types.keys()), + format_func=lambda x: poll_generator.poll_types[x] + ) + + # Number of options for multiple choice + if poll_type == "multiple_choice": + num_options = st.slider("Number of Options", min_value=2, max_value=4, value=4) + else: + num_options = 4 # Default for other poll types + + # Tone selection + tone = st.selectbox( + "Tone", + options=["professional", "casual", "authoritative", "conversational", "thoughtful"], + index=0 + ) + + # Research button + if st.button("Research Topic", key="research_button"): + with st.spinner("Researching topic..."): + research_results = poll_generator.research_topic(topic, industry) + + # Store research results in session state + st.session_state.research_results = research_results + + # Display research results + st.subheader("Research Results") + + if research_results["articles"]: + st.write(f"Found {len(research_results['articles'])} relevant articles.") + + # Display insights + if research_results["insights"]: + st.subheader("Key Insights") + for insight in research_results["insights"]: + st.markdown(f"- {insight}") + + # Display trends + if research_results["trends"]: + st.subheader("Emerging Trends") + for trend in research_results["trends"]: + st.markdown(f"- {trend}") + + # Display potential questions + if research_results["questions"]: + st.subheader("Potential Poll Questions") + for i, question in enumerate(research_results["questions"]): + st.markdown(f"{i+1}. {question}") + else: + st.warning("No research results found. Try a different topic or industry.") + + # Generate poll button + if st.button("Generate Poll", key="generate_button"): + with st.spinner("Generating poll..."): + # Generate poll question + question = poll_generator.generate_poll_question(topic, industry, poll_type, tone) + + # Generate poll options + options = poll_generator.generate_poll_options(question, poll_type, industry, num_options) + + # Predict engagement + engagement = poll_generator.predict_engagement(question, options, industry) + + # Suggest poll duration + duration = poll_generator.suggest_poll_duration(question, industry) + + # Store poll data in session state + st.session_state.poll_data = { + "question": question, + "options": options, + "engagement": engagement, + "duration": duration + } + + # Display poll + st.subheader("Your LinkedIn Poll") + + # Display question + st.markdown(f"### {question}") + + # Display options + for i, option in enumerate(options): + st.markdown(f"**{i+1}.** {option}") + + # Display engagement prediction + st.subheader("Engagement Prediction") + + col1, col2, col3 = st.columns(3) + with col1: + st.metric("Expected Responses", engagement["engagement_level"].title()) + with col2: + st.metric("Comment Likelihood", engagement["comment_likelihood"].title()) + with col3: + st.metric("Share Likelihood", engagement["share_likelihood"].title()) + + # Display response distribution + if engagement["response_distribution"]: + st.subheader("Predicted Response Distribution") + + # Create a bar chart + import plotly.express as px + + # Prepare data for the chart + chart_data = [] + for option, percentage in engagement["response_distribution"].items(): + chart_data.append({"Option": option, "Percentage": percentage}) + + # Create the chart + fig = px.bar(chart_data, x="Option", y="Percentage", + title="Predicted Response Distribution", + color="Option", + color_discrete_sequence=px.colors.qualitative.Set3) + + st.plotly_chart(fig, use_container_width=True) + + # Display poll duration + st.subheader("Recommended Poll Duration") + st.write(f"**{poll_generator.poll_durations[duration]}**") + + # Display insights + if engagement["insights"]: + st.subheader("Potential Insights") + for insight in engagement["insights"]: + st.markdown(f"- {insight}") + + with tab2: + st.header("Research & Insights") + + # Check if research results exist + if "research_results" in st.session_state: + research_results = st.session_state.research_results + + # Display research results + st.subheader("Research Results") + + if research_results["articles"]: + st.write(f"Found {len(research_results['articles'])} relevant articles.") + + # Display insights + if research_results["insights"]: + st.subheader("Key Insights") + for insight in research_results["insights"]: + st.markdown(f"- {insight}") + + # Display trends + if research_results["trends"]: + st.subheader("Emerging Trends") + for trend in research_results["trends"]: + st.markdown(f"- {trend}") + + # Display potential questions + if research_results["questions"]: + st.subheader("Potential Poll Questions") + for i, question in enumerate(research_results["questions"]): + st.markdown(f"{i+1}. {question}") + else: + st.warning("No research results found. Try a different topic or industry.") + else: + st.info("Generate a poll first to see research results.") + + with tab3: + st.header("Optimization & Follow-up") + + # Check if poll data exists + if "poll_data" in st.session_state: + poll_data = st.session_state.poll_data + + # Optimization section + st.subheader("Poll Optimization") + + if st.button("Optimize Poll", key="optimize_button"): + with st.spinner("Optimizing poll..."): + # Optimize poll + optimization = poll_generator.optimize_poll_for_engagement( + poll_data["question"], + poll_data["options"], + industry + ) + + # Store optimization in session state + st.session_state.optimization = optimization + + # Display question improvements + st.subheader("Question Improvements") + for improvement in optimization["question_improvements"]: + st.markdown(f"- {improvement}") + + # Display option improvements + st.subheader("Option Improvements") + for improvement in optimization["option_improvements"]: + st.markdown(f"- {improvement}") + + # Display timing suggestions + st.subheader("Timing Suggestions") + for suggestion in optimization["timing_suggestions"]: + st.markdown(f"- {suggestion}") + + # Display audience targeting + st.subheader("Audience Targeting") + for audience in optimization["audience_targeting"]: + st.markdown(f"- {audience}") + + # Display hashtag suggestions + st.subheader("Hashtag Suggestions") + hashtags = " ".join([f"#{tag.strip('#')}" for tag in optimization["hashtag_suggestions"]]) + st.markdown(hashtags) + + # Follow-up content section + st.subheader("Follow-up Content") + + if st.button("Generate Follow-up Content", key="followup_button"): + with st.spinner("Generating follow-up content..."): + # Generate follow-up content + follow_up = poll_generator.generate_follow_up_content( + poll_data["question"], + poll_data["options"], + industry + ) + + # Store follow-up in session state + st.session_state.follow_up = follow_up + + # Display post templates + st.subheader("Post Templates") + for i, template in enumerate(follow_up["post_templates"]): + st.markdown(f"**Template {i+1}:**") + st.markdown(template) + st.markdown("---") + + # Display visual suggestions + st.subheader("Visual Suggestions") + for suggestion in follow_up["visual_suggestions"]: + st.markdown(f"- {suggestion}") + + # Display hashtag suggestions + st.subheader("Hashtag Suggestions") + hashtags = " ".join([f"#{tag.strip('#')}" for tag in follow_up["hashtag_suggestions"]]) + st.markdown(hashtags) + + # Display next poll suggestions + st.subheader("Next Poll Suggestions") + for suggestion in follow_up["next_poll_suggestions"]: + st.markdown(f"- {suggestion}") + + # Data visualization section + st.subheader("Data Visualization") + + if st.button("Generate Visualization", key="visualization_button"): + with st.spinner("Generating visualization suggestion..."): + # Generate visualization + visualization = poll_generator.generate_data_visualization( + poll_data["question"], + poll_data["options"], + poll_data["engagement"]["response_distribution"] + ) + + # Store visualization in session state + st.session_state.visualization = visualization + + # Display visualization + st.markdown(visualization) + else: + st.info("Generate a poll first to see optimization and follow-up suggestions.") \ No newline at end of file diff --git a/lib/ai_writers/linkedin_writer/modules/post_generator/README.md b/lib/ai_writers/linkedin_writer/modules/post_generator/README.md new file mode 100644 index 00000000..093b988e --- /dev/null +++ b/lib/ai_writers/linkedin_writer/modules/post_generator/README.md @@ -0,0 +1,291 @@ +# LinkedIn Post Generator πŸš€ + +An AI-powered tool designed for LinkedIn professionals to create engaging, research-backed posts that drive engagement and establish thought leadership. + +## ✨ Key Features + +### Content Creation +- **AI-Powered Research**: Multi-source research integration (Metaphor, Google, Tavily) +- **Smart Content Generation**: Industry-specific, engagement-optimized content +- **Hashtag Optimization**: Data-driven hashtag suggestions +- **Visual Content**: AI-generated images with professional aesthetics +- **Engagement Predictions**: AI-powered performance analytics +- **Poll Creation**: Interactive audience engagement tools +- **Posting Time Optimization**: Industry-specific timing recommendations + +### Research Excellence +- **Triple-Engine Research**: + - πŸ” Metaphor AI: Deep industry insights + - 🌐 Google Search: Comprehensive coverage + - πŸ€– Tavily AI: Focused research +- **Real-Time Data**: Current trends and statistics +- **Expert Insights**: Professional quotes and perspectives +- **Fact Verification**: Web-researched authenticity + +### Visual Enhancement +- **Professional Imagery**: AI-generated visuals +- **Custom Prompts**: Tailored image generation +- **Style Customization**: Brand-aligned visuals +- **Multiple Formats**: Various visual content options + +## 🎯 Getting Started + +### 1. Post Creation Process + +#### Initial Setup +- Choose your topic +- Select your industry +- Pick your writing tone: + - Professional + - Casual + - Informative + - Inspirational + +#### Research Configuration +Select your preferred research source: +- Metaphor AI: Comprehensive insights +- Google Search: Wide coverage +- Tavily AI: Focused research + +#### Content Options +Customize your post with: +- βœ… Hashtags +- πŸ–ΌοΈ Visual content +- πŸ“Š Polls +- ⏰ Posting time recommendations + +### 2. Post Generation and Preview + +#### Content Tab +- View generated post +- Edit content directly +- Copy to clipboard +- Download as text + +#### Analytics Tab +- Engagement predictions +- Optimal posting times +- Performance metrics +- Audience insights + +#### Visual Content Tab +- Preview generated images +- Edit image prompts +- Generate alternatives +- Download visuals + +## πŸ’‘ Best Practices + +### 1. Content Strategy +- **Topic Selection** + - Industry relevance + - Current trends + - Audience interest + - Professional expertise + +- **Content Structure** + - Strong hook + - Value proposition + - Supporting points + - Clear CTA + +### 2. Engagement Optimization +- **Hashtag Strategy** + - Industry-specific tags + - Trending topics + - Balanced visibility + - Professional relevance + +- **Visual Impact** + - Professional imagery + - Brand consistency + - Clear messaging + - Mobile optimization + +### 3. Timing and Frequency +- **Posting Schedule** + - Industry peak times + - Audience activity + - Time zone consideration + - Consistency + +## πŸ“Š Content Types + +### 1. Thought Leadership +- Industry insights +- Expert opinions +- Trend analysis +- Future predictions + +### 2. Professional Tips +- Best practices +- How-to guides +- Career advice +- Industry hacks + +### 3. Success Stories +- Case studies +- Achievements +- Lessons learned +- Professional growth + +### 4. Industry Updates +- Market trends +- News analysis +- Technology updates +- Professional developments + +## 🎨 Visual Guidelines + +### Image Generation +- **Style Options** + - Professional photography + - Abstract concepts + - Data visualization + - Brand elements + +- **Customization** + - Color schemes + - Text overlays + - Visual hierarchy + - Professional aesthetics + +### Brand Alignment +- **Visual Identity** + - Consistent style + - Professional tone + - Brand colors + - Quality standards + +## πŸ“± Platform Optimization + +### Mobile Experience +- Text readability +- Image scaling +- Content preview +- Performance check + +### Cross-Platform +- LinkedIn app compatibility +- Web version optimization +- Image resolution +- Loading speed + +## πŸ’ͺ Engagement Strategies + +### 1. Interactive Elements +- **Polls** + - Topic relevance + - Response options + - Duration setting + - Follow-up strategy + +- **Call-to-Action** + - Clear direction + - Value proposition + - Professional tone + - Measurable goals + +### 2. Hashtag Optimization +- **Selection Criteria** + - Relevance + - Reach + - Professional context + - Trending topics + +### 3. Timing Strategy +- **Post Scheduling** + - Peak engagement times + - Industry patterns + - Audience availability + - Global considerations + +## πŸ› οΈ Technical Features + +### Performance +- Fast generation +- Real-time preview +- Smooth editing +- Quick updates + +### Integration +- Browser compatibility +- API connections +- Data security +- Regular updates + +## πŸ“ˆ Analytics and Insights + +### Engagement Metrics +- Like predictions +- Comment estimates +- Share potential +- Profile visit forecasts + +### Performance Tracking +- Engagement rates +- Reach estimates +- Audience response +- Content effectiveness + +## πŸ”„ Workflow Integration + +### 1. Content Planning +- Topic research +- Content creation +- Visual design +- Optimization + +### 2. Quality Assurance +- Content review +- Image verification +- Hashtag check +- Timing optimization + +### 3. Publication +- Final preview +- Platform check +- Scheduling +- Performance monitoring + +## 🎯 Success Metrics + +### Engagement Goals +- Professional visibility +- Audience growth +- Industry authority +- Network expansion + +### Content Quality +- Professional standards +- Industry relevance +- Value delivery +- Brand alignment + +## πŸ” Support and Resources + +### Help Center +- User guides +- Best practices +- FAQs +- Technical support + +### Updates +- Feature releases +- Performance improvements +- Platform updates +- Content tips + +--- + +Transform your LinkedIn presence with professional, engaging posts that drive results! πŸš€ + +### Quick Tips +- Keep posts between 800-1200 characters +- Include 3-5 relevant hashtags +- Add professional visuals +- Engage with comments +- Monitor performance +- Stay consistent + +Start creating impactful LinkedIn posts that establish your professional authority! πŸ’« \ No newline at end of file diff --git a/lib/ai_writers/linkedin_writer/modules/post_generator/__init__.py b/lib/ai_writers/linkedin_writer/modules/post_generator/__init__.py new file mode 100644 index 00000000..489ea028 --- /dev/null +++ b/lib/ai_writers/linkedin_writer/modules/post_generator/__init__.py @@ -0,0 +1,10 @@ +""" +LinkedIn Post Generator Package + +This package provides functionality for generating LinkedIn posts with research-backed content, +optimized hashtags, and engagement predictions. +""" + +from .linkedin_post_generator import linkedin_post_generator_ui, LinkedInPostGenerator + +__all__ = ['linkedin_post_generator_ui', 'LinkedInPostGenerator'] \ No newline at end of file diff --git a/lib/ai_writers/linkedin_writer/modules/post_generator/linkedin_post_generator.py b/lib/ai_writers/linkedin_writer/modules/post_generator/linkedin_post_generator.py new file mode 100644 index 00000000..f938eebd --- /dev/null +++ b/lib/ai_writers/linkedin_writer/modules/post_generator/linkedin_post_generator.py @@ -0,0 +1,1207 @@ +""" +LinkedIn Post Generator + +This module provides functionality for generating LinkedIn posts with research-backed content, +optimized hashtags, and engagement predictions. +""" + +import os +import json +import time +import streamlit as st +from typing import Dict, List, Optional, Tuple, Union +from loguru import logger +import random + +from .....gpt_providers.text_generation.main_text_generation import llm_text_gen +from .....ai_web_researcher.gpt_online_researcher import do_google_serp_search +from .....ai_web_researcher.metaphor_basic_neural_web_search import metaphor_search_articles, streamlit_display_metaphor_results +from .....ai_web_researcher.tavily_ai_search import do_tavily_ai_search, streamlit_display_results + + +class LinkedInPostGenerator: + """ + A class for generating LinkedIn posts with research-backed content. + + This class provides methods for: + - Researching topics using Metaphor and Google + - Generating outlines based on research + - Creating post content optimized for engagement + - Optimizing hashtags + - Generating visual content recommendations + - Predicting engagement metrics + - Suggesting optimal posting times + - Creating polls + """ + + def __init__(self): + """Initialize the LinkedIn Post Generator.""" + self.research_results = {} + self.outline = {} + self.post_content = "" + self.hashtags = [] + self.visual_content = {} + self.engagement_prediction = {} + self.posting_time_suggestions = [] + self.poll = {} + + def research_topic(self, topic: str, industry: str, search_engine: str = "metaphor", status_container=None) -> Dict: + """ + Research a topic using the selected search engine. + + Args: + topic: The topic to research + industry: The industry context + search_engine: The search engine to use (metaphor, google, tavily) + status_container: Optional container for status updates + + Returns: + Dict containing research results + """ + # Update progress + if status_container: + status_container.text("πŸ” Researching topic...") + + # Research with selected search engine + research_results = None + if search_engine == "metaphor": + research_results = self._research_with_metaphor(topic, industry, status_container) + elif search_engine == "google": + research_results = self._research_with_google(topic, industry, status_container) + elif search_engine == "tavily": + research_results = self._research_with_tavily(topic, industry, status_container) + else: + research_results = self._research_with_metaphor(topic, industry, status_container) + + # Analyze research results + combined_results = self._analyze_research_results(research_results, topic, industry) + + # Add topic and industry to the results + combined_results["topic"] = topic + combined_results["industry"] = industry + + # Update progress + if status_container: + status_container.text("βœ… Research complete!") + + return combined_results + + def _research_with_metaphor(self, topic: str, industry: str, status_container=None) -> Dict: + """ + Research a topic using Metaphor. + + Args: + topic: The topic to research + industry: The industry context + status_container: Optional container for status updates + + Returns: + Dict containing research results + """ + # Update progress + if status_container: + status_container.text("πŸ” Searching with Metaphor...") + + try: + # Construct search query + search_query = f"{topic} in {industry} industry" + + # Search with Metaphor + metaphor_results = metaphor_search_articles(search_query) + + # Display the results using streamlit_display_metaphor_results + if metaphor_results: + streamlit_display_metaphor_results(metaphor_results, search_query) + + # Update progress + if status_container: + status_container.text("βœ… Metaphor search complete!") + + # Ensure we return a valid dictionary even if metaphor_results is None + if metaphor_results is None: + logger.warning(f"No results returned from Metaphor search for: {search_query}") + return {"sources": []} + + return metaphor_results + except Exception as e: + logger.error(f"Error in Metaphor search: {e}") + if status_container: + status_container.text(f"⚠️ Error in Metaphor search: {str(e)}") + return {"sources": []} + + def _research_with_google(self, topic: str, industry: str, status_container=None) -> Dict: + """ + Research a topic using Google. + + Args: + topic: The topic to research + industry: The industry context + status_container: Optional container for status updates + + Returns: + Dict containing research results + """ + # Update progress + if status_container: + status_container.text("πŸ” Searching with Google...") + + try: + # Search with Google + google_results = do_google_serp_search(topic, industry) + + # Display the results using streamlit_display_google_results + if google_results: + streamlit_display_google_results(google_results) + + # Update progress + if status_container: + status_container.text("βœ… Google search complete!") + + # Ensure we return a valid dictionary even if google_results is None + if google_results is None: + logger.warning(f"No results returned from Google search for: {topic} in {industry}") + return {"sources": []} + + return google_results + except Exception as e: + logger.error(f"Error in Google search: {e}") + if status_container: + status_container.text(f"⚠️ Error in Google search: {str(e)}") + return {"sources": []} + + def _research_with_tavily(self, topic: str, industry: str, status_container=None) -> Dict: + """ + Research a topic using Tavily AI. + + Args: + topic: The topic to research + industry: The industry context + status_container: Optional container for status updates + + Returns: + Dict containing research results + """ + # Update progress + if status_container: + status_container.text("πŸ” Searching with Tavily AI...") + + # Construct search query + search_query = f"{topic} in {industry} industry" + + try: + # Search with Tavily + tavily_results = do_tavily_ai_search(search_query) + + # Display the results using streamlit_display_results + if tavily_results: + streamlit_display_results(tavily_results) + + # Update progress + if status_container: + status_container.text("βœ… Tavily search complete!") + + # Ensure we return a valid dictionary even if tavily_results is None + if tavily_results is None: + logger.warning(f"No results returned from Tavily search for: {search_query}") + return {"sources": []} + + return tavily_results + except Exception as e: + logger.error(f"Error in Tavily search: {e}") + if status_container: + status_container.text(f"⚠️ Error in Tavily search: {str(e)}") + return {"sources": []} + + def _analyze_research_results(self, research_results: Dict, topic: str, industry: str) -> Dict: + """ + Analyze research results to extract key insights. + + Args: + research_results: Results from research + topic: The topic being researched + industry: The industry context + + Returns: + Dict containing analyzed insights + """ + # Handle case where research_results is None + if research_results is None: + logger.warning(f"No research results available for {topic} in {industry}") + return { + "key_insights": [f"Unable to find specific insights about {topic} in {industry}"], + "data_points": [f"No specific data available for {topic} in {industry}"], + "expert_quotes": [f"No expert quotes found about {topic} in {industry}"], + "industry_context": f"Limited information available about {topic} in {industry} industry" + } + + # Extract sources and content from research results + sources = research_results.get("sources", []) + content = "\n".join([source.get("content", "") for source in sources]) + + # Generate analysis prompt + analysis_prompt = f""" + Analyze the following research about {topic} in the {industry} industry: + + {content} + + Extract the following: + 1. Key insights and trends + 2. Supporting data and statistics + 3. Expert opinions and quotes + 4. Industry-specific context + + Format the response as a JSON with these keys: + - key_insights: List of main insights + - data_points: List of relevant data/statistics + - expert_quotes: List of expert opinions + - industry_context: Relevant industry information + """ + + # Generate analysis using LLM + analysis = llm_text_gen(analysis_prompt) + + # Parse the analysis + try: + analysis_dict = json.loads(analysis) + except json.JSONDecodeError: + # Fallback if JSON parsing fails + analysis_dict = { + "key_insights": ["Unable to parse analysis"], + "data_points": [], + "expert_quotes": [], + "industry_context": "Unable to parse industry context" + } + + return analysis_dict + + def generate_outline(self, research_results: Dict) -> Dict: + """ + Generate a detailed post outline based on research results. + + Args: + research_results: Dictionary containing analyzed research results + + Returns: + Dict containing the post outline + """ + # Extract key information from research results + topic = research_results.get("topic", "") + industry = research_results.get("industry", "") + key_insights = research_results.get("key_insights", []) + data_points = research_results.get("data_points", []) + expert_quotes = research_results.get("expert_quotes", []) + industry_context = research_results.get("industry_context", "") + + # Generate outline prompt + outline_prompt = f""" + Create a detailed LinkedIn post outline about {topic} in the {industry} industry. + + Use these research insights: + Key Insights: {', '.join(key_insights)} + Data Points: {', '.join(data_points)} + Expert Quotes: {', '.join(expert_quotes)} + Industry Context: {industry_context} + + The outline should include: + 1. A compelling hook that grabs attention and introduces the topic + 2. 3-4 main points that provide valuable insights, each supported by: + - Specific data or statistics + - Expert opinions or quotes + - Real-world examples or case studies + 3. A thought-provoking conclusion + 4. A clear call to action that encourages engagement + + Format the response as a JSON with these keys: + - hook: The opening statement (1-2 sentences) + - main_points: List of 3-4 main points (each 1-2 sentences) + - supporting_evidence: List of supporting evidence for each point (include data, quotes, examples) + - conclusion: A strong conclusion (1-2 sentences) + - call_to_action: The closing call to action (1-2 sentences) + - key_statistics: List of 2-3 key statistics to highlight + - expert_insights: List of 2-3 expert insights to include + """ + + # Generate outline using LLM + outline = llm_text_gen(outline_prompt) + + # Parse the outline + try: + outline_dict = json.loads(outline) + except json.JSONDecodeError: + # Fallback if JSON parsing fails + outline_dict = { + "hook": f"Let's talk about {topic}", + "main_points": ["Unable to generate main points"], + "supporting_evidence": ["Unable to generate supporting evidence"], + "conclusion": f"These insights highlight the importance of {topic}", + "call_to_action": "What are your thoughts on this topic?", + "key_statistics": ["No statistics available"], + "expert_insights": ["No expert insights available"] + } + + # Add topic and industry to the outline + outline_dict["topic"] = topic + outline_dict["industry"] = industry + + return outline_dict + + def generate_post_content(self, outline: Dict, tone: str = "professional", include_hashtags: bool = True) -> str: + """ + Generate detailed LinkedIn post content based on the outline. + + Args: + outline: Dictionary containing the post outline + tone: The tone to use for the post + include_hashtags: Whether to include hashtags + + Returns: + str: The generated post content + """ + # Extract outline components + hook = outline.get("hook", "") + main_points = outline.get("main_points", []) + supporting_evidence = outline.get("supporting_evidence", []) + conclusion = outline.get("conclusion", "") + call_to_action = outline.get("call_to_action", "") + key_statistics = outline.get("key_statistics", []) + expert_insights = outline.get("expert_insights", []) + topic = outline.get("topic", "") + industry = outline.get("industry", "") + + # Generate post prompt + post_prompt = f""" + Create a detailed, insightful LinkedIn post about {topic} in the {industry} industry with the following components: + + Hook: {hook} + Main Points: {', '.join(main_points)} + Supporting Evidence: {', '.join(supporting_evidence)} + Conclusion: {conclusion} + Call to Action: {call_to_action} + Key Statistics: {', '.join(key_statistics)} + Expert Insights: {', '.join(expert_insights)} + + Tone: {tone} + + IMPORTANT INSTRUCTIONS: + 1. Write ONLY the post content - no explanations or meta-commentary + 2. Do not include phrases like "Here's a LinkedIn post" or "I wrote this post" + 3. Format with appropriate emojis and line breaks for readability + 4. Make it engaging, professional, and insightful + 5. Include specific data points, statistics, and expert quotes where relevant + 6. Structure the post with clear paragraphs and bullet points where appropriate + 7. Keep the post between 800-1200 characters for optimal engagement + 8. Focus specifically on {topic} in the {industry} industry + 9. Include a personal perspective or experience if relevant + 10. End with a thought-provoking question to encourage comments + 11. Use line breaks between paragraphs for better readability + 12. Include relevant emojis to highlight key points + """ + + # Generate post content + post_content = llm_text_gen(post_prompt) + + # Clean up any potential explanations + post_content = post_content.replace("Here's a LinkedIn post:", "") + post_content = post_content.replace("LinkedIn post:", "") + post_content = post_content.replace("Post:", "") + post_content = post_content.strip() + + # Add hashtags if requested + if include_hashtags: + hashtags = self.optimize_hashtags(post_content) + if hashtags: + post_content += "\n\n" + " ".join(hashtags) + + return post_content + + def optimize_hashtags(self, post_content: str) -> List[str]: + """ + Generate optimized hashtags for a LinkedIn post. + + Args: + post_content: The content of the post + + Returns: + List[str]: List of optimized hashtags + """ + # Generate hashtag prompt + hashtag_prompt = f""" + Generate relevant hashtags for this LinkedIn post: + + {post_content} + + IMPORTANT INSTRUCTIONS: + 1. Return ONLY a comma-separated list of hashtags + 2. Do not include any explanations or commentary + 3. Each hashtag should start with # + 4. Do not include spaces in hashtags + 5. Include a mix of broad and specific hashtags + 6. Limit to 5-7 most relevant hashtags + 7. Make hashtags searchable and professional + 8. Do not include any text before or after the hashtags + """ + + # Generate hashtags + hashtag_response = llm_text_gen(hashtag_prompt) + + # Clean up the response + hashtag_response = hashtag_response.replace("Hashtags:", "") + hashtag_response = hashtag_response.replace("Suggested hashtags:", "") + hashtag_response = hashtag_response.strip() + + # Parse hashtags + hashtags = [tag.strip() for tag in hashtag_response.split(",")] + hashtags = [tag for tag in hashtags if tag.startswith("#")] + + return hashtags + + def generate_visual_content(self, post_content: str, topic: str) -> Dict: + """ + Generate visual content recommendations and create images for a LinkedIn post. + + Args: + post_content: The post content + topic: The post topic + + Returns: + Dict containing visual content recommendations and generated images + """ + logger.info(f"Generating visual content for topic: {topic}") + + # Create a progress bar + progress_bar = st.progress(0) + status_text = st.empty() + + # Update progress + progress_bar.progress(20) + status_text.text("Analyzing post content...") + + # Extract key elements from post content for better image generation + key_elements_prompt = f""" + Analyze this LinkedIn post content and extract SPECIFIC visual elements that would create a UNIQUE and TARGETED image: + + {post_content} + + Extract these elements and be EXTREMELY SPECIFIC: + 1. Main concept or message (What's the core idea we need to visualize?) + 2. Key data or statistics mentioned (Can we visualize these?) + 3. Industry-specific elements (What visuals would resonate with this industry?) + 4. Abstract concepts mentioned (How can we represent these visually?) + 5. Text to potentially overlay (What 2-3 word phrase would enhance the image?) + + Return ONLY a JSON object with these keys: + {{ + "main_concept": "The specific core concept to visualize", + "data_elements": ["List of specific data points to potentially visualize"], + "industry_visuals": ["List of industry-specific visual elements"], + "abstract_concepts": ["List of abstract concepts to represent"], + "potential_text": ["2-3 short phrases that could be overlaid on the image"], + "style_keywords": ["List of specific style keywords for this topic"] + }} + """ + + # Get key elements from LLM + key_elements_response = llm_text_gen(key_elements_prompt) + + # Parse the key elements + try: + key_elements = json.loads(key_elements_response) + except json.JSONDecodeError: + key_elements = { + "main_concept": topic, + "data_elements": [], + "industry_visuals": [], + "abstract_concepts": [], + "potential_text": [], + "style_keywords": ["professional", "modern", "clean"] + } + + # Update progress + progress_bar.progress(40) + status_text.text("Generating visual content recommendations...") + + # Randomly decide whether to include text overlay (30% chance) + include_text = random.random() < 0.3 + text_overlay = "" + if include_text and key_elements.get("potential_text"): + text_overlay = random.choice(key_elements.get("potential_text")) + + # Generate visual content recommendations using the extracted key elements + prompt = f""" + Generate HIGHLY SPECIFIC visual content recommendations for a LinkedIn post about {topic}. + + Content Focus: + - Main concept: {key_elements.get('main_concept')} + - Data elements: {', '.join(key_elements.get('data_elements', []))} + - Industry visuals: {', '.join(key_elements.get('industry_visuals', []))} + - Abstract concepts: {', '.join(key_elements.get('abstract_concepts', []))} + - Style keywords: {', '.join(key_elements.get('style_keywords', []))} + + Create recommendations that: + 1. Are UNIQUE to this specific post and topic + 2. Incorporate actual elements from the post content + 3. Use industry-specific imagery and symbolism + 4. {"Include text overlay: " + text_overlay if include_text else "Focus on purely visual elements"} + + Format as JSON: + {{ + "main_image": {{ + "concept": "Detailed description of the main image", + "prompt": "Detailed generation prompt that is SPECIFIC to this post", + "colors": ["Color 1", "Color 2", "Color 3"], + "text_overlay": "Text to overlay (if any)" + }}, + "alternative_formats": [ + {{ + "format": "Format name", + "description": "Description", + "suggestions": ["Suggestion 1", "Suggestion 2"] + }} + ] + }} + """ + + # Get visual content recommendations from LLM + visual_content_response = llm_text_gen(prompt) + + # Parse the visual content recommendations + try: + visual_content = json.loads(visual_content_response) + + # Enhance the prompt with specific style and quality requirements + base_prompt = visual_content["main_image"]["prompt"] + text_part = f", with elegant text overlay: '{text_overlay}'" if include_text else "" + + visual_content["main_image"]["prompt"] = f""" + {base_prompt}{text_part}, + Style requirements: ultra high quality, 8k resolution, professional photography, + dramatic lighting, perfect composition, extremely detailed, + corporate style, professional context, LinkedIn-optimized, + specific to {topic}, unique visualization + """.strip() + + except json.JSONDecodeError: + visual_content = { + "main_image": { + "concept": f"Professional visualization of {topic}", + "prompt": f"Professional visualization of {topic}, ultra high quality, 8k resolution, dramatic lighting", + "colors": ["#0A66C2", "#FFFFFF", "#000000"], + "text_overlay": text_overlay if include_text else "" + }, + "alternative_formats": [] + } + + # Update progress + progress_bar.progress(100) + status_text.text("Visual content recommendations generated!") + time.sleep(0.5) + + return visual_content + + def generate_image(self, prompt: str, refinement: str = "") -> str: + """ + Generate an image using Stable Diffusion. + + Args: + prompt: The image generation prompt + refinement: Optional refinement to the prompt + + Returns: + str: Path to the generated image + """ + try: + # Import the image generation function from the correct location + from .....gpt_providers.text_to_image_generation.main_generate_image_from_prompt import generate_image as generate_image_from_prompt + + # Enhance the prompt with additional details for better quality + enhanced_prompt = f""" + {prompt} + + Additional requirements: + - High resolution, 4K quality + - Professional lighting and composition + - Sharp focus and clear details + - Suitable for LinkedIn's professional audience + - Clean, modern aesthetic + - No text or watermarks + """ + + # Combine enhanced prompt with refinement if provided + full_prompt = f"{enhanced_prompt} {refinement}".strip() + + # Generate the image + image_path = generate_image_from_prompt(full_prompt) + + return image_path + except Exception as e: + logger.error(f"Error generating image: {e}") + return None + + def predict_engagement(self, post_content: str, hashtags: List[str]) -> Dict: + """ + Predict engagement metrics for a LinkedIn post. + + Args: + post_content: The post content + hashtags: The hashtags used in the post + + Returns: + Dict containing engagement predictions + """ + logger.info("Predicting engagement metrics") + + # Create a progress bar + progress_bar = st.progress(0) + status_text = st.empty() + + # Update progress + progress_bar.progress(20) + status_text.text("Analyzing post content and hashtags...") + + # Predict engagement using LLM + prompt = f""" + Predict engagement metrics for the following LinkedIn post: + + Content: + {post_content} + + Hashtags: + {', '.join(hashtags)} + + Predict the following metrics: + 1. Likely number of likes + 2. Likely number of comments + 3. Likely number of shares + 4. Likely number of profile visits + 5. Engagement rate + + Format the predictions as a JSON object with the following structure: + {{ + "likes": "Predicted number of likes", + "comments": "Predicted number of comments", + "shares": "Predicted number of shares", + "profile_visits": "Predicted number of profile visits", + "engagement_rate": "Predicted engagement rate" + }} + """ + + # Update progress + progress_bar.progress(40) + status_text.text("Predicting engagement with AI...") + + # Get engagement predictions from LLM + engagement_response = llm_text_gen(prompt) + + # Parse the engagement predictions + try: + engagement_prediction = json.loads(engagement_response) + except json.JSONDecodeError: + # Fallback engagement prediction if JSON parsing fails + engagement_prediction = { + "likes": "50-100", + "comments": "10-20", + "shares": "5-10", + "profile_visits": "20-30", + "engagement_rate": "3-5%" + } + + # Update progress + progress_bar.progress(80) + status_text.text("Finalizing engagement predictions...") + + # Store the engagement predictions + self.engagement_prediction = engagement_prediction + + # Complete progress + progress_bar.progress(100) + status_text.text("Engagement predictions generated!") + time.sleep(0.5) + + return engagement_prediction + + def suggest_posting_time(self, industry: str) -> List[Dict]: + """ + Suggest optimal posting times based on industry and audience activity. + + Args: + industry: The industry context + + Returns: + List of suggested posting times + """ + logger.info(f"Suggesting posting times for industry: {industry}") + + # Create a progress bar + progress_bar = st.progress(0) + status_text = st.empty() + + # Update progress + progress_bar.progress(20) + status_text.text("Analyzing industry data...") + + # Suggest posting times using LLM + prompt = f""" + Suggest optimal posting times for LinkedIn posts in the {industry} industry. + + Consider: + - Industry-specific audience activity patterns + - Global time zones + - Day of the week variations + - Best practices for the {industry} industry + + Format the suggestions as a JSON array with the following structure: + [ + {{ + "day": "Day of the week", + "time": "Time of day", + "timezone": "Timezone", + "reason": "Reason for this suggestion" + }} + ] + + Provide 3-5 suggestions. + """ + + # Update progress + progress_bar.progress(40) + status_text.text("Generating posting time suggestions...") + + # Get posting time suggestions from LLM + posting_time_response = llm_text_gen(prompt) + + # Parse the posting time suggestions + try: + posting_time_suggestions = json.loads(posting_time_response) + except json.JSONDecodeError: + # Fallback posting time suggestions if JSON parsing fails + posting_time_suggestions = [ + { + "day": "Tuesday", + "time": "9:00 AM", + "timezone": "EST", + "reason": "Highest engagement for professional content" + }, + { + "day": "Wednesday", + "time": "12:00 PM", + "timezone": "EST", + "reason": "Lunch break engagement peak" + }, + { + "day": "Thursday", + "time": "3:00 PM", + "timezone": "EST", + "reason": "End of workday engagement" + } + ] + + # Update progress + progress_bar.progress(80) + status_text.text("Finalizing posting time suggestions...") + + # Store the posting time suggestions + self.posting_time_suggestions = posting_time_suggestions + + # Complete progress + progress_bar.progress(100) + status_text.text("Posting time suggestions generated!") + time.sleep(0.5) + + return posting_time_suggestions + + def create_poll(self, topic: str, industry: str) -> Dict: + """ + Create a poll for a LinkedIn post. + + Args: + topic: The post topic + industry: The industry context + + Returns: + Dict containing the poll + """ + logger.info(f"Creating poll for topic: {topic}") + + # Create a progress bar + progress_bar = st.progress(0) + status_text = st.empty() + + # Update progress + progress_bar.progress(20) + status_text.text("Analyzing topic and industry...") + + # Create poll using LLM + prompt = f""" + Create a LinkedIn poll about {topic} in the {industry} industry. + + The poll should: + - Be relevant to the topic and industry + - Have 4 options + - Be engaging and encourage participation + - Follow LinkedIn poll best practices + + Format the poll as a JSON object with the following structure: + {{ + "question": "The poll question", + "options": ["Option 1", "Option 2", "Option 3", "Option 4"], + "duration": "Poll duration in days", + "follow_up_post": "Suggested follow-up post after poll ends" + }} + """ + + # Update progress + progress_bar.progress(40) + status_text.text("Generating poll with AI...") + + # Get poll from LLM + poll_response = llm_text_gen(prompt) + + # Parse the poll + try: + poll = json.loads(poll_response) + except json.JSONDecodeError: + # Fallback poll if JSON parsing fails + poll = { + "question": f"What's your biggest challenge with {topic}?", + "options": [ + f"Option 1 related to {topic}", + f"Option 2 related to {topic}", + f"Option 3 related to {topic}", + f"Option 4 related to {topic}" + ], + "duration": "7", + "follow_up_post": f"Thanks for participating in our poll about {topic}! Here are the results and insights..." + } + + # Update progress + progress_bar.progress(80) + status_text.text("Finalizing poll...") + + # Store the poll + self.poll = poll + + # Complete progress + progress_bar.progress(100) + status_text.text("Poll created!") + time.sleep(0.5) + + return poll + + def _extract_image_prompts_from_post(self, post_content: str) -> List[str]: + """ + Extract potential image prompts from the post content. + + Args: + post_content: The content of the post + + Returns: + List[str]: List of extracted image prompts + """ + # Generate prompt for extracting image prompts + prompt = f""" + Create 3 HIGHLY SPECIFIC image prompts from this LinkedIn post content. + Each prompt should create a unique, content-specific image that directly relates to the post's message. + + Post content: + {post_content} + + For each prompt, follow these requirements: + 1. Focus on SPECIFIC concepts, data, or insights from the post + 2. Include clear visual elements that represent the post's main message + 3. Specify exact composition, style, and technical details + 4. Add relevant industry-specific elements + 5. Consider including a short text overlay (2-3 words max) that enhances the message + 6. Make the image unique to this post - avoid generic business imagery + + Technical Requirements for Each Prompt: + - Main subject placement and composition + - Lighting style and atmosphere + - Color scheme and mood + - Camera angle and perspective + - Background elements and context + - Foreground details and focal points + - Text overlay (if appropriate) + - Industry-specific visual elements + + Return ONLY a JSON array of 3 complete image prompts. + Each prompt should be extremely detailed and specific to this post's content. + Do not include any explanations or additional text. + """ + + # Generate image prompts using LLM + image_prompts_response = llm_text_gen(prompt) + + try: + # Parse the image prompts + image_prompts = json.loads(image_prompts_response) + + # Enhance each prompt with quality requirements + enhanced_prompts = [] + for prompt in image_prompts: + # Randomly decide whether to include text overlay (30% chance) + include_text = random.random() < 0.3 + + # Extract a potential text overlay from the prompt if it exists + text_overlay = "" + if include_text: + # Look for text in quotes within the prompt + import re + text_matches = re.findall(r'"([^"]*)"', prompt) + if text_matches: + text_overlay = text_matches[0] + if len(text_overlay.split()) > 3: # Ensure text is not too long + text_overlay = " ".join(text_overlay.split()[:3]) + + enhanced_prompt = f""" + {prompt}, + Style requirements: ultra high quality, 8k resolution, professional photography, + dramatic lighting, perfect composition, extremely detailed, + corporate style, professional context, LinkedIn-optimized + {f', with elegant text overlay: "{text_overlay}"' if text_overlay else ''} + """.strip() + + enhanced_prompts.append(enhanced_prompt) + + return enhanced_prompts + + except json.JSONDecodeError: + logger.error("Failed to parse image prompts JSON") + return [] + + +def linkedin_post_generator_ui(): + """ + Streamlit UI for LinkedIn Post Generator. + """ + st.title("πŸ“ LinkedIn Post Generator") + st.write("Generate engaging LinkedIn posts with AI assistance") + + # Initialize generator + generator = LinkedInPostGenerator() + + # Initialize session state for storing post data + if "post_data" not in st.session_state: + st.session_state.post_data = { + "post_content": "", + "hashtags": [], + "visual_content": None, + "topic": "", + "industry": "", + "tone": "Professional", + "include_hashtags": True, + "include_visual": True, + "include_poll": False, + "include_timing": True, + "search_engine": "metaphor" + } + + # Input form + with st.form("linkedin_post_form"): + topic = st.text_input("Post Topic", placeholder="Enter the main topic of your post") + industry = st.text_input("Industry", placeholder="e.g., Technology, Healthcare, Finance") + tone = st.selectbox("Tone", ["Professional", "Casual", "Informative", "Inspirational"]) + + # Search engine selection + search_engine = st.radio( + "Select Search Engine", + ["Metaphor AI Search", "Google Search", "Tavily AI Search"], + index=0, + format_func=lambda x: x + ) + search_engine = search_engine.lower().replace(" search", "").replace(" ai", "") + + col1, col2 = st.columns(2) + with col1: + include_hashtags = st.checkbox("Include Hashtags", value=True) + include_visual = st.checkbox("Include Visual Content", value=True) + with col2: + include_poll = st.checkbox("Include Poll", value=False) + include_timing = st.checkbox("Include Posting Time", value=True) + + submit = st.form_submit_button("Generate Post") + + if submit and topic and industry: + with st.spinner("Generating your LinkedIn post..."): + # Research the topic + research_results = generator.research_topic(topic, industry, search_engine) + + # Generate outline + outline = generator.generate_outline(research_results) + + # Generate post content + post_content = generator.generate_post_content(outline, tone, include_hashtags) + + # Generate hashtags if requested + hashtags = [] + if include_hashtags: + hashtags = generator.optimize_hashtags(post_content) + + # Generate visual content if requested + visual_content = None + if include_visual: + visual_content = generator.generate_visual_content(post_content, topic) + # Generate image automatically + if visual_content and "main_image" in visual_content: + image_path = generator.generate_image(visual_content["main_image"]["prompt"]) + if image_path: + st.session_state.generated_image_path = image_path + + # Store data in session state + st.session_state.post_data = { + "post_content": post_content, + "hashtags": hashtags, + "visual_content": visual_content, + "topic": topic, + "industry": industry, + "tone": tone, + "include_hashtags": include_hashtags, + "include_visual": include_visual, + "include_poll": include_poll, + "include_timing": include_timing, + "search_engine": search_engine + } + + # Display results if we have post data + if st.session_state.post_data["post_content"]: + # Display results + st.markdown("---") + st.subheader("πŸ“ Your LinkedIn Post") + + # Create tabs for different sections + tab1, tab2, tab3 = st.tabs(["πŸ“ Post Content", "πŸ“Š Analytics & Timing", "πŸ–ΌοΈ Visual Content"]) + + with tab1: + # Post preview container with image if available + with st.container(): + col1, col2 = st.columns([2, 1]) + with col1: + st.markdown("### Post Content") + st.markdown(st.session_state.post_data["post_content"]) + + if st.session_state.post_data["hashtags"]: + st.markdown("### Hashtags") + st.markdown(" ".join(st.session_state.post_data["hashtags"])) + + with col2: + if "generated_image_path" in st.session_state: + st.image(st.session_state.generated_image_path, caption="Generated Image", use_container_width=True) + with open(st.session_state.generated_image_path, "rb") as file: + st.download_button( + label="Download Image", + data=file, + file_name=f"linkedin_image_{st.session_state.post_data['topic'].replace(' ', '_').lower()}.png", + mime="image/png" + ) + + # Action buttons + col1, col2, col3 = st.columns(3) + with col1: + if st.button("πŸ“‹ Copy to Clipboard"): + st.write("Post copied to clipboard!") + with col2: + if st.button("πŸ’Ύ Download as Text"): + st.write("Post downloaded successfully!") + with col3: + if st.button("πŸ”„ Generate New Post"): + # Clear session state + st.session_state.post_data = { + "post_content": "", + "hashtags": [], + "visual_content": None, + "topic": "", + "industry": "", + "tone": "Professional", + "include_hashtags": True, + "include_visual": True, + "include_poll": False, + "include_timing": True, + "search_engine": "metaphor" + } + if "generated_image_path" in st.session_state: + del st.session_state.generated_image_path + st.experimental_rerun() + + with tab2: + # Engagement predictions + st.markdown("### Engagement Predictions") + st.info("This post is predicted to perform well based on current LinkedIn trends.") + + # Posting time suggestions + if st.session_state.post_data["include_timing"]: + st.markdown("### Suggested Posting Times") + st.info("Best times to post: Tuesday-Thursday, 9:00 AM - 11:00 AM") + + with tab3: + if st.session_state.post_data["include_visual"] and st.session_state.post_data["visual_content"]: + visual_content = st.session_state.post_data["visual_content"] + + # Display image concept + st.markdown("#### Image Concept") + st.write(visual_content["main_image"]["concept"]) + + # Display color scheme + st.markdown("#### Color Scheme") + colors = visual_content["main_image"]["colors"] + for i, color in enumerate(colors): + st.markdown(f"
", unsafe_allow_html=True) + + # Image generation section + st.markdown("#### Generate Image") + + # Store the image prompt in session state if not already there + if "image_prompt" not in st.session_state: + st.session_state.image_prompt = visual_content["main_image"]["prompt"] + + # Display the current prompt + st.markdown("**Current Prompt:**") + st.code(st.session_state.image_prompt) + + # Refinement input + refinement = st.text_input("Refine the image prompt (optional)", + placeholder="Add details like 'more vibrant colors' or 'include text overlay'") + + # Generate image button + if st.button("Generate New Image"): + with st.spinner("Generating image..."): + # Generate the image + image_path = generator.generate_image(st.session_state.image_prompt, refinement) + + if image_path: + # Store the image path in session state + st.session_state.generated_image_path = image_path + st.success("Image generated successfully!") + else: + st.error("Failed to generate image. Please try again.") + + # Extract image prompts from post content + st.markdown("#### Image Prompts from Post") + post_content = st.session_state.post_data["post_content"] + + # Generate image prompts from post content + image_prompts = generator._extract_image_prompts_from_post(post_content) + + if image_prompts: + for i, prompt in enumerate(image_prompts): + st.markdown(f"**Prompt {i+1}:**") + st.code(prompt) + if st.button(f"Use Prompt {i+1}", key=f"use_prompt_{i}"): + st.session_state.image_prompt = prompt + st.success(f"Prompt {i+1} set as current prompt!") + st.experimental_rerun() + else: + st.info("No image prompts found in the post content.") + + # Alternative formats + st.markdown("#### Alternative Formats") + for alt_format in visual_content["alternative_formats"]: + st.markdown(f"**{alt_format['format']}:**") + st.write(alt_format["description"]) + st.markdown("**Suggestions:**") + for suggestion in alt_format["suggestions"]: + st.write(f"- {suggestion}") + elif submit: + st.error("Please provide both a topic and industry.") + + +if __name__ == "__main__": + linkedin_post_generator_ui() \ No newline at end of file diff --git a/lib/ai_writers/linkedin_writer/modules/profile_optimizer/README.md b/lib/ai_writers/linkedin_writer/modules/profile_optimizer/README.md new file mode 100644 index 00000000..dc9fdaa3 --- /dev/null +++ b/lib/ai_writers/linkedin_writer/modules/profile_optimizer/README.md @@ -0,0 +1,184 @@ +# LinkedIn Profile Optimizer + +## Overview +The LinkedIn Profile Optimizer is an AI-powered tool designed to help content creators maximize their LinkedIn presence and professional appeal. This tool analyzes and enhances every aspect of your LinkedIn profile to increase visibility, engagement, and professional opportunities. + +## Key Features + +### 1. Profile Strength Analysis πŸ“Š +- Comprehensive profile audit with section-by-section scoring +- SEO optimization recommendations +- Missing elements identification +- Prioritized improvement suggestions +- Engagement potential assessment +- Overall profile strength score + +### 2. Headline Optimizer 🎯 +- Industry-specific keyword optimization +- Value proposition enhancement +- Achievement integration +- Character count optimization +- Professional title recommendations +- SEO-friendly formatting + +### 3. About Section Generator πŸ“ +- Compelling narrative creation +- Professional journey storytelling +- Achievement showcase +- Industry expertise highlighting +- Call-to-action optimization +- Target audience alignment +- Proper formatting and structure + +### 4. Experience Description Enhancer πŸ’Ό +- Action verb optimization +- Quantifiable achievement integration +- Key responsibility highlighting +- Industry-relevant keyword incorporation +- Professional formatting +- Impact measurement metrics +- Role-specific enhancements + +### 5. Skills Recommender πŸŽ“ +- Industry-trending skills analysis +- Role-specific recommendations +- Technical and soft skills balance +- Skill categorization +- Obsolete skill identification +- Endorsement strategy suggestions + +## Getting Started + +### Prerequisites +- Python 3.8 or higher +- Streamlit +- Access to ALwrity's AI services + +### Usage + +1. **Profile Analysis** + ```python + # Initialize the optimizer + optimizer = LinkedInProfileOptimizer() + + # Analyze profile + analysis = await optimizer.analyze_profile_strength(profile_data) + ``` + +2. **Headline Optimization** + ```python + # Optimize your headline + headline_result = await optimizer.optimize_headline( + current_headline="Your current headline", + industry="Your industry", + role="Your role" + ) + ``` + +3. **About Section Generation** + ```python + # Generate optimized About section + about_section = await optimizer.generate_about_section( + current_about="Your current about", + experience=experience_list, + achievements=achievements_list, + target_audience="Your target audience" + ) + ``` + +## Best Practices for Content Creators + +### 1. Profile Optimization Strategy +- Start with the Profile Analysis to identify key improvement areas +- Focus on your headline first - it's your first impression +- Craft your About section to showcase your content creation expertise +- Highlight your content creation achievements with metrics +- Include multimedia samples of your work + +### 2. Content Creator Specific Tips +- Emphasize your content creation specialties in your headline +- Showcase engagement metrics in your experience descriptions +- Include platform-specific expertise (LinkedIn, YouTube, etc.) +- Highlight collaboration experiences with brands +- Demonstrate thought leadership in your niche + +### 3. SEO Optimization +- Use industry-standard content creation terms +- Include platform-specific keywords +- Incorporate trending industry hashtags +- Balance creative and professional terminology +- Optimize for both human readers and search algorithms + +### 4. Skills Strategy +- Balance technical content creation skills with soft skills +- Include platform-specific skills (LinkedIn content creation, etc.) +- Add emerging content formats (Shorts, Lives, etc.) +- Include analytics and measurement skills +- Showcase collaboration and community management abilities + +## Advanced Features + +### Custom URL Optimization +- Professional URL structure recommendations +- Brand alignment suggestions +- SEO-friendly formatting +- Consistency with other social profiles + +### Project Highlights +- Content campaign showcases +- Viral content examples +- Brand collaboration features +- Impact metrics display +- Portfolio integration + +### Endorsement Strategy +- Skill endorsement prioritization +- Network engagement tactics +- Reciprocal endorsement approaches +- Expertise validation methods + +## Performance Metrics + +The Profile Optimizer evaluates profiles based on: +- Profile Completeness Score +- Keyword Optimization Level +- Content Quality Metrics +- Engagement Potential +- Network Growth Indicators +- Professional Appeal Score + +## Tips for Maximum Impact + +1. **Regular Updates** + - Review and update your profile monthly + - Add new content creation achievements regularly + - Keep skills current with industry trends + - Update metrics and performance statistics + +2. **Content Strategy Integration** + - Align profile messaging with your content + - Cross-reference your content platforms + - Showcase your content creation process + - Highlight your unique value proposition + +3. **Network Growth** + - Optimize for your target audience + - Use industry-specific terminology + - Showcase collaboration opportunities + - Highlight your community engagement + +## Support and Resources + +For additional support: +- Check the [ALwrity Documentation](https://docs.alwrity.com) +- Join our [Content Creator Community](https://community.alwrity.com) +- Follow our [LinkedIn Page](https://linkedin.com/company/alwrity) +- Contact support at support@alwrity.com + +## Contributing + +We welcome contributions from the content creator community! Please read our [Contributing Guidelines](CONTRIBUTING.md) for details on submitting pull requests. + +## License + +This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. \ No newline at end of file diff --git a/lib/ai_writers/linkedin_writer/modules/profile_optimizer/__init__.py b/lib/ai_writers/linkedin_writer/modules/profile_optimizer/__init__.py new file mode 100644 index 00000000..9ea2d0a5 --- /dev/null +++ b/lib/ai_writers/linkedin_writer/modules/profile_optimizer/__init__.py @@ -0,0 +1,11 @@ +""" +LinkedIn Profile Optimizer Module + +This module provides AI-powered optimization for LinkedIn profiles to improve visibility +and professional appeal. +""" + +from .linkedin_profile_optimizer import LinkedInProfileOptimizer +from .linkedin_profile_optimizer_ui import linkedin_profile_optimizer_ui + +__all__ = ['LinkedInProfileOptimizer', 'linkedin_profile_optimizer_ui'] \ No newline at end of file diff --git a/lib/ai_writers/linkedin_writer/modules/profile_optimizer/linkedin_profile_optimizer.py b/lib/ai_writers/linkedin_writer/modules/profile_optimizer/linkedin_profile_optimizer.py new file mode 100644 index 00000000..49b07e80 --- /dev/null +++ b/lib/ai_writers/linkedin_writer/modules/profile_optimizer/linkedin_profile_optimizer.py @@ -0,0 +1,240 @@ +""" +LinkedIn Profile Optimizer + +This module provides AI-powered optimization for LinkedIn profiles to improve visibility +and professional appeal. +""" + +import json +from typing import Dict, List, Optional +from loguru import logger + +from .....gpt_providers.text_generation.main_text_generation import llm_text_gen +from .....ai_web_researcher.gpt_online_researcher import do_google_serp_search +from .....ai_web_researcher.metaphor_basic_neural_web_search import metaphor_search_articles +from .....ai_web_researcher.tavily_ai_search import do_tavily_ai_search + +class LinkedInProfileOptimizer: + """ + AI-powered LinkedIn Profile Optimizer that enhances profiles for better visibility + and professional appeal. + """ + + def __init__(self): + """Initialize the LinkedIn Profile Optimizer.""" + self.industry_keywords = {} + self.seo_patterns = {} + self.profile_sections = [ + "headline", + "about", + "experience", + "skills", + "projects", + "endorsements", + "summary", + "custom_url" + ] + + async def optimize_headline(self, current_headline: str, industry: str, role: str) -> Dict: + """ + Optimize the LinkedIn headline for better visibility and impact. + + Args: + current_headline: Current LinkedIn headline + industry: User's industry + role: User's current or target role + + Returns: + Dict containing optimized headline and explanation + """ + prompt = f""" + As an expert LinkedIn profile optimizer, enhance this headline for maximum impact and visibility: + Current Headline: {current_headline} + Industry: {industry} + Role: {role} + + Consider: + - Including relevant keywords for {industry} + - Highlighting unique value proposition + - Using industry-standard titles + - Incorporating achievements or specialties + - Keeping it under LinkedIn's character limit + + Return a JSON with: + - optimized_headline: The enhanced headline + - explanation: Why changes were made + - keywords_used: Key terms included + """ + + response = await llm_text_gen(prompt) + return json.loads(response) + + async def generate_about_section(self, + current_about: str, + experience: List[Dict], + achievements: List[str], + target_audience: str + ) -> Dict: + """ + Generate an optimized About section. + + Args: + current_about: Current About section content + experience: List of work experiences + achievements: List of key achievements + target_audience: Intended profile visitors + + Returns: + Dict containing new About section and explanation + """ + prompt = f""" + As an expert LinkedIn profile writer, create an engaging About section that showcases professional value: + + Current About: {current_about} + Key Experiences: {json.dumps(experience)} + Achievements: {json.dumps(achievements)} + Target Audience: {target_audience} + + Consider: + - Strong opening hook + - Professional journey narrative + - Key achievements and impact + - Industry expertise + - Call to action + - Proper formatting and structure + + Return a JSON with: + - about_section: The optimized content + - structure_explanation: Section breakdown + - impact_factors: Key elements that drive engagement + """ + + response = await llm_text_gen(prompt) + return json.loads(response) + + async def enhance_experience_descriptions(self, + experiences: List[Dict] + ) -> List[Dict]: + """ + Enhance work experience descriptions for better impact. + + Args: + experiences: List of work experiences with roles and descriptions + + Returns: + List of enhanced experience descriptions + """ + enhanced_experiences = [] + + for exp in experiences: + prompt = f""" + As an expert LinkedIn profile writer, enhance this work experience description: + + Role: {exp.get('role')} + Company: {exp.get('company')} + Current Description: {exp.get('description')} + + Enhance the description to: + - Lead with strong action verbs + - Include quantifiable achievements + - Highlight key responsibilities + - Incorporate relevant keywords + - Use proper formatting + + Return a JSON with: + - enhanced_description: The improved description + - achievements_highlighted: Key accomplishments + - keywords_used: Industry terms included + """ + + response = await llm_text_gen(prompt) + enhanced_exp = json.loads(response) + enhanced_experiences.append({ + **exp, + 'enhanced_description': enhanced_exp['enhanced_description'], + 'achievements': enhanced_exp['achievements_highlighted'], + 'keywords': enhanced_exp['keywords_used'] + }) + + return enhanced_experiences + + async def recommend_skills(self, + current_skills: List[str], + industry: str, + role: str + ) -> Dict: + """ + Recommend relevant skills based on industry and role. + + Args: + current_skills: List of current skills + industry: User's industry + role: User's role + + Returns: + Dict containing skill recommendations + """ + # Research trending skills in the industry + industry_research = await do_tavily_ai_search( + f"most in-demand skills for {role} in {industry} LinkedIn 2024" + ) + + prompt = f""" + As a LinkedIn profile optimization expert, recommend skills based on: + + Current Skills: {json.dumps(current_skills)} + Industry: {industry} + Role: {role} + Industry Research: {json.dumps(industry_research)} + + Provide: + - Must-have technical skills + - Important soft skills + - Trending skills in the industry + - Skills to remove (if any) + + Return a JSON with: + - recommended_skills: New skills to add + - skills_to_remove: Skills to consider removing + - skill_categories: Grouping of skills by category + - trending_skills: Currently popular skills + """ + + response = await llm_text_gen(prompt) + return json.loads(response) + + async def analyze_profile_strength(self, + profile_data: Dict + ) -> Dict: + """ + Analyze overall profile strength and provide improvement recommendations. + + Args: + profile_data: Complete profile information + + Returns: + Dict containing analysis and recommendations + """ + prompt = f""" + As a LinkedIn profile optimization expert, analyze this profile: + + Profile Data: {json.dumps(profile_data)} + + Provide a comprehensive analysis including: + - Overall profile strength score + - Section-by-section analysis + - Missing elements + - Improvement opportunities + - SEO optimization suggestions + - Engagement potential + + Return a JSON with: + - strength_score: 0-100 rating + - section_scores: Individual section ratings + - missing_elements: Key missing components + - priority_improvements: Ordered list of suggestions + - seo_recommendations: Keyword and optimization tips + """ + + response = await llm_text_gen(prompt) + return json.loads(response) \ No newline at end of file diff --git a/lib/ai_writers/linkedin_writer/modules/profile_optimizer/linkedin_profile_optimizer_ui.py b/lib/ai_writers/linkedin_writer/modules/profile_optimizer/linkedin_profile_optimizer_ui.py new file mode 100644 index 00000000..551ae076 --- /dev/null +++ b/lib/ai_writers/linkedin_writer/modules/profile_optimizer/linkedin_profile_optimizer_ui.py @@ -0,0 +1,209 @@ +""" +LinkedIn Profile Optimizer UI + +This module provides the Streamlit UI for the LinkedIn Profile Optimizer. +""" + +import streamlit as st +import json +from typing import Dict, List +from .linkedin_profile_optimizer import LinkedInProfileOptimizer + +async def linkedin_profile_optimizer_ui(): + """ + Streamlit UI for the LinkedIn Profile Optimizer. + """ + # Initialize the profile optimizer + optimizer = LinkedInProfileOptimizer() + + # Create tabs for different optimization sections + tabs = st.tabs([ + "Profile Analysis", + "Headline Optimizer", + "About Section", + "Experience Enhancer", + "Skills Recommender" + ]) + + # Profile Analysis Tab + with tabs[0]: + st.header("Profile Strength Analysis") + st.info("Upload your profile information for a comprehensive analysis") + + # Profile Data Input + with st.expander("Enter Profile Information", expanded=True): + profile_data = { + "headline": st.text_input("Current Headline"), + "about": st.text_area("About Section"), + "industry": st.text_input("Industry"), + "current_role": st.text_input("Current Role"), + "experience": [], + "skills": st.text_area("Current Skills (one per line)").split("\n"), + "education": st.text_area("Education (one per line)").split("\n") + } + + # Experience Input + st.subheader("Work Experience") + num_experiences = st.number_input("Number of experiences to add", min_value=0, max_value=10, value=1) + + for i in range(num_experiences): + with st.expander(f"Experience {i+1}"): + exp = { + "role": st.text_input(f"Role {i+1}"), + "company": st.text_input(f"Company {i+1}"), + "description": st.text_area(f"Description {i+1}") + } + profile_data["experience"].append(exp) + + if st.button("Analyze Profile"): + with st.spinner("Analyzing your profile..."): + analysis = await optimizer.analyze_profile_strength(profile_data) + + # Display Analysis Results + col1, col2 = st.columns(2) + + with col1: + st.metric("Profile Strength Score", f"{analysis['strength_score']}/100") + + st.subheader("Section Scores") + for section, score in analysis['section_scores'].items(): + st.progress(score/100, text=f"{section}: {score}%") + + with col2: + st.subheader("Priority Improvements") + for improvement in analysis['priority_improvements']: + st.warning(improvement) + + st.subheader("SEO Recommendations") + for rec in analysis['seo_recommendations']: + st.info(rec) + + # Headline Optimizer Tab + with tabs[1]: + st.header("Headline Optimizer") + st.info("Optimize your headline for better visibility and impact") + + current_headline = st.text_input("Current Headline") + industry = st.text_input("Industry") + role = st.text_input("Current/Target Role") + + if st.button("Optimize Headline"): + with st.spinner("Generating optimized headline..."): + headline_optimization = await optimizer.optimize_headline( + current_headline, + industry, + role + ) + + st.subheader("Optimized Headline") + st.success(headline_optimization['optimized_headline']) + + st.subheader("Optimization Explanation") + st.write(headline_optimization['explanation']) + + st.subheader("Keywords Used") + for keyword in headline_optimization['keywords_used']: + st.info(keyword) + + # About Section Tab + with tabs[2]: + st.header("About Section Generator") + st.info("Create an engaging and professional About section") + + current_about = st.text_area("Current About Section") + achievements = st.text_area("Key Achievements (one per line)").split("\n") + target_audience = st.text_input("Target Audience") + + if st.button("Generate About Section"): + with st.spinner("Generating optimized About section..."): + about_optimization = await optimizer.generate_about_section( + current_about, + profile_data.get("experience", []), + achievements, + target_audience + ) + + st.subheader("Optimized About Section") + st.markdown(about_optimization['about_section']) + + st.subheader("Section Structure") + for section, explanation in about_optimization['structure_explanation'].items(): + with st.expander(section): + st.write(explanation) + + st.subheader("Impact Factors") + for factor in about_optimization['impact_factors']: + st.success(factor) + + # Experience Enhancer Tab + with tabs[3]: + st.header("Experience Description Enhancer") + st.info("Enhance your work experience descriptions for maximum impact") + + experiences = [] + num_exp = st.number_input("Number of experiences to enhance", min_value=1, max_value=10, value=1) + + for i in range(num_exp): + with st.expander(f"Experience {i+1}"): + exp = { + "role": st.text_input(f"Role {i+1}"), + "company": st.text_input(f"Company {i+1}"), + "description": st.text_area(f"Current Description {i+1}") + } + experiences.append(exp) + + if st.button("Enhance Experiences"): + with st.spinner("Enhancing experience descriptions..."): + enhanced_experiences = await optimizer.enhance_experience_descriptions(experiences) + + for i, exp in enumerate(enhanced_experiences): + with st.expander(f"Enhanced Experience {i+1}"): + st.subheader(f"{exp['role']} at {exp['company']}") + st.markdown(exp['enhanced_description']) + + st.subheader("Key Achievements") + for achievement in exp['achievements']: + st.success(achievement) + + st.subheader("Keywords Used") + for keyword in exp['keywords']: + st.info(keyword) + + # Skills Recommender Tab + with tabs[4]: + st.header("Skills Recommender") + st.info("Get personalized skill recommendations for your profile") + + current_skills = st.text_area("Current Skills (one per line)").split("\n") + industry = st.text_input("Industry (for skills)") + role = st.text_input("Role (for skills)") + + if st.button("Get Skill Recommendations"): + with st.spinner("Analyzing and recommending skills..."): + skill_recommendations = await optimizer.recommend_skills( + current_skills, + industry, + role + ) + + col1, col2 = st.columns(2) + + with col1: + st.subheader("Recommended Skills to Add") + for skill in skill_recommendations['recommended_skills']: + st.success(skill) + + st.subheader("Consider Removing") + for skill in skill_recommendations['skills_to_remove']: + st.warning(skill) + + with col2: + st.subheader("Trending Skills") + for skill in skill_recommendations['trending_skills']: + st.info(skill) + + st.subheader("Skill Categories") + for category, skills in skill_recommendations['skill_categories'].items(): + with st.expander(category): + for skill in skills: + st.write(f"- {skill}") \ No newline at end of file diff --git a/lib/ai_writers/linkedin_writer/modules/video_script_generator/README.md b/lib/ai_writers/linkedin_writer/modules/video_script_generator/README.md new file mode 100644 index 00000000..bf490a6e --- /dev/null +++ b/lib/ai_writers/linkedin_writer/modules/video_script_generator/README.md @@ -0,0 +1,207 @@ +# LinkedIn Video Script Generator + +## Overview + +The LinkedIn Video Script Generator is an AI-powered tool designed to help professionals create engaging and effective video scripts for LinkedIn content. This tool combines research-driven insights with professional scriptwriting techniques to generate compelling video content that resonates with your target audience. + +## Features + +### 1. Script Generation +- **Hook Creation**: Generate attention-grabbing openings that capture viewer interest +- **Story Structure**: Create well-organized scripts with clear sections and transitions +- **Professional Speaking Points**: Generate key talking points and emphasis areas +- **Multiple Video Types**: + - Thought Leadership + - Tutorial/How-to + - Product Demo + - Company Culture + - Industry Insights + - Event Highlights + - Customer Story + - Behind the Scenes + +### 2. Research Integration +- **Multi-Source Research**: Gather insights from: + - Metaphor (neural search) + - Google SERP + - Tavily AI +- **Insight Extraction**: Automatically extract key insights and trends +- **Topic Analysis**: Research current trends and best practices + +### 3. Visual Elements +- **Visual Cue Suggestions**: Get recommendations for: + - B-roll footage + - Graphics and animations + - Text overlays + - Visual transitions +- **Timing Guidance**: Precise timing suggestions for each visual element +- **Visual Hierarchy**: Optimize visual flow and emphasis + +### 4. Engagement Optimization +- **Call-to-Action Generation**: Create compelling CTAs +- **Engagement Prompts**: Generate discussion questions and interaction points +- **Audience Targeting**: Tailor content to specific professional audiences +- **Tone Customization**: Multiple tone options: + - Professional & Authoritative + - Conversational & Friendly + - Educational & Informative + - Inspirational & Motivational + - Storytelling & Narrative + +### 5. Length Options +- Short (< 1 minute) +- Medium (1-3 minutes) +- Long (3-5 minutes) +- Extended (5+ minutes) + +## Usage + +### Basic Workflow + +1. **Enter Basic Details** + - Choose your topic + - Select your industry + - Pick a video type + - Set desired length + +2. **Configure Advanced Options** + - Select target audience + - Choose tone + - Set additional preferences + +3. **Research Your Topic** + - Choose research source + - Review insights and trends + - Incorporate key findings + +4. **Generate Script** + - Get complete script with sections + - Review visual suggestions + - Check engagement elements + +### Best Practices + +#### Script Structure +1. **Strong Hook (0-15 seconds)** + - Capture attention immediately + - Present clear value proposition + - Use professional tone + +2. **Content Flow (Main Body)** + - Clear section transitions + - Logical progression + - Engaging visuals + +3. **Effective Closing** + - Strong call-to-action + - Engagement prompts + - Next steps + +#### Visual Elements +1. **Professional Quality** + - High-quality visuals + - Consistent branding + - Clear text overlays + +2. **Engagement Focus** + - Strategic visual pacing + - Attention-holding elements + - Professional transitions + +3. **Brand Consistency** + - Aligned with brand guidelines + - Professional appearance + - Consistent style + +#### Content Tips by Video Type + +##### Thought Leadership +- Focus on unique insights +- Share expert perspective +- Include industry trends + +##### Tutorial/How-to +- Clear step-by-step structure +- Visual demonstrations +- Practical examples + +##### Product Demo +- Feature highlights +- Use cases +- Value proposition + +##### Company Culture +- Authentic representation +- Team involvement +- Behind-the-scenes elements + +## Technical Details + +### Dependencies +- Streamlit: For the user interface +- GPT Providers: For AI text generation +- Web Research Tools: For gathering insights + +### Integration +The Video Script Generator is fully integrated into the LinkedIn AI Writer suite and can be accessed through the main interface. + +## Examples + +### Example 1: Tech Industry Thought Leadership +**Topic**: "The Future of AI in Business" +**Length**: 2-3 minutes +**Target**: Decision Makers + +### Example 2: Healthcare Tutorial +**Topic**: "Implementing Telehealth Solutions" +**Length**: 3-5 minutes +**Target**: Healthcare Professionals + +### Example 3: Product Demo +**Topic**: "New CRM Features Overview" +**Length**: 1-2 minutes +**Target**: Business Users + +## Troubleshooting + +### Common Issues + +1. **Research Not Returning Results** + - Try different search terms + - Switch research sources + - Check connection + +2. **Script Generation Issues** + - Verify all fields are filled + - Try different topic phrasing + - Check length settings + +3. **Visual Suggestions** + - Ensure script sections are clear + - Check timing specifications + - Review section content + +## Future Enhancements + +- **AI Voice Generation**: Add AI voiceover capabilities +- **Template Library**: Pre-built script templates +- **Analytics Integration**: Performance tracking +- **Multi-language Support**: Generate scripts in multiple languages +- **Direct Publishing**: Integration with LinkedIn video upload + +## Contributing + +Contributions to the LinkedIn Video Script Generator are welcome! Please follow these steps: + +1. Fork the repository +2. Create a feature branch +3. Make your changes +4. Submit a pull request + +## License + +This project is licensed under the MIT License - see the LICENSE file for details. + +## Contact + +For questions or feedback about the LinkedIn Video Script Generator, please contact the development team. \ No newline at end of file diff --git a/lib/ai_writers/linkedin_writer/modules/video_script_generator/__init__.py b/lib/ai_writers/linkedin_writer/modules/video_script_generator/__init__.py new file mode 100644 index 00000000..0a393a03 --- /dev/null +++ b/lib/ai_writers/linkedin_writer/modules/video_script_generator/__init__.py @@ -0,0 +1,10 @@ +""" +LinkedIn Video Script Generator Module + +This module provides functionality for generating professional video scripts +for LinkedIn content with AI-powered optimization and engagement features. +""" + +from .linkedin_video_script_generator import LinkedInVideoScriptGenerator, linkedin_video_script_generator_ui + +__all__ = ["LinkedInVideoScriptGenerator", "linkedin_video_script_generator_ui"] \ No newline at end of file diff --git a/lib/ai_writers/linkedin_writer/modules/video_script_generator/linkedin_video_script_generator.py b/lib/ai_writers/linkedin_writer/modules/video_script_generator/linkedin_video_script_generator.py new file mode 100644 index 00000000..927bce16 --- /dev/null +++ b/lib/ai_writers/linkedin_writer/modules/video_script_generator/linkedin_video_script_generator.py @@ -0,0 +1,462 @@ +""" +LinkedIn Video Script Generator + +This module provides functionality for generating professional video scripts +for LinkedIn content with AI-powered optimization and engagement features. +""" + +import streamlit as st +import json +from typing import Dict, List, Optional, Union +from loguru import logger + +from .....gpt_providers.text_generation.main_text_generation import llm_text_gen +from .....ai_web_researcher.gpt_online_researcher import do_google_serp_search +from .....ai_web_researcher.metaphor_basic_neural_web_search import metaphor_search_articles +from .....ai_web_researcher.tavily_ai_search import do_tavily_ai_search + + +class LinkedInVideoScriptGenerator: + """ + AI-powered LinkedIn video script generator that creates engaging scripts with + hooks, story structure, and visual suggestions. + """ + + def __init__(self): + """Initialize the LinkedIn Video Script Generator.""" + self.video_types = { + "thought_leadership": "Thought Leadership", + "tutorial": "Tutorial/How-to", + "product_demo": "Product Demo", + "company_culture": "Company Culture", + "industry_insights": "Industry Insights", + "event_highlights": "Event Highlights", + "customer_story": "Customer Story", + "behind_scenes": "Behind the Scenes" + } + + self.video_lengths = { + "short": "Short (< 1 minute)", + "medium": "Medium (1-3 minutes)", + "long": "Long (3-5 minutes)", + "extended": "Extended (5+ minutes)" + } + + self.tone_options = { + "professional": "Professional & Authoritative", + "conversational": "Conversational & Friendly", + "educational": "Educational & Informative", + "inspirational": "Inspirational & Motivational", + "storytelling": "Storytelling & Narrative" + } + + self.target_audiences = { + "professionals": "Industry Professionals", + "decision_makers": "Decision Makers", + "job_seekers": "Job Seekers", + "students": "Students & Early Career", + "entrepreneurs": "Entrepreneurs & Business Owners", + "general": "General Professional Network" + } + + def research_topic(self, topic: str, industry: str, search_engine: str = "metaphor") -> Dict: + """ + Research a topic to gather insights for video content. + + Args: + topic: The main topic for the video + industry: The target industry + search_engine: The search engine to use (metaphor, google, tavily) + + Returns: + Dict containing research results and insights + """ + try: + search_query = f"{topic} {industry} trends insights best practices" + + if search_engine == "metaphor": + articles = metaphor_search_articles(search_query) + elif search_engine == "google": + articles = do_google_serp_search(search_query) + elif search_engine == "tavily": + articles = do_tavily_ai_search(search_query) + else: + raise ValueError(f"Unsupported search engine: {search_engine}") + + insights, trends = self._extract_insights_and_trends(articles) + + return { + "articles": articles, + "insights": insights, + "trends": trends + } + + except Exception as e: + logger.error(f"Error researching topic: {str(e)}") + return { + "articles": [], + "insights": [], + "trends": [] + } + + def _extract_insights_and_trends(self, articles: List[Dict]) -> tuple[List[str], List[str]]: + """Extract key insights and trends from research articles.""" + try: + prompt = f""" + Based on the following articles, extract: + 1. Key insights relevant for a video script + 2. Current trends in the industry + + Articles: + {json.dumps(articles, indent=2)} + + Return a JSON with two lists: 'insights' and 'trends' + """ + + response = llm_text_gen(prompt) + result = json.loads(response) + + return result.get("insights", []), result.get("trends", []) + + except Exception as e: + logger.error(f"Error extracting insights and trends: {str(e)}") + return [], [] + + def generate_hook(self, topic: str, video_type: str, target_audience: str, tone: str) -> str: + """ + Generate an attention-grabbing hook for the video. + + Args: + topic: The main topic of the video + video_type: Type of video content + target_audience: Target audience for the video + tone: Desired tone of the hook + + Returns: + str: The generated hook + """ + try: + prompt = f""" + Create an attention-grabbing hook for a LinkedIn video with: + - Topic: {topic} + - Video Type: {self.video_types[video_type]} + - Target Audience: {self.target_audiences[target_audience]} + - Tone: {self.tone_options[tone]} + + The hook should be: + 1. Under 15 seconds when spoken + 2. Immediately capture attention + 3. Clear value proposition + 4. Professional and engaging + + Return only the hook text. + """ + + return llm_text_gen(prompt) + + except Exception as e: + logger.error(f"Error generating hook: {str(e)}") + return "" + + def generate_story_structure(self, topic: str, video_type: str, length: str, insights: List[str]) -> Dict: + """ + Generate a structured outline for the video. + + Args: + topic: Main topic of the video + video_type: Type of video content + length: Target video length + insights: Research insights to incorporate + + Returns: + Dict containing the story structure + """ + try: + prompt = f""" + Create a professional video script structure for LinkedIn with: + - Topic: {topic} + - Video Type: {self.video_types[video_type]} + - Length: {self.video_lengths[length]} + - Key Insights: {json.dumps(insights)} + + Return a JSON with: + 1. sections: List of sections with timing and content + 2. transitions: List of smooth transitions + 3. key_points: Main points to emphasize + 4. pacing_notes: Guidance on pacing and delivery + """ + + response = llm_text_gen(prompt) + return json.loads(response) + + except Exception as e: + logger.error(f"Error generating story structure: {str(e)}") + return { + "sections": [], + "transitions": [], + "key_points": [], + "pacing_notes": [] + } + + def generate_visual_cues(self, script_sections: List[Dict]) -> List[Dict]: + """ + Generate visual suggestions for each section of the script. + + Args: + script_sections: List of script sections + + Returns: + List of visual cue suggestions + """ + try: + prompt = f""" + For each section of the LinkedIn video script, suggest visual elements: + + Script Sections: + {json.dumps(script_sections, indent=2)} + + For each section, provide: + 1. Visual type (b-roll, graphics, text overlay, etc.) + 2. Description of visual content + 3. Timing and duration + 4. Visual transitions + + Return a JSON array of visual suggestions. + """ + + response = llm_text_gen(prompt) + return json.loads(response) + + except Exception as e: + logger.error(f"Error generating visual cues: {str(e)}") + return [] + + def generate_full_script(self, topic: str, video_type: str, length: str, + target_audience: str, tone: str, insights: List[str]) -> Dict: + """ + Generate a complete video script with all components. + + Args: + topic: Main topic of the video + video_type: Type of video content + length: Target video length + target_audience: Target audience + tone: Desired tone + insights: Research insights + + Returns: + Dict containing the complete script + """ + try: + # Generate hook + hook = self.generate_hook(topic, video_type, target_audience, tone) + + # Generate story structure + structure = self.generate_story_structure(topic, video_type, length, insights) + + # Generate visual cues + visuals = self.generate_visual_cues(structure["sections"]) + + # Generate call-to-action + cta = self.generate_cta(topic, video_type, target_audience) + + # Combine all components + script = { + "metadata": { + "topic": topic, + "video_type": video_type, + "length": length, + "target_audience": target_audience, + "tone": tone + }, + "hook": hook, + "structure": structure, + "visuals": visuals, + "cta": cta + } + + return script + + except Exception as e: + logger.error(f"Error generating full script: {str(e)}") + return {} + + def generate_cta(self, topic: str, video_type: str, target_audience: str) -> Dict: + """Generate an effective call-to-action.""" + try: + prompt = f""" + Create an effective call-to-action for a LinkedIn video: + - Topic: {topic} + - Video Type: {self.video_types[video_type]} + - Target Audience: {self.target_audiences[target_audience]} + + Return a JSON with: + 1. primary_cta: Main call-to-action text + 2. secondary_cta: Optional follow-up action + 3. engagement_prompts: Questions or prompts for comments + """ + + response = llm_text_gen(prompt) + return json.loads(response) + + except Exception as e: + logger.error(f"Error generating CTA: {str(e)}") + return {} + +def linkedin_video_script_generator_ui(): + """Streamlit UI for the LinkedIn Video Script Generator.""" + + st.title("LinkedIn Video Script Generator") + st.markdown(""" + Create professional video scripts for LinkedIn that drive engagement and showcase your expertise. + """) + + # Initialize the video script generator + generator = LinkedInVideoScriptGenerator() + + # Create tabs for different sections + tab1, tab2, tab3 = st.tabs(["Script Details", "Research & Insights", "Generated Script"]) + + with tab1: + st.header("Video Script Details") + + # Basic information + col1, col2 = st.columns(2) + with col1: + topic = st.text_input("Topic", placeholder="e.g., AI in Healthcare, Remote Work Best Practices") + industry = st.text_input("Industry", placeholder="e.g., Technology, Healthcare, Finance") + + with col2: + video_type = st.selectbox( + "Video Type", + options=list(generator.video_types.keys()), + format_func=lambda x: generator.video_types[x] + ) + length = st.selectbox( + "Video Length", + options=list(generator.video_lengths.keys()), + format_func=lambda x: generator.video_lengths[x] + ) + + # Advanced options + with st.expander("Advanced Options"): + col3, col4 = st.columns(2) + with col3: + target_audience = st.selectbox( + "Target Audience", + options=list(generator.target_audiences.keys()), + format_func=lambda x: generator.target_audiences[x] + ) + + with col4: + tone = st.selectbox( + "Tone", + options=list(generator.tone_options.keys()), + format_func=lambda x: generator.tone_options[x] + ) + + with tab2: + st.header("Research & Insights") + + if topic and industry: + # Research options + search_engine = st.selectbox( + "Research Source", + options=["metaphor", "google", "tavily"], + format_func=lambda x: x.title() + ) + + if st.button("Research Topic"): + with st.spinner("Researching topic..."): + research_results = generator.research_topic(topic, industry, search_engine) + + if research_results["insights"] or research_results["trends"]: + # Store results in session state + st.session_state.research_results = research_results + + # Display insights + st.subheader("Key Insights") + for insight in research_results["insights"]: + st.markdown(f"- {insight}") + + # Display trends + st.subheader("Current Trends") + for trend in research_results["trends"]: + st.markdown(f"- {trend}") + else: + st.warning("No insights found. Try adjusting your topic or using a different research source.") + else: + st.info("Please enter a topic and industry in the Script Details tab to research insights.") + + with tab3: + st.header("Generated Script") + + if all([topic, industry, video_type, length, target_audience, tone]): + if st.button("Generate Script"): + with st.spinner("Generating video script..."): + # Get insights from research if available + insights = [] + if hasattr(st.session_state, 'research_results'): + insights = st.session_state.research_results.get("insights", []) + + # Generate full script + script = generator.generate_full_script( + topic=topic, + video_type=video_type, + length=length, + target_audience=target_audience, + tone=tone, + insights=insights + ) + + if script: + # Display hook + st.subheader("Hook") + st.write(script["hook"]) + + # Display structure + st.subheader("Script Structure") + for i, section in enumerate(script["structure"]["sections"], 1): + with st.expander(f"Section {i}"): + st.write(f"**Timing:** {section.get('timing', 'N/A')}") + st.write(f"**Content:** {section.get('content', 'N/A')}") + + # Display visual cues for this section + if script["visuals"]: + visual = next((v for v in script["visuals"] if v.get("section") == i), None) + if visual: + st.write("**Visual Elements:**") + st.write(f"- Type: {visual.get('type', 'N/A')}") + st.write(f"- Description: {visual.get('description', 'N/A')}") + st.write(f"- Duration: {visual.get('duration', 'N/A')}") + + # Display transitions + st.subheader("Transitions") + for transition in script["structure"]["transitions"]: + st.markdown(f"- {transition}") + + # Display key points + st.subheader("Key Points to Emphasize") + for point in script["structure"]["key_points"]: + st.markdown(f"- {point}") + + # Display CTA + st.subheader("Call-to-Action") + st.write(f"**Primary CTA:** {script['cta'].get('primary_cta', 'N/A')}") + if script['cta'].get('secondary_cta'): + st.write(f"**Secondary CTA:** {script['cta']['secondary_cta']}") + + # Display engagement prompts + if script['cta'].get('engagement_prompts'): + st.subheader("Engagement Prompts") + for prompt in script['cta']['engagement_prompts']: + st.markdown(f"- {prompt}") + + # Display pacing notes + st.subheader("Pacing & Delivery Notes") + for note in script["structure"]["pacing_notes"]: + st.markdown(f"- {note}") + else: + st.error("Failed to generate script. Please try again.") + else: + st.info("Please fill in all required fields in the Script Details tab to generate a script.") \ No newline at end of file diff --git a/lib/gpt_providers/text_to_image_generation/main_generate_image_from_prompt.py b/lib/gpt_providers/text_to_image_generation/main_generate_image_from_prompt.py index 92c1eb50..a16d9897 100644 --- a/lib/gpt_providers/text_to_image_generation/main_generate_image_from_prompt.py +++ b/lib/gpt_providers/text_to_image_generation/main_generate_image_from_prompt.py @@ -55,7 +55,6 @@ def generate_image(user_prompt): image_stored_at = generate_dalle3_images(img_prompt) elif 'Stability-AI' in image_engine: logger.info(f"Calling Stable diffusion text-to-image with prompt: \n{img_prompt}") - print("\n\n") image_stored_at = generate_stable_diffusion_image(img_prompt) elif 'Gemini-AI' in image_engine: logger.info(f"Calling Gemini text-to-image with prompt: \n{img_prompt}") diff --git a/lib/utils/alwrity_utils.py b/lib/utils/alwrity_utils.py index 540a555e..51cc726c 100644 --- a/lib/utils/alwrity_utils.py +++ b/lib/utils/alwrity_utils.py @@ -15,8 +15,8 @@ from lib.ai_writers.ai_news_article_writer import ai_news_generation #from lib.ai_writers.ai_agents_crew_writer import ai_agents_writers from lib.ai_writers.ai_financial_writer import write_basic_ta_report from lib.ai_writers.ai_facebook_writer.facebook_ai_writer import facebook_main_menu -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.linkedin_writer.linkedin_ai_writer import linkedin_main_menu +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 from lib.ai_writers.web_url_ai_writer import blog_from_url diff --git a/lib/utils/content_generators.py b/lib/utils/content_generators.py index 568520b3..b8b22ee7 100644 --- a/lib/utils/content_generators.py +++ b/lib/utils/content_generators.py @@ -8,6 +8,7 @@ from lib.alwrity_ui.keyword_web_researcher import do_web_research 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.content_planning_calender.content_planning_agents_alwrity_crew import ai_agents_content_planner @@ -20,6 +21,7 @@ def ai_writers(): "Write Financial TA report", "AI Product Description Writer", "AI Copywriter", + "LinkedIn AI Writer", "Quit" ] choice = st.selectbox("**πŸ‘‡Select a content creation type:**", options, index=0, format_func=lambda x: f"πŸ“ {x}") @@ -39,6 +41,10 @@ def ai_writers(): elif choice == "AI Copywriter": # Initialize the copywriter dashboard copywriter_dashboard() + elif choice == "LinkedIn AI Writer": + # Initialize the LinkedIn AI Writer + linkedin_writer = LinkedInAIWriter() + linkedin_writer.run() elif choice == "Quit": st.info("Thank you for using Alwrity. Goodbye!") st.stop() diff --git a/lib/utils/ui_setup.py b/lib/utils/ui_setup.py index f4be28d4..358abcb4 100644 --- a/lib/utils/ui_setup.py +++ b/lib/utils/ui_setup.py @@ -8,7 +8,7 @@ from lib.utils.settings_page import render_settings_page # Import social media writer functions from lib.ai_writers.ai_facebook_writer.facebook_ai_writer import facebook_main_menu -from lib.ai_writers.linkedin_ai_writer import linked_post_writer +from lib.ai_writers.linkedin_writer.linkedin_ai_writer import linkedin_main_menu 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 @@ -321,7 +321,7 @@ def setup_alwrity_ui(): # Define sub-menu items for AI Social Tools social_tools_submenu = { "Facebook": ("πŸ“˜", lambda: facebook_main_menu()), - "LinkedIn": ("πŸ’Ό", lambda: linked_post_writer()), + "LinkedIn": ("πŸ’Ό", lambda: linkedin_main_menu()), "Twitter": ("🐦", lambda: tweet_writer()), "Instagram": ("πŸ“Έ", lambda: insta_writer()), "YouTube": ("πŸŽ₯", lambda: youtube_main_menu()) diff --git a/lib/workspace/alwrity_content/generated_image_2025-04-13-21-55-28.webp b/lib/workspace/alwrity_content/generated_image_2025-04-13-21-55-28.webp new file mode 100644 index 00000000..283dc16a Binary files /dev/null and b/lib/workspace/alwrity_content/generated_image_2025-04-13-21-55-28.webp differ diff --git a/lib/workspace/alwrity_content/generated_image_2025-04-13-22-15-51.webp b/lib/workspace/alwrity_content/generated_image_2025-04-13-22-15-51.webp new file mode 100644 index 00000000..7c9e9fb4 Binary files /dev/null and b/lib/workspace/alwrity_content/generated_image_2025-04-13-22-15-51.webp differ diff --git a/lib/workspace/alwrity_content/generated_image_2025-04-13-22-35-25.webp b/lib/workspace/alwrity_content/generated_image_2025-04-13-22-35-25.webp new file mode 100644 index 00000000..2d5e438a Binary files /dev/null and b/lib/workspace/alwrity_content/generated_image_2025-04-13-22-35-25.webp differ diff --git a/lib/workspace/alwrity_content/generated_image_2025-04-13-22-43-24.webp b/lib/workspace/alwrity_content/generated_image_2025-04-13-22-43-24.webp new file mode 100644 index 00000000..6301e26b Binary files /dev/null and b/lib/workspace/alwrity_content/generated_image_2025-04-13-22-43-24.webp differ diff --git a/lib/workspace/alwrity_content/generated_image_2025-04-13-22-44-16.webp b/lib/workspace/alwrity_content/generated_image_2025-04-13-22-44-16.webp new file mode 100644 index 00000000..faf9822c Binary files /dev/null and b/lib/workspace/alwrity_content/generated_image_2025-04-13-22-44-16.webp differ diff --git a/lib/workspace/alwrity_content/generated_image_2025-04-14-12-07-45.webp b/lib/workspace/alwrity_content/generated_image_2025-04-14-12-07-45.webp new file mode 100644 index 00000000..2795b2c9 Binary files /dev/null and b/lib/workspace/alwrity_content/generated_image_2025-04-14-12-07-45.webp differ diff --git a/lib/workspace/alwrity_content/generated_image_2025-04-14-12-27-58.webp b/lib/workspace/alwrity_content/generated_image_2025-04-14-12-27-58.webp new file mode 100644 index 00000000..128cf7ef Binary files /dev/null and b/lib/workspace/alwrity_content/generated_image_2025-04-14-12-27-58.webp differ diff --git a/lib/workspace/alwrity_content/generated_image_2025-04-14-12-48-36.webp b/lib/workspace/alwrity_content/generated_image_2025-04-14-12-48-36.webp new file mode 100644 index 00000000..e8039769 Binary files /dev/null and b/lib/workspace/alwrity_content/generated_image_2025-04-14-12-48-36.webp differ diff --git a/lib/workspace/alwrity_content/generated_image_2025-04-14-13-16-09.webp b/lib/workspace/alwrity_content/generated_image_2025-04-14-13-16-09.webp new file mode 100644 index 00000000..abb81739 Binary files /dev/null and b/lib/workspace/alwrity_content/generated_image_2025-04-14-13-16-09.webp differ diff --git a/lib/workspace/alwrity_content/generated_image_2025-04-14-15-04-19.webp b/lib/workspace/alwrity_content/generated_image_2025-04-14-15-04-19.webp new file mode 100644 index 00000000..57a3ecf4 Binary files /dev/null and b/lib/workspace/alwrity_content/generated_image_2025-04-14-15-04-19.webp differ diff --git a/lib/workspace/alwrity_content/generated_image_2025-04-14-15-15-54.webp b/lib/workspace/alwrity_content/generated_image_2025-04-14-15-15-54.webp new file mode 100644 index 00000000..02d56897 Binary files /dev/null and b/lib/workspace/alwrity_content/generated_image_2025-04-14-15-15-54.webp differ diff --git a/lib/workspace/alwrity_content/generated_image_2025-04-14-16-53-49.webp b/lib/workspace/alwrity_content/generated_image_2025-04-14-16-53-49.webp new file mode 100644 index 00000000..2aae946c Binary files /dev/null and b/lib/workspace/alwrity_content/generated_image_2025-04-14-16-53-49.webp differ diff --git a/lib/workspace/alwrity_content/generated_image_2025-04-14-17-23-01.webp b/lib/workspace/alwrity_content/generated_image_2025-04-14-17-23-01.webp new file mode 100644 index 00000000..3dcd9cc2 Binary files /dev/null and b/lib/workspace/alwrity_content/generated_image_2025-04-14-17-23-01.webp differ diff --git a/lib/workspace/alwrity_content/generated_image_2025-04-14-17-55-52.webp b/lib/workspace/alwrity_content/generated_image_2025-04-14-17-55-52.webp new file mode 100644 index 00000000..3a57f3c0 Binary files /dev/null and b/lib/workspace/alwrity_content/generated_image_2025-04-14-17-55-52.webp differ diff --git a/lib/workspace/alwrity_content/generated_image_2025-04-14-18-12-46.webp b/lib/workspace/alwrity_content/generated_image_2025-04-14-18-12-46.webp new file mode 100644 index 00000000..710abbe6 Binary files /dev/null and b/lib/workspace/alwrity_content/generated_image_2025-04-14-18-12-46.webp differ diff --git a/lib/workspace/alwrity_content/generated_image_2025-04-14-18-28-03.webp b/lib/workspace/alwrity_content/generated_image_2025-04-14-18-28-03.webp new file mode 100644 index 00000000..0a11e9df Binary files /dev/null and b/lib/workspace/alwrity_content/generated_image_2025-04-14-18-28-03.webp differ diff --git a/lib/workspace/alwrity_content/generated_image_2025-04-14-18-37-25.webp b/lib/workspace/alwrity_content/generated_image_2025-04-14-18-37-25.webp new file mode 100644 index 00000000..8f20fd94 Binary files /dev/null and b/lib/workspace/alwrity_content/generated_image_2025-04-14-18-37-25.webp differ