react-speech-recognition-ui
Version:
A beautiful, production-ready voice transcription package for React applications using the Web Speech API
547 lines (545 loc) • 23 kB
JavaScript
import { useState as S, useRef as K, useCallback as N, useEffect as q, forwardRef as W, createElement as O } from "react";
import { jsx as e, jsxs as t, Fragment as P } from "react/jsx-runtime";
const J = (i = {}) => {
const {
continuous: c = !0,
interimResults: l = !0,
language: s = "en-US",
onResult: n,
onError: u,
onStart: a,
onEnd: d
} = i, [h, m] = S(""), [r, b] = S(""), [g, M] = S(!1), [R, y] = S(null), [L, U] = S(0), p = K(null), f = "SpeechRecognition" in window || "webkitSpeechRecognition" in window, $ = N(() => {
if (!f) return null;
const v = window.SpeechRecognition || window.webkitSpeechRecognition, w = new v();
return w.continuous = c, w.interimResults = l, w.lang = s, w.onstart = () => {
M(!0), y(null), a == null || a();
}, w.onresult = (j) => {
let k = "", I = "";
for (let T = j.resultIndex; T < j.results.length; T++) {
const A = j.results[T], V = A[0].transcript;
A.isFinal ? (k += V, U(A[0].confidence)) : I += V;
}
k && (m((T) => T + k), n == null || n({
transcript: k,
confidence: L,
isFinal: !0,
timestamp: Date.now()
})), b(I);
}, w.onerror = (j) => {
const k = Z(j.error);
y(k), M(!1), u == null || u(k);
}, w.onend = () => {
M(!1), b(""), d == null || d();
}, w;
}, [c, l, s, n, u, a, d, f]), D = N(() => {
if (!f) {
y("Speech recognition is not supported in this browser");
return;
}
if (p.current && p.current.stop(), p.current = $(), p.current)
try {
p.current.start();
} catch {
y("Failed to start speech recognition");
}
}, [f, $]), o = N(() => {
p.current && (p.current.stop(), p.current = null);
}, []), C = N(() => {
o(), m(""), b(""), y(null), U(0);
}, [o]);
return q(() => () => {
p.current && p.current.stop();
}, []), {
transcript: h,
interimTranscript: r,
isListening: g,
isSupported: f,
error: R,
confidence: L,
start: D,
stop: o,
reset: C
};
}, Z = (i) => {
switch (i) {
case "no-speech":
return "No speech was detected. Please try again.";
case "audio-capture":
return "Audio capture failed. Please check your microphone.";
case "not-allowed":
return "Microphone access denied. Please grant permission.";
case "network":
return "Network error occurred. Please check your connection.";
default:
return "An error occurred during speech recognition.";
}
};
/**
* @license lucide-react v0.344.0 - ISC
*
* This source code is licensed under the ISC license.
* See the LICENSE file in the root directory of this source tree.
*/
var _ = {
xmlns: "http://www.w3.org/2000/svg",
width: 24,
height: 24,
viewBox: "0 0 24 24",
fill: "none",
stroke: "currentColor",
strokeWidth: 2,
strokeLinecap: "round",
strokeLinejoin: "round"
};
/**
* @license lucide-react v0.344.0 - ISC
*
* This source code is licensed under the ISC license.
* See the LICENSE file in the root directory of this source tree.
*/
const Q = (i) => i.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase().trim(), x = (i, c) => {
const l = W(
({
color: s = "currentColor",
size: n = 24,
strokeWidth: u = 2,
absoluteStrokeWidth: a,
className: d = "",
children: h,
...m
}, r) => O(
"svg",
{
ref: r,
..._,
width: n,
height: n,
stroke: s,
strokeWidth: a ? Number(u) * 24 / Number(n) : u,
className: ["lucide", `lucide-${Q(i)}`, d].join(" "),
...m
},
[
...c.map(([b, g]) => O(b, g)),
...Array.isArray(h) ? h : [h]
]
)
);
return l.displayName = `${i}`, l;
};
/**
* @license lucide-react v0.344.0 - ISC
*
* This source code is licensed under the ISC license.
* See the LICENSE file in the root directory of this source tree.
*/
const F = x("Clock", [
["circle", { cx: "12", cy: "12", r: "10", key: "1mglay" }],
["polyline", { points: "12 6 12 12 16 14", key: "68esgv" }]
]);
/**
* @license lucide-react v0.344.0 - ISC
*
* This source code is licensed under the ISC license.
* See the LICENSE file in the root directory of this source tree.
*/
const X = x("Copy", [
["rect", { width: "14", height: "14", x: "8", y: "8", rx: "2", ry: "2", key: "17jyea" }],
["path", { d: "M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2", key: "zix9uf" }]
]);
/**
* @license lucide-react v0.344.0 - ISC
*
* This source code is licensed under the ISC license.
* See the LICENSE file in the root directory of this source tree.
*/
const H = x("Download", [
["path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4", key: "ih7n3h" }],
["polyline", { points: "7 10 12 15 17 10", key: "2ggqvy" }],
["line", { x1: "12", x2: "12", y1: "15", y2: "3", key: "1vk2je" }]
]);
/**
* @license lucide-react v0.344.0 - ISC
*
* This source code is licensed under the ISC license.
* See the LICENSE file in the root directory of this source tree.
*/
const Y = x("Globe", [
["circle", { cx: "12", cy: "12", r: "10", key: "1mglay" }],
["path", { d: "M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20", key: "13o1zl" }],
["path", { d: "M2 12h20", key: "9i4pu4" }]
]);
/**
* @license lucide-react v0.344.0 - ISC
*
* This source code is licensed under the ISC license.
* See the LICENSE file in the root directory of this source tree.
*/
const B = x("MicOff", [
["line", { x1: "2", x2: "22", y1: "2", y2: "22", key: "a6p6uj" }],
["path", { d: "M18.89 13.23A7.12 7.12 0 0 0 19 12v-2", key: "80xlxr" }],
["path", { d: "M5 10v2a7 7 0 0 0 12 5", key: "p2k8kg" }],
["path", { d: "M15 9.34V5a3 3 0 0 0-5.68-1.33", key: "1gzdoj" }],
["path", { d: "M9 9v3a3 3 0 0 0 5.12 2.12", key: "r2i35w" }],
["line", { x1: "12", x2: "12", y1: "19", y2: "22", key: "x3vr5v" }]
]);
/**
* @license lucide-react v0.344.0 - ISC
*
* This source code is licensed under the ISC license.
* See the LICENSE file in the root directory of this source tree.
*/
const E = x("Mic", [
["path", { d: "M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3Z", key: "131961" }],
["path", { d: "M19 10v2a7 7 0 0 1-14 0v-2", key: "1vc78b" }],
["line", { x1: "12", x2: "12", y1: "19", y2: "22", key: "x3vr5v" }]
]);
/**
* @license lucide-react v0.344.0 - ISC
*
* This source code is licensed under the ISC license.
* See the LICENSE file in the root directory of this source tree.
*/
const ee = x("RotateCcw", [
["path", { d: "M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8", key: "1357e3" }],
["path", { d: "M3 3v5h5", key: "1xhq8a" }]
]);
/**
* @license lucide-react v0.344.0 - ISC
*
* This source code is licensed under the ISC license.
* See the LICENSE file in the root directory of this source tree.
*/
const te = x("Square", [
["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2", key: "afitv7" }]
]);
/**
* @license lucide-react v0.344.0 - ISC
*
* This source code is licensed under the ISC license.
* See the LICENSE file in the root directory of this source tree.
*/
const z = x("Trash2", [
["path", { d: "M3 6h18", key: "d0wm0j" }],
["path", { d: "M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6", key: "4alrt4" }],
["path", { d: "M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2", key: "v07s0e" }],
["line", { x1: "10", x2: "10", y1: "11", y2: "17", key: "1uufr5" }],
["line", { x1: "14", x2: "14", y1: "11", y2: "17", key: "xtxkd" }]
]);
/**
* @license lucide-react v0.344.0 - ISC
*
* This source code is licensed under the ISC license.
* See the LICENSE file in the root directory of this source tree.
*/
const re = x("Volume2", [
["polygon", { points: "11 5 6 9 2 9 2 15 6 15 11 19 11 5", key: "16drj5" }],
["path", { d: "M15.54 8.46a5 5 0 0 1 0 7.07", key: "ltjumu" }],
["path", { d: "M19.07 4.93a10 10 0 0 1 0 14.14", key: "1kegas" }]
]), ie = ({
onTranscriptChange: i,
onResult: c,
className: l = "",
language: s = "en-US",
continuous: n = !0,
interimResults: u = !0,
maxHeight: a = "400px"
}) => {
const [d, h] = S([]);
S(!1);
const m = N((o) => {
h((C) => [...C, o]), c == null || c(o);
}, [c]), {
transcript: r,
interimTranscript: b,
isListening: g,
isSupported: M,
error: R,
confidence: y,
start: L,
stop: U,
reset: p
} = J({
continuous: n,
interimResults: u,
language: s,
onResult: m
// onTranscriptChange,
}), f = N(() => {
navigator.clipboard.writeText(r);
}, [r]), $ = N(() => {
const o = new Blob([r], { type: "text/plain" }), C = URL.createObjectURL(o), v = document.createElement("a");
v.href = C, v.download = `transcription-${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}.txt`, document.body.appendChild(v), v.click(), document.body.removeChild(v), URL.revokeObjectURL(C);
}, [r]), D = N(() => {
if ("speechSynthesis" in window && r) {
const o = new SpeechSynthesisUtterance(r);
o.lang = s, speechSynthesis.speak(o);
}
}, [r, s]);
return M ? /* @__PURE__ */ t("div", { className: `bg-white border border-gray-200 rounded-xl shadow-lg overflow-hidden ${l}`, children: [
/* @__PURE__ */ e("div", { className: "bg-gradient-to-r from-blue-600 to-purple-600 p-6", children: /* @__PURE__ */ t("div", { className: "flex items-center justify-between", children: [
/* @__PURE__ */ t("div", { className: "flex items-center space-x-3", children: [
/* @__PURE__ */ e("div", { className: `p-2 rounded-full ${g ? "bg-red-500" : "bg-white/20"}`, children: g ? /* @__PURE__ */ e(E, { className: "h-6 w-6 text-white animate-pulse" }) : /* @__PURE__ */ e(B, { className: "h-6 w-6 text-white" }) }),
/* @__PURE__ */ t("div", { children: [
/* @__PURE__ */ e("h2", { className: "text-xl font-bold text-white", children: "Voice Transcriber" }),
/* @__PURE__ */ e("p", { className: "text-blue-100 text-sm", children: g ? "Listening..." : "Ready to transcribe" })
] })
] }),
/* @__PURE__ */ e("div", { className: "flex items-center space-x-2", children: y > 0 && /* @__PURE__ */ t("div", { className: "text-white text-sm", children: [
"Confidence: ",
Math.round(y * 100),
"%"
] }) })
] }) }),
/* @__PURE__ */ e("div", { className: "p-4 bg-gray-50 border-b border-gray-200", children: /* @__PURE__ */ t("div", { className: "flex items-center justify-between", children: [
/* @__PURE__ */ t("div", { className: "flex items-center space-x-2", children: [
/* @__PURE__ */ e(
"button",
{
onClick: g ? U : L,
disabled: !!R,
className: `flex items-center space-x-2 px-4 py-2 rounded-lg font-medium transition-all duration-200 ${g ? "bg-red-500 hover:bg-red-600 text-white" : "bg-blue-500 hover:bg-blue-600 text-white"} disabled:opacity-50 disabled:cursor-not-allowed`,
children: g ? /* @__PURE__ */ t(P, { children: [
/* @__PURE__ */ e(te, { className: "h-4 w-4" }),
/* @__PURE__ */ e("span", { children: "Stop" })
] }) : /* @__PURE__ */ t(P, { children: [
/* @__PURE__ */ e(E, { className: "h-4 w-4" }),
/* @__PURE__ */ e("span", { children: "Start" })
] })
}
),
/* @__PURE__ */ t(
"button",
{
onClick: p,
className: "flex items-center space-x-2 px-4 py-2 bg-gray-500 hover:bg-gray-600 text-white rounded-lg font-medium transition-all duration-200",
children: [
/* @__PURE__ */ e(ee, { className: "h-4 w-4" }),
/* @__PURE__ */ e("span", { children: "Reset" })
]
}
)
] }),
/* @__PURE__ */ t("div", { className: "flex items-center space-x-2", children: [
/* @__PURE__ */ e(
"button",
{
onClick: f,
disabled: !r,
className: "p-2 bg-gray-200 hover:bg-gray-300 rounded-lg transition-colors duration-200 disabled:opacity-50 disabled:cursor-not-allowed",
title: "Copy transcript",
children: /* @__PURE__ */ e(X, { className: "h-4 w-4 text-gray-600" })
}
),
/* @__PURE__ */ e(
"button",
{
onClick: $,
disabled: !r,
className: "p-2 bg-gray-200 hover:bg-gray-300 rounded-lg transition-colors duration-200 disabled:opacity-50 disabled:cursor-not-allowed",
title: "Download transcript",
children: /* @__PURE__ */ e(H, { className: "h-4 w-4 text-gray-600" })
}
),
/* @__PURE__ */ e(
"button",
{
onClick: D,
disabled: !r,
className: "p-2 bg-gray-200 hover:bg-gray-300 rounded-lg transition-colors duration-200 disabled:opacity-50 disabled:cursor-not-allowed",
title: "Speak transcript",
children: /* @__PURE__ */ e(re, { className: "h-4 w-4 text-gray-600" })
}
)
] })
] }) }),
R && /* @__PURE__ */ e("div", { className: "p-4 bg-red-50 border-b border-red-200", children: /* @__PURE__ */ t("div", { className: "flex items-center space-x-2", children: [
/* @__PURE__ */ e("div", { className: "h-2 w-2 bg-red-500 rounded-full" }),
/* @__PURE__ */ e("span", { className: "text-red-700 font-medium", children: R })
] }) }),
/* @__PURE__ */ t("div", { className: "p-6", children: [
/* @__PURE__ */ e(
"div",
{
className: "min-h-[200px] p-4 bg-gray-50 rounded-lg border border-gray-200 overflow-y-auto",
style: { maxHeight: a },
children: r || b ? /* @__PURE__ */ t("div", { className: "space-y-2", children: [
r && /* @__PURE__ */ e("div", { className: "text-gray-900 leading-relaxed", children: r }),
b && /* @__PURE__ */ e("div", { className: "text-gray-500 italic", children: b })
] }) : /* @__PURE__ */ e("div", { className: "flex items-center justify-center h-full text-gray-500", children: /* @__PURE__ */ t("div", { className: "text-center", children: [
/* @__PURE__ */ e(E, { className: "h-12 w-12 mx-auto mb-2 text-gray-400" }),
/* @__PURE__ */ e("p", { children: 'Click "Start" to begin transcription' })
] }) })
}
),
r && /* @__PURE__ */ t("div", { className: "mt-4 flex items-center justify-between text-sm text-gray-600", children: [
/* @__PURE__ */ t("div", { children: [
"Words: ",
r.split(" ").filter((o) => o.length > 0).length
] }),
/* @__PURE__ */ t("div", { children: [
"Characters: ",
r.length
] }),
/* @__PURE__ */ t("div", { children: [
"Results: ",
d.length
] })
] })
] })
] }) : /* @__PURE__ */ e("div", { className: `p-6 bg-red-50 border border-red-200 rounded-xl ${l}`, children: /* @__PURE__ */ t("div", { className: "flex items-center space-x-3", children: [
/* @__PURE__ */ e(B, { className: "h-6 w-6 text-red-500" }),
/* @__PURE__ */ t("div", { children: [
/* @__PURE__ */ e("h3", { className: "text-lg font-semibold text-red-800", children: "Not Supported" }),
/* @__PURE__ */ e("p", { className: "text-red-600", children: "Speech recognition is not supported in this browser." })
] })
] }) });
}, se = ({
history: i,
onDelete: c,
onClear: l,
className: s = ""
}) => {
const n = () => {
const a = i.map((r) => `[${new Date(r.timestamp).toLocaleString()}] ${r.transcript}`).join(`
`), d = new Blob([a], { type: "text/plain" }), h = URL.createObjectURL(d), m = document.createElement("a");
m.href = h, m.download = `transcription-history-${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}.txt`, document.body.appendChild(m), m.click(), document.body.removeChild(m), URL.revokeObjectURL(h);
}, u = (a) => {
const d = Math.floor(a / 60), h = Math.floor(a % 60);
return `${d}:${h.toString().padStart(2, "0")}`;
};
return /* @__PURE__ */ t("div", { className: `bg-white border border-gray-200 rounded-xl shadow-lg overflow-hidden ${s}`, children: [
/* @__PURE__ */ e("div", { className: "bg-gradient-to-r from-green-600 to-blue-600 p-4", children: /* @__PURE__ */ t("div", { className: "flex items-center justify-between", children: [
/* @__PURE__ */ t("div", { className: "flex items-center space-x-3", children: [
/* @__PURE__ */ e(F, { className: "h-6 w-6 text-white" }),
/* @__PURE__ */ t("div", { children: [
/* @__PURE__ */ e("h2", { className: "text-lg font-bold text-white", children: "Transcription History" }),
/* @__PURE__ */ t("p", { className: "text-green-100 text-sm", children: [
i.length,
" recordings"
] })
] })
] }),
/* @__PURE__ */ t("div", { className: "flex items-center space-x-2", children: [
/* @__PURE__ */ t(
"button",
{
onClick: n,
disabled: i.length === 0,
className: "flex items-center space-x-2 px-3 py-1 bg-white/20 hover:bg-white/30 rounded-lg text-white text-sm font-medium transition-colors duration-200 disabled:opacity-50 disabled:cursor-not-allowed",
children: [
/* @__PURE__ */ e(H, { className: "h-4 w-4" }),
/* @__PURE__ */ e("span", { children: "Download All" })
]
}
),
/* @__PURE__ */ t(
"button",
{
onClick: l,
disabled: i.length === 0,
className: "flex items-center space-x-2 px-3 py-1 bg-red-500/20 hover:bg-red-500/30 rounded-lg text-white text-sm font-medium transition-colors duration-200 disabled:opacity-50 disabled:cursor-not-allowed",
children: [
/* @__PURE__ */ e(z, { className: "h-4 w-4" }),
/* @__PURE__ */ e("span", { children: "Clear All" })
]
}
)
] })
] }) }),
/* @__PURE__ */ e("div", { className: "p-4", children: i.length === 0 ? /* @__PURE__ */ t("div", { className: "text-center py-8 text-gray-500", children: [
/* @__PURE__ */ e(F, { className: "h-12 w-12 mx-auto mb-2 text-gray-400" }),
/* @__PURE__ */ e("p", { children: "No transcription history yet" })
] }) : /* @__PURE__ */ e("div", { className: "space-y-3 max-h-96 overflow-y-auto", children: i.map((a) => /* @__PURE__ */ e(
"div",
{
className: "p-4 bg-gray-50 border border-gray-200 rounded-lg hover:bg-gray-100 transition-colors duration-200",
children: /* @__PURE__ */ t("div", { className: "flex items-start justify-between", children: [
/* @__PURE__ */ t("div", { className: "flex-1", children: [
/* @__PURE__ */ t("div", { className: "flex items-center space-x-3 mb-2", children: [
/* @__PURE__ */ e("span", { className: "text-sm text-gray-500", children: new Date(a.timestamp).toLocaleString() }),
/* @__PURE__ */ e("span", { className: "text-sm text-gray-500", children: u(a.duration) }),
/* @__PURE__ */ t("span", { className: "text-sm text-gray-500", children: [
Math.round(a.confidence * 100),
"% confidence"
] })
] }),
/* @__PURE__ */ e("p", { className: "text-gray-900 text-sm leading-relaxed", children: a.transcript })
] }),
/* @__PURE__ */ e(
"button",
{
onClick: () => c(a.id),
className: "ml-4 p-1 text-gray-400 hover:text-red-500 transition-colors duration-200",
children: /* @__PURE__ */ e(z, { className: "h-4 w-4" })
}
)
] })
},
a.id
)) }) })
] });
}, G = [
{ code: "en-US", name: "English (US)", nativeName: "English (US)" },
{ code: "en-GB", name: "English (UK)", nativeName: "English (UK)" },
{ code: "es-ES", name: "Spanish", nativeName: "Español" },
{ code: "fr-FR", name: "French", nativeName: "Français" },
{ code: "de-DE", name: "German", nativeName: "Deutsch" },
{ code: "it-IT", name: "Italian", nativeName: "Italiano" },
{ code: "pt-BR", name: "Portuguese (Brazil)", nativeName: "Português (Brasil)" },
{ code: "ru-RU", name: "Russian", nativeName: "Русский" },
{ code: "ja-JP", name: "Japanese", nativeName: "日本語" },
{ code: "ko-KR", name: "Korean", nativeName: "한국어" },
{ code: "zh-CN", name: "Chinese (Simplified)", nativeName: "中文 (简体)" },
{ code: "zh-TW", name: "Chinese (Traditional)", nativeName: "中文 (繁體)" },
{ code: "ar-SA", name: "Arabic", nativeName: "العربية" },
{ code: "hi-IN", name: "Hindi", nativeName: "हिन्दी" }
], ce = ({
selectedLanguage: i,
onLanguageChange: c,
className: l = ""
}) => {
const s = G.find((n) => n.code === i);
return /* @__PURE__ */ t("div", { className: `bg-white border border-gray-200 rounded-xl shadow-lg overflow-hidden ${l}`, children: [
/* @__PURE__ */ e("div", { className: "bg-gradient-to-r from-purple-600 to-pink-600 p-4", children: /* @__PURE__ */ t("div", { className: "flex items-center space-x-3", children: [
/* @__PURE__ */ e(Y, { className: "h-6 w-6 text-white" }),
/* @__PURE__ */ t("div", { children: [
/* @__PURE__ */ e("h2", { className: "text-lg font-bold text-white", children: "Language Settings" }),
/* @__PURE__ */ t("p", { className: "text-purple-100 text-sm", children: [
"Currently: ",
(s == null ? void 0 : s.nativeName) || "English (US)"
] })
] })
] }) }),
/* @__PURE__ */ t("div", { className: "p-4", children: [
/* @__PURE__ */ e("label", { htmlFor: "language-select", className: "block text-sm font-medium text-gray-700 mb-2", children: "Select Language" }),
/* @__PURE__ */ e(
"select",
{
id: "language-select",
value: i,
onChange: (n) => c(n.target.value),
className: "w-full p-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent bg-white text-gray-900",
children: G.map((n) => /* @__PURE__ */ t("option", { value: n.code, children: [
n.nativeName,
" (",
n.name,
")"
] }, n.code))
}
),
/* @__PURE__ */ e("div", { className: "mt-4 p-3 bg-blue-50 border border-blue-200 rounded-lg", children: /* @__PURE__ */ t("p", { className: "text-sm text-blue-800", children: [
/* @__PURE__ */ e("strong", { children: "Note:" }),
" Language support depends on your browser and operating system. Some languages may not be available or may have limited accuracy."
] }) })
] })
] });
};
export {
ce as LanguageSelector,
se as TranscriptionHistory,
ie as VoiceTranscriber,
J as useVoiceTranscription
};
//# sourceMappingURL=index.esm.js.map