Spraak converteren naar PDF met NextJS en ExpressJS PlatoBlockchain Data Intelligence. Verticaal zoeken. Ai.

Spraak converteren naar PDF met NextJS en ExpressJS

Nu spraakinterfaces steeds meer een ding worden, is het de moeite waard om enkele dingen te onderzoeken die we met spraakinteracties kunnen doen. Wat als we iets zouden kunnen zeggen en dat laten transcriberen en uitpompen als een downloadbare pdf?

Nou, spoiler alert: wij absoluut wel doe dat! Er zijn bibliotheken en frameworks die we kunnen samenvoegen om het mogelijk te maken, en dat is wat we samen gaan doen in dit artikel.

Dit zijn de tools die we gebruiken

Ten eerste zijn dit de twee grote spelers: Next.js en Express.js.

Next.js pakt extra functionaliteiten voor React aan, inclusief belangrijke functies voor het bouwen van statische sites. Het is een go-to voor veel ontwikkelaars vanwege wat het direct uit de doos biedt, zoals dynamische routering, beeldoptimalisatie, ingebouwde domein- en subdomeinroutering, snelle vernieuwingen, bestandssysteemroutering en API-routes ... onder andere vele, vele andere dingen.

In ons geval hebben we Next.js zeker nodig voor zijn API-routes op onze clientserver. We willen een route die een tekstbestand neemt, het naar PDF converteert, het naar ons bestandssysteem schrijft en vervolgens een reactie naar de klant stuurt.

Express.js stelt ons in staat om een โ€‹โ€‹kleine Node.js-app aan de gang te krijgen met routering, HTTP-helpers en sjablonen. Het is een server voor onze eigen API, wat we nodig hebben als we gegevens tussen dingen doorgeven en parseren.

We hebben nog enkele andere afhankelijkheden die we gaan gebruiken:

  1. reageren-spraakherkenning: Een bibliotheek voor het converteren van spraak naar tekst, zodat deze beschikbaar wordt voor React-componenten.
  2. regenerator-looptijd: een bibliotheek voor het oplossen van problemen met de "regeneratorRuntime is niet gedefinieerd "fout die verschijnt in Next.js bij gebruik van react-speech-recognition
  3. html-pdf-knooppunt: Een bibliotheek voor het converteren van een HTML-pagina of openbare URL naar een PDF
  4. Axios: Een bibliotheek voor het maken van HTTP-verzoeken in zowel de browser als Node.js
  5. hoorns: een bibliotheek die het delen van bronnen tussen verschillende bronnen mogelijk maakt

Opzetten

Het eerste dat we willen doen, is twee projectmappen maken, รฉรฉn voor de client en รฉรฉn voor de server. Noem ze zoals je wilt. ik noem de mijne audio-to-pdf-client en audio-to-pdf-server, Respectievelijk.

De snelste manier om met Next.js aan de clientzijde aan de slag te gaan, is door het op te starten met maak-volgende-app. Open dus uw terminal en voer de volgende opdracht uit vanuit uw clientprojectmap:

npx create-next-app client

Nu hebben we onze Express-server nodig. We kunnen het krijgen door cd-ing naar de serverprojectmap en het uitvoeren van de npm init opdracht. EEN package.json bestand wordt gemaakt in de serverprojectmap zodra het klaar is.

We moeten Express nog echt installeren, dus laten we dat nu doen met npm install express. Nu kunnen we een nieuwe . maken index.js bestand in de serverprojectmap en zet deze code daar neer:

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

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

Klaar om de server te draaien?

node index.js

We hebben nog een paar mappen nodig en nog een bestand om verder te gaan:

  • Maak een components map in de projectmap van de klant.
  • Maak een SpeechToText.jsx bestand in de components submap.

Voordat we verder gaan, moeten we nog wat opruimen. In het bijzonder moeten we de standaardcode in de pages/index.js bestand met dit:

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

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

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

de geรฏmporteerde SpeechToText component zal uiteindelijk worden geรซxporteerd van components/SpeechToText.jsx.

Laten we de andere afhankelijkheden installeren

Okรฉ, we hebben de eerste installatie voor onze app uit de weg. Nu kunnen we de bibliotheken installeren die de gegevens verwerken die worden doorgegeven.

We kunnen onze klantafhankelijkheden installeren met:

npm install react-speech-recognition regenerator-runtime axios

