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