elismasilva commited on
Commit
97cba6f
Β·
1 Parent(s): bde774d
Files changed (3) hide show
  1. README.md +1 -1
  2. app.py +169 -166
  3. requirements.txt +3 -2
README.md CHANGED
@@ -4,7 +4,7 @@ emoji: πŸ†
4
  colorFrom: pink
5
  colorTo: yellow
6
  sdk: gradio
7
- sdk_version: 6.0.1
8
  app_file: app.py
9
  pinned: false
10
  license: apache-2.0
 
4
  colorFrom: pink
5
  colorTo: yellow
6
  sdk: gradio
7
+ sdk_version: 5.50.0
8
  app_file: app.py
9
  pinned: false
10
  license: apache-2.0
app.py CHANGED
@@ -5,6 +5,7 @@ import os
5
  from datetime import datetime
6
  from dotenv import load_dotenv
7
  import re
 
8
 
9
  load_dotenv()
10
 
@@ -12,8 +13,6 @@ load_dotenv()
12
  DATASET_REPO_ID = "MCP-1st-Birthday/hackathon-community-voting"
13
  PROJECTS_FILE = "projects.csv"
14
  VOTES_FILE = "votes.csv"
15
-
16
- # Authentication with the token from Space secrets
17
  HF_TOKEN = os.getenv("HF_TOKEN")
18
  api = HfApi(token=HF_TOKEN)
19
 
@@ -22,19 +21,17 @@ TRACKS = ["Track 1: Building MCP", "Track 2: MCP in Action"]
22
  CATEGORIES = ["Enterprise", "Consumer", "Creative"]
23
  LEADERBOARD_VIEWS = ["Track 1 (Overall)", "Track 2 (Enterprise)", "Track 2 (Consumer)", "Track 2 (Creative)"]
24
 
25
-
26
- # DATA HANDLING FUNCTIONS
27
-
28
  def load_data(filename, repo_id):
29
  """Loads a CSV from the dataset, or creates an empty DataFrame if it doesn't exist."""
30
  try:
31
  filepath = hf_hub_download(repo_id=repo_id, filename=filename, repo_type="dataset", token=HF_TOKEN)
32
- return pd.read_csv(filepath)
33
  except Exception:
34
  if filename == PROJECTS_FILE:
35
  return pd.DataFrame(columns=["space_url", "video_url", "track", "category", "submitted_by", "timestamp"])
36
  elif filename == VOTES_FILE:
37
- return pd.DataFrame(columns=["space_url", "voted_by", "timestamp"])
38
  return pd.DataFrame()
39
 
40
  def save_data(df, filename, repo_id, commit_message):
@@ -42,199 +39,211 @@ def save_data(df, filename, repo_id, commit_message):
42
  temp_path = f"./{filename}"
43
  df.to_csv(temp_path, index=False)
44
  upload_file(
45
- path_or_fileobj=temp_path,
46
- path_in_repo=filename,
47
- repo_id=repo_id,
48
- repo_type="dataset",
49
- token=HF_TOKEN,
50
- commit_message=commit_message,
51
  )
52
  os.remove(temp_path)
53
 
54
- # CORE LOGIC
 
 
 
 
 
 
 
 
 
 
 
 
 
55
 
56
  def render_leaderboard(request: gr.Request, filter_view: str):
57
- """
58
- Renders the leaderboard HTML. It gets the user identity from the request object.
59
- This is a "pure" rendering function without side-effects.
60
- """
61
- username = request.request.session['oauth_info']['userinfo']['preferred_username'] if hasattr(request.request, "session") and len(request.request.session) > 0 and 'userinfo' in request.request.session['oauth_info'] else None
62
- if not username:
63
- return "<h3>Please log in to view and vote on projects!</h3>", gr.update(choices=[], value=None)
64
-
65
  projects_df = load_data(PROJECTS_FILE, DATASET_REPO_ID)
66
  votes_df = load_data(VOTES_FILE, DATASET_REPO_ID)
 
67
 
68
  if projects_df.empty:
69
- return "<h3>No projects have been submitted yet. Be the first!</h3>", gr.update(choices=[], value=None)
70
 
71
- vote_counts = votes_df['space_url'].value_counts().reset_index()
72
- vote_counts.columns = ['space_url', 'votes']
73
-
74
- full_leaderboard_df = pd.merge(projects_df, vote_counts, on="space_url", how="left").fillna(0)
75
- full_leaderboard_df['votes'] = full_leaderboard_df['votes'].astype(int)
76
 
