import streamlit as st import PyPDF2 import io import docx2txt from typing import Optional import re from pinecone_handler import PineconeHandler from time_handling import read_timestamp from datetime import datetime import os from settings import DATE_FORMAT def extract_text_from_pdf(pdf_file) -> str: """Extract text content from PDF file""" pdf_reader = PyPDF2.PdfReader(pdf_file) text = "" for page in pdf_reader.pages: text += page.extract_text() + "\n" return text def extract_text_from_docx(docx_file) -> str: """Extract text content from DOCX file""" text = docx2txt.process(docx_file) return text def extract_resume_text(uploaded_file) -> Optional[str]: """Extract text from uploaded resume file""" if uploaded_file is None: return None # Get the file extension file_extension = uploaded_file.name.split('.')[-1].lower() try: # Process based on file type if file_extension == 'pdf': return extract_text_from_pdf(uploaded_file) elif file_extension in ['docx', 'doc']: return extract_text_from_docx(uploaded_file) elif file_extension == 'txt': return str(uploaded_file.read(), "utf-8") else: st.error(f"Unsupported file format: {file_extension}") return None except Exception as e: st.error(f"Error processing file: {str(e)}") return None def clean_resume_text(text: str) -> str: """Clean and process resume text""" if not text: return "" # Remove special characters and extra whitespace text = re.sub(r'\s+', ' ', text) text = text.strip() return text def is_description_truncated(description: str) -> bool: """Check if the description appears to be truncated""" # Check for obvious truncation indicators truncation_indicators = [ lambda x: len(x) >= 995, # Close to the 1000 char limit lambda x: x.rstrip().endswith(('...', 'ā¦')), lambda x: re.search(r'\w+$', x) and not re.search(r'[.!?]$', x), # Ends mid-word or without punctuation ] return any(indicator(description) for indicator in truncation_indicators) def format_job_description(description: str, truncated: bool = False) -> str: """Format job description text with proper sections and line breaks""" if not description: return "" # Common section headers in job descriptions sections = [ "About us", "About you", "About the role", "About the position", "Requirements", "Qualifications", "Skills", "Responsibilities", "What you'll do", "What we offer", "Benefits", "Your profile", "Required skills", "What you need", "Who you are" ] # Add line breaks before section headers formatted_text = description for section in sections: # Look for section headers with case-insensitive matching pattern = re.compile(f'({section}:?)', re.IGNORECASE) formatted_text = pattern.sub(r'\n\n\1', formatted_text) # Handle bullet points (both ā¢ and - symbols) formatted_text = re.sub(r'[ā¢-]\s*', '\nā¢ ', formatted_text) # Add line breaks for sentences that look like list items formatted_text = re.sub(r'(?<=\w)\.(?=\s*[A-Z])', '.\n', formatted_text) # Clean up any excessive line breaks formatted_text = re.sub(r'\n{3,}', '\n\n', formatted_text) if truncated: formatted_text = formatted_text.rstrip() + "..." return formatted_text.strip() def main(): # Add custom CSS st.markdown(""" """, unsafe_allow_html=True) # Main content st.markdown('
AI-Powered Job Search
', unsafe_allow_html=True) st.markdown(""" š¼ **Transform Your Job Search with AI** Tired of searching for jobs? Let AI do the work for you! Upload your resume and discover perfectly matched opportunities in minutes - not hours! """) # Sidebar content with st.sidebar: st.markdown("### How It Works") st.markdown("1. **Upload Resume**\n PDF, DOCX, DOC, or TXT formats") st.markdown("2. **Extract Content**\n AI processes your resume") st.markdown("3. **Smart Search**\n Match against our database") st.markdown("4. **Get Matches**\n View personalized recommendations") st.markdown("---") # Divider # Database update info try: last_update = read_timestamp() last_update_dt = datetime.strptime(last_update, DATE_FORMAT) st.markdown("### Database Status") st.markdown("š Updates once a day at midnight.") st.markdown(f"**Last update:** {last_update_dt.strftime('%B %d, %Y at %I:%M %p')} (Stockholm Time)") except Exception as e: st.error(f"Error reading timestamp: {str(e)}") # Initialize PineconeHandler try: handler = PineconeHandler() except Exception as e: st.error(f"Error connecting to Pinecone: {str(e)}") return # File uploader uploaded_file = st.file_uploader("Upload your resume", type=['pdf', 'docx', 'doc', 'txt']) # Search parameters num_results = st.slider("Number of results", min_value=1, max_value=20, value=5) if uploaded_file: with st.spinner("Processing resume..."): # Extract and clean resume text resume_text = extract_resume_text(uploaded_file) if resume_text: clean_text = clean_resume_text(resume_text) # Preview extracted text with st.expander("Preview extracted text"): st.text(clean_text[:500] + "..." if len(clean_text) > 500 else clean_text) # Add a city filter textbox above the search button city_filter = st.text_input("Filter by city (optional)", value="", help="Enter a city to filter job results by location") # Search button if st.button("Search Jobs"): with st.spinner("Searching for matching jobs..."): try: # Search for similar job ads results = handler.search_similar_ads(clean_text, top_k=num_results, city=city_filter.strip()) if results: st.subheader("Matching Jobs") for i, match in enumerate(results, 1): metadata = match.metadata score = match.score # Create job card with st.container(): # Header section with key information col1, col2 = st.columns([2, 1]) with col1: st.markdown(f"### {metadata['headline']}") with col2: st.markdown(f"**Match Score (Cosine):** {score:.2f}") # Job details section if metadata.get('logo_url'): st.image(metadata['logo_url'], width=100) st.markdown(f"**Location:** {metadata['city']}") st.markdown(f"**Occupation:** {metadata['occupation']}") st.markdown(f"**Published:** {metadata['published']}") # Check if description is truncated description = metadata['description'] is_truncated = is_description_truncated(description) # Display initial description preview formatted_description = format_job_description( description[:2000] if is_truncated else description, truncated=is_truncated ) st.markdown(formatted_description) # If truncated, show expandable full description if is_truncated: with st.expander("Read Full Description"): # Try to fetch full description from webpage_url st.markdown(""" **Note:** The full description has been truncated in our database. Please visit the original job posting for complete details and for searching the job. """) if metadata.get('webpage_url'): st.markdown(f"[View Original Job Posting]({metadata['webpage_url']})") st.markdown(f"š§ Contact: {metadata['email']}") st.markdown("---") else: st.info("No matching jobs found. Try adjusting your search criteria.") except Exception as e: st.error(f"Error searching jobs: {str(e)}") if __name__ == "__main__": main()