使用 React、NodeJS 和 AI 申请新工作
TL;DR
TL;DR
在本系列的上一篇文章中,我向您介绍了如何构建一个简历生成器应用程序,该应用程序接受来自用户的一些特定信息并创建可打印的简历。

在本文中,我们将进一步完善该应用程序,添加冷电子邮件功能,允许用户使用 OpenAI API 和 EmailJS发送包含成功求职信和简历的求职申请。
你为什么需要它?
过去十年我一直是一名程序员,
但当需要在纸面上(营销)展示我的技能时,
我并不适合做程序员。
我以前因为简历而被很多工作拒之门外(我甚至还没参加过面试)。今天,我们将用 GPT3 来改变这种状况 🙌🏻

项目设置和安装
在此处克隆该项目的 GitHub 存储库。
运行npm install
以安装项目的依赖项。
在此登录或创建 OpenAI 帐户 。
点击Personal
导航栏,View API
从菜单栏中选择密钥来创建一个新的密钥。
在文件中添加您的 OpenAI API 密钥index.js
。
创建一个名为SendResume.js
- 的组件,用户将在其中提供发送冷电子邮件所需的数据。
cd client/src/components
touch SendResume.js
SendResume
使用 React Router 通过其自己的路由渲染组件。
import React, { useState } from "react";
import { BrowserRouter, Routes, Route } from "react-router-dom";
import Home from "./components/Home";
import Resume from "./components/Resume";
//👇🏻 imports the component
import SendResume from "./components/SendResume";
const App = () => {
const [result, setResult] = useState({});
return (
<div>
<BrowserRouter>
<Routes>
<Route path='/' element={<Home setResult={setResult} />} />
<Route path='/resume' element={<Resume result={result} />} />
{/*-- displays the component --*/}
<Route path='/send/resume' element={<SendResume />} />
</Routes>
</BrowserRouter>
</div>
);
};
export default App;
更新Home.js
组件以呈现一个链接,将用户导航到SendResume
页面顶部的组件。
const Home = () => {
//...other code statements
return (
<>
<div className='buttonGroup'>
<button onClick={handlePrint}>Print Resume</button>
<Link to='/send/resume' className='sendEmail'>
Send via Email
</Link>
</div>
{/*--- other UI elements ---*/}
</>
);
};
将下面的代码片段添加到src/index.css
文件中。
.buttonGroup {
padding: 20px;
width: 60%;
margin: 0 auto;
display: flex;
align-items: center;
justify-content: space-between;
position: sticky;
top: 0;
background-color: #fff;
}
.sendEmail {
background-color: #f99417;
padding: 20px;
text-decoration: none;
border-radius: 3px;
}
.resume__title {
margin-bottom: 30px;
}
.companyDescription {
padding: 15px;
border: 1px solid #e8e2e2;
border-radius: 3px;
margin-bottom: 15px;
}
.sendEmailBtn {
width: 200px;
}
.nestedItem {
display: flex;
flex-direction: column;
width: 50%;
}
.nestedItem > input {
width: 95%;
}
构建应用程序用户界面
更新SendResume
组件以显示所需的表单字段,如下所示。
import React, { useState } from "react";
const SendResume = () => {
const [companyName, setCompanyName] = useState("");
const [jobTitle, setJobTitle] = useState("");
const [companyDescription, setCompanyDescription] = useState("");
const [recruiterName, setRecruiterName] = useState("");
const [recruiterEmail, setRecruiterEmail] = useState("");
const [myEmail, setMyEmail] = useState("");
const [resume, setResume] = useState(null);
const handleFormSubmit = (e) => {
e.preventDefault();
console.log("Submit button clicked!");
};
return (
<div className='app'>
<h1 className='resume__title'>Send an email</h1>
<form onSubmit={handleFormSubmit} encType='multipart/form-data'>
<div className='nestedContainer'>
<div className='nestedItem'>
<label htmlFor='recruiterName'>Recruiter's Name</label>
<input
type='text'
value={recruiterName}
required
onChange={(e) => setRecruiterName(e.target.value)}
id='recruiterName'
className='recruiterName'
/>
</div>
<div className='nestedItem'>
<label htmlFor='recruiterEmail'>Recruiter's Email Address</label>
<input
type='email'
value={recruiterEmail}
required
onChange={(e) => setRecruiterEmail(e.target.value)}
id='recruiterEmail'
className='recruiterEmail'
/>
</div>
</div>
<div className='nestedContainer'>
<div className='nestedItem'>
<label htmlFor='myEmail'>Your Email Address </label>
<input
type='email'
value={myEmail}
required
onChange={(e) => setMyEmail(e.target.value)}
id='myEmail'
className='myEmail'
/>
</div>
<div className='nestedItem'>
<label htmlFor='jobTitle'>Position Applying For</label>
<input
type='text'
value={jobTitle}
required
onChange={(e) => setJobTitle(e.target.value)}
id='jobTitle'
className='jobTitle'
/>
</div>
</div>
<label htmlFor='companyName'>Company Name</label>
<input
type='text'
value={companyName}
required
onChange={(e) => setCompanyName(e.target.value)}
id='companyName'
className='companyName'
/>
<label htmlFor='companyDescription'>Company Description</label>
<textarea
rows={5}
className='companyDescription'
required
value={companyDescription}
onChange={(e) => setCompanyDescription(e.target.value)}
/>
<label htmlFor='resume'>Upload Resume</label>
<input
type='file'
accept='.pdf, .doc, .docx'
required
id='resume'
className='resume'
onChange={(e) => setResume(e.target.files[0])}
/>
<button className='sendEmailBtn'>SEND EMAIL</button>
</form>
</div>
);
};
export default SendResume;
该代码片段接受公司名称和描述、职位名称、招聘人员姓名、用户电子邮件和简历。它仅接受 PDF 或 Word 格式的简历。
所有这些数据对于创建一份出色且量身定制的求职信都是必需的。在接下来的部分中,我将指导您生成求职信并通过电子邮件发送。
将Loading
组件和 React Router 的useNavigate
钩子导入到SendResume.js
文件中。
import Loading from "./Loading";
import { useNavigate } from "react-router-dom";
const SendResume = () => {
const [loading, setLoading] = useState(false);
const navigate = useNavigate();
if (loading) {
return <Loading />;
}
//...other code statements
};
上面的代码片段在 POST 请求仍处于挂起状态时显示“正在加载”页面。请继续往下看,我会逐步讲解其中的逻辑。
更新handleFormSubmit
函数以将所有表单输入发送到 Node.js 服务器。
const handleFormSubmit = (e) => {
e.preventDefault();
//👇🏻 form object
const formData = new FormData();
formData.append("resume", resume, resume.name);
formData.append("companyName", companyName);
formData.append("companyDescription", companyDescription);
formData.append("jobTitle", jobTitle);
formData.append("recruiterEmail", recruiterEmail);
formData.append("recruiterName", recruiterName);
formData.append("myEmail", myEmail);
//👇🏻 imported function
sendResume(formData, setLoading, navigate);
//👇🏻 states update
setMyEmail("");
setRecruiterEmail("");
setRecruiterName("");
setJobTitle("");
setCompanyName("");
setCompanyDescription("");
setResume(null);
};
- 从上面的代码片段来看,
- 我将所有表单输入(文件和文本)添加到 JavaScript FormData 对象中。
- 该
sendResume
函数接受表单数据、setLoading
和navigate
变量作为参数。接下来,让我们创建该函数。
创建一个文件夹,其中utils
包含一个文件。util.js
client/src
cd client/src
mkdir utils
cd utils
touch util.js
如下所示在文件sendResume
中创建函数。我们将在函数中配置 POST 请求。util.js
sendResume
const sendResume = (formData, setLoading, navigate) => {};
export default sendResume;
导入Axios(已安装)并将表单数据发送到 Node.js 服务器,如下所示。
import axios from "axios";
const sendResume = (formData, setLoading, navigate) => {
setLoading(true);
axios
.post("http://localhost:4000/resume/send", formData, {})
.then((res) => {
console.log("Response", res);
})
.catch((err) => console.error(err));
};
该函数将加载状态设置为 true,在请求待处理时显示加载组件,然后向服务器上的端点发出 POST 请求。
如下所示在服务器上创建端点。
app.post("/resume/send", upload.single("resume"), async (req, res) => {
const {
recruiterName,
jobTitle,
myEmail,
recruiterEmail,
companyName,
companyDescription,
} = req.body;
//👇🏻 log the contents
console.log({
recruiterName,
jobTitle,
myEmail,
recruiterEmail,
companyName,
companyDescription,
resume: `http://localhost:4000/uploads/${req.file.filename}`,
});
});
上面的代码片段接受来自前端的数据,并通过Multer将简历上传到服务器。resume
该对象的 属性包含服务器上简历的 URL。
通过 OpenAI API 生成求职信
在上一篇文章中,我们接受了用户的工作经历、姓名和熟练技术列表。将这些变量设为全局变量,以便/resume/send
端点能够访问其内容。
let workArray = [];
let applicantName = "";
let technologies = "";
app.post("/resume/create", upload.single("headshotImage"), async (req, res) => {
const {
fullName,
currentPosition,
currentLength,
currentTechnologies,
workHistory,
} = req.body;
workArray = JSON.parse(workHistory);
applicantName = fullName;
technologies = currentTechnologies;
//other code statements...
});
从代码片段来看,workArray
、applicantName
和technologies
是包含用户工作经历、全名和技能的全局变量。创建求职信时需要这些变量。
/resume/send
按照如下所示更新端点:
app.post("/resume/send", upload.single("resume"), async (req, res) => {
const {
recruiterName,
jobTitle,
myEmail,
recruiterEmail,
companyName,
companyDescription,
} = req.body;
const prompt = `My name is ${applicantName}. I want to work for ${companyName}, they are ${companyDescription}
I am applying for the job ${jobTitle}. I have been working before for: ${remainderText()}
And I have used the technologies such as ${technologies}
I want to cold email ${recruiterName} my resume and write why I fit for the company.
Can you please write me the email in a friendly voice, not offical? without subject, maximum 300 words and say in the end that my CV is attached.`;
const coverLetter = await GPTFunction(prompt);
res.json({
message: "Successful",
data: {
cover_letter: coverLetter,
recruiter_email: recruiterEmail,
my_email: myEmail,
applicant_name: applicantName,
resume: `http://localhost:4000/uploads/${req.file.filename}`,
},
});
});
- 从上面的代码片段来看,
- 我们解构了从 React 应用程序接受的所有数据,并使用该数据为 AI 创建了提示。
- 然后将提示传递到 OpenAI API 中,为用户生成出色的求职信。
- 然后,发送电子邮件所需的所有必要数据都会作为响应发送回 React 应用程序。
- resume 变量保存文档(简历)的 URL。
回想一下上一教程,ChatGPTFunction
接受提示并生成对请求的答案或响应。
const GPTFunction = async (text) => {
const response = await openai.createCompletion({
model: "text-davinci-003",
prompt: text,
temperature: 0.6,
max_tokens: 350,
top_p: 1,
frequency_penalty: 1,
presence_penalty: 1,
});
return response.data.choices[0].text;
};
在 React 中通过 EmailJS 发送电子邮件
在这里,我将指导您将 EmailJS 添加到 React.js 应用程序以及如何将 AI 生成的电子邮件发送给招聘人员。
通过运行以下代码将 EmailJS 安装到 React 应用程序:
npm install @emailjs/browser
在此创建一个 EmailJS 帐户 并将电子邮件服务提供商添加到您的帐户。
创建一个电子邮件模板,如下图所示:
花括号中的单词代表可以保存动态数据的变量。
将EmailJS包导入util.js
文件,并将邮件发送到招聘人员的邮箱。
import emailjs from "@emailjs/browser";
axios
.post("http://localhost:4000/resume/send", formData, {})
.then((res) => {
if (res.data.message) {
const {
cover_letter,
recruiter_email,
my_email,
applicant_name,
resume,
} = res.data.data;
emailjs
.send(
"<SERVICE_ID>",
"<TEMPLATE_ID>",
{
cover_letter,
applicant_name,
recruiter_email,
my_email,
resume,
},
"<YOUR_PUBLIC KEY>"
)
.then((res) => {
if (res.status === 200) {
setLoading(false);
alert("Message sent!");
navigate("/");
}
})
.catch((err) => console.error(err));
}
})
.catch((err) => console.error(err));
代码片段检查请求是否成功;如果成功,它会对响应中的数据进行解构,并向招聘人员发送包含求职信和简历的电子邮件。
EmailJS 仪表板上模板中创建的变量将被传入函数的数据替换send
。然后,用户将被重定向到主页,并收到电子邮件已发送的通知。
恭喜!您已完成本教程的项目。
以下是已发送的电子邮件示例:
结论
到目前为止,在本系列中,您已经学习了:
- OpenAI GPT-3 是什么
- 如何在 Node.js 和 React.js 应用程序中通过表单上传图像,
- 如何与 OpenAI GPT-3 API 交互,
- 如何通过 React-to-print 库打印 React 网页,以及
- 如何在 React 中通过 EmailJS 发送电子邮件。
本教程将引导您了解一个使用 OpenAI API 构建的应用程序示例。您可以对该应用程序进行一些小升级,例如验证用户身份,并将简历以附件(而非 URL)的形式发送。
附言:通过 EmailJS 发送附件并非免费。您需要订阅付费计划。
本教程的源代码可以在这里找到:
https://github.com/novuhq/blog/tree/main/cold-emailing-with-react-ai
感谢您的阅读!
一个小请求🥺
我们是 Novu - 一项开源通知基础设施服务,可以管理您所有的通知渠道(电子邮件 / 短信 / 推送通知 / 聊天 / 应用内通知)。
您可以使用 Novu 在一个简单的仪表板中将所有渠道自动化。
我每周都会创作内容,您的支持对创作更多内容大有帮助。
请为我们的 GitHub 库点赞,支持我。
非常感谢!❤️❤️❤️
https://github.com/novuhq/novu
