muhammadsalmanalfaridzi's picture
Update app.py
5f61464 verified
import os
import re
import markdown
import gradio as gr
from weasyprint import HTML
from markitdown import MarkItDown
from cerebras.cloud.sdk import Cerebras
# Ambil API key dari environment variable
api_key = os.environ.get("CEREBRAS_API_KEY")
# Inisialisasi MarkItDown untuk konversi file (pdf/docx/dll) ke Markdown
md_converter = MarkItDown()
def create_prompt(resume_string: str, jd_string: str) -> str:
"""
Membuat prompt detail agar AI melakukan optimasi resume berdasarkan job description.
Di sini kita tambahkan instruksi khusus tentang formatting, agar Work Experience
berbentuk bullet list.
"""
return f"""
You are a professional resume optimization expert specializing in tailoring resumes to specific job descriptions.
Your goal is to optimize my resume and provide actionable suggestions for improvement to align with the target role.
### Guidelines:
1. **Relevance**:
- Prioritize experiences, skills, and achievements **most relevant to the job description**.
- Remove or de-emphasize irrelevant details to ensure a **concise** and **targeted** resume.
- Limit work experience section to 2-3 most relevant roles
- Limit bullet points under each role to 2-3 most relevant impacts
2. **Action-Driven Results**:
- Use **strong action verbs** and **quantifiable results** (e.g., percentages, revenue, efficiency improvements) to highlight impact.
3. **Keyword Optimization**:
- Integrate **keywords** and phrases from the job description naturally to optimize for ATS (Applicant Tracking Systems).
4. **Additional Suggestions** *(If Gaps Exist)*:
- If the resume does not fully align with the job description, suggest:
1. **Additional technical or soft skills** that I could add to make my profile stronger.
2. **Certifications or courses** I could pursue to bridge the gap.
3. **Project ideas or experiences** that would better align with the role.
5. **Formatting**:
- Output the tailored resume in **clean Markdown format**.
- Use "##" for main headings (WORK EXPERIENCE, EDUCATION, etc.).
- Use "###" for each role in WORK EXPERIENCE or each education entry.
- For each role in WORK EXPERIENCE, use bullet points ("- ") in separate lines to describe responsibilities/achievements.
- Include an **"Additional Suggestions"** section at the end with actionable improvement recommendations.
- Resume should not exceed one page if possible.
---
### Input:
- **My resume**:
{resume_string}
- **The job description**:
{jd_string}
---
### Output:
1. - A resume in **Markdown format** that emphasizes relevant experience, skills, and achievements.
- Incorporates job description **keywords** to optimize for ATS.
- Uses strong language and is no longer than **one page**.
2. **Additional Suggestions** *(if applicable)*:
- List **skills** that could strengthen alignment with the role.
- Recommend **certifications or courses** to pursue.
- Suggest **specific projects or experiences** to develop.
"""
def get_resume_response(prompt: str, api_key: str, model: str = "llama-3.3-70b", temperature: float = 0.7) -> str:
"""
Mengirim prompt ke model Cerebras (LLM) dan mengembalikan hasil streaming response.
"""
client = Cerebras(api_key=api_key)
stream = client.chat.completions.create(
messages=[
{"role": "system", "content": "Expert resume writer"},
{"role": "user", "content": prompt}
],
model=model,
stream=True,
temperature=temperature,
max_completion_tokens=1024,
top_p=1
)
response_string = ""
for chunk in stream:
response_string += chunk.choices[0].delta.content or ""
return response_string
def remove_unwanted_headings(markdown_text: str) -> str:
"""
Menghapus heading apa pun yang mengandung kata 'resume' atau 'optimized'
(dalam berbagai huruf besar/kecil).
Contoh heading yang akan dihapus: '# Resume', '## optimized', dsb.
"""
pattern = r'^#+.*\b(?:[Rr]esume|[Oo]ptimized)\b.*$'
return re.sub(pattern, '', markdown_text, flags=re.MULTILINE)
def fix_work_experience_bullets(text: str) -> str:
"""
Mencari pola ' - ' di tengah kalimat (yang menandakan bullet),
lalu memecahnya ke baris baru agar menjadi bullet list Markdown yang valid.
"""
# Ganti " - " dengan "\n- " agar bullet pindah ke baris baru
return re.sub(r'\s-\s', '\n- ', text)
def process_resume(resume, jd_string):
"""
Memproses file resume (pdf, docx, dll) dan job description,
lalu menghasilkan resume yang telah dioptimasi + saran perbaikan.
"""
# Ekstensi yang didukung
supported_extensions = ('.pptx', '.docx', '.pdf', '.jpg', '.jpeg', '.png', '.xlsx')
# Cek apakah file resume memiliki ekstensi yang didukung
if resume.name.lower().endswith(supported_extensions):
# Konversi file ke Markdown
result = md_converter.convert(resume.name)
resume_string = result.text_content # konten Markdown hasil konversi
else:
return "File format not supported for conversion to Markdown.", "", "", "", ""
# Buat prompt untuk AI
prompt = create_prompt(resume_string, jd_string)
# Dapatkan response dari AI
response_string = get_resume_response(prompt, api_key)
# Pisahkan response menjadi "optimized resume" dan "additional suggestions"
response_list = response_string.split("## Additional Suggestions")
new_resume = response_list[0].strip()
suggestions = "## Additional Suggestions\n\n" + response_list[1].strip() if len(response_list) > 1 else ""
# 1) Hapus heading yang mengandung kata "resume" / "optimized"
new_resume = remove_unwanted_headings(new_resume)
# 2) Rapikan bullet Work Experience jika AI menaruh ' - ' dalam satu baris
new_resume = fix_work_experience_bullets(new_resume)
# Simpan resume asli (Markdown) jika diperlukan
original_resume_path = "resumes/original_resume.md"
with open(original_resume_path, "w", encoding='utf-8') as f:
f.write(resume_string)
# Simpan resume hasil optimasi (Markdown)
optimized_resume_path = "resumes/optimized_resume.md"
with open(optimized_resume_path, "w", encoding='utf-8') as f:
f.write(new_resume)
# Kembalikan output untuk di-render di Gradio
return resume_string, new_resume, original_resume_path, optimized_resume_path, suggestions
def export_resume(new_resume):
"""
Meng-export resume hasil optimasi (Markdown) menjadi PDF menggunakan WeasyPrint.
Pastikan path 'style.css' sesuai lokasi file style.css Anda.
"""
try:
# Konversi Markdown ke HTML
html_content = markdown.markdown(new_resume)
# Path output PDF
output_pdf_file = "resumes/optimized_resume.pdf"
# Gunakan stylesheet (pastikan path style.css benar)
HTML(string=html_content).write_pdf(
output_pdf_file,
stylesheets=["resumes/style.css"] # Jika style.css ada di folder yang sama dengan app.py
)
return output_pdf_file
except Exception as e:
return f"Failed to export resume: {str(e)}"
# Bangun aplikasi Gradio
with gr.Blocks() as app:
gr.Markdown("# Resume Optimizer πŸ“„")
gr.Markdown("Upload your resume, paste the job description, and get actionable insights!")
with gr.Row():
resume_input = gr.File(label="Upload Your Resume")
jd_input = gr.Textbox(
label="Paste the Job Description Here",
lines=9,
interactive=True,
placeholder="Paste job description..."
)
run_button = gr.Button("Optimize Resume πŸ€–")
with gr.Row():
before_md = gr.Markdown(label="Original Resume (Before)")
after_md = gr.Markdown(label="Optimized Resume (After)")
output_suggestions = gr.Markdown(label="Suggestions")
with gr.Row():
download_before = gr.File(label="Download Original Resume")
download_after = gr.File(label="Download Optimized Resume")
export_button = gr.Button("Export Optimized Resume as PDF πŸš€")
export_result = gr.File(label="Download PDF")
# Saat tombol Optimize Resume diklik
run_button.click(
process_resume,
inputs=[resume_input, jd_input],
outputs=[before_md, after_md, download_before, download_after, output_suggestions]
)
# Saat tombol Export PDF diklik
export_button.click(
export_resume,
inputs=[after_md],
outputs=[export_result]
)
app.launch()