|
|
import gradio as gr |
|
|
import pandas as pd |
|
|
from huggingface_hub import HfApi, hf_hub_download, upload_file |
|
|
import os |
|
|
from datetime import datetime |
|
|
from dotenv import load_dotenv |
|
|
import re |
|
|
from gradio_htmlplus import HTMLPlus |
|
|
|
|
|
load_dotenv() |
|
|
|
|
|
|
|
|
DATASET_REPO_ID = "elismasilva/hackathon-builders-choice" |
|
|
PROJECTS_FILE = "projects.csv" |
|
|
VOTES_FILE = "votes.csv" |
|
|
HF_TOKEN = os.getenv("HF_TOKEN") |
|
|
api = HfApi(token=HF_TOKEN) |
|
|
|
|
|
|
|
|
TRACKS = ["Track 1: Building MCP", "Track 2: MCP in Action"] |
|
|
CATEGORIES = ["Enterprise", "Consumer", "Creative"] |
|
|
LEADERBOARD_VIEWS = [ |
|
|
"Track 1 (Overall)", |
|
|
"Track 2 (Enterprise)", |
|
|
"Track 2 (Consumer)", |
|
|
"Track 2 (Creative)", |
|
|
] |
|
|
|
|
|
|
|
|
|
|
|
def load_data(filename, repo_id): |
|
|
try: |
|
|
filepath = hf_hub_download( |
|
|
repo_id=repo_id, filename=filename, repo_type="dataset", token=HF_TOKEN |
|
|
) |
|
|
return pd.read_csv(filepath, dtype={"track": str, "category": str}) |
|
|
except Exception: |
|
|
if filename == PROJECTS_FILE: |
|
|
return pd.DataFrame( |
|
|
columns=[ |
|
|
"space_url", |
|
|
"video_url", |
|
|
"track", |
|
|
"category", |
|
|
"submitted_by", |
|
|
"timestamp", |
|
|
] |
|
|
) |
|
|
elif filename == VOTES_FILE: |
|
|
return pd.DataFrame( |
|
|
columns=["space_url", "voted_by", "track", "category", "timestamp"] |
|
|
) |
|
|
return pd.DataFrame() |
|
|
|
|
|
|
|
|
def save_data(df, filename, repo_id, commit_message): |
|
|
temp_path = f"./{filename}" |
|
|
df.to_csv(temp_path, index=False) |
|
|
upload_file( |
|
|
path_or_fileobj=temp_path, |
|
|
path_in_repo=filename, |
|
|
repo_id=repo_id, |
|
|
repo_type="dataset", |
|
|
token=HF_TOKEN, |
|
|
commit_message=commit_message, |
|
|
) |
|
|
os.remove(temp_path) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_username(request: gr.Request): |
|
|
return ( |
|
|
request.request.session.get("oauth_info", {}) |
|
|
.get("userinfo", {}) |
|
|
.get("preferred_username") |
|
|
) |
|
|
|
|
|
|
|
|
def parse_view_to_context(filter_view: str): |
|
|
if "Track 1" in filter_view: |
|
|
return TRACKS[0], "Overall" |
|
|
if "Track 2" in filter_view: |
|
|
if "Enterprise" in filter_view: |
|
|
return TRACKS[1], "Enterprise" |
|
|
if "Consumer" in filter_view: |
|
|
return TRACKS[1], "Consumer" |
|
|
if "Creative" in filter_view: |
|
|
return TRACKS[1], "Creative" |
|
|
return None, None |
|
|
|
|
|
|
|
|
def render_leaderboard(request: gr.Request, filter_view: str): |
|
|
username = get_username(request) |
|
|
projects_df = load_data(PROJECTS_FILE, DATASET_REPO_ID) |
|
|
votes_df = load_data(VOTES_FILE, DATASET_REPO_ID) |
|
|
vote_context_str = f"βΉοΈ Your vote will be cast in the **'{filter_view}'** context." |
|
|
|
|
|
if projects_df.empty: |
|
|
return ( |
|
|
"<h3>No projects submitted yet.</h3>", |
|
|
gr.update(choices=[], value=None), |
|
|
vote_context_str, |
|
|
gr.update(visible=False), |
|
|
) |
|
|
|
|
|
track_filter, category_filter = parse_view_to_context(filter_view) |
|
|
display_df = pd.DataFrame() |
|
|
|
|
|
if track_filter == TRACKS[0]: |
|
|
track1_projects = projects_df[ |
|
|
projects_df["track"].str.contains(TRACKS[0], na=False) |
|
|
].copy() |
|
|
if not track1_projects.empty: |
|
|
track1_projects["display_category"] = track1_projects[ |
|
|
"category" |
|
|
].str.replace(";", " | ") |
|
|
track1_votes = votes_df[ |
|
|
(votes_df["track"] == TRACKS[0]) & (votes_df["category"] == "Overall") |
|
|
] |
|
|
vote_counts = ( |
|
|
track1_votes.groupby("space_url").size().reset_index(name="votes") |
|
|
) |
|
|
display_df = pd.merge( |
|
|
track1_projects, vote_counts, on="space_url", how="left" |
|
|
).fillna(0) |
|
|
elif track_filter == TRACKS[1]: |
|
|
projects_df["track"] = projects_df["track"].str.split(";") |
|
|
exploded_tracks = projects_df.explode("track") |
|
|
exploded_tracks["category"] = exploded_tracks["category"].str.split(";") |
|
|
exploded_projects = exploded_tracks.explode("category") |
|
|
track2_filtered = exploded_projects[ |
|
|
(exploded_projects["track"] == track_filter) |
|
|
& (exploded_projects["category"] == category_filter) |
|
|
] |
|
|
if not track2_filtered.empty: |
|
|
context_votes = votes_df[ |
|
|
(votes_df["track"] == track_filter) |
|
|
& (votes_df["category"] == category_filter) |
|
|
] |
|
|
vote_counts = ( |
|
|
context_votes.groupby("space_url").size().reset_index(name="votes") |
|
|
) |
|
|
display_df = pd.merge( |
|
|
track2_filtered, vote_counts, on="space_url", how="left" |
|
|
).fillna(0) |
|
|
display_df["display_category"] = display_df["category"] |
|
|
|
|
|
if display_df.empty: |
|
|
return ( |
|
|
f"<h3>No projects submitted for '{filter_view}' yet.</h3>", |
|
|
gr.update(choices=[], value=None), |
|
|
vote_context_str, |
|
|
gr.update(visible=False), |
|
|
) |
|
|
|
|
|
display_df["votes"] = display_df["votes"].astype(int) |
|
|
display_df["rank"] = ( |
|
|
display_df["votes"].rank(method="dense", ascending=False).astype(int) |
|
|
) |
|
|
display_df = display_df.sort_values( |
|
|
by=["rank", "space_url"], ascending=True |
|
|
).reset_index(drop=True) |
|
|
|
|
|
html = "<div>" |
|
|
trophies = {1: "π₯", 2: "π₯", 3: "π₯"} |
|
|
for _, row in display_df.iterrows(): |
|
|
rank_num = row["rank"] |
|
|
rank_display = trophies.get(rank_num, f"<b>#{rank_num}</b>") |
|
|
space_name = re.sub(r"https://huggingface.co/spaces/", "", row["space_url"]) |
|
|
category_text = row["display_category"] |
|
|
submitter_name = row["submitted_by"] |
|
|
|
|
|
video_link_html = "" |
|
|
if pd.notna(row["video_url"]) and str(row["video_url"]).strip() and str(row["video_url"]).strip() != '0': |
|
|
video_link_html = ( |
|
|
f' | <a href="{row["video_url"]}" target="_blank">π¬ Video</a>' |
|
|
) |
|
|
|
|
|
action_button_html = "" |
|
|
if username and row["submitted_by"] == username: |
|
|
action_button_html = f'<a href="#" class="edit-button" data-url="{row["space_url"]}" style="text-decoration: none; font-size: 20px; margin-left: 15px;" title="Edit this project">βοΈ</a>' |
|
|
|
|
|
html += f""" |
|
|
<div style="display: flex; align-items: center; padding: 12px; border-bottom: 1px solid #eee; gap: 15px;" data-space-url="{row['space_url']}"> |
|
|
<div style="font-size: 24px; width: 50px;">{rank_display}</div> |
|
|
<div style="flex-grow: 1;"> |
|
|
<div style="font-weight: bold; font-size: 16px;">{space_name} <span style="font-weight: normal; color: #888; font-size: 14px;">({submitter_name})</span></div> |
|
|
<div style="font-size: 12px; color: #555;"> |
|
|
<a href="{row["space_url"]}" target="_blank">π Space</a> |
|
|
{video_link_html} |
|
|
| <span style="color: #777;">Track: {row["track"].split(';')[0].split(':')[0]} | Categories: {category_text}</span> |
|
|
</div> |
|
|
</div> |
|
|
<div style="font-size: 20px; font-weight: bold; color: #3B82F6;">{row["votes"]} votes</div> |
|
|
{action_button_html} |
|
|
</div> |
|
|
""" |
|
|
html += "</div>" |
|
|
|
|
|
project_urls = display_df["space_url"].unique().tolist() |
|
|
return ( |
|
|
html, |
|
|
gr.update(choices=project_urls, value=None), |
|
|
vote_context_str, |
|
|
gr.update(visible=False), |
|
|
) |
|
|
|
|
|
|
|
|
def submit_project( |
|
|
request: gr.Request, space_url, video_url, selected_tracks, selected_categories |
|
|
): |
|
|
username = get_username(request) |
|
|
if not username: |
|
|
gr.Info("Error: You must be logged in to submit a project.") |
|
|
return gr.skip(), gr.skip(), gr.skip(), gr.skip() |
|
|
|
|
|
if not all([space_url, selected_tracks, selected_categories]): |
|
|
gr.Info( |
|
|
"Error: Space URL, at least one Track, and at least one Category are required." |
|
|
) |
|
|
return gr.skip(), gr.skip(), gr.skip(), gr.skip() |
|
|
|
|
|
if not "huggingface.co/spaces/" in space_url: |
|
|
gr.Info("Error: Please enter a valid Hugging Face Space URL.") |
|
|
return gr.skip(), gr.skip(), gr.skip(), gr.skip() |
|
|
|
|
|
projects_df = load_data(PROJECTS_FILE, DATASET_REPO_ID) |
|
|
if space_url in projects_df["space_url"].values: |
|
|
gr.Info("Error: This project has already been submitted.") |
|
|
return gr.skip(), gr.skip(), gr.skip(), gr.skip() |
|
|
|
|
|
tracks_str = ";".join(selected_tracks) |
|
|
categories_str = ";".join(selected_categories) |
|
|
new_project = pd.DataFrame( |
|
|
[ |
|
|
{ |
|
|
"space_url": space_url, |
|
|
"video_url": video_url, |
|
|
"track": tracks_str, |
|
|
"category": categories_str, |
|
|
"submitted_by": username, |
|
|
"timestamp": datetime.now().isoformat(), |
|
|
} |
|
|
] |
|
|
) |
|
|
updated_projects = pd.concat([projects_df, new_project], ignore_index=True) |
|
|
save_data( |
|
|
updated_projects, |
|
|
PROJECTS_FILE, |
|
|
DATASET_REPO_ID, |
|
|
f"Project submitted by {username}", |
|
|
) |
|
|
gr.Info(f"β
Success! Project '{space_url.split('/')[-1]}' submitted.") |
|
|
html, dropdown, context, edit_box_visibility = render_leaderboard( |
|
|
request, LEADERBOARD_VIEWS[0] |
|
|
) |
|
|
return f"Last action: Success.", html, dropdown, context |
|
|
|
|
|
|
|
|
def cast_vote(request: gr.Request, project_to_vote, filter_view): |
|
|
username = get_username(request) |
|
|
if not username: |
|
|
gr.Info("Error: You must be logged in to vote.") |
|
|
return gr.skip(), gr.skip(), gr.skip(), gr.skip() |
|
|
if not project_to_vote: |
|
|
gr.Info("Error: Please select a project to vote for.") |
|
|
return gr.skip(), gr.skip(), gr.skip(), gr.skip() |
|
|
|
|
|
votes_df = load_data(VOTES_FILE, DATASET_REPO_ID) |
|
|
vote_track, vote_category = parse_view_to_context(filter_view) |
|
|
existing_vote = votes_df[ |
|
|
(votes_df["space_url"] == project_to_vote) |
|
|
& (votes_df["voted_by"] == username) |
|
|
& (votes_df["track"] == vote_track) |
|
|
& (votes_df["category"] == vote_category) |
|
|
] |
|
|
if not existing_vote.empty: |
|
|
gr.Info( |
|
|
f"Notice: You have already voted for this project in the '{filter_view}' context." |
|
|
) |
|
|
return gr.skip(), gr.skip(), gr.skip(), gr.skip() |
|
|
|
|
|
new_vote = pd.DataFrame( |
|
|
[ |
|
|
{ |
|
|
"space_url": project_to_vote, |
|
|
"voted_by": username, |
|
|
"track": vote_track, |
|
|
"category": vote_category, |
|
|
"timestamp": datetime.now().isoformat(), |
|
|
} |
|
|
] |
|
|
) |
|
|
updated_votes = pd.concat([votes_df, new_vote], ignore_index=True) |
|
|
save_data(updated_votes, VOTES_FILE, DATASET_REPO_ID, f"Vote cast by {username}") |
|
|
gr.Info( |
|
|
f"β
Vote successfully cast for '{project_to_vote.split('/')[-1]}' in '{filter_view}'!" |
|
|
) |
|
|
|
|
|
html, dropdown, context, _ = render_leaderboard(request, filter_view) |
|
|
|
|
|
return f"Last action: Success.", html, dropdown, context |
|
|
|
|
|
|
|
|
def edit_project( |
|
|
request: gr.Request, |
|
|
old_space_url: str, |
|
|
new_space_url: str, |
|
|
new_video_url: str, |
|
|
filter_view: str, |
|
|
): |
|
|
username = get_username(request) |
|
|
if not username: |
|
|
gr.Info("Error: Authentication failed. Please log in again.") |
|
|
return gr.skip(), gr.skip(), gr.skip(), gr.update(visible=False) |
|
|
projects_df = load_data(PROJECTS_FILE, DATASET_REPO_ID) |
|
|
votes_df = load_data(VOTES_FILE, DATASET_REPO_ID) |
|
|
project_index = projects_df[ |
|
|
(projects_df["space_url"] == old_space_url) |
|
|
& (projects_df["submitted_by"] == username) |
|
|
].index |
|
|
if project_index.empty: |
|
|
gr.Info( |
|
|
"Error: You do not have permission to edit this project, or it no longer exists." |
|
|
) |
|
|
return gr.skip(), gr.skip(), gr.skip(), gr.update(visible=False) |
|
|
projects_df.loc[project_index, "space_url"] = new_space_url |
|
|
projects_df.loc[project_index, "video_url"] = new_video_url |
|
|
save_data( |
|
|
projects_df, |
|
|
PROJECTS_FILE, |
|
|
DATASET_REPO_ID, |
|
|
f"Project edited by {username}: {old_space_url} -> {new_space_url}", |
|
|
) |
|
|
if old_space_url != new_space_url: |
|
|
votes_df.loc[votes_df["space_url"] == old_space_url, "space_url"] = ( |
|
|
new_space_url |
|
|
) |
|
|
save_data( |
|
|
votes_df, |
|
|
VOTES_FILE, |
|
|
DATASET_REPO_ID, |
|
|
f"Vote URLs updated for project edit by {username}", |
|
|
) |
|
|
gr.Info("β
Project URLs updated successfully! Votes have been preserved.") |
|
|
html, dropdown, context, _ = render_leaderboard(request, filter_view) |
|
|
return "Project edited.", html, dropdown, context, gr.update(visible=False) |
|
|
|
|
|
|
|
|
|
|
|
theme = gr.themes.Default( |
|
|
primary_hue="blue", secondary_hue="yellow", neutral_hue="neutral" |
|
|
).set( |
|
|
body_background_fill="*neutral_100", |
|
|
body_background_fill_dark="*neutral_800", |
|
|
body_text_color="*neutral_700", |
|
|
body_text_color_dark="*neutral_200", |
|
|
body_text_size="1.1em", |
|
|
code_background_fill="*neutral_100", |
|
|
code_background_fill_dark="*neutral_800", |
|
|
shadow_drop="2px 2px 4px *neutral_400", |
|
|
block_label_background_fill="*neutral_100", |
|
|
block_label_background_fill_dark="*neutral_800", |
|
|
block_label_text_color="*neutral_700", |
|
|
block_label_text_color_dark="*neutral_200", |
|
|
block_title_text_color="*primary_700", |
|
|
block_title_text_color_dark="*primary_300", |
|
|
panel_background_fill="*neutral_50", |
|
|
panel_background_fill_dark="*neutral_900", |
|
|
panel_border_color="*neutral_200", |
|
|
panel_border_color_dark="*neutral_700", |
|
|
checkbox_border_color="*neutral_300", |
|
|
checkbox_border_color_dark="*neutral_600", |
|
|
input_background_fill="white", |
|
|
input_background_fill_dark="*neutral_800", |
|
|
input_border_color="*neutral_300", |
|
|
input_border_color_dark="*neutral_600", |
|
|
slider_color="*primary_500", |
|
|
slider_color_dark="*primary_400", |
|
|
button_primary_background_fill="*primary_600", |
|
|
button_primary_background_fill_dark="*primary_500", |
|
|
button_primary_background_fill_hover="*primary_700", |
|
|
button_primary_background_fill_hover_dark="*primary_600", |
|
|
button_primary_text_color="white", |
|
|
button_primary_text_color_dark="white", |
|
|
button_secondary_background_fill="*secondary_400", |
|
|
button_secondary_background_fill_dark="*secondary_500", |
|
|
button_secondary_background_fill_hover="*secondary_500", |
|
|
button_secondary_background_fill_hover_dark="*secondary_600", |
|
|
button_secondary_text_color="*neutral_700", |
|
|
button_secondary_text_color_dark="*neutral_200", |
|
|
button_cancel_background_fill="*neutral_200", |
|
|
button_cancel_background_fill_dark="*neutral_700", |
|
|
button_cancel_background_fill_hover="*neutral_300", |
|
|
button_cancel_background_fill_hover_dark="*neutral_600", |
|
|
button_cancel_text_color="*neutral_700", |
|
|
button_cancel_text_color_dark="*neutral_200", |
|
|
) |
|
|
|
|
|
|
|
|
with gr.Blocks(theme=theme, title="Hackathon Builders' Choice") as app: |
|
|
gr.Markdown("# π Hackathon Builders' Choice") |
|
|
gr.Markdown("An unofficial poll for participants to appreciate each other's work. Log in to submit your project and vote!") |
|
|
gr.LoginButton() |
|
|
|
|
|
with gr.Tabs() as tabs: |
|
|
with gr.TabItem("π Leaderboard & Vote", id=0): |
|
|
leaderboard_filter = gr.Radio( |
|
|
LEADERBOARD_VIEWS, |
|
|
label="Select Leaderboard View", |
|
|
value=LEADERBOARD_VIEWS[0], |
|
|
) |
|
|
with gr.Group(visible=False) as edit_group: |
|
|
gr.Markdown("### Edit Project URLs") |
|
|
edit_old_url = gr.Textbox(label="Original Space URL", interactive=False) |
|
|
edit_new_url = gr.Textbox(label="New Space URL") |
|
|
edit_new_video_url = gr.Textbox(label="New Video URL") |
|
|
with gr.Row(): |
|
|
save_edit_button = gr.Button("Save Changes", variant="primary") |
|
|
cancel_edit_button = gr.Button("Cancel") |
|
|
|
|
|
with gr.Row(): |
|
|
with gr.Column(scale=2): |
|
|
leaderboard_html = HTMLPlus( |
|
|
"Please log in to load...", selectable_elements=[".edit-button"] |
|
|
) |
|
|
with gr.Column(scale=1): |
|
|
gr.Markdown("### Cast Your Vote") |
|
|
vote_context_display = gr.Markdown() |
|
|
vote_status = gr.Markdown() |
|
|
project_dropdown = gr.Dropdown( |
|
|
label="Select a Project to Vote For", interactive=True |
|
|
) |
|
|
vote_button = gr.Button( |
|
|
"π Vote for Selected Project", variant="primary" |
|
|
) |
|
|
|
|
|
with gr.TabItem("π Submit Your Project", id=1): |
|
|
gr.Markdown("### Register Your Project") |
|
|
submission_status = gr.Markdown() |
|
|
space_url_input = gr.Textbox( |
|
|
label="Your Hugging Face Space URL", |
|
|
placeholder="https://huggingface.co/spaces/...", |
|
|
) |
|
|
|
|
|
video_url_input = gr.Textbox( |
|
|
label="Your Demo Video URL (Optional)", |
|
|
placeholder="https://www.youtube.com/watch?v=...", |
|
|
) |
|
|
track_checkboxes = gr.CheckboxGroup(TRACKS, label="Select Your Track(s)") |
|
|
category_checkboxes = gr.CheckboxGroup( |
|
|
CATEGORIES, label="Select Your Category(s)" |
|
|
) |
|
|
submit_button = gr.Button("Submit Project", variant="primary") |
|
|
|
|
|
|
|
|
def handle_page_load(request: gr.Request, filter_view: str): |
|
|
username = get_username(request) |
|
|
if username: |
|
|
gr.Info(f"Welcome, {username}!") |
|
|
return render_leaderboard(request, filter_view) |
|
|
|
|
|
def cancel_edit(): |
|
|
return gr.update(visible=False), "", "", "" |
|
|
|
|
|
def handle_leaderboard_click(evt: gr.SelectData): |
|
|
if evt.index == ".edit-button": |
|
|
project_to_edit_url = evt.value.get("url") |
|
|
if project_to_edit_url: |
|
|
projects_df = load_data(PROJECTS_FILE, DATASET_REPO_ID) |
|
|
project_data = projects_df[ |
|
|
projects_df["space_url"] == project_to_edit_url |
|
|
].iloc[0] |
|
|
return ( |
|
|
gr.update(visible=True), |
|
|
project_data["space_url"], |
|
|
project_data["space_url"], |
|
|
project_data["video_url"], |
|
|
) |
|
|
return gr.skip(), gr.skip(), gr.skip(), gr.skip() |
|
|
|
|
|
app.load( |
|
|
fn=handle_page_load, |
|
|
inputs=[leaderboard_filter], |
|
|
outputs=[leaderboard_html, project_dropdown, vote_context_display, edit_group], |
|
|
) |
|
|
leaderboard_filter.change( |
|
|
fn=render_leaderboard, |
|
|
inputs=[leaderboard_filter], |
|
|
outputs=[leaderboard_html, project_dropdown, vote_context_display, edit_group], |
|
|
) |
|
|
submit_button.click( |
|
|
fn=submit_project, |
|
|
inputs=[ |
|
|
space_url_input, |
|
|
video_url_input, |
|
|
track_checkboxes, |
|
|
category_checkboxes, |
|
|
], |
|
|
outputs=[ |
|
|
submission_status, |
|
|
leaderboard_html, |
|
|
project_dropdown, |
|
|
vote_context_display, |
|
|
], |
|
|
) |
|
|
vote_button.click( |
|
|
fn=cast_vote, |
|
|
inputs=[project_dropdown, leaderboard_filter], |
|
|
outputs=[vote_status, leaderboard_html, project_dropdown, vote_context_display], |
|
|
) |
|
|
|
|
|
leaderboard_html.select( |
|
|
fn=handle_leaderboard_click, |
|
|
outputs=[edit_group, edit_old_url, edit_new_url, edit_new_video_url], |
|
|
) |
|
|
|
|
|
save_edit_button.click( |
|
|
fn=edit_project, |
|
|
inputs=[edit_old_url, edit_new_url, edit_new_video_url, leaderboard_filter], |
|
|
outputs=[ |
|
|
submission_status, |
|
|
leaderboard_html, |
|
|
project_dropdown, |
|
|
vote_context_display, |
|
|
edit_group, |
|
|
], |
|
|
) |
|
|
cancel_edit_button.click( |
|
|
fn=cancel_edit, |
|
|
outputs=[edit_group, edit_old_url, edit_new_url, edit_new_video_url], |
|
|
) |
|
|
|
|
|
app.launch() |
|
|
|