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 # Import the custom component load_dotenv() # CONFIGURATION DATASET_REPO_ID = "MCP-1st-Birthday/hackathon-community-voting" PROJECTS_FILE = "projects.csv" VOTES_FILE = "votes.csv" HF_TOKEN = os.getenv("HF_TOKEN") api = HfApi(token=HF_TOKEN) # Hackathon Structure 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)"] # --- DATA HANDLING FUNCTIONS --- def load_data(filename, repo_id): """Loads a CSV from the dataset, or creates an empty DataFrame if it doesn't exist.""" 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): """Saves a DataFrame as a CSV to the dataset.""" 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) # --- CORE LOGIC --- def get_username(request: gr.Request): """Safely gets the username using the user's original, working method.""" return request.request.session.get('oauth_info', {}).get('userinfo', {}).get('preferred_username') def parse_view_to_context(filter_view: str): """Helper to get track and category from the leaderboard view string.""" 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): """Renders the leaderboard HTML, including a conditional Edit button.""" 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 "

No projects submitted yet.

", 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"

No projects submitted for '{filter_view}' yet.

", gr.update(choices=[], value=None), vote_context_str, gr.update(visible=False) display_df['votes'] = display_df['votes'].astype(int) display_df = display_df.sort_values(by="votes", ascending=False).reset_index(drop=True) html = "
" trophies = {0: "đŸĨ‡", 1: "đŸĨˆ", 2: "đŸĨ‰"} for index, row in display_df.iterrows(): rank = trophies.get(index, f"#{index + 1}") space_name = re.sub(r'https://huggingface.co/spaces/', '', row["space_url"]) category_text = row["display_category"] action_button_html = "" if username and row["submitted_by"] == username: action_button_html = f'âœī¸' html += f"""
{rank}
{space_name}
🚀 Space | đŸŽŦ Video | Track: {row["track"].split(';')[0].split(':')[0]} | Categories: {category_text}
{row["votes"]} votes
{action_button_html}
""" html += "
" 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, video_url, selected_tracks, selected_categories]): gr.Info("Error: All fields are required, including at least one track and one category.") 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() if not project_to_vote: gr.Info("Error: Please select a project to vote for.") return 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() 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 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 --- 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' ) # --- GRADIO INTERFACE --- with gr.Blocks(theme=theme, title="Hackathon Community Choice") as app: gr.Markdown("# 🏆 Hackathon Community Choice Award") gr.Markdown("Vote for your favorite hackathon projects! Please log in to participate.") 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 (YouTube)", 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") # --- Event Handling --- 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]) 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()