"use client"; import { Check, ChevronRight, Copy, RotateCcw, Undo2 } from "lucide-react"; import { useState } from "react"; import { toast } from "sonner"; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger, } from "@/components/ui/alert-dialog"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { getToolDisplayName, getToolIcon } from "@/contracts/enums/toolIcons"; import { type AgentAction, agentActionsApiService } from "@/lib/apis/agent-actions-api.service"; import { AppError } from "@/lib/error"; import { formatRelativeDate } from "@/lib/format-date"; import { cn } from "@/lib/utils"; interface ActionLogItemProps { action: AgentAction; threadId: number; onRevertSuccess: () => void; } function formatPrimitiveValue(value: unknown) { if (value === null) return "null"; if (value === undefined) return "undefined"; if (typeof value === "string") return value; if (typeof value === "number" || typeof value === "boolean") return String(value); return JSON.stringify(value, null, 2); } function ArgumentValue({ value }: { value: unknown }) { const formatted = formatPrimitiveValue(value); const isBlockValue = typeof value === "object" || (typeof value === "string" && (value.includes("\n") || value.length > 120)); if (isBlockValue) { return (
				{formatted}
			
); } return (

{formatted}

); } function StructuredArguments({ args }: { args: Record }) { return (
{Object.entries(args).map(([key, value]) => (

{key}

))}
); } export function ActionLogItem({ action, threadId, onRevertSuccess }: ActionLogItemProps) { const [isExpanded, setIsExpanded] = useState(false); const [isReverting, setIsReverting] = useState(false); const [confirmOpen, setConfirmOpen] = useState(false); const [copiedSection, setCopiedSection] = useState<"arguments" | null>(null); const isAlreadyReverted = action.reverted_by_action_id !== null; const isRevertAction = action.is_revert_action; const hasError = action.error !== null && action.error !== undefined; const Icon = getToolIcon(action.tool_name); const displayName = getToolDisplayName(action.tool_name); const argsPreview = action.args ? JSON.stringify(action.args, null, 2) : null; const canRevert = action.reversible && !isAlreadyReverted && !isRevertAction && !hasError; const handleCopyArguments = async () => { if (!argsPreview) return; try { await navigator.clipboard.writeText(argsPreview); setCopiedSection("arguments"); toast.success("Arguments copied"); window.setTimeout(() => setCopiedSection(null), 1200); } catch { toast.error("Failed to copy arguments."); } }; const handleRevert = async () => { setIsReverting(true); try { const response = await agentActionsApiService.revert(threadId, action.id); toast.success(response.message || "Action reverted successfully."); onRevertSuccess(); } catch (err) { const message = err instanceof AppError ? err.message : err instanceof Error ? err.message : "Failed to revert action."; toast.error(message); } finally { setIsReverting(false); setConfirmOpen(false); } }; return (
{isExpanded && (
{action.args && argsPreview && (

Arguments

)} {action.error && (

Error

								{JSON.stringify(action.error, null, 2)}
							
)} {action.reverse_descriptor && (

Reverse plan

								{JSON.stringify(action.reverse_descriptor, null, 2)}
							
)}

Action ID: {action.id}

{canRevert ? ( Revert this action? This will undo {displayName} and append a new audit entry. The agent's chat history is preserved — only the tool's effects on your knowledge base or connectors will be reversed where possible. Cancel { e.preventDefault(); handleRevert(); }} disabled={isReverting} className="bg-secondary text-secondary-foreground hover:bg-secondary/80 focus-visible:ring-0" > {isReverting ? "Reverting…" : "Revert"} ) : (
{isAlreadyReverted ? "Already reverted" : isRevertAction ? "Revert entry" : hasError ? "Cannot revert errored action" : "Not reversible"}
)}
)}
); }