kolaslab commited on
Commit
32efa22
Β·
verified Β·
1 Parent(s): 5e1377b

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +515 -268
app.py CHANGED
@@ -9,32 +9,133 @@ from functools import lru_cache
9
  import time
10
  import requests
11
  from collections import Counter
 
12
 
13
  st.set_page_config(page_title="HF Contributions", layout="wide", initial_sidebar_state="expanded")
14
 
15
- # Set custom sidebar width - UPDATED to 40% of the screen
16
  st.markdown("""
17
  <style>
 
18
  [data-testid="stSidebar"] {
19
- min-width: 40vw !important;
20
- max-width: 40vw !important;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
  }
22
  </style>
23
  """, unsafe_allow_html=True)
24
- api = HfApi()
25
 
 
26
 
27
  # Cache for API responses
28
  @lru_cache(maxsize=1000)
29
  def cached_repo_info(repo_id, repo_type):
30
  return api.repo_info(repo_id=repo_id, repo_type=repo_type)
31
 
32
-
33
  @lru_cache(maxsize=1000)
34
  def cached_list_commits(repo_id, repo_type):
35
  return list(api.list_repo_commits(repo_id=repo_id, repo_type=repo_type))
36
 
37
-
38
  @lru_cache(maxsize=100)
39
  def cached_list_items(username, kind):
40
  if kind == "model":
@@ -45,7 +146,6 @@ def cached_list_items(username, kind):
45
  return list(api.list_spaces(author=username))
46
  return []
47
 
48
-
49
  # Function to fetch trending accounts and create stats
50
  @lru_cache(maxsize=1)
51
  def get_trending_accounts(limit=100):
@@ -126,7 +226,6 @@ def get_trending_accounts(limit=100):
126
  fallback_authors = ["ritvik77", "facebook", "google", "stabilityai", "Salesforce", "tiiuae", "bigscience"]
127
  return fallback_authors, [(author, 0) for author in fallback_authors], [(author, 0) for author in fallback_authors]
128
 
129
-
130
  # Rate limiting
131
  class RateLimiter:
132
  def __init__(self, calls_per_second=10):
@@ -140,10 +239,8 @@ class RateLimiter:
140
  time.sleep((1.0 / self.calls_per_second) - time_since_last_call)
141
  self.last_call = time.time()
142
 
143
-
144
  rate_limiter = RateLimiter()
145
 
146
-
147
  # Function to fetch commits for a repository (optimized)
148
  def fetch_commits_for_repo(repo_id, repo_type, username, selected_year):
149
  try:
@@ -151,7 +248,7 @@ def fetch_commits_for_repo(repo_id, repo_type, username, selected_year):
151
  # Skip private/gated repos upfront
152
  repo_info = cached_repo_info(repo_id, repo_type)
153
  if repo_info.private or (hasattr(repo_info, 'gated') and repo_info.gated):
154
- return [], []
155
 
156
  # Get initial commit date
157
  initial_commit_date = pd.to_datetime(repo_info.created_at).tz_localize(None).date()
@@ -172,10 +269,9 @@ def fetch_commits_for_repo(repo_id, repo_type, username, selected_year):
172
  commit_count += 1
173
 
174
  return commit_dates, commit_count
175
- except Exception:
176
  return [], 0
177
 
178
-
179
  # Function to get commit events for a user (optimized)
180
  def get_commit_events(username, kind=None, selected_year=None):
181
  commit_dates = []
@@ -210,7 +306,6 @@ def get_commit_events(username, kind=None, selected_year=None):
210
  df = df.drop_duplicates() # Remove any duplicate dates
211
  return df, items_with_type
212
 
213
-
214
  # Calendar heatmap function (optimized)
215
  def make_calendar_heatmap(df, title, year):
216
  if df.empty:
@@ -252,7 +347,7 @@ def make_calendar_heatmap(df, title, year):
252
  norm = BoundaryNorm(bounds, cmap.N)
253
 
254
  # Create plot more efficiently
255
- fig, ax = plt.subplots(figsize=(12, 1.2))
256
 
257
  # Convert pivot values to integers to ensure proper color mapping
258
  pivot_int = pivot.astype(int)
@@ -261,15 +356,19 @@ def make_calendar_heatmap(df, title, year):
261
  sns.heatmap(pivot_int, ax=ax, cmap=cmap, norm=norm, linewidths=0.5, linecolor="white",
262
  square=True, cbar=False, yticklabels=["M", "T", "W", "T", "F", "S", "S"])
263
 
264
- ax.set_title(f"{title}", fontsize=12, pad=10)
265
  ax.set_xlabel("")
266
  ax.set_ylabel("")
267
  ax.set_xticks(month_positions)
268
- ax.set_xticklabels(month_labels, fontsize=8)
269
- ax.set_yticklabels(ax.get_yticklabels(), rotation=0, fontsize=8)
 
 
 
 
 
270
  st.pyplot(fig)
271
 
272
-
273
  # Function to create a fancy contribution radar chart
274
  def create_contribution_radar(username, models_count, spaces_count, datasets_count, commits_count):
275
  # Create radar chart for contribution metrics
