1use async_trait::async_trait;
2use serde::{Deserialize, Serialize};
3
4#[async_trait]
7pub trait UserInterface: Send + Sync {
8 fn has_ui(&self) -> bool;
10
11 async fn notify(&self, message: &str, level: NotifyLevel);
13
14 async fn confirm(&self, title: &str, message: &str) -> Option<bool>;
16
17 async fn select(&self, title: &str, options: &[SelectOption]) -> Option<usize> {
19 self.select_with_context(title, "", options).await
20 }
21
22 async fn select_with_context(
24 &self,
25 title: &str,
26 context: &str,
27 options: &[SelectOption],
28 ) -> Option<usize>;
29
30 async fn multi_select_with_context(
32 &self,
33 title: &str,
34 context: &str,
35 options: &[SelectOption],
36 ) -> Option<Vec<usize>> {
37 self.select_with_context(title, context, options)
38 .await
39 .map(|index| vec![index])
40 }
41
42 async fn input(&self, title: &str, placeholder: &str) -> Option<String> {
44 self.input_with_context(title, "", placeholder).await
45 }
46
47 async fn input_with_context(
49 &self,
50 title: &str,
51 context: &str,
52 placeholder: &str,
53 ) -> Option<String>;
54
55 async fn set_status(&self, key: &str, text: Option<&str>);
57
58 async fn set_widget(&self, key: &str, content: Option<WidgetContent>);
60
61 async fn custom(&self, component: ComponentSpec) -> Option<serde_json::Value>;
63}
64
65#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
66pub enum NotifyLevel {
67 Info,
68 Warning,
69 Error,
70}
71
72#[derive(Debug, Clone, Serialize, Deserialize)]
73pub struct SelectOption {
74 pub label: String,
75 pub description: Option<String>,
76}
77
78#[derive(Debug, Clone, Serialize, Deserialize)]
79pub enum WidgetContent {
80 Lines(Vec<String>),
81 Component(ComponentSpec),
82}
83
84#[derive(Debug, Clone, Serialize, Deserialize)]
86pub struct ComponentSpec {
87 pub component_type: String,
88 pub props: serde_json::Value,
89 pub children: Vec<ComponentSpec>,
90}
91
92pub struct NullInterface;
94
95#[async_trait]
96impl UserInterface for NullInterface {
97 fn has_ui(&self) -> bool {
98 false
99 }
100 async fn notify(&self, _message: &str, _level: NotifyLevel) {}
101 async fn confirm(&self, _title: &str, _message: &str) -> Option<bool> {
102 None
103 }
104 async fn select_with_context(
105 &self,
106 _title: &str,
107 _context: &str,
108 _options: &[SelectOption],
109 ) -> Option<usize> {
110 None
111 }
112 async fn multi_select_with_context(
113 &self,
114 _title: &str,
115 _context: &str,
116 _options: &[SelectOption],
117 ) -> Option<Vec<usize>> {
118 None
119 }
120 async fn input_with_context(
121 &self,
122 _title: &str,
123 _context: &str,
124 _placeholder: &str,
125 ) -> Option<String> {
126 None
127 }
128 async fn set_status(&self, _key: &str, _text: Option<&str>) {}
129 async fn set_widget(&self, _key: &str, _content: Option<WidgetContent>) {}
130 async fn custom(&self, _component: ComponentSpec) -> Option<serde_json::Value> {
131 None
132 }
133}