mgbam commited on
Commit
67588b7
ยท
verified ยท
1 Parent(s): 9dc7cda

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +128 -162
app.py CHANGED
@@ -1,43 +1,51 @@
1
  """
2
- AI Resume Studio โ€“ Huggingย Faceย Space
3
- Author: Oluwafemiย Idiakhoa
4
- Last update: 2025โ€‘06โ€‘27
5
 
6
  Features
7
  โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
8
  1. Generate resume โ†’ Word & PDF downloads
9
  2. Score resume against job description
10
- 3. AI Section Coโ€‘Pilot (rewrite, quantify, bulletizeโ€ฆ)
11
- 4. Coverโ€‘letter generator
12
- 5. Jobโ€‘description scraper by URL
13
- 6. Multilingual export via DeepL (free & pro keys supported)
14
  """
15
 
16
- # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
17
- # Imports & setup
18
- # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
19
- import os, tempfile
20
  import requests
21
  import gradio as gr
22
  import google.generativeai as genai
23
  from dotenv import load_dotenv
24
  from bs4 import BeautifulSoup
25
- from docx import Document # pythonโ€‘docx
26
  from reportlab.lib.pagesizes import LETTER
27
  from reportlab.pdfgen import canvas
28
- import deepl # DeepL translation
29
 
 
 
 
30
  load_dotenv()
31
 
32
- # Gemini model (make sure key has access to this version)
33
  genai.configure(api_key=os.getenv("API_KEY"))
34
- GEMINI_MODEL = genai.GenerativeModel("gemini-1.5-pro-latest")
35
 
36
- # DeepL translator
37
  DEEPL_KEY = os.getenv("DEEPL_API_KEY")
38
- DEEPL_TRANSLATOR = deepl.Translator(DEEPL_KEY) if DEEPL_KEY else None
 
 
 
 
 
 
 
39
 
40
- # Supported DeepL target languages (code โ†’ label)
41
  LANGS = {
42
  "EN": "English",
43
  "DE": "German",
@@ -45,231 +53,189 @@ LANGS = {
45
  "ES": "Spanish",
46
  "IT": "Italian",
47
  "NL": "Dutch",
48
- "PT-PT": "Portuguese",
49
- "PT-BR": "Portuguese (BR)",
50
  "PL": "Polish",
51
  "JA": "Japanese",
52
  "ZH": "Chinese",
53
  }
54
 
55
  # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
56
- # Helpers
57
  # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
58
- def ask_gemini(prompt: str, temperature: float = 0.6) -> str:
59
  try:
60
- rsp = GEMINI_MODEL.generate_content(
61
- prompt, generation_config={"temperature": temperature}
62
- )
63
- return rsp.text.strip()
64
- except Exception as err:
65
- return f"[Geminiย Error] {err}"
66
-
67
- def translate_if_needed(text: str, target_code: str) -> str:
68
- if target_code == "EN" or not DEEPL_TRANSLATOR:
69
- return text # already English or DeepL key missing
70
- try:
71
- res = DEEPL_TRANSLATOR.translate_text(text, target_lang=target_code)
72
- return res.text
73
- except Exception as err:
74
- return f"[DeepLย Error] {err}\n\n{text}"
75
 
76
- def save_docx(text: str) -> str:
77
- tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".docx")
78
  doc = Document()
79
- for line in text.splitlines():
80
  doc.add_paragraph(line)
81
- doc.save(tmp.name)
82
- return tmp.name
83
 
84
- def save_pdf(text: str) -> str:
85
- tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".pdf")
86
- c = canvas.Canvas(tmp.name, pagesize=LETTER)
87
  width, height = LETTER
88
  y = height - 72
89
- for line in text.splitlines():
90
  c.drawString(72, y, line)
91
  y -= 14
92
  if y < 72:
93
  c.showPage()
94
  y = height - 72
95
  c.save()
96
- return tmp.name
97
 
98
  # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
99
- # Core AI functions
100
  # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
101
- def generate_resume(name, email, phone, summary, experience, education, skills, lang):
102
  prompt = f"""
103
- Create a professional resume in Markdown. **Do not use firstโ€‘person pronouns.**
104
-
105
- Targetย language: {LANGS[lang]}
106
 
107
  Name: {name}
108
  Email: {email}
109
  Phone: {phone}
110
 
111
- Professionalย Summary:
112
  {summary}
113
 
114
  Experience:
115
- {experience}
116
 
117
  Education:
118
- {education}
119
 
120
  Skills:
121
  {skills}
122
  """
