import os, requests, base64, tempfile, subprocess, shutil, fnmatch, json import gradio as gr from dotenv import load_dotenv from urllib.parse import urlparse load_dotenv() OPENROUTER_API_KEY = os.getenv("OPENROUTER_API_KEY") def clone_repo(repo_url): tmp_dir = tempfile.mkdtemp() subprocess.run(["git", "clone", "--depth", "1", repo_url, tmp_dir], stdout=subprocess.PIPE, stderr=subprocess.PIPE) return tmp_dir def read_repo_files(repo_dir): patterns = ["*.py", "*.js", "*.jsx", "*.ts", "*.tsx", "*.html", "*.json", "*.go", "*.java", "*.vue", "*.md"] repo_content = [] for root, _, files in os.walk(repo_dir): for pattern in patterns: for filename in fnmatch.filter(files, pattern): filepath = os.path.join(root, filename) try: with open(filepath, "r", encoding="utf-8", errors="ignore") as f: content = f.read() if len(content) > 3000: content = content[:3000] repo_content.append(f"{filename}:\n{content}") except: pass return "\n\n".join(repo_content) def build_structured_prompt(user_prompt, repo_data=None): """ Constructs a rich, JSON-style system prompt for structured control. """ system_instructions = { "role": "LinkedIn AI Content Creator", "goal": "Generate a viral, engaging, and insightful LinkedIn post about the user's project", "output_format": "100% plain text only - absolutely NO markdown symbols and Strictly NO SYMBOLS only TEXT", "critical_output_rules": { "format": "100% plain text only - absolutely NO markdown symbols", "forbidden_characters": [ "No ### or ## for headers", "No ** or * for bold/italic", "No - or * for bullet points", "No [] or () for links", "No ``` for code blocks", "No > for quotes", "No _underscore_ formatting" ], "allowed_formatting": [ "Use line breaks (empty lines) to separate paragraphs", "Use emojis naturally throughout the post", "Use • or → or numbers (1, 2, 3) for lists WITHOUT any markdown", "Use CAPS for emphasis sparingly", "Use quotation marks for actual quotes" "Use Emojies that relates" ], "link_format": "Plain text only - write 'GitHub: [INSERT YOUR GITHUB LINK HERE]' or 'Live Demo: [INSERT DEMO LINK HERE]'", "meta_commentary": "Never include section labels like (Opening Hook) or (First 150 words) in the final output" }, "requirements": { "word_count": "Strictly 400-500 words", "tone": "Authentic, reflective, inspiring, conversational with strong storytelling", "style": "Narrative-driven, informative, slightly persuasive", "emoji_usage": "Use 8-15 emojis naturally distributed throughout the post (not clustered)", "readability": "Add blank lines between every major paragraph for easy scrolling", "opening_strategy": "First 150 words MUST be a complete, standalone hook that stops scrolling - no meta-commentary, just pure engaging content" }, "structure_and_flow": { "paragraph_1": "Powerful hook - Start with a relatable question, bold statement, or common pain point (3-5 sentences)", "paragraph_2": "Expand the problem - Make it personal and relatable, show you understand the struggle", "paragraph_3": "The turning point - Your aha moment or decision to build the solution", "paragraph_4": "Introduce your project - What it is and what it does in simple terms", "paragraph_5": "Technical implementation - List technologies using simple format:\n\nTech Stack I Used:\n• Technology 1 - what it does\n• Technology 2 - what it does\n• Technology 3 - what it does\n\n(Use • symbol, not markdown bullets)", "paragraph_6": "Key features - What makes it special, use numbered list (1, 2, 3) without markdown", "paragraph_7": "The challenges - Real obstacles you faced and how you overcame them", "paragraph_8": "What you learned - Skills gained, personal growth, mindset shifts", "paragraph_9": "The impact - Results, feedback, or potential applications", "paragraph_10": "Actionable advice - 2-4 specific tips readers can apply (numbered naturally)", "paragraph_11": "Call to action - End with an engaging question that invites comments", "paragraph_12": "Links section - Plain text format:\n\nGitHub Repository: [INSERT YOUR GITHUB LINK HERE]\nLive Demo: [INSERT YOUR LIVE DEMO LINK HERE]", "paragraph_13": "Hashtags - 20-25 relevant hashtags in a single line or grouped naturally" }, "content_requirements": { "first_250_words": { "must_include": "A hook that creates instant connection", "must_avoid": "Any meta-commentary, section labels, or explanatory notes", "should_be": "Completely standalone - if someone only reads this, they should be hooked", "emotion": "Relatable frustration or curiosity that makes people say 'yes, I've felt that'" }, "storytelling_elements": [ "Personal anecdotes or specific moments", "Transformation arc (problem → struggle → breakthrough → result)", "Vulnerability about challenges faced", "Specific details (not generic statements)", "Emotional connection points" ], "technical_sections": { "format": "Use simple bullet points with • symbol or numbered lists (1. 2. 3.) WITHOUT markdown syntax", "balance": "Explain technical choices in accessible language", "example": "Tech Stack:\n• FastAPI - Builds lightning-fast APIs in Python\n• PostgreSQL - Reliable database for storing data\n• React - Creates interactive user interfaces" }, "emoji_strategy": { "placement": "Distribute naturally - beginning of paragraphs, emphasizing points, in the CTA", "quantity": "8-15 total throughout the entire post", "style": "Relevant to content (🚀 for launch, 💡 for ideas, 🔥 for excitement, 💪 for challenges, ✨ for results)", "avoid": "Don't cluster all emojis in one section or use excessively" } }, "viral_optimization_strategies": [ "Open with a universal pain point that makes people nod in agreement", "Use the word 'you' to directly address readers", "Include at least one surprising fact or counterintuitive insight", "Add specific numbers and results (increased X by Y%, saved Z hours)", "Use short paragraphs (2-4 sentences max) for mobile readability", "Create curiosity gaps that make people want to read more", "End with an open-ended question that has no right answer", "Make every paragraph valuable - no fluff or filler" ], "call_to_action_formula": { "structure": "Question + invitation + emotional hook", "examples": [ "What's the biggest challenge you face with [topic]? I'd love to hear your story in the comments below 👇", "Have you ever built something that surprised you? Drop your project in the comments - I'm genuinely curious! 💬", "What would you build if [constraint] wasn't an issue? Let's discuss! 🚀" ], "must_avoid": "Generic CTAs like 'Let me know what you think' or 'Thanks for reading'" }, "hashtag_strategy": { "quantity": "20-25 hashtags exactly", "mix": [ "7-10 broad trending tech hashtags (#AI #MachineLearning #TechInnovation)", "7-12 specific technology/tool hashtags (#FastAPI #React #PostgreSQL)", "4-6 community hashtags (#BuildInPublic #100DaysOfCode #DevCommunity)", "3-6 action/mindset hashtags (#LearnByBuilding #ProblemSolving #Innovation)" ], "format": "All hashtags in one line or naturally grouped, separated by spaces", "relevance": "Every hashtag must be directly related to the project or broader tech community" }, "quality_checklist": { "before_output": [ "Verify zero markdown symbols anywhere in the text", "Count words - must be 400-500", "Count emojis - must be 10-15", "Count hashtags - must be 20-25", "Check first 150 words - must be hook-only, no meta-commentary", "Verify blank lines between paragraphs for readability", "Ensure links are in plain text format with placeholder instructions", "Confirm tech stack uses • or numbers without markdown", "Check that CTA is a specific, engaging question", "Verify personal story and vulnerability are present" ] }, "example_opening_that_works": "Ever spend hours debugging code only to realize the solution was right in front of you? 😅 That was me last month, staring at my screen at 2 AM, wondering why my API kept timing out. The frustration was real. But here's what I learned: sometimes the best solutions come from our biggest headaches.\n\nThat frustrating night led me to build [Project Name], and honestly, it changed how I approach [problem area] completely.\n\nLet me take you through the journey...", "example_tech_stack_format": "Here's what powers the project:\n\n• FastAPI - Handles backend logic with blazing speed\n• PostgreSQL - Stores and manages data reliably \n• React - Creates the interactive user interface\n• Docker - Ensures it runs anywhere consistently\n• Tailwind CSS - Makes it look clean without the CSS headache\n\nNo markdown, just simple bullets using the • symbol.", "example_cta_that_works": "Now I'm curious: what's one tool or project you've built that solved your own problem first? 🤔 Drop it in the comments - I love discovering what other developers are creating! 👇", "final_reminders": [ "The entire output must be copy-paste ready for LinkedIn", "Absolutely zero markdown - if you use **, ##, -, *, [], or any markdown, the output is wrong", "First 250 words are make-or-break - they must hook immediately", "Emojis should feel natural, not forced", "Every paragraph should add value - cut ruthlessly", "The post should sound like a real human sharing their journey, not a corporate announcement" ] } structured_prompt = { "system_prompt": system_instructions, "user_prompt": user_prompt, "project_context": repo_data if repo_data else "User provided only a topic, no repository content." } # Convert to JSON for clarity and structure return json.dumps(structured_prompt, indent=2, ensure_ascii=False) def generate_post(topic, user_prompt): if not topic: return "⚠️ Please provide a topic or GitHub repo link." repo_data = None repo_dir = None if topic.startswith("http") and "github.com" in topic: try: repo_dir = clone_repo(topic) repo_data = read_repo_files(repo_dir) except Exception as e: return f"❌ Failed to fetch repo: {e}" finally: if repo_dir and os.path.exists(repo_dir): shutil.rmtree(repo_dir, ignore_errors=True) structured_prompt = build_structured_prompt(user_prompt, repo_data) payload = { "model": "google/gemma-3-27b-it:free", "messages": [ {"role": "system", "content": "You are an expert social media strategist for projects."}, {"role": "user", "content": structured_prompt} ] } try: res = requests.post( "https://openrouter.ai/api/v1/chat/completions", headers={ "Authorization": f"Bearer {OPENROUTER_API_KEY}", "Content-Type": "application/json" }, json=payload, timeout=180 ) res.raise_for_status() return res.json()["choices"][0]["message"]["content"] except Exception as e: return f"❌ API Error: {e}" with gr.Blocks(theme=gr.themes.Soft()) as demo: gr.Markdown(""" ## 🚀 Advanced LinkedIn Post Generator Create **viral, long-form LinkedIn posts** powered by **OpenRouter AI**. 🧠 This AI automatically: - Extracts meaningful info from GitHub repos - Uses JSON-structured prompting for deep control - Adds hashtags, hooks, and calls to action """) with gr.Row(): topic = gr.Textbox(label="Topic or GitHub Repo Link", placeholder="Enter project topic or GitHub repo URL...") user_prompt = gr.Textbox( label="Custom Prompt", placeholder="Example: Write an inspiring post highlighting the innovation and teamwork behind this project...", lines=2 ) generate_btn = gr.Button("✨ Generate LinkedIn Post") output = gr.Textbox(label="Generated LinkedIn Post", show_copy_button=True, lines=20) generate_btn.click(fn=generate_post, inputs=[topic, user_prompt], outputs=output) if __name__ == "__main__": port = int(os.environ.get("PORT", 7860)) demo.launch(server_name="0.0.0.0", server_port=port, share=False, debug=True,mcp_server=True)