import React, { useState, useRef, useEffect } from 'react'; import { useNavigate } from 'react-router-dom'; import { Upload, X, ImageIcon, ArrowRight, Sparkles, BrainCircuit, AlertCircle, CheckCircle2, Clock, Mic, MicOff, Loader2, Volume2, Globe, ChevronDown } from 'lucide-react'; import { motion, AnimatePresence } from 'framer-motion'; import { Button } from "../../components/ui/button"; import { Card, CardHeader, CardTitle, CardDescription, CardContent } from "../../components/ui/card"; import { Textarea } from "../../components/ui/textarea"; import Tesseract from 'tesseract.js'; import { translateText, SUPPORTED_LANGUAGES } from '../../services/translationService'; const CreateTicket = () => { const [issue, setIssue] = useState(''); const [file, setFile] = useState(null); const [imagePreview, setImagePreview] = useState(null); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(''); const [extractedOCR, setExtractedOCR] = useState(''); const [isOcrLoading, setIsOcrLoading] = useState(false); const [isListening, setIsListening] = useState(false); const fileInputRef = useRef(null); const navigate = useNavigate(); const MAX_CHARS = 1000; const supportsSpeech = 'SpeechRecognition' in window || 'webkitSpeechRecognition' in window; const [selectedLanguage, setSelectedLanguage] = useState('en'); const [isTranslating, setIsTranslating] = useState(false); const [isLangOpen, setIsLangOpen] = useState(false); const langRef = useRef(null); // Voice UI states const [showVoiceModal, setShowVoiceModal] = useState(false); const [voiceTranscript, setVoiceTranscript] = useState(''); const [interimVoice, setInterimVoice] = useState(''); // Voice Refs & Visualizer const recognitionRef = useRef(null); const audioContextRef = useRef(null); const analyserRef = useRef(null); const dataArrayRef = useRef(null); const animationFrameRef = useRef(null); const [visualizerData, setVisualizerData] = useState(new Array(16).fill(15)); const streamRef = useRef(null); useEffect(() => { return () => { if (recognitionRef.current) recognitionRef.current.stop(); if (audioContextRef.current) audioContextRef.current.close(); if (animationFrameRef.current) cancelAnimationFrame(animationFrameRef.current); }; }, []); // Close language dropdown on outside click useEffect(() => { const handleClickOutside = (event) => { if (langRef.current && !langRef.current.contains(event.target)) { setIsLangOpen(false); } }; document.addEventListener('mousedown', handleClickOutside); return () => document.removeEventListener('mousedown', handleClickOutside); }, []); // Clean up preview URL on unmount useEffect(() => { return () => { if (imagePreview) URL.revokeObjectURL(imagePreview); }; }, [imagePreview]); const processOCR = async (imageFile) => { setIsOcrLoading(true); try { const { data: { text } } = await Tesseract.recognize(imageFile, 'eng'); setExtractedOCR(text.trim()); } catch (err) { console.error("OCR Failed:", err); // Non-fatal, just log it. Backend will still try if this fails. } finally { setIsOcrLoading(false); } }; const toggleMic = () => { if (isListening) { stopListening(); return; } startListening(); }; const startListening = async () => { const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition; if (!SpeechRecognition) { setError("Speech recognition is not supported in this browser."); return; } try { // Start Visualizer for UI feedback const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); streamRef.current = stream; const AudioContext = window.AudioContext || window.webkitAudioContext; audioContextRef.current = new AudioContext(); const source = audioContextRef.current.createMediaStreamSource(stream); const analyser = audioContextRef.current.createAnalyser(); analyser.fftSize = 64; const bufferLength = analyser.frequencyBinCount; const dataArray = new Uint8Array(bufferLength); analyserRef.current = analyser; dataArrayRef.current = dataArray; source.connect(analyser); const updateVisualizer = () => { if (!analyserRef.current) return; analyserRef.current.getByteFrequencyData(dataArrayRef.current); const bars = []; for (let i = 0; i < 16; i++) { const val = dataArrayRef.current[i] || 0; const height = Math.max(5, (val / 255) * 50); bars.push(height); } setVisualizerData(bars); animationFrameRef.current = requestAnimationFrame(updateVisualizer); }; updateVisualizer(); // Initialize Speech Recognition const recognition = new SpeechRecognition(); recognition.continuous = true; recognition.interimResults = true; recognition.lang = 'en-US'; recognition.onresult = (event) => { let finalStr = ''; let interimStr = ''; for (let i = event.resultIndex; i < event.results.length; ++i) { if (event.results[i].isFinal) { finalStr += event.results[i][0].transcript; } else { interimStr += event.results[i][0].transcript; } } if (finalStr) setVoiceTranscript(prev => prev + ' ' + finalStr); setInterimVoice(interimStr); }; recognition.onerror = (event) => { console.error("Speech Recognition Error:", event.error); if (event.error !== 'no-speech') { setError(`Microphone error: ${event.error}`); } }; recognition.onend = () => { // Only stop visualizer if we actually intended to stop if (!isListening) { if (animationFrameRef.current) cancelAnimationFrame(animationFrameRef.current); } }; recognitionRef.current = recognition; recognition.start(); setIsListening(true); setShowVoiceModal(true); setVoiceTranscript(''); setInterimVoice(''); setError(''); } catch (err) { console.error("Microphone access denied:", err); setError("Could not access microphone. Please ensure permissions are granted."); } }; const stopListening = () => { setIsListening(false); if (recognitionRef.current) { recognitionRef.current.stop(); recognitionRef.current = null; } if (animationFrameRef.current) { cancelAnimationFrame(animationFrameRef.current); animationFrameRef.current = null; } if (audioContextRef.current) { audioContextRef.current.close(); audioContextRef.current = null; } if (streamRef.current) { streamRef.current.getTracks().forEach(track => track.stop()); streamRef.current = null; } }; const handleSaveVoice = () => { stopListening(); setIssue(prev => { const combined = prev + ' ' + voiceTranscript + ' ' + interimVoice; return combined.trim().substring(0, MAX_CHARS); }); setShowVoiceModal(false); }; const handleCancelVoice = () => { stopListening(); setShowVoiceModal(false); }; const handleFileChange = (e) => { const selected = e.target.files?.[0]; if (selected && (selected.type === 'image/png' || selected.type === 'image/jpeg')) { if (imagePreview) URL.revokeObjectURL(imagePreview); setFile(selected); setImagePreview(URL.createObjectURL(selected)); setError(''); processOCR(selected); } else if (selected) { setError('Please upload only PNG or JPG images.'); } }; const removeFile = () => { setFile(null); setExtractedOCR(''); if (imagePreview) URL.revokeObjectURL(imagePreview); setImagePreview(null); if (fileInputRef.current) fileInputRef.current.value = ''; }; const handleDragOver = (e) => { e.preventDefault(); e.stopPropagation(); }; const handleDrop = (e) => { e.preventDefault(); e.stopPropagation(); const droppedFile = e.dataTransfer.files?.[0]; if (droppedFile && (droppedFile.type === 'image/png' || droppedFile.type === 'image/jpeg')) { if (imagePreview) URL.revokeObjectURL(imagePreview); setFile(droppedFile); setImagePreview(URL.createObjectURL(droppedFile)); setError(''); processOCR(droppedFile); } }; const handleAnalyze = async (e) => { e.preventDefault(); if (!issue.trim()) { setError('Please describe your issue first.'); return; } if (file && !isOcrLoading && !extractedOCR.trim()) { setError('No text could be extracted from the image. Please upload a clear screenshot containing text, or remove the image to continue.'); return; } setIsLoading(true); setError(''); try { let textToSubmit = issue; // Translate to English if a different language is selected if (selectedLanguage !== 'en') { setIsTranslating(true); textToSubmit = await translateText(issue, selectedLanguage, 'en'); setIsTranslating(false); } let imageBase64 = ""; let extractedOCRText = extractedOCR; if (file) { imageBase64 = await new Promise((resolve) => { const reader = new FileReader(); reader.onloadend = () => { resolve(reader.result); }; reader.readAsDataURL(file); }); } // Navigate to AI Processing workflow where the API will be called navigate('/ai-processing', { state: { text: textToSubmit, original_text: issue, original_language: selectedLanguage, image_base64: imageBase64, image_text: extractedOCRText } }); } catch (err) { console.error(err); setError('Failed to submit ticket. Please try again later.'); } finally { setIsLoading(false); } }; return (
{/* Left Column: User Input */}
Workspace
Report a New Issue Describe the problem and our AI will analyze it instantly.
{/* Description Textarea */}
= MAX_CHARS ? 'text-red-500' : 'text-gray-400'}`}> {issue.length} / {MAX_CHARS}
{/* Premium Language Selector */}
{isLangOpen && (
{SUPPORTED_LANGUAGES.map(lang => ( ))}
)}
{selectedLanguage !== 'en' && ( Translating )}