Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
@@ -192,12 +192,8 @@ def get_user_commit_stats(username):
|
|
192 |
except:
|
193 |
pass
|
194 |
|
195 |
-
# Calculate contribution score
|
196 |
-
|
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
|
786 |
-
st.markdown('<p style="font-size: 0.9rem; color: #666; margin-bottom: 10px;">Ranked by
|
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", "
|
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 |
-
"
|
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)
|