77
- # Filtering logic...
78
- if filter_view == "Track 1 (Overall)":
79
- filtered_df = full_leaderboard_df[full_leaderboard_df['track'] == TRACKS[0]]
80
- elif filter_view == "Track 2 (Enterprise)":
81
- filtered_df = full_leaderboard_df[(full_leaderboard_df['track'] == TRACKS[1]) & (full_leaderboard_df['category'] == 'Enterprise')]
82
- elif filter_view == "Track 2 (Consumer)":
83
- filtered_df = full_leaderboard_df[(full_leaderboard_df['track'] == TRACKS[1]) & (full_leaderboard_df['category'] == 'Consumer')]
84
- elif filter_view == "Track 2 (Creative)":
85
- filtered_df = full_leaderboard_df[(full_leaderboard_df['track'] == TRACKS[1]) & (full_leaderboard_df['category'] == 'Creative')]
86
- else:
87
- filtered_df = full_leaderboard_df[full_leaderboard_df['track'] == TRACKS[0]]
 
 
 
 
 
 
 
 
 
 
 
88
 
89
- if filtered_df.empty:
90
- return f"<h3>No projects submitted for this view ('{filter_view}') yet.</h3>", gr.update(choices=[], value=None)
91
 
92
- filtered_df = filtered_df.sort_values(by="votes", ascending=False).reset_index(drop=True)
93
- html = "<div>" # HTML generation remains the same...
94
  trophies = {0: "πŸ₯‡", 1: "πŸ₯ˆ", 2: "πŸ₯‰"}
95
- for index, row in filtered_df.iterrows():
96
  rank = trophies.get(index, f"<b>#{index + 1}</b>")
97
- space_name = re.sub(r'https://huggingface.co/spaces/', '', row['space_url'])
 
 
 
 
 
 
98
  html += f"""
99
- <div style="display: flex; align-items: center; padding: 12px; border-bottom: 1px solid #eee; gap: 15px;">
100
  <div style="font-size: 24px; width: 50px;">{rank}</div>
101
  <div style="flex-grow: 1;">
102
  <div style="font-weight: bold; font-size: 16px;">{space_name}</div>
103
  <div style="font-size: 12px; color: #555;">
104
- <a href="{row['space_url']}" target="_blank">πŸš€ Space</a> |
105
- <a href="{row['video_url']}" target="_blank">🎬 Video</a> |
106
- <span style="color: #777;">Track: {row['track'].split(':')[0]} | Category: {row['category']}</span>
107
  </div>
108
  </div>
109
- <div style="font-size: 20px; font-weight: bold; color: #3B82F6;">{row['votes']} votes</div>
 
110
  </div>
111
  """
112
  html += "</div>"
113
- project_urls = filtered_df['space_url'].tolist()
114
- return html, gr.update(choices=project_urls, value=None)
 
115
 
116
 
117
- def submit_project(request: gr.Request, space_url, video_url, track, category):
118
- """Adds a new project."""
119
- username = request.request.session['oauth_info']['userinfo']['preferred_username'] if hasattr(request.request, "session") and len(request.request.session) > 0 and 'userinfo' in request.request.session['oauth_info'] else None
120
  if not username:
121
  gr.Info("Error: You must be logged in to submit a project.")
122
- return gr.skip(), gr.skip(), gr.skip()
123
- if not all([space_url, video_url, track, category]):
124
- gr.Info("Error: All fields are required.")
125
- return gr.skip(), gr.skip(), gr.skip()
126
- if not ("huggingface.co/spaces/" in space_url and ("youtube.com/" in video_url or "youtu.be/" in video_url)):
127
- gr.Info("Error: Please enter valid URLs from Hugging Face Spaces and YouTube.")
128
- return gr.skip(), gr.skip(), gr.skip()
129
-
130
  projects_df = load_data(PROJECTS_FILE, DATASET_REPO_ID)
131
-
132
  if space_url in projects_df['space_url'].values:
133
  gr.Info("Error: This project has already been submitted.")
134
- return gr.skip(), gr.skip(), gr.skip()
135
-
136
- new_project = pd.DataFrame([{"space_url": space_url, "video_url": video_url, "track": track, "category": category, "submitted_by": username, "timestamp": datetime.now().isoformat()}])
 
137
  updated_projects = pd.concat([projects_df, new_project], ignore_index=True)
138
  save_data(updated_projects, PROJECTS_FILE, DATASET_REPO_ID, f"Project submitted by {username}")
