SyedAzlanzar commited on
Commit
c7bd3fe
Β·
0 Parent(s):

Clean Repo Initialiazed with generator

Browse files
.gitattributes ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ *.7z filter=lfs diff=lfs merge=lfs -text
2
+ *.arrow filter=lfs diff=lfs merge=lfs -text
3
+ *.bin filter=lfs diff=lfs merge=lfs -text
4
+ *.bz2 filter=lfs diff=lfs merge=lfs -text
5
+ *.ckpt filter=lfs diff=lfs merge=lfs -text
6
+ *.ftz filter=lfs diff=lfs merge=lfs -text
7
+ *.gz filter=lfs diff=lfs merge=lfs -text
8
+ *.h5 filter=lfs diff=lfs merge=lfs -text
9
+ *.joblib filter=lfs diff=lfs merge=lfs -text
10
+ *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
+ *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
+ *.model filter=lfs diff=lfs merge=lfs -text
13
+ *.msgpack filter=lfs diff=lfs merge=lfs -text
14
+ *.npy filter=lfs diff=lfs merge=lfs -text
15
+ *.npz filter=lfs diff=lfs merge=lfs -text
16
+ *.onnx filter=lfs diff=lfs merge=lfs -text
17
+ *.ot filter=lfs diff=lfs merge=lfs -text
18
+ *.parquet filter=lfs diff=lfs merge=lfs -text
19
+ *.pb filter=lfs diff=lfs merge=lfs -text
20
+ *.pickle filter=lfs diff=lfs merge=lfs -text
21
+ *.pkl filter=lfs diff=lfs merge=lfs -text
22
+ *.pt filter=lfs diff=lfs merge=lfs -text
23
+ *.pth filter=lfs diff=lfs merge=lfs -text
24
+ *.rar filter=lfs diff=lfs merge=lfs -text
25
+ *.safetensors filter=lfs diff=lfs merge=lfs -text
26
+ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
+ *.tar.* filter=lfs diff=lfs merge=lfs -text
28
+ *.tar filter=lfs diff=lfs merge=lfs -text
29
+ *.tflite filter=lfs diff=lfs merge=lfs -text
30
+ *.tgz filter=lfs diff=lfs merge=lfs -text
31
+ *.wasm filter=lfs diff=lfs merge=lfs -text
32
+ *.xz filter=lfs diff=lfs merge=lfs -text
33
+ *.zip filter=lfs diff=lfs merge=lfs -text
34
+ *.zst filter=lfs diff=lfs merge=lfs -text
35
+ *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ app/static/fonts/Roboto-Regular.ttf filter=lfs diff=lfs merge=lfs -text
Dockerfile ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.11-slim
2
+
3
+ WORKDIR /app
4
+ COPY requirements.txt .
5
+ RUN pip install --no-cache-dir -r requirements.txt
6
+ COPY . .
7
+
8
+ # Create cache folder and fix permissions
9
+ RUN mkdir /.cache
10
+ RUN chmod -R 777 /.cache
11
+
12
+ RUN mkdir -p /code/backend/transformers_cache && chmod -R 777 /code/backend/transformers_cache
13
+
14
+ CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "7860"]
README.md ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Resume Parser
3
+ emoji: 😻
4
+ colorFrom: yellow
5
+ colorTo: yellow
6
+ sdk: docker
7
+ pinned: false
8
+ ---
9
+
10
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
app/__init__.py ADDED
File without changes
app/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (146 Bytes). View file
 
app/__pycache__/main.cpython-312.pyc ADDED
Binary file (1.84 kB). View file
 
app/api/__pycache__/routes.cpython-312.pyc ADDED
Binary file (1.38 kB). View file
 
