Files changed (1) hide show
  1. app.py +793 -299
app.py CHANGED
@@ -1,398 +1,892 @@
1
- # app.py β€” Clean, fixed, single-file Gradio app for Synchrony
2
- # Last updated: 2025-11-06
3
- # Author: (rewritten for Lujain)
4
- # -------------------------------------------------------
5
-
6
  import gradio as gr
7
  import requests
 
8
  from datetime import datetime
9
- from typing import Tuple, Any, Dict, List
10
 
11
  # ============================================
12
- # Configuration / Webhook URLs (update if needed)
 
13
  # ============================================
 
 
14
  BASE_URL = "https://maryyam.app.n8n.cloud/webhook-test"
15
  START_SESSION_URL = f"{BASE_URL}/start-session"
16
  GENERATE_CHALLENGES_URL = f"{BASE_URL}/generate-challenges"
17
  REQUEST_HINT_URL = f"{BASE_URL}/request-hint"
18
 
19
- # Keep a lightweight in-memory "current session" (not persistent)
20
- current_session: Dict[str, Any] = {"session_id": None, "challenges": None}
21
-
 
 
 
 
 
 
 
 
 
 
22
 
23
  # ============================================
24
- # Helper / API functions
25
  # ============================================
26
- def safe_post(url: str, payload: dict, timeout: int = 30) -> Tuple[bool, Any]:
 
 
27
  try:
28
- r = requests.post(url, json=payload, headers={"Content-Type": "application/json"}, timeout=timeout)
29
- if r.status_code == 200:
30
- try:
31
- return True, r.json()
32
- except ValueError:
33
- return True, r.text
 
 
 
 
 
 
 
 
 
 
34
  else:
35
- return False, f"Error {r.status_code}: {r.text}"
 
 
36
  except Exception as e:
37
- return False, f"Connection Error: {str(e)}"
 
38
 
39
 
40
- def start_session(group_id: str) -> Tuple[str, str]:
41
- if not group_id:
42
- return "❌ Please enter a Group ID (e.g., G001).", ""
43
-
44
- ok, result = safe_post(START_SESSION_URL, {"group_id": group_id}, timeout=30)
45
- if not ok:
46
- return f"❌ {result}", ""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
47
 
48
- if isinstance(result, dict):
49
- session_id = result.get("session_id", "")
50
- current_session["session_id"] = session_id
51
- students = result.get("students", [])
52
- students_list = ", ".join([s.get("name", "Unknown") for s in students]) if students else "No students found"
53
 
54
- message = (
55
- "βœ… **Session Started!**\n\n"
56
- f"**πŸ†” Session ID:** `{session_id}`\n\n"
57
- f"**πŸ’¬ Synco says:** {result.get('synco_message', 'Welcome!')}\n\n"
58
- f"**πŸ‘₯ Students:** {students_list}\n\n"
59
- "🎯 Ready to generate challenges!"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60
  )
61
- return message, session_id
62
-
63
- return f"βœ… Session started (response):\n```\n{result}\n```", str(result)
64
-
65
-
66
- def generate_challenges(session_id: str) -> str:
67
- sid = session_id or current_session.get("session_id")
68
- if not sid:
69
- return "❌ Please provide a session ID (or start a session first)."
70
-
71
- ok, result = safe_post(GENERATE_CHALLENGES_URL, {"session_id": sid}, timeout=60)
72
- if not ok:
73
- return f"❌ {result}"
74
-
75
- if isinstance(result, dict):
76
- challenges = result.get("challenges", [])
77
- current_session["challenges"] = challenges
78
-
79
- output_lines: List[str] = []
80
- output_lines.append("βœ… **Challenges Generated!**\n")
81
- synco_msg = result.get("synco_message", "")
82
- if synco_msg:
83
- output_lines.append(f"**πŸ’¬ Synco says:** {synco_msg}\n")
84
- output_lines.append("---\n")
85
-
86
- if not challenges:
87
- output_lines.append("_No challenges returned from the API._")
88
  else:
89
- for i, ch in enumerate(challenges, start=1):
90
- ch_json = ch.get("json") if isinstance(ch, dict) else None
91
- description = ""
92
- topics = ""
93
- if isinstance(ch_json, dict):
94
- description = ch_json.get("description", "")
95
- topics = ch_json.get("topics_involved", "")
96
- else:
97
- description = str(ch)
98
-
99
- output_lines.append(f"### 🎯 Challenge {i}\n")
100
- output_lines.append(f"**Description:**\n{description}\n")
101
- if topics:
102
- output_lines.append(f"**Topics:** {topics}\n")
103
- output_lines.append("πŸ’‘ 3 hints available - request them when stuck!\n")
104
- output_lines.append("---\n")
105
-
106
- return "\n".join(output_lines)
107
-
108
- return f"βœ… Response:\n```\n{result}\n```"
109
-
110
 
111
- def request_hint(session_id: str, challenge_num: str, hint_level: str) -> str:
112
- sid = session_id or current_session.get("session_id")
113
- if not sid:
114
- return "❌ Please provide a session ID."
115
 
 
 
 
 
 
 
 
 
116
  try:
117
- cnum = int(challenge_num)
118
- hlvl = int(hint_level)
119
- except Exception:
120
- return "❌ Challenge number and hint level must be integers."
121
-
122
- ok, result = safe_post(REQUEST_HINT_URL, {
123
- "session_id": sid,
124
- "challenge_number": cnum,
125
- "hint_level": hlvl
126
- }, timeout=15)
127
- if not ok:
128
- return f"❌ {result}"
129
-
130
- if isinstance(result, dict):
131
- synco_msg = result.get("synco_message", "")
132
- hint_text = result.get("hint", "") or str(result.get("hint", ""))
133
- output = []
134
- if synco_msg:
135
- output.append(f"πŸ’‘ **{synco_msg}**\n")
136
- output.append(f"🎯 Challenge {cnum} | Hint Level {hlvl}\n")
137
- output.append("---\n")
138
- output.append(hint_text or "_No hint provided by the API._")
139
- return "\n\n".join(output)
140
-
141
- return f"πŸ’‘ Response:\n```\n{result}\n```"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
142
 
143
 
144
  # ============================================
145
- # Custom Light Theme CSS β€” Apple Inspired
146
  # ============================================
 