123
- resume_md = ask_gemini(prompt)
124
- return translate_if_needed(resume_md, lang)
125
 
126
  def score_resume(resume_md, job_desc):
127
  prompt = f"""
128
- Evaluate the RESUME against the JOBย DESCRIPTION. Return *only* compact Markdown:
129
- ### Matchย Score
130
- <integer 0โ€‘100>
 
131
 
132
  ### Suggestions
133
- - <bulletย 1>
134
- - <bulletย 2>
135
- - <bulletย 3>
136
  """
137
- return ask_gemini(prompt.format(resume_md=resume_md, job_desc=job_desc), temperature=0.4)
138
 
139
- def refine_section(section_text, instruction, lang):
140
  prompt = f"""
141
- Perform the following instruction on the resume section. Respond in {LANGS[lang]}.
142
 
143
  Instruction: {instruction}
144
- Text:
145
- {section_text}
146
  """
147
- refined = ask_gemini(prompt)
148
- return translate_if_needed(refined, lang)
149
 
150
- def generate_cover_letter(resume_md, job_desc, tone, lang):
151
  prompt = f"""
152
- Draft a oneโ€‘page cover letter (โ‰คโ€ฏ300โ€ฏwords) in {tone} tone, aligning the RESUME
153
- to the JOBย DESCRIPTION. Use {LANGS[lang]} throughout. Salutation: "Dear Hiring Manager,".
154
 
155
- RESUME:
156
  {resume_md}
157
 
158
- JOBย DESCRIPTION:
159
  {job_desc}
160
  """
161
  letter = ask_gemini(prompt)
162
- return translate_if_needed(letter, lang)
163
 
164
- # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
165
- # JD scraper
166
- # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
167
  def scrape_job_description(url):
168
  try:
169
- headers = {"User-Agent": "Mozilla/5.0"}
170
- page = requests.get(url, headers=headers, timeout=10)
171
- soup = BeautifulSoup(page.text, "html.parser")
172
-
173
- # Heuristics for popular boards
174
- selectors = [
175
- "div.jobsearch-jobDescriptionText", # Indeed
176
- "section.description", # generic
177
- "div.jobs-description__content", # LinkedIn
178
- "div#job-details" # Greenhouse
179
- ]
180
- for sel in selectors:
181
  block = soup.select_one(sel)
182
  if block:
183
  return block.get_text(" ", strip=True)
184
  return soup.get_text(" ", strip=True)[:3000]
185
  except Exception as e:
186
- return f"[Error] {e}"
187
 
188
- # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
189
- # Wrapper to export resume + files
190
- # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
191
- def generate_resume_and_files(name, email, phone, summary,
192
- experience, education, skills, lang):
193
- resume_md = generate_resume(name, email, phone, summary,
194
- experience, education, skills, lang)
195
- return resume_md, save_docx(resume_md), save_pdf(resume_md)
196
 
197
  # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
198
  # Gradio UI
199
  # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
200
  with gr.Blocks(title="AI Resume Studio") as demo:
201
- gr.Markdown("## ๐Ÿง ย Interactiveย AIย Resumeย Studio (Geminiย ร—ย DeepL)")
202
-
203
- LANG_CHOICES = [(v, k) for k, v in LANGS.items()]
204
 
205
- # โ”€โ”€ 1 ยท Resume generation โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
206
  with gr.Tab("๐Ÿ“„ Generate Resume"):
207
  with gr.Row():
208
- name = gr.Textbox(label="Name")
209
- email = gr.Textbox(label="Email")
210
- phone = gr.Textbox(label="Phone")
211
- summary = gr.Textbox(label="Professional Summary")
212
- experience = gr.Textbox(label="Experience")
213
- education = gr.Textbox(label="Education")
214
- skills = gr.Textbox(label="Skills")
215
- lang_sel = gr.Dropdown(LANG_CHOICES, value="EN", label="Outputย Language")
216
-
217
- resume_md = gr.Markdown(label="Generated Resume")
218
- docx_file = gr.File(label="โฌ‡ย Word (.docx)")
219
- pdf_file = gr.File(label="โฌ‡ย PDF (.pdf)")
220
- gen_btn = gr.Button("Generate Resume")
221
 
222
  gen_btn.click(
223
- generate_resume_and_files,
224
- inputs=[name, email, phone, summary, experience, education, skills, lang_sel],
225
- outputs=[resume_md, docx_file, pdf_file],
226
  )
227
 
228
- # โ”€โ”€ 2 ยท Match score โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
229
  with gr.Tab("๐Ÿงฎ Score Resume Against Job"):