@@ -286,31 +385,41 @@ def create_contribution_radar(username, models_count, spaces_count, datasets_cou
286
 
287
  normalized += normalized[:1] # Close the loop
288
 
289
- fig, ax = plt.subplots(figsize=(6, 6), subplot_kw={'polar': True})
290
 
291
- # Add background grid
292
  ax.set_theta_offset(np.pi / 2)
293
  ax.set_theta_direction(-1)
294
- ax.set_thetagrids(np.degrees(angles[:-1]), categories)
295
 
296
- # Draw the chart
 
 
 
297
  ax.fill(angles, normalized, color='#4CAF50', alpha=0.25)
298
- ax.plot(angles, normalized, color='#4CAF50', linewidth=2)
299
 
300
- # Add value labels
301
  for i, val in enumerate(values):
302
  angle = angles[i]
303
- x = normalized[i] * np.cos(angle)
304
- y = normalized[i] * np.sin(angle)
305
- ax.text(angle, normalized[i] + 0.05, str(val),
306
- ha='center', va='center', fontsize=10,
307
- fontweight='bold')
 
 
 
 
 
308
 
309
- ax.set_title(f"{username}'s Contribution Profile", fontsize=15, pad=20)
 
 
 
310
 
311
  return fig
312
 
313
-
314
  # Function to create contribution distribution pie chart
315
  def create_contribution_pie(model_commits, dataset_commits, space_commits):
316
  labels = ['Models', 'Datasets', 'Spaces']
@@ -323,22 +432,48 @@ def create_contribution_pie(model_commits, dataset_commits, space_commits):
323
  if not filtered_sizes:
324
  return None # No data to show
325
 
326
- fig, ax = plt.subplots(figsize=(6, 6))
327
  colors = ['#FF9800', '#2196F3', '#4CAF50']
328
  filtered_colors = [color for color, size in zip(colors, sizes) if size > 0]
329
 
330
- # Create exploded pie chart
331
- explode = [0.05] * len(filtered_sizes) # Explode all slices slightly
332
 
333
- ax.pie(filtered_sizes, labels=filtered_labels, colors=filtered_colors,
334
- autopct='%1.1f%%', startangle=90, shadow=True, explode=explode)
335
- ax.axis('equal') # Equal aspect ratio ensures that pie is drawn as a circle
336
 
337
- ax.set_title('Distribution of Contributions by Type', fontsize=15)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
338
 
339
  return fig
340
 
341
-
342
  # Function to create monthly activity chart
343
  def create_monthly_activity(df, year):
344
  if df.empty:
@@ -346,42 +481,58 @@ def create_monthly_activity(df, year):
346
 
347
  # Aggregate by month
348
  df['date'] = pd.to_datetime(df['date'])
349
- df['month'] = df['date'].dt.strftime('%b')
350
- monthly_counts = df.groupby('month')['date'].count().reindex(
351
- pd.date_range(start=f'{year}-01-01', end=f'{year}-12-31', freq='MS').strftime('%b')
352
- ).fillna(0)
353
 
354
- # Create bar chart
355
- fig, ax = plt.subplots(figsize=(12, 5))
356
- months = monthly_counts.index
357
- counts = monthly_counts.values
358
 
359
- bars = ax.bar(months, counts, color='#2196F3')
 
 
 
 
 
 
 
360
 
361
  # Highlight the month with most activity
362
- if counts.max() > 0:
363
- max_idx = counts.argmax()
364
  bars[max_idx].set_color('#FF5722')
 
 
365
 
366
- # Add labels and styling
367
- ax.set_title(f'Monthly Activity in {year}', fontsize=15)
368
- ax.set_xlabel('Month', fontsize=12)
369
- ax.set_ylabel('Number of Contributions', fontsize=12)
370
 
371
- # Add value labels on top of bars
372
- for i, count in enumerate(counts):
373
  if count > 0:
374
- ax.text(i, count + 0.5, str(int(count)), ha='center', fontsize=10)
375
 
376
- # Add grid for better readability
377
- ax.grid(axis='y', linestyle='--', alpha=0.7)
 
 
 
 
 
 
 
 
 
 
 
378
 
379
- plt.xticks(rotation=45)
380
  plt.tight_layout()
381
 
382
  return fig
383
 
384
-
385
  # Function to render follower growth simulation
386
  def simulate_follower_data(username, spaces_count, models_count, total_commits):
387
  # Simulate follower growth based on contribution metrics
@@ -409,73 +560,137 @@ def simulate_follower_data(username, spaces_count, models_count, total_commits):
409
  # Ensure end value matches our base_followers estimate
410
  followers[-1] = base_followers
411
 
412
- # Create the chart
413
- fig, ax = plt.subplots(figsize=(12, 5))
414
- ax.plot(dates, followers, marker='o', linestyle='-', color='#9C27B0', markersize=5)
415
 
416
- # Add styling
417
- ax.set_title(f"Estimated Follower Growth for {username}", fontsize=15)
418
- ax.set_xlabel("Date", fontsize=12)
419
- ax.set_ylabel("Followers", fontsize=12)
420
 
421
- # Add grid for better readability
422
- ax.grid(True, linestyle='--', alpha=0.7)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
423
 
424
- # Format date axis
425
- plt.xticks(rotation=45)
426
  plt.tight_layout()
427
 
428
  return fig
429
 
430
-
431
  # Function to create ranking position visualization
432
  def create_ranking_chart(username, overall_rank, spaces_rank, models_rank):
433
  if not (overall_rank or spaces_rank or models_rank):
434
  return None
435
 
436
- # Create a horizontal bar chart for rankings
437
- fig, ax = plt.subplots(figsize=(10, 4))
438
 
439
  categories = []
440
  positions = []
441
  colors = []
 
442
 
443
  if overall_rank:
444
  categories.append('Overall')
445
  positions.append(101 - overall_rank) # Invert rank for visualization (higher is better)
446
  colors.append('#673AB7')
 
447
 
448
  if spaces_rank:
449
  categories.append('Spaces')
450
  positions.append(101 - spaces_rank)
451
  colors.append('#2196F3')
 
452
 
453
  if models_rank:
454
  categories.append('Models')
455
  positions.append(101 - models_rank)
456
  colors.append('#FF9800')
 
457
 
458
- # Create horizontal bars
459
- bars = ax.barh(categories, positions, color=colors, alpha=0.7)
 
460
 
461
- # Add rank values as text
462
  for i, bar in enumerate(bars):
463
- rank_val = 0
464
- if categories[i] == 'Overall': rank_val = overall_rank
465
- elif categories[i] == 'Spaces': rank_val = spaces_rank
466
- elif categories[i] == 'Models': rank_val = models_rank
467
-
468
- ax.text(bar.get_width() + 1, bar.get_y() + bar.get_height()/2,
469
- f'Rank #{rank_val}', va='center', fontsize=10, fontweight='bold')
 
 
 
 
 
470
 
471
- # Set chart properties
472
- ax.set_xlim(0, 100)
473
- ax.set_title(f"Ranking Positions for {username} (Top 100)", fontsize=15)
474
- ax.set_xlabel("Percentile (higher is better)", fontsize=12)
475
 
476
- # Add a vertical line at 90th percentile to highlight top 10
477
- ax.axvline(x=90, color='red', linestyle='--', alpha=0.5)
478
- ax.text(91, 0.5, 'Top 10', color='red', fontsize=10, rotation=90, va='center')
 
 
 
 
 
 
 
 
 
 
479
 
480
  # Invert x-axis to show ranking position more intuitively
481
  ax.invert_xaxis()
@@ -483,30 +698,23 @@ def create_ranking_chart(username, overall_rank, spaces_rank, models_rank):
483
  plt.tight_layout()
484
  return fig
485
 
486
-
487
- # Import additional libraries for advanced visualizations
488
- import numpy as np
489
-
490
  # Fetch trending accounts with a loading spinner (do this once at the beginning)
491
  with st.spinner("Loading trending accounts..."):
492
  trending_accounts, top_owners_spaces, top_owners_models = get_trending_accounts(limit=100)
493
 
494
  # Sidebar
495
  with st.sidebar:
496
- st.title("πŸ‘€ Contributor")
497
 
498
  # Create tabs for Spaces and Models rankings - ONLY SHOWING FIRST TWO TABS
499
  tab1, tab2 = st.tabs([
500
- "Top 100 Overall Contributors",
501
- "Top 100 by Spaces & Models"
502
  ])
503
 
504
  with tab1:
505
  # Show combined trending accounts list
506
- st.subheader("πŸ”₯ Top 100 Overall Contributors")
507
-
508
- # Display the top 100 accounts list
509
- st.markdown("### Combined Contributors Ranking")
510
 
511
  # Create a data frame for the table
512
  if trending_accounts:
@@ -532,101 +740,68 @@ with st.sidebar:
532
  ranking_data_overall,
533
  column_config={
534
  "Contributor": st.column_config.TextColumn("Contributor"),
535
- "Spaces Rank": st.column_config.TextColumn("Spaces Rank (top 100)"),
536
- "Models Rank": st.column_config.TextColumn("Models Rank (top 100)")
537
  },
538
  use_container_width=True,
539
  hide_index=False
540
  )