147
  custom_css = """
 
148
  .gradio-container {
149
- background: linear-gradient(135deg, #f5f5f7 0%, #ffffff 100%) !important;
150
- font-family: -apple-system, 'Segoe UI', 'Helvetica Neue', sans-serif !important;
151
- color: #1c1c1e !important;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
152
  }
153
 
154
- /* Tabs styling */
155
  .tab-nav button {
156
- background: rgba(0, 0, 0, 0.03) !important;
157
- border: 1px solid rgba(0, 0, 0, 0.1) !important;
158
- color: #007aff !important;
159
- font-weight: 600 !important;
 
 
 
160
  transition: all 0.3s ease !important;
 
 
 
161
  }
 
162
  .tab-nav button:hover {
163
- background: rgba(0, 122, 255, 0.1) !important;
164
- border-color: #007aff !important;
 
 
 
165
  }
 
166
  .tab-nav button.selected {
167
- background: #007aff !important;
168
- border-color: #007aff !important;
169
- color: white !important;
170
- box-shadow: 0 4px 10px rgba(0, 122, 255, 0.3) !important;
 
171
  }
172
 
173
  /* Input fields */
174
  input, textarea, select {
175
- background: rgba(242, 242, 247, 0.8) !important;
176
- border: 1px solid rgba(0,0,0,0.1) !important;
177
- color: #1c1c1e !important;
178
- border-radius: 12px !important;
179
- padding: 10px !important;
 
 
180
  }
 
181
  input:focus, textarea:focus, select:focus {
182
- border-color: #007aff !important;
183
- box-shadow: 0 0 10px rgba(0, 122, 255, 0.2) !important;
 
 
 
 
 
 
184
  }
185
 
186
  /* Buttons */
187
  button {
188
- background: #007aff !important;
189
  border: none !important;
190
- color: white !important;
191
- font-weight: 600 !important;
192
- border-radius: 12px !important;
193
- padding: 12px 24px !important;
 
 
 
194
  transition: all 0.3s ease !important;
195
- box-shadow: 0 4px 15px rgba(0, 122, 255, 0.3) !important;
 
196
  }
 
197
  button:hover {
198
- transform: translateY(-2px) !important;
199
- box-shadow: 0 6px 20px rgba(0, 122, 255, 0.4) !important;
 
 
 
 
 
 
 
 
 
200
  }
201
 
202
  /* Output boxes */
203
- .output-markdown {
204
- background: rgba(242, 242, 247, 0.8) !important;
205
- border: 1px solid rgba(0,0,0,0.1) !important;
206
- border-radius: 15px !important;
207
- padding: 20px !important;
208
- color: #1c1c1e !important;
 
 
209
  }
210
 
211
  /* Headers */
212
- h1, h2, h3 {
213
- background: linear-gradient(135deg, #007aff 0%, #5ac8fa 100%);
214
  -webkit-background-clip: text;
215
  -webkit-text-fill-color: transparent;
 
 
 
 
 
 
 
 
 
 
 
 
216
  font-weight: 800 !important;
 
 
 
 
217
  }
218
 
219
- /* Markdown code blocks */
220
- code {
221
- background: rgba(0, 122, 255, 0.1) !important;
222
- color: #007aff !important;
223
- padding: 2px 6px !important;
224
- border-radius: 5px !important;
 
 
 
 
 
 
225
  }
226
 
227
  /* Links */
228
  a {
229
- color: #007aff !important;
230
  text-decoration: none !important;
231
- font-weight: 600 !important;
 
232
  }
 
233
  a:hover {
234
- color: #5ac8fa !important;
235
- text-decoration: underline !important;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
236
  }
237
 
238
  /* Scrollbar */
239
  ::-webkit-scrollbar {
240
- width: 10px;
 
241
  }
 
242
  ::-webkit-scrollbar-track {
243
- background: rgba(242, 242, 247, 0.8);
 
244
  }
 
245
  ::-webkit-scrollbar-thumb {
246
- background: #007aff;
247
  border-radius: 10px;
 
248
  }
 
249
  ::-webkit-scrollbar-thumb:hover {
250
- background: #5ac8fa;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
251
  }
252
- """
253
 
 
 
 
 
 
 
 
 
 
254
 
255
  # ============================================
256
- # Gradio UI
257
  # ============================================
258
- with gr.Blocks(css=custom_css, title="🌟 Synchrony - AI Study Sessions", theme=gr.themes.Soft()) as app:
259
- # Header
260
- gr.Markdown(
261
- """
262
- # 🌟 SYNCHRONY
263
- ## AI-Powered Collaborative Learning Platform
264
- ### Where Students Teach Each Other πŸš€
265
- ---
266
- """
267
- )
268
-
269
- session_display = gr.Textbox(
270
- label="πŸ“ Current Session ID",
271
- placeholder="No active session",
272
- interactive=False,
273
- lines=1,
274
- value=""
275
- )
276
-
277
- # TAB 1: Start Session
278
- with gr.Tab("πŸš€ Start Session"):
279
- gr.Markdown("""## 🎯 Start a New Study Session
280
- Enter a Group ID to begin. Synco will welcome the students and prepare the session!
281
- *Example Group IDs:* G001, G002, G003""")
282
  with gr.Row():
283
- group_id_input = gr.Textbox(label="πŸ†” Group ID", placeholder="Enter Group ID (e.g., G001)", scale=3)
284
- start_btn = gr.Button("πŸš€ Start Session", scale=1, variant="primary")
285
- start_output = gr.Markdown(label="πŸ“’ Output")
286
- start_btn.click(fn=start_session, inputs=[group_id_input], outputs=[start_output, session_display])
287
-
288
- # TAB 2: Generate Challenges
289
- with gr.Tab("🎯 Generate Challenges"):
290
- gr.Markdown("""## 🧠 AI-Powered Challenge Generation
291
- Synco will create *3 collaborative challenges* tailored to your group's topics!
292
- Each challenge includes *3 progressive hints* for adaptive learning.""")
293
- session_id_challenges = gr.Textbox(label="πŸ“ Session ID", placeholder="Enter session ID from previous step")
294
- generate_btn = gr.Button("🎯 Generate Challenges", variant="primary")
295
- challenges_output = gr.Markdown(label="πŸ“‹ Challenges")
296
- generate_btn.click(fn=generate_challenges, inputs=[session_id_challenges], outputs=[challenges_output])
297
-
298
- # TAB 3: Request Hints
299
- with gr.Tab("πŸ’‘ Get Hints"):
300
- gr.Markdown("""## πŸ’‘ Adaptive Hint System
301
- Stuck on a challenge? Request progressive hints from Synco!
302
- *Hint 1:* Gentle nudge πŸ€”
303
- *Hint 2:* Clearer guidance πŸ’­
304
- *Hint 3:* Almost the answer 🎯""")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
305
  with gr.Row():
