File size: 10,009 Bytes
849c2f4 6eaaedd 849c2f4 6eaaedd 849c2f4 6eaaedd 849c2f4 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 |
import express from "express";
import multer from "multer";
import cors from "cors";
import * as fs from "fs";
import * as path from "path";
import { Request, Response, NextFunction } from "express";
import pdfParse from "pdf-parse";
import OpenAI from "openai";
import dotenv from "dotenv";
import { getRecommendations } from "../utils/parseResume";
dotenv.config();
// Initialize OpenAI client
const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY, // Make sure to set this in your environment
});
interface ParsedData {
skills: string[];
experience: string[];
education: string[];
}
// Add type declaration for Multer request
interface MulterRequest extends Request {
file: Express.Multer.File;
}
interface QuestionnaireData {
currentStatus: "student" | "unemployed";
interimRole: boolean;
dreamRole: string;
motivation: string;
learningPreference: "videos" | "projects" | "reading" | "all";
timeCommitment: "1-2" | "2-4" | "4+";
flexibleForInterim: boolean;
challenges: string[];
timeframe: "6months" | "12months" | "2years" | "flexible";
}
const app = express();
// Configure CORS
app.use(
cors({
origin: "*", // Your frontend URL
methods: ["POST", "GET", "OPTIONS"],
allowedHeaders: ["Content-Type"],
})
);
// Create uploads directory if it doesn't exist
if (!fs.existsSync("/tmp/uploads")) {
fs.mkdirSync("/tmp/uploads");
}
// Configure multer for file uploads
const upload = multer({
dest: "/tmp/uploads/",
fileFilter: (req, file, cb) => {
if (file.mimetype === "application/pdf") {
cb(null, true);
} else {
cb(null, false);
return cb(new Error("Only PDF files are allowed"));
}
},
});
// Import pdf-parse with options
const pdfParseOptions = {
// Ensure all pages are read
max: 0,
// Use a more robust page rendering
pagerender: function (pageData: any) {
const renderOptions = {
normalizeWhitespace: false,
disableCombineTextItems: false,
};
return pageData
.getTextContent(renderOptions)
.then(function (textContent: any) {
let lastY,
text = "";
for (let item of textContent.items) {
if (lastY == item.transform[5] || !lastY) {
text += item.str;
} else {
text += "\n" + item.str;
}
lastY = item.transform[5];
}
return text;
});
},
};
const parseResumeContent = (text: string) => {
console.log("Raw text from PDF:", text); // Debug log to see the raw text
// More flexible regex patterns
const skillsMatch = text.match(
/(?:SKILLS?|TECHNICAL SKILLS?)[:\s]+([\s\S]*?)(?=(?:EXPERIENCE|EDUCATION|WORK|EMPLOYMENT|PROFESSIONAL|$))/i
);
const experienceMatch = text.match(
/(?:EXPERIENCE|WORK|EMPLOYMENT|PROFESSIONAL)[:\s]+([\s\S]*?)(?=(?:EDUCATION|SKILLS?|$))/i
);
const educationMatch = text.match(
/(?:EDUCATION|ACADEMIC|QUALIFICATIONS)[:\s]+([\s\S]*?)(?=(?:EXPERIENCE|WORK|SKILLS?|$))/i
);
// Process skills
const skillsText = skillsMatch ? skillsMatch[1] : "";
const skillsList = skillsText
.split(/[,\n•]/) // Split by commas, newlines, or bullet points
.map((skill) => skill.trim())
.filter((skill) => skill.length > 2); // Filter out very short strings
// Process experience
const experienceText = experienceMatch ? experienceMatch[1] : "";
const experienceList = experienceText
.split(/(?:\r?\n){2,}/) // Split by multiple newlines
.map((exp) => exp.replace(/^\s*[•-]\s*/gm, "").trim()) // Remove bullet points
.filter((exp) => exp.length > 10); // Filter out short lines
// Process education
const educationText = educationMatch ? educationMatch[1] : "";
const educationList = educationText
.split(/(?:\r?\n){2,}/)
.map((edu) => edu.replace(/^\s*[•-]\s*/gm, "").trim())
.filter((edu) => edu.length > 10);
// Debug logs
console.log("Found sections:", {
skills: !!skillsMatch,
experience: !!experienceMatch,
education: !!educationMatch,
});
console.log("Parsed sections:", {
skillsCount: skillsList.length,
experienceCount: experienceList.length,
educationCount: educationList.length,
skillsList,
experienceList,
educationList,
});
return {
skills: skillsList,
experience: experienceList,
education: educationList,
};
};
const handleFileUpload = async (req: Request, res: Response): Promise<void> => {
let filePath = "";
try {
if (!req.file) {
res.status(400).json({ error: "No file uploaded" });
return;
}
const questionnaireData = JSON.parse(req.body.questionnaireData || "{}");
console.log("Received dream role:", questionnaireData.dreamRole);
filePath = req.file.path;
console.log("File received:", req.file);
const dataBuffer = fs.readFileSync(filePath);
const pdfData = await pdfParse(dataBuffer);
// Parse resume sections
const parsedData = parseResumeContent(pdfData.text);
console.log("Parsed resume data:", parsedData);
// Get recommendations using the parsed data
const recommendations = await getRecommendations(
parsedData,
questionnaireData.dreamRole,
questionnaireData
);
// Update the OpenAI prompt to specifically request benefits information
const completion = await openai.chat.completions.create({
model: "gpt-4o-mini", // or whichever model you're using
messages: [
{
role: "system",
content:
"You are a career advisor creating personalized course recommendations.",
},
{
role: "user",
content: `Create detailed course recommendations based on:
Skills: ${parsedData.skills.join(", ")}
Experience: ${parsedData.experience.join("\n")}
Education: ${parsedData.education.join("\n")}
Dream Role: ${questionnaireData.dreamRole}
Additional Personal Context:
- Current Status: ${questionnaireData.currentStatus}
- Interested in Interim Role: ${questionnaireData.interimRole}
- Motivation: ${questionnaireData.motivation}
- Preferred Learning Style: ${questionnaireData.learningPreference}
- Daily Time Commitment: ${questionnaireData.timeCommitment} hours
- Main Challenges: ${questionnaireData.challenges.join(", ")}
- Target Timeframe: ${questionnaireData.timeframe}
For each course, you MUST include:
- title: A specific course title
- description: Brief description of the course content
- platform: Where the course is offered (Coursera, Udemy, etc.)
- duration: How long it takes to complete
- level: Difficulty level
- link: URL to the course
- benefitsInterim: IMPORTANT - Specific benefits for an interim role
- benefitsDream: IMPORTANT - Specific benefits for the dream role
Return exactly this JSON structure:
{
"courses": [
{
"title": "Course Name",
"description": "Course Description",
"duration": "Duration",
"platform": "Platform Name",
"level": "Difficulty Level",
"link": "https://example.com",
"benefitsInterim": "Detailed explanation of how this helps with interim roles",
"benefitsDream": "Detailed explanation of how this helps with the dream role"
}
],
"roles": [
{
"title": "Role Title",
"description": "Role Description",
"timeline": "Timeline",
"salary": "Salary Range"
}
]
}`,
},
],
response_format: { type: "json_object" },
});
// Make sure we're properly handling the response
const result = JSON.parse(completion.choices[0].message.content || "{}");
// Validate that each course has the required fields
const validatedCourses = result.courses.map((course: any) => {
return {
title: course.title || "Course Title",
description: course.description || "No description available",
duration: course.duration || "Unknown duration",
platform: course.platform || "Online platform",
level: course.level || "Intermediate",
link: course.link || "#",
benefitsInterim:
course.benefitsInterim ||
"Builds foundational skills needed for entry-level positions",
benefitsDream:
course.benefitsDream ||
"Contributes to the skill set required for your dream role",
};
});
// Return the validated courses
res.json({
skills: parsedData.skills,
experience: parsedData.experience,
education: parsedData.education,
recommendations: {
courses: recommendations.courses || [],
roles: recommendations.roles || [],
},
});
} catch (error) {
console.error("Server Error:", error);
res.status(500).json({
error: "Failed to process resume",
details: error instanceof Error ? error.message : "Unknown error",
});
} finally {
// Clean up uploaded file
if (filePath && fs.existsSync(filePath)) {
fs.unlinkSync(filePath);
}
}
};
app.post("/api/parse-resume", upload.single("resume"), handleFileUpload);
app.get("/api/test", (req, res) => {
res.json({ message: "Hello, World!" });
});
const PORT = process.env.PORT || 3001;
app
.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
})
.on("error", (err: NodeJS.ErrnoException) => {
if (err.code === "EADDRINUSE") {
console.log(`Port ${PORT} is busy, trying ${PORT}...`);
app.listen(PORT);
} else {
console.error("Server error:", err);
process.exit(1);
}
});
|