Pretvarjanje govora v PDF z NextJS in ExpressJS PlatoBlockchain Data Intelligence. Navpično iskanje. Ai.

Pretvarjanje govora v PDF z NextJS in ExpressJS

Ker govorni vmesniki postajajo vse bolj priljubljeni, je vredno raziskati nekatere stvari, ki jih lahko naredimo z govornimi interakcijami. Na primer, kaj če bi lahko rekli nekaj in dali to prepisati in natisniti kot prenosljiv PDF?

No, opozorilo o spojlerju: vsekakor lahko naredi to! Obstajajo knjižnice in ogrodja, ki jih lahko sestavimo, da se to zgodi, in to je tisto, kar bomo skupaj storili v tem članku.

To so orodja, ki jih uporabljamo

Prvič, to sta dva velika igralca: Next.js in Express.js.

Naprej.js dodaja dodatne funkcije za React, vključno s ključnimi funkcijami za izdelavo statičnih spletnih mest. To je izbira za mnoge razvijalce zaradi tega, kar ponuja takoj po izdelavi, kot so dinamično usmerjanje, optimizacija slik, vgrajeno usmerjanje v domeni in poddomeni, hitro osveževanje, usmerjanje v datotečnem sistemu in poti API-jev ... veliko, veliko drugih stvari.

V našem primeru zanj vsekakor potrebujemo Next.js API poti na našem odjemalskem strežniku. Želimo pot, ki sprejme besedilno datoteko, jo pretvori v PDF, zapiše v naš datotečni sistem in nato pošlje odgovor odjemalcu.

Express.js nam omogoča, da zaženemo majhno aplikacijo Node.js z usmerjanjem, pomočniki HTTP in predlogami. To je strežnik za naš lastni API, kar bomo potrebovali pri posredovanju in razčlenjevanju podatkov med stvarmi.

Imamo nekaj drugih odvisnosti, ki jih bomo dali v uporabo:

  1. reagiranje-prepoznavanje govora: Knjižnica za pretvorbo govora v besedilo, tako da je na voljo komponentam React.
  2. regenerator-čas delovanja: knjižnica za odpravljanje težav zregeneratorRuntime ni definirano«, ki se prikaže v Next.js pri uporabi prepoznavanja govora reakcije
  3. html-pdf-vozlišče: knjižnica za pretvorbo strani HTML ali javnega URL-ja v PDF
  4. axios: knjižnica za izdelavo zahtev HTTP v brskalniku in Node.js
  5. korzi: Knjižnica, ki omogoča skupno rabo virov med izvori

Postavitev

Prva stvar, ki jo želimo narediti, je ustvariti dve projektni mapi, eno za odjemalca in eno za strežnik. Poimenujte jih, kakor želite. Jaz svojega imenujem audio-to-pdf-client in audio-to-pdf-serverOz.

Najhitrejši način, da začnete uporabljati Next.js na strani odjemalca, je, da ga zaženete z create-next-app. Torej odprite terminal in zaženite naslednji ukaz iz mape vašega odjemalskega projekta:

npx create-next-app client

Zdaj potrebujemo naš Express strežnik. Lahko ga dobimo cd-ing v mapo projekta strežnika in zagon npm init ukaz. A package.json datoteka bo ustvarjena v mapi strežniškega projekta, ko bo končana.

Še vedno moramo dejansko namestiti Express, zato naredimo to zdaj z npm install express. Zdaj lahko ustvarimo novo index.js datoteko v mapi projekta strežnika in tja spustite to kodo:

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

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

Ste pripravljeni zagnati strežnik?

node index.js

Za nadaljevanje bomo potrebovali še nekaj map in še eno datoteko:

  • Ustvarite components v mapi odjemalskega projekta.
  • Ustvarite SpeechToText.jsx datoteko v components podmapa.

Preden gremo naprej, nas čaka še malo čiščenja. Natančneje, zamenjati moramo privzeto kodo v pages/index.js datoteka s tem:

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

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

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

Uvoženo SpeechToText komponenta bo na koncu izvožena iz components/SpeechToText.jsx.

Namestimo druge odvisnosti

V redu, končali smo začetno nastavitev naše aplikacije. Zdaj lahko namestimo knjižnice, ki obravnavajo posredovane podatke.

