import gradio as gr import pandas as pd import random import json from datetime import datetime # ============================================ # 🌟 SYNCHRONY - DARK ACADEMIA # Oxford Libraries β€’ Scholarly Elegance # ============================================ REGISTRATION_FORM = "https://docs.google.com/forms/d/e/1FAIpQLSfJFGx-yd0FPIuRYLUJut3BOOiQ14x_5DYheWpgrUqcHdQaCA/viewform" GOOGLE_SHEETS = "https://docs.google.com/spreadsheets/d/1rpR-E_RSooDkNDh-Q_1BDIeGw4vfgmusNAfwPqATaW4/edit?usp=sharing" SHEET_ID = "1rpR-E_RSooDkNDh-Q_1BDIeGw4vfgmusNAfwPqATaW4" STUDENTS_URL = f"https://docs.google.com/spreadsheets/d/{SHEET_ID}/gviz/tq?tqx=out:csv&sheet=Students" GROUPS_URL = f"https://docs.google.com/spreadsheets/d/{SHEET_ID}/gviz/tq?tqx=out:csv&sheet=Groups" CHALLENGES_URL = f"https://docs.google.com/spreadsheets/d/{SHEET_ID}/gviz/tq?tqx=out:csv&sheet=Challenges" current_student = { "name": None, "email": None, "group_id": None, "session_id": None, "team_members": [], "challenges": [] } MOCK_CHALLENGES = [ { "challenge_number": 1, "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?", "topics_involved": "Binary Search Trees, Linked Lists, Queues", "hints": [ "Start by drawing a simple diagram showing how nodes are organized in a BST with the property: left < parent < right", "Think about the left-subtree and right-subtree properties - what makes a BST special compared to a regular tree?", "Compare time complexity: BST search is O(log n) average case, while linked list search is O(n)" ] }, { "challenge_number": 2, "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.", "topics_involved": "Linked Lists, Binary Search Trees, Queues", "hints": [ "Think about how nodes point to each other - singly linked has 'next' pointer, doubly linked has 'next' and 'prev'", "Consider real-world examples: browser history (doubly linked), music playlist (singly linked), undo/redo (doubly linked)", "Both BST nodes and queue nodes use pointers/references just like linked lists - they're all node-based structures!" ] }, { "challenge_number": 3, "description": "Everyone: Design ONE real-world system that uses ALL three data structures (BST, Linked Lists, Queues). Explain how they work together. Be creative!", "topics_involved": "Binary Search Trees, Linked Lists, Queues", "hints": [ "Think of a system with searching, ordering, and waiting - like a restaurant management system or task scheduler", "Example: Restaurant app with BST for menu items (fast lookup by price/name), Queue for order processing, Linked List for order history", "Draw a diagram showing how data flows between the three structures in your system" ] } ] def read_sheet_safe(url, sheet_name="Unknown"): try: df = pd.read_csv(url) print(f"βœ… Loaded {len(df)} rows from {sheet_name}") return df except Exception as e: print(f"⚠️ Could not read {sheet_name}: {e}") return pd.DataFrame() def get_students_in_group(group_id): try: groups_df = read_sheet_safe(GROUPS_URL, "Groups") if groups_df.empty: return [ {"name": "Omar Khalil", "topic": "Linked Lists"}, {"name": "Khaled Ibrahim", "topic": "Queues"}, {"name": "Layla Mahmoud", "topic": "Binary Search Trees"} ] group = groups_df[groups_df['group_id'] == group_id] if group.empty: return [ {"name": "Omar Khalil", "topic": "Linked Lists"}, {"name": "Khaled Ibrahim", "topic": "Queues"}, {"name": "Layla Mahmoud", "topic": "Binary Search Trees"} ] group_row = group.iloc[0] member_names = str(group_row.get('member_names', '')).split(',') topics = str(group_row.get('topics', '')).split(',') team_members = [] for i, name in enumerate(member_names): name = name.strip() topic = topics[i].strip() if i < len(topics) else "Data Structures" if name: team_members.append({"name": name, "topic": topic}) if team_members: return team_members else: return [ {"name": "Omar Khalil", "topic": "Linked Lists"}, {"name": "Khaled Ibrahim", "topic": "Queues"}, {"name": "Layla Mahmoud", "topic": "Binary Search Trees"} ] except Exception as e: return [ {"name": "Omar Khalil", "topic": "Linked Lists"}, {"name": "Khaled Ibrahim", "topic": "Queues"}, {"name": "Layla Mahmoud", "topic": "Binary Search Trees"} ] def get_challenges_for_session(session_id): try: challenges_df = read_sheet_safe(CHALLENGES_URL, "Challenges") if challenges_df.empty: return MOCK_CHALLENGES session_challenges = challenges_df[challenges_df['session_id'] == session_id] if session_challenges.empty: return MOCK_CHALLENGES challenges = [] for _, row in session_challenges.iterrows(): hints_json = row.get('hints_json', '[]') try: hints = json.loads(hints_json) if isinstance(hints_json, str) else [] except: hints = [] challenges.append({ "challenge_number": row.get('challenge_number', len(challenges) + 1), "description": row.get('description', 'No description'), "topics_involved": row.get('topics_involved', 'N/A'), "hints": hints }) if challenges: return challenges else: return MOCK_CHALLENGES except Exception as e: return MOCK_CHALLENGES def lookup_student(email): try: if not email or "@" not in email: return "❌ Please enter a valid email address" name = email.split("@")[0].replace(".", " ").replace("_", " ").title() try: students_df = read_sheet_safe(STUDENTS_URL, "Students") if not students_df.empty: student = students_df[students_df['email'].str.lower() == email.lower()] if not student.empty: student_row = student.iloc[0] name = student_row.get('name', name) group_id = student_row.get('group_id', 'G001') else: group_id = "G001" else: group_id = "G001" except: group_id = "G001" team_members = get_students_in_group(group_id) session_id = f"S{datetime.utcnow().strftime('%Y%m%d%H%M%S')}" current_student["name"] = name current_student["email"] = email current_student["group_id"] = group_id current_student["session_id"] = session_id current_student["team_members"] = team_members welcome_msg = f"### βœ… Welcome back, {name}!\n\n" welcome_msg += f"**Session ID:** `{session_id}`\n\n" welcome_msg += f"**Synco says:**\n" welcome_msg += f"Hey {', '.join([m['name'] for m in team_members])}! Welcome to your Synchrony study session. " welcome_msg += f"You're all studying Data Structures with different focus areasβ€”perfect for peer teaching!\n\n" welcome_msg += f"**Your Team ({group_id}):** {', '.join([m['name'] for m in team_members])}\n\n" welcome_msg += f"πŸ‘‰ Head to the **My Team** tab to see everyone's topics!" return welcome_msg except Exception as e: return f"❌ Error: {str(e)}" def get_team_info(): if not current_student.get("team_members"): return "⚠️ Please login first in the Home tab" output = f"## πŸ‘₯ Your Study Squad - {current_student.get('group_id', 'G001')}\n\n" output += f"**Session ID:** `{current_student.get('session_id', 'not started')}`\n\n" output += "---\n\n" for member in current_student["team_members"]: user_name = current_student.get("name", "").lower() member_name = member.get("name", "").lower() is_you = " **(You)**" if user_name in member_name or member_name in user_name else "" output += f"### πŸŽ“ {member['name']}{is_you}\n" output += f"**Focus Area:** {member.get('topic', 'N/A')}\n\n" output += "---\n\nπŸ’‘ Collaborate, learn together, and grow!" return output def load_challenges(): if not current_student.get("session_id"): return "⚠️ No active session. Please login first in the Home tab" try: session_id = current_student["session_id"] challenges = get_challenges_for_session(session_id) current_student["challenges"] = challenges output = f"# 🎯 Your Collaborative Challenges\n\n" output += f"**Synco says:** Here are your 3 collaborative challenges! Work together, discuss your approaches, " output += f"and request hints when needed. Ready? Let's go!\n\n" output += "---\n\n" for challenge in challenges: output += f"## Challenge {challenge['challenge_number']}\n\n" output += f"{challenge['description']}\n\n" output += f"**πŸ“š Topics:** {challenge['topics_involved']}\n\n" output += f"πŸ’‘ *Need help? Request a hint in the Hints tab!*\n\n" output += "---\n\n" return output except Exception as e: return f"❌ Error loading challenges: {str(e)}" def request_hint(challenge_num, hint_level_text): if not current_student.get("session_id"): return "⚠️ No active session. Please login first" if not current_student.get("challenges"): return "⚠️ Please load challenges first in the Challenges tab" try: hint_level = int(hint_level_text.split(" - ")[0]) challenge_idx = int(challenge_num) - 1 if challenge_idx < 0 or challenge_idx >= len(current_student["challenges"]): return "⚠️ Invalid challenge number" challenge = current_student["challenges"][challenge_idx] hints = challenge.get("hints", []) if hint_level < 1 or hint_level > len(hints): return f"⚠️ Only {len(hints)} hints available" hint = hints[hint_level - 1] if hint_level == 1: encouragement = "πŸ’‘ **Think about it a bit more**" elif hint_level == 2: encouragement = "πŸ’­ **You're getting closer**" else: encouragement = "🎯 **Almost there**" output = f"## {encouragement}\n\n" output += f"**Challenge {challenge_num}** β€’ Hint Level {hint_level}\n\n" output += "---\n\n" output += f"{hint}\n\n" output += "---\n\n" output += f"πŸ’ͺ You got this! Keep going!" return output except Exception as e: return f"❌ Error: {str(e)}" def send_message(message, history): if not message.strip(): return history, "" history.append({ "role": "user", "content": f"**{current_student.get('name', 'You')}:** {message}" }) if not current_student.get("team_members"): response = "**Synco:** Please login first to chat with your team" else: other_members = [m for m in current_student["team_members"] if m['name'].lower() != current_student.get('name', '').lower()] if other_members: member = random.choice(other_members) responses = [ f"**{member['name']}:** That's a great point about {member['topic']}!", f"**{member['name']}:** Interesting! Let me explain how {member['topic']} relates to that...", f"**{member['name']}:** I agree! In {member['topic']}, we approach it similarly.", f"**{member['name']}:** Good question! Let me draw a quick diagram...", f"**Synco:** Excellent collaboration! Keep discussing!" ] response = random.choice(responses) else: response = "**Synco:** Great thinking!" history.append({"role": "assistant", "content": response}) return history, "" # ============================================ # 🎨 DARK ACADEMIA AESTHETIC # ============================================ custom_css = """ @import url('https://fonts.googleapis.com/css2?family=Crimson+Pro:wght@400;600;700&family=Inter:wght@400;500;600&display=swap'); .gradio-container { background: linear-gradient(135deg, #F5F1E8 0%, #EBE3D5 100%) !important; color: #2B1B17 !important; min-height: 100vh !important; } * { font-family: 'Inter', serif !important; } h1, h2, h3 { font-family: 'Crimson Pro', serif !important; } :root { --burgundy: #8B2635; --forest: #2F4538; --gold: #B8860B; --parchment: #F5F1E8; --paper: #FFFEF7; --espresso: #2B1B17; --taupe: #C4B5A0; } .contain { max-width: 100% !important; width: 100% !important; padding: 24px 48px !important; margin: 0 !important; } .tab-nav { background: var(--paper) !important; border-radius: 12px !important; padding: 8px !important; margin-bottom: 24px !important; box-shadow: 0 4px 12px rgba(43, 27, 23, 0.08) !important; border: 1px solid var(--taupe) !important; } .tab-nav button { background: transparent !important; border: none !important; color: var(--forest) !important; font-weight: 600 !important; font-size: 15px !important; padding: 12px 24px !important; border-radius: 8px !important; transition: all 0.3s ease !important; } .tab-nav button:hover { background: rgba(139, 38, 53, 0.08) !important; color: var(--burgundy) !important; } .tab-nav button.selected { background: var(--burgundy) !important; color: var(--paper) !important; box-shadow: 0 2px 8px rgba(139, 38, 53, 0.3) !important; } input, textarea, select { background: var(--paper) !important; border: 2px solid var(--taupe) !important; color: var(--espresso) !important; border-radius: 8px !important; font-size: 15px !important; padding: 12px 16px !important; transition: all 0.3s ease !important; } input:focus, textarea:focus, select:focus { border-color: var(--burgundy) !important; box-shadow: 0 0 0 3px rgba(139, 38, 53, 0.1) !important; outline: none !important; } button { background: var(--burgundy) !important; border: none !important; color: var(--paper) !important; font-weight: 600 !important; font-size: 15px !important; border-radius: 8px !important; padding: 12px 24px !important; transition: all 0.3s ease !important; box-shadow: 0 3px 10px rgba(139, 38, 53, 0.2) !important; } button:hover { background: #6D1F2A !important; transform: translateY(-2px) !important; box-shadow: 0 5px 15px rgba(139, 38, 53, 0.3) !important; } button.secondary { background: var(--forest) !important; box-shadow: 0 3px 10px rgba(47, 69, 56, 0.2) !important; } button.secondary:hover { background: #1F2F28 !important; } .output-markdown, .prose, .markdown { background: var(--paper) !important; border: 1px solid var(--taupe) !important; border-radius: 12px !important; padding: 24px !important; color: var(--espresso) !important; box-shadow: 0 4px 12px rgba(43, 27, 23, 0.06) !important; line-height: 1.8 !important; } h1 { color: var(--burgundy) !important; font-weight: 700 !important; font-size: 48px !important; text-align: center !important; margin: 24px 0 !important; letter-spacing: -0.5px !important; } h2 { color: var(--burgundy) !important; font-weight: 600 !important; font-size: 32px !important; margin: 20px 0 12px 0 !important; } h3 { color: var(--forest) !important; font-weight: 600 !important; font-size: 22px !important; margin: 16px 0 8px 0 !important; } p, li { color: var(--espresso) !important; font-size: 15px !important; line-height: 1.8 !important; } strong { color: var(--burgundy) !important; font-weight: 700 !important; } a { color: var(--burgundy) !important; text-decoration: none !important; font-weight: 600 !important; border-bottom: 1px solid var(--taupe) !important; transition: all 0.2s ease !important; } a:hover { color: var(--forest) !important; border-bottom-color: var(--forest) !important; } code { background: #F0EBE3 !important; color: var(--forest) !important; padding: 3px 8px !important; border-radius: 4px !important; border: 1px solid var(--taupe) !important; font-family: 'Courier New', monospace !important; font-size: 14px !important; } hr { border: none !important; height: 1px !important; background: var(--taupe) !important; margin: 24px 0 !important; } label { color: var(--espresso) !important; font-weight: 600 !important; font-size: 14px !important; } .message.user { background: var(--burgundy) !important; color: var(--paper) !important; border-radius: 12px 12px 4px 12px !important; padding: 12px 16px !important; margin-left: 20% !important; box-shadow: 0 2px 8px rgba(139, 38, 53, 0.2) !important; } .message.bot { background: var(--paper) !important; color: var(--espresso) !important; border: 1px solid var(--taupe) !important; border-radius: 12px 12px 12px 4px !important; padding: 12px 16px !important; margin-right: 20% !important; box-shadow: 0 2px 8px rgba(43, 27, 23, 0.08) !important; } ::-webkit-scrollbar { width: 12px; } ::-webkit-scrollbar-track { background: var(--parchment); } ::-webkit-scrollbar-thumb { background: var(--burgundy); border-radius: 6px; } """ demo = gr.Blocks(css=custom_css, title="Synchrony β€’ Collaborative Learning", theme=gr.themes.Soft()) with demo: gr.Markdown(""" # 🌟 Synchrony ## because chaos is expensive. *AI-powered collaborative learning platform* --- """) with gr.Tab("🏠 Home"): with gr.Row(): with gr.Column(scale=1): gr.Markdown(""" ## Welcome to Synchrony Join a community of students learning together through peer teaching. ### πŸ“ New Here? Register to get matched with your perfect study group """) register_btn = gr.Button("Register Now", variant="primary", size="lg") with gr.Column(scale=1): gr.Markdown(""" ### πŸ” Already Registered? Enter your university email to access your study session """) email_input = gr.Textbox( label="University Email", placeholder="your.email@university.edu" ) login_btn = gr.Button("Login", variant="secondary", size="lg") login_output = gr.Markdown() register_btn.click(fn=lambda: None, js=f"() => window.open('{REGISTRATION_FORM}', '_blank')") login_btn.click(fn=lookup_student, inputs=[email_input], outputs=[login_output]) email_input.submit(fn=lookup_student, inputs=[email_input], outputs=[login_output]) with gr.Tab("πŸ‘₯ My Team"): gr.Markdown("## Your Study Squad") gr.Markdown("Meet your teammates and see what everyone's focusing on") team_display = gr.Markdown(value="⚠️ Please login first in the Home tab") refresh_btn = gr.Button("Refresh Team Info", variant="secondary") refresh_btn.click(fn=get_team_info, outputs=[team_display]) with gr.Tab("🎯 Challenges"): gr.Markdown(""" ## Collaborative Challenges Work together on AI-generated challenges designed for your group's topics """) load_btn = gr.Button("Load Challenges", variant="primary", size="lg") challenges_display = gr.Markdown(value="Click the button above to load your challenges") load_btn.click(fn=load_challenges, outputs=[challenges_display]) with gr.Tab("πŸ’‘ Hints"): gr.Markdown(""" ## Adaptive Hints Get progressive hints when you're stuckβ€”from gentle nudges to detailed guidance """) with gr.Row(): challenge_select = gr.Dropdown(label="Select Challenge", choices=["1", "2", "3"], value="1", scale=1) hint_level_select = gr.Dropdown(label="Hint Level", choices=["1 - Gentle nudge", "2 - Clearer guidance", "3 - Almost there"], value="1 - Gentle nudge", scale=2) hint_btn = gr.Button("Get Hint", variant="primary", size="lg") hint_display = gr.Markdown(value="Select a challenge and hint level, then click the button") hint_btn.click(fn=request_hint, inputs=[challenge_select, hint_level_select], outputs=[hint_display]) with gr.Tab("πŸ’¬ Chat"): gr.Markdown(""" ## Team Chat Discuss challenges, share insights, and help each other learn *Demo mode - responses are simulated* """) chatbot = gr.Chatbot(value=[], label="", height=500, type="messages") with gr.Row(): msg_input = gr.Textbox(label="", placeholder="Type your message...", scale=5) send_btn = gr.Button("Send", variant="primary", scale=1) send_btn.click(fn=send_message, inputs=[msg_input, chatbot], outputs=[chatbot, msg_input]) msg_input.submit(fn=send_message, inputs=[msg_input, chatbot], outputs=[chatbot, msg_input]) gr.Markdown(""" --- ### Synchrony β€’ Collaborative Learning Platform *Powered by AI β€’ Built for Students β€’ Designed for Impact* Created by Maryam Shanabli β€’ 2025 """) print("\n🎨 DARK ACADEMIA AESTHETIC") print("πŸ“š Oxford libraries β€’ Scholarly elegance") print("βœ… All features functional\n") demo.launch()