import { AgentRun } from "@vertesia/common"; import { Button, Dropdown, MenuGroup, MenuItem, cn, useToast } from "@vertesia/ui/core"; import { useUserSession } from "@vertesia/ui/session"; import { Bot, ClipboardList, CopyIcon, DownloadCloudIcon, ExternalLink, GitFork, InfoIcon, MoreVertical, RefreshCcw, XIcon } from "lucide-react"; import { useUITranslation } from '../../../../i18n/index.js'; import { PayloadBuilderProvider, usePayloadBuilder } from "../../PayloadBuilder"; import { type AgentConversationViewMode } from "./AllMessagesMixed"; import { getConversationUrl } from "./utils"; export interface HeaderProps { title: string; isCompleted: boolean; /** Workflow is in a terminal state (completed/failed/cancelled) — not just idle */ isTerminal?: boolean; onClose?: () => void; isModal: boolean; agentRunId: string; workflowRunId: string; viewMode: AgentConversationViewMode; onViewModeChange: (mode: AgentConversationViewMode) => void; showPlanPanel: boolean; hasPlan?: boolean; showPlanButton?: boolean; onTogglePlanPanel: () => void; onDownload?: () => void; // onCopyRunId?: () => void; resetWorkflow?: () => void; onExportPdf?: () => void; /** Called to show run details/internals modal */ onShowDetails?: () => void; /** Called after a restart succeeds — receives the new AgentRun */ onRestart?: (newRun: AgentRun) => void; /** Called after a clone succeeds — receives the new AgentRun */ onClone?: (newRun: AgentRun) => void; /** Show green indicator when receiving streaming chunks */ isReceivingChunks?: boolean; /** Additional className for the outer container */ className?: string; } export default function Header({ title, isTerminal = false, onClose, isModal, agentRunId, workflowRunId, viewMode, onViewModeChange, showPlanPanel, hasPlan = false, showPlanButton = true, onTogglePlanPanel, onDownload, // onCopyRunId, resetWorkflow, onExportPdf, onShowDetails, onRestart, onClone, isReceivingChunks = false, className, }: HeaderProps) { const { t } = useUITranslation(); const continueWorkflow = useContinueWorkflow(agentRunId, onRestart); return (
{title}
(Agent Run ID: {agentRunId}) {/* Streaming chunk indicator - gray when idle, purple when receiving */}
{/* View Mode Toggle */}
{showPlanButton && (
{/* Notification badge when plan is available but hidden */} {hasPlan && !showPlanPanel && ( )}
)} {onRestart && isTerminal && ( )} {/* More actions */} {onClose && !isModal && ( )}
); } function useContinueWorkflow(agentRunId: string, onRestart?: (newRun: AgentRun) => void) { const { t } = useUITranslation(); const toast = useToast(); const { client } = useUserSession(); return async () => { try { const newRun = await client.agents.restart(agentRunId); toast({ status: "success", title: t('agent.conversationContinued'), duration: 2000, }); onRestart?.(newRun); } catch (_error) { toast({ status: "error", title: t('agent.failedToContinueConversation'), duration: 2000, }); } }; } function MoreDropdown({ agentRunId, workflowRunId, isModal, isTerminal, onClose, onDownload, resetWorkflow, onExportPdf, onShowDetails, onRestart, onClone, }: { agentRunId: string; workflowRunId: string; isModal: boolean; isTerminal: boolean; onClose?: () => void; onDownload?: () => void; onCopyRunId?: () => void; resetWorkflow?: () => void; onExportPdf?: () => void; onShowDetails?: () => void; onRestart?: (newRun: AgentRun) => void; onClone?: (newRun: AgentRun) => void; }) { const { t } = useUITranslation(); const toast = useToast(); const { client } = useUserSession(); const builder = usePayloadBuilder(); const continueWorkflow = useContinueWorkflow(agentRunId, onRestart); const cancelWorkflow = async () => { try { await client.agents.terminate(agentRunId, "cancel"); toast({ status: "success", title: t('agent.workflowCancelled'), duration: 2000, }); builder.reset(); resetWorkflow?.(); return true; } catch (_error) { toast({ status: "error", title: t('agent.failedToCancelWorkflow'), duration: 2000, }); return false; } }; const cloneWorkflow = async () => { try { const newRun = await client.agents.fork(agentRunId); toast({ status: "success", title: t('agent.conversationCloned'), duration: 2000, }); onClone?.(newRun); } catch (_error) { toast({ status: "error", title: t('agent.failedToCloneConversation'), duration: 2000, }); } }; const openUrl = (url: string) => { window.open(url, "_blank"); return url; } const copyAgentRunId = () => { navigator.clipboard.writeText(agentRunId); toast({ status: "success", title: t('agent.agentRunIdCopied'), duration: 2000, }); }; const copyWorkflowRunId = () => { navigator.clipboard.writeText(workflowRunId); toast({ status: "success", title: t('agent.workflowRunIdCopied'), duration: 2000, }); }; return ( } > {isModal && ( openUrl(`/store/agent-runner/${agentRunId}`)}> {t('agent.openInNewTab')} )} {t('agent.copyAgentRunId')} {t('agent.copyWorkflowRunId')} {onShowDetails && ( {t('agent.details')} )} { if (onDownload) { onDownload(); } else { getConversationUrl(client, agentRunId).then((r) => window.open(r, "_blank")); } }}> {t('agent.downloadConversation')} {onExportPdf && ( {t('agent.exportAsPdf')} )} {onClose && isModal && ( {t('agent.close')} )} {onRestart && isTerminal && ( {t('agent.continueConversation')} )} {onClone && ( {t('agent.cloneConversation')} )} {!isTerminal && ( {t('agent.cancelWorkflow')} )} ); }