Naše odvisnosti odjemalca lahko namestimo z:

npm install react-speech-recognition regenerator-runtime axios

Naslednje so naše odvisnosti od strežnika Express, zato pojdimo cd v mapo strežniškega projekta in namestite tiste:

npm install html-pdf-node cors

Verjetno je pravi trenutek, da se ustavimo in se prepričamo, da so datoteke v mapah naših projektov neoporečne. Tukaj je tisto, kar bi morali imeti v mapi odjemalskega projekta na tej točki:

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

In tukaj je tisto, kar bi morali imeti v mapi strežniškega projekta:

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

Gradnja uporabniškega vmesnika

No, naš govor v PDF ne bi bil tako dober, če ne bi bilo možnosti za interakcijo z njim, zato naredimo zanj komponento React, ki jo lahko pokličemo <SpeechToText>.

V celoti lahko uporabite svoje oznake. Tukaj je tisto, kar vam dam idejo o delih, ki jih sestavljamo:

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;

Ta komponenta vrne a Fragment reakcije ki vsebuje HTML <``section``> element, ki vsebuje tri dive:

  • .button-container vsebuje dva gumba, ki bosta uporabljena za zagon in zaustavitev prepoznavanja govora.
  • .words je contentEditable in suppressContentEditableWarning atribute, da omogočite urejanje tega elementa in preprečite morebitna opozorila Reacta.
  • Še ena .button-container ima še dva gumba, ki bosta uporabljena za ponastavitev in pretvorbo govora v PDF.

Stajling je nekaj povsem drugega. Tukaj se ne bom spuščal v to, vendar lahko uporabite nekatere sloge, ki sem jih napisal, kot izhodišče za svoje styles/global.css Datoteka.

Oglejte si celoten 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;
}

Tamkajšnje spremenljivke CSS se uporabljajo za nadzor barve ozadja gumbov.

Oglejmo si najnovejše spremembe! Teči npm run dev v terminalu in jih preverite.

To bi morali videti v brskalniku, ko obiščete http://localhost:3000:

Pretvarjanje govora v PDF z NextJS in ExpressJS

Naša prva pretvorba govora v besedilo!

Najprej morate uvoziti potrebne odvisnosti v naš <SpeechToText> komponenta:

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

Nato preverimo, ali brskalnik podpira prepoznavanje govora, in prikažemo obvestilo, če ni podprto:

const speechRecognitionSupported =
  SpeechRecognition.browserSupportsSpeechRecognition();

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

Naprej, pojdimo ekstrahirati transcript in resetTranscript Iz useSpeechRecognition() kljuka:

const { transcript, resetTranscript } = useSpeechRecognition();

To je tisto, kar potrebujemo za državo, ki upravlja listening:

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

Potrebujemo tudi a ref za div s contentEditable atribut, potem moramo dodati ref pripisati in prenesti transcript as children:

const textBodyRef = useRef(null);

…in:

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

Zadnja stvar, ki jo potrebujemo, je funkcija, ki sproži prepoznavanje govora in to funkcijo poveže z onClick poslušalec dogodkov našega gumba. Gumb nastavi poslušanje true in omogoča neprekinjeno delovanje. Gumb bomo onemogočili, ko je v tem stanju, da preprečimo sprožitev dodatnih dogodkov.

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

…in:

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

S klikom na gumb bi se zdaj moral zagnati prepis.

Več funkcij

OK, torej imamo komponento, ki zmore Začetek poslušanje. Zdaj pa ga potrebujemo tudi za nekaj drugih stvari, npr stopListening, resetText in handleConversion. Naredimo te funkcije.

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

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

const handleConversion = async () => {}

Vsaka od funkcij bo dodana v onClick poslušalec dogodkov na ustreznih gumbih:

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

O handleConversion funkcija je asinhrona, ker bomo sčasoma naredili zahtevo API. Gumb »Stop« ima onemogočen atribut, ki bi se sprožil, ko je poslušanje napačno.

Če znova zaženemo strežnik in osvežimo brskalnik, lahko zdaj zaženemo, ustavimo in ponastavimo prepis govora v brskalniku.

