Spaces:
Sleeping
Sleeping
Meet Patel
commited on
Commit
·
8372659
1
Parent(s):
0228818
Step 5: Added Gradio web interface and run script for the TutorX MCP server
Browse files- app.py +280 -0
- pyproject.toml +4 -1
- run.py +93 -0
app.py
ADDED
@@ -0,0 +1,280 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""
|
2 |
+
Gradio web interface for the TutorX MCP Server
|
3 |
+
"""
|
4 |
+
|
5 |
+
import gradio as gr
|
6 |
+
import numpy as np
|
7 |
+
import json
|
8 |
+
import base64
|
9 |
+
from io import BytesIO
|
10 |
+
from PIL import Image
|
11 |
+
from datetime import datetime
|
12 |
+
|
13 |
+
# Import MCP server tools
|
14 |
+
from main import (
|
15 |
+
# Core features
|
16 |
+
assess_skill,
|
17 |
+
get_concept_graph,
|
18 |
+
get_learning_path,
|
19 |
+
generate_quiz,
|
20 |
+
analyze_error_patterns,
|
21 |
+
|
22 |
+
# Advanced features
|
23 |
+
analyze_cognitive_state,
|
24 |
+
get_curriculum_standards,
|
25 |
+
align_content_to_standard,
|
26 |
+
generate_lesson,
|
27 |
+
|
28 |
+
# User experience
|
29 |
+
get_student_dashboard,
|
30 |
+
get_accessibility_settings,
|
31 |
+
update_accessibility_settings,
|
32 |
+
|
33 |
+
# Multi-modal
|
34 |
+
text_interaction,
|
35 |
+
voice_interaction,
|
36 |
+
handwriting_recognition,
|
37 |
+
|
38 |
+
# Assessment
|
39 |
+
create_assessment,
|
40 |
+
grade_assessment,
|
41 |
+
get_student_analytics,
|
42 |
+
check_submission_originality
|
43 |
+
)
|
44 |
+
|
45 |
+
# Utility functions
|
46 |
+
def image_to_base64(img):
|
47 |
+
"""Convert a PIL image or numpy array to base64 string"""
|
48 |
+
if isinstance(img, np.ndarray):
|
49 |
+
img = Image.fromarray(img)
|
50 |
+
|
51 |
+
buffered = BytesIO()
|
52 |
+
img.save(buffered, format="PNG")
|
53 |
+
img_str = base64.b64encode(buffered.getvalue()).decode()
|
54 |
+
return img_str
|
55 |
+
|
56 |
+
def format_json(data):
|
57 |
+
"""Format JSON data for display"""
|
58 |
+
return json.dumps(data, indent=2)
|
59 |
+
|
60 |
+
# Create Gradio interface
|
61 |
+
with gr.Blocks(title="TutorX Educational AI", theme=gr.themes.Soft()) as demo:
|
62 |
+
gr.Markdown("# 📚 TutorX Educational AI Platform")
|
63 |
+
gr.Markdown("""
|
64 |
+
An adaptive, multi-modal, and collaborative AI tutoring platform built with MCP.
|
65 |
+
|
66 |
+
This interface demonstrates the functionality of the TutorX MCP server.
|
67 |
+
""")
|
68 |
+
|
69 |
+
# Set a default student ID for the demo
|
70 |
+
student_id = "student_12345"
|
71 |
+
|
72 |
+
with gr.Tabs() as tabs:
|
73 |
+
# Tab 1: Core Features
|
74 |
+
with gr.Tab("Core Features"):
|
75 |
+
gr.Markdown("## Adaptive Learning Engine")
|
76 |
+
|
77 |
+
with gr.Row():
|
78 |
+
with gr.Column():
|
79 |
+
concept_id_input = gr.Dropdown(
|
80 |
+
choices=["math_algebra_basics", "math_algebra_linear_equations", "math_algebra_quadratic_equations"],
|
81 |
+
label="Select Concept",
|
82 |
+
value="math_algebra_linear_equations"
|
83 |
+
)
|
84 |
+
assess_btn = gr.Button("Assess Skill")
|
85 |
+
|
86 |
+
with gr.Column():
|
87 |
+
assessment_output = gr.JSON(label="Skill Assessment")
|
88 |
+
|
89 |
+
assess_btn.click(
|
90 |
+
fn=lambda concept: assess_skill(student_id, concept),
|
91 |
+
inputs=[concept_id_input],
|
92 |
+
outputs=[assessment_output]
|
93 |
+
)
|
94 |
+
|
95 |
+
gr.Markdown("## Concept Graph")
|
96 |
+
concept_graph_btn = gr.Button("Show Concept Graph")
|
97 |
+
concept_graph_output = gr.JSON(label="Concept Graph")
|
98 |
+
|
99 |
+
concept_graph_btn.click(
|
100 |
+
fn=lambda: get_concept_graph(),
|
101 |
+
inputs=[],
|
102 |
+
outputs=[concept_graph_output]
|
103 |
+
)
|
104 |
+
|
105 |
+
gr.Markdown("## Assessment Generation")
|
106 |
+
with gr.Row():
|
107 |
+
with gr.Column():
|
108 |
+
concepts_input = gr.CheckboxGroup(
|
109 |
+
choices=["math_algebra_basics", "math_algebra_linear_equations", "math_algebra_quadratic_equations"],
|
110 |
+
label="Select Concepts",
|
111 |
+
value=["math_algebra_linear_equations"]
|
112 |
+
)
|
113 |
+
diff_input = gr.Slider(minimum=1, maximum=5, value=2, step=1, label="Difficulty")
|
114 |
+
gen_quiz_btn = gr.Button("Generate Quiz")
|
115 |
+
|
116 |
+
with gr.Column():
|
117 |
+
quiz_output = gr.JSON(label="Generated Quiz")
|
118 |
+
|
119 |
+
gen_quiz_btn.click(
|
120 |
+
fn=lambda concepts, diff: generate_quiz(concepts, diff),
|
121 |
+
inputs=[concepts_input, diff_input],
|
122 |
+
outputs=[quiz_output]
|
123 |
+
)
|
124 |
+
|
125 |
+
# Tab 2: Advanced Features
|
126 |
+
with gr.Tab("Advanced Features"):
|
127 |
+
gr.Markdown("## Lesson Generation")
|
128 |
+
|
129 |
+
with gr.Row():
|
130 |
+
with gr.Column():
|
131 |
+
topic_input = gr.Textbox(label="Lesson Topic", value="Solving Quadratic Equations")
|
132 |
+
grade_input = gr.Slider(minimum=1, maximum=12, value=9, step=1, label="Grade Level")
|
133 |
+
duration_input = gr.Slider(minimum=15, maximum=90, value=45, step=5, label="Duration (minutes)")
|
134 |
+
gen_lesson_btn = gr.Button("Generate Lesson Plan")
|
135 |
+
|
136 |
+
with gr.Column():
|
137 |
+
lesson_output = gr.JSON(label="Lesson Plan")
|
138 |
+
|
139 |
+
gen_lesson_btn.click(
|
140 |
+
fn=lambda topic, grade, duration: generate_lesson(topic, grade, duration),
|
141 |
+
inputs=[topic_input, grade_input, duration_input],
|
142 |
+
outputs=[lesson_output]
|
143 |
+
)
|
144 |
+
|
145 |
+
gr.Markdown("## Curriculum Standards")
|
146 |
+
|
147 |
+
with gr.Row():
|
148 |
+
with gr.Column():
|
149 |
+
country_input = gr.Dropdown(
|
150 |
+
choices=["us", "uk"],
|
151 |
+
label="Country",
|
152 |
+
value="us"
|
153 |
+
)
|
154 |
+
standards_btn = gr.Button("Get Standards")
|
155 |
+
|
156 |
+
with gr.Column():
|
157 |
+
standards_output = gr.JSON(label="Curriculum Standards")
|
158 |
+
|
159 |
+
standards_btn.click(
|
160 |
+
fn=lambda country: get_curriculum_standards(country),
|
161 |
+
inputs=[country_input],
|
162 |
+
outputs=[standards_output]
|
163 |
+
)
|
164 |
+
|
165 |
+
# Tab 3: Multi-Modal Interaction
|
166 |
+
with gr.Tab("Multi-Modal Interaction"):
|
167 |
+
gr.Markdown("## Text Interaction")
|
168 |
+
|
169 |
+
with gr.Row():
|
170 |
+
with gr.Column():
|
171 |
+
text_input = gr.Textbox(label="Ask a Question", value="How do I solve a quadratic equation?")
|
172 |
+
text_btn = gr.Button("Submit")
|
173 |
+
|
174 |
+
with gr.Column():
|
175 |
+
text_output = gr.JSON(label="Response")
|
176 |
+
|
177 |
+
text_btn.click(
|
178 |
+
fn=lambda query: text_interaction(query, student_id),
|
179 |
+
inputs=[text_input],
|
180 |
+
outputs=[text_output]
|
181 |
+
)
|
182 |
+
|
183 |
+
gr.Markdown("## Handwriting Recognition")
|
184 |
+
|
185 |
+
with gr.Row():
|
186 |
+
with gr.Column():
|
187 |
+
drawing_input = gr.Sketchpad(label="Draw an Equation")
|
188 |
+
drawing_btn = gr.Button("Recognize")
|
189 |
+
|
190 |
+
with gr.Column():
|
191 |
+
drawing_output = gr.JSON(label="Recognition Results")
|
192 |
+
|
193 |
+
# Convert drawing to base64 then process
|
194 |
+
drawing_btn.click(
|
195 |
+
fn=lambda img: handwriting_recognition(image_to_base64(img), student_id),
|
196 |
+
inputs=[drawing_input],
|
197 |
+
outputs=[drawing_output]
|
198 |
+
)
|
199 |
+
|
200 |
+
# Tab 4: Analytics
|
201 |
+
with gr.Tab("Analytics"):
|
202 |
+
gr.Markdown("## Student Performance")
|
203 |
+
analytics_btn = gr.Button("Generate Analytics Report")
|
204 |
+
timeframe = gr.Slider(minimum=7, maximum=90, value=30, step=1, label="Timeframe (days)")
|
205 |
+
analytics_output = gr.JSON(label="Performance Analytics")
|
206 |
+
|
207 |
+
analytics_btn.click(
|
208 |
+
fn=lambda days: get_student_analytics(student_id, days),
|
209 |
+
inputs=[timeframe],
|
210 |
+
outputs=[analytics_output]
|
211 |
+
)
|
212 |
+
|
213 |
+
gr.Markdown("## Error Pattern Analysis")
|
214 |
+
|
215 |
+
error_concept = gr.Dropdown(
|
216 |
+
choices=["math_algebra_basics", "math_algebra_linear_equations", "math_algebra_quadratic_equations"],
|
217 |
+
label="Select Concept for Error Analysis",
|
218 |
+
value="math_algebra_linear_equations"
|
219 |
+
)
|
220 |
+
error_btn = gr.Button("Analyze Errors")
|
221 |
+
error_output = gr.JSON(label="Error Pattern Analysis")
|
222 |
+
|
223 |
+
error_btn.click(
|
224 |
+
fn=lambda concept: analyze_error_patterns(student_id, concept),
|
225 |
+
inputs=[error_concept],
|
226 |
+
outputs=[error_output]
|
227 |
+
)
|
228 |
+
|
229 |
+
# Tab 5: Assessment Tools
|
230 |
+
with gr.Tab("Assessment Tools"):
|
231 |
+
gr.Markdown("## Create Assessment")
|
232 |
+
|
233 |
+
with gr.Row():
|
234 |
+
with gr.Column():
|
235 |
+
assess_concepts = gr.CheckboxGroup(
|
236 |
+
choices=["math_algebra_basics", "math_algebra_linear_equations", "math_algebra_quadratic_equations"],
|
237 |
+
label="Select Concepts",
|
238 |
+
value=["math_algebra_linear_equations"]
|
239 |
+
)
|
240 |
+
assess_questions = gr.Slider(minimum=1, maximum=10, value=3, step=1, label="Number of Questions")
|
241 |
+
assess_diff = gr.Slider(minimum=1, maximum=5, value=3, step=1, label="Difficulty")
|
242 |
+
create_assess_btn = gr.Button("Create Assessment")
|
243 |
+
|
244 |
+
with gr.Column():
|
245 |
+
assessment_output = gr.JSON(label="Generated Assessment")
|
246 |
+
|
247 |
+
create_assess_btn.click(
|
248 |
+
fn=lambda concepts, num, diff: create_assessment(concepts, num, diff),
|
249 |
+
inputs=[assess_concepts, assess_questions, assess_diff],
|
250 |
+
outputs=[assessment_output]
|
251 |
+
)
|
252 |
+
|
253 |
+
gr.Markdown("## Plagiarism Detection")
|
254 |
+
|
255 |
+
with gr.Row():
|
256 |
+
with gr.Column():
|
257 |
+
submission_input = gr.Textbox(
|
258 |
+
label="Student Submission",
|
259 |
+
lines=5,
|
260 |
+
value="The quadratic formula states that if ax² + bx + c = 0, then x = (-b ± √(b² - 4ac)) / 2a."
|
261 |
+
)
|
262 |
+
reference_input = gr.Textbox(
|
263 |
+
label="Reference Source",
|
264 |
+
lines=5,
|
265 |
+
value="According to the quadratic formula, for any equation in the form ax² + bx + c = 0, the solutions are x = (-b ± √(b² - 4ac)) / 2a."
|
266 |
+
)
|
267 |
+
plagiarism_btn = gr.Button("Check Originality")
|
268 |
+
|
269 |
+
with gr.Column():
|
270 |
+
plagiarism_output = gr.JSON(label="Originality Report")
|
271 |
+
|
272 |
+
plagiarism_btn.click(
|
273 |
+
fn=lambda sub, ref: check_submission_originality(sub, [ref]),
|
274 |
+
inputs=[submission_input, reference_input],
|
275 |
+
outputs=[plagiarism_output]
|
276 |
+
)
|
277 |
+
|
278 |
+
# Launch the app
|
279 |
+
if __name__ == "__main__":
|
280 |
+
demo.launch()
|
pyproject.toml
CHANGED
@@ -1,9 +1,12 @@
|
|
1 |
[project]
|
2 |
name = "tutorx-mcp"
|
3 |
version = "0.1.0"
|
4 |
-
description = "
|
5 |
readme = "README.md"
|
6 |
requires-python = ">=3.12"
|
7 |
dependencies = [
|
8 |
"mcp[cli]>=1.9.3",
|
|
|
|
|
|
|
9 |
]
|
|
|
1 |
[project]
|
2 |
name = "tutorx-mcp"
|
3 |
version = "0.1.0"
|
4 |
+
description = "Educational AI Tutor MCP Server"
|
5 |
readme = "README.md"
|
6 |
requires-python = ">=3.12"
|
7 |
dependencies = [
|
8 |
"mcp[cli]>=1.9.3",
|
9 |
+
"gradio>=4.19.0",
|
10 |
+
"numpy>=1.24.0",
|
11 |
+
"pillow>=10.0.0",
|
12 |
]
|
run.py
ADDED
@@ -0,0 +1,93 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""
|
2 |
+
Script to run either the MCP server or the Gradio interface
|
3 |
+
"""
|
4 |
+
|
5 |
+
import argparse
|
6 |
+
import importlib.util
|
7 |
+
import os
|
8 |
+
import sys
|
9 |
+
|
10 |
+
def load_module(name, path):
|
11 |
+
"""Load a module from path"""
|
12 |
+
spec = importlib.util.spec_from_file_location(name, path)
|
13 |
+
module = importlib.util.module_from_spec(spec)
|
14 |
+
spec.loader.exec_module(module)
|
15 |
+
return module
|
16 |
+
|
17 |
+
def run_mcp_server():
|
18 |
+
"""Run the MCP server"""
|
19 |
+
print("Starting TutorX MCP Server...")
|
20 |
+
main_module = load_module("main", "main.py")
|
21 |
+
|
22 |
+
# Access the mcp instance and run it
|
23 |
+
if hasattr(main_module, "mcp"):
|
24 |
+
main_module.mcp.run()
|
25 |
+
else:
|
26 |
+
print("Error: MCP server instance not found in main.py")
|
27 |
+
sys.exit(1)
|
28 |
+
|
29 |
+
def run_gradio_interface():
|
30 |
+
"""Run the Gradio interface"""
|
31 |
+
print("Starting TutorX Gradio Interface...")
|
32 |
+
app_module = load_module("app", "app.py")
|
33 |
+
|
34 |
+
# Run the Gradio demo
|
35 |
+
if hasattr(app_module, "demo"):
|
36 |
+
app_module.demo.launch()
|
37 |
+
else:
|
38 |
+
print("Error: Gradio demo not found in app.py")
|
39 |
+
sys.exit(1)
|
40 |
+
|
41 |
+
if __name__ == "__main__":
|
42 |
+
parser = argparse.ArgumentParser(description="Run TutorX MCP Server or Gradio Interface")
|
43 |
+
parser.add_argument(
|
44 |
+
"--mode",
|
45 |
+
choices=["mcp", "gradio", "both"],
|
46 |
+
default="mcp",
|
47 |
+
help="Run mode: 'mcp' for MCP server, 'gradio' for Gradio interface, 'both' for both"
|
48 |
+
)
|
49 |
+
parser.add_argument(
|
50 |
+
"--host",
|
51 |
+
default="127.0.0.1",
|
52 |
+
help="Host address to use"
|
53 |
+
)
|
54 |
+
parser.add_argument(
|
55 |
+
"--port",
|
56 |
+
type=int,
|
57 |
+
default=8000,
|
58 |
+
help="Port to use"
|
59 |
+
)
|
60 |
+
|
61 |
+
args = parser.parse_args()
|
62 |
+
|
63 |
+
if args.mode == "mcp":
|
64 |
+
# Set environment variables for MCP server
|
65 |
+
os.environ["MCP_HOST"] = args.host
|
66 |
+
os.environ["MCP_PORT"] = str(args.port)
|
67 |
+
run_mcp_server()
|
68 |
+
elif args.mode == "gradio":
|
69 |
+
run_gradio_interface()
|
70 |
+
elif args.mode == "both":
|
71 |
+
# For 'both' mode, we'll start MCP server in a separate process
|
72 |
+
import subprocess
|
73 |
+
import time
|
74 |
+
|
75 |
+
# Start MCP server in a background process
|
76 |
+
mcp_process = subprocess.Popen(
|
77 |
+
[sys.executable, "run.py", "--mode", "mcp", "--host", args.host, "--port", str(args.port)],
|
78 |
+
stdout=subprocess.PIPE,
|
79 |
+
stderr=subprocess.PIPE
|
80 |
+
)
|
81 |
+
|
82 |
+
# Give the MCP server a moment to start up
|
83 |
+
print("Starting MCP server in background...")
|
84 |
+
time.sleep(2)
|
85 |
+
|
86 |
+
try:
|
87 |
+
# Then start Gradio interface
|
88 |
+
run_gradio_interface()
|
89 |
+
finally:
|
90 |
+
# Make sure to terminate the MCP server process when exiting
|
91 |
+
print("Shutting down MCP server...")
|
92 |
+
mcp_process.terminate()
|
93 |
+
mcp_process.wait(timeout=5)
|