|
|
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." |
|
|
} |
|
|
|
|
|
|
|
|
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) |
|
|
|