Konvertieren von Sprache in PDF mit NextJS und ExpressJS PlatoBlockchain Data Intelligence. Vertikale Suche. Ai.

Konvertieren von Sprache in PDF mit NextJS und ExpressJS

Da Sprachschnittstellen immer mehr zu einer Sache werden, lohnt es sich, einige der Dinge zu untersuchen, die wir mit Sprachinteraktionen machen können. Was wäre, wenn wir etwas sagen könnten und es transkribieren und als herunterladbares PDF herausgeben könnten?

Nun, Spoiler-Alarm: Wir absolut kann TU das! Es gibt Bibliotheken und Frameworks, die wir zusammenschustern können, um dies zu erreichen, und das werden wir in diesem Artikel gemeinsam tun.

Dies sind die Tools, die wir verwenden

Zunächst einmal sind dies die beiden großen Player: Next.js und Express.js.

Next.js fügt zusätzliche Funktionalitäten zu React hinzu, einschließlich Schlüsselfunktionen zum Erstellen statischer Sites. Es ist eine Anlaufstelle für viele Entwickler, da es sofort einsatzbereit ist, wie dynamisches Routing, Bildoptimierung, integriertes Domänen- und Subdomänen-Routing, schnelle Aktualisierungen, Dateisystem-Routing und API-Routen … unter anderem viele, viele andere Dinge.

In unserem Fall brauchen wir definitiv Next.js dafür API-Routen auf unserem Client-Server. Wir wollen eine Route, die eine Textdatei nimmt, sie in PDF konvertiert, sie in unser Dateisystem schreibt und dann eine Antwort an den Client sendet.

Express.js ermöglicht es uns, eine kleine Node.js-App mit Routing, HTTP-Helfern und Templating zum Laufen zu bringen. Es ist ein Server für unsere eigene API, die wir brauchen, wenn wir Daten zwischen Dingen übergeben und parsen.

Wir haben einige andere Abhängigkeiten, die wir verwenden werden:

  1. Reagieren-Spracherkennung: Eine Bibliothek zum Konvertieren von Sprache in Text, die für React-Komponenten verfügbar ist.
  2. Regenerator-Laufzeit: Eine Bibliothek zur Fehlerbehebung bei „regeneratorRuntime ist nicht definiert“ Fehler, der in Next.js angezeigt wird, wenn React-Speech-Recognition verwendet wird
  3. html-pdf-Knoten: Eine Bibliothek zum Konvertieren einer HTML-Seite oder öffentlichen URL in ein PDF
  4. Axios: Eine Bibliothek zum Erstellen von HTTP-Anforderungen sowohl im Browser als auch in Node.js
  5. Hörner: Eine Bibliothek, die eine ursprungsübergreifende Ressourcenfreigabe ermöglicht

Einrichten

Als erstes wollen wir zwei Projektordner erstellen, einen für den Client und einen für den Server. Nennen Sie sie, wie Sie möchten. Ich nenne meine audio-to-pdf-client und audio-to-pdf-server, Bzw.

Der schnellste Weg, um mit Next.js auf der Clientseite zu beginnen, besteht darin, es mit zu booten nächste App erstellen. Öffnen Sie also Ihr Terminal und führen Sie den folgenden Befehl aus Ihrem Client-Projektordner aus:

npx create-next-app client

Jetzt brauchen wir unseren Express-Server. Wir können es schaffen cd-ing in den Serverprojektordner und Ausführen der npm init Befehl. EIN package.json Datei wird im Server-Projektordner erstellt, sobald sie fertig ist.

Wir müssen Express noch tatsächlich installieren, also machen wir das jetzt mit npm install express. Jetzt können wir eine neue erstellen index.js Datei im Serverprojektordner und legen Sie diesen Code dort ab:

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

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

Bereit, den Server zu betreiben?

node index.js

Wir brauchen noch ein paar Ordner und eine weitere Datei, um voranzukommen:

  • Erstellen Sie components Ordner im Client-Projektordner.
  • Erstellen Sie SpeechToText.jsx Datei in das components Unterordner.

Bevor wir weitermachen, müssen wir noch ein wenig aufräumen. Insbesondere müssen wir den Standardcode in der ersetzen pages/index.js Datei damit:

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

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

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

Das importierte SpeechToText Komponente wird schließlich aus exportiert components/SpeechToText.jsx.

Lassen Sie uns die anderen Abhängigkeiten installieren

Okay, wir haben die Ersteinrichtung für unsere App aus dem Weg geräumt. Jetzt können wir die Bibliotheken installieren, die die herumgereichten Daten verarbeiten.

Wir können unsere Client-Abhängigkeiten installieren mit:

npm install react-speech-recognition regenerator-runtime axios

