import os import sys import shutil import datetime import json import gradio as gr # Ensure `src` is in Python's module search path sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "src"))) from markdown_pdf import MarkdownPdf, Section from gradio_pdf import PDF from resume_crew.crew import ResumeCrew from crewai.knowledge.source.pdf_knowledge_source import PDFKnowledgeSource # Set backend directories for Hugging Face Spaces UPLOAD_DIR = "/tmp/uploads" OUTPUT_DIR = "/tmp/output" os.makedirs(UPLOAD_DIR, exist_ok=True) os.makedirs(OUTPUT_DIR, exist_ok=True) def convert_md_to_pdf(md_path: str) -> str: """ Convert a local .md file to .pdf using markdown-pdf. Returns the resulting PDF file path, or an empty string if conversion fails. """ if not os.path.isfile(md_path): return "" with open(md_path, "r", encoding="utf-8") as f: md_content = f.read() pdf_obj = MarkdownPdf(toc_level=2) pdf_obj.add_section(Section(md_content)) pdf_path = os.path.splitext(md_path)[0] + ".pdf" pdf_obj.save(pdf_path) return pdf_path if os.path.isfile(pdf_path) else "" def process_resume(openai_api_key, serper_api_key, model_choice, new_resume, company_name, job_url): """ Processes the uploaded resume using ResumeCrew and converts the output Markdown files to PDFs. Handles errors gracefully and stops execution upon failure. """ try: current_date = datetime.datetime.now().strftime("%Y%m%d") # --- Ensure a resume file is uploaded --- if new_resume is None or not (hasattr(new_resume, "name") and new_resume.name.strip() != ""): return ("Error: Please upload a resume.", None, None, None, None, None, None) # --- Set API keys --- os.environ["OPENAI_API_KEY"] = openai_api_key or "" os.environ["SERPER_API_KEY"] = serper_api_key or "" # --- Save uploaded file --- try: if hasattr(new_resume, "read"): original_filename = os.path.basename(new_resume.name) file_data = new_resume.read() else: original_filename = os.path.basename(new_resume) file_data = None base_filename, ext = os.path.splitext(original_filename) new_resume_filename = f"{base_filename}_{current_date}{ext}" physical_path = os.path.join("knowledge", new_resume_filename) os.makedirs("knowledge", exist_ok=True) if file_data is not None: with open(physical_path, "wb") as f: f.write(file_data) else: shutil.copy(new_resume, physical_path) except Exception as e: return (f"Error saving the uploaded resume: {str(e)}", None, None, None, None, None, None) # --- Initialize ResumeCrew --- try: crew_instance = ResumeCrew( model=model_choice, openai_api_key=openai_api_key, serper_api_key=serper_api_key, resume_pdf_path=new_resume_filename ) except Exception as e: return (f"Error initializing ResumeCrew: {str(e)}", None, None, None, None, None, None) # --- Run the resume processing --- try: crew_instance.crew().kickoff(inputs={'job_url': job_url, 'company_name': company_name}) except Exception as e: return (f"Error during resume processing: {str(e)}", None, None, None, None, None, None) # --- Retrieve output files --- try: job_analysis_path = os.path.join("output", "job_analysis.json") with open(job_analysis_path, "r") as f: job_data = json.load(f) position_name = job_data.get("job_title", "position") except Exception: position_name = "position" optimized_resume_path = os.path.join("output", "optimized_resume.md") candidate_name = "candidate" try: with open(optimized_resume_path, "r") as f: first_line = f.readline() if first_line.startswith("#"): candidate_name = first_line.lstrip("#").strip().replace(" ", "_") except Exception: candidate_name = "candidate" # --- Create the output folder --- try: folder_name = f"{company_name}_{position_name}_{candidate_name}_{current_date}" new_output_dir = os.path.join("output", folder_name) os.makedirs(new_output_dir, exist_ok=True) for filename in os.listdir("output"): file_path = os.path.join("output", filename) if file_path == new_output_dir: continue if filename.endswith(".json") or filename.endswith(".md"): if os.path.isfile(file_path): shutil.move(file_path, os.path.join(new_output_dir, filename)) except Exception as e: return (f"Error organizing output files: {str(e)}", None, None, None, None, None, None) # --- Convert Markdown to PDF --- def md_to_pdf_in_dir(md_filename): try: md_path = os.path.join(new_output_dir, md_filename) if os.path.isfile(md_path): return convert_md_to_pdf(md_path) return "" except Exception as e: return f"Error converting {md_filename} to PDF: {str(e)}" pdf_opt = md_to_pdf_in_dir("optimized_resume.md") pdf_final = md_to_pdf_in_dir("final_report.md") pdf_int = md_to_pdf_in_dir("interview_questions.md") message = f"Processing completed using model {model_choice}. Output saved in: {new_output_dir}" return (message, pdf_opt, pdf_opt, pdf_final, pdf_final, pdf_int, pdf_int) except Exception as e: return (f"Unexpected error: {str(e)}", None, None, None, None, None, None) # --- Define available models --- model_choices = { "GPT-4o-mini": "gpt-4o-mini-2024-07-18", "GPT-4o": "gpt-4o-2024-08-06", "o3-mini": "o3-mini-2025-01-31", "o1-mini": "o1-mini-2024-09-12" } with gr.Blocks(css=".output-column { width: 700px; }") as demo: with gr.Row(): # Left pane: Input fields with gr.Column(scale=1): gr.Markdown("## Resume Optimization System") gr.Markdown( "Create an optimized resume, job research report, and interview question sheet " "by simply uploading your resume, entering the company name, and providing the job posting URL. " "This tool leverages multi-agentic AI and web search to analyze job descriptions, research the company, and " "tailor your resume for better ATS compatibility and job relevance." ) openai_api_key_input = gr.Textbox(label="OpenAI API Key", type="password", placeholder="Enter OpenAI API Key") serper_api_key_input = gr.Textbox(label="Serper API Key", type="password", placeholder="Enter Serper API Key") model_dropdown = gr.Dropdown( choices=list(model_choices.values()), label="Select Model", value="gpt-4o-2024-08-06", interactive=True, info="Select the model to use for processing." ) new_resume_file = gr.File(label="Upload New Resume PDF", file_types=[".pdf"]) company_name_text = gr.Textbox(label="Company Name", placeholder="Enter company name") job_url_text = gr.Textbox(label="Job URL", placeholder="Enter job posting URL") run_button = gr.Button("Run") # Right pane: Output display with gr.Column(scale=2, elem_classes="output-column"): # Scale set to an integer to avoid warnings gr.Markdown("## Processing Status") status_output = gr.Textbox(label="Status") with gr.Tabs(): with gr.Tab("Optimized Resume PDF"): pdf_opt_download = gr.File(label="Download Optimized Resume") pdf_opt_viewer = PDF(label="View Optimized Resume") with gr.Tab("Final Report PDF"): pdf_final_download = gr.File(label="Download Final Report") pdf_final_viewer = PDF(label="View Final Report") with gr.Tab("Interview Questions PDF"): pdf_int_download = gr.File(label="Download Interview Questions") pdf_int_viewer = PDF(label="View Interview Questions") run_button.click( process_resume, inputs=[ openai_api_key_input, serper_api_key_input, model_dropdown, new_resume_file, company_name_text, job_url_text ], outputs=[ status_output, pdf_opt_viewer, pdf_opt_download, pdf_final_viewer, pdf_final_download, pdf_int_viewer, pdf_int_download ] ) if __name__ == "__main__": demo.launch()