306
- session_id_hints = gr.Textbox(label="πŸ“ Session ID", placeholder="Enter session ID", scale=2)
307
- challenge_num = gr.Dropdown(label="🎯 Challenge Number", choices=["1", "2", "3"], value="1", scale=1)
308
- hint_level = gr.Dropdown(label="πŸ’‘ Hint Level", choices=["1", "2", "3"], value="1", scale=1)
309
- hint_btn = gr.Button("πŸ’‘ Get Hint", variant="primary")
310
- hint_output = gr.Markdown(label="πŸ” Hint")
311
- hint_btn.click(fn=request_hint, inputs=[session_id_hints, challenge_num, hint_level], outputs=[hint_output])
312
-
313
- # TAB 4: About
314
- with gr.Tab("β„Ή About"):
315
- gr.Markdown(
316
- """
317
- ## 🌟 About Synchrony
318
- *Synchrony* is an AI-powered collaborative learning platform that:
319
- ✨ Matches students with complementary learning styles
320
- 🀝 Creates study groups based on chemistry scores
321
- 🎯 Generates personalized collaborative challenges
322
- πŸ’‘ Provides adaptive hints for progressive learning
323
- πŸ† Tracks progress and awards achievements
324
- ---
325
- ### πŸ”— Quick Links
326
- - πŸ“ Student Registration Form (Add your Google Form link)
327
- - πŸ“Š View Student Database (Add your Google Sheet link)
328
- - πŸŽ“ View Groups (Add your Google Sheet link)
329
- ---
330
- ### πŸ›  Tech Stack
331
- - *Frontend:* Gradio (Python)
332
- - *Backend:* n8n Workflows
333
- - *AI:* Groq API (Llama 3.3 70B)
334
- - *Database:* Google Sheets
335
- - *Matching Algorithm:* Chemistry Score System
336
- ---
337
- ### πŸ‘₯ Created By
338
- *Maryam Shanabli* | 2025-11-06
339
- Powered by AI β€’ Built for Students β€’ Designed for Impact πŸš€
340
- """
341
  )
342
-
343
- # TAB 5: API Tester
344
- with gr.Tab("πŸ§ͺ API Tester"):
345
- gr.Markdown("## πŸ§ͺ Test Your API Endpoints")
346
- with gr.Accordion("πŸš€ Test Start Session", open=False):
347
- test_group = gr.Textbox(label="Group ID", value="G001")
348
- test_start_btn = gr.Button("Test Start Session")
349
- test_start_out = gr.JSON(label="Response")
350
- def test_start(gid):
351
- ok, res = safe_post(START_SESSION_URL, {"group_id": gid}, timeout=20)
352
- return res if ok else {"error": res}
353
- test_start_btn.click(test_start, inputs=[test_group], outputs=[test_start_out])
354
-
355
- with gr.Accordion("🎯 Test Generate Challenges", open=False):
356
- test_session = gr.Textbox(label="Session ID", value="S20251106160510")
357
- test_gen_btn = gr.Button("Test Generate Challenges")
358
- test_gen_out = gr.JSON(label="Response")
359
- def test_gen(sid):
360
- ok, res = safe_post(GENERATE_CHALLENGES_URL, {"session_id": sid}, timeout=60)
361
- return res if ok else {"error": res}
362
- test_gen_btn.click(test_gen, inputs=[test_session], outputs=[test_gen_out])
363
-
364
- with gr.Accordion("πŸ’‘ Test Request Hint", open=False):
365
- test_hint_session = gr.Textbox(label="Session ID", value="S20251106160510")
366
- test_hint_num = gr.Number(label="Challenge Number", value=1)
367
- test_hint_lvl = gr.Number(label="Hint Level", value=1)
368
- test_hint_btn = gr.Button("Test Request Hint")
369
- test_hint_out = gr.JSON(label="Response")
370
- def test_hint(sid, cnum, hlvl):
371
- try:
372
- ok, res = safe_post(REQUEST_HINT_URL, {
373
- "session_id": sid,
374
- "challenge_number": int(cnum),
375
- "hint_level": int(hlvl)
376
- }, timeout=20)
377
- return res if ok else {"error": res}
378
- except Exception as e:
379
- return {"error": str(e)}
380
- test_hint_btn.click(test_hint, inputs=[test_hint_session, test_hint_num, test_hint_lvl], outputs=[test_hint_out])
381
-
382
- # Footer
383
- gr.Markdown("""---
384
- ### 🌟 *Synchrony* | Collaborative Learning, Powered by AI
385
- Made with πŸ’œ by Maryam Shanabli | 2025
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
386
  """)
387
 
388
-
389
  # ============================================
390
- # Launch
391
  # ============================================
 
392
  if __name__ == "__main__":
 
 
 
 
 
 
393
  app.launch(
394
  server_name="0.0.0.0",
395
  server_port=7860,
396
- share=True,
397
- show_error=True
398
- )
 
 
 
 
 
 
 
 
1
  import gradio as gr
2
  import requests
3
+ import json
4
  from datetime import datetime
 
5
 
6
  # ============================================
7
+ # 🌟 SYNCHRONY - STUDENT PORTAL (GEN Z EDITION)
8
+ # Real Team Data | No Hardcoding | Pure Black Style
9
  # ============================================
10
+
11
+ # Your n8n webhook URLs (CORRECTED)
12
  BASE_URL = "https://maryyam.app.n8n.cloud/webhook-test"
13
  START_SESSION_URL = f"{BASE_URL}/start-session"
14
  GENERATE_CHALLENGES_URL = f"{BASE_URL}/generate-challenges"
15
  REQUEST_HINT_URL = f"{BASE_URL}/request-hint"
16
 
17
+ # Google Form and Sheets URLs
18
+ REGISTRATION_FORM = "https://docs.google.com/forms/d/e/1FAIpQLSfJFGx-yd0FPIuRYLUJut3BOOiQ14x_5DYheWpgrUqcHdQaCA/viewform"
19
+ GOOGLE_SHEETS = "https://docs.google.com/spreadsheets/d/1rpR-E_RSooDkNDh-Q_1BDIeGw4vfgmusNAfwPqATaW4/edit?usp=sharing"
20
+
21
+ # Current student session (stored in memory)
22
+ current_student = {
23
+ "name": None,
24
+ "email": None,
25
+ "group_id": None,
26
+ "session_id": None,
27
+ "team_members": [],
28
+ "challenges": []
29
+ }
30
 