541
 
542
  with tab2:
543
- # Show trending accounts list by Spaces
544
- st.subheader("πŸš€ Top 100 by Spaces & Models")
545
-
546
- # Display the top 100 accounts list
547
- st.markdown("### Spaces Contributors Ranking")
548
 
549
  # Create a data frame for the table
550
  if top_owners_spaces:
551
- ranking_data_spaces = pd.DataFrame(top_owners_spaces[:100], columns=["Contributor", "Spaces Count"])
552
  ranking_data_spaces.index = ranking_data_spaces.index + 1 # Start index from 1 for ranking
553
 
554
  st.dataframe(
555
  ranking_data_spaces,
556
  column_config={
557
  "Contributor": st.column_config.TextColumn("Contributor"),
558
- "Spaces Count": st.column_config.NumberColumn("Spaces Count (based on top 500 spaces)", format="%d")
559
  },
560
  use_container_width=True,
561
  hide_index=False
562
  )
563
 
564
- # Add stats expander with visualization
565
- with st.expander("View Top 30 Spaces Contributors Chart"):
566
- # Create a bar chart for top 30 contributors
567
- if top_owners_spaces:
568
- chart_data = pd.DataFrame(top_owners_spaces[:30], columns=["Owner", "Spaces Count"])
569
-
570
- fig, ax = plt.subplots(figsize=(10, 8))
571
- bars = ax.barh(chart_data["Owner"], chart_data["Spaces Count"])
572
-
573
- # Add color gradient to bars
574
- for i, bar in enumerate(bars):
575
- bar.set_color(plt.cm.viridis(i/len(bars)))
576
-
577
- ax.set_title("Top 30 Contributors by Number of Spaces")
578
- ax.set_xlabel("Number of Spaces")
579
- plt.tight_layout()
580
- st.pyplot(fig)
581
-
582
- # Display the top 100 Models accounts list (ADDED SECTION)
583
- st.markdown("### Models Contributors Ranking")
584
 
585
  # Create a data frame for the Models table
586
  if top_owners_models:
587
- ranking_data_models = pd.DataFrame(top_owners_models[:100], columns=["Contributor", "Models Count"])
588
  ranking_data_models.index = ranking_data_models.index + 1 # Start index from 1 for ranking
589
 
590
  st.dataframe(
591
  ranking_data_models,
592
  column_config={
593
  "Contributor": st.column_config.TextColumn("Contributor"),
594
- "Models Count": st.column_config.NumberColumn("Models Count (based on top 500 models)", format="%d")
595
  },
596
  use_container_width=True,
597
  hide_index=False
598
  )
599
-
600
- # Add stats expander with visualization for Models (ADDED SECTION)
601
- with st.expander("View Top 30 Models Contributors Chart"):
602
- # Create a bar chart for top 30 models contributors
603
- if top_owners_models:
604
- chart_data = pd.DataFrame(top_owners_models[:30], columns=["Owner", "Models Count"])
605
-
606
- fig, ax = plt.subplots(figsize=(10, 8))
607
- bars = ax.barh(chart_data["Owner"], chart_data["Models Count"])
608
-
609
- # Add color gradient to bars
610
- for i, bar in enumerate(bars):
611
- bar.set_color(plt.cm.plasma(i/len(bars))) # Using a different colormap for distinction
612
-
613
- ax.set_title("Top 30 Contributors by Number of Models")
614
- ax.set_xlabel("Number of Models")
615
- plt.tight_layout()
616
- st.pyplot(fig)
617
 
618
- # Display trending accounts selection dropdown
619
- st.subheader("Select Contributor")
 
 
 
620
  selected_trending = st.selectbox(
621
- "Select trending account",
622
  options=trending_accounts[:100], # Limit to top 100
623
  index=0 if trending_accounts else None,
624
  key="trending_selectbox"
625
  )
