mgbam commited on
Commit
03aa58e
ยท
verified ยท
1 Parent(s): 67588b7

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +120 -122
app.py CHANGED
@@ -1,88 +1,105 @@
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",
52
- "FR": "French",
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:
@@ -92,18 +109,23 @@ def save_pdf(content: str) -> str:
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:
@@ -116,35 +138,32 @@ 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รฉ:
@@ -154,88 +173,67 @@ 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)
 
1
  """
2
  AI Resume Studio โ€“ Hugging Face Space
3
  Author: Oluwafemi Idiakhoa
4
+ Updated: 2025-06-27
5
 
6
  Features
7
  โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
8
+ 1. Generate rรฉsumรฉ โ†’ Word & PDF downloads
9
+ 2. Score rรฉsumรฉ vs. job description
10
  3. AI Section Co-Pilot (rewrite, quantify, bulletizeโ€ฆ)
11
  4. Cover-letter generator
12
+ 5. Job-description via LinkedIn API (OAuth client_credentials)
13
  6. Multilingual export via Deep-Translator (DeepL backend)
14
  """
15
 
16
+ import os, re, tempfile
 
17
  import requests
18
  import gradio as gr
19
  import google.generativeai as genai
20
  from dotenv import load_dotenv
21
+ from docx import Document
 
22
  from reportlab.lib.pagesizes import LETTER
23
  from reportlab.pdfgen import canvas
24
  from deep_translator import DeeplTranslator
25
 
26
  # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
27
+ # Load secrets & configure services
28
  # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
29
  load_dotenv()
30
 
31
+ # Gemini setup
32
+ genai.configure(api_key=os.getenv("GEMINI_API_KEY"))
33
  GEMINI = genai.GenerativeModel("gemini-1.5-pro-latest")
34
 
35
  # DeepL via Deep-Translator
36
  DEEPL_KEY = os.getenv("DEEPL_API_KEY")
37
+ def translate_text(text: str, tgt: str) -> str:
38
+ if not DEEPL_KEY or tgt.upper()=="EN": return text
 
 
39
  try:
40
+ return DeeplTranslator(api_key=DEEPL_KEY, target=tgt).translate(text)
41
  except Exception as e:
42
  return f"[Translation Error] {e}\n\n{text}"
43
 
44
+ # LinkedIn OAuth 2.0 Client-Credentials
45
+ LINKEDIN_CLIENT_ID = os.getenv("LINKEDIN_CLIENT_ID")
46
+ LINKEDIN_CLIENT_SECRET = os.getenv("LINKEDIN_CLIENT_SECRET")
47
+ _TOKEN_CACHE = {}
48
+ def get_linkedin_token():
49
+ # cache until expiry
50
+ token_data = _TOKEN_CACHE.get("data")
51
+ if token_data and token_data["expires_at"] > time.time():
52
+ return token_data["access_token"]
53
+ resp = requests.post(
54
+ "https://www.linkedin.com/oauth/v2/accessToken",
55
+ data={
56
+ "grant_type": "client_credentials",
57
+ "client_id": LINKEDIN_CLIENT_ID,
58
+ "client_secret": LINKEDIN_CLIENT_SECRET,
59
+ },
60
+ )
61
+ resp.raise_for_status()
62
+ data = resp.json()
63
+ data["expires_at"] = time.time() + data.get("expires_in", 0) - 60
64
+ _TOKEN_CACHE["data"] = data
65
+ return data["access_token"]
66
+
67
+ def fetch_job_via_api(job_url: str) -> str:
68
+ job_id = (re.search(r"/jobs/view/(\d+)", job_url) or re.search(r"currentJobId=(\d+)", job_url))
69
+ if not job_id:
70
+ return "[Error] Unable to parse job ID from URL."
71
+ token = get_linkedin_token()
72
+ headers = {"Authorization": f"Bearer {token}"}
73
+ # LinkedIn v2 Jobs endpoint (requires r_jobs scope)
74
+ api_url = f"https://api.linkedin.com/v2/jobPosts/{job_id.group(1)}?projection=(description)"
75
+ r = requests.get(api_url, headers=headers, timeout=10)
76
+ if r.status_code != 200:
77
+ return f"[LinkedIn API Error {r.status_code}] {r.text}"
78
+ return r.json().get("description", "")
79
 
