mgbam commited on
Commit
a6db53c
Β·
verified Β·
1 Parent(s): e882334

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +449 -101
app.py CHANGED
@@ -1,23 +1,70 @@
1
- import os
2
  import json
 
3
  import requests
4
  import streamlit as st
5
  from datetime import datetime
6
  from docx import Document
7
- from PyPDF2 import PdfReader
 
 
 
 
 
8
 
9
  # ------------------------------------------------------------------------------
10
- # πŸš€ CONFIGURATION - API & SETTINGS
 
11
  # ------------------------------------------------------------------------------
12
- GROQ_API_KEY = os.getenv("GROQ_API")
 
 
 
 
 
 
 
 
 
 
 
13
  GROQ_ENDPOINT = "https://api.groq.com/openai/v1/chat/completions"
 
 
 
 
14
  GROQ_MODEL = "llama-3.3-70b-versatile"
15
 
16
- # ------------------------------------------------------------------------------
17
- # πŸ”₯ API HANDLER FOR GROQ AI
18
- # ------------------------------------------------------------------------------
19
- def call_groq_api(messages, temperature=0.7):
20
- """Handles API calls to Groq's Llama 3.3 model with robust error handling."""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
  headers = {
22
  "Authorization": f"Bearer {GROQ_API_KEY}",
23
  "Content-Type": "application/json"
@@ -26,122 +73,423 @@ def call_groq_api(messages, temperature=0.7):
26
  "model": GROQ_MODEL,
27
  "messages": messages,
28
  "temperature": temperature,
29
- "max_tokens": 1024
 
 
 
30
  }
 
31
  try:
32
- response = requests.post(GROQ_ENDPOINT, headers=headers, json=payload, timeout=15)
33
- response.raise_for_status()
34
- return response.json()["choices"][0]["message"]["content"].strip()
 
 
 
35
  except requests.exceptions.RequestException as e:
36
- st.error(f"❌ API Request Failed: {e}")
 
 
 
 
 
 
 
 
 
 
37
  return None
 
 
38
 
39
- # ------------------------------------------------------------------------------
40
- # πŸ“„ FILE PROCESSING (PDF / DOCX)
41
- # ------------------------------------------------------------------------------
42
  def extract_resume_text(file_obj):
43
- """Extracts text from uploaded resumes (PDF, DOCX, TXT)."""
44
- file_bytes = file_obj.read()
45
- file_obj.seek(0)
46
- ext = os.path.splitext(file_obj.name)[-1].lower()
47
-
48
- if ext == ".pdf":
49
- return "\n".join(page.extract_text() for page in PdfReader(file_bytes).pages if page.extract_text())
50
- elif ext in [".docx", ".doc"]:
51
- return "\n".join([para.text for para in Document(file_bytes).paragraphs])
52
- else:
53
- return file_bytes.decode("utf-8", errors="ignore")
54
 
55
- # ------------------------------------------------------------------------------
56
- # 🎯 AI-POWERED RESUME GENERATION
57
- # ------------------------------------------------------------------------------
58
- def generate_resume(first_name, last_name, location, work_experience, school_experience, skills):
59
- """Generates a structured, ATS-friendly resume."""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60
  candidate_data = json.dumps({
61
  "first_name": first_name,
62
  "last_name": last_name,
63
  "location": location,
64
  "work_experience": work_experience,
65
  "school_experience": school_experience,
66
- "skills": skills
 
 
 
67
  }, indent=2)
68
 
69
  messages = [
70
- {"role": "system", "content": "You are a professional resume writer specializing in ATS optimization."},
 
 
 
 
 
71
  {"role": "user", "content": f"""
72
- Generate a **highly professional**, **ATS-optimized resume** using the following structured information:
 
 
 
 
 
73
  {candidate_data}
74
-
75
- - Ensure proper formatting: Name, Contact, Summary, Experience, Education, Skills, Certifications.
76
- - Use industry best practices.
77
- - Format each job with measurable achievements.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
78
  """}
79
  ]
80
- return call_groq_api(messages, temperature=0.5)
81
 
