import React, { useState, useRef, useEffect } from 'react' import './InputArea.css' import sendIcon from '../assets/send-icon.svg' const MAX_IMAGE_DIMENSION = 1600 const JPEG_QUALITY = 0.82 const fileToDataUrl = (file) => new Promise((resolve, reject) => { const reader = new FileReader() reader.onload = () => resolve(String(reader.result || '')) reader.onerror = () => reject(new Error('Impossibile leggere il file selezionato')) reader.readAsDataURL(file) }) const loadImageElement = (file) => new Promise((resolve, reject) => { const image = new Image() const objectUrl = URL.createObjectURL(file) image.onload = () => { URL.revokeObjectURL(objectUrl) resolve(image) } image.onerror = () => { URL.revokeObjectURL(objectUrl) reject(new Error('Impossibile aprire l\'immagine selezionata')) } image.src = objectUrl }) const resizeImageFile = async (file) => { if (!file.type.startsWith('image/')) { return fileToDataUrl(file) } try { const image = await loadImageElement(file) const longestSide = Math.max(image.width, image.height) if (longestSide <= MAX_IMAGE_DIMENSION && file.size <= 1_500_000) { return fileToDataUrl(file) } const scale = MAX_IMAGE_DIMENSION / longestSide const width = Math.max(1, Math.round(image.width * scale)) const height = Math.max(1, Math.round(image.height * scale)) const canvas = document.createElement('canvas') canvas.width = width canvas.height = height const context = canvas.getContext('2d') if (!context) { return fileToDataUrl(file) } context.drawImage(image, 0, 0, width, height) return canvas.toDataURL('image/jpeg', JPEG_QUALITY) } catch (error) { console.error('Errore compressione immagine:', error) return fileToDataUrl(file) } } function InputArea({ onSendMessage, isLoading, allowPhotoUpload = false }) { const [input, setInput] = useState('') const [isListening, setIsListening] = useState(false) const [selectedPhotos, setSelectedPhotos] = useState([]) const [micError, setMicError] = useState('') const recognitionRef = useRef(null) const shouldKeepListeningRef = useRef(false) const baseInputRef = useRef('') const finalTranscriptRef = useRef('') useEffect(() => () => { shouldKeepListeningRef.current = false if (recognitionRef.current) { recognitionRef.current.onstart = null recognitionRef.current.onend = null recognitionRef.current.onresult = null recognitionRef.current.onerror = null try { recognitionRef.current.stop() } catch (error) { // Ignora errori quando il riconoscimento e' gia' fermo. } recognitionRef.current = null } }, []) const SpeechRecognitionAPI = window.SpeechRecognition || window.webkitSpeechRecognition const speechSupported = !!SpeechRecognitionAPI const stopListening = () => { shouldKeepListeningRef.current = false setIsListening(false) if (recognitionRef.current) { try { recognitionRef.current.stop() } catch (error) { // Ignora errori quando il riconoscimento e' gia' fermo. } } } // Crea una nuova istanza ad ogni avvio (necessario su iOS Safari) const startListening = () => { if (!SpeechRecognitionAPI) return setMicError('') shouldKeepListeningRef.current = true baseInputRef.current = input finalTranscriptRef.current = '' const recognition = new SpeechRecognitionAPI() recognitionRef.current = recognition recognition.lang = 'it-IT' recognition.continuous = false recognition.interimResults = true recognition.onstart = () => { setIsListening(true) } recognition.onend = () => { if (shouldKeepListeningRef.current) { try { recognition.start() } catch (error) { setTimeout(() => { if (!shouldKeepListeningRef.current) return try { recognition.start() } catch (restartError) { console.error('Errore riavvio Speech Recognition:', restartError) shouldKeepListeningRef.current = false setIsListening(false) } }, 150) } } else { setIsListening(false) } } recognition.onresult = (event) => { let interimTranscript = '' for (let i = event.resultIndex; i < event.results.length; i++) { const chunk = event.results[i][0].transcript if (event.results[i].isFinal) { finalTranscriptRef.current += chunk } else { interimTranscript += chunk } } setInput(`${baseInputRef.current}${finalTranscriptRef.current}${interimTranscript}`) } recognition.onerror = (event) => { console.error('Errore Speech Recognition:', event.error) if (event.error === 'not-allowed') { shouldKeepListeningRef.current = false setIsListening(false) setMicError('Permesso microfono negato. Abilitalo nelle impostazioni del browser.') } else if (event.error === 'network') { shouldKeepListeningRef.current = false setIsListening(false) setMicError('Errore di rete. Controlla la connessione.') } } recognition.start() } const handleSubmit = (e) => { e.preventDefault() if (isListening) { stopListening() } const text = input.trim() const hasPhotos = selectedPhotos.length > 0 if ((text || hasPhotos) && !isLoading) { onSendMessage({ text, photos: selectedPhotos.map((item) => item.dataUrl) }) setInput('') setSelectedPhotos([]) } } const handlePhotoSelect = async (event) => { const files = Array.from(event.target.files || []).filter((file) => file.type.startsWith('image/')) if (!files.length) return try { const nextPhotos = await Promise.all(files.slice(0, 4).map(async (file) => ({ name: file.name, dataUrl: await resizeImageFile(file) }))) setSelectedPhotos((prev) => { const known = new Set(prev.map((item) => item.dataUrl)) const merged = [...prev] nextPhotos.forEach((item) => { if (!known.has(item.dataUrl)) { known.add(item.dataUrl) merged.push(item) } }) return merged.slice(-6) }) } catch (error) { console.error('Errore selezione foto:', error) } } const removePhoto = (dataUrl) => { setSelectedPhotos((prev) => prev.filter((item) => item.dataUrl !== dataUrl)) } const handleMicClick = () => { if (!speechSupported) { setMicError('Il tuo browser non supporta il riconoscimento vocale.') return } if (isListening) { stopListening() } else { startListening() } } const handleKeyDown = (e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault() handleSubmit(e) } } return (