139
-
140
  gr.Info(f"βœ… Success! Project '{space_url.split('/')[-1]}' submitted.")
141
-
142
- # After submission, refresh the leaderboard
143
- html, dropdown_update = render_leaderboard(request, LEADERBOARD_VIEWS[0])
144
- return f"Last action: Success.", html, dropdown_update
145
 
146
 
147
  def cast_vote(request: gr.Request, project_to_vote, filter_view):
148
- """Registers a vote."""
149
- username = request.request.session['oauth_info']['userinfo']['preferred_username'] if hasattr(request.request, "session") and len(request.request.session) > 0 and 'userinfo' in request.request.session['oauth_info'] else None
150
  if not username:
151
  gr.Info("Error: You must be logged in to vote.")
152
  return gr.skip(), gr.skip(), gr.skip()
153
  if not project_to_vote:
154
  gr.Info("Error: Please select a project to vote for.")
155
  return gr.skip(), gr.skip(), gr.skip()
156
-
157
  votes_df = load_data(VOTES_FILE, DATASET_REPO_ID)
158
-
159
- if not votes_df[(votes_df['space_url'] == project_to_vote) & (votes_df['voted_by'] == username)].empty:
160
- gr.Info(f"Notice: You have already voted for '{project_to_vote.split('/')[-1]}'.")
 
161
  return gr.skip(), gr.skip(), gr.skip()
162
-
163
- new_vote = pd.DataFrame([{"space_url": project_to_vote, "voted_by": username, "timestamp": datetime.now().isoformat()}])
164
  updated_votes = pd.concat([votes_df, new_vote], ignore_index=True)
165
  save_data(updated_votes, VOTES_FILE, DATASET_REPO_ID, f"Vote cast by {username}")
 
 
 
166
 
167
- gr.Info(f"βœ… Vote successfully cast for '{project_to_vote.split('/')[-1]}'!")
168
 
169
- # After voting, refresh the leaderboard with the current filter
170
- html, dropdown_update = render_leaderboard(request, filter_view)
171
- return f"Last action: Success.", html, dropdown_update
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
172
 
173
- theme = gr.themes.Default(
174
- primary_hue='blue',
175
- secondary_hue='yellow',
176
- neutral_hue='neutral'
177
- ).set(
178
- body_background_fill='*neutral_100',
179
- body_background_fill_dark='*neutral_800',
180
- body_text_color='*neutral_700',
181
- body_text_color_dark='*neutral_200',
182
- body_text_size='1.1em',
183
- code_background_fill='*neutral_100',
184
- code_background_fill_dark='*neutral_800',
185
- shadow_drop='2px 2px 4px *neutral_400',
186
- block_label_background_fill='*neutral_100',
187
- block_label_background_fill_dark='*neutral_800',
188
- block_label_text_color='*neutral_700',
189
- block_label_text_color_dark='*neutral_200',
190
- block_title_text_color='*primary_700',
191
- block_title_text_color_dark='*primary_300',
192
- panel_background_fill='*neutral_50',
193
- panel_background_fill_dark='*neutral_900',
194
- panel_border_color='*neutral_200',
195
- panel_border_color_dark='*neutral_700',
196
- checkbox_border_color='*neutral_300',
197
- checkbox_border_color_dark='*neutral_600',
198
- input_background_fill='white',
199
- input_background_fill_dark='*neutral_800',
200
- input_border_color='*neutral_300',
201
- input_border_color_dark='*neutral_600',
202
- slider_color='*primary_500',
203
- slider_color_dark='*primary_400',
204
- button_primary_background_fill='*primary_600',
205
- button_primary_background_fill_dark='*primary_500',
206
- button_primary_background_fill_hover='*primary_700',
207
- button_primary_background_fill_hover_dark='*primary_600',
208
- button_primary_text_color='white',
209
- button_primary_text_color_dark='white',
210
- button_secondary_background_fill='*secondary_400',
211
- button_secondary_background_fill_dark='*secondary_500',
212
- button_secondary_background_fill_hover='*secondary_500',
213
- button_secondary_background_fill_hover_dark='*secondary_600',
214
- button_secondary_text_color='*neutral_700',
215
- button_secondary_text_color_dark='*neutral_200',
216
- button_cancel_background_fill='*neutral_200',
217
- button_cancel_background_fill_dark='*neutral_700',
218
- button_cancel_background_fill_hover='*neutral_300',
219
- button_cancel_background_fill_hover_dark='*neutral_600',
220
- button_cancel_text_color='*neutral_700',
221
  button_cancel_text_color_dark='*neutral_200'
222
  )