82
- # ------------------------------------------------------------------------------
83
- # ✍️ AI-POWERED COVER LETTER GENERATION
84
- # ------------------------------------------------------------------------------
85
- def generate_cover_letter(candidate_json, job_description):
86
- """Generates a compelling cover letter using structured candidate details."""
87
- date_str = datetime.today().strftime("%d - %b - %Y")
 
 
 
 
 
 
 
 
 
 
 
88
  messages = [
89
- {"role": "system", "content": "You are a top-tier career advisor. Write highly persuasive cover letters."},
 
 
 
 
 
90
  {"role": "user", "content": f"""
91
- Generate a **professional cover letter** using:
92
- - Candidate Profile: {candidate_json}
93
- - Job Description: {job_description}
94
- - Date: {date_str}
95
-
96
- - Ensure **persuasion**, **tailored content**, and **measurable achievements**.
97
- - Format: **Introduction, Key Skills, Experience Alignment, Closing**.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
98
  """}
99
  ]
100
- return call_groq_api(messages, temperature=0.6)
101
 
102
- # ------------------------------------------------------------------------------
103
- # 🎨 STREAMLIT UI DESIGN
104
- # ------------------------------------------------------------------------------
105
- st.set_page_config(page_title="AI Resume & Cover Letter Generator", layout="wide")
106
- st.title("πŸš€ AI-Powered Resume & Cover Letter Generator")
107
- st.markdown("### Create a **perfect ATS-friendly Resume** and **tailored Cover Letter**")
108
-
109
- tabs = st.tabs(["πŸ“„ Cover Letter Generator", "πŸ“‘ Resume Creator"])
110
-
111
- # ----- COVER LETTER GENERATOR -----
112
- with tabs[0]:
113
- st.header("πŸ“„ Cover Letter Generator")
114
-
115
- resume_file = st.file_uploader("Upload Your Resume", type=["pdf", "docx", "txt"])
116
- job_description = st.text_area("Paste Job Description", height=200)
117
-
118
- if st.button("πŸ”Ή Generate Cover Letter"):
119
- if resume_file and job_description.strip():
120
- with st.spinner("✨ Processing resume..."):
121
- resume_text = extract_resume_text(resume_file)
122
- candidate_json = generate_resume("First", "Last", "Houston, TX", "Experience", "Education", "Skills")
123
- cover_letter = generate_cover_letter(candidate_json, job_description)
124
- st.success("βœ… Cover Letter Generated!")
125
- st.text_area("πŸ“œ Your Cover Letter:", cover_letter, height=300)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
126
  else:
127
- st.warning("⚠️ Please upload a resume and paste the job description.")
128
-
129
- # ----- RESUME CREATOR -----
130
- with tabs[1]:
131
- st.header("πŸ“‘ Resume Creator")
132
-
133
- with st.form("resume_form"):
134
- col1, col2 = st.columns(2)
135
- first_name = col1.text_input("First Name")
136
- last_name = col2.text_input("Last Name")
137
- location = st.text_input("Location")
138
- work_experience = st.text_area("Work Experience", height=150)
139
- school_experience = st.text_area("Education", height=150)
140
- skills = st.text_area("Skills", height=100)
141
- submit = st.form_submit_button("πŸ“‘ Generate Resume")
142
-
143
- if submit:
144
- with st.spinner("πŸ“ Creating Resume..."):
145
- resume_text = generate_resume(first_name, last_name, location, work_experience, school_experience, skills)
146
- st.success("βœ… Resume Generated!")
147
- st.text_area("πŸ“œ Your Resume:", resume_text, height=400)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import json
2
+ import os
3
  import requests
4
  import streamlit as st
5
  from datetime import datetime
6
  from docx import Document
7
+ from io import BytesIO
8
+ from PyPDF2 import PdfReader, PdfWriter
9
+
10
+ # Consider using a more robust environment variable loading mechanism for production
11
+ from dotenv import load_dotenv
12
+ load_dotenv()
13
 
14
  # ------------------------------------------------------------------------------
15
+ # 🌟 ULTIMATE AI-POWERED IMPORT OS - "ResumeForge Pro" 🌟
16
+ # Unleash the Power of AI for Career Advancement!
17
  # ------------------------------------------------------------------------------
