使用 React、NodeJS 和 AI 创建简历生成器 🚀 TL;DR

2025-05-25

使用 React、NodeJS 和 AI 🚀 创建简历生成器

TL;DR

TL;DR

在本文中,你将学习如何使用 React、Node.js 和 OpenAI API 创建简历生成器。
找工作时,如果说你已经用 AI 构建了一个简历生成器,还有什么比这更好的呢?🤩

一个小请求🥺

我每周都会创作内容,您的支持对创作更多内容非常有帮助。请为我们的 GitHub 库点赞,支持我。非常感谢!❤️

https://github.com/novuhq/novu

OpenAI API 简介

GPT-3 是由 OpenAI 开发的一种人工智能程序,非常擅长理解和处理人类语言。它已经接受了来自互联网的大量文本数据的训练,这使得它能够对各种与语言相关的任务生成高质量的响应。

本文将使用 OpenAI GPT3。ChatGPT
API 发布后,我会用它写另一篇文章 🤗
从 OpenAI 发布第一个 API 的那天起,我就一直是它的忠实粉丝。我联系了他们的一位员工,并向他们发送了 GPT3 测试版的访问请求,现在终于如愿以偿了 😅

GPT3
这就是 2020 年 12 月 30 日的我

项目设置

在这里,我将指导您创建 Web 应用程序的项目环境。我们将使用 React.js 作为前端,使用 Node.js 作为后端服务器。

通过运行以下代码为 Web 应用程序创建项目文件夹:



mkdir resume-builder
cd resume-builder
mkdir client server


Enter fullscreen mode Exit fullscreen mode

设置 Node.js 服务器

导航到服务器文件夹并创建一个package.json文件。



cd server & npm init -y


Enter fullscreen mode Exit fullscreen mode

安装 Express、Nodemon 和 CORS 库。



npm install express cors nodemon


Enter fullscreen mode Exit fullscreen mode

ExpressJS是一个快速、简约的框架,它提供了在 Node.js 中构建 Web 应用程序的多种功能,  CORS是一个允许不同域之间通信的 Node.js 包,  Nodemon是一个在检测到文件更改后自动重启服务器的 Node.js 工具。

创建一个index.js文件——Web 服务器的入口点。



touch index.js


Enter fullscreen mode Exit fullscreen mode

使用 Express.js 设置 Node.js 服务器。当您http://localhost:4000/api在浏览器中访问时,下面的代码片段会返回一个 JSON 对象。



//👇🏻index.js
const express = require("express");
const cors = require("cors");
const app = express();
const PORT = 4000;

app.use(express.urlencoded({ extended: true }));
app.use(express.json());
app.use(cors());

app.get("/api", (req, res) => {
    res.json({
        message: "Hello world",
    });
});

app.listen(PORT, () => {
    console.log(`Server listening on ${PORT}`);
});


Enter fullscreen mode Exit fullscreen mode

通过将启动命令添加到package.json文件中的脚本列表中来配置 Nodemon。下面的代码片段使用 Nodemon 启动服务器。



//In server/package.json

"scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "nodemon index.js"
  },


Enter fullscreen mode Exit fullscreen mode

恭喜!您现在可以使用以下命令启动服务器。



npm start


Enter fullscreen mode Exit fullscreen mode

设置 React 应用程序

通过终端导航到客户端文件夹并创建一个新的 React.js 项目。



cd client
npx create-react-app ./


Enter fullscreen mode Exit fullscreen mode

安装 Axios 和 React Router。React  Router是一个 JavaScript 库,它使我们能够在 React 应用程序中的页面之间导航。Axios 是一个基于 Promise 的 Node.js HTTP 客户端,用于执行异步请求



npm install axios react-router-dom


Enter fullscreen mode Exit fullscreen mode

从 React 应用程序中删除冗余文件(例如徽标和测试文件),并更新App.js文件以显示如下所示的 Hello World。



function App() {
    return (
        <div>
            <p>Hello World!</p>
        </div>
    );
}
export default App;


Enter fullscreen mode Exit fullscreen mode

导航到src/index.css文件并复制以下代码。它包含设计此项目所需的所有 CSS。



@import url("https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&display=swap");

