diff --git a/public/index.html b/public/index.html
index 9cd6051..258f46a 100644
--- a/public/index.html
+++ b/public/index.html
@@ -4,20 +4,23 @@
-
Extracted Resume Text:
-
+
AI-Generated Candidate Profile:
+
Displayed in JSON format. Edit as needed.
+
@@ -28,7 +31,7 @@
Generated Cover Letter:
-
+
diff --git a/public/js/script.js b/public/js/script.js
index cfacbee..ede7cc9 100644
--- a/public/js/script.js
+++ b/public/js/script.js
@@ -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
+ generateBtn.disabled = true;
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"
}
@@ -119,4 +121,16 @@ document.getElementById('downloadBtn').addEventListener('click', async function
console.error('Error:', error);
alert('Failed to download document.');
}
-});
\ No newline at end of file
+});
+
+
+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
+}
\ No newline at end of file
diff --git a/routes/generate.js b/routes/generate.js
index d8ec650..bb55c5e 100644
--- a/routes/generate.js
+++ b/routes/generate.js
@@ -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);
- res.json({ extractedText: extractedResumeText.text });
+ // 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) });
+
} 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);