31
  # ============================================
32
+ # πŸ”§ API FUNCTIONS (ALL REAL - NO HARDCODING)
33
  # ============================================
34
+
35
+ def start_session_and_get_team(group_id):
36
+ """Start session and get REAL team data from n8n"""
37
  try:
38
+ response = requests.post(
39
+ START_SESSION_URL,
40
+ json={"group_id": group_id},
41
+ headers={"Content-Type": "application/json"},
42
+ timeout=30
43
+ )
44
+
45
+ if response.status_code == 200:
46
+ data = response.json()
47
+
48
+ # Store real data from API
49
+ current_student["session_id"] = data.get("session_id")
50
+ current_student["group_id"] = group_id
51
+ current_student["team_members"] = data.get("students", [])
52
+
53
+ return data
54
  else:
55
+ print(f"Error starting session: {response.status_code}")
56
+ return None
57
+
58
  except Exception as e:
59
+ print(f"Exception starting session: {str(e)}")
60
+ return None
61
 
62
 
63
+ def lookup_student(email):
64
+ """Login with any email - assigns to G001 for demo, gets REAL team"""
65
+ try:
66
+ if not email or "@" not in email:
67
+ return False, "❌ please enter a valid email address!"
68
+
69
+ # Extract name from email (demo approach)
70
+ name_part = email.split("@")[0]
71
+ name = name_part.replace(".", " ").replace("_", " ").title()
72
+
73
+ # For demo: everyone joins G001 (in production: query Sheets for real group)
74
+ # Start session to get REAL team data
75
+ session_data = start_session_and_get_team("G001")
76
+
77
+ if session_data:
78
+ current_student["name"] = name
79
+ current_student["email"] = email
80
+
81
+ welcome_msg = f"βœ… **welcome back, {name}!** πŸŽ‰\n\n"
82
+ welcome_msg += f"πŸ†” **session id:** `{session_data.get('session_id')}`\n\n"
83
+ welcome_msg += f"πŸ’¬ **synco says:**\n{session_data.get('synco_message', '')}\n\n"
84
+ welcome_msg += f"πŸ‘₯ **your team:** {', '.join([s['name'] for s in session_data.get('students', [])])}\n\n"
85
+ welcome_msg += f"✨ head to **MY TEAM** tab to see everyone!"
86
+
87
+ return True, welcome_msg
88
+ else:
89
+ return False, "❌ could not connect to server. try again!"
90
+
91
+ except Exception as e:
92
+ return False, f"❌ error: {str(e)}"
93
 
 
 
 
 
 
94
 
95
+ def get_team_info():
96
+ """Display REAL team information from start-session API"""
97
+ if not current_student.get("team_members"):
98
+ # Try to load team if not loaded
99
+ if current_student.get("group_id"):
100
+ session_data = start_session_and_get_team(current_student["group_id"])
101
+ if not session_data:
102
+ return "⚠️ could not load team info. try logging in again!"
103
+ else:
104
+ return "⚠️ please login first in the **HOME** tab!"
105
+
106
+ output = f"## πŸ‘₯ your study squad - {current_student.get('group_id', '???')} πŸ”₯\n\n"
107
+ output += f"**session id:** `{current_student.get('session_id', 'not started')}`\n\n"
108
+ output += "---\n\n"
109
+
110
+ for i, member in enumerate(current_student["team_members"], 1):
111
+ # Check if this is the logged-in user (fuzzy match)
112
+ user_name = current_student.get("name", "").lower()
113
+ member_name = member.get("name", "").lower()
114
+ is_you = " **(YOU)** 🌟" if user_name in member_name or member_name in user_name else ""
115
+
116
+ output += f"### πŸŽ“ {member['name']}{is_you}\n"
117
+ output += f"**studying:** {member.get('topic', 'N/A')}\n\n"
118
+
119
+ output += "---\n\n"
120
+ output += f"πŸ’‘ *collaborate, learn together, level up!*"
121
+
122
+ return output
123
+
124
+
125
+ def load_challenges():
126
+ """Load REAL challenges from generate-challenges API"""
127
+ if not current_student.get("session_id"):
128
+ return "⚠️ no active session. please login first in the **HOME** tab!"
129
+
130
+ try:
131
+ response = requests.post(
132
+ GENERATE_CHALLENGES_URL,
133
+ json={"session_id": current_student["session_id"]},
134
+ headers={"Content-Type": "application/json"},
135
+ timeout=60 # AI generation can take time
136
  )
137
+
138
+ if response.status_code == 200:
139
+ data = response.json()
140
+ current_student["challenges"] = data.get("challenges", [])
141
+
142
+ output = f"# 🎯 your collaborative challenges\n\n"
143
+ output += f"πŸ’¬ **synco says:**\n{data.get('synco_message', '')}\n\n"
144
+ output += "---\n\n"
145
+
146
+ for i, challenge in enumerate(current_student["challenges"], 1):
147
+ chal = challenge.get('json', challenge)
148
+ output += f"## 🎯 challenge {i}\n\n"
149
+ output += f"{chal.get('description', 'no description')}\n\n"
150
+ output += f"**πŸ“š topics:** {chal.get('topics_involved', 'N/A')}\n\n"
151
+ output += f"πŸ’‘ *stuck? request a hint in the **HINTS** tab!*\n\n"
152
+ output += "---\n\n"
153
+
154
+ return output
 
 
 
 
 
 
 
 
 
155
  else:
156
+ return f"❌ error loading challenges: {response.status_code}\n\ntry refreshing or check your session id!"
157
+
158
+ except Exception as e:
159
+ return f"❌ connection error: {str(e)}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
160
 
 
 
 
 
161
 
162
+ def request_hint(challenge_num, hint_level_text):
163
+ """Request REAL hint from request-hint API"""
164
+ if not current_student.get("session_id"):
165
+ return "⚠️ no active session. please login first!"
166
+
167
+ # Extract hint level number from text (e.g., "1 - gentle nudge πŸ€”" -> "1")
168
+ hint_level = hint_level_text.split(" - ")[0].strip()
169
+
170
  try:
171
+ response = requests.post(
172
+ REQUEST_HINT_URL,
173
+ json={
174
+ "session_id": current_student["session_id"],
175
+ "challenge_number": int(challenge_num),
176
+ "hint_level": int(hint_level)
177
+ },
178
+ headers={"Content-Type": "application/json"},
179
+ timeout=10
180
+ )
181
+
182
+ if response.status_code == 200:
183
+ data = response.json()
184
+
185
+ output = f"## {data.get('synco_message', '')}\n\n"
186
+ output += f"🎯 **challenge {challenge_num}** | **hint level {hint_level}**\n\n"
187
+ output += "---\n\n"
188
+ output += f"{data.get('hint', '')}\n\n"
189
+ output += "---\n\n"
190
+ output += f"πŸ’ͺ *you got this! keep going!*"
191
+
192
+ return output
193
+ else:
194
+ return f"❌ error: {response.status_code}\n\nmake sure you've loaded challenges first!"
195
+
196
+ except Exception as e:
197
+ return f"❌ connection error: {str(e)}"
198
+
199
+
200
+ def send_message(message, history):
201
+ """Send a chat message (SIMULATED for demo - would need real-time backend)"""
202
+ if not message.strip():
203
+ return history, ""
204
+
205
+ # Add user message
206
+ history.append({
207
+ "role": "user",
208
+ "content": f"**{current_student.get('name', 'you')}:** {message}"
209
+ })
210
+
211
+ # Simulate team member responses (FAKE - for demo only)
212
+ import random
213
+
214
+ if not current_student.get("team_members"):
215
+ response = "**synco (ai):** please login first to chat with your team! πŸ’¬"
216
+ else:
217
+ # Get random team member
218
+ other_members = [m for m in current_student["team_members"]
219
+ if m['name'].lower() != current_student.get('name', '').lower()]
220
+
221
+ if other_members:
222
+ member = random.choice(other_members)
223
+ responses = [
224
+ f"**{member['name']}:** that's a great point! let me add to that...",
225
+ f"**{member['name']}:** interesting! in {member['topic']}, we do it differently...",
226
+ f"**{member['name']}:** i agree! should we try that approach?",
227
+ f"**{member['name']}:** wait, can you explain that part again?",
228
+ f"**synco (ai):** great discussion everyone! πŸ’‘ keep collaborating!"
229
+ ]
230
+ response = random.choice(responses)
231
+ else:
232
+ response = "**synco (ai):** great question! your teammates will respond soon! πŸ’¬"
233
+
234
+ history.append({
235
+ "role": "assistant",
236
+ "content": response
237
+ })
238
+
239
+ return history, ""
240
 
241
 
242
  # ============================================
243
+ # 🎨 CUSTOM CSS - PURE BLACK GEN Z STYLE
244
  # ============================================
