import gradio as gr from transformers import pipeline import re import os from huggingface_hub import login # ✅ Authenticate using HF token stored in secret login(token=os.environ.get("HUGGINGFACEHUB_API_TOKEN")) # ✅ Use a lightweight instruction-tuned model compatible with CPU summarizer = pipeline("text2text-generation", model="declare-lab/flan-alpaca-base") # ✅ Highlight matching and find missing keywords from sklearn.feature_extraction.text import ENGLISH_STOP_WORDS def compare_keywords(resume_text, job_desc): resume_words = set(re.findall(r"\b\w{3,}\b", resume_text.lower())) - ENGLISH_STOP_WORDS job_words = set(re.findall(r"\b\w{3,}\b", job_desc.lower())) - ENGLISH_STOP_WORDS matched = resume_words & job_words missing = job_words - resume_words return matched, missing def highlight_keywords(resume_text, matched): highlighted = resume_text for word in sorted(matched, key=len, reverse=True): highlighted = re.sub(rf"\b({re.escape(word)})\b", r"**\1**", highlighted, flags=re.IGNORECASE) return highlighted # 🔍 Use LLM to extract missing keywords contextually from JD def extract_missing_keywords_with_llm(job_desc, resume_text): prompt = f""" Given the following job description and resume, list the important skills, tools, and concepts from the job description that are missing or weakly represented in the resume. Job Description: {job_desc} Resume: {resume_text} Only list the missing keywords as bullet points. """ result = summarizer(prompt, max_new_tokens=300, do_sample=True)[0] return result.get('generated_text', result.get('summary_text', str(result))).strip() # 🔍 Prompt for dynamic section classification and feedback def build_dynamic_prompt(job_desc, resume_text, analyze_with_jd): prompt = f""" Analyze the resume below and organize it into meaningful categories (e.g., Skills, Education, Work Experience, etc.). If a job description is provided, compare it against the resume and suggest improvements section by section. Job Description: {job_desc if analyze_with_jd else '[None provided]'} Resume: {resume_text} Return structured Markdown with headers for each section and improvement suggestions. """ return prompt # 🧠 Function to call Hugging Face model and get structured resume feedback def analyze_resume(job_desc, resume_text, analyze_with_jd): if not resume_text.strip(): return "⚠️ Please paste your resume text." user_prompt = build_dynamic_prompt(job_desc, resume_text, analyze_with_jd) try: result = summarizer(user_prompt, max_new_tokens=512, do_sample=True)[0] response_text = result.get('generated_text', result.get('summary_text', str(result))).strip() if analyze_with_jd and job_desc: matched, missing = compare_keywords(resume_text, job_desc) highlighted_resume = highlight_keywords(resume_text, matched) llm_missing_keywords = extract_missing_keywords_with_llm(job_desc, resume_text) return f"### 🔍 Resume with Highlighted Matches\n\n{highlighted_resume}\n\n---\n**✅ Matched Keywords (Basic Comparison):**\n{', '.join(sorted(matched)) or 'None'}\n\n**❌ Missing Keywords (Basic Comparison):**\n{', '.join(sorted(missing)) or 'None'}\n\n**🤖 LLM-Inferred Missing Keywords:**\n{llm_missing_keywords}\n\n---\n{response_text}" return response_text except Exception as e: return f"❌ Error: {str(e)}" # 🎛️ Gradio UI def create_ui(): with gr.Blocks() as demo: with gr.Row(): with gr.Column(): analyze_checkbox = gr.Checkbox(label="Analyze with Job Description", value=True) job_desc = gr.Textbox(label="Job Description", lines=6, placeholder="Paste job description here...") resume_text = gr.Textbox(label="Resume Text", lines=16, placeholder="Paste resume content here...") analyze_button = gr.Button("Run Analysis") with gr.Column(): output_analysis = gr.Markdown(label="Resume Analysis and Suggestions") analyze_button.click(fn=analyze_resume, inputs=[job_desc, resume_text, analyze_checkbox], outputs=output_analysis) return demo if __name__ == '__main__': create_ui().launch()