626
 
627
- # Custom account input option
628
- st.markdown("<div style='text-align: center; margin: 10px 0;'>OR</div>", unsafe_allow_html=True)
629
- custom = st.text_input("Enter username/org", label_visibility="collapsed")
 
 
 
630
 
631
  # Set username based on selection or custom input
632
  if custom.strip():
@@ -636,22 +811,39 @@ with st.sidebar:
636
  else:
637
  username = "facebook" # Default fallback
638
 
639
- # Year selection
640
- st.subheader("πŸ—“οΈ Time Period")
641
  year_options = list(range(datetime.now().year, 2017, -1))
642
- selected_year = st.selectbox("Select Year", options=year_options)
643
 
644
- # Additional options for customization
645
- st.subheader("βš™οΈ Display Options")
646
  show_models = st.checkbox("Show Models", value=True)
647
  show_datasets = st.checkbox("Show Datasets", value=True)
648
  show_spaces = st.checkbox("Show Spaces", value=True)
649
 
650
  # Main Content
651
- st.title("πŸ€— Hugging Face Contributions")
652
 
653
  if username:
654
- with st.spinner(f"Fetching commit data for {username}..."):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
655
  # Initialize variables for tracking
656
  overall_rank = None
657
  spaces_rank = None
@@ -663,14 +855,17 @@ if username:
663
  # Display contributor rank if in top 100
664
  if username in trending_accounts[:100]:
665
  overall_rank = trending_accounts.index(username) + 1
666
- st.success(f"πŸ† {username} is ranked #{overall_rank} in the top trending contributors!")
 
 
 
 
667
 
668
  # Find user in spaces ranking
669
  for i, (owner, count) in enumerate(top_owners_spaces):
670
  if owner == username:
671
  spaces_rank = i+1
672
  spaces_count = count
673
- st.info(f"πŸš€ Spaces Ranking: #{spaces_rank} with {count} spaces")
674
  break
675
 
676
  # Find user in models ranking
@@ -678,20 +873,9 @@ if username:
678
  if owner == username:
679
  models_rank = i+1
680
  models_count = count
681
- st.info(f"🧠 Models Ranking: #{models_rank} with {count} models")
682
  break
683
 
684
- # Display combined ranking info
685
- combined_info = []
686
- if spaces_rank and spaces_rank <= 100:
687
- combined_info.append(f"Spaces: #{spaces_rank}")
688
- if models_rank and models_rank <= 100:
689
- combined_info.append(f"Models: #{models_rank}")
690
-
691
- if combined_info:
692
- st.success(f"Combined Rankings (Top 100): {', '.join(combined_info)}")
693
-
694
- # Add ranking visualization
695
  rank_chart = create_ranking_chart(username, overall_rank, spaces_rank, models_rank)
696
  if rank_chart:
697
  st.pyplot(rank_chart)
@@ -713,8 +897,13 @@ if username:
713
  st.warning("Please select at least one content type to display (Models, Datasets, or Spaces)")
714
  st.stop()
715
 
 
 
 
 
 
716
  # Fetch commits for each selected type
717
- for kind in types_to_fetch:
718
  try:
719
  items = cached_list_items(username, kind)
720
 
@@ -728,14 +917,13 @@ if username:
728
 
729
  repo_ids = [item.id for item in items]
730
 
731
- st.info(f"Found {len(repo_ids)} {kind}s for {username}")
732
 
733
  # Process repos in chunks
734
  chunk_size = 5
735
  total_commits = 0
736
  all_commit_dates = []
737
 
738
- progress_bar = st.progress(0)
739
  for i in range(0, len(repo_ids), chunk_size):
740
  chunk = repo_ids[i:i + chunk_size]
741
  with ThreadPoolExecutor(max_workers=min(5, len(chunk))) as executor:
@@ -749,13 +937,12 @@ if username:
749
  all_commit_dates.extend(repo_commits)
750
  total_commits += repo_count
751
 
752
- # Update progress
753
- progress = min(1.0, (i + len(chunk)) / max(1, len(repo_ids)))
754
- progress_bar.progress(progress)
 
 
755
 
756
- # Complete progress
757
- progress_bar.progress(1.0)
758
-
759
  commits_by_type[kind] = all_commit_dates
760
  commit_counts_by_type[kind] = total_commits
761
 
@@ -764,72 +951,86 @@ if username:
764
  commits_by_type[kind] = []
765
  commit_counts_by_type[kind] = 0
766
 
 
 
 
 
 
 
767
  # Calculate total commits across all types
768
  total_commits = sum(commit_counts_by_type.values())
769
 
770
- st.subheader(f"{username}'s Activity in {selected_year}")
 
 
 
 
771
 
772
- # Profile information
773
- profile_col1, profile_col2 = st.columns([1, 3])
774
  with profile_col1:
775
- # Skip avatar image display since it's causing problems
776
- st.info(f"Profile: {username}")
777
- st.metric("Total Commits", total_commits)
 
 
 
 
 
 
 
 
 
778
 
779
- # Show contributor rank if in top owners
780
- for owner, count in top_owners_spaces:
781
- if owner.lower() == username.lower():
782
- st.metric("Spaces Count", count)
783
- break
784
 
785
- st.markdown(f"[View Profile on Hugging Face](https://huggingface.co/{username})")
 
 
786
 
787
  with profile_col2:
788
  # Display contribution radar chart
789
  radar_fig = create_contribution_radar(username, models_count, spaces_count, datasets_count, total_commits)
790
  st.pyplot(radar_fig)
791
 
792
- # Create DataFrame for all commits
793
- all_commits = []
794
- for commits in commits_by_type.values():
795
- all_commits.extend(commits)
796
- all_df = pd.DataFrame(all_commits, columns=["date"])
 
 
 
 
 
 
797
  if not all_df.empty:
798
- all_df = all_df.drop_duplicates() # Remove any duplicate dates
 
 
799
 
800
  # Monthly activity chart
