import { html, useEffect, useRef } from '../lib/preact.js'; import { getStatusMeta } from '../utils/status.js'; import { formatDuration, getContextUsageSummary } from '../utils/formatting.js'; import { ChatMessages } from './ChatMessages.js'; import { QuestionBlock } from './QuestionBlock.js'; import { SimpleInput } from './SimpleInput.js'; export function SessionCard({ session, onClick, conversation, onFetchConversation, onRespond, onDismiss, enlarged = false }) { const hasQuestions = session.pending_questions && session.pending_questions.length > 0; const statusMeta = getStatusMeta(session.status); const agent = session.agent === 'codex' ? 'codex' : 'claude'; const agentHeaderClass = agent === 'codex' ? 'agent-header-codex' : 'agent-header-claude'; const contextUsage = getContextUsageSummary(session.context_usage); // Fetch conversation when card mounts useEffect(() => { if (!conversation && onFetchConversation) { onFetchConversation(session.session_id, session.project_dir, agent); } }, [session.session_id, session.project_dir, agent, conversation, onFetchConversation]); const chatPaneRef = useRef(null); const wasAtBottomRef = useRef(true); const prevConversationLenRef = useRef(0); // Track scroll position for smart scrolling useEffect(() => { const el = chatPaneRef.current; if (!el) return; const handleScroll = () => { const threshold = 50; wasAtBottomRef.current = el.scrollHeight - el.scrollTop - el.clientHeight < threshold; }; el.addEventListener('scroll', handleScroll); return () => el.removeEventListener('scroll', handleScroll); }, []); // Smart scroll: only scroll to bottom on new messages if user was already at bottom useEffect(() => { const el = chatPaneRef.current; if (!el || !conversation) return; const hasNewMessages = conversation.length > prevConversationLenRef.current; prevConversationLenRef.current = conversation.length; if (hasNewMessages && wasAtBottomRef.current) { el.scrollTop = el.scrollHeight; } }, [conversation]); const handleDismissClick = (e) => { e.stopPropagation(); if (onDismiss) onDismiss(session.session_id); }; // Container classes differ based on enlarged mode const containerClasses = enlarged ? 'glass-panel flex w-full max-w-[90vw] max-h-[90vh] flex-col overflow-hidden rounded-2xl border border-selection/80' : 'glass-panel flex h-[850px] max-h-[850px] w-[600px] cursor-pointer flex-col overflow-hidden rounded-xl border border-selection/70 transition-[border-color,box-shadow] duration-200 hover:border-starting/35 hover:shadow-panel'; return html`
onClick && onClick(session)} >
${session.project || session.name || 'Session'}
${statusMeta.label} ${agent} ${session.cwd && html` ${session.cwd.split('/').slice(-2).join('/')} `}
${contextUsage && html`
${contextUsage.headline} ${contextUsage.detail} ${contextUsage.trail && html`${contextUsage.trail}`}
`}
${formatDuration(session.started_at)} ${session.status === 'done' && html` `}
<${ChatMessages} messages=${conversation || []} status=${session.status} limit=${enlarged ? null : 20} />
${hasQuestions ? html` <${QuestionBlock} questions=${session.pending_questions} sessionId=${session.session_id} status=${session.status} onRespond=${onRespond} /> ` : html` <${SimpleInput} sessionId=${session.session_id} status=${session.status} onRespond=${onRespond} /> `}
`; }