Conversia vorbirii în PDF cu NextJS și ExpressJS PlatoBlockchain Data Intelligence. Căutare verticală. Ai.

Conversia vorbirii în PDF cu NextJS și ExpressJS

Întrucât interfețele de vorbire devin mai mult un lucru, merită să explorăm câteva dintre lucrurile pe care le putem face cu interacțiunile vocale. Cum ar fi, ce se întâmplă dacă am putea spune ceva și să-l transcriem și să le extragem ca PDF descărcabil?

Ei bine, alertă de spoiler: suntem absolut poate să fa aia! Există biblioteci și cadre pe care le putem pune împreună pentru a face acest lucru, și asta vom face împreună în acest articol.

Acestea sunt instrumentele pe care le folosim

În primul rând, aceștia sunt cei doi jucători mari: Next.js și Express.js.

Next.js abordează funcționalități suplimentare pentru React, inclusiv caracteristici cheie pentru construirea de site-uri statice. Este o alegere pentru mulți dezvoltatori datorită a ceea ce oferă imediat, cum ar fi rutarea dinamică, optimizarea imaginii, rutarea în domeniul și subdomeniul încorporat, reîmprospătările rapide, rutarea sistemului de fișiere și rutele API... printre multe, multe alte lucruri.

În cazul nostru, cu siguranță avem nevoie de Next.js pentru acesta rute API pe serverul nostru client. Vrem o rută care preia un fișier text, îl convertește în PDF, îl scrie în sistemul nostru de fișiere, apoi trimite un răspuns clientului.

Express.js ne permite să punem în funcțiune o mică aplicație Node.js cu rutare, ajutoare HTTP și șabloane. Este un server pentru propriul nostru API, care este ceea ce vom avea nevoie pe măsură ce trecem și analizăm datele între lucruri.

Avem și alte dependențe pe care le vom folosi:

  1. reacționează-recunoașterea vorbirii: O bibliotecă pentru conversia vorbirii în text, făcându-l disponibil pentru componentele React.
  2. balsam-durata: O bibliotecă pentru depanarea „regeneratorRuntime nu este definită” eroare care apare în Next.js atunci când utilizați react-speech-recognition
  3. html-pdf-node: O bibliotecă pentru conversia unei pagini HTML sau a unei adrese URL publice într-un PDF
  4. Axios: O bibliotecă pentru a face solicitări HTTP atât în ​​browser, cât și în Node.js
  5. CORS: O bibliotecă care permite partajarea resurselor între origini

Configurare

Primul lucru pe care vrem să-l facem este să creăm două foldere de proiect, unul pentru client și unul pentru server. Numiți-le cum doriți. Îl numesc pe al meu audio-to-pdf-client și audio-to-pdf-server, respectiv.

Cea mai rapidă modalitate de a începe cu Next.js din partea clientului este să-l pornești create-next-app. Deci, deschideți terminalul și rulați următoarea comandă din folderul proiectului client:

npx create-next-app client

Acum avem nevoie de serverul nostru Express. Putem ajunge cd-ing în folderul de proiect al serverului și rulează npm init comanda. A package.json fișierul va fi creat în folderul de proiect al serverului odată ce este finalizat.

Încă trebuie să instalăm Express, așa că hai să facem asta acum cu npm install express. Acum putem crea un nou index.js fișier în folderul de proiect al serverului și plasați acest cod acolo:

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

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

Sunteți gata să rulați serverul?

node index.js

Vom avea nevoie de încă câteva foldere și un alt fișier pentru a merge mai departe:

  • Crearea unei components folder în folderul proiect client.
  • Crearea unei SpeechToText.jsx de fișier în components subdosar.

Înainte de a merge mai departe, avem de făcut o mică curățare. Mai exact, trebuie să înlocuim codul implicit în fișierul pages/index.js fisier cu asta:

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

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

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

Cele importate SpeechToText componenta va fi în cele din urmă exportată din components/SpeechToText.jsx.

Să instalăm celelalte dependențe

Bine, avem configurarea inițială pentru aplicația noastră. Acum putem instala bibliotecile care gestionează datele care sunt transmise.

