quartzap1/ihire
0
1import streamlit as st2import PyPDF23import io4import numpy as np5from sentence_transformers import SentenceTransformer6from sklearn.metrics.pairwise import cosine_similarity7import google.generativeai as genai8import base649from datetime import datetime10from reportlab.lib.pagesizes import letter11from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer12from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle13from reportlab.lib.enums import TA_CENTER, TA_LEFT14from reportlab.pdfbase import pdfmetrics15from reportlab.pdfbase.ttfonts import TTFont16 17 18def extract_text_from_pdf(pdf_file):19 """Extract text content from uploaded PDF file"""20 pdf_reader = PyPDF2.PdfReader(pdf_file)21 text = ""22 for page in pdf_reader.pages:23 text += page.extract_text()24 return text25 26 27def get_embedding(text, model):28 """Generate embeddings for input text"""29 return model.encode(text)30 31 32def generate_questions_with_gemini(context, api_key, num_questions=5):33 """Generate interview questions using Google's Gemini API"""34 genai.configure(api_key=api_key)35 36 # Configure the model37 generation_config = {38 "temperature": 0.7,39 "top_p": 0.95,40 "top_k": 40,41 "max_output_tokens": 1024,42 }43 44 # Initialize the model45 model = genai.GenerativeModel(46 model_name="gemini-2.0-flash",47 generation_config=generation_config48 )49 50 prompt = f"""51 Generate {num_questions} specific and relevant interview questions based on both 52 the job description and the candidate's profile below. The questions should:53 1. Be tailored to assess the candidate's fit for this specific role54 2. Reference specific skills or experiences from the candidate's profile55 3. Probe for examples that demonstrate required competencies56 4. Include technical questions relevant to the position57 5. Avoid generic questions that could be asked to any candidate58 59 FORMAT: Return only numbered questions (1-{num_questions}), with no additional text.60 61 CONTEXT:62 {context}63 """64 65 try:66 response = model.generate_content(prompt)67 questions_text = response.text.strip()68 69 # Split by newlines and/or numbers to get individual questions70 import re71 questions = re.split(r'\n+|\d+\.', questions_text)72 questions = [q.strip() for q in questions if q.strip()]73 74 # Ensure we have exactly num_questions75 if len(questions) < num_questions:76 # Add generic questions if not enough were generated77 generic_questions = [78 "Can you tell me more about your experience with this technology?",79 "How would you handle challenges in this role?",80 "What interests you most about this position?",81 "How do your skills align with our requirements?",82 "Can you describe a relevant project you worked on?",83 "What has been your most significant professional achievement?",84 "How do you stay updated with industry trends?",85 "What's your approach to problem-solving?",86 "How do you handle tight deadlines?",87 "Where do you see yourself in five years?"88 ]89 questions.extend(generic_questions[:(num_questions - len(questions))])90 91 return questions[:num_questions]92 93 except Exception as e:94 st.error(f"Error generating questions: {str(e)}")95 return [f"Could not generate questions: {str(e)}"]96 97 98def create_download_link(pdf_bytes, filename):99 """Generate a download link for the PDF"""100 b64 = base64.b64encode(pdf_bytes).decode()101 return f'<a href="data:application/pdf;base64,{b64}" download="{filename}">Download PDF</a>'102 103 104def generate_pdf(candidate_name, job_desc_text, questions):105 """Generate a PDF with the questions using ReportLab for better Unicode support"""106 buffer = io.BytesIO()107 108 # Create the PDF document109 doc = SimpleDocTemplate(buffer, pagesize=letter)110 styles = getSampleStyleSheet()111 112 # Add custom styles - Check if style exists before adding113 if 'Title' not in styles:114 styles.add(ParagraphStyle(name='Title',115 fontName='Helvetica-Bold',116 fontSize=16,117 alignment=TA_CENTER,118 spaceAfter=12))119 120 if 'Subtitle' not in styles:121 styles.add(ParagraphStyle(name='Subtitle',122 fontName='Helvetica-Bold',123 fontSize=12,124 spaceAfter=6))125 126 if 'Normal' not in styles:127 styles.add(ParagraphStyle(name='Normal',128 fontName='Helvetica',129 fontSize=10,130 spaceAfter=10))131 132 elements = []133 134 # Add title135 title = Paragraph(f"Interview Questions for {candidate_name}", styles['Title'])136 elements.append(title)137 elements.append(Spacer(1, 12))138 139 # Add timestamp140 current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")141 timestamp = Paragraph(f"Generated on: {current_time}", styles['Normal'])142 elements.append(timestamp)143 elements.append(Spacer(1, 12))144 145 # Add job description summary146 elements.append(Paragraph("Job Description Summary:", styles['Subtitle']))147 job_summary = job_desc_text[:500] + "..." if len(job_desc_text) > 500 else job_desc_text148 elements.append(Paragraph(job_summary, styles['Normal']))149 elements.append(Spacer(1, 12))150 151 # Add questions152 elements.append(Paragraph("Interview Questions:", styles['Subtitle']))153 elements.append(Spacer(1, 6))154 155 for i, question in enumerate(questions, 1):156 q_text = f"{i}. {question}"157 elements.append(Paragraph(q_text, styles['Normal']))158 159 # Build the PDF160 doc.build(elements)161 162 # Get the value from the buffer163 pdf_bytes = buffer.getvalue()164 buffer.close()165 166 return pdf_bytes167 168 169def main():170 st.title("iHIRE")171 st.write("Upload a Job Description and Candidate CVs to Filter the Best Profiles and Generate Custom Interview Questions!")172 173 # Initialize session state for storing results174 if 'cv_similarities' not in st.session_state:175 st.session_state.cv_similarities = []176 if 'show_results' not in st.session_state:177 st.session_state.show_results = False178 if 'job_desc_text' not in st.session_state:179 st.session_state.job_desc_text = ""180 if 'questions_generated' not in st.session_state:181 st.session_state.questions_generated = {}182 if 'should_rerun' not in st.session_state:183 st.session_state.should_rerun = False184 185 # Check if we need to rerun from previous iteration186 if st.session_state.should_rerun:187 st.session_state.should_rerun = False188 st.rerun()189 190 # Sidebar configuration191 st.sidebar.header("Configuration")192 api_key = st.sidebar.text_input("Enter Gemini API Key", type="password")193 num_questions = st.sidebar.slider("Number of questions to generate", 3, 15, 5)194 195 if not api_key:196 st.sidebar.warning("Please enter your Gemini API key to enable question generation")197 198 # File upload section199 st.header("Upload Files")200 job_desc_file = st.file_uploader("Upload Job Description (PDF)", type="pdf")201 cv_files = st.file_uploader("Upload Candidate CVs (PDF)", type="pdf", accept_multiple_files=True)202 203 # Match button204 if job_desc_file is not None and cv_files:205 if st.button("Match"):206 # Load the sentence transformer model207 model = SentenceTransformer('paraphrase-MiniLM-L6-v2')208 209 # Process job description210 job_desc_text = extract_text_from_pdf(job_desc_file)211 st.session_state.job_desc_text = job_desc_text212 job_desc_embedding = get_embedding(job_desc_text, model)213 214 # Process CVs and calculate similarities215 cv_similarities = []216 217 for cv_file in cv_files:218 cv_text = extract_text_from_pdf(cv_file)219 cv_embedding = get_embedding(cv_text, model)220 221 # Calculate similarity222 similarity = cosine_similarity(223 [job_desc_embedding],224 [cv_embedding]225 )[0][0]226 227 cv_similarities.append({228 'filename': cv_file.name,229 'similarity': similarity,230 'text': cv_text231 })232 233 # Sort candidates by similarity234 cv_similarities.sort(key=lambda x: x['similarity'], reverse=True)235 236 # Keep only top 3 if there are more than 3237 if len(cv_similarities) > 3:238 cv_similarities = cv_similarities[:3]239 240 st.session_state.cv_similarities = cv_similarities241 st.session_state.show_results = True242 st.session_state.questions_generated = {} # Reset questions when new matching is done243 244 # Display results245 if st.session_state.show_results and st.session_state.cv_similarities:246 st.header("Top 3 Matching Results")247 248 # Create checkboxes for selecting candidates249 selected_candidates = {}250 for idx, cv in enumerate(st.session_state.cv_similarities):251 similarity_percentage = cv['similarity'] * 100252 253 st.subheader(f"Candidate {idx + 1}: {cv['filename']}")254 st.write(f"Match Score: {similarity_percentage:.2f}%")255 256 with st.expander("View CV Content"):257 st.write(cv['text'])258 259 # Add checkbox for this candidate260 selected_candidates[cv['filename']] = st.checkbox(f"Select {cv['filename']} for question generation",261 key=f"check_{cv['filename']}")262 263 # Show previously generated questions if they exist264 if cv['filename'] in st.session_state.questions_generated:265 st.subheader(f"Interview Questions for {cv['filename']}")266 for i, question in enumerate(st.session_state.questions_generated[cv['filename']], 1):267 st.write(f"{i}. {question}")268 269 # Add download button for PDF270 try:271 pdf_bytes = generate_pdf(cv['filename'], st.session_state.job_desc_text,272 st.session_state.questions_generated[cv['filename']])273 pdf_filename = f"questions_{cv['filename'].replace(' ', '_')}.pdf"274 275 st.markdown(create_download_link(pdf_bytes, pdf_filename), unsafe_allow_html=True)276 except Exception as e:277 st.error(f"Error generating PDF: {str(e)}")278 279 st.markdown("---")280 281 # Generate questions button - only show if API key is provided282 if api_key:283 if st.button("Generate Questions for Selected Candidates"):284 questions_were_generated = False285 286 for cv in st.session_state.cv_similarities:287 if selected_candidates.get(cv['filename'], False):288 with st.spinner(f"Generating {num_questions} questions for {cv['filename']}..."):289 context = f"Job Description:\n{st.session_state.job_desc_text}\n\nCandidate Profile:\n{cv['text']}"290 questions = generate_questions_with_gemini(context, api_key, num_questions)291 st.session_state.questions_generated[cv['filename']] = questions292 questions_were_generated = True293 294 # Set flag to rerun on next iteration if questions were generated295 if questions_were_generated:296 st.session_state.should_rerun = True297 st.rerun()298 else:299 st.warning("Please enter your Gemini API key in the sidebar to generate questions")300 301 302if __name__ == "__main__":303 main()