Unsere Express-Server-Abhängigkeiten sind als nächstes dran, also los cd in den Serverprojektordner und installieren Sie diese:

npm install html-pdf-node cors

Wahrscheinlich ein guter Zeitpunkt, um innezuhalten und sicherzustellen, dass die Dateien in unseren Projektordnern intakt sind. Hier ist, was Sie zu diesem Zeitpunkt im Client-Projektordner haben sollten:

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

Und hier ist, was Sie im Serverprojektordner haben sollten:

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

Erstellen der Benutzeroberfläche

Nun, unser Speech-to-PDF wäre nicht so toll, wenn es keine Möglichkeit gäbe, damit zu interagieren, also erstellen wir dafür eine React-Komponente, die wir aufrufen können <SpeechToText>.

Sie können Ihr eigenes Markup vollständig verwenden. Hier ist, was ich habe, um Ihnen eine Vorstellung von den Teilen zu geben, die wir zusammenstellen:

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;

Diese Komponente gibt a zurück Reaktionsfragment die ein HTML enthält <``section``> Element, das drei divs enthält:

  • .button-container enthält zwei Schaltflächen, die zum Starten und Stoppen der Spracherkennung verwendet werden.
  • .words hat contentEditable und suppressContentEditableWarning Attribute, um dieses Element bearbeitbar zu machen und alle Warnungen von React zu unterdrücken.
  • Ein anderer .button-container enthält zwei weitere Schaltflächen, die zum Zurücksetzen bzw. Konvertieren von Sprache in PDF verwendet werden.

Styling ist eine ganz andere Sache. Ich werde hier nicht darauf eingehen, aber Sie können gerne einige Stile, die ich geschrieben habe, als Ausgangspunkt für Ihre eigenen verwenden styles/global.css Datei.

Vollständiges CSS anzeigen
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;
}

Die darin enthaltenen CSS-Variablen werden verwendet, um die Hintergrundfarbe der Schaltflächen zu steuern.

Sehen wir uns die neuesten Änderungen an! Laufen npm run dev im Terminal und check sie aus.

Sie sollten dies bei Ihrem Besuch im Browser sehen http://localhost:3000:

Konvertieren von Sprache in PDF mit NextJS und ExpressJS

Unsere erste Rede-zu-Text-Konvertierung!

Als Erstes müssen Sie die erforderlichen Abhängigkeiten in unsere importieren <SpeechToText> Komponente:

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

Dann prüfen wir, ob die Spracherkennung vom Browser unterstützt wird und geben einen Hinweis aus, wenn sie nicht unterstützt wird:

const speechRecognitionSupported =
  SpeechRecognition.browserSupportsSpeechRecognition();

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

Als nächstes extrahieren wir transcript und resetTranscript von dem useSpeechRecognition() Haken:

const { transcript, resetTranscript } = useSpeechRecognition();

Das brauchen wir für den Staat, der damit umgeht listening:

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

Wir brauchen auch eine ref für die div an. Nach der Installation können Sie HEIC-Dateien mit der contentEditable Attribut, dann müssen wir das hinzufügen ref ihm zuschreiben und weitergeben transcript as children:

const textBodyRef = useRef(null);

…und:

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

Das Letzte, was wir hier brauchen, ist eine Funktion, die die Spracherkennung auslöst und diese Funktion mit der verknüpft onClick Event-Listener unserer Schaltfläche. Die Taste stellt das Hören ein true und lässt es kontinuierlich laufen. Wir deaktivieren die Schaltfläche, während sie sich in diesem Zustand befindet, um zu verhindern, dass wir zusätzliche Ereignisse auslösen.

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

…und:

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

Ein Klick auf die Schaltfläche sollte nun die Transkription starten.

Mehr Funktionen

OK, wir haben also eine Komponente, die das kann Anfang Hören. Aber jetzt brauchen wir es auch, um ein paar andere Dinge zu tun, wie z stopListening, resetText und handleConversion. Lassen Sie uns diese Funktionen erstellen.

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

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

const handleConversion = async () => {}

Jede der Funktionen wird zu einer hinzugefügt onClick Ereignis-Listener auf den entsprechenden Schaltflächen:

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

Das handleConversion Die Funktion ist asynchron, da wir schließlich eine API-Anfrage stellen werden. Die Schaltfläche „Stopp“ hat das deaktivierte Attribut, das ausgelöst würde, wenn das Zuhören falsch ist.

Wenn wir den Server neu starten und den Browser aktualisieren, können wir jetzt unsere Sprachtranskription im Browser starten, stoppen und zurücksetzen.

Was wir jetzt brauchen, ist die App transkribieren diese erkannte Sprache durch Konvertieren in eine PDF-Datei. Dafür benötigen wir den serverseitigen Pfad von Express.js.

