Spaces:
Sleeping
Sleeping
Add application file
Browse files
app.py
ADDED
@@ -0,0 +1,188 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# app.py
|
2 |
+
|
3 |
+
import os
|
4 |
+
import io
|
5 |
+
import json
|
6 |
+
from datetime import datetime
|
7 |
+
|
8 |
+
import streamlit as st
|
9 |
+
from transformers import pipeline
|
10 |
+
from docx import Document
|
11 |
+
from PyPDF2 import PdfReader
|
12 |
+
|
13 |
+
# ------------------------------------------------------------------------------
|
14 |
+
# Model Pipeline Setup
|
15 |
+
# ------------------------------------------------------------------------------
|
16 |
+
|
17 |
+
# Use an instruction-tuned model (e.g., Flan-T5-xl)
|
18 |
+
model_name = "google/flan-t5-xl"
|
19 |
+
instruct_pipeline = pipeline(
|
20 |
+
"text2text-generation", model=model_name, max_length=1024, truncation=True
|
21 |
+
)
|
22 |
+
|
23 |
+
# ------------------------------------------------------------------------------
|
24 |
+
# Helper Functions for File Reading
|
25 |
+
# ------------------------------------------------------------------------------
|
26 |
+
|
27 |
+
def read_docx(file_bytes: bytes) -> str:
|
28 |
+
"""Extract text from a DOCX file."""
|
29 |
+
document = Document(io.BytesIO(file_bytes))
|
30 |
+
return "\n".join([para.text for para in document.paragraphs])
|
31 |
+
|
32 |
+
def read_pdf(file_bytes: bytes) -> str:
|
33 |
+
"""Extract text from a PDF file."""
|
34 |
+
reader = PdfReader(io.BytesIO(file_bytes))
|
35 |
+
text = []
|
36 |
+
for page in reader.pages:
|
37 |
+
extracted = page.extract_text()
|
38 |
+
if extracted:
|
39 |
+
text.append(extracted)
|
40 |
+
return "\n".join(text)
|
41 |
+
|
42 |
+
def read_plain(file_bytes: bytes) -> str:
|
43 |
+
"""Extract text from a plain text file."""
|
44 |
+
return file_bytes.decode("utf-8", errors="ignore")
|
45 |
+
|
46 |
+
def extract_resume_text(file_obj) -> str:
|
47 |
+
"""
|
48 |
+
Determine file type and extract text accordingly.
|
49 |
+
Supports PDF, DOCX, and plain text.
|
50 |
+
"""
|
51 |
+
file_bytes = file_obj.read()
|
52 |
+
file_obj.seek(0) # Reset pointer for potential further use
|
53 |
+
filename = file_obj.name if hasattr(file_obj, "name") else "resume.txt"
|
54 |
+
ext = os.path.splitext(filename)[-1].lower()
|
55 |
+
|
56 |
+
if ext == ".pdf":
|
57 |
+
return read_pdf(file_bytes)
|
58 |
+
elif ext in [".docx", ".doc"]:
|
59 |
+
return read_docx(file_bytes)
|
60 |
+
else:
|
61 |
+
return read_plain(file_bytes)
|
62 |
+
|
63 |
+
# ------------------------------------------------------------------------------
|
64 |
+
# Core AI Functions
|
65 |
+
# ------------------------------------------------------------------------------
|
66 |
+
|
67 |
+
def parse_resume(resume_text: str) -> str:
|
68 |
+
"""
|
69 |
+
Extract candidate details from the resume text.
|
70 |
+
The model returns a JSON with keys: first_name, last_name, location,
|
71 |
+
work_experience, school_experience, and skills.
|
72 |
+
"""
|
73 |
+
prompt = (
|
74 |
+
"You are an expert resume parser. Extract the following keys from the resume text: "
|
75 |
+
"first_name, last_name, location, work_experience, school_experience, and skills. "
|
76 |
+
"Return the answer strictly in JSON format with no extra text.\n\n"
|
77 |
+
f"Resume:\n{resume_text}"
|
78 |
+
)
|
79 |
+
result = instruct_pipeline(prompt, max_length=512)[0]["generated_text"]
|
80 |
+
return result.strip()
|
81 |
+
|
82 |
+
def generate_cover_letter(candidate_json: str, job_description: str, date_str: str) -> str:
|
83 |
+
"""
|
84 |
+
Generate a cover letter using the candidate's JSON profile, the job description,
|
85 |
+
and the current date.
|
86 |
+
"""
|
87 |
+
prompt = (
|
88 |
+
"You are a seasoned career advisor and professional cover letter writer. "
|
89 |
+
"Using the candidate information provided in JSON format, craft a persuasive and personalized cover letter "
|
90 |
+
"tailored to the job description. Structure your answer with an introduction, body, and closing, "
|
91 |
+
"and include the date {date}.\n\n"
|
92 |
+
"Candidate JSON:\n{candidate_json}\n\n"
|
93 |
+
"Job Description:\n{job_description}\n\n"
|
94 |
+
"Cover Letter:".format(
|
95 |
+
candidate_json=candidate_json, job_description=job_description, date=date_str
|
96 |
+
)
|
97 |
+
)
|
98 |
+
result = instruct_pipeline(prompt, max_length=1024)[0]["generated_text"]
|
99 |
+
return result.strip()
|
100 |
+
|
101 |
+
def generate_resume(first_name: str, last_name: str, location: str,
|
102 |
+
work_experience: str, school_experience: str, skills: str) -> str:
|
103 |
+
"""
|
104 |
+
Generate a professional resume using candidate information provided in a form.
|
105 |
+
"""
|
106 |
+
candidate_info = {
|
107 |
+
"first_name": first_name,
|
108 |
+
"last_name": last_name,
|
109 |
+
"location": location,
|
110 |
+
"work_experience": work_experience,
|
111 |
+
"school_experience": school_experience,
|
112 |
+
"skills": skills,
|
113 |
+
}
|
114 |
+
candidate_str = json.dumps(candidate_info, indent=2)
|
115 |
+
|
116 |
+
prompt = (
|
117 |
+
"You are a professional resume writer. Using the candidate information provided in JSON, "
|
118 |
+
"create a compelling, well-organized resume in plain text format. Include clear sections for "
|
119 |
+
"personal details, work experience, education, and skills. Ensure the resume is concise and professional.\n\n"
|
120 |
+
"Candidate Information:\n" + candidate_str + "\n\nResume:"
|
121 |
+
)
|
122 |
+
result = instruct_pipeline(prompt, max_length=1024)[0]["generated_text"]
|
123 |
+
return result.strip()
|
124 |
+
|
125 |
+
# ------------------------------------------------------------------------------
|
126 |
+
# Streamlit UI
|
127 |
+
# ------------------------------------------------------------------------------
|
128 |
+
|
129 |
+
st.set_page_config(page_title="AI-Powered Job Application Assistant", layout="wide")
|
130 |
+
st.title("AI-Powered Job Application Assistant")
|
131 |
+
st.markdown("Generate a **Cover Letter** from your resume or **Create a Resume** from scratch using AI.")
|
132 |
+
|
133 |
+
# Create tabs for the two functionalities
|
134 |
+
tabs = st.tabs(["Cover Letter Generator", "Resume Creator"])
|
135 |
+
|
136 |
+
# ----- Tab 1: Cover Letter Generator -----
|
137 |
+
with tabs[0]:
|
138 |
+
st.header("Cover Letter Generator")
|
139 |
+
st.markdown("Upload your resume (PDF, DOCX, or TXT) and paste the job description below.")
|
140 |
+
|
141 |
+
col1, col2 = st.columns(2)
|
142 |
+
|
143 |
+
with col1:
|
144 |
+
resume_file = st.file_uploader("Upload Your Resume", type=["pdf", "docx", "doc", "txt"])
|
145 |
+
|
146 |
+
with col2:
|
147 |
+
job_description = st.text_area("Job Description", height=200, placeholder="Paste the job description here...")
|
148 |
+
|
149 |
+
if st.button("Generate Cover Letter"):
|
150 |
+
if resume_file is None or not job_description.strip():
|
151 |
+
st.error("Please upload a resume and enter a job description.")
|
152 |
+
else:
|
153 |
+
with st.spinner("Processing resume..."):
|
154 |
+
resume_text = extract_resume_text(resume_file)
|
155 |
+
candidate_json = parse_resume(resume_text)
|
156 |
+
current_date = datetime.today().strftime("%d - %b - %Y")
|
157 |
+
cover_letter = generate_cover_letter(candidate_json, job_description, current_date)
|
158 |
+
|
159 |
+
st.subheader("Extracted Candidate Profile (JSON)")
|
160 |
+
st.code(candidate_json, language="json")
|
161 |
+
|
162 |
+
st.subheader("Generated Cover Letter")
|
163 |
+
st.text_area("", cover_letter, height=300)
|
164 |
+
|
165 |
+
# ----- Tab 2: Resume Creator -----
|
166 |
+
with tabs[1]:
|
167 |
+
st.header("Resume Creator")
|
168 |
+
st.markdown("Fill in your details below to generate a professional resume from scratch.")
|
169 |
+
|
170 |
+
with st.form("resume_form"):
|
171 |
+
col1, col2 = st.columns(2)
|
172 |
+
with col1:
|
173 |
+
first_name = st.text_input("First Name", placeholder="John")
|
174 |
+
with col2:
|
175 |
+
last_name = st.text_input("Last Name", placeholder="Doe")
|
176 |
+
|
177 |
+
location = st.text_input("Location", placeholder="City, State, Country")
|
178 |
+
work_experience = st.text_area("Work Experience", height=150, placeholder="List your past roles and achievements...")
|
179 |
+
school_experience = st.text_area("Education", height=150, placeholder="List your academic qualifications...")
|
180 |
+
skills = st.text_area("Skills", height=100, placeholder="List your key skills...")
|
181 |
+
|
182 |
+
submit = st.form_submit_button("Generate Resume")
|
183 |
+
|
184 |
+
if submit:
|
185 |
+
with st.spinner("Generating resume..."):
|
186 |
+
resume_text = generate_resume(first_name, last_name, location, work_experience, school_experience, skills)
|
187 |
+
st.subheader("Generated Resume")
|
188 |
+
st.text_area("", resume_text, height=400)
|