Konvertering av tale til PDF med NextJS og ExpressJS PlatoBlockchain Data Intelligence. Vertikalt søk. Ai.

Konvertering av tale til PDF med NextJS og ExpressJS

Ettersom talegrensesnitt blir mer av en ting, er det verdt å utforske noen av tingene vi kan gjøre med taleinteraksjoner. Hva om vi kunne si noe og få det transkribert og pumpet ut som en nedlastbar PDF?

Vel, spoileralarm: vi absolutt kan gjør det! Det er biblioteker og rammer vi kan flette sammen for å få det til, og det er det vi skal gjøre sammen i denne artikkelen.

Dette er verktøyene vi bruker

For det første er dette de to store aktørene: Next.js og Express.js.

Next.js tar tak i tilleggsfunksjoner til React, inkludert nøkkelfunksjoner for å bygge statiske nettsteder. Det er en go-to for mange utviklere på grunn av det den tilbyr rett ut av esken, som dynamisk ruting, bildeoptimalisering, innebygd domene- og underdomeneruting, raske oppdateringer, filsystemruting og API-ruter ... blant mange, mange andre ting.

I vårt tilfelle trenger vi definitivt Next.js for det API-ruter på vår klientserver. Vi vil ha en rute som tar en tekstfil, konverterer den til PDF, skriver den til filsystemet vårt og sender et svar til klienten.

Express.js lar oss få i gang en liten Node.js-app med ruting, HTTP-hjelpere og maler. Det er en server for vår egen API, som er det vi trenger når vi sender og analyserer data mellom ting.

Vi har noen andre avhengigheter vi skal ta i bruk:

  1. reagere-tale-gjenkjenning: Et bibliotek for å konvertere tale til tekst, noe som gjør det tilgjengelig for React-komponenter.
  2. regenerator-kjøretid: Et bibliotek for feilsøking av "regeneratorRuntime er ikke definert»-feil som vises i Next.js når du bruker react-talegjenkjenning
  3. html-pdf-node: Et bibliotek for å konvertere en HTML-side eller offentlig URL til en PDF
  4. Axios: Et bibliotek for å lage HTTP-forespørsler i både nettleseren og Node.js
  5. horn: Et bibliotek som tillater deling av ressurser på tvers av opprinnelse

Setter opp

Det første vi ønsker å gjøre er å lage to prosjektmapper, en for klienten og en for serveren. Gi dem et navn hva du vil. Jeg gir min navn audio-to-pdf-client og audio-to-pdf-serverHhv.

Den raskeste måten å komme i gang med Next.js på klientsiden er å bootstrap den med lag-neste-app. Så åpne terminalen din og kjør følgende kommando fra klientprosjektmappen:

npx create-next-app client

Nå trenger vi vår Express-server. Vi kan klare det cd-ing inn i serverprosjektmappen og kjører npm init kommando. EN package.json filen vil bli opprettet i serverprosjektmappen når den er ferdig.

Vi må fortsatt installere Express, så la oss gjøre det nå med npm install express. Nå kan vi lage en ny index.js fil i serverprosjektmappen og slipp denne koden der:

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

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

Klar til å kjøre serveren?

node index.js

Vi trenger et par flere mapper og en annen fil for å gå videre:

  • Lag en components mappe i klientprosjektmappen.
  • Lag en SpeechToText.jsx fil i components undermappe.

Før vi går videre, har vi en liten opprydding å gjøre. Spesielt må vi erstatte standardkoden i pages/index.js fil med denne:

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

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

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

Den importerte SpeechToText komponent vil til slutt bli eksportert fra components/SpeechToText.jsx.

La oss installere de andre avhengighetene

Greit, vi har det første oppsettet for appen vår ute av veien. Nå kan vi installere bibliotekene som håndterer dataene som sendes rundt.

Vi kan installere våre klientavhengigheter med:

npm install react-speech-recognition regenerator-runtime axios

Våre Express-serveravhengigheter er neste gang, så la oss cd inn i serverprosjektmappen og installer disse:

npm install html-pdf-node cors

Sannsynligvis et godt tidspunkt å pause og sørge for at filene i prosjektmappene våre er i takt. Her er hva du bør ha i klientprosjektmappen på dette tidspunktet:

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

Og her er hva du bør ha i serverprosjektmappen:

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

Bygge brukergrensesnittet

Vel, vår tale-til-PDF ville ikke vært så bra hvis det ikke er mulig å samhandle med den, så la oss lage en React-komponent for den som vi kan kalle <SpeechToText>.

Du kan helt bruke din egen markering. Her er det jeg har for å gi deg en idé om delene vi setter sammen:

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;

Denne komponenten returnerer en Reaksjonsfragment som inneholder en HTML <``section``> element som inneholder tre divs:

  • .button-container inneholder to knapper som skal brukes til å starte og stoppe talegjenkjenning.
  • .words har contentEditable og suppressContentEditableWarning attributter for å gjøre dette elementet redigerbart og undertrykke eventuelle advarsler fra React.
  • En annen .button-container holder to knapper til som skal brukes til å tilbakestille og konvertere tale til henholdsvis PDF.

Styling er en helt annen ting. Jeg skal ikke gå inn på det her, men du må gjerne bruke noen stiler jeg skrev enten som utgangspunkt for dine egne styles/global.css filen.

Se hele 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;
}

CSS-variablene der brukes til å kontrollere bakgrunnsfargen på knappene.

La oss se de siste endringene! Løpe npm run dev i terminalen og sjekk dem ut.

Du bør se dette i nettleseren når du besøker http://localhost:3000:

Konvertering av tale til PDF med NextJS og ExpressJS

Vår første tale til tekstkonvertering!