80
  # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
81
+ # AI & File utilities
82
  # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
83
  def ask_gemini(prompt: str, temp: float = 0.6) -> str:
84
  try:
85
+ return GEMINI.generate_content(prompt, generation_config={"temperature": temp}).text.strip()
 
86
  except Exception as e:
87
  return f"[Gemini Error] {e}"
88
 
89
+ def save_docx(text: str) -> str:
90
  f = tempfile.NamedTemporaryFile(delete=False, suffix=".docx")
91
  doc = Document()
92
+ for line in text.splitlines():
93
  doc.add_paragraph(line)
94
  doc.save(f.name)
95
  return f.name
96
 
97
+ def save_pdf(text: str) -> str:
98
  f = tempfile.NamedTemporaryFile(delete=False, suffix=".pdf")
99
  c = canvas.Canvas(f.name, pagesize=LETTER)
100
  width, height = LETTER
101
  y = height - 72
102
+ for line in text.splitlines():
103
  c.drawString(72, y, line)
104
  y -= 14
105
  if y < 72:
 
109
  return f.name
110
 
111
  # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
112
+ # Core application logic
113
  # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
114
+ LANGS = {
115
+ "EN": "English", "DE": "German", "FR": "French", "ES": "Spanish",
116
+ "IT": "Italian", "NL": "Dutch", "PT": "Portuguese","PL": "Polish",
117
+ "JA": "Japanese","ZH": "Chinese"
118
+ }
119
+
120
+ def generate_resume(name, email, phone, summary, exp, edu, skills, lang):
121
  prompt = f"""
122
+ Create a professional rรฉsumรฉ in Markdownโ€”no first-person. Output in {LANGS[lang]}.
 
123
 
124
  Name: {name}
125
  Email: {email}
126
  Phone: {phone}
127
 
128
+ Summary:
129
  {summary}
130
 
131
  Experience:
 
138
  {skills}
139
  """
140
  md = ask_gemini(prompt)
141
+ return translate_text(md, lang)
142
+
143
+ def generate_and_export(name, email, phone, summary, exp, edu, skills, lang):
144
+ md = generate_resume(name, email, phone, summary, exp, edu, skills, lang)
145
+ return md, save_docx(md), save_pdf(md)
146
 
147
  def score_resume(resume_md, job_desc):
148
  prompt = f"""
149
+ Evaluate this rรฉsumรฉ vs. the job description. Return Markdown:
150
 
151
  ### Match Score
152
  <0-100>
153
 
154
  ### Suggestions
155
+ - ...
 
156
  """
157
  return ask_gemini(prompt, temp=0.4)
158
 
159
+ def refine_section(section, instr, lang):
160
+ prompt = f"Refine this rรฉsumรฉ section in {LANGS[lang]}.\nInstruction: {instr}\nText:\n{section}"
 
 
 
 
 
 
161
  out = ask_gemini(prompt)
162
+ return translate_text(out, lang)
163
 
164
+ def generate_cover_letter(resume_md, job_desc, tone, lang):
165
  prompt = f"""
166
+ Draft a one-page cover letter (โ‰ค300 words), {tone} tone, in {LANGS[lang]}.
167
  Salutation: "Dear Hiring Manager,"
168
 
169
  Rรฉsumรฉ:
 
173
  {job_desc}
174
  """
175
  letter = ask_gemini(prompt)
176
+ return translate_text(letter, lang)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
177
 
178
  # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
179
  # Gradio UI
180
  # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
