import gradio as gr from dotenv import load_dotenv from .objective_handlers import ( process_files, process_files_and_generate_questions, process_user_objectives, process_user_objectives_and_generate_questions, ) from .question_handlers import generate_questions from .edit_handlers import load_quiz_for_editing, load_file_for_editing, accept_and_next, go_previous, save_and_download from .formatting import format_quiz_for_ui from .run_manager import get_run_manager from models import MODELS # Load environment variables load_dotenv() # Set to False to disable saving output files (folders, logs, JSON, markdown). # Tab 3 download of edited questions still works. SAVE_OUTPUTS = True def create_ui(): """Create the Gradio UI.""" get_run_manager().save_outputs = SAVE_OUTPUTS with gr.Blocks(title="Quiz Generator V3") as app: gr.Markdown("# Quiz Generator V3") gr.Markdown("Upload course materials and generate learning objectives and quiz questions.") with gr.Tab("Generate Learning Objectives"): with gr.Row(): with gr.Column(): mode_radio = gr.Radio( choices=["Generate from course materials", "Use my own learning objectives"], value="Generate from course materials", label="Learning Objectives Source" ) files_input = gr.File( file_count="multiple", label="Upload Course Materials (.vtt, .srt, .ipynb, .md)", file_types=[".ipynb", ".vtt", ".srt", ".md"], type="filepath" ) # Generate-mode controls num_objectives = gr.Slider(minimum=1, maximum=20, value=4, step=1, label="Number of Learning Objectives per Run") # Manual-mode input (hidden by default) user_objectives_input = gr.Textbox( label="Enter Learning Objectives (one per line)", lines=8, placeholder=( "Enter each learning objective on a new line.\n" "Leading numbers or letters are stripped automatically.\n\n" "Example:\n" "1. Identify the key stages of the data engineering lifecycle\n" "2. Articulate a mental framework for building solutions\n" "3. Identify upstream and downstream stakeholders" ), visible=False ) with gr.Accordion("Advanced Options", open=False): num_runs = gr.Dropdown( choices=["1", "2", "3", "4", "5"], value="2", label="Number of Generation Runs" ) model_dropdown = gr.Dropdown( choices=MODELS, value="gpt-5.2", label="Model" ) incorrect_answer_model_dropdown = gr.Dropdown( choices=MODELS, value="gpt-5.2", label="Model for Incorrect Answer Suggestions" ) temperature_dropdown = gr.Dropdown( choices=["0.0", "0.1", "0.2", "0.3", "0.4", "0.5", "0.6", "0.7", "0.8", "0.9", "1.0"], value="1.0", label="Temperature (0.0: Deterministic, 1.0: Creative)" ) # Buttons — generate_button / process_user_obj_button toggle by mode; # generate_all_button is always visible generate_button = gr.Button("Generate Learning Objectives") process_user_obj_button = gr.Button("Process Learning Objectives", visible=False) generate_all_button = gr.Button("Generate all", variant="primary") with gr.Column(): status_output = gr.Textbox(label="Status") objectives_output = gr.Textbox(label="Best-in-Group Learning Objectives", lines=10) grouped_output = gr.Textbox(label="All Grouped Learning Objectives", lines=10) raw_ungrouped_output = gr.Textbox(label="Raw Ungrouped Learning Objectives (Debug)", lines=10) with gr.Tab("Generate Questions"): with gr.Row(): with gr.Column(): objectives_input = gr.Textbox(label="Learning Objectives JSON", lines=10, max_lines=10) num_questions_slider = gr.Slider(minimum=1, maximum=10, value=10, step=1, label="Number of questions") with gr.Accordion("Advanced Options", open=False): model_dropdown_q = gr.Dropdown( choices=MODELS, value="gpt-5.2", label="Model" ) temperature_dropdown_q = gr.Dropdown( choices=["0.0", "0.1", "0.2", "0.3", "0.4", "0.5", "0.6", "0.7", "0.8", "0.9", "1.0"], value="1.0", label="Temperature (0.0: Deterministic, 1.0: Creative)" ) num_runs_q = gr.Slider(minimum=1, maximum=5, value=2, step=1, label="Number of Question Generation Runs") generate_q_button = gr.Button("Generate Questions") with gr.Column(): status_q_output = gr.Textbox(label="Status") best_questions_output = gr.Textbox(label="Ranked Best-in-Group Questions", lines=10) all_questions_output = gr.Textbox(label="All Grouped Questions", lines=10) formatted_quiz_output = gr.Textbox(label="Formatted Quiz", lines=15) with gr.Tab("Propose/Edit Question"): # State for editing flow questions_state = gr.State([]) index_state = gr.State(0) edited_state = gr.State([]) with gr.Row(): with gr.Column(): edit_status = gr.Textbox(label="Status", interactive=False) with gr.Accordion("Load questions from file (.md or .yml)", open=False): file_upload = gr.File( label="Upload a quiz file (.md or .yml)", file_types=[".md", ".yml", ".yaml"], type="filepath" ) load_file_button = gr.Button("Load from file") edit_button = gr.Button("Edit questions from Tab 2", variant="primary") question_editor = gr.Textbox( label="Question", lines=15, interactive=True, placeholder="Click 'Edit questions' to load the generated quiz." ) with gr.Row(): prev_button = gr.Button("Previous") next_button = gr.Button("Accept & Next", variant="primary") download_button = gr.Button("Download edited quiz") download_file = gr.File(label="Download", interactive=False) # --- Mode toggle: show/hide controls based on selected source --- def _toggle_objective_mode(mode): is_generate = mode == "Generate from course materials" return ( gr.update(visible=is_generate), # num_objectives gr.update(visible=is_generate), # num_runs gr.update(visible=not is_generate), # user_objectives_input gr.update(visible=is_generate), # generate_button gr.update(visible=not is_generate), # process_user_obj_button ) mode_radio.change( _toggle_objective_mode, inputs=[mode_radio], outputs=[num_objectives, num_runs, user_objectives_input, generate_button, process_user_obj_button] ) # --- "Generate all" dispatcher: routes based on current mode --- def _generate_all(mode, files, num_obj, num_r, user_obj_text, model, ia_model, temp, model_q, temp_q, num_q, num_runs_q_val): if mode == "Generate from course materials": return process_files_and_generate_questions( files, num_obj, num_r, model, ia_model, temp, model_q, temp_q, num_q, num_runs_q_val ) else: return process_user_objectives_and_generate_questions( files, user_obj_text, model, ia_model, temp, model_q, temp_q, num_q, num_runs_q_val ) # Set up event handlers generate_button.click( process_files, inputs=[files_input, num_objectives, num_runs, model_dropdown, incorrect_answer_model_dropdown, temperature_dropdown], outputs=[status_output, objectives_output, grouped_output, raw_ungrouped_output] ) process_user_obj_button.click( process_user_objectives, inputs=[files_input, user_objectives_input, model_dropdown, incorrect_answer_model_dropdown, temperature_dropdown], outputs=[status_output, objectives_output, grouped_output, raw_ungrouped_output] ) generate_all_button.click( _generate_all, inputs=[ mode_radio, files_input, num_objectives, num_runs, user_objectives_input, model_dropdown, incorrect_answer_model_dropdown, temperature_dropdown, model_dropdown_q, temperature_dropdown_q, num_questions_slider, num_runs_q ], outputs=[ status_output, objectives_output, grouped_output, raw_ungrouped_output, status_q_output, best_questions_output, all_questions_output, formatted_quiz_output ] ) objectives_output.change( lambda x: x, inputs=[objectives_output], outputs=[objectives_input] ) generate_q_button.click( generate_questions, inputs=[objectives_input, model_dropdown_q, temperature_dropdown_q, num_questions_slider, num_runs_q], outputs=[status_q_output, best_questions_output, all_questions_output, formatted_quiz_output] ) best_questions_output.change( format_quiz_for_ui, inputs=[best_questions_output], outputs=[formatted_quiz_output] ) edit_button.click( load_quiz_for_editing, inputs=[formatted_quiz_output], outputs=[edit_status, question_editor, questions_state, index_state, edited_state, next_button] ) load_file_button.click( load_file_for_editing, inputs=[file_upload], outputs=[edit_status, question_editor, questions_state, index_state, edited_state, next_button] ) next_button.click( accept_and_next, inputs=[question_editor, questions_state, index_state, edited_state], outputs=[edit_status, question_editor, questions_state, index_state, edited_state, next_button] ) prev_button.click( go_previous, inputs=[question_editor, questions_state, index_state, edited_state], outputs=[edit_status, question_editor, questions_state, index_state, edited_state, next_button] ) download_button.click( save_and_download, inputs=[question_editor, questions_state, index_state, edited_state], outputs=[edit_status, download_file] ) return app if __name__ == "__main__": app = create_ui() app.launch()