mgbam's picture
Update app.py
a1a2096 verified
raw
history blame
12.2 kB
"""
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