Untitled

Anonymous
plain_text
05/07/2026 7:56 AM
60.1 KB
10
Indexable
'use client';

import React, { useState, useRef, useEffect, useCallback } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { Button } from '@/components/ui/button';
import { Textarea } from '@/components/ui/textarea';
import { ScrollArea } from '@/components/ui/scroll-area';
import { Card } from '@/components/ui/card';
import { Badge } from '@/components/ui/badge';
import {
  Send,
  Trash2,
  Sparkles,
  ChevronDown,
  Loader2,
  Sun,
  Moon,
  Info,
  LogOut,
  User,
  Mail,
  Lock,
  Eye,
  EyeOff,
  Camera,
  Baby,
  UserCog,
  Mic,
  MicOff,
  Volume2,
  VolumeX,
} from 'lucide-react';

/* ============ Speech API Types ============ */
interface SpeechRecognitionEvent {
  results: SpeechRecognitionResultList;
  resultIndex: number;
}

interface SpeechRecognitionResultList {
  length: number;
  [index: number]: SpeechRecognitionResult;
}

interface SpeechRecognitionResult {
  isFinal: boolean;
  [index: number]: SpeechRecognitionAlternative;
}

interface SpeechRecognitionAlternative {
  transcript: string;
}

interface SpeechRecognition extends EventTarget {
  lang: string;
  continuous: boolean;
  interimResults: boolean;
  start(): void;
  stop(): void;
  abort(): void;
  onresult: ((event: SpeechRecognitionEvent) => void) | null;
  onerror: ((event: Event) => void) | null;
  onend: (() => void) | null;
}

declare global {
  interface Window {
    SpeechRecognition: new () => SpeechRecognition;
    webkitSpeechRecognition: new () => SpeechRecognition;
  }
}

/* ============ Interfaces ============ */
interface Message {
  id: string;
  role: 'user' | 'assistant';
  content: string;
  timestamp: Date;
}

interface UserProfile {
  name: string;
  email: string;
  password: string;
  avatar: string;
  avatarType: 'emoji' | 'custom';
}

/* ============ Constants ============ */
const PRESET_AVATARS = ['😀', '🐱', '🐶', '🦊', '🐼', '🦄', '🐲', '🦉', '🐙', '🦋', '🌸', '⭐', '🎯', '🎸', '🚀', '🎮', '🏀', '🍕'];

const SUGGESTIONS = [
  { icon: '💡', text: 'Tell me a joke!' },
  { icon: '🧠', text: 'Explain quantum computing simply' },
  { icon: '✨', text: 'What can Warda do?' },
  { icon: '🌟', text: 'Give me a motivational quote' },
  { icon: '📚', text: 'Help me learn something new' },
  { icon: '🎨', text: 'Suggest a creative project idea' },
];

const KIDS_SUGGESTIONS = [
  { icon: '🌈', text: 'Tell me a fun story!' },
  { icon: '🧩', text: 'Give me a riddle!' },
  { icon: '🦁', text: 'Tell me a cool animal fact!' },
  { icon: '🎨', text: 'What should I draw?' },
  { icon: '🚀', text: 'Tell me about space!' },
  { icon: '🎵', text: 'Sing me a silly song!' },
];

/* ============ Helpers ============ */
function generateId() {
  return Math.random().toString(36).substring(2, 15);
}

function loadProfile(): UserProfile | null {
  if (typeof window === 'undefined') return null;
  try {
    const stored = localStorage.getItem('warda-profile');
    if (stored) return JSON.parse(stored);
  } catch {}
  return null;
}

function saveProfile(profile: UserProfile) {
  localStorage.setItem('warda-profile', JSON.stringify(profile));
}

function loadDarkMode(): boolean {
  if (typeof window === 'undefined') return false;
  try { return localStorage.getItem('warda-darkmode') === 'true'; } catch {}
  return false;
}

function loadKidsMode(): boolean {
  if (typeof window === 'undefined') return false;
  try { return localStorage.getItem('warda-kidsmode') === 'true'; } catch {}
  return false;
}

/* ============ Bot Avatar ============ */
function BotAvatar({ speaking }: { speaking?: boolean }) {
  return (
    <div className="relative flex-shrink-0">
      <motion.div
        animate={speaking ? { scale: [1, 1.05, 1], rotate: [0, -2, 2, 0] } : {}}
        transition={speaking ? { duration: 0.6, repeat: Infinity, ease: 'easeInOut' } : {}}
        className="w-9 h-9 rounded-full bg-gradient-to-br from-yellow-300 to-orange-400 flex items-center justify-center shadow-md border-2 border-white/50"
      >
        <span className="text-lg">🦆</span>
      </motion.div>
      {speaking && (
        <motion.div
          className="absolute -top-1 -right-1 w-3 h-3 bg-green-400 rounded-full border-2 border-white"
          animate={{ scale: [1, 1.3, 1] }}
          transition={{ duration: 1, repeat: Infinity }}
        />
      )}
    </div>
  );
}

/* ============ User Avatar ============ */
function UserAvatar({ profile }: { profile: UserProfile }) {
  if (profile.avatarType === 'custom' && profile.avatar) {
    return (
      <div className="w-9 h-9 rounded-full overflow-hidden shadow-md border-2 border-white/50 flex-shrink-0">
        <img src={profile.avatar} alt={profile.name} className="w-full h-full object-cover" />
      </div>
    );
  }
  return (
    <div className="w-9 h-9 rounded-full bg-gradient-to-br from-sky-400 to-blue-500 flex items-center justify-center shadow-md border-2 border-white/50 flex-shrink-0">
      <span className="text-lg">{profile.avatar || '👤'}</span>
    </div>
  );
}

