'use client' import { useState, useRef, useEffect } from 'react' import { useChat } from '@ai-sdk/react' import { DefaultChatTransport } from 'ai' import type { UIMessage } from 'ai' import { cn } from '@/lib/utils' import { Button } from '@/components/ui/button' import { X, Bot, Sparkles, History, Send, Globe, Briefcase, Palette, GraduationCap, Coffee, Loader2, BookOpen, Layers, Square, Plus } from 'lucide-react' import { useLanguage } from '@/lib/i18n' import { MarkdownContent } from '@/components/markdown-content' import { useWebSearchAvailable } from '@/hooks/use-web-search-available' import { useNotebooks } from '@/context/notebooks-context' import { HierarchicalNotebookSelector } from '@/components/hierarchical-notebook-selector' import { toast } from 'sonner' import { createConversation } from '@/app/actions/chat-actions' function getTextContent(msg: UIMessage): string { if (msg.parts && Array.isArray(msg.parts)) { return msg.parts.filter((p: any) => p.type === 'text' && typeof p.text === 'string').map((p: any) => p.text).join('') } if (typeof (msg as any).content === 'string') return (msg as any).content return '' } const TONES = [ { id: 'professional', label: 'Professional', icon: Briefcase }, { id: 'creative', label: 'Creative', icon: Palette }, { id: 'academic', label: 'Academic', icon: GraduationCap }, { id: 'casual', label: 'Casual', icon: Coffee }, ] export function AIChat({ showFloatingTrigger = true }: { showFloatingTrigger?: boolean } = {}) { const { t, language } = useLanguage() const webSearchAvailable = useWebSearchAvailable() const { notebooks } = useNotebooks() const [isOpen, setIsOpen] = useState(false) const [isExpanded, setIsExpanded] = useState(false) const [activeTab, setActiveTab] = useState('chat') const [isContextualAIVisible, setIsContextualAIVisible] = useState(false) const [selectedTone, setSelectedTone] = useState('professional') const [webSearch, setWebSearch] = useState(false) const [chatScope, setChatScope] = useState<'all' | string>('all') const [input, setInput] = useState('') const [conversationId, setConversationId] = useState() const [history, setHistory] = useState([]) const [historyLoading, setHistoryLoading] = useState(false) const [insights, setInsights] = useState('') const [insightsLoading, setInsightsLoading] = useState(false) const messagesEndRef = useRef(null) const transport = useRef(new DefaultChatTransport({ api: '/api/chat' })).current const { messages, setMessages, sendMessage, status, stop } = useChat({ transport, onError: (error) => { console.error('Chat error:', error) toast.error(t('chat.assistantError') || 'Chat error') } }) const isLoading = status === 'submitted' || status === 'streaming' const handleSend = async () => { const text = input.trim() if (!text || isLoading) return setInput('') // Create conversation upfront so we have the ID for continuity let convId = conversationId if (!convId) { try { const result = await createConversation(text, chatScope !== 'all' ? chatScope : undefined) convId = result.id setConversationId(convId) } catch { toast.error(t('chat.createError')) return } } try { await sendMessage( { text }, { body: { tone: selectedTone, chatScope, notebookId: chatScope !== 'all' ? chatScope : undefined, webSearch: webSearch && webSearchAvailable, conversationId: convId, language, } } ) } catch (error) { console.error('Chat send error:', error) toast.error(t('chat.assistantError') || 'Failed to send message') } } const fetchHistory = async () => { setHistoryLoading(true) try { const res = await fetch('/api/chat/history') if (res.ok) setHistory(await res.json()) } catch (e) { console.error(e) } setHistoryLoading(false) } const fetchInsights = async () => { setInsightsLoading(true) try { const res = await fetch('/api/chat/insights') if (res.ok) { const data = await res.json() setInsights(data.insight) } } catch (e) { console.error(e) } setInsightsLoading(false) } useEffect(() => { if (activeTab === 'history') fetchHistory() if (activeTab === 'insights' && !insights) fetchInsights() }, [activeTab]) useEffect(() => { if (messagesEndRef.current) { messagesEndRef.current.scrollIntoView({ behavior: 'smooth' }) } }, [messages]) useEffect(() => { const handleToggle = () => setIsOpen(v => !v) const handleVisibility = (e: any) => setIsContextualAIVisible(e.detail) window.addEventListener('toggle-ai-chat', handleToggle) window.addEventListener('contextual-ai-visibility', handleVisibility) return () => { window.removeEventListener('toggle-ai-chat', handleToggle) window.removeEventListener('contextual-ai-visibility', handleVisibility) } }, []) if (!isOpen) { if (isContextualAIVisible) return null if (!showFloatingTrigger) return null return ( ) } return (