18
+ # This is not just an import statement, it's a portal to career empowerment.
19
+ # It combines cutting-edge AI, meticulous resume craftsmanship, and an intuitive UI.
20
+
21
+ # ==============================================================================
22
+ # πŸš€ CONFIGURATION - API KEYS, ENDPOINTS, & MODEL SELECTION πŸš€
23
+ # ==============================================================================
24
+ # Ensure your GROQ_API key is securely stored. For development, use environment variables.
25
+ # For production, consider secrets management solutions.
26
+ GROQ_API_KEY = os.getenv("GROQ_API_KEY") # Added correct env variable name
27
+
28
+ # Define the API endpoint for the Groq service. This provides flexibility
29
+ # to switch to different Groq deployments if needed.
30
  GROQ_ENDPOINT = "https://api.groq.com/openai/v1/chat/completions"
31
+
32
+ # Choose the most appropriate Llama 3.3 model variant for your needs.
33
+ # "llama-3.3-70b-versatile" offers a balance of capabilities, but other
34
+ # variants might be better suited for specific tasks or resource constraints.
35
  GROQ_MODEL = "llama-3.3-70b-versatile"
36
 
37
+ # ==============================================================================
38
+ # πŸ”₯ API HANDLER FOR GROQ AI - ROBUST AND RESILIENT πŸ›‘οΈ
39
+ # ==============================================================================
40
+ def call_groq_api(messages, temperature=0.7, max_tokens=1024, top_p=0.95, frequency_penalty=0.0, presence_penalty=0.0):
41
+ """
42
+ Handles API calls to Groq's Llama 3.3 model with comprehensive error handling,
43
+ parameter tuning, and advanced logging.
44
+
45
+ Args:
46
+ messages (list): A list of message dictionaries, representing the conversation history.
47
+ Each dictionary should have 'role' (e.g., 'system', 'user', 'assistant')
48
+ and 'content' keys.
49
+ temperature (float): Controls the randomness of the output (0.0 - deterministic, 1.0 - very random).
50
+ Lower values are suitable for tasks requiring precision (e.g., resume formatting),
51
+ while higher values can be used for creative tasks (e.g., cover letter generation).
52
+ Defaults to 0.7.
53
+ max_tokens (int): The maximum number of tokens to generate in the response. Adjust this
54
+ based on the expected length of the output to balance completeness and cost.
55
+ Defaults to 1024.
56
+ top_p (float): Nucleus sampling parameter. Similar to temperature but controls the cumulative
57
+ probability of the tokens to sample from. A higher value allows for more
58
+ diverse outputs. Defaults to 0.95.
59
+ frequency_penalty (float): Penalizes new tokens based on their existing frequency in the text so far.
60
+ Encourages the model to use novel words and phrases. Defaults to 0.0.
61
+ presence_penalty (float): Penalizes new tokens that already appear in the text so far, regardless of frequency.
62
+ Discourages the model from repeating the same topics or ideas. Defaults to 0.0.
63
+
64
+ Returns:
65
+ str: The generated text content from the API response, or None if the API call fails.
66
+ """
67
+
68
  headers = {
69
  "Authorization": f"Bearer {GROQ_API_KEY}",
70
  "Content-Type": "application/json"
 
73
  "model": GROQ_MODEL,
74
  "messages": messages,
75
  "temperature": temperature,
76
+ "max_tokens": max_tokens,
77
+ "top_p": top_p,
78
+ "frequency_penalty": frequency_penalty,
79
+ "presence_penalty": presence_penalty
80
  }
81
+
82
  try:
83
+ st.sidebar.write(f"**API Request Payload:**\n\n{json.dumps(payload, indent=2)}") # Log the API payload
84
+ response = requests.post(GROQ_ENDPOINT, headers=headers, json=payload, timeout=25) # Increased timeout for potentially long requests
85
+ response.raise_for_status() # Raise HTTPError for bad responses (4xx or 5xx)
86
+ response_json = response.json()
87
+ st.sidebar.write(f"**API Response:**\n\n{json.dumps(response_json, indent=2)}") # Log the entire response
88
+ return response_json["choices"][0]["message"]["content"].strip()
89
  except requests.exceptions.RequestException as e:
