ResumeOptimizer / app.py
pankajrajdeo's picture
Update app.py: adjust file handling and UI for resume optimization
2b9969b
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()