import React, { useState, useRef, useEffect } from 'react'; import { ChatMessage, TriviaQuestion } from '../types'; import { createStudyChat, generateTrivia, analyzeImage } from '../services/geminiService'; import { StorageService } from '../services/storageService'; import { Button } from '../components/ui/Button'; import { Send, Bot, Loader2, X, Mic, RefreshCw, Image as ImageIcon, MessageSquare, Trophy, Volume2, VolumeX, Eye } from 'lucide-react'; import { useLanguage } from '../contexts/LanguageContext'; import confetti from 'canvas-confetti'; // --- MESSAGE PARSER FOR DUAL LANGUAGE --- const parseMessage = (rawText: string) => { try { const json = JSON.parse(rawText); if (json.en || json.ne) return json; return { en: rawText, ne: rawText, type: 'text' }; } catch { return { en: rawText, ne: rawText, type: 'text' }; } }; const StudyBuddy: React.FC = () => { const { language } = useLanguage(); // 'en' or 'ne' // -- STATE -- const [activeTab, setActiveTab] = useState<'chat' | 'voice' | 'trivia'>('chat'); const [messages, setMessages] = useState([]); const [input, setInput] = useState(''); const [isTyping, setIsTyping] = useState(false); // Image Analysis State const [selectedImage, setSelectedImage] = useState(null); // Trivia Game State const [triviaState, setTriviaState] = useState<'LOBBY' | 'LANG_SELECT' | 'PLAYING' | 'GAMEOVER'>('LOBBY'); const [selectedCategory, setSelectedCategory] = useState(''); const [triviaLang, setTriviaLang] = useState<'en' | 'ne'>('en'); const [questions, setQuestions] = useState([]); const [currentQIndex, setCurrentQIndex] = useState(0); const [score, setScore] = useState(0); const [streak, setStreak] = useState(0); const [triviaLoading, setTriviaLoading] = useState(false); const [selectedAnswer, setSelectedAnswer] = useState(null); const [isCorrect, setIsCorrect] = useState(null); const [soundEnabled, setSoundEnabled] = useState(true); // Refs const chatEndRef = useRef(null); const chatSessionRef = useRef(null); // -- INITIALIZATION -- useEffect(() => { // Load history if exists const history = StorageService.getStudyChatHistory(); if (history.length > 0) { setMessages(history); } // Initialize Chat Session if (!chatSessionRef.current) { chatSessionRef.current = createStudyChat(); } }, []); useEffect(() => { StorageService.saveStudyChatHistory(messages); chatEndRef.current?.scrollIntoView({ behavior: 'smooth' }); }, [messages]); // -- SOUND EFFECTS -- const playSound = (type: 'correct' | 'wrong' | 'click' | 'win') => { if (!soundEnabled) return; try { const AudioContext = window.AudioContext || (window as any).webkitAudioContext; if (!AudioContext) return; const ctx = new AudioContext(); const osc = ctx.createOscillator(); const gain = ctx.createGain(); osc.connect(gain); gain.connect(ctx.destination); const now = ctx.currentTime; if (type === 'correct') { osc.frequency.setValueAtTime(600, now); osc.frequency.exponentialRampToValueAtTime(1000, now + 0.1); gain.gain.setValueAtTime(0.1, now); gain.gain.exponentialRampToValueAtTime(0.01, now + 0.3); osc.start(); osc.stop(now + 0.3); } else if (type === 'wrong') { osc.type = 'sawtooth'; osc.frequency.setValueAtTime(150, now); osc.frequency.linearRampToValueAtTime(100, now + 0.3); gain.gain.setValueAtTime(0.1, now); gain.gain.exponentialRampToValueAtTime(0.01, now + 0.3); osc.start(); osc.stop(now + 0.3); } else if (type === 'click') { osc.frequency.setValueAtTime(800, now); gain.gain.setValueAtTime(0.05, now); gain.gain.exponentialRampToValueAtTime(0.01, now + 0.05); osc.start(); osc.stop(now + 0.05); } else if (type === 'win') { osc.type = 'triangle'; osc.frequency.setValueAtTime(400, now); osc.frequency.linearRampToValueAtTime(800, now + 0.2); osc.frequency.linearRampToValueAtTime(600, now + 0.4); gain.gain.setValueAtTime(0.1, now); gain.gain.linearRampToValueAtTime(0, now + 0.6); osc.start(); osc.stop(now + 0.6); } } catch(e) {} }; // -- HANDLERS -- const handleImageUpload = (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (file) { const reader = new FileReader(); reader.onloadend = () => { setSelectedImage(reader.result as string); }; reader.readAsDataURL(file); } }; const handleSend = async (e?: React.FormEvent, overrideText?: string) => { if (e) e.preventDefault(); const textToSend = overrideText || input; // Allow empty text if image is selected (visual query) if ((!textToSend.trim() && !selectedImage) || isTyping) return; // Display optimistic user message const userMsg: ChatMessage = { id: Date.now().toString(), role: 'user', text: JSON.stringify({ en: textToSend, ne: textToSend, type: 'text' }), timestamp: Date.now(), image: selectedImage || undefined // Store image in history if present }; setMessages(prev => [...prev, userMsg]); setInput(''); const tempImage = selectedImage; // Local capture setSelectedImage(null); // Clear input immediately setIsTyping(true); try { let responseText = "{}"; if (tempImage) { // Multimodal Analysis request via specialized function // The prompt defaults to "Analyze this image" if text is empty const prompt = textToSend.trim() || "What is in this image? Analyze it in detail."; responseText = await analyzeImage(tempImage, prompt); } else if (chatSessionRef.current) { // Standard Text Chat const result = await chatSessionRef.current.sendMessage({ message: textToSend }); responseText = result.text || "{}"; } const botMsg: ChatMessage = { id: (Date.now() + 1).toString(), role: 'model', text: responseText, timestamp: Date.now() }; setMessages(prev => [...prev, botMsg]); } catch (error) { console.error(error); setMessages(prev => [...prev, { id: Date.now().toString(), role: 'model', text: JSON.stringify({ en: "Connection interrupted.", ne: "सम्पर्क विच्छेद भयो।", type: "text" }), timestamp: Date.now() }]); } finally { setIsTyping(false); } }; const handleClearChat = () => { if(confirm("Clear chat history?")) { setMessages([]); StorageService.clearStudyChatHistory(); chatSessionRef.current = createStudyChat(); } }; // -- TRIVIA LOGIC -- const handleCategorySelect = (category: string) => { setSelectedCategory(category); setTriviaState('LANG_SELECT'); }; const startTrivia = async (lang: 'en' | 'ne') => { setTriviaLang(lang); setTriviaLoading(true); playSound('click'); try { const qs = await generateTrivia(selectedCategory, lang); setQuestions(qs); setScore(0); setStreak(0); setCurrentQIndex(0); setTriviaState('PLAYING'); } catch (e) { alert("Failed to generate trivia. Check connection."); setTriviaState('LOBBY'); } finally { setTriviaLoading(false); } }; const handleTriviaAnswer = (index: number) => { if (selectedAnswer !== null) return; // Prevent double click setSelectedAnswer(index); const correct = index === questions[currentQIndex].correctAnswer; setIsCorrect(correct); if (correct) { playSound('correct'); setScore(prev => prev + 10 + (streak * 2)); setStreak(prev => prev + 1); confetti({ particleCount: 50, spread: 60, origin: { y: 0.7 }, colors: ['#4ade80', '#ffffff'] }); } else { playSound('wrong'); setStreak(0); } setTimeout(() => { if (currentQIndex < questions.length - 1) { setCurrentQIndex(prev => prev + 1); setSelectedAnswer(null); setIsCorrect(null); } else { playSound('win'); setTriviaState('GAMEOVER'); // Save Karma StorageService.addPoints(score, score * 2, 'trivia', 'Rudra Trivia Reward'); } }, 1500); }; return (
{/* --- HEADER --- */}

