Conversione di parlato in PDF con NextJS ed ExpressJS PlatoBlockchain Data Intelligence. Ricerca verticale. Ai.

Conversione della voce in PDF con NextJS ed ExpressJS

Con le interfacce vocali che stanno diventando più importanti, vale la pena esplorare alcune delle cose che possiamo fare con le interazioni vocali. Ad esempio, e se potessimo dire qualcosa e averlo trascritto e pompato come PDF scaricabile?

Bene, avviso spoiler: noi assolutamente può Fai quello! Ci sono librerie e framework che possiamo mettere insieme per realizzarlo, ed è quello che faremo insieme in questo articolo.

Questi sono gli strumenti che stiamo usando

Prima di tutto, questi sono i due grandi giocatori: Next.js ed Express.js.

Next.js punta su funzionalità aggiuntive per React, comprese le funzionalità chiave per la costruzione di siti statici. È un punto di riferimento per molti sviluppatori grazie a ciò che offre immediatamente, come routing dinamico, ottimizzazione delle immagini, routing integrato di dominio e sottodomini, aggiornamenti rapidi, routing del file system e percorsi API... tra tante, tante altre cose.

Nel nostro caso, abbiamo sicuramente bisogno di Next.js per il suo Percorsi API sul nostro server client. Vogliamo un percorso che prenda un file di testo, lo converta in PDF, lo scriva nel nostro filesystem, quindi invii una risposta al cliente.

Express.js ci consente di utilizzare una piccola app Node.js con routing, helper HTTP e modelli. È un server per la nostra API, che è ciò di cui avremo bisogno mentre passiamo e analizzeremo i dati tra le cose.

Abbiamo alcune altre dipendenze che useremo:

  1. reagire-riconoscimento vocale: Una libreria per convertire il parlato in testo, rendendolo disponibile ai componenti di React.
  2. runtime del rigeneratore: Una libreria per la risoluzione dei problemi di "regeneratorRuntime non è definito" errore visualizzato in Next.js quando si utilizza il riconoscimento vocale di reazione
  3. nodo html-pdf: una libreria per convertire una pagina HTML o un URL pubblico in un PDF
  4. Axios: una libreria per effettuare richieste HTTP sia nel browser che in Node.js
  5. corna: una libreria che consente la condivisione di risorse tra le origini

Impostare

La prima cosa che vogliamo fare è creare due cartelle di progetto, una per il client e una per il server. Dai loro un nome come preferisci. Sto nominando il mio audio-to-pdf-client ed audio-to-pdf-server, Rispettivamente.

Il modo più veloce per iniziare con Next.js sul lato client è avviarlo con crea-successiva-app. Quindi, apri il tuo terminale ed esegui il seguente comando dalla cartella del progetto client:

npx create-next-app client

Ora abbiamo bisogno del nostro server Express. Possiamo farcela cd-ing nella cartella del progetto del server ed eseguendo il file npm init comando. UN package.json il file verrà creato nella cartella del progetto del server una volta terminato.

Dobbiamo ancora installare effettivamente Express, quindi facciamolo ora con npm install express. Ora possiamo crearne uno nuovo index.js file nella cartella del progetto del server e rilascia questo codice lì:

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

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

Pronto per eseguire il server?

node index.js

Avremo bisogno di un altro paio di cartelle e di un altro file per andare avanti:

  • Creare un components cartella nella cartella del progetto client.
  • Creare un SpeechToText.jsx file nella components sottocartella.

Prima di andare oltre, abbiamo un po' di pulizia da fare. In particolare, dobbiamo sostituire il codice predefinito nel file pages/index.js file con questo:

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

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

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

L'importato SpeechToText il componente verrà eventualmente esportato da components/SpeechToText.jsx.

Installiamo le altre dipendenze

Bene, abbiamo la configurazione iniziale per la nostra app fuori mano. Ora possiamo installare le librerie che gestiscono i dati che vengono passati.

Possiamo installare le nostre dipendenze client con:

npm install react-speech-recognition regenerator-runtime axios

Le nostre dipendenze del server Express sono le prossime, quindi andiamo cd nella cartella del progetto del server e installa quelli:

npm install html-pdf-node cors

Probabilmente un buon momento per fare una pausa e assicurarsi che i file nelle nostre cartelle di progetto siano intatti. Ecco cosa dovresti avere nella cartella del progetto client a questo punto:

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

Ed ecco cosa dovresti avere nella cartella del progetto del server:

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

Costruire l'interfaccia utente

Bene, il nostro discorso in PDF non sarebbe eccezionale se non ci fosse modo di interagire con esso, quindi creiamo un componente React per esso che possiamo chiamare <SpeechToText>.

Puoi usare totalmente il tuo markup. Ecco cosa ho per darti un'idea dei pezzi che stiamo mettendo insieme:

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;

Questo componente restituisce a Frammento di reazione che contiene un HTML <``section``> elemento che contiene tre div:

  • .button-container contiene due pulsanti che verranno utilizzati per avviare e interrompere il riconoscimento vocale.
  • .words ha contentEditable ed suppressContentEditableWarning attributi per rendere questo elemento modificabile e sopprimere eventuali avvisi da React.
  • Un altro .button-container contiene altri due pulsanti che verranno utilizzati rispettivamente per ripristinare e convertire la voce in PDF.

Lo stile è un'altra cosa. Non ne parlerò qui, ma puoi usare alcuni stili che ho scritto come punto di partenza per il tuo styles/global.css file.

Visualizza CSS completo
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;
}

Le variabili CSS in esso contenute vengono utilizzate per controllare il colore di sfondo dei pulsanti.

Vediamo le ultime modifiche! Correre npm run dev nel terminale e controllali.

Dovresti vederlo nel browser quando visiti http://localhost:3000:

Conversione della voce in PDF con NextJS ed ExpressJS

La nostra prima conversione da discorso a testo!

La prima azione da intraprendere è importare le dipendenze necessarie nel ns <SpeechToText> componente:

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

Quindi controlliamo se il riconoscimento vocale è supportato dal browser e visualizziamo un avviso se non supportato:

const speechRecognitionSupported =
  SpeechRecognition.browserSupportsSpeechRecognition();

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

Successivamente, estraiamo transcript ed resetTranscript dal useSpeechRecognition() gancio:

const { transcript, resetTranscript } = useSpeechRecognition();

Questo è ciò di cui abbiamo bisogno per lo stato che gestisce listening:

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

Abbiamo anche bisogno di un ref per l' div con la contentEditable attributo, quindi dobbiamo aggiungere l' ref attribuire ad esso e passare transcript as children:

const textBodyRef = useRef(null);

…e:

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

L'ultima cosa di cui abbiamo bisogno qui è una funzione che attivi il riconoscimento vocale e colleghi quella funzione al onClick ascoltatore di eventi del nostro pulsante. Il pulsante imposta l'ascolto true e lo fa funzionare continuamente. Disattiveremo il pulsante mentre è in quello stato per impedirci di attivare ulteriori eventi.

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

…e:

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

Cliccando sul pulsante dovrebbe ora avviare la trascrizione.

Altre funzioni

OK, quindi abbiamo un componente che può inizia a ascoltando. Ma ora abbiamo bisogno che faccia anche alcune altre cose, come stopListening, resetText ed handleConversion. Facciamo quelle funzioni.

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

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

const handleConversion = async () => {}

Ciascuna delle funzioni verrà aggiunta a un onClick listener di eventi sui pulsanti appropriati:

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

Il handleConversion la funzione è asincrona perché alla fine faremo una richiesta API. Il pulsante "Stop" ha l'attributo disabilitato che verrebbe attivato quando l'ascolto è falso.

Se riavviamo il server e aggiorniamo il browser, ora possiamo avviare, interrompere e ripristinare la nostra trascrizione vocale nel browser.