801
- st.subheader(f"Monthly Activity Pattern ({selected_year})")
 
802
  monthly_fig = create_monthly_activity(all_df, selected_year)
803
  if monthly_fig:
804
  st.pyplot(monthly_fig)
805
  else:
806
  st.info(f"No activity data available for {username} in {selected_year}")
807
-
808
- # Calendar heatmap for all commits
809
- st.subheader(f"Contribution Calendar ({selected_year})")
810
- make_calendar_heatmap(all_df, "All Commits", selected_year)
811
-
812
- # Contribution distribution pie chart
813
- st.subheader("Contribution Distribution by Type")
814
- model_commits = commit_counts_by_type.get("model", 0)
815
- dataset_commits = commit_counts_by_type.get("dataset", 0)
816
- space_commits = commit_counts_by_type.get("space", 0)
817
-
818
- pie_chart = create_contribution_pie(model_commits, dataset_commits, space_commits)
819
- if pie_chart:
820
- st.pyplot(pie_chart)
821
- else:
822
- st.info("No contribution data available to show distribution")
823
 
824
  # Follower growth simulation
825
- st.subheader(f"Follower Growth Simulation")
826
- st.caption("Based on contribution metrics - for visualization purposes only")
 
 
 
827
  follower_chart = simulate_follower_data(username, spaces_count, models_count, total_commits)
828
  st.pyplot(follower_chart)
829
 
830
- # Add analysis message
831
  if total_commits > 0:
832
- st.subheader("πŸ“Š Analytics Summary")
833
 
834
  # Contribution pattern analysis
835
  monthly_df = pd.DataFrame(all_commits, columns=["date"])
@@ -840,58 +1041,104 @@ if username:
840
  most_active_month = monthly_df['month'].value_counts().idxmax()
841
  month_name = datetime(2020, most_active_month, 1).strftime('%B')
842
 
843
- st.markdown(f"""
844
- ### Activity Analysis for {username}
845
-
846
- - **Total Activity**: {total_commits} contributions in {selected_year}
847
- - **Most Active Month**: {month_name} with {monthly_df['month'].value_counts().max()} contributions
848
- - **Repository Breakdown**: {models_count} Models, {spaces_count} Spaces, {datasets_count} Datasets
849
- """)
 
850
 
851
  # Add ranking context if available
852
  if overall_rank:
853
  percentile = 100 - overall_rank
854
- st.markdown(f"""
855
- ### Ranking Analysis
 
 
856
 
857
- - **Overall Ranking**: #{overall_rank} (Top {percentile}% of contributors)
858
- """)
859
 
860
  if spaces_rank and spaces_rank <= 10:
861
- st.markdown(f"- 🌟 **Elite Spaces Contributor**: Top 10 ({spaces_rank}) in Spaces contributions")
862
  elif spaces_rank and spaces_rank <= 30:
863
- st.markdown(f"- ✨ **Outstanding Spaces Contributor**: Top 30 ({spaces_rank}) in Spaces contributions")
864
 
865
  if models_rank and models_rank <= 10:
866
- st.markdown(f"- 🌟 **Elite Models Contributor**: Top 10 ({models_rank}) in Models contributions")
867
  elif models_rank and models_rank <= 30:
868
- st.markdown(f"- ✨ **Outstanding Models Contributor**: Top 30 ({models_rank}) in Models contributions")
 
 
 
 
 
 
 
 
869
 
870
- # Metrics and heatmaps for each selected type
871
- st.subheader("Detailed Category Analysis")
 
 
872
  cols = st.columns(len(types_to_fetch)) if types_to_fetch else st.columns(1)
873
 
874
- for i, (kind, emoji, label) in enumerate([
875
- ("model", "🧠", "Models"),
876
- ("dataset", "πŸ“¦", "Datasets"),
877
- ("space", "πŸš€", "Spaces")
878
- ]):
879
- if kind in types_to_fetch:
880
- with cols[types_to_fetch.index(kind)]:
881
- try:
882
- total = len(cached_list_items(username, kind))
883
- commits = commits_by_type.get(kind, [])
884
- commit_count = commit_counts_by_type.get(kind, 0)
885
- df_kind = pd.DataFrame(commits, columns=["date"])
886
- if not df_kind.empty:
887
- df_kind = df_kind.drop_duplicates() # Remove any duplicate dates
888
- st.metric(f"{emoji} {label}", total)
889
- st.metric(f"Commits in {selected_year}", commit_count)
890
- make_calendar_heatmap(df_kind, f"{labels} Commits", selected_year)
891
- except Exception as e:
892
- st.warning(f"Error processing {label}: {str(e)}")
893
- st.metric(f"{emoji} {label}", 0)
894
- st.metric(f"Commits in {selected_year}", 0)
895
- make_calendar_heatmap(pd.DataFrame(), f"{label} Commits", selected_year)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
896
  else:
897
- st.info("Please select an account from the sidebar to view contributions.")
 
 
 
 
 
 
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":
 
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):
 
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):
 
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:
 
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()
 
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 = []
 
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:
 
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)
 
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
 
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']
 
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:
 
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
 
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()
 
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:
 
740
  ranking_data_overall,
741
  column_config={
742
  "Contributor": st.column_config.TextColumn("Contributor"),
743
+ "Spaces Rank": st.column_config.TextColumn("Spaces Rank"),
744
+ "Models Rank": st.column_config.TextColumn("Models Rank")
745
  },
746
  use_container_width=True,
747
  hide_index=False
748
  )
749
 
750
  with tab2:
751
+ # Show trending accounts by Spaces & Models
752
+ st.markdown('<div class="subheader"><h3>πŸš€ Spaces Leaders</h3></div>', unsafe_allow_html=True)
 
 
 
753
 
754
  # Create a data frame for the table
755
  if top_owners_spaces:
756
+ ranking_data_spaces = pd.DataFrame(top_owners_spaces[:50], columns=["Contributor", "Spaces Count"])
757
  ranking_data_spaces.index = ranking_data_spaces.index + 1 # Start index from 1 for ranking
