""" Gradio web interface for the TutorX MCP Server with SSE support """ import os import json import asyncio import gradio as gr from typing import Optional, Dict, List, Tuple import requests import networkx as nx import matplotlib import matplotlib.pyplot as plt from datetime import datetime # Set matplotlib to use 'Agg' backend to avoid GUI issues in Gradio matplotlib.use('Agg') # Import MCP client components from mcp.client.sse import sse_client from mcp.client.session import ClientSession # Server configuration # SERVER_URL = "http://localhost:8000/sse" # Ensure this is the SSE endpoint SERVER_URL = "https://tutorx-mcp.onrender.com/sse" # Utility functions async def ping_mcp_server() -> None: """Send a ping request to the MCP server""" try: async with sse_client(SERVER_URL) as (sse, write): async with ClientSession(sse, write) as session: await session.initialize() print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] Successfully pinged MCP server") except Exception as e: print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] Error pinging MCP server: {str(e)}") async def start_periodic_ping(interval_minutes: int = 10) -> None: """Start a background task to ping the MCP server periodically""" while True: await ping_mcp_server() await asyncio.sleep(interval_minutes * 60) # Store the ping task reference ping_task = None async def check_plagiarism_async(submission, reference): """Check submission for plagiarism against reference sources""" async with sse_client(SERVER_URL) as (sse, write): async with ClientSession(sse, write) as session: await session.initialize() response = await session.call_tool( "check_submission_originality", { "submission": submission, "reference_sources": [reference] if isinstance(reference, str) else reference } ) return await extract_response_content(response) def start_ping_task(): """Start the ping task when the Gradio app launches""" global ping_task try: if ping_task is None: try: loop = asyncio.get_event_loop() except RuntimeError: loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) if loop.is_running(): ping_task = loop.create_task(start_periodic_ping()) print("Started periodic ping task") else: # If loop is not running, we'll start it in a separate thread import threading def start_loop(): asyncio.set_event_loop(loop) loop.run_forever() thread = threading.Thread(target=start_loop, daemon=True) thread.start() ping_task = asyncio.run_coroutine_threadsafe(start_periodic_ping(), loop) print("Started periodic ping task in new thread") except Exception as e: print(f"Error starting ping task: {e}") # Only run this code when the module is executed directly if __name__ == "__main__" and not hasattr(gr, 'blocks'): # This ensures we don't start the task when imported by Gradio start_ping_task() async def load_concept_graph(concept_id: str = None) -> Tuple[Optional[plt.Figure], Dict, List]: """ Load and visualize the concept graph for a given concept ID. If no concept_id is provided, returns the first available concept. Args: concept_id: The ID or name of the concept to load Returns: tuple: (figure, concept_details, related_concepts) or (None, error_dict, []) """ try: async with sse_client(SERVER_URL) as (sse, write): async with ClientSession(sse, write) as session: await session.initialize() # Call the concept graph tool result = await session.call_tool( "get_concept_graph_tool", {"concept_id": concept_id} if concept_id else {} ) # Extract content if it's a TextContent object if hasattr(result, 'content') and isinstance(result.content, list): for item in result.content: if hasattr(item, 'text') and item.text: try: result = json.loads(item.text) break except json.JSONDecodeError as e: return None, {"error": f"Failed to parse JSON from TextContent: {str(e)}"}, [] # If result is a string, try to parse it as JSON if isinstance(result, str): try: result = json.loads(result) except json.JSONDecodeError as e: return None, {"error": f"Failed to parse concept graph data: {str(e)}"}, [] # Handle backend error response if isinstance(result, dict) and "error" in result: error_msg = f"Backend error: {result['error']}" return None, {"error": error_msg}, [] concept = None # Handle different response formats if isinstance(result, dict): # Case 1: Direct concept object if "id" in result or "name" in result: concept = result # Case 2: Response with 'concepts' list elif "concepts" in result: if result["concepts"]: concept = result["concepts"][0] if not concept_id else None # Try to find the requested concept by ID or name if concept_id: for c in result["concepts"]: if (isinstance(c, dict) and (c.get("id") == concept_id or str(c.get("name", "")).lower() == concept_id.lower())): concept = c break if not concept: error_msg = f"Concept '{concept_id}' not found in the concept graph" return None, {"error": error_msg}, [] else: error_msg = "No concepts found in the concept graph" return None, {"error": error_msg}, [] # If we still don't have a valid concept if not concept or not isinstance(concept, dict): error_msg = "Could not extract valid concept data from response" return None, {"error": error_msg}, [] # Ensure required fields exist with defaults concept.setdefault('related_concepts', []) concept.setdefault('prerequisites', []) # Create a new directed graph G = nx.DiGraph() # Add the main concept node main_node_id = concept["id"] G.add_node(main_node_id, label=concept["name"], type="main", description=concept["description"]) # Add related concepts and edges all_related = [] # Process related concepts for rel in concept.get('related_concepts', []): if isinstance(rel, dict): rel_id = rel.get('id', str(hash(str(rel.get('name', ''))))) rel_name = rel.get('name', 'Unnamed') rel_desc = rel.get('description', 'Related concept') G.add_node(rel_id, label=rel_name, type="related", description=rel_desc) G.add_edge(main_node_id, rel_id, type="related_to") all_related.append(["Related", rel_name, rel_desc]) # Process prerequisites for prereq in concept.get('prerequisites', []): if isinstance(prereq, dict): prereq_id = prereq.get('id', str(hash(str(prereq.get('name', ''))))) prereq_name = f"[Prerequisite] {prereq.get('name', 'Unnamed')}" prereq_desc = prereq.get('description', 'Prerequisite concept') G.add_node(prereq_id, label=prereq_name, type="prerequisite", description=prereq_desc) G.add_edge(prereq_id, main_node_id, type="prerequisite_for") all_related.append(["Prerequisite", prereq_name, prereq_desc]) # Create the plot plt.figure(figsize=(14, 10)) # Calculate node positions using spring layout pos = nx.spring_layout(G, k=0.5, iterations=50, seed=42) # Define node colors and sizes based on type node_colors = [] node_sizes = [] for node, data in G.nodes(data=True): if data.get('type') == 'main': node_colors.append('#4e79a7') # Blue for main concept node_sizes.append(1500) elif data.get('type') == 'prerequisite': node_colors.append('#59a14f') # Green for prerequisites node_sizes.append(1000) else: # related node_colors.append('#e15759') # Red for related concepts node_sizes.append(1000) # Draw nodes nx.draw_networkx_nodes( G, pos, node_color=node_colors, node_size=node_sizes, alpha=0.9, edgecolors='white', linewidths=2 ) # Draw edges with different styles for different relationships related_edges = [(u, v) for u, v, d in G.edges(data=True) if d.get('type') == 'related_to'] prereq_edges = [(u, v) for u, v, d in G.edges(data=True) if d.get('type') == 'prerequisite_for'] # Draw related edges nx.draw_networkx_edges( G, pos, edgelist=related_edges, width=1.5, alpha=0.7, edge_color="#e15759", style="solid", arrowsize=15, arrowstyle='-|>', connectionstyle='arc3,rad=0.1' ) # Draw prerequisite edges nx.draw_networkx_edges( G, pos, edgelist=prereq_edges, width=1.5, alpha=0.7, edge_color="#59a14f", style="dashed", arrowsize=15, arrowstyle='-|>', connectionstyle='arc3,rad=0.1' ) # Draw node labels with white background for better readability node_labels = {node: data["label"] for node, data in G.nodes(data=True) if "label" in data} nx.draw_networkx_labels( G, pos, labels=node_labels, font_size=10, font_weight="bold", font_family="sans-serif", bbox=dict( facecolor="white", edgecolor='none', alpha=0.8, boxstyle='round,pad=0.3', linewidth=0 ) ) # Add a legend import matplotlib.patches as mpatches legend_elements = [ mpatches.Patch(facecolor='#4e79a7', label='Main Concept', alpha=0.9), mpatches.Patch(facecolor='#e15759', label='Related Concept', alpha=0.9), mpatches.Patch(facecolor='#59a14f', label='Prerequisite', alpha=0.9) ] plt.legend( handles=legend_elements, loc='upper right', bbox_to_anchor=(1.0, 1.0), frameon=True, framealpha=0.9 ) plt.axis('off') plt.tight_layout() # Create concept details dictionary concept_details = { 'name': concept['name'], 'id': concept['id'], 'description': concept['description'] } # Return the figure, concept details, and related concepts return plt.gcf(), concept_details, all_related except Exception as e: return None, {"error": f"Failed to load concept graph: {str(e)}"}, [] def sync_load_concept_graph(concept_id): """Synchronous wrapper for async load_concept_graph, always returns 3 outputs.""" try: result = asyncio.run(load_concept_graph(concept_id)) if result and len(result) == 3: return result else: return None, {"error": "Unexpected result format"}, [] except Exception as e: return None, {"error": str(e)}, [] # Synchronous wrapper functions for Gradio def sync_check_plagiarism(submission, reference): """Synchronous wrapper for check_plagiarism_async""" try: return asyncio.run(check_plagiarism_async(submission, reference)) except Exception as e: return {"error": str(e)} # Interactive Quiz synchronous wrappers def sync_start_interactive_quiz(quiz_data, student_id): """Synchronous wrapper for start_interactive_quiz_async""" try: return asyncio.run(start_interactive_quiz_async(quiz_data, student_id)) except Exception as e: return {"error": str(e)} def sync_submit_quiz_answer(session_id, question_id, selected_answer): """Synchronous wrapper for submit_quiz_answer_async""" try: return asyncio.run(submit_quiz_answer_async(session_id, question_id, selected_answer)) except Exception as e: return {"error": str(e)} def sync_get_quiz_hint(session_id, question_id): """Synchronous wrapper for get_quiz_hint_async""" try: return asyncio.run(get_quiz_hint_async(session_id, question_id)) except Exception as e: return {"error": str(e)} def sync_get_quiz_session_status(session_id): """Synchronous wrapper for get_quiz_session_status_async""" try: return asyncio.run(get_quiz_session_status_async(session_id)) except Exception as e: return {"error": str(e)} # Helper functions for interactive quiz interface def format_question_display(quiz_session_data): """Format quiz session data for display""" if not quiz_session_data or "error" in quiz_session_data: return "❌ No active quiz session" question = quiz_session_data.get("question", {}) if not question: return "✅ Quiz completed or no current question" question_text = question.get("question", "") options = question.get("options", []) question_num = quiz_session_data.get("current_question_number", 1) total = quiz_session_data.get("total_questions", 1) display_text = f""" ### Question {question_num} of {total} **{question_text}** **Options:** """ for option in options: display_text += f"\n- {option}" return display_text def update_answer_options(quiz_session_data): """Update answer options based on current question""" if not quiz_session_data or "error" in quiz_session_data: return gr.Radio(choices=["No options available"], value=None) question = quiz_session_data.get("question", {}) options = question.get("options", ["A) Option A", "B) Option B", "C) Option C", "D) Option D"]) return gr.Radio(choices=options, value=None, label="Select Your Answer") def extract_question_id(quiz_session_data): """Extract question ID from quiz session data""" if not quiz_session_data or "error" in quiz_session_data: return "" question = quiz_session_data.get("question", {}) return question.get("question_id", "") def sync_generate_quiz(concept, difficulty): """Synchronous wrapper for on_generate_quiz""" try: return asyncio.run(on_generate_quiz(concept, difficulty)) except Exception as e: return {"error": str(e)} def sync_generate_lesson(topic, grade, duration): """Synchronous wrapper for generate_lesson_async""" try: return asyncio.run(generate_lesson_async(topic, grade, duration)) except Exception as e: return {"error": str(e)} def sync_generate_learning_path(student_id, concept_ids, student_level): """Synchronous wrapper for on_generate_learning_path""" try: return asyncio.run(on_generate_learning_path(student_id, concept_ids, student_level)) except Exception as e: return {"error": str(e)} def sync_text_interaction(text, student_id): """Synchronous wrapper for text_interaction_async""" try: return asyncio.run(text_interaction_async(text, student_id)) except Exception as e: return {"error": str(e)} def sync_document_ocr(file): """Synchronous wrapper for document_ocr_async""" try: return asyncio.run(document_ocr_async(file)) except Exception as e: return {"error": str(e)} # Adaptive learning synchronous wrappers def sync_start_adaptive_session(student_id, concept_id, difficulty): """Synchronous wrapper for start_adaptive_session_async""" try: return asyncio.run(start_adaptive_session_async(student_id, concept_id, difficulty)) except Exception as e: return {"error": str(e)} def sync_record_learning_event(student_id, concept_id, event_type, session_id, correct, time_taken): """Synchronous wrapper for record_learning_event_async""" try: return asyncio.run(record_learning_event_async(student_id, concept_id, event_type, session_id, correct, time_taken)) except Exception as e: return {"error": str(e)} def sync_get_adaptive_recommendations(student_id, concept_id, session_id=None): """Synchronous wrapper for get_adaptive_recommendations_async""" try: return asyncio.run(get_adaptive_recommendations_async(student_id, concept_id, session_id)) except Exception as e: return {"error": str(e)} def sync_get_adaptive_learning_path(student_id, concept_ids, strategy, max_concepts): """Synchronous wrapper for get_adaptive_learning_path_async""" try: return asyncio.run(get_adaptive_learning_path_async(student_id, concept_ids, strategy, max_concepts)) except Exception as e: return {"error": str(e)} def sync_get_progress_summary(student_id, days=7): """Synchronous wrapper for get_progress_summary_async""" try: return asyncio.run(get_progress_summary_async(student_id, days)) except Exception as e: return {"error": str(e)} # AI Tutoring synchronous wrappers def sync_start_tutoring_session(student_id, subject, learning_objectives): """Synchronous wrapper for start_tutoring_session_async""" try: return asyncio.run(start_tutoring_session_async(student_id, subject, learning_objectives)) except Exception as e: return {"error": str(e)} def sync_ai_tutor_chat(session_id, student_query, request_type): """Synchronous wrapper for ai_tutor_chat_async""" try: return asyncio.run(ai_tutor_chat_async(session_id, student_query, request_type)) except Exception as e: return {"error": str(e)} def sync_get_step_by_step_guidance(session_id, concept, current_step): """Synchronous wrapper for get_step_by_step_guidance_async""" try: return asyncio.run(get_step_by_step_guidance_async(session_id, concept, current_step)) except Exception as e: return {"error": str(e)} def sync_get_alternative_explanations(session_id, concept, explanation_types): """Synchronous wrapper for get_alternative_explanations_async""" try: return asyncio.run(get_alternative_explanations_async(session_id, concept, explanation_types)) except Exception as e: return {"error": str(e)} def sync_end_tutoring_session(session_id, session_summary): """Synchronous wrapper for end_tutoring_session_async""" try: return asyncio.run(end_tutoring_session_async(session_id, session_summary)) except Exception as e: return {"error": str(e)} # Content Generation synchronous wrappers def sync_generate_interactive_exercise(concept, exercise_type, difficulty_level, student_level): """Synchronous wrapper for generate_interactive_exercise_async""" try: return asyncio.run(generate_interactive_exercise_async(concept, exercise_type, difficulty_level, student_level)) except Exception as e: return {"error": str(e)} def sync_generate_scenario_based_learning(concept, scenario_type, complexity_level): """Synchronous wrapper for generate_scenario_based_learning_async""" try: return asyncio.run(generate_scenario_based_learning_async(concept, scenario_type, complexity_level)) except Exception as e: return {"error": str(e)} def sync_generate_gamified_content(concept, game_type, target_age_group): """Synchronous wrapper for generate_gamified_content_async""" try: return asyncio.run(generate_gamified_content_async(concept, game_type, target_age_group)) except Exception as e: return {"error": str(e)} # Define async functions outside the interface async def on_generate_quiz(concept, difficulty): try: if not concept or not str(concept).strip(): return {"error": "Please enter a concept"} try: difficulty = int(float(difficulty)) difficulty = max(1, min(5, difficulty)) except (ValueError, TypeError): difficulty = 3 if difficulty <= 2: difficulty_str = "easy" elif difficulty == 3: difficulty_str = "medium" else: difficulty_str = "hard" async with sse_client(SERVER_URL) as (sse, write): async with ClientSession(sse, write) as session: await session.initialize() response = await session.call_tool("generate_quiz_tool", {"concept": concept.strip(), "difficulty": difficulty_str}) return await extract_response_content(response) except Exception as e: import traceback return { "error": f"Error generating quiz: {str(e)}\n\n{traceback.format_exc()}" } async def generate_lesson_async(topic, grade, duration): async with sse_client(SERVER_URL) as (sse, write): async with ClientSession(sse, write) as session: await session.initialize() response = await session.call_tool("generate_lesson_tool", {"topic": topic, "grade_level": grade, "duration_minutes": duration}) return await extract_response_content(response) async def on_generate_learning_path(student_id, concept_ids, student_level): try: async with sse_client(SERVER_URL) as (sse, write): async with ClientSession(sse, write) as session: await session.initialize() result = await session.call_tool("get_learning_path", { "student_id": student_id, "concept_ids": [c.strip() for c in concept_ids.split(",") if c.strip()], "student_level": student_level }) return await extract_response_content(result) except Exception as e: return {"error": str(e)} # New adaptive learning functions async def start_adaptive_session_async(student_id, concept_id, difficulty): try: async with sse_client(SERVER_URL) as (sse, write): async with ClientSession(sse, write) as session: await session.initialize() result = await session.call_tool("start_adaptive_session", { "student_id": student_id, "concept_id": concept_id, "initial_difficulty": float(difficulty) }) return await extract_response_content(result) except Exception as e: return {"error": str(e)} async def record_learning_event_async(student_id, concept_id, event_type, session_id, correct, time_taken): try: async with sse_client(SERVER_URL) as (sse, write): async with ClientSession(sse, write) as session: await session.initialize() result = await session.call_tool("record_learning_event", { "student_id": student_id, "concept_id": concept_id, "event_type": event_type, "session_id": session_id, "event_data": {"correct": correct, "time_taken": time_taken} }) return await extract_response_content(result) except Exception as e: return {"error": str(e)} async def get_adaptive_recommendations_async(student_id, concept_id, session_id=None): try: async with sse_client(SERVER_URL) as (sse, write): async with ClientSession(sse, write) as session: await session.initialize() params = { "student_id": student_id, "concept_id": concept_id } if session_id: params["session_id"] = session_id result = await session.call_tool("get_adaptive_recommendations", params) return await extract_response_content(result) except Exception as e: return {"error": str(e)} async def get_adaptive_learning_path_async(student_id, concept_ids, strategy, max_concepts): try: # Parse concept_ids if it's a string if isinstance(concept_ids, str): concept_ids = [c.strip() for c in concept_ids.split(',') if c.strip()] async with sse_client(SERVER_URL) as (sse, write): async with ClientSession(sse, write) as session: await session.initialize() result = await session.call_tool("get_adaptive_learning_path", { "student_id": student_id, "target_concepts": concept_ids, "strategy": strategy, "max_concepts": int(max_concepts) }) return await extract_response_content(result) except Exception as e: return {"error": str(e)} async def get_progress_summary_async(student_id, days=7): try: async with sse_client(SERVER_URL) as (sse, write): async with ClientSession(sse, write) as session: await session.initialize() result = await session.call_tool("get_student_progress_summary", { "student_id": student_id, "days": int(days) }) return await extract_response_content(result) except Exception as e: return {"error": str(e)} # Interactive Quiz async functions async def start_interactive_quiz_async(quiz_data, student_id): async with sse_client(SERVER_URL) as (sse, write): async with ClientSession(sse, write) as session: await session.initialize() response = await session.call_tool("start_interactive_quiz_tool", {"quiz_data": quiz_data, "student_id": student_id}) return await extract_response_content(response) async def submit_quiz_answer_async(session_id, question_id, selected_answer): async with sse_client(SERVER_URL) as (sse, write): async with ClientSession(sse, write) as session: await session.initialize() response = await session.call_tool("submit_quiz_answer_tool", {"session_id": session_id, "question_id": question_id, "selected_answer": selected_answer}) return await extract_response_content(response) async def get_quiz_hint_async(session_id, question_id): async with sse_client(SERVER_URL) as (sse, write): async with ClientSession(sse, write) as session: await session.initialize() response = await session.call_tool("get_quiz_hint_tool", {"session_id": session_id, "question_id": question_id}) return await extract_response_content(response) async def get_quiz_session_status_async(session_id): async with sse_client(SERVER_URL) as (sse, write): async with ClientSession(sse, write) as session: await session.initialize() response = await session.call_tool("get_quiz_session_status_tool", {"session_id": session_id}) return await extract_response_content(response) async def extract_response_content(response): """Helper function to extract content from MCP response""" # Handle direct dictionary responses (new format) if isinstance(response, dict): return response # Handle MCP response with content structure (CallToolResult format) if hasattr(response, 'content') and isinstance(response.content, list): for item in response.content: # Handle TextContent objects if hasattr(item, 'text') and item.text: try: return json.loads(item.text) except Exception as e: return {"error": f"Failed to parse response: {str(e)}", "raw_text": item.text} # Handle other content types elif hasattr(item, 'type') and item.type == 'text': try: return json.loads(str(item)) except Exception: return {"error": "Failed to parse text content", "raw_text": str(item)} # Handle string responses if isinstance(response, str): try: return json.loads(response) except Exception: return {"error": "Failed to parse string response", "raw_text": response} # Handle any other response type - try to extract useful information if hasattr(response, '__dict__'): return {"error": "Unexpected response format", "type": type(response).__name__, "raw_text": str(response)} return {"error": "Unknown response format", "type": type(response).__name__, "raw_text": str(response)} async def text_interaction_async(text, student_id): async with sse_client(SERVER_URL) as (sse, write): async with ClientSession(sse, write) as session: await session.initialize() response = await session.call_tool("text_interaction", {"query": text, "student_id": student_id}) return await extract_response_content(response) async def upload_file_to_storage(file_path): """Helper function to upload file to storage API""" try: url = "https://storage-bucket-api.vercel.app/upload" with open(file_path, 'rb') as f: files = {'file': (os.path.basename(file_path), f)} response = requests.post(url, files=files) response.raise_for_status() return response.json() except Exception as e: return {"error": f"Error uploading file to storage: {str(e)}", "success": False} async def document_ocr_async(file): if not file: return {"error": "No file provided", "success": False} try: if isinstance(file, dict): file_path = file.get("path", "") else: file_path = file if not file_path or not os.path.exists(file_path): return {"error": "File not found", "success": False} upload_result = await upload_file_to_storage(file_path) if not upload_result.get("success"): return upload_result storage_url = upload_result.get("storage_url") if not storage_url: return {"error": "No storage URL returned from upload", "success": False} async with sse_client(SERVER_URL) as (sse, write): async with ClientSession(sse, write) as session: await session.initialize() response = await session.call_tool("mistral_document_ocr", {"document_url": storage_url}) return await extract_response_content(response) except Exception as e: return {"error": f"Error processing document: {str(e)}", "success": False} # AI Tutoring async functions async def start_tutoring_session_async(student_id, subject, learning_objectives): try: async with sse_client(SERVER_URL) as (sse, write): async with ClientSession(sse, write) as session: await session.initialize() response = await session.call_tool("start_tutoring_session", { "student_id": student_id, "subject": subject, "learning_objectives": learning_objectives }) return await extract_response_content(response) except Exception as e: return {"error": str(e)} async def ai_tutor_chat_async(session_id, student_query, request_type): try: async with sse_client(SERVER_URL) as (sse, write): async with ClientSession(sse, write) as session: await session.initialize() response = await session.call_tool("ai_tutor_chat", { "session_id": session_id, "student_query": student_query, "request_type": request_type }) return await extract_response_content(response) except Exception as e: return {"error": str(e)} async def get_step_by_step_guidance_async(session_id, concept, current_step): try: async with sse_client(SERVER_URL) as (sse, write): async with ClientSession(sse, write) as session: await session.initialize() response = await session.call_tool("get_step_by_step_guidance", { "session_id": session_id, "concept": concept, "current_step": current_step }) return await extract_response_content(response) except Exception as e: return {"error": str(e)} async def get_alternative_explanations_async(session_id, concept, explanation_types): try: async with sse_client(SERVER_URL) as (sse, write): async with ClientSession(sse, write) as session: await session.initialize() response = await session.call_tool("get_alternative_explanations", { "session_id": session_id, "concept": concept, "explanation_types": explanation_types }) return await extract_response_content(response) except Exception as e: return {"error": str(e)} async def end_tutoring_session_async(session_id, session_summary): try: async with sse_client(SERVER_URL) as (sse, write): async with ClientSession(sse, write) as session: await session.initialize() response = await session.call_tool("end_tutoring_session", { "session_id": session_id, "session_summary": session_summary }) return await extract_response_content(response) except Exception as e: return {"error": str(e)} # Content Generation async functions async def generate_interactive_exercise_async(concept, exercise_type, difficulty_level, student_level): try: async with sse_client(SERVER_URL) as (sse, write): async with ClientSession(sse, write) as session: await session.initialize() response = await session.call_tool("generate_interactive_exercise", { "concept": concept, "exercise_type": exercise_type, "difficulty_level": difficulty_level, "student_level": student_level }) return await extract_response_content(response) except Exception as e: return {"error": str(e)} async def generate_scenario_based_learning_async(concept, scenario_type, complexity_level): try: async with sse_client(SERVER_URL) as (sse, write): async with ClientSession(sse, write) as session: await session.initialize() response = await session.call_tool("generate_scenario_based_learning", { "concept": concept, "scenario_type": scenario_type, "complexity_level": complexity_level }) return await extract_response_content(response) except Exception as e: return {"error": str(e)} async def generate_gamified_content_async(concept, game_type, target_age_group): try: async with sse_client(SERVER_URL) as (sse, write): async with ClientSession(sse, write) as session: await session.initialize() response = await session.call_tool("generate_gamified_content", { "concept": concept, "game_type": game_type, "target_age_group": target_age_group }) return await extract_response_content(response) except Exception as e: return {"error": str(e)} # Enhanced UI/UX helper functions with Gradio Soft theme colors def get_info_card_html(title, description, icon="ℹ️"): """Get HTML for a consistent info card component matching Gradio Soft theme""" return f"""

