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 | |
# Initialize Groq client | |
client = Groq( | |
api_key=os.getenv("GROQ_API_KEY") | |
) | |
class QuizApp: | |
def __init__(self): | |
self.current_questions = [] | |
def generate_questions(self, text, num_questions): | |
prompt = 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 | |
}} | |
] | |
""" | |
try: | |
response = 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 | |
) | |
response_text = response.choices[0].message.content.strip() | |
response_text = response_text.replace("```json", "").replace("```", "").strip() | |
response_text = response_text[response_text.find("["):response_text.rfind("]")+1] | |
questions = json.loads(response_text) | |
validated_questions = [] | |
for q in questions: | |
if not all(key in q for key in ["question", "options", "correct_answer"]): | |
continue | |
clean_options = [opt.strip()[:100] for opt in q["options"] if isinstance(opt, str)] | |
if len(clean_options) != 4: | |
continue | |
clean_q = { | |
"question": q["question"].strip(), | |
"options": clean_options, | |
"correct_answer": int(q["correct_answer"]) % 4 | |
} | |
validated_questions.append(clean_q) | |
self.current_questions = validated_questions[:num_questions] | |
return True, self.current_questions | |
except Exception as e: | |
print(f"Error in question generation: {str(e)}") | |
return False, [] | |
def calculate_score(self, answers): | |
if not answers or not self.current_questions: | |
return 0, [] | |
total = len(self.current_questions) | |
correct = 0 | |
results = [] | |
for i, (q, a) in enumerate(zip(self.current_questions, answers)): | |
try: | |
if a is not None: | |
selected_index = q["options"].index(a) | |
is_correct = selected_index == q["correct_answer"] | |
if is_correct: | |
correct += 1 | |
results.append({ | |
"is_correct": is_correct, | |
"correct_answer": q["options"][q["correct_answer"]] | |
}) | |
else: | |
results.append({ | |
"is_correct": False, | |
"correct_answer": q["options"][q["correct_answer"]] | |
}) | |
except (ValueError, TypeError) as e: | |
print(f"Error processing answer {i}: {e}") | |
results.append({ | |
"is_correct": False, | |
"correct_answer": q["options"][q["correct_answer"]] | |
}) | |
return (correct / total) * 100, results | |
def create_quiz_interface(): | |
quiz_app = QuizApp() | |
with gr.Blocks(title="CertifyMe AI", theme=gr.themes.Soft()) as demo: | |
# Header | |
gr.Markdown(""" | |
# 🎓 CertifyMe AI | |
### Transform Your Knowledge into Recognized Achievements | |
""") | |
# Tabs | |
with gr.Tabs() as tabs: | |
# Step 1: Profile Setup | |
with gr.Tab("📋 Step 1: Profile Setup") as tab1: | |
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") | |
# Step 2: Take Assessment | |
with gr.Tab("📝 Step 2: Take Assessment") as tab2: | |
question_box = gr.Markdown("") | |
answers = [] | |
feedback = [] | |
for i in range(5): | |
radio = gr.Radio( | |
choices=[], | |
label=f"Question {i+1}", | |
visible=False | |
) | |
answers.append(radio) | |
fb = gr.Markdown(visible=False) | |
feedback.append(fb) | |
submit_btn = gr.Button("Submit Assessment", variant="primary", size="lg") | |
result_message = gr.Markdown("") | |
# Step 3: Get Certified | |
with gr.Tab("🎓 Step 3: Get Certified") as tab3: | |
score_display = gr.Number(label="Your Score") | |
completion_message = gr.Markdown("") | |
course_name = gr.Textbox( | |
label="Certification Title", | |
value="Professional Assessment Certification" | |
) | |
certificate_display = gr.Image(label="Your Certificate") | |
# Store questions state | |
questions_state = gr.State([]) | |
def update_questions(text, num_questions): | |
if not text.strip(): | |
return ( | |
gr.update(value="Please enter content to generate questions."), | |
*[gr.update(visible=False) for _ in range(5)], # Radio buttons | |
*[gr.update(visible=False) for _ in range(5)], # Feedback | |
[], # questions state | |
1, # tab index | |
"Please enter content to generate questions." # result message | |
) | |
success, questions = quiz_app.generate_questions(text, num_questions) | |
if not success: | |
return ( | |
gr.update(value="Failed to generate questions. Please try again."), | |
*[gr.update(visible=False) for _ in range(5)], | |
*[gr.update(visible=False) for _ in range(5)], | |
[], | |
1, | |
"Failed to generate questions. Please try again." | |
) | |
# Create question display with embedded radio buttons | |
questions_md = "# 📝 Assessment Questions\n\n" | |
# Update radio buttons and create question display | |
radio_updates = [] | |
feedback_updates = [] | |
for i, q in enumerate(questions): | |
questions_md += f"### Question {i+1}\n{q['question']}\n\n" | |
radio_updates.append(gr.update( | |
choices=q["options"], | |
label=f"Select your answer for Question {i+1}:", | |
visible=True, | |
value=None | |
)) | |
feedback_updates.append(gr.update(visible=False, value="")) | |
# Hide unused radio buttons | |
for i in range(len(questions), 5): | |
radio_updates.append(gr.update(visible=False)) | |
feedback_updates.append(gr.update(visible=False)) | |
return ( | |
gr.update(value=questions_md), | |
*radio_updates, | |
*feedback_updates, | |
questions, | |
1, | |
"" | |
) | |
def submit_answers(q1, q2, q3, q4, q5, questions): | |
answers = [q1, q2, q3, q4, q5][:len(questions)] | |
if not all(a is not None for a in answers): | |
return [gr.update() for _ in range(5)], 0, "Please answer all questions before submitting." | |
score, results = quiz_app.calculate_score(answers) | |
# Create feedback for each answer | |
feedback_updates = [] | |
for i, result in enumerate(results): | |
if result["is_correct"]: | |
feedback_updates.append(gr.update( | |
visible=True, | |
value='<div style="color: green; margin-top: 10px;">✅ Correct!</div>' | |
)) | |
else: | |
feedback_updates.append(gr.update( | |
visible=True, | |
value=f'<div style="color: red; margin-top: 10px;">❌ Incorrect. Correct answer: {result["correct_answer"]}</div>' | |
)) | |
# Add empty updates for unused feedback slots | |
for _ in range(len(results), 5): | |
feedback_updates.append(gr.update(visible=False)) | |
# Create result message | |
if score >= 80: | |
message = f""" | |
### 🎉 Congratulations! | |
You passed the assessment with a score of {score:.1f}% | |
Your certificate has been generated. | |
""" | |
else: | |
message = f""" | |
### Please Try Again | |
Your score: {score:.1f}% | |
You need 80% or higher to pass and receive a certificate. | |
""" | |
return feedback_updates, score, message | |
# Event handlers | |
generate_btn.click( | |
fn=update_questions, | |
inputs=[text_input, num_questions], | |
outputs=[ | |
question_box, | |
*answers, | |
*feedback, | |
questions_state, | |
gr.State(1), | |
result_message | |
] | |
).then( | |
fn=lambda x: gr.update(selected=x), | |
inputs=gr.State(1), | |
outputs=tabs | |
) | |
submit_btn.click( | |
fn=submit_answers, | |
inputs=[*answers, questions_state], | |
outputs=[ | |
*feedback, | |
score_display, | |
result_message | |
] | |
).then( | |
fn=lambda x: gr.update(selected=x), | |
inputs=gr.State(2), | |
outputs=tabs | |
) | |
# Certificate generation event handler | |
score_display.change( | |
fn=generate_certificate, | |
inputs=[score_display, name, course_name, company_logo, participant_photo], | |
outputs=[certificate_display, completion_message] | |
) | |
def generate_certificate(score, name, course_name, company_logo=None, participant_photo=None): | |
""" | |
Generate a certificate with custom styling and optional logo/photo | |
Args: | |
score (float): Assessment score (0-100) | |
name (str): Participant's name | |
course_name (str): Name of the course/assessment | |
company_logo (str, optional): Path to company logo image | |
participant_photo (str, optional): Path to participant's photo | |
Returns: | |
str: Path to generated certificate image file | |
""" | |
try: | |
# Create certificate with a light blue background | |
certificate = Image.new('RGB', (1200, 800), '#F0F8FF') | |
draw = ImageDraw.Draw(certificate) | |
# Try to load fonts, fallback to default if necessary | |
try: | |
title_font = ImageFont.truetype("arial.ttf", 60) | |
text_font = ImageFont.truetype("arial.ttf", 40) | |
subtitle_font = ImageFont.truetype("arial.ttf", 30) | |
except Exception as e: | |
print(f"Font loading error: {e}. Using default font.") | |
title_font = ImageFont.load_default() | |
text_font = ImageFont.load_default() | |
subtitle_font = ImageFont.load_default() | |
# Add decorative border | |
border_color = '#4682B4' # Steel blue | |
draw.rectangle([20, 20, 1180, 780], outline=border_color, width=3) | |
# Add inner border | |
draw.rectangle([40, 40, 1160, 760], outline=border_color, width=1) | |
# Add certificate content | |
# Title | |
draw.text( | |
(600, 100), | |
"CertifyMe AI", | |
font=title_font, | |
fill='#4682B4', | |
anchor="mm" | |
) | |
# Subtitle | |
draw.text( | |
(600, 160), | |
"Certificate of Achievement", | |
font=subtitle_font, | |
fill='#4682B4', | |
anchor="mm" | |
) | |
# Main content | |
draw.text( | |
(600, 300), | |
"This is to certify that", | |
font=text_font, | |
fill='black', | |
anchor="mm" | |
) | |
# Participant name | |
name = name.strip() if name else "Participant" | |
draw.text( | |
(600, 380), | |
name, | |
font=text_font, | |
fill='#4682B4', | |
anchor="mm" | |
) | |
# Completion text | |
draw.text( | |
(600, 460), | |
"has successfully completed", | |
font=text_font, | |
fill='black', | |
anchor="mm" | |
) | |
# Course name | |
course_name = course_name.strip() if course_name else "Assessment" | |
draw.text( | |
(600, 540), | |
course_name, | |
font=text_font, | |
fill='#4682B4', | |
anchor="mm" | |
) | |
# Score | |
draw.text( | |
(600, 620), | |
f"with a score of {score:.1f}%", | |
font=text_font, | |
fill='black', | |
anchor="mm" | |
) | |
# Date | |
current_date = datetime.now().strftime("%B %d, %Y") | |
draw.text( | |
(600, 700), | |
current_date, | |
font=text_font, | |
fill='black', | |
anchor="mm" | |
) | |
# Add logo if provided | |
if company_logo is not None: | |
try: | |
logo = Image.open(company_logo) | |
# Maintain aspect ratio | |
logo.thumbnail((150, 150)) | |
# Calculate position to place logo | |
logo_x = 50 | |
logo_y = 50 | |
certificate.paste(logo, (logo_x, logo_y)) | |
except Exception as e: | |
print(f"Error adding logo: {e}") | |
# Add photo if provided | |
if participant_photo is not None: | |
try: | |
photo = Image.open(participant_photo) | |
# Maintain aspect ratio | |
photo.thumbnail((150, 150)) | |
# Calculate position to place photo | |
photo_x = 1000 | |
photo_y = 50 | |
certificate.paste(photo, (photo_x, photo_y)) | |
except Exception as e: | |
print(f"Error adding photo: {e}") | |
# Add decorative corners | |
corner_size = 20 | |
corner_color = '#4682B4' | |
# Top-left corner | |
draw.line([(20, 40), (20 + corner_size, 40)], fill=corner_color, width=2) | |
draw.line([(40, 20), (40, 20 + corner_size)], fill=corner_color, width=2) | |
# Top-right corner | |
draw.line([(1180 - corner_size, 40), (1180, 40)], fill=corner_color, width=2) | |
draw.line([(1160, 20), (1160, 20 + corner_size)], fill=corner_color, width=2) | |
# Bottom-left corner | |
draw.line([(20, 760), (20 + corner_size, 760)], fill=corner_color, width=2) | |
draw.line([(40, 780 - corner_size), (40, 780)], fill=corner_color, width=2) | |
# Bottom-right corner | |
draw.line([(1180 - corner_size, 760), (1180, 760)], fill=corner_color, width=2) | |
draw.line([(1160, 780 - corner_size), (1160, 780)], fill=corner_color, width=2) | |
# Save certificate | |
temp_file = tempfile.NamedTemporaryFile(delete=False, suffix='.png') | |
certificate.save(temp_file.name, 'PNG', quality=95) | |
return temp_file.name | |
except Exception as e: | |
print(f"Error generating certificate: {e}") | |
return None | |
score_display.change( | |
fn=generate_certificate, | |
inputs=[score_display, name, course_name, company_logo, participant_photo], | |
outputs=certificate_display | |
) | |
return demo | |
if __name__ == "__main__": | |
if not os.getenv("GROQ_API_KEY"): | |
print("Please set your GROQ_API_KEY environment variable") | |
exit(1) | |
demo = create_quiz_interface() | |
demo.launch() |