/* ============ Message Bubble ============ */
function MessageBubble({ message, profile }: { message: Message; profile: UserProfile }) {
  const isUser = message.role === 'user';
  return (
    <motion.div
      initial={{ opacity: 0, y: 20, scale: 0.95 }}
      animate={{ opacity: 1, y: 0, scale: 1 }}
      transition={{ type: 'spring', stiffness: 300, damping: 24 }}
      className={`flex gap-3 ${isUser ? 'flex-row-reverse' : 'flex-row'}`}
    >
      {isUser ? <UserAvatar profile={profile} /> : <BotAvatar />}
      <div className={`max-w-[80%] ${isUser ? 'items-end' : 'items-start'}`}>
        <div className={`rounded-2xl px-4 py-3 shadow-sm ${
          isUser
            ? 'bg-gradient-to-br from-sky-400 to-blue-500 text-white rounded-tr-md'
            : 'bg-white border border-amber-100 text-gray-800 rounded-tl-md dark:bg-gray-800 dark:border-gray-700 dark:text-gray-100'
        }`}>
          <p className="text-sm leading-relaxed whitespace-pre-wrap break-words">{message.content}</p>
        </div>
        <p className={`text-[10px] text-gray-400 mt-1 ${isUser ? 'text-right' : 'text-left'}`}>
          {message.timestamp.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
        </p>
      </div>
    </motion.div>
  );
}

/* ============ Typing Indicator ============ */
function TypingIndicator() {
  return (
    <motion.div initial={{ opacity: 0, y: 10 }} animate={{ opacity: 1, y: 0 }} exit={{ opacity: 0, y: -10 }} className="flex gap-3 items-start">
      <BotAvatar speaking />
      <div className="bg-white border border-amber-100 rounded-2xl rounded-tl-md px-5 py-3 shadow-sm dark:bg-gray-800 dark:border-gray-700">
        <div className="flex gap-1.5 items-center">
          <motion.span className="w-2 h-2 bg-amber-400 rounded-full" animate={{ y: [0, -6, 0] }} transition={{ duration: 0.6, repeat: Infinity, delay: 0 }} />
          <motion.span className="w-2 h-2 bg-orange-400 rounded-full" animate={{ y: [0, -6, 0] }} transition={{ duration: 0.6, repeat: Infinity, delay: 0.15 }} />
          <motion.span className="w-2 h-2 bg-yellow-400 rounded-full" animate={{ y: [0, -6, 0] }} transition={{ duration: 0.6, repeat: Infinity, delay: 0.3 }} />
        </div>
      </div>
    </motion.div>
  );
}

/* ============ Welcome Screen ============ */
function WelcomeScreen({ onSuggestionClick, kidsMode, profile }: { onSuggestionClick: (text: string) => void; kidsMode: boolean; profile: UserProfile }) {
  const suggestions = kidsMode ? KIDS_SUGGESTIONS : SUGGESTIONS;
  return (
    <motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} transition={{ duration: 0.5 }} className="flex flex-col items-center justify-center h-full px-4 text-center">
      <motion.div animate={{ y: [0, -8, 0] }} transition={{ duration: 3, repeat: Infinity, ease: 'easeInOut' }} className="mb-6">
        <div className="w-24 h-24 rounded-full bg-gradient-to-br from-yellow-200 to-orange-300 flex items-center justify-center shadow-lg border-4 border-white/60 mx-auto">
          <img src="/duck-logo.png" alt="Warda" className="w-20 h-20 rounded-full object-cover" />
        </div>
      </motion.div>
      <h2 className="text-3xl font-bold bg-gradient-to-r from-amber-500 to-orange-500 bg-clip-text text-transparent mb-1">
        {kidsMode ? 'Hi there!' : `Hi! I&apos;m Warda`}
      </h2>
      {!kidsMode && <p className="text-sm text-amber-600 font-medium mb-2">Welcome back, {profile.name}! 👋</p>}
      <p className="text-gray-500 max-w-md mb-8">
        {kidsMode
          ? "I'm Warda! Ask me anything fun — stories, riddles, animal facts, and more! Everything here is super safe and kid-friendly! 🌟"
          : "Your friendly AI assistant! Ask me anything and I'll do my best to help you out! 💡"}
      </p>
      <div className="grid grid-cols-1 sm:grid-cols-2 gap-2 w-full max-w-lg">
        {suggestions.map((suggestion, i) => (
          <motion.button key={i} initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }} transition={{ delay: 0.1 * i + 0.3 }}
            whileHover={{ scale: 1.03, y: -2 }} whileTap={{ scale: 0.97 }}
            onClick={() => onSuggestionClick(suggestion.text)}
            className="flex items-center gap-3 px-4 py-3 bg-white dark:bg-gray-800 rounded-xl border border-amber-100 dark:border-gray-700 hover:border-amber-300 hover:shadow-md transition-all text-left">
            <span className="text-xl">{suggestion.icon}</span>
            <span className="text-sm text-gray-600 dark:text-gray-300">{suggestion.text}</span>
          </motion.button>
        ))}
      </div>
    </motion.div>
  );
}

/* ============ Fun Facts Bar ============ */
function FunFactsBar({ fact }: { fact: string }) {
  const [isVisible, setIsVisible] = useState(true);
  if (!isVisible) return null;
  return (
    <motion.div initial={{ opacity: 0, y: -20 }} animate={{ opacity: 1, y: 0 }} exit={{ opacity: 0, y: -20 }}
      className="bg-gradient-to-r from-yellow-50 to-orange-50 border-b border-amber-200/50 dark:from-gray-800 dark:to-gray-900 dark:border-gray-700 px-4 py-2">
      <div className="max-w-3xl mx-auto flex items-center justify-center gap-2 text-xs text-amber-700 dark:text-amber-300">
        <Sparkles className="w-3.5 h-3.5 text-amber-500" />
        <span className="truncate">{fact}</span>
        <button onClick={() => setIsVisible(false)} className="ml-1 hover:text-amber-900 transition-colors">
          <ChevronDown className="w-3.5 h-3.5" />
        </button>
      </div>
    </motion.div>
  );
}

