|
import os |
|
import re |
|
import markdown |
|
import gradio as gr |
|
from weasyprint import HTML |
|
from markitdown import MarkItDown |
|
from cerebras.cloud.sdk import Cerebras |
|
|
|
|
|
|
|
|
|
api_key = os.environ.get("CEREBRAS_API_KEY") |
|
|
|
|
|
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. |
|
""" |
|
|
|
try: |
|
result = md_converter.convert(resume.file) |
|
resume_string = result.text_content |
|
except Exception as e: |
|
return f"Conversion failed: {str(e)}", "", "", "", "" |
|
|
|
|
|
prompt = create_prompt(resume_string, jd_string) |
|
|
|
|
|
response_string = get_resume_response(prompt, api_key) |
|
|
|
|
|
response_list = response_string.split("## Additional Suggestions") |
|
new_resume = response_list[0].strip() |
|
|
|
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 "" |
|
|
|
|
|
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) |
|
|
|
|
|
|
|
original_resume_path = "resumes/original_resume.md" |
|
with open(original_resume_path, "w", encoding='utf-8') as f: |
|
f.write(resume_string) |
|
|
|
|
|
optimized_resume_path = "resumes/optimized_resume.md" |
|
with open(optimized_resume_path, "w", encoding='utf-8') as f: |
|
f.write(new_resume) |
|
|
|
|
|
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: |
|
|
|
html_content = markdown.markdown(new_resume, extensions=['extra', 'nl2br']) |
|
|
|
|
|
output_pdf_file = "resumes/optimized_resume.pdf" |
|
|
|
|
|
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)} π" |
|
|
|
|
|
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") |
|
|
|
|
|
run_button.click( |
|
process_resume, |
|
inputs=[resume_input, jd_input], |
|
outputs=[before_md, after_md, download_before, download_after, output_suggestions] |
|
) |
|
|
|
|
|
export_button.click( |
|
export_resume, |
|
inputs=[after_md], |
|
outputs=[export_result] |
|
) |
|
|
|
app.launch() |
|
|