sifat371 commited on
Commit
08aa69f
·
1 Parent(s): 9c6d7dc

Updated app.py

Browse files
Files changed (1) hide show
  1. app.py +505 -80
app.py CHANGED
@@ -13,122 +13,553 @@ import gradio as gr
13
  from src.analyzer import GitHubClient, analyze_repo
14
  from src.reporter import format_report, format_summary_table, to_json
15
 
16
- # ── HF Spaces: token from Space secret ────────────────────
17
  GITHUB_TOKEN = os.environ.get("GITHUB_TOKEN")
18
  client = GitHubClient(token=GITHUB_TOKEN)
19
 
20
-
21
- # ── Default example repos ──────────────────────────────────
22
  EXAMPLE_REPOS = "\n".join([
23
  "https://github.com/c2siorg/Webiu",
24
- "https://github.com/django/django",
25
  "https://github.com/fastapi/fastapi",
26
  "https://github.com/pallets/flask",
27
  "https://github.com/huggingface/transformers",
28
  "https://github.com/torvalds/linux",
29
  ])
30
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
 
32
- def analyze(repo_input: str) -> tuple[str, str]:
33
- """
34
- Core Gradio handler.
35
- Returns (text_report, json_report).
36
- """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
  urls = [u.strip() for u in repo_input.strip().splitlines() if u.strip()]
38
  if not urls:
39
- return "⚠️ Please enter at least one GitHub repository URL.", "{}"
40
-
41
  if len(urls) > 10:
42
- return "⚠️ Please limit to 10 repositories per run to stay within API rate limits.", "{}"
43
 
44
  reports = []
45
  full_text = []
46
 
47
  for i, url in enumerate(urls, 1):
48
- full_text.append(f"Analyzing [{i}/{len(urls)}]: {url}...")
49
  report = analyze_repo(url, client)
50
  reports.append(report)
51
  full_text.append(format_report(report))
52
 
53
  if len(reports) > 1:
54
- full_text.append("\n📋 Summary")
55
  full_text.append(format_summary_table(reports))
56
 
57
- full_text.append(f"\n Done. Analyzed {len(reports)} repository/repositories.")
58
-
59
- text_output = "\n".join(full_text)
60
- json_output = to_json(reports, indent=2)
61
-
62
- return text_output, json_output
63
 
64
 
65
- # ── UI Layout ──────────────────────────────────────────────
66
- with gr.Blocks(title="GitHub Repo Intelligence Analyzer") as demo:
67
 
68
- gr.Markdown(
69
- """
70
- # 🔍 GitHub Repository Intelligence Analyzer
71
- Analyze GitHub repositories for **activity**, **complexity**, and **learning difficulty**.
72
 
73
- > Built for **C2SI GSoC 2026 Pre-Task #541**
74
-
75
- ### How it works
76
- - **Activity Score** (0–100): weighted signal from commits, contributors, stars, forks, recency, releases
77
- - **Complexity Score** (0–100): file count, language diversity, dependencies, repo size, CI presence
78
- - **Difficulty**: Beginner 🟢 / Intermediate 🟡 / Advanced 🔴 — derived from both scores
79
- """
80
- )
81
 
82
  with gr.Row():
83
- with gr.Column(scale=2):
84
  repo_input = gr.Textbox(
85
- label="GitHub Repository URLs (one per line, max 10)",
86
- placeholder="https://github.com/owner/repo",
87
- lines=8,
88
  value=EXAMPLE_REPOS,
89
  )
90
- analyze_btn = gr.Button("🚀 Analyze Repositories", variant="primary", size="lg")
91
-
92
- with gr.Column(scale=1):
93
- gr.Markdown(
94
- """
95
- ### Tips
96
- - Paste any public GitHub URL
97
- - Up to **10 repos** per run
98
- - Set `GITHUB_TOKEN` secret for higher rate limits (5000 req/hr vs 60)
99
- - Empty / archived repos are handled gracefully
100
-
101
- ### Scoring Formula
102
- **Activity** (weights):
103
- - Commits last 90d — 30%
104
- - Contributors — 20%
105
- - Stars — 15%
106
- - Forks — 10%
107
- - Open issues — 10%
108
- - Recency — 10%
109
- - Releases — 5%
110
-
111
- **Complexity** (weights):
112
- - File count — 30%
113
- - Language diversity — 25%
114
- - Dependency files — 20%
115
- - Repo size — 15%
116
- - Has CI/CD — 10%
117
- """
118
- )
119
 
