การแปลงคำพูดเป็น PDF ด้วย NextJS และ ExpressJS PlatoBlockchain Data Intelligence ค้นหาแนวตั้ง AI.

การแปลงคำพูดเป็น PDF ด้วย NextJS และ ExpressJS

เนื่องจากอินเทอร์เฟซคำพูดมีความสำคัญมากขึ้น การสำรวจบางสิ่งที่เราสามารถทำได้ด้วยการโต้ตอบด้วยคำพูดจึงคุ้มค่า เช่น จะเกิดอะไรขึ้นถ้าเราสามารถพูดอะไรบางอย่างและให้ถอดความและพิมพ์ออกมาเป็น PDF ที่ดาวน์โหลดได้

เอาล่ะ สปอยล์แจ้งเตือน: พวกเราแน่นอน สามารถ ทำอย่างนั้น! มีไลบรารีและเฟรมเวิร์กที่เราสามารถร่วมกันทำให้มันเกิดขึ้นได้ และนั่นคือสิ่งที่เราจะทำร่วมกันในบทความนี้

นี่คือเครื่องมือที่เราใช้

ก่อนอื่น นี่คือผู้เล่นรายใหญ่สองคน: Next.js และ Express.js

เน็กซ์.เจส ใช้ฟังก์ชันเพิ่มเติมใน React รวมถึงคุณสมบัติหลักสำหรับการสร้างไซต์แบบคงที่ เป็นสิ่งที่ควรไปสำหรับนักพัฒนาหลายๆ คนเพราะสิ่งที่นำเสนอได้ทันที เช่น การกำหนดเส้นทางแบบไดนามิก การปรับภาพให้เหมาะสม การกำหนดเส้นทางโดเมนในตัวและโดเมนย่อย การรีเฟรชอย่างรวดเร็ว การกำหนดเส้นทางระบบไฟล์ และเส้นทาง API... อื่นๆอีกมากมาย.

ในกรณีของเรา เราต้องการ Next.js สำหรับมัน เส้นทาง API บนเซิร์ฟเวอร์ลูกค้าของเรา เราต้องการเส้นทางที่ใช้ไฟล์ข้อความ แปลงเป็น PDF เขียนลงในระบบไฟล์ของเรา แล้วส่งการตอบกลับไปยังไคลเอ็นต์

Express.js ช่วยให้เรารับแอป Node.js เล็กๆ ที่มีการกำหนดเส้นทาง ตัวช่วย HTTP และการสร้างเทมเพลต เป็นเซิร์ฟเวอร์สำหรับ API ของเราเอง ซึ่งเป็นสิ่งที่เราต้องการเมื่อเราส่งผ่านและแยกวิเคราะห์ข้อมูลระหว่างสิ่งต่างๆ

เรามีการพึ่งพาอื่น ๆ ที่เราจะนำไปใช้:

  1. ตอบสนอง-พูด-รู้จำ: ไลบรารีสำหรับแปลงคำพูดเป็นข้อความ ทำให้พร้อมใช้งานสำหรับส่วนประกอบ React
  2. regenerator-รันไทม์: ห้องสมุดสำหรับแก้ไขปัญหาเรื่อง “regeneratorRuntime ไม่ได้กำหนด” ข้อผิดพลาดที่แสดงใน Next.js เมื่อใช้ react-speech-recognition
  3. html-pdf-โหนด: ไลบรารีสำหรับแปลงหน้า HTML หรือ URL สาธารณะเป็น PDF
  4. Axios: ไลบรารี่สำหรับส่งคำขอ HTTP ทั้งในเบราว์เซอร์และ Node.js
  5. : ไลบรารีที่อนุญาตการแชร์ทรัพยากรข้ามต้นทาง

การตั้งค่า

สิ่งแรกที่เราต้องการทำคือสร้างโฟลเดอร์โปรเจ็กต์สองโฟลเดอร์ โฟลเดอร์หนึ่งสำหรับลูกค้าและอีกโฟลเดอร์หนึ่งสำหรับเซิร์ฟเวอร์ ตั้งชื่อพวกเขาตามที่คุณต้องการ ฉันกำลังตั้งชื่อของฉัน audio-to-pdf-client และ audio-to-pdf-serverตามลำดับ