Zdaj potrebujemo, da aplikacija zavezal ki je prepoznal govor tako, da ga pretvori v datoteko PDF. Za to potrebujemo strežniško pot iz Express.js.

Nastavitev poti API

Namen te poti je vzeti besedilno datoteko, jo pretvoriti v PDF, napisati ta PDF v naš datotečni sistem in nato poslati odgovor stranki.

Za nastavitev bi odprli server/index.js datoteko in uvozite html-pdf-node in fs odvisnosti, ki bodo uporabljene za pisanje in odpiranje našega datotečnega sistema.

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

Nato bomo nastavili našo pot:

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

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

Nato nadaljujemo z opredelitvijo naših možnosti, potrebnih za uporabo html-pdf-node znotraj poti:

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

O options objekt sprejme vrednost za nastavitev velikosti in sloga papirja. Velikosti papirja sledijo precej drugačnemu sistemu kot enote za velikost, ki jih običajno uporabljamo v spletu. na primer A4 je tipična velikost črk.

O file objekt sprejme URL javnega spletnega mesta ali oznako HTML. Za ustvarjanje naše strani HTML bomo uporabili html, body, pre Oznake HTML in besedilo iz req.body.

Uporabite lahko poljuben stil po vaši izbiri.

Nato bomo dodali a trycatch za obravnavo morebitnih napak, ki se lahko pojavijo na poti:

try {

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

Nato bomo uporabili generatePdf Iz html-pdf-node knjižnica za ustvarjanje a pdfBuffer (surovo datoteko PDF) iz naše datoteke in ustvarite edinstveno pdfName:

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

  // Next code here
}

Od tam uporabljamo modul datotečnega sistema za pisanje, branje in (ja, končno!) pošiljanje odgovora odjemalski aplikaciji:

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

Razčlenimo to malo:

  • O writeFile modul datotečnega sistema sprejme ime datoteke, podatke in funkcijo povratnega klica, ki lahko vrne sporočilo o napaki, če pride do težave pri zapisovanju v datoteko. Če delate s CDN, ki ponuja končne točke napak, jih lahko uporabite namesto tega.
  • O readFile modul datotečnega sistema sprejme ime datoteke in funkcijo povratnega klica, ki lahko ali vrne napako pri branju in prebrane podatke. Ko nimamo nobene napake pri branju in so prebrani podatki prisotni, bomo sestavili in poslali odgovor stranki. Še enkrat, to je mogoče nadomestiti s končnimi točkami vašega CDN, če jih imate.
  • O res.setHeader("Content-Type", "application/pdf"); pove brskalniku, da pošiljamo datoteko PDF.
  • O res.setHeader("Content-Disposition", "attachment"); pove brskalniku, naj omogoči prenos prejetih podatkov.

Ker je pot API pripravljena, jo lahko uporabimo v naši aplikaciji na http://localhost:4000. Za dokončanje lahko nadaljujemo z odjemalskim delom naše prijave handleConversion Funkcija.

Ravnanje s pretvorbo

Preden lahko začnemo delati na a handleConversion funkcijo, moramo ustvariti stanje, ki obravnava naše zahteve API-ja za nalaganje, napake, uspeh in druga sporočila. Uporabili bomo Reactov useState kavelj za nastavitev tega:

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

v handleConversion funkcijo, bomo preverili, kdaj je spletna stran naložena, preden zaženemo našo kodo, in se prepričali div s editable atribut ni prazen:

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

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

Nadaljujemo tako, da našo morebitno zahtevo API zavijemo v a trycatch, obravnava morebitne napake, ki se lahko pojavi, in posodablja stanje odziva:

try {

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

Nato smo nastavili nekaj vrednosti za stanje odziva in nastavili tudi konfiguracijo za axios in pošljite zahtevo za objavo na strežnik:

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

Ko dobimo uspešen odgovor, nastavimo stanje odgovora z ustreznimi vrednostmi in brskalniku ukažemo, da prenese prejeti 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();

In lahko uporabimo naslednje pod contentEditable div za prikaz sporočil:

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

Končna koda

Vse sem zapakiral na GitHub, tako da si lahko ogledate celotno izvorno kodo za strežnik in odjemalca.

Časovni žig:

Več od Triki CSS