'use client' import { useState, useRef, useEffect, useCallback } from 'react' import { useChat } from '@ai-sdk/react' import { DefaultChatTransport } from 'ai' import type { UIMessage } from 'ai' import { cn } from '@/lib/utils' import { X, Bot, Sparkles, History, Send, Globe, Briefcase, Palette, GraduationCap, Coffee, Loader2, Layers, Square, Plus, ChevronRight, MessageSquare, FileCode, Zap, Network, Clock, Scissors, Languages, Layout, ArrowRightLeft, BookOpen, Maximize2, Minimize2 } 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' import { motion, AnimatePresence } from 'motion/react' 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 TONE_IDS = [ { id: 'professional', icon: Briefcase }, { id: 'creative', icon: Palette }, { id: 'academic', icon: GraduationCap }, { id: 'casual', icon: Coffee }, ] as const type AITab = 'discussion' | 'actions' | 'explore' | 'resources' 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 [aiTab, setAiTab] = useState('discussion') 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 [isContextualAIVisible, setIsContextualAIVisible] = useState(false) 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('') 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 (aiTab === 'discussion') { // history is loaded on demand via insights tab } }, [aiTab]) useEffect(() => { if (messagesEndRef.current) { messagesEndRef.current.scrollIntoView({ behavior: 'smooth' }) } }, [messages]) const handleToggle = useCallback(() => setIsOpen(v => !v), []) const handleVisibility = useCallback((e: any) => setIsContextualAIVisible(e.detail), []) useEffect(() => { 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) } }, [handleToggle, handleVisibility]) // Floating trigger when closed if (!isOpen) { if (!showFloatingTrigger || isContextualAIVisible) return null return ( setIsOpen(true)} className="fixed bottom-6 end-6 h-12 w-12 rounded-2xl shadow-xl z-40 bg-brand-accent text-white hover:scale-105 border border-brand-accent/20" whileHover={{ scale: 1.05 }} whileTap={{ scale: 0.95 }} title={t('ai.openAssistant')} > ) } return (
{/* Header */}

{t('ai.assistantTitle')}

{t('ai.poweredByMomento')}

{/* Tabs */}
{(['discussion', 'actions', 'explore', 'resources'] as AITab[]).map((tab) => { const labels: Record = { discussion: t('ai.chatTab') || 'Discussion', actions: t('ai.actionsTab') || 'Actions', explore: t('ai.exploreTab') || 'Explorer', resources: t('ai.resourcesTab') || 'Ressources', } return ( ) })}
{/* Content */}
{aiTab === 'discussion' && ( {/* Context selector */}
{TONE_IDS.map((tone) => { const Icon = tone.icon return ( ) })}
+ Carnet
!nb.trashedAt)} selectedId={chatScope !== 'all' ? chatScope : null} onSelect={(id) => setChatScope(id)} placeholder={t('ai.selectNotebook') || 'Inclure un carnet...'} dropUp />
{/* Messages */} {messages.length === 0 && (

{t('ai.welcomeMsg')}

)} {messages.map((msg: UIMessage) => { const text = getTextContent(msg) if (msg.role === 'assistant' && !text) return null return (
{msg.role === 'user' ? 'U' : }
{msg.role === 'assistant' ? :

{text}

}
) })} {isLoading && (
)}
)} {aiTab === 'actions' && (

Transformations

{[ { icon: , label: t('ai.actionClarify') || 'Clarifier' }, { icon: , label: t('ai.actionShorten') || 'Raccourcir' }, { icon: , label: t('ai.actionImprove') || 'Améliorer' }, { icon: , label: t('ai.actionTranslate') || 'Traduire' }, ].map((action, i) => ( ))}

Generation Tools

{t('ai.slides') || 'Présentation'}

{t('ai.slidesDesc') || 'Convertir en slides interactives'}

{t('ai.diagram') || 'Diagramme'}

{t('ai.diagramDesc') || 'Visualisation de structure'}

Auto-Save Enabled
)} {aiTab === 'explore' && (

Intelligence Modules

{t('ai.modulesHint') || 'Ces modules utilisent les embeddings pour analyser vos pensées.'}

)} {aiTab === 'resources' && (