aiqtech commited on
Commit
cf64597
Β·
verified Β·
1 Parent(s): f832c8e

Create app-backup.py

Browse files
Files changed (1) hide show
  1. app-backup.py +1179 -0
app-backup.py ADDED
@@ -0,0 +1,1179 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ from huggingface_hub import HfApi
3
+ import pandas as pd
4
+ import matplotlib.pyplot as plt
5
+ import seaborn as sns
6
+ from datetime import datetime
7
+ from concurrent.futures import ThreadPoolExecutor, as_completed
8
+ from functools import lru_cache
9
+ import time
10
+ import requests
11
+ from collections import Counter
12
+ import numpy as np
13
+
14
+ st.set_page_config(page_title="HF Contributions", layout="wide", initial_sidebar_state="expanded")
15
+
16
+ # ν–₯μƒλœ UI μŠ€νƒ€μΌλ§
17
+ st.markdown("""
18
+ <style>
19
+ /* μ‚¬μ΄λ“œλ°” μŠ€νƒ€μΌλ§ */
20
+ [data-testid="stSidebar"] {
21
+ min-width: 35vw !important;
22
+ max-width: 35vw !important;
23
+ background-color: #f8f9fa;
24
+ padding: 1rem;
25
+ border-right: 1px solid #e9ecef;
26
+ }
27
+
28
+ /* 헀더 μŠ€νƒ€μΌλ§ */
29
+ h1, h2, h3 {
30
+ color: #1e88e5;
31
+ font-weight: 700;
32
+ }
33
+ h1 {
34
+ font-size: 2.5rem;
35
+ margin-bottom: 1.5rem;
36
+ border-bottom: 2px solid #e0e0e0;
37
+ padding-bottom: 0.5rem;
38
+ }
39
+ h2 {
40
+ font-size: 1.8rem;
41
+ margin-top: 1.5rem;
42
+ }
43
+ h3 {
44
+ font-size: 1.4rem;
45
+ margin-top: 1rem;
46
+ }
47
+
48
+ /* μΉ΄λ“œ μŠ€νƒ€μΌλ§ */
49
+ div[data-testid="stMetric"] {
50
+ background-color: #f1f8fe;
51
+ border-radius: 10px;
52
+ padding: 1rem;
53
+ box-shadow: 0 2px 5px rgba(0,0,0,0.05);
54
+ margin-bottom: 1rem;
55
+ }
56
+
57
+ /* 차트 μ»¨ν…Œμ΄λ„ˆ μŠ€νƒ€μΌλ§ */
58
+ .chart-container {
59
+ background-color: white;
60
+ border-radius: 10px;
61
+ padding: 1rem;
62
+ box-shadow: 0 2px 10px rgba(0,0,0,0.1);
63
+ margin: 1rem 0;
64
+ }
65
+
66
+ /* ν…Œμ΄λΈ” μŠ€νƒ€μΌλ§ */
67
+ div[data-testid="stDataFrame"] {
68
+ background-color: white;
69
+ border-radius: 10px;
70
+ padding: 0.5rem;
71
+ box-shadow: 0 2px 5px rgba(0,0,0,0.05);
72
+ }
73
+
74
+ /* νƒ­ μŠ€νƒ€μΌλ§ */
75
+ button[data-baseweb="tab"] {
76
+ font-weight: 600;
77
+ }
78
+
79
+ /* μ„œλΈŒν—€λ” λ°°κ²½ */
80
+ .subheader {
81
+ background-color: #f1f8fe;
82
+ padding: 0.5rem 1rem;
83
+ border-radius: 5px;
84
+ margin-bottom: 1rem;
85
+ }
86
+
87
+ /* 정보 뱃지 */
88
+ .info-badge {
89
+ background-color: #e3f2fd;
90
+ color: #1976d2;
91
+ padding: 0.3rem 0.7rem;
92
+ border-radius: 20px;
93
+ display: inline-block;
94
+ font-weight: 500;
95
+ margin-right: 0.5rem;
96
+ }
97
+
98
+ /* ν”„λ‘œκ·Έλ ˆμŠ€ λ°” */
99
+ div[data-testid="stProgress"] {
100
+ height: 0.5rem !important;
101
+ }
102
+
103
+ /* λ²„νŠΌ μŠ€νƒ€μΌλ§ */
104
+ .stButton button {
105
+ background-color: #1e88e5;
106
+ color: white;
107
+ border: none;
108
+ font-weight: 500;
109
+ }
110
+
111
+ /* κ²½κ³ /성곡 λ©”μ‹œμ§€ κ°œμ„  */
112
+ div[data-testid="stAlert"] {
113
+ border-radius: 10px;
114
+ margin: 1rem 0;
115
+ }
116
+
117
+ /* μΉ΄ν…Œκ³ λ¦¬ 뢄석 μ„Ήμ…˜ */
118
+ .category-section {
119
+ background-color: white;
120
+ border-radius: 10px;
121
+ padding: 1rem;
122
+ margin-bottom: 1.5rem;
123
+ box-shadow: 0 2px 5px rgba(0,0,0,0.05);
124
+ }
125
+ </style>
126
+ """, unsafe_allow_html=True)
127
+
128
+ api = HfApi()
129
+
130
+ # Cache for API responses
131
+ @lru_cache(maxsize=1000)
132
+ def cached_repo_info(repo_id, repo_type):
133
+ return api.repo_info(repo_id=repo_id, repo_type=repo_type)
134
+
135
+ @lru_cache(maxsize=1000)
136
+ def cached_list_commits(repo_id, repo_type):
137
+ return list(api.list_repo_commits(repo_id=repo_id, repo_type=repo_type))
138
+
139
+ @lru_cache(maxsize=100)
140
+ def cached_list_items(username, kind):
141
+ if kind == "model":
142
+ return list(api.list_models(author=username))
143
+ elif kind == "dataset":
144
+ return list(api.list_datasets(author=username))
145
+ elif kind == "space":
146
+ return list(api.list_spaces(author=username))
147
+ return []
148
+
149
+ # Function to fetch trending accounts and create stats
150
+ @lru_cache(maxsize=1)
151
+ def get_trending_accounts(limit=100):
152
+ try:
153
+ trending_data = {"spaces": [], "models": []}
154
+
155
+ # Get spaces for stats calculation
156
+ spaces_response = requests.get("https://huggingface.co/api/spaces",
157
+ params={"limit": 10000},
158
+ timeout=30)
159
+
160
+ # Get models for stats calculation
161
+ models_response = requests.get("https://huggingface.co/api/models",
162
+ params={"limit": 10000},
163
+ timeout=30)
164
+
165
+ # Process spaces data
166
+ spaces_owners = []
167
+ if spaces_response.status_code == 200:
168
+ spaces = spaces_response.json()
169
+
170
+ # Count spaces by owner
171
+ owner_counts_spaces = {}
172
+ for space in spaces:
173
+ if '/' in space.get('id', ''):
174
+ owner, _ = space.get('id', '').split('/', 1)
175
+ else:
176
+ owner = space.get('owner', '')
177
+
178
+ if owner != 'None':
179
+ owner_counts_spaces[owner] = owner_counts_spaces.get(owner, 0) + 1
180
+
181
+ # Get top owners by count for spaces
182
+ top_owners_spaces = sorted(owner_counts_spaces.items(), key=lambda x: x[1], reverse=True)[:limit]
183
+ trending_data["spaces"] = top_owners_spaces
184
+ spaces_owners = [owner for owner, _ in top_owners_spaces]
185
+
186
+ # Process models data
187
+ models_owners = []
188
+ if models_response.status_code == 200:
189
+ models = models_response.json()
190
+
191
+ # Count models by owner
192
+ owner_counts_models = {}
193
+ for model in models:
194
+ if '/' in model.get('id', ''):
195
+ owner, _ = model.get('id', '').split('/', 1)
196
+ else:
197
+ owner = model.get('owner', '')
198
+
199
+ if owner != 'None':
200
+ owner_counts_models[owner] = owner_counts_models.get(owner, 0) + 1
201
+
202
+ # Get top owners by count for models
203
+ top_owners_models = sorted(owner_counts_models.items(), key=lambda x: x[1], reverse=True)[:limit]
204
+ trending_data["models"] = top_owners_models
205
+ models_owners = [owner for owner, _ in top_owners_models]
206
+
207
+ # Combine rankings for overall trending based on appearance in both lists
208
+ combined_score = {}
209
+ for i, owner in enumerate(spaces_owners):
210
+ if owner not in combined_score:
211
+ combined_score[owner] = 0
212
+ combined_score[owner] += (limit - i) # Higher rank gives more points
213
+
214
+ for i, owner in enumerate(models_owners):
215
+ if owner not in combined_score:
216
+ combined_score[owner] = 0
217
+ combined_score[owner] += (limit - i) # Higher rank gives more points
218
+
219
+ # Sort by combined score
220
+ sorted_combined = sorted(combined_score.items(), key=lambda x: x[1], reverse=True)[:limit]
221
+ trending_authors = [owner for owner, _ in sorted_combined]
222
+
223
+ return trending_authors, trending_data["spaces"], trending_data["models"]
224
+ except Exception as e:
225
+ st.error(f"Error fetching trending accounts: {str(e)}")
226
+ fallback_authors = ["ritvik77", "facebook", "google", "stabilityai", "Salesforce", "tiiuae", "bigscience"]
227
+ return fallback_authors, [(author, 0) for author in fallback_authors], [(author, 0) for author in fallback_authors]
228
+
229
+ # Rate limiting
230
+ class RateLimiter:
231
+ def __init__(self, calls_per_second=10):
232
+ self.calls_per_second = calls_per_second
233
+ self.last_call = 0
234
+
235
+ def wait(self):
236
+ current_time = time.time()
237
+ time_since_last_call = current_time - self.last_call
238
+ if time_since_last_call < (1.0 / self.calls_per_second):
239
+ time.sleep((1.0 / self.calls_per_second) - time_since_last_call)
240
+ self.last_call = time.time()
241
+
242
+ rate_limiter = RateLimiter()
243
+
244
+ # Function to fetch commits for a repository (optimized)
245
+ def fetch_commits_for_repo(repo_id, repo_type, username, selected_year):
246
+ try:
247
+ rate_limiter.wait()
248
+ # Skip private/gated repos upfront
249
+ repo_info = cached_repo_info(repo_id, repo_type)
250
+ if repo_info.private or (hasattr(repo_info, 'gated') and repo_info.gated):
251
+ return [], 0
252
+
253
+ # Get initial commit date
254
+ initial_commit_date = pd.to_datetime(repo_info.created_at).tz_localize(None).date()
255
+ commit_dates = []
256
+ commit_count = 0
257
+
258
+ # Add initial commit if it's from the selected year
259
+ if initial_commit_date.year == selected_year:
260
+ commit_dates.append(initial_commit_date)
261
+ commit_count += 1
262
+
263
+ # Get all commits
264
+ commits = cached_list_commits(repo_id, repo_type)
265
+ for commit in commits:
266
+ commit_date = pd.to_datetime(commit.created_at).tz_localize(None).date()
267
+ if commit_date.year == selected_year:
268
+ commit_dates.append(commit_date)
269
+ commit_count += 1
270
+
271
+ return commit_dates, commit_count
272
+ except Exception as e:
273
+ return [], 0
274
+
275
+ # Function to get commit events for a user (optimized)
276
+ def get_commit_events(username, kind=None, selected_year=None):
277
+ commit_dates = []
278
+ items_with_type = []
279
+ kinds = [kind] if kind else ["model", "dataset", "space"]
280
+
281
+ for k in kinds:
282
+ try:
283
+ items = cached_list_items(username, k)
284
+ items_with_type.extend((item, k) for item in items)
285
+ repo_ids = [item.id for item in items]
286
+
287
+ # Optimized parallel fetch with chunking
288
+ chunk_size = 5 # Process 5 repos at a time
289
+ for i in range(0, len(repo_ids), chunk_size):
290
+ chunk = repo_ids[i:i + chunk_size]
291
+ with ThreadPoolExecutor(max_workers=min(5, len(chunk))) as executor:
292
+ future_to_repo = {
293
+ executor.submit(fetch_commits_for_repo, repo_id, k, username, selected_year): repo_id
294
+ for repo_id in chunk
295
+ }
296
+ for future in as_completed(future_to_repo):
297
+ repo_commits, repo_count = future.result()
298
+ if repo_commits: # Only extend if we got commits
299
+ commit_dates.extend(repo_commits)
300
+ except Exception as e:
301
+ st.warning(f"Error fetching {k}s for {username}: {str(e)}")
302
+
303
+ # Create DataFrame with all commits
304
+ df = pd.DataFrame(commit_dates, columns=["date"])
305
+ if not df.empty:
306
+ df = df.drop_duplicates() # Remove any duplicate dates
307
+ return df, items_with_type
308
+
309
+ # Calendar heatmap function (optimized)
310
+ def make_calendar_heatmap(df, title, year):
311
+ if df.empty:
312
+ st.info(f"No {title.lower()} found for {year}.")
313
+ return
314
+
315
+ # Optimize DataFrame operations
316
+ df["count"] = 1
317
+ df = df.groupby("date", as_index=False).sum()
318
+ df["date"] = pd.to_datetime(df["date"])
319
+
320
+ # Create date range more efficiently
321
+ start = pd.Timestamp(f"{year}-01-01")
322
+ end = pd.Timestamp(f"{year}-12-31")
323
+ all_days = pd.date_range(start=start, end=end)
324
+
325
+ # Optimize DataFrame creation and merging
326
+ heatmap_data = pd.DataFrame({"date": all_days, "count": 0})
327
+ heatmap_data = heatmap_data.merge(df, on="date", how="left", suffixes=("", "_y"))
328
+ heatmap_data["count"] = heatmap_data["count_y"].fillna(0)
329
+ heatmap_data = heatmap_data.drop("count_y", axis=1)
330
+
331
+ # Calculate week and day of week more efficiently
332
+ heatmap_data["dow"] = heatmap_data["date"].dt.dayofweek
333
+ heatmap_data["week"] = (heatmap_data["date"] - start).dt.days // 7
334
+
335
+ # Create pivot table more efficiently
336
+ pivot = heatmap_data.pivot(index="dow", columns="week", values="count").fillna(0)
337
+
338
+ # Optimize month labels calculation
339
+ month_labels = pd.date_range(start, end, freq="MS").strftime("%b")
340
+ month_positions = pd.date_range(start, end, freq="MS").map(lambda x: (x - start).days // 7)
341
+
342
+ # Create custom colormap with specific boundaries
343
+ from matplotlib.colors import ListedColormap, BoundaryNorm
344
+ colors = ['#ebedf0', '#9be9a8', '#40c463', '#30a14e', '#216e39'] # GitHub-style green colors
345
+ bounds = [0, 1, 3, 11, 31, float('inf')] # Boundaries for color transitions
346
+ cmap = ListedColormap(colors)
347
+ norm = BoundaryNorm(bounds, cmap.N)
348
+
349
+ # Create plot more efficiently
350
+ fig, ax = plt.subplots(figsize=(12, 1.5))
351
+
352
+ # Convert pivot values to integers to ensure proper color mapping
353
+ pivot_int = pivot.astype(int)
354
+
355
+ # Create heatmap with explicit vmin and vmax
356
+ sns.heatmap(pivot_int, ax=ax, cmap=cmap, norm=norm, linewidths=0.5, linecolor="white",
357
+ square=True, cbar=False, yticklabels=["M", "T", "W", "T", "F", "S", "S"])
358
+
359
+ ax.set_title(f"{title}", fontsize=14, pad=10)
360
+ ax.set_xlabel("")
361
+ ax.set_ylabel("")
362
+ ax.set_xticks(month_positions)
363
+ ax.set_xticklabels(month_labels, fontsize=10)
364
+ ax.set_yticklabels(ax.get_yticklabels(), rotation=0, fontsize=10)
365
+
366
+ # μ‹œκ°μ  ν–₯상을 μœ„ν•œ figure μŠ€νƒ€μΌλ§
367
+ fig.tight_layout()
368
+ fig.patch.set_facecolor('#F8F9FA')
369
+
370
+ st.pyplot(fig)
371
+
372
+ # Function to create a fancy contribution radar chart
373
+ def create_contribution_radar(username, models_count, spaces_count, datasets_count, commits_count):
374
+ # Create radar chart for contribution metrics
375
+ categories = ['Models', 'Spaces', 'Datasets', 'Activity']
376
+ values = [models_count, spaces_count, datasets_count, commits_count]
377
+
378
+ # Normalize values for better visualization
379
+ max_vals = [100, 100, 50, 500] # Reasonable max values for each category
380
+ normalized = [min(v/m, 1.0) for v, m in zip(values, max_vals)]
381
+
382
+ # Create radar chart
383
+ angles = np.linspace(0, 2*np.pi, len(categories), endpoint=False).tolist()
384
+ angles += angles[:1] # Close the loop
385
+
386
+ normalized += normalized[:1] # Close the loop
387
+
388
+ fig, ax = plt.subplots(figsize=(6, 6), subplot_kw={'polar': True}, facecolor='#F8F9FA')
389
+
390
+ # Add background grid with improved styling
391
+ ax.set_theta_offset(np.pi / 2)
392
+ ax.set_theta_direction(-1)
393
+ ax.set_thetagrids(np.degrees(angles[:-1]), categories, fontsize=12, fontweight='bold')
394
+
395
+ # κ·Έλ¦¬λ“œ μŠ€νƒ€μΌλ§ κ°œμ„ 
396
+ ax.grid(color='#CCCCCC', linestyle='-', linewidth=0.5, alpha=0.7)
397
+
398
+ # Draw the chart with improved color scheme
399
+ ax.fill(angles, normalized, color='#4CAF50', alpha=0.25)
400
+ ax.plot(angles, normalized, color='#4CAF50', linewidth=3)
401
+
402
+ # Add value labels with improved styling
403
+ for i, val in enumerate(values):
404
+ angle = angles[i]
405
+ x = (normalized[i] + 0.1) * np.cos(angle)
406
+ y = (normalized[i] + 0.1) * np.sin(angle)
407
+ ax.text(angle, normalized[i] + 0.1, str(val),
408
+ ha='center', va='center', fontsize=12,
409
+ fontweight='bold', color='#1976D2')
410
+
411
+ # Add highlight circles
412
+ circles = [0.25, 0.5, 0.75, 1.0]
413
+ for circle in circles:
414
+ ax.plot(angles, [circle] * len(angles), color='gray', alpha=0.3, linewidth=0.5, linestyle='--')
415
+
416
+ ax.set_title(f"{username}'s Contribution Profile", fontsize=16, pad=20, fontweight='bold')
417
+
418
+ # λ°°κ²½ 원 μ—†μ• κΈ°
419
+ ax.set_facecolor('#F8F9FA')
420
+
421
+ return fig
422
+
423
+ # Function to create contribution distribution pie chart
424
+ def create_contribution_pie(model_commits, dataset_commits, space_commits):
425
+ labels = ['Models', 'Datasets', 'Spaces']
426
+ sizes = [model_commits, dataset_commits, space_commits]
427
+
428
+ # Filter out zero values
429
+ filtered_labels = [label for label, size in zip(labels, sizes) if size > 0]
430
+ filtered_sizes = [size for size in sizes if size > 0]
431
+
432
+ if not filtered_sizes:
433
+ return None # No data to show
434
+
435
+ # Use a more attractive color scheme
436
+ colors = ['#FF9800', '#2196F3', '#4CAF50']
437
+ filtered_colors = [color for color, size in zip(colors, sizes) if size > 0]
438
+
439
+ fig, ax = plt.subplots(figsize=(7, 7), facecolor='#F8F9FA')
440
+
441
+ # Create exploded pie chart with improved styling
442
+ explode = [0.1] * len(filtered_sizes) # Explode all slices for better visualization
443
+
444
+ wedges, texts, autotexts = ax.pie(
445
+ filtered_sizes,
446
+ labels=None, # We'll add custom labels
447
+ colors=filtered_colors,
448
+ autopct='%1.1f%%',
449
+ startangle=90,
450
+ shadow=True,
451
+ explode=explode,
452
+ textprops={'fontsize': 14, 'weight': 'bold'},
453
+ wedgeprops={'edgecolor': 'white', 'linewidth': 2}
454
+ )
455
+
456
+ # Customize the percentage text
457
+ for autotext in autotexts:
458
+ autotext.set_color('white')
459
+ autotext.set_fontsize(12)
460
+ autotext.set_weight('bold')
461
+
462
+ # Add legend with custom styling
463
+ ax.legend(
464
+ wedges,
465
+ [f"{label} ({size})" for label, size in zip(filtered_labels, filtered_sizes)],
466
+ title="Contribution Types",
467
+ loc="center left",
468
+ bbox_to_anchor=(0.85, 0.5),
469
+ fontsize=12
470
+ )
471
+
472
+ ax.set_title('Distribution of Contributions by Type', fontsize=16, pad=20, fontweight='bold')
473
+ ax.axis('equal') # Equal aspect ratio ensures that pie is drawn as a circle
474
+
475
+ return fig
476
+
477
+ # Function to create monthly activity chart
478
+ def create_monthly_activity(df, year):
479
+ if df.empty:
480
+ return None
481
+
482
+ # Aggregate by month
483
+ df['date'] = pd.to_datetime(df['date'])
484
+ df['month'] = df['date'].dt.month
485
+ df['month_name'] = df['date'].dt.strftime('%b')
486
+
487
+ # Count by month and ensure all months are present
488
+ month_order = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
489
+ counts_by_month = df.groupby('month_name')['date'].count()
490
+ monthly_counts = pd.Series([counts_by_month.get(m, 0) for m in month_order], index=month_order)
491
+
492
+ # Create bar chart with improved styling
493
+ fig, ax = plt.subplots(figsize=(14, 6), facecolor='#F8F9FA')
494
+
495
+ # Create bars with gradient colors based on activity level
496
+ norm = plt.Normalize(0, monthly_counts.max() if monthly_counts.max() > 0 else 1)
497
+ colors = plt.cm.viridis(norm(monthly_counts.values))
498
+
499
+ bars = ax.bar(monthly_counts.index, monthly_counts.values, color=colors, width=0.7)
500
+
501
+ # Highlight the month with most activity
502
+ if monthly_counts.max() > 0:
503
+ max_idx = monthly_counts.argmax()
504
+ bars[max_idx].set_color('#FF5722')
505
+ bars[max_idx].set_edgecolor('black')
506
+ bars[max_idx].set_linewidth(1.5)
507
+
508
+ # Add labels and styling with enhanced design
509
+ ax.set_title(f'Monthly Activity in {year}', fontsize=18, pad=20, fontweight='bold')
510
+ ax.set_xlabel('Month', fontsize=14, labelpad=10)
511
+ ax.set_ylabel('Number of Contributions', fontsize=14, labelpad=10)
512
+
513
+ # Add value labels on top of bars with improved styling
514
+ for i, count in enumerate(monthly_counts.values):
515
+ if count > 0:
516
+ ax.text(i, count + 0.5, str(int(count)), ha='center', fontsize=12, fontweight='bold')
517
+
518
+ # Add grid for better readability with improved styling
519
+ ax.grid(axis='y', linestyle='--', alpha=0.7, color='#CCCCCC')
520
+ ax.set_axisbelow(True) # Grid lines behind bars
521
+
522
+ # Style the chart borders and background
523
+ ax.spines['top'].set_visible(False)
524
+ ax.spines['right'].set_visible(False)
525
+ ax.spines['left'].set_linewidth(0.5)
526
+ ax.spines['bottom'].set_linewidth(0.5)
527
+
528
+ # Adjust tick parameters for better look
529
+ ax.tick_params(axis='x', labelsize=12, pad=5)
530
+ ax.tick_params(axis='y', labelsize=12, pad=5)
531
+
532
+ plt.tight_layout()
533
+
534
+ return fig
535
+
536
+ # Function to render follower growth simulation
537
+ def simulate_follower_data(username, spaces_count, models_count, total_commits):
538
+ # Simulate follower growth based on contribution metrics
539
+ # This is just a simulation for visual purposes
540
+ import numpy as np
541
+ from datetime import timedelta
542
+
543
+ # Start with a base number of followers proportional to contribution metrics
544
+ base_followers = max(10, int((spaces_count * 2 + models_count * 3 + total_commits/10) / 6))
545
+
546
+ # Generate timestamps for the past year
547
+ end_date = datetime.now()
548
+ start_date = end_date - timedelta(days=365)
549
+ dates = pd.date_range(start=start_date, end=end_date, freq='W') # Weekly data points
550
+
551
+ # Generate follower growth with some randomness
552
+ followers = []
553
+ current = base_followers / 2 # Start from half the base
554
+
555
+ for i in range(len(dates)):
556
+ growth_factor = 1 + (np.random.random() * 0.1) # Random growth between 0% and 10%
557
+ current = current * growth_factor
558
+ followers.append(int(current))
559
+
560
+ # Ensure end value matches our base_followers estimate
561
+ followers[-1] = base_followers
562
+
563
+ # Create the chart with improved styling
564
+ fig, ax = plt.subplots(figsize=(14, 6), facecolor='#F8F9FA')
565
+
566
+ # Create gradient line for better visualization
567
+ points = np.array([dates, followers]).T.reshape(-1, 1, 2)
568
+ segments = np.concatenate([points[:-1], points[1:]], axis=1)
569
+
570
+ from matplotlib.collections import LineCollection
571
+ norm = plt.Normalize(0, len(segments))
572
+ lc = LineCollection(segments, cmap='viridis', norm=norm, linewidth=3, alpha=0.8)
573
+ lc.set_array(np.arange(len(segments)))
574
+ line = ax.add_collection(lc)
575
+
576
+ # Add markers
577
+ ax.scatter(dates, followers, s=50, color='#9C27B0', alpha=0.8, zorder=10)
578
+
579
+ # Add styling with enhanced design
580
+ ax.set_title(f"Estimated Follower Growth for {username}", fontsize=18, pad=20, fontweight='bold')
581
+ ax.set_xlabel("Date", fontsize=14, labelpad=10)
582
+ ax.set_ylabel("Followers", fontsize=14, labelpad=10)
583
+
584
+ # Format the axes limits
585
+ ax.set_xlim(dates.min(), dates.max())
586
+ ax.set_ylim(0, max(followers) * 1.1)
587
+
588
+ # Add grid for better readability with improved styling
589
+ ax.grid(True, linestyle='--', alpha=0.7, color='#CCCCCC')
590
+ ax.set_axisbelow(True) # Grid lines behind plot
591
+
592
+ # Style the chart borders and background
593
+ ax.spines['top'].set_visible(False)
594
+ ax.spines['right'].set_visible(False)
595
+ ax.spines['left'].set_linewidth(0.5)
596
+ ax.spines['bottom'].set_linewidth(0.5)
597
+
598
+ # Adjust tick parameters for better look
599
+ ax.tick_params(axis='x', labelsize=12, rotation=45)
600
+ ax.tick_params(axis='y', labelsize=12)
601
+
602
+ # Add annotations for start and end points
603
+ ax.annotate(f"Start: {followers[0]}",
604
+ xy=(dates[0], followers[0]),
605
+ xytext=(10, 10),
606
+ textcoords='offset points',
607
+ fontsize=12,
608
+ fontweight='bold',
609
+ color='#9C27B0',
610
+ bbox=dict(boxstyle="round,pad=0.3", fc="#F3E5F5", ec="#9C27B0", alpha=0.8))
611
+
612
+ ax.annotate(f"Current: {followers[-1]}",
613
+ xy=(dates[-1], followers[-1]),
614
+ xytext=(-10, 10),
615
+ textcoords='offset points',
616
+ fontsize=12,
617
+ fontweight='bold',
618
+ color='#9C27B0',
619
+ ha='right',
620
+ bbox=dict(boxstyle="round,pad=0.3", fc="#F3E5F5", ec="#9C27B0", alpha=0.8))
621
+
622
+ plt.tight_layout()
623
+
624
+ return fig
625
+
626
+ # Function to create ranking position visualization
627
+ def create_ranking_chart(username, overall_rank, spaces_rank, models_rank):
628
+ if not (overall_rank or spaces_rank or models_rank):
629
+ return None
630
+
631
+ # Create a horizontal bar chart for rankings with improved styling
632
+ fig, ax = plt.subplots(figsize=(12, 5), facecolor='#F8F9FA')
633
+
634
+ categories = []
635
+ positions = []
636
+ colors = []
637
+ rank_values = []
638
+
639
+ if overall_rank:
640
+ categories.append('Overall')
641
+ positions.append(101 - overall_rank) # Invert rank for visualization (higher is better)
642
+ colors.append('#673AB7')
643
+ rank_values.append(overall_rank)
644
+
645
+ if spaces_rank:
646
+ categories.append('Spaces')
647
+ positions.append(101 - spaces_rank)
648
+ colors.append('#2196F3')
649
+ rank_values.append(spaces_rank)
650
+
651
+ if models_rank:
652
+ categories.append('Models')
653
+ positions.append(101 - models_rank)
654
+ colors.append('#FF9800')
655
+ rank_values.append(models_rank)
656
+
657
+ # Create horizontal bars with enhanced styling
658
+ bars = ax.barh(categories, positions, color=colors, alpha=0.8, height=0.6,
659
+ edgecolor='white', linewidth=1.5)
660
+
661
+ # Add rank values as text with improved styling
662
+ for i, bar in enumerate(bars):
663
+ ax.text(bar.get_width() + 2, bar.get_y() + bar.get_height()/2,
664
+ f'Rank #{rank_values[i]}', va='center', fontsize=12,
665
+ fontweight='bold', color=colors[i])
666
+
667
+ # Set chart properties with enhanced styling
668
+ ax.set_xlim(0, 105)
669
+ ax.set_title(f"Ranking Positions for {username} (Top 100)", fontsize=18, pad=20, fontweight='bold')
670
+ ax.set_xlabel("Percentile (higher is better)", fontsize=14, labelpad=10)
671
+
672
+ # Add explanatory text
673
+ ax.text(50, -0.6, "← Lower rank (higher number) | Higher rank (lower number) β†’",
674
+ ha='center', va='center', fontsize=10, fontweight='bold', color='#666666')
675
+
676
+ # Add a vertical line at 90th percentile to highlight top 10 with improved styling
677
+ ax.axvline(x=90, color='#FF5252', linestyle='--', alpha=0.7, linewidth=2)
678
+ ax.text(92, len(categories)/2, 'Top 10', color='#D32F2F', fontsize=12,
679
+ rotation=90, va='center', fontweight='bold')
680
+
681
+ # Style the chart borders and background
682
+ ax.spines['top'].set_visible(False)
683
+ ax.spines['right'].set_visible(False)
684
+ ax.spines['left'].set_linewidth(0.5)
685
+ ax.spines['bottom'].set_linewidth(0.5)
686
+
687
+ # Adjust tick parameters for better look
688
+ ax.tick_params(axis='x', labelsize=12)
689
+ ax.tick_params(axis='y', labelsize=14, pad=5)
690
+
691
+ # Add grid for better readability
692
+ ax.grid(axis='x', linestyle='--', alpha=0.5, color='#CCCCCC')
693
+ ax.set_axisbelow(True) # Grid lines behind bars
694
+
695
+ # Invert x-axis to show ranking position more intuitively
696
+ ax.invert_xaxis()
697
+
698
+ plt.tight_layout()
699
+ return fig
700
+
701
+ # Fetch trending accounts with a loading spinner (do this once at the beginning)
702
+ with st.spinner("Loading trending accounts..."):
703
+ trending_accounts, top_owners_spaces, top_owners_models = get_trending_accounts(limit=100)
704
+
705
+ # Sidebar
706
+ with st.sidebar:
707
+ st.markdown('<h1 style="text-align: center; color: #1E88E5;">πŸ‘€ Contributor</h1>', unsafe_allow_html=True)
708
+
709
+ # Create tabs for Spaces and Models rankings - ONLY SHOWING FIRST TWO TABS
710
+ tab1, tab2 = st.tabs([
711
+ "Top 100 Overall",
712
+ "Top Spaces & Models"
713
+ ])
714
+
715
+ with tab1:
716
+ # Show combined trending accounts list
717
+ st.markdown('<div class="subheader"><h3>πŸ”₯ Top 100 Contributors</h3></div>', unsafe_allow_html=True)
718
+
719
+ # Create a data frame for the table
720
+ if trending_accounts:
721
+ # Create a mapping from username to Spaces and Models rankings
722
+ spaces_rank = {owner: idx+1 for idx, (owner, _) in enumerate(top_owners_spaces)}
723
+ models_rank = {owner: idx+1 for idx, (owner, _) in enumerate(top_owners_models)}
724
+
725
+ # Create the overall ranking dataframe with trophies for top 3
726
+ overall_data = []
727
+ for idx, username in enumerate(trending_accounts[:100]):
728
+ # Add trophy emojis for top 3
729
+ rank_display = ""
730
+ if idx == 0:
731
+ rank_display = "πŸ† " # Gold trophy for 1st place
732
+ elif idx == 1:
733
+ rank_display = "πŸ† " # Silver trophy for 2nd place
734
+ elif idx == 2:
735
+ rank_display = "πŸ† " # Bronze trophy for 3rd place
736
+
737
+ # Use strings for all rankings to avoid type conversion issues
738
+ spaces_position = str(spaces_rank.get(username, "-"))
739
+ models_position = str(models_rank.get(username, "-"))
740
+ overall_data.append([f"{rank_display}{username}", spaces_position, models_position])
741
+
742
+ ranking_data_overall = pd.DataFrame(
743
+ overall_data,
744
+ columns=["Contributor", "Spaces Rank", "Models Rank"]
745
+ )
746
+ ranking_data_overall.index = ranking_data_overall.index + 1 # Start index from 1 for ranking
747
+
748
+ st.dataframe(
749
+ ranking_data_overall,
750
+ column_config={
751
+ "Contributor": st.column_config.TextColumn("Contributor"),
752
+ "Spaces Rank": st.column_config.TextColumn("Spaces Rank"),
753
+ "Models Rank": st.column_config.TextColumn("Models Rank")
754
+ },
755
+ use_container_width=True,
756
+ hide_index=False
757
+ )
758
+
759
+ with tab2:
760
+ # Show trending accounts by Spaces & Models
761
+ st.markdown('<div class="subheader"><h3>πŸš€ Spaces Leaders</h3></div>', unsafe_allow_html=True)
762
+
763
+ # Create a data frame for the Spaces table with medals for top 3
764
+ if top_owners_spaces:
765
+ spaces_data = []
766
+ for idx, (owner, count) in enumerate(top_owners_spaces[:50]):
767
+ # Add medal emojis for top 3
768
+ rank_display = ""
769
+ if idx == 0:
770
+ rank_display = "πŸ₯‡ " # Gold medal for 1st place
771
+ elif idx == 1:
772
+ rank_display = "πŸ₯ˆ " # Silver medal for 2nd place
773
+ elif idx == 2:
774
+ rank_display = "πŸ₯‰ " # Bronze medal for 3rd place
775
+
776
+ spaces_data.append([f"{rank_display}{owner}", count])
777
+
778
+ ranking_data_spaces = pd.DataFrame(spaces_data, columns=["Contributor", "Spaces Count(Top 500 positions)"])
779
+ ranking_data_spaces.index = ranking_data_spaces.index + 1 # Start index from 1 for ranking
780
+
781
+ st.dataframe(
782
+ ranking_data_spaces,
783
+ column_config={
784
+ "Contributor": st.column_config.TextColumn("Contributor"),
785
+ "Spaces Count": st.column_config.NumberColumn("Spaces Count", format="%d")
786
+ },
787
+ use_container_width=True,
788
+ hide_index=False
789
+ )
790
+
791
+ # Display the top Models accounts list with medals for top 3
792
+ st.markdown('<div class="subheader"><h3>🧠 Models Leaders</h3></div>', unsafe_allow_html=True)
793
+
794
+ # Create a data frame for the Models table with medals for top 3
795
+ if top_owners_models:
796
+ models_data = []
797
+ for idx, (owner, count) in enumerate(top_owners_models[:50]):
798
+ # Add medal emojis for top 3
799
+ rank_display = ""
800
+ if idx == 0:
801
+ rank_display = "πŸ₯‡ " # Gold medal for 1st place
802
+ elif idx == 1:
803
+ rank_display = "πŸ₯ˆ " # Silver medal for 2nd place
804
+ elif idx == 2:
805
+ rank_display = "πŸ₯‰ " # Bronze medal for 3rd place
806
+
807
+ models_data.append([f"{rank_display}{owner}", count])
808
+
809
+ ranking_data_models = pd.DataFrame(models_data, columns=["Contributor", "Models Count(Top 500 positions)"])
810
+ ranking_data_models.index = ranking_data_models.index + 1 # Start index from 1 for ranking
811
+
812
+ st.dataframe(
813
+ ranking_data_models,
814
+ column_config={
815
+ "Contributor": st.column_config.TextColumn("Contributor"),
816
+ "Models Count": st.column_config.NumberColumn("Models Count", format="%d")
817
+ },
818
+ use_container_width=True,
819
+ hide_index=False
820
+ )
821
+
822
+ # Add visual divider
823
+ st.markdown('<hr style="margin: 2rem 0; border-color: #e0e0e0;">', unsafe_allow_html=True)
824
+
825
+ # Display contributor selection with enhanced styling
826
+ st.markdown('<div class="subheader"><h3>Select Contributor</h3></div>', unsafe_allow_html=True)
827
+ selected_trending = st.selectbox(
828
+ "Choose from trending accounts",
829
+ options=trending_accounts[:100], # Limit to top 100
830
+ index=0 if trending_accounts else None,
831
+ key="trending_selectbox"
832
+ )
833
+
834
+ # Custom account input option with enhanced styling
835
+ st.markdown('<div style="text-align: center; margin: 15px 0; font-weight: bold;">- OR -</div>', unsafe_allow_html=True)
836
+ custom = st.text_input("Enter a username/organization:", placeholder="e.g. facebook, google...")
837
+
838
+ # Add visual divider
839
+ st.markdown('<hr style="margin: 1.5rem 0; border-color: #e0e0e0;">', unsafe_allow_html=True)
840
+
841
+ # Set username based on selection or custom input
842
+ if custom.strip():
843
+ username = custom.strip()
844
+ elif selected_trending:
845
+ username = selected_trending
846
+ else:
847
+ username = "facebook" # Default fallback
848
+
849
+ # Year selection with enhanced styling
850
+ st.markdown('<div class="subheader"><h3>πŸ—“οΈ Time Period</h3></div>', unsafe_allow_html=True)
851
+ year_options = list(range(datetime.now().year, 2017, -1))
852
+ selected_year = st.selectbox("Select Year:", options=year_options)
853
+
854
+ # Additional options for customization with enhanced styling
855
+ st.markdown('<div class="subheader"><h3>βš™οΈ Display Options</h3></div>', unsafe_allow_html=True)
856
+ show_models = st.checkbox("Show Models", value=True)
857
+ show_datasets = st.checkbox("Show Datasets", value=True)
858
+ show_spaces = st.checkbox("Show Spaces", value=True)
859
+
860
+ # Main Content
861
+ st.markdown(f'<h1 style="text-align: center; color: #1E88E5; margin-bottom: 2rem;">πŸ€— Hugging Face Contributions</h1>', unsafe_allow_html=True)
862
+
863
+ if username:
864
+ # Create a header card with contributor info
865
+ header_col1, header_col2 = st.columns([1, 2])
866
+ with header_col1:
867
+ st.markdown(f'<div style="background-color: #E3F2FD; padding: 20px; border-radius: 10px; border-left: 5px solid #1E88E5;">'
868
+ f'<h2 style="color: #1E88E5;">πŸ‘€ {username}</h2>'
869
+ f'<p style="font-size: 16px;">Analyzing contributions for {selected_year}</p>'
870
+ f'<p><a href="https://huggingface.co/{username}" target="_blank" style="color: #1E88E5; font-weight: bold;">View Profile</a></p>'
871
+ f'</div>', unsafe_allow_html=True)
872
+
873
+ with header_col2:
874
+ # Add explanation about the app
875
+ st.markdown(f'<div style="background-color: #F3E5F5; padding: 20px; border-radius: 10px; border-left: 5px solid #9C27B0;">'
876
+ f'<h3 style="color: #9C27B0;">About This Analysis</h3>'
877
+ f'<p>This dashboard analyzes {username}\'s contributions to Hugging Face in {selected_year}, including models, datasets, and spaces.</p>'
878
+ f'<p style="font-style: italic; font-size: 12px;">* Some metrics like follower growth are simulated for visualization purposes.</p>'
879
+ f'</div>', unsafe_allow_html=True)
880
+
881
+ with st.spinner(f"Fetching contribution data for {username}..."):
882
+ # Initialize variables for tracking
883
+ overall_rank = None
884
+ spaces_rank = None
885
+ models_rank = None
886
+ spaces_count = 0
887
+ models_count = 0
888
+ datasets_count = 0
889
+
890
+ # Display contributor rank if in top 100
891
+ if username in trending_accounts[:100]:
892
+ overall_rank = trending_accounts.index(username) + 1
893
+
894
+ # Create a prominent ranking display
895
+ st.markdown(f'<div style="background-color: #FFF8E1; padding: 20px; border-radius: 10px; border-left: 5px solid #FFC107; margin: 1rem 0;">'
896
+ f'<h2 style="color: #FFA000; text-align: center;">πŸ† Ranked #{overall_rank} in Top Contributors</h2>'
897
+ f'</div>', unsafe_allow_html=True)
898
+
899
+ # Find user in spaces ranking
900
+ for i, (owner, count) in enumerate(top_owners_spaces):
901
+ if owner == username:
902
+ spaces_rank = i+1
903
+ spaces_count = count
904
+ break
905
+
906
+ # Find user in models ranking
907
+ for i, (owner, count) in enumerate(top_owners_models):
908
+ if owner == username:
909
+ models_rank = i+1
910
+ models_count = count
911
+ break
912
+
913
+ # Display ranking visualization
914
+ rank_chart = create_ranking_chart(username, overall_rank, spaces_rank, models_rank)
915
+ if rank_chart:
916
+ st.pyplot(rank_chart)
917
+
918
+ # Create a dictionary to store commits by type
919
+ commits_by_type = {}
920
+ commit_counts_by_type = {}
921
+
922
+ # Determine which types to fetch based on checkboxes
923
+ types_to_fetch = []
924
+ if show_models:
925
+ types_to_fetch.append("model")
926
+ if show_datasets:
927
+ types_to_fetch.append("dataset")
928
+ if show_spaces:
929
+ types_to_fetch.append("space")
930
+
931
+ if not types_to_fetch:
932
+ st.warning("Please select at least one content type to display (Models, Datasets, or Spaces)")
933
+ st.stop()
934
+
935
+ # Create a progress container
936
+ progress_container = st.container()
937
+ progress_container.markdown('<h3 style="color: #1E88E5;">Fetching Repository Data...</h3>', unsafe_allow_html=True)
938
+ progress_bar = progress_container.progress(0)
939
+
940
+ # Fetch commits for each selected type
941
+ for type_index, kind in enumerate(types_to_fetch):
942
+ try:
943
+ items = cached_list_items(username, kind)
944
+
945
+ # Update counts for radar chart
946
+ if kind == "model":
947
+ models_count = len(items)
948
+ elif kind == "dataset":
949
+ datasets_count = len(items)
950
+ elif kind == "space":
951
+ spaces_count = len(items)
952
+
953
+ repo_ids = [item.id for item in items]
954
+
955
+ progress_container.info(f"Found {len(repo_ids)} {kind}s for {username}")
956
+
957
+ # Process repos in chunks
958
+ chunk_size = 5
959
+ total_commits = 0
960
+ all_commit_dates = []
961
+
962
+ for i in range(0, len(repo_ids), chunk_size):
963
+ chunk = repo_ids[i:i + chunk_size]
964
+ with ThreadPoolExecutor(max_workers=min(5, len(chunk))) as executor:
965
+ future_to_repo = {
966
+ executor.submit(fetch_commits_for_repo, repo_id, kind, username, selected_year): repo_id
967
+ for repo_id in chunk
968
+ }
969
+ for future in as_completed(future_to_repo):
970
+ repo_commits, repo_count = future.result()
971
+ if repo_commits:
972
+ all_commit_dates.extend(repo_commits)
973
+ total_commits += repo_count
974
+
975
+ # Update progress for all types
976
+ progress_per_type = 1.0 / len(types_to_fetch)
977
+ current_type_progress = min(1.0, (i + len(chunk)) / max(1, len(repo_ids)))
978
+ overall_progress = (type_index * progress_per_type) + (current_type_progress * progress_per_type)
979
+ progress_bar.progress(overall_progress)
980
+
981
+ commits_by_type[kind] = all_commit_dates
982
+ commit_counts_by_type[kind] = total_commits
983
+
984
+ except Exception as e:
985
+ st.warning(f"Error fetching {kind}s for {username}: {str(e)}")
986
+ commits_by_type[kind] = []
987
+ commit_counts_by_type[kind] = 0
988
+
989
+ # Complete progress
990
+ progress_bar.progress(1.0)
991
+ progress_container.success("Data fetching complete!")
992
+ time.sleep(0.5) # Short pause for visual feedback
993
+ progress_container.empty() # Clear the progress indicators
994
+
995
+ # Calculate total commits across all types
996
+ total_commits = sum(commit_counts_by_type.values())
997
+
998
+ # Main dashboard layout with improved structure
999
+ 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)
1000
+
1001
+ # Profile summary
1002
+ profile_col1, profile_col2 = st.columns([1, 2])
1003
+
1004
+ with profile_col1:
1005
+ # Create a stats card with key metrics
1006
+ st.markdown(f'<div style="background-color: white; padding: 20px; border-radius: 10px; box-shadow: 0 2px 10px rgba(0,0,0,0.1);">'
1007
+ f'<h3 style="color: #1E88E5; text-align: center; margin-bottom: 15px;">Contribution Stats</h3>'
1008
+ f'<div style="display: flex; justify-content: space-between; margin-bottom: 10px;">'
1009
+ f'<span style="font-weight: bold;">Total Commits:</span><span>{total_commits}</span></div>'
1010
+ f'<div style="display: flex; justify-content: space-between; margin-bottom: 10px;">'
1011
+ f'<span style="font-weight: bold;">Models:</span><span>{models_count}</span></div>'
1012
+ f'<div style="display: flex; justify-content: space-between; margin-bottom: 10px;">'
1013
+ f'<span style="font-weight: bold;">Datasets:</span><span>{datasets_count}</span></div>'
1014
+ f'<div style="display: flex; justify-content: space-between; margin-bottom: 10px;">'
1015
+ f'<span style="font-weight: bold;">Spaces:</span><span>{spaces_count}</span></div>'
1016
+ f'</div>', unsafe_allow_html=True)
1017
+
1018
+ # Type breakdown pie chart
1019
+ model_commits = commit_counts_by_type.get("model", 0)
1020
+ dataset_commits = commit_counts_by_type.get("dataset", 0)
1021
+ space_commits = commit_counts_by_type.get("space", 0)
1022
+
1023
+ pie_chart = create_contribution_pie(model_commits, dataset_commits, space_commits)
1024
+ if pie_chart:
1025
+ st.pyplot(pie_chart)
1026
+
1027
+ with profile_col2:
1028
+ # Display contribution radar chart
1029
+ radar_fig = create_contribution_radar(username, models_count, spaces_count, datasets_count, total_commits)
1030
+ st.pyplot(radar_fig)
1031
+
1032
+ # Create DataFrame for all commits
1033
+ all_commits = []
1034
+ for commits in commits_by_type.values():
1035
+ all_commits.extend(commits)
1036
+ all_df = pd.DataFrame(all_commits, columns=["date"])
1037
+ if not all_df.empty:
1038
+ all_df = all_df.drop_duplicates() # Remove any duplicate dates
1039
+
1040
+ # Calendar heatmap for all commits in a separate section
1041
+ 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)
1042
+
1043
+ if not all_df.empty:
1044
+ make_calendar_heatmap(all_df, "All Contributions", selected_year)
1045
+ else:
1046
+ st.info(f"No contributions found for {username} in {selected_year}")
1047
+
1048
+ # Monthly activity chart
1049
+ 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)
1050
+
1051
+ monthly_fig = create_monthly_activity(all_df, selected_year)
1052
+ if monthly_fig:
1053
+ st.pyplot(monthly_fig)
1054
+ else:
1055
+ st.info(f"No activity data available for {username} in {selected_year}")
1056
+
1057
+ # Follower growth simulation
1058
+ 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)
1059
+ st.markdown('<div style="background-color: #EDE7F6; padding: 10px; border-radius: 5px; margin-bottom: 15px;">'
1060
+ '<p style="font-style: italic; margin: 0;">πŸ“Š This is a simulation based on contribution metrics - for visualization purposes only</p>'
1061
+ '</div>', unsafe_allow_html=True)
1062
+
1063
+ follower_chart = simulate_follower_data(username, spaces_count, models_count, total_commits)
1064
+ st.pyplot(follower_chart)
1065
+
1066
+ # Analytics summary section
1067
+ if total_commits > 0:
1068
+ 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)
1069
+
1070
+ # Contribution pattern analysis
1071
+ monthly_df = pd.DataFrame(all_commits, columns=["date"])
1072
+ monthly_df['date'] = pd.to_datetime(monthly_df['date'])
1073
+ monthly_df['month'] = monthly_df['date'].dt.month
1074
+
1075
+ if not monthly_df.empty:
1076
+ most_active_month = monthly_df['month'].value_counts().idxmax()
1077
+ month_name = datetime(2020, most_active_month, 1).strftime('%B')
1078
+
1079
+ # Create a summary card
1080
+ st.markdown(f'<div style="background-color: white; padding: 25px; border-radius: 10px; box-shadow: 0 2px 10px rgba(0,0,0,0.1);">'
1081
+ f'<h3 style="color: #1E88E5; border-bottom: 1px solid #E0E0E0; padding-bottom: 10px;">Activity Analysis for {username}</h3>'
1082
+ f'<ul style="list-style-type: none; padding-left: 5px;">'
1083
+ f'<li style="margin: 15px 0; font-size: 16px;">πŸ“ˆ <strong>Total Activity:</strong> {total_commits} contributions in {selected_year}</li>'
1084
+ 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>'
1085
+ f'<li style="margin: 15px 0; font-size: 16px;">🧩 <strong>Repository Breakdown:</strong> {models_count} Models, {spaces_count} Spaces, {datasets_count} Datasets</li>'
1086
+ f'</ul>', unsafe_allow_html=True)
1087
+
1088
+ # Add ranking context if available
1089
+ if overall_rank:
1090
+ percentile = 100 - overall_rank
1091
+ st.markdown(f'<div style="margin-top: 20px;">'
1092
+ f'<h3 style="color: #1E88E5; border-bottom: 1px solid #E0E0E0; padding-bottom: 10px;">Ranking Analysis</h3>'
1093
+ f'<ul style="list-style-type: none; padding-left: 5px;">'
1094
+ f'<li style="margin: 15px 0; font-size: 16px;">πŸ† <strong>Overall Ranking:</strong> #{overall_rank} (Top {percentile}% of contributors)</li>', unsafe_allow_html=True)
1095
+
1096
+ badge_html = '<div style="margin: 20px 0;">'
1097
+
1098
+ if spaces_rank and spaces_rank <= 10:
1099
+ 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>'
1100
+ elif spaces_rank and spaces_rank <= 30:
1101
+ 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>'
1102
+
1103
+ if models_rank and models_rank <= 10:
1104
+ 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>'
1105
+ elif models_rank and models_rank <= 30:
1106
+ 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>'
1107
+
1108
+ badge_html += '</div>'
1109
+
1110
+ # Add achievement badges
1111
+ if spaces_rank or models_rank:
1112
+ st.markdown(badge_html, unsafe_allow_html=True)
1113
+
1114
+ st.markdown('</ul></div></div>', unsafe_allow_html=True)
1115
+
1116
+ # Detailed category analysis section
1117
+ 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)
1118
+
1119
+ # Create category cards in columns
1120
+ cols = st.columns(len(types_to_fetch)) if types_to_fetch else st.columns(1)
1121
+
1122
+ category_icons = {
1123
+ "model": "🧠",
1124
+ "dataset": "πŸ“¦",
1125
+ "space": "πŸš€"
1126
+ }
1127
+
1128
+ category_colors = {
1129
+ "model": "#FF9800",
1130
+ "dataset": "#2196F3",
1131
+ "space": "#4CAF50"
1132
+ }
1133
+
1134
+ for i, kind in enumerate(types_to_fetch):
1135
+ with cols[i]:
1136
+ try:
1137
+ emoji = category_icons.get(kind, "πŸ“Š")
1138
+ label = kind.capitalize() + "s"
1139
+ color = category_colors.get(kind, "#1E88E5")
1140
+
1141
+ total = len(cached_list_items(username, kind))
1142
+ commits = commits_by_type.get(kind, [])
1143
+ commit_count = commit_counts_by_type.get(kind, 0)
1144
+
1145
+ # Create styled card header
1146
+ 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};">'
1147
+ f'<h3 style="color: {color}; text-align: center;">{emoji} {label}</h3>'
1148
+ f'<div style="display: flex; justify-content: space-between; margin: 15px 0;">'
1149
+ f'<span style="font-weight: bold;">Total:</span><span>{total}</span></div>'
1150
+ f'<div style="display: flex; justify-content: space-between; margin-bottom: 15px;">'
1151
+ f'<span style="font-weight: bold;">Commits:</span><span>{commit_count}</span></div>'
1152
+ f'</div>', unsafe_allow_html=True)
1153
+
1154
+ # Create calendar for this type
1155
+ df_kind = pd.DataFrame(commits, columns=["date"])
1156
+ if not df_kind.empty:
1157
+ df_kind = df_kind.drop_duplicates() # Remove any duplicate dates
1158
+ make_calendar_heatmap(df_kind, f"{label} Commits", selected_year)
1159
+ else:
1160
+ st.info(f"No {label.lower()} activity in {selected_year}")
1161
+
1162
+ except Exception as e:
1163
+ st.warning(f"Error processing {kind.capitalize()}s: {str(e)}")
1164
+ # Show empty placeholder
1165
+ 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;">'
1166
+ f'<h3 style="color: #9E9E9E;">⚠️ Error</h3>'
1167
+ f'<p>Could not load {kind.capitalize()}s data</p>'
1168
+ f'</div>', unsafe_allow_html=True)
1169
+
1170
+ # Footer
1171
+ st.markdown('<hr style="margin: 3rem 0 1rem 0;">', unsafe_allow_html=True)
1172
+ 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)
1173
+ else:
1174
+ # If no username is selected, show welcome screen
1175
+ st.markdown(f'<div style="text-align: center; margin: 50px 0;">'
1176
+ f'<img src="https://huggingface.co/datasets/huggingface/brand-assets/resolve/main/hf-logo.svg" style="width: 200px; margin-bottom: 30px;">'
1177
+ f'<h2>Welcome to Hugging Face Contributions Dashboard</h2>'
1178
+ f'<p style="font-size: 1.2rem;">Please select a contributor from the sidebar to view their activity.</p>'
1179
+ f'</div>', unsafe_allow_html=True)