File size: 8,029 Bytes
7145d28
950a6c3
 
 
7145d28
950a6c3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7145d28
950a6c3
 
7145d28
950a6c3
 
7145d28
950a6c3
 
 
 
 
 
 
 
 
 
 
 
7145d28
 
950a6c3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7145d28
 
950a6c3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7145d28
950a6c3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7145d28
950a6c3
 
 
 
 
 
7145d28
950a6c3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7145d28
 
950a6c3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
import gradio as gr
from datasets import load_dataset, Dataset
from datetime import datetime
from datetime import date
import requests
import tempfile
import asyncio
from huggingface_hub import upload_file
from functools import partial
import io
import os
from PIL import Image, ImageDraw, ImageFont
from huggingface_hub import login

login(token=os.environ["HUGGINGFACE_TOKEN"])

# Constants
SCORES_DATASET = "agents-course/unit4-students-scores"
CERTIFICATES_DATASET = "agents-course/course-certificates-of-excellence"
THRESHOLD_SCORE = 30
CERTIFYING_ORG_LINKEDIN_ID = os.getenv("CERTIFYING_ORG_LINKEDIN_ID", "000000")
COURSE_TITLE = os.getenv("COURSE_TITLE", "Hugging Face Agents Course")

# Function to check user score
def check_user_score(username):
    score_data = load_dataset(SCORES_DATASET, split="train", download_mode="force_redownload")
    matches = [row for row in score_data if row["username"] == username]
    return matches[0] if matches else None

# Function to check if certificate entry exists
def has_certificate_entry(username):
    cert_data = load_dataset(CERTIFICATES_DATASET, split="train", download_mode="force_redownload")
    print(username)
    return any(row["username"] == username for row in cert_data)

# Function to add certificate entry
def add_certificate_entry(username, name, score):
    # Load current dataset
    ds = load_dataset(CERTIFICATES_DATASET, split="train", download_mode="force_redownload")
    
    # Remove any existing entry with the same username
    filtered_rows = [row for row in ds if row["username"] != username]

    # Append the updated/new entry
    new_entry = {
        "username": username,
        "score": score,
        "timestamp": datetime.now().isoformat()
    }
    filtered_rows.append(new_entry)

    # Rebuild dataset and push
    updated_ds = Dataset.from_list(filtered_rows)
    updated_ds.push_to_hub(CERTIFICATES_DATASET)

# Function to generate certificate PDF
def generate_certificate(name, score):
    """Generate certificate image and PDF."""
    certificate_path = os.path.join(
        os.path.dirname(__file__), "templates", "certificate.png"
    )
    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 = name.title()
    d.text((1000, 740), name, fill="black", anchor="mm", font=name_font)

    d.text((1480, 1170), str(date.today()), fill="black", anchor="mm", font=date_font)

    pdf = im.convert("RGB")
    pdf.save("certificate.pdf")

    return im, "certificate.pdf"

async def upload_certificate_to_hub(username: str, certificate_img) -> str:
    """Upload certificate to the dataset hub and return the URL asynchronously."""
    # Save image to temporary file
    with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmp:
        certificate_img.save(tmp.name)

        try:
            # Run upload in a thread pool since upload_file is blocking
            loop = asyncio.get_event_loop()
            upload_func = partial(
                upload_file,
                path_or_fileobj=tmp.name,
                path_in_repo=f"certificates/{username}/{date.today()}.png",
                repo_id="agents-course/final-certificates",
                repo_type="dataset",
                token=os.getenv("HF_TOKEN"),
            )
            await loop.run_in_executor(None, upload_func)

            # Construct the URL to the image
            cert_url = (
                f"https://huggingface.co/datasets/agents-course/final-certificates/"
                f"resolve/main/certificates/{username}/{date.today()}.png"
            )

            # 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 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,
        "issueYear": 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: 0 auto; width: fit-content;">
            <img src="https://download.linkedin.com/desktop/add2profile/buttons/en_US.png" 
                alt="LinkedIn Add to Profile button"
                style="height: 40px; width: auto; display: block;" />
        </a>
    """
    return message

# Main function to handle certificate generation
async def handle_certificate(name, profile: gr.OAuthProfile):
    if profile is None:
        return "You must be logged in with your Hugging Face account.", None

    username = profile.username
    user_score = check_user_score(username)

    if not user_score:
        return "You need to complete Unit 4 first.", None, None, None

    score = user_score["score"]

    if score < THRESHOLD_SCORE:
        return f"Your score is {score}. You need at least {THRESHOLD_SCORE} to pass.", None, None

    certificate_image, certificate_pdf = generate_certificate(name, score)
    add_certificate_entry(username, name, score)
    
    # Start certificate upload asynchronously
    gr.Info("Uploading your certificate...")
    cert_url = await upload_certificate_to_hub(username, certificate_image)

    if cert_url is None:
        gr.Warning("Certificate upload failed, but you still passed!")
        cert_url = "https://huggingface.co/agents-course"

    linkedin_button = create_linkedin_button(username, cert_url)
    return "Congratulations! Here's your certificate:", certificate_image, gr.update(value=linkedin_button, visible=True), certificate_pdf
    

# Gradio interface    
with gr.Blocks() as demo:
    gr.Markdown("# πŸŽ“ Agents Course - Get Your Final Certificate")
    gr.Markdown("Welcome! Follow the steps below to receive your official certificate:")
    gr.Markdown("⚠️ **Note**: Due to high demand, you might experience occasional bugs. If something doesn't work, please try again after a moment!")
    
    with gr.Group():
        gr.Markdown("## βœ… How it works")
        gr.Markdown("""
        1. **Sign in** with your Hugging Face account using the button below.  
        2. **Enter your full name** (this will appear on the certificate).  
        3. Click **'Get My Certificate'** to check your score and download your certificate.
        """)
    gr.Markdown("---")
    gr.Markdown("πŸ“ **Note**: You must have completed [Unit 4](https://huggingface.co/learn/agents-course/unit4/introduction) and your Agent must have scored **above 30** to get your certificate.")

    gr.LoginButton()
    with gr.Row():
        name_input = gr.Text(label="Enter your name (this will appear on the certificate)")
    generate_btn = gr.Button("Get my certificate")
    output_text = gr.Textbox(label="Result")
    linkedin_btn = gr.HTML(visible=False)

    cert_image = gr.Image(label="Your Certificate")
    cert_file = gr.File(label="Download Certificate (PDF)", file_types=[".pdf"])

    generate_btn.click(
        fn=handle_certificate,
        inputs=[name_input],
        outputs=[output_text, cert_image, linkedin_btn, cert_file]
    )

demo.launch()