mgbam commited on
Commit
a1a2096
ยท
verified ยท
1 Parent(s): 7c31f12

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +224 -129
app.py CHANGED
@@ -1,85 +1,79 @@
1
- import os, tempfile, datetime
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
  import gradio as gr
3
  import google.generativeai as genai
4
  from dotenv import load_dotenv
5
-
6
- # โ”€โ”€ 3rdโ€‘party formatters โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
7
- from docx import Document # python-docx
8
- from reportlab.lib.pagesizes import LETTER # reportlab
9
  from reportlab.pdfgen import canvas
 
10
 
11
- # โ”€โ”€ Environment & Model โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
12
  load_dotenv()
13
- API_KEY = os.getenv("GEMINI_API_KEY")
14
- genai.configure(api_key=API_KEY)
15
- model = genai.GenerativeModel("gemini-1.5-pro-latest")
16
-
17
- # โ”€โ”€ Core AI helpers โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
18
- def _ask_gemini(prompt: str) -> str:
19
- """Utility wrapper with basic errorโ€handling."""
20
- try:
21
- rsp = model.generate_content(prompt, generation_config={"temperature": 0.7})
22
- return rsp.text.strip()
23
- except Exception as exc:
24
- return f"[Gemini Error] {exc}"
25
-
26
- # โ”€โ”€ Business Logic โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
27
- def generate_resume(name, email, phone, summary, experience, education, skills):
28
- prompt = f"""
29
- Create a professionally formatted resume. Use Markdown headings and no firstโ€‘person pronouns.
30
-
31
- Name: {name}
32
- Email: {email}
33
- Phone: {phone}
34
-
35
- Professional Summary:
36
- {summary}
37
-
38
- Experience:
39
- {experience}
40
 
41
- Education:
42
- {education}
 
43
 
44
- Skills:
45
- {skills}
46
- """
47
- return _ask_gemini(prompt)
48
 
49
- def score_resume(resume, job_desc):
50
- prompt = f"""
51
- Evaluate the RESUME against the JOB DESCRIPTION. Return JSON:
52
- {{
53
- "score": <0โ€‘100>,
54
- "suggestions": [ "โ€ฆ", "โ€ฆ" ]
55
- }}
56
- RESUME:
57
- {resume}
58
-
59
- JOB DESCRIPTION:
60
- {job_desc}
61
- """
62
- return _ask_gemini(prompt)
63
-
64
- def refine_text(section_text, instruction):
65
- prompt = f"Refine the following text.\nInstruction: {instruction}\nText:\n{section_text}"
66
- return _ask_gemini(prompt)
67
-
68
- def generate_cover_letter(resume, job_desc, tone):
69
- prompt = f"""
70
- Act as a career coach. Draft a oneโ€‘page cover letter **in {tone} tone** that aligns this RESUME
71
- to the JOB DESCRIPTION. Do not exceed 300 words.
72
 
73
- RESUME:
74
- {resume}
 
 
 
 
 
 
 
 
 
75
 
76
- JOB DESCRIPTION:
77
- {job_desc}
78
- """
79
- return _ask_gemini(prompt)
 
 
 
 
80
 
81
- # โ”€โ”€ Fileโ€‘export helpers โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
82
- def _to_docx(text: str) -> str:
83
  tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".docx")
84
  doc = Document()
85
  for line in text.splitlines():
@@ -87,7 +81,7 @@ def _to_docx(text: str) -> str:
87
  doc.save(tmp.name)
88
  return tmp.name
89
 
90
- def _to_pdf(text: str) -> str:
91
  tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".pdf")
92
  c = canvas.Canvas(tmp.name, pagesize=LETTER)
93
  width, height = LETTER
@@ -101,80 +95,181 @@ def _to_pdf(text: str) -> str:
101
  c.save()
102
  return tmp.name
103
 
