import { useMemo, useState } from 'react'; import { DisplayItemList } from '@renderer/components/chat/DisplayItemList'; import { LastOutputDisplay } from '@renderer/components/chat/LastOutputDisplay'; import { SystemChatGroup } from '@renderer/components/chat/SystemChatGroup'; import { MarkdownViewer } from '@renderer/components/chat/viewers/MarkdownViewer'; import { Tooltip, TooltipContent, TooltipTrigger } from '@renderer/components/ui/tooltip'; import { enhanceAIGroup } from '@renderer/utils/aiGroupEnhancer'; import { transformChunksToConversation } from '@renderer/utils/groupTransformer'; import { extractAgentBlockContents, stripAgentBlocks } from '@shared/constants/agentBlocks'; import { format } from 'date-fns'; import { Bot, ChevronDown, ChevronRight } from 'lucide-react'; import type { EnhancedChunk } from '@renderer/types/data'; import type { AIGroup, UserGroup } from '@renderer/types/groups'; interface MemberExecutionLogProps { chunks: EnhancedChunk[]; memberName?: string; } type ExpandedItemIdsByGroup = Map>; export const MemberExecutionLog = ({ chunks, memberName, }: MemberExecutionLogProps): React.JSX.Element => { const conversation = useMemo(() => transformChunksToConversation(chunks, [], false), [chunks]); // Show newest groups first — most recent activity is most relevant in execution logs. const orderedItems = useMemo(() => [...conversation.items].reverse(), [conversation.items]); // Store collapsed groups instead of expanded: by default, everything is expanded. // This avoids resetting state in an effect when conversation changes. const [collapsedGroupIds, setCollapsedGroupIds] = useState>(new Set()); const [expandedItemIdsByGroup, setExpandedItemIdsByGroup] = useState( new Map() ); if (!orderedItems.length) { return (
Nothing to display
); } return (
{orderedItems.map((item) => { if (item.type === 'system') { return ; } if (item.type === 'user') { return ; } if (item.type === 'ai') { return ( { setCollapsedGroupIds((prev) => { const next = new Set(prev); if (next.has(item.group.id)) next.delete(item.group.id); else next.add(item.group.id); return next; }); }} onToggleItem={(itemId) => { setExpandedItemIdsByGroup((prev) => { const next = new Map(prev); const current = new Set(next.get(item.group.id) ?? []); if (current.has(itemId)) current.delete(itemId); else current.add(itemId); next.set(item.group.id, current); return next; }); }} /> ); } if (item.type === 'compact') { // Compact boundaries are useful in full session view but noisy here return null; } return null; })}
); }; /** Extract agent-only instruction blocks and human-visible text from a message. */ function splitAgentBlocks(raw: string): { humanText: string; agentInfo: string[] } { const agentInfo = extractAgentBlockContents(raw); const humanText = stripAgentBlocks(raw); return { humanText, agentInfo }; } const UserLogItem = ({ group }: { group: UserGroup }): React.JSX.Element => { const text = group.content.rawText ?? group.content.text ?? ''; const { humanText, agentInfo } = useMemo(() => splitAgentBlocks(text), [text]); const [agentInfoOpen, setAgentInfoOpen] = useState(false); if (!humanText && agentInfo.length === 0) { return (
{format(group.timestamp, 'h:mm:ss a')} — (empty)
); } return (
{format(group.timestamp, 'h:mm:ss a')}
{humanText && (
)} {agentInfo.length > 0 && (
{agentInfoOpen && (
              {agentInfo.join('\n\n')}
            
)}
)}
); }; interface AIExecutionGroupProps { group: AIGroup; memberName?: string; expanded: boolean; expandedItemIds: Set; onToggleExpanded: () => void; onToggleItem: (itemId: string) => void; } const AIExecutionGroup = ({ group, memberName, expanded, expandedItemIds, onToggleExpanded, onToggleItem, }: AIExecutionGroupProps): React.JSX.Element => { const enhanced = useMemo(() => { if (!memberName) { return enhanceAIGroup(group); } const normalized = memberName.trim().toLowerCase(); const filteredProcesses = group.processes.filter( (p) => p.team?.memberName?.toLowerCase() === normalized ); return enhanceAIGroup({ ...group, processes: filteredProcesses }); }, [group, memberName]); const groupLabel = memberName?.trim() ? `${memberName.trim()} turn` : 'Agent turn'; const hasToggleContent = enhanced.displayItems.length > 0; const visibleLastOutput = enhanced.lastOutput?.type === 'tool_result' && hasToggleContent ? null : enhanced.lastOutput; return (
{hasToggleContent ? ( {expanded ? 'Collapse' : 'Expand'} ) : null} {hasToggleContent && expanded ? (
) : null}
); };