Καθώς οι διεπαφές ομιλίας γίνονται όλο και περισσότερο θέμα, αξίζει να εξερευνήσετε μερικά από τα πράγματα που μπορούμε να κάνουμε με τις αλληλεπιδράσεις ομιλίας. Τι θα γινόταν αν μπορούσαμε να πούμε κάτι και να μεταγραφεί και να κυκλοφορήσει ως PDF με δυνατότητα λήψης;
Λοιπόν, spoiler alert: εμείς απολύτως κουτί Κάνε αυτό! Υπάρχουν βιβλιοθήκες και πλαίσια που μπορούμε να συνδυάσουμε για να το πετύχουμε, και αυτό θα κάνουμε μαζί σε αυτό το άρθρο.
Αυτά είναι τα εργαλεία που χρησιμοποιούμε
Πρώτα απ 'όλα, αυτοί είναι οι δύο μεγάλοι παίκτες: Next.js και Express.js.
Next.js επιβάλλει πρόσθετες λειτουργίες στο React, συμπεριλαμβανομένων βασικών λειτουργιών για τη δημιουργία στατικών τοποθεσιών. Είναι μια επιλογή για πολλούς προγραμματιστές λόγω των όσων προσφέρει αμέσως, όπως δυναμική δρομολόγηση, βελτιστοποίηση εικόνας, δρομολόγηση ενσωματωμένου τομέα και υποτομέα, γρήγορες ανανεώσεις, δρομολόγηση συστήματος αρχείων και διαδρομές API... πολλά, πολλά άλλα πράγματα.
Στην περίπτωσή μας, χρειαζόμαστε οπωσδήποτε το Next.js για αυτό Διαδρομές API στον διακομιστή πελάτη μας. Θέλουμε μια διαδρομή που παίρνει ένα αρχείο κειμένου, το μετατρέπει σε PDF, το γράφει στο σύστημα αρχείων μας και μετά στέλνει μια απάντηση στον πελάτη.
Express.js μας επιτρέπει να χρησιμοποιήσουμε μια μικρή εφαρμογή Node.js με δρομολόγηση, βοηθούς HTTP και πρότυπα. Είναι ένας διακομιστής για το δικό μας API, που είναι αυτό που θα χρειαστούμε καθώς περνάμε και αναλύουμε δεδομένα μεταξύ των πραγμάτων.
Έχουμε κάποιες άλλες εξαρτήσεις που θα χρησιμοποιήσουμε:
- αντίδραση-ομιλία-αναγνώριση: Μια βιβλιοθήκη για τη μετατροπή ομιλίας σε κείμενο, καθιστώντας τη διαθέσιμη στα στοιχεία του React.
- αναγεννητής-χρόνος λειτουργίας: Μια βιβλιοθήκη για την αντιμετώπιση προβλημάτων του "
regeneratorRuntime
δεν είναι ορισμένο» που εμφανίζεται στο Next.js όταν χρησιμοποιείται το react-speech-recognition - html-pdf-node: Μια βιβλιοθήκη για τη μετατροπή μιας σελίδας HTML ή μιας δημόσιας διεύθυνσης URL σε PDF
- αξίους: Μια βιβλιοθήκη για την υποβολή αιτημάτων HTTP τόσο στο πρόγραμμα περιήγησης όσο και στο Node.js
- κέρατα: Μια βιβλιοθήκη που επιτρέπει την κοινή χρήση πόρων μεταξύ προέλευσης
Εγκαθιστώ
Το πρώτο πράγμα που θέλουμε να κάνουμε είναι να δημιουργήσουμε δύο φακέλους έργου, έναν για τον πελάτη και έναν για τον διακομιστή. Ονομάστε τους όπως θέλετε. Ονομάζω το δικό μου audio-to-pdf-client
και audio-to-pdf-server
, Αντίστοιχα.
Ο πιο γρήγορος τρόπος για να ξεκινήσετε με το Next.js στην πλευρά του πελάτη είναι να το κάνετε bootstrap δημιουργία-επόμενη-εφαρμογή. Επομένως, ανοίξτε το τερματικό σας και εκτελέστε την ακόλουθη εντολή από το φάκελο του έργου πελάτη σας:
npx create-next-app client
Τώρα χρειαζόμαστε τον διακομιστή Express μας. Μπορούμε να τα βγάλουμε πέρα cd
-μπαίνετε στο φάκελο του έργου διακομιστή και εκτελείτε το npm init
εντολή. ΕΝΑ package.json
το αρχείο θα δημιουργηθεί στο φάκελο του έργου διακομιστή μόλις ολοκληρωθεί.
Πρέπει ακόμα να εγκαταστήσουμε το Express, οπότε ας το κάνουμε τώρα με npm install express
. Τώρα μπορούμε να δημιουργήσουμε ένα νέο index.js
αρχείο στο φάκελο του έργου διακομιστή και αποθέστε αυτόν τον κώδικα εκεί:
const express = require("express")
const app = express()
app.listen(4000, () => console.log("Server is running on port 4000"))
Είστε έτοιμοι να εκτελέσετε τον διακομιστή;
node index.js
Θα χρειαστούμε μερικούς ακόμη φακέλους και άλλο ένα αρχείο για να προχωρήσουμε:
- Δημιουργία
components
φάκελο στο φάκελο του έργου πελάτη. - Δημιουργία
SpeechToText.jsx
αρχείο στοcomponents
υποφάκελος.
Πριν προχωρήσουμε περαιτέρω, έχουμε ένα μικρό καθαρισμό να κάνουμε. Συγκεκριμένα, πρέπει να αντικαταστήσουμε τον προεπιλεγμένο κωδικό στο pages/index.js
αρχείο με αυτό:
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="/el/favicon.ico" />
</Head>
<h1>Convert your speech to pdf</h1>
<main>
<SpeechToText />
</main>
</div>
);
}
Το εισαγόμενο SpeechToText
το στοιχείο θα εξαχθεί τελικά από components/SpeechToText.jsx
.
Ας εγκαταστήσουμε τις άλλες εξαρτήσεις
Εντάξει, δεν έχουμε την αρχική ρύθμιση για την εφαρμογή μας. Τώρα μπορούμε να εγκαταστήσουμε τις βιβλιοθήκες που χειρίζονται τα δεδομένα που διαβιβάζονται.
Μπορούμε να εγκαταστήσουμε τις εξαρτήσεις πελατών μας με:
npm install react-speech-recognition regenerator-runtime axios
Οι εξαρτήσεις των διακομιστών Express είναι οι επόμενες, οπότε ας πάμε cd
στον φάκελο του έργου διακομιστή και εγκαταστήστε τα:
npm install html-pdf-node cors
Πιθανώς είναι καλή στιγμή για παύση και βεβαιωθείτε ότι τα αρχεία στους φακέλους του έργου μας είναι εντάξει. Εδώ είναι τι πρέπει να έχετε στο φάκελο του έργου πελάτη σε αυτό το σημείο:
/audio-to-pdf-web-client
├─ /components
| └── SpeechToText.jsx
├─ /pages
| ├─ _app.js
| └── index.js
└── /styles
├─globals.css
└── Home.module.css
Και εδώ είναι τι πρέπει να έχετε στο φάκελο του έργου διακομιστή:
/audio-to-pdf-server
└── index.js
Δημιουργία του UI
Λοιπόν, η ομιλία μας σε PDF δεν θα ήταν τόσο καλή αν δεν υπάρχει τρόπος να αλληλεπιδράσουμε μαζί της, οπότε ας φτιάξουμε ένα στοιχείο React για αυτό που μπορούμε να καλέσουμε <SpeechToText>
.
Μπορείτε να χρησιμοποιήσετε πλήρως τη δική σας σήμανση. Εδώ είναι τι έχω για να σας δώσω μια ιδέα για τα κομμάτια που συνθέτουμε:
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;
Αυτό το στοιχείο επιστρέφει a Θραύσμα αντίδρασης που περιέχει ένα HTML <``section``>
στοιχείο που περιέχει τρία div:
.button-container
περιέχει δύο κουμπιά που θα χρησιμοποιηθούν για την έναρξη και τη διακοπή της αναγνώρισης ομιλίας..words
έχειcontentEditable
καιsuppressContentEditableWarning
χαρακτηριστικά για να κάνετε αυτό το στοιχείο επεξεργάσιμο και να αποκρύψετε τυχόν προειδοποιήσεις από το React.- Άλλος
.button-container
κρατά δύο ακόμη κουμπιά που θα χρησιμοποιηθούν για την επαναφορά και τη μετατροπή της ομιλίας σε PDF, αντίστοιχα.
Το styling είναι άλλο πράγμα. Δεν θα μπω σε αυτό εδώ, αλλά μπορείτε να χρησιμοποιήσετε κάποια στυλ που έγραψα είτε ως αφετηρία για το δικό σας styles/global.css
αρχείο.
Προβολή πλήρους 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 εκεί χρησιμοποιούνται για τον έλεγχο του χρώματος φόντου των κουμπιών.
Ας δούμε τις τελευταίες αλλαγές! Τρέξιμο npm run dev
στο τερματικό και ελέγξτε τα.
Θα πρέπει να το δείτε στο πρόγραμμα περιήγησης όταν το επισκέπτεστε http://localhost:3000
:
Η πρώτη μας μετατροπή ομιλίας σε κείμενο!
Η πρώτη ενέργεια που πρέπει να κάνετε είναι να εισάγετε τις απαραίτητες εξαρτήσεις στο δικό μας <SpeechToText>
συστατικό:
import React, { useRef, useState } from "react";
import SpeechRecognition, {
useSpeechRecognition,
} from "react-speech-recognition";
import axios from "axios";
Στη συνέχεια, ελέγχουμε εάν η αναγνώριση ομιλίας υποστηρίζεται από το πρόγραμμα περιήγησης και αποδίδουμε μια ειδοποίηση εάν δεν υποστηρίζεται:
const speechRecognitionSupported =
SpeechRecognition.browserSupportsSpeechRecognition();
if (!speechRecognitionSupported) {
return <div>Your browser does not support speech recognition.</div>;
}
Στη συνέχεια, ας εξάγουμε transcript
και resetTranscript
από το useSpeechRecognition()
άγκιστρο:
const { transcript, resetTranscript } = useSpeechRecognition();
Αυτό χρειαζόμαστε για το κράτος που χειρίζεται listening
:
const [listening, setListening] = useState(false);
Χρειαζόμαστε επίσης ένα ref
των div
με contentEditable
χαρακτηριστικό, τότε πρέπει να προσθέσουμε το ref
αποδίδουν σε αυτό και περνούν transcript
as children
:
const textBodyRef = useRef(null);
…και:
<div
className="words"
contentEditable
ref={textBodyRef}
suppressContentEditableWarning={true}
>
{transcript}
</div>
Το τελευταίο πράγμα που χρειαζόμαστε εδώ είναι μια λειτουργία που ενεργοποιεί την αναγνώριση ομιλίας και να συνδέσει αυτή τη λειτουργία με το onClick
ακροατής εκδήλωσης του κουμπιού μας. Το κουμπί ρυθμίζει την ακρόαση true
και το κάνει να λειτουργεί συνεχώς. Θα απενεργοποιήσουμε το κουμπί όσο είναι σε αυτήν την κατάσταση για να μας αποτρέψει από την ενεργοποίηση πρόσθετων συμβάντων.
const startListening = () => {
setListening(true);
SpeechRecognition.startListening({
continuous: true,
});
};
…και:
<button
type="button"
onClick={startListening}
style={{ "--bgColor": "blue" }}
disabled={listening}
>
Start
</button>
Κάνοντας κλικ στο κουμπί θα πρέπει τώρα να ξεκινήσει η μεταγραφή.
Περισσότερες λειτουργίες
Εντάξει, έχουμε λοιπόν ένα εξάρτημα που μπορεί Εκκίνηση ακούγοντας. Αλλά τώρα το χρειαζόμαστε για να κάνουμε και μερικά άλλα πράγματα, όπως stopListening
, resetText
και handleConversion
. Ας φτιάξουμε αυτές τις λειτουργίες.
const stopListening = () => {
setListening(false);
SpeechRecognition.stopListening();
};
const resetText = () => {
stopListening();
resetTranscript();
textBodyRef.current.innerText = "";
};
const handleConversion = async () => {}
Κάθε μία από τις λειτουργίες θα προστεθεί σε ένα onClick
πρόγραμμα ακρόασης συμβάντων στα κατάλληλα κουμπιά:
<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
Η λειτουργία είναι ασύγχρονη επειδή τελικά θα κάνουμε ένα αίτημα API. Το κουμπί "Διακοπή" έχει το χαρακτηριστικό απενεργοποιημένο που θα ενεργοποιηθεί όταν η ακρόαση είναι ψευδής.
Εάν κάνουμε επανεκκίνηση του διακομιστή και ανανεώσουμε το πρόγραμμα περιήγησης, μπορούμε τώρα να ξεκινήσουμε, να σταματήσουμε και να επαναφέρουμε τη μεταγραφή της ομιλίας μας στο πρόγραμμα περιήγησης.
Τώρα αυτό που χρειαζόμαστε είναι να το κάνει η εφαρμογή αντιγράφω που αναγνώρισε την ομιλία μετατρέποντάς την σε αρχείο PDF. Για αυτό, χρειαζόμαστε τη διαδρομή από την πλευρά του διακομιστή από το Express.js.
Ρύθμιση της διαδρομής API
Ο σκοπός αυτής της διαδρομής είναι να λάβουμε ένα αρχείο κειμένου, να το μετατρέψουμε σε PDF, να γράψουμε αυτό το PDF στο σύστημα αρχείων μας και μετά να στείλουμε μια απάντηση στον πελάτη.
Για τη ρύθμιση, θα ανοίξαμε το server/index.js
αρχείο και εισαγωγή του html-pdf-node
και fs
εξαρτήσεις που θα χρησιμοποιηθούν για την εγγραφή και το άνοιγμα του συστήματος αρχείων μας.
const HTMLToPDF = require("html-pdf-node");
const fs = require("fs");
const cors = require("cors)
Στη συνέχεια, θα ρυθμίσουμε τη διαδρομή μας:
app.use(cors())
app.use(express.json())
app.post("/", (req, res) => {
// etc.
})
Στη συνέχεια, προχωράμε στον ορισμό των επιλογών που απαιτούνται για χρήση html-pdf-node
εντός της διαδρομής:
let options = { format: "A4" };
let file = {
content: `<html><body><pre style='font-size: 1.2rem'>${req.body.text}</pre></body></html>`,
};
Η options
Το αντικείμενο δέχεται μια τιμή για να ορίσει το μέγεθος και το στυλ χαρτιού. Τα μεγέθη χαρτιού ακολουθούν ένα πολύ διαφορετικό σύστημα από τις μονάδες μεγέθους που χρησιμοποιούμε συνήθως στον Ιστό. Για παράδειγμα, Το Α4 είναι το τυπικό μέγεθος γράμματος.
Η file
Το αντικείμενο δέχεται είτε τη διεύθυνση URL ενός δημόσιου ιστότοπου είτε τη σήμανση HTML. Για να δημιουργήσουμε τη σελίδα μας HTML, θα χρησιμοποιήσουμε το html
, body
, pre
Ετικέτες HTML και το κείμενο από το req.body
.
Μπορείτε να εφαρμόσετε οποιοδήποτε στυλ της επιλογής σας.
Στη συνέχεια, θα προσθέσουμε ένα trycatch
για να χειριστείτε τυχόν σφάλματα που μπορεί να εμφανιστούν στην πορεία:
try {
} catch(error){
console.log(error);
res.status(500).send(error);
}
Στη συνέχεια, θα χρησιμοποιήσουμε το generatePdf
από το html-pdf-node
βιβλιοθήκη για τη δημιουργία α pdfBuffer
(το ακατέργαστο αρχείο PDF) από το αρχείο μας και να δημιουργήσουμε ένα μοναδικό pdfName
:
HTMLToPDF.generatePdf(file, options).then((pdfBuffer) => {
// console.log("PDF Buffer:-", pdfBuffer);
const pdfName = "./data/speech" + Date.now() + ".pdf";
// Next code here
}
Από εκεί, χρησιμοποιούμε τη λειτουργική μονάδα συστήματος αρχείων για να γράψουμε, να διαβάσουμε και (ναι, τελικά!) να στείλουμε μια απάντηση στην εφαρμογή πελάτη:
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." });
});
});
Ας το αναλύσουμε λίγο:
- Η
writeFile
Η μονάδα συστήματος αρχείων δέχεται ένα όνομα αρχείου, δεδομένα και μια λειτουργία επανάκλησης που μπορεί να επιστρέψει ένα μήνυμα σφάλματος εάν υπάρχει πρόβλημα με την εγγραφή στο αρχείο. Εάν εργάζεστε με ένα CDN που παρέχει τελικά σημεία σφάλματος, θα μπορούσατε να τα χρησιμοποιήσετε. - Η
readFile
Η μονάδα συστήματος αρχείων δέχεται ένα όνομα αρχείου και μια λειτουργία επανάκλησης που είναι ικανή ή επιστρέφει ένα σφάλμα ανάγνωσης καθώς και τα δεδομένα ανάγνωσης. Μόλις δεν έχουμε σφάλμα ανάγνωσης και υπάρχουν τα δεδομένα ανάγνωσης, θα κατασκευάσουμε και θα στείλουμε μια απάντηση στον πελάτη. Και πάλι, αυτό μπορεί να αντικατασταθεί με τα τελικά σημεία του CDN σας, εάν τα έχετε. - Η
res.setHeader("Content-Type", "application/pdf");
λέει στο πρόγραμμα περιήγησης ότι στέλνουμε ένα αρχείο PDF. - Η
res.setHeader("Content-Disposition", "attachment");
λέει στο πρόγραμμα περιήγησης να κάνει τα ληφθέντα δεδομένα με δυνατότητα λήψης.
Εφόσον η διαδρομή API είναι έτοιμη, μπορούμε να τη χρησιμοποιήσουμε στην εφαρμογή μας στο http://localhost:4000
. Μπορούμε να προχωρήσουμε στο τμήμα πελάτη της αίτησής μας για να το ολοκληρώσουμε handleConversion
λειτουργία.
Χειρισμός της μετατροπής
Πριν αρχίσουμε να εργαζόμαστε σε ένα handleConversion
λειτουργία, πρέπει να δημιουργήσουμε μια κατάσταση που να χειρίζεται τα αιτήματα API για φόρτωση, σφάλματα, επιτυχία και άλλα μηνύματα. Θα χρησιμοποιήσουμε το React useState
συνδέστε για να το ρυθμίσετε:
const [response, setResponse] = useState({
loading: false,
message: "",
error: false,
success: false,
});
Στο handleConversion
λειτουργία, θα ελέγξουμε πότε έχει φορτωθεί η ιστοσελίδα πριν εκτελέσουμε τον κώδικά μας και θα βεβαιωθούμε ότι div
με editable
Το χαρακτηριστικό δεν είναι κενό:
if (typeof window !== "undefined") {
const userText = textBodyRef.current.innerText;
// console.log(textBodyRef.current.innerText);
if (!userText) {
alert("Please speak or write some text.");
return;
}
}
Προχωράμε τυλίγοντας το ενδεχόμενο αίτημα API σε α trycatch
, χειρισμός τυχόν σφάλματος που μπορεί να προκύψει και ενημέρωση της κατάστασης απόκρισης:
try {
} catch(error){
setResponse({
...response,
loading: false,
error: true,
message:
"An unexpected error occurred. Text not converted. Please try again",
success: false,
});
}
Στη συνέχεια, ορίσαμε κάποιες τιμές για την κατάσταση απόκρισης και ορίσαμε επίσης τη διαμόρφωση για axios
και κάντε ένα αίτημα ανάρτησης στον διακομιστή:
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
);
Μόλις λάβουμε μια επιτυχημένη απάντηση, ορίζουμε την κατάσταση απόκρισης με τις κατάλληλες τιμές και δίνουμε οδηγίες στο πρόγραμμα περιήγησης να κατεβάσει το ληφθέν PDF:
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();
Και μπορούμε να χρησιμοποιήσουμε τα παρακάτω κάτω από το περιεχόμενο Επεξεργάσιμο div
για την εμφάνιση μηνυμάτων:
<div>
{response.success && <i className="success">{response.message}</i>}
{response.error && <i className="error">{response.message}</i>}
</div>
Τελικός κωδικός
Έχω συσκευάσει τα πάντα στο GitHub, ώστε να μπορείτε να δείτε τον πλήρη πηγαίο κώδικα τόσο για τον διακομιστή όσο και για τον πελάτη.