diff --git a/public/js/script.js b/public/js/script.js index afeca25..961bc22 100644 --- a/public/js/script.js +++ b/public/js/script.js @@ -1,13 +1,12 @@ document.getElementById('uploadForm').addEventListener('submit', async function (event) { event.preventDefault(); - const formData = new FormData(this); const outputSection = document.getElementById('outputSection'); const coverLetterOutput = document.getElementById('coverLetterOutput'); const generateBtn = document.getElementById('generateBtn'); const downloadBtn = document.getElementById('downloadBtn'); - + generateBtn.disabled = true; generateBtn.textContent = "Generating..."; coverLetterOutput.value = ""; //This clear any previous generated output @@ -16,11 +15,37 @@ document.getElementById('uploadForm').addEventListener('submit', async function outputSection.style.display = "block"; coverLetterOutput.value = "Generating cover letter..."; + + const fileInput = document.getElementById('resume'); + const jobDescriptionInput = document.getElementById('jobDescription'); + + if (!fileInput || !jobDescriptionInput) { + console.error("Form elements not found."); + return; + } + + const file = fileInput.files[0]; + const jobDescription = jobDescriptionInput.value; + + if (!file) { + alert("Please upload a resume."); + return; + } + + if (!jobDescription.trim()) { + alert("Please enter a job description."); + return; + } + + const formData = new FormData(); + formData.append("resume", file); + formData.append("jobDescription", jobDescription); + try { const response = await fetch('/generate', { - method: 'POST', - body: formData - }); + method: "POST", + body: formData, + }); const result = await response.json(); if (result.coverLetter) { @@ -41,25 +66,34 @@ document.getElementById('uploadForm').addEventListener('submit', async function }); -document.getElementById('downloadBtn').addEventListener('click', async function() { +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 }) - }); + const response = await fetch('/generate/download', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ coverLetterText }) + }); - const result = await response.json(); - if (result.downloadLink) { - window.location.href = result.downloadLink; - } else { - alert('Error downloading document.') - } + 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.') + console.error('Error:', error); + alert('Failed to download document.'); } - }); \ No newline at end of file diff --git a/routes/generate.js b/routes/generate.js index 06dc51e..8ddc1e5 100644 --- a/routes/generate.js +++ b/routes/generate.js @@ -3,17 +3,13 @@ const express = require('express'); const multer = require('multer'); const pdf = require('pdf-parse'); const { Document, Packer, Paragraph, TextRun } = require('docx'); -const fs = require('fs'); -const promises = require('fs/promises'); const Anthropic = require('@anthropic-ai/sdk'); -const path = require('path'); -const { runInNewContext } = require('vm'); // Load Environment Variables require('dotenv').config(); const router = express.Router(); -const upload = multer({ dest: 'uploads/'}); +const upload = multer({ storage: multer.memoryStorage() }); // initialize LLM //TODO Update to have User Provide their own key @@ -25,21 +21,27 @@ const anthropic = new Anthropic({ // Handle Resume upload and user input for Job Description router.post('/', upload.single('resume'), async (req, res) =>{ try{ - const resumePath = req.file.path; + + if (!req.file || !req.body.jobDescription) { + return res.status(400).json({ error: 'Resume and Job Description are required' }); + } + + let resumeText = req.file.buffer.toString('utf-8'); const jobDescription = req.body.jobDescription; + if (req.file.mimetype === 'application/pdf') { + const pdfData = await pdf(req.file.buffer); + resumeText = pdfData.text; + } + // Load the LLM APi Messages // These include placeholders to be replaced later in function const resume_parser_api = require('../data/resume_parser_api.json'); const cover_letter_api = require('../data/cover_letter_api.json'); - // Extract Data from PDF (includes text and meta data if needed for later development) - const resumeData = await promises.readFile(resumePath); - const extractedResumeText = await pdf(resumeData); - // Replace placeholder in resume api with extracted text - resume_parser_api.messages[0].content[0].text = resume_parser_api.messages[0].content[0].text.replace("{{resume}}", extractedResumeText.text); + resume_parser_api.messages[0].content[0].text = resume_parser_api.messages[0].content[0].text.replace("{{resume}}", resumeText); // Send resume to LLM const resumeResponse = await anthropic.messages.create(resume_parser_api); @@ -64,7 +66,7 @@ router.post('/', upload.single('resume'), async (req, res) =>{ router.post('/download', async (req, res) => { try { - const { coverLetterText } = res.body; + const { coverLetterText } = req.body; const outputFilename = path.join(__dirname, '../uploads/cover_letter.docx'); generateCoverLetter(coverLetterText, outputFilename); @@ -77,10 +79,56 @@ router.post('/download', async (req, res) => { }); function generateCoverLetter(rawText, outputFilename) { + const header = rawText.match(/
(.*?)<\/header>/s)[1].trim(); + const greeting = rawText.match(/(.*?)<\/greeting>/s)[1].trim(); + const introduction = rawText.match(/(.*?)<\/introduction>/s)[1].trim(); + const body = rawText.match(/(.*?)<\/body>/s)[1].trim(); + const conclusion = rawText.match(/(.*?)<\/conclusion>/s)[1].trim(); + const signature = rawText.match(/(.*?)<\/signature>/s)[1].trim(); + + // Create a new document using docx const doc = new Document({ sections: [ { - children: [new Paragraph({ children: [new TextRun(rawText)] })], + properties: {}, + children: [ + new Paragraph({ + children: [ + new TextRun(header), + new TextRun("\n\n"), // Add line breaks between sections + ], + }), + new Paragraph({ + children: [ + new TextRun(greeting), + new TextRun("\n\n"), + ], + }), + new Paragraph({ + children: [ + new TextRun(introduction), + new TextRun("\n\n"), + ], + }), + new Paragraph({ + children: [ + new TextRun(body), + new TextRun("\n\n"), + ], + }), + new Paragraph({ + children: [ + new TextRun(conclusion), + new TextRun("\n\n"), + ], + }), + new Paragraph({ + children: [ + new TextRun(signature), + new TextRun("\n\n"), + ], + }), + ], }, ], });