import { useState, useRef, useEffect } from 'react'; import { Send, Square } from 'lucide-react'; import type { Message, ToolResult } from '@/api/types'; import { api } from '@/api/client'; import { MessageBubble } from './MessageBubble'; interface Props { sessionId: string; chatId: string; messages: Message[]; isStreaming: boolean; connected: boolean; } export function ChatPane({ sessionId, chatId, messages, isStreaming, connected }: Props) { const [input, setInput] = useState(''); const [sending, setSending] = useState(false); const messagesEndRef = useRef(null); const textareaRef = useRef(null); // Auto-scroll to bottom when messages change useEffect(() => { messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); }, [messages]); // Auto-resize textarea useEffect(() => { const el = textareaRef.current; if (!el) return; el.style.height = 'auto'; el.style.height = Math.min(el.scrollHeight, 200) + 'px'; }, [input]); const handleSend = async () => { const content = input.trim(); if (!content || sending || isStreaming) return; setInput(''); setSending(true); try { await api.messages.send(sessionId, chatId, content); } catch (err) { console.error('send failed:', err); // Restore input on failure setInput(content); } finally { setSending(false); } }; const handleStop = async () => { try { await api.messages.stop(sessionId); } catch (err) { console.error('stop failed:', err); } }; const handleKeyDown = (e: React.KeyboardEvent) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); handleSend(); } }; // Filter out system messages for display (sentinels) const visibleMessages = messages.filter((m) => m.role !== 'system'); // Build a lookup map from tool_call_id -> ToolResult for all messages const toolResultsMap: Record = {}; for (const msg of messages) { if (msg.tool_results) { toolResultsMap[msg.tool_results.tool_call_id] = msg.tool_results; } } return (
{/* Connection indicator */}
{connected ? 'Connected' : 'Disconnected'} {isStreaming && ( Generating... )}
{/* Messages list */}
{visibleMessages.length === 0 && (

BooCoder

Send a message to start coding.

)} {visibleMessages.map((msg) => ( ))}
{/* Input area */}