/* ============ SIGN-IN SCREEN ============ */
function SignInScreen({ onSignIn, darkMode }: { onSignIn: (profile: UserProfile) => void; darkMode: boolean }) {
  const [name, setName] = useState('');
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const [showPassword, setShowPassword] = useState(false);
  const [selectedAvatar, setSelectedAvatar] = useState('👤');
  const [customImage, setCustomImage] = useState('');
  const [error, setError] = useState('');
  const [step, setStep] = useState<'info' | 'avatar'>('info');
  const fileInputRef = useRef<HTMLInputElement>(null);

  const handleInfoNext = () => {
    if (!name.trim()) { setError('Please enter your name!'); return; }
    if (!email.trim() || !email.includes('@')) { setError('Please enter a valid email!'); return; }
    if (password.length < 3) { setError('Password must be at least 3 characters!'); return; }
    setError('');
    setStep('avatar');
  };

  const handleCustomUpload = (e: React.ChangeEvent<HTMLInputElement>) => {
    const file = e.target.files?.[0];
    if (!file) return;
    if (file.size > 2 * 1024 * 1024) { setError('Image too big! Max 2MB.'); return; }
    const reader = new FileReader();
    reader.onload = () => {
      const result = reader.result as string;
      setCustomImage(result);
      setSelectedAvatar(result);
    };
    reader.readAsDataURL(file);
  };

  const handleSignIn = () => {
    const isCustom = customImage && selectedAvatar === customImage;
    const profile: UserProfile = {
      name: name.trim(),
      email: email.trim(),
      password: password,
      avatar: selectedAvatar,
      avatarType: isCustom ? 'custom' : 'emoji',
    };
    saveProfile(profile);
    onSignIn(profile);
  };

  const handleKeyDown = (e: React.KeyboardEvent) => {
    if (e.key === 'Enter') { e.preventDefault(); if (step === 'info') handleInfoNext(); else handleSignIn(); }
  };

  return (
    <div className={`h-screen flex items-center justify-center px-4 transition-colors duration-300 ${darkMode ? 'bg-gradient-to-b from-gray-900 via-gray-850 to-gray-900' : 'bg-gradient-to-b from-amber-50 via-orange-50/30 to-sky-50/40'}`}>
      <motion.div initial={{ opacity: 0, y: 30, scale: 0.95 }} animate={{ opacity: 1, y: 0, scale: 1 }} transition={{ type: 'spring', stiffness: 300, damping: 24 }} className="w-full max-w-sm">
        {/* Logo */}
        <div className="text-center mb-6">
          <motion.div animate={{ y: [0, -8, 0] }} transition={{ duration: 3, repeat: Infinity, ease: 'easeInOut' }} className="mb-4 inline-block">
            <div className="w-20 h-20 rounded-full bg-gradient-to-br from-yellow-200 to-orange-300 flex items-center justify-center shadow-lg border-4 border-white/60 mx-auto">
              <img src="/duck-logo.png" alt="Warda" className="w-16 h-16 rounded-full object-cover" />
            </div>
          </motion.div>
          <h1 className="text-2xl font-bold bg-gradient-to-r from-amber-500 to-orange-500 bg-clip-text text-transparent mb-1">Welcome to Warda!</h1>
          <p className={`text-sm ${darkMode ? 'text-gray-400' : 'text-gray-500'}`}>
            {step === 'info' ? 'Create your account to get started' : 'Set your Warda profile picture! 🎨'}
          </p>
        </div>

        {/* Error */}
        <AnimatePresence>
          {error && (
            <motion.div initial={{ opacity: 0, height: 0 }} animate={{ opacity: 1, height: 'auto' }} exit={{ opacity: 0, height: 0 }} className="mb-4">
              <div className="bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-lg px-4 py-2 text-sm text-red-600 dark:text-red-400">{error}</div>
            </motion.div>
          )}
        </AnimatePresence>

        <div className={`rounded-2xl border p-6 shadow-lg ${darkMode ? 'bg-gray-800 border-gray-700' : 'bg-white border-amber-100'}`}>
          <AnimatePresence mode="wait">
            {step === 'info' ? (
              <motion.div key="info-step" initial={{ opacity: 0, x: -20 }} animate={{ opacity: 1, x: 0 }} exit={{ opacity: 0, x: 20 }} className="space-y-4" onKeyDown={handleKeyDown}>
                {/* Name */}
                <div>
                  <label className={`block text-xs font-medium mb-1.5 ${darkMode ? 'text-gray-300' : 'text-gray-600'}`}>Your Name</label>
                  <div className="relative">
                    <User className={`absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 ${darkMode ? 'text-gray-500' : 'text-gray-400'}`} />
                    <input type="text" value={name} onChange={(e) => setName(e.target.value)} placeholder="Enter your name" autoFocus
                      className={`w-full pl-10 pr-4 py-2.5 rounded-xl border text-sm transition-colors focus:outline-none focus:ring-2 focus:ring-amber-400/50 focus:border-amber-400 ${darkMode ? 'bg-gray-700 border-gray-600 text-gray-100 placeholder:text-gray-500' : 'bg-amber-50/50 border-amber-200 text-gray-800 placeholder:text-gray-400'}`} />
                  </div>
                </div>
                {/* Email */}
                <div>
                  <label className={`block text-xs font-medium mb-1.5 ${darkMode ? 'text-gray-300' : 'text-gray-600'}`}>Email</label>
                  <div className="relative">
                    <Mail className={`absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 ${darkMode ? 'text-gray-500' : 'text-gray-400'}`} />
                    <input type="email" value={email} onChange={(e) => setEmail(e.target.value)} placeholder="[email protected]"
                      className={`w-full pl-10 pr-4 py-2.5 rounded-xl border text-sm transition-colors focus:outline-none focus:ring-2 focus:ring-amber-400/50 focus:border-amber-400 ${darkMode ? 'bg-gray-700 border-gray-600 text-gray-100 placeholder:text-gray-500' : 'bg-amber-50/50 border-amber-200 text-gray-800 placeholder:text-gray-400'}`} />
                  </div>
                </div>
                {/* Password */}
                <div>
                  <label className={`block text-xs font-medium mb-1.5 ${darkMode ? 'text-gray-300' : 'text-gray-600'}`}>Password</label>
                  <div className="relative">
                    <Lock className={`absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 ${darkMode ? 'text-gray-500' : 'text-gray-400'}`} />
                    <input type={showPassword ? 'text' : 'password'} value={password} onChange={(e) => setPassword(e.target.value)} placeholder="Create a password"
                      className={`w-full pl-10 pr-10 py-2.5 rounded-xl border text-sm transition-colors focus:outline-none focus:ring-2 focus:ring-amber-400/50 focus:border-amber-400 ${darkMode ? 'bg-gray-700 border-gray-600 text-gray-100 placeholder:text-gray-500' : 'bg-amber-50/50 border-amber-200 text-gray-800 placeholder:text-gray-400'}`} />
                    <button type="button" onClick={() => setShowPassword(!showPassword)} className={`absolute right-3 top-1/2 -translate-y-1/2 ${darkMode ? 'text-gray-500 hover:text-gray-300' : 'text-gray-400 hover:text-gray-600'}`}>
                      {showPassword ? <EyeOff className="w-4 h-4" /> : <Eye className="w-4 h-4" />}
                    </button>
                  </div>
                </div>
                <motion.button whileHover={{ scale: 1.02 }} whileTap={{ scale: 0.98 }} onClick={handleInfoNext}
                  className="w-full py-2.5 rounded-xl bg-gradient-to-r from-amber-400 to-orange-500 hover:from-amber-500 hover:to-orange-600 text-white font-medium text-sm shadow-md transition-all">
                  Next — Set Profile Picture →
                </motion.button>
              </motion.div>
            ) : (
              <motion.div key="avatar-step" initial={{ opacity: 0, x: 20 }} animate={{ opacity: 1, x: 0 }} exit={{ opacity: 0, x: -20 }} className="space-y-4">
                {/* Preview */}
                <div className="text-center mb-2">
                  <motion.div key={selectedAvatar} initial={{ scale: 0.5, rotate: -20 }} animate={{ scale: 1, rotate: 0 }} transition={{ type: 'spring', stiffness: 300, damping: 20 }}
                    className="w-20 h-20 rounded-full bg-gradient-to-br from-sky-400 to-blue-500 flex items-center justify-center shadow-lg mx-auto mb-3 overflow-hidden">
                    {customImage && selectedAvatar === customImage ? (
                      <img src={customImage} alt="Custom avatar" className="w-full h-full object-cover" />
                    ) : (
                      <span className="text-4xl">{selectedAvatar}</span>
                    )}
                  </motion.div>
                  <p className={`text-sm ${darkMode ? 'text-gray-400' : 'text-gray-500'}`}>This is how you&apos;ll look in chat!</p>
                </div>

                {/* Upload from Computer */}
                <input ref={fileInputRef} type="file" accept="image/*" onChange={handleCustomUpload} className="hidden" />
                <motion.button whileHover={{ scale: 1.02 }} whileTap={{ scale: 0.98 }} onClick={() => fileInputRef.current?.click()}
                  className={`w-full flex items-center justify-center gap-2 py-2.5 rounded-xl border-2 border-dashed text-sm font-medium mb-2 transition-all ${darkMode ? 'border-gray-600 text-gray-300 hover:bg-gray-700' : 'border-amber-200 text-gray-600 hover:bg-amber-50'}`}>
                  <Camera className="w-4 h-4" /> Upload from Computer
                </motion.button>

                {/* Divider */}
                <div className="flex items-center gap-2">
                  <div className={`flex-1 h-px ${darkMode ? 'bg-gray-700' : 'bg-amber-200'}`} />
                  <span className={`text-xs ${darkMode ? 'text-gray-500' : 'text-gray-400'}`}>or pick an emoji</span>
                  <div className={`flex-1 h-px ${darkMode ? 'bg-gray-700' : 'bg-amber-200'}`} />
                </div>

                {/* Emoji grid */}
                <div className="grid grid-cols-6 gap-2">
                  {PRESET_AVATARS.map((avatar) => (
                    <motion.button key={avatar} whileHover={{ scale: 1.15 }} whileTap={{ scale: 0.9 }}
                      onClick={() => { setSelectedAvatar(avatar); setCustomImage(''); }}
                      className={`w-11 h-11 rounded-full flex items-center justify-center text-xl border-2 transition-all ${selectedAvatar === avatar && !customImage ? 'border-amber-400 bg-amber-50 dark:bg-amber-900/20 shadow-md scale-110' : `border-transparent ${darkMode ? 'hover:bg-gray-700' : 'hover:bg-amber-50'}`}`}>
                      {avatar}
                    </motion.button>
                  ))}
                </div>

                <div className="flex gap-2">
                  <motion.button whileHover={{ scale: 1.02 }} whileTap={{ scale: 0.98 }} onClick={() => setStep('info')}
                    className={`flex-1 py-2.5 rounded-xl border text-sm font-medium transition-all ${darkMode ? 'border-gray-600 text-gray-300 hover:bg-gray-700' : 'border-amber-200 text-gray-600 hover:bg-amber-50'}`}>
                    ← Back
                  </motion.button>
                  <motion.button whileHover={{ scale: 1.02 }} whileTap={{ scale: 0.98 }} onClick={handleSignIn}
                    className="flex-1 py-2.5 rounded-xl bg-gradient-to-r from-amber-400 to-orange-500 hover:from-amber-500 hover:to-orange-600 text-white font-medium text-sm shadow-md transition-all">
                    Start Chatting! ✨
                  </motion.button>
                </div>
              </motion.div>
            )}
          </AnimatePresence>
        </div>
      </motion.div>
    </div>
  );
}

