|
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_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/" |
|
|
|
|
|
|
|
|
|
|
|
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 = { |
|
|
|
"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, |
|
|
|
|
|
"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']) |
|
|
|
|
|
|
|
|
|
|
|
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) |
|
|
|
|
|
language_component.change(lambda x, old_state: old_state | {"language": x}, [language_component, state], [state]) |
|
|
|
|
|
level_component.change(lambda x, old_state: old_state | {"level": x}, [level_component, state], [state]) |
|
|
|
|
|
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]) |
|
|
|
|
|
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]) |
|
|
|
|
|
lo_component.change(lambda x, old_state: old_state | {"learningObjective": x}, [lo_component, state], [state]) |
|
|
|
|
|
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]) |
|
|
|
|
|
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]) |
|
|
|
|
|
like_btn.click(like) |
|
dislike_btn.click(dislike) |
|
|
|
|
|
|
|
generate_btn.click(generate_exercise, [state], [output_component]) |
|
|
|
demo.launch(show_api=False) |