223
 
224
- with gr.Blocks(title="Hackathon Community Choice") as app:
 
225
  gr.Markdown("# πŸ† Hackathon Community Choice Award")
226
  gr.Markdown("Vote for your favorite hackathon projects! Please log in to participate.")
227
-
228
  gr.LoginButton()
229
 
230
  with gr.Tabs() as tabs:
231
  with gr.TabItem("πŸ† Leaderboard & Vote", id=0):
232
  leaderboard_filter = gr.Radio(LEADERBOARD_VIEWS, label="Select Leaderboard View", value=LEADERBOARD_VIEWS[0])
 
 
 
 
 
 
 
 
 
233
  with gr.Row():
234
  with gr.Column(scale=2):
235
- leaderboard_html = gr.HTML("Please log in to load...")
 
 
 
236
  with gr.Column(scale=1):
237
- gr.Markdown("### Cast Your Vote!")
 
238
  vote_status = gr.Markdown()
239
  project_dropdown = gr.Dropdown(label="Select a Project to Vote For", interactive=True)
240
  vote_button = gr.Button("πŸ‘ Vote for Selected Project", variant="primary")
@@ -244,43 +253,37 @@ with gr.Blocks(title="Hackathon Community Choice") as app:
244
  submission_status = gr.Markdown()
245
  space_url_input = gr.Textbox(label="Your Hugging Face Space URL", placeholder="https://huggingface.co/spaces/...")
246
  video_url_input = gr.Textbox(label="Your Demo Video URL (YouTube)", placeholder="https://www.youtube.com/watch?v=...")
247
- track_radio = gr.Radio(TRACKS, label="Select Your Track")
248
- category_dropdown = gr.Dropdown(CATEGORIES, label="Select Your Category")
249
  submit_button = gr.Button("Submit Project", variant="primary")
250
 
251
- # Event Handling
252
-
253
  def handle_page_load(request: gr.Request, filter_view: str):
254
- """Called only on app.load. Gives welcome message and renders."""
255
- username = request.request.session['oauth_info']['userinfo']['preferred_username'] if hasattr(request.request, "session") and len(request.request.session) > 0 and 'userinfo' in request.request.session['oauth_info'] else None
256
  if username:
257
  gr.Info(f"Welcome, {username}!")
258
  return render_leaderboard(request, filter_view)
259
 
260
- # The page load triggers the first render and welcome message.
261
- app.load(
262
- fn=handle_page_load,
263
- inputs=[leaderboard_filter],
264
- outputs=[leaderboard_html, project_dropdown]
265
- )
266
-
267
- # When the user changes the filter, re-render the leaderboard (no welcome message).
268
- leaderboard_filter.change(
269
- fn=render_leaderboard,
270
- inputs=[leaderboard_filter],
271
- outputs=[leaderboard_html, project_dropdown]
272
- )
 
 
 
273
 
274
- submit_button.click(
275
- fn=submit_project,
276
- inputs=[space_url_input, video_url_input, track_radio, category_dropdown],
277
- outputs=[submission_status, leaderboard_html, project_dropdown]
278
- )
279
 
280
- vote_button.click(
281
- fn=cast_vote,
282
- inputs=[project_dropdown, leaderboard_filter],
283
- outputs=[vote_status, leaderboard_html, project_dropdown]
284
- )
285
 
286
- app.launch(theme=theme)
 
5
  from datetime import datetime
6
  from dotenv import load_dotenv
7
  import re
8
+ from gradio_htmlplus import HTMLPlus # Import the custom component
9
 
10
  load_dotenv()
11
 
 
13
  DATASET_REPO_ID = "MCP-1st-Birthday/hackathon-community-voting"
14
  PROJECTS_FILE = "projects.csv"
15
  VOTES_FILE = "votes.csv"
 
 
16
  HF_TOKEN = os.getenv("HF_TOKEN")
17
  api = HfApi(token=HF_TOKEN)
18
 
 
21
  CATEGORIES = ["Enterprise", "Consumer", "Creative"]
22
  LEADERBOARD_VIEWS = ["Track 1 (Overall)", "Track 2 (Enterprise)", "Track 2 (Consumer)", "Track 2 (Creative)"]
23
 
24
+ # --- DATA HANDLING FUNCTIONS ---
 
 
25
  def load_data(filename, repo_id):
26
  """Loads a CSV from the dataset, or creates an empty DataFrame if it doesn't exist."""
27
  try:
28
  filepath = hf_hub_download(repo_id=repo_id, filename=filename, repo_type="dataset", token=HF_TOKEN)
29
+ return pd.read_csv(filepath, dtype={'track': str, 'category': str})
30
  except Exception:
31
  if filename == PROJECTS_FILE:
32
  return pd.DataFrame(columns=["space_url", "video_url", "track", "category", "submitted_by", "timestamp"])
33
  elif filename == VOTES_FILE:
34
+ return pd.DataFrame(columns=["space_url", "voted_by", "track", "category", "timestamp"])
35
  return pd.DataFrame()
36
 
37
  def save_data(df, filename, repo_id, commit_message):
 
39
  temp_path = f"./{filename}"
40
  df.to_csv(temp_path, index=False)
41
  upload_file(
42
+ path_or_fileobj=temp_path, path_in_repo=filename, repo_id=repo_id, repo_type="dataset",
43
+ token=HF_TOKEN, commit_message=commit_message,
 
 
 
 
44
  )
45
  os.remove(temp_path)
46
 
47
+ # --- CORE LOGIC ---
48
+
49
+ def get_username(request: gr.Request):
50
+ """Safely gets the username using the user's original, working method."""
51
+ return request.request.session.get('oauth_info', {}).get('userinfo', {}).get('preferred_username')
52
+
53
+ def parse_view_to_context(filter_view: str):
54
+ """Helper to get track and category from the leaderboard view string."""
55
+ if "Track 1" in filter_view: return TRACKS[0], "Overall"
56
+ if "Track 2" in filter_view:
57
+ if "Enterprise" in filter_view: return TRACKS[1], "Enterprise"
58
+ if "Consumer" in filter_view: return TRACKS[1], "Consumer"
59
+ if "Creative" in filter_view: return TRACKS[1], "Creative"
60
+ return None, None
61
 
62
  def render_leaderboard(request: gr.Request, filter_view: str):
63
+ """Renders the leaderboard HTML, including a conditional Edit button."""
64
+ username = get_username(request)
 
 
 
 
 
 
65
  projects_df = load_data(PROJECTS_FILE, DATASET_REPO_ID)
66
  votes_df = load_data(VOTES_FILE, DATASET_REPO_ID)
67
+ vote_context_str = f"ℹ️ Your vote will be cast in the **'{filter_view}'** context."
68
 
69
  if projects_df.empty:
70
+ return "<h3>No projects submitted yet.</h3>", gr.update(choices=[], value=None), vote_context_str, gr.update(visible=False)
71
 
72
+ track_filter, category_filter = parse_view_to_context(filter_view)
73
+ display_df = pd.DataFrame()
 
 
 
74
 
75
+ if track_filter == TRACKS[0]:
76
+ track1_projects = projects_df[projects_df['track'].str.contains(TRACKS[0], na=False)].copy()
77
+ if not track1_projects.empty:
78
+ track1_projects['display_category'] = track1_projects['category'].str.replace(';', ' | ')
79
+ track1_votes = votes_df[(votes_df['track'] == TRACKS[0]) & (votes_df['category'] == "Overall")]
80
+ vote_counts = track1_votes.groupby('space_url').size().reset_index(name='votes')
81
+ display_df = pd.merge(track1_projects, vote_counts, on='space_url', how='left').fillna(0)
82
+
83
+ elif track_filter == TRACKS[1]:
84
+ projects_df['track'] = projects_df['track'].str.split(';')
85
+ exploded_tracks = projects_df.explode('track')
86
+ exploded_tracks['category'] = exploded_tracks['category'].str.split(';')
87
+ exploded_projects = exploded_tracks.explode('category')
88
+ track2_filtered = exploded_projects[(exploded_projects['track'] == track_filter) & (exploded_projects['category'] == category_filter)]
89
+ if not track2_filtered.empty:
90
+ context_votes = votes_df[(votes_df['track'] == track_filter) & (votes_df['category'] == category_filter)]
91
+ vote_counts = context_votes.groupby('space_url').size().reset_index(name='votes')
92
+ display_df = pd.merge(track2_filtered, vote_counts, on='space_url', how='left').fillna(0)
93
+ display_df['display_category'] = display_df['category']
94
+
95
+ if display_df.empty:
96
+ return f"<h3>No projects submitted for '{filter_view}' yet.</h3>", gr.update(choices=[], value=None), vote_context_str, gr.update(visible=False)
97
 
98
+ display_df['votes'] = display_df['votes'].astype(int)
99
+ display_df = display_df.sort_values(by="votes", ascending=False).reset_index(drop=True)
100
 