90
+ st.error(f"❌ API Request Failed: {e} - Please ensure your API key is valid and Groq's service is operational.")
91
+ st.error(f"Detailed Error: {e}") # Provide a detailed error message
92
+ return None
93
+ except KeyError as e:
94
+ st.error(f"❌ Error parsing API response: Missing key {e}. Check the API response format.")
95
+ return None
96
+ except json.JSONDecodeError as e:
97
+ st.error(f"❌ Error decoding JSON response: {e}. The API might be returning invalid JSON.")
98
+ return None
99
+ except Exception as e:
100
+ st.error(f"❌ An unexpected error occurred: {e}")
101
  return None
102
+ finally:
103
+ st.sidebar.write("--- API Request Complete ---") # Add a clear marker for request completion
104
 
105
+ # ==============================================================================
106
+ # πŸ“„ FILE PROCESSING - PDF, DOCX, TXT - WITH EXTENSIVE VALIDATION πŸ›‘οΈ
107
+ # ==============================================================================
108
  def extract_resume_text(file_obj):
109
+ """
110
+ Extracts text from uploaded resumes (PDF, DOCX, TXT) with robust error handling,
111
+ file type validation, and size limitations.
 
 
 
 
 
 
 
 
112
 
113
+ Args:
114
+ file_obj (streamlit.UploadedFile): The uploaded file object.
115
+
116
+ Returns:
117
+ str: The extracted text from the resume, or None if extraction fails.
118
+ """
119
+ MAX_FILE_SIZE_MB = 5 # Set a reasonable file size limit
120
+ try:
121
+ file_bytes = file_obj.read()
122
+ file_size_mb = len(file_bytes) / (1024 * 1024)
123
+ if file_size_mb > MAX_FILE_SIZE_MB:
124
+ st.error(f"❌ File size exceeds the limit of {MAX_FILE_SIZE_MB} MB. Please upload a smaller file.")
125
+ return None
126
+
127
+ file_obj.seek(0) # Reset file pointer to the beginning
128
+ ext = os.path.splitext(file_obj.name)[-1].lower()
129
+
130
+ if ext == ".pdf":
131
+ try:
132
+ reader = PdfReader(BytesIO(file_bytes)) # Use BytesIO to handle in-memory file
133
+ text = "\n".join(page.extract_text() for page in reader.pages if page.extract_text())
134
+ return text
135
+ except Exception as e:
136
+ st.error(f"❌ Error extracting text from PDF: {e}")
137
+ return None
138
+ elif ext in [".docx", ".doc"]:
139
+ try:
140
+ document = Document(BytesIO(file_bytes))
141
+ text = "\n".join([para.text for para in document.paragraphs])
142
+ return text
143
+ except Exception as e:
144
+ st.error(f"❌ Error extracting text from DOCX: {e}")
145
+ return None
146
+ elif ext == ".txt":
147
+ try:
148
+ return file_bytes.decode("utf-8", errors="ignore") # Handle encoding issues
149
+ except Exception as e:
150
+ st.error(f"❌ Error decoding text file: {e}")
151
+ return None
152
+ else:
153
+ st.warning(f"⚠️ Unsupported file type: {ext}. Please upload a PDF, DOCX, or TXT file.")
154
+ return None
155
+
156
+ except Exception as e:
157
+ st.error(f"❌ An unexpected error occurred during file processing: {e}")
158
+ return None
159
+
160
+ # ==============================================================================
161
+ # 🎯 AI-POWERED RESUME GENERATION - ATS OPTIMIZED & STRUCTURED πŸš€
162
+ # ==============================================================================
163
+ def generate_resume(first_name, last_name, location, work_experience, school_experience, skills, contact_number, email_address, linkedin_profile):
164
+ """
165
+ Generates a structured, ATS-friendly resume with enhanced formatting,
166
+ detailed instructions to the AI, and incorporates contact information.
167
+
168
+ Args:
169
+ first_name (str): The candidate's first name.
170
+ last_name (str): The candidate's last name.
171
+ location (str): The candidate's location.
172
+ work_experience (str): A detailed description of work experience.
173
+ school_experience (str): Details of educational background.
174
+ skills (str): A comma-separated list of skills.
175
+ contact_number (str): Candidate's phone number.
176
+ email_address (str): Candidate's email address.
177
+ linkedin_profile (str): Candidate's LinkedIn profile URL.
178
+
179
+ Returns:
180
+ str: The generated resume text.
181
+ """
182
  candidate_data = json.dumps({
183
  "first_name": first_name,
184
  "last_name": last_name,
185
  "location": location,
186
  "work_experience": work_experience,
187
  "school_experience": school_experience,
188
+ "skills": skills,
189
+ "contact_number": contact_number,
190
+ "email_address": email_address,
191
+ "linkedin_profile": linkedin_profile
192
  }, indent=2)