104
- def generate_resume_and_files(*inputs):
105
- resume_md = generate_resume(*inputs)
106
- return (
107
- resume_md,
108
- _to_docx(resume_md),
109
- _to_pdf(resume_md),
110
- )
111
-
112
- # โ”€โ”€ Gradio UI โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
113
- with gr.Blocks(title="AIย Resumeย Builder using Gemini") as demo:
114
- gr.Markdown("## ๐Ÿง ย AIโ€ฏResumeย Builderย ร—ย Gemini")
115
-
116
- # โ”€โ”€ Tabย 1: Resume generation โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
117
- with gr.Tab("๐Ÿ“„ย Generate Resume"):
118
- name = gr.Textbox(label="Name")
119
- email = gr.Textbox(label="Email")
120
- phone = gr.Textbox(label="Phone")
121
- summary = gr.Textbox(label="Professional Summary")
122
- experience= gr.Textbox(label="Experience")
123
- education = gr.Textbox(label="Education")
124
- skills = gr.Textbox(label="Skills")
125
-
126
- resume_out = gr.Markdown(label="Generated Resume")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
127
  docx_file = gr.File(label="โฌ‡ย Word (.docx)")
128
  pdf_file = gr.File(label="โฌ‡ย PDF (.pdf)")
129
  gen_btn = gr.Button("Generate Resume")
130
 
131
  gen_btn.click(
132
  generate_resume_and_files,
133
- inputs=[name, email, phone, summary, experience, education, skills],
134
- outputs=[resume_out, docx_file, pdf_file],
135
  )
136
 
137
- # โ”€โ”€ Tabย 2: Jobโ€‘match score โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
138
- with gr.Tab("๐Ÿงฎย Score Resume Against Job"):
139
- resume_in = gr.Textbox(label="Resumeย (Markdown)", lines=12)
140
- jd_in = gr.Textbox(label="Jobย Description", lines=8)
141
- score_out = gr.Markdown(label="Score & Suggestions")
142
- score_btn = gr.Button("Evaluate")
143
 
144
- def parse_score(rsp):
145
- return rsp # we leave JSON parsing to the user for now
146
 
147
- score_btn.click(
148
- lambda r, jd: parse_score(score_resume(r, jd)),
149
- inputs=[resume_in, jd_in],
150
- outputs=score_out,
 
 
151
  )
 
 
 
152
 
153
- # โ”€โ”€ Tabย 3: Refine section โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
154
- with gr.Tab("โœ๏ธย Refine Resume Section"):
155
- sec_in = gr.Textbox(label="Section Text", lines=6)
156
- instr_in = gr.Textbox(label="Instruction (e.g., make concise)")
157
- refined = gr.Textbox(label="Refined Output", lines=6)
158
- refine_btn = gr.Button("Refine")
159
-
160
- refine_btn.click(
161
- lambda text, instr: refine_text(text, instr),
162
- inputs=[sec_in, instr_in],
163
- outputs=refined,
164
  )
165
 
166
- # โ”€โ”€ Tabย 4: Coverโ€‘Letter generator โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
167
- with gr.Tab("๐Ÿ“งย Coverโ€‘Letter Generator"):
168
- cv_resume = gr.Textbox(label="Resumeย (Markdown)", lines=12)
169
- cv_jd = gr.Textbox(label="Jobย Description", lines=8)
170
- cv_tone = gr.Radio(["Professional", "Friendly", "Enthusiastic"], value="Professional", label="Tone")
171
- cv_out = gr.Markdown(label="Generated Cover Letter")
172
- cv_btn = gr.Button("Generate Cover Letter")
 
173
 
174
  cv_btn.click(
175
- lambda r, jd, tone: generate_cover_letter(r, jd, tone),
176
- inputs=[cv_resume, cv_jd, cv_tone],
177
  outputs=cv_out,
178
  )
179
 
180
- demo.launch(share=False) # โ† Spaces already exposes the public URL
 
 
 
 
 
 
 
 
 
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",
44
+ "FR": "French",
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():
 
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
 
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