230
- resume_in = gr.Textbox(label="Resumeย (Markdown)", lines=12)
231
- jd_in = gr.Textbox(label="Jobย Description", lines=8)
232
- score_out = gr.Markdown(label="Scoreย &ย Suggestions")
233
- score_btn = gr.Button("Evaluate")
234
-
235
- score_btn.click(score_resume, inputs=[resume_in, jd_in], outputs=score_out)
236
-
237
- # โ”€โ”€ 3 ยท AI Section Coโ€‘Pilot โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
238
- with gr.Tab("โœ๏ธ AI Section Coโ€‘Pilot"):
239
- sec_text = gr.Textbox(label="Sectionย Text", lines=6)
240
- action = gr.Radio(
241
- ["Rewrite", "Make More Concise", "Quantify Achievements", "Convert to Bullet Points"],
242
- label="Action"
243
- )
244
- sec_lang = gr.Dropdown(LANG_CHOICES, value="EN", label="Language")
245
- sec_out = gr.Textbox(label="AI Output", lines=6)
246
- sec_btn = gr.Button("Apply")
247
-
248
- sec_btn.click(
249
- refine_section, inputs=[sec_text, action, sec_lang], outputs=sec_out
250
- )
251
-
252
- # โ”€โ”€ 4 ยท Coverโ€‘letter generator โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
253
- with gr.Tab("๐Ÿ“ง Coverโ€‘Letter Generator"):
254
- cv_resume = gr.Textbox(label="Resumeย (Markdown)", lines=12)
255
- cv_jd = gr.Textbox(label="Jobย Description", lines=8)
256
  cv_tone = gr.Radio(["Professional", "Friendly", "Enthusiastic"], value="Professional", label="Tone")
257
- cv_lang = gr.Dropdown(LANG_CHOICES, value="EN", label="Language")
258
- cv_out = gr.Markdown(label="Coverย Letter")
259
- cv_btn = gr.Button("Generate Cover Letter")
260
-
261
- cv_btn.click(
262
- generate_cover_letter,
263
- inputs=[cv_resume, cv_jd, cv_tone, cv_lang],
264
- outputs=cv_out,
265
- )
266
 
267
- # โ”€โ”€ 5 ยท Jobโ€‘description scraper โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
268
  with gr.Tab("๐ŸŒ Job Description Scraper"):
269
- url_in = gr.Textbox(label="Jobย URL")
270
- jd_out = gr.Textbox(label="Extractedย Description", lines=12)
271
- scrape_btn = gr.Button("Fetch Description")
272
 
273
- scrape_btn.click(scrape_job_description, inputs=url_in, outputs=jd_out)
274
 
275
- demo.launch(share=False) # HFย Spaces already publishes the app
 
1
  """
2
+ AI Resume Studio โ€“ Hugging Face Space
3
+ Author: Oluwafemi Idiakhoa
4
+ Last update: 2025-06-27
5
 
6
  Features
7
  โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
8
  1. Generate resume โ†’ Word & PDF downloads
9
  2. Score resume against job description
10
+ 3. AI Section Co-Pilot (rewrite, quantify, bulletizeโ€ฆ)
11
+ 4. Cover-letter generator
12
+ 5. Job-description scraper by URL
13
+ 6. Multilingual export via Deep-Translator (DeepL backend)
14
  """
15
 
16
+ import os
17
+ import tempfile
 
 
18
  import requests
19
  import gradio as gr
20
  import google.generativeai as genai
21
  from dotenv import load_dotenv
22
  from bs4 import BeautifulSoup
23
+ from docx import Document # python-docx
24
  from reportlab.lib.pagesizes import LETTER
25
  from reportlab.pdfgen import canvas
26
+ from deep_translator import DeeplTranslator
27
 
28
+ # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
29
+ # Environment & Model Setup
30
+ # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
31
  load_dotenv()
32
 
33
+ # Gemini (DeepMind) setup
34
  genai.configure(api_key=os.getenv("API_KEY"))
35
+ GEMINI = genai.GenerativeModel("gemini-1.5-pro-latest")
36
 
37
+ # DeepL via Deep-Translator
38
  DEEPL_KEY = os.getenv("DEEPL_API_KEY")
39
+ # Note: Deep-Translatorโ€™s DeeplTranslator requires target_lang codes like "FR", "DE", etc.
40
+ def translate_text(text: str, target_code: str) -> str:
41
+ if not DEEPL_KEY or target_code == "EN":
42
+ return text
43
+ try:
44
+ return DeeplTranslator(api_key=DEEPL_KEY, target=target_code).translate(text)
45
+ except Exception as e:
46
+ return f"[Translation Error] {e}\n\n{text}"
47
 