Einrichten der API-Route

Der Zweck dieser Route besteht darin, eine Textdatei zu nehmen, sie in ein PDF zu konvertieren, dieses PDF in unser Dateisystem zu schreiben und dann eine Antwort an den Client zu senden.

Zur Einrichtung öffnen wir die server/index.js Datei und importiere die html-pdf-node und fs Abhängigkeiten, die zum Schreiben und Öffnen unseres Dateisystems verwendet werden.

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

Als nächstes werden wir unsere Route einrichten:

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

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

Wir fahren dann fort, unsere Optionen zu definieren, die für die Verwendung erforderlich sind html-pdf-node innerhalb der Strecke:

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

Das options Objekt akzeptiert einen Wert, um die Papiergröße und den Stil festzulegen. Papiergrößen folgen einem ganz anderen System als die Größeneinheiten, die wir normalerweise im Internet verwenden. Zum Beispiel, A4 ist die typische Briefgröße.

Das file -Objekt akzeptiert entweder die URL einer öffentlichen Website oder HTML-Markup. Um unsere HTML-Seite zu generieren, verwenden wir die html, body, pre HTML-Tags und der Text aus der req.body.

Sie können jedes Styling Ihrer Wahl anwenden.

Als nächstes fügen wir a hinzu trycatch um alle Fehler zu behandeln, die auf dem Weg auftreten könnten:

try {

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

Als nächstes verwenden wir die generatePdf von dem html-pdf-node Bibliothek zum Generieren einer pdfBuffer (die rohe PDF-Datei) aus unserer Datei und erstellen Sie ein Unikat pdfName:

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

  // Next code here
}

Von dort aus verwenden wir das Dateisystemmodul, um eine Antwort an die Client-App zu schreiben, zu lesen und (ja, endlich!) zu senden:

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

Lassen Sie uns das ein wenig aufschlüsseln:

  • Das writeFile Das Dateisystemmodul akzeptiert einen Dateinamen, Daten und eine Rückruffunktion, die eine Fehlermeldung zurückgeben kann, wenn beim Schreiben in die Datei ein Problem auftritt. Wenn Sie mit einem CDN arbeiten, das Fehlerendpunkte bereitstellt, können Sie stattdessen diese verwenden.
  • Das readFile Das Dateisystemmodul akzeptiert einen Dateinamen und eine Callback-Funktion, die in der Lage ist, einen Lesefehler sowie die gelesenen Daten zurückzugeben. Sobald wir keinen Lesefehler haben und die gelesenen Daten vorhanden sind, werden wir eine Antwort erstellen und an den Client senden. Auch dies kann durch die Endpunkte Ihres CDN ersetzt werden, falls Sie diese haben.
  • Das res.setHeader("Content-Type", "application/pdf"); teilt dem Browser mit, dass wir eine PDF-Datei senden.
  • Das res.setHeader("Content-Disposition", "attachment"); weist den Browser an, die empfangenen Daten herunterladbar zu machen.

Da die API-Route bereit ist, können wir sie in unserer App verwenden http://localhost:4000. Wir können mit dem Kundenteil unserer Bewerbung fortfahren, um den zu vervollständigen handleConversion Funktion.

Umgang mit der Konvertierung

Bevor wir mit der Arbeit an a beginnen können handleConversion Funktion müssen wir einen Zustand erstellen, der unsere API-Anforderungen für Lade-, Fehler-, Erfolgs- und andere Nachrichten verarbeitet. Wir werden React verwenden useState Haken, um das einzurichten:

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

Im handleConversion Funktion prüfen wir, wann die Webseite geladen wurde, bevor wir unseren Code ausführen, und stellen sicher, dass die div an. Nach der Installation können Sie HEIC-Dateien mit der editable Attribut ist nicht leer:

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

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

Wir fahren fort, indem wir unsere eventuelle API-Anfrage in a verpacken trycatch, Behandlung eventuell auftretender Fehler und Aktualisierung des Antwortstatus:

try {

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

Als nächstes setzen wir einige Werte für den Antwortstatus und setzen auch config for axios und stellen Sie eine Post-Anfrage an den 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
);

Sobald wir eine erfolgreiche Antwort erhalten haben, setzen wir den Antwortstatus mit den entsprechenden Werten und weisen den Browser an, das empfangene PDF herunterzuladen:

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

Und wir können das Folgende unter contentEditable verwenden div zum Anzeigen von Meldungen:

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

Endgültiger Code

Ich habe alles auf GitHub gepackt, sodass Sie den vollständigen Quellcode sowohl für den Server als auch für den Client einsehen können.

Zeitstempel:

Mehr von CSS-Tricks