import { type KeyboardEvent, useEffect, useMemo, useRef, useState } from 'react'; import { AlertCircle, ArrowUp, Bot, Loader2, User } from 'lucide-react'; import { buildAgentActivityRows, formatAgentActivityLabel, isAgentActivityActive, isAgentActivityCompleted, type SessionActivityState, } from '@renderer/lib/sessionActivity'; import type { PatternDefinition } from '@shared/domain/pattern'; import type { ProjectRecord } from '@shared/domain/project'; import type { SessionRecord } from '@shared/domain/session'; function ThinkingDots() { return (
); } interface ChatPaneProps { activity?: SessionActivityState; project: ProjectRecord; pattern: PatternDefinition; session: SessionRecord; onSend: (content: string) => Promise; } export function ChatPane({ activity, project, pattern, session, onSend }: ChatPaneProps) { const [input, setInput] = useState(''); const transcriptRef = useRef(null); const textareaRef = useRef(null); const isBusy = session.status === 'running'; const activityRows = useMemo( () => buildAgentActivityRows(activity, pattern.agents, isBusy), [activity, isBusy, pattern.agents], ); useEffect(() => { transcriptRef.current?.scrollTo({ top: transcriptRef.current.scrollHeight, behavior: 'smooth', }); }, [session.messages.length, isBusy]); async function handleSubmit() { const text = input.trim(); if (!text || isBusy) return; setInput(''); await onSend(text); } function handleKeyDown(e: KeyboardEvent) { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); void handleSubmit(); } } return (
{/* Header — extra top padding clears the title bar overlay zone */}

{session.title}

{project.name} · {pattern.name} · {pattern.mode}

{session.status === 'running' && ( )} {session.status === 'error' && (
Error
)} {session.status === 'idle' && session.messages.length > 0 && ( {session.messages.length} message{session.messages.length === 1 ? '' : 's'} )}
{/* Messages */}
{session.messages.length === 0 ? (

Send a message to start the conversation

Using {pattern.name} in{' '} {project.name}

) : (
{session.messages.map((message) => { const isUser = message.role === 'user'; return (
{isUser ? : }
{message.authorName}
{message.content} {message.pending && message.content && ( )}
{message.pending && !message.content && }
); })}
{isBusy && activityRows.length > 0 && (
Agent activity
{activityRows.map((row) => (
{row.agentName}
{formatAgentActivityLabel(row.activity)}
))}
)}
)}
{/* Input area */}
{session.lastError && (
{session.lastError}
)}