openfree commited on
Commit
d38fe40
·
verified ·
1 Parent(s): f949063

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +1261 -15
app.py CHANGED
@@ -192,12 +192,8 @@ def get_user_commit_stats(username):
192
  except:
193
  pass
194
 
195
- # Calculate contribution score
196
- # Weight: Models=3, Spaces=2, Datasets=1, Commits=0.1
197
- score = (items_count["model"] * 3 +
198
- items_count["space"] * 2 +
199
- items_count["dataset"] * 1 +
200
- total_commits * 0.1)
201
 
202
  return {
203
  "username": username,
@@ -782,8 +778,8 @@ with st.sidebar:
782
 
783
  with tab1:
784
  # Show combined trending accounts list with commit-based ranking
785
- st.markdown('<div class="subheader"><h3>🔥 Top 100 Contributors by Score</h3></div>', unsafe_allow_html=True)
786
- st.markdown('<p style="font-size: 0.9rem; color: #666; margin-bottom: 10px;">Ranked by contribution score (Models×3 + Spaces×2 + Datasets×1 + Commits×0.1)</p>', unsafe_allow_html=True)
787
 
788
  # Create a data frame for the table
789
  if user_stats:
@@ -801,15 +797,15 @@ with st.sidebar:
801
 
802
  overall_data.append([
803
  f"{rank_display}{stat['username']}",
804
- f"{stat['score']:.1f}",
805
  str(stat['estimated_commits']),
806
  str(stat['models']),
807
- str(stat['spaces'])
 
808
  ])
809
 
810
  ranking_data_overall = pd.DataFrame(
811
  overall_data,
812
- columns=["Contributor", "Score", "Est. Commits", "Models", "Spaces"]
813
  )
814
  ranking_data_overall.index = ranking_data_overall.index + 1 # Start index from 1 for ranking
815
 
@@ -818,10 +814,10 @@ with st.sidebar:
818
  height=900, # 약 30행 정도 보이도록 픽셀 단위 높이 설정
819
  column_config={
820
  "Contributor": st.column_config.TextColumn("Contributor"),
821
- "Score": st.column_config.TextColumn("Score"),
822
- "Est. Commits": st.column_config.TextColumn("Est. Commits"),
823
  "Models": st.column_config.TextColumn("Models"),
824
- "Spaces": st.column_config.TextColumn("Spaces")
 
825
  },
826
  use_container_width=True,
827
  hide_index=False
@@ -1252,4 +1248,1254 @@ else:
1252
  f'<img src="https://huggingface.co/datasets/huggingface/brand-assets/resolve/main/hf-logo.svg" style="width: 200px; margin-bottom: 30px;">'
1253
  f'<h2>Welcome to Hugging Face Contributions Dashboard</h2>'
1254
  f'<p style="font-size: 1.2rem;">Please select a contributor from the sidebar to view their activity.</p>'
1255
- f'</div>', unsafe_allow_html=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
192
  except:
193
  pass
194
 
195
+ # Calculate contribution score based on commits only
196
+ score = total_commits
 
 
 
 
197
 
198
  return {
199
  "username": username,
 
778
 
779
  with tab1:
780
  # Show combined trending accounts list with commit-based ranking
781
+ st.markdown('<div class="subheader"><h3>🔥 Top 100 Contributors by Commits</h3></div>', unsafe_allow_html=True)
782
+ st.markdown('<p style="font-size: 0.9rem; color: #666; margin-bottom: 10px;">Ranked by total commit count</p>', unsafe_allow_html=True)
783
 
784
  # Create a data frame for the table
785
  if user_stats:
 
797
 
798
  overall_data.append([
799
  f"{rank_display}{stat['username']}",
 
800
  str(stat['estimated_commits']),
801
  str(stat['models']),
802
+ str(stat['spaces']),
803
+ str(stat['datasets'])
804
  ])
805
 
806
  ranking_data_overall = pd.DataFrame(
807
  overall_data,
808
+ columns=["Contributor", "Total Commits", "Models", "Spaces", "Datasets"]
809
  )
810
  ranking_data_overall.index = ranking_data_overall.index + 1 # Start index from 1 for ranking
811
 
 
814
  height=900, # 약 30행 정도 보이도록 픽셀 단위 높이 설정
815
  column_config={
816
  "Contributor": st.column_config.TextColumn("Contributor"),
817
+ "Total Commits": st.column_config.TextColumn("Total Commits"),
 
818
  "Models": st.column_config.TextColumn("Models"),
819
+ "Spaces": st.column_config.TextColumn("Spaces"),
820
+ "Datasets": st.column_config.TextColumn("Datasets")
821
  },
822
  use_container_width=True,
823
  hide_index=False
 
1248
  f'<img src="https://huggingface.co/datasets/huggingface/brand-assets/resolve/main/hf-logo.svg" style="width: 200px; margin-bottom: 30px;">'
1249
  f'<h2>Welcome to Hugging Face Contributions Dashboard</h2>'
1250
  f'<p style="font-size: 1.2rem;">Please select a contributor from the sidebar to view their activity.</p>'
1251
+ f'</div>', unsafe_allow_html=True)import streamlit as st
1252
+ from huggingface_hub import HfApi
1253
+ import pandas as pd
1254
+ import matplotlib.pyplot as plt
1255
+ import seaborn as sns
1256
+ from datetime import datetime
1257
+ from concurrent.futures import ThreadPoolExecutor, as_completed
1258
+ from functools import lru_cache
1259
+ import time
1260
+ import requests
1261
+ from collections import Counter
1262
+ import numpy as np
1263
+
1264
+ st.set_page_config(page_title="HF Contributions", layout="wide", initial_sidebar_state="expanded")
1265
+
1266
+ # 향상된 UI 스타일링
1267
+ st.markdown("""
1268
+ <style>
1269
+ /* 사이드바 스타일링 */
1270
+ [data-testid="stSidebar"] {
1271
+ min-width: 35vw !important;
1272
+ max-width: 35vw !important;
1273
+ background-color: #f8f9fa;
1274
+ padding: 1rem;
1275
+ border-right: 1px solid #e9ecef;
1276
+ }
1277
+
1278
+ /* 헤더 스타일링 */
1279
+ h1, h2, h3 {
1280
+ color: #1e88e5;
1281
+ font-weight: 700;
1282
+ }
1283
+ h1 {
1284
+ font-size: 2.5rem;
1285
+ margin-bottom: 1.5rem;
1286
+ border-bottom: 2px solid #e0e0e0;
1287
+ padding-bottom: 0.5rem;
1288
+ }
1289
+ h2 {
1290
+ font-size: 1.8rem;
1291
+ margin-top: 1.5rem;
1292
+ }
1293
+ h3 {
1294
+ font-size: 1.4rem;
1295
+ margin-top: 1rem;
1296
+ }
1297
+
1298
+ /* 카드 스타일링 */
1299
+ div[data-testid="stMetric"] {
1300
+ background-color: #f1f8fe;
1301
+ border-radius: 10px;
1302
+ padding: 1rem;
1303
+ box-shadow: 0 2px 5px rgba(0,0,0,0.05);
1304
+ margin-bottom: 1rem;
1305
+ }
1306
+
1307
+ /* 차트 컨테이너 스타일링 */
1308
+ .chart-container {
1309
+ background-color: white;
1310
+ border-radius: 10px;
1311
+ padding: 1rem;
1312
+ box-shadow: 0 2px 10px rgba(0,0,0,0.1);
1313
+ margin: 1rem 0;
1314
+ }
1315
+
1316
+ /* 테이블 스타일링 */
1317
+ div[data-testid="stDataFrame"] {
1318
+ background-color: white;
1319
+ border-radius: 10px;
1320
+ padding: 0.5rem;
1321
+ box-shadow: 0 2px 5px rgba(0,0,0,0.05);
1322
+ }
1323
+
1324
+ /* 탭 스타일링 */
1325
+ button[data-baseweb="tab"] {
1326
+ font-weight: 600;
1327
+ }
1328
+
1329
+ /* 서브헤더 배경 */
1330
+ .subheader {
1331
+ background-color: #f1f8fe;
1332
+ padding: 0.5rem 1rem;
1333
+ border-radius: 5px;
1334
+ margin-bottom: 1rem;
1335
+ }
1336
+
1337
+ /* 정보 뱃지 */
1338
+ .info-badge {
1339
+ background-color: #e3f2fd;
1340
+ color: #1976d2;
1341
+ padding: 0.3rem 0.7rem;
1342
+ border-radius: 20px;
1343
+ display: inline-block;
1344
+ font-weight: 500;
1345
+ margin-right: 0.5rem;
1346
+ }
1347
+
1348
+ /* 프로그레스 바 */
1349
+ div[data-testid="stProgress"] {
1350
+ height: 0.5rem !important;
1351
+ }
1352
+
1353
+ /* 버튼 스타일링 */
1354
+ .stButton button {
1355
+ background-color: #1e88e5;
1356
+ color: white;
1357
+ border: none;
1358
+ font-weight: 500;
1359
+ }
1360
+
1361
+ /* 경고/성공 메시지 개선 */
1362
+ div[data-testid="stAlert"] {
1363
+ border-radius: 10px;
1364
+ margin: 1rem 0;
1365
+ }
1366
+
1367
+ /* 카테고리 분석 섹션 */
1368
+ .category-section {
1369
+ background-color: white;
1370
+ border-radius: 10px;
1371
+ padding: 1rem;
1372
+ margin-bottom: 1.5rem;
1373
+ box-shadow: 0 2px 5px rgba(0,0,0,0.05);
1374
+ }
1375
+ </style>
1376
+ """, unsafe_allow_html=True)
1377
+
1378
+ api = HfApi()
1379
+
1380
+ # Cache for API responses
1381
+ @lru_cache(maxsize=1000)
1382
+ def cached_repo_info(repo_id, repo_type):
1383
+ return api.repo_info(repo_id=repo_id, repo_type=repo_type)
1384
+
1385
+ @lru_cache(maxsize=1000)
1386
+ def cached_list_commits(repo_id, repo_type):
1387
+ return list(api.list_repo_commits(repo_id=repo_id, repo_type=repo_type))
1388
+
1389
+ @lru_cache(maxsize=100)
1390
+ def cached_list_items(username, kind):
1391
+ if kind == "model":
1392
+ return list(api.list_models(author=username))
1393
+ elif kind == "dataset":
1394
+ return list(api.list_datasets(author=username))
1395
+ elif kind == "space":
1396
+ return list(api.list_spaces(author=username))
1397
+ return []
1398
+
1399
+ # Rate limiting
1400
+ class RateLimiter:
1401
+ def __init__(self, calls_per_second=10):
1402
+ self.calls_per_second = calls_per_second
1403
+ self.last_call = 0
1404
+
1405
+ def wait(self):
1406
+ current_time = time.time()
1407
+ time_since_last_call = current_time - self.last_call
1408
+ if time_since_last_call < (1.0 / self.calls_per_second):
1409
+ time.sleep((1.0 / self.calls_per_second) - time_since_last_call)
1410
+ self.last_call = time.time()
1411
+
1412
+ rate_limiter = RateLimiter()
1413
+
1414
+ # Function to fetch quick commit stats for a user (optimized for ranking)
1415
+ @st.cache_data(ttl=3600) # Cache for 1 hour
1416
+ def get_user_commit_stats(username):
1417
+ """Fetch basic commit statistics for a user"""
1418
+ try:
1419
+ total_commits = 0
1420
+ items_count = {"model": 0, "dataset": 0, "space": 0}
1421
+
1422
+ for kind in ["model", "dataset", "space"]:
1423
+ try:
1424
+ items = cached_list_items(username, kind)
1425
+ items_count[kind] = len(items)
1426
+
1427
+ # Sample a few repos to estimate commit activity
1428
+ sample_size = min(5, len(items)) # Check up to 5 repos per type
1429
+ if sample_size > 0:
1430
+ sample_items = items[:sample_size]
1431
+ for item in sample_items:
1432
+ try:
1433
+ rate_limiter.wait()
1434
+ commits = cached_list_commits(item.id, kind)
1435
+ total_commits += len(commits)
1436
+ except:
1437
+ pass
1438
+
1439
+ # Estimate total commits based on sample
1440
+ if sample_size < len(items):
1441
+ total_commits = int(total_commits * len(items) / sample_size)
1442
+ except:
1443
+ pass
1444
+
1445
+ # Calculate contribution score based on commits only
1446
+ score = total_commits
1447
+
1448
+ return {
1449
+ "username": username,
1450
+ "models": items_count["model"],
1451
+ "spaces": items_count["space"],
1452
+ "datasets": items_count["dataset"],
1453
+ "estimated_commits": total_commits,
1454
+ "score": score
1455
+ }
1456
+ except Exception as e:
1457
+ return {
1458
+ "username": username,
1459
+ "models": 0,
1460
+ "spaces": 0,
1461
+ "datasets": 0,
1462
+ "estimated_commits": 0,
1463
+ "score": 0
1464
+ }
1465
+
1466
+ # Enhanced function to get trending accounts with commit-based ranking
1467
+ @st.cache_data(ttl=3600) # Cache for 1 hour
1468
+ def get_trending_accounts_with_commits(limit=100):
1469
+ try:
1470
+ # First, get top accounts by model/space count
1471
+ spaces_response = requests.get("https://huggingface.co/api/spaces",
1472
+ params={"limit": 10000},
1473
+ timeout=30)
1474
+ models_response = requests.get("https://huggingface.co/api/models",
1475
+ params={"limit": 10000},
1476
+ timeout=30)
1477
+
1478
+ # Process spaces data
1479
+ top_space_owners = []
1480
+ if spaces_response.status_code == 200:
1481
+ spaces = spaces_response.json()
1482
+ owner_counts_spaces = {}
1483
+ for space in spaces:
1484
+ if '/' in space.get('id', ''):
1485
+ owner, _ = space.get('id', '').split('/', 1)
1486
+ else:
1487
+ owner = space.get('owner', '')
1488
+
1489
+ if owner != 'None':
1490
+ owner_counts_spaces[owner] = owner_counts_spaces.get(owner, 0) + 1
1491
+
1492
+ top_space_owners = sorted(owner_counts_spaces.items(), key=lambda x: x[1], reverse=True)[:limit]
1493
+
1494
+ # Process models data
1495
+ top_model_owners = []
1496
+ if models_response.status_code == 200:
1497
+ models = models_response.json()
1498
+ owner_counts_models = {}
1499
+ for model in models:
1500
+ if '/' in model.get('id', ''):
1501
+ owner, _ = model.get('id', '').split('/', 1)
1502
+ else:
1503
+ owner = model.get('owner', '')
1504
+
1505
+ if owner != 'None':
1506
+ owner_counts_models[owner] = owner_counts_models.get(owner, 0) + 1
1507
+
1508
+ top_model_owners = sorted(owner_counts_models.items(), key=lambda x: x[1], reverse=True)[:limit]
1509
+
1510
+ # Get unique users from top 100 of both lists
1511
+ unique_users = set()
1512
+ for owner, _ in top_space_owners[:100]:
1513
+ unique_users.add(owner)
1514
+ for owner, _ in top_model_owners[:100]:
1515
+ unique_users.add(owner)
1516
+
1517
+ # Create progress bar for fetching commit stats
1518
+ progress_text = st.empty()
1519
+ progress_bar = st.progress(0)
1520
+ progress_text.text(f"Analyzing top contributors... (0/{len(unique_users)})")
1521
+
1522
+ # Fetch commit stats for all unique users
1523
+ user_stats = []
1524
+ with ThreadPoolExecutor(max_workers=5) as executor:
1525
+ future_to_user = {executor.submit(get_user_commit_stats, user): user for user in unique_users}
1526
+ completed = 0
1527
+ for future in as_completed(future_to_user):
1528
+ stats = future.result()
1529
+ if stats["score"] > 0: # Only include users with some activity
1530
+ user_stats.append(stats)
1531
+ completed += 1
1532
+ progress = completed / len(unique_users)
1533
+ progress_bar.progress(progress)
1534
+ progress_text.text(f"Analyzing top contributors... ({completed}/{len(unique_users)})")
1535
+
1536
+ # Clear progress indicators
1537
+ progress_text.empty()
1538
+ progress_bar.empty()
1539
+
1540
+ # Sort by score (combination of commits and repo counts)
1541
+ user_stats.sort(key=lambda x: x["score"], reverse=True)
1542
+
1543
+ # Extract rankings
1544
+ trending_authors = [stat["username"] for stat in user_stats[:limit]]
1545
+
1546
+ # Create detailed rankings for display
1547
+ spaces_rank_data = [(stat["username"], stat["spaces"]) for stat in user_stats if stat["spaces"] > 0][:limit]
1548
+ models_rank_data = [(stat["username"], stat["models"]) for stat in user_stats if stat["models"] > 0][:limit]
1549
+
1550
+ return trending_authors, spaces_rank_data, models_rank_data, user_stats[:limit]
1551
+
1552
+ except Exception as e:
1553
+ st.error(f"Error fetching trending accounts: {str(e)}")
1554
+ fallback_authors = ["ritvik77", "facebook", "google", "stabilityai", "Salesforce", "tiiuae", "bigscience"]
1555
+ fallback_stats = [{"username": author, "models": 0, "spaces": 0, "datasets": 0, "estimated_commits": 0, "score": 0} for author in fallback_authors]
1556
+ return fallback_authors, [(author, 0) for author in fallback_authors], [(author, 0) for author in fallback_authors], fallback_stats
1557
+
1558
+ # Function to fetch commits for a repository (optimized)
1559
+ def fetch_commits_for_repo(repo_id, repo_type, username, selected_year):
1560
+ try:
1561
+ rate_limiter.wait()
1562
+ # Skip private/gated repos upfront
1563
+ repo_info = cached_repo_info(repo_id, repo_type)
1564
+ if repo_info.private or (hasattr(repo_info, 'gated') and repo_info.gated):
1565
+ return [], 0
1566
+
1567
+ # Get initial commit date
1568
+ initial_commit_date = pd.to_datetime(repo_info.created_at).tz_localize(None).date()
1569
+ commit_dates = []
1570
+ commit_count = 0
1571
+
1572
+ # Add initial commit if it's from the selected year
1573
+ if initial_commit_date.year == selected_year:
1574
+ commit_dates.append(initial_commit_date)
1575
+ commit_count += 1
1576
+
1577
+ # Get all commits
1578
+ commits = cached_list_commits(repo_id, repo_type)
1579
+ for commit in commits:
1580
+ commit_date = pd.to_datetime(commit.created_at).tz_localize(None).date()
1581
+ if commit_date.year == selected_year:
1582
+ commit_dates.append(commit_date)
1583
+ commit_count += 1
1584
+
1585
+ return commit_dates, commit_count
1586
+ except Exception as e:
1587
+ return [], 0
1588
+
1589
+ # Function to get commit events for a user (optimized)
1590
+ def get_commit_events(username, kind=None, selected_year=None):
1591
+ commit_dates = []
1592
+ items_with_type = []
1593
+ kinds = [kind] if kind else ["model", "dataset", "space"]
1594
+
1595
+ for k in kinds:
1596
+ try:
1597
+ items = cached_list_items(username, k)
1598
+ items_with_type.extend((item, k) for item in items)
1599
+ repo_ids = [item.id for item in items]
1600
+
1601
+ # Optimized parallel fetch with chunking
1602
+ chunk_size = 5 # Process 5 repos at a time
1603
+ for i in range(0, len(repo_ids), chunk_size):
1604
+ chunk = repo_ids[i:i + chunk_size]
1605
+ with ThreadPoolExecutor(max_workers=min(5, len(chunk))) as executor:
1606
+ future_to_repo = {
1607
+ executor.submit(fetch_commits_for_repo, repo_id, k, username, selected_year): repo_id
1608
+ for repo_id in chunk
1609
+ }
1610
+ for future in as_completed(future_to_repo):
1611
+ repo_commits, repo_count = future.result()
1612
+ if repo_commits: # Only extend if we got commits
1613
+ commit_dates.extend(repo_commits)
1614
+ except Exception as e:
1615
+ st.warning(f"Error fetching {k}s for {username}: {str(e)}")
1616
+
1617
+ # Create DataFrame with all commits
1618
+ df = pd.DataFrame(commit_dates, columns=["date"])
1619
+ if not df.empty:
1620
+ df = df.drop_duplicates() # Remove any duplicate dates
1621
+ return df, items_with_type
1622
+
1623
+ # Calendar heatmap function (optimized)
1624
+ def make_calendar_heatmap(df, title, year):
1625
+ if df.empty:
1626
+ st.info(f"No {title.lower()} found for {year}.")
1627
+ return
1628
+
1629
+ # Optimize DataFrame operations
1630
+ df["count"] = 1
1631
+ df = df.groupby("date", as_index=False).sum()
1632
+ df["date"] = pd.to_datetime(df["date"])
1633
+
1634
+ # Create date range more efficiently
1635
+ start = pd.Timestamp(f"{year}-01-01")
1636
+ end = pd.Timestamp(f"{year}-12-31")
1637
+ all_days = pd.date_range(start=start, end=end)
1638
+
1639
+ # Optimize DataFrame creation and merging
1640
+ heatmap_data = pd.DataFrame({"date": all_days, "count": 0})
1641
+ heatmap_data = heatmap_data.merge(df, on="date", how="left", suffixes=("", "_y"))
1642
+ heatmap_data["count"] = heatmap_data["count_y"].fillna(0)
1643
+ heatmap_data = heatmap_data.drop("count_y", axis=1)
1644
+
1645
+ # Calculate week and day of week more efficiently
1646
+ heatmap_data["dow"] = heatmap_data["date"].dt.dayofweek
1647
+ heatmap_data["week"] = (heatmap_data["date"] - start).dt.days // 7
1648
+
1649
+ # Create pivot table more efficiently
1650
+ pivot = heatmap_data.pivot(index="dow", columns="week", values="count").fillna(0)
1651
+
1652
+ # Optimize month labels calculation
1653
+ month_labels = pd.date_range(start, end, freq="MS").strftime("%b")
1654
+ month_positions = pd.date_range(start, end, freq="MS").map(lambda x: (x - start).days // 7)
1655
+
1656
+ # Create custom colormap with specific boundaries
1657
+ from matplotlib.colors import ListedColormap, BoundaryNorm
1658
+ colors = ['#ebedf0', '#9be9a8', '#40c463', '#30a14e', '#216e39'] # GitHub-style green colors
1659
+ bounds = [0, 1, 3, 11, 31, float('inf')] # Boundaries for color transitions
1660
+ cmap = ListedColormap(colors)
1661
+ norm = BoundaryNorm(bounds, cmap.N)
1662
+
1663
+ # Create plot more efficiently
1664
+ fig, ax = plt.subplots(figsize=(12, 1.5))
1665
+
1666
+ # Convert pivot values to integers to ensure proper color mapping
1667
+ pivot_int = pivot.astype(int)
1668
+
1669
+ # Create heatmap with explicit vmin and vmax
1670
+ sns.heatmap(pivot_int, ax=ax, cmap=cmap, norm=norm, linewidths=0.5, linecolor="white",
1671
+ square=True, cbar=False, yticklabels=["M", "T", "W", "T", "F", "S", "S"])
1672
+
1673
+ ax.set_title(f"{title}", fontsize=14, pad=10)
1674
+ ax.set_xlabel("")
1675
+ ax.set_ylabel("")
1676
+ ax.set_xticks(month_positions)
1677
+ ax.set_xticklabels(month_labels, fontsize=10)
1678
+ ax.set_yticklabels(ax.get_yticklabels(), rotation=0, fontsize=10)
1679
+
1680
+ # 시각적 향상을 위한 figure 스타일링
1681
+ fig.tight_layout()
1682
+ fig.patch.set_facecolor('#F8F9FA')
1683
+
1684
+ st.pyplot(fig)
1685
+
1686
+ # Function to create a fancy contribution radar chart
1687
+ def create_contribution_radar(username, models_count, spaces_count, datasets_count, commits_count):
1688
+ # Create radar chart for contribution metrics
1689
+ categories = ['Models', 'Spaces', 'Datasets', 'Activity']
1690
+ values = [models_count, spaces_count, datasets_count, commits_count]
1691
+
1692
+ # Normalize values for better visualization
1693
+ max_vals = [100, 100, 50, 500] # Reasonable max values for each category
1694
+ normalized = [min(v/m, 1.0) for v, m in zip(values, max_vals)]
1695
+
1696
+ # Create radar chart
1697
+ angles = np.linspace(0, 2*np.pi, len(categories), endpoint=False).tolist()
1698
+ angles += angles[:1] # Close the loop
1699
+
1700
+ normalized += normalized[:1] # Close the loop
1701
+
1702
+ fig, ax = plt.subplots(figsize=(6, 6), subplot_kw={'polar': True}, facecolor='#F8F9FA')
1703
+
1704
+ # Add background grid with improved styling
1705
+ ax.set_theta_offset(np.pi / 2)
1706
+ ax.set_theta_direction(-1)
1707
+ ax.set_thetagrids(np.degrees(angles[:-1]), categories, fontsize=12, fontweight='bold')
1708
+
1709
+ # 그리드 스타일링 개선
1710
+ ax.grid(color='#CCCCCC', linestyle='-', linewidth=0.5, alpha=0.7)
1711
+
1712
+ # Draw the chart with improved color scheme
1713
+ ax.fill(angles, normalized, color='#4CAF50', alpha=0.25)
1714
+ ax.plot(angles, normalized, color='#4CAF50', linewidth=3)
1715
+
1716
+ # Add value labels with improved styling
1717
+ for i, val in enumerate(values):
1718
+ angle = angles[i]
1719
+ x = (normalized[i] + 0.1) * np.cos(angle)
1720
+ y = (normalized[i] + 0.1) * np.sin(angle)
1721
+ ax.text(angle, normalized[i] + 0.1, str(val),
1722
+ ha='center', va='center', fontsize=12,
1723
+ fontweight='bold', color='#1976D2')
1724
+
1725
+ # Add highlight circles
1726
+ circles = [0.25, 0.5, 0.75, 1.0]
1727
+ for circle in circles:
1728
+ ax.plot(angles, [circle] * len(angles), color='gray', alpha=0.3, linewidth=0.5, linestyle='--')
1729
+
1730
+ ax.set_title(f"{username}'s Contribution Profile", fontsize=16, pad=20, fontweight='bold')
1731
+
1732
+ # 배경 원 없애기
1733
+ ax.set_facecolor('#F8F9FA')
1734
+
1735
+ return fig
1736
+
1737
+ # Function to create contribution distribution pie chart
1738
+ def create_contribution_pie(model_commits, dataset_commits, space_commits):
1739
+ labels = ['Models', 'Datasets', 'Spaces']
1740
+ sizes = [model_commits, dataset_commits, space_commits]
1741
+
1742
+ # Filter out zero values
1743
+ filtered_labels = [label for label, size in zip(labels, sizes) if size > 0]
1744
+ filtered_sizes = [size for size in sizes if size > 0]
1745
+
1746
+ if not filtered_sizes:
1747
+ return None # No data to show
1748
+
1749
+ # Use a more attractive color scheme
1750
+ colors = ['#FF9800', '#2196F3', '#4CAF50']
1751
+ filtered_colors = [color for color, size in zip(colors, sizes) if size > 0]
1752
+
1753
+ fig, ax = plt.subplots(figsize=(7, 7), facecolor='#F8F9FA')
1754
+
1755
+ # Create exploded pie chart with improved styling
1756
+ explode = [0.1] * len(filtered_sizes) # Explode all slices for better visualization
1757
+
1758
+ wedges, texts, autotexts = ax.pie(
1759
+ filtered_sizes,
1760
+ labels=None, # We'll add custom labels
1761
+ colors=filtered_colors,
1762
+ autopct='%1.1f%%',
1763
+ startangle=90,
1764
+ shadow=True,
1765
+ explode=explode,
1766
+ textprops={'fontsize': 14, 'weight': 'bold'},
1767
+ wedgeprops={'edgecolor': 'white', 'linewidth': 2}
1768
+ )
1769
+
1770
+ # Customize the percentage text
1771
+ for autotext in autotexts:
1772
+ autotext.set_color('white')
1773
+ autotext.set_fontsize(12)
1774
+ autotext.set_weight('bold')
1775
+
1776
+ # Add legend with custom styling
1777
+ ax.legend(
1778
+ wedges,
1779
+ [f"{label} ({size})" for label, size in zip(filtered_labels, filtered_sizes)],
1780
+ title="Contribution Types",
1781
+ loc="center left",
1782
+ bbox_to_anchor=(0.85, 0.5),
1783
+ fontsize=12
1784
+ )
1785
+
1786
+ ax.set_title('Distribution of Contributions by Type', fontsize=16, pad=20, fontweight='bold')
1787
+ ax.axis('equal') # Equal aspect ratio ensures that pie is drawn as a circle
1788
+
1789
+ return fig
1790
+
1791
+ # Function to create monthly activity chart
1792
+ def create_monthly_activity(df, year):
1793
+ if df.empty:
1794
+ return None
1795
+
1796
+ # Aggregate by month
1797
+ df['date'] = pd.to_datetime(df['date'])
1798
+ df['month'] = df['date'].dt.month
1799
+ df['month_name'] = df['date'].dt.strftime('%b')
1800
+
1801
+ # Count by month and ensure all months are present
1802
+ month_order = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
1803
+ counts_by_month = df.groupby('month_name')['date'].count()
1804
+ monthly_counts = pd.Series([counts_by_month.get(m, 0) for m in month_order], index=month_order)
1805
+
1806
+ # Create bar chart with improved styling
1807
+ fig, ax = plt.subplots(figsize=(14, 6), facecolor='#F8F9FA')
1808
+
1809
+ # Create bars with gradient colors based on activity level
1810
+ norm = plt.Normalize(0, monthly_counts.max() if monthly_counts.max() > 0 else 1)
1811
+ colors = plt.cm.viridis(norm(monthly_counts.values))
1812
+
1813
+ bars = ax.bar(monthly_counts.index, monthly_counts.values, color=colors, width=0.7)
1814
+
1815
+ # Highlight the month with most activity
1816
+ if monthly_counts.max() > 0:
1817
+ max_idx = monthly_counts.argmax()
1818
+ bars[max_idx].set_color('#FF5722')
1819
+ bars[max_idx].set_edgecolor('black')
1820
+ bars[max_idx].set_linewidth(1.5)
1821
+
1822
+ # Add labels and styling with enhanced design
1823
+ ax.set_title(f'Monthly Activity in {year}', fontsize=18, pad=20, fontweight='bold')
1824
+ ax.set_xlabel('Month', fontsize=14, labelpad=10)
1825
+ ax.set_ylabel('Number of Contributions', fontsize=14, labelpad=10)
1826
+
1827
+ # Add value labels on top of bars with improved styling
1828
+ for i, count in enumerate(monthly_counts.values):
1829
+ if count > 0:
1830
+ ax.text(i, count + 0.5, str(int(count)), ha='center', fontsize=12, fontweight='bold')
1831
+
1832
+ # Add grid for better readability with improved styling
1833
+ ax.grid(axis='y', linestyle='--', alpha=0.7, color='#CCCCCC')
1834
+ ax.set_axisbelow(True) # Grid lines behind bars
1835
+
1836
+ # Style the chart borders and background
1837
+ ax.spines['top'].set_visible(False)
1838
+ ax.spines['right'].set_visible(False)
1839
+ ax.spines['left'].set_linewidth(0.5)
1840
+ ax.spines['bottom'].set_linewidth(0.5)
1841
+
1842
+ # Adjust tick parameters for better look
1843
+ ax.tick_params(axis='x', labelsize=12, pad=5)
1844
+ ax.tick_params(axis='y', labelsize=12, pad=5)
1845
+
1846
+ plt.tight_layout()
1847
+
1848
+ return fig
1849
+
1850
+ # Function to render follower growth simulation
1851
+ def simulate_follower_data(username, spaces_count, models_count, total_commits):
1852
+ # Simulate follower growth based on contribution metrics
1853
+ # This is just a simulation for visual purposes
1854
+ import numpy as np
1855
+ from datetime import timedelta
1856
+
1857
+ # Start with a base number of followers proportional to contribution metrics
1858
+ base_followers = max(10, int((spaces_count * 2 + models_count * 3 + total_commits/10) / 6))
1859
+
1860
+ # Generate timestamps for the past year
1861
+ end_date = datetime.now()
1862
+ start_date = end_date - timedelta(days=365)
1863
+ dates = pd.date_range(start=start_date, end=end_date, freq='W') # Weekly data points
1864
+
1865
+ # Generate follower growth with some randomness
1866
+ followers = []
1867
+ current = base_followers / 2 # Start from half the base
1868
+
1869
+ for i in range(len(dates)):
1870
+ growth_factor = 1 + (np.random.random() * 0.1) # Random growth between 0% and 10%
1871
+ current = current * growth_factor
1872
+ followers.append(int(current))
1873
+
1874
+ # Ensure end value matches our base_followers estimate
1875
+ followers[-1] = base_followers
1876
+
1877
+ # Create the chart with improved styling
1878
+ fig, ax = plt.subplots(figsize=(14, 6), facecolor='#F8F9FA')
1879
+
1880
+ # Create gradient line for better visualization
1881
+ points = np.array([dates, followers]).T.reshape(-1, 1, 2)
1882
+ segments = np.concatenate([points[:-1], points[1:]], axis=1)
1883
+
1884
+ from matplotlib.collections import LineCollection
1885
+ norm = plt.Normalize(0, len(segments))
1886
+ lc = LineCollection(segments, cmap='viridis', norm=norm, linewidth=3, alpha=0.8)
1887
+ lc.set_array(np.arange(len(segments)))
1888
+ line = ax.add_collection(lc)
1889
+
1890
+ # Add markers
1891
+ ax.scatter(dates, followers, s=50, color='#9C27B0', alpha=0.8, zorder=10)
1892
+
1893
+ # Add styling with enhanced design
1894
+ ax.set_title(f"Estimated Follower Growth for {username}", fontsize=18, pad=20, fontweight='bold')
1895
+ ax.set_xlabel("Date", fontsize=14, labelpad=10)
1896
+ ax.set_ylabel("Followers", fontsize=14, labelpad=10)
1897
+
1898
+ # Format the axes limits
1899
+ ax.set_xlim(dates.min(), dates.max())
1900
+ ax.set_ylim(0, max(followers) * 1.1)
1901
+
1902
+ # Add grid for better readability with improved styling
1903
+ ax.grid(True, linestyle='--', alpha=0.7, color='#CCCCCC')
1904
+ ax.set_axisbelow(True) # Grid lines behind plot
1905
+
1906
+ # Style the chart borders and background
1907
+ ax.spines['top'].set_visible(False)
1908
+ ax.spines['right'].set_visible(False)
1909
+ ax.spines['left'].set_linewidth(0.5)
1910
+ ax.spines['bottom'].set_linewidth(0.5)
1911
+
1912
+ # Adjust tick parameters for better look
1913
+ ax.tick_params(axis='x', labelsize=12, rotation=45)
1914
+ ax.tick_params(axis='y', labelsize=12)
1915
+
1916
+ # Add annotations for start and end points
1917
+ ax.annotate(f"Start: {followers[0]}",
1918
+ xy=(dates[0], followers[0]),
1919
+ xytext=(10, 10),
1920
+ textcoords='offset points',
1921
+ fontsize=12,
1922
+ fontweight='bold',
1923
+ color='#9C27B0',
1924
+ bbox=dict(boxstyle="round,pad=0.3", fc="#F3E5F5", ec="#9C27B0", alpha=0.8))
1925
+
1926
+ ax.annotate(f"Current: {followers[-1]}",
1927
+ xy=(dates[-1], followers[-1]),
1928
+ xytext=(-10, 10),
1929
+ textcoords='offset points',
1930
+ fontsize=12,
1931
+ fontweight='bold',
1932
+ color='#9C27B0',
1933
+ ha='right',
1934
+ bbox=dict(boxstyle="round,pad=0.3", fc="#F3E5F5", ec="#9C27B0", alpha=0.8))
1935
+
1936
+ plt.tight_layout()
1937
+
1938
+ return fig
1939
+
1940
+ # Function to create ranking position visualization
1941
+ def create_ranking_chart(username, overall_rank, spaces_rank, models_rank):
1942
+ if not (overall_rank or spaces_rank or models_rank):
1943
+ return None
1944
+
1945
+ # Create a horizontal bar chart for rankings with improved styling
1946
+ fig, ax = plt.subplots(figsize=(12, 5), facecolor='#F8F9FA')
1947
+
1948
+ categories = []
1949
+ positions = []
1950
+ colors = []
1951
+ rank_values = []
1952
+
1953
+ if overall_rank:
1954
+ categories.append('Overall')
1955
+ positions.append(101 - overall_rank) # Invert rank for visualization (higher is better)
1956
+ colors.append('#673AB7')
1957
+ rank_values.append(overall_rank)
1958
+
1959
+ if spaces_rank:
1960
+ categories.append('Spaces')
1961
+ positions.append(101 - spaces_rank)
1962
+ colors.append('#2196F3')
1963
+ rank_values.append(spaces_rank)
1964
+
1965
+ if models_rank:
1966
+ categories.append('Models')
1967
+ positions.append(101 - models_rank)
1968
+ colors.append('#FF9800')
1969
+ rank_values.append(models_rank)
1970
+
1971
+ # Create horizontal bars with enhanced styling
1972
+ bars = ax.barh(categories, positions, color=colors, alpha=0.8, height=0.6,
1973
+ edgecolor='white', linewidth=1.5)
1974
+
1975
+ # Add rank values as text with improved styling
1976
+ for i, bar in enumerate(bars):
1977
+ ax.text(bar.get_width() + 2, bar.get_y() + bar.get_height()/2,
1978
+ f'Rank #{rank_values[i]}', va='center', fontsize=12,
1979
+ fontweight='bold', color=colors[i])
1980
+
1981
+ # Set chart properties with enhanced styling
1982
+ ax.set_xlim(0, 105)
1983
+ ax.set_title(f"Ranking Positions for {username} (Top 100)", fontsize=18, pad=20, fontweight='bold')
1984
+ ax.set_xlabel("Percentile (higher is better)", fontsize=14, labelpad=10)
1985
+
1986
+ # Add explanatory text
1987
+ ax.text(50, -0.6, "← Lower rank (higher number) | Higher rank (lower number) →",
1988
+ ha='center', va='center', fontsize=10, fontweight='bold', color='#666666')
1989
+
1990
+ # Add a vertical line at 90th percentile to highlight top 10 with improved styling
1991
+ ax.axvline(x=90, color='#FF5252', linestyle='--', alpha=0.7, linewidth=2)
1992
+ ax.text(92, len(categories)/2, 'Top 10', color='#D32F2F', fontsize=12,
1993
+ rotation=90, va='center', fontweight='bold')
1994
+
1995
+ # Style the chart borders and background
1996
+ ax.spines['top'].set_visible(False)
1997
+ ax.spines['right'].set_visible(False)
1998
+ ax.spines['left'].set_linewidth(0.5)
1999
+ ax.spines['bottom'].set_linewidth(0.5)
2000
+
2001
+ # Adjust tick parameters for better look
2002
+ ax.tick_params(axis='x', labelsize=12)
2003
+ ax.tick_params(axis='y', labelsize=14, pad=5)
2004
+
2005
+ # Add grid for better readability
2006
+ ax.grid(axis='x', linestyle='--', alpha=0.5, color='#CCCCCC')
2007
+ ax.set_axisbelow(True) # Grid lines behind bars
2008
+
2009
+ # Invert x-axis to show ranking position more intuitively
2010
+ ax.invert_xaxis()
2011
+
2012
+ plt.tight_layout()
2013
+ return fig
2014
+
2015
+ # Fetch trending accounts with a loading spinner (do this once at the beginning)
2016
+ with st.spinner("Loading and analyzing top contributors... This may take a few moments."):
2017
+ trending_accounts, top_owners_spaces, top_owners_models, user_stats = get_trending_accounts_with_commits(limit=100)
2018
+
2019
+ # Sidebar
2020
+ with st.sidebar:
2021
+ st.markdown('<h1 style="text-align: center; color: #1E88E5;">👤 Contributor</h1>', unsafe_allow_html=True)
2022
+
2023
+ # Create tabs for rankings
2024
+ tab1, tab2 = st.tabs([
2025
+ "Top 100 Overall",
2026
+ "Top Spaces & Models"
2027
+ ])
2028
+
2029
+ with tab1:
2030
+ # Show combined trending accounts list with commit-based ranking
2031
+ st.markdown('<div class="subheader"><h3>🔥 Top 100 Contributors by Commits</h3></div>', unsafe_allow_html=True)
2032
+ st.markdown('<p style="font-size: 0.9rem; color: #666; margin-bottom: 10px;">Ranked by total commit count</p>', unsafe_allow_html=True)
2033
+
2034
+ # Create a data frame for the table
2035
+ if user_stats:
2036
+ # Create the overall ranking dataframe with trophies for top 3
2037
+ overall_data = []
2038
+ for idx, stat in enumerate(user_stats[:100]):
2039
+ # Add trophy emojis for top 3
2040
+ rank_display = ""
2041
+ if idx == 0:
2042
+ rank_display = "🏆 " # Gold trophy for 1st place
2043
+ elif idx == 1:
2044
+ rank_display = "🏆 " # Silver trophy for 2nd place
2045
+ elif idx == 2:
2046
+ rank_display = "🏆 " # Bronze trophy for 3rd place
2047
+
2048
+ overall_data.append([
2049
+ f"{rank_display}{stat['username']}",
2050
+ str(stat['estimated_commits']),
2051
+ str(stat['models']),
2052
+ str(stat['spaces']),
2053
+ str(stat['datasets'])
2054
+ ])
2055
+
2056
+ ranking_data_overall = pd.DataFrame(
2057
+ overall_data,
2058
+ columns=["Contributor", "Total Commits", "Models", "Spaces", "Datasets"]
2059
+ )
2060
+ ranking_data_overall.index = ranking_data_overall.index + 1 # Start index from 1 for ranking
2061
+
2062
+ st.dataframe(
2063
+ ranking_data_overall,
2064
+ height=900, # 약 30행 정도 보이도록 픽셀 단위 높이 설정
2065
+ column_config={
2066
+ "Contributor": st.column_config.TextColumn("Contributor"),
2067
+ "Total Commits": st.column_config.TextColumn("Total Commits"),
2068
+ "Models": st.column_config.TextColumn("Models"),
2069
+ "Spaces": st.column_config.TextColumn("Spaces"),
2070
+ "Datasets": st.column_config.TextColumn("Datasets")
2071
+ },
2072
+ use_container_width=True,
2073
+ hide_index=False
2074
+ )
2075
+
2076
+ with tab2:
2077
+ # Show trending accounts by Spaces & Models
2078
+ st.markdown('<div class="subheader"><h3>🚀 Spaces Leaders</h3></div>', unsafe_allow_html=True)
2079
+
2080
+ # Create a data frame for the Spaces table with medals for top 3
2081
+ if top_owners_spaces:
2082
+ spaces_data = []
2083
+ for idx, (owner, count) in enumerate(top_owners_spaces[:50]):
2084
+ # Add medal emojis for top 3
2085
+ rank_display = ""
2086
+ if idx == 0:
2087
+ rank_display = "🥇 " # Gold medal for 1st place
2088
+ elif idx == 1:
2089
+ rank_display = "🥈 " # Silver medal for 2nd place
2090
+ elif idx == 2:
2091
+ rank_display = "🥉 " # Bronze medal for 3rd place
2092
+
2093
+ spaces_data.append([f"{rank_display}{owner}", count])
2094
+
2095
+ ranking_data_spaces = pd.DataFrame(spaces_data, columns=["Contributor", "Spaces Count"])
2096
+ ranking_data_spaces.index = ranking_data_spaces.index + 1 # Start index from 1 for ranking
2097
+
2098
+ st.dataframe(
2099
+ ranking_data_spaces,
2100
+ column_config={
2101
+ "Contributor": st.column_config.TextColumn("Contributor"),
2102
+ "Spaces Count": st.column_config.NumberColumn("Spaces Count", format="%d")
2103
+ },
2104
+ use_container_width=True,
2105
+ hide_index=False
2106
+ )
2107
+
2108
+ # Display the top Models accounts list with medals for top 3
2109
+ st.markdown('<div class="subheader"><h3>🧠 Models Leaders</h3></div>', unsafe_allow_html=True)
2110
+
2111
+ # Create a data frame for the Models table with medals for top 3
2112
+ if top_owners_models:
2113
+ models_data = []
2114
+ for idx, (owner, count) in enumerate(top_owners_models[:50]):
2115
+ # Add medal emojis for top 3
2116
+ rank_display = ""
2117
+ if idx == 0:
2118
+ rank_display = "🥇 " # Gold medal for 1st place
2119
+ elif idx == 1:
2120
+ rank_display = "🥈 " # Silver medal for 2nd place
2121
+ elif idx == 2:
2122
+ rank_display = "🥉 " # Bronze medal for 3rd place
2123
+
2124
+ models_data.append([f"{rank_display}{owner}", count])
2125
+
2126
+ ranking_data_models = pd.DataFrame(models_data, columns=["Contributor", "Models Count"])
2127
+ ranking_data_models.index = ranking_data_models.index + 1 # Start index from 1 for ranking
2128
+
2129
+ st.dataframe(
2130
+ ranking_data_models,
2131
+ column_config={
2132
+ "Contributor": st.column_config.TextColumn("Contributor"),
2133
+ "Models Count": st.column_config.NumberColumn("Models Count", format="%d")
2134
+ },
2135
+ use_container_width=True,
2136
+ hide_index=False
2137
+ )
2138
+
2139
+ # Add visual divider
2140
+ st.markdown('<hr style="margin: 2rem 0; border-color: #e0e0e0;">', unsafe_allow_html=True)
2141
+
2142
+ # Display contributor selection with enhanced styling
2143
+ st.markdown('<div class="subheader"><h3>Select Contributor</h3></div>', unsafe_allow_html=True)
2144
+ selected_trending = st.selectbox(
2145
+ "Choose from trending accounts",
2146
+ options=trending_accounts[:100], # Limit to top 100
2147
+ index=0 if trending_accounts else None,
2148
+ key="trending_selectbox"
2149
+ )
2150
+
2151
+ # Custom account input option with enhanced styling
2152
+ st.markdown('<div style="text-align: center; margin: 15px 0; font-weight: bold;">- OR -</div>', unsafe_allow_html=True)
2153
+ custom = st.text_input("Enter a username/organization:", placeholder="e.g. facebook, google...")
2154
+
2155
+ # Add visual divider
2156
+ st.markdown('<hr style="margin: 1.5rem 0; border-color: #e0e0e0;">', unsafe_allow_html=True)
2157
+
2158
+ # Set username based on selection or custom input
2159
+ if custom.strip():
2160
+ username = custom.strip()
2161
+ elif selected_trending:
2162
+ username = selected_trending
2163
+ else:
2164
+ username = "facebook" # Default fallback
2165
+
2166
+ # Year selection with enhanced styling
2167
+ st.markdown('<div class="subheader"><h3>🗓️ Time Period</h3></div>', unsafe_allow_html=True)
2168
+ year_options = list(range(datetime.now().year, 2017, -1))
2169
+ selected_year = st.selectbox("Select Year:", options=year_options)
2170
+
2171
+ # Additional options for customization with enhanced styling
2172
+ st.markdown('<div class="subheader"><h3>⚙️ Display Options</h3></div>', unsafe_allow_html=True)
2173
+ show_models = st.checkbox("Show Models", value=True)
2174
+ show_datasets = st.checkbox("Show Datasets", value=True)
2175
+ show_spaces = st.checkbox("Show Spaces", value=True)
2176
+
2177
+ # Main Content
2178
+ st.markdown(f'<h1 style="text-align: center; color: #1E88E5; margin-bottom: 2rem;">🤗 Hugging Face Contributions</h1>', unsafe_allow_html=True)
2179
+
2180
+ if username:
2181
+ # Find user's stats in the pre-calculated data
2182
+ user_stat = next((stat for stat in user_stats if stat["username"] == username), None)
2183
+
2184
+ # Create a header card with contributor info
2185
+ header_col1, header_col2 = st.columns([1, 2])
2186
+ with header_col1:
2187
+ score_display = f"Score: {user_stat['score']:.1f}" if user_stat else "Score: N/A"
2188
+ st.markdown(f'<div style="background-color: #E3F2FD; padding: 20px; border-radius: 10px; border-left: 5px solid #1E88E5;">'
2189
+ f'<h2 style="color: #1E88E5;">👤 {username}</h2>'
2190
+ f'<p style="font-size: 16px;">Analyzing contributions for {selected_year}</p>'
2191
+ f'<p style="font-size: 14px; font-weight: bold;">{score_display}</p>'
2192
+ f'<p><a href="https://huggingface.co/{username}" target="_blank" style="color: #1E88E5; font-weight: bold;">View Profile</a></p>'
2193
+ f'</div>', unsafe_allow_html=True)
2194
+
2195
+ with header_col2:
2196
+ # Add explanation about the app
2197
+ st.markdown(f'<div style="background-color: #F3E5F5; padding: 20px; border-radius: 10px; border-left: 5px solid #9C27B0;">'
2198
+ f'<h3 style="color: #9C27B0;">About This Analysis</h3>'
2199
+ f'<p>This dashboard analyzes {username}\'s contributions to Hugging Face in {selected_year}, including models, datasets, and spaces.</p>'
2200
+ f'<p style="font-style: italic; font-size: 12px;">* Rankings are based on contribution scores combining repos and commit activity.</p>'
2201
+ f'</div>', unsafe_allow_html=True)
2202
+
2203
+ with st.spinner(f"Fetching detailed contribution data for {username}..."):
2204
+ # Initialize variables for tracking
2205
+ overall_rank = None
2206
+ spaces_rank = None
2207
+ models_rank = None
2208
+ spaces_count = 0
2209
+ models_count = 0
2210
+ datasets_count = 0
2211
+
2212
+ # Display contributor rank if in top 100
2213
+ if username in trending_accounts[:100]:
2214
+ overall_rank = trending_accounts.index(username) + 1
2215
+
2216
+ # Create a prominent ranking display
2217
+ st.markdown(f'<div style="background-color: #FFF8E1; padding: 20px; border-radius: 10px; border-left: 5px solid #FFC107; margin: 1rem 0;">'
2218
+ f'<h2 style="color: #FFA000; text-align: center;">🏆 Ranked #{overall_rank} in Top Contributors</h2>'
2219
+ f'</div>', unsafe_allow_html=True)
2220
+
2221
+ # Find user in spaces ranking
2222
+ for i, (owner, count) in enumerate(top_owners_spaces):
2223
+ if owner == username:
2224
+ spaces_rank = i+1
2225
+ spaces_count = count
2226
+ break
2227
+
2228
+ # Find user in models ranking
2229
+ for i, (owner, count) in enumerate(top_owners_models):
2230
+ if owner == username:
2231
+ models_rank = i+1
2232
+ models_count = count
2233
+ break
2234
+
2235
+ # Display ranking visualization
2236
+ rank_chart = create_ranking_chart(username, overall_rank, spaces_rank, models_rank)
2237
+ if rank_chart:
2238
+ st.pyplot(rank_chart)
2239
+
2240
+ # Create a dictionary to store commits by type
2241
+ commits_by_type = {}
2242
+ commit_counts_by_type = {}
2243
+
2244
+ # Determine which types to fetch based on checkboxes
2245
+ types_to_fetch = []
2246
+ if show_models:
2247
+ types_to_fetch.append("model")
2248
+ if show_datasets:
2249
+ types_to_fetch.append("dataset")
2250
+ if show_spaces:
2251
+ types_to_fetch.append("space")
2252
+
2253
+ if not types_to_fetch:
2254
+ st.warning("Please select at least one content type to display (Models, Datasets, or Spaces)")
2255
+ st.stop()
2256
+
2257
+ # Create a progress container
2258
+ progress_container = st.container()
2259
+ progress_container.markdown('<h3 style="color: #1E88E5;">Fetching Repository Data...</h3>', unsafe_allow_html=True)
2260
+ progress_bar = progress_container.progress(0)
2261
+
2262
+ # Fetch commits for each selected type
2263
+ for type_index, kind in enumerate(types_to_fetch):
2264
+ try:
2265
+ items = cached_list_items(username, kind)
2266
+
2267
+ # Update counts for radar chart
2268
+ if kind == "model":
2269
+ models_count = len(items)
2270
+ elif kind == "dataset":
2271
+ datasets_count = len(items)
2272
+ elif kind == "space":
2273
+ spaces_count = len(items)
2274
+
2275
+ repo_ids = [item.id for item in items]
2276
+
2277
+ progress_container.info(f"Found {len(repo_ids)} {kind}s for {username}")
2278
+
2279
+ # Process repos in chunks
2280
+ chunk_size = 5
2281
+ total_commits = 0
2282
+ all_commit_dates = []
2283
+
2284
+ for i in range(0, len(repo_ids), chunk_size):
2285
+ chunk = repo_ids[i:i + chunk_size]
2286
+ with ThreadPoolExecutor(max_workers=min(5, len(chunk))) as executor:
2287
+ future_to_repo = {
2288
+ executor.submit(fetch_commits_for_repo, repo_id, kind, username, selected_year): repo_id
2289
+ for repo_id in chunk
2290
+ }
2291
+ for future in as_completed(future_to_repo):
2292
+ repo_commits, repo_count = future.result()
2293
+ if repo_commits:
2294
+ all_commit_dates.extend(repo_commits)
2295
+ total_commits += repo_count
2296
+
2297
+ # Update progress for all types
2298
+ progress_per_type = 1.0 / len(types_to_fetch)
2299
+ current_type_progress = min(1.0, (i + len(chunk)) / max(1, len(repo_ids)))
2300
+ overall_progress = (type_index * progress_per_type) + (current_type_progress * progress_per_type)
2301
+ progress_bar.progress(overall_progress)
2302
+
2303
+ commits_by_type[kind] = all_commit_dates
2304
+ commit_counts_by_type[kind] = total_commits
2305
+
2306
+ except Exception as e:
2307
+ st.warning(f"Error fetching {kind}s for {username}: {str(e)}")
2308
+ commits_by_type[kind] = []
2309
+ commit_counts_by_type[kind] = 0
2310
+
2311
+ # Complete progress
2312
+ progress_bar.progress(1.0)
2313
+ progress_container.success("Data fetching complete!")
2314
+ time.sleep(0.5) # Short pause for visual feedback
2315
+ progress_container.empty() # Clear the progress indicators
2316
+
2317
+ # Calculate total commits across all types
2318
+ total_commits = sum(commit_counts_by_type.values())
2319
+
2320
+ # Main dashboard layout with improved structure
2321
+ st.markdown(f'<h2 style="color: #1E88E5; border-bottom: 2px solid #E0E0E0; padding-bottom: 8px; margin-top: 2rem;">Activity Overview</h2>', unsafe_allow_html=True)
2322
+
2323
+ # Profile summary
2324
+ profile_col1, profile_col2 = st.columns([1, 2])
2325
+
2326
+ with profile_col1:
2327
+ # Create a stats card with key metrics
2328
+ st.markdown(f'<div style="background-color: white; padding: 20px; border-radius: 10px; box-shadow: 0 2px 10px rgba(0,0,0,0.1);">'
2329
+ f'<h3 style="color: #1E88E5; text-align: center; margin-bottom: 15px;">Contribution Stats</h3>'
2330
+ f'<div style="display: flex; justify-content: space-between; margin-bottom: 10px;">'
2331
+ f'<span style="font-weight: bold;">Total Commits:</span><span>{total_commits}</span></div>'
2332
+ f'<div style="display: flex; justify-content: space-between; margin-bottom: 10px;">'
2333
+ f'<span style="font-weight: bold;">Models:</span><span>{models_count}</span></div>'
2334
+ f'<div style="display: flex; justify-content: space-between; margin-bottom: 10px;">'
2335
+ f'<span style="font-weight: bold;">Datasets:</span><span>{datasets_count}</span></div>'
2336
+ f'<div style="display: flex; justify-content: space-between; margin-bottom: 10px;">'
2337
+ f'<span style="font-weight: bold;">Spaces:</span><span>{spaces_count}</span></div>'
2338
+ f'</div>', unsafe_allow_html=True)
2339
+
2340
+ # Type breakdown pie chart
2341
+ model_commits = commit_counts_by_type.get("model", 0)
2342
+ dataset_commits = commit_counts_by_type.get("dataset", 0)
2343
+ space_commits = commit_counts_by_type.get("space", 0)
2344
+
2345
+ pie_chart = create_contribution_pie(model_commits, dataset_commits, space_commits)
2346
+ if pie_chart:
2347
+ st.pyplot(pie_chart)
2348
+
2349
+ with profile_col2:
2350
+ # Display contribution radar chart
2351
+ radar_fig = create_contribution_radar(username, models_count, spaces_count, datasets_count, total_commits)
2352
+ st.pyplot(radar_fig)
2353
+
2354
+ # Create DataFrame for all commits
2355
+ all_commits = []
2356
+ for commits in commits_by_type.values():
2357
+ all_commits.extend(commits)
2358
+ all_df = pd.DataFrame(all_commits, columns=["date"])
2359
+ if not all_df.empty:
2360
+ all_df = all_df.drop_duplicates() # Remove any duplicate dates
2361
+
2362
+ # Calendar heatmap for all commits in a separate section
2363
+ st.markdown(f'<h2 style="color: #1E88E5; border-bottom: 2px solid #E0E0E0; padding-bottom: 8px; margin-top: 2rem;">Contribution Calendar</h2>', unsafe_allow_html=True)
2364
+
2365
+ if not all_df.empty:
2366
+ make_calendar_heatmap(all_df, "All Contributions", selected_year)
2367
+ else:
2368
+ st.info(f"No contributions found for {username} in {selected_year}")
2369
+
2370
+ # Monthly activity chart
2371
+ st.markdown(f'<h2 style="color: #1E88E5; border-bottom: 2px solid #E0E0E0; padding-bottom: 8px; margin-top: 2rem;">Monthly Activity</h2>', unsafe_allow_html=True)
2372
+
2373
+ monthly_fig = create_monthly_activity(all_df, selected_year)
2374
+ if monthly_fig:
2375
+ st.pyplot(monthly_fig)
2376
+ else:
2377
+ st.info(f"No activity data available for {username} in {selected_year}")
2378
+
2379
+ # Follower growth simulation
2380
+ st.markdown(f'<h2 style="color: #1E88E5; border-bottom: 2px solid #E0E0E0; padding-bottom: 8px; margin-top: 2rem;">Growth Projection</h2>', unsafe_allow_html=True)
2381
+ st.markdown('<div style="background-color: #EDE7F6; padding: 10px; border-radius: 5px; margin-bottom: 15px;">'
2382
+ '<p style="font-style: italic; margin: 0;">📊 This is a simulation based on contribution metrics - for visualization purposes only</p>'
2383
+ '</div>', unsafe_allow_html=True)
2384
+
2385
+ follower_chart = simulate_follower_data(username, spaces_count, models_count, total_commits)
2386
+ st.pyplot(follower_chart)
2387
+
2388
+ # Analytics summary section
2389
+ if total_commits > 0:
2390
+ st.markdown(f'<h2 style="color: #1E88E5; border-bottom: 2px solid #E0E0E0; padding-bottom: 8px; margin-top: 2rem;">📊 Analytics Summary</h2>', unsafe_allow_html=True)
2391
+
2392
+ # Contribution pattern analysis
2393
+ monthly_df = pd.DataFrame(all_commits, columns=["date"])
2394
+ monthly_df['date'] = pd.to_datetime(monthly_df['date'])
2395
+ monthly_df['month'] = monthly_df['date'].dt.month
2396
+
2397
+ if not monthly_df.empty:
2398
+ most_active_month = monthly_df['month'].value_counts().idxmax()
2399
+ month_name = datetime(2020, most_active_month, 1).strftime('%B')
2400
+
2401
+ # Create a summary card
2402
+ st.markdown(f'<div style="background-color: white; padding: 25px; border-radius: 10px; box-shadow: 0 2px 10px rgba(0,0,0,0.1);">'
2403
+ f'<h3 style="color: #1E88E5; border-bottom: 1px solid #E0E0E0; padding-bottom: 10px;">Activity Analysis for {username}</h3>'
2404
+ f'<ul style="list-style-type: none; padding-left: 5px;">'
2405
+ f'<li style="margin: 15px 0; font-size: 16px;">📈 <strong>Total Activity:</strong> {total_commits} contributions in {selected_year}</li>'
2406
+ f'<li style="margin: 15px 0; font-size: 16px;">🗓️ <strong>Most Active Month:</strong> {month_name} with {monthly_df["month"].value_counts().max()} contributions</li>'
2407
+ f'<li style="margin: 15px 0; font-size: 16px;">🧩 <strong>Repository Breakdown:</strong> {models_count} Models, {spaces_count} Spaces, {datasets_count} Datasets</li>'
2408
+ f'</ul>', unsafe_allow_html=True)
2409
+
2410
+ # Add ranking context if available
2411
+ if overall_rank:
2412
+ percentile = 100 - overall_rank
2413
+ st.markdown(f'<div style="margin-top: 20px;">'
2414
+ f'<h3 style="color: #1E88E5; border-bottom: 1px solid #E0E0E0; padding-bottom: 10px;">Ranking Analysis</h3>'
2415
+ f'<ul style="list-style-type: none; padding-left: 5px;">'
2416
+ f'<li style="margin: 15px 0; font-size: 16px;">🏆 <strong>Overall Ranking:</strong> #{overall_rank} (Top {percentile}% of contributors)</li>', unsafe_allow_html=True)
2417
+
2418
+ badge_html = '<div style="margin: 20px 0;">'
2419
+
2420
+ if spaces_rank and spaces_rank <= 10:
2421
+ badge_html += f'<span style="background-color: #FFECB3; color: #FF6F00; padding: 8px 15px; border-radius: 20px; font-weight: bold; margin-right: 10px; display: inline-block; margin-bottom: 10px;">🌟 Elite Spaces Contributor (#{spaces_rank})</span>'
2422
+ elif spaces_rank and spaces_rank <= 30:
2423
+ badge_html += f'<span style="background-color: #E1F5FE; color: #0277BD; padding: 8px 15px; border-radius: 20px; font-weight: bold; margin-right: 10px; display: inline-block; margin-bottom: 10px;">✨ Outstanding Spaces Contributor (#{spaces_rank})</span>'
2424
+
2425
+ if models_rank and models_rank <= 10:
2426
+ badge_html += f'<span style="background-color: #FFECB3; color: #FF6F00; padding: 8px 15px; border-radius: 20px; font-weight: bold; margin-right: 10px; display: inline-block; margin-bottom: 10px;">🌟 Elite Models Contributor (#{models_rank})</span>'
2427
+ elif models_rank and models_rank <= 30:
2428
+ badge_html += f'<span style="background-color: #E1F5FE; color: #0277BD; padding: 8px 15px; border-radius: 20px; font-weight: bold; margin-right: 10px; display: inline-block; margin-bottom: 10px;">✨ Outstanding Models Contributor (#{models_rank})</span>'
2429
+
2430
+ badge_html += '</div>'
2431
+
2432
+ # Add achievement badges
2433
+ if spaces_rank or models_rank:
2434
+ st.markdown(badge_html, unsafe_allow_html=True)
2435
+
2436
+ st.markdown('</ul></div></div>', unsafe_allow_html=True)
2437
+
2438
+ # Detailed category analysis section
2439
+ st.markdown(f'<h2 style="color: #1E88E5; border-bottom: 2px solid #E0E0E0; padding-bottom: 8px; margin-top: 2rem;">Detailed Category Analysis</h2>', unsafe_allow_html=True)
2440
+
2441
+ # Create category cards in columns
2442
+ cols = st.columns(len(types_to_fetch)) if types_to_fetch else st.columns(1)
2443
+
2444
+ category_icons = {
2445
+ "model": "🧠",
2446
+ "dataset": "📦",
2447
+ "space": "🚀"
2448
+ }
2449
+
2450
+ category_colors = {
2451
+ "model": "#FF9800",
2452
+ "dataset": "#2196F3",
2453
+ "space": "#4CAF50"
2454
+ }
2455
+
2456
+ for i, kind in enumerate(types_to_fetch):
2457
+ with cols[i]:
2458
+ try:
2459
+ emoji = category_icons.get(kind, "📊")
2460
+ label = kind.capitalize() + "s"
2461
+ color = category_colors.get(kind, "#1E88E5")
2462
+
2463
+ total = len(cached_list_items(username, kind))
2464
+ commits = commits_by_type.get(kind, [])
2465
+ commit_count = commit_counts_by_type.get(kind, 0)
2466
+
2467
+ # Create styled card header
2468
+ st.markdown(f'<div style="background-color: white; padding: 20px; border-radius: 10px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); border-top: 5px solid {color};">'
2469
+ f'<h3 style="color: {color}; text-align: center;">{emoji} {label}</h3>'
2470
+ f'<div style="display: flex; justify-content: space-between; margin: 15px 0;">'
2471
+ f'<span style="font-weight: bold;">Total:</span><span>{total}</span></div>'
2472
+ f'<div style="display: flex; justify-content: space-between; margin-bottom: 15px;">'
2473
+ f'<span style="font-weight: bold;">Commits:</span><span>{commit_count}</span></div>'
2474
+ f'</div>', unsafe_allow_html=True)
2475
+
2476
+ # Create calendar for this type
2477
+ df_kind = pd.DataFrame(commits, columns=["date"])
2478
+ if not df_kind.empty:
2479
+ df_kind = df_kind.drop_duplicates() # Remove any duplicate dates
2480
+ make_calendar_heatmap(df_kind, f"{label} Commits", selected_year)
2481
+ else:
2482
+ st.info(f"No {label.lower()} activity in {selected_year}")
2483
+
2484
+ except Exception as e:
2485
+ st.warning(f"Error processing {kind.capitalize()}s: {str(e)}")
2486
+ # Show empty placeholder
2487
+ st.markdown(f'<div style="background-color: white; padding: 20px; border-radius: 10px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); border-top: 5px solid #9E9E9E; text-align: center;">'
2488
+ f'<h3 style="color: #9E9E9E;">⚠️ Error</h3>'
2489
+ f'<p>Could not load {kind.capitalize()}s data</p>'
2490
+ f'</div>', unsafe_allow_html=True)
2491
+
2492
+ # Footer
2493
+ st.markdown('<hr style="margin: 3rem 0 1rem 0;">', unsafe_allow_html=True)
2494
+ st.markdown('<p style="text-align: center; color: #9E9E9E; font-size: 0.8rem;">Hugging Face Contributions Dashboard | Data fetched from Hugging Face API</p>', unsafe_allow_html=True)
2495
+ else:
2496
+ # If no username is selected, show welcome screen
2497
+ st.markdown(f'<div style="text-align: center; margin: 50px 0;">'
2498
+ f'<img src="https://huggingface.co/datasets/huggingface/brand-assets/resolve/main/hf-logo.svg" style="width: 200px; margin-bottom: 30px;">'
2499
+ f'<h2>Welcome to Hugging Face Contributions Dashboard</h2>'
2500
+ f'<p style="font-size: 1.2rem;">Please select a contributor from the sidebar to view their activity.</p>'
2501
+ f'</div>', unsafe_allow_html=True)