245
+
246
  custom_css = """
247
+ /* Pure Black Gen Z Background */
248
  .gradio-container {
249
+ background: #000000 !important;
250
+ font-family: 'Inter', 'SF Pro Display', 'Segoe UI', sans-serif !important;
251
+ color: #ffffff !important;
252
+ }
253
+
254
+ /* Neon accent colors */
255
+ :root {
256
+ --neon-pink: #ff0080;
257
+ --neon-cyan: #00ffff;
258
+ --neon-purple: #a855f7;
259
+ --neon-green: #00ff88;
260
+ }
261
+
262
+ /* Main container */
263
+ .contain {
264
+ background: #000000 !important;
265
+ max-width: 1400px !important;
266
+ margin: 0 auto !important;
267
+ }
268
+
269
+ /* Tabs */
270
+ .tab-nav {
271
+ background: #0a0a0a !important;
272
+ border-bottom: 2px solid var(--neon-pink) !important;
273
+ padding: 10px 0 !important;
274
  }
275
 
 
276
  .tab-nav button {
277
+ background: #1a1a1a !important;
278
+ border: 2px solid #222222 !important;
279
+ color: var(--neon-cyan) !important;
280
+ font-weight: 800 !important;
281
+ font-size: 15px !important;
282
+ text-transform: uppercase !important;
283
+ letter-spacing: 1.5px !important;
284
  transition: all 0.3s ease !important;
285
+ margin: 0 5px !important;
286
+ padding: 12px 20px !important;
287
+ border-radius: 8px !important;
288
  }
289
+
290
  .tab-nav button:hover {
291
+ background: #252525 !important;
292
+ border-color: var(--neon-pink) !important;
293
+ color: var(--neon-pink) !important;
294
+ transform: translateY(-2px) !important;
295
+ box-shadow: 0 5px 20px rgba(255, 0, 128, 0.4) !important;
296
  }
297
+
298
  .tab-nav button.selected {
299
+ background: linear-gradient(135deg, var(--neon-pink) 0%, var(--neon-purple) 100%) !important;
300
+ border-color: var(--neon-pink) !important;
301
+ color: #000000 !important;
302
+ font-weight: 900 !important;
303
+ box-shadow: 0 0 30px rgba(255, 0, 128, 0.8) !important;
304
  }
305
 
306
  /* Input fields */
307
  input, textarea, select {
308
+ background: #1a1a1a !important;
309
+ border: 2px solid #333333 !important;
310
+ color: #ffffff !important;
311
+ border-radius: 10px !important;
312
+ font-size: 16px !important;
313
+ padding: 14px !important;
314
+ transition: all 0.3s ease !important;
315
  }
316
+
317
  input:focus, textarea:focus, select:focus {
318
+ border-color: var(--neon-cyan) !important;
319
+ box-shadow: 0 0 25px rgba(0, 255, 255, 0.5) !important;
320
+ outline: none !important;
321
+ background: #222222 !important;
322
+ }
323
+
324
+ input::placeholder, textarea::placeholder {
325
+ color: #666666 !important;
326
  }
327
 
328
  /* Buttons */
329
  button {
330
+ background: linear-gradient(135deg, var(--neon-pink) 0%, var(--neon-purple) 100%) !important;
331
  border: none !important;
332
+ color: #000000 !important;
333
+ font-weight: 900 !important;
334
+ font-size: 16px !important;
335
+ text-transform: uppercase !important;
336
+ letter-spacing: 2px !important;
337
+ border-radius: 10px !important;
338
+ padding: 16px 32px !important;
339
  transition: all 0.3s ease !important;
340
+ box-shadow: 0 5px 25px rgba(255, 0, 128, 0.4) !important;
341
+ cursor: pointer !important;
342
  }
343
+
344
  button:hover {
345
+ transform: translateY(-4px) scale(1.03) !important;
346
+ box-shadow: 0 10px 40px rgba(255, 0, 128, 0.7) !important;
347
+ }
348
+
349
+ button:active {
350
+ transform: translateY(-1px) scale(0.98) !important;
351
+ }
352
+
353
+ /* Secondary buttons */
354
+ button.secondary {
355
+ background: linear-gradient(135deg, var(--neon-cyan) 0%, var(--neon-green) 100%) !important;
356
  }
357
 
358
  /* Output boxes */
359
+ .output-markdown, .prose, .markdown {
360
+ background: #0a0a0a !important;
361
+ border: 2px solid #1a1a1a !important;
362
+ border-radius: 12px !important;
363
+ padding: 24px !important;
364
+ color: #e0e0e0 !important;
365
+ box-shadow: inset 0 0 30px rgba(0, 255, 255, 0.05) !important;
366
+ min-height: 100px !important;
367
  }
368
 
369
  /* Headers */
370
+ h1 {
371
+ background: linear-gradient(135deg, var(--neon-pink) 0%, var(--neon-cyan) 100%);
372
  -webkit-background-clip: text;
373
  -webkit-text-fill-color: transparent;
374
+ background-clip: text;
375
+ font-weight: 900 !important;
376
+ font-size: 56px !important;
377
+ text-transform: uppercase !important;
378
+ letter-spacing: 4px !important;
379
+ text-align: center !important;
380
+ margin: 30px 0 !important;
381
+ filter: drop-shadow(0 0 20px rgba(255, 0, 128, 0.5));
382
+ }
383
+
384
+ h2 {
385
+ color: var(--neon-cyan) !important;
386
  font-weight: 800 !important;
387
+ font-size: 32px !important;
388
+ text-transform: lowercase !important;
389
+ letter-spacing: 1px !important;
390
+ margin: 20px 0 !important;
391
  }
392
 
393
+ h3 {
394
+ color: var(--neon-pink) !important;
395
+ font-weight: 700 !important;
396
+ font-size: 24px !important;
397
+ margin: 16px 0 !important;
398
+ }
399
+
400
+ /* Paragraphs */
401
+ p {
402
+ color: #cccccc !important;
403
+ font-size: 16px !important;
404
+ line-height: 1.6 !important;
405
  }
406
 
407
  /* Links */
408
  a {
409
+ color: var(--neon-cyan) !important;
410
  text-decoration: none !important;
411
+ font-weight: 700 !important;
412
+ transition: all 0.3s ease !important;
413
  }
414
+
415
  a:hover {
416
+ color: var(--neon-pink) !important;
417
+ text-shadow: 0 0 15px rgba(255, 0, 128, 0.8) !important;
418
+ }
419
+
420
+ /* Code blocks */
421
+ code {
422
+ background: #1a1a1a !important;
423
+ color: var(--neon-green) !important;
424
+ padding: 4px 10px !important;
425
+ border-radius: 6px !important;
426
+ border: 1px solid var(--neon-cyan) !important;
427
+ font-family: 'Fira Code', 'Consolas', monospace !important;
428
+ font-size: 14px !important;
429
+ }
430
+
431
+ /* Horizontal rules */
432
+ hr {
433
+ border: none !important;
434
+ border-top: 2px solid var(--neon-pink) !important;
435
+ margin: 30px 0 !important;
436
+ opacity: 0.3 !important;
437
  }
438
 
439
  /* Scrollbar */
440
  ::-webkit-scrollbar {
441
+ width: 12px;
442
+ height: 12px;
443
  }
444
+
445
  ::-webkit-scrollbar-track {
446
+ background: #0a0a0a;
447
+ border-radius: 10px;
448
  }
449
+
450
  ::-webkit-scrollbar-thumb {
451
+ background: linear-gradient(135deg, var(--neon-pink) 0%, var(--neon-cyan) 100%);
452
  border-radius: 10px;
453
+ border: 2px solid #000000;
454
  }
455
+
456
  ::-webkit-scrollbar-thumb:hover {
457
+ background: linear-gradient(135deg, var(--neon-cyan) 0%, var(--neon-pink) 100%);
458
+ }
459
+
460
+ /* Labels */
461
+ label {
462
+ color: var(--neon-cyan) !important;
463
+ font-weight: 700 !important;
464
+ font-size: 13px !important;
465
+ text-transform: uppercase !important;
466
+ letter-spacing: 1.2px !important;
467
+ margin-bottom: 8px !important;
468
+ }
469
+
470
+ /* Chatbot styling */
471
+ .message-wrap {
472
+ background: #1a1a1a !important;
473
+ border: 1px solid #333333 !important;
474
+ border-radius: 12px !important;
475
+ margin: 8px 0 !important;
476
+ padding: 12px !important;
477
+ }
478
+
479
+ .message.user {
480
+ background: linear-gradient(135deg, var(--neon-pink) 0%, var(--neon-purple) 100%) !important;
481
+ color: #ffffff !important;
482
+ border-radius: 12px !important;
483
+ padding: 12px !important;
484
+ }
485
+
486
+ .message.bot {
487
+ background: #222222 !important;
488
+ border: 1px solid var(--neon-cyan) !important;
489
+ color: #ffffff !important;
490
+ border-radius: 12px !important;
491
+ padding: 12px !important;
492
+ }
493
+
494
+ /* Dropdown menus */
495
+ .dropdown-container {
496
+ background: #1a1a1a !important;
497
+ border: 2px solid #333333 !important;
498
+ border-radius: 10px !important;
499
+ }
500
+
501
+ /* Info/Warning boxes */
502
+ .info {
503
+ background: rgba(0, 255, 255, 0.1) !important;
504
+ border-left: 4px solid var(--neon-cyan) !important;
505
+ padding: 16px !important;
506
+ border-radius: 8px !important;
507
+ margin: 16px 0 !important;
508
+ }
509
+
510
+ .warning {
511
+ background: rgba(255, 0, 128, 0.1) !important;
512
+ border-left: 4px solid var(--neon-pink) !important;
513
+ padding: 16px !important;
514
+ border-radius: 8px !important;
515
+ margin: 16px 0 !important;
516
+ }
517
+
518
+ /* Loading animation */
519
+ @keyframes glow {
520
+ 0%, 100% {
521
+ box-shadow: 0 0 20px var(--neon-pink), 0 0 40px var(--neon-pink);
522
+ }
523
+ 50% {
524
+ box-shadow: 0 0 30px var(--neon-cyan), 0 0 60px var(--neon-cyan);
525
+ }
526
+ }
527
+
528
+ .loading {
529
+ animation: glow 2s ease-in-out infinite;
530
+ }
531
+
532
+ /* Strong/Bold text */
533
+ strong {
534
+ color: var(--neon-pink) !important;
535
+ font-weight: 800 !important;
536
+ }
537
+
538
+ /* Emphasis/Italic text */
539
+ em {
540
+ color: var(--neon-cyan) !important;
541
+ font-style: italic !important;
542
+ }
543
+
544
+ /* Lists */
545
+ ul, ol {
546
+ color: #cccccc !important;
547
+ padding-left: 30px !important;
548
+ }
549
+
550
+ li {
551
+ margin: 8px 0 !important;
552
  }
 
553
 
554
+ /* Blockquotes */
555
+ blockquote {
556
+ border-left: 4px solid var(--neon-purple) !important;
557
+ padding-left: 20px !important;
558
+ color: #aaaaaa !important;
559
+ font-style: italic !important;
560
+ margin: 20px 0 !important;
561
+ }
562
+ """
563
 