app/api/routes.py ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import APIRouter, HTTPException
2
+ from app.models.schema import GenerateRequest, GenerateResponse
3
+ from app.services.generator import generate_cover_letter
4
+ from app.services.pdf_creator import save_pdf
5
+ from app.services.resume_parser import extract_text_from_resume
6
+ from app.utils.file_utils import generate_unique_filename
7
+
8
+ router = APIRouter()
9
+
10
+ @router.post("/generate", response_model=GenerateResponse)
11
+ async def generate_cover_letter_api(data: GenerateRequest):
12
+ try:
13
+ resume_text = extract_text_from_resume(data.resume_path)
14
+ letter_text = generate_cover_letter(data, resume_text)
15
+
16
+ filename = generate_unique_filename()
17
+ pdf_path = save_pdf(letter_text, filename)
18
+
19
+ return GenerateResponse(
20
+ letter=letter_text,
21
+ pdf_url=f"/{filename}"
22
+ )
23
+ except Exception as e:
24
+ raise HTTPException(status_code=500, detail=str(e))
app/main.py ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI, Request
2
+ from fastapi.staticfiles import StaticFiles
3
+ from app.api.routes import router
4
+ import os
5
+ import logging
6
+
7
+ logging.basicConfig(level=logging.INFO)
8
+ app = FastAPI(title="Cover Letter Generator")
9
+
10
+ BASE_DIR = os.path.dirname(os.path.abspath(__file__))
11
+
12
+
13
+
14
+ # Serve resumes (these are static, bundled in repo, read-only is fine)
15
+ app.mount(
16
+ "/resumes",
17
+ StaticFiles(directory=os.path.join(BASE_DIR, "static/resumes")),
18
+ name="resumes"
19
+ )
20
+
21
+ app.mount("/fonts", StaticFiles(directory=os.path.join(BASE_DIR, "static/fonts")), name="fonts")
22
+
23
+ # Writable directory for PDFs
24
+ PDF_DIR = "/tmp/pdfs"
25
+ os.makedirs(PDF_DIR, exist_ok=True)
26
+
27
+ # Serve PDFs (generated at runtime) under /static/pdfs
28
+ app.mount("/static/pdfs", StaticFiles(directory=PDF_DIR), name="pdfs")
29
+
30
+
31
+ @app.middleware("http")
32
+ async def log_requests(request: Request, call_next):
33
+ logging.info(f"API hit: {request.method} {request.url}")
34
+ response = await call_next(request)
35
+ return response
36
+
37
+
38
+ @app.get("/")
39
+ def ping():
40
+ print()
41
+ return {"status": "ok"}
42
+
43
+
44
+ # Register routes
45
+ app.include_router(router)
app/models/__pycache__/schema.cpython-312.pyc ADDED
Binary file (657 Bytes). View file
 
app/models/schema.py ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ from pydantic import BaseModel
2
+
3
+ class GenerateRequest(BaseModel):
4
+ job_details: str
5
+ resume_path: str
6
+
7
+ class GenerateResponse(BaseModel):
8
+ letter: str
9
+ pdf_url: str
app/services/__pycache__/generator.cpython-312.pyc ADDED
Binary file (7.4 kB). View file
 
app/services/__pycache__/pdf_creator.cpython-312.pyc ADDED
Binary file (775 Bytes). View file
 
app/services/__pycache__/resume_parser.cpython-312.pyc ADDED
Binary file (747 Bytes). View file
 