193
 
194
  messages = [
195
+ {"role": "system", "content": """
196
+ You are an expert resume writer with extensive experience in Applicant Tracking Systems (ATS) optimization and modern resume design principles.
197
+ Your goal is to create a compelling and highly effective resume that showcases the candidate's strengths and experiences in a clear, concise, and ATS-friendly format.
198
+
199
+ You are an expert in using bullet points to quantify experience with metrics and achievements.
200
+ """},
201
  {"role": "user", "content": f"""
202
+ Generate a **highly professional**, **ATS-optimized resume** using the following structured information. Focus on:
203
+ * **Quantifiable Achievements:** Emphasize results and use numbers/metrics wherever possible.
204
+ * **Keywords:** Incorporate industry-specific keywords naturally.
205
+ * **Action Verbs:** Start each bullet point with strong action verbs.
206
+ * **Conciseness:** Keep sentences short and to the point.
207
+
208
  {candidate_data}
209
+
210
+ **Resume Structure Requirements:**
211
+
212
+ 1. **Contact Information:**
213
+ * Full Name (prominently displayed)
214
+ * Phone Number
215
+ * Email Address
216
+ * LinkedIn Profile URL (if provided, ensure it's valid)
217
+ * Location (City, State - no full address)
218
+
219
+ 2. **Summary/Profile:**
220
+ * A brief (3-4 sentence) summary highlighting key skills, experience, and career goals. Tailor this to be generic enough to apply to several positions.
221
+
222
+ 3. **Work Experience:**
223
+ * Job Title
224
+ * Company Name
225
+ * Dates of Employment (Month Year - Month Year)
226
+ * **Responsibilities and Achievements:** Use bullet points to describe responsibilities and, most importantly, **quantifiable achievements**.
227
+ * Example: "Increased sales by 25% in Q3 by implementing a new marketing strategy."
228
+ * Example: "Reduced customer support tickets by 15% by improving the onboarding process."
229
+ * Order experiences from most recent to oldest.
230
+
231
+ 4. **Education:**
232
+ * Degree Name
233
+ * Major (if applicable)
234
+ * University Name
235
+ * Graduation Date (Month Year) or Expected Graduation Date
236
+ * Include relevant coursework or academic achievements (optional).
237
+
238
+ 5. **Skills:**
239
+ * List both technical and soft skills relevant to the candidate's field.
240
+ * Categorize skills (e.g., "Technical Skills," "Soft Skills," "Programming Languages").
241
+
242
+ 6. **Certifications/Awards (Optional):**
243
+ * List any relevant certifications or awards.
244
+
245
+ **Formatting Guidelines:**
246
+
247
+ * Use a clean and professional font (e.g., Arial, Calibri, Times New Roman) – Size 10-12.
248
+ * Use clear headings and subheadings.
249
+ * Use white space effectively to improve readability.
250
+ * Maintain consistent formatting throughout the document.
251
+ * Adhere to a one-page or two-page limit, depending on the candidate's experience level.
252
+ * Make sure to include enough information so the bot can create a great resume.
253
+
254
+ **IMPORTANT:** The resume MUST be ATS-compatible. Avoid using tables, columns, or graphics that can confuse ATS systems.
255
+
256
+ **Tone:** Professional, confident, and results-oriented.
257
  """}
258
  ]
259
+ return call_groq_api(messages, temperature=0.4, max_tokens=2048) # Lower temperature for more precision
260
 