Rudra AI

{language === 'ne' ? 'नेपाली मोड' : 'English Mode'}
{/* Compact Tab Switcher */}
{[ { id: 'chat', icon: MessageSquare }, { id: 'trivia', icon: Trophy }, { id: 'voice', icon: Mic }, ].map(tab => ( ))}
{/* --- MAIN CONTENT --- */}
{/* CHAT INTERFACE */} {activeTab === 'chat' && (
{messages.length === 0 && (

Start a conversation or Scan an image

)} {messages.map((msg) => { const content = parseMessage(msg.text); const displayText = language === 'ne' ? (content.ne || content.en) : content.en; const isUser = msg.role === 'user'; return (
{msg.image && (
User Upload
)}
{displayText}
{/* Show Newari if available and relevant */} {content.newa && !isUser && (
Newari: {content.newa}
)}
); })} {isTyping && (
)}
{/* Image Preview Overlay */} {selectedImage && (
Preview Image Selected
)} {/* Input Area */}
handleSend(e)} className="flex items-end gap-2 bg-gray-100 dark:bg-gray-800 p-1.5 rounded-[1.5rem]"> setInput(e.target.value)} placeholder="Type a message or describe the image..." className="flex-1 bg-transparent border-none outline-none text-sm py-2.5 px-2 text-gray-900 dark:text-white placeholder-gray-500" />
)} {/* TRIVIA SECTION */} {activeTab === 'trivia' && (
{triviaState === 'LOBBY' && (

Nepal Trivia

Test your knowledge. Earn Karma.

{['History', 'Geography', 'Culture', 'Nature'].map(cat => ( ))}
)} {triviaState === 'LANG_SELECT' && (

Select Language

Which language should we use?

{triviaLoading ? (

Generating Quiz...

) : (
)}
)} {triviaState === 'PLAYING' && questions.length > 0 && (
{/* Progress Bar */}
Question {currentQIndex + 1} of {questions.length}

{questions[currentQIndex].question}

{questions[currentQIndex].options.map((opt, idx) => { let btnClass = "bg-white dark:bg-gray-800 border-gray-100 dark:border-gray-700 text-gray-700 dark:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-700"; if (selectedAnswer !== null) { if (idx === questions[currentQIndex].correctAnswer) btnClass = "bg-green-500 border-green-600 text-white"; else if (idx === selectedAnswer) btnClass = "bg-red-500 border-red-600 text-white animate-shake"; else btnClass = "opacity-50 bg-gray-100 dark:bg-gray-800"; } return ( ); })}
Score: {score} {streak > 1 && 🔥 {streak} Streak!}
)} {triviaState === 'GAMEOVER' && (

Quiz Complete!

Knowledge verified.

Total Score

{score}

+ {score} Karma Earned

)}
)} {/* VOICE VIEW (Simple Placeholder to direct to main button) */} {activeTab === 'voice' && (

Voice Command

Use the global Rudra button (bottom right) for real-time voice conversations.

)}
); }; export default StudyBuddy;