Spaces:
Configuration error
Configuration error
| import React, { useState, useEffect, useRef } from 'react'; | |
| interface ChatInputProps { | |
| onSendMessage: (text: string) => void; | |
| isLoading: boolean; | |
| } | |
| // Check for SpeechRecognition API | |
| // FIX: Cast window to `any` to access non-standard SpeechRecognition APIs without TypeScript errors. | |
| const SpeechRecognition = (window as any).SpeechRecognition || (window as any).webkitSpeechRecognition; | |
| const recognition = SpeechRecognition ? new SpeechRecognition() : null; | |
| if (recognition) { | |
| recognition.continuous = false; | |
| recognition.lang = 'en-US'; | |
| recognition.interimResults = false; | |
| } | |
| const ChatInput: React.FC<ChatInputProps> = ({ onSendMessage, isLoading }) => { | |
| const [text, setText] = useState(''); | |
| const [isListening, setIsListening] = useState(false); | |
| const recognitionRef = useRef(recognition); | |
| useEffect(() => { | |
| const currentRecognition = recognitionRef.current; | |
| if (!currentRecognition) return; | |
| // FIX: Use `any` for the event type as SpeechRecognitionEvent is not defined in this project's TS config. | |
| const handleResult = (event: any) => { | |
| const transcript = event.results[0][0].transcript; | |
| setText(transcript); | |
| handleSend(); // Auto-send after transcription | |
| }; | |
| const handleEnd = () => { | |
| setIsListening(false); | |
| }; | |
| // FIX: Use `any` for the event type as SpeechRecognitionError (or the correct SpeechRecognitionErrorEvent) is not defined. | |
| const handleError = (event: any) => { | |
| console.error('Speech recognition error', event.error); | |
| setIsListening(false); | |
| } | |
| currentRecognition.addEventListener('result', handleResult); | |
| currentRecognition.addEventListener('end', handleEnd); | |
| currentRecognition.addEventListener('error', handleError); | |
| return () => { | |
| currentRecognition.removeEventListener('result', handleResult); | |
| currentRecognition.removeEventListener('end', handleEnd); | |
| currentRecognition.removeEventListener('error', handleError); | |
| }; | |
| // eslint-disable-next-line react-hooks/exhaustive-deps | |
| }, []); | |
| const handleSend = () => { | |
| if (text.trim() && !isLoading) { | |
| onSendMessage(text); | |
| setText(''); | |
| } | |
| }; | |
| const handleKeyPress = (e: React.KeyboardEvent<HTMLInputElement>) => { | |
| if (e.key === 'Enter') { | |
| handleSend(); | |
| } | |
| }; | |
| const toggleListening = () => { | |
| if (!recognitionRef.current) return; | |
| if (isListening) { | |
| recognitionRef.current.stop(); | |
| } else { | |
| recognitionRef.current.start(); | |
| } | |
| setIsListening(!isListening); | |
| }; | |
| return ( | |
| <footer className="bg-white border-t border-gray-200 p-4"> | |
| <div className="max-w-4xl mx-auto flex items-center space-x-3"> | |
| {recognition && ( | |
| <button | |
| onClick={toggleListening} | |
| disabled={isLoading} | |
| className={`flex-shrink-0 w-10 h-10 rounded-full flex items-center justify-center transition-colors duration-200 ${ | |
| isListening ? 'bg-red-500 text-white' : 'bg-gray-200 text-gray-600 hover:bg-gray-300' | |
| } disabled:opacity-50 disabled:cursor-not-allowed`} | |
| > | |
| <svg xmlns="http://www.w3.org/2000/svg" className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor"> | |
| <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 11a7 7 0 01-7 7m0 0a7 7 0 01-7-7m7 7v4m0 0H8m4 0h4m-4-8a3 3 0 01-3-3V5a3 3 0 116 0v6a3 3 0 01-3 3z" /> | |
| </svg> | |
| </button> | |
| )} | |
| <input | |
| type="text" | |
| value={text} | |
| onChange={(e) => setText(e.target.value)} | |
| onKeyPress={handleKeyPress} | |
| placeholder="Type your medical question..." | |
| className="w-full p-3 border border-gray-300 rounded-full focus:outline-none focus:ring-2 focus:ring-blue-500 transition-shadow" | |
| disabled={isLoading} | |
| /> | |
| <button | |
| onClick={handleSend} | |
| disabled={isLoading || !text.trim()} | |
| className="bg-blue-500 text-white rounded-full p-3 flex-shrink-0 hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 disabled:bg-blue-300 disabled:cursor-not-allowed transition-colors" | |
| > | |
| <svg xmlns="http://www.w3.org/2000/svg" className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor"> | |
| <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 10l7-7m0 0l7 7m-7-7v18" /> | |
| </svg> | |
| </button> | |
| </div> | |
| </footer> | |
| ); | |
| }; | |
| export default ChatInput; |