Spaces:
Sleeping
Sleeping
""" | |
AI Resume Studio โ Huggingย Faceย Space | |
Author: Oluwafemiย Idiakhoa | |
Last update: 2025โ06โ27 | |
Features | |
โโโโโโโโ | |
1. Generate resume โ Word & PDF downloads | |
2. Score resume against job description | |
3. AI Section CoโPilot (rewrite, quantify, bulletizeโฆ) | |
4. Coverโletter generator | |
5. Jobโdescription scraper by URL | |
6. Multilingual export via DeepL (free & pro keys supported) | |
""" | |
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
# Imports & setup | |
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
import os, tempfile | |
import requests | |
import gradio as gr | |
import google.generativeai as genai | |
from dotenv import load_dotenv | |
from bs4 import BeautifulSoup | |
from docx import Document # pythonโdocx | |
from reportlab.lib.pagesizes import LETTER | |
from reportlab.pdfgen import canvas | |
import deepl # DeepL translation | |
load_dotenv() | |
# Gemini model (make sure key has access to this version) | |
genai.configure(api_key=os.getenv("API_KEY")) | |
GEMINI_MODEL = genai.GenerativeModel("gemini-1.5-pro-latest") | |
# DeepL translator | |
DEEPL_KEY = os.getenv("DEEPL_API_KEY") | |
DEEPL_TRANSLATOR = deepl.Translator(DEEPL_KEY) if DEEPL_KEY else None | |
# Supported DeepL target languages (code โ label) | |
LANGS = { | |
"EN": "English", | |
"DE": "German", | |
"FR": "French", | |
"ES": "Spanish", | |
"IT": "Italian", | |
"NL": "Dutch", | |
"PT-PT": "Portuguese", | |
"PT-BR": "Portuguese (BR)", | |
"PL": "Polish", | |
"JA": "Japanese", | |
"ZH": "Chinese", | |
} | |
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
# Helpers | |
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
def ask_gemini(prompt: str, temperature: float = 0.6) -> str: | |
try: | |
rsp = GEMINI_MODEL.generate_content( | |
prompt, generation_config={"temperature": temperature} | |
) | |
return rsp.text.strip() | |
except Exception as err: | |
return f"[Geminiย Error] {err}" | |
def translate_if_needed(text: str, target_code: str) -> str: | |
if target_code == "EN" or not DEEPL_TRANSLATOR: | |
return text # already English or DeepL key missing | |
try: | |
res = DEEPL_TRANSLATOR.translate_text(text, target_lang=target_code) | |
return res.text | |
except Exception as err: | |
return f"[DeepLย Error] {err}\n\n{text}" | |
def save_docx(text: str) -> str: | |
tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".docx") | |
doc = Document() | |
for line in text.splitlines(): | |
doc.add_paragraph(line) | |
doc.save(tmp.name) | |
return tmp.name | |
def save_pdf(text: str) -> str: | |
tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".pdf") | |
c = canvas.Canvas(tmp.name, pagesize=LETTER) | |
width, height = LETTER | |
y = height - 72 | |
for line in text.splitlines(): | |
c.drawString(72, y, line) | |
y -= 14 | |
if y < 72: | |
c.showPage() | |
y = height - 72 | |
c.save() | |
return tmp.name | |
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
# Core AI functions | |
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
def generate_resume(name, email, phone, summary, experience, education, skills, lang): | |
prompt = f""" | |
Create a professional resume in Markdown. **Do not use firstโperson pronouns.** | |
Targetย language: {LANGS[lang]} | |
Name: {name} | |
Email: {email} | |
Phone: {phone} | |
Professionalย Summary: | |
{summary} | |
Experience: | |
{experience} | |
Education: | |
{education} | |
Skills: | |
{skills} | |
""" | |
resume_md = ask_gemini(prompt) | |
return translate_if_needed(resume_md, lang) | |
def score_resume(resume_md, job_desc): | |
prompt = f""" | |
Evaluate the RESUME against the JOBย DESCRIPTION. Return *only* compact Markdown: | |
### Matchย Score | |
<integer 0โ100> | |
### Suggestions | |
- <bulletย 1> | |
- <bulletย 2> | |
- <bulletย 3> | |
""" | |
return ask_gemini(prompt.format(resume_md=resume_md, job_desc=job_desc), temperature=0.4) | |
def refine_section(section_text, instruction, lang): | |
prompt = f""" | |
Perform the following instruction on the resume section. Respond in {LANGS[lang]}. | |
Instruction: {instruction} | |
Text: | |
{section_text} | |
""" | |
refined = ask_gemini(prompt) | |
return translate_if_needed(refined, lang) | |
def generate_cover_letter(resume_md, job_desc, tone, lang): | |
prompt = f""" | |
Draft a oneโpage cover letter (โคโฏ300โฏwords) in {tone} tone, aligning the RESUME | |
to the JOBย DESCRIPTION. Use {LANGS[lang]} throughout. Salutation: "Dear Hiring Manager,". | |
RESUME: | |
{resume_md} | |
JOBย DESCRIPTION: | |
{job_desc} | |
""" | |
letter = ask_gemini(prompt) | |
return translate_if_needed(letter, lang) | |
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
# JD scraper | |
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
def scrape_job_description(url): | |
try: | |
headers = {"User-Agent": "Mozilla/5.0"} | |
page = requests.get(url, headers=headers, timeout=10) | |
soup = BeautifulSoup(page.text, "html.parser") | |
# Heuristics for popular boards | |
selectors = [ | |
"div.jobsearch-jobDescriptionText", # Indeed | |
"section.description", # generic | |
"div.jobs-description__content", # LinkedIn | |
"div#job-details" # Greenhouse | |
] | |
for sel in selectors: | |
block = soup.select_one(sel) | |
if block: | |
return block.get_text(" ", strip=True) | |
return soup.get_text(" ", strip=True)[:3000] | |
except Exception as e: | |
return f"[Error] {e}" | |
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
# Wrapper to export resume + files | |
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
def generate_resume_and_files(name, email, phone, summary, | |
experience, education, skills, lang): | |
resume_md = generate_resume(name, email, phone, summary, | |
experience, education, skills, lang) | |
return resume_md, save_docx(resume_md), save_pdf(resume_md) | |
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
# Gradio UI | |
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
with gr.Blocks(title="AI Resume Studio") as demo: | |
gr.Markdown("## ๐ง ย Interactiveย AIย Resumeย Studio (Geminiย รย DeepL)") | |
LANG_CHOICES = [(v, k) for k, v in LANGS.items()] | |
# โโ 1 ยท Resume generation โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
with gr.Tab("๐ Generate Resume"): | |
with gr.Row(): | |
name = gr.Textbox(label="Name") | |
email = gr.Textbox(label="Email") | |
phone = gr.Textbox(label="Phone") | |
summary = gr.Textbox(label="Professional Summary") | |
experience = gr.Textbox(label="Experience") | |
education = gr.Textbox(label="Education") | |
skills = gr.Textbox(label="Skills") | |
lang_sel = gr.Dropdown(LANG_CHOICES, value="EN", label="Outputย Language") | |
resume_md = gr.Markdown(label="Generated Resume") | |
docx_file = gr.File(label="โฌย Word (.docx)") | |
pdf_file = gr.File(label="โฌย PDF (.pdf)") | |
gen_btn = gr.Button("Generate Resume") | |
gen_btn.click( | |
generate_resume_and_files, | |
inputs=[name, email, phone, summary, experience, education, skills, lang_sel], | |
outputs=[resume_md, docx_file, pdf_file], | |
) | |
# โโ 2 ยท Match score โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
with gr.Tab("๐งฎ Score Resume Against Job"): | |
resume_in = gr.Textbox(label="Resumeย (Markdown)", lines=12) | |
jd_in = gr.Textbox(label="Jobย Description", lines=8) | |
score_out = gr.Markdown(label="Scoreย &ย Suggestions") | |
score_btn = gr.Button("Evaluate") | |
score_btn.click(score_resume, inputs=[resume_in, jd_in], outputs=score_out) | |
# โโ 3 ยท AI Section CoโPilot โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
with gr.Tab("โ๏ธ AI Section CoโPilot"): | |
sec_text = gr.Textbox(label="Sectionย Text", lines=6) | |
action = gr.Radio( | |
["Rewrite", "Make More Concise", "Quantify Achievements", "Convert to Bullet Points"], | |
label="Action" | |
) | |
sec_lang = gr.Dropdown(LANG_CHOICES, value="EN", label="Language") | |
sec_out = gr.Textbox(label="AI Output", lines=6) | |
sec_btn = gr.Button("Apply") | |
sec_btn.click( | |
refine_section, inputs=[sec_text, action, sec_lang], outputs=sec_out | |
) | |
# โโ 4 ยท Coverโletter generator โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
with gr.Tab("๐ง CoverโLetter Generator"): | |
cv_resume = gr.Textbox(label="Resumeย (Markdown)", lines=12) | |
cv_jd = gr.Textbox(label="Jobย Description", lines=8) | |
cv_tone = gr.Radio(["Professional", "Friendly", "Enthusiastic"], value="Professional", label="Tone") | |
cv_lang = gr.Dropdown(LANG_CHOICES, value="EN", label="Language") | |
cv_out = gr.Markdown(label="Coverย Letter") | |
cv_btn = gr.Button("Generate Cover Letter") | |
cv_btn.click( | |
generate_cover_letter, | |
inputs=[cv_resume, cv_jd, cv_tone, cv_lang], | |
outputs=cv_out, | |
) | |
# โโ 5 ยท Jobโdescription scraper โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
with gr.Tab("๐ Job Description Scraper"): | |
url_in = gr.Textbox(label="Jobย URL") | |
jd_out = gr.Textbox(label="Extractedย Description", lines=12) | |
scrape_btn = gr.Button("Fetch Description") | |
scrape_btn.click(scrape_job_description, inputs=url_in, outputs=jd_out) | |
demo.launch(share=False) # HFย Spaces already publishes the app | |