import os import json from typing import List from urllib.parse import quote import logging import gradio as gr import pandas as pd from server import cv_processor, job_processor, applicant_evaluator logger = logging.getLogger(__name__) logging.basicConfig(level=logging.INFO) logo_base64 = cv_processor.encode_base64( "static/AIRecruiterAgent.png" ) def evaluate_applicants( cv_files: List[str], job_description: str, #progress=gr.Progress(visible=True, label="Evaluating Applicants...") ) -> pd.DataFrame: """ Evaluate applicants' CVs against the job description. Parameters ---------- cv_files: List[str] List of CV file paths to evaluate. job_description: str The job description text to evaluate against. Returns ------- pd.DataFrame: DataFrame containing evaluation results with match scores and reasoning. """ # TODO: Add progress bar support with batch processing # TODO: Add error handling for file processing and evaluation # if not cv_files: # gr.Error("Please upload applicants CV files in PDF format.") # if not job_description: # gr.Error("Please provide the job description text for evaluation.") # Get job annotation logger.info("Getting job annotation from job description.") job_annotation = job_processor.get_job_content(job_description) evaluation_res = [] for cv_file in cv_files: # Get CV annotation logger.info("Getting cv annotation from CV file: %s", cv_file.name) cv_annotation = cv_processor.get_cv_content(cv_file.name) # Evaluate the applicant against the job description logger.info("Evaluating applicant CV against job description.") res = applicant_evaluator.evaluate_applicant( cv_annotation["cv"]["annotation"], job_annotation["job"]["annotation"] ) evaluation = json.loads(res["evaluation"]) cv_base64 = cv_processor.encode_base64(cv_file.name) score = float(evaluation["match_score"]) if score >= 0.8: match_labels = "Strong Matched" elif score >= 0.5: match_labels = "Partially Matched" else: match_labels = "Not Matched" evaluation_res.append( { "Applicant": os.path.basename(cv_file.name), "Match Score": evaluation["match_score"], "Match Labels": match_labels, "Match Reasoning": evaluation["match_reasoning"], "CV Base64": cv_base64, "CV Url": f"gradio_api/file={cv_file.name}" } ) #logger.info(f"Evaluation results: {response}") evaluation_res = sorted( evaluation_res, key=lambda d: d['Match Score'], reverse=True ) return pd.DataFrame.from_records(evaluation_res) def df_select_callback(df: pd.DataFrame, evt: gr.SelectData): selected_row = evt.row_value if not selected_row: return "No row selected.", "" match_score = selected_row[1] # .get('Match Score', 'N/A') match_labels = selected_row[2] # .get('Match Labels', 'N/A') match_reasoning = selected_row[3] # .get('Match Reasoning', 'N/A') cv_base64 = selected_row[4] # .get('CV Base64', '') pdf_url = selected_row[5] # .get('CV Url', '') pdf_encoded_url = f"https://agents-mcp-hackathon-airecruiteragent.hf.space/{pdf_url}" if cv_base64: pdf_encoded = gr.HTML( """
""".format( title=selected_row[0], cv_base64=cv_base64, pdf_url=pdf_url, pdf_encoded_url=pdf_encoded_url ), label="CV PDF Viewer", elem_id="pdf_viewer", min_height="0px", max_height="100%", visible=True, ) else: pdf_encoded = gr.HTML( "", label="CV PDF Viewer", elem_id="pdf_viewer", min_height="0px", max_height="100%", visible=False, ) return match_reasoning, pdf_encoded head = """ """ css = """ .logo-container { width:100%; height:auto; padding:1%; } .logo-img { margin-left:2%; float:left; height:40px; width:40px; } /* The Modal (background) */ .modal { display: none; position: fixed; z-index: 1000; /* Sit on top */ padding-top: 100px; left: 0; top: 0; width: 100%; height: 100%; overflow: auto; /* Enable scroll if needed */ background-color: rgb(0,0,0); /* Fallback color */ background-color: rgba(0,0,0,0.4); /* Black w/ opacity */ } /* Modal Content */ .modal-content { position: relative; background-color: #fefefe; margin: auto; padding: 0; border: 1px solid #888; width: 80%; height: 90%; overflow: auto; box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2),0 6px 20px 0 rgba(0,0,0,0.19); -webkit-animation-name: animatetop; -webkit-animation-duration: 0.4s; animation-name: animatetop; animation-duration: 0.4s } /* Add Animation */ @-webkit-keyframes animatetop { from {top:-300px; opacity:0} to {top:0; opacity:1} } @keyframes animatetop { from {top:-300px; opacity:0} to {top:0; opacity:1} } /* The Close Button */ .close { color: white; float: right; font-size: 28px; font-weight: bold; } .close:hover, .close:focus { color: #000; text-decoration: none; cursor: pointer; } .modal-header { padding: 2px 16px; background-color: #5cb85c; color: white; } .modal-body { padding: 2px 16px; height: 90%; } .modal-footer { padding: 2px 16px; background-color: #5cb85c; color: white; } """ # Create the Gradio interface with gr.Blocks(head=head, css=css) as demo_app: # Title section with gr.Row(): gr.HTML( """