Compare commits

..

No commits in common. "9ef73adffbe94e48f80b4b37365d3307d9c767f4" and "63c5e880ac67b44ae9730601268c3afce4626d6b" have entirely different histories.

3 changed files with 35 additions and 117 deletions

View file

@ -11,10 +11,10 @@
<br> <br>
<form id="uploadForm" enctype="multipart/form-data"> <form id="uploadForm" enctype="multipart/form-data">
<label for="resume">Upload Resume (pdf or docx):</label> <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" name="resume" accept=".txt,.pdf,.docx" required><br><br>
<label for="jobDescription">Paste Job Description:</label> <label for="jobDescription">Paste Job Description:</label>
<textarea id="jobDescription" name="jobDescription" rows="6" cols="50" required></textarea><br><br> <textarea name="jobDescription" rows="6" cols="50" required></textarea><br><br>
<button type="submit" id="generateBtn">Generate Cover Letter</button> <button type="submit" id="generateBtn">Generate Cover Letter</button>
</form> </form>

View file

@ -1,12 +1,13 @@
document.getElementById('uploadForm').addEventListener('submit', async function (event) { document.getElementById('uploadForm').addEventListener('submit', async function (event) {
event.preventDefault(); event.preventDefault();
const formData = new FormData(this);
const outputSection = document.getElementById('outputSection'); const outputSection = document.getElementById('outputSection');
const coverLetterOutput = document.getElementById('coverLetterOutput'); const coverLetterOutput = document.getElementById('coverLetterOutput');
const generateBtn = document.getElementById('generateBtn'); const generateBtn = document.getElementById('generateBtn');
const downloadBtn = document.getElementById('downloadBtn'); const downloadBtn = document.getElementById('downloadBtn');
generateBtn.disabled = true; generateBtn.disabled = true;
generateBtn.textContent = "Generating..."; generateBtn.textContent = "Generating...";
coverLetterOutput.value = ""; //This clear any previous generated output coverLetterOutput.value = ""; //This clear any previous generated output
@ -15,37 +16,11 @@ document.getElementById('uploadForm').addEventListener('submit', async function
outputSection.style.display = "block"; outputSection.style.display = "block";
coverLetterOutput.value = "Generating cover letter..."; 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 { try {
const response = await fetch('/generate', { const response = await fetch('/generate', {
method: "POST", method: 'POST',
body: formData, body: formData
}); });
const result = await response.json(); const result = await response.json();
if (result.coverLetter) { if (result.coverLetter) {
@ -66,34 +41,25 @@ 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; const coverLetterText = document.getElementById('coverLetterOutput').value;
try { try {
const response = await fetch('/generate/download', { const response = await fetch('/generate/download', {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ coverLetterText }) body: JSON.stringify({ coverLetterText })
}); });
if (!response.ok) throw new Error('Failed to download document.'); const result = await response.json();
if (result.downloadLink) {
// Convert response to blob window.location.href = result.downloadLink;
const blob = await response.blob(); } else {
const url = window.URL.createObjectURL(blob); alert('Error downloading document.')
}
// 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) { } catch (error) {
console.error('Error:', error); console.error('Error:', error);
alert('Failed to download document.'); alert('Failed to download document.')
} }
}); });

View file

@ -3,13 +3,17 @@ const express = require('express');
const multer = require('multer'); const multer = require('multer');
const pdf = require('pdf-parse'); const pdf = require('pdf-parse');
const { Document, Packer, Paragraph, TextRun } = require('docx'); const { Document, Packer, Paragraph, TextRun } = require('docx');
const fs = require('fs');
const promises = require('fs/promises');
const Anthropic = require('@anthropic-ai/sdk'); const Anthropic = require('@anthropic-ai/sdk');
const path = require('path');
const { runInNewContext } = require('vm');
// Load Environment Variables // Load Environment Variables
require('dotenv').config(); require('dotenv').config();
const router = express.Router(); const router = express.Router();
const upload = multer({ storage: multer.memoryStorage() }); const upload = multer({ dest: 'uploads/'});
// initialize LLM // initialize LLM
//TODO Update to have User Provide their own key //TODO Update to have User Provide their own key
@ -21,27 +25,21 @@ const anthropic = new Anthropic({
// Handle Resume upload and user input for Job Description // Handle Resume upload and user input for Job Description
router.post('/', upload.single('resume'), async (req, res) =>{ router.post('/', upload.single('resume'), async (req, res) =>{
try{ 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; 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 // Load the LLM APi Messages
// These include placeholders to be replaced later in function // These include placeholders to be replaced later in function
const resume_parser_api = require('../data/resume_parser_api.json'); const resume_parser_api = require('../data/resume_parser_api.json');
const cover_letter_api = require('../data/cover_letter_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 // 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}}", resumeText); resume_parser_api.messages[0].content[0].text = resume_parser_api.messages[0].content[0].text.replace("{{resume}}", extractedResumeText.text);
// Send resume to LLM // Send resume to LLM
const resumeResponse = await anthropic.messages.create(resume_parser_api); const resumeResponse = await anthropic.messages.create(resume_parser_api);
@ -66,7 +64,7 @@ router.post('/', upload.single('resume'), async (req, res) =>{
router.post('/download', async (req, res) => { router.post('/download', async (req, res) => {
try { try {
const { coverLetterText } = req.body; const { coverLetterText } = res.body;
const outputFilename = path.join(__dirname, '../uploads/cover_letter.docx'); const outputFilename = path.join(__dirname, '../uploads/cover_letter.docx');
generateCoverLetter(coverLetterText, outputFilename); generateCoverLetter(coverLetterText, outputFilename);
@ -79,56 +77,10 @@ router.post('/download', async (req, res) => {
}); });
function generateCoverLetter(rawText, outputFilename) { function generateCoverLetter(rawText, outputFilename) {
const header = rawText.match(/<header>(.*?)<\/header>/s)[1].trim();
const greeting = rawText.match(/<greeting>(.*?)<\/greeting>/s)[1].trim();
const introduction = rawText.match(/<introduction>(.*?)<\/introduction>/s)[1].trim();
const body = rawText.match(/<body>(.*?)<\/body>/s)[1].trim();
const conclusion = rawText.match(/<conclusion>(.*?)<\/conclusion>/s)[1].trim();
const signature = rawText.match(/<signature>(.*?)<\/signature>/s)[1].trim();
// Create a new document using docx
const doc = new Document({ const doc = new Document({
sections: [ sections: [
{ {
properties: {}, children: [new Paragraph({ children: [new TextRun(rawText)] })],
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"),
],
}),
],
}, },
], ],
}); });