import { useState, useRef, useEffect } from 'react'; import { useMutation } from '@tanstack/react-query'; import { copilotApi } from '@/lib/api'; import { X, Send, Bot, User, ImagePlus, Loader2, Sparkles, Trash2, } from 'lucide-react'; import ReactMarkdown from 'react-markdown'; interface ContentBlock { type: 'text' | 'image'; text?: string; source?: { type: 'base64'; media_type: string; data: string; }; } interface Message { id: string; role: 'user' | 'assistant'; content: string | ContentBlock[]; timestamp: Date; toolCalls?: any[]; } export function AICopilot() { const [isOpen, setIsOpen] = useState(false); const [messages, setMessages] = useState([]); const [input, setInput] = useState(''); const [pendingImage, setPendingImage] = useState<{ data: string; type: string } | null>(null); const messagesEndRef = useRef(null); const fileInputRef = useRef(null); const scrollToBottom = () => { messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); }; useEffect(() => { scrollToBottom(); }, [messages]); const chatMutation = useMutation({ mutationFn: async (chatMessages: { role: string; content: string | ContentBlock[] }[]) => { const { data } = await copilotApi.post('/copilot/chat', { messages: chatMessages }); return data; }, onSuccess: (data) => { const assistantMessage: Message = { id: Date.now().toString(), role: 'assistant', content: data.response, timestamp: new Date(), toolCalls: data.toolCalls, }; setMessages((prev) => [...prev, assistantMessage]); }, onError: (error: any) => { const errorMessage: Message = { id: Date.now().toString(), role: 'assistant', content: `Sorry, I encountered an error: ${error.message || 'Unknown error'}. Please try again.`, timestamp: new Date(), }; setMessages((prev) => [...prev, errorMessage]); }, }); const handleSend = () => { if (!input.trim() && !pendingImage) return; // Build content let content: string | ContentBlock[]; if (pendingImage) { content = []; if (input.trim()) { content.push({ type: 'text', text: input.trim() }); } content.push({ type: 'image', source: { type: 'base64', media_type: pendingImage.type, data: pendingImage.data, }, }); } else { content = input.trim(); } const userMessage: Message = { id: Date.now().toString(), role: 'user', content, timestamp: new Date(), }; const newMessages = [...messages, userMessage]; setMessages(newMessages); setInput(''); setPendingImage(null); // Prepare messages for API const apiMessages = newMessages.map((msg) => ({ role: msg.role, content: msg.content, })); chatMutation.mutate(apiMessages); }; const handleKeyDown = (e: React.KeyboardEvent) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); handleSend(); } }; const handleImageUpload = (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (!file) return; const reader = new FileReader(); reader.onload = () => { const base64 = (reader.result as string).split(',')[1]; setPendingImage({ data: base64, type: file.type, }); }; reader.readAsDataURL(file); }; const clearChat = () => { setMessages([]); setPendingImage(null); }; const renderContent = (content: string | ContentBlock[]) => { if (typeof content === 'string') { return (

{children}

, ul: ({ children }) =>
    {children}
, ol: ({ children }) =>
    {children}
, li: ({ children }) =>
  • {children}
  • , strong: ({ children }) => {children}, code: ({ children }) => ( {children} ), pre: ({ children }) => (
    {children}
    ), }} > {content}
    ); } return (
    {content.map((block, i) => { if (block.type === 'text') { return

    {block.text}

    ; } if (block.type === 'image' && block.source) { return ( Uploaded ); } return null; })}
    ); }; return ( <> {/* Floating button */} {/* Chat panel */}
    {/* Header */}
    VIP Coordinator AI
    {/* Messages */}
    {messages.length === 0 && (

    Hi! I'm your AI assistant.

    I can help you with VIPs, drivers, events, and more.
    You can also upload screenshots of emails!

    Try asking:

    )} {messages.map((message) => (
    {message.role === 'assistant' && (
    )}
    {renderContent(message.content)}
    {message.toolCalls && message.toolCalls.length > 0 && (
    Actions taken: {message.toolCalls.map((tc) => tc.tool).join(', ')}
    )}
    {message.role === 'user' && (
    )}
    ))} {chatMutation.isPending && (
    )}
    {/* Pending image preview */} {pendingImage && (
    To upload
    )} {/* Input */}