UNPKG

@broadreachalliance/q-assistant

Version:

A React-based chat window supporting both voice and text communication modes.

134 lines 4.81 kB
import { useEffect, useState, useRef } from "react"; import axios from "axios"; import config from "../config"; const useAssistant = () => { const finalTranscriptRef = useRef(""); const speechRecognitionref = useRef(null); const availableVoicesref = useRef([]); const [state, setState] = useState("idle"); // idle | listening | responding | processing const timeoutRef = useRef(null); const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition; useEffect(() => { const loadVoices = () => { const voices = window.speechSynthesis.getVoices(); availableVoicesref.current = voices; }; const initInteractions = () => { const sttSupported = "SpeechRecognition" in window || "webkitSpeechRecognition" in window; const ttsSupported = "speechSynthesis" in window; if (ttsSupported) { window.speechSynthesis.addEventListener("voiceschanged", loadVoices); } if (sttSupported) { speechRecognitionref.current = new SpeechRecognition(); speechRecognitionref.current.continuous = true; speechRecognitionref.current.interimResults = true; speechRecognitionref.current.lang = "en-US"; speechRecognitionref.current.onresult = event => { handleRecognitionResult(event); }; speechRecognitionref.current.onerror = event => { console.error("Speech recognition error", event.error); }; } else { console.error("Speech recognition not supported in this browser."); } return () => { if (speechRecognitionref.current) { speechRecognitionref.current.stop(); } }; }; initInteractions(); return () => { window.speechSynthesis.removeEventListener("voiceschanged", loadVoices); }; }, []); const startListening = () => { if (!localStorage.getItem("token") || !localStorage.getItem(config.USER_DETAIL_STORAGE_KEY) || !process.env.REACT_APP_AI_ASSISTANT_HOST) { speakText("Unable to process your request."); stopListening(); return null; } if (speechRecognitionref.current) { speechRecognitionref.current.start(); setState("listening"); } }; const stopListening = () => { if (speechRecognitionref.current) { speechRecognitionref.current.stop(); setState("idle"); } }; const handleRecognitionResult = event => { for (let i = event.resultIndex; i < event.results.length; i++) { finalTranscriptRef.current = event.results[i][0].transcript; } if (timeoutRef.current) { clearTimeout(timeoutRef.current); } timeoutRef.current = setTimeout(() => { sendMessage(); }, 2000); }; const sendMessage = async () => { const messageToSend = finalTranscriptRef.current; if (!messageToSend.trim()) return; stopListening(); finalTranscriptRef.current = ""; const userData = localStorage.getItem(config.USER_DETAIL_STORAGE_KEY); try { setState("processing"); const response = await axios.post(process.env.REACT_APP_AI_ASSISTANT_HOST, { messages: messageToSend, thread_id: JSON.parse(userData)?.userID ?? "new_user", debug: false }, { headers: { Authorization: `Bearer ${localStorage.getItem("token")}`, "Content-Type": "application/json" } }); let lines = response.data.response; lines = lines.replace(/\s*\(.*?\)\s*/g, " ").trim(); lines = lines.replaceAll("**", " "); lines = lines.replace(/\b([aApP])\.(m|M)\b/g, "$1$2"); speechRecognitionref.current?.stop(); speakText(lines); } catch (error) { console.error("Error:", error); speakText("Sorry, there was an error processing your message."); } }; const speakText = text => { text = text.replace(/\s*\(.*?\)\s*/g, ' ').trim(); if (!window.speechSynthesis) return; window.speechSynthesis.cancel(); const utterance = new SpeechSynthesisUtterance(text); const selectedVoice = localStorage.getItem("selectedVoice"); if (selectedVoice) { utterance.voice = availableVoicesref.current.find(voice => voice.name === selectedVoice); } else { utterance.voice = availableVoicesref.current?.[0]; } utterance.rate = process.env.REACT_APP_SPEECH_RATE; utterance.onstart = () => { setState("responding"); }; utterance.onend = () => { if (localStorage.getItem("token") && localStorage.getItem(config.USER_DETAIL_STORAGE_KEY) && process.env.REACT_APP_AI_ASSISTANT_HOST) { startListening(); } else { setState("idle"); } }; window.speechSynthesis.speak(utterance); }; return { startListening, stopListening, state }; }; export default useAssistant;