101
+ html = "<div>"
 
102
  trophies = {0: "πŸ₯‡", 1: "πŸ₯ˆ", 2: "πŸ₯‰"}
103
+ for index, row in display_df.iterrows():
104
  rank = trophies.get(index, f"<b>#{index + 1}</b>")
105
+ space_name = re.sub(r'https://huggingface.co/spaces/', '', row["space_url"])
106
+ category_text = row["display_category"]
107
+
108
+ action_button_html = ""
109
+ if username and row["submitted_by"] == username:
110
+ 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>'
111
+
112
  html += f"""
113
+ <div style="display: flex; align-items: center; padding: 12px; border-bottom: 1px solid #eee; gap: 15px;" data-space-url="{row['space_url']}">
114
  <div style="font-size: 24px; width: 50px;">{rank}</div>
115
  <div style="flex-grow: 1;">
116
  <div style="font-weight: bold; font-size: 16px;">{space_name}</div>
117
  <div style="font-size: 12px; color: #555;">
118
+ <a href="{row["space_url"]}" target="_blank">πŸš€ Space</a> |
119
+ <a href="{row["video_url"]}" target="_blank">🎬 Video</a> |
120
+ <span style="color: #777;">Track: {row["track"].split(';')[0].split(':')[0]} | Categories: {category_text}</span>
121
  </div>
122
  </div>
123
+ <div style="font-size: 20px; font-weight: bold; color: #3B82F6;">{row["votes"]} votes</div>
124
+ {action_button_html}
125
  </div>
126
  """
127
  html += "</div>"
128
+
129
+ project_urls = display_df['space_url'].unique().tolist()
130
+ return html, gr.update(choices=project_urls, value=None), vote_context_str, gr.update(visible=False)
131
 
132
 
133
+ def submit_project(request: gr.Request, space_url, video_url, selected_tracks, selected_categories):
134
+ username = get_username(request)
 
135
  if not username:
136
  gr.Info("Error: You must be logged in to submit a project.")
137
+ return gr.skip(), gr.skip(), gr.skip(), gr.skip()
138
+ if not all([space_url, video_url, selected_tracks, selected_categories]):
139
+ gr.Info("Error: All fields are required, including at least one track and one category.")
140
+ return gr.skip(), gr.skip(), gr.skip(), gr.skip()
 
 
 
 
141
  projects_df = load_data(PROJECTS_FILE, DATASET_REPO_ID)
 
142
  if space_url in projects_df['space_url'].values:
143
  gr.Info("Error: This project has already been submitted.")
144
+ return gr.skip(), gr.skip(), gr.skip(), gr.skip()
145
+ tracks_str = ";".join(selected_tracks)
146
+ categories_str = ";".join(selected_categories)
147
+ 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()}])
148
  updated_projects = pd.concat([projects_df, new_project], ignore_index=True)
149
  save_data(updated_projects, PROJECTS_FILE, DATASET_REPO_ID, f"Project submitted by {username}")
 
150
  gr.Info(f"βœ… Success! Project '{space_url.split('/')[-1]}' submitted.")
151
+ html, dropdown, context, edit_box_visibility = render_leaderboard(request, LEADERBOARD_VIEWS[0])
152
+ return f"Last action: Success.", html, dropdown, context
 
 
153
 
154
 
155
  def cast_vote(request: gr.Request, project_to_vote, filter_view):
156
+ username = get_username(request)
 
157
  if not username:
158
  gr.Info("Error: You must be logged in to vote.")
159
  return gr.skip(), gr.skip(), gr.skip()
160
  if not project_to_vote:
161
  gr.Info("Error: Please select a project to vote for.")
162
  return gr.skip(), gr.skip(), gr.skip()
 
163
  votes_df = load_data(VOTES_FILE, DATASET_REPO_ID)
164
+ vote_track, vote_category = parse_view_to_context(filter_view)
165
+ 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)]
166
+ if not existing_vote.empty:
167
+ gr.Info(f"Notice: You have already voted for this project in the '{filter_view}' context.")
168
  return gr.skip(), gr.skip(), gr.skip()
169
+ new_vote = pd.DataFrame([{"space_url": project_to_vote, "voted_by": username, "track": vote_track, "category": vote_category, "timestamp": datetime.now().isoformat()}])
 
170
  updated_votes = pd.concat([votes_df, new_vote], ignore_index=True)
171
  save_data(updated_votes, VOTES_FILE, DATASET_REPO_ID, f"Vote cast by {username}")