120
  with gr.Tabs():
121
- with gr.Tab("📋 Text Report"):
122
  text_output = gr.Textbox(
123
- label="Analysis Report",
124
- lines=30,
125
  interactive=False,
126
  )
127
- with gr.Tab("📄 JSON Output"):
128
  json_output = gr.Code(
129
  label="Structured JSON",
130
  language="json",
131
- lines=30,
132
  interactive=False,
133
  )
134
 
@@ -138,14 +569,8 @@ with gr.Blocks(title="GitHub Repo Intelligence Analyzer") as demo:
138
  outputs=[text_output, json_output],
139
  )
140
 
141
- gr.Markdown(
142
- """
143
- ---
144
- Built with Python · Gradio · GitHub API
145
- | [Source Code](https://github.com/c2siorg/Webiu) | GSoC 2026 Pre-Task Submission
146
- """
147
- )
148
 
149
 
150
  if __name__ == "__main__":
151
- demo.launch(theme=gr.themes.Soft(primary_hue="blue"))
 
13
  from src.analyzer import GitHubClient, analyze_repo
14
  from src.reporter import format_report, format_summary_table, to_json
15
 
 
16
  GITHUB_TOKEN = os.environ.get("GITHUB_TOKEN")
17
  client = GitHubClient(token=GITHUB_TOKEN)
18
 
 
 
19
  EXAMPLE_REPOS = "\n".join([
20
  "https://github.com/c2siorg/Webiu",
 
21
  "https://github.com/fastapi/fastapi",
22
  "https://github.com/pallets/flask",
23
  "https://github.com/huggingface/transformers",
24
  "https://github.com/torvalds/linux",
25
  ])
26
 
