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