48
+ # Supported languages mapping
49
  LANGS = {
50
  "EN": "English",
51
  "DE": "German",
 
53
  "ES": "Spanish",
54
  "IT": "Italian",
55
  "NL": "Dutch",
56
+ "PT": "Portuguese",
 
57
  "PL": "Polish",
58
  "JA": "Japanese",
59
  "ZH": "Chinese",
60
  }
61
 
62
  # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
63
+ # AI / File Helpers
64
  # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
65
+ def ask_gemini(prompt: str, temp: float = 0.6) -> str:
66
  try:
67
+ res = GEMINI.generate_content(prompt, generation_config={"temperature": temp})
68
+ return res.text.strip()
69
+ except Exception as e:
70
+ return f"[Gemini Error] {e}"
 
 
 
 
 
 
 
 
 
 
 
71
 
72
+ def save_docx(content: str) -> str:
73
+ f = tempfile.NamedTemporaryFile(delete=False, suffix=".docx")
74
  doc = Document()
75
+ for line in content.splitlines():
76
  doc.add_paragraph(line)
77
+ doc.save(f.name)
78
+ return f.name
79
 
80
+ def save_pdf(content: str) -> str:
81
+ f = tempfile.NamedTemporaryFile(delete=False, suffix=".pdf")
82
+ c = canvas.Canvas(f.name, pagesize=LETTER)
83
  width, height = LETTER
84
  y = height - 72
85
+ for line in content.splitlines():
86
  c.drawString(72, y, line)
87
  y -= 14
88
  if y < 72:
89
  c.showPage()
90
  y = height - 72
91
  c.save()
92
+ return f.name
93
 
94
  # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
95
+ # Core Features
96
  # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
97
+ def generate_resume(name, email, phone, summary, exp, edu, skills, lang_code):
98
  prompt = f"""
99
+ Create a professional rรฉsumรฉ in Markdown without first-person pronouns.
100
+ Output language: {LANGS[lang_code]}
 
101
 
102
  Name: {name}
103
  Email: {email}
104
  Phone: {phone}
105
 
106
+ Professional Summary:
107
  {summary}
108
 
109
  Experience:
110
+ {exp}
111
 
112
  Education:
113
+ {edu}
114
 
115
  Skills:
116
  {skills}
117
  """
118
+ md = ask_gemini(prompt)
119
+ return translate_text(md, lang_code)
120
 
121
  def score_resume(resume_md, job_desc):
122
  prompt = f"""
123
+ Evaluate this rรฉsumรฉ against the job description. Return compact Markdown:
124
+
125
+ ### Match Score
126
+ <0-100>
127
 
128
  ### Suggestions
129
+ - suggestion 1
130
+ - suggestion 2
 
131
  """
132
+ return ask_gemini(prompt, temp=0.4)
133
 
134
+ def refine_section(section, instruction, lang_code):
135
  prompt = f"""
136
+ Apply the following instruction to this rรฉsumรฉ section. Respond in {LANGS[lang_code]}.
137
 
138
  Instruction: {instruction}
139
+ Section:
140
+ {section}
141
  """
142
+ out = ask_gemini(prompt)
143
+ return translate_text(out, lang_code)
144
 
145
+ def generate_cover_letter(resume_md, job_desc, tone, lang_code):
146
  prompt = f"""
147
+ Draft a one-page cover letter (max 300 words) in a {tone} tone, matching this rรฉsumรฉ to the job description. Use {LANGS[lang_code]}.
148
+ Salutation: "Dear Hiring Manager,"
149
 
150
+ Rรฉsumรฉ:
151
  {resume_md}
152
 
153
+ Job Description:
154
  {job_desc}
155
  """
156
  letter = ask_gemini(prompt)
157
+ return translate_text(letter, lang_code)
158
 
 
 
 
159
  def scrape_job_description(url):
160
  try:
161
+ hdr = {"User-Agent": "Mozilla/5.0"}
162
+ r = requests.get(url, headers=hdr, timeout=10)
163
+ soup = BeautifulSoup(r.text, "html.parser")
164
+ for sel in ["div.jobsearch-jobDescriptionText", "section.description", "div.jobs-description__content"]:
 
 
 
 
 
 
 
 
165
  block = soup.select_one(sel)
166
  if block:
167
  return block.get_text(" ", strip=True)
168
  return soup.get_text(" ", strip=True)[:3000]
