"""
Hugging Face Contributions Graph
A GitHub-style contribution calendar for Hugging Face users.
Shows daily commit activity across models, datasets, and spaces.
"""
import gradio as gr
from datetime import datetime, timedelta
from hf_contributions import fetch_user_contributions, ContributionStats
from typing import Optional
import json
# GitHub-style color palette
COLORS = {
"light": {
0: "#ebedf0", # No contributions
1: "#9be9a8", # Level 1 (1-3)
2: "#40c463", # Level 2 (4-6)
3: "#30a14e", # Level 3 (7-9)
4: "#216e39", # Level 4 (10+)
},
"dark": {
0: "#161b22",
1: "#0e4429",
2: "#006d32",
3: "#26a641",
4: "#39d353",
}
}
def get_contribution_level(count: int) -> int:
"""Determine the contribution level (0-4) based on count."""
if count == 0:
return 0
elif count <= 3:
return 1
elif count <= 6:
return 2
elif count <= 9:
return 3
else:
return 4
def generate_calendar_data(contributions: dict[str, int], days: int = 365) -> list[dict]:
"""Generate calendar data for the past N days."""
today = datetime.now().date()
start_date = today - timedelta(days=days - 1)
# Adjust to start from Sunday
days_since_sunday = start_date.weekday() + 1
if days_since_sunday == 7:
days_since_sunday = 0
start_date = start_date - timedelta(days=days_since_sunday)
calendar_data = []
current_date = start_date
while current_date <= today:
date_str = current_date.strftime("%Y-%m-%d")
count = contributions.get(date_str, 0)
level = get_contribution_level(count)
calendar_data.append({
"date": date_str,
"count": count,
"level": level,
"weekday": current_date.weekday(),
"month": current_date.month,
"day": current_date.day
})
current_date += timedelta(days=1)
return calendar_data
def build_daily_activity_data(contributions_by_repo: dict) -> dict[str, list[dict]]:
"""Build a mapping of date -> list of contributions for that date."""
daily_activities = {}
for repo_id, commits in contributions_by_repo.items():
for commit in commits:
date = commit["date"]
if date not in daily_activities:
daily_activities[date] = []
daily_activities[date].append({
"repo_id": repo_id,
"repo_type": commit["repo_type"],
"message": commit.get("message", "No message")[:80]
})
return daily_activities
def generate_contribution_graph_html(
stats: Optional[ContributionStats],
theme: str = "light",
username: str = ""
) -> str:
"""Generate the HTML/CSS for the GitHub-style contribution graph."""
if stats is None:
return """
Enter a Hugging Face username to see their contribution graph
"""
colors = COLORS[theme]
calendar_data = generate_calendar_data(stats.contributions_by_date)
# Build daily activity data for the detail view
daily_activities = build_daily_activity_data(stats.contributions_by_repo)
daily_activities_json = json.dumps(daily_activities)
# Group by weeks
weeks = []
current_week = []
for day in calendar_data:
if len(current_week) == 7:
weeks.append(current_week)
current_week = []
current_week.append(day)
if current_week:
weeks.append(current_week)
# Generate month labels
months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
month_positions = {}
for week_idx, week in enumerate(weeks):
for day in week:
if day["day"] <= 7: # First week of month
month_positions[week_idx] = months[day["month"] - 1]
break
# Build HTML
squares_html = ""
for week_idx, week in enumerate(weeks):
for day in week:
color = colors[day["level"]]
tooltip = f'{day["count"]} contributions on {day["date"]}'
squares_html += f'''
'''
# Month labels HTML
months_html = ""
last_month_week = -4
for week_idx, month_name in sorted(month_positions.items()):
if week_idx - last_month_week >= 4: # At least 4 weeks apart
left_pos = week_idx * 15 # 11px square + 4px gap
months_html += f'{month_name}'
last_month_week = week_idx
bg_color = "#ffffff" if theme == "light" else "#0d1117"
text_color = "#24292f" if theme == "light" else "#c9d1d9"
border_color = "#d0d7de" if theme == "light" else "#30363d"
html = f'''
Mon
Wed
Fri
{months_html}
{squares_html}
{stats.total_commits}
Total Contributions
{stats.models_count}
Models
{stats.datasets_count}
Datasets
{stats.spaces_count}
Spaces
{stats.longest_streak}
Longest Streak
{stats.current_streak}
Current Streak
'''
return html
# Store the current stats globally for the detail view
current_stats: Optional[ContributionStats] = None
def fetch_and_display(username: str, theme: str) -> tuple[str, str]:
"""Fetch contributions and generate the display."""
global current_stats
if not username or not username.strip():
return (
generate_contribution_graph_html(None, theme),
"Please enter a username"
)
username = username.strip()
try:
# Fetch the data
current_stats = fetch_user_contributions(username)
# Generate the graph HTML
graph_html = generate_contribution_graph_html(current_stats, theme, username)
# Generate summary
summary = f"""
### {username}'s Hugging Face Activity
- **Total Contributions:** {current_stats.total_commits}
- **Repositories:** {current_stats.total_repos} ({current_stats.models_count} models, {current_stats.datasets_count} datasets, {current_stats.spaces_count} spaces)
- **Longest Streak:** {current_stats.longest_streak} days
- **Current Streak:** {current_stats.current_streak} days
"""
if current_stats.most_active_day:
summary += f"- **Most Active Day:** {current_stats.most_active_day} ({current_stats.most_active_count} contributions)"
return graph_html, summary
except Exception as e:
return (
f'Error: {str(e)}
',
f"Error fetching data for {username}: {str(e)}"
)
# Create the Gradio interface
with gr.Blocks(
title="HF Contributions Graph",
theme=gr.themes.Soft(),
css="""
.gradio-container { max-width: 1200px !important; }
footer { display: none !important; }
"""
) as demo:
gr.Markdown("""
# ๐ค Hugging Face Contributions Graph
GitHub-style contribution calendar for Hugging Face users.
See your daily activity across models, datasets, and spaces!
""")
with gr.Row():
with gr.Column(scale=4):
username_input = gr.Textbox(
label="Hugging Face Username",
placeholder="e.g., huggingface, Qwen, meta-llama",
lines=1
)
with gr.Column(scale=1):
theme_dropdown = gr.Dropdown(
choices=["light", "dark"],
value="light",
label="Theme"
)
with gr.Column(scale=1):
fetch_btn = gr.Button("Fetch Contributions", variant="primary")
graph_output = gr.HTML(
value=generate_contribution_graph_html(None, "light"),
label="Contribution Graph"
)
summary_output = gr.Markdown(
value="Enter a username and click 'Fetch Contributions' to see their activity."
)
# Event handlers
fetch_btn.click(
fn=fetch_and_display,
inputs=[username_input, theme_dropdown],
outputs=[graph_output, summary_output]
)
username_input.submit(
fn=fetch_and_display,
inputs=[username_input, theme_dropdown],
outputs=[graph_output, summary_output]
)
theme_dropdown.change(
fn=lambda u, t: fetch_and_display(u, t) if u else (generate_contribution_graph_html(None, t), ""),
inputs=[username_input, theme_dropdown],
outputs=[graph_output, summary_output]
)
gr.Markdown("""
---
**How it works:** This app fetches your public repositories (models, datasets, spaces)
from the Hugging Face Hub API and counts commits to generate a contribution calendar.
**Note:** Only public repositories are counted. Private repos require authentication.
""")
if __name__ == "__main__":
demo.launch()