Compare commits
4 commits
2d984aa0e5
...
7a072445f4
Author | SHA1 | Date | |
---|---|---|---|
![]() |
7a072445f4 | ||
![]() |
1d6a5f4950 | ||
![]() |
651cb015b3 | ||
![]() |
3e6fe42135 |
7 changed files with 241 additions and 56 deletions
131
package-lock.json
generated
131
package-lock.json
generated
|
@ -14,6 +14,7 @@
|
|||
"docx": "^9.1.1",
|
||||
"dotenv": "^16.4.7",
|
||||
"express": "^4.21.2",
|
||||
"mammoth": "^1.9.0",
|
||||
"multer": "^1.4.5-lts.1",
|
||||
"pdf-parse": "^1.1.1",
|
||||
"tailwindcss": "^4.0.6"
|
||||
|
@ -579,6 +580,15 @@
|
|||
"form-data": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@xmldom/xmldom": {
|
||||
"version": "0.8.10",
|
||||
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.10.tgz",
|
||||
"integrity": "sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/abort-controller": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
|
||||
|
@ -648,6 +658,15 @@
|
|||
"integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/argparse": {
|
||||
"version": "1.0.10",
|
||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
|
||||
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"sprintf-js": "~1.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/array-flatten": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
|
||||
|
@ -660,6 +679,32 @@
|
|||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/base64-js": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
|
||||
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/bluebird": {
|
||||
"version": "3.4.7",
|
||||
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.4.7.tgz",
|
||||
"integrity": "sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/body-parser": {
|
||||
"version": "1.20.3",
|
||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz",
|
||||
|
@ -995,6 +1040,12 @@
|
|||
"node": ">=0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/dingbat-to-unicode": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dingbat-to-unicode/-/dingbat-to-unicode-1.0.1.tgz",
|
||||
"integrity": "sha512-98l0sW87ZT58pU4i61wa2OHwxbiYSbuxsCBozaVnYX2iCnr3bLM3fIes1/ej7h1YdOKuKt/MLs706TVnALA65w==",
|
||||
"license": "BSD-2-Clause"
|
||||
},
|
||||
"node_modules/docx": {
|
||||
"version": "9.1.1",
|
||||
"resolved": "https://registry.npmjs.org/docx/-/docx-9.1.1.tgz",
|
||||
|
@ -1039,6 +1090,15 @@
|
|||
"url": "https://dotenvx.com"
|
||||
}
|
||||
},
|
||||
"node_modules/duck": {
|
||||
"version": "0.1.12",
|
||||
"resolved": "https://registry.npmjs.org/duck/-/duck-0.1.12.tgz",
|
||||
"integrity": "sha512-wkctla1O6VfP89gQ+J/yDesM0S7B7XLXjKGzXxMDVFg7uEn706niAtyYovKbyq1oT9YwDcly721/iUWoc8MVRg==",
|
||||
"license": "BSD",
|
||||
"dependencies": {
|
||||
"underscore": "^1.13.1"
|
||||
}
|
||||
},
|
||||
"node_modules/dunder-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||
|
@ -1816,6 +1876,41 @@
|
|||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lop": {
|
||||
"version": "0.4.2",
|
||||
"resolved": "https://registry.npmjs.org/lop/-/lop-0.4.2.tgz",
|
||||
"integrity": "sha512-RefILVDQ4DKoRZsJ4Pj22TxE3omDO47yFpkIBoDKzkqPRISs5U1cnAdg/5583YPkWPaLIYHOKRMQSvjFsO26cw==",
|
||||
"license": "BSD-2-Clause",
|
||||
"dependencies": {
|
||||
"duck": "^0.1.12",
|
||||
"option": "~0.2.1",
|
||||
"underscore": "^1.13.1"
|
||||
}
|
||||
},
|
||||
"node_modules/mammoth": {
|
||||
"version": "1.9.0",
|
||||
"resolved": "https://registry.npmjs.org/mammoth/-/mammoth-1.9.0.tgz",
|
||||
"integrity": "sha512-F+0NxzankQV9XSUAuVKvkdQK0GbtGGuqVnND9aVf9VSeUA82LQa29GjLqYU6Eez8LHqSJG3eGiDW3224OKdpZg==",
|
||||
"license": "BSD-2-Clause",
|
||||
"dependencies": {
|
||||
"@xmldom/xmldom": "^0.8.6",
|
||||
"argparse": "~1.0.3",
|
||||
"base64-js": "^1.5.1",
|
||||
"bluebird": "~3.4.0",
|
||||
"dingbat-to-unicode": "^1.0.1",
|
||||
"jszip": "^3.7.1",
|
||||
"lop": "^0.4.2",
|
||||
"path-is-absolute": "^1.0.0",
|
||||
"underscore": "^1.13.1",
|
||||
"xmlbuilder": "^10.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"mammoth": "bin/mammoth"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/math-intrinsics": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||
|
@ -2069,6 +2164,12 @@
|
|||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/option": {
|
||||
"version": "0.2.4",
|
||||
"resolved": "https://registry.npmjs.org/option/-/option-0.2.4.tgz",
|
||||
"integrity": "sha512-pkEqbDyl8ou5cpq+VsnQbe/WlEy5qS7xPzMS1U55OCG9KPvwFD46zDbxQIj3egJSFc3D+XhYOPUzz49zQAVy7A==",
|
||||
"license": "BSD-2-Clause"
|
||||
},
|
||||
"node_modules/pako": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
|
||||
|
@ -2084,6 +2185,15 @@
|
|||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/path-is-absolute": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
|
||||
"integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/path-to-regexp": {
|
||||
"version": "0.1.12",
|
||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz",
|
||||
|
@ -2392,6 +2502,12 @@
|
|||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/sprintf-js": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
|
||||
"integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/statuses": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
|
||||
|
@ -2540,6 +2656,12 @@
|
|||
"integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/underscore": {
|
||||
"version": "1.13.7",
|
||||
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.7.tgz",
|
||||
"integrity": "sha512-GMXzWtsc57XAtguZgaQViUOzs0KTkk8ojr3/xAxXLITqf/3EMwxC0inyETfDFjH/Krbhuep0HNbbjI9i/q3F3g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "5.26.5",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
|
||||
|
@ -2640,6 +2762,15 @@
|
|||
"xml-js": "bin/cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/xmlbuilder": {
|
||||
"version": "10.1.1",
|
||||
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-10.1.1.tgz",
|
||||
"integrity": "sha512-OyzrcFLL/nb6fMGHbiRDuPup9ljBycsdCypwuyg5AAHvyWzGfChJpCXMG88AGTIMFhGZ9RccFN1e6lhg3hkwKg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/xtend": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
"docx": "^9.1.1",
|
||||
"dotenv": "^16.4.7",
|
||||
"express": "^4.21.2",
|
||||
"mammoth": "^1.9.0",
|
||||
"multer": "^1.4.5-lts.1",
|
||||
"pdf-parse": "^1.1.1",
|
||||
"tailwindcss": "^4.0.6"
|
||||
|
@ -14,9 +15,9 @@
|
|||
"description": "",
|
||||
"main": "server.js",
|
||||
"scripts": {
|
||||
"start": "node server.js",
|
||||
"build:css": "npx @tailwindcss/cli -i ./public/css/base_styles.css -o ./public/css/styles.css --watch",
|
||||
"dev": "concurrently \"npm run build:css\" \"npm run start\""
|
||||
"start": "node server.js",
|
||||
"build:css": "npx @tailwindcss/cli -i ./public/css/base_styles.css -o ./public/css/styles.css --watch",
|
||||
"dev": "concurrently \"npm run build:css\" \"npm run start\""
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
|
|
|
@ -10,3 +10,9 @@
|
|||
#coverLetterSection {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@layer base {
|
||||
textarea {
|
||||
resize: auto;
|
||||
}
|
||||
}
|
|
@ -521,8 +521,15 @@
|
|||
.static {
|
||||
position: static;
|
||||
}
|
||||
.block {
|
||||
display: block;
|
||||
.grid {
|
||||
display: grid;
|
||||
}
|
||||
.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
.text-2xl {
|
||||
font-size: var(--text-2xl);
|
||||
line-height: var(--tw-leading, var(--text-2xl--line-height));
|
||||
}
|
||||
.outline {
|
||||
outline-style: var(--tw-outline-style);
|
||||
|
@ -535,6 +542,11 @@
|
|||
#coverLetterSection {
|
||||
display: none;
|
||||
}
|
||||
@layer base {
|
||||
textarea {
|
||||
resize: auto;
|
||||
}
|
||||
}
|
||||
@keyframes spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
|
|
|
@ -4,20 +4,23 @@
|
|||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>AI Cover Letter Generator</title>
|
||||
<link rel="stylesheet" href="css/styles.css">
|
||||
<!-- <link rel="stylesheet" href="css/styles.css"> -->
|
||||
<!-- Used for Testing purposes only -->
|
||||
<link rel="stylesheet" href="css/base_styles.css">
|
||||
</head>
|
||||
<body>
|
||||
<h1>Generate Your Cover Letter</h1>
|
||||
<h1 class="text-2xl text-center">Generate Your Cover Letter</h1>
|
||||
<br>
|
||||
<form id="uploadForm" enctype="multipart/form-data">
|
||||
<label for="resume">Upload Resume (pdf or docx):</label>
|
||||
<input type="file" id="resume" name="resume" accept=".txt,.pdf,.docx" required><br><br>
|
||||
<input type="file" id="resume" name="resume" accept=".pdf,.docx,.doc" required><br><br>
|
||||
<button type="submit" id="generateBtn">Read Resume</button>
|
||||
</form>
|
||||
|
||||
<div id="resumePreviewSection">
|
||||
<h3>Extracted Resume Text:</h3>
|
||||
<textarea id="resumeTextOutput" rows="10"></textarea>
|
||||
<h3>AI-Generated Candidate Profile:</h3>
|
||||
<p>Displayed in JSON format. Edit as needed.</p>
|
||||
<textarea id="profileJson" rows="10"></textarea>
|
||||
<label for="jobDescription">Paste Job Description:</label>
|
||||
<textarea id="jobDescription" rows="5" required></textarea>
|
||||
<label for="keyPoints">Key Points for Letter (optional):</label>
|
||||
|
@ -28,7 +31,7 @@
|
|||
<!-- Hidden To Start With -->
|
||||
<div id="coverLetterSection">
|
||||
<h3>Generated Cover Letter:</h3>
|
||||
<textarea id="coverLetterOutput" rows="10"></textarea>
|
||||
<textarea id="coverLetterOutput" rows="15" cols="80"></textarea>
|
||||
<button id="downloadBtn">Download as DOCX</button>
|
||||
</div>
|
||||
<!-- End Hidden Section -->
|
||||
|
|
|
@ -2,14 +2,14 @@ document.getElementById('uploadForm').addEventListener('submit', async function
|
|||
event.preventDefault();
|
||||
|
||||
const resumePreviewSection = document.getElementById('resumePreviewSection');
|
||||
const resumeTextPreview = document.getElementById("resumeTextOutput")
|
||||
const generateBtn = document.getElementById('generateCoverLetterBtn');
|
||||
const profileJson = document.getElementById('profileJson');
|
||||
|
||||
|
||||
|
||||
generateBtn.disabled = true;
|
||||
resumePreviewSection.value = ""; //This clear any previous generated output
|
||||
resumePreviewSection.style.display = "grid";
|
||||
profileJson.textContent = "Parsing Resume....."
|
||||
|
||||
const fileInput = document.getElementById('resume');
|
||||
const file = fileInput.files[0];
|
||||
|
@ -28,13 +28,14 @@ document.getElementById('uploadForm').addEventListener('submit', async function
|
|||
body: formData,
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
if (data.error) {
|
||||
alert("Error: " + data.error)
|
||||
} else {
|
||||
resumeTextPreview.value = data.extractedText;
|
||||
generateBtn.disabled = false;
|
||||
}
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json();
|
||||
throw new Error(errorData.error || 'Failed to process the resume.');
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
generateBtn.disabled = false;
|
||||
profileJson.textContent = JSON.stringify(data.candidateProfile, null, 2);
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
alert('Something went wrong. Please try again.');
|
||||
|
@ -45,12 +46,12 @@ document.getElementById('uploadForm').addEventListener('submit', async function
|
|||
|
||||
// Send Resume and Job Description to Generate Cover Letter
|
||||
document.getElementById("generateCoverLetterBtn").addEventListener("click", async function () {
|
||||
const extractedResumeText = document.getElementById("resumeTextOutput").value;
|
||||
const candidateProfile = document.getElementById("profileJson").value;
|
||||
const jobDescription = document.getElementById("jobDescription").value;
|
||||
const keyPoints = document.getElementById("keyPoints").value;
|
||||
const generateBtn = document.getElementById("generateCoverLetterBtn")
|
||||
|
||||
if (!extractedResumeText.trim()) {
|
||||
if (!candidateProfile.trim()) {
|
||||
alert("Please confirm the extracted resume text.");
|
||||
return;
|
||||
}
|
||||
|
@ -61,7 +62,7 @@ document.getElementById("generateCoverLetterBtn").addEventListener("click", asyn
|
|||
}
|
||||
|
||||
const requestData = {
|
||||
extractedResumeText,
|
||||
candidateProfile,
|
||||
jobDescription,
|
||||
keyPoints,
|
||||
};
|
||||
|
@ -79,7 +80,8 @@ document.getElementById("generateCoverLetterBtn").addEventListener("click", asyn
|
|||
if (data.error) {
|
||||
alert("Error: " + data.error);
|
||||
} else {
|
||||
document.getElementById("coverLetterOutput").innerText = data.coverLetter;
|
||||
const formattedText = formatCoverLetter(data.coverLetter);
|
||||
document.getElementById("coverLetterOutput").value = formattedText;
|
||||
document.getElementById("coverLetterSection").style.display = "grid"; // Show cover letter section
|
||||
generateBtn.textContent = "Generate New Cover Letter"
|
||||
}
|
||||
|
@ -120,3 +122,15 @@ document.getElementById('downloadBtn').addEventListener('click', async function
|
|||
alert('Failed to download document.');
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
function formatCoverLetter(rawText) {
|
||||
return rawText
|
||||
.replace(/<\/header>/g, '')
|
||||
.replace(/<\/greeting>/g, '')
|
||||
.replace(/<\/introduction>/g, '')
|
||||
.replace(/<\/body>/g, '')
|
||||
.replace(/<\/conclusion>/g, '')
|
||||
.replace(/<\/signature>/g, '')
|
||||
.replace(/<[^>]+>/g, ''); // Remove all XML-like tags
|
||||
}
|
|
@ -20,10 +20,38 @@ const anthropic = new Anthropic({
|
|||
// Extract resume text and return it for preview
|
||||
router.post('/extract-resume', upload.single('resume'), async (req, res) => {
|
||||
try {
|
||||
const resumeBuffer = req.file.buffer;
|
||||
const extractedResumeText = await pdf(resumeBuffer);
|
||||
if (!req.file) {
|
||||
return res.status(400).json({ error: 'No file uploaded' });
|
||||
}
|
||||
let extractedText = '';
|
||||
const fileBuffer = req.file.buffer;
|
||||
const fileType = req.file.mimetype;
|
||||
|
||||
if (fileType === 'application/pdf') {
|
||||
const pdfData = await pdf(fileBuffer);
|
||||
extractedText = pdfData.text;
|
||||
} else if (
|
||||
fileType === 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
|
||||
) {
|
||||
const result = await mammoth.extractRawText({ buffer: fileBuffer });
|
||||
extractedText = result.value;
|
||||
} else {
|
||||
return res.status(400).json({ error: 'Unsupported file type' });
|
||||
}
|
||||
// Load Resume Parser API Prompt Template
|
||||
const resume_parser_api = require('../data/resume_parser_api.json');
|
||||
|
||||
// Replace placeholder with extracted resume text
|
||||
resume_parser_api.messages[0].content[0].text = resume_parser_api.messages[0].content[0].text.replace('{{resume}}', extractedText);
|
||||
|
||||
// Send the extracted resume text to AI model
|
||||
const resumeResponse = await anthropic.messages.create(resume_parser_api);
|
||||
|
||||
// Extract JSON-formatted profile from the response
|
||||
const candidateProfile = resumeResponse.content[0].text.split('```json')[1].split('```')[0].trim();
|
||||
|
||||
res.json({candidateProfile: JSON.parse(candidateProfile) });
|
||||
|
||||
res.json({ extractedText: extractedResumeText.text });
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
res.status(500).json({ error: 'Error extracting resume text' });
|
||||
|
@ -33,17 +61,10 @@ router.post('/extract-resume', upload.single('resume'), async (req, res) => {
|
|||
// Handle Resume upload and user input for Job Description
|
||||
router.post('/', async (req, res) => {
|
||||
try {
|
||||
const { extractedResumeText, jobDescription, keyPoints } = req.body;
|
||||
const { candidateProfile, jobDescription, keyPoints } = req.body;
|
||||
|
||||
const resume_parser_api = require('../data/resume_parser_api.json');
|
||||
const cover_letter_api = require('../data/cover_letter_api.json');
|
||||
|
||||
// Replace placeholder in API call
|
||||
resume_parser_api.messages[0].content[0].text = resume_parser_api.messages[0].content[0].text.replace("{{resume}}", extractedResumeText);
|
||||
|
||||
const resumeResponse = await anthropic.messages.create(resume_parser_api);
|
||||
const candidateProfile = resumeResponse.content[0].text.split('```json')[1].split('```')[0].trim();
|
||||
|
||||
cover_letter_api.messages[0].content[0].text = cover_letter_api.messages[0].content[0].text
|
||||
.replace('{{resume_json}}', candidateProfile)
|
||||
.replace('{{job_description}}', jobDescription)
|
||||
|
@ -79,27 +100,24 @@ router.post('/download', async (req, res) => {
|
|||
}
|
||||
});
|
||||
|
||||
function generateCoverLetter(rawText, outputFilename) {
|
||||
// Extract all sections in one go using an object
|
||||
const sections = ['header', 'greeting', 'introduction', 'body', 'conclusion', 'signature']
|
||||
.reduce((acc, section) => ({
|
||||
...acc,
|
||||
[section]: rawText.match(new RegExp(`<${section}>(.*?)<\/${section}>`, 's'))[1].trim()
|
||||
}), {});
|
||||
function generateCoverLetter(rawText) {
|
||||
if (!rawText || typeof rawText !== "string") {
|
||||
throw new Error("Invalid cover letter text provided.");
|
||||
}
|
||||
|
||||
// Create document with all sections
|
||||
// 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: Object.values(sections).map(text =>
|
||||
new Paragraph({
|
||||
children: [
|
||||
new TextRun(text),
|
||||
new TextRun("\n\n")
|
||||
]
|
||||
})
|
||||
)
|
||||
}]
|
||||
sections: [{ properties: {}, children: paragraphs }]
|
||||
});
|
||||
|
||||
return Packer.toBuffer(doc);
|
||||
|
|
Loading…
Reference in a new issue