Spaces:
Running
Running
SyedAzlanzar
commited on
Commit
·
91aab29
1
Parent(s):
a704218
@feat : enhance cover letter generation with markdown formatting; update PDF saving logic
Browse files- app/api/routes.py +16 -7
- app/models/schema.py +8 -0
- app/services/generator.py +21 -27
- app/services/pdf_creator.py +32 -0
app/api/routes.py
CHANGED
@@ -6,11 +6,7 @@ from app.services.resume_parser import extract_resume_text
|
|
6 |
from app.utils.file_utils import generate_unique_filename
|
7 |
from fastapi import UploadFile, File
|
8 |
from app.services.hf_storage_service import HuggingFaceStorageService
|
9 |
-
import
|
10 |
-
from dotenv import load_dotenv
|
11 |
-
|
12 |
-
|
13 |
-
|
14 |
|
15 |
storage_service = HuggingFaceStorageService()
|
16 |
|
@@ -20,13 +16,26 @@ router = APIRouter()
|
|
20 |
async def generate_cover_letter_api(data: GenerateRequest):
|
21 |
try:
|
22 |
if len(data.job_details) > 2048:
|
23 |
-
raise HTTPException(status_code=400, detail="Job
|
24 |
|
25 |
resume_text = extract_resume_text(data.resume_path)
|
26 |
letter_text = await generate_cover_letter(data, resume_text)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
27 |
|
28 |
filename = generate_unique_filename()
|
29 |
-
pdf_path = save_pdf(
|
30 |
|
31 |
return GenerateResponse(
|
32 |
letter=letter_text,
|
|
|
6 |
from app.utils.file_utils import generate_unique_filename
|
7 |
from fastapi import UploadFile, File
|
8 |
from app.services.hf_storage_service import HuggingFaceStorageService
|
9 |
+
from app.services.pdf_creator import build_cover_letter_md, convert_md_to_text
|
|
|
|
|
|
|
|
|
10 |
|
11 |
storage_service = HuggingFaceStorageService()
|
12 |
|
|
|
16 |
async def generate_cover_letter_api(data: GenerateRequest):
|
17 |
try:
|
18 |
if len(data.job_details) > 2048:
|
19 |
+
raise HTTPException(status_code=400, detail="Job detail is too long")
|
20 |
|
21 |
resume_text = extract_resume_text(data.resume_path)
|
22 |
letter_text = await generate_cover_letter(data, resume_text)
|
23 |
+
|
24 |
+
md_cover_letter = build_cover_letter_md(
|
25 |
+
your_name=data.full_name,
|
26 |
+
postal_code=data.postal_code,
|
27 |
+
city=data.city,
|
28 |
+
email=data.email,
|
29 |
+
phone=data.phone_number,
|
30 |
+
job_title=data.job_title,
|
31 |
+
company_name=data.company_name,
|
32 |
+
generated_paragraphs=letter_text
|
33 |
+
)
|
34 |
+
|
35 |
+
text_cover_letter = convert_md_to_text(md_cover_letter)
|
36 |
|
37 |
filename = generate_unique_filename()
|
38 |
+
pdf_path = save_pdf(text_cover_letter, filename)
|
39 |
|
40 |
return GenerateResponse(
|
41 |
letter=letter_text,
|
app/models/schema.py
CHANGED
@@ -3,6 +3,14 @@ from pydantic import BaseModel
|
|
3 |
class GenerateRequest(BaseModel):
|
4 |
job_details: str
|
5 |
resume_path: str
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
6 |
|
7 |
class GenerateResponse(BaseModel):
|
8 |
letter: str
|
|
|
3 |
class GenerateRequest(BaseModel):
|
4 |
job_details: str
|
5 |
resume_path: str
|
6 |
+
job_title: str
|
7 |
+
company_name: str
|
8 |
+
email: str
|
9 |
+
phone_number: str
|
10 |
+
postal_code: str
|
11 |
+
city: str
|
12 |
+
country: str
|
13 |
+
full_name: str
|
14 |
|
15 |
class GenerateResponse(BaseModel):
|
16 |
letter: str
|
app/services/generator.py
CHANGED
@@ -29,35 +29,30 @@ async def generate_cover_letter(data: GenerateRequest, resume_text: str) -> str:
|
|
29 |
|
30 |
altered_job_details = await job_details_alteration(data.job_details)
|
31 |
|
|
|
32 |
prompt = f"""
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
|
39 |
-
|
40 |
-
|
41 |
-
|
42 |
-
|
43 |
-
|
44 |
-
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
|
49 |
-
|
50 |
-
|
51 |
-
|
52 |
-
|
53 |
-
1. Tailored specifically to the job requirements
|
54 |
-
2. Highlights relevant experience from the resume
|
55 |
-
3. Professional and engaging tone
|
56 |
-
4. Proper markdown formatting throughout
|
57 |
-
"""
|
58 |
|
59 |
messages = [
|
60 |
-
{"role": "system", "content": "You are Qwen, created by Alibaba Cloud. You are a helpful AI cover letter bot that generates professional cover letters in markdown format. Always respond with properly formatted markdown."},
|
61 |
{"role": "user", "content": prompt}
|
62 |
]
|
63 |
# Apply chat template
|
@@ -123,7 +118,6 @@ async def job_details_alteration(job_details:str) -> str:
|
|
123 |
- If a section is not found, skip it. """
|
124 |
|
125 |
messages = [
|
126 |
-
{"role":"system", "content": "You are a job description cleaner. I will give you a long job description that includes many sections like company intro, perks, and marketing fluff. "},
|
127 |
{"role": "user", "content": prompt}
|
128 |
]
|
129 |
text = tokenizer.apply_chat_template(
|
|
|
29 |
|
30 |
altered_job_details = await job_details_alteration(data.job_details)
|
31 |
|
32 |
+
|
33 |
prompt = f"""
|
34 |
+
You are a professional cover letter writer.
|
35 |
+
|
36 |
+
Generate exactly **3 paragraphs** of a cover letter in **markdown format** based on the following:
|
37 |
+
|
38 |
+
**Job Title:** {data.job_title}
|
39 |
+
**Company Name:** {data.company_name}
|
40 |
+
**Job Details:** {altered_job_details}
|
41 |
+
**Resume Content:** {resume_text}
|
42 |
+
|
43 |
+
### Instructions:
|
44 |
+
- Write in a professional and engaging tone.
|
45 |
+
- Tailor the content specifically to the job requirements.
|
46 |
+
- Highlight relevant experience, skills, and achievements from the resume.
|
47 |
+
- Each paragraph should be distinct and flow logically:
|
48 |
+
1. Introduction (state interest and motivation)
|
49 |
+
2. Main body (highlight relevant skills and experience)
|
50 |
+
3. Closing (express enthusiasm and call to action)
|
51 |
+
- Return only **3 paragraphs**, no extra text.
|
52 |
+
- Do not include contact details, date, or subject line (these will be injected into a template).
|
53 |
+
"""
|
|
|
|
|
|
|
|
|
|
|
54 |
|
55 |
messages = [
|
|
|
56 |
{"role": "user", "content": prompt}
|
57 |
]
|
58 |
# Apply chat template
|
|
|
118 |
- If a section is not found, skip it. """
|
119 |
|
120 |
messages = [
|
|
|
121 |
{"role": "user", "content": prompt}
|
122 |
]
|
123 |
text = tokenizer.apply_chat_template(
|
app/services/pdf_creator.py
CHANGED
@@ -4,6 +4,7 @@ from datetime import datetime
|
|
4 |
import io
|
5 |
from app.services.hf_storage_service import HuggingFaceStorageService
|
6 |
import unicodedata
|
|
|
7 |
|
8 |
storage_service = HuggingFaceStorageService()
|
9 |
|
@@ -54,3 +55,34 @@ def save_pdf(text: str, filename: str) -> str:
|
|
54 |
# Upload PDF bytes to Hugging Face using your class method
|
55 |
url = storage_service.upload_file_to_hf(file_content=pdf_data, folder="cover-letters", filename=filename)
|
56 |
return url
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
4 |
import io
|
5 |
from app.services.hf_storage_service import HuggingFaceStorageService
|
6 |
import unicodedata
|
7 |
+
import markdown2
|
8 |
|
9 |
storage_service = HuggingFaceStorageService()
|
10 |
|
|
|
55 |
# Upload PDF bytes to Hugging Face using your class method
|
56 |
url = storage_service.upload_file_to_hf(file_content=pdf_data, folder="cover-letters", filename=filename)
|
57 |
return url
|
58 |
+
|
59 |
+
def build_cover_letter_md(
|
60 |
+
your_name, postal_code, city, email, phone,
|
61 |
+
job_title, company_name, generated_paragraphs
|
62 |
+
):
|
63 |
+
template = f"""
|
64 |
+
# {your_name}
|
65 |
+
|
66 |
+
{postal_code} {city}
|
67 |
+
{email} | {phone}
|
68 |
+
|
69 |
+
---
|
70 |
+
|
71 |
+
**Subject:** {job_title}
|
72 |
+
|
73 |
+
Dear {company_name} Team,
|
74 |
+
|
75 |
+
{generated_paragraphs}
|
76 |
+
|
77 |
+
Yours sincerely,
|
78 |
+
{your_name}
|
79 |
+
"""
|
80 |
+
return template
|
81 |
+
|
82 |
+
|
83 |
+
def convert_md_to_text(md_text: str) -> str:
|
84 |
+
"""Convert markdown to plain text for PDF saving."""
|
85 |
+
# markdown2 converts to HTML, then strip tags
|
86 |
+
html = markdown2.markdown(md_text)
|
87 |
+
plain_text = "".join(html.split("<")[0] if "<" in html else html for html in html.split(">"))
|
88 |
+
return plain_text
|