Files changed (1) hide show
  1. app.py +552 -302
app.py CHANGED
@@ -1,398 +1,648 @@
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
  # ============================================
9
+
10
+ # Your n8n webhook URLs
11
  BASE_URL = "https://maryyam.app.n8n.cloud/webhook-test"
12
  START_SESSION_URL = f"{BASE_URL}/start-session"
13
  GENERATE_CHALLENGES_URL = f"{BASE_URL}/generate-challenges"
14
  REQUEST_HINT_URL = f"{BASE_URL}/request-hint"
15
 
16
+ # Google Form URL
17
+ REGISTRATION_FORM = "https://docs.google.com/forms/d/e/1FAIpQLSfJFGx-yd0FPIuRYLUJut3BOOiQ14x_5DYheWpgrUqcHdQaCA/viewform"
18
+
19
+ # Mock student session (in production, this would be from login)
20
+ current_student = {
21
+ "name": None,
22
+ "email": None,
23
+ "group_id": None,
24
+ "session_id": None,
25
+ "team_members": [],
26
+ "challenges": []
27
+ }
28
 
29
+ # Mock chat history
30
+ chat_history = []
31
 
32
  # ============================================
33
+ # πŸ”§ API FUNCTIONS
34
  # ============================================
35
+
36
+ def lookup_student(email):
37
+ """Look up student by email and get their group"""
38
+ # In production, this would query Google Sheets
39
+ # For demo, we'll use mock data
40
+
41
+ # Mock student data
42
+ mock_students = {
43
+ "layla@university.edu": {
44
+ "name": "Layla Mahmoud",
45
+ "email": "layla@university.edu",
46
+ "group_id": "G001",
47
+ "topic": "Binary Search Trees",
48
+ "session_id": "S20251106160510"
49
+ },
50
+ "omar@university.edu": {
51
+ "name": "Omar Khalil",
52
+ "email": "omar@university.edu",
53
+ "group_id": "G001",
54
+ "topic": "Linked Lists",
55
+ "session_id": "S20251106160510"
56
+ },
57
+ "khaled@university.edu": {
58
+ "name": "Khaled Ibrahim",
59
+ "email": "khaled@university.edu",
60
+ "group_id": "G001",
61
+ "topic": "Queues",
62
+ "session_id": "S20251106160510"
63
+ }
64
+ }
65
+
66
+ if email.lower() in mock_students:
67
+ student = mock_students[email.lower()]
68
+ current_student.update(student)
69
+
70
+ # Get team members
71
+ team = [s for s in mock_students.values() if s["group_id"] == student["group_id"]]
72
+ current_student["team_members"] = team
73
+
74
+ return True, f"βœ… Welcome back, {student['name']}!"
75
+ else:
76
+ return False, "❌ Email not found. Please register first!"
77
+
78
+
79
+ def get_team_info():
80
+ """Display team information"""
81
+ if not current_student["name"]:
82
+ return "⚠️ Please login first!"
83
+
84
+ output = f"## πŸ‘₯ Your Study Team - {current_student['group_id']}\n\n"
85
+ output += f"**Session ID:** `{current_student['session_id']}`\n\n"
86
+ output += "---\n\n"
87
+
88
+ for member in current_student["team_members"]:
89
+ is_you = " **(YOU)**" if member["email"] == current_student["email"] else ""
90
+ output += f"### πŸŽ“ {member['name']}{is_you}\n"
91
+ output += f"**Topic:** {member['topic']}\n"
92
+ output += f"**Email:** {member['email']}\n\n"
93
+
94
+ return output
95
+
96
+
97
+ def load_challenges():
98
+ """Load challenges for current session"""
99
+ if not current_student["session_id"]:
100
+ return "⚠️ No active session. Please login first!"
101
+
102
  try:
103
+ response = requests.post(
104
+ GENERATE_CHALLENGES_URL,
105
+ json={"session_id": current_student["session_id"]},
106
+ headers={"Content-Type": "application/json"},
107
+ timeout=60
108
+ )
109
+
110
+ if response.status_code == 200:
111
+ data = response.json()
112
+ current_student["challenges"] = data.get("challenges", [])
113
+
114
+ output = f"# 🎯 Your Collaborative Challenges\n\n"
115
+ output += f"πŸ’¬ **Synco says:** {data.get('synco_message', '')}\n\n"
116
+ output += "---\n\n"
117
+
118
+ for i, challenge in enumerate(current_student["challenges"], 1):
119
+ chal = challenge.get('json', challenge)
120
+ output += f"## 🎯 Challenge {i}\n\n"
121
+ output += f"{chal.get('description', 'No description')}\n\n"
122
+ output += f"**πŸ“š Topics:** {chal.get('topics_involved', 'N/A')}\n\n"
123
+ output += f"πŸ’‘ *Stuck? Request a hint below!*\n\n"
124
+ output += "---\n\n"
125
+
126
+ return output
127
  else:
