# -*- coding: utf-8 -*- import streamlit as st import os import pandas as pd import matplotlib.pyplot as plt from resume_generation_gemini_pro import generate_gemini from similarity_score_refined import similarity_main from pdf2image import convert_from_path, convert_from_bytes from docx import Document import subprocess import shutil import io from io import BytesIO import tempfile from PIL import Image, ImageDraw, ImageFont import PyPDF2 from docx2pdf import convert import pdfplumber import docx import numpy as np import pypandoc import streamlit.components.v1 as components from docx.enum.text import WD_PARAGRAPH_ALIGNMENT # Create temporary directories temp_dir = tempfile.mkdtemp() # Custom CSS for styling st.markdown(""" """, unsafe_allow_html=True) # Add ResumeMagic Logo # st.markdown('
', unsafe_allow_html=True) # st.image("template_image.png", width=80) # st.markdown('', unsafe_allow_html=True) st.image("template_image.png", use_container_width =True) # Title and Description st.title("Resume Tailoring with Google Generative AI") st.markdown("### Upload your resume and job description to check similarity and generate a tailored resume.") # Helper function to save uploaded files temporarily and return their paths def save_uploaded_file(content): if hasattr(content, 'name'): file_path = os.path.join("/tmp", content.name) with open(file_path, "wb") as f: f.write(content.read()) else: file_path = os.path.join("/tmp", "temp_upload") with open(file_path, "w") as f: f.write(str(content)) return file_path # def save_uploaded_file(uploaded_file): # file_path = os.path.join("/tmp", uploaded_file.name) # with open(file_path, "wb") as f: # f.write(uploaded_file.getbuffer()) # return file_path # Two columns for file uploaders col1, col2 = st.columns(2) with col1: uploaded_resume = st.file_uploader("Upload Current Resume (.docx or .pdf)", type=["docx", "pdf"], key="resume") with col2: uploaded_job_description = st.file_uploader("Upload Job Description (.docx or .pdf)", type=["docx", "pdf"], key="job_description") def get_score(resume_path, job_description_path): similarity_score = similarity_main(resume_path, job_description_path) if isinstance(similarity_score, str) and '%' in similarity_score: similarity_score = float(similarity_score.replace('%', '')) # Display messages based on score range if similarity_score < 50: st.markdown('Low chance, skills gap identified!
', unsafe_allow_html=True) pie_colors = ['#FF4B4B', '#E5E5E5'] elif 50 <= similarity_score < 70: st.markdown('Good chance but you can improve further!
', unsafe_allow_html=True) pie_colors = ['#FFC107', '#E5E5E5'] else: st.markdown('Excellent! You can submit your CV.
', unsafe_allow_html=True) pie_colors = ['#4CAF50', '#E5E5E5'] return similarity_score, pie_colors def display_score(similarity, colors): # Display Score as a Pie Chart st.markdown(f"### Resume - Job Match: {int(similarity_score)}%") # Pie chart to show similarity fig, ax = plt.subplots() # ax.pie([similarity_score, 100 - similarity_score], labels=['Match', 'Difference'], autopct='%1.1f%%', startangle=140, colors=['#4B7BE5', '#E5E5E5']) ax.pie([similarity_score, 100 - similarity_score], labels=['Match', 'Difference'], autopct='%1.1f%%', startangle=140, colors=pie_colors) ax.axis('equal') st.pyplot(fig) def display_docx_content(file): doc = docx.Document(file) full_text = [] for para in doc.paragraphs: full_text.append(para.text) return '\n'.join(full_text) # Function to save a file from BytesIO to a temporary file def save_bytes_to_tempfile(bytes_data, suffix): with tempfile.NamedTemporaryFile(delete=False, suffix=suffix) as temp_file: temp_file.write(bytes_data) return temp_file.name def save_bytes_as_pdf(docx_bytes, output_path='output.pdf'): # Create a temporary directory with tempfile.TemporaryDirectory() as tmp_dir: # Write the DOCX bytes to a temporary file temp_file = os.path.join(tmp_dir, 'temp.docx') with open(temp_file, 'wb') as f: f.write(docx_bytes) # Convert the temporary DOCX to PDF pdf_path = os.path.join(tmp_dir, 'output.pdf') convert(temp_file, pdf_path) # Copy the PDF to the desired output location with open(output_path, 'wb') as f: with open(pdf_path, 'rb') as src_f: f.write(src_f.read()) # Clean up the temporary directory os.remove(output_path) def display_content_with_page_numbers(content, words_per_page=290): # Split content into words words = content.split() total_pages = (len(words) // words_per_page) + (1 if len(words) % words_per_page != 0 else 0) # Display content with page numbers for i in range(total_pages): start_index = i * words_per_page end_index = start_index + words_per_page page_content = ' '.join(words[start_index:end_index]) # st.markdown(f"#### Page {i + 1}") # st.write(page_content) st.markdown(f"#### Page {total_pages}") def save_docx_as_pdf(input_path, output_path='output.pdf'): if input_path.lower().endswith('.docx'): try: # Convert .docx to .pdf using LibreOffice subprocess.run(['libreoffice', '--headless', '--convert-to', 'pdf', input_path, '--outdir', os.path.dirname(output_path)], check=True) if not os.path.exists(output_path): raise FileNotFoundError("Conversion failed; output PDF not found.") except (FileNotFoundError, subprocess.CalledProcessError): st.error("Failed to convert DOCX to PDF. Please check LibreOffice installation.") elif input_path.lower().endswith('.pdf'): shutil.copy(input_path, output_path) else: raise ValueError("Unsupported file format. Please upload a .docx or .pdf file.") def display_pdf_page(pdf_path): try: # Open PDF file with open(pdf_path, 'rb') as file: reader = PyPDF2.PdfReader(file) # Extract text from the first page page = reader.pages[0] x_object = page.extract_text() # Convert text to image (using PIL) img = Image.new('RGB', (800, 1000)) draw = ImageDraw.Draw(img) font = ImageFont.truetype("arial.ttf", 20) # Draw text on the image draw.text((10, 10), x_object[:500], fill=(255, 255, 255), font=font) # Display the image display(img) except Exception as e: st.error(f"Failed to display image: {str(e)}") # def display_pdf_pages_as_images(pdf_path): # try: # with pdfplumber.open(pdf_path) as pdf: # for i, page in enumerate(pdf.pages): # st.markdown(f"#### Page {i + 1}") # # Convert the page to an image # image = page.to_image() # # Render the image using Streamlit # # st.image(image.original, use_column_width=True) # st.image(image.original, use_container_width=False) # except Exception as e: # st.error(f"Failed to display PDF as image: {str(e)}") def display_pdf_pages_as_images(pdf_path): try: with pdfplumber.open(pdf_path) as pdf: num_pages = len(pdf.pages) # Create a container with columns for each page columns = st.columns(num_pages) for i, page in enumerate(pdf.pages): # Convert the page to an image image = page.to_image() # Display each page image in its respective column with columns[i]: st.markdown(f"#### Page {i + 1}") st.image(image.original, use_container_width=True) except Exception as e: st.error(f"Failed to display PDF as image: {str(e)}") def display_doc_as_image2(pdf_path): iframe_code = f""" """ st.markdown(iframe_code, unsafe_allow_html=True) def add_bold_and_normal_text(paragraph, text): """Adds text to the paragraph, handling bold formatting.""" while "**" in text: before, bold_part, after = text.partition("**") if before: paragraph.add_run(before) if bold_part == "**": bold_text, _, text = after.partition("**") paragraph.add_run(bold_text).bold = True else: text = after if text: paragraph.add_run(text) # Process if files are uploaded if uploaded_resume and uploaded_job_description: # Save files resume_path = save_uploaded_file(uploaded_resume) job_description_path = save_uploaded_file(uploaded_job_description) # Similarity Score Section st.markdown("---") # st.subheader("Check Job Match") if st.button("Resume-JD Matching"): with st.spinner("Computing Match"): similarity_score, pie_colors = get_score(resume_path, job_description_path) display_score(similarity_score, pie_colors) #Autoscroll components.html(""" """) # Generate Tailored Resume Section st.markdown("---") # st.subheader("Tailor Resume") if st.button("Tailor Resume"): with st.spinner("Generating resume..."): generated_resume, new_resume_path = generate_gemini(resume_path, job_description_path) # resume_path = save_uploaded_file(generated_resume) # st.markdown("Generated Tailored Resume:") # st.write(generated_resume) #Autoscroll components.html(""" """) # with st.spinner("Computing Match"): # similarity_score, pie_colors = get_score(resume_path, job_description_path) # display_score(similarity_score, pie_colors) if generated_resume is not None: # st.markdown("---") st.title("Uploaded Resume") doc = Document() # Split the text into lines for processing lines = generated_resume.splitlines() for line in lines: if line.startswith("# "): # Top-level heading (Highest level) paragraph = doc.add_heading(line[2:].strip(), level=0) # Level 0 is the highest heading in Word paragraph.alignment = WD_PARAGRAPH_ALIGNMENT.JUSTIFY elif line.startswith("## "): # Main heading (Level 1) paragraph = doc.add_heading(line[3:].strip(), level=1) paragraph.alignment = WD_PARAGRAPH_ALIGNMENT.JUSTIFY elif line.startswith("### "): # Subheading (Level 2) paragraph = doc.add_heading(line[4:].strip(), level=2) paragraph.alignment = WD_PARAGRAPH_ALIGNMENT.JUSTIFY elif line.startswith("- "): # Bullet points paragraph = doc.add_paragraph(style="List Bullet") add_bold_and_normal_text(paragraph, line[2:].strip()) paragraph.alignment = WD_PARAGRAPH_ALIGNMENT.JUSTIFY elif line.startswith("* "): # Sub-bullet points or normal list items paragraph = doc.add_paragraph(style="List Bullet 2") add_bold_and_normal_text(paragraph, line[2:].strip()) paragraph.alignment = WD_PARAGRAPH_ALIGNMENT.JUSTIFY elif line.strip(): # Normal text (ignores blank lines) paragraph = doc.add_paragraph() add_bold_and_normal_text(paragraph, line.strip()) paragraph.alignment = WD_PARAGRAPH_ALIGNMENT.JUSTIFY # Save the generated document as a .docx file in memory resume_bytes = BytesIO() doc.save(resume_bytes) resume_bytes.seek(0) # Save the .docx to a temporary file gen_docx_path = save_bytes_to_tempfile(resume_bytes.getvalue(), 'docx') # Convert the generated .docx to a .pdf gen_pdf_path = save_uploaded_file(gen_docx_path) # st.write(display_docx_content(gen_pdf_path)) # st.markdown("### Uploaded Resume") save_docx_as_pdf(resume_path, '/tmp/uploaded_resume.pdf') display_pdf_pages_as_images(resume_path) st.success(f"Download tailored resume") st.download_button( label="Generated Resume (Word)", data=resume_bytes, file_name="tailored_resume.docx", mime="application/vnd.openxmlformats-officedocument.wordprocessingml.document" ) # Display uploaded and generated resumes side-by-side # col1, col2 = st.columns(2) # with col1: # st.markdown("### Uploaded Resume") # save_docx_as_pdf(resume_path, '/tmp/uploaded_resume.pdf') # display_pdf_pages_as_images(resume_path) # with col2: # st.markdown("### Tailored Resume") # # display_pdf_pages_as_images(gen_pdf_path) # display_content_with_page_numbers(generated_resume, 290) # st.write(generated_resume) # # display_content_with_page_numbers(generated_resume, 290) # st.success(f"Download tailored resume") # col1, col2 = st.columns(2) # with col1: # st.download_button( # label="Generated Resume (PDF)", # data=open(gen_pdf_path, 'rb').read(), # file_name="tailored_resume.pdf", # mime="application/pdf" # ) # with col2: # st.download_button( # label="Generated Resume (Word)", # data=resume_bytes, # file_name="tailored_resume.docx", # mime="application/vnd.openxmlformats-officedocument.wordprocessingml.document" # ) else: st.warning("Please upload both the resume and job description files.")