Spaces:
Running
Running
| """ | |
| 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""" | |
| <div style="background: var(--background-fill-secondary, #f7f7f7); | |
| border-left: 4px solid var(--color-accent, #ff6b6b); | |
| padding: 1rem; | |
| margin: 0.5rem 0; | |
| border-radius: var(--radius-lg, 8px); | |
| border: 1px solid var(--border-color-primary, #e5e5e5);"> | |
| <h4 style="margin: 0 0 0.5rem 0; color: var(--body-text-color, #374151); font-weight: 600;"> | |
| {icon} {title} | |
| </h4> | |
| <p style="margin: 0; color: var(--body-text-color-subdued, #6b7280); font-size: 0.9rem; line-height: 1.5;"> | |
| {description} | |
| </p> | |
| </div> | |
| """ | |
| 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""" | |
| <div style="background: var(--background-fill-secondary, #f7f7f7); | |
| border: 1px solid {color}; | |
| color: var(--body-text-color, #374151); | |
| padding: 0.75rem; | |
| border-radius: var(--radius-md, 6px); | |
| margin: 0.5rem 0; | |
| border-left: 4px solid {color};"> | |
| <span style="color: {color}; font-weight: 600;">{icon}</span> {message} | |
| </div> | |
| """ | |
| def create_feature_section(title, description, icon="๐ง"): | |
| """Create a consistent feature section header matching Gradio Soft theme""" | |
| gr.Markdown(f""" | |
| <div style="background: var(--color-accent-soft, #ff6b6b20); | |
| border: 1px solid var(--color-accent, #ff6b6b); | |
| color: var(--body-text-color, #374151); | |
| padding: 1.5rem; | |
| margin: 1rem 0 0.5rem 0; | |
| border-radius: var(--radius-lg, 8px); | |
| box-shadow: var(--shadow-drop, 0 1px 3px rgba(0,0,0,0.1));"> | |
| <h2 style="margin: 0; font-size: 1.5rem; color: var(--body-text-color, #374151); font-weight: 700;"> | |
| {icon} {title} | |
| </h2> | |
| <p style="margin: 0.5rem 0 0 0; color: var(--body-text-color-subdued, #6b7280); font-size: 0.95rem; line-height: 1.5;"> | |
| {description} | |
| </p> | |
| </div> | |
| """) | |
| # 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(""" | |
| <div style="background: var(--background-fill-primary, #ffffff); | |
| border: 2px solid var(--color-accent, #ff6b6b); | |
| color: var(--body-text-color, #374151); | |
| padding: 2rem; | |
| border-radius: var(--radius-xl, 12px); | |
| text-align: center; | |
| margin-bottom: 1rem; | |
| box-shadow: var(--shadow-drop-lg, 0 4px 6px rgba(0,0,0,0.1));"> | |
| <h1 style="margin: 0 0 1rem 0; font-size: 2.5rem; font-weight: 700; color: var(--body-text-color, #374151);"> | |
| ๐ง TutorX Educational AI Platform | |
| </h1> | |
| <p style="margin: 0 0 1rem 0; font-size: 1.2rem; color: var(--body-text-color-subdued, #6b7280); line-height: 1.5;"> | |
| An adaptive, multi-modal, and collaborative AI tutoring platform with real-time personalization | |
| </p> | |
| <div style="display: flex; justify-content: center; gap: 1rem; flex-wrap: wrap; margin-top: 1.5rem;"> | |
| <span class="feature-highlight">๐ฏ Adaptive Learning</span> | |
| <span class="feature-highlight">๐ค AI Tutoring</span> | |
| <span class="feature-highlight">๐ Real-time Analytics</span> | |
| <span class="feature-highlight">๐ฎ Interactive Content</span> | |
| </div> | |
| </div> | |
| """) | |
| # Quick Start Guide - Gradio Soft theme compatible | |
| with gr.Accordion("๐ Quick Start Guide - New Users Start Here!", open=True): | |
| gr.Markdown(""" | |
| <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 1rem; margin: 1rem 0;"> | |
| <div style="background: var(--background-fill-secondary, #f7f7f7); | |
| padding: 1.5rem; | |
| border-radius: var(--radius-lg, 8px); | |
| border-left: 4px solid var(--color-green-500, #10b981); | |
| border: 1px solid var(--border-color-primary, #e5e5e5);"> | |
| <h4 style="color: var(--color-green-500, #10b981); margin: 0 0 0.5rem 0; font-weight: 600;">๐ฏ Step 1: Explore Concepts</h4> | |
| <p style="margin: 0; color: var(--body-text-color-subdued, #6b7280); line-height: 1.5;">Start with the <strong>Core Features</strong> tab to visualize concept relationships and generate your first quiz.</p> | |
| </div> | |
| <div style="background: var(--background-fill-secondary, #f7f7f7); | |
| padding: 1.5rem; | |
| border-radius: var(--radius-lg, 8px); | |
| border-left: 4px solid var(--color-blue-500, #3b82f6); | |
| border: 1px solid var(--border-color-primary, #e5e5e5);"> | |
| <h4 style="color: var(--color-blue-500, #3b82f6); margin: 0 0 0.5rem 0; font-weight: 600;">๐ค Step 2: Try AI Tutoring</h4> | |
| <p style="margin: 0; color: var(--body-text-color-subdued, #6b7280); line-height: 1.5;">Visit the <strong>AI Tutoring</strong> tab for personalized, step-by-step learning assistance.</p> | |
| </div> | |
| <div style="background: var(--background-fill-secondary, #f7f7f7); | |
| padding: 1.5rem; | |
| border-radius: var(--radius-lg, 8px); | |
| border-left: 4px solid var(--color-purple-500, #8b5cf6); | |
| border: 1px solid var(--border-color-primary, #e5e5e5);"> | |
| <h4 style="color: var(--color-purple-500, #8b5cf6); margin: 0 0 0.5rem 0; font-weight: 600;">๐ง Step 3: Adaptive Learning</h4> | |
| <p style="margin: 0; color: var(--body-text-color-subdued, #6b7280); line-height: 1.5;">Experience the <strong>Adaptive Learning</strong> system that adjusts to your performance in real-time.</p> | |
| </div> | |
| </div> | |
| """) | |
| # 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(""" | |
| <div style="background: #f8f9fa; padding: 1rem; border-radius: 6px; margin-top: 0.5rem;"> | |
| <strong>๐ Graph Legend:</strong><br> | |
| ๐ต <span style="color: #4e79a7;">Main Concept</span> | | |
| ๐ด <span style="color: #e15759;">Related Concepts</span> | | |
| ๐ข <span style="color: #59a14f;">Prerequisites</span><br> | |
| <em>Tip: The graph shows how concepts connect and build upon each other</em> | |
| </div> | |
| """) | |
| # 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(""" | |
| <div style="background: var(--background-fill-secondary, #f7f7f7); | |
| padding: 1rem; | |
| border-radius: var(--radius-md, 6px); | |
| margin-top: 0.5rem; | |
| border: 1px solid var(--border-color-primary, #e5e5e5);"> | |
| <strong style="color: var(--body-text-color, #374151);">๐ Quiz will appear here after generation</strong><br> | |
| <em style="color: var(--body-text-color-subdued, #6b7280);">Click "Generate Quiz" to create your assessment</em> | |
| </div> | |
| """) | |
| # 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(""" | |
| <div style="background: var(--background-fill-secondary, #f7f7f7); | |
| padding: 1rem; | |
| border-radius: var(--radius-md, 6px); | |
| text-align: center; | |
| border: 1px solid var(--border-color-primary, #e5e5e5);"> | |
| <strong style="color: var(--body-text-color, #374151);">๐ Complete a quiz to see your performance metrics</strong><br> | |
| <em style="color: var(--body-text-color-subdued, #6b7280);">Accuracy โข Speed โข Learning Progress</em> | |
| </div> | |
| """) | |
| # 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(""" | |
| <div style="background: var(--background-fill-secondary, #f7f7f7); | |
| padding: 2rem; | |
| margin-top: 2rem; | |
| border-radius: var(--radius-xl, 12px); | |
| border-top: 3px solid var(--color-accent, #ff6b6b); | |
| border: 1px solid var(--border-color-primary, #e5e5e5);"> | |
| <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 2rem;"> | |
| <div> | |
| <h3 style="color: var(--body-text-color, #374151); margin: 0 0 1rem 0; font-weight: 600;">๐ง About TutorX</h3> | |
| <p style="color: var(--body-text-color-subdued, #6b7280); margin: 0; line-height: 1.6;"> | |
| TutorX is an AI-powered educational platform that provides adaptive learning, | |
| interactive assessments, and personalized tutoring to enhance the learning experience. | |
| </p> | |
| </div> | |
| <div> | |
| <h3 style="color: var(--body-text-color, #374151); margin: 0 0 1rem 0; font-weight: 600;">๐ Quick Links</h3> | |
| <div style="color: var(--body-text-color-subdued, #6b7280);"> | |
| <p style="margin: 0.5rem 0;">๐ <a href="https://github.com/Meetpatel006/TutorX/blob/main/README.md" target="_blank" style="color: var(--color-accent, #ff6b6b); text-decoration: none;">Documentation</a></p> | |
| <p style="margin: 0.5rem 0;">๐ป <a href="https://github.com/Meetpatel006/TutorX" target="_blank" style="color: var(--color-accent, #ff6b6b); text-decoration: none;">GitHub Repository</a></p> | |
| <p style="margin: 0.5rem 0;">๐ <a href="https://github.com/Meetpatel006/TutorX/issues" target="_blank" style="color: var(--color-accent, #ff6b6b); text-decoration: none;">Report an Issue</a></p> | |
| </div> | |
| </div> | |
| <div> | |
| <h3 style="color: var(--body-text-color, #374151); margin: 0 0 1rem 0; font-weight: 600;">โจ Key Features</h3> | |
| <div style="color: var(--body-text-color-subdued, #6b7280); font-size: 0.9rem;"> | |
| <p style="margin: 0.25rem 0;">๐ฏ Adaptive Learning Paths</p> | |
| <p style="margin: 0.25rem 0;">๐ค AI-Powered Tutoring</p> | |
| <p style="margin: 0.25rem 0;">๐ Real-time Analytics</p> | |
| <p style="margin: 0.25rem 0;">๐ฎ Interactive Assessments</p> | |
| </div> | |
| </div> | |
| </div> | |
| <div style="text-align: center; margin-top: 2rem; padding-top: 1rem; border-top: 1px solid var(--border-color-primary, #e5e5e5);"> | |
| <p style="color: var(--body-text-color-subdued, #6b7280); margin: 0; font-size: 0.9rem;"> | |
| ยฉ 2025 TutorX Educational AI Platform - Empowering Learning Through Technology | |
| </p> | |
| </div> | |
| </div> | |
| """) | |
| return demo | |
| # Launch the interface | |
| if __name__ == "__main__": | |
| demo = create_gradio_interface() | |
| demo.queue().launch(server_name="0.0.0.0", server_port=7860) | |