| import 'package:jaspr/dom.dart'; |
| import 'package:jaspr/jaspr.dart'; |
| |
| import '../../../utils/styles.dart'; |
| |
| import '../models/agent_tool_event.dart'; |
| import '../models/agent_tool_event_presenter.dart'; |
| |
| /// A component that renders a single row describing a tool call executed by the agent. |
| /// |
| /// Displays the action description in either running or finished state. |
| /// For screenshot tool calls, also displays a thumbnail of the captured image. |
| class AgentToolCallRow extends StatelessComponent { |
| /// Creates an [AgentToolCallRow] component. |
| const AgentToolCallRow({required this.event, super.key}); |
| |
| /// The tool execution event details to render. |
| final AgentToolEvent event; |
| |
| @override |
| Component build(BuildContext context) { |
| final presentation = AgentToolEventPresenter.present(event); |
| final isRunning = event.status == ToolStatus.running; |
| final screenshot = event.screenshotBase64; |
| |
| return div( |
| classes: 'agent-tool-call-row${isRunning ? ' running' : ' finished'}', |
| attributes: { |
| 'role': 'status', |
| 'aria-label': presentation.ariaLabel, |
| }, |
| [ |
| span( |
| classes: 'agent-tool-call-text', |
| [Component.text(presentation.actionText)], |
| ), |
| if (screenshot != null && !isRunning) |
| div(classes: 'agent-tool-screenshot-container', [ |
| img( |
| classes: 'agent-tool-screenshot-thumb', |
| src: 'data:image/png;base64,$screenshot', |
| attributes: { |
| 'alt': 'Screenshot of ${event.summary}', |
| }, |
| ), |
| ]), |
| ], |
| ); |
| } |
| |
| @css |
| static List<StyleRule> get styles => [ |
| css('.agent-tool-call-row').styles( |
| display: .flex, |
| padding: Padding.symmetric(vertical: 4.px, horizontal: 0.px), |
| margin: Margin.zero, |
| flexDirection: .column, |
| alignItems: .start, |
| gap: Gap.all(8.px), |
| color: colorOnSurface, |
| fontSize: 12.px, |
| ), |
| css('.agent-tool-call-row.running').styles( |
| raw: { |
| 'animation': 'agent-tool-pulse 1.5s ease-in-out infinite', |
| }, |
| ), |
| css('@keyframes agent-tool-pulse').styles( |
| raw: { |
| '0%': 'opacity: 0.6;', |
| '50%': 'opacity: 1.0;', |
| '100%': 'opacity: 0.6;', |
| }, |
| ), |
| css('.agent-tool-call-text').styles( |
| fontFamily: const .list([FontFamily('Inter'), FontFamilies.sansSerif]), |
| ), |
| css('.agent-tool-screenshot-container').styles( |
| display: .flex, |
| padding: Padding.all(4.px), |
| border: Border.all(color: colorBorder, width: 1.px), |
| radius: .circular(6.px), |
| overflow: .hidden, |
| backgroundColor: colorContainerLow, |
| ), |
| css('.agent-tool-screenshot-thumb').styles( |
| display: .block, |
| radius: .circular(4.px), |
| raw: { |
| 'max-width': '240px', |
| 'max-height': '180px', |
| 'object-fit': 'contain', |
| }, |
| ), |
| ]; |
| } |