/* ============ PROFILE PICTURE CHANGER MODAL ============ */
function ProfileChangerModal({ profile, onSave, onClose, darkMode }: { profile: UserProfile; onSave: (updated: UserProfile) => void; onClose: () => void; darkMode: boolean }) {
  const [selectedAvatar, setSelectedAvatar] = useState(profile.avatar);
  const [customImage, setCustomImage] = useState(profile.avatarType === 'custom' ? profile.avatar : '');
  const fileInputRef = useRef<HTMLInputElement>(null);

  const handleCustomUpload = (e: React.ChangeEvent<HTMLInputElement>) => {
    const file = e.target.files?.[0];
    if (!file) return;
    if (file.size > 2 * 1024 * 1024) { alert('Image too big! Max 2MB.'); return; }
    const reader = new FileReader();
    reader.onload = () => { const result = reader.result as string; setCustomImage(result); setSelectedAvatar(result); };
    reader.readAsDataURL(file);
  };

  const handleSave = () => {
    const isCustom = customImage && selectedAvatar === customImage;
    const updated: UserProfile = { ...profile, avatar: selectedAvatar, avatarType: isCustom ? 'custom' : 'emoji' };
    saveProfile(updated);
    onSave(updated);
  };

  return (
    <motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }} className="fixed inset-0 z-50 flex items-center justify-center px-4" onClick={onClose}>
      <div className="absolute inset-0 bg-black/40 backdrop-blur-sm" />
      <motion.div initial={{ scale: 0.9, y: 20 }} animate={{ scale: 1, y: 0 }} exit={{ scale: 0.9, y: 20 }} onClick={(e) => e.stopPropagation()}
        className={`relative w-full max-w-sm rounded-2xl border p-6 shadow-xl ${darkMode ? 'bg-gray-800 border-gray-700' : 'bg-white border-amber-100'}`}>
        <h3 className={`text-lg font-bold text-center mb-4 ${darkMode ? 'text-gray-100' : 'text-gray-800'}`}>Change Avatar 🎨</h3>
        <div className="text-center mb-4">
          <motion.div key={selectedAvatar} initial={{ scale: 0.8 }} animate={{ scale: 1 }} transition={{ type: 'spring', stiffness: 300 }}
            className="w-20 h-20 rounded-full bg-gradient-to-br from-sky-400 to-blue-500 flex items-center justify-center shadow-lg mx-auto overflow-hidden">
            {customImage && selectedAvatar === customImage ? (
              <img src={customImage} alt="Custom avatar" className="w-full h-full object-cover" />
            ) : (
              <span className="text-4xl">{selectedAvatar}</span>
            )}
          </motion.div>
          <p className={`text-xs mt-2 ${darkMode ? 'text-gray-400' : 'text-gray-500'}`}>Preview</p>
        </div>
        <input ref={fileInputRef} type="file" accept="image/*" onChange={handleCustomUpload} className="hidden" />
        <motion.button whileHover={{ scale: 1.02 }} whileTap={{ scale: 0.98 }} onClick={() => fileInputRef.current?.click()}
          className={`w-full flex items-center justify-center gap-2 py-2.5 rounded-xl border-2 border-dashed text-sm font-medium mb-3 transition-all ${darkMode ? 'border-gray-600 text-gray-300 hover:bg-gray-700' : 'border-amber-200 text-gray-600 hover:bg-amber-50'}`}>
          <Camera className="w-4 h-4" /> Upload Custom Picture
        </motion.button>
        <div className="grid grid-cols-6 gap-2 mb-4">
          {PRESET_AVATARS.map((avatar) => (
            <motion.button key={avatar} whileHover={{ scale: 1.15 }} whileTap={{ scale: 0.9 }}
              onClick={() => { setSelectedAvatar(avatar); setCustomImage(''); }}
              className={`w-11 h-11 rounded-full flex items-center justify-center text-xl border-2 transition-all ${selectedAvatar === avatar && !customImage ? 'border-amber-400 bg-amber-50 dark:bg-amber-900/20 shadow-md scale-110' : `border-transparent ${darkMode ? 'hover:bg-gray-700' : 'hover:bg-amber-50'}`}`}>
              {avatar}
            </motion.button>
          ))}
        </div>
        <div className="flex gap-2">
          <motion.button whileHover={{ scale: 1.02 }} whileTap={{ scale: 0.98 }} onClick={onClose}
            className={`flex-1 py-2.5 rounded-xl border text-sm font-medium transition-all ${darkMode ? 'border-gray-600 text-gray-300 hover:bg-gray-700' : 'border-amber-200 text-gray-600 hover:bg-amber-50'}`}>Cancel</motion.button>
          <motion.button whileHover={{ scale: 1.02 }} whileTap={{ scale: 0.98 }} onClick={handleSave}
            className="flex-1 py-2.5 rounded-xl bg-gradient-to-r from-amber-400 to-orange-500 hover:from-amber-500 hover:to-orange-600 text-white font-medium text-sm shadow-md transition-all">Save ✨</motion.button>
        </div>
      </motion.div>
    </motion.div>
  );
}

