mgbam commited on
Commit
146b35d
ยท
verified ยท
1 Parent(s): 073ee45

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +38 -41
app.py CHANGED
@@ -13,6 +13,7 @@ Features
13
  โ€ข LinkedIn via OAuth2 Jobs API
14
  โ€ข All other sites via HTML scraping
15
  6. Multilingual export via Deep-Translator (DeepL backend)
 
16
  """
17
 
18
  import os
@@ -79,61 +80,47 @@ def get_linkedin_token():
79
  # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
80
  def fetch_job_description(url: str) -> str:
81
  domain = urlparse(url).netloc.lower()
82
- # 1) If it's LinkedIn, try the Jobs API first
83
  if "linkedin.com" in domain:
84
  m = re.search(r"(?:jobs/view/|currentJobId=)(\d+)", url)
85
  if m:
86
  job_id = m.group(1)
87
  try:
88
  token = get_linkedin_token()
89
- api_url = (
90
- f"https://api.linkedin.com/v2/jobPosts/{job_id}"
91
- "?projection=(description)"
92
- )
93
- r = requests.get(
94
- api_url,
95
- headers={"Authorization": f"Bearer {token}"},
96
- timeout=10
97
- )
98
  r.raise_for_status()
99
  return r.json().get("description", "")
100
  except Exception:
101
- # fall through to generic scraping
102
  pass
103
 
104
- # 2) Generic scraping for any other site (or LinkedIn fallback)
105
  try:
106
  page = requests.get(url, headers={"User-Agent":"Mozilla/5.0"}, timeout=10)
107
  soup = BeautifulSoup(page.text, "html.parser")
108
-
109
- # Common job-description selectors across platforms
110
  selectors = [
111
- "div.jobsearch-jobDescriptionText", # Indeed
112
- "section.description", # generic
113
- "div.jobs-description__content", # LinkedIn client-side
114
- "div#job-details", # Greenhouse
115
- "article.jobPosting", # Workday / custom
116
- "div.jd-container", # many ATS
117
  ]
118
  for sel in selectors:
119
  block = soup.select_one(sel)
120
  if block and block.get_text(strip=True):
121
  return block.get_text(" ", strip=True)
122
-
123
- # Fallback: return full page text (truncated)
124
  text = soup.get_text(" ", strip=True)
125
  return text[:5000] + ("โ€ฆ" if len(text) > 5000 else "")
126
  except Exception as e:
127
  return f"[Error fetching job description] {e}"
128
 
129
  # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
130
- # AI & File Utilities (unchanged)
131
  # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
132
  def ask_gemini(prompt: str, temp: float = 0.6) -> str:
133
  try:
134
- return GEMINI.generate_content(
135
- prompt, generation_config={"temperature": temp}
136
- ).text.strip()
137
  except Exception as e:
138
  return f"[Gemini Error] {e}"
139
 
@@ -160,7 +147,7 @@ def save_pdf(text: str) -> str:
160
  return f.name
161
 
162
  # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
163
- # Core AI Logic & UI Setup (unchanged)
164
  # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
165
  LANGS = {
166
  "EN": "English", "DE": "German", "FR": "French", "ES": "Spanish",
@@ -234,18 +221,23 @@ Job Description:
234
  return translate_text(letter, lang)
235
 
236
  # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
237
- # Gradio App Definition
238
  # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€๏ฟฝ๏ฟฝโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
239
  with gr.Blocks(title="AI Resume Studio") as demo:
240
  gr.Markdown("## ๐Ÿง  AI Resume Studio (Gemini ร— DeepL + Universal Job Fetcher)")
241
 
 
 
 
 
242
  # Tab 1: Generate Rรฉsumรฉ
243
  with gr.Tab("๐Ÿ“„ Generate Rรฉsumรฉ"):
244
- name_in, email_in, phone_in = (
245
- gr.Textbox(label="Name"),
246
- gr.Textbox(label="Email"),
247
- gr.Textbox(label="Phone")
248
- )
 
249
  sum_in = gr.Textbox(label="Professional Summary")
250
  exp_in = gr.Textbox(label="Experience")
251
  edu_in = gr.Textbox(label="Education")
@@ -260,13 +252,13 @@ with gr.Blocks(title="AI Resume Studio") as demo:
260
  btn_gen.click(
261
  generate_and_export,
262
  inputs=[name_in, email_in, phone_in, sum_in, exp_in, edu_in, skills_in, lang_in],
263
- outputs=[md_out, docx_out, pdf_out],
264
  )
265
 
266
  # Tab 2: Score Rรฉsumรฉ
267
  with gr.Tab("๐Ÿงฎ Score Rรฉsumรฉ Against Job"):
268
- res_in = gr.Textbox(label="Rรฉsumรฉ (Markdown)", lines=10)
269
- jd_in = gr.Textbox(label="Job Description", lines=8)
270
  score_out = gr.Markdown(label="Score & Suggestions")
271
  btn_score = gr.Button("Evaluate")
272
  btn_score.click(score_resume, inputs=[res_in, jd_in], outputs=score_out)
@@ -274,9 +266,10 @@ with gr.Blocks(title="AI Resume Studio") as demo:
274
  # Tab 3: AI Section Co-Pilot
275
  with gr.Tab("โœ๏ธ AI Section Co-Pilot"):
276
  sec_in = gr.Textbox(label="Section Text", lines=6)
277
- act_in = gr.Radio([
278
- "Rewrite", "Make More Concise", "Quantify Achievements", "Convert to Bullet Points"
279
- ], label="Action")
 
280
  lang_sec = gr.Dropdown(list(LANGS.keys()), value="EN", label="Language")
281
  sec_out = gr.Textbox(label="AI Output", lines=6)
282
  btn_sec = gr.Button("Apply")
@@ -286,7 +279,7 @@ with gr.Blocks(title="AI Resume Studio") as demo:
286
  with gr.Tab("๐Ÿ“ง Cover-Letter Generator"):
287
  cv_res = gr.Textbox(label="Rรฉsumรฉ (Markdown)", lines=12)
288
  cv_jd = gr.Textbox(label="Job Description", lines=8)
289
- cv_tone = gr.Radio(["Professional","Friendly","Enthusiastic"], label="Tone")
290
  cv_lang = gr.Dropdown(list(LANGS.keys()), value="EN", label="Language")
291
  cv_out = gr.Markdown(label="Cover Letter")
292
  btn_cv = gr.Button("Generate")
@@ -299,6 +292,10 @@ with gr.Blocks(title="AI Resume Studio") as demo:
299
  url_in = gr.Textbox(label="Job URL")
300
  jd_out = gr.Textbox(label="Job Description", lines=12)
301
  btn_fetch = gr.Button("Fetch Description")
302
- btn_fetch.click(fetch_job_description, inputs=[url_in], outputs=[jd_out])
 
 
 
 
303
 
304
  demo.launch(share=False)
 
13
  โ€ข LinkedIn via OAuth2 Jobs API
14
  โ€ข All other sites via HTML scraping
15
  6. Multilingual export via Deep-Translator (DeepL backend)
16
+ 7. Auto-populate Score tab from latest Resume & JD
17
  """
