Konuşma arayüzlerinin daha çok bir şey haline gelmesiyle, konuşma etkileşimleriyle yapabileceğimiz bazı şeyleri keşfetmeye değer. Mesela, bir şey söyleyip bunu yazıya döküp indirilebilir bir PDF olarak yayınlasak nasıl olur?
Pekala, spoiler uyarısı: biz kesinlikle yapabilmek yap bunu! Bunu gerçekleştirmek için bir araya getirebileceğimiz kütüphaneler ve çerçeveler var ve bu makalede birlikte yapacağımız şey bu.
Bunlar kullandığımız araçlar
Öncelikle, bunlar iki büyük oyuncu: Next.js ve Express.js.
Next.js Statik siteler oluşturmak için temel özellikler de dahil olmak üzere, React'e ek işlevler üzerinde çalışır. Dinamik yönlendirme, görüntü optimizasyonu, yerleşik alan ve alt alan yönlendirme, hızlı yenilemeler, dosya sistemi yönlendirme ve API yolları gibi kutudan çıktığı anda sunduğu özellikler nedeniyle birçok geliştirici için bir tercihtir. birçok, başka birçok şey.
Bizim durumumuzda, bunun için kesinlikle Next.js'ye ihtiyacımız var. API yolları istemci sunucumuzda. Bir metin dosyası alan, onu PDF'ye dönüştüren, onu dosya sistemimize yazan ve ardından istemciye bir yanıt gönderen bir yol istiyoruz.
Express.js yönlendirme, HTTP yardımcıları ve şablonlama ile çalışan küçük bir Node.js uygulaması edinmemizi sağlar. Bu, kendi API'miz için bir sunucudur, bu, şeyler arasında veri geçirirken ve ayrıştırırken ihtiyacımız olan şeydir.
Kullanacağımız başka bağımlılıklarımız var:
- tepki-konuşma-tanıma: Konuşmayı metne dönüştüren ve onu React bileşenlerinin kullanımına sunan bir kitaplık.
- rejeneratör-çalışma zamanı: Sorun giderme için bir kitaplık “
regeneratorRuntime
tepki-konuşma-tanıma kullanılırken Next.js'de görünen tanımlanmadı" hatası - html-pdf-düğüm: Bir HTML sayfasını veya genel URL'yi PDF'ye dönüştürmek için bir kitaplık
- AXIOS: Hem tarayıcıda hem de Node.js'de HTTP istekleri yapmak için bir kitaplık
- cors: Kökenler arası kaynak paylaşımına izin veren bir kitaplık
Kurma
Yapmak istediğimiz ilk şey, biri istemci diğeri sunucu için olmak üzere iki proje klasörü oluşturmak. Onlara ne istersen isim ver. benimkine isim veriyorum audio-to-pdf-client
ve audio-to-pdf-server
, Sırasıyla.
İstemci tarafında Next.js'yi kullanmaya başlamanın en hızlı yolu, onu aşağıdakilerle önyüklemektir: sonraki uygulama oluştur. Bu nedenle, terminalinizi açın ve istemci proje klasörünüzden aşağıdaki komutu çalıştırın:
npx create-next-app client
Şimdi Express sunucumuza ihtiyacımız var. onu alabiliriz cd
- sunucu proje klasörüne girerek ve npm init
emretmek. A package.json
dosya, tamamlandıktan sonra sunucu proje klasöründe oluşturulacaktır.
Hala Express'i gerçekten yüklememiz gerekiyor, o yüzden şimdi bunu npm install express
. Artık yeni bir tane oluşturabiliriz index.js
sunucu proje klasöründeki dosya ve şu kodu buraya bırakın:
const express = require("express")
const app = express()
app.listen(4000, () => console.log("Server is running on port 4000"))
Sunucuyu çalıştırmaya hazır mısınız?
node index.js
İlerlemek için birkaç klasöre ve bir dosyaya daha ihtiyacımız olacak:
- Hat için bir
components
istemci proje klasöründeki klasör. - Hat için bir
SpeechToText.jsx
içindeki dosyayıcomponents
alt klasör.
Daha ileri gitmeden önce, yapmamız gereken küçük bir temizlik var. Özellikle, varsayılan kodu değiştirmemiz gerekiyor. pages/index.js
bununla dosya:
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="/tr/favicon.ico" />
</Head>
<h1>Convert your speech to pdf</h1>
<main>
<SpeechToText />
</main>
</div>
);
}
İthal SpeechToText
bileşen sonunda ihraç edilecek components/SpeechToText.jsx
.
Diğer bağımlılıkları yükleyelim
Pekala, uygulamamızın ilk kurulumunu aradan çıkardık. Artık etrafta dolaşan verileri işleyen kitaplıkları kurabiliriz.
İstemci bağımlılıklarımızı aşağıdakilerle kurabiliriz:
npm install react-speech-recognition regenerator-runtime axios
Sırada Ekspres sunucu bağımlılıklarımız var, hadi cd
sunucu proje klasörüne atın ve şunları kurun:
npm install html-pdf-node cors
Duraklatmak ve proje klasörlerimizdeki dosyaların eksiksiz olduğundan emin olmak için muhtemelen iyi bir zaman. Bu noktada istemci proje klasöründe olması gerekenler:
/audio-to-pdf-web-client
├─ /components
| └── SpeechToText.jsx
├─ /pages
| ├─ _app.js
| └── index.js
└── /styles
├─globals.css
└── Home.module.css
Ve sunucu proje klasöründe olması gerekenler:
/audio-to-pdf-server
└── index.js
Kullanıcı Arayüzü Oluşturma
Pekala, eğer onunla etkileşime geçmenin bir yolu yoksa, konuşmadan PDF'e o kadar da harika olmazdı, o yüzden arayabileceğimiz bir React bileşeni yapalım. <SpeechToText>
.
Tamamen kendi işaretlemenizi kullanabilirsiniz. İşte size bir araya getirdiğimiz parçalar hakkında bir fikir vermem gerekenler:
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;
Bu bileşen, bir Tepki fragmanı HTML içeren <``section``>
üç div içeren öğe:
.button-container
konuşma tanımayı başlatmak ve durdurmak için kullanılacak iki düğme içerir..words
vardırcontentEditable
vesuppressContentEditableWarning
Bu öğeyi düzenlenebilir kılmak ve React'ten gelen uyarıları bastırmak için öznitelikler.- Başka
.button-container
sırasıyla konuşmayı sıfırlamak ve PDF'ye dönüştürmek için kullanılacak iki düğmeyi daha tutar.
Stil tamamen başka bir şeydir. Burada buna girmeyeceğim, ancak yazdığım bazı stilleri kendi tarzınız için bir başlangıç noktası olarak kullanabilirsiniz. styles/global.css
dosyası.
Tam CSS'yi Görüntüle
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;
}
Buradaki CSS değişkenleri, düğmelerin arka plan rengini kontrol etmek için kullanılıyor.
En son değişiklikleri görelim! Koşmak npm run dev
terminalde ve kontrol edin.
Bunu ziyaret ettiğinizde tarayıcıda görmelisiniz http://localhost:3000
:
Metne dönüştürmeye ilk konuşmamız!
Yapılacak ilk işlem, gerekli bağımlılıkları programımıza aktarmaktır. <SpeechToText>
bileşen:
import React, { useRef, useState } from "react";
import SpeechRecognition, {
useSpeechRecognition,
} from "react-speech-recognition";
import axios from "axios";
Ardından tarayıcı tarafından konuşma tanımanın desteklenip desteklenmediğini kontrol ediyoruz ve desteklenmiyorsa bir bildirimde bulunuyoruz:
const speechRecognitionSupported =
SpeechRecognition.browserSupportsSpeechRecognition();
if (!speechRecognitionSupported) {
return <div>Your browser does not support speech recognition.</div>;
}
Sıradaki, çıkaralım transcript
ve resetTranscript
itibaren useSpeechRecognition()
kanca:
const { transcript, resetTranscript } = useSpeechRecognition();
Yöneten devlet için ihtiyacımız olan şey bu. listening
:
const [listening, setListening] = useState(false);
Ayrıca bir ihtiyacımız var ref
için div
ile contentEditable
öznitelik, sonra eklememiz gerekir ref
ona atfet ve geç transcript
as children
:
const textBodyRef = useRef(null);
…ve:
<div
className="words"
contentEditable
ref={textBodyRef}
suppressContentEditableWarning={true}
>
{transcript}
</div>
Burada ihtiyacımız olan son şey, konuşma tanımayı tetikleyen ve bu işlevi ana işleve bağlayan bir işlevdir. onClick
düğmemizin olay dinleyicisi. Düğme dinlemeyi ayarlar true
ve sürekli çalışmasını sağlar. Ek olayları tetiklememizi önlemek için bu durumdayken düğmeyi devre dışı bırakacağız.
const startListening = () => {
setListening(true);
SpeechRecognition.startListening({
continuous: true,
});
};
…ve:
<button
type="button"
onClick={startListening}
style={{ "--bgColor": "blue" }}
disabled={listening}
>
Start
</button>
Düğmeye tıklamak şimdi transkripsiyona başlamalıdır.
Diğer işlevler
Tamam, yani yapabilecek bir bileşenimiz var başlama dinleme. Ama şimdi birkaç şey daha yapmasına ihtiyacımız var, örneğin stopListening
, resetText
ve handleConversion
. Bu fonksiyonları yapalım.
const stopListening = () => {
setListening(false);
SpeechRecognition.stopListening();
};
const resetText = () => {
stopListening();
resetTranscript();
textBodyRef.current.innerText = "";
};
const handleConversion = async () => {}
İşlevlerin her biri bir onClick
uygun düğmelerde olay dinleyicisi:
<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>
The handleConversion
işlev eşzamansızdır çünkü sonunda bir API isteğinde bulunacağız. “Durdur” düğmesi, dinleme yanlış olduğunda tetiklenecek olan devre dışı bırakılmış özniteliğe sahiptir.
Sunucuyu yeniden başlatıp tarayıcıyı yenilersek, artık tarayıcıdaki konuşma transkripsiyonumuzu başlatabilir, durdurabilir ve sıfırlayabiliriz.
Şimdi ihtiyacımız olan şey, uygulamanın uyarlamak bu konuşmayı bir PDF dosyasına dönüştürerek tanıdı. Bunun için Express.js'den sunucu tarafı yoluna ihtiyacımız var.
API rotasını ayarlama
Bu rotanın amacı bir metin dosyası almak, onu PDF'ye dönüştürmek, bu PDF'yi dosya sistemimize yazmak ve ardından istemciye bir yanıt göndermektir.
Kurulum için, server/index.js
dosya ve içe aktar html-pdf-node
ve fs
dosya sistemimizi yazmak ve açmak için kullanılacak bağımlılıklar.
const HTMLToPDF = require("html-pdf-node");
const fs = require("fs");
const cors = require("cors)
Ardından, rotamızı oluşturacağız:
app.use(cors())
app.use(express.json())
app.post("/", (req, res) => {
// etc.
})
Daha sonra kullanmak için gerekli seçeneklerimizi tanımlamaya devam ediyoruz. html-pdf-node
rota içinde:
let options = { format: "A4" };
let file = {
content: `<html><body><pre style='font-size: 1.2rem'>${req.body.text}</pre></body></html>`,
};
The options
nesne, kağıt boyutunu ve stilini ayarlamak için bir değer kabul eder. Kağıt boyutları, genellikle web'de kullandığımız boyutlandırma birimlerinden çok farklı bir sistem izler. Örneğin, A4 tipik harf boyutudur.
The file
nesne, genel bir web sitesinin URL'sini veya HTML işaretlemesini kabul eder. HTML sayfamızı oluşturmak için kullanacağız html
, body
, pre
HTML etiketleri ve metinden req.body
.
Seçtiğiniz herhangi bir stili uygulayabilirsiniz.
Ardından, bir ekleyeceğiz trycatch
yol boyunca ortaya çıkabilecek hataları işlemek için:
try {
} catch(error){
console.log(error);
res.status(500).send(error);
}
Daha sonra, kullanacağız generatePdf
itibaren html-pdf-node
oluşturmak için kitaplık pdfBuffer
(ham PDF dosyası) dosyamızdan ve benzersiz bir pdfName
:
HTMLToPDF.generatePdf(file, options).then((pdfBuffer) => {
// console.log("PDF Buffer:-", pdfBuffer);
const pdfName = "./data/speech" + Date.now() + ".pdf";
// Next code here
}
Oradan, istemci uygulamasına bir yanıt yazmak, okumak ve (evet, sonunda!) göndermek için dosya sistemi modülünü kullanırız:
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." });
});
});
Bunu biraz parçalayalım:
- The
writeFile
dosya sistemi modülü bir dosya adı, veri ve dosyaya yazarken bir sorun olduğunda bir hata mesajı döndürebilen bir geri arama işlevini kabul eder. Hata bitiş noktaları sağlayan bir CDN ile çalışıyorsanız, bunun yerine bunları kullanabilirsiniz. - The
readFile
dosya sistemi modülü, bir dosya adı ve okuma verilerinin yanı sıra bir okuma hatası verebilen veya döndürebilen bir geri arama işlevini kabul eder. Okuma hatamız olmadığında ve okunan veriler mevcut olduğunda, istemciye bir yanıt oluşturup göndereceğiz. Yine, bu, varsa CDN'nizin uç noktalarıyla değiştirilebilir. - The
res.setHeader("Content-Type", "application/pdf");
tarayıcıya bir PDF dosyası gönderdiğimizi söyler. - The
res.setHeader("Content-Disposition", "attachment");
tarayıcıya alınan verileri indirilebilir hale getirmesini söyler.
API rotası hazır olduğundan, uygulamamızda şu adreste kullanabiliriz: http://localhost:4000
. İşlemi tamamlamak için uygulamamızın istemci kısmına geçebiliriz. handleConversion
fonksiyonu.
Dönüşümü işlemek
Bir üzerinde çalışmaya başlamadan önce handleConversion
yükleme, hata, başarı ve diğer mesajlar için API isteklerimizi işleyen bir durum oluşturmamız gerekiyor. React'i kullanacağız useState
bunu ayarlamak için kanca:
const [response, setResponse] = useState({
loading: false,
message: "",
error: false,
success: false,
});
içinde handleConversion
fonksiyonu, kodumuzu çalıştırmadan önce web sayfasının ne zaman yüklendiğini kontrol edeceğiz ve div
ile editable
nitelik boş değil:
if (typeof window !== "undefined") {
const userText = textBodyRef.current.innerText;
// console.log(textBodyRef.current.innerText);
if (!userText) {
alert("Please speak or write some text.");
return;
}
}
Nihai API isteğimizi bir trycatch
, ortaya çıkabilecek herhangi bir hatayı ele alma ve yanıt durumunu güncelleme:
try {
} catch(error){
setResponse({
...response,
loading: false,
error: true,
message:
"An unexpected error occurred. Text not converted. Please try again",
success: false,
});
}
Ardından, yanıt durumu için bazı değerler belirledik ve ayrıca axios
ve sunucuya bir gönderi isteği yapın:
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
);
Başarılı bir yanıt aldığımızda, yanıt durumunu uygun değerlerle ayarlar ve tarayıcıya alınan PDF'yi indirmesi talimatını veririz:
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();
Ve aşağıdakileri contentEditable'ın altında kullanabiliriz div
mesajları görüntülemek için:
<div>
{response.success && <i className="success">{response.message}</i>}
{response.error && <i className="error">{response.message}</i>}
</div>
Son kod
Hem sunucu hem de istemci için tam kaynak kodunu kontrol edebilmeniz için her şeyi GitHub'da paketledim.