Перетворення мовлення в PDF за допомогою NextJS і ExpressJS PlatoBlockchain Data Intelligence. Вертикальний пошук. Ai.

Перетворення мовлення в PDF за допомогою NextJS і ExpressJS

З огляду на те, що мовні інтерфейси стають все більш поширеними речами, варто вивчити деякі речі, які ми можемо робити за допомогою мовної взаємодії. Наприклад, що, якби ми могли щось сказати і мати це транскрибовано та викачати як PDF-файл, який можна завантажити?

Що ж, спойлер: точно так може зробити це! Є бібліотеки та фреймворки, які ми можемо об’єднати, щоб це сталося, і це те, що ми збираємося зробити разом у цій статті.

Це інструменти, які ми використовуємо

По-перше, це два великих гравці: Next.js і Express.js.

Next.js надає додаткові функції для React, включаючи ключові функції для створення статичних сайтів. Завдяки таким функціям, як динамічна маршрутизація, оптимізація зображень, вбудована маршрутизація доменів і субдоменів, швидке оновлення, маршрутизація файлової системи та маршрути API… багато, багато іншого.

У нашому випадку для нього нам точно потрібен Next.js Маршрути API на нашому клієнтському сервері. Нам потрібен маршрут, який бере текстовий файл, перетворює його на PDF, записує в нашу файлову систему, а потім надсилає відповідь клієнту.

Express.js дозволяє нам створити маленьку програму Node.js із маршрутизацією, помічниками HTTP та шаблонами. Це сервер для нашого власного API, який нам знадобиться, коли ми передаємо та аналізуємо дані між речами.

У нас є деякі інші залежності, які ми будемо використовувати:

  1. реагувати-розпізнавати мову: бібліотека для перетворення мови в текст, що робить її доступною для компонентів React.
  2. регенератор-час виконання: бібліотека для усунення несправностей "regeneratorRuntime не визначено», яка з’являється в Next.js під час використання функції розпізнавання мови react-speech
  3. html-pdf-вузол: бібліотека для перетворення сторінки HTML або загальнодоступної URL-адреси в PDF
  4. аксіоси: бібліотека для виконання HTTP-запитів як у браузері, так і в Node.js
  5. кор: бібліотека, яка дозволяє обмінюватися ресурсами між джерелами

Налаштовуючи

Перше, що ми хочемо зробити, це створити дві папки проекту, одну для клієнта та одну для сервера. Назвіть їх як завгодно. Я називаю своє audio-to-pdf-client та audio-to-pdf-server, відповідно.

Найшвидший спосіб почати роботу з Next.js на стороні клієнта — завантажити його за допомогою create-next-app. Отже, відкрийте свій термінал і виконайте таку команду з папки клієнтського проекту:

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="/uk/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

Побудова інтерфейсу користувача

Що ж, наш 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 відповідно.

Зовсім інша справа — стиль. Я не буду вдаватися в це тут, але ви можете використовувати деякі стилі, написані мною, як відправну точку для власного 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);

Нам також потрібен a 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. Кнопка «Зупинити» має атрибут disabled, який буде активовано, якщо прослуховування буде помилковим.

Якщо ми перезапустимо сервер і оновимо браузер, тепер ми зможемо запускати, зупиняти та скидати нашу транскрипцію мовлення в браузері.

Тепер нам потрібно, щоб додаток працював перекладати що розпізнане мовлення, перетворивши його на файл 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.

Ви можете застосувати будь-яку укладку на свій смак.

Далі ми додамо a trycatch для обробки будь-яких помилок, які можуть виникнути на шляху:

try {

} catch(error){
  console.log(error);
  res.status(500).send(error);
}

Далі ми будемо використовувати generatePdf від html-pdf-node бібліотека для створення a 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 функції.

Обробка перетворення

Перш ніж ми зможемо почати працювати над a 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 в a 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, щоб ви могли перевірити повний вихідний код як для сервера, так і для клієнта.

Часова мітка:

Більше від CSS-хитрощі