import React, { useState } from 'react'; import { AlertCircle, ChevronRight, CheckCircle2, XCircle, Terminal, Wrench } from 'lucide-react'; import { cn } from '@/lib/utils'; import { Button } from './ui/button'; import { Checkbox } from './ui/checkbox'; import type { ApprovalEvent } from './ToolConfirmationHandler'; import type { JSONSchema7 } from 'json-schema'; import { ApprovalType } from '@dexto/core'; interface ApprovalTimelineProps { approval: ApprovalEvent; onApprove: (formData?: Record, rememberChoice?: boolean) => void; onDeny: () => void; } export function ApprovalTimeline({ approval, onApprove, onDeny }: ApprovalTimelineProps) { const [expanded, setExpanded] = useState(false); const [formData, setFormData] = useState>({}); const [formErrors, setFormErrors] = useState>({}); const [rememberChoice, setRememberChoice] = useState(false); const updateFormField = (fieldName: string, value: unknown) => { setFormData((prev) => ({ ...prev, [fieldName]: value })); if (formErrors[fieldName]) { setFormErrors((prev) => { const newErrors = { ...prev }; delete newErrors[fieldName]; return newErrors; }); } }; const handleApprove = () => { if (approval.type === ApprovalType.ELICITATION) { const { schema } = approval.metadata; const required = (schema.required as string[]) || []; const errors: Record = {}; for (const fieldName of required) { const value = formData[fieldName]; const isEmptyString = typeof value === 'string' && value.trim() === ''; if (value === undefined || value === null || isEmptyString) { errors[fieldName] = 'This field is required'; } } if (Object.keys(errors).length > 0) { setFormErrors(errors); return; } onApprove(formData); } else { onApprove(undefined, rememberChoice); } }; // Generate display info based on approval type const getDisplayInfo = () => { let summary = ''; let displayName = ''; let source = ''; if (approval.type === ApprovalType.COMMAND_CONFIRMATION) { displayName = 'bash'; summary = 'Command requires approval'; source = 'system'; } else if (approval.type === ApprovalType.TOOL_CONFIRMATION) { const toolName = approval.metadata.toolName; if (toolName.startsWith('mcp__')) { displayName = toolName.substring(5); source = 'mcp'; } else { displayName = toolName; } summary = `Tool requires approval`; } else if (approval.type === ApprovalType.ELICITATION) { displayName = approval.metadata.serverName || 'Agent'; summary = 'Information requested'; source = 'input'; } return { summary, displayName, source }; }; const { summary, displayName, source } = getDisplayInfo(); const renderFormField = (fieldName: string, fieldSchema: JSONSchema7, isRequired: boolean) => { const fieldType = fieldSchema.type || 'string'; const fieldValue = formData[fieldName]; const hasError = !!formErrors[fieldName]; const label = fieldSchema.title || fieldName; if (fieldType === 'boolean') { return (
updateFormField(fieldName, checked === true) } />
{fieldSchema.description && (

{fieldSchema.description}

)}
); } if (fieldType === 'number' || fieldType === 'integer') { return (
{fieldSchema.description && (

{fieldSchema.description}

)} { const raw = e.target.value; const nextValue = raw === '' ? undefined : Number(raw); updateFormField(fieldName, nextValue); }} className={cn( 'w-full px-2 py-1.5 border rounded text-xs bg-background', hasError ? 'border-red-500' : 'border-border' )} placeholder={isRequired ? 'Required' : 'Optional'} /> {hasError && (

{formErrors[fieldName]}

)}
); } if (fieldSchema.enum && Array.isArray(fieldSchema.enum)) { return (
{fieldSchema.description && (

{fieldSchema.description}

)} {hasError && (

{formErrors[fieldName]}

)}
); } return (
{fieldSchema.description && (

{fieldSchema.description}

)} updateFormField(fieldName, e.target.value)} className={cn( 'w-full px-2 py-1.5 border rounded text-xs bg-background', hasError ? 'border-red-500' : 'border-border' )} placeholder={isRequired ? 'Required' : 'Optional'} /> {hasError &&

{formErrors[fieldName]}

}
); }; return (
{/* Timeline column */}
{/* Status indicator with pulse */}
{/* Vertical line */}
{/* Content */}
{/* Summary line */}
{/* Tool/command name */}
{displayName}
{/* Inline action buttons (when not expanded) */} {!expanded && approval.type !== ApprovalType.ELICITATION && (
)} {/* For elicitation, always show expanded since form is required */} {approval.type === ApprovalType.ELICITATION && !expanded && ( )}
{/* Expanded details */} {expanded && (
{/* Command confirmation */} {approval.type === ApprovalType.COMMAND_CONFIRMATION && ( <>
Command
                                    {approval.metadata.command}
                                
This command may modify your system.
)} {/* Tool confirmation */} {approval.type === ApprovalType.TOOL_CONFIRMATION && ( <>
{approval.metadata.toolName}
{approval.metadata.description && (

{approval.metadata.description}

)}

Arguments

{Object.entries(approval.metadata.args || {}).map( ([key, value]) => (
{key}: {typeof value === 'string' ? value : typeof value === 'object' ? JSON.stringify(value) : String(value)}
) )}
setRememberChoice(checked === true) } />
)} {/* Elicitation (form) */} {approval.type === ApprovalType.ELICITATION && ( <>

{approval.metadata.prompt}

{(() => { const { schema } = approval.metadata; if ( !schema?.properties || typeof schema.properties !== 'object' ) { return (

Invalid form schema

); } const required = (schema.required as string[]) || []; const properties = schema.properties; return Object.entries(properties).map( ([fieldName, fieldSchema]) => { const isRequired = required.includes(fieldName); return renderFormField( fieldName, fieldSchema as JSONSchema7, isRequired ); } ); })()}
)} {/* Action buttons (expanded view) */}
)}
); }