128
+ return f"❌ Error loading challenges: {response.status_code}"
129
+
130
  except Exception as e:
131
+ return f"❌ Connection Error: {str(e)}"
 
132
 
 
 
 
133
 
134
+ def request_hint(challenge_num, hint_level):
135
+ """Request a hint for a specific challenge"""
136
+ if not current_student["session_id"]:
137
+ return "⚠️ No active session. Please login first!"
138
+
139
+ try:
140
+ response = requests.post(
141
+ REQUEST_HINT_URL,
142
+ json={
143
+ "session_id": current_student["session_id"],
144
+ "challenge_number": int(challenge_num),
145
+ "hint_level": int(hint_level)
146
+ },
147
+ headers={"Content-Type": "application/json"},
148
+ timeout=10
 
149
  )
150
+
151
+ if response.status_code == 200:
152
+ data = response.json()
153
+ return f"## {data.get('synco_message', '')}\n\n{data.get('hint', '')}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
154
  else:
155
+ return f"❌ Error: {response.status_code}"
156
+
157
+ except Exception as e:
158
+ return f"❌ Connection Error: {str(e)}"
159
+
160
+
161
+ def send_message(message, history):
162
+ """Send a chat message (simulated for demo)"""
163
+ if not message.strip():
164
+ return history, ""
165
+
166
+ # Add user message
167
+ history.append({
168
+ "role": "user",
169
+ "content": f"**{current_student['name'] or 'You'}:** {message}"
170
+ })
171
+
172
+ # Simulate team member responses
173
+ responses = [
174
+ f"**Omar:** That's a great point! Let me add to that...",
175
+ f"**Layla:** I think we should also consider...",
176
+ f"**Khaled:** Interesting! In my topic, we do it differently...",
177
+ f"**Synco (AI):** Great discussion! Keep going! πŸ’‘"
178
+ ]
179
+
180
+ import random
181
+ response = random.choice(responses)
182
+
183
+ history.append({
184
+ "role": "assistant",
185
+ "content": response
186
+ })
187
+
188
+ return history, ""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
189
 
190
 
191
  # ============================================
192
+ # 🎨 CUSTOM CSS - PURE BLACK GEN Z STYLE
193
  # ============================================
