UNPKG

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
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