18
 
19
  import os
 
80
  # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
81
  def fetch_job_description(url: str) -> str:
82
  domain = urlparse(url).netloc.lower()
 
83
  if "linkedin.com" in domain:
84
  m = re.search(r"(?:jobs/view/|currentJobId=)(\d+)", url)
85
  if m:
86
  job_id = m.group(1)
87
  try:
88
  token = get_linkedin_token()
89
+ api_url = f"https://api.linkedin.com/v2/jobPosts/{job_id}?projection=(description)"
90
+ r = requests.get(api_url,
91
+ headers={"Authorization": f"Bearer {token}"},
92
+ timeout=10)
 
 
 
 
 
93
  r.raise_for_status()
94
  return r.json().get("description", "")
95
  except Exception:
 
96
  pass
97
 
 
98
  try:
99
  page = requests.get(url, headers={"User-Agent":"Mozilla/5.0"}, timeout=10)
100
  soup = BeautifulSoup(page.text, "html.parser")
 
 
101
  selectors = [
102
+ "div.jobsearch-jobDescriptionText",
103
+ "section.description",
104
+ "div.jobs-description__content",
105
+ "div#job-details",
106
+ "article.jobPosting",
107
+ "div.jd-container",
108
  ]
109
  for sel in selectors:
110
  block = soup.select_one(sel)
111
  if block and block.get_text(strip=True):
112
  return block.get_text(" ", strip=True)
 
 
113
  text = soup.get_text(" ", strip=True)
114
  return text[:5000] + ("โ€ฆ" if len(text) > 5000 else "")
