set up downloads for tailored resume and cover letter

This commit is contained in:
Kyle Belanger 2025-02-20 15:57:38 -05:00
parent 099ccc40e8
commit 8efa97d950
3 changed files with 193 additions and 46 deletions

View file

@ -32,11 +32,12 @@
<div id="coverLetterSection"> <div id="coverLetterSection">
<h3>Generated Cover Letter:</h3> <h3>Generated Cover Letter:</h3>
<textarea id="coverLetterOutput" rows="15" cols="80"></textarea> <textarea id="coverLetterOutput" rows="15" cols="80"></textarea>
<button id="downloadBtn">Download as DOCX</button> <button id="downloadCoverLetterBtn">Download as DOCX</button>
</div> </div>
<div id="tailoredResumeSection"> <div id="tailoredResumeSection">
<h3>Tailored Resume:</h3> <h3>Tailored Resume:</h3>
<textarea id="tailoredResumeOutput" rows="15" cols="80"></textarea> <textarea id="tailoredResumeOutput" rows="15" cols="80"></textarea>
<button id="downloadResumeBtn">Download Resume as DOCX</button>
<h3>Key Updates</h3> <h3>Key Updates</h3>
<pre id="keyResumeUpdates"></pre> <pre id="keyResumeUpdates"></pre>
</div> </div>

View file