564
  # ============================================
565
+ # 🎭 GRADIO INTERFACE
566
  # ============================================
567
+
568
+ with gr.Blocks(css=custom_css, title="🌟 SYNCHRONY - Student Portal", theme=gr.themes.Base()) as app:
569
+
570
+ # ============================================
571
+ # HEADER
572
+ # ============================================
573
+ gr.Markdown("""
574
+ # 🌟 SYNCHRONY
575
+ ## *where students teach students* πŸ’«
576
+
577
+ ---
578
+ """)
579
+
580
+ # ============================================
581
+ # TAB 1: HOME / LOGIN
582
+ # ============================================
583
+ with gr.Tab("🏠 home"):
584
+ gr.Markdown("""
585
+ ## welcome to synchrony ✨
586
+
587
+ *the ai-powered study platform where you learn by teaching each other*
588
+ """)
589
+
 
590
  with gr.Row():
591
+ with gr.Column(scale=1):
592
+ gr.Markdown("""
593
+ ### πŸ“ new here?
594
+
595
+ join synchrony and get matched with your perfect study squad!
596
+
597
+ click below to fill out the registration form πŸ‘‡
598
+ """)
599
+
600
+ register_btn = gr.Button("πŸš€ REGISTER NOW", variant="primary", size="lg")
601
+
602
+ gr.Markdown("""
603
+
604
+ ---
605
+
606
+ ### πŸ” already registered?
607
+
608
+ enter your university email to access your study session
609
+ """)
610
+
611
+ email_input = gr.Textbox(
612
+ label="πŸ“§ university email",
613
+ placeholder="your.email@university.edu",
614
+ lines=1
615
+ )
616
+
617
+ login_btn = gr.Button("πŸ”“ LOGIN", variant="secondary", size="lg")
618
+ login_output = gr.Markdown(label="")
619
+
620
+ gr.Markdown("""
621
+
622
+ ---
623
+
624
+ ### πŸ“Š view database
625
+
626
+ check out the student database and see who's in synchrony!
627
+ """)
628
+
629
+ sheets_btn = gr.Button("πŸ“Š OPEN GOOGLE SHEETS", variant="secondary")
630
+
631
+ # Register button opens Google Form in new tab
632
+ register_btn.click(
633
+ fn=lambda: None,
634
+ js=f"() => window.open('{REGISTRATION_FORM}', '_blank')"
635
+ )
636
+
637
+ # Sheets button opens Google Sheets in new tab
638
+ sheets_btn.click(
639
+ fn=lambda: None,
640
+ js=f"() => window.open('{GOOGLE_SHEETS}', '_blank')"
641
+ )
642
+
643
+ # Login button
644
+ def handle_login(email):
645
+ success, message = lookup_student(email)
646
+ return message
647
+
648
+ login_btn.click(
649
+ fn=handle_login,
650
+ inputs=[email_input],
651
+ outputs=[login_output]
652
+ )
653
+
654
+ email_input.submit(
655
+ fn=handle_login,
656
+ inputs=[email_input],
657
+ outputs=[login_output]
658
+ )
659
+
660
+ # ============================================
661
+ # TAB 2: MY TEAM
662
+ # ============================================
663
+ with gr.Tab("πŸ‘₯ my team"):
664
+ gr.Markdown("""
665
+ ## your study squad πŸ”₯
666
+
667
+ meet your teammates and see what everyone's working on
668
+ """)
669
+
670
+ team_display = gr.Markdown(value="⚠️ please login first in the **HOME** tab!")
671
+
672
+ refresh_team_btn = gr.Button("πŸ”„ REFRESH TEAM INFO", variant="secondary")
673
+
674
+ refresh_team_btn.click(
675
+ fn=get_team_info,
676
+ outputs=[team_display]
677
+ )
678
+
679
+ # ============================================
680
+ # TAB 3: CHALLENGES
681
+ # ============================================
682
+ with gr.Tab("🎯 challenges"):
683
+ gr.Markdown("""
684
+ ## your collaborative challenges πŸ’ͺ
685
+
686
+ work together, learn from each other, level up!
687
+
688
+ *ai-generated challenges tailored to your group's topics*
689
+ """)
690
+
691
+ load_challenges_btn = gr.Button("πŸ“₯ LOAD CHALLENGES", variant="primary", size="lg")
692
+ challenges_display = gr.Markdown(value="⚠️ click the button above to load your challenges!")
693
+
694
+ load_challenges_btn.click(
695
+ fn=load_challenges,
696
+ outputs=[challenges_display]
697
+ )
698
+
699
+ # ============================================
700
+ # TAB 4: HINTS
701
+ # ============================================
702
+ with gr.Tab("πŸ’‘ hints"):
703
+ gr.Markdown("""
704
+ ## need help? no worries! 🀝
705
+
706
+ synco's got your back with **adaptive hints**
707
+
708
+ **hint 1:** gentle nudge πŸ€”
709
+ **hint 2:** clearer guidance πŸ’­
710
+ **hint 3:** almost there! 🎯
711
+ """)
712
+
713
  with gr.Row():