Onze Express-serverafhankelijkheden zijn de volgende, dus laten we cd in de serverprojectmap en installeer deze:

npm install html-pdf-node cors

Waarschijnlijk een goed moment om even te pauzeren en ervoor te zorgen dat de bestanden in onze projectmappen intact zijn. Dit is wat u op dit moment in de clientprojectmap zou moeten hebben:

/audio-to-pdf-web-client
โ”œโ”€ /components
|  โ””โ”€โ”€ SpeechToText.jsx
โ”œโ”€ /pages
|  โ”œโ”€ _app.js
|  โ””โ”€โ”€ index.js
โ””โ”€โ”€ /styles
    โ”œโ”€globals.css
    โ””โ”€โ”€ Home.module.css

En dit is wat u in de serverprojectmap zou moeten hebben:

/audio-to-pdf-server
โ””โ”€โ”€ index.js

De gebruikersinterface bouwen

Welnu, onze spraak-naar-PDF zou niet zo geweldig zijn als er geen manier is om ermee te communiceren, dus laten we er een React-component voor maken die we kunnen noemen <SpeechToText>.

U kunt volledig uw eigen opmaak gebruiken. Dit is wat ik heb om je een idee te geven van de stukken die we aan het samenstellen zijn:

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;

Deze component retourneert a Reageer fragment die een HTML bevat <``section``> element dat drie divs bevat:

  • .button-container bevat twee knoppen die worden gebruikt om spraakherkenning te starten en te stoppen.
  • .words heeft contentEditable en suppressContentEditableWarning attributen om dit element bewerkbaar te maken en eventuele waarschuwingen van React te onderdrukken.
  • Nog een .button-container bevat nog twee knoppen die zullen worden gebruikt om respectievelijk spraak opnieuw in te stellen en naar PDF te converteren.

Styling is iets heel anders. Ik zal er hier niet op ingaan, maar u bent van harte welkom om enkele stijlen die ik heb geschreven te gebruiken als uitgangspunt voor uw eigen stijlen styles/global.css bestand.

Volledige CSS bekijken
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;
}

De CSS-variabelen daarin worden gebruikt om de achtergrondkleur van de knoppen te regelen.

Laten we eens kijken naar de laatste wijzigingen! Rennen npm run dev in de terminal en bekijk ze.

U zou dit in de browser moeten zien wanneer u bezoekt http://localhost:3000:

Spraak converteren naar PDF met NextJS en ExpressJS

Onze eerste spraak naar tekst conversie!

De eerste actie die moet worden ondernomen, is het importeren van de noodzakelijke afhankelijkheden in onze <SpeechToText> component:

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

Vervolgens controleren we of spraakherkenning door de browser wordt ondersteund en geven we een melding als dit niet wordt ondersteund:

const speechRecognitionSupported =
  SpeechRecognition.browserSupportsSpeechRecognition();

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

Laten we vervolgens extraheren transcript en resetTranscript van het useSpeechRecognition() haak:

const { transcript, resetTranscript } = useSpeechRecognition();

Dit is wat we nodig hebben voor de staat die handelt listening:

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

We hebben ook een ref voor de div met de contentEditable attribuut, dan moeten we de . toevoegen ref toeschrijven en doorgeven transcript as children:

const textBodyRef = useRef(null);

โ€ฆen:

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

Het laatste dat we hier nodig hebben, is een functie die spraakherkenning activeert en die functie koppelt aan de onClick gebeurtenis luisteraar van onze knop. Met de knop wordt luisteren naar true en laat het continu draaien. We schakelen de knop uit terwijl deze zich in die staat bevindt om te voorkomen dat we extra gebeurtenissen afvuren.

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

โ€ฆen:

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

Als u op de knop klikt, moet de transcriptie nu worden gestart.

Meer functies

OK, dus we hebben een component die kan begin luisteren. Maar nu hebben we het ook nodig om een โ€‹โ€‹paar andere dingen te doen, zoals: stopListening, resetText en handleConversion. Laten we die functies maken.

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

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

const handleConversion = async () => {}

Elk van de functies wordt toegevoegd aan een onClick gebeurtenislistener op de juiste knoppen:

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

De handleConversion functie is asynchroon omdat we uiteindelijk een API-verzoek zullen doen. De knop "Stoppen" heeft het kenmerk uitgeschakeld dat zou worden geactiveerd als luisteren niet waar is.

