Întrucât interfețele de vorbire devin mai mult un lucru, merită să explorăm câteva dintre lucrurile pe care le putem face cu interacțiunile vocale. Cum ar fi, ce se întâmplă dacă am putea spune ceva și să-l transcriem și să le extragem ca PDF descărcabil?
Ei bine, alertă de spoiler: suntem absolut poate să fa aia! Există biblioteci și cadre pe care le putem pune împreună pentru a face acest lucru, și asta vom face împreună în acest articol.
Acestea sunt instrumentele pe care le folosim
În primul rând, aceștia sunt cei doi jucători mari: Next.js și Express.js.
Next.js abordează funcționalități suplimentare pentru React, inclusiv caracteristici cheie pentru construirea de site-uri statice. Este o alegere pentru mulți dezvoltatori datorită a ceea ce oferă imediat, cum ar fi rutarea dinamică, optimizarea imaginii, rutarea în domeniul și subdomeniul încorporat, reîmprospătările rapide, rutarea sistemului de fișiere și rutele API... printre multe, multe alte lucruri.
În cazul nostru, cu siguranță avem nevoie de Next.js pentru acesta rute API pe serverul nostru client. Vrem o rută care preia un fișier text, îl convertește în PDF, îl scrie în sistemul nostru de fișiere, apoi trimite un răspuns clientului.
Express.js ne permite să punem în funcțiune o mică aplicație Node.js cu rutare, ajutoare HTTP și șabloane. Este un server pentru propriul nostru API, care este ceea ce vom avea nevoie pe măsură ce trecem și analizăm datele între lucruri.
Avem și alte dependențe pe care le vom folosi:
- reacționează-recunoașterea vorbirii: O bibliotecă pentru conversia vorbirii în text, făcându-l disponibil pentru componentele React.
- balsam-durata: O bibliotecă pentru depanarea „
regeneratorRuntime
nu este definită” eroare care apare în Next.js atunci când utilizați react-speech-recognition - html-pdf-node: O bibliotecă pentru conversia unei pagini HTML sau a unei adrese URL publice într-un PDF
- Axios: O bibliotecă pentru a face solicitări HTTP atât în browser, cât și în Node.js
- CORS: O bibliotecă care permite partajarea resurselor între origini
Configurare
Primul lucru pe care vrem să-l facem este să creăm două foldere de proiect, unul pentru client și unul pentru server. Numiți-le cum doriți. Îl numesc pe al meu audio-to-pdf-client
și audio-to-pdf-server
, respectiv.
Cea mai rapidă modalitate de a începe cu Next.js din partea clientului este să-l pornești create-next-app. Deci, deschideți terminalul și rulați următoarea comandă din folderul proiectului client:
npx create-next-app client
Acum avem nevoie de serverul nostru Express. Putem ajunge cd
-ing în folderul de proiect al serverului și rulează npm init
comanda. A package.json
fișierul va fi creat în folderul de proiect al serverului odată ce este finalizat.
Încă trebuie să instalăm Express, așa că hai să facem asta acum cu npm install express
. Acum putem crea un nou index.js
fișier în folderul de proiect al serverului și plasați acest cod acolo:
const express = require("express")
const app = express()
app.listen(4000, () => console.log("Server is running on port 4000"))
Sunteți gata să rulați serverul?
node index.js
Vom avea nevoie de încă câteva foldere și un alt fișier pentru a merge mai departe:
- Crearea unei
components
folder în folderul proiect client. - Crearea unei
SpeechToText.jsx
de fișier încomponents
subdosar.
Înainte de a merge mai departe, avem de făcut o mică curățare. Mai exact, trebuie să înlocuim codul implicit în fișierul pages/index.js
fisier cu asta:
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="/ro/favicon.ico" />
</Head>
<h1>Convert your speech to pdf</h1>
<main>
<SpeechToText />
</main>
</div>
);
}
Cele importate SpeechToText
componenta va fi în cele din urmă exportată din components/SpeechToText.jsx
.
Să instalăm celelalte dependențe
Bine, avem configurarea inițială pentru aplicația noastră. Acum putem instala bibliotecile care gestionează datele care sunt transmise.
Putem instala dependențele clienților noștri cu:
npm install react-speech-recognition regenerator-runtime axios
Urmează dependențele serverului nostru Express, așa că haideți cd
în folderul de proiect al serverului și instalați cele:
npm install html-pdf-node cors
Probabil este un moment bun pentru a face pauză și pentru a vă asigura că fișierele din folderele proiectelor noastre sunt intacte. Iată ce ar trebui să aveți în folderul proiectului client în acest moment:
/audio-to-pdf-web-client
├─ /components
| └── SpeechToText.jsx
├─ /pages
| ├─ _app.js
| └── index.js
└── /styles
├─globals.css
└── Home.module.css
Și iată ce ar trebui să aveți în folderul de proiect al serverului:
/audio-to-pdf-server
└── index.js
Construirea interfeței de utilizare
Ei bine, discursul nostru în PDF nu ar fi chiar atât de grozav dacă nu există nicio modalitate de a interacționa cu el, așa că haideți să facem o componentă React pe care o putem numi <SpeechToText>
.
Puteți folosi în totalitate propriul dvs. marcaj. Iată ce trebuie să vă dau o idee despre piesele pe care le punem împreună:
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;
Această componentă returnează a Fragment de reacție care conține un HTML <``section``>
element care conține trei div-uri:
.button-container
conține două butoane care vor fi folosite pentru a porni și opri recunoașterea vorbirii..words
arecontentEditable
șisuppressContentEditableWarning
atribute pentru a face acest element editabil și pentru a suprima orice avertismente de la React.- O alta
.button-container
mai deține două butoane care vor fi folosite pentru a reseta și, respectiv, a converti vorbirea în PDF.
Styling-ul este cu totul altceva. Nu voi intra aici, dar sunteți binevenit să folosiți unele stiluri pe care le-am scris, fie ca punct de plecare pentru propriul dvs. styles/global.css
fișier.
Vizualizați CSS complet
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;
}
Variabilele CSS din acolo sunt folosite pentru a controla culoarea de fundal a butoanelor.
Să vedem cele mai recente modificări! Alerga npm run dev
în terminal și verificați-le.
Ar trebui să vedeți acest lucru în browser când vizitați http://localhost:3000
:
Prima noastră conversie discurs în text!
Prima acțiune de luat este să importați dependențele necesare în sistemul nostru <SpeechToText>
componentă:
import React, { useRef, useState } from "react";
import SpeechRecognition, {
useSpeechRecognition,
} from "react-speech-recognition";
import axios from "axios";
Apoi verificăm dacă recunoașterea vorbirii este acceptată de browser și redăm o notificare dacă nu este acceptată:
const speechRecognitionSupported =
SpeechRecognition.browserSupportsSpeechRecognition();
if (!speechRecognitionSupported) {
return <div>Your browser does not support speech recognition.</div>;
}
În continuare, să extragem transcript
și resetTranscript
de la useSpeechRecognition()
cârlig:
const { transcript, resetTranscript } = useSpeechRecognition();
De asta avem nevoie pentru statul care se ocupă listening
:
const [listening, setListening] = useState(false);
Avem nevoie și de un ref
pentru div
cu contentEditable
atribut, atunci trebuie să adăugăm ref
atribui-i și treci transcript
as children
:
const textBodyRef = useRef(null);
…și:
<div
className="words"
contentEditable
ref={textBodyRef}
suppressContentEditableWarning={true}
>
{transcript}
</div>
Ultimul lucru de care avem nevoie aici este o funcție care să declanșeze recunoașterea vorbirii și să lege această funcție de onClick
ascultător de eveniment al butonului nostru. Butonul setează ascultarea true
și îl face să ruleze continuu. Vom dezactiva butonul în timp ce este în acea stare pentru a ne împiedica să lansăm evenimente suplimentare.
const startListening = () => {
setListening(true);
SpeechRecognition.startListening({
continuous: true,
});
};
…și:
<button
type="button"
onClick={startListening}
style={{ "--bgColor": "blue" }}
disabled={listening}
>
Start
</button>
Făcând clic pe butonul ar trebui să pornească acum transcrierea.
Mai multe funcții
OK, deci avem o componentă care poate Începe ascultare. Dar acum avem nevoie de el pentru a face și alte câteva lucruri, cum ar fi stopListening
, resetText
și handleConversion
. Să facem acele funcții.
const stopListening = () => {
setListening(false);
SpeechRecognition.stopListening();
};
const resetText = () => {
stopListening();
resetTranscript();
textBodyRef.current.innerText = "";
};
const handleConversion = async () => {}
Fiecare dintre funcții va fi adăugată la un onClick
ascultător de evenimente pe butoanele corespunzătoare:
<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>
handleConversion
funcția este asincronă, deoarece în cele din urmă vom face o solicitare API. Butonul „Stop” are atributul dezactivat care ar fi declanșat atunci când ascultarea este falsă.
Dacă repornim serverul și reîmprospătăm browserul, acum putem porni, opri și reseta transcrierea vorbirii în browser.
Acum, ceea ce avem nevoie este ca aplicația transcrie care a recunoscut vorbirea transformându-l într-un fișier PDF. Pentru asta, avem nevoie de calea pe partea serverului din Express.js.
Configurarea rutei API
Scopul acestei rute este de a prelua un fișier text, de a-l converti într-un PDF, de a scrie acel PDF în sistemul nostru de fișiere, apoi de a trimite un răspuns clientului.
Pentru a configura, am deschide server/index.js
fișier și importați fișierul html-pdf-node
și fs
dependențe care vor fi folosite pentru a scrie și deschide sistemul nostru de fișiere.
const HTMLToPDF = require("html-pdf-node");
const fs = require("fs");
const cors = require("cors)
În continuare, ne vom configura traseul:
app.use(cors())
app.use(express.json())
app.post("/", (req, res) => {
// etc.
})
Apoi trecem la definirea opțiunilor necesare pentru utilizare html-pdf-node
in interiorul traseului:
let options = { format: "A4" };
let file = {
content: `<html><body><pre style='font-size: 1.2rem'>${req.body.text}</pre></body></html>`,
};
options
obiectul acceptă o valoare pentru a seta dimensiunea și stilul hârtiei. Dimensiunile hârtiei urmează un sistem mult diferit față de unitățile de dimensionare pe care le folosim de obicei pe web. De exemplu, A4 este dimensiunea tipică a literelor.
file
obiect acceptă fie adresa URL a unui site web public, fie marcaj HTML. Pentru a genera pagina noastră HTML, vom folosi html
, body
, pre
Etichetele HTML și textul din req.body
.
Puteți aplica orice stil la alegere.
În continuare, vom adăuga un trycatch
pentru a gestiona orice erori care ar putea apărea pe parcurs:
try {
} catch(error){
console.log(error);
res.status(500).send(error);
}
În continuare, vom folosi generatePdf
de la html-pdf-node
bibliotecă pentru a genera a pdfBuffer
(fișierul PDF brut) din fișierul nostru și creați un unic pdfName
:
HTMLToPDF.generatePdf(file, options).then((pdfBuffer) => {
// console.log("PDF Buffer:-", pdfBuffer);
const pdfName = "./data/speech" + Date.now() + ".pdf";
// Next code here
}
De acolo, folosim modulul sistemului de fișiere pentru a scrie, citi și (da, în sfârșit!) trimite un răspuns la aplicația client:
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." });
});
});
Să dezvăluim asta puțin:
-
writeFile
Modulul sistemului de fișiere acceptă un nume de fișier, date și o funcție de apel invers care poate returna un mesaj de eroare dacă există o problemă la scrierea fișierului. Dacă lucrați cu un CDN care oferă puncte finale de eroare, le puteți folosi în schimb. -
readFile
Modulul sistem de fișiere acceptă un nume de fișier și o funcție de apel invers care este capabilă sau returnează o eroare de citire, precum și datele citite. Odată ce nu avem nicio eroare de citire și datele citite sunt prezente, vom construi și vom trimite un răspuns clientului. Din nou, acesta poate fi înlocuit cu punctele finale ale CDN-ului dvs. dacă le aveți. -
res.setHeader("Content-Type", "application/pdf");
spune browserului că trimitem un fișier PDF. -
res.setHeader("Content-Disposition", "attachment");
spune browserului să facă datele primite descărcabile.
Deoarece ruta API este gata, o putem folosi în aplicația noastră la http://localhost:4000
. Putem trece la partea client a aplicației noastre pentru a finaliza handleConversion
Funcția.
Gestionarea conversiei
Înainte de a putea începe să lucrăm la un handleConversion
funcție, trebuie să creăm o stare care să gestioneze solicitările noastre API pentru încărcare, eroare, succes și alte mesaje. Vom folosi React's useState
cârlig pentru a configura asta:
const [response, setResponse] = useState({
loading: false,
message: "",
error: false,
success: false,
});
În handleConversion
funcție, vom verifica când pagina web a fost încărcată înainte de a rula codul nostru și ne vom asigura că div
cu editable
atributul nu este gol:
if (typeof window !== "undefined") {
const userText = textBodyRef.current.innerText;
// console.log(textBodyRef.current.innerText);
if (!userText) {
alert("Please speak or write some text.");
return;
}
}
Continuăm prin împachetarea eventualei noastre solicitări API într-un trycatch
, gestionând orice eroare care poate apărea și actualizarea stării de răspuns:
try {
} catch(error){
setResponse({
...response,
loading: false,
error: true,
message:
"An unexpected error occurred. Text not converted. Please try again",
success: false,
});
}
Apoi, setăm câteva valori pentru starea răspunsului și, de asemenea, setăm config pentru axios
și faceți o cerere de postare pe 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
);
Odată ce am primit un răspuns de succes, setăm starea răspunsului cu valorile corespunzătoare și instruim browserul să descarce PDF-ul primit:
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();
Și putem folosi următoarele sub conținutul Editabil div
pentru afișarea mesajelor:
<div>
{response.success && <i className="success">{response.message}</i>}
{response.error && <i className="error">{response.message}</i>}
</div>
Cod final
Am împachetat totul pe GitHub, astfel încât să puteți verifica întregul cod sursă atât pentru server, cât și pentru client.