194
+
195
  custom_css = """
196
+ /* Pure Black Gen Z Background */
197
  .gradio-container {
198
+ background: #000000 !important;
199
+ font-family: 'SF Pro Display', 'Inter', 'Segoe UI', sans-serif !important;
200
+ color: #ffffff !important;
201
+ }
202
+
203
+ /* Neon accent colors */
204
+ :root {
205
+ --neon-pink: #ff00ff;
206
+ --neon-cyan: #00ffff;
207
+ --neon-purple: #9d00ff;
208
+ --neon-green: #00ff88;
209
+ }
210
+
211
+ /* Main container */
212
+ .contain {
213
+ background: #000000 !important;
214
+ }
215
+
216
+ /* Tabs */
217
+ .tab-nav {
218
+ background: #111111 !important;
219
+ border-bottom: 2px solid var(--neon-pink) !important;
220
  }
221
 
 
222
  .tab-nav button {
223
+ background: #1a1a1a !important;
224
+ border: 1px solid #333333 !important;
225
+ color: var(--neon-cyan) !important;
226
+ font-weight: 700 !important;
227
+ font-size: 16px !important;
228
+ text-transform: uppercase !important;
229
+ letter-spacing: 1px !important;
230
  transition: all 0.3s ease !important;
231
  }
232
+
233
  .tab-nav button:hover {
234
+ background: #222222 !important;
235
+ border-color: var(--neon-pink) !important;
236
+ color: var(--neon-pink) !important;
237
+ transform: translateY(-2px) !important;
238
+ box-shadow: 0 5px 20px rgba(255, 0, 255, 0.4) !important;
239
  }
240
+
241
  .tab-nav button.selected {
242
+ background: linear-gradient(135deg, var(--neon-pink) 0%, var(--neon-purple) 100%) !important;
243
+ border-color: var(--neon-pink) !important;
244
+ color: #000000 !important;
245
+ font-weight: 900 !important;
246
+ box-shadow: 0 0 30px rgba(255, 0, 255, 0.8) !important;
247
  }
248
 
249
  /* Input fields */
250
  input, textarea, select {
251
+ background: #1a1a1a !important;
252
+ border: 2px solid #333333 !important;
253
+ color: #ffffff !important;
254
  border-radius: 12px !important;
255
+ font-size: 16px !important;
256
+ padding: 12px !important;
257
  }
258
+
259
  input:focus, textarea:focus, select:focus {
260
+ border-color: var(--neon-cyan) !important;
261
+ box-shadow: 0 0 20px rgba(0, 255, 255, 0.5) !important;
262
+ outline: none !important;
263
  }
264
 
265
  /* Buttons */
266
+ button.primary {
267
+ background: linear-gradient(135deg, var(--neon-pink) 0%, var(--neon-purple) 100%) !important;
268
  border: none !important;
269
+ color: #000000 !important;
270
+ font-weight: 900 !important;
271
+ font-size: 18px !important;
272
+ text-transform: uppercase !important;
273
+ letter-spacing: 2px !important;
274
  border-radius: 12px !important;
275
+ padding: 16px 32px !important;
276
  transition: all 0.3s ease !important;
277
+ box-shadow: 0 5px 30px rgba(255, 0, 255, 0.5) !important;
278
  }
279
+
280
+ button.primary:hover {
281
+ transform: translateY(-4px) scale(1.05) !important;
282
+ box-shadow: 0 10px 50px rgba(255, 0, 255, 0.8) !important;
283
+ }
284
+
285
+ button.secondary {
286
+ background: linear-gradient(135deg, var(--neon-cyan) 0%, var(--neon-green) 100%) !important;
287
+ border: none !important;
288
+ color: #000000 !important;
289
+ font-weight: 800 !important;
290
+ border-radius: 12px !important;
291
+ padding: 12px 24px !important;
292
  }
293
 
294
  /* Output boxes */
295
+ .output-markdown, .prose {
296
+ background: #0a0a0a !important;
297
+ border: 2px solid #222222 !important;
298
+ border-radius: 16px !important;
299
+ padding: 24px !important;
300
+ color: #e0e0e0 !important;
301
+ box-shadow: inset 0 0 20px rgba(0, 255, 255, 0.1) !important;
302
  }
303
 
304
  /* Headers */
305
+ h1 {
306
+ background: linear-gradient(135deg, var(--neon-pink) 0%, var(--neon-cyan) 100%);
307
  -webkit-background-clip: text;
308
  -webkit-text-fill-color: transparent;
309
+ background-clip: text;
310
+ font-weight: 900 !important;
311
+ font-size: 48px !important;
312
+ text-transform: uppercase !important;
313
+ letter-spacing: 3px !important;
314
+ text-align: center !important;
315
+ margin: 20px 0 !important;
316
+ text-shadow: 0 0 30px rgba(255, 0, 255, 0.5) !important;
317
+ }
318
+
319
+ h2 {
320
+ color: var(--neon-cyan) !important;
321
  font-weight: 800 !important;
322
+ font-size: 32px !important;
323
+ text-transform: uppercase !important;
324
+ letter-spacing: 2px !important;
325
+ margin: 16px 0 !important;
326
  }
327
 
328
+ h3 {
329
+ color: var(--neon-pink) !important;
330
+ font-weight: 700 !important;
331
+ font-size: 24px !important;
 
 
332
  }
333
 
334
  /* Links */
335
  a {
336
+ color: var(--neon-cyan) !important;
337
  text-decoration: none !important;
338
+ font-weight: 700 !important;
339
+ transition: all 0.3s ease !important;
340
  }
341
+
342
  a:hover {
343
+ color: var(--neon-pink) !important;
344
+ text-shadow: 0 0 10px rgba(255, 0, 255, 0.8) !important;
345
+ }
346
+
347
+ /* Code blocks */
348
+ code {
349
+ background: #1a1a1a !important;
350
+ color: var(--neon-green) !important;
351
+ padding: 4px 8px !important;
352
+ border-radius: 6px !important;
353
+ border: 1px solid var(--neon-cyan) !important;
354
+ font-family: 'Fira Code', monospace !important;
355
  }
356
 
357
  /* Scrollbar */
358
  ::-webkit-scrollbar {
359
+ width: 12px;
360
  }
361
+
362
  ::-webkit-scrollbar-track {
363
+ background: #0a0a0a;
364
  }
365
+
366
  ::-webkit-scrollbar-thumb {
367
+ background: linear-gradient(135deg, var(--neon-pink) 0%, var(--neon-cyan) 100%);
368
  border-radius: 10px;
369
  }
370
+
371
  ::-webkit-scrollbar-thumb:hover {
372
+ background: linear-gradient(135deg, var(--neon-cyan) 0%, var(--neon-pink) 100%);
373
  }
 
374
 
375
+ /* Labels */
376
+ label {
377
+ color: var(--neon-cyan) !important;
378
+ font-weight: 700 !important;
379
+ font-size: 14px !important;
380
+ text-transform: uppercase !important;
381
+ letter-spacing: 1px !important;
382
+ }
383
+
384
+ /* Chatbot styling */
385
+ .message-wrap {
386
+ background: #1a1a1a !important;
387
+ border: 1px solid #333333 !important;
388
+ border-radius: 12px !important;
389
+ margin: 8px 0 !important;
390
+ }
391
+
392
+ .message.user {
393
+ background: linear-gradient(135deg, var(--neon-pink) 0%, var(--neon-purple) 100%) !important;
394
+ color: #ffffff !important;
395
+ }
396
+
397
+ .message.bot {
398
+ background: #222222 !important;
399
+ border: 1px solid var(--neon-cyan) !important;
400
+ color: #ffffff !important;
401
+ }
402
+
403
+ /* Glow effects */
404
+ .glow {
405
+ animation: glow 2s ease-in-out infinite alternate;
406
+ }
407
+
408
+ @keyframes glow {
409
+ from {
410
+ box-shadow: 0 0 10px var(--neon-pink), 0 0 20px var(--neon-pink);
411
+ }
412
+ to {
413
+ box-shadow: 0 0 20px var(--neon-cyan), 0 0 40px var(--neon-cyan);
414
+ }
415
+ }
416
+
417
+ /* Dropdown menus */
418
+ .dropdown-container {
419
+ background: #1a1a1a !important;
420
+ border: 2px solid #333333 !important;
421
+ }
422
+
423
+ /* Accordions */
424
+ .accordion {
425
+ background: #111111 !important;
426
+ border: 1px solid var(--neon-pink) !important;
427
+ }
428
+
429
+ /* Info boxes */
430
+ .info {
431
+ background: rgba(0, 255, 255, 0.1) !important;
432
+ border-left: 4px solid var(--neon-cyan) !important;
433
+ padding: 16px !important;
434
+ border-radius: 8px !important;
435
+ }
436
+
437
+ .warning {
438
+ background: rgba(255, 0, 255, 0.1) !important;
439
+ border-left: 4px solid var(--neon-pink) !important;
440
+ padding: 16px !important;
441
+ border-radius: 8px !important;
442
+ }
443
+ """
444
 
