use crate::compat::{Result, Tool, ToolContext};
use crate::schema::*;
use crate::tools::{LegacyProtocolOptions, render_ui_response_with_protocol};
use async_trait::async_trait;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::sync::Arc;
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct RenderModalParams {
pub title: String,
pub message: String,
#[serde(default = "default_size")]
pub size: String,
#[serde(default = "default_true")]
pub closable: bool,
pub confirm_label: Option<String>,
pub cancel_label: Option<String>,
#[serde(default = "default_confirm_action")]
pub confirm_action: String,
#[serde(default = "default_cancel_action")]
pub cancel_action: String,
#[serde(flatten)]
pub protocol: LegacyProtocolOptions,
}
fn default_size() -> String {
"medium".to_string()
}
fn default_true() -> bool {
true
}
fn default_confirm_action() -> String {
"modal_confirm".to_string()
}
fn default_cancel_action() -> String {
"modal_cancel".to_string()
}
pub struct RenderModalTool;
impl RenderModalTool {
pub fn new() -> Self {
Self
}
}
impl Default for RenderModalTool {
fn default() -> Self {
Self::new()
}
}
#[async_trait]
impl Tool for RenderModalTool {
fn name(&self) -> &str {
"render_modal"
}
fn description(&self) -> &str {
"Render a modal dialog overlay. Use for important messages, confirmations, or focused interactions that require user attention."
}
fn parameters_schema(&self) -> Option<Value> {
Some(super::generate_gemini_schema::<RenderModalParams>())
}
async fn execute(&self, _ctx: Arc<dyn ToolContext>, args: Value) -> Result<Value> {
let params: RenderModalParams = serde_json::from_value(args)
.map_err(|e| crate::compat::AdkError::tool(format!("Invalid parameters: {}", e)))?;
let protocol_options = params.protocol.clone();
let size = match params.size.as_str() {
"small" => ModalSize::Small,
"large" => ModalSize::Large,
"full" => ModalSize::Full,
_ => ModalSize::Medium,
};
let content = vec![Component::Text(Text {
id: None,
content: params.message,
variant: TextVariant::Body,
})];
let footer = if params.confirm_label.is_some() || params.cancel_label.is_some() {
let mut buttons = Vec::new();
if let Some(cancel) = params.cancel_label {
buttons.push(Component::Button(Button {
id: None,
label: cancel,
action_id: params.cancel_action,
variant: ButtonVariant::Secondary,
disabled: false,
icon: None,
}));
}
if let Some(confirm) = params.confirm_label {
buttons.push(Component::Button(Button {
id: None,
label: confirm,
action_id: params.confirm_action,
variant: ButtonVariant::Primary,
disabled: false,
icon: None,
}));
}
Some(buttons)
} else {
None
};
let ui = UiResponse::new(vec![Component::Modal(Modal {
id: None,
title: params.title,
content,
footer,
size,
closable: params.closable,
})]);
render_ui_response_with_protocol(ui, &protocol_options, "modal")
}
}