app/services/generator.py ADDED
@@ -0,0 +1,130 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from transformers import AutoTokenizer, AutoModelForCausalLM, pipeline
2
+ import torch
3
+ from app.models.schema import GenerateRequest
4
+ import os
5
+ from fastapi import FastAPI, HTTPException
6
+
7
+
8
+ os.environ["TRANSFORMERS_CACHE"] = "/code/backend/transformers_cache"
9
+ os.makedirs(os.environ["TRANSFORMERS_CACHE"], exist_ok=True)
10
+
11
+
12
+ # Initialization model immediately after server is Up!
13
+ device = "cuda" if torch.cuda.is_available() else "cpu"
14
+ dtype = torch.float16 if device == "cuda" else torch.float32
15
+
16
+ # TinyLlama/TinyLlama-1.1B-Chat-v1.0
17
+ model_name = "Qwen/Qwen2.5-0.5B-Instruct"
18
+ tokenizer = AutoTokenizer.from_pretrained(model_name)
19
+ model = AutoModelForCausalLM.from_pretrained(
20
+ model_name,
21
+ torch_dtype="auto",
22
+ device_map="auto"
23
+ )
24
+
25
+ model = model.to(device)
26
+
27
+
28
+ def generate_cover_letter(data: GenerateRequest, resume_text: str) -> str:
29
+ prompt = f"""
30
+ AI Cover Letter Writer Bot Prompt
31
+
32
+ You are an expert cover letter writer that creates personalized, human-written cover letters based on job descriptions and CV information. Your goal is to craft authentic letters that sound like they're written by a Pakistani professional with 6.5 IELTS band score - natural, clear, but not overly sophisticated or AI-generated.
33
+
34
+ Input Requirements
35
+
36
+ - **Job Details:** Complete job posting with requirements, responsibilities, and company information
37
+ Job Details: {data.job_details}
38
+
39
+ - **CV/Resume:** Tailored resume specifically prepared for the target role
40
+ Resume Text: {resume_text}
41
+
42
+ Output Format Required:
43
+ Dear [Hiring Team/Company Name extracted from Job Details],
44
+
45
+ [Paragraph 1: Position + why interested in company]
46
+ [Paragraph 2: Relevant experience + skills matching job requirements]
47
+ [Paragraph 3: Value you bring + enthusiasm to join]
48
+
49
+ Best regards,
50
+ - Name of user extract from resume
51
+
52
+ **Do not include the following data from resume:**
53
+
54
+ - Header with personal contact information
55
+ - Date
56
+ - Subject line
57
+ - Closing signature
58
+ - Any formatting beyond the basic greeting and paragraphs
59
+
60
+ Rules:
61
+ - 3 paragraphs only (50-80 words each)
62
+ - Total: 200-300 words
63
+ - Sound natural, not AI-written
64
+ - Match resume skills to job requirements
65
+ - Mention specific technologies/companies from inputs
66
+ - Use simple language, avoid: robust, leverage, utilize, optimal, innovative
67
+
68
+ **Missing Skills? Say: "I am eager to learn [skill] and have quickly mastered [example] before"
69
+ **Less Experience? Focus on quality: "My experience with [skill] includes [specific achievement]"
70
+
71
+ Before finalizing, ensure the cover letter:
72
+
73
+ - [ ] Addresses the specific role and company by name
74
+ - [ ] Highlights 2-3 most relevant experiences from the CV
75
+ - [ ] Addresses any obvious skill gaps with learning commitment
76
+ - [ ] Uses natural, varied language (avoiding the banned word list)
77
+ - [ ] Uses simple, clear language that sounds human-written (not AI-generated)
78
+ - [ ] Avoids overly sophisticated vocabulary or complex sentence structures
79
+ - [ ] Includes a clear call to action
80
+ - [ ] Stays within 3-4 paragraph limit
81
+
82
+ Remember: Your goal is to write a cover letter that sounds like it was written by a real person with good English skills, not by AI. Keep it natural, straightforward, and genuine.
83
+ """
84
+ try:
85
+
86
+ messages = [
87
+ {"role": "system", "content": "You are Qwen, created by Alibaba Cloud. You are a helpful AI covert letter bot."},
88
+ {"role": "user", "content": prompt}
89
+ ]
90
+
91
+ # Apply chat template
92
+ text = tokenizer.apply_chat_template(
93
+ messages,
94
+ tokenize=False,
95
+ add_generation_prompt=True
96
+ )
97
+
98
+ # Tokenize input
99
+ model_inputs = tokenizer(
100
+ [text],
101
+ return_tensors="pt",
102
+ padding=True,
103
+ truncation=True,
104
+ max_length=2048
105
+ ).to(model.device)
106
+
107
+ # Generate with better parameters
108
+ generated_ids = model.generate(
109
+ **model_inputs,
110
+ max_new_tokens=512,
111
+ do_sample=True, # sampling enabled for more natural output
112
+ temperature=0.7, # control creativity
113
+ top_p=0.9, # nucleus sampling
114
+ top_k=50, # top-k sampling
115
+ repetition_penalty=1.1, # reduce repetition
116
+ eos_token_id=tokenizer.eos_token_id
117
+ )
118
+
119
+ # Decode output
120
+ response = tokenizer.decode(
121
+ generated_ids[0],
122
+ skip_special_tokens=True
123
+ )
124
+
125
+ return response
126
+
127
+
128
+
129
+ except Exception as e:
130
+ return {"error": str(e)}
app/services/pdf_creator.py ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fpdf import FPDF
2
+ import os
3
+ from datetime import datetime
4
+
5
+ PDF_DIR = "/tmp/pdfs"
6
+ os.makedirs(PDF_DIR, exist_ok=True)
7
+
8
+ # fallback for local testing
9
+ SPACE_URL = os.getenv("SPACE_URL", "http://localhost:8000")
10
+
11
+
12
+ def normalize_text(text: str) -> str:
13
+ replacements = {
14
+ "’": "'", "β€˜": "'",
15
+ "β€œ": '"', "”": '"',
16
+ "β€”": "-", "–": "-",
17
+ "…": "...",
18
+ }
19
+ for old, new in replacements.items():
20
+ text = text.replace(old, new)
21
+ return text
22
+
23
+
24
+ def save_pdf(text: str, filename: str) -> str:
25
+ text = normalize_text(text)
26
+ path = os.path.join(PDF_DIR, filename)
27
+
28
+ pdf = FPDF()
29
+ pdf.add_page()
30
+ pdf.set_font("Arial", size=11) # Built-in Latin-1 font
31
+ pdf.set_auto_page_break(auto=True, margin=15)
32
+
33
+ sections = text.split('\n\n')
34
+ for section in sections:
35
+ if section.strip():
36
+ if any(word in section.lower() for word in ["best regards", "yours sincerely", "sincerely"]):
37
+ pdf.ln(5)
38
+ lines = section.strip().split('\n')
39
+ for line in lines:
40
+ pdf.cell(0, 6, line.strip(), ln=True, align='L')
41
+ else:
42
+ pdf.multi_cell(0, 6, section.strip(), align='L')
43
+ pdf.ln(8)
44
+
45
+ pdf.output(path)
46
+ return f"{SPACE_URL}/static/pdfs/{filename}"
app/services/resume_parser.py ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ import pdfplumber
2
+
3
+ def extract_text_from_resume(file_path: str) -> str:
4
+ text = ""
5
+ with pdfplumber.open(file_path) as pdf:
6
+ for page in pdf.pages:
7
+ text += page.extract_text() or ""
8
+ return text.strip()
app/static/pdfs/14b6024d26444d919569ceca3c31a597.pdf ADDED
Binary file (2.54 kB). View file
 
app/static/resumes/resume.pdf ADDED
Binary file (52.1 kB). View file
 
app/utils/__pycache__/file_utils.cpython-312.pyc ADDED
Binary file (513 Bytes). View file
 
app/utils/file_utils.py ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ import uuid
2
+
3
+ def generate_unique_filename(extension: str = "pdf") -> str:
4
+ """Generate a unique filename with given extension."""
5
+ return f"{uuid.uuid4().hex}.{extension}"
requirements.txt ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ fastapi
2
+ uvicorn
3
+ transformers
4
+ torch
5
+ fpdf
6
+ pdfplumber
7
+ accelerate
8
+ safetensors