File size: 8,338 Bytes
f8387c1 bfd445f e98268a f8387c1 169b5ce f8387c1 db49350 37ffd3f f8387c1 c9135a2 db49350 c9135a2 f8387c1 37ffd3f f8387c1 37ffd3f f8387c1 dabfbfe f8387c1 5f61464 169b5ce f8387c1 169b5ce 37ffd3f 169b5ce 888d73b ef396f3 37ffd3f ef396f3 db49350 169b5ce db49350 46fc669 169b5ce 98e28e8 37ffd3f 169b5ce 98e28e8 888d73b 169b5ce 98e28e8 db49350 37ffd3f 98e28e8 f8387c1 37ffd3f 169b5ce 37ffd3f 169b5ce 62bcdd1 169b5ce 62bcdd1 169b5ce 62bcdd1 169b5ce 62bcdd1 169b5ce 62bcdd1 f8387c1 888d73b 67ff76d 37ffd3f 888d73b db49350 37ffd3f 98e28e8 169b5ce 93fba37 e8f939d 169b5ce db49350 169b5ce 93fba37 169b5ce f8387c1 37ffd3f f8387c1 169b5ce f8387c1 28634dd 169b5ce 28634dd f8387c1 c9135a2 28634dd 95242f6 c9135a2 28634dd 67ff76d 28634dd 169b5ce ef396f3 67ff76d ef396f3 169b5ce f8387c1 d422d60 |
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 211 212 213 214 215 216 217 218 |
import os
import re
import markdown
import gradio as gr
from weasyprint import HTML
from markitdown import MarkItDown
from cerebras.cloud.sdk import Cerebras
# Pastikan file style.css tersedia sesuai path (misalnya di folder "resumes" atau di direktori yang sama)
# Dapatkan API key dari environment variables
api_key = os.environ.get("CEREBRAS_API_KEY")
# Inisialisasi MarkItDown dengan semua optional dependencies (pastikan Anda telah menginstal 'markitdown[all]')
md_converter = MarkItDown(enable_plugins=True)
def create_prompt(resume_string: str, jd_string: str) -> str:
"""
Membuat prompt detail untuk AI agar melakukan optimasi resume
berdasarkan job description.
"""
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**.
- Include an **"Additional Suggestions"** section at the end with actionable improvement recommendations.
---
### 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
### Optimized Resume
dsb.
"""
pattern = r'^#+.*\b(?:[Rr]esume|[Oo]ptimized)\b.*$'
return re.sub(pattern, '', markdown_text, flags=re.MULTILINE)
def process_resume(resume, jd_string):
"""
Memproses file resume yang di-upload dan job description, lalu
menghasilkan resume yang telah dioptimasi + saran perbaikan.
"""
# Gunakan file-like stream dari upload (tanpa cek ekstensi, dukung semua format yang didukung MarkItDown)
try:
result = md_converter.convert(resume.file)
resume_string = result.text_content # konten Markdown hasil konversi
except Exception as e:
return f"Conversion failed: {str(e)}", "", "", "", ""
# 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()
# Ganti tanda asterisk di awal baris menjadi tanda "-" untuk bullet list standar
new_resume = re.sub(r'^\* ', '- ', new_resume, flags=re.MULTILINE)
suggestions = "## Additional Suggestions\n\n" + response_list[1].strip() if len(response_list) > 1 else ""
# ===== Hapus heading yang mengandung kata resume/optimized =====
new_resume = new_resume.replace("# Optimized Resume", "")
new_resume = new_resume.replace("## Optimized Resume", "")
new_resume = new_resume.replace("Optimized Resume", "")
new_resume = new_resume.replace("# Resume", "")
new_resume = new_resume.replace("## Resume", "")
new_resume = re.sub(r'^#+\s*Resume\s*', '', new_resume, flags=re.MULTILINE)
new_resume = remove_unwanted_headings(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.
"""
try:
# Konversi Markdown ke HTML dengan ekstensi tambahan agar format terjaga
html_content = markdown.markdown(new_resume, extensions=['extra', 'nl2br'])
# 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"]
)
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()
|