445
  # ============================================
446
+ # 🎭 GRADIO INTERFACE
447
  # ============================================
448
+
449
+ with gr.Blocks(css=custom_css, title="🌟 SYNCHRONY - Student Portal", theme=gr.themes.Base()) as app:
450
+
451
+ # ============================================
452
+ # HEADER
453
+ # ============================================
454
+ gr.Markdown("""
455
+ # 🌟 SYNCHRONY
456
+ ## *where students teach students* πŸ’«
457
+ """)
458
+
459
+ # ============================================
460
+ # TAB 1: HOME / LOGIN
461
+ # ============================================
462
+ with gr.Tab("🏠 HOME"):
463
+ gr.Markdown("""
464
+ ## welcome to synchrony ✨
465
+
466
+ *the ai-powered study platform where you learn by teaching*
467
+ """)
468
+
469
+ with gr.Row():
470
+ with gr.Column(scale=1):
471
+ gr.Markdown("""
472
+ ### πŸ“ new here?
473
+
474
+ register now and get matched with your perfect study squad!
475
+ """)
476
+
477
+ register_btn = gr.Button("πŸš€ REGISTER NOW", variant="primary", size="lg")
478
+
479
+ gr.Markdown("""
480
+ ### πŸ” already registered?
481
+
482
+ enter your email to access your study session
483
+ """)
484
+
485
+ email_input = gr.Textbox(
486
+ label="πŸ“§ university email",
487
+ placeholder="your.email@university.edu"
488
+ )
489
+
490
+ login_btn = gr.Button("πŸ”“ LOGIN", variant="secondary")
491
+ login_output = gr.Markdown()
492
+
493
+ # Register button opens Google Form
494
+ register_btn.click(
495
+ fn=lambda: gr.update(visible=True),
496
+ outputs=None
497
+ ).then(
498
+ fn=None,
499
+ js=f"() => window.open('{REGISTRATION_FORM}', '_blank')"
500
+ )
501
+
502
+ # Login button
503
+ login_btn.click(
504
+ fn=lookup_student,
505
+ inputs=[email_input],
506
+ outputs=[login_output, login_output]
507
+ )
508
+
509
+ # ============================================
510
+ # TAB 2: MY TEAM
511
+ # ============================================
512
+ with gr.Tab("πŸ‘₯ MY TEAM"):
513
+ gr.Markdown("""
514
+ ## your study squad πŸ”₯
515
+
516
+ meet your teammates and see what everyone's working on
517
+ """)
518
+
519
+ team_display = gr.Markdown()
520
+
521
+ refresh_team_btn = gr.Button("πŸ”„ REFRESH TEAM INFO", variant="secondary")
522
+
523
+ refresh_team_btn.click(
524
+ fn=get_team_info,
525
+ outputs=[team_display]
526
+ )
527
+
528
+ # Auto-load on tab open
529
+ app.load(
530
+ fn=get_team_info,
531
+ outputs=[team_display]
532
+ )
533
+
534
+ # ============================================
535
+ # TAB 3: CHALLENGES
536
+ # ============================================
537
+ with gr.Tab("🎯 CHALLENGES"):
538
+ gr.Markdown("""
539
+ ## your collaborative challenges πŸ’ͺ
540
+
541
+ work together, learn from each other, level up!
542
+ """)
543
+
544
+ load_challenges_btn = gr.Button("πŸ“₯ LOAD CHALLENGES", variant="primary")
545
+ challenges_display = gr.Markdown()
546
+
547
+ load_challenges_btn.click(
548
+ fn=load_challenges,
549
+ outputs=[challenges_display]
550
+ )
551
+
552
+ # ============================================
553
+ # TAB 4: HINTS
554
+ # ============================================
555
+ with gr.Tab("πŸ’‘ HINTS"):
556
+ gr.Markdown("""
557
+ ## need help? no worries! 🀝
558
+
559
+ synco's got your back with adaptive hints
560
+ """)
561
+
562
  with gr.Row():
