CoolFace
Apppublic

quartzap1/ihire

sourceHugging Faceapache-2.0updated 2y agoView on Hugging Face
0likes
app.py303 linesDownload Raw Back to root
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()