115
  except Exception as e:
116
  return f"[Error fetching job description] {e}"
117
 
118
  # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
119
+ # AI & File Utilities
120
  # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
121
  def ask_gemini(prompt: str, temp: float = 0.6) -> str:
122
  try:
123
+ return GEMINI.generate_content(prompt, generation_config={"temperature": temp}).text.strip()
 
 
124
  except Exception as e:
125
  return f"[Gemini Error] {e}"
126
 
 
147
  return f.name
148
 
149
  # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
150
+ # Core AI Logic
151
  # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
152
  LANGS = {
153
  "EN": "English", "DE": "German", "FR": "French", "ES": "Spanish",
 
221
  return translate_text(letter, lang)
222
 
223
  # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
224
+ # Gradio App Definition with State for Auto-Populate
225
  # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€๏ฟฝ๏ฟฝโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
226
  with gr.Blocks(title="AI Resume Studio") as demo:
227
  gr.Markdown("## ๐Ÿง  AI Resume Studio (Gemini ร— DeepL + Universal Job Fetcher)")
228
 
229
+ # State to hold last-generated rรฉsumรฉ & JD
230
+ resume_state = gr.State(value="")
231
+ jd_state = gr.State(value="")
232
+
233
  # Tab 1: Generate Rรฉsumรฉ
234
  with gr.Tab("๐Ÿ“„ Generate Rรฉsumรฉ"):
235
+ with gr.Row():
236
+ name_in, email_in, phone_in = (
237
+ gr.Textbox(label="Name"),
238
+ gr.Textbox(label="Email"),
239
+ gr.Textbox(label="Phone"),
240
+ )
241
  sum_in = gr.Textbox(label="Professional Summary")
242
  exp_in = gr.Textbox(label="Experience")
243
  edu_in = gr.Textbox(label="Education")
 
252
  btn_gen.click(
253
  generate_and_export,
254
  inputs=[name_in, email_in, phone_in, sum_in, exp_in, edu_in, skills_in, lang_in],
255
+ outputs=[md_out, docx_out, pdf_out, resume_state],
256
  )
257
 
258
  # Tab 2: Score Rรฉsumรฉ
259
  with gr.Tab("๐Ÿงฎ Score Rรฉsumรฉ Against Job"):
260
+ res_in = gr.Textbox(value=resume_state, label="Rรฉsumรฉ (Markdown)", lines=10)
261
+ jd_in = gr.Textbox(value=jd_state, label="Job Description", lines=8)
262
  score_out = gr.Markdown(label="Score & Suggestions")
263
  btn_score = gr.Button("Evaluate")
264
  btn_score.click(score_resume, inputs=[res_in, jd_in], outputs=score_out)
 
266
  # Tab 3: AI Section Co-Pilot
267
  with gr.Tab("โœ๏ธ AI Section Co-Pilot"):
268
  sec_in = gr.Textbox(label="Section Text", lines=6)
269
+ act_in = gr.Radio(
270
+ ["Rewrite", "Make More Concise", "Quantify Achievements", "Convert to Bullet Points"],
271
+ label="Action"
272
+ )
273
  lang_sec = gr.Dropdown(list(LANGS.keys()), value="EN", label="Language")
274
  sec_out = gr.Textbox(label="AI Output", lines=6)
275
  btn_sec = gr.Button("Apply")
 
279
  with gr.Tab("๐Ÿ“ง Cover-Letter Generator"):
280
  cv_res = gr.Textbox(label="Rรฉsumรฉ (Markdown)", lines=12)
281
  cv_jd = gr.Textbox(label="Job Description", lines=8)
282
+ cv_tone = gr.Radio(["Professional", "Friendly", "Enthusiastic"], label="Tone")
283
  cv_lang = gr.Dropdown(list(LANGS.keys()), value="EN", label="Language")
284
  cv_out = gr.Markdown(label="Cover Letter")
285
  btn_cv = gr.Button("Generate")
 
292
  url_in = gr.Textbox(label="Job URL")
293
  jd_out = gr.Textbox(label="Job Description", lines=12)
294
  btn_fetch = gr.Button("Fetch Description")
295
+ btn_fetch.click(
296
+ fetch_job_description,
297
+ inputs=[url_in],
298
+ outputs=[jd_out, jd_state],
299
+ )
300
 
301
  demo.launch(share=False)