261
+ # ==============================================================================
262
+ # ✍️ AI-POWERED COVER LETTER GENERATION - PERSUASIVE & TAILORED πŸš€
263
+ # ==============================================================================
264
+ def generate_cover_letter(candidate_json, job_description, hiring_manager_name="Hiring Manager"):
265
+ """
266
+ Generates a compelling cover letter using structured candidate details and
267
+ a provided job description.
268
+
269
+ Args:
270
+ candidate_json (str): A JSON string containing candidate information (name, skills, experience).
271
+ job_description (str): The text of the job description.
272
+ hiring_manager_name (str, optional): The name of the hiring manager. Defaults to "Hiring Manager".
273
+
274
+ Returns:
275
+ str: The generated cover letter text.
276
+ """
277
+ date_str = datetime.today().strftime("%d %B %Y")
278
  messages = [
279
+ {"role": "system", "content": """
280
+ You are a world-class career advisor, adept at crafting highly persuasive and tailored cover letters.
281
+ Your cover letters are known for their ability to capture the attention of hiring managers and showcase the candidate's
282
+ unique qualifications and enthusiasm for the role. You are familiar with best practices in cover letter writing,
283
+ including the importance of tailoring the content to the specific job description and highlighting measurable achievements.
284
+ """},
285
  {"role": "user", "content": f"""
286
+ Generate a **compelling and professional cover letter** using the following information:
287
+
288
+ **Candidate Profile:** {candidate_json}
289
+
290
+ **Job Description:** {job_description}
291
+
292
+ **Date:** {date_str}
293
+
294
+ **Hiring Manager Name (Salutation):** {hiring_manager_name}
295
+
296
+ **Cover Letter Requirements:**
297
+
298
+ 1. **Opening Paragraph (Introduction):**
299
+ * Start with a strong opening that grabs the reader's attention.
300
+ * Clearly state the position you are applying for and where you saw the job posting.
301
+ * Express your enthusiasm for the opportunity.
302
+
303
+ 2. **Body Paragraphs (Skills and Experience Alignment):**
304
+ * Highlight 2-3 key skills or experiences that directly align with the requirements outlined in the job description.
305
+ * Provide specific examples of how you have demonstrated these skills and achieved measurable results in previous roles.
306
+ * Quantify your achievements whenever possible (e.g., "Increased sales by 20%," "Reduced costs by 15%").
307
+ * Showcase your understanding of the company and the role.
308
+
309
+ 3. **Closing Paragraph (Call to Action):**
310
+ * Reiterate your interest in the position and your enthusiasm for the opportunity.
311
+ * Express your confidence in your ability to contribute to the company's success.
312
+ * Thank the hiring manager for their time and consideration.
313
+ * Include a call to action, such as requesting an interview.
314
+
315
+ **Formatting Guidelines:**
316
+
317
+ * Use a professional and easy-to-read font (e.g., Arial, Calibri, Times New Roman).
318
+ * Keep the cover letter concise and focused (ideally, no more than one page).
319
+ * Use proper grammar and spelling.
320
+ * Address the cover letter to the hiring manager by name, if possible.
321
+ * If the hiring manager's name is not available, use "Dear Hiring Manager."
322
+
323
+ **Tone:** Enthusiastic, confident, professional, and tailored.
324
  """}
325
  ]
326
+ return call_groq_api(messages, temperature=0.5, max_tokens=1536) # Adjusted temperature for persuasion
327
 
