├── README.md ├── .babelrc ├── .eslintrc.json ├── public ├── favicon.ico └── vercel.svg ├── next.config.js ├── pages ├── _app.js └── index.js ├── package.json ├── .gitignore ├── styles ├── globals.css └── Home.module.css └── components └── SpeechToText.jsx /README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["next/babel"], 3 | "plugins": [] 4 | } 5 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["next/babel", "next/core-web-vitals"] 3 | } 4 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bonarhyme/audio-to-pdf-web-client/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | reactStrictMode: true, 4 | } 5 | 6 | module.exports = nextConfig 7 | -------------------------------------------------------------------------------- /pages/_app.js: -------------------------------------------------------------------------------- 1 | import "regenerator-runtime/runtime"; 2 | import "../styles/globals.css"; 3 | 4 | function MyApp({ Component, pageProps }) { 5 | return ; 6 | } 7 | 8 | export default MyApp; 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "audio-to-pdf", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "axios": "^0.27.2", 13 | "next": "12.1.6", 14 | "react": "^18.1.0", 15 | "react-dom": "18.1.0", 16 | "react-speech-recognition": "^3.9.1", 17 | "regenerator-runtime": "^0.13.9" 18 | }, 19 | "devDependencies": { 20 | "eslint": "8.16.0", 21 | "eslint-config-next": "12.1.6" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /pages/index.js: -------------------------------------------------------------------------------- 1 | import Head from "next/head"; 2 | import SpeechToText from "../components/SpeechToText"; 3 | 4 | export default function Home() { 5 | return ( 6 |
7 | 8 | Audio To PDF 9 | 13 | 14 | 15 | 16 |

Convert your speech to pdf

17 | 18 |
19 | 20 |
21 |
22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | .pnpm-debug.log* 27 | 28 | # local env files 29 | .env.local 30 | .env.development.local 31 | .env.test.local 32 | .env.production.local 33 | 34 | # vercel 35 | .vercel 36 | 37 | blog.md 38 | blog.v2.md 39 | proposal.md 40 | -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /styles/globals.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | padding: 0; 4 | margin: 0; 5 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, 6 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; 7 | } 8 | 9 | a { 10 | color: inherit; 11 | text-decoration: none; 12 | } 13 | 14 | * { 15 | box-sizing: border-box; 16 | } 17 | 18 | .home { 19 | background-color: #333; 20 | min-height: 100%; 21 | padding: 0 1rem; 22 | padding-bottom: 3rem; 23 | } 24 | 25 | h1 { 26 | width: 100%; 27 | max-width: 400px; 28 | margin: auto; 29 | padding: 2rem 0; 30 | text-align: center; 31 | text-transform: capitalize; 32 | color: white; 33 | font-size: 1rem; 34 | } 35 | .button-container { 36 | text-align: center; 37 | display: flex; 38 | justify-content: center; 39 | gap: 3rem; 40 | } 41 | 42 | button { 43 | color: white; 44 | background-color: var(--bgColor); 45 | font-size: 1.2rem; 46 | padding: 0.5rem 1.5rem; 47 | border: none; 48 | border-radius: 20px; 49 | cursor: pointer; 50 | } 51 | 52 | button:hover { 53 | opacity: 0.9; 54 | } 55 | 56 | button:active { 57 | transform: scale(0.99); 58 | } 59 | 60 | .words { 61 | max-width: 700px; 62 | margin: 50px auto; 63 | height: 50vh; 64 | background: white; 65 | border-radius: 5px; 66 | padding: 1rem 2rem 1rem 5rem; 67 | background: -webkit-gradient( 68 | linear, 69 | 0 0, 70 | 0 100%, 71 | from(#d9eaf3), 72 | color-stop(4%, #fff) 73 | ) 74 | 0 4px; 75 | background-size: 100% 3rem; 76 | background-attachment: scroll; 77 | position: relative; 78 | line-height: 3rem; 79 | overflow-y: auto; 80 | } 81 | 82 | .success, 83 | .error { 84 | background-color: white; 85 | margin: 1rem auto; 86 | padding: 0.5rem 1rem; 87 | border-radius: 5px; 88 | width: max-content; 89 | text-align: center; 90 | display: block; 91 | } 92 | 93 | .success { 94 | color: green; 95 | } 96 | 97 | .error { 98 | color: red; 99 | } 100 | -------------------------------------------------------------------------------- /styles/Home.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | padding: 0 2rem; 3 | } 4 | 5 | .main { 6 | min-height: 100vh; 7 | padding: 4rem 0; 8 | flex: 1; 9 | display: flex; 10 | flex-direction: column; 11 | justify-content: center; 12 | align-items: center; 13 | } 14 | 15 | .footer { 16 | display: flex; 17 | flex: 1; 18 | padding: 2rem 0; 19 | border-top: 1px solid #eaeaea; 20 | justify-content: center; 21 | align-items: center; 22 | } 23 | 24 | .footer a { 25 | display: flex; 26 | justify-content: center; 27 | align-items: center; 28 | flex-grow: 1; 29 | } 30 | 31 | .title a { 32 | color: #0070f3; 33 | text-decoration: none; 34 | } 35 | 36 | .title a:hover, 37 | .title a:focus, 38 | .title a:active { 39 | text-decoration: underline; 40 | } 41 | 42 | .title { 43 | margin: 0; 44 | line-height: 1.15; 45 | font-size: 4rem; 46 | } 47 | 48 | .title, 49 | .description { 50 | text-align: center; 51 | } 52 | 53 | .description { 54 | margin: 4rem 0; 55 | line-height: 1.5; 56 | font-size: 1.5rem; 57 | } 58 | 59 | .code { 60 | background: #fafafa; 61 | border-radius: 5px; 62 | padding: 0.75rem; 63 | font-size: 1.1rem; 64 | font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, 65 | Bitstream Vera Sans Mono, Courier New, monospace; 66 | } 67 | 68 | .grid { 69 | display: flex; 70 | align-items: center; 71 | justify-content: center; 72 | flex-wrap: wrap; 73 | max-width: 800px; 74 | } 75 | 76 | .card { 77 | margin: 1rem; 78 | padding: 1.5rem; 79 | text-align: left; 80 | color: inherit; 81 | text-decoration: none; 82 | border: 1px solid #eaeaea; 83 | border-radius: 10px; 84 | transition: color 0.15s ease, border-color 0.15s ease; 85 | max-width: 300px; 86 | } 87 | 88 | .card:hover, 89 | .card:focus, 90 | .card:active { 91 | color: #0070f3; 92 | border-color: #0070f3; 93 | } 94 | 95 | .card h2 { 96 | margin: 0 0 1rem 0; 97 | font-size: 1.5rem; 98 | } 99 | 100 | .card p { 101 | margin: 0; 102 | font-size: 1.25rem; 103 | line-height: 1.5; 104 | } 105 | 106 | .logo { 107 | height: 1em; 108 | margin-left: 0.5rem; 109 | } 110 | 111 | @media (max-width: 600px) { 112 | .grid { 113 | width: 100%; 114 | flex-direction: column; 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /components/SpeechToText.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useRef, useState } from "react"; 2 | import SpeechRecognition, { 3 | useSpeechRecognition, 4 | } from "react-speech-recognition"; 5 | import axios from "axios"; 6 | 7 | const SpeechToText = () => { 8 | const speechRecognitionSupported = 9 | SpeechRecognition.browserSupportsSpeechRecognition(); 10 | 11 | const [isSupported, setIsSupported] = useState(null); 12 | const { transcript, resetTranscript } = useSpeechRecognition(); 13 | const [listening, setListening] = useState(false); 14 | const [response, setResponse] = useState({ 15 | loading: false, 16 | message: "", 17 | error: false, 18 | success: false, 19 | }); 20 | const textBodyRef = useRef(null); 21 | 22 | const startListening = () => { 23 | setListening(true); 24 | SpeechRecognition.startListening({ 25 | continuous: true, 26 | }); 27 | }; 28 | 29 | const stopListening = () => { 30 | setListening(false); 31 | SpeechRecognition.stopListening(); 32 | }; 33 | 34 | const resetText = () => { 35 | stopListening(); 36 | resetTranscript(); 37 | textBodyRef.current.innerText = ""; 38 | }; 39 | 40 | const handleConversion = async () => { 41 | if (typeof window !== "undefined") { 42 | const userText = textBodyRef.current.innerText; 43 | // console.log(textBodyRef.current.innerText); 44 | 45 | if (!userText) { 46 | alert("Please speak or write some text."); 47 | return; 48 | } 49 | 50 | try { 51 | setResponse({ 52 | ...response, 53 | loading: true, 54 | message: "", 55 | error: false, 56 | success: false, 57 | }); 58 | const config = { 59 | headers: { 60 | "Content-Type": "application/json", 61 | }, 62 | responseType: "blob", 63 | }; 64 | 65 | const res = await axios.post( 66 | "http://localhost:4000", 67 | { 68 | text: textBodyRef.current.innerText, 69 | }, 70 | config 71 | ); 72 | setResponse({ 73 | ...response, 74 | loading: false, 75 | error: false, 76 | message: 77 | "Conversion was successful. Your download will start soon...", 78 | success: true, 79 | }); 80 | 81 | const url = window.URL.createObjectURL(new Blob([res.data])); 82 | const link = document.createElement("a"); 83 | link.href = url; 84 | link.setAttribute("download", "yourfile.pdf"); 85 | document.body.appendChild(link); 86 | link.click(); 87 | 88 | console.log(res); 89 | } catch (error) { 90 | setResponse({ 91 | ...response, 92 | loading: false, 93 | error: true, 94 | message: 95 | "An unexpected error occured. Text not converted. Please try again", 96 | success: false, 97 | }); 98 | } 99 | } 100 | }; 101 | 102 | useEffect(() => { 103 | setIsSupported(speechRecognitionSupported); 104 | }, []); 105 | 106 | if (!isSupported) { 107 | return
Your browser does not support speech recognition.
; 108 | } 109 | 110 | return ( 111 | <> 112 |
113 |
114 | 122 | 130 |
131 |
137 | {transcript} 138 |
139 |
140 | {response.success && {response.message}} 141 | {response.error && {response.message}} 142 |
143 |
144 | 151 | 158 |
159 |
160 | 161 | ); 162 | }; 163 | 164 | export default SpeechToText; 165 | --------------------------------------------------------------------------------