181
  with gr.Blocks(title="AI Resume Studio") as demo:
182
+ gr.Markdown("## ๐Ÿง  AI Resume Studio (Gemini ร— DeepL ร— LinkedIn)")
183
 
184
+ # Tab 1: Generate Rรฉsumรฉ
185
+ with gr.Tab("๐Ÿ“„ Generate Rรฉsumรฉ"):
186
  with gr.Row():
187
  name_in, email_in, phone_in = gr.Textbox(label="Name"), gr.Textbox(label="Email"), gr.Textbox(label="Phone")
188
+ sum_in = gr.Textbox(label="Summary")
189
+ exp_in = gr.Textbox(label="Experience")
190
+ edu_in = gr.Textbox(label="Education")
191
+ skills_in = gr.Textbox(label="Skills")
192
+ lang_in = gr.Dropdown(list(LANGS.keys()), value="EN", label="Language")
193
 
194
+ out_md = gr.Markdown(label="Rรฉsumรฉ (Markdown)")
195
+ out_docx = gr.File(label="โฌ‡ Download .docx")
196
+ out_pdf = gr.File(label="โฌ‡ Download .pdf")
197
+ gen_btn = gr.Button("Generate")
198
 
199
  gen_btn.click(
200
  generate_and_export,
201
+ inputs=[name_in, email_in, phone_in, sum_in, exp_in, edu_in, skills_in, lang_in],
202
+ outputs=[out_md, out_docx, out_pdf],
203
  )
204
 
205
+ # Tab 2: Score Rรฉsumรฉ
206
+ with gr.Tab("๐Ÿงฎ Score Rรฉsumรฉ Against Job"):
207
+ res_in = gr.Textbox(label="Rรฉsumรฉ (Markdown)", lines=10)
208
+ jd_in = gr.Textbox(label="Job Description", lines=8)
209
+ score_out = gr.Markdown(label="Score & Suggestions")
210
+ score_btn = gr.Button("Evaluate")
211
+ score_btn.click(score_resume, inputs=[res_in, jd_in], outputs=score_out)
 
212
 
213
+ # Tab 3: AI Section Co-Pilot
214
  with gr.Tab("โœ๏ธ AI Section Co-Pilot"):
215
+ sec_in = gr.Textbox(label="Section Text", lines=6)
216
+ action = gr.Radio(["Rewrite","Make More Concise","Quantify Achievements","Convert to Bullet Points"], label="Action")
217
+ sec_lang = gr.Dropdown(list(LANGS.keys()), value="EN", label="Language")
218
+ sec_out = gr.Textbox(label="AI Output", lines=6)
219
+ sec_btn = gr.Button("Apply")
220
+ sec_btn.click(refine_section, inputs=[sec_in, action, sec_lang], outputs=sec_out)
221
+
222
+ # Tab 4: Cover-Letter Generator
 
223
  with gr.Tab("๐Ÿ“ง Cover-Letter Generator"):
224
+ cv_res = gr.Textbox(label="Rรฉsumรฉ (Markdown)", lines=12)
225
+ cv_jd = gr.Textbox(label="Job Description", lines=8)
226
+ cv_tone = gr.Radio(["Professional","Friendly","Enthusiastic"], 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
+ cv_btn.click(generate_cover_letter, inputs=[cv_res, cv_jd, cv_tone, cv_lang], outputs=cv_out)
231
+
232
+ # Tab 5: LinkedIn Job Fetcher
233
+ with gr.Tab("๐ŸŒ Fetch Job via LinkedIn API"):
234
+ url_in = gr.Textbox(label="LinkedIn Job URL")
235
+ jd_out = gr.Textbox(label="Job Description", lines=12)
236
+ fetch_btn = gr.Button("Fetch from LinkedIn")
237
+ fetch_btn.click(fetch_job_via_api, inputs=[url_in], outputs=[jd_out])
 
 
238
 
239
  demo.launch(share=False)