Als we de server opnieuw opstarten en de browser vernieuwen, kunnen we nu onze spraaktranscriptie in de browser starten, stoppen en resetten.

Wat we nu nodig hebben, is dat de app overschrijven die herkende spraak door deze naar een PDF-bestand te converteren. Daarvoor hebben we het pad aan de serverzijde van Express.js nodig.

De API-route instellen

Het doel van deze route is om een โ€‹โ€‹tekstbestand te nemen, het naar een PDF te converteren, die PDF naar ons bestandssysteem te schrijven en vervolgens een reactie naar de klant te sturen.

Om in te stellen, zouden we de . openen server/index.js bestand en importeer de html-pdf-node en fs afhankelijkheden die zullen worden gebruikt om ons bestandssysteem te schrijven en te openen.

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

Vervolgens zullen we onze route opzetten:

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

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

We gaan dan verder met het definiรซren van onze opties die nodig zijn om te gebruiken html-pdf-node binnen de route:

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

De options object accepteert een waarde om het papierformaat en de stijl in te stellen. Papierformaten volgen een heel ander systeem dan de maateenheden die we doorgaans op internet gebruiken. Bijvoorbeeld, A4 is het typische letterformaat.

De file object accepteert de URL van een openbare website of HTML-opmaak. Om onze HTML-pagina te genereren, gebruiken we de html, body, pre HTML-tags en de tekst van de req.body.

U kunt elke gewenste styling toepassen.

Vervolgens zullen we een toevoegen trycatch om eventuele fouten op te lossen die onderweg kunnen optreden:

try {

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

Vervolgens gebruiken we de generatePdf van het html-pdf-node bibliotheek om een โ€‹โ€‹te genereren pdfBuffer (het onbewerkte PDF-bestand) uit ons bestand en maak een unieke pdfName:

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

  // Next code here
}

Van daaruit gebruiken we de bestandssysteemmodule om te schrijven, lezen en (ja, eindelijk!) een reactie naar de client-app te sturen:

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

Laten we dat een beetje opsplitsen:

  • De writeFile bestandssysteemmodule accepteert een bestandsnaam, gegevens en een callback-functie die een foutmelding kan retourneren als er een probleem is met het schrijven naar het bestand. Als u werkt met een CDN die fouteindpunten biedt, kunt u die in plaats daarvan gebruiken.
  • De readFile bestandssysteemmodule accepteert een bestandsnaam en een callback-functie die in staat is of een leesfout retourneert, evenals de leesgegevens. Zodra we geen leesfout hebben en de leesgegevens aanwezig zijn, zullen we een reactie opstellen en naar de klant sturen. Nogmaals, dit kan worden vervangen door de eindpunten van uw CDN als u die heeft.
  • De res.setHeader("Content-Type", "application/pdf"); vertelt de browser dat we een PDF-bestand verzenden.
  • De res.setHeader("Content-Disposition", "attachment"); vertelt de browser om de ontvangen gegevens downloadbaar te maken.

Aangezien de API-route gereed is, kunnen we deze gebruiken in onze app op http://localhost:4000. We kunnen doorgaan naar het klantgedeelte van onze applicatie om de handleConversion functie.

De conversie afhandelen

Voordat we kunnen gaan werken aan een handleConversion functie, moeten we een status maken die onze API-verzoeken voor laden, fouten, succes en andere berichten afhandelt. We gaan React's gebruiken useState haak om dat in te stellen:

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

In het handleConversion functie, zullen we controleren wanneer de webpagina is geladen voordat onze code wordt uitgevoerd en ervoor zorgen dat de div met de editable attribuut is niet leeg:

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

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

We gaan verder door ons uiteindelijke API-verzoek in een trycatch, het afhandelen van eventuele fouten en het bijwerken van de antwoordstatus:

try {

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

Vervolgens stellen we enkele waarden in voor de responsstatus en stellen we ook config in voor axios en doe een postverzoek aan de 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
);

Zodra we een succesvol antwoord hebben gekregen, stellen we de antwoordstatus in met de juiste waarden en instrueren we de browser om de ontvangen PDF te downloaden:

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

En we kunnen het volgende gebruiken onder de inhoudBewerkbaar div voor het weergeven van berichten:

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

Laatste code

Ik heb alles op GitHub verpakt, zodat je de volledige broncode voor zowel de server als de client kunt bekijken.

Tijdstempel:

Meer van CSS-trucs