วิธีที่เร็วที่สุดในการเริ่มต้นใช้งาน Next.js ทางฝั่งไคลเอ็นต์คือการบูตสแตรปด้วย สร้างแอปถัดไป. ดังนั้น เปิดเทอร์มินัลของคุณและเรียกใช้คำสั่งต่อไปนี้จากโฟลเดอร์โครงการไคลเอนต์ของคุณ:

npx create-next-app client

ตอนนี้เราต้องการเซิร์ฟเวอร์ด่วนของเรา เรารับได้นะ cd- เข้าไปในโฟลเดอร์โปรเจ็กต์เซิร์ฟเวอร์และเรียกใช้ npm init สั่งการ. NS package.json ไฟล์จะถูกสร้างขึ้นในโฟลเดอร์โครงการเซิร์ฟเวอร์เมื่อเสร็จสิ้น

เรายังคงต้องติดตั้ง Express จริงๆ งั้นมาทำกันตอนนี้กับ npm install express. ตอนนี้เราสามารถสร้างใหม่ได้ index.js ไฟล์ในโฟลเดอร์โปรเจ็กต์เซิร์ฟเวอร์และวางรหัสนี้ในนั้น:

const express = require("express")
const app = express()

app.listen(4000, () => console.log("Server is running on port 4000"))

พร้อมที่จะเรียกใช้เซิร์ฟเวอร์หรือไม่

node index.js

เราต้องการอีกสองสามโฟลเดอร์และไฟล์อื่นเพื่อดำเนินการต่อ:

  • สร้าง components โฟลเดอร์ในโฟลเดอร์โครงการไคลเอ็นต์
  • สร้าง SpeechToText.jsx แฟ้มใน components โฟลเดอร์ย่อย

ก่อนที่เราจะไปไกลกว่านี้เรามีการล้างข้อมูลเล็กน้อยที่ต้องทำ โดยเฉพาะอย่างยิ่ง เราจำเป็นต้องแทนที่รหัสเริ่มต้นใน pages/index.js ไฟล์ด้วยสิ่งนี้:

import Head from "next/head";
import SpeechToText from "../components/SpeechToText";

export default function Home() {
  return (
    <div className="home">
      <Head>
        <title>Audio To PDF</title>
        <meta
          name="description"
          content="An app that converts audio to pdf in the browser"
        />
        <link rel="icon" href="/th/favicon.ico" />
      </Head>

      <h1>Convert your speech to pdf</h1>

      <main>
        <SpeechToText />
      </main>
    </div>
  );
}

นำเข้า SpeechToText ในที่สุดส่วนประกอบจะถูกส่งออกจาก components/SpeechToText.jsx.

มาติดตั้งการพึ่งพาอื่น ๆ กันเถอะ

เอาล่ะ เรามีการตั้งค่าเริ่มต้นสำหรับแอปของเราเรียบร้อยแล้ว ตอนนี้ เราสามารถติดตั้งไลบรารีที่จัดการข้อมูลที่ส่งผ่านไปมาได้แล้ว

เราสามารถติดตั้งการพึ่งพาลูกค้าของเราด้วย:

npm install react-speech-recognition regenerator-runtime axios

การขึ้นต่อกันของเซิร์ฟเวอร์ Express ของเรากำลังจะตามมา ดังนั้นมา cd ลงในโฟลเดอร์โครงการเซิร์ฟเวอร์และติดตั้งสิ่งเหล่านั้น:

npm install html-pdf-node cors

อาจเป็นช่วงเวลาที่ดีในการหยุดชั่วคราวและตรวจดูให้แน่ใจว่าไฟล์ในโฟลเดอร์โครงการของเรามีความปลอดภัย นี่คือสิ่งที่คุณควรมีในโฟลเดอร์โครงการไคลเอนต์ ณ จุดนี้:

/audio-to-pdf-web-client
├─ /components
|  └── SpeechToText.jsx
├─ /pages
|  ├─ _app.js
|  └── index.js
└── /styles
    ├─globals.css
    └── Home.module.css

และนี่คือสิ่งที่คุณควรมีในโฟลเดอร์โครงการเซิร์ฟเวอร์:

/audio-to-pdf-server
└── index.js

การสร้าง UI

คำพูดของเราเป็น PDF คงไม่ดีขนาดนั้นถ้าไม่มีทางโต้ตอบกับมันได้ เรามาสร้างองค์ประกอบ React ขึ้นมาที่เราเรียกมันได้ <SpeechToText>.

