275 lines
8.5 KiB
JavaScript
275 lines
8.5 KiB
JavaScript
import React, { useState, useEffect, useRef, forwardRef, useImperativeHandle } from "react";
|
|
import { FaMicrophone, FaMicrophoneSlash } from "react-icons/fa";
|
|
import './VoiceToText.scss'
|
|
import { Capacitor } from "@capacitor/core";
|
|
import { SpeechRecognition } from "@capacitor-community/speech-recognition";
|
|
|
|
const VoiceToTextItemCard = forwardRef(({ setVoiceData, setprodQty, uomNames = [] }, ref) => {
|
|
const [formData, setFormData] = useState({ product: "", qty: "", uom: "" });
|
|
const [transcript, setTranscript] = useState("");
|
|
const [isListening, setIsListening] = useState(false);
|
|
const recognitionRef = useRef(null);
|
|
|
|
const numberWords = {
|
|
one: "1", two: "2", three: "3", four: "4", five: "5",
|
|
six: "6", seven: "7", eight: "8", nine: "9", ten: "10"
|
|
};
|
|
|
|
const listening = () => {
|
|
console.log('Voice listening started');
|
|
// your voice start logic here
|
|
if (isListening) {
|
|
stopListening();
|
|
} else {
|
|
startListening();
|
|
}
|
|
};
|
|
useImperativeHandle(ref, () => ({
|
|
listening,
|
|
}));
|
|
|
|
|
|
const extractEntities = (text) => {
|
|
if (typeof text !== "string") return { product: "", qty: "", uom: "" };
|
|
|
|
const lowerUomNames = (uomNames?.length ? uomNames : [
|
|
"kg", "kgs", "gram", "grams", "litre", "litres", "pack", "packs", "piece", "pieces"
|
|
]).map(u => u?.toLowerCase());
|
|
|
|
const words = text?.toLowerCase().split(/\s+/);
|
|
const numberWords = {
|
|
one: "1", two: "2", three: "3", four: "4", five: "5",
|
|
six: "6", seven: "7", eight: "8", nine: "9", ten: "10"
|
|
};
|
|
|
|
let qty = "";
|
|
let uom = "";
|
|
const productWords = [];
|
|
|
|
for (let i = 0; i < words.length; i++) {
|
|
const word = words[i];
|
|
const numVal = numberWords[word] || (isNaN(word) ? null : word);
|
|
|
|
if (lowerUomNames.includes(word)) {
|
|
uom = word;
|
|
} else if (numVal) {
|
|
qty = numVal;
|
|
} else {
|
|
productWords.push(word);
|
|
}
|
|
}
|
|
|
|
const cleanProduct = productWords
|
|
.filter(w => w !== qty && w !== uom)
|
|
.join(" ")
|
|
.trim();
|
|
|
|
return {
|
|
product: cleanProduct,
|
|
qty,
|
|
uom
|
|
};
|
|
};
|
|
|
|
|
|
useEffect(() => {
|
|
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
|
|
|
|
if (!SpeechRecognition) {
|
|
alert("Speech Recognition not supported");
|
|
return;
|
|
}
|
|
|
|
const recognition = new SpeechRecognition();
|
|
recognition.lang = "en-US";
|
|
recognition.continuous = false;
|
|
|
|
recognition.onresult = (event) => {
|
|
const spokenText = event.results[0][0].transcript;
|
|
setTranscript(spokenText);
|
|
|
|
const { product, qty, uom } = extractEntities(spokenText);
|
|
setFormData({ product, qty, uom });
|
|
|
|
// Send to parent component
|
|
if (setVoiceData) setVoiceData({ product, qty, uom });
|
|
if (setprodQty) setprodQty(qty); // optional
|
|
};
|
|
|
|
recognition.onerror = (err) => console.error("Speech recognition error:", err);
|
|
recognition.onend = () => {
|
|
setIsListening(false);
|
|
};
|
|
recognitionRef.current = recognition;
|
|
}, [setVoiceData, setprodQty, uomNames]);
|
|
|
|
// const startListening = () => {
|
|
// recognitionRef.current?.start();
|
|
// setIsListening(true);
|
|
// setTranscript("");
|
|
// setVoiceData && setVoiceData("");
|
|
// };
|
|
//sree coment line
|
|
// const startListening = async () => {
|
|
// try {
|
|
// // Step 1: Check microphone access
|
|
// const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
|
// stream.getTracks().forEach(track => track.stop()); // stop test mic stream
|
|
|
|
// // Step 2: Try starting speech recognition
|
|
// try {
|
|
// recognitionRef.current?.start();
|
|
// setIsListening(true);
|
|
// setTranscript("");
|
|
// if (setVoiceData) setVoiceData("");
|
|
// } catch (err) {
|
|
// console.error("SpeechRecognition start error:", err);
|
|
// alert("Unable to start speech recognition. Please check mic access or try again.");
|
|
// setIsListening(false);
|
|
// }
|
|
|
|
// } catch (error) {
|
|
// console.error("Microphone access denied or not available", error);
|
|
// alert("Microphone not connected or permission denied.");
|
|
// setIsListening(false);
|
|
// }
|
|
// };
|
|
|
|
const startListening = async () => {
|
|
|
|
// Detect platform
|
|
const platform = Capacitor.getPlatform();
|
|
|
|
if (platform === "web") {
|
|
try {
|
|
|
|
// Check microphone permission
|
|
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
|
stream.getTracks().forEach(track => track.stop());
|
|
|
|
recognitionRef.current?.start();
|
|
setIsListening(true);
|
|
setTranscript("");
|
|
|
|
if (setVoiceData) setVoiceData("");
|
|
|
|
} catch (error) {
|
|
|
|
console.error("Microphone access denied or not available", error);
|
|
alert("Microphone not connected or permission denied.");
|
|
setIsListening(false);
|
|
}
|
|
}
|
|
else {
|
|
// 📱 Capacitor Android / iOS
|
|
try {
|
|
|
|
const permission = await SpeechRecognition.requestPermissions();
|
|
|
|
if (!permission.speechRecognition) {
|
|
alert("Speech recognition permission denied");
|
|
return;
|
|
}
|
|
|
|
setIsListening(true);
|
|
|
|
const result = await SpeechRecognition.start({
|
|
language: "en-US",
|
|
maxResults: 1,
|
|
partialResults: false,
|
|
});
|
|
|
|
if (result.matches && result.matches.length > 0) {
|
|
|
|
const spokenText = result.matches[0];
|
|
|
|
setTranscript(spokenText);
|
|
|
|
const { product, qty, uom } = extractEntities(spokenText);
|
|
|
|
setFormData({ product, qty, uom });
|
|
|
|
if (setVoiceData) setVoiceData({ product, qty, uom });
|
|
if (setprodQty) setprodQty(qty);
|
|
}
|
|
|
|
setIsListening(false);
|
|
|
|
} catch (err) {
|
|
console.error(err);
|
|
setIsListening(false);
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
// const stopListening = () => {
|
|
// if (recognitionRef.current) {
|
|
// console.log("Stopping voice recognition...");
|
|
// recognitionRef.current.stop();
|
|
// } else {
|
|
// console.warn("Recognition not initialized");
|
|
// }
|
|
|
|
// setIsListening(false);
|
|
// };
|
|
|
|
const stopListening = async () => {
|
|
const platform = Capacitor.getPlatform();
|
|
|
|
try {
|
|
if (platform === "web") {
|
|
if (recognitionRef.current) {
|
|
recognitionRef.current.stop();
|
|
console.log("Stopping voice recognition...");
|
|
}
|
|
} else {
|
|
await SpeechRecognition.stop();
|
|
console.log("Stopping native speech recognition...");
|
|
}
|
|
} catch (error) {
|
|
console.error("Error stopping speech recognition:", error);
|
|
}
|
|
|
|
setIsListening(false);
|
|
};
|
|
|
|
return (
|
|
<div style={{ display: 'flex', alignItems: 'center', gap: '5px' }}>
|
|
<button>
|
|
{isListening ? <FaMicrophone /> : <FaMicrophoneSlash />}
|
|
</button>
|
|
|
|
<div style={{ display: 'flex', flexDirection: 'column-reverse', alignItems: 'center', gap: '5px' }}>
|
|
{isListening && (
|
|
<div style={{ display: "flex", gap: "5px" }}>
|
|
<div style={dotStyle(0)}></div>
|
|
<div style={dotStyle(0.2)}></div>
|
|
<div style={dotStyle(0.4)}></div>
|
|
</div>
|
|
)}
|
|
{!isListening && <div>Voice</div>}
|
|
</div>
|
|
{/* <div style={{ marginTop: "20px" }}>
|
|
<strong>Transcript:</strong> {transcript}
|
|
<br />
|
|
<strong>Product:</strong> {formData.product} <br />
|
|
<strong>Qty:</strong> {formData.qty} <br />
|
|
<strong>UOM:</strong> {formData.uom}
|
|
</div> */}
|
|
</div>
|
|
);
|
|
});
|
|
|
|
export default VoiceToTextItemCard;
|
|
|
|
// Style for dots (voice loader)
|
|
const dotStyle = (delay = 0) => ({
|
|
width: "8px",
|
|
height: "8px",
|
|
borderRadius: "50%",
|
|
background: "#ff1744",
|
|
animation: "pulse-color 1s infinite ease-in-out",
|
|
animationDelay: `${delay}s`,
|
|
});
|