169
  except Exception as e:
170
+ return f"[Scrape Error] {e}"
171
 
172
+ def generate_and_export(name, email, phone, summary, exp, edu, skills, lang_code):
173
+ md = generate_resume(name, email, phone, summary, exp, edu, skills, lang_code)
174
+ return md, save_docx(md), save_pdf(md)
 
 
 
 
 
175
 
176
  # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
177
  # Gradio UI
178
  # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
179
  with gr.Blocks(title="AI Resume Studio") as demo:
180
+ gr.Markdown("## ๐Ÿง  Interactive AI Resume Studio (Gemini ร— DeepL)")
 
 
181
 
182
+ # Generate Resume
183
  with gr.Tab("๐Ÿ“„ Generate Resume"):
184
  with gr.Row():
185
+ name_in, email_in, phone_in = gr.Textbox(label="Name"), gr.Textbox(label="Email"), gr.Textbox(label="Phone")
186
+ summary_in = gr.Textbox(label="Professional Summary")
187
+ exp_in = gr.Textbox(label="Experience")
188
+ edu_in = gr.Textbox(label="Education")
189
+ skills_in = gr.Textbox(label="Skills")
190
+ lang_in = gr.Dropdown(list(LANGS.keys()), value="EN", label="Language")
191
+
192
+ resume_md_out = gr.Markdown(label="Rรฉsume (Markdown)")
193
+ docx_out = gr.File(label="โฌ‡ Download .docx")
194
+ pdf_out = gr.File(label="โฌ‡ Download .pdf")
195
+ gen_btn = gr.Button("Generate")
 
 
196
 
197
  gen_btn.click(
198
+ generate_and_export,
199
+ inputs=[name_in, email_in, phone_in, summary_in, exp_in, edu_in, skills_in, lang_in],
200
+ outputs=[resume_md_out, docx_out, pdf_out],
201
  )
202
 
203
+ # Score Resume
204
  with gr.Tab("๐Ÿงฎ Score Resume Against Job"):
205
+ resume_score_in = gr.Textbox(label="Rรฉsumรฉ (Markdown)", lines=10)
206
+ jd_score_in = gr.Textbox(label="Job Description", lines=8)
207
+ score_out = gr.Markdown(label="Score & Suggestions")
208
+ score_btn = gr.Button("Evaluate")
209
+
210
+ score_btn.click(score_resume, inputs=[resume_score_in, jd_score_in], outputs=score_out)
211
+
212
+ # AI Section Co-Pilot
213
+ with gr.Tab("โœ๏ธ AI Section Co-Pilot"):
214
+ sec_in = gr.Textbox(label="Section Text", lines=6)
215
+ inst_in = gr.Radio(["Rewrite", "Make More Concise", "Quantify Achievements", "Convert to Bullet Points"], label="Action")
216
+ lang_sec = gr.Dropdown(list(LANGS.keys()), value="EN", label="Language")
217
+ sec_out = gr.Textbox(label="AI Output", lines=6)
218
+ sec_btn = gr.Button("Apply")
219
+
220
+ sec_btn.click(refine_section, inputs=[sec_in, inst_in, lang_sec], outputs=sec_out)
221
+
222
+ # Cover-Letter Generator
223
+ with gr.Tab("๐Ÿ“ง Cover-Letter Generator"):
224
+ cv_res_in = gr.Textbox(label="Rรฉsumรฉ (Markdown)", lines=12)
225
+ cv_jd_in = gr.Textbox(label="Job Description", lines=8)
 
 
 
 
 
226
  cv_tone = gr.Radio(["Professional", "Friendly", "Enthusiastic"], value="Professional", label="Tone")
227
+ cv_lang = gr.Dropdown(list(LANGS.keys()), value="EN", label="Language")
228
+ cv_out = gr.Markdown(label="Cover Letter")
229
+ cv_btn = gr.Button("Generate")
230
+
231
+ cv_btn.click(generate_cover_letter, inputs=[cv_res_in, cv_jd_in, cv_tone, cv_lang], outputs=cv_out)
 
 
 
 
232
 
233
+ # Job-Description Scraper
234
  with gr.Tab("๐ŸŒ Job Description Scraper"):
235
+ url_in = gr.Textbox(label="Job URL")
236
+ jd_out = gr.Textbox(label="Extracted Description", lines=12)
237
+ scrape_btn= gr.Button("Fetch")
238
 
239
+ scrape_btn.click(scrape_job_description, inputs=[url_in], outputs=[jd_out])
240
 
241
+ demo.launch(share=False)