Den første handlingen å ta er å importere de nødvendige avhengighetene til vår <SpeechToText> komponent:

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

Deretter sjekker vi om talegjenkjenning støttes av nettleseren og gir en melding hvis den ikke støttes:

const speechRecognitionSupported =
  SpeechRecognition.browserSupportsSpeechRecognition();

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

Neste, la oss trekke ut transcript og resetTranscript fra useSpeechRecognition() krok:

const { transcript, resetTranscript } = useSpeechRecognition();

Det er dette vi trenger for staten som håndterer listening:

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

Vi trenger også en ref for div med contentEditable attributt, så må vi legge til ref tilskrive det og bestå transcript as children:

const textBodyRef = useRef(null);

…og:

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

Det siste vi trenger her er en funksjon som utløser talegjenkjenning og å knytte den funksjonen til onClick begivenhetslytter av knappen vår. Knappen setter lytting til true og får den til å kjøre kontinuerlig. Vi deaktiverer knappen mens den er i den tilstanden for å hindre oss i å utløse flere hendelser.

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

…og:

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

Ved å klikke på knappen skal transkripsjonen nå startes.

Flere funksjoner

OK, så vi har en komponent som kan Begynn lytter. Men nå trenger vi det for å gjøre noen andre ting også, som stopListening, resetText og handleConversion. La oss lage disse funksjonene.

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

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

const handleConversion = async () => {}

Hver av funksjonene vil bli lagt til en onClick hendelseslytter på de aktuelle knappene:

<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 funksjonen er asynkron fordi vi til slutt vil lage en API-forespørsel. "Stopp"-knappen har det deaktiverte attributtet som vil bli utløst når lyttingen er falsk.

Hvis vi starter serveren på nytt og oppdaterer nettleseren, kan vi nå starte, stoppe og tilbakestille taletranskripsjonen vår i nettleseren.

Nå trenger vi appen transkribere den gjenkjente talen ved å konvertere den til en PDF-fil. For det trenger vi stien på serversiden fra Express.js.

Sette opp API-ruten

Hensikten med denne ruten er å ta en tekstfil, konvertere den til en PDF, skrive den PDF-filen til vårt filsystem, og deretter sende et svar til klienten.

For å sette opp, ville vi åpne server/index.js fil og importer html-pdf-node og fs avhengigheter som vil bli brukt til å skrive og åpne filsystemet vårt.

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

Deretter vil vi sette opp ruten vår:

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

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

Vi fortsetter deretter med å definere alternativene som kreves for å bruke html-pdf-node inne på ruten:

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

De options objektet godtar en verdi for å angi papirstørrelse og stil. Papirstørrelser følger et mye annet system enn størrelsesenhetene vi vanligvis bruker på nettet. For eksempel, A4 er den typiske bokstavstørrelsen.

De file objektet godtar enten URL-en til et offentlig nettsted eller HTML-oppmerking. For å generere HTML-siden vår, vil vi bruke html, body, pre HTML-koder og teksten fra req.body.

Du kan bruke hvilken som helst styling du ønsker.

Deretter vil vi legge til en trycatch for å håndtere eventuelle feil som kan dukke opp underveis:

try {

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

Deretter vil vi bruke generatePdf fra html-pdf-node bibliotek for å generere en pdfBuffer (den rå PDF-filen) fra vår fil og lag en unik pdfName:

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

  // Next code here
}

Derfra bruker vi filsystemmodulen til å skrive, lese og (ja, endelig!) sende et svar til klientappen:

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

La oss bryte det ned litt:

  • De writeFile filsystemmodulen godtar et filnavn, data og en tilbakeringingsfunksjon som kan returnere en feilmelding hvis det er et problem med å skrive til filen. Hvis du jobber med et CDN som gir feilendepunkter, kan du bruke disse i stedet.
  • De readFile filsystemmodulen aksepterer et filnavn og en tilbakeringingsfunksjon som kan returnere en lesefeil samt lesedata. Når vi ikke har noen lesefeil og lesedataene er tilstede, vil vi konstruere og sende et svar til klienten. Igjen, dette kan erstattes med CDNs endepunkter hvis du har dem.
  • De res.setHeader("Content-Type", "application/pdf"); forteller nettleseren at vi sender en PDF-fil.
  • De res.setHeader("Content-Disposition", "attachment"); ber nettleseren gjøre de mottatte dataene nedlastbare.

Siden API-ruten er klar, kan vi bruke den i appen vår på http://localhost:4000. Vi kan gå videre til klientdelen av søknaden vår for å fullføre handleConversion funksjon.

Håndtere konverteringen

Før vi kan begynne å jobbe med en handleConversion funksjon, må vi opprette en tilstand som håndterer API-forespørslene våre for lasting, feil, suksess og andre meldinger. Vi kommer til å bruke React's useState krok for å sette det opp:

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

handleConversion funksjon, vil vi se etter når nettsiden er lastet inn før vi kjører koden vår og sørge for at div med editable attributtet er ikke tomt:

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

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

Vi fortsetter ved å pakke inn vår eventuelle API-forespørsel i en trycatch, håndtering av eventuelle feil som kan oppstå, og oppdatering av svartilstanden:

try {

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

Deretter setter vi noen verdier for responstilstanden og setter også konfigurasjon for axios og foreta en postforespørsel til serveren:

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

Når vi har fått et vellykket svar, setter vi svartilstanden med de riktige verdiene og ber nettleseren laste ned den mottatte PDF-filen:

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

Og vi kan bruke følgende under contentEditable div for å vise meldinger:

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

Endelig kode

Jeg har pakket alt opp på GitHub slik at du kan sjekke ut hele kildekoden for både serveren og klienten.

Tidstempel:

Mer fra CSS triks