import datetime
import os
import json
import gradio as gr
import requests
import firebase_admin
from itertools import chain
from firebase_admin import db, credentials
def clamp(x, minimum, maximum):
return max(minimum, min(x, maximum))
#################################################################################################################################################
# API calls
#################################################################################################################################################
# read secret api key
API_KEY = os.environ['ApiKey']
FIREBASE_API_KEY = os.environ['FirebaseSecret']
FIREBASE_URL = os.environ['FirebaseURL']
SETUP_MODEL = os.environ['SETUP_MODEL']
creds = credentials.Certificate(json.loads(FIREBASE_API_KEY))
firebase_app = firebase_admin.initialize_app(creds, {'databaseURL': FIREBASE_URL})
firebase_data_ref = db.reference("data")
firebase_current_ref = None
BASE_URL = "https://skapi.polyglot-edu.com/"
##################################################################
# Data Layer
##################################################################
levels = ["Primary School", "Middle School", "High School", "College", "Academy"]
languages = ["English", "Italian", "French", "German", "Spanish"]
type_of_exercise = ["Open Question", "Short Answer Question", "True or False", "Fill in the Blanks", "Single Choice", "Multiple Choice", "Debate", "Essay", "Brainstorming", "Knowledge Exposition"]
bloom_levels = ["Remembering", "Understanding", "Applying", "Analyzing", "Evaluating", "Creating"]
def like():
global firebase_current_ref
if firebase_current_ref is not None:
firebase_current_ref.update({"like": 1})
gr.Info("Generated text liked.")
else:
gr.Warning("No generated text to vote.")
def dislike():
global firebase_current_ref
if firebase_current_ref is not None:
firebase_current_ref.update({"like": -1})
gr.Info("Generated text disliked.")
else:
gr.Warning("No generated text to vote.")
def analyze_resource(url):
response = requests.post(
BASE_URL + "MaterialAnalyser/analyseMaterial",
headers={"ApiKey": API_KEY, "SetupModel": str(SETUP_MODEL)},
json={
"material": url
},
timeout=20
)
if response.status_code != 200:
raise gr.Error(f"""Failed to analyze resource: {response.text}
Please try again with different parameters""")
return response.json()
def generate_learning_objective(topic, context, level):
response = requests.post(
BASE_URL + "LearningObjectiveGenerator/generateLearningObjective",
headers={"ApiKey": API_KEY, "SetupModel": str(SETUP_MODEL)},
json={
"topic": topic,
"context": context,
"level": levels.index(level)
},
timeout=20
)
if response.status_code != 200:
raise gr.Error(f"""Failed to generate learning objective: {response.text}
Please try again with different parameters""")
return response.json()
def generate_exercise(state):
def find_key(d, item):
for key, value in d.items():
if item in value:
return key
return None
gr.Info(f'Generating exercise with Bloom level: {find_key(state["learningObjectiveList"], state["learningObjective"])}')
print(state["correctAnswersNumber"], state["distractorsNumber"], state["easyDistractorsNumber"])
try:
_json = {
# filled in with the data from the previous steps
"macroSubject": state['MacroSubject'],
"title": state['Title'],
"level": levels.index(state['level']),
"learningObjective": state['learningObjective'],
"bloomLevel": bloom_levels.index(find_key(state["learningObjectiveList"], state['learningObjective'])),
"language": state['Language'],
"material": state['material_url'],
"assignmentType": [topic['Type'] for topic in state['MainTopics'] if topic['Topic'] == state['topic']][0],
"topic": state['topic'],
"temperature": 0,
# to be filled in manually
"typeOfActivity": state['typeOfExercise'],
"correctAnswersNumber": state["correctAnswersNumber"],
"distractorsNumber": state["distractorsNumber"],
"easilyDiscardableDistractorsNumber": state["easyDistractorsNumber"],
}
except KeyError as e:
raise gr.Error(f"Missing key: {e}")
print(json)
step3 = requests.post(
BASE_URL + "ActivityGenerator/generateActivity",
headers={"ApiKey": API_KEY, "SetupModel": str(SETUP_MODEL)},
json=_json,
timeout=20
)
if step3.status_code != 200:
raise gr.Error(f"""Failed to generate exercise: {step3.text}
Please try again with different parameters""")
global firebase_current_ref
firebase_current_ref = firebase_data_ref.push({
"type": "open_question",
"input": _json,
"output": step3.json(),
"datetime": str(datetime.datetime.now()),
"like": 0,
})
return format_output(step3.json(), state['typeOfExercise'])
##################################################################
# UI Layer
##################################################################
def format_output(output, exercise_type):
if type_of_exercise[exercise_type] in ["Open Question", "Short Answer Question", "True or False"]:
return f"
Question:
{output['Assignment']}
Reference Answer:
{output['Solutions'][0]}
"
elif type_of_exercise[exercise_type] in ["Multiple Choice", "Single Choice"]:
return f"""Question:
{output['Assignment']}
Options:
{
"
".join(["✅" + x for x in output['Solutions']] + ["❌" + x for x in output['Distractors'] + output["EasilyDiscardableDistractors"]])
}
"""
elif type_of_exercise[exercise_type] in ["Debate", "Essay", "Brainstorming", "Knowledge Exposition"]:
return f"Assignment:
{output['Assignment']}
"
elif type_of_exercise[exercise_type] in ["Fill in the Blanks"]:
return f"""Paragraph:
{output['Plus']}
Question:
{output['Assignment']}
Options:
{
"
".join(["✅" + x for x in output['Solutions']] + ["❌" + x for x in output['Distractors'] + output["EasilyDiscardableDistractors"]])
}
"""
return f"Ouput
{output['Solutions'][0]}
"
def on_url_change(url, state):
for key in ['topic', 'learningObjective', 'learningObjectiveList', 'material_url']:
if key in state:
del state[key]
material = analyze_resource(url)
topics = [topic['Topic'] for topic in material['MainTopics']]
state = state | material
state['material_url'] = url
lo_component = gr.Dropdown(label="Learning Objective", choices=[], value="placeholder", interactive=True)
return [gr.Radio(label="Topic", choices=topics, interactive=True), lo_component, state]
def on_topic_change(topic, old_state):
old_state['topic'] = topic
learning_objective = generate_learning_objective(topic, f"A {old_state['level']} class", old_state["level"])
old_state['learningObjectiveList'] = learning_objective
possible_objectives = list(chain.from_iterable(learning_objective.values()))
return [gr.Dropdown(label="Learning Objective", choices=possible_objectives, value=possible_objectives[0], interactive=True), old_state]
css = """
body, html {
margin: 0;
height: 100%; /* Full height */
width: 100%; /* Full width */
overflow: hidden; /* Prevent scrolling */
}
.interface, .block-container {
display: flex;
flex-direction: column;
height: 100%; /* Full height */
width: 100%; /* Full width */
}
.row-content {
height: 90vh; /* Full height */
overflow: auto; /* Scrollable content */
}
.column-content {
display: flex;
flex-direction: column;
flex: 1; /* Flexibly take up available space */
height: 100%; /* Full height */
}
iframe.second-row {
width: 100%; /* Full width */
height: 60vh; /* Full height */
border: none; /* No border */
background-color: #f9f9f9; /* Light background */
}
/* Base style for Markdown content */
.markdown-body {
font-family: 'Helvetica Neue', Arial, sans-serif; /* Clean and modern font */
line-height: 1.6; /* Ample line height for readability */
font-size: 16px; /* Standard font size for readability */
color: #333; /* Dark grey color for text for less strain */
background-color: #f9f9f9; /* Light background to reduce glare */
padding: 20px; /* Padding around text */
border-radius: 8px; /* Slightly rounded corners for a softer look */
box-shadow: 0 2px 4px rgba(0,0,0,0.1); /* Subtle shadow for depth */
max-width: 800px; /* Max width to maintain optimal line length */
margin: 20px auto; /* Center align the Markdown content */
max-height: 70vh; /* Max height to prevent scrolling */
overflow-y: auto; /* Auto-scroll for overflow */
}
/* Headings with increased weight and spacing for clear hierarchy */
.markdown-body h1,
.markdown-body h2,
.markdown-body h3,
.markdown-body h4,
.markdown-body h5,
.markdown-body h6 {
color: #2a2a2a; /* Slightly darker than the text color */
margin-top: 24px;
margin-bottom: 16px;
font-weight: bold;
}
.markdown-body h1 {
font-size: 2em; /* Larger size for main titles */
}
.markdown-body h2 {
font-size: 1.5em;
}
.markdown-body h3 {
font-size: 1.17em;
}
/* Paragraphs with bottom margin for better separation */
.markdown-body p {
margin-bottom: 16px;
}
/* Links with a subtle color to stand out */
.markdown-body a {
color: #0656b5;
text-decoration: none; /* No underline */
}
.markdown-body a:hover,
.markdown-body a:focus {
text-decoration: underline; /* Underline on hover/focus for visibility */
}
/* Lists styled with padding and margin for clarity */
.markdown-body ul,
.markdown-body ol {
padding-left: 20px;
margin-top: 0;
margin-bottom: 16px;
}
.markdown-body li {
margin-bottom: 8px; /* Space between list items */
}
/* Blockquotes with a left border and padding for emphasis */
.markdown-body blockquote {
padding: 10px 20px;
margin: 0;
border-left: 5px solid #ccc; /* Subtle grey line to indicate quotes */
background-color: #f0f0f0; /* Very light background for contrast */
font-style: italic;
}
/* Code styling for inline and blocks */
.markdown-body code {
font-family: monospace;
background-color: #eee; /* Light grey background */
padding: 2px 4px;
border-radius: 3px; /* Rounded corners for code blocks */
font-size: 90%;
}
.markdown-body pre {
background-color: #f4f4f4; /* Slightly different background for distinction */
border: 1px solid #ddd; /* Border for definition */
padding: 10px; /* Padding inside code blocks */
overflow: auto; /* Auto-scroll for overflow */
line-height: 1.45;
border-radius: 5px;
}
"""
def make_visible(components, visible):
return [gr.update(visible=visible) for _ in range(components)]
with gr.Blocks(title="Educational AI", css=css) as demo:
state = gr.State({"level": levels[-1], "language": "English", "correctAnswersNumber": 1, "easyDistractorsNumber": 1, "distractorsNumber": 1, "typeOfExercise": 0})
with gr.Row(elem_classes=["row-content"]):
with gr.Column(scale=3, elem_classes=["column-content"]):
level_component = gr.Dropdown(label="Level", choices=levels, value=levels[-1])
url_component = gr.Textbox(label="Input URL - Do not provide pages that are too long (e.g. Wikipedia pages) or too short, as they may not be analyzed correctly\n Example: https://lilianweng.github.io/posts/2024-02-05-human-data-quality/", placeholder="Enter URL here...")
iframe_component = gr.HTML("")
with gr.Column(scale=3):
language_component = gr.Dropdown(languages, label="Exercise Language", value="English")
topic_component = gr.Radio(label="Topic", choices=["placeholder"], interactive=True)
lo_component = gr.Dropdown(label="Learning Objective", choices=[], value="placeholder", interactive=True)
question_type_component = gr.Dropdown(label="Question Type", choices=type_of_exercise, type="index", value=0)
correct_answers_component = gr.Number(value=1, minimum=1, maximum=3, step=1, label="Number of correct answers", visible=False, interactive=True)
easy_distractors_component = gr.Number(value=1, minimum=0, maximum=8, step=1, label="Number of easy distractors", visible=False, interactive=True)
distractors_component = gr.Number(value=1, minimum=0, maximum=8, step=1, label="Number of distractors", visible=False, interactive=True)
generate_btn = gr.Button("Generate Question")
with gr.Column(scale=3):
output_component = gr.HTML("")
with gr.Row():
like_btn = gr.Button("👍 like")
dislike_btn = gr.Button("👎 dislike")
gr.Button(value="📝 Fill our Questionnaire", link="https://forms.gle/T8CS5CiQgPbKUdeM9", interactive=True)
# on language change
language_component.change(lambda x, old_state: old_state | {"language": x}, [language_component, state], [state])
# on level change
level_component.change(lambda x, old_state: old_state | {"level": x}, [level_component, state], [state])
# on url change
url_component.change(lambda x: gr.HTML(f""), [url_component], [iframe_component])
url_component.change(lambda x: gr.Info(f"Analyzing resource at {x}..."), [url_component], [])
url_component.change(on_url_change, [url_component, state], [topic_component, lo_component, state])
# on topic change
topic_component.change(lambda x: gr.Info(f"Generating learning objective for {x}..."), [topic_component], [])
topic_component.change(on_topic_change, [topic_component, state], [lo_component, state])
# on lo change
lo_component.change(lambda x, old_state: old_state | {"learningObjective": x}, [lo_component, state], [state])
# on question type change
question_type_component.change(lambda x, old_state: old_state | {"typeOfExercise": x}, [question_type_component, state], [state])
question_type_component.change(lambda x: make_visible(3, x in [type_of_exercise.index(y) for y in ["Multiple Choice", "Single Choice", "Fill in the Blanks"]]), [question_type_component], [correct_answers_component, easy_distractors_component, distractors_component])
# exercise-specific settings
correct_answers_component.change(lambda x, old_state: old_state | {"correctAnswersNumber": int(x)}, [correct_answers_component, state], [state])
easy_distractors_component.change(lambda x, old_state: old_state | {"easyDistractorsNumber": int(x)}, [correct_answers_component, state], [state])
distractors_component.change(lambda x, old_state: old_state | {"distractorsNumber": int(x)}, [correct_answers_component, state], [state])
# on like/dislike
like_btn.click(like)
dislike_btn.click(dislike)
# on generate question
generate_btn.click(generate_exercise, [state], [output_component])
demo.launch(show_api=False)