import { memo, useCallback, useMemo, useRef } from 'react'; import { useAppTranslation } from '@features/localization/renderer'; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, } from '@renderer/components/ui/dialog'; import { CARD_ICON_MUTED } from '@renderer/constants/cssVariables'; import { getTeamColorSet } from '@renderer/constants/teamColors'; import { agentAvatarUrl, buildMemberAvatarMap } from '@renderer/utils/memberHelpers'; import { MemberBadge } from '../MemberBadge'; import { ActivityItem } from './ActivityItem'; import { buildMessageContext, resolveMessageRenderProps } from './activityMessageContext'; import { ThoughtBodyContent } from './ThoughtBodyContent'; import type { LeadThoughtGroup, TimelineItem } from './LeadThoughtsGroup'; import type { InboxMessage, ResolvedTeamMember } from '@shared/types'; function formatTime(timestamp: string): string { const d = new Date(timestamp); if (Number.isNaN(d.getTime())) return timestamp; return d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); } interface DialogThoughtsContentProps { group: LeadThoughtGroup; members?: ResolvedTeamMember[]; memberColor?: string; onTaskIdClick?: (taskId: string) => void; onReply?: (message: InboxMessage) => void; memberColorMap?: Map; teamNames?: string[]; teamColorByName?: ReadonlyMap; onTeamClick?: (teamName: string) => void; } const DialogThoughtsContent = ({ group, members, memberColor, onTaskIdClick, onReply, memberColorMap, teamNames = [], teamColorByName, onTeamClick, }: DialogThoughtsContentProps): React.JSX.Element => { const { t } = useAppTranslation('team'); const { thoughts } = group; const newest = thoughts[0]; const oldest = thoughts[thoughts.length - 1]; const colors = getTeamColorSet(memberColor ?? ''); const avatarMap = useMemo(() => buildMemberAvatarMap(members ?? []), [members]); const chronological = useMemo(() => [...thoughts].reverse(), [thoughts]); return (
{/* Header */}
{t('activity.thoughts.count', { count: thoughts.length })} {formatTime(oldest.timestamp) === formatTime(newest.timestamp) ? formatTime(oldest.timestamp) : `${formatTime(oldest.timestamp)}–${formatTime(newest.timestamp)}`}
{/* Body */}
{chronological.map((thought, idx) => ( 0} onTaskIdClick={onTaskIdClick} onReply={onReply} memberColorMap={memberColorMap} teamNames={teamNames} teamColorByName={teamColorByName} onTeamClick={onTeamClick} /> ))}
); }; interface MessageExpandDialogProps { expandedItem: TimelineItem | null; open: boolean; onOpenChange: (open: boolean) => void; teamName: string; members?: ResolvedTeamMember[]; onCreateTaskFromMessage?: (subject: string, description: string) => void; onReplyToMessage?: (message: InboxMessage) => void; onMemberClick?: (member: ResolvedTeamMember) => void; onTaskIdClick?: (taskId: string) => void; onRestartTeam?: () => void; teamNames?: string[]; teamColorByName?: ReadonlyMap; onTeamClick?: (teamName: string) => void; } export const MessageExpandDialog = memo(function MessageExpandDialog({ expandedItem, open, onOpenChange, teamName, members, onCreateTaskFromMessage, onReplyToMessage, onMemberClick, onTaskIdClick, onRestartTeam, teamNames = [], teamColorByName, onTeamClick, }: MessageExpandDialogProps): React.JSX.Element { const { t } = useAppTranslation('team'); // Keep last valid item for exit animation const lastItemRef = useRef(null); if (expandedItem) lastItemRef.current = expandedItem; const displayItem = expandedItem ?? lastItemRef.current; const ctx = useMemo(() => buildMessageContext(members), [members]); const handleMemberNameClick = useCallback( (name: string) => { const member = members?.find( (candidate) => candidate.name === name || candidate.agentType === name ); if (member) onMemberClick?.(member); }, [members, onMemberClick] ); const renderProps = displayItem?.type === 'message' ? resolveMessageRenderProps(displayItem.message, ctx) : null; const thoughtMemberColor = displayItem?.type === 'lead-thoughts' ? ctx.memberInfo.get(displayItem.group.thoughts[0].from)?.color : undefined; const headerTitle = displayItem?.type === 'message' ? displayItem.message.from : displayItem?.type === 'lead-thoughts' ? t('activity.thoughts.titleForMember', { name: displayItem.group.thoughts[0].from }) : ''; return ( {headerTitle} {t('activity.expandDialog.description')}
{displayItem?.type === 'message' ? ( ) : displayItem?.type === 'lead-thoughts' ? ( ) : null}
); });