File size: 4,948 Bytes
f833cab
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
import io
import json
from typing import Optional

import gradio as gr
import PyPDF2

from resume_ai import score, improve

def extract_text_from_pdf(file_obj: io.IOBase) -> str:
    """Extract text from a PDF file-like object."""
    try:
        reader = PyPDF2.PdfReader(file_obj)
        text_chunks = []
        for page in reader.pages:
            page_text = page.extract_text() or ""
            text_chunks.append(page_text)
        text = "\n".join(text_chunks).strip()
        if not text:
            raise ValueError("No extractable text found in PDF.")
        return text
    except Exception as e:
        raise ValueError(f"Error reading PDF: {e}")

def read_resume_to_text(resume_file_path) -> str:
    """
    Accepts a file path and returns text content.
    Supports PDF and plain text files.
    """
    if resume_file_path is None:
        raise ValueError("Please upload a resume file.")

    filename = str(resume_file_path).lower()
    if filename.endswith(".pdf"):
        with open(resume_file_path, "rb") as f:
            return extract_text_from_pdf(f)
    else:
        with open(resume_file_path, "rb") as f:
            data = f.read()
        if not data:
            raise ValueError("Uploaded file is empty.")
        try:
            return data.decode("utf-8").strip()
        except UnicodeDecodeError:
            return data.decode("latin-1").strip()

def score_fn(resume_file_path, job_desc: str) -> str:
    try:
        if not job_desc or not job_desc.strip():
            raise ValueError("Please paste a job description.")
        resume_text = read_resume_to_text(resume_file_path)
        result = score(resume_text, job_desc)
        return json.dumps(result, indent=2, ensure_ascii=False)
    except Exception as e:
        return f"Error: {e}"

def improve_fn(resume_file_path, job_desc: Optional[str]) -> str:
    try:
        resume_text = read_resume_to_text(resume_file_path)
        jd_text = job_desc if job_desc and job_desc.strip() else None
        suggestions = improve(resume_text, jd_text)
        if isinstance(suggestions, (list, tuple)):
            bullets = "\n".join(f"- {s}" for s in suggestions)
            return f"### Suggestions\n{bullets}"
        elif isinstance(suggestions, dict):
            return "```json\n" + json.dumps(suggestions, indent=2, ensure_ascii=False) + "\n```"
        else:
            return str(suggestions)
    except Exception as e:
        return f"Error: {e}"

def format_score_display(result_json) -> str:
    """
    Takes the result JSON (as dict or str), parses it, and returns a Markdown string for display.
    """
    if isinstance(result_json, str):
        try:
            result = json.loads(result_json)
        except Exception:
            return f"```\n{result_json}\n```"
    else:
        result = result_json

    md = f"## πŸ† ATS Compatibility Score: **{result.get('overall_score', 0)}%**\n\n"
    md += "### Category Scores\n"
    md += "| Skills | Experience | Education |\n"
    md += "|--------|------------|-----------|\n"
    cs = result.get("category_scores", {})
    md += f"| {cs.get('skills',0)}% | {cs.get('experience',0)}% | {cs.get('education',0)}% |\n\n"

    gaps = result.get("top_skill_gaps", [])
    if gaps:
        md += "### 🚩 Top Skill Gaps\n"
        for gap in gaps:
            md += f"- {gap}\n"
    return md

# ...existing code...

with gr.Blocks(title="Resume AI (Score & Improve)") as demo:
    gr.Markdown(
        """
        # πŸ“„ Resume AI β€” Score & Improve
        Upload your resume (PDF or TXT), paste a Job Description, and get:
        - **Score**: A formatted breakdown of your resume's ATS compatibility
        - **Improve**: A healthy set of suggestions to enhance your resume
        """
    )

    with gr.Row():
        resume = gr.File(label="Upload Resume (PDF or TXT)", file_types=[".pdf", ".txt"], type="filepath")
        jd = gr.Textbox(label="Job Description (paste here)", lines=10, placeholder="Paste JD text...")

    with gr.Row():
        score_btn = gr.Button("βš–οΈ Score Resume", variant="primary")
        improve_btn = gr.Button("✨ Improve Resume")

    score_out = gr.Markdown(label="Score (Formatted)")
    improve_out = gr.Markdown(label="Improvement Suggestions")

    def score_fn_display(resume_file_path, job_desc: str) -> str:
        try:
            if not job_desc or not job_desc.strip():
                raise ValueError("Please paste a job description.")
            resume_text = read_resume_to_text(resume_file_path)
            result = score(resume_text, job_desc)
            return format_score_display(result)
        except Exception as e:
            return f"Error: {e}"

    score_btn.click(fn=score_fn_display, inputs=[resume, jd], outputs=score_out)
    improve_btn.click(fn=improve_fn, inputs=[resume, jd], outputs=improve_out)


if __name__ == "__main__":
    demo.launch(server_name="0.0.0.0",server_port=7860, pwa=True)