Beszéd konvertálása PDF-be a NextJS és az ExpressJS PlatoBlockchain Data Intelligence segítségével. Függőleges keresés. Ai.

Beszéd konvertálása PDF-be a NextJS és ExpressJS segítségével

Mivel a beszédfelületek egyre népszerűbbek, érdemes megvizsgálni néhány dolgot, amit a beszédinterakciókkal tehetünk. Például, mi lenne, ha mondanánk valamit, és azt átíratnánk, és letölthető PDF-ként kipumpálnánk?

Nos, spoiler figyelmeztetés: mi feltétlenül tud csináld! Vannak olyan könyvtárak és keretrendszerek, amelyeket összekövezhetünk, hogy ez megvalósuljon, és ebben a cikkben ezt fogjuk megtenni.

Ezeket az eszközöket használjuk

Először is ez a két nagy szereplő: Next.js és Express.js.

Next.js további funkciókat tartalmaz a Reacthoz, beleértve a statikus helyek építésének kulcsfontosságú funkcióit. Sok fejlesztő számára bevált, mert olyan szolgáltatásokat kínál, mint például a dinamikus útválasztás, a képoptimalizálás, a beépített tartomány- és aldomain-útválasztás, a gyors frissítések, a fájlrendszer-útválasztás és az API-útvonalak… sok-sok más dolog.

Esetünkben mindenképpen Next.js kell hozzá API útvonalak kliens szerverünkön. Olyan útvonalat szeretnénk, amely egy szöveges fájlt átvesz, PDF-be konvertálja, beírja a fájlrendszerünkbe, majd választ küld a kliensnek.

Express.js lehetővé teszi, hogy egy kis Node.js alkalmazást indítsunk útválasztással, HTTP-segédekkel és sablonokkal. Ez egy szerver a saját API-nkhoz, amelyre szükségünk lesz, amikor adatokat továbbítunk és értelmezünk a dolgok között.

Van még néhány függőségünk, amelyeket használni fogunk:

  1. reagál-beszéd-felismerés: A beszédet szöveggé alakító könyvtár, amely elérhetővé teszi a React összetevők számára.
  2. regenerátor-futási idő: Egy könyvtár a „regeneratorRuntime nincs definiálva” hiba, amely a Next.js fájlban jelenik meg, ha reagál a beszédfelismerésre
  3. html-pdf-csomópont: HTML-oldalak vagy nyilvános URL-címek PDF-formátumba konvertálására szolgáló könyvtár
  4. Axios: Egy könyvtár HTTP-kérések lebonyolításához mind a böngészőben, mind a Node.js-ben
  5. Cors: Olyan könyvtár, amely lehetővé teszi a források közötti erőforrások megosztását

Felállítása

Első lépésként két projektmappát szeretnénk létrehozni, egyet a kliensnek és egyet a szervernek. Nevezd el őket, ahogy akarod. Megnevezem az enyémet audio-to-pdf-client és a audio-to-pdf-server, Ill.

A Next.js kliensoldali használatának leggyorsabb elindítása a rendszerindítás Create-next-app. Tehát nyissa meg a terminált, és futtassa a következő parancsot az ügyfél projekt mappájából:

npx create-next-app client

Most szükségünk van az Express szerverünkre. Meg tudjuk szerezni cd-a kiszolgáló projekt mappájába, és futtassa a npm init parancs. A package.json A fájl létrejön a kiszolgáló projekt mappájában, miután elkészült.

Még ténylegesen telepítenünk kell az Expresst, úgyhogy most tegyük meg npm install express. Most létrehozhatunk egy újat index.js fájlt a szerver projekt mappájába, és dobja be ezt a kódot:

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

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

Készen áll a szerver futtatására?

node index.js

Szükségünk lesz még néhány mappára és egy másik fájlra a továbblépéshez:

  • Hozzon létre egy components mappát az ügyfél projekt mappájában.
  • Hozzon létre egy SpeechToText.jsx fájl a components almappát.

Mielőtt továbbmennénk, van egy kis takarításunk. Pontosabban, le kell cserélnünk az alapértelmezett kódot a pages/index.js fájl ezzel:

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="/hu/favicon.ico" />
      </Head>

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

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

Az importált SpeechToText komponens végül exportálásra kerül components/SpeechToText.jsx.

Telepítsük a többi függőséget

