Update app.py
Browse files
app.py
CHANGED
@@ -1,21 +1,24 @@
|
|
1 |
import os
|
2 |
import re
|
3 |
-
from markitdown import MarkItDown
|
4 |
-
from cerebras.cloud.sdk import Cerebras
|
5 |
-
from weasyprint import HTML
|
6 |
import markdown
|
7 |
import gradio as gr
|
|
|
|
|
|
|
8 |
|
9 |
-
#
|
|
|
|
|
|
|
10 |
api_key = os.environ.get("CEREBRAS_API_KEY")
|
11 |
|
12 |
-
#
|
13 |
md_converter = MarkItDown()
|
14 |
|
15 |
-
# Functions for resume optimization
|
16 |
def create_prompt(resume_string: str, jd_string: str) -> str:
|
17 |
"""
|
18 |
-
|
|
|
19 |
"""
|
20 |
return f"""
|
21 |
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.
|
@@ -66,6 +69,9 @@ You are a professional resume optimization expert specializing in tailoring resu
|
|
66 |
"""
|
67 |
|
68 |
def get_resume_response(prompt: str, api_key: str, model: str = "llama-3.3-70b", temperature: float = 0.7) -> str:
|
|
|
|
|
|
|
69 |
client = Cerebras(api_key=api_key)
|
70 |
stream = client.chat.completions.create(
|
71 |
messages=[
|
@@ -84,77 +90,98 @@ def get_resume_response(prompt: str, api_key: str, model: str = "llama-3.3-70b",
|
|
84 |
response_string += chunk.choices[0].delta.content or ""
|
85 |
return response_string
|
86 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
87 |
def process_resume(resume, jd_string):
|
88 |
"""
|
89 |
-
|
|
|
90 |
"""
|
91 |
-
#
|
92 |
supported_extensions = ('.pptx', '.docx', '.pdf', '.jpg', '.jpeg', '.png', '.xlsx')
|
93 |
|
94 |
-
#
|
95 |
if resume.name.lower().endswith(supported_extensions):
|
96 |
-
#
|
97 |
result = md_converter.convert(resume.name)
|
98 |
-
resume_string = result.text_content #
|
99 |
else:
|
100 |
-
return "File format not supported for conversion to Markdown.", "", "", "", ""
|
101 |
|
102 |
-
#
|
103 |
prompt = create_prompt(resume_string, jd_string)
|
104 |
|
105 |
-
#
|
106 |
response_string = get_resume_response(prompt, api_key)
|
107 |
|
108 |
-
#
|
109 |
response_list = response_string.split("## Additional Suggestions")
|
110 |
new_resume = response_list[0].strip()
|
111 |
suggestions = "## Additional Suggestions\n\n" + response_list[1].strip() if len(response_list) > 1 else ""
|
112 |
-
|
113 |
-
new_resume = new_resume.replace("# Optimized Resume", "")
|
114 |
-
new_resume = new_resume.replace("## Optimized Resume", "")
|
115 |
-
new_resume = new_resume.replace("Optimized Resume", "")
|
116 |
-
new_resume = re.sub(r'^\* ', '- ', new_resume, flags=re.MULTILINE)
|
117 |
|
118 |
-
#
|
|
|
|
|
|
|
|
|
119 |
original_resume_path = "resumes/original_resume.md"
|
120 |
-
with open(original_resume_path, "w") as f:
|
121 |
f.write(resume_string)
|
122 |
|
123 |
-
#
|
124 |
optimized_resume_path = "resumes/optimized_resume.md"
|
125 |
-
with open(optimized_resume_path, "w") as f:
|
126 |
f.write(new_resume)
|
127 |
|
128 |
-
#
|
129 |
return resume_string, new_resume, original_resume_path, optimized_resume_path, suggestions
|
130 |
|
131 |
def export_resume(new_resume):
|
132 |
"""
|
133 |
-
|
|
|
134 |
"""
|
135 |
try:
|
136 |
-
#
|
137 |
-
html_content = markdown.markdown(new_resume
|
138 |
|
139 |
-
#
|
140 |
output_pdf_file = "resumes/optimized_resume.pdf"
|
141 |
-
stylesheets = ['resumes/style.css'] # Ensure this path is correct and accessible
|
142 |
|
143 |
-
#
|
144 |
-
HTML(string=html_content).write_pdf(
|
|
|
|
|
|
|
145 |
|
146 |
-
return output_pdf_file
|
147 |
except Exception as e:
|
148 |
return f"Failed to export resume: {str(e)} π"
|
149 |
|
150 |
-
# Gradio
|
151 |
with gr.Blocks() as app:
|
152 |
gr.Markdown("# Resume Optimizer π")
|
153 |
gr.Markdown("Upload your resume, paste the job description, and get actionable insights!")
|
154 |
|
155 |
with gr.Row():
|
156 |
resume_input = gr.File(label="Upload Your Resume")
|
157 |
-
jd_input = gr.Textbox(
|
|
|
|
|
|
|
|
|
|
|
158 |
|
159 |
run_button = gr.Button("Optimize Resume π€")
|
160 |
|
@@ -170,12 +197,18 @@ with gr.Blocks() as app:
|
|
170 |
export_button = gr.Button("Export Optimized Resume as PDF π")
|
171 |
export_result = gr.File(label="Download PDF")
|
172 |
|
173 |
-
#
|
174 |
run_button.click(
|
175 |
process_resume,
|
176 |
inputs=[resume_input, jd_input],
|
177 |
outputs=[before_md, after_md, download_before, download_after, output_suggestions]
|
178 |
)
|
179 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
180 |
|
181 |
app.launch()
|
|
|
1 |
import os
|
2 |
import re
|
|
|
|
|
|
|
3 |
import markdown
|
4 |
import gradio as gr
|
5 |
+
from weasyprint import HTML
|
6 |
+
from markitdown import MarkItDown
|
7 |
+
from cerebras.cloud.sdk import Cerebras
|
8 |
|
9 |
+
# Pastikan Anda memiliki file style.css di direktori yang sama
|
10 |
+
# atau sesuaikan path 'stylesheets' di fungsi export_resume
|
11 |
+
|
12 |
+
# Dapatkan API key dari environment variables
|
13 |
api_key = os.environ.get("CEREBRAS_API_KEY")
|
14 |
|
15 |
+
# Inisialisasi MarkItDown
|
16 |
md_converter = MarkItDown()
|
17 |
|
|
|
18 |
def create_prompt(resume_string: str, jd_string: str) -> str:
|
19 |
"""
|
20 |
+
Membuat prompt detail untuk AI agar melakukan optimasi resume
|
21 |
+
berdasarkan job description.
|
22 |
"""
|
23 |
return f"""
|
24 |
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.
|
|
|
69 |
"""
|
70 |
|
71 |
def get_resume_response(prompt: str, api_key: str, model: str = "llama-3.3-70b", temperature: float = 0.7) -> str:
|
72 |
+
"""
|
73 |
+
Mengirim prompt ke model Cerebras (LLM) dan mengembalikan hasil streaming response.
|
74 |
+
"""
|
75 |
client = Cerebras(api_key=api_key)
|
76 |
stream = client.chat.completions.create(
|
77 |
messages=[
|
|
|
90 |
response_string += chunk.choices[0].delta.content or ""
|
91 |
return response_string
|
92 |
|
93 |
+
def remove_unwanted_headings(markdown_text: str) -> str:
|
94 |
+
"""
|
95 |
+
Menghapus heading apa pun yang mengandung kata 'resume' atau 'optimized'
|
96 |
+
(dalam berbagai huruf besar/kecil).
|
97 |
+
Contoh heading yang akan dihapus:
|
98 |
+
# Resume
|
99 |
+
## optimized
|
100 |
+
### Optimized Resume
|
101 |
+
dsb.
|
102 |
+
"""
|
103 |
+
pattern = r'^#+.*\b(?:[Rr]esume|[Oo]ptimized)\b.*$'
|
104 |
+
return re.sub(pattern, '', markdown_text, flags=re.MULTILINE)
|
105 |
+
|
106 |
def process_resume(resume, jd_string):
|
107 |
"""
|
108 |
+
Memproses file resume yang di-upload dan job description, lalu
|
109 |
+
menghasilkan resume yang telah dioptimasi + saran perbaikan.
|
110 |
"""
|
111 |
+
# Format yang didukung
|
112 |
supported_extensions = ('.pptx', '.docx', '.pdf', '.jpg', '.jpeg', '.png', '.xlsx')
|
113 |
|
114 |
+
# Cek apakah file resume memiliki ekstensi yang didukung
|
115 |
if resume.name.lower().endswith(supported_extensions):
|
116 |
+
# Konversi file ke Markdown menggunakan MarkItDown
|
117 |
result = md_converter.convert(resume.name)
|
118 |
+
resume_string = result.text_content # konten Markdown hasil konversi
|
119 |
else:
|
120 |
+
return "File format not supported for conversion to Markdown.", "", "", "", ""
|
121 |
|
122 |
+
# Buat prompt untuk AI
|
123 |
prompt = create_prompt(resume_string, jd_string)
|
124 |
|
125 |
+
# Dapatkan response dari AI
|
126 |
response_string = get_resume_response(prompt, api_key)
|
127 |
|
128 |
+
# Pisahkan response menjadi "optimized resume" dan "additional suggestions"
|
129 |
response_list = response_string.split("## Additional Suggestions")
|
130 |
new_resume = response_list[0].strip()
|
131 |
suggestions = "## Additional Suggestions\n\n" + response_list[1].strip() if len(response_list) > 1 else ""
|
|
|
|
|
|
|
|
|
|
|
132 |
|
133 |
+
# ===== Hapus heading yang mengandung kata resume/optimized =====
|
134 |
+
new_resume = remove_unwanted_headings(new_resume)
|
135 |
+
# ===============================================================
|
136 |
+
|
137 |
+
# Simpan resume asli (Markdown) jika diperlukan
|
138 |
original_resume_path = "resumes/original_resume.md"
|
139 |
+
with open(original_resume_path, "w", encoding='utf-8') as f:
|
140 |
f.write(resume_string)
|
141 |
|
142 |
+
# Simpan resume hasil optimasi (Markdown)
|
143 |
optimized_resume_path = "resumes/optimized_resume.md"
|
144 |
+
with open(optimized_resume_path, "w", encoding='utf-8') as f:
|
145 |
f.write(new_resume)
|
146 |
|
147 |
+
# Kembalikan output untuk di-render di Gradio
|
148 |
return resume_string, new_resume, original_resume_path, optimized_resume_path, suggestions
|
149 |
|
150 |
def export_resume(new_resume):
|
151 |
"""
|
152 |
+
Meng-export resume hasil optimasi (Markdown) menjadi PDF
|
153 |
+
menggunakan WeasyPrint.
|
154 |
"""
|
155 |
try:
|
156 |
+
# Konversi Markdown ke HTML
|
157 |
+
html_content = markdown.markdown(new_resume)
|
158 |
|
159 |
+
# Path output PDF
|
160 |
output_pdf_file = "resumes/optimized_resume.pdf"
|
|
|
161 |
|
162 |
+
# Gunakan stylesheet (pastikan path style.css benar)
|
163 |
+
HTML(string=html_content).write_pdf(
|
164 |
+
output_pdf_file,
|
165 |
+
stylesheets=["style.css"] # atau "resumes/style.css" jika style.css di folder "resumes"
|
166 |
+
)
|
167 |
|
168 |
+
return output_pdf_file
|
169 |
except Exception as e:
|
170 |
return f"Failed to export resume: {str(e)} π"
|
171 |
|
172 |
+
# Bangun aplikasi Gradio
|
173 |
with gr.Blocks() as app:
|
174 |
gr.Markdown("# Resume Optimizer π")
|
175 |
gr.Markdown("Upload your resume, paste the job description, and get actionable insights!")
|
176 |
|
177 |
with gr.Row():
|
178 |
resume_input = gr.File(label="Upload Your Resume")
|
179 |
+
jd_input = gr.Textbox(
|
180 |
+
label="Paste the Job Description Here",
|
181 |
+
lines=9,
|
182 |
+
interactive=True,
|
183 |
+
placeholder="Paste job description..."
|
184 |
+
)
|
185 |
|
186 |
run_button = gr.Button("Optimize Resume π€")
|
187 |
|
|
|
197 |
export_button = gr.Button("Export Optimized Resume as PDF π")
|
198 |
export_result = gr.File(label="Download PDF")
|
199 |
|
200 |
+
# Saat tombol Optimize Resume diklik
|
201 |
run_button.click(
|
202 |
process_resume,
|
203 |
inputs=[resume_input, jd_input],
|
204 |
outputs=[before_md, after_md, download_before, download_after, output_suggestions]
|
205 |
)
|
206 |
+
|
207 |
+
# Saat tombol Export PDF diklik
|
208 |
+
export_button.click(
|
209 |
+
export_resume,
|
210 |
+
inputs=[after_md],
|
211 |
+
outputs=[export_result]
|
212 |
+
)
|
213 |
|
214 |
app.launch()
|