'use client' import { useState, useRef, useEffect } from 'react' import { Button } from '@/components/ui/button' import { Textarea } from '@/components/ui/textarea' import { ScrollArea } from '@/components/ui/scroll-area' import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' import { Badge } from '@/components/ui/badge' import { Dialog, DialogContent, DialogTitle } from '@/components/ui/dialog' import { Bot, User, Send, Loader2, FileText, Lightbulb, StickyNote, Clock } from 'lucide-react' import ReactMarkdown from 'react-markdown' import remarkGfm from 'remark-gfm' import { SourceChatMessage, SourceChatContextIndicator, BaseChatSession } from '@/lib/types/api' import { ModelSelector } from './ModelSelector' import { ContextIndicator } from '@/components/common/ContextIndicator' import { SessionManager } from '@/components/source/SessionManager' import { MessageActions } from '@/components/source/MessageActions' import { convertReferencesToCompactMarkdown, createCompactReferenceLinkComponent } from '@/lib/utils/source-references' import { useModalManager } from '@/lib/hooks/use-modal-manager' import { toast } from 'sonner' interface NotebookContextStats { sourcesInsights: number sourcesFull: number notesCount: number tokenCount?: number charCount?: number } interface ChatPanelProps { messages: SourceChatMessage[] isStreaming: boolean contextIndicators: SourceChatContextIndicator | null onSendMessage: (message: string, modelOverride?: string) => void modelOverride?: string onModelChange?: (model?: string) => void // Session management props sessions?: BaseChatSession[] currentSessionId?: string | null onCreateSession?: (title: string) => void onSelectSession?: (sessionId: string) => void onDeleteSession?: (sessionId: string) => void onUpdateSession?: (sessionId: string, title: string) => void loadingSessions?: boolean // Generic props for reusability title?: string contextType?: 'source' | 'notebook' // Notebook context stats (for notebook chat) notebookContextStats?: NotebookContextStats // Notebook ID for saving notes notebookId?: string } export function ChatPanel({ messages, isStreaming, contextIndicators, onSendMessage, modelOverride, onModelChange, sessions = [], currentSessionId, onCreateSession, onSelectSession, onDeleteSession, onUpdateSession, loadingSessions = false, title = 'Chat with Source', contextType = 'source', notebookContextStats, notebookId }: ChatPanelProps) { const [input, setInput] = useState('') const [sessionManagerOpen, setSessionManagerOpen] = useState(false) const scrollAreaRef = useRef(null) const messagesEndRef = useRef(null) const { openModal } = useModalManager() const handleReferenceClick = (type: string, id: string) => { const modalType = type === 'source_insight' ? 'insight' : type as 'source' | 'note' | 'insight' try { openModal(modalType, id) // Note: The modal system uses URL parameters and doesn't throw errors for missing items. // The modal component itself will handle displaying "not found" states. // This try-catch is here for future enhancements or unexpected errors. } catch { const typeLabel = type === 'source_insight' ? 'insight' : type toast.error(`This ${typeLabel} could not be found`) } } // Auto-scroll to bottom when new messages arrive useEffect(() => { messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }) }, [messages]) const handleSend = () => { if (input.trim() && !isStreaming) { onSendMessage(input.trim(), modelOverride) setInput('') } } const handleKeyDown = (e: React.KeyboardEvent) => { // Detect platform for correct modifier key const isMac = typeof navigator !== 'undefined' && navigator.userAgent.toUpperCase().indexOf('MAC') >= 0 const isModifierPressed = isMac ? e.metaKey : e.ctrlKey if (e.key === 'Enter' && isModifierPressed) { e.preventDefault() handleSend() } } // Detect platform for placeholder text const isMac = typeof navigator !== 'undefined' && navigator.userAgent.toUpperCase().indexOf('MAC') >= 0 const keyHint = isMac ? '⌘+Enter' : 'Ctrl+Enter' return ( <>
{title} {onSelectSession && onCreateSession && onDeleteSession && ( Chat Sessions onCreateSession?.(title)} onSelectSession={(sessionId) => { onSelectSession(sessionId) setSessionManagerOpen(false) }} onUpdateSession={(sessionId, title) => onUpdateSession?.(sessionId, title)} onDeleteSession={(sessionId) => onDeleteSession?.(sessionId)} loadingSessions={loadingSessions} /> )}
{messages.length === 0 ? (

Start a conversation about this {contextType}

Ask questions to understand the content better

) : ( messages.map((message) => (
{message.type === 'ai' && (
)}
{message.type === 'ai' ? ( ) : (

{message.content}

)}
{message.type === 'ai' && ( )}
{message.type === 'human' && (
)}
)) )} {isStreaming && (
)}
{/* Context Indicators */} {contextIndicators && (
{contextIndicators.sources?.length > 0 && ( {contextIndicators.sources.length} source{contextIndicators.sources.length > 1 ? 's' : ''} )} {contextIndicators.insights?.length > 0 && ( {contextIndicators.insights.length} insight{contextIndicators.insights.length > 1 ? 's' : ''} )} {contextIndicators.notes?.length > 0 && ( {contextIndicators.notes.length} note{contextIndicators.notes.length > 1 ? 's' : ''} )}
)} {/* Notebook Context Indicator */} {notebookContextStats && ( )} {/* Input Area */}
{/* Model selector */} {onModelChange && (
Model
)}