27
+ CUSTOM_CSS = """
28
+ @import url('https://fonts.googleapis.com/css2?family=Syne:wght@400;600;700;800&family=JetBrains+Mono:wght@300;400;500&display=swap');
29
+
30
+ :root {
31
+ --bg-0: #080b0f;
32
+ --bg-1: #0d1117;
33
+ --bg-2: #161b22;
34
+ --bg-3: #1c2128;
35
+ --border: #2a3441;
36
+ --border-bright: #3a4a5c;
37
+ --green: #3fb950;
38
+ --green-dim: #1a4a2a;
39
+ --amber: #d29922;
40
+ --amber-dim: #3d2e0a;
41
+ --red: #f85149;
42
+ --blue: #388bfd;
43
+ --text-1: #e6edf3;
44
+ --text-2: #8b949e;
45
+ --text-3: #484f58;
46
+ --font-display: 'Syne', sans-serif;
47
+ --font-mono: 'JetBrains Mono', monospace;
48
+ }
49
+
50
+ /* ── Reset everything ── */
51
+ *, *::before, *::after { box-sizing: border-box; }
52
+
53
+ .gradio-container {
54
+ background: var(--bg-0) !important;
55
+ font-family: var(--font-mono) !important;
56
+ max-width: 960px !important;
57
+ margin: 0 auto !important;
58
+ padding: 0 24px 64px !important;
59
+ }
60
+
61
+ /* ── Header ── */
62
+ .gh-header {
63
+ padding: 56px 0 40px;
64
+ border-bottom: 1px solid var(--border);
65
+ margin-bottom: 40px;
66
+ }
67
+
68
+ .gh-header-eyebrow {
69
+ font-family: var(--font-mono);
70
+ font-size: 11px;
71
+ font-weight: 500;
72
+ letter-spacing: 0.15em;
73
+ text-transform: uppercase;
74
+ color: var(--green);
75
+ margin-bottom: 16px;
76
+ display: flex;
77
+ align-items: center;
78
+ gap: 8px;
79
+ }
80
+
81
+ .gh-header-eyebrow::before {
82
+ content: '';
83
+ display: inline-block;
84
+ width: 20px;
85
+ height: 1px;
86
+ background: var(--green);
87
+ }
88
+
89
+ .gh-header h1 {
90
+ font-family: var(--font-display) !important;
91
+ font-size: clamp(28px, 5vw, 42px) !important;
92
+ font-weight: 800 !important;
93
+ color: var(--text-1) !important;
94
+ line-height: 1.1 !important;
95
+ margin: 0 0 16px !important;
96
+ letter-spacing: -0.02em !important;
97
+ }
98
+
99
+ .gh-header h1 span {
100
+ color: var(--green);
101
+ }
102
+
103
+ .gh-header p {
104
+ font-family: var(--font-mono) !important;
105
+ font-size: 13px !important;
106
+ color: var(--text-2) !important;
107
+ margin: 0 !important;
108
+ line-height: 1.7 !important;
109
+ max-width: 560px !important;
110
+ }
111
+
112
+ /* ── Score cards strip ── */
113
+ .score-strip {
114
+ display: grid;
115
+ grid-template-columns: repeat(3, 1fr);
116
+ gap: 1px;
117
+ background: var(--border);
118
+ border: 1px solid var(--border);
119
+ border-radius: 10px;
120
+ overflow: hidden;
121
+ margin-bottom: 32px;
122
+ }
123
+
124
+ .score-card {
125
+ background: var(--bg-2);
126
+ padding: 20px 24px;
127
+ display: flex;
128
+ flex-direction: column;
129
+ gap: 6px;
130
+ }
131
+
132
+ .score-card-label {
133
+ font-family: var(--font-mono);
134
+ font-size: 10px;
135
+ font-weight: 500;
136
+ letter-spacing: 0.12em;
137
+ text-transform: uppercase;
138
+ color: var(--text-3);
139
+ }
140
+
141
+ .score-card-value {
142
+ font-family: var(--font-display);
143
+ font-size: 22px;
144
+ font-weight: 700;
145
+ color: var(--text-1);
146
+ }
147
+
148
+ .score-card-sub {
149
+ font-family: var(--font-mono);
150
+ font-size: 11px;
151
+ color: var(--text-2);
152
+ }
153
+
154
+ /* ── Input area ── */
155
+ .input-section {
156
+ margin-bottom: 28px;
157
+ }
158
+
159
+ .input-label {
160
+ font-family: var(--font-mono);
161
+ font-size: 11px;
162
+ font-weight: 500;
163
+ letter-spacing: 0.1em;
164
+ text-transform: uppercase;
165
+ color: var(--text-2);
166
+ margin-bottom: 10px;
167
+ display: block;
168
+ }
169
+
170
+ /* Override Gradio textarea */
171
+ .gradio-container textarea {
172
+ background: var(--bg-2) !important;
173
+ border: 1px solid var(--border) !important;
174
+ border-radius: 8px !important;
175
+ color: var(--text-1) !important;
176
+ font-family: var(--font-mono) !important;
177
+ font-size: 13px !important;
178
+ line-height: 1.7 !important;
179
+ padding: 16px !important;
180
+ resize: vertical !important;
181
+ transition: border-color 0.15s ease !important;
182
+ }
183
+
184
+ .gradio-container textarea:focus {
185
+ border-color: var(--green) !important;
186
+ outline: none !important;
187
+ box-shadow: 0 0 0 3px rgba(63, 185, 80, 0.08) !important;
188
+ }
189
+
190
+ .gradio-container textarea::placeholder {
191
+ color: var(--text-3) !important;
192
+ }
193
+
194
+ /* Labels */
195
+ .gradio-container label span,
196
+ .gradio-container .label-wrap span {
197
+ font-family: var(--font-mono) !important;
198
+ font-size: 11px !important;
199
+ font-weight: 500 !important;
200
+ letter-spacing: 0.1em !important;
201
+ text-transform: uppercase !important;
202
+ color: var(--text-2) !important;
203
+ }
204
+
205
+ /* ── Button ── */
206
+ .gradio-container button.primary {
207
+ background: var(--green) !important;
208
+ color: #000 !important;
209
+ font-family: var(--font-mono) !important;
210
+ font-size: 13px !important;
211
+ font-weight: 500 !important;
212
+ letter-spacing: 0.06em !important;
213
+ border: none !important;
214
+ border-radius: 8px !important;
215
+ padding: 12px 28px !important;
216
+ cursor: pointer !important;
217
+ transition: all 0.15s ease !important;
218
+ width: 100% !important;
219
+ margin-top: 12px !important;
220
+ }
221
+
222
+ .gradio-container button.primary:hover {
223
+ background: #4fc660 !important;
224
+ transform: translateY(-1px) !important;
225
+ box-shadow: 0 4px 16px rgba(63, 185, 80, 0.25) !important;
226
+ }
227
+
228
+ .gradio-container button.primary:active {
229
+ transform: translateY(0) !important;
230
+ }
231
+
232
+ /* ── Output tabs ── */
233
+ .gradio-container .tabs {
234
+ border: 1px solid var(--border) !important;
235
+ border-radius: 10px !important;
236
+ overflow: hidden !important;
237
+ background: var(--bg-1) !important;
238
+ }
239
+
240
+ .gradio-container .tab-nav {
241
+ background: var(--bg-2) !important;
242
+ border-bottom: 1px solid var(--border) !important;
243
+ padding: 0 4px !important;
244
+ gap: 0 !important;
245
+ }
246
+
247
+ .gradio-container .tab-nav button {
248
+ font-family: var(--font-mono) !important;
249
+ font-size: 12px !important;
250
+ font-weight: 400 !important;
251
+ letter-spacing: 0.05em !important;
252
+ color: var(--text-2) !important;
253
+ border: none !important;
254
+ border-bottom: 2px solid transparent !important;
255
+ border-radius: 0 !important;
256
+ padding: 12px 20px !important;
257
+ background: transparent !important;
258
+ transition: color 0.15s !important;
259
+ }
260
+
261
+ .gradio-container .tab-nav button.selected {
262
+ color: var(--green) !important;
263
+ border-bottom-color: var(--green) !important;
264
+ background: transparent !important;
265
+ }
266
+
267
+ .gradio-container .tabitem {
268
+ background: var(--bg-1) !important;
269
+ padding: 20px !important;
270
+ }
271
+
272
+ /* Output textbox */
273
+ .gradio-container .output-textbox textarea,
274
+ .gradio-container .block textarea {
275
+ background: transparent !important;
276
+ border: none !important;
277
+ color: var(--text-1) !important;
278
+ font-family: var(--font-mono) !important;
279
+ font-size: 12.5px !important;
280
+ line-height: 1.75 !important;
281
+ }
282
+
283
+ /* Code block */
284
+ .gradio-container .codemirror-wrapper,
285
+ .gradio-container code {
286
+ background: transparent !important;
287
+ font-family: var(--font-mono) !important;
288
+ font-size: 12.5px !important;
289
+ }
290
+
291
+ /* ── Formula section ── */
292
+ .formula-grid {
293
+ display: grid;
294
+ grid-template-columns: 1fr 1fr;
295
+ gap: 1px;
296
+ background: var(--border);
297
+ border: 1px solid var(--border);
298
+ border-radius: 10px;
299
+ overflow: hidden;
300
+ margin-bottom: 32px;
301
+ }
302
+
303
+ .formula-card {
304
+ background: var(--bg-2);
305
+ padding: 20px 24px;
306
+ }
307
+
308
+ .formula-title {
309
+ font-family: var(--font-display);
310
+ font-size: 13px;
311
+ font-weight: 700;
312
+ color: var(--text-1);
313
+ margin-bottom: 14px;
314
+ display: flex;
315
+ align-items: center;
316
+ gap: 8px;
317
+ }
318
+
319
+ .formula-title .dot {
320
+ width: 7px;
321
+ height: 7px;
322
+ border-radius: 50%;
323
+ background: var(--green);
324
+ flex-shrink: 0;
325
+ }
326
 
327
+ .formula-title .dot.amber { background: var(--amber); }
328
+
329
+ .formula-row {
330
+ display: flex;
331
+ justify-content: space-between;
332
+ align-items: center;
333
+ padding: 5px 0;
334
+ border-bottom: 1px solid var(--bg-3);
335
+ gap: 12px;
336
+ }
337
+
338
+ .formula-row:last-child { border-bottom: none; }
339
+
340
+ .formula-metric {
341
+ font-family: var(--font-mono);
342
+ font-size: 11.5px;
343
+ color: var(--text-2);
344
+ }
345
+
346
+ .formula-weight {
347
+ font-family: var(--font-mono);
348
+ font-size: 11px;
349
+ font-weight: 500;
350
+ color: var(--green);
351
+ background: var(--green-dim);
352
+ padding: 2px 8px;
353
+ border-radius: 4px;
354
+ white-space: nowrap;
355
+ }
356
+
357
+ .formula-weight.amber {
358
+ color: var(--amber);
359
+ background: var(--amber-dim);
360
+ }
361
+
362
+ /* ── Difficulty legend ── */
363
+ .legend-strip {
364
+ display: flex;
365
+ gap: 1px;
366
+ background: var(--border);
367
+ border: 1px solid var(--border);
368
+ border-radius: 10px;
369
+ overflow: hidden;
370
+ margin-bottom: 40px;
371
+ }
372
+
373
+ .legend-item {
374
+ flex: 1;
375
+ background: var(--bg-2);
376
+ padding: 14px 20px;
377
+ display: flex;
378
+ align-items: center;
379
+ gap: 10px;
380
+ }
381
+
382
+ .legend-dot {
383
+ width: 8px;
384
+ height: 8px;
385
+ border-radius: 50%;
386
+ flex-shrink: 0;
387
+ }
388
+
389
+ .legend-text {
390
+ font-family: var(--font-mono);
391
+ font-size: 11.5px;
392
+ color: var(--text-2);
393
+ }
394
+
395
+ .legend-text strong {
396
+ display: block;
397
+ color: var(--text-1);
398
+ font-size: 12px;
399
+ margin-bottom: 2px;
400
+ }
401
+
402
+ /* ── Footer ── */
403
+ .gh-footer {
404
+ margin-top: 56px;
405
+ padding-top: 24px;
406
+ border-top: 1px solid var(--border);
407
+ display: flex;
408
+ justify-content: space-between;
409
+ align-items: center;
410
+ }
411
+
412
+ .gh-footer-text {
413
+ font-family: var(--font-mono);
414
+ font-size: 11px;
415
+ color: var(--text-3);
416
+ }
417
+
418
+ .gh-footer-badge {
419
+ font-family: var(--font-mono);
420
+ font-size: 10px;
421
+ font-weight: 500;
422
+ letter-spacing: 0.1em;
423
+ text-transform: uppercase;
424
+ color: var(--green);
425
+ background: var(--green-dim);
426
+ padding: 4px 10px;
427
+ border-radius: 4px;
428
+ border: 1px solid rgba(63, 185, 80, 0.2);
429
+ }
430
+
431
+ /* ── Scrollbar ── */
432
+ ::-webkit-scrollbar { width: 6px; height: 6px; }
433
+ ::-webkit-scrollbar-track { background: var(--bg-1); }
434
+ ::-webkit-scrollbar-thumb { background: var(--border-bright); border-radius: 3px; }
435
+ ::-webkit-scrollbar-thumb:hover { background: var(--text-3); }
436
+
437
+ /* ── Misc Gradio overrides ── */
438
+ .gradio-container .block {
439
+ background: transparent !important;
440
+ border: none !important;
441
+ padding: 0 !important;
442
+ }
443
+
444
+ .gradio-container .form {
445
+ background: transparent !important;
446
+ border: none !important;
447
+ }
448
+
449
+ .gradio-container footer { display: none !important; }
450
+
451
+ .gradio-container .gap { gap: 16px !important; }
452
+
453
+ /* Spinner */
454
+ .gradio-container .generating {
455
+ border-color: var(--green) !important;
456
+ }
457
+ """
458
+
459
+ HEADER_HTML = """
460
+ <div class="gh-header">
461
+ <div class="gh-header-eyebrow">C2SI · GSoC 2026 · Pre-Task #541</div>
462
+ <h1>Repository <span>Intelligence</span><br>Analyzer</h1>
463
+ <p>Analyze GitHub repositories for activity, complexity, and contributor-facing learning difficulty — powered by the GitHub REST API.</p>
464
+ </div>
465
+ """
466
+
467
+ FORMULA_HTML = """
468
+ <div class="formula-grid">
469
+ <div class="formula-card">
470
+ <div class="formula-title"><span class="dot"></span> Activity Score</div>
471
+ <div class="formula-row"><span class="formula-metric">Commits · last 90 days</span><span class="formula-weight">30%</span></div>
472
+ <div class="formula-row"><span class="formula-metric">Contributor count</span><span class="formula-weight">20%</span></div>
473
+ <div class="formula-row"><span class="formula-metric">Stars (log-scaled)</span><span class="formula-weight">15%</span></div>
474
+ <div class="formula-row"><span class="formula-metric">Forks</span><span class="formula-weight">10%</span></div>
475
+ <div class="formula-row"><span class="formula-metric">Open issues</span><span class="formula-weight">10%</span></div>
476
+ <div class="formula-row"><span class="formula-metric">Recency (days since push)</span><span class="formula-weight">10%</span></div>
477
+ <div class="formula-row"><span class="formula-metric">Release count</span><span class="formula-weight">5%</span></div>
478
+ </div>
479
+ <div class="formula-card">
480
+ <div class="formula-title"><span class="dot amber"></span> Complexity Score</div>
481
+ <div class="formula-row"><span class="formula-metric">File count (log-scaled)</span><span class="formula-weight amber">30%</span></div>
482
+ <div class="formula-row"><span class="formula-metric">Language diversity</span><span class="formula-weight amber">25%</span></div>
483
+ <div class="formula-row"><span class="formula-metric">Dependency files detected</span><span class="formula-weight amber">20%</span></div>
484
+ <div class="formula-row"><span class="formula-metric">Repository size (KB)</span><span class="formula-weight amber">15%</span></div>
485
+ <div class="formula-row"><span class="formula-metric">CI/CD pipeline present</span><span class="formula-weight amber">10%</span></div>
486
+ </div>
487
+ </div>
488
+ <div class="legend-strip">
489
+ <div class="legend-item">
490
+ <span class="legend-dot" style="background:#3fb950"></span>
491
+ <span class="legend-text"><strong>Beginner</strong>Both scores &lt; 50</span>
492
+ </div>
493
+ <div class="legend-item">
494
+ <span class="legend-dot" style="background:#d29922"></span>
495
+ <span class="legend-text"><strong>Intermediate</strong>Avg ≥ 50 or either ≥ 55</span>
496
+ </div>
497
+ <div class="legend-item">
498
+ <span class="legend-dot" style="background:#f85149"></span>
499
+ <span class="legend-text"><strong>Advanced</strong>Complexity ≥ 70 and Activity ≥ 75</span>
500
+ </div>
501
+ </div>
502
+ """
503
+
504
+ FOOTER_HTML = """
505
+ <div class="gh-footer">
506
+ <span class="gh-footer-text">Built with Python · Gradio · GitHub REST API v3</span>
507
+ <span class="gh-footer-badge">GSoC 2026</span>
508
+ </div>
509
+ """
510
+
511
+
512
+ def analyze(repo_input: str):
513
  urls = [u.strip() for u in repo_input.strip().splitlines() if u.strip()]