563
+ challenge_select = gr.Dropdown(
564
+ label="🎯 select challenge",
565
+ choices=["1", "2", "3"],
566
+ value="1"
567
+ )
568
+
569
+ hint_level_select = gr.Dropdown(
570
+ label="πŸ’‘ hint level",
571
+ choices=["1 - gentle nudge πŸ€”", "2 - clearer guidance πŸ’­", "3 - almost there 🎯"],
572
+ value="1 - gentle nudge πŸ€”"
573
+ )
574
+
575
+ get_hint_btn = gr.Button("πŸ’‘ GET HINT", variant="primary")
576
+ hint_display = gr.Markdown()
577
+
578
+ def extract_hint_level(choice):
579
+ return choice.split(" - ")[0]
580
+
581
+ get_hint_btn.click(
582
+ fn=lambda c, h: request_hint(c, extract_hint_level(h)),
583
+ inputs=[challenge_select, hint_level_select],
584
+ outputs=[hint_display]
585
+ )
586
+
587
+ # ============================================
588
+ # TAB 5: TEAM CHAT
589
+ # ============================================
590
+ with gr.Tab("πŸ’¬ CHAT"):
591
+ gr.Markdown("""
592
+ ## team chat πŸ’¬
593
+
594
+ discuss challenges, share ideas, help each other out!
595
+
596
+ *demo mode - simulated responses*
597
+ """)
598
+
599
+ chatbot = gr.Chatbot(
600
+ value=[],
601
+ label="πŸ’¬ team chat",
602
+ height=400,
603
+ type="messages"
604
+ )
605
+
606
  with gr.Row():
607
+ msg_input = gr.Textbox(
608
+ label="",
609
+ placeholder="type your message...",
610
+ scale=4
611
+ )
612
+ send_btn = gr.Button("πŸ“€ SEND", variant="primary", scale=1)
613
+
614
+ send_btn.click(
615
+ fn=send_message,
616
+ inputs=[msg_input, chatbot],
617
+ outputs=[chatbot, msg_input]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
618
  )
619
+
620
+ msg_input.submit(
621
+ fn=send_message,
622
+ inputs=[msg_input, chatbot],
623
+ outputs=[chatbot, msg_input]
624
+ )
625
+
626
+ # ============================================
627
+ # FOOTER
628
+ # ============================================
629
+ gr.Markdown("""
630
+ ---
631
+
632
+ ### 🌟 **synchrony** | *powered by ai, built for students* πŸ’œ
633
+
634
+ made with πŸ”₯ by maryam shanabli | 2025
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
635
  """)
636
 
 
637
  # ============================================
638
+ # πŸš€ LAUNCH APP
639
  # ============================================
640
+
641
  if __name__ == "__main__":
642
  app.launch(
643
  server_name="0.0.0.0",
644
  server_port=7860,
645
  share=True,
646
+ show_error=True,
647
+ favicon_path=None
648
+ )