172
+ gr.Info(f"βœ… Vote successfully cast for '{project_to_vote.split('/')[-1]}' in '{filter_view}'!")
173
+ html, dropdown, context = render_leaderboard(request, filter_view)
174
+ return f"Last action: Success.", html, dropdown
175
 
 
176
 
177
+ def edit_project(request: gr.Request, old_space_url: str, new_space_url: str, new_video_url: str, filter_view: str):
178
+ username = get_username(request)
179
+ if not username:
180
+ gr.Info("Error: Authentication failed. Please log in again.")
181
+ return gr.skip(), gr.skip(), gr.skip(), gr.update(visible=False)
182
+ projects_df = load_data(PROJECTS_FILE, DATASET_REPO_ID)
183
+ votes_df = load_data(VOTES_FILE, DATASET_REPO_ID)
184
+ project_index = projects_df[(projects_df['space_url'] == old_space_url) & (projects_df['submitted_by'] == username)].index
185
+ if project_index.empty:
186
+ gr.Info("Error: You do not have permission to edit this project, or it no longer exists.")
187
+ return gr.skip(), gr.skip(), gr.skip(), gr.update(visible=False)
188
+ projects_df.loc[project_index, 'space_url'] = new_space_url
189
+ projects_df.loc[project_index, 'video_url'] = new_video_url
190
+ save_data(projects_df, PROJECTS_FILE, DATASET_REPO_ID, f"Project edited by {username}: {old_space_url} -> {new_space_url}")
191
+ if old_space_url != new_space_url:
192
+ votes_df.loc[votes_df['space_url'] == old_space_url, 'space_url'] = new_space_url
193
+ save_data(votes_df, VOTES_FILE, DATASET_REPO_ID, f"Vote URLs updated for project edit by {username}")
194
+ gr.Info("βœ… Project URLs updated successfully! Votes have been preserved.")
195
+ html, dropdown, context, _ = render_leaderboard(request, filter_view)
196
+ return "Project edited.", html, dropdown, context, gr.update(visible=False)
197
 
198
+ # --- THEME ---
199
+ theme = gr.themes.Default(primary_hue='blue', secondary_hue='yellow', neutral_hue='neutral').set(
200
+ body_background_fill='*neutral_100', body_background_fill_dark='*neutral_800', body_text_color='*neutral_700',
201
+ body_text_color_dark='*neutral_200', body_text_size='1.1em', code_background_fill='*neutral_100',
202
+ code_background_fill_dark='*neutral_800', shadow_drop='2px 2px 4px *neutral_400', block_label_background_fill='*neutral_100',
203
+ block_label_background_fill_dark='*neutral_800', block_label_text_color='*neutral_700', block_label_text_color_dark='*neutral_200',
204
+ block_title_text_color='*primary_700', block_title_text_color_dark='*primary_300', panel_background_fill='*neutral_50',
205
+ panel_background_fill_dark='*neutral_900', panel_border_color='*neutral_200', panel_border_color_dark='*neutral_700',
206
+ checkbox_border_color='*neutral_300', checkbox_border_color_dark='*neutral_600', input_background_fill='white',
207
+ input_background_fill_dark='*neutral_800', input_border_color='*neutral_300', input_border_color_dark='*neutral_600',
208
+ slider_color='*primary_500', slider_color_dark='*primary_400', button_primary_background_fill='*primary_600',
209
+ button_primary_background_fill_dark='*primary_500', button_primary_background_fill_hover='*primary_700',
210
+ button_primary_background_fill_hover_dark='*primary_600', button_primary_text_color='white',
211
+ button_primary_text_color_dark='white', button_secondary_background_fill='*secondary_400',
212
+ button_secondary_background_fill_dark='*secondary_500', button_secondary_background_fill_hover='*secondary_500',
213
+ button_secondary_background_fill_hover_dark='*secondary_600', button_secondary_text_color='*neutral_700',
214
+ button_secondary_text_color_dark='*neutral_200', button_cancel_background_fill='*neutral_200',
215
+ button_cancel_background_fill_dark='*neutral_700', button_cancel_background_fill_hover='*neutral_300',
216
+ button_cancel_background_fill_hover_dark='*neutral_600', button_cancel_text_color='*neutral_700',
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
217
  button_cancel_text_color_dark='*neutral_200'
218
  )
219
 
220
+ # --- GRADIO INTERFACE ---
221
+ with gr.Blocks(theme=theme, title="Hackathon Community Choice") as app:
222
  gr.Markdown("# πŸ† Hackathon Community Choice Award")