{icon} {title}

{description}

""" def get_status_display_html(message, status_type="info"): """Get HTML for a status display with Gradio Soft theme compatible styling""" # Using softer, more muted colors that match Gradio Soft theme colors = { "success": "var(--color-green-500, #10b981)", "error": "var(--color-red-500, #ef4444)", "warning": "var(--color-yellow-500, #f59e0b)", "info": "var(--color-blue-500, #3b82f6)" } icons = { "success": "✅", "error": "❌", "warning": "⚠️", "info": "ℹ️" } color = colors.get(status_type, colors["info"]) icon = icons.get(status_type, icons["info"]) return f"""
{icon} {message}
""" def create_feature_section(title, description, icon="🔧"): """Create a consistent feature section header matching Gradio Soft theme""" gr.Markdown(f"""

{icon} {title}

{description}

""") # Create Gradio interface with enhanced UI/UX def create_gradio_interface(): # Set a default student ID for the demo student_id = "student_12345" # Custom CSS for enhanced styling - Gradio Soft theme compatible custom_css = """ .gradio-container { max-width: 1400px !important; margin: 0 auto !important; } /* Tab navigation with Gradio Soft theme colors */ .tab-nav { background: var(--color-accent-soft, #ff6b6b20) !important; border: 1px solid var(--color-accent, #ff6b6b) !important; border-radius: var(--radius-lg, 8px) var(--radius-lg, 8px) 0 0 !important; } .tab-nav button { color: var(--body-text-color, #374151) !important; font-weight: 500 !important; padding: 12px 20px !important; margin: 0 2px !important; border-radius: var(--radius-md, 6px) var(--radius-md, 6px) 0 0 !important; transition: all 0.3s ease !important; background: transparent !important; } .tab-nav button:hover { background: var(--color-accent-soft, #ff6b6b20) !important; transform: translateY(-1px) !important; } .tab-nav button.selected { background: var(--background-fill-primary, #ffffff) !important; color: var(--body-text-color, #374151) !important; box-shadow: var(--shadow-drop, 0 1px 3px rgba(0,0,0,0.1)) !important; border-bottom: 2px solid var(--color-accent, #ff6b6b) !important; } /* Accordion styling */ .accordion { border: 1px solid var(--border-color-primary, #e5e5e5) !important; border-radius: var(--radius-lg, 8px) !important; margin: 0.5rem 0 !important; overflow: hidden !important; background: var(--background-fill-primary, #ffffff) !important; } .accordion summary { background: var(--background-fill-secondary, #f7f7f7) !important; padding: 1rem !important; font-weight: 600 !important; cursor: pointer !important; border-bottom: 1px solid var(--border-color-primary, #e5e5e5) !important; color: var(--body-text-color, #374151) !important; } .accordion[open] summary { border-bottom: 1px solid var(--border-color-primary, #e5e5e5) !important; } /* Button styling with Gradio theme */ .button-primary { background: var(--color-accent, #ff6b6b) !important; border: none !important; color: white !important; font-weight: 500 !important; padding: 10px 20px !important; border-radius: var(--radius-md, 6px) !important; transition: all 0.3s ease !important; box-shadow: var(--shadow-drop, 0 1px 3px rgba(0,0,0,0.1)) !important; } .button-primary:hover { background: var(--color-accent-hover, #ff5252) !important; transform: translateY(-1px) !important; box-shadow: var(--shadow-drop-lg, 0 4px 6px rgba(0,0,0,0.1)) !important; } /* Loading spinner */ .loading-spinner { display: inline-block; width: 20px; height: 20px; border: 3px solid var(--border-color-primary, #e5e5e5); border-top: 3px solid var(--color-accent, #ff6b6b); border-radius: 50%; animation: spin 1s linear infinite; } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } /* Status cards */ .status-card { background: var(--background-fill-primary, #ffffff); border: 1px solid var(--border-color-primary, #e5e5e5); border-radius: var(--radius-lg, 8px); padding: 1rem; margin: 0.5rem 0; box-shadow: var(--shadow-drop, 0 1px 3px rgba(0,0,0,0.1)); } /* Feature highlights */ .feature-highlight { background: var(--color-accent, #ff6b6b); color: white; padding: 0.5rem 1rem; border-radius: var(--radius-full, 20px); font-size: 0.85rem; font-weight: 500; display: inline-block; margin: 0.25rem; box-shadow: var(--shadow-drop, 0 1px 3px rgba(0,0,0,0.1)); } /* Custom scrollbar for better theme integration */ ::-webkit-scrollbar { width: 8px; } ::-webkit-scrollbar-track { background: var(--background-fill-secondary, #f7f7f7); } ::-webkit-scrollbar-thumb { background: var(--color-accent-soft, #ff6b6b40); border-radius: var(--radius-md, 6px); } ::-webkit-scrollbar-thumb:hover { background: var(--color-accent, #ff6b6b); } """ with gr.Blocks( title="TutorX Educational AI", theme=gr.themes.Soft(), css=custom_css ) as demo: # Start the ping task when the app loads demo.load( fn=start_ping_task, inputs=None, outputs=None, queue=False ) # Enhanced Header Section with Welcome and Quick Start - Gradio Soft theme with gr.Row(): with gr.Column(): gr.Markdown("""

🧠 TutorX Educational AI Platform

An adaptive, multi-modal, and collaborative AI tutoring platform with real-time personalization

🎯 Adaptive Learning 🤖 AI Tutoring 📊 Real-time Analytics 🎮 Interactive Content
""") # Quick Start Guide - Gradio Soft theme compatible with gr.Accordion("🚀 Quick Start Guide - New Users Start Here!", open=True): gr.Markdown("""

🎯 Step 1: Explore Concepts

Start with the Core Features tab to visualize concept relationships and generate your first quiz.

🤖 Step 2: Try AI Tutoring

Visit the AI Tutoring tab for personalized, step-by-step learning assistance.

🧠 Step 3: Adaptive Learning

Experience the Adaptive Learning system that adjusts to your performance in real-time.

""") # Main Tabs with enhanced navigation with gr.Tabs(): # Tab 1: Core Features - Enhanced with better organization with gr.Tab("🎯 Core Features", elem_id="core_features_tab"): # Feature section header create_feature_section( "Concept Graph Visualization", "Explore relationships between educational concepts through interactive graph visualization", "🔍" ) # Enhanced concept graph interface with better UX with gr.Row(): # Left panel - Controls and Information with gr.Column(scale=3): # Input section with enhanced styling with gr.Group(): gr.Markdown("### 🎯 Concept Explorer") concept_input = gr.Textbox( label="Enter Concept to Explore", placeholder="e.g., machine_learning, calculus, quantum_physics", value="machine_learning", info="Enter any educational concept to visualize its relationships" ) with gr.Row(): load_btn = gr.Button("🔍 Load Graph", variant="primary", scale=2) clear_btn = gr.Button("🗑️ Clear", variant="secondary", scale=1) # Quick examples for easy access with gr.Group(): gr.Markdown("### 💡 Quick Examples") with gr.Row(): example_btns = [] examples = ["machine_learning", "calculus", "quantum_physics", "biology"] for example in examples: btn = gr.Button(example.replace("_", " ").title(), size="sm") example_btns.append(btn) # Concept details with enhanced presentation with gr.Accordion("📋 Concept Details", open=True): concept_details = gr.JSON( label=None, show_label=False ) # Related concepts with better formatting with gr.Accordion("🔗 Related Concepts & Prerequisites", open=True): related_concepts = gr.Dataframe( headers=["Type", "Name", "Description"], datatype=["str", "str", "str"], interactive=False, wrap=True # height=200 ) # Right panel - Graph visualization with gr.Column(scale=7): with gr.Group(): gr.Markdown("### 🌐 Interactive Concept Graph") graph_plot = gr.Plot( label=None, show_label=False, container=True ) # Graph legend and instructions gr.Markdown("""
📖 Graph Legend:
🔵 Main Concept | 🔴 Related Concepts | 🟢 Prerequisites
Tip: The graph shows how concepts connect and build upon each other
""") # Enhanced event handlers with better UX def clear_concept_input(): return "", None, {"message": "Enter a concept to explore"}, [] def load_example_concept(example): return example # Main load button load_btn.click( fn=sync_load_concept_graph, inputs=[concept_input], outputs=[graph_plot, concept_details, related_concepts] ) # Clear button clear_btn.click( fn=clear_concept_input, inputs=[], outputs=[concept_input, graph_plot, concept_details, related_concepts] ) # Example buttons for i, (btn, example) in enumerate(zip(example_btns, examples)): btn.click( fn=lambda ex=example: load_example_concept(ex), inputs=[], outputs=[concept_input] ).then( fn=sync_load_concept_graph, inputs=[concept_input], outputs=[graph_plot, concept_details, related_concepts] ) # Load initial graph on startup demo.load( fn=lambda: sync_load_concept_graph("machine_learning"), outputs=[graph_plot, concept_details, related_concepts] ) # Enhanced Assessment Generation Section create_feature_section( "Assessment Generation", "Create customized quizzes and assessments with immediate feedback and detailed explanations", "📝" ) with gr.Row(): # Left panel - Quiz configuration with gr.Column(scale=2): with gr.Group(): gr.Markdown("### ⚙️ Quiz Configuration") quiz_concept_input = gr.Textbox( label="📚 Concept or Topic", placeholder="e.g., Linear Equations, Photosynthesis, World War II", lines=2, info="Enter the subject matter for your quiz" ) with gr.Row(): diff_input = gr.Slider( minimum=1, maximum=5, value=3, step=1, label="🎯 Difficulty Level", info="1=Very Easy, 3=Medium, 5=Very Hard" ) with gr.Row(): gen_quiz_btn = gr.Button("🎲 Generate Quiz", variant="primary", scale=2) preview_btn = gr.Button("👁️ Preview", variant="secondary", scale=1) # Right panel - Generated quiz display with gr.Column(scale=3): with gr.Group(): gr.Markdown("### 📋 Generated Quiz") quiz_output = gr.JSON( label=None, show_label=False, container=True ) # Quiz statistics - Gradio Soft theme compatible quiz_stats = gr.Markdown("""
📊 Quiz will appear here after generation
Click "Generate Quiz" to create your assessment
""") # Enhanced quiz generation with better UX def generate_quiz_with_feedback(concept, difficulty): """Generate quiz with user-friendly feedback""" if not concept or not concept.strip(): return { "error": "Please enter a concept or topic for the quiz", "status": "error" } # Show loading state result = sync_generate_quiz(concept, difficulty) # Add user-friendly formatting if isinstance(result, dict) and "error" not in result: # Add metadata for better display result["_ui_metadata"] = { "concept": concept, "difficulty": difficulty, "generated_at": "Just now", "status": "success" } return result # Connect enhanced quiz generation gen_quiz_btn.click( fn=generate_quiz_with_feedback, inputs=[quiz_concept_input, diff_input], outputs=[quiz_output], api_name="generate_quiz" ) # Enhanced Interactive Quiz Section create_feature_section( "Interactive Quiz Taking", "Take quizzes with immediate feedback, hints, and detailed explanations for enhanced learning", "🎮" ) # Quiz workflow with step-by-step guidance with gr.Accordion("🚀 Step 1: Start Interactive Quiz Session", open=True): with gr.Row(): with gr.Column(scale=2): with gr.Group(): gr.Markdown("### 👤 Student Information") quiz_student_id = gr.Textbox( label="Student ID", value=student_id, info="Your unique identifier for tracking progress" ) start_quiz_btn = gr.Button("🎯 Start Interactive Quiz", variant="primary") gr.Markdown(get_info_card_html( "📋 Prerequisites", "Make sure you have generated a quiz above before starting an interactive session" )) with gr.Column(scale=3): with gr.Group(): gr.Markdown("### 📊 Session Status") quiz_session_output = gr.JSON( label=None, show_label=False ) # Enhanced Quiz Taking Interface with gr.Accordion("📝 Step 2: Answer Questions", open=True): with gr.Row(): # Left panel - Question and controls with gr.Column(scale=2): with gr.Group(): gr.Markdown("### 🎯 Current Question") session_id_input = gr.Textbox( label="Session ID", placeholder="Enter session ID from above", info="Copy the session ID from the status above" ) question_id_input = gr.Textbox( label="Question ID", placeholder="e.g., q1", info="Current question identifier" ) # Enhanced answer options answer_choice = gr.Radio( choices=["A) Option A", "B) Option B", "C) Option C", "D) Option D"], label="📋 Select Your Answer", value=None, info="Choose the best answer from the options below" ) # Action buttons with better organization with gr.Row(): submit_answer_btn = gr.Button("✅ Submit Answer", variant="primary", scale=2) get_hint_btn = gr.Button("💡 Get Hint", variant="secondary", scale=1) with gr.Row(): check_status_btn = gr.Button("📊 Check Progress", variant="secondary") # Right panel - Feedback and results with gr.Column(scale=3): with gr.Group(): gr.Markdown("### 📋 Question & Feedback") current_question_display = gr.Markdown("*Start a quiz session to see questions here*") with gr.Accordion("💬 Answer Feedback", open=True): answer_feedback = gr.JSON( label=None, show_label=False ) with gr.Accordion("💡 Hints & Help", open=False): hint_output = gr.JSON( label=None, show_label=False ) # Enhanced Quiz Progress and Results with gr.Accordion("📊 Step 3: Track Progress & Results", open=True): with gr.Row(): with gr.Column(): with gr.Group(): gr.Markdown("### 📈 Progress Overview") quiz_stats_display = gr.JSON( label=None, show_label=False ) with gr.Column(): with gr.Group(): gr.Markdown("### 🏆 Performance Summary") performance_summary = gr.Markdown("""
📊 Complete a quiz to see your performance metrics
Accuracy • Speed • Learning Progress
""") # Connect interactive quiz buttons with enhanced functionality def start_quiz_with_display(student_id, quiz_data): """Start quiz and update displays""" if not quiz_data or "error" in quiz_data: return {"error": "Please generate a quiz first"}, "*Please generate a quiz first*", gr.Radio(choices=["No options available"], value=None), "" session_result = sync_start_interactive_quiz(quiz_data, student_id) question_display = format_question_display(session_result) answer_options = update_answer_options(session_result) question_id = extract_question_id(session_result) return session_result, question_display, answer_options, question_id def submit_answer_with_feedback(session_id, question_id, selected_answer): """Submit answer and update displays""" feedback = sync_submit_quiz_answer(session_id, question_id, selected_answer) # Update question display if there's a next question if "next_question" in feedback: next_q_data = {"question": feedback["next_question"]} question_display = format_question_display(next_q_data) answer_options = update_answer_options(next_q_data) next_question_id = feedback["next_question"].get("question_id", "") else: question_display = "✅ Quiz completed! Check your final results below." answer_options = gr.Radio(choices=["Quiz completed"], value=None) next_question_id = "" return feedback, question_display, answer_options, next_question_id start_quiz_btn.click( fn=start_quiz_with_display, inputs=[quiz_student_id, quiz_output], outputs=[quiz_session_output, current_question_display, answer_choice, question_id_input] ) submit_answer_btn.click( fn=submit_answer_with_feedback, inputs=[session_id_input, question_id_input, answer_choice], outputs=[answer_feedback, current_question_display, answer_choice, question_id_input] ) get_hint_btn.click( fn=sync_get_quiz_hint, inputs=[session_id_input, question_id_input], outputs=[hint_output] ) check_status_btn.click( fn=sync_get_quiz_session_status, inputs=[session_id_input], outputs=[quiz_stats_display] ) # Instructions and Examples with gr.Accordion("📖 How to Use Interactive Quizzes", open=False): gr.Markdown(""" ### 🚀 Quick Start Guide **Step 1: Generate a Quiz** 1. Enter a concept (e.g., "Linear Equations", "Photosynthesis") 2. Set difficulty level (1-5) 3. Click "Generate Quiz" **Step 2: Start Interactive Session** 1. Enter your Student ID 2. Click "Start Interactive Quiz" 3. Copy the Session ID for tracking **Step 3: Answer Questions** 1. Read the question displayed 2. Select your answer from the options 3. Click "Submit Answer" for immediate feedback 4. Use "Get Hint" if you need help **Step 4: Track Progress** - Use "Check Status" to see your overall progress - View explanations for each answer - See your final score when completed ### 🎯 Features - **Immediate Feedback**: Get instant results for each answer - **Detailed Explanations**: Understand why answers are correct/incorrect - **Helpful Hints**: Get guidance when you're stuck - **Progress Tracking**: Monitor your performance throughout - **Adaptive Content**: Questions tailored to your difficulty level ### 💡 Tips - Read questions carefully before selecting answers - Use hints strategically to learn concepts - Review explanations to reinforce learning - Track your progress to identify improvement areas """) gr.Markdown("---") # Tab 2: Advanced Features - Enhanced with gr.Tab("📚 Advanced Features", elem_id="advanced_features_tab"): create_feature_section( "Lesson Generation", "Create comprehensive lesson plans with structured content and learning objectives", "📖" ) with gr.Row(): with gr.Column(): topic_input = gr.Textbox(label="Lesson Topic", value="Solving Quadratic Equations") grade_input = gr.Slider(minimum=1, maximum=12, value=9, step=1, label="Grade Level") duration_input = gr.Slider(minimum=15, maximum=90, value=45, step=5, label="Duration (minutes)") gen_lesson_btn = gr.Button("Generate Lesson Plan") with gr.Column(): lesson_output = gr.JSON(label="Lesson Plan") # Connect lesson generation button gen_lesson_btn.click( fn=sync_generate_lesson, inputs=[topic_input, grade_input, duration_input], outputs=[lesson_output] ) create_feature_section( "Learning Path Generation", "Enhanced with adaptive learning capabilities for personalized educational journeys", "🛤️" ) with gr.Row(): with gr.Column(): lp_student_id = gr.Textbox(label="Student ID", value=student_id) lp_concept_ids = gr.Textbox(label="Concept IDs (comma-separated)", placeholder="e.g., python,functions,oop") lp_student_level = gr.Dropdown(choices=["beginner", "intermediate", "advanced"], value="beginner", label="Student Level") with gr.Row(): lp_btn = gr.Button("Generate Basic Path") adaptive_lp_btn = gr.Button("Generate Adaptive Path", variant="primary") with gr.Column(): lp_output = gr.JSON(label="Learning Path") # Connect learning path generation buttons lp_btn.click( fn=sync_generate_learning_path, inputs=[lp_student_id, lp_concept_ids, lp_student_level], outputs=[lp_output] ) adaptive_lp_btn.click( fn=lambda sid, cids, _: sync_get_adaptive_learning_path(sid, cids, "adaptive", 10), inputs=[lp_student_id, lp_concept_ids, lp_student_level], outputs=[lp_output] ) # Tab 3: Interactive Tools - Enhanced with gr.Tab("🛠️ Interactive Tools", elem_id="interactive_tools_tab"): create_feature_section( "Text Interaction & Document Processing", "Ask questions, get explanations, and process documents with AI-powered analysis", "💬" ) with gr.Row(): with gr.Column(): text_input = gr.Textbox(label="Ask a Question", value="How do I solve a quadratic equation?") text_btn = gr.Button("Submit") with gr.Column(): text_output = gr.JSON(label="Response") # Connect text interaction button text_btn.click( fn=lambda text: sync_text_interaction(text, student_id), inputs=[text_input], outputs=[text_output] ) # Document OCR (PDF, images, etc.) create_feature_section( "Document OCR & LLM Analysis", "Upload and analyze documents with advanced OCR and AI-powered content extraction", "📄" ) with gr.Row(): with gr.Column(): doc_input = gr.File(label="Upload PDF or Document", file_types=[".pdf", ".jpg", ".jpeg", ".png"]) doc_ocr_btn = gr.Button("Extract Text & Analyze") with gr.Column(): doc_output = gr.JSON(label="Document OCR & LLM Analysis") # Connect document OCR button doc_ocr_btn.click( fn=sync_document_ocr, inputs=[doc_input], outputs=[doc_output] ) # Tab 4: AI Tutoring - Enhanced with gr.Tab("🤖 AI Tutoring", elem_id="ai_tutoring_tab"): create_feature_section( "Contextualized AI Tutoring", "Experience personalized AI tutoring with step-by-step guidance and alternative explanations", "🤖" ) with gr.Accordion("ℹ️ How AI Tutoring Works", open=False): gr.Markdown(""" ### 🎯 Contextualized Learning - **Session Memory**: AI remembers your conversation and adapts responses - **Step-by-Step Guidance**: Break down complex concepts into manageable steps - **Alternative Explanations**: Multiple ways to understand the same concept - **Personalized Feedback**: Responses tailored to your understanding level ### 🚀 Getting Started 1. Start a tutoring session with your preferred subject 2. Ask questions or request explanations 3. Get step-by-step guidance for complex topics 4. Request alternative explanations if needed 5. End session to get a comprehensive summary """) # Tutoring Session Management with gr.Accordion("📚 Start Tutoring Session", open=True): with gr.Row(): with gr.Column(): tutor_student_id = gr.Textbox(label="Student ID", value=student_id) tutor_subject = gr.Textbox(label="Subject", value="Mathematics", placeholder="e.g., Mathematics, Physics, Chemistry") tutor_objectives = gr.Textbox( label="Learning Objectives (optional)", placeholder="e.g., Understand quadratic equations, Learn calculus basics", lines=2 ) start_tutor_btn = gr.Button("Start Tutoring Session", variant="primary") with gr.Column(): tutor_session_output = gr.JSON(label="Session Information") # AI Chat Interface with gr.Accordion("💬 Chat with AI Tutor", open=True): with gr.Row(): with gr.Column(): chat_session_id = gr.Textbox(label="Session ID", placeholder="Enter session ID from above") chat_query = gr.Textbox( label="Ask Your Question", placeholder="e.g., How do I solve quadratic equations?", lines=3 ) chat_request_type = gr.Dropdown( choices=["explanation", "step_by_step", "alternative", "practice", "clarification"], value="explanation", label="Request Type" ) chat_btn = gr.Button("Ask AI Tutor", variant="primary") with gr.Column(): chat_response = gr.JSON(label="AI Tutor Response") # Step-by-Step Guidance with gr.Accordion("📋 Step-by-Step Guidance", open=True): with gr.Row(): with gr.Column(): step_session_id = gr.Textbox(label="Session ID") step_concept = gr.Textbox(label="Concept", placeholder="e.g., Solving quadratic equations") step_current = gr.Number(label="Current Step", value=1, minimum=1) get_steps_btn = gr.Button("Get Step-by-Step Guidance") with gr.Column(): steps_output = gr.JSON(label="Step-by-Step Guidance") # Alternative Explanations with gr.Accordion("🔄 Alternative Explanations", open=True): with gr.Row(): with gr.Column(): alt_session_id = gr.Textbox(label="Session ID") alt_concept = gr.Textbox(label="Concept", placeholder="e.g., Photosynthesis") alt_types = gr.CheckboxGroup( choices=["visual", "analogy", "real_world", "simplified", "technical"], value=["visual", "analogy", "real_world"], label="Explanation Types" ) get_alt_btn = gr.Button("Get Alternative Explanations") with gr.Column(): alt_output = gr.JSON(label="Alternative Explanations") # Session Management with gr.Accordion("🔚 End Session & Summary", open=True): with gr.Row(): with gr.Column(): end_session_id = gr.Textbox(label="Session ID") session_summary = gr.Textbox( label="Session Summary (optional)", placeholder="What did you learn? Any feedback?", lines=3 ) end_session_btn = gr.Button("End Session & Get Summary", variant="secondary") with gr.Column(): session_end_output = gr.JSON(label="Session Summary") # Connect all AI tutoring buttons start_tutor_btn.click( fn=lambda sid, subj, obj: sync_start_tutoring_session(sid, subj, obj.split(',') if obj else []), inputs=[tutor_student_id, tutor_subject, tutor_objectives], outputs=[tutor_session_output] ) chat_btn.click( fn=sync_ai_tutor_chat, inputs=[chat_session_id, chat_query, chat_request_type], outputs=[chat_response] ) get_steps_btn.click( fn=sync_get_step_by_step_guidance, inputs=[step_session_id, step_concept, step_current], outputs=[steps_output] ) get_alt_btn.click( fn=sync_get_alternative_explanations, inputs=[alt_session_id, alt_concept, alt_types], outputs=[alt_output] ) end_session_btn.click( fn=sync_end_tutoring_session, inputs=[end_session_id, session_summary], outputs=[session_end_output] ) # Tab 5: Content Generation - Enhanced with gr.Tab("🎨 Content Generation", elem_id="content_generation_tab"): create_feature_section( "Advanced Content Generation", "Generate interactive exercises, scenarios, and gamified content automatically with AI assistance", "🎨" ) # Interactive Exercise Generation with gr.Accordion("🎯 Interactive Exercise Generation", open=True): with gr.Row(): with gr.Column(): ex_concept = gr.Textbox(label="Concept", placeholder="e.g., Photosynthesis, Linear Algebra") ex_type = gr.Dropdown( choices=["problem_solving", "simulation", "case_study", "lab", "project"], value="problem_solving", label="Exercise Type" ) ex_difficulty = gr.Slider(minimum=0.1, maximum=1.0, value=0.5, step=0.1, label="Difficulty Level") ex_level = gr.Dropdown( choices=["beginner", "intermediate", "advanced"], value="intermediate", label="Student Level" ) gen_exercise_btn = gr.Button("Generate Interactive Exercise", variant="primary") with gr.Column(): exercise_output = gr.JSON(label="Generated Exercise") # Scenario-Based Learning with gr.Accordion("🎭 Scenario-Based Learning", open=True): with gr.Row(): with gr.Column(): scenario_concept = gr.Textbox(label="Concept", placeholder="e.g., Climate Change, Economics") scenario_type = gr.Dropdown( choices=["real_world", "historical", "futuristic", "problem_solving"], value="real_world", label="Scenario Type" ) scenario_complexity = gr.Dropdown( choices=["simple", "moderate", "complex"], value="moderate", label="Complexity Level" ) gen_scenario_btn = gr.Button("Generate Scenario", variant="primary") with gr.Column(): scenario_output = gr.JSON(label="Generated Scenario") # Gamified Content with gr.Accordion("🎮 Gamified Content Generation", open=True): with gr.Row(): with gr.Column(): game_concept = gr.Textbox(label="Concept", placeholder="e.g., Fractions, Chemical Reactions") game_type = gr.Dropdown( choices=["quest", "puzzle", "simulation", "competition", "story"], value="quest", label="Game Type" ) game_age = gr.Dropdown( choices=["child", "teen", "adult"], value="teen", label="Target Age Group" ) gen_game_btn = gr.Button("Generate Gamified Content", variant="primary") with gr.Column(): game_output = gr.JSON(label="Generated Game Content") # Connect content generation buttons gen_exercise_btn.click( fn=sync_generate_interactive_exercise, inputs=[ex_concept, ex_type, ex_difficulty, ex_level], outputs=[exercise_output] ) gen_scenario_btn.click( fn=sync_generate_scenario_based_learning, inputs=[scenario_concept, scenario_type, scenario_complexity], outputs=[scenario_output] ) gen_game_btn.click( fn=sync_generate_gamified_content, inputs=[game_concept, game_type, game_age], outputs=[game_output] ) # Tab 6: Adaptive Learning - Enhanced with gr.Tab("🧠 Adaptive Learning", elem_id="adaptive_learning_tab"): create_feature_section( "Adaptive Learning System", "Experience personalized learning with real-time adaptation based on your performance and learning patterns", "🧠" ) with gr.Accordion("ℹ️ How It Works", open=False): gr.Markdown(""" ### 🎯 Real-Time Adaptation - **Performance Tracking**: Monitor accuracy, time spent, and engagement - **Difficulty Adjustment**: Automatically adjust content difficulty based on performance - **Learning Path Optimization**: Personalize learning sequences based on your progress - **Mastery Detection**: Multi-indicator assessment of concept understanding ### 📊 Analytics & Insights - **Learning Patterns**: Detect your learning style and preferences - **Progress Monitoring**: Track milestones and achievements - **Predictive Recommendations**: Suggest next best concepts to learn ### 🚀 Getting Started 1. Start an adaptive session with a concept you want to learn 2. Record your learning events (answers, time taken, etc.) 3. Get real-time recommendations for difficulty adjustments 4. View your progress and mastery assessments """) # Adaptive Learning Session Management with gr.Accordion("📚 Learning Session Management", open=True): with gr.Row(): with gr.Column(): session_student_id = gr.Textbox(label="Student ID", value=student_id) session_concept_id = gr.Textbox(label="Concept ID", value="algebra_linear_equations") session_difficulty = gr.Slider(minimum=0.1, maximum=1.0, value=0.5, step=0.1, label="Initial Difficulty") start_session_btn = gr.Button("Start Adaptive Session", variant="primary") with gr.Column(): session_output = gr.JSON(label="Session Status") # Record Learning Events with gr.Row(): with gr.Column(): event_session_id = gr.Textbox(label="Session ID", placeholder="Enter session ID from above") event_type = gr.Dropdown( choices=["answer_submitted", "hint_used", "session_pause", "session_resume"], value="answer_submitted", label="Event Type" ) event_correct = gr.Checkbox(label="Answer Correct", value=True) event_time = gr.Number(label="Time Taken (seconds)", value=30) record_event_btn = gr.Button("Record Event") with gr.Column(): event_output = gr.JSON(label="Event Response") # Learning Path Optimization with gr.Accordion("🛤️ Learning Path Optimization", open=True): with gr.Row(): with gr.Column(): opt_student_id = gr.Textbox(label="Student ID", value=student_id) opt_concepts = gr.Textbox( label="Target Concepts (comma-separated)", value="algebra_basics,linear_equations,quadratic_equations" ) opt_strategy = gr.Dropdown( choices=["mastery_focused", "breadth_first", "depth_first", "adaptive", "remediation"], value="adaptive", label="Optimization Strategy" ) opt_max_concepts = gr.Slider(minimum=3, maximum=15, value=8, step=1, label="Max Concepts") optimize_path_btn = gr.Button("Optimize Learning Path", variant="primary") with gr.Column(): optimization_output = gr.JSON(label="Optimized Learning Path") # Mastery Assessment with gr.Accordion("🎓 Mastery Assessment", open=True): with gr.Row(): with gr.Column(): mastery_student_id = gr.Textbox(label="Student ID", value=student_id) mastery_concept_id = gr.Textbox(label="Concept ID", value="algebra_linear_equations") assess_mastery_btn = gr.Button("Assess Mastery", variant="primary") with gr.Column(): mastery_output = gr.JSON(label="Mastery Assessment") # Learning Analytics with gr.Accordion("📊 Learning Analytics & Progress", open=True): with gr.Row(): with gr.Column(): analytics_student_id = gr.Textbox(label="Student ID", value=student_id) analytics_days = gr.Slider(minimum=7, maximum=90, value=30, step=7, label="Analysis Period (days)") get_analytics_btn = gr.Button("Get Learning Analytics") get_progress_btn = gr.Button("Get Progress Summary") with gr.Column(): analytics_output = gr.JSON(label="Learning Analytics") progress_output = gr.JSON(label="Progress Summary") # Connect all the buttons start_session_btn.click( fn=sync_start_adaptive_session, inputs=[session_student_id, session_concept_id, session_difficulty], outputs=[session_output] ) record_event_btn.click( fn=sync_record_learning_event, inputs=[session_student_id, session_concept_id, event_type, event_session_id, event_correct, event_time], outputs=[event_output] ) optimize_path_btn.click( fn=sync_get_adaptive_learning_path, inputs=[opt_student_id, opt_concepts, opt_strategy, opt_max_concepts], outputs=[optimization_output] ) assess_mastery_btn.click( fn=lambda sid, cid: sync_get_adaptive_recommendations(sid, cid), inputs=[mastery_student_id, mastery_concept_id], outputs=[mastery_output] ) get_analytics_btn.click( fn=lambda sid, days: sync_get_progress_summary(sid, days), inputs=[analytics_student_id, analytics_days], outputs=[analytics_output] ) get_progress_btn.click( fn=lambda sid: sync_get_progress_summary(sid, 7), inputs=[analytics_student_id], outputs=[progress_output] ) # Examples and Tips with gr.Accordion("💡 Examples & Tips", open=False): gr.Markdown(""" ### 📝 Example Workflow **1. Start a Session:** - Student ID: `student_001` - Concept: `algebra_linear_equations` - Difficulty: `0.5` (medium) **2. Record Events:** - Answer submitted: correct=True, time=30s - Hint used: correct=False, time=45s **3. Get Recommendations:** - System suggests difficulty adjustments - Provides next concept suggestions **4. Optimize Learning Path:** - Target concepts: `algebra_basics,linear_equations,quadratic_equations` - Strategy: `adaptive` (recommended) ### 🎯 Optimization Strategies - **Mastery Focused**: Deep understanding before moving on - **Breadth First**: Cover many concepts quickly - **Depth First**: Thorough exploration of fewer concepts - **Adaptive**: System chooses best strategy for you - **Remediation**: Focus on filling knowledge gaps ### 📊 Understanding Analytics - **Learning Patterns**: Identifies your learning style - **Performance Trends**: Shows improvement over time - **Mastery Levels**: Tracks concept understanding - **Engagement Metrics**: Measures learning engagement """) # Tab 7: Data Analytics - Enhanced with gr.Tab("📊 Analytics", elem_id="data_analytics_tab"): create_feature_section( "Plagiarism Detection & Analytics", "Advanced plagiarism detection with detailed similarity analysis and originality reporting", "📊" ) with gr.Row(): with gr.Column(): submission_input = gr.Textbox( label="Student Submission", lines=5, value="The quadratic formula states that if ax² + bx + c = 0, then x = (-b ± √(b² - 4ac)) / 2a." ) reference_input = gr.Textbox( label="Reference Source", lines=5, value="According to the quadratic formula, for any equation in the form ax² + bx + c = 0, the solutions are x = (-b ± √(b² - 4ac)) / 2a." ) plagiarism_btn = gr.Button("Check Originality") with gr.Column(): with gr.Group(): gr.Markdown("### 🔍 Originality Report") plagiarism_output = gr.JSON(label="", show_label=False, container=False) # Connect the button to the plagiarism check function plagiarism_btn.click( fn=sync_check_plagiarism, inputs=[submission_input, reference_input], outputs=[plagiarism_output] ) # Enhanced Footer Section - Gradio Soft theme compatible gr.Markdown("""

🧠 About TutorX

TutorX is an AI-powered educational platform that provides adaptive learning, interactive assessments, and personalized tutoring to enhance the learning experience.

🔗 Quick Links

📖 Documentation

💻 GitHub Repository

🐛 Report an Issue

✨ Key Features

🎯 Adaptive Learning Paths

🤖 AI-Powered Tutoring

📊 Real-time Analytics

🎮 Interactive Assessments

© 2025 TutorX Educational AI Platform - Empowering Learning Through Technology

""") return demo # Launch the interface if __name__ == "__main__": demo = create_gradio_interface() demo.queue().launch(server_name="0.0.0.0", server_port=7860)