714
+ challenge_select = gr.Dropdown(
715
+ label="🎯 select challenge",
716
+ choices=["1", "2", "3"],
717
+ value="1",
718
+ scale=1
719
+ )
720
+
721
+ hint_level_select = gr.Dropdown(
722
+ label="πŸ’‘ hint level",
723
+ choices=[
724
+ "1 - gentle nudge πŸ€”",
725
+ "2 - clearer guidance πŸ’­",
726
+ "3 - almost there! 🎯"
727
+ ],
728
+ value="1 - gentle nudge πŸ€”",
729
+ scale=2
730
+ )
731
+
732
+ get_hint_btn = gr.Button("πŸ’‘ GET HINT", variant="primary", size="lg")
733
+ hint_display = gr.Markdown(value="⚠️ select a challenge and hint level, then click the button!")
734
+
735
+ get_hint_btn.click(
736
+ fn=request_hint,
737
+ inputs=[challenge_select, hint_level_select],
738
+ outputs=[hint_display]
 
 
 
 
 
 
 
 
 
 
739
  )
740
+
741
+ # ============================================
742
+ # TAB 5: TEAM CHAT
743
+ # ============================================
744
+ with gr.Tab("πŸ’¬ chat"):
745
+ gr.Markdown("""
746
+ ## team chat πŸ’¬
747
+
748
+ discuss challenges, share ideas, help each other out!
749
+
750
+ <div class="warning">
751
+ ⚠️ <strong>demo mode:</strong> chat responses are simulated. in production, this would be real-time collaboration!
752
+ </div>
753
+ """)
754
+
755
+ chatbot = gr.Chatbot(
756
+ value=[],
757
+ label="",
758
+ height=450,
759
+ type="messages",
760
+ avatar_images=None
761
+ )
762
+
763
+ with gr.Row():
764
+ msg_input = gr.Textbox(
765
+ label="",
766
+ placeholder="type your message here...",
767
+ scale=5,
768
+ lines=1
769
+ )
770
+ send_btn = gr.Button("πŸ“€ SEND", variant="primary", scale=1)
771
+
772
+ send_btn.click(
773
+ fn=send_message,
774
+ inputs=[msg_input, chatbot],
775
+ outputs=[chatbot, msg_input]
776
+ )
777
+
778
+ msg_input.submit(
779
+ fn=send_message,
780
+ inputs=[msg_input, chatbot],
781
+ outputs=[chatbot, msg_input]
782
+ )
783
+
784
+ # ============================================
785
+ # TAB 6: ABOUT
786
+ # ============================================
787
+ with gr.Tab("ℹ️ about"):
788
+ gr.Markdown(f"""
789
+ ## 🌟 about synchrony
790
+
791
+ **synchrony** is an ai-powered collaborative learning platform that revolutionizes how students study together.
792
+
793
+ ---
794
+
795
+ ### ✨ what we do
796
+
797
+ βœ… **smart matching** - match students with complementary learning styles
798
+ βœ… **group formation** - create study groups based on chemistry scores
799
+ βœ… **ai challenges** - generate personalized collaborative challenges
800
+ βœ… **adaptive hints** - provide progressive learning support
801
+ βœ… **peer teaching** - students teach each other, reinforcing their knowledge
802
+
803
+ ---
804
+
805
+ ### πŸ› οΈ tech stack
806
+
807
+ - **frontend:** gradio (python)
808
+ - **backend:** n8n workflows
809
+ - **ai:** groq api (llama 3.3 70b)
810
+ - **database:** google sheets
811
+ - **matching:** chemistry score algorithm
812
+
813
+ ---
814
+
815
+ ### πŸ”— links
816
+
817
+ - πŸ“ [student registration form]({REGISTRATION_FORM})
818
+ - πŸ“Š [view database (google sheets)]({GOOGLE_SHEETS})
819
+ - 🌐 **webhook base:** `{BASE_URL}`
820
+
821
+ ---
822
+
823
+ ### πŸ“Š what's real vs demo
824
+
825
+ <div class="info">
826
+ βœ… <strong>fully functional:</strong>
827
+ <ul>
828
+ <li>student registration (google form β†’ sheets)</li>
829
+ <li>ai challenge generation (groq api)</li>
830
+ <li>adaptive hints system (n8n + sheets)</li>
831
+ <li>team data from real api calls</li>
832
+ </ul>
833
+ </div>
834
+
835
+ <div class="warning">
836
+ 🎭 <strong>simulated for demo:</strong>
837
+ <ul>
838
+ <li>login system (assigns everyone to G001)</li>
839
+ <li>team chat (random responses)</li>
840
+ </ul>
841
+
842
+ <em>these would connect to authentication/real-time systems in production</em>
843
+ </div>
844
+
845
+ ---
846
+
847
+ ### πŸ‘₯ created by
848
+
849
+ **maryam shanabli** | november 2025
850
+
851
+ *powered by ai β€’ built for students β€’ designed for impact* πŸš€
852
+
853
+ ---
854
+
855
+ ### 🎯 project status
856
+
857
+ **current phase:** demo/prototype
858
+ **backend status:** fully operational βœ…
859
+ **frontend status:** student portal (v2) βœ…
860
+ **next steps:** real-time chat, authentication, mobile app
861
+ """)
862
+
863
+ # ============================================
864
+ # FOOTER
865
+ # ============================================
866
+ gr.Markdown("""
867
+ ---
868
+
869
+ ### 🌟 **synchrony** | *collaborative learning, powered by ai* πŸ’œ
870
+
871
+ made with πŸ”₯ by maryam shanabli | 2025
872
  """)
873
 
 
874
  # ============================================
875
+ # πŸš€ LAUNCH APP
876
  # ============================================
877
+
878
  if __name__ == "__main__":
879
+ print("πŸš€ Starting SYNCHRONY Student Portal...")
880
+ print(f"πŸ“‘ Connected to: {BASE_URL}")
881
+ print(f"πŸ“ Registration: {REGISTRATION_FORM}")
882
+ print(f"πŸ“Š Database: {GOOGLE_SHEETS}")
883
+ print("✨ All systems ready!")
884
+
885
  app.launch(
886
  server_name="0.0.0.0",
887
  server_port=7860,
888
+ share=True, # Creates public shareable link
889
+ show_error=True,
890
+ favicon_path=None,
891
+ show_api=False
892
+ )