223
  gr.Markdown("Vote for your favorite hackathon projects! Please log in to participate.")
 
224
  gr.LoginButton()
225
 
226
  with gr.Tabs() as tabs:
227
  with gr.TabItem("πŸ† Leaderboard & Vote", id=0):
228
  leaderboard_filter = gr.Radio(LEADERBOARD_VIEWS, label="Select Leaderboard View", value=LEADERBOARD_VIEWS[0])
229
+ with gr.Group(visible=False) as edit_group:
230
+ gr.Markdown("### Edit Project URLs")
231
+ edit_old_url = gr.Textbox(label="Original Space URL", interactive=False)
232
+ edit_new_url = gr.Textbox(label="New Space URL")
233
+ edit_new_video_url = gr.Textbox(label="New Video URL")
234
+ with gr.Row():
235
+ save_edit_button = gr.Button("Save Changes", variant="primary")
236
+ cancel_edit_button = gr.Button("Cancel")
237
+
238
  with gr.Row():
239
  with gr.Column(scale=2):
240
+ leaderboard_html = HTMLPlus(
241
+ "Please log in to load...",
242
+ selectable_elements=[".edit-button"]
243
+ )
244
  with gr.Column(scale=1):
245
+ gr.Markdown("### Cast Your Vote")
246
+ vote_context_display = gr.Markdown()
247
  vote_status = gr.Markdown()
248
  project_dropdown = gr.Dropdown(label="Select a Project to Vote For", interactive=True)
249
  vote_button = gr.Button("πŸ‘ Vote for Selected Project", variant="primary")
 
253
  submission_status = gr.Markdown()
254
  space_url_input = gr.Textbox(label="Your Hugging Face Space URL", placeholder="https://huggingface.co/spaces/...")
255
  video_url_input = gr.Textbox(label="Your Demo Video URL (YouTube)", placeholder="https://www.youtube.com/watch?v=...")
256
+ track_checkboxes = gr.CheckboxGroup(TRACKS, label="Select Your Track(s)")
257
+ category_checkboxes = gr.CheckboxGroup(CATEGORIES, label="Select Your Category(s)")
258
  submit_button = gr.Button("Submit Project", variant="primary")
259
 
260
+ # --- Event Handling ---
 
261
  def handle_page_load(request: gr.Request, filter_view: str):
262
+ username = get_username(request)
 
263
  if username:
264
  gr.Info(f"Welcome, {username}!")
265
  return render_leaderboard(request, filter_view)
266
 
267
+ def cancel_edit():
268
+ return gr.update(visible=False), "", "", ""
269
+
270
+ def handle_leaderboard_click(evt: gr.SelectData):
271
+ if evt.index == ".edit-button":
272
+ project_to_edit_url = evt.value.get("url")
273
+ if project_to_edit_url:
274
+ projects_df = load_data(PROJECTS_FILE, DATASET_REPO_ID)
275
+ project_data = projects_df[projects_df['space_url'] == project_to_edit_url].iloc[0]
276
+ return gr.update(visible=True), project_data['space_url'], project_data['space_url'], project_data['video_url']
277
+ return gr.skip(), gr.skip(), gr.skip(), gr.skip()
278
+
279
+ app.load(fn=handle_page_load, inputs=[leaderboard_filter], outputs=[leaderboard_html, project_dropdown, vote_context_display, edit_group])
280
+ leaderboard_filter.change(fn=render_leaderboard, inputs=[leaderboard_filter], outputs=[leaderboard_html, project_dropdown, vote_context_display, edit_group])
281
+ 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])
282
+ vote_button.click(fn=cast_vote, inputs=[project_dropdown, leaderboard_filter], outputs=[vote_status, leaderboard_html, project_dropdown])
283
 
284
+ leaderboard_html.select(fn=handle_leaderboard_click, outputs=[edit_group, edit_old_url, edit_new_url, edit_new_video_url])
 
 
 
 
285
 
286
+ 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])
287
+ cancel_edit_button.click(fn=cancel_edit, outputs=[edit_group, edit_old_url, edit_new_url, edit_new_video_url])
 
 
 
288
 
289
+ app.launch()
requirements.txt CHANGED
@@ -1,4 +1,5 @@
1
- #gradio==5.50.0
2
- #gradio[oauth]==5.50.0
 
3
  pandas
4
  python-dotenv
 
1
+ gradio==5.50.0
2
+ gradio[oauth]==5.50.0
3
+ gradio_htmlplus
4
  pandas
5
  python-dotenv