Преобразование речи в PDF с помощью NextJS и ExpressJS PlatoBlockchain Data Intelligence. Вертикальный поиск. Ай.

Преобразование речи в 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 при использовании распознавания речи
  3. html-pdf-узел: библиотека для преобразования HTML-страницы или общедоступного URL-адреса в PDF-файл.
  4. Вардар: библиотека для выполнения HTTP-запросов как в браузере, так и в Node.js.
  5. рожки: библиотека, позволяющая совместно использовать ресурсы из разных источников.

Настройка

Первое, что мы хотим сделать, это создать две папки проекта, одну для клиента и одну для сервера. Назовите их как хотите. я называю свою audio-to-pdf-client и audio-to-pdf-server, Соответственно.

Самый быстрый способ начать работу с Next.js на стороне клиента — загрузить его с помощью создать следующее приложение. Итак, откройте терминал и выполните следующую команду из папки вашего клиентского проекта:

npx create-next-app client

Теперь нам нужен наш экспресс-сервер. Мы можем получить его 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="/ru/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;

Этот компонент возвращает Фрагмент реакции который содержит 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);

Нам также нужен 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();

И мы можем использовать следующее ниже contentEditable div для отображения сообщений:

<div>
  {response.success && <i className="success">{response.message}</i>}
  {response.error && <i className="error">{response.message}</i>}
</div>

Окончательный код

Я упаковал все на GitHub, чтобы вы могли проверить полный исходный код как для сервера, так и для клиента.

Отметка времени:

Больше от CSS хитрости