328
+ # ==============================================================================
329
+ # πŸ’Ύ SAVE FUNCTION
330
+ # ==============================================================================
331
+
332
+ def save_text_as_file(text, filename, file_format):
333
+ """Saves the generated text as a downloadable file in specified format."""
334
+ try:
335
+ if file_format == "txt":
336
+ # UTF-8 encoding to support special characters.
337
+ data = text.encode('utf-8')
338
+ st.download_button(
339
+ label=f"Download {filename}.txt",
340
+ data=data,
341
+ file_name=f"{filename}.txt",
342
+ mime="text/plain",
343
+ help="Click to download the generated text as a plain text file.",
344
+ )
345
+ elif file_format == "docx":
346
+ document = Document()
347
+ document.add_paragraph(text)
348
+ # Write docx to a BytesIO object for downloading.
349
+ buffer = BytesIO()
350
+ document.save(buffer)
351
+ buffer.seek(0) # Go to the beginning of the buffer so it can be read.
352
+ st.download_button(
353
+ label=f"Download {filename}.docx",
354
+ data=buffer,
355
+ file_name=f"{filename}.docx",
356
+ mime="application/vnd.openxmlformats-officedocument.wordprocessingml.document",
357
+ help="Click to download the generated text as a Word document (.docx).",
358
+ )
359
+ elif file_format == "pdf":
360
+ # Creating PDF from the given text.
361
+ from reportlab.lib.pagesizes import letter
362
+ from reportlab.pdfgen import canvas
363
+ # Buffer to hold the PDF in memory
364
+ buffer = BytesIO()
365
+ # Create the PDF object, using the buffer as its "file."
366
+ c = canvas.Canvas(buffer, pagesize=letter)
367
+ # Set font and size
368
+ c.setFont('Helvetica', 12)
369
+ textobject = c.beginText()
370
+ textobject.setTextOrigin(50, 750)
371
+ # Split the text into lines
372
+ lines = text.splitlines()
373
+ # Setting line height
374
+ line_height = 14
375
+ # Write each line of text
376
+ for line in lines:
377
+ textobject.textLine(line.strip())
378
+ textobject.moveCursor(0, -line_height) # Adjust line height to move down a bit
379
+
380
+ # Finish up
381
+ c.drawText(textobject)
382
+ c.showPage()
383
+ c.save()
384
+
385
+ # Move the buffer position to the beginning
386
+ buffer.seek(0)
387
+ st.download_button(
388
+ label=f"Download {filename}.pdf",
389
+ data=buffer,
390
+ file_name=f"{filename}.pdf",
391
+ mime="application/pdf",
392
+ help="Click to download the generated text as a PDF document.",
393
+ )
394
  else:
