Spaces:
Running
Running
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""" | |
<a href="{button_url}" target="_blank" style="display: block; margin-top: 20px; text-align: center;"> | |
<img src="https://download.linkedin.com/desktop/add2profile/buttons/en_US.png" | |
alt="LinkedIn Add to Profile button"> | |
</a> | |
""" | |
message += """ | |
<a href="https://huggingface.co/agents-course-finishers" target="_blank" | |
style="display: inline-block; background-color: #fff7e0; border: 2px solid #ffa500; | |
border-radius: 10px; padding: 10px 20px; margin: 20px auto; text-align: center; | |
text-decoration: none; color: #000; white-space: nowrap;"> | |
<img src="https://agents-course-unit1-certification-app.hf.space/gradio_api/file=/usr/local/lib/python3.10/site-packages/gradio/icons/huggingface-logo.svg" | |
style="display: inline-block; height: 20px; vertical-align: middle; margin-right: 10px;"> | |
<span style="display: inline-block; vertical-align: middle; color: #000;">You are now an Agents Course Finisher</span> | |
</a> | |
""" | |
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) | |