|
|
""" |
|
|
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 = """ |
|
|
/* ======================================== |
|
|
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; |
|
|
} |
|
|
""" |
|
|
|
|
|
|
|
|
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 '<div style="color: var(--text-muted, #71717a); text-align: center; padding: 32px 20px;"><div style="font-size: 16px; margin-bottom: 8px;">Waiting for input...</div><span style="font-size: 13px;">Enter a GitHub repo URL to begin</span></div>' |
|
|
|
|
|
color_map = { |
|
|
"text-primary": "#8b5cf6", |
|
|
"text-accent": "#22c55e", |
|
|
"text-chart-3": "#06b6d4", |
|
|
"text-chart-4": "#ec4899", |
|
|
"text-chart-5": "#f59e0b", |
|
|
"text-destructive": "#ef4444", |
|
|
"text-chart-1": "#22c55e", |
|
|
} |
|
|
|
|
|
lines = [] |
|
|
for log in current_job["logs"]: |
|
|
color = color_map.get(log["color"], "var(--text-primary, #18181b)") |
|
|
lines.append( |
|
|
f'<div style="margin: 6px 0; padding: 4px 0;">' |
|
|
f'<span style="color: var(--text-muted, #71717a);">βΊ</span> ' |
|
|
f'<span style="color: {color}; font-weight: 600;">[{log["agent"]}]</span> ' |
|
|
f'<span style="color: var(--text-secondary, #52525b);">{log["message"]}</span>' |
|
|
f"</div>" |
|
|
) |
|
|
return "".join(lines) |
|
|
|
|
|
|
|
|
def get_progress_html(progress: int, status: str) -> str: |
|
|
"""Generate progress bar HTML.""" |
|
|
status_colors = { |
|
|
"idle": ("var(--text-muted, #71717a)", "var(--border-color, #e4e4e7)"), |
|
|
"analyzing": ("#8b5cf6", "rgba(139, 92, 246, 0.2)"), |
|
|
"generating": ("#22c55e", "rgba(34, 197, 94, 0.2)"), |
|
|
"completed": ("#22c55e", "rgba(34, 197, 94, 0.2)"), |
|
|
"failed": ("#ef4444", "rgba(239, 68, 68, 0.2)"), |
|
|
} |
|
|
text_color, bg_color = status_colors.get(status, status_colors["idle"]) |
|
|
|
|
|
return f""" |
|
|
<div style="margin-bottom: 16px;"> |
|
|
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;"> |
|
|
<span style="color: #8b5cf6; font-weight: 600; font-size: 14px;">Pipeline Status</span> |
|
|
<span style="color: {text_color}; font-size: 12px; padding: 4px 12px; background: {bg_color}; border: 1px solid {text_color}33; border-radius: 16px; font-weight: 500; text-transform: capitalize;">{status}</span> |
|
|
</div> |
|
|
<div style="width: 100%; height: 10px; background: var(--bg-tertiary, #f5f5f5); border-radius: 5px; overflow: hidden; border: 1px solid var(--border-color, #e4e4e7);"> |
|
|
<div style="width: {progress}%; height: 100%; background: linear-gradient(90deg, #8b5cf6, #22c55e); transition: width 0.4s cubic-bezier(0.4, 0, 0.2, 1); border-radius: 5px;"></div> |
|
|
</div> |
|
|
</div> |
|
|
""" |
|
|
|
|
|
|
|
|
def get_profile_html(profile: Optional[RepoProfile]) -> str: |
|
|
"""Generate profile info HTML.""" |
|
|
if not profile: |
|
|
return "" |
|
|
|
|
|
commits_html = "" |
|
|
if profile.commits and len(profile.commits) > 0: |
|
|
commit_items = "".join( |
|
|
[ |
|
|
f'<li style="font-size: 12px; display: flex; justify-content: space-between; padding: 6px 0; border-bottom: 1px solid var(--border-color, #e4e4e7);">' |
|
|
f'<span style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 250px; color: var(--text-secondary, #52525b);" title="{c.get("message", "")}">{c.get("message", "")[:50]}</span>' |
|
|
f'<a href="{c.get("url", "#")}" target="_blank" style="color: #22c55e; text-decoration: none; font-weight: 500;">{c.get("sha", "")[:7]}</a>' |
|
|
f"</li>" |
|
|
for c in profile.commits[:5] |
|
|
] |
|
|
) |
|
|
commits_html = f""" |
|
|
<div style="margin-top: 16px; padding: 14px; background: var(--bg-tertiary, #f5f5f5); border: 1px solid var(--border-color, #e4e4e7); border-radius: 12px; max-height: 140px; overflow-y: auto;"> |
|
|
<div style="color: var(--text-muted, #71717a); font-size: 11px; font-weight: 600; margin-bottom: 8px; text-transform: uppercase; letter-spacing: 0.5px;">Recent Commits</div> |
|
|
<ul style="margin: 0; padding: 0; list-style: none;">{commit_items}</ul> |
|
|
</div> |
|
|
""" |
|
|
|
|
|
|
|
|
breaking_html = "" |
|
|
if profile.breaking_changes and len(profile.breaking_changes) > 0: |
|
|
bc_items = "".join( |
|
|
[ |
|
|
f'<li style="font-size: 12px; color: #f59e0b; padding: 4px 0;">' |
|
|
f'<span title="{bc.get("message", "")}">[{bc.get("sha", "")}] {bc.get("message", "")[:40]}...</span>' |
|
|
f"</li>" |
|
|
for bc in profile.breaking_changes[:3] |
|
|
] |
|
|
) |
|
|
breaking_html = f""" |
|
|
<div style="margin-top: 16px; padding: 14px; border: 1px solid rgba(245, 158, 11, 0.3); border-radius: 12px; background: rgba(245, 158, 11, 0.08);"> |
|
|
<div style="color: #f59e0b; font-size: 12px; font-weight: 600; margin-bottom: 8px;">β οΈ Potential Breaking Changes</div> |
|
|
<ul style="margin: 0; padding-left: 18px;">{bc_items}</ul> |
|
|
</div> |
|
|
""" |
|
|
|
|
|
|
|
|
readme_html = "" |
|
|
if profile.has_readme_changes: |
|
|
readme_html = f""" |
|
|
<div style="margin-top: 16px; padding: 14px; border: 1px solid rgba(34, 197, 94, 0.3); border-radius: 12px; background: rgba(34, 197, 94, 0.08);"> |
|
|
<div style="color: #22c55e; font-size: 12px; font-weight: 600;">π README Documentation Updated</div> |
|
|
<div style="color: #4ade80; font-size: 12px; margin-top: 6px;">High priority - documentation changes detected</div> |
|
|
</div> |
|
|
""" |
|
|
|
|
|
features_list = ", ".join(profile.main_features[:3]) if profile.main_features else "N/A" |
|
|
|
|
|
return f""" |
|
|
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 16px; font-size: 13px; margin-top: 16px; padding: 16px; background: var(--bg-tertiary, #f5f5f5); border-radius: 12px; border: 1px solid var(--border-color, #e4e4e7);"> |
|
|
<div> |
|
|
<div style="color: var(--text-muted, #71717a); font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 4px;">Repository</div> |
|
|
<div style="color: #8b5cf6; font-weight: 600; overflow: hidden; text-overflow: ellipsis;">{profile.name}</div> |
|
|
</div> |
|
|
<div> |
|
|
<div style="color: var(--text-muted, #71717a); font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 4px;">Language</div> |
|
|
<div style="color: #06b6d4; font-weight: 600;">{profile.primary_language}</div> |
|
|
</div> |
|
|
<div style="grid-column: span 2;"> |
|
|
<div style="color: var(--text-muted, #71717a); font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 4px;">Key Features</div> |
|
|
<div style="color: var(--text-secondary, #52525b); font-size: 12px; line-height: 1.5;">{features_list}</div> |
|
|
</div> |
|
|
</div> |
|
|
{breaking_html} |
|
|
{readme_html} |
|
|
{commits_html} |
|
|
""" |
|
|
|
|
|
|
|
|
async def generate_campaign(repo_url: str, time_range: str, anthropic_key: str, github_token: str, progress=gr.Progress()): |
|
|
"""Main generation function with streaming progress updates.""" |
|
|
global current_job |
|
|
|
|
|
if not repo_url: |
|
|
yield ( |
|
|
format_logs(), |
|
|
get_progress_html(0, "idle"), |
|
|
"", |
|
|
"", |
|
|
"", |
|
|
"", |
|
|
"", |
|
|
"", |
|
|
"", |
|
|
) |
|
|
return |
|
|
|
|
|
|
|
|
|
|
|
custom_anthropic_key = anthropic_key.strip() if anthropic_key else None |
|
|
custom_github_token = github_token.strip() if github_token else None |
|
|
|
|
|
|
|
|
current_job = { |
|
|
"id": f"job-{datetime.now().timestamp()}", |
|
|
"status": "pending", |
|
|
"progress": 0, |
|
|
"logs": [], |
|
|
"profile": None, |
|
|
"variants": {}, |
|
|
"selected_variants": {}, |
|
|
} |
|
|
|
|
|
formats = ["twitter", "blog", "tutorial", "talk", "linkedin", "hackathon"] |
|
|
|
|
|
try: |
|
|
|
|
|
current_job["status"] = "analyzing" |
|
|
current_job["progress"] = 10 |
|
|
add_log("System", "Initializing DevRel Campaign Generator...", "text-primary") |
|
|
progress(0.1, desc="Initializing...") |
|
|
yield ( |
|
|
format_logs(), |
|
|
get_progress_html(10, "analyzing"), |
|
|
"", |
|
|
"", |
|
|
"", |
|
|
"", |
|
|
"", |
|
|
"", |
|
|
"", |
|
|
) |
|
|
|
|
|
add_log("Understanding Agent", "Fetching repository metadata...", "text-chart-3") |
|
|
yield ( |
|
|
format_logs(), |
|
|
get_progress_html(15, "analyzing"), |
|
|
"", |
|
|
"", |
|
|
"", |
|
|
"", |
|
|
"", |
|
|
"", |
|
|
"", |
|
|
) |
|
|
|
|
|
repo_data = await fetch_github_repo(repo_url, custom_github_token) |
|
|
add_log( |
|
|
"Understanding Agent", |
|
|
f"Found {len(repo_data.get('languages', {}))} languages", |
|
|
"text-chart-3", |
|
|
) |
|
|
|
|
|
|
|
|
add_log( |
|
|
"Understanding Agent", |
|
|
f"Fetching recent commits for {time_range} window...", |
|
|
"text-chart-3", |
|
|
) |
|
|
since_iso = get_since_iso(time_range) |
|
|
recent_commits = await fetch_recent_commits(repo_url, 50, since_iso, github_token=custom_github_token) |
|
|
|
|
|
if recent_commits: |
|
|
add_log( |
|
|
"Understanding Agent", |
|
|
f"Loaded {len(recent_commits)} recent commits since {since_iso[:10]}", |
|
|
"text-chart-3", |
|
|
) |
|
|
else: |
|
|
add_log( |
|
|
"Understanding Agent", |
|
|
"No recent commits available or fetch failed", |
|
|
"text-chart-5", |
|
|
) |
|
|
|
|
|
|
|
|
add_log( |
|
|
"Understanding Agent", |
|
|
"Fetching code diffs for recent commits...", |
|
|
"text-chart-3", |
|
|
) |
|
|
commits_with_diffs = await fetch_commits_with_diffs( |
|
|
repo_url, recent_commits, max_commits=5, github_token=custom_github_token |
|
|
) |
|
|
if commits_with_diffs: |
|
|
add_log( |
|
|
"Understanding Agent", |
|
|
f"Analyzed diffs from {len(commits_with_diffs)} commits", |
|
|
"text-chart-3", |
|
|
) |
|
|
|
|
|
|
|
|
add_log( |
|
|
"Understanding Agent", |
|
|
"Scanning for potential breaking changes...", |
|
|
"text-chart-3", |
|
|
) |
|
|
breaking_changes = await analyze_breaking_changes(commits_with_diffs) |
|
|
if breaking_changes: |
|
|
add_log( |
|
|
"Understanding Agent", |
|
|
f"β οΈ Found {len(breaking_changes)} commits with potential breaking changes", |
|
|
"text-chart-5", |
|
|
) |
|
|
else: |
|
|
add_log( |
|
|
"Understanding Agent", |
|
|
"No breaking changes detected", |
|
|
"text-chart-3", |
|
|
) |
|
|
|
|
|
|
|
|
add_log( |
|
|
"Understanding Agent", |
|
|
"Checking for README documentation changes...", |
|
|
"text-chart-3", |
|
|
) |
|
|
readme_changes = await fetch_readme_changes(repo_url, since_iso, custom_github_token) |
|
|
if readme_changes.get("has_readme_changes"): |
|
|
add_log( |
|
|
"Understanding Agent", |
|
|
f"π README updated {len(readme_changes.get('readme_commits', []))} times - HIGH PRIORITY", |
|
|
"text-chart-1", |
|
|
) |
|
|
else: |
|
|
add_log( |
|
|
"Understanding Agent", |
|
|
"No README changes in the analysis period", |
|
|
"text-chart-3", |
|
|
) |
|
|
|
|
|
yield ( |
|
|
format_logs(), |
|
|
get_progress_html(25, "analyzing"), |
|
|
"", |
|
|
"", |
|
|
"", |
|
|
"", |
|
|
"", |
|
|
"", |
|
|
"", |
|
|
) |
|
|
|
|
|
|
|
|
current_job["progress"] = 25 |
|
|
add_log("Claude", "Analyzing repository structure...", "text-primary") |
|
|
|
|
|
try: |
|
|
claude = ClaudeService(api_key=custom_anthropic_key) |
|
|
add_log("Claude", "Extracting repository profile...", "text-primary") |
|
|
extracted_profile = claude.extract_repo_profile(repo_data) |
|
|
except Exception as e: |
|
|
error_msg = str(e) |
|
|
if "ANTHROPIC_API_KEY" in error_msg or "API key" in error_msg: |
|
|
add_log( |
|
|
"System", "β οΈ Claude API key not configured or quota exhausted", "text-chart-5" |
|
|
) |
|
|
add_log( |
|
|
"System", "π‘ Please add your own API key in Settings below", "text-chart-5" |
|
|
) |
|
|
add_log( |
|
|
"System", "Using fallback content generation...", "text-chart-5" |
|
|
) |
|
|
elif "credit" in error_msg.lower() or "quota" in error_msg.lower() or "rate" in error_msg.lower(): |
|
|
add_log( |
|
|
"System", "β οΈ API quota exhausted or rate limited", "text-chart-5" |
|
|
) |
|
|
add_log( |
|
|
"System", "π‘ Please add your own API key in Settings below", "text-chart-5" |
|
|
) |
|
|
add_log( |
|
|
"System", "Using fallback content generation...", "text-chart-5" |
|
|
) |
|
|
else: |
|
|
add_log("System", f"β οΈ Claude error: {error_msg}", "text-chart-5") |
|
|
add_log( |
|
|
"System", "Using fallback content generation...", "text-chart-5" |
|
|
) |
|
|
|
|
|
extracted_profile = { |
|
|
"mainFeatures": ["Feature analysis unavailable - add API key"], |
|
|
"technicalStack": list(repo_data.get("languages", {}).keys()), |
|
|
"quickstartPath": None, |
|
|
} |
|
|
claude = None |
|
|
|
|
|
profile = RepoProfile( |
|
|
url=repo_data.get("url", repo_url), |
|
|
owner=repo_data.get("owner", ""), |
|
|
name=repo_data.get("name", ""), |
|
|
description=repo_data.get("description", ""), |
|
|
languages=repo_data.get("languages", {}), |
|
|
primary_language=repo_data.get("primaryLanguage", "Unknown"), |
|
|
stars=repo_data.get("stars", 0), |
|
|
forks=repo_data.get("forks", 0), |
|
|
structure=repo_data.get("structure", {}), |
|
|
readme=repo_data.get("readme", "")[:5000], |
|
|
main_features=extracted_profile.get("mainFeatures", []), |
|
|
technical_stack=extracted_profile.get("technicalStack", []), |
|
|
quickstart_path=extracted_profile.get("quickstartPath"), |
|
|
commits=recent_commits, |
|
|
commits_with_diffs=commits_with_diffs, |
|
|
breaking_changes=breaking_changes, |
|
|
initial_readme=readme_changes.get("initial_readme", ""), |
|
|
readme_diff=readme_changes.get("readme_diff", ""), |
|
|
has_readme_changes=readme_changes.get("has_readme_changes", False), |
|
|
) |
|
|
|
|
|
current_job["profile"] = profile |
|
|
current_job["progress"] = 35 |
|
|
add_log( |
|
|
"Claude", |
|
|
f"Identified {len(profile.main_features)} key features", |
|
|
"text-primary", |
|
|
) |
|
|
progress(0.35, desc="Profile extracted...") |
|
|
|
|
|
profile_html = get_profile_html(profile) |
|
|
yield ( |
|
|
format_logs(), |
|
|
get_progress_html(35, "analyzing") + profile_html, |
|
|
"", |
|
|
"", |
|
|
"", |
|
|
"", |
|
|
"", |
|
|
"", |
|
|
"", |
|
|
) |
|
|
|
|
|
|
|
|
current_job["status"] = "generating" |
|
|
current_job["progress"] = 40 |
|
|
|
|
|
has_api_key = bool(custom_anthropic_key or os.environ.get("ANTHROPIC_API_KEY")) |
|
|
if not has_api_key or claude is None: |
|
|
add_log( |
|
|
"Claude", |
|
|
"Generating fallback content (no API key)...", |
|
|
"text-chart-5", |
|
|
) |
|
|
add_log( |
|
|
"System", |
|
|
"π‘ Add your API key in Settings for AI-powered generation", |
|
|
"text-chart-5", |
|
|
) |
|
|
|
|
|
try: |
|
|
claude = ClaudeService(api_key=custom_anthropic_key) |
|
|
except Exception: |
|
|
pass |
|
|
else: |
|
|
add_log( |
|
|
"Claude", |
|
|
"Generating AI-powered content variants...", |
|
|
"text-primary", |
|
|
) |
|
|
|
|
|
all_variants = [] |
|
|
progress_per_format = 50 / len(formats) |
|
|
|
|
|
for i, fmt in enumerate(formats): |
|
|
add_log("Claude", f"Creating {fmt} content...", "text-primary") |
|
|
|
|
|
if claude: |
|
|
variants = claude.generate_content_variants(profile, fmt, 1) |
|
|
else: |
|
|
|
|
|
variants = [ |
|
|
ContentVariant( |
|
|
id=f"{fmt}-variant-1", |
|
|
format=fmt, |
|
|
content=f"# {fmt.title()} Content for {profile.name}\n\nβ οΈ **AI content generation unavailable**\n\nTo generate AI-powered content, please add your Anthropic API key in the **βοΈ Settings** section below the Command Center.\n\n**Get your API key:**\n1. Go to [console.anthropic.com](https://console.anthropic.com)\n2. Create an account or sign in\n3. Generate an API key\n4. Paste it in the Settings section", |
|
|
) |
|
|
] |
|
|
|
|
|
all_variants.extend(variants) |
|
|
current_job["progress"] = 40 + int((i + 1) * progress_per_format) |
|
|
progress(current_job["progress"] / 100, desc=f"Generated {fmt}...") |
|
|
|
|
|
|
|
|
for v in variants: |
|
|
current_job["variants"][v.format] = v |
|
|
|
|
|
yield ( |
|
|
format_logs(), |
|
|
get_progress_html(current_job["progress"], "generating") + profile_html, |
|
|
current_job["variants"].get("twitter", ContentVariant("", "twitter", "")).content, |
|
|
current_job["variants"].get("blog", ContentVariant("", "blog", "")).content, |
|
|
current_job["variants"].get("tutorial", ContentVariant("", "tutorial", "")).content, |
|
|
current_job["variants"].get("talk", ContentVariant("", "talk", "")).content, |
|
|
current_job["variants"].get("linkedin", ContentVariant("", "linkedin", "")).content, |
|
|
current_job["variants"].get("hackathon", ContentVariant("", "hackathon", "")).content, |
|
|
"", |
|
|
) |
|
|
|
|
|
|
|
|
current_job["status"] = "completed" |
|
|
current_job["progress"] = 100 |
|
|
add_log("System", "Campaign generation complete! β¨", "text-primary") |
|
|
progress(1.0, desc="Complete!") |
|
|
|
|
|
yield ( |
|
|
format_logs(), |
|
|
get_progress_html(100, "completed") + profile_html, |
|
|
current_job["variants"].get("twitter", ContentVariant("", "twitter", "")).content, |
|
|
current_job["variants"].get("blog", ContentVariant("", "blog", "")).content, |
|
|
current_job["variants"].get("tutorial", ContentVariant("", "tutorial", "")).content, |
|
|
current_job["variants"].get("talk", ContentVariant("", "talk", "")).content, |
|
|
current_job["variants"].get("linkedin", ContentVariant("", "linkedin", "")).content, |
|
|
current_job["variants"].get("hackathon", ContentVariant("", "hackathon", "")).content, |
|
|
"", |
|
|
) |
|
|
|
|
|
except Exception as e: |
|
|
current_job["status"] = "failed" |
|
|
add_log("System", f"β Error: {str(e)}", "text-destructive") |
|
|
yield ( |
|
|
format_logs(), |
|
|
get_progress_html(current_job["progress"], "failed"), |
|
|
"", |
|
|
"", |
|
|
"", |
|
|
"", |
|
|
"", |
|
|
"", |
|
|
"", |
|
|
) |
|
|
|
|
|
|
|
|
def reset_state(): |
|
|
"""Reset all state.""" |
|
|
global current_job |
|
|
current_job = { |
|
|
"id": None, |
|
|
"status": "idle", |
|
|
"progress": 0, |
|
|
"logs": [], |
|
|
"profile": None, |
|
|
"variants": {}, |
|
|
"selected_variants": {}, |
|
|
} |
|
|
return ( |
|
|
"", |
|
|
"3months", |
|
|
format_logs(), |
|
|
get_progress_html(0, "idle"), |
|
|
"", |
|
|
"", |
|
|
"", |
|
|
"", |
|
|
"", |
|
|
"", |
|
|
"", |
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
def create_app(): |
|
|
"""Create and configure the Gradio app.""" |
|
|
|
|
|
|
|
|
custom_theme = gr.themes.Soft( |
|
|
primary_hue="purple", |
|
|
secondary_hue="green", |
|
|
neutral_hue="zinc", |
|
|
font=gr.themes.GoogleFont("Inter"), |
|
|
font_mono=gr.themes.GoogleFont("JetBrains Mono"), |
|
|
).set( |
|
|
|
|
|
body_background_fill="#fafafa", |
|
|
block_background_fill="#ffffff", |
|
|
panel_background_fill="#ffffff", |
|
|
|
|
|
body_background_fill_dark="#09090b", |
|
|
block_background_fill_dark="#1c1c1f", |
|
|
panel_background_fill_dark="#1c1c1f", |
|
|
|
|
|
block_border_color="#e4e4e7", |
|
|
block_border_color_dark="rgba(139, 92, 246, 0.2)", |
|
|
border_color_primary="#8b5cf6", |
|
|
border_color_primary_dark="#a78bfa", |
|
|
|
|
|
input_background_fill="#f5f5f5", |
|
|
input_background_fill_dark="#27272a", |
|
|
input_border_color="#e4e4e7", |
|
|
input_border_color_dark="#3f3f46", |
|
|
|
|
|
body_text_color="#18181b", |
|
|
body_text_color_dark="#fafafa", |
|
|
block_title_text_color="#18181b", |
|
|
block_title_text_color_dark="#fafafa", |
|
|
block_label_text_color="#52525b", |
|
|
block_label_text_color_dark="#a1a1aa", |
|
|
|
|
|
button_primary_background_fill="linear-gradient(135deg, #8b5cf6, #7c3aed)", |
|
|
button_primary_background_fill_dark="linear-gradient(135deg, #a78bfa, #8b5cf6)", |
|
|
button_primary_background_fill_hover="linear-gradient(135deg, #7c3aed, #6d28d9)", |
|
|
button_primary_background_fill_hover_dark="linear-gradient(135deg, #8b5cf6, #7c3aed)", |
|
|
button_primary_text_color="white", |
|
|
button_primary_text_color_dark="white", |
|
|
button_secondary_background_fill="#f5f5f5", |
|
|
button_secondary_background_fill_dark="#27272a", |
|
|
button_secondary_text_color="#52525b", |
|
|
button_secondary_text_color_dark="#a1a1aa", |
|
|
|
|
|
block_shadow="0 4px 6px -1px rgba(0, 0, 0, 0.1)", |
|
|
block_shadow_dark="0 4px 6px -1px rgba(0, 0, 0, 0.4)", |
|
|
|
|
|
block_radius="16px", |
|
|
button_large_radius="12px", |
|
|
button_small_radius="8px", |
|
|
) |
|
|
|
|
|
|
|
|
DARK_MODE_JS = """ |
|
|
function() { |
|
|
// Respect system preference |
|
|
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) { |
|
|
document.documentElement.classList.add('dark'); |
|
|
} |
|
|
} |
|
|
""" |
|
|
|
|
|
with gr.Blocks(title="DevRel Campaign Generator") as app: |
|
|
|
|
|
app.theme = custom_theme |
|
|
app.css = CUSTOM_CSS |
|
|
app.js = DARK_MODE_JS |
|
|
|
|
|
with gr.Row(): |
|
|
with gr.Column(): |
|
|
gr.HTML( |
|
|
""" |
|
|
<div style="display: flex; align-items: center; justify-content: space-between; padding: 20px 0; margin-bottom: 8px;"> |
|
|
<div style="display: flex; align-items: center; gap: 16px;"> |
|
|
<div style="width: 52px; height: 52px; background: linear-gradient(135deg, rgba(139, 92, 246, 0.15), rgba(34, 197, 94, 0.1)); border: 1px solid rgba(139, 92, 246, 0.3); border-radius: 14px; display: flex; align-items: center; justify-content: center; box-shadow: 0 0 30px rgba(139, 92, 246, 0.2);"> |
|
|
<span style="font-size: 26px;">β¨</span> |
|
|
</div> |
|
|
<div> |
|
|
<h1 style="margin: 0; background: linear-gradient(135deg, #8b5cf6, #22c55e); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; font-size: 28px; font-weight: 800; letter-spacing: -0.5px;">DevRel Campaign Generator</h1> |
|
|
<p style="margin: 4px 0 0 0; color: var(--text-muted, #71717a); font-size: 14px;">AI-Powered Content for Developer Relations</p> |
|
|
</div> |
|
|
</div> |
|
|
<div style="display: flex; align-items: center; gap: 10px; padding: 10px 18px; background: rgba(34, 197, 94, 0.1); border: 1px solid rgba(34, 197, 94, 0.2); border-radius: 24px;"> |
|
|
<div style="width: 10px; height: 10px; border-radius: 50%; background: #22c55e; animation: pulse 2s infinite; box-shadow: 0 0 12px rgba(34, 197, 94, 0.6);"></div> |
|
|
<span style="font-size: 13px; font-weight: 600; color: #22c55e;">System Online</span> |
|
|
</div> |
|
|
</div> |
|
|
<style> |
|
|
@keyframes pulse { |
|
|
0%, 100% { opacity: 1; transform: scale(1); } |
|
|
50% { opacity: 0.7; transform: scale(0.9); } |
|
|
} |
|
|
</style> |
|
|
""" |
|
|
) |
|
|
|
|
|
|
|
|
with gr.Row(): |
|
|
with gr.Column(): |
|
|
gr.HTML( |
|
|
""" |
|
|
<div style="background: var(--bg-card, #ffffff); border: 1px solid var(--border-color, #e4e4e7); border-radius: 20px; padding: 32px; margin-bottom: 24px; box-shadow: var(--shadow-md, 0 4px 6px -1px rgba(0, 0, 0, 0.1)); transition: all 0.3s ease;"> |
|
|
<!-- Header --> |
|
|
<div style="display: flex; align-items: flex-start; justify-content: space-between; margin-bottom: 24px;"> |
|
|
<div style="display: flex; align-items: center; gap: 16px;"> |
|
|
<div style="width: 56px; height: 56px; background: linear-gradient(135deg, rgba(139, 92, 246, 0.2), rgba(34, 197, 94, 0.1)); border: 1px solid rgba(139, 92, 246, 0.3); border-radius: 16px; display: flex; align-items: center; justify-content: center; box-shadow: 0 0 30px rgba(139, 92, 246, 0.15);"> |
|
|
<span style="font-size: 28px;">π</span> |
|
|
</div> |
|
|
<div> |
|
|
<h2 style="margin: 0; font-size: 24px; font-weight: 700; color: var(--text-primary, #18181b);">About This Tool</h2> |
|
|
<p style="margin: 6px 0 0 0; color: var(--text-muted, #71717a); font-size: 14px;">Transform repositories into complete DevRel campaigns</p> |
|
|
</div> |
|
|
</div> |
|
|
<div style="display: flex; gap: 10px;"> |
|
|
<span style="background: linear-gradient(135deg, rgba(139, 92, 246, 0.15), rgba(139, 92, 246, 0.05)); color: #8b5cf6; padding: 8px 16px; border-radius: 20px; font-size: 13px; font-weight: 600; border: 1px solid rgba(139, 92, 246, 0.25);">Claude AI</span> |
|
|
<span style="background: linear-gradient(135deg, rgba(249, 115, 22, 0.15), rgba(249, 115, 22, 0.05)); color: #f97316; padding: 8px 16px; border-radius: 20px; font-size: 13px; font-weight: 600; border: 1px solid rgba(249, 115, 22, 0.25);">Gradio 6</span> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<!-- Description --> |
|
|
<p style="margin: 0 0 24px 0; color: var(--text-secondary, #52525b); font-size: 16px; line-height: 1.7;"> |
|
|
Analyze any GitHub repository and automatically generate <strong style="color: #22c55e;">professional DevRel content</strong> including |
|
|
social threads, technical blogs, tutorials, conference talks, and hackathon challenges. Our intelligent pipeline |
|
|
understands code changes and <strong style="color: #8b5cf6;">detects breaking changes</strong> to create relevant, actionable content. |
|
|
</p> |
|
|
|
|
|
<!-- Feature Grid --> |
|
|
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 14px; margin-bottom: 24px;"> |
|
|
<div style="background: rgba(139, 92, 246, 0.08); border: 1px solid rgba(139, 92, 246, 0.15); border-radius: 14px; padding: 16px; transition: all 0.2s ease; cursor: default;"> |
|
|
<div style="font-size: 22px; margin-bottom: 8px;">π¦</div> |
|
|
<div style="color: #8b5cf6; font-size: 13px; font-weight: 600; margin-bottom: 4px;">Social Threads</div> |
|
|
<div style="color: var(--text-muted, #71717a); font-size: 12px; line-height: 1.5;">Viral Twitter/X content with hooks</div> |
|
|
</div> |
|
|
<div style="background: rgba(34, 197, 94, 0.08); border: 1px solid rgba(34, 197, 94, 0.15); border-radius: 14px; padding: 16px; transition: all 0.2s ease;"> |
|
|
<div style="font-size: 22px; margin-bottom: 8px;">π</div> |
|
|
<div style="color: #22c55e; font-size: 13px; font-weight: 600; margin-bottom: 4px;">Technical Blogs</div> |
|
|
<div style="color: var(--text-muted, #71717a); font-size: 12px; line-height: 1.5;">SEO-optimized deep-dives</div> |
|
|
</div> |
|
|
<div style="background: rgba(249, 115, 22, 0.08); border: 1px solid rgba(249, 115, 22, 0.15); border-radius: 14px; padding: 16px; transition: all 0.2s ease;"> |
|
|
<div style="font-size: 22px; margin-bottom: 8px;">π</div> |
|
|
<div style="color: #f97316; font-size: 13px; font-weight: 600; margin-bottom: 4px;">Tutorials</div> |
|
|
<div style="color: var(--text-muted, #71717a); font-size: 12px; line-height: 1.5;">Step-by-step guides</div> |
|
|
</div> |
|
|
<div style="background: rgba(6, 182, 212, 0.08); border: 1px solid rgba(6, 182, 212, 0.15); border-radius: 14px; padding: 16px; transition: all 0.2s ease;"> |
|
|
<div style="font-size: 22px; margin-bottom: 8px;">π€</div> |
|
|
<div style="color: #06b6d4; font-size: 13px; font-weight: 600; margin-bottom: 4px;">Talk Outlines</div> |
|
|
<div style="color: var(--text-muted, #71717a); font-size: 12px; line-height: 1.5;">Conference-ready decks</div> |
|
|
</div> |
|
|
<div style="background: rgba(59, 130, 246, 0.08); border: 1px solid rgba(59, 130, 246, 0.15); border-radius: 14px; padding: 16px; transition: all 0.2s ease;"> |
|
|
<div style="font-size: 22px; margin-bottom: 8px;">πΌ</div> |
|
|
<div style="color: #3b82f6; font-size: 13px; font-weight: 600; margin-bottom: 4px;">LinkedIn Posts</div> |
|
|
<div style="color: var(--text-muted, #71717a); font-size: 12px; line-height: 1.5;">Professional updates</div> |
|
|
</div> |
|
|
<div style="background: rgba(236, 72, 153, 0.08); border: 1px solid rgba(236, 72, 153, 0.15); border-radius: 14px; padding: 16px; transition: all 0.2s ease;"> |
|
|
<div style="font-size: 22px; margin-bottom: 8px;">π</div> |
|
|
<div style="color: #ec4899; font-size: 13px; font-weight: 600; margin-bottom: 4px;">Hackathon</div> |
|
|
<div style="color: var(--text-muted, #71717a); font-size: 12px; line-height: 1.5;">Challenge briefs</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<!-- Key Innovations --> |
|
|
<div style="background: var(--bg-tertiary, #f5f5f5); border: 1px solid var(--border-color, #e4e4e7); border-radius: 16px; padding: 20px; margin-bottom: 20px;"> |
|
|
<div style="color: #8b5cf6; font-size: 14px; font-weight: 700; margin-bottom: 16px; display: flex; align-items: center; gap: 8px;"> |
|
|
<span>β¨</span> Key Innovations |
|
|
</div> |
|
|
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 12px;"> |
|
|
<div style="display: flex; align-items: flex-start; gap: 12px;"> |
|
|
<span style="color: #22c55e; font-size: 16px;">β</span> |
|
|
<div> |
|
|
<div style="color: var(--text-primary, #18181b); font-weight: 600; font-size: 14px;">Code Diff Analysis</div> |
|
|
<div style="color: var(--text-muted, #71717a); font-size: 13px;">Understands actual code changes</div> |
|
|
</div> |
|
|
</div> |
|
|
<div style="display: flex; align-items: flex-start; gap: 12px;"> |
|
|
<span style="color: #22c55e; font-size: 16px;">β</span> |
|
|
<div> |
|
|
<div style="color: var(--text-primary, #18181b); font-weight: 600; font-size: 14px;">Breaking Change Detection</div> |
|
|
<div style="color: var(--text-muted, #71717a); font-size: 13px;">Auto-generates migration guides</div> |
|
|
</div> |
|
|
</div> |
|
|
<div style="display: flex; align-items: flex-start; gap: 12px;"> |
|
|
<span style="color: #22c55e; font-size: 16px;">β</span> |
|
|
<div> |
|
|
<div style="color: var(--text-primary, #18181b); font-weight: 600; font-size: 14px;">README Priority</div> |
|
|
<div style="color: var(--text-muted, #71717a); font-size: 13px;">Documentation changes highlighted</div> |
|
|
</div> |
|
|
</div> |
|
|
<div style="display: flex; align-items: flex-start; gap: 12px;"> |
|
|
<span style="color: #22c55e; font-size: 16px;">β</span> |
|
|
<div> |
|
|
<div style="color: var(--text-primary, #18181b); font-weight: 600; font-size: 14px;">Real-time Streaming</div> |
|
|
<div style="color: var(--text-muted, #71717a); font-size: 13px;">Watch content generate live</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<!-- Tip --> |
|
|
<div style="display: flex; align-items: center; gap: 10px; padding-top: 16px; border-top: 1px solid var(--border-color, #e4e4e7);"> |
|
|
<span style="font-size: 18px;">π‘</span> |
|
|
<p style="margin: 0; color: var(--text-muted, #71717a); font-size: 14px;"> |
|
|
<strong style="color: var(--text-secondary, #52525b);">Tip:</strong> Configure your own API keys in |
|
|
<span style="color: #f97316; font-weight: 600;">βοΈ Settings</span> if the default quota is exhausted. |
|
|
</p> |
|
|
</div> |
|
|
</div> |
|
|
""" |
|
|
) |
|
|
|
|
|
|
|
|
with gr.Row(): |
|
|
|
|
|
with gr.Column(scale=2): |
|
|
|
|
|
with gr.Group(elem_classes=["command-center-card"]): |
|
|
gr.HTML( |
|
|
""" |
|
|
<div style="display: flex; align-items: center; gap: 14px; margin-bottom: 20px; padding-bottom: 16px; border-bottom: 1px solid var(--border-color, #e4e4e7);"> |
|
|
<div style="width: 48px; height: 48px; background: linear-gradient(135deg, rgba(139, 92, 246, 0.2), rgba(139, 92, 246, 0.05)); border: 1px solid rgba(139, 92, 246, 0.3); border-radius: 14px; display: flex; align-items: center; justify-content: center; box-shadow: 0 0 20px rgba(139, 92, 246, 0.15);"> |
|
|
<span style="font-size: 24px;">π</span> |
|
|
</div> |
|
|
<div> |
|
|
<h3 style="margin: 0; font-size: 20px; font-weight: 700; color: var(--text-primary, #18181b);">Command Center</h3> |
|
|
<p style="margin: 4px 0 0 0; color: var(--text-muted, #71717a); font-size: 13px;">Configure and launch your campaign</p> |
|
|
</div> |
|
|
</div> |
|
|
""" |
|
|
) |
|
|
|
|
|
repo_url = gr.Textbox( |
|
|
label="Repository URL", |
|
|
placeholder="https://github.com/username/repo", |
|
|
lines=1, |
|
|
) |
|
|
|
|
|
time_range = gr.Dropdown( |
|
|
label="Analysis Period", |
|
|
choices=[ |
|
|
("Last 1 week", "1week"), |
|
|
("Last 1 month", "1month"), |
|
|
("Last 3 months", "3months"), |
|
|
("Last 6 months", "6months"), |
|
|
("Last Year", "1year"), |
|
|
], |
|
|
value="3months", |
|
|
) |
|
|
|
|
|
|
|
|
with gr.Accordion("βοΈ Settings (API Keys)", open=False): |
|
|
gr.HTML( |
|
|
'<div style="padding: 12px 0; color: var(--text-muted, #71717a); font-size: 13px;">π‘ Add your own API keys if the default quota is exhausted</div>' |
|
|
) |
|
|
anthropic_key = gr.Textbox( |
|
|
label="Anthropic API Key (Claude)", |
|
|
placeholder="sk-ant-api03-...", |
|
|
type="password", |
|
|
lines=1, |
|
|
) |
|
|
github_token = gr.Textbox( |
|
|
label="GitHub Token (Optional)", |
|
|
placeholder="ghp_...", |
|
|
type="password", |
|
|
lines=1, |
|
|
) |
|
|
gr.HTML( |
|
|
'''<div style="padding: 12px 0; color: var(--text-muted, #71717a); font-size: 13px;"> |
|
|
<p style="margin: 0 0 8px 0;">π <a href="https://console.anthropic.com" target="_blank" style="color: #8b5cf6; text-decoration: none; font-weight: 500;">Get Anthropic API Key β</a></p> |
|
|
<p style="margin: 0;">π <a href="https://github.com/settings/tokens" target="_blank" style="color: #22c55e; text-decoration: none; font-weight: 500;">Get GitHub Token β</a></p> |
|
|
</div>''' |
|
|
) |
|
|
|
|
|
with gr.Row(): |
|
|
generate_btn = gr.Button( |
|
|
"β¨ Generate Campaign", |
|
|
variant="primary", |
|
|
size="lg", |
|
|
) |
|
|
reset_btn = gr.Button("π Reset", variant="secondary", size="lg") |
|
|
|
|
|
|
|
|
with gr.Group(elem_classes=["pipeline-status-card"]): |
|
|
gr.HTML( |
|
|
""" |
|
|
<div style="display: flex; align-items: center; gap: 14px; margin-bottom: 16px; padding-bottom: 14px; border-bottom: 1px solid var(--border-color, #e4e4e7);"> |
|
|
<div style="width: 44px; height: 44px; background: linear-gradient(135deg, rgba(34, 197, 94, 0.2), rgba(34, 197, 94, 0.05)); border: 1px solid rgba(34, 197, 94, 0.3); border-radius: 12px; display: flex; align-items: center; justify-content: center; box-shadow: 0 0 20px rgba(34, 197, 94, 0.15);"> |
|
|
<span style="font-size: 22px;">π</span> |
|
|
</div> |
|
|
<div> |
|
|
<h3 style="margin: 0; font-size: 18px; font-weight: 700; color: var(--text-primary, #18181b);">Pipeline Status</h3> |
|
|
<p style="margin: 4px 0 0 0; color: var(--text-muted, #71717a); font-size: 12px;">Real-time generation progress</p> |
|
|
</div> |
|
|
</div> |
|
|
""" |
|
|
) |
|
|
|
|
|
|
|
|
progress_display = gr.HTML(value=get_progress_html(0, "idle")) |
|
|
|
|
|
|
|
|
gr.HTML( |
|
|
""" |
|
|
<div style="margin-top: 16px; border-top: 1px solid var(--border-color, #e4e4e7); padding-top: 16px;"> |
|
|
<div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 12px;"> |
|
|
<div style="display: flex; align-items: center; gap: 12px;"> |
|
|
<div style="display: flex; gap: 6px;"> |
|
|
<div style="width: 12px; height: 12px; border-radius: 50%; background: linear-gradient(135deg, #ef4444, #dc2626); box-shadow: 0 0 6px rgba(239, 68, 68, 0.4);"></div> |
|
|
<div style="width: 12px; height: 12px; border-radius: 50%; background: linear-gradient(135deg, #eab308, #ca8a04); box-shadow: 0 0 6px rgba(234, 179, 8, 0.4);"></div> |
|
|
<div style="width: 12px; height: 12px; border-radius: 50%; background: linear-gradient(135deg, #22c55e, #16a34a); box-shadow: 0 0 6px rgba(34, 197, 94, 0.4);"></div> |
|
|
</div> |
|
|
<span style="font-weight: 600; color: var(--text-primary, #18181b); font-size: 14px;">Event Log</span> |
|
|
</div> |
|
|
<div style="display: flex; align-items: center; gap: 6px; padding: 4px 12px; background: rgba(34, 197, 94, 0.1); border: 1px solid rgba(34, 197, 94, 0.25); border-radius: 16px;"> |
|
|
<div style="width: 6px; height: 6px; border-radius: 50%; background: #22c55e; animation: pulse 2s infinite; box-shadow: 0 0 8px rgba(34, 197, 94, 0.6);"></div> |
|
|
<span style="color: #22c55e; font-size: 11px; font-weight: 600;">LIVE</span> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
""" |
|
|
) |
|
|
|
|
|
|
|
|
logs_display = gr.HTML( |
|
|
value=format_logs(), |
|
|
elem_classes=["terminal-content"], |
|
|
) |
|
|
|
|
|
|
|
|
gr.HTML( |
|
|
""" |
|
|
<div style="display: flex; align-items: center; justify-content: space-between; margin-top: 16px; padding-top: 14px; border-top: 1px solid var(--border-color, #e4e4e7); font-size: 12px;"> |
|
|
<div style="display: flex; align-items: center; gap: 12px;"> |
|
|
<span style="color: var(--text-muted, #71717a);">Powered by:</span> |
|
|
<span style="background: linear-gradient(135deg, rgba(139, 92, 246, 0.15), rgba(139, 92, 246, 0.05)); color: #8b5cf6; padding: 4px 12px; border-radius: 14px; font-weight: 600; font-size: 11px; border: 1px solid rgba(139, 92, 246, 0.2);">Claude</span> |
|
|
<span style="color: var(--text-muted, #71717a);">Γ</span> |
|
|
<span style="background: linear-gradient(135deg, rgba(249, 115, 22, 0.15), rgba(249, 115, 22, 0.05)); color: #f97316; padding: 4px 12px; border-radius: 14px; font-weight: 600; font-size: 11px; border: 1px solid rgba(249, 115, 22, 0.2);">Gradio 6</span> |
|
|
</div> |
|
|
</div> |
|
|
""" |
|
|
) |
|
|
|
|
|
|
|
|
with gr.Column(scale=3): |
|
|
|
|
|
with gr.Group(elem_classes=["campaign-output-card"]): |
|
|
gr.HTML( |
|
|
""" |
|
|
<div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 20px; padding-bottom: 16px; border-bottom: 1px solid var(--border-color, #e4e4e7);"> |
|
|
<div style="display: flex; align-items: center; gap: 14px;"> |
|
|
<div style="width: 48px; height: 48px; background: linear-gradient(135deg, rgba(34, 197, 94, 0.2), rgba(6, 182, 212, 0.1)); border: 1px solid rgba(34, 197, 94, 0.3); border-radius: 14px; display: flex; align-items: center; justify-content: center; box-shadow: 0 0 20px rgba(34, 197, 94, 0.15);"> |
|
|
<span style="font-size: 24px;">π€</span> |
|
|
</div> |
|
|
<div> |
|
|
<h3 style="margin: 0; font-size: 20px; font-weight: 700; color: var(--text-primary, #18181b);">Campaign Output</h3> |
|
|
<p style="margin: 4px 0 0 0; color: var(--text-muted, #71717a); font-size: 13px;">Generated content for all platforms</p> |
|
|
</div> |
|
|
</div> |
|
|
<div style="display: flex; gap: 10px;"> |
|
|
<span style="background: rgba(34, 197, 94, 0.1); color: #22c55e; padding: 6px 14px; border-radius: 16px; font-size: 12px; font-weight: 600; border: 1px solid rgba(34, 197, 94, 0.2);">6 Formats</span> |
|
|
</div> |
|
|
</div> |
|
|
""" |
|
|
) |
|
|
|
|
|
metrics_display = gr.HTML(value="") |
|
|
|
|
|
with gr.Tabs() as tabs: |
|
|
with gr.Tab("π¦ Social", id="social"): |
|
|
twitter_output = gr.Markdown( |
|
|
label="Twitter Thread", |
|
|
value="Content not yet generated...", |
|
|
) |
|
|
|
|
|
with gr.Tab("π Blog", id="blog"): |
|
|
blog_output = gr.Markdown( |
|
|
label="Technical Blog Post", |
|
|
value="Content not yet generated...", |
|
|
) |
|
|
|
|
|
with gr.Tab("π Tutorial", id="tutorial"): |
|
|
tutorial_output = gr.Markdown( |
|
|
label="Getting Started Tutorial", |
|
|
value="Content not yet generated...", |
|
|
) |
|
|
|
|
|
with gr.Tab("π€ Talk", id="talk"): |
|
|
talk_output = gr.Markdown( |
|
|
label="Conference Talk Outline", |
|
|
value="Content not yet generated...", |
|
|
) |
|
|
|
|
|
with gr.Tab("πΌ LinkedIn", id="linkedin"): |
|
|
linkedin_output = gr.Markdown( |
|
|
label="LinkedIn Post", |
|
|
value="Content not yet generated...", |
|
|
) |
|
|
|
|
|
with gr.Tab("π Hackathon", id="hackathon"): |
|
|
hackathon_output = gr.Markdown( |
|
|
label="Hackathon Challenge", |
|
|
value="Content not yet generated...", |
|
|
) |
|
|
|
|
|
|
|
|
generate_btn.click( |
|
|
fn=generate_campaign, |
|
|
inputs=[repo_url, time_range, anthropic_key, github_token], |
|
|
outputs=[ |
|
|
logs_display, |
|
|
progress_display, |
|
|
twitter_output, |
|
|
blog_output, |
|
|
tutorial_output, |
|
|
talk_output, |
|
|
linkedin_output, |
|
|
hackathon_output, |
|
|
metrics_display, |
|
|
], |
|
|
) |
|
|
|
|
|
reset_btn.click( |
|
|
fn=reset_state, |
|
|
inputs=[], |
|
|
outputs=[ |
|
|
repo_url, |
|
|
time_range, |
|
|
logs_display, |
|
|
progress_display, |
|
|
twitter_output, |
|
|
blog_output, |
|
|
tutorial_output, |
|
|
talk_output, |
|
|
linkedin_output, |
|
|
hackathon_output, |
|
|
metrics_display, |
|
|
], |
|
|
) |
|
|
|
|
|
return app |
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__": |
|
|
app = create_app() |
|
|
app.launch( |
|
|
server_name="0.0.0.0", |
|
|
server_port=7860, |
|
|
share=False, |
|
|
show_error=True, |
|
|
ssr_mode=False, |
|
|
) |
|
|
|