Konvertera tal till PDF med NextJS och ExpressJS PlatoBlockchain Data Intelligence. Vertikal sökning. Ai.

Konvertera tal till PDF med NextJS och ExpressJS

När talgränssnitt blir mer av en sak är det värt att utforska några av de saker vi kan göra med talinteraktioner. Tänk om vi kunde säga något och få det transkriberat och pumpat ut som en nedladdningsbar PDF?

Tja, spoiler alert: vi absolut Kan gör det! Det finns bibliotek och ramverk som vi kan lägga ihop för att få det att hända, och det är vad vi ska göra tillsammans i den här artikeln.

Det här är verktygen vi använder

Först och främst är det här de två stora spelarna: Next.js och Express.js.

Next.js använder ytterligare funktioner till React, inklusive nyckelfunktioner för att bygga statiska platser. Det är ett val för många utvecklare på grund av vad det erbjuder direkt från lådan, som dynamisk routing, bildoptimering, inbyggd domän och underdomän routing, snabbuppdateringar, filsystem routing och API-rutter ... bland många, många andra saker.

I vårt fall behöver vi definitivt Next.js för dess API-rutter på vår klientserver. Vi vill ha en rutt som tar en textfil, konverterar den till PDF, skriver den till vårt filsystem och sedan skickar ett svar till klienten.

Express.js låter oss få igång en liten Node.js-app med routing, HTTP-hjälpare och mallar. Det är en server för vårt eget API, vilket är vad vi behöver när vi skickar och analyserar data mellan saker.

Vi har några andra beroenden som vi kommer att använda:

  1. reagera-taligenkänning: Ett bibliotek för att konvertera tal till text, vilket gör det tillgängligt för React-komponenter.
  2. regenerator-körtid: Ett bibliotek för felsökning av "regeneratorRuntime är inte definierat”-fel som dyker upp i Next.js när du använder react-speech recognition
  3. html-pdf-nod: Ett bibliotek för att konvertera en HTML-sida eller offentlig URL till en PDF
  4. Axios: Ett bibliotek för att göra HTTP-förfrågningar i både webbläsaren och Node.js
  5. cors: Ett bibliotek som tillåter resursdelning mellan olika ursprung

Inställning

Det första vi vill göra är att skapa två projektmappar, en för klienten och en för servern. Namnge dem vad du vill. Jag namnger min audio-to-pdf-client och audio-to-pdf-server, Respektive.

Det snabbaste sättet att komma igång med Next.js på klientsidan är att bootstrap det med skapa-nästa-app. Så öppna din terminal och kör följande kommando från din klientprojektmapp:

npx create-next-app client

Nu behöver vi vår Express-server. Vi kan klara det cdin i serverns projektmapp och kör npm init kommando. A package.json filen kommer att skapas i serverns projektmapp när den är klar.

Vi behöver fortfarande faktiskt installera Express, så låt oss göra det nu med npm install express. Nu kan vi skapa en ny index.js fil i serverprojektmappen och släpp den här koden där:

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

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

Är du redo att köra servern?

node index.js

Vi kommer att behöva ytterligare ett par mappar och ytterligare en fil för att gå vidare:

  • Skapa en components mapp i klientprojektmappen.
  • Skapa en SpeechToText.jsx fil i components mapp.

Innan vi går vidare har vi en liten städning att göra. Specifikt måste vi ersätta standardkoden i pages/index.js fil med detta:

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

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

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

Den importerade SpeechToText komponent kommer så småningom att exporteras från components/SpeechToText.jsx.

Låt oss installera de andra beroenden

Okej, vi har den första installationen för vår app ur vägen. Nu kan vi installera biblioteken som hanterar data som skickas runt.

Vi kan installera våra klientberoenden med:

npm install react-speech-recognition regenerator-runtime axios

Våra Express-serverberoenden är nästa, så låt oss cd i serverprojektmappen och installera dessa:

npm install html-pdf-node cors

Förmodligen en bra tid att pausa och se till att filerna i våra projektmappar är i takt. Här är vad du bör ha i klientprojektmappen vid det här laget:

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

Och här är vad du bör ha i serverprojektmappen:

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

Bygga UI

Tja, vårt tal-till-PDF skulle inte vara så bra om det inte finns något sätt att interagera med det, så låt oss skapa en React-komponent för den som vi kan kalla <SpeechToText>.

Du kan helt använda din egen uppmärkning. Här är vad jag har för att ge dig en uppfattning om de delar vi sätter ihop:

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;

Denna komponent returnerar en Reaktionsfragment som innehåller en HTML <``section``> element som innehåller tre div:er:

  • .button-container innehåller två knappar som kommer att användas för att starta och stoppa taligenkänning.
  • .words har contentEditable och suppressContentEditableWarning attribut för att göra detta element redigerbart och undertrycka alla varningar från React.
  • Annan .button-container innehåller ytterligare två knappar som kommer att användas för att återställa respektive konvertera tal till PDF.

Styling är en helt annan sak. Jag går inte in på det här, men du får gärna använda några stilar jag skrev antingen som utgångspunkt för din egen styles/global.css fil.

Visa hela 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-variablerna där används för att styra bakgrundsfärgen på knapparna.

Låt oss se de senaste förändringarna! Springa npm run dev i terminalen och kolla in dem.

Du bör se detta i webbläsaren när du besöker http://localhost:3000:

Konvertera tal till PDF med NextJS och ExpressJS

Vårt första tal till textkonvertering!

Den första åtgärden att vidta är att importera nödvändiga beroenden till vårt <SpeechToText> komponent:

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

