Spaces:
Sleeping
Sleeping
go
Browse files
app.py
CHANGED
|
@@ -1,17 +1,23 @@
|
|
| 1 |
import gradio as gr
|
|
|
|
|
|
|
| 2 |
import json
|
| 3 |
from datetime import datetime
|
| 4 |
-
import random
|
| 5 |
|
| 6 |
# ============================================
|
| 7 |
-
# 🌟 SYNCHRONY -
|
| 8 |
-
#
|
| 9 |
# ============================================
|
| 10 |
|
| 11 |
REGISTRATION_FORM = "https://docs.google.com/forms/d/e/1FAIpQLSfJFGx-yd0FPIuRYLUJut3BOOiQ14x_5DYheWpgrUqcHdQaCA/viewform"
|
| 12 |
GOOGLE_SHEETS = "https://docs.google.com/spreadsheets/d/1rpR-E_RSooDkNDh-Q_1BDIeGw4vfgmusNAfwPqATaW4/edit?usp=sharing"
|
| 13 |
|
| 14 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
current_student = {
|
| 16 |
"name": None,
|
| 17 |
"email": None,
|
|
@@ -21,22 +27,16 @@ current_student = {
|
|
| 21 |
"challenges": []
|
| 22 |
}
|
| 23 |
|
| 24 |
-
#
|
| 25 |
-
MOCK_STUDENTS = [
|
| 26 |
-
{"name": "Omar Khalil", "topic": "Linked Lists"},
|
| 27 |
-
{"name": "Khaled Ibrahim", "topic": "Queues"},
|
| 28 |
-
{"name": "Layla Mahmoud", "topic": "Binary Search Trees"}
|
| 29 |
-
]
|
| 30 |
-
|
| 31 |
MOCK_CHALLENGES = [
|
| 32 |
{
|
| 33 |
"challenge_number": 1,
|
| 34 |
"description": "Layla, explain how Binary Search Trees work to the group. Focus on BST properties, insertion, and search operations. Omar and Khaled, compare this with your data structures - how is searching in a BST different from searching in a linked list or checking elements in a queue?",
|
| 35 |
"topics_involved": "Binary Search Trees, Linked Lists, Queues",
|
| 36 |
"hints": [
|
| 37 |
-
"Start by drawing a simple diagram showing how nodes are organized in a BST",
|
| 38 |
-
"Think about the left-subtree and right-subtree properties - what makes a BST special?",
|
| 39 |
-
"Compare time complexity: BST search is O(log n) while linked list is O(n)"
|
| 40 |
]
|
| 41 |
},
|
| 42 |
{
|
|
@@ -44,9 +44,9 @@ MOCK_CHALLENGES = [
|
|
| 44 |
"description": "Omar, teach us about Linked Lists. Create a real-world example showing both singly and doubly linked lists. Layla and Khaled, find connections between Linked Lists and your data structures.",
|
| 45 |
"topics_involved": "Linked Lists, Binary Search Trees, Queues",
|
| 46 |
"hints": [
|
| 47 |
-
"Think about how nodes point to each other -
|
| 48 |
-
"Consider real-world examples: browser history, music
|
| 49 |
-
"Both BST nodes and queue nodes use
|
| 50 |
]
|
| 51 |
},
|
| 52 |
{
|
|
@@ -54,156 +54,255 @@ MOCK_CHALLENGES = [
|
|
| 54 |
"description": "Everyone: Design ONE real-world system that uses ALL three data structures (BST, Linked Lists, Queues). Explain how they work together. Be creative!",
|
| 55 |
"topics_involved": "Binary Search Trees, Linked Lists, Queues",
|
| 56 |
"hints": [
|
| 57 |
-
"Think of a system with searching, ordering, and waiting - like a restaurant management system",
|
| 58 |
-
"BST for menu items (fast lookup), Queue for order processing, Linked List for order history",
|
| 59 |
-
"Draw a diagram showing how data flows between the three structures"
|
| 60 |
]
|
| 61 |
}
|
| 62 |
]
|
| 63 |
|
| 64 |
# ============================================
|
| 65 |
-
#
|
| 66 |
# ============================================
|
| 67 |
|
| 68 |
-
def
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 72 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 73 |
|
| 74 |
def lookup_student(email):
|
| 75 |
-
"""Login simulation"""
|
| 76 |
try:
|
| 77 |
if not email or "@" not in email:
|
| 78 |
-
return "❌
|
| 79 |
|
| 80 |
-
|
| 81 |
-
name_part = email.split("@")[0]
|
| 82 |
-
name = name_part.replace(".", " ").replace("_", " ").title()
|
| 83 |
|
| 84 |
-
|
| 85 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 86 |
|
| 87 |
current_student["name"] = name
|
| 88 |
current_student["email"] = email
|
| 89 |
-
current_student["group_id"] =
|
| 90 |
current_student["session_id"] = session_id
|
| 91 |
-
current_student["team_members"] =
|
| 92 |
-
|
| 93 |
-
welcome_msg = f"✅
|
| 94 |
-
welcome_msg += f"
|
| 95 |
-
welcome_msg += f"
|
| 96 |
-
welcome_msg += f"Hey {', '.join([
|
| 97 |
-
welcome_msg += f"You're all studying Data Structures
|
| 98 |
-
welcome_msg += f"
|
| 99 |
-
welcome_msg += f"
|
| 100 |
-
welcome_msg += f"learn from each other's expertise. Ready? Let's go! 🚀\n\n"
|
| 101 |
-
welcome_msg += f"👥 **your team:** {', '.join([s['name'] for s in MOCK_STUDENTS])}\n\n"
|
| 102 |
-
welcome_msg += f"✨ head to **MY TEAM** tab to see everyone!"
|
| 103 |
|
| 104 |
return welcome_msg
|
| 105 |
|
| 106 |
except Exception as e:
|
| 107 |
-
return f"❌
|
| 108 |
-
|
| 109 |
|
| 110 |
def get_team_info():
|
| 111 |
-
"""Display team information"""
|
| 112 |
if not current_student.get("team_members"):
|
| 113 |
-
return "⚠️
|
| 114 |
|
| 115 |
-
output = f"## 👥
|
| 116 |
-
output += f"**
|
| 117 |
output += "---\n\n"
|
| 118 |
|
| 119 |
for member in current_student["team_members"]:
|
| 120 |
user_name = current_student.get("name", "").lower()
|
| 121 |
member_name = member.get("name", "").lower()
|
| 122 |
-
is_you = " **(
|
| 123 |
output += f"### 🎓 {member['name']}{is_you}\n"
|
| 124 |
-
output += f"**
|
| 125 |
|
| 126 |
-
output += "---\n\n💡
|
| 127 |
return output
|
| 128 |
|
| 129 |
-
|
| 130 |
def load_challenges():
|
| 131 |
-
"""Load challenges (simulated)"""
|
| 132 |
if not current_student.get("session_id"):
|
| 133 |
-
return "⚠️
|
| 134 |
-
|
| 135 |
-
current_student["challenges"] = MOCK_CHALLENGES
|
| 136 |
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
output += f"
|
| 145 |
-
output += f"{challenge['description']}\n\n"
|
| 146 |
-
output += f"**📚 topics:** {challenge['topics_involved']}\n\n"
|
| 147 |
-
output += f"💡 *stuck? request a hint in the **HINTS** tab!*\n\n"
|
| 148 |
output += "---\n\n"
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 152 |
|
| 153 |
def request_hint(challenge_num, hint_level_text):
|
| 154 |
-
"""Request hint (simulated)"""
|
| 155 |
if not current_student.get("session_id"):
|
| 156 |
-
return "⚠️
|
| 157 |
|
| 158 |
if not current_student.get("challenges"):
|
| 159 |
-
return "⚠️
|
| 160 |
|
| 161 |
try:
|
| 162 |
hint_level = int(hint_level_text.split(" - ")[0])
|
| 163 |
challenge_idx = int(challenge_num) - 1
|
| 164 |
|
| 165 |
-
if challenge_idx < 0 or challenge_idx >= len(
|
| 166 |
-
return "⚠️
|
|
|
|
|
|
|
|
|
|
| 167 |
|
| 168 |
-
if hint_level < 1 or hint_level >
|
| 169 |
-
return "⚠️
|
| 170 |
|
| 171 |
-
|
| 172 |
-
hint = challenge["hints"][hint_level - 1]
|
| 173 |
|
| 174 |
-
# Encouraging messages based on hint level
|
| 175 |
if hint_level == 1:
|
| 176 |
-
encouragement = "💡 **Think about it a bit more
|
| 177 |
elif hint_level == 2:
|
| 178 |
-
encouragement = "
|
| 179 |
else:
|
| 180 |
-
encouragement = "
|
| 181 |
|
| 182 |
-
output = f"## {encouragement}
|
| 183 |
-
output += f"
|
| 184 |
output += "---\n\n"
|
| 185 |
output += f"{hint}\n\n"
|
| 186 |
output += "---\n\n"
|
| 187 |
-
output += f"💪
|
| 188 |
|
| 189 |
return output
|
| 190 |
|
| 191 |
except Exception as e:
|
| 192 |
-
return f"❌
|
| 193 |
-
|
| 194 |
|
| 195 |
def send_message(message, history):
|
| 196 |
-
"""Simulated team chat"""
|
| 197 |
if not message.strip():
|
| 198 |
return history, ""
|
| 199 |
|
| 200 |
history.append({
|
| 201 |
"role": "user",
|
| 202 |
-
"content": f"**{current_student.get('name', '
|
| 203 |
})
|
| 204 |
|
| 205 |
if not current_student.get("team_members"):
|
| 206 |
-
response = "**
|
| 207 |
else:
|
| 208 |
other_members = [m for m in current_student["team_members"]
|
| 209 |
if m['name'].lower() != current_student.get('name', '').lower()]
|
|
@@ -211,133 +310,328 @@ def send_message(message, history):
|
|
| 211 |
if other_members:
|
| 212 |
member = random.choice(other_members)
|
| 213 |
responses = [
|
| 214 |
-
f"**{member['name']}:**
|
| 215 |
-
f"**{member['name']}:**
|
| 216 |
-
f"**{member['name']}:**
|
| 217 |
-
f"**{member['name']}:**
|
| 218 |
-
f"**
|
| 219 |
-
f"**synco (ai):** great discussion everyone! 💡 keep collaborating!"
|
| 220 |
]
|
| 221 |
response = random.choice(responses)
|
| 222 |
else:
|
| 223 |
-
response = "**
|
| 224 |
|
| 225 |
history.append({"role": "assistant", "content": response})
|
| 226 |
return history, ""
|
| 227 |
|
| 228 |
-
|
| 229 |
# ============================================
|
| 230 |
-
# 🎨
|
| 231 |
# ============================================
|
| 232 |
|
| 233 |
custom_css = """
|
|
|
|
|
|
|
| 234 |
.gradio-container {
|
| 235 |
-
background: #
|
| 236 |
-
font-family: 'Inter', sans-serif !important;
|
| 237 |
-
color: #
|
| 238 |
}
|
| 239 |
|
|
|
|
| 240 |
:root {
|
| 241 |
-
--
|
| 242 |
-
--
|
| 243 |
-
--
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 244 |
}
|
| 245 |
|
| 246 |
.tab-nav button {
|
| 247 |
-
background:
|
| 248 |
-
border:
|
| 249 |
-
color: var(--
|
| 250 |
-
font-weight:
|
| 251 |
-
|
| 252 |
-
|
|
|
|
|
|
|
|
|
|
| 253 |
}
|
| 254 |
|
| 255 |
.tab-nav button:hover {
|
| 256 |
-
|
| 257 |
-
color: var(--
|
| 258 |
-
transform: translateY(-2px) !important;
|
| 259 |
}
|
| 260 |
|
| 261 |
.tab-nav button.selected {
|
| 262 |
-
background:
|
| 263 |
-
color:
|
| 264 |
-
box-shadow: 0
|
| 265 |
}
|
| 266 |
|
| 267 |
-
|
| 268 |
-
|
| 269 |
-
|
| 270 |
-
|
|
|
|
| 271 |
border-radius: 10px !important;
|
|
|
|
|
|
|
|
|
|
| 272 |
}
|
| 273 |
|
| 274 |
-
input:focus, textarea:focus {
|
| 275 |
-
border-color: var(--
|
| 276 |
-
box-shadow: 0 0
|
|
|
|
| 277 |
}
|
| 278 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 279 |
button {
|
| 280 |
-
background:
|
| 281 |
border: none !important;
|
| 282 |
-
color:
|
| 283 |
-
font-weight:
|
| 284 |
-
|
| 285 |
border-radius: 10px !important;
|
| 286 |
-
padding:
|
| 287 |
-
transition: all 0.
|
|
|
|
| 288 |
}
|
| 289 |
|
| 290 |
button:hover {
|
| 291 |
-
|
| 292 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 293 |
}
|
| 294 |
|
| 295 |
-
|
| 296 |
-
|
| 297 |
-
|
|
|
|
| 298 |
border-radius: 12px !important;
|
| 299 |
padding: 24px !important;
|
| 300 |
-
color:
|
|
|
|
|
|
|
| 301 |
}
|
| 302 |
|
|
|
|
| 303 |
h1 {
|
| 304 |
-
|
| 305 |
-
-
|
| 306 |
-
-
|
| 307 |
-
font-weight: 900 !important;
|
| 308 |
-
font-size: 56px !important;
|
| 309 |
text-align: center !important;
|
|
|
|
|
|
|
| 310 |
}
|
| 311 |
|
| 312 |
h2 {
|
| 313 |
-
color: var(--
|
| 314 |
-
font-weight:
|
|
|
|
|
|
|
| 315 |
}
|
| 316 |
|
| 317 |
h3 {
|
| 318 |
-
color: var(--
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 319 |
font-weight: 700 !important;
|
| 320 |
}
|
| 321 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 322 |
code {
|
| 323 |
-
background: #
|
| 324 |
-
color:
|
| 325 |
-
padding:
|
| 326 |
border-radius: 6px !important;
|
| 327 |
-
border: 1px solid var(--
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 328 |
}
|
| 329 |
|
|
|
|
| 330 |
::-webkit-scrollbar {
|
| 331 |
-
width:
|
|
|
|
| 332 |
}
|
| 333 |
|
| 334 |
::-webkit-scrollbar-track {
|
| 335 |
-
background: #
|
|
|
|
| 336 |
}
|
| 337 |
|
| 338 |
::-webkit-scrollbar-thumb {
|
| 339 |
-
background:
|
| 340 |
border-radius: 10px;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 341 |
}
|
| 342 |
"""
|
| 343 |
|
|
@@ -345,36 +639,37 @@ code {
|
|
| 345 |
# 🎭 GRADIO INTERFACE
|
| 346 |
# ============================================
|
| 347 |
|
| 348 |
-
demo = gr.Blocks(css=custom_css, title="
|
| 349 |
|
| 350 |
with demo:
|
| 351 |
gr.Markdown("""
|
| 352 |
-
# 🌟
|
| 353 |
-
##
|
| 354 |
|
| 355 |
-
*
|
| 356 |
|
| 357 |
---
|
| 358 |
""")
|
| 359 |
|
| 360 |
-
|
| 361 |
-
with gr.Tab("🏠 home"):
|
| 362 |
gr.Markdown("""
|
| 363 |
-
##
|
| 364 |
|
| 365 |
-
|
| 366 |
""")
|
| 367 |
|
| 368 |
with gr.Column():
|
| 369 |
-
gr.Markdown("### 📝
|
| 370 |
-
|
|
|
|
| 371 |
|
| 372 |
-
gr.Markdown("### 🔐
|
|
|
|
| 373 |
email_input = gr.Textbox(
|
| 374 |
-
label="
|
| 375 |
placeholder="your.email@university.edu"
|
| 376 |
)
|
| 377 |
-
login_btn = gr.Button("
|
| 378 |
login_output = gr.Markdown()
|
| 379 |
|
| 380 |
register_btn.click(
|
|
@@ -392,84 +687,77 @@ with demo:
|
|
| 392 |
outputs=[login_output]
|
| 393 |
)
|
| 394 |
|
| 395 |
-
|
| 396 |
-
|
| 397 |
-
gr.Markdown("
|
| 398 |
-
team_display = gr.Markdown(value="⚠️
|
| 399 |
-
|
| 400 |
-
|
| 401 |
|
| 402 |
-
|
| 403 |
-
with gr.Tab("🎯 challenges"):
|
| 404 |
gr.Markdown("""
|
| 405 |
-
##
|
| 406 |
|
| 407 |
-
|
| 408 |
""")
|
| 409 |
-
|
| 410 |
-
challenges_display = gr.Markdown(value="
|
| 411 |
-
|
| 412 |
|
| 413 |
-
|
| 414 |
-
with gr.Tab("💡 hints"):
|
| 415 |
gr.Markdown("""
|
| 416 |
-
##
|
| 417 |
-
|
| 418 |
-
synco's got your back with **adaptive hints**
|
| 419 |
|
| 420 |
-
|
| 421 |
-
**hint 2:** clearer guidance 💭
|
| 422 |
-
**hint 3:** almost there! 🎯
|
| 423 |
""")
|
| 424 |
|
| 425 |
with gr.Row():
|
| 426 |
challenge_select = gr.Dropdown(
|
| 427 |
-
label="
|
| 428 |
choices=["1", "2", "3"],
|
| 429 |
value="1"
|
| 430 |
)
|
| 431 |
hint_level_select = gr.Dropdown(
|
| 432 |
-
label="
|
| 433 |
choices=[
|
| 434 |
-
"1 -
|
| 435 |
-
"2 -
|
| 436 |
-
"3 -
|
| 437 |
],
|
| 438 |
-
value="1 -
|
| 439 |
)
|
| 440 |
|
| 441 |
-
|
| 442 |
-
hint_display = gr.Markdown(value="
|
| 443 |
-
|
| 444 |
fn=request_hint,
|
| 445 |
inputs=[challenge_select, hint_level_select],
|
| 446 |
outputs=[hint_display]
|
| 447 |
)
|
| 448 |
|
| 449 |
-
|
| 450 |
-
with gr.Tab("💬 chat"):
|
| 451 |
gr.Markdown("""
|
| 452 |
-
##
|
| 453 |
|
| 454 |
-
|
| 455 |
|
| 456 |
-
*
|
| 457 |
""")
|
| 458 |
|
| 459 |
chatbot = gr.Chatbot(
|
| 460 |
value=[],
|
| 461 |
label="",
|
| 462 |
-
height=
|
| 463 |
type="messages"
|
| 464 |
)
|
| 465 |
|
| 466 |
with gr.Row():
|
| 467 |
msg_input = gr.Textbox(
|
| 468 |
label="",
|
| 469 |
-
placeholder="
|
| 470 |
scale=5
|
| 471 |
)
|
| 472 |
-
send_btn = gr.Button("
|
| 473 |
|
| 474 |
send_btn.click(
|
| 475 |
fn=send_message,
|
|
@@ -482,72 +770,22 @@ with demo:
|
|
| 482 |
outputs=[chatbot, msg_input]
|
| 483 |
)
|
| 484 |
|
| 485 |
-
# ABOUT
|
| 486 |
-
with gr.Tab("ℹ️ about"):
|
| 487 |
-
gr.Markdown(f"""
|
| 488 |
-
## 🌟 about synchrony
|
| 489 |
-
|
| 490 |
-
**synchrony** is an ai-powered collaborative learning platform.
|
| 491 |
-
|
| 492 |
-
---
|
| 493 |
-
|
| 494 |
-
### ✨ features
|
| 495 |
-
|
| 496 |
-
✅ **smart matching** - students matched by learning styles
|
| 497 |
-
✅ **ai challenges** - personalized collaborative challenges
|
| 498 |
-
✅ **adaptive hints** - progressive learning support
|
| 499 |
-
✅ **peer teaching** - students teach each other
|
| 500 |
-
|
| 501 |
-
---
|
| 502 |
-
|
| 503 |
-
### 🛠️ tech stack
|
| 504 |
-
|
| 505 |
-
- **frontend:** gradio (python)
|
| 506 |
-
- **backend:** n8n workflows
|
| 507 |
-
- **ai:** groq api (llama 3.3 70b)
|
| 508 |
-
- **database:** google sheets
|
| 509 |
-
|
| 510 |
-
---
|
| 511 |
-
|
| 512 |
-
### 🔗 links
|
| 513 |
-
|
| 514 |
-
- 📝 [student registration]({REGISTRATION_FORM})
|
| 515 |
-
- 📊 [view database]({GOOGLE_SHEETS})
|
| 516 |
-
|
| 517 |
-
---
|
| 518 |
-
|
| 519 |
-
### 👥 created by
|
| 520 |
-
|
| 521 |
-
**maryam shanabli** | november 2025
|
| 522 |
-
|
| 523 |
-
*powered by ai • built for students • designed for impact* 🚀
|
| 524 |
-
|
| 525 |
-
---
|
| 526 |
-
|
| 527 |
-
### 📊 demo note
|
| 528 |
-
|
| 529 |
-
This is a **demonstration version** using simulated data. The real system connects to live n8n workflows, Groq AI API, and Google Sheets database for full functionality.
|
| 530 |
-
""")
|
| 531 |
-
|
| 532 |
-
# FOOTER
|
| 533 |
gr.Markdown("""
|
| 534 |
---
|
| 535 |
|
| 536 |
-
###
|
| 537 |
|
| 538 |
-
|
|
|
|
|
|
|
| 539 |
""")
|
| 540 |
|
| 541 |
-
# ============================================
|
| 542 |
-
# 🚀 LAUNCH
|
| 543 |
-
# ============================================
|
| 544 |
-
|
| 545 |
print("\n" + "="*60)
|
| 546 |
-
print("
|
| 547 |
print("="*60)
|
| 548 |
-
print("✅
|
|
|
|
| 549 |
print("✅ All features functional")
|
| 550 |
-
print("✅ Ready for demo!")
|
| 551 |
print("="*60 + "\n")
|
| 552 |
|
| 553 |
demo.launch()
|
|
|
|
| 1 |
import gradio as gr
|
| 2 |
+
import pandas as pd
|
| 3 |
+
import random
|
| 4 |
import json
|
| 5 |
from datetime import datetime
|
|
|
|
| 6 |
|
| 7 |
# ============================================
|
| 8 |
+
# 🌟 SYNCHRONY - PROFESSIONAL LIGHT MODE
|
| 9 |
+
# Soft Academic Aesthetic
|
| 10 |
# ============================================
|
| 11 |
|
| 12 |
REGISTRATION_FORM = "https://docs.google.com/forms/d/e/1FAIpQLSfJFGx-yd0FPIuRYLUJut3BOOiQ14x_5DYheWpgrUqcHdQaCA/viewform"
|
| 13 |
GOOGLE_SHEETS = "https://docs.google.com/spreadsheets/d/1rpR-E_RSooDkNDh-Q_1BDIeGw4vfgmusNAfwPqATaW4/edit?usp=sharing"
|
| 14 |
|
| 15 |
+
SHEET_ID = "1rpR-E_RSooDkNDh-Q_1BDIeGw4vfgmusNAfwPqATaW4"
|
| 16 |
+
|
| 17 |
+
STUDENTS_URL = f"https://docs.google.com/spreadsheets/d/{SHEET_ID}/gviz/tq?tqx=out:csv&sheet=Students"
|
| 18 |
+
GROUPS_URL = f"https://docs.google.com/spreadsheets/d/{SHEET_ID}/gviz/tq?tqx=out:csv&sheet=Groups"
|
| 19 |
+
CHALLENGES_URL = f"https://docs.google.com/spreadsheets/d/{SHEET_ID}/gviz/tq?tqx=out:csv&sheet=Challenges"
|
| 20 |
+
|
| 21 |
current_student = {
|
| 22 |
"name": None,
|
| 23 |
"email": None,
|
|
|
|
| 27 |
"challenges": []
|
| 28 |
}
|
| 29 |
|
| 30 |
+
# Mock data fallback
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 31 |
MOCK_CHALLENGES = [
|
| 32 |
{
|
| 33 |
"challenge_number": 1,
|
| 34 |
"description": "Layla, explain how Binary Search Trees work to the group. Focus on BST properties, insertion, and search operations. Omar and Khaled, compare this with your data structures - how is searching in a BST different from searching in a linked list or checking elements in a queue?",
|
| 35 |
"topics_involved": "Binary Search Trees, Linked Lists, Queues",
|
| 36 |
"hints": [
|
| 37 |
+
"Start by drawing a simple diagram showing how nodes are organized in a BST with the property: left < parent < right",
|
| 38 |
+
"Think about the left-subtree and right-subtree properties - what makes a BST special compared to a regular tree?",
|
| 39 |
+
"Compare time complexity: BST search is O(log n) average case, while linked list search is O(n)"
|
| 40 |
]
|
| 41 |
},
|
| 42 |
{
|
|
|
|
| 44 |
"description": "Omar, teach us about Linked Lists. Create a real-world example showing both singly and doubly linked lists. Layla and Khaled, find connections between Linked Lists and your data structures.",
|
| 45 |
"topics_involved": "Linked Lists, Binary Search Trees, Queues",
|
| 46 |
"hints": [
|
| 47 |
+
"Think about how nodes point to each other - singly linked has 'next' pointer, doubly linked has 'next' and 'prev'",
|
| 48 |
+
"Consider real-world examples: browser history (doubly linked), music playlist (singly linked), undo/redo (doubly linked)",
|
| 49 |
+
"Both BST nodes and queue nodes use pointers/references just like linked lists - they're all node-based structures!"
|
| 50 |
]
|
| 51 |
},
|
| 52 |
{
|
|
|
|
| 54 |
"description": "Everyone: Design ONE real-world system that uses ALL three data structures (BST, Linked Lists, Queues). Explain how they work together. Be creative!",
|
| 55 |
"topics_involved": "Binary Search Trees, Linked Lists, Queues",
|
| 56 |
"hints": [
|
| 57 |
+
"Think of a system with searching, ordering, and waiting - like a restaurant management system or task scheduler",
|
| 58 |
+
"Example: Restaurant app with BST for menu items (fast lookup by price/name), Queue for order processing, Linked List for order history",
|
| 59 |
+
"Draw a diagram showing how data flows between the three structures in your system"
|
| 60 |
]
|
| 61 |
}
|
| 62 |
]
|
| 63 |
|
| 64 |
# ============================================
|
| 65 |
+
# 📊 GOOGLE SHEETS FUNCTIONS
|
| 66 |
# ============================================
|
| 67 |
|
| 68 |
+
def read_sheet_safe(url, sheet_name="Unknown"):
|
| 69 |
+
try:
|
| 70 |
+
df = pd.read_csv(url)
|
| 71 |
+
print(f"✅ Loaded {len(df)} rows from {sheet_name}")
|
| 72 |
+
return df
|
| 73 |
+
except Exception as e:
|
| 74 |
+
print(f"⚠️ Could not read {sheet_name}: {e}")
|
| 75 |
+
return pd.DataFrame()
|
| 76 |
|
| 77 |
+
def get_students_in_group(group_id):
|
| 78 |
+
try:
|
| 79 |
+
groups_df = read_sheet_safe(GROUPS_URL, "Groups")
|
| 80 |
+
|
| 81 |
+
if groups_df.empty:
|
| 82 |
+
return [
|
| 83 |
+
{"name": "Omar Khalil", "topic": "Linked Lists"},
|
| 84 |
+
{"name": "Khaled Ibrahim", "topic": "Queues"},
|
| 85 |
+
{"name": "Layla Mahmoud", "topic": "Binary Search Trees"}
|
| 86 |
+
]
|
| 87 |
+
|
| 88 |
+
group = groups_df[groups_df['group_id'] == group_id]
|
| 89 |
+
|
| 90 |
+
if group.empty:
|
| 91 |
+
return [
|
| 92 |
+
{"name": "Omar Khalil", "topic": "Linked Lists"},
|
| 93 |
+
{"name": "Khaled Ibrahim", "topic": "Queues"},
|
| 94 |
+
{"name": "Layla Mahmoud", "topic": "Binary Search Trees"}
|
| 95 |
+
]
|
| 96 |
+
|
| 97 |
+
group_row = group.iloc[0]
|
| 98 |
+
member_names = str(group_row.get('member_names', '')).split(',')
|
| 99 |
+
topics = str(group_row.get('topics', '')).split(',')
|
| 100 |
+
|
| 101 |
+
team_members = []
|
| 102 |
+
for i, name in enumerate(member_names):
|
| 103 |
+
name = name.strip()
|
| 104 |
+
topic = topics[i].strip() if i < len(topics) else "Data Structures"
|
| 105 |
+
if name:
|
| 106 |
+
team_members.append({"name": name, "topic": topic})
|
| 107 |
+
|
| 108 |
+
if team_members:
|
| 109 |
+
return team_members
|
| 110 |
+
else:
|
| 111 |
+
return [
|
| 112 |
+
{"name": "Omar Khalil", "topic": "Linked Lists"},
|
| 113 |
+
{"name": "Khaled Ibrahim", "topic": "Queues"},
|
| 114 |
+
{"name": "Layla Mahmoud", "topic": "Binary Search Trees"}
|
| 115 |
+
]
|
| 116 |
+
|
| 117 |
+
except Exception as e:
|
| 118 |
+
print(f"❌ Error: {e}")
|
| 119 |
+
return [
|
| 120 |
+
{"name": "Omar Khalil", "topic": "Linked Lists"},
|
| 121 |
+
{"name": "Khaled Ibrahim", "topic": "Queues"},
|
| 122 |
+
{"name": "Layla Mahmoud", "topic": "Binary Search Trees"}
|
| 123 |
+
]
|
| 124 |
+
|
| 125 |
+
def get_challenges_for_session(session_id):
|
| 126 |
+
try:
|
| 127 |
+
challenges_df = read_sheet_safe(CHALLENGES_URL, "Challenges")
|
| 128 |
+
|
| 129 |
+
if challenges_df.empty:
|
| 130 |
+
return MOCK_CHALLENGES
|
| 131 |
+
|
| 132 |
+
session_challenges = challenges_df[challenges_df['session_id'] == session_id]
|
| 133 |
+
|
| 134 |
+
if session_challenges.empty:
|
| 135 |
+
return MOCK_CHALLENGES
|
| 136 |
+
|
| 137 |
+
challenges = []
|
| 138 |
+
for _, row in session_challenges.iterrows():
|
| 139 |
+
hints_json = row.get('hints_json', '[]')
|
| 140 |
+
try:
|
| 141 |
+
hints = json.loads(hints_json) if isinstance(hints_json, str) else []
|
| 142 |
+
except:
|
| 143 |
+
hints = []
|
| 144 |
+
|
| 145 |
+
challenges.append({
|
| 146 |
+
"challenge_number": row.get('challenge_number', len(challenges) + 1),
|
| 147 |
+
"description": row.get('description', 'No description'),
|
| 148 |
+
"topics_involved": row.get('topics_involved', 'N/A'),
|
| 149 |
+
"hints": hints
|
| 150 |
+
})
|
| 151 |
+
|
| 152 |
+
if challenges:
|
| 153 |
+
return challenges
|
| 154 |
+
else:
|
| 155 |
+
return MOCK_CHALLENGES
|
| 156 |
+
|
| 157 |
+
except Exception as e:
|
| 158 |
+
return MOCK_CHALLENGES
|
| 159 |
+
|
| 160 |
+
# ============================================
|
| 161 |
+
# 🔧 APPLICATION FUNCTIONS
|
| 162 |
+
# ============================================
|
| 163 |
|
| 164 |
def lookup_student(email):
|
|
|
|
| 165 |
try:
|
| 166 |
if not email or "@" not in email:
|
| 167 |
+
return "❌ Please enter a valid email address"
|
| 168 |
|
| 169 |
+
name = email.split("@")[0].replace(".", " ").replace("_", " ").title()
|
|
|
|
|
|
|
| 170 |
|
| 171 |
+
try:
|
| 172 |
+
students_df = read_sheet_safe(STUDENTS_URL, "Students")
|
| 173 |
+
|
| 174 |
+
if not students_df.empty:
|
| 175 |
+
student = students_df[students_df['email'].str.lower() == email.lower()]
|
| 176 |
+
|
| 177 |
+
if not student.empty:
|
| 178 |
+
student_row = student.iloc[0]
|
| 179 |
+
name = student_row.get('name', name)
|
| 180 |
+
group_id = student_row.get('group_id', 'G001')
|
| 181 |
+
else:
|
| 182 |
+
group_id = "G001"
|
| 183 |
+
else:
|
| 184 |
+
group_id = "G001"
|
| 185 |
+
except:
|
| 186 |
+
group_id = "G001"
|
| 187 |
+
|
| 188 |
+
team_members = get_students_in_group(group_id)
|
| 189 |
+
session_id = f"S{datetime.utcnow().strftime('%Y%m%d%H%M%S')}"
|
| 190 |
|
| 191 |
current_student["name"] = name
|
| 192 |
current_student["email"] = email
|
| 193 |
+
current_student["group_id"] = group_id
|
| 194 |
current_student["session_id"] = session_id
|
| 195 |
+
current_student["team_members"] = team_members
|
| 196 |
+
|
| 197 |
+
welcome_msg = f"### ✅ Welcome back, {name}!\n\n"
|
| 198 |
+
welcome_msg += f"**Session ID:** `{session_id}`\n\n"
|
| 199 |
+
welcome_msg += f"**Synco says:**\n"
|
| 200 |
+
welcome_msg += f"Hey {', '.join([m['name'] for m in team_members])}! Welcome to your Synchrony study session. "
|
| 201 |
+
welcome_msg += f"You're all studying Data Structures with different focus areas—perfect for peer teaching!\n\n"
|
| 202 |
+
welcome_msg += f"**Your Team ({group_id}):** {', '.join([m['name'] for m in team_members])}\n\n"
|
| 203 |
+
welcome_msg += f"👉 Head to the **My Team** tab to see everyone's topics!"
|
|
|
|
|
|
|
|
|
|
| 204 |
|
| 205 |
return welcome_msg
|
| 206 |
|
| 207 |
except Exception as e:
|
| 208 |
+
return f"❌ Error: {str(e)}"
|
|
|
|
| 209 |
|
| 210 |
def get_team_info():
|
|
|
|
| 211 |
if not current_student.get("team_members"):
|
| 212 |
+
return "⚠️ Please login first in the Home tab"
|
| 213 |
|
| 214 |
+
output = f"## 👥 Your Study Squad - {current_student.get('group_id', 'G001')}\n\n"
|
| 215 |
+
output += f"**Session ID:** `{current_student.get('session_id', 'not started')}`\n\n"
|
| 216 |
output += "---\n\n"
|
| 217 |
|
| 218 |
for member in current_student["team_members"]:
|
| 219 |
user_name = current_student.get("name", "").lower()
|
| 220 |
member_name = member.get("name", "").lower()
|
| 221 |
+
is_you = " **(You)**" if user_name in member_name or member_name in user_name else ""
|
| 222 |
output += f"### 🎓 {member['name']}{is_you}\n"
|
| 223 |
+
output += f"**Focus Area:** {member.get('topic', 'N/A')}\n\n"
|
| 224 |
|
| 225 |
+
output += "---\n\n💡 Collaborate, learn together, and grow!"
|
| 226 |
return output
|
| 227 |
|
|
|
|
| 228 |
def load_challenges():
|
|
|
|
| 229 |
if not current_student.get("session_id"):
|
| 230 |
+
return "⚠️ No active session. Please login first in the Home tab"
|
|
|
|
|
|
|
| 231 |
|
| 232 |
+
try:
|
| 233 |
+
session_id = current_student["session_id"]
|
| 234 |
+
challenges = get_challenges_for_session(session_id)
|
| 235 |
+
current_student["challenges"] = challenges
|
| 236 |
+
|
| 237 |
+
output = f"# 🎯 Your Collaborative Challenges\n\n"
|
| 238 |
+
output += f"**Synco says:** Here are your 3 collaborative challenges! Work together, discuss your approaches, "
|
| 239 |
+
output += f"and request hints when needed. Ready? Let's go!\n\n"
|
|
|
|
|
|
|
|
|
|
| 240 |
output += "---\n\n"
|
| 241 |
+
|
| 242 |
+
for challenge in challenges:
|
| 243 |
+
output += f"## Challenge {challenge['challenge_number']}\n\n"
|
| 244 |
+
output += f"{challenge['description']}\n\n"
|
| 245 |
+
output += f"**📚 Topics:** {challenge['topics_involved']}\n\n"
|
| 246 |
+
output += f"💡 *Need help? Request a hint in the Hints tab!*\n\n"
|
| 247 |
+
output += "---\n\n"
|
| 248 |
+
|
| 249 |
+
return output
|
| 250 |
+
|
| 251 |
+
except Exception as e:
|
| 252 |
+
return f"❌ Error loading challenges: {str(e)}"
|
| 253 |
|
| 254 |
def request_hint(challenge_num, hint_level_text):
|
|
|
|
| 255 |
if not current_student.get("session_id"):
|
| 256 |
+
return "⚠️ No active session. Please login first"
|
| 257 |
|
| 258 |
if not current_student.get("challenges"):
|
| 259 |
+
return "⚠️ Please load challenges first in the Challenges tab"
|
| 260 |
|
| 261 |
try:
|
| 262 |
hint_level = int(hint_level_text.split(" - ")[0])
|
| 263 |
challenge_idx = int(challenge_num) - 1
|
| 264 |
|
| 265 |
+
if challenge_idx < 0 or challenge_idx >= len(current_student["challenges"]):
|
| 266 |
+
return "⚠️ Invalid challenge number"
|
| 267 |
+
|
| 268 |
+
challenge = current_student["challenges"][challenge_idx]
|
| 269 |
+
hints = challenge.get("hints", [])
|
| 270 |
|
| 271 |
+
if hint_level < 1 or hint_level > len(hints):
|
| 272 |
+
return f"⚠️ Only {len(hints)} hints available"
|
| 273 |
|
| 274 |
+
hint = hints[hint_level - 1]
|
|
|
|
| 275 |
|
|
|
|
| 276 |
if hint_level == 1:
|
| 277 |
+
encouragement = "💡 **Think about it a bit more**"
|
| 278 |
elif hint_level == 2:
|
| 279 |
+
encouragement = "💭 **You're getting closer**"
|
| 280 |
else:
|
| 281 |
+
encouragement = "🎯 **Almost there**"
|
| 282 |
|
| 283 |
+
output = f"## {encouragement}\n\n"
|
| 284 |
+
output += f"**Challenge {challenge_num}** • Hint Level {hint_level}\n\n"
|
| 285 |
output += "---\n\n"
|
| 286 |
output += f"{hint}\n\n"
|
| 287 |
output += "---\n\n"
|
| 288 |
+
output += f"💪 You got this! Keep going!"
|
| 289 |
|
| 290 |
return output
|
| 291 |
|
| 292 |
except Exception as e:
|
| 293 |
+
return f"❌ Error: {str(e)}"
|
|
|
|
| 294 |
|
| 295 |
def send_message(message, history):
|
|
|
|
| 296 |
if not message.strip():
|
| 297 |
return history, ""
|
| 298 |
|
| 299 |
history.append({
|
| 300 |
"role": "user",
|
| 301 |
+
"content": f"**{current_student.get('name', 'You')}:** {message}"
|
| 302 |
})
|
| 303 |
|
| 304 |
if not current_student.get("team_members"):
|
| 305 |
+
response = "**Synco:** Please login first to chat with your team"
|
| 306 |
else:
|
| 307 |
other_members = [m for m in current_student["team_members"]
|
| 308 |
if m['name'].lower() != current_student.get('name', '').lower()]
|
|
|
|
| 310 |
if other_members:
|
| 311 |
member = random.choice(other_members)
|
| 312 |
responses = [
|
| 313 |
+
f"**{member['name']}:** That's a great point about {member['topic']}!",
|
| 314 |
+
f"**{member['name']}:** Interesting! Let me explain how {member['topic']} relates to that...",
|
| 315 |
+
f"**{member['name']}:** I agree! In {member['topic']}, we approach it similarly.",
|
| 316 |
+
f"**{member['name']}:** Good question! Let me draw a quick diagram...",
|
| 317 |
+
f"**Synco:** Excellent collaboration! Keep discussing!"
|
|
|
|
| 318 |
]
|
| 319 |
response = random.choice(responses)
|
| 320 |
else:
|
| 321 |
+
response = "**Synco:** Great thinking!"
|
| 322 |
|
| 323 |
history.append({"role": "assistant", "content": response})
|
| 324 |
return history, ""
|
| 325 |
|
|
|
|
| 326 |
# ============================================
|
| 327 |
+
# 🎨 PROFESSIONAL LIGHT MODE CSS
|
| 328 |
# ============================================
|
| 329 |
|
| 330 |
custom_css = """
|
| 331 |
+
/* Professional Light Mode - Soft Academic Aesthetic */
|
| 332 |
+
|
| 333 |
.gradio-container {
|
| 334 |
+
background: linear-gradient(135deg, #FDFBF7 0%, #F8F6F3 100%) !important;
|
| 335 |
+
font-family: 'Inter', 'SF Pro Display', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif !important;
|
| 336 |
+
color: #1F2937 !important;
|
| 337 |
}
|
| 338 |
|
| 339 |
+
/* Color Variables */
|
| 340 |
:root {
|
| 341 |
+
--primary: #7C3AED;
|
| 342 |
+
--primary-light: #A78BFA;
|
| 343 |
+
--accent: #F97316;
|
| 344 |
+
--success: #10B981;
|
| 345 |
+
--bg-cream: #FDFBF7;
|
| 346 |
+
--bg-light: #FFFFFF;
|
| 347 |
+
--text-dark: #1F2937;
|
| 348 |
+
--text-gray: #6B7280;
|
| 349 |
+
--border-light: #E5E7EB;
|
| 350 |
+
}
|
| 351 |
+
|
| 352 |
+
/* Main Container */
|
| 353 |
+
.contain {
|
| 354 |
+
background: var(--bg-cream) !important;
|
| 355 |
+
max-width: 1200px !important;
|
| 356 |
+
margin: 0 auto !important;
|
| 357 |
+
padding: 20px !important;
|
| 358 |
+
}
|
| 359 |
+
|
| 360 |
+
/* Tabs */
|
| 361 |
+
.tab-nav {
|
| 362 |
+
background: var(--bg-light) !important;
|
| 363 |
+
border-bottom: 2px solid var(--border-light) !important;
|
| 364 |
+
padding: 8px 0 !important;
|
| 365 |
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05) !important;
|
| 366 |
}
|
| 367 |
|
| 368 |
.tab-nav button {
|
| 369 |
+
background: transparent !important;
|
| 370 |
+
border: none !important;
|
| 371 |
+
color: var(--text-gray) !important;
|
| 372 |
+
font-weight: 600 !important;
|
| 373 |
+
font-size: 15px !important;
|
| 374 |
+
padding: 12px 24px !important;
|
| 375 |
+
margin: 0 4px !important;
|
| 376 |
+
border-radius: 8px !important;
|
| 377 |
+
transition: all 0.2s ease !important;
|
| 378 |
}
|
| 379 |
|
| 380 |
.tab-nav button:hover {
|
| 381 |
+
background: rgba(124, 58, 237, 0.08) !important;
|
| 382 |
+
color: var(--primary) !important;
|
|
|
|
| 383 |
}
|
| 384 |
|
| 385 |
.tab-nav button.selected {
|
| 386 |
+
background: var(--primary) !important;
|
| 387 |
+
color: white !important;
|
| 388 |
+
box-shadow: 0 2px 8px rgba(124, 58, 237, 0.25) !important;
|
| 389 |
}
|
| 390 |
|
| 391 |
+
/* Input Fields */
|
| 392 |
+
input, textarea, select {
|
| 393 |
+
background: var(--bg-light) !important;
|
| 394 |
+
border: 2px solid var(--border-light) !important;
|
| 395 |
+
color: var(--text-dark) !important;
|
| 396 |
border-radius: 10px !important;
|
| 397 |
+
font-size: 15px !important;
|
| 398 |
+
padding: 12px 16px !important;
|
| 399 |
+
transition: all 0.2s ease !important;
|
| 400 |
}
|
| 401 |
|
| 402 |
+
input:focus, textarea:focus, select:focus {
|
| 403 |
+
border-color: var(--primary) !important;
|
| 404 |
+
box-shadow: 0 0 0 3px rgba(124, 58, 237, 0.1) !important;
|
| 405 |
+
outline: none !important;
|
| 406 |
}
|
| 407 |
|
| 408 |
+
input::placeholder, textarea::placeholder {
|
| 409 |
+
color: #9CA3AF !important;
|
| 410 |
+
}
|
| 411 |
+
|
| 412 |
+
/* Buttons */
|
| 413 |
button {
|
| 414 |
+
background: var(--primary) !important;
|
| 415 |
border: none !important;
|
| 416 |
+
color: white !important;
|
| 417 |
+
font-weight: 600 !important;
|
| 418 |
+
font-size: 15px !important;
|
| 419 |
border-radius: 10px !important;
|
| 420 |
+
padding: 12px 24px !important;
|
| 421 |
+
transition: all 0.2s ease !important;
|
| 422 |
+
box-shadow: 0 2px 8px rgba(124, 58, 237, 0.2) !important;
|
| 423 |
}
|
| 424 |
|
| 425 |
button:hover {
|
| 426 |
+
background: #6D28D9 !important;
|
| 427 |
+
transform: translateY(-1px) !important;
|
| 428 |
+
box-shadow: 0 4px 12px rgba(124, 58, 237, 0.3) !important;
|
| 429 |
+
}
|
| 430 |
+
|
| 431 |
+
button:active {
|
| 432 |
+
transform: translateY(0) !important;
|
| 433 |
+
}
|
| 434 |
+
|
| 435 |
+
button.secondary {
|
| 436 |
+
background: var(--accent) !important;
|
| 437 |
+
box-shadow: 0 2px 8px rgba(249, 115, 22, 0.2) !important;
|
| 438 |
+
}
|
| 439 |
+
|
| 440 |
+
button.secondary:hover {
|
| 441 |
+
background: #EA580C !important;
|
| 442 |
}
|
| 443 |
|
| 444 |
+
/* Output/Markdown Boxes */
|
| 445 |
+
.output-markdown, .prose, .markdown {
|
| 446 |
+
background: var(--bg-light) !important;
|
| 447 |
+
border: 2px solid var(--border-light) !important;
|
| 448 |
border-radius: 12px !important;
|
| 449 |
padding: 24px !important;
|
| 450 |
+
color: var(--text-dark) !important;
|
| 451 |
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05) !important;
|
| 452 |
+
line-height: 1.7 !important;
|
| 453 |
}
|
| 454 |
|
| 455 |
+
/* Headers */
|
| 456 |
h1 {
|
| 457 |
+
color: var(--primary) !important;
|
| 458 |
+
font-weight: 800 !important;
|
| 459 |
+
font-size: 42px !important;
|
|
|
|
|
|
|
| 460 |
text-align: center !important;
|
| 461 |
+
margin: 24px 0 !important;
|
| 462 |
+
letter-spacing: -0.5px !important;
|
| 463 |
}
|
| 464 |
|
| 465 |
h2 {
|
| 466 |
+
color: var(--primary) !important;
|
| 467 |
+
font-weight: 700 !important;
|
| 468 |
+
font-size: 28px !important;
|
| 469 |
+
margin: 20px 0 12px 0 !important;
|
| 470 |
}
|
| 471 |
|
| 472 |
h3 {
|
| 473 |
+
color: var(--accent) !important;
|
| 474 |
+
font-weight: 600 !important;
|
| 475 |
+
font-size: 20px !important;
|
| 476 |
+
margin: 16px 0 8px 0 !important;
|
| 477 |
+
}
|
| 478 |
+
|
| 479 |
+
/* Paragraphs */
|
| 480 |
+
p {
|
| 481 |
+
color: var(--text-dark) !important;
|
| 482 |
+
font-size: 15px !important;
|
| 483 |
+
line-height: 1.7 !important;
|
| 484 |
+
margin: 12px 0 !important;
|
| 485 |
+
}
|
| 486 |
+
|
| 487 |
+
/* Strong/Bold */
|
| 488 |
+
strong {
|
| 489 |
+
color: var(--primary) !important;
|
| 490 |
font-weight: 700 !important;
|
| 491 |
}
|
| 492 |
|
| 493 |
+
/* Links */
|
| 494 |
+
a {
|
| 495 |
+
color: var(--primary) !important;
|
| 496 |
+
text-decoration: none !important;
|
| 497 |
+
font-weight: 600 !important;
|
| 498 |
+
transition: color 0.2s ease !important;
|
| 499 |
+
}
|
| 500 |
+
|
| 501 |
+
a:hover {
|
| 502 |
+
color: var(--accent) !important;
|
| 503 |
+
text-decoration: underline !important;
|
| 504 |
+
}
|
| 505 |
+
|
| 506 |
+
/* Code blocks */
|
| 507 |
code {
|
| 508 |
+
background: #F3F4F6 !important;
|
| 509 |
+
color: var(--primary) !important;
|
| 510 |
+
padding: 3px 8px !important;
|
| 511 |
border-radius: 6px !important;
|
| 512 |
+
border: 1px solid var(--border-light) !important;
|
| 513 |
+
font-family: 'SF Mono', 'Monaco', 'Consolas', monospace !important;
|
| 514 |
+
font-size: 14px !important;
|
| 515 |
+
}
|
| 516 |
+
|
| 517 |
+
/* Horizontal Rules */
|
| 518 |
+
hr {
|
| 519 |
+
border: none !important;
|
| 520 |
+
border-top: 2px solid var(--border-light) !important;
|
| 521 |
+
margin: 24px 0 !important;
|
| 522 |
}
|
| 523 |
|
| 524 |
+
/* Scrollbar */
|
| 525 |
::-webkit-scrollbar {
|
| 526 |
+
width: 10px;
|
| 527 |
+
height: 10px;
|
| 528 |
}
|
| 529 |
|
| 530 |
::-webkit-scrollbar-track {
|
| 531 |
+
background: #F3F4F6;
|
| 532 |
+
border-radius: 10px;
|
| 533 |
}
|
| 534 |
|
| 535 |
::-webkit-scrollbar-thumb {
|
| 536 |
+
background: var(--primary-light);
|
| 537 |
border-radius: 10px;
|
| 538 |
+
border: 2px solid #F3F4F6;
|
| 539 |
+
}
|
| 540 |
+
|
| 541 |
+
::-webkit-scrollbar-thumb:hover {
|
| 542 |
+
background: var(--primary);
|
| 543 |
+
}
|
| 544 |
+
|
| 545 |
+
/* Labels */
|
| 546 |
+
label {
|
| 547 |
+
color: var(--text-dark) !important;
|
| 548 |
+
font-weight: 600 !important;
|
| 549 |
+
font-size: 14px !important;
|
| 550 |
+
margin-bottom: 6px !important;
|
| 551 |
+
}
|
| 552 |
+
|
| 553 |
+
/* Chatbot Messages */
|
| 554 |
+
.message-wrap {
|
| 555 |
+
background: var(--bg-light) !important;
|
| 556 |
+
border: 1px solid var(--border-light) !important;
|
| 557 |
+
border-radius: 12px !important;
|
| 558 |
+
margin: 8px 0 !important;
|
| 559 |
+
padding: 12px !important;
|
| 560 |
+
}
|
| 561 |
+
|
| 562 |
+
.message.user {
|
| 563 |
+
background: var(--primary) !important;
|
| 564 |
+
color: white !important;
|
| 565 |
+
border-radius: 12px !important;
|
| 566 |
+
padding: 12px 16px !important;
|
| 567 |
+
margin-left: 20% !important;
|
| 568 |
+
}
|
| 569 |
+
|
| 570 |
+
.message.bot {
|
| 571 |
+
background: #F3F4F6 !important;
|
| 572 |
+
color: var(--text-dark) !important;
|
| 573 |
+
border-radius: 12px !important;
|
| 574 |
+
padding: 12px 16px !important;
|
| 575 |
+
margin-right: 20% !important;
|
| 576 |
+
}
|
| 577 |
+
|
| 578 |
+
/* Dropdown */
|
| 579 |
+
.dropdown-container {
|
| 580 |
+
background: var(--bg-light) !important;
|
| 581 |
+
border: 2px solid var(--border-light) !important;
|
| 582 |
+
border-radius: 10px !important;
|
| 583 |
+
}
|
| 584 |
+
|
| 585 |
+
/* Lists */
|
| 586 |
+
ul, ol {
|
| 587 |
+
color: var(--text-dark) !important;
|
| 588 |
+
padding-left: 24px !important;
|
| 589 |
+
}
|
| 590 |
+
|
| 591 |
+
li {
|
| 592 |
+
margin: 8px 0 !important;
|
| 593 |
+
line-height: 1.6 !important;
|
| 594 |
+
}
|
| 595 |
+
|
| 596 |
+
/* Blockquotes */
|
| 597 |
+
blockquote {
|
| 598 |
+
border-left: 4px solid var(--primary) !important;
|
| 599 |
+
padding-left: 16px !important;
|
| 600 |
+
color: var(--text-gray) !important;
|
| 601 |
+
font-style: italic !important;
|
| 602 |
+
margin: 16px 0 !important;
|
| 603 |
+
}
|
| 604 |
+
|
| 605 |
+
/* Success Messages */
|
| 606 |
+
.success {
|
| 607 |
+
background: rgba(16, 185, 129, 0.1) !important;
|
| 608 |
+
border-left: 4px solid var(--success) !important;
|
| 609 |
+
padding: 16px !important;
|
| 610 |
+
border-radius: 8px !important;
|
| 611 |
+
color: var(--text-dark) !important;
|
| 612 |
+
}
|
| 613 |
+
|
| 614 |
+
/* Warning Messages */
|
| 615 |
+
.warning {
|
| 616 |
+
background: rgba(249, 115, 22, 0.1) !important;
|
| 617 |
+
border-left: 4px solid var(--accent) !important;
|
| 618 |
+
padding: 16px !important;
|
| 619 |
+
border-radius: 8px !important;
|
| 620 |
+
color: var(--text-dark) !important;
|
| 621 |
+
}
|
| 622 |
+
|
| 623 |
+
/* Cards/Panels */
|
| 624 |
+
.panel {
|
| 625 |
+
background: var(--bg-light) !important;
|
| 626 |
+
border: 2px solid var(--border-light) !important;
|
| 627 |
+
border-radius: 12px !important;
|
| 628 |
+
padding: 20px !important;
|
| 629 |
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04) !important;
|
| 630 |
+
}
|
| 631 |
+
|
| 632 |
+
/* Emoji styling */
|
| 633 |
+
.emoji {
|
| 634 |
+
font-size: 1.2em !important;
|
| 635 |
}
|
| 636 |
"""
|
| 637 |
|
|
|
|
| 639 |
# 🎭 GRADIO INTERFACE
|
| 640 |
# ============================================
|
| 641 |
|
| 642 |
+
demo = gr.Blocks(css=custom_css, title="Synchrony • Collaborative Learning", theme=gr.themes.Soft())
|
| 643 |
|
| 644 |
with demo:
|
| 645 |
gr.Markdown("""
|
| 646 |
+
# 🌟 Synchrony
|
| 647 |
+
## Where students teach students
|
| 648 |
|
| 649 |
+
*AI-powered collaborative learning platform*
|
| 650 |
|
| 651 |
---
|
| 652 |
""")
|
| 653 |
|
| 654 |
+
with gr.Tab("🏠 Home"):
|
|
|
|
| 655 |
gr.Markdown("""
|
| 656 |
+
## Welcome to Synchrony
|
| 657 |
|
| 658 |
+
Join a community of students learning together through peer teaching.
|
| 659 |
""")
|
| 660 |
|
| 661 |
with gr.Column():
|
| 662 |
+
gr.Markdown("### 📝 New Here?")
|
| 663 |
+
gr.Markdown("Register to get matched with your perfect study group")
|
| 664 |
+
register_btn = gr.Button("Register Now", variant="primary", size="lg")
|
| 665 |
|
| 666 |
+
gr.Markdown("### 🔐 Already Registered?")
|
| 667 |
+
gr.Markdown("Enter your university email to access your study session")
|
| 668 |
email_input = gr.Textbox(
|
| 669 |
+
label="University Email",
|
| 670 |
placeholder="your.email@university.edu"
|
| 671 |
)
|
| 672 |
+
login_btn = gr.Button("Login", variant="secondary", size="lg")
|
| 673 |
login_output = gr.Markdown()
|
| 674 |
|
| 675 |
register_btn.click(
|
|
|
|
| 687 |
outputs=[login_output]
|
| 688 |
)
|
| 689 |
|
| 690 |
+
with gr.Tab("👥 My Team"):
|
| 691 |
+
gr.Markdown("## Your Study Squad")
|
| 692 |
+
gr.Markdown("Meet your teammates and see what everyone's focusing on")
|
| 693 |
+
team_display = gr.Markdown(value="⚠️ Please login first in the Home tab")
|
| 694 |
+
refresh_btn = gr.Button("Refresh Team Info", variant="secondary")
|
| 695 |
+
refresh_btn.click(fn=get_team_info, outputs=[team_display])
|
| 696 |
|
| 697 |
+
with gr.Tab("🎯 Challenges"):
|
|
|
|
| 698 |
gr.Markdown("""
|
| 699 |
+
## Collaborative Challenges
|
| 700 |
|
| 701 |
+
Work together on AI-generated challenges designed for your group's topics
|
| 702 |
""")
|
| 703 |
+
load_btn = gr.Button("Load Challenges", variant="primary", size="lg")
|
| 704 |
+
challenges_display = gr.Markdown(value="Click the button above to load your challenges")
|
| 705 |
+
load_btn.click(fn=load_challenges, outputs=[challenges_display])
|
| 706 |
|
| 707 |
+
with gr.Tab("💡 Hints"):
|
|
|
|
| 708 |
gr.Markdown("""
|
| 709 |
+
## Adaptive Hints
|
|
|
|
|
|
|
| 710 |
|
| 711 |
+
Get progressive hints when you're stuck—from gentle nudges to detailed guidance
|
|
|
|
|
|
|
| 712 |
""")
|
| 713 |
|
| 714 |
with gr.Row():
|
| 715 |
challenge_select = gr.Dropdown(
|
| 716 |
+
label="Select Challenge",
|
| 717 |
choices=["1", "2", "3"],
|
| 718 |
value="1"
|
| 719 |
)
|
| 720 |
hint_level_select = gr.Dropdown(
|
| 721 |
+
label="Hint Level",
|
| 722 |
choices=[
|
| 723 |
+
"1 - Gentle nudge",
|
| 724 |
+
"2 - Clearer guidance",
|
| 725 |
+
"3 - Almost there"
|
| 726 |
],
|
| 727 |
+
value="1 - Gentle nudge"
|
| 728 |
)
|
| 729 |
|
| 730 |
+
hint_btn = gr.Button("Get Hint", variant="primary", size="lg")
|
| 731 |
+
hint_display = gr.Markdown(value="Select a challenge and hint level, then click the button")
|
| 732 |
+
hint_btn.click(
|
| 733 |
fn=request_hint,
|
| 734 |
inputs=[challenge_select, hint_level_select],
|
| 735 |
outputs=[hint_display]
|
| 736 |
)
|
| 737 |
|
| 738 |
+
with gr.Tab("💬 Chat"):
|
|
|
|
| 739 |
gr.Markdown("""
|
| 740 |
+
## Team Chat
|
| 741 |
|
| 742 |
+
Discuss challenges, share insights, and help each other learn
|
| 743 |
|
| 744 |
+
*Demo mode - responses are simulated*
|
| 745 |
""")
|
| 746 |
|
| 747 |
chatbot = gr.Chatbot(
|
| 748 |
value=[],
|
| 749 |
label="",
|
| 750 |
+
height=450,
|
| 751 |
type="messages"
|
| 752 |
)
|
| 753 |
|
| 754 |
with gr.Row():
|
| 755 |
msg_input = gr.Textbox(
|
| 756 |
label="",
|
| 757 |
+
placeholder="Type your message...",
|
| 758 |
scale=5
|
| 759 |
)
|
| 760 |
+
send_btn = gr.Button("Send", variant="primary", scale=1)
|
| 761 |
|
| 762 |
send_btn.click(
|
| 763 |
fn=send_message,
|
|
|
|
| 770 |
outputs=[chatbot, msg_input]
|
| 771 |
)
|
| 772 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 773 |
gr.Markdown("""
|
| 774 |
---
|
| 775 |
|
| 776 |
+
### Synchrony • Collaborative Learning Platform
|
| 777 |
|
| 778 |
+
*Powered by AI • Built for Students • Designed for Impact*
|
| 779 |
+
|
| 780 |
+
Created by Maryam Shanabli • 2025
|
| 781 |
""")
|
| 782 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 783 |
print("\n" + "="*60)
|
| 784 |
+
print("🌟 SYNCHRONY - Professional Light Mode")
|
| 785 |
print("="*60)
|
| 786 |
+
print("✅ Soft academic aesthetic")
|
| 787 |
+
print("✅ Optimal readability")
|
| 788 |
print("✅ All features functional")
|
|
|
|
| 789 |
print("="*60 + "\n")
|
| 790 |
|
| 791 |
demo.launch()
|