Putem instala dependențele clienților noștri cu:

npm install react-speech-recognition regenerator-runtime axios

Urmează dependențele serverului nostru Express, așa că haideți cd în folderul de proiect al serverului și instalați cele:

npm install html-pdf-node cors

Probabil este un moment bun pentru a face pauză și pentru a vă asigura că fișierele din folderele proiectelor noastre sunt intacte. Iată ce ar trebui să aveți în folderul proiectului client în acest moment:

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

Și iată ce ar trebui să aveți în folderul de proiect al serverului:

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

Construirea interfeței de utilizare

Ei bine, discursul nostru în PDF nu ar fi chiar atât de grozav dacă nu există nicio modalitate de a interacționa cu el, așa că haideți să facem o componentă React pe care o putem numi <SpeechToText>.

Puteți folosi în totalitate propriul dvs. marcaj. Iată ce trebuie să vă dau o idee despre piesele pe care le punem împreună:

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;

Această componentă returnează a Fragment de reacție care conține un HTML <``section``> element care conține trei div-uri:

  • .button-container conține două butoane care vor fi folosite pentru a porni și opri recunoașterea vorbirii.
  • .words are contentEditable și suppressContentEditableWarning atribute pentru a face acest element editabil și pentru a suprima orice avertismente de la React.
  • O alta .button-container mai deține două butoane care vor fi folosite pentru a reseta și, respectiv, a converti vorbirea în PDF.

Styling-ul este cu totul altceva. Nu voi intra aici, dar sunteți binevenit să folosiți unele stiluri pe care le-am scris, fie ca punct de plecare pentru propriul dvs. styles/global.css fișier.

Vizualizați CSS complet
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;
}

Variabilele CSS din acolo sunt folosite pentru a controla culoarea de fundal a butoanelor.

Să vedem cele mai recente modificări! Alerga npm run dev în terminal și verificați-le.

Ar trebui să vedeți acest lucru în browser când vizitați http://localhost:3000:

Conversia vorbirii în PDF cu NextJS și ExpressJS

Prima noastră conversie discurs în text!

Prima acțiune de luat este să importați dependențele necesare în sistemul nostru <SpeechToText> componentă:

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

Apoi verificăm dacă recunoașterea vorbirii este acceptată de browser și redăm o notificare dacă nu este acceptată:

const speechRecognitionSupported =
  SpeechRecognition.browserSupportsSpeechRecognition();

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

În continuare, să extragem transcript și resetTranscript de la useSpeechRecognition() cârlig:

const { transcript, resetTranscript } = useSpeechRecognition();

De asta avem nevoie pentru statul care se ocupă listening:

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

Avem nevoie și de un ref pentru div cu contentEditable atribut, atunci trebuie să adăugăm ref atribui-i și treci transcript as children:

const textBodyRef = useRef(null);

…și:

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

Ultimul lucru de care avem nevoie aici este o funcție care să declanșeze recunoașterea vorbirii și să lege această funcție de onClick ascultător de eveniment al butonului nostru. Butonul setează ascultarea true și îl face să ruleze continuu. Vom dezactiva butonul în timp ce este în acea stare pentru a ne împiedica să lansăm evenimente suplimentare.

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

…și:

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

Făcând clic pe butonul ar trebui să pornească acum transcrierea.

Mai multe funcții

OK, deci avem o componentă care poate Începe ascultare. Dar acum avem nevoie de el pentru a face și alte câteva lucruri, cum ar fi stopListening, resetText și handleConversion. Să facem acele funcții.

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

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

const handleConversion = async () => {}

Fiecare dintre funcții va fi adăugată la un onClick ascultător de evenimente pe butoanele corespunzătoare:

<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 funcția este asincronă, deoarece în cele din urmă vom face o solicitare API. Butonul „Stop” are atributul dezactivat care ar fi declanșat atunci când ascultarea este falsă.

Dacă repornim serverul și reîmprospătăm browserul, acum putem porni, opri și reseta transcrierea vorbirii în browser.

