Skip to main content

adk_ui/tools/
render_card.rs

1use crate::schema::*;
2use crate::tools::{LegacyProtocolOptions, render_ui_response_with_protocol};
3use adk_core::{Result, Tool, ToolContext};
4use async_trait::async_trait;
5use schemars::JsonSchema;
6use serde::{Deserialize, Serialize};
7use serde_json::Value;
8use std::sync::Arc;
9
10/// Parameters for the render_card tool
11#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
12pub struct RenderCardParams {
13    /// Title of the card
14    pub title: String,
15    /// Optional description/subtitle
16    #[serde(default)]
17    pub description: Option<String>,
18    /// Main content text (supports markdown-like formatting)
19    pub content: String,
20    /// Optional action buttons
21    #[serde(default)]
22    pub actions: Vec<CardAction>,
23    /// Optional protocol output configuration.
24    #[serde(flatten)]
25    pub protocol: LegacyProtocolOptions,
26}
27
28#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
29pub struct CardAction {
30    /// Button label
31    pub label: String,
32    /// Action ID triggered when clicked
33    pub action_id: String,
34    /// Button variant: primary, secondary, danger, ghost
35    #[serde(default = "default_variant")]
36    pub variant: String,
37}
38
39fn default_variant() -> String {
40    "primary".to_string()
41}
42
43/// Tool for rendering information cards.
44///
45/// Creates styled card components to display content with optional action buttons.
46/// Cards are ideal for status updates, summaries, or any structured information.
47///
48/// # Example JSON Parameters
49///
50/// ```json
51/// {
52///   "title": "Welcome",
53///   "description": "Getting started with your account",
54///   "content": "Your account has been created successfully. Click below to continue.",
55///   "actions": [
56///     { "label": "Get Started", "action_id": "start", "variant": "primary" }
57///   ]
58/// }
59/// ```
60pub struct RenderCardTool;
61
62impl RenderCardTool {
63    pub fn new() -> Self {
64        Self
65    }
66}
67
68impl Default for RenderCardTool {
69    fn default() -> Self {
70        Self::new()
71    }
72}
73
74#[async_trait]
75impl Tool for RenderCardTool {
76    fn name(&self) -> &str {
77        "render_card"
78    }
79
80    fn description(&self) -> &str {
81        r#"Render an information card. Output example:
82┌─────────────────────────────┐
83│ Welcome                     │
84│ Your account is ready       │
85│ ─────────────────────────── │
86│ Click below to get started. │
87│      [Get Started]          │
88└─────────────────────────────┘
89Use for status updates, summaries, or any structured info with optional action buttons."#
90    }
91
92    fn parameters_schema(&self) -> Option<Value> {
93        Some(super::generate_gemini_schema::<RenderCardParams>())
94    }
95
96    async fn execute(&self, _ctx: Arc<dyn ToolContext>, args: Value) -> Result<Value> {
97        let params: RenderCardParams = serde_json::from_value(args)
98            .map_err(|e| adk_core::AdkError::Tool(format!("Invalid parameters: {}", e)))?;
99        let protocol_options = params.protocol.clone();
100
101        // Build card content
102        let content = vec![Component::Text(Text {
103            id: None,
104            content: params.content,
105            variant: TextVariant::Body,
106        })];
107
108        // Build footer with action buttons
109        let footer = if params.actions.is_empty() {
110            None
111        } else {
112            Some(
113                params
114                    .actions
115                    .into_iter()
116                    .map(|action| {
117                        let variant = match action.variant.as_str() {
118                            "secondary" => ButtonVariant::Secondary,
119                            "danger" => ButtonVariant::Danger,
120                            "ghost" => ButtonVariant::Ghost,
121                            "outline" => ButtonVariant::Outline,
122                            _ => ButtonVariant::Primary,
123                        };
124                        Component::Button(Button {
125                            id: None,
126                            label: action.label,
127                            action_id: action.action_id,
128                            variant,
129                            disabled: false,
130                            icon: None,
131                        })
132                    })
133                    .collect(),
134            )
135        };
136
137        let ui = UiResponse::new(vec![Component::Card(Card {
138            id: None,
139            title: Some(params.title),
140            description: params.description,
141            content,
142            footer,
143        })]);
144
145        render_ui_response_with_protocol(ui, &protocol_options, "card")
146    }
147}