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 whoami, 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."""
response = requests.get(profile_url)
return Image.open(BytesIO(response.content))
def generate_certificate(
certificate_path: str, first_name: str, last_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)
name = f"{first_name} {last_name}"
# 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 get_user_info(oauth_token):
"""Get user info from HF token."""
if oauth_token is None:
return None, None, None, None
try:
user_info = whoami(oauth_token.token)
username = user_info["name"]
name_parts = user_info["fullname"].split(" ", 1)
first_name = name_parts[0]
last_name = name_parts[1] if len(name_parts) > 1 else ""
profile_url = user_info["avatarUrl"]
return username, first_name, last_name, profile_url
except:
return None, None, None, None
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",
)
# 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):
"""Check certification status for logged-in user."""
if token is None:
gr.Warning("Please log in to Hugging Face before checking certification!")
return None, None, None, gr.Row.update(visible=False)
username, first_name, last_name, profile_url = get_user_info(token)
if not username:
return (
"Please login with your Hugging Face account to check certification status",
None,
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,
first_name=first_name,
last_name=last_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=pdf_path, label="Download Certificate"),
gr.update(visible=result.passed, value=certificate_img, label="Certificate"),
)
def create_gradio_interface():
"""Create Gradio web interface with OAuth login."""
with gr.Blocks() as demo:
gr.Markdown("""
# Get your Hugging Face Course Certificate 🎓
The certification process is completely free.
To receive your certificate, you need to **pass 80% of the quiz**.
There's **no deadlines, the course is self-paced**.
Don't hesitate to share your certificate on Twitter
(tag @huggingface) and on Linkedin.
""")
# Add login button
gr.LoginButton()
check_progress_button = gr.Button(value="Check My Progress")
output_text = gr.Markdown(visible=False, sanitize_html=False)
output_img = gr.Image(type="pil", visible=False)
output_pdf = gr.File(visible=False)
check_progress_button.click(
fn=check_certification,
outputs=[output_text, output_pdf, output_img],
).then(
fn=join_finishers_org,
)
return demo
if __name__ == "__main__":
demo = create_gradio_interface()
demo.launch(debug=True)