758
 
759
  st.dataframe(
760
  ranking_data_spaces,
761
  column_config={
762
  "Contributor": st.column_config.TextColumn("Contributor"),
763
+ "Spaces Count": st.column_config.NumberColumn("Spaces Count", format="%d")
764
  },
765
  use_container_width=True,
766
  hide_index=False
767
  )
768
 
769
+ # Display the top Models accounts list
770
+ st.markdown('<div class="subheader"><h3>🧠 Models Leaders</h3></div>', unsafe_allow_html=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
771
 
772
  # Create a data frame for the Models table
773
  if top_owners_models:
774
+ ranking_data_models = pd.DataFrame(top_owners_models[:50], columns=["Contributor", "Models Count"])
775
  ranking_data_models.index = ranking_data_models.index + 1 # Start index from 1 for ranking
776
 
777
  st.dataframe(
778
  ranking_data_models,
779
  column_config={
780
  "Contributor": st.column_config.TextColumn("Contributor"),
781
+ "Models Count": st.column_config.NumberColumn("Models Count", format="%d")
782
  },
783
  use_container_width=True,
784
  hide_index=False
785
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
786
 
787
+ # Add visual divider
788
+ st.markdown('<hr style="margin: 2rem 0; border-color: #e0e0e0;">', unsafe_allow_html=True)
789
+
790
+ # Display contributor selection with enhanced styling
791
+ st.markdown('<div class="subheader"><h3>Select Contributor</h3></div>', unsafe_allow_html=True)
792
  selected_trending = st.selectbox(
793
+ "Choose from trending accounts",
794
  options=trending_accounts[:100], # Limit to top 100
795
  index=0 if trending_accounts else None,
796
  key="trending_selectbox"
797
  )
798
 
799
+ # Custom account input option with enhanced styling
800
+ st.markdown('<div style="text-align: center; margin: 15px 0; font-weight: bold;">- OR -</div>', unsafe_allow_html=True)
801
+ custom = st.text_input("Enter a username/organization:", placeholder="e.g. facebook, google...")
802
+
803
+ # Add visual divider
804
+ st.markdown('<hr style="margin: 1.5rem 0; border-color: #e0e0e0;">', unsafe_allow_html=True)
805
 
806
  # Set username based on selection or custom input
807
  if custom.strip():
 
811
  else:
812
  username = "facebook" # Default fallback
813
 
814
+ # Year selection with enhanced styling
815
+ st.markdown('<div class="subheader"><h3>πŸ—“οΈ Time Period</h3></div>', unsafe_allow_html=True)
816
  year_options = list(range(datetime.now().year, 2017, -1))
817
+ selected_year = st.selectbox("Select Year:", options=year_options)
818
 
819
+ # Additional options for customization with enhanced styling
820
+ st.markdown('<div class="subheader"><h3>βš™οΈ Display Options</h3></div>', unsafe_allow_html=True)
821
  show_models = st.checkbox("Show Models", value=True)
822
  show_datasets = st.checkbox("Show Datasets", value=True)
823
  show_spaces = st.checkbox("Show Spaces", value=True)
824
 
825
  # Main Content
826
+ st.markdown(f'<h1 style="text-align: center; color: #1E88E5; margin-bottom: 2rem;">πŸ€— Hugging Face Contributions</h1>', unsafe_allow_html=True)
827
 
828
  if username:
829
+ # Create a header card with contributor info
830
+ header_col1, header_col2 = st.columns([1, 2])
831
+ with header_col1:
832
+ st.markdown(f'<div style="background-color: #E3F2FD; padding: 20px; border-radius: 10px; border-left: 5px solid #1E88E5;">'
833
+ f'<h2 style="color: #1E88E5;">πŸ‘€ {username}</h2>'
834
+ f'<p style="font-size: 16px;">Analyzing contributions for {selected_year}</p>'
835
+ f'<p><a href="https://huggingface.co/{username}" target="_blank" style="color: #1E88E5; font-weight: bold;">View Profile</a></p>'
836
+ f'</div>', unsafe_allow_html=True)
837
+
838
+ with header_col2:
839
+ # Add explanation about the app
840
+ st.markdown(f'<div style="background-color: #F3E5F5; padding: 20px; border-radius: 10px; border-left: 5px solid #9C27B0;">'
841
+ f'<h3 style="color: #9C27B0;">About This Analysis</h3>'
842
+ f'<p>This dashboard analyzes {username}\'s contributions to Hugging Face in {selected_year}, including models, datasets, and spaces.</p>'
843
+ f'<p style="font-style: italic; font-size: 12px;">* Some metrics like follower growth are simulated for visualization purposes.</p>'
844
+ f'</div>', unsafe_allow_html=True)
845
+
846
+ with st.spinner(f"Fetching contribution data for {username}..."):
847
  # Initialize variables for tracking
848
  overall_rank = None
849
  spaces_rank = None
 
855
  # Display contributor rank if in top 100
856
  if username in trending_accounts[:100]:
857
  overall_rank = trending_accounts.index(username) + 1
858
+
859
+ # Create a prominent ranking display
860
+ st.markdown(f'<div style="background-color: #FFF8E1; padding: 20px; border-radius: 10px; border-left: 5px solid #FFC107; margin: 1rem 0;">'
861
+ f'<h2 style="color: #FFA000; text-align: center;">πŸ† Ranked #{overall_rank} in Top Contributors</h2>'
862
+ f'</div>', unsafe_allow_html=True)
863
 
864
  # Find user in spaces ranking
865
  for i, (owner, count) in enumerate(top_owners_spaces):
866
  if owner == username:
867
  spaces_rank = i+1
868
  spaces_count = count
 
869
  break
870
 
871
  # Find user in models ranking
 
873
  if owner == username:
874
  models_rank = i+1
875
  models_count = count
 
876
  break
877
 
878
+ # Display ranking visualization
 
 
 
 
 
 
 
 
 
 
879
  rank_chart = create_ranking_chart(username, overall_rank, spaces_rank, models_rank)
880
  if rank_chart:
881
  st.pyplot(rank_chart)
 
897
  st.warning("Please select at least one content type to display (Models, Datasets, or Spaces)")
898
  st.stop()
899
 
900
+ # Create a progress container
901
+ progress_container = st.container()
902
+ progress_container.markdown('<h3 style="color: #1E88E5;">Fetching Repository Data...</h3>', unsafe_allow_html=True)
903
+ progress_bar = progress_container.progress(0)
904
+
905
  # Fetch commits for each selected type
906
+ for type_index, kind in enumerate(types_to_fetch):
907
  try:
908
  items = cached_list_items(username, kind)
909
 
 
917
 
918
  repo_ids = [item.id for item in items]
919
 
920
+ progress_container.info(f"Found {len(repo_ids)} {kind}s for {username}")
921
 
922
  # Process repos in chunks
923
  chunk_size = 5
924
  total_commits = 0
925
  all_commit_dates = []
926
 
 
927
  for i in range(0, len(repo_ids), chunk_size):
928
  chunk = repo_ids[i:i + chunk_size]
929
  with ThreadPoolExecutor(max_workers=min(5, len(chunk))) as executor:
 
937
  all_commit_dates.extend(repo_commits)
938
  total_commits += repo_count
939
 
940
+ # Update progress for all types
941
+ progress_per_type = 1.0 / len(types_to_fetch)
942
+ current_type_progress = min(1.0, (i + len(chunk)) / max(1, len(repo_ids)))
943
+ overall_progress = (type_index * progress_per_type) + (current_type_progress * progress_per_type)
944
+ progress_bar.progress(overall_progress)
945
 
 
 
 
946
  commits_by_type[kind] = all_commit_dates
947
  commit_counts_by_type[kind] = total_commits
948
 
 
951
  commits_by_type[kind] = []
952
  commit_counts_by_type[kind] = 0
953
 
954
+ # Complete progress
955
+ progress_bar.progress(1.0)
956
+ progress_container.success("Data fetching complete!")
957
+ time.sleep(0.5) # Short pause for visual feedback
958
+ progress_container.empty() # Clear the progress indicators
959
+
960
  # Calculate total commits across all types
961
  total_commits = sum(commit_counts_by_type.values())
962
 
963
+ # Main dashboard layout with improved structure
964
+ 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)
965
+
966
+ # Profile summary
967
+ profile_col1, profile_col2 = st.columns([1, 2])
968
 
 
 
969
  with profile_col1:
970
+ # Create a stats card with key metrics
971
+ st.markdown(f'<div style="background-color: white; padding: 20px; border-radius: 10px; box-shadow: 0 2px 10px rgba(0,0,0,0.1);">'
972
+ f'<h3 style="color: #1E88E5; text-align: center; margin-bottom: 15px;">Contribution Stats</h3>'
973
+ f'<div style="display: flex; justify-content: space-between; margin-bottom: 10px;">'
974
+ f'<span style="font-weight: bold;">Total Commits:</span><span>{total_commits}</span></div>'
975
+ f'<div style="display: flex; justify-content: space-between; margin-bottom: 10px;">'
976
+ f'<span style="font-weight: bold;">Models:</span><span>{models_count}</span></div>'
977
+ f'<div style="display: flex; justify-content: space-between; margin-bottom: 10px;">'
978
+ f'<span style="font-weight: bold;">Datasets:</span><span>{datasets_count}</span></div>'
979
+ f'<div style="display: flex; justify-content: space-between; margin-bottom: 10px;">'
980
+ f'<span style="font-weight: bold;">Spaces:</span><span>{spaces_count}</span></div>'
981
+ f'</div>', unsafe_allow_html=True)
982
 
983
+ # Type breakdown pie chart
984
+ model_commits = commit_counts_by_type.get("model", 0)
985
+ dataset_commits = commit_counts_by_type.get("dataset", 0)
986
+ space_commits = commit_counts_by_type.get("space", 0)
 
987
 
988
+ pie_chart = create_contribution_pie(model_commits, dataset_commits, space_commits)
989
+ if pie_chart:
990
+ st.pyplot(pie_chart)
991
 
992
  with profile_col2:
993
  # Display contribution radar chart
994
  radar_fig = create_contribution_radar(username, models_count, spaces_count, datasets_count, total_commits)
995
  st.pyplot(radar_fig)
996
 
997
+ # Create DataFrame for all commits
998
+ all_commits = []
999
+ for commits in commits_by_type.values():
1000
+ all_commits.extend(commits)
1001
+ all_df = pd.DataFrame(all_commits, columns=["date"])
1002
+ if not all_df.empty:
1003
+ all_df = all_df.drop_duplicates() # Remove any duplicate dates
1004
+
1005
+ # Calendar heatmap for all commits in a separate section
1006
+ 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)
1007
+
1008
  if not all_df.empty:
1009
+ make_calendar_heatmap(all_df, "All Contributions", selected_year)
1010
+ else:
1011
+ st.info(f"No contributions found for {username} in {selected_year}")
1012
 
1013
  # Monthly activity chart
1014
+ 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)
1015
+
1016
  monthly_fig = create_monthly_activity(all_df, selected_year)
1017
  if monthly_fig:
1018
  st.pyplot(monthly_fig)
1019
  else:
1020
  st.info(f"No activity data available for {username} in {selected_year}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1021
 
1022
  # Follower growth simulation
1023
+ 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)
1024
+ st.markdown('<div style="background-color: #EDE7F6; padding: 10px; border-radius: 5px; margin-bottom: 15px;">'
1025
+ '<p style="font-style: italic; margin: 0;">πŸ“Š This is a simulation based on contribution metrics - for visualization purposes only</p>'
1026
+ '</div>', unsafe_allow_html=True)
1027
+
1028
  follower_chart = simulate_follower_data(username, spaces_count, models_count, total_commits)
1029
  st.pyplot(follower_chart)
1030
 
1031
+ # Analytics summary section
1032
  if total_commits > 0:
1033
+ 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)
1034
 
1035
  # Contribution pattern analysis
1036
  monthly_df = pd.DataFrame(all_commits, columns=["date"])
 
