use std::collections::HashMap;
use std::io;
use crossterm::{
event::{self, Event, KeyCode},
execute,
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
};
use ratatui::{
Terminal,
backend::CrosstermBackend,
layout::{Constraint, Direction, Layout},
style::{Color, Style},
text::Line,
widgets::Paragraph,
};
use a2ui::core::catalog::function_api::{FunctionImplementation, ReturnType};
use a2ui::core::error::A2uiError;
use a2ui::core::message_processor::MessageProcessor;
use a2ui::core::model::data_context::DataContext;
use a2ui::tui::catalogs::basic::{build_basic_catalog, build_basic_registry};
struct GreetFunction;
impl FunctionImplementation for GreetFunction {
fn name(&self) -> &'static str {
"greet"
}
fn return_type(&self) -> ReturnType {
ReturnType::String
}
fn execute(
&self,
args: &HashMap<String, serde_json::Value>,
_context: &DataContext,
) -> Result<serde_json::Value, A2uiError> {
let name = args
.get("name")
.and_then(|v| v.as_str())
.unwrap_or("World");
let emoji = args
.get("emoji")
.and_then(|v| v.as_str())
.unwrap_or("👋");
let greeting = format!("{} Hello, {}!", emoji, name);
Ok(serde_json::Value::String(greeting))
}
}
struct UpperFunction;
impl FunctionImplementation for UpperFunction {
fn name(&self) -> &'static str {
"upper"
}
fn return_type(&self) -> ReturnType {
ReturnType::String
}
fn execute(
&self,
args: &HashMap<String, serde_json::Value>,
_context: &DataContext,
) -> Result<serde_json::Value, A2uiError> {
let value = args
.get("value")
.and_then(|v| v.as_str())
.unwrap_or("");
Ok(serde_json::Value::String(value.to_uppercase()))
}
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let registry = build_basic_registry();
let catalog = build_basic_catalog()
.with_function(Box::new(GreetFunction))
.with_function(Box::new(UpperFunction));
let render_catalog = build_basic_catalog()
.with_function(Box::new(GreetFunction))
.with_function(Box::new(UpperFunction));
let mut processor = MessageProcessor::new(vec![catalog]);
let create_msg = serde_json::json!({
"version": "v1.0",
"createSurface": {
"surfaceId": "custom",
"catalogId": "https://a2ui.org/specification/v1_0/catalogs/basic/catalog.json",
"dataModel": {
"user": "A2UI Developer"
}
}
});
processor.process_message(MessageProcessor::parse_message(&create_msg.to_string())?)?;
let update_msg = serde_json::json!({
"version": "v1.0",
"updateComponents": {
"surfaceId": "custom",
"components": [
{
"id": "root",
"component": "Column",
"children": ["title", "greeting", "divider", "upper_demo", "help"],
"justify": "center",
"align": "center"
},
{
"id": "title",
"component": "Text",
"text": "Custom Functions",
"variant": "h1"
},
{
"id": "greeting",
"component": "Text",
"text": {
"call": "greet",
"args": {
"name": {"path": "/user"},
"emoji": "🚀"
}
},
"variant": "h2"
},
{
"id": "divider",
"component": "Divider",
"axis": "horizontal"
},
{
"id": "upper_demo",
"component": "Text",
"text": {
"call": "upper",
"args": {
"value": {"path": "/user"}
}
},
"variant": "body"
},
{
"id": "help",
"component": "Text",
"text": "n: cycle name q: quit",
"variant": "caption"
}
]
}
});
processor.process_message(MessageProcessor::parse_message(&update_msg.to_string())?)?;
enable_raw_mode()?;
let mut stdout = io::stderr();
execute!(stdout, EnterAlternateScreen)?;
let backend = CrosstermBackend::new(io::stderr());
let mut terminal = Terminal::new(backend)?;
let names = ["A2UI Developer", "Rustacean", "Terminal Hacker", "Claude"];
let mut idx = 0;
loop {
terminal.draw(|frame| {
let area = frame.area();
let chunks = Layout::default()
.direction(Direction::Vertical)
.constraints([Constraint::Min(5), Constraint::Length(2)])
.split(area);
if let Some(surface) = processor.model.get_surface("custom") {
let renderer = a2ui::tui::surface::SurfaceRenderer::new(
surface, ®istry, &render_catalog,
);
renderer.render(frame, chunks[0], None);
}
let bar = Paragraph::new(Line::from(format!(
" Data: user={:?} | n: cycle name q: quit ",
names[idx]
)))
.style(Style::default().fg(Color::DarkGray));
frame.render_widget(bar, chunks[1]);
})?;
if event::poll(std::time::Duration::from_millis(100))? {
if let Event::Key(key) = event::read()? {
match key.code {
KeyCode::Char('q') => break,
KeyCode::Char('n') => {
idx = (idx + 1) % names.len();
let msg = serde_json::json!({
"version": "v1.0",
"updateDataModel": {
"surfaceId": "custom",
"path": "/user",
"value": names[idx]
}
});
let _ = processor.process_message(
MessageProcessor::parse_message(&msg.to_string()).unwrap(),
);
}
_ => {}
}
}
}
}
disable_raw_mode()?;
execute!(stdout, LeaveAlternateScreen)?;
Ok(())
}