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("

Output

") 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)