* {
    font-family: "Space Grotesk", sans-serif;
    box-sizing: border-box;
    margin: 0;
    padding: 0;
}
form {
    padding: 10px;
    width: 80%;
    display: flex;
    flex-direction: column;
}
input {
    margin-bottom: 15px;
    padding: 10px 20px;
    border-radius: 3px;
    outline: none;
    border: 1px solid #ddd;
}
h3 {
    margin: 15px 0;
}
button {
    padding: 15px;
    cursor: pointer;
    outline: none;
    background-color: #5d3891;
    border: none;
    color: #f5f5f5;
    font-size: 16px;
    font-weight: bold;
    border-radius: 3px;
}
.app {
    min-height: 100vh;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    padding: 30px;
}
.app > p {
    margin-bottom: 30px;
}
.nestedContainer {
    display: flex;
    align-items: center;
    justify-content: space-between;
    width: 100%;
}
.companies {
    display: flex;
    flex-direction: column;
    width: 39%;
}
.currentInput {
    width: 95%;
}
#photo {
    width: 50%;
}
#addBtn {
    background-color: green;
    margin-right: 5px;
}
#deleteBtn {
    background-color: red;
}
.container {
    min-height: 100vh;
    padding: 30px;
}
.header {
    width: 80%;
    margin: 0 auto;
    min-height: 10vh;
    background-color: #e8e2e2;
    padding: 30px;
    border-radius: 3px 3px 0 0;
    display: flex;
    align-items: center;
    justify-content: space-between;
}
.resumeTitle {
    opacity: 0.6;
}
.headerTitle {
    margin-bottom: 15px;
}
.resumeImage {
    vertical-align: middle;
    width: 150px;
    height: 150px;
    border-radius: 50%;
}
.resumeBody {
    width: 80%;
    margin: 0 auto;
    padding: 30px;
    min-height: 80vh;
    border: 1px solid #e0e0ea;
}
.resumeBodyTitle {
    margin-bottom: 5px;
}
.resumeBodyContent {
    text-align: justify;
    margin-bottom: 30px;
}


Enter fullscreen mode Exit fullscreen mode

构建应用程序用户界面

在这里,我们将为简历生成器应用程序创建用户界面,以使用户能够提交他们的信息并打印 AI 生成的简历。

client/src在包含Home.jsLoading.jsResume.js、文件的文件夹中创建一个 components 文件夹ErrorPage.js



cd client/src
mkdir components
touch Home.js Loading.js Resume.js ErrorPage.js


Enter fullscreen mode Exit fullscreen mode

从上面的代码片段来看:

  • Home.js文件呈现表单字段,以使用户能够输入必要的信息。
  • 包含Loading.js请求待处理时向用户显示的组件。
  • 向用户显示Resume.jsAI 生成的简历。
  • ErrorPage.js发生错误时会显示

更新App.js文件以使用 React Router 呈现组件。



import React from "react";
import { BrowserRouter, Routes, Route } from "react-router-dom";
import Home from "./components/Home";
import Resume from "./components/Resume";

const App = () => {
    return (
        <div>
            <BrowserRouter>
                <Routes>
                    <Route path='/' element={<Home />} />
                    <Route path='/resume' element={<Resume />} />
                </Routes>
            </BrowserRouter>
        </div>
    );
};

export default App;


Enter fullscreen mode Exit fullscreen mode

主页

在这里,您将学习如何构建一个可以通过 HTTP 请求发送图像并动态添加和删除输入字段的表单布局。

首先,更新 Loading 组件以呈现下面的代码片段,在简历待处理时显示给用户。



import React from "react";

const Loading = () => {
    return (
        <div className='app'>
            <h1>Loading, please wait...</h1>
        </div>
    );
};

export default Loading;


Enter fullscreen mode Exit fullscreen mode

接下来,更新ErrorPage.js文件以在用户直接导航到简历页面时显示下面的组件。



import React from "react";
import { Link } from "react-router-dom";

const ErrorPage = () => {
    return (
        <div className='app'>
            <h3>
                You've not provided your details. Kindly head back to the{" "}
                <Link to='/'>homepage</Link>.
            </h3>
        </div>
    );
};

