Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -21,8 +21,7 @@ if not os.path.exists(DATA_FILE) or os.path.getsize(DATA_FILE) == 0:
|
|
21 |
# Country list
|
22 |
ALL_COUNTRIES = sorted(list(set([
|
23 |
"United States of America", "United Kingdom", "India", "France", "Germany", "Canada", "Australia", "Japan", "Brazil", "Mexico",
|
24 |
-
|
25 |
-
] + [ # Fallback: full list
|
26 |
"Afghanistan", "Albania", "Algeria", "Andorra", "Angola", "Argentina", "Armenia", "Austria", "Azerbaijan",
|
27 |
"Bangladesh", "Belgium", "Bhutan", "Bolivia", "Bosnia and Herzegovina", "Botswana", "Bulgaria", "Cambodia",
|
28 |
"Chile", "China", "Colombia", "Croatia", "Cuba", "Czech Republic", "Denmark", "Dominican Republic", "Ecuador",
|
@@ -35,20 +34,23 @@ ALL_COUNTRIES = sorted(list(set([
|
|
35 |
])))
|
36 |
|
37 |
# Backup
|
|
|
38 |
def backup_data():
|
39 |
os.makedirs(BACKUP_DIR, exist_ok=True)
|
40 |
timestamp = datetime.datetime.now().strftime('%Y%m%d_%H%M%S')
|
41 |
backup_file = os.path.join(BACKUP_DIR, f'teamup_data_backup_{timestamp}.json')
|
42 |
shutil.copy(DATA_FILE, backup_file)
|
43 |
|
44 |
-
#
|
|
|
45 |
def atomic_save(data, path):
|
46 |
tmp_path = path + ".tmp"
|
47 |
with open(tmp_path, "w") as f:
|
48 |
json.dump(data, f, indent=2)
|
49 |
os.replace(tmp_path, path)
|
50 |
|
51 |
-
#
|
|
|
52 |
def submit_profile(name, discord, city, country, address, looking, onlinecheck, languages, laptop, robot, skills, describe3, experience, idea):
|
53 |
if not discord or not city or not country or not laptop or not robot:
|
54 |
return "β Please fill in all required fields."
|
@@ -56,7 +58,6 @@ def submit_profile(name, discord, city, country, address, looking, onlinecheck,
|
|
56 |
if not languages or not isinstance(languages, list):
|
57 |
return "β Please select at least one language."
|
58 |
|
59 |
-
# Normalize input
|
60 |
city = city.strip().title()
|
61 |
country = country.strip().title()
|
62 |
discord = discord.strip()
|
@@ -107,7 +108,8 @@ def submit_profile(name, discord, city, country, address, looking, onlinecheck,
|
|
107 |
except Exception as e:
|
108 |
return f"β Failed to save: {e}"
|
109 |
|
110 |
-
#
|
|
|
111 |
def update_city_filter(country):
|
112 |
with open(DATA_FILE, "r") as f:
|
113 |
data = json.load(f)
|
@@ -119,7 +121,28 @@ def update_city_filter(country):
|
|
119 |
cities = sorted(set(df["City"].dropna().unique()))
|
120 |
return gr.update(choices=["All"] + cities, value="All")
|
121 |
|
122 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
123 |
def filter_by_fields(selected_country, selected_city, selected_language):
|
124 |
with open(DATA_FILE, "r") as f:
|
125 |
data = json.load(f)
|
@@ -129,73 +152,36 @@ def filter_by_fields(selected_country, selected_city, selected_language):
|
|
129 |
|
130 |
df = pd.DataFrame(data)
|
131 |
|
132 |
-
# Normalize Country & City
|
133 |
df["Country"] = df["Country"].astype(str).str.strip().str.title()
|
134 |
df["City"] = df["City"].astype(str).str.strip().str.title()
|
|
|
135 |
|
136 |
-
# Normalize Languages: always string, lowercase, comma-separated
|
137 |
-
df["Languages"] = df["Languages"].apply(
|
138 |
-
lambda x: ", ".join(x) if isinstance(x, list) else str(x)
|
139 |
-
).str.strip().str.lower()
|
140 |
-
|
141 |
-
# Normalize filters
|
142 |
if selected_country != "All":
|
143 |
-
|
144 |
-
df = df[df["Country"] == selected_country]
|
145 |
-
|
146 |
if selected_city != "All":
|
147 |
-
|
148 |
-
df = df[df["City"] == selected_city]
|
149 |
-
|
150 |
if selected_language != "All":
|
151 |
-
|
152 |
-
df = df[df["Languages"].str.contains(selected_language, na=False)]
|
153 |
|
154 |
if df.empty:
|
155 |
return "<p>No participants match your filters.</p>"
|
156 |
|
157 |
-
# Hide address if present
|
158 |
if "Address" in df.columns:
|
159 |
df = df.drop(columns=["Address"])
|
160 |
|
161 |
-
|
162 |
-
"Discord": "Discord",
|
163 |
-
"Name": "Name",
|
164 |
-
"City": "City",
|
165 |
-
"Country": "Country",
|
166 |
-
"Looking for Team": "Looking for Team",
|
167 |
-
"Onlinecheck": "How?",
|
168 |
-
"Languages": "Languages",
|
169 |
-
"Laptop": "Laptop",
|
170 |
-
"Robot": "Robot",
|
171 |
-
"Skills": "Skills",
|
172 |
-
"Describe3": "Description",
|
173 |
-
"Experience": "Experience",
|
174 |
-
"Project Idea": "Project Idea"
|
175 |
-
}
|
176 |
-
|
177 |
-
html = '<h3 style="margin-top:20px;">π Find your matching Teammates & Register your team <a href="https://forms.gle/gJEMGD4CEA2emhD18" target="_blank">here</a></h3>'
|
178 |
html += "<table style='width:100%; border-collapse: collapse;'>"
|
179 |
-
|
180 |
-
html += "<tr>"
|
181 |
-
for col in df.columns:
|
182 |
-
html += f"<th style='border: 1px solid #ccc; padding: 6px;'>{display_names.get(col, col)}</th>"
|
183 |
-
html += "</tr>"
|
184 |
-
|
185 |
for _, row in df.iterrows():
|
186 |
-
html += "<tr>"
|
187 |
-
|
188 |
-
val =
|
189 |
-
|
190 |
-
val = f"<a href='https://discord.com/users/{val}' target='_blank'>{val}</a>"
|
191 |
-
html += f"<td style='border: 1px solid #eee; padding: 6px;'>{val}</td>"
|
192 |
-
html += "</tr>"
|
193 |
-
|
194 |
html += "</table>"
|
195 |
return html
|
196 |
|
|
|
197 |
|
198 |
-
# Delete discord
|
199 |
def delete_by_discord(discord, code):
|
200 |
if code != ADMIN_CODE:
|
201 |
return "β Invalid admin code."
|
@@ -206,11 +192,21 @@ def delete_by_discord(discord, code):
|
|
206 |
json.dump(new_data, f, indent=2)
|
207 |
return f"ποΈ Deleted user with Discord: {discord}"
|
208 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
209 |
|
210 |
-
#
|
211 |
with gr.Blocks(css=".gr-dropdown { max-height: 100px; overflow-y: auto; font-size: 12px; }") as demo:
|
212 |
gr.Markdown("# π LeRobot Worldwide Hackathon - Team-Up Dashboard")
|
213 |
-
gr.Markdown("1. Submit or update your profile to find matching teammates and contact them on Discord. (Required fields marked with *.) ")
|
214 |
|
215 |
with gr.Row():
|
216 |
with gr.Column():
|
@@ -232,88 +228,47 @@ with gr.Blocks(css=".gr-dropdown { max-height: 100px; overflow-y: auto; font-siz
|
|
232 |
status = gr.Textbox(label="", interactive=False)
|
233 |
|
234 |
with gr.Column():
|
235 |
-
gr.Markdown("π― 2. Choose your preferences to find your teammates
|
236 |
country_filter = gr.Dropdown(label="Filter by Country", choices=["All"] + ALL_COUNTRIES, value="All")
|
237 |
city_filter = gr.Dropdown(label="Filter by City", choices=["All"], value="All")
|
238 |
language_filter = gr.Dropdown(label="Filter by Language", choices=["All", "English", "French", "Spanish", "German", "Portuguese", "Chinese", "Arabic", "Hindi"], value="All")
|
239 |
table_html = gr.HTML(label="Matching Participants")
|
240 |
|
241 |
-
|
242 |
-
def update_dropdown_choices():
|
243 |
-
with open(DATA_FILE, "r") as f:
|
244 |
-
data = json.load(f)
|
245 |
-
df = pd.DataFrame(data)
|
246 |
-
|
247 |
-
country_choices = sorted(df["Country"].dropna().unique()) if "Country" in df else []
|
248 |
-
city_choices = sorted(df["City"].dropna().unique()) if "City" in df else []
|
249 |
-
language_set = set()
|
250 |
-
if "Languages" in df:
|
251 |
-
for lang_list in df["Languages"].dropna():
|
252 |
-
if isinstance(lang_list, list):
|
253 |
-
language_set.update(lang_list)
|
254 |
-
elif isinstance(lang_list, str):
|
255 |
-
language_set.update(lang_list.split(", "))
|
256 |
-
return (
|
257 |
-
gr.update(choices=["All"] + list(country_choices), value="All"),
|
258 |
-
gr.update(choices=["All"] + list(city_choices), value="All"),
|
259 |
-
gr.update(choices=["All"] + sorted(language_set), value="All")
|
260 |
-
)
|
261 |
-
|
262 |
-
with demo:
|
263 |
-
demo.load(
|
264 |
-
fn=lambda: filter_by_fields("All", "All", "All"),
|
265 |
-
inputs=[],
|
266 |
-
outputs=[table_html]
|
267 |
-
)
|
268 |
demo.load(fn=update_dropdown_choices, outputs=[country_filter, city_filter, language_filter])
|
269 |
|
270 |
-
|
271 |
country_filter.change(fn=update_city_filter, inputs=[country_filter], outputs=[city_filter])
|
272 |
country_filter.change(fn=filter_by_fields, inputs=[country_filter, city_filter, language_filter], outputs=[table_html])
|
273 |
city_filter.change(fn=filter_by_fields, inputs=[country_filter, city_filter, language_filter], outputs=[table_html])
|
274 |
language_filter.change(fn=filter_by_fields, inputs=[country_filter, city_filter, language_filter], outputs=[table_html])
|
275 |
-
|
276 |
-
submit_btn.click(
|
277 |
-
fn=submit_profile,
|
278 |
-
inputs=[name, discord, city, country, address, looking, onlinecheck, languages, laptop, robot, skills, describe3, experience, idea],
|
279 |
-
outputs=[status]
|
280 |
-
).then(
|
281 |
-
fn=update_dropdown_choices,
|
282 |
-
inputs=[],
|
283 |
-
outputs=[country_filter, city_filter, language_filter]
|
284 |
-
).then(
|
285 |
-
fn=filter_by_fields,
|
286 |
-
inputs=[country_filter, city_filter, language_filter],
|
287 |
-
outputs=[table_html]
|
288 |
-
)
|
289 |
-
|
290 |
-
|
291 |
-
def download_csv(code):
|
292 |
-
if code != ADMIN_CODE:
|
293 |
-
raise gr.Error("β Invalid admin code.")
|
294 |
-
with open(DATA_FILE, "r") as f:
|
295 |
-
data = json.load(f)
|
296 |
-
df = pd.DataFrame(data)
|
297 |
-
csv_path = os.path.join("data", "teamup_export.csv")
|
298 |
-
df.to_csv(csv_path, index=False)
|
299 |
-
return csv_path
|
300 |
-
|
301 |
-
|
302 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
303 |
|
304 |
gr.Markdown("---\n### π‘οΈ Admin Panel (delete by Discord)")
|
305 |
admin_discord = gr.Text(label="Discord Username")
|
306 |
admin_code = gr.Text(label="Admin Code", type="password")
|
307 |
del_btn = gr.Button("Delete Profile")
|
308 |
del_status = gr.Textbox(label="Status", interactive=False)
|
309 |
-
del_btn.click(delete_by_discord, inputs=[admin_discord, admin_code], outputs=del_status)
|
310 |
-
|
311 |
-
|
312 |
-
gr.Markdown("---\n### π₯ Admin Export CSV")
|
313 |
export_code = gr.Text(label="Admin Code", type="password")
|
314 |
download_btn = gr.Button("Generate and Download CSV")
|
315 |
download_file = gr.File(label="CSV Export", interactive=False)
|
316 |
download_btn.click(fn=download_csv, inputs=[export_code], outputs=[download_file])
|
317 |
|
318 |
-
|
|
|
319 |
|
|
|
21 |
# Country list
|
22 |
ALL_COUNTRIES = sorted(list(set([
|
23 |
"United States of America", "United Kingdom", "India", "France", "Germany", "Canada", "Australia", "Japan", "Brazil", "Mexico",
|
24 |
+
] + [ # Full list
|
|
|
25 |
"Afghanistan", "Albania", "Algeria", "Andorra", "Angola", "Argentina", "Armenia", "Austria", "Azerbaijan",
|
26 |
"Bangladesh", "Belgium", "Bhutan", "Bolivia", "Bosnia and Herzegovina", "Botswana", "Bulgaria", "Cambodia",
|
27 |
"Chile", "China", "Colombia", "Croatia", "Cuba", "Czech Republic", "Denmark", "Dominican Republic", "Ecuador",
|
|
|
34 |
])))
|
35 |
|
36 |
# Backup
|
37 |
+
|
38 |
def backup_data():
|
39 |
os.makedirs(BACKUP_DIR, exist_ok=True)
|
40 |
timestamp = datetime.datetime.now().strftime('%Y%m%d_%H%M%S')
|
41 |
backup_file = os.path.join(BACKUP_DIR, f'teamup_data_backup_{timestamp}.json')
|
42 |
shutil.copy(DATA_FILE, backup_file)
|
43 |
|
44 |
+
# Save data safely
|
45 |
+
|
46 |
def atomic_save(data, path):
|
47 |
tmp_path = path + ".tmp"
|
48 |
with open(tmp_path, "w") as f:
|
49 |
json.dump(data, f, indent=2)
|
50 |
os.replace(tmp_path, path)
|
51 |
|
52 |
+
# Submit profile
|
53 |
+
|
54 |
def submit_profile(name, discord, city, country, address, looking, onlinecheck, languages, laptop, robot, skills, describe3, experience, idea):
|
55 |
if not discord or not city or not country or not laptop or not robot:
|
56 |
return "β Please fill in all required fields."
|
|
|
58 |
if not languages or not isinstance(languages, list):
|
59 |
return "β Please select at least one language."
|
60 |
|
|
|
61 |
city = city.strip().title()
|
62 |
country = country.strip().title()
|
63 |
discord = discord.strip()
|
|
|
108 |
except Exception as e:
|
109 |
return f"β Failed to save: {e}"
|
110 |
|
111 |
+
# Filter dropdowns
|
112 |
+
|
113 |
def update_city_filter(country):
|
114 |
with open(DATA_FILE, "r") as f:
|
115 |
data = json.load(f)
|
|
|
121 |
cities = sorted(set(df["City"].dropna().unique()))
|
122 |
return gr.update(choices=["All"] + cities, value="All")
|
123 |
|
124 |
+
def update_dropdown_choices():
|
125 |
+
with open(DATA_FILE, "r") as f:
|
126 |
+
data = json.load(f)
|
127 |
+
df = pd.DataFrame(data)
|
128 |
+
|
129 |
+
country_choices = sorted(df["Country"].dropna().unique()) if "Country" in df else []
|
130 |
+
city_choices = sorted(df["City"].dropna().unique()) if "City" in df else []
|
131 |
+
language_set = set()
|
132 |
+
if "Languages" in df:
|
133 |
+
for lang_list in df["Languages"].dropna():
|
134 |
+
if isinstance(lang_list, list):
|
135 |
+
language_set.update(lang_list)
|
136 |
+
elif isinstance(lang_list, str):
|
137 |
+
language_set.update(lang_list.split(", "))
|
138 |
+
return (
|
139 |
+
gr.update(choices=["All"] + list(country_choices), value="All"),
|
140 |
+
gr.update(choices=["All"] + list(city_choices), value="All"),
|
141 |
+
gr.update(choices=["All"] + sorted(language_set), value="All")
|
142 |
+
)
|
143 |
+
|
144 |
+
# Filter table
|
145 |
+
|
146 |
def filter_by_fields(selected_country, selected_city, selected_language):
|
147 |
with open(DATA_FILE, "r") as f:
|
148 |
data = json.load(f)
|
|
|
152 |
|
153 |
df = pd.DataFrame(data)
|
154 |
|
|
|
155 |
df["Country"] = df["Country"].astype(str).str.strip().str.title()
|
156 |
df["City"] = df["City"].astype(str).str.strip().str.title()
|
157 |
+
df["Languages"] = df["Languages"].apply(lambda x: ", ".join(x) if isinstance(x, list) else str(x)).str.strip().str.lower()
|
158 |
|
|
|
|
|
|
|
|
|
|
|
|
|
159 |
if selected_country != "All":
|
160 |
+
df = df[df["Country"] == selected_country.strip().title()]
|
|
|
|
|
161 |
if selected_city != "All":
|
162 |
+
df = df[df["City"] == selected_city.strip().title()]
|
|
|
|
|
163 |
if selected_language != "All":
|
164 |
+
df = df[df["Languages"].str.contains(selected_language.strip().lower(), na=False)]
|
|
|
165 |
|
166 |
if df.empty:
|
167 |
return "<p>No participants match your filters.</p>"
|
168 |
|
|
|
169 |
if "Address" in df.columns:
|
170 |
df = df.drop(columns=["Address"])
|
171 |
|
172 |
+
html = '<h3 style="margin-top:20px;">\U0001f50d Find your matching Teammates & Register your team <a href="https://forms.gle/gJEMGD4CEA2emhD18" target="_blank">here</a></h3>'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
173 |
html += "<table style='width:100%; border-collapse: collapse;'>"
|
174 |
+
html += "<tr>" + "".join(f"<th style='border: 1px solid #ccc; padding: 6px;'>{col}</th>" for col in df.columns) + "</tr>"
|
|
|
|
|
|
|
|
|
|
|
175 |
for _, row in df.iterrows():
|
176 |
+
html += "<tr>" + "".join(
|
177 |
+
f"<td style='border: 1px solid #eee; padding: 6px;'>" +
|
178 |
+
(f"<a href='https://discord.com/users/{val}' target='_blank'>{val}</a>" if col == "Discord" else str(val)) +
|
179 |
+
"</td>" for col, val in row.items()) + "</tr>"
|
|
|
|
|
|
|
|
|
180 |
html += "</table>"
|
181 |
return html
|
182 |
|
183 |
+
# Delete
|
184 |
|
|
|
185 |
def delete_by_discord(discord, code):
|
186 |
if code != ADMIN_CODE:
|
187 |
return "β Invalid admin code."
|
|
|
192 |
json.dump(new_data, f, indent=2)
|
193 |
return f"ποΈ Deleted user with Discord: {discord}"
|
194 |
|
195 |
+
# CSV export
|
196 |
+
|
197 |
+
def download_csv(code):
|
198 |
+
if code != ADMIN_CODE:
|
199 |
+
raise gr.Error("β Invalid admin code.")
|
200 |
+
with open(DATA_FILE, "r") as f:
|
201 |
+
data = json.load(f)
|
202 |
+
df = pd.DataFrame(data)
|
203 |
+
csv_path = os.path.join("data", "teamup_export.csv")
|
204 |
+
df.to_csv(csv_path, index=False)
|
205 |
+
return csv_path
|
206 |
|
207 |
+
# Interface
|
208 |
with gr.Blocks(css=".gr-dropdown { max-height: 100px; overflow-y: auto; font-size: 12px; }") as demo:
|
209 |
gr.Markdown("# π LeRobot Worldwide Hackathon - Team-Up Dashboard")
|
|
|
210 |
|
211 |
with gr.Row():
|
212 |
with gr.Column():
|
|
|
228 |
status = gr.Textbox(label="", interactive=False)
|
229 |
|
230 |
with gr.Column():
|
231 |
+
gr.Markdown("\n\nπ― 2. Choose your preferences to find your teammates")
|
232 |
country_filter = gr.Dropdown(label="Filter by Country", choices=["All"] + ALL_COUNTRIES, value="All")
|
233 |
city_filter = gr.Dropdown(label="Filter by City", choices=["All"], value="All")
|
234 |
language_filter = gr.Dropdown(label="Filter by Language", choices=["All", "English", "French", "Spanish", "German", "Portuguese", "Chinese", "Arabic", "Hindi"], value="All")
|
235 |
table_html = gr.HTML(label="Matching Participants")
|
236 |
|
237 |
+
demo.load(fn=filter_by_fields, inputs=[country_filter, city_filter, language_filter], outputs=[table_html])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
238 |
demo.load(fn=update_dropdown_choices, outputs=[country_filter, city_filter, language_filter])
|
239 |
|
|
|
240 |
country_filter.change(fn=update_city_filter, inputs=[country_filter], outputs=[city_filter])
|
241 |
country_filter.change(fn=filter_by_fields, inputs=[country_filter, city_filter, language_filter], outputs=[table_html])
|
242 |
city_filter.change(fn=filter_by_fields, inputs=[country_filter, city_filter, language_filter], outputs=[table_html])
|
243 |
language_filter.change(fn=filter_by_fields, inputs=[country_filter, city_filter, language_filter], outputs=[table_html])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
244 |
|
245 |
+
submit_btn.click(
|
246 |
+
fn=submit_profile,
|
247 |
+
inputs=[name, discord, city, country, address, looking, onlinecheck, languages, laptop, robot, skills, describe3, experience, idea],
|
248 |
+
outputs=[status]
|
249 |
+
).then(
|
250 |
+
fn=update_dropdown_choices,
|
251 |
+
inputs=[],
|
252 |
+
outputs=[country_filter, city_filter, language_filter]
|
253 |
+
).then(
|
254 |
+
fn=filter_by_fields,
|
255 |
+
inputs=[country_filter, city_filter, language_filter],
|
256 |
+
outputs=[table_html]
|
257 |
+
)
|
258 |
|
259 |
gr.Markdown("---\n### π‘οΈ Admin Panel (delete by Discord)")
|
260 |
admin_discord = gr.Text(label="Discord Username")
|
261 |
admin_code = gr.Text(label="Admin Code", type="password")
|
262 |
del_btn = gr.Button("Delete Profile")
|
263 |
del_status = gr.Textbox(label="Status", interactive=False)
|
264 |
+
del_btn.click(delete_by_discord, inputs=[admin_discord, admin_code], outputs=[del_status])
|
265 |
+
|
266 |
+
gr.Markdown("---\n### π
Admin Export CSV")
|
|
|
267 |
export_code = gr.Text(label="Admin Code", type="password")
|
268 |
download_btn = gr.Button("Generate and Download CSV")
|
269 |
download_file = gr.File(label="CSV Export", interactive=False)
|
270 |
download_btn.click(fn=download_csv, inputs=[export_code], outputs=[download_file])
|
271 |
|
272 |
+
if __name__ == "__main__":
|
273 |
+
demo.launch()
|
274 |
|