Rendben, az alkalmazásunk kezdeti beállításai már nincsenek útban. Most már telepíthetjük azokat a könyvtárakat, amelyek kezelik a továbbított adatokat.

Ügyfélfüggőségeinket a következőkkel tudjuk telepíteni:

npm install react-speech-recognition regenerator-runtime axios

Az Express szerver függőségei következnek, szóval lássuk cd a szerver projekt mappájába, és telepítse ezeket:

npm install html-pdf-node cors

Valószínűleg itt az ideje megállni, és megbizonyosodni arról, hogy a projektmappákban lévő fájlok rendben vannak. A következőnek kell lennie ezen a ponton az ügyfél projekt mappájában:

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

És a következőnek kell lennie a szerver projekt mappájában:

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

A felhasználói felület építése

Nos, a beszédünk PDF-be nem lenne olyan nagyszerű, ha nincs mód vele interakcióra, ezért készítsünk hozzá egy React összetevőt, amelyet hívhatunk. <SpeechToText>.

Teljesen használhatja a saját jelölését. Íme, hogy képet adjak az összeállított darabokról:

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;

Ez a komponens visszaadja a Reakció fragmentum amely HTML-t tartalmaz <``section``> három div elemet tartalmazó elem:

  • .button-container két gombot tartalmaz, amelyek a beszédfelismerés elindítására és leállítására szolgálnak.
  • .words több mint contentEditable és a suppressContentEditableWarning attribútumokat, hogy ez az elem szerkeszthető legyen, és elrejtsék a React figyelmeztetéseit.
  • Másik .button-container két további gombot tartalmaz, amelyek a beszéd visszaállítására és PDF-be konvertálására szolgálnak.

A stílus teljesen más dolog. Itt nem megyek bele, de nyugodtan használhatsz néhány általam írt stílust kiindulópontként a sajátodhoz. styles/global.css fájlt.

Teljes CSS megtekintése
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;
}

Az ott található CSS-változók a gombok háttérszínének szabályozására szolgálnak.

Lássuk a legújabb változásokat! Fuss npm run dev a terminálban, és nézd meg őket.

Ezt látnia kell a böngészőben, amikor meglátogatja http://localhost:3000:

Beszéd konvertálása PDF-be a NextJS és ExpressJS segítségével

Első beszédünk szöveggé konvertálása!

Az első lépés a szükséges függőségek importálása a mi rendszerünkbe <SpeechToText> összetevő:

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

Ezután ellenőrizzük, hogy a böngésző támogatja-e a beszédfelismerést, és értesítést küldünk, ha nem támogatja:

const speechRecognitionSupported =
  SpeechRecognition.browserSupportsSpeechRecognition();

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

Következő lépésként kivonjuk transcript és a resetTranscript tól useSpeechRecognition() horog:

const { transcript, resetTranscript } = useSpeechRecognition();

Erre van szükségünk annak az államnak, amelyik kezeli listening:

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

Szükségünk van továbbá a ref az div a ... val contentEditable attribútumot, akkor hozzá kell adnunk a ref tulajdonít neki és adja át transcript as children:

const textBodyRef = useRef(null);

…és:

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

Az utolsó dolog, amire itt szükségünk van, egy olyan funkció, amely kiváltja a beszédfelismerést, és ezt a funkciót a onClick gombunk eseményfigyelője. A gomb beállítja a hallgatást true és folyamatos működésre készteti. Amíg ebben az állapotban van, letiltjuk a gombot, nehogy további eseményeket indítsunk el.

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

…és:

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

A gombra kattintva most elindul az átírás.

További funkciók

Rendben, van egy alkatrészünk, ami képes kezdet hallgat. De most szükségünk van néhány más dologra is, mint pl stopListening, resetText és a handleConversion. Készítsük el ezeket a függvényeket.

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

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

const handleConversion = async () => {}

Mindegyik funkció hozzáadódik egy onClick eseményfigyelő a megfelelő gombokon:

<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>

A handleConversion függvény aszinkron, mert végül API kérést fogunk benyújtani. A „Stop” gombnak van egy letiltott attribútuma, amely akkor aktiválódik, ha a hallás hamis.

Ha újraindítjuk a szervert és frissítjük a böngészőt, akkor most már elindíthatjuk, leállíthatjuk és visszaállíthatjuk a beszédátírásunkat a böngészőben.

