TestGen / app.py
vladyslav
Added states for users
220d9f2
import json
import os
import random
import gradio as gr
from dotenv import load_dotenv
from constants import MODELS, MODELS_PATH, BOOKS, CLASSES, STUDENTS, MODEL_NAME_TO_CHOICE
from utils import save_results, get_test_by_student_class_book
load_dotenv()
if os.getenv("ENV_TYPE") == "dev":
MODELS["Test"] = "test"
MODELS_PATH["Test"] = "test"
BOOKS["Test"] = "test.json"
MODEL_NAME_TO_CHOICE["test"] = "Test"
def get_question(questions_data, current_question_index):
question = questions_data[current_question_index]
question_text = f"### Питання {current_question_index + 1}/{len(questions_data)}:\n#### {question['question']}"
answers = [answer['answer'] for answer in question['answers']]
random.shuffle(answers)
return question_text, answers
def load_questions(model, book, student_name, class_name):
print("load_questions")
if not model or not book or student_name is None or class_name is None:
return (
"# Будь ласка, заповніть усі поля!", # question_output
[], # answer_radio
[], # questions_data_state
)
model_path = MODELS_PATH[model]
book_filename = BOOKS[book]
file_path = os.path.join("questions", model_path, book_filename)
if not os.path.exists(file_path):
return (
f"Файл за шляхом {file_path} не знайдено.", # question_output
[], # answer_radio
[], # questions_data_state
)
with open(file_path, "r", encoding="utf-8") as file:
questions_data = json.load(file)
if questions_data:
question_text, answers = get_question(questions_data, 0)
return (
question_text, # question_output
answers, # answer_radio
questions_data, # questions_data_state
)
else:
return (
"У файлі немає питань.", # question_output
[], # answer_radio
questions_data, # questions_data_state
)
def get_next_question(selected_answer, questions_data, current_question_index, answers_log):
print("get_next_question answers_log", answers_log)
if not selected_answer:
question_text, answers = get_question(questions_data, current_question_index)
return (
question_text, # question_radio
gr.update(choices=answers, value=None, interactive=True, visible=True), # answer_radio
gr.update(visible=True), # next_button
gr.update(visible=False), # feedback_input
gr.update(visible=False), # submit_feedback_button
gr.update(visible=False), # rating_text
gr.update(visible=False), # question_correct
gr.update(visible=False), # answers_correct
gr.update(visible=False), # interesting_question
gr.update(visible=False, value=""), # feedback_questions_output
current_question_index, # current_question_index_state
answers_log, # answers_log_state
"", # feedback_questions_state
)
# Writing answer in log
answers_log.append({
"selected": selected_answer,
"isCorrect": any(answer['answer'] == selected_answer and answer['isCorrect'] for answer in
questions_data[current_question_index]['answers'])
})
print("get_next_question updated answers_log", answers_log)
# Move to the next question
current_question_index += 1
if current_question_index < len(questions_data):
question_text, answers = get_question(questions_data, current_question_index)
return (
question_text, # question_radio
gr.update(choices=answers, value=None, interactive=True, visible=True), # answer_radio
gr.update(visible=True), # next_button
gr.update(visible=False), # feedback_input
gr.update(visible=False), # submit_feedback_button
gr.update(visible=False), # rating_text
gr.update(visible=False), # question_correct
gr.update(visible=False), # answers_correct
gr.update(visible=False), # interesting_question
gr.update(visible=False, value=""), # feedback_questions_output
current_question_index, # current_question_index_state
answers_log, # answers_log_state
"", # feedback_questions_state
)
else:
# All questions are completed — ask for feedback
question_text = "#### Дякуємо за участь у тесті!\n#### Нижче можете переглянути ваші та правильні відповіді\n#### Залиште, будь ласка, відгук про тест в кінці сторінки.\n---"
feedback_questions = prepare_questions_for_feedback(questions_data, answers_log)
return (
question_text, # question_radio
gr.update(visible=False, value=None), # answer_radio
gr.update(visible=False), # next_button
gr.update(visible=True), # feedback_input
gr.update(visible=True), # submit_feedback_button
gr.update(visible=True), # rating_text
gr.update(visible=True), # question_correct
gr.update(visible=True), # answers_correct
gr.update(visible=True), # interesting_question
gr.update(visible=True, value=feedback_questions), # feedback_questions_output
0, # current_question_index_state
answers_log, # answers_log_state
feedback_questions, # feedback_questions_state
)
def summarize_results(student_name,
class_name,
model,
book,
feedback,
question_correct,
answers_correct,
interesting_question,
questions_data,
answers_log,
feedback_questions,
available_models):
print("summarize_results questions_data", questions_data)
questions = []
if question_correct is None or answers_correct is None or interesting_question is None:
return (
"### Залиште відгук про тест!", # question_output
gr.update(visible=True), # feedback_input
gr.update(visible=True), # submit_feedback_button
gr.update(visible=True), # question_correct
gr.update(visible=True), # answers_correct
gr.update(visible=True), # interesting_question
gr.update(visible=True), # rating_text
gr.update(visible=True, value=feedback_questions), # feedback_questions_output
gr.update(visible=True), # feedback_not_provided
questions_data, # questions_data_state
0, # current_question_index_state
answers_log, # answers_log_state
feedback_questions, # feedback_questions_state
available_models, # available_models_state
)
print("summarize_results answers_log", answers_log)
for question, answer in zip(questions_data, answers_log):
questions.append({
"question": question,
"answer": answer
})
correct_answers_count = sum(1 for answer in answers_log if answer['isCorrect'])
total_questions = len(questions_data)
grade_12 = (correct_answers_count / total_questions) * 12
print("summarize_results questions_data before save", questions_data)
save_results(
student_name,
class_name,
MODELS[model],
book,
questions,
feedback,
question_correct,
answers_correct,
interesting_question,
grade_12,
correct_answers_count,
)
summary = (
f"#### Тест для моделі {model} закінчено!\n\n"
f"#### Ви відповіли правильно на {correct_answers_count} з {total_questions} питань.\n\n"
f"#### Оцінка за 12-бальною шкалою: {grade_12:.2f}.\n\n"
)
available_models_list = available_models
print(available_models_list)
updated_available_models = [m for m in available_models_list if
m != model] if student_name != "Вчитель" else available_models_list
print(list(updated_available_models))
print(updated_available_models)
return (
summary, # question_output
gr.update(visible=False, value=""), # feedback_input
gr.update(visible=False), # submit_feedback_button
gr.update(visible=False, value=None), # question_correct
gr.update(visible=False, value=None), # answers_correct
gr.update(visible=False, value=None), # interesting_question
gr.update(visible=False), # rating_text
gr.update(visible=False, value=""), # feedback_questions_output
gr.update(visible=False), # feedback_not_provided
gr.update(choices=list(updated_available_models), value=None, visible=True), # model_radio
[], # questions_data_state
0, # current_question_index_state
[], # answers_log_state
"", # feedback_questions_state
updated_available_models, # available_models_state
)
def prepare_questions_for_feedback(questions, answer_log):
feedback = []
for i, question in enumerate(questions):
question_text = f"#### Питання: {question['question']}"
quote = f"#### Цитата: *{question['textPart']}*"
user_answer = answer_log[i]['selected']
answers_text = "\n".join(
[
f"- {'**(Правильна)**' if ans['isCorrect'] else ''} {'**(Обрана)**' if ans['answer'] == user_answer else ''} {ans['answer']}"
for ans in question['answers']
]
)
feedback.append(f"{question_text}\n{quote}\n#### Відповіді:\n{answers_text}\n---")
return "\n".join(feedback)
def update_students(class_name, available_models):
print('update_students: ', available_models)
students = STUDENTS.get(class_name, [])
return (
gr.update(choices=students, value=None, visible=True), # student_name_input
gr.update(choices=[], value=None, visible=False), # book_radio
gr.update(choices=available_models, value=None, visible=False), # model_radio
gr.update(value=""), # questions_output
gr.update(choices=[], value=None, visible=False), # answer_radio
gr.update(visible=False), # next_button
gr.update(value="", visible=False), # feedback_questions_output
gr.update(visible=False), # rating_text
gr.update(visible=False), # feedback_not_provided
gr.update(value=None, visible=False), # question_correct_radio
gr.update(value=None, visible=False), # answers_correct_radio
gr.update(value=None, visible=False), # interesting_question_radio
gr.update(value="", visible=False), # feedback_input
gr.update(visible=False), # submit_feedback_button
[], # questions_data_state
0, # current_question_index_state
[], # answers_log_state
"", # feedback_questions_state
available_models, # available_models_state
)
def handle_student_name_change(student_name, available_models):
print("handle_student_name_change", available_models)
if student_name == "Вчитель":
available_models = list(MODELS.keys())
print("handle_student_name_change available_models: ", available_models)
if not student_name:
return (
gr.update(choices=[], value=None, visible=False), # book_radio
gr.update(choices=available_models, value=None, visible=False), # model_radio
gr.update(value=""), # questions_output
gr.update(choices=[], value=None, visible=False), # answer_radio
gr.update(visible=False), # next_button
gr.update(value="", visible=False), # feedback_questions_output
gr.update(visible=False), # rating_text
gr.update(visible=False), # feedback_not_provided
gr.update(value=None, visible=False), # question_correct_radio
gr.update(value=None, visible=False), # answers_correct_radio
gr.update(value=None, visible=False), # interesting_question_radio
gr.update(value="", visible=False), # feedback_input
gr.update(visible=False), # submit_feedback_button
[], # questions_data_state
0, # current_question_index_state
[], # answers_log_state
"", # feedback_questions_state
available_models, # available_models_state
)
return (
gr.update(choices=list(BOOKS.keys()), value=None, visible=True), # book_radio
gr.update(choices=available_models, value=None, visible=False), # model_radio
gr.update(value=""), # questions_output
gr.update(choices=[], value=None, visible=False), # answer_radio
gr.update(visible=False), # next_button
gr.update(value="", visible=False), # feedback_questions_output
gr.update(visible=False), # rating_text
gr.update(visible=False), # feedback_not_provided
gr.update(value=None, visible=False), # question_correct_radio
gr.update(value=None, visible=False), # answers_correct_radio
gr.update(value=None, visible=False), # interesting_question_radio
gr.update(value="", visible=False), # feedback_input
gr.update(visible=False), # submit_feedback_button
[], # questions_data_state
0, # current_question_index_state
[], # answers_log_state
"", # feedback_questions_state
available_models, # available_models_state
)
def filter_models(student_name, class_name, book, available_models):
print('filter_models: ', available_models)
if not book:
return (
gr.update(choices=available_models, value=None, visible=False), # model_radio
gr.update(value=""), # questions_output
gr.update(choices=[], value=None, visible=False), # answer_radio
gr.update(visible=False), # next_button
gr.update(value="", visible=False), # feedback_questions_output
gr.update(visible=False), # rating_text
gr.update(visible=False), # feedback_not_provided
gr.update(value=None, visible=False), # question_correct_radio
gr.update(value=None, visible=False), # answers_correct_radio
gr.update(value=None, visible=False), # interesting_question_radio
gr.update(value="", visible=False), # feedback_input
gr.update(visible=False), # submit_feedback_button
[], # questions_data_state
0, # current_question_index_state
[], # answers_log_state
"", # feedback_questions_state
available_models, # available_models_state
)
if student_name == "Вчитель":
return (
gr.update(choices=MODELS.keys(), value=None, visible=True), # model_radio
gr.update(value=""), # questions_output
gr.update(choices=[], value=None, visible=False), # answer_radio
gr.update(visible=False), # next_button
gr.update(value="", visible=False), # feedback_questions_output
gr.update(visible=False), # rating_text
gr.update(visible=False), # feedback_not_provided
gr.update(value=None, visible=False), # question_correct_radio
gr.update(value=None, visible=False), # answers_correct_radio
gr.update(value=None, visible=False), # interesting_question_radio
gr.update(value="", visible=False), # feedback_input
gr.update(visible=False), # submit_feedback_button
[], # questions_data_state
0, # current_question_index_state
[], # answers_log_state
"", # feedback_questions_state
MODELS.keys(), # available_models_state
)
tests = get_test_by_student_class_book(student_name, class_name, book)
available_models_names = list(MODELS.values())
for test in tests:
if test.get("model") in available_models_names:
available_models_names.remove(test.get("model"))
print("Available models (before update):", available_models_names)
if not available_models_names:
return gr.update(choices=[], label="Немає доступних моделей", value=None, visible=True)
models_list = []
for model in list(available_models_names):
models_list.append(MODEL_NAME_TO_CHOICE.get(model))
return (
gr.update(choices=models_list, value=None, visible=True), # model_radio
gr.update(value=""), # questions_output
gr.update(choices=[], value=None, visible=False), # answer_radio
gr.update(visible=False), # next_button
gr.update(value="", visible=False), # feedback_questions_output
gr.update(visible=False), # rating_text
gr.update(visible=False), # feedback_not_provided
gr.update(value=None, visible=False), # question_correct_radio
gr.update(value=None, visible=False), # answers_correct_radio
gr.update(value=None, visible=False), # interesting_question_radio
gr.update(value="", visible=False), # feedback_input
gr.update(visible=False), # submit_feedback_button
[], # questions_data_state
0, # current_question_index_state
[], # answers_log_state
"", # feedback_questions_state
models_list, # available_models_state
)
def handle_model_change(model, available_models):
print("handle_model_change", available_models)
print("handle_model_change")
if model is not None:
return (
gr.update(value=""), # questions_output
gr.update(choices=[], value=None, visible=False), # answer_radio
gr.update(visible=False), # next_button
gr.update(value="", visible=False), # feedback_questions_output
gr.update(visible=False), # rating_text
gr.update(visible=False), # feedback_not_provided
gr.update(value=None, visible=False), # question_correct_radio
gr.update(value=None, visible=False), # answers_correct_radio
gr.update(value=None, visible=False), # interesting_question_radio
gr.update(value="", visible=False), # feedback_input
gr.update(visible=False), # submit_feedback_button
[], # questions_data_state
0, # current_question_index_state
[], # answers_log_state
"", # feedback_questions_state
available_models, # available_models_state
)
return (
gr.update(visible=True), # questions_output
gr.update(choices=[], value=None, visible=False), # answer_radio
gr.update(visible=False), # next_button
gr.update(value="", visible=False), # feedback_questions_output
gr.update(visible=False), # rating_text
gr.update(visible=False), # feedback_not_provided
gr.update(value=None, visible=False), # question_correct_radio
gr.update(value=None, visible=False), # answers_correct_radio
gr.update(value=None, visible=False), # interesting_question_radio
gr.update(value="", visible=False), # feedback_input
gr.update(visible=False), # submit_feedback_button
[], # questions_data_state
0, # current_question_index_state
[], # answers_log_state
"", # feedback_questions_state
available_models, # available_models_state
)
with gr.Blocks() as demo:
with gr.Column():
gr.Markdown("## Оберіть модель та книгу, щоб завантажити питання")
class_name_input = gr.Dropdown(choices=CLASSES, label="Ваш клас", value=None)
student_name_input = gr.Dropdown(label="Ваше ім'я", value=None, visible=False)
book_radio = gr.Radio([], label="Оберіть книгу", interactive=True, value=None, visible=False)
model_radio = gr.Radio([], label="Оберіть модель", interactive=True, value=None, visible=False)
load_button = gr.Button("Завантажити питання")
question_output = gr.Markdown(label="Питання")
answer_radio = gr.Radio([], label="Варіанти відповіді", interactive=True, visible=False)
next_button = gr.Button("Наступне питання", visible=False)
feedback_questions_output = gr.Markdown(label="Пройдені питання", value="Nothing", visible=False)
rating_text = gr.Markdown(
"### Шкала оцінювання:\n\n#### -2 — дуже погано\n\n#### -1 — погано\n\n#### 0 — задовільно\n\n#### 1 — добре\n\n#### 2 — відмінно",
visible=False)
feedback_not_provided = gr.Markdown("# Заповніть поля відгуку та залишіть оцінку!", visible=False)
question_correct_radio = gr.Radio([-2, -1, 0, 1, 2], label="Чи коректно поставлені запитання?", visible=False,
interactive=True)
answers_correct_radio = gr.Radio([-2, -1, 0, 1, 2], label="Чи коректно поставлені варіанти відповідей?",
visible=False, interactive=True)
interesting_question_radio = gr.Radio([-2, -1, 0, 1, 2], label="Чи цікаві були запитання?", visible=False,
interactive=True)
feedback_input = gr.Textbox(label="Будь-який коментар про тест (за бажанням)", visible=False)
submit_feedback_button = gr.Button("Завершити тест", visible=False)
questions_data_state = gr.State([])
current_question_index_state = gr.State(0)
answers_log_state = gr.State([]) # Log for saving answers
feedback_questions_state = gr.State("")
available_models_state = gr.State([])
class_name_input.change(
update_students,
inputs=[
class_name_input,
available_models_state,
],
outputs=[
student_name_input,
book_radio,
model_radio,
question_output,
answer_radio,
next_button,
feedback_questions_output,
rating_text,
feedback_not_provided,
question_correct_radio,
answers_correct_radio,
interesting_question_radio,
feedback_input,
submit_feedback_button,
questions_data_state,
current_question_index_state,
answers_log_state,
feedback_questions_state,
available_models_state,
]
)
student_name_input.change(
handle_student_name_change,
inputs=[
student_name_input,
available_models_state,
],
outputs=[
book_radio,
model_radio,
question_output,
answer_radio,
next_button,
feedback_questions_output,
rating_text,
feedback_not_provided,
question_correct_radio,
answers_correct_radio,
interesting_question_radio,
feedback_input,
submit_feedback_button,
questions_data_state,
current_question_index_state,
answers_log_state,
feedback_questions_state,
available_models_state,
]
)
book_radio.change(
filter_models,
inputs=[
student_name_input,
class_name_input,
book_radio,
available_models_state,
],
outputs=[
model_radio,
question_output,
answer_radio,
next_button,
feedback_questions_output,
rating_text,
feedback_not_provided,
question_correct_radio,
answers_correct_radio,
interesting_question_radio,
feedback_input,
submit_feedback_button,
questions_data_state,
current_question_index_state,
answers_log_state,
feedback_questions_state,
available_models_state,
]
)
model_radio.change(
handle_model_change,
inputs=[
model_radio,
available_models_state,
],
outputs=[
question_output,
answer_radio,
next_button,
feedback_questions_output,
rating_text,
feedback_not_provided,
question_correct_radio,
answers_correct_radio,
interesting_question_radio,
feedback_input,
submit_feedback_button,
questions_data_state,
current_question_index_state,
answers_log_state,
feedback_questions_state,
available_models_state,
]
)
def update_question(model, book, student_name, class_name, available_models):
question, answers, questions_data = load_questions(model, book, student_name, class_name)
print("update_question", questions_data)
is_field_filled = len(answers) > 0
return (
question, # question_output
gr.update(choices=answers, interactive=True, visible=is_field_filled), # answer_radio
gr.update(visible=is_field_filled), # next_button
gr.update(visible=False, value=""), # feedback_input
gr.update(visible=False), # submit_feedback_button
gr.update(visible=False), # rating_text
gr.update(visible=False, value=None), # question_correct
gr.update(visible=False, value=None), # answers_correct
gr.update(visible=False, value=None), # interesting_question
gr.update(visible=False, value=""), # feedback_questions_output
gr.update(visible=False), # feedback_not_provided
questions_data, # questions_data_state
0, # current_question_index_state
[], # answers_log_state
"", # feedback_questions_state
available_models, # available_models_state
)
load_button.click(
update_question,
inputs=[
model_radio,
book_radio,
student_name_input,
class_name_input,
available_models_state,
],
outputs=[
question_output,
answer_radio,
next_button,
feedback_input,
submit_feedback_button,
rating_text,
question_correct_radio,
answers_correct_radio,
interesting_question_radio,
feedback_questions_output,
feedback_not_provided,
questions_data_state,
current_question_index_state,
answers_log_state,
feedback_questions_state,
available_models_state,
]
)
next_button.click(
get_next_question,
inputs=[
answer_radio,
questions_data_state,
current_question_index_state,
answers_log_state
],
outputs=[
question_output,
answer_radio,
next_button,
feedback_input,
submit_feedback_button,
rating_text,
question_correct_radio,
answers_correct_radio,
interesting_question_radio,
feedback_questions_output,
current_question_index_state,
answers_log_state,
feedback_questions_state,
]
)
submit_feedback_button.click(
summarize_results,
inputs=[
student_name_input,
class_name_input,
model_radio,
book_radio,
feedback_input,
question_correct_radio,
answers_correct_radio,
interesting_question_radio,
questions_data_state,
answers_log_state,
feedback_questions_state,
available_models_state,
],
outputs=[
question_output,
feedback_input,
submit_feedback_button,
question_correct_radio,
answers_correct_radio,
interesting_question_radio,
rating_text,
feedback_questions_output,
feedback_not_provided,
model_radio,
questions_data_state,
current_question_index_state,
answers_log_state,
feedback_questions_state,
available_models_state,
]
)
if __name__ == "__main__":
demo.launch(server_name="0.0.0.0")