@ -115,38 +115,89 @@ document.getElementById("generateCoverLetterBtn").addEventListener("click", asyn
} }
}); });
document.getElementById('downloadBtn').addEventListener('click', async function () {
const coverLetterText = document.getElementById('coverLetterOutput').value;
async function downloadDocument(content, endpoint, filename) {
try { try {
const response = await fetch('/generate/download', { const response = await fetch(endpoint, {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ coverLetterText }) body: JSON.stringify({ content })
}); });
if (!response.ok) throw new Error('Failed to download document.'); if (!response.ok) throw new Error('Failed to download document.');
// Convert response to blob // Convert response to blob
const blob = await response.blob(); const blob = await response.blob();
const url = window.URL.createObjectURL(blob); const url = window.URL.createObjectURL(blob);
// Create a temporary download link // Create a temporary download link
const a = document.createElement('a'); const a = document.createElement('a');
a.href = url; a.href = url;
a.download = 'cover_letter.docx'; a.download = filename;
document.body.appendChild(a); document.body.appendChild(a);
a.click(); a.click();
// Cleanup // Cleanup
document.body.removeChild(a); document.body.removeChild(a);
window.URL.revokeObjectURL(url); window.URL.revokeObjectURL(url);
} catch (error) { } catch (error) {
console.error('Error:', error); console.error('Error:', error);
alert('Failed to download document.'); alert('Failed to download document.');
} }
}
// Event listener for cover letter download
document.getElementById('downloadCoverLetterBtn').addEventListener('click', function () {
const coverLetterText = document.getElementById('coverLetterOutput').value;
if (!coverLetterText.trim()) {
alert("Cover letter is empty!");
return;
}
downloadDocument(coverLetterText, '/generate/download-cover-letter', 'cover_letter.docx');
}); });
// Event listener for tailored resume download
document.getElementById('downloadResumeBtn').addEventListener('click', function () {
const tailoredResumeText = document.getElementById('tailoredResumeOutput').value;
if (!tailoredResumeText.trim()) {
alert("Tailored resume is empty!");
return;
}
downloadDocument(tailoredResumeText, '/generate/download-resume', 'tailored_resume.docx');
});
// document.getElementById('downloadBtn').addEventListener('click', async function () {
// const coverLetterText = document.getElementById('coverLetterOutput').value;
// try {
// const response = await fetch('/generate/download', {
// method: 'POST',
// headers: { 'Content-Type': 'application/json' },
// body: JSON.stringify({ coverLetterText })
// });
// if (!response.ok) throw new Error('Failed to download document.');
// // Convert response to blob
// const blob = await response.blob();
// const url = window.URL.createObjectURL(blob);
// // Create a temporary download link
// const a = document.createElement('a');
// a.href = url;
// a.download = 'cover_letter.docx';
// document.body.appendChild(a);
// a.click();
// // Cleanup
// document.body.removeChild(a);
// window.URL.revokeObjectURL(url);
// } catch (error) {
// console.error('Error:', error);
// alert('Failed to download document.');
// }
// });
function formatCoverLetter(rawText) { function formatCoverLetter(rawText) {
return rawText return rawText

View file

@ -95,39 +95,102 @@ router.post('/', async (req, res) => {
} }
}); });
router.post('/download', async (req, res) => { // router.post('/download', async (req, res) => {
try { // try {
const { coverLetterText } = req.body; // const { coverLetterText } = req.body;
// Generate .docx file dynamically // // Generate .docx file dynamically
const docBuffer = await generateCoverLetter(coverLetterText); // const docBuffer = await generateCoverLetter(coverLetterText);
// Set response headers for file download // // Set response headers for file download
res.setHeader('Content-Disposition', 'attachment; filename="cover_letter.docx"'); // res.setHeader('Content-Disposition', 'attachment; filename="cover_letter.docx"');
res.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'); // res.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document');
res.send(docBuffer); // res.send(docBuffer);
} catch (error) { // } catch (error) {
console.error(error); // console.error(error);
res.status(500).json({ error: 'Error generating document' }); // res.status(500).json({ error: 'Error generating document' });
} // }
}); // });
function generateCoverLetter(rawText) { // function generateCoverLetter(rawText) {
// if (!rawText || typeof rawText !== "string") {
// throw new Error("Invalid cover letter text provided.");
// }
// // Convert text into paragraphs, splitting by double newlines
// const paragraphs = rawText.split("\n\n").map(text =>
// new Paragraph({
// children: [
// new TextRun(text),
// new TextRun("\n") // Ensures spacing between paragraphs
// ]
// })
// );
// // Create the document
// const doc = new Document({
// sections: [{ properties: {}, children: paragraphs }]
// });
// return Packer.toBuffer(doc);
// }
// Function to generate a DOCX file
// function generateDocx(rawText) {
// if (!rawText || typeof rawText !== "string") {
// throw new Error("Invalid text provided.");
// }
// const paragraphs = rawText.split("\n\n").map(text =>
// new Paragraph({
// children: [
// new TextRun(text),
// new TextRun("\n") // Ensures spacing between paragraphs
// ]
// })
// );
// const doc = new Document({
// sections: [{ properties: {}, children: paragraphs }]
// });
// return Packer.toBuffer(doc);
// }
function generateDocx(rawText) {
if (!rawText || typeof rawText !== "string") { if (!rawText || typeof rawText !== "string") {
throw new Error("Invalid cover letter text provided."); throw new Error("Invalid resume text provided.");
} }
// Convert text into paragraphs, splitting by double newlines const paragraphs = [];
const paragraphs = rawText.split("\n\n").map(text =>
new Paragraph({ // Split text into lines
children: [ rawText.split("\n").forEach(line => {
new TextRun(text), if (line.trim().startsWith("•")) {
new TextRun("\n") // Ensures spacing between paragraphs // Format bullet points properly
] paragraphs.push(
}) new Paragraph({
); text: line.trim(), // Keep bullet point text
bullet: { level: 0 } // Apply bullet point formatting
})
);
} else if (line.trim() === "") {
// Add spacing for empty lines
paragraphs.push(new Paragraph({ text: "" }));
} else {
// Format normal text
paragraphs.push(
new Paragraph({
children: [
new TextRun(line.trim()),
new TextRun("\n")
]
})
);
}
});
// Create the document // Create the document
const doc = new Document({ const doc = new Document({
@ -138,5 +201,37 @@ function generateCoverLetter(rawText) {
} }
// Route to download cover letter
router.post('/download-cover-letter', async (req, res) => {
try {
const { content } = req.body;
const docBuffer = await generateDocx(content);
res.setHeader('Content-Disposition', 'attachment; filename="cover_letter.docx"');
res.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document');
res.send(docBuffer);
} catch (error) {
console.error(error);
res.status(500).json({ error: 'Error generating document' });
}
});
// Route to download tailored resume
router.post('/download-resume', async (req, res) => {
try {
const { content } = req.body;
const docBuffer = await generateDocx(content);
res.setHeader('Content-Disposition', 'attachment; filename="tailored_resume.docx"');
res.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document');
res.send(docBuffer);
} catch (error) {
console.error(error);
res.status(500).json({ error: 'Error generating document' });
}
});
module.exports = router; module.exports = router;