Most az alkalmazásra van szükségünk lemásol amely felismerte a beszédet PDF-fájllá alakítva. Ehhez szükségünk van az Express.js szerveroldali elérési útjára.

Az API útvonal beállítása

Ennek az útvonalnak az a célja, hogy vegyünk egy szöveges fájlt, konvertáljuk PDF formátumba, beírjuk azt a fájlrendszerünkbe, majd választ küldünk az ügyfélnek.

A beállításhoz megnyitjuk a server/index.js fájlt és importálja a html-pdf-node és a fs a fájlrendszer írásához és megnyitásához használt függőségek.

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

Ezután felállítjuk az útvonalunkat:

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

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

Ezután folytatjuk a használathoz szükséges opciók meghatározását html-pdf-node az útvonalon belül:

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

A options objektum elfogad egy értéket a papír méretének és stílusának beállításához. A papírméretek más rendszert követnek, mint a weben általában használt méretezési egységek. Például, Az A4 a tipikus betűméret.

A file Az objektum vagy egy nyilvános webhely URL-címét vagy HTML-jelölést fogad el. HTML oldalunk létrehozásához a html, body, pre HTML címkék és a szöveg a req.body.

Bármilyen stílust alkalmazhat, amelyet választott.

Ezután hozzáadjuk a trycatch az útközben esetlegesen felbukkanó hibák kezelésére:

try {

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

Ezután a generatePdf tól html-pdf-node könyvtár létrehozásához a pdfBuffer (a nyers PDF fájl) a fájlunkból, és hozzon létre egy egyedit pdfName:

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

  // Next code here
}

Innentől kezdve a fájlrendszer modul segítségével írunk, olvasunk és (igen, végül!) választ küldünk az ügyfélalkalmazásnak:

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." });
  });
});

Ezt bontsuk egy kicsit:

  • A writeFile A fájlrendszer modul elfogad egy fájlnevet, adatokat és egy visszahívási funkciót, amely hibaüzenetet küldhet, ha probléma adódik a fájl írása során. Ha olyan CDN-nel dolgozik, amely hibavégpontokat biztosít, használhatja ezeket helyette.
  • A readFile A fájlrendszer modul olyan fájlnevet és visszahívási függvényt fogad el, amely képes vagy visszaadja az olvasási hibát, valamint az olvasási adatokat. Ha nincs olvasási hiba, és az olvasott adatok jelen vannak, elkészítjük és választ küldünk az ügyfélnek. Ez ismét lecserélhető a CDN végpontjaira, ha rendelkezik velük.
  • A res.setHeader("Content-Type", "application/pdf"); közli a böngészővel, hogy PDF-fájlt küldünk.
  • A res.setHeader("Content-Disposition", "attachment"); utasítja a böngészőt, hogy tegye letölthetővé a kapott adatokat.

Mivel az API-útvonal készen áll, használhatjuk az alkalmazásunkban a címen http://localhost:4000. Folytathatjuk kérelmünk kliens részére történő kitöltését handleConversion funkciót.

Az átalakítás kezelése

Mielőtt elkezdhetnénk dolgozni a handleConversion függvényt, létre kell hoznunk egy állapotot, amely kezeli az API-kéréseinket a betöltésre, a hibára, a sikerre és az egyéb üzenetekre. A React-ot fogjuk használni useState kampó a beállításhoz:

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

A handleConversion funkciót, a kód futtatása előtt ellenőrizzük, hogy a weboldal betöltődött-e, és ellenőrizzük, hogy a div a ... val editable Az attribútum nem üres:

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

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

Úgy járunk el, hogy esetleges API-kérésünket a trycatch, az esetlegesen felmerülő hibák kezelése és a válaszállapot frissítése:

try {

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

Ezután beállítunk néhány értéket a válaszállapothoz, és beállítjuk a konfigurációt is axios és küldjön egy bejegyzési kérelmet a szervernek:

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
);

Miután megkaptuk a sikeres választ, beállítjuk a válasz állapotát a megfelelő értékekkel, és utasítjuk a böngészőt a kapott PDF letöltésére:

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();

És használhatjuk a következőket a contentEditable alatt div üzenetek megjelenítéséhez:

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

Végső kód

Mindent összecsomagoltam a GitHubon, így mind a szerver, mind a kliens teljes forráskódját megnézheti.

Időbélyeg:

Még több CSS trükkök