395
+ st.error("Unsupported file format. Please choose txt, docx, or pdf.")
396
+
397
+ except Exception as e:
398
+ st.error(f"Error generating download: {e}")
399
+
400
+
401
+ # ==============================================================================
402
+ # 🎨 STREAMLIT UI DESIGN - USER-FRIENDLY & RESPONSIVE πŸ“±
403
+ # ==============================================================================
404
+ def main():
405
+ st.set_page_config(page_title="AI Resume & Cover Letter Generator", layout="wide")
406
+ st.title("πŸš€ AI-Powered Resume & Cover Letter Generator")
407
+ st.markdown("### Create a **perfect ATS-friendly Resume** and **tailored Cover Letter** using **Groq AI (Llama 3.3-70B)**")
408
+
409
+ # Sidebar for API key and advanced settings
410
+ with st.sidebar:
411
+ st.header("βš™οΈ Advanced Settings")
412
+ api_key_input = st.text_input("Groq API Key", type="password", value=GROQ_API_KEY if GROQ_API_KEY else "", help="Enter your Groq API key. Keep it safe!", key="api_key_input")
413
+
414
+ if api_key_input:
415
+ os.environ["GROQ_API_KEY"] = api_key_input # Correct way to set/update env var
416
+
417
+ model_temperature = st.slider("Model Temperature", min_value=0.0, max_value=1.0, value=0.5, step=0.05, help="Adjust the randomness of the AI's output. Lower values for more precise output.")
418
+ model_max_tokens = st.slider("Max Tokens", min_value=256, max_value=4096, value=1024, step=128, help="The maximum number of tokens in the generated output.")
419
+ top_p = st.slider("Top P", min_value=0.0, max_value=1.0, value=0.95, step=0.05, help="Nucleus sampling parameter. Controls the cumulative probability of the tokens to sample from.")
420
+ frequency_penalty = st.slider("Frequency Penalty", min_value=-2.0, max_value=2.0, value=0.0, step=0.1, help="Penalizes new tokens based on their existing frequency.")
421
+ presence_penalty = st.slider("Presence Penalty", min_value=-2.0, max_value=2.0, value=0.0, step=0.1, help="Penalizes new tokens that already appear in the text.")
422
+ st.markdown("---")
423
+ st.markdown("πŸ’‘ **Tip:** Adjust these settings to fine-tune the AI's performance. Lower temperature for resumes, higher for cover letters.")
424
+
425
+ tabs = st.tabs(["πŸ“„ Cover Letter Generator", "πŸ“‘ Resume Creator"])
426
+
427
+ # ----- COVER LETTER GENERATOR -----
428
+ with tabs[0]:
429
+ st.header("πŸ“„ Cover Letter Generator")
430
+
431
+ resume_file = st.file_uploader("Upload Your Resume", type=["pdf", "docx", "txt"], help="Upload your resume in PDF, DOCX, or TXT format.")
432
+ job_description = st.text_area("Paste Job Description", height=200, help="Paste the job description from the job posting.")
433
+ hiring_manager_name = st.text_input("Hiring Manager Name (Optional)", value="Hiring Manager", help="Enter the name of the hiring manager if known.")
434
+
435
+ if st.button("πŸ”Ή Generate Cover Letter"):
436
+ if not os.getenv("GROQ_API_KEY"):
437
+ st.error("Please enter your Groq API key in the sidebar.")
438
+ return
439
+ if resume_file and job_description.strip():
440
+ with st.spinner("✨ Processing resume..."):
441
+ resume_text = extract_resume_text(resume_file)
442
+ if resume_text:
443
+ # Dummy data, consider extracting dynamically from resume.
444
+ candidate_json = json.dumps({
445
+ "first_name": "Jane",
446
+ "last_name": "Doe",
447
+ "location": "San Francisco, CA",
448
+ "work_experience": "Experienced Software Engineer with a proven track record of success.",
449
+ "school_experience": "Master's Degree in Computer Science from Stanford University",
450
+ "skills": "Python, Java, Machine Learning, Cloud Computing"
451
+ })
452
+ cover_letter = generate_cover_letter(candidate_json, job_description, hiring_manager_name)
453
+ if cover_letter:
454
+ st.success("βœ… Cover Letter Generated!")
455
+ st.text_area("πŸ“œ Your Cover Letter:", cover_letter, height=300)
456
+ filename = "cover_letter"
457
+ file_format = st.selectbox("Select download format:", ["txt", "docx", "pdf"])
458
+ save_text_as_file(cover_letter, filename, file_format)
459
+
460
+ else:
461
+ st.warning("⚠️ Please upload a resume and paste the job description.")
462
+
463
+ # ----- RESUME CREATOR -----
464
+ with tabs[1]:
465
+ st.header("πŸ“‘ Resume Creator")
466
+
467
+ with st.form("resume_form"):
468
+ col1, col2 = st.columns(2)
469
+ first_name = col1.text_input("First Name", help="Enter your first name.")
470
+ last_name = col2.text_input("Last Name", help="Enter your last name.")
471
+ location = st.text_input("Location", help="Enter your city and state.")
472
+ contact_number = st.text_input("Contact Number", help="Enter your phone number.")
473
+ email_address = st.text_input("Email Address", help="Enter your email address.")
474
+ linkedin_profile = st.text_input("LinkedIn Profile URL", help="Enter your LinkedIn profile URL (optional).")
475
+ work_experience = st.text_area("Work Experience", height=150, help="Describe your work experience, including job titles, company names, dates of employment, and responsibilities/achievements.")
476
+ school_experience = st.text_area("Education", height=150, help="Describe your educational background, including degree names, university names, and graduation dates.")
477
+ skills = st.text_area("Skills", height=100, help="List your skills, separated by commas.")
478
+ submit = st.form_submit_button("πŸ“‘ Generate Resume")
479
+
480
+ if submit:
481
+ if not os.getenv("GROQ_API_KEY"):
482
+ st.error("Please enter your Groq API key in the sidebar.")
483
+ return
484
+ with st.spinner("πŸ“ Creating Resume..."):
485
+ resume_text = generate_resume(first_name, last_name, location, work_experience, school_experience, skills, contact_number, email_address, linkedin_profile)
486
+ if resume_text:
487
+ st.success("βœ… Resume Generated!")
488
+ st.text_area("πŸ“œ Your Resume:", resume_text, height=400)
489
+
490
+ filename = f"{first_name}_{last_name}_resume"
491
+ file_format = st.selectbox("Select download format:", ["txt", "docx", "pdf"])
492
+ save_text_as_file(resume_text, filename, file_format)
493
+
494
+ if __name__ == "__main__":
495
+ main()