import React, { useCallback, useEffect, useRef, useState } from 'react'; import { getTeamColorSet } from '@renderer/constants/teamColors'; import { useStore } from '@renderer/store'; import { FileText, Search, Terminal } from 'lucide-react'; import type { ToolApprovalRequest } from '@shared/types'; // --------------------------------------------------------------------------- // Tool icon mapping // --------------------------------------------------------------------------- function getToolIcon(toolName: string): React.ReactNode { const cls = 'size-4 shrink-0'; switch (toolName) { case 'Bash': return ; case 'Read': case 'Edit': case 'Write': case 'NotebookEdit': return ; case 'Grep': case 'Glob': return ; default: return ; } } // --------------------------------------------------------------------------- // Smart input preview // --------------------------------------------------------------------------- function renderToolInput(toolName: string, input: Record): string { switch (toolName) { case 'Bash': return typeof input.command === 'string' ? input.command : JSON.stringify(input, null, 2); case 'Edit': case 'Read': case 'Write': case 'NotebookEdit': return typeof input.file_path === 'string' ? input.file_path : JSON.stringify(input, null, 2); case 'Grep': case 'Glob': return typeof input.pattern === 'string' ? input.pattern : JSON.stringify(input, null, 2); default: return JSON.stringify(input, null, 2); } } // --------------------------------------------------------------------------- // Elapsed timer hook // --------------------------------------------------------------------------- function useElapsed(receivedAt: string): number { const [elapsed, setElapsed] = useState(() => Math.max(0, Math.floor((Date.now() - new Date(receivedAt).getTime()) / 1000)) ); useEffect(() => { setElapsed(Math.max(0, Math.floor((Date.now() - new Date(receivedAt).getTime()) / 1000))); const id = setInterval(() => { setElapsed(Math.max(0, Math.floor((Date.now() - new Date(receivedAt).getTime()) / 1000))); }, 1000); return () => clearInterval(id); }, [receivedAt]); return elapsed; } // --------------------------------------------------------------------------- // Main component // --------------------------------------------------------------------------- export const ToolApprovalSheet: React.FC = () => { const pendingApprovals = useStore((s) => s.pendingApprovals); const respondToToolApproval = useStore((s) => s.respondToToolApproval); const teams = useStore((s) => s.teams); const current: ToolApprovalRequest | undefined = pendingApprovals[0]; const containerRef = useRef(null); const [disabled, setDisabled] = useState(false); // Auto-focus when new approval arrives useEffect(() => { if (current && containerRef.current) { containerRef.current.focus(); } }, [current?.requestId]); // eslint-disable-line react-hooks/exhaustive-deps const handleRespond = useCallback( (allow: boolean) => { if (!current || disabled) return; setDisabled(true); void respondToToolApproval(current.teamName, current.runId, current.requestId, allow).finally( () => { setTimeout(() => setDisabled(false), 200); } ); }, [current, disabled, respondToToolApproval] ); const handleKeyDown = useCallback( (e: React.KeyboardEvent) => { if (e.key === 'Enter') { e.preventDefault(); handleRespond(true); } else if (e.key === 'Escape') { e.preventDefault(); handleRespond(false); } }, [handleRespond] ); if (!current) return null; const teamSummary = teams.find((t) => t.teamName === current.teamName); const teamColor = teamSummary?.color ? getTeamColorSet(teamSummary.color) : null; return (
{/* Header */}
{getToolIcon(current.toolName)} {current.toolName}
{teamColor ? ( {teamSummary?.displayName ?? current.teamName} ) : ( {current.teamName} )}
{/* Tool input preview */}
          {renderToolInput(current.toolName, current.toolInput)}
        
{/* Actions */}
{pendingApprovals.length > 1 && ( {pendingApprovals.length - 1} pending )}
); }; // --------------------------------------------------------------------------- // Elapsed display sub-component (uses hook) // --------------------------------------------------------------------------- function ElapsedDisplay({ receivedAt }: { receivedAt: string }): React.JSX.Element { const elapsed = useElapsed(receivedAt); return ( {elapsed}s ); }