""" DevRel Campaign Generator - Gradio 6 Version A cyberpunk-themed DevRel content generator powered by Gradio 6 and Claude. Transform any GitHub repository into a complete DevRel campaign in minutes. Features: - š Step-by-step tutorials - š Technical blog posts - š¤ Conference talk outlines - š¦ Social media threads (Twitter/X) - š¼ LinkedIn posts - š Hackathon challenges """ import asyncio import os import gradio as gr from typing import Optional from datetime import datetime from services import ( ClaudeService, RepoProfile, ContentVariant, ) from github_utils import ( fetch_github_repo, fetch_recent_commits, get_since_iso, fetch_commits_with_diffs, fetch_readme_changes, analyze_breaking_changes, ) # Custom CSS - Modern, adaptive design with smooth transitions CUSTOM_CSS = """ /* ======================================== CSS Variables for Light/Dark Mode ======================================== */ :root { /* Light mode colors */ --bg-primary: #fafafa; --bg-secondary: #ffffff; --bg-tertiary: #f5f5f5; --bg-card: #ffffff; --text-primary: #18181b; --text-secondary: #52525b; --text-muted: #a1a1aa; --border-color: #e4e4e7; --border-light: #f4f4f5; --accent-purple: #8b5cf6; --accent-purple-light: rgba(139, 92, 246, 0.1); --accent-green: #22c55e; --accent-green-light: rgba(34, 197, 94, 0.1); --accent-orange: #f97316; --accent-orange-light: rgba(249, 115, 22, 0.1); --accent-blue: #3b82f6; --accent-cyan: #06b6d4; --accent-pink: #ec4899; --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05); --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); --shadow-glow: 0 0 40px rgba(139, 92, 246, 0.15); --radius-sm: 8px; --radius-md: 12px; --radius-lg: 16px; --radius-xl: 20px; --transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); } /* Dark mode colors */ .dark, html.dark, [data-theme="dark"] { --bg-primary: #09090b; --bg-secondary: #18181b; --bg-tertiary: #27272a; --bg-card: #1c1c1f; --text-primary: #fafafa; --text-secondary: #a1a1aa; --text-muted: #71717a; --border-color: #27272a; --border-light: #3f3f46; --accent-purple: #a78bfa; --accent-purple-light: rgba(167, 139, 250, 0.15); --accent-green: #4ade80; --accent-green-light: rgba(74, 222, 128, 0.15); --accent-orange: #fb923c; --accent-orange-light: rgba(251, 146, 60, 0.15); --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.3); --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.4); --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.5); --shadow-glow: 0 0 60px rgba(139, 92, 246, 0.2); } /* ======================================== Base Styles ======================================== */ html, body { background: var(--bg-primary) !important; color: var(--text-primary) !important; font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif !important; transition: var(--transition); } .gradio-container { background: var(--bg-primary) !important; max-width: 1600px !important; margin: 0 auto !important; padding: 32px !important; } /* ======================================== Cards & Groups - Glass Morphism Style ======================================== */ .group { background: var(--bg-card) !important; border: 1px solid var(--border-color) !important; border-radius: var(--radius-xl) !important; padding: 28px !important; margin-bottom: 24px !important; box-shadow: var(--shadow-md) !important; backdrop-filter: blur(10px) !important; transition: var(--transition); } .group:hover { box-shadow: var(--shadow-lg), var(--shadow-glow) !important; transform: translateY(-2px); } .dark .group { background: linear-gradient(145deg, var(--bg-card) 0%, rgba(28, 28, 31, 0.8) 100%) !important; border: 1px solid rgba(139, 92, 246, 0.15) !important; } /* ======================================== Form Elements ======================================== */ input, textarea, select { background: var(--bg-tertiary) !important; border: 1px solid var(--border-color) !important; border-radius: var(--radius-md) !important; color: var(--text-primary) !important; padding: 14px 18px !important; font-size: 15px !important; transition: var(--transition); } input:focus, textarea:focus, select:focus { border-color: var(--accent-purple) !important; box-shadow: 0 0 0 3px var(--accent-purple-light) !important; outline: none !important; } label, .label-wrap { color: var(--text-secondary) !important; font-weight: 500 !important; font-size: 14px !important; margin-bottom: 8px !important; } /* ======================================== Buttons - Modern with Smooth Hover ======================================== */ button.primary { background: linear-gradient(135deg, var(--accent-purple), #7c3aed) !important; border: none !important; border-radius: var(--radius-md) !important; color: white !important; font-weight: 600 !important; font-size: 15px !important; padding: 14px 28px !important; box-shadow: var(--shadow-md), 0 0 20px rgba(139, 92, 246, 0.3) !important; transition: var(--transition); } button.primary:hover { transform: translateY(-2px) !important; box-shadow: var(--shadow-lg), 0 0 40px rgba(139, 92, 246, 0.5) !important; } button.secondary { background: var(--bg-tertiary) !important; border: 1px solid var(--border-color) !important; border-radius: var(--radius-md) !important; color: var(--text-secondary) !important; font-weight: 500 !important; padding: 14px 28px !important; transition: var(--transition); } button.secondary:hover { background: var(--bg-secondary) !important; border-color: var(--accent-purple) !important; color: var(--accent-purple) !important; } /* ======================================== Tabs - Premium Look ======================================== */ .tabs { background: var(--bg-card) !important; border: 1px solid var(--border-color) !important; border-radius: var(--radius-xl) !important; overflow: hidden !important; box-shadow: var(--shadow-md) !important; } .dark .tabs { background: linear-gradient(145deg, var(--bg-card) 0%, rgba(28, 28, 31, 0.9) 100%) !important; border: 1px solid rgba(74, 222, 128, 0.15) !important; } .tab-nav { background: var(--bg-tertiary) !important; border-bottom: 1px solid var(--border-color) !important; padding: 12px 16px !important; display: flex !important; gap: 8px !important; } .tab-nav button { background: transparent !important; border: none !important; border-radius: var(--radius-md) !important; color: var(--text-muted) !important; font-weight: 500 !important; font-size: 14px !important; padding: 12px 20px !important; transition: var(--transition); } .tab-nav button:hover { background: var(--accent-purple-light) !important; color: var(--accent-purple) !important; } .tab-nav button.selected { background: linear-gradient(135deg, var(--accent-purple-light), var(--accent-green-light)) !important; color: var(--accent-purple) !important; font-weight: 600 !important; } .tabitem { background: var(--bg-secondary) !important; padding: 28px !important; } /* ======================================== Accordion - Expandable Sections ======================================== */ .accordion { background: var(--bg-card) !important; border: 1px solid var(--border-color) !important; border-radius: var(--radius-lg) !important; margin-top: 20px !important; overflow: hidden !important; transition: var(--transition); } .dark .accordion { background: linear-gradient(145deg, rgba(28, 28, 31, 0.8), rgba(39, 39, 42, 0.5)) !important; border: 1px solid rgba(249, 115, 22, 0.15) !important; } .accordion:hover { border-color: var(--accent-orange) !important; } /* ======================================== Markdown Content - Clean Typography ======================================== */ .markdown, .prose { color: var(--text-primary) !important; font-size: 15px !important; line-height: 1.8 !important; padding: 20px !important; } .markdown h1, .markdown h2, .markdown h3 { color: var(--text-primary) !important; font-weight: 700 !important; margin-top: 28px !important; margin-bottom: 16px !important; } .markdown p { margin-bottom: 16px !important; color: var(--text-secondary) !important; } .markdown code { background: var(--accent-purple-light) !important; color: var(--accent-purple) !important; padding: 3px 8px !important; border-radius: 6px !important; font-family: 'JetBrains Mono', 'Fira Code', monospace !important; font-size: 14px !important; } .markdown pre { background: var(--bg-tertiary) !important; border: 1px solid var(--border-color) !important; border-radius: var(--radius-md) !important; padding: 20px !important; overflow-x: auto !important; } .markdown ul, .markdown ol { padding-left: 28px !important; margin-bottom: 16px !important; } .markdown li { margin-bottom: 8px !important; color: var(--text-secondary) !important; } /* ======================================== Scrollbar - Subtle & Modern ======================================== */ ::-webkit-scrollbar { width: 10px; height: 10px; } ::-webkit-scrollbar-track { background: var(--bg-tertiary); border-radius: 5px; } ::-webkit-scrollbar-thumb { background: var(--border-light); border-radius: 5px; border: 2px solid var(--bg-tertiary); } ::-webkit-scrollbar-thumb:hover { background: var(--text-muted); } /* ======================================== Animations ======================================== */ @keyframes pulse { 0%, 100% { opacity: 1; transform: scale(1); } 50% { opacity: 0.7; transform: scale(0.95); } } @keyframes float { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-5px); } } @keyframes shimmer { 0% { background-position: -200% 0; } 100% { background-position: 200% 0; } } @keyframes glow-pulse { 0%, 100% { box-shadow: 0 0 20px rgba(139, 92, 246, 0.3); } 50% { box-shadow: 0 0 40px rgba(139, 92, 246, 0.5); } } /* ======================================== Utility Classes ======================================== */ .glow-purple { box-shadow: 0 0 30px rgba(139, 92, 246, 0.4); } .glow-green { box-shadow: 0 0 30px rgba(34, 197, 94, 0.4); } /* ======================================== Layout Adjustments ======================================== */ .row { gap: 28px !important; } .col { padding: 0 !important; } /* Container max width */ .gradio-container > .main { padding: 0 !important; } /* Responsive adjustments */ @media (max-width: 768px) { .gradio-container { padding: 16px !important; } .group { padding: 20px !important; } .tab-nav { flex-wrap: wrap !important; } .tab-nav button { font-size: 12px !important; padding: 10px 14px !important; } } /* ======================================== Special Elements ======================================== */ /* Hero section styling */ .hero-card { background: linear-gradient(145deg, var(--bg-card), var(--bg-secondary)) !important; border: 1px solid var(--border-color) !important; } .dark .hero-card { background: linear-gradient(145deg, rgba(28, 28, 31, 0.9), rgba(18, 18, 20, 0.9)) !important; border: 1px solid rgba(139, 92, 246, 0.2) !important; } /* Terminal styling */ .terminal-header { background: linear-gradient(90deg, var(--bg-tertiary), var(--bg-secondary)) !important; } .dark .terminal-header { background: linear-gradient(90deg, rgba(28, 28, 31, 0.9), rgba(24, 24, 27, 0.9)) !important; } /* Status indicator */ .status-online { width: 10px; height: 10px; border-radius: 50%; background: var(--accent-green); animation: pulse 2s infinite; box-shadow: 0 0 10px var(--accent-green); } /* Badge styling */ .badge { display: inline-flex; align-items: center; padding: 6px 14px; border-radius: 20px; font-size: 12px; font-weight: 600; transition: var(--transition); } .badge-purple { background: var(--accent-purple-light); color: var(--accent-purple); border: 1px solid rgba(139, 92, 246, 0.3); } .badge-green { background: var(--accent-green-light); color: var(--accent-green); border: 1px solid rgba(34, 197, 94, 0.3); } .badge-orange { background: var(--accent-orange-light); color: var(--accent-orange); border: 1px solid rgba(249, 115, 22, 0.3); } /* Feature card */ .feature-card { background: var(--bg-secondary) !important; border: 1px solid var(--border-color) !important; border-radius: var(--radius-lg) !important; padding: 16px !important; transition: var(--transition); } .feature-card:hover { transform: translateY(-3px); box-shadow: var(--shadow-md); border-color: var(--accent-purple) !important; } /* Icon box */ .icon-box { width: 44px; height: 44px; border-radius: var(--radius-md); display: flex; align-items: center; justify-content: center; transition: var(--transition); } .icon-box:hover { transform: scale(1.1); } /* ======================================== Custom Card Classes - Matching About Section Style ======================================== */ .command-center-card, .pipeline-status-card, .campaign-output-card { background: var(--bg-card, #ffffff) !important; border: 1px solid var(--border-color, #e4e4e7) !important; border-radius: 20px !important; padding: 32px !important; margin-bottom: 24px !important; box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06) !important; transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important; } .command-center-card:hover, .pipeline-status-card:hover, .campaign-output-card:hover { box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05), 0 0 40px rgba(139, 92, 246, 0.1) !important; } /* Dark mode for all business cards */ .dark .command-center-card, .dark .pipeline-status-card, .dark .campaign-output-card { background: var(--bg-card, #1c1c1f) !important; border: 1px solid rgba(139, 92, 246, 0.15) !important; box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.4), 0 2px 4px -1px rgba(0, 0, 0, 0.3) !important; } .dark .command-center-card:hover, .dark .pipeline-status-card:hover, .dark .campaign-output-card:hover { box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.5), 0 4px 6px -2px rgba(0, 0, 0, 0.4), 0 0 60px rgba(139, 92, 246, 0.2) !important; border-color: rgba(139, 92, 246, 0.25) !important; } /* Command Center - Purple accent border on hover */ .command-center-card:hover { border-color: rgba(139, 92, 246, 0.3) !important; } /* Pipeline Status - Green accent border on hover */ .pipeline-status-card:hover { border-color: rgba(34, 197, 94, 0.3) !important; } /* Campaign Output - Cyan accent border on hover */ .campaign-output-card:hover { border-color: rgba(6, 182, 212, 0.3) !important; } /* Terminal content styling */ .terminal-content { max-height: 200px !important; overflow-y: auto !important; padding: 16px !important; background: var(--bg-tertiary, #f5f5f5) !important; border-radius: 12px !important; border: 1px solid var(--border-color, #e4e4e7) !important; font-family: 'JetBrains Mono', 'Fira Code', monospace !important; font-size: 13px !important; line-height: 1.6 !important; } .dark .terminal-content { background: rgba(39, 39, 42, 0.5) !important; border-color: rgba(63, 63, 70, 0.5) !important; } /* Tabs inside Campaign Output - matching card style */ .campaign-output-card .tabs { background: transparent !important; border: none !important; box-shadow: none !important; margin-top: 0 !important; } .campaign-output-card .tab-nav { background: var(--bg-tertiary, #f5f5f5) !important; border: 1px solid var(--border-color, #e4e4e7) !important; border-radius: 14px !important; padding: 8px !important; gap: 6px !important; margin-bottom: 16px !important; } .dark .campaign-output-card .tab-nav { background: rgba(39, 39, 42, 0.4) !important; border-color: rgba(63, 63, 70, 0.4) !important; } .campaign-output-card .tab-nav button { background: transparent !important; border: none !important; border-radius: 10px !important; color: var(--text-muted, #71717a) !important; font-weight: 500 !important; font-size: 13px !important; padding: 10px 16px !important; transition: all 0.2s ease !important; } .campaign-output-card .tab-nav button:hover { background: rgba(139, 92, 246, 0.1) !important; color: var(--accent-purple, #8b5cf6) !important; } .campaign-output-card .tab-nav button.selected { background: linear-gradient(135deg, rgba(139, 92, 246, 0.15), rgba(34, 197, 94, 0.1)) !important; color: var(--accent-purple, #8b5cf6) !important; font-weight: 600 !important; box-shadow: 0 2px 4px rgba(139, 92, 246, 0.15) !important; } .campaign-output-card .tabitem { background: var(--bg-tertiary, #f5f5f5) !important; border: 1px solid var(--border-color, #e4e4e7) !important; border-radius: 14px !important; padding: 24px !important; min-height: 300px !important; } .dark .campaign-output-card .tabitem { background: rgba(39, 39, 42, 0.3) !important; border-color: rgba(63, 63, 70, 0.4) !important; } /* Markdown content inside tabs */ .campaign-output-card .markdown { color: var(--text-primary, #18181b) !important; font-size: 14px !important; line-height: 1.7 !important; } .dark .campaign-output-card .markdown { color: var(--text-primary, #fafafa) !important; } /* Form elements inside cards */ .command-center-card input, .command-center-card textarea, .command-center-card select { background: var(--bg-tertiary, #f5f5f5) !important; border: 1px solid var(--border-color, #e4e4e7) !important; border-radius: 12px !important; } .dark .command-center-card input, .dark .command-center-card textarea, .dark .command-center-card select { background: rgba(39, 39, 42, 0.5) !important; border-color: rgba(63, 63, 70, 0.5) !important; } /* Accordion styling inside cards */ .command-center-card .accordion { background: var(--bg-tertiary, #f5f5f5) !important; border: 1px solid var(--border-color, #e4e4e7) !important; border-radius: 14px !important; margin-top: 16px !important; } .dark .command-center-card .accordion { background: rgba(39, 39, 42, 0.4) !important; border-color: rgba(249, 115, 22, 0.15) !important; } .command-center-card .accordion:hover { border-color: rgba(249, 115, 22, 0.3) !important; } /* Buttons inside cards */ .command-center-card button.primary { background: linear-gradient(135deg, #8b5cf6, #7c3aed) !important; border: none !important; border-radius: 12px !important; color: white !important; font-weight: 600 !important; padding: 14px 24px !important; box-shadow: 0 4px 6px rgba(139, 92, 246, 0.25), 0 0 20px rgba(139, 92, 246, 0.2) !important; transition: all 0.3s ease !important; } .command-center-card button.primary:hover { transform: translateY(-2px) !important; box-shadow: 0 6px 12px rgba(139, 92, 246, 0.35), 0 0 30px rgba(139, 92, 246, 0.3) !important; } .command-center-card button.secondary { background: var(--bg-tertiary, #f5f5f5) !important; border: 1px solid var(--border-color, #e4e4e7) !important; border-radius: 12px !important; color: var(--text-secondary, #52525b) !important; font-weight: 500 !important; padding: 14px 24px !important; transition: all 0.3s ease !important; } .dark .command-center-card button.secondary { background: rgba(39, 39, 42, 0.5) !important; border-color: rgba(63, 63, 70, 0.5) !important; color: var(--text-secondary, #a1a1aa) !important; } .command-center-card button.secondary:hover { background: var(--bg-secondary, #ffffff) !important; border-color: var(--accent-purple, #8b5cf6) !important; color: var(--accent-purple, #8b5cf6) !important; } """ # Global state current_job = { "id": None, "status": "idle", "progress": 0, "logs": [], "profile": None, "variants": {}, "selected_variants": {}, } def add_log(agent: str, message: str, color: str = "text-foreground") -> str: """Add a log entry and return formatted log.""" timestamp = datetime.now().strftime("%H:%M:%S") entry = f"[{timestamp}] [{agent}] {message}" current_job["logs"].append({"agent": agent, "message": message, "color": color}) return format_logs() def format_logs() -> str: """Format all logs as HTML.""" if not current_job["logs"]: return '
AI-Powered Content for Developer Relations
Transform repositories into complete DevRel campaigns
Analyze any GitHub repository and automatically generate professional DevRel content including social threads, technical blogs, tutorials, conference talks, and hackathon challenges. Our intelligent pipeline understands code changes and detects breaking changes to create relevant, actionable content.
Tip: Configure your own API keys in āļø Settings if the default quota is exhausted.
Configure and launch your campaign
Real-time generation progress
Generated content for all platforms