import { memo, useMemo, useState, type CSSProperties, type ReactNode, type Ref, type RefObject } from "react"; import { GitFork } from "lucide-react"; import { ScrollArea } from "@tutti-os/ui-system/components"; import type { AgentActivitySessionForkLineage } from "@tutti-os/agent-activity-core"; import type { AgentMessageMarkdownWorkspaceAppIcon } from "../../../shared/AgentMessageMarkdown"; import type { AgentConversationVM } from "../../../shared/agentConversation/contracts/agentConversationVM"; import type { AgentGUIProviderSkillOption } from "../model/agentGuiNodeTypes"; import type { WorkspaceLinkAction } from "../../../actions/workspaceLinkActions"; import { AgentGUIConversationTimelinePane } from "./AgentGUIConversationTimelinePane"; import styles from "../AgentGUINode.styles"; import type { AgentTranscriptVirtualScrollController } from "../../../shared/agentConversation/components/AgentTranscriptView"; import type { AgentConversationFollowEndMode } from "../../../shared/agentConversation/agentConversationFollowEndController"; import type { AgentTranscriptEditRetryControl } from "../../../shared/agentConversation/components/useAgentTranscriptEditRetryProjection"; import type { AgentActivityEditRetryRecoveryAction } from "@tutti-os/agent-activity-core"; import type { AgentGUIEditRetryPresentation } from "../model/agentGUIEditRetryModel"; import { AgentGUIEditRetryStatus } from "./AgentGUIEditRetryStatus"; import { AgentGUITextSelectionActions, readAgentGUITextSelection, type AgentGUITextSelectionSnapshot } from "./AgentGUITextSelectionActions"; const TIMELINE_CONTENT_STYLE: CSSProperties = { width: "100%", minWidth: "100%", display: "grid", gridTemplateColumns: "minmax(0, 1fr)", gap: "24px" }; interface AgentGUIDetailTimelineProps { availableSkills: readonly AgentGUIProviderSkillOption[]; conversation: AgentConversationVM | null; editRetry?: { control?: AgentTranscriptEditRetryControl; presentation: AgentGUIEditRetryPresentation; recover: (action: AgentActivityEditRetryRecoveryAction) => Promise; }; conversationFlowEmpty: React.JSX.Element; conversationFlowLabels: { thinkingLabel: string; toolCallsLabel: (count: number) => string; processing: string; turnSummary: string; userMessageLocator: string; }; hasActiveConversation: boolean; followEndMode: AgentConversationFollowEndMode; forkedFrom?: AgentActivitySessionForkLineage | null; forkThroughTurnPendingTurnIds?: readonly string[]; homeContent: ReactNode; isLoadingOlderMessages: boolean; isVisible: boolean; isTimelineScrolledToTop: boolean; labels: { loadingConversation: string; continuedFromTask: string; selectionAddToConversation: string; selectionAskInSide: string; }; onAuthLogin?: (provider?: string | null) => void; onForkThroughTurn?: (turnId: string) => void; onOpenForkSourceSession?: (agentSessionId: string) => void; onLinkAction?: (action: WorkspaceLinkAction) => void; onAddSelectionToConversation: (text: string) => void; onAskSelectionInSide?: (text: string) => void; textSelectionActionsEnabled?: boolean; showTimelineSkeleton: boolean; showUnavailableChatEmpty: boolean; timelineContentRef: RefObject; timelineRef: RefObject; virtualScrollControllerRef: Ref; workspaceAppIcons: readonly AgentMessageMarkdownWorkspaceAppIcon[]; } export const AgentGUIDetailTimeline = memo(function AgentGUIDetailTimeline({ availableSkills, conversation, editRetry, conversationFlowEmpty, conversationFlowLabels, hasActiveConversation, followEndMode, forkedFrom, forkThroughTurnPendingTurnIds, homeContent, isLoadingOlderMessages, isVisible, isTimelineScrolledToTop, labels, onAuthLogin, onForkThroughTurn, onOpenForkSourceSession, onLinkAction, onAddSelectionToConversation, onAskSelectionInSide, textSelectionActionsEnabled = true, showTimelineSkeleton, showUnavailableChatEmpty, timelineContentRef, timelineRef, virtualScrollControllerRef, workspaceAppIcons }: AgentGUIDetailTimelineProps): React.JSX.Element { "use memo"; const [textSelection, setTextSelection] = useState(null); const forkLineageAttachments = useMemo( () => forkedFrom?.targetTurnId ? [ { id: `fork-lineage:${forkedFrom.operationId}`, anchorTurnId: forkedFrom.targetTurnId, missingAnchorBehavior: "hide" as const, content: (
) } ] : [], [ forkedFrom?.operationId, forkedFrom?.sourceAgentSessionId, forkedFrom?.targetTurnId, labels.continuedFromTask, onOpenForkSourceSession ] ); return ( <> setTextSelection(readAgentGUITextSelection(event.currentTarget)), onPointerDown: () => setTextSelection(null), onScroll: () => setTextSelection(null) }} > {hasActiveConversation ? ( <> {editRetry ? ( ) : null} ) : ( homeContent )} {textSelectionActionsEnabled && hasActiveConversation && textSelection && timelineRef.current ? ( setTextSelection(null)} /> ) : null} ); });