import React, { useState, useRef, useEffect, useCallback } from 'react'; import { motion, AnimatePresence } from 'framer-motion'; import type { ChatMessage as ChatMessageType, Persona, ChatSession, UserTier } from '@/lib/ai/types'; import { canAccessPersona } from '@/lib/ai/types'; import { PERSONAS, getDefaultPersona } from '@/lib/ai/personas'; import { runChat, generateTitle } from '@/lib/ai/gemini-service'; import { ChatMessage } from './ChatMessage'; import { ChatInput } from './ChatInput'; import { PersonaSelector } from './PersonaSelector'; import { getPersonaIcon, CloseIcon, TrashIcon, SparklesIcon, ChatIcon } from './Icons'; import { Button } from '@/components/ui/button'; import { ScrollArea } from '@/components/ui/scroll-area'; import { useAuth } from '@/contexts/AuthContext'; interface AIChatProps { isOpen: boolean; onClose: () => void; initialPersonaId?: string; currentRealm?: string; } const STORAGE_KEY = 'aethex-ai-sessions'; const getUserTier = (roles: string[]): UserTier => { if (roles.includes('council') || roles.includes('admin') || roles.includes('owner')) { return 'Council'; } if (roles.includes('architect') || roles.includes('staff') || roles.includes('premium')) { return 'Architect'; } return 'Free'; }; export const AIChat: React.FC = ({ isOpen, onClose, initialPersonaId, currentRealm }) => { const { user, roles } = useAuth(); const userTier = getUserTier(roles); const [currentPersona, setCurrentPersona] = useState(() => { if (initialPersonaId) { return PERSONAS.find(p => p.id === initialPersonaId) || getDefaultPersona(); } return getDefaultPersona(); }); const [messages, setMessages] = useState([ { role: 'model', content: currentPersona.initialMessage, timestamp: Date.now() } ]); const [isLoading, setIsLoading] = useState(false); const [sessions, setSessions] = useState([]); const [currentSessionId, setCurrentSessionId] = useState(null); const messagesEndRef = useRef(null); const hasAccess = canAccessPersona(userTier, currentPersona.requiredTier); useEffect(() => { const stored = localStorage.getItem(STORAGE_KEY); if (stored) { try { setSessions(JSON.parse(stored)); } catch { localStorage.removeItem(STORAGE_KEY); } } }, []); useEffect(() => { if (sessions.length > 0) { localStorage.setItem(STORAGE_KEY, JSON.stringify(sessions.slice(0, 20))); } }, [sessions]); useEffect(() => { messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); }, [messages]); const handlePersonaChange = useCallback((persona: Persona) => { setCurrentPersona(persona); setMessages([ { role: 'model', content: persona.initialMessage, timestamp: Date.now() } ]); setCurrentSessionId(null); }, []); const handleSendMessage = useCallback(async (content: string) => { if (!content.trim() || isLoading || !hasAccess) return; const userMessage: ChatMessageType = { role: 'user', content, timestamp: Date.now() }; setMessages(prev => [...prev, userMessage]); setIsLoading(true); try { const history = messages.filter(m => m.role !== 'model' || messages.indexOf(m) > 0); let response: string; try { response = await runChat( content, history, currentPersona.systemInstruction, currentPersona.tools ); } catch (err) { const errorMessage = err instanceof Error ? err.message : 'Unknown error'; if (errorMessage.includes('AI service not configured') || errorMessage.includes('not configured')) { response = "The AI service is currently being set up. Please check back soon, or contact the administrator to configure the Gemini API key."; } else { throw err; } } const modelMessage: ChatMessageType = { role: 'model', content: response, timestamp: Date.now() }; setMessages(prev => [...prev, modelMessage]); if (!currentSessionId && messages.length === 1) { const title = await generateTitle(content); const newSession: ChatSession = { id: crypto.randomUUID(), personaId: currentPersona.id, title, messages: [...messages, userMessage, modelMessage], timestamp: Date.now() }; setSessions(prev => [newSession, ...prev]); setCurrentSessionId(newSession.id); } else if (currentSessionId) { setSessions(prev => prev.map(s => s.id === currentSessionId ? { ...s, messages: [...messages, userMessage, modelMessage], timestamp: Date.now() } : s )); } } catch (error) { console.error('[AIChat] Error:', error); const errorMessage: ChatMessageType = { role: 'model', content: "I encountered an error processing your request. Please try again.", timestamp: Date.now() }; setMessages(prev => [...prev, errorMessage]); } finally { setIsLoading(false); } }, [messages, isLoading, hasAccess, currentPersona, currentSessionId]); const handleClearChat = useCallback(() => { setMessages([ { role: 'model', content: currentPersona.initialMessage, timestamp: Date.now() } ]); setCurrentSessionId(null); }, [currentPersona]); const Icon = getPersonaIcon(currentPersona.icon); return ( {isOpen && ( <>
{messages.length > 1 && !isLoading && ( )}
{messages.map((msg, index) => ( ))} {isLoading && (
)}

{user ? `Signed in as ${user.email}` : 'Sign in for personalized experience'} ยท {userTier} Tier

)} ); }; export const AIChatButton: React.FC<{ currentRealm?: string }> = ({ currentRealm }) => { const [isOpen, setIsOpen] = useState(false); return ( <> setIsOpen(true)} className="fixed bottom-6 right-6 w-14 h-14 rounded-full bg-gradient-to-tr from-cyan-500 to-purple-600 shadow-lg flex items-center justify-center z-30 hover:scale-110 transition-transform" whileHover={{ scale: 1.1 }} whileTap={{ scale: 0.95 }} > setIsOpen(false)} currentRealm={currentRealm} /> ); }; export default AIChat;