تحويل الكلام إلى PDF باستخدام NextJS و ExpressJS PlatoBlockchain Data Intelligence. البحث العمودي. عاي.

تحويل الكلام إلى PDF باستخدام NextJS و ExpressJS

نظرًا لأن واجهات الكلام أصبحت شيئًا أكثر أهمية ، فمن الجدير استكشاف بعض الأشياء التي يمكننا القيام بها من خلال تفاعلات الكلام. مثل ، ماذا لو تمكنا من قول شيء ما وقمنا بنسخه وضخه كملف PDF قابل للتنزيل؟

حسنًا ، تنبيه المفسد: نحن بالتأكيد يمكن إفعل ذلك! توجد مكتبات وأطر عمل يمكننا تجميعها معًا لتحقيق ذلك ، وهذا ما سنفعله معًا في هذه المقالة.

هذه هي الأدوات التي نستخدمها

أولاً ، هذان اللاعبان الكبيران: Next.js و Express.js.

Next.js يتعامل مع وظائف إضافية لـ React ، بما في ذلك الميزات الأساسية لبناء مواقع ثابتة. إنه خيار للعديد من المطورين نظرًا لما يقدمه فورًا ، مثل التوجيه الديناميكي ، وتحسين الصور ، وتوجيه المجال الداخلي والمجال الفرعي ، والتحديثات السريعة ، وتوجيه نظام الملفات ، ومسارات واجهة برمجة التطبيقات ... من بين أشياء أخرى كثيرة.

في حالتنا ، نحتاج بالتأكيد إلى Next.js من أجل طرق API على خادم العميل الخاص بنا. نريد مسارًا يأخذ ملفًا نصيًا ، ويحوله إلى PDF ، ويكتبه إلى نظام الملفات الخاص بنا ، ثم يرسل ردًا إلى العميل.

Express.js يسمح لنا بالحصول على القليل من تطبيق Node.js مع التوجيه ومساعدات HTTP والقوالب. إنه خادم لواجهة برمجة التطبيقات الخاصة بنا ، وهو ما سنحتاجه أثناء تمرير البيانات وتحليلها بين الأشياء.

لدينا بعض التبعيات الأخرى التي سنستخدمها:

  1. رد فعل التعرف على الكلام: مكتبة لتحويل الكلام إلى نص ، وإتاحته لمكونات React.
  2. وقت تشغيل المجدد: مكتبة لاستكشاف أخطاء "regeneratorRuntime لم يتم تعريفه "الخطأ الذي يظهر في Next.js عند استخدام التعرف على التفاعل والكلام
  3. html-pdf-عقدة: مكتبة لتحويل صفحة HTML أو عنوان URL عام إلى ملف PDF
  4. أكسيوس: مكتبة لإجراء طلبات HTTP في كل من المتصفح و Node.js
  5. كر: مكتبة تسمح بمشاركة الموارد عبر الأصل

اعداد

أول شيء نريد القيام به هو إنشاء مجلدين للمشروع ، أحدهما للعميل والآخر للخادم. اسم لهم ما تريد. أنا أسمي لي 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:

تحويل الكلام إلى PDF باستخدام NextJS و ExpressJS

أول خطاب لنا لتحويل النص!

الإجراء الأول الذي يجب اتخاذه هو استيراد التبعيات الضرورية إلى ملف <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 حتى تتمكن من التحقق من شفرة المصدر الكاملة لكل من الخادم والعميل.

الطابع الزمني:

اكثر من الخدع المغلق