diff --git a/ui/src/pages/Invocations.tsx b/ui/src/pages/Invocations.tsx index 1eaf56c..05b3d70 100644 --- a/ui/src/pages/Invocations.tsx +++ b/ui/src/pages/Invocations.tsx @@ -1,6 +1,5 @@ /* eslint-disable react/jsx-no-bind */ import React, { useCallback, useMemo, useState } from "react"; -import ResizablePanels from "../components/ResizablePanels"; import { Badge, Box, @@ -12,7 +11,6 @@ import { Flex, FormControl, FormLabel, - Heading, HStack, Icon, IconButton, @@ -35,6 +33,7 @@ import { Tbody, Td, Text, + type TextProps, Textarea, Th, Thead, @@ -45,6 +44,7 @@ import { AlertDescription, AlertIcon, useToast, + Divider, } from "@chakra-ui/react"; import { Select } from "chakra-react-select"; import { @@ -138,6 +138,18 @@ const loadPendingInvocations = async (): Promise => { } }; +const Header: React.FC = ({ children, ...props }) => ( + + {children} + +); + const EventRow: React.FC<{ event: InvocationEvent }> = ({ event }) => { const [showDetails, setShowDetails] = useState(false); const hasData = event.data != null; @@ -276,7 +288,10 @@ const Invocations: React.FC = () => { } for (const binding of agent.claude_managed_agents ?? []) { if (!map.has(binding.claude_agent_id)) { - map.set(binding.claude_agent_id, { cuid: agent.cuid, name: agent.name }); + map.set(binding.claude_agent_id, { + cuid: agent.cuid, + name: agent.name, + }); } } } @@ -285,12 +300,11 @@ const Invocations: React.FC = () => { const resolvedSelectedId = useMemo(() => { if (detailClosed) return null; - if (items.length === 0) return null; - if (!selectedId) return items[0].invocation_id; + if (!selectedId) return null; const stillSelected = items.some( (item) => item.invocation_id === selectedId, ); - return stillSelected ? selectedId : items[0].invocation_id; + return stillSelected ? selectedId : null; }, [detailClosed, items, selectedId]); const { data: detail, isLoading: detailLoading } = @@ -380,6 +394,30 @@ const Invocations: React.FC = () => { setSelectedId(null); }; + const selectInvocation = (inv: Invocation) => { + setDetailClosed(false); + setSelectedId(inv.invocation_id); + if (resolvedSelectedId !== inv.invocation_id) { + setShowDenyInput(false); + setShowArgsJson(false); + setDenyMessage(""); + resetRuleForm(inv.server_name, inv.tool_name); + resetCreateRuleForm(inv.server_name, inv.tool_name); + } + }; + + const selectedIndex = items.findIndex( + (item) => item.invocation_id === resolvedSelectedId, + ); + const hasPrev = selectedIndex > 0; + const hasNext = selectedIndex >= 0 && selectedIndex < items.length - 1; + + const handleNavigate = (direction: -1 | 1) => { + if (selectedIndex === -1) return; + const nextInvocation = items[selectedIndex + direction]; + if (nextInvocation) selectInvocation(nextInvocation); + }; + const handleClearInvocations = () => { if (items.length === 0) return; setClearedInvocationIds((ids) => { @@ -522,1109 +560,1034 @@ const Invocations: React.FC = () => { borderRadius="md" overflow="hidden" minH={0}> - - - - - {showFilters ? ( - + + + + {showFilters ? ( + setShowFilters(false)} + aria-label="Close filters" + title="Close filters" + data-testid="invocations-filter-toggle" + /> + ) : ( + + )} + + + + + + + Server + + setShowFilters(false)} - aria-label="Close filters" - title="Close filters" - data-testid="invocations-filter-toggle" + placeholder="Any" + value={draftFilters.server} + onChange={(e) => + setDraftFilters((f) => ({ + ...f, + server: e.target.value, + })) + } /> - ) : ( - - )} - - - - - - - Server - - - setDraftFilters((f) => ({ - ...f, - server: e.target.value, - })) - } - /> - - - - Tool - - - setDraftFilters((f) => ({ - ...f, - tool: e.target.value, - })) - } - /> - - - - Agent - - - setDraftFilters((f) => ({ - ...f, - client_name: e.target.value, - })) - } - /> - - - - - - Status - - + setDraftFilters((f) => ({ + ...f, + client_name: e.target.value, + })) + } + /> + + + + + + Status + +