/* ============ MAIN APP ============ */
export default function WardaChat() {
  const [messages, setMessages] = useState<Message[]>([]);
  const [input, setInput] = useState('');
  const [isLoading, setIsLoading] = useState(false);
  const [darkMode, setDarkMode] = useState(false);
  const [showInfo, setShowInfo] = useState(false);
  const [profile, setProfile] = useState<UserProfile | null>(null);
  const [isSignedIn, setIsSignedIn] = useState(false);
  const [showProfileChanger, setShowProfileChanger] = useState(false);
  const [kidsMode, setKidsMode] = useState(false);
  const [isListening, setIsListening] = useState(false);
  const [speakerOn, setSpeakerOn] = useState(false);
  const [isSpeaking, setIsSpeaking] = useState(false);
  const messagesEndRef = useRef<HTMLDivElement>(null);
  const textareaRef = useRef<HTMLTextAreaElement>(null);
  const chatContainerRef = useRef<HTMLDivElement>(null);
  const recognitionRef = useRef<SpeechRecognition | null>(null);

  // Initialize from localStorage
  useEffect(() => {
    const saved = loadProfile();
    if (saved) { setProfile(saved); setIsSignedIn(true); }
    setDarkMode(loadDarkMode());
    setKidsMode(loadKidsMode());
  }, []);

  // Fun facts
  const funFacts = [
    '💡 Did you know? The average person spends 6 months of their lifetime waiting for red lights!',
    '🧠 Warda processes your questions at lightning speed — try me!',
    '🌍 Honey never spoils. Archaeologists have found 3000-year-old honey in Egyptian tombs!',
    '📚 The shortest war in history lasted only 38 minutes!',
    '🌟 A day on Venus is longer than a year on Venus!',
    '🎨 Octopuses have three hearts and blue blood!',
  ];
  const randomFact = funFacts[Math.floor(Math.random() * funFacts.length)];

  const scrollToBottom = useCallback(() => {
    if (messagesEndRef.current) messagesEndRef.current.scrollIntoView({ behavior: 'smooth' });
  }, []);

  useEffect(() => { scrollToBottom(); }, [messages, isLoading, scrollToBottom]);

  useEffect(() => {
    if (darkMode) document.documentElement.classList.add('dark');
    else document.documentElement.classList.remove('dark');
    localStorage.setItem('warda-darkmode', String(darkMode));
  }, [darkMode]);

  useEffect(() => { localStorage.setItem('warda-kidsmode', String(kidsMode)); }, [kidsMode]);

  // Text-to-Speech: speak assistant messages when speaker is ON
  const speakText = useCallback((text: string) => {
    if (typeof window === 'undefined') return;
    window.speechSynthesis.cancel();
    const utterance = new SpeechSynthesisUtterance(text);
    utterance.rate = 1;
    utterance.pitch = 1;
    utterance.onstart = () => setIsSpeaking(true);
    utterance.onend = () => setIsSpeaking(false);
    utterance.onerror = () => setIsSpeaking(false);
    window.speechSynthesis.speak(utterance);
  }, []);

  // Track last spoken message ID to avoid re-speaking
  const lastSpokenRef = useRef<string>('');

  useEffect(() => {
    if (speakerOn && messages.length > 0) {
      const lastMsg = messages[messages.length - 1];
      if (lastMsg.role === 'assistant' && lastMsg.id !== lastSpokenRef.current) {
        lastSpokenRef.current = lastMsg.id;
        speakText(lastMsg.content);
      }
    }
  }, [messages, speakerOn, speakText]);

  // Cleanup speech on unmount
  useEffect(() => {
    return () => { if (typeof window !== 'undefined') window.speechSynthesis?.cancel(); };
  }, []);

  // Mic: Speech-to-Text
  const toggleListening = useCallback(() => {
    if (isListening) {
      recognitionRef.current?.stop();
      setIsListening(false);
      return;
    }
    const SpeechRecognitionAPI = window.SpeechRecognition || window.webkitSpeechRecognition;
    if (!SpeechRecognitionAPI) return;
    const recognition = new SpeechRecognitionAPI();
    recognition.lang = 'en-US';
    recognition.continuous = false;
    recognition.interimResults = true;
    recognition.onresult = (event: SpeechRecognitionEvent) => {
      let transcript = '';
      for (let i = 0; i < event.results.length; i++) {
        transcript += event.results[i][0].transcript;
      }
      setInput(transcript);
    };
    recognition.onerror = () => setIsListening(false);
    recognition.onend = () => setIsListening(false);
    recognition.start();
    recognitionRef.current = recognition;
    setIsListening(true);
  }, [isListening]);

  const sendMessage = useCallback(async (text: string) => {
    if (!text.trim() || isLoading) return;
    const userMessage: Message = { id: generateId(), role: 'user', content: text.trim(), timestamp: new Date() };
    setMessages((prev) => [...prev, userMessage]);
    setInput('');
    setIsLoading(true);
    if (textareaRef.current) textareaRef.current.style.height = 'auto';
    try {
      const response = await fetch('/api/chat', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          messages: [...messages, userMessage].map((m) => ({ role: m.role, content: m.content })),
          kidsMode: kidsMode,
        }),
      });
      if (!response.ok) throw new Error('Failed to get response');
      const data = await response.json();
      const assistantMessage: Message = { id: generateId(), role: 'assistant', content: data.message.content, timestamp: new Date() };
      setMessages((prev) => [...prev, assistantMessage]);
    } catch (error) {
      console.error('Error:', error);
      const errorMessage: Message = { id: generateId(), role: 'assistant', content: 'Oops! I ran into a little problem... Could you try again?', timestamp: new Date() };
      setMessages((prev) => [...prev, errorMessage]);
    } finally { setIsLoading(false); }
  }, [isLoading, messages, kidsMode]);

  const handleSubmit = useCallback((e: React.FormEvent) => { e.preventDefault(); sendMessage(input); }, [input, sendMessage]);
  const handleKeyDown = useCallback((e: React.KeyboardEvent<HTMLTextAreaElement>) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendMessage(input); } }, [input, sendMessage]);
  const clearChat = useCallback(() => { setMessages([]); lastSpokenRef.current = ''; }, []);
  const handleSignOut = () => { localStorage.removeItem('warda-profile'); setProfile(null); setIsSignedIn(false); setMessages([]); lastSpokenRef.current = ''; };
  const handleProfileSave = (updated: UserProfile) => { setProfile(updated); setShowProfileChanger(false); };

  const supportsSpeechRecognition = typeof window !== 'undefined' && !!(window.SpeechRecognition || window.webkitSpeechRecognition);

  // Sign-in screen
  if (!isSignedIn) return <SignInScreen onSignIn={(p) => { setProfile(p); setIsSignedIn(true); }} darkMode={darkMode} />;
  if (!profile) return null;

  return (
    <div className={`h-screen flex flex-col transition-colors duration-300 ${darkMode ? 'bg-gradient-to-b from-gray-900 via-gray-850 to-gray-900' : 'bg-gradient-to-b from-amber-50 via-orange-50/30 to-sky-50/40'}`}>
      {/* Kids Mode Banner */}
      {kidsMode && (
        <div className="flex-shrink-0 bg-gradient-to-r from-green-400 to-emerald-400 text-white text-center py-1.5 text-xs font-bold flex items-center justify-center gap-1.5">
          <Baby className="w-3.5 h-3.5" /> Kids Mode — Safe &amp; Fun Zone! <Baby className="w-3.5 h-3.5" />
        </div>
      )}

      {/* Fun Facts Bar */}
      {messages.length === 0 && <FunFactsBar fact={randomFact} />}

      {/* Header */}
      <header className={`flex-shrink-0 backdrop-blur-md border-b ${darkMode ? 'bg-gray-900/80 border-gray-800' : 'bg-white/70 border-amber-100'}`}>
        <div className="max-w-3xl mx-auto px-4 py-3 flex items-center justify-between">
          <div className="flex items-center gap-3">
            <motion.div whileHover={{ rotate: [0, -10, 10, 0] }} transition={{ duration: 0.5 }} className="w-10 h-10 rounded-full bg-gradient-to-br from-yellow-300 to-orange-400 flex items-center justify-center shadow-md cursor-pointer">
              <img src="/duck-logo.png" alt="Warda" className="w-10 h-10 rounded-full object-cover" />
            </motion.div>
            <div>
              <h1 className="text-lg font-bold bg-gradient-to-r from-amber-500 to-orange-500 bg-clip-text text-transparent">Warda</h1>
              <div className="flex items-center gap-1.5">
                <motion.div animate={{ scale: [1, 1.2, 1] }} transition={{ duration: 2, repeat: Infinity }} className="w-2 h-2 bg-green-400 rounded-full" />
                <span className={`text-xs ${darkMode ? 'text-gray-400' : 'text-gray-500'}`}>Online and ready to help!</span>
              </div>
            </div>
          </div>
          <div className="flex items-center gap-1.5">
            <Badge variant="secondary" className={`text-[10px] ${darkMode ? 'bg-amber-900/30 text-amber-300 border-amber-800' : 'bg-amber-50 text-amber-600 border-amber-200'}`}>v7.0 ✨</Badge>

            {/* Kids/Adults Mode Toggle */}
            <motion.button whileHover={{ scale: 1.05 }} whileTap={{ scale: 0.95 }} onClick={() => setKidsMode(!kidsMode)}
              className={`relative h-8 px-3 rounded-full flex items-center gap-1.5 text-xs font-bold transition-all border ${kidsMode ? 'bg-green-500 text-white border-green-400 shadow-sm' : darkMode ? 'bg-gray-700 text-gray-300 border-gray-600 hover:bg-gray-600' : 'bg-gray-100 text-gray-600 border-gray-200 hover:bg-gray-200'}`}
              title={kidsMode ? 'Switch to Adults Mode' : 'Switch to Kids Mode'}>
              {kidsMode ? (<><Baby className="w-3.5 h-3.5" /> Kids</>) : (<><UserCog className="w-3.5 h-3.5" /> Adult</>)}
            </motion.button>

            {/* User avatar */}
            <motion.button whileHover={{ scale: 1.1 }} whileTap={{ scale: 0.9 }} onClick={() => setShowProfileChanger(true)}
              className={`w-8 h-8 rounded-full flex items-center justify-center shadow-sm border-2 transition-all ${darkMode ? 'border-gray-600' : 'border-white'} ${profile.avatarType === 'custom' && profile.avatar ? '' : 'bg-gradient-to-br from-sky-400 to-blue-500'}`}
              title="Change profile picture">
              {profile.avatarType === 'custom' && profile.avatar ? (
                <img src={profile.avatar} alt={profile.name} className="w-full h-full rounded-full object-cover" />
              ) : (
                <span className="text-sm">{profile.avatar || '👤'}</span>
              )}
            </motion.button>
            <span className={`text-xs font-medium hidden sm:inline ${darkMode ? 'text-gray-300' : 'text-gray-600'}`}>{profile.name}</span>

            {/* Speaker Toggle: ON = hear Warda, OFF = silent */}
            <motion.button whileHover={{ scale: 1.1 }} whileTap={{ scale: 0.9 }} onClick={() => {
              const newVal = !speakerOn;
              setSpeakerOn(newVal);
              if (!newVal) { window.speechSynthesis.cancel(); setIsSpeaking(false); }
            }}
              className={`h-8 w-8 rounded-full flex items-center justify-center transition-all ${speakerOn ? 'bg-amber-100 dark:bg-amber-900/30 text-amber-500' : darkMode ? 'text-gray-500 hover:bg-gray-800' : 'text-gray-400 hover:bg-amber-50'}`}
              title={speakerOn ? 'Voice ON — Warda speaks aloud' : 'Voice OFF — silent'}>
              {speakerOn ? <Volume2 className="w-4 h-4" /> : <VolumeX className="w-4 h-4" />}
            </motion.button>

            <Button variant="ghost" size="icon" onClick={() => setShowInfo(!showInfo)} className={`h-8 w-8 ${darkMode ? 'hover:bg-gray-800' : 'hover:bg-amber-50'}`}>
              <Info className={`w-4 h-4 ${darkMode ? 'text-gray-400' : 'text-gray-500'}`} />
            </Button>
            <Button variant="ghost" size="icon" onClick={() => setDarkMode(!darkMode)} className={`h-8 w-8 ${darkMode ? 'hover:bg-gray-800' : 'hover:bg-amber-50'}`}>
              {darkMode ? <Sun className="w-4 h-4 text-yellow-400" /> : <Moon className="w-4 h-4 text-gray-500" />}
            </Button>
            {messages.length > 0 && (
              <Button variant="ghost" size="icon" onClick={clearChat} className={`h-8 w-8 ${darkMode ? 'hover:bg-gray-800' : 'hover:bg-amber-50'}`} title="Clear chat">
                <Trash2 className="w-4 h-4 text-gray-500" />
              </Button>
            )}
            <Button variant="ghost" size="icon" onClick={handleSignOut} className={`h-8 w-8 ${darkMode ? 'hover:bg-gray-800' : 'hover:bg-amber-50'}`} title="Sign out">
              <LogOut className="w-4 h-4 text-gray-500" />
            </Button>
          </div>
        </div>

        {/* Info Banner */}
        <AnimatePresence>
          {showInfo && (
            <motion.div initial={{ height: 0, opacity: 0 }} animate={{ height: 'auto', opacity: 1 }} exit={{ height: 0, opacity: 0 }} transition={{ duration: 0.2 }} className="overflow-hidden">
              <div className={`mx-4 mb-3 p-3 rounded-lg text-xs ${darkMode ? 'bg-gray-800 text-gray-300' : 'bg-amber-50 text-amber-700'}`}>
                <p className="font-medium mb-1">About Warda</p>
                <p>Warda is a smart and friendly AI chatbot powered by a real language model. Ask about coding, writing, brainstorming, or just chat for fun! Press Shift+Enter for new lines.</p>
                <p className="mt-2"><strong>Kids Mode:</strong> Filters content to be child-friendly and safe. Switch between Adults and Kids mode using the toggle button! 🎮</p>
              </div>
            </motion.div>
          )}
        </AnimatePresence>
      </header>

      {/* Chat Area */}
      <div ref={chatContainerRef} className="flex-1 overflow-y-auto">
        <div className="max-w-3xl mx-auto px-4 py-4">
          {messages.length === 0 ? (
            <WelcomeScreen onSuggestionClick={sendMessage} kidsMode={kidsMode} profile={profile} />
          ) : (
            <div className="space-y-4">
              <AnimatePresence>
                {messages.map((message) => (
                  <MessageBubble key={message.id} message={message} profile={profile} />
                ))}
              </AnimatePresence>
              <AnimatePresence>{isLoading && <TypingIndicator />}</AnimatePresence>
              <div ref={messagesEndRef} />
            </div>
          )}
        </div>
      </div>

      {/* Input Area */}
      <div className={`flex-shrink-0 border-t backdrop-blur-md ${darkMode ? 'bg-gray-900/80 border-gray-800' : 'bg-white/70 border-amber-100'}`}>
        <form onSubmit={handleSubmit} className="max-w-3xl mx-auto p-4">
          {/* Listening indicator */}
          <AnimatePresence>
            {isListening && (
              <motion.div initial={{ opacity: 0, height: 0 }} animate={{ opacity: 1, height: 'auto' }} exit={{ opacity: 0, height: 0 }} className="mb-2 flex items-center justify-center gap-2">
                <motion.div animate={{ scale: [1, 1.5, 1] }} transition={{ duration: 0.8, repeat: Infinity }} className="w-3 h-3 bg-red-500 rounded-full" />
                <motion.div animate={{ scale: [1, 1.5, 1] }} transition={{ duration: 0.8, repeat: Infinity, delay: 0.2 }} className="w-3 h-3 bg-red-400 rounded-full" />
                <motion.div animate={{ scale: [1, 1.5, 1] }} transition={{ duration: 0.8, repeat: Infinity, delay: 0.4 }} className="w-3 h-3 bg-red-300 rounded-full" />
                <span className="text-sm text-red-500 font-medium ml-1">Listening...</span>
              </motion.div>
            )}
          </AnimatePresence>

          <div className={`flex items-end gap-2 rounded-2xl border px-3 py-2 ${darkMode ? 'bg-gray-800 border-gray-700' : 'bg-white border-amber-100'}`}>
            <Textarea
              ref={textareaRef}
              value={input}
              onChange={(e) => setInput(e.target.value)}
              onKeyDown={handleKeyDown}
              placeholder={kidsMode ? 'Ask Warda anything fun! 🌟' : 'Ask Warda anything...'}
              disabled={isLoading}
              rows={1}
              className={`flex-1 resize-none border-0 shadow-none focus-visible:ring-0 p-2 text-sm min-h-[36px] max-h-32 ${darkMode ? 'bg-transparent text-gray-100 placeholder:text-gray-500' : 'bg-transparent text-gray-800 placeholder:text-gray-400'}`}
              onInput={(e) => {
                const target = e.target as HTMLTextAreaElement;
                target.style.height = 'auto';
                target.style.height = Math.min(target.scrollHeight, 128) + 'px';
              }}
            />

            {/* Mic Button */}
            {supportsSpeechRecognition && (
              <motion.button
                type="button"
                whileHover={{ scale: 1.1 }}
                whileTap={{ scale: 0.9 }}
                onClick={toggleListening}
                className={`h-9 w-9 rounded-full flex items-center justify-center transition-all flex-shrink-0 ${
                  isListening
                    ? 'bg-red-500 text-white shadow-lg shadow-red-500/30'
                    : darkMode
                    ? 'bg-gray-700 text-gray-300 hover:bg-gray-600'
                    : 'bg-amber-50 text-amber-500 hover:bg-amber-100'
                }`}
                title={isListening ? 'Stop listening' : 'Start voice input'}>
                {isListening ? <MicOff className="w-4 h-4" /> : <Mic className="w-4 h-4" />}
              </motion.button>
            )}

            {/* Send Button */}
            <motion.button
              type="submit"
              whileHover={{ scale: 1.1 }}
              whileTap={{ scale: 0.9 }}
              disabled={!input.trim() || isLoading}
              className="h-9 w-9 rounded-full bg-gradient-to-r from-amber-400 to-orange-500 hover:from-amber-500 hover:to-orange-600 text-white flex items-center justify-center shadow-md transition-all flex-shrink-0 disabled:opacity-40 disabled:cursor-not-allowed">
              {isLoading ? <Loader2 className="w-4 h-4 animate-spin" /> : <Send className="w-4 h-4" />}
            </motion.button>
          </div>

          <p className={`text-center text-[10px] mt-2 ${darkMode ? 'text-gray-600' : 'text-gray-400'}`}>
            Warda may make mistakes. Always double-check important information.
          </p>
        </form>
      </div>

      {/* Profile Changer Modal */}
      <AnimatePresence>
        {showProfileChanger && (
          <ProfileChangerModal profile={profile} onSave={handleProfileSave} onClose={() => setShowProfileChanger(false)} darkMode={darkMode} />
        )}
      </AnimatePresence>
    </div>
  );
}
Editor is loading...
Leave a Comment