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:
- reagere-tale-gjenkjenning: Et bibliotek for å konvertere tale til tekst, noe som gjør det tilgjengelig for React-komponenter.
- 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 - html-pdf-node: Et bibliotek for å konvertere en HTML-side eller offentlig URL til en PDF
- Axios: Et bibliotek for å lage HTTP-forespørsler i både nettleseren og Node.js
- 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-server
Hhv.
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 icomponents
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
harcontentEditable
ogsuppressContentEditableWarning
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
:
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,
});
på 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.