Sedan kontrollerar vi om taligenkänning stöds av webbläsaren och ger ett meddelande om det inte stöds:

const speechRecognitionSupported =
  SpeechRecognition.browserSupportsSpeechRecognition();

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

Nästa upp, låt oss extrahera transcript och resetTranscript från useSpeechRecognition() krok:

const { transcript, resetTranscript } = useSpeechRecognition();

Detta är vad vi behöver för staten som hanterar listening:

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

Vi behöver också en ref för div med contentEditable attribut, då måste vi lägga till ref tillskriva det och passera transcript as children:

const textBodyRef = useRef(null);

…och:

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

Det sista vi behöver här är en funktion som triggar taligenkänning och för att knyta den funktionen till onClick händelseavlyssnare av vår knapp. Knappen ställer in lyssna på true och gör att den körs kontinuerligt. Vi kommer att inaktivera knappen medan den är i det tillståndet för att förhindra oss från att avfyra ytterligare händelser.

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

…och:

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

Genom att klicka på knappen bör nu transkriptionen startas.

Fler funktioner

OK, så vi har en komponent som kan starta lyssnande. Men nu behöver vi det för att göra lite andra saker också, som stopListening, resetText och handleConversion. Låt oss göra dessa funktioner.

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

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

const handleConversion = async () => {}

Var och en av funktionerna kommer att läggas till i en onClick händelseavlyssnare på lämpliga knappar:

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

Smakämnen handleConversion funktionen är asynkron eftersom vi så småningom kommer att göra en API-förfrågan. "Stopp"-knappen har det inaktiverade attributet som skulle utlösas när lyssnandet är falskt.

Om vi ​​startar om servern och uppdaterar webbläsaren kan vi nu starta, stoppa och återställa vår taltranskription i webbläsaren.

Nu behöver vi appen transkribera det igenkända talet genom att konvertera det till en PDF-fil. För det behöver vi sökvägen på serversidan från Express.js.

Konfigurera API-rutten

Syftet med denna väg är att ta en textfil, konvertera den till en PDF, skriva den PDF-filen till vårt filsystem och sedan skicka ett svar till klienten.

För att ställa in skulle vi öppna server/index.js fil och importera html-pdf-node och fs beroenden som kommer att användas för att skriva och öppna vårt filsystem.

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

Därefter kommer vi att ställa in vår rutt:

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

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

Vi fortsätter sedan med att definiera vilka alternativ som krävs för att kunna använda html-pdf-node inne på rutten:

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

Smakämnen options objekt accepterar ett värde för att ställa in pappersstorlek och stil. Pappersstorlekar följer ett mycket annat system än de storleksenheter vi vanligtvis använder på webben. Till exempel, A4 är den typiska bokstavsstorleken.

Smakämnen file objekt accepterar antingen webbadressen till en offentlig webbplats eller HTML-kod. För att skapa vår HTML-sida kommer vi att använda html, body, pre HTML-taggar och texten från req.body.

Du kan applicera valfri styling.

Därefter lägger vi till en trycatch för att hantera eventuella fel som kan dyka upp längs vägen:

try {

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

Därefter använder vi generatePdf från html-pdf-node bibliotek för att generera en pdfBuffer (den råa PDF-filen) från vår fil och skapa en unik pdfName:

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

  // Next code here
}

Därifrån använder vi filsystemmodulen för att skriva, läsa och (ja, äntligen!) skicka ett svar till 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." });
  });
});

Låt oss bryta ner det lite:

  • Smakämnen writeFile filsystemmodulen accepterar ett filnamn, data och en återuppringningsfunktion som kan returnera ett felmeddelande om det finns problem med att skriva till filen. Om du arbetar med ett CDN som tillhandahåller felslutpunkter kan du använda dem istället.
  • Smakämnen readFile filsystemmodulen accepterar ett filnamn och en återuppringningsfunktion som kan eller returnerar ett läsfel samt läsdata. När vi inte har något läsfel och läsdata finns, kommer vi att konstruera och skicka ett svar till klienten. Återigen, detta kan ersättas med ditt CDN:s slutpunkter om du har dem.
  • Smakämnen res.setHeader("Content-Type", "application/pdf"); talar om för webbläsaren att vi skickar en PDF-fil.
  • Smakämnen res.setHeader("Content-Disposition", "attachment"); talar om för webbläsaren att göra mottagna data nedladdningsbara.

Eftersom API-rutten är klar kan vi använda den i vår app på http://localhost:4000. Vi kan gå vidare till klientdelen av vår ansökan för att slutföra handleConversion funktion.

Hantera konverteringen

Innan vi kan börja arbeta på en handleConversion funktion måste vi skapa ett tillstånd som hanterar våra API-förfrågningar för laddning, fel, framgång och andra meddelanden. Vi kommer att använda React's useState krok för att ställa in det:

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

I handleConversion funktion kommer vi att kontrollera när webbsidan har laddats innan vi kör vår kod och se till att div med editable attributet är inte 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 fortsätter genom att slå in vår eventuella API-förfrågan i en trycatch, hantera eventuella fel som kan uppstå och uppdatera svarstillståndet:

try {

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

Därefter ställer vi in ​​några värden för svarstillståndet och ställer även in config för axios och gör en postbegäran till servern:

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 ett framgångsrikt svar ställer vi in ​​svarstillståndet med lämpliga värden och instruerar webbläsaren att ladda ner den mottagna 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();

Och vi kan använda följande under contentEditable div för att visa meddelanden:

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

Slutlig kod

Jag har paketerat allt på GitHub så att du kan kolla in hela källkoden för både servern och klienten.

Tidsstämpel:

Mer från CSS-tricks