Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -1,43 +1,51 @@
|
|
1 |
"""
|
2 |
-
AI Resume Studio โ Hugging
|
3 |
-
Author: Oluwafemi
|
4 |
-
Last update: 2025
|
5 |
|
6 |
Features
|
7 |
โโโโโโโโ
|
8 |
1. Generate resume โ Word & PDF downloads
|
9 |
2. Score resume against job description
|
10 |
-
3. AI Section Co
|
11 |
-
4. Cover
|
12 |
-
5. Job
|
13 |
-
6. Multilingual export via
|
14 |
"""
|
15 |
|
16 |
-
|
17 |
-
|
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
|
26 |
from reportlab.lib.pagesizes import LETTER
|
27 |
from reportlab.pdfgen import canvas
|
28 |
-
|
29 |
|
|
|
|
|
|
|
30 |
load_dotenv()
|
31 |
|
32 |
-
# Gemini
|
33 |
genai.configure(api_key=os.getenv("API_KEY"))
|
34 |
-
|
35 |
|
36 |
-
# DeepL
|
37 |
DEEPL_KEY = os.getenv("DEEPL_API_KEY")
|
38 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
39 |
|
40 |
-
# Supported
|
41 |
LANGS = {
|
42 |
"EN": "English",
|
43 |
"DE": "German",
|
@@ -45,231 +53,189 @@ LANGS = {
|
|
45 |
"ES": "Spanish",
|
46 |
"IT": "Italian",
|
47 |
"NL": "Dutch",
|
48 |
-
"PT
|
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,
|
59 |
try:
|
60 |
-
|
61 |
-
|
62 |
-
|
63 |
-
return
|
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(
|
77 |
-
|
78 |
doc = Document()
|
79 |
-
for line in
|
80 |
doc.add_paragraph(line)
|
81 |
-
doc.save(
|
82 |
-
return
|
83 |
|
84 |
-
def save_pdf(
|
85 |
-
|
86 |
-
c = canvas.Canvas(
|
87 |
width, height = LETTER
|
88 |
y = height - 72
|
89 |
-
for line in
|
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
|
97 |
|
98 |
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
99 |
-
# Core
|
100 |
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
101 |
-
def generate_resume(name, email, phone, summary,
|
102 |
prompt = f"""
|
103 |
-
Create a professional
|
104 |
-
|
105 |
-
Targetย language: {LANGS[lang]}
|
106 |
|
107 |
Name: {name}
|
108 |
Email: {email}
|
109 |
Phone: {phone}
|
110 |
|
111 |
-
Professional
|
112 |
{summary}
|
113 |
|
114 |
Experience:
|
115 |
-
{
|
116 |
|
117 |
Education:
|
118 |
-
{
|
119 |
|
120 |
Skills:
|
121 |
{skills}
|
122 |
"""
|
123 |
-
|
124 |
-
return
|
125 |
|
126 |
def score_resume(resume_md, job_desc):
|
127 |
prompt = f"""
|
128 |
-
Evaluate
|
129 |
-
|
130 |
-
|
|
|
131 |
|
132 |
### Suggestions
|
133 |
-
-
|
134 |
-
-
|
135 |
-
- <bulletย 3>
|
136 |
"""
|
137 |
-
return ask_gemini(prompt
|
138 |
|
139 |
-
def refine_section(
|
140 |
prompt = f"""
|
141 |
-
|
142 |
|
143 |
Instruction: {instruction}
|
144 |
-
|
145 |
-
{
|
146 |
"""
|
147 |
-
|
148 |
-
return
|
149 |
|
150 |
-
def generate_cover_letter(resume_md, job_desc, tone,
|
151 |
prompt = f"""
|
152 |
-
Draft a one
|
153 |
-
|
154 |
|
155 |
-
|
156 |
{resume_md}
|
157 |
|
158 |
-
|
159 |
{job_desc}
|
160 |
"""
|
161 |
letter = ask_gemini(prompt)
|
162 |
-
return
|
163 |
|
164 |
-
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
165 |
-
# JD scraper
|
166 |
-
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
167 |
def scrape_job_description(url):
|
168 |
try:
|
169 |
-
|
170 |
-
|
171 |
-
soup = BeautifulSoup(
|
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 |
-
|
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("## ๐ง
|
202 |
-
|
203 |
-
LANG_CHOICES = [(v, k) for k, v in LANGS.items()]
|
204 |
|
205 |
-
#
|
206 |
with gr.Tab("๐ Generate Resume"):
|
207 |
with gr.Row():
|
208 |
-
|
209 |
-
|
210 |
-
|
211 |
-
|
212 |
-
|
213 |
-
|
214 |
-
|
215 |
-
|
216 |
-
|
217 |
-
|
218 |
-
|
219 |
-
pdf_file = gr.File(label="โฌย PDF (.pdf)")
|
220 |
-
gen_btn = gr.Button("Generate Resume")
|
221 |
|
222 |
gen_btn.click(
|
223 |
-
|
224 |
-
inputs=[
|
225 |
-
outputs=[
|
226 |
)
|
227 |
|
228 |
-
#
|
229 |
with gr.Tab("๐งฎ Score Resume Against Job"):
|
230 |
-
|
231 |
-
|
232 |
-
score_out
|
233 |
-
score_btn
|
234 |
-
|
235 |
-
score_btn.click(score_resume, inputs=[
|
236 |
-
|
237 |
-
#
|
238 |
-
with gr.Tab("โ๏ธ AI Section Co
|
239 |
-
|
240 |
-
|
241 |
-
|
242 |
-
|
243 |
-
)
|
244 |
-
|
245 |
-
|
246 |
-
|
247 |
-
|
248 |
-
|
249 |
-
|
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(
|
258 |
-
cv_out = gr.Markdown(label="Cover
|
259 |
-
cv_btn = gr.Button("Generate
|
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 |
-
#
|
268 |
with gr.Tab("๐ Job Description Scraper"):
|
269 |
-
url_in
|
270 |
-
jd_out
|
271 |
-
scrape_btn
|
272 |
|
273 |
-
scrape_btn.click(scrape_job_description, inputs=url_in, outputs=jd_out)
|
274 |
|
275 |
-
demo.launch(share=False)
|
|
|
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)
|