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 }} ] Keep all options concise (10 words or less each). """ 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() 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] 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) if not validated_questions: raise ValueError("No valid questions after validation") 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, None, [] total = len(self.current_questions) correct = 0 feedback = [] 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 feedback.append({ "is_correct": is_correct, "selected": a, "correct_answer": q["options"][q["correct_answer"]] }) else: feedback.append({ "is_correct": False, "selected": None, "correct_answer": q["options"][q["correct_answer"]] }) except (ValueError, TypeError) as e: print(f"Error processing answer {i}: {e}") feedback.append({ "is_correct": False, "selected": None, "correct_answer": q["options"][q["correct_answer"]] }) score = (correct / total) * 100 passed = score >= 80 return score, passed, feedback # Update the interface functions def create_quiz_interface(): quiz_app = QuizApp() with gr.Blocks(title="CertifyMe AI", theme=gr.themes.Soft()) as demo: current_questions = gr.State([]) # 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: feedback_box = gr.Markdown("") question_box = gr.Markdown("") answers = [] for i in range(5): with gr.Group(): radio = gr.Radio( choices=[], label=f"Question {i+1}", visible=False, interactive=True ) answers.append(radio) submit_btn = gr.Button("Submit Assessment", variant="primary", size="lg") # Step 3: Get Certified with gr.Tab("🎓 Step 3: Get Certified") as tab3: 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") def update_questions(text, num_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 = quiz_app.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(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(value="⚠️ Please answer all questions before submitting."), *[gr.update() for _ in range(5)], 0, "", gr.update(selected=1) ) score, passed, feedback = quiz_app.calculate_score(answers) # Create feedback HTML feedback_html = "# Assessment Results\n\n" for i, (q, f) in enumerate(zip(quiz_app.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"]}
{symbol} Your answer: {f["selected"]} {'' if f["is_correct"] else f'
Correct answer: {f["correct_answer"]}'}
""" # Add result message if passed: feedback_html += f"""

🎉 Congratulations!

You passed the assessment with a score of {score:.1f}%

Your certificate has been generated.

""" result_msg = f"🎉 Congratulations! You passed with {score:.1f}%" else: feedback_html += f"""

Please Try Again

Your score: {score:.1f}%

You need 80% or higher to pass and receive a certificate.

""" 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) ) # Event handlers generate_btn.click( fn=update_questions, inputs=[text_input, num_questions], outputs=[ question_box, feedback_box, *answers, current_questions, tabs ] ) submit_btn.click( fn=submit_quiz, inputs=[*answers, current_questions], outputs=[ feedback_box, *answers, score_display, result_message, tabs ] ) 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 # Certificate generation event handler 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()