Spaces:
Sleeping
Sleeping
| """ | |
| GitHub Repository Intelligence Analyzer — Gradio Web UI | |
| Deployed on Hugging Face Spaces. | |
| """ | |
| import os | |
| import sys | |
| import json | |
| sys.path.insert(0, os.path.dirname(__file__)) | |
| import gradio as gr | |
| from src.analyzer import GitHubClient, analyze_repo | |
| from src.reporter import format_report, format_summary_table, to_json | |
| GITHUB_TOKEN = os.environ.get("GITHUB_TOKEN") | |
| client = GitHubClient(token=GITHUB_TOKEN) | |
| EXAMPLE_REPOS = "\n".join([ | |
| "https://github.com/c2siorg/Webiu", | |
| "https://github.com/fastapi/fastapi", | |
| "https://github.com/pallets/flask", | |
| "https://github.com/huggingface/transformers", | |
| "https://github.com/torvalds/linux", | |
| ]) | |
| CUSTOM_CSS = """ | |
| @import url('https://fonts.googleapis.com/css2?family=Syne:wght@400;600;700;800&family=JetBrains+Mono:wght@300;400;500&display=swap'); | |
| :root { | |
| --bg-0: #080b0f; | |
| --bg-1: #0d1117; | |
| --bg-2: #161b22; | |
| --bg-3: #1c2128; | |
| --border: #2a3441; | |
| --border-bright: #3a4a5c; | |
| --green: #3fb950; | |
| --green-dim: #1a4a2a; | |
| --amber: #d29922; | |
| --amber-dim: #3d2e0a; | |
| --red: #f85149; | |
| --blue: #388bfd; | |
| --text-1: #e6edf3; | |
| --text-2: #8b949e; | |
| --text-3: #484f58; | |
| --font-display: 'Syne', sans-serif; | |
| --font-mono: 'JetBrains Mono', monospace; | |
| } | |
| /* ── Reset everything ── */ | |
| *, *::before, *::after { box-sizing: border-box; } | |
| .gradio-container { | |
| background: var(--bg-0) !important; | |
| font-family: var(--font-mono) !important; | |
| max-width: 960px !important; | |
| margin: 0 auto !important; | |
| padding: 0 24px 64px !important; | |
| } | |
| /* ── Header ── */ | |
| .gh-header { | |
| padding: 56px 0 40px; | |
| border-bottom: 1px solid var(--border); | |
| margin-bottom: 40px; | |
| } | |
| .gh-header-eyebrow { | |
| font-family: var(--font-mono); | |
| font-size: 11px; | |
| font-weight: 500; | |
| letter-spacing: 0.15em; | |
| text-transform: uppercase; | |
| color: var(--green); | |
| margin-bottom: 16px; | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| } | |
| .gh-header-eyebrow::before { | |
| content: ''; | |
| display: inline-block; | |
| width: 20px; | |
| height: 1px; | |
| background: var(--green); | |
| } | |
| .gh-header h1 { | |
| font-family: var(--font-display) !important; | |
| font-size: clamp(28px, 5vw, 42px) !important; | |
| font-weight: 800 !important; | |
| color: var(--text-1) !important; | |
| line-height: 1.1 !important; | |
| margin: 0 0 16px !important; | |
| letter-spacing: -0.02em !important; | |
| } | |
| .gh-header h1 span { | |
| color: var(--green); | |
| } | |
| .gh-header p { | |
| font-family: var(--font-mono) !important; | |
| font-size: 13px !important; | |
| color: var(--text-2) !important; | |
| margin: 0 !important; | |
| line-height: 1.7 !important; | |
| max-width: 560px !important; | |
| } | |
| /* ── Score cards strip ── */ | |
| .score-strip { | |
| display: grid; | |
| grid-template-columns: repeat(3, 1fr); | |
| gap: 1px; | |
| background: var(--border); | |
| border: 1px solid var(--border); | |
| border-radius: 10px; | |
| overflow: hidden; | |
| margin-bottom: 32px; | |
| } | |
| .score-card { | |
| background: var(--bg-2); | |
| padding: 20px 24px; | |
| display: flex; | |
| flex-direction: column; | |
| gap: 6px; | |
| } | |
| .score-card-label { | |
| font-family: var(--font-mono); | |
| font-size: 10px; | |
| font-weight: 500; | |
| letter-spacing: 0.12em; | |
| text-transform: uppercase; | |
| color: var(--text-3); | |
| } | |
| .score-card-value { | |
| font-family: var(--font-display); | |
| font-size: 22px; | |
| font-weight: 700; | |
| color: var(--text-1); | |
| } | |
| .score-card-sub { | |
| font-family: var(--font-mono); | |
| font-size: 11px; | |
| color: var(--text-2); | |
| } | |
| /* ── Input area ── */ | |
| .input-section { | |
| margin-bottom: 28px; | |
| } | |
| .input-label { | |
| font-family: var(--font-mono); | |
| font-size: 11px; | |
| font-weight: 500; | |
| letter-spacing: 0.1em; | |
| text-transform: uppercase; | |
| color: var(--text-2); | |
| margin-bottom: 10px; | |
| display: block; | |
| } | |
| /* Override Gradio textarea */ | |
| .gradio-container textarea { | |
| background: var(--bg-2) !important; | |
| border: 1px solid var(--border) !important; | |
| border-radius: 8px !important; | |
| color: var(--text-1) !important; | |
| font-family: var(--font-mono) !important; | |
| font-size: 13px !important; | |
| line-height: 1.7 !important; | |
| padding: 16px !important; | |
| resize: vertical !important; | |
| transition: border-color 0.15s ease !important; | |
| } | |
| .gradio-container textarea:focus { | |
| border-color: var(--green) !important; | |
| outline: none !important; | |
| box-shadow: 0 0 0 3px rgba(63, 185, 80, 0.08) !important; | |
| } | |
| .gradio-container textarea::placeholder { | |
| color: var(--text-3) !important; | |
| } | |
| /* Labels */ | |
| .gradio-container label span, | |
| .gradio-container .label-wrap span { | |
| font-family: var(--font-mono) !important; | |
| font-size: 11px !important; | |
| font-weight: 500 !important; | |
| letter-spacing: 0.1em !important; | |
| text-transform: uppercase !important; | |
| color: var(--text-2) !important; | |
| } | |
| /* ── Button ── */ | |
| .gradio-container button.primary { | |
| background: var(--green) !important; | |
| color: #000 !important; | |
| font-family: var(--font-mono) !important; | |
| font-size: 13px !important; | |
| font-weight: 500 !important; | |
| letter-spacing: 0.06em !important; | |
| border: none !important; | |
| border-radius: 8px !important; | |
| padding: 12px 28px !important; | |
| cursor: pointer !important; | |
| transition: all 0.15s ease !important; | |
| width: 100% !important; | |
| margin-top: 12px !important; | |
| } | |
| .gradio-container button.primary:hover { | |
| background: #4fc660 !important; | |
| transform: translateY(-1px) !important; | |
| box-shadow: 0 4px 16px rgba(63, 185, 80, 0.25) !important; | |
| } | |
| .gradio-container button.primary:active { | |
| transform: translateY(0) !important; | |
| } | |
| /* ── Output tabs ── */ | |
| .gradio-container .tabs { | |
| border: 1px solid var(--border) !important; | |
| border-radius: 10px !important; | |
| overflow: hidden !important; | |
| background: var(--bg-1) !important; | |
| } | |
| .gradio-container .tab-nav { | |
| background: var(--bg-2) !important; | |
| border-bottom: 1px solid var(--border) !important; | |
| padding: 0 4px !important; | |
| gap: 0 !important; | |
| } | |
| .gradio-container .tab-nav button { | |
| font-family: var(--font-mono) !important; | |
| font-size: 12px !important; | |
| font-weight: 400 !important; | |
| letter-spacing: 0.05em !important; | |
| color: var(--text-2) !important; | |
| border: none !important; | |
| border-bottom: 2px solid transparent !important; | |
| border-radius: 0 !important; | |
| padding: 12px 20px !important; | |
| background: transparent !important; | |
| transition: color 0.15s !important; | |
| } | |
| .gradio-container .tab-nav button.selected { | |
| color: var(--green) !important; | |
| border-bottom-color: var(--green) !important; | |
| background: transparent !important; | |
| } | |
| .gradio-container .tabitem { | |
| background: var(--bg-1) !important; | |
| padding: 20px !important; | |
| } | |
| /* Output textbox */ | |
| .gradio-container .output-textbox textarea, | |
| .gradio-container .block textarea { | |
| background: transparent !important; | |
| border: none !important; | |
| color: var(--text-1) !important; | |
| font-family: var(--font-mono) !important; | |
| font-size: 12.5px !important; | |
| line-height: 1.75 !important; | |
| } | |
| /* Code block */ | |
| .gradio-container .codemirror-wrapper, | |
| .gradio-container code { | |
| background: transparent !important; | |
| font-family: var(--font-mono) !important; | |
| font-size: 12.5px !important; | |
| } | |
| /* ── Formula section ── */ | |
| .formula-grid { | |
| display: grid; | |
| grid-template-columns: 1fr 1fr; | |
| gap: 1px; | |
| background: var(--border); | |
| border: 1px solid var(--border); | |
| border-radius: 10px; | |
| overflow: hidden; | |
| margin-bottom: 32px; | |
| } | |
| .formula-card { | |
| background: var(--bg-2); | |
| padding: 20px 24px; | |
| } | |
| .formula-title { | |
| font-family: var(--font-display); | |
| font-size: 13px; | |
| font-weight: 700; | |
| color: var(--text-1); | |
| margin-bottom: 14px; | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| } | |
| .formula-title .dot { | |
| width: 7px; | |
| height: 7px; | |
| border-radius: 50%; | |
| background: var(--green); | |
| flex-shrink: 0; | |
| } | |
| .formula-title .dot.amber { background: var(--amber); } | |
| .formula-row { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| padding: 5px 0; | |
| border-bottom: 1px solid var(--bg-3); | |
| gap: 12px; | |
| } | |
| .formula-row:last-child { border-bottom: none; } | |
| .formula-metric { | |
| font-family: var(--font-mono); | |
| font-size: 11.5px; | |
| color: var(--text-2); | |
| } | |
| .formula-weight { | |
| font-family: var(--font-mono); | |
| font-size: 11px; | |
| font-weight: 500; | |
| color: var(--green); | |
| background: var(--green-dim); | |
| padding: 2px 8px; | |
| border-radius: 4px; | |
| white-space: nowrap; | |
| } | |
| .formula-weight.amber { | |
| color: var(--amber); | |
| background: var(--amber-dim); | |
| } | |
| /* ── Difficulty legend ── */ | |
| .legend-strip { | |
| display: flex; | |
| gap: 1px; | |
| background: var(--border); | |
| border: 1px solid var(--border); | |
| border-radius: 10px; | |
| overflow: hidden; | |
| margin-bottom: 40px; | |
| } | |
| .legend-item { | |
| flex: 1; | |
| background: var(--bg-2); | |
| padding: 14px 20px; | |
| display: flex; | |
| align-items: center; | |
| gap: 10px; | |
| } | |
| .legend-dot { | |
| width: 8px; | |
| height: 8px; | |
| border-radius: 50%; | |
| flex-shrink: 0; | |
| } | |
| .legend-text { | |
| font-family: var(--font-mono); | |
| font-size: 11.5px; | |
| color: var(--text-2); | |
| } | |
| .legend-text strong { | |
| display: block; | |
| color: var(--text-1); | |
| font-size: 12px; | |
| margin-bottom: 2px; | |
| } | |
| /* ── Footer ── */ | |
| .gh-footer { | |
| margin-top: 56px; | |
| padding-top: 24px; | |
| border-top: 1px solid var(--border); | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| } | |
| .gh-footer-text { | |
| font-family: var(--font-mono); | |
| font-size: 11px; | |
| color: var(--text-3); | |
| } | |
| .gh-footer-badge { | |
| font-family: var(--font-mono); | |
| font-size: 10px; | |
| font-weight: 500; | |
| letter-spacing: 0.1em; | |
| text-transform: uppercase; | |
| color: var(--green); | |
| background: var(--green-dim); | |
| padding: 4px 10px; | |
| border-radius: 4px; | |
| border: 1px solid rgba(63, 185, 80, 0.2); | |
| } | |
| /* ── Scrollbar ── */ | |
| ::-webkit-scrollbar { width: 6px; height: 6px; } | |
| ::-webkit-scrollbar-track { background: var(--bg-1); } | |
| ::-webkit-scrollbar-thumb { background: var(--border-bright); border-radius: 3px; } | |
| ::-webkit-scrollbar-thumb:hover { background: var(--text-3); } | |
| /* ── Misc Gradio overrides ── */ | |
| .gradio-container .block { | |
| background: transparent !important; | |
| border: none !important; | |
| padding: 0 !important; | |
| } | |
| .gradio-container .form { | |
| background: transparent !important; | |
| border: none !important; | |
| } | |
| .gradio-container footer { display: none !important; } | |
| .gradio-container .gap { gap: 16px !important; } | |
| /* Spinner */ | |
| .gradio-container .generating { | |
| border-color: var(--green) !important; | |
| } | |
| """ | |
| HEADER_HTML = """ | |
| <div class="gh-header"> | |
| <div class="gh-header-eyebrow">C2SI · GSoC 2026 · Pre-Task #541</div> | |
| <h1>Repository <span>Intelligence</span><br>Analyzer</h1> | |
| <p>Analyze GitHub repositories for activity, complexity, and contributor-facing learning difficulty — powered by the GitHub REST API.</p> | |
| </div> | |
| """ | |
| FORMULA_HTML = """ | |
| <div class="formula-grid"> | |
| <div class="formula-card"> | |
| <div class="formula-title"><span class="dot"></span> Activity Score</div> | |
| <div class="formula-row"><span class="formula-metric">Commits · last 90 days</span><span class="formula-weight">30%</span></div> | |
| <div class="formula-row"><span class="formula-metric">Contributor count</span><span class="formula-weight">20%</span></div> | |
| <div class="formula-row"><span class="formula-metric">Stars (log-scaled)</span><span class="formula-weight">15%</span></div> | |
| <div class="formula-row"><span class="formula-metric">Forks</span><span class="formula-weight">10%</span></div> | |
| <div class="formula-row"><span class="formula-metric">Open issues</span><span class="formula-weight">10%</span></div> | |
| <div class="formula-row"><span class="formula-metric">Recency (days since push)</span><span class="formula-weight">10%</span></div> | |
| <div class="formula-row"><span class="formula-metric">Release count</span><span class="formula-weight">5%</span></div> | |
| </div> | |
| <div class="formula-card"> | |
| <div class="formula-title"><span class="dot amber"></span> Complexity Score</div> | |
| <div class="formula-row"><span class="formula-metric">File count (log-scaled)</span><span class="formula-weight amber">30%</span></div> | |
| <div class="formula-row"><span class="formula-metric">Language diversity</span><span class="formula-weight amber">25%</span></div> | |
| <div class="formula-row"><span class="formula-metric">Dependency files detected</span><span class="formula-weight amber">20%</span></div> | |
| <div class="formula-row"><span class="formula-metric">Repository size (KB)</span><span class="formula-weight amber">15%</span></div> | |
| <div class="formula-row"><span class="formula-metric">CI/CD pipeline present</span><span class="formula-weight amber">10%</span></div> | |
| </div> | |
| </div> | |
| <div class="legend-strip"> | |
| <div class="legend-item"> | |
| <span class="legend-dot" style="background:#3fb950"></span> | |
| <span class="legend-text"><strong>Beginner</strong>Both scores < 50</span> | |
| </div> | |
| <div class="legend-item"> | |
| <span class="legend-dot" style="background:#d29922"></span> | |
| <span class="legend-text"><strong>Intermediate</strong>Avg ≥ 50 or either ≥ 55</span> | |
| </div> | |
| <div class="legend-item"> | |
| <span class="legend-dot" style="background:#f85149"></span> | |
| <span class="legend-text"><strong>Advanced</strong>Complexity ≥ 70 and Activity ≥ 75</span> | |
| </div> | |
| </div> | |
| """ | |
| FOOTER_HTML = """ | |
| <div class="gh-footer"> | |
| <span class="gh-footer-text">Built with Python · Gradio · GitHub REST API v3</span> | |
| <span class="gh-footer-badge">GSoC 2026</span> | |
| </div> | |
| """ | |
| def analyze(repo_input: str): | |
| urls = [u.strip() for u in repo_input.strip().splitlines() if u.strip()] | |
| if not urls: | |
| return "⚠️ Please enter at least one GitHub repository URL.", "{}" | |
| if len(urls) > 10: | |
| return "⚠️ Maximum 10 repositories per run.", "{}" | |
| reports = [] | |
| full_text = [] | |
| for i, url in enumerate(urls, 1): | |
| full_text.append(f"── [{i}/{len(urls)}] {url}") | |
| report = analyze_repo(url, client) | |
| reports.append(report) | |
| full_text.append(format_report(report)) | |
| if len(reports) > 1: | |
| full_text.append(format_summary_table(reports)) | |
| full_text.append(f"\n✓ Analyzed {len(reports)} repo(s).") | |
| return "\n".join(full_text), to_json(reports, indent=2) | |
| with gr.Blocks(css=CUSTOM_CSS, title="Repository Intelligence Analyzer") as demo: | |
| gr.HTML(HEADER_HTML) | |
| gr.HTML(FORMULA_HTML) | |
| with gr.Row(): | |
| with gr.Column(): | |
| repo_input = gr.Textbox( | |
| label="GitHub Repository URLs — one per line, max 10", | |
| placeholder="https://github.com/owner/repository", | |
| lines=7, | |
| value=EXAMPLE_REPOS, | |
| ) | |
| analyze_btn = gr.Button("→ Run Analysis", variant="primary") | |
| with gr.Tabs(): | |
| with gr.Tab("Report"): | |
| text_output = gr.Textbox( | |
| label="Analysis Output", | |
| lines=28, | |
| interactive=False, | |
| ) | |
| with gr.Tab("JSON"): | |
| json_output = gr.Code( | |
| label="Structured JSON", | |
| language="json", | |
| lines=28, | |
| interactive=False, | |
| ) | |
| analyze_btn.click( | |
| fn=analyze, | |
| inputs=[repo_input], | |
| outputs=[text_output, json_output], | |
| ) | |
| gr.HTML(FOOTER_HTML) | |
| if __name__ == "__main__": | |
| demo.launch() |