คุณสามารถใช้มาร์กอัปของคุณเองได้ทั้งหมด นี่คือสิ่งที่ฉันต้องให้คุณมีความคิดเกี่ยวกับชิ้นส่วนต่างๆ ที่เรานำมารวมกัน:

import React from "react";

const SpeechToText = () => {
  return (
    <>
      <section>
        <div className="button-container">
          <button type="button" style={{ "--bgColor": "blue" }}>
            Start
          </button>
          <button type="button" style={{ "--bgColor": "orange" }}>
            Stop
          </button>
        </div>
        <div
          className="words"
          contentEditable
          suppressContentEditableWarning={true}
        ></div>
        <div className="button-container">
          <button type="button" style={{ "--bgColor": "red" }}>
            Reset
          </button>
          <button type="button" style={{ "--bgColor": "green" }}>
            Convert to pdf
          </button>
        </div>
      </section>
    </>
  );
};

export default SpeechToText;

องค์ประกอบนี้ส่งคืน a ส่วนตอบสนอง ที่มี HTML <``section``> องค์ประกอบที่มีสาม divs:

  • .button-container มีสองปุ่มที่จะใช้เพื่อเริ่มและหยุดการรู้จำคำพูด
  • .words มี contentEditable และ suppressContentEditableWarning คุณลักษณะเพื่อให้องค์ประกอบนี้สามารถแก้ไขได้และระงับคำเตือนจาก React
  • อื่น .button-container มีปุ่มอีกสองปุ่มที่จะใช้เพื่อรีเซ็ตและแปลงคำพูดเป็น PDF ตามลำดับ

การจัดแต่งทรงผมเป็นอีกเรื่องหนึ่งโดยสิ้นเชิง ฉันจะไม่พูดถึงที่นี่ แต่คุณสามารถใช้รูปแบบที่ฉันเขียนเป็นจุดเริ่มต้นสำหรับตัวคุณเอง styles/global.css ไฟล์

ดู CSS แบบเต็ม
html,
body {
  padding: 0;
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
    Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
}

a {
  color: inherit;
  text-decoration: none;
}

* {
  box-sizing: border-box;
}

.home {
  background-color: #333;
  min-height: 100%;
  padding: 0 1rem;
  padding-bottom: 3rem;
}

h1 {
  width: 100%;
  max-width: 400px;
  margin: auto;
  padding: 2rem 0;
  text-align: center;
  text-transform: capitalize;
  color: white;
  font-size: 1rem;
}

.button-container {
  text-align: center;
  display: flex;
  justify-content: center;
  gap: 3rem;
}

button {
  color: white;
  background-color: var(--bgColor);
  font-size: 1.2rem;
  padding: 0.5rem 1.5rem;
  border: none;
  border-radius: 20px;
  cursor: pointer;
}

button:hover {
  opacity: 0.9;
}

button:active {
  transform: scale(0.99);
}

