├── 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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------