המרת דיבור ל-PDF עם NextJS ו-ExpressJS PlatoBlockchain Data Intelligence. חיפוש אנכי. איי.

המרת דיבור ל-PDF עם NextJS ו-ExpressJS

מכיוון שממשקי דיבור הופכים לעניין יותר, כדאי לחקור כמה מהדברים שאנו יכולים לעשות עם אינטראקציות דיבור. כאילו, מה אם היינו יכולים להגיד משהו ולהעביר את זה לתמלל ולהוציא אותו כקובץ PDF להורדה?

ובכן, התראת ספוילר: אנחנו בהחלט יכול לעשות את זה! יש ספריות ומסגרות שאנחנו יכולים לאסוף יחד כדי לגרום לזה לקרות, וזה מה שאנחנו הולכים לעשות ביחד במאמר הזה.

אלו הכלים שבהם אנו משתמשים

ראשית, אלו שני השחקנים הגדולים: Next.js ו-Express.js.

Next.js מטפל בפונקציות נוספות ל-React, כולל תכונות מפתח לבניית אתרים סטטיים. זוהי בחירה עבור מפתחים רבים בגלל מה שהיא מציעה ישירות מהקופסה, כמו ניתוב דינמי, אופטימיזציה של תמונות, ניתוב מובנה של דומיין ותת-דומיין, רענון מהיר, ניתוב מערכת קבצים ומסלולי API... בין הרבה הרבה דברים אחרים.

במקרה שלנו, אנחנו בהחלט צריכים את Next.js בשבילו מסלולי API בשרת הלקוח שלנו. אנחנו רוצים מסלול שלוקח קובץ טקסט, ממיר אותו ל-PDF, כותב אותו למערכת הקבצים שלנו, ואז שולח תגובה ללקוח.

Express.js מאפשר לנו להפעיל אפליקציית Node.js קטנה עם ניתוב, עוזרי HTTP ותבניות. זה שרת עבור ה-API שלנו, וזה מה שנצטרך כשאנחנו מעבירים ומנתחים נתונים בין דברים.

יש לנו כמה תלות אחרות שנשתמש:

  1. להגיב-דיבור-זיהוי: ספרייה להמרת דיבור לטקסט, מה שהופך אותו זמין לרכיבי React.
  2. זמן ריצה מחדש: ספריה לפתרון בעיות של "regeneratorRuntime אינו מוגדר" שגיאה המופיעה ב-Next.js בעת שימוש בזיהוי-דיבור-react
  3. html-pdf-node: ספרייה להמרת דף HTML או כתובת אתר ציבורית ל-PDF
  4. אקסיוס: ספריה לביצוע בקשות HTTP הן בדפדפן והן ב-Node.js
  5. cors: ספרייה המאפשרת שיתוף משאבים בין מקורות

מגדיר

הדבר הראשון שאנו רוצים לעשות הוא ליצור שתי תיקיות פרויקט, אחת ללקוח ואחת לשרת. תן להם שם איך שתרצה. אני נותן את השם שלי audio-to-pdf-client ו audio-to-pdf-server, בהתאמה.

הדרך המהירה ביותר להתחיל עם Next.js בצד הלקוח היא לאתחל אותו באמצעות create-next-app. אז, פתח את הטרמינל שלך והפעל את הפקודה הבאה מתיקיית פרויקט הלקוח שלך:

npx create-next-app client

עכשיו אנחנו צריכים את שרת האקספרס שלנו. אנחנו יכולים להסתדר cd- נכנסים לתיקיית פרויקט השרת ומריצים את npm init פקודה. א 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="/iw/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

בניית ממשק המשתמש

ובכן, הנאום ל-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;

רכיב זה מחזיר א קטע תגובה שמכיל 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 האובייקט מקבל את כתובת האתר של אתר ציבורי או סימון 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 פונקציה.

טיפול בהמרה

לפני שנוכל להתחיל לעבוד על א handleConversion פונקציה, עלינו ליצור מצב שמטפל בבקשות ה-API שלנו לטעינה, שגיאה, הצלחה והודעות אחרות. אנחנו הולכים להשתמש ב-React's useState הוק כדי להגדיר את זה:

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

ב 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,
  });
}

לאחר מכן, אנו מגדירים כמה ערכים עבור מצב התגובה וגם מגדירים תצורה עבור 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