Acum, ceea ce avem nevoie este ca aplicația transcrie care a recunoscut vorbirea transformându-l într-un fișier PDF. Pentru asta, avem nevoie de calea pe partea serverului din Express.js.

Configurarea rutei API

Scopul acestei rute este de a prelua un fișier text, de a-l converti într-un PDF, de a scrie acel PDF în sistemul nostru de fișiere, apoi de a trimite un răspuns clientului.

Pentru a configura, am deschide server/index.js fișier și importați fișierul html-pdf-node și fs dependențe care vor fi folosite pentru a scrie și deschide sistemul nostru de fișiere.

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

În continuare, ne vom configura traseul:

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

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

Apoi trecem la definirea opțiunilor necesare pentru utilizare html-pdf-node in interiorul traseului:

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

options obiectul acceptă o valoare pentru a seta dimensiunea și stilul hârtiei. Dimensiunile hârtiei urmează un sistem mult diferit față de unitățile de dimensionare pe care le folosim de obicei pe web. De exemplu, A4 este dimensiunea tipică a literelor.

file obiect acceptă fie adresa URL a unui site web public, fie marcaj HTML. Pentru a genera pagina noastră HTML, vom folosi html, body, pre Etichetele HTML și textul din req.body.

Puteți aplica orice stil la alegere.

În continuare, vom adăuga un trycatch pentru a gestiona orice erori care ar putea apărea pe parcurs:

try {

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

În continuare, vom folosi generatePdf de la html-pdf-node bibliotecă pentru a genera a pdfBuffer (fișierul PDF brut) din fișierul nostru și creați un unic pdfName:

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

  // Next code here
}

De acolo, folosim modulul sistemului de fișiere pentru a scrie, citi și (da, în sfârșit!) trimite un răspuns la aplicația client:

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

Să dezvăluim asta puțin:

  • writeFile Modulul sistemului de fișiere acceptă un nume de fișier, date și o funcție de apel invers care poate returna un mesaj de eroare dacă există o problemă la scrierea fișierului. Dacă lucrați cu un CDN care oferă puncte finale de eroare, le puteți folosi în schimb.
  • readFile Modulul sistem de fișiere acceptă un nume de fișier și o funcție de apel invers care este capabilă sau returnează o eroare de citire, precum și datele citite. Odată ce nu avem nicio eroare de citire și datele citite sunt prezente, vom construi și vom trimite un răspuns clientului. Din nou, acesta poate fi înlocuit cu punctele finale ale CDN-ului dvs. dacă le aveți.
  • res.setHeader("Content-Type", "application/pdf"); spune browserului că trimitem un fișier PDF.
  • res.setHeader("Content-Disposition", "attachment"); spune browserului să facă datele primite descărcabile.

Deoarece ruta API este gata, o putem folosi în aplicația noastră la http://localhost:4000. Putem trece la partea client a aplicației noastre pentru a finaliza handleConversion Funcția.

Gestionarea conversiei

Înainte de a putea începe să lucrăm la un handleConversion funcție, trebuie să creăm o stare care să gestioneze solicitările noastre API pentru încărcare, eroare, succes și alte mesaje. Vom folosi React's useState cârlig pentru a configura asta:

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

În handleConversion funcție, vom verifica când pagina web a fost încărcată înainte de a rula codul nostru și ne vom asigura că div cu editable atributul nu este gol:

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

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

Continuăm prin împachetarea eventualei noastre solicitări API într-un trycatch, gestionând orice eroare care poate apărea și actualizarea stării de răspuns:

try {

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

Apoi, setăm câteva valori pentru starea răspunsului și, de asemenea, setăm config pentru axios și faceți o cerere de postare pe server:

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

Odată ce am primit un răspuns de succes, setăm starea răspunsului cu valorile corespunzătoare și instruim browserul să descarce PDF-ul primit:

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

Și putem folosi următoarele sub conținutul Editabil div pentru afișarea mesajelor:

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

Cod final

Am împachetat totul pe GitHub, astfel încât să puteți verifica întregul cod sursă atât pentru server, cât și pentru client.

Timestamp-ul:

Mai mult de la CSS Trucuri