1041
  most_active_month = monthly_df['month'].value_counts().idxmax()
1042
  month_name = datetime(2020, most_active_month, 1).strftime('%B')
1043
 
1044
+ # Create a summary card
1045
+ st.markdown(f'<div style="background-color: white; padding: 25px; border-radius: 10px; box-shadow: 0 2px 10px rgba(0,0,0,0.1);">'
1046
+ f'<h3 style="color: #1E88E5; border-bottom: 1px solid #E0E0E0; padding-bottom: 10px;">Activity Analysis for {username}</h3>'
1047
+ f'<ul style="list-style-type: none; padding-left: 5px;">'
1048
+ f'<li style="margin: 15px 0; font-size: 16px;">πŸ“ˆ <strong>Total Activity:</strong> {total_commits} contributions in {selected_year}</li>'
1049
+ 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>'
1050
+ f'<li style="margin: 15px 0; font-size: 16px;">🧩 <strong>Repository Breakdown:</strong> {models_count} Models, {spaces_count} Spaces, {datasets_count} Datasets</li>'
1051
+ f'</ul>', unsafe_allow_html=True)
1052
 
1053
  # Add ranking context if available
1054
  if overall_rank:
1055
  percentile = 100 - overall_rank
1056
+ st.markdown(f'<div style="margin-top: 20px;">'
1057
+ f'<h3 style="color: #1E88E5; border-bottom: 1px solid #E0E0E0; padding-bottom: 10px;">Ranking Analysis</h3>'
1058
+ f'<ul style="list-style-type: none; padding-left: 5px;">'
1059
+ f'<li style="margin: 15px 0; font-size: 16px;">πŸ† <strong>Overall Ranking:</strong> #{overall_rank} (Top {percentile}% of contributors)</li>', unsafe_allow_html=True)
1060
 
1061
+ badge_html = '<div style="margin: 20px 0;">'
 
1062
 
1063
  if spaces_rank and spaces_rank <= 10:
1064
+ 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>'
1065
  elif spaces_rank and spaces_rank <= 30:
1066
+ 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>'
1067
 
1068
  if models_rank and models_rank <= 10:
1069
+ 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>'
1070
  elif models_rank and models_rank <= 30:
1071
+ 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>'
1072
+
1073
+ badge_html += '</div>'
1074
+
1075
+ # Add achievement badges
1076
+ if spaces_rank or models_rank:
1077
+ st.markdown(badge_html, unsafe_allow_html=True)
1078
+
1079
+ st.markdown('</ul></div></div>', unsafe_allow_html=True)
1080
 
1081
+ # Detailed category analysis section
1082
+ 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)
1083
+
1084
+ # Create category cards in columns
1085
  cols = st.columns(len(types_to_fetch)) if types_to_fetch else st.columns(1)
1086
 
1087
+ category_icons = {
1088
+ "model": "🧠",
1089
+ "dataset": "πŸ“¦",
1090
+ "space": "πŸš€"
1091
+ }
1092
+
1093
+ category_colors = {
1094
+ "model": "#FF9800",
1095
+ "dataset": "#2196F3",
1096
+ "space": "#4CAF50"
1097
+ }
1098
+
1099
+ for i, kind in enumerate(types_to_fetch):
1100
+ with cols[i]:
1101
+ try:
1102
+ emoji = category_icons.get(kind, "πŸ“Š")
1103
+ label = kind.capitalize() + "s"
1104
+ color = category_colors.get(kind, "#1E88E5")
1105
+
1106
+ total = len(cached_list_items(username, kind))
1107
+ commits = commits_by_type.get(kind, [])
1108
+ commit_count = commit_counts_by_type.get(kind, 0)
1109
+
1110
+ # Create styled card header
1111
+ 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};">'
1112
+ f'<h3 style="color: {color}; text-align: center;">{emoji} {label}</h3>'
1113
+ f'<div style="display: flex; justify-content: space-between; margin: 15px 0;">'
1114
+ f'<span style="font-weight: bold;">Total:</span><span>{total}</span></div>'
1115
+ f'<div style="display: flex; justify-content: space-between; margin-bottom: 15px;">'
1116
+ f'<span style="font-weight: bold;">Commits:</span><span>{commit_count}</span></div>'
1117
+ f'</div>', unsafe_allow_html=True)
1118
+
1119
+ # Create calendar for this type
1120
+ df_kind = pd.DataFrame(commits, columns=["date"])
1121
+ if not df_kind.empty:
1122
+ df_kind = df_kind.drop_duplicates() # Remove any duplicate dates
1123
+ make_calendar_heatmap(df_kind, f"{label} Commits", selected_year)
1124
+ else:
1125
+ st.info(f"No {label.lower()} activity in {selected_year}")
1126
+
1127
+ except Exception as e:
1128
+ st.warning(f"Error processing {kind.capitalize()}s: {str(e)}")
1129
+ # Show empty placeholder
1130
+ 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;">'
1131
+ f'<h3 style="color: #9E9E9E;">⚠️ Error</h3>'
1132
+ f'<p>Could not load {kind.capitalize()}s data</p>'
1133
+ f'</div>', unsafe_allow_html=True)
1134
+
1135
+ # Footer
1136
+ st.markdown('<hr style="margin: 3rem 0 1rem 0;">', unsafe_allow_html=True)
1137
+ 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)
1138
  else:
1139
+ # If no username is selected, show welcome screen
1140
+ st.markdown(f'<div style="text-align: center; margin: 50px 0;">'
1141
+ f'<img src="https://huggingface.co/datasets/huggingface/brand-assets/resolve/main/hf-logo.svg" style="width: 200px; margin-bottom: 30px;">'
1142
+ f'<h2>Welcome to Hugging Face Contributions Dashboard</h2>'
1143
+ f'<p style="font-size: 1.2rem;">Please select a contributor from the sidebar to view their activity.</p>'
1144
+ f'</div>', unsafe_allow_html=True)