514
  if not urls:
515
+ return "⚠️ Please enter at least one GitHub repository URL.", "{}"
 
516
  if len(urls) > 10:
517
+ return "⚠️ Maximum 10 repositories per run.", "{}"
518
 
519
  reports = []
520
  full_text = []
521
 
522
  for i, url in enumerate(urls, 1):
523
+ full_text.append(f"── [{i}/{len(urls)}] {url}")
524
  report = analyze_repo(url, client)
525
  reports.append(report)
526
  full_text.append(format_report(report))
527
 
528
  if len(reports) > 1:
 
529
  full_text.append(format_summary_table(reports))
530
 
531
+ full_text.append(f"\n Analyzed {len(reports)} repo(s).")
532
+ return "\n".join(full_text), to_json(reports, indent=2)
 
 
 
 
533
 
534
 
535
+ with gr.Blocks(css=CUSTOM_CSS, title="Repository Intelligence Analyzer") as demo:
 
536
 
537
+ gr.HTML(HEADER_HTML)
 
 
 
538
 
539
+ gr.HTML(FORMULA_HTML)
 
 
 
 
 
 
 
540
 
541
  with gr.Row():
542
+ with gr.Column():
543
  repo_input = gr.Textbox(
544
+ label="GitHub Repository URLs one per line, max 10",
545
+ placeholder="https://github.com/owner/repository",
546
+ lines=7,
547
  value=EXAMPLE_REPOS,
548
  )
549
+ analyze_btn = gr.Button(" Run Analysis", variant="primary")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
550
 
551
  with gr.Tabs():
552
+ with gr.Tab("Report"):
553
  text_output = gr.Textbox(
554
+ label="Analysis Output",
555
+ lines=28,
556
  interactive=False,
557
  )
558
+ with gr.Tab("JSON"):
559
  json_output = gr.Code(
560
  label="Structured JSON",
561
  language="json",
562
+ lines=28,
563
  interactive=False,
564
  )
565
 
 
569
  outputs=[text_output, json_output],
570
  )
571
 
572
+ gr.HTML(FOOTER_HTML)
 
 
 
 
 
 
573
 
574
 
575
  if __name__ == "__main__":
576
+ demo.launch()