Skip to main content

adk_ui/tools/
render_modal.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_modal tool
11#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
12pub struct RenderModalParams {
13    /// Modal title shown in the header
14    pub title: String,
15    /// Main message or content to display
16    pub message: String,
17    /// Modal size: small, medium, large, full
18    #[serde(default = "default_size")]
19    pub size: String,
20    /// Whether to show the close button
21    #[serde(default = "default_true")]
22    pub closable: bool,
23    /// Optional confirm button label
24    pub confirm_label: Option<String>,
25    /// Optional cancel button label
26    pub cancel_label: Option<String>,
27    /// Action ID for the confirm button
28    #[serde(default = "default_confirm_action")]
29    pub confirm_action: String,
30    /// Action ID for the cancel button
31    #[serde(default = "default_cancel_action")]
32    pub cancel_action: String,
33    /// Optional protocol output configuration.
34    #[serde(flatten)]
35    pub protocol: LegacyProtocolOptions,
36}
37
38fn default_size() -> String {
39    "medium".to_string()
40}
41
42fn default_true() -> bool {
43    true
44}
45
46fn default_confirm_action() -> String {
47    "modal_confirm".to_string()
48}
49
50fn default_cancel_action() -> String {
51    "modal_cancel".to_string()
52}
53
54/// Tool for rendering modal dialogs.
55///
56/// Creates overlay modal dialogs for focused interactions, confirmations,
57/// or important messages that require user attention.
58///
59/// # Example JSON Parameters
60///
61/// ```json
62/// {
63///   "title": "Confirm Action",
64///   "message": "Are you sure you want to proceed with this action?",
65///   "size": "medium",
66///   "confirm_label": "Yes, Proceed",
67///   "cancel_label": "Cancel",
68///   "confirm_action": "confirm_action",
69///   "cancel_action": "cancel_action"
70/// }
71/// ```
72pub struct RenderModalTool;
73
74impl RenderModalTool {
75    pub fn new() -> Self {
76        Self
77    }
78}
79
80impl Default for RenderModalTool {
81    fn default() -> Self {
82        Self::new()
83    }
84}
85
86#[async_trait]
87impl Tool for RenderModalTool {
88    fn name(&self) -> &str {
89        "render_modal"
90    }
91
92    fn description(&self) -> &str {
93        "Render a modal dialog overlay. Use for important messages, confirmations, or focused interactions that require user attention."
94    }
95
96    fn parameters_schema(&self) -> Option<Value> {
97        Some(super::generate_gemini_schema::<RenderModalParams>())
98    }
99
100    async fn execute(&self, _ctx: Arc<dyn ToolContext>, args: Value) -> Result<Value> {
101        let params: RenderModalParams = serde_json::from_value(args)
102            .map_err(|e| adk_core::AdkError::Tool(format!("Invalid parameters: {}", e)))?;
103        let protocol_options = params.protocol.clone();
104
105        let size = match params.size.as_str() {
106            "small" => ModalSize::Small,
107            "large" => ModalSize::Large,
108            "full" => ModalSize::Full,
109            _ => ModalSize::Medium,
110        };
111
112        // Build modal content
113        let content = vec![Component::Text(Text {
114            id: None,
115            content: params.message,
116            variant: TextVariant::Body,
117        })];
118
119        // Build footer with buttons if provided
120        let footer = if params.confirm_label.is_some() || params.cancel_label.is_some() {
121            let mut buttons = Vec::new();
122            if let Some(cancel) = params.cancel_label {
123                buttons.push(Component::Button(Button {
124                    id: None,
125                    label: cancel,
126                    action_id: params.cancel_action,
127                    variant: ButtonVariant::Secondary,
128                    disabled: false,
129                    icon: None,
130                }));
131            }
132            if let Some(confirm) = params.confirm_label {
133                buttons.push(Component::Button(Button {
134                    id: None,
135                    label: confirm,
136                    action_id: params.confirm_action,
137                    variant: ButtonVariant::Primary,
138                    disabled: false,
139                    icon: None,
140                }));
141            }
142            Some(buttons)
143        } else {
144            None
145        };
146
147        let ui = UiResponse::new(vec![Component::Modal(Modal {
148            id: None,
149            title: params.title,
150            content,
151            footer,
152            size,
153            closable: params.closable,
154        })]);
155
156        render_ui_response_with_protocol(ui, &protocol_options, "modal")
157    }
158}