Spaces:
Sleeping
Sleeping
import gradio as gr | |
from groq import Groq | |
import os | |
from PIL import Image, ImageDraw, ImageFont | |
from datetime import datetime | |
import json | |
import tempfile | |
from typing import List, Dict, Tuple, Optional | |
from dataclasses import dataclass | |
import subprocess | |
class Question: | |
question: str | |
options: List[str] | |
correct_answer: int | |
class QuizFeedback: | |
is_correct: bool | |
selected: Optional[str] | |
correct_answer: str | |
class QuizGenerator: | |
def __init__(self, api_key: str): | |
self.client = Groq(api_key=api_key) | |
def generate_questions(self, text: str, num_questions: int) -> List[Question]: | |
prompt = self._create_prompt(text, num_questions) | |
try: | |
response = self.client.chat.completions.create( | |
messages=[ | |
{ | |
"role": "system", | |
"content": "You are a quiz generator. Create clear questions with concise answer options." | |
}, | |
{ | |
"role": "user", | |
"content": prompt | |
} | |
], | |
model="llama-3.2-3b-preview", | |
temperature=0.3, | |
max_tokens=2048 | |
) | |
questions = self._parse_response(response.choices[0].message.content) | |
return self._validate_questions(questions, num_questions) | |
except Exception as e: | |
raise QuizGenerationError(f"Failed to generate questions: {str(e)}") | |
def _create_prompt(self, text: str, num_questions: int) -> str: | |
return f"""Create exactly {num_questions} multiple choice questions based on this text: | |
{text} | |
For each question: | |
1. Create a clear, concise question | |
2. Provide exactly 4 options | |
3. Mark the correct answer with the index (0-3) | |
4. Ensure options are concise and clear | |
Return ONLY a JSON array with this EXACT format - no other text: | |
[ | |
{{ | |
"question": "Question text here?", | |
"options": [ | |
"Brief option 1", | |
"Brief option 2", | |
"Brief option 3", | |
"Brief option 4" | |
], | |
"correct_answer": 0 | |
}} | |
] | |
Keep all options concise (10 words or less each). | |
""" | |
def _parse_response(self, response_text: str) -> List[Dict]: | |
response_text = response_text.replace("```json", "").replace("```", "").strip() | |
start_idx = response_text.find("[") | |
end_idx = response_text.rfind("]") | |
if start_idx == -1 or end_idx == -1: | |
raise ValueError("No valid JSON array found in response") | |
response_text = response_text[start_idx:end_idx + 1] | |
return json.loads(response_text) | |
def _validate_questions(self, questions: List[Dict], num_questions: int) -> List[Question]: | |
validated = [] | |
for q in questions: | |
if not self._is_valid_question(q): | |
continue | |
validated.append(Question( | |
question=q["question"].strip(), | |
options=[opt.strip()[:100] for opt in q["options"]], | |
correct_answer=int(q["correct_answer"]) % 4 | |
)) | |
if not validated: | |
raise ValueError("No valid questions after validation") | |
return validated[:num_questions] | |
def _is_valid_question(self, question: Dict) -> bool: | |
return ( | |
all(key in question for key in ["question", "options", "correct_answer"]) and | |
isinstance(question["options"], list) and | |
len(question["options"]) == 4 and | |
all(isinstance(opt, str) for opt in question["options"]) | |
) | |
class FontManager: | |
"""Manages font installation and loading for the certificate generator""" | |
def install_fonts(): | |
"""Install required fonts if they're not already present""" | |
try: | |
# Install fonts package | |
subprocess.run([ | |
"apt-get", "update", "-y" | |
], check=True) | |
subprocess.run([ | |
"apt-get", "install", "-y", | |
"fonts-liberation", # Liberation Sans fonts | |
"fontconfig", # Font configuration | |
"fonts-dejavu-core" # DejaVu fonts as fallback | |
], check=True) | |
# Clear font cache | |
subprocess.run(["fc-cache", "-f"], check=True) | |
print("Fonts installed successfully") | |
except subprocess.CalledProcessError as e: | |
print(f"Warning: Could not install fonts: {e}") | |
except Exception as e: | |
print(f"Warning: Unexpected error installing fonts: {e}") | |
def get_font_paths() -> Dict[str, str]: | |
"""Get the paths to the required fonts with multiple fallbacks""" | |
standard_paths = [ | |
"/usr/share/fonts", | |
"/usr/local/share/fonts", | |
"/usr/share/fonts/truetype", | |
"~/.fonts" | |
] | |
font_paths = { | |
'regular': None, | |
'bold': None | |
} | |
# Common font filenames to try | |
fonts_to_try = { | |
'regular': [ | |
'LiberationSans-Regular.ttf', | |
'DejaVuSans.ttf', | |
'FreeSans.ttf' | |
], | |
'bold': [ | |
'LiberationSans-Bold.ttf', | |
'DejaVuSans-Bold.ttf', | |
'FreeSans-Bold.ttf' | |
] | |
} | |
def find_font(font_name: str) -> Optional[str]: | |
"""Search for a font file in standard locations""" | |
for base_path in standard_paths: | |
for root, _, files in os.walk(os.path.expanduser(base_path)): | |
if font_name in files: | |
return os.path.join(root, font_name) | |
return None | |
# Try to find each font | |
for style in ['regular', 'bold']: | |
for font_name in fonts_to_try[style]: | |
font_path = find_font(font_name) | |
if font_path: | |
font_paths[style] = font_path | |
break | |
# If no fonts found, try using fc-match as fallback | |
if not all(font_paths.values()): | |
try: | |
for style in ['regular', 'bold']: | |
if not font_paths[style]: | |
result = subprocess.run( | |
['fc-match', '-f', '%{file}', 'sans-serif:style=' + style], | |
capture_output=True, | |
text=True | |
) | |
if result.returncode == 0 and result.stdout.strip(): | |
font_paths[style] = result.stdout.strip() | |
except Exception as e: | |
print(f"Warning: Could not use fc-match to find fonts: {e}") | |
return font_paths | |
class QuizGenerationError(Exception): | |
"""Exception raised for errors in quiz generation""" | |
pass | |
class CertificateGenerator: | |
def __init__(self): | |
self.certificate_size = (1200, 800) | |
self.border_color = '#4682B4' | |
self.background_color = '#F0F8FF' | |
# Install fonts if needed | |
FontManager.install_fonts() | |
self.font_paths = FontManager.get_font_paths() | |
def _load_fonts(self) -> Dict[str, ImageFont.FreeTypeFont]: | |
"""Load fonts with fallbacks""" | |
fonts = {} | |
try: | |
if self.font_paths['regular'] and self.font_paths['bold']: | |
fonts['title'] = ImageFont.truetype(self.font_paths['bold'], 60) | |
fonts['text'] = ImageFont.truetype(self.font_paths['regular'], 40) | |
fonts['subtitle'] = ImageFont.truetype(self.font_paths['regular'], 30) | |
else: | |
raise ValueError("No suitable fonts found") | |
except Exception as e: | |
print(f"Font loading error: {e}. Using default font.") | |
default = ImageFont.load_default() | |
fonts = { | |
'title': default, | |
'text': default, | |
'subtitle': default | |
} | |
return fonts | |
def generate( | |
self, | |
score: float, | |
name: str, | |
course_name: str, | |
company_logo: Optional[str] = None, | |
participant_photo: Optional[str] = None | |
) -> str: | |
""" | |
Generate a certificate with custom styling and optional logo/photo | |
""" | |
try: | |
certificate = self._create_base_certificate() | |
draw = ImageDraw.Draw(certificate) | |
fonts = self._load_fonts() | |
self._add_borders(draw) | |
self._add_content(draw, fonts, str(name), str(course_name), float(score)) | |
self._add_images(certificate, company_logo, participant_photo) | |
return self._save_certificate(certificate) | |
except Exception as e: | |
print(f"Error generating certificate: {e}") | |
return None | |
def _create_base_certificate(self) -> Image.Image: | |
return Image.new('RGB', self.certificate_size, self.background_color) | |
def _load_fonts(self) -> Dict[str, ImageFont.FreeTypeFont]: | |
try: | |
return { | |
'title': ImageFont.truetype("arial.ttf", 60), | |
'text': ImageFont.truetype("arial.ttf", 40), | |
'subtitle': ImageFont.truetype("arial.ttf", 30) | |
} | |
except Exception as e: | |
print(f"Font loading error: {e}. Using default font.") | |
default = ImageFont.load_default() | |
return {'title': default, 'text': default, 'subtitle': default} | |
def _add_borders(self, draw: ImageDraw.Draw): | |
# Main borders | |
draw.rectangle([20, 20, 1180, 780], outline=self.border_color, width=3) | |
draw.rectangle([40, 40, 1160, 760], outline=self.border_color, width=1) | |
# Decorative corners | |
self._add_decorative_corners(draw) | |
def _add_decorative_corners(self, draw: ImageDraw.Draw): | |
corner_size = 20 | |
corners = [ | |
# Top-left | |
[(20, 40), (20 + corner_size, 40)], | |
[(40, 20), (40, 20 + corner_size)], | |
# Top-right | |
[(1180 - corner_size, 40), (1180, 40)], | |
[(1160, 20), (1160, 20 + corner_size)], | |
# Bottom-left | |
[(20, 760), (20 + corner_size, 760)], | |
[(40, 780 - corner_size), (40, 780)], | |
# Bottom-right | |
[(1180 - corner_size, 760), (1180, 760)], | |
[(1160, 780 - corner_size), (1160, 780)] | |
] | |
for corner in corners: | |
draw.line(corner, fill=self.border_color, width=2) | |
def _add_content( | |
self, | |
draw: ImageDraw.Draw, | |
fonts: Dict[str, ImageFont.FreeTypeFont], | |
name: str, | |
course_name: str, | |
score: float | |
): | |
# Title and headers | |
draw.text((600, 100), "CertifyMe AI", font=fonts['title'], fill=self.border_color, anchor="mm") | |
draw.text((600, 160), "Certificate of Achievement", font=fonts['subtitle'], fill=self.border_color, anchor="mm") | |
# Clean inputs | |
name = str(name).strip() or "Participant" | |
course_name = str(course_name).strip() or "Assessment" | |
# Main content | |
content = [ | |
(300, "This is to certify that", 'black'), | |
(380, name, self.border_color), | |
(460, "has successfully completed", 'black'), | |
(540, course_name, self.border_color), | |
(620, f"with a score of {float(score):.1f}%", 'black'), | |
(700, datetime.now().strftime("%B %d, %Y"), 'black') | |
] | |
for y, text, color in content: | |
draw.text((600, y), text, font=fonts['text'], fill=color, anchor="mm") | |
def _add_images( | |
self, | |
certificate: Image.Image, | |
company_logo: Optional[str], | |
participant_photo: Optional[str] | |
): | |
if company_logo: | |
self._add_image(certificate, company_logo, (50, 50)) | |
if participant_photo: | |
self._add_image(certificate, participant_photo, (1000, 50)) | |
def _add_image(self, certificate: Image.Image, image_path: str, position: Tuple[int, int]): | |
try: | |
img = Image.open(image_path) | |
img.thumbnail((150, 150)) | |
certificate.paste(img, position) | |
except Exception as e: | |
print(f"Error adding image: {e}") | |
def _save_certificate(self, certificate: Image.Image) -> str: | |
temp_file = tempfile.NamedTemporaryFile(delete=False, suffix='.png') | |
certificate.save(temp_file.name, 'PNG', quality=95) | |
return temp_file.name | |
class QuizApp: | |
def __init__(self, api_key: str): | |
self.quiz_generator = QuizGenerator(api_key) | |
self.certificate_generator = CertificateGenerator() | |
self.current_questions: List[Question] = [] | |
def generate_questions(self, text: str, num_questions: int) -> Tuple[bool, List[Question]]: | |
""" | |
Generate quiz questions using the QuizGenerator | |
Returns (success, questions) tuple | |
""" | |
try: | |
questions = self.quiz_generator.generate_questions(text, num_questions) | |
self.current_questions = questions | |
return True, questions | |
except Exception as e: | |
print(f"Error generating questions: {e}") | |
return False, [] | |
def calculate_score(self, answers: List[Optional[str]]) -> Tuple[float, bool, List[QuizFeedback]]: | |
""" | |
Calculate the quiz score and generate feedback | |
Returns (score, passed, feedback) tuple | |
""" | |
if not answers or not self.current_questions: | |
return 0, False, [] | |
feedback = [] | |
correct = 0 | |
for question, answer in zip(self.current_questions, answers): | |
if answer is None: | |
feedback.append(QuizFeedback(False, None, question.options[question.correct_answer])) | |
continue | |
try: | |
selected_index = question.options.index(answer) | |
is_correct = selected_index == question.correct_answer | |
if is_correct: | |
correct += 1 | |
feedback.append(QuizFeedback( | |
is_correct, | |
answer, | |
question.options[question.correct_answer] | |
)) | |
except ValueError: | |
feedback.append(QuizFeedback(False, answer, question.options[question.correct_answer])) | |
score = (correct / len(self.current_questions)) * 100 | |
return score, score >= 80, feedback | |
def update_questions(self, text: str, num_questions: int) -> Tuple[gr.update, gr.update, List[gr.update], List[Question], gr.update]: | |
""" | |
Event handler for generating new questions | |
""" | |
if not text.strip(): | |
return ( | |
gr.update(value=""), | |
gr.update(value="⚠️ Please enter some text content to generate questions."), | |
*[gr.update(visible=False, choices=[]) for _ in range(5)], | |
[], | |
gr.update(selected=1) | |
) | |
success, questions = self.generate_questions(text, num_questions) | |
if not success or not questions: | |
return ( | |
gr.update(value=""), | |
gr.update(value="❌ Failed to generate questions. Please try again."), | |
*[gr.update(visible=False, choices=[]) for _ in range(5)], | |
[], | |
gr.update(selected=1) | |
) | |
# Create question display | |
questions_html = "# 📝 Assessment Questions\n\n" | |
questions_html += "> Please select one answer for each question.\n\n" | |
# Update radio buttons | |
updates = [] | |
for i, q in enumerate(questions): | |
questions_html += f"### Question {i+1}\n{q.question}\n\n" | |
updates.append(gr.update( | |
visible=True, | |
choices=q.options, | |
value=None, | |
label=f"Select your answer:" | |
)) | |
# Hide unused radio buttons | |
for i in range(len(questions), 5): | |
updates.append(gr.update(visible=False, choices=[])) | |
return ( | |
gr.update(value=questions_html), | |
gr.update(value=""), | |
*updates, | |
questions, | |
gr.update(selected=1) | |
) | |
def submit_quiz(self, q1: Optional[str], q2: Optional[str], q3: Optional[str], | |
q4: Optional[str], q5: Optional[str], questions: List[Question] | |
) -> Tuple[gr.update, List[gr.update], float, str, gr.update]: | |
""" | |
Event handler for quiz submission | |
""" | |
answers = [q1, q2, q3, q4, q5][:len(questions)] | |
if not all(a is not None for a in answers): | |
return ( | |
gr.update(value="⚠️ Please answer all questions before submitting."), | |
*[gr.update() for _ in range(5)], | |
0, | |
"", | |
gr.update(selected=1) | |
) | |
score, passed, feedback = self.calculate_score(answers) | |
# Create feedback HTML | |
feedback_html = "# Assessment Results\n\n" | |
for i, (q, f) in enumerate(zip(self.current_questions, feedback)): | |
color = "green" if f.is_correct else "red" | |
symbol = "✅" if f.is_correct else "❌" | |
feedback_html += f""" | |
### Question {i+1} | |
{q.question} | |
<div style="color: {color}; padding: 10px; margin: 5px 0; border-left: 3px solid {color};"> | |
{symbol} Your answer: {f.selected} | |
{'' if f.is_correct else f'<br>Correct answer: {f.correct_answer}'} | |
</div> | |
""" | |
# Add result message | |
if passed: | |
feedback_html += self._create_success_message(score) | |
result_msg = f"🎉 Congratulations! You passed with {score:.1f}%" | |
else: | |
feedback_html += self._create_failure_message(score) | |
result_msg = f"Score: {score:.1f}%. You need 80% to pass." | |
return ( | |
gr.update(value=feedback_html), | |
*[gr.update(visible=False) for _ in range(5)], | |
score, | |
result_msg, | |
gr.update(selected=2) | |
) | |
def _create_success_message(self, score: float) -> str: | |
return f""" | |
<div style="background-color: #e6ffe6; padding: 20px; margin-top: 20px; border-radius: 10px;"> | |
<h3 style="color: #008000;">🎉 Congratulations!</h3> | |
<p>You passed the assessment with a score of {score:.1f}%</p> | |
<p>Your certificate has been generated.</p> | |
</div> | |
""" | |
def _create_failure_message(self, score: float) -> str: | |
return f""" | |
<div style="background-color: #ffe6e6; padding: 20px; margin-top: 20px; border-radius: 10px;"> | |
<h3 style="color: #cc0000;">Please Try Again</h3> | |
<p>Your score: {score:.1f}%</p> | |
<p>You need 80% or higher to pass and receive a certificate.</p> | |
</div> | |
""" | |
def create_quiz_interface(): | |
if not os.getenv("GROQ_API_KEY"): | |
raise EnvironmentError("Please set your GROQ_API_KEY environment variable") | |
global quiz_app # Make quiz_app accessible to helper functions | |
quiz_app = QuizApp(os.getenv("GROQ_API_KEY")) | |
with gr.Blocks(title="CertifyMe AI", theme=gr.themes.Soft()) as demo: | |
# State management | |
current_questions = gr.State([]) | |
current_question_idx = gr.State(0) | |
answer_state = gr.State([None] * 5) # Store all answers | |
# Header | |
gr.Markdown(""" | |
# 🎓 CertifyMe AI | |
### Transform Your Knowledge into Recognized Achievements | |
""") | |
with gr.Tabs() as tabs: | |
# Profile Setup Tab | |
with gr.Tab("📋 Step 1: Profile Setup"): | |
with gr.Row(): | |
name = gr.Textbox(label="Full Name", placeholder="Enter your full name") | |
email = gr.Textbox(label="Email", placeholder="Enter your email") | |
text_input = gr.Textbox( | |
label="Learning Content", | |
placeholder="Enter the text content you want to be assessed on", | |
lines=10 | |
) | |
num_questions = gr.Slider( | |
minimum=1, | |
maximum=5, | |
value=3, | |
step=1, | |
label="Number of Questions" | |
) | |
with gr.Row(): | |
company_logo = gr.Image(label="Company Logo (Optional)", type="filepath") | |
participant_photo = gr.Image(label="Your Photo (Optional)", type="filepath") | |
generate_btn = gr.Button("Generate Assessment", variant="primary", size="lg") | |
# Assessment Tab | |
with gr.Tab("📝 Step 2: Take Assessment") as assessment_tab: | |
with gr.Column(visible=True) as question_box: | |
# Question section | |
with gr.Group(): | |
question_display = gr.Markdown("", label="Current Question") | |
current_options = gr.Radio( | |
choices=[], | |
label="Select your answer:", | |
visible=False | |
) | |
with gr.Row(): | |
prev_btn = gr.Button("← Previous", variant="secondary", size="sm") | |
question_counter = gr.Markdown("Question 1 of 3") | |
next_btn = gr.Button("Next →", variant="secondary", size="sm") | |
gr.Markdown("---") | |
with gr.Row(): | |
submit_btn = gr.Button( | |
"Submit Assessment", | |
variant="primary", | |
size="lg" | |
) | |
reset_btn = gr.Button( | |
"Reset Quiz", | |
variant="secondary", | |
size="lg" | |
) | |
# Results section | |
with gr.Group(visible=False) as results_group: | |
feedback_box = gr.Markdown("") | |
with gr.Row(): | |
view_cert_btn = gr.Button( | |
"View Certificate", | |
variant="primary", | |
size="lg", | |
visible=False | |
) | |
back_to_assessment = gr.Button( | |
"Back to Assessment", | |
variant="secondary", | |
size="lg", | |
visible=True | |
) | |
# Certification Tab | |
with gr.Tab("🎓 Step 3: Get Certified"): | |
score_display = gr.Number(label="Your Score") | |
result_message = gr.Markdown("") | |
course_name = gr.Textbox( | |
label="Certification Title", | |
value="Professional Assessment Certification" | |
) | |
certificate_display = gr.Image(label="Your Certificate") | |
# Helper Functions | |
def on_generate_questions(text: str, num_questions: int) -> List: | |
success, questions = quiz_app.generate_questions(text, num_questions) | |
tab_index = 1 # Index for "Take Assessment" tab | |
if not success: | |
return [ | |
"", | |
gr.update(choices=[], visible=False), | |
"", | |
gr.update(visible=False), | |
[], | |
0, | |
[None] * 5, | |
gr.update(selected=tab_index), | |
gr.update(visible=False), | |
gr.update(visible=False) | |
] | |
initial_answers = [None] * len(questions) | |
question = questions[0] | |
return [ | |
f"## Question 1\n{question.question}\n\nPlease select one answer:", | |
gr.update( | |
choices=question.options, | |
value=None, | |
visible=True, | |
label="Select your answer for Question 1:" | |
), | |
f"Question 1 of {len(questions)}", | |
gr.update(visible=True), | |
questions, | |
0, | |
initial_answers, | |
gr.update(selected=tab_index), | |
gr.update(visible=False), | |
gr.update(visible=False) | |
] | |
def goto_take_assessment(): | |
"""Navigate to Take Assessment tab""" | |
return gr.update(selected=1) | |
def goto_certificate(): | |
"""Navigate to Get Certified tab""" | |
return gr.update(selected=2) | |
# Fix View Certificate navigation | |
view_cert_btn.click( | |
fn=goto_certificate, | |
outputs=tabs | |
) | |
# Add "Back to Assessment" button in results view | |
back_to_assessment = gr.Button("Back to Assessment", visible=False) | |
back_to_assessment.click( | |
fn=goto_take_assessment, | |
outputs=tabs | |
) | |
def navigate(direction: int, current_idx: int, questions: List, answers: List, current_answer: Optional[str]) -> List: | |
if not questions: | |
return [ | |
0, | |
answers, | |
"", | |
gr.update(choices=[], value=None, visible=False), | |
"", | |
gr.update(visible=False) | |
] | |
new_answers = list(answers) | |
if current_answer and 0 <= current_idx < len(new_answers): | |
new_answers[current_idx] = current_answer | |
new_idx = max(0, min(len(questions) - 1, current_idx + direction)) | |
question = questions[new_idx] | |
return [ | |
new_idx, | |
new_answers, | |
f"## Question {new_idx + 1}\n{question.question}\n\nPlease select one answer:", | |
gr.update( | |
choices=question.options, | |
value=new_answers[new_idx] if new_idx < len(new_answers) else None, | |
visible=True, | |
label=f"Select your answer for Question {new_idx + 1}:" | |
), | |
f"Question {new_idx + 1} of {len(questions)}", | |
gr.update(visible=True) | |
] | |
def handle_prev(current_idx, questions, answers, current_answer): | |
return navigate(-1, current_idx, questions, answers, current_answer) | |
def handle_next(current_idx, questions, answers, current_answer): | |
return navigate(1, current_idx, questions, answers, current_answer) | |
def update_answer_state(answer, idx, current_answers): | |
new_answers = list(current_answers) | |
if 0 <= idx < len(new_answers): | |
new_answers[idx] = answer | |
return new_answers | |
def on_submit(questions, answers, current_idx, current_answer): | |
final_answers = list(answers) | |
if 0 <= current_idx < len(final_answers): | |
final_answers[current_idx] = current_answer | |
if not all(a is not None for a in final_answers[:len(questions)]): | |
return [ | |
"⚠️ Please answer all questions before submitting.", | |
gr.update(visible=True), | |
0, | |
"", | |
gr.update(visible=True), | |
gr.update(selected=1), | |
gr.update(visible=False) | |
] | |
score, passed, feedback = quiz_app.calculate_score(final_answers[:len(questions)]) | |
feedback_html = "# Assessment Results\n\n" | |
for i, (q, f) in enumerate(zip(questions, feedback)): | |
color = "green" if f.is_correct else "red" | |
symbol = "✅" if f.is_correct else "❌" | |
feedback_html += f"""### Question {i+1} | |
{q.question} | |
<div style="color: {color}; padding: 10px; margin: 5px 0; border-left: 3px solid {color};"> | |
{symbol} Your answer: {f.selected or "No answer"} | |
{'' if f.is_correct else f'<br>Correct answer: {f.correct_answer}'} | |
</div> | |
""" | |
result_msg = "🎉 Passed!" if passed else "Please try again" | |
if not passed: | |
feedback_html += f""" | |
<div style="background-color: #ffe6e6; padding: 20px; margin-top: 20px; border-radius: 10px;"> | |
<h3 style="color: #cc0000;">Please Try Again</h3> | |
<p>Your score: {score:.1f}%</p> | |
<p>You need 80% or higher to pass and receive a certificate.</p> | |
</div> | |
""" | |
return [ | |
feedback_html, | |
gr.update(visible=True), | |
score, | |
result_msg, | |
gr.update(visible=not passed), | |
gr.update(selected=1), | |
gr.update(visible=passed) | |
] | |
def reset_quiz(text, num_questions): | |
"""Handle quiz reset""" | |
return on_generate_questions(text, num_questions) | |
def view_certificate(): | |
"""Navigate to certificate tab""" | |
return gr.update(selected=2) | |
# Event handlers | |
generate_btn.click( | |
fn=on_generate_questions, | |
inputs=[text_input, num_questions], | |
outputs=[ | |
question_display, | |
current_options, | |
question_counter, | |
question_box, | |
current_questions, | |
current_question_idx, | |
answer_state, | |
tabs, | |
results_group, | |
view_cert_btn | |
] | |
).then( | |
fn=lambda: gr.update(selected=1), | |
outputs=tabs | |
) | |
prev_btn.click( | |
fn=handle_prev, | |
inputs=[ | |
current_question_idx, | |
current_questions, | |
answer_state, | |
current_options | |
], | |
outputs=[ | |
current_question_idx, | |
answer_state, | |
question_display, | |
current_options, | |
question_counter, | |
question_box | |
] | |
) | |
next_btn.click( | |
fn=handle_next, | |
inputs=[ | |
current_question_idx, | |
current_questions, | |
answer_state, | |
current_options | |
], | |
outputs=[ | |
current_question_idx, | |
answer_state, | |
question_display, | |
current_options, | |
question_counter, | |
question_box | |
] | |
) | |
current_options.change( | |
fn=update_answer_state, | |
inputs=[current_options, current_question_idx, answer_state], | |
outputs=answer_state | |
) | |
submit_btn.click( | |
fn=on_submit, | |
inputs=[ | |
current_questions, | |
answer_state, | |
current_question_idx, | |
current_options | |
], | |
outputs=[ | |
feedback_box, | |
results_group, | |
score_display, | |
result_message, | |
question_box, | |
tabs, | |
view_cert_btn | |
] | |
) | |
reset_btn.click( | |
fn=reset_quiz, | |
inputs=[text_input, num_questions], | |
outputs=[ | |
question_display, | |
current_options, | |
question_counter, | |
question_box, | |
current_questions, | |
current_question_idx, | |
answer_state, | |
tabs, | |
results_group, | |
view_cert_btn | |
] | |
) | |
view_cert_btn.click( | |
fn=lambda: gr.update(selected=2), | |
outputs=tabs | |
) | |
back_to_assessment.click( | |
fn=lambda: gr.update(selected=1), | |
outputs=tabs | |
) | |
score_display.change( | |
fn=lambda s, n, c, l, p: quiz_app.certificate_generator.generate(s, n, c, l, p) or gr.update(value=None), | |
inputs=[score_display, name, course_name, company_logo, participant_photo], | |
outputs=certificate_display | |
) | |
return demo | |
if __name__ == "__main__": | |
demo = create_quiz_interface() | |
demo.launch() |