使用 React、NodeJS 和 AI 申请新工作 TL;DR

2025-05-25

使用 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
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

更新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 ---*/}
        </>
    );
};
Enter fullscreen mode Exit fullscreen mode

代码片段

将下面的代码片段添加到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%;
}
Enter fullscreen mode Exit fullscreen mode

构建应用程序用户界面

更新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;
Enter fullscreen mode Exit fullscreen mode

该代码片段接受公司名称和描述、职位名称、招聘人员姓名、用户电子邮件和简历。它仅接受 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
};
Enter fullscreen mode Exit fullscreen mode

上面的代码片段在 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);
};
Enter fullscreen mode Exit fullscreen mode
  • 从上面的代码片段来看,
    • 我将所有表单输入(文件和文本)添加到 JavaScript FormData 对象中。
    • sendResume函数接受表单数据、setLoadingnavigate变量作为参数。接下来,让我们创建该函数。

创建一个文件夹,其中utils包含一个文件util.jsclient/src

cd client/src
mkdir utils
cd utils
touch util.js
Enter fullscreen mode Exit fullscreen mode

如下所示在文件sendResume创建函数。我们将在函数中配置 POST 请求。util.jssendResume

const sendResume = (formData, setLoading, navigate) => {};
export default sendResume;
Enter fullscreen mode Exit fullscreen mode

导入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));
};
Enter fullscreen mode Exit fullscreen mode

该函数将加载状态设置为 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}`,
    });
});
Enter fullscreen mode Exit fullscreen mode

上面的代码片段接受来自前端的数据,并通过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...
});
Enter fullscreen mode Exit fullscreen mode

从代码片段来看,workArrayapplicantNametechnologies是包含用户工作经历、全名和技能的全局变量。创建求职信时需要这些变量。

/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}`,
        },
    });
});
Enter fullscreen mode Exit fullscreen mode
  • 从上面的代码片段来看,
    • 我们解构了从 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;
};
Enter fullscreen mode Exit fullscreen mode

在 React 中通过 EmailJS 发送电子邮件

在这里,我将指导您将 EmailJS 添加到 React.js 应用程序以及如何将 AI 生成的电子邮件发送给招聘人员。

通过运行以下代码将 EmailJS 安装到 React 应用程序:

npm install @emailjs/browser
Enter fullscreen mode Exit fullscreen mode

在此创建一个 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));
Enter fullscreen mode Exit fullscreen mode

代码片段检查请求是否成功;如果成功,它会对响应中的数据进行解构,并向招聘人员发送包含求职信和简历的电子邮件。

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

帮助

文章来源:https://dev.to/novu/applying-for-a-new-job-with-react-and-nodejs-and-ai-17a9
PREV
使用 Node.js、React 和 Websockets 构建看板 📝 ✨
NEXT
🚀 10 个 Github 存储库助您精通 Javascript 🧙‍♂️🪄✨ HackSquad 2023 现已上线!