import os
import requests
from io import BytesIO
from datetime import date
import tempfile
import gradio as gr
from PIL import Image, ImageDraw, ImageFont
from huggingface_hub import upload_file
from criteria import check_certification as check_certification_criteria
from org import join_finishers_org
CERTIFYING_ORG_LINKEDIN_ID = os.getenv("CERTIFYING_ORG_LINKEDIN_ID", "000000")
COURSE_TITLE = os.getenv("COURSE_TITLE", "AI Agents Fundamentals")
def download_profile_picture(profile_url: str):
"""Download profile picture from URL."""
try:
response = requests.get(profile_url)
response.raise_for_status() # Ensure we got a proper response
return Image.open(BytesIO(response.content))
except Exception as e:
print(f"Error downloading profile picture from {profile_url}: {e}")
# Return a default fallback image (a plain white 100x100 image)
return Image.new("RGB", (100, 100), color="white")
def generate_certificate(certificate_path: str, name: str, profile_url: str):
"""Generate certificate image and PDF."""
im = Image.open(certificate_path)
d = ImageDraw.Draw(im)
name_font = ImageFont.truetype("Quattrocento-Regular.ttf", 100)
date_font = ImageFont.truetype("Quattrocento-Regular.ttf", 48)
# Capitalize first letter of each name
name = name.title()
# Add name
d.text((1000, 740), name, fill="black", anchor="mm", font=name_font)
# Add profile picture just below the name
profile_img = download_profile_picture(profile_url)
profile_img = profile_img.resize((100, 100))
im.paste(im=profile_img, box=(350, 700))
# Add date
d.text((1480, 1170), str(date.today()), fill="black", anchor="mm", font=date_font)
# Save PDF
pdf = im.convert("RGB")
pdf.save("certificate.pdf")
return im, "certificate.pdf"
def create_linkedin_button(username: str, cert_url: str | None) -> str:
"""Create LinkedIn 'Add to Profile' button HTML."""
current_year = date.today().year
current_month = date.today().month
# Use the dataset certificate URL if available, otherwise fallback to default
certificate_url = cert_url or "https://huggingface.co/agents-course-finishers"
linkedin_params = {
"startTask": "CERTIFICATION_NAME",
"name": COURSE_TITLE,
"organizationName": "Hugging Face",
"organizationId": CERTIFYING_ORG_LINKEDIN_ID,
"organizationIdissueYear": str(current_year),
"issueMonth": str(current_month),
"certUrl": certificate_url,
"certId": username, # Using username as cert ID
}
# Build the LinkedIn button URL
base_url = "https://www.linkedin.com/profile/add?"
params = "&".join(
f"{k}={requests.utils.quote(v)}" for k, v in linkedin_params.items()
)
button_url = base_url + params
message = f"""
"""
message += """
You are now an Agents Course Finisher
"""
return message
def upload_certificate_to_hub(username: str, certificate_img) -> str:
"""Upload certificate to the dataset hub and return the URL."""
# Save image to temporary file
with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmp:
certificate_img.save(tmp.name)
try:
# Upload file to hub
repo_id = "agents-course/certificates"
path_in_repo = f"certificates/{username}/{date.today()}.png"
upload_file(
path_or_fileobj=tmp.name,
path_in_repo=path_in_repo,
repo_id=repo_id,
repo_type="dataset",
token=os.getenv("HF_TOKEN"),
)
# Construct the URL to the image
cert_url = (
f"https://huggingface.co/datasets/{repo_id}/resolve/main/{path_in_repo}"
)
# Clean up temp file
os.unlink(tmp.name)
return cert_url
except Exception as e:
print(f"Error uploading certificate: {e}")
os.unlink(tmp.name)
return None
def check_certification(
token: gr.OAuthToken | None,
profile: gr.OAuthProfile | None,
custom_name: str | None = None,
):
"""Check certification status for logged-in user."""
if token is None or profile is None:
gr.Warning("Please log in to Hugging Face before checking certification!")
return None, None, gr.Row.update(visible=False)
username = profile.username
# Use custom name if provided, otherwise fall back to profile name
name = custom_name.strip() if custom_name and custom_name.strip() else profile.name
profile_url = profile.picture
if not username:
return (
"Please login with your Hugging Face account to check certification status",
None,
gr.Row.update(visible=False),
)
# Check certification criteria
gr.Info("Collecting data from your course progress...")
result = check_certification_criteria(username)
# Generate certificate if passed
if result.passed and result.certificate_path:
certificate_img, pdf_path = generate_certificate(
certificate_path=result.certificate_path,
name=name,
profile_url=profile_url,
)
# Upload certificate to hub and get URL
cert_url = upload_certificate_to_hub(username, certificate_img)
# Add LinkedIn button for passed certificates
linkedin_button = create_linkedin_button(username, cert_url)
result_message = f"{result.message}\n\n{linkedin_button}"
else:
certificate_img = None
pdf_path = None
result_message = result.message
return (
gr.update(visible=True, value=result_message, label="Grade"),
gr.update(visible=result.passed, value=certificate_img, label="Certificate"),
)
with gr.Blocks() as demo:
gr.Markdown(
"""
# Get your Certificate of Fundamentals of Agents 🎓
The certification process is completely free.
To earn this certificate, you need to complete Unit 1 of the Agents Course and **pass 80% of the final quiz**.
Once you receive your certificate, don't hesitate to share it on X (and tag @huggingface) as well as on LinkedIn so that we can congratulate you.
"""
)
with gr.Row():
# Add login button
gr.LoginButton()
# Add optional name input
custom_name_input = gr.Textbox(
label="Custom Name (Optional)",
placeholder="Enter your name as you want it to appear on the certificate",
info="Leave empty to use your Hugging Face profile name",
)
check_progress_button = gr.Button(value="Get My Certificate")
output_text = gr.Markdown(visible=False, sanitize_html=False)
output_img = gr.Image(type="pil", visible=False)
check_progress_button.click(
fn=check_certification,
inputs=[custom_name_input],
outputs=[output_text, output_img],
).then(
fn=join_finishers_org,
)
if __name__ == "__main__":
demo.launch(debug=True)