export default ErrorPage;


Enter fullscreen mode Exit fullscreen mode

将下面的代码片段复制到Home.js文件中



import React, { useState } from "react";
import Loading from "./Loading";

const Home = () => {
    const [fullName, setFullName] = useState("");
    const [currentPosition, setCurrentPosition] = useState("");
    const [currentLength, setCurrentLength] = useState(1);
    const [currentTechnologies, setCurrentTechnologies] = useState("");
    const [headshot, setHeadshot] = useState(null);
    const [loading, setLoading] = useState(false);

    const handleFormSubmit = (e) => {
        e.preventDefault();
        console.log({
            fullName,
            currentPosition,
            currentLength,
            currentTechnologies,
            headshot,
        });
        setLoading(true);
    };
    //👇🏻 Renders the Loading component you submit the form
    if (loading) {
        return <Loading />;
    }
    return (
        <div className='app'>
            <h1>Resume Builder</h1>
            <p>Generate a resume with ChatGPT in few seconds</p>
            <form
                onSubmit={handleFormSubmit}
                method='POST'
                encType='multipart/form-data'
            >
                <label htmlFor='fullName'>Enter your full name</label>
                <input
                    type='text'
                    required
                    name='fullName'
                    id='fullName'
                    value={fullName}
                    onChange={(e) => setFullName(e.target.value)}
                />
                <div className='nestedContainer'>
                    <div>
                        <label htmlFor='currentPosition'>Current Position</label>
                        <input
                            type='text'
                            required
                            name='currentPosition'
                            className='currentInput'
                            value={currentPosition}
                            onChange={(e) => setCurrentPosition(e.target.value)}
                        />
                    </div>
                    <div>
                        <label htmlFor='currentLength'>For how long? (year)</label>
                        <input
                            type='number'
                            required
                            name='currentLength'
                            className='currentInput'
                            value={currentLength}
                            onChange={(e) => setCurrentLength(e.target.value)}
                        />
                    </div>
                    <div>
                        <label htmlFor='currentTechnologies'>Technologies used</label>
                        <input
                            type='text'
                            required
                            name='currentTechnologies'
                            className='currentInput'
                            value={currentTechnologies}
                            onChange={(e) => setCurrentTechnologies(e.target.value)}
                        />
                    </div>
                </div>
                <label htmlFor='photo'>Upload your headshot image</label>
                <input
                    type='file'
                    name='photo'
                    required
                    id='photo'
                    accept='image/x-png,image/jpeg'
                    onChange={(e) => setHeadshot(e.target.files[0])}
                />

                <button>CREATE RESUME</button>
            </form>
        </div>
    );
};

export default Home;


Enter fullscreen mode Exit fullscreen mode

形式

代码片段渲染了下面的表单字段。它接受全名和当前工作经历(年份、职位、头衔),并允许用户通过表单字段上传头像。

最后,你需要接受用户之前的工作经历。因此,添加一个保存职位描述数组的新状态。



const [companyInfo, setCompanyInfo] = useState([{ name: "", position: "" }]);


Enter fullscreen mode Exit fullscreen mode

添加以下有助于更新状态的功能。



//👇🏻 updates the state with user's input
const handleAddCompany = () =>
    setCompanyInfo([...companyInfo, { name: "", position: "" }]);

//👇🏻 removes a selected item from the list
const handleRemoveCompany = (index) => {
    const list = [...companyInfo];
    list.splice(index, 1);
    setCompanyInfo(list);
};
//👇🏻 updates an item within the list
const handleUpdateCompany = (e, index) => {
    const { name, value } = e.target;
    const list = [...companyInfo];
    list[index][name] = value;
    setCompanyInfo(list);
};


Enter fullscreen mode Exit fullscreen mode

使用用户的输入handleAddCompany更新状态,用于从提供的数据列表中删除一个项目,并更新列表内的项目属性 - (名称和位置)。companyInfohandleRemoveCompanyhandleUpdateCompany

接下来,渲染工作经验部分的 UI 元素。



return (
    <div className='app'>
        <h3>Companies you've worked at</h3>
        <form>
            {/*--- other UI tags --- */}
            {companyInfo.map((company, index) => (
                <div className='nestedContainer' key={index}>
                    <div className='companies'>
                        <label htmlFor='name'>Company Name</label>
                        <input
                            type='text'
                            name='name'
                            required
                            onChange={(e) => handleUpdateCompany(e, index)}
                        />
                    </div>
                    <div className='companies'>
                        <label htmlFor='position'>Position Held</label>
                        <input
                            type='text'
                            name='position'
                            required
                            onChange={(e) => handleUpdateCompany(e, index)}
                        />
                    </div>

                    <div className='btn__group'>
                        {companyInfo.length - 1 === index && companyInfo.length < 4 && (
                            <button id='addBtn' onClick={handleAddCompany}>
                                Add
                            </button>
                        )}
                        {companyInfo.length > 1 && (
                            <button id='deleteBtn' onClick={() => handleRemoveCompany(index)}>
                                Del
                            </button>
                        )}
                    </div>
                </div>
            ))}

            <button>CREATE RESUME</button>
        </form>
    </div>
);


Enter fullscreen mode Exit fullscreen mode

该代码片段映射了数组中的元素companyInfo并将其显示在网页上。该handleUpdateCompany函数在用户更新输入字段,然后handleRemoveCompany从元素列表中删除一项并handleAddCompany添加新的输入字段时运行。

简历页面

简历页面

本页面以可打印的格式展示了由 OpenAI API 生成的简历。请将以下代码复制到Resume.js文件中。我们将在本教程的后续部分更新其内容。



import React from "react";
import ErrorPage from "./ErrorPage";

const Resume = ({ result }) => {
    if (JSON.stringify(result) === "{}") {
        return <ErrorPage />;
    }

    const handlePrint = () => alert("Print Successful!");
    return (
        <>
            <button onClick={handlePrint}>Print Page</button>
            <main className='container'>
                <p>Hello!</p>
            </main>
        </>
    );
};


Enter fullscreen mode Exit fullscreen mode

如何在 Node.js 中通过表单提交图像

 这里,我将指导你如何将表单数据提交到 Node.js 服务器。由于表单包含图像,我们需要在 Node.js 服务器上设置 Multer 。

💡 [Multer](https://www.npmjs.com/package/multer) 是一个用于将文件上传到服务器的 Node.js 中间件。

设置Multer

运行下面的代码来安装 Multer



npm install multer


Enter fullscreen mode Exit fullscreen mode

确保前端应用程序上的表单具有方法和encType属性,因为 Multer 仅处理多部分的表单。



<form method="POST" enctype="multipart/form-data"></form>


Enter fullscreen mode Exit fullscreen mode

将 Multer 和 Node.js 路径包导入index.js文件中。



const multer = require("multer");
const path = require("path");


Enter fullscreen mode Exit fullscreen mode

将下面的代码复制到index.js配置Multer中。



app.use("/uploads", express.static("uploads"));

const storage = multer.diskStorage({
    destination: (req, file, cb) => {
        cb(null, "uploads");
    },
    filename: (req, file, cb) => {
        cb(null, Date.now() + path.extname(file.originalname));
    },
});

const upload = multer({
    storage: storage,
    limits: { fileSize: 1024 * 1024 * 5 },
});


Enter fullscreen mode Exit fullscreen mode
  • 从上面的代码片段来看:
    • app.use()函数使 Node.js 能够提供文件夹内容uploads。这些内容指的是静态文件,例如图像、CSS 和 JavaScript 文件。
    • 变量storage“containing”multer.diskStorage赋予我们存储图像的完全控制权。上面的函数将图像存储在上传文件夹中,并将图像重命名为其上传时间(以防止文件名冲突)。
    • 上传变量将配置传递给 Multer 并为图像设置 5MB 的大小限制。

在服务器上创建uploads文件夹。图像将保存在此处。



mkdir uploads


Enter fullscreen mode Exit fullscreen mode

如何将图像上传到 Node.js 服务器

添加一个路由,用于接收来自 React 应用的所有表单输入。该upload.single("headshotImage")函数将通过表单上传的图片添加到uploads文件夹中。



app.post("/resume/create", upload.single("headshotImage"), async (req, res) => {
    const {
        fullName,
        currentPosition,
        currentLength,
        currentTechnologies,
        workHistory,
    } = req.body;

    console.log(req.body);

    res.json({
        message: "Request successful!",
        data: {},
    });
});


Enter fullscreen mode Exit fullscreen mode

更新组件handleFormSubmit内的函数Home.js以将表单数据提交到 Node.js 服务器。



import axios from "axios";

const handleFormSubmit = (e) => {
    e.preventDefault();

    const formData = new FormData();
    formData.append("headshotImage", headshot, headshot.name);
    formData.append("fullName", fullName);
    formData.append("currentPosition", currentPosition);
    formData.append("currentLength", currentLength);
    formData.append("currentTechnologies", currentTechnologies);
    formData.append("workHistory", JSON.stringify(companyInfo));
    axios
        .post("http://localhost:4000/resume/create", formData, {})
        .then((res) => {
            if (res.data.message) {
                console.log(res.data.data);
                navigate("/resume");
            }
        })
        .catch((err) => console.error(err));
    setLoading(true);
};


Enter fullscreen mode Exit fullscreen mode

上面的代码片段创建了一个键/值对,代表表单字段及其值,并通过 Axios 发送到服务器上的 API 端点。如果有响应,它会记录响应并将用户重定向到“简历”页面。

如何在 Node.js 中与 OpenAI API 进行通信

在本节中,您将学习如何在 Node.js 服务器中与 OpenAI API 通信。
我们会将用户信息发送到 API,以生成个人资料摘要、职位描述以及在之前机构完成的成就或相关活动。为此:

通过运行以下代码安装 OpenAI API Node.js 库。



npm install openai


Enter fullscreen mode Exit fullscreen mode

在此登录或创建 OpenAI 帐户 

点击Personal导航栏,View API keys从菜单栏中选择创建一个新的密钥。

导航

将 API 密钥复制到计算机上的安全位置;我们很快就会用到它。

通过将以下代码复制到index.js文件中来配置 API。



const { Configuration, OpenAIApi } = require("openai");

const configuration = new Configuration({
    apiKey: "<YOUR_API_KEY>",
});

const openai = new OpenAIApi(configuration);


Enter fullscreen mode Exit fullscreen mode

创建一个接受文本(提示)作为参数并返回 AI 生成结果的函数。



const GPTFunction = async (text) => {
    const response = await openai.createCompletion({
        model: "text-davinci-003",
        prompt: text,
        temperature: 0.6,
        max_tokens: 250,
        top_p: 1,
        frequency_penalty: 1,
        presence_penalty: 1,
    });
    return response.data.choices[0].text;
};


Enter fullscreen mode Exit fullscreen mode

上面的代码片段使用text-davinci-003模型来生成针对提示的适当答案。其他键值帮助我们生成所需的特定类型的响应。

/resume/create按照如下所示更新路线。



app.post("/resume/create", upload.single("headshotImage"), async (req, res) => {
    const {
        fullName,
        currentPosition,
        currentLength,
        currentTechnologies,
        workHistory, //JSON format
    } = req.body;

    const workArray = JSON.parse(workHistory); //an array

    //👇🏻 group the values into an object
    const newEntry = {
        id: generateID(),
        fullName,
        image_url: `http://localhost:4000/uploads/${req.file.filename}`,
        currentPosition,
        currentLength,
        currentTechnologies,
        workHistory: workArray,
    };
});


Enter fullscreen mode Exit fullscreen mode

上面的代码片段接受来自客户端的表单数据,转换workHistory为其原始数据结构(数组),并将它们全部放入一个对象中。

接下来,创建您想要传递到的提示GPTFunction



//👇🏻 loops through the items in the workArray and converts them to a string
const remainderText = () => {
    let stringText = "";
    for (let i = 0; i < workArray.length; i++) {
        stringText += ` ${workArray[i].name} as a ${workArray[i].position}.`;
    }
    return stringText;
};
//👇🏻 The job description prompt
const prompt1 = `I am writing a resume, my details are \n name: ${fullName} \n role: ${currentPosition} (${currentLength} years). \n I write in the technolegies: ${currentTechnologies}. Can you write a 100 words description for the top of the resume(first person writing)?`;
//👇🏻 The job responsibilities prompt
const prompt2 = `I am writing a resume, my details are \n name: ${fullName} \n role: ${currentPosition} (${currentLength} years). \n I write in the technolegies: ${currentTechnologies}. Can you write 10 points for a resume on what I am good at?`;
//👇🏻 The job achievements prompt
const prompt3 = `I am writing a resume, my details are \n name: ${fullName} \n role: ${currentPosition} (${currentLength} years). \n During my years I worked at ${
    workArray.length
} companies. ${remainderText()} \n Can you write me 50 words for each company seperated in numbers of my succession in the company (in first person)?`;

//👇🏻 generate a GPT-3 result
const objective = await GPTFunction(prompt1);
const keypoints = await GPTFunction(prompt2);
const jobResponsibilities = await GPTFunction(prompt3);
//👇🏻 put them into an object
const chatgptData = { objective, keypoints, jobResponsibilities };
//👇🏻log the result
console.log(chatgptData);


Enter fullscreen mode Exit fullscreen mode
  • 从上面的代码片段来看:
    • remainderText函数循环遍历工作经历数组,并返回所有工作经历的字符串数据类型。
    • 然后,会出现三个提示,说明 GPT-3 API 需要什么。
    • 接下来,将结果存储在一个对象中并将其记录到控制台。

最后,返回 AI 生成的结果和用户输入的信息。您还可以创建一个表示存储结果的数据库的数组,如下所示。



let database = [];

app.post("/resume/create", upload.single("headshotImage"), async (req, res) => {
    //...other code statements
    const data = { ...newEntry, ...chatgptData };
    database.push(data);

    res.json({
        message: "Request successful!",
        data,
    });
});


Enter fullscreen mode Exit fullscreen mode

显示来自 OpenAI API 的响应

在本节中,我将指导您以可读和可打印的格式在网页上显示从 OpenAI API 生成的结果。

在文件中创建一个 React 状态App.js。该状态将保存从 Node.js 服务器发送的结果。



import React, { useState } from "react";
import { BrowserRouter, Routes, Route } from "react-router-dom";
import Home from "./components/Home";
import Resume from "./components/Resume";

const App = () => {
    //👇🏻 state holding the result
    const [result, setResult] = useState({});

    return (
        <div>
            <BrowserRouter>
                <Routes>
                    <Route path='/' element={<Home setResult={setResult} />} />
                    <Route path='/resume' element={<Resume result={result} />} />
                </Routes>
            </BrowserRouter>
        </div>
    );
};

export default App;


Enter fullscreen mode Exit fullscreen mode

从上面的代码片段中,只有setResult作为 prop 传递到 Home 组件,并且仅result传递给 Resume 组件。setResult一旦表单提交并且请求成功,就会更新结果的值,而result包含从服务器检索到的响应,显示在 Resume 组件中。

result表单提交且请求成功后更新Home组件内的状态。



const Home = ({ setResult }) => {
    const handleFormSubmit = (e) => {
        e.preventDefault();

        //...other code statements
        axios
            .post("http://localhost:4000/resume/create", formData, {})
            .then((res) => {
                if (res.data.message) {
                    //👇🏻 updates the result object
                    setResult(res.data.data);
                    navigate("/resume");
                }
            })
            .catch((err) => console.error(err));
        setLoading(true);
    };
    return <div></div>;
};


Enter fullscreen mode Exit fullscreen mode

按照如下所示更新 Resume 组件,以在 React 应用程序中预览结果。



import ErrorPage from "./ErrorPage";

const Resume = ({ result }) => {
    //👇🏻 function that replaces the new line with a break tag
    const replaceWithBr = (string) => {
        return string.replace(/\n/g, "<br />");
    };

    //👇🏻 returns an error page if the result object is empty
    if (JSON.stringify(result) === "{}") {
        return <ErrorPage />;
    }

    const handlePrint = () => alert("Printing");

    return (
        <>
            <button onClick={handlePrint}>Print Page</button>
            <main className='container' ref={componentRef}>
                <header className='header'>
                    <div>
                        <h1>{result.fullName}</h1>
                        <p className='resumeTitle headerTitle'>
                            {result.currentPosition} ({result.currentTechnologies})
                        </p>
                        <p className='resumeTitle'>
                            {result.currentLength}year(s) work experience
                        </p>
                    </div>
                    <div>
                        <img
                            src={result.image_url}
                            alt={result.fullName}
                            className='resumeImage'
                        />
                    </div>
                </header>
                <div className='resumeBody'>
                    <div>
                        <h2 className='resumeBodyTitle'>PROFILE SUMMARY</h2>
                        <p
                            dangerouslySetInnerHTML={{
                                __html: replaceWithBr(result.objective),
                            }}
                            className='resumeBodyContent'
                        />
                    </div>
                    <div>
                        <h2 className='resumeBodyTitle'>WORK HISTORY</h2>
                        {result.workHistory.map((work) => (
                            <p className='resumeBodyContent' key={work.name}>
                                <span style={{ fontWeight: "bold" }}>{work.name}</span> -{" "}
                                {work.position}
                            </p>
                        ))}
                    </div>
                    <div>
                        <h2 className='resumeBodyTitle'>JOB PROFILE</h2>
                        <p
                            dangerouslySetInnerHTML={{
                                __html: replaceWithBr(result.jobResponsibilities),
                            }}
                            className='resumeBodyContent'
                        />
                    </div>
                    <div>
                        <h2 className='resumeBodyTitle'>JOB RESPONSIBILITIES</h2>
                        <p
                            dangerouslySetInnerHTML={{
                                __html: replaceWithBr(result.keypoints),
                            }}
                            className='resumeBodyContent'
                        />
                    </div>
                </div>
            </main>
        </>
    );
};


Enter fullscreen mode Exit fullscreen mode

上面的代码片段根据指定的布局在网页上显示结果。该函数replaceWithBr将每个新行 (\n) 替换为换行标记,并且该handlePrint函数将允许用户打印简历。

如何使用 React-to-print 包打印 React 页面

在这里,您将学习如何向网页添加打印按钮,使用户能够通过 React-toprint包打印简历。

💡 React-to-print  是一个简单的 JavaScript 包,它使您能够打印 React 组件的内容,而无需篡改组件 CSS 样式。

运行下面的代码来安装包



npm install react-to-print


Enter fullscreen mode Exit fullscreen mode

在文件中导入库Resume.js并添加useRef钩子。



import { useReactToPrint } from "react-to-print";
import React, { useRef } from "react";


Enter fullscreen mode Exit fullscreen mode

Resume.js按照如下所示更新文件。



const Resume = ({ result }) => {
    const componentRef = useRef();

    const handlePrint = useReactToPrint({
        content: () => componentRef.current,
        documentTitle: `${result.fullName} Resume`,
        onAfterPrint: () => alert("Print Successful!"),
    });
    //...other function statements
    return (
        <>
            <button onClick={handlePrint}>Print Page</button>
            <main className='container' ref={componentRef}>
                {/*---other code statements---*/}
            </main>
        </>
    );
};


Enter fullscreen mode Exit fullscreen mode

handlePrint函数打印 -main 标签内的元素componentRef,将文档的名称设置为用户的全名,并在用户打印表单时运行警报函数。

恭喜!您已完成本教程的项目。

以下是该项目所获得结果的示例:

结果

结论

到目前为止,您已经学习了:

  • OpenAI GPT-3 是什么
  • 如何在 Node.js 和 React.js 应用程序中通过表单上传图像,
  • 如何与 OpenAI GPT-3 API 交互,以及
  • 如何通过 React-to-print 库打印 React 网页。

本教程将引导您了解使用 OpenAI API 构建的应用程序示例。借助该 API,您可以创建功能强大的应用程序,应用于各个领域,例如翻译、问答、代码解释或生成等。

本教程的源代码可以在这里找到:

https://github.com/novuhq/blog/tree/main/resume-builder-with-react-chatgpt-nodejs

感谢您的阅读!

文章来源:https://dev.to/novu/creating-a-resume-builder-with-react-nodejs-and-ai-4k6l
PREV
使用 React、Nodejs 和 EmailJS 创建一个调度应用程序
NEXT
在 React 上创建具有双因素身份验证的注册和登录