نظرًا لأن واجهات الكلام أصبحت شيئًا أكثر أهمية ، فمن الجدير استكشاف بعض الأشياء التي يمكننا القيام بها من خلال تفاعلات الكلام. مثل ، ماذا لو تمكنا من قول شيء ما وقمنا بنسخه وضخه كملف PDF قابل للتنزيل؟
حسنًا ، تنبيه المفسد: نحن بالتأكيد يمكن إفعل ذلك! توجد مكتبات وأطر عمل يمكننا تجميعها معًا لتحقيق ذلك ، وهذا ما سنفعله معًا في هذه المقالة.
هذه هي الأدوات التي نستخدمها
أولاً ، هذان اللاعبان الكبيران: Next.js و Express.js.
Next.js يتعامل مع وظائف إضافية لـ React ، بما في ذلك الميزات الأساسية لبناء مواقع ثابتة. إنه خيار للعديد من المطورين نظرًا لما يقدمه فورًا ، مثل التوجيه الديناميكي ، وتحسين الصور ، وتوجيه المجال الداخلي والمجال الفرعي ، والتحديثات السريعة ، وتوجيه نظام الملفات ، ومسارات واجهة برمجة التطبيقات ... من بين أشياء أخرى كثيرة.
في حالتنا ، نحتاج بالتأكيد إلى Next.js من أجل طرق API على خادم العميل الخاص بنا. نريد مسارًا يأخذ ملفًا نصيًا ، ويحوله إلى PDF ، ويكتبه إلى نظام الملفات الخاص بنا ، ثم يرسل ردًا إلى العميل.
Express.js يسمح لنا بالحصول على القليل من تطبيق Node.js مع التوجيه ومساعدات HTTP والقوالب. إنه خادم لواجهة برمجة التطبيقات الخاصة بنا ، وهو ما سنحتاجه أثناء تمرير البيانات وتحليلها بين الأشياء.
لدينا بعض التبعيات الأخرى التي سنستخدمها:
- رد فعل التعرف على الكلام: مكتبة لتحويل الكلام إلى نص ، وإتاحته لمكونات React.
- وقت تشغيل المجدد: مكتبة لاستكشاف أخطاء "
regeneratorRuntime
لم يتم تعريفه "الخطأ الذي يظهر في Next.js عند استخدام التعرف على التفاعل والكلام - html-pdf-عقدة: مكتبة لتحويل صفحة HTML أو عنوان URL عام إلى ملف PDF
- أكسيوس: مكتبة لإجراء طلبات HTTP في كل من المتصفح و Node.js
- كر: مكتبة تسمح بمشاركة الموارد عبر الأصل
اعداد
أول شيء نريد القيام به هو إنشاء مجلدين للمشروع ، أحدهما للعميل والآخر للخادم. اسم لهم ما تريد. أنا أسمي لي audio-to-pdf-client
و audio-to-pdf-server
، على التوالي.
أسرع طريقة لبدء استخدام Next.js من جانب العميل هي تشغيله باستخدام إنشاء التطبيق التالي. لذلك ، افتح Terminal وقم بتشغيل الأمر التالي من مجلد مشروع العميل الخاص بك:
npx create-next-app client
الآن نحن بحاجة إلى خادمنا السريع. يمكننا الحصول عليها cd
-ing في مجلد مشروع الخادم وتشغيل ملف 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="/ar/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
تبعيات خادمنا السريع هي التالية ، لذلك دعونا 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
بناء واجهة المستخدم
حسنًا ، لن يكون تحويل الكلام إلى 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;
يقوم هذا المكون بإرجاع ملف رد فعل جزء الذي يحتوي على HTML <``section``>
عنصر يحتوي على ثلاثة divs:
.button-container
يحتوي على زرين سيتم استخدامهما لبدء وإيقاف التعرف على الكلام..words
لديهاcontentEditable
وsuppressContentEditableWarning
سمات لجعل هذا العنصر قابلاً للتعديل وإلغاء أي تحذيرات من React.- آخر
.button-container
يحتوي على زرين آخرين سيتم استخدامهما لإعادة تعيين الكلام وتحويله إلى PDF ، على التوالي.
التصميم شيء آخر تمامًا. لن أخوض في الأمر هنا ، لكن يمكنك استخدام بعض الأنماط التي كتبتها إما كنقطة انطلاق لأسلوبك الخاص 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
يقبل الكائن قيمة لتعيين حجم الورق ونمطه. تتبع أحجام الورق نظامًا مختلفًا كثيرًا عن وحدات التحجيم التي نستخدمها عادةً على الويب. فمثلا، A4 هو الحجم النموذجي للرسالة.
• 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");
يخبر المتصفح بجعل البيانات المستلمة قابلة للتنزيل.
نظرًا لأن مسار واجهة برمجة التطبيقات جاهز ، يمكننا استخدامه في تطبيقنا على http://localhost:4000
. يمكننا المتابعة إلى جزء العميل من طلبنا لإكمال handleConversion
وظيفة.
التعامل مع التحويل
قبل أن نبدأ العمل على ملف handleConversion
وظيفة ، نحتاج إلى إنشاء حالة تتعامل مع طلبات واجهة برمجة التطبيقات الخاصة بنا للتحميل والخطأ والنجاح والرسائل الأخرى. سنقوم باستخدام React's 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();
ويمكننا استخدام ما يلي أدناه contentEditable div
لعرض الرسائل:
<div>
{response.success && <i className="success">{response.message}</i>}
{response.error && <i className="error">{response.message}</i>}
</div>
الكود النهائي
لقد قمت بتجميع كل شيء على GitHub حتى تتمكن من التحقق من شفرة المصدر الكاملة لكل من الخادم والعميل.