Spaces:
Runtime error
Runtime error
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"<div class='markdown-body'><h3>Question:</h3><p>{output['Assignment']}</p><h3>Reference Answer:</h3><p>{output['Solutions'][0]}</p></div>" | |
elif type_of_exercise[exercise_type] in ["Multiple Choice", "Single Choice"]: | |
return f"""<div class='markdown-body'><h3>Question:</h3><p>{output['Assignment']}</p><h3>Options:</h3><p>{ | |
"<br/>".join(["β " + x for x in output['Solutions']] + ["β" + x for x in output['Distractors'] + output["EasilyDiscardableDistractors"]]) | |
}</p></div>""" | |
elif type_of_exercise[exercise_type] in ["Debate", "Essay", "Brainstorming", "Knowledge Exposition"]: | |
return f"<div class='markdown-body'><h3>Assignment:</h3><p>{output['Assignment']}</p></div>" | |
elif type_of_exercise[exercise_type] in ["Fill in the Blanks"]: | |
return f"""<div class='markdown-body'><h3>Paragraph:</h3><p>{output['Plus']}</p><h3>Question:</h3><p>{output['Assignment']}</p><h3>Options:</h3><p>{ | |
"<br/>".join(["β " + x for x in output['Solutions']] + ["β" + x for x in output['Distractors'] + output["EasilyDiscardableDistractors"]]) | |
}</p></div>""" | |
return f"<div class='markdown-body'><h3>Ouput</h3><p>{output['Solutions'][0]}</p></div>" | |
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("<iframe class='second-row' src='' allowfullscreen></iframe>") | |
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("<div class='markdown-body'><h3>Output</h3><p></p></div>") | |
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"<iframe class='second-row' src='{x}' allowfullscreen></iframe>"), [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) |