Ora ciò di cui abbiamo bisogno è che l'app lo faccia trascrivere quel parlato riconosciuto convertendolo in un file PDF. Per questo, abbiamo bisogno del percorso lato server da Express.js.

Configurazione del percorso API

Lo scopo di questo percorso è prendere un file di testo, convertirlo in PDF, scrivere quel PDF nel nostro filesystem, quindi inviare una risposta al cliente.

Per configurare, vorremmo aprire il server/index.js archiviare e importare il html-pdf-node ed fs dipendenze che verranno utilizzate per scrivere e aprire il nostro filesystem.

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

Successivamente, imposteremo il nostro percorso:

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

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

Procediamo quindi a definire le nostre opzioni necessarie per l'utilizzo html-pdf-node all'interno del percorso:

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

Il options oggetto accetta un valore per impostare il formato e lo stile della carta. I formati carta seguono un sistema molto diverso rispetto alle unità di dimensionamento che utilizziamo tipicamente sul web. Per esempio, A4 è il tipico formato lettera.

Il file oggetto accetta l'URL di un sito Web pubblico o il markup HTML. Per generare la nostra pagina HTML, utilizzeremo il file html, body, pre Tag HTML e il testo da req.body.

Puoi applicare qualsiasi stile di tua scelta.

Successivamente, aggiungeremo a trycatch per gestire eventuali errori che potrebbero apparire lungo il percorso:

try {

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

Successivamente, useremo il generatePdf dal html-pdf-node libreria per generare a pdfBuffer (il file PDF grezzo) dal nostro file e creare un file unico pdfName:

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

  // Next code here
}

Da lì, utilizziamo il modulo del filesystem per scrivere, leggere e (sì, finalmente!) inviare una risposta all'app 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." });
  });
});

Analizziamolo un po':

  • Il writeFile il modulo filesystem accetta un nome file, dati e una funzione di callback che può restituire un messaggio di errore se si verifica un problema durante la scrittura del file. Se stai lavorando con una CDN che fornisce endpoint di errore, puoi invece usarli.
  • Il readFile il modulo filesystem accetta un nome file e una funzione di callback che è in grado o restituisce un errore di lettura così come i dati letti. Una volta che non abbiamo errori di lettura e i dati letti sono presenti, costruiremo e invieremo una risposta al client. Ancora una volta, questo può essere sostituito con gli endpoint della tua CDN se li hai.
  • Il res.setHeader("Content-Type", "application/pdf"); indica al browser che stiamo inviando un file PDF.
  • Il res.setHeader("Content-Disposition", "attachment"); indica al browser di rendere scaricabili i dati ricevuti.

Poiché il percorso API è pronto, possiamo usarlo nella nostra app all'indirizzo http://localhost:4000. Possiamo procedere alla parte client della nostra applicazione per completare il handleConversion funzione.

Gestire la conversione

Prima di poter iniziare a lavorare su a handleConversion funzione, dobbiamo creare uno stato che gestisca le nostre richieste API per il caricamento, l'errore, il successo e altri messaggi. Useremo React's useState hook per configurarlo:

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

Nel handleConversion funzione, verificheremo quando la pagina web è stata caricata prima di eseguire il nostro codice e ci assicureremo che il div con la editable l'attributo non è vuoto:

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

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

Procediamo avvolgendo la nostra eventuale richiesta API in a trycatch, gestendo eventuali errori che potrebbero verificarsi e aggiornando lo stato di risposta:

try {

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

Successivamente, impostiamo alcuni valori per lo stato di risposta e impostiamo anche config per axios e fai una richiesta di posta al 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
);

Una volta ottenuta una risposta positiva, impostiamo lo stato della risposta con i valori appropriati e istruiamo il browser a scaricare il PDF ricevuto:

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

E possiamo usare quanto segue sotto il contentEditable div per la visualizzazione dei messaggi:

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

Codice finale

Ho impacchettato tutto su GitHub in modo da poter controllare il codice sorgente completo sia per il server che per il client.

Timestamp:

Di più da Trucchi CSS