AdvanceCovRes / app.py
mgbam's picture
Update app.py
be3930a verified
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()