.words {
  max-width: 700px;
  margin: 50px auto;
  height: 50vh;
  border-radius: 5px;
  padding: 1rem 2rem 1rem 5rem;
  background-image: -webkit-gradient(
    linear,
    0 0,
    0 100%,
    from(#d9eaf3),
    color-stop(4%, #fff)
  ) 0 4px;
  background-size: 100% 3rem;
  background-attachment: scroll;
  position: relative;
  line-height: 3rem;
  overflow-y: auto;
}

.success,
.error {
  background-color: #fff;
  margin: 1rem auto;
  padding: 0.5rem 1rem;
  border-radius: 5px;
  width: max-content;
  text-align: center;
  display: block;
}

.success {
  color: green;
}

.error {
  color: red;
}

ตัวแปร CSS ในนั้นกำลังถูกใช้เพื่อควบคุมสีพื้นหลังของปุ่มต่างๆ

มาดูการเปลี่ยนแปลงล่าสุดกัน! วิ่ง npm run dev ในเทอร์มินัลและตรวจสอบ

คุณควรเห็นสิ่งนี้ในเบราว์เซอร์เมื่อคุณเยี่ยมชม http://localhost:3000:

การแปลงคำพูดเป็น PDF ด้วย NextJS และ ExpressJS

การแปลงคำพูดเป็นข้อความครั้งแรกของเรา!

การดำเนินการแรกที่ต้องทำคือนำเข้าการขึ้นต่อกันที่จำเป็นลงใน .ของเรา <SpeechToText> ส่วนประกอบ:

import React, { useRef, useState } from "react";
import SpeechRecognition, {
  useSpeechRecognition,
} from "react-speech-recognition";
import axios from "axios";

จากนั้นเราจะตรวจสอบว่าเบราว์เซอร์รองรับการรู้จำเสียงหรือไม่ และแสดงการแจ้งเตือนหากไม่รองรับ:

const speechRecognitionSupported =
  SpeechRecognition.browserSupportsSpeechRecognition();

if (!speechRecognitionSupported) {
  return <div>Your browser does not support speech recognition.</div>;
}

ต่อไปเรามาแยก transcript และ resetTranscript จาก useSpeechRecognition() ตะขอ:

const { transcript, resetTranscript } = useSpeechRecognition();

นี่คือสิ่งที่เราต้องการสำหรับรัฐที่จัดการ listening:

const [listening, setListening] = useState(false);

เรายังต้องการ ref สำหรับ div กับ contentEditable แอตทริบิวต์ จากนั้นเราต้องเพิ่ม ref คุณลักษณะของมันและผ่าน transcript as children:

const textBodyRef = useRef(null);

…และ:

<div
  className="words"
  contentEditable
  ref={textBodyRef}
  suppressContentEditableWarning={true}
  >
  {transcript}
</div>

สิ่งสุดท้ายที่เราต้องการที่นี่คือฟังก์ชันที่ทริกเกอร์การรู้จำเสียงพูดและเชื่อมโยงฟังก์ชันนั้นกับ onClick ฟังเหตุการณ์ของปุ่มของเรา ปุ่มตั้งค่าการฟัง true และทำให้ดำเนินไปอย่างต่อเนื่อง เราจะปิดการใช้งานปุ่มในขณะที่อยู่ในสถานะนั้นเพื่อป้องกันไม่ให้เราเริ่มต้นกิจกรรมเพิ่มเติม

const startListening = () => {
  setListening(true);
  SpeechRecognition.startListening({
    continuous: true,
  });
};

…และ:

<button
  type="button"
  onClick={startListening}
  style={{ "--bgColor": "blue" }}
  disabled={listening}
>
  Start
</button>

การคลิกที่ปุ่มควรเริ่มต้นการถอดความ

ฟังก์ชั่นเพิ่มเติม

โอเค เรามีส่วนประกอบที่สามารถ เริ่มต้น การฟัง. แต่ตอนนี้เราจำเป็นต้องทำอย่างอื่นด้วย เช่น stopListening, resetText และ handleConversion. มาสร้างฟังก์ชันเหล่านั้นกัน

const stopListening = () => {
  setListening(false);
  SpeechRecognition.stopListening();
};

const resetText = () => {
  stopListening();
  resetTranscript();
  textBodyRef.current.innerText = "";
};

const handleConversion = async () => {}

แต่ละฟังก์ชันจะถูกเพิ่มในan onClick ฟังเหตุการณ์บนปุ่มที่เหมาะสม:

<button
  type="button"
  onClick={stopListening}
  style={{ "--bgColor": "orange" }}
  disabled={listening === false}
>
  Stop
</button>

<div className="button-container">
  <button
    type="button"
    onClick={resetText}
    style={{ "--bgColor": "red" }}
  >
    Reset
  </button>
  <button
    type="button"
    style={{ "--bgColor": "green" }}
    onClick={handleConversion}
  >
    Convert to pdf
  </button>
</div>

พื้นที่ handleConversion ฟังก์ชั่นเป็นแบบอะซิงโครนัสเพราะในที่สุดเราจะทำการร้องขอ API ปุ่ม "หยุด" มีแอตทริบิวต์ที่ปิดใช้งานซึ่งจะถูกเรียกใช้เมื่อการฟังเป็นเท็จ

หากเรารีสตาร์ทเซิร์ฟเวอร์และรีเฟรชเบราว์เซอร์ ตอนนี้เราสามารถเริ่ม หยุด และรีเซ็ตการถอดเสียงพูดในเบราว์เซอร์ได้แล้ว

ตอนนี้สิ่งที่เราต้องการคือเพื่อให้แอป คัดลอก ที่จำคำพูดได้โดยแปลงเป็นไฟล์ PDF สำหรับสิ่งนั้น เราต้องการพาธฝั่งเซิร์ฟเวอร์จาก Express.js

การตั้งค่าเส้นทาง API

จุดประสงค์ของเส้นทางนี้คือการนำไฟล์ข้อความ แปลงเป็น PDF เขียน PDF นั้นไปยังระบบไฟล์ของเรา จากนั้นส่งการตอบกลับไปยังไคลเอ็นต์

ในการตั้งค่า เราจะเปิด server/index.js ไฟล์และนำเข้า html-pdf-node และ fs การพึ่งพาที่จะใช้ในการเขียนและเปิดระบบไฟล์ของเรา

const HTMLToPDF = require("html-pdf-node");
const fs = require("fs");
const cors = require("cors)

ต่อไป เราจะตั้งค่าเส้นทางของเรา:

app.use(cors())
app.use(express.json())

app.post("/", (req, res) => {
  // etc.
})

จากนั้นเราดำเนินการกำหนดตัวเลือกที่จำเป็นเพื่อใช้งาน html-pdf-node ภายในเส้นทาง:

let options = { format: "A4" };
let file = {
  content: `<html><body><pre style='font-size: 1.2rem'>${req.body.text}</pre></body></html>`,
};

พื้นที่ options วัตถุยอมรับค่าเพื่อกำหนดขนาดและรูปแบบกระดาษ ขนาดกระดาษเป็นไปตามระบบที่แตกต่างจากหน่วยขนาดที่เรามักใช้บนเว็บ ตัวอย่างเช่น, A4 คือขนาดตัวอักษรทั่วไป.

พื้นที่ file object ยอมรับ URL ของเว็บไซต์สาธารณะหรือมาร์กอัป HTML ในการสร้างหน้า HTML เราจะใช้ html, body, pre แท็ก HTML และข้อความจาก req.body.

คุณสามารถใช้สไตล์ใดก็ได้ตามต้องการ

ต่อไปเราจะเพิ่ม trycatch เพื่อจัดการกับข้อผิดพลาดที่อาจปรากฏขึ้นระหว่างทาง:

try {

} catch(error){
  console.log(error);
  res.status(500).send(error);
}

ต่อไปเราจะใช้ generatePdf จาก html-pdf-node ห้องสมุดเพื่อสร้าง pdfBuffer (ไฟล์ PDF ดิบ) จากไฟล์ของเราและสร้างเอกลักษณ์ pdfName:

HTMLToPDF.generatePdf(file, options).then((pdfBuffer) => {
  // console.log("PDF Buffer:-", pdfBuffer);
  const pdfName = "./data/speech" + Date.now() + ".pdf";

  // Next code here
}

จากนั้น เราใช้โมดูลระบบไฟล์เพื่อเขียน อ่าน และ (ใช่ ในที่สุด!) ส่งการตอบกลับไปยังแอปไคลเอนต์:

fs.writeFile(pdfName, pdfBuffer, function (writeError) {
  if (writeError) {
    return res
      .status(500)
      .json({ message: "Unable to write file. Try again." });
  }

  fs.readFile(pdfName, function (readError, readData) {
    if (!readError && readData) {
      // console.log({ readData });
      res.setHeader("Content-Type", "application/pdf");
      res.setHeader("Content-Disposition", "attachment");
      res.send(readData);
      return;
    }

    return res
      .status(500)
      .json({ message: "Unable to write file. Try again." });
  });
});

มาทำลายมันสักหน่อย:

  • พื้นที่ writeFile โมดูลระบบไฟล์ยอมรับชื่อไฟล์ ข้อมูล และฟังก์ชันเรียกกลับที่สามารถส่งคืนข้อความแสดงข้อผิดพลาดได้ หากมีปัญหาในการเขียนไฟล์ หากคุณกำลังทำงานกับ CDN ที่มีจุดสิ้นสุดข้อผิดพลาด คุณสามารถใช้สิ่งเหล่านั้นแทนได้
  • พื้นที่ readFile โมดูลระบบไฟล์ยอมรับชื่อไฟล์และฟังก์ชันเรียกกลับที่มีความสามารถหรือส่งคืนข้อผิดพลาดในการอ่านรวมถึงข้อมูลที่อ่าน เมื่อเราไม่มีข้อผิดพลาดในการอ่านและมีข้อมูลการอ่านแล้ว เราจะสร้างและส่งการตอบกลับไปยังไคลเอ็นต์ อีกครั้ง คุณสามารถแทนที่สิ่งนี้ด้วยปลายทางของ CDN หากคุณมี
  • พื้นที่ res.setHeader("Content-Type", "application/pdf"); บอกเบราว์เซอร์ว่าเรากำลังส่งไฟล์ PDF
  • พื้นที่ res.setHeader("Content-Disposition", "attachment"); บอกให้เบราว์เซอร์ทำการดาวน์โหลดข้อมูลที่ได้รับ

เนื่องจากเส้นทาง API พร้อม เราจึงสามารถใช้ในแอปของเราได้ที่ http://localhost:4000. เราสามารถดำเนินการในส่วนลูกค้าของใบสมัครของเราเพื่อกรอก handleConversion ฟังก์ชัน

การจัดการการแปลง

ก่อนที่เราจะเริ่มทำงานกับ a handleConversion เราต้องสร้างสถานะที่จัดการคำขอ API ของเราสำหรับการโหลด ข้อผิดพลาด ความสำเร็จ และข้อความอื่นๆ เราจะใช้ React's useState ขอตั้งค่านั้น:

const [response, setResponse] = useState({
  loading: false,
  message: "",
  error: false,
  success: false,
});

ตัว Vortex Indicator ได้ถูกนำเสนอลงในนิตยสาร handleConversion ฟังก์ชัน เราจะตรวจสอบเมื่อโหลดหน้าเว็บก่อนเรียกใช้โค้ดของเรา และตรวจสอบให้แน่ใจว่า div กับ editable แอตทริบิวต์ไม่ว่างเปล่า:

if (typeof window !== "undefined") {
const userText = textBodyRef.current.innerText;
  // console.log(textBodyRef.current.innerText);

  if (!userText) {
    alert("Please speak or write some text.");
    return;
  }
}

เราดำเนินการโดยห่อคำขอ API ในที่สุดของเราใน a trycatchจัดการข้อผิดพลาดที่อาจเกิดขึ้น และอัปเดตสถานะการตอบสนอง:

try {

} catch(error){
  setResponse({
    ...response,
    loading: false,
    error: true,
    message:
      "An unexpected error occurred. Text not converted. Please try again",
    success: false,
  });
}

ต่อไป เราตั้งค่าบางอย่างสำหรับสถานะการตอบสนองและตั้งค่า config สำหรับ axios และส่งคำขอโพสต์ไปยังเซิร์ฟเวอร์:

setResponse({
  ...response,
  loading: true,
  message: "",
  error: false,
  success: false,
});
const config = {
  headers: {
    "Content-Type": "application/json",
  },
  responseType: "blob",
};

const res = await axios.post(
  "http://localhost:4000",
  {
    text: textBodyRef.current.innerText,
  },
  config
);

เมื่อเราได้รับการตอบสนองที่ประสบความสำเร็จ เราจะตั้งค่าสถานะการตอบกลับด้วยค่าที่เหมาะสม และสั่งให้เบราว์เซอร์ดาวน์โหลดไฟล์ PDF ที่ได้รับ:

setResponse({
  ...response,
  loading: false,
  error: false,
  message:
    "Conversion was successful. Your download will start soon...",
  success: true,
});

// convert the received data to a file
const url = window.URL.createObjectURL(new Blob([res.data]));
// create an anchor element
const link = document.createElement("a");
// set the href of the created anchor element
link.href = url;
// add the download attribute, give the downloaded file a name
link.setAttribute("download", "yourfile.pdf");
// add the created anchor tag to the DOM
document.body.appendChild(link);
// force a click on the link to start a simulated download
link.click();

และเราสามารถใช้สิ่งต่อไปนี้ด้านล่าง contentEditable div สำหรับแสดงข้อความ:

<div>
  {response.success && <i className="success">{response.message}</i>}
  {response.error && <i className="error">{response.message}</i>}
</div>

รหัสสุดท้าย

ฉันได้รวมทุกอย่างไว้ใน GitHub เพื่อให้คุณสามารถตรวจสอบซอร์สโค้ดแบบเต็มสำหรับทั้งเซิร์ฟเวอร์และไคลเอนต์

ประทับเวลา:

เพิ่มเติมจาก เคล็ดลับ CSS