1#[derive(Clone)]
2pub struct DialogOption {
3 pub value: String,
4 pub title: String,
5 pub description: Option<String>,
6 pub category: Option<String>,
7 pub footer: Option<String>,
8 pub disabled: bool,
9}
10
11impl DialogOption {
12 pub fn new(value: impl Into<String>, title: impl Into<String>) -> Self {
13 Self {
14 value: value.into(),
15 title: title.into(),
16 description: None,
17 category: None,
18 footer: None,
19 disabled: false,
20 }
21 }
22
23 pub fn with_description(mut self, d: impl Into<String>) -> Self {
24 self.description = Some(d.into());
25 self
26 }
27
28 pub fn with_category(mut self, c: impl Into<String>) -> Self {
29 self.category = Some(c.into());
30 self
31 }
32
33 pub fn with_footer(mut self, f: impl Into<String>) -> Self {
34 self.footer = Some(f.into());
35 self
36 }
37}
38
39#[derive(Debug, Clone, Copy, PartialEq, Eq)]
40pub enum PermissionChoice {
41 Once,
42 Always,
43 Reject,
44}
45
46pub enum ModalKind {
47 Permission {
48 tool_name: String,
49 description: String,
50 choice: PermissionChoice,
51 },
52 Select {
53 title: String,
54 query: String,
55 options: Vec<DialogOption>,
56 selected: usize,
57 current_value: Option<String>,
58 kind: SelectKind,
59 footer_hint: Option<String>,
60 },
61 Info {
62 title: String,
63 rows: Vec<(String, String)>,
64 },
65 Input {
66 title: String,
67 prompt: String,
68 buffer: String,
69 kind: InputKind,
70 },
71}
72
73#[derive(Debug, Clone, Copy, PartialEq, Eq)]
74pub enum InputKind {
75 SessionRename,
76 AskUserQuestion,
77}
78
79#[derive(Clone, Copy, PartialEq, Eq)]
80pub enum SelectKind {
81 CommandPalette,
82 ModelPicker,
83 SessionList,
84 Help,
85 SkillPicker,
86 FileMention,
87 ThemePicker,
88}
89
90pub struct ModalState {
91 pub active: Option<ModalKind>,
92}
93
94impl ModalState {
95 pub fn new() -> Self {
96 Self { active: None }
97 }
98
99 pub fn open_select(
100 &mut self,
101 kind: SelectKind,
102 title: impl Into<String>,
103 options: Vec<DialogOption>,
104 current_value: Option<String>,
105 footer_hint: Option<String>,
106 ) {
107 let selected = current_value
108 .as_ref()
109 .and_then(|cv| options.iter().position(|o| &o.value == cv))
110 .unwrap_or(0);
111 self.active = Some(ModalKind::Select {
112 title: title.into(),
113 query: String::new(),
114 options,
115 selected,
116 current_value,
117 kind,
118 footer_hint,
119 });
120 }
121
122 pub fn open_permission(&mut self, tool_name: impl Into<String>, description: impl Into<String>) {
123 self.active = Some(ModalKind::Permission {
124 tool_name: tool_name.into(),
125 description: description.into(),
126 choice: PermissionChoice::Once,
127 });
128 }
129
130 pub fn open_info(&mut self, title: impl Into<String>, rows: Vec<(String, String)>) {
131 self.active = Some(ModalKind::Info { title: title.into(), rows });
132 }
133
134 pub fn open_input(
135 &mut self,
136 title: impl Into<String>,
137 prompt: impl Into<String>,
138 initial: impl Into<String>,
139 kind: InputKind,
140 ) {
141 self.active = Some(ModalKind::Input {
142 title: title.into(),
143 prompt: prompt.into(),
144 buffer: initial.into(),
145 kind,
146 });
147 }
148
149 pub fn close(&mut self) {
150 self.active = None;
151 }
152}
153
154impl Default for ModalState {
155 fn default() -> Self {
156 Self::new()
157 }
158}
159
160pub fn filter_options(opts: &[DialogOption], query: &str) -> Vec<usize> {
161 let q = query.trim().to_lowercase();
162 if q.is_empty() {
163 return (0..opts.len()).collect();
164 }
165 opts.iter()
166 .enumerate()
167 .filter(|(_, o)| {
168 o.title.to_lowercase().contains(&q)
169 || o.description
170 .as_deref()
171 .map(|d| d.to_lowercase().contains(&q))
172 .unwrap_or(false)
173 || o.category
174 .as_deref()
175 .map(|c| c.to_lowercase().contains(&q))
176 .unwrap_or(false)
177 })
178 .map(|(i, _)| i)
179 .collect()
180}