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 input(&self, title: &str, placeholder: &str) -> Option<String> {
32 self.input_with_context(title, "", placeholder).await
33 }
34
35 async fn input_with_context(
37 &self,
38 title: &str,
39 context: &str,
40 placeholder: &str,
41 ) -> Option<String>;
42
43 async fn set_status(&self, key: &str, text: Option<&str>);
45
46 async fn set_widget(&self, key: &str, content: Option<WidgetContent>);
48
49 async fn custom(&self, component: ComponentSpec) -> Option<serde_json::Value>;
51}
52
53#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
54pub enum NotifyLevel {
55 Info,
56 Warning,
57 Error,
58}
59
60#[derive(Debug, Clone, Serialize, Deserialize)]
61pub struct SelectOption {
62 pub label: String,
63 pub description: Option<String>,
64}
65
66#[derive(Debug, Clone, Serialize, Deserialize)]
67pub enum WidgetContent {
68 Lines(Vec<String>),
69 Component(ComponentSpec),
70}
71
72#[derive(Debug, Clone, Serialize, Deserialize)]
74pub struct ComponentSpec {
75 pub component_type: String,
76 pub props: serde_json::Value,
77 pub children: Vec<ComponentSpec>,
78}
79
80pub struct NullInterface;
82
83#[async_trait]
84impl UserInterface for NullInterface {
85 fn has_ui(&self) -> bool {
86 false
87 }
88 async fn notify(&self, _message: &str, _level: NotifyLevel) {}
89 async fn confirm(&self, _title: &str, _message: &str) -> Option<bool> {
90 None
91 }
92 async fn select_with_context(
93 &self,
94 _title: &str,
95 _context: &str,
96 _options: &[SelectOption],
97 ) -> Option<usize> {
98 None
99 }
100 async fn input_with_context(
101 &self,
102 _title: &str,
103 _context: &str,
104 _placeholder: &str,
105 ) -> Option<String> {
106 None
107 }
108 async fn set_status(&self, _key: &str, _text: Option<&str>) {}
109 async fn set_widget(&self, _key: &str, _content: Option<WidgetContent>) {